diff --git a/src/main/java/com/comphenix/protocol/events/AbstractStructure.java b/src/main/java/com/comphenix/protocol/events/AbstractStructure.java index 6b3703152..0dbb210cd 100644 --- a/src/main/java/com/comphenix/protocol/events/AbstractStructure.java +++ b/src/main/java/com/comphenix/protocol/events/AbstractStructure.java @@ -28,7 +28,10 @@ import com.comphenix.protocol.PacketType; import com.comphenix.protocol.reflect.EquivalentConverter; +import com.comphenix.protocol.reflect.FieldAccessException; import com.comphenix.protocol.reflect.StructureModifier; +import com.comphenix.protocol.reflect.accessors.Accessors; +import com.comphenix.protocol.reflect.accessors.ConstructorAccessor; import com.comphenix.protocol.utility.MinecraftReflection; import com.comphenix.protocol.utility.MinecraftVersion; import com.comphenix.protocol.utility.StreamSerializer; @@ -664,6 +667,76 @@ public StructureModifier getEntityUseActions() { * @return A modifier for EntityUseAction class fields. */ public StructureModifier getEnumEntityUseActions() { + if (MinecraftVersion.v26_1.atOrAbove()) { + final Class packetClass = PacketType.Play.Client.USE_ENTITY.getPacketClass(); + final ConstructorAccessor constructor = Accessors.getConstructorAccessor( + packetClass, + int.class, + EnumWrappers.getHandClass(), + MinecraftReflection.getVec3DClass(), + boolean.class + ); + + return new StructureModifier() { + @Override + public WrappedEnumEntityUseAction read(int fieldIndex) { + if (fieldIndex != 0) { + throw FieldAccessException.fromFormat( + "Field index %d is out of bounds for length %s", + fieldIndex, + 1); + } + + return WrappedEnumEntityUseAction.fromHandle(handle); + } + + @Override + public WrappedEnumEntityUseAction readSafely(int fieldIndex) { + return fieldIndex == 0 && handle != null ? WrappedEnumEntityUseAction.fromHandle(handle) : null; + } + + @Override + public StructureModifier write(int fieldIndex, WrappedEnumEntityUseAction value) { + if (fieldIndex != 0) { + throw FieldAccessException.fromFormat( + "Field index %d is out of bounds for length %s", + fieldIndex, + 1); + } + + if (value == null) { + return this; + } + + if (handle == null) { + throw new IllegalStateException("Cannot write USE_ENTITY action without a packet handle"); + } + + StructureModifier current = new StructureModifier<>(packetClass).withTarget(handle); + int entityId = (Integer) current.withType(int.class).read(0); + boolean usingSecondaryAction = (Boolean) current.withType(boolean.class).read(0); + + Object genericHand = EnumWrappers.getHandConverter().getGeneric(value.getHand()); + Object genericVector = BukkitConverters.getVectorConverter().getGeneric(value.getPosition()); + + Object newHandle = constructor.invoke(entityId, genericHand, genericVector, usingSecondaryAction); + AbstractStructure.this.handle = newHandle; + AbstractStructure.this.structureModifier = AbstractStructure.this.structureModifier.withTarget(newHandle); + return this; + } + + @Override + public StructureModifier writeSafely(int fieldIndex, WrappedEnumEntityUseAction value) { + return write(fieldIndex, value); + } + + @Override + protected com.comphenix.protocol.reflect.accessors.FieldAccessor findFieldAccessor(int fieldIndex) { + return null; + } + }; + } + return structureModifier.withType( MinecraftReflection.getEnumEntityUseActionClass(), WrappedEnumEntityUseAction.CONVERTER); diff --git a/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java b/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java index 3486265fe..41fd2f3e6 100644 --- a/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java +++ b/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java @@ -1229,6 +1229,10 @@ static Class getOrInferMinecraftClass(String className, Supplier> su * @return The EntityUseAction class */ public static Class getEnumEntityUseActionClass() { + if (MinecraftVersion.v26_1.atOrAbove()) { + return PacketType.Play.Client.USE_ENTITY.getPacketClass(); + } + return getOrInferMinecraftClass("ServerboundInteractPacket.Action", () -> { Class packetClass = PacketType.Play.Client.USE_ENTITY.getPacketClass(); FuzzyReflection fuzzyReflection = FuzzyReflection.fromClass(packetClass, true); @@ -1255,10 +1259,18 @@ public static Class getEnumEntityUseActionClass() { * @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())); + if (MinecraftVersion.v26_1.atOrAbove()) { + return null; + } + + try { + FuzzyReflection fuzzy = FuzzyReflection.fromClass(MinecraftReflection.getEnumEntityUseActionClass(), true); + return Accessors.getMethodAccessor(fuzzy.getMethod(FuzzyMethodContract.newBuilder() + .returnTypeExact(EnumWrappers.getEntityUseActionClass()) + .build())); + } catch (IllegalArgumentException ex) { + return null; + } } /** diff --git a/src/main/java/com/comphenix/protocol/wrappers/WrappedEnumEntityUseAction.java b/src/main/java/com/comphenix/protocol/wrappers/WrappedEnumEntityUseAction.java index 728edd902..0dccba4cd 100644 --- a/src/main/java/com/comphenix/protocol/wrappers/WrappedEnumEntityUseAction.java +++ b/src/main/java/com/comphenix/protocol/wrappers/WrappedEnumEntityUseAction.java @@ -11,6 +11,7 @@ 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.MinecraftVersion; import com.comphenix.protocol.utility.MinecraftReflection; import com.comphenix.protocol.wrappers.EnumWrappers.EntityUseAction; import com.comphenix.protocol.wrappers.EnumWrappers.Hand; @@ -30,19 +31,21 @@ public class WrappedEnumEntityUseAction extends AbstractWrapper implements Clona 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 static final MethodAccessor ACTION_USE = getActionUseAccessor(); + + private static final ConstructorAccessor PACKET_CONSTRUCTOR = MinecraftVersion.v26_1.atOrAbove() + ? Accessors.getConstructorAccessor(PACKET_CLASS, int.class, EnumWrappers.getHandClass(), + MinecraftReflection.getVec3DClass(), boolean.class) + : null; + private static final ConstructorAccessor INTERACT = MinecraftVersion.v26_1.atOrAbove() + ? null + : useAction(EnumWrappers.getHandClass()); + private static final ConstructorAccessor INTERACT_AT = MinecraftVersion.v26_1.atOrAbove() + ? null + : useAction(EnumWrappers.getHandClass(), MinecraftReflection.getVec3DClass()); + + private static final Object ATTACK = initializeAttack(); + private static final WrappedEnumEntityUseAction ATTACK_WRAPPER = ATTACK != null ? new WrappedEnumEntityUseAction(ATTACK) : null; private final EntityUseAction action; // these fields are only available for interact & interact_at @@ -57,7 +60,32 @@ private WrappedEnumEntityUseAction(Object handle) { super(HANDLE_TYPE); setHandle(handle); - action = EnumWrappers.getEntityUseActionConverter().getSpecific(ACTION_USE.invoke(handle)); + action = resolveAction(); + } + + private static MethodAccessor getActionUseAccessor() { + return MinecraftReflection.getEntityUseActionEnumMethodAccessor(); + } + + private static Object initializeAttack() { + if (MinecraftVersion.v26_1.atOrAbove()) { + return null; + } + + return Accessors.getFieldAccessor(FuzzyReflection.fromClass(PACKET_CLASS, true) + .getField(FuzzyFieldContract.newBuilder() + .requireModifier(Modifier.STATIC) + .typeExact(MinecraftReflection.getEnumEntityUseActionClass()) + .build()) + ).get(null); + } + + private EntityUseAction resolveAction() { + if (ACTION_USE != null) { + return EnumWrappers.getEntityUseActionConverter().getSpecific(ACTION_USE.invoke(handle)); + } + + return EntityUseAction.INTERACT_AT; } /** @@ -91,6 +119,10 @@ public static WrappedEnumEntityUseAction fromHandle(Object handle) { * @return the action for an entity attack. */ public static WrappedEnumEntityUseAction attack() { + if (ATTACK_WRAPPER == null) { + throw new UnsupportedOperationException("Attack is no longer part of the USE_ENTITY packet on 26.1+"); + } + return ATTACK_WRAPPER; } @@ -100,7 +132,10 @@ public static WrappedEnumEntityUseAction attack() { * @return the action for an interact. */ public static WrappedEnumEntityUseAction interact(Hand hand) { - Object handle = INTERACT.invoke(EnumWrappers.getHandConverter().getGeneric(hand)); + Object handle = MinecraftVersion.v26_1.atOrAbove() + ? PACKET_CONSTRUCTOR.invoke(0, EnumWrappers.getHandConverter().getGeneric(hand), + BukkitConverters.getVectorConverter().getGeneric(new Vector()), false) + : INTERACT.invoke(EnumWrappers.getHandConverter().getGeneric(hand)); return new WrappedEnumEntityUseAction(handle); } @@ -111,8 +146,11 @@ public static WrappedEnumEntityUseAction interact(Hand hand) { * @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)); + Object handle = MinecraftVersion.v26_1.atOrAbove() + ? PACKET_CONSTRUCTOR.invoke(0, EnumWrappers.getHandConverter().getGeneric(hand), + BukkitConverters.getVectorConverter().getGeneric(vector), false) + : INTERACT_AT.invoke(EnumWrappers.getHandConverter().getGeneric(hand), + BukkitConverters.getVectorConverter().getGeneric(vector)); return new WrappedEnumEntityUseAction(handle); } @@ -148,7 +186,7 @@ public void setHand(Hand hand) { * @throws IllegalArgumentException if called for attack or interact. */ public Vector getPosition() { - return BukkitConverters.getVectorConverter().getSpecific(getPositionAccessor().get(handle)); + return getRawPosition(handle); } /** @@ -162,7 +200,7 @@ public void setPosition(Vector position) { @Override public WrappedEnumEntityUseAction deepClone() { - switch (action) { + switch (getAction()) { case ATTACK: return WrappedEnumEntityUseAction.attack(); case INTERACT: @@ -170,7 +208,7 @@ public WrappedEnumEntityUseAction deepClone() { case INTERACT_AT: return WrappedEnumEntityUseAction.interactAt(getHand(), getPosition()); default: - throw new IllegalArgumentException("Invalid EntityUseAction: " + action); + throw new IllegalArgumentException("Invalid EntityUseAction: " + getAction()); } } @@ -197,4 +235,8 @@ public FieldAccessor getPositionAccessor() { } return positionAccessor; } + + private Vector getRawPosition(Object target) { + return BukkitConverters.getVectorConverter().getSpecific(getPositionAccessor().get(target)); + } } diff --git a/src/test/java/com/comphenix/protocol/events/PacketContainerTest.java b/src/test/java/com/comphenix/protocol/events/PacketContainerTest.java index 357a20ab1..556b039c9 100644 --- a/src/test/java/com/comphenix/protocol/events/PacketContainerTest.java +++ b/src/test/java/com/comphenix/protocol/events/PacketContainerTest.java @@ -68,6 +68,7 @@ import com.comphenix.protocol.reflect.cloning.SerializableCloner; import com.comphenix.protocol.utility.MinecraftMethods; import com.comphenix.protocol.utility.MinecraftReflection; +import com.comphenix.protocol.utility.MinecraftVersion; import com.comphenix.protocol.wrappers.BlockPosition; import com.comphenix.protocol.wrappers.BukkitConverters; import com.comphenix.protocol.wrappers.ComponentConverter; @@ -753,50 +754,72 @@ public void testGameStateChange() { assertEquals(2, (int) packet.getGameStateIDs().read(0)); } - // @Test - // TODO: needs to be fixed for 26.1 + @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()); + if (MinecraftVersion.v26_1.atOrAbove()) { + packet.getEnumEntityUseActions().write(0, WrappedEnumEntityUseAction.interact(Hand.OFF_HAND)); + action = packet.getEnumEntityUseActions().read(0); + assertEquals(EntityUseAction.INTERACT_AT, action.getAction()); + assertEquals(Hand.OFF_HAND, action.getHand()); + assertEquals(new Vector(), action.getPosition()); + clone = action.deepClone(); + assertEquals(EntityUseAction.INTERACT_AT, clone.getAction()); + assertEquals(Hand.OFF_HAND, clone.getHand()); + assertEquals(new Vector(), clone.getPosition()); + + 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()); + clone = action.deepClone(); + assertEquals(EntityUseAction.INTERACT_AT, clone.getAction()); + assertEquals(Hand.MAIN_HAND, clone.getHand()); + assertEquals(position, clone.getPosition()); + } else { + // 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()); + } } @Test