Skip to content

Commit dcf63d1

Browse files
feature: expose mannequin poses and provide pose argument
1 parent 6da8af7 commit dcf63d1

File tree

8 files changed

+133
-4
lines changed

8 files changed

+133
-4
lines changed

paper-api/src/main/java/io/papermc/paper/InternalAPIBridge.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import io.papermc.paper.datacomponent.item.ResolvableProfile;
66
import io.papermc.paper.world.damagesource.CombatEntry;
77
import io.papermc.paper.world.damagesource.FallLocationType;
8+
import java.util.Set;
89
import java.util.function.Function;
910
import java.util.function.Predicate;
1011
import net.kyori.adventure.text.Component;
@@ -14,6 +15,7 @@
1415
import org.bukkit.damage.DamageEffect;
1516
import org.bukkit.damage.DamageSource;
1617
import org.bukkit.entity.LivingEntity;
18+
import org.bukkit.entity.Pose;
1719
import org.jetbrains.annotations.ApiStatus;
1820
import org.jetbrains.annotations.Contract;
1921
import org.jspecify.annotations.NullMarked;
@@ -100,4 +102,6 @@ class Holder {
100102
Component defaultMannequinDescription();
101103

102104
<MODERN, LEGACY> GameRule<LEGACY> legacyGameRuleBridge(GameRule<MODERN> rule, Function<LEGACY, MODERN> fromLegacyToModern, Function<MODERN, LEGACY> toLegacyFromModern, Class<LEGACY> legacyClass);
105+
106+
Set<Pose> validMannequinPoses();
103107
}

paper-api/src/main/java/io/papermc/paper/command/brigadier/argument/ArgumentTypes.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import org.bukkit.block.BlockState;
3333
import org.bukkit.block.structure.Mirror;
3434
import org.bukkit.block.structure.StructureRotation;
35+
import org.bukkit.entity.Pose;
3536
import org.bukkit.inventory.ItemStack;
3637
import org.bukkit.scoreboard.Criteria;
3738
import org.bukkit.scoreboard.DisplaySlot;
@@ -452,6 +453,26 @@ public static <T> ArgumentType<TypedKey<T>> resourceKey(final RegistryKey<T> reg
452453
return provider().resourceKey(registryKey);
453454
}
454455

456+
/**
457+
* An argument for getting a pose
458+
* Keep in mind that a mannequin has only a subset of poses.
459+
* Use {@link #mannequin()} for mannequin-specific poses.
460+
*
461+
* @return argument
462+
*/
463+
public static ArgumentType<Pose> pose() {
464+
return provider().pose();
465+
}
466+
467+
/**
468+
* An argument for getting a mannequin-specific pose
469+
*
470+
* @return argument
471+
*/
472+
public static ArgumentType<Pose> mannequin() {
473+
return provider().mannequin();
474+
}
475+
455476
private ArgumentTypes() {
456477
}
457478
}

paper-api/src/main/java/io/papermc/paper/command/brigadier/argument/VanillaArgumentProvider.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import org.bukkit.block.BlockState;
3333
import org.bukkit.block.structure.Mirror;
3434
import org.bukkit.block.structure.StructureRotation;
35+
import org.bukkit.entity.Pose;
3536
import org.bukkit.inventory.ItemStack;
3637
import org.bukkit.scoreboard.Criteria;
3738
import org.bukkit.scoreboard.DisplaySlot;
@@ -120,4 +121,8 @@ static VanillaArgumentProvider provider() {
120121
<T> ArgumentType<TypedKey<T>> resourceKey(RegistryKey<T> registryKey);
121122

122123
<T> ArgumentType<T> resource(RegistryKey<T> registryKey);
124+
125+
ArgumentType<Pose> pose();
126+
127+
ArgumentType<Pose> mannequin();
123128
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package io.papermc.paper.command.brigadier.argument.pose;
2+
3+
import com.mojang.brigadier.arguments.ArgumentType;
4+
import com.mojang.brigadier.arguments.StringArgumentType;
5+
import com.mojang.brigadier.context.CommandContext;
6+
import com.mojang.brigadier.exceptions.CommandSyntaxException;
7+
import com.mojang.brigadier.exceptions.DynamicCommandExceptionType;
8+
import com.mojang.brigadier.suggestion.Suggestions;
9+
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
10+
import io.papermc.paper.command.brigadier.MessageComponentSerializer;
11+
import io.papermc.paper.command.brigadier.argument.CustomArgumentType;
12+
import java.util.Arrays;
13+
import java.util.HashSet;
14+
import java.util.Locale;
15+
import java.util.Set;
16+
import java.util.concurrent.CompletableFuture;
17+
import net.kyori.adventure.text.Component;
18+
import org.bukkit.entity.Mannequin;
19+
import org.bukkit.entity.Pose;
20+
21+
public class PoseArgument implements CustomArgumentType.Converted<Pose, String> {
22+
23+
private final Set<Pose> validPoses;
24+
25+
private PoseArgument(final Set<Pose> poses) {
26+
validPoses = poses;
27+
}
28+
29+
public static PoseArgument pose() {
30+
return new PoseArgument(new HashSet<>(Arrays.asList(Pose.values())));
31+
}
32+
33+
public static PoseArgument mannequin() {
34+
return new PoseArgument(Mannequin.validPoses());
35+
}
36+
37+
private static final DynamicCommandExceptionType ERROR_INVALID = new DynamicCommandExceptionType(value -> {
38+
return MessageComponentSerializer.message().serialize(Component.text(value + " is not a valid pose!"));
39+
});
40+
41+
@Override
42+
public ArgumentType<String> getNativeType() {
43+
return StringArgumentType.word();
44+
}
45+
46+
@Override
47+
public Pose convert(String nativeType) throws CommandSyntaxException {
48+
try {
49+
return Pose.valueOf(nativeType.toUpperCase(Locale.ROOT));
50+
} catch (IllegalArgumentException ignored) {
51+
throw ERROR_INVALID.create(nativeType);
52+
}
53+
}
54+
55+
@Override
56+
public <S> CompletableFuture<Suggestions> listSuggestions(CommandContext<S> context, SuggestionsBuilder builder) {
57+
for (Pose pose : this.validPoses) {
58+
String name = pose.toString().toLowerCase(Locale.ROOT);
59+
60+
if (name.startsWith(builder.getRemainingLowerCase())) {
61+
builder.suggest(pose.toString());
62+
}
63+
}
64+
65+
return builder.buildFuture();
66+
}
67+
}

paper-api/src/main/java/org/bukkit/entity/Mannequin.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import com.destroystokyo.paper.SkinParts;
44
import io.papermc.paper.InternalAPIBridge;
55
import io.papermc.paper.datacomponent.item.ResolvableProfile;
6+
import java.util.Set;
67
import net.kyori.adventure.text.Component;
78
import org.bukkit.inventory.EntityEquipment;
89
import org.bukkit.inventory.MainHand;
@@ -12,6 +13,15 @@
1213
@NullMarked
1314
public interface Mannequin extends LivingEntity {
1415

16+
/**
17+
* Returns the valid poses for a mannequin.
18+
*
19+
* @return the valid poses
20+
*/
21+
static Set<Pose> validPoses() {
22+
return InternalAPIBridge.get().validMannequinPoses();
23+
}
24+
1525
/**
1626
* Returns the default mannequin profile.
1727
*

paper-server/src/main/java/io/papermc/paper/PaperServerInternalAPIBridge.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@
1010
import io.papermc.paper.world.damagesource.FallLocationType;
1111
import io.papermc.paper.world.damagesource.PaperCombatEntryWrapper;
1212
import io.papermc.paper.world.damagesource.PaperCombatTrackerWrapper;
13+
import java.util.Set;
1314
import java.util.function.Function;
1415
import java.util.function.Predicate;
16+
import java.util.stream.Collectors;
1517
import net.kyori.adventure.text.Component;
1618
import net.minecraft.Optionull;
1719
import net.minecraft.commands.Commands;
@@ -27,13 +29,17 @@
2729
import org.bukkit.damage.DamageEffect;
2830
import org.bukkit.damage.DamageSource;
2931
import org.bukkit.entity.LivingEntity;
32+
import org.bukkit.entity.Pose;
3033
import org.jspecify.annotations.NullMarked;
3134
import org.jspecify.annotations.Nullable;
3235

3336
@NullMarked
3437
public class PaperServerInternalAPIBridge implements InternalAPIBridge {
3538
public static final PaperServerInternalAPIBridge INSTANCE = new PaperServerInternalAPIBridge();
3639

40+
private static final Set<Pose> validMannequinPoses = Mannequin.VALID_POSES.stream()
41+
.map(pose -> Pose.values()[pose.ordinal()]).collect(Collectors.toSet());
42+
3743
@Override
3844
public DamageEffect getDamageEffect(final String key) {
3945
return CraftDamageEffect.getById(key);
@@ -116,4 +122,9 @@ public Component defaultMannequinDescription() {
116122
public <MODERN, LEGACY> GameRule<LEGACY> legacyGameRuleBridge(GameRule<MODERN> rule, Function<LEGACY, MODERN> fromLegacyToModern, Function<MODERN, LEGACY> toLegacyFromModern, Class<LEGACY> legacyClass) {
117123
return CraftGameRule.wrap(rule, fromLegacyToModern, toLegacyFromModern, legacyClass);
118124
}
125+
126+
@Override
127+
public Set<Pose> validMannequinPoses() {
128+
return validMannequinPoses;
129+
}
119130
}

paper-server/src/main/java/io/papermc/paper/command/brigadier/argument/VanillaArgumentProviderImpl.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
1414
import io.papermc.paper.adventure.PaperAdventure;
1515
import io.papermc.paper.command.brigadier.PaperCommands;
16+
import io.papermc.paper.command.brigadier.argument.pose.PoseArgument;
1617
import io.papermc.paper.command.brigadier.argument.predicate.BlockInWorldPredicate;
1718
import io.papermc.paper.command.brigadier.argument.predicate.ItemStackPredicate;
1819
import io.papermc.paper.command.brigadier.argument.range.DoubleRangeProvider;
@@ -114,6 +115,7 @@
114115
import org.bukkit.craftbukkit.scoreboard.CraftScoreboardTranslations;
115116
import org.bukkit.craftbukkit.util.CraftLocation;
116117
import org.bukkit.craftbukkit.util.CraftNamespacedKey;
118+
import org.bukkit.entity.Pose;
117119
import org.bukkit.inventory.ItemStack;
118120
import org.bukkit.scoreboard.Criteria;
119121
import org.bukkit.scoreboard.DisplaySlot;
@@ -416,6 +418,16 @@ public <T> ArgumentType<T> resource(final RegistryKey<T> registryKey) {
416418
return this.resourceRaw(registryKey);
417419
}
418420

421+
@Override
422+
public ArgumentType<Pose> pose() {
423+
return PoseArgument.pose();
424+
}
425+
426+
@Override
427+
public ArgumentType<Pose> mannequin() {
428+
return PoseArgument.mannequin();
429+
}
430+
419431
@SuppressWarnings({"unchecked", "rawtypes", "UnnecessaryLocalVariable"})
420432
private <T, K extends Keyed> ArgumentType<T> resourceRaw(final RegistryKey registryKeyRaw) { // TODO remove Keyed
421433
final RegistryKey<K> registryKey = registryKeyRaw;

paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftMannequin.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,14 @@ public net.minecraft.world.entity.decoration.Mannequin getHandle() {
3131
@Override
3232
public void setPose(Pose pose, boolean fixed) {
3333
Preconditions.checkArgument(pose != null, "pose cannot be null");
34-
net.minecraft.world.entity.Pose internalPose = net.minecraft.world.entity.Pose.values()[pose.ordinal()];
35-
if (!net.minecraft.world.entity.decoration.Mannequin.VALID_POSES.contains(internalPose)) {
34+
if (!Mannequin.validPoses().contains(pose)) {
3635
throw new IllegalArgumentException("Invalid pose '%s', expected one of: %s".formatted(
3736
pose.name(),
38-
net.minecraft.world.entity.decoration.Mannequin.VALID_POSES.stream().map(p -> Pose.values()[p.ordinal()]).toList() // name doesn't match
37+
Mannequin.validPoses().stream().toList() // name doesn't match
3938
));
4039
}
4140

42-
this.setPose0(internalPose, fixed);
41+
this.setPose0(net.minecraft.world.entity.Pose.values()[pose.ordinal()], fixed);
4342
}
4443

4544
@Override

0 commit comments

Comments
 (0)