Compare commits

...
296 Commits
Author SHA1 Message Date
NEZNAMY 3c0b44b365 4.1.0 2023-12-05 20:45:58 +01:00
NEZNAMY ec4aec50ef Mark 1.20.3 as latest known version 2023-12-05 18:55:42 +01:00
NEZNAMY 9c86cfe056 Add Vault to softdepend to avoid console warn 2023-12-05 18:53:49 +01:00
NEZNAMY 6bb2337276 Print warn if RedisSupport received unknown message instead of throwing a NPE 2023-12-05 18:49:53 +01:00
NEZNAMY 4702ca255b Update Fabric to 1.20.3 2023-12-05 18:48:54 +01:00
NEZNAMY ab47aa36cc Confirm network id of 1.20.3 2023-12-05 18:40:55 +01:00
NEZNAMY 4bb14c4a4e Rename new config options 2023-12-05 17:27:24 +01:00
NEZNAMY 1dd75162f9 Add a version to list 2023-12-04 21:09:37 +01:00
NEZNAMY a2242e3c27 Move server version check 2023-12-04 20:52:29 +01:00
NEZNAMY 26ecf494a9 Little fix 2023-12-04 20:45:51 +01:00
NEZNAMY 659af5d0b3 Completely get rid of NMSStorage class 2023-12-04 20:33:23 +01:00
NEZNAMY 382a5ee7fc Move packet sending method to a separate class and make loading optional 2023-12-04 19:17:51 +01:00
NEZNAMY 7364496844 Move ping retrieving method to a separate class 2023-12-04 17:52:32 +01:00
NEZNAMY d342316b87 Reduce dependence on the main NMS class on Bukkit 2023-12-04 17:29:25 +01:00
NEZNAMY b38141da88 Add &f at the beginning of scoreboard lines for 1.13+ players to prevent color field from coloring text if no color is used 2023-12-04 16:40:06 +01:00
NEZNAMY 98f80f7dae Remove unused methods 2023-12-03 21:36:22 +01:00
NEZNAMY c134ec6bfe Simplify class loading 2023-12-03 21:32:00 +01:00
NEZNAMY 4e3f759243 Use NMS prefix only once instead of every usage 2023-12-03 21:03:08 +01:00
NEZNAMY a5f2e0157b Make player game profile getting optional instead of throwing a compatibility error 2023-12-03 20:52:29 +01:00
NEZNAMY fd21032e51 Make pipeline injection compatibility optional and print a warning if a compatibility error is found 2023-12-03 19:22:33 +01:00
NEZNAMY c4f5f9b7d3 Update comment 2023-12-03 18:11:40 +01:00
NEZNAMY 6baf5a7068 Fix NPE 2023-12-03 17:28:41 +01:00
NEZNAMY d3460174de Enable fallback solutions for Scoreboard implementation on Bukkit 2023-12-03 16:07:05 +01:00
NEZNAMY f9f19628ca Fix BukkitScoreboard class issues 2023-12-03 15:38:22 +01:00
NEZNAMY 04e48fea5d Clean up method 2023-12-01 20:22:41 +01:00
NEZNAMY 3e99e22fce Simplify 2023-12-01 19:18:58 +01:00
NEZNAMY 0b244d5ff7 Refactor PacketScoreboard class and draft 1.20.3 support 2023-12-01 19:15:56 +01:00
NEZNAMY 703f2ff316 Fix potentially wrong uuid write 2023-12-01 09:35:47 +01:00
NEZNAMY 34a2bbdbae Simplify 2023-12-01 09:34:14 +01:00
NEZNAMY cd8697c1a4 Add exclusions for some dependencies 2023-11-30 19:15:20 +01:00
NEZNAMY ed2dc2dd51 Avoid rare error on Velocity 2023-11-30 18:50:53 +01:00
NEZNAMY 2dfdc1b993 Update Fabric to 1.20.3-rc1 2023-11-30 18:27:49 +01:00
NEZNAMY 6f2d7f1194 Fix 1.20.3 client crash on BungeeCord 2023-11-30 17:52:01 +01:00
NEZNAMY 30009ef127 Attempt to fix error (#1146) 2023-11-29 17:19:11 +01:00
NEZNAMY 051a422331 Update Fabric to 1.20.3-pre4 2023-11-29 16:56:35 +01:00
NEZNAMY fab6489e9c Fix compilation 2023-11-28 16:12:27 +01:00
NEZNAMY a448614e4a Slightly better organize method 2023-11-27 18:54:28 +01:00
NEZNAMY 84ad874b2a Split complex method into 2 2023-11-27 18:43:43 +01:00
NEZNAMY 19ba978291 Simplify 2023-11-27 18:16:48 +01:00
NEZNAMY e304469e55 Remove star import 2023-11-27 18:10:28 +01:00
NEZNAMY 7ce9e2592a Update README.md 2023-11-27 18:07:35 +01:00
NEZNAMY 681b812c7b Rename feature in /tab cpu output 2023-11-26 13:59:29 +01:00
NEZNAMY ee1ab6785d Rename objective 2023-11-26 12:36:12 +01:00
NEZNAMY 57704b6622 Move a few classes around 2023-11-26 11:15:23 +01:00
NEZNAMY 7fe804e0fc Fix disconnect on bungeecord on 1.20.3 due to null text in component 2023-11-25 22:24:02 +01:00
NEZNAMY a71529b641 Simplify 2023-11-25 18:07:47 +01:00
NEZNAMY 4055173bdb Replace .length() == 0 with .isEmpty() 2023-11-25 17:09:20 +01:00
NEZNAMY 3f8841a276 Update to latest BungeeCord (for the 3rd time in a row) 2023-11-25 17:04:34 +01:00
NEZNAMY 15c38851bc Add misconfiguration check and prevent startup error 2023-11-25 16:56:11 +01:00
NEZNAMY 05030ae817 Update outdated message 2023-11-25 13:05:57 +01:00
NEZNAMY 255fa11371 Fix NPE 2023-11-24 18:41:08 +01:00
NEZNAMY a074c0bf74 Simplify 2023-11-24 10:49:12 +01:00
NEZNAMY 6ccd649fc4 Remove unused imports 2023-11-24 10:46:59 +01:00
NEZNAMY 21dbea07dd Add support for nested relational placeholders (#1153) 2023-11-23 20:28:21 +01:00
NEZNAMY 4c21d9d127 Remove unused import 2023-11-23 20:27:16 +01:00
NEZNAMY 766002b079 Delete aligned suffix feature, since 1.20.3 now has it naturally using playerlist objective (rip 1.20.2-) 2023-11-23 19:39:47 +01:00
NEZNAMY 2ce430ec41 Add support for all Fabric versions in a single jar 2023-11-23 19:18:31 +01:00
NEZNAMY cf2b151045 Bump Velocity version and use new method 2023-11-19 16:57:22 +01:00
NEZNAMY 92216179df Replace try/catch with SneakyThrows annotation 2023-11-19 16:27:06 +01:00
NEZNAMY a2a95b8591 Replace scoreboard enums with fields 2023-11-19 10:50:54 +01:00
NEZNAMY 4a22dc111a Fix attempt to send header/footer to 1.7 players on bungeecord, causing disconnect 2023-11-19 10:16:27 +01:00
NEZNAMY 5df7ecc245 Fix compatibility error message 2023-11-18 20:49:24 +01:00
NEZNAMY d4ef520f26 Print required bridge version in debug command if not connected 2023-11-18 20:39:09 +01:00
NEZNAMY e0f8737865 Use direct code for bungeecord instead of reflection and drop support for old versions 2023-11-18 20:37:37 +01:00
NEZNAMY 37b13a54a7 Create classes for plugin message types instead of writing all content on each usage + bump bridge version to 5 due to braking changes 2023-11-18 14:44:22 +01:00
NEZNAMY d9d28d31d0 Prepare for new 1.20.3 scoreboard features 2023-11-17 20:21:17 +01:00
NEZNAMY 66bf5da3f5 Delete CustomLine class due to not being viable 2023-11-17 11:41:11 +01:00
NEZNAMY 840c721afc Simplify, the performance gain was pretty much zero 2023-11-17 10:59:28 +01:00
NEZNAMY 859e705a48 Drop multi-version code for fabric (hard to maintain + a lot of it didn't actually work) 2023-11-16 22:10:45 +01:00
NEZNAMY 7375070b21 oops (#1144) 2023-11-15 16:53:42 +01:00
NEZNAMY 3e2eda6a71 Add ClickEvent to components 2023-11-11 20:17:22 +01:00
NEZNAMY 798d327dcb Fix plugin not removing redundant character width overrides from config 2023-11-11 18:11:37 +01:00
NEZNAMY f5ca9139e6 Fix double bossbar on Velocity on server switch (#1139) 2023-11-11 14:02:07 +01:00
NEZNAMY f35e8cfa51 Improve vanish tracking for nametag feature 2023-11-10 21:31:21 +01:00
NEZNAMY 0f6953711b Update build.yml 2023-11-10 15:57:34 +01:00
NEZNAMY e2c5e6f9ac Allow empty text for fixed slots in layout 2023-11-10 15:46:26 +01:00
NEZNAMY 5ef5d6ddba Update README.md 2023-11-10 12:51:41 +01:00
NEZNAMY 5b19a5d798 Only unregister team for other players when player is vanishing 2023-11-10 11:02:35 +01:00
NEZNAMY bf804e8923 Merge pull request #1137 from NEZNAMY/vanish-patch
Alternate implementation of #1136
2023-11-09 18:53:45 +01:00
NEZNAMY f47b8b5528 Alternate implementation of #1136 2023-11-09 18:30:56 +01:00
NEZNAMY c353f6787a Fix endsWith condition (#1134) 2023-11-07 17:37:52 +01:00
NEZNAMY 1b7d5c1a5c Add support for latest BungeeCord (yes, again), as well as some previous versions and remove compatibility check. 2023-11-06 17:12:26 +01:00
NEZNAMY 956456f157 4.1.0-SNAPSHOT 2023-11-06 13:41:51 +01:00
NEZNAMY 49077bd798 Create enum constants for packet actions for better readability and understanding of code 2023-11-05 21:16:07 +01:00
NEZNAMY 019a0287b2 4.0.9 2023-11-05 14:27:55 +01:00
NEZNAMY a71dac28c0 Hotfix after adding support for latest bungeecord (#1132) 2023-11-05 14:21:25 +01:00
NEZNAMY 155886e000 Fix typo 2023-11-04 15:39:45 +01:00
NEZNAMY ed4c303d2e 4.0.8 2023-11-04 14:55:32 +01:00
NEZNAMY 9da0db5f74 Disable creation of blank issues 2023-11-04 11:55:58 +01:00
NEZNAMY 562a973c84 Create new issue template 2023-11-04 11:53:10 +01:00
NEZNAMY 967980ddf7 Update bug_report.yml 2023-11-03 21:27:24 +01:00
NEZNAMY 81d3675a87 Check for bundle packet on 1.19.4+ when reading entity packets for unlimited nametags 2023-11-03 21:25:21 +01:00
NEZNAMY 7be786bbaf Fix vanish change being called on join even if vanish status did not change 2023-11-02 17:24:02 +01:00
NEZNAMY f41c9be1f7 Fix race condition on Velocity causing errors on shutdown 2023-11-02 17:21:48 +01:00
NEZNAMY e88062d028 Fix unvanish method not being called on server switch when clearing vanish status 2023-11-02 16:08:15 +01:00
NEZNAMY edb2828643 Fix wrong java instruction 2023-11-02 15:58:34 +01:00
NEZNAMY 58c189cd0e Catch all exceptions/errors on load/unload to prevent players from getting kicked on bungeecord if something changes again in the future 2023-10-31 16:41:29 +01:00
NEZNAMY 012994847e Unregister teams of players who vanished for those who cannot see vanished players to prevent 3rd party clients from revealing vanished players by finding out who is vanished and who actually left. 2023-10-30 20:39:21 +01:00
NEZNAMY d87801be5f Add support for latest BungeeCord (#1123) 2023-10-30 16:02:31 +01:00
NEZNAMY 6d78c0c104 Create a class storing values of minecraft limitations 2023-10-29 15:25:00 +01:00
NEZNAMY 3a83519122 Fix tablist entry related issues on both BungeeCord and Velocity (each one was affected by a different issue) 2023-10-27 22:27:42 +02:00
NEZNAMY a9f940ebed Avoid attempts to send packets to offline players on bungeecord 2023-10-27 19:17:56 +02:00
NEZNAMY db73d9ef81 Simplify 2023-10-24 17:46:25 +02:00
NEZNAMY e2bd21c717 Cache adventure components & use Paper API for header/footer by default. This has reduced CPU usage of the feature by 50% in the test I have done. 2023-10-24 17:40:22 +02:00
NEZNAMY 871215b7b5 Do not attempt to send scoreboard-related packets between login packet send and process 2023-10-24 15:18:17 +02:00
NEZNAMY a6153ef46f Force-fix NPE that doesn't make sense 2023-10-24 13:42:55 +02:00
NEZNAMY 35475a6103 Fix the previous error on Fabric & Sponge as well 2023-10-24 13:42:35 +02:00
NEZNAMY bf77ffc1a5 Fix error (#1117) 2023-10-22 23:01:53 +02:00
NEZNAMY 7a64a9a534 Code style 2023-10-22 20:08:27 +02:00
NEZNAMY 68e057b99c Fabric - Implement world name recognition and everything related to it (#1115) 2023-10-22 17:44:34 +02:00
NEZNAMY 7338afff96 Fix error when using 3 years old version of PlaceholderAPI 2023-10-21 11:27:38 +02:00
NEZNAMY 97ae7813aa Add vanish status updating to RedisBungee (#1109) 2023-10-18 16:48:01 +02:00
NEZNAMY c0f3667ac1 4.0.8-SNAPSHOT 2023-10-18 16:47:37 +02:00
NEZNAMY d5c5502abe 4.0.7 2023-10-16 20:45:35 +02:00
NEZNAMY 194e8ffbd9 Code style 2023-10-15 20:39:06 +02:00
NEZNAMY ef39458abd With all the problems on 1.20.2 BungeeCord it turns out that Velocity isn't that bad. 2023-10-15 17:39:21 +02:00
NEZNAMY 6a6907fb0a Process login packet with a delay to avoid problems 2023-10-15 17:33:06 +02:00
NEZNAMY 8bb7c4a4a6 Adapt redis support subfeatures to recent changes 2023-10-15 17:00:55 +02:00
NEZNAMY 91700e7ed0 Fix global playerlist not readding players for 1.20.2 clients on server switch 2023-10-15 17:00:16 +02:00
NEZNAMY 661c2e6fc3 Simplify 2023-10-15 15:37:38 +02:00
NEZNAMY 3d053a6149 Implement backup solutions if header/footer fields fail to initialize 2023-10-15 15:35:25 +02:00
NEZNAMY 34179e4f62 Fix startup on old versions due to wrong enum names 2023-10-15 14:56:40 +02:00
NEZNAMY a23e5d9f50 Remove toComponent method from Platform interface and only keep it in platforms that actually use it 2023-10-15 14:26:09 +02:00
NEZNAMY 1cbcbf70d9 Fix aligned playerlist attempting to send packets to players who just left 2023-10-15 14:08:35 +02:00
NEZNAMY 0a7371aaec Micro optimizations with enum conversions 2023-10-14 13:55:52 +02:00
NEZNAMY da13e2d861 Fix Bridge still injecting placeholders that are not used on proxy anymore 2023-10-13 12:27:12 +02:00
NEZNAMY 5b18fa8c25 Fix PingSpoof spoofing everything instead of just online players (or their respective layout entries) 2023-10-13 11:34:38 +02:00
NEZNAMY 347231850a Fix error 2023-10-13 09:51:40 +02:00
NEZNAMY b1f9bdc26c Resend BossBar on server switch for 1.20.2 players as Login packet now clears it 2023-10-12 20:51:50 +02:00
NEZNAMY 03c9b95041 Listen to login packet instead of server switch delay for 1.20.2 players for better consistency (also fix layout not being resent on server switch on 1.20.2) 2023-10-12 19:20:22 +02:00
NEZNAMY 6e7e1d783b Fix rare placeholder formatting error 2023-10-12 16:33:58 +02:00
NEZNAMY cfb9b0a029 Fix compatibility with some random 1.8.8 overpriced fork (#1105) 2023-10-12 14:32:21 +02:00
NEZNAMY b7ea6e5af6 Fix wrong exception being caught causing the try/catch to not work (#1092) 2023-10-12 09:33:50 +02:00
NEZNAMY 44104df712 Change error message about invalid team/objective actions to a debug message. They don't cause any issues as they are properly caught and debugging is hard, especially if reporter has no interest in cooperating (#1083) 2023-10-11 19:35:57 +02:00
NEZNAMY 5de20d1c58 Add try/catch for BungeeCord sendPacket method to avoid a NPE in BungeeCord code (#1090, #1092) 2023-10-11 16:24:43 +02:00
NEZNAMY d156303740 Remove warn when placeholders use too much CPU. Since they have been moved to another thread, they will no longer prevent the plugin from working. While the warning can be useful for more advanced users, this is a public plugin used by many and most of them don't care about performance at all. This has resulted in having to answer people who would have never contacted me otherwise. 2023-10-10 15:04:00 +02:00
NEZNAMY 6e36eb3554 Add back fabric class for multi-version code for easier backport 2023-10-09 21:49:21 +02:00
NEZNAMY f7f3891817 Avoid attempts to send plugin message to offline players on bungeecord (#1090? #1092?) that may? cause errors on 1.20.2 2023-10-08 12:41:12 +02:00
NEZNAMY 1ec28856ff Avoid attempts to send packets to offline players on bungeecord (#1090? #1092?) that may? cause errors on 1.20.2 2023-10-06 11:41:32 +02:00
NEZNAMY 9658e0b74d Add debug message showing how long bridge took to respond to player join plugin message 2023-10-06 11:31:09 +02:00
NEZNAMY 86cd8d4151 Make feature loading more readable 2023-10-05 17:39:27 +02:00
NEZNAMY 2418eb206e Improve unknown version handling code & error messages 2023-10-05 13:57:56 +02:00
NEZNAMY 08fa715d26 Fix runtime warns false triggering on proxy when placeholders are not loaded due to bridge not having a chance to respond yet (#1088) 2023-10-04 14:16:08 +02:00
NEZNAMY 52426de283 Remove double method call 2023-10-04 14:04:57 +02:00
NEZNAMY 64b3da14c4 It's called disable condition, not display condition 2023-10-02 20:38:21 +02:00
NEZNAMY bf7341c52c Change way getProfile method is retrieved (#1089) 2023-10-02 19:09:53 +02:00
NEZNAMY e3490564a2 4.0.7-SNAPSHOT 2023-10-02 19:07:05 +02:00
NEZNAMY 6218f0ed16 4.0.6 2023-10-01 16:35:39 +02:00
NEZNAMY 54e8e1ea98 Add a temporary workaround for https://github.com/SpigotMC/BungeeCord/issues/3542 2023-10-01 16:35:33 +02:00
NEZNAMY 4cd50fb3d5 Use sendPacketQueued method on BungeeCord to avoid error on 1.20.2 clients 2023-10-01 16:31:25 +02:00
NEZNAMY 590286a6c2 4.0.5 2023-09-27 21:12:58 +02:00
NEZNAMY 6c96650dcc Add support for new Spigot build that uses mojang mappings for fields and methods 2023-09-27 21:12:22 +02:00
NEZNAMY 7c83a74fd8 Fix plugin trying to send scoreboard packets to players who left already 2023-09-27 19:20:32 +02:00
NEZNAMY 7b3f60cb3b 4.0.5-SNAPSHOT 2023-09-23 16:37:02 +02:00
NEZNAMY bfc845088a Merge pull request #1084 from GsTio86/master
Fix Arclight compatibility
2023-09-23 12:36:32 +02:00
Galen Huang e67d93a592 Update DataWatcher.java 2023-09-23 16:58:19 +08:00
gstio86 b518b980ae Fix Arclight compatibility 2023-09-23 14:35:06 +08:00
NEZNAMY 744bc1403a Remove tps placeholder from config entirely, the negation was wrong as well 2023-09-22 11:01:35 +02:00
NEZNAMY e11113073f 4.0.4 2023-09-22 09:00:37 +02:00
NEZNAMY 3615818812 Add condition to default config to prevent %tps% being displayed on BungeeCord in scoreboard, as the message is too long 2023-09-22 08:59:31 +02:00
NEZNAMY 0cdef12caf Add support for BungeeCord 1.20.2 2023-09-22 08:56:19 +02:00
NEZNAMY dda8c6c053 Fix another error on Bukkit 1.20.2 2023-09-21 22:21:46 +02:00
NEZNAMY 8c3327f553 Fix error on Bukkit 1.20.2 2023-09-21 20:49:29 +02:00
NEZNAMY c037a14e1f Update Bukkit to 1.20.2 2023-09-21 19:45:06 +02:00
NEZNAMY bd661360ee Delete %server_uptime% from default config as it's a PlaceholderAPI placeholder, which is not always guaranteed to work. (#1077) 2023-09-21 18:33:27 +02:00
NEZNAMY 317fabbc87 Update Fabric to 1.20.2 2023-09-21 18:31:49 +02:00
NEZNAMY 87161446c5 Add 1.20.2 to protocol versions 2023-09-21 18:25:22 +02:00
NEZNAMY 7db8b2e84b Send warn if trying to use parse command from the console with "me" player name argument 2023-09-21 15:04:03 +02:00
NEZNAMY 5f3fa2b317 Layout - Fix ROWS direction type not working properly & avoid real players appearing on top when teams are disabled 2023-09-19 22:14:37 +02:00
NEZNAMY 64c15730a6 Add missing correct Placeholder#update implementations (#1075) 2023-09-17 18:48:41 +02:00
NEZNAMY e5ab46b8ad Initialize scoreboard placeholders to avoid them rarely not parsing due to not being set 2023-09-17 15:01:51 +02:00
NEZNAMY 3060d8cc2b Add override for %player_ping% placeholder because 1.20.2 will have a breaking change to prevent github issues reporting server expansion not supporting new version 2023-09-16 10:30:05 +02:00
NEZNAMY f5d6ba462e Remove unnecessary fully qualified class names 2023-09-15 15:21:39 +02:00
NEZNAMY 81fdeece96 Port new Velocity anti-override to Sponge as well 2023-09-15 14:21:20 +02:00
NEZNAMY 8f60439ef3 Fix multiple sorting types with same priority separated with | not actually working 2023-09-14 16:13:44 +02:00
NEZNAMY c5420ead90 Send gamemode update packet on world switch when spectator fix is enabled as the server may resend gamemode on world switch back to spectator (#1069) 2023-09-13 16:55:21 +02:00
NEZNAMY 99d372d03c Fix refresh interval config conversion ignoring random invalid user-made maps and then causing error on load 2023-09-12 14:32:15 +02:00
NEZNAMY 1219a9822f Remove unused dependency 2023-09-10 10:42:37 +02:00
NEZNAMY d40c182af9 Clean up repositories 2023-09-10 10:39:12 +02:00
NEZNAMY 785e986a72 Fix component cache including child components 2023-09-09 21:04:41 +02:00
NEZNAMY e8b80f455b Replace converter methods with arrays for fast access 2023-09-09 11:33:16 +02:00
NEZNAMY 2d545f1bf1 Fix potential blocking of other plugins in new anti-override for Velocity 2023-09-09 08:22:45 +02:00
NEZNAMY d4713dc7f4 Use direct code for LibsDisguises hook instead of reflection and move it into a separate class 2023-09-08 23:54:21 +02:00
NEZNAMY cd96c13670 Remove unused imports 2023-09-08 23:07:13 +02:00
NEZNAMY 808607faf0 Add try/catch for Fabric loading to send warn on unsupported versions instead of throwing an error (#1068) 2023-09-08 21:30:54 +02:00
NEZNAMY bc403f5c23 Fix ConcurrentModificationException on Velocity 2023-09-08 19:04:00 +02:00
NEZNAMY bfc67e1953 Allow the new anti-override to be disabled with standard settings 2023-09-08 08:21:42 +02:00
NEZNAMY a186c16c34 Implement anti-override for tablist display names on Velocity 2023-09-07 21:37:33 +02:00
NEZNAMY f9be9d4c4b Improve check to reduce errors thrown 2023-09-07 18:09:38 +02:00
NEZNAMY 79fc3ce33f Fix BossBars getting resent when output of placeholder used in display condition changed even if condition result didn't change (#1065) 2023-09-06 21:14:35 +02:00
NEZNAMY 055ff8749e Add support for custom fonts in configuration 2023-09-05 21:38:38 +02:00
NEZNAMY d36410e851 Simplify 2023-09-05 21:32:01 +02:00
NEZNAMY 0fed5dca7f Add font field and conversion to component class and quality of life changes for component conversion 2023-09-05 15:30:42 +02:00
NEZNAMY dd34ffc5b2 Create internal enum for scoreboard display type instead of using a boolean 2023-09-04 21:00:17 +02:00
NEZNAMY 2cd5ac85f9 Simplify 2023-09-04 18:20:17 +02:00
NEZNAMY 571f949f50 Remove unnecessary player version check 2023-09-04 17:58:38 +02:00
NEZNAMY 81e32957b2 Fix startup error 2023-09-04 17:57:20 +02:00
NEZNAMY 4639016ffb Preload NMS GameModes instead of calling Enum#valueOf every time 2023-09-04 17:41:26 +02:00
NEZNAMY dabc2b49dc Remove unused fields and methods 2023-09-03 17:52:29 +02:00
NEZNAMY 2dec389c8c Load NMS method by parameter types instead of name 2023-09-01 11:26:05 +02:00
NEZNAMY bfe5b5960e Fix error in per world playerlist on <1.8 2023-08-31 21:04:36 +02:00
NEZNAMY e352967a17 Improve parse command and fix color-related issues on bukkit 2023-08-31 18:48:04 +02:00
NEZNAMY 930917f285 Fill auto-complete suggestions in plugin's PlaceholderAPI expansion 2023-08-30 21:04:49 +02:00
NEZNAMY b9747f7fbe Fix NPE when user deletes default-refresh-interval option from config (1052) 2023-08-28 13:39:39 +02:00
NEZNAMY 5ac9dcec37 Fix startup on Sponge 7 2023-08-28 13:00:11 +02:00
NEZNAMY 8328b43783 Fix NPE when using relational placeholder with multiple proxies 2023-08-25 22:28:23 +02:00
NEZNAMY 131fcfe144 Remove redundant annotation 2023-08-24 17:36:16 +02:00
NEZNAMY 709cb967b5 Fix plugin sometimes trying to format tablist entries for 1.7 players 2023-08-24 12:16:16 +02:00
NEZNAMY 3370b83087 Fix issue in config conversion 2023-08-24 12:06:04 +02:00
NEZNAMY 5c44bd500c Simplify 2023-08-24 10:05:21 +02:00
NEZNAMY ab8a345944 Small style changes 2023-08-24 09:45:23 +02:00
NEZNAMY 337dd56c87 Simplify 2023-08-24 09:38:54 +02:00
NEZNAMY de78ae30db Copy the previous fix to aligned playerlist as well 2023-08-22 14:29:04 +02:00
NEZNAMY 3f64185389 Fix disable condition not being respected in playerlist in one method (#1037) 2023-08-22 14:23:32 +02:00
NEZNAMY 5077cbbc9a Return empty list instead of new list 2023-08-22 14:01:50 +02:00
NEZNAMY 4d522ba5f5 Improve bossbar command to match scoreboard command features 2023-08-22 13:57:45 +02:00
NEZNAMY 4f1180b3f5 Boost performance of tablist getter on BungeeCord 2023-08-17 17:53:22 +02:00
NEZNAMY 577a3f2551 Add support for Mohist 1.18.2 2023-08-13 17:54:42 +02:00
NEZNAMY 8623ab053e Remove some unnecessary local variables 2023-08-12 20:51:25 +02:00
NEZNAMY 6d0f99eaf1 Change WeakHashMap key to use own player object instead of server's to prevent map getting big when another plugin has a memory leak 2023-08-12 19:04:30 +02:00
NEZNAMY f9490d2652 Move field into local variable 2023-08-12 15:08:15 +02:00
NEZNAMY 9d321e4a31 Remove unused parameter and simplify 2023-08-11 22:12:00 +02:00
NEZNAMY b8c2625b80 Only show placeholder retrieve time message if debug is enabled 2023-08-09 20:22:38 +02:00
NEZNAMY 8e42f328dc Register plugin's PlaceholderAPI expansion automatically without having to enable a config option 2023-08-09 20:13:26 +02:00
NEZNAMY 0c6b4efba5 Simplify 2023-08-09 17:02:25 +02:00
NEZNAMY 0821ed4dae Bukkit - Make NMS loading less dependent on the main NMS class 2023-08-09 17:00:20 +02:00
NEZNAMY 8bc38e25f9 Fix empty line detection sometimes not working 2023-08-07 21:51:43 +02:00
NEZNAMY 7a7a4e6da2 Global playerlist - Do not remove & add self on server switch 2023-08-07 14:04:25 +02:00
NEZNAMY f7d703ed1e Placeholders - Decrease return time warn threshold from 1000ms to 50ms 2023-08-07 13:40:35 +02:00
NEZNAMY 443d7fd082 Merge branch 'master' of https://github.com/NEZNAMY/TAB 2023-08-07 13:38:47 +02:00
NEZNAMY b6114625c1 Remove duplicate check 2023-08-07 12:18:13 +02:00
NEZNAMY 4a3eb78f12 Add back ping spoof feature 2023-08-06 11:32:02 +02:00
NEZNAMY fa117a671a Simplify placeholder registration 2023-08-05 23:04:36 +02:00
NEZNAMY 6448fb975a Fix scoreboard title limit 2023-08-05 14:41:40 +02:00
NEZNAMY 1c685ce0bb Bukkit - Properly play around Bukkit API limits in scoreboard 2023-08-05 14:36:14 +02:00
NEZNAMY 1662fa8d60 Bukkit - Implement scoreboard handler using Bukkit API (still unused) 2023-08-05 13:23:03 +02:00
NEZNAMY 2787b1735b Header/Footer - Fix being sent on server switch even if disable condition is met & fix being sent twice 2023-08-04 22:07:06 +02:00
NEZNAMY e58390e30b Add some missing NotNull / Nullable annotations 2023-08-04 20:43:58 +02:00
NEZNAMY 41a10a61bf Small refactoring 2023-08-03 22:56:05 +02:00
NEZNAMY fdcc7d5e7c Global Playerlist - Add support for * symbol in server-groups 2023-08-01 13:35:06 +02:00
NEZNAMY 50b39e462f Remove unnecessary full manual import 2023-07-28 23:18:49 +02:00
NEZNAMY 4bc781f0da Refresh placeholders in a different thread, so they can no longer kill the plugin 2023-07-28 22:33:33 +02:00
NEZNAMY aca4c00083 Do not refresh placeholders in PlaceholderManager on join, as it can cause error when updating nested placeholder in a feature that's not loaded yet 2023-07-28 17:17:07 +02:00
NEZNAMY 77b820ad4d Do not spawn armor stands of dead players on join (#1005) 2023-07-28 17:02:42 +02:00
NEZNAMY 328f604236 Add pipeline injection for fabric 2023-07-28 17:02:13 +02:00
NEZNAMY 2f77bf3d10 Create a separate class for CPU report 2023-07-28 13:05:04 +02:00
NEZNAMY 23eb55aa06 Add back support for mojang-mapped server jars 2023-07-26 16:19:21 +02:00
NEZNAMY 32b7356bc0 Send console hint if both layout and prevent-spectator-effect are enabled 2023-07-25 18:22:34 +02:00
NEZNAMY 6091641ed9 Add warn if both layout and yellow-number features are enabled 2023-07-25 18:16:01 +02:00
NEZNAMY e368213d18 Add misconfiguration check for scoreboard chaining 2023-07-25 15:17:41 +02:00
NEZNAMY 58f9bb6fca Add unlimited nametag mode to Fabric (with a few missing things) 2023-07-24 22:26:24 +02:00
NEZNAMY 79798199b8 More abstraction of unlimited nametag mode 2023-07-24 20:53:14 +02:00
NEZNAMY c8d20f8479 Move NMS loading of classes 2023-07-24 17:59:17 +02:00
NEZNAMY c0498a60b4 Remove some console messages on startup 2023-07-24 16:46:00 +02:00
NEZNAMY faa01ddfc2 Add back compatibility attempt for nick plugins (#974) 2023-07-23 12:47:16 +02:00
NEZNAMY a2f628e267 Add try/catch for PremiumVanish hook due to its bug (#993) 2023-07-23 11:54:04 +02:00
NEZNAMY 66007b9121 Decrease component cache size to 1000 entries 2023-07-22 01:25:28 +02:00
NEZNAMY 9e0056b603 Improve source code parity between platforms (Sponge 7, Sponge 8 and Fabric) and improvements to Fabric 2023-07-22 01:22:13 +02:00
NEZNAMY b15cc3a6af Improve source code parity between platforms (BungeeCord and Velocity) 2023-07-21 22:33:55 +02:00
NEZNAMY 5393f869e7 Remove option to disable global playerlist latency update. The performance drop is almost zero and updating is actually needed as initial ping value can be invalid ((#990) 2023-07-20 18:05:39 +02:00
NEZNAMY f8088402e8 Make it more obvious that %tps% is a backend-only placeholder (#990) 2023-07-20 17:48:27 +02:00
NEZNAMY 979ff7d2b8 Move %mspt% placeholder registration into backend package 2023-07-19 23:29:07 +02:00
NEZNAMY bdbde58f42 Drop krypton platform as author abandoned it 2023-07-19 21:33:07 +02:00
NEZNAMY 1e26e0bd13 Change backend placeholder registration method 2023-07-19 19:34:36 +02:00
NEZNAMY be3c00d17e Fix colors in error messages 2023-07-16 12:53:46 +02:00
NEZNAMY d542bb67de Add a console warn if anti-override is disabled 2023-07-16 12:09:16 +02:00
NEZNAMY a79cdfaa1c Add a console warn if placeholder used in numeric sorting did not return a valid number 2023-07-16 11:46:07 +02:00
NEZNAMY 42db715421 Fix NPE 2023-07-15 21:16:10 +02:00
NEZNAMY 2228d5a35d Small cleanup 2023-07-15 21:08:18 +02:00
NEZNAMY c434d0eafd Remove component cache from Sponge 7 2023-07-15 21:01:27 +02:00
NEZNAMY 414a3a971f Optimize placeholder high_to_low and low_to_high to only take 3 characters instead of 10 and slightly increase range as well 2023-07-15 18:32:58 +02:00
NEZNAMY b8a22a2561 Fix layout fixed slot updating issue when switching between layouts 2023-07-15 15:06:25 +02:00
NEZNAMY bc1dc2f84f Fix layout skins not working on sponge 8 2023-07-15 14:24:01 +02:00
NEZNAMY 904c4f27aa Add check if a placeholder's refresh interval is configured same as default interval 2023-07-14 16:28:21 +02:00
NEZNAMY b19e5435ef Add check for redundant per-server / per-world overrides for header/footer 2023-07-14 15:45:18 +02:00
NEZNAMY 78fbcd829a Construct player object async 2023-07-14 14:22:56 +02:00
NEZNAMY a255844a92 Create separate methods for logging info/warn and use them 2023-07-14 14:19:22 +02:00
NEZNAMY c7a548c575 Fix placeholders in tablist formatting getting stuck when on bungeecord and joining into disabled server 2023-07-14 11:50:18 +02:00
NEZNAMY 90a4bab114 Fix global playerlist formatting tablist names for players meeting disable condition 2023-07-13 23:47:28 +02:00
NEZNAMY a1633d146a Fix NPE when using relational placeholders on bungeecord 2023-07-12 22:42:21 +02:00
NEZNAMY 87bf4b03df Add misconfiguration checks for bossbar properties 2023-07-12 17:58:17 +02:00
NEZNAMY 7731aea0d6 Create %player_ping% placeholder on bukkit to override the PAPI one and avoid issues like #972 when ping field changes 2023-07-10 21:37:33 +02:00
NEZNAMY e66350111e Make detectPlaceholders method 7x faster when text only consists of 1 placeholder 2023-07-09 22:27:13 +02:00
NEZNAMY 7e5df68626 Round animation change interval instead of decreasing it when invalid value is used 2023-07-09 21:25:10 +02:00
NEZNAMY 8e3e7de4ee Send warn if a placeholder took longer than 1000ms to return value 2023-07-09 18:35:03 +02:00
NEZNAMY 0ee535fbd5 Small code style improvements from PR 2023-07-09 10:45:05 +02:00
NEZNAMY 1b471d266c Merge pull request #970 from TFSMads/master
Added support for different default skins depending on the slot index in the layout feature
2023-07-09 10:38:44 +02:00
NEZNAMY 19a4f6ea1f Merge pull request #971 from misteeka/master
@NotNull marked function actually can return null
2023-07-08 19:10:36 +02:00
misteeka 7cc8d71224 Fix typo 2023-07-08 19:42:30 +03:00
_Mads b843475a0a Remove unnecessary imports and debug message 2023-07-08 15:18:05 +02:00
_Mads 027c0fa771 Added support for different default skins depending on the slot index 2023-07-08 15:09:50 +02:00
NEZNAMY c08e76f110 Fix startup error when refresh intervals are misconfigured (#969) 2023-07-08 12:46:39 +02:00
NEZNAMY b837fe7d15 4.0.4-SNAPSHOT 2023-07-08 12:34:21 +02:00
NEZNAMY d4d1bed408 4.0.3 2023-07-07 22:51:41 +02:00
NEZNAMY 8870295d54 Fix compatibility with bukkit/forge hybrids if they use different mappings 2023-07-07 22:50:32 +02:00
NEZNAMY 79cd6e4ae2 Add null check for messages form bridge (for future updates to avoid NPE / protocol break) 2023-07-07 17:36:44 +02:00
312 changed files with 13195 additions and 71475 deletions

No files matched your search

+2
View File
@@ -60,4 +60,6 @@ body:
- label: I ticked all of the boxes without actually reading them
required: false
- label: I have read the Compatibility wiki page and am not trying to run the plugin on an unsupported server version / platform
required: true
- label: I am willing to cooperate to get this issue resolved faster, including providing any additional information requested or testing new builds
required: true
@@ -0,0 +1,38 @@
name: Compatibility issue
description: A compatibility issue with another plugin/mod or a specific version of a platform marked as supported
labels: ["Type: Compatibility issue"]
body:
- type: input
attributes:
label: TAB version
description: Full version in numbers, "latest" is not a version.
placeholder: 4.0.0
validations:
required: true
- type: textarea
attributes:
label: Software causing compatibility issue
description: plugin/mod/platform version causing the issue
validations:
required: true
- type: textarea
attributes:
label: Additional info
description: Anything else you want to provide that will help in resolving the issue faster.
validations:
required: false
- type: checkboxes
attributes:
label: Checklist
description: Let's make sure this report is valid
options:
- label: I am running latest version of the plugin
required: true
- label: I ticked all of the boxes without actually reading them
required: false
- label: The compatibility wiki page does not mention this incompatibility
required: true
- label: I am willing to cooperate to get this issue resolved faster, including providing any additional information requested or testing new builds
required: true
+1
View File
@@ -0,0 +1 @@
blank_issues_enabled: false
+1 -4
View File
@@ -3,9 +3,6 @@ on:
push:
branches:
- master
- v4
pull_request:
types: [opened, synchronize, reopened]
jobs:
build:
name: Build
@@ -41,5 +38,5 @@ jobs:
- name: Upload a Build Artifact
uses: actions/upload-artifact@v3.1.2 #https://github.com/actions/upload-artifact/releases
with:
name: Jar
name: Click here to download
path: jar/build/libs/TAB-**.jar
+34
View File
@@ -3,6 +3,17 @@ TAB aims to be a superior all-in-one minecraft plugin for displaying information
similar plugins in terms of features, performance and compatibility.
More information can be found at [Why TAB?](https://github.com/NEZNAMY/TAB/wiki/Why-TAB%3F) wiki page.
# Download
### Releases
You can download full releases from [GitHub releases](https://github.com/NEZNAMY/TAB/releases),
[SpigotMC](https://www.spigotmc.org/resources/57806/) or [Modrinth](https://modrinth.com/plugin/tab-was-taken).
### Dev builds
Plugin's development is continuous and contains lots of small changes/fixes not worthy of being called a full
update, therefore development builds are being created and eventually a full update is released when enough changes are made.
If you want to benefit from a recent change in the code before official release, you can download development builds
from [GitHub actions](https://github.com/NEZNAMY/TAB/actions).
# Compiling
Compilation requires JDK 17 and up.
To compile the plugin, run `./gradlew build` from the terminal.
@@ -21,3 +32,26 @@ is in quality of the report. Bug reports where steps to reproduce are "idk" have
# Contributing
See [CONTRIBUTING.md](https://github.com/NEZNAMY/TAB/blob/master/CONTRIBUTING.md)
# Provided services
The plugin is being used by a large amount of servers, which also means the demand for my attention is high. Unfortunately,
I am no longer able to keep up with the demand since I don't want to dedicate my whole existence to a free plugin.
In order to provide the best quality of all provided services, the amount of services provided may vary over time.
Here is the full list of potential services and their status:
| Service type | Availability |
|-------------------------------|--------------|
| Updates for new MC versions | ✔ |
| Bug fixes | ✔ |
| Wiki updates<sup>1</sup> | ✔ |
| Feature requests | ❌ |
| Customer support<sup>2</sup> | ✔ |
| Free user support<sup>3</sup> | ❌ |
| Optimization<sup>4</sup> | ✔ |
<sup>1</sup> - Adding new examples, guides and other kind of useful information people ask about.
<sup>2</sup> - Assistance with the plugin on discord, including answering any questions and helping with
configuration to those, who bought a copy of the plugin when it was purchasable.
<sup>3</sup> - Like <sup>2</sup>, but for anyone in the world who asks for it.
<sup>4</sup> - Improving plugin's performance while not losing out on any features. This was done
proactively a lot in the past, so there isn't much to do anymore, only if someone actually finds and reports something.
@@ -5,5 +5,12 @@ package me.neznamy.tab.api.bossbar;
* Calling ordinal() will return color's network ID.
*/
public enum BarColor {
PINK, BLUE, RED, GREEN, YELLOW, PURPLE, WHITE
PINK,
BLUE,
RED,
GREEN,
YELLOW,
PURPLE,
WHITE
}
@@ -1,22 +1,14 @@
package me.neznamy.tab.api.bossbar;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* An enum representing all available BossBar styles.
* Calling ordinal() will return style's network ID.
* Style names are equal to NMS names.
*/
@AllArgsConstructor
public enum BarStyle {
PROGRESS("SOLID"),
NOTCHED_6("SEGMENTED_6"),
NOTCHED_10("SEGMENTED_10"),
NOTCHED_12("SEGMENTED_12"),
NOTCHED_20("SEGMENTED_20");
/** Style's name used in Bukkit API */
@Getter private final String bukkitName;
PROGRESS,
NOTCHED_6,
NOTCHED_10,
NOTCHED_12,
NOTCHED_20
}
@@ -193,6 +193,20 @@ public interface BossBar {
*/
@NotNull List<TabPlayer> getPlayers();
/**
* Returns {@code true} if this bossbar is visible to specified player based on
* display condition or adding via {@link #addPlayer(TabPlayer)}, {@code false} if not.
* <p>
* This may return {@code true} even if player cannot physically see the bossbar due to
* having it disabled via toggle command or {@link BossBarManager#toggleBossBar(TabPlayer, boolean)}
* (or {@link BossBarManager#setBossBarVisible(TabPlayer, boolean, boolean)}).
*
* @param player
* Player to check
* @return {@code true} if player is allowed to see this Boss Bar, {@code false} if not.
*/
boolean containsPlayer(TabPlayer player);
/**
* Returns {@code true} if this boss bar is configured as announcement-only bar,
* {@code false} if not.
@@ -6,6 +6,7 @@ import java.util.Map;
import lombok.NonNull;
import me.neznamy.tab.api.TabPlayer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Interface allowing work with scoreboards, such as creating,
@@ -164,5 +165,5 @@ public interface ScoreboardManager {
* player to get active scoreboard of
* @return player's active scoreboard or {@code null} if player has no scoreboard
*/
@NotNull Scoreboard getActiveScoreboard(@NonNull TabPlayer player);
@Nullable Scoreboard getActiveScoreboard(@NonNull TabPlayer player);
}
+13 -3
View File
@@ -4,7 +4,7 @@ plugins {
allprojects {
group = "me.neznamy"
version = "4.0.3-SNAPSHOT"
version = "4.1.0"
description = "An all-in-one solution that works"
ext.set("id", "tab")
@@ -15,11 +15,21 @@ allprojects {
val platforms = setOf(
projects.bukkit,
projects.bungeecord,
projects.krypton,
projects.velocity,
projects.sponge7,
projects.sponge8,
projects.fabric
projects.fabric,
projects.fabric.v1144,
projects.fabric.v1152,
projects.fabric.v1165,
projects.fabric.v117,
projects.fabric.v1171,
projects.fabric.v1182,
projects.fabric.v1192,
projects.fabric.v1193,
projects.fabric.v1201,
projects.fabric.v1202,
projects.fabric.v1203
).map { it.dependencyProject }
val special = setOf(
+11 -1
View File
@@ -4,5 +4,15 @@ dependencies {
compileOnly("org.purpurmc.purpur:purpur-api:1.16.5-R0.1-SNAPSHOT")
compileOnly("me.clip:placeholderapi:2.11.2")
compileOnly("com.mojang:authlib:1.5.25")
compileOnly("com.github.MilkBowl:VaultAPI:1.7")
compileOnly("com.github.MilkBowl:VaultAPI:1.7") {
exclude("org.bukkit", "bukkit")
}
compileOnly("LibsDisguises:LibsDisguises:10.0.21") {
exclude("org.spigotmc", "spigot")
exclude("org.spigotmc", "spigot-api")
exclude("com.github.dmulloy2", "ProtocolLib")
exclude("org.ow2.asm", "asm")
exclude("net.md-5", "bungeecord-chat")
}
compileOnly("com.github.LeonMangler:PremiumVanishAPI:2.8.8")
}
@@ -1,5 +1,7 @@
package me.neznamy.tab.platforms.bukkit;
import me.neznamy.tab.platforms.bukkit.platform.BukkitPlatform;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.platform.EventListener;
import me.neznamy.tab.shared.platform.TabPlayer;
import org.bukkit.entity.Player;
@@ -10,6 +12,7 @@ import org.bukkit.event.player.PlayerChangedWorldEvent;
import org.bukkit.event.player.PlayerCommandPreprocessEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.jetbrains.annotations.NotNull;
/**
* The core for bukkit forwarding events into all enabled features
@@ -37,7 +40,8 @@ public class BukkitEventListener extends EventListener<Player> implements Listen
}
@Override
public TabPlayer createPlayer(Player player) {
return new BukkitTabPlayer(player);
@NotNull
public TabPlayer createPlayer(@NotNull Player player) {
return new BukkitTabPlayer((BukkitPlatform) TAB.getInstance().getPlatform(), player);
}
}
@@ -2,19 +2,26 @@ package me.neznamy.tab.platforms.bukkit;
import com.mojang.authlib.GameProfile;
import io.netty.channel.Channel;
import lombok.Getter;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
import me.neznamy.tab.platforms.bukkit.scoreboard.PacketScoreboard;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.features.nametags.NameTag;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.features.injection.NettyPipelineInjector;
import me.neznamy.tab.shared.features.sorting.Sorting;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.bukkit.Bukkit;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;
@@ -23,6 +30,14 @@ import java.util.stream.Collectors;
*/
public class BukkitPipelineInjector extends NettyPipelineInjector {
private static Method getHandle;
private static Field PLAYER_CONNECTION;
private static Field NETWORK_MANAGER;
private static Field CHANNEL;
@Getter
private static boolean available;
/**
* Constructs new instance
*/
@@ -30,29 +45,55 @@ public class BukkitPipelineInjector extends NettyPipelineInjector {
super("packet_handler");
}
@Override
protected Channel getChannel(@NotNull TabPlayer player) {
BukkitTabPlayer bukkit = (BukkitTabPlayer) player;
NMSStorage nms = NMSStorage.getInstance();
public static void tryLoad() {
try {
if (nms.CHANNEL != null) return (Channel) nms.CHANNEL.get(nms.NETWORK_MANAGER.get(bukkit.getPlayerConnection()));
} catch (IllegalAccessException exception) {
TAB.getInstance().getErrorManager().printError("Failed to get channel of " + bukkit.getName(), exception);
Class<?> NetworkManager = BukkitReflection.getClass("network.Connection", "network.NetworkManager", "NetworkManager");
Class<?> PlayerConnection = BukkitReflection.getClass("server.network.ServerGamePacketListenerImpl",
"server.network.PlayerConnection", "PlayerConnection");
Class<?> EntityPlayer = BukkitReflection.getClass("server.level.ServerPlayer", "server.level.EntityPlayer", "EntityPlayer");
getHandle = Class.forName("org.bukkit.craftbukkit." + BukkitReflection.getServerPackage() + ".entity.CraftPlayer").getMethod("getHandle");
PLAYER_CONNECTION = ReflectionUtils.getOnlyField(EntityPlayer, PlayerConnection);
if (BukkitReflection.is1_20_2Plus()) {
NETWORK_MANAGER = ReflectionUtils.getOnlyField(PlayerConnection.getSuperclass(), NetworkManager);
} else {
NETWORK_MANAGER = ReflectionUtils.getOnlyField(PlayerConnection, NetworkManager);
}
CHANNEL = ReflectionUtils.getOnlyField(NetworkManager, Channel.class);
available = true;
} catch (Exception e) {
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.RED.getFormat() + "[TAB] Failed to initialize NMS fields for " +
"network channel injection due to a compatibility error. This will make the following features not work: " +
"Unlimited nametag mode, anti-override for tablist formatting & nametags, detecting nickname change for compatibility " +
"with nick plugins and compatibility with other scoreboard plugins. " +
"Please update the plugin a to version with native support for your server version to unlock the features.");
}
return null;
}
@Override
@Nullable
@SneakyThrows
protected Channel getChannel(@NotNull TabPlayer player) {
return (Channel) CHANNEL.get(NETWORK_MANAGER.get(PLAYER_CONNECTION.get(getHandle.invoke(player.getPlayer()))));
}
@Override
@SneakyThrows
public void onDisplayObjective(@NotNull TabPlayer player, @NotNull Object packet) {
TAB.getInstance().getFeatureManager().onDisplayObjective(player,
PacketScoreboard.DisplayObjective_POSITION.getInt(packet),
(String) PacketScoreboard.DisplayObjective_OBJECTIVE_NAME.get(packet));
if (!PacketScoreboard.isAvailable()) return;
int position;
if (BukkitReflection.is1_20_2Plus()) {
position = ((Enum<?>)PacketScoreboard.displayPacketData.DisplayObjective_POSITION.get(packet)).ordinal();
} else {
position = PacketScoreboard.displayPacketData.DisplayObjective_POSITION.getInt(packet);
}
TAB.getInstance().getFeatureManager().onDisplayObjective(player, position,
(String) PacketScoreboard.displayPacketData.DisplayObjective_OBJECTIVE_NAME.get(packet));
}
@Override
@SneakyThrows
public void onObjective(@NotNull TabPlayer player, @NotNull Object packet) {
if (!PacketScoreboard.isAvailable()) return;
TAB.getInstance().getFeatureManager().onObjective(player,
PacketScoreboard.Objective_METHOD.getInt(packet),
(String) PacketScoreboard.Objective_OBJECTIVE_NAME.get(packet));
@@ -60,77 +101,112 @@ public class BukkitPipelineInjector extends NettyPipelineInjector {
@Override
public boolean isDisplayObjective(@NotNull Object packet) {
return PacketScoreboard.DisplayObjectiveClass.isInstance(packet);
if (!PacketScoreboard.isAvailable()) return false;
return PacketScoreboard.displayPacketData.DisplayObjectiveClass.isInstance(packet);
}
@Override
public boolean isObjective(@NotNull Object packet) {
if (!PacketScoreboard.isAvailable()) return false;
return PacketScoreboard.ObjectivePacketClass.isInstance(packet);
}
@Override
public boolean isTeam(@NotNull Object packet) {
return PacketScoreboard.TeamPacketClass.isInstance(packet);
if (!PacketScoreboard.isAvailable()) return false;
return PacketScoreboard.teamPacketData.TeamPacketClass.isInstance(packet);
}
@Override
public boolean isPlayerInfo(@NotNull Object packet) {
return BukkitTabList.PacketPlayOutPlayerInfoClass.isInstance(packet);
return BukkitTabList.PlayerInfoClass.isInstance(packet);
}
@Override
@SneakyThrows
public void onPlayerInfo(@NotNull TabPlayer receiver, @NotNull Object packet) {
NMSStorage nms = NMSStorage.getInstance();
List<String> actions;
if (BukkitTabList.ClientboundPlayerInfoRemovePacket != null) {
//1.19.3+
actions = ((EnumSet<?>)BukkitTabList.ACTION.get(packet)).stream().map(Enum::name).collect(Collectors.toList());
if (BukkitReflection.is1_19_3Plus()) {
onPlayerInfo1_19_3(receiver, packet);
} else {
//1.19.2-
actions = Collections.singletonList(BukkitTabList.ACTION.get(packet).toString());
onPlayerInfo1_19_2(receiver, packet);
}
}
@SneakyThrows
private void onPlayerInfo1_19_3(@NotNull TabPlayer receiver, @NotNull Object packet) {
List<String> actions = ((EnumSet<?>)BukkitTabList.ACTION.get(packet)).stream().map(Enum::name).collect(Collectors.toList());
List<Object> updatedList = new ArrayList<>();
for (Object nmsData : (List<?>) BukkitTabList.PLAYERS.get(packet)) {
GameProfile profile = (GameProfile) BukkitTabList.PlayerInfoData_getProfile.invoke(nmsData);
GameProfile profile = (GameProfile) BukkitTabList.PlayerInfoData_Profile.get(nmsData);
UUID id;
id = (UUID) BukkitTabList.PlayerInfoData_UUID.get(nmsData);
Object displayName = null;
if (actions.contains(TabList.Action.UPDATE_DISPLAY_NAME.name()) || actions.contains(TabList.Action.ADD_PLAYER.name())) {
int latency = 0;
if (actions.contains(TabList.Action.UPDATE_DISPLAY_NAME.name())) {
displayName = BukkitTabList.PlayerInfoData_DisplayName.get(nmsData);
IChatBaseComponent newDisplayName = TAB.getInstance().getFeatureManager().onDisplayNameChange(receiver, profile.getId());
if (newDisplayName != null) displayName = nms.toNMSComponent(newDisplayName, receiver.getVersion());
if (!nms.is1_19_3Plus()) BukkitTabList.PlayerInfoData_DisplayName.set(nmsData, displayName);
IChatBaseComponent newDisplayName = TAB.getInstance().getFeatureManager().onDisplayNameChange(receiver, id);
if (newDisplayName != null) displayName = ((BukkitTabPlayer)receiver).getTabList().toComponent(newDisplayName);
}
if (nms.is1_19_3Plus()) {
// 1.19.3 is using records, which do not allow changing final fields, need to rewrite the list entirely
updatedList.add(BukkitTabList.newPlayerInfoData.newInstance(
profile.getId(),
profile,
BukkitTabList.PlayerInfoData_Listed.getBoolean(nmsData),
BukkitTabList.PlayerInfoData_Latency.getInt(nmsData),
BukkitTabList.PlayerInfoData_GameMode.get(nmsData),
displayName,
BukkitTabList.PlayerInfoData_RemoteChatSession.get(nmsData)));
if (actions.contains(TabList.Action.UPDATE_LATENCY.name())) {
latency = TAB.getInstance().getFeatureManager().onLatencyChange(receiver, id, BukkitTabList.PlayerInfoData_Latency.getInt(nmsData));
}
if (actions.contains(TabList.Action.ADD_PLAYER.name())) {
TAB.getInstance().getFeatureManager().onEntryAdd(receiver, id, profile.getName());
}
// 1.19.3 is using records, which do not allow changing final fields, need to rewrite the list entirely
updatedList.add(BukkitTabList.newPlayerInfoData.newInstance(
id,
profile,
BukkitTabList.PlayerInfoData_Listed.getBoolean(nmsData),
latency,
BukkitTabList.PlayerInfoData_GameMode.get(nmsData),
displayName,
BukkitTabList.PlayerInfoData_RemoteChatSession.get(nmsData)));
}
BukkitTabList.PLAYERS.set(packet, updatedList);
}
@SneakyThrows
private void onPlayerInfo1_19_2(@NotNull TabPlayer receiver, @NotNull Object packet) {
String action = BukkitTabList.ACTION.get(packet).toString();
for (Object nmsData : (List<?>) BukkitTabList.PLAYERS.get(packet)) {
GameProfile profile = (GameProfile) BukkitTabList.PlayerInfoData_Profile.get(nmsData);
UUID id = profile.getId();
if (action.equals(TabList.Action.UPDATE_DISPLAY_NAME.name()) || action.equals(TabList.Action.ADD_PLAYER.name())) {
Object displayName = BukkitTabList.PlayerInfoData_DisplayName.get(nmsData);
IChatBaseComponent newDisplayName = TAB.getInstance().getFeatureManager().onDisplayNameChange(receiver, id);
if (newDisplayName != null) displayName = ((BukkitTabPlayer)receiver).getTabList().toComponent(newDisplayName);
BukkitTabList.PlayerInfoData_DisplayName.set(nmsData, displayName);
}
if (action.equals(TabList.Action.UPDATE_LATENCY.name()) || action.equals(TabList.Action.ADD_PLAYER.name())) {
int latency = TAB.getInstance().getFeatureManager().onLatencyChange(receiver, id, BukkitTabList.PlayerInfoData_Latency.getInt(nmsData));
BukkitTabList.PlayerInfoData_Latency.set(nmsData, latency);
}
if (action.equals(TabList.Action.ADD_PLAYER.name())) {
TAB.getInstance().getFeatureManager().onEntryAdd(receiver, id, profile.getName());
}
}
if (nms.is1_19_3Plus()) {
BukkitTabList.PLAYERS.set(packet, updatedList);
}
}
@Override
public boolean isLogin(@NotNull Object packet) {
return false;
}
@SuppressWarnings("unchecked")
@Override
@SneakyThrows
public void modifyPlayers(@NotNull Object packetPlayOutScoreboardTeam) {
if (!PacketScoreboard.isAvailable()) return;
if (TAB.getInstance().getNameTagManager() == null) return;
int action = PacketScoreboard.TeamPacket_ACTION.getInt(packetPlayOutScoreboardTeam);
int action = PacketScoreboard.teamPacketData.TeamPacket_ACTION.getInt(packetPlayOutScoreboardTeam);
if (action == 1 || action == 2 || action == 4) return;
Collection<String> players = (Collection<String>) PacketScoreboard.TeamPacket_PLAYERS.get(packetPlayOutScoreboardTeam);
String teamName = (String) PacketScoreboard.TeamPacket_NAME.get(packetPlayOutScoreboardTeam);
Collection<String> players = (Collection<String>) PacketScoreboard.teamPacketData.TeamPacket_PLAYERS.get(packetPlayOutScoreboardTeam);
String teamName = (String) PacketScoreboard.teamPacketData.TeamPacket_NAME.get(packetPlayOutScoreboardTeam);
if (players == null) return;
//creating a new list to prevent NoSuchFieldException in minecraft packet encoder when a player is removed
Collection<String> newList = new ArrayList<>();
for (String entry : players) {
TabPlayer p = TAB.getInstance().getPlayer(entry);
TabPlayer p = getPlayer(entry);
if (p == null) {
newList.add(entry);
continue;
@@ -148,6 +224,6 @@ public class BukkitPipelineInjector extends NettyPipelineInjector {
newList.add(entry);
}
}
PacketScoreboard.TeamPacket_PLAYERS.set(packetPlayOutScoreboardTeam, newList);
PacketScoreboard.teamPacketData.TeamPacket_PLAYERS.set(packetPlayOutScoreboardTeam, newList);
}
}
@@ -1,82 +0,0 @@
package me.neznamy.tab.platforms.bukkit;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.api.placeholder.PlaceholderManager;
import me.neznamy.tab.shared.placeholders.UniversalPlaceholderRegistry;
import net.milkbowl.vault.chat.Chat;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.plugin.RegisteredServiceProvider;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* Bukkit registry to register bukkit-only and universal placeholders
*/
public class BukkitPlaceholderRegistry extends UniversalPlaceholderRegistry {
private Chat chat;
/** NMS server to get TPS from on spigot */
private Object server;
/** TPS field*/
private Field recentTps;
/** Detection for presence of Paper's TPS getter */
private Method paperTps;
/** Detection for presence of Paper's MSPT getter */
private Method paperMspt;
/**
* Constructs new instance and loads hooks
*/
public BukkitPlaceholderRegistry() {
try {
server = Bukkit.getServer().getClass().getMethod("getServer").invoke(Bukkit.getServer());
recentTps = server.getClass().getField("recentTps");
} catch (ReflectiveOperationException e) {
//not spigot
}
try { paperTps = Bukkit.class.getMethod("getTPS"); } catch (NoSuchMethodException ignored) {}
try { paperMspt = Bukkit.class.getMethod("getAverageTickTime"); } catch (NoSuchMethodException ignored) {}
if (Bukkit.getPluginManager().isPluginEnabled("Vault")) {
RegisteredServiceProvider<Chat> rspChat = Bukkit.getServicesManager().getRegistration(Chat.class);
if (rspChat != null) chat = rspChat.getProvider();
}
}
@SuppressWarnings("deprecation")
@Override
public void registerPlaceholders(@NotNull PlaceholderManager manager) {
manager.registerPlayerPlaceholder(TabConstants.Placeholder.DISPLAY_NAME, 500, p -> ((Player) p.getPlayer()).getDisplayName());
if (paperTps != null) {
manager.registerServerPlaceholder(TabConstants.Placeholder.TPS, 1000, () -> formatTPS(Bukkit.getTPS()[0]));
} else if (recentTps != null) {
manager.registerServerPlaceholder(TabConstants.Placeholder.TPS, 1000, () -> {
try {
return formatTPS(((double[]) recentTps.get(server))[0]);
} catch (IllegalAccessException e) {
return -1;
}
});
} else {
manager.registerServerPlaceholder(TabConstants.Placeholder.TPS, -1, () -> -1);
}
if (paperMspt != null) {
manager.registerServerPlaceholder(TabConstants.Placeholder.MSPT, 1000, () -> format(Bukkit.getAverageTickTime()));
}
manager.registerPlayerPlaceholder(TabConstants.Placeholder.HEALTH, 100, p -> (int) Math.ceil(((Player) p.getPlayer()).getHealth()));
if (chat != null) {
manager.registerPlayerPlaceholder("%vault-prefix%", 1000, p -> chat.getPlayerPrefix((Player) p.getPlayer()));
manager.registerPlayerPlaceholder("%vault-suffix%", 1000, p -> chat.getPlayerSuffix((Player) p.getPlayer()));
} else {
manager.registerServerPlaceholder("%vault-prefix%", -1, () -> "");
manager.registerServerPlaceholder("%vault-suffix%", -1, () -> "");
}
super.registerPlaceholders(manager);
}
}
@@ -1,51 +1,28 @@
package me.neznamy.tab.platforms.bukkit;
import me.neznamy.tab.platforms.bukkit.nms.*;
import me.neznamy.tab.platforms.bukkit.platform.BukkitPlatform;
import me.neznamy.tab.platforms.bukkit.platform.FoliaPlatform;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.*;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.bstats.bukkit.Metrics;
import org.bstats.charts.SimplePie;
import org.bukkit.Bukkit;
import org.bukkit.command.*;
import org.bukkit.plugin.java.JavaPlugin;
/**
* Main class for Bukkit platform
* Main class for Bukkit.
*/
public class BukkitTAB extends JavaPlugin {
@Override
public void onEnable() {
String version = Bukkit.getBukkitVersion().split("-")[0];
String serverPackage = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3];
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.color("[TAB] Server version: " + version + " (" + serverPackage + ")"));
if (!isVersionSupported()) {
Bukkit.getPluginManager().disablePlugin(this);
return;
}
BukkitPlatform platform = ReflectionUtils.classExists("io.papermc.paper.threadedregions.RegionizedServer") ?
new FoliaPlatform(this) : new BukkitPlatform(this);
TAB.setInstance(new TAB(platform, ProtocolVersion.fromFriendlyName(version), getDataFolder()));
if (TAB.getInstance().getServerVersion() == ProtocolVersion.UNKNOWN_SERVER_VERSION) {
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.color("&c[TAB] Unknown server version: " + Bukkit.getBukkitVersion() + "! Plugin may not work correctly."));
}
Bukkit.getPluginManager().registerEvents(new BukkitEventListener(), this);
TAB.getInstance().load();
Metrics metrics = new Metrics(this, 5304);
metrics.addCustomChart(new SimplePie(TabConstants.MetricsChart.UNLIMITED_NAME_TAG_MODE_ENABLED, () -> TAB.getInstance().getFeatureManager().isFeatureEnabled(TabConstants.Feature.UNLIMITED_NAME_TAGS) ? "Yes" : "No"));
metrics.addCustomChart(new SimplePie(TabConstants.MetricsChart.PLACEHOLDER_API, () -> platform.isPlaceholderAPI() ? "Yes" : "No"));
metrics.addCustomChart(new SimplePie(TabConstants.MetricsChart.PERMISSION_SYSTEM, () -> TAB.getInstance().getGroupManager().getPermissionPlugin()));
metrics.addCustomChart(new SimplePie(TabConstants.MetricsChart.SERVER_VERSION, () -> "1." + TAB.getInstance().getServerVersion().getMinorVersion() + ".x"));
PluginCommand cmd = Bukkit.getPluginCommand(TabConstants.COMMAND_BACKEND);
if (cmd == null) return;
BukkitTabCommand command = new BukkitTabCommand();
cmd.setExecutor(command);
cmd.setTabCompleter(command);
TAB.create(ReflectionUtils.classExists("io.papermc.paper.threadedregions.RegionizedServer") ?
new FoliaPlatform(this) : new BukkitPlatform(this));
}
@Override
@@ -63,20 +40,19 @@ public class BukkitTAB extends JavaPlugin {
*/
private boolean isVersionSupported() {
try {
long time = System.currentTimeMillis();
String serverPackage = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3];
int minorVersion = Integer.parseInt(serverPackage.split("_")[1]);
NMSStorage.setInstance(minorVersion >= 17 ? new BukkitModernNMSStorage() : new BukkitLegacyNMSStorage());
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.color("[TAB] Loaded NMS hook in " + (System.currentTimeMillis()-time) + "ms"));
DataWatcher.load();
PacketEntityView.load();
BukkitTabList.load();
return true;
} catch (Exception ex) {
if (ProtocolVersion.fromFriendlyName(Bukkit.getBukkitVersion().split("-")[0]) == ProtocolVersion.UNKNOWN_SERVER_VERSION) {
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.color("&c[TAB] Your server version is not compatible. " +
"This plugin version was made for " + ProtocolVersion.values()[ProtocolVersion.values().length-1].getFriendlyName() +
" - " + ProtocolVersion.values()[3].getFriendlyName() + ". Disabling."));
Bukkit.getConsoleSender().sendMessage(String.format(
"%s[TAB] Your server version is not compatible. This plugin version was made for %s - %s. Disabling.",
EnumChatFormat.RED.getFormat(), ProtocolVersion.V1_5.getFriendlyName(), ProtocolVersion.LATEST_KNOWN_VERSION.getFriendlyName()
));
} else {
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.color("&c[TAB] A compatibility issue " +
"with your server was found. Unless you are running some really weird server software, this is a bug."));
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.RED.getFormat() + "[TAB] A compatibility issue " +
"with your server was found. Unless you are running some really weird server software, this is a bug.");
}
return false;
}
@@ -11,7 +11,7 @@ import org.bukkit.command.TabCompleter;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
@@ -20,7 +20,7 @@ import java.util.List;
public class BukkitTabCommand implements CommandExecutor, TabCompleter {
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] args) {
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if (TAB.getInstance().isPluginDisabled()) {
for (String message : TAB.getInstance().getDisabledCommand().execute(args, sender.hasPermission(TabConstants.Permission.COMMAND_RELOAD), sender.hasPermission(TabConstants.Permission.COMMAND_ALL))) {
sender.sendMessage(EnumChatFormat.color(message));
@@ -37,11 +37,12 @@ public class BukkitTabCommand implements CommandExecutor, TabCompleter {
}
@Override
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, String[] args) {
@NotNull
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, @NotNull String[] args) {
TabPlayer p = null;
if (sender instanceof Player) {
p = TAB.getInstance().getPlayer(((Player)sender).getUniqueId());
if (p == null) return new ArrayList<>(); //player not loaded correctly
if (p == null) return Collections.emptyList(); //player not loaded correctly
}
return TAB.getInstance().getCommand().complete(p, args);
}
@@ -4,11 +4,15 @@ import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.platforms.bukkit.header.HeaderFooter;
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.TabList.Entry.Builder;
import me.neznamy.tab.shared.util.ComponentCache;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.bukkit.Bukkit;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -32,170 +36,234 @@ import java.util.*;
@SuppressWarnings({"unchecked", "rawtypes"})
public class BukkitTabList implements TabList {
// NMS Fields
public static Class<?> PacketPlayOutPlayerListHeaderFooterClass;
public static Constructor<?> newPacketPlayOutPlayerListHeaderFooter;
public static Field HEADER;
public static Field FOOTER;
public static Class<?> PacketPlayOutPlayerInfoClass;
public static Constructor<?> newPacketPlayOutPlayerInfo;
public static Class<?> PlayerInfoClass;
private static Constructor<?> newPlayerInfo;
public static Field ACTION;
public static Field PLAYERS;
public static Class<Enum> EnumPlayerInfoActionClass;
public static Class<Enum> EnumGamemodeClass;
private static Class<Enum> ActionClass;
public static Class<?> ClientboundPlayerInfoRemovePacket;
public static Class<?> RemoteChatSession$Data;
public static Constructor<?> newClientboundPlayerInfoRemovePacket;
private static Constructor<?> newClientboundPlayerInfoRemovePacket;
private static Class<?> EntityPlayer;
public static Class<?> PlayerInfoDataClass;
public static Constructor<?> newPlayerInfoData;
public static Method PlayerInfoData_getProfile;
public static Field PlayerInfoData_UUID;
public static Field PlayerInfoData_Profile;
public static Field PlayerInfoData_Latency;
public static Field PlayerInfoData_GameMode;
public static Field PlayerInfoData_DisplayName;
public static Field PlayerInfoData_Listed;
public static Field PlayerInfoData_RemoteChatSession;
private static Object[] gameModes;
@Nullable
private static SkinData skinData;
private static Method ChatSerializer_DESERIALIZE;
private static final ComponentCache<IChatBaseComponent, Object> componentCache = new ComponentCache<>(1000,
(component, clientVersion) -> ChatSerializer_DESERIALIZE.invoke(null, component.toString(clientVersion)));
/** Player this TabList belongs to */
private final BukkitTabPlayer player;
public static void load(NMSStorage nms) throws NoSuchMethodException {
if (nms.getMinorVersion() >= 17) {
newPacketPlayOutPlayerListHeaderFooter = PacketPlayOutPlayerListHeaderFooterClass.getConstructor(nms.IChatBaseComponent, nms.IChatBaseComponent);
} else {
newPacketPlayOutPlayerListHeaderFooter = PacketPlayOutPlayerListHeaderFooterClass.getConstructor();
HEADER = ReflectionUtils.getFields(PacketPlayOutPlayerListHeaderFooterClass, nms.IChatBaseComponent).get(0);
FOOTER = ReflectionUtils.getFields(PacketPlayOutPlayerListHeaderFooterClass, nms.IChatBaseComponent).get(1);
}
if (nms.is1_19_3Plus()) {
public static void load() throws NoSuchMethodException, ClassNotFoundException {
if (BukkitReflection.getMinorVersion() < 8) return; // Not supported
Class<?> ChatSerializer = BukkitReflection.getClass("network.chat.Component$Serializer",
"network.chat.IChatBaseComponent$ChatSerializer", "IChatBaseComponent$ChatSerializer", "ChatSerializer");
ChatSerializer_DESERIALIZE = ReflectionUtils.getMethods(ChatSerializer, Object.class, String.class).get(0);
Class<?> IChatBaseComponent = BukkitReflection.getClass("network.chat.Component", "network.chat.IChatBaseComponent", "IChatBaseComponent");
Class<Enum> EnumGamemodeClass = (Class<Enum>) BukkitReflection.getClass("world.level.GameType",
"world.level.EnumGamemode", "EnumGamemode", "WorldSettings$EnumGamemode");
EntityPlayer = BukkitReflection.getClass("server.level.ServerPlayer", "server.level.EntityPlayer", "EntityPlayer");
ActionClass = (Class<Enum>) BukkitReflection.getClass(
"network.protocol.game.ClientboundPlayerInfoUpdatePacket$Action", // Mojang 1.19.3+
"network.protocol.game.ClientboundPlayerInfoPacket$Action", // Mojang 1.17 - 1.19.2
"network.protocol.game.ClientboundPlayerInfoUpdatePacket$a", // Bukkit 1.19.3+
"network.protocol.game.PacketPlayOutPlayerInfo$EnumPlayerInfoAction", // Bukkit 1.17 - 1.19.2
"PacketPlayOutPlayerInfo$EnumPlayerInfoAction", // Bukkit 1.8.1 - 1.16.5
"EnumPlayerInfoAction" // Bukkit 1.8.0
);
PlayerInfoClass = BukkitReflection.getClass("network.protocol.game.ClientboundPlayerInfoUpdatePacket",
"network.protocol.game.ClientboundPlayerInfoPacket",
"network.protocol.game.PacketPlayOutPlayerInfo", "PacketPlayOutPlayerInfo");
Class<?> playerInfoDataClass = BukkitReflection.getClass(
"network.protocol.game.ClientboundPlayerInfoUpdatePacket$Entry", // Mojang 1.19.3
"network.protocol.game.ClientboundPlayerInfoPacket$PlayerUpdate", // Mojang 1.17 - 1.19.2
"network.protocol.game.ClientboundPlayerInfoUpdatePacket$b", // Bukkit 1.19.3+
"network.protocol.game.PacketPlayOutPlayerInfo$PlayerInfoData", // Bukkit 1.17 - 1.19.2
"PacketPlayOutPlayerInfo$PlayerInfoData", // Bukkit 1.8.1 - 1.16.5
"PlayerInfoData" // Bukkit 1.8.0
);
PLAYERS = ReflectionUtils.getOnlyField(PlayerInfoClass, List.class);
newPlayerInfoData = playerInfoDataClass.getConstructors()[0]; // #1105, a specific 1.8.8 fork has 2 constructors
PlayerInfoData_Profile = ReflectionUtils.getOnlyField(playerInfoDataClass, GameProfile.class);
PlayerInfoData_Latency = ReflectionUtils.getOnlyField(playerInfoDataClass, int.class);
PlayerInfoData_GameMode = ReflectionUtils.getOnlyField(playerInfoDataClass, EnumGamemodeClass);
PlayerInfoData_DisplayName = ReflectionUtils.getOnlyField(playerInfoDataClass, IChatBaseComponent);
if (BukkitReflection.is1_19_3Plus()) {
ClientboundPlayerInfoRemovePacket = BukkitReflection.getClass("network.protocol.game.ClientboundPlayerInfoRemovePacket");
Class<?> RemoteChatSession$Data = BukkitReflection.getClass("network.chat.RemoteChatSession$Data", "network.chat.RemoteChatSession$a");
newClientboundPlayerInfoRemovePacket = ClientboundPlayerInfoRemovePacket.getConstructor(List.class);
newPacketPlayOutPlayerInfo = PacketPlayOutPlayerInfoClass.getConstructor(EnumSet.class, Collection.class);
ACTION = ReflectionUtils.getOnlyField(PacketPlayOutPlayerInfoClass, EnumSet.class);
newPlayerInfo = PlayerInfoClass.getConstructor(EnumSet.class, Collection.class);
ACTION = ReflectionUtils.getOnlyField(PlayerInfoClass, EnumSet.class);
PlayerInfoData_Listed = ReflectionUtils.getOnlyField(playerInfoDataClass, boolean.class);
PlayerInfoData_RemoteChatSession = ReflectionUtils.getOnlyField(playerInfoDataClass, RemoteChatSession$Data);
PlayerInfoData_UUID = ReflectionUtils.getOnlyField(playerInfoDataClass, UUID.class);
} else {
newPacketPlayOutPlayerInfo = PacketPlayOutPlayerInfoClass.getConstructor(EnumPlayerInfoActionClass, Array.newInstance(nms.EntityPlayer, 0).getClass());
ACTION = ReflectionUtils.getOnlyField(PacketPlayOutPlayerInfoClass, EnumPlayerInfoActionClass);
newPlayerInfo = PlayerInfoClass.getConstructor(ActionClass, Array.newInstance(EntityPlayer, 0).getClass());
ACTION = ReflectionUtils.getOnlyField(PlayerInfoClass, ActionClass);
}
PLAYERS = ReflectionUtils.getOnlyField(PacketPlayOutPlayerInfoClass, List.class);
newPlayerInfoData = ReflectionUtils.getOnlyConstructor(PlayerInfoDataClass);
PlayerInfoData_getProfile = ReflectionUtils.getOnlyMethod(PlayerInfoDataClass, GameProfile.class);
PlayerInfoData_Latency = ReflectionUtils.getOnlyField(PlayerInfoDataClass, int.class);
PlayerInfoData_GameMode = ReflectionUtils.getOnlyField(PlayerInfoDataClass, EnumGamemodeClass);
PlayerInfoData_DisplayName = ReflectionUtils.getOnlyField(PlayerInfoDataClass, nms.IChatBaseComponent);
if (nms.is1_19_3Plus()) {
PlayerInfoData_Listed = ReflectionUtils.getOnlyField(PlayerInfoDataClass, boolean.class);
PlayerInfoData_RemoteChatSession = ReflectionUtils.getOnlyField(PlayerInfoDataClass, RemoteChatSession$Data);
gameModes = new Object[] {
Enum.valueOf(EnumGamemodeClass, "SURVIVAL"),
Enum.valueOf(EnumGamemodeClass, "CREATIVE"),
Enum.valueOf(EnumGamemodeClass, "ADVENTURE"),
Enum.valueOf(EnumGamemodeClass, "SPECTATOR")
};
try {
skinData = new SkinData();
} catch (Exception e) {
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.RED.getFormat() + "[TAB] Failed to initialize NMS fields for " +
"getting player's game profile due to a compatibility error. This will " +
"result in player skins not properly working in layout feature. " +
"Please update the plugin a to version with native support for your server version for optimal experience.");
}
}
@Override
@SneakyThrows
public void removeEntry(@NotNull UUID entry) {
if (PlayerInfoClass == null) return;
if (ClientboundPlayerInfoRemovePacket != null) {
//1.19.3+
player.sendPacket(newClientboundPlayerInfoRemovePacket.newInstance(Collections.singletonList(entry)));
} else {
//1.19.2-
player.sendPacket(createPacket(Action.REMOVE_PLAYER, new Entry.Builder(entry).build(), player.getVersion()));
player.sendPacket(createPacket(Action.REMOVE_PLAYER, new Builder(entry).build()));
}
}
@Override
public void updateDisplayName(@NotNull UUID entry, @Nullable IChatBaseComponent displayName) {
player.sendPacket(createPacket(Action.UPDATE_DISPLAY_NAME,
new Entry.Builder(entry).displayName(displayName).build(), player.getVersion())
);
if (PlayerInfoClass == null) return;
player.sendPacket(createPacket(Action.UPDATE_DISPLAY_NAME, new Builder(entry).displayName(displayName).build()));
}
@Override
public void updateLatency(@NotNull UUID entry, int latency) {
player.sendPacket(createPacket(Action.UPDATE_LATENCY,
new Entry.Builder(entry).latency(latency).build(), player.getVersion())
);
if (PlayerInfoClass == null) return;
player.sendPacket(createPacket(Action.UPDATE_LATENCY, new Builder(entry).latency(latency).build()));
}
@Override
public void updateGameMode(@NotNull UUID entry, int gameMode) {
player.sendPacket(createPacket(Action.UPDATE_GAME_MODE,
new Entry.Builder(entry).gameMode(gameMode).build(), player.getVersion())
);
if (PlayerInfoClass == null) return;
player.sendPacket(createPacket(Action.UPDATE_GAME_MODE, new Builder(entry).gameMode(gameMode).build()));
}
@Override
public void addEntry(@NotNull Entry entry) {
player.sendPacket(createPacket(Action.ADD_PLAYER, entry, player.getVersion()));
if (PlayerInfoClass == null) return;
player.sendPacket(createPacket(Action.ADD_PLAYER, entry));
}
@Override
@SneakyThrows
public void setPlayerListHeaderFooter(@NotNull IChatBaseComponent header, @NotNull IChatBaseComponent footer) {
if (PacketPlayOutPlayerListHeaderFooterClass == null) return;
NMSStorage nms = NMSStorage.getInstance();
Object packet;
if (nms.getMinorVersion() >= 17) {
packet = newPacketPlayOutPlayerListHeaderFooter.newInstance(
nms.toNMSComponent(header, player.getVersion()), nms.toNMSComponent(footer, player.getVersion()));
} else {
packet = newPacketPlayOutPlayerListHeaderFooter.newInstance();
HEADER.set(packet, nms.toNMSComponent(header, player.getVersion()));
FOOTER.set(packet, nms.toNMSComponent(footer, player.getVersion()));
}
player.sendPacket(packet);
if (HeaderFooter.getInstance() != null) HeaderFooter.getInstance().set(player, header, footer);
}
@SneakyThrows
private Object createPacket(TabList.Action action, TabList.Entry entry, ProtocolVersion clientVersion) {
NMSStorage nms = NMSStorage.getInstance();
if (nms.getMinorVersion() < 8) return null;
@NotNull
private Object createPacket(@NotNull Action action, @NotNull Entry entry) {
Object packet;
List<Object> players = new ArrayList<>();
if (NMSStorage.getInstance().is1_19_3Plus()) {
if (BukkitReflection.is1_19_3Plus()) {
EnumSet<?> actions;
if (action == TabList.Action.ADD_PLAYER) {
actions = EnumSet.allOf(EnumPlayerInfoActionClass);
if (action == Action.ADD_PLAYER) {
actions = EnumSet.allOf(ActionClass);
} else {
actions = EnumSet.of(Enum.valueOf(EnumPlayerInfoActionClass, action.name()));
actions = EnumSet.of(Enum.valueOf(ActionClass, action.name()));
}
packet = newPacketPlayOutPlayerInfo.newInstance(actions, Collections.emptyList());
GameProfile profile = new GameProfile(entry.getUniqueId(), entry.getName());
if (entry.getSkin() != null) profile.getProperties().put(TabList.TEXTURES_PROPERTY,
new Property(TabList.TEXTURES_PROPERTY, entry.getSkin().getValue(), entry.getSkin().getSignature()));
packet = newPlayerInfo.newInstance(actions, Collections.emptyList());
players.add(newPlayerInfoData.newInstance(
entry.getUniqueId(),
profile,
createProfile(entry.getUniqueId(), entry.getName(), entry.getSkin()),
true,
entry.getLatency(),
int2GameMode(entry.getGameMode()),
entry.getDisplayName() == null ? null : nms.toNMSComponent(entry.getDisplayName(), clientVersion),
gameModes[entry.getGameMode()],
entry.getDisplayName() == null ? null : toComponent(entry.getDisplayName()),
null
));
} else {
packet = newPacketPlayOutPlayerInfo.newInstance(Enum.valueOf(EnumPlayerInfoActionClass, action.name()),
Array.newInstance(NMSStorage.getInstance().EntityPlayer, 0));
GameProfile profile = new GameProfile(entry.getUniqueId(), entry.getName());
if (entry.getSkin() != null) profile.getProperties().put(TabList.TEXTURES_PROPERTY,
new Property(TabList.TEXTURES_PROPERTY, entry.getSkin().getValue(), entry.getSkin().getSignature()));
packet = newPlayerInfo.newInstance(Enum.valueOf(ActionClass, action.name()), Array.newInstance(EntityPlayer, 0));
List<Object> parameters = new ArrayList<>();
if (newPlayerInfoData.getParameterTypes()[0] == PacketPlayOutPlayerInfoClass) {
if (newPlayerInfoData.getParameterTypes()[0] == PlayerInfoClass) {
parameters.add(packet);
}
parameters.add(profile);
parameters.add(createProfile(entry.getUniqueId(), entry.getName(), entry.getSkin()));
parameters.add(entry.getLatency());
parameters.add(int2GameMode(entry.getGameMode()));
parameters.add(entry.getDisplayName() == null ? null : nms.toNMSComponent(entry.getDisplayName(), clientVersion));
if (nms.getMinorVersion() >= 19) parameters.add(null);
parameters.add(gameModes[entry.getGameMode()]);
parameters.add(entry.getDisplayName() == null ? null : toComponent(entry.getDisplayName()));
if (BukkitReflection.getMinorVersion() >= 19) parameters.add(null);
players.add(newPlayerInfoData.newInstance(parameters.toArray()));
}
PLAYERS.set(packet, players);
return packet;
}
private Object int2GameMode(int gameMode) {
switch (gameMode) {
case 1: return Enum.valueOf(EnumGamemodeClass, "CREATIVE");
case 2: return Enum.valueOf(EnumGamemodeClass, "ADVENTURE");
case 3: return Enum.valueOf(EnumGamemodeClass, "SPECTATOR");
default: return Enum.valueOf(EnumGamemodeClass, "SURVIVAL");
public Object toComponent(IChatBaseComponent component) {
return componentCache.get(component, player.getVersion());
}
@NotNull
private GameProfile createProfile(@NotNull UUID id, @Nullable String name, @Nullable Skin skin) {
GameProfile profile = new GameProfile(id, name == null ? "" : name);
if (skin != null) {
profile.getProperties().put(TabList.TEXTURES_PROPERTY,
new Property(TabList.TEXTURES_PROPERTY, skin.getValue(), skin.getSignature()));
}
return profile;
}
@Nullable
public Skin getSkin() {
if (skinData == null) return null;
return skinData.getSkin(player);
}
private static class SkinData {
private final Method getHandle;
private final Method getProfile;
@SneakyThrows
public SkinData() {
Class<?> EntityHuman = BukkitReflection.getClass("world.entity.player.Player", "world.entity.player.EntityHuman", "EntityHuman");
getHandle = Class.forName("org.bukkit.craftbukkit." + BukkitReflection.getServerPackage() + ".entity.CraftPlayer").getMethod("getHandle");
// There is only supposed to be one, however there are exceptions:
// #1 - CatServer adds another method
// #2 - Random mods may perform deep hack into the server and add another one (see #1089)
// Get first and hope for the best, alternatively players may not have correct skins in layout, but who cares
getProfile = ReflectionUtils.getMethods(EntityHuman, GameProfile.class).get(0);
}
@Nullable
@SneakyThrows
public Skin getSkin(@NotNull BukkitTabPlayer player) {
Collection<Property> col = ((GameProfile) getProfile.invoke(getHandle.invoke(player.getPlayer()))).getProperties().get(TEXTURES_PROPERTY);
if (col.isEmpty()) return null; //offline mode
Property property = col.iterator().next();
if (BukkitReflection.is1_20_2Plus()) {
return new Skin(
(String) property.getClass().getMethod("value").invoke(property),
(String) property.getClass().getMethod("signature").invoke(property)
);
} else {
return new Skin(property.getValue(), property.getSignature());
}
}
}
}
@@ -1,37 +1,31 @@
package me.neznamy.tab.platforms.bukkit;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
import lombok.Getter;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.hook.LibsDisguisesHook;
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
import me.neznamy.tab.platforms.bukkit.nms.PacketEntityView;
import me.neznamy.tab.platforms.bukkit.nms.PacketSender;
import me.neznamy.tab.platforms.bukkit.nms.PingRetriever;
import me.neznamy.tab.platforms.bukkit.platform.BukkitPlatform;
import me.neznamy.tab.platforms.bukkit.scoreboard.PacketScoreboard;
import me.neznamy.tab.shared.chat.rgb.RGBUtils;
import me.neznamy.tab.shared.hook.ViaVersionHook;
import me.neznamy.tab.shared.platform.bossbar.BossBar;
import me.neznamy.tab.platforms.bukkit.scoreboard.ScoreboardLoader;
import me.neznamy.tab.shared.backend.entityview.EntityView;
import me.neznamy.tab.shared.platform.BossBar;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.platforms.bukkit.bossbar.EntityBossBar;
import me.neznamy.tab.platforms.bukkit.bossbar.BukkitBossBar;
import me.neznamy.tab.platforms.bukkit.bossbar.ViaBossBar;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.*;
import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.backend.BackendTabPlayer;
import me.neznamy.tab.shared.backend.EntityData;
import me.neznamy.tab.shared.backend.Location;
import org.bukkit.entity.Entity;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.md_5.bungee.chat.ComponentSerializer;
import org.bukkit.entity.Player;
import org.bukkit.metadata.MetadataValue;
import org.bukkit.potion.PotionEffectType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.UUID;
/**
* TabPlayer implementation for Bukkit platform
*/
@@ -39,29 +33,31 @@ import java.util.UUID;
@Getter
public class BukkitTabPlayer extends BackendTabPlayer {
/** Player's NMS handle (EntityPlayer), preloading for speed */
private final Object handle;
private static final boolean spigot = ReflectionUtils.classExists("org.bukkit.entity.Player$Spigot");
/** Player's connection for sending packets, preloading for speed */
private final Object playerConnection;
@NotNull
private final Scoreboard<BukkitTabPlayer> scoreboard = ScoreboardLoader.getInstance().apply(this);
private final Scoreboard<BukkitTabPlayer> scoreboard = new PacketScoreboard(this);
private final TabList tabList = new BukkitTabList(this);
private final BossBar bossBar = TAB.getInstance().getServerVersion().getMinorVersion() >= 9 ?
new BukkitBossBar(this) : getVersion().getMinorVersion() >= 9 ? new ViaBossBar(this) : new EntityBossBar(this);
@NotNull
private final BukkitTabList tabList = new BukkitTabList(this);
@NotNull
private final BossBar bossBar = BukkitReflection.getMinorVersion() >= 9 ? new BukkitBossBar(this) :
getVersion().getMinorVersion() >= 9 ? new ViaBossBar(this) : new EntityBossBar(this);
@NotNull
private final EntityView entityView = new PacketEntityView(this);
/**
* Constructs new instance with given bukkit player and protocol version
* Constructs new instance with given bukkit player
*
* @param platform
* Server platform
* @param p
* bukkit player
*/
@SneakyThrows
public BukkitTabPlayer(Player p) {
super(p, p.getUniqueId(), p.getName(), TAB.getInstance().getConfiguration().getServerName(),
p.getWorld().getName(), ViaVersionHook.getInstance().getPlayerVersion(p.getUniqueId(), p.getName()));
handle = NMSStorage.getInstance().getHandle.invoke(player);
playerConnection = NMSStorage.getInstance().PLAYER_CONNECTION.get(handle);
public BukkitTabPlayer(@NotNull BukkitPlatform platform, @NotNull Player p) {
super(platform, p, p.getUniqueId(), p.getName(), p.getWorld().getName());
}
@Override
@@ -70,27 +66,23 @@ public class BukkitTabPlayer extends BackendTabPlayer {
}
@Override
@SneakyThrows
public int getPing() {
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 17) {
return getPlayer().getPing();
}
try {
return NMSStorage.getInstance().PING.getInt(handle);
} catch (IllegalAccessException e) {
return -1;
}
return PingRetriever.getPing(getPlayer());
}
@SneakyThrows
public void sendPacket(@Nullable Object nmsPacket) {
if (nmsPacket == null || !getPlayer().isOnline()) return;
NMSStorage.getInstance().sendPacket.invoke(playerConnection, nmsPacket);
public void sendPacket(@NotNull Object nmsPacket) {
PacketSender.sendPacket(getPlayer(), nmsPacket);
}
@Override
public void sendMessage(@NotNull IChatBaseComponent message) {
getPlayer().sendMessage(RGBUtils.getInstance().convertToBukkitFormat(message.toFlatText(),
getVersion().getMinorVersion() >= 16 && TAB.getInstance().getServerVersion().getMinorVersion() >= 16));
if (spigot) {
getPlayer().spigot().sendMessage(ComponentSerializer.parse(message.toString(getVersion())));
} else {
getPlayer().sendMessage(BukkitUtils.toBukkitFormat(message, getVersion().getMinorVersion() >= 16));
}
}
@Override
@@ -100,28 +92,18 @@ public class BukkitTabPlayer extends BackendTabPlayer {
@Override
public boolean isDisguised() {
try {
if (!((BukkitPlatform)TAB.getInstance().getPlatform()).isLibsDisguisesEnabled()) return false;
return (boolean) Class.forName("me.libraryaddict.disguise.DisguiseAPI").getMethod("isDisguised", Entity.class).invoke(null, getPlayer());
} catch (LinkageError | ReflectiveOperationException e) {
//java.lang.NoClassDefFoundError: Could not initialize class me.libraryaddict.disguise.DisguiseAPI
TAB.getInstance().getErrorManager().printError("Failed to check disguise status using LibsDisguises", e);
((BukkitPlatform)TAB.getInstance().getPlatform()).setLibsDisguisesEnabled(false);
return false;
}
return LibsDisguisesHook.isDisguised(this);
}
@Override
@SneakyThrows
@Nullable
public TabList.Skin getSkin() {
Collection<Property> col = ((GameProfile)NMSStorage.getInstance().getProfile.invoke(handle)).getProperties().get(TabList.TEXTURES_PROPERTY);
if (col.isEmpty()) return null; //offline mode
Property property = col.iterator().next();
return new TabList.Skin(property.getValue(), property.getSignature());
return tabList.getSkin();
}
@Override
public @NotNull Player getPlayer() {
@NotNull
public Player getPlayer() {
return (Player) player;
}
@@ -130,6 +112,11 @@ public class BukkitTabPlayer extends BackendTabPlayer {
return getPlayer().isOnline();
}
@Override
public BukkitPlatform getPlatform() {
return (BukkitPlatform) platform;
}
@Override
public boolean isVanished() {
return getPlayer().getMetadata("vanished").stream().anyMatch(MetadataValue::asBoolean);
@@ -141,39 +128,13 @@ public class BukkitTabPlayer extends BackendTabPlayer {
}
@Override
public void spawnEntity(int entityId, @NotNull UUID id, @NotNull Object entityType, @NotNull Location location, @NotNull EntityData data) {
sendPacket(PacketPlayOutSpawnEntityLivingStorage.build(entityId, id, entityType, location, data));
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 15) {
updateEntityMetadata(entityId, data);
}
public double getHealth() {
return getPlayer().getHealth();
}
@Override
@SneakyThrows
public void updateEntityMetadata(int entityId, @NotNull EntityData data) {
if (PacketPlayOutEntityMetadataStorage.CONSTRUCTOR.getParameterCount() == 2) {
//1.19.3+
sendPacket(PacketPlayOutEntityMetadataStorage.CONSTRUCTOR.newInstance(entityId, DataWatcher.packDirty.invoke(data.build())));
} else {
sendPacket(PacketPlayOutEntityMetadataStorage.CONSTRUCTOR.newInstance(entityId, data.build(), true));
}
}
@Override
public void teleportEntity(int entityId, @NotNull Location location) {
sendPacket(PacketPlayOutEntityTeleportStorage.build(entityId, location));
}
@Override
@SneakyThrows
public void destroyEntities(int... entities) {
if (PacketPlayOutEntityDestroyStorage.CONSTRUCTOR.getParameterTypes()[0] != int.class) {
sendPacket(PacketPlayOutEntityDestroyStorage.CONSTRUCTOR.newInstance(new Object[]{entities}));
} else {
//1.17.0 Mojank
for (int entity : entities) {
sendPacket(PacketPlayOutEntityDestroyStorage.CONSTRUCTOR.newInstance(entity));
}
}
@NotNull
public String getDisplayName() {
return getPlayer().getDisplayName();
}
}
@@ -0,0 +1,72 @@
package me.neznamy.tab.platforms.bukkit;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
/**
* Class containing static util methods used on Bukkit.
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class BukkitUtils {
/**
* Returns online players from Bukkit API. This requires reflection, as return type changed in 1.8,
* and we want to avoid errors.
*
* @return Online players from Bukkit API.
*/
@SuppressWarnings("unchecked")
@SneakyThrows
@NotNull
public static Player[] getOnlinePlayers() {
Object players = Bukkit.class.getMethod("getOnlinePlayers").invoke(null);
if (players instanceof Player[]) {
//1.7-
return (Player[]) players;
} else {
//1.8+
return ((Collection<Player>)players).toArray(new Player[0]);
}
}
/**
* Converts component to legacy string using bukkit RGB format if supported by both server and client.
* If not, closest legacy color is used instead.
*
* @param component
* Component to convert
* @param rgbClient
* Whether client accepts RGB colors or not.
* @return Converted string using bukkit color format
*/
@NotNull
public static String toBukkitFormat(@NotNull IChatBaseComponent component, boolean rgbClient) {
StringBuilder sb = new StringBuilder();
if (component.getModifier().getColor() != null) {
if (BukkitReflection.getMinorVersion() >= 16 && rgbClient) {
String hexCode = component.getModifier().getColor().getHexCode();
char c = EnumChatFormat.COLOR_CHAR;
sb.append(c).append("x").append(c).append(hexCode.charAt(0)).append(c).append(hexCode.charAt(1))
.append(c).append(hexCode.charAt(2)).append(c).append(hexCode.charAt(3))
.append(c).append(hexCode.charAt(4)).append(c).append(hexCode.charAt(5));
} else {
sb.append(component.getModifier().getColor().getLegacyColor().getFormat());
}
}
sb.append(component.getModifier().getMagicCodes());
if (component.getText() != null) sb.append(component.getText());
for (IChatBaseComponent extra : component.getExtra()) {
sb.append(toBukkitFormat(extra, rgbClient));
}
return sb.toString();
}
}
@@ -3,10 +3,10 @@ package me.neznamy.tab.platforms.bukkit.bossbar;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.api.bossbar.BarColor;
import me.neznamy.tab.api.bossbar.BarStyle;
import me.neznamy.tab.shared.platform.bossbar.BossBar;
import me.neznamy.tab.shared.chat.rgb.RGBUtils;
import me.neznamy.tab.platforms.bukkit.BukkitUtils;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.BossBar;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.TAB;
import org.bukkit.Bukkit;
import org.jetbrains.annotations.NotNull;
@@ -21,20 +21,25 @@ import java.util.UUID;
@RequiredArgsConstructor
public class BukkitBossBar implements BossBar {
/** Style array for fast access */
private static final org.bukkit.boss.BarStyle[] styles = org.bukkit.boss.BarStyle.values();
/** Player this handler belongs to */
@NotNull
private final BukkitTabPlayer player;
/** Bukkit BossBars the player can currently see */
@NotNull
private final Map<UUID, org.bukkit.boss.BossBar> bossBars = new HashMap<>();
@Override
public void create(@NotNull UUID id, @NotNull String title, float progress, @NotNull BarColor color, @NotNull BarStyle style) {
if (bossBars.containsKey(id)) return;
org.bukkit.boss.BossBar bar = Bukkit.createBossBar(
RGBUtils.getInstance().convertToBukkitFormat(title,
player.getVersion().getMinorVersion() >= 16 && TAB.getInstance().getServerVersion().getMinorVersion() >= 16),
BukkitUtils.toBukkitFormat(IChatBaseComponent.optimizedComponent(title), player.getVersion().getMinorVersion() >= 16),
org.bukkit.boss.BarColor.valueOf(color.name()),
org.bukkit.boss.BarStyle.valueOf(style.getBukkitName()));
styles[style.ordinal()]
);
bar.setProgress(progress);
bar.addPlayer(player.getPlayer());
bossBars.put(id, bar);
@@ -42,8 +47,7 @@ public class BukkitBossBar implements BossBar {
@Override
public void update(@NotNull UUID id, @NotNull String title) {
bossBars.get(id).setTitle(RGBUtils.getInstance().convertToBukkitFormat(title,
player.getVersion().getMinorVersion() >= 16 && TAB.getInstance().getServerVersion().getMinorVersion() >= 16));
bossBars.get(id).setTitle(BukkitUtils.toBukkitFormat(IChatBaseComponent.optimizedComponent(title), player.getVersion().getMinorVersion() >= 16));
}
@Override
@@ -53,7 +57,7 @@ public class BukkitBossBar implements BossBar {
@Override
public void update(@NotNull UUID id, @NotNull BarStyle style) {
bossBars.get(id).setStyle(org.bukkit.boss.BarStyle.valueOf(style.getBukkitName()));
bossBars.get(id).setStyle(styles[style.ordinal()]);
}
@Override
@@ -1,11 +1,11 @@
package me.neznamy.tab.platforms.bukkit.bossbar;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.shared.platform.bossbar.BossBar;
import me.neznamy.tab.shared.platform.BossBar;
import me.neznamy.tab.api.bossbar.BarColor;
import me.neznamy.tab.api.bossbar.BarStyle;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
import me.neznamy.tab.platforms.bukkit.nms.DataWatcher;
import me.neznamy.tab.shared.backend.Location;
import org.bukkit.entity.EntityType;
import org.jetbrains.annotations.NotNull;
@@ -21,6 +21,7 @@ import java.util.UUID;
public class EntityBossBar implements BossBar {
/** Player this handler belongs to */
@NotNull
private final BukkitTabPlayer player;
@Override
@@ -28,18 +29,18 @@ public class EntityBossBar implements BossBar {
DataWatcher w = new DataWatcher();
float health = 300*progress;
if (health == 0) health = 1;
w.getHelper().setHealth(health);
w.getHelper().setCustomName(title, player.getVersion());
w.getHelper().setEntityFlags((byte) 32);
w.getHelper().setWitherInvulnerableTime(880); // Magic number
player.spawnEntity(id.hashCode(), new UUID(0, 0), EntityType.WITHER, new Location(0, 0, 0, 0, 0), w);
w.setHealth(health);
w.setCustomName(title, player.getVersion());
w.setEntityFlags((byte) 32);
w.setWitherInvulnerableTime(880); // Magic number
player.getEntityView().spawnEntity(id.hashCode(), new UUID(0, 0), EntityType.WITHER, new Location(0, 0, 0), w);
}
@Override
public void update(@NotNull UUID id, @NotNull String title) {
DataWatcher w = new DataWatcher();
w.getHelper().setCustomName(title, player.getVersion());
player.updateEntityMetadata(id.hashCode(), w);
w.setCustomName(title, player.getVersion());
player.getEntityView().updateEntityMetadata(id.hashCode(), w);
}
@Override
@@ -47,8 +48,8 @@ public class EntityBossBar implements BossBar {
DataWatcher w = new DataWatcher();
float health = 300*progress;
if (health == 0) health = 1;
w.getHelper().setHealth(health);
player.updateEntityMetadata(id.hashCode(), w);
w.setHealth(health);
player.getEntityView().updateEntityMetadata(id.hashCode(), w);
}
@Override
@@ -59,6 +60,6 @@ public class EntityBossBar implements BossBar {
@Override
public void remove(@NotNull UUID id) {
player.destroyEntities(id.hashCode());
player.getEntityView().destroyEntities(id.hashCode());
}
}
@@ -4,10 +4,10 @@ import com.viaversion.viaversion.api.Via;
import com.viaversion.viaversion.api.legacy.bossbar.BossColor;
import com.viaversion.viaversion.api.legacy.bossbar.BossStyle;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.shared.platform.bossbar.BossBar;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.BossBar;
import me.neznamy.tab.api.bossbar.BarColor;
import me.neznamy.tab.api.bossbar.BarStyle;
import me.neznamy.tab.shared.chat.rgb.RGBUtils;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import org.jetbrains.annotations.NotNull;
@@ -21,28 +21,33 @@ import java.util.UUID;
@RequiredArgsConstructor
public class ViaBossBar implements BossBar {
/** Style array for fast access */
private static final BossStyle[] styles = BossStyle.values();
/** Player this handler belongs to */
@NotNull
private final BukkitTabPlayer player;
/** ViaVersion BossBars this 1.9+ player can see on 1.8 server */
@NotNull
private final Map<UUID, com.viaversion.viaversion.api.legacy.bossbar.BossBar> viaBossBars = new HashMap<>();
@Override
public void create(@NotNull UUID id, @NotNull String title, float progress, @NotNull BarColor color, @NotNull BarStyle style) {
if (viaBossBars.containsKey(id)) return;
com.viaversion.viaversion.api.legacy.bossbar.BossBar bar = Via.getAPI().legacyAPI().createLegacyBossBar(
RGBUtils.getInstance().convertToBukkitFormat(title, player.getVersion().getMinorVersion() >= 16),
IChatBaseComponent.optimizedComponent(title).toString(player.getVersion()),
progress,
BossColor.valueOf(color.name()),
BossStyle.valueOf(style.getBukkitName()));
styles[style.ordinal()]
);
viaBossBars.put(id, bar);
bar.addPlayer(player.getPlayer().getUniqueId());
}
@Override
public void update(@NotNull UUID id, @NotNull String title) {
viaBossBars.get(id).setTitle(RGBUtils.getInstance().convertToBukkitFormat(title,
player.getVersion().getMinorVersion() >= 16));
viaBossBars.get(id).setTitle(IChatBaseComponent.optimizedComponent(title).toString(player.getVersion()));
}
@Override
@@ -52,7 +57,7 @@ public class ViaBossBar implements BossBar {
@Override
public void update(@NotNull UUID id, @NotNull BarStyle style) {
viaBossBars.get(id).setStyle(BossStyle.valueOf(style.getBukkitName()));
viaBossBars.get(id).setStyle(styles[style.ordinal()]);
}
@Override
@@ -1,18 +1,13 @@
package me.neznamy.tab.platforms.bukkit.features;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
import me.neznamy.tab.platforms.bukkit.nms.DataWatcher;
import me.neznamy.tab.platforms.bukkit.platform.BukkitPlatform;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.features.types.PacketSendListener;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutEntityDestroyStorage;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutEntityTeleportStorage;
import me.neznamy.tab.shared.backend.BackendTabPlayer;
import me.neznamy.tab.shared.backend.EntityData;
import me.neznamy.tab.shared.backend.features.unlimitedtags.BackendNameTagX;
import me.neznamy.tab.shared.platform.TabPlayer;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.entity.Entity;
@@ -36,10 +31,7 @@ import java.util.stream.Collectors;
/**
* The core class for unlimited NameTag mode on Bukkit
*/
public class BukkitNameTagX extends BackendNameTagX implements Listener, PacketSendListener {
/** Reference to NMS storage for quick access */
private final NMSStorage nms = NMSStorage.getInstance();
public class BukkitNameTagX extends BackendNameTagX implements Listener {
public BukkitNameTagX(@NotNull JavaPlugin plugin) {
Bukkit.getPluginManager().registerEvents(this, plugin);
@@ -47,45 +39,12 @@ public class BukkitNameTagX extends BackendNameTagX implements Listener, PacketS
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onSneak(PlayerToggleSneakEvent e) {
TabPlayer p = TAB.getInstance().getPlayer(e.getPlayer().getUniqueId());
if (p == null || isPlayerDisabled(p)) return;
TAB.getInstance().getCPUManager().runMeasuredTask(featureName, TabConstants.CpuUsageCategory.PLAYER_SNEAK,
() -> getArmorStandManager(p).sneak(e.isSneaking()));
sneak(e.getPlayer().getUniqueId(), e.isSneaking());
}
@EventHandler
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onRespawn(PlayerRespawnEvent e) {
TabPlayer respawned = TAB.getInstance().getPlayer(e.getPlayer().getUniqueId());
if (respawned == null || isPlayerDisabled(respawned)) return;
TAB.getInstance().getCPUManager().runMeasuredTask(featureName, TabConstants.CpuUsageCategory.PLAYER_RESPAWN,
() -> getArmorStandManager(respawned).teleport());
}
@SuppressWarnings("unchecked")
@Override
@SneakyThrows
public void onPacketSend(@NotNull TabPlayer receiver, @NotNull Object packet) {
if (receiver.getVersion().getMinorVersion() < 8) return;
if (!receiver.isLoaded() || getDisableChecker().isDisabledPlayer(receiver) || getUnlimitedDisableChecker().isDisabledPlayer(receiver)) return;
if (nms.PacketPlayOutEntity.isInstance(packet) && !nms.PacketPlayOutEntityLook.isInstance(packet)) { //ignoring head rotation only packets
packetListener.onEntityMove((BackendTabPlayer) receiver, nms.PacketPlayOutEntity_ENTITYID.getInt(packet));
} else if (PacketPlayOutEntityTeleportStorage.CLASS.isInstance(packet)) {
packetListener.onEntityMove((BackendTabPlayer) receiver, PacketPlayOutEntityTeleportStorage.ENTITY_ID.getInt(packet));
} else if (nms.PacketPlayOutNamedEntitySpawn.isInstance(packet)) {
packetListener.onEntitySpawn((BackendTabPlayer) receiver, nms.PacketPlayOutNamedEntitySpawn_ENTITYID.getInt(packet));
} else if (PacketPlayOutEntityDestroyStorage.CLASS.isInstance(packet)) {
if (nms.getMinorVersion() >= 17) {
Object entities = PacketPlayOutEntityDestroyStorage.ENTITIES.get(packet);
if (entities instanceof List) {
packetListener.onEntityDestroy((BackendTabPlayer) receiver, (List<Integer>) entities);
} else {
//1.17.0
packetListener.onEntityDestroy((BackendTabPlayer) receiver, (int) entities);
}
} else {
packetListener.onEntityDestroy((BackendTabPlayer) receiver, (int[]) PacketPlayOutEntityDestroyStorage.ENTITIES.get(packet));
}
}
respawn(e.getPlayer().getUniqueId());
}
@Override
@@ -112,9 +71,10 @@ public class BukkitNameTagX extends BackendNameTagX implements Listener, PacketS
@SuppressWarnings("deprecation")
@Override
public @NotNull List<Integer> getPassengers(@NotNull Object entity) {
@NotNull
public List<Integer> getPassengers(@NotNull Object entity) {
Entity vehicle = (Entity) entity;
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 11) {
if (BukkitReflection.getMinorVersion() >= 11) {
return vehicle.getPassengers().stream().map(Entity::getEntityId).collect(Collectors.toList());
} else {
if (vehicle.getPassenger() != null) {
@@ -126,7 +86,8 @@ public class BukkitNameTagX extends BackendNameTagX implements Listener, PacketS
}
@Override
public @Nullable Object getVehicle(@NotNull TabPlayer player) {
@Nullable
public Object getVehicle(@NotNull TabPlayer player) {
return ((Player)player.getPlayer()).getVehicle();
}
@@ -136,7 +97,8 @@ public class BukkitNameTagX extends BackendNameTagX implements Listener, PacketS
}
@Override
public @NotNull String getEntityType(@NotNull Object entity) {
@NotNull
public String getEntityType(@NotNull Object entity) {
return ((Entity) entity).getType().toString().toLowerCase();
}
@@ -148,13 +110,13 @@ public class BukkitNameTagX extends BackendNameTagX implements Listener, PacketS
@Override
public boolean isSwimming(@NotNull TabPlayer player) {
Player p = (Player) player.getPlayer();
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 14 && p.getPose() == Pose.SWIMMING) return true;
return TAB.getInstance().getServerVersion().getMinorVersion() == 13 && p.isSwimming();
if (BukkitReflection.getMinorVersion() >= 14 && p.getPose() == Pose.SWIMMING) return true;
return BukkitReflection.getMinorVersion() == 13 && p.isSwimming();
}
@Override
public boolean isGliding(@NotNull TabPlayer player) {
return TAB.getInstance().getServerVersion().getMinorVersion() >= 9 && ((Player)player.getPlayer()).isGliding();
return BukkitReflection.getMinorVersion() >= 9 && ((Player)player.getPlayer()).isGliding();
}
@Override
@@ -163,7 +125,8 @@ public class BukkitNameTagX extends BackendNameTagX implements Listener, PacketS
}
@Override
public @NotNull Object getArmorStandType() {
@NotNull
public Object getArmorStandType() {
return EntityType.ARMOR_STAND;
}
@@ -183,17 +146,23 @@ public class BukkitNameTagX extends BackendNameTagX implements Listener, PacketS
}
@Override
@NotNull
public EntityData createDataWatcher(@NotNull TabPlayer viewer, byte flags, @NotNull String displayName, boolean nameVisible) {
DataWatcher datawatcher = new DataWatcher();
datawatcher.getHelper().setEntityFlags(flags);
datawatcher.getHelper().setCustomName(displayName, viewer.getVersion());
datawatcher.getHelper().setCustomNameVisible(nameVisible);
datawatcher.getHelper().setArmorStandFlags((byte)16);
datawatcher.setEntityFlags(flags);
datawatcher.setCustomName(displayName, viewer.getVersion());
datawatcher.setCustomNameVisible(nameVisible);
datawatcher.setArmorStandFlags((byte)16);
return datawatcher;
}
@Override
public void runInEntityScheduler(Object entity, Runnable task) {
public void runInEntityScheduler(@NotNull Object entity, @NotNull Runnable task) {
((BukkitPlatform)TAB.getInstance().getPlatform()).runEntityTask((Entity) entity, task);
}
@Override
public boolean isDead(@NotNull TabPlayer player) {
return ((BukkitTabPlayer)player).getPlayer().isDead();
}
}
@@ -7,11 +7,14 @@ import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.placeholders.expansion.TabExpansion;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
@@ -22,11 +25,36 @@ import java.util.WeakHashMap;
public class BukkitTabExpansion extends PlaceholderExpansion implements TabExpansion {
/** Map holding all values for all players for easy and high-performance access */
private final WeakHashMap<Player, Map<String, String>> values = new WeakHashMap<>();
@NotNull
private final WeakHashMap<TabPlayer, Map<String, String>> values = new WeakHashMap<>();
private final String author = TabConstants.PLUGIN_AUTHOR;
private final String identifier = TabConstants.PLUGIN_ID;
private final String version = TabConstants.PLUGIN_VERSION;
/** List of all placeholders offered by the plugin for command suggestions */
@NotNull
private final List<String> placeholders = Arrays.asList(
"%tab_tabprefix%",
"%tab_tabsuffix%",
"%tab_tagprefix%",
"%tab_tagsuffix%",
"%tab_customtabname%",
"%tab_customtagname%",
"%tab_belowname%",
"%tab_abovename%",
"%tab_tabprefix_raw%",
"%tab_tabsuffix_raw%",
"%tab_tagprefix_raw%",
"%tab_tagsuffix_raw%",
"%tab_customtabname_raw%",
"%tab_customtagname_raw%",
"%tab_belowname_raw%",
"%tab_abovename_raw%",
"%tab_scoreboard_name%",
"%tab_scoreboard_visible%",
"%tab_bossbar_visible%",
"%tab_nametag_preview%",
"%tab_nametag_visibility%",
"%tab_replace_<placeholder>%",
"%tab_placeholder_<placeholder>%"
);
@Override
public boolean persist() {
@@ -34,6 +62,31 @@ public class BukkitTabExpansion extends PlaceholderExpansion implements TabExpan
}
@Override
@NotNull
public String getAuthor() {
return TabConstants.PLUGIN_AUTHOR;
}
@Override
@NotNull
public String getIdentifier() {
return TabConstants.PLUGIN_ID;
}
@Override
@NotNull
public String getVersion() {
return TabConstants.PLUGIN_VERSION;
}
@Override
@NotNull
public List<String> getPlaceholders() {
return placeholders;
}
@Override
@Nullable
public String onPlaceholderRequest(@Nullable Player player, @NotNull String identifier) {
if (identifier.startsWith("replace_")) {
String text = "%" + identifier.substring(8) + "%";
@@ -50,11 +103,25 @@ public class BukkitTabExpansion extends PlaceholderExpansion implements TabExpan
if (identifier.startsWith("placeholder_")) {
TAB.getInstance().getPlaceholderManager().addUsedPlaceholder("%" + identifier.substring(12) + "%", TAB.getInstance().getPlaceholderManager());
}
return values.get(player).get(identifier);
if (player == null) return "<Player cannot be null>";
TabPlayer p = TAB.getInstance().getPlayer(player.getUniqueId());
if (p == null || !p.isLoaded()) return "<Player is not loaded>";
return values.get(p).get(identifier);
}
@Override
public void setValue(@NotNull TabPlayer player, @NotNull String key, @NotNull String value) {
values.computeIfAbsent((Player) player.getPlayer(), p -> new HashMap<>()).put(key, value);
values.computeIfAbsent(player, p -> new HashMap<>()).put(key, value);
}
@Override
@SuppressWarnings({"UnstableApiUsage", "deprecation"})
public void unregisterExpansion() {
if (ReflectionUtils.methodExists(PlaceholderExpansion.class, "unregister")) {
// Added in 2.10.7 (Jul 28, 2020)
unregister();
} else {
PlaceholderAPI.unregisterExpansion(this);
}
}
}
@@ -6,7 +6,7 @@ import java.util.Map;
import java.util.Map.Entry;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.platforms.bukkit.BukkitUtils;
import me.neznamy.tab.shared.features.types.Loadable;
import me.neznamy.tab.shared.features.types.UnLoadable;
import org.bukkit.Bukkit;
@@ -27,29 +27,36 @@ import org.jetbrains.annotations.NotNull;
* Per-world-PlayerList feature handler
*/
@SuppressWarnings("deprecation")
@RequiredArgsConstructor
public class PerWorldPlayerList extends TabFeature implements Listener, Loadable, UnLoadable {
@Getter private final String featureName = "Per world PlayerList";
/** Config options */
private final boolean allowBypass = TAB.getInstance().getConfig().getBoolean("per-world-playerlist.allow-bypass-permission", false);
private final List<String> ignoredWorlds = TAB.getInstance().getConfig().getStringList("per-world-playerlist.ignore-effect-in-worlds", Arrays.asList("ignored_world", "build"));
private final Map<String, List<String>> sharedWorlds = TAB.getInstance().getConfig().getConfigurationSection("per-world-playerlist.shared-playerlist-world-groups");
/** Plugin reference*/
@NotNull private final JavaPlugin plugin;
@Getter private final String featureName = "Per world PlayerList";
@Override
public void load() {
Bukkit.getOnlinePlayers().forEach(this::checkPlayer);
/**
* Constructs new instance and registers events
*
* @param plugin
* Plugin instance to register events
*/
public PerWorldPlayerList(JavaPlugin plugin) {
Bukkit.getPluginManager().registerEvents(this, plugin);
}
@Override
public void load() {
for (Player p : BukkitUtils.getOnlinePlayers()) {
checkPlayer(p);
}
}
@Override
public void unload() {
for (Player p : Bukkit.getOnlinePlayers()) {
for (Player pl : Bukkit.getOnlinePlayers()) {
for (Player p : BukkitUtils.getOnlinePlayers()) {
for (Player pl : BukkitUtils.getOnlinePlayers()) {
p.showPlayer(pl);
}
}
@@ -60,14 +67,14 @@ public class PerWorldPlayerList extends TabFeature implements Listener, Loadable
public void onJoin(PlayerJoinEvent e) {
long time = System.nanoTime();
checkPlayer(e.getPlayer());
TAB.getInstance().getCPUManager().addTime(featureName, TabConstants.CpuUsageCategory.PLAYER_JOIN, System.nanoTime()-time);
TAB.getInstance().getCPUManager().addTime(this, TabConstants.CpuUsageCategory.PLAYER_JOIN, System.nanoTime()-time);
}
@EventHandler
public void onWorldChange(PlayerChangedWorldEvent e) {
long time = System.nanoTime();
checkPlayer(e.getPlayer());
TAB.getInstance().getCPUManager().addTime(featureName, TabConstants.CpuUsageCategory.WORLD_SWITCH, System.nanoTime()-time);
TAB.getInstance().getCPUManager().addTime(this, TabConstants.CpuUsageCategory.WORLD_SWITCH, System.nanoTime()-time);
}
/**
@@ -79,7 +86,7 @@ public class PerWorldPlayerList extends TabFeature implements Listener, Loadable
* Player to update
*/
private void checkPlayer(@NotNull Player p) {
for (Player all : Bukkit.getOnlinePlayers()) {
for (Player all : BukkitUtils.getOnlinePlayers()) {
if (all == p) continue;
if (!shouldSee(p, all) && p.canSee(all)) p.hidePlayer(all);
if (shouldSee(p, all) && !p.canSee(all)) p.showPlayer(all);
@@ -1,6 +1,7 @@
package me.neznamy.tab.platforms.bukkit.features;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.api.bossbar.BossBar;
@@ -10,7 +11,6 @@ import me.neznamy.tab.shared.backend.BackendTabPlayer;
import me.neznamy.tab.shared.features.bossbar.BossBarManagerImpl;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener;
@@ -24,15 +24,18 @@ import org.jetbrains.annotations.NotNull;
@RequiredArgsConstructor
public class WitherBossBar extends BossBarManagerImpl implements Listener, WorldSwitchListener {
/** Distance of the Wither in blocks */
private static final int WITHER_DISTANCE = 60;
/** Reference to plugin for registering listener */
private final @NotNull JavaPlugin plugin;
/**
* Constructs new instance and registers events
*
* @param plugin
* Plugin instance to register events
*/
public WitherBossBar(JavaPlugin plugin) {
Bukkit.getPluginManager().registerEvents(this, plugin);
}
@Override
public void load() {
Bukkit.getPluginManager().registerEvents(this, plugin);
//when MC is on fullscreen, BossBar disappears after 1 second of not being seen
//when in a small window, it's about 100ms
TAB.getInstance().getCPUManager().startRepeatingMeasuredTask(100,
@@ -49,9 +52,10 @@ public class WitherBossBar extends BossBarManagerImpl implements Listener, World
if (p.getVersion().getMinorVersion() > 8) continue; //sending VV packets to those
for (BossBar line : getRegisteredBossBars().values()) {
if (!line.getPlayers().contains(p)) continue;
Location loc = ((Player) p.getPlayer()).getEyeLocation().add(((Player) p.getPlayer()).getEyeLocation().getDirection().normalize().multiply(WITHER_DISTANCE));
Location eyeLocation = ((BukkitTabPlayer)p).getPlayer().getEyeLocation();
Location loc = eyeLocation.add(eyeLocation.getDirection().normalize().multiply(60)); // Wither distance
if (loc.getY() < 1) loc.setY(1);
((BackendTabPlayer)p).teleportEntity(line.getUniqueId().hashCode(), new me.neznamy.tab.shared.backend.Location(loc.getX(), loc.getY(), loc.getZ(), 0, 0));
((BackendTabPlayer)p).getEntityView().teleportEntity(line.getUniqueId().hashCode(), new me.neznamy.tab.shared.backend.Location(loc.getX(), loc.getY(), loc.getZ()));
}
}
}
@@ -0,0 +1,33 @@
package me.neznamy.tab.platforms.bukkit.header;
import lombok.Getter;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.platforms.bukkit.BukkitUtils;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
/**
* Header/footer sender using Bukkit API. Despite this feature getting
* added into the game in 1.8, Bukkit team took 4 entire years to add
* it to the API, which eventually made it there in 1.13. <p>
* The input is just a String, meaning Bukkit will have to deserialize it
* into components, which is a massive performance loss. It also does not
* seem to support fonts.
*/
public class BukkitHeaderFooter extends HeaderFooter {
/** Flag tracking whether this class can be used on current server */
@Getter
private static final boolean available = ReflectionUtils.methodExists(Player.class, "setPlayerListHeaderFooter", String.class, String.class);
@Override
@SuppressWarnings("deprecation") // Marked as deprecated by Paper to make us use their methods instead
public void set(@NotNull BukkitTabPlayer player, @NotNull IChatBaseComponent header, @NotNull IChatBaseComponent footer) {
player.getPlayer().setPlayerListHeaderFooter(
BukkitUtils.toBukkitFormat(header, player.getVersion().getMinorVersion() >= 16),
BukkitUtils.toBukkitFormat(footer, player.getVersion().getMinorVersion() >= 16)
);
}
}
@@ -0,0 +1,55 @@
package me.neznamy.tab.platforms.bukkit.header;
import lombok.Getter;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.platforms.bukkit.nms.PacketSender;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import org.bukkit.Bukkit;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* A class responsible for sending header/footer to players on request.
*/
public abstract class HeaderFooter {
/** Instance of the class, null if no implementation is available */
@Getter
@Nullable
private static HeaderFooter instance;
/**
* Finds the best available instance for current server software.
*/
public static void findInstance() {
if (PaperHeaderFooter.isAvailable()) {
instance = new PaperHeaderFooter();
} else if (PacketHeaderFooter.isAvailable() && PacketSender.isAvailable()) {
instance = new PacketHeaderFooter();
} else if (BukkitHeaderFooter.isAvailable()) {
instance = new BukkitHeaderFooter();
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.RED.getFormat() + "[TAB] Failed to initialize NMS fields for " +
"sending Header/Footer due to a compatibility error. Using fallback solution using Bukkit API. " +
"This will drastically drop performance, as well as miss support for fonts (added in 1.16). " +
"Please update the plugin to version with proper support for your server version.");
} else {
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.RED.getFormat() + "[TAB] Failed to initialize NMS fields for " +
"sending Header/Footer due to a compatibility error. No fallback solution was found either. This will " +
"result in the feature not working. " +
"Please update the plugin to version with proper support for your server version.");
}
}
/**
* Sends header/footer to player.
*
* @param player
* Player to send header/footer to.
* @param header
* Header to use.
* @param footer
* Footer to use.
*/
public abstract void set(@NotNull BukkitTabPlayer player, @NotNull IChatBaseComponent header, @NotNull IChatBaseComponent footer);
}
@@ -0,0 +1,73 @@
package me.neznamy.tab.platforms.bukkit.header;
import lombok.Getter;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.util.ComponentCache;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* Header/footer sender that uses NMS to send packets. Available
* on all versions since 1.8 when the feature was added into the game.
*/
public class PacketHeaderFooter extends HeaderFooter {
private static Constructor<?> newHeaderFooter;
private static Field HEADER;
private static Field FOOTER;
private static Method ChatSerializer_DESERIALIZE;
private static final ComponentCache<IChatBaseComponent, Object> componentCache = new ComponentCache<>(1000,
(component, clientVersion) -> ChatSerializer_DESERIALIZE.invoke(null, component.toString(clientVersion)));
@Getter
private static boolean available;
static {
try {
if (BukkitReflection.getMinorVersion() >= 8) {
Class<?> IChatBaseComponent = BukkitReflection.getClass("network.chat.Component",
"network.chat.IChatBaseComponent", "IChatBaseComponent");
Class<?> HeaderFooterClass = BukkitReflection.getClass("network.protocol.game.ClientboundTabListPacket",
"network.protocol.game.PacketPlayOutPlayerListHeaderFooter", "PacketPlayOutPlayerListHeaderFooter");
Class<?> ChatSerializer = BukkitReflection.getClass("network.chat.Component$Serializer",
"network.chat.IChatBaseComponent$ChatSerializer", "IChatBaseComponent$ChatSerializer", "ChatSerializer");
ChatSerializer_DESERIALIZE = ReflectionUtils.getMethods(ChatSerializer, Object.class, String.class).get(0);
if (BukkitReflection.getMinorVersion() >= 17) {
newHeaderFooter = HeaderFooterClass.getConstructor(IChatBaseComponent, IChatBaseComponent);
} else {
newHeaderFooter = HeaderFooterClass.getConstructor();
HEADER = ReflectionUtils.getFields(HeaderFooterClass, IChatBaseComponent).get(0);
FOOTER = ReflectionUtils.getFields(HeaderFooterClass, IChatBaseComponent).get(1);
}
available = true;
}
} catch (Exception ignored) {
// Print exception to find out what went wrong
}
}
@SneakyThrows
@Override
public void set(@NotNull BukkitTabPlayer player, @NotNull IChatBaseComponent header, @NotNull IChatBaseComponent footer) {
if (BukkitReflection.getMinorVersion() >= 17) {
player.sendPacket(newHeaderFooter.newInstance(convert(header, player), convert(footer, player)));
} else {
Object packet = newHeaderFooter.newInstance();
HEADER.set(packet, convert(header, player));
FOOTER.set(packet, convert(footer, player));
player.sendPacket(packet);
}
}
private Object convert(IChatBaseComponent component, TabPlayer player) {
return componentCache.get(component, player.getVersion());
}
}
@@ -0,0 +1,32 @@
package me.neznamy.tab.platforms.bukkit.header;
import lombok.Getter;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.hook.AdventureHook;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.kyori.adventure.text.Component;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
/**
* Header/footer sender using Paper's API added in 1.16.5.
* Thanks to both cache and direct component conversion, this
* implementation is even faster than using NMS components
* and is therefore preferred if available.
*/
public class PaperHeaderFooter extends HeaderFooter {
/** Flag tracking whether this class can be used on current server */
@Getter
private static final boolean available = ReflectionUtils.classExists("net.kyori.adventure.text.Component") &&
ReflectionUtils.methodExists(Player.class, "sendPlayerListHeaderAndFooter", Component.class, Component.class);
@Override
public void set(@NotNull BukkitTabPlayer player, @NotNull IChatBaseComponent header, @NotNull IChatBaseComponent footer) {
player.getPlayer().sendPlayerListHeaderAndFooter(
AdventureHook.toAdventureComponent(header, player.getVersion()),
AdventureHook.toAdventureComponent(footer, player.getVersion())
);
}
}
@@ -0,0 +1,33 @@
package me.neznamy.tab.platforms.bukkit.hook;
import me.libraryaddict.disguise.DisguiseAPI;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.util.ReflectionUtils;
/**
* Class for hooking into LibsDisguises to get disguise status of players.
*/
public class LibsDisguisesHook {
/** Flag tracking if LibsDisguises is installed or not */
private static boolean installed = ReflectionUtils.classExists("me.libraryaddict.disguise.DisguiseAPI");
/**
* Returns {@code true} if LibsDisguises is installed and player is disguised,
* {@code false} otherwise.
*
* @param player
* Player to check
* @return {@code true} if disguised, {@code false} otherwise
*/
public static boolean isDisguised(TabPlayer player) {
try {
return installed && DisguiseAPI.isDisguised(((BukkitTabPlayer)player).getPlayer());
} catch (LinkageError e) {
//java.lang.NoClassDefFoundError: Could not initialize class me.libraryaddict.disguise.DisguiseAPI
installed = false;
return false;
}
}
}
@@ -0,0 +1,61 @@
package me.neznamy.tab.platforms.bukkit.nms;
import lombok.Getter;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.bukkit.Bukkit;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
public class BukkitReflection {
/** Server's NMS/CraftBukkit package */
@Getter
private static final String serverPackage = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3];
/** Server's minor version */
@Getter
private static final int minorVersion = Integer.parseInt(serverPackage.split("_")[1]);
/** Flag determining whether the server version is at least 1.19.3 or not */
@Getter
private static final boolean is1_19_3Plus = ReflectionUtils.classExists("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket");
/** Flag determining whether the server version is at least 1.19.4 or not */
@Getter
private static final boolean is1_19_4Plus = is1_19_3Plus && !serverPackage.equals("v1_19_R2");
/** Flag determining whether the server version is at least 1.20.2 or not */
@Getter
private static final boolean is1_20_2Plus = ReflectionUtils.classExists("net.minecraft.world.scores.DisplaySlot");
/** Flag determining whether the server version is at least 1.20.3 or not */
@Getter
private static final boolean is1_20_3Plus = ReflectionUtils.classExists("net.minecraft.network.protocol.game.ClientboundResetScorePacket");
/**
* Returns class with given potential names in same order. For 1.17+ it takes packaged class names
* without "net.minecraft." prefix, for <1.17 it takes class name only.
*
* @param names
* possible class names
* @return class for specified names
* @throws ClassNotFoundException
* if class does not exist
*/
public static Class<?> getClass(@NotNull String... names) throws ClassNotFoundException {
ClassLoader loader = BukkitReflection.class.getClassLoader();
for (String name : names) {
try {
if (minorVersion >= 17) {
return Class.forName("net.minecraft." + name);
} else {
return loader.loadClass("net.minecraft.server." + serverPackage + "." + name);
}
} catch (ClassNotFoundException | NullPointerException ignored) {
// not the first class name in array
}
}
throw new ClassNotFoundException("No class found with possible names " + Arrays.toString(names));
}
}
@@ -0,0 +1,283 @@
package me.neznamy.tab.platforms.bukkit.nms;
import lombok.*;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.backend.EntityData;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.util.ComponentCache;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
/**
* A class representing the n.m.s.DataWatcher class to make work with it much easier
*/
@ToString
public class DataWatcher implements EntityData {
/** NMS Fields */
public static Class<?> DataWatcher;
private static Constructor<?> newDataWatcher;
private static Method DataWatcher_register;
private static Method DataWatcher_markDirty;
public static Method DataWatcher_packDirty;
private static Class<?> DataWatcherObject;
private static Constructor<?> newDataWatcherObject;
private static Object DataWatcherSerializer_BYTE;
private static Object DataWatcherSerializer_FLOAT;
private static Object DataWatcherSerializer_STRING;
private static Object DataWatcherSerializer_OPTIONAL_COMPONENT;
private static Object DataWatcherSerializer_BOOLEAN;
private static final int armorStandFlagsPosition = getArmorStandFlagsPosition();
private static Method ChatSerializer_DESERIALIZE;
private static final ComponentCache<IChatBaseComponent, Object> componentCache = new ComponentCache<>(1000,
(component, clientVersion) -> ChatSerializer_DESERIALIZE.invoke(null, component.toString(clientVersion)));
/** Watched data */
private final Map<Integer, Item> dataValues = new HashMap<>();
/**
* Returns armor stand flags position based on server version
*
* @return armor stand flags position based on server version
*/
private static int getArmorStandFlagsPosition() {
if (BukkitReflection.getMinorVersion() >= 17) {
//1.17.x, 1.18.x, 1.19.x, 1.20.x
return 15;
} else if (BukkitReflection.getMinorVersion() >= 15) {
//1.15.x, 1.16.x
return 14;
} else if (BukkitReflection.getMinorVersion() == 14) {
//1.14.x
return 13;
} else if (BukkitReflection.getMinorVersion() >= 10) {
//1.10.x - 1.13.x
return 11;
} else {
//1.8.x - 1.9.x
return 10;
}
}
/**
* Loads all required Fields and throws Exception if something went wrong
*/
public static void load() throws ReflectiveOperationException {
int minorVersion = BukkitReflection.getMinorVersion();
DataWatcher = BukkitReflection.getClass("network.syncher.SynchedEntityData", "network.syncher.DataWatcher", "DataWatcher");
if (minorVersion >= 7) {
Class<?> ChatSerializer = BukkitReflection.getClass("network.chat.Component$Serializer",
"network.chat.IChatBaseComponent$ChatSerializer", "IChatBaseComponent$ChatSerializer", "ChatSerializer");
ChatSerializer_DESERIALIZE = ReflectionUtils.getMethods(ChatSerializer, Object.class, String.class).get(0);
newDataWatcher = DataWatcher.getConstructor(BukkitReflection.getClass("world.entity.Entity", "Entity"));
} else {
newDataWatcher = DataWatcher.getConstructor();
}
if (minorVersion >= 9) {
DataWatcherObject = BukkitReflection.getClass("network.syncher.EntityDataAccessor",
"network.syncher.DataWatcherObject", "DataWatcherObject");
Class<?> dataWatcherRegistry = BukkitReflection.getClass("network.syncher.EntityDataSerializers",
"network.syncher.DataWatcherRegistry", "DataWatcherRegistry");
Class<?> dataWatcherSerializer = BukkitReflection.getClass("network.syncher.EntityDataSerializer",
"network.syncher.DataWatcherSerializer", "DataWatcherSerializer");
DataWatcher_register = ReflectionUtils.getMethod(
DataWatcher,
new String[]{"define", "register", "a", "m_135372_"}, // {Mojang, Bukkit, Bukkit 1.18+, Mohist 1.18.2}
DataWatcherObject, Object.class
);
newDataWatcherObject = DataWatcherObject.getConstructor(int.class, dataWatcherSerializer);
DataWatcherSerializer_BYTE = ReflectionUtils.getField(dataWatcherRegistry, "BYTE", "a", "f_135027_").get(null); // Mohist 1.18.2
DataWatcherSerializer_FLOAT = ReflectionUtils.getField(dataWatcherRegistry, "FLOAT", "c", "f_135029_").get(null); // Mohist 1.18.2
DataWatcherSerializer_STRING = ReflectionUtils.getField(dataWatcherRegistry, "STRING", "d", "f_135030_").get(null); // Mohist 1.18.2
if (BukkitReflection.is1_19_3Plus()) {
DataWatcherSerializer_OPTIONAL_COMPONENT = ReflectionUtils.getField(dataWatcherRegistry, "OPTIONAL_COMPONENT", "g").get(null);
if (BukkitReflection.is1_19_4Plus()) {
DataWatcherSerializer_BOOLEAN = ReflectionUtils.getField(dataWatcherRegistry, "BOOLEAN", "k").get(null);
} else {
DataWatcherSerializer_BOOLEAN = ReflectionUtils.getField(dataWatcherRegistry, "BOOLEAN", "j").get(null);
}
} else {
if (minorVersion >= 13) {
DataWatcherSerializer_OPTIONAL_COMPONENT = ReflectionUtils.getField(dataWatcherRegistry,
"OPTIONAL_COMPONENT", "f", "f_135032_").get(null); // Mohist 1.18.2
DataWatcherSerializer_BOOLEAN = ReflectionUtils.getField(dataWatcherRegistry,
"BOOLEAN", "i", "f_135035_").get(null); // Mohist 1.18.2
} else {
DataWatcherSerializer_BOOLEAN = dataWatcherRegistry.getDeclaredField("h").get(null);
}
}
} else {
DataWatcher_register = ReflectionUtils.getMethod(
DataWatcher,
new String[]{"func_75682_a", "a"}, int.class, // {Thermos 1.7.10, Bukkit}
Object.class
);
}
if (minorVersion >= 19) {
DataWatcher_packDirty = ReflectionUtils.getMethod(DataWatcher, new String[] {"packDirty", "b"}); // {Mojang, Bukkit}
}
if (BukkitReflection.is1_19_3Plus()) {
DataWatcher_markDirty = ReflectionUtils.getMethods(DataWatcher, void.class, DataWatcherObject).get(0);
}
}
/**
* Sets value into data values
*
* @param position
* data position
* @param serializer
* Object serializer (1.9+)
* @param value
* value
*/
public void setValue(int position, @Nullable Object serializer, @NotNull Object value) {
dataValues.put(position, new Item(position, serializer, value));
}
/**
* Writes entity byte flags
*
* @param flags
* flags to write
*/
public void setEntityFlags(byte flags) {
setValue(0, DataWatcherSerializer_BYTE, flags);
}
/**
* Writes entity custom name with position based on server version and value depending on client version (RGB or not)
*
* @param customName
* target custom name
* @param clientVersion
* client version
*/
public void setCustomName(@NotNull String customName, @NotNull ProtocolVersion clientVersion) {
if (BukkitReflection.getMinorVersion() >= 13) {
setValue(2, DataWatcherSerializer_OPTIONAL_COMPONENT,
Optional.of(componentCache.get(IChatBaseComponent.optimizedComponent(customName), clientVersion)));
} else if (BukkitReflection.getMinorVersion() >= 8) {
setValue(2, DataWatcherSerializer_STRING, customName);
} else {
//name length is limited to 64 characters on <1.8
String cutName = (customName.length() > 64 ? customName.substring(0, 64) : customName);
if (BukkitReflection.getMinorVersion() >= 6) {
setValue(10, null, cutName);
} else {
setValue(5, null, cutName);
}
}
}
/**
* Writes custom name visibility boolean
*
* @param visible
* if visible or not
*/
public void setCustomNameVisible(boolean visible) {
if (BukkitReflection.getMinorVersion() >= 9) {
setValue(3, DataWatcherSerializer_BOOLEAN, visible);
} else {
setValue(3, null, (byte)(visible?1:0));
}
}
/**
* Writes entity health
*
* @param health
* health of entity
*/
public void setHealth(float health) {
if (BukkitReflection.getMinorVersion() >= 6) {
setValue(6, DataWatcherSerializer_FLOAT, health);
} else {
setValue(16, null, (int)health);
}
}
/**
* Writes armor stand flags
*
* @param flags
* flags to write
*/
public void setArmorStandFlags(byte flags) {
setValue(armorStandFlagsPosition, DataWatcherSerializer_BYTE, flags);
}
/**
* Writes wither invulnerable time
* @param time
* Time, apparently
*/
public void setWitherInvulnerableTime(int time) {
if (BukkitReflection.getMinorVersion() > 8)
throw new UnsupportedOperationException("Not supported on 1.9+");
setValue(20, null, time);
}
/**
* Converts the class into an instance of NMS.DataWatcher
*
* @return an instance of NMS.DataWatcher with same data
*/
@SneakyThrows
public @NotNull Object build() {
Object nmsWatcher;
if (newDataWatcher.getParameterCount() == 1) { //1.7+
nmsWatcher = newDataWatcher.newInstance(new Object[] {null});
} else {
nmsWatcher = newDataWatcher.newInstance();
}
for (Item item : dataValues.values()) {
Object nmsObject = item.createObject();
DataWatcher_register.invoke(nmsWatcher, nmsObject, item.getValue());
if (BukkitReflection.is1_19_3Plus()) DataWatcher_markDirty.invoke(nmsWatcher, nmsObject);
}
return nmsWatcher;
}
@RequiredArgsConstructor
@Getter
public static class Item {
/** Item position */
private final int position;
/** Item serializer (1.9+) */
@Nullable
private final Object serializer;
/** Item value */
@NotNull
private final Object value;
/**
* Creates data watcher object with position and serializer
*
* @return NMS data watcher object
*/
@SneakyThrows
public Object createObject() {
if (BukkitReflection.getMinorVersion() >= 9) {
return newDataWatcherObject.newInstance(position, serializer);
} else {
return position;
}
}
}
}
@@ -0,0 +1,409 @@
package me.neznamy.tab.platforms.bukkit.nms;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.backend.EntityData;
import me.neznamy.tab.shared.backend.Location;
import me.neznamy.tab.shared.backend.entityview.EntityView;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.bukkit.Bukkit;
import org.bukkit.entity.EntityType;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.EnumMap;
import java.util.List;
import java.util.UUID;
@RequiredArgsConstructor
@SuppressWarnings("unchecked")
public class PacketEntityView implements EntityView {
/** PacketPlayOutEntityDestroy */
private static Class<?> EntityDestroyClass;
private static Constructor<?> newEntityDestroy;
private static Field EntityDestroy_Entities;
/** PacketPlayOutEntityMetadata */
private static Constructor<?> newEntityMetadata;
/** PacketPlayOutEntityTeleport */
private static Class<?> EntityTeleportClass;
private static Constructor<?> newEntityTeleport;
private static Field EntityTeleport_EntityId;
private static Field EntityTeleport_X;
private static Field EntityTeleport_Y;
private static Field EntityTeleport_Z;
/** PacketPlayOutSpawnEntityLiving */
private static Class<?> SpawnEntityClass;
private static Constructor<?> newSpawnEntity;
/** 1.17+ */
private static Object Vec3D_Empty;
private static Object EntityTypes_ARMOR_STAND;
/** 1.16.5- */
private static Field SpawnEntity_EntityId;
private static Field SpawnEntity_EntityType;
private static Field SpawnEntity_UUID;
private static Field SpawnEntity_X;
private static Field SpawnEntity_Y;
private static Field SpawnEntity_Z;
private static Field SpawnEntity_DataWatcher;
private static final EnumMap<EntityType, Integer> entityIds = new EnumMap<>(EntityType.class);
/** Other entity packets */
private static Class<?> PacketPlayOutEntity;
private static Field PacketPlayOutEntity_ENTITYID;
private static Class<?> PacketPlayOutEntityLook;
private static Class<?> PacketPlayOutNamedEntitySpawn;
private static Field PacketPlayOutNamedEntitySpawn_ENTITYID;
private static Class<?> ClientboundBundlePacket;
private static Field ClientboundBundlePacket_packets;
private static Object dummyEntity;
@Getter
private static boolean available;
/** Player this view belongs to */
private final BukkitTabPlayer player;
/**
* Loads all required classes and fields and throws Exception if something went wrong
*
* @throws ReflectiveOperationException
* If something fails
*/
public static void load() throws ReflectiveOperationException {
loadEntityMetadata();
loadEntityDestroy();
loadEntityTeleport();
loadEntityMove();
loadEntitySpawn();
if (BukkitReflection.is1_19_4Plus()) {
ClientboundBundlePacket = Class.forName("net.minecraft.network.protocol.game.ClientboundBundlePacket");
ClientboundBundlePacket_packets = ReflectionUtils.getOnlyField(ClientboundBundlePacket.getSuperclass(), Iterable.class);
}
available = true;
}
private static void loadEntityMetadata() throws ReflectiveOperationException {
// Class
Class<?> entityMetadataClass = getClass("network.protocol.game.ClientboundSetEntityDataPacket",
"network.protocol.game.PacketPlayOutEntityMetadata", "PacketPlayOutEntityMetadata", "Packet40EntityMetadata");
// Constructor
if (BukkitReflection.is1_19_3Plus()) {
newEntityMetadata = entityMetadataClass.getConstructor(int.class, List.class);
} else {
newEntityMetadata = entityMetadataClass.getConstructor(int.class, DataWatcher.DataWatcher, boolean.class);
}
}
private static void loadEntityDestroy() throws ReflectiveOperationException {
// Class
EntityDestroyClass = getClass("network.protocol.game.ClientboundRemoveEntitiesPacket",
"network.protocol.game.PacketPlayOutEntityDestroy", "PacketPlayOutEntityDestroy", "Packet29DestroyEntity");
// Constructor
try {
newEntityDestroy = EntityDestroyClass.getConstructor(int[].class);
} catch (NoSuchMethodException e) {
//1.17.0
newEntityDestroy = EntityDestroyClass.getConstructor(int.class);
}
// Field
EntityDestroy_Entities = ReflectionUtils.getOnlyField(EntityDestroyClass);
}
private static void loadEntityTeleport() throws ReflectiveOperationException {
// Class
EntityTeleportClass = getClass("network.protocol.game.ClientboundTeleportEntityPacket",
"network.protocol.game.PacketPlayOutEntityTeleport", "PacketPlayOutEntityTeleport", "Packet34EntityTeleport");
// Constructor
if (BukkitReflection.getMinorVersion() >= 17) {
newEntityTeleport = EntityTeleportClass.getConstructor(getClass("world.entity.Entity"));
} else {
newEntityTeleport = EntityTeleportClass.getConstructor();
}
// Fields
EntityTeleport_EntityId = ReflectionUtils.getFields(EntityTeleportClass, int.class).get(0);
if (BukkitReflection.getMinorVersion() >= 9) {
EntityTeleport_X = ReflectionUtils.getFields(EntityTeleportClass, double.class).get(0);
EntityTeleport_Y = ReflectionUtils.getFields(EntityTeleportClass, double.class).get(1);
EntityTeleport_Z = ReflectionUtils.getFields(EntityTeleportClass, double.class).get(2);
} else {
EntityTeleport_X = ReflectionUtils.getFields(EntityTeleportClass, int.class).get(1);
EntityTeleport_Y = ReflectionUtils.getFields(EntityTeleportClass, int.class).get(2);
EntityTeleport_Z = ReflectionUtils.getFields(EntityTeleportClass, int.class).get(3);
}
// Dummy armor stand for constructor
if (BukkitReflection.getMinorVersion() >= 17) {
Class<?> world = getClass("world.level.Level", "world.level.World", "World");
Class<?> entityArmorStand = getClass("world.entity.decoration.ArmorStand",
"world.entity.decoration.EntityArmorStand", "EntityArmorStand");
Constructor<?> newEntityArmorStand = entityArmorStand.getConstructor(world, double.class, double.class, double.class);
Method World_getHandle = Class.forName("org.bukkit.craftbukkit." + BukkitReflection.getServerPackage() + ".CraftWorld").getMethod("getHandle");
dummyEntity = newEntityArmorStand.newInstance(World_getHandle.invoke(Bukkit.getWorlds().get(0)), 0, 0, 0);
}
}
private static void loadEntityMove() throws ReflectiveOperationException {
// Classes
PacketPlayOutEntity = getClass("network.protocol.game.ClientboundMoveEntityPacket",
"network.protocol.game.PacketPlayOutEntity", "PacketPlayOutEntity", "Packet30Entity");
PacketPlayOutEntityLook = getClass("network.protocol.game.ClientboundMoveEntityPacket$Rot",
"network.protocol.game.PacketPlayOutEntity$PacketPlayOutEntityLook", "PacketPlayOutEntity$PacketPlayOutEntityLook",
"PacketPlayOutEntityLook", "Packet32EntityLook");
// Field
PacketPlayOutEntity_ENTITYID = ReflectionUtils.getFields(PacketPlayOutEntity, int.class).get(0);
}
private static void loadEntitySpawn() throws ReflectiveOperationException {
SpawnEntityClass = getClass("network.protocol.game.ClientboundAddEntityPacket",
"network.protocol.game.PacketPlayOutSpawnEntity", "PacketPlayOutSpawnEntityLiving", "Packet24MobSpawn");
SpawnEntity_EntityId = ReflectionUtils.getFields(SpawnEntityClass, int.class).get(0);
if (BukkitReflection.getMinorVersion() >= 17) {
Class<?> Vec3D = getClass("world.phys.Vec3", "world.phys.Vec3D");
Vec3D_Empty = ReflectionUtils.getOnlyField(Vec3D, Vec3D).get(null);
Class<?> EntityTypes = getClass("world.entity.EntityType", "world.entity.EntityTypes");
if (BukkitReflection.getMinorVersion() >= 19) {
EntityTypes_ARMOR_STAND = ReflectionUtils.getField(EntityTypes, "ARMOR_STAND", "d").get(null);
} else {
EntityTypes_ARMOR_STAND = ReflectionUtils.getField(EntityTypes, "ARMOR_STAND", "c", "f_20529_").get(null); // Mohist 1.18.2
}
if (BukkitReflection.getMinorVersion() >= 19) {
newSpawnEntity = SpawnEntityClass.getConstructor(int.class, UUID.class, double.class, double.class, double.class, float.class, float.class, EntityTypes, int.class, Vec3D, double.class);
} else {
newSpawnEntity = SpawnEntityClass.getConstructor(int.class, UUID.class, double.class, double.class, double.class, float.class, float.class, EntityTypes, int.class, Vec3D);
}
} else {
newSpawnEntity = SpawnEntityClass.getConstructor();
if (BukkitReflection.getMinorVersion() >= 9) {
SpawnEntity_UUID = ReflectionUtils.getOnlyField(SpawnEntityClass, UUID.class);
SpawnEntity_X = ReflectionUtils.getFields(SpawnEntityClass, double.class).get(0);
SpawnEntity_Y = ReflectionUtils.getFields(SpawnEntityClass, double.class).get(1);
SpawnEntity_Z = ReflectionUtils.getFields(SpawnEntityClass, double.class).get(2);
} else {
SpawnEntity_X = ReflectionUtils.getFields(SpawnEntityClass, int.class).get(2);
SpawnEntity_Y = ReflectionUtils.getFields(SpawnEntityClass, int.class).get(3);
SpawnEntity_Z = ReflectionUtils.getFields(SpawnEntityClass, int.class).get(4);
}
SpawnEntity_EntityType = ReflectionUtils.getFields(SpawnEntityClass, int.class).get(1);
if (BukkitReflection.getMinorVersion() <= 14) {
SpawnEntity_DataWatcher = ReflectionUtils.getOnlyField(SpawnEntityClass, DataWatcher.DataWatcher);
}
}
if (BukkitReflection.getMinorVersion() >= 13) {
entityIds.put(EntityType.ARMOR_STAND, 1);
} else {
entityIds.put(EntityType.WITHER, 64);
if (BukkitReflection.getMinorVersion() >= 8) {
entityIds.put(EntityType.ARMOR_STAND, 30);
}
}
if (!BukkitReflection.is1_20_2Plus()) {
PacketPlayOutNamedEntitySpawn = getClass("network.protocol.game.ClientboundAddPlayerPacket",
"network.protocol.game.PacketPlayOutNamedEntitySpawn", "PacketPlayOutNamedEntitySpawn", "Packet20NamedEntitySpawn");
PacketPlayOutNamedEntitySpawn_ENTITYID = ReflectionUtils.getFields(PacketPlayOutNamedEntitySpawn, int.class).get(0);
}
}
private static int floor(double paramDouble) {
int i = (int)paramDouble;
return paramDouble < i ? i - 1 : i;
}
@SneakyThrows
@Override
public void spawnEntity(int entityId, @NotNull UUID id, @NotNull Object entityType, @NotNull Location l, @NotNull EntityData data) {
int minorVersion = BukkitReflection.getMinorVersion();
if (minorVersion >= 19) {
player.sendPacket(newSpawnEntity.newInstance(entityId, id, l.getX(), l.getY(), l.getZ(), 0, 0, EntityTypes_ARMOR_STAND, 0, Vec3D_Empty, 0d));
} else if (minorVersion >= 17) {
player.sendPacket(newSpawnEntity.newInstance(entityId, id, l.getX(), l.getY(), l.getZ(), 0, 0, EntityTypes_ARMOR_STAND, 0, Vec3D_Empty));
} else {
Object nmsPacket = newSpawnEntity.newInstance();
SpawnEntity_EntityId.set(nmsPacket, entityId);
if (minorVersion <= 14) {
SpawnEntity_DataWatcher.set(nmsPacket, data.build());
}
if (minorVersion >= 9) {
SpawnEntity_UUID.set(nmsPacket, id);
SpawnEntity_X.set(nmsPacket, l.getX());
SpawnEntity_Y.set(nmsPacket, l.getY());
SpawnEntity_Z.set(nmsPacket, l.getZ());
} else {
SpawnEntity_X.set(nmsPacket, floor(l.getX()*32));
SpawnEntity_Y.set(nmsPacket, floor(l.getY()*32));
SpawnEntity_Z.set(nmsPacket, floor(l.getZ()*32));
}
SpawnEntity_EntityType.set(nmsPacket, entityIds.get((EntityType) entityType));
player.sendPacket(nmsPacket);
}
if (BukkitReflection.getMinorVersion() >= 15) {
updateEntityMetadata(entityId, data);
}
}
@SneakyThrows
@Override
public void updateEntityMetadata(int entityId, @NotNull EntityData data) {
if (newEntityMetadata.getParameterCount() == 2) {
//1.19.3+
player.sendPacket(newEntityMetadata.newInstance(entityId, DataWatcher.DataWatcher_packDirty.invoke(data.build())));
} else {
player.sendPacket(newEntityMetadata.newInstance(entityId, data.build(), true));
}
}
@SneakyThrows
@Override
public void teleportEntity(int entityId, @NotNull Location location) {
Object nmsPacket;
if (BukkitReflection.getMinorVersion() >= 17) {
nmsPacket = newEntityTeleport.newInstance(dummyEntity);
} else {
nmsPacket = newEntityTeleport.newInstance();
}
EntityTeleport_EntityId.set(nmsPacket, entityId);
if (BukkitReflection.getMinorVersion() >= 9) {
EntityTeleport_X.set(nmsPacket, location.getX());
EntityTeleport_Y.set(nmsPacket, location.getY());
EntityTeleport_Z.set(nmsPacket, location.getZ());
} else {
EntityTeleport_X.set(nmsPacket, floor(location.getX()*32));
EntityTeleport_Y.set(nmsPacket, floor(location.getY()*32));
EntityTeleport_Z.set(nmsPacket, floor(location.getZ()*32));
}
player.sendPacket(nmsPacket);
}
@SneakyThrows
@Override
public void destroyEntities(int... entities) {
if (newEntityDestroy.getParameterTypes()[0] != int.class) {
player.sendPacket(newEntityDestroy.newInstance(new Object[]{entities}));
} else {
//1.17.0 Mojank
for (int entity : entities) {
player.sendPacket(newEntityDestroy.newInstance(entity));
}
}
}
@Override
public boolean isDestroyPacket(@NotNull Object packet) {
return EntityDestroyClass.isInstance(packet);
}
@Override
public boolean isTeleportPacket(@NotNull Object packet) {
return EntityTeleportClass.isInstance(packet);
}
@Override
public boolean isNamedEntitySpawnPacket(@NotNull Object packet) {
if (BukkitReflection.is1_20_2Plus()) {
return SpawnEntityClass.isInstance(packet);
} else {
return PacketPlayOutNamedEntitySpawn.isInstance(packet);
}
}
@Override
public boolean isMovePacket(@NotNull Object packet) {
return PacketPlayOutEntity.isInstance(packet);
}
@Override
public boolean isLookPacket(@NotNull Object packet) {
return PacketPlayOutEntityLook.isInstance(packet);
}
@Override
@SneakyThrows
public int getTeleportEntityId(@NotNull Object teleportPacket) {
return EntityTeleport_EntityId.getInt(teleportPacket);
}
@Override
@SneakyThrows
public int getMoveEntityId(@NotNull Object movePacket) {
return PacketPlayOutEntity_ENTITYID.getInt(movePacket);
}
@Override
@SneakyThrows
public int getSpawnedPlayer(@NotNull Object playerSpawnPacket) {
if (BukkitReflection.is1_20_2Plus()) {
return SpawnEntity_EntityId.getInt(playerSpawnPacket);
} else {
return PacketPlayOutNamedEntitySpawn_ENTITYID.getInt(playerSpawnPacket);
}
}
@Override
@SneakyThrows
public int[] getDestroyedEntities(@NotNull Object destroyPacket) {
Object entities = PacketEntityView.EntityDestroy_Entities.get(destroyPacket);
if (BukkitReflection.getMinorVersion() >= 17) {
if (entities instanceof List) {
return ((List<Integer>)entities).stream().mapToInt(i -> i).toArray();
} else {
//1.17.0
return new int[]{(int) entities};
}
} else {
return (int[]) entities;
}
}
@Override
public boolean isBundlePacket(@NotNull Object packet) {
return BukkitReflection.is1_19_4Plus() && ClientboundBundlePacket.isInstance(packet);
}
@Override
@SneakyThrows
public Iterable<Object> getPackets(@NotNull Object bundlePacket) {
return (Iterable<Object>) ClientboundBundlePacket_packets.get(bundlePacket);
}
/**
* Returns class from given potential names. Supports modded servers, such as Thermos.
*
* @param names
* Potential class names
* @return class from given name
* @throws ClassNotFoundException
* if class was not found
*/
public static Class<?> getClass(@NotNull String... names) throws ClassNotFoundException {
for (String name : names) {
try {
if (BukkitReflection.getMinorVersion() >= 17) {
return PacketEntityView.class.getClassLoader().loadClass("net.minecraft." + name);
} else {
return PacketEntityView.class.getClassLoader().loadClass("net.minecraft.server." + BukkitReflection.getServerPackage() + "." + name);
}
} catch (ClassNotFoundException | NullPointerException e) {
// Wrong class name
}
}
throw new ClassNotFoundException("No class found with potential names " + Arrays.toString(names));
}
}
@@ -0,0 +1,47 @@
package me.neznamy.tab.platforms.bukkit.nms;
import lombok.Getter;
import lombok.SneakyThrows;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class PacketSender {
private static Method getHandle;
private static Field PLAYER_CONNECTION;
private static Method sendPacket;
@Getter private static boolean available;
public static void tryLoad() {
try {
Class<?> Packet = BukkitReflection.getClass("network.protocol.Packet", "Packet");
Class<?> EntityPlayer = BukkitReflection.getClass("server.level.ServerPlayer", "server.level.EntityPlayer", "EntityPlayer");
Class<?> PlayerConnection = BukkitReflection.getClass("server.network.ServerGamePacketListenerImpl",
"server.network.PlayerConnection", "PlayerConnection");
getHandle = Class.forName("org.bukkit.craftbukkit." + BukkitReflection.getServerPackage() + ".entity.CraftPlayer").getMethod("getHandle");
PLAYER_CONNECTION = ReflectionUtils.getOnlyField(EntityPlayer, PlayerConnection);
if (BukkitReflection.getMinorVersion() >= 7) {
sendPacket = ReflectionUtils.getMethods(PlayerConnection, void.class, Packet).get(0);
} else {
sendPacket = ReflectionUtils.getMethod(PlayerConnection, new String[]{"sendPacket"}, Packet);
}
available = true;
} catch (Exception e) {
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.RED.getFormat() + "[TAB] Failed to initialize NMS fields for " +
"sending packets due to a compatibility error. Majority of the features will not work / be very limited. " +
"Please update the plugin to a version with proper support for your server version.");
}
}
@SneakyThrows
public static void sendPacket(@NotNull Player player, @NotNull Object packet) {
if (!player.isOnline() || sendPacket == null) return;
sendPacket.invoke(PLAYER_CONNECTION.get(getHandle.invoke(player)), packet);
}
}
@@ -0,0 +1,41 @@
package me.neznamy.tab.platforms.bukkit.nms;
import lombok.SneakyThrows;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class PingRetriever {
private static Method getHandle;
private static Field PING;
public static void tryLoad() {
try {
if (BukkitReflection.getMinorVersion() < 17) {
getHandle = Class.forName("org.bukkit.craftbukkit." + BukkitReflection.getServerPackage() + ".entity.CraftPlayer").getMethod("getHandle");
Class<?> EntityPlayer = BukkitReflection.getClass("server.level.ServerPlayer", "server.level.EntityPlayer", "EntityPlayer");
PING = ReflectionUtils.getField(EntityPlayer, "ping", "field_71138_i"); // 1.5.2 - 1.16.5, 1.7.10 Thermos
}
} catch (Exception e) {
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.RED.getFormat() + "[TAB] Failed to initialize NMS fields for " +
"getting player's ping due to a compatibility error. This will " +
"result in ping showing \"-1\". " +
"Please update the plugin a to version with native support for your server version to properly show ping.");
}
}
@SneakyThrows
public static int getPing(@NotNull Player player) {
if (BukkitReflection.getMinorVersion() >= 17) {
return player.getPing();
}
if (PING == null) return -1;
return PING.getInt(getHandle.invoke(player));
}
}
@@ -1,89 +0,0 @@
package me.neznamy.tab.platforms.bukkit.nms.datawatcher;
import lombok.Getter;
import lombok.SneakyThrows;
import lombok.ToString;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import me.neznamy.tab.shared.backend.EntityData;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
/**
* A class representing the n.m.s.DataWatcher class to make work with it much easier
*/
@ToString
public class DataWatcher implements EntityData {
/** NMS Fields */
public static Class<?> CLASS;
private static Constructor<?> CONSTRUCTOR;
private static Method REGISTER;
public static Method markDirty;
public static Method packDirty;
/** Watched data */
private final Map<Integer, DataWatcherItem> dataValues = new HashMap<>();
/** Helper for easier data write */
@Getter private final DataWatcherHelper helper = new DataWatcherHelper(this);
/**
* Loads all required Fields and throws Exception if something went wrong
*
* @param nms
* NMS storage reference
*/
public static void load(NMSStorage nms) throws NoSuchMethodException {
CONSTRUCTOR = ReflectionUtils.getOnlyConstructor(CLASS);
if (nms.getMinorVersion() >= 9) {
REGISTER = ReflectionUtils.getMethod(CLASS, new String[]{"register", "a"}, DataWatcherObject.CLASS, Object.class); // {Bukkit, Bukkit 1.18+}
} else {
REGISTER = ReflectionUtils.getMethod(CLASS, new String[]{"func_75682_a", "a"}, int.class, Object.class); // {Thermos 1.7.10, Bukkit}
}
if (nms.getMinorVersion() >= 19) {
packDirty = CLASS.getMethod("b");
}
if (nms.is1_19_3Plus()) {
markDirty = ReflectionUtils.getOnlyMethod(CLASS, void.class, DataWatcherObject.CLASS);
}
}
/**
* Sets value into data values
*
* @param type
* type of value
* @param value
* value
*/
public void setValue(@NotNull DataWatcherObject type, @NotNull Object value) {
dataValues.put(type.getPosition(), new DataWatcherItem(type, value));
}
/**
* Converts the class into an instance of NMS.DataWatcher
*
* @return an instance of NMS.DataWatcher with same data
*/
@SneakyThrows
public @NotNull Object build() {
NMSStorage nms = NMSStorage.getInstance();
Object nmsWatcher;
if (CONSTRUCTOR.getParameterCount() == 1) { //1.7+
nmsWatcher = CONSTRUCTOR.newInstance(new Object[] {null});
} else {
nmsWatcher = CONSTRUCTOR.newInstance();
}
for (DataWatcherItem item : dataValues.values()) {
Object nmsObject = item.getType().build();
REGISTER.invoke(nmsWatcher, nmsObject, item.getValue());
if (nms.is1_19_3Plus()) markDirty.invoke(nmsWatcher, nmsObject);
}
return nmsWatcher;
}
}
@@ -1,160 +0,0 @@
package me.neznamy.tab.platforms.bukkit.nms.datawatcher;
import lombok.AllArgsConstructor;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import me.neznamy.tab.shared.TAB;
import org.jetbrains.annotations.NotNull;
import java.util.Optional;
/**
* A class to help to assign DataWatcher items as positions often change per-version
*/
@AllArgsConstructor
public class DataWatcherHelper {
/** NMS Fields */
public static Class<?> DataWatcherRegistry;
public static Class<?> DataWatcherSerializer;
public static Object DataWatcherSerializer_BYTE;
public static Object DataWatcherSerializer_FLOAT;
public static Object DataWatcherSerializer_STRING;
public static Object DataWatcherSerializer_OPTIONAL_COMPONENT;
public static Object DataWatcherSerializer_BOOLEAN;
/** Instance fields */
private final int armorStandFlagsPosition = getArmorStandFlagsPosition();
@NotNull private final DataWatcher data;
/**
* Returns armor stand flags position based on server version
*
* @return armor stand flags position based on server version
*/
private int getArmorStandFlagsPosition() {
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 17) {
//1.17.x, 1.18.x, 1.19.x
return 15;
} else if (TAB.getInstance().getServerVersion().getMinorVersion() >= 15) {
//1.15.x, 1.16.x
return 14;
} else if (TAB.getInstance().getServerVersion().getMinorVersion() >= 14) {
//1.14.x
return 13;
} else if (TAB.getInstance().getServerVersion().getMinorVersion() >= 10) {
//1.10.x - 1.13.x
return 11;
} else {
//1.8.x - 1.9.x
return 10;
}
}
public static void load(NMSStorage nms) throws NoSuchFieldException, IllegalAccessException {
if (nms.getMinorVersion() < 9) return;
DataWatcherSerializer_BYTE = DataWatcherRegistry.getDeclaredField("a").get(null);
DataWatcherSerializer_FLOAT = DataWatcherRegistry.getDeclaredField("c").get(null);
DataWatcherSerializer_STRING = DataWatcherRegistry.getDeclaredField("d").get(null);
if (nms.is1_19_3Plus()) {
DataWatcherSerializer_OPTIONAL_COMPONENT = DataWatcherRegistry.getDeclaredField("g").get(null);
if (nms.is1_19_4Plus()) {
DataWatcherSerializer_BOOLEAN = DataWatcherRegistry.getDeclaredField("k").get(null);
} else {
DataWatcherSerializer_BOOLEAN = DataWatcherRegistry.getDeclaredField("j").get(null);
}
} else {
if (nms.getMinorVersion() >= 13) {
DataWatcherSerializer_OPTIONAL_COMPONENT = DataWatcherRegistry.getDeclaredField("f").get(null);
DataWatcherSerializer_BOOLEAN = DataWatcherRegistry.getDeclaredField("i").get(null);
} else {
DataWatcherSerializer_BOOLEAN = DataWatcherRegistry.getDeclaredField("h").get(null);
}
}
}
/**
* Writes entity byte flags
*
* @param flags
* flags to write
*/
public void setEntityFlags(byte flags) {
data.setValue(new DataWatcherObject(0, DataWatcherSerializer_BYTE), flags);
}
/**
* Writes entity custom name with position based on server version and value depending on client version (RGB or not)
*
* @param customName
* target custom name
* @param clientVersion
* client version
*/
public void setCustomName(@NotNull String customName, @NotNull ProtocolVersion clientVersion) {
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 13) {
data.setValue(new DataWatcherObject(2, DataWatcherSerializer_OPTIONAL_COMPONENT),
Optional.ofNullable(NMSStorage.getInstance().toNMSComponent(IChatBaseComponent.optimizedComponent(customName), clientVersion)));
} else if (TAB.getInstance().getServerVersion().getMinorVersion() >= 8) {
data.setValue(new DataWatcherObject(2, DataWatcherSerializer_STRING), customName);
} else {
//name length is limited to 64 characters on <1.8
String cutName = (customName.length() > 64 ? customName.substring(0, 64) : customName);
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 6) {
data.setValue(new DataWatcherObject(10, null), cutName);
} else {
data.setValue(new DataWatcherObject(5, null), cutName);
}
}
}
/**
* Writes custom name visibility boolean
*
* @param visible
* if visible or not
*/
public void setCustomNameVisible(boolean visible) {
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 9) {
data.setValue(new DataWatcherObject(3, DataWatcherSerializer_BOOLEAN), visible);
} else {
data.setValue(new DataWatcherObject(3, null), (byte)(visible?1:0));
}
}
/**
* Writes entity health
*
* @param health
* health of entity
*/
public void setHealth(float health) {
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 6) {
data.setValue(new DataWatcherObject(6, DataWatcherSerializer_FLOAT), health);
} else {
data.setValue(new DataWatcherObject(16, null), (int)health);
}
}
/**
* Writes armor stand flags
*
* @param flags
* flags to write
*/
public void setArmorStandFlags(byte flags) {
data.setValue(new DataWatcherObject(armorStandFlagsPosition, DataWatcherSerializer_BYTE), flags);
}
/**
* Writes wither invulnerable time
* @param time
* Time, apparently
*/
public void setWitherInvulnerableTime(int time) {
if (TAB.getInstance().getServerVersion().getMinorVersion() > 8)
throw new UnsupportedOperationException("Not supported on 1.9+");
data.setValue(new DataWatcherObject(20, null), time);
}
}
@@ -1,39 +0,0 @@
package me.neznamy.tab.platforms.bukkit.nms.datawatcher;
import lombok.Data;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
/**
* Class representing NMS Data Watcher Item
*/
@Data
public class DataWatcherItem {
/** NMS Fields */
public static Class<?> CLASS;
public static Field TYPE;
public static Field VALUE;
/** Instance fields */
@NotNull private final DataWatcherObject type;
@NotNull private final Object value;
/**
* Loads all required Fields
*
* @param nms
* NMS storage reference
*/
public static void load(NMSStorage nms) {
VALUE = ReflectionUtils.getFields(CLASS, Object.class).get(0);
if (nms.getMinorVersion() >= 9) {
TYPE = ReflectionUtils.getOnlyField(CLASS, DataWatcherObject.CLASS);
} else {
TYPE = ReflectionUtils.getFields(CLASS, int.class).get(1);
}
}
}
@@ -1,54 +0,0 @@
package me.neznamy.tab.platforms.bukkit.nms.datawatcher;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
/**
* A class representing the n.m.s.DataWatcherObject class to make work with it much easier
*/
@Data @AllArgsConstructor
public class DataWatcherObject {
/** NMS Fields */
public static Class<?> CLASS;
public static Constructor<?> CONSTRUCTOR;
public static Field SLOT;
public static Field SERIALIZER;
/** Instance fields */
private final int position;
@Nullable private final Object serializer;
/**
* Loads all required Fields and throws Exception if something went wrong
*
* @throws NoSuchMethodException
* If something fails
*/
public static void load() throws NoSuchMethodException {
CONSTRUCTOR = CLASS.getConstructor(int.class, DataWatcherHelper.DataWatcherSerializer);
SLOT = ReflectionUtils.getOnlyField(CLASS, int.class);
SERIALIZER = ReflectionUtils.getOnlyField(CLASS, DataWatcherHelper.DataWatcherSerializer);
}
/**
* Converts the object into NMS object
*
* @return NMS object
*/
@SneakyThrows
public Object build() {
if (NMSStorage.getInstance().getMinorVersion() >= 9) {
return DataWatcherObject.CONSTRUCTOR.newInstance(position, serializer);
} else {
return position;
}
}
}
@@ -1,109 +0,0 @@
package me.neznamy.tab.platforms.bukkit.nms.storage.nms;
import me.neznamy.tab.platforms.bukkit.BukkitTabList;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherHelper;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherItem;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherObject;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.*;
import me.neznamy.tab.platforms.bukkit.scoreboard.PacketScoreboard;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
/**
* NMS loader for Minecraft 1.5.2 - 1.16.5 using Bukkit mapping.
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public class BukkitLegacyNMSStorage extends NMSStorage {
/**
* Returns class with given potential names in same order
*
* @param names
* possible class names
* @return class for specified names
* @throws ClassNotFoundException
* if class does not exist
*/
private Class<?> getLegacyClass(@NotNull String... names) throws ClassNotFoundException {
for (String name : names) {
try {
return getLegacyClass(name);
} catch (ClassNotFoundException e) {
//not the first class name in array
}
}
throw new ClassNotFoundException("No class found with possible names " + Arrays.toString(names));
}
/**
* Returns class from given name. Supports modded servers, such as Thermos.
*
* @param name
* class name
* @return class from given name
* @throws ClassNotFoundException
* if class was not found
*/
public Class<?> getLegacyClass(@NotNull String name) throws ClassNotFoundException {
try {
return getClass().getClassLoader().loadClass("net.minecraft.server." + serverPackage + "." + name);
} catch (NullPointerException e) {
// nested class not found
throw new ClassNotFoundException(name);
}
}
@Override
public void loadClasses() throws ClassNotFoundException {
EntityHuman = getLegacyClass("EntityHuman");
World = getLegacyClass("World");
Packet = getLegacyClass("Packet");
EnumChatFormat = (Class<Enum>) getLegacyClass("EnumChatFormat");
EntityPlayer = getLegacyClass("EntityPlayer");
Entity = getLegacyClass("Entity");
EntityLiving = getLegacyClass("EntityLiving");
PlayerConnection = getLegacyClass("PlayerConnection");
NetworkManager = getLegacyClass("NetworkManager");
DataWatcher.CLASS = getLegacyClass("DataWatcher");
DataWatcherItem.CLASS = getLegacyClass("DataWatcher$Item", "DataWatcher$WatchableObject", "WatchableObject");
PacketPlayOutSpawnEntityLivingStorage.CLASS = getLegacyClass("PacketPlayOutSpawnEntityLiving", "Packet24MobSpawn");
PacketPlayOutEntityTeleportStorage.CLASS = getLegacyClass("PacketPlayOutEntityTeleport", "Packet34EntityTeleport");
PacketPlayOutEntity = getLegacyClass("PacketPlayOutEntity", "Packet30Entity");
PacketPlayOutEntityDestroyStorage.CLASS = getLegacyClass("PacketPlayOutEntityDestroy", "Packet29DestroyEntity");
PacketPlayOutEntityLook = getLegacyClass("PacketPlayOutEntity$PacketPlayOutEntityLook", "PacketPlayOutEntityLook", "Packet32EntityLook");
PacketPlayOutEntityMetadataStorage.CLASS = getLegacyClass("PacketPlayOutEntityMetadata", "Packet40EntityMetadata");
PacketPlayOutNamedEntitySpawn = getLegacyClass("PacketPlayOutNamedEntitySpawn", "Packet20NamedEntitySpawn");
PacketScoreboard.DisplayObjectiveClass = getLegacyClass("PacketPlayOutScoreboardDisplayObjective", "Packet208SetScoreboardDisplayObjective");
PacketScoreboard.ObjectivePacketClass = getLegacyClass("PacketPlayOutScoreboardObjective", "Packet206SetScoreboardObjective");
PacketScoreboard.TeamPacketClass = getLegacyClass("PacketPlayOutScoreboardTeam", "Packet209SetScoreboardTeam");
PacketScoreboard.ScorePacketClass = getLegacyClass("PacketPlayOutScoreboardScore", "Packet207SetScoreboardScore");
PacketScoreboard.Scoreboard = getLegacyClass("Scoreboard");
PacketScoreboard.ScoreboardObjective = getLegacyClass("ScoreboardObjective");
PacketScoreboard.ScoreboardScoreClass = getLegacyClass("ScoreboardScore");
PacketScoreboard.IScoreboardCriteria = getLegacyClass("IScoreboardCriteria", "IObjective"); // 1.5.1+, 1.5
PacketScoreboard.ScoreboardTeam = getLegacyClass("ScoreboardTeam");
if (minorVersion >= 7) {
IChatBaseComponent = getLegacyClass("IChatBaseComponent");
ChatSerializer = getLegacyClass("IChatBaseComponent$ChatSerializer", "ChatSerializer");
}
if (minorVersion >= 8) {
BukkitTabList.PacketPlayOutPlayerListHeaderFooterClass = getLegacyClass("PacketPlayOutPlayerListHeaderFooter");
EntityArmorStand = getLegacyClass("EntityArmorStand");
BukkitTabList.PacketPlayOutPlayerInfoClass = getLegacyClass("PacketPlayOutPlayerInfo");
BukkitTabList.EnumPlayerInfoActionClass = (Class<Enum>) getLegacyClass("PacketPlayOutPlayerInfo$EnumPlayerInfoAction", "EnumPlayerInfoAction");
BukkitTabList.PlayerInfoDataClass = getLegacyClass("PacketPlayOutPlayerInfo$PlayerInfoData", "PlayerInfoData");
BukkitTabList.EnumGamemodeClass = (Class<Enum>) getLegacyClass("EnumGamemode", "WorldSettings$EnumGamemode");
PacketScoreboard.EnumScoreboardHealthDisplay = (Class<Enum>) getLegacyClass("IScoreboardCriteria$EnumScoreboardHealthDisplay", "EnumScoreboardHealthDisplay");
PacketScoreboard.EnumScoreboardAction = (Class<Enum>) getLegacyClass("ScoreboardServer$Action", "PacketPlayOutScoreboardScore$EnumScoreboardAction", "EnumScoreboardAction");
PacketScoreboard.EnumNameTagVisibility = (Class<Enum>) getLegacyClass("ScoreboardTeamBase$EnumNameTagVisibility", "EnumNameTagVisibility");
}
if (minorVersion >= 9) {
DataWatcherObject.CLASS = getLegacyClass("DataWatcherObject");
DataWatcherHelper.DataWatcherRegistry = getLegacyClass("DataWatcherRegistry");
DataWatcherHelper.DataWatcherSerializer = getLegacyClass("DataWatcherSerializer");
PacketScoreboard.EnumTeamPush = (Class<Enum>) getLegacyClass("ScoreboardTeamBase$EnumTeamPush");
}
}
}
@@ -1,72 +0,0 @@
package me.neznamy.tab.platforms.bukkit.nms.storage.nms;
import me.neznamy.tab.platforms.bukkit.BukkitTabList;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherHelper;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherItem;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherObject;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.*;
import me.neznamy.tab.platforms.bukkit.scoreboard.PacketScoreboard;
/**
* NMS loader for minecraft 1.17+ using Mojang packaging and bukkit names.
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public class BukkitModernNMSStorage extends NMSStorage {
@Override
public void loadClasses() throws ClassNotFoundException {
ChatSerializer = Class.forName("net.minecraft.network.chat.IChatBaseComponent$ChatSerializer");
World = Class.forName("net.minecraft.world.level.World");
EntityArmorStand = Class.forName("net.minecraft.world.entity.decoration.EntityArmorStand");
EntityHuman = Class.forName("net.minecraft.world.entity.player.EntityHuman");
NetworkManager = Class.forName("net.minecraft.network.NetworkManager");
IChatBaseComponent = Class.forName("net.minecraft.network.chat.IChatBaseComponent");
Packet = Class.forName("net.minecraft.network.protocol.Packet");
EnumChatFormat = (Class<Enum>) Class.forName("net.minecraft.EnumChatFormat");
EntityPlayer = Class.forName("net.minecraft.server.level.EntityPlayer");
Entity = Class.forName("net.minecraft.world.entity.Entity");
EntityLiving = Class.forName("net.minecraft.world.entity.EntityLiving");
PlayerConnection = Class.forName("net.minecraft.server.network.PlayerConnection");
BukkitTabList.PacketPlayOutPlayerListHeaderFooterClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutPlayerListHeaderFooter");
PacketScoreboard.DisplayObjectiveClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutScoreboardDisplayObjective");
PacketScoreboard.ObjectivePacketClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutScoreboardObjective");
PacketScoreboard.Scoreboard = Class.forName("net.minecraft.world.scores.Scoreboard");
PacketScoreboard.ScorePacketClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutScoreboardScore");
PacketScoreboard.ScoreboardObjective = Class.forName("net.minecraft.world.scores.ScoreboardObjective");
PacketScoreboard.ScoreboardScoreClass = Class.forName("net.minecraft.world.scores.ScoreboardScore");
PacketScoreboard.IScoreboardCriteria = Class.forName("net.minecraft.world.scores.criteria.IScoreboardCriteria");
PacketScoreboard.EnumScoreboardHealthDisplay = (Class<Enum>) Class.forName("net.minecraft.world.scores.criteria.IScoreboardCriteria$EnumScoreboardHealthDisplay");
PacketScoreboard.EnumScoreboardAction = (Class<Enum>) Class.forName("net.minecraft.server.ScoreboardServer$Action");
PacketScoreboard.TeamPacketClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutScoreboardTeam");
PacketScoreboard.ScoreboardTeam = Class.forName("net.minecraft.world.scores.ScoreboardTeam");
PacketScoreboard.EnumNameTagVisibility = (Class<Enum>) Class.forName("net.minecraft.world.scores.ScoreboardTeamBase$EnumNameTagVisibility");
PacketScoreboard.EnumTeamPush = (Class<Enum>) Class.forName("net.minecraft.world.scores.ScoreboardTeamBase$EnumTeamPush");
DataWatcher.CLASS = Class.forName("net.minecraft.network.syncher.DataWatcher");
DataWatcherItem.CLASS = Class.forName("net.minecraft.network.syncher.DataWatcher$Item");
DataWatcherObject.CLASS = Class.forName("net.minecraft.network.syncher.DataWatcherObject");
DataWatcherHelper.DataWatcherRegistry = Class.forName("net.minecraft.network.syncher.DataWatcherRegistry");
DataWatcherHelper.DataWatcherSerializer = Class.forName("net.minecraft.network.syncher.DataWatcherSerializer");
PacketPlayOutEntityTeleportStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutEntityTeleport");
PacketPlayOutEntity = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutEntity");
PacketPlayOutEntityDestroyStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutEntityDestroy");
PacketPlayOutEntityLook = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutEntity$PacketPlayOutEntityLook");
PacketPlayOutEntityMetadataStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutEntityMetadata");
PacketPlayOutNamedEntitySpawn = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutNamedEntitySpawn");
BukkitTabList.EnumGamemodeClass = (Class<Enum>) Class.forName("net.minecraft.world.level.EnumGamemode");
PacketPlayOutSpawnEntityLivingStorage.Vec3D = Class.forName("net.minecraft.world.phys.Vec3D");
PacketPlayOutSpawnEntityLivingStorage.EntityTypes = Class.forName("net.minecraft.world.entity.EntityTypes");
PacketPlayOutSpawnEntityLivingStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutSpawnEntity");
if (is1_19_3Plus()) {
BukkitTabList.ClientboundPlayerInfoRemovePacket = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket");
BukkitTabList.PacketPlayOutPlayerInfoClass = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket");
BukkitTabList.EnumPlayerInfoActionClass = (Class<Enum>) Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket$a");
BukkitTabList.PlayerInfoDataClass = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket$b");
BukkitTabList.RemoteChatSession$Data = Class.forName("net.minecraft.network.chat.RemoteChatSession$a");
} else {
BukkitTabList.PacketPlayOutPlayerInfoClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutPlayerInfo");
BukkitTabList.EnumPlayerInfoActionClass = (Class<Enum>) Class.forName("net.minecraft.network.protocol.game.PacketPlayOutPlayerInfo$EnumPlayerInfoAction");
BukkitTabList.PlayerInfoDataClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutPlayerInfo$PlayerInfoData");
}
}
}
@@ -1,159 +0,0 @@
package me.neznamy.tab.platforms.bukkit.nms.storage.nms;
import com.mojang.authlib.GameProfile;
import io.netty.channel.Channel;
import lombok.Getter;
import lombok.Setter;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.BukkitTabList;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherHelper;
import me.neznamy.tab.platforms.bukkit.scoreboard.PacketScoreboard;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.util.ComponentCache;
import me.neznamy.tab.shared.util.ReflectionUtils;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherItem;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherObject;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.*;
import org.bukkit.Bukkit;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* Class holding all NMS classes, methods, fields and constructors used by TAB.
*/
@SuppressWarnings("rawtypes")
public abstract class NMSStorage {
/** Instance of this class */
@Getter @Setter private static NMSStorage instance;
/** Server's NMS/CraftBukkit package */
@Getter protected final String serverPackage = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3];
/** Server's minor version */
@Getter protected final int minorVersion = Integer.parseInt(serverPackage.split("_")[1]);
/** Flag determining whether the server version is at least 1.19.3 or not */
@Getter private final boolean is1_19_3Plus = ReflectionUtils.classExists("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket");
@Getter private final boolean is1_19_4Plus = is1_19_3Plus && !serverPackage.equals("v1_19_R2");
/** Basic universal values */
protected Class<?> Packet;
protected Class<?> NetworkManager;
protected Class<?> EntityArmorStand;
protected Class<?> World;
public Class<Enum> EnumChatFormat;
public Class<?> EntityPlayer;
protected Class<?> EntityHuman;
public Class<?> Entity;
public Class<?> EntityLiving;
protected Class<?> PlayerConnection;
// Ping field for 1.5.2 - 1.16.5, 1.17+ has Player#getPing()
public Field PING;
public Field PLAYER_CONNECTION;
public Field NETWORK_MANAGER;
public Field CHANNEL;
public Method getHandle;
public Method sendPacket;
public Method getProfile;
/** Chat components */
public Class<?> IChatBaseComponent;
protected Class<?> ChatSerializer;
public Method ChatSerializer_DESERIALIZE;
/** Other entity packets */
public Class<?> PacketPlayOutEntity;
public Field PacketPlayOutEntity_ENTITYID;
public Class<?> PacketPlayOutEntityLook;
public Class<?> PacketPlayOutNamedEntitySpawn;
public Field PacketPlayOutNamedEntitySpawn_ENTITYID;
public Object dummyEntity;
private final ComponentCache<IChatBaseComponent, Object> componentCache = new ComponentCache<>(10000,
(component, clientVersion) -> ChatSerializer_DESERIALIZE.invoke(null, component.toString(clientVersion)));
/**
* Constructs new instance and attempts to load all fields, methods and constructors.
*/
@SneakyThrows
public NMSStorage() {
ProtocolVersion.UNKNOWN_SERVER_VERSION.setMinorVersion(minorVersion); //fixing compatibility with forks that set version field value to "Unknown"
loadClasses();
if (minorVersion >= 7) {
NETWORK_MANAGER = ReflectionUtils.getOnlyField(PlayerConnection, NetworkManager);
sendPacket = ReflectionUtils.getMethods(PlayerConnection, void.class, Packet).get(0);
} else {
sendPacket = ReflectionUtils.getMethod(PlayerConnection, new String[]{"sendPacket"}, Packet);
}
if (minorVersion >= 8) {
ChatSerializer_DESERIALIZE = ChatSerializer.getMethod("a", String.class);
CHANNEL = ReflectionUtils.getOnlyField(NetworkManager, Channel.class);
try {
getProfile = ReflectionUtils.getOnlyMethod(EntityHuman, GameProfile.class);
} catch (IllegalStateException catServer) {
getProfile = EntityHuman.getMethod("getProfile");
}
Constructor<?> newEntityArmorStand = EntityArmorStand.getConstructor(World, double.class, double.class, double.class);
Method World_getHandle = Class.forName("org.bukkit.craftbukkit." + serverPackage + ".CraftWorld").getMethod("getHandle");
dummyEntity = newEntityArmorStand.newInstance(World_getHandle.invoke(Bukkit.getWorlds().get(0)), 0, 0, 0);
BukkitTabList.load(this);
}
if (minorVersion >= 9) {
DataWatcherObject.load();
}
PLAYER_CONNECTION = ReflectionUtils.getOnlyField(EntityPlayer, PlayerConnection);
getHandle = Class.forName("org.bukkit.craftbukkit." + serverPackage + ".entity.CraftPlayer").getMethod("getHandle");
DataWatcher.load(this);
DataWatcherItem.load(this);
DataWatcherHelper.load(this);
PacketPlayOutEntityDestroyStorage.load();
PacketPlayOutEntityMetadataStorage.load(this);
PacketPlayOutEntityTeleportStorage.load(this);
PacketPlayOutSpawnEntityLivingStorage.load(this);
PacketScoreboard.load(this);
PacketPlayOutEntity_ENTITYID = ReflectionUtils.getFields(PacketPlayOutEntity, int.class).get(0);
PacketPlayOutNamedEntitySpawn_ENTITYID = ReflectionUtils.getFields(PacketPlayOutNamedEntitySpawn, int.class).get(0);
if (minorVersion < 17) {
try {
(PING = EntityPlayer.getDeclaredField("ping")).setAccessible(true); // 1.5.2 - 1.16.5
} catch (NoSuchFieldException e) {
(PING = EntityPlayer.getDeclaredField("field_71138_i")).setAccessible(true); // 1.7.10 Thermos
}
}
}
/**
* Loads all classes used by the plugin
*
* @throws ClassNotFoundException
* If a class was not found
*/
public abstract void loadClasses() throws ClassNotFoundException;
/**
* Converts TAB's IChatBaseComponent into minecraft's component using String deserialization.
* If the requested component is found in cache, it is returned. If not, it is created, added into cache and returned.
* If {@code component} is {@code null}, returns {@code null}
*
* @param component
* component to convert
* @param clientVersion
* client version used to decide RGB conversion
* @return converted component or {@code null} if {@code component} is {@code null}
*/
public @Nullable Object toNMSComponent(@NotNull IChatBaseComponent component, @NotNull ProtocolVersion clientVersion) {
return componentCache.get(component, clientVersion);
}
}
@@ -1,33 +0,0 @@
package me.neznamy.tab.platforms.bukkit.nms.storage.packet;
import me.neznamy.tab.shared.util.ReflectionUtils;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
/**
* Custom class for holding data used in PacketPlayOutEntityDestroy minecraft packet.
*/
public class PacketPlayOutEntityDestroyStorage {
/** NMS Fields */
public static Class<?> CLASS;
public static Constructor<?> CONSTRUCTOR;
public static Field ENTITIES;
/**
* Loads all required Fields and throws Exception if something went wrong
*
* @throws NoSuchMethodException
* If something fails
*/
public static void load() throws NoSuchMethodException {
ENTITIES = ReflectionUtils.getOnlyField(CLASS);
try {
CONSTRUCTOR = CLASS.getConstructor(int[].class);
} catch (NoSuchMethodException e) {
//1.17.0
CONSTRUCTOR = CLASS.getConstructor(int.class);
}
}
}
@@ -1,33 +0,0 @@
package me.neznamy.tab.platforms.bukkit.nms.storage.packet;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import java.lang.reflect.Constructor;
import java.util.List;
/**
* Custom class for holding data used in PacketPlayOutEntityMetadata minecraft packet.
*/
public class PacketPlayOutEntityMetadataStorage {
/** NMS Fields */
public static Class<?> CLASS;
public static Constructor<?> CONSTRUCTOR;
/**
* Loads all required Fields and throws Exception if something went wrong
*
* @param nms
* NMS storage reference
* @throws NoSuchMethodException
* If something fails
*/
public static void load(NMSStorage nms) throws NoSuchMethodException {
if (nms.is1_19_3Plus()) {
CONSTRUCTOR = CLASS.getConstructor(int.class, List.class);
} else {
CONSTRUCTOR = CLASS.getConstructor(int.class, DataWatcher.CLASS, boolean.class);
}
}
}
@@ -1,87 +0,0 @@
package me.neznamy.tab.platforms.bukkit.nms.storage.packet;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import me.neznamy.tab.shared.backend.Location;
import me.neznamy.tab.shared.util.ReflectionUtils;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
/**
* Custom class for holding data used in PacketPlayOutEntityTeleport minecraft packet.
*/
public class PacketPlayOutEntityTeleportStorage {
/** NMS Fields */
public static Class<?> CLASS;
public static Constructor<?> CONSTRUCTOR;
public static Field ENTITY_ID;
public static Field X;
public static Field Y;
public static Field Z;
public static Field YAW;
public static Field PITCH;
/**
* Loads all required Fields and throws Exception if something went wrong
*
* @param nms
* NMS storage reference
* @throws NoSuchMethodException
* If something fails
*/
public static void load(NMSStorage nms) throws NoSuchMethodException {
ENTITY_ID = ReflectionUtils.getFields(CLASS, int.class).get(0);
YAW = ReflectionUtils.getFields(CLASS, byte.class).get(0);
PITCH = ReflectionUtils.getFields(CLASS, byte.class).get(1);
if (nms.getMinorVersion() >= 17) {
CONSTRUCTOR = CLASS.getConstructor(nms.Entity);
} else {
CONSTRUCTOR = CLASS.getConstructor();
}
if (nms.getMinorVersion() >= 9) {
X = ReflectionUtils.getFields(CLASS, double.class).get(0);
Y = ReflectionUtils.getFields(CLASS, double.class).get(1);
Z = ReflectionUtils.getFields(CLASS, double.class).get(2);
} else {
X = ReflectionUtils.getFields(CLASS, int.class).get(1);
Y = ReflectionUtils.getFields(CLASS, int.class).get(2);
Z = ReflectionUtils.getFields(CLASS, int.class).get(3);
}
}
/**
* Converts this class into NMS packet
*
* @return NMS packet
*/
@SneakyThrows
public static Object build(int entityId, Location location) {
NMSStorage nms = NMSStorage.getInstance();
Object nmsPacket;
if (nms.getMinorVersion() >= 17) {
nmsPacket = CONSTRUCTOR.newInstance(nms.dummyEntity);
} else {
nmsPacket = CONSTRUCTOR.newInstance();
}
ENTITY_ID.set(nmsPacket, entityId);
if (nms.getMinorVersion() >= 9) {
X.set(nmsPacket, location.getX());
Y.set(nmsPacket, location.getY());
Z.set(nmsPacket, location.getZ());
} else {
X.set(nmsPacket, floor(location.getX()*32));
Y.set(nmsPacket, floor(location.getY()*32));
Z.set(nmsPacket, floor(location.getZ()*32));
}
YAW.set(nmsPacket, (byte) (location.getYaw()/360*256));
PITCH.set(nmsPacket, (byte) (location.getPitch()/360*256));
return nmsPacket;
}
private static int floor(double paramDouble) {
int i = (int)paramDouble;
return paramDouble < i ? i - 1 : i;
}
}
@@ -1,131 +0,0 @@
package me.neznamy.tab.platforms.bukkit.nms.storage.packet;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import me.neznamy.tab.shared.backend.EntityData;
import me.neznamy.tab.shared.backend.Location;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.bukkit.entity.EntityType;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.EnumMap;
import java.util.UUID;
/**
* Custom class for holding data used in PacketPlayOutSpawnEntityLiving minecraft packet.
*/
public class PacketPlayOutSpawnEntityLivingStorage {
/** NMS Fields */
public static Class<?> CLASS;
public static Constructor<?> CONSTRUCTOR;
/** 1.17+ */
public static Class<?> Vec3D;
public static Class<?> EntityTypes;
public static Object Vec3D_Empty;
public static Object EntityTypes_ARMOR_STAND;
/** 1.16.5- */
public static Field ENTITY_ID;
public static Field ENTITY_TYPE;
public static Field YAW;
public static Field PITCH;
public static Field UUID;
public static Field X;
public static Field Y;
public static Field Z;
public static Field DATA_WATCHER;
public static final EnumMap<EntityType, Integer> entityIds = new EnumMap<>(EntityType.class);
/**
* Loads all required Fields and throws Exception if something went wrong
*
* @param nms
* NMS storage reference
* @throws NoSuchMethodException
* If something fails
*/
public static void load(NMSStorage nms) throws NoSuchMethodException, IllegalAccessException, NoSuchFieldException {
int minorVersion = nms.getMinorVersion();
if (minorVersion >= 13) {
entityIds.put(EntityType.ARMOR_STAND, 1);
} else {
entityIds.put(EntityType.WITHER, 64);
if (minorVersion >= 8) {
entityIds.put(EntityType.ARMOR_STAND, 30);
}
}
if (minorVersion >= 19) {
EntityTypes_ARMOR_STAND = EntityTypes.getDeclaredField("d").get(null);
CONSTRUCTOR = CLASS.getConstructor(int.class, UUID.class, double.class, double.class, double.class, float.class, float.class, EntityTypes, int.class, Vec3D, double.class);
Vec3D_Empty = ReflectionUtils.getOnlyField(Vec3D, Vec3D).get(null);
} else if (minorVersion >= 17) {
EntityTypes_ARMOR_STAND = EntityTypes.getDeclaredField("c").get(null);
CONSTRUCTOR = CLASS.getConstructor(int.class, UUID.class, double.class, double.class, double.class, float.class, float.class, EntityTypes, int.class, Vec3D);
Vec3D_Empty = ReflectionUtils.getOnlyField(Vec3D, Vec3D).get(null);
} else {
CONSTRUCTOR = CLASS.getConstructor();
ENTITY_ID = ReflectionUtils.getFields(CLASS, int.class).get(0);
YAW = ReflectionUtils.getFields(CLASS, byte.class).get(0);
PITCH = ReflectionUtils.getFields(CLASS, byte.class).get(1);
if (minorVersion >= 9) {
UUID = ReflectionUtils.getOnlyField(CLASS, UUID.class);
X = ReflectionUtils.getFields(CLASS, double.class).get(0);
Y = ReflectionUtils.getFields(CLASS, double.class).get(1);
Z = ReflectionUtils.getFields(CLASS, double.class).get(2);
} else {
X = ReflectionUtils.getFields(CLASS, int.class).get(2);
Y = ReflectionUtils.getFields(CLASS, int.class).get(3);
Z = ReflectionUtils.getFields(CLASS, int.class).get(4);
}
ENTITY_TYPE = ReflectionUtils.getFields(CLASS, int.class).get(1);
if (minorVersion <= 14) {
DATA_WATCHER = ReflectionUtils.getOnlyField(CLASS, DataWatcher.CLASS);
}
}
}
/**
* Converts this class into NMS packet
*
* @return NMS packet
*/
@SneakyThrows
public static Object build(int id, UUID uuid, Object entityType, Location l, EntityData data) {
int minorVersion = NMSStorage.getInstance().getMinorVersion();
if (minorVersion >= 19) {
return CONSTRUCTOR.newInstance(id, uuid, l.getX(), l.getY(), l.getZ(), l.getYaw(), l.getPitch(), EntityTypes_ARMOR_STAND, 0, Vec3D_Empty, 0d);
}
if (minorVersion >= 17) {
return CONSTRUCTOR.newInstance(id, uuid, l.getX(), l.getY(), l.getZ(), l.getYaw(), l.getPitch(), EntityTypes_ARMOR_STAND, 0, Vec3D_Empty);
}
Object nmsPacket = CONSTRUCTOR.newInstance();
ENTITY_ID.set(nmsPacket, id);
YAW.set(nmsPacket, (byte)(l.getYaw() * 256.0f / 360.0f));
PITCH.set(nmsPacket, (byte)(l.getPitch() * 256.0f / 360.0f));
if (minorVersion <= 14) {
DATA_WATCHER.set(nmsPacket, data.build());
}
if (minorVersion >= 9) {
UUID.set(nmsPacket, uuid);
X.set(nmsPacket, l.getX());
Y.set(nmsPacket, l.getY());
Z.set(nmsPacket, l.getZ());
} else {
X.set(nmsPacket, floor(l.getX()*32));
Y.set(nmsPacket, floor(l.getY()*32));
Z.set(nmsPacket, floor(l.getZ()*32));
}
ENTITY_TYPE.set(nmsPacket, entityIds.get((EntityType) entityType));
return nmsPacket;
}
private static int floor(double paramDouble) {
int i = (int)paramDouble;
return paramDouble < i ? i - 1 : i;
}
}
@@ -1,24 +1,26 @@
package me.neznamy.tab.platforms.bukkit.platform;
import de.myzelyam.api.vanish.VanishAPI;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.SneakyThrows;
import me.clip.placeholderapi.PlaceholderAPI;
import me.neznamy.tab.platforms.bukkit.BukkitPipelineInjector;
import me.neznamy.tab.platforms.bukkit.BukkitPlaceholderRegistry;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.platforms.bukkit.*;
import me.neznamy.tab.platforms.bukkit.header.HeaderFooter;
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
import me.neznamy.tab.platforms.bukkit.nms.PacketSender;
import me.neznamy.tab.platforms.bukkit.nms.PingRetriever;
import me.neznamy.tab.platforms.bukkit.scoreboard.ScoreboardLoader;
import me.neznamy.tab.shared.GroupManager;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.chat.rgb.RGBUtils;
import me.neznamy.tab.shared.features.injection.PipelineInjector;
import me.neznamy.tab.shared.features.types.TabFeature;
import me.neznamy.tab.platforms.bukkit.features.BukkitTabExpansion;
import me.neznamy.tab.platforms.bukkit.features.PerWorldPlayerList;
import me.neznamy.tab.platforms.bukkit.features.WitherBossBar;
import me.neznamy.tab.platforms.bukkit.features.BukkitNameTagX;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.backend.BackendPlatform;
import me.neznamy.tab.shared.features.PlaceholderManagerImpl;
@@ -28,9 +30,14 @@ import me.neznamy.tab.shared.hook.LuckPermsHook;
import me.neznamy.tab.shared.placeholders.PlayerPlaceholderImpl;
import me.neznamy.tab.shared.placeholders.expansion.EmptyTabExpansion;
import me.neznamy.tab.shared.placeholders.expansion.TabExpansion;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.milkbowl.vault.chat.Chat;
import net.milkbowl.vault.permission.Permission;
import org.bstats.bukkit.Metrics;
import org.bstats.charts.SimplePie;
import org.bukkit.Bukkit;
import org.bukkit.command.PluginCommand;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.plugin.RegisteredServiceProvider;
@@ -38,50 +45,115 @@ import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.io.File;
import java.lang.reflect.Field;
/**
* Implementation of Platform interface for Bukkit platform
*/
@RequiredArgsConstructor
@Getter
public class BukkitPlatform implements BackendPlatform {
/** Plugin instance for registering tasks and events */
/** Flag tracking plugin presence */
private final boolean premiumVanish = Bukkit.getPluginManager().isPluginEnabled("PremiumVanish");
/**
* Plugin instance for registering tasks and events
*/
@NotNull
private final JavaPlugin plugin;
/** Variables checking presence of other plugins to hook into */
/**
* Variables checking presence of other plugins to hook into
*/
private final boolean placeholderAPI = ReflectionUtils.classExists("me.clip.placeholderapi.PlaceholderAPI");
@Setter private boolean libsDisguisesEnabled = ReflectionUtils.classExists("me.libraryaddict.disguise.DisguiseAPI");
public @NotNull BossBarManagerImpl getLegacyBossBar() {
/**
* NMS server to get TPS from on spigot
*/
@Nullable
private Object server;
/**
* TPS field
*/
@Nullable
private Field spigotTps;
/**
* Detection for presence of Paper's TPS getter
*/
private final boolean paperTps = ReflectionUtils.methodExists(Bukkit.class, "getTPS");
/**
* Detection for presence of Paper's MSPT getter
*/
private final boolean paperMspt = ReflectionUtils.methodExists(Bukkit.class, "getAverageTickTime");
public BukkitPlatform(@NotNull JavaPlugin plugin) {
this.plugin = plugin;
try {
server = Bukkit.getServer().getClass().getMethod("getServer").invoke(Bukkit.getServer());
spigotTps = server.getClass().getField("recentTps");
} catch (ReflectiveOperationException e) {
//not spigot
}
PingRetriever.tryLoad();
PacketSender.tryLoad();
ScoreboardLoader.findInstance();
if (BukkitReflection.getMinorVersion() >= 8) {
BukkitPipelineInjector.tryLoad();
HeaderFooter.findInstance();
}
}
@Override
@NotNull
public BossBarManagerImpl getLegacyBossBar() {
return new WitherBossBar(plugin);
}
@Override
public void loadPlayers() {
for (Player p : getOnlinePlayers()) {
TAB.getInstance().addPlayer(new BukkitTabPlayer(p));
for (Player p : BukkitUtils.getOnlinePlayers()) {
TAB.getInstance().addPlayer(new BukkitTabPlayer(this, p));
}
}
@Override
public void registerPlaceholders() {
new BukkitPlaceholderRegistry().registerPlaceholders(TAB.getInstance().getPlaceholderManager());
PlaceholderManagerImpl manager = TAB.getInstance().getPlaceholderManager();
manager.registerServerPlaceholder("%vault-prefix%", -1, () -> "");
manager.registerServerPlaceholder("%vault-suffix%", -1, () -> "");
if (Bukkit.getPluginManager().isPluginEnabled("Vault")) {
RegisteredServiceProvider<Chat> rspChat = Bukkit.getServicesManager().getRegistration(Chat.class);
if (rspChat != null) {
Chat chat = rspChat.getProvider();
manager.registerPlayerPlaceholder("%vault-prefix%", 1000, p -> chat.getPlayerPrefix((Player) p.getPlayer()));
manager.registerPlayerPlaceholder("%vault-suffix%", 1000, p -> chat.getPlayerSuffix((Player) p.getPlayer()));
}
}
// Override for the PAPI placeholder to prevent console errors on unsupported server versions when ping field changes
manager.registerPlayerPlaceholder("%player_ping%", manager.getRefreshInterval("%player_ping%"),
p -> ((TabPlayer) p).getPing());
BackendPlatform.super.registerPlaceholders();
}
@Override
public @Nullable PipelineInjector createPipelineInjector() {
return NMSStorage.getInstance().getMinorVersion() >= 8 ? new BukkitPipelineInjector() : null;
@Nullable
public PipelineInjector createPipelineInjector() {
return BukkitReflection.getMinorVersion() >= 8 && BukkitPipelineInjector.isAvailable() ? new BukkitPipelineInjector() : null;
}
@Override
public @NotNull NameTag getUnlimitedNameTags() {
return new BukkitNameTagX(plugin);
@NotNull
public NameTag getUnlimitedNameTags() {
return BukkitReflection.getMinorVersion() >= 8 ? new BukkitNameTagX(plugin) : new NameTag();
}
@Override
public @NotNull TabExpansion createTabExpansion() {
@NotNull
public TabExpansion createTabExpansion() {
if (placeholderAPI) {
BukkitTabExpansion expansion = new BukkitTabExpansion();
expansion.register();
@@ -91,73 +163,108 @@ public class BukkitPlatform implements BackendPlatform {
}
@Override
public @Nullable TabFeature getPerWorldPlayerList() {
@Nullable
public TabFeature getPerWorldPlayerList() {
return new PerWorldPlayerList(plugin);
}
/**
* Returns online players from Bukkit API
*
* @return online players from Bukkit API
*/
@SuppressWarnings("unchecked")
@SneakyThrows
private @NotNull Player[] getOnlinePlayers() {
Object players = Bukkit.class.getMethod("getOnlinePlayers").invoke(null);
if (players instanceof Player[]) {
//1.7-
return (Player[]) players;
} else {
//1.8+
return ((Collection<Player>)players).toArray(new Player[0]);
}
}
@Override
public void registerUnknownPlaceholder(@NotNull String identifier) {
if (!placeholderAPI) {
registerDummyPlaceholder(identifier);
return;
}
PlaceholderManagerImpl pl = TAB.getInstance().getPlaceholderManager();
int refresh = pl.getRefreshInterval(identifier);
if (identifier.startsWith("%rel_")) {
//relational placeholder
TAB.getInstance().getPlaceholderManager().registerRelationalPlaceholder(identifier, pl.getRefreshInterval(identifier), (viewer, target) ->
placeholderAPI ? PlaceholderAPI.setRelationalPlaceholders((Player) viewer.getPlayer(), (Player) target.getPlayer(), identifier) : identifier);
PlaceholderAPI.setRelationalPlaceholders((Player) viewer.getPlayer(), (Player) target.getPlayer(), identifier));
} else if (identifier.startsWith("%sync:")) {
registerSyncPlaceholder(identifier, refresh);
} else if (identifier.startsWith("%server_")) {
TAB.getInstance().getPlaceholderManager().registerServerPlaceholder(identifier, refresh, () ->
placeholderAPI ? PlaceholderAPI.setPlaceholders(null, identifier) : identifier);
TAB.getInstance().getPlaceholderManager().registerServerPlaceholder(identifier, refresh,
() -> PlaceholderAPI.setPlaceholders(null, identifier));
} else {
TAB.getInstance().getPlaceholderManager().registerPlayerPlaceholder(identifier, refresh, p ->
placeholderAPI ? PlaceholderAPI.setPlaceholders((Player) p.getPlayer(), identifier) : identifier);
TAB.getInstance().getPlaceholderManager().registerPlayerPlaceholder(identifier, refresh,
p -> PlaceholderAPI.setPlaceholders((Player) p.getPlayer(), identifier));
}
}
public void registerSyncPlaceholder(String identifier, int refresh) {
public void registerSyncPlaceholder(@NotNull String identifier, int refresh) {
String syncedPlaceholder = "%" + identifier.substring(6);
PlayerPlaceholderImpl[] ppl = new PlayerPlaceholderImpl[1];
ppl[0] = TAB.getInstance().getPlaceholderManager().registerPlayerPlaceholder(identifier, refresh, p -> {
Bukkit.getScheduler().runTask(plugin, () -> {
long time = System.nanoTime();
ppl[0].updateValue(p, placeholderAPI ? PlaceholderAPI.setPlaceholders((Player) p.getPlayer(), syncedPlaceholder) : identifier);
TAB.getInstance().getCPUManager().addPlaceholderTime(identifier, System.nanoTime()-time);
TAB.getInstance().getCPUManager().addPlaceholderTime(identifier, System.nanoTime() - time);
});
return null;
});
}
@Override
public void sendConsoleMessage(@NotNull IChatBaseComponent message) {
Bukkit.getConsoleSender().sendMessage("[TAB] " + RGBUtils.getInstance().convertToBukkitFormat(message.toFlatText(),
TAB.getInstance().getServerVersion().getMinorVersion() >= 16));
public void logInfo(@NotNull IChatBaseComponent message) {
Bukkit.getConsoleSender().sendMessage("[TAB] " + BukkitUtils.toBukkitFormat(message, true));
}
@Override
public void logWarn(@NotNull IChatBaseComponent message) {
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.RED.getFormat() + "[TAB] [WARN] " + BukkitUtils.toBukkitFormat(message, true));
}
@Override
@NotNull
public String getServerVersionInfo() {
return "[Bukkit] " + Bukkit.getName() + " - " + Bukkit.getBukkitVersion().split("-")[0] + " (" + NMSStorage.getInstance().getServerPackage() + ")";
return "[Bukkit] " + Bukkit.getName() + " - " +
Bukkit.getBukkitVersion().split("-")[0] +
" (" + Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3] + ")";
}
@Override
@NotNull public GroupManager detectPermissionPlugin() {
public void registerListener() {
Bukkit.getPluginManager().registerEvents(new BukkitEventListener(), plugin);
}
@Override
public void registerCommand() {
PluginCommand command = Bukkit.getPluginCommand(TabConstants.COMMAND_BACKEND);
if (command != null) {
BukkitTabCommand cmd = new BukkitTabCommand();
command.setExecutor(cmd);
command.setTabCompleter(cmd);
} else {
logWarn(new IChatBaseComponent("Failed to register command, is it defined in plugin.yml?"));
}
}
@Override
public void startMetrics() {
Metrics metrics = new Metrics(plugin, 5304);
metrics.addCustomChart(new SimplePie(TabConstants.MetricsChart.UNLIMITED_NAME_TAG_MODE_ENABLED,
() -> TAB.getInstance().getFeatureManager().isFeatureEnabled(TabConstants.Feature.UNLIMITED_NAME_TAGS) ? "Yes" : "No"));
metrics.addCustomChart(new SimplePie(TabConstants.MetricsChart.PERMISSION_SYSTEM,
() -> TAB.getInstance().getGroupManager().getPermissionPlugin()));
metrics.addCustomChart(new SimplePie(TabConstants.MetricsChart.SERVER_VERSION,
() -> "1." + BukkitReflection.getMinorVersion() + ".x"));
}
@Override
@NotNull
public ProtocolVersion getServerVersion() {
return ProtocolVersion.fromFriendlyName(Bukkit.getBukkitVersion().split("-")[0]);
}
@Override
@NotNull
public File getDataFolder() {
return plugin.getDataFolder();
}
@Override
@NotNull
public GroupManager detectPermissionPlugin() {
if (LuckPermsHook.getInstance().isInstalled()) {
return new GroupManager("LuckPerms", LuckPermsHook.getInstance().getGroupFunction());
}
@@ -170,7 +277,40 @@ public class BukkitPlatform implements BackendPlatform {
return new GroupManager("None", p -> TabConstants.NO_GROUP);
}
public void runEntityTask(Entity entity, Runnable task) {
@Override
@SneakyThrows
public double getTPS() {
if (paperTps) {
return Bukkit.getTPS()[0];
} else if (spigotTps != null) {
return ((double[]) spigotTps.get(server))[0];
} else {
return -1;
}
}
@Override
public double getMSPT() {
if (paperMspt) return Bukkit.getAverageTickTime();
return -1;
}
public void runSync(@NotNull Entity entity, @NotNull Runnable task) {
Bukkit.getScheduler().runTask(plugin, task);
}
public void runEntityTask(@NotNull Entity entity, @NotNull Runnable task) {
task.run();
}
@Override
public boolean canSee(@NotNull TabPlayer viewer, @NotNull TabPlayer target) {
Player bViewer = ((BukkitTabPlayer)viewer).getPlayer();
Player bTarget = ((BukkitTabPlayer)target).getPlayer();
//noinspection ConstantConditions
if (premiumVanish && VanishAPI.canSee(bViewer, bTarget)) return true;
if (bViewer.canSee(bTarget)) return true;
return BackendPlatform.super.canSee(viewer, target);
}
}
@@ -5,6 +5,7 @@ import me.clip.placeholderapi.PlaceholderAPI;
import me.neznamy.tab.platforms.bukkit.features.PerWorldPlayerList;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.placeholders.PlayerPlaceholderImpl;
import me.neznamy.tab.shared.platform.TabPlayer;
import org.bukkit.entity.Entity;
@@ -12,6 +13,7 @@ import org.bukkit.entity.Player;
import org.bukkit.event.player.PlayerChangedWorldEvent;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.NotNull;
import java.util.function.Consumer;
@@ -20,7 +22,7 @@ import java.util.function.Consumer;
*/
public class FoliaPlatform extends BukkitPlatform {
public FoliaPlatform(JavaPlugin plugin) {
public FoliaPlatform(@NotNull JavaPlugin plugin) {
super(plugin);
}
@@ -29,8 +31,8 @@ public class FoliaPlatform extends BukkitPlatform {
super.loadPlayers();
// Values are never updated in the API, warn users
TAB.getInstance().sendConsoleMessage("&cFolia never updates MSPT and TPS values in the API, making " +
"%mspt% and %tps% return the default values (0 and 20).", true);
logWarn(new IChatBaseComponent("Folia never updates MSPT and TPS values in the API, making " +
"%mspt% and %tps% return the default values (0 and 20)."));
// Folia never calls PlayerChangedWorldEvent, this is a workaround
TAB.getInstance().getCPUManager().startRepeatingMeasuredTask(100, "Folia compatibility", "Refreshing world", () -> {
@@ -48,7 +50,7 @@ public class FoliaPlatform extends BukkitPlatform {
}
@Override
public void registerSyncPlaceholder(String identifier, int refresh) {
public void registerSyncPlaceholder(@NotNull String identifier, int refresh) {
String syncedPlaceholder = "%" + identifier.substring(6);
PlayerPlaceholderImpl[] ppl = new PlayerPlaceholderImpl[1];
ppl[0] = TAB.getInstance().getPlaceholderManager().registerPlayerPlaceholder(identifier, refresh, p -> {
@@ -72,9 +74,10 @@ public class FoliaPlatform extends BukkitPlatform {
* @param task
* Task to run
*/
@Override
@SneakyThrows
@SuppressWarnings("JavaReflectionMemberAccess")
private void runSync(Entity entity, Runnable task) {
public void runSync(@NotNull Entity entity, @NotNull Runnable task) {
Object entityScheduler = Entity.class.getMethod("getScheduler").invoke(entity);
Consumer<?> consumer = $ -> task.run(); // Reflection and lambdas don't go together
entityScheduler.getClass().getMethod("run", Plugin.class, Consumer.class, Runnable.class)
@@ -82,7 +85,7 @@ public class FoliaPlatform extends BukkitPlatform {
}
@Override
public void runEntityTask(Entity entity, Runnable task) {
public void runEntityTask(@NotNull Entity entity, @NotNull Runnable task) {
runSync(entity, task);
}
}
@@ -0,0 +1,268 @@
package me.neznamy.tab.platforms.bukkit.scoreboard;
import lombok.Getter;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.platforms.bukkit.BukkitUtils;
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
import me.neznamy.tab.shared.Limitations;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.scoreboard.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.concurrent.CountDownLatch;
/**
* Scoreboard implementation using Bukkit API (1.5.2+). It has several issues:
* #1 - Limitations on legacy versions are forced in the API.
* While this may not seem like a problem, it enforces those
* limits even for 1.13+ players (if using ViaVersion).<p>
* #2 - Modern versions no longer have any limits, but md_5
* decided to add some random limits for absolutely no reason
* at all. Scoreboard title received a random 128 characters
* limit including color codes. Together with the almighty bukkit
* RGB format using 14 characters for 1 color code, this makes
* gradients just impossible to use. Team prefix/suffix also
* received a 64 characters limit (excluding color codes at least),
* however that might not be enough for displaying a line of text
* in sidebar, which would require splitting the text into prefix
* and suffix, which is just begging for bugs to be introduced.<p>
* #3 - Other plugins can decide to put players into their own
* scoreboard, automatically destroying all visuals made by the
* plugin. They might also put all players into the same scoreboard,
* making per-player view of teams, especially sidebar not working.<p>
*/
// Throw the NPE if something is not as expected, parent class should ensure it anyway
@SuppressWarnings({"ConstantConditions", "deprecation"})
public class BukkitScoreboard extends Scoreboard<BukkitTabPlayer> {
/** Flag tracking whether this implementation is available for use */
@Getter
private static final boolean available;
/** Array of display slots, because the Bukkit order does not match network ordinals */
private static org.bukkit.scoreboard.DisplaySlot[] slots;
/** Server's minor version */
private final int serverMinorVersion = BukkitReflection.getMinorVersion();
protected org.bukkit.scoreboard.Scoreboard scoreboard;
static {
available = ReflectionUtils.methodExists(Bukkit.class, "getScoreboardManager");
if (available) {
slots = new org.bukkit.scoreboard.DisplaySlot[]{
org.bukkit.scoreboard.DisplaySlot.PLAYER_LIST,
org.bukkit.scoreboard.DisplaySlot.SIDEBAR,
org.bukkit.scoreboard.DisplaySlot.BELOW_NAME
};
}
}
@SneakyThrows
public BukkitScoreboard(@NotNull BukkitTabPlayer player) {
super(player);
// Put player into a different scoreboard for per-player view
runSync(() -> {
scoreboard = Bukkit.getScoreboardManager().getNewScoreboard();
player.getPlayer().setScoreboard(scoreboard);
});
}
@Override
public void setDisplaySlot0(int slot, @NotNull String objective) {
checkPlayerScoreboard();
scoreboard.getObjective(objective).setDisplaySlot(slots[slot]);
}
@Override
public void setScore0(@NotNull String objective, @NotNull String scoreHolder, int score,
@Nullable IChatBaseComponent displayName, @Nullable IChatBaseComponent numberFormat) {
checkPlayerScoreboard();
if (serverMinorVersion >= 7 && TAB.getInstance().getServerVersion().getNetworkId() >= ProtocolVersion.V1_7_8.getNetworkId()) {
scoreboard.getObjective(objective).getScore(scoreHolder).setScore(score);
} else {
scoreboard.getObjective(objective).getScore(Bukkit.getOfflinePlayer(scoreHolder)).setScore(score);
}
}
@Override
public void removeScore0(@NotNull String objective, @NotNull String scoreHolder) {
checkPlayerScoreboard();
if (serverMinorVersion >= 7 && TAB.getInstance().getServerVersion().getNetworkId() >= ProtocolVersion.V1_7_8.getNetworkId()) {
scoreboard.resetScores(scoreHolder);
} else {
scoreboard.resetScores(Bukkit.getOfflinePlayer(scoreHolder));
}
}
@Override
public void registerObjective0(@NotNull String objectiveName, @NotNull String title, int display,
@Nullable IChatBaseComponent numberFormat) {
checkPlayerScoreboard();
newObjective(objectiveName, "dummy", title, display);
}
@Override
public void unregisterObjective0(@NotNull String objectiveName) {
checkPlayerScoreboard();
scoreboard.getObjective(objectiveName).unregister();
}
@Override
public void updateObjective0(@NotNull String objectiveName, @NotNull String title, int display,
@Nullable IChatBaseComponent numberFormat) {
checkPlayerScoreboard();
Objective obj = scoreboard.getObjective(objectiveName);
setDisplayName(obj, title);
if (serverMinorVersion >= 14) obj.setRenderType(RenderType.values()[display]);
}
@Override
public void registerTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix,
@NotNull NameVisibility visibility, @NotNull CollisionRule collision,
@NotNull Collection<String> players, int options) {
checkPlayerScoreboard();
Team team = scoreboard.registerNewTeam(name);
setPrefix(team, prefix);
setSuffix(team, suffix);
if (serverMinorVersion >= 8)
team.setNameTagVisibility(NameTagVisibility.valueOf(visibility.name()));
if (serverMinorVersion >= 9)
team.setOption(Team.Option.COLLISION_RULE, Team.OptionStatus.values()[collision.ordinal()]);
if (serverMinorVersion >= 13)
team.setColor(ChatColor.valueOf(EnumChatFormat.lastColorsOf(prefix).name()));
if (serverMinorVersion >= 7 && TAB.getInstance().getServerVersion().getNetworkId() >= ProtocolVersion.V1_7_8.getNetworkId()) {
players.forEach(team::addEntry);
} else {
players.forEach(player -> team.addPlayer(Bukkit.getOfflinePlayer(player)));
}
team.setAllowFriendlyFire((options & 0x01) != 0);
team.setCanSeeFriendlyInvisibles((options & 0x02) != 0);
}
@Override
public void unregisterTeam0(@NotNull String name) {
checkPlayerScoreboard();
scoreboard.getTeam(name).unregister();
}
@Override
public void updateTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix,
@NotNull NameVisibility visibility, @NotNull CollisionRule collision, int options) {
checkPlayerScoreboard();
Team team = scoreboard.getTeam(name);
setPrefix(team, prefix);
setSuffix(team, suffix);
if (serverMinorVersion >= 8)
team.setNameTagVisibility(NameTagVisibility.valueOf(visibility.name()));
if (serverMinorVersion >= 9)
team.setOption(Team.Option.COLLISION_RULE, Team.OptionStatus.values()[collision.ordinal()]);
if (serverMinorVersion >= 13)
team.setColor(ChatColor.valueOf(EnumChatFormat.lastColorsOf(prefix).name()));
team.setAllowFriendlyFire((options & 0x01) != 0);
team.setCanSeeFriendlyInvisibles((options & 0x02) != 0);
}
public void newObjective(String objectiveName, String criteria, String title, int display) {
if (serverMinorVersion >= 14) {
scoreboard.registerNewObjective(
objectiveName,
criteria,
transform(title, 128, Limitations.SCOREBOARD_TITLE_PRE_1_13),
RenderType.values()[display]
);
} else {
setDisplayName(scoreboard.registerNewObjective(objectiveName, display == HealthDisplay.HEARTS ? "health" : "dummy"), title);
}
}
public void setDisplayName(@NotNull Objective objective, @NotNull String displayName) {
objective.setDisplayName(transform(displayName, 128, Limitations.SCOREBOARD_TITLE_PRE_1_13));
}
public void setPrefix(@NotNull Team team, @NotNull String prefix) {
team.setPrefix(transform(prefix, 64, Limitations.TEAM_PREFIX_SUFFIX_PRE_1_13));
}
public void setSuffix(@NotNull Team team, @NotNull String suffix) {
team.setSuffix(transform(suffix, 64, Limitations.TEAM_PREFIX_SUFFIX_PRE_1_13));
}
/**
* Transforms text into format using bukkit RGB syntax if server version is 1.16+
*
* @param text
* Text to transform
* @param maxLengthModern
* Maximum text length defined by bukkit API on 1.13+
* @param maxLengthLegacy
* Maximum text length defined by bukkit API on 1.12-
* @return Converted text
*/
@NotNull
private String transform(@NotNull String text, int maxLengthModern, int maxLengthLegacy) {
String transformed = BukkitUtils.toBukkitFormat(IChatBaseComponent.optimizedComponent(text), player.getVersion().getMinorVersion() >= 16);
if (serverMinorVersion >= 16 && maxLengthModern < 128) { // Scoreboard title is not stripping colors
while (ChatColor.stripColor(transformed).length() > maxLengthModern)
transformed = transformed.substring(0, transformed.length()-1);
} else if (serverMinorVersion >= 13) {
if (transformed.length() > maxLengthModern)
transformed = transformed.substring(0, maxLengthModern);
} else {
if (transformed.length() > maxLengthLegacy)
transformed = transformed.substring(0, maxLengthLegacy);
}
return transformed;
}
/**
* Makes sure player is in the correct scoreboard.
* If not, puts the player into correct scoreboard and prints
* a warning.
* The only possible reason for this to happen is another plugin
* putting a player into a different scoreboard.
* Sadly there is no efficient solution to this, which md_5 fails
* to understand and keeps saying you don't need packets for scoreboards.
*/
@SneakyThrows
private void checkPlayerScoreboard() {
if (player.getPlayer().getScoreboard() != scoreboard) {
TAB.getInstance().getErrorManager().printError("Player " + player.getName() + " was in a different scoreboard " +
"than expected. This means another plugin changed player's scoreboard.");
runSync(() -> player.getPlayer().setScoreboard(scoreboard));
}
}
/**
* Runs the task if in main thread. If not, submits the task and waits for the result.
*
* @param task
* Task to run
*/
@SneakyThrows
private void runSync(@NotNull Runnable task) {
if (Bukkit.isPrimaryThread()) {
// Server thread (plugin reload)
task.run();
} else {
// Plugin thread (player join)
CountDownLatch c = new CountDownLatch(1);
player.getPlatform().runSync(player.getPlayer(), () -> {
task.run();
c.countDown();
});
c.await();
}
}
}
@@ -0,0 +1,67 @@
package me.neznamy.tab.platforms.bukkit.scoreboard;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.Scoreboard;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
public class NullScoreboard extends Scoreboard<BukkitTabPlayer> {
public NullScoreboard(@NotNull BukkitTabPlayer player) {
super(player);
}
@Override
protected void setDisplaySlot0(int slot, @NotNull String objective) {
// Do nothing
}
@Override
protected void setScore0(@NotNull String objective, @NotNull String scoreHolder, int score,
@Nullable IChatBaseComponent displayName, @Nullable IChatBaseComponent numberFormat) {
// Do nothing
}
@Override
protected void removeScore0(@NotNull String objective, @NotNull String scoreHolder) {
// Do nothing
}
@Override
protected void registerObjective0(@NotNull String objectiveName, @NotNull String title, int display,
@Nullable IChatBaseComponent numberFormat) {
// Do nothing
}
@Override
protected void unregisterObjective0(@NotNull String objectiveName) {
// Do nothing
}
@Override
protected void updateObjective0(@NotNull String objectiveName, @NotNull String title, int display,
@Nullable IChatBaseComponent numberFormat) {
// Do nothing
}
@Override
protected void registerTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix,
@NotNull NameVisibility visibility, @NotNull CollisionRule collision,
@NotNull Collection<String> players, int options) {
// Do nothing
}
@Override
protected void unregisterTeam0(@NotNull String name) {
// Do nothing
}
@Override
protected void updateTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix,
@NotNull NameVisibility visibility, @NotNull CollisionRule collision, int options) {
// Do nothing
}
}
@@ -1,14 +1,16 @@
package me.neznamy.tab.platforms.bukkit.scoreboard;
import lombok.Getter;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.util.ComponentCache;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
@@ -18,188 +20,134 @@ import java.util.List;
/**
* Scoreboard implementation for Bukkit, which uses packets
* to send scoreboards. While md_5 keeps saying that you don't
* need packets for scoreboards in every single spigot update,
* this is not true. It may seem that way at first, but after
* investigating, fatal problems are found:<p>
* #1 - Limitations on legacy versions are forced in the API.
* While this may not seem like a problem, it enforces those
* limits even for 1.13+ players (if using ViaVersion).<p>
* #2 - Modern versions no longer have any limits, but md_5
* decided to add some random limits for absolutely no reason
* at all. Scoreboard title received a random 128 characters
* limit including color codes. Together with the almighty bukkit
* RGB format using 14 characters for 1 color code, this makes
* gradients just impossible to use. Team prefix/suffix also
* received a 64 characters limit (excluding color codes at least),
* however that might not be enough for displaying a line of text
* in sidebar, which would require splitting the text into prefix
* and suffix, which is just begging for bugs to be introduced.<p>
* #3 - Other plugins can decide to put players into their own
* scoreboard, automatically destroying all visuals made by the
* plugin. They might also put all players into the same scoreboard,
* making per-player view of teams, especially sidebar not working.<p>
* to send scoreboards to use the full potential on all versions
* and server software without any artificial limits.
*/
@SuppressWarnings({"unchecked", "rawtypes"})
@SuppressWarnings("unchecked")
public class PacketScoreboard extends Scoreboard<BukkitTabPlayer> {
private static NMSStorage nms;
@Getter
private static boolean available;
public static Class<?> Scoreboard;
public static Class<?> IScoreboardCriteria;
private static Class<?> Component;
private static Class<?> Scoreboard;
private static Class<?> ScoreboardObjective;
private static Object emptyScoreboard;
private static Field IScoreboardCriteria_self;
private static Class<?> NumberFormat;
private static Constructor<?> newFixedFormat;
// PacketPlayOutScoreboardDisplayObjective
public static Class<?> DisplayObjectiveClass;
private static Constructor<?> newDisplayObjective;
public static Field DisplayObjective_POSITION;
public static Field DisplayObjective_OBJECTIVE_NAME;
// PacketPlayOutScoreboardScore
public static Class<?> ScorePacketClass;
public static Class<?> ScoreboardScoreClass;
public static Class<Enum> EnumScoreboardAction;
private static Constructor<?> newScorePacket_1_13;
private static Constructor<?> newScorePacket_String;
private static Constructor<?> newScorePacket;
private static Constructor<?> newScoreboardScore;
public static Method ScoreboardScore_setScore;
// PacketPlayOutScoreboardObjective
// Objective packet
public static Class<?> ObjectivePacketClass;
private static Constructor<?> newObjectivePacket;
public static Field Objective_OBJECTIVE_NAME;
public static Field Objective_METHOD;
private static Field Objective_RENDER_TYPE;
private static Field Objective_DISPLAY_NAME;
public static Class<Enum> EnumScoreboardHealthDisplay;
public static Class<?> ScoreboardObjective;
private static Constructor<?> newScoreboardObjective;
private static Method ScoreboardObjective_setDisplayName;
private static Enum<?>[] healthDisplays;
private static Object IScoreboardCriteria_dummy;
// PacketPlayOutScoreboardTeamStorage
public static Class<?> TeamPacketClass;
private static Constructor<?> newTeamPacket;
private static Method TeamPacketConstructor_of;
private static Method TeamPacketConstructor_ofBoolean;
public static Field TeamPacket_NAME;
public static Field TeamPacket_ACTION;
public static Field TeamPacket_PLAYERS;
public static Class<Enum> EnumNameTagVisibility;
public static Class<Enum> EnumTeamPush;
public static Class<?> ScoreboardTeam;
private static Constructor<?> newScoreboardTeam;
private static Method ScoreboardTeam_getPlayerNameSet;
public static Method ScoreboardTeam_setNameTagVisibility;
private static Method ScoreboardTeam_setCollisionRule;
public static Method ScoreboardTeam_setPrefix;
public static Method ScoreboardTeam_setSuffix;
private static Method ScoreboardTeam_setColor;
public static Method ScoreboardTeam_setAllowFriendlyFire;
public static Method ScoreboardTeam_setCanSeeFriendlyInvisibles;
private static ScorePacketData scorePacketData;
public static TeamPacketData teamPacketData;
public static DisplayPacketData displayPacketData;
public static void load(NMSStorage nms) throws ReflectiveOperationException {
PacketScoreboard.nms = nms;
emptyScoreboard = Scoreboard.getConstructor().newInstance();
IScoreboardCriteria_self = ReflectionUtils.getFields(IScoreboardCriteria, IScoreboardCriteria).get(0);
newDisplayObjective = DisplayObjectiveClass.getConstructor(int.class, ScoreboardObjective);
DisplayObjective_POSITION = ReflectionUtils.getOnlyField(DisplayObjectiveClass, int.class);
DisplayObjective_OBJECTIVE_NAME = ReflectionUtils.getOnlyField(DisplayObjectiveClass, String.class);
newScoreboardObjective = ReflectionUtils.getOnlyConstructor(ScoreboardObjective);
Objective_OBJECTIVE_NAME = ReflectionUtils.getFields(ObjectivePacketClass, String.class).get(0);
List<Field> list = ReflectionUtils.getFields(ObjectivePacketClass, int.class);
Objective_METHOD = list.get(list.size()-1);
newScoreboardScore = ScoreboardScoreClass.getConstructor(Scoreboard, ScoreboardObjective, String.class);
newScoreboardTeam = ScoreboardTeam.getConstructor(Scoreboard, String.class);
TeamPacket_NAME = ReflectionUtils.getFields(TeamPacketClass, String.class).get(0);
TeamPacket_ACTION = ReflectionUtils.getInstanceFields(TeamPacketClass, int.class).get(0);
TeamPacket_PLAYERS = ReflectionUtils.getOnlyField(TeamPacketClass, Collection.class);
ScoreboardTeam_getPlayerNameSet = ReflectionUtils.getOnlyMethod(ScoreboardTeam, Collection.class);
ScoreboardScore_setScore = ReflectionUtils.getMethod(ScoreboardScoreClass, new String[] {"func_96647_c", "setScore", "b", "c"}, int.class); // {Thermos, 1.5.1 - 1.17.1, 1.18+, 1.5}
ScoreboardTeam_setAllowFriendlyFire = ReflectionUtils.getMethod(ScoreboardTeam, new String[] {"func_96660_a", "setAllowFriendlyFire", "a"}, boolean.class); // {Thermos, 1.5.1+, 1.5 & 1.18+}
ScoreboardTeam_setCanSeeFriendlyInvisibles = ReflectionUtils.getMethod(ScoreboardTeam, new String[] {"func_98300_b", "setCanSeeFriendlyInvisibles", "b"}, boolean.class); // {Thermos, 1.5.1+, 1.5 & 1.18+}
if (nms.getMinorVersion() >= 13) {
newScorePacket_1_13 = ScorePacketClass.getConstructor(EnumScoreboardAction, String.class, String.class, int.class);
private static Method ChatSerializer_DESERIALIZE;
private static final ComponentCache<IChatBaseComponent, Object> componentCache = new ComponentCache<>(1000,
(component, clientVersion) -> ChatSerializer_DESERIALIZE.invoke(null, component.toString(clientVersion)));
static {
try {
int minorVersion = BukkitReflection.getMinorVersion();
Scoreboard = BukkitReflection.getClass("world.scores.Scoreboard", "Scoreboard");
ScoreboardObjective = BukkitReflection.getClass("world.scores.Objective", "world.scores.ScoreboardObjective", "ScoreboardObjective");
Class<?> IScoreboardCriteria = BukkitReflection.getClass(
"world.scores.criteria.ObjectiveCriteria", // Mojang mapped
"world.scores.criteria.IScoreboardCriteria", // Bukkit 1.17.+
"IScoreboardCriteria", // 1.5.1 - 1.16.5
"IObjective" // 1.5.0
);
ObjectivePacketClass = BukkitReflection.getClass(
"network.protocol.game.ClientboundSetObjectivePacket", // Mojang mapped
"network.protocol.game.PacketPlayOutScoreboardObjective", // Bukkit 1.17+
"PacketPlayOutScoreboardObjective", // 1.7 - 1.16.5
"Packet206SetScoreboardObjective" // 1.5 - 1.6.4
);
emptyScoreboard = Scoreboard.getConstructor().newInstance();
Objective_OBJECTIVE_NAME = ReflectionUtils.getFields(ObjectivePacketClass, String.class).get(0);
List<Field> list = ReflectionUtils.getFields(ObjectivePacketClass, int.class);
Objective_METHOD = list.get(list.size()-1);
newObjectivePacket = ObjectivePacketClass.getConstructor(ScoreboardObjective, int.class);
Objective_DISPLAY_NAME = ReflectionUtils.getOnlyField(ObjectivePacketClass, nms.IChatBaseComponent);
ScoreboardTeam_setColor = ReflectionUtils.getOnlyMethod(ScoreboardTeam, void.class, nms.EnumChatFormat);
ScoreboardTeam_setPrefix = ReflectionUtils.getMethod(ScoreboardTeam, new String[]{"setPrefix", "b"}, nms.IChatBaseComponent); // {1.17.1-, 1.18+}
ScoreboardTeam_setSuffix = ReflectionUtils.getMethod(ScoreboardTeam, new String[]{"setSuffix", "c"}, nms.IChatBaseComponent); // {1.17.1-, 1.18+}
} else {
newScorePacket_String = ScorePacketClass.getConstructor(String.class);
newObjectivePacket = ObjectivePacketClass.getConstructor();
Objective_DISPLAY_NAME = ReflectionUtils.getFields(ObjectivePacketClass, String.class).get(1);
ScoreboardTeam_setPrefix = ReflectionUtils.getMethod(ScoreboardTeam, new String[] {"func_96666_b", "setPrefix", "b"}, String.class); // {Thermos, 1.5.1+, 1.5}
ScoreboardTeam_setSuffix = ReflectionUtils.getMethod(ScoreboardTeam, new String[] {"func_96662_c", "setSuffix", "c"}, String.class); // {Thermos, 1.5.1+, 1.5}
if (nms.getMinorVersion() >= 8) {
newScorePacket = ScorePacketClass.getConstructor(ScoreboardScoreClass);
Objective_RENDER_TYPE = ReflectionUtils.getOnlyField(ObjectivePacketClass, EnumScoreboardHealthDisplay);
} else {
newScorePacket = ScorePacketClass.getConstructor(ScoreboardScoreClass, int.class);
IScoreboardCriteria_dummy = ReflectionUtils.getFields(IScoreboardCriteria, IScoreboardCriteria).get(0).get(null);
newScoreboardObjective = ReflectionUtils.getOnlyConstructor(ScoreboardObjective);
if (minorVersion >= 7) {
Component = BukkitReflection.getClass("network.chat.Component", "network.chat.IChatBaseComponent", "IChatBaseComponent");
Class<?> ChatSerializer = BukkitReflection.getClass("network.chat.Component$Serializer",
"network.chat.IChatBaseComponent$ChatSerializer", "IChatBaseComponent$ChatSerializer", "ChatSerializer");
ChatSerializer_DESERIALIZE = ReflectionUtils.getMethods(ChatSerializer, Object.class, String.class).get(0);
}
}
if (nms.getMinorVersion() >= 8) {
ScoreboardTeam_setNameTagVisibility = ReflectionUtils.getMethod(ScoreboardTeam, new String[] {"setNameTagVisibility", "a"}, EnumNameTagVisibility); // {1.8.1+, 1.8 & 1.18+}
}
if (nms.getMinorVersion() >= 9) {
ScoreboardTeam_setCollisionRule = ReflectionUtils.getOnlyMethod(ScoreboardTeam, void.class, EnumTeamPush);
}
if (nms.getMinorVersion() >= 17) {
TeamPacketConstructor_of = ReflectionUtils.getOnlyMethod(TeamPacketClass, TeamPacketClass, ScoreboardTeam);
TeamPacketConstructor_ofBoolean = ReflectionUtils.getOnlyMethod(TeamPacketClass, TeamPacketClass, ScoreboardTeam, boolean.class);
} else {
newTeamPacket = TeamPacketClass.getConstructor(ScoreboardTeam, int.class);
if (minorVersion >= 8) {
Class<?> EnumScoreboardHealthDisplay = BukkitReflection.getClass(
"world.scores.criteria.ObjectiveCriteria$RenderType",
"world.scores.criteria.IScoreboardCriteria$EnumScoreboardHealthDisplay",
"IScoreboardCriteria$EnumScoreboardHealthDisplay",
"EnumScoreboardHealthDisplay");
healthDisplays = (Enum<?>[]) EnumScoreboardHealthDisplay.getMethod("values").invoke(null);
if (minorVersion < 13) {
Objective_RENDER_TYPE = ReflectionUtils.getOnlyField(ObjectivePacketClass, EnumScoreboardHealthDisplay);
}
}
if (minorVersion < 13) {
ScoreboardObjective_setDisplayName = ReflectionUtils.getOnlyMethod(ScoreboardObjective, void.class, String.class);
}
if (BukkitReflection.is1_20_3Plus()) {
NumberFormat = BukkitReflection.getClass("network.chat.numbers.NumberFormat");
newFixedFormat = BukkitReflection.getClass("network.chat.numbers.FixedFormat").getConstructor(Component);
}
scorePacketData = new ScorePacketData();
teamPacketData = new TeamPacketData();
displayPacketData = new DisplayPacketData();
available = true;
} catch (Exception ignored) {
// Print exception to find out what went wrong
}
}
public PacketScoreboard(BukkitTabPlayer player) {
public PacketScoreboard(@NotNull BukkitTabPlayer player) {
super(player);
}
@Override
@SneakyThrows
public void setDisplaySlot(@NotNull DisplaySlot slot, @NotNull String objective) {
player.sendPacket(newDisplayObjective.newInstance(slot.ordinal(), newScoreboardObjective(objective)));
public void setDisplaySlot0(int slot, @NotNull String objective) {
player.sendPacket(displayPacketData.setDisplaySlot(slot, newObjective(objective, "", 0, null)));
}
@Override
@SneakyThrows
public void registerObjective0(@NotNull String objectiveName, @NotNull String title, boolean hearts) {
player.sendPacket(buildObjective(0, objectiveName, title, hearts));
public void registerObjective0(@NotNull String objectiveName, @NotNull String title, int display,
@Nullable IChatBaseComponent numberFormat) {
player.sendPacket(newObjectivePacket(ObjectiveAction.REGISTER, objectiveName, title, display, numberFormat));
}
@Override
public void unregisterObjective0(@NotNull String objectiveName) {
player.sendPacket(buildObjective(1, objectiveName, "", false));
player.sendPacket(newObjectivePacket(ObjectiveAction.UNREGISTER, objectiveName, "", 0, null));
}
@Override
public void updateObjective0(@NotNull String objectiveName, @NotNull String title, boolean hearts) {
player.sendPacket(buildObjective(2, objectiveName, title, hearts));
public void updateObjective0(@NotNull String objectiveName, @NotNull String title, int display,
@Nullable IChatBaseComponent numberFormat) {
player.sendPacket(newObjectivePacket(ObjectiveAction.UPDATE, objectiveName, title, display, numberFormat));
}
@SneakyThrows
private Object buildObjective(int action, String objectiveName, String title, boolean hearts) {
if (nms.getMinorVersion() >= 13) {
return newObjectivePacket.newInstance(
newScoreboardObjective.newInstance(null, objectiveName, null,
nms.toNMSComponent(IChatBaseComponent.optimizedComponent(title), player.getVersion()),
asDisplayType(hearts)),
action
);
private Object newObjectivePacket(int action, @NotNull String objectiveName, @NotNull String title, int display,
@Nullable IChatBaseComponent numberFormat) {
Object packet = newObjectivePacket.newInstance(newObjective(objectiveName, title, display, numberFormat), action);
if (BukkitReflection.getMinorVersion() >= 8 && BukkitReflection.getMinorVersion() < 13) {
Objective_RENDER_TYPE.set(packet, healthDisplays[display]);
}
Object nmsPacket = newObjectivePacket.newInstance();
Objective_OBJECTIVE_NAME.set(nmsPacket, objectiveName);
Objective_DISPLAY_NAME.set(nmsPacket, title);
if (nms.getMinorVersion() >= 8) {
Objective_RENDER_TYPE.set(nmsPacket, asDisplayType(hearts));
}
Objective_METHOD.set(nmsPacket, action);
return nmsPacket;
}
private Object asDisplayType(boolean hearts) {
return Enum.valueOf(EnumScoreboardHealthDisplay, hearts ? "HEARTS" : "INTEGER");
return packet;
}
@Override
@@ -207,106 +155,363 @@ public class PacketScoreboard extends Scoreboard<BukkitTabPlayer> {
public void registerTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix,
@NotNull NameVisibility visibility, @NotNull CollisionRule collision,
@NotNull Collection<String> players, int options) {
Object team = createTeam(name, prefix, suffix, visibility, collision, options);
((Collection<String>)ScoreboardTeam_getPlayerNameSet.invoke(team)).addAll(players);
if (nms.getMinorVersion() >= 17) {
player.sendPacket(TeamPacketConstructor_ofBoolean.invoke(null, team, true));
} else {
player.sendPacket(newTeamPacket.newInstance(team, 0));
}
player.sendPacket(teamPacketData.registerTeam(name, prefix, toComponent(prefix), suffix,
toComponent(suffix), visibility, collision, players, options));
}
@Override
@SneakyThrows
public void unregisterTeam0(@NotNull String name) {
Object team = newScoreboardTeam.newInstance(emptyScoreboard, name);
if (nms.getMinorVersion() >= 17) {
player.sendPacket(TeamPacketConstructor_of.invoke(null, team));
} else {
player.sendPacket(newTeamPacket.newInstance(team, 1));
}
player.sendPacket(teamPacketData.unregisterTeam(name));
}
@Override
@SneakyThrows
public void updateTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix,
@NotNull NameVisibility visibility, @NotNull CollisionRule collision, int options) {
Object team = createTeam(name, prefix, suffix, visibility, collision, options);
if (nms.getMinorVersion() >= 17) {
player.sendPacket(TeamPacketConstructor_ofBoolean.invoke(null, team, false));
} else {
player.sendPacket(newTeamPacket.newInstance(team, 2));
}
}
@SneakyThrows
private Object createTeam(String teamName, String prefix, String suffix, NameVisibility visibility, CollisionRule collision, int options) {
Object team = newScoreboardTeam.newInstance(emptyScoreboard, teamName);
ScoreboardTeam_setAllowFriendlyFire.invoke(team, (options & 0x1) > 0);
ScoreboardTeam_setCanSeeFriendlyInvisibles.invoke(team, (options & 0x2) > 0);
if (nms.getMinorVersion() >= 13) {
ScoreboardTeam_setPrefix.invoke(team, nms.toNMSComponent(IChatBaseComponent.optimizedComponent(prefix), player.getVersion()));
ScoreboardTeam_setSuffix.invoke(team, nms.toNMSComponent(IChatBaseComponent.optimizedComponent(suffix), player.getVersion()));
ScoreboardTeam_setColor.invoke(team, Enum.valueOf(nms.EnumChatFormat, EnumChatFormat.lastColorsOf(prefix).toString()));
} else {
ScoreboardTeam_setPrefix.invoke(team, prefix);
ScoreboardTeam_setSuffix.invoke(team, suffix);
}
if (nms.getMinorVersion() >= 8) ScoreboardTeam_setNameTagVisibility.invoke(team, Enum.valueOf(EnumNameTagVisibility, visibility.name()));
if (nms.getMinorVersion() >= 9) ScoreboardTeam_setCollisionRule.invoke(team, Enum.valueOf(EnumTeamPush, collision.name()));
return team;
player.sendPacket(teamPacketData.updateTeam(name, prefix, toComponent(prefix), suffix,
toComponent(suffix), visibility, collision, options));
}
@Override
@SneakyThrows
public void setScore0(@NotNull String objective, @NotNull String playerName, int score) {
Object packet;
if (nms.getMinorVersion() >= 13) {
packet = newScorePacket_1_13.newInstance(Enum.valueOf(EnumScoreboardAction, "CHANGE"), objective, playerName, score);
} else {
Object scoreboardScore = newScoreboardScore.newInstance(emptyScoreboard, newScoreboardObjective(objective), playerName);
ScoreboardScore_setScore.invoke(scoreboardScore, score);
if (nms.getMinorVersion() >= 8) {
packet = newScorePacket.newInstance(scoreboardScore);
} else {
packet = newScorePacket.newInstance(scoreboardScore, 0);
}
}
player.sendPacket(packet);
public void setScore0(@NotNull String objective, @NotNull String scoreHolder, int score,
@Nullable IChatBaseComponent displayName, @Nullable IChatBaseComponent numberFormat) {
player.sendPacket(scorePacketData.setScore(objective, scoreHolder, score, toComponent(displayName), toFixedFormat(numberFormat)));
}
@Override
@SneakyThrows
public void removeScore0(@NotNull String objective, @NotNull String playerName) {
if (nms.getMinorVersion() >= 13) {
player.sendPacket(newScorePacket_1_13.newInstance(Enum.valueOf(EnumScoreboardAction, "REMOVE"), objective, playerName, 0));
} else {
player.sendPacket(newScorePacket_String.newInstance(playerName));
}
public void removeScore0(@NotNull String objective, @NotNull String scoreHolder) {
player.sendPacket(scorePacketData.removeScore(objective, scoreHolder));
}
/**
* Creates a new Scoreboard Objective with given name.
* Creates a new Scoreboard Objective with given parameters.
*
* @param objectiveName
* Objective name
* @return NMS Objective
* @param title
* Objective title
* @param renderType
* Render type
* @param numberFormat
* Default number format (1.20.3+)
* @return Created objective
*/
@SneakyThrows
public Object newScoreboardObjective(@NotNull String objectiveName) {
if (nms.getMinorVersion() >= 13) {
public Object newObjective(@NotNull String objectiveName, @NotNull String title, int renderType,
@Nullable IChatBaseComponent numberFormat) {
if (BukkitReflection.is1_20_3Plus()) {
// 1.20.3+
return newScoreboardObjective.newInstance(
null,
emptyScoreboard,
objectiveName,
null,
nms.toNMSComponent(new IChatBaseComponent(""), TAB.getInstance().getServerVersion()),
null
null, // Criteria
toComponent(title),
healthDisplays[renderType],
false, // Auto update
toFixedFormat(numberFormat)
);
}
return newScoreboardObjective.newInstance(
null,
objectiveName,
IScoreboardCriteria_self.get(null)
);
if (BukkitReflection.getMinorVersion() >= 13) {
// 1.13 - 1.20.2
return newScoreboardObjective.newInstance(
emptyScoreboard,
objectiveName,
null, // Criteria
toComponent(title),
healthDisplays[renderType]
);
}
// 1.5 - 1.12.2
Object objective = newScoreboardObjective.newInstance(emptyScoreboard, objectiveName, IScoreboardCriteria_dummy);
ScoreboardObjective_setDisplayName.invoke(objective, title);
return objective;
}
@Nullable
private Object toComponent(@NotNull String text) {
return toComponent(IChatBaseComponent.optimizedComponent(text));
}
@Nullable
private Object toComponent(@Nullable IChatBaseComponent component) {
if (component == null || BukkitReflection.getMinorVersion() < 8) return null;
return componentCache.get(component, player.getVersion());
}
@Nullable
@SneakyThrows
private Object toFixedFormat(@Nullable IChatBaseComponent numberFormat) {
if (numberFormat == null || newFixedFormat == null) return null;
return newFixedFormat.newInstance(toComponent(numberFormat));
}
private static class ScorePacketData {
private final Constructor<?> newSetScorePacket;
private Constructor<?> newResetScorePacket; // 1.20.3+
private Constructor<?> newSetScorePacket_String; // 1.12-
private Constructor<?> newScoreboardScore; // 1.12-
private Field SetScorePacket_SCORE; // 1.12-
private Enum<?>[] scoreboardActions; // 1.20.2-
@SneakyThrows
public ScorePacketData() {
Class<?> SetScorePacket = BukkitReflection.getClass(
"network.protocol.game.ClientboundSetScorePacket", // Mojang mapped
"network.protocol.game.PacketPlayOutScoreboardScore", // Bukkit 1.17+
"PacketPlayOutScoreboardScore", // 1.7 - 1.16.5
"Packet207SetScoreboardScore" // 1.5 - 1.6.4
);
if (BukkitReflection.is1_20_3Plus()) {
newResetScorePacket = BukkitReflection.getClass("network.protocol.game.ClientboundResetScorePacket").getConstructor(String.class, String.class);
newSetScorePacket = SetScorePacket.getConstructor(String.class, String.class, int.class, Component, NumberFormat);
} else if (BukkitReflection.getMinorVersion() >= 13) {
Class<?> EnumScoreboardAction = BukkitReflection.getClass("server.ServerScoreboard$Method",
"server.ScoreboardServer$Action", "ScoreboardServer$Action");
newSetScorePacket = SetScorePacket.getConstructor(EnumScoreboardAction, String.class, String.class, int.class);
scoreboardActions = (Enum<?>[]) EnumScoreboardAction.getMethod("values").invoke(null);
} else {
Class<?> ScoreboardScore = BukkitReflection.getClass("ScoreboardScore");
newSetScorePacket_String = SetScorePacket.getConstructor(String.class);
SetScorePacket_SCORE = ReflectionUtils.getFields(SetScorePacket, int.class).get(0);
newScoreboardScore = ScoreboardScore.getConstructor(Scoreboard, ScoreboardObjective, String.class);
if (BukkitReflection.getMinorVersion() >= 8) {
newSetScorePacket = SetScorePacket.getConstructor(ScoreboardScore);
} else {
newSetScorePacket = SetScorePacket.getConstructor(ScoreboardScore, int.class);
}
}
}
@SneakyThrows
public Object setScore(@NotNull String objective, @NotNull String scoreHolder, int score,
@Nullable Object displayName, @Nullable Object numberFormat) {
if (BukkitReflection.is1_20_3Plus()) {
return newSetScorePacket.newInstance(scoreHolder, objective, score, displayName, numberFormat);
} else if (BukkitReflection.getMinorVersion() >= 13) {
return newSetScorePacket.newInstance(scoreboardActions[0], objective, scoreHolder, score);
} else {
Object scoreboardScore = newScoreboardScore.newInstance(
emptyScoreboard,
newScoreboardObjective.newInstance(emptyScoreboard, objective, IScoreboardCriteria_dummy),
scoreHolder
);
Object packet;
if (BukkitReflection.getMinorVersion() >= 8) {
packet = newSetScorePacket.newInstance(scoreboardScore);
} else {
packet = newSetScorePacket.newInstance(scoreboardScore, ScoreAction.CHANGE);
}
SetScorePacket_SCORE.set(packet, score);
return packet;
}
}
@SneakyThrows
public Object removeScore(@NotNull String objective, @NotNull String scoreHolder) {
if (BukkitReflection.is1_20_3Plus()) {
return newResetScorePacket.newInstance(scoreHolder, objective);
} else if (BukkitReflection.getMinorVersion() >= 13) {
return newSetScorePacket.newInstance(scoreboardActions[1], objective, scoreHolder, 0);
} else {
return newSetScorePacket_String.newInstance(scoreHolder);
}
}
}
public static class TeamPacketData {
public Class<?> TeamPacketClass;
private Constructor<?> newTeamPacket;
private final Constructor<?> newScoreboardTeam;
private Method TeamPacketConstructor_of;
private Method TeamPacketConstructor_ofBoolean;
public Field TeamPacket_NAME;
public Field TeamPacket_ACTION;
public Field TeamPacket_PLAYERS;
private final Method ScoreboardTeam_getPlayerNameSet;
private Method ScoreboardTeam_setNameTagVisibility;
private Method ScoreboardTeam_setCollisionRule;
private final Method ScoreboardTeam_setPrefix;
private final Method ScoreboardTeam_setSuffix;
private Method ScoreboardTeam_setColor;
private final Method ScoreboardTeam_setAllowFriendlyFire;
private final Method ScoreboardTeam_setCanSeeFriendlyInvisibles;
private final Enum<?>[] chatFormats;
private Enum<?>[] nameVisibilities;
private Enum<?>[] collisionRules;
@SneakyThrows
public TeamPacketData() {
int minorVersion = BukkitReflection.getMinorVersion();
Class<?> scoreboardTeam = BukkitReflection.getClass("world.scores.PlayerTeam", "world.scores.ScoreboardTeam", "ScoreboardTeam");
Class<?> enumChatFormatClass = BukkitReflection.getClass("ChatFormatting", "EnumChatFormat", "EnumChatFormat");
TeamPacketClass = BukkitReflection.getClass(
"network.protocol.game.ClientboundSetPlayerTeamPacket", // Mojang mapped
"network.protocol.game.PacketPlayOutScoreboardTeam", // Bukkit 1.17+
"PacketPlayOutScoreboardTeam", // Bukkit 1.7 - 1.16.5
"Packet209SetScoreboardTeam" // 1.5 - 1.6.4
);
newScoreboardTeam = scoreboardTeam.getConstructor(Scoreboard, String.class);
TeamPacket_NAME = ReflectionUtils.getFields(TeamPacketClass, String.class).get(0);
TeamPacket_ACTION = ReflectionUtils.getInstanceFields(TeamPacketClass, int.class).get(0);
TeamPacket_PLAYERS = ReflectionUtils.getOnlyField(TeamPacketClass, Collection.class);
ScoreboardTeam_getPlayerNameSet = ReflectionUtils.getOnlyMethod(scoreboardTeam, Collection.class);
chatFormats = (Enum<?>[]) enumChatFormatClass.getMethod("values").invoke(null);
ScoreboardTeam_setAllowFriendlyFire = ReflectionUtils.getMethod(
scoreboardTeam,
new String[] {"func_96660_a", "setAllowFriendlyFire", "a", "m_83355_"}, // {Thermos, 1.5.1+, 1.5 & 1.18+, Mohist 1.18.2}
boolean.class
);
ScoreboardTeam_setCanSeeFriendlyInvisibles = ReflectionUtils.getMethod(
scoreboardTeam,
new String[] {"func_98300_b", "setCanSeeFriendlyInvisibles", "b", "m_83362_", "setSeeFriendlyInvisibles"}, // {Thermos, 1.5.1+, 1.5 & 1.18+, Mohist 1.18.2, 1.20.2+}
boolean.class
);
if (minorVersion >= 8) {
Class<?> enumNameTagVisibility = BukkitReflection.getClass(
"world.scores.Team$Visibility", // Mojang mapped
"world.scores.ScoreboardTeamBase$EnumNameTagVisibility", // Bukkit 1.17+
"ScoreboardTeamBase$EnumNameTagVisibility", // Bukkit 1.8.1 - 1.16.5
"EnumNameTagVisibility" // Bukkit 1.8.0
);
nameVisibilities = (Enum<?>[]) enumNameTagVisibility.getMethod("values").invoke(null);
ScoreboardTeam_setNameTagVisibility = ReflectionUtils.getMethod(
scoreboardTeam,
new String[] {"setNameTagVisibility", "a", "m_83346_"}, // {1.8.1+, 1.8 & 1.18+, Mohist 1.18.2}
enumNameTagVisibility
);
}
if (minorVersion >= 9) {
Class<?> enumTeamPush = BukkitReflection.getClass("world.scores.Team$CollisionRule",
"world.scores.ScoreboardTeamBase$EnumTeamPush", "ScoreboardTeamBase$EnumTeamPush");
ScoreboardTeam_setCollisionRule = ReflectionUtils.getOnlyMethod(scoreboardTeam, void.class, enumTeamPush);
collisionRules = (Enum<?>[]) enumTeamPush.getMethod("values").invoke(null);
}
if (minorVersion >= 13) {
ScoreboardTeam_setColor = ReflectionUtils.getOnlyMethod(scoreboardTeam, void.class, enumChatFormatClass);
ScoreboardTeam_setPrefix = ReflectionUtils.getMethod(
scoreboardTeam,
new String[]{"setPrefix", "b", "m_83360_", "setPlayerPrefix"}, // {1.17.1-, 1.18 - 1.20.1, Mohist 1.18.2, 1.20.2+}
Component
);
ScoreboardTeam_setSuffix = ReflectionUtils.getMethod(
scoreboardTeam,
new String[]{"setSuffix", "c", "m_83365_", "setPlayerSuffix"}, // {1.17.1-, 1.18 - 1.20.1, Mohist 1.18.2, 1.20.2+}
Component
);
} else {
ScoreboardTeam_setPrefix = ReflectionUtils.getMethod(
scoreboardTeam,
new String[] {"func_96666_b", "setPrefix", "b"}, // {Thermos, 1.5.1+, 1.5}
String.class
);
ScoreboardTeam_setSuffix = ReflectionUtils.getMethod(
scoreboardTeam,
new String[] {"func_96662_c", "setSuffix", "c"}, // {Thermos, 1.5.1+, 1.5}
String.class
);
}
if (minorVersion >= 17) {
TeamPacketConstructor_of = ReflectionUtils.getOnlyMethod(TeamPacketClass, TeamPacketClass, scoreboardTeam);
TeamPacketConstructor_ofBoolean = ReflectionUtils.getOnlyMethod(TeamPacketClass, TeamPacketClass, scoreboardTeam, boolean.class);
} else {
newTeamPacket = TeamPacketClass.getConstructor(scoreboardTeam, int.class);
}
}
@SneakyThrows
public Object registerTeam(@NotNull String name, @NotNull String prefix, @Nullable Object prefixComponent,
@NotNull String suffix, @Nullable Object suffixComponent,
@NotNull NameVisibility visibility, @NotNull CollisionRule collision,
@NotNull Collection<String> players, int options) {
Object team = createTeam(name, prefix, prefixComponent, suffix, suffixComponent, visibility, collision, options);
((Collection<String>)ScoreboardTeam_getPlayerNameSet.invoke(team)).addAll(players);
if (BukkitReflection.getMinorVersion() >= 17) {
return TeamPacketConstructor_ofBoolean.invoke(null, team, true);
} else {
return newTeamPacket.newInstance(team, TeamAction.CREATE);
}
}
@SneakyThrows
public Object unregisterTeam(@NotNull String name) {
Object team = newScoreboardTeam.newInstance(emptyScoreboard, name);
if (BukkitReflection.getMinorVersion() >= 17) {
return TeamPacketConstructor_of.invoke(null, team);
} else {
return newTeamPacket.newInstance(team, TeamAction.REMOVE);
}
}
@SneakyThrows
public Object updateTeam(@NotNull String name, @NotNull String prefix, @Nullable Object prefixComponent,
@NotNull String suffix, @Nullable Object suffixComponent,
@NotNull NameVisibility visibility, @NotNull CollisionRule collision, int options) {
Object team = createTeam(name, prefix, prefixComponent, suffix, suffixComponent, visibility, collision, options);
if (BukkitReflection.getMinorVersion() >= 17) {
return TeamPacketConstructor_ofBoolean.invoke(null, team, false);
} else {
return newTeamPacket.newInstance(team, TeamAction.UPDATE);
}
}
@SneakyThrows
private Object createTeam(@NotNull String teamName, @NotNull String prefix, @Nullable Object prefixComponent,
@NotNull String suffix, @Nullable Object suffixComponent,
@NotNull NameVisibility visibility, @NotNull CollisionRule collision, int options) {
Object team = newScoreboardTeam.newInstance(emptyScoreboard, teamName);
ScoreboardTeam_setAllowFriendlyFire.invoke(team, (options & 0x1) > 0);
ScoreboardTeam_setCanSeeFriendlyInvisibles.invoke(team, (options & 0x2) > 0);
if (BukkitReflection.getMinorVersion() >= 13) {
ScoreboardTeam_setPrefix.invoke(team, prefixComponent);
ScoreboardTeam_setSuffix.invoke(team, suffixComponent);
ScoreboardTeam_setColor.invoke(team, chatFormats[EnumChatFormat.lastColorsOf(prefix).ordinal()]);
} else {
ScoreboardTeam_setPrefix.invoke(team, prefix);
ScoreboardTeam_setSuffix.invoke(team, suffix);
}
if (BukkitReflection.getMinorVersion() >= 8)
ScoreboardTeam_setNameTagVisibility.invoke(team, nameVisibilities[visibility.ordinal()]);
if (BukkitReflection.getMinorVersion() >= 9)
ScoreboardTeam_setCollisionRule.invoke(team, collisionRules[collision.ordinal()]);
return team;
}
}
public static class DisplayPacketData {
public final Class<?> DisplayObjectiveClass;
private final Constructor<?> newDisplayObjective;
public final Field DisplayObjective_POSITION;
public final Field DisplayObjective_OBJECTIVE_NAME;
private final Object[] displaySlots;
@SneakyThrows
public DisplayPacketData() {
DisplayObjectiveClass = BukkitReflection.getClass(
"network.protocol.game.ClientboundSetDisplayObjectivePacket", // Mojang mapped
"network.protocol.game.PacketPlayOutScoreboardDisplayObjective", // Bukkit 1.17+
"PacketPlayOutScoreboardDisplayObjective", // Bukkit 1.7 - 1.16.5
"Packet208SetScoreboardDisplayObjective" // Bukkit 1.5 - 1.6.4
);
DisplayObjective_OBJECTIVE_NAME = ReflectionUtils.getOnlyField(DisplayObjectiveClass, String.class);
if (BukkitReflection.is1_20_2Plus()) {
Class<?> DisplaySlot = BukkitReflection.getClass("world.scores.DisplaySlot");
displaySlots = (Object[]) DisplaySlot.getDeclaredMethod("values").invoke(null);
DisplayObjective_POSITION = ReflectionUtils.getOnlyField(DisplayObjectiveClass, DisplaySlot);
newDisplayObjective = DisplayObjectiveClass.getConstructor(DisplaySlot, ScoreboardObjective);
} else {
displaySlots = new Object[]{0, 1, 2};
DisplayObjective_POSITION = ReflectionUtils.getOnlyField(DisplayObjectiveClass, int.class);
newDisplayObjective = DisplayObjectiveClass.getConstructor(int.class, ScoreboardObjective);
}
}
@SneakyThrows
public Object setDisplaySlot(int slot, @NotNull Object objective) {
return newDisplayObjective.newInstance(displaySlots[slot], objective);
}
}
}
@@ -1,20 +1,16 @@
package me.neznamy.tab.platforms.bukkit.scoreboard;
import lombok.Getter;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.hook.AdventureHook;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.kyori.adventure.text.Component;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.scoreboard.Objective;
import org.bukkit.scoreboard.RenderType;
import org.bukkit.scoreboard.Team;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
/**
* Scoreboard handler using Paper API, which got added
* in early 1.16.5 builds. Unlike the Bukkit API, the new
@@ -23,98 +19,36 @@ import java.util.Collection;
* <p>
* However, it still inherits the issue with compatibility
* with other plugins that create scoreboards.
* <p>
* Currently, this class is never used. In the future, it might
* be used as a fallback solution for unsupported versions if NMS
* changes and fields fail to load. Or as a complete replacement
* if compatibility with other plugins turns out not to be a problem.
*/
// Throw the NPE if something is not as expected, parent class should ensure it anyway
// Use Bukkit setColor, the adventure one does not support magic codes while they are permitted
@SuppressWarnings({"ConstantConditions", "deprecation"})
public class PaperScoreboard extends Scoreboard<BukkitTabPlayer> {
public class PaperScoreboard extends BukkitScoreboard {
private final org.bukkit.scoreboard.Scoreboard scoreboard = Bukkit.getScoreboardManager().getNewScoreboard();
/** Flag tracking whether this implementation is available for use */
@Getter
private static final boolean available = ReflectionUtils.classExists("net.kyori.adventure.text.Component") &&
ReflectionUtils.methodExists(Team.class, "prefix", Component.class);
public PaperScoreboard(BukkitTabPlayer player) {
public PaperScoreboard(@NotNull BukkitTabPlayer player) {
super(player);
// Put player into a different scoreboard for per-player view
player.getPlayer().setScoreboard(scoreboard);
}
@Override
public void setDisplaySlot(@NotNull DisplaySlot slot, @NotNull String objective) {
checkPlayerScoreboard();
scoreboard.getObjective(objective).setDisplaySlot(org.bukkit.scoreboard.DisplaySlot.valueOf(slot.name()));
public void newObjective(String objectiveName, String criteria, String title, int display) {
scoreboard.registerNewObjective(objectiveName, criteria, toAdventure(title), RenderType.values()[display]);
}
@Override
public void setScore0(@NotNull String objective, @NotNull String playerName, int score) {
checkPlayerScoreboard();
scoreboard.getObjective(objective).getScore(playerName).setScore(score);
public void setDisplayName(@NotNull Objective objective, @NotNull String displayName) {
objective.displayName(toAdventure(displayName));
}
@Override
public void removeScore0(@NotNull String objective, @NotNull String playerName) {
checkPlayerScoreboard();
scoreboard.resetScores(playerName);
}
@Override
public void registerObjective0(@NotNull String objectiveName, @NotNull String title, boolean hearts) {
checkPlayerScoreboard();
scoreboard.registerNewObjective(objectiveName, "dummy", toAdventure(title), hearts ? RenderType.HEARTS : RenderType.INTEGER);
}
@Override
public void unregisterObjective0(@NotNull String objectiveName) {
checkPlayerScoreboard();
scoreboard.getObjective(objectiveName).unregister();
}
@Override
public void updateObjective0(@NotNull String objectiveName, @NotNull String title, boolean hearts) {
checkPlayerScoreboard();
Objective obj = scoreboard.getObjective(objectiveName);
obj.setRenderType(hearts ? RenderType.HEARTS : RenderType.INTEGER);
obj.displayName(toAdventure(title));
}
@Override
public void registerTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix,
@NotNull NameVisibility visibility, @NotNull CollisionRule collision,
@NotNull Collection<String> players, int options) {
checkPlayerScoreboard();
Team team = scoreboard.registerNewTeam(name);
public void setPrefix(@NotNull Team team, @NotNull String prefix) {
team.prefix(toAdventure(prefix));
team.suffix(toAdventure(suffix));
team.setColor(ChatColor.valueOf(EnumChatFormat.lastColorsOf(prefix).name()));
team.setOption(Team.Option.NAME_TAG_VISIBILITY, Team.OptionStatus.values()[visibility.ordinal()]);
team.setOption(Team.Option.COLLISION_RULE, Team.OptionStatus.values()[collision.ordinal()]);
players.forEach(team::addEntry);
team.setAllowFriendlyFire((options & 0x01) != 0);
team.setCanSeeFriendlyInvisibles((options & 0x02) != 0);
}
@Override
public void unregisterTeam0(@NotNull String name) {
checkPlayerScoreboard();
scoreboard.getTeam(name).unregister();
}
@Override
public void updateTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix,
@NotNull NameVisibility visibility, @NotNull CollisionRule collision, int options) {
checkPlayerScoreboard();
Team team = scoreboard.getTeam(name);
team.prefix(toAdventure(prefix));
public void setSuffix(@NotNull Team team, @NotNull String suffix) {
team.suffix(toAdventure(suffix));
team.setColor(ChatColor.valueOf(EnumChatFormat.lastColorsOf(prefix).name()));
team.setOption(Team.Option.NAME_TAG_VISIBILITY, Team.OptionStatus.values()[visibility.ordinal()]);
team.setOption(Team.Option.COLLISION_RULE, Team.OptionStatus.values()[collision.ordinal()]);
team.setAllowFriendlyFire((options & 0x01) != 0);
team.setCanSeeFriendlyInvisibles((options & 0x02) != 0);
}
/**
@@ -124,24 +58,8 @@ public class PaperScoreboard extends Scoreboard<BukkitTabPlayer> {
* Text to convert
* @return Converted component
*/
private @NotNull Component toAdventure(@NotNull String text) {
return IChatBaseComponent.optimizedComponent(text).toAdventureComponent(player.getVersion());
}
/**
* Makes sure player is in the correct scoreboard.
* If not, puts the player into correct scoreboard and prints
* a warning.
* The only possible reason for this to happen is another plugin
* putting a player into a different scoreboard.
* Sadly there is no efficient solution to this, which md_5 fails
* to understand and keeps saying you don't need packets for scoreboards.
*/
private void checkPlayerScoreboard() {
if (player.getPlayer().getScoreboard() != scoreboard) {
player.getPlayer().setScoreboard(scoreboard);
TAB.getInstance().getErrorManager().printError("Player " + player.getName() + " was in a different scoreboard " +
"than expected. This means another plugin changed player's scoreboard.");
}
@NotNull
private Component toAdventure(@NotNull String text) {
return AdventureHook.toAdventureComponent(IChatBaseComponent.optimizedComponent(text), player.getVersion());
}
}
@@ -0,0 +1,45 @@
package me.neznamy.tab.platforms.bukkit.scoreboard;
import lombok.Getter;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.platforms.bukkit.nms.PacketSender;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.platform.Scoreboard;
import org.bukkit.Bukkit;
import java.util.function.Function;
public class ScoreboardLoader {
/** Instance function */
@Getter
private static Function<BukkitTabPlayer, Scoreboard<BukkitTabPlayer>> instance;
/**
* Finds the best available instance for current server software.
*/
public static void findInstance() {
if (PacketScoreboard.isAvailable() && PacketSender.isAvailable()) {
instance = PacketScoreboard::new;
} else if (PaperScoreboard.isAvailable()) {
instance = PaperScoreboard::new;
sendMessage("Paper");
} else if (BukkitScoreboard.isAvailable()) {
instance = BukkitScoreboard::new;
sendMessage("Bukkit");
} else {
instance = NullScoreboard::new;
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.RED.getFormat() + "[TAB] Failed to initialize NMS fields for " +
"Scoreboards due to a compatibility error. No fallback solution was found either. This will " +
"result in the features not working. " +
"Please update the plugin a to version with native support for your server version to unlock the features.");
}
}
private static void sendMessage(String implementationName) {
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.RED.getFormat() + "[TAB] Failed to initialize NMS fields for " +
"Scoreboards due to a compatibility error. Using fallback solution using " + implementationName + " API. " +
"This may result in features not working properly, mainly compatibility with other plugins. " +
"Please update the plugin to a version with native support for your server version for optimal experience.");
}
}
+1 -4
View File
@@ -7,7 +7,7 @@ load: POSTWORLD
author: @author@
website: "@website@"
depend: []
softdepend: [PlaceholderAPI, LuckPerms, LibsDisguises, ViaVersion, ViaRewind, floodgate]
softdepend: [PlaceholderAPI, LuckPerms, LibsDisguises, ViaVersion, ViaRewind, floodgate, Vault]
loadbefore: []
prefix: TAB
libraries: []
@@ -49,9 +49,6 @@ permissions:
tab.change.*:
description: Allows player to change property using command
default: op
tab.width:
description: Allows player to run /tab width
default: op
tab.announce.bar:
description: Allows player to run /tab announce bar
default: op
+1 -1
View File
@@ -1,7 +1,7 @@
dependencies {
implementation(projects.shared)
implementation("org.bstats:bstats-bungeecord:3.0.1")
compileOnly("net.md-5:bungeecord-proxy:1.20-R0.1-SNAPSHOT")
compileOnly("net.md-5:bungeecord-proxy:1.20-R0.2-SNAPSHOT")
compileOnly("com.github.limework.redisbungee:RedisBungee-Bungee:0.11.0")
compileOnly("com.github.LeonMangler:PremiumVanishAPI:2.8.8")
}
@@ -1,10 +1,10 @@
package me.neznamy.tab.platforms.bungeecord;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.shared.platform.bossbar.BossBar;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.BossBar;
import me.neznamy.tab.api.bossbar.BarColor;
import me.neznamy.tab.api.bossbar.BarStyle;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
@@ -18,11 +18,15 @@ import java.util.UUID;
*/
@RequiredArgsConstructor
public class BungeeBossBar implements BossBar {
private final @NotNull BungeeTabPlayer player;
@NotNull
private final BungeeTabPlayer player;
/** Both are included in the same action, need to remember and write them both */
@NotNull
private final Map<UUID, Integer> colors = new HashMap<>();
@NotNull
private final Map<UUID, Integer> styles = new HashMap<>();
@Override
@@ -31,10 +35,10 @@ public class BungeeBossBar implements BossBar {
net.md_5.bungee.protocol.packet.BossBar packet = new net.md_5.bungee.protocol.packet.BossBar(id, 0);
packet.setHealth(progress);
packet.setTitle(IChatBaseComponent.optimizedComponent(title).toString(player.getVersion()));
packet.setTitle(player.getPlatform().toComponent(IChatBaseComponent.optimizedComponent(title), player.getVersion()));
packet.setColor(color.ordinal());
packet.setDivision(style.ordinal());
player.getPlayer().unsafe().sendPacket(packet);
player.sendPacket(packet);
colors.put(id, color.ordinal());
styles.put(id, style.ordinal());
@@ -45,8 +49,8 @@ public class BungeeBossBar implements BossBar {
if (player.getVersion().getMinorVersion() < 9) return;
net.md_5.bungee.protocol.packet.BossBar packet = new net.md_5.bungee.protocol.packet.BossBar(id, 3);
packet.setTitle(IChatBaseComponent.optimizedComponent(title).toString(player.getVersion()));
player.getPlayer().unsafe().sendPacket(packet);
packet.setTitle(player.getPlatform().toComponent(IChatBaseComponent.optimizedComponent(title), player.getVersion()));
player.sendPacket(packet);
}
@Override
@@ -55,7 +59,7 @@ public class BungeeBossBar implements BossBar {
net.md_5.bungee.protocol.packet.BossBar packet = new net.md_5.bungee.protocol.packet.BossBar(id, 2);
packet.setHealth(progress);
player.getPlayer().unsafe().sendPacket(packet);
player.sendPacket(packet);
}
@Override
@@ -65,7 +69,7 @@ public class BungeeBossBar implements BossBar {
net.md_5.bungee.protocol.packet.BossBar packet = new net.md_5.bungee.protocol.packet.BossBar(id, 4);
packet.setDivision(style.ordinal());
packet.setColor(colors.get(id));
player.getPlayer().unsafe().sendPacket(packet);
player.sendPacket(packet);
styles.put(id, style.ordinal());
}
@@ -77,7 +81,7 @@ public class BungeeBossBar implements BossBar {
net.md_5.bungee.protocol.packet.BossBar packet = new net.md_5.bungee.protocol.packet.BossBar(id, 4);
packet.setDivision(styles.get(id));
packet.setColor(color.ordinal());
player.getPlayer().unsafe().sendPacket(packet);
player.sendPacket(packet);
colors.put(id, color.ordinal());
}
@@ -86,7 +90,7 @@ public class BungeeBossBar implements BossBar {
public void remove(@NotNull UUID id) {
if (player.getVersion().getMinorVersion() < 9) return;
player.getPlayer().unsafe().sendPacket(new net.md_5.bungee.protocol.packet.BossBar(id, 1));
player.sendPacket(new net.md_5.bungee.protocol.packet.BossBar(id, 1));
colors.remove(id);
styles.remove(id);
@@ -1,5 +1,7 @@
package me.neznamy.tab.platforms.bungeecord;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.platform.EventListener;
import me.neznamy.tab.shared.platform.TabPlayer;
@@ -10,6 +12,7 @@ import net.md_5.bungee.api.event.PluginMessageEvent;
import net.md_5.bungee.api.event.ServerSwitchEvent;
import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.event.EventHandler;
import org.jetbrains.annotations.NotNull;
/**
* The core for BungeeCord forwarding events into all enabled features
@@ -23,27 +26,57 @@ public class BungeeEventListener extends EventListener<ProxiedPlayer> implements
@EventHandler
public void onSwitch(ServerSwitchEvent e) {
serverChange(e.getPlayer(), e.getPlayer().getUniqueId(), e.getPlayer().getServer().getInfo().getName());
TAB tab = TAB.getInstance();
if (tab.isPluginDisabled()) return;
// Avoid 1.20.3 client crash on scoreboard packets, do it sync to prevent packet being sent after event, but before processing
TabPlayer p = tab.getPlayer(e.getPlayer().getUniqueId());
if (p != null && p.getVersion().getNetworkId() >= ProtocolVersion.V1_20_3.getNetworkId()) {
p.getScoreboard().freeze();
}
tab.getCPUManager().runTask(() -> {
TabPlayer player = tab.getPlayer(e.getPlayer().getUniqueId());
if (player == null) {
player = createPlayer(e.getPlayer());
// Things will get cleared immediately after, so no point in sending it, also someone said it fixed some warn from Geyser
// Sending these packets before login packet will also crash the client on 1.20.3
if (player.getVersion().getNetworkId() >= ProtocolVersion.V1_20_2.getNetworkId()) {
player.getScoreboard().freeze();
}
tab.getFeatureManager().onJoin(player);
} else {
tab.getFeatureManager().onServerChange(player.getUniqueId(), e.getPlayer().getServer().getInfo().getName());
if (player.getVersion().getNetworkId() < ProtocolVersion.V1_20_2.getNetworkId()) {
// For versions below 1.20.2 the tablist is already clean when this event is called
// For 1.20.2+ this event is called before, so we listen to Login packet instead
tab.getFeatureManager().onTabListClear(player);
}
}
});
}
@EventHandler
public void onChat(ChatEvent e) {
public void onCommand(ChatEvent e) {
if (e.isCommand() && command(((ProxiedPlayer)e.getSender()).getUniqueId(), e.getMessage())) {
e.setCancelled(true);
}
}
@EventHandler
public void on(PluginMessageEvent event) {
if (!event.getTag().equals(TabConstants.PLUGIN_MESSAGE_CHANNEL_NAME)) return;
if (event.getReceiver() instanceof ProxiedPlayer) {
event.setCancelled(true);
pluginMessage(((ProxiedPlayer) event.getReceiver()).getUniqueId(), event.getData());
public void on(PluginMessageEvent e) {
if (!e.getTag().equals(TabConstants.PLUGIN_MESSAGE_CHANNEL_NAME)) return;
if (e.getReceiver() instanceof ProxiedPlayer) {
e.setCancelled(true);
pluginMessage(((ProxiedPlayer) e.getReceiver()).getUniqueId(), e.getData());
}
}
@Override
public TabPlayer createPlayer(ProxiedPlayer player) {
return new BungeeTabPlayer(player);
@NotNull
public TabPlayer createPlayer(@NotNull ProxiedPlayer player) {
return new BungeeTabPlayer((BungeePlatform) TAB.getInstance().getPlatform(), player);
}
}
@@ -8,6 +8,7 @@ import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
import java.lang.reflect.Field;
import lombok.SneakyThrows;
import me.neznamy.tab.shared.features.nametags.NameTag;
import me.neznamy.tab.shared.features.redis.RedisSupport;
import me.neznamy.tab.shared.features.redis.feature.RedisTeams;
@@ -18,14 +19,17 @@ import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.features.injection.NettyPipelineInjector;
import me.neznamy.tab.shared.features.redis.RedisPlayer;
import me.neznamy.tab.shared.features.sorting.Sorting;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.connection.InitialHandler;
import net.md_5.bungee.netty.ChannelWrapper;
import net.md_5.bungee.protocol.DefinedPacket;
import net.md_5.bungee.protocol.Protocol;
import net.md_5.bungee.protocol.packet.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.function.Function;
import java.util.function.Supplier;
@@ -38,41 +42,51 @@ import java.util.function.Supplier;
@SuppressWarnings("unchecked")
public class BungeePipelineInjector extends NettyPipelineInjector {
private static @Nullable Field wrapperField;
/** Inaccessible bungee internals */
@Nullable
private static Field wrapperField;
@Nullable
private static Object directionData;
@Nullable
private static Method getId;
static {
try {
(wrapperField = InitialHandler.class.getDeclaredField("ch")).setAccessible(true);
directionData = ReflectionUtils.setAccessible(Protocol.class.getDeclaredField("TO_CLIENT")).get(Protocol.GAME);
getId = ReflectionUtils.setAccessible(Protocol.DirectionData.class.getDeclaredMethod("getId", Class.class, int.class));
} catch (ReflectiveOperationException exception) {
TAB.getInstance().getErrorManager().criticalError("Failed to initialize bungee internal fields", exception);
}
}
/** Packets used by the plugin that must be deserialized and BungeeCord does not do it automatically */
@NotNull
private final Class<? extends DefinedPacket>[] extraPacketClasses = new Class[]{Team.class, ScoreboardDisplay.class, ScoreboardObjective.class};
@NotNull
private final Supplier<DefinedPacket>[] extraPacketSuppliers = new Supplier[]{Team::new, ScoreboardDisplay::new, ScoreboardObjective::new};
@Override
@NotNull
public Function<TabPlayer, ChannelDuplexHandler> getChannelFunction() {
return byteBufDeserialization ? DeserializableBungeeChannelDuplexHandler::new : TabChannelDuplexHandler::new;
}
@Override
protected @Nullable Channel getChannel(@NotNull TabPlayer player) {
@Nullable
@SneakyThrows
protected Channel getChannel(@NotNull TabPlayer player) {
if (wrapperField == null) return null;
final BungeeTabPlayer bungee = (BungeeTabPlayer) player;
try {
return ((ChannelWrapper) wrapperField.get(bungee.getPlayer().getPendingConnection())).getHandle();
} catch (final IllegalAccessException exception) {
TAB.getInstance().getErrorManager().criticalError("Failed to get channel of " + bungee.getPlayer().getName(), exception);
}
return null;
return ((ChannelWrapper) wrapperField.get(((BungeeTabPlayer) player).getPlayer().getPendingConnection())).getHandle();
}
@Override
public void onDisplayObjective(@NotNull TabPlayer player, @NotNull Object packet) {
TAB.getInstance().getFeatureManager().onDisplayObjective(player,
((ScoreboardDisplay) packet).getPosition(), ((ScoreboardDisplay) packet).getName());
((ScoreboardDisplay)packet).getPosition(), ((ScoreboardDisplay) packet).getName());
}
@Override
@@ -110,10 +124,11 @@ public class BungeePipelineInjector extends NettyPipelineInjector {
for (TabPlayer p : TAB.getInstance().getOnlinePlayers()) {
Sorting sorting = TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.SORTING);
String expectedTeam = sorting.getShortTeamName(p);
if (expectedTeam != null && col.contains(p.getName()) &&
if (expectedTeam != null && (col.contains(p.getNickname()) || col.contains(p.getName())) &&
!((NameTag)TAB.getInstance().getNameTagManager()).getDisableChecker().isDisabledPlayer(p) &&
!TAB.getInstance().getNameTagManager().hasTeamHandlingPaused(p) && !packet.getName().equals(expectedTeam)) {
logTeamOverride(packet.getName(), p.getName(), expectedTeam);
col.remove(p.getNickname());
col.remove(p.getName());
}
}
@@ -122,9 +137,9 @@ public class BungeePipelineInjector extends NettyPipelineInjector {
RedisTeams teams = redis.getRedisTeams();
if (teams != null) {
for (RedisPlayer p : redis.getRedisPlayers().values()) {
if (col.contains(p.getName()) && !packet.getName().equals(teams.getTeamNames().get(p))) {
logTeamOverride(packet.getName(), p.getName(), teams.getTeamNames().get(p));
col.remove(p.getName());
if (col.contains(p.getNickname()) && !packet.getName().equals(teams.getTeamNames().get(p))) {
logTeamOverride(packet.getName(), p.getNickname(), teams.getTeamNames().get(p));
col.remove(p.getNickname());
}
}
}
@@ -134,25 +149,42 @@ public class BungeePipelineInjector extends NettyPipelineInjector {
@Override
public void onPlayerInfo(@NotNull TabPlayer receiver, @NotNull Object packet) {
if (packet instanceof PlayerListItemUpdate) {
PlayerListItemUpdate update = (PlayerListItemUpdate) packet;
for (PlayerListItem.Item item : update.getItems()) {
if (update.getActions().contains(PlayerListItemUpdate.Action.UPDATE_DISPLAY_NAME)) {
IChatBaseComponent newDisplayName = TAB.getInstance().getFeatureManager().onDisplayNameChange(receiver, item.getUuid());
if (newDisplayName != null) item.setDisplayName(newDisplayName.toString(receiver.getVersion()));
}
}
} else {
if (packet instanceof PlayerListItem) {
PlayerListItem listItem = (PlayerListItem) packet;
for (PlayerListItem.Item item : listItem.getItems()) {
if (listItem.getAction() == PlayerListItem.Action.UPDATE_DISPLAY_NAME || listItem.getAction() == PlayerListItem.Action.ADD_PLAYER) {
IChatBaseComponent newDisplayName = TAB.getInstance().getFeatureManager().onDisplayNameChange(receiver, item.getUuid());
if (newDisplayName != null) item.setDisplayName(newDisplayName.toString(receiver.getVersion()));
if (newDisplayName != null) item.setDisplayName(((BungeeTabPlayer)receiver).getPlatform().toComponent(newDisplayName, receiver.getVersion()));
}
if (listItem.getAction() == PlayerListItem.Action.UPDATE_LATENCY || listItem.getAction() == PlayerListItem.Action.ADD_PLAYER) {
item.setPing(TAB.getInstance().getFeatureManager().onLatencyChange(receiver, item.getUuid(), item.getPing()));
}
if (listItem.getAction() == PlayerListItem.Action.ADD_PLAYER) {
TAB.getInstance().getFeatureManager().onEntryAdd(receiver, item.getUuid(), item.getUsername());
}
}
} else {
PlayerListItemUpdate update = (PlayerListItemUpdate) packet;
for (PlayerListItem.Item item : update.getItems()) {
if (update.getActions().contains(PlayerListItemUpdate.Action.UPDATE_DISPLAY_NAME)) {
IChatBaseComponent newDisplayName = TAB.getInstance().getFeatureManager().onDisplayNameChange(receiver, item.getUuid());
if (newDisplayName != null) item.setDisplayName(((BungeeTabPlayer)receiver).getPlatform().toComponent(newDisplayName, receiver.getVersion()));
}
if (update.getActions().contains(PlayerListItemUpdate.Action.UPDATE_LATENCY)) {
item.setPing(TAB.getInstance().getFeatureManager().onLatencyChange(receiver, item.getUuid(), item.getPing()));
}
if (update.getActions().contains(PlayerListItemUpdate.Action.ADD_PLAYER)) {
TAB.getInstance().getFeatureManager().onEntryAdd(receiver, item.getUuid(), item.getUsername());
}
}
}
}
@Override
public boolean isLogin(@NotNull Object packet) {
return packet instanceof Login;
}
/**
* Constructs new instance of the feature
*/
@@ -173,12 +205,12 @@ public class BungeePipelineInjector extends NettyPipelineInjector {
* @param player
* player to inject
*/
public DeserializableBungeeChannelDuplexHandler(TabPlayer player) {
public DeserializableBungeeChannelDuplexHandler(@NotNull TabPlayer player) {
super(player);
}
@Override
public void write(ChannelHandlerContext context, Object packet, ChannelPromise channelPromise) {
public void write(@NotNull ChannelHandlerContext context, @NotNull Object packet, @NotNull ChannelPromise channelPromise) {
long time = System.nanoTime();
Object modifiedPacket = packet instanceof ByteBuf ? deserialize((ByteBuf) packet) : packet;
TAB.getInstance().getCPUManager().addTime(TabConstants.Feature.PACKET_DESERIALIZING, TabConstants.CpuUsageCategory.BYTE_BUF, System.nanoTime()-time);
@@ -193,12 +225,18 @@ public class BungeePipelineInjector extends NettyPipelineInjector {
* byte buf to deserialize
* @return deserialized packet or input byte buf if packet is not tracked
*/
private @NotNull Object deserialize(@NotNull ByteBuf buf) {
@NotNull
private Object deserialize(@NotNull ByteBuf buf) {
if (wrapperField == null) return buf;
int marker = buf.readerIndex();
try {
int packetId = buf.readByte();
for (int i=0; i<extraPacketClasses.length; i++) {
if (packetId == ((BungeeTabPlayer)player).getPacketId(extraPacketClasses[i])) {
ChannelWrapper ch = (ChannelWrapper) wrapperField.get(((BungeeTabPlayer) player).getPlayer().getPendingConnection());
if (!ch.getEncodeProtocol().TO_CLIENT.hasPacket(extraPacketClasses[i], ((ProxiedPlayer)player.getPlayer()).getPendingConnection().getVersion())) {
continue;
}
if (packetId == getPacketId(((BungeeTabPlayer)player).getPlayer().getPendingConnection().getVersion(), extraPacketClasses[i])) {
DefinedPacket packet = extraPacketSuppliers[i].get();
packet.read(buf, null, ((ProxiedPlayer)player.getPlayer()).getPendingConnection().getVersion());
buf.release();
@@ -211,5 +249,20 @@ public class BungeePipelineInjector extends NettyPipelineInjector {
buf.readerIndex(marker);
return buf;
}
/**
* Returns packet ID of specified packet on the protocol version
*
* @param protocolVersion
* Protocol version to get packet id for
* @param clazz
* packet class
* @return packet ID
*/
@SneakyThrows
private int getPacketId(int protocolVersion, @NotNull Class<? extends DefinedPacket> clazz) {
if (getId == null) return -1;
return (int) getId.invoke(directionData, clazz, protocolVersion);
}
}
}
@@ -1,54 +1,175 @@
package me.neznamy.tab.platforms.bungeecord;
import com.imaginarycode.minecraft.redisbungee.RedisBungeeAPI;
import de.myzelyam.api.vanish.BungeeVanishAPI;
import lombok.AllArgsConstructor;
import lombok.Getter;
import me.neznamy.tab.platforms.bungeecord.features.BungeeRedisSupport;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.chat.ChatModifier;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.features.injection.PipelineInjector;
import me.neznamy.tab.shared.features.redis.RedisSupport;
import me.neznamy.tab.shared.hook.ViaVersionHook;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.proxy.ProxyPlatform;
import me.neznamy.tab.shared.util.ComponentCache;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.ClickEvent;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import org.bstats.bungeecord.Metrics;
import org.bstats.charts.SimplePie;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.util.stream.Collectors;
/**
* BungeeCord implementation of Platform
*/
@AllArgsConstructor
public class BungeePlatform extends ProxyPlatform {
@NotNull private final BungeeTAB plugin;
/** Flag tracking plugin presence */
@Getter
private final boolean premiumVanish = ProxyServer.getInstance().getPluginManager().getPlugin("PremiumVanish") != null;
/** Component cache for better performance */
private final ComponentCache<IChatBaseComponent, BaseComponent> cache = new ComponentCache<>(1000, this::toComponent0);
@NotNull
private final BungeeTAB plugin;
@Override
public @Nullable RedisSupport getRedisSupport() {
public void loadPlayers() {
ViaVersionHook.getInstance().printProxyWarn();
for (ProxiedPlayer p : ProxyServer.getInstance().getPlayers()) {
TAB.getInstance().addPlayer(new BungeeTabPlayer(this, p));
}
}
@Override
@Nullable
public RedisSupport getRedisSupport() {
if (ReflectionUtils.classExists("com.imaginarycode.minecraft.redisbungee.RedisBungeeAPI") &&
RedisBungeeAPI.getRedisBungeeApi() != null) {
return new RedisBungeeSupport(plugin);
return new BungeeRedisSupport(plugin);
}
return null;
}
@Override
public void sendConsoleMessage(@NotNull IChatBaseComponent message) {
public void logInfo(@NotNull IChatBaseComponent message) {
plugin.getLogger().info(message.toLegacyText());
}
@Override
public void logWarn(@NotNull IChatBaseComponent message) {
plugin.getLogger().warning(EnumChatFormat.RED.getFormat() + message.toLegacyText());
}
@Override
@NotNull
public String getServerVersionInfo() {
return "[BungeeCord] " + plugin.getProxy().getName() + " - " + plugin.getProxy().getVersion();
}
@Override
public void loadPlayers() {
for (ProxiedPlayer p : ProxyServer.getInstance().getPlayers()) {
TAB.getInstance().addPlayer(new BungeeTabPlayer(p));
}
public void registerListener() {
ProxyServer.getInstance().getPluginManager().registerListener(plugin, new BungeeEventListener());
}
@Override
public @Nullable PipelineInjector createPipelineInjector() {
public void registerCommand() {
ProxyServer.getInstance().getPluginManager().registerCommand(plugin, new BungeeTabCommand());
}
@Override
public void startMetrics() {
new Metrics(plugin, 10535).addCustomChart(new SimplePie(TabConstants.MetricsChart.GLOBAL_PLAYER_LIST_ENABLED,
() -> TAB.getInstance().getFeatureManager().isFeatureEnabled(TabConstants.Feature.GLOBAL_PLAYER_LIST) ? "Yes" : "No"));
}
@Override
@NotNull
public File getDataFolder() {
return plugin.getDataFolder();
}
@Override
@Nullable
public PipelineInjector createPipelineInjector() {
return new BungeePipelineInjector();
}
}
@Override
public void registerChannel() {
ProxyServer.getInstance().registerChannel(TabConstants.PLUGIN_MESSAGE_CHANNEL_NAME);
}
@Override
public boolean canSee(@NotNull TabPlayer viewer, @NotNull TabPlayer target) {
//noinspection ConstantConditions
if (premiumVanish && BungeeVanishAPI.canSee(
((BungeeTabPlayer)viewer).getPlayer(),
((BungeeTabPlayer)target).getPlayer())) return true;
return super.canSee(viewer, target);
}
/**
* Converts internal component class to platform's component class. If the component is
* present in the cache, it is taken from it.
*
* @param component
* Component to convert
* @param version
* Game version to convert component for
* @return Converted component
*/
public BaseComponent toComponent(@NotNull IChatBaseComponent component, @NotNull ProtocolVersion version) {
return cache.get(component, version);
}
/**
* Converts internal component class to platform's component class
*
* @param component
* Component to convert
* @param version
* Game version to convert component for
* @return Converted component
*/
private BaseComponent toComponent0(@NotNull IChatBaseComponent component, @NotNull ProtocolVersion version) {
TextComponent textComponent = new TextComponent(component.getText());
ChatModifier modifier = component.getModifier();
if (modifier.getColor() != null) textComponent.setColor(ChatColor.of(
modifier.getColor().toString(version.getMinorVersion() >= 16)));
if (modifier.isBold()) textComponent.setBold(true);
if (modifier.isItalic()) textComponent.setItalic(true);
if (modifier.isObfuscated()) textComponent.setObfuscated(true);
if (modifier.isStrikethrough()) textComponent.setStrikethrough(true);
if (modifier.isUnderlined()) textComponent.setUnderlined(true);
textComponent.setFont(modifier.getFont());
if (modifier.getClickEvent() != null) {
textComponent.setClickEvent(new ClickEvent(
ClickEvent.Action.valueOf(modifier.getClickEvent().getAction().name()),
modifier.getClickEvent().getValue()
));
}
if (!component.getExtra().isEmpty()) textComponent.setExtra(
component.getExtra().stream().map(c -> toComponent(c, version)).collect(Collectors.toList()));
return textComponent;
}
}
@@ -4,11 +4,12 @@ import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.Scoreboard;
import net.md_5.bungee.protocol.packet.ScoreboardDisplay;
import net.md_5.bungee.protocol.packet.ScoreboardObjective;
import net.md_5.bungee.protocol.packet.ScoreboardScore;
import net.md_5.bungee.protocol.packet.Team;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.protocol.Either;
import net.md_5.bungee.protocol.NumberFormat;
import net.md_5.bungee.protocol.packet.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
@@ -19,39 +20,72 @@ import java.util.Collection;
*/
public class BungeeScoreboard extends Scoreboard<BungeeTabPlayer> {
public BungeeScoreboard(BungeeTabPlayer player) {
public BungeeScoreboard(@NotNull BungeeTabPlayer player) {
super(player);
}
@Override
public void setDisplaySlot(@NotNull DisplaySlot slot, @NotNull String objective) {
player.sendPacket(new ScoreboardDisplay((byte)slot.ordinal(), objective));
public void setDisplaySlot0(int slot, @NotNull String objective) {
player.sendPacket(new ScoreboardDisplay(slot, objective));
}
@Override
public void registerObjective0(@NotNull String objectiveName, @NotNull String title, boolean hearts) {
player.sendPacket(new ScoreboardObjective(objectiveName, jsonOrRaw(title, player.getVersion()), hearts ? ScoreboardObjective.HealthDisplay.HEARTS : ScoreboardObjective.HealthDisplay.INTEGER, (byte) 0));
public void registerObjective0(@NotNull String objectiveName, @NotNull String title, int display,
@Nullable IChatBaseComponent numberFormat) {
player.sendPacket(new ScoreboardObjective(
objectiveName,
either(title),
ScoreboardObjective.HealthDisplay.values()[display],
(byte) ObjectiveAction.REGISTER,
numberFormat == null ? null : new NumberFormat(NumberFormat.Type.FIXED,
player.getPlatform().toComponent(numberFormat, player.getVersion()))
));
}
@Override
public void unregisterObjective0(@NotNull String objectiveName) {
player.sendPacket(new ScoreboardObjective(objectiveName, "", null, (byte) 1)); // Empty string to prevent kick on 1.7
player.sendPacket(new ScoreboardObjective(
objectiveName,
either(""), // Empty value instead of null to prevent NPE kick on 1.7
null,
(byte) ObjectiveAction.UNREGISTER,
null
));
}
@Override
public void updateObjective0(@NotNull String objectiveName, @NotNull String title, boolean hearts) {
player.sendPacket(new ScoreboardObjective(objectiveName, jsonOrRaw(title, player.getVersion()), hearts ? ScoreboardObjective.HealthDisplay.HEARTS : ScoreboardObjective.HealthDisplay.INTEGER, (byte) 2));
public void updateObjective0(@NotNull String objectiveName, @NotNull String title, int display,
@Nullable IChatBaseComponent numberFormat) {
player.sendPacket(new ScoreboardObjective(
objectiveName,
either(title),
ScoreboardObjective.HealthDisplay.values()[display],
(byte) ObjectiveAction.UPDATE,
numberFormat == null ? null : new NumberFormat(NumberFormat.Type.FIXED,
player.getPlatform().toComponent(numberFormat, player.getVersion()))
));
}
@Override
public void registerTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix, @NotNull NameVisibility visibility, @NotNull CollisionRule collision, @NotNull Collection<String> players, int options) {
public void registerTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix,
@NotNull NameVisibility visibility, @NotNull CollisionRule collision,
@NotNull Collection<String> players, int options) {
int color = 0;
if (player.getVersion().getMinorVersion() >= 13) {
color = EnumChatFormat.lastColorsOf(prefix).ordinal();
}
player.sendPacket(new Team(name, (byte) 0, jsonOrRaw(name, player.getVersion()),
jsonOrRaw(prefix, player.getVersion()), jsonOrRaw(suffix, player.getVersion()),
visibility.toString(), collision.toString(), color, (byte)options, players.toArray(new String[0])));
player.sendPacket(new Team(
name,
(byte) TeamAction.CREATE,
either(name),
either(prefix),
either(suffix),
visibility.toString(),
collision.toString(),
color,
(byte)options,
players.toArray(new String[0])
));
}
@Override
@@ -60,43 +94,54 @@ public class BungeeScoreboard extends Scoreboard<BungeeTabPlayer> {
}
@Override
public void updateTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix, @NotNull NameVisibility visibility, @NotNull CollisionRule collision, int options) {
public void updateTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix,
@NotNull NameVisibility visibility, @NotNull CollisionRule collision, int options) {
int color = 0;
if (player.getVersion().getMinorVersion() >= 13) {
color = EnumChatFormat.lastColorsOf(prefix).ordinal();
}
player.sendPacket(new Team(name, (byte) 2, jsonOrRaw(name, player.getVersion()),
jsonOrRaw(prefix, player.getVersion()), jsonOrRaw(suffix, player.getVersion()),
visibility.toString(), collision.toString(), color, (byte)options, null));
player.sendPacket(new Team(
name,
(byte) TeamAction.UPDATE,
either(name),
either(prefix),
either(suffix),
visibility.toString(),
collision.toString(),
color,
(byte)options,
null
));
}
/**
* If {@code clientVersion} is &gt;= 1.13, creates a component from given text and returns
* it as a serialized component, which BungeeCord uses.
* <p>
* If {@code clientVersion} is &lt; 1.12, the text is returned
*
* @param text
* Text to convert
* @param clientVersion
* Version of player to convert text for
* @return serialized component for 1.13+ clients, cut string for 1.12-
*/
private String jsonOrRaw(@NotNull String text, @NotNull ProtocolVersion clientVersion) {
if (clientVersion.getMinorVersion() >= 13) {
return IChatBaseComponent.optimizedComponent(text).toString(clientVersion);
@Override
public void setScore0(@NotNull String objective, @NotNull String scoreHolder, int score,
@Nullable IChatBaseComponent displayName, @Nullable IChatBaseComponent numberFormat) {
player.sendPacket(new ScoreboardScore(
scoreHolder,
(byte) ScoreAction.CHANGE,
objective,
score,
displayName == null ? null : player.getPlatform().toComponent(displayName, player.getVersion()),
numberFormat == null ? null : new NumberFormat(NumberFormat.Type.FIXED,
player.getPlatform().toComponent(numberFormat, player.getVersion()))
));
}
@Override
public void removeScore0(@NotNull String objective, @NotNull String scoreHolder) {
if (player.getVersion().getNetworkId() >= ProtocolVersion.V1_20_3.getNetworkId()) {
player.sendPacket(new ScoreboardScoreReset(scoreHolder, objective));
} else {
return text;
player.sendPacket(new ScoreboardScore(scoreHolder, (byte) ScoreAction.REMOVE, objective, 0, null, null));
}
}
@Override
public void setScore0(@NotNull String objective, @NotNull String playerName, int score) {
player.sendPacket(new ScoreboardScore(playerName, (byte) 0, objective, score));
}
@Override
public void removeScore0(@NotNull String objective, @NotNull String playerName) {
player.sendPacket(new ScoreboardScore(playerName, (byte) 1, objective, 0));
private Either<String, BaseComponent> either(@NotNull String text) {
if (player.getVersion().getMinorVersion() >= 13) {
return Either.right(player.getPlatform().toComponent(IChatBaseComponent.optimizedComponent(text), player.getVersion()));
} else {
return Either.left(text);
}
}
}
@@ -1,33 +1,23 @@
package me.neznamy.tab.platforms.bungeecord;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.md_5.bungee.api.plugin.Plugin;
import org.bstats.bungeecord.Metrics;
import org.bstats.charts.SimplePie;
/**
* Main class for BungeeCord platform
* Main class for BungeeCord.
*/
public class BungeeTAB extends Plugin {
@Override
public void onEnable() {
if (!ReflectionUtils.classExists("net.md_5.bungee.protocol.packet.PlayerListItemUpdate")) {
getLogger().info(EnumChatFormat.color("&cThe plugin requires BungeeCord build #1671 and up (or an equivalent fork) to work."));
if (!ReflectionUtils.classExists("net.md_5.bungee.protocol.packet.ScoreboardScoreReset")) {
getLogger().warning(EnumChatFormat.color("&cThe plugin requires BungeeCord build #1774 " +
"(released on November 25th, 2023) and up (or an equivalent fork) to work."));
return;
}
getProxy().registerChannel(TabConstants.PLUGIN_MESSAGE_CHANNEL_NAME);
getProxy().getPluginManager().registerListener(this, new BungeeEventListener());
getProxy().getPluginManager().registerCommand(this, new BungeeTabCommand());
TAB.setInstance(new TAB(new BungeePlatform(this), ProtocolVersion.PROXY, getDataFolder()));
TAB.getInstance().load();
Metrics metrics = new Metrics(this, 10535);
metrics.addCustomChart(new SimplePie(TabConstants.MetricsChart.PERMISSION_SYSTEM, () -> TAB.getInstance().getGroupManager().getPermissionPlugin()));
metrics.addCustomChart(new SimplePie(TabConstants.MetricsChart.GLOBAL_PLAYER_LIST_ENABLED, () -> TAB.getInstance().getFeatureManager().isFeatureEnabled(TabConstants.Feature.GLOBAL_PLAYER_LIST) ? "Yes" : "No"));
TAB.create(new BungeePlatform(this));
}
@Override
@@ -9,8 +9,9 @@ import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.plugin.Command;
import net.md_5.bungee.api.plugin.TabExecutor;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Collections;
public class BungeeTabCommand extends Command implements TabExecutor {
@@ -22,7 +23,7 @@ public class BungeeTabCommand extends Command implements TabExecutor {
}
@Override
public void execute(CommandSender sender, String[] args) {
public void execute(@NotNull CommandSender sender, @NotNull String[] args) {
if (TAB.getInstance().isPluginDisabled()) {
for (String message : TAB.getInstance().getDisabledCommand().execute(args, sender.hasPermission(TabConstants.Permission.COMMAND_RELOAD), sender.hasPermission(TabConstants.Permission.COMMAND_ALL))) {
sender.sendMessage(new TextComponent(EnumChatFormat.color(message)));
@@ -38,11 +39,12 @@ public class BungeeTabCommand extends Command implements TabExecutor {
}
@Override
public Iterable<String> onTabComplete(CommandSender sender, String[] args) {
@NotNull
public Iterable<String> onTabComplete(@NotNull CommandSender sender, @NotNull String[] args) {
TabPlayer p = null;
if (sender instanceof ProxiedPlayer) {
p = TAB.getInstance().getPlayer(((ProxiedPlayer)sender).getUniqueId());
if (p == null) return new ArrayList<>(); //player not loaded correctly
if (p == null) return Collections.emptyList(); //player not loaded correctly
}
return TAB.getInstance().getCommand().complete(p, args);
}
@@ -11,61 +11,51 @@ import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.bossbar.BossBar;
import me.neznamy.tab.shared.platform.BossBar;
import me.neznamy.tab.shared.proxy.ProxyTabPlayer;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.UserConnection;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.chat.ComponentSerializer;
import net.md_5.bungee.connection.InitialHandler;
import net.md_5.bungee.connection.LoginResult;
import net.md_5.bungee.protocol.DefinedPacket;
import net.md_5.bungee.protocol.Property;
import net.md_5.bungee.protocol.Protocol;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Method;
/**
* TabPlayer implementation for BungeeCord
*/
@Getter
public class BungeeTabPlayer extends ProxyTabPlayer {
/** Inaccessible bungee internals */
private static @Nullable Object directionData;
private static @Nullable Method getId;
/** Flag tracking plugin presence */
private static final boolean premiumVanish = ProxyServer.getInstance().getPluginManager().getPlugin("PremiumVanish") != null;
static {
try {
directionData = ReflectionUtils.setAccessible(Protocol.class.getDeclaredField("TO_CLIENT")).get(Protocol.GAME);
getId = ReflectionUtils.setAccessible(directionData.getClass().getDeclaredMethod("getId", Class.class, int.class));
} catch (ReflectiveOperationException e) {
TAB.getInstance().getErrorManager().criticalError("Failed to initialize bungee internal fields", e);
}
}
/** Player's scoreboard */
@Getter private final @NotNull Scoreboard<BungeeTabPlayer> scoreboard = new BungeeScoreboard(this);
@NotNull
private final Scoreboard<BungeeTabPlayer> scoreboard = new BungeeScoreboard(this);
/** Player's tab list based on version */
private final @NotNull TabList tabList1_7 = new BungeeTabList17(this);
private final @NotNull TabList tabList1_8 = new BungeeTabList18(this);
private final @NotNull TabList tabList1_19_3 = new BungeeTabList1193(this);
@NotNull
private final TabList tabList1_7 = new BungeeTabList17(this);
@Getter private final @NotNull BossBar bossBar = new BungeeBossBar(this);
@NotNull
private final TabList tabList1_8 = new BungeeTabList18(this);
@NotNull
private final TabList tabList1_19_3 = new BungeeTabList1193(this);
/** Player's boss bar view */
@NotNull
private final BossBar bossBar = new BungeeBossBar(this);
/**
* Constructs new instance for given player
*
* @param platform
* Server platform
* @param p
* BungeeCord player
*/
public BungeeTabPlayer(@NotNull ProxiedPlayer p) {
super(p, p.getUniqueId(), p.getName(), p.getServer() != null ? p.getServer().getInfo().getName() : "-", -1);
public BungeeTabPlayer(@NotNull BungeePlatform platform, @NotNull ProxiedPlayer p) {
super(platform, p, p.getUniqueId(), p.getName(), p.getServer() != null ? p.getServer().getInfo().getName() : "-", -1);
}
@Override
@@ -78,44 +68,51 @@ public class BungeeTabPlayer extends ProxyTabPlayer {
return getPlayer().getPing();
}
public void sendPacket(@NotNull Object nmsPacket) {
getPlayer().unsafe().sendPacket((DefinedPacket) nmsPacket);
}
@Override
public void sendMessage(@NotNull IChatBaseComponent message) {
getPlayer().sendMessage(ComponentSerializer.parse(message.toString(getVersion())));
getPlayer().sendMessage(getPlatform().toComponent(message, getVersion()));
}
@Override
public @Nullable TabList.Skin getSkin() {
@Nullable
public TabList.Skin getSkin() {
LoginResult loginResult = ((InitialHandler)getPlayer().getPendingConnection()).getLoginProfile();
if (loginResult == null) return null;
Property[] properties = loginResult.getProperties();
if (properties == null || properties.length == 0) return null;
if (properties == null || properties.length == 0) return null; //Offline mode
return new TabList.Skin(properties[0].getValue(), properties[0].getSignature());
}
@Override
public @NotNull ProxiedPlayer getPlayer() {
@NotNull
public ProxiedPlayer getPlayer() {
return (ProxiedPlayer) player;
}
/**
* Returns packet ID for this player of provided packet class
*
* @param clazz
* packet class
* @return packet ID
*/
public int getPacketId(@NotNull Class<? extends DefinedPacket> clazz) {
if (getId == null) return -1;
@Override
public boolean isOnline() {
return getPlayer().isConnected();
}
@Override
public BungeePlatform getPlatform() {
return (BungeePlatform) platform;
}
@Override
public void sendPluginMessage(byte[] message) {
if (!getPlayer().isConnected()) return;
try {
return (int) getId.invoke(directionData, clazz, getPlayer().getPendingConnection().getVersion());
} catch (ReflectiveOperationException e) {
TAB.getInstance().getErrorManager().printError("Failed to get packet id for packet " + clazz +
" with client version " + getPlayer().getPendingConnection().getVersion(), e);
return -1;
getPlayer().getServer().sendData(TabConstants.PLUGIN_MESSAGE_CHANNEL_NAME, message);
} catch (NullPointerException BungeeCordBug) {
// java.lang.NullPointerException: Cannot invoke "net.md_5.bungee.protocol.MinecraftEncoder.getProtocol()" because the return value of "io.netty.channel.ChannelPipeline.get(java.lang.Class)" is null
// at net.md_5.bungee.netty.ChannelWrapper.getEncodeProtocol(ChannelWrapper.java:51)
// at net.md_5.bungee.ServerConnection.sendPacketQueued(ServerConnection.java:48)
// at net.md_5.bungee.ServerConnection.sendData(ServerConnection.java:70)
if (TAB.getInstance().getConfiguration().isDebugMode()) {
TAB.getInstance().getErrorManager().printError("Failed to deliver plugin message to player " + getName() +
" (online = " + getPlayer().isConnected() + ")", BungeeCordBug);
}
}
}
@@ -127,35 +124,50 @@ public class BungeeTabPlayer extends ProxyTabPlayer {
* @return Player's current protocol version
*/
@Override
public @NotNull ProtocolVersion getVersion() {
@NotNull
public ProtocolVersion getVersion() {
return ProtocolVersion.fromNetworkId(getPlayer().getPendingConnection().getVersion());
}
@Override
public boolean isVanished() {
//noinspection ConstantConditions
if (premiumVanish && BungeeVanishAPI.isInvisible(getPlayer())) return true;
try {
//noinspection ConstantConditions
if (getPlatform().isPremiumVanish() && BungeeVanishAPI.isInvisible(getPlayer())) return true;
} catch (IllegalStateException ignored) {
// PV Bug: PremiumVanish must be enabled to use its API
}
return super.isVanished();
}
@Override
public boolean isOnline() {
return getPlayer().isConnected();
@NotNull
public TabList getTabList() {
int version = getPlayer().getPendingConnection().getVersion();
if (version >= ProtocolVersion.V1_19_3.getNetworkId()) return tabList1_19_3;
if (version >= ProtocolVersion.V1_8.getNetworkId()) return tabList1_8;
return tabList1_7;
}
@Override
public @NotNull TabList getTabList() {
return getVersion().getNetworkId() >= ProtocolVersion.V1_19_3.getNetworkId() ?
tabList1_19_3 : getVersion().getMinorVersion() >= 8 ? tabList1_8 : tabList1_7;
}
@Override
public void sendPluginMessage(byte[] message) {
if (getPlayer().getServer() == null) {
TAB.getInstance().getErrorManager().printError("Skipped plugin message send to " + getName() + ", because player is not" +
"connected to any server (message=" + new String(message) + ")");
return;
/**
* Sends packet to the player. If BungeeCord supports 1.20.2+, new packet queue method is used
* to avoid error when sending packet in configuration phase.
*
* @param packet
* Packet to send
*/
public void sendPacket(@NotNull DefinedPacket packet) {
if (!getPlayer().isConnected()) return;
try {
((UserConnection)getPlayer()).sendPacketQueued(packet);
} catch (NullPointerException BungeeCordBug) {
// java.lang.NullPointerException: Cannot invoke "net.md_5.bungee.protocol.MinecraftEncoder.getProtocol()" because the return value of "io.netty.channel.ChannelPipeline.get(java.lang.Class)" is null
// at net.md_5.bungee.netty.ChannelWrapper.getEncodeProtocol(ChannelWrapper.java:51)
// at net.md_5.bungee.UserConnection.sendPacketQueued(UserConnection.java:194)
if (TAB.getInstance().getConfiguration().isDebugMode()) {
TAB.getInstance().getErrorManager().printError("Failed to deliver packet to player " + getName() +
" (online = " + getPlayer().isConnected() + "): " + packet, BungeeCordBug);
}
}
getPlayer().getServer().sendData(TabConstants.PLUGIN_MESSAGE_CHANNEL_NAME, message);
}
}
@@ -1,24 +1,25 @@
package me.neznamy.tab.platforms.bungeecord;
package me.neznamy.tab.platforms.bungeecord.features;
import com.imaginarycode.minecraft.redisbungee.RedisBungeeAPI;
import com.imaginarycode.minecraft.redisbungee.events.PubSubMessageEvent;
import lombok.AllArgsConstructor;
import me.neznamy.tab.platforms.bungeecord.BungeeTAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.features.redis.RedisSupport;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.api.plugin.Plugin;
import net.md_5.bungee.event.EventHandler;
import org.jetbrains.annotations.NotNull;
/**
* Redis implementation for BungeeCord
* RedisBungee implementation for BungeeCord
*/
@AllArgsConstructor
public class RedisBungeeSupport extends RedisSupport implements Listener {
public class BungeeRedisSupport extends RedisSupport implements Listener {
/** Plugin reference for registering listener */
private final @NotNull Plugin plugin;
@NotNull
private final BungeeTAB plugin;
@EventHandler
public void onMessage(@NotNull PubSubMessageEvent e) {
@@ -0,0 +1,74 @@
package me.neznamy.tab.platforms.bungeecord.tablist;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bungeecord.BungeeTabPlayer;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.md_5.bungee.UserConnection;
import net.md_5.bungee.protocol.Property;
import net.md_5.bungee.protocol.packet.PlayerListHeaderFooter;
import net.md_5.bungee.protocol.packet.PlayerListItem.Item;
import net.md_5.bungee.tab.ServerUnique;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
import java.util.UUID;
public abstract class BungeeTabList implements TabList {
/** Player this TabList belongs to */
@NotNull
protected final BungeeTabPlayer player;
/** Pointer to UUIDs in player's TabList */
private final Collection<UUID> uuids;
@SneakyThrows
@SuppressWarnings("unchecked")
public BungeeTabList(@NotNull BungeeTabPlayer player) {
this.player = player;
uuids = (Collection<UUID>) ReflectionUtils.getField(ServerUnique.class, "uuids").get(((UserConnection)player.getPlayer()).getTabListHandler());
}
@Override
public void setPlayerListHeaderFooter(@NotNull IChatBaseComponent header, @NotNull IChatBaseComponent footer) {
player.sendPacket(new PlayerListHeaderFooter(
player.getPlatform().toComponent(header, player.getVersion()),
player.getPlatform().toComponent(footer, player.getVersion())
));
}
@NotNull
public Item item(@NotNull UUID id) {
Item item = new Item();
item.setUuid(id);
return item;
}
@NotNull
public Item entryToItem(Entry entry) {
Item item = item(entry.getUniqueId());
if (entry.getDisplayName() != null) {
item.setDisplayName(player.getPlatform().toComponent(entry.getDisplayName(), player.getVersion()));
}
item.setGamemode(entry.getGameMode());
item.setListed(true);
item.setPing(entry.getLatency());
if (entry.getSkin() != null) {
item.setProperties(new Property[]{new Property(TEXTURES_PROPERTY, entry.getSkin().getValue(), entry.getSkin().getSignature())});
} else {
item.setProperties(new Property[0]);
}
item.setUsername(entry.getName());
return item;
}
public void addUuid(UUID id) {
uuids.add(id);
}
public void removeUuid(UUID id) {
uuids.remove(id);
}
}
@@ -1,11 +1,7 @@
package me.neznamy.tab.platforms.bungeecord.tablist;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.platforms.bungeecord.BungeeTabPlayer;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.TabList;
import net.md_5.bungee.UserConnection;
import net.md_5.bungee.protocol.Property;
import net.md_5.bungee.protocol.packet.PlayerListItem.Item;
import net.md_5.bungee.protocol.packet.PlayerListItemRemove;
import net.md_5.bungee.protocol.packet.PlayerListItemUpdate;
@@ -18,26 +14,25 @@ import java.util.UUID;
/**
* TabList handler for 1.19.3+ players using the new tab list packets.
* Because BungeeCord does not have a TabList API, we need to use packets.
* They are sent using an internal BungeeCord method that keeps track of them,
* so they are removed on server switch to secure parity with Velocity.
*/
@RequiredArgsConstructor
public class BungeeTabList1193 implements TabList {
public class BungeeTabList1193 extends BungeeTabList {
/** Player this TabList belongs to */
private final BungeeTabPlayer player;
public BungeeTabList1193(@NotNull BungeeTabPlayer player) {
super(player);
}
@Override
public void removeEntry(@NotNull UUID entry) {
removeUuid(entry);
PlayerListItemRemove remove = new PlayerListItemRemove();
remove.setUuids(new UUID[]{entry});
((UserConnection)player.getPlayer()).getTabListHandler().onUpdate(remove);
player.sendPacket(remove);
}
@Override
public void updateDisplayName(@NotNull UUID entry, @Nullable IChatBaseComponent displayName) {
Item item = item(entry);
item.setDisplayName(displayName == null ? null : displayName.toString(player.getVersion()));
if (displayName != null) item.setDisplayName(player.getPlatform().toComponent(displayName, player.getVersion()));
sendPacket(EnumSet.of(PlayerListItemUpdate.Action.UPDATE_DISPLAY_NAME), item);
}
@@ -57,35 +52,14 @@ public class BungeeTabList1193 implements TabList {
@Override
public void addEntry(@NotNull Entry entry) {
Item item = item(entry.getUniqueId());
if (entry.getDisplayName() != null) item.setDisplayName(entry.getDisplayName().toString(player.getVersion()));
item.setGamemode(entry.getGameMode());
item.setListed(true);
item.setPing(entry.getLatency());
if (entry.getSkin() != null) {
item.setProperties(new Property[]{new Property(TEXTURES_PROPERTY, entry.getSkin().getValue(), entry.getSkin().getSignature())});
} else {
item.setProperties(new Property[0]);
}
item.setUsername(entry.getName());
sendPacket(EnumSet.allOf(PlayerListItemUpdate.Action.class), item);
}
public @NotNull Item item(@NotNull UUID id) {
Item item = new Item();
item.setUuid(id);
return item;
addUuid(entry.getUniqueId());
sendPacket(EnumSet.allOf(PlayerListItemUpdate.Action.class), entryToItem(entry));
}
private void sendPacket(@NotNull EnumSet<PlayerListItemUpdate.Action> actions, @NotNull Item item) {
PlayerListItemUpdate packet = new PlayerListItemUpdate();
packet.setActions(actions);
packet.setItems(new Item[]{item});
((UserConnection)player.getPlayer()).getTabListHandler().onUpdate(packet);
}
@Override
public void setPlayerListHeaderFooter(@NotNull IChatBaseComponent header, @NotNull IChatBaseComponent footer) {
player.getPlayer().setTabHeader(header.toBungeeComponent(player.getVersion()), footer.toBungeeComponent(player.getVersion()));
player.sendPacket(packet);
}
}
@@ -1,10 +1,8 @@
package me.neznamy.tab.platforms.bungeecord.tablist;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.platforms.bungeecord.BungeeTabPlayer;
import me.neznamy.tab.shared.Limitations;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.TabList;
import net.md_5.bungee.UserConnection;
import net.md_5.bungee.protocol.packet.PlayerListItem;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -19,24 +17,26 @@ import java.util.UUID;
* names are changed by removing old entry and adding a new one, which
* requires further tracking than just the UUID.<p>
* Because BungeeCord does not have a TabList API, we need to use packets.
* They are sent using an internal BungeeCord method that keeps track of them,
* so they are removed on server switch to secure parity with Velocity.
* While BungeeCord itself does not support 1.7, some of its forks do.
* This was tested on FlameCord fork.
*/
@RequiredArgsConstructor
public class BungeeTabList17 implements TabList {
/** Player this TabList belongs to */
private final BungeeTabPlayer player;
public class BungeeTabList17 extends BungeeTabList {
/** Because entries are identified by names and not uuids on 1.7 */
@NotNull
private final Map<UUID, String> userNames = new HashMap<>();
@NotNull
private final Map<UUID, String> displayNames = new HashMap<>();
public BungeeTabList17(@NotNull BungeeTabPlayer player) {
super(player);
}
@Override
public void removeEntry(@NotNull UUID entry) {
if (!displayNames.containsKey(entry)) return; // Entry not tracked by TAB
removeUuid(entry);
update(PlayerListItem.Action.REMOVE_PLAYER, createItem(null, displayNames.get(entry), 0));
// Remove from map
@@ -62,8 +62,10 @@ public class BungeeTabList17 implements TabList {
@Override
public void addEntry(@NotNull Entry entry) {
addUuid(entry.getUniqueId());
String displayNameString = entry.getDisplayName() == null ? String.valueOf(entry.getName()) : entry.getDisplayName().toLegacyText();
if (displayNameString.length() > 16) displayNameString = displayNameString.substring(0, 16); // 16 character limit
if (displayNameString.length() > Limitations.MAX_DISPLAY_NAME_LENGTH_1_7)
displayNameString = displayNameString.substring(0, Limitations.MAX_DISPLAY_NAME_LENGTH_1_7);
update(PlayerListItem.Action.ADD_PLAYER, createItem(entry.getName(), displayNameString, entry.getLatency()));
// Add to map
@@ -71,23 +73,23 @@ public class BungeeTabList17 implements TabList {
displayNames.put(entry.getUniqueId(), displayNameString);
}
@Override
public void setPlayerListHeaderFooter(@NotNull IChatBaseComponent header, @NotNull IChatBaseComponent footer) {
// Not available on 1.7
}
private void update(@NotNull PlayerListItem.Action action, @NotNull PlayerListItem.Item item) {
PlayerListItem packet = new PlayerListItem();
packet.setAction(action);
packet.setItems(new PlayerListItem.Item[]{item});
((UserConnection)player.getPlayer()).getTabListHandler().onUpdate(packet);
player.sendPacket(packet);
}
private PlayerListItem.Item createItem(@Nullable String username, @NotNull String displayName, int latency) {
PlayerListItem.Item item = new PlayerListItem.Item();
item.setUsername(username);
item.setDisplayName(displayName);
item.setPing(latency);
item.setDisplayName(player.getPlatform().toComponent(IChatBaseComponent.optimizedComponent(displayName), player.getVersion()));
return item;
}
@Override
public void setPlayerListHeaderFooter(@NotNull IChatBaseComponent header, @NotNull IChatBaseComponent footer) {
// Added in 1.8
}
}
@@ -1,11 +1,7 @@
package me.neznamy.tab.platforms.bungeecord.tablist;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.platforms.bungeecord.BungeeTabPlayer;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.TabList;
import net.md_5.bungee.UserConnection;
import net.md_5.bungee.protocol.Property;
import net.md_5.bungee.protocol.packet.PlayerListItem;
import net.md_5.bungee.protocol.packet.PlayerListItem.Item;
import org.jetbrains.annotations.NotNull;
@@ -16,24 +12,23 @@ import java.util.UUID;
/**
* TabList handler for players between 1.8 and 1.19.2.<p>
* Because BungeeCord does not have a TabList API, we need to use packets.
* They are sent using an internal BungeeCord method that keeps track of them,
* so they are removed on server switch to secure parity with Velocity.
*/
@RequiredArgsConstructor
public class BungeeTabList18 implements TabList {
public class BungeeTabList18 extends BungeeTabList {
/** Player this TabList belongs to */
private final BungeeTabPlayer player;
public BungeeTabList18(@NotNull BungeeTabPlayer player) {
super(player);
}
@Override
public void removeEntry(@NotNull UUID entry) {
removeUuid(entry);
sendPacket(PlayerListItem.Action.REMOVE_PLAYER, item(entry));
}
@Override
public void updateDisplayName(@NotNull UUID entry, @Nullable IChatBaseComponent displayName) {
Item item = item(entry);
item.setDisplayName(displayName == null ? null : displayName.toString(player.getVersion()));
if (displayName != null) item.setDisplayName(player.getPlatform().toComponent(displayName, player.getVersion()));
sendPacket(PlayerListItem.Action.UPDATE_DISPLAY_NAME, item);
}
@@ -53,34 +48,14 @@ public class BungeeTabList18 implements TabList {
@Override
public void addEntry(@NotNull Entry entry) {
Item item = item(entry.getUniqueId());
if (entry.getDisplayName() != null) item.setDisplayName(entry.getDisplayName().toString(player.getVersion()));
item.setGamemode(entry.getGameMode());
item.setPing(entry.getLatency());
if (entry.getSkin() != null) {
item.setProperties(new Property[]{new Property(TEXTURES_PROPERTY, entry.getSkin().getValue(), entry.getSkin().getSignature())});
} else {
item.setProperties(new Property[0]);
}
item.setUsername(entry.getName());
sendPacket(PlayerListItem.Action.ADD_PLAYER, item);
}
public @NotNull Item item(@NotNull UUID id) {
Item item = new Item();
item.setUuid(id);
return item;
addUuid(entry.getUniqueId());
sendPacket(PlayerListItem.Action.ADD_PLAYER, entryToItem(entry));
}
private void sendPacket(@NotNull PlayerListItem.Action action, @NotNull Item item) {
PlayerListItem packet = new PlayerListItem();
packet.setAction(action);
packet.setItems(new Item[]{item});
((UserConnection)player.getPlayer()).getTabListHandler().onUpdate(packet);
}
@Override
public void setPlayerListHeaderFooter(@NotNull IChatBaseComponent header, @NotNull IChatBaseComponent footer) {
player.getPlayer().setTabHeader(header.toBungeeComponent(player.getVersion()), footer.toBungeeComponent(player.getVersion()));
player.sendPacket(packet);
}
}
+11 -63
View File
@@ -1,39 +1,4 @@
// Versions that don't require any code changes:
// 1.19.3, 1.19.4, 1.20, 1.20.1
val minecraftVersion = "1.20.1"
val v114 = "0.28.5+1.14"
val v115 = "0.28.5+1.15"
val v116 = "0.42.0+1.16"
val fabricApiVersions = mapOf(
"1.20.1" to "0.83.0+1.20.1",
"1.20" to "0.83.0+1.20",
"1.19.4" to "0.77.0+1.19.4",
"1.19.3" to "0.76.0+1.19.3",
"1.19.2" to "0.76.0+1.19.2",
"1.19.1" to "0.58.5+1.19.1",
"1.19" to "0.58.0+1.19",
"1.18.2" to "0.76.0+1.18.2",
"1.18.1" to "0.46.6+1.18",
"1.18" to "0.46.6+1.18",
"1.17.1" to "0.46.1+1.17",
"1.17" to "0.46.1+1.17",
"1.16.5" to v116,
"1.16.4" to v116,
"1.16.3" to v116,
"1.16.2" to v116,
"1.16.1" to v116,
"1.16" to v116,
"1.15.2" to v115,
"1.15.1" to v115,
"1.15" to v115,
"1.14.4" to v114,
"1.14.3" to v114,
"1.14.2" to v114,
"1.14.1" to v114,
"1.14" to v114,
)
val minecraftVersion = "1.20.3"
plugins {
id("fabric-loom")
@@ -41,38 +6,21 @@ plugins {
repositories {
// Gradle doesn't support combining settings and project repositories, so we have to re-declare all the settings repos we need
maven("https://repo.kryptonmc.org/releases")
maven("https://jitpack.io") // YamlAssist
maven("https://repo.opencollab.dev/maven-snapshots/")
maven("https://repo.viaversion.com/")
maven("https://oss.sonatype.org/content/repositories/snapshots")
}
dependencies {
implementation(projects.shared)
api(projects.shared)
minecraft("com.mojang:minecraft:$minecraftVersion")
mappings(loom.officialMojangMappings())
modImplementation("net.fabricmc", "fabric-loader", "0.14.17")
modImplementation("me.lucko", "fabric-permissions-api", "0.2-SNAPSHOT")
modImplementation(fabricApi.module("fabric-api-base", fabricApiVersions[minecraftVersion]))
modImplementation(fabricApi.module("fabric-lifecycle-events-v1", fabricApiVersions[minecraftVersion]))
modImplementation(fabricApi.module("fabric-networking-api-v1", fabricApiVersions[minecraftVersion]))
modImplementation(fabricApi.module("fabric-entity-events-v1", fabricApiVersions[minecraftVersion]))
modImplementation(fabricApi.module("fabric-command-api-v${if (minecraftVersion.split(".")[1].toInt() >= 19) "2" else "1"}", fabricApiVersions[minecraftVersion]))
}
loom {
accessWidenerPath.set(file("src/main/resources/tab.accesswidener"))
}
tasks {
compileJava {
options.release.set(17)
}
validateAccessWidener {
// We don't want to validate the access wideners, as we have wideners for multiple versions, and the validation task will
// fail, as the classes for the old wideners don't exist in the new versions and vice versa.
enabled = false
}
}
modImplementation("me.lucko:fabric-permissions-api:0.2-SNAPSHOT")
modImplementation("net.fabricmc:fabric-loader:0.14.17")
val version = "0.90.4+1.20.3"
modImplementation(fabricApi.module("fabric-api-base", version))
modImplementation(fabricApi.module("fabric-lifecycle-events-v1", version))
modImplementation(fabricApi.module("fabric-networking-api-v1", version))
modImplementation(fabricApi.module("fabric-entity-events-v1", version))
}
@@ -1,53 +1,58 @@
package me.neznamy.tab.platforms.fabric;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.api.bossbar.BarColor;
import me.neznamy.tab.api.bossbar.BarStyle;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.bossbar.BossBar;
import me.neznamy.tab.shared.platform.BossBar;
import net.minecraft.server.level.ServerBossEvent;
import net.minecraft.world.BossEvent;
import net.minecraft.world.BossEvent.BossBarColor;
import net.minecraft.world.BossEvent.BossBarOverlay;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
@RequiredArgsConstructor
public class FabricBossBar implements BossBar {
@NotNull
private final FabricTabPlayer player;
@NotNull
private final Map<UUID, ServerBossEvent> bars = new HashMap<>();
@Override
public void create(@NotNull UUID id, @NotNull String title, float progress, @NotNull BarColor color, @NotNull BarStyle style) {
ServerBossEvent bar = new ServerBossEvent(
FabricTAB.getInstance().toComponent(IChatBaseComponent.optimizedComponent(title), player.getVersion()),
BossEvent.BossBarColor.valueOf(color.name()),
BossEvent.BossBarOverlay.valueOf(style.name())
player.getPlatform().toComponent(IChatBaseComponent.optimizedComponent(title), player.getVersion()),
BossBarColor.valueOf(color.name()),
BossBarOverlay.valueOf(style.name())
);
bar.setProgress(progress);
bar.setProgress(progress); // Somehow the compiled method name is same despite method being renamed in 1.17
bars.put(id, bar);
bar.addPlayer(player.getPlayer());
}
@Override
public void update(@NotNull UUID id, @NotNull String title) {
bars.get(id).setName(FabricTAB.getInstance().toComponent(IChatBaseComponent.optimizedComponent(title), player.getVersion()));
bars.get(id).setName(player.getPlatform().toComponent(IChatBaseComponent.optimizedComponent(title), player.getVersion()));
}
@Override
public void update(@NotNull UUID id, float progress) {
bars.get(id).setProgress(progress);
bars.get(id).setProgress(progress); // Somehow the compiled method name is same despite method being renamed in 1.17
}
@Override
public void update(@NotNull UUID id, @NotNull BarStyle style) {
bars.get(id).setOverlay(BossEvent.BossBarOverlay.valueOf(style.name()));
bars.get(id).setOverlay(BossBarOverlay.valueOf(style.name()));
}
@Override
public void update(@NotNull UUID id, @NotNull BarColor color) {
bars.get(id).setColor(BossEvent.BossBarColor.valueOf(color.name()));
bars.get(id).setColor(BossBarColor.valueOf(color.name()));
}
@Override
@@ -0,0 +1,106 @@
package me.neznamy.tab.platforms.fabric;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import me.neznamy.tab.shared.backend.EntityData;
import me.neznamy.tab.shared.backend.Location;
import me.neznamy.tab.shared.backend.entityview.EntityView;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.minecraft.network.protocol.game.ClientboundMoveEntityPacket;
import net.minecraft.network.protocol.game.ClientboundRemoveEntitiesPacket;
import net.minecraft.network.protocol.game.ClientboundTeleportEntityPacket;
import net.minecraft.world.entity.decoration.ArmorStand;
import org.jetbrains.annotations.NotNull;
import java.util.UUID;
@RequiredArgsConstructor
public class FabricEntityView implements EntityView {
private final FabricTabPlayer player;
@NotNull
private static final ArmorStand dummyEntity = new ArmorStand(null, 0, 0, 0);
@Override
public void spawnEntity(int entityId, @NotNull UUID id, @NotNull Object entityType, @NotNull Location location,
@NotNull EntityData data) {
player.sendPacket(FabricTAB.getVersion().spawnEntity(entityId, id, entityType, location));
updateEntityMetadata(entityId, data);
}
@Override
public void updateEntityMetadata(int entityId, @NotNull EntityData data) {
player.sendPacket(FabricTAB.getVersion().newEntityMetadata(entityId, data));
}
@Override
public void teleportEntity(int entityId, @NotNull Location location) {
dummyEntity.setId(entityId);
dummyEntity.setPos(location.getX(), location.getY(), location.getZ());
player.sendPacket(new ClientboundTeleportEntityPacket(dummyEntity));
}
@Override
public void destroyEntities(int... entities) {
FabricTAB.getVersion().destroyEntities(player, entities);
}
@Override
public boolean isDestroyPacket(@NotNull Object packet) {
return packet instanceof ClientboundRemoveEntitiesPacket;
}
@Override
public boolean isTeleportPacket(@NotNull Object packet) {
return packet instanceof ClientboundTeleportEntityPacket;
}
@Override
public boolean isNamedEntitySpawnPacket(@NotNull Object packet) {
return FabricTAB.getVersion().isSpawnPlayerPacket(packet);
}
@Override
public boolean isMovePacket(@NotNull Object packet) {
return packet instanceof ClientboundMoveEntityPacket;
}
@Override
public boolean isLookPacket(@NotNull Object packet) {
return packet instanceof ClientboundMoveEntityPacket.Rot;
}
@Override
@SneakyThrows
public int getTeleportEntityId(@NotNull Object teleportPacket) {
// Reflection because on 1.16.5 there is no getter and per-version code would only add unnecessary code
return ReflectionUtils.getFields(ClientboundTeleportEntityPacket.class, int.class).get(0).getInt(teleportPacket);
}
@Override
@SneakyThrows
public int getMoveEntityId(@NotNull Object movePacket) {
return (int) ReflectionUtils.getFields(ClientboundMoveEntityPacket.class, int.class).get(0).get(movePacket);
}
@Override
public int getSpawnedPlayer(@NotNull Object playerSpawnPacket) {
return FabricTAB.getVersion().getSpawnedPlayer(playerSpawnPacket);
}
@Override
public int[] getDestroyedEntities(@NotNull Object destroyPacket) {
return FabricTAB.getVersion().getDestroyedEntities(destroyPacket);
}
@Override
public boolean isBundlePacket(@NotNull Object packet) {
return FabricTAB.getVersion().isBundlePacket(packet);
}
@Override
public Iterable<Object> getPackets(@NotNull Object bundlePacket) {
return FabricTAB.getVersion().getPackets(bundlePacket);
}
}
@@ -1,23 +1,37 @@
package me.neznamy.tab.platforms.fabric;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.platform.EventListener;
import me.neznamy.tab.shared.platform.TabPlayer;
import net.fabricmc.fabric.api.entity.event.v1.ServerEntityWorldChangeEvents;
import net.fabricmc.fabric.api.entity.event.v1.ServerPlayerEvents;
import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents;
import net.minecraft.server.level.ServerPlayer;
import org.jetbrains.annotations.NotNull;
public class FabricEventListener extends EventListener<ServerPlayer> {
public void register() {
ServerPlayConnectionEvents.JOIN.register((connection, $, $$) -> join(connection.player));
ServerPlayConnectionEvents.DISCONNECT.register((connection, $) -> quit(connection.player.getUUID()));
ServerPlayerEvents.AFTER_RESPAWN.register((oldPlayer, newPlayer, alive) -> replacePlayer(oldPlayer.getUUID(), newPlayer));
ServerPlayConnectionEvents.JOIN.register((connection, $, $$) -> join(connection.player));
//TODO command preprocess
if (ProtocolVersion.fromFriendlyName(FabricTAB.getVersion().getServerVersion()).getMinorVersion() >= 16) {
// Added in 1.16
ServerPlayerEvents.AFTER_RESPAWN.register(
(oldPlayer, newPlayer, alive) -> {
replacePlayer(newPlayer.getUUID(), newPlayer);
// respawning from death & taking end portal in the end do not call world change event
worldChange(newPlayer.getUUID(), FabricTAB.getVersion().getName(FabricTAB.getVersion().getLevel(newPlayer)));
});
ServerEntityWorldChangeEvents.AFTER_PLAYER_CHANGE_WORLD.register(
(player, origin, destination) -> worldChange(player.getUUID(), FabricTAB.getVersion().getName(destination)));
} // TODO else
}
@Override
public TabPlayer createPlayer(ServerPlayer player) {
return new FabricTabPlayer(player);
@NotNull
public TabPlayer createPlayer(@NotNull ServerPlayer player) {
return new FabricTabPlayer((FabricPlatform) TAB.getInstance().getPlatform(), player);
}
//TODO world switch
}
@@ -1,16 +0,0 @@
package me.neznamy.tab.platforms.fabric;
import me.neznamy.tab.api.placeholder.PlaceholderManager;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.placeholders.UniversalPlaceholderRegistry;
import net.minecraft.server.level.ServerPlayer;
import org.jetbrains.annotations.NotNull;
public class FabricPlaceholderRegistry extends UniversalPlaceholderRegistry {
@Override
public void registerPlaceholders(@NotNull PlaceholderManager manager) {
manager.registerPlayerPlaceholder(TabConstants.Placeholder.HEALTH, 100, p -> (int) Math.ceil(((ServerPlayer) p.getPlayer()).getHealth()));
super.registerPlaceholders(manager);
}
}
@@ -1,6 +1,13 @@
package me.neznamy.tab.platforms.fabric;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import me.lucko.fabric.api.permissions.v0.Permissions;
import me.neznamy.tab.platforms.fabric.features.FabricNameTagX;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.backend.BackendPlatform;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.features.injection.PipelineInjector;
@@ -8,59 +15,148 @@ import me.neznamy.tab.shared.features.nametags.NameTag;
import me.neznamy.tab.shared.features.types.TabFeature;
import me.neznamy.tab.shared.placeholders.expansion.EmptyTabExpansion;
import me.neznamy.tab.shared.placeholders.expansion.TabExpansion;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.fabricmc.fabric.api.networking.v1.PlayerLookup;
import net.minecraft.SharedConstants;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.network.chat.Component;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerPlayer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public final class FabricPlatform implements BackendPlatform {
import java.io.File;
/**
* Platform implementation for Fabric
*/
@RequiredArgsConstructor
@Getter
public class FabricPlatform implements BackendPlatform {
private static final boolean fabricPermissionsApi = FabricLoader.getInstance().isModLoaded("fabric-permissions-api-v0");
private final MinecraftServer server;
@Override
public void registerUnknownPlaceholder(@NotNull String identifier) {
TAB.getInstance().getPlaceholderManager().registerServerPlaceholder(identifier, -1, () -> identifier);
registerDummyPlaceholder(identifier);
}
@Override
public void loadPlayers() {
for (ServerPlayer player : PlayerLookup.all(FabricTAB.getInstance().getServer())) {
TAB.getInstance().addPlayer(new FabricTabPlayer(player));
for (ServerPlayer player : PlayerLookup.all(server)) {
TAB.getInstance().addPlayer(new FabricTabPlayer(this, player));
}
}
@Override
public void registerPlaceholders() {
new FabricPlaceholderRegistry().registerPlaceholders(TAB.getInstance().getPlaceholderManager());
@NotNull
public PipelineInjector createPipelineInjector() {
return FabricTAB.getVersion().createPipelineInjector();
}
@Override
public @NotNull NameTag getUnlimitedNameTags() {
return new NameTag();
@NotNull
public NameTag getUnlimitedNameTags() {
return new FabricNameTagX();
}
@Override
public @Nullable PipelineInjector createPipelineInjector() {
return null;
}
@Override
public @NotNull TabExpansion createTabExpansion() {
@NotNull
public TabExpansion createTabExpansion() {
return new EmptyTabExpansion();
}
@Override
public @Nullable TabFeature getPerWorldPlayerList() {
@Nullable
public TabFeature getPerWorldPlayerList() {
return null;
}
@Override
public void sendConsoleMessage(@NotNull IChatBaseComponent message) {
MinecraftServer.LOGGER.info("[TAB] " + message.toRawText());
@SneakyThrows
public void logInfo(@NotNull IChatBaseComponent message) {
Object logger = getLogger();
logger.getClass().getMethod("info", String.class).invoke(logger, message.toRawText());
}
@Override
@SneakyThrows
public void logWarn(@NotNull IChatBaseComponent message) {
Object logger = getLogger();
logger.getClass().getMethod("warn", String.class).invoke(logger, message.toRawText());
}
@SneakyThrows
private Object getLogger() {
Class<?> loggerClass;
if (getServerVersion().getNetworkId() >= ProtocolVersion.V1_18_2.getNetworkId()) {
loggerClass = Class.forName("org.slf4j.Logger");
} else {
loggerClass = Class.forName("org.apache.logging.log4j.Logger");
}
return ReflectionUtils.getFields(MinecraftServer.class, loggerClass).get(0).get(null);
}
@Override
@NotNull
public String getServerVersionInfo() {
return "[Fabric] " + SharedConstants.getCurrentVersion().getName();
return "[Fabric] " + FabricTAB.getVersion().getServerVersion();
}
@Override
public void registerListener() {
new FabricEventListener().register();
}
@Override
public void registerCommand() {
// Event listener must be registered in main class
}
@Override
public void startMetrics() {
// Not available
}
@Override
@NotNull
public ProtocolVersion getServerVersion() {
return ProtocolVersion.fromFriendlyName(FabricTAB.getVersion().getServerVersion());
}
@Override
@NotNull
public File getDataFolder() {
return FabricLoader.getInstance().getConfigDir().resolve(TabConstants.PLUGIN_ID).toFile();
}
/**
* Converts internal component class to platform's component class
*
* @param component
* Component to convert
* @param version
* Game version to convert component for
* @return Converted component
*/
public Component toComponent(@NotNull IChatBaseComponent component, @NotNull ProtocolVersion version) {
return FabricTAB.getVersion().deserialize(component.toString(version));
}
@Override
public double getTPS() {
return -1; // Not available
}
@Override
public double getMSPT() {
return FabricTAB.getVersion().getMSPT();
}
public boolean hasPermission(@NotNull CommandSourceStack source, @NotNull String permission) {
if (source.hasPermission(4)) return true;
return fabricPermissionsApi && Permissions.check(source, permission);
}
}
@@ -1,136 +1,124 @@
package me.neznamy.tab.platforms.fabric;
import java.util.Collection;
import java.util.Objects;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.Scoreboard;
import net.minecraft.ChatFormatting;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.game.ClientboundSetDisplayObjectivePacket;
import net.minecraft.network.protocol.game.ClientboundSetObjectivePacket;
import net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket;
import net.minecraft.network.protocol.game.ClientboundSetScorePacket;
import net.minecraft.server.ServerScoreboard;
import net.minecraft.world.scores.Objective;
import net.minecraft.world.scores.PlayerTeam;
import net.minecraft.world.scores.Team;
import net.minecraft.world.scores.criteria.ObjectiveCriteria;
import net.minecraft.world.scores.criteria.ObjectiveCriteria.RenderType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
public class FabricScoreboard extends Scoreboard<FabricTabPlayer> {
private final @NotNull net.minecraft.world.scores.Scoreboard dummyScoreboard = new net.minecraft.world.scores.Scoreboard();
private final @NotNull Component EMPTY_COMPONENT = Objects.requireNonNull(Component.Serializer.fromJson(IChatBaseComponent.EMPTY_COMPONENT));
@NotNull
private static final net.minecraft.world.scores.Scoreboard dummyScoreboard = new net.minecraft.world.scores.Scoreboard();
private final Map<String, Objective> objectives = new HashMap<>();
public FabricScoreboard(FabricTabPlayer player) {
super(player);
}
@Override
public void setDisplaySlot(@NotNull DisplaySlot slot, @NotNull String objective) {
player.sendPacket(
new ClientboundSetDisplayObjectivePacket(
slot.ordinal(),
new Objective(
dummyScoreboard,
objective,
ObjectiveCriteria.DUMMY,
EMPTY_COMPONENT,
ObjectiveCriteria.RenderType.INTEGER
)
)
);
public void setDisplaySlot0(int slot, @NotNull String objective) {
player.sendPacket(FabricTAB.getVersion().setDisplaySlot(slot, objectives.get(objective)));
}
@Override
public void registerObjective0(@NotNull String objectiveName, @NotNull String title, boolean hearts) {
player.sendPacket(
new ClientboundSetObjectivePacket(
new Objective(
dummyScoreboard,
objectiveName,
ObjectiveCriteria.DUMMY,
FabricTAB.getInstance().toComponent(IChatBaseComponent.optimizedComponent(title), player.getVersion()),
hearts ? ObjectiveCriteria.RenderType.HEARTS : ObjectiveCriteria.RenderType.INTEGER
),
0
)
public void registerObjective0(@NotNull String objectiveName, @NotNull String title, int display,
@Nullable IChatBaseComponent numberFormat) {
Objective obj = FabricTAB.getVersion().newObjective(
objectiveName,
toComponent(title),
RenderType.values()[display],
numberFormat == null ? null : toComponent(numberFormat)
);
objectives.put(objectiveName, obj);
player.sendPacket(new ClientboundSetObjectivePacket(obj, ObjectiveAction.REGISTER));
}
@Override
public void unregisterObjective0(@NotNull String objectiveName) {
player.sendPacket(
new ClientboundSetObjectivePacket(
new Objective(
dummyScoreboard,
objectiveName,
ObjectiveCriteria.DUMMY,
EMPTY_COMPONENT,
ObjectiveCriteria.RenderType.INTEGER
),
1
)
);
player.sendPacket(new ClientboundSetObjectivePacket(objectives.remove(objectiveName), ObjectiveAction.UNREGISTER));
}
@Override
public void updateObjective0(@NotNull String objectiveName, @NotNull String title, boolean hearts) {
player.sendPacket(
new ClientboundSetObjectivePacket(
new Objective(
dummyScoreboard,
objectiveName,
ObjectiveCriteria.DUMMY,
FabricTAB.getInstance().toComponent(IChatBaseComponent.optimizedComponent(title), player.getVersion()),
hearts ? ObjectiveCriteria.RenderType.HEARTS : ObjectiveCriteria.RenderType.INTEGER
),
2
)
);
public void updateObjective0(@NotNull String objectiveName, @NotNull String title, int display,
@Nullable IChatBaseComponent numberFormat) {
Objective obj = objectives.get(objectiveName);
obj.setDisplayName(toComponent(title));
obj.setRenderType(RenderType.values()[display]);
player.sendPacket(new ClientboundSetObjectivePacket(obj, ObjectiveAction.UPDATE));
}
@Override
public void registerTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix, @NotNull NameVisibility visibility, @NotNull CollisionRule collision, @NotNull Collection<String> players, int options) {
public void registerTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix,
@NotNull NameVisibility visibility, @NotNull CollisionRule collision,
@NotNull Collection<String> players, int options) {
PlayerTeam team = new PlayerTeam(dummyScoreboard, name);
team.setAllowFriendlyFire((options & 0x01) > 0);
team.setSeeFriendlyInvisibles((options & 0x02) > 0);
team.setColor(ChatFormatting.valueOf(EnumChatFormat.lastColorsOf(prefix).name()));
team.setCollisionRule(Team.CollisionRule.valueOf(collision.name()));
team.setNameTagVisibility(Team.Visibility.valueOf(visibility.name()));
team.setPlayerPrefix(FabricTAB.getInstance().toComponent(IChatBaseComponent.optimizedComponent(prefix), player.getVersion()));
team.setPlayerSuffix(FabricTAB.getInstance().toComponent(IChatBaseComponent.optimizedComponent(suffix), player.getVersion()));
team.setPlayerPrefix(toComponent(prefix));
team.setPlayerSuffix(toComponent(suffix));
team.getPlayers().addAll(players);
player.sendPacket(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, true));
player.sendPacket(FabricTAB.getVersion().registerTeam(team));
}
@Override
public void unregisterTeam0(@NotNull String name) {
player.sendPacket(ClientboundSetPlayerTeamPacket.createRemovePacket(new PlayerTeam(dummyScoreboard, name)));
player.sendPacket(FabricTAB.getVersion().unregisterTeam(new PlayerTeam(dummyScoreboard, name)));
}
@Override
public void updateTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix, @NotNull NameVisibility visibility, @NotNull CollisionRule collision, int options) {
public void updateTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix,
@NotNull NameVisibility visibility, @NotNull CollisionRule collision, int options) {
PlayerTeam team = new PlayerTeam(dummyScoreboard, name);
team.setAllowFriendlyFire((options & 0x01) > 0);
team.setSeeFriendlyInvisibles((options & 0x02) > 0);
team.setAllowFriendlyFire((options & 0x01) != 0);
team.setSeeFriendlyInvisibles((options & 0x02) != 0);
team.setColor(ChatFormatting.valueOf(EnumChatFormat.lastColorsOf(prefix).name()));
team.setCollisionRule(Team.CollisionRule.valueOf(collision.name()));
team.setNameTagVisibility(Team.Visibility.valueOf(visibility.name()));
team.setPlayerPrefix(FabricTAB.getInstance().toComponent(IChatBaseComponent.optimizedComponent(prefix), player.getVersion()));
team.setPlayerSuffix(FabricTAB.getInstance().toComponent(IChatBaseComponent.optimizedComponent(suffix), player.getVersion()));
player.sendPacket(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, false));
team.setPlayerPrefix(toComponent(prefix));
team.setPlayerSuffix(toComponent(suffix));
player.sendPacket(FabricTAB.getVersion().updateTeam(team));
}
@Override
public void setScore0(@NotNull String objective, @NotNull String playerName, int score) {
player.sendPacket(new ClientboundSetScorePacket(ServerScoreboard.Method.CHANGE, objective, playerName, score));
public void setScore0(@NotNull String objective, @NotNull String scoreHolder, int score,
@Nullable IChatBaseComponent displayName, @Nullable IChatBaseComponent numberFormat) {
player.sendPacket(FabricTAB.getVersion().setScore(
objective,
scoreHolder,
score,
displayName == null ? null : toComponent(displayName),
numberFormat == null ? null : toComponent(numberFormat)
));
}
@Override
public void removeScore0(@NotNull String objective, @NotNull String playerName) {
player.sendPacket(new ClientboundSetScorePacket(ServerScoreboard.Method.REMOVE, objective, playerName, 0));
public void removeScore0(@NotNull String objective, @NotNull String scoreHolder) {
player.sendPacket(FabricTAB.getVersion().removeScore(objective, scoreHolder));
}
@NotNull
private Component toComponent(@NotNull String string) {
return toComponent(IChatBaseComponent.optimizedComponent(string));
}
@NotNull
private Component toComponent(@NotNull IChatBaseComponent component) {
return player.getPlatform().toComponent(component, player.getVersion());
}
}
@@ -1,54 +1,40 @@
package me.neznamy.tab.platforms.fabric;
import lombok.Getter;
import me.lucko.fabric.api.permissions.v0.Permissions;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.util.ComponentCache;
import net.fabricmc.api.DedicatedServerModInitializer;
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.SharedConstants;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.network.chat.Component;
import net.minecraft.server.MinecraftServer;
import org.jetbrains.annotations.NotNull;
import java.io.File;
public class FabricTAB implements DedicatedServerModInitializer {
private final ComponentCache<IChatBaseComponent, Component> componentCache = new ComponentCache<>(1000,
(text, version) -> Component.Serializer.fromJson(text.toString(version)));
@Getter private static FabricTAB instance;
private final boolean fabricPermissionsApi = FabricLoader.getInstance().isModLoaded("fabric-permissions-api-v0");
@Getter private MinecraftServer server;
@Getter
private static VersionLoader version;
@Override
public void onInitializeServer() {
instance = this;
ProtocolVersion protocolVersion = ProtocolVersion.fromNetworkId(SharedConstants.getCurrentVersion().getProtocolVersion());
File folder = FabricLoader.getInstance().getConfigDir().resolve(TabConstants.PLUGIN_ID).toFile();
TAB.setInstance(new TAB(new FabricPlatform(), protocolVersion, folder));
new FabricEventListener().register();
ServerLifecycleEvents.SERVER_STARTING.register(server -> {
this.server = server;
TAB.getInstance().load();
});
ServerLifecycleEvents.SERVER_STOPPING.register(server -> TAB.getInstance().unload());
CommandRegistrationCallback.EVENT.register((dispatcher, $, $$) -> new FabricTabCommand().onRegisterCommands(dispatcher));
}
public Component toComponent(@NotNull IChatBaseComponent component, @NotNull ProtocolVersion clientVersion) {
return componentCache.get(component, clientVersion);
}
public boolean hasPermission(@NotNull CommandSourceStack source, @NotNull String permission) {
if (source.hasPermission(4)) return true;
return fabricPermissionsApi && Permissions.check(source, permission);
String[] modules = {
"v1_14_4",
"v1_15_2",
"v1_16_5",
"v1_17",
"v1_17_1",
"v1_18_2", // 1.18, 1.18.1, 1.18.2
"v1_19_2", // 1.19, 1.19.1, 1.19.2
"v1_19_3",
"v1_20_1", // 1.19.4, 1.20, 1.20.1
"v1_20_2",
"v1_20_3"
};
for (String module : modules) {
try {
version = (VersionLoader) Class.forName("me.neznamy.tab.platforms.fabric." + module + ".FabricTAB").getConstructor().newInstance();
if (!version.getSupportedVersions().contains(version.getServerVersion())) continue;
version.registerCommandCallback();
ServerLifecycleEvents.SERVER_STARTING.register(server -> TAB.create(new FabricPlatform(server)));
ServerLifecycleEvents.SERVER_STOPPING.register($ -> TAB.getInstance().unload());
return;
} catch (Throwable ignored) {}
}
throw new IllegalStateException("Your server version is marked as compatible, but a compatibility issue was found.");
}
}
@@ -13,7 +13,6 @@ import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.TabPlayer;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import net.minecraft.network.chat.Component;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
@@ -33,6 +32,7 @@ public class FabricTabCommand {
dispatcher.getRoot().addChild(command);
}
@NotNull
private String[] getArguments(@NotNull CommandContext<CommandSourceStack> context) {
String input = context.getInput();
int firstSpace = input.indexOf(' ');
@@ -48,10 +48,11 @@ public class FabricTabCommand {
private int executeCommand(@NotNull CommandSourceStack source, @NotNull String[] args) {
if (TAB.getInstance().isPluginDisabled()) {
boolean hasReloadPermission = FabricTAB.getInstance().hasPermission(source, TabConstants.Permission.COMMAND_RELOAD);
boolean hasAdminPermission = FabricTAB.getInstance().hasPermission(source, TabConstants.Permission.COMMAND_ALL);
boolean hasReloadPermission = ((FabricPlatform)TAB.getInstance().getPlatform()).hasPermission(source, TabConstants.Permission.COMMAND_RELOAD);
boolean hasAdminPermission = ((FabricPlatform)TAB.getInstance().getPlatform()).hasPermission(source, TabConstants.Permission.COMMAND_ALL);
for (String message : TAB.getInstance().getDisabledCommand().execute(args, hasReloadPermission, hasAdminPermission)) {
source.sendSystemMessage(Component.Serializer.fromJson(IChatBaseComponent.optimizedComponent(message).toString()));
FabricTAB.getVersion().sendMessage(source, ((FabricPlatform)TAB.getInstance().getPlatform()).toComponent(
IChatBaseComponent.optimizedComponent(message), TAB.getInstance().getServerVersion()));
}
} else {
if (source.getEntity() == null) {
@@ -64,20 +65,23 @@ public class FabricTabCommand {
return 0;
}
private @NotNull CompletableFuture<Suggestions> getSuggestions(@NotNull CommandSourceStack source, @NotNull String[] args, @NotNull SuggestionsBuilder builder) {
@NotNull
private CompletableFuture<Suggestions> getSuggestions(@NotNull CommandSourceStack source, @NotNull String[] args,
@NotNull SuggestionsBuilder builder) {
TabPlayer player = null;
if (source.getEntity() != null) {
player = TAB.getInstance().getPlayer(source.getEntity().getUUID());
if (player == null) return Suggestions.empty();
}
int lastSpace = builder.getRemaining().lastIndexOf(' ');
SuggestionsBuilder newBuilder = builder;
int lastSpace = newBuilder.getRemaining().lastIndexOf(' ');
if (lastSpace != -1) {
builder = builder.createOffset(lastSpace + 1 + builder.getStart());
newBuilder = newBuilder.createOffset(lastSpace + 1 + newBuilder.getStart());
}
for (String suggestion : TAB.getInstance().getCommand().complete(player, args)) {
builder.suggest(suggestion);
newBuilder.suggest(suggestion);
}
return builder.buildFuture();
return newBuilder.buildFuture();
}
}
@@ -4,72 +4,64 @@ import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.TabList;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket;
import net.minecraft.network.protocol.game.ClientboundTabListPacket;
import net.minecraft.world.level.GameType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.UUID;
public record FabricTabList(FabricTabPlayer player) implements TabList {
@RequiredArgsConstructor
public class FabricTabList implements TabList {
@NotNull
private final FabricTabPlayer player;
@Override
public void removeEntry(@NotNull UUID entry) {
player.sendPacket(new ClientboundPlayerInfoRemovePacket(Collections.singletonList(entry)));
player.sendPacket(FabricTAB.getVersion().build(Action.UPDATE_DISPLAY_NAME, new Builder(entry)));
}
@Override
public void updateDisplayName(@NotNull UUID entry, @Nullable IChatBaseComponent displayName) {
player.sendPacket(build(EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME),
new Builder(entry).setDisplayName(displayName == null ? null : FabricTAB.getInstance().toComponent(displayName, player.getVersion()))));
player.sendPacket(FabricTAB.getVersion().build(Action.UPDATE_DISPLAY_NAME,
new Builder(entry).setDisplayName(displayName == null ? null : player.getPlatform().toComponent(displayName, player.getVersion()))
));
}
@Override
public void updateLatency(@NotNull UUID entry, int latency) {
player.sendPacket(build(EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY), new Builder(entry).setLatency(latency)));
player.sendPacket(FabricTAB.getVersion().build(Action.UPDATE_LATENCY,
new Builder(entry).setLatency(latency)));
}
@Override
public void updateGameMode(@NotNull UUID entry, int gameMode) {
player.sendPacket(build(EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE), new Builder(entry).setGameMode(gameMode)));
player.sendPacket(FabricTAB.getVersion().build(Action.UPDATE_GAME_MODE,
new Builder(entry).setGameMode(gameMode)));
}
@Override
public void addEntry(@NotNull Entry entry) {
player.sendPacket(build(EnumSet.allOf(ClientboundPlayerInfoUpdatePacket.Action.class), new Builder(entry.getUniqueId())
player.sendPacket(FabricTAB.getVersion().build(Action.ADD_PLAYER,
new Builder(entry.getUniqueId())
.setName(entry.getName())
.setSkin(entry.getSkin())
.setGameMode(entry.getGameMode())
.setLatency(entry.getLatency())
.setDisplayName(entry.getDisplayName() == null ? null : FabricTAB.getInstance().toComponent(entry.getDisplayName(), player.getVersion()))));
.setDisplayName(entry.getDisplayName() == null ? null : player.getPlatform().toComponent(entry.getDisplayName(), player.getVersion()))
));
}
@Override
@SneakyThrows
public void setPlayerListHeaderFooter(@NotNull IChatBaseComponent header, @NotNull IChatBaseComponent footer) {
player.getPlayer().connection.send(new ClientboundTabListPacket(
FabricTAB.getInstance().toComponent(header, player.getVersion()),
FabricTAB.getInstance().toComponent(footer, player.getVersion()))
);
}
private Packet<?> build(EnumSet<ClientboundPlayerInfoUpdatePacket.Action> actions, FabricTabList.Builder entry) {
ClientboundPlayerInfoUpdatePacket packet = new ClientboundPlayerInfoUpdatePacket(actions, Collections.emptyList());
packet.entries = Collections.singletonList(new ClientboundPlayerInfoUpdatePacket.Entry(
entry.getId(),
entry.createProfile(),
true,
entry.getLatency(),
GameType.byId(entry.getGameMode()),
entry.getDisplayName(),
null
player.sendPacket(FabricTAB.getVersion().newHeaderFooter(
player.getPlatform().toComponent(header, player.getVersion()),
player.getPlatform().toComponent(footer, player.getVersion())
));
return packet;
}
@RequiredArgsConstructor
@@ -77,37 +69,43 @@ public record FabricTabList(FabricTabPlayer player) implements TabList {
public static class Builder {
@NotNull private final UUID id;
@Nullable private String name;
@NotNull private String name = ""; // Avoid nullability issues as things are changing over versions
@Nullable private Skin skin;
private int latency;
private int gameMode;
@Nullable private Component displayName;
public Builder setName(String name) {
@NotNull
public Builder setName(@NotNull String name) {
this.name = name;
return this;
}
public Builder setSkin(Skin skin) {
@NotNull
public Builder setSkin(@Nullable Skin skin) {
this.skin = skin;
return this;
}
@NotNull
public Builder setLatency(int latency) {
this.latency = latency;
return this;
}
@NotNull
public Builder setGameMode(int gameMode) {
this.gameMode = gameMode;
return this;
}
public Builder setDisplayName(Component displayName) {
@NotNull
public Builder setDisplayName(@Nullable Component displayName) {
this.displayName = displayName;
return this;
}
@NotNull
public GameProfile createProfile() {
GameProfile profile = new GameProfile(id, name);
if (skin != null) {
@@ -1,46 +1,53 @@
package me.neznamy.tab.platforms.fabric;
import com.mojang.authlib.properties.Property;
import java.util.Collection;
import lombok.Getter;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.backend.BackendTabPlayer;
import me.neznamy.tab.shared.backend.entityview.EntityView;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.hook.ViaVersionHook;
import me.neznamy.tab.shared.platform.bossbar.BossBar;
import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.BossBar;
import net.minecraft.network.protocol.Packet;
import net.minecraft.server.level.ServerPlayer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@Getter
public class FabricTabPlayer extends TabPlayer {
import java.util.Collection;
@Getter
public class FabricTabPlayer extends BackendTabPlayer {
@NotNull
private final Scoreboard<FabricTabPlayer> scoreboard = new FabricScoreboard(this);
@NotNull
private final TabList tabList = new FabricTabList(this);
@NotNull
private final BossBar bossBar = new FabricBossBar(this);
public FabricTabPlayer(ServerPlayer player) {
super(player, player.getUUID(), player.getGameProfile().getName(), TAB.getInstance().getConfiguration().getServerName(),
"N/A", ViaVersionHook.getInstance().getPlayerVersion(player.getUUID(), player.getGameProfile().getName()), true);
@NotNull
private final EntityView entityView = new FabricEntityView(this);
public FabricTabPlayer(@NotNull FabricPlatform platform, @NotNull ServerPlayer player) {
super(platform, player, player.getUUID(), player.getGameProfile().getName(),
FabricTAB.getVersion().getName(FabricTAB.getVersion().getLevel(player)));
}
@Override
public boolean hasPermission(@NotNull String permission) {
return FabricTAB.getInstance().hasPermission(getPlayer().createCommandSourceStack(), permission);
return getPlatform().hasPermission(getPlayer().createCommandSourceStack(), permission);
}
@Override
public int getPing() {
return getPlayer().latency;
return FabricTAB.getVersion().getPing(getPlayer());
}
@Override
public void sendMessage(@NotNull IChatBaseComponent message) {
getPlayer().sendSystemMessage(FabricTAB.getInstance().toComponent(message, getVersion()));
FabricTAB.getVersion().sendMessage(getPlayer(), getPlatform().toComponent(message, getVersion()));
}
@Override
@@ -54,27 +61,29 @@ public class FabricTabPlayer extends TabPlayer {
}
@Override
public @Nullable TabList.Skin getSkin() {
@Nullable
public TabList.Skin getSkin() {
Collection<Property> properties = getPlayer().getGameProfile().getProperties().get(TabList.TEXTURES_PROPERTY);
if (properties.isEmpty()) return null;
Property skinProperty = properties.iterator().next();
return new TabList.Skin(skinProperty.getValue(), skinProperty.getSignature());
if (properties.isEmpty()) return null; // Offline mode
return FabricTAB.getVersion().propertyToSkin(properties.iterator().next());
}
@Override
public @NotNull ServerPlayer getPlayer() {
@NotNull
public ServerPlayer getPlayer() {
return (ServerPlayer) player;
}
public void sendPacket(Packet<?> packet) {
getPlayer().connection.send(packet);
}
@Override
public boolean isOnline() {
return true;
}
@Override
public FabricPlatform getPlatform() {
return (FabricPlatform) platform;
}
@Override
public boolean isVanished() {
return false;
@@ -84,4 +93,25 @@ public class FabricTabPlayer extends TabPlayer {
public int getGamemode() {
return getPlayer().gameMode.getGameModeForPlayer().getId();
}
@Override
public double getHealth() {
return getPlayer().getHealth();
}
@Override
@NotNull
public String getDisplayName() {
return getPlayer().getDisplayName().getString(); // Will make it work properly if someone asks
}
/**
* Sends packet to the player
*
* @param packet
* Packet to send
*/
public void sendPacket(@NotNull Packet<?> packet) {
getPlayer().connection.send(packet);
}
}
@@ -0,0 +1,90 @@
package me.neznamy.tab.platforms.fabric;
import com.mojang.authlib.properties.Property;
import me.neznamy.tab.shared.backend.EntityData;
import me.neznamy.tab.shared.backend.Location;
import me.neznamy.tab.shared.features.injection.PipelineInjector;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.TabPlayer;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.Packet;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.decoration.ArmorStand;
import net.minecraft.world.level.Level;
import net.minecraft.world.scores.Objective;
import net.minecraft.world.scores.PlayerTeam;
import net.minecraft.world.scores.Scoreboard;
import net.minecraft.world.scores.criteria.ObjectiveCriteria.RenderType;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.UUID;
public interface VersionLoader {
Scoreboard dummyScoreboard = new Scoreboard();
ArmorStand dummyEntity = new ArmorStand(null, 0, 0, 0);
String getName(Level level);
int getPing(ServerPlayer player);
void sendMessage(ServerPlayer player, Component message);
TabList.Skin propertyToSkin(Property property);
void sendMessage(CommandSourceStack sender, Component message);
String getServerVersion();
double getMSPT();
Component deserialize(String string);
Packet<?> build(TabList.Action action, FabricTabList.Builder entry);
Level getLevel(ServerPlayer player);
boolean isSneaking(ServerPlayer player);
EntityData createDataWatcher(TabPlayer viewer, byte flags, String displayName, boolean nameVisible);
Packet<?> spawnEntity(int entityId, UUID id, Object entityType, Location location);
Packet<?> newEntityMetadata(int entityId, EntityData data);
boolean isSpawnPlayerPacket(Object packet);
int getSpawnedPlayer(Object spawnPacket);
int[] getDestroyedEntities(Object entityDestroyPacket);
boolean isBundlePacket(Object packet);
Iterable<Object> getPackets(Object bundlePacket);
PipelineInjector createPipelineInjector();
void destroyEntities(FabricTabPlayer player, int... entities);
void registerCommandCallback();
List<String> getSupportedVersions();
Packet<?> newHeaderFooter(Component header, Component footer);
Packet<?> setDisplaySlot(int slot, Objective objective);
Objective newObjective(String name, Component displayName, RenderType renderType, @Nullable Component numberFormat);
Packet<?> registerTeam(PlayerTeam team);
Packet<?> unregisterTeam(PlayerTeam team);
Packet<?> updateTeam(PlayerTeam team);
Packet<?> setScore(String objective, String scoreHolder, int score, Component displayName, @Nullable Component numberFormat);
Packet<?> removeScore(String objective, String scoreHolder);
}
@@ -0,0 +1,135 @@
package me.neznamy.tab.platforms.fabric.features;
import me.neznamy.tab.platforms.fabric.FabricTAB;
import me.neznamy.tab.platforms.fabric.FabricTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.backend.EntityData;
import me.neznamy.tab.shared.backend.features.unlimitedtags.BackendNameTagX;
import me.neznamy.tab.shared.platform.TabPlayer;
import net.fabricmc.fabric.api.entity.event.v1.ServerPlayerEvents;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.stream.Collectors;
public class FabricNameTagX extends BackendNameTagX {
public boolean enabled = true;
public FabricNameTagX() {
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 16) {
// Added in 1.16
ServerPlayerEvents.AFTER_RESPAWN.register((oldPlayer, newPlayer, alive) -> {
if (enabled) respawn(oldPlayer.getUUID());
});
} // TODO else
// TODO sneaking
}
@Override
public double getDistance(@NotNull TabPlayer player1, @NotNull TabPlayer player2) {
Vec3 loc1 = ((FabricTabPlayer)player1).getPlayer().position();
Vec3 loc2 = ((FabricTabPlayer)player2).getPlayer().position();
return Math.sqrt(Math.pow(loc1.x-loc2.x, 2) + Math.pow(loc1.z-loc2.z, 2));
}
@Override
public boolean areInSameWorld(@NotNull TabPlayer player1, @NotNull TabPlayer player2) {
return FabricTAB.getVersion().getLevel(((FabricTabPlayer)player1).getPlayer()) ==
FabricTAB.getVersion().getLevel(((FabricTabPlayer)player2).getPlayer());
}
@Override
public boolean canSee(@NotNull TabPlayer viewer, @NotNull TabPlayer target) {
return true;
}
@Override
public void unregisterListener() {
enabled = false;
}
@Override
@NotNull
public List<Integer> getPassengers(@NotNull Object vehicle) {
return ((Entity)vehicle).getPassengers().stream().map(Entity::getId).collect(Collectors.toList());
}
@Override
@Nullable
public Object getVehicle(@NotNull TabPlayer player) {
return ((FabricTabPlayer)player).getPlayer().getVehicle();
}
@Override
public int getEntityId(@NotNull Object entity) {
return ((Entity)entity).getId();
}
@Override
@NotNull
public String getEntityType(@NotNull Object entity) {
return ((Entity)entity).getType().toString(); // TODO test/fix
}
@Override
public boolean isSneaking(@NotNull TabPlayer player) {
return FabricTAB.getVersion().isSneaking(((FabricTabPlayer)player).getPlayer());
}
@Override
public boolean isSwimming(@NotNull TabPlayer player) {
return ((FabricTabPlayer)player).getPlayer().isSwimming();
}
@Override
public boolean isGliding(@NotNull TabPlayer player) {
return false; // TODO
}
@Override
public boolean isSleeping(@NotNull TabPlayer player) {
return ((FabricTabPlayer)player).getPlayer().isSleeping();
}
@Override
@NotNull
public Object getArmorStandType() {
return EntityType.ARMOR_STAND;
}
@Override
public double getX(@NotNull TabPlayer player) {
return ((FabricTabPlayer)player).getPlayer().position().x;
}
@Override
public double getY(@NotNull Object entity) {
return ((Entity)entity).position().y;
}
@Override
public double getZ(@NotNull TabPlayer player) {
return ((FabricTabPlayer)player).getPlayer().position().z;
}
@Override
@NotNull
public EntityData createDataWatcher(@NotNull TabPlayer viewer, byte flags, @NotNull String displayName, boolean nameVisible) {
return FabricTAB.getVersion().createDataWatcher(viewer, flags, displayName, nameVisible);
}
@Override
public void runInEntityScheduler(@NotNull Object entity, @NotNull Runnable task) {
task.run();
}
@Override
public boolean isDead(@NotNull TabPlayer player) {
return !((FabricTabPlayer)player).getPlayer().isAlive();
}
}
+11 -4
View File
@@ -10,11 +10,18 @@
"server": ["me.neznamy.tab.platforms.fabric.FabricTAB"]
},
"depends": {
"fabricloader": ">=0.9.0",
"fabric-api-base": "*"
"fabric-api-base": "*",
"minecraft": [
"1.14.4",
"1.15.2",
"1.16.5",
"1.17", "1.17.1",
"1.18", "1.18.1", "1.18.2",
"1.19", "1.19.1", "1.19.2", "1.19.3", "1.19.4",
"1.20", "1.20.1", "1.20.2", "1.20.3"
]
},
"recommends": {
"fabric-permissions-api-v0": "*"
},
"accessWidener": "tab.accesswidener"
}
}
@@ -1,21 +0,0 @@
accessWidener v1 named
# new
accessible field net/minecraft/server/MinecraftServer LOGGER Lorg/slf4j/Logger;
mutable field net/minecraft/server/MinecraftServer LOGGER Lorg/slf4j/Logger;
# old (~1.16.5-?)
accessible field net/minecraft/server/MinecraftServer LOGGER Lorg/apache/logging/log4j/Logger;
mutable field net/minecraft/server/MinecraftServer LOGGER Lorg/apache/logging/log4j/Logger;
# 1.19.3+
accessible field net/minecraft/network/protocol/game/ClientboundPlayerInfoUpdatePacket entries Ljava/util/List;
mutable field net/minecraft/network/protocol/game/ClientboundPlayerInfoUpdatePacket entries Ljava/util/List;
# 1.19.2-
accessible field net/minecraft/network/protocol/game/ClientboundPlayerInfoPacket entries Ljava/util/List;
mutable field net/minecraft/network/protocol/game/ClientboundPlayerInfoPacket entries Ljava/util/List;
# 1.16.5-
accessible field net/minecraft/network/protocol/game/ClientboundTabListPacket header Lnet/minecraft/network/chat/Component;
accessible field net/minecraft/network/protocol/game/ClientboundTabListPacket footer Lnet/minecraft/network/chat/Component;
+22
View File
@@ -0,0 +1,22 @@
plugins {
id("fabric-loom")
}
repositories {
// Gradle doesn't support combining settings and project repositories, so we have to re-declare all the settings repos we need
maven("https://jitpack.io") // YamlAssist
maven("https://repo.opencollab.dev/maven-snapshots/")
maven("https://repo.viaversion.com/")
maven("https://oss.sonatype.org/content/repositories/snapshots")
}
dependencies {
implementation(projects.fabric)
minecraft("com.mojang:minecraft:1.14.4")
mappings(loom.officialMojangMappings())
modImplementation("net.fabricmc:fabric-loader:0.14.17")
val fabricApiVersion = "0.28.5+1.14"
modImplementation(fabricApi.module("fabric-api-base", fabricApiVersion))
modImplementation(fabricApi.module("fabric-lifecycle-events-v1", fabricApiVersion))
modImplementation(fabricApi.module("fabric-command-api-v1", fabricApiVersion))
}
@@ -0,0 +1,149 @@
package me.neznamy.tab.platforms.fabric.v1_14_4;
import com.mojang.authlib.GameProfile;
import io.netty.channel.Channel;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.fabric.FabricTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.features.injection.NettyPipelineInjector;
import me.neznamy.tab.shared.features.nametags.NameTag;
import me.neznamy.tab.shared.features.sorting.Sorting;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.minecraft.network.Connection;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket.Action;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket.PlayerUpdate;
import net.minecraft.network.protocol.game.ClientboundSetDisplayObjectivePacket;
import net.minecraft.network.protocol.game.ClientboundSetObjectivePacket;
import net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket;
import net.minecraft.server.network.ServerGamePacketListenerImpl;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public class FabricPipelineInjector extends NettyPipelineInjector {
public FabricPipelineInjector() {
super("packet_handler");
}
@Override
@Nullable
@SneakyThrows
protected Channel getChannel(@NotNull TabPlayer player) {
Connection c = (Connection) ReflectionUtils.getFields(ServerGamePacketListenerImpl.class, Connection.class)
.get(0).get(((FabricTabPlayer)player).getPlayer().connection);
return (Channel) ReflectionUtils.getFields(Connection.class, Channel.class).get(0).get(c);
}
@Override
@SneakyThrows
public void onDisplayObjective(@NotNull TabPlayer player, @NotNull Object packet) {
// Getters are client only
int slot = ReflectionUtils.getFields(ClientboundSetDisplayObjectivePacket.class, int.class).get(0).getInt(packet);
String objective = (String) ReflectionUtils.getFields(ClientboundSetDisplayObjectivePacket.class, String.class).get(0).get(packet);
TAB.getInstance().getFeatureManager().onDisplayObjective(player, slot, objective);
}
@Override
@SneakyThrows
public void onObjective(@NotNull TabPlayer player, @NotNull Object packet) {
// Getters are client only
int action = ReflectionUtils.getFields(ClientboundSetObjectivePacket.class, int.class).get(0).getInt(packet);
String objective = (String) ReflectionUtils.getFields(ClientboundSetObjectivePacket.class, String.class).get(0).get(packet);
TAB.getInstance().getFeatureManager().onObjective(player, action, objective);
}
@Override
public boolean isDisplayObjective(@NotNull Object packet) {
return packet instanceof ClientboundSetDisplayObjectivePacket;
}
@Override
public boolean isObjective(@NotNull Object packet) {
return packet instanceof ClientboundSetObjectivePacket;
}
@Override
public boolean isTeam(@NotNull Object packet) {
return packet instanceof ClientboundSetPlayerTeamPacket;
}
@Override
public boolean isPlayerInfo(@NotNull Object packet) {
return packet instanceof ClientboundPlayerInfoPacket;
}
@Override
@SneakyThrows
@SuppressWarnings("unchecked")
public void onPlayerInfo(@NotNull TabPlayer receiver, @NotNull Object packet) {
// Getters are client only
Action action = (Action) ReflectionUtils.getFields(ClientboundPlayerInfoPacket.class, Action.class).get(0).get(packet);
List<PlayerUpdate> players = (List<PlayerUpdate>) ReflectionUtils.getFields(ClientboundPlayerInfoPacket.class, List.class).get(0).get(packet);
for (PlayerUpdate nmsData : players) {
GameProfile profile = nmsData.getProfile();
Field displayNameField = ReflectionUtils.getFields(PlayerUpdate.class, Component.class).get(0);
Field latencyField = ReflectionUtils.getFields(PlayerUpdate.class, int.class).get(0);
if (action == Action.UPDATE_DISPLAY_NAME) {
IChatBaseComponent newDisplayName = TAB.getInstance().getFeatureManager().onDisplayNameChange(receiver, profile.getId());
if (newDisplayName != null) displayNameField.set(nmsData, ((FabricTabPlayer)receiver).getPlatform().toComponent(newDisplayName, receiver.getVersion()));
}
if (action == Action.UPDATE_LATENCY) {
latencyField.set(nmsData, TAB.getInstance().getFeatureManager().onLatencyChange(receiver, profile.getId(), latencyField.getInt(nmsData)));
}
if (action == Action.ADD_PLAYER) {
TAB.getInstance().getFeatureManager().onEntryAdd(receiver, profile.getId(), profile.getName());
}
}
}
@Override
public boolean isLogin(@NotNull Object packet) {
return false;
}
@Override
@SneakyThrows
@SuppressWarnings("unchecked")
public void modifyPlayers(@NotNull Object teamPacket) {
if (TAB.getInstance().getNameTagManager() == null) return;
ClientboundSetPlayerTeamPacket packet = (ClientboundSetPlayerTeamPacket) teamPacket;
int action = ReflectionUtils.getInstanceFields(ClientboundSetPlayerTeamPacket.class, int.class).get(0).getInt(packet);
if (action == 1 || action == 2 || action == 4) return;
Field playersField = ReflectionUtils.getFields(ClientboundSetPlayerTeamPacket.class, Collection.class).get(0);
Collection<String> players = (Collection<String>) playersField.get(packet);
String teamName = String.valueOf(ReflectionUtils.getFields(ClientboundSetPlayerTeamPacket.class, String.class).get(0).get(packet));
if (players == null) return;
//creating a new list to prevent NoSuchFieldException in minecraft packet encoder when a player is removed
Collection<String> newList = new ArrayList<>();
for (String entry : players) {
TabPlayer p = getPlayer(entry);
if (p == null) {
newList.add(entry);
continue;
}
Sorting sorting = TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.SORTING);
String expectedTeam = sorting.getShortTeamName(p);
if (expectedTeam == null) {
newList.add(entry);
continue;
}
if (!((NameTag)TAB.getInstance().getNameTagManager()).getDisableChecker().isDisabledPlayer(p) &&
!TAB.getInstance().getNameTagManager().hasTeamHandlingPaused(p) && !teamName.equals(expectedTeam)) {
logTeamOverride(teamName, p.getName(), expectedTeam);
} else {
newList.add(entry);
}
}
playersField.set(packet, newList);
}
}
@@ -0,0 +1,223 @@
package me.neznamy.tab.platforms.fabric.v1_14_4;
import com.mojang.authlib.properties.Property;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.fabric.*;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.backend.EntityData;
import me.neznamy.tab.shared.backend.Location;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.features.injection.PipelineInjector;
import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.fabricmc.fabric.api.command.v1.CommandRegistrationCallback;
import net.minecraft.SharedConstants;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.*;
import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.network.syncher.EntityDataSerializers;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.server.ServerScoreboard;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.level.GameType;
import net.minecraft.world.level.Level;
import net.minecraft.world.scores.Objective;
import net.minecraft.world.scores.PlayerTeam;
import net.minecraft.world.scores.criteria.ObjectiveCriteria;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Field;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
public class FabricTAB implements VersionLoader {
@Override
public List<String> getSupportedVersions() {
return Collections.singletonList("1.14.4");
}
@Override
public String getName(Level level) {
return level.getLevelData().getLevelName() + level.dimension.getType().getFileSuffix();
}
@Override
public int getPing(ServerPlayer player) {
return player.latency;
}
@Override
public void sendMessage(ServerPlayer player, Component message) {
player.sendMessage(message);
}
@Override
public TabList.Skin propertyToSkin(Property property) {
return new TabList.Skin(property.getValue(), property.getSignature());
}
@Override
public void sendMessage(CommandSourceStack sender, Component message) {
sender.sendSuccess(message, false);
}
@Override
public String getServerVersion() {
return SharedConstants.getCurrentVersion().getName();
}
@Override
public double getMSPT() {
return ((FabricPlatform)TAB.getInstance().getPlatform()).getServer().getAverageTickTime();
}
@Override
public Component deserialize(String string) {
return Component.Serializer.fromJson(string);
}
@Override
@SneakyThrows
public Packet<?> build(TabList.Action action, FabricTabList.Builder entry) {
ClientboundPlayerInfoPacket packet = new ClientboundPlayerInfoPacket(ClientboundPlayerInfoPacket.Action.valueOf(action.name()), Collections.emptyList());
ReflectionUtils.getFields(ClientboundPlayerInfoPacket.class, List.class).get(0).set(packet,
Collections.singletonList(new ClientboundPlayerInfoPacket().new PlayerUpdate(
entry.createProfile(),
entry.getLatency(),
GameType.byId(entry.getGameMode()),
entry.getDisplayName()
)));
return packet;
}
@Override
public Level getLevel(ServerPlayer player) {
return player.level;
}
@Override
public boolean isSneaking(ServerPlayer player) {
return player.isSneaking();
}
@Override
public EntityData createDataWatcher(TabPlayer viewer, byte flags, String displayName, boolean nameVisible) {
SynchedEntityData data = new SynchedEntityData(null);
data.define(new EntityDataAccessor<>(0, EntityDataSerializers.BYTE), flags);
data.define(new EntityDataAccessor<>(2, EntityDataSerializers.OPTIONAL_COMPONENT),
Optional.of(((FabricTabPlayer)viewer).getPlatform().toComponent(IChatBaseComponent.optimizedComponent(displayName), viewer.getVersion())));
data.define(new EntityDataAccessor<>(3, EntityDataSerializers.BOOLEAN), nameVisible);
data.define(new EntityDataAccessor<>(13, EntityDataSerializers.BYTE), (byte)16);
return () -> data;
}
@Override
public Packet<?> spawnEntity(int entityId, UUID id, Object entityType, Location location) {
dummyEntity.setId(entityId);
dummyEntity.setUUID(id);
dummyEntity.setPos(location.getX(), location.getY(), location.getZ());
return new ClientboundAddMobPacket(dummyEntity);
}
@Override
public Packet<?> newEntityMetadata(int entityId, EntityData data) {
return new ClientboundSetEntityDataPacket(entityId, (SynchedEntityData) data.build(), true);
}
@Override
public boolean isSpawnPlayerPacket(Object packet) {
return packet instanceof ClientboundAddPlayerPacket;
}
@Override
@SneakyThrows
public int getSpawnedPlayer(Object spawnPacket) {
// Getter is client only
return ReflectionUtils.getFields(ClientboundAddPlayerPacket.class, int.class).get(0).getInt(spawnPacket);
}
@Override
@SneakyThrows
public int[] getDestroyedEntities(Object entityDestroyPacket) {
// Getter is client only
return (int[]) ReflectionUtils.getFields(ClientboundRemoveEntitiesPacket.class, int[].class).get(0).get(entityDestroyPacket);
}
@Override
public boolean isBundlePacket(Object packet) {
return false;
}
@Override
public Iterable<Object> getPackets(Object bundlePacket) {
return Collections.emptyList();
}
@Override
public PipelineInjector createPipelineInjector() {
return new FabricPipelineInjector();
}
@Override
public void destroyEntities(FabricTabPlayer player, int... entities) {
player.sendPacket(new ClientboundRemoveEntitiesPacket(entities));
}
@Override
public void registerCommandCallback() {
CommandRegistrationCallback.EVENT.register((dispatcher, $) -> new FabricTabCommand().onRegisterCommands(dispatcher));
}
@Override
@SneakyThrows
public Packet<?> newHeaderFooter(Component header, Component footer) {
ClientboundTabListPacket packet = new ClientboundTabListPacket();
List<Field> fields = ReflectionUtils.getFields(ClientboundTabListPacket.class, Component.class);
fields.get(0).set(packet, header);
fields.get(1).set(packet, footer);
return packet;
}
@Override
public Packet<?> setDisplaySlot(int slot, Objective objective) {
return new ClientboundSetDisplayObjectivePacket(slot, objective);
}
@Override
public Objective newObjective(String name, Component displayName, ObjectiveCriteria.RenderType renderType,
@Nullable Component numberFormat) {
return new Objective(dummyScoreboard, name, ObjectiveCriteria.DUMMY, displayName, renderType);
}
@Override
public Packet<?> registerTeam(PlayerTeam team) {
return new ClientboundSetPlayerTeamPacket(team, Scoreboard.TeamAction.CREATE);
}
@Override
public Packet<?> unregisterTeam(PlayerTeam team) {
return new ClientboundSetPlayerTeamPacket(team, Scoreboard.TeamAction.REMOVE);
}
@Override
public Packet<?> updateTeam(PlayerTeam team) {
return new ClientboundSetPlayerTeamPacket(team, Scoreboard.TeamAction.UPDATE);
}
@Override
public Packet<?> setScore(String objective, String scoreHolder, int score, Component displayName, @Nullable Component numberFormat) {
return new ClientboundSetScorePacket(ServerScoreboard.Method.CHANGE, objective, scoreHolder, score);
}
@Override
public Packet<?> removeScore(String objective, String scoreHolder) {
return new ClientboundSetScorePacket(ServerScoreboard.Method.REMOVE, objective, scoreHolder, 0);
}
}
+22
View File
@@ -0,0 +1,22 @@
plugins {
id("fabric-loom")
}
repositories {
// Gradle doesn't support combining settings and project repositories, so we have to re-declare all the settings repos we need
maven("https://jitpack.io") // YamlAssist
maven("https://repo.opencollab.dev/maven-snapshots/")
maven("https://repo.viaversion.com/")
maven("https://oss.sonatype.org/content/repositories/snapshots")
}
dependencies {
implementation(projects.fabric)
minecraft("com.mojang:minecraft:1.15.2")
mappings(loom.officialMojangMappings())
modImplementation("net.fabricmc:fabric-loader:0.14.17")
val fabricApiVersion = "0.28.5+1.15"
modImplementation(fabricApi.module("fabric-api-base", fabricApiVersion))
modImplementation(fabricApi.module("fabric-lifecycle-events-v1", fabricApiVersion))
modImplementation(fabricApi.module("fabric-command-api-v1", fabricApiVersion))
}
@@ -0,0 +1,149 @@
package me.neznamy.tab.platforms.fabric.v1_15_2;
import com.mojang.authlib.GameProfile;
import io.netty.channel.Channel;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.fabric.FabricTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.features.injection.NettyPipelineInjector;
import me.neznamy.tab.shared.features.nametags.NameTag;
import me.neznamy.tab.shared.features.sorting.Sorting;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.minecraft.network.Connection;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket.Action;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket.PlayerUpdate;
import net.minecraft.network.protocol.game.ClientboundSetDisplayObjectivePacket;
import net.minecraft.network.protocol.game.ClientboundSetObjectivePacket;
import net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket;
import net.minecraft.server.network.ServerGamePacketListenerImpl;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public class FabricPipelineInjector extends NettyPipelineInjector {
public FabricPipelineInjector() {
super("packet_handler");
}
@Override
@Nullable
@SneakyThrows
protected Channel getChannel(@NotNull TabPlayer player) {
Connection c = (Connection) ReflectionUtils.getFields(ServerGamePacketListenerImpl.class, Connection.class)
.get(0).get(((FabricTabPlayer)player).getPlayer().connection);
return (Channel) ReflectionUtils.getFields(Connection.class, Channel.class).get(0).get(c);
}
@Override
@SneakyThrows
public void onDisplayObjective(@NotNull TabPlayer player, @NotNull Object packet) {
// Getters are client only
int slot = ReflectionUtils.getFields(ClientboundSetDisplayObjectivePacket.class, int.class).get(0).getInt(packet);
String objective = (String) ReflectionUtils.getFields(ClientboundSetDisplayObjectivePacket.class, String.class).get(0).get(packet);
TAB.getInstance().getFeatureManager().onDisplayObjective(player, slot, objective);
}
@Override
@SneakyThrows
public void onObjective(@NotNull TabPlayer player, @NotNull Object packet) {
// Getters are client only
int action = ReflectionUtils.getFields(ClientboundSetObjectivePacket.class, int.class).get(0).getInt(packet);
String objective = (String) ReflectionUtils.getFields(ClientboundSetObjectivePacket.class, String.class).get(0).get(packet);
TAB.getInstance().getFeatureManager().onObjective(player, action, objective);
}
@Override
public boolean isDisplayObjective(@NotNull Object packet) {
return packet instanceof ClientboundSetDisplayObjectivePacket;
}
@Override
public boolean isObjective(@NotNull Object packet) {
return packet instanceof ClientboundSetObjectivePacket;
}
@Override
public boolean isTeam(@NotNull Object packet) {
return packet instanceof ClientboundSetPlayerTeamPacket;
}
@Override
public boolean isPlayerInfo(@NotNull Object packet) {
return packet instanceof ClientboundPlayerInfoPacket;
}
@Override
@SneakyThrows
@SuppressWarnings("unchecked")
public void onPlayerInfo(@NotNull TabPlayer receiver, @NotNull Object packet) {
// Getters are client only
Action action = (Action) ReflectionUtils.getFields(ClientboundPlayerInfoPacket.class, Action.class).get(0).get(packet);
List<PlayerUpdate> players = (List<PlayerUpdate>) ReflectionUtils.getFields(ClientboundPlayerInfoPacket.class, List.class).get(0).get(packet);
for (PlayerUpdate nmsData : players) {
GameProfile profile = nmsData.getProfile();
Field displayNameField = ReflectionUtils.getFields(PlayerUpdate.class, Component.class).get(0);
Field latencyField = ReflectionUtils.getFields(PlayerUpdate.class, int.class).get(0);
if (action == Action.UPDATE_DISPLAY_NAME) {
IChatBaseComponent newDisplayName = TAB.getInstance().getFeatureManager().onDisplayNameChange(receiver, profile.getId());
if (newDisplayName != null) displayNameField.set(nmsData, ((FabricTabPlayer)receiver).getPlatform().toComponent(newDisplayName, receiver.getVersion()));
}
if (action == Action.UPDATE_LATENCY) {
latencyField.set(nmsData, TAB.getInstance().getFeatureManager().onLatencyChange(receiver, profile.getId(), latencyField.getInt(nmsData)));
}
if (action == Action.ADD_PLAYER) {
TAB.getInstance().getFeatureManager().onEntryAdd(receiver, profile.getId(), profile.getName());
}
}
}
@Override
public boolean isLogin(@NotNull Object packet) {
return false;
}
@Override
@SneakyThrows
@SuppressWarnings("unchecked")
public void modifyPlayers(@NotNull Object teamPacket) {
if (TAB.getInstance().getNameTagManager() == null) return;
ClientboundSetPlayerTeamPacket packet = (ClientboundSetPlayerTeamPacket) teamPacket;
int action = ReflectionUtils.getInstanceFields(ClientboundSetPlayerTeamPacket.class, int.class).get(0).getInt(packet);
if (action == 1 || action == 2 || action == 4) return;
Field playersField = ReflectionUtils.getFields(ClientboundSetPlayerTeamPacket.class, Collection.class).get(0);
Collection<String> players = (Collection<String>) playersField.get(packet);
String teamName = String.valueOf(ReflectionUtils.getFields(ClientboundSetPlayerTeamPacket.class, String.class).get(0).get(packet));
if (players == null) return;
//creating a new list to prevent NoSuchFieldException in minecraft packet encoder when a player is removed
Collection<String> newList = new ArrayList<>();
for (String entry : players) {
TabPlayer p = getPlayer(entry);
if (p == null) {
newList.add(entry);
continue;
}
Sorting sorting = TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.SORTING);
String expectedTeam = sorting.getShortTeamName(p);
if (expectedTeam == null) {
newList.add(entry);
continue;
}
if (!((NameTag)TAB.getInstance().getNameTagManager()).getDisableChecker().isDisabledPlayer(p) &&
!TAB.getInstance().getNameTagManager().hasTeamHandlingPaused(p) && !teamName.equals(expectedTeam)) {
logTeamOverride(teamName, p.getName(), expectedTeam);
} else {
newList.add(entry);
}
}
playersField.set(packet, newList);
}
}
@@ -0,0 +1,220 @@
package me.neznamy.tab.platforms.fabric.v1_15_2;
import com.mojang.authlib.properties.Property;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.fabric.*;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.backend.EntityData;
import me.neznamy.tab.shared.backend.Location;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.features.injection.PipelineInjector;
import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.fabricmc.fabric.api.command.v1.CommandRegistrationCallback;
import net.minecraft.SharedConstants;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.*;
import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.network.syncher.EntityDataSerializers;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.server.ServerScoreboard;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.level.GameType;
import net.minecraft.world.level.Level;
import net.minecraft.world.scores.Objective;
import net.minecraft.world.scores.PlayerTeam;
import net.minecraft.world.scores.criteria.ObjectiveCriteria;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Field;
import java.util.*;
public class FabricTAB implements VersionLoader {
@Override
public List<String> getSupportedVersions() {
return Collections.singletonList("1.15.2");
}
@Override
public String getName(Level level) {
return level.getLevelData().getLevelName() + level.dimension.getType().getFileSuffix();
}
@Override
public int getPing(ServerPlayer player) {
return player.latency;
}
@Override
public void sendMessage(ServerPlayer player, Component message) {
player.sendMessage(message);
}
@Override
public TabList.Skin propertyToSkin(Property property) {
return new TabList.Skin(property.getValue(), property.getSignature());
}
@Override
public void sendMessage(CommandSourceStack sender, Component message) {
sender.sendSuccess(message, false);
}
@Override
public String getServerVersion() {
return SharedConstants.getCurrentVersion().getName();
}
@Override
public double getMSPT() {
return ((FabricPlatform)TAB.getInstance().getPlatform()).getServer().getAverageTickTime();
}
@Override
public Component deserialize(String string) {
return Component.Serializer.fromJson(string);
}
@Override
@SneakyThrows
public Packet<?> build(TabList.Action action, FabricTabList.Builder entry) {
ClientboundPlayerInfoPacket packet = new ClientboundPlayerInfoPacket(ClientboundPlayerInfoPacket.Action.valueOf(action.name()), Collections.emptyList());
ReflectionUtils.getFields(ClientboundPlayerInfoPacket.class, List.class).get(0).set(packet,
Collections.singletonList(new ClientboundPlayerInfoPacket().new PlayerUpdate(
entry.createProfile(),
entry.getLatency(),
GameType.byId(entry.getGameMode()),
entry.getDisplayName()
)));
return packet;
}
@Override
public Level getLevel(ServerPlayer player) {
return player.level;
}
@Override
public boolean isSneaking(ServerPlayer player) {
return player.isCrouching();
}
@Override
public EntityData createDataWatcher(TabPlayer viewer, byte flags, String displayName, boolean nameVisible) {
SynchedEntityData data = new SynchedEntityData(null);
data.define(new EntityDataAccessor<>(0, EntityDataSerializers.BYTE), flags);
data.define(new EntityDataAccessor<>(2, EntityDataSerializers.OPTIONAL_COMPONENT),
Optional.of(((FabricTabPlayer)viewer).getPlatform().toComponent(IChatBaseComponent.optimizedComponent(displayName), viewer.getVersion())));
data.define(new EntityDataAccessor<>(3, EntityDataSerializers.BOOLEAN), nameVisible);
data.define(new EntityDataAccessor<>(14, EntityDataSerializers.BYTE), (byte)16);
return () -> data;
}
@Override
public Packet<?> spawnEntity(int entityId, UUID id, Object entityType, Location location) {
dummyEntity.setId(entityId);
dummyEntity.setUUID(id);
dummyEntity.setPos(location.getX(), location.getY(), location.getZ());
return new ClientboundAddMobPacket(dummyEntity);
}
@Override
public Packet<?> newEntityMetadata(int entityId, EntityData data) {
return new ClientboundSetEntityDataPacket(entityId, (SynchedEntityData) data.build(), true);
}
@Override
public boolean isSpawnPlayerPacket(Object packet) {
return packet instanceof ClientboundAddPlayerPacket;
}
@Override
@SneakyThrows
public int getSpawnedPlayer(Object spawnPacket) {
// Getter is client only
return ReflectionUtils.getFields(ClientboundAddPlayerPacket.class, int.class).get(0).getInt(spawnPacket);
}
@Override
@SneakyThrows
public int[] getDestroyedEntities(Object entityDestroyPacket) {
// Getter is client only
return (int[]) ReflectionUtils.getFields(ClientboundRemoveEntitiesPacket.class, int[].class).get(0).get(entityDestroyPacket);
}
@Override
public boolean isBundlePacket(Object packet) {
return false;
}
@Override
public Iterable<Object> getPackets(Object bundlePacket) {
return Collections.emptyList();
}
@Override
public PipelineInjector createPipelineInjector() {
return new FabricPipelineInjector();
}
@Override
public void destroyEntities(FabricTabPlayer player, int... entities) {
player.sendPacket(new ClientboundRemoveEntitiesPacket(entities));
}
@Override
public void registerCommandCallback() {
CommandRegistrationCallback.EVENT.register((dispatcher, $) -> new FabricTabCommand().onRegisterCommands(dispatcher));
}
@Override
@SneakyThrows
public Packet<?> newHeaderFooter(Component header, Component footer) {
ClientboundTabListPacket packet = new ClientboundTabListPacket();
List<Field> fields = ReflectionUtils.getFields(ClientboundTabListPacket.class, Component.class);
fields.get(0).set(packet, header);
fields.get(1).set(packet, footer);
return packet;
}
@Override
public Packet<?> setDisplaySlot(int slot, Objective objective) {
return new ClientboundSetDisplayObjectivePacket(slot, objective);
}
@Override
public Objective newObjective(String name, Component displayName, ObjectiveCriteria.RenderType renderType,
@Nullable Component numberFormat) {
return new Objective(dummyScoreboard, name, ObjectiveCriteria.DUMMY, displayName, renderType);
}
@Override
public Packet<?> registerTeam(PlayerTeam team) {
return new ClientboundSetPlayerTeamPacket(team, Scoreboard.TeamAction.CREATE);
}
@Override
public Packet<?> unregisterTeam(PlayerTeam team) {
return new ClientboundSetPlayerTeamPacket(team, Scoreboard.TeamAction.REMOVE);
}
@Override
public Packet<?> updateTeam(PlayerTeam team) {
return new ClientboundSetPlayerTeamPacket(team, Scoreboard.TeamAction.UPDATE);
}
@Override
public Packet<?> setScore(String objective, String scoreHolder, int score, Component displayName, @Nullable Component numberFormat) {
return new ClientboundSetScorePacket(ServerScoreboard.Method.CHANGE, objective, scoreHolder, score);
}
@Override
public Packet<?> removeScore(String objective, String scoreHolder) {
return new ClientboundSetScorePacket(ServerScoreboard.Method.REMOVE, objective, scoreHolder, 0);
}
}
+22
View File
@@ -0,0 +1,22 @@
plugins {
id("fabric-loom")
}
repositories {
// Gradle doesn't support combining settings and project repositories, so we have to re-declare all the settings repos we need
maven("https://jitpack.io") // YamlAssist
maven("https://repo.opencollab.dev/maven-snapshots/")
maven("https://repo.viaversion.com/")
maven("https://oss.sonatype.org/content/repositories/snapshots")
}
dependencies {
implementation(projects.fabric)
minecraft("com.mojang:minecraft:1.16.5")
mappings(loom.officialMojangMappings())
modImplementation("net.fabricmc:fabric-loader:0.14.17")
val fabricApiVersion = "0.42.0+1.16"
modImplementation(fabricApi.module("fabric-api-base", fabricApiVersion))
modImplementation(fabricApi.module("fabric-lifecycle-events-v1", fabricApiVersion))
modImplementation(fabricApi.module("fabric-command-api-v1", fabricApiVersion))
}
@@ -0,0 +1,149 @@
package me.neznamy.tab.platforms.fabric.v1_16_5;
import com.mojang.authlib.GameProfile;
import io.netty.channel.Channel;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.fabric.FabricTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.features.injection.NettyPipelineInjector;
import me.neznamy.tab.shared.features.nametags.NameTag;
import me.neznamy.tab.shared.features.sorting.Sorting;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.minecraft.network.Connection;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket.Action;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket.PlayerUpdate;
import net.minecraft.network.protocol.game.ClientboundSetDisplayObjectivePacket;
import net.minecraft.network.protocol.game.ClientboundSetObjectivePacket;
import net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket;
import net.minecraft.server.network.ServerGamePacketListenerImpl;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public class FabricPipelineInjector extends NettyPipelineInjector {
public FabricPipelineInjector() {
super("packet_handler");
}
@Override
@Nullable
@SneakyThrows
protected Channel getChannel(@NotNull TabPlayer player) {
Connection c = (Connection) ReflectionUtils.getFields(ServerGamePacketListenerImpl.class, Connection.class)
.get(0).get(((FabricTabPlayer)player).getPlayer().connection);
return (Channel) ReflectionUtils.getFields(Connection.class, Channel.class).get(0).get(c);
}
@Override
@SneakyThrows
public void onDisplayObjective(@NotNull TabPlayer player, @NotNull Object packet) {
// Getters are client only
int slot = ReflectionUtils.getFields(ClientboundSetDisplayObjectivePacket.class, int.class).get(0).getInt(packet);
String objective = (String) ReflectionUtils.getFields(ClientboundSetDisplayObjectivePacket.class, String.class).get(0).get(packet);
TAB.getInstance().getFeatureManager().onDisplayObjective(player, slot, objective);
}
@Override
@SneakyThrows
public void onObjective(@NotNull TabPlayer player, @NotNull Object packet) {
// Getters are client only
int action = ReflectionUtils.getFields(ClientboundSetObjectivePacket.class, int.class).get(0).getInt(packet);
String objective = (String) ReflectionUtils.getFields(ClientboundSetObjectivePacket.class, String.class).get(0).get(packet);
TAB.getInstance().getFeatureManager().onObjective(player, action, objective);
}
@Override
public boolean isDisplayObjective(@NotNull Object packet) {
return packet instanceof ClientboundSetDisplayObjectivePacket;
}
@Override
public boolean isObjective(@NotNull Object packet) {
return packet instanceof ClientboundSetObjectivePacket;
}
@Override
public boolean isTeam(@NotNull Object packet) {
return packet instanceof ClientboundSetPlayerTeamPacket;
}
@Override
public boolean isPlayerInfo(@NotNull Object packet) {
return packet instanceof ClientboundPlayerInfoPacket;
}
@Override
@SneakyThrows
@SuppressWarnings("unchecked")
public void onPlayerInfo(@NotNull TabPlayer receiver, @NotNull Object packet) {
// Getters are client only
Action action = (Action) ReflectionUtils.getFields(ClientboundPlayerInfoPacket.class, Action.class).get(0).get(packet);
List<PlayerUpdate> players = (List<PlayerUpdate>) ReflectionUtils.getFields(ClientboundPlayerInfoPacket.class, List.class).get(0).get(packet);
for (PlayerUpdate nmsData : players) {
GameProfile profile = nmsData.getProfile();
Field displayNameField = ReflectionUtils.getFields(PlayerUpdate.class, Component.class).get(0);
Field latencyField = ReflectionUtils.getFields(PlayerUpdate.class, int.class).get(0);
if (action == Action.UPDATE_DISPLAY_NAME) {
IChatBaseComponent newDisplayName = TAB.getInstance().getFeatureManager().onDisplayNameChange(receiver, profile.getId());
if (newDisplayName != null) displayNameField.set(nmsData, ((FabricTabPlayer)receiver).getPlatform().toComponent(newDisplayName, receiver.getVersion()));
}
if (action == Action.UPDATE_LATENCY) {
latencyField.set(nmsData, TAB.getInstance().getFeatureManager().onLatencyChange(receiver, profile.getId(), latencyField.getInt(nmsData)));
}
if (action == Action.ADD_PLAYER) {
TAB.getInstance().getFeatureManager().onEntryAdd(receiver, profile.getId(), profile.getName());
}
}
}
@Override
public boolean isLogin(@NotNull Object packet) {
return false;
}
@Override
@SneakyThrows
@SuppressWarnings("unchecked")
public void modifyPlayers(@NotNull Object teamPacket) {
if (TAB.getInstance().getNameTagManager() == null) return;
ClientboundSetPlayerTeamPacket packet = (ClientboundSetPlayerTeamPacket) teamPacket;
int action = ReflectionUtils.getInstanceFields(ClientboundSetPlayerTeamPacket.class, int.class).get(0).getInt(packet);
if (action == 1 || action == 2 || action == 4) return;
Field playersField = ReflectionUtils.getFields(ClientboundSetPlayerTeamPacket.class, Collection.class).get(0);
Collection<String> players = (Collection<String>) playersField.get(packet);
String teamName = String.valueOf(ReflectionUtils.getFields(ClientboundSetPlayerTeamPacket.class, String.class).get(0).get(packet));
if (players == null) return;
//creating a new list to prevent NoSuchFieldException in minecraft packet encoder when a player is removed
Collection<String> newList = new ArrayList<>();
for (String entry : players) {
TabPlayer p = getPlayer(entry);
if (p == null) {
newList.add(entry);
continue;
}
Sorting sorting = TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.SORTING);
String expectedTeam = sorting.getShortTeamName(p);
if (expectedTeam == null) {
newList.add(entry);
continue;
}
if (!((NameTag)TAB.getInstance().getNameTagManager()).getDisableChecker().isDisabledPlayer(p) &&
!TAB.getInstance().getNameTagManager().hasTeamHandlingPaused(p) && !teamName.equals(expectedTeam)) {
logTeamOverride(teamName, p.getName(), expectedTeam);
} else {
newList.add(entry);
}
}
playersField.set(packet, newList);
}
}
@@ -0,0 +1,234 @@
package me.neznamy.tab.platforms.fabric.v1_16_5;
import com.mojang.authlib.properties.Property;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.fabric.*;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.backend.EntityData;
import me.neznamy.tab.shared.backend.Location;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.features.injection.PipelineInjector;
import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.fabricmc.fabric.api.command.v1.CommandRegistrationCallback;
import net.minecraft.SharedConstants;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.*;
import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.network.syncher.EntityDataSerializers;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.server.ServerScoreboard;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.level.GameType;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.storage.ServerLevelData;
import net.minecraft.world.scores.Objective;
import net.minecraft.world.scores.PlayerTeam;
import net.minecraft.world.scores.criteria.ObjectiveCriteria;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Field;
import java.util.*;
public class FabricTAB implements VersionLoader {
@Override
public List<String> getSupportedVersions() {
return Collections.singletonList("1.16.5");
}
@Override
public String getName(Level level) {
String path = level.dimension().location().getPath();
String dimensionSuffix;
switch (path) {
case "overworld":
dimensionSuffix = ""; // No suffix for overworld
break;
case "the_nether":
dimensionSuffix = "_nether";
break;
default:
dimensionSuffix = "_" + path; // End + default behavior for other dimensions created by mods
break;
}
return ((ServerLevelData)level.getLevelData()).getLevelName() + dimensionSuffix;
}
@Override
public int getPing(ServerPlayer player) {
return player.latency;
}
@Override
public void sendMessage(ServerPlayer player, Component message) {
player.sendMessage(message, new UUID(0, 0));
}
@Override
public TabList.Skin propertyToSkin(Property property) {
return new TabList.Skin(property.getValue(), property.getSignature());
}
@Override
public void sendMessage(CommandSourceStack sender, Component message) {
sender.sendSuccess(message, false);
}
@Override
public String getServerVersion() {
return SharedConstants.getCurrentVersion().getName();
}
@Override
public double getMSPT() {
return ((FabricPlatform)TAB.getInstance().getPlatform()).getServer().getAverageTickTime();
}
@Override
public Component deserialize(String string) {
return Component.Serializer.fromJson(string);
}
@Override
@SneakyThrows
public Packet<?> build(TabList.Action action, FabricTabList.Builder entry) {
ClientboundPlayerInfoPacket packet = new ClientboundPlayerInfoPacket(ClientboundPlayerInfoPacket.Action.valueOf(action.name()), Collections.emptyList());
ReflectionUtils.getFields(ClientboundPlayerInfoPacket.class, List.class).get(0).set(packet,
Collections.singletonList(new ClientboundPlayerInfoPacket().new PlayerUpdate(
entry.createProfile(),
entry.getLatency(),
GameType.byId(entry.getGameMode()),
entry.getDisplayName()
)));
return packet;
}
@Override
public Level getLevel(ServerPlayer player) {
return player.level;
}
@Override
public boolean isSneaking(ServerPlayer player) {
return player.isCrouching();
}
@Override
public EntityData createDataWatcher(TabPlayer viewer, byte flags, String displayName, boolean nameVisible) {
SynchedEntityData data = new SynchedEntityData(null);
data.define(new EntityDataAccessor<>(0, EntityDataSerializers.BYTE), flags);
data.define(new EntityDataAccessor<>(2, EntityDataSerializers.OPTIONAL_COMPONENT),
Optional.of(((FabricTabPlayer)viewer).getPlatform().toComponent(IChatBaseComponent.optimizedComponent(displayName), viewer.getVersion())));
data.define(new EntityDataAccessor<>(3, EntityDataSerializers.BOOLEAN), nameVisible);
data.define(new EntityDataAccessor<>(14, EntityDataSerializers.BYTE), (byte)16);
return () -> data;
}
@Override
public Packet<?> spawnEntity(int entityId, UUID id, Object entityType, Location location) {
dummyEntity.setId(entityId);
dummyEntity.setUUID(id);
dummyEntity.setPos(location.getX(), location.getY(), location.getZ());
return new ClientboundAddMobPacket(dummyEntity);
}
@Override
public Packet<?> newEntityMetadata(int entityId, EntityData data) {
return new ClientboundSetEntityDataPacket(entityId, (SynchedEntityData) data.build(), true);
}
@Override
public boolean isSpawnPlayerPacket(Object packet) {
return packet instanceof ClientboundAddPlayerPacket;
}
@Override
@SneakyThrows
public int getSpawnedPlayer(Object spawnPacket) {
// Getter is client only
return ReflectionUtils.getFields(ClientboundAddPlayerPacket.class, int.class).get(0).getInt(spawnPacket);
}
@Override
@SneakyThrows
public int[] getDestroyedEntities(Object entityDestroyPacket) {
// Getter is client only
return (int[]) ReflectionUtils.getFields(ClientboundRemoveEntitiesPacket.class, int[].class).get(0).get(entityDestroyPacket);
}
@Override
public boolean isBundlePacket(Object packet) {
return false;
}
@Override
public Iterable<Object> getPackets(Object bundlePacket) {
return Collections.emptyList();
}
@Override
public PipelineInjector createPipelineInjector() {
return new FabricPipelineInjector();
}
@Override
public void destroyEntities(FabricTabPlayer player, int... entities) {
player.sendPacket(new ClientboundRemoveEntitiesPacket(entities));
}
@Override
public void registerCommandCallback() {
CommandRegistrationCallback.EVENT.register((dispatcher, $) -> new FabricTabCommand().onRegisterCommands(dispatcher));
}
@Override
@SneakyThrows
public Packet<?> newHeaderFooter(Component header, Component footer) {
ClientboundTabListPacket packet = new ClientboundTabListPacket();
List<Field> fields = ReflectionUtils.getFields(ClientboundTabListPacket.class, Component.class);
fields.get(0).set(packet, header);
fields.get(1).set(packet, footer);
return packet;
}
@Override
public Packet<?> setDisplaySlot(int slot, Objective objective) {
return new ClientboundSetDisplayObjectivePacket(slot, objective);
}
@Override
public Objective newObjective(String name, Component displayName, ObjectiveCriteria.RenderType renderType,
@Nullable Component numberFormat) {
return new Objective(dummyScoreboard, name, ObjectiveCriteria.DUMMY, displayName, renderType);
}
@Override
public Packet<?> registerTeam(PlayerTeam team) {
return new ClientboundSetPlayerTeamPacket(team, Scoreboard.TeamAction.CREATE);
}
@Override
public Packet<?> unregisterTeam(PlayerTeam team) {
return new ClientboundSetPlayerTeamPacket(team, Scoreboard.TeamAction.REMOVE);
}
@Override
public Packet<?> updateTeam(PlayerTeam team) {
return new ClientboundSetPlayerTeamPacket(team, Scoreboard.TeamAction.UPDATE);
}
@Override
public Packet<?> setScore(String objective, String scoreHolder, int score, Component displayName, @Nullable Component numberFormat) {
return new ClientboundSetScorePacket(ServerScoreboard.Method.CHANGE, objective, scoreHolder, score);
}
@Override
public Packet<?> removeScore(String objective, String scoreHolder) {
return new ClientboundSetScorePacket(ServerScoreboard.Method.REMOVE, objective, scoreHolder, 0);
}
}
+28
View File
@@ -0,0 +1,28 @@
plugins {
id("fabric-loom")
}
repositories {
// Gradle doesn't support combining settings and project repositories, so we have to re-declare all the settings repos we need
maven("https://jitpack.io") // YamlAssist
maven("https://repo.opencollab.dev/maven-snapshots/")
maven("https://repo.viaversion.com/")
maven("https://oss.sonatype.org/content/repositories/snapshots")
}
dependencies {
implementation(projects.fabric)
minecraft("com.mojang:minecraft:1.17")
mappings(loom.officialMojangMappings())
modImplementation("net.fabricmc:fabric-loader:0.14.17")
val fabricApiVersion = "0.46.1+1.17"
modImplementation(fabricApi.module("fabric-api-base", fabricApiVersion))
modImplementation(fabricApi.module("fabric-lifecycle-events-v1", fabricApiVersion))
modImplementation(fabricApi.module("fabric-command-api-v1", fabricApiVersion))
}
tasks {
compileJava {
options.release.set(17)
}
}
@@ -0,0 +1,137 @@
package me.neznamy.tab.platforms.fabric.v1_17;
import com.mojang.authlib.GameProfile;
import io.netty.channel.Channel;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.fabric.FabricTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.features.injection.NettyPipelineInjector;
import me.neznamy.tab.shared.features.nametags.NameTag;
import me.neznamy.tab.shared.features.sorting.Sorting;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.minecraft.network.Connection;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket;
import net.minecraft.network.protocol.game.ClientboundSetDisplayObjectivePacket;
import net.minecraft.network.protocol.game.ClientboundSetObjectivePacket;
import net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket;
import net.minecraft.server.network.ServerGamePacketListenerImpl;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
public class FabricPipelineInjector extends NettyPipelineInjector {
public FabricPipelineInjector() {
super("packet_handler");
}
@Override
@Nullable
@SneakyThrows
protected Channel getChannel(@NotNull TabPlayer player) {
Connection c = (Connection) ReflectionUtils.getFields(ServerGamePacketListenerImpl.class, Connection.class)
.get(0).get(((FabricTabPlayer)player).getPlayer().connection);
return (Channel) ReflectionUtils.getFields(Connection.class, Channel.class).get(0).get(c);
}
@Override
public void onDisplayObjective(@NotNull TabPlayer player, @NotNull Object packet) {
TAB.getInstance().getFeatureManager().onDisplayObjective(player,
((ClientboundSetDisplayObjectivePacket) packet).getSlot(),
String.valueOf(((ClientboundSetDisplayObjectivePacket)packet).getObjectiveName()));
}
@Override
public void onObjective(@NotNull TabPlayer player, @NotNull Object packet) {
TAB.getInstance().getFeatureManager().onObjective(player,
((ClientboundSetObjectivePacket)packet).getMethod(),
((ClientboundSetObjectivePacket)packet).getObjectiveName());
}
@Override
public boolean isDisplayObjective(@NotNull Object packet) {
return packet instanceof ClientboundSetDisplayObjectivePacket;
}
@Override
public boolean isObjective(@NotNull Object packet) {
return packet instanceof ClientboundSetObjectivePacket;
}
@Override
public boolean isTeam(@NotNull Object packet) {
return packet instanceof ClientboundSetPlayerTeamPacket;
}
@Override
public boolean isPlayerInfo(@NotNull Object packet) {
return packet instanceof ClientboundPlayerInfoPacket;
}
@Override
@SneakyThrows
public void onPlayerInfo(@NotNull TabPlayer receiver, @NotNull Object packet0) {
ClientboundPlayerInfoPacket packet = (ClientboundPlayerInfoPacket) packet0;
for (ClientboundPlayerInfoPacket.PlayerUpdate nmsData : packet.getEntries()) {
GameProfile profile = nmsData.getProfile();
Field displayNameField = ReflectionUtils.getFields(ClientboundPlayerInfoPacket.PlayerUpdate.class, Component.class).get(0);
Field latencyField = ReflectionUtils.getFields(ClientboundPlayerInfoPacket.PlayerUpdate.class, int.class).get(0);
if (packet.getAction() == ClientboundPlayerInfoPacket.Action.UPDATE_DISPLAY_NAME) {
IChatBaseComponent newDisplayName = TAB.getInstance().getFeatureManager().onDisplayNameChange(receiver, profile.getId());
if (newDisplayName != null) displayNameField.set(nmsData, ((FabricTabPlayer)receiver).getPlatform().toComponent(newDisplayName, receiver.getVersion()));
}
if (packet.getAction() == ClientboundPlayerInfoPacket.Action.UPDATE_LATENCY) {
latencyField.set(nmsData, TAB.getInstance().getFeatureManager().onLatencyChange(receiver, profile.getId(), latencyField.getInt(nmsData)));
}
if (packet.getAction() == ClientboundPlayerInfoPacket.Action.ADD_PLAYER) {
TAB.getInstance().getFeatureManager().onEntryAdd(receiver, profile.getId(), profile.getName());
}
}
}
@Override
public boolean isLogin(@NotNull Object packet) {
return false;
}
@Override
@SneakyThrows
public void modifyPlayers(@NotNull Object teamPacket) {
if (TAB.getInstance().getNameTagManager() == null) return;
ClientboundSetPlayerTeamPacket packet = (ClientboundSetPlayerTeamPacket) teamPacket;
int action = ReflectionUtils.getInstanceFields(ClientboundSetPlayerTeamPacket.class, int.class).get(0).getInt(packet);
if (action == 1 || action == 2 || action == 4) return;
Collection<String> players = packet.getPlayers();
String teamName = packet.getName();
if (players == null) return;
//creating a new list to prevent NoSuchFieldException in minecraft packet encoder when a player is removed
Collection<String> newList = new ArrayList<>();
for (String entry : players) {
TabPlayer p = getPlayer(entry);
if (p == null) {
newList.add(entry);
continue;
}
Sorting sorting = TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.SORTING);
String expectedTeam = sorting.getShortTeamName(p);
if (expectedTeam == null) {
newList.add(entry);
continue;
}
if (!((NameTag)TAB.getInstance().getNameTagManager()).getDisableChecker().isDisabledPlayer(p) &&
!TAB.getInstance().getNameTagManager().hasTeamHandlingPaused(p) && !teamName.equals(expectedTeam)) {
logTeamOverride(teamName, p.getName(), expectedTeam);
} else {
newList.add(entry);
}
}
ReflectionUtils.getFields(ClientboundSetPlayerTeamPacket.class, Collection.class).get(0).set(packet, newList);
}
}
@@ -0,0 +1,221 @@
package me.neznamy.tab.platforms.fabric.v1_17;
import com.mojang.authlib.properties.Property;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.fabric.*;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.backend.EntityData;
import me.neznamy.tab.shared.backend.Location;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.features.injection.PipelineInjector;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.fabricmc.fabric.api.command.v1.CommandRegistrationCallback;
import net.minecraft.SharedConstants;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.*;
import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.network.syncher.EntityDataSerializers;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.server.ServerScoreboard;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.level.GameType;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.storage.ServerLevelData;
import net.minecraft.world.phys.Vec3;
import net.minecraft.world.scores.Objective;
import net.minecraft.world.scores.PlayerTeam;
import net.minecraft.world.scores.criteria.ObjectiveCriteria;
import org.jetbrains.annotations.Nullable;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
public class FabricTAB implements VersionLoader {
@Override
public List<String> getSupportedVersions() {
return List.of("1.17");
}
@Override
public String getName(Level level) {
String path = level.dimension().location().getPath();
String dimensionSuffix = switch (path) {
case "overworld" -> ""; // No suffix for overworld
case "the_nether" -> "_nether";
default -> "_" + path; // End + default behavior for other dimensions created by mods
};
return ((ServerLevelData)level.getLevelData()).getLevelName() + dimensionSuffix;
}
@Override
public int getPing(ServerPlayer player) {
return player.latency;
}
@Override
public void sendMessage(ServerPlayer player, Component message) {
player.sendMessage(message, new UUID(0, 0));
}
@Override
public TabList.Skin propertyToSkin(Property property) {
return new TabList.Skin(property.getValue(), property.getSignature());
}
@Override
public void sendMessage(CommandSourceStack sender, Component message) {
sender.sendSuccess(message, false);
}
@Override
public String getServerVersion() {
return SharedConstants.getCurrentVersion().getName();
}
@Override
public double getMSPT() {
return ((FabricPlatform)TAB.getInstance().getPlatform()).getServer().getAverageTickTime();
}
@Override
public Component deserialize(String string) {
return Component.Serializer.fromJson(string);
}
@Override
@SneakyThrows
public Packet<?> build(TabList.Action action, FabricTabList.Builder entry) {
ClientboundPlayerInfoPacket packet = new ClientboundPlayerInfoPacket(ClientboundPlayerInfoPacket.Action.valueOf(action.name()), Collections.emptyList());
ReflectionUtils.getFields(ClientboundPlayerInfoPacket.class, List.class).get(0).set(packet,
Collections.singletonList(new ClientboundPlayerInfoPacket.PlayerUpdate(
entry.createProfile(),
entry.getLatency(),
GameType.byId(entry.getGameMode()),
entry.getDisplayName()
)));
return packet;
}
@Override
public Level getLevel(ServerPlayer player) {
return player.level;
}
@Override
public boolean isSneaking(ServerPlayer player) {
return player.isCrouching();
}
@Override
public EntityData createDataWatcher(TabPlayer viewer, byte flags, String displayName, boolean nameVisible) {
SynchedEntityData data = new SynchedEntityData(null);
data.define(new EntityDataAccessor<>(0, EntityDataSerializers.BYTE), flags);
data.define(new EntityDataAccessor<>(2, EntityDataSerializers.OPTIONAL_COMPONENT),
Optional.of(((FabricTabPlayer)viewer).getPlatform().toComponent(IChatBaseComponent.optimizedComponent(displayName), viewer.getVersion())));
data.define(new EntityDataAccessor<>(3, EntityDataSerializers.BOOLEAN), nameVisible);
data.define(new EntityDataAccessor<>(15, EntityDataSerializers.BYTE), (byte)16);
return () -> data;
}
@Override
public Packet<?> spawnEntity(int entityId, UUID id, Object entityType, Location location) {
return new ClientboundAddEntityPacket(entityId, id, location.getX(), location.getY(), location.getZ(),
0, 0, (EntityType<?>) entityType, 0, Vec3.ZERO);
}
@Override
public Packet<?> newEntityMetadata(int entityId, EntityData data) {
return new ClientboundSetEntityDataPacket(entityId, (SynchedEntityData) data.build(), true);
}
@Override
public boolean isSpawnPlayerPacket(Object packet) {
return packet instanceof ClientboundAddPlayerPacket;
}
@Override
public int getSpawnedPlayer(Object spawnPacket) {
return ((ClientboundAddPlayerPacket) spawnPacket).getEntityId();
}
@Override
public int[] getDestroyedEntities(Object entityDestroyPacket) {
return new int[]{((ClientboundRemoveEntityPacket) entityDestroyPacket).getEntityId()};
}
@Override
public boolean isBundlePacket(Object packet) {
return false;
}
@Override
public Iterable<Object> getPackets(Object bundlePacket) {
return Collections.emptyList();
}
@Override
public PipelineInjector createPipelineInjector() {
return new FabricPipelineInjector();
}
@Override
public void destroyEntities(FabricTabPlayer player, int... entities) {
for (int entity : entities) {
player.sendPacket(new ClientboundRemoveEntityPacket(entity));
}
}
@Override
public void registerCommandCallback() {
CommandRegistrationCallback.EVENT.register((dispatcher, $) -> new FabricTabCommand().onRegisterCommands(dispatcher));
}
@Override
public Packet<?> newHeaderFooter(Component header, Component footer) {
return new ClientboundTabListPacket(header, footer);
}
@Override
public Packet<?> setDisplaySlot(int slot, Objective objective) {
return new ClientboundSetDisplayObjectivePacket(slot, objective);
}
@Override
public Objective newObjective(String name, Component displayName, ObjectiveCriteria.RenderType renderType,
@Nullable Component numberFormat) {
return new Objective(dummyScoreboard, name, ObjectiveCriteria.DUMMY, displayName, renderType);
}
@Override
public Packet<?> registerTeam(PlayerTeam team) {
return ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, true);
}
@Override
public Packet<?> unregisterTeam(PlayerTeam team) {
return ClientboundSetPlayerTeamPacket.createRemovePacket(team);
}
@Override
public Packet<?> updateTeam(PlayerTeam team) {
return ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, false);
}
@Override
public Packet<?> setScore(String objective, String scoreHolder, int score, Component displayName, @Nullable Component numberFormat) {
return new ClientboundSetScorePacket(ServerScoreboard.Method.CHANGE, objective, scoreHolder, score);
}
@Override
public Packet<?> removeScore(String objective, String scoreHolder) {
return new ClientboundSetScorePacket(ServerScoreboard.Method.REMOVE, objective, scoreHolder, 0);
}
}
+28
View File
@@ -0,0 +1,28 @@
plugins {
id("fabric-loom")
}
repositories {
// Gradle doesn't support combining settings and project repositories, so we have to re-declare all the settings repos we need
maven("https://jitpack.io") // YamlAssist
maven("https://repo.opencollab.dev/maven-snapshots/")
maven("https://repo.viaversion.com/")
maven("https://oss.sonatype.org/content/repositories/snapshots")
}
dependencies {
implementation(projects.fabric)
minecraft("com.mojang:minecraft:1.17.1")
mappings(loom.officialMojangMappings())
modImplementation("net.fabricmc:fabric-loader:0.14.17")
val fabricApiVersion = "0.46.1+1.17"
modImplementation(fabricApi.module("fabric-api-base", fabricApiVersion))
modImplementation(fabricApi.module("fabric-lifecycle-events-v1", fabricApiVersion))
modImplementation(fabricApi.module("fabric-command-api-v1", fabricApiVersion))
}
tasks {
compileJava {
options.release.set(17)
}
}
@@ -0,0 +1,137 @@
package me.neznamy.tab.platforms.fabric.v1_17_1;
import com.mojang.authlib.GameProfile;
import io.netty.channel.Channel;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.fabric.FabricTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.features.injection.NettyPipelineInjector;
import me.neznamy.tab.shared.features.nametags.NameTag;
import me.neznamy.tab.shared.features.sorting.Sorting;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.minecraft.network.Connection;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket;
import net.minecraft.network.protocol.game.ClientboundSetDisplayObjectivePacket;
import net.minecraft.network.protocol.game.ClientboundSetObjectivePacket;
import net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket;
import net.minecraft.server.network.ServerGamePacketListenerImpl;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
public class FabricPipelineInjector extends NettyPipelineInjector {
public FabricPipelineInjector() {
super("packet_handler");
}
@Override
@Nullable
@SneakyThrows
protected Channel getChannel(@NotNull TabPlayer player) {
Connection c = (Connection) ReflectionUtils.getFields(ServerGamePacketListenerImpl.class, Connection.class)
.get(0).get(((FabricTabPlayer)player).getPlayer().connection);
return (Channel) ReflectionUtils.getFields(Connection.class, Channel.class).get(0).get(c);
}
@Override
public void onDisplayObjective(@NotNull TabPlayer player, @NotNull Object packet) {
TAB.getInstance().getFeatureManager().onDisplayObjective(player,
((ClientboundSetDisplayObjectivePacket) packet).getSlot(),
String.valueOf(((ClientboundSetDisplayObjectivePacket)packet).getObjectiveName()));
}
@Override
public void onObjective(@NotNull TabPlayer player, @NotNull Object packet) {
TAB.getInstance().getFeatureManager().onObjective(player,
((ClientboundSetObjectivePacket)packet).getMethod(),
((ClientboundSetObjectivePacket)packet).getObjectiveName());
}
@Override
public boolean isDisplayObjective(@NotNull Object packet) {
return packet instanceof ClientboundSetDisplayObjectivePacket;
}
@Override
public boolean isObjective(@NotNull Object packet) {
return packet instanceof ClientboundSetObjectivePacket;
}
@Override
public boolean isTeam(@NotNull Object packet) {
return packet instanceof ClientboundSetPlayerTeamPacket;
}
@Override
public boolean isPlayerInfo(@NotNull Object packet) {
return packet instanceof ClientboundPlayerInfoPacket;
}
@Override
@SneakyThrows
public void onPlayerInfo(@NotNull TabPlayer receiver, @NotNull Object packet0) {
ClientboundPlayerInfoPacket packet = (ClientboundPlayerInfoPacket) packet0;
for (ClientboundPlayerInfoPacket.PlayerUpdate nmsData : packet.getEntries()) {
GameProfile profile = nmsData.getProfile();
Field displayNameField = ReflectionUtils.getFields(ClientboundPlayerInfoPacket.PlayerUpdate.class, Component.class).get(0);
Field latencyField = ReflectionUtils.getFields(ClientboundPlayerInfoPacket.PlayerUpdate.class, int.class).get(0);
if (packet.getAction() == ClientboundPlayerInfoPacket.Action.UPDATE_DISPLAY_NAME) {
IChatBaseComponent newDisplayName = TAB.getInstance().getFeatureManager().onDisplayNameChange(receiver, profile.getId());
if (newDisplayName != null) displayNameField.set(nmsData, ((FabricTabPlayer)receiver).getPlatform().toComponent(newDisplayName, receiver.getVersion()));
}
if (packet.getAction() == ClientboundPlayerInfoPacket.Action.UPDATE_LATENCY) {
latencyField.set(nmsData, TAB.getInstance().getFeatureManager().onLatencyChange(receiver, profile.getId(), latencyField.getInt(nmsData)));
}
if (packet.getAction() == ClientboundPlayerInfoPacket.Action.ADD_PLAYER) {
TAB.getInstance().getFeatureManager().onEntryAdd(receiver, profile.getId(), profile.getName());
}
}
}
@Override
public boolean isLogin(@NotNull Object packet) {
return false;
}
@Override
@SneakyThrows
public void modifyPlayers(@NotNull Object teamPacket) {
if (TAB.getInstance().getNameTagManager() == null) return;
ClientboundSetPlayerTeamPacket packet = (ClientboundSetPlayerTeamPacket) teamPacket;
int action = ReflectionUtils.getInstanceFields(ClientboundSetPlayerTeamPacket.class, int.class).get(0).getInt(packet);
if (action == 1 || action == 2 || action == 4) return;
Collection<String> players = packet.getPlayers();
String teamName = packet.getName();
if (players == null) return;
//creating a new list to prevent NoSuchFieldException in minecraft packet encoder when a player is removed
Collection<String> newList = new ArrayList<>();
for (String entry : players) {
TabPlayer p = getPlayer(entry);
if (p == null) {
newList.add(entry);
continue;
}
Sorting sorting = TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.SORTING);
String expectedTeam = sorting.getShortTeamName(p);
if (expectedTeam == null) {
newList.add(entry);
continue;
}
if (!((NameTag)TAB.getInstance().getNameTagManager()).getDisableChecker().isDisabledPlayer(p) &&
!TAB.getInstance().getNameTagManager().hasTeamHandlingPaused(p) && !teamName.equals(expectedTeam)) {
logTeamOverride(teamName, p.getName(), expectedTeam);
} else {
newList.add(entry);
}
}
ReflectionUtils.getFields(ClientboundSetPlayerTeamPacket.class, Collection.class).get(0).set(packet, newList);
}
}
@@ -0,0 +1,219 @@
package me.neznamy.tab.platforms.fabric.v1_17_1;
import com.mojang.authlib.properties.Property;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.fabric.*;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.backend.EntityData;
import me.neznamy.tab.shared.backend.Location;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.features.injection.PipelineInjector;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.fabricmc.fabric.api.command.v1.CommandRegistrationCallback;
import net.minecraft.SharedConstants;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.*;
import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.network.syncher.EntityDataSerializers;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.server.ServerScoreboard;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.level.GameType;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.storage.ServerLevelData;
import net.minecraft.world.phys.Vec3;
import net.minecraft.world.scores.Objective;
import net.minecraft.world.scores.PlayerTeam;
import net.minecraft.world.scores.criteria.ObjectiveCriteria;
import org.jetbrains.annotations.Nullable;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
public class FabricTAB implements VersionLoader {
@Override
public List<String> getSupportedVersions() {
return List.of("1.17.1");
}
@Override
public String getName(Level level) {
String path = level.dimension().location().getPath();
String dimensionSuffix = switch (path) {
case "overworld" -> ""; // No suffix for overworld
case "the_nether" -> "_nether";
default -> "_" + path; // End + default behavior for other dimensions created by mods
};
return ((ServerLevelData)level.getLevelData()).getLevelName() + dimensionSuffix;
}
@Override
public int getPing(ServerPlayer player) {
return player.latency;
}
@Override
public void sendMessage(ServerPlayer player, Component message) {
player.sendMessage(message, new UUID(0, 0));
}
@Override
public TabList.Skin propertyToSkin(Property property) {
return new TabList.Skin(property.getValue(), property.getSignature());
}
@Override
public void sendMessage(CommandSourceStack sender, Component message) {
sender.sendSuccess(message, false);
}
@Override
public String getServerVersion() {
return SharedConstants.getCurrentVersion().getName();
}
@Override
public double getMSPT() {
return ((FabricPlatform)TAB.getInstance().getPlatform()).getServer().getAverageTickTime();
}
@Override
public Component deserialize(String string) {
return Component.Serializer.fromJson(string);
}
@Override
@SneakyThrows
public Packet<?> build(TabList.Action action, FabricTabList.Builder entry) {
ClientboundPlayerInfoPacket packet = new ClientboundPlayerInfoPacket(ClientboundPlayerInfoPacket.Action.valueOf(action.name()), Collections.emptyList());
ReflectionUtils.getFields(ClientboundPlayerInfoPacket.class, List.class).get(0).set(packet,
Collections.singletonList(new ClientboundPlayerInfoPacket.PlayerUpdate(
entry.createProfile(),
entry.getLatency(),
GameType.byId(entry.getGameMode()),
entry.getDisplayName()
)));
return packet;
}
@Override
public Level getLevel(ServerPlayer player) {
return player.level;
}
@Override
public boolean isSneaking(ServerPlayer player) {
return player.isCrouching();
}
@Override
public EntityData createDataWatcher(TabPlayer viewer, byte flags, String displayName, boolean nameVisible) {
SynchedEntityData data = new SynchedEntityData(null);
data.define(new EntityDataAccessor<>(0, EntityDataSerializers.BYTE), flags);
data.define(new EntityDataAccessor<>(2, EntityDataSerializers.OPTIONAL_COMPONENT),
Optional.of(((FabricTabPlayer)viewer).getPlatform().toComponent(IChatBaseComponent.optimizedComponent(displayName), viewer.getVersion())));
data.define(new EntityDataAccessor<>(3, EntityDataSerializers.BOOLEAN), nameVisible);
data.define(new EntityDataAccessor<>(15, EntityDataSerializers.BYTE), (byte)16);
return () -> data;
}
@Override
public Packet<?> spawnEntity(int entityId, UUID id, Object entityType, Location location) {
return new ClientboundAddEntityPacket(entityId, id, location.getX(), location.getY(), location.getZ(),
0, 0, (EntityType<?>) entityType, 0, Vec3.ZERO);
}
@Override
public Packet<?> newEntityMetadata(int entityId, EntityData data) {
return new ClientboundSetEntityDataPacket(entityId, (SynchedEntityData) data.build(), true);
}
@Override
public boolean isSpawnPlayerPacket(Object packet) {
return packet instanceof ClientboundAddPlayerPacket;
}
@Override
public int getSpawnedPlayer(Object spawnPacket) {
return ((ClientboundAddPlayerPacket) spawnPacket).getEntityId();
}
@Override
public int[] getDestroyedEntities(Object entityDestroyPacket) {
return ((ClientboundRemoveEntitiesPacket) entityDestroyPacket).getEntityIds().toIntArray();
}
@Override
public boolean isBundlePacket(Object packet) {
return false;
}
@Override
public Iterable<Object> getPackets(Object bundlePacket) {
return Collections.emptyList();
}
@Override
public PipelineInjector createPipelineInjector() {
return new FabricPipelineInjector();
}
@Override
public void destroyEntities(FabricTabPlayer player, int... entities) {
player.sendPacket(new ClientboundRemoveEntitiesPacket(entities));
}
@Override
public void registerCommandCallback() {
CommandRegistrationCallback.EVENT.register((dispatcher, $) -> new FabricTabCommand().onRegisterCommands(dispatcher));
}
@Override
public Packet<?> newHeaderFooter(Component header, Component footer) {
return new ClientboundTabListPacket(header, footer);
}
@Override
public Packet<?> setDisplaySlot(int slot, Objective objective) {
return new ClientboundSetDisplayObjectivePacket(slot, objective);
}
@Override
public Objective newObjective(String name, Component displayName, ObjectiveCriteria.RenderType renderType,
@Nullable Component numberFormat) {
return new Objective(dummyScoreboard, name, ObjectiveCriteria.DUMMY, displayName, renderType);
}
@Override
public Packet<?> registerTeam(PlayerTeam team) {
return ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, true);
}
@Override
public Packet<?> unregisterTeam(PlayerTeam team) {
return ClientboundSetPlayerTeamPacket.createRemovePacket(team);
}
@Override
public Packet<?> updateTeam(PlayerTeam team) {
return ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, false);
}
@Override
public Packet<?> setScore(String objective, String scoreHolder, int score, Component displayName, @Nullable Component numberFormat) {
return new ClientboundSetScorePacket(ServerScoreboard.Method.CHANGE, objective, scoreHolder, score);
}
@Override
public Packet<?> removeScore(String objective, String scoreHolder) {
return new ClientboundSetScorePacket(ServerScoreboard.Method.REMOVE, objective, scoreHolder, 0);
}
}
+28
View File
@@ -0,0 +1,28 @@
plugins {
id("fabric-loom")
}
repositories {
// Gradle doesn't support combining settings and project repositories, so we have to re-declare all the settings repos we need
maven("https://jitpack.io") // YamlAssist
maven("https://repo.opencollab.dev/maven-snapshots/")
maven("https://repo.viaversion.com/")
maven("https://oss.sonatype.org/content/repositories/snapshots")
}
dependencies {
implementation(projects.fabric)
minecraft("com.mojang:minecraft:1.18.2")
mappings(loom.officialMojangMappings())
modImplementation("net.fabricmc:fabric-loader:0.14.17")
val fabricApiVersion = "0.76.0+1.18.2"
modImplementation(fabricApi.module("fabric-api-base", fabricApiVersion))
modImplementation(fabricApi.module("fabric-lifecycle-events-v1", fabricApiVersion))
modImplementation(fabricApi.module("fabric-command-api-v1", fabricApiVersion))
}
tasks {
compileJava {
options.release.set(17)
}
}
Loaded 100 of 312 files, more files were not shown because too many files have changed in this diff. Show more