Compare commits

...
240 Commits
Author SHA1 Message Date
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
NEZNAMY 8e5ea29c40 Define decimal separator in one more way to make sure it works properly (#967) 2023-07-07 15:25:11 +02:00
NEZNAMY 287b51a0bf Send warn if using debug command on player who is not loaded (#959) 2023-07-06 20:55:16 +02:00
NEZNAMY 28cbf952fa Send warn if both per world playerlist and layout are enabled 2023-07-06 15:12:28 +02:00
NEZNAMY 598c35a3aa Simplify 2023-07-06 12:37:24 +02:00
NEZNAMY b8b67a64ab Simplify nms loading 2023-07-06 12:33:47 +02:00
NEZNAMY 7f6020ef34 Fix loading on catserver 2023-07-06 12:13:12 +02:00
NEZNAMY aeed453e15 Move named field loading to respective classes 2023-07-05 23:41:54 +02:00
NEZNAMY 71dddcca4c Drop support for mojang mapped server jar 2023-07-05 21:49:54 +02:00
NEZNAMY 46cdf79e20 Use all arg constructor for spawn entity packet on 1.17+ (using the wrong packet still spawns the entity) 2023-07-05 21:12:47 +02:00
NEZNAMY d7af96823b Merge pull request #962 from MachineBreaker/patch-1
Fix ViaVersion bug returning -1 version
2023-07-04 23:11:08 +02:00
MachineBreaker 5a228bd6b3 Fix ViaVersion bug returning -1 version
Explanation:

Both TAB and ViaVersion PlayerJoinEvent priority are LOWEST and seems that TAB registers earlier than ViaVersion the listeners, this causes TAB not being able to get the client protocol version due to ViaVersion not being able to register it firstly.

There might be another way to workaround this since i don't know changing the priority would cause any issue with TAB itself.
2023-07-04 16:19:07 -04:00
NEZNAMY fcd95655ce Send warn when trying to use parse command without any placeholders 2023-07-04 11:09:14 +02:00
NEZNAMY 7f5b57f8d2 Merge pull request #956 from Tanguygab/master
Update skins in Layout's FixedSlots
2023-07-03 17:07:01 +02:00
NEZNAMY f596877716 Fix forwarding unlimited nametag disable status to backend 2023-07-02 17:30:58 +02:00
NEZNAMY 1802c8fd90 Fix debug command not showing properties from unlimited nametag as disabled when they are 2023-07-02 17:30:35 +02:00
Tanguygab e05856facc only update name if skin didn't update 2023-07-02 13:51:37 +02:00
Tanguygab 45cfc3abb0 simplified FixedSlot skin updating 2023-07-02 13:40:08 +02:00
Tanguygab 2d39579861 added support for placeholders in layout's fixed-slots' skins 2023-07-02 13:27:37 +02:00
NEZNAMY 431f4bb4d5 Change Velocity policy 2023-07-02 00:39:10 +02:00
NEZNAMY 7bc9d38176 Slightly improve debug messages 2023-07-01 20:15:50 +02:00
NEZNAMY 7536447a8f Update README.md 2023-06-29 20:47:33 +02:00
NEZNAMY 059b796061 Update CONTRIBUTING.md 2023-06-29 20:35:44 +02:00
NEZNAMY a89942484f Layout - Fix duplicate internal names causing issues 2023-06-28 21:03:13 +02:00
NEZNAMY c28be3fd65 Add (another) warn if plugin's CPU usage is high 2023-06-26 21:04:26 +02:00
NEZNAMY 5f1116795c Add Folia support (#919) 2023-06-25 12:11:41 +02:00
NEZNAMY 4f54d2f6b2 4.0.3-SNAPSHOT 2023-06-25 10:59:05 +02:00
NEZNAMY 6b7bedbe2d Fix NPE when placeholder returns null 2023-06-24 14:30:44 +02:00
NEZNAMY 20ff225c31 Add missing set team color 2023-06-23 22:03:32 +02:00
245 changed files with 7734 additions and 4729 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 -2
View File
@@ -6,5 +6,4 @@ is in quality of the report (typically providing correct steps to reproduce that
* Test **experimental builds**, which can be found in [GitHub Actions](https://github.com/NEZNAMY/TAB/actions) for problems of any kind including bugs.
This way you will help make the actual releases more stable / help get stable releases sooner.
* Help improve the wiki by opening a [Wiki change issue](https://github.com/NEZNAMY/TAB/issues/new?assignees=&labels=Wiki&template=wiki_change.yml),
where you can improve existing descriptions, add information you found missing, fix typos / grammar mistakes or add more examples of usage of functions.
* [Buy a copy](https://builtbybit.com/resources/14009/) of the plugin or [send a donation](https://paypal.me/neznamy1) to keep the plugin active.
where you can improve existing descriptions, add information you found missing, fix typos / grammar mistakes or add more examples.
+8 -55
View File
@@ -1,70 +1,23 @@
# About
TAB aims to be a superior all-in-one minecraft plugin for displaying information that outperforms all
similar plugins in terms of features, performance, compatibility and support.
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.
# Distribution
As of TAB 3.0.0, released with MC 1.18 (November 30th, 2021), there is no free / premium
version anymore. Only one version of the plugin exists now, which contains all implemented features
and is available to everyone for free. However, customer support is only provided to
customers who have purchased the plugin (see [Customer support](#customer-support)).
# Compiling
Compilation requires JDK 17 and up.
To compile the plugin, run `./gradlew build` from the terminal.
Once the plugin compiles, grab the jar from `/jar/build/libs/` folder.
The universal jar contains all modules for all supported platforms.
# Customer support
Unlike with other projects where requirements and level of support provided are unclear,
we provide a clear answer to what kind of service you will or will not receive. Some projects don't
even clearly state if they are free or paid. While they are listed as paid on spigot, they have
a free download link in description, which can be confusing.
Support is also quite often "we have a discord server, but don't ping devs for support", suggesting
there is no official support, just the community.
In TAB, everything is clearly defined. **Downloads are free, but services are paid.**
The plugin is free to download for everyone. The only exception might be occasional update delays
for free users once a year.
Customer support, however, is only provided to customers who have formally purchased a copy
of the plugin on any of the websites the plugin is being sold on:
* [BuiltByBit](https://builtbybit.com/resources/14009/)
* [Polymart](https://polymart.org/resource/484)
After purchasing, you can request assistance via either private messages on the website where you
have purchased it, or using [TAB's Discord server](https://discord.gg/EaSvdk6) (which is highly
preferred, since the communication will be much faster thanks to notifications and more).
* We use a ticket system, which allows us to effectively track any amount of unresolved problems.
No more questions getting lost in the sea of messages from other people asking. Tickets are only closed
after user confirmed all of their questions were answered and then receive a transcript of the ticket
from the bot after ticket is closed into private messages (unless user is blocking them).
* We do not rely on the community helping, which often makes things even worse. We know people have
better things to do than helping random people with a random plugin, especially when most people only
have a very basic knowledge of the plugin, not being able to answer more complicated problems.
* We have notifications enabled and reply as soon as possible. We do not have the server muted unlike
most mods on most servers who only check it when they are really bored, give a wiki link and
disappear again.
* As the developer, I do not rely on helpers providing support instead of me. They are handy as
I am obviously not available 24/7, but I do not intentionally leave anything to them. I reply
whenever I am available without any intentional delays.
# Other links
Download the plugin for free:
* [GitHub releases](https://github.com/NEZNAMY/TAB/releases)
* [BuiltByBit](https://builtbybit.com/resources/20631/)
* [Modrinth](https://modrinth.com/plugin/tab-was-taken)
[Wiki](https://github.com/NEZNAMY/TAB/wiki)
# Documentation
You can find everything about the plugin on the [Wiki](https://github.com/NEZNAMY/TAB/wiki). This includes a detailed description
of all features, as well as information regarding compatibility or limitations of each feature.
# Issues
The [Issues](https://github.com/NEZNAMY/TAB/issues) section is **only** available for cases included in templates.
In other words, they **are not available for**:
* **Support** - support is provided on discord for buyers, where roles are given to verified users
allowing us to know who bought it and who didn't. It also serves as a much faster way of communication.
* **Feature requests** - closed indefinitely.
**If you're a customer, please use discord for reporting bugs, especially if you aren't good at making bug reports.
You will be provided with a much faster and higher quality service.**
The [Issues](https://github.com/NEZNAMY/TAB/issues) section is an issue tracker for all the kind of problems covered in issue templates.
Please refrain from attempting to use it to receive help with your server, such issues will be closed immediately.
When opening an issue, provide as much information as possible. The difference between a bug getting fixed in 1 week vs 1 hour
is in quality of the report. Bug reports where steps to reproduce are "idk" have no value and will not help to get the bug fixed at all.
# Contributing
See [CONTRIBUTING.md](https://github.com/NEZNAMY/TAB/blob/master/CONTRIBUTING.md)
@@ -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);
}
+1 -2
View File
@@ -4,7 +4,7 @@ plugins {
allprojects {
group = "me.neznamy"
version = "4.0.2"
version = "4.0.9"
description = "An all-in-one solution that works"
ext.set("id", "tab")
@@ -15,7 +15,6 @@ allprojects {
val platforms = setOf(
projects.bukkit,
projects.bungeecord,
projects.krypton,
projects.velocity,
projects.sponge7,
projects.sponge8,
+6 -1
View File
@@ -4,5 +4,10 @@ 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")
}
}
@@ -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
@@ -21,7 +24,7 @@ public class BukkitEventListener extends EventListener<Player> implements Listen
quit(e.getPlayer().getUniqueId());
}
@EventHandler(priority = EventPriority.LOWEST)
@EventHandler(priority = EventPriority.LOW)
public void onJoin(PlayerJoinEvent e) {
join(e.getPlayer());
}
@@ -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);
}
}
}
@@ -3,17 +3,19 @@ package me.neznamy.tab.platforms.bukkit;
import com.mojang.authlib.GameProfile;
import io.netty.channel.Channel;
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.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.platforms.bukkit.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 org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.stream.Collectors;
@@ -31,6 +33,7 @@ public class BukkitPipelineInjector extends NettyPipelineInjector {
}
@Override
@Nullable
protected Channel getChannel(@NotNull TabPlayer player) {
BukkitTabPlayer bukkit = (BukkitTabPlayer) player;
NMSStorage nms = NMSStorage.getInstance();
@@ -45,8 +48,13 @@ public class BukkitPipelineInjector extends NettyPipelineInjector {
@Override
@SneakyThrows
public void onDisplayObjective(@NotNull TabPlayer player, @NotNull Object packet) {
TAB.getInstance().getFeatureManager().onDisplayObjective(player,
PacketScoreboard.DisplayObjective_POSITION.getInt(packet),
int position;
if (BukkitReflection.is1_20_2Plus()) {
position = ((Enum<?>)PacketScoreboard.DisplayObjective_POSITION.get(packet)).ordinal();
} else {
position = PacketScoreboard.DisplayObjective_POSITION.getInt(packet);
}
TAB.getInstance().getFeatureManager().onDisplayObjective(player, position,
(String) PacketScoreboard.DisplayObjective_OBJECTIVE_NAME.get(packet));
}
@@ -75,13 +83,12 @@ public class BukkitPipelineInjector extends NettyPipelineInjector {
@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+
@@ -92,31 +99,51 @@ public class BukkitPipelineInjector extends NettyPipelineInjector {
}
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;
if (BukkitReflection.is1_19_3Plus()) {
id = (UUID) BukkitTabList.PlayerInfoData_UUID.get(nmsData);
} else {
id = profile.getId();
}
Object displayName = null;
int latency = 0;
if (actions.contains(TabList.Action.UPDATE_DISPLAY_NAME.name()) || actions.contains(TabList.Action.ADD_PLAYER.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).getPlatform().toComponent(newDisplayName, receiver.getVersion());
if (!BukkitReflection.is1_19_3Plus()) BukkitTabList.PlayerInfoData_DisplayName.set(nmsData, displayName);
}
if (nms.is1_19_3Plus()) {
if (actions.contains(TabList.Action.UPDATE_LATENCY.name()) || actions.contains(TabList.Action.ADD_PLAYER.name())) {
latency = BukkitTabList.PlayerInfoData_Latency.getInt(nmsData);
latency = TAB.getInstance().getFeatureManager().onLatencyChange(receiver, id, latency);
if (!BukkitReflection.is1_19_3Plus()) BukkitTabList.PlayerInfoData_Latency.set(nmsData, latency);
}
if (actions.contains(TabList.Action.ADD_PLAYER.name())) {
TAB.getInstance().getFeatureManager().onEntryAdd(receiver, id, profile.getName());
}
if (BukkitReflection.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(),
id,
profile,
BukkitTabList.PlayerInfoData_Listed.getBoolean(nmsData),
BukkitTabList.PlayerInfoData_Latency.getInt(nmsData),
latency,
BukkitTabList.PlayerInfoData_GameMode.get(nmsData),
displayName,
BukkitTabList.PlayerInfoData_RemoteChatSession.get(nmsData)));
}
}
if (nms.is1_19_3Plus()) {
if (BukkitReflection.is1_19_3Plus()) {
BukkitTabList.PLAYERS.set(packet, updatedList);
}
}
@Override
public boolean isLogin(@NotNull Object packet) {
return false;
}
@SuppressWarnings("unchecked")
@Override
@SneakyThrows
@@ -130,7 +157,7 @@ public class BukkitPipelineInjector extends NettyPipelineInjector {
//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;
@@ -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,168 +0,0 @@
package me.neznamy.tab.platforms.bukkit;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.SneakyThrows;
import me.clip.placeholderapi.PlaceholderAPI;
import me.neznamy.tab.shared.GroupManager;
import me.neznamy.tab.shared.TabConstants;
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;
import me.neznamy.tab.shared.features.bossbar.BossBarManagerImpl;
import me.neznamy.tab.shared.features.nametags.NameTag;
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.util.ReflectionUtils;
import net.milkbowl.vault.permission.Permission;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.plugin.RegisteredServiceProvider;
import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
/**
* Implementation of Platform interface for Bukkit platform
*/
@RequiredArgsConstructor
@Getter
public class BukkitPlatform implements BackendPlatform {
/** Plugin instance for registering tasks and events */
private final JavaPlugin plugin;
/** 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() {
return new WitherBossBar(plugin);
}
@Override
public void loadPlayers() {
for (Player p : getOnlinePlayers()) {
TAB.getInstance().addPlayer(new BukkitTabPlayer(p));
}
}
@Override
public void registerPlaceholders() {
new BukkitPlaceholderRegistry().registerPlaceholders(TAB.getInstance().getPlaceholderManager());
}
@Override
public @Nullable PipelineInjector createPipelineInjector() {
return NMSStorage.getInstance().getMinorVersion() >= 8 ? new BukkitPipelineInjector() : null;
}
@Override
public @NotNull NameTag getUnlimitedNameTags() {
return new BukkitNameTagX(plugin);
}
@Override
public @NotNull TabExpansion createTabExpansion() {
if (placeholderAPI) {
BukkitTabExpansion expansion = new BukkitTabExpansion();
expansion.register();
return expansion;
}
return new EmptyTabExpansion();
}
@Override
public @Nullable 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) {
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);
} else if (identifier.startsWith("%sync:")) {
String syncedPlaceholder = "%" + identifier.substring(6, identifier.length()-1) + "%";
PlayerPlaceholderImpl[] ppl = new PlayerPlaceholderImpl[1];
ppl[0] = pl.registerPlayerPlaceholder(identifier, refresh, p -> {
try {
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);
});
return null;
} catch (UnsupportedOperationException e) {
return "<Folia does not support sync placeholders>";
}
});
} else if (identifier.startsWith("%server_")) {
TAB.getInstance().getPlaceholderManager().registerServerPlaceholder(identifier, refresh, () ->
placeholderAPI ? PlaceholderAPI.setPlaceholders(null, identifier) : identifier);
} else {
TAB.getInstance().getPlaceholderManager().registerPlayerPlaceholder(identifier, refresh, p ->
placeholderAPI ? PlaceholderAPI.setPlaceholders((Player) p.getPlayer(), identifier) : identifier);
}
}
@Override
public void sendConsoleMessage(@NotNull IChatBaseComponent message) {
Bukkit.getConsoleSender().sendMessage("[TAB] " + RGBUtils.getInstance().convertToBukkitFormat(message.toFlatText(),
TAB.getInstance().getServerVersion().getMinorVersion() >= 16));
}
@Override
public String getServerVersionInfo() {
return "[Bukkit] " + Bukkit.getName() + " - " + Bukkit.getBukkitVersion().split("-")[0] + " (" + NMSStorage.getInstance().getServerPackage() + ")";
}
@Override
@NotNull public GroupManager detectPermissionPlugin() {
if (LuckPermsHook.getInstance().isInstalled()) {
return new GroupManager("LuckPerms", LuckPermsHook.getInstance().getGroupFunction());
}
if (Bukkit.getPluginManager().isPluginEnabled("Vault")) {
RegisteredServiceProvider<Permission> provider = Bukkit.getServicesManager().getRegistration(Permission.class);
if (provider != null && !provider.getProvider().getName().equals("SuperPerms")) {
return new GroupManager(provider.getProvider().getName(), p -> provider.getProvider().getPrimaryGroup((Player) p.getPlayer()));
}
}
return new GroupManager("None", p -> TabConstants.NO_GROUP);
}
}
@@ -1,52 +1,28 @@
package me.neznamy.tab.platforms.bukkit;
import me.neznamy.tab.platforms.bukkit.nms.NMSStorage;
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 org.bstats.bukkit.Metrics;
import org.bstats.charts.SimplePie;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.bukkit.Bukkit;
import org.bukkit.command.*;
import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Callable;
/**
* 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 = 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
@@ -64,35 +40,19 @@ public class BukkitTAB extends JavaPlugin {
*/
private boolean isVersionSupported() {
try {
long time = System.currentTimeMillis();
NMSStorage.setInstance(getNMSLoader());
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.color("[TAB] Loaded NMS hook in " + (System.currentTimeMillis()-time) + "ms"));
NMSStorage.setInstance(new NMSStorage());
return true;
} catch (IllegalStateException ex) {
} 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, ProtocolVersion.LATEST_KNOWN_VERSION
));
} 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;
}
}
private @NotNull NMSStorage getNMSLoader() {
List<Callable<NMSStorage>> loaders = Arrays.asList(
BukkitLegacyNMSStorage::new,
BukkitModernNMSStorage::new,
MojangModernNMSStorage::new,
ThermosNMSStorage::new
);
for (Callable<NMSStorage> loader : loaders) {
try {
return loader.call();
} catch (Exception ignored) {}
}
throw new IllegalStateException("Unsupported server version");
}
}
@@ -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);
}
@@ -2,12 +2,14 @@ package me.neznamy.tab.platforms.bukkit;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
import lombok.Getter;
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.IChatBaseComponent;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.TabList.Entry.Builder;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -15,7 +17,6 @@ import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
/**
@@ -33,169 +34,199 @@ import java.util.*;
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 Class<?> RemoteChatSession$Data;
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;
@Getter
private static boolean available;
/** 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);
public static void load() throws NoSuchMethodException, ClassNotFoundException {
if (BukkitReflection.getMinorVersion() < 8) return; // Not supported (yet?)
// Classes
Class<?> playerInfoDataClass;
Class<?> IChatBaseComponent;
Class<Enum> EnumGamemodeClass;
if (BukkitReflection.isMojangMapped()) {
IChatBaseComponent = Class.forName("net.minecraft.network.chat.Component");
EntityPlayer = Class.forName("net.minecraft.server.level.ServerPlayer");
EnumGamemodeClass = (Class<Enum>) Class.forName("net.minecraft.world.level.GameType");
if (BukkitReflection.is1_19_3Plus()) {
ClientboundPlayerInfoRemovePacket = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket");
PlayerInfoClass = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket");
ActionClass = (Class<Enum>) Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket$Action");
playerInfoDataClass = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket$Entry");
RemoteChatSession$Data = Class.forName("net.minecraft.network.chat.RemoteChatSession$Data");
} else {
PlayerInfoClass = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket");
ActionClass = (Class<Enum>) Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket$Action");
playerInfoDataClass = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket$PlayerUpdate");
}
} else if (BukkitReflection.getMinorVersion() >= 17) {
IChatBaseComponent = Class.forName("net.minecraft.network.chat.IChatBaseComponent");
EntityPlayer = Class.forName("net.minecraft.server.level.EntityPlayer");
EnumGamemodeClass = (Class<Enum>) Class.forName("net.minecraft.world.level.EnumGamemode");
if (BukkitReflection.is1_19_3Plus()) {
ClientboundPlayerInfoRemovePacket = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket");
PlayerInfoClass = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket");
ActionClass = (Class<Enum>) Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket$a");
playerInfoDataClass = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket$b");
RemoteChatSession$Data = Class.forName("net.minecraft.network.chat.RemoteChatSession$a");
} else {
PlayerInfoClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutPlayerInfo");
ActionClass = (Class<Enum>) Class.forName("net.minecraft.network.protocol.game.PacketPlayOutPlayerInfo$EnumPlayerInfoAction");
playerInfoDataClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutPlayerInfo$PlayerInfoData");
}
} else {
newPacketPlayOutPlayerListHeaderFooter = PacketPlayOutPlayerListHeaderFooterClass.getConstructor();
HEADER = ReflectionUtils.getFields(PacketPlayOutPlayerListHeaderFooterClass, nms.IChatBaseComponent).get(0);
FOOTER = ReflectionUtils.getFields(PacketPlayOutPlayerListHeaderFooterClass, nms.IChatBaseComponent).get(1);
IChatBaseComponent = BukkitReflection.getLegacyClass("IChatBaseComponent");
EntityPlayer = BukkitReflection.getLegacyClass("EntityPlayer");
PlayerInfoClass = BukkitReflection.getLegacyClass("PacketPlayOutPlayerInfo");
ActionClass = (Class<Enum>) BukkitReflection.getLegacyClass("PacketPlayOutPlayerInfo$EnumPlayerInfoAction", "EnumPlayerInfoAction");
playerInfoDataClass = BukkitReflection.getLegacyClass("PacketPlayOutPlayerInfo$PlayerInfoData", "PlayerInfoData");
EnumGamemodeClass = (Class<Enum>) BukkitReflection.getLegacyClass("EnumGamemode", "WorldSettings$EnumGamemode");
}
if (nms.is1_19_3Plus()) {
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()) {
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);
}
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);
newPlayerInfo = PlayerInfoClass.getConstructor(ActionClass, Array.newInstance(EntityPlayer, 0).getClass());
ACTION = ReflectionUtils.getOnlyField(PlayerInfoClass, ActionClass);
}
gameModes = new Object[] {
Enum.valueOf(EnumGamemodeClass, "SURVIVAL"),
Enum.valueOf(EnumGamemodeClass, "CREATIVE"),
Enum.valueOf(EnumGamemodeClass, "ADVENTURE"),
Enum.valueOf(EnumGamemodeClass, "SPECTATOR")
};
available = true;
}
@Override
@SneakyThrows
public void removeEntry(@NotNull UUID entry) {
if (!available) 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 (!available) 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 (!available) 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 (!available) 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 (!available) 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");
private Object toComponent(IChatBaseComponent component) {
return player.getPlatform().toComponent(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;
}
}
@@ -4,24 +4,22 @@ 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.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.backend.entityview.EntityView;
import me.neznamy.tab.shared.platform.bossbar.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.platforms.bukkit.nms.NMSStorage;
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 org.bukkit.entity.Player;
import org.bukkit.metadata.MetadataValue;
import org.bukkit.potion.PotionEffectType;
@@ -29,7 +27,6 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.UUID;
/**
* TabPlayer implementation for Bukkit platform
@@ -39,26 +36,37 @@ import java.util.UUID;
public class BukkitTabPlayer extends BackendTabPlayer {
/** Player's NMS handle (EntityPlayer), preloading for speed */
@NotNull
private final Object handle;
/** Player's connection for sending packets, preloading for speed */
@NotNull
private final Object playerConnection;
@NotNull
private final Scoreboard<BukkitTabPlayer> scoreboard = new PacketScoreboard(this);
@NotNull
private final TabList tabList = new BukkitTabList(this);
@NotNull
private final BossBar bossBar = TAB.getInstance().getServerVersion().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()));
public BukkitTabPlayer(@NotNull BukkitPlatform platform, @NotNull Player p) {
super(platform, p, p.getUniqueId(), p.getName(), p.getWorld().getName());
handle = NMSStorage.getInstance().getHandle.invoke(player);
playerConnection = NMSStorage.getInstance().PLAYER_CONNECTION.get(handle);
}
@@ -69,27 +77,23 @@ public class BukkitTabPlayer extends BackendTabPlayer {
}
@Override
@SneakyThrows
public int getPing() {
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 17) {
if (BukkitReflection.getMinorVersion() >= 17) {
return getPlayer().getPing();
}
try {
return NMSStorage.getInstance().PING.getInt(handle);
} catch (IllegalAccessException e) {
return -1;
}
return NMSStorage.getInstance().PING.getInt(handle);
}
@SneakyThrows
public void sendPacket(@Nullable Object nmsPacket) {
if (nmsPacket == null || !getPlayer().isOnline()) return;
public void sendPacket(@NotNull Object nmsPacket) {
if (!getPlayer().isOnline()) return;
NMSStorage.getInstance().sendPacket.invoke(playerConnection, nmsPacket);
}
@Override
public void sendMessage(@NotNull IChatBaseComponent message) {
getPlayer().sendMessage(RGBUtils.getInstance().convertToBukkitFormat(message.toFlatText(),
getVersion().getMinorVersion() >= 16 && TAB.getInstance().getServerVersion().getMinorVersion() >= 16));
getPlayer().sendMessage(BukkitUtils.toBukkitFormat(message, getVersion().getMinorVersion() >= 16));
}
@Override
@@ -99,28 +103,29 @@ 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());
if (BukkitReflection.is1_20_2Plus()) {
return new TabList.Skin(
(String) property.getClass().getMethod("value").invoke(property),
(String) property.getClass().getMethod("signature").invoke(property)
);
} else {
return new TabList.Skin(property.getValue(), property.getSignature());
}
}
@Override
public @NotNull Player getPlayer() {
@NotNull
public Player getPlayer() {
return (Player) player;
}
@@ -129,6 +134,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);
@@ -140,39 +150,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.platforms.bukkit.BukkitUtils;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.bossbar.BossBar;
import me.neznamy.tab.shared.chat.rgb.RGBUtils;
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,42 @@ import java.util.UUID;
@RequiredArgsConstructor
public class BukkitBossBar implements BossBar {
/** Color array for fast access */
private static final org.bukkit.boss.BarColor[] colors = new org.bukkit.boss.BarColor[] {
org.bukkit.boss.BarColor.PINK,
org.bukkit.boss.BarColor.BLUE,
org.bukkit.boss.BarColor.RED,
org.bukkit.boss.BarColor.GREEN,
org.bukkit.boss.BarColor.YELLOW,
org.bukkit.boss.BarColor.PURPLE,
org.bukkit.boss.BarColor.WHITE
};
/** Style array for fast access */
private static final org.bukkit.boss.BarStyle[] styles = new org.bukkit.boss.BarStyle[] {
org.bukkit.boss.BarStyle.SOLID,
org.bukkit.boss.BarStyle.SEGMENTED_6,
org.bukkit.boss.BarStyle.SEGMENTED_10,
org.bukkit.boss.BarStyle.SEGMENTED_12,
org.bukkit.boss.BarStyle.SEGMENTED_20
};
/** 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),
org.bukkit.boss.BarColor.valueOf(color.name()),
org.bukkit.boss.BarStyle.valueOf(style.getBukkitName()));
BukkitUtils.toBukkitFormat(IChatBaseComponent.optimizedComponent(title), player.getVersion().getMinorVersion() >= 16),
colors[color.ordinal()],
styles[style.ordinal()]
);
bar.setProgress(progress);
bar.addPlayer(player.getPlayer());
bossBars.put(id, bar);
@@ -42,8 +64,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,12 +74,12 @@ 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
public void update(@NotNull UUID id, @NotNull BarColor color) {
bossBars.get(id).setColor(org.bukkit.boss.BarColor.valueOf(color.name()));
bossBars.get(id).setColor(colors[color.ordinal()]);
}
@Override
@@ -5,7 +5,7 @@ import me.neznamy.tab.shared.platform.bossbar.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.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.bossbar.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,50 @@ import java.util.UUID;
@RequiredArgsConstructor
public class ViaBossBar implements BossBar {
/** Color array for fast access */
private static final BossColor[] colors = new BossColor[] {
BossColor.PINK,
BossColor.BLUE,
BossColor.RED,
BossColor.GREEN,
BossColor.YELLOW,
BossColor.PURPLE,
BossColor.WHITE
};
/** Style array for fast access */
private static final BossStyle[] styles = new BossStyle[] {
BossStyle.SOLID,
BossStyle.SEGMENTED_6,
BossStyle.SEGMENTED_10,
BossStyle.SEGMENTED_12,
BossStyle.SEGMENTED_20
};
/** 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()));
colors[color.ordinal()],
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,12 +74,12 @@ 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
public void update(@NotNull UUID id, @NotNull BarColor color) {
viaBossBars.get(id).setColor(BossColor.valueOf(color.name()));
viaBossBars.get(id).setColor(colors[color.ordinal()]);
}
@Override
@@ -1,17 +1,12 @@
package me.neznamy.tab.platforms.bukkit.features;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
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;
@@ -35,10 +30,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);
@@ -46,45 +38,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
@@ -111,7 +70,8 @@ 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) {
return vehicle.getPassengers().stream().map(Entity::getEntityId).collect(Collectors.toList());
@@ -125,7 +85,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();
}
@@ -135,7 +96,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();
}
@@ -162,7 +124,8 @@ public class BukkitNameTagX extends BackendNameTagX implements Listener, PacketS
}
@Override
public @NotNull Object getArmorStandType() {
@NotNull
public Object getArmorStandType() {
return EntityType.ARMOR_STAND;
}
@@ -182,12 +145,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(@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);
}
}
}
@@ -5,8 +5,7 @@ import java.util.List;
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,7 +26,6 @@ import org.jetbrains.annotations.NotNull;
* Per-world-PlayerList feature handler
*/
@SuppressWarnings("deprecation")
@RequiredArgsConstructor
public class PerWorldPlayerList extends TabFeature implements Listener, Loadable, UnLoadable {
/** Config options */
@@ -35,21 +33,27 @@ public class PerWorldPlayerList extends TabFeature implements Listener, Loadable
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 +64,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 +83,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);
@@ -109,4 +113,10 @@ public class PerWorldPlayerList extends TabFeature implements Listener, Loadable
}
return viewerWorldGroup.equals(targetWorldGroup);
}
@Override
@NotNull
public String getFeatureName() {
return "Per world PlayerList";
}
}
@@ -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.BukkitReflection;
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()) {
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 if (BukkitReflection.getMinorVersion() >= 8) {
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,70 @@
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.util.ReflectionUtils;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
/**
* 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;
@Getter
private static boolean available;
static {
try {
if (BukkitReflection.getMinorVersion() >= 8) {
Class<?> IChatBaseComponent;
Class<?> HeaderFooterClass;
if (BukkitReflection.isMojangMapped()) {
IChatBaseComponent = Class.forName("net.minecraft.network.chat.Component");
HeaderFooterClass = Class.forName("net.minecraft.network.protocol.game.ClientboundTabListPacket");
} else if (BukkitReflection.getMinorVersion() >= 17) {
IChatBaseComponent = Class.forName("net.minecraft.network.chat.IChatBaseComponent");
HeaderFooterClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutPlayerListHeaderFooter");
} else {
IChatBaseComponent = BukkitReflection.getLegacyClass("IChatBaseComponent");
HeaderFooterClass = BukkitReflection.getLegacyClass("PacketPlayOutPlayerListHeaderFooter");
}
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) {
Object headerComponent = player.getPlatform().toComponent(header, player.getVersion());
Object footerComponent = player.getPlatform().toComponent(footer, player.getVersion());
if (BukkitReflection.getMinorVersion() >= 17) {
player.sendPacket(newHeaderFooter.newInstance(headerComponent, footerComponent));
} else {
Object packet = newHeaderFooter.newInstance();
HEADER.set(packet, headerComponent);
FOOTER.set(packet, footerComponent);
player.sendPacket(packet);
}
}
}
@@ -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.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.
* Due to lack of component cache for adventure components in TAB,
* there is a performance loss compared to using NMS. However, we do not
* need to serialize/deserialize anything ourselves thanks to direct component
* conversion, so in the end it is roughly the same.
*/
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,83 @@
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 this server jar is mojang mapped or not */
@Getter
private static final boolean mojangMapped = checkMojangMapped();
/** 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 = minorVersion >= 20 && !serverPackage.equals("v1_20_R1");
private static boolean checkMojangMapped() {
try {
Class.forName("net.minecraft.network.syncher.SynchedEntityData").getMethod("define",
Class.forName("net.minecraft.network.syncher.EntityDataAccessor"), Object.class);
return true;
} catch (ReflectiveOperationException | NullPointerException ex) {
return false;
}
}
/**
* 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
*/
public static 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 static Class<?> getLegacyClass(@NotNull String name) throws ClassNotFoundException {
try {
return BukkitReflection.class.getClassLoader().loadClass("net.minecraft.server." + serverPackage + "." + name);
} catch (NullPointerException e) {
// nested class not found
throw new ClassNotFoundException(name);
}
}
}
@@ -0,0 +1,292 @@
package me.neznamy.tab.platforms.bukkit.nms;
import lombok.*;
import me.neznamy.tab.platforms.bukkit.platform.BukkitPlatform;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.backend.EntityData;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
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 Class<?> DataWatcherRegistry;
private static Class<?> DataWatcherSerializer;
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();
/** 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();
Class<?> Entity;
if (BukkitReflection.isMojangMapped()) {
Entity = Class.forName("net.minecraft.world.entity.Entity");
DataWatcher = Class.forName("net.minecraft.network.syncher.SynchedEntityData");
DataWatcherObject = Class.forName("net.minecraft.network.syncher.EntityDataAccessor");
DataWatcherRegistry = Class.forName("net.minecraft.network.syncher.EntityDataSerializers");
DataWatcherSerializer = Class.forName("net.minecraft.network.syncher.EntityDataSerializer");
} else if (minorVersion >= 17) {
Entity = Class.forName("net.minecraft.world.entity.Entity");
DataWatcher = Class.forName("net.minecraft.network.syncher.DataWatcher");
DataWatcherObject = Class.forName("net.minecraft.network.syncher.DataWatcherObject");
DataWatcherRegistry = Class.forName("net.minecraft.network.syncher.DataWatcherRegistry");
DataWatcherSerializer = Class.forName("net.minecraft.network.syncher.DataWatcherSerializer");
} else {
Entity = BukkitReflection.getLegacyClass("Entity");
DataWatcher = BukkitReflection.getLegacyClass("DataWatcher");
if (minorVersion >= 9) {
DataWatcherObject = BukkitReflection.getLegacyClass("DataWatcherObject");
DataWatcherRegistry = BukkitReflection.getLegacyClass("DataWatcherRegistry");
DataWatcherSerializer = BukkitReflection.getLegacyClass("DataWatcherSerializer");
}
}
if (minorVersion >= 7) {
newDataWatcher = DataWatcher.getConstructor(Entity);
} else {
newDataWatcher = DataWatcher.getConstructor();
}
if (minorVersion >= 9) {
DataWatcher_register = ReflectionUtils.getMethod(
DataWatcher,
new String[]{"define", "register", "a", "m_135372_"}, // {Bukkit 1.20.2+, Bukkit, Bukkit 1.18+, Mohist 1.18.2}
DataWatcherObject, Object.class
);
} else {
DataWatcher_register = ReflectionUtils.getMethod(DataWatcher, new String[]{"func_75682_a", "a"}, int.class, Object.class); // {Thermos 1.7.10, Bukkit}
}
if (minorVersion >= 19) {
DataWatcher_packDirty = ReflectionUtils.getMethod(DataWatcher, new String[] {"packDirty", "b"}); // {Mojang | 1.20.2+, 1.20.2-}
}
if (BukkitReflection.is1_19_3Plus()) {
DataWatcher_markDirty = ReflectionUtils.getMethods(DataWatcher, void.class, DataWatcherObject).get(0);
}
if (minorVersion >= 9) {
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);
}
}
}
}
/**
* 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(((BukkitPlatform)TAB.getInstance().getPlatform()).toComponent(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,123 @@
package me.neznamy.tab.platforms.bukkit.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.header.HeaderFooter;
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 org.bukkit.Bukkit;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* Class holding all NMS classes, methods, fields and constructors used by TAB.
*/
public class NMSStorage {
/** Instance of this class */
@Getter @Setter private static NMSStorage instance;
/** Server's NMS/CraftBukkit package */
private final String serverPackage = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3];
/** Server's minor version */
@Getter private final int minorVersion = Integer.parseInt(serverPackage.split("_")[1]);
/** Basic universal values */
protected Class<?> Packet;
protected Class<?> NetworkManager;
public Class<?> EntityPlayer;
protected Class<?> EntityHuman;
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 */
protected Class<?> ChatSerializer;
public Method ChatSerializer_DESERIALIZE;
public final ComponentCache<IChatBaseComponent, Object> componentCache = new ComponentCache<>(1000,
(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) {
if (BukkitReflection.is1_20_2Plus()) {
NETWORK_MANAGER = ReflectionUtils.getOnlyField(PlayerConnection.getSuperclass(), NetworkManager);
} else {
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 = ReflectionUtils.getMethods(ChatSerializer, Object.class, String.class).get(0);
CHANNEL = ReflectionUtils.getOnlyField(NetworkManager, Channel.class);
// 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);
}
PLAYER_CONNECTION = ReflectionUtils.getOnlyField(EntityPlayer, PlayerConnection);
getHandle = Class.forName("org.bukkit.craftbukkit." + serverPackage + ".entity.CraftPlayer").getMethod("getHandle");
BukkitTabList.load();
DataWatcher.load();
PacketEntityView.load();
PacketScoreboard.load();
HeaderFooter.findInstance();
if (minorVersion < 17) {
PING = ReflectionUtils.getField(EntityPlayer, "ping", "field_71138_i"); // 1.5.2 - 1.16.5, 1.7.10 Thermos
}
}
private void loadClasses() throws ClassNotFoundException {
if (minorVersion >= 17 && ReflectionUtils.classExists("net.minecraft.ChatFormatting")) {
ChatSerializer = Class.forName("net.minecraft.network.chat.Component$Serializer");
EntityHuman = Class.forName("net.minecraft.world.entity.player.Player");
NetworkManager = Class.forName("net.minecraft.network.Connection");
Packet = Class.forName("net.minecraft.network.protocol.Packet");
EntityPlayer = Class.forName("net.minecraft.server.level.ServerPlayer");
PlayerConnection = Class.forName("net.minecraft.server.network.ServerGamePacketListenerImpl");
} else if (minorVersion >= 17) {
ChatSerializer = Class.forName("net.minecraft.network.chat.IChatBaseComponent$ChatSerializer");
EntityHuman = Class.forName("net.minecraft.world.entity.player.EntityHuman");
NetworkManager = Class.forName("net.minecraft.network.NetworkManager");
Packet = Class.forName("net.minecraft.network.protocol.Packet");
EntityPlayer = Class.forName("net.minecraft.server.level.EntityPlayer");
PlayerConnection = Class.forName("net.minecraft.server.network.PlayerConnection");
} else {
EntityHuman = BukkitReflection.getLegacyClass("EntityHuman");
Packet = BukkitReflection.getLegacyClass("Packet");
EntityPlayer = BukkitReflection.getLegacyClass("EntityPlayer");
PlayerConnection = BukkitReflection.getLegacyClass("PlayerConnection");
NetworkManager = BukkitReflection.getLegacyClass("NetworkManager");
if (minorVersion >= 7) {
ChatSerializer = BukkitReflection.getLegacyClass("IChatBaseComponent$ChatSerializer", "ChatSerializer");
}
}
}
}
@@ -0,0 +1,410 @@
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.TAB;
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 (TAB.getInstance().getServerVersion().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));
}
}
@@ -1,81 +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;
public static Constructor<?> CONSTRUCTOR;
public 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) {
CONSTRUCTOR = ReflectionUtils.getOnlyConstructor(CLASS);
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,138 +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;
}
}
/**
* 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,139 +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 me.neznamy.tab.shared.util.ReflectionUtils;
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
*
* @param name
* class name
* @return class from given name
* @throws ClassNotFoundException
* if class was not found
*/
public Class<?> getLegacyClass(@NotNull String name) throws ClassNotFoundException {
return Class.forName("net.minecraft.server." + serverPackage + "." + name);
}
@Override
public void loadNamedFieldsAndMethods() throws ReflectiveOperationException {
PacketScoreboard.ScoreboardScore_setScore = ReflectionUtils.getMethod(PacketScoreboard.ScoreboardScoreClass, new String[] {"setScore", "c"}, int.class); // 1.5.1+, 1.5
PacketScoreboard.ScoreboardTeam_setAllowFriendlyFire = ReflectionUtils.getMethod(PacketScoreboard.ScoreboardTeam, new String[] {"setAllowFriendlyFire", "a"}, boolean.class); // 1.5.1+, 1.5
PacketScoreboard.ScoreboardTeam_setCanSeeFriendlyInvisibles = ReflectionUtils.getMethod(PacketScoreboard.ScoreboardTeam, new String[] {"setCanSeeFriendlyInvisibles", "b"}, boolean.class); // 1.5.1+, 1.5
if (minorVersion >= 7) {
ChatSerializer_DESERIALIZE = ChatSerializer.getMethod("a", String.class);
}
if (minorVersion >= 8) {
PacketScoreboard.ScoreboardTeam_setNameTagVisibility = ReflectionUtils.getMethod(PacketScoreboard.ScoreboardTeam, new String[] {"setNameTagVisibility", "a"}, PacketScoreboard.EnumNameTagVisibility); // {1.8.1+, 1.8}
}
if (minorVersion >= 9) {
DataWatcher.REGISTER = DataWatcher.CLASS.getMethod("register", DataWatcherObject.CLASS, Object.class);
DataWatcherHelper.DataWatcherSerializer_BYTE = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("a").get(null);
DataWatcherHelper.DataWatcherSerializer_FLOAT = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("c").get(null);
DataWatcherHelper.DataWatcherSerializer_STRING = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("d").get(null);
if (getMinorVersion() >= 13) {
DataWatcherHelper.DataWatcherSerializer_OPTIONAL_COMPONENT = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("f").get(null);
DataWatcherHelper.DataWatcherSerializer_BOOLEAN = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("i").get(null);
} else {
DataWatcherHelper.DataWatcherSerializer_BOOLEAN = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("h").get(null);
}
} else {
DataWatcher.REGISTER = DataWatcher.CLASS.getMethod("a", int.class, Object.class);
}
if (minorVersion >= 13) {
PacketScoreboard.ScoreboardTeam_setPrefix = PacketScoreboard.ScoreboardTeam.getMethod("setPrefix", IChatBaseComponent);
PacketScoreboard.ScoreboardTeam_setSuffix = PacketScoreboard.ScoreboardTeam.getMethod("setSuffix", IChatBaseComponent);
} else {
PacketScoreboard.ScoreboardTeam_setPrefix = ReflectionUtils.getMethod(PacketScoreboard.ScoreboardTeam, new String[] {"setPrefix", "b"}, String.class); // 1.5.1+, 1.5
PacketScoreboard.ScoreboardTeam_setSuffix = ReflectionUtils.getMethod(PacketScoreboard.ScoreboardTeam, new String[] {"setSuffix", "c"}, String.class); // 1.5.1+, 1.5
}
}
@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,107 +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 me.neznamy.tab.shared.util.ReflectionUtils;
/**
* NMS loader for minecraft 1.17+ using Mojang packaging and bukkit names.
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public class BukkitModernNMSStorage extends NMSStorage {
@Override
public void loadNamedFieldsAndMethods() throws ReflectiveOperationException {
ChatSerializer_DESERIALIZE = ChatSerializer.getMethod("a", String.class);
DataWatcher.REGISTER = ReflectionUtils.getMethod(DataWatcher.CLASS, new String[]{"register", "a"}, DataWatcherObject.CLASS, Object.class); // {Bukkit, Bukkit 1.18+}
PacketScoreboard.ScoreboardScore_setScore = ReflectionUtils.getMethod(PacketScoreboard.ScoreboardScoreClass, new String[]{"setScore", "b"}, int.class); // {Bukkit, Bukkit 1.18+}
PacketScoreboard.ScoreboardTeam_setAllowFriendlyFire = ReflectionUtils.getMethod(PacketScoreboard.ScoreboardTeam, new String[]{"setAllowFriendlyFire", "a"}, boolean.class); // {Bukkit, Bukkit 1.18+}
PacketScoreboard.ScoreboardTeam_setCanSeeFriendlyInvisibles = ReflectionUtils.getMethod(PacketScoreboard.ScoreboardTeam, new String[]{"setCanSeeFriendlyInvisibles", "b"}, boolean.class); // {Bukkit, Bukkit 1.18+}
PacketScoreboard.ScoreboardTeam_setPrefix = ReflectionUtils.getMethod(PacketScoreboard.ScoreboardTeam, new String[]{"setPrefix", "b"}, IChatBaseComponent); // {Bukkit, Bukkit 1.18+}
PacketScoreboard.ScoreboardTeam_setSuffix = ReflectionUtils.getMethod(PacketScoreboard.ScoreboardTeam, new String[]{"setSuffix", "c"}, IChatBaseComponent); // {Bukkit, Bukkit 1.18+}
PacketScoreboard.ScoreboardTeam_setNameTagVisibility = ReflectionUtils.getMethod(PacketScoreboard.ScoreboardTeam, new String[]{"setNameTagVisibility", "a"}, PacketScoreboard.EnumNameTagVisibility); // {Bukkit, Bukkit 1.18+}
DataWatcherHelper.DataWatcherSerializer_BYTE = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("a").get(null);
DataWatcherHelper.DataWatcherSerializer_FLOAT = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("c").get(null);
DataWatcherHelper.DataWatcherSerializer_STRING = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("d").get(null);
if (is1_19_3Plus()) {
DataWatcherHelper.DataWatcherSerializer_OPTIONAL_COMPONENT = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("g").get(null);
if (is1_19_4Plus()) {
DataWatcherHelper.DataWatcherSerializer_BOOLEAN = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("k").get(null);
} else {
DataWatcherHelper.DataWatcherSerializer_BOOLEAN = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("j").get(null);
}
} else {
DataWatcherHelper.DataWatcherSerializer_OPTIONAL_COMPONENT = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("f").get(null);
DataWatcherHelper.DataWatcherSerializer_BOOLEAN = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("i").get(null);
}
if (minorVersion >= 19) {
PacketPlayOutSpawnEntityLivingStorage.EntityTypes_ARMOR_STAND = PacketPlayOutSpawnEntityLivingStorage.EntityTypes.getDeclaredField("d").get(null);
(PacketPlayOutSpawnEntityLivingStorage.ENTITY_TYPE = PacketPlayOutSpawnEntityLivingStorage.CLASS.getDeclaredField("e")).setAccessible(true);
DataWatcher.packDirty = DataWatcher.CLASS.getMethod("b");
}
}
@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");
if (minorVersion >= 19) {
PacketPlayOutSpawnEntityLivingStorage.EntityTypes = Class.forName("net.minecraft.world.entity.EntityTypes");
PacketPlayOutSpawnEntityLivingStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutSpawnEntity");
} else {
PacketPlayOutSpawnEntityLivingStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutSpawnEntityLiving");
}
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,97 +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 names.
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public class MojangModernNMSStorage extends NMSStorage {
@Override
public void loadNamedFieldsAndMethods() throws ReflectiveOperationException {
ChatSerializer_DESERIALIZE = ChatSerializer.getMethod("fromJson", String.class);
DataWatcher.REGISTER = DataWatcher.CLASS.getMethod("define", DataWatcherObject.CLASS, Object.class);
PacketScoreboard.ScoreboardScore_setScore = PacketScoreboard.ScoreboardScoreClass.getMethod("setScore", int.class);
PacketScoreboard.ScoreboardTeam_setAllowFriendlyFire = PacketScoreboard.ScoreboardTeam.getMethod("setAllowFriendlyFire", boolean.class);
PacketScoreboard.ScoreboardTeam_setCanSeeFriendlyInvisibles = PacketScoreboard.ScoreboardTeam.getMethod("setSeeFriendlyInvisibles", boolean.class);
PacketScoreboard.ScoreboardTeam_setPrefix = PacketScoreboard.ScoreboardTeam.getMethod("setPlayerPrefix", IChatBaseComponent);
PacketScoreboard.ScoreboardTeam_setSuffix = PacketScoreboard.ScoreboardTeam.getMethod("setPlayerSuffix", IChatBaseComponent);
PacketScoreboard.ScoreboardTeam_setNameTagVisibility = PacketScoreboard.ScoreboardTeam.getMethod("setNameTagVisibility", PacketScoreboard.EnumNameTagVisibility);
DataWatcherHelper.DataWatcherSerializer_BYTE = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("BYTE").get(null);
DataWatcherHelper.DataWatcherSerializer_FLOAT = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("FLOAT").get(null);
DataWatcherHelper.DataWatcherSerializer_STRING = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("STRING").get(null);
DataWatcherHelper.DataWatcherSerializer_OPTIONAL_COMPONENT = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("OPTIONAL_COMPONENT").get(null);
DataWatcherHelper.DataWatcherSerializer_BOOLEAN = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("BOOLEAN").get(null);
if (minorVersion >= 19) {
PacketPlayOutSpawnEntityLivingStorage.EntityTypes_ARMOR_STAND = PacketPlayOutSpawnEntityLivingStorage.EntityTypes.getDeclaredField("ARMOR_STAND").get(null);
(PacketPlayOutSpawnEntityLivingStorage.ENTITY_TYPE = PacketPlayOutSpawnEntityLivingStorage.CLASS.getDeclaredField("type")).setAccessible(true);
DataWatcher.packDirty = DataWatcher.CLASS.getMethod("packDirty");
}
}
@Override
public void loadClasses() throws ClassNotFoundException {
IChatBaseComponent = Class.forName("net.minecraft.network.chat.Component");
ChatSerializer = Class.forName("net.minecraft.network.chat.Component$Serializer");
World = Class.forName("net.minecraft.world.level.Level");
EntityArmorStand = Class.forName("net.minecraft.world.entity.decoration.ArmorStand");
EntityHuman = Class.forName("net.minecraft.world.entity.player.Player");
NetworkManager = Class.forName("net.minecraft.network.Connection");
Packet = Class.forName("net.minecraft.network.protocol.Packet");
EnumChatFormat = (Class<Enum>) Class.forName("net.minecraft.ChatFormatting");
EntityPlayer = Class.forName("net.minecraft.server.level.ServerPlayer");
Entity = Class.forName("net.minecraft.world.entity.Entity");
EntityLiving = Class.forName("net.minecraft.world.entity.LivingEntity");
PlayerConnection = Class.forName("net.minecraft.server.network.ServerGamePacketListenerImpl");
BukkitTabList.PacketPlayOutPlayerListHeaderFooterClass = Class.forName("net.minecraft.network.protocol.game.ClientboundTabListPacket");
DataWatcher.CLASS = Class.forName("net.minecraft.network.syncher.SynchedEntityData");
DataWatcherItem.CLASS = Class.forName("net.minecraft.network.syncher.SynchedEntityData$DataItem");
DataWatcherObject.CLASS = Class.forName("net.minecraft.network.syncher.EntityDataAccessor");
DataWatcherHelper.DataWatcherRegistry = Class.forName("net.minecraft.network.syncher.EntityDataSerializers");
DataWatcherHelper.DataWatcherSerializer = Class.forName("net.minecraft.network.syncher.EntityDataSerializer");
PacketPlayOutEntityTeleportStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.ClientboundTeleportEntityPacket");
PacketPlayOutEntity = Class.forName("net.minecraft.network.protocol.game.ClientboundMoveEntityPacket");
PacketPlayOutEntityDestroyStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.ClientboundRemoveEntitiesPacket");
PacketPlayOutEntityLook = Class.forName("net.minecraft.network.protocol.game.ClientboundMoveEntityPacket$Rot");
PacketPlayOutEntityMetadataStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket");
PacketPlayOutNamedEntitySpawn = Class.forName("net.minecraft.network.protocol.game.ClientboundAddPlayerPacket");
BukkitTabList.EnumGamemodeClass = (Class<Enum>) Class.forName("net.minecraft.world.level.GameType");
PacketScoreboard.DisplayObjectiveClass = Class.forName("net.minecraft.network.protocol.game.ClientboundSetDisplayObjectivePacket");
PacketScoreboard.ObjectivePacketClass = Class.forName("net.minecraft.network.protocol.game.ClientboundSetObjectivePacket");
PacketScoreboard.Scoreboard = Class.forName("net.minecraft.world.scores.Scoreboard");
PacketScoreboard.ScorePacketClass = Class.forName("net.minecraft.network.protocol.game.ClientboundSetScorePacket");
PacketScoreboard.ScoreboardObjective = Class.forName("net.minecraft.world.scores.Objective");
PacketScoreboard.ScoreboardScoreClass = Class.forName("net.minecraft.world.scores.Score");
PacketScoreboard.IScoreboardCriteria = Class.forName("net.minecraft.world.scores.criteria.ObjectiveCriteria");
PacketScoreboard.EnumScoreboardHealthDisplay = (Class<Enum>) Class.forName("net.minecraft.world.scores.criteria.ObjectiveCriteria$RenderType");
PacketScoreboard.EnumScoreboardAction = (Class<Enum>) Class.forName("net.minecraft.server.ServerScoreboard$Method");
PacketScoreboard.TeamPacketClass = Class.forName("net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket");
PacketScoreboard.ScoreboardTeam = Class.forName("net.minecraft.world.scores.PlayerTeam");
PacketScoreboard.EnumNameTagVisibility = (Class<Enum>) Class.forName("net.minecraft.world.scores.Team$Visibility");
PacketScoreboard.EnumTeamPush = (Class<Enum>) Class.forName("net.minecraft.world.scores.Team$CollisionRule");
if (minorVersion >= 19) {
PacketPlayOutSpawnEntityLivingStorage.EntityTypes = Class.forName("net.minecraft.world.entity.EntityType");
PacketPlayOutSpawnEntityLivingStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.ClientboundAddEntityPacket");
} else {
PacketPlayOutSpawnEntityLivingStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.ClientboundAddMobPacket");
}
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$Action");
BukkitTabList.PlayerInfoDataClass = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket$Entry");
BukkitTabList.RemoteChatSession$Data = Class.forName("net.minecraft.network.chat.RemoteChatSession$Data");
} else {
BukkitTabList.PacketPlayOutPlayerInfoClass = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket");
BukkitTabList.EnumPlayerInfoActionClass = (Class<Enum>) Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket$Action");
BukkitTabList.PlayerInfoDataClass = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket$PlayerUpdate");
}
}
}
@@ -1,162 +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.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) {
CHANNEL = ReflectionUtils.getOnlyField(NetworkManager, Channel.class);
getProfile = ReflectionUtils.getOnlyMethod(EntityHuman, GameProfile.class);
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);
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);
loadNamedFieldsAndMethods();
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;
/**
* Loads all fields and methods which must be loaded by their name due to
* lack of other methods of reliably retrieving them.
*
* @throws ReflectiveOperationException
* If a field or method was not found
*/
public abstract void loadNamedFieldsAndMethods() throws ReflectiveOperationException;
/**
* 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,31 +0,0 @@
package me.neznamy.tab.platforms.bukkit.nms.storage.nms;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
import me.neznamy.tab.platforms.bukkit.scoreboard.PacketScoreboard;
import org.jetbrains.annotations.NotNull;
/**
* NMS loader for Thermos 1.7.10.
*/
public class ThermosNMSStorage extends BukkitLegacyNMSStorage {
@Override
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 loadNamedFieldsAndMethods() throws ReflectiveOperationException {
PacketScoreboard.ScoreboardScore_setScore = PacketScoreboard.ScoreboardScoreClass.getMethod("func_96647_c", int.class);
PacketScoreboard.ScoreboardTeam_setAllowFriendlyFire = PacketScoreboard.ScoreboardTeam.getMethod("func_96660_a", boolean.class);
PacketScoreboard.ScoreboardTeam_setCanSeeFriendlyInvisibles = PacketScoreboard.ScoreboardTeam.getMethod("func_98300_b", boolean.class);
DataWatcher.REGISTER = DataWatcher.CLASS.getMethod("func_75682_a", int.class, Object.class);
PacketScoreboard.ScoreboardTeam_setPrefix = PacketScoreboard.ScoreboardTeam.getMethod("func_96666_b", String.class);
PacketScoreboard.ScoreboardTeam_setSuffix = PacketScoreboard.ScoreboardTeam.getMethod("func_96662_c", String.class);
}
}
@@ -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,133 +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 Class<?> EntityTypes;
public static Constructor<?> CONSTRUCTOR;
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 Object EntityTypes_ARMOR_STAND;
/** Entity types */
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 {
if (nms.getMinorVersion() >= 19) {
entityIds.put(EntityType.ARMOR_STAND, 2);
} else if (nms.getMinorVersion() >= 13) {
entityIds.put(EntityType.ARMOR_STAND, 1);
} else {
entityIds.put(EntityType.WITHER, 64);
if (nms.getMinorVersion() >= 8) {
entityIds.put(EntityType.ARMOR_STAND, 30);
}
}
if (nms.getMinorVersion() >= 17) {
CONSTRUCTOR = CLASS.getConstructor(nms.is1_19_3Plus() ? nms.Entity : nms.EntityLiving);
} 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 (nms.getMinorVersion() >= 9) {
UUID = ReflectionUtils.getOnlyField(CLASS, UUID.class);
if (nms.getMinorVersion() >= 19) {
X = ReflectionUtils.getFields(CLASS, double.class).get(2);
Y = ReflectionUtils.getFields(CLASS, double.class).get(3);
Z = ReflectionUtils.getFields(CLASS, double.class).get(4);
} else {
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);
}
if (nms.getMinorVersion() < 19) {
ENTITY_TYPE = ReflectionUtils.getFields(CLASS, int.class).get(1);
}
if (nms.getMinorVersion() <= 14) {
DATA_WATCHER = ReflectionUtils.getOnlyField(CLASS, DataWatcher.CLASS);
}
}
/**
* Converts this class into NMS packet
*
* @return NMS packet
*/
@SneakyThrows
public static Object build(int entityId, UUID id, Object entityType, Location location, EntityData data) {
NMSStorage nms = NMSStorage.getInstance();
Object nmsPacket;
if (nms.getMinorVersion() >= 17) {
nmsPacket = CONSTRUCTOR.newInstance(nms.dummyEntity);
} else {
nmsPacket = CONSTRUCTOR.newInstance();
}
ENTITY_ID.set(nmsPacket, entityId);
YAW.set(nmsPacket, (byte)(location.getYaw() * 256.0f / 360.0f));
PITCH.set(nmsPacket, (byte)(location.getPitch() * 256.0f / 360.0f));
if (nms.getMinorVersion() <= 14) {
DATA_WATCHER.set(nmsPacket, data.build());
}
if (nms.getMinorVersion() >= 9) {
UUID.set(nmsPacket, id);
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));
}
if (nms.getMinorVersion() >= 19) {
ENTITY_TYPE.set(nmsPacket, EntityTypes_ARMOR_STAND); // :(
} else {
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;
}
}
@@ -0,0 +1,285 @@
package me.neznamy.tab.platforms.bukkit.platform;
import lombok.Getter;
import lombok.SneakyThrows;
import me.clip.placeholderapi.PlaceholderAPI;
import me.neznamy.tab.platforms.bukkit.*;
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.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.NMSStorage;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.backend.BackendPlatform;
import me.neznamy.tab.shared.features.PlaceholderManagerImpl;
import me.neznamy.tab.shared.features.bossbar.BossBarManagerImpl;
import me.neznamy.tab.shared.features.nametags.NameTag;
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;
import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.lang.reflect.Field;
/**
* Implementation of Platform interface for Bukkit platform
*/
@Getter
public class BukkitPlatform implements BackendPlatform {
/** Plugin instance for registering tasks and events */
@NotNull
private final JavaPlugin plugin;
/** Variables checking presence of other plugins to hook into */
private final boolean placeholderAPI = ReflectionUtils.classExists("me.clip.placeholderapi.PlaceholderAPI");
/** 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
}
}
@Override
@NotNull
public BossBarManagerImpl getLegacyBossBar() {
return new WitherBossBar(plugin);
}
@Override
public void loadPlayers() {
for (Player p : BukkitUtils.getOnlinePlayers()) {
TAB.getInstance().addPlayer(new BukkitTabPlayer(this, p));
}
}
@Override
public void registerPlaceholders() {
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
@Nullable
public PipelineInjector createPipelineInjector() {
return NMSStorage.getInstance().getMinorVersion() >= 8 ? new BukkitPipelineInjector() : null;
}
@Override
@NotNull
public NameTag getUnlimitedNameTags() {
return new BukkitNameTagX(plugin);
}
@Override
@NotNull
public TabExpansion createTabExpansion() {
if (placeholderAPI) {
BukkitTabExpansion expansion = new BukkitTabExpansion();
expansion.register();
return expansion;
}
return new EmptyTabExpansion();
}
@Override
@Nullable
public TabFeature getPerWorldPlayerList() {
return new PerWorldPlayerList(plugin);
}
@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.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.setPlaceholders(null, identifier));
} else {
TAB.getInstance().getPlaceholderManager().registerPlayerPlaceholder(identifier, refresh,
p -> PlaceholderAPI.setPlaceholders((Player) p.getPlayer(), identifier));
}
}
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);
});
return null;
});
}
@Override
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] +
" (" + Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3] + ")";
}
@Override
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." + TAB.getInstance().getServerVersion().getMinorVersion() + ".x"));
}
@Override
@NotNull
public ProtocolVersion getServerVersion() {
return ProtocolVersion.fromFriendlyName(Bukkit.getBukkitVersion().split("-")[0]);
}
@Override
@NotNull
public File getDataFolder() {
return plugin.getDataFolder();
}
/**
* 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 Object toComponent(@NotNull IChatBaseComponent component, @NotNull ProtocolVersion version) {
return NMSStorage.getInstance().componentCache.get(component, version);
}
@Override
@NotNull
public GroupManager detectPermissionPlugin() {
if (LuckPermsHook.getInstance().isInstalled()) {
return new GroupManager("LuckPerms", LuckPermsHook.getInstance().getGroupFunction());
}
if (Bukkit.getPluginManager().isPluginEnabled("Vault")) {
RegisteredServiceProvider<Permission> provider = Bukkit.getServicesManager().getRegistration(Permission.class);
if (provider != null && !provider.getProvider().getName().equals("SuperPerms")) {
return new GroupManager(provider.getProvider().getName(), p -> provider.getProvider().getPrimaryGroup((Player) p.getPlayer()));
}
}
return new GroupManager("None", p -> TabConstants.NO_GROUP);
}
@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 runEntityTask(@NotNull Entity entity, @NotNull Runnable task) {
task.run();
}
}
@@ -0,0 +1,90 @@
package me.neznamy.tab.platforms.bukkit.platform;
import lombok.SneakyThrows;
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;
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;
/**
* Platform override for Folia
*/
public class FoliaPlatform extends BukkitPlatform {
public FoliaPlatform(@NotNull JavaPlugin plugin) {
super(plugin);
}
@Override
public void loadPlayers() {
super.loadPlayers();
// Values are never updated in the API, warn users
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", () -> {
for (TabPlayer player : TAB.getInstance().getOnlinePlayers()) {
String bukkitWorld = ((Player)player.getPlayer()).getWorld().getName();
if (!player.getWorld().equals(bukkitWorld)) {
TAB.getInstance().getFeatureManager().onWorldChange(player.getUniqueId(), bukkitWorld);
PerWorldPlayerList pwp = TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.PER_WORLD_PLAYER_LIST);
if (pwp != null) {
runSync((Entity) player.getPlayer(), () -> pwp.onWorldChange(new PlayerChangedWorldEvent((Player) player.getPlayer(), ((Player) player.getPlayer()).getWorld())));
}
}
}
});
}
@Override
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 -> {
runSync((Entity) p.getPlayer(), () -> {
long time = System.nanoTime();
String output = isPlaceholderAPI() ? PlaceholderAPI.setPlaceholders((Player) p.getPlayer(), syncedPlaceholder) : identifier;
TAB.getInstance().getCPUManager().addPlaceholderTime(identifier, System.nanoTime()-time);
TAB.getInstance().getCPUManager().runTask(() -> ppl[0].updateValue(p, output)); // To ensure player is loaded
});
return null;
});
}
/**
* Runs task using player's entity scheduler. It's using reflection, because
* Folia uses Java 17 while TAB maintains Java 8 compatibility for compatibility
* with MC versions older than their player base.
*
* @param entity
* entity to run task for
* @param task
* Task to run
*/
@SneakyThrows
@SuppressWarnings("JavaReflectionMemberAccess")
private 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)
.invoke(entityScheduler, getPlugin(), consumer, null);
}
@Override
public void runEntityTask(@NotNull Entity entity, @NotNull Runnable task) {
runSync(entity, task);
}
}
@@ -0,0 +1,220 @@
package me.neznamy.tab.platforms.bukkit.scoreboard;
import lombok.Getter;
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.NameTagVisibility;
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 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 = ReflectionUtils.methodExists(Bukkit.class, "getScoreboardManager");
/** Server's minor version */
private final int serverMinorVersion = BukkitReflection.getMinorVersion();
@NotNull
protected final org.bukkit.scoreboard.Scoreboard scoreboard = Bukkit.getScoreboardManager().getNewScoreboard();
public BukkitScoreboard(@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()));
}
@Override
public void setScore0(@NotNull String objective, @NotNull String playerName, int score) {
checkPlayerScoreboard();
if (serverMinorVersion >= 7 && TAB.getInstance().getServerVersion().getNetworkId() >= ProtocolVersion.V1_7_8.getNetworkId()) {
scoreboard.getObjective(objective).getScore(playerName).setScore(score);
} else {
scoreboard.getObjective(objective).getScore(Bukkit.getOfflinePlayer(playerName)).setScore(score);
}
}
@Override
public void removeScore0(@NotNull String objective, @NotNull String playerName) {
checkPlayerScoreboard();
if (serverMinorVersion >= 7 && TAB.getInstance().getServerVersion().getNetworkId() >= ProtocolVersion.V1_7_8.getNetworkId()) {
scoreboard.resetScores(playerName);
} else {
scoreboard.resetScores(Bukkit.getOfflinePlayer(playerName));
}
}
@Override
public void registerObjective0(@NotNull String objectiveName, @NotNull String title, @NotNull HealthDisplay display) {
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, @NotNull HealthDisplay display) {
checkPlayerScoreboard();
Objective obj = scoreboard.getObjective(objectiveName);
setDisplayName(obj, title);
if (serverMinorVersion >= 14) obj.setRenderType(RenderType.valueOf(display.name()));
}
@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, @NotNull HealthDisplay display) {
if (serverMinorVersion >= 14) {
scoreboard.registerNewObjective(objectiveName, criteria, transform(title, 128, Limitations.SCOREBOARD_TITLE_PRE_1_13), RenderType.valueOf(display.name()));
} 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.
*/
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.");
}
}
}
@@ -1,9 +1,9 @@
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;
@@ -16,37 +16,19 @@ import java.lang.reflect.Method;
import java.util.Collection;
import java.util.List;
import static me.neznamy.tab.platforms.bukkit.nms.BukkitReflection.getLegacyClass;
/**
* 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"})
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 Object emptyScoreboard;
private static Field IScoreboardCriteria_self;
@@ -57,14 +39,12 @@ public class PacketScoreboard extends Scoreboard<BukkitTabPlayer> {
public static Field DisplayObjective_OBJECTIVE_NAME;
// PacketPlayOutScoreboardScore
public static Class<?> ScorePacketClass;
public static Class<?> ScoreboardScoreClass;
public static Class<Enum> EnumScoreboardAction;
private 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;
private static Method ScoreboardScore_setScore;
// PacketPlayOutScoreboardObjective
public static Class<?> ObjectivePacketClass;
@@ -73,8 +53,7 @@ public class PacketScoreboard extends Scoreboard<BukkitTabPlayer> {
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 Class<Enum> EnumScoreboardHealthDisplay;
private static Constructor<?> newScoreboardObjective;
// PacketPlayOutScoreboardTeamStorage
@@ -85,113 +64,259 @@ public class PacketScoreboard extends Scoreboard<BukkitTabPlayer> {
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 Class<Enum> EnumNameTagVisibility;
private static Class<Enum> EnumTeamPush;
private static Constructor<?> newScoreboardTeam;
private static Method ScoreboardTeam_getPlayerNameSet;
public static Method ScoreboardTeam_setNameTagVisibility;
private static Method ScoreboardTeam_setNameTagVisibility;
private static Method ScoreboardTeam_setCollisionRule;
public static Method ScoreboardTeam_setPrefix;
public static Method ScoreboardTeam_setSuffix;
private static Method ScoreboardTeam_setPrefix;
private static Method ScoreboardTeam_setSuffix;
private static Method ScoreboardTeam_setColor;
public static Method ScoreboardTeam_setAllowFriendlyFire;
public static Method ScoreboardTeam_setCanSeeFriendlyInvisibles;
private static Method ScoreboardTeam_setAllowFriendlyFire;
private static Method ScoreboardTeam_setCanSeeFriendlyInvisibles;
public static void load(NMSStorage nms) throws ReflectiveOperationException {
PacketScoreboard.nms = nms;
emptyScoreboard = Scoreboard.getConstructor().newInstance();
private static Enum[] scoreboardActions;
private static Enum[] displaySlots;
private static Enum[] nameVisibilities;
private static Enum[] collisionRules;
private static Enum[] healthDisplays;
private static Enum[] chatFormats;
public static void load() throws ReflectiveOperationException {
int minorVersion = BukkitReflection.getMinorVersion();
Class<?> scoreboardTeam;
Class<?> scoreboardObjective;
Class<?> scoreboardScoreClass;
Class<?> IScoreboardCriteria;
Class<?> scoreboard;
Class<?> scorePacketClass;
Class<?> IChatBaseComponent = null;
Class<Enum> enumChatFormatClass;
if (BukkitReflection.isMojangMapped()) {
IChatBaseComponent = Class.forName("net.minecraft.network.chat.Component");
enumChatFormatClass = (Class<Enum>) Class.forName("net.minecraft.ChatFormatting");
DisplayObjectiveClass = Class.forName("net.minecraft.network.protocol.game.ClientboundSetDisplayObjectivePacket");
ObjectivePacketClass = Class.forName("net.minecraft.network.protocol.game.ClientboundSetObjectivePacket");
scoreboard = Class.forName("net.minecraft.world.scores.Scoreboard");
scorePacketClass = Class.forName("net.minecraft.network.protocol.game.ClientboundSetScorePacket");
scoreboardObjective = Class.forName("net.minecraft.world.scores.Objective");
scoreboardScoreClass = Class.forName("net.minecraft.world.scores.Score");
IScoreboardCriteria = Class.forName("net.minecraft.world.scores.criteria.ObjectiveCriteria");
EnumScoreboardHealthDisplay = (Class<Enum>) Class.forName("net.minecraft.world.scores.criteria.ObjectiveCriteria$RenderType");
EnumScoreboardAction = (Class<Enum>) Class.forName("net.minecraft.server.ServerScoreboard$Method");
TeamPacketClass = Class.forName("net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket");
scoreboardTeam = Class.forName("net.minecraft.world.scores.PlayerTeam");
EnumNameTagVisibility = (Class<Enum>) Class.forName("net.minecraft.world.scores.Team$Visibility");
EnumTeamPush = (Class<Enum>) Class.forName("net.minecraft.world.scores.Team$CollisionRule");
} else if (minorVersion >= 17) {
IChatBaseComponent = Class.forName("net.minecraft.network.chat.IChatBaseComponent");
enumChatFormatClass = (Class<Enum>) Class.forName("net.minecraft.EnumChatFormat");
DisplayObjectiveClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutScoreboardDisplayObjective");
ObjectivePacketClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutScoreboardObjective");
scoreboard = Class.forName("net.minecraft.world.scores.Scoreboard");
scorePacketClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutScoreboardScore");
scoreboardObjective = Class.forName("net.minecraft.world.scores.ScoreboardObjective");
scoreboardScoreClass = Class.forName("net.minecraft.world.scores.ScoreboardScore");
IScoreboardCriteria = Class.forName("net.minecraft.world.scores.criteria.IScoreboardCriteria");
EnumScoreboardHealthDisplay = (Class<Enum>) Class.forName("net.minecraft.world.scores.criteria.IScoreboardCriteria$EnumScoreboardHealthDisplay");
EnumScoreboardAction = (Class<Enum>) Class.forName("net.minecraft.server.ScoreboardServer$Action");
TeamPacketClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutScoreboardTeam");
scoreboardTeam = Class.forName("net.minecraft.world.scores.ScoreboardTeam");
EnumNameTagVisibility = (Class<Enum>) Class.forName("net.minecraft.world.scores.ScoreboardTeamBase$EnumNameTagVisibility");
EnumTeamPush = (Class<Enum>) Class.forName("net.minecraft.world.scores.ScoreboardTeamBase$EnumTeamPush");
} else {
enumChatFormatClass = (Class<Enum>) getLegacyClass("EnumChatFormat");
DisplayObjectiveClass = getLegacyClass("PacketPlayOutScoreboardDisplayObjective", "Packet208SetScoreboardDisplayObjective");
ObjectivePacketClass = getLegacyClass("PacketPlayOutScoreboardObjective", "Packet206SetScoreboardObjective");
TeamPacketClass = getLegacyClass("PacketPlayOutScoreboardTeam", "Packet209SetScoreboardTeam");
scorePacketClass = getLegacyClass("PacketPlayOutScoreboardScore", "Packet207SetScoreboardScore");
scoreboard = getLegacyClass("Scoreboard");
scoreboardObjective = getLegacyClass("ScoreboardObjective");
scoreboardScoreClass = getLegacyClass("ScoreboardScore");
IScoreboardCriteria = getLegacyClass("IScoreboardCriteria", "IObjective"); // 1.5.1+, 1.5
scoreboardTeam = getLegacyClass("ScoreboardTeam");
if (minorVersion >= 7) {
IChatBaseComponent = getLegacyClass("IChatBaseComponent");
}
if (minorVersion >= 8) {
EnumScoreboardHealthDisplay = (Class<Enum>) getLegacyClass("IScoreboardCriteria$EnumScoreboardHealthDisplay", "EnumScoreboardHealthDisplay");
EnumScoreboardAction = (Class<Enum>) getLegacyClass("ScoreboardServer$Action", "PacketPlayOutScoreboardScore$EnumScoreboardAction", "EnumScoreboardAction");
EnumNameTagVisibility = (Class<Enum>) getLegacyClass("ScoreboardTeamBase$EnumNameTagVisibility", "EnumNameTagVisibility");
}
if (minorVersion >= 9) {
EnumTeamPush = (Class<Enum>) getLegacyClass("ScoreboardTeamBase$EnumTeamPush");
}
}
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);
if (BukkitReflection.is1_20_2Plus()) {
Class<?> DisplaySlot = Class.forName("net.minecraft.world.scores.DisplaySlot");
DisplayObjective_POSITION = ReflectionUtils.getOnlyField(DisplayObjectiveClass, DisplaySlot);
newDisplayObjective = DisplayObjectiveClass.getConstructor(DisplaySlot, scoreboardObjective);
displaySlots = (Enum[]) DisplaySlot.getDeclaredMethod("values").invoke(null);
} else {
DisplayObjective_POSITION = ReflectionUtils.getOnlyField(DisplayObjectiveClass, int.class);
newDisplayObjective = DisplayObjectiveClass.getConstructor(int.class, scoreboardObjective);
}
DisplayObjective_OBJECTIVE_NAME = ReflectionUtils.getOnlyField(DisplayObjectiveClass, String.class);
newScoreboardObjective = ReflectionUtils.getOnlyConstructor(ScoreboardObjective);
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);
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);
if (nms.getMinorVersion() >= 13) {
newScorePacket_1_13 = ScorePacketClass.getConstructor(EnumScoreboardAction, String.class, String.class, int.class);
newObjectivePacket = ObjectivePacketClass.getConstructor(ScoreboardObjective, int.class);
Objective_DISPLAY_NAME = ReflectionUtils.getOnlyField(ObjectivePacketClass, nms.IChatBaseComponent);
ScoreboardTeam_setColor = ReflectionUtils.getOnlyMethod(ScoreboardTeam, void.class, nms.EnumChatFormat);
ScoreboardTeam_getPlayerNameSet = ReflectionUtils.getOnlyMethod(scoreboardTeam, Collection.class);
if (minorVersion >= 13) {
newScorePacket_1_13 = scorePacketClass.getConstructor(EnumScoreboardAction, String.class, String.class, int.class);
newObjectivePacket = ObjectivePacketClass.getConstructor(scoreboardObjective, int.class);
Objective_DISPLAY_NAME = ReflectionUtils.getOnlyField(ObjectivePacketClass, IChatBaseComponent);
ScoreboardTeam_setColor = ReflectionUtils.getOnlyMethod(scoreboardTeam, void.class, enumChatFormatClass);
scoreboardActions = new Enum[] {
Enum.valueOf(EnumScoreboardAction, "CHANGE"),
Enum.valueOf(EnumScoreboardAction, "REMOVE")
};
chatFormats = (Enum[]) enumChatFormatClass.getMethod("values").invoke(null);
} else {
newScorePacket_String = ScorePacketClass.getConstructor(String.class);
newScorePacket_String = scorePacketClass.getConstructor(String.class);
newObjectivePacket = ObjectivePacketClass.getConstructor();
Objective_DISPLAY_NAME = ReflectionUtils.getFields(ObjectivePacketClass, String.class).get(1);
if (nms.getMinorVersion() >= 8) {
newScorePacket = ScorePacketClass.getConstructor(ScoreboardScoreClass);
if (minorVersion >= 8) {
newScorePacket = scorePacketClass.getConstructor(scoreboardScoreClass);
Objective_RENDER_TYPE = ReflectionUtils.getOnlyField(ObjectivePacketClass, EnumScoreboardHealthDisplay);
} else {
newScorePacket = ScorePacketClass.getConstructor(ScoreboardScoreClass, int.class);
newScorePacket = scorePacketClass.getConstructor(scoreboardScoreClass, int.class);
}
}
if (nms.getMinorVersion() >= 9) {
ScoreboardTeam_setCollisionRule = ReflectionUtils.getOnlyMethod(ScoreboardTeam, void.class, EnumTeamPush);
if (minorVersion >= 8) {
nameVisibilities = new Enum[] {
Enum.valueOf(EnumNameTagVisibility, "ALWAYS"),
Enum.valueOf(EnumNameTagVisibility, "NEVER"),
Enum.valueOf(EnumNameTagVisibility, "HIDE_FOR_OTHER_TEAMS"),
Enum.valueOf(EnumNameTagVisibility, "HIDE_FOR_OWN_TEAM")
};
healthDisplays = new Enum[] {
Enum.valueOf(EnumScoreboardHealthDisplay, "INTEGER"),
Enum.valueOf(EnumScoreboardHealthDisplay, "HEARTS")
};
}
if (nms.getMinorVersion() >= 17) {
TeamPacketConstructor_of = ReflectionUtils.getOnlyMethod(TeamPacketClass, TeamPacketClass, ScoreboardTeam);
TeamPacketConstructor_ofBoolean = ReflectionUtils.getOnlyMethod(TeamPacketClass, TeamPacketClass, ScoreboardTeam, boolean.class);
if (minorVersion >= 9) {
ScoreboardTeam_setCollisionRule = ReflectionUtils.getOnlyMethod(scoreboardTeam, void.class, EnumTeamPush);
collisionRules = (Enum[]) EnumTeamPush.getMethod("values").invoke(null); // <1.13 has wrong enum constant names
}
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);
newTeamPacket = TeamPacketClass.getConstructor(scoreboardTeam, int.class);
}
ScoreboardScore_setScore = ReflectionUtils.getMethod(
scoreboardScoreClass,
new String[] {"func_96647_c", "setScore", "b", "c", "m_83402_"}, // {Thermos, 1.5.1 - 1.17.1 & 1.20.2+, 1.18 - 1.20.1, 1.5, Mohist 1.18.2}
int.class
);
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 >= 13) {
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+}
IChatBaseComponent
);
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+}
IChatBaseComponent
);
} 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 >= 8) {
ScoreboardTeam_setNameTagVisibility = ReflectionUtils.getMethod(
scoreboardTeam,
new String[] {"setNameTagVisibility", "a", "m_83346_"}, // {1.8.1+, 1.8 & 1.18+, Mohist 1.18.2}
EnumNameTagVisibility
);
}
available = true;
}
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)));
Object displaySlot;
if (BukkitReflection.is1_20_2Plus()) {
displaySlot = displaySlots[slot.ordinal()];
} else {
displaySlot = slot.ordinal();
}
player.sendPacket(newDisplayObjective.newInstance(displaySlot, newScoreboardObjective(objective)));
}
@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, @NotNull HealthDisplay display) {
player.sendPacket(buildObjective(0, objectiveName, title, display));
}
@Override
public void unregisterObjective0(@NotNull String objectiveName) {
player.sendPacket(buildObjective(1, objectiveName, "", false));
player.sendPacket(buildObjective(1, objectiveName, "", HealthDisplay.INTEGER));
}
@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, @NotNull HealthDisplay display) {
player.sendPacket(buildObjective(2, objectiveName, title, display));
}
@SneakyThrows
private Object buildObjective(int action, String objectiveName, String title, boolean hearts) {
if (nms.getMinorVersion() >= 13) {
private Object buildObjective(int action, String objectiveName, String title, @NotNull HealthDisplay display) {
if (BukkitReflection.getMinorVersion() >= 13) {
return newObjectivePacket.newInstance(
newScoreboardObjective.newInstance(null, objectiveName, null,
nms.toNMSComponent(IChatBaseComponent.optimizedComponent(title), player.getVersion()),
asDisplayType(hearts)),
newScoreboardObjective.newInstance(
null,
objectiveName,
null,
toComponent(title),
healthDisplays[display.ordinal()]
),
action
);
}
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));
if (BukkitReflection.getMinorVersion() >= 8) {
Objective_RENDER_TYPE.set(nmsPacket, healthDisplays[display.ordinal()]);
}
Objective_METHOD.set(nmsPacket, action);
return nmsPacket;
}
private Object asDisplayType(boolean hearts) {
return Enum.valueOf(EnumScoreboardHealthDisplay, hearts ? "HEARTS" : "INTEGER");
}
@Override
@SneakyThrows
public void registerTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix,
@@ -199,7 +324,7 @@ public class PacketScoreboard extends Scoreboard<BukkitTabPlayer> {
@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) {
if (BukkitReflection.getMinorVersion() >= 17) {
player.sendPacket(TeamPacketConstructor_ofBoolean.invoke(null, team, true));
} else {
player.sendPacket(newTeamPacket.newInstance(team, 0));
@@ -210,7 +335,7 @@ public class PacketScoreboard extends Scoreboard<BukkitTabPlayer> {
@SneakyThrows
public void unregisterTeam0(@NotNull String name) {
Object team = newScoreboardTeam.newInstance(emptyScoreboard, name);
if (nms.getMinorVersion() >= 17) {
if (BukkitReflection.getMinorVersion() >= 17) {
player.sendPacket(TeamPacketConstructor_of.invoke(null, team));
} else {
player.sendPacket(newTeamPacket.newInstance(team, 1));
@@ -222,7 +347,7 @@ public class PacketScoreboard extends Scoreboard<BukkitTabPlayer> {
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) {
if (BukkitReflection.getMinorVersion() >= 17) {
player.sendPacket(TeamPacketConstructor_ofBoolean.invoke(null, team, false));
} else {
player.sendPacket(newTeamPacket.newInstance(team, 2));
@@ -230,46 +355,47 @@ public class PacketScoreboard extends Scoreboard<BukkitTabPlayer> {
}
@SneakyThrows
private Object createTeam(String teamName, String prefix, String suffix, NameVisibility visibility, CollisionRule collision, int options) {
private Object createTeam(@NotNull String teamName, @NotNull String prefix, @NotNull String suffix,
@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 (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()));
if (BukkitReflection.getMinorVersion() >= 13) {
ScoreboardTeam_setPrefix.invoke(team, toComponent(prefix));
ScoreboardTeam_setSuffix.invoke(team, toComponent(suffix));
ScoreboardTeam_setColor.invoke(team, chatFormats[EnumChatFormat.lastColorsOf(prefix).ordinal()]);
} 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()));
if (BukkitReflection.getMinorVersion() >= 8)
ScoreboardTeam_setNameTagVisibility.invoke(team, nameVisibilities[visibility.ordinal()]);
if (BukkitReflection.getMinorVersion() >= 9)
ScoreboardTeam_setCollisionRule.invoke(team, collisionRules[collision.ordinal()]);
return team;
}
@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);
if (BukkitReflection.getMinorVersion() >= 13) {
player.sendPacket(newScorePacket_1_13.newInstance(scoreboardActions[0], 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);
if (BukkitReflection.getMinorVersion() >= 8) {
player.sendPacket(newScorePacket.newInstance(scoreboardScore));
} else {
packet = newScorePacket.newInstance(scoreboardScore, 0);
player.sendPacket(newScorePacket.newInstance(scoreboardScore, 0));
}
}
player.sendPacket(packet);
}
@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));
if (BukkitReflection.getMinorVersion() >= 13) {
player.sendPacket(newScorePacket_1_13.newInstance(scoreboardActions[1], objective, playerName, 0));
} else {
player.sendPacket(newScorePacket_String.newInstance(playerName));
}
@@ -284,12 +410,12 @@ public class PacketScoreboard extends Scoreboard<BukkitTabPlayer> {
*/
@SneakyThrows
public Object newScoreboardObjective(@NotNull String objectiveName) {
if (nms.getMinorVersion() >= 13) {
if (BukkitReflection.getMinorVersion() >= 13) {
return newScoreboardObjective.newInstance(
null,
objectiveName,
null,
nms.toNMSComponent(new IChatBaseComponent(""), TAB.getInstance().getServerVersion()),
toComponent(""),
null
);
}
@@ -299,4 +425,8 @@ public class PacketScoreboard extends Scoreboard<BukkitTabPlayer> {
IScoreboardCriteria_self.get(null)
);
}
private Object toComponent(String text) {
return player.getPlatform().toComponent(IChatBaseComponent.optimizedComponent(text), player.getVersion());
}
}
@@ -1,18 +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.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.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
@@ -21,94 +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.
*/
@SuppressWarnings("ConstantConditions") // Throw the NPE if something is not as expected, parent class should ensure it anyway
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, @NotNull HealthDisplay display) {
scoreboard.registerNewObjective(objectiveName, criteria, toAdventure(title), RenderType.valueOf(display.name()));
}
@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.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.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);
}
/**
@@ -118,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());
}
}
+1 -1
View File
@@ -11,7 +11,7 @@ softdepend: [PlaceholderAPI, LuckPerms, LibsDisguises, ViaVersion, ViaRewind, fl
loadbefore: []
prefix: TAB
libraries: []
folia-supported: false
folia-supported: true
commands:
tab:
description: Plugin's main command
@@ -4,7 +4,6 @@ import lombok.RequiredArgsConstructor;
import me.neznamy.tab.shared.platform.bossbar.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 +17,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 +34,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()));
BungeeMultiVersion.setTitle(packet, 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 +48,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);
BungeeMultiVersion.setTitle(packet, title, player.getVersion());
player.sendPacket(packet);
}
@Override
@@ -55,7 +58,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 +68,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 +80,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 +89,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,50 @@ 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;
tab.getCPUManager().runTask(() -> {
TabPlayer player = tab.getPlayer(e.getPlayer().getUniqueId());
if (player == null) {
player = createPlayer(e.getPlayer());
// I don't think this is actually needed, but someone said it fixed geyser warn in console
// Doing this won't hurt, so whatever
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);
}
}
@@ -0,0 +1,200 @@
package me.neznamy.tab.platforms.bungeecord;
import lombok.SneakyThrows;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.chat.ChatModifier;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.md_5.bungee.UserConnection;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.protocol.DefinedPacket;
import net.md_5.bungee.protocol.packet.*;
import net.md_5.bungee.protocol.packet.PlayerListItem.Item;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.InvocationTargetException;
import java.util.stream.Collectors;
public class BungeeMultiVersion {
/**
* Flag tracking if the current BungeeCord version contains changes made in build 1732.
* This build changes "ScoreboardDisplay" field "position" from "byte" to "int".
*/
private static final boolean b1732Plus = ReflectionUtils.getFields(ScoreboardDisplay.class, int.class).size() > 0;
/**
* Flag tracking if the current BungeeCord version contains changes made in build 1738.
* This build adds "sendPacketQueued" method to UserConnection class.
*/
private static final boolean b1738Plus = ReflectionUtils.methodExists(UserConnection.class, "sendPacketQueued", DefinedPacket.class);
/**
* Flag tracking if the current BungeeCord version contains changes made in build 1760.
* This build changes all packet fields for displaying information from String to BaseComponent.
*/
private static final boolean b1760Plus = ReflectionUtils.methodExists(BossBar.class, "setTitle", BaseComponent.class);
@SneakyThrows
@NotNull
public static ScoreboardDisplay newScoreboardDisplay(int position, @NotNull String name) {
if (b1732Plus) {
return ScoreboardDisplay.class.getConstructor(int.class, String.class).newInstance(position, name);
} else {
return new ScoreboardDisplay((byte) position, name);
}
}
@SneakyThrows
public static void sendPacket(@NotNull BungeeTabPlayer player, @NotNull DefinedPacket packet) {
if (b1738Plus) {
try {
UserConnection.class.getDeclaredMethod("sendPacketQueued", DefinedPacket.class).invoke(player.getPlayer(), packet);
} catch (InvocationTargetException BungeeCordBug) { // Since we use reflection we must catch this one
// 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 " + player.getName() +
" (online = " + player.getPlayer().isConnected() + "): " + packet, BungeeCordBug);
}
}
} else {
player.getPlayer().unsafe().sendPacket(packet);
}
}
@SneakyThrows
public static void setDisplayName(@NotNull Item item, @NotNull IChatBaseComponent displayName, @NotNull ProtocolVersion version) {
if (b1760Plus) {
Item.class.getMethod("setDisplayName", BaseComponent.class).invoke(item, toComponent(displayName, version));
} else {
item.setDisplayName(displayName.toString(version));
}
}
@SneakyThrows
@NotNull
public static PlayerListHeaderFooter newPlayerListHeaderFooter(@NotNull IChatBaseComponent header,
@NotNull IChatBaseComponent footer, ProtocolVersion version) {
if (b1760Plus) {
return PlayerListHeaderFooter.class.getConstructor(BaseComponent.class, BaseComponent.class).newInstance(
toComponent(header, version),
toComponent(footer, version)
);
} else {
return new PlayerListHeaderFooter(
header.toString(version),
footer.toString(version)
);
}
}
@SneakyThrows
public static void setTitle(@NotNull BossBar bossBar, @NotNull String title, @NotNull ProtocolVersion version) {
if (b1760Plus) {
BossBar.class.getMethod("setTitle", BaseComponent.class).invoke(bossBar, toComponent(IChatBaseComponent.optimizedComponent(title), version));
} else {
bossBar.setTitle(IChatBaseComponent.optimizedComponent(title).toString(version));
}
}
@NotNull
@SneakyThrows
public static ScoreboardObjective newScoreboardObjective(@NotNull String objective, @NotNull String title,
@Nullable ScoreboardObjective.HealthDisplay healthDisplay, byte action,
@NotNull ProtocolVersion version) {
if (b1760Plus) {
return ScoreboardObjective.class
.getConstructor(String.class, BaseComponent.class, ScoreboardObjective.HealthDisplay.class, byte.class)
.newInstance(objective, toComponent(IChatBaseComponent.optimizedComponent(title), version), healthDisplay, action);
} else {
return new ScoreboardObjective(objective, jsonOrRaw(title, version), healthDisplay, action);
}
}
@NotNull
@SneakyThrows
public static Team newTeam(@NotNull String name, byte mode, @NotNull String prefix, @NotNull String suffix,
@NotNull String nameTagVisibility, @NotNull String collisionRule, int color,
byte options, @Nullable String[] players, @NotNull ProtocolVersion version) {
if (b1760Plus) {
return Team.class.getConstructor(String.class, byte.class, BaseComponent.class, BaseComponent.class,
BaseComponent.class, String.class, String.class, int.class, byte.class, String[].class)
.newInstance(
name,
mode,
toComponent(IChatBaseComponent.optimizedComponent(name), version),
toComponent(IChatBaseComponent.optimizedComponent(prefix), version),
toComponent(IChatBaseComponent.optimizedComponent(suffix), version),
nameTagVisibility,
collisionRule,
color,
options,
players
);
} else {
return new Team(
name,
mode,
jsonOrRaw(name, version),
jsonOrRaw(prefix, version),
jsonOrRaw(suffix, version),
nameTagVisibility,
collisionRule,
color,
options,
players
);
}
}
/**
* If player's version is 1.13+, creates a component from given text and returns
* it as a serialized component, which BungeeCord uses.
* <p>
* If player's version is 1.12-, the text is returned
*
* @param text
* Text to convert
* @param version
* Protocol version to create json string for
* @return serialized component for 1.13+ clients, cut string for 1.12-
*/
@NotNull
private static String jsonOrRaw(@NotNull String text, @NotNull ProtocolVersion version) {
if (version.getMinorVersion() >= 13) {
return IChatBaseComponent.optimizedComponent(text).toString(version);
} else {
return text;
}
}
/**
* 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 static BaseComponent toComponent(@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);
if (!component.getExtra().isEmpty()) textComponent.setExtra(
component.getExtra().stream().map(c -> toComponent(c, version)).collect(Collectors.toList()));
return textComponent;
}
}
@@ -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,60 @@ 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(directionData.getClass().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
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) {
} catch (IllegalAccessException exception) {
TAB.getInstance().getErrorManager().criticalError("Failed to get channel of " + bungee.getPlayer().getName(), exception);
}
return null;
}
@Override
@SneakyThrows
public void onDisplayObjective(@NotNull TabPlayer player, @NotNull Object packet) {
TAB.getInstance().getFeatureManager().onDisplayObjective(player,
((ScoreboardDisplay) packet).getPosition(), ((ScoreboardDisplay) packet).getName());
TAB.getInstance().getFeatureManager().onDisplayObjective(
player,
((Number) ScoreboardDisplay.class.getMethod("getPosition").invoke(packet)).intValue(),
((ScoreboardDisplay) packet).getName()
);
}
@Override
@@ -110,10 +133,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 +146,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());
}
}
}
@@ -139,7 +163,13 @@ public class BungeePipelineInjector extends NettyPipelineInjector {
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()));
if (newDisplayName != null) BungeeMultiVersion.setDisplayName(item, 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());
}
}
} else {
@@ -147,12 +177,23 @@ public class BungeePipelineInjector extends NettyPipelineInjector {
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) BungeeMultiVersion.setDisplayName(item, 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());
}
}
}
}
@Override
public boolean isLogin(@NotNull Object packet) {
return packet instanceof Login;
}
/**
* Constructs new instance of the feature
*/
@@ -173,12 +214,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 +234,13 @@ 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) {
int marker = buf.readerIndex();
try {
int packetId = buf.readByte();
for (int i=0; i<extraPacketClasses.length; i++) {
if (packetId == ((BungeeTabPlayer)player).getPacketId(extraPacketClasses[i])) {
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 +253,25 @@ 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
*/
private int getPacketId(int protocolVersion, @NotNull Class<? extends DefinedPacket> clazz) {
if (getId == null) return -1;
try {
return (int) getId.invoke(directionData, clazz, protocolVersion);
} catch (ReflectiveOperationException e) {
TAB.getInstance().getErrorManager().printError("Failed to get packet id for packet " + clazz +
" with client version " + protocolVersion, e);
return -1;
}
}
}
}
@@ -2,53 +2,98 @@ package me.neznamy.tab.platforms.bungeecord;
import com.imaginarycode.minecraft.redisbungee.RedisBungeeAPI;
import lombok.AllArgsConstructor;
import me.neznamy.tab.platforms.bungeecord.features.BungeeRedisSupport;
import me.neznamy.tab.shared.TAB;
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.features.injection.PipelineInjector;
import me.neznamy.tab.shared.features.redis.RedisSupport;
import me.neznamy.tab.shared.hook.ViaVersionHook;
import me.neznamy.tab.shared.proxy.ProxyPlatform;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.md_5.bungee.api.ProxyServer;
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;
/**
* BungeeCord implementation of Platform
*/
@AllArgsConstructor
public class BungeePlatform extends ProxyPlatform {
@NotNull private final BungeeTAB plugin;
@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);
}
}
@@ -1,10 +1,8 @@
package me.neznamy.tab.platforms.bungeecord;
import me.neznamy.tab.shared.ProtocolVersion;
import lombok.SneakyThrows;
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;
@@ -19,39 +17,69 @@ import java.util.Collection;
*/
public class BungeeScoreboard extends Scoreboard<BungeeTabPlayer> {
public BungeeScoreboard(BungeeTabPlayer player) {
public BungeeScoreboard(@NotNull BungeeTabPlayer player) {
super(player);
}
@Override
@SneakyThrows
public void setDisplaySlot(@NotNull DisplaySlot slot, @NotNull String objective) {
player.sendPacket(new ScoreboardDisplay((byte)slot.ordinal(), objective));
player.sendPacket(BungeeMultiVersion.newScoreboardDisplay(slot.ordinal(), 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, @NotNull HealthDisplay display) {
player.sendPacket(BungeeMultiVersion.newScoreboardObjective(
objectiveName,
title,
ScoreboardObjective.HealthDisplay.valueOf(display.name()),
(byte) 0,
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(BungeeMultiVersion.newScoreboardObjective(
objectiveName,
"", // Empty value instead of null to prevent NPE kick on 1.7
null,
(byte) 1,
player.getVersion()
));
}
@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, @NotNull HealthDisplay display) {
player.sendPacket(BungeeMultiVersion.newScoreboardObjective(
objectiveName,
title,
ScoreboardObjective.HealthDisplay.valueOf(display.name()),
(byte) 2,
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(BungeeMultiVersion.newTeam(
name,
(byte) 0,
prefix,
suffix,
visibility.toString(),
collision.toString(),
color,
(byte)options,
players.toArray(new String[0]),
player.getVersion()
));
}
@Override
@@ -60,34 +88,24 @@ 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));
}
/**
* 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);
} else {
return text;
}
player.sendPacket(BungeeMultiVersion.newTeam(
name,
(byte) 2,
prefix,
suffix,
visibility.toString(),
collision.toString(),
color,
(byte)options,
null,
player.getVersion()
));
}
@Override
@@ -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."));
getLogger().info(EnumChatFormat.color("&cThe plugin requires BungeeCord build #1671 " +
"(released on December 7th, 2022) 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);
}
@@ -2,6 +2,7 @@ package me.neznamy.tab.platforms.bungeecord;
import de.myzelyam.api.vanish.BungeeVanishAPI;
import lombok.Getter;
import lombok.SneakyThrows;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.platforms.bungeecord.tablist.BungeeTabList1193;
import me.neznamy.tab.platforms.bungeecord.tablist.BungeeTabList17;
@@ -13,7 +14,6 @@ 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.proxy.ProxyTabPlayer;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.chat.ComponentSerializer;
@@ -21,51 +21,46 @@ 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 +73,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())));
}
@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 +129,41 @@ 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 (premiumVanish && 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;
}
getPlayer().getServer().sendData(TabConstants.PLUGIN_MESSAGE_CHANNEL_NAME, message);
/**
* 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
*/
@SneakyThrows
public void sendPacket(@NotNull DefinedPacket packet) {
if (!getPlayer().isConnected()) return;
BungeeMultiVersion.sendPacket(this, packet);
}
}
@@ -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,71 @@
package me.neznamy.tab.platforms.bungeecord.tablist;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bungeecord.BungeeMultiVersion;
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.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(BungeeMultiVersion.newPlayerListHeaderFooter(header, 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) {
BungeeMultiVersion.setDisplayName(item, 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,8 @@
package me.neznamy.tab.platforms.bungeecord.tablist;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.platforms.bungeecord.BungeeMultiVersion;
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 +15,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) BungeeMultiVersion.setDisplayName(item, displayName, player.getVersion());
sendPacket(EnumSet.of(PlayerListItemUpdate.Action.UPDATE_DISPLAY_NAME), item);
}
@@ -57,35 +53,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,9 @@
package me.neznamy.tab.platforms.bungeecord.tablist;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.platforms.bungeecord.BungeeMultiVersion;
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 +18,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 +63,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
@@ -75,19 +78,14 @@ public class BungeeTabList17 implements TabList {
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);
BungeeMultiVersion.setDisplayName(item, IChatBaseComponent.optimizedComponent(displayName), player.getVersion());
item.setPing(latency);
return item;
}
@Override
public void setPlayerListHeaderFooter(@NotNull IChatBaseComponent header, @NotNull IChatBaseComponent footer) {
// Added in 1.8
}
}
@@ -1,11 +1,8 @@
package me.neznamy.tab.platforms.bungeecord.tablist;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.platforms.bungeecord.BungeeMultiVersion;
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 +13,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) BungeeMultiVersion.setDisplayName(item, displayName, player.getVersion());
sendPacket(PlayerListItem.Action.UPDATE_DISPLAY_NAME, item);
}
@@ -53,34 +49,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);
}
}
+7 -11
View File
@@ -1,12 +1,10 @@
// 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 minecraftVersion = "1.20.2"
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.2" to "0.88.5+1.20.2",
"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",
@@ -28,11 +26,7 @@ val fabricApiVersions = mapOf(
"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,
"1.14.4" to "0.28.5+1.14",
)
plugins {
@@ -41,7 +35,7 @@ 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")
@@ -58,8 +52,10 @@ dependencies {
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]))
if (minecraftVersion.split(".")[1].toInt() >= 16) {
modImplementation(fabricApi.module("fabric-entity-events-v1", fabricApiVersions[minecraftVersion]))
}
}
loom {
@@ -9,45 +9,69 @@ import me.neznamy.tab.api.bossbar.BarStyle;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.bossbar.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;
@RequiredArgsConstructor
public class FabricBossBar implements BossBar {
/** Color array for fast access */
private static final BossBarColor[] colors = new BossBarColor[] {
BossBarColor.PINK,
BossBarColor.BLUE,
BossBarColor.RED,
BossBarColor.GREEN,
BossBarColor.YELLOW,
BossBarColor.PURPLE,
BossBarColor.WHITE
};
/** Overlay array for fast access */
private static final BossBarOverlay[] overlays = new BossBarOverlay[] {
BossBarOverlay.PROGRESS,
BossBarOverlay.NOTCHED_6,
BossBarOverlay.NOTCHED_10,
BossBarOverlay.NOTCHED_12,
BossBarOverlay.NOTCHED_20
};
@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()),
colors[color.ordinal()],
overlays[style.ordinal()]
);
bar.setProgress(progress);
FabricMultiVersion.setProgress(bar, progress);
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);
FabricMultiVersion.setProgress(bars.get(id), progress);
}
@Override
public void update(@NotNull UUID id, @NotNull BarStyle style) {
bars.get(id).setOverlay(BossEvent.BossBarOverlay.valueOf(style.name()));
bars.get(id).setOverlay(overlays[style.ordinal()]);
}
@Override
public void update(@NotNull UUID id, @NotNull BarColor color) {
bars.get(id).setColor(BossEvent.BossBarColor.valueOf(color.name()));
bars.get(id).setColor(colors[color.ordinal()]);
}
@Override
@@ -0,0 +1,98 @@
package me.neznamy.tab.platforms.fabric;
import me.neznamy.tab.shared.backend.EntityData;
import me.neznamy.tab.shared.backend.Location;
import me.neznamy.tab.shared.backend.entityview.EntityView;
import net.minecraft.network.protocol.game.*;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.decoration.ArmorStand;
import org.jetbrains.annotations.NotNull;
import java.util.UUID;
public record FabricEntityView(FabricTabPlayer player) implements EntityView {
@NotNull
private static final Entity 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(FabricMultiVersion.addEntity(entityId, id, entityType, location, data));
updateEntityMetadata(entityId, data);
}
@Override
public void updateEntityMetadata(int entityId, @NotNull EntityData data) {
player.sendPacket(FabricMultiVersion.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) {
FabricMultiVersion.destroyEntities(player, entities);
}
@Override
public boolean isDestroyPacket(@NotNull Object packet) {
return FabricMultiVersion.isEntityDestroyPacket(packet);
}
@Override
public boolean isTeleportPacket(@NotNull Object packet) {
return packet instanceof ClientboundTeleportEntityPacket;
}
@Override
public boolean isNamedEntitySpawnPacket(@NotNull Object packet) {
return packet instanceof ClientboundAddEntityPacket;
}
@Override
public boolean isMovePacket(@NotNull Object packet) {
return packet instanceof ClientboundMoveEntityPacket;
}
@Override
public boolean isLookPacket(@NotNull Object packet) {
return packet instanceof ClientboundMoveEntityPacket.Rot;
}
@Override
public int getTeleportEntityId(@NotNull Object teleportPacket) {
return ((ClientboundTeleportEntityPacket) teleportPacket).getId();
}
@Override
public int getMoveEntityId(@NotNull Object movePacket) {
return ((ClientboundMoveEntityPacket) movePacket).entityId;
}
@Override
public int getSpawnedPlayer(@NotNull Object playerSpawnPacket) {
return ((ClientboundAddEntityPacket) playerSpawnPacket).getId();
}
@Override
public int[] getDestroyedEntities(@NotNull Object destroyPacket) {
return FabricMultiVersion.getDestroyedEntities(destroyPacket);
}
@Override
public boolean isBundlePacket(@NotNull Object packet) {
return FabricMultiVersion.isBundlePacket(packet);
}
@SuppressWarnings("unchecked")
@Override
public Iterable<Object> getPackets(@NotNull Object bundlePacket) {
return (Iterable<Object>) (Object) FabricMultiVersion.getPackets(bundlePacket);
}
}
@@ -1,23 +1,24 @@
package me.neznamy.tab.platforms.fabric;
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.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));
FabricMultiVersion.registerEntityEvents(this::replacePlayer, this::worldChange);
//TODO command preprocess
}
@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
}
@@ -0,0 +1,324 @@
package me.neznamy.tab.platforms.fabric;
import com.mojang.authlib.properties.Property;
import me.neznamy.tab.platforms.fabric.features.FabricNameTagX;
import me.neznamy.tab.shared.ProtocolVersion;
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.platform.TabList;
import me.neznamy.tab.shared.platform.TabPlayer;
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.EntityDataSerializers;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.server.level.ServerBossEvent;
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 org.jetbrains.annotations.NotNull;
import java.util.*;
import java.util.function.BiConsumer;
/**
* Class that contains (almost) all code that needs to be changed
* to set compatibility with any MC version that fabric supports (1.14.4+).
*/
public class FabricMultiVersion {
private static final int armorStandFlagsPosition = getArmorStandFlagsPosition();
private static int getArmorStandFlagsPosition() {
int minorVersion = ProtocolVersion.fromFriendlyName(SharedConstants.getCurrentVersion().getName()).getMinorVersion();
if (minorVersion >= 17) {
//1.17.x, 1.18.x, 1.19.x, 1.20.x
return 15;
} else if (minorVersion >= 15) {
//1.15.x, 1.16.x
return 14;
} else {
//1.14.x
return 13;
}
}
public static String getWorldName(Level level) {
// 1.15.2-
// return level.getLevelData().getLevelName() + level.dimension.getType().getFileSuffix();
// 1.16+
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;
}
public static void sendSystemMessage(CommandSourceStack source, Component message) {
// 1.19.4-
// source.sendSuccess(message, false);
// 1.19.1+
source.sendSystemMessage(message);
}
public static boolean isSneaking(ServerPlayer player) {
// 1.14.4-
// return player.isSneaking();
// 1.15+
return player.isCrouching();
}
public static void registerNameTagXEvents(FabricNameTagX nameTagX) {
// 1.16+
net.fabricmc.fabric.api.entity.event.v1.ServerPlayerEvents.AFTER_RESPAWN.register((oldPlayer, newPlayer, alive) -> {
if (nameTagX.enabled) nameTagX.respawn(oldPlayer.getUUID());
});
// TODO sneaking
}
public static void registerEntityEvents(BiConsumer<UUID, ServerPlayer> respawnAction, BiConsumer<UUID, String> worldChangeAction) {
// 1.16+
net.fabricmc.fabric.api.entity.event.v1.ServerPlayerEvents.AFTER_RESPAWN.register(
(oldPlayer, newPlayer, alive) -> {
respawnAction.accept(newPlayer.getUUID(), newPlayer);
// respawning from death & taking end portal in the end do not call world change event
worldChangeAction.accept(newPlayer.getUUID(), getWorldName(newPlayer.level));
});
net.fabricmc.fabric.api.entity.event.v1.ServerEntityWorldChangeEvents.AFTER_PLAYER_CHANGE_WORLD.register(
(player, origin, destination) -> worldChangeAction.accept(player.getUUID(), getWorldName(destination)));
}
public static ClientboundTabListPacket setHeaderFooter(Component header, Component footer) {
// 1.16.5-
/*ClientboundTabListPacket packet = new ClientboundTabListPacket();
packet.header = header;
packet.footer = footer;
return packet;*/
// 1.17+
return new ClientboundTabListPacket(header, footer);
}
public static ClientboundSetPlayerTeamPacket createTeam(PlayerTeam team) {
// 1.16.5-
// return new ClientboundSetPlayerTeamPacket(team, 0);
// 1.17+
return ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, true);
}
public static ClientboundSetPlayerTeamPacket removeTeam(PlayerTeam team) {
// 1.16.5-
// return new ClientboundSetPlayerTeamPacket(team, 1);
// 1.17+
return ClientboundSetPlayerTeamPacket.createRemovePacket(team);
}
public static ClientboundSetPlayerTeamPacket updateTeam(PlayerTeam team) {
// 1.16.5-
// return new ClientboundSetPlayerTeamPacket(team, 2);
// 1.17+
return ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, false);
}
public static void setProgress(ServerBossEvent bar, float progress) {
// 1.16.5-
// bar.setPercent(progress);
// 1.17+
bar.setProgress(progress);
}
public static int[] getDestroyedEntities(Object destroyPacket) {
// 1.16.5-
// return ((ClientboundRemoveEntitiesPacket) destroyPacket).getEntityIds();
// 1.17
// return new int[]{((ClientboundRemoveEntityPacket)destroyPacket).getEntityId()};
// 1.17.1+
return ((ClientboundRemoveEntitiesPacket) destroyPacket).getEntityIds().toIntArray();
}
public static void destroyEntities(FabricTabPlayer player, int... entities) {
// 1.17
/*for (int i : entities) {
player.sendPacket(new ClientboundRemoveEntityPacket(i));
}*/
// every other version
player.sendPacket(new ClientboundRemoveEntitiesPacket(entities));
}
public static boolean isEntityDestroyPacket(Object packet) {
// 1.17
// return packet instanceof ClientboundRemoveEntityPacket;
// every other version
return packet instanceof ClientboundRemoveEntitiesPacket;
}
public static void registerCommand() {
// 1.18.2-
// net.fabricmc.fabric.api.command.v1.CommandRegistrationCallback.EVENT.register((dispatcher, $) -> new FabricTabCommand().onRegisterCommands(dispatcher));
// 1.19+
net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback.EVENT.register((dispatcher, $, $$) -> new FabricTabCommand().onRegisterCommands(dispatcher));
}
public static void sendMessage(FabricTabPlayer player, Component message) {
// 1.15.2-
// player.getPlayer().sendMessage(message);
// 1.16 - 1.18.2
// player.getPlayer().sendMessage(message, new UUID(0, 0));
// 1.19+
player.getPlayer().sendSystemMessage(message);
}
public static ClientboundAddEntityPacket addEntity(int entityId, @NotNull UUID id, @NotNull Object entityType,
@NotNull Location location, @NotNull EntityData data) {
// 1.18.2-
// return new ClientboundAddEntityPacket(entityId, id, location.getX(), location.getY(), location.getZ(), 0, 0, (EntityType<?>) entityType, 0, Vec3.ZERO);
// 1.19+
return new ClientboundAddEntityPacket(entityId, id, location.getX(), location.getY(), location.getZ(), 0, 0, (EntityType<?>) entityType, 0, Vec3.ZERO, 0);
}
public static boolean isPlayerInfo(Object packet) {
// 1.19.2-
// return packet instanceof ClientboundPlayerInfoPacket;
// 1.19.3+
return packet instanceof ClientboundPlayerInfoUpdatePacket;
}
public static Packet<?> build(FabricTabList.Action action, FabricTabList.Builder entry) {
// 1.16.5-
/*ClientboundPlayerInfoPacket packet = new ClientboundPlayerInfoPacket(ClientboundPlayerInfoPacket.Action.valueOf(action.name()), Collections.emptyList());
packet.entries = Collections.singletonList(new ClientboundPlayerInfoPacket().new PlayerUpdate(
entry.createProfile(), entry.getLatency(), GameType.byId(entry.getGameMode()), entry.getDisplayName()
));
return packet;*/
// 1.17 - 1.18.2
/*ClientboundPlayerInfoPacket packet = new ClientboundPlayerInfoPacket(ClientboundPlayerInfoPacket.Action.valueOf(action.name()), Collections.emptyList());
packet.entries = Collections.singletonList(new ClientboundPlayerInfoPacket.PlayerUpdate(
entry.createProfile(), entry.getLatency(), GameType.byId(entry.getGameMode()), entry.getDisplayName()
));
return packet;*/
// 1.19 - 1.19.2
/*ClientboundPlayerInfoPacket packet = new ClientboundPlayerInfoPacket(ClientboundPlayerInfoPacket.Action.valueOf(action.name()), Collections.emptyList());
packet.entries = Collections.singletonList(new ClientboundPlayerInfoPacket.PlayerUpdate(
entry.createProfile(), entry.getLatency(), GameType.byId(entry.getGameMode()), entry.getDisplayName(), null
));
return packet;*/
// 1.19.3+
if (action == FabricTabList.Action.REMOVE_PLAYER) {
return new ClientboundPlayerInfoRemovePacket(Collections.singletonList(entry.getId()));
}
EnumSet<ClientboundPlayerInfoUpdatePacket.Action> actions = action == FabricTabList.Action.ADD_PLAYER ?
EnumSet.allOf(ClientboundPlayerInfoUpdatePacket.Action.class) :
EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.valueOf(action.name()));
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
));
return packet;
}
public static EntityData createDataWatcher(@NotNull TabPlayer viewer, byte flags, @NotNull String displayName, boolean nameVisible) {
// 1.19.2-
/*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;*/
// 1.19.3+
return () -> java.util.Arrays.asList(
new SynchedEntityData.DataValue<>(0, EntityDataSerializers.BYTE, flags),
new SynchedEntityData.DataValue<>(2, EntityDataSerializers.OPTIONAL_COMPONENT,
Optional.of(((FabricTabPlayer)viewer).getPlatform().toComponent(IChatBaseComponent.optimizedComponent(displayName), viewer.getVersion()))),
new SynchedEntityData.DataValue<>(3, EntityDataSerializers.BOOLEAN, nameVisible),
new SynchedEntityData.DataValue<>(armorStandFlagsPosition, EntityDataSerializers.BYTE, (byte)16)
);
}
@SuppressWarnings("unchecked")
public static ClientboundSetEntityDataPacket newEntityMetadata(int entityId, EntityData data) {
// 1.19.2-
// return new ClientboundSetEntityDataPacket(entityId, (SynchedEntityData) data.build(), true);
// 1.19.3+
return new ClientboundSetEntityDataPacket(entityId, (List<SynchedEntityData.DataValue<?>>) data.build());
}
public static boolean isBundlePacket(Object packet) {
// 1.19.4+
return packet instanceof ClientboundBundlePacket;
}
public static Iterable<Packet<ClientGamePacketListener>> getPackets(Object bundlePacket) {
// 1.19.4+
return ((ClientboundBundlePacket)bundlePacket).subPackets();
}
public static ClientboundSetDisplayObjectivePacket newDisplayObjective(int slot, Objective objective) {
// 1.20.1-
// return new ClientboundSetDisplayObjectivePacket(slot, objective);
// 1.20.2+
return new ClientboundSetDisplayObjectivePacket(net.minecraft.world.scores.DisplaySlot.values()[slot], objective);
}
public static int getSlot(ClientboundSetDisplayObjectivePacket packet) {
// 1.20.1-
// return packet.getSlot();
// 1.20.2+
return packet.getSlot().ordinal();
}
public static int getPing(FabricTabPlayer player) {
// 1.20.1-
// return player.getPlayer().latency;
// 1.20.2+
return player.getPlayer().connection.latency();
}
public static TabList.Skin propertyToSkin(Property property) {
// 1.20.1-
// return new TabList.Skin(property.getValue(), property.getSignature());
// 1.20.2+
return new TabList.Skin(property.value(), property.signature());
}
}
@@ -0,0 +1,135 @@
package me.neznamy.tab.platforms.fabric;
import com.mojang.authlib.GameProfile;
import io.netty.channel.Channel;
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 net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket;
import net.minecraft.network.protocol.game.ClientboundSetDisplayObjectivePacket;
import net.minecraft.network.protocol.game.ClientboundSetObjectivePacket;
import net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumSet;
import java.util.List;
public class FabricPipelineInjector extends NettyPipelineInjector {
public FabricPipelineInjector() {
super("packet_handler");
}
@Override
@Nullable
protected Channel getChannel(@NotNull TabPlayer player) {
return ((FabricTabPlayer)player).getPlayer().connection.connection.channel;
}
@Override
public void onDisplayObjective(@NotNull TabPlayer player, @NotNull Object packet) {
TAB.getInstance().getFeatureManager().onDisplayObjective(player,
FabricMultiVersion.getSlot((ClientboundSetDisplayObjectivePacket) packet),
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 FabricMultiVersion.isPlayerInfo(packet);
}
@Override
public void onPlayerInfo(@NotNull TabPlayer receiver, @NotNull Object packet0) {
// Comment this entire method out when compiling with 1.19.2-, adding compatibility would be tough and not worth,
// as nobody needs these features on Fabric anyway
ClientboundPlayerInfoUpdatePacket packet = (ClientboundPlayerInfoUpdatePacket) packet0;
EnumSet<ClientboundPlayerInfoUpdatePacket.Action> actions = packet.actions();
List<ClientboundPlayerInfoUpdatePacket.Entry> updatedList = new ArrayList<>();
for (ClientboundPlayerInfoUpdatePacket.Entry nmsData : packet.entries()) {
GameProfile profile = nmsData.profile();
Component displayName = nmsData.displayName();
int latency = nmsData.latency();
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME)) {
IChatBaseComponent newDisplayName = TAB.getInstance().getFeatureManager().onDisplayNameChange(receiver, nmsData.profileId());
if (newDisplayName != null) displayName = ((FabricTabPlayer)receiver).getPlatform().toComponent(newDisplayName, receiver.getVersion());
}
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY)) {
latency = TAB.getInstance().getFeatureManager().onLatencyChange(receiver, nmsData.profileId(), latency);
}
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER)) {
TAB.getInstance().getFeatureManager().onEntryAdd(receiver, nmsData.profileId(), profile.getName());
}
updatedList.add(new ClientboundPlayerInfoUpdatePacket.Entry(nmsData.profileId(), profile, nmsData.listed(), latency, nmsData.gameMode(), displayName, nmsData.chatSession()));
}
packet.entries = updatedList;
}
@Override
public boolean isLogin(@NotNull Object packet) {
return false;
}
@Override
public void modifyPlayers(@NotNull Object teamPacket) {
if (TAB.getInstance().getNameTagManager() == null) return;
ClientboundSetPlayerTeamPacket packet = (ClientboundSetPlayerTeamPacket) teamPacket;
int action = packet.method;
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);
}
}
packet.players = newList;
}
}
@@ -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,9 @@
package me.neznamy.tab.platforms.fabric;
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;
@@ -9,58 +12,120 @@ 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 net.fabricmc.fabric.api.networking.v1.PlayerLookup;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.SharedConstants;
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
*/
public record FabricPlatform(MinecraftServer server) implements BackendPlatform {
@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 new FabricPipelineInjector();
}
@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) {
public void logInfo(@NotNull IChatBaseComponent message) {
MinecraftServer.LOGGER.info("[TAB] " + message.toRawText());
}
@Override
public void logWarn(@NotNull IChatBaseComponent message) {
MinecraftServer.LOGGER.warn("[TAB] " + message.toRawText()); // Fabric console does not support colors
}
@Override
@NotNull
public String getServerVersionInfo() {
return "[Fabric] " + SharedConstants.getCurrentVersion().getName();
}
@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(SharedConstants.getCurrentVersion().getName());
}
@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 Component.Serializer.fromJson(component.toString(version));
}
@Override
public double getTPS() {
return -1; // Not available
}
@Override
public double getMSPT() {
return server.getAverageTickTime();
}
}
@@ -8,9 +8,7 @@ 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;
@@ -21,8 +19,11 @@ import org.jetbrains.annotations.NotNull;
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();
@NotNull
private static final Component EMPTY_COMPONENT = Objects.requireNonNull(Component.Serializer.fromJson(IChatBaseComponent.EMPTY_COMPONENT));
public FabricScoreboard(FabricTabPlayer player) {
super(player);
@@ -31,7 +32,7 @@ public class FabricScoreboard extends Scoreboard<FabricTabPlayer> {
@Override
public void setDisplaySlot(@NotNull DisplaySlot slot, @NotNull String objective) {
player.sendPacket(
new ClientboundSetDisplayObjectivePacket(
FabricMultiVersion.newDisplayObjective(
slot.ordinal(),
new Objective(
dummyScoreboard,
@@ -45,15 +46,15 @@ public class FabricScoreboard extends Scoreboard<FabricTabPlayer> {
}
@Override
public void registerObjective0(@NotNull String objectiveName, @NotNull String title, boolean hearts) {
public void registerObjective0(@NotNull String objectiveName, @NotNull String title, @NotNull HealthDisplay display) {
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
dummyScoreboard,
objectiveName,
ObjectiveCriteria.DUMMY,
player.getPlatform().toComponent(IChatBaseComponent.optimizedComponent(title), player.getVersion()),
ObjectiveCriteria.RenderType.valueOf(display.name())
),
0
)
@@ -77,15 +78,15 @@ public class FabricScoreboard extends Scoreboard<FabricTabPlayer> {
}
@Override
public void updateObjective0(@NotNull String objectiveName, @NotNull String title, boolean hearts) {
public void updateObjective0(@NotNull String objectiveName, @NotNull String title, @NotNull HealthDisplay display) {
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
dummyScoreboard,
objectiveName,
ObjectiveCriteria.DUMMY,
player.getPlatform().toComponent(IChatBaseComponent.optimizedComponent(title), player.getVersion()),
ObjectiveCriteria.RenderType.valueOf(display.name())
),
2
)
@@ -93,35 +94,38 @@ public class FabricScoreboard extends Scoreboard<FabricTabPlayer> {
}
@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(player.getPlatform().toComponent(IChatBaseComponent.optimizedComponent(prefix), player.getVersion()));
team.setPlayerSuffix(player.getPlatform().toComponent(IChatBaseComponent.optimizedComponent(suffix), player.getVersion()));
team.getPlayers().addAll(players);
player.sendPacket(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, true));
player.sendPacket(FabricMultiVersion.createTeam(team));
}
@Override
public void unregisterTeam0(@NotNull String name) {
player.sendPacket(ClientboundSetPlayerTeamPacket.createRemovePacket(new PlayerTeam(dummyScoreboard, name)));
player.sendPacket(FabricMultiVersion.removeTeam(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(player.getPlatform().toComponent(IChatBaseComponent.optimizedComponent(prefix), player.getVersion()));
team.setPlayerSuffix(player.getPlatform().toComponent(IChatBaseComponent.optimizedComponent(suffix), player.getVersion()));
player.sendPacket(FabricMultiVersion.updateTeam(team));
}
@Override
@@ -1,53 +1,25 @@
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;
private static final boolean fabricPermissionsApi = FabricLoader.getInstance().isModLoaded("fabric-permissions-api-v0");
@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));
FabricMultiVersion.registerCommand();
ServerLifecycleEvents.SERVER_STARTING.register(server -> TAB.create(new FabricPlatform(server)));
ServerLifecycleEvents.SERVER_STOPPING.register($ -> TAB.getInstance().unload());
}
public Component toComponent(@NotNull IChatBaseComponent component, @NotNull ProtocolVersion clientVersion) {
return componentCache.get(component, clientVersion);
}
public boolean hasPermission(@NotNull CommandSourceStack source, @NotNull String permission) {
public static boolean hasPermission(@NotNull CommandSourceStack source, @NotNull String permission) {
if (source.hasPermission(4)) return true;
return fabricPermissionsApi && Permissions.check(source, permission);
}
@@ -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 = FabricTAB.hasPermission(source, TabConstants.Permission.COMMAND_RELOAD);
boolean hasAdminPermission = FabricTAB.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()));
FabricMultiVersion.sendSystemMessage(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();
}
}
@@ -7,107 +7,96 @@ import lombok.RequiredArgsConstructor;
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.*;
public record FabricTabList(FabricTabPlayer player) implements TabList {
public record FabricTabList(@NotNull FabricTabPlayer player) implements TabList {
@Override
public void removeEntry(@NotNull UUID entry) {
player.sendPacket(new ClientboundPlayerInfoRemovePacket(Collections.singletonList(entry)));
player.sendPacket(FabricMultiVersion.build(Action.REMOVE_PLAYER, 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(FabricMultiVersion.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(FabricMultiVersion.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(FabricMultiVersion.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(FabricMultiVersion.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
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()))
player.getPlayer().connection.send(FabricMultiVersion.setHeaderFooter(
player.getPlatform().toComponent(header, player.getVersion()),
player.getPlatform().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
));
return packet;
}
@RequiredArgsConstructor
@Getter
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,52 @@
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.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(), FabricMultiVersion.getWorldName(player.level));
}
@Override
public boolean hasPermission(@NotNull String permission) {
return FabricTAB.getInstance().hasPermission(getPlayer().createCommandSourceStack(), permission);
return FabricTAB.hasPermission(getPlayer().createCommandSourceStack(), permission);
}
@Override
public int getPing() {
return getPlayer().latency;
return FabricMultiVersion.getPing(this);
}
@Override
public void sendMessage(@NotNull IChatBaseComponent message) {
getPlayer().sendSystemMessage(FabricTAB.getInstance().toComponent(message, getVersion()));
FabricMultiVersion.sendMessage(this, getPlatform().toComponent(message, getVersion()));
}
@Override
@@ -54,27 +60,30 @@ 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;
if (properties.isEmpty()) return null; // Offline mode
Property skinProperty = properties.iterator().next();
return new TabList.Skin(skinProperty.getValue(), skinProperty.getSignature());
return FabricMultiVersion.propertyToSkin(skinProperty);
}
@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,126 @@
package me.neznamy.tab.platforms.fabric.features;
import me.neznamy.tab.platforms.fabric.FabricMultiVersion;
import me.neznamy.tab.platforms.fabric.FabricTabPlayer;
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.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() {
FabricMultiVersion.registerNameTagXEvents(this);
}
@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 ((FabricTabPlayer)player1).getPlayer().level == ((FabricTabPlayer)player2).getPlayer().level;
}
@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 FabricMultiVersion.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 FabricMultiVersion.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();
}
}
+2 -1
View File
@@ -11,7 +11,8 @@
},
"depends": {
"fabricloader": ">=0.9.0",
"fabric-api-base": "*"
"fabric-api-base": "*",
"minecraft": "1.20.2"
},
"recommends": {
"fabric-permissions-api-v0": "*"
@@ -1,5 +1,16 @@
accessWidener v1 named
accessible field net/minecraft/network/protocol/game/ClientboundMoveEntityPacket entityId I
accessible field net/minecraft/server/network/ServerGamePacketListenerImpl connection Lnet/minecraft/network/Connection;
accessible field net/minecraft/network/Connection channel Lio/netty/channel/Channel;
accessible field net/minecraft/network/protocol/game/ClientboundSetPlayerTeamPacket method I
accessible field net/minecraft/network/protocol/game/ClientboundSetPlayerTeamPacket players Ljava/util/Collection;
mutable field net/minecraft/network/protocol/game/ClientboundSetPlayerTeamPacket players Ljava/util/Collection;
accessible field net/minecraft/server/network/ServerCommonPacketListenerImpl connection Lnet/minecraft/network/Connection;
# 1.20+
accessible field net/minecraft/world/entity/Entity level Lnet/minecraft/world/level/Level;
# new
accessible field net/minecraft/server/MinecraftServer LOGGER Lorg/slf4j/Logger;
mutable field net/minecraft/server/MinecraftServer LOGGER Lorg/slf4j/Logger;
-2
View File
@@ -7,11 +7,9 @@ plugins {
val platforms = setOf(
rootProject.projects.bukkit,
rootProject.projects.bungeecord,
rootProject.projects.krypton,
rootProject.projects.velocity,
rootProject.projects.sponge7,
rootProject.projects.sponge8,
// rootProject.projects.fabric
).map { it.dependencyProject }
tasks {
-10
View File
@@ -1,10 +0,0 @@
dependencies {
implementation(projects.shared)
compileOnly("me.lucko:spark-api:0.1-SNAPSHOT")
compileOnly("org.kryptonmc:krypton-api:f93a12382f")
annotationProcessor("org.kryptonmc:krypton-annotation-processor:f93a12382f")
}
tasks.compileJava {
options.release.set(17)
}
@@ -1,36 +0,0 @@
package me.neznamy.tab.platforms.krypton;
import me.neznamy.tab.shared.platform.EventListener;
import me.neznamy.tab.shared.platform.TabPlayer;
import org.kryptonmc.api.entity.player.Player;
import org.kryptonmc.api.event.Listener;
import org.kryptonmc.api.event.command.CommandExecuteEvent;
import org.kryptonmc.api.event.player.PlayerJoinEvent;
import org.kryptonmc.api.event.player.PlayerQuitEvent;
public class KryptonEventListener extends EventListener<Player> {
@Listener
public void onJoin(PlayerJoinEvent event) {
join(event.getPlayer());
}
@Listener
public void onQuit(PlayerQuitEvent event) {
quit(event.getPlayer().getUuid());
}
//TODO world switch
@Listener
public void onCommand(CommandExecuteEvent event) {
if (event.getSender() instanceof Player && command(((Player) event.getSender()).getUuid(), event.getCommand())) {
event.deny();
}
}
@Override
public TabPlayer createPlayer(Player player) {
return new KryptonTabPlayer(player);
}
}
@@ -1,84 +0,0 @@
package me.neznamy.tab.platforms.krypton;
import lombok.RequiredArgsConstructor;
import me.lucko.spark.api.Spark;
import me.lucko.spark.api.statistic.StatisticWindow.*;
import me.lucko.spark.api.statistic.misc.DoubleAverageInfo;
import me.lucko.spark.api.statistic.types.DoubleStatistic;
import me.lucko.spark.api.statistic.types.GenericStatistic;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.api.placeholder.PlaceholderManager;
import me.neznamy.tab.shared.placeholders.UniversalPlaceholderRegistry;
import org.jetbrains.annotations.NotNull;
import org.kryptonmc.api.entity.player.Player;
@RequiredArgsConstructor
public class KryptonPlaceholderRegistry extends UniversalPlaceholderRegistry {
private final @NotNull KryptonTAB plugin;
@Override
public void registerPlaceholders(@NotNull PlaceholderManager manager) {
// Built-in stuff
manager.registerPlayerPlaceholder(TabConstants.Placeholder.DISPLAY_NAME, 500, p -> ((Player) p.getPlayer()).getDisplayName());
manager.registerPlayerPlaceholder(TabConstants.Placeholder.HEALTH, 100, p -> Math.ceil(((Player) p.getPlayer()).getHealth()));
// Spark placeholders, registered if Spark is present
if (plugin.getServer().getPluginManager().isLoaded("spark")) SparkPlaceholders.register(this, manager);
super.registerPlaceholders(manager);
}
/*
* This class is a massive hack. The issue is that the lambdas in here create InvokeDynamic instructions, which have
* references to the Spark classes that will be resolved on class load, causing class not found errors when the registry
* is loaded.
*
* This class solves this, as the InvokeDynamic instructions are now part of this class, rather than the registry class,
* which means we can have this class only load if Spark is present, solving the issue.
*/
private static final class SparkPlaceholders {
static void register(KryptonPlaceholderRegistry registry, PlaceholderManager manager) {
Spark spark = registry.plugin.getServer().getServicesManager().provide(Spark.class);
if (spark == null) return;
// TPS
DoubleStatistic<TicksPerSecond> tps = spark.tps();
if (tps != null) {
manager.registerServerPlaceholder("%tps_5s%", 1000, () -> registry.format(tps.poll(TicksPerSecond.SECONDS_5)));
manager.registerServerPlaceholder("%tps_10s%", 1000, () -> registry.format(tps.poll(TicksPerSecond.SECONDS_10)));
manager.registerServerPlaceholder("%tps_1m%", 1000, () -> registry.format(tps.poll(TicksPerSecond.MINUTES_1)));
manager.registerServerPlaceholder("%tps_5m%", 1000, () -> registry.format(tps.poll(TicksPerSecond.MINUTES_5)));
manager.registerServerPlaceholder("%tps_15m%", 1000, () -> registry.format(tps.poll(TicksPerSecond.MINUTES_15)));
}
// MSPT
GenericStatistic<DoubleAverageInfo, MillisPerTick> mspt = spark.mspt();
if (mspt != null) {
manager.registerServerPlaceholder("%mspt_min_10s%", 1000, () -> registry.format(mspt.poll(MillisPerTick.SECONDS_10).min()));
manager.registerServerPlaceholder("%mspt_max_10s%", 1000, () -> registry.format(mspt.poll(MillisPerTick.SECONDS_10).max()));
manager.registerServerPlaceholder("%mspt_mean_10s%", 1000, () -> registry.format(mspt.poll(MillisPerTick.SECONDS_10).mean()));
manager.registerServerPlaceholder("%mspt_median_10s%", 1000, () -> registry.format(mspt.poll(MillisPerTick.SECONDS_10).median()));
manager.registerServerPlaceholder("%mspt_percentile95_10s%", 1000, () -> registry.format(mspt.poll(MillisPerTick.SECONDS_10).percentile95th()));
manager.registerServerPlaceholder("%mspt_min_1m%", 1000, () -> registry.format(mspt.poll(MillisPerTick.MINUTES_1).min()));
manager.registerServerPlaceholder("%mspt_max_1m%", 1000, () -> registry.format(mspt.poll(MillisPerTick.MINUTES_1).max()));
manager.registerServerPlaceholder("%mspt_mean_1m%", 1000, () -> registry.format(mspt.poll(MillisPerTick.MINUTES_1).mean()));
manager.registerServerPlaceholder("%mspt_median_1m%", 1000, () -> registry.format(mspt.poll(MillisPerTick.MINUTES_1).median()));
manager.registerServerPlaceholder("%mspt_percentile95_1m%", 1000, () -> registry.format(mspt.poll(MillisPerTick.SECONDS_10).percentile95th()));
}
// CPU process
DoubleStatistic<CpuUsage> process = spark.cpuProcess();
manager.registerServerPlaceholder("%cpu_process_10s%", 1000, () -> registry.format(process.poll(CpuUsage.SECONDS_10)));
manager.registerServerPlaceholder("%cpu_process_1m%", 1000, () -> registry.format(process.poll(CpuUsage.MINUTES_1)));
manager.registerServerPlaceholder("%cpu_process_15m%", 1000, () -> registry.format(process.poll(CpuUsage.MINUTES_15)));
// CPU system
DoubleStatistic<CpuUsage> system = spark.cpuSystem();
manager.registerServerPlaceholder("%cpu_system_10s%", 1000, () -> registry.format(system.poll(CpuUsage.SECONDS_10)));
manager.registerServerPlaceholder("%cpu_system_1m%", 1000, () -> registry.format(system.poll(CpuUsage.MINUTES_1)));
manager.registerServerPlaceholder("%cpu_system_15m%", 1000, () -> registry.format(system.poll(CpuUsage.MINUTES_15)));
}
}
}
@@ -1,73 +0,0 @@
package me.neznamy.tab.platforms.krypton;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.features.types.TabFeature;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.backend.BackendPlatform;
import me.neznamy.tab.shared.features.injection.PipelineInjector;
import me.neznamy.tab.shared.placeholders.expansion.TabExpansion;
import me.neznamy.tab.shared.features.nametags.NameTag;
import me.neznamy.tab.shared.placeholders.expansion.EmptyTabExpansion;
import net.kyori.adventure.text.Component;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.kryptonmc.api.Server;
import org.kryptonmc.api.entity.player.Player;
public class KryptonPlatform implements BackendPlatform {
@NotNull private final KryptonTAB plugin;
@NotNull private final Server server;
public KryptonPlatform(@NotNull KryptonTAB plugin) {
this.plugin = plugin;
server = plugin.getServer();
}
@Override
public void sendConsoleMessage(@NotNull IChatBaseComponent message) {
server.getConsole().sendMessage(Component.text("[TAB] ").append(message.toAdventureComponent(TAB.getInstance().getServerVersion())));
}
@Override
public String getServerVersionInfo() {
return "[Krypton] " + server.getPlatform().version();
}
@Override
public void registerUnknownPlaceholder(@NotNull String identifier) {
TAB.getInstance().getPlaceholderManager().registerServerPlaceholder(identifier, -1, () -> "");
}
@Override
public void loadPlayers() {
for (Player player : server.getPlayers()) {
TAB.getInstance().addPlayer(new KryptonTabPlayer(player));
}
}
@Override
public void registerPlaceholders() {
new KryptonPlaceholderRegistry(plugin).registerPlaceholders(TAB.getInstance().getPlaceholderManager());
}
@Override
public @Nullable PipelineInjector createPipelineInjector() {
return null;
}
@Override
public @NotNull NameTag getUnlimitedNameTags() {
return new NameTag();
}
@Override
public @NotNull TabExpansion createTabExpansion() {
return new EmptyTabExpansion();
}
@Override
public @Nullable TabFeature getPerWorldPlayerList() {
return null;
}
}
@@ -1,108 +0,0 @@
package me.neznamy.tab.platforms.krypton;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.Scoreboard;
import org.jetbrains.annotations.NotNull;
import org.kryptonmc.api.scoreboard.Objective;
import org.kryptonmc.api.scoreboard.ObjectiveRenderType;
import org.kryptonmc.api.scoreboard.Team;
import org.kryptonmc.api.scoreboard.Visibility;
import org.kryptonmc.api.scoreboard.criteria.Criteria;
import java.util.Collection;
public class KryptonScoreboard extends Scoreboard<KryptonTabPlayer> {
public KryptonScoreboard(@NotNull KryptonTabPlayer player) {
super(player);
// Create a new, blank scoreboard for each player to avoid conflicts
player.getPlayer().showScoreboard(org.kryptonmc.api.scoreboard.Scoreboard.create());
}
@Override
public void setDisplaySlot(@NotNull DisplaySlot slot, @NotNull String objectiveName) {
Objective objective = getScoreboard().getObjective(objectiveName);
if (objective != null) getScoreboard().updateSlot(objective, convertDisplaySlot(slot));
}
private org.kryptonmc.api.scoreboard.DisplaySlot convertDisplaySlot(DisplaySlot slot) {
return org.kryptonmc.api.scoreboard.DisplaySlot.values()[slot.ordinal()];
}
@Override
public void registerObjective0(@NotNull String objectiveName, @NotNull String title, boolean hearts) {
Objective ignored = getScoreboard().createObjectiveBuilder()
.name(objectiveName)
.criterion(Criteria.DUMMY.get())
.displayName(IChatBaseComponent.optimizedComponent(title).toAdventureComponent(player.getVersion()))
.renderType(hearts ? ObjectiveRenderType.HEARTS : ObjectiveRenderType.INTEGER)
.buildAndRegister();
}
@Override
public void unregisterObjective0(@NotNull String objectiveName) {
Objective objective = getScoreboard().getObjective(objectiveName);
if (objective != null) getScoreboard().removeObjective(objective);
}
@Override
public void updateObjective0(@NotNull String objectiveName, @NotNull String title, boolean hearts) {
Objective objective = getScoreboard().getObjective(objectiveName);
if (objective == null) return;
objective.setDisplayName(IChatBaseComponent.optimizedComponent(title).toAdventureComponent(player.getVersion()));
objective.setRenderType(hearts ? ObjectiveRenderType.HEARTS : ObjectiveRenderType.INTEGER);
}
@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) {
Team team = getScoreboard().createTeamBuilder(name)
.displayName(IChatBaseComponent.optimizedComponent(name).toAdventureComponent(player.getVersion()))
.prefix(IChatBaseComponent.optimizedComponent(prefix).toAdventureComponent(player.getVersion()))
.suffix(IChatBaseComponent.optimizedComponent(suffix).toAdventureComponent(player.getVersion()))
.friendlyFire((options & 0x01) != 0)
.canSeeInvisibleMembers((options & 0x02) != 0)
.collisionRule(org.kryptonmc.api.scoreboard.CollisionRule.valueOf(collision.name()))
.nameTagVisibility(Visibility.valueOf(visibility.name()))
.buildAndRegister();
for (String member : players) {
team.addMember(IChatBaseComponent.optimizedComponent(member).toAdventureComponent(player.getVersion()));
}
}
@Override
public void unregisterTeam0(@NotNull String name) {
Team team = getScoreboard().getTeam(name);
if (team != null) getScoreboard().removeTeam(team);
}
@Override
public void updateTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix, @NotNull NameVisibility visibility, @NotNull CollisionRule collision, int options) {
Team team = getScoreboard().getTeam(name);
if (team == null) return;
team.setDisplayName(IChatBaseComponent.optimizedComponent(name).toAdventureComponent(player.getVersion()));
team.setPrefix(IChatBaseComponent.optimizedComponent(prefix).toAdventureComponent(player.getVersion()));
team.setSuffix(IChatBaseComponent.optimizedComponent(suffix).toAdventureComponent(player.getVersion()));
team.setAllowFriendlyFire((options & 0x01) != 0);
team.setCanSeeInvisibleMembers((options & 0x02) != 0);
team.setCollisionRule(org.kryptonmc.api.scoreboard.CollisionRule.valueOf(collision.name()));
team.setNameTagVisibility(Visibility.valueOf(visibility.name()));
}
@Override
public void setScore0(@NotNull String objectiveName, @NotNull String playerName, int score) {
Objective objective = getScoreboard().getObjective(objectiveName);
if (objective != null)
objective.getOrCreateScore(IChatBaseComponent.optimizedComponent(playerName)
.toAdventureComponent(player.getVersion())).setScore(score);
}
@Override
public void removeScore0(@NotNull String objectiveName, @NotNull String playerName) {
Objective objective = getScoreboard().getObjective(objectiveName);
if (objective != null) objective.removeScore(IChatBaseComponent.optimizedComponent(playerName).toAdventureComponent(player.getVersion()));
}
private org.kryptonmc.api.scoreboard.Scoreboard getScoreboard() {
return player.getPlayer().getScoreboard();
}
}
@@ -1,57 +0,0 @@
package me.neznamy.tab.platforms.krypton;
import com.google.inject.Inject;
import lombok.Getter;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import org.kryptonmc.api.Server;
import org.kryptonmc.api.command.CommandMeta;
import org.kryptonmc.api.event.Event;
import org.kryptonmc.api.event.EventFilter;
import org.kryptonmc.api.event.EventNode;
import org.kryptonmc.api.event.Listener;
import org.kryptonmc.api.event.server.ServerStartEvent;
import org.kryptonmc.api.event.server.ServerStopEvent;
import org.kryptonmc.api.plugin.annotation.Dependency;
import org.kryptonmc.api.plugin.annotation.Plugin;
import java.nio.file.Path;
/**
* Main class for Krypton platform
*/
@Plugin(
id = TabConstants.PLUGIN_ID,
name = TabConstants.PLUGIN_NAME,
version = TabConstants.PLUGIN_VERSION,
description = TabConstants.PLUGIN_DESCRIPTION,
authors = {TabConstants.PLUGIN_AUTHOR, "BomBardyGamer"},
dependencies = {
@Dependency(id = "luckperms", optional = true),
@Dependency(id = "viaversion", optional = true)
}
)
public class KryptonTAB {
@Inject @Getter private Server server;
@Inject private EventNode<Event> pluginEventNode;
@Inject private Path folder;
private final EventNode<Event> eventNode = EventNode.Companion.filteredForEvent("tab_events", EventFilter.ALL,
(it) -> !TAB.getInstance().isPluginDisabled());
@Listener
public final void onStart(ServerStartEvent event) {
pluginEventNode.addChild(eventNode);
eventNode.registerListeners(new KryptonEventListener());
server.getCommandManager().register(new KryptonTabCommand(), CommandMeta.builder(TabConstants.COMMAND_BACKEND).build());
TAB.setInstance(new TAB(new KryptonPlatform(this), ProtocolVersion.fromNetworkId(server.getPlatform().protocolVersion()),
folder.toFile()));
TAB.getInstance().load();
}
@Listener
public final void onStop(ServerStopEvent event) {
TAB.getInstance().unload();
}
}
@@ -1,44 +0,0 @@
package me.neznamy.tab.platforms.krypton;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.platform.TabPlayer;
import net.kyori.adventure.text.Component;
import org.jetbrains.annotations.NotNull;
import org.kryptonmc.api.command.Sender;
import org.kryptonmc.api.command.SimpleCommand;
import org.kryptonmc.api.entity.player.Player;
import java.util.Collections;
import java.util.List;
public class KryptonTabCommand implements SimpleCommand {
@Override
public void execute(@NotNull Sender sender, String[] args) {
if (TAB.getInstance().isPluginDisabled()) {
boolean canReload = sender.hasPermission(TabConstants.Permission.COMMAND_RELOAD);
boolean isAdmin = sender.hasPermission(TabConstants.Permission.COMMAND_ALL);
for (String message : TAB.getInstance().getDisabledCommand().execute(args, canReload, isAdmin)) {
sender.sendMessage(Component.text(message));
}
return;
}
TabPlayer player = null;
if (sender instanceof Player) {
player = TAB.getInstance().getPlayer(((Player) sender).getUuid());
if (player == null) return;
}
TAB.getInstance().getCommand().execute(player, args);
}
@Override
public @NotNull List<String> suggest(@NotNull Sender sender, String[] args) {
TabPlayer player = null;
if (sender instanceof Player) {
player = TAB.getInstance().getPlayer(((Player) sender).getUuid());
if (player == null) return Collections.emptyList();
}
return TAB.getInstance().getCommand().complete(player, args);
}
}
@@ -1,65 +0,0 @@
package me.neznamy.tab.platforms.krypton;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.TabList;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.kryptonmc.api.auth.GameProfile;
import org.kryptonmc.api.auth.ProfileProperty;
import org.kryptonmc.api.entity.player.TabListEntry;
import org.kryptonmc.api.world.GameMode;
import java.util.Collections;
import java.util.UUID;
public record KryptonTabList(KryptonTabPlayer player) implements TabList {
@Override
public void removeEntry(@NotNull UUID entryId) {
player.getPlayer().getTabList().removeEntry(entryId);
}
@Override
public void updateDisplayName(@NotNull UUID entryId, @Nullable IChatBaseComponent displayName) {
TabListEntry entry = player.getPlayer().getTabList().getEntry(entryId);
if (entry != null) entry.setDisplayName(displayName == null ? null : displayName.toAdventureComponent(player.getVersion()));
}
@Override
public void updateLatency(@NotNull UUID entryId, int latency) {
TabListEntry entry = player.getPlayer().getTabList().getEntry(entryId);
if (entry != null) entry.setLatency(latency);
}
@Override
public void updateGameMode(@NotNull UUID entryId, int gameMode) {
TabListEntry entry = player.getPlayer().getTabList().getEntry(entryId);
if (entry != null) entry.setGameMode(GameMode.values()[gameMode]);
}
@Override
public void addEntry(@NotNull Entry entry) {
GameProfile profile = createGameProfile(entry.getUniqueId(), entry.getName(), entry.getSkin());
player.getPlayer().getTabList().createEntryBuilder(entry.getUniqueId(), profile)
.displayName(entry.getDisplayName() == null ? null : entry.getDisplayName().toAdventureComponent(player.getVersion()))
.gameMode(GameMode.values()[entry.getGameMode()])
.latency(entry.getLatency())
.listed(true)
.buildAndRegister();
}
@Override
public void setPlayerListHeaderFooter(@NotNull IChatBaseComponent header, @NotNull IChatBaseComponent footer) {
player.getPlayer().getTabList().setHeaderAndFooter(
header.toAdventureComponent(player.getVersion()),
footer.toAdventureComponent(player.getVersion())
);
}
private @NotNull
GameProfile createGameProfile(@NotNull UUID uuid, @Nullable String name, @Nullable Skin skin) {
String newName = name == null ? "" : name;
if (skin == null) return GameProfile.of(newName, uuid);
ProfileProperty property = ProfileProperty.of(TabList.TEXTURES_PROPERTY, skin.getValue(), skin.getSignature());
return GameProfile.of(newName, uuid, Collections.singletonList(property));
}
}
@@ -1,81 +0,0 @@
package me.neznamy.tab.platforms.krypton;
import lombok.Getter;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.platform.bossbar.AdventureBossBar;
import me.neznamy.tab.shared.platform.bossbar.BossBar;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.Scoreboard;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.kryptonmc.api.auth.ProfileProperty;
import org.kryptonmc.api.entity.player.Player;
import java.util.List;
@Getter
public class KryptonTabPlayer extends TabPlayer {
private final Scoreboard<KryptonTabPlayer> scoreboard = new KryptonScoreboard(this);
private final TabList tabList = new KryptonTabList(this);
private final BossBar bossBar = new AdventureBossBar(this);
public KryptonTabPlayer(Player player) {
super(player, player.getUuid(), player.getProfile().name(), TAB.getInstance().getConfiguration().getServerName(),
player.getWorld().getName(), TAB.getInstance().getServerVersion().getNetworkId(), true);
}
@Override
public boolean hasPermission(@NotNull String permission) {
return getPlayer().hasPermission(permission);
}
@Override
public int getPing() {
return getPlayer().getPing();
}
@Override
public void sendMessage(@NotNull IChatBaseComponent message) {
getPlayer().sendMessage(message.toAdventureComponent(getVersion()));
}
@Override
public boolean hasInvisibilityPotion() {
return false;
}
@Override
public boolean isDisguised() {
return false;
}
@Override
public @Nullable TabList.Skin getSkin() {
List<ProfileProperty> list = getPlayer().getProfile().properties();
if (list.isEmpty()) return null;
return new TabList.Skin(list.get(0).value(), list.get(0).signature());
}
@Override
public @NotNull Player getPlayer() {
return (Player) player;
}
@Override
public boolean isOnline() {
return getPlayer().isOnline();
}
@Override
public boolean isVanished() {
return false;
}
@Override
public int getGamemode() {
return getPlayer().getGameMode().ordinal();
}
}
+5 -10
View File
@@ -2,19 +2,15 @@ enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
dependencyResolutionManagement {
repositories {
mavenCentral() // Netty, SnakeYaml, json-simple, slf4j, Guava, Kyori event, bStats, AuthLib
mavenCentral() // Netty, SnakeYaml, json-simple, Guava, Kyori event, bStats, AuthLib, LuckPerms
maven("https://repo.extendedclip.com/content/repositories/placeholderapi/") // PlaceholderAPI
maven("https://repo.viaversion.com/") // ViaVersion
maven("https://repo.codemc.org/repository/maven-public/") // RedisBungee
maven("https://repo.kryptonmc.org/releases") // Krypton, YamlAssist, LuckPerms
maven("https://repo.kryptonmc.org/snapshots") // BungeeCord
maven("https://repo.codemc.org/repository/maven-public/") // RedisBungee, BungeeCord-proxy
maven("https://repo.opencollab.dev/maven-snapshots/") // Floodgate
maven("https://repo.purpurmc.org/snapshots") // Purpur
maven("https://repo.papermc.io/repository/maven-public/") // Velocity
maven("https://repo.spongepowered.org/repository/maven-public/") // Sponge
maven("https://oss.sonatype.org/content/repositories/snapshots/") // Spark
maven("https://maven.fabricmc.net/") // Fabric
maven("https://jitpack.io") // PremiumVanish, Vault
maven("https://jitpack.io") // PremiumVanish, Vault, YamlAssist
maven("https://repo.md-5.net/content/groups/public/") // LibsDisguises
}
}
@@ -31,11 +27,10 @@ rootProject.name = "TAB"
include(":api")
include(":shared")
include(":krypton")
include(":velocity")
include(":bukkit")
include(":bungeecord")
include(":sponge7")
include(":sponge8")
include(":fabric")
include(":jar")
include(":jar")
+1 -3
View File
@@ -5,7 +5,7 @@ plugins {
dependencies {
api(projects.api)
api("org.yaml:snakeyaml:1.33")
api("me.neznamy:yamlassist:1.0.5")
api("com.github.NEZNAMY:yamlassist:1.0.5")
api("com.googlecode.json-simple:json-simple:1.1.1") {
exclude("junit", "junit")
}
@@ -13,13 +13,11 @@ dependencies {
exclude("com.google.guava", "guava")
exclude("org.checkerframework", "checker-qual")
}
compileOnlyApi("net.md-5:bungeecord-chat:1.19-R0.1-SNAPSHOT")
compileOnlyApi("com.viaversion:viaversion-api:4.5.1")
compileOnlyApi("io.netty:netty-all:4.1.90.Final")
compileOnlyApi("net.luckperms:api:5.4")
compileOnlyApi("com.google.guava:guava:31.1-jre")
compileOnlyApi("org.geysermc.floodgate:api:2.2.0-SNAPSHOT")
compileOnlyApi("org.slf4j:slf4j-api:2.0.6")
compileOnlyApi("net.kyori:adventure-api:4.13.0")
compileOnlyApi("net.kyori:adventure-text-serializer-legacy:4.13.0")
compileOnlyApi("net.kyori:adventure-text-minimessage:4.13.0")
@@ -1,248 +0,0 @@
package me.neznamy.tab.shared;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.*;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import me.neznamy.tab.shared.features.types.TabFeature;
import org.jetbrains.annotations.NotNull;
/**
* A class which measures CPU usage of all tasks inserted into it and shows usage
*/
public class CpuManager {
private final int UPDATE_RATE_SECONDS = 10;
private final long TIME_PERCENT = TimeUnit.SECONDS.toNanos(1) / UPDATE_RATE_SECONDS;
/** Active time in current time period saved as nanoseconds from features */
private volatile Map<String, Map<String, Long>> featureUsageCurrent = new ConcurrentHashMap<>();
/** Active time in current time period saved as nanoseconds from placeholders */
private volatile Map<String, Long> placeholderUsageCurrent = new ConcurrentHashMap<>();
/** Active time in previous time period saved as nanoseconds from features */
private volatile Map<String, Map<String, Long>> featureUsagePrevious = new HashMap<>();
/** Active time in previous time period saved as nanoseconds from placeholders */
private volatile Map<String, Long> placeholderUsagePrevious = new HashMap<>();
// Scheduler for scheduling delayed and repeating tasks
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(
new ThreadFactoryBuilder().setNameFormat("TAB Processing Thread").build());
/** Tasks submitted to main thread before plugin was fully enabled */
private final Queue<Runnable> taskQueue = new ConcurrentLinkedQueue<>();
/** Enabled flag used to queue incoming tasks if plugin is not enabled yet */
private volatile boolean enabled = false;
/**
* Constructs new instance and starts repeating task that resets values in configured interval
*/
public CpuManager() {
startRepeatingTask((int) TimeUnit.SECONDS.toMillis(UPDATE_RATE_SECONDS), () -> {
featureUsagePrevious = Collections.unmodifiableMap(featureUsageCurrent);
placeholderUsagePrevious = Collections.unmodifiableMap(placeholderUsageCurrent);
featureUsageCurrent = new ConcurrentHashMap<>();
placeholderUsageCurrent = new ConcurrentHashMap<>();
for (Entry<String, Long> entry : placeholderUsagePrevious.entrySet()) {
float usagePercent = nanosToPercent(entry.getValue());
if (usagePercent > 50) {
TAB.getInstance().sendConsoleMessage("&c[WARN] CPU usage of placeholder " + entry.getKey() +
" is " + (int)usagePercent + "%. It will most likely cause problems. Try increasing refresh interval.", true);
}
}
});
}
/**
* Cancels all tasks and shuts down thread pools
*/
public void cancelAllTasks() {
scheduler.shutdownNow();
}
/**
* Marks cpu manager as loaded and submits all queued tasks
*/
public void enable() {
enabled = true;
Runnable r;
while ((r = taskQueue.poll()) != null) {
submit(r);
}
}
/**
* Submits task to TAB's main thread. If plugin is not enabled yet,
* queues the task instead and executes once it's loaded.
*
* @param task task to execute
*/
private void submit(@NotNull Runnable task) {
if (scheduler.isShutdown()) return;
if (!enabled) {
taskQueue.add(task);
return;
}
scheduler.submit(() -> run(task));
}
/**
* Returns cpu usage map of placeholders from previous time period
*
* @return cpu usage map of placeholders
*/
public Map<String, Float> getPlaceholderUsage() {
return getUsage(placeholderUsagePrevious);
}
/**
* Converts nano map to percent and sorts it from highest to lowest usage.
*
* @param map map to convert
* @return converted and sorted map
*/
private @NotNull Map<String, Float> getUsage(@NotNull Map<String, Long> map) {
return map
.entrySet()
.stream()
.sorted(Entry.comparingByValue((o1, o2) -> Long.compare(o2, o1)))
.collect(LinkedHashMap::new,
(m, e) -> m.put(e.getKey(), nanosToPercent(e.getValue())),
Map::putAll
);
}
/**
* Returns map of CPU usage per feature and type in the previous time period
*
* @return map of CPU usage per feature and type
*/
public @NotNull Map<String, Map<String, Float>> getFeatureUsage() {
final Map<String, Map<String, Long>> map = featureUsagePrevious;
TreeMap<Long, Map.Entry<String, Map<String, Float>>> sorted
= new TreeMap<>((o1, o2) -> Long.compare(o2, o1));
map.forEach((key, val) -> {
Set<Map.Entry<String, Long>> entries = val.entrySet();
Map<String, Float> percent = new LinkedHashMap<>(entries.size());
long sum = entries
.stream()
.sorted(Map.Entry.comparingByValue((o1, o2) -> Long.compare(o2, o1)))
.peek(e -> percent.put(e.getKey(), nanosToPercent(e.getValue())))
.mapToLong(Map.Entry::getValue)
.sum();
sorted.put(sum, // Map.entry(key, percent) inline type java9+
new AbstractMap.SimpleImmutableEntry<>(key, percent));
});
// we will also try to get rid of O(log(n)) for random reading
int assumeCapacity = map.size();
return sorted
.values()
.stream()
.collect(() -> new LinkedHashMap<>(assumeCapacity),
(m, e) -> m.put(e.getKey(), e.getValue()),
Map::putAll
);
}
/**
* Converts nanoseconds to percent usage.
*
* @param nanos nanoseconds of cpu time
* @return usage in % (0-100)
*/
private float nanosToPercent(long nanos) {
return (float) nanos / TIME_PERCENT;
}
/**
* Adds cpu time to specified feature and usage type
*
* @param feature feature to add time to
* @param type sub-feature to add time to
* @param nanoseconds time to add
*/
public void addTime(@NotNull TabFeature feature, @NotNull String type, long nanoseconds) {
addTime(feature.getFeatureName(), type, nanoseconds);
}
/**
* Adds cpu time to specified feature and usage type
*
* @param feature feature to add time to
* @param type sub-feature to add time to
* @param nanoseconds time to add
*/
public void addTime(@NotNull String feature, @NotNull String type, long nanoseconds) {
featureUsageCurrent
.computeIfAbsent(feature, f -> new ConcurrentHashMap<>())
.merge(type, nanoseconds, Long::sum);
}
/**
* Adds used time to specified key into specified map
*
* @param map map to add usage to
* @param key usage key
* @param time nanoseconds to add
*/
private void addTime(@NotNull Map<String, Long> map, @NotNull String key, long time) {
map.merge(key, time, Long::sum);
}
/**
* Adds placeholder time to specified placeholder
*
* @param placeholder placeholder to add time to
* @param nanoseconds time to add
*/
public void addPlaceholderTime(@NotNull String placeholder, long nanoseconds) {
addTime(placeholderUsageCurrent, placeholder, nanoseconds);
}
public void runMeasuredTask(@NotNull String feature, @NotNull String type, @NotNull Runnable task) {
submit(() -> {
long time = System.nanoTime();
task.run();
addTime(feature, type, System.nanoTime() - time);
});
}
public void runTask(@NotNull Runnable task) {
submit(task);
}
public void startRepeatingMeasuredTask(int intervalMilliseconds, @NotNull String feature, @NotNull String type, @NotNull Runnable task) {
if (scheduler.isShutdown()) return;
scheduler.scheduleAtFixedRate(() -> runMeasuredTask(feature, type, task), intervalMilliseconds, intervalMilliseconds, TimeUnit.MILLISECONDS);
}
public void startRepeatingTask(int intervalMilliseconds, @NotNull Runnable task) {
if (scheduler.isShutdown()) return;
scheduler.scheduleAtFixedRate(() -> run(task), intervalMilliseconds, intervalMilliseconds, TimeUnit.MILLISECONDS);
}
public void runTaskLater(int delayMilliseconds, @NotNull String feature, @NotNull String type, @NotNull Runnable task) {
if (scheduler.isShutdown()) return;
scheduler.schedule(() -> runMeasuredTask(feature, type, task), delayMilliseconds, TimeUnit.MILLISECONDS);
}
private void run(@NotNull Runnable task) {
try {
task.run();
} catch (Exception | LinkageError | StackOverflowError e) {
TAB.getInstance().getErrorManager().printError("An error was thrown when executing task", e);
}
}
}
@@ -2,6 +2,7 @@ package me.neznamy.tab.shared;
import lombok.Getter;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -123,24 +124,24 @@ public class ErrorManager {
if (file.length() < 1000000)
buf.write(dateFormat.format(new Date()) + "[TAB v" + TabConstants.PLUGIN_VERSION + "] " + EnumChatFormat.decolor(message) + System.getProperty("line.separator"));
if (intoConsoleToo || TAB.getInstance().getConfiguration().isDebugMode())
TAB.getInstance().sendConsoleMessage(EnumChatFormat.color("&c[TAB v" + TabConstants.PLUGIN_VERSION + "] ") + EnumChatFormat.decolor(message), false);
TAB.getInstance().getPlatform().logWarn(new IChatBaseComponent(message));
}
for (String line : error) {
if (file.length() < 1000000)
buf.write(dateFormat.format(new Date()) + line + System.getProperty("line.separator"));
if (intoConsoleToo || TAB.getInstance().getConfiguration().isDebugMode())
TAB.getInstance().sendConsoleMessage(EnumChatFormat.color("&c") + line, false);
TAB.getInstance().getPlatform().logWarn(new IChatBaseComponent(line));
}
}
} catch (IOException ex) {
TAB.getInstance().sendConsoleMessage("&cAn error occurred when printing error message into file", true);
TAB.getInstance().sendConsoleMessage(ex.getClass().getName() + ": " + ex.getMessage(), true);
TAB.getInstance().getPlatform().logWarn(new IChatBaseComponent("An error occurred when printing error message into file"));
TAB.getInstance().getPlatform().logWarn(new IChatBaseComponent(ex.getClass().getName() + ": " + ex.getMessage()));
for (StackTraceElement e : ex.getStackTrace()) {
TAB.getInstance().sendConsoleMessage("\t" + e.toString(), true);
TAB.getInstance().getPlatform().logWarn(new IChatBaseComponent("\t" + e.toString()));
}
TAB.getInstance().sendConsoleMessage("&cOriginal error: " + message, true);
TAB.getInstance().getPlatform().logWarn(new IChatBaseComponent("Original error: " + message));
for (String line : error) {
TAB.getInstance().sendConsoleMessage(line, true);
TAB.getInstance().getPlatform().logWarn(new IChatBaseComponent(line));
}
}
}
@@ -211,7 +212,7 @@ public class ErrorManager {
*/
public double parseDouble(@NotNull String string, double defaultValue) {
try {
return Double.parseDouble(string);
return Double.parseDouble(string.replace(",", "."));
} catch (NumberFormatException e) {
return defaultValue;
}
@@ -69,7 +69,7 @@ public class FeatureManager {
((UnLoadable) f).unload();
TAB.getInstance().debug("Feature " + f.getClass().getSimpleName() + " processed unload in " + (System.currentTimeMillis()-time) + "ms");
}
TAB.getInstance().getPlaceholderManager().getTabExpansion().unregister();
TAB.getInstance().getPlaceholderManager().getTabExpansion().unregisterExpansion();
if (TAB.getInstance().getPlatform() instanceof ProxyPlatform) {
for (TabPlayer player : TAB.getInstance().getOnlinePlayers()) {
((ProxyTabPlayer)player).sendPluginMessage("Unload");
@@ -164,7 +164,6 @@ public class FeatureManager {
if (changed == null) return;
String from = changed.getServer();
changed.setServer(to);
changed.getScoreboard().clearRegisteredObjectives();
((ProxyTabPlayer)changed).sendJoinPluginMessage();
for (TabFeature f : values) {
if (!(f instanceof ServerSwitchListener)) continue;
@@ -251,6 +250,45 @@ public class FeatureManager {
}
}
public void onEntryAdd(TabPlayer packetReceiver, UUID id, String name) {
for (TabFeature f : values) {
if (!(f instanceof EntryAddListener)) continue;
long time = System.nanoTime();
((EntryAddListener)f).onEntryAdd(packetReceiver, id, name);
TAB.getInstance().getCPUManager().addTime(f, TabConstants.CpuUsageCategory.PACKET_PLAYER_INFO, System.nanoTime() - time);
}
}
public int onLatencyChange(TabPlayer packetReceiver, UUID id, int latency) {
int newLatency = latency;
for (TabFeature f : values) {
if (!(f instanceof LatencyListener)) continue;
long time = System.nanoTime();
newLatency = ((LatencyListener)f).onLatencyChange(packetReceiver, id, newLatency);
TAB.getInstance().getCPUManager().addTime(f, TabConstants.CpuUsageCategory.PACKET_PLAYER_INFO, System.nanoTime() - time);
}
return newLatency;
}
public void onLoginPacket(TabPlayer packetReceiver) {
packetReceiver.getScoreboard().unfreeze();
for (TabFeature f : values) {
if (!(f instanceof LoginPacketListener)) continue;
long time = System.nanoTime();
((LoginPacketListener)f).onLoginPacket(packetReceiver);
TAB.getInstance().getCPUManager().addTime(f, TabConstants.CpuUsageCategory.PACKET_LOGIN, System.nanoTime() - time);
}
}
public void onTabListClear(TabPlayer packetReceiver) {
for (TabFeature f : values) {
if (!(f instanceof TabListClearListener)) continue;
long time = System.nanoTime();
((TabListClearListener)f).onTabListClear(packetReceiver);
TAB.getInstance().getCPUManager().addTime(f, TabConstants.CpuUsageCategory.TABLIST_CLEAR, System.nanoTime() - time);
}
}
public void registerFeature(@NotNull String featureName, @NotNull TabFeature featureHandler) {
features.put(featureName, featureHandler);
values = features.values().toArray(new TabFeature[0]);
@@ -284,45 +322,50 @@ public class FeatureManager {
FeatureManager featureManager = TAB.getInstance().getFeatureManager();
int minorVersion = TAB.getInstance().getServerVersion().getMinorVersion();
if (configuration.getConfig().getBoolean("bossbar.enabled", false)) {
featureManager.registerFeature(TabConstants.Feature.BOSS_BAR,
minorVersion >= 9 ? new BossBarManagerImpl() : TAB.getInstance().getPlatform().getLegacyBossBar());
}
boolean pingSpoof = configuration.getConfig().getBoolean("ping-spoof.enabled", false);
boolean bossbar = configuration.getConfig().getBoolean("bossbar.enabled", false);
boolean headerFooter = configuration.getConfig().getBoolean("header-footer.enabled", true);
boolean spectatorFix = configuration.getConfig().getBoolean("prevent-spectator-effect.enabled", false);
boolean scoreboard = configuration.getConfig().getBoolean("scoreboard.enabled", false);
boolean perWorldPlayerList = configuration.getConfig().getBoolean("per-world-playerlist.enabled", false);
boolean layout = configuration.getConfig().getBoolean("layout.enabled", false);
boolean yellowNumber = configuration.getConfig().getBoolean("yellow-number-in-tablist.enabled", true);
boolean belowName = configuration.getConfig().getBoolean("belowname-objective.enabled", false);
boolean teams = configuration.getConfig().getBoolean("scoreboard-teams.enabled", true);
boolean unlimitedTags = configuration.getConfig().getBoolean("scoreboard-teams.unlimited-nametag-mode.enabled", false);
boolean globalPlayerList = configuration.getConfig().getBoolean("global-playerlist.enabled", false);
boolean tablistFormatting = configuration.getConfig().getBoolean("tablist-name-formatting.enabled", true);
boolean alignedSuffix = configuration.getConfig().getBoolean("tablist-name-formatting.align-tabsuffix-on-the-right", false);
if (configuration.getConfig().getBoolean("header-footer.enabled", true))
featureManager.registerFeature(TabConstants.Feature.HEADER_FOOTER, new HeaderFooter());
if (configuration.getConfig().getBoolean("prevent-spectator-effect.enabled", false))
featureManager.registerFeature(TabConstants.Feature.SPECTATOR_FIX, new SpectatorFix());
if (perWorldPlayerList && layout) TAB.getInstance().getMisconfigurationHelper().bothPerWorldPlayerListAndLayoutEnabled();
if (yellowNumber && layout) TAB.getInstance().getMisconfigurationHelper().layoutBreaksYellowNumber();
if (spectatorFix && layout) TAB.getInstance().getMisconfigurationHelper().layoutIncludesPreventSpectatorEffect();
if (globalPlayerList && layout) TAB.getInstance().getMisconfigurationHelper().bothGlobalPlayerListAndLayoutEnabled();
if (configuration.isPipelineInjection()) {
PipelineInjector inj = TAB.getInstance().getPlatform().createPipelineInjector();
if (inj != null) featureManager.registerFeature(TabConstants.Feature.PIPELINE_INJECTION, inj);
}
if (configuration.getConfig().getBoolean("scoreboard.enabled", false))
featureManager.registerFeature(TabConstants.Feature.SCOREBOARD, new ScoreboardManagerImpl());
if (configuration.getConfig().getBoolean("per-world-playerlist.enabled", false)) {
if (perWorldPlayerList) {
TabFeature pwp = TAB.getInstance().getPlatform().getPerWorldPlayerList();
if (pwp != null) featureManager.registerFeature(TabConstants.Feature.PER_WORLD_PLAYER_LIST, pwp);
}
if (configuration.getConfig().getBoolean("yellow-number-in-tablist.enabled", true))
featureManager.registerFeature(TabConstants.Feature.YELLOW_NUMBER, new YellowNumber());
if (configuration.getConfig().getBoolean("belowname-objective.enabled", true))
featureManager.registerFeature(TabConstants.Feature.BELOW_NAME, new BelowName());
// No requirements, but due to chicken vs egg, the feature uses NameTags, Layout and RedisBungee
if (configuration.getConfig().getBoolean("scoreboard-teams.enabled", true) ||
configuration.getConfig().getBoolean("layout.enabled", false)) {
featureManager.registerFeature(TabConstants.Feature.SORTING, new Sorting());
if (bossbar) {
featureManager.registerFeature(TabConstants.Feature.BOSS_BAR,
minorVersion >= 9 ? new BossBarManagerImpl() : TAB.getInstance().getPlatform().getLegacyBossBar());
}
if (pingSpoof) featureManager.registerFeature(TabConstants.Feature.PING_SPOOF, new PingSpoof());
if (headerFooter) featureManager.registerFeature(TabConstants.Feature.HEADER_FOOTER, new HeaderFooter());
if (spectatorFix) featureManager.registerFeature(TabConstants.Feature.SPECTATOR_FIX, new SpectatorFix());
if (scoreboard) featureManager.registerFeature(TabConstants.Feature.SCOREBOARD, new ScoreboardManagerImpl());
if (yellowNumber) featureManager.registerFeature(TabConstants.Feature.YELLOW_NUMBER, new YellowNumber());
if (belowName) featureManager.registerFeature(TabConstants.Feature.BELOW_NAME, new BelowName());
if (teams || layout) featureManager.registerFeature(TabConstants.Feature.SORTING, new Sorting());
// Must be loaded after: Sorting
if (configuration.getConfig().getBoolean("scoreboard-teams.enabled", true)) {
if (configuration.getConfig().getBoolean("scoreboard-teams.unlimited-nametag-mode.enabled", false) && minorVersion >= 8) {
if (teams) {
if (unlimitedTags && minorVersion >= 8) {
NameTag unlimited = TAB.getInstance().getPlatform().getUnlimitedNameTags();
if (unlimited instanceof NameTagX) {
featureManager.registerFeature(TabConstants.Feature.UNLIMITED_NAME_TAGS, unlimited);
@@ -335,13 +378,11 @@ public class FeatureManager {
}
// Must be loaded after: Sorting
if (minorVersion >= 8 && configuration.getConfig().getBoolean("layout.enabled", false)) {
featureManager.registerFeature(TabConstants.Feature.LAYOUT, new LayoutManagerImpl());
}
if (layout) featureManager.registerFeature(TabConstants.Feature.LAYOUT, new LayoutManagerImpl());
// Must be loaded after: Layout
if (minorVersion >= 8 && configuration.getConfig().getBoolean("tablist-name-formatting.enabled", true)) {
if (configuration.getConfig().getBoolean("tablist-name-formatting.align-tabsuffix-on-the-right", false)) {
if (tablistFormatting) {
if (alignedSuffix) {
featureManager.registerFeature(TabConstants.Feature.PLAYER_LIST, new AlignedPlayerList());
} else {
featureManager.registerFeature(TabConstants.Feature.PLAYER_LIST, new PlayerList());
@@ -349,16 +390,14 @@ public class FeatureManager {
}
// Must be loaded after: PlayerList
if (configuration.getConfig().getBoolean("global-playerlist.enabled", false) &&
TAB.getInstance().getServerVersion() == ProtocolVersion.PROXY) {
if (globalPlayerList && TAB.getInstance().getServerVersion() == ProtocolVersion.PROXY) {
featureManager.registerFeature(TabConstants.Feature.GLOBAL_PLAYER_LIST, new GlobalPlayerList());
if (configuration.getConfig().getBoolean("layout.enabled", false)) {
TAB.getInstance().getMisconfigurationHelper().bothGlobalPlayerListAndLayoutEnabled();
}
}
// Must be loaded after: Global PlayerList, PlayerList, NameTags, YellowNumber, BelowName
RedisSupport redis = TAB.getInstance().getPlatform().getRedisSupport();
if (redis != null) TAB.getInstance().getFeatureManager().registerFeature(TabConstants.Feature.REDIS_BUNGEE, redis);
featureManager.registerFeature(TabConstants.Feature.NICK_COMPATIBILITY, new NickCompatibility());
}
}
Loaded 100 of 245 files, more files were not shown because too many files have changed in this diff. Show more