2626import org .bukkit .ChatColor ;
2727import org .bukkit .entity .Player ;
2828
29+ import java .lang .invoke .MethodHandle ;
30+ import java .lang .invoke .MethodHandles ;
31+ import java .lang .invoke .MethodType ;
2932import java .lang .reflect .Array ;
30- import java .lang .reflect .Constructor ;
3133import java .lang .reflect .Field ;
32- import java .lang .reflect .Method ;
3334import java .util .ArrayList ;
3435import java .util .Arrays ;
3536import java .util .Collection ;
3637import java .util .Collections ;
38+ import java .util .HashMap ;
3739import java .util .List ;
40+ import java .util .Map ;
3841import java .util .Objects ;
42+ import java .util .concurrent .ThreadLocalRandom ;
3943
4044/**
4145 * Lightweight packet-based scoreboard API for Bukkit plugins.
4852 */
4953public class FastBoard {
5054
55+ private static final Map <Class <?>, List <Field >> PACKETS = new HashMap <>(8 );
5156 private static final VersionType VERSION_TYPE ;
52- // Packets sending
53- private static final Field PLAYER_CONNECTION ;
54- private static final Method SEND_PACKET ;
55- private static final Method PLAYER_GET_HANDLE ;
56- // Chat components
57+ // Packets and components
5758 private static final Class <?> CHAT_COMPONENT_CLASS ;
58- private static final Method MESSAGE_FROM_STRING ;
59+ private static final MethodHandle MESSAGE_FROM_STRING ;
60+ private static final MethodHandle PLAYER_CONNECTION ;
61+ private static final MethodHandle SEND_PACKET ;
62+ private static final MethodHandle PLAYER_GET_HANDLE ;
5963 // Scoreboard packets
60- private static final Constructor <?> PACKET_SB_OBJ ;
61- private static final Constructor <?> PACKET_SB_DISPLAY_OBJ ;
62- private static final Constructor <?> PACKET_SB_SCORE ;
63- private static final Constructor <?> PACKET_SB_TEAM ;
64+ private static final MethodHandle PACKET_SB_OBJ ;
65+ private static final MethodHandle PACKET_SB_DISPLAY_OBJ ;
66+ private static final MethodHandle PACKET_SB_SCORE ;
67+ private static final MethodHandle PACKET_SB_TEAM ;
6468 // Scoreboard enums
6569 private static final Class <?> ENUM_SB_HEALTH_DISPLAY ;
6670 private static final Class <?> ENUM_SB_ACTION ;
@@ -70,6 +74,9 @@ public class FastBoard {
7074
7175 static {
7276 try {
77+ MethodHandles .Lookup lookup = MethodHandles .lookup ();
78+ MethodType voidType = MethodType .methodType (void .class );
79+
7380 if (FastReflection .nmsOptionalClass ("ScoreboardServer$Action" ).isPresent ()) {
7481 VERSION_TYPE = VersionType .V1_13 ;
7582 } else if (FastReflection .nmsOptionalClass ("IScoreboardCriteria$EnumScoreboardHealthDisplay" ).isPresent ()) {
@@ -78,22 +85,31 @@ public class FastBoard {
7885 VERSION_TYPE = VersionType .V1_7 ;
7986 }
8087
88+ Class <?> craftPlayerClass = FastReflection .obcClass ("entity.CraftPlayer" );
8189 Class <?> craftChatMessageClass = FastReflection .obcClass ("util.CraftChatMessage" );
8290 Class <?> entityPlayerClass = FastReflection .nmsClass ("EntityPlayer" );
8391 Class <?> playerConnectionClass = FastReflection .nmsClass ("PlayerConnection" );
84- Class <?> craftPlayerClass = FastReflection .obcClass ("entity.CraftPlayer" );
92+ Class <?> packetClass = FastReflection .nmsClass ("Packet" );
93+ Class <?> packetSbObjClass = FastReflection .nmsClass ("PacketPlayOutScoreboardObjective" );
94+ Class <?> packetSbDisplayObjClass = FastReflection .nmsClass ("PacketPlayOutScoreboardDisplayObjective" );
95+ Class <?> packetSbScoreClass = FastReflection .nmsClass ("PacketPlayOutScoreboardScore" );
96+ Class <?> packetSbTeamClass = FastReflection .nmsClass ("PacketPlayOutScoreboardTeam" );
8597
86- MESSAGE_FROM_STRING = craftChatMessageClass .getDeclaredMethod ("fromString" , String .class );
98+ MESSAGE_FROM_STRING = lookup . unreflect ( craftChatMessageClass .getMethod ("fromString" , String .class ) );
8799 CHAT_COMPONENT_CLASS = FastReflection .nmsClass ("IChatBaseComponent" );
88-
89- PLAYER_GET_HANDLE = craftPlayerClass .getDeclaredMethod ("getHandle" );
90- PLAYER_CONNECTION = entityPlayerClass .getDeclaredField ("playerConnection" );
91- SEND_PACKET = playerConnectionClass .getDeclaredMethod ("sendPacket" , FastReflection .nmsClass ("Packet" ));
92-
93- PACKET_SB_OBJ = FastReflection .nmsClass ("PacketPlayOutScoreboardObjective" ).getConstructor ();
94- PACKET_SB_DISPLAY_OBJ = FastReflection .nmsClass ("PacketPlayOutScoreboardDisplayObjective" ).getConstructor ();
95- PACKET_SB_SCORE = FastReflection .nmsClass ("PacketPlayOutScoreboardScore" ).getConstructor ();
96- PACKET_SB_TEAM = FastReflection .nmsClass ("PacketPlayOutScoreboardTeam" ).getConstructor ();
100+ PLAYER_GET_HANDLE = lookup .findVirtual (craftPlayerClass , "getHandle" , MethodType .methodType (entityPlayerClass ));
101+ PLAYER_CONNECTION = lookup .findGetter (entityPlayerClass , "playerConnection" , playerConnectionClass );
102+ SEND_PACKET = lookup .findVirtual (playerConnectionClass , "sendPacket" , MethodType .methodType (void .class , packetClass ));
103+ PACKET_SB_OBJ = lookup .findConstructor (packetSbObjClass , voidType );
104+ PACKET_SB_DISPLAY_OBJ = lookup .findConstructor (packetSbDisplayObjClass , voidType );
105+ PACKET_SB_SCORE = lookup .findConstructor (packetSbScoreClass , voidType );
106+ PACKET_SB_TEAM = lookup .findConstructor (packetSbTeamClass , voidType );
107+
108+ for (Class <?> clazz : Arrays .asList (packetSbObjClass , packetSbDisplayObjClass , packetSbScoreClass , packetSbTeamClass )) {
109+ List <Field > fields = Arrays .asList (clazz .getDeclaredFields ());
110+ fields .forEach (field -> field .setAccessible (true ));
111+ PACKETS .put (clazz , fields );
112+ }
97113
98114 if (VersionType .V1_8 .isHigherOrEqual ()) {
99115 String enumSbActionClass = VersionType .V1_13 .isHigherOrEqual ()
@@ -131,13 +147,13 @@ public class FastBoard {
131147 */
132148 public FastBoard (Player player ) {
133149 this .player = Objects .requireNonNull (player , "player" );
134- this .id = "fb-" + Double . toString ( Math . random ()). substring ( 2 , 10 );
150+ this .id = "fb-" + Integer . toHexString ( ThreadLocalRandom . current (). nextInt () );
135151
136152 try {
137153 sendObjectivePacket (ObjectiveMode .CREATE );
138154 sendDisplayObjectivePacket ();
139- } catch (ReflectiveOperationException e ) {
140- throw new RuntimeException (e );
155+ } catch (Throwable t ) {
156+ throw new RuntimeException ("Unable to create scoreboard" , t );
141157 }
142158 }
143159
@@ -170,8 +186,8 @@ public void updateTitle(String title) {
170186
171187 try {
172188 sendObjectivePacket (ObjectiveMode .UPDATE );
173- } catch (ReflectiveOperationException e ) {
174- throw new RuntimeException (e );
189+ } catch (Throwable t ) {
190+ throw new RuntimeException ("Unable to update scoreboard title" , t );
175191 }
176192 }
177193
@@ -226,8 +242,8 @@ public synchronized void updateLine(int line, String text) {
226242 newLines .add (text );
227243
228244 updateLines (newLines );
229- } catch (ReflectiveOperationException e ) {
230- throw new RuntimeException (e );
245+ } catch (Throwable t ) {
246+ throw new RuntimeException ("Unable to update scoreboard lines" , t );
231247 }
232248 }
233249
@@ -312,8 +328,8 @@ public synchronized void updateLines(Collection<String> lines) {
312328 sendTeamPacket (i , TeamMode .UPDATE );
313329 }
314330 }
315- } catch (ReflectiveOperationException e ) {
316- throw new RuntimeException (e );
331+ } catch (Throwable t ) {
332+ throw new RuntimeException ("Unable to update scoreboard lines" , t );
317333 }
318334 }
319335
@@ -366,8 +382,8 @@ public void delete() {
366382 }
367383
368384 sendObjectivePacket (ObjectiveMode .REMOVE );
369- } catch (ReflectiveOperationException e ) {
370- throw new RuntimeException (e );
385+ } catch (Throwable t ) {
386+ throw new RuntimeException ("Unable to delete scoreboard" , t );
371387 }
372388
373389 this .deleted = true ;
@@ -384,16 +400,16 @@ protected boolean hasLinesMaxLength() {
384400 return !VersionType .V1_13 .isHigherOrEqual ();
385401 }
386402
387- private void checkLineNumber (int line , boolean checkMax , boolean checkValid ) {
403+ private void checkLineNumber (int line , boolean checkInRange , boolean checkMax ) {
388404 if (line < 0 ) {
389405 throw new IllegalArgumentException ("Line number must be positive" );
390406 }
391407
392- if (checkMax && line >= this .lines .size ()) {
408+ if (checkInRange && line >= this .lines .size ()) {
393409 throw new IllegalArgumentException ("Line number must be under " + this .lines .size ());
394410 }
395411
396- if (checkValid && line >= ChatColor .values ().length - 1 ) {
412+ if (checkMax && line >= ChatColor .values ().length - 1 ) {
397413 throw new IllegalArgumentException ("Line number is too high: " + this .lines .size ());
398414 }
399415 }
@@ -414,8 +430,8 @@ private String getLineByScore(List<String> lines, int score) {
414430 return lines .get (lines .size () - score - 1 );
415431 }
416432
417- private void sendObjectivePacket (ObjectiveMode mode ) throws ReflectiveOperationException {
418- Object packet = PACKET_SB_OBJ .newInstance ();
433+ private void sendObjectivePacket (ObjectiveMode mode ) throws Throwable {
434+ Object packet = PACKET_SB_OBJ .invoke ();
419435
420436 setField (packet , String .class , this .id );
421437 setField (packet , int .class , mode .ordinal ());
@@ -433,17 +449,17 @@ private void sendObjectivePacket(ObjectiveMode mode) throws ReflectiveOperationE
433449 sendPacket (packet );
434450 }
435451
436- private void sendDisplayObjectivePacket () throws ReflectiveOperationException {
437- Object packet = PACKET_SB_DISPLAY_OBJ .newInstance ();
452+ private void sendDisplayObjectivePacket () throws Throwable {
453+ Object packet = PACKET_SB_DISPLAY_OBJ .invoke ();
438454
439455 setField (packet , int .class , 1 ); // Position (1: sidebar)
440456 setField (packet , String .class , this .id ); // Score Name
441457
442458 sendPacket (packet );
443459 }
444460
445- private void sendScorePacket (int score , ScoreboardAction action ) throws ReflectiveOperationException {
446- Object packet = PACKET_SB_SCORE .newInstance ();
461+ private void sendScorePacket (int score , ScoreboardAction action ) throws Throwable {
462+ Object packet = PACKET_SB_SCORE .invoke ();
447463
448464 setField (packet , String .class , getColorCode (score ), 0 ); // Player Name
449465
@@ -461,13 +477,13 @@ private void sendScorePacket(int score, ScoreboardAction action) throws Reflecti
461477 sendPacket (packet );
462478 }
463479
464- private void sendTeamPacket (int score , TeamMode mode ) throws ReflectiveOperationException {
480+ private void sendTeamPacket (int score , TeamMode mode ) throws Throwable {
465481 if (mode == TeamMode .ADD_PLAYERS || mode == TeamMode .REMOVE_PLAYERS ) {
466482 throw new UnsupportedOperationException ();
467483 }
468484
469485 int maxLength = hasLinesMaxLength () ? 16 : 1024 ;
470- Object packet = PACKET_SB_TEAM .newInstance ();
486+ Object packet = PACKET_SB_TEAM .invoke ();
471487
472488 setField (packet , String .class , this .id + ':' + score ); // Team name
473489 setField (packet , int .class , mode .ordinal (), VERSION_TYPE == VersionType .V1_8 ? 1 : 0 ); // Update mode
@@ -517,14 +533,14 @@ private void sendTeamPacket(int score, TeamMode mode) throws ReflectiveOperation
517533 sendPacket (packet );
518534 }
519535
520- private void sendPacket (Object packet ) throws ReflectiveOperationException {
536+ private void sendPacket (Object packet ) throws Throwable {
521537 if (this .deleted ) {
522538 throw new IllegalStateException ("This FastBoard is deleted" );
523539 }
524540
525541 if (this .player .isOnline ()) {
526542 Object entityPlayer = PLAYER_GET_HANDLE .invoke (this .player );
527- Object playerConnection = PLAYER_CONNECTION .get (entityPlayer );
543+ Object playerConnection = PLAYER_CONNECTION .invoke (entityPlayer );
528544 SEND_PACKET .invoke (playerConnection , packet );
529545 }
530546 }
@@ -533,27 +549,25 @@ private void setField(Object object, Class<?> fieldType, Object value) throws Re
533549 setField (object , fieldType , value , 0 );
534550 }
535551
536- private void setField (Object object , Class <?> fieldType , Object value , int count ) throws ReflectiveOperationException {
552+ private void setField (Object packet , Class <?> fieldType , Object value , int count ) throws ReflectiveOperationException {
537553 int i = 0 ;
538- for (Field field : object . getClass (). getDeclaredFields ( )) {
554+ for (Field field : PACKETS . get ( packet . getClass () )) {
539555 if (field .getType () == fieldType && count == i ++) {
540- field .setAccessible (true );
541- field .set (object , value );
556+ field .set (packet , value );
542557 }
543558 }
544559 }
545560
546- private void setComponentField (Object object , String value , int count ) throws ReflectiveOperationException {
561+ private void setComponentField (Object packet , String value , int count ) throws Throwable {
547562 if (VERSION_TYPE != VersionType .V1_13 ) {
548- setField (object , String .class , value , count );
563+ setField (packet , String .class , value , count );
549564 return ;
550565 }
551566
552567 int i = 0 ;
553- for (Field field : object . getClass (). getDeclaredFields ( )) {
568+ for (Field field : PACKETS . get ( packet . getClass () )) {
554569 if ((field .getType () == String .class || field .getType () == CHAT_COMPONENT_CLASS ) && count == i ++) {
555- field .setAccessible (true );
556- field .set (object , Array .get (MESSAGE_FROM_STRING .invoke (null , value ), 0 ));
570+ field .set (packet , Array .get (MESSAGE_FROM_STRING .invoke (value ), 0 ));
557571 }
558572 }
559573 }
0 commit comments