Skip to content

Commit 0a32f24

Browse files
authored
Fix EntityUseAction & Hand read for minecraft 1.17 (#1230)
1 parent 1c2bc27 commit 0a32f24

File tree

8 files changed

+339
-19
lines changed

8 files changed

+339
-19
lines changed

src/main/java/com/comphenix/protocol/events/PacketContainer.java

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -732,7 +732,7 @@ public StructureModifier<Difficulty> getDifficulties() {
732732
EnumWrappers.getDifficultyClass(),
733733
EnumWrappers.getDifficultyConverter());
734734
}
735-
735+
736736
/**
737737
* Retrieve a read/write structure for the EntityUse enum in 1.7.2.
738738
* @return A modifier for EntityUse enum fields.
@@ -744,6 +744,17 @@ public StructureModifier<EntityUseAction> getEntityUseActions() {
744744
EnumWrappers.getEntityUseActionConverter());
745745
}
746746

747+
/**
748+
* Retrieves a read/write structure for the EntityUseAction class in the UseEntity packet sent by the client for
749+
* 1.17 and above.
750+
* @return A modifier for EntityUseAction class fields.
751+
*/
752+
public StructureModifier<WrappedEnumEntityUseAction> getEnumEntityUseActions() {
753+
return structureModifier.withType(
754+
MinecraftReflection.getEnumEntityUseActionClass(),
755+
WrappedEnumEntityUseAction.CONVERTER);
756+
}
757+
747758
/**
748759
* Retrieve a read/write structure for the NativeGameMode enum in 1.7.2.
749760
* @return A modifier for NativeGameMode enum fields.
@@ -916,7 +927,7 @@ public StructureModifier<ItemSlot> getItemSlots() {
916927
public StructureModifier<Hand> getHands() {
917928
return structureModifier.withType(
918929
EnumWrappers.getHandClass(),
919-
EnumWrappers.getHandConverter());
930+
EnumWrappers.getHandConverter());
920931
}
921932

922933
/**

src/main/java/com/comphenix/protocol/reflect/accessors/Accessors.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,7 @@ public static ConstructorAccessor getConstructorAccessor(Class<?> instanceClass,
289289
* @return The method accessor.
290290
*/
291291
public static ConstructorAccessor getConstructorAccessor(final Constructor<?> constructor) {
292+
constructor.setAccessible(true); // let us in even if we are not allowed to
292293
return new DefaultConstrutorAccessor(constructor);
293294
}
294295

src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,8 @@
2727
import java.lang.reflect.ParameterizedType;
2828
import java.lang.reflect.Type;
2929
import java.net.InetAddress;
30-
import java.net.ServerSocket;
3130
import java.util.Collection;
3231
import java.util.HashSet;
33-
import java.util.List;
3432
import java.util.Map;
3533
import java.util.Set;
3634
import java.util.concurrent.ConcurrentMap;
@@ -57,6 +55,7 @@
5755
import com.comphenix.protocol.reflect.ClassAnalyser.AsmMethod;
5856
import com.comphenix.protocol.reflect.FuzzyReflection;
5957
import com.comphenix.protocol.reflect.accessors.Accessors;
58+
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
6059
import com.comphenix.protocol.reflect.accessors.MethodAccessor;
6160
import com.comphenix.protocol.reflect.fuzzy.AbstractFuzzyMatcher;
6261
import com.comphenix.protocol.reflect.fuzzy.FuzzyClassContract;
@@ -65,6 +64,7 @@
6564
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
6665
import com.comphenix.protocol.utility.RemappedClassSource.RemapperUnavailableException;
6766
import com.comphenix.protocol.utility.RemappedClassSource.RemapperUnavailableException.Reason;
67+
import com.comphenix.protocol.wrappers.EnumWrappers;
6868
import com.comphenix.protocol.wrappers.nbt.NbtFactory;
6969
import com.comphenix.protocol.wrappers.nbt.NbtType;
7070
import com.google.common.base.Joiner;
@@ -1793,6 +1793,52 @@ public static Class<?> getPlayerInfoDataClass() {
17931793
"PacketPlayOutPlayerInfo$PlayerInfoData", "PlayerInfoData");
17941794
}
17951795

1796+
/**
1797+
* Retrieves the entity use action class in 1.17.
1798+
* @return The EntityUseAction class
1799+
*/
1800+
public static Class<?> getEnumEntityUseActionClass() {
1801+
Class<?> packetClass = PacketType.Play.Client.USE_ENTITY.getPacketClass();
1802+
return FuzzyReflection.fromClass(packetClass, true).getFieldByType("^.*(EnumEntityUseAction)").getType();
1803+
}
1804+
1805+
/**
1806+
* Get a method accessor to get the actual use action out of the wrapping EnumEntityUseAction in 1.17.
1807+
* @return a method accessor to get the actual use action
1808+
*/
1809+
public static MethodAccessor getEntityUseActionEnumMethodAccessor() {
1810+
FuzzyReflection fuzzy = FuzzyReflection.fromClass(MinecraftReflection.getEnumEntityUseActionClass(), true);
1811+
return Accessors.getMethodAccessor(fuzzy.getMethod(FuzzyMethodContract.newBuilder()
1812+
.returnTypeExact(EnumWrappers.getEntityUseActionClass())
1813+
.build()));
1814+
}
1815+
1816+
/**
1817+
* Get a field accessor for the hand in the wrapping EnumEntityUseAction in 1.17.
1818+
*
1819+
* @param enumEntityUseAction the object instance of the action, the field is not present in attack.
1820+
* @return a field accessor for the hand in the wrapping EnumEntityUseAction
1821+
*/
1822+
public static FieldAccessor getHandEntityUseActionEnumFieldAccessor(Object enumEntityUseAction) {
1823+
FuzzyReflection fuzzy = FuzzyReflection.fromObject(enumEntityUseAction, true);
1824+
return Accessors.getFieldAccessor(fuzzy.getField(FuzzyFieldContract.newBuilder()
1825+
.typeExact(EnumWrappers.getHandClass())
1826+
.build()));
1827+
}
1828+
1829+
/**
1830+
* Get a field accessor for the vec3d in the wrapping EnumEntityUseAction in 1.17.
1831+
*
1832+
* @param enumEntityUseAction the object instance of the action, the field is not present in attack.
1833+
* @return a field accessor for the hand in the wrapping EnumEntityUseAction
1834+
*/
1835+
public static FieldAccessor getVec3EntityUseActionEnumFieldAccessor(Object enumEntityUseAction) {
1836+
FuzzyReflection fuzzy = FuzzyReflection.fromObject(enumEntityUseAction, true);
1837+
return Accessors.getFieldAccessor(fuzzy.getField(FuzzyFieldContract.newBuilder()
1838+
.typeExact(MinecraftReflection.getVec3DClass())
1839+
.build()));
1840+
}
1841+
17961842
/**
17971843
* Determine if the given object is a PlayerInfoData.
17981844
* @param obj - the given object.

src/main/java/com/comphenix/protocol/wrappers/EnumWrappers.java

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import com.comphenix.protocol.reflect.EquivalentConverter;
1111
import com.comphenix.protocol.reflect.FuzzyReflection;
1212
import com.comphenix.protocol.reflect.accessors.Accessors;
13+
import com.comphenix.protocol.utility.MinecraftVersion;
1314
import com.comphenix.protocol.utility.MinecraftReflection;
1415
import com.google.common.collect.Maps;
1516

@@ -481,7 +482,6 @@ private static void initialize() {
481482
DIFFICULTY_CLASS = getEnum(PacketType.Play.Server.LOGIN.getPacketClass(), 1);
482483
}
483484

484-
ENTITY_USE_ACTION_CLASS = getEnum(PacketType.Play.Client.USE_ENTITY.getPacketClass(), 0);
485485
GAMEMODE_CLASS = getEnum(PacketType.Play.Server.LOGIN.getPacketClass(), 0);
486486
RESOURCE_PACK_STATUS_CLASS = getEnum(PacketType.Play.Client.RESOURCE_PACK_STATUS.getPacketClass(), 0);
487487
PLAYER_INFO_ACTION_CLASS = getEnum(PacketType.Play.Server.PLAYER_INFO.getPacketClass(), 0);
@@ -501,7 +501,16 @@ private static void initialize() {
501501
ITEM_SLOT_CLASS = getEnum(PacketType.Play.Server.ENTITY_EQUIPMENT.getPacketClass(), 0);
502502
}
503503

504-
HAND_CLASS = getEnum(PacketType.Play.Client.USE_ENTITY.getPacketClass(), 1);
504+
// In 1.17 the hand and use action class is no longer a field in the packet
505+
if (MinecraftVersion.CAVES_CLIFFS_1.atOrAbove()) {
506+
HAND_CLASS = MinecraftReflection.getMinecraftClass("world.EnumHand");
507+
// 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)
508+
ENTITY_USE_ACTION_CLASS = MinecraftReflection.getEnumEntityUseActionClass().getMethods()[0].getReturnType();
509+
} else {
510+
HAND_CLASS = getEnum(PacketType.Play.Client.USE_ENTITY.getPacketClass(), 1);
511+
ENTITY_USE_ACTION_CLASS = getEnum(PacketType.Play.Client.USE_ENTITY.getPacketClass(), 0);
512+
}
513+
505514
DIRECTION_CLASS = getEnum(PacketType.Play.Server.SPAWN_ENTITY_PAINTING.getPacketClass(), 0);
506515
CHAT_TYPE_CLASS = getEnum(PacketType.Play.Server.CHAT.getPacketClass(), 0);
507516
ENTITY_POSE_CLASS = MinecraftReflection.getNullableNMS("world.entity.EntityPose", "EntityPose");
@@ -510,7 +519,6 @@ private static void initialize() {
510519
associate(CLIENT_COMMAND_CLASS, ClientCommand.class, getClientCommandConverter());
511520
associate(CHAT_VISIBILITY_CLASS, ChatVisibility.class, getChatVisibilityConverter());
512521
associate(DIFFICULTY_CLASS, Difficulty.class, getDifficultyConverter());
513-
associate(ENTITY_USE_ACTION_CLASS, EntityUseAction.class, getEntityUseActionConverter());
514522
associate(GAMEMODE_CLASS, NativeGameMode.class, getGameModeConverter());
515523
associate(RESOURCE_PACK_STATUS_CLASS, ResourcePackStatus.class, getResourcePackStatusConverter());
516524
associate(PLAYER_INFO_ACTION_CLASS, PlayerInfoAction.class, getPlayerInfoActionConverter());
@@ -523,15 +531,14 @@ private static void initialize() {
523531
associate(PARTICLE_CLASS, Particle.class, getParticleConverter());
524532
associate(SOUND_CATEGORY_CLASS, SoundCategory.class, getSoundCategoryConverter());
525533
associate(ITEM_SLOT_CLASS, ItemSlot.class, getItemSlotConverter());
526-
associate(HAND_CLASS, Hand.class, getHandConverter());
527534
associate(DIRECTION_CLASS, Direction.class, getDirectionConverter());
528535
associate(CHAT_TYPE_CLASS, ChatType.class, getChatTypeConverter());
529-
536+
associate(HAND_CLASS, Hand.class, getHandConverter());
537+
associate(ENTITY_USE_ACTION_CLASS, EntityUseAction.class, getEntityUseActionConverter());
538+
530539
if (ENTITY_POSE_CLASS != null) {
531540
associate(ENTITY_POSE_CLASS, EntityPose.class, getEntityPoseConverter());
532541
}
533-
534-
INITIALIZED = true;
535542
}
536543

537544
private static void associate(Class<?> nativeClass, Class<?> wrapperClass, EquivalentConverter<?> converter) {
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
package com.comphenix.protocol.wrappers;
2+
3+
import java.lang.reflect.Modifier;
4+
import java.util.Arrays;
5+
6+
import com.comphenix.protocol.PacketType;
7+
import com.comphenix.protocol.reflect.EquivalentConverter;
8+
import com.comphenix.protocol.reflect.FuzzyReflection;
9+
import com.comphenix.protocol.reflect.accessors.Accessors;
10+
import com.comphenix.protocol.reflect.accessors.ConstructorAccessor;
11+
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
12+
import com.comphenix.protocol.reflect.accessors.MethodAccessor;
13+
import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract;
14+
import com.comphenix.protocol.utility.MinecraftReflection;
15+
import com.comphenix.protocol.wrappers.EnumWrappers.EntityUseAction;
16+
import com.comphenix.protocol.wrappers.EnumWrappers.Hand;
17+
18+
import org.bukkit.util.Vector;
19+
20+
/**
21+
* Represents an entity used action used in the UseEntity packet sent by the client.
22+
* @author derklaro
23+
*/
24+
public class WrappedEnumEntityUseAction extends AbstractWrapper implements ClonableWrapper {
25+
26+
public static final EquivalentConverter<WrappedEnumEntityUseAction> CONVERTER = Converters.handle(AbstractWrapper::getHandle,
27+
WrappedEnumEntityUseAction::fromHandle, WrappedEnumEntityUseAction.class);
28+
29+
private static final Class<?> PACKET_CLASS = PacketType.Play.Client.USE_ENTITY.getPacketClass();
30+
private static final Class<?>[] DECLARED_CLASSES = PACKET_CLASS.getDeclaredClasses();
31+
32+
private static final Class<?> HANDLE_TYPE = MinecraftReflection.getEnumEntityUseActionClass();
33+
private static final MethodAccessor ACTION_USE = MinecraftReflection.getEntityUseActionEnumMethodAccessor();
34+
35+
private static final ConstructorAccessor INTERACT = useAction(EnumWrappers.getHandClass());
36+
private static final ConstructorAccessor INTERACT_AT = useAction(EnumWrappers.getHandClass(),
37+
MinecraftReflection.getVec3DClass());
38+
39+
private static final Object ATTACK = Accessors.getFieldAccessor(FuzzyReflection.fromClass(PACKET_CLASS, true)
40+
.getField(FuzzyFieldContract.newBuilder()
41+
.requireModifier(Modifier.STATIC)
42+
.typeExact(MinecraftReflection.getEnumEntityUseActionClass())
43+
.build())
44+
).get(null);
45+
private static final WrappedEnumEntityUseAction ATTACK_WRAPPER = new WrappedEnumEntityUseAction(ATTACK);
46+
47+
private final EntityUseAction action;
48+
// these fields are only available for interact & interact_at
49+
private FieldAccessor handAccessor;
50+
private FieldAccessor positionAccessor;
51+
52+
/**
53+
* Construct a new wrapper for the entity use action class in the UseEntity packet.
54+
* @param handle - the NMS handle.
55+
*/
56+
private WrappedEnumEntityUseAction(Object handle) {
57+
super(HANDLE_TYPE);
58+
setHandle(handle);
59+
60+
action = EnumWrappers.getEntityUseActionConverter().getSpecific(ACTION_USE.invoke(handle));
61+
}
62+
63+
/**
64+
* Finds a constructor of a declared class in the UseEntity class. Used to find the action class implementations.
65+
* @param parameterTypes - the types the constructor of the class must have.
66+
* @return a constructor for a matching class.
67+
* @throws IllegalArgumentException if no constructor was found.
68+
*/
69+
private static ConstructorAccessor useAction(Class<?>... parameterTypes) {
70+
for (Class<?> subClass : DECLARED_CLASSES) {
71+
ConstructorAccessor accessor = Accessors.getConstructorAccessorOrNull(subClass, parameterTypes);
72+
if (accessor != null) {
73+
return accessor;
74+
}
75+
}
76+
throw new IllegalArgumentException(
77+
"No constructor with " + Arrays.toString(parameterTypes) + " in " + PACKET_CLASS);
78+
}
79+
80+
/**
81+
* Construct a new wrapper for the entity use action class in the UseEntity packet.
82+
* @param handle - the NMS handle.
83+
* @return the created wrapper.
84+
*/
85+
public static WrappedEnumEntityUseAction fromHandle(Object handle) {
86+
return new WrappedEnumEntityUseAction(handle);
87+
}
88+
89+
/**
90+
* Get the jvm static action for attacking an entity.
91+
* @return the action for an entity attack.
92+
*/
93+
public static WrappedEnumEntityUseAction attack() {
94+
return ATTACK_WRAPPER;
95+
}
96+
97+
/**
98+
* Get an action for interacting with an entity.
99+
* @param hand - the hand used for the interact.
100+
* @return the action for an interact.
101+
*/
102+
public static WrappedEnumEntityUseAction interact(Hand hand) {
103+
Object handle = INTERACT.invoke(EnumWrappers.getHandConverter().getGeneric(hand));
104+
return new WrappedEnumEntityUseAction(handle);
105+
}
106+
107+
/**
108+
* Get an action for interacting with an entity at a specific location.
109+
* @param hand - the hand used for the interact.
110+
* @param vector - the position of the interact.
111+
* @return the action for an interact_at.
112+
*/
113+
public static WrappedEnumEntityUseAction interactAt(Hand hand, Vector vector) {
114+
Object handle = INTERACT_AT.invoke(EnumWrappers.getHandConverter().getGeneric(hand),
115+
BukkitConverters.getVectorConverter().getGeneric(vector));
116+
return new WrappedEnumEntityUseAction(handle);
117+
}
118+
119+
/**
120+
* Get the action used for the interact.
121+
* @return the interact action.
122+
*/
123+
public EntityUseAction getAction() {
124+
return action;
125+
}
126+
127+
/**
128+
* Get the hand used for the interact. Only available if this represents interact or interact_at.
129+
* @return the hand used for the interact.
130+
* @throws IllegalArgumentException if called for attack.
131+
*/
132+
public Hand getHand() {
133+
return EnumWrappers.getHandConverter().getSpecific(getHandAccessor().get(handle));
134+
}
135+
136+
/**
137+
* Sets the hand used for the interact.
138+
* @param hand the used hand.
139+
* @throws IllegalArgumentException if called for attack.
140+
*/
141+
public void setHand(Hand hand) {
142+
getHandAccessor().set(handle, EnumWrappers.getHandConverter().getGeneric(hand));
143+
}
144+
145+
/**
146+
* Get the position of the interact. Only available if this represents interact_at.
147+
* @return the position of the interact.
148+
* @throws IllegalArgumentException if called for attack or interact.
149+
*/
150+
public Vector getPosition() {
151+
return BukkitConverters.getVectorConverter().getSpecific(getPositionAccessor().get(handle));
152+
}
153+
154+
/**
155+
* Sets the position of the interact.
156+
* @param position the position.
157+
* @throws IllegalArgumentException if called for attack or interact.
158+
*/
159+
public void setPosition(Vector position) {
160+
getPositionAccessor().set(handle, BukkitConverters.getVectorConverter().getGeneric(position));
161+
}
162+
163+
@Override
164+
public WrappedEnumEntityUseAction deepClone() {
165+
switch (action) {
166+
case ATTACK:
167+
return WrappedEnumEntityUseAction.attack();
168+
case INTERACT:
169+
return WrappedEnumEntityUseAction.interact(getHand());
170+
case INTERACT_AT:
171+
return WrappedEnumEntityUseAction.interactAt(getHand(), getPosition());
172+
default:
173+
throw new IllegalArgumentException("Invalid EntityUseAction: " + action);
174+
}
175+
}
176+
177+
/**
178+
* Get a field accessor for the hand in the interact and interact_at type.
179+
* @return a field accessor for the hand field.
180+
* @throws IllegalArgumentException if called for attack.
181+
*/
182+
private FieldAccessor getHandAccessor() {
183+
if (handAccessor == null) {
184+
handAccessor = MinecraftReflection.getHandEntityUseActionEnumFieldAccessor(handle);
185+
}
186+
return handAccessor;
187+
}
188+
189+
/**
190+
* Get a field accessor for the position in the interact_at type.
191+
* @return a field accessor for the position field.
192+
* @throws IllegalArgumentException if called for attack or interact.
193+
*/
194+
public FieldAccessor getPositionAccessor() {
195+
if (positionAccessor == null) {
196+
positionAccessor = MinecraftReflection.getVec3EntityUseActionEnumFieldAccessor(handle);
197+
}
198+
return positionAccessor;
199+
}
200+
}

0 commit comments

Comments
 (0)