Compare commits

...
Author SHA1 Message Date
Dan Mulloy b84420b03b simplify 2026-08-02 21:00:04 -05:00
Dan Mulloy f84475e51a wip: fix for use entity 2026-08-02 20:42:53 -05:00
4 changed files with 234 additions and 24 deletions

No files matched your search

@@ -5,6 +5,7 @@ import java.time.Instant;
import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@@ -28,7 +29,9 @@ import org.jetbrains.annotations.NotNull;
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.FieldAccessor;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.comphenix.protocol.utility.StreamSerializer;
@@ -662,11 +665,88 @@ public abstract class AbstractStructure {
* @return A modifier for EntityUseAction class fields.
*/
public StructureModifier<WrappedEnumEntityUseAction> getEnumEntityUseActions() {
if (MinecraftVersion.v26_1.atOrAbove()) {
return new EntityUseActionModifier(structureModifier);
}
return structureModifier.withType(
MinecraftReflection.getEnumEntityUseActionClass(),
WrappedEnumEntityUseAction.CONVERTER);
}
private static final class EntityUseActionModifier extends StructureModifier<WrappedEnumEntityUseAction> {
private final StructureModifier<Object> packetModifier;
private EntityUseActionModifier(StructureModifier<Object> packetModifier) {
this.packetModifier = packetModifier;
List<FieldAccessor> fields = this.packetModifier
.withType(MinecraftReflection.getVec3DClass())
.getFields();
this.initialize(
packetModifier.getTargetType(),
MinecraftReflection.getVec3DClass(),
fields,
Map.of(),
null,
new HashMap<>());
this.target = packetModifier.getTarget();
}
@Override
public WrappedEnumEntityUseAction read(int fieldIndex) {
this.checkEntityUseActionIndex(fieldIndex);
return WrappedEnumEntityUseAction.fromHandle(this.target);
}
@Override
public WrappedEnumEntityUseAction readSafely(int fieldIndex) {
return fieldIndex == 0 && this.target != null
? WrappedEnumEntityUseAction.fromHandle(this.target)
: null;
}
@Override
public StructureModifier<WrappedEnumEntityUseAction> write(
int fieldIndex,
WrappedEnumEntityUseAction value
) {
this.checkEntityUseActionIndex(fieldIndex);
Preconditions.checkNotNull(value, "value cannot be null");
this.packetModifier
.withType(EnumWrappers.getHandClass(), EnumWrappers.getHandConverter())
.write(0, value.getHand());
this.packetModifier
.withType(MinecraftReflection.getVec3DClass(), BukkitConverters.getVectorConverter())
.write(0, value.getPosition());
return this;
}
@Override
public StructureModifier<WrappedEnumEntityUseAction> writeSafely(
int fieldIndex,
WrappedEnumEntityUseAction value
) {
return fieldIndex == 0 ? this.write(fieldIndex, value) : this;
}
@Override
public StructureModifier<WrappedEnumEntityUseAction> withTarget(Object target) {
return new EntityUseActionModifier(this.packetModifier.withTarget(target));
}
private void checkEntityUseActionIndex(int fieldIndex) {
if (this.target == null) {
throw new IllegalStateException("Cannot access a modifier with no packet handle");
}
if (fieldIndex != 0) {
throw FieldAccessException.fromFormat(
"Field index %d is out of bounds for length 1",
fieldIndex);
}
}
}
/**
* Retrieve a read/write structure for the NativeGameMode enum in 1.7.2.
* @return A modifier for NativeGameMode enum fields.
@@ -1235,9 +1235,13 @@ public final class MinecraftReflection {
/**
* Retrieves the entity use action class in 1.17.
*
* @return The EntityUseAction class
* @return The EntityUseAction class, or {@code null} if it was removed in 26.1
*/
public static Class<?> getEnumEntityUseActionClass() {
if (MinecraftVersion.v26_1.atOrAbove()) {
return null;
}
return getOrInferMinecraftClass("ServerboundInteractPacket.Action", () -> {
Class<?> packetClass = PacketType.Play.Client.USE_ENTITY.getPacketClass();
FuzzyReflection fuzzyReflection = FuzzyReflection.fromClass(packetClass, true);
@@ -1261,10 +1265,15 @@ public final class MinecraftReflection {
/**
* 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
* @return a method accessor to get the actual use action, or {@code null} if it was removed in 26.1
*/
public static MethodAccessor getEntityUseActionEnumMethodAccessor() {
FuzzyReflection fuzzy = FuzzyReflection.fromClass(MinecraftReflection.getEnumEntityUseActionClass(), true);
Class<?> actionClass = MinecraftReflection.getEnumEntityUseActionClass();
if (actionClass == null) {
return null;
}
FuzzyReflection fuzzy = FuzzyReflection.fromClass(actionClass, true);
return Accessors.getMethodAccessor(fuzzy.getMethod(FuzzyMethodContract.newBuilder()
.returnTypeExact(EnumWrappers.getEntityUseActionClass())
.build()));
@@ -2,6 +2,7 @@ package com.comphenix.protocol.wrappers;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Objects;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.reflect.EquivalentConverter;
@@ -12,6 +13,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.MinecraftReflection;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.comphenix.protocol.wrappers.EnumWrappers.EntityUseAction;
import com.comphenix.protocol.wrappers.EnumWrappers.Hand;
@@ -28,21 +30,30 @@ public class WrappedEnumEntityUseAction extends AbstractWrapper implements Clona
private static final Class<?> PACKET_CLASS = PacketType.Play.Client.USE_ENTITY.getPacketClass();
private static final Class<?>[] DECLARED_CLASSES = PACKET_CLASS.getDeclaredClasses();
private static final boolean ACTION_CLASS_REMOVED = MinecraftVersion.v26_1.atOrAbove();
private static final Class<?> HANDLE_TYPE = MinecraftReflection.getEnumEntityUseActionClass();
private static final MethodAccessor ACTION_USE = MinecraftReflection.getEntityUseActionEnumMethodAccessor();
private static final Class<?> HANDLE_TYPE = ACTION_CLASS_REMOVED
? PACKET_CLASS
: MinecraftReflection.getEnumEntityUseActionClass();
private static final MethodAccessor ACTION_USE = ACTION_CLASS_REMOVED
? null
: MinecraftReflection.getEntityUseActionEnumMethodAccessor();
private static final ConstructorAccessor INTERACT = useAction(EnumWrappers.getHandClass());
private static final ConstructorAccessor INTERACT_AT = useAction(EnumWrappers.getHandClass(),
MinecraftReflection.getVec3DClass());
private static final ConstructorAccessor PACKET_CONSTRUCTOR = ACTION_CLASS_REMOVED
? Accessors.getConstructorAccessor(PACKET_CLASS, int.class, EnumWrappers.getHandClass(),
MinecraftReflection.getVec3DClass(), boolean.class)
: null;
private static final ConstructorAccessor INTERACT = ACTION_CLASS_REMOVED
? null
: useAction(EnumWrappers.getHandClass());
private static final ConstructorAccessor INTERACT_AT = ACTION_CLASS_REMOVED
? null
: 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 Object ATTACK = ACTION_CLASS_REMOVED ? null : getAttackHandle();
private static final WrappedEnumEntityUseAction ATTACK_WRAPPER = ATTACK == null
? null
: new WrappedEnumEntityUseAction(ATTACK);
private final EntityUseAction action;
// these fields are only available for interact & interact_at
@@ -57,7 +68,18 @@ public class WrappedEnumEntityUseAction extends AbstractWrapper implements Clona
super(HANDLE_TYPE);
setHandle(handle);
action = EnumWrappers.getEntityUseActionConverter().getSpecific(ACTION_USE.invoke(handle));
action = ACTION_CLASS_REMOVED
? EntityUseAction.INTERACT_AT
: EnumWrappers.getEntityUseActionConverter().getSpecific(ACTION_USE.invoke(handle));
}
private static Object getAttackHandle() {
return Accessors.getFieldAccessor(FuzzyReflection.fromClass(PACKET_CLASS, true)
.getField(FuzzyFieldContract.newBuilder()
.requireModifier(Modifier.STATIC)
.typeExact(MinecraftReflection.getEnumEntityUseActionClass())
.build())
).get(null);
}
/**
@@ -91,6 +113,10 @@ public class WrappedEnumEntityUseAction extends AbstractWrapper implements Clona
* @return the action for an entity attack.
*/
public static WrappedEnumEntityUseAction attack() {
if (ATTACK_WRAPPER == null) {
throw new UnsupportedOperationException("Attack uses its own packet in 26.1 and above");
}
return ATTACK_WRAPPER;
}
@@ -100,6 +126,10 @@ public class WrappedEnumEntityUseAction extends AbstractWrapper implements Clona
* @return the action for an interact.
*/
public static WrappedEnumEntityUseAction interact(Hand hand) {
if (ACTION_CLASS_REMOVED) {
return interactAt(hand, new Vector());
}
Object handle = INTERACT.invoke(EnumWrappers.getHandConverter().getGeneric(hand));
return new WrappedEnumEntityUseAction(handle);
}
@@ -111,8 +141,11 @@ public class WrappedEnumEntityUseAction extends AbstractWrapper implements Clona
* @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 genericHand = EnumWrappers.getHandConverter().getGeneric(hand);
Object genericVector = BukkitConverters.getVectorConverter().getGeneric(vector);
Object handle = ACTION_CLASS_REMOVED
? PACKET_CONSTRUCTOR.invoke(0, genericHand, genericVector, false)
: INTERACT_AT.invoke(genericHand, genericVector);
return new WrappedEnumEntityUseAction(handle);
}
@@ -174,6 +207,48 @@ public class WrappedEnumEntityUseAction extends AbstractWrapper implements Clona
}
}
@Override
public boolean equals(Object obj) {
if (!ACTION_CLASS_REMOVED) {
return super.equals(obj);
}
if (obj == this) {
return true;
}
if (!(obj instanceof WrappedEnumEntityUseAction)) {
return false;
}
WrappedEnumEntityUseAction that = (WrappedEnumEntityUseAction) obj;
if (this.action != that.action) {
return false;
}
if (this.action == EntityUseAction.ATTACK) {
return true;
}
if (!Objects.equals(this.getHand(), that.getHand())) {
return false;
}
return this.action != EntityUseAction.INTERACT_AT
|| Objects.equals(this.getPosition(), that.getPosition());
}
@Override
public int hashCode() {
if (!ACTION_CLASS_REMOVED) {
return super.hashCode();
}
int result = this.action.hashCode();
if (this.action != EntityUseAction.ATTACK) {
result = 31 * result + this.getHand().hashCode();
}
if (this.action == EntityUseAction.INTERACT_AT) {
result = 31 * result + this.getPosition().hashCode();
}
return result;
}
/**
* Get a field accessor for the hand in the interact and interact_at type.
* @return a field accessor for the hand field.
@@ -23,6 +23,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotSame;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -68,6 +69,7 @@ import com.comphenix.protocol.reflect.accessors.FieldAccessor;
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,16 +755,60 @@ public class PacketContainerTest {
assertEquals(2, (int) packet.getGameStateIDs().read(0));
}
// @Test
// TODO: needs to be fixed for 26.1
@Test
public void testUseEntity() {
if (MinecraftVersion.v26_1.atOrAbove()) {
testUseEntityWithoutActionClass();
} else {
testUseEntityWithActionClass();
}
}
private void testUseEntityWithoutActionClass() {
PacketContainer packet = new PacketContainer(PacketType.Play.Client.USE_ENTITY);
packet.getIntegers().write(0, 42);
packet.getBooleans().write(0, true);
StructureModifier<WrappedEnumEntityUseAction> actionModifier = packet.getEnumEntityUseActions();
assertEquals(1, actionModifier.size());
assertEquals(1, actionModifier.getFields().size());
assertSame(packet.getHandle(), actionModifier.getTarget());
assertSame(packet.getHandle().getClass(), actionModifier.getTargetType());
actionModifier.getField(0);
actionModifier.writeDefaults();
assertNull(actionModifier.readSafely(1));
actionModifier.writeSafely(1, WrappedEnumEntityUseAction.interact(Hand.MAIN_HAND));
assertThrows(UnsupportedOperationException.class, WrappedEnumEntityUseAction::attack);
Vector position = new Vector(1, 199, 4);
packet.getEnumEntityUseActions().write(0, WrappedEnumEntityUseAction.interactAt(Hand.MAIN_HAND, position));
WrappedEnumEntityUseAction action = packet.getEnumEntityUseActions().read(0);
assertEquals(EntityUseAction.INTERACT_AT, action.getAction());
assertEquals(Hand.MAIN_HAND, action.getHand());
assertEquals(position, action.getPosition());
assertEquals(42, packet.getIntegers().read(0));
assertTrue(packet.getBooleans().read(0));
WrappedEnumEntityUseAction clone = action.deepClone();
assertEquals(EntityUseAction.INTERACT_AT, clone.getAction());
assertEquals(Hand.MAIN_HAND, clone.getHand());
assertEquals(position, clone.getPosition());
assertEquals(action, clone);
assertEquals(action.hashCode(), clone.hashCode());
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());
}
private void testUseEntityWithActionClass() {
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);
WrappedEnumEntityUseAction action = packet.getEnumEntityUseActions().read(0);
// attack's handle should always be the same
assertEquals(WrappedEnumEntityUseAction.attack(), action);
assertEquals(EntityUseAction.ATTACK, action.getAction());
@@ -770,7 +816,7 @@ public class PacketContainerTest {
assertThrows(IllegalArgumentException.class, action::getHand);
assertThrows(IllegalArgumentException.class, action::getPosition);
// test cloning
clone = action.deepClone();
WrappedEnumEntityUseAction clone = action.deepClone();
assertSame(WrappedEnumEntityUseAction.attack(), clone);
// test interact