diff --git a/src/main/java/com/comphenix/protocol/events/PacketContainer.java b/src/main/java/com/comphenix/protocol/events/PacketContainer.java index 1843df8a..8c956472 100644 --- a/src/main/java/com/comphenix/protocol/events/PacketContainer.java +++ b/src/main/java/com/comphenix/protocol/events/PacketContainer.java @@ -732,7 +732,7 @@ public class PacketContainer implements Serializable { EnumWrappers.getDifficultyClass(), EnumWrappers.getDifficultyConverter()); } - + /** * Retrieve a read/write structure for the EntityUse enum in 1.7.2. * @return A modifier for EntityUse enum fields. @@ -744,6 +744,17 @@ public class PacketContainer implements Serializable { EnumWrappers.getEntityUseActionConverter()); } + /** + * Retrieves a read/write structure for the EntityUseAction class in the UseEntity packet sent by the client for + * 1.17 and above. + * @return A modifier for EntityUseAction class fields. + */ + public StructureModifier getEnumEntityUseActions() { + return structureModifier.withType( + MinecraftReflection.getEnumEntityUseActionClass(), + WrappedEnumEntityUseAction.CONVERTER); + } + /** * Retrieve a read/write structure for the NativeGameMode enum in 1.7.2. * @return A modifier for NativeGameMode enum fields. @@ -916,7 +927,7 @@ public class PacketContainer implements Serializable { public StructureModifier getHands() { return structureModifier.withType( EnumWrappers.getHandClass(), - EnumWrappers.getHandConverter()); + EnumWrappers.getHandConverter()); } /** diff --git a/src/main/java/com/comphenix/protocol/reflect/accessors/Accessors.java b/src/main/java/com/comphenix/protocol/reflect/accessors/Accessors.java index b950593c..059ae17d 100644 --- a/src/main/java/com/comphenix/protocol/reflect/accessors/Accessors.java +++ b/src/main/java/com/comphenix/protocol/reflect/accessors/Accessors.java @@ -289,6 +289,7 @@ public final class Accessors { * @return The method accessor. */ public static ConstructorAccessor getConstructorAccessor(final Constructor constructor) { + constructor.setAccessible(true); // let us in even if we are not allowed to return new DefaultConstrutorAccessor(constructor); } diff --git a/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java b/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java index 6245ce00..dc7ce8ef 100644 --- a/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java +++ b/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java @@ -27,10 +27,8 @@ import java.lang.reflect.Modifier; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.net.InetAddress; -import java.net.ServerSocket; import java.util.Collection; import java.util.HashSet; -import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentMap; @@ -57,6 +55,7 @@ import com.comphenix.protocol.reflect.ClassAnalyser; import com.comphenix.protocol.reflect.ClassAnalyser.AsmMethod; import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.reflect.accessors.Accessors; +import com.comphenix.protocol.reflect.accessors.FieldAccessor; import com.comphenix.protocol.reflect.accessors.MethodAccessor; import com.comphenix.protocol.reflect.fuzzy.AbstractFuzzyMatcher; import com.comphenix.protocol.reflect.fuzzy.FuzzyClassContract; @@ -65,6 +64,7 @@ import com.comphenix.protocol.reflect.fuzzy.FuzzyMatchers; import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract; import com.comphenix.protocol.utility.RemappedClassSource.RemapperUnavailableException; import com.comphenix.protocol.utility.RemappedClassSource.RemapperUnavailableException.Reason; +import com.comphenix.protocol.wrappers.EnumWrappers; import com.comphenix.protocol.wrappers.nbt.NbtFactory; import com.comphenix.protocol.wrappers.nbt.NbtType; import com.google.common.base.Joiner; @@ -1793,6 +1793,52 @@ public class MinecraftReflection { "PacketPlayOutPlayerInfo$PlayerInfoData", "PlayerInfoData"); } + /** + * Retrieves the entity use action class in 1.17. + * @return The EntityUseAction class + */ + public static Class getEnumEntityUseActionClass() { + Class packetClass = PacketType.Play.Client.USE_ENTITY.getPacketClass(); + return FuzzyReflection.fromClass(packetClass, true).getFieldByType("^.*(EnumEntityUseAction)").getType(); + } + + /** + * Get a method accessor to get the actual use action out of the wrapping EnumEntityUseAction in 1.17. + * @return a method accessor to get the actual use action + */ + public static MethodAccessor getEntityUseActionEnumMethodAccessor() { + FuzzyReflection fuzzy = FuzzyReflection.fromClass(MinecraftReflection.getEnumEntityUseActionClass(), true); + return Accessors.getMethodAccessor(fuzzy.getMethod(FuzzyMethodContract.newBuilder() + .returnTypeExact(EnumWrappers.getEntityUseActionClass()) + .build())); + } + + /** + * Get a field accessor for the hand in the wrapping EnumEntityUseAction in 1.17. + * + * @param enumEntityUseAction the object instance of the action, the field is not present in attack. + * @return a field accessor for the hand in the wrapping EnumEntityUseAction + */ + public static FieldAccessor getHandEntityUseActionEnumFieldAccessor(Object enumEntityUseAction) { + FuzzyReflection fuzzy = FuzzyReflection.fromObject(enumEntityUseAction, true); + return Accessors.getFieldAccessor(fuzzy.getField(FuzzyFieldContract.newBuilder() + .typeExact(EnumWrappers.getHandClass()) + .build())); + } + + /** + * Get a field accessor for the vec3d in the wrapping EnumEntityUseAction in 1.17. + * + * @param enumEntityUseAction the object instance of the action, the field is not present in attack. + * @return a field accessor for the hand in the wrapping EnumEntityUseAction + */ + public static FieldAccessor getVec3EntityUseActionEnumFieldAccessor(Object enumEntityUseAction) { + FuzzyReflection fuzzy = FuzzyReflection.fromObject(enumEntityUseAction, true); + return Accessors.getFieldAccessor(fuzzy.getField(FuzzyFieldContract.newBuilder() + .typeExact(MinecraftReflection.getVec3DClass()) + .build())); + } + /** * Determine if the given object is a PlayerInfoData. * @param obj - the given object. diff --git a/src/main/java/com/comphenix/protocol/wrappers/EnumWrappers.java b/src/main/java/com/comphenix/protocol/wrappers/EnumWrappers.java index 0c808a3c..6d921422 100644 --- a/src/main/java/com/comphenix/protocol/wrappers/EnumWrappers.java +++ b/src/main/java/com/comphenix/protocol/wrappers/EnumWrappers.java @@ -10,6 +10,7 @@ import com.comphenix.protocol.ProtocolLogger; import com.comphenix.protocol.reflect.EquivalentConverter; import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.reflect.accessors.Accessors; +import com.comphenix.protocol.utility.MinecraftVersion; import com.comphenix.protocol.utility.MinecraftReflection; import com.google.common.collect.Maps; @@ -481,7 +482,6 @@ public abstract class EnumWrappers { DIFFICULTY_CLASS = getEnum(PacketType.Play.Server.LOGIN.getPacketClass(), 1); } - ENTITY_USE_ACTION_CLASS = getEnum(PacketType.Play.Client.USE_ENTITY.getPacketClass(), 0); GAMEMODE_CLASS = getEnum(PacketType.Play.Server.LOGIN.getPacketClass(), 0); RESOURCE_PACK_STATUS_CLASS = getEnum(PacketType.Play.Client.RESOURCE_PACK_STATUS.getPacketClass(), 0); PLAYER_INFO_ACTION_CLASS = getEnum(PacketType.Play.Server.PLAYER_INFO.getPacketClass(), 0); @@ -501,7 +501,16 @@ public abstract class EnumWrappers { ITEM_SLOT_CLASS = getEnum(PacketType.Play.Server.ENTITY_EQUIPMENT.getPacketClass(), 0); } - HAND_CLASS = getEnum(PacketType.Play.Client.USE_ENTITY.getPacketClass(), 1); + // In 1.17 the hand and use action class is no longer a field in the packet + if (MinecraftVersion.CAVES_CLIFFS_1.atOrAbove()) { + HAND_CLASS = MinecraftReflection.getMinecraftClass("world.EnumHand"); + // class is named 'b' in the packet but class order differs in spigot and paper so we can only use the first method's return type (safest way) + ENTITY_USE_ACTION_CLASS = MinecraftReflection.getEnumEntityUseActionClass().getMethods()[0].getReturnType(); + } else { + HAND_CLASS = getEnum(PacketType.Play.Client.USE_ENTITY.getPacketClass(), 1); + ENTITY_USE_ACTION_CLASS = getEnum(PacketType.Play.Client.USE_ENTITY.getPacketClass(), 0); + } + DIRECTION_CLASS = getEnum(PacketType.Play.Server.SPAWN_ENTITY_PAINTING.getPacketClass(), 0); CHAT_TYPE_CLASS = getEnum(PacketType.Play.Server.CHAT.getPacketClass(), 0); ENTITY_POSE_CLASS = MinecraftReflection.getNullableNMS("world.entity.EntityPose", "EntityPose"); @@ -510,7 +519,6 @@ public abstract class EnumWrappers { associate(CLIENT_COMMAND_CLASS, ClientCommand.class, getClientCommandConverter()); associate(CHAT_VISIBILITY_CLASS, ChatVisibility.class, getChatVisibilityConverter()); associate(DIFFICULTY_CLASS, Difficulty.class, getDifficultyConverter()); - associate(ENTITY_USE_ACTION_CLASS, EntityUseAction.class, getEntityUseActionConverter()); associate(GAMEMODE_CLASS, NativeGameMode.class, getGameModeConverter()); associate(RESOURCE_PACK_STATUS_CLASS, ResourcePackStatus.class, getResourcePackStatusConverter()); associate(PLAYER_INFO_ACTION_CLASS, PlayerInfoAction.class, getPlayerInfoActionConverter()); @@ -523,15 +531,14 @@ public abstract class EnumWrappers { associate(PARTICLE_CLASS, Particle.class, getParticleConverter()); associate(SOUND_CATEGORY_CLASS, SoundCategory.class, getSoundCategoryConverter()); associate(ITEM_SLOT_CLASS, ItemSlot.class, getItemSlotConverter()); - associate(HAND_CLASS, Hand.class, getHandConverter()); associate(DIRECTION_CLASS, Direction.class, getDirectionConverter()); associate(CHAT_TYPE_CLASS, ChatType.class, getChatTypeConverter()); - + associate(HAND_CLASS, Hand.class, getHandConverter()); + associate(ENTITY_USE_ACTION_CLASS, EntityUseAction.class, getEntityUseActionConverter()); + if (ENTITY_POSE_CLASS != null) { associate(ENTITY_POSE_CLASS, EntityPose.class, getEntityPoseConverter()); } - - INITIALIZED = true; } private static void associate(Class nativeClass, Class wrapperClass, EquivalentConverter converter) { diff --git a/src/main/java/com/comphenix/protocol/wrappers/WrappedEnumEntityUseAction.java b/src/main/java/com/comphenix/protocol/wrappers/WrappedEnumEntityUseAction.java new file mode 100644 index 00000000..b2b0f994 --- /dev/null +++ b/src/main/java/com/comphenix/protocol/wrappers/WrappedEnumEntityUseAction.java @@ -0,0 +1,200 @@ +package com.comphenix.protocol.wrappers; + +import java.lang.reflect.Modifier; +import java.util.Arrays; + +import com.comphenix.protocol.PacketType; +import com.comphenix.protocol.reflect.EquivalentConverter; +import com.comphenix.protocol.reflect.FuzzyReflection; +import com.comphenix.protocol.reflect.accessors.Accessors; +import com.comphenix.protocol.reflect.accessors.ConstructorAccessor; +import com.comphenix.protocol.reflect.accessors.FieldAccessor; +import com.comphenix.protocol.reflect.accessors.MethodAccessor; +import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract; +import com.comphenix.protocol.utility.MinecraftReflection; +import com.comphenix.protocol.wrappers.EnumWrappers.EntityUseAction; +import com.comphenix.protocol.wrappers.EnumWrappers.Hand; + +import org.bukkit.util.Vector; + +/** + * Represents an entity used action used in the UseEntity packet sent by the client. + * @author derklaro + */ +public class WrappedEnumEntityUseAction extends AbstractWrapper implements ClonableWrapper { + + public static final EquivalentConverter CONVERTER = Converters.handle(AbstractWrapper::getHandle, + WrappedEnumEntityUseAction::fromHandle, WrappedEnumEntityUseAction.class); + + private static final Class PACKET_CLASS = PacketType.Play.Client.USE_ENTITY.getPacketClass(); + private static final Class[] DECLARED_CLASSES = PACKET_CLASS.getDeclaredClasses(); + + private static final Class HANDLE_TYPE = MinecraftReflection.getEnumEntityUseActionClass(); + private static final MethodAccessor ACTION_USE = MinecraftReflection.getEntityUseActionEnumMethodAccessor(); + + private static final ConstructorAccessor INTERACT = useAction(EnumWrappers.getHandClass()); + private static final ConstructorAccessor INTERACT_AT = useAction(EnumWrappers.getHandClass(), + MinecraftReflection.getVec3DClass()); + + private static final Object ATTACK = Accessors.getFieldAccessor(FuzzyReflection.fromClass(PACKET_CLASS, true) + .getField(FuzzyFieldContract.newBuilder() + .requireModifier(Modifier.STATIC) + .typeExact(MinecraftReflection.getEnumEntityUseActionClass()) + .build()) + ).get(null); + private static final WrappedEnumEntityUseAction ATTACK_WRAPPER = new WrappedEnumEntityUseAction(ATTACK); + + private final EntityUseAction action; + // these fields are only available for interact & interact_at + private FieldAccessor handAccessor; + private FieldAccessor positionAccessor; + + /** + * Construct a new wrapper for the entity use action class in the UseEntity packet. + * @param handle - the NMS handle. + */ + private WrappedEnumEntityUseAction(Object handle) { + super(HANDLE_TYPE); + setHandle(handle); + + action = EnumWrappers.getEntityUseActionConverter().getSpecific(ACTION_USE.invoke(handle)); + } + + /** + * Finds a constructor of a declared class in the UseEntity class. Used to find the action class implementations. + * @param parameterTypes - the types the constructor of the class must have. + * @return a constructor for a matching class. + * @throws IllegalArgumentException if no constructor was found. + */ + private static ConstructorAccessor useAction(Class... parameterTypes) { + for (Class subClass : DECLARED_CLASSES) { + ConstructorAccessor accessor = Accessors.getConstructorAccessorOrNull(subClass, parameterTypes); + if (accessor != null) { + return accessor; + } + } + throw new IllegalArgumentException( + "No constructor with " + Arrays.toString(parameterTypes) + " in " + PACKET_CLASS); + } + + /** + * Construct a new wrapper for the entity use action class in the UseEntity packet. + * @param handle - the NMS handle. + * @return the created wrapper. + */ + public static WrappedEnumEntityUseAction fromHandle(Object handle) { + return new WrappedEnumEntityUseAction(handle); + } + + /** + * Get the jvm static action for attacking an entity. + * @return the action for an entity attack. + */ + public static WrappedEnumEntityUseAction attack() { + return ATTACK_WRAPPER; + } + + /** + * Get an action for interacting with an entity. + * @param hand - the hand used for the interact. + * @return the action for an interact. + */ + public static WrappedEnumEntityUseAction interact(Hand hand) { + Object handle = INTERACT.invoke(EnumWrappers.getHandConverter().getGeneric(hand)); + return new WrappedEnumEntityUseAction(handle); + } + + /** + * Get an action for interacting with an entity at a specific location. + * @param hand - the hand used for the interact. + * @param vector - the position of the interact. + * @return the action for an interact_at. + */ + public static WrappedEnumEntityUseAction interactAt(Hand hand, Vector vector) { + Object handle = INTERACT_AT.invoke(EnumWrappers.getHandConverter().getGeneric(hand), + BukkitConverters.getVectorConverter().getGeneric(vector)); + return new WrappedEnumEntityUseAction(handle); + } + + /** + * Get the action used for the interact. + * @return the interact action. + */ + public EntityUseAction getAction() { + return action; + } + + /** + * Get the hand used for the interact. Only available if this represents interact or interact_at. + * @return the hand used for the interact. + * @throws IllegalArgumentException if called for attack. + */ + public Hand getHand() { + return EnumWrappers.getHandConverter().getSpecific(getHandAccessor().get(handle)); + } + + /** + * Sets the hand used for the interact. + * @param hand the used hand. + * @throws IllegalArgumentException if called for attack. + */ + public void setHand(Hand hand) { + getHandAccessor().set(handle, EnumWrappers.getHandConverter().getGeneric(hand)); + } + + /** + * Get the position of the interact. Only available if this represents interact_at. + * @return the position of the interact. + * @throws IllegalArgumentException if called for attack or interact. + */ + public Vector getPosition() { + return BukkitConverters.getVectorConverter().getSpecific(getPositionAccessor().get(handle)); + } + + /** + * Sets the position of the interact. + * @param position the position. + * @throws IllegalArgumentException if called for attack or interact. + */ + public void setPosition(Vector position) { + getPositionAccessor().set(handle, BukkitConverters.getVectorConverter().getGeneric(position)); + } + + @Override + public WrappedEnumEntityUseAction deepClone() { + switch (action) { + case ATTACK: + return WrappedEnumEntityUseAction.attack(); + case INTERACT: + return WrappedEnumEntityUseAction.interact(getHand()); + case INTERACT_AT: + return WrappedEnumEntityUseAction.interactAt(getHand(), getPosition()); + default: + throw new IllegalArgumentException("Invalid EntityUseAction: " + action); + } + } + + /** + * Get a field accessor for the hand in the interact and interact_at type. + * @return a field accessor for the hand field. + * @throws IllegalArgumentException if called for attack. + */ + private FieldAccessor getHandAccessor() { + if (handAccessor == null) { + handAccessor = MinecraftReflection.getHandEntityUseActionEnumFieldAccessor(handle); + } + return handAccessor; + } + + /** + * Get a field accessor for the position in the interact_at type. + * @return a field accessor for the position field. + * @throws IllegalArgumentException if called for attack or interact. + */ + public FieldAccessor getPositionAccessor() { + if (positionAccessor == null) { + positionAccessor = MinecraftReflection.getVec3EntityUseActionEnumFieldAccessor(handle); + } + return positionAccessor; + } +} diff --git a/src/test/java/com/comphenix/protocol/events/PacketContainerTest.java b/src/test/java/com/comphenix/protocol/events/PacketContainerTest.java index 31a1d133..657a9bd0 100644 --- a/src/test/java/com/comphenix/protocol/events/PacketContainerTest.java +++ b/src/test/java/com/comphenix/protocol/events/PacketContainerTest.java @@ -16,6 +16,8 @@ */ package com.comphenix.protocol.events; +import com.comphenix.protocol.wrappers.EnumWrappers.EntityUseAction; +import com.comphenix.protocol.wrappers.EnumWrappers.Hand; import java.lang.reflect.Array; import java.lang.reflect.Field; import java.util.*; @@ -585,6 +587,51 @@ public class PacketContainerTest { assertTrue(packet.getGameStateIDs().read(0) == 2); } + @Test + public void testUseEntity() { + PacketContainer packet = new PacketContainer(PacketType.Play.Client.USE_ENTITY); + + WrappedEnumEntityUseAction action; + WrappedEnumEntityUseAction clone; + // test attack + packet.getEnumEntityUseActions().write(0, WrappedEnumEntityUseAction.attack()); + action = packet.getEnumEntityUseActions().read(0); + // attack's handle should always be the same + assertEquals(WrappedEnumEntityUseAction.attack(), action); + assertEquals(EntityUseAction.ATTACK, action.getAction()); + // hand & position should not be available + assertThrows(IllegalArgumentException.class, action::getHand); + assertThrows(IllegalArgumentException.class, action::getPosition); + // test cloning + clone = action.deepClone(); + assertSame(WrappedEnumEntityUseAction.attack(), clone); + + // test interact + packet.getEnumEntityUseActions().write(0, WrappedEnumEntityUseAction.interact(Hand.OFF_HAND)); + action = packet.getEnumEntityUseActions().read(0); + assertEquals(EntityUseAction.INTERACT, action.getAction()); + assertEquals(Hand.OFF_HAND, action.getHand()); + // position should not be available + assertThrows(IllegalArgumentException.class, action::getPosition); + // test cloning + clone = action.deepClone(); + assertEquals(EntityUseAction.INTERACT, clone.getAction()); + assertEquals(Hand.OFF_HAND, clone.getHand()); + + // test interact_at + Vector position = new Vector(1, 199, 4); + packet.getEnumEntityUseActions().write(0, WrappedEnumEntityUseAction.interactAt(Hand.MAIN_HAND, position)); + action = packet.getEnumEntityUseActions().read(0); + assertEquals(EntityUseAction.INTERACT_AT, action.getAction()); + assertEquals(Hand.MAIN_HAND, action.getHand()); + assertEquals(position, action.getPosition()); + // test cloning + clone = action.deepClone(); + assertEquals(EntityUseAction.INTERACT_AT, clone.getAction()); + assertEquals(Hand.MAIN_HAND, clone.getHand()); + assertEquals(position, clone.getPosition()); + } + /** * Actions from the outbound Boss packet. Used for testing generic enums. * @author dmulloy2 diff --git a/src/test/java/com/comphenix/protocol/utility/MinecraftReflectionTest.java b/src/test/java/com/comphenix/protocol/utility/MinecraftReflectionTest.java index 63a5d9af..62b72e74 100644 --- a/src/test/java/com/comphenix/protocol/utility/MinecraftReflectionTest.java +++ b/src/test/java/com/comphenix/protocol/utility/MinecraftReflectionTest.java @@ -150,4 +150,10 @@ public class MinecraftReflectionTest { public void testGameProfile() { assertEquals(GameProfile.class, MinecraftReflection.getGameProfileClass()); } + + @Test + public void testEnumEntityUseAction() { + // this class is package-private in PacketPlayInUseEntity, so we can only check if no exception is thrown during retrieval + MinecraftReflection.getEnumEntityUseActionClass(); + } } diff --git a/src/test/java/com/comphenix/protocol/wrappers/EnumWrappersTest.java b/src/test/java/com/comphenix/protocol/wrappers/EnumWrappersTest.java index f1d91cc3..40436484 100644 --- a/src/test/java/com/comphenix/protocol/wrappers/EnumWrappersTest.java +++ b/src/test/java/com/comphenix/protocol/wrappers/EnumWrappersTest.java @@ -12,12 +12,11 @@ import com.comphenix.protocol.reflect.EquivalentConverter; import com.comphenix.protocol.reflect.accessors.Accessors; import com.comphenix.protocol.reflect.accessors.FieldAccessor; -import java.util.ArrayList; -import java.util.HashSet; import java.util.Set; import net.minecraft.network.EnumProtocol; import net.minecraft.network.protocol.game.PacketPlayInClientCommand.EnumClientCommand; +import net.minecraft.world.EnumHand; import net.minecraft.world.EnumDifficulty; import net.minecraft.world.entity.player.EnumChatVisibility; import net.minecraft.world.level.EnumGamemode; @@ -28,15 +27,16 @@ public class EnumWrappersTest { public EnumClientCommand command; public EnumChatVisibility visibility; public EnumDifficulty difficulty; - // public EnumEntityUseAction action; + public EnumHand hand; + // public EnumEntityUseAction action; // moved to PacketPlayInUseEntity but is private public EnumGamemode mode; } - + @BeforeClass public static void initializeBukkit() { BukkitInitialization.initializePackage(); } - + @Test public void testEnum() { EnumClass obj = new EnumClass(); @@ -44,13 +44,15 @@ public class EnumWrappersTest { obj.command = EnumClientCommand.b; obj.visibility = EnumChatVisibility.c; obj.difficulty = EnumDifficulty.d; + obj.hand = EnumHand.b; // obj.action = EnumEntityUseAction.INTERACT; obj.mode = EnumGamemode.e; - + assertEquals(obj.protocol, roundtrip(obj, "protocol", EnumWrappers.getProtocolConverter()) ); assertEquals(obj.command, roundtrip(obj, "command", EnumWrappers.getClientCommandConverter()) ); assertEquals(obj.visibility, roundtrip(obj, "visibility", EnumWrappers.getChatVisibilityConverter()) ); assertEquals(obj.difficulty, roundtrip(obj, "difficulty", EnumWrappers.getDifficultyConverter()) ); + assertEquals(obj.hand, roundtrip(obj, "hand", EnumWrappers.getHandConverter()) ); // assertEquals(obj.action, roundtrip(obj, "action", EnumWrappers.getEntityUseActionConverter()) ); assertEquals(obj.mode, roundtrip(obj, "mode", EnumWrappers.getGameModeConverter()) ); } @@ -58,14 +60,14 @@ public class EnumWrappersTest { @SuppressWarnings("unchecked") public > T roundtrip(Object target, String fieldName, EquivalentConverter converter) { FieldAccessor accessor = Accessors.getFieldAccessor(target.getClass(), fieldName, true); - + return (T) converter.getGeneric( converter.getSpecific(accessor.get(target)) ); } private static final Set KNOWN_INVALID = Sets.newHashSet( - "Particle", "WorldBorderAction", "CombatEventType", "EntityUseAction", "TitleAction", "Hand" + "Particle", "WorldBorderAction", "CombatEventType", "TitleAction" ); @Test