33import com .github .retrooper .packetevents .protocol .entity .type .EntityType ;
44import com .github .retrooper .packetevents .protocol .packettype .PacketType ;
55import com .github .retrooper .packetevents .protocol .packettype .PacketTypeCommon ;
6- import com .github .retrooper .packetevents .util .Vector3d ;
76import com .github .retrooper .packetevents .wrapper .PacketWrapper ;
87import com .github .retrooper .packetevents .wrapper .play .client .WrapperPlayClientSelectBundleItem ;
98import com .github .retrooper .packetevents .wrapper .play .server .*;
1312import org .bukkit .block .data .BlockData ;
1413import org .bukkit .block .sign .Side ;
1514import org .bukkit .util .Vector ;
15+ import org .jspecify .annotations .NonNull ;
1616
1717import java .util .*;
1818import java .util .function .BiConsumer ;
@@ -24,65 +24,72 @@ public class PacketConstructorRegistry {
2424
2525 static {
2626 builder (PacketType .Play .Server .ENTITY_VELOCITY , WrapperPlayServerEntityVelocity .class )
27- .requiredField ("entity id" , Number .class , WrapperPlayServerEntityVelocity ::getEntityId ,
28- (w , id ) -> w .setEntityId (id .intValue ()))
29- .requiredField ("vector" , Vector .class , w -> ConversionUtil .toBukkitVector (w .getVelocity ()),
30- (w , vector ) -> w .setVelocity (ConversionUtil .toPeVectorD (vector )))
31- .constructor (values -> {
32- Vector vec = values .get ("vector" , Vector .class );
33- return new WrapperPlayServerEntityVelocity (
34- values .get ("entity id" , Number .class ).intValue (),
35- new Vector3d (vec .getX (), vec .getY (), vec .getZ ())
36- );
37- })
27+ .requiredField (Number .class , WrapperPlayServerEntityVelocity ::getEntityId ,
28+ (w , id ) -> w .setEntityId (id .intValue ()),
29+ "entity id" , "id" )
30+ .requiredField (Vector .class , w -> ConversionUtil .toBukkitVector (w .getVelocity ()),
31+ (w , vector ) -> w .setVelocity (ConversionUtil .toPeVectorD (vector )),
32+ "velocity vector" , "vector" , "velocity" )
33+ .constructor (values -> new WrapperPlayServerEntityVelocity (
34+ values .get ("entity id" , Number .class ).intValue (),
35+ ConversionUtil .toPeVectorD (values .get ("velocity vector" , Vector .class )))
36+ )
3837 .build ();
3938
4039 builder (PacketType .Play .Server .GAME_TEST_HIGHLIGHT_POS , WrapperPlayServerGameTestHighlightPos .class )
41- .requiredField ("position" , Vector .class , w -> ConversionUtil .toBukkitVector (w .getAbsolutePos ()),
42- (w , vector ) -> w .setAbsolutePos (ConversionUtil .toPeVectorI (vector )))
43- .optionalField ("relative position" , Vector .class , w -> ConversionUtil .toBukkitVector (w .getRelativePos ()),
44- (w , vector ) -> w .setRelativePos (ConversionUtil .toPeVectorI (vector )))
40+ .requiredField (Vector .class , w -> ConversionUtil .toBukkitVector (w .getAbsolutePos ()),
41+ (w , vector ) -> w .setAbsolutePos (ConversionUtil .toPeVectorI (vector )),
42+ "absolute position" , "absolute pos" , "abs position" , "abs pos" , "position" , "pos" )
43+ .optionalField (Vector .class , w -> ConversionUtil .toBukkitVector (w .getRelativePos ()),
44+ (w , vector ) -> w .setRelativePos (ConversionUtil .toPeVectorI (vector )),
45+ "relative position" , "relative pos" , "rel position" , "rel pos" )
4546 .constructor (values -> new WrapperPlayServerGameTestHighlightPos (
46- ConversionUtil .toPeVectorI (values .get ("position" , Vector .class )),
47+ ConversionUtil .toPeVectorI (values .get ("absolute position" , Vector .class )),
4748 ConversionUtil .toPeVectorI (values .get ("relative position" , Vector .class )))
4849 )
4950 .build ();
5051
5152 builder (PacketType .Play .Server .ENTITY_METADATA , WrapperPlayServerEntityMetadata .class )
52- .requiredField ("entity id" , Number .class , WrapperPlayServerEntityMetadata ::getEntityId ,
53- (w , id ) -> w .setEntityId (id .intValue ()))
54- .requiredField ("entity meta" , EntityMeta .class , w -> {
55- EntityType type = EntityTracker .getType (w .getEntityId ());
56- if (type == null ) {
57- throw new IllegalStateException ("Failed to find entity type of entity with id " + w .getEntityId ());
58- }
59-
60- EntityMeta meta = EntityMeta .createMeta (w .getEntityId (), type );
61- meta .getMetadata ().setMetaFromPacket (w );
62- return meta ;
63- }, WrapperPlayServerEntityMetadata ::setEntityMetadata )
53+ .requiredField (Number .class , WrapperPlayServerEntityMetadata ::getEntityId ,
54+ (w , id ) -> w .setEntityId (id .intValue ()),
55+ "entity id" , "id" )
56+ .requiredField (EntityMeta .class , w -> {
57+ EntityType type = EntityTracker .getType (w .getEntityId ());
58+ if (type == null ) {
59+ throw new IllegalStateException ("Failed to find entity type of entity with id " + w .getEntityId ());
60+ }
61+
62+ EntityMeta meta = EntityMeta .createMeta (w .getEntityId (), type );
63+ meta .getMetadata ().setMetaFromPacket (w );
64+ return meta ;
65+ }, WrapperPlayServerEntityMetadata ::setEntityMetadata ,
66+ "entity metadata" , "entity meta" , "metadata" , "meta" )
6467 .constructor (values -> new WrapperPlayServerEntityMetadata (
6568 values .get ("entity id" , Number .class ).intValue (),
66- values .get ("entity meta " , EntityMeta .class ))
69+ values .get ("entity metadata " , EntityMeta .class ))
6770 )
6871 .build ();
6972
7073 builder (PacketType .Play .Server .BLOCK_CHANGE , WrapperPlayServerBlockChange .class )
71- .requiredField ("block position" , Vector .class , w -> ConversionUtil .toBukkitVector (w .getBlockPosition ()),
72- (w , vector ) -> w .setBlockPosition (ConversionUtil .toPeVectorI (vector )))
73- .requiredField ("block state" , BlockData .class , w -> SpigotConversionUtil .toBukkitBlockData (w .getBlockState ()),
74- (w , blockData ) -> w .setBlockState (SpigotConversionUtil .fromBukkitBlockData (blockData )))
74+ .requiredField (Vector .class , w -> ConversionUtil .toBukkitVector (w .getBlockPosition ()),
75+ (w , vector ) -> w .setBlockPosition (ConversionUtil .toPeVectorI (vector )),
76+ "block position" , "block pos" , "position" , "pos" )
77+ .requiredField (BlockData .class , w -> SpigotConversionUtil .toBukkitBlockData (w .getBlockState ()),
78+ (w , blockData ) -> w .setBlockState (SpigotConversionUtil .fromBukkitBlockData (blockData )),
79+ "block state" , "state" , "block data" , "data" )
7580 .constructor (values -> new WrapperPlayServerBlockChange (
7681 ConversionUtil .toPeVectorI (values .get ("block position" , Vector .class )),
7782 SpigotConversionUtil .fromBukkitBlockData (values .get ("block state" , BlockData .class ))
7883 ))
7984 .build ();
8085
8186 builder (PacketType .Play .Server .OPEN_SIGN_EDITOR , WrapperPlayServerOpenSignEditor .class )
82- .requiredField ("block position" , Vector .class , w -> ConversionUtil .toBukkitVector (w .getPosition ()),
83- (w , vector ) -> w .setPosition (ConversionUtil .toPeVectorI (vector )))
84- .requiredField ("sign side" , Side .class , w -> w .isFrontText () ? Side .FRONT : Side .BACK
85- , (w , side ) -> w .setFrontText (side == Side .FRONT ))
87+ .requiredField (Vector .class , w -> ConversionUtil .toBukkitVector (w .getPosition ()),
88+ (w , vector ) -> w .setPosition (ConversionUtil .toPeVectorI (vector )),
89+ "block position" , "block pos" , "position" , "pos" )
90+ .requiredField (Side .class , w -> w .isFrontText () ? Side .FRONT : Side .BACK ,
91+ (w , side ) -> w .setFrontText (side == Side .FRONT ),
92+ "sign side" , "side" )
8693 .constructor (values -> new WrapperPlayServerOpenSignEditor (
8794 ConversionUtil .toPeVectorI (values .get ("block position" , Vector .class )),
8895 values .get ("sign side" , Side .class ) == Side .FRONT
@@ -94,27 +101,29 @@ public class PacketConstructorRegistry {
94101 .build ();
95102
96103 builder (PacketType .Play .Server .DESTROY_ENTITIES , WrapperPlayServerDestroyEntities .class )
97- .requiredField ("entity ids" , Number [].class ,
104+ .requiredField (Number [].class ,
98105 w -> Arrays .stream (w .getEntityIds ()).boxed ().toArray (Number []::new ),
99- (w , ids ) -> w .setEntityIds (Arrays .stream (ids ).mapToInt (Number ::intValue ).toArray ()))
106+ (w , ids ) -> w .setEntityIds (Arrays .stream (ids ).mapToInt (Number ::intValue ).toArray ()),
107+ "entity ids" , "ids" )
100108 .constructor (values -> new WrapperPlayServerDestroyEntities (
101109 Arrays .stream (values .get ("entity ids" , Number [].class )).mapToInt (Number ::intValue ).toArray ()
102110 ))
103111 .build ();
104112
105113 builder (PacketType .Play .Client .SELECT_BUNDLE_ITEM , WrapperPlayClientSelectBundleItem .class )
106- .requiredField ("slot id" , Number .class , WrapperPlayClientSelectBundleItem ::getSlotId ,
107- (w , id ) -> w .setSlotId (id .intValue ()))
108- .requiredField ("selected item index" , Number .class , WrapperPlayClientSelectBundleItem ::getSelectedItemIndex ,
109- (w , index ) -> w .setSelectedItemIndex (index .intValue ()))
114+ .requiredField (Number .class , WrapperPlayClientSelectBundleItem ::getSlotId ,
115+ (w , id ) -> w .setSlotId (id .intValue ()),
116+ "slot id" , "id" )
117+ .requiredField (Number .class , WrapperPlayClientSelectBundleItem ::getSelectedItemIndex ,
118+ (w , index ) -> w .setSelectedItemIndex (index .intValue ()),
119+ "selected item index" , "selected index" , "item index" , "index" )
110120 .constructor (values -> new WrapperPlayClientSelectBundleItem (
111121 values .get ("slot id" , Number .class ).intValue (),
112122 values .get ("selected item index" , Number .class ).intValue ()
113123 ))
114124 .build ();
115125 }
116126
117- // wrapperClass is an essential argument for the lambda methods
118127 public static <W extends PacketWrapper <?>> PacketBuilder <W > builder (PacketTypeCommon type , Class <W > wrapperClass ) {
119128 return new PacketBuilder <>(type );
120129 }
@@ -127,17 +136,43 @@ public static Collection<PacketDefinition> getAllDefinitions() {
127136 return REGISTRY .values ();
128137 }
129138
130- public record PacketField <T >(String name , Class <T > expectedType , boolean isOptional ,
139+ public record PacketField <T >(String name , String [] aliases , Class <T > expectedType , boolean isOptional ,
131140 Function <PacketWrapper <?>, T > getter , BiConsumer <PacketWrapper <?>, T > setter ) {
141+ public boolean matches (String input ) {
142+ if (name .equalsIgnoreCase (input )) return true ;
143+ for (String alias : aliases ) {
144+ if (alias .equalsIgnoreCase (input )) return true ;
145+ }
146+ return false ;
147+ }
132148 }
133149
134- public record PacketDefinition (List <PacketField <?>> fields , Function <PacketValues , PacketWrapper <?>> constructor ) {
150+ public record PacketDefinition (PacketTypeCommon type , List <PacketField <?>> fields , Function <PacketValues , PacketWrapper <?>> constructor ) {
135151 public PacketField <?> getField (String name ) {
136152 for (PacketField <?> field : fields ) {
137- if (field .name ().equalsIgnoreCase (name )) return field ;
153+ if (field .matches (name )) {
154+ return field ;
155+ }
138156 }
139157 return null ;
140158 }
159+
160+ public String getReadableFields () {
161+ List <String > fieldStrings = new ArrayList <>();
162+ for (PacketField <?> field : fields ) {
163+ if (field .aliases ().length > 0 ) {
164+ fieldStrings .add (field .name () + " (or: " + String .join (", " , field .aliases ()) + ")" );
165+ } else {
166+ fieldStrings .add (field .name ());
167+ }
168+ }
169+ return String .join (" | " , fieldStrings );
170+ }
171+
172+ @ Override
173+ public @ NonNull String toString () {
174+ return type .getName ().replace ("_" , " " ).toLowerCase (Locale .ENGLISH );
175+ }
141176 }
142177
143178 public static class PacketBuilder <W extends PacketWrapper <?>> {
@@ -150,14 +185,26 @@ public PacketBuilder(PacketTypeCommon type) {
150185 }
151186
152187 @ SuppressWarnings ("unchecked" )
153- public <T > PacketBuilder <W > requiredField (String name , Class <T > type , Function <W , T > getter , BiConsumer <W , T > setter ) {
154- this .fields .add (new PacketField <>(name , type , false , (Function <PacketWrapper <?>, T >) getter , (BiConsumer <PacketWrapper <?>, T >) setter ));
188+ public <T > PacketBuilder <W > requiredField (Class <T > type , Function <W , T > getter , BiConsumer <W , T > setter , String ... names ) {
189+ if (names == null || names .length == 0 ) {
190+ throw new IllegalArgumentException ("Field registration must specify at least a primary name." );
191+ }
192+ String primaryName = names [0 ];
193+ String [] aliases = Arrays .copyOfRange (names , 1 , names .length );
194+
195+ this .fields .add (new PacketField <>(primaryName , aliases , type , false , (Function <PacketWrapper <?>, T >) getter , (BiConsumer <PacketWrapper <?>, T >) setter ));
155196 return this ;
156197 }
157198
158199 @ SuppressWarnings ("unchecked" )
159- public <T > PacketBuilder <W > optionalField (String name , Class <T > type , Function <W , T > getter , BiConsumer <W , T > setter ) {
160- this .fields .add (new PacketField <>(name , type , true , (Function <PacketWrapper <?>, T >) getter , (BiConsumer <PacketWrapper <?>, T >) setter ));
200+ public <T > PacketBuilder <W > optionalField (Class <T > type , Function <W , T > getter , BiConsumer <W , T > setter , String ... names ) {
201+ if (names == null || names .length == 0 ) {
202+ throw new IllegalArgumentException ("Field registration must specify at least a primary name." );
203+ }
204+ String primaryName = names [0 ];
205+ String [] aliases = Arrays .copyOfRange (names , 1 , names .length );
206+
207+ this .fields .add (new PacketField <>(primaryName , aliases , type , true , (Function <PacketWrapper <?>, T >) getter , (BiConsumer <PacketWrapper <?>, T >) setter ));
161208 return this ;
162209 }
163210
@@ -172,28 +219,77 @@ public void build() {
172219 throw new IllegalStateException ("Cannot build packet definition for '" + type .getName () + "' because no constructor was provided." );
173220 }
174221
175- REGISTRY .put (this .type , new PacketDefinition (Collections .unmodifiableList (this .fields ), (Function ) this .packetConstructor ));
222+ REGISTRY .put (this .type , new PacketDefinition (this . type , Collections .unmodifiableList (this .fields ), (Function ) this .packetConstructor ));
176223 }
177224 }
178225
179226 public static class PacketValues {
227+ private final List <PacketField <?>> registeredFields ;
180228 private final Map <String , Object > values ;
181229
182- public PacketValues (Map <String , Object > values ) {
230+ public PacketValues (List <PacketField <?>> registeredFields , Map <String , Object > values ) {
231+ this .registeredFields = registeredFields ;
183232 this .values = values ;
184233 }
185234
235+ private void assertKeyRegistered (String key ) {
236+ for (PacketField <?> field : registeredFields ) {
237+ if (field .matches (key )) return ;
238+ }
239+ throw new IllegalArgumentException ("Attempted to look up field '" + key + "', but it is not registered in this packet definition's fields." );
240+ }
241+
186242 public <T > T get (String key , Class <T > clazz ) {
187- return clazz .cast (values .get (key ));
243+ assertKeyRegistered (key );
244+
245+ PacketField <?> targetField = null ;
246+ for (PacketField <?> field : registeredFields ) {
247+ if (field .matches (key )) {
248+ targetField = field ;
249+ break ;
250+ }
251+ }
252+
253+ if (targetField != null ) {
254+ if (values .containsKey (targetField .name ())) return clazz .cast (values .get (targetField .name ()));
255+ for (String alias : targetField .aliases ()) {
256+ if (values .containsKey (alias )) return clazz .cast (values .get (alias ));
257+ }
258+
259+ for (Map .Entry <String , Object > entry : values .entrySet ()) {
260+ if (targetField .matches (entry .getKey ())) return clazz .cast (entry .getValue ());
261+ }
262+ }
263+
264+ return null ;
188265 }
189266
190267 public <T > T getOrElse (String key , Class <T > clazz , T defaultValue ) {
191- Object val = values . get (key );
192- return clazz . isInstance ( val ) ? clazz . cast ( val ) : defaultValue ;
268+ T val = get (key , clazz );
269+ return val != null ? val : defaultValue ;
193270 }
194271
195272 public boolean has (String key ) {
196- return values .containsKey (key );
273+ assertKeyRegistered (key );
274+
275+ PacketField <?> targetField = null ;
276+ for (PacketField <?> field : registeredFields ) {
277+ if (field .matches (key )) {
278+ targetField = field ;
279+ break ;
280+ }
281+ }
282+
283+ if (targetField != null ) {
284+ if (values .containsKey (targetField .name ())) return true ;
285+ for (String alias : targetField .aliases ()) {
286+ if (values .containsKey (alias )) return true ;
287+ }
288+ for (String k : values .keySet ()) {
289+ if (targetField .matches (k )) return true ;
290+ }
291+ }
292+ return false ;
197293 }
198294 }
199295}
0 commit comments