Compare commits

...
537 Commits
Author SHA1 Message Date
NEZNAMY 0efb69fb4b [Fabric] Add 1.21.1 to supported versions 2024-08-10 09:46:22 +02:00
NEZNAMY 5cbec52ea3 4.1.7 2024-08-10 09:38:23 +02:00
NEZNAMY 8db9e64121 Update to 1.21.1 2024-08-10 09:37:20 +02:00
NEZNAMY 05009b17f0 [Layout] Fix API usage causing placeholders to not refresh (#1347) 2024-08-06 17:57:41 +02:00
NEZNAMY 032af707dd [Sorting] Fix sorting placeholder using old instance when a placeholder is overridden 2024-07-25 20:52:33 +02:00
NEZNAMY cf227a97f3 [BungeeCord] Add try/catch for yet another NPE in BungeeCord code (#1338) 2024-07-25 15:56:21 +02:00
NEZNAMY 1a2075dfe2 [Bukkit] Fix compatibility with random modded server 2024-07-22 19:26:57 +02:00
NEZNAMY 1de7665309 [Bug fix] Colors not displaying correctly in Fabric and Velocity console (#1298) 2024-06-26 14:25:05 +02:00
NEZNAMY 9473037127 Merge pull request #1323 from sunmisc/master
represent getOnlinePlayers as a collection
2024-06-22 23:37:47 +02:00
sunmisc dcb234dc08 stream api to collection 2024-06-23 00:35:16 +03:00
sunmisc 79eb34fc32 Revert "stream api to collection"
This reverts commit 40544ef5d3.
2024-06-23 00:31:38 +03:00
sunmisc 40544ef5d3 stream api to collection 2024-06-23 00:19:33 +03:00
sunmisc 14dfa0154a Stream api for getOnlinePlayers 2024-06-22 23:44:51 +03:00
NEZNAMY 7df2aba2d1 [RedisBungee] Override %serveronline% to count redis players as well (#1316) 2024-06-16 20:10:25 +02:00
NEZNAMY 9edf3176ea [MiniMessage] Add dummy character to parsed text to disable color compacting (#1312) 2024-06-16 09:16:57 +02:00
NEZNAMY ec73aa13b4 4.1.7-SNAPSHOT 2024-06-16 09:16:48 +02:00
NEZNAMY 6f002cd61b Change action builds to only upload from v5 branch 2024-06-13 21:11:12 +02:00
NEZNAMY cf065ba27d 4.1.6 2024-06-13 19:19:54 +02:00
NEZNAMY f96e263bd7 Add 1.21 support 2024-06-13 19:19:41 +02:00
NEZNAMY a22f8eccaf Update message about unlimited nametags 2024-06-13 15:28:44 +02:00
NEZNAMY 718ee89211 [BungeeCord] Fix another issue leading to "Network Protocol Error" disconnect on 1.20.5+ 2024-06-13 11:33:04 +02:00
NEZNAMY 54b44ce6cd [Unlimited nametags] Print console warn if the feature is enabled 2024-06-13 11:30:26 +02:00
NEZNAMY 29da28e955 [BungeeCord] Fix error in pipeline injector (#1310) 2024-06-12 17:44:24 +02:00
NEZNAMY fa195389ab [Sorting] Fix sorting by pre-defined values space trimming inconsistency (#1308) 2024-06-11 15:39:39 +02:00
NEZNAMY 9e3864be53 [Bukkit] Add missing support for mojang mapped jars 2024-06-06 15:16:17 +02:00
NEZNAMY 29b89b3bb7 [Commands] Fix error when setting property value to ' or " 2024-06-01 16:50:02 +02:00
NEZNAMY e741608261 [Placeholders] Fix server placeholders not returning proper value on request sometimes 2024-05-30 16:10:38 +02:00
NEZNAMY 8e8a4cc9e8 [Teams] [Bukkit] Fix team action not being taken correctly on 1.8 - 1.12, causing issues with previous commit 2024-05-29 13:34:35 +02:00
NEZNAMY 3bb6e0c3f2 [Teams] Fix another instance of "Network Protocol Error" disconnect on 1.20.5+ 2024-05-28 14:40:17 +02:00
NEZNAMY 4c3b13a81c 4.1.6-SNAPSHOT 2024-05-28 14:40:04 +02:00
NEZNAMY 6b9063c789 4.1.5 2024-05-27 15:58:52 +02:00
NEZNAMY ce0a45b76c [Bukkit] Add support for latest 1.21 snapshot 2024-05-27 13:46:47 +02:00
NEZNAMY 41a0bd37ce [Sorting] Fix number compression bug (#1297) 2024-05-26 12:38:28 +02:00
NEZNAMY 449d3cb3ee [BossBar] Fix error when trying to display bossbar that was not visible on join 2024-05-22 10:14:52 +02:00
NEZNAMY a1a3852b08 [BossBar] Fix Network Protocol Error disconnect on server switch 2024-05-20 12:41:07 +02:00
NEZNAMY ef63df5175 [BossBar] Make %countdown% return 0 after announcement is over instead of going to negative numbers 2024-05-20 11:08:08 +02:00
NEZNAMY 17e57f9d0c [Bug fix] Fix disabled tablist formatting referencing wrong variable 2024-05-16 21:00:01 +02:00
NEZNAMY 949216325f [Nametags] Only make nametags of invisible players invisible for 1.8 players to compensate for the bug instead of everyone (#1287) 2024-05-11 21:29:55 +02:00
NEZNAMY dddf0b35a2 [Code style] Add missing access level to field 2024-05-11 11:43:51 +02:00
NEZNAMY 35ec2bf215 [Code style] Make fields final 2024-05-11 11:43:05 +02:00
NEZNAMY 607b60b323 [Code style] Remove unused method 2024-05-11 11:41:35 +02:00
NEZNAMY bb6ad72962 [Code style] Improve presence check of Player#getPing method 2024-05-10 17:05:47 +02:00
NEZNAMY 4fb8420d69 [Code style] Remove unused method 2024-05-10 16:59:11 +02:00
NEZNAMY 74af19a9d9 [Code style] Move field to a local variable 2024-05-10 16:53:51 +02:00
NEZNAMY 737ac70e6e [Fabric] Update to 24w19a 2024-05-10 16:28:39 +02:00
NEZNAMY bfc72b735c [Fabric] Add another submodule for 1.18.2 to avoid hardcoding method and class names 2024-05-10 16:05:09 +02:00
NEZNAMY 323412f935 [Code style] Decrease field visibility 2024-05-10 14:56:00 +02:00
NEZNAMY db34ba4995 [Fabric] Improve check for Fabric API features availability 2024-05-10 14:54:01 +02:00
NEZNAMY 15d7eb6d91 [Javadoc] Document more methods 2024-05-10 14:50:02 +02:00
NEZNAMY e2b95df2e4 [Bukkit] Explicitly specify constructor to fix compatibility with forge mods that widen a constructor access, causing TAB to choose the wrong one on 1.19.3+ (#1276) 2024-05-10 13:42:50 +02:00
NEZNAMY 1117094412 [Bug fix] Update library to fix error 2024-05-09 14:54:46 +02:00
NEZNAMY 4bab076642 [Bug fix] Fix group change from Bridge not being processed correctly (#1281) 2024-05-09 13:38:36 +02:00
NEZNAMY d602818f67 [Bug fix] Disable tablist containsEntry usage due to a race condition that sometimes makes it not work properly until a better solution is found (#1284) 2024-05-08 09:37:35 +02:00
NEZNAMY 1a76321062 [Fabric] Update to 24w18a 2024-05-04 20:37:08 +02:00
NEZNAMY 4b056529dd [Fix] Add cpu usage of placeholders inside plugin's main thread instead of placeholder thread to avoid incorrect numbers when it's overloaded 2024-05-04 20:29:21 +02:00
NEZNAMY bdbf3ba5b1 [Velocity] Remove message printing if attempting to send plugin message to a player who is not connected to any server (#1248) 2024-05-03 22:35:29 +02:00
NEZNAMY aab9a650b0 Fix startup error when using a plugin that shades MiniMessage without shading component serializers (#1280) 2024-05-03 14:47:25 +02:00
NEZNAMY f8487f07a6 Create array of component decorations to take values from to avoid instantiation each time 2024-05-02 21:06:15 +02:00
NEZNAMY e38721fa69 Speed up function 2024-05-02 19:30:22 +02:00
NEZNAMY 18e52f2f10 Add 1.20.6 to known versions 2024-04-29 18:06:16 +02:00
NEZNAMY f1300410b8 Update Fabric to 1.20.6 2024-04-29 18:03:52 +02:00
NEZNAMY e17059f1f7 Update library to fix error (1276) 2024-04-29 12:26:29 +02:00
NEZNAMY f3e9f7cf06 [BungeeCord] Remove console warn regarding ViaVersion on BungeeCord as it no longer breaks anything 2024-04-28 18:08:50 +02:00
NEZNAMY 63ed24d136 [Bukkit] Use Spigot internals over Paper API as a primary TPS getter (uses 10x less CPU and 100x less memory allocations) 2024-04-28 14:01:33 +02:00
NEZNAMY 66008cd767 [Velocity] Fix "Received packet for unknown scoreboard objective: TAB-BelowName" on server switch 2024-04-27 19:03:46 +02:00
NEZNAMY 1852c1b5c4 Create array to iterate over to prevent creating new instance every time 2024-04-26 20:12:45 +02:00
NEZNAMY 9a4fc2fc73 Fix field type 2024-04-26 11:52:55 +02:00
NEZNAMY 6846a78833 Require plugin list in a bug report 2024-04-26 11:52:15 +02:00
NEZNAMY d3d9e85a7f Merge branch '4.1.5'
# Conflicts:
#	build.gradle.kts
2024-04-26 11:47:03 +02:00
NEZNAMY f867b6b2ea 4.1.5-SNAPSHOT 2024-04-23 21:09:31 +02:00
NEZNAMY 6b38954694 4.1.4 2024-04-23 20:57:31 +02:00
NEZNAMY 9eb350df30 Fix startup on 1.19.3 - 1.20.4 2024-04-23 20:57:12 +02:00
NEZNAMY 639aa5e251 Fix startup on 1.19.3 - 1.20.4 2024-04-23 20:56:06 +02:00
NEZNAMY f089e26998 Added 1.20.5 support to Bukkit 2024-04-23 19:30:14 +02:00
NEZNAMY 9070a6aadc 4.1.3 2024-04-23 19:11:24 +02:00
NEZNAMY c68e135c66 Added 1.20.5 support to Bukkit 2024-04-23 19:11:17 +02:00
NEZNAMY 8693b4fe8f Mark 1.20.5 as latest supported version 2024-04-23 17:37:21 +02:00
NEZNAMY 9390a402ff Update Fabric to 1.20.5 2024-04-23 17:35:38 +02:00
NEZNAMY 7fda2c5f88 Add 1.20.5 ProtocolVersion constant 2024-04-23 17:35:15 +02:00
NEZNAMY cb394007a4 Update Fabric to 1.20.5 2024-04-23 17:25:00 +02:00
NEZNAMY cd92910281 Add 1.20.5 ProtocolVersion constant 2024-04-23 17:24:35 +02:00
NEZNAMY 7f0b2e2b13 Initialize feature data structured on declaration to make it cleaner & fix error 2024-04-23 16:16:51 +02:00
NEZNAMY bdc44d46c8 Add Exception throwing when attempting to use API class instances that got discarded on reload. 2024-04-23 16:10:24 +02:00
NEZNAMY 8d8480469a Update Fabric to 1.20.5-rc3 2024-04-23 15:28:30 +02:00
NEZNAMY a91fe6db36 Remove unused SneakyThrows 2024-04-23 12:51:41 +02:00
NEZNAMY 20d734aa80 Add missing action mapping 2024-04-23 12:43:20 +02:00
NEZNAMY f053bbd544 Access Team packet data directly on 1.17+ Fabric instead of using reflection 2024-04-23 12:40:14 +02:00
NEZNAMY 7eba26cab1 Rename class to match version number 2024-04-23 12:15:47 +02:00
NEZNAMY 5f77dd0adb Rename class to match version number 2024-04-23 12:14:42 +02:00
NEZNAMY 4ef94d3c60 Update Fabric to 1.20.5-rc3 2024-04-22 17:03:08 +02:00
NEZNAMY 36006045e2 Add "listed" TabList flag support into internal API 2024-04-22 16:54:39 +02:00
NEZNAMY d5d025c2c9 Store information about player meeting a feature's disable condition inside player class to improve performance by avoiding Map lookup each time 2024-04-22 14:41:34 +02:00
NEZNAMY 5b7053d500 Store feature data of players inside TabPlayer class to improve performance by avoiding Map lookup 2024-04-22 14:06:37 +02:00
NEZNAMY ce2e60c268 4.1.4-SNAPSHOT 2024-04-21 21:43:25 +02:00
NEZNAMY 8e9b575c37 Update Fabric to 1.20.5-rc2 2024-04-19 20:13:26 +02:00
NEZNAMY beab7d5a4f Update Fabric to 1.20.5-rc1 2024-04-18 16:26:16 +02:00
NEZNAMY d2f5fc4d69 Update Fabric to 1.20.5-pre4 2024-04-17 15:57:16 +02:00
NEZNAMY fb9a7f693b Update Fabric to 1.20.5-pre3 2024-04-16 16:48:03 +02:00
NEZNAMY 1d2d636270 Fix compilation warning on Fabric 1.14.4 submodule 2024-04-16 09:28:35 +02:00
NEZNAMY 37ec5ee442 Update Fabric to 1.20.5-pre2 2024-04-15 19:08:50 +02:00
NEZNAMY 5ed67cf5f8 Remove recently added config option that doesn't add any real value 2024-04-13 13:21:04 +02:00
NEZNAMY 76fc47d5bd Fix client lag when another plugin removes player from team (#1193) 2024-04-13 13:11:35 +02:00
NEZNAMY aa856beee1 Refactor team anti-override code to avoid duplicating code 2024-04-13 11:37:50 +02:00
NEZNAMY e6f2cc3eda Simplify 2024-04-12 15:00:32 +02:00
NEZNAMY 02fd45587b Fix replacements to %group% changing the group name inside plugin internals as well 2024-04-12 14:45:32 +02:00
NEZNAMY 7cef6eeb61 Update Fabric to 1.20.5-pre1 2024-04-10 19:11:43 +02:00
NEZNAMY da919bc43b Add BuiltByBit link to download links 2024-04-05 22:52:13 +02:00
NEZNAMY 2b661d6c5b Downgrade Java requirement on Fabric back to 17 2024-04-04 19:01:58 +02:00
NEZNAMY ee0ced63ef Update Fabric to 24w14a and Java version to 21 2024-04-03 21:22:35 +02:00
NEZNAMY 736dad5819 Merge pull request #1263 from JT122406/master
Update Loom and Gradle to Latest
2024-04-03 19:08:45 +02:00
Joseph T. McQuigg 11f36e8a22 Update Loom and Gradle to Latest 2024-04-03 13:05:57 -04:00
NEZNAMY 2cddf0b313 Add version compatibility check on Velocity 2024-03-31 12:03:58 +02:00
NEZNAMY 36a2d449b7 Add player version printing to debug command 2024-03-30 13:48:42 +01:00
NEZNAMY b6786456a2 Fix wrong method being overridden 2024-03-30 13:28:14 +01:00
NEZNAMY 76e8e1af6f Remove redundant .toLowerCase() 2024-03-30 12:33:49 +01:00
NEZNAMY ff55838a54 Remove unused imports 2024-03-29 14:21:21 +01:00
NEZNAMY b14b6828cc Move accesswidener file to resources/ folder instead of root in the compiled jar 2024-03-28 10:58:03 +01:00
NEZNAMY be7fb67988 Move configuration files to config/ folder instead of root in the compiled jar 2024-03-28 10:49:53 +01:00
NEZNAMY ae0be45d7d Drop 1.17(.0) support on Fabric (1.17.1 still supported) 2024-03-28 10:36:47 +01:00
NEZNAMY ba237d5dc4 Update Fabric to 24w13a 2024-03-28 10:27:22 +01:00
NEZNAMY bdf5cd8751 Remove unused constructor 2024-03-28 10:27:06 +01:00
NEZNAMY 9a6d309840 Update Fabric to 24w12a 2024-03-20 17:42:51 +01:00
NEZNAMY 9a6e92a58c Micro optimization of placeholder output replacements 2024-03-20 15:05:20 +01:00
NEZNAMY 1bc018918b Use common implementation of a method as default instead of making it abstract 2024-03-20 14:49:37 +01:00
NEZNAMY f32f8f5cdd Convert components in scoreboard class instead of each subclass 2024-03-20 14:39:46 +01:00
NEZNAMY 39585de3f0 Remove fallback solutions for Scoreboard on Bukkit 2024-03-20 14:16:18 +01:00
NEZNAMY 9ce81fc68e Convert component in abstract TabList class instead of manually in every class 2024-03-20 13:54:14 +01:00
NEZNAMY 0d56f0da98 Remove backup solutions for header/footer on Bukkit and only use packets 2024-03-20 13:49:31 +01:00
NEZNAMY 14a3d8da12 Save converted components as a field in TAB component instead of a separate cache that requires lookup, making it slower 2024-03-19 12:57:35 +01:00
NEZNAMY e4fa99b865 Use packets as primary solution for header/footer on Bukkit 2024-03-19 09:42:48 +01:00
NEZNAMY 96af2cb573 Drop native <gradient:#RRGGBB:#RRGGBB>Text</gradient> support to fix unexpected results when used in a way supported by MiniMessage but not this TAB function (and also because TAB called that function first) (#1252) 2024-03-18 22:40:57 +01:00
NEZNAMY f53ec58b2f Fix redundant empty component creation when using magic code after color code 2024-03-18 21:58:46 +01:00
NEZNAMY 2ff30dda83 Use HashMap instead of ConcurrentHashMap for better performance 2024-03-18 18:38:56 +01:00
NEZNAMY c0972f8faf Simplify 2024-03-18 15:52:25 +01:00
NEZNAMY 9663e1aa7e Remove redundant check 2024-03-18 15:46:28 +01:00
NEZNAMY d83a61b30a Replace &#RRGGBB with #RRGGBB sooner to save resources while refreshing 2024-03-18 15:35:59 +01:00
NEZNAMY 96d83da9b7 Remove unused method 2024-03-18 15:23:31 +01:00
NEZNAMY f1d881e0ba Add more checks to API methods 2024-03-18 13:41:23 +01:00
NEZNAMY d1285c7e04 Only send belowname update packets to players in the same world and server 2024-03-18 11:31:41 +01:00
NEZNAMY d4476e2d4c Optimize tablist packet creation on 1.19.3+ Bukkit & Fabric 2024-03-18 10:57:47 +01:00
NEZNAMY 3124d51381 Micro optimization of permission conditions 2024-03-18 10:19:12 +01:00
NEZNAMY b6ae1a79d4 Fix breaking errors on Fabric 1.16 - 1.18.2 2024-03-15 21:29:49 +01:00
NEZNAMY 42d7b598bf Improve comments in accesswidener 2024-03-15 21:29:00 +01:00
NEZNAMY 052e828d5a Split player spawn packet reading on Fabric based on version 2024-03-15 20:51:38 +01:00
NEZNAMY 55d4f8da6b Use logger directly on Fabric 1.18.2+ 2024-03-15 20:33:52 +01:00
NEZNAMY 1c5a4bbacc Simplify channel getting on BungeeCord 2024-03-15 16:54:27 +01:00
NEZNAMY 565d4bdeec Add java version requirement to fabric.mod.json 2024-03-15 15:47:56 +01:00
NEZNAMY 3d9ef7d252 Remove no longer needed SneakyThrows annotation 2024-03-15 15:39:23 +01:00
NEZNAMY 161bcbb236 Merge scoreboard packet listener methods into one 2024-03-15 15:22:23 +01:00
NEZNAMY c8af0ab5bc Use access widener on Fabric instead of reflection where easily possible 2024-03-14 20:46:04 +01:00
NEZNAMY 98e9c167c5 Update Fabric to 24w11a 2024-03-14 18:52:44 +01:00
NEZNAMY ed815c5767 Remove unintended return type 2024-03-14 16:14:14 +01:00
NEZNAMY f6e53ac91f Fix raw generic usage 2024-03-14 16:09:15 +01:00
NEZNAMY 3e5df50f9e Rename generic types 2024-03-14 16:05:44 +01:00
NEZNAMY 1e28a93c5f Replace star import with class imports 2024-03-14 16:02:13 +01:00
NEZNAMY 717bdd3550 Catch everything 2024-03-12 22:55:44 +01:00
NEZNAMY 3bee514bb9 Actually fix the previous error at its source 2024-03-12 10:32:56 +01:00
NEZNAMY 678d4b8db7 Use ConcurrentHashMap to fix ConcurrentModificationException 2024-03-12 10:02:06 +01:00
NEZNAMY d76e5e7f9b Simplify method (also probably speed it up) 2024-03-11 16:03:25 +01:00
NEZNAMY 9629192305 Simplify 2024-03-11 16:00:52 +01:00
NEZNAMY eef7debdba Remove unnecessary full class name 2024-03-09 11:35:23 +01:00
NEZNAMY 138d9f7f46 Initialize more variables in constructor 2024-03-08 12:52:47 +01:00
NEZNAMY e4abd9ec2f Refactor multi-version support on Fabric 2024-03-08 11:21:42 +01:00
NEZNAMY 8efce72e26 Remove component serialization code as it is no longer used 2024-03-07 19:04:27 +01:00
NEZNAMY 15ebca2d11 Use direct component conversion on Fabric instead of serializing and deserializing json 2024-03-07 18:47:55 +01:00
NEZNAMY 6583489523 Remove json deserializer support on Bukkit and only use direct component conversion 2024-03-07 17:15:53 +01:00
NEZNAMY 5610d52813 Remove part of a comment 2024-03-07 17:03:42 +01:00
NEZNAMY 618b61dd60 Use Adventure API to serialize components on Velocity 2024-03-07 17:03:24 +01:00
NEZNAMY 2de58f7a3b Remove RGB support in ViaBossbar (1.16+ clients on 1.8 server) 2024-03-07 16:44:50 +01:00
NEZNAMY 2bd94158f9 Remove redundant code 2024-03-07 14:47:56 +01:00
NEZNAMY 4e7ab43ac9 Update Fabric to 24w10a 2024-03-06 22:38:08 +01:00
NEZNAMY 33529d0cfd Slightly optimize vanish check and usage 2024-03-06 21:00:40 +01:00
NEZNAMY a5b929bdf5 Optimize tablist packet check on 1.19.3+ 2024-03-06 20:37:01 +01:00
NEZNAMY d10bd3358d Simplify 2024-03-05 15:58:14 +01:00
NEZNAMY ffe928fbe8 Save EnumSet for each tablist action into map and retrieve it instead of creating new Set on every packet send. 2024-03-05 15:51:16 +01:00
NEZNAMY fd61739d96 Remove deleted option from default config 2024-03-04 17:03:30 +01:00
NEZNAMY e1602a5655 Refactor TabList class to extract common content from each subclass into the main one 2024-03-04 13:48:53 +01:00
NEZNAMY a936302fd2 Rework tablist anti-override to save expected names and then restore the save values instead of creating them again 2024-03-04 11:45:18 +01:00
NEZNAMY 7f0d4df6ca Save teams for later use instead of recreating them every time 2024-03-03 21:23:47 +01:00
NEZNAMY 35ab0c67c5 Remove unused dependencies 2024-03-02 18:41:05 +01:00
NEZNAMY 090633140f Remove bug compensation 2024-03-02 17:21:16 +01:00
NEZNAMY 8efe02c2ef Further improve performance of command listener method (#1237) 2024-03-01 21:01:02 +01:00
NEZNAMY 2dbcaf029c Improve performance of a method if both scoreboard and bossbar features are disabled (#1237) 2024-03-01 20:28:50 +01:00
NEZNAMY ce758534bd Remove ClickEvent support as it's never used by the plugin 2024-03-01 11:58:19 +01:00
NEZNAMY 15caf70170 Update Bukkit 1.20.5 draft to 24w09a 2024-03-01 10:54:04 +01:00
NEZNAMY c07c3a3a4f Update Fabric to 24w09a 2024-03-01 10:37:43 +01:00
NEZNAMY b9000af55e Implement direct component conversion on Bukkit and use it as primary option. 2024-02-29 21:32:29 +01:00
NEZNAMY ed15ba29bb Create separate class for component conversion on Bukkit (will be extended soon) 2024-02-29 12:42:00 +01:00
NEZNAMY 2fb8da022a Fix startup on <1.8 2024-02-29 12:28:49 +01:00
NEZNAMY ed65088cb8 Add API method for setting player's expected game profile name (#1240) 2024-02-29 09:24:45 +01:00
NEZNAMY f367542096 Replace unoptimized online player check with an instant one 2024-02-28 14:19:38 +01:00
NEZNAMY 00e457542d Remove TabPlayer#isOnline as it was never really used 2024-02-28 14:12:22 +01:00
NEZNAMY b0c423b2dd Change sortingPlaceholder data type to avoid retrieving it each time 2024-02-27 20:57:32 +01:00
NEZNAMY eb5e3d637c Split method into multiple for better readability 2024-02-27 14:45:03 +01:00
NEZNAMY 4acd24b829 Make permission-related options refresh interval configurable 2024-02-27 13:59:58 +01:00
NEZNAMY f8576834fe Optimize method 2024-02-27 13:12:40 +01:00
NEZNAMY 1e9888a8b5 Optimize method 2024-02-27 13:08:17 +01:00
NEZNAMY d99b6643e9 Fix team color defaulting to black on Sponge 2024-02-27 11:17:16 +01:00
NEZNAMY 0b98d98867 Add Sponge 1.20.4 support 2024-02-27 11:04:55 +01:00
NEZNAMY d040b0faf5 Save global playerlist server group mapping instead of computing it on each request 2024-02-27 10:16:55 +01:00
NEZNAMY d53c20da56 Do not respawn armor stands on sneak for flying players (#1236). 2024-02-26 20:28:06 +01:00
NEZNAMY fad035699f Revert broken commit 2024-02-26 19:00:28 +01:00
NEZNAMY c7ab2b93a3 Move armor stands instead of teleporting them when players move. This should result in a significantly better sync. May also help with #1236 a bit. 2024-02-26 18:46:51 +01:00
NEZNAMY 4b75144276 Create cache for last color of prefix 2024-02-26 11:16:30 +01:00
NEZNAMY 99edcf68c1 Use new TabList method for better performance 2024-02-25 15:05:56 +01:00
NEZNAMY 9b6f1ce955 Add TabList#containsEntry method 2024-02-25 15:00:34 +01:00
NEZNAMY b570020944 Only send global playerlist latency update packets of players a player can actually see 2024-02-23 20:44:57 +01:00
NEZNAMY e91ba4ef79 Slightly speed up method 2024-02-23 19:14:45 +01:00
NEZNAMY a1fbefb13f Set use-numbers to true by default because buggy clients don't work without it and will not fix it anytime soon 2024-02-22 23:42:57 +01:00
NEZNAMY 340592d951 Document class 2024-02-22 11:59:45 +01:00
NEZNAMY a1c0bf7dec Use new Paper API for Scoreboard NumberFormat in fallback PaperScoreboard implementation 2024-02-20 18:49:52 +01:00
NEZNAMY a62f20469e Replace NotNull with NonNull in Scoreboard class for better troubleshooting 2024-02-19 20:22:17 +01:00
NEZNAMY 3f1f78315b Add Getter 2024-02-19 20:12:18 +01:00
NEZNAMY 12d8bc75cd Replace NotNull with NonNull in TabList class for better troubleshooting 2024-02-19 20:10:26 +01:00
NEZNAMY c1cf02ee9f Fix ConcurrentModificationException 2024-02-19 16:17:13 +01:00
NEZNAMY 7ac240af76 Update Fabric to 24w07a 2024-02-15 09:29:33 +01:00
NEZNAMY 82c34a33b5 Do not update tablist format for players on different servers who the player cannot see (#1232) 2024-02-14 18:08:04 +01:00
NEZNAMY d2330337ef Simplify method 2024-02-14 16:56:45 +01:00
NEZNAMY af38bd0383 Fix double score send on join 2024-02-14 11:52:07 +01:00
NEZNAMY c0a9ede377 Add runtime warn when belowname / playerlist objective placeholder returned decimal number 2024-02-14 11:45:40 +01:00
NEZNAMY 555ba3eb73 Split method into multiple for better readability and clean up 2024-02-11 20:24:54 +01:00
NEZNAMY 6f0e757ad6 Add console warn if condition type is needed but not configured 2024-02-11 20:07:59 +01:00
NEZNAMY 077b8fee96 Add more info into error message 2024-02-11 16:11:55 +01:00
NEZNAMY 643df47437 (Attempt to) fix deadlock (#1230) 2024-02-11 10:28:43 +01:00
NEZNAMY 9e1ea4678d Create shared empty instance for PlaceholderReplacementPattern to avoid new instances even if nothing is configured 2024-02-10 17:37:20 +01:00
NEZNAMY f3576b678b Make MySQL errors more user-friendly 2024-02-10 11:22:52 +01:00
NEZNAMY 2b49bce998 Add config option to disable SSL certification in MySQL if the database is not using it 2024-02-10 10:22:42 +01:00
NEZNAMY 896048ffff Replace "featureName" field with getter 2024-02-09 16:56:12 +01:00
NEZNAMY 04cb14f790 Add some more Javadoc 2024-02-09 16:12:16 +01:00
NEZNAMY bd22b0c08b Properly remove disconnected player from bossbars 2024-02-09 15:47:47 +01:00
NEZNAMY 95ce52bbeb Add TODO note 2024-02-09 11:04:40 +01:00
NEZNAMY 6b411f5cce Slightly optimize method 2024-02-09 10:56:59 +01:00
NEZNAMY c15b0d178b Create array field to iterate over for slightly better performance 2024-02-09 10:40:48 +01:00
NEZNAMY f5ffb69675 Disable tab expansion by default 2024-02-08 20:59:18 +01:00
NEZNAMY 24e59d970f Initialize NotNull field just in case 2024-02-08 17:10:55 +01:00
NEZNAMY 4ece4b66cd Only initialize map if used 2024-02-07 22:02:50 +01:00
NEZNAMY 7852d244bb Fix #1227 2024-02-07 21:37:27 +01:00
NEZNAMY d1c2487ba4 Enable CPU usage tracking only after running /tab cpu for the first time 2024-02-07 20:43:32 +01:00
NEZNAMY 4ea789efcf Add config option to disable refreshing placeholders in another thread (boosts performance) 2024-02-07 20:17:02 +01:00
NEZNAMY 43fc97c671 Update Fabric to 24w06a 2024-02-07 18:28:09 +01:00
NEZNAMY a20378d5cb Change abstract class to interface 2024-02-06 21:58:35 +01:00
NEZNAMY 7c0384c96b Create local variable to prevent spamming Map#get 2024-02-06 18:51:22 +01:00
NEZNAMY 7d4419b300 Override some methods in EmptyTabExpansion to avoid unnecessary operations 2024-02-06 17:56:28 +01:00
NEZNAMY 393af9b5c5 Add check to avoid unnecessary operations 2024-02-06 17:55:12 +01:00
NEZNAMY 559e423a1d Vastly improve performance of CPU usage tracking 2024-02-06 17:43:45 +01:00
NEZNAMY a2d68e14e6 Simplify 2024-02-06 17:10:21 +01:00
NEZNAMY 580b67d9ed Fix thread submitting tasks to itself 2024-02-06 16:45:57 +01:00
NEZNAMY 080654f703 Increase scoreboard line length limit by 2 characters on <1.13 2024-02-05 20:14:36 +01:00
NEZNAMY 002a002749 Remove redundant check 2024-02-05 19:19:51 +01:00
NEZNAMY 7e888e46f9 Implement best parts of #1219 2024-02-05 10:57:40 +01:00
NEZNAMY 199ca94acd Add compensation for 1.8.0 client-sided bug directly into TabList class (#1225) 2024-02-04 22:03:29 +01:00
NEZNAMY 67455bd47d Fix replacements not applying in nested placeholders 2024-02-04 18:32:21 +01:00
NEZNAMY fbb0e252c5 Allow -1 in refresh intervals 2024-02-04 17:57:44 +01:00
NEZNAMY 2566972ac7 Recode internal component class 2024-02-03 18:12:37 +01:00
NEZNAMY 9455294060 Simplify 2024-02-03 13:24:57 +01:00
NEZNAMY 6870b42a1a Merge branch 'master' of https://github.com/NEZNAMY/TAB 2024-02-03 11:22:45 +01:00
NEZNAMY 32cd414a51 Merge pull request #1224 from XXMA16/fix-spongeforge
Fix SpongeForge startup crash
2024-02-03 11:22:35 +01:00
XXMA16 35f63b7075 Fix code style
Signed-off-by: XXMA16 <xxma1610@gmail.com>
2024-02-03 02:43:51 +02:00
XXMA16 f9097b11b6 Fix SpongeForge startup crash
Signed-off-by: XXMA16 <xxma1610@gmail.com>
2024-02-02 19:37:19 +02:00
NEZNAMY dc5acdf729 Add default implementation to method instead of making it abstract 2024-02-01 22:09:49 +01:00
NEZNAMY e5fb25daee Update Bukkit 1.20.5 draft to match 24w05b changes 2024-02-01 20:47:03 +01:00
NEZNAMY 33b6867c02 Update Fabric to 24w05b 2024-02-01 20:39:16 +01:00
NEZNAMY d572973b60 Add console hint when configuring redundant placeholder replacement pattern using "else" 2024-01-31 19:28:30 +01:00
NEZNAMY 3d240f0f7d Fix NPE 2024-01-31 19:12:13 +01:00
NEZNAMY 3a4973d760 Change sidebar team naming 2024-01-29 11:43:25 +01:00
NEZNAMY 811c9716a2 Fix message 2024-01-28 13:26:04 +01:00
NEZNAMY 771f6db89a Remove unused imports 2024-01-28 10:55:44 +01:00
NEZNAMY 97f515f573 Remove placeholder output replacement from default config 2024-01-27 20:19:17 +01:00
NEZNAMY c589dfb281 Fix per-world settings of unlimited nametags not updating on world switch 2024-01-27 20:01:41 +01:00
NEZNAMY 82efe5b4d1 Simplify 2024-01-27 13:57:25 +01:00
NEZNAMY 765f593b88 Specify locale in .toLowerCase to avoid error when java is using a weird language 2024-01-27 13:52:01 +01:00
NEZNAMY 21e5de25cb Remove console warn when ViaVersion returns unknown version, because kids kept asking about it on ViaVersion discord despite the message clearly saying to update TAB. 2024-01-27 13:42:06 +01:00
NEZNAMY 4333e44f6d Fix false warn on proxy when bukkit permissions are enabled 2024-01-26 21:21:40 +01:00
NEZNAMY 7070fd886b Add support for Bukkit jars without CraftBukkit relocation (#1220) 2024-01-25 15:15:12 +01:00
NEZNAMY 89480a9018 Remove cpu report printing into console if overloaded and debug is enabled 2024-01-24 18:16:15 +01:00
NEZNAMY 19ce873037 Update Fabric to 24w04a 2024-01-24 17:30:58 +01:00
NEZNAMY 977007b427 Do not cache subcomponents on bungeecord (#1211) 2024-01-24 17:07:37 +01:00
NEZNAMY 218c1c9d9a Remove global serverVersion field and only use it in each platform if needed 2024-01-24 15:32:38 +01:00
NEZNAMY f358ac9d9c Create and use Platform#isProxy method 2024-01-24 14:02:38 +01:00
NEZNAMY 58fe2b4582 Make "enable-collision" support conditions 2024-01-24 13:08:03 +01:00
NEZNAMY 211624b1a9 Improve style of iteration 2024-01-24 11:56:01 +01:00
NEZNAMY 6874369a48 Simplify animation placeholder creation 2024-01-24 11:53:52 +01:00
NEZNAMY f8c52d3577 Initialize player group in declaration to prevent "Vault through Bridge" returning null on the first time (#1211) 2024-01-24 09:58:16 +01:00
NEZNAMY 376bf5314d Actually fix trying to process offline players (players are still marked as online by Bukkit API even after they disconnect) 2024-01-23 18:18:12 +01:00
NEZNAMY 2346df67a0 Fix trying to process offline players in placeholder update task 2024-01-23 18:05:01 +01:00
NEZNAMY e63c7b43e1 Show a runtime warn if belowname / playerlist objective cannot be evaluated to a number 2024-01-23 18:04:18 +01:00
NEZNAMY 578691fe8c Document and clean up class 2024-01-22 19:15:07 +01:00
NEZNAMY 189d8a82d5 Update default config to account for missing NumberFormat in scoreboards in bedrock edition 2024-01-22 17:43:45 +01:00
NEZNAMY 9412d9cc28 Add %bedrock% placeholder returning true for bedrock clients 2024-01-22 17:40:02 +01:00
NEZNAMY 75b2bfa14c Implement TabPlayer#isOnline on Fabric 2024-01-22 15:40:09 +01:00
NEZNAMY 7fcb5c8aa3 Create functions for different error types instead of printing them directly 2024-01-22 15:15:59 +01:00
NEZNAMY 9827711111 Simplify 2024-01-22 13:41:06 +01:00
NEZNAMY 16aecd65e6 Add error message when permission plugin returned null group 2024-01-22 13:32:29 +01:00
NEZNAMY c143ecc6ae Remove redundant Getter annotation 2024-01-20 17:56:29 +01:00
NEZNAMY 2935f9e205 Clean up 2024-01-20 17:54:51 +01:00
NEZNAMY a6a33a79d4 Attempt to fix error (#1218) 2024-01-20 17:22:23 +01:00
NEZNAMY 82f9d8ddee Attempt to fix error (#1218) 2024-01-20 11:08:59 +01:00
NEZNAMY daa6012b36 Simplify 2024-01-20 11:05:45 +01:00
NEZNAMY 5001c82f23 Update Fabric to 24w03b 2024-01-18 21:17:10 +01:00
NEZNAMY 59dbad9abf Fix snapshot name in fabric.mod.json 2024-01-18 12:12:13 +01:00
NEZNAMY 945c7cc7b8 Draft 1.20.5 support on Bukkit 2024-01-18 11:19:30 +01:00
NEZNAMY 3391f7646c Simplify 2024-01-17 23:27:27 +01:00
NEZNAMY 88070aa2b2 Use different constructor to simplify usage 2024-01-17 23:06:15 +01:00
NEZNAMY c792296746 Use reflection to get rid of 2 Fabric submodules 2024-01-17 23:06:06 +01:00
NEZNAMY 18a13f777d Allow empty output in sorting by pre-defined values 2024-01-17 22:27:34 +01:00
NEZNAMY 469529b324 Update Fabric to 24w03a 2024-01-17 21:15:26 +01:00
NEZNAMY 4715a5ab9d Use static number as a start of numbering if use-numbers is enabled 2024-01-17 19:08:46 +01:00
NEZNAMY be305162a6 Fix scoreboard use-numbers duplicating numbers 2024-01-17 19:08:17 +01:00
NEZNAMY 7a1bc16e9d Fix NPE 2024-01-17 11:27:36 +01:00
NEZNAMY eb2c80deb8 Add try/catch for ViaVersion hook (#1201) 2024-01-16 21:56:51 +01:00
NEZNAMY 0863b77bf0 Remove unused import 2024-01-16 20:51:16 +01:00
NEZNAMY 769a698509 Fix NPE and replace it with a warning (#1198) 2024-01-16 20:50:51 +01:00
NEZNAMY 53870b2c81 Fix anti-override on Velocity and Sponge no longer working after players are removed & added back to the tablist by another plugin 2024-01-16 12:54:52 +01:00
NEZNAMY e198a5df88 Another attempt to fix #1212 2024-01-16 11:17:28 +01:00
NEZNAMY ac947f698b Fix ClassCastException (#1213) 2024-01-16 11:05:06 +01:00
NEZNAMY ecabd62b6b Catch another exception (#1212) 2024-01-15 10:35:09 +01:00
NEZNAMY f350d200ac Fix memory leak 2024-01-14 20:28:35 +01:00
NEZNAMY 97d5a3f7ef Print all exceptions in a chain, not just the last one 2024-01-14 13:25:59 +01:00
NEZNAMY f15f3efc3f Add plugin's name prefix into console messages on Fabric 2024-01-14 11:48:24 +01:00
NEZNAMY 7563b6b946 Convert \n to newline if snakeyaml does not do it because text is in '' and not "" 2024-01-13 18:52:21 +01:00
NEZNAMY 421c53e6be Replace forEach with for 2024-01-13 18:06:16 +01:00
NEZNAMY 3db8666951 Hide spectator effect for players who unvanished (#1209) 2024-01-13 12:58:00 +01:00
NEZNAMY ecc6e01cf9 Add links to Tanguygab's new discord server where he will provide support to kids 2024-01-12 10:39:12 +01:00
NEZNAMY becf81db00 Merge 2 fields into 1 2024-01-10 15:04:50 +01:00
NEZNAMY 92f5b337c9 Use dummy implementation if original one is not available & remove redundant class 2024-01-10 14:26:56 +01:00
NEZNAMY 3ddf884c23 Get CraftBukkit classes using package name instead of version as recommended by Paper 2024-01-10 14:18:54 +01:00
NEZNAMY 116117af55 Create method to simplify usage 2024-01-09 18:20:51 +01:00
NEZNAMY 0980bcfca5 Merge pull request #1202 from Jake-Moore/master
Support to use Global Playerlist `server-groups` as Header/Footer `per-server` keys.
2024-01-09 18:12:05 +01:00
NEZNAMY 20e2e8ded6 Fix relational placeholders not refreshing properly & nested relational placeholders not being marked as used by the feature (and therefore not refreshing properly) (#1203) 2024-01-09 18:06:35 +01:00
Jake Moore 13af0b7b2b Improve handling of failed cases 2024-01-08 20:58:19 -08:00
Jake Moore cbcbfdcbf1 Adds support to use Global Playerlist server-groups as Header/Footer keys in per-server. Allowing for configuration of a server group with several velocity servers which can have a unique header-footer section. 2024-01-08 17:07:44 -08:00
NEZNAMY 38eb294667 Move version check 2024-01-08 12:24:50 +01:00
NEZNAMY 71d16ff906 Only send messages that actually affect the user 2024-01-08 12:22:19 +01:00
NEZNAMY 395058d771 Send console message saying how long NMS hook took to load 2024-01-08 12:13:58 +01:00
NEZNAMY d8af7c454c Simplify 2024-01-07 23:07:38 +01:00
NEZNAMY 2f10084e7a Replace more fields with functional interfaces 2024-01-07 22:56:08 +01:00
NEZNAMY 5af387b32d Move ScorePacketData to a separate class 2024-01-07 21:41:10 +01:00
NEZNAMY 4b7144f705 Replace fields with functional interfaces for NMS access 2024-01-07 21:20:24 +01:00
NEZNAMY 0fbbbb6485 Simplify 2024-01-07 20:23:09 +01:00
NEZNAMY f033d78785 Optionally print compatibility exception into console 2024-01-07 20:08:44 +01:00
NEZNAMY 2ea79eb57d Remove unused import 2024-01-07 14:44:49 +01:00
NEZNAMY 226ee0db9d Include supported version range in compatibility error message 2024-01-06 22:45:21 +01:00
NEZNAMY 8a62f21e84 Improve compatibility error messages 2024-01-06 22:25:33 +01:00
NEZNAMY c353c39086 Make entity view compatibility on Bukkit optional 2024-01-06 21:33:19 +01:00
NEZNAMY 2b2c877ab2 Add more Javadoc 2024-01-06 21:15:20 +01:00
NEZNAMY 016af4bfee Make DummyEntityView methods do nothing instead of throwing 2024-01-06 20:54:23 +01:00
NEZNAMY b3d26d91de Add console warn when waterfall option causing issues is enabled 2024-01-06 17:23:41 +01:00
NEZNAMY a7a7f409eb Fix sorting note getting overridden on refresh when set using API 2024-01-06 15:52:24 +01:00
NEZNAMY f44ccec712 Remove StaticLine class and use LongLine instead for the same effect 2024-01-06 14:29:08 +01:00
NEZNAMY 1c0887733c Initialize property names to random values instead of specific names 2024-01-06 14:14:33 +01:00
NEZNAMY 7ee3c35785 Rename some cpu usage categories to make them easier to understand 2024-01-06 14:01:13 +01:00
NEZNAMY 01b725cf38 Define team color immediately and forward it instead of figuring it out later 2024-01-05 21:30:43 +01:00
NEZNAMY 1ac6d90633 Add a runtime warn when sorting element is not in list (group, placeholder, or no permission) 2024-01-02 16:27:08 +01:00
NEZNAMY 9704193237 Add a runtime warn when numeric placeholder in condition did not return a number 2024-01-02 15:43:22 +01:00
NEZNAMY 69b150b391 Make team color always white for scoreboard lines (#1189) 2024-01-02 14:13:17 +01:00
NEZNAMY 7ffb3edda5 Spawn entity using bundle packet for 1.19.4+ to prevent frame being rendered between packets, causing visible armor stands for a single frame (#1168) 2024-01-02 13:10:20 +01:00
NEZNAMY 046eb68ce6 Split MisconfigurationHelper class into 3 for different message types 2024-01-01 22:03:08 +01:00
NEZNAMY 816d80a27c Construct ArmorStand with non-null level on Fabric (#1191) (one more) 2023-12-31 11:27:17 +01:00
NEZNAMY 957037e5f4 Construct ArmorStand with non-null level on Fabric (#1191) 2023-12-30 15:38:54 +01:00
NEZNAMY 9ca179a71f Make BungeeCord compatibility error message more clear about what exactly went wrong (#1190). 2023-12-30 15:34:12 +01:00
NEZNAMY cb4d96f1ba Remove unused imports 2023-12-29 22:15:05 +01:00
NEZNAMY 7d57522775 Add more Javadoc 2023-12-29 22:04:54 +01:00
NEZNAMY 902565f1b1 Remove TabList.Entry.Builder class and build entries directly 2023-12-29 19:07:13 +01:00
NEZNAMY 2f8de436af Improve Javadoc 2023-12-29 18:56:17 +01:00
NEZNAMY 38bd035d99 Create separate class for each incoming plugin message and move handling method to ProxyPlatform class 2023-12-29 18:29:51 +01:00
NEZNAMY 88b6d1a3ca Remove redundant SneakyThrows and replace some instances with throws 2023-12-29 14:40:17 +01:00
NEZNAMY a74878e7bb Make PacketSender an instance class 2023-12-29 14:17:29 +01:00
NEZNAMY 2b6eba7c4d Replace star import with direct imports 2023-12-26 12:21:06 +01:00
NEZNAMY 0de1b86ef5 Remove unnecessary static modifier 2023-12-26 12:19:46 +01:00
NEZNAMY ae0ab6471b Remove unused import 2023-12-26 12:17:58 +01:00
NEZNAMY c24cdaa8a4 Fix json syntax error when sorting character ends up being \ (#1184) 2023-12-24 23:17:07 +01:00
NEZNAMY df1442dd8a Fix YellowNumber & BelowName sending score update to disabled players 2023-12-24 15:30:57 +01:00
NEZNAMY b17d5306db Create and use TabFeature#config method for shorter code in all features 2023-12-24 15:08:34 +01:00
NEZNAMY 88d5294b8e Skip placeholder refreshing code if no placeholders are being refreshed 2023-12-24 14:25:25 +01:00
NEZNAMY 8c3405d01f Replace streams with direct code for better performance 2023-12-24 14:23:13 +01:00
NEZNAMY 9f83ba700c Fix bossbar & scoreboard writing into file on every join when remembering toggle choice is enabled even if value didn't change 2023-12-23 23:06:08 +01:00
NEZNAMY ae147211ca Code style 2023-12-23 22:58:01 +01:00
NEZNAMY 01d305eba0 Move scoreboard packet listening from pipeline class to scoreboard class 2023-12-23 21:54:26 +01:00
NEZNAMY 08c30574a9 Code style 2023-12-23 21:01:25 +01:00
NEZNAMY 39c4725a2f Change access from public to private 2023-12-23 19:45:32 +01:00
NEZNAMY 7002aeefaf Create constants for "magic numbers" 2023-12-23 17:28:19 +01:00
NEZNAMY f1201cc5bc Code style 2023-12-23 16:30:14 +01:00
NEZNAMY 0b99d4ac52 Extract duplicated code into a method 2023-12-23 16:29:11 +01:00
NEZNAMY 642cb701dd Remove unused import 2023-12-23 15:42:49 +01:00
NEZNAMY 66d6aee115 Move method to avoid duplication 2023-12-23 15:39:01 +01:00
NEZNAMY 38d8eb1108 Simplify 2023-12-23 15:20:09 +01:00
NEZNAMY f9941c5dff Add some more Javadocs 2023-12-23 14:48:26 +01:00
NEZNAMY 855a6f447a Use constants for bStats plugin ids 2023-12-23 13:59:02 +01:00
NEZNAMY fa0a1f8553 Create and use ProtocolVersion#supportsRGB instead of manually comparing number each time. 2023-12-23 13:55:52 +01:00
NEZNAMY 0d1fee1636 Add missing Javadoc to Fabric 2023-12-23 13:48:35 +01:00
NEZNAMY ec8269332f Add missing Javadoc to Bukkit 2023-12-23 13:31:16 +01:00
NEZNAMY 00fe9400e6 Add missing Javadoc to BungeeCord 2023-12-23 12:40:59 +01:00
NEZNAMY 8389b1e151 Add missing Javadoc to API 2023-12-23 12:31:09 +01:00
NEZNAMY 2289d10d6c Add missing Javadoc to Sponge and Velocity 2023-12-23 12:22:01 +01:00
NEZNAMY b30af4f144 Remove redundant "new" when creating arrays 2023-12-23 12:00:05 +01:00
NEZNAMY 041322c53f Update latest known version field 2023-12-23 11:21:03 +01:00
NEZNAMY 548968bcfc Print console warn on startup if error log has reaches its size limit 2023-12-22 20:37:31 +01:00
NEZNAMY 6d405b70e7 Properly apply replacements to server placeholders initially 2023-12-22 17:27:02 +01:00
NEZNAMY 1b4d4acc63 Fix map merging mistake 2023-12-22 17:26:24 +01:00
NEZNAMY 6f498089fb Add back option to disable tab expansion 2023-12-22 15:32:53 +01:00
NEZNAMY 8e27cba0f6 Fix compilation 2023-12-22 15:30:05 +01:00
NEZNAMY ae48041a96 Simplify 2023-12-21 23:55:57 +01:00
NEZNAMY bad16a4cdc Update Fabric to 23w51b 2023-12-21 20:50:16 +01:00
NEZNAMY 07baf769dd Suppress random irrelevant warnings 2023-12-21 20:21:23 +01:00
NEZNAMY ab409643f6 Convert class to record 2023-12-21 18:00:36 +01:00
NEZNAMY c745a61601 Recode placeholder refreshing to make refreshing thread not do anything more than placeholder requests & various minor performance improvements 2023-12-21 17:57:47 +01:00
NEZNAMY c4e8e83d1e Add check to improve method's performance 2023-12-21 13:03:49 +01:00
NEZNAMY 9133f3d792 Repackage classes and remove unused interface 2023-12-21 09:54:22 +01:00
NEZNAMY b779d584a5 Speed up placeholder replacement code if nothing is configured 2023-12-20 17:31:22 +01:00
NEZNAMY 05bb61b3ab Improve performance of a method 2023-12-20 15:50:25 +01:00
NEZNAMY 5d2885e662 Wrap maps into synchronizedMap to avoid deadlock on concurrent access 2023-12-20 15:48:26 +01:00
NEZNAMY ef645c1058 Fix random NPE 2023-12-17 20:16:09 +01:00
NEZNAMY 52a476fd0a Print cpu report into console if plugin is overloaded and debug is enabled 2023-12-17 11:19:08 +01:00
NEZNAMY 7386c45f06 Implement TabList class on Bukkit for 1.7, implement a backup solution using API and make the loading optional 2023-12-16 15:53:51 +01:00
NEZNAMY 1a508ddedf Fix error on old versions 2023-12-16 14:28:31 +01:00
NEZNAMY a8675f254a Move sendPacket call 2023-12-15 18:46:03 +01:00
NEZNAMY 0f70cd789e Move team anti-override to scoreboard class 2023-12-15 14:53:47 +01:00
NEZNAMY b8f594f42c Move tablist packet processing to tablist class 2023-12-15 12:20:17 +01:00
NEZNAMY 7857527ce1 Add console warn if floodgate is not configured correctly 2023-12-11 20:40:14 +01:00
NEZNAMY 05c41a5469 Create separate class for PremiumVanish hook & add check for outdated version to avoid an error (#1171) 2023-12-11 15:11:46 +01:00
NEZNAMY f11f8afdc4 Update README.md 2023-12-11 09:05:28 +01:00
NEZNAMY fa499aa46d Merge pull request #1170 from NicoNekoDev/master
Add NexEngine gradient
2023-12-10 13:25:05 +01:00
NicoNekoDev e8a7cc026f Documentation 2023-12-10 14:09:53 +02:00
NicoNekoDev 433ef6a8a7 Placeholders and legacy text 2023-12-10 14:08:09 +02:00
NicoNekoDev fc4b5a7bcd Fix codestyle 2023-12-10 13:40:44 +02:00
NicoNekoDev bd43134d58 Fix text 2023-12-10 13:26:34 +02:00
NicoNekoDev 557970ba4e Add NexEngine gradient
https://github.com/nulli0n/NexEngine-spigot/wiki/Configuration-Tips
2023-12-10 11:18:41 +02:00
NEZNAMY d3418e0067 Remove duplicate method 2023-12-09 20:52:44 +01:00
NEZNAMY b8d1845d4e Change way of detecting 1.19.4+ flag 2023-12-09 19:03:11 +01:00
NEZNAMY 32e360409d Create and use method for retrieving a CraftBukkit class instead of doing it manually each time (the method might also change in the future according to Paper devs, this will also make modifying and maintenance easier) 2023-12-09 19:00:46 +01:00
NEZNAMY c54a778af9 Fix RGB color conversion from adventure component to internal component 2023-12-09 15:26:55 +01:00
NEZNAMY ce25698f87 Do not cache nested components 2023-12-09 14:26:49 +01:00
NEZNAMY 4d95cc838a Change way of multi-version code handling on Fabric to significantly reduce complexity, module count and compilation time 2023-12-09 14:14:31 +01:00
NEZNAMY 67fa0a231e Add more misconfiguration checks for layout feature 2023-12-09 11:23:39 +01:00
NEZNAMY 3c2e8c81c4 4.1.3-SNAPSHOT 2023-12-08 09:50:11 +01:00
NEZNAMY 484d46252b 4.1.2 2023-12-08 09:47:09 +01:00
NEZNAMY a2e3c67298 Fix MiniMessage parsing causing issues to different syntax rules (#1166) 2023-12-08 09:46:36 +01:00
NEZNAMY b2ea05d8dd Merge 1.14.4 and 1.15.2 fabric modules for faster compilation 2023-12-07 20:26:13 +01:00
NEZNAMY 1477f47962 Merge 1.17 and 1.17.1 fabric modules for faster compilation 2023-12-07 19:18:25 +01:00
NEZNAMY 273ce49c14 4.1.2-SNAPSHOT 2023-12-07 19:16:56 +01:00
NEZNAMY 96b6790646 4.1.1 2023-12-07 18:50:30 +01:00
NEZNAMY 38227ec878 Update Fabric to 1.20.4 2023-12-07 18:48:08 +01:00
NEZNAMY 11da632972 Add 1.20.4 to version list 2023-12-07 17:46:51 +01:00
NEZNAMY 3721b9fe22 Fix MiniMessage not working in scoreboard 2023-12-07 14:12:13 +01:00
NEZNAMY ecb697e5fb Update default config to match recent scoreboard change (using ||) 2023-12-07 13:31:51 +01:00
NEZNAMY c754772280 Remove warn if ViaVersion returns -1 version (finally got to reproduce it & make tests) 2023-12-06 18:21:58 +01:00
NEZNAMY 5f2b4ba0b3 Fix disconnect on BungeeCord on 1.20.3 2023-12-06 13:46:21 +01:00
NEZNAMY 0ab60becfc Change scoreboard line number format split sequence to || instead of | to prevent false trigger when trying to actually display the character. 2023-12-05 23:01:54 +01:00
NEZNAMY 1dcfb7d6dc 4.1.1-SNAPSHOT 2023-12-05 23:01:11 +01:00
NEZNAMY 3c0b44b365 4.1.0 2023-12-05 20:45:58 +01:00
NEZNAMY ec4aec50ef Mark 1.20.3 as latest known version 2023-12-05 18:55:42 +01:00
NEZNAMY 9c86cfe056 Add Vault to softdepend to avoid console warn 2023-12-05 18:53:49 +01:00
NEZNAMY 6bb2337276 Print warn if RedisSupport received unknown message instead of throwing a NPE 2023-12-05 18:49:53 +01:00
NEZNAMY 4702ca255b Update Fabric to 1.20.3 2023-12-05 18:48:54 +01:00
NEZNAMY ab47aa36cc Confirm network id of 1.20.3 2023-12-05 18:40:55 +01:00
NEZNAMY 4bb14c4a4e Rename new config options 2023-12-05 17:27:24 +01:00
NEZNAMY 1dd75162f9 Add a version to list 2023-12-04 21:09:37 +01:00
NEZNAMY a2242e3c27 Move server version check 2023-12-04 20:52:29 +01:00
NEZNAMY 26ecf494a9 Little fix 2023-12-04 20:45:51 +01:00
NEZNAMY 659af5d0b3 Completely get rid of NMSStorage class 2023-12-04 20:33:23 +01:00
NEZNAMY 382a5ee7fc Move packet sending method to a separate class and make loading optional 2023-12-04 19:17:51 +01:00
NEZNAMY 7364496844 Move ping retrieving method to a separate class 2023-12-04 17:52:32 +01:00
NEZNAMY d342316b87 Reduce dependence on the main NMS class on Bukkit 2023-12-04 17:29:25 +01:00
NEZNAMY b38141da88 Add &f at the beginning of scoreboard lines for 1.13+ players to prevent color field from coloring text if no color is used 2023-12-04 16:40:06 +01:00
NEZNAMY 98f80f7dae Remove unused methods 2023-12-03 21:36:22 +01:00
NEZNAMY c134ec6bfe Simplify class loading 2023-12-03 21:32:00 +01:00
NEZNAMY 4e3f759243 Use NMS prefix only once instead of every usage 2023-12-03 21:03:08 +01:00
NEZNAMY a5f2e0157b Make player game profile getting optional instead of throwing a compatibility error 2023-12-03 20:52:29 +01:00
NEZNAMY fd21032e51 Make pipeline injection compatibility optional and print a warning if a compatibility error is found 2023-12-03 19:22:33 +01:00
NEZNAMY c4f5f9b7d3 Update comment 2023-12-03 18:11:40 +01:00
NEZNAMY 6baf5a7068 Fix NPE 2023-12-03 17:28:41 +01:00
NEZNAMY d3460174de Enable fallback solutions for Scoreboard implementation on Bukkit 2023-12-03 16:07:05 +01:00
NEZNAMY f9f19628ca Fix BukkitScoreboard class issues 2023-12-03 15:38:22 +01:00
NEZNAMY 04e48fea5d Clean up method 2023-12-01 20:22:41 +01:00
NEZNAMY 3e99e22fce Simplify 2023-12-01 19:18:58 +01:00
NEZNAMY 0b244d5ff7 Refactor PacketScoreboard class and draft 1.20.3 support 2023-12-01 19:15:56 +01:00
NEZNAMY 703f2ff316 Fix potentially wrong uuid write 2023-12-01 09:35:47 +01:00
NEZNAMY 34a2bbdbae Simplify 2023-12-01 09:34:14 +01:00
NEZNAMY cd8697c1a4 Add exclusions for some dependencies 2023-11-30 19:15:20 +01:00
NEZNAMY ed2dc2dd51 Avoid rare error on Velocity 2023-11-30 18:50:53 +01:00
NEZNAMY 2dfdc1b993 Update Fabric to 1.20.3-rc1 2023-11-30 18:27:49 +01:00
NEZNAMY 6f2d7f1194 Fix 1.20.3 client crash on BungeeCord 2023-11-30 17:52:01 +01:00
NEZNAMY 30009ef127 Attempt to fix error (#1146) 2023-11-29 17:19:11 +01:00
NEZNAMY 051a422331 Update Fabric to 1.20.3-pre4 2023-11-29 16:56:35 +01:00
NEZNAMY fab6489e9c Fix compilation 2023-11-28 16:12:27 +01:00
NEZNAMY a448614e4a Slightly better organize method 2023-11-27 18:54:28 +01:00
NEZNAMY 84ad874b2a Split complex method into 2 2023-11-27 18:43:43 +01:00
NEZNAMY 19ba978291 Simplify 2023-11-27 18:16:48 +01:00
NEZNAMY e304469e55 Remove star import 2023-11-27 18:10:28 +01:00
NEZNAMY 7ce9e2592a Update README.md 2023-11-27 18:07:35 +01:00
NEZNAMY 681b812c7b Rename feature in /tab cpu output 2023-11-26 13:59:29 +01:00
NEZNAMY ee1ab6785d Rename objective 2023-11-26 12:36:12 +01:00
NEZNAMY 57704b6622 Move a few classes around 2023-11-26 11:15:23 +01:00
NEZNAMY 7fe804e0fc Fix disconnect on bungeecord on 1.20.3 due to null text in component 2023-11-25 22:24:02 +01:00
NEZNAMY a71529b641 Simplify 2023-11-25 18:07:47 +01:00
NEZNAMY 4055173bdb Replace .length() == 0 with .isEmpty() 2023-11-25 17:09:20 +01:00
NEZNAMY 3f8841a276 Update to latest BungeeCord (for the 3rd time in a row) 2023-11-25 17:04:34 +01:00
NEZNAMY 15c38851bc Add misconfiguration check and prevent startup error 2023-11-25 16:56:11 +01:00
NEZNAMY 05030ae817 Update outdated message 2023-11-25 13:05:57 +01:00
NEZNAMY 255fa11371 Fix NPE 2023-11-24 18:41:08 +01:00
NEZNAMY a074c0bf74 Simplify 2023-11-24 10:49:12 +01:00
NEZNAMY 6ccd649fc4 Remove unused imports 2023-11-24 10:46:59 +01:00
NEZNAMY 21dbea07dd Add support for nested relational placeholders (#1153) 2023-11-23 20:28:21 +01:00
NEZNAMY 4c21d9d127 Remove unused import 2023-11-23 20:27:16 +01:00
NEZNAMY 766002b079 Delete aligned suffix feature, since 1.20.3 now has it naturally using playerlist objective (rip 1.20.2-) 2023-11-23 19:39:47 +01:00
NEZNAMY 2ce430ec41 Add support for all Fabric versions in a single jar 2023-11-23 19:18:31 +01:00
NEZNAMY cf2b151045 Bump Velocity version and use new method 2023-11-19 16:57:22 +01:00
NEZNAMY 92216179df Replace try/catch with SneakyThrows annotation 2023-11-19 16:27:06 +01:00
NEZNAMY a2a95b8591 Replace scoreboard enums with fields 2023-11-19 10:50:54 +01:00
NEZNAMY 4a22dc111a Fix attempt to send header/footer to 1.7 players on bungeecord, causing disconnect 2023-11-19 10:16:27 +01:00
NEZNAMY 5df7ecc245 Fix compatibility error message 2023-11-18 20:49:24 +01:00
NEZNAMY d4ef520f26 Print required bridge version in debug command if not connected 2023-11-18 20:39:09 +01:00
NEZNAMY e0f8737865 Use direct code for bungeecord instead of reflection and drop support for old versions 2023-11-18 20:37:37 +01:00
NEZNAMY 37b13a54a7 Create classes for plugin message types instead of writing all content on each usage + bump bridge version to 5 due to braking changes 2023-11-18 14:44:22 +01:00
NEZNAMY d9d28d31d0 Prepare for new 1.20.3 scoreboard features 2023-11-17 20:21:17 +01:00
NEZNAMY 66bf5da3f5 Delete CustomLine class due to not being viable 2023-11-17 11:41:11 +01:00
NEZNAMY 840c721afc Simplify, the performance gain was pretty much zero 2023-11-17 10:59:28 +01:00
NEZNAMY 859e705a48 Drop multi-version code for fabric (hard to maintain + a lot of it didn't actually work) 2023-11-16 22:10:45 +01:00
NEZNAMY 7375070b21 oops (#1144) 2023-11-15 16:53:42 +01:00
NEZNAMY 3e2eda6a71 Add ClickEvent to components 2023-11-11 20:17:22 +01:00
NEZNAMY 798d327dcb Fix plugin not removing redundant character width overrides from config 2023-11-11 18:11:37 +01:00
NEZNAMY f5ca9139e6 Fix double bossbar on Velocity on server switch (#1139) 2023-11-11 14:02:07 +01:00
NEZNAMY f35e8cfa51 Improve vanish tracking for nametag feature 2023-11-10 21:31:21 +01:00
NEZNAMY 0f6953711b Update build.yml 2023-11-10 15:57:34 +01:00
NEZNAMY e2c5e6f9ac Allow empty text for fixed slots in layout 2023-11-10 15:46:26 +01:00
NEZNAMY 5ef5d6ddba Update README.md 2023-11-10 12:51:41 +01:00
NEZNAMY 5b19a5d798 Only unregister team for other players when player is vanishing 2023-11-10 11:02:35 +01:00
NEZNAMY bf804e8923 Merge pull request #1137 from NEZNAMY/vanish-patch
Alternate implementation of #1136
2023-11-09 18:53:45 +01:00
NEZNAMY f47b8b5528 Alternate implementation of #1136 2023-11-09 18:30:56 +01:00
NEZNAMY c353f6787a Fix endsWith condition (#1134) 2023-11-07 17:37:52 +01:00
NEZNAMY 1b7d5c1a5c Add support for latest BungeeCord (yes, again), as well as some previous versions and remove compatibility check. 2023-11-06 17:12:26 +01:00
NEZNAMY 956456f157 4.1.0-SNAPSHOT 2023-11-06 13:41:51 +01:00
NEZNAMY 49077bd798 Create enum constants for packet actions for better readability and understanding of code 2023-11-05 21:16:07 +01:00
NEZNAMY 019a0287b2 4.0.9 2023-11-05 14:27:55 +01:00
NEZNAMY a71dac28c0 Hotfix after adding support for latest bungeecord (#1132) 2023-11-05 14:21:25 +01:00
NEZNAMY 155886e000 Fix typo 2023-11-04 15:39:45 +01:00
NEZNAMY ed4c303d2e 4.0.8 2023-11-04 14:55:32 +01:00
366 changed files with 15838 additions and 74781 deletions

No files matched your search

+8
View File
@@ -18,6 +18,14 @@ body:
validations:
required: true
- type: textarea
attributes:
label: Plugin list
description: Full list of plugins (output of /plugins)
placeholder: ...
validations:
required: true
- type: textarea
attributes:
label: Describe the bug
+5 -1
View File
@@ -1 +1,5 @@
blank_issues_enabled: false
blank_issues_enabled: false
contact_links:
- name: Support
url: https://discord.gg/YPqXt63YQj
about: Fresh new community discord providing support with all plugins, including TAB
+6 -9
View File
@@ -2,23 +2,20 @@ name: Build
on:
push:
branches:
- master
- v4
pull_request:
types: [opened, synchronize, reopened]
- v5
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3.3.0 #https://github.com/actions/checkout/releases
- uses: actions/checkout@v4 #https://github.com/actions/checkout/releases
with:
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
- name: Set up JDK 17
uses: actions/setup-java@v3.9.0 #https://github.com/actions/setup-java/releases
- name: Set up JDK 21
uses: actions/setup-java@v4 #https://github.com/actions/setup-java/releases
with:
distribution: 'temurin'
java-version: 17
java-version: 21
- name: Cache SonarCloud packages
uses: actions/cache@v3.2.3 #https://github.com/actions/cache/releases
with:
@@ -41,5 +38,5 @@ jobs:
- name: Upload a Build Artifact
uses: actions/upload-artifact@v3.1.2 #https://github.com/actions/upload-artifact/releases
with:
name: Jar
name: Click here to download
path: jar/build/libs/TAB-**.jar
+36 -1
View File
@@ -3,8 +3,19 @@ TAB aims to be a superior all-in-one minecraft plugin for displaying information
similar plugins in terms of features, performance and compatibility.
More information can be found at [Why TAB?](https://github.com/NEZNAMY/TAB/wiki/Why-TAB%3F) wiki page.
# Download
### Releases
You can download full releases from [GitHub releases](https://github.com/NEZNAMY/TAB/releases),
[SpigotMC](https://www.spigotmc.org/resources/57806/), [Modrinth](https://modrinth.com/plugin/tab-was-taken) or [BuiltByBit](https://builtbybit.com/resources/20631).
### Dev builds
Plugin's development is continuous and contains lots of small changes/fixes not worthy of being called a full
update, therefore development builds are being created and eventually a full update is released when enough changes are made.
If you want to benefit from a recent change in the code before official release, you can download development builds
from [GitHub actions](https://github.com/NEZNAMY/TAB/actions) (you must be logged into GitHub to be able to download it).
# Compiling
Compilation requires JDK 17 and up.
Compilation requires JDK 21 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.
@@ -21,3 +32,27 @@ is in quality of the report. Bug reports where steps to reproduce are "idk" have
# Contributing
See [CONTRIBUTING.md](https://github.com/NEZNAMY/TAB/blob/master/CONTRIBUTING.md)
# Provided services
The plugin is being used by a large amount of servers, which also means the demand for my attention is high. Unfortunately,
I am no longer able to keep up with the demand since I don't want to dedicate my whole existence to a free plugin.
In order to provide the best quality of all provided services, the amount of services provided may vary over time.
Here is the full list of potential services and their status:
| Service type | Availability |
|-------------------------------|--------------|
| Updates for new MC versions | ✔ |
| Bug fixes | ✔ |
| Wiki updates<sup>1</sup> | ✔ |
| Feature requests | ❌ |
| Customer support<sup>2</sup> | ✔ |
| Free user support<sup>3</sup> | ❌* |
| Optimization<sup>4</sup> | ✔ |
<sup>1</sup> - Adding new examples, guides and other kind of useful information people ask about.
<sup>2</sup> - Unrestricted assistance with the plugin for those, who bought a copy of the plugin when it was purchasable.
<sup>3</sup> - Unlimited access to my free time for everyone in the world.
<sup>4</sup> - Improving plugin's performance while not losing out on any features. This was done
proactively a lot in the past, so there isn't much to do anymore, only if someone actually finds and reports something.
*Instead, you can use [this fresh new community discord](https://discord.gg/YPqXt63YQj) made by a nice person who will provide this service.
+1 -1
View File
@@ -1,5 +1,5 @@
dependencies {
compileOnlyApi("org.jetbrains:annotations:24.0.1")
compileOnlyApi("org.jetbrains:annotations:24.1.0")
}
tasks.javadoc {
@@ -1,5 +1,6 @@
package me.neznamy.tab.api;
import java.util.Collection;
import java.util.UUID;
import lombok.NonNull;
@@ -67,8 +68,17 @@ public abstract class TabAPI {
*
* @return collection of online players
*/
@Deprecated
public abstract @NotNull TabPlayer[] getOnlinePlayers();
/**
* Returns a stream of all online players
*
* @return stream of online players
*/
public abstract @NotNull Collection<? extends TabPlayer> onlinePlayers();
/**
* Return BossBar manager instance if the feature is enabled. If not, returns {@code null}.
*
@@ -1,5 +1,6 @@
package me.neznamy.tab.api;
import lombok.NonNull;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -8,6 +9,7 @@ import java.util.UUID;
/**
* An interface representing a player
*/
@SuppressWarnings("unused") // API class
public interface TabPlayer {
/**
@@ -27,7 +29,7 @@ public interface TabPlayer {
/**
* Returns platform-specific entity
*
* @return an instance of bukkit/bungee player depending on platform
* @return platform's player object
*/
@NotNull Object getPlayer();
@@ -66,4 +68,27 @@ public interface TabPlayer {
* @see #setTemporaryGroup(String)
*/
boolean hasTemporaryGroup();
/**
* Changes expected profile name of the player. This adapts all name-bound features
* to use this new profile name if another plugin changed profile name of the player. <p>
* Automatic profile name change detection is available on Bukkit, BungeeCord and Fabric,
* therefore using this is redundant there. This function is only needed on Sponge and Velocity,
* where the detection is not available. <p>
* Warning: This function does NOT change player's profile name, it only updates the tracked
* name inside the plugin.
*
* @param profileName
* New expected profile name
*/
void setExpectedProfileName(@NonNull String profileName);
/**
* Returns player's expected profile name. This defaults to player's username, but may
* get changed by other plugins and then detected by TAB.
*
* @return Player's expected profile name
*/
@NotNull
String getExpectedProfileName();
}
@@ -4,13 +4,27 @@ package me.neznamy.tab.api.bossbar;
* An enum representing all available BossBar colors.
* Calling ordinal() will return color's network ID.
*/
@SuppressWarnings("unused") // API class
public enum BarColor {
/** Pink color */
PINK,
/** Blue color */
BLUE,
/** Red color */
RED,
/** Green color */
GREEN,
/** Yellow color */
YELLOW,
/** Purple color */
PURPLE,
/** White color */
WHITE
}
@@ -4,11 +4,21 @@ package me.neznamy.tab.api.bossbar;
* An enum representing all available BossBar styles.
* Calling ordinal() will return style's network ID.
*/
@SuppressWarnings("unused") // API class
public enum BarStyle {
/** Fully solid line */
PROGRESS,
/** Line segmented into 6 parts */
NOTCHED_6,
/** Line segmented into 10 parts */
NOTCHED_10,
/** Line segmented into 12 parts */
NOTCHED_12,
/** Line segmented into 20 parts */
NOTCHED_20
}
@@ -205,7 +205,7 @@ public interface BossBar {
* Player to check
* @return {@code true} if player is allowed to see this Boss Bar, {@code false} if not.
*/
boolean containsPlayer(TabPlayer player);
boolean containsPlayer(@NonNull TabPlayer player);
/**
* Returns {@code true} if this boss bar is configured as announcement-only bar,
@@ -17,6 +17,7 @@ import org.jetbrains.annotations.Nullable;
* This requires the BossBar feature to be enabled in config, otherwise the method will
* return {@code null}.
*/
@SuppressWarnings("unused") // API class
public interface BossBarManager {
/**
@@ -6,6 +6,7 @@ import lombok.NonNull;
* The event bus. This is used to register event listeners/handlers for
* handling events that may be fired by TAB.
*/
@SuppressWarnings("unused") // API class
public interface EventBus {
/**
@@ -12,6 +12,7 @@ import org.jetbrains.annotations.Nullable;
* This requires the Team feature to be enabled in config, otherwise the method will
* return {@code null}.
*/
@SuppressWarnings("unused") // API class
public interface NameTagManager {
/**
@@ -150,13 +151,51 @@ public interface NameTagManager {
*/
void setSuffix(@NonNull TabPlayer player, @Nullable String suffix);
@Nullable String getCustomPrefix(@NonNull TabPlayer player);
/**
* Returns custom prefix assigned using {@link #setPrefix(TabPlayer, String)}.
* If no custom prefix is set, returns {@code null}.
*
* @param player
* Player to get custom prefix of
* @return Custom prefix assigned using the API.
* @see #setPrefix(TabPlayer, String)
* @see #getOriginalPrefix(TabPlayer)
*/
@Nullable
String getCustomPrefix(@NonNull TabPlayer player);
@Nullable String getCustomSuffix(@NonNull TabPlayer player);
/**
* Returns custom suffix assigned using {@link #setSuffix(TabPlayer, String)}.
* If no custom suffix is set, returns {@code null}.
*
* @param player
* Player to get custom suffix of
* @return Custom suffix assigned using the API.
* @see #setSuffix(TabPlayer, String)
* @see #getOriginalSuffix(TabPlayer)
*/
@Nullable
String getCustomSuffix(@NonNull TabPlayer player);
@NonNull String getOriginalPrefix(@NonNull TabPlayer player);
/**
* Returns original prefix assigned by the plugin using internal logic.
* @param player
* Player to get original prefix of
* @return Original prefix assigned by the plugin's internal logic.
* @see #getCustomPrefix(TabPlayer)
*/
@NonNull
String getOriginalPrefix(@NonNull TabPlayer player);
@NonNull String getOriginalSuffix(@NonNull TabPlayer player);
/**
* Returns original suffix assigned by the plugin using internal logic.
* @param player
* Player to get original suffix of
* @return Original suffix assigned by the plugin's internal logic.
* @see #getCustomSuffix(TabPlayer)
*/
@NonNull
String getOriginalSuffix(@NonNull TabPlayer player);
/**
* Toggles name tag visibility view on all players for specified player.
@@ -15,6 +15,7 @@ import org.jetbrains.annotations.Nullable;
* This requires both Team and Unlimited name tag features to be enabled in config, otherwise the method will
* return {@code null} or not be an instance of this class.
*/
@SuppressWarnings("unused") // API class
public interface UnlimitedNameTagManager extends NameTagManager {
/**
@@ -13,6 +13,7 @@ import java.util.function.Supplier;
* An interface allowing placeholder registration. Instance can be
* obtained using {@link TabAPI#getPlaceholderManager()}
*/
@SuppressWarnings("unused") // API class
public interface PlaceholderManager {
/**
@@ -2,6 +2,7 @@ package me.neznamy.tab.api.placeholder;
import lombok.NonNull;
import me.neznamy.tab.api.TabPlayer;
import org.jetbrains.annotations.Nullable;
/**
* Interface for player placeholders (placeholders returning different value for each player)
@@ -18,7 +19,7 @@ public interface PlayerPlaceholder extends Placeholder {
* @param value
* New value
*/
void updateValue(@NonNull TabPlayer player, @NonNull Object value);
void updateValue(@NonNull TabPlayer player, @Nullable Object value);
/**
* Force-updates placeholder value using previously provided update function.
@@ -2,10 +2,12 @@ package me.neznamy.tab.api.placeholder;
import lombok.NonNull;
import me.neznamy.tab.api.TabPlayer;
import org.jetbrains.annotations.Nullable;
/**
* An interface for relational placeholders (returning different value for each player duo)
*/
@SuppressWarnings("unused") // API class
public interface RelationalPlaceholder extends Placeholder {
/**
@@ -20,7 +22,7 @@ public interface RelationalPlaceholder extends Placeholder {
* @param value
* New value
*/
void updateValue(@NonNull TabPlayer viewer, @NonNull TabPlayer target, @NonNull Object value);
void updateValue(@NonNull TabPlayer viewer, @NonNull TabPlayer target, @Nullable Object value);
/**
* Force-updates placeholder value using previously provided update function.
@@ -1,6 +1,6 @@
package me.neznamy.tab.api.placeholder;
import lombok.NonNull;
import org.jetbrains.annotations.Nullable;
/**
* An interface for server placeholders (returning same value for all players)
@@ -15,7 +15,7 @@ public interface ServerPlaceholder extends Placeholder {
* @param value
* New value
*/
void updateValue(@NonNull Object value);
void updateValue(@Nullable Object value);
/**
* Force-updates placeholder value using previously provided update function.
@@ -11,6 +11,7 @@ import java.util.List;
* {@link ScoreboardManager#createScoreboard(String, String, List)},
* for scoreboards from config {@link ScoreboardManager#getRegisteredScoreboards()}.
*/
@SuppressWarnings("unused") // API class
public interface Scoreboard {
/**
@@ -16,6 +16,7 @@ import org.jetbrains.annotations.Nullable;
* This requires the scoreboard feature to be enabled in config,
* the method will return {@code null} otherwise.
*/
@SuppressWarnings("unused") // API class
public interface ScoreboardManager {
/**
@@ -12,6 +12,7 @@ import org.jetbrains.annotations.Nullable;
* This requires the Header/footer feature to be enabled in config, otherwise
* the method will return {@code null}.
*/
@SuppressWarnings("unused") // API class
public interface HeaderFooterManager {
/**
@@ -1,10 +1,18 @@
package me.neznamy.tab.api.tablist;
import lombok.NonNull;
import me.neznamy.tab.api.TabAPI;
import me.neznamy.tab.api.TabPlayer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Interface for managing player sorting by directly configuring
* team names of players. Instance can be obtained using
* {@link TabAPI#getSortingManager()}. If the feature is disabled,
* it will return {@code null}.
*/
@SuppressWarnings("unused") // API class
public interface SortingManager {
/**
@@ -14,6 +14,7 @@ import org.jetbrains.annotations.Nullable;
* This requires the feature to be enabled in config, otherwise the method will
* return {@code null}.
*/
@SuppressWarnings("unused") // API class
public interface TabListFormatManager {
/**
@@ -7,6 +7,7 @@ import org.jetbrains.annotations.Nullable;
/**
* Interface representing a layout.
*/
@SuppressWarnings("unused") // API class
public interface Layout {
/**
@@ -10,6 +10,7 @@ import org.jetbrains.annotations.Nullable;
* {@link TabAPI#getLayoutManager()}. If the feature is disabled in
* config, it will return {@code null}.
*/
@SuppressWarnings("unused") // API class
public interface LayoutManager {
/**
+2 -2
View File
@@ -4,6 +4,6 @@ plugins {
dependencies {
implementation("com.github.johnrengelman:shadow:8.1.1")
implementation("io.freefair.gradle:lombok-plugin:8.0.1")
implementation("fabric-loom:fabric-loom.gradle.plugin:1.1-SNAPSHOT")
implementation("io.freefair.gradle:lombok-plugin:8.6")
implementation("fabric-loom:fabric-loom.gradle.plugin:1.6-SNAPSHOT")
}
@@ -31,6 +31,6 @@ tasks {
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(17))
languageVersion.set(JavaLanguageVersion.of(21))
}
}
+4 -2
View File
@@ -4,7 +4,7 @@ plugins {
allprojects {
group = "me.neznamy"
version = "4.0.8-SNAPSHOT"
version = "4.1.7"
description = "An all-in-one solution that works"
ext.set("id", "tab")
@@ -18,7 +18,9 @@ val platforms = setOf(
projects.velocity,
projects.sponge7,
projects.sponge8,
projects.fabric
projects.fabric,
projects.fabric.v1144,
projects.fabric.v1182
).map { it.dependencyProject }
val special = setOf(
+5
View File
@@ -9,5 +9,10 @@ dependencies {
}
compileOnly("LibsDisguises:LibsDisguises:10.0.21") {
exclude("org.spigotmc", "spigot")
exclude("org.spigotmc", "spigot-api")
exclude("com.github.dmulloy2", "ProtocolLib")
exclude("org.ow2.asm", "asm")
exclude("net.md-5", "bungeecord-chat")
}
compileOnly("com.github.LeonMangler:PremiumVanishAPI:2.8.8")
}
@@ -17,23 +17,47 @@ import org.jetbrains.annotations.NotNull;
/**
* The core for bukkit forwarding events into all enabled features
*/
public class BukkitEventListener extends EventListener<Player> implements Listener {
public class BukkitEventListener implements EventListener<Player>, Listener {
/**
* Listens to player quit event.
*
* @param e
* Disconnect event
*/
@EventHandler(priority = EventPriority.HIGHEST)
public void onQuit(PlayerQuitEvent e) {
quit(e.getPlayer().getUniqueId());
}
/**
* Listens to player join event.
*
* @param e
* Join event
*/
@EventHandler(priority = EventPriority.LOW)
public void onJoin(PlayerJoinEvent e) {
join(e.getPlayer());
}
/**
* Listens to world change event.
*
* @param e
* World change event
*/
@EventHandler(priority = EventPriority.LOWEST)
public void onWorldChange(PlayerChangedWorldEvent e) {
worldChange(e.getPlayer().getUniqueId(), e.getPlayer().getWorld().getName());
}
/**
* Listens to command event to potentially cancel it.
*
* @param e
* Command event
*/
@EventHandler
public void onCommand(PlayerCommandPreprocessEvent e) {
if (command(e.getPlayer().getUniqueId(), e.getMessage())) e.setCancelled(true);
@@ -1,30 +1,26 @@
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.NMSStorage;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.features.injection.NettyPipelineInjector;
import me.neznamy.tab.shared.features.sorting.Sorting;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.util.FunctionWithException;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.stream.Collectors;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* Pipeline injection for bukkit
* Pipeline injection for Bukkit 1.8+.
*/
public class BukkitPipelineInjector extends NettyPipelineInjector {
/** Function for getting player's channel */
private static FunctionWithException<TabPlayer, Channel> getChannel;
/**
* Constructs new instance
*/
@@ -32,149 +28,48 @@ public class BukkitPipelineInjector extends NettyPipelineInjector {
super("packet_handler");
}
/**
* Attempts to load required classes, fields and methods and marks class as available.
* If something fails, error message is printed and class is not marked as available.
*/
public static void tryLoad() {
try {
Class<?> NetworkManager = BukkitReflection.getClass("network.Connection", "network.NetworkManager", "NetworkManager");
Class<?> PlayerConnection = BukkitReflection.getClass("server.network.ServerGamePacketListenerImpl",
"server.network.PlayerConnection", "PlayerConnection");
Class<?> EntityPlayer = BukkitReflection.getClass("server.level.ServerPlayer", "server.level.EntityPlayer", "EntityPlayer");
Method getHandle = BukkitReflection.getBukkitClass("entity.CraftPlayer").getMethod("getHandle");
Field PLAYER_CONNECTION = ReflectionUtils.getOnlyField(EntityPlayer, PlayerConnection);
Field NETWORK_MANAGER;
if (BukkitReflection.is1_20_2Plus()) {
NETWORK_MANAGER = ReflectionUtils.getOnlyField(PlayerConnection.getSuperclass(), NetworkManager);
} else {
NETWORK_MANAGER = ReflectionUtils.getOnlyField(PlayerConnection, NetworkManager);
}
Field CHANNEL = ReflectionUtils.getOnlyField(NetworkManager, Channel.class);
getChannel = player -> (Channel) CHANNEL.get(NETWORK_MANAGER.get(PLAYER_CONNECTION.get(getHandle.invoke(player.getPlayer()))));
} catch (Exception e) {
BukkitUtils.compatibilityError(e, "network channel injection", null,
"Unlimited nametag mode not working and being replaced with regular nametags",
"Anti-override for tablist & nametags not working",
"Compatibility with nickname plugins changing player names will not work",
"Scoreboard will not be checking for other plugins");
}
}
/**
* Returns {@code true} if pipeline injection is available, {@code false} if not.
*
* @return {@code true} if pipeline injection is available, {@code false} if not
*/
public static boolean isAvailable() {
return getChannel != null;
}
@Override
@Nullable
@SneakyThrows
protected Channel getChannel(@NotNull TabPlayer player) {
BukkitTabPlayer bukkit = (BukkitTabPlayer) player;
NMSStorage nms = NMSStorage.getInstance();
try {
if (nms.CHANNEL != null) return (Channel) nms.CHANNEL.get(nms.NETWORK_MANAGER.get(bukkit.getPlayerConnection()));
} catch (IllegalAccessException exception) {
TAB.getInstance().getErrorManager().printError("Failed to get channel of " + bukkit.getName(), exception);
}
return null;
}
@Override
@SneakyThrows
public void onDisplayObjective(@NotNull TabPlayer player, @NotNull Object 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));
}
@Override
@SneakyThrows
public void onObjective(@NotNull TabPlayer player, @NotNull Object packet) {
TAB.getInstance().getFeatureManager().onObjective(player,
PacketScoreboard.Objective_METHOD.getInt(packet),
(String) PacketScoreboard.Objective_OBJECTIVE_NAME.get(packet));
}
@Override
public boolean isDisplayObjective(@NotNull Object packet) {
return PacketScoreboard.DisplayObjectiveClass.isInstance(packet);
}
@Override
public boolean isObjective(@NotNull Object packet) {
return PacketScoreboard.ObjectivePacketClass.isInstance(packet);
}
@Override
public boolean isTeam(@NotNull Object packet) {
return PacketScoreboard.TeamPacketClass.isInstance(packet);
}
@Override
public boolean isPlayerInfo(@NotNull Object packet) {
return BukkitTabList.PlayerInfoClass.isInstance(packet);
}
@Override
@SneakyThrows
public void onPlayerInfo(@NotNull TabPlayer receiver, @NotNull Object packet) {
List<String> actions;
if (BukkitTabList.ClientboundPlayerInfoRemovePacket != null) {
//1.19.3+
actions = ((EnumSet<?>)BukkitTabList.ACTION.get(packet)).stream().map(Enum::name).collect(Collectors.toList());
} else {
//1.19.2-
actions = Collections.singletonList(BukkitTabList.ACTION.get(packet).toString());
}
List<Object> updatedList = new ArrayList<>();
for (Object nmsData : (List<?>) BukkitTabList.PLAYERS.get(packet)) {
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, id);
if (newDisplayName != null) displayName = ((BukkitTabPlayer)receiver).getPlatform().toComponent(newDisplayName, receiver.getVersion());
if (!BukkitReflection.is1_19_3Plus()) BukkitTabList.PlayerInfoData_DisplayName.set(nmsData, displayName);
}
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(
id,
profile,
BukkitTabList.PlayerInfoData_Listed.getBoolean(nmsData),
latency,
BukkitTabList.PlayerInfoData_GameMode.get(nmsData),
displayName,
BukkitTabList.PlayerInfoData_RemoteChatSession.get(nmsData)));
}
}
if (BukkitReflection.is1_19_3Plus()) {
BukkitTabList.PLAYERS.set(packet, updatedList);
}
}
@Override
public boolean isLogin(@NotNull Object packet) {
return false;
}
@SuppressWarnings("unchecked")
@Override
@SneakyThrows
public void modifyPlayers(@NotNull Object packetPlayOutScoreboardTeam) {
if (TAB.getInstance().getNameTagManager() == null) return;
int action = PacketScoreboard.TeamPacket_ACTION.getInt(packetPlayOutScoreboardTeam);
if (action == 1 || action == 2 || action == 4) return;
Collection<String> players = (Collection<String>) PacketScoreboard.TeamPacket_PLAYERS.get(packetPlayOutScoreboardTeam);
String teamName = (String) PacketScoreboard.TeamPacket_NAME.get(packetPlayOutScoreboardTeam);
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);
}
}
PacketScoreboard.TeamPacket_PLAYERS.set(packetPlayOutScoreboardTeam, newList);
return getChannel.apply(player);
}
}
@@ -1,13 +1,9 @@
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.chat.EnumChatFormat;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.bukkit.Bukkit;
import org.bukkit.plugin.java.JavaPlugin;
/**
@@ -17,42 +13,12 @@ public class BukkitTAB extends JavaPlugin {
@Override
public void onEnable() {
if (!isVersionSupported()) {
Bukkit.getPluginManager().disablePlugin(this);
return;
}
TAB.create(ReflectionUtils.classExists("io.papermc.paper.threadedregions.RegionizedServer") ?
new FoliaPlatform(this) : new BukkitPlatform(this));
boolean folia = ReflectionUtils.classExists("io.papermc.paper.threadedregions.RegionizedServer");
TAB.create(folia ? new FoliaPlatform(this) : new BukkitPlatform(this));
}
@Override
public void onDisable() {
//null check due to compatibility check making instance not get set on unsupported versions
if (TAB.getInstance() != null) TAB.getInstance().unload();
}
/**
* Initializes all used NMS classes, constructors, fields and methods.
* Returns {@code true} if everything went successfully,
* {@code false} if anything went wrong.
*
* @return {@code true} if server version is compatible, {@code false} if not
*/
private boolean isVersionSupported() {
try {
NMSStorage.setInstance(new NMSStorage());
return true;
} catch (Exception ex) {
if (ProtocolVersion.fromFriendlyName(Bukkit.getBukkitVersion().split("-")[0]) == ProtocolVersion.UNKNOWN_SERVER_VERSION) {
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.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;
}
TAB.getInstance().unload();
}
}
@@ -1,232 +0,0 @@
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.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;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.*;
/**
* TabList which support modifying many entries at once
* for significantly better performance. For 1.7 players,
* ViaVersion properly splits the packet into multiple, so
* we don't need to worry about that here.
* <p>
* This class does not support server versions of 1.7 and
* below, because of the massive differences in tab list
* and packet fields.
*/
@RequiredArgsConstructor
@SuppressWarnings({"unchecked", "rawtypes"})
public class BukkitTabList implements TabList {
// NMS Fields
public static Class<?> PlayerInfoClass;
private static Constructor<?> newPlayerInfo;
public static Field ACTION;
public static Field PLAYERS;
private static Class<Enum> ActionClass;
public static Class<?> ClientboundPlayerInfoRemovePacket;
private static Class<?> RemoteChatSession$Data;
private static Constructor<?> newClientboundPlayerInfoRemovePacket;
private static Class<?> EntityPlayer;
public static Constructor<?> newPlayerInfoData;
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() 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 {
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");
}
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);
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 {
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 Builder(entry).build()));
}
}
@Override
public void updateDisplayName(@NotNull UUID entry, @Nullable IChatBaseComponent displayName) {
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) {
if (!available) return;
player.sendPacket(createPacket(Action.UPDATE_LATENCY, new Builder(entry).latency(latency).build()));
}
@Override
public void updateGameMode(@NotNull UUID entry, int gameMode) {
if (!available) return;
player.sendPacket(createPacket(Action.UPDATE_GAME_MODE, new Builder(entry).gameMode(gameMode).build()));
}
@Override
public void addEntry(@NotNull Entry entry) {
if (!available) return;
player.sendPacket(createPacket(Action.ADD_PLAYER, entry));
}
@Override
@SneakyThrows
public void setPlayerListHeaderFooter(@NotNull IChatBaseComponent header, @NotNull IChatBaseComponent footer) {
if (HeaderFooter.getInstance() != null) HeaderFooter.getInstance().set(player, header, footer);
}
@SneakyThrows
@NotNull
private Object createPacket(@NotNull Action action, @NotNull Entry entry) {
Object packet;
List<Object> players = new ArrayList<>();
if (BukkitReflection.is1_19_3Plus()) {
EnumSet<?> actions;
if (action == Action.ADD_PLAYER) {
actions = EnumSet.allOf(ActionClass);
} else {
actions = EnumSet.of(Enum.valueOf(ActionClass, action.name()));
}
packet = newPlayerInfo.newInstance(actions, Collections.emptyList());
players.add(newPlayerInfoData.newInstance(
entry.getUniqueId(),
createProfile(entry.getUniqueId(), entry.getName(), entry.getSkin()),
true,
entry.getLatency(),
gameModes[entry.getGameMode()],
entry.getDisplayName() == null ? null : toComponent(entry.getDisplayName()),
null
));
} else {
packet = newPlayerInfo.newInstance(Enum.valueOf(ActionClass, action.name()), Array.newInstance(EntityPlayer, 0));
List<Object> parameters = new ArrayList<>();
if (newPlayerInfoData.getParameterTypes()[0] == PlayerInfoClass) {
parameters.add(packet);
}
parameters.add(createProfile(entry.getUniqueId(), entry.getName(), entry.getSkin()));
parameters.add(entry.getLatency());
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 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;
}
}
@@ -1,24 +1,19 @@
package me.neznamy.tab.platforms.bukkit;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
import lombok.Getter;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.bossbar.BossBarLoader;
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.entity.PacketEntityView;
import me.neznamy.tab.platforms.bukkit.nms.PingRetriever;
import me.neznamy.tab.platforms.bukkit.platform.BukkitPlatform;
import me.neznamy.tab.platforms.bukkit.scoreboard.PacketScoreboard;
import me.neznamy.tab.platforms.bukkit.scoreboard.ScoreboardLoader;
import me.neznamy.tab.platforms.bukkit.tablist.TabListBase;
import me.neznamy.tab.shared.backend.entityview.DummyEntityView;
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.BossBar;
import me.neznamy.tab.shared.chat.TabComponent;
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.NMSStorage;
import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.backend.BackendTabPlayer;
import org.bukkit.entity.Player;
import org.bukkit.metadata.MetadataValue;
@@ -26,8 +21,6 @@ import org.bukkit.potion.PotionEffectType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
/**
* TabPlayer implementation for Bukkit platform
*/
@@ -35,26 +28,17 @@ import java.util.Collection;
@Getter
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;
private final Scoreboard<BukkitTabPlayer, ?> scoreboard = ScoreboardLoader.getInstance().apply(this);
@NotNull
private final Scoreboard<BukkitTabPlayer> scoreboard = new PacketScoreboard(this);
private final TabListBase<?> tabList = TabListBase.getInstance().apply(this);
@NotNull
private final TabList tabList = new BukkitTabList(this);
private final BossBar bossBar = BossBarLoader.findInstance(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);
private final EntityView entityView = PacketEntityView.isAvailable() ? new PacketEntityView(this) : new DummyEntityView();
/**
* Constructs new instance with given bukkit player
@@ -64,11 +48,8 @@ public class BukkitTabPlayer extends BackendTabPlayer {
* @param p
* bukkit player
*/
@SneakyThrows
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);
super(platform, p, p.getUniqueId(), p.getName(), p.getWorld().getName(), platform.getServerVersion().getNetworkId());
}
@Override
@@ -77,23 +58,13 @@ public class BukkitTabPlayer extends BackendTabPlayer {
}
@Override
@SneakyThrows
public int getPing() {
if (BukkitReflection.getMinorVersion() >= 17) {
return getPlayer().getPing();
}
return NMSStorage.getInstance().PING.getInt(handle);
}
@SneakyThrows
public void sendPacket(@NotNull Object nmsPacket) {
if (!getPlayer().isOnline()) return;
NMSStorage.getInstance().sendPacket.invoke(playerConnection, nmsPacket);
return PingRetriever.getPing(getPlayer());
}
@Override
public void sendMessage(@NotNull IChatBaseComponent message) {
getPlayer().sendMessage(BukkitUtils.toBukkitFormat(message, getVersion().getMinorVersion() >= 16));
public void sendMessage(@NotNull TabComponent message) {
getPlayer().sendMessage(getPlatform().toBukkitFormat(message, getVersion().supportsRGB()));
}
@Override
@@ -107,20 +78,9 @@ public class BukkitTabPlayer extends BackendTabPlayer {
}
@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();
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());
}
return tabList.getSkin();
}
@Override
@@ -129,11 +89,6 @@ public class BukkitTabPlayer extends BackendTabPlayer {
return (Player) player;
}
@Override
public boolean isOnline() {
return getPlayer().isOnline();
}
@Override
public BukkitPlatform getPlatform() {
return (BukkitPlatform) platform;
@@ -141,7 +96,10 @@ public class BukkitTabPlayer extends BackendTabPlayer {
@Override
public boolean isVanished() {
return getPlayer().getMetadata("vanished").stream().anyMatch(MetadataValue::asBoolean);
for (MetadataValue v : getPlayer().getMetadata("vanished")) {
if (v.asBoolean()) return true;
}
return false;
}
@Override
@@ -3,12 +3,12 @@ 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.ProtocolVersion;
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 org.jetbrains.annotations.Nullable;
import java.util.Collection;
@@ -18,6 +18,11 @@ import java.util.Collection;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class BukkitUtils {
/** Whether compatibility exceptions should be printed or not, enabling when adding support for new versions */
public static final boolean PRINT_EXCEPTIONS = false;
private static boolean compatibilityIssue;
/**
* Returns online players from Bukkit API. This requires reflection, as return type changed in 1.8,
* and we want to avoid errors.
@@ -39,34 +44,45 @@ public class BukkitUtils {
}
/**
* Converts component to legacy string using bukkit RGB format if supported by both server and client.
* If not, closest legacy color is used instead.
* Prints a console warn that some compatibility issue was found.
*
* @param component
* Component to convert
* @param rgbClient
* Whether client accepts RGB colors or not.
* @return Converted string using bukkit color format
* @param exception
* Exception thrown when initializing fields, if applicable
* @param failedCheck
* Check that failed
* @param fallback
* Fallback implementation, if available
* @param missingFeatures
* Features that will be broken because of the incompatibility
*/
@NotNull
public static String toBukkitFormat(@NotNull IChatBaseComponent component, boolean rgbClient) {
public static void compatibilityError(@NotNull Exception exception, @NotNull String failedCheck,
@Nullable String fallback, @NotNull String... missingFeatures) {
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(EnumChatFormat.RED);
sb.append("[TAB] Failed to initialize minecraft fields for ");
sb.append(failedCheck);
sb.append(" due to a compatibility error. ");
if (fallback != null) {
sb.append("Using fallback solution using ").append(fallback).append(". ");
} else {
sb.append("No fallback solution was found. ");
}
sb.append(component.getModifier().getMagicCodes());
if (component.getText() != null) sb.append(component.getText());
for (IChatBaseComponent extra : component.getExtra()) {
sb.append(toBukkitFormat(extra, rgbClient));
if (missingFeatures.length > 0) sb.append("This will result in: ");
for (int i=0; i<missingFeatures.length; i++) {
sb.append("\n").append("#").append(i + 1).append(": ").append(missingFeatures[i]);
}
return sb.toString();
Bukkit.getConsoleSender().sendMessage(sb.toString());
if (PRINT_EXCEPTIONS) exception.printStackTrace();
compatibilityIssue = true;
}
/**
* Sends a message asking user to update the plugin if some compatibility issue was found.
*/
public static void sendCompatibilityMessage() {
if (!compatibilityIssue) return;
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.RED + "[TAB] Please update the plugin to " +
"a version with native support for your server version for optimal experience. This plugin version " +
"was made for " + ProtocolVersion.V1_5.getFriendlyName() + " - " + ProtocolVersion.LATEST_KNOWN_VERSION.getFriendlyName() + ".");
}
}
@@ -0,0 +1,31 @@
package me.neznamy.tab.platforms.bukkit.bossbar;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
import me.neznamy.tab.shared.platform.BossBar;
import org.jetbrains.annotations.NotNull;
/**
* Class for finding BossBar implementation based on server and client version.
*/
public class BossBarLoader {
/**
* Finds best available instance for given player.
*
* @param player
* Player to find instance for
* @return BossBar instance for player
*/
@NotNull
public static BossBar findInstance(@NotNull BukkitTabPlayer player) {
// 1.9+ server, handle using API, potential 1.8 players are handled by ViaVersion
if (BukkitReflection.getMinorVersion() >= 9) return new BukkitBossBar(player);
// 1.9+ player on 1.8 server, handle using ViaVersion API
if (player.getVersion().getMinorVersion() >= 9) return new ViaBossBar(player);
// 1.8- server and player, use wither
return new EntityBossBar(player);
}
}
@@ -3,9 +3,8 @@ 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.TabComponent;
import me.neznamy.tab.shared.platform.BossBar;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import org.bukkit.Bukkit;
import org.jetbrains.annotations.NotNull;
@@ -21,25 +20,8 @@ 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
};
private static final org.bukkit.boss.BarStyle[] styles = org.bukkit.boss.BarStyle.values();
/** Player this handler belongs to */
@NotNull
@@ -53,8 +35,8 @@ public class BukkitBossBar implements BossBar {
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(
BukkitUtils.toBukkitFormat(IChatBaseComponent.optimizedComponent(title), player.getVersion().getMinorVersion() >= 16),
colors[color.ordinal()],
player.getPlatform().toBukkitFormat(TabComponent.optimized(title), player.getVersion().supportsRGB()),
org.bukkit.boss.BarColor.valueOf(color.name()),
styles[style.ordinal()]
);
bar.setProgress(progress);
@@ -64,7 +46,7 @@ public class BukkitBossBar implements BossBar {
@Override
public void update(@NotNull UUID id, @NotNull String title) {
bossBars.get(id).setTitle(BukkitUtils.toBukkitFormat(IChatBaseComponent.optimizedComponent(title), player.getVersion().getMinorVersion() >= 16));
bossBars.get(id).setTitle(player.getPlatform().toBukkitFormat(TabComponent.optimized(title), player.getVersion().supportsRGB()));
}
@Override
@@ -79,7 +61,7 @@ public class BukkitBossBar implements BossBar {
@Override
public void update(@NotNull UUID id, @NotNull BarColor color) {
bossBars.get(id).setColor(colors[color.ordinal()]);
bossBars.get(id).setColor(org.bukkit.boss.BarColor.valueOf(color.name()));
}
@Override
@@ -1,11 +1,11 @@
package me.neznamy.tab.platforms.bukkit.bossbar;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.shared.platform.bossbar.BossBar;
import me.neznamy.tab.shared.platform.BossBar;
import me.neznamy.tab.api.bossbar.BarColor;
import me.neznamy.tab.api.bossbar.BarStyle;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.platforms.bukkit.nms.DataWatcher;
import me.neznamy.tab.platforms.bukkit.entity.DataWatcher;
import me.neznamy.tab.shared.backend.Location;
import org.bukkit.entity.EntityType;
import org.jetbrains.annotations.NotNull;
@@ -20,6 +20,15 @@ import java.util.UUID;
@RequiredArgsConstructor
public class EntityBossBar implements BossBar {
/** Max health of wither */
private static final int WITHER_MAX_HEALTH = 300;
/** Ideal value for invulnerable time for minimal wither size */
private static final int IDEAL_INVULNERABLE_TIME = 880;
/** Flag making entity invisible */
private static final byte INVISIBLE_FLAG = 1 << 5;
/** Player this handler belongs to */
@NotNull
private final BukkitTabPlayer player;
@@ -27,12 +36,12 @@ public class EntityBossBar implements BossBar {
@Override
public void create(@NotNull UUID id, @NotNull String title, float progress, @NotNull BarColor color, @NotNull BarStyle style) {
DataWatcher w = new DataWatcher();
float health = 300*progress;
float health = WITHER_MAX_HEALTH*progress;
if (health == 0) health = 1;
w.setHealth(health);
w.setCustomName(title, player.getVersion());
w.setEntityFlags((byte) 32);
w.setWitherInvulnerableTime(880); // Magic number
w.setEntityFlags(INVISIBLE_FLAG);
w.setWitherInvulnerableTime(IDEAL_INVULNERABLE_TIME);
player.getEntityView().spawnEntity(id.hashCode(), new UUID(0, 0), EntityType.WITHER, new Location(0, 0, 0), w);
}
@@ -46,7 +55,7 @@ public class EntityBossBar implements BossBar {
@Override
public void update(@NotNull UUID id, float progress) {
DataWatcher w = new DataWatcher();
float health = 300*progress;
float health = WITHER_MAX_HEALTH*progress;
if (health == 0) health = 1;
w.setHealth(health);
player.getEntityView().updateEntityMetadata(id.hashCode(), w);
@@ -4,8 +4,8 @@ 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.shared.chat.TabComponent;
import me.neznamy.tab.shared.platform.BossBar;
import me.neznamy.tab.api.bossbar.BarColor;
import me.neznamy.tab.api.bossbar.BarStyle;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
@@ -21,25 +21,8 @@ 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
};
private static final BossStyle[] styles = BossStyle.values();
/** Player this handler belongs to */
@NotNull
@@ -53,9 +36,9 @@ public class ViaBossBar implements BossBar {
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(
IChatBaseComponent.optimizedComponent(title).toString(player.getVersion()),
TabComponent.optimized(title).toLegacyText(),
progress,
colors[color.ordinal()],
BossColor.valueOf(color.name()),
styles[style.ordinal()]
);
viaBossBars.put(id, bar);
@@ -64,7 +47,7 @@ public class ViaBossBar implements BossBar {
@Override
public void update(@NotNull UUID id, @NotNull String title) {
viaBossBars.get(id).setTitle(IChatBaseComponent.optimizedComponent(title).toString(player.getVersion()));
viaBossBars.get(id).setTitle(TabComponent.optimized(title).toLegacyText());
}
@Override
@@ -79,7 +62,7 @@ public class ViaBossBar implements BossBar {
@Override
public void update(@NotNull UUID id, @NotNull BarColor color) {
viaBossBars.get(id).setColor(colors[color.ordinal()]);
viaBossBars.get(id).setColor(BossColor.valueOf(color.name()));
}
@Override
@@ -0,0 +1,273 @@
package me.neznamy.tab.platforms.bukkit.entity;
import lombok.*;
import me.neznamy.tab.platforms.bukkit.nms.ComponentConverter;
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.backend.EntityData;
import me.neznamy.tab.shared.chat.TabComponent;
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.*;
/**
* A class representing the n.m.s.DataWatcher class to make work with it much easier
*/
@ToString
public class DataWatcher implements EntityData {
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 = EntityData.getArmorStandFlagsPosition(BukkitReflection.getMinorVersion());
/** 1.19.3+ */
private static Constructor<?> newDataWatcher$Item;
/** 1.19.2- */
public static Class<?> DataWatcher;
private static Constructor<?> newDataWatcher;
private static Method DataWatcher_register;
private static Constructor<?> newDataWatcherObject;
/** Watched data */
private final Map<Integer, Item> dataValues = new HashMap<>();
/**
* Loads all required Fields and throws Exception if something went wrong.
*
* @throws ReflectiveOperationException
* If something goes wrong
*/
public static void load() throws ReflectiveOperationException {
int minorVersion = BukkitReflection.getMinorVersion();
if (minorVersion >= 9) {
loadSerializers();
}
if (BukkitReflection.is1_19_3Plus()) {
Class<?> dataWatcher$Item = BukkitReflection.getClass("network.syncher.SynchedEntityData$DataValue", "network.syncher.DataWatcher$c", "network.syncher.DataWatcher$b");
Class<?> dataWatcherSerializer = BukkitReflection.getClass("network.syncher.EntityDataSerializer",
"network.syncher.DataWatcherSerializer", "DataWatcherSerializer");
newDataWatcher$Item = dataWatcher$Item.getConstructor(int.class, dataWatcherSerializer, Object.class);
} else {
DataWatcher = BukkitReflection.getClass("network.syncher.SynchedEntityData", "network.syncher.DataWatcher", "DataWatcher");
if (minorVersion >= 7) {
ComponentConverter.ensureAvailable();
newDataWatcher = DataWatcher.getConstructor(BukkitReflection.getClass("world.entity.Entity", "Entity"));
} else {
newDataWatcher = DataWatcher.getConstructor();
}
if (minorVersion >= 9) {
Class<?> dataWatcherObject = BukkitReflection.getClass("network.syncher.EntityDataAccessor",
"network.syncher.DataWatcherObject", "DataWatcherObject");
Class<?> dataWatcherSerializer = BukkitReflection.getClass("network.syncher.EntityDataSerializer",
"network.syncher.DataWatcherSerializer", "DataWatcherSerializer");
DataWatcher_register = ReflectionUtils.getMethod(
DataWatcher,
new String[]{"define", "register", "a", "m_135372_"}, // {Mojang, Bukkit, Bukkit 1.18+, Mohist 1.18.2}
dataWatcherObject, Object.class
);
newDataWatcherObject = dataWatcherObject.getConstructor(int.class, dataWatcherSerializer);
} else {
DataWatcher_register = ReflectionUtils.getMethod(
DataWatcher,
new String[]{"func_75682_a", "a"}, int.class, // {Thermos 1.7.10, Bukkit}
Object.class
);
}
}
}
private static void loadSerializers() throws ReflectiveOperationException {
Class<?> dataWatcherRegistry = BukkitReflection.getClass("network.syncher.EntityDataSerializers",
"network.syncher.DataWatcherRegistry", "DataWatcherRegistry");
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 (BukkitReflection.getMinorVersion() >= 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(TabComponent.optimized(customName).convert(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() > Limitations.BOSSBAR_NAME_LENGTH_1_7 ?
customName.substring(0, Limitations.BOSSBAR_NAME_LENGTH_1_7) : 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
@NotNull
public Object build() {
if (BukkitReflection.is1_19_3Plus()) {
List<Object> items = new ArrayList<>();
for (Item item : dataValues.values()) {
items.add(newDataWatcher$Item.newInstance(item.position, item.serializer, item.value));
}
return items;
} else {
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());
}
return nmsWatcher;
}
}
/**
* Class for specific data watcher item.
*/
@RequiredArgsConstructor
@Getter
private static class Item {
/** Item position */
private final int position;
/** Item serializer (1.9+) */
@Nullable
private final Object serializer;
/** Item value */
@NonNull
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,432 @@
package me.neznamy.tab.platforms.bukkit.entity;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.platforms.bukkit.BukkitUtils;
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
import me.neznamy.tab.platforms.bukkit.nms.PacketSender;
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.*;
import org.bukkit.Bukkit;
import org.bukkit.entity.EntityType;
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;
import java.util.*;
import java.util.concurrent.Callable;
import java.util.function.Function;
/**
* EntityView implementation for Bukkit using packets.
*/
@RequiredArgsConstructor
@SuppressWarnings("unchecked")
public class PacketEntityView implements EntityView {
/** Version with entity metadata being split from spawn packet */
private static final int SPLIT_METADATA_VERSION = 15;
private static PacketSender packetSender;
/** PacketPlayOutEntityDestroy */
private static Class<?> EntityDestroyClass;
private static BiConsumerWithException<BukkitTabPlayer, int[]> destroyEntities;
private static FunctionWithException<Object, int[]> getDestroyedEntities;
/** PacketPlayOutEntityMetadata */
private static BiFunctionWithException<Integer, EntityData, Object> newEntityMetadata;
/** PacketPlayOutEntityTeleport */
private static BiFunctionWithException<Integer, Location, Object> newEntityTeleport;
private static Class<?> EntityTeleportClass;
private static Field EntityTeleport_EntityId;
/** PacketPlayOutSpawnEntityLiving */
private static QuintFunction<Integer, UUID, Object, Location, EntityData, Object> newSpawnEntity;
/** Other entity packets */
private static Class<?> PacketPlayOutEntity;
private static Field PacketPlayOutEntity_ENTITYID;
private static Field PacketPlayOutEntity_X;
private static Field PacketPlayOutEntity_Y;
private static Field PacketPlayOutEntity_Z;
private static Class<?> PacketPlayOutEntityLook;
private static QuadFunction<Integer, Long, Long, Long, Object> newMovePacket;
private static Class<?> PacketPlayOutNamedEntitySpawn;
private static Field PacketPlayOutNamedEntitySpawn_ENTITYID;
private static Constructor<?> newClientboundBundlePacket;
private static Field ClientboundBundlePacket_packets;
private static Function<Object, Boolean> isBundlePacket = packet -> false;
private static BiConsumerWithException<BukkitTabPlayer, Iterable<?>> sendAsBundle = (player, packets) -> {
for (Object packet : packets) {
packetSender.sendPacket(player.getPlayer(), packet);
}
};
@Getter
private static boolean available;
/** Player this view belongs to */
private final BukkitTabPlayer player;
/**
* Loads all required classes and fields and marks class as available.
* If something goes wrong, a warning is printed and class is not marked as available.
*/
public static void tryLoad() {
try {
DataWatcher.load();
loadEntityMetadata();
loadEntityDestroy();
loadEntityTeleport();
loadEntityMove();
loadEntitySpawn();
if (BukkitReflection.is1_19_4Plus()) {
Class<?> ClientboundBundlePacket = Class.forName("net.minecraft.network.protocol.game.ClientboundBundlePacket");
newClientboundBundlePacket = ClientboundBundlePacket.getConstructor(Iterable.class);
ClientboundBundlePacket_packets = ReflectionUtils.getOnlyField(ClientboundBundlePacket.getSuperclass(), Iterable.class);
isBundlePacket = ClientboundBundlePacket::isInstance;
sendAsBundle = (player, packets) -> packetSender.sendPacket(player.getPlayer(), newClientboundBundlePacket.newInstance(packets));
}
packetSender = new PacketSender();
available = true;
} catch (Exception e) {
List<String> missingFeatures = new ArrayList<>();
if (BukkitReflection.getMinorVersion() >= 8) {
missingFeatures.add("Unlimited nametag mode not working and being replaced with regular nametags");
}
if (BukkitReflection.getMinorVersion() <= 8) {
missingFeatures.add("BossBar feature not working");
}
BukkitUtils.compatibilityError(e, "sending entity packets", null, missingFeatures.toArray(new String[0]));
}
}
@SuppressWarnings("JavaReflectionInvocation")
private static void loadEntityMetadata() throws ReflectiveOperationException {
Class<?> entityMetadataClass = BukkitReflection.getClass("network.protocol.game.ClientboundSetEntityDataPacket",
"network.protocol.game.PacketPlayOutEntityMetadata", "PacketPlayOutEntityMetadata", "Packet40EntityMetadata");
if (BukkitReflection.is1_19_3Plus()) {
Constructor<?> constructor = entityMetadataClass.getConstructor(int.class, List.class);
newEntityMetadata = (entityId, data) -> constructor.newInstance(entityId, data.build());
} else {
Constructor<?> constructor = entityMetadataClass.getConstructor(int.class, DataWatcher.DataWatcher, boolean.class);
newEntityMetadata = (entityId, data) -> constructor.newInstance(entityId, data.build(), true);
}
}
private static void loadEntityDestroy() throws ReflectiveOperationException {
EntityDestroyClass = BukkitReflection.getClass("network.protocol.game.ClientboundRemoveEntitiesPacket",
"network.protocol.game.PacketPlayOutEntityDestroy", "PacketPlayOutEntityDestroy", "Packet29DestroyEntity");
Field entities = ReflectionUtils.getOnlyField(EntityDestroyClass);
try {
Constructor<?> constructor = EntityDestroyClass.getConstructor(int[].class);
destroyEntities = (player, ids) -> packetSender.sendPacket(player.getPlayer(), constructor.newInstance(new Object[]{ids}));
if (BukkitReflection.getMinorVersion() >= 17) {
getDestroyedEntities = packet -> ((List<Integer>) entities.get(packet)).stream().mapToInt(i -> i).toArray();
} else {
getDestroyedEntities = packet -> (int[]) entities.get(packet);
}
} catch (NoSuchMethodException e) {
//1.17.0 Mojank
Constructor<?> constructor = EntityDestroyClass.getConstructor(int.class);
destroyEntities = (player, ids) -> { for (int entity : ids) packetSender.sendPacket(player.getPlayer(), constructor.newInstance(entity));};
getDestroyedEntities = packet -> new int[]{entities.getInt(packet)};
}
}
private static void loadEntityTeleport() throws ReflectiveOperationException {
EntityTeleportClass = BukkitReflection.getClass("network.protocol.game.ClientboundTeleportEntityPacket",
"network.protocol.game.PacketPlayOutEntityTeleport", "PacketPlayOutEntityTeleport", "Packet34EntityTeleport");
Callable<Object> newPacket;
if (BukkitReflection.getMinorVersion() >= 17) {
// Dummy armor stand for constructor
Class<?> world = BukkitReflection.getClass("world.level.Level", "world.level.World", "World");
Class<?> entityArmorStand = BukkitReflection.getClass("world.entity.decoration.ArmorStand",
"world.entity.decoration.EntityArmorStand", "EntityArmorStand");
Constructor<?> newEntityArmorStand = entityArmorStand.getConstructor(world, double.class, double.class, double.class);
Method World_getHandle = BukkitReflection.getBukkitClass("CraftWorld").getMethod("getHandle");
Object dummyEntity = newEntityArmorStand.newInstance(World_getHandle.invoke(Bukkit.getWorlds().get(0)), 0, 0, 0);
Constructor<?> constructor = EntityTeleportClass.getConstructor(BukkitReflection.getClass("world.entity.Entity"));
newPacket = () -> constructor.newInstance(dummyEntity);
} else {
Constructor<?> constructor = EntityTeleportClass.getConstructor();
newPacket = constructor::newInstance;
}
EntityTeleport_EntityId = ReflectionUtils.getFields(EntityTeleportClass, int.class).get(0);
Field EntityTeleport_X;
Field EntityTeleport_Y;
Field EntityTeleport_Z;
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);
}
newEntityTeleport = (entityId, location) -> {
Object packet = newPacket.call();
EntityTeleport_EntityId.set(packet, entityId);
EntityTeleport_X.set(packet, toPosition(location.getX()));
EntityTeleport_Y.set(packet, toPosition(location.getY()));
EntityTeleport_Z.set(packet, toPosition(location.getZ()));
return packet;
};
}
private static void loadEntityMove() throws ReflectiveOperationException {
// Classes
PacketPlayOutEntity = BukkitReflection.getClass("network.protocol.game.ClientboundMoveEntityPacket",
"network.protocol.game.PacketPlayOutEntity", "PacketPlayOutEntity", "Packet30Entity");
PacketPlayOutEntityLook = BukkitReflection.getClass("network.protocol.game.ClientboundMoveEntityPacket$Rot",
"network.protocol.game.PacketPlayOutEntity$PacketPlayOutEntityLook", "PacketPlayOutEntity$PacketPlayOutEntityLook",
"PacketPlayOutEntityLook", "Packet32EntityLook");
Class<?> packetPlayOutRelEntityMove = BukkitReflection.getClass("network.protocol.game.ClientboundMoveEntityPacket$Pos",
"network.protocol.game.PacketPlayOutEntity$PacketPlayOutRelEntityMove", "PacketPlayOutEntity$PacketPlayOutRelEntityMove",
"PacketPlayOutRelEntityMove", "Packet31RelEntityMove");
// Fields
PacketPlayOutEntity_ENTITYID = ReflectionUtils.getFields(PacketPlayOutEntity, int.class).get(0);
if (BukkitReflection.getMinorVersion() >= 14) {
List<Field> fields = ReflectionUtils.getFields(PacketPlayOutEntity, short.class);
PacketPlayOutEntity_X = fields.get(0);
PacketPlayOutEntity_Y = fields.get(1);
PacketPlayOutEntity_Z = fields.get(2);
Constructor<?> constructor = packetPlayOutRelEntityMove.getConstructor(int.class, short.class, short.class, short.class, boolean.class);
newMovePacket = (entityId, x, y, z) -> constructor.newInstance(entityId, x.shortValue(), y.shortValue(), z.shortValue(), false);
} else if (BukkitReflection.getMinorVersion() >= 9) {
List<Field> fields = ReflectionUtils.getFields(PacketPlayOutEntity, int.class);
PacketPlayOutEntity_X = fields.get(1);
PacketPlayOutEntity_Y = fields.get(2);
PacketPlayOutEntity_Z = fields.get(3);
Constructor<?> constructor = packetPlayOutRelEntityMove.getConstructor(int.class, long.class, long.class, long.class, boolean.class);
newMovePacket = (entityId, x, y, z) -> constructor.newInstance(entityId, x, y, z, false);
} else if (BukkitReflection.getMinorVersion() == 8) {
List<Field> fields = ReflectionUtils.getFields(PacketPlayOutEntity, byte.class);
PacketPlayOutEntity_X = fields.get(0);
PacketPlayOutEntity_Y = fields.get(1);
PacketPlayOutEntity_Z = fields.get(2);
Constructor<?> constructor = packetPlayOutRelEntityMove.getConstructor(int.class, byte.class, byte.class, byte.class, boolean.class);
newMovePacket = (entityId, x, y, z) -> constructor.newInstance(entityId, x.byteValue(), y.byteValue(), z.byteValue(), false);
}
}
/**
* Loads required NMS classes, fields and methods. If it fails, exception is thrown.
*
* @throws ReflectiveOperationException
* If anything fails
*/
private static void loadEntitySpawn() throws ReflectiveOperationException {
int minorVersion = BukkitReflection.getMinorVersion();
// Entity ids
EnumMap<EntityType, Integer> entityIds = new EnumMap<>(EntityType.class);
if (minorVersion >= 13) {
entityIds.put(EntityType.ARMOR_STAND, 1);
} else {
entityIds.put(EntityType.WITHER, 64);
if (minorVersion >= 8) {
entityIds.put(EntityType.ARMOR_STAND, 30);
}
}
Class<?> SpawnEntityClass = BukkitReflection.getClass("network.protocol.game.ClientboundAddEntityPacket",
"network.protocol.game.PacketPlayOutSpawnEntity", "PacketPlayOutSpawnEntityLiving", "Packet24MobSpawn");
Field SpawnEntity_EntityId = ReflectionUtils.getFields(SpawnEntityClass, int.class).get(0);
if (BukkitReflection.is1_20_2Plus()) {
PacketPlayOutNamedEntitySpawn = SpawnEntityClass;
PacketPlayOutNamedEntitySpawn_ENTITYID = SpawnEntity_EntityId;
} else {
PacketPlayOutNamedEntitySpawn = BukkitReflection.getClass("network.protocol.game.ClientboundAddPlayerPacket",
"network.protocol.game.PacketPlayOutNamedEntitySpawn", "PacketPlayOutNamedEntitySpawn", "Packet20NamedEntitySpawn");
PacketPlayOutNamedEntitySpawn_ENTITYID = ReflectionUtils.getFields(PacketPlayOutNamedEntitySpawn, int.class).get(0);
}
Constructor<?> constructor;
// 1.17+ constructor using Spawn entity packet
if (minorVersion >= 17) {
Class<?> Vec3D = BukkitReflection.getClass("world.phys.Vec3", "world.phys.Vec3D");
Object Vec3D_Empty = ReflectionUtils.getOnlyField(Vec3D, Vec3D).get(null);
Class<?> EntityTypes = BukkitReflection.getClass("world.entity.EntityType", "world.entity.EntityTypes");
if (minorVersion >= 19) {
Object EntityTypes_ARMOR_STAND = ReflectionUtils.getField(EntityTypes, "ARMOR_STAND", "d").get(null);
constructor = SpawnEntityClass.getConstructor(int.class, UUID.class, double.class, double.class, double.class, float.class, float.class, EntityTypes, int.class, Vec3D, double.class);
newSpawnEntity = (id, uuid, type, l, data) -> constructor.newInstance(id, uuid, l.getX(), l.getY(), l.getZ(), 0, 0, EntityTypes_ARMOR_STAND, 0, Vec3D_Empty, 0.0d);
} else {
Object EntityTypes_ARMOR_STAND = ReflectionUtils.getField(EntityTypes, "ARMOR_STAND", "c", "f_20529_").get(null); // Mohist 1.18.2
constructor = SpawnEntityClass.getConstructor(int.class, UUID.class, double.class, double.class, double.class, float.class, float.class, EntityTypes, int.class, Vec3D);
newSpawnEntity = (id, uuid, type, l, data) -> constructor.newInstance(id, uuid, l.getX(), l.getY(), l.getZ(), 0, 0, EntityTypes_ARMOR_STAND, 0, Vec3D_Empty);
}
return;
}
// 1.16- constructor using spawn entity living packet
constructor = SpawnEntityClass.getConstructor();
@Nullable Field SpawnEntity_UUID;
Field SpawnEntity_X;
Field SpawnEntity_Y;
Field SpawnEntity_Z;
@Nullable Field SpawnEntity_DataWatcher;
Field SpawnEntity_EntityType = ReflectionUtils.getFields(SpawnEntityClass, int.class).get(1);
if (minorVersion >= 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_UUID = null;
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);
}
if (minorVersion < SPLIT_METADATA_VERSION) {
SpawnEntity_DataWatcher = ReflectionUtils.getOnlyField(SpawnEntityClass, DataWatcher.DataWatcher);
} else {
SpawnEntity_DataWatcher = null;
}
newSpawnEntity = (id, uuid, type, l, data) -> {
Object nmsPacket = constructor.newInstance();
SpawnEntity_EntityId.set(nmsPacket, id);
if (minorVersion < SPLIT_METADATA_VERSION) SpawnEntity_DataWatcher.set(nmsPacket, data.build());
if (minorVersion >= 9) SpawnEntity_UUID.set(nmsPacket, uuid);
SpawnEntity_X.set(nmsPacket, toPosition(l.getX()));
SpawnEntity_Y.set(nmsPacket, toPosition(l.getY()));
SpawnEntity_Z.set(nmsPacket, toPosition(l.getZ()));
SpawnEntity_EntityType.set(nmsPacket, entityIds.get((EntityType) type));
return nmsPacket;
};
}
private static Object toPosition(double paramDouble) {
if (BukkitReflection.getMinorVersion() >= 9) {
return paramDouble;
} else {
int i = (int) (paramDouble*32);
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) {
if (BukkitReflection.getMinorVersion() >= SPLIT_METADATA_VERSION) {
sendAsBundle.accept(player, Arrays.asList(
newSpawnEntity.apply(entityId, id, entityType, l, null),
newEntityMetadata.apply(entityId, data)
));
} else {
packetSender.sendPacket(player.getPlayer(), newSpawnEntity.apply(entityId, id, entityType, l, data));
}
}
@Override
@SneakyThrows
public void updateEntityMetadata(int entityId, @NotNull EntityData data) {
packetSender.sendPacket(player.getPlayer(), newEntityMetadata.apply(entityId, data));
}
@SneakyThrows
@Override
public void teleportEntity(int entityId, @NotNull Location location) {
packetSender.sendPacket(player.getPlayer(), newEntityTeleport.apply(entityId, location));
}
@SneakyThrows
@Override
public void destroyEntities(int... entities) {
destroyEntities.accept(player, entities);
}
@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) {
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) {
return PacketPlayOutNamedEntitySpawn_ENTITYID.getInt(playerSpawnPacket);
}
@Override
@SneakyThrows
public int[] getDestroyedEntities(@NotNull Object destroyPacket) {
return getDestroyedEntities.apply(destroyPacket);
}
@Override
public boolean isBundlePacket(@NotNull Object packet) {
return isBundlePacket.apply(packet);
}
@Override
@SneakyThrows
public Iterable<Object> getPackets(@NotNull Object bundlePacket) {
return (Iterable<Object>) ClientboundBundlePacket_packets.get(bundlePacket);
}
@Override
@SneakyThrows
@NotNull
public Location getMoveDiff(@NotNull Object movePacket) {
return new Location(
((Number)PacketPlayOutEntity_X.get(movePacket)).intValue(),
((Number)PacketPlayOutEntity_Y.get(movePacket)).intValue(),
((Number)PacketPlayOutEntity_Z.get(movePacket)).intValue()
);
}
@Override
@SneakyThrows
public void moveEntity(int entityId, @NotNull Location moveDiff) {
packetSender.sendPacket(player.getPlayer(), newMovePacket.apply(
entityId, (long) moveDiff.getX(), (long) moveDiff.getY(), (long) moveDiff.getZ()));
}
}
@@ -1,7 +1,8 @@
package me.neznamy.tab.platforms.bukkit.features;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.platforms.bukkit.nms.DataWatcher;
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
import me.neznamy.tab.platforms.bukkit.entity.DataWatcher;
import me.neznamy.tab.platforms.bukkit.platform.BukkitPlatform;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.backend.EntityData;
@@ -25,6 +26,7 @@ import org.jetbrains.annotations.Nullable;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.stream.Collectors;
/**
@@ -32,15 +34,36 @@ import java.util.stream.Collectors;
*/
public class BukkitNameTagX extends BackendNameTagX implements Listener {
/** Version in which multiple passenger option was added */
private static final int MULTI_PASSENGER_VERSION = 11;
/**
* Constructs new instance and registers the class listener.
*
* @param plugin
* Plugin instance to use for registering listener
*/
public BukkitNameTagX(@NotNull JavaPlugin plugin) {
Bukkit.getPluginManager().registerEvents(this, plugin);
}
/**
* Listener to player sneak event to sneak armor stands.
*
* @param e
* Sneak event
*/
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onSneak(PlayerToggleSneakEvent e) {
sneak(e.getPlayer().getUniqueId(), e.isSneaking());
}
/**
* Listener to respawn event to resend armor stands.
*
* @param e
* Respawn event
*/
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onRespawn(PlayerRespawnEvent e) {
respawn(e.getPlayer().getUniqueId());
@@ -73,7 +96,7 @@ public class BukkitNameTagX extends BackendNameTagX implements Listener {
@NotNull
public List<Integer> getPassengers(@NotNull Object entity) {
Entity vehicle = (Entity) entity;
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 11) {
if (BukkitReflection.getMinorVersion() >= MULTI_PASSENGER_VERSION) {
return vehicle.getPassengers().stream().map(Entity::getEntityId).collect(Collectors.toList());
} else {
if (vehicle.getPassenger() != null) {
@@ -98,7 +121,7 @@ public class BukkitNameTagX extends BackendNameTagX implements Listener {
@Override
@NotNull
public String getEntityType(@NotNull Object entity) {
return ((Entity) entity).getType().toString().toLowerCase();
return ((Entity) entity).getType().toString().toLowerCase(Locale.US);
}
@Override
@@ -109,13 +132,13 @@ public class BukkitNameTagX extends BackendNameTagX implements Listener {
@Override
public boolean isSwimming(@NotNull TabPlayer player) {
Player p = (Player) player.getPlayer();
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 14 && p.getPose() == Pose.SWIMMING) return true;
return TAB.getInstance().getServerVersion().getMinorVersion() == 13 && p.isSwimming();
if (BukkitReflection.getMinorVersion() >= 14 && p.getPose() == Pose.SWIMMING) return true;
return BukkitReflection.getMinorVersion() == 13 && p.isSwimming();
}
@Override
public boolean isGliding(@NotNull TabPlayer player) {
return TAB.getInstance().getServerVersion().getMinorVersion() >= 9 && ((Player)player.getPlayer()).isGliding();
return BukkitReflection.getMinorVersion() >= 9 && ((Player)player.getPlayer()).isGliding();
}
@Override
@@ -151,7 +174,7 @@ public class BukkitNameTagX extends BackendNameTagX implements Listener {
datawatcher.setEntityFlags(flags);
datawatcher.setCustomName(displayName, viewer.getVersion());
datawatcher.setCustomNameVisible(nameVisible);
datawatcher.setArmorStandFlags((byte)16);
datawatcher.setArmorStandFlags(datawatcher.MARKER_FLAG);
return datawatcher;
}
@@ -164,4 +187,9 @@ public class BukkitNameTagX extends BackendNameTagX implements Listener {
public boolean isDead(@NotNull TabPlayer player) {
return ((BukkitTabPlayer)player).getPlayer().isDead();
}
@Override
public boolean isFlying(@NotNull TabPlayer player) {
return ((BukkitTabPlayer)player).getPlayer().isFlying();
}
}
@@ -12,11 +12,7 @@ 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;
import java.util.*;
/**
* TAB's expansion for PlaceholderAPI
@@ -24,13 +20,9 @@ import java.util.WeakHashMap;
@Getter
public class BukkitTabExpansion extends PlaceholderExpansion implements TabExpansion {
/** Map holding all values for all players for easy and high-performance access */
@NotNull
private final WeakHashMap<TabPlayer, Map<String, String>> values = new WeakHashMap<>();
/** List of all placeholders offered by the plugin for command suggestions */
@NotNull
private final List<String> placeholders = Arrays.asList(
private final List<String> placeholders = Collections.unmodifiableList(Arrays.asList(
"%tab_tabprefix%",
"%tab_tabsuffix%",
"%tab_tagprefix%",
@@ -54,7 +46,7 @@ public class BukkitTabExpansion extends PlaceholderExpansion implements TabExpan
"%tab_nametag_visibility%",
"%tab_replace_<placeholder>%",
"%tab_placeholder_<placeholder>%"
);
));
@Override
public boolean persist() {
@@ -101,17 +93,17 @@ public class BukkitTabExpansion extends PlaceholderExpansion implements TabExpan
return text;
}
if (identifier.startsWith("placeholder_")) {
TAB.getInstance().getPlaceholderManager().addUsedPlaceholder("%" + identifier.substring(12) + "%", TAB.getInstance().getPlaceholderManager());
TAB.getInstance().getPlaceholderManager().addUsedPlaceholder("%" + identifier.substring("placeholder_".length()) + "%", TAB.getInstance().getPlaceholderManager());
}
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);
if (p == null) return "<Player is not loaded>";
return p.expansionValues.values.get(identifier);
}
@Override
public void setValue(@NotNull TabPlayer player, @NotNull String key, @NotNull String value) {
values.computeIfAbsent(player, p -> new HashMap<>()).put(key, value);
player.expansionValues.values.put(key, value);
}
@Override
@@ -29,9 +29,9 @@ import org.jetbrains.annotations.NotNull;
public class PerWorldPlayerList extends TabFeature implements Listener, Loadable, UnLoadable {
/** Config options */
private final boolean allowBypass = TAB.getInstance().getConfig().getBoolean("per-world-playerlist.allow-bypass-permission", false);
private final List<String> ignoredWorlds = TAB.getInstance().getConfig().getStringList("per-world-playerlist.ignore-effect-in-worlds", Arrays.asList("ignored_world", "build"));
private final Map<String, List<String>> sharedWorlds = TAB.getInstance().getConfig().getConfigurationSection("per-world-playerlist.shared-playerlist-world-groups");
private final boolean allowBypass = config().getBoolean("per-world-playerlist.allow-bypass-permission", false);
private final List<String> ignoredWorlds = config().getStringList("per-world-playerlist.ignore-effect-in-worlds", Arrays.asList("ignored_world", "build"));
private final Map<String, List<String>> sharedWorlds = config().getConfigurationSection("per-world-playerlist.shared-playerlist-world-groups");
/**
* Constructs new instance and registers events
@@ -60,18 +60,30 @@ public class PerWorldPlayerList extends TabFeature implements Listener, Loadable
HandlerList.unregisterAll(this);
}
/**
* Join event listener to synchronously perform hiding.
*
* @param e
* Join event
*/
@EventHandler
public void onJoin(PlayerJoinEvent e) {
long time = System.nanoTime();
checkPlayer(e.getPlayer());
TAB.getInstance().getCPUManager().addTime(this, TabConstants.CpuUsageCategory.PLAYER_JOIN, System.nanoTime()-time);
TAB.getInstance().getCPUManager().addTime(getFeatureName(), TabConstants.CpuUsageCategory.PLAYER_JOIN, System.nanoTime()-time);
}
/**
* World change listener to synchronously perform player showing/hiding.
*
* @param e
* World change event
*/
@EventHandler
public void onWorldChange(PlayerChangedWorldEvent e) {
long time = System.nanoTime();
checkPlayer(e.getPlayer());
TAB.getInstance().getCPUManager().addTime(this, TabConstants.CpuUsageCategory.WORLD_SWITCH, System.nanoTime()-time);
TAB.getInstance().getCPUManager().addTime(getFeatureName(), TabConstants.CpuUsageCategory.WORLD_SWITCH, System.nanoTime()-time);
}
/**
@@ -24,6 +24,9 @@ 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;
/**
* Constructs new instance and registers events
*
@@ -39,7 +42,7 @@ public class WitherBossBar extends BossBarManagerImpl implements Listener, World
//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,
featureName, TabConstants.CpuUsageCategory.TELEPORTING_WITHER, this::teleport);
getFeatureName(), TabConstants.CpuUsageCategory.TELEPORTING_WITHER, this::teleport);
super.load();
teleport();
}
@@ -51,9 +54,9 @@ public class WitherBossBar extends BossBarManagerImpl implements Listener, World
for (TabPlayer p : TAB.getInstance().getOnlinePlayers()) {
if (p.getVersion().getMinorVersion() > 8) continue; //sending VV packets to those
for (BossBar line : getRegisteredBossBars().values()) {
if (!line.getPlayers().contains(p)) continue;
if (!line.containsPlayer(p)) continue;
Location eyeLocation = ((BukkitTabPlayer)p).getPlayer().getEyeLocation();
Location loc = eyeLocation.add(eyeLocation.getDirection().normalize().multiply(60)); // Wither distance
Location loc = eyeLocation.add(eyeLocation.getDirection().normalize().multiply(WITHER_DISTANCE));
if (loc.getY() < 1) loc.setY(1);
((BackendTabPlayer)p).getEntityView().teleportEntity(line.getUniqueId().hashCode(), new me.neznamy.tab.shared.backend.Location(loc.getX(), loc.getY(), loc.getZ()));
}
@@ -67,13 +70,16 @@ public class WitherBossBar extends BossBarManagerImpl implements Listener, World
}
/**
* Respawning wither as respawn screen destroys all entities in client
* Respawning wither as respawn screen destroys all entities in client.
*
* @param e
* Respawn event
*/
@EventHandler
public void onRespawn(PlayerRespawnEvent e) {
TabPlayer player = TAB.getInstance().getPlayer(e.getPlayer().getUniqueId());
if (player == null) return;
TAB.getInstance().getCPUManager().runMeasuredTask(featureName, TabConstants.CpuUsageCategory.PLAYER_RESPAWN,
TAB.getInstance().getCPUManager().runMeasuredTask(getFeatureName(), TabConstants.CpuUsageCategory.PLAYER_RESPAWN,
() -> detectBossBarsAndSend(player));
}
@@ -1,33 +0,0 @@
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)
);
}
}
@@ -1,55 +0,0 @@
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 with to version 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 with to version 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);
}
@@ -1,70 +0,0 @@
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);
}
}
}
@@ -1,33 +0,0 @@
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,23 @@
package me.neznamy.tab.platforms.bukkit.hook;
import de.myzelyam.api.vanish.VanishAPI;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.hook.PremiumVanishHook;
import me.neznamy.tab.shared.platform.TabPlayer;
import org.jetbrains.annotations.NotNull;
/**
* PremiumVanish hook for Bukkit.
*/
public class BukkitPremiumVanishHook extends PremiumVanishHook {
@Override
public boolean canSee(@NotNull TabPlayer viewer, @NotNull TabPlayer target) {
return VanishAPI.canSee(((BukkitTabPlayer)viewer).getPlayer(), ((BukkitTabPlayer)target).getPlayer());
}
@Override
public boolean isVanished(@NotNull TabPlayer player) {
return VanishAPI.isInvisible(((BukkitTabPlayer)player).getPlayer());
}
}
@@ -1,25 +1,25 @@
package me.neznamy.tab.platforms.bukkit.nms;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import me.neznamy.tab.shared.util.FunctionWithException;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.bukkit.Bukkit;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
/**
* Class containing main NMS-related information and methods.
*/
public class BukkitReflection {
/** Server's NMS/CraftBukkit package */
@Getter
private static final String serverPackage = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3];
/** CraftBukkit package */
private static final String CRAFTBUKKIT_PACKAGE = Bukkit.getServer().getClass().getPackage().getName();
/** 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();
/** Server version data */
private static final ServerVersion serverVersion = detectServerVersion();
/** Flag determining whether the server version is at least 1.19.3 or not */
@Getter
@@ -27,24 +27,47 @@ public class BukkitReflection {
/** 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");
private static final boolean is1_19_4Plus = ReflectionUtils.classExists("net.minecraft.network.protocol.game.ClientboundBundlePacket");
/** 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 final boolean is1_20_2Plus = ReflectionUtils.classExists("net.minecraft.world.scores.DisplaySlot");
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;
/** Flag determining whether the server version is at least 1.20.3 or not */
@Getter
private static final boolean is1_20_3Plus = ReflectionUtils.classExists("net.minecraft.network.protocol.game.ClientboundResetScorePacket");
private static ServerVersion detectServerVersion() {
FunctionWithException<String, Class<?>> classFunction = name -> Class.forName("net.minecraft." + name);
String[] array = Bukkit.getServer().getClass().getPackage().getName().split("\\.");
int minorVersion;
if (array.length > 3) {
// Normal packaging
String serverPackage = array[3];
minorVersion = Integer.parseInt(serverPackage.split("_")[1]);
if (minorVersion < 17) {
ClassLoader loader = BukkitReflection.class.getClassLoader();
classFunction = name -> loader.loadClass("net.minecraft.server." + serverPackage + "." + name);
}
} else {
// Paper without CB relocation
minorVersion = Integer.parseInt(Bukkit.getBukkitVersion().split("-")[0].split("\\.")[1]);
}
return new ServerVersion(classFunction, minorVersion);
}
/**
* Returns class with given potential names in same order
* Returns server's minor version, such as 20 for 1.20.4
*
* @return server's minor version
*/
public static int getMinorVersion() {
return serverVersion.getMinorVersion();
}
/**
* Returns class with given potential names in same order. For 1.17+ it takes packaged class names
* without "net.minecraft." prefix, for <1.17 it takes class name only.
*
* @param names
* possible class names
@@ -52,32 +75,39 @@ public class BukkitReflection {
* @throws ClassNotFoundException
* if class does not exist
*/
public static Class<?> getLegacyClass(@NotNull String... names) throws ClassNotFoundException {
@SneakyThrows
public static Class<?> getClass(@NotNull String... names) throws ClassNotFoundException {
for (String name : names) {
try {
return getLegacyClass(name);
} catch (ClassNotFoundException e) {
//not the first class name in array
return serverVersion.getClass.apply(name);
} catch (ClassNotFoundException | NullPointerException ignored) {
// 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.
* Returns CraftBukkit class with given package and name.
*
* @param name
* class name
* @return class from given name
* Package and name of the class
* @return CraftBukkit class
* @throws ClassNotFoundException
* if class was not found
* If class does not exist
*/
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);
}
public static Class<?> getBukkitClass(@NotNull String name) throws ClassNotFoundException {
return Class.forName(CRAFTBUKKIT_PACKAGE + "." + name);
}
/**
* Class with server version information.
*/
@RequiredArgsConstructor
@Getter
private static class ServerVersion {
private final FunctionWithException<String, Class<?>> getClass;
private final int minorVersion;
}
}
@@ -0,0 +1,161 @@
package me.neznamy.tab.platforms.bukkit.nms;
import lombok.SneakyThrows;
import me.neznamy.tab.shared.chat.ChatModifier;
import me.neznamy.tab.shared.chat.SimpleComponent;
import me.neznamy.tab.shared.chat.StructuredComponent;
import me.neznamy.tab.shared.chat.TabComponent;
import me.neznamy.tab.shared.util.FunctionWithException;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.List;
import java.util.function.BiFunction;
/**
* Class for converting TAB component into NMS components (1.7+).
*/
@SuppressWarnings({"rawtypes", "unchecked"})
public class ComponentConverter {
/** Instance of the class */
@Nullable
public static ComponentConverter INSTANCE;
private final FunctionWithException<String, Object> newTextComponent;
private final BiFunction<ChatModifier, Boolean, Object> convertModifier;
private final Class<?> ChatModifier = BukkitReflection.getClass("network.chat.Style", "network.chat.ChatModifier", "ChatModifier");
private final Class<Enum> EnumChatFormat = (Class<Enum>) BukkitReflection.getClass("ChatFormatting", "EnumChatFormat");
private final Constructor<?> newChatModifier;
private final Method ChatBaseComponent_addSibling;
private final Field Component_modifier;
private final List<Field> magicCodes = ReflectionUtils.getFields(ChatModifier, Boolean.class);
// 1.15-
private Method ChatModifier_setColor;
// 1.16+
private Method ChatHexColor_fromRGB;
private Method ResourceLocation_tryParse;
/**
* Constructs new instance and loads all NMS classes, constructors and methods.
*
* @throws ReflectiveOperationException
* If something failed
*/
private ComponentConverter() throws ReflectiveOperationException {
Class<?> IChatBaseComponent = BukkitReflection.getClass("network.chat.Component", "network.chat.IChatBaseComponent", "IChatBaseComponent");
if (BukkitReflection.getMinorVersion() >= 19) {
Method IChatBaseComponent_b = ReflectionUtils.getMethod(IChatBaseComponent, new String[] {"b", "literal"}, String.class);
newTextComponent = text -> IChatBaseComponent_b.invoke(null, text);
Class<?> IChatMutableComponent = BukkitReflection.getClass("network.chat.MutableComponent", "network.chat.IChatMutableComponent", "IChatMutableComponent");
Component_modifier = ReflectionUtils.getOnlyField(IChatMutableComponent, ChatModifier);
ChatBaseComponent_addSibling = ReflectionUtils.getOnlyMethod(IChatMutableComponent, IChatMutableComponent, IChatBaseComponent);
} else {
Class<?> ChatComponentText = BukkitReflection.getClass("network.chat.TextComponent", "network.chat.ChatComponentText", "ChatComponentText");
Constructor<?> newChatComponentText = ChatComponentText.getConstructor(String.class);
newTextComponent = newChatComponentText::newInstance;
Class<?> ChatBaseComponent = BukkitReflection.getClass("network.chat.BaseComponent", "network.chat.ChatBaseComponent", "ChatBaseComponent");
Component_modifier = ReflectionUtils.getOnlyField(ChatBaseComponent, ChatModifier);
ChatBaseComponent_addSibling = ReflectionUtils.getOnlyMethod(ChatComponentText, IChatBaseComponent, IChatBaseComponent);
}
if (BukkitReflection.getMinorVersion() >= 16) {
Class<?> chatHexColor = BukkitReflection.getClass("network.chat.TextColor", "network.chat.ChatHexColor", "ChatHexColor");
Class<?> ResourceLocation = BukkitReflection.getClass("resources.ResourceLocation", "resources.MinecraftKey", "MinecraftKey");
Class<?> chatClickable = BukkitReflection.getClass("network.chat.ClickEvent", "network.chat.ChatClickable", "ChatClickable");
Class<?> chatHoverable = BukkitReflection.getClass("network.chat.HoverEvent", "network.chat.ChatHoverable", "ChatHoverable");
ResourceLocation_tryParse = ReflectionUtils.getMethod(ResourceLocation, new String[]{"tryParse", "m_135820_", "a"}, String.class);
ChatHexColor_fromRGB = ReflectionUtils.getMethods(chatHexColor, chatHexColor, int.class).get(0); // There should only be 1, but some mods add more
newChatModifier = ReflectionUtils.setAccessible(ChatModifier.getDeclaredConstructor(chatHexColor, Boolean.class, Boolean.class, Boolean.class,
Boolean.class, Boolean.class, chatClickable, chatHoverable, String.class, ResourceLocation));
convertModifier = this::createModifierModern;
} else {
newChatModifier = ChatModifier.getConstructor();
ChatModifier_setColor = ReflectionUtils.getOnlyMethod(ChatModifier, ChatModifier, EnumChatFormat);
convertModifier = (modifier, protocolVersion) -> createModifierLegacy(modifier);
}
}
/**
* Converts TAB component to NMS component.
*
* @param component
* Component to convert
* @param modern
* Whether client supports RGB or not
* @return Converted component
*/
@SneakyThrows
public Object convert(@NotNull TabComponent component, boolean modern) {
if (component instanceof SimpleComponent) return newTextComponent.apply(((SimpleComponent) component).getText());
StructuredComponent component1 = (StructuredComponent) component;
Object nmsComponent = newTextComponent.apply(component1.getText());
Component_modifier.set(nmsComponent, convertModifier.apply(component1.getModifier(), modern));
for (StructuredComponent extra : component1.getExtra()) {
ChatBaseComponent_addSibling.invoke(nmsComponent, convert(extra, modern));
}
return nmsComponent;
}
@SneakyThrows
private Object createModifierModern(@NotNull ChatModifier modifier, boolean modern) {
Object color = null;
if (modifier.getColor() != null) {
if (modern) {
color = ChatHexColor_fromRGB.invoke(null, modifier.getColor().getRgb());
} else {
color = ChatHexColor_fromRGB.invoke(null, modifier.getColor().getLegacyColor().getRgb());
}
}
return newChatModifier.newInstance(
color,
modifier.isBold(),
modifier.isItalic(),
modifier.isUnderlined(),
modifier.isStrikethrough(),
modifier.isObfuscated(),
null,
null,
null,
modifier.getFont() == null ? null : ResourceLocation_tryParse.invoke(null, modifier.getFont())
);
}
@SneakyThrows
private Object createModifierLegacy(@NotNull ChatModifier modifier) {
Object nmsModifier = newChatModifier.newInstance();
if (modifier.getColor() != null) {
ChatModifier_setColor.invoke(nmsModifier, Enum.valueOf(EnumChatFormat, modifier.getColor().getLegacyColor().name()));
}
if (modifier.isBold()) magicCodes.get(0).set(nmsModifier, true);
if (modifier.isItalic()) magicCodes.get(1).set(nmsModifier, true);
if (modifier.isStrikethrough()) magicCodes.get(2).set(nmsModifier, true);
if (modifier.isUnderlined()) magicCodes.get(3).set(nmsModifier, true);
if (modifier.isObfuscated()) magicCodes.get(4).set(nmsModifier, true);
return nmsModifier;
}
/**
* Attempts to load component converter.
*/
public static void tryLoad() {
try {
INSTANCE = new ComponentConverter();
} catch (Exception ignored) {
}
}
/**
* Makes sure the component converter is available and throws an exception if not.
*/
public static void ensureAvailable() {
if (INSTANCE == null) throw new IllegalStateException("Component converter is not available");
}
}
@@ -1,292 +0,0 @@
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;
}
}
}
}
@@ -1,123 +0,0 @@
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");
}
}
}
}
@@ -1,410 +0,0 @@
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));
}
}
@@ -0,0 +1,56 @@
package me.neznamy.tab.platforms.bukkit.nms;
import lombok.SneakyThrows;
import me.neznamy.tab.shared.util.BiConsumerWithException;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* Class for sending packets to players.
*/
public class PacketSender {
/** Function for sending packet */
@NotNull
private final BiConsumerWithException<Player, Object> send;
/**
* Constructs new instance and attempts to load required classes, fields and methods.
* If something fails, error is thrown.
*
* @throws ReflectiveOperationException
* If something fails
*/
public PacketSender() throws ReflectiveOperationException {
Class<?> Packet = BukkitReflection.getClass("network.protocol.Packet", "Packet");
Class<?> EntityPlayer = BukkitReflection.getClass("server.level.ServerPlayer", "server.level.EntityPlayer", "EntityPlayer");
Class<?> PlayerConnection = BukkitReflection.getClass("server.network.ServerGamePacketListenerImpl", "server.network.PlayerConnection", "PlayerConnection");
Method getHandle = BukkitReflection.getBukkitClass("entity.CraftPlayer").getMethod("getHandle");
Field PLAYER_CONNECTION = ReflectionUtils.getOnlyField(EntityPlayer, PlayerConnection);
Method sendPacket;
if (BukkitReflection.getMinorVersion() >= 7) {
sendPacket = ReflectionUtils.getMethods(PlayerConnection, void.class, Packet).get(0);
} else {
sendPacket = ReflectionUtils.getMethod(PlayerConnection, new String[]{"sendPacket"}, Packet);
}
send = (player, packet) -> sendPacket.invoke(PLAYER_CONNECTION.get(getHandle.invoke(player)), packet);
}
/**
* Sends packet to specified player.
* If something goes wrong, throws an exception.
*
* @param player
* Player to send packet to
* @param packet
* Packet to send
*/
@SneakyThrows
public void sendPacket(@NotNull Player player, @NotNull Object packet) {
send.accept(player, packet);
}
}
@@ -0,0 +1,54 @@
package me.neznamy.tab.platforms.bukkit.nms;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.BukkitUtils;
import me.neznamy.tab.shared.util.FunctionWithException;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* Class for retrieving ping of players.
*/
public class PingRetriever {
/** Ping getter function */
private static FunctionWithException<Player, Integer> getPing;
/**
* Attempts to load required classes, fields and methods and marks class as available.
* If something fails, error message is printed and class is not marked as available.
*/
public static void tryLoad() {
try {
if (ReflectionUtils.methodExists(Player.class, "getPing")) {
getPing = Player::getPing;
} else {
Method getHandle = BukkitReflection.getBukkitClass("entity.CraftPlayer").getMethod("getHandle");
Class<?> EntityPlayer = BukkitReflection.getClass("server.level.ServerPlayer", "server.level.EntityPlayer", "EntityPlayer");
Field PING = ReflectionUtils.getField(EntityPlayer, "ping", "field_71138_i"); // 1.5.2 - 1.16.5, 1.7.10 Thermos
getPing = player -> PING.getInt(getHandle.invoke(player));
}
} catch (Exception e) {
getPing = p -> -1;
BukkitUtils.compatibilityError(e, "getting player's ping", null, "%ping% returning -1");
}
}
/**
* Returns player's ping. If ping getter is not available and fields
* failed to load, returns {@code -1}. If an exception was throws by
* reflective operation, it is re-thrown.
*
* @param player
* Player to get ping of
* @return Player's ping or {@code -1} if it failed
*/
@SneakyThrows
public static int getPing(@NotNull Player player) {
return getPing.apply(player);
}
}
@@ -1,28 +1,36 @@
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.platforms.bukkit.entity.PacketEntityView;
import me.neznamy.tab.platforms.bukkit.hook.BukkitPremiumVanishHook;
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
import me.neznamy.tab.platforms.bukkit.nms.ComponentConverter;
import me.neznamy.tab.platforms.bukkit.nms.PingRetriever;
import me.neznamy.tab.platforms.bukkit.scoreboard.ScoreboardLoader;
import me.neznamy.tab.platforms.bukkit.tablist.TabListBase;
import me.neznamy.tab.shared.GroupManager;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.chat.StructuredComponent;
import me.neznamy.tab.shared.chat.SimpleComponent;
import me.neznamy.tab.shared.chat.TabComponent;
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.hook.PremiumVanishHook;
import me.neznamy.tab.shared.placeholders.types.PlayerPlaceholderImpl;
import me.neznamy.tab.shared.placeholders.expansion.EmptyTabExpansion;
import me.neznamy.tab.shared.placeholders.expansion.TabExpansion;
import me.neznamy.tab.shared.platform.TabPlayer;
@@ -41,7 +49,6 @@ 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
@@ -53,14 +60,15 @@ public class BukkitPlatform implements BackendPlatform {
@NotNull
private final JavaPlugin plugin;
/** Server version */
@Getter
private final ProtocolVersion serverVersion = ProtocolVersion.fromFriendlyName(Bukkit.getBukkitVersion().split("-")[0]);
/** 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;
/** Spigot field for tracking TPS, the array is final and only being modified instead of re-instantiated */
private double[] recentTps;
/** Detection for presence of Paper's TPS getter */
private final boolean paperTps = ReflectionUtils.methodExists(Bukkit.class, "getTPS");
@@ -68,20 +76,41 @@ public class BukkitPlatform implements BackendPlatform {
/** Detection for presence of Paper's MSPT getter */
private final boolean paperMspt = ReflectionUtils.methodExists(Bukkit.class, "getAverageTickTime");
/**
* Constructs new instance with given plugin.
*
* @param plugin
* Plugin
*/
public BukkitPlatform(@NotNull JavaPlugin plugin) {
this.plugin = plugin;
long time = System.currentTimeMillis();
try {
server = Bukkit.getServer().getClass().getMethod("getServer").invoke(Bukkit.getServer());
spigotTps = server.getClass().getField("recentTps");
} catch (ReflectiveOperationException e) {
Object server = Bukkit.getServer().getClass().getMethod("getServer").invoke(Bukkit.getServer());
recentTps = ((double[]) server.getClass().getField("recentTps").get(server));
} catch (ReflectiveOperationException ignored) {
//not spigot
}
if (Bukkit.getPluginManager().isPluginEnabled("PremiumVanish")) {
PremiumVanishHook.setInstance(new BukkitPremiumVanishHook());
}
ComponentConverter.tryLoad();
PacketEntityView.tryLoad();
PingRetriever.tryLoad();
TabListBase.findInstance();
ScoreboardLoader.tryLoad();
if (BukkitReflection.getMinorVersion() >= 8) {
BukkitPipelineInjector.tryLoad();
}
BukkitUtils.sendCompatibilityMessage();
Bukkit.getConsoleSender().sendMessage("[TAB] " + EnumChatFormat.GRAY + "Loaded NMS hook in " + (System.currentTimeMillis()-time) + "ms");
}
@Override
@NotNull
public BossBarManagerImpl getLegacyBossBar() {
return new WitherBossBar(plugin);
public BossBarManagerImpl getBossBar() {
if (BukkitReflection.getMinorVersion() <= 8) return new WitherBossBar(plugin);
return new BossBarManagerImpl();
}
@Override
@@ -100,26 +129,30 @@ public class BukkitPlatform implements BackendPlatform {
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()));
int refresh = TAB.getInstance().getConfiguration().getPermissionRefreshInterval();
manager.registerPlayerPlaceholder("%vault-prefix%", refresh, p -> chat.getPlayerPrefix((Player) p.getPlayer()));
manager.registerPlayerPlaceholder("%vault-suffix%", refresh, 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());
p -> ((TabPlayer) p).getPing());
BackendPlatform.super.registerPlaceholders();
}
@Override
@Nullable
public PipelineInjector createPipelineInjector() {
return NMSStorage.getInstance().getMinorVersion() >= 8 ? new BukkitPipelineInjector() : null;
return BukkitReflection.getMinorVersion() >= 8 && BukkitPipelineInjector.isAvailable() ? new BukkitPipelineInjector() : null;
}
@Override
@NotNull
public NameTag getUnlimitedNameTags() {
return new BukkitNameTagX(plugin);
return BukkitReflection.getMinorVersion() >= 8 &&
PacketEntityView.isAvailable() &&
BukkitPipelineInjector.isAvailable() ?
new BukkitNameTagX(plugin) : new NameTag();
}
@Override
@@ -162,6 +195,14 @@ public class BukkitPlatform implements BackendPlatform {
}
}
/**
* Registers a sync placeholder with given identifier and refresh.
*
* @param identifier
* Placeholder identifier
* @param refresh
* Placeholder refresh
*/
public void registerSyncPlaceholder(@NotNull String identifier, int refresh) {
String syncedPlaceholder = "%" + identifier.substring(6);
PlayerPlaceholderImpl[] ppl = new PlayerPlaceholderImpl[1];
@@ -169,28 +210,26 @@ public class BukkitPlatform implements BackendPlatform {
Bukkit.getScheduler().runTask(plugin, () -> {
long time = System.nanoTime();
ppl[0].updateValue(p, placeholderAPI ? PlaceholderAPI.setPlaceholders((Player) p.getPlayer(), syncedPlaceholder) : identifier);
TAB.getInstance().getCPUManager().addPlaceholderTime(identifier, System.nanoTime()-time);
TAB.getInstance().getCPUManager().addPlaceholderTime(identifier, System.nanoTime() - time);
});
return null;
});
}
@Override
public void logInfo(@NotNull IChatBaseComponent message) {
Bukkit.getConsoleSender().sendMessage("[TAB] " + BukkitUtils.toBukkitFormat(message, true));
public void logInfo(@NotNull TabComponent message) {
Bukkit.getConsoleSender().sendMessage("[TAB] " + toBukkitFormat(message, true));
}
@Override
public void logWarn(@NotNull IChatBaseComponent message) {
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.RED.getFormat() + "[TAB] [WARN] " + BukkitUtils.toBukkitFormat(message, true));
public void logWarn(@NotNull TabComponent message) {
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.RED + "[TAB] [WARN] " + toBukkitFormat(message, true));
}
@Override
@NotNull
public String getServerVersionInfo() {
return "[Bukkit] " + Bukkit.getName() + " - " +
Bukkit.getBukkitVersion().split("-")[0] +
" (" + Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3] + ")";
return "[Bukkit] " + Bukkit.getName() + " - " + Bukkit.getBukkitVersion().split("-")[0];
}
@Override
@@ -206,25 +245,19 @@ public class BukkitPlatform implements BackendPlatform {
command.setExecutor(cmd);
command.setTabCompleter(cmd);
} else {
logWarn(new IChatBaseComponent("Failed to register command, is it defined in plugin.yml?"));
logWarn(new SimpleComponent("Failed to register command, is it defined in plugin.yml?"));
}
}
@Override
public void startMetrics() {
Metrics metrics = new Metrics(plugin, 5304);
Metrics metrics = new Metrics(plugin, TabConstants.BSTATS_PLUGIN_ID_BUKKIT);
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]);
() -> "1." + BukkitReflection.getMinorVersion() + ".x"));
}
@Override
@@ -233,17 +266,13 @@ public class BukkitPlatform implements BackendPlatform {
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
public Object convertComponent(@NotNull TabComponent component, boolean modern) {
if (ComponentConverter.INSTANCE != null) {
return ComponentConverter.INSTANCE.convert(component, modern);
} else {
return component;
}
}
@Override
@@ -262,12 +291,11 @@ public class BukkitPlatform implements BackendPlatform {
}
@Override
@SneakyThrows
public double getTPS() {
if (paperTps) {
if (recentTps != null) {
return recentTps[0];
} else if (paperTps) {
return Bukkit.getTPS()[0];
} else if (spigotTps != null) {
return ((double[]) spigotTps.get(server))[0];
} else {
return -1;
}
@@ -275,11 +303,59 @@ public class BukkitPlatform implements BackendPlatform {
@Override
public double getMSPT() {
if (paperMspt) return Bukkit.getAverageTickTime();
return -1;
if (paperMspt) return Bukkit.getAverageTickTime();
return -1;
}
/**
* Runs an entity task that may or may not need to use its main thread.
*
* @param entity
* Entity to work with
* @param task
* Task to run
*/
public void runEntityTask(@NotNull Entity entity, @NotNull Runnable task) {
task.run();
}
@Override
public boolean canSee(@NotNull TabPlayer viewer, @NotNull TabPlayer target) {
if (BackendPlatform.super.canSee(viewer, target)) return true;
return ((BukkitTabPlayer)viewer).getPlayer().canSee(((BukkitTabPlayer)target).getPlayer());
}
/**
* 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 String toBukkitFormat(@NotNull TabComponent component, boolean rgbClient) {
if (component instanceof SimpleComponent) return component.toLegacyText();
StructuredComponent iComponent = (StructuredComponent) component;
StringBuilder sb = new StringBuilder();
if (iComponent.getModifier().getColor() != null) {
if (serverVersion.supportsRGB() && rgbClient) {
String hexCode = iComponent.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(iComponent.getModifier().getColor().getLegacyColor());
}
}
sb.append(iComponent.getModifier().getMagicCodes());
sb.append(iComponent.getText());
for (StructuredComponent extra : iComponent.getExtra()) {
sb.append(toBukkitFormat(extra, rgbClient));
}
return sb.toString();
}
}
@@ -5,8 +5,8 @@ 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.chat.SimpleComponent;
import me.neznamy.tab.shared.placeholders.types.PlayerPlaceholderImpl;
import me.neznamy.tab.shared.platform.TabPlayer;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
@@ -18,10 +18,16 @@ import org.jetbrains.annotations.NotNull;
import java.util.function.Consumer;
/**
* Platform override for Folia
* Platform override for Folia.
*/
public class FoliaPlatform extends BukkitPlatform {
/**
* Constructs new instance with given plugin.
*
* @param plugin
* Plugin
*/
public FoliaPlatform(@NotNull JavaPlugin plugin) {
super(plugin);
}
@@ -31,7 +37,7 @@ public class FoliaPlatform extends BukkitPlatform {
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 " +
logWarn(new SimpleComponent("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
@@ -76,7 +82,7 @@ public class FoliaPlatform extends BukkitPlatform {
*/
@SneakyThrows
@SuppressWarnings("JavaReflectionMemberAccess")
private void runSync(@NotNull Entity entity, @NotNull Runnable task) {
public void runSync(@NotNull Entity entity, @NotNull Runnable task) {
Object entityScheduler = Entity.class.getMethod("getScheduler").invoke(entity);
Consumer<?> consumer = $ -> task.run(); // Reflection and lambdas don't go together
entityScheduler.getClass().getMethod("run", Plugin.class, Consumer.class, Runnable.class)
@@ -1,220 +0,0 @@
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.");
}
}
}
@@ -0,0 +1,77 @@
package me.neznamy.tab.platforms.bukkit.scoreboard;
import lombok.NonNull;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.platform.Scoreboard;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
/**
* Dummy implementation if no scoreboard implementation is available.
*/
public class NullScoreboard extends Scoreboard<BukkitTabPlayer, Object> {
/**
* Constructs new instance with given player.
*
* @param player
* Player this scoreboard will belong to
*/
public NullScoreboard(@NonNull BukkitTabPlayer player) {
super(player);
}
@Override
protected void setDisplaySlot0(int slot, @NonNull String objective) {
// Do nothing
}
@Override
protected void setScore0(@NonNull String objective, @NonNull String scoreHolder, int score,
@Nullable Object displayName, @Nullable Object numberFormat) {
// Do nothing
}
@Override
protected void removeScore0(@NonNull String objective, @NonNull String scoreHolder) {
// Do nothing
}
@Override
protected void registerObjective0(@NonNull String objectiveName, @NonNull String title, int display,
@Nullable Object numberFormat) {
// Do nothing
}
@Override
protected void unregisterObjective0(@NonNull String objectiveName) {
// Do nothing
}
@Override
protected void updateObjective0(@NonNull String objectiveName, @NonNull String title, int display,
@Nullable Object numberFormat) {
// Do nothing
}
@Override
protected void registerTeam0(@NonNull String name, @NonNull String prefix, @NonNull String suffix,
@NonNull NameVisibility visibility, @NonNull CollisionRule collision,
@NonNull Collection<String> players, int options, @NonNull EnumChatFormat color) {
// Do nothing
}
@Override
protected void unregisterTeam0(@NonNull String name) {
// Do nothing
}
@Override
protected void updateTeam0(@NonNull String name, @NonNull String prefix, @NonNull String suffix,
@NonNull NameVisibility visibility, @NonNull CollisionRule collision,
int options, @NonNull EnumChatFormat color) {
// Do nothing
}
}
@@ -1,432 +0,0 @@
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.BukkitReflection;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
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 to use the full potential on all versions
* and server software without any artificial limits.
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public class PacketScoreboard extends Scoreboard<BukkitTabPlayer> {
@Getter
private static boolean available;
private static Object emptyScoreboard;
private static Field IScoreboardCriteria_self;
// PacketPlayOutScoreboardDisplayObjective
public static Class<?> DisplayObjectiveClass;
private static Constructor<?> newDisplayObjective;
public static Field DisplayObjective_POSITION;
public static Field DisplayObjective_OBJECTIVE_NAME;
// PacketPlayOutScoreboardScore
private static Class<Enum> EnumScoreboardAction;
private static Constructor<?> newScorePacket_1_13;
private static Constructor<?> newScorePacket_String;
private static Constructor<?> newScorePacket;
private static Constructor<?> newScoreboardScore;
private static Method ScoreboardScore_setScore;
// PacketPlayOutScoreboardObjective
public static Class<?> ObjectivePacketClass;
private static Constructor<?> newObjectivePacket;
public static Field Objective_OBJECTIVE_NAME;
public static Field Objective_METHOD;
private static Field Objective_RENDER_TYPE;
private static Field Objective_DISPLAY_NAME;
private static Class<Enum> EnumScoreboardHealthDisplay;
private static Constructor<?> newScoreboardObjective;
// PacketPlayOutScoreboardTeamStorage
public static Class<?> TeamPacketClass;
private static Constructor<?> newTeamPacket;
private static Method TeamPacketConstructor_of;
private static Method TeamPacketConstructor_ofBoolean;
public static Field TeamPacket_NAME;
public static Field TeamPacket_ACTION;
public static Field TeamPacket_PLAYERS;
private static Class<Enum> EnumNameTagVisibility;
private static Class<Enum> EnumTeamPush;
private static Constructor<?> newScoreboardTeam;
private static Method ScoreboardTeam_getPlayerNameSet;
private static Method ScoreboardTeam_setNameTagVisibility;
private static Method ScoreboardTeam_setCollisionRule;
private static Method ScoreboardTeam_setPrefix;
private static Method ScoreboardTeam_setSuffix;
private static Method ScoreboardTeam_setColor;
private static Method ScoreboardTeam_setAllowFriendlyFire;
private static Method ScoreboardTeam_setCanSeeFriendlyInvisibles;
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);
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);
Objective_OBJECTIVE_NAME = ReflectionUtils.getFields(ObjectivePacketClass, String.class).get(0);
List<Field> list = ReflectionUtils.getFields(ObjectivePacketClass, int.class);
Objective_METHOD = list.get(list.size()-1);
newScoreboardScore = scoreboardScoreClass.getConstructor(scoreboard, scoreboardObjective, String.class);
newScoreboardTeam = scoreboardTeam.getConstructor(scoreboard, String.class);
TeamPacket_NAME = ReflectionUtils.getFields(TeamPacketClass, String.class).get(0);
TeamPacket_ACTION = ReflectionUtils.getInstanceFields(TeamPacketClass, int.class).get(0);
TeamPacket_PLAYERS = ReflectionUtils.getOnlyField(TeamPacketClass, Collection.class);
ScoreboardTeam_getPlayerNameSet = ReflectionUtils.getOnlyMethod(scoreboardTeam, Collection.class);
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);
newObjectivePacket = ObjectivePacketClass.getConstructor();
Objective_DISPLAY_NAME = ReflectionUtils.getFields(ObjectivePacketClass, String.class).get(1);
if (minorVersion >= 8) {
newScorePacket = scorePacketClass.getConstructor(scoreboardScoreClass);
Objective_RENDER_TYPE = ReflectionUtils.getOnlyField(ObjectivePacketClass, EnumScoreboardHealthDisplay);
} else {
newScorePacket = scorePacketClass.getConstructor(scoreboardScoreClass, int.class);
}
}
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 (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);
}
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(@NotNull BukkitTabPlayer player) {
super(player);
}
@Override
@SneakyThrows
public void setDisplaySlot(@NotNull DisplaySlot slot, @NotNull String 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, @NotNull HealthDisplay display) {
player.sendPacket(buildObjective(0, objectiveName, title, display));
}
@Override
public void unregisterObjective0(@NotNull String objectiveName) {
player.sendPacket(buildObjective(1, objectiveName, "", HealthDisplay.INTEGER));
}
@Override
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, @NotNull HealthDisplay display) {
if (BukkitReflection.getMinorVersion() >= 13) {
return newObjectivePacket.newInstance(
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 (BukkitReflection.getMinorVersion() >= 8) {
Objective_RENDER_TYPE.set(nmsPacket, healthDisplays[display.ordinal()]);
}
Objective_METHOD.set(nmsPacket, action);
return nmsPacket;
}
@Override
@SneakyThrows
public void registerTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix,
@NotNull NameVisibility visibility, @NotNull CollisionRule collision,
@NotNull Collection<String> players, int options) {
Object team = createTeam(name, prefix, suffix, visibility, collision, options);
((Collection<String>)ScoreboardTeam_getPlayerNameSet.invoke(team)).addAll(players);
if (BukkitReflection.getMinorVersion() >= 17) {
player.sendPacket(TeamPacketConstructor_ofBoolean.invoke(null, team, true));
} else {
player.sendPacket(newTeamPacket.newInstance(team, 0));
}
}
@Override
@SneakyThrows
public void unregisterTeam0(@NotNull String name) {
Object team = newScoreboardTeam.newInstance(emptyScoreboard, name);
if (BukkitReflection.getMinorVersion() >= 17) {
player.sendPacket(TeamPacketConstructor_of.invoke(null, team));
} else {
player.sendPacket(newTeamPacket.newInstance(team, 1));
}
}
@Override
@SneakyThrows
public void updateTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix,
@NotNull NameVisibility visibility, @NotNull CollisionRule collision, int options) {
Object team = createTeam(name, prefix, suffix, visibility, collision, options);
if (BukkitReflection.getMinorVersion() >= 17) {
player.sendPacket(TeamPacketConstructor_ofBoolean.invoke(null, team, false));
} else {
player.sendPacket(newTeamPacket.newInstance(team, 2));
}
}
@SneakyThrows
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 (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 (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) {
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 (BukkitReflection.getMinorVersion() >= 8) {
player.sendPacket(newScorePacket.newInstance(scoreboardScore));
} else {
player.sendPacket(newScorePacket.newInstance(scoreboardScore, 0));
}
}
}
@Override
@SneakyThrows
public void removeScore0(@NotNull String objective, @NotNull String playerName) {
if (BukkitReflection.getMinorVersion() >= 13) {
player.sendPacket(newScorePacket_1_13.newInstance(scoreboardActions[1], objective, playerName, 0));
} else {
player.sendPacket(newScorePacket_String.newInstance(playerName));
}
}
/**
* Creates a new Scoreboard Objective with given name.
*
* @param objectiveName
* Objective name
* @return NMS Objective
*/
@SneakyThrows
public Object newScoreboardObjective(@NotNull String objectiveName) {
if (BukkitReflection.getMinorVersion() >= 13) {
return newScoreboardObjective.newInstance(
null,
objectiveName,
null,
toComponent(""),
null
);
}
return newScoreboardObjective.newInstance(
null,
objectiveName,
IScoreboardCriteria_self.get(null)
);
}
private Object toComponent(String text) {
return player.getPlatform().toComponent(IChatBaseComponent.optimizedComponent(text), player.getVersion());
}
}
@@ -1,65 +0,0 @@
package me.neznamy.tab.platforms.bukkit.scoreboard;
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.scoreboard.Objective;
import org.bukkit.scoreboard.RenderType;
import org.bukkit.scoreboard.Team;
import org.jetbrains.annotations.NotNull;
/**
* Scoreboard handler using Paper API, which got added
* in early 1.16.5 builds. Unlike the Bukkit API, the new
* methods using adventure components do not have any
* pointless artificial limits added in.
* <p>
* However, it still inherits the issue with compatibility
* with other plugins that create scoreboards.
*/
public class PaperScoreboard extends BukkitScoreboard {
/** 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(@NotNull BukkitTabPlayer player) {
super(player);
}
@Override
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 setDisplayName(@NotNull Objective objective, @NotNull String displayName) {
objective.displayName(toAdventure(displayName));
}
@Override
public void setPrefix(@NotNull Team team, @NotNull String prefix) {
team.prefix(toAdventure(prefix));
}
@Override
public void setSuffix(@NotNull Team team, @NotNull String suffix) {
team.suffix(toAdventure(suffix));
}
/**
* Converts raw text into adventure component
*
* @param text
* Text to convert
* @return Converted component
*/
@NotNull
private Component toAdventure(@NotNull String text) {
return AdventureHook.toAdventureComponent(IChatBaseComponent.optimizedComponent(text), player.getVersion());
}
}
@@ -0,0 +1,36 @@
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.scoreboard.packet.PacketScoreboard;
import me.neznamy.tab.shared.platform.Scoreboard;
import org.jetbrains.annotations.NotNull;
import java.util.function.Function;
/**
* Loader finding the best available Scoreboard implementation.
*/
public class ScoreboardLoader {
/** Instance function */
@Getter
@NotNull
private static Function<BukkitTabPlayer, Scoreboard<BukkitTabPlayer, ?>> instance = NullScoreboard::new;
/**
* Tries to load Scoreboard packets.
*/
public static void tryLoad() {
if (PacketScoreboard.isAvailable()) {
instance = PacketScoreboard::new;
} else {
BukkitUtils.compatibilityError(PacketScoreboard.getException(), "Scoreboards", null,
"Scoreboard feature will not work",
"Belowname feature will not work",
"Player objective feature will not work",
"Scoreboard teams feature will not work (nametags & sorting)");
}
}
}
@@ -0,0 +1,83 @@
package me.neznamy.tab.platforms.bukkit.scoreboard.packet;
import lombok.NonNull;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.util.FunctionWithException;
import me.neznamy.tab.shared.util.ReflectionUtils;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
/**
* Class storing NMS fields and methods of DisplayObjective packet.
*/
public class DisplayPacketData {
private final Class<?> DisplayObjectiveClass;
private final Constructor<?> newDisplayObjective;
private final Field DisplayObjective_OBJECTIVE_NAME;
private final Object[] displaySlots;
private final FunctionWithException<Object, Integer> packetToSlot;
/**
* Constructs new instance and loads all required NMS fields.
*
* @throws ReflectiveOperationException
* If something fails
*/
public DisplayPacketData() throws ReflectiveOperationException {
Class<?> ScoreboardObjective = BukkitReflection.getClass("world.scores.Objective", "world.scores.ScoreboardObjective", "ScoreboardObjective");
DisplayObjectiveClass = BukkitReflection.getClass(
"network.protocol.game.ClientboundSetDisplayObjectivePacket", // Mojang mapped
"network.protocol.game.PacketPlayOutScoreboardDisplayObjective", // Bukkit 1.17+
"PacketPlayOutScoreboardDisplayObjective", // Bukkit 1.7 - 1.16.5
"Packet208SetScoreboardDisplayObjective" // Bukkit 1.5 - 1.6.4
);
DisplayObjective_OBJECTIVE_NAME = ReflectionUtils.getOnlyField(DisplayObjectiveClass, String.class);
if (BukkitReflection.is1_20_2Plus()) {
Class<?> DisplaySlot = BukkitReflection.getClass("world.scores.DisplaySlot");
displaySlots = (Object[]) DisplaySlot.getDeclaredMethod("values").invoke(null);
Field DisplayObjective_POSITION = ReflectionUtils.getOnlyField(DisplayObjectiveClass, DisplaySlot);
newDisplayObjective = DisplayObjectiveClass.getConstructor(DisplaySlot, ScoreboardObjective);
packetToSlot = packet -> ((Enum<?>)DisplayObjective_POSITION.get(packet)).ordinal();
} else {
displaySlots = new Object[]{0, 1, 2};
Field DisplayObjective_POSITION = ReflectionUtils.getOnlyField(DisplayObjectiveClass, int.class);
newDisplayObjective = DisplayObjectiveClass.getConstructor(int.class, ScoreboardObjective);
packetToSlot = DisplayObjective_POSITION::getInt;
}
}
/**
* Creates a packet for setting display slot.
*
* @param slot
* Objective slot
* @param objective
* Objective to display
* @return Packet for setting display slot
*/
@SneakyThrows
public Object setDisplaySlot(int slot, @NonNull Object objective) {
return newDisplayObjective.newInstance(displaySlots[slot], objective);
}
/**
* Checks if packet is display objective packet and forwards it to enabled features.
*
* @param player
* Player who received packet
* @param packet
* Received packet
*/
@SneakyThrows
public void onPacketSend(@NonNull TabPlayer player, @NonNull Object packet) {
if (DisplayObjectiveClass.isInstance(packet)) {
TAB.getInstance().getFeatureManager().onDisplayObjective(player, packetToSlot.apply(packet),
(String) DisplayObjective_OBJECTIVE_NAME.get(packet));
}
}
}
@@ -0,0 +1,262 @@
package me.neznamy.tab.platforms.bukkit.scoreboard.packet;
import lombok.Getter;
import lombok.NonNull;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.platforms.bukkit.nms.ComponentConverter;
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
import me.neznamy.tab.platforms.bukkit.nms.PacketSender;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.chat.TabComponent;
import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Scoreboard implementation which uses packets
* to send scoreboards to use the full potential on all versions
* and server software without any artificial limits.
*/
public class PacketScoreboard extends Scoreboard<BukkitTabPlayer, Object> {
@Getter
private static boolean available;
@Getter
private static Exception exception;
static Class<?> Component;
static Class<?> Scoreboard;
static Class<?> ScoreboardObjective;
static Object emptyScoreboard;
static Class<?> NumberFormat;
private static Constructor<?> newFixedFormat;
// Objective packet
private static Class<?> ObjectivePacketClass;
private static Constructor<?> newObjectivePacket;
private static Field Objective_OBJECTIVE_NAME;
private static Field Objective_METHOD;
private static Field Objective_RENDER_TYPE;
static Constructor<?> newScoreboardObjective;
private static Method ScoreboardObjective_setDisplayName;
private static Enum<?>[] healthDisplays;
static Object IScoreboardCriteria_dummy;
private static ScorePacketData scorePacketData;
@Getter private static TeamPacketData teamPacketData;
@Getter private static DisplayPacketData displayPacketData;
private static PacketSender packetSender;
private final Map<String, Object> teams = new HashMap<>();
static {
try {
int minorVersion = BukkitReflection.getMinorVersion();
Scoreboard = BukkitReflection.getClass("world.scores.Scoreboard", "Scoreboard");
ScoreboardObjective = BukkitReflection.getClass("world.scores.Objective", "world.scores.ScoreboardObjective", "ScoreboardObjective");
Class<?> IScoreboardCriteria = BukkitReflection.getClass(
"world.scores.criteria.ObjectiveCriteria", // Mojang mapped
"world.scores.criteria.IScoreboardCriteria", // Bukkit 1.17.+
"IScoreboardCriteria", // 1.5.1 - 1.16.5
"IObjective" // 1.5.0
);
ObjectivePacketClass = BukkitReflection.getClass(
"network.protocol.game.ClientboundSetObjectivePacket", // Mojang mapped
"network.protocol.game.PacketPlayOutScoreboardObjective", // Bukkit 1.17+
"PacketPlayOutScoreboardObjective", // 1.7 - 1.16.5
"Packet206SetScoreboardObjective" // 1.5 - 1.6.4
);
emptyScoreboard = Scoreboard.getConstructor().newInstance();
Objective_OBJECTIVE_NAME = ReflectionUtils.getFields(ObjectivePacketClass, String.class).get(0);
List<Field> list = ReflectionUtils.getFields(ObjectivePacketClass, int.class);
Objective_METHOD = list.get(list.size()-1);
newObjectivePacket = ObjectivePacketClass.getConstructor(ScoreboardObjective, int.class);
IScoreboardCriteria_dummy = ReflectionUtils.getFields(IScoreboardCriteria, IScoreboardCriteria).get(0).get(null);
newScoreboardObjective = ReflectionUtils.getOnlyConstructor(ScoreboardObjective);
if (minorVersion >= 7) {
Component = BukkitReflection.getClass("network.chat.Component", "network.chat.IChatBaseComponent", "IChatBaseComponent");
ComponentConverter.ensureAvailable();
}
if (minorVersion >= 8) {
Class<?> EnumScoreboardHealthDisplay = BukkitReflection.getClass(
"world.scores.criteria.ObjectiveCriteria$RenderType",
"world.scores.criteria.IScoreboardCriteria$EnumScoreboardHealthDisplay",
"IScoreboardCriteria$EnumScoreboardHealthDisplay",
"EnumScoreboardHealthDisplay");
healthDisplays = (Enum<?>[]) EnumScoreboardHealthDisplay.getMethod("values").invoke(null);
if (minorVersion < 13) {
Objective_RENDER_TYPE = ReflectionUtils.getOnlyField(ObjectivePacketClass, EnumScoreboardHealthDisplay);
}
}
if (minorVersion < 13) {
ScoreboardObjective_setDisplayName = ReflectionUtils.getOnlyMethod(ScoreboardObjective, void.class, String.class);
}
if (BukkitReflection.is1_20_3Plus()) {
NumberFormat = BukkitReflection.getClass("network.chat.numbers.NumberFormat");
newFixedFormat = BukkitReflection.getClass("network.chat.numbers.FixedFormat").getConstructor(Component);
}
scorePacketData = new ScorePacketData();
teamPacketData = new TeamPacketData();
displayPacketData = new DisplayPacketData();
packetSender = new PacketSender();
available = true;
} catch (Exception e) {
exception = e;
}
}
/**
* Constructs new instance with given player.
*
* @param player
* Player this scoreboard will belong to
*/
public PacketScoreboard(@NonNull BukkitTabPlayer player) {
super(player);
}
@Override
public void setDisplaySlot0(int slot, @NonNull String objective) {
packetSender.sendPacket(player.getPlayer(), displayPacketData.setDisplaySlot(slot, newObjective(objective, "", 0, null)));
}
@Override
public void registerObjective0(@NonNull String objectiveName, @NonNull String title, int display,
@Nullable Object numberFormat) {
packetSender.sendPacket(player.getPlayer(), newObjectivePacket(ObjectiveAction.REGISTER, objectiveName, title, display, numberFormat));
}
@Override
public void unregisterObjective0(@NonNull String objectiveName) {
packetSender.sendPacket(player.getPlayer(), newObjectivePacket(ObjectiveAction.UNREGISTER, objectiveName, "", 0, null));
}
@Override
public void updateObjective0(@NonNull String objectiveName, @NonNull String title, int display,
@Nullable Object numberFormat) {
packetSender.sendPacket(player.getPlayer(), newObjectivePacket(ObjectiveAction.UPDATE, objectiveName, title, display, numberFormat));
}
@SneakyThrows
private Object newObjectivePacket(int action, @NonNull String objectiveName, @NonNull String title, int display,
@Nullable Object numberFormat) {
Object packet = newObjectivePacket.newInstance(newObjective(objectiveName, title, display, numberFormat), action);
if (BukkitReflection.getMinorVersion() >= 8 && BukkitReflection.getMinorVersion() < 13) {
Objective_RENDER_TYPE.set(packet, healthDisplays[display]);
}
return packet;
}
@Override
public void registerTeam0(@NonNull String name, @NonNull String prefix, @NonNull String suffix,
@NonNull NameVisibility visibility, @NonNull CollisionRule collision,
@NonNull Collection<String> players, int options, @NonNull EnumChatFormat color) {
Object team = teamPacketData.createTeam(name);
teams.put(name, team);
packetSender.sendPacket(player.getPlayer(), teamPacketData.registerTeam(team, prefix, toComponent(prefix), suffix,
toComponent(suffix), visibility, collision, players, options, color));
}
@Override
public void unregisterTeam0(@NonNull String name) {
packetSender.sendPacket(player.getPlayer(), teamPacketData.unregisterTeam(teams.remove(name)));
}
@Override
public void updateTeam0(@NonNull String name, @NonNull String prefix, @NonNull String suffix,
@NonNull NameVisibility visibility, @NonNull CollisionRule collision,
int options, @NonNull EnumChatFormat color) {
packetSender.sendPacket(player.getPlayer(), teamPacketData.updateTeam(teams.get(name), prefix, toComponent(prefix), suffix,
toComponent(suffix), visibility, collision, options, color));
}
@Override
public void setScore0(@NonNull String objective, @NonNull String scoreHolder, int score,
@Nullable Object displayName, @Nullable Object numberFormat) {
packetSender.sendPacket(player.getPlayer(), scorePacketData.setScore(objective, scoreHolder, score, displayName, toFixedFormat(numberFormat)));
}
@Override
public void removeScore0(@NonNull String objective, @NonNull String scoreHolder) {
packetSender.sendPacket(player.getPlayer(), scorePacketData.removeScore(objective, scoreHolder));
}
@Override
@SneakyThrows
public void onPacketSend(@NonNull Object packet) {
displayPacketData.onPacketSend(player, packet);
if (ObjectivePacketClass.isInstance(packet)) {
TAB.getInstance().getFeatureManager().onObjective(player,
Objective_METHOD.getInt(packet), (String) Objective_OBJECTIVE_NAME.get(packet));
}
if (isAntiOverrideTeams()) teamPacketData.onPacketSend(player, packet);
}
/**
* Creates a new Scoreboard Objective with given parameters.
*
* @param objectiveName
* Objective name
* @param title
* Objective title
* @param renderType
* Render type
* @param numberFormat
* Default number format (1.20.3+)
* @return Created objective
*/
@SneakyThrows
public Object newObjective(@NonNull String objectiveName, @NonNull String title, int renderType,
@Nullable Object numberFormat) {
if (BukkitReflection.is1_20_3Plus()) {
// 1.20.3+
return newScoreboardObjective.newInstance(
emptyScoreboard,
objectiveName,
null, // Criteria
toComponent(title),
healthDisplays[renderType],
false, // Auto update
toFixedFormat(numberFormat)
);
}
if (BukkitReflection.getMinorVersion() >= 13) {
// 1.13 - 1.20.2
return newScoreboardObjective.newInstance(
emptyScoreboard,
objectiveName,
null, // Criteria
toComponent(title),
healthDisplays[renderType]
);
}
// 1.5 - 1.12.2
Object objective = newScoreboardObjective.newInstance(emptyScoreboard, objectiveName, IScoreboardCriteria_dummy);
ScoreboardObjective_setDisplayName.invoke(objective, title);
return objective;
}
@NotNull
private Object toComponent(@NonNull String text) {
return TabComponent.optimized(text).convert(player.getVersion());
}
@Nullable
@SneakyThrows
private Object toFixedFormat(@Nullable Object numberFormat) {
if (numberFormat == null || newFixedFormat == null) return null;
return newFixedFormat.newInstance(numberFormat);
}
}
@@ -0,0 +1,123 @@
package me.neznamy.tab.platforms.bukkit.scoreboard.packet;
import lombok.NonNull;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.util.BiFunctionWithException;
import me.neznamy.tab.shared.util.QuintFunction;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.Optional;
/**
* Class containing data for scoreboard score set/reset packets.
*/
public class ScorePacketData {
private final QuintFunction<String, String, Integer, Object, Object, Object> setScore;
private final BiFunctionWithException<String, String, Object> removeScore;
/**
* Constructs new instance and loads all fields. Throws exception if something went wrong.
*
* @throws ReflectiveOperationException
* If something went wrong
*/
public ScorePacketData() throws ReflectiveOperationException {
Class<?> SetScorePacket = BukkitReflection.getClass(
"network.protocol.game.ClientboundSetScorePacket", // Mojang mapped
"network.protocol.game.PacketPlayOutScoreboardScore", // Bukkit 1.17+
"PacketPlayOutScoreboardScore", // 1.7 - 1.16.5
"Packet207SetScoreboardScore" // 1.5 - 1.6.4
);
if (BukkitReflection.is1_20_3Plus()) {
QuintFunction<String, String, Integer, Object, Object, Object> setScore0;
try {
// 1.20.5+
Constructor<?> newSetScore = SetScorePacket.getConstructor(String.class, String.class, int.class, Optional.class, Optional.class);
setScore0 = (objective, holder, score, displayName, numberFormat) ->
newSetScore.newInstance(holder, objective, score, Optional.ofNullable(displayName), Optional.ofNullable(numberFormat));
} catch (ReflectiveOperationException e) {
// 1.20.3 - 1.20.4
Constructor<?> newSetScore = SetScorePacket.getConstructor(String.class, String.class, int.class, PacketScoreboard.Component, PacketScoreboard.NumberFormat);
setScore0 = (objective, holder, score, displayName, numberFormat) ->
newSetScore.newInstance(holder, objective, score, displayName, numberFormat);
}
setScore = setScore0;
Constructor<?> newResetScore = BukkitReflection.getClass("network.protocol.game.ClientboundResetScorePacket").getConstructor(String.class, String.class);
removeScore = (objective, holder) -> newResetScore.newInstance(holder, objective);
} else if (BukkitReflection.getMinorVersion() >= 13) {
Class<?> actionClass = BukkitReflection.getClass("server.ServerScoreboard$Method", "server.ScoreboardServer$Action", "ScoreboardServer$Action");
Constructor<?> newSetScore = SetScorePacket.getConstructor(actionClass, String.class, String.class, int.class);
Enum<?>[] scoreboardActions = (Enum<?>[]) actionClass.getMethod("values").invoke(null);
setScore = (objective, holder, score, displayName, numberFormat) -> newSetScore.newInstance(scoreboardActions[0], objective, holder, score);
removeScore = (objective, holder) -> newSetScore.newInstance(scoreboardActions[1], objective, holder, 0);
} else {
Class<?> ScoreboardScore = BukkitReflection.getClass("ScoreboardScore");
Constructor<?> newResetScore = SetScorePacket.getConstructor(String.class);
Field SetScorePacket_SCORE = ReflectionUtils.getFields(SetScorePacket, int.class).get(0);
Constructor<?> newScoreboardScore = ScoreboardScore.getConstructor(PacketScoreboard.Scoreboard, PacketScoreboard.ScoreboardObjective, String.class);
Constructor<?> newSetScore;
if (BukkitReflection.getMinorVersion() >= 8) {
newSetScore = SetScorePacket.getConstructor(ScoreboardScore);
} else {
newSetScore = SetScorePacket.getConstructor(ScoreboardScore, int.class);
}
setScore = (objective, holder, score, displayName, numberFormat) -> {
Object scoreboardScore = newScoreboardScore.newInstance(
PacketScoreboard.emptyScoreboard,
PacketScoreboard.newScoreboardObjective.newInstance(PacketScoreboard.emptyScoreboard, objective, PacketScoreboard.IScoreboardCriteria_dummy),
holder
);
Object packet;
if (BukkitReflection.getMinorVersion() >= 8) {
packet = newSetScore.newInstance(scoreboardScore);
} else {
packet = newSetScore.newInstance(scoreboardScore, Scoreboard.ScoreAction.CHANGE);
}
SetScorePacket_SCORE.set(packet, score);
return packet;
};
removeScore = (objective, holder) -> newResetScore.newInstance(holder);
}
}
/**
* Creates set score packet.
*
* @param objective
* Objective name
* @param scoreHolder
* Score holder
* @param score
* Score value
* @param displayName
* Score holder display name
* @param numberFormat
* Score value number format
* @return Set score packet with given parameters
*/
@SneakyThrows
public Object setScore(@NonNull String objective, @NonNull String scoreHolder, int score,
@Nullable Object displayName, @Nullable Object numberFormat) {
return setScore.apply(objective, scoreHolder, score, displayName, numberFormat);
}
/**
* Creates reset score packet.
*
* @param objective
* Objective name
* @param scoreHolder
* Score holder
* @return Reset score packet with given parameters.
*/
@SneakyThrows
public Object removeScore(@NonNull String objective, @NonNull String scoreHolder) {
return removeScore.apply(objective, scoreHolder);
}
}
@@ -0,0 +1,315 @@
package me.neznamy.tab.platforms.bukkit.scoreboard.packet;
import lombok.Getter;
import lombok.NonNull;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.platform.Scoreboard.TeamAction;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.util.BiConsumerWithException;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.List;
/**
* Class storing all team related fields and methods.
*/
@SuppressWarnings("unchecked")
public class TeamPacketData {
/** First version with modern team data using components */
private final int MODERN_TEAM_DATA_VERSION = 13;
/** First version with static constructor-like methods */
private final int STATIC_CONSTRUCTOR_VERSION = 17;
private final Object emptyScoreboard;
@Getter private final Class<?> TeamPacketClass;
private Constructor<?> newTeamPacket;
private final Constructor<?> newScoreboardTeam;
private Method TeamPacketConstructor_of;
private Method TeamPacketConstructor_ofBoolean;
private final Field TeamPacket_NAME;
private final Field TeamPacket_ACTION;
private final Field TeamPacket_PLAYERS;
private final Method ScoreboardTeam_getPlayerNameSet;
private final Method ScoreboardTeam_setPrefix;
private final Method ScoreboardTeam_setSuffix;
private Method ScoreboardTeam_setColor;
private final Method ScoreboardTeam_setAllowFriendlyFire;
private final Method ScoreboardTeam_setCanSeeFriendlyInvisibles;
private final Enum<?>[] chatFormats;
private BiConsumerWithException<Object, Scoreboard.NameVisibility> setVisibility = (team, visibility) -> {};
private BiConsumerWithException<Object, Scoreboard.CollisionRule> setCollision = (team, collision) -> {};
/**
* Constructs new instance and loads all required NMS classes, fields and methods.
* If anything fails, exception is thrown.
*
* @throws ReflectiveOperationException
* If anything fails
*/
public TeamPacketData() throws ReflectiveOperationException {
int minorVersion = BukkitReflection.getMinorVersion();
Class<?> Scoreboard = BukkitReflection.getClass("world.scores.Scoreboard", "Scoreboard");
Class<?> scoreboardTeam = BukkitReflection.getClass("world.scores.PlayerTeam", "world.scores.ScoreboardTeam", "ScoreboardTeam");
Class<?> enumChatFormatClass = BukkitReflection.getClass("ChatFormatting", "EnumChatFormat", "EnumChatFormat");
TeamPacketClass = BukkitReflection.getClass(
"network.protocol.game.ClientboundSetPlayerTeamPacket", // Mojang mapped
"network.protocol.game.PacketPlayOutScoreboardTeam", // Bukkit 1.17+
"PacketPlayOutScoreboardTeam", // Bukkit 1.7 - 1.16.5
"Packet209SetScoreboardTeam" // 1.5 - 1.6.4
);
emptyScoreboard = Scoreboard.getConstructor().newInstance();
newScoreboardTeam = scoreboardTeam.getConstructor(Scoreboard, String.class);
TeamPacket_NAME = ReflectionUtils.getFields(TeamPacketClass, String.class).get(0);
List<Field> intFields = ReflectionUtils.getInstanceFields(TeamPacketClass, int.class);
if (minorVersion >= 8 && minorVersion <= 12) {
TeamPacket_ACTION = intFields.get(1);
} else {
TeamPacket_ACTION = intFields.get(0);
}
TeamPacket_PLAYERS = ReflectionUtils.getOnlyField(TeamPacketClass, Collection.class);
ScoreboardTeam_getPlayerNameSet = ReflectionUtils.getOnlyMethod(scoreboardTeam, Collection.class);
chatFormats = (Enum<?>[]) enumChatFormatClass.getMethod("values").invoke(null);
ScoreboardTeam_setAllowFriendlyFire = ReflectionUtils.getMethod(
scoreboardTeam,
new String[]{"func_96660_a", "setAllowFriendlyFire", "a", "m_83355_"}, // {Thermos, 1.5.1+, 1.5 & 1.18+, Mohist 1.18.2}
boolean.class
);
ScoreboardTeam_setCanSeeFriendlyInvisibles = ReflectionUtils.getMethod(
scoreboardTeam,
new String[]{"func_98300_b", "setCanSeeFriendlyInvisibles", "b", "m_83362_", "setSeeFriendlyInvisibles"}, // {Thermos, 1.5.1+, 1.5 & 1.18+, Mohist 1.18.2, 1.20.2+}
boolean.class
);
if (minorVersion >= 8) loadVisibility(scoreboardTeam);
if (minorVersion >= 9) loadCollision(scoreboardTeam);
if (minorVersion >= MODERN_TEAM_DATA_VERSION) {
Class<?> Component = BukkitReflection.getClass("network.chat.Component", "network.chat.IChatBaseComponent", "IChatBaseComponent");
ScoreboardTeam_setColor = ReflectionUtils.getOnlyMethod(scoreboardTeam, void.class, enumChatFormatClass);
ScoreboardTeam_setPrefix = ReflectionUtils.getMethod(
scoreboardTeam,
new String[]{"setPrefix", "b", "m_83360_", "setPlayerPrefix"}, // {1.17.1-, 1.18 - 1.20.1, Mohist 1.18.2, 1.20.2+}
Component
);
ScoreboardTeam_setSuffix = ReflectionUtils.getMethod(
scoreboardTeam,
new String[]{"setSuffix", "c", "m_83365_", "setPlayerSuffix"}, // {1.17.1-, 1.18 - 1.20.1, Mohist 1.18.2, 1.20.2+}
Component
);
} else {
ScoreboardTeam_setPrefix = ReflectionUtils.getMethod(
scoreboardTeam,
new String[]{"func_96666_b", "setPrefix", "b"}, // {Thermos, 1.5.1+, 1.5}
String.class
);
ScoreboardTeam_setSuffix = ReflectionUtils.getMethod(
scoreboardTeam,
new String[]{"func_96662_c", "setSuffix", "c"}, // {Thermos, 1.5.1+, 1.5}
String.class
);
}
if (minorVersion >= STATIC_CONSTRUCTOR_VERSION) {
TeamPacketConstructor_of = ReflectionUtils.getOnlyMethod(TeamPacketClass, TeamPacketClass, scoreboardTeam);
TeamPacketConstructor_ofBoolean = ReflectionUtils.getOnlyMethod(TeamPacketClass, TeamPacketClass, scoreboardTeam, boolean.class);
} else {
newTeamPacket = TeamPacketClass.getConstructor(scoreboardTeam, int.class);
}
}
@SneakyThrows
private void loadVisibility(@NonNull Class<?> scoreboardTeam) {
Class<?> enumNameTagVisibility = BukkitReflection.getClass(
"world.scores.Team$Visibility", // Mojang mapped
"world.scores.ScoreboardTeamBase$EnumNameTagVisibility", // Bukkit 1.17+
"ScoreboardTeamBase$EnumNameTagVisibility", // Bukkit 1.8.1 - 1.16.5
"EnumNameTagVisibility" // Bukkit 1.8.0
);
Enum<?>[] nameVisibilities = (Enum<?>[]) enumNameTagVisibility.getMethod("values").invoke(null);
Method setNameTagVisibility = ReflectionUtils.getMethod(
scoreboardTeam,
new String[]{"setNameTagVisibility", "a", "m_83346_"}, // {1.8.1+, 1.8 & 1.18+, Mohist 1.18.2}
enumNameTagVisibility
);
setVisibility = (team, visibility) -> setNameTagVisibility.invoke(team, nameVisibilities[visibility.ordinal()]);
}
@SneakyThrows
private void loadCollision(@NonNull Class<?> scoreboardTeam) {
Class<?> enumTeamPush = BukkitReflection.getClass("world.scores.Team$CollisionRule",
"world.scores.ScoreboardTeamBase$EnumTeamPush", "ScoreboardTeamBase$EnumTeamPush");
Enum<?>[] collisionRules = (Enum<?>[]) enumTeamPush.getMethod("values").invoke(null);
Method setCollisionRule = ReflectionUtils.getOnlyMethod(scoreboardTeam, void.class, enumTeamPush);
setCollision = (team, collision) -> setCollisionRule.invoke(team, collisionRules[collision.ordinal()]);
}
/**
* Creates team register packet with specified parameters.
*
* @param team
* Team to register
* @param prefix
* Team prefix for 1.12-
* @param prefixComponent
* Team prefix component for 1.13+
* @param suffix
* Team suffix for 1.12-
* @param suffixComponent
* Team suffix component for 1.13+
* @param visibility
* Nametag visibility rule
* @param collision
* Player collision rule
* @param players
* Team members
* @param options
* Team flags
* @param color
* Team color for 1.13+
* @return Register team packet with specified parameters
*/
@SneakyThrows
public Object registerTeam(@NonNull Object team, @NonNull String prefix, @Nullable Object prefixComponent,
@NonNull String suffix, @Nullable Object suffixComponent,
@NonNull Scoreboard.NameVisibility visibility, @NonNull Scoreboard.CollisionRule collision,
@NonNull Collection<String> players, int options, @NonNull EnumChatFormat color) {
updateTeamData(team, prefix, prefixComponent, suffix, suffixComponent, visibility, collision, options, color);
((Collection<String>) ScoreboardTeam_getPlayerNameSet.invoke(team)).addAll(players);
if (BukkitReflection.getMinorVersion() >= STATIC_CONSTRUCTOR_VERSION) {
return TeamPacketConstructor_ofBoolean.invoke(null, team, true);
} else {
return newTeamPacket.newInstance(team, TeamAction.CREATE);
}
}
/**
* Creates unregister team packet with given team.
*
* @param team
* Team to unregister
* @return Packet for unregistering team
*/
@SneakyThrows
public Object unregisterTeam(@NonNull Object team) {
if (BukkitReflection.getMinorVersion() >= STATIC_CONSTRUCTOR_VERSION) {
return TeamPacketConstructor_of.invoke(null, team);
} else {
return newTeamPacket.newInstance(team, TeamAction.REMOVE);
}
}
/**
* Creates team update packet with specified parameters.
*
* @param team
* Team to update
* @param prefix
* Team prefix for 1.12-
* @param prefixComponent
* Team prefix component for 1.13+
* @param suffix
* Team suffix for 1.12-
* @param suffixComponent
* Team suffix component for 1.13+
* @param visibility
* Nametag visibility rule
* @param collision
* Player collision rule
* @param options
* Team flags
* @param color
* Team color for 1.13+
* @return Update team packet with specified parameters
*/
@SneakyThrows
public Object updateTeam(@NonNull Object team, @NonNull String prefix, @Nullable Object prefixComponent,
@NonNull String suffix, @Nullable Object suffixComponent,
@NonNull Scoreboard.NameVisibility visibility, @NonNull Scoreboard.CollisionRule collision,
int options, @NonNull EnumChatFormat color) {
updateTeamData(team, prefix, prefixComponent, suffix, suffixComponent, visibility, collision, options, color);
if (BukkitReflection.getMinorVersion() >= STATIC_CONSTRUCTOR_VERSION) {
return TeamPacketConstructor_ofBoolean.invoke(null, team, false);
} else {
return newTeamPacket.newInstance(team, TeamAction.UPDATE);
}
}
/**
* Updates team properties.
*
* @param team
* Team to update
* @param prefix
* Team prefix for 1.12-
* @param prefixComponent
* Team prefix component for 1.13+
* @param suffix
* Team suffix for 1.12-
* @param suffixComponent
* Team suffix component for 1.13+
* @param visibility
* Nametag visibility rule
* @param collision
* Player collision rule
* @param options
* Team flags
* @param color
* Team color for 1.13+
*/
@SneakyThrows
private void updateTeamData(@NonNull Object team, @NonNull String prefix, @Nullable Object prefixComponent,
@NonNull String suffix, @Nullable Object suffixComponent,
@NonNull Scoreboard.NameVisibility visibility, @NonNull Scoreboard.CollisionRule collision,
int options, @NonNull EnumChatFormat color) {
ScoreboardTeam_setAllowFriendlyFire.invoke(team, (options & 0x1) > 0);
ScoreboardTeam_setCanSeeFriendlyInvisibles.invoke(team, (options & 0x2) > 0);
if (BukkitReflection.getMinorVersion() >= MODERN_TEAM_DATA_VERSION) {
ScoreboardTeam_setPrefix.invoke(team, prefixComponent);
ScoreboardTeam_setSuffix.invoke(team, suffixComponent);
ScoreboardTeam_setColor.invoke(team, chatFormats[color.ordinal()]);
} else {
ScoreboardTeam_setPrefix.invoke(team, prefix);
ScoreboardTeam_setSuffix.invoke(team, suffix);
}
setVisibility.accept(team, visibility);
setCollision.accept(team, collision);
}
/**
* Creates a team with given name.
*
* @param name
* Team name
* @return Team with specified name
*/
@SneakyThrows
public Object createTeam(@NonNull String name) {
return newScoreboardTeam.newInstance(emptyScoreboard, name);
}
/**
* Checks if packet is team packet and removes all real players from team
* if sent by other plugins and anti-override is fully active on a player.
*
* @param player
* Player who received the packet
* @param packet
* Received packet
*/
@SneakyThrows
public void onPacketSend(@NonNull TabPlayer player, @NonNull Object packet) {
if (!TeamPacketClass.isInstance(packet)) return;
int action = TeamPacket_ACTION.getInt(packet);
if (action == TeamAction.UPDATE) return;
TeamPacket_PLAYERS.set(packet, player.getScoreboard().onTeamPacket(
action, (String) TeamPacket_NAME.get(packet), (Collection<String>) TeamPacket_PLAYERS.get(packet)));
}
}
@@ -0,0 +1,64 @@
package me.neznamy.tab.platforms.bukkit.tablist;
import lombok.NonNull;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.chat.TabComponent;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.Nullable;
import java.util.UUID;
/**
* TabList handler using the almighty Bukkit API.
*/
public class BukkitTabList extends TabListBase<String> {
/**
* Constructs new instance with given player.
*
* @param player
* Player this tablist will belong to.
*/
public BukkitTabList(@NonNull BukkitTabPlayer player) {
super(player);
}
@Override
public void removeEntry(@NonNull UUID entry) {
// Shrug
}
@Override
@SuppressWarnings("deprecation")
public void updateDisplayName0(@NonNull UUID entry, @Nullable String displayName) {
Player p = Bukkit.getPlayer(entry);
if (p == null) return;
p.setPlayerListName(displayName);
}
@Override
public void updateLatency(@NonNull UUID entry, int latency) {
// Shrug
}
@Override
public void updateGameMode(@NonNull UUID entry, int gameMode) {
// Shrug
}
@Override
public void updateListed(@NonNull UUID entry, boolean listed) {
// Shrug
}
@Override
public void addEntry0(@NonNull UUID id, @NonNull String name, @Nullable Skin skin, boolean listed, int latency, int gameMode, @Nullable String displayName) {
// Shrug
}
@Override
public String toComponent(@NonNull TabComponent component) {
return player.getPlatform().toBukkitFormat(component, true);
}
}
@@ -0,0 +1,65 @@
package me.neznamy.tab.platforms.bukkit.tablist;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
import me.neznamy.tab.platforms.bukkit.nms.ComponentConverter;
import me.neznamy.tab.platforms.bukkit.nms.PacketSender;
import me.neznamy.tab.shared.util.BiFunctionWithException;
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 {
private final PacketSender packetSender = new PacketSender();
private final BiFunctionWithException<Object, Object, Object> createPacket;
/**
* Constructs new instance and loads all NMS content. Throws exception if something went wrong.
*
* @throws ReflectiveOperationException
* If something went wrong
*/
public PacketHeaderFooter() throws ReflectiveOperationException {
Class<?> Component = BukkitReflection.getClass("network.chat.Component", "network.chat.IChatBaseComponent", "IChatBaseComponent");
Class<?> HeaderFooterClass = BukkitReflection.getClass("network.protocol.game.ClientboundTabListPacket",
"network.protocol.game.PacketPlayOutPlayerListHeaderFooter", "PacketPlayOutPlayerListHeaderFooter");
ComponentConverter.ensureAvailable();
if (BukkitReflection.getMinorVersion() >= 17) {
Constructor<?> newHeaderFooter = HeaderFooterClass.getConstructor(Component, Component);
createPacket = newHeaderFooter::newInstance;
} else {
Constructor<?> newHeaderFooter = HeaderFooterClass.getConstructor();
Field HEADER = ReflectionUtils.getFields(HeaderFooterClass, Component).get(0);
Field FOOTER = ReflectionUtils.getFields(HeaderFooterClass, Component).get(1);
createPacket = (header, footer) -> {
Object packet = newHeaderFooter.newInstance();
HEADER.set(packet, header);
FOOTER.set(packet, footer);
return packet;
};
}
}
/**
* Sends header/footer to player.
*
* @param player
* Player to send header/footer to.
* @param header
* Header to use.
* @param footer
* Footer to use.
*/
@SneakyThrows
public void set(@NotNull BukkitTabPlayer player, @NotNull Object header, @NotNull Object footer) {
packetSender.sendPacket(player.getPlayer(), createPacket.apply(header, footer));
}
}
@@ -0,0 +1,163 @@
package me.neznamy.tab.platforms.bukkit.tablist;
import com.mojang.authlib.GameProfile;
import lombok.NonNull;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.*;
/**
* TabList handler for 1.19.3+ servers using packets.
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public class PacketTabList1193 extends PacketTabList18 {
/** Map of actions to prevent creating new EnumSet on each packet send */
private static final Map<Action, EnumSet<?>> actionToEnumSet = new EnumMap<>(Action.class);
private static Enum actionAddPlayer;
private static Enum actionUpdateDisplayName;
private static Enum actionUpdateLatency;
private static Constructor<?> newRemovePacket;
private static Field PlayerInfoData_UUID;
private static Field PlayerInfoData_GameMode;
private static Field PlayerInfoData_Listed;
private static Field PlayerInfoData_RemoteChatSession;
/**
* Constructs new instance with given player.
*
* @param player
* Player this tablist will belong to.
*/
public PacketTabList1193(@NonNull BukkitTabPlayer player) {
super(player);
}
/**
* Attempts to load all required NMS classes, fields and methods.
* If anything fails, throws an exception.
*
* @throws ReflectiveOperationException
* If something goes wrong
*/
public static void loadNew() throws ReflectiveOperationException {
Class<?> IChatBaseComponent = BukkitReflection.getClass("network.chat.Component", "network.chat.IChatBaseComponent", "IChatBaseComponent");
Class<?> RemoteChatSession$Data = BukkitReflection.getClass("network.chat.RemoteChatSession$Data", "network.chat.RemoteChatSession$a");
Class<Enum> EnumGamemodeClass = (Class<Enum>) BukkitReflection.getClass("world.level.GameType", "world.level.EnumGamemode");
ActionClass = (Class<Enum>) BukkitReflection.getClass(
"network.protocol.game.ClientboundPlayerInfoUpdatePacket$Action", // Mojang
"network.protocol.game.ClientboundPlayerInfoUpdatePacket$a" // Bukkit
);
PlayerInfoClass = BukkitReflection.getClass("network.protocol.game.ClientboundPlayerInfoUpdatePacket");
Class<?> playerInfoDataClass = BukkitReflection.getClass(
"network.protocol.game.ClientboundPlayerInfoUpdatePacket$Entry", // Mojang
"network.protocol.game.ClientboundPlayerInfoUpdatePacket$b" // Bukkit
);
newPlayerInfo = PlayerInfoClass.getConstructor(EnumSet.class, Collection.class);
ACTION = ReflectionUtils.getOnlyField(PlayerInfoClass, EnumSet.class);
loadSharedContent(playerInfoDataClass, EnumGamemodeClass);
newPlayerInfoData = playerInfoDataClass.getConstructor(UUID.class, GameProfile.class, boolean.class, int.class, EnumGamemodeClass, IChatBaseComponent, RemoteChatSession$Data);
PlayerInfoData_Listed = ReflectionUtils.getOnlyField(playerInfoDataClass, boolean.class);
PlayerInfoData_GameMode = ReflectionUtils.getOnlyField(playerInfoDataClass, EnumGamemodeClass);
PlayerInfoData_RemoteChatSession = ReflectionUtils.getOnlyField(playerInfoDataClass, RemoteChatSession$Data);
PlayerInfoData_UUID = ReflectionUtils.getOnlyField(playerInfoDataClass, UUID.class);
newRemovePacket = BukkitReflection.getClass("network.protocol.game.ClientboundPlayerInfoRemovePacket").getConstructor(List.class);
actionAddPlayer = Enum.valueOf(ActionClass, Action.ADD_PLAYER.name());
actionUpdateDisplayName = Enum.valueOf(ActionClass, Action.UPDATE_DISPLAY_NAME.name());
actionUpdateLatency = Enum.valueOf(ActionClass, Action.UPDATE_LATENCY.name());
actionToEnumSet.put(Action.ADD_PLAYER, EnumSet.allOf(ActionClass));
actionToEnumSet.put(Action.UPDATE_GAME_MODE, EnumSet.of(Enum.valueOf(ActionClass, Action.UPDATE_GAME_MODE.name())));
actionToEnumSet.put(Action.UPDATE_DISPLAY_NAME, EnumSet.of(actionUpdateDisplayName));
actionToEnumSet.put(Action.UPDATE_LATENCY, EnumSet.of(actionUpdateLatency));
actionToEnumSet.put(Action.UPDATE_LISTED, EnumSet.of(Enum.valueOf(ActionClass, Action.UPDATE_LISTED.name())));
}
@Override
@SneakyThrows
public void removeEntry(@NonNull UUID entry) {
packetSender.sendPacket(player.getPlayer(), newRemovePacket.newInstance(Collections.singletonList(entry)));
}
@Override
public void updateListed(@NonNull UUID entry, boolean listed) {
packetSender.sendPacket(player.getPlayer(),
createPacket(Action.UPDATE_LISTED, entry, "", null, listed, 0, 0, null));
}
@SneakyThrows
@NonNull
@Override
public Object createPacket(@NonNull Action action, @NonNull UUID id, @NonNull String name, @Nullable Skin skin,
boolean listed, int latency, int gameMode, @Nullable Object displayName) {
Object packet = newPlayerInfo.newInstance(actionToEnumSet.get(action), Collections.emptyList());
PLAYERS.set(packet, Collections.singletonList(newPlayerInfoData.newInstance(
id,
action == Action.ADD_PLAYER ? createProfile(id, name, skin) : null,
listed,
latency,
gameModes[gameMode],
displayName,
null
)));
return packet;
}
@Override
@SneakyThrows
public void onPacketSend(@NonNull Object packet) {
if (!(PlayerInfoClass.isInstance(packet))) return;
EnumSet<?> actions = (EnumSet<?>) ACTION.get(packet);
List<Object> updatedList = new ArrayList<>();
boolean rewritePacket = false;
for (Object nmsData : (List<?>) PLAYERS.get(packet)) {
boolean rewriteEntry = false;
UUID id = (UUID) PlayerInfoData_UUID.get(nmsData);
GameProfile profile = (GameProfile) PlayerInfoData_Profile.get(nmsData);
Object displayName = PlayerInfoData_DisplayName.get(nmsData);
int latency = PlayerInfoData_Latency.getInt(nmsData);
if (actions.contains(actionUpdateDisplayName)) {
Object expectedName = getExpectedDisplayName(id);
if (expectedName != null && expectedName != displayName) {
displayName = expectedName;
rewriteEntry = rewritePacket = true;
}
}
if (actions.contains(actionUpdateLatency)) {
int newLatency = TAB.getInstance().getFeatureManager().onLatencyChange(player, id, latency);
if (newLatency != latency) {
latency = newLatency;
rewriteEntry = rewritePacket = true;
}
}
if (actions.contains(actionAddPlayer)) {
TAB.getInstance().getFeatureManager().onEntryAdd(player, id, profile.getName());
}
// 1.19.3 is using records, which do not allow changing final fields, need to rewrite the list entirely
updatedList.add(rewriteEntry ? newPlayerInfoData.newInstance(
id,
profile,
PlayerInfoData_Listed.getBoolean(nmsData),
latency,
PlayerInfoData_GameMode.get(nmsData),
displayName,
PlayerInfoData_RemoteChatSession.get(nmsData)) : nmsData);
}
if (rewritePacket) PLAYERS.set(packet, updatedList);
}
}
@@ -0,0 +1,123 @@
package me.neznamy.tab.platforms.bukkit.tablist;
import lombok.NonNull;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
import me.neznamy.tab.platforms.bukkit.nms.PacketSender;
import me.neznamy.tab.shared.Limitations;
import me.neznamy.tab.shared.chat.TabComponent;
import me.neznamy.tab.shared.util.ReflectionUtils;
import me.neznamy.tab.shared.util.TriFunctionWithException;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* TabList handler for 1.7- servers using packets.
*/
public class PacketTabList17 extends TabListBase<String> {
private static TriFunctionWithException<String, Boolean, Integer, Object> newPacket;
private static PacketSender packetSender;
/** Because entries are identified by names and not uuids on 1.7- */
@NonNull
private final Map<UUID, String> userNames = new HashMap<>();
@NonNull
private final Map<UUID, String> displayNames = new HashMap<>();
/**
* Constructs new instance with given player.
*
* @param player
* Player this tablist will belong to.
*/
public PacketTabList17(@NonNull BukkitTabPlayer player) {
super(player);
}
/**
* Attempts to load all required NMS classes, fields and methods.
* If anything fails, throws an exception.
*
* @throws ReflectiveOperationException
* If something goes wrong
*/
public static void load() throws ReflectiveOperationException {
Class<?> PlayerInfoClass = BukkitReflection.getClass("PacketPlayOutPlayerInfo", "Packet201PlayerInfo");
try {
Constructor<?> newPlayerInfo = PlayerInfoClass.getConstructor(String.class, boolean.class, int.class);
newPacket = newPlayerInfo::newInstance;
} catch (NoSuchMethodException e) {
// 1.7.10 spigot with protocol hack
Constructor<?> newPlayerInfo = PlayerInfoClass.getConstructor();
Field USERNAME = ReflectionUtils.getField(PlayerInfoClass, "username");
Field ACTION = ReflectionUtils.getField(PlayerInfoClass, "action");
Field PING = ReflectionUtils.getField(PlayerInfoClass, "ping");
newPacket = (name, addOrUpdate, latency) -> {
Object packet = newPlayerInfo.newInstance();
USERNAME.set(packet, name);
ACTION.set(packet, addOrUpdate ? 3 : 4);
PING.set(packet, latency);
return packet;
};
}
packetSender = new PacketSender();
}
@Override
@SneakyThrows
public void removeEntry(@NonNull UUID entry) {
if (!displayNames.containsKey(entry)) return; // Entry not tracked by TAB
packetSender.sendPacket(player.getPlayer(), newPacket.apply(displayNames.get(entry), false, 0));
userNames.remove(entry);
displayNames.remove(entry);
}
@Override
@SneakyThrows
public void updateDisplayName0(@NonNull UUID entry, @Nullable String displayName) {
if (!displayNames.containsKey(entry)) return; // Entry not tracked by TAB
packetSender.sendPacket(player.getPlayer(), newPacket.apply(displayNames.get(entry), false, 0));
addEntry0(entry, userNames.get(entry), null, false, 0, 0, displayName);
}
@Override
@SneakyThrows
public void updateLatency(@NonNull UUID entry, int latency) {
if (!displayNames.containsKey(entry)) return; // Entry not tracked by TAB
packetSender.sendPacket(player.getPlayer(), newPacket.apply(displayNames.get(entry), true, latency));
}
@Override
public void updateGameMode(@NonNull UUID entry, int gameMode) {
// Added in 1.8
}
@Override
public void updateListed(@NonNull UUID entry, boolean listed) {
// Added in 1.19.3
}
@Override
@SneakyThrows
public void addEntry0(@NonNull UUID id, @NonNull String name, @Nullable Skin skin, boolean listed, int latency, int gameMode, @Nullable String displayName) {
String display = displayName == null ? name : displayName;
packetSender.sendPacket(player.getPlayer(), newPacket.apply(display, true, latency));
userNames.put(id, name);
displayNames.put(id, display);
}
@Override
public String toComponent(@NonNull TabComponent component) {
String name = component.toLegacyText();
if (name.length() > Limitations.MAX_DISPLAY_NAME_LENGTH_1_7) name = name.substring(0, Limitations.MAX_DISPLAY_NAME_LENGTH_1_7);
return name;
}
}
@@ -0,0 +1,227 @@
package me.neznamy.tab.platforms.bukkit.tablist;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
import lombok.NonNull;
import lombok.Setter;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.platforms.bukkit.BukkitUtils;
import me.neznamy.tab.platforms.bukkit.nms.ComponentConverter;
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
import me.neznamy.tab.platforms.bukkit.nms.PacketSender;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.*;
/**
* TabList handler for 1.8 - 1.19.2 servers using packets.
*/
@Setter
@SuppressWarnings({"unchecked", "rawtypes"})
public class PacketTabList18 extends TabListBase<Object> {
protected static Class<?> PlayerInfoClass;
protected static Constructor<?> newPlayerInfo;
protected static Field ACTION;
protected static Field PLAYERS;
protected static Class<Enum> ActionClass;
protected static Constructor<?> newPlayerInfoData;
protected static Field PlayerInfoData_Profile;
protected static Field PlayerInfoData_Latency;
protected static Field PlayerInfoData_DisplayName;
protected static Object[] gameModes;
protected static PacketSender packetSender;
/**
* Constructs new instance with given player.
*
* @param player
* Player this tablist will belong to.
*/
public PacketTabList18(@NonNull BukkitTabPlayer player) {
super(player);
}
/**
* Attempts to load all required NMS classes, fields and methods.
* If anything fails, throws an exception.
*
* @throws ReflectiveOperationException
* If something goes wrong
*/
public static void load() throws ReflectiveOperationException {
Class<Enum> EnumGamemodeClass = (Class<Enum>) BukkitReflection.getClass("world.level.GameType",
"world.level.EnumGamemode", "EnumGamemode", "WorldSettings$EnumGamemode");
ActionClass = (Class<Enum>) BukkitReflection.getClass(
"network.protocol.game.ClientboundPlayerInfoPacket$Action", // Mojang 1.17 - 1.19.2
"network.protocol.game.PacketPlayOutPlayerInfo$EnumPlayerInfoAction", // Bukkit 1.17 - 1.19.2
"PacketPlayOutPlayerInfo$EnumPlayerInfoAction", // Bukkit 1.8.1 - 1.16.5
"EnumPlayerInfoAction" // Bukkit 1.8.0
);
PlayerInfoClass = BukkitReflection.getClass("network.protocol.game.ClientboundPlayerInfoUpdatePacket",
"network.protocol.game.ClientboundPlayerInfoPacket",
"network.protocol.game.PacketPlayOutPlayerInfo", "PacketPlayOutPlayerInfo");
Class<?> playerInfoDataClass = BukkitReflection.getClass(
"network.protocol.game.ClientboundPlayerInfoPacket$PlayerUpdate", // Mojang 1.17 - 1.19.2
"network.protocol.game.PacketPlayOutPlayerInfo$PlayerInfoData", // Bukkit 1.17 - 1.19.2
"PacketPlayOutPlayerInfo$PlayerInfoData", // Bukkit 1.8.1 - 1.16.5
"PlayerInfoData" // Bukkit 1.8.0
);
Class<?> classType = BukkitReflection.getMinorVersion() >= 17 ? Collection.class : Iterable.class;
newPlayerInfo = PlayerInfoClass.getConstructor(ActionClass, classType);
ACTION = ReflectionUtils.getOnlyField(PlayerInfoClass, ActionClass);
loadSharedContent(playerInfoDataClass, EnumGamemodeClass);
newPlayerInfoData = playerInfoDataClass.getConstructors()[0]; // #1105, a specific 1.8.8 fork has 2 constructors
}
protected static void loadSharedContent(Class<?> infoData, Class<Enum> gameMode) throws ReflectiveOperationException {
Class<?> IChatBaseComponent = BukkitReflection.getClass("network.chat.Component", "network.chat.IChatBaseComponent", "IChatBaseComponent");
PLAYERS = ReflectionUtils.getOnlyField(PlayerInfoClass, List.class);
PlayerInfoData_Profile = ReflectionUtils.getOnlyField(infoData, GameProfile.class);
PlayerInfoData_Latency = ReflectionUtils.getOnlyField(infoData, int.class);
PlayerInfoData_DisplayName = ReflectionUtils.getOnlyField(infoData, IChatBaseComponent);
gameModes = new Object[] {
Enum.valueOf(gameMode, "SURVIVAL"),
Enum.valueOf(gameMode, "CREATIVE"),
Enum.valueOf(gameMode, "ADVENTURE"),
Enum.valueOf(gameMode, "SPECTATOR")
};
packetSender = new PacketSender();
ComponentConverter.ensureAvailable();
try {
skinData = new SkinData();
} catch (Exception e) {
BukkitUtils.compatibilityError(e, "getting player's game profile", null,
"Player skins not working in layout feature");
}
}
@Override
public void removeEntry(@NonNull UUID entry) {
packetSender.sendPacket(player.getPlayer(),
createPacket(Action.REMOVE_PLAYER, entry, "", null, false, 0, 0, null));
}
@Override
public void updateDisplayName0(@NonNull UUID entry, @Nullable Object displayName) {
packetSender.sendPacket(player.getPlayer(),
createPacket(Action.UPDATE_DISPLAY_NAME, entry, "", null, false, 0, 0, displayName));
}
@Override
public void updateLatency(@NonNull UUID entry, int latency) {
packetSender.sendPacket(player.getPlayer(),
createPacket(Action.UPDATE_LATENCY, entry, "", null, false, latency, 0, null));
}
@Override
public void updateGameMode(@NonNull UUID entry, int gameMode) {
packetSender.sendPacket(player.getPlayer(),
createPacket(Action.UPDATE_GAME_MODE, entry, "", null, false, 0, gameMode, null));
}
@Override
public void updateListed(@NonNull UUID entry, boolean listed) {
// Added in 1.19.3
}
@Override
public void addEntry0(@NonNull UUID id, @NonNull String name, @Nullable Skin skin, boolean listed, int latency, int gameMode, @Nullable Object displayName) {
packetSender.sendPacket(player.getPlayer(),
createPacket(Action.ADD_PLAYER, id, name, skin, listed, latency, gameMode, displayName));
}
/**
* Creates packet from given parameters.
*
* @param action
* Packet action
* @param id
* Entry UUID
* @param name
* Entry name
* @param skin
* Entry skin
* @param listed
* Whether entry should be listed or not
* @param latency
* Entry latency
* @param gameMode
* Entry game mode
* @param displayName
* Entry display name
* @return Packet from given parameters
*/
@SneakyThrows
@NonNull
public Object createPacket(@NonNull Action action, @NonNull UUID id, @NonNull String name, @Nullable Skin skin,
boolean listed, int latency, int gameMode, @Nullable Object displayName) {
Object packet = newPlayerInfo.newInstance(Enum.valueOf(ActionClass, action.name()), Collections.emptyList());
List<Object> parameters = new ArrayList<>();
if (newPlayerInfoData.getParameterTypes()[0] == PlayerInfoClass) {
parameters.add(packet);
}
parameters.add(createProfile(id, name, skin));
parameters.add(latency);
parameters.add(gameModes[gameMode]);
parameters.add(displayName);
if (BukkitReflection.getMinorVersion() >= 19) parameters.add(null);
PLAYERS.set(packet, Collections.singletonList(newPlayerInfoData.newInstance(parameters.toArray())));
return packet;
}
/**
* Creates GameProfile from given parameters.
*
* @param id
* Profile ID
* @param name
* Profile name
* @param skin
* Player skin
* @return GameProfile from given parameters
*/
@NonNull
public GameProfile createProfile(@NonNull UUID id, @NonNull String name, @Nullable Skin skin) {
GameProfile profile = new GameProfile(id, name);
if (skin != null) {
profile.getProperties().put(TabList.TEXTURES_PROPERTY,
new Property(TabList.TEXTURES_PROPERTY, skin.getValue(), skin.getSignature()));
}
return profile;
}
@Override
@SneakyThrows
public void onPacketSend(@NonNull Object packet) {
if (!(PlayerInfoClass.isInstance(packet))) return;
String action = ACTION.get(packet).toString();
for (Object nmsData : (List<?>) PLAYERS.get(packet)) {
GameProfile profile = (GameProfile) PlayerInfoData_Profile.get(nmsData);
UUID id = profile.getId();
if (action.equals(Action.UPDATE_DISPLAY_NAME.name()) || action.equals(Action.ADD_PLAYER.name())) {
Object expectedName = getExpectedDisplayName(id);
if (expectedName != null) PlayerInfoData_DisplayName.set(nmsData, expectedName);
}
if (action.equals(Action.UPDATE_LATENCY.name()) || action.equals(Action.ADD_PLAYER.name())) {
int latency = TAB.getInstance().getFeatureManager().onLatencyChange(player, id, PlayerInfoData_Latency.getInt(nmsData));
PlayerInfoData_Latency.set(nmsData, latency);
}
if (action.equals(Action.ADD_PLAYER.name())) {
TAB.getInstance().getFeatureManager().onEntryAdd(player, id, profile.getName());
}
}
}
}
@@ -0,0 +1,60 @@
package me.neznamy.tab.platforms.bukkit.tablist;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Method;
import java.util.Collection;
/**
* Class for getting skin of players.
*/
public class SkinData {
private final Method getHandle = BukkitReflection.getBukkitClass("entity.CraftPlayer").getMethod("getHandle");
// There is only supposed to be one, however there are exceptions:
// #1 - CatServer adds another method
// #2 - Random mods may perform deep hack into the server and add another one (see #1089)
// Get first and hope for the best, alternatively players may not have correct skins in layout, but who cares
private final Method getProfile = ReflectionUtils.getMethods(BukkitReflection.getClass(
"world.entity.player.Player", "world.entity.player.EntityHuman", "EntityHuman"), GameProfile.class).get(0);
/**
* Constructs new instance and loads NMS fields. If it fails, throws an Exception.
*
* @throws ReflectiveOperationException
* If something goes wrong
*/
public SkinData() throws ReflectiveOperationException {}
/**
* Returns player's skin. If server is in offline mode, returns {@code null}.
*
* @param player
* Player to get skin of
* @return Player's skin or {@code null}.
*/
@Nullable
@SneakyThrows
public TabList.Skin getSkin(@NotNull Player player) {
Collection<Property> col = ((GameProfile) getProfile.invoke(getHandle.invoke(player))).getProperties().get(TabList.TEXTURES_PROPERTY);
if (col.isEmpty()) return null; //offline mode
Property property = col.iterator().next();
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());
}
}
}
@@ -0,0 +1,99 @@
package me.neznamy.tab.platforms.bukkit.tablist;
import lombok.Getter;
import lombok.NonNull;
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.platform.TabList;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.UUID;
import java.util.function.Function;
/**
* Base TabList class for all implementations.
*
* @param <C>
* Component class
*/
public abstract class TabListBase<C> extends TabList<BukkitTabPlayer, C> {
/** Instance function */
@Getter
private static Function<BukkitTabPlayer, TabListBase<?>> instance;
@Nullable
protected static SkinData skinData;
@Nullable
private static PacketHeaderFooter headerFooter;
/**
* Constructs new instance.
*
* @param player
* Player this tablist will belong to
*/
protected TabListBase(@NotNull BukkitTabPlayer player) {
super(player);
}
/**
* Finds the best available instance for current server software.
*/
public static void findInstance() {
try {
if (BukkitReflection.is1_19_3Plus()) {
PacketTabList1193.loadNew();
instance = PacketTabList1193::new;
} else if (BukkitReflection.getMinorVersion() >= 8) {
PacketTabList18.load();
instance = PacketTabList18::new;
} else {
PacketTabList17.load();
instance = PacketTabList17::new;
}
} catch (Exception e) {
BukkitUtils.compatibilityError(e, "tablist entry management", "Bukkit API",
"Layout feature will not work",
"Prevent-spectator-effect feature will not work",
"Ping spoof feature will not work",
"Tablist formatting missing anti-override",
"Tablist formatting not supporting relational placeholders");
instance = BukkitTabList::new;
}
try {
if (BukkitReflection.getMinorVersion() >= 8) {
headerFooter = new PacketHeaderFooter();
}
} catch (Exception e) {
BukkitUtils.compatibilityError(e, "sending header/footer", null,
"Header/footer will not work");
}
}
@Override
public void setPlayerListHeaderFooter0(@NonNull Object header, @NonNull Object footer) {
if (headerFooter != null) headerFooter.set(player, header, footer);
}
@Override
public boolean containsEntry(@NonNull UUID entry) {
return true; // TODO?
}
/**
* Returns player's skin. If NMS fields did not load or server is in
* offline mode, returns {@code null}.
*
* @return Player's skin or {@code null}.
*/
@Nullable
public Skin getSkin() {
if (skinData == null) return null;
return skinData.getSkin(player.getPlayer());
}
}
+1 -4
View File
@@ -7,7 +7,7 @@ load: POSTWORLD
author: @author@
website: "@website@"
depend: []
softdepend: [PlaceholderAPI, LuckPerms, LibsDisguises, ViaVersion, ViaRewind, floodgate]
softdepend: [PlaceholderAPI, LuckPerms, LibsDisguises, ViaVersion, ViaRewind, floodgate, Vault]
loadbefore: []
prefix: TAB
libraries: []
@@ -49,9 +49,6 @@ permissions:
tab.change.*:
description: Allows player to change property using command
default: op
tab.width:
description: Allows player to run /tab width
default: op
tab.announce.bar:
description: Allows player to run /tab announce bar
default: op
+1 -1
View File
@@ -1,7 +1,7 @@
dependencies {
implementation(projects.shared)
implementation("org.bstats:bstats-bungeecord:3.0.1")
compileOnly("net.md-5:bungeecord-proxy:1.20-R0.1-SNAPSHOT")
compileOnly("net.md-5:bungeecord-proxy:1.20-R0.3-SNAPSHOT")
compileOnly("com.github.limework.redisbungee:RedisBungee-Bungee:0.11.0")
compileOnly("com.github.LeonMangler:PremiumVanishAPI:2.8.8")
}
@@ -1,7 +1,8 @@
package me.neznamy.tab.platforms.bungeecord;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.shared.platform.bossbar.BossBar;
import me.neznamy.tab.shared.chat.TabComponent;
import me.neznamy.tab.shared.platform.BossBar;
import me.neznamy.tab.api.bossbar.BarColor;
import me.neznamy.tab.api.bossbar.BarStyle;
import org.jetbrains.annotations.NotNull;
@@ -28,13 +29,15 @@ public class BungeeBossBar implements BossBar {
@NotNull
private final Map<UUID, Integer> styles = new HashMap<>();
/** Flag tracking whether boss bars should be frozen or not */
private boolean frozen;
@Override
public void create(@NotNull UUID id, @NotNull String title, float progress, @NotNull BarColor color, @NotNull BarStyle style) {
if (player.getVersion().getMinorVersion() < 9) return;
if (frozen) return; // Server switch
net.md_5.bungee.protocol.packet.BossBar packet = new net.md_5.bungee.protocol.packet.BossBar(id, 0);
packet.setHealth(progress);
BungeeMultiVersion.setTitle(packet, title, player.getVersion());
packet.setTitle(TabComponent.optimized(title).convert(player.getVersion()));
packet.setColor(color.ordinal());
packet.setDivision(style.ordinal());
player.sendPacket(packet);
@@ -45,17 +48,15 @@ public class BungeeBossBar implements BossBar {
@Override
public void update(@NotNull UUID id, @NotNull String title) {
if (player.getVersion().getMinorVersion() < 9) return;
if (frozen) return; // Server switch
net.md_5.bungee.protocol.packet.BossBar packet = new net.md_5.bungee.protocol.packet.BossBar(id, 3);
BungeeMultiVersion.setTitle(packet, title, player.getVersion());
packet.setTitle(TabComponent.optimized(title).convert(player.getVersion()));
player.sendPacket(packet);
}
@Override
public void update(@NotNull UUID id, float progress) {
if (player.getVersion().getMinorVersion() < 9) return;
if (frozen) return; // Server switch
net.md_5.bungee.protocol.packet.BossBar packet = new net.md_5.bungee.protocol.packet.BossBar(id, 2);
packet.setHealth(progress);
player.sendPacket(packet);
@@ -63,8 +64,7 @@ public class BungeeBossBar implements BossBar {
@Override
public void update(@NotNull UUID id, @NotNull BarStyle style) {
if (player.getVersion().getMinorVersion() < 9) return;
if (frozen) return; // Server switch
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));
@@ -75,8 +75,7 @@ public class BungeeBossBar implements BossBar {
@Override
public void update(@NotNull UUID id, @NotNull BarColor color) {
if (player.getVersion().getMinorVersion() < 9) return;
if (frozen) return; // Server switch
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());
@@ -87,11 +86,20 @@ public class BungeeBossBar implements BossBar {
@Override
public void remove(@NotNull UUID id) {
if (player.getVersion().getMinorVersion() < 9) return;
if (frozen) return; // Server switch
player.sendPacket(new net.md_5.bungee.protocol.packet.BossBar(id, 1));
colors.remove(id);
styles.remove(id);
}
}
@Override
public void freeze() {
frozen = true;
}
@Override
public void unfreeze() {
frozen = false;
}
}
@@ -17,26 +17,49 @@ import org.jetbrains.annotations.NotNull;
/**
* The core for BungeeCord forwarding events into all enabled features
*/
public class BungeeEventListener extends EventListener<ProxiedPlayer> implements Listener {
public class BungeeEventListener implements EventListener<ProxiedPlayer>, Listener {
/**
* Listens to player disconnecting from the server.
*
* @param e
* Disconnect event
*/
@EventHandler
public void onQuit(PlayerDisconnectEvent e) {
quit(e.getPlayer().getUniqueId());
}
/**
* Listens to player connecting to a backend server. This handles
* both initial connections and server switch.
*
* @param e
* Server switch event
*/
@EventHandler
public void onSwitch(ServerSwitchEvent e) {
TAB tab = TAB.getInstance();
if (tab.isPluginDisabled()) return;
// Avoid 1.20.3+ client crash on scoreboard packets, do it sync to prevent packet being sent after event, but before processing
// Avoid 1.20.5+ client disconnect with "Network Protocol Error"
TabPlayer p = tab.getPlayer(e.getPlayer().getUniqueId());
if (p != null && p.getVersion().getNetworkId() >= ProtocolVersion.V1_20_2.getNetworkId()) {
p.getScoreboard().freeze();
p.getBossBar().freeze();
}
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
// Things will get cleared immediately after, so no point in sending it, also someone said it fixed some warn from Geyser
// Sending these packets before login packet will also crash the client on 1.20.3
if (player.getVersion().getNetworkId() >= ProtocolVersion.V1_20_2.getNetworkId()) {
player.getScoreboard().freeze();
player.getBossBar().freeze();
}
tab.getFeatureManager().onJoin(player);
@@ -51,6 +74,12 @@ public class BungeeEventListener extends EventListener<ProxiedPlayer> implements
});
}
/**
* Listens to command execute event to potentially cancel it.
*
* @param e
* Command execute event
*/
@EventHandler
public void onCommand(ChatEvent e) {
if (e.isCommand() && command(((ProxiedPlayer)e.getSender()).getUniqueId(), e.getMessage())) {
@@ -58,8 +87,14 @@ public class BungeeEventListener extends EventListener<ProxiedPlayer> implements
}
}
/**
* Listens to plugin messages.
*
* @param e
* Plugin message event
*/
@EventHandler
public void on(PluginMessageEvent e) {
public void onPluginMessage(PluginMessageEvent e) {
if (!e.getTag().equals(TabConstants.PLUGIN_MESSAGE_CHANNEL_NAME)) return;
if (e.getReceiver() instanceof ProxiedPlayer) {
e.setCancelled(true);
@@ -1,200 +0,0 @@
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 IChatBaseComponent 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(title, version), healthDisplay, action);
} else {
return new ScoreboardObjective(objective, title.toString(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;
}
}
@@ -1,36 +1,29 @@
package me.neznamy.tab.platforms.bungeecord;
import com.google.common.collect.Lists;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelDuplexHandler;
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;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.TAB;
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.platform.TabPlayer;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.md_5.bungee.UserConnection;
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 net.md_5.bungee.protocol.packet.Login;
import net.md_5.bungee.protocol.packet.ScoreboardDisplay;
import net.md_5.bungee.protocol.packet.ScoreboardObjective;
import net.md_5.bungee.protocol.packet.Team;
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;
@@ -43,9 +36,6 @@ import java.util.function.Supplier;
public class BungeePipelineInjector extends NettyPipelineInjector {
/** Inaccessible bungee internals */
@Nullable
private static Field wrapperField;
@Nullable
private static Object directionData;
@@ -54,9 +44,8 @@ public class BungeePipelineInjector extends NettyPipelineInjector {
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));
getId = ReflectionUtils.setAccessible(Protocol.DirectionData.class.getDeclaredMethod("getId", Class.class, int.class));
} catch (ReflectiveOperationException exception) {
TAB.getInstance().getErrorManager().criticalError("Failed to initialize bungee internal fields", exception);
}
@@ -69,6 +58,21 @@ public class BungeePipelineInjector extends NettyPipelineInjector {
@NotNull
private final Supplier<DefinedPacket>[] extraPacketSuppliers = new Supplier[]{Team::new, ScoreboardDisplay::new, ScoreboardObjective::new};
/** Whether ByteBuf deserialization should be enabled or not */
protected boolean byteBufDeserialization;
/**
* Constructs new instance of the feature
*/
public BungeePipelineInjector() {
super("inbound-boss");
boolean antiOverrideTeams = config().getBoolean("scoreboard-teams.enabled", true) &&
config().getBoolean("scoreboard-teams.anti-override", true);
boolean respectOtherScoreboardPlugins = config().getBoolean("scoreboard.enabled", false) &&
config().getBoolean("scoreboard.respect-other-plugins", true);
byteBufDeserialization = antiOverrideTeams || respectOtherScoreboardPlugins;
}
@Override
@NotNull
public Function<TabPlayer, ChannelDuplexHandler> getChannelFunction() {
@@ -76,117 +80,9 @@ public class BungeePipelineInjector extends NettyPipelineInjector {
}
@Override
@Nullable
@NotNull
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 (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,
((Number) ScoreboardDisplay.class.getMethod("getPosition").invoke(packet)).intValue(),
((ScoreboardDisplay) packet).getName()
);
}
@Override
public void onObjective(@NotNull TabPlayer player, @NotNull Object packet) {
TAB.getInstance().getFeatureManager().onObjective(player,
((ScoreboardObjective) packet).getAction(), ((ScoreboardObjective) packet).getName());
}
@Override
public boolean isDisplayObjective(@NotNull Object packet) {
return packet instanceof ScoreboardDisplay;
}
@Override
public boolean isObjective(@NotNull Object packet) {
return packet instanceof ScoreboardObjective;
}
@Override
public boolean isTeam(@NotNull Object packet) {
return packet instanceof Team;
}
@Override
public boolean isPlayerInfo(@NotNull Object packet) {
return packet instanceof PlayerListItem || packet instanceof PlayerListItemUpdate;
}
@Override
public void modifyPlayers(@NotNull Object team) {
if (TAB.getInstance().getNameTagManager() == null) return;
Team packet = (Team) team;
if (packet.getMode() == 1 || packet.getMode() == 2 || packet.getMode() == 4) return;
Collection<String> col = Lists.newArrayList(packet.getPlayers());
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.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());
}
}
RedisSupport redis = TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.REDIS_BUNGEE);
if (redis != null) {
RedisTeams teams = redis.getRedisTeams();
if (teams != null) {
for (RedisPlayer p : redis.getRedisPlayers().values()) {
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());
}
}
}
}
packet.setPlayers(col.toArray(new String[0]));
}
@Override
public void onPlayerInfo(@NotNull TabPlayer receiver, @NotNull Object packet) {
if (packet instanceof PlayerListItemUpdate) {
PlayerListItemUpdate update = (PlayerListItemUpdate) packet;
for (PlayerListItem.Item item : update.getItems()) {
if (update.getActions().contains(PlayerListItemUpdate.Action.UPDATE_DISPLAY_NAME)) {
IChatBaseComponent newDisplayName = TAB.getInstance().getFeatureManager().onDisplayNameChange(receiver, item.getUuid());
if (newDisplayName != null) 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 {
PlayerListItem listItem = (PlayerListItem) packet;
for (PlayerListItem.Item item : listItem.getItems()) {
if (listItem.getAction() == PlayerListItem.Action.UPDATE_DISPLAY_NAME || listItem.getAction() == PlayerListItem.Action.ADD_PLAYER) {
IChatBaseComponent newDisplayName = TAB.getInstance().getFeatureManager().onDisplayNameChange(receiver, item.getUuid());
if (newDisplayName != null) 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());
}
}
}
return ((UserConnection) ((BungeeTabPlayer) player).getPlayer()).getCh().getHandle();
}
@Override
@@ -194,19 +90,12 @@ public class BungeePipelineInjector extends NettyPipelineInjector {
return packet instanceof Login;
}
/**
* Constructs new instance of the feature
*/
public BungeePipelineInjector() {
super("inbound-boss");
}
/**
* Channel duplex handler override if features using packets that must be
* deserialized manually are used. If they are disabled, deserialization is
* disabled for better performance.
*/
public class DeserializableBungeeChannelDuplexHandler extends TabChannelDuplexHandler {
private class DeserializableBungeeChannelDuplexHandler extends TabChannelDuplexHandler {
/**
* Constructs new instance with given player
@@ -214,7 +103,7 @@ public class BungeePipelineInjector extends NettyPipelineInjector {
* @param player
* player to inject
*/
public DeserializableBungeeChannelDuplexHandler(@NotNull TabPlayer player) {
private DeserializableBungeeChannelDuplexHandler(@NotNull TabPlayer player) {
super(player);
}
@@ -240,6 +129,10 @@ public class BungeePipelineInjector extends NettyPipelineInjector {
try {
int packetId = buf.readByte();
for (int i=0; i<extraPacketClasses.length; i++) {
ChannelWrapper ch = ((UserConnection) ((BungeeTabPlayer) player).getPlayer()).getCh();
if (!ch.getEncodeProtocol().TO_CLIENT.hasPacket(extraPacketClasses[i], ((ProxiedPlayer)player.getPlayer()).getPendingConnection().getVersion())) {
continue;
}
if (packetId == getPacketId(((BungeeTabPlayer)player).getPlayer().getPendingConnection().getVersion(), extraPacketClasses[i])) {
DefinedPacket packet = extraPacketSuppliers[i].get();
packet.read(buf, null, ((ProxiedPlayer)player.getPlayer()).getPendingConnection().getVersion());
@@ -263,15 +156,10 @@ public class BungeePipelineInjector extends NettyPipelineInjector {
* packet class
* @return packet ID
*/
@SneakyThrows
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;
}
return (int) getId.invoke(directionData, clazz, protocolVersion);
}
}
}
@@ -1,18 +1,21 @@
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.platforms.bungeecord.hook.BungeePremiumVanishHook;
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.chat.*;
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.hook.PremiumVanishHook;
import me.neznamy.tab.shared.proxy.ProxyPlatform;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.ProxyConfig;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import org.bstats.bungeecord.Metrics;
import org.bstats.charts.SimplePie;
@@ -20,19 +23,42 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
/**
* BungeeCord implementation of Platform
*/
@AllArgsConstructor
public class BungeePlatform extends ProxyPlatform {
@NotNull
private final BungeeTAB plugin;
/**
* Constructs new instance with given plugin instance.
*
* @param plugin
* Plugin instance
*/
public BungeePlatform(@NotNull BungeeTAB plugin) {
this.plugin = plugin;
if (ProxyServer.getInstance().getPluginManager().getPlugin("PremiumVanish") != null) {
PremiumVanishHook.setInstance(new BungeePremiumVanishHook(this));
}
}
@SuppressWarnings("deprecation")
@Override
public void loadPlayers() {
ViaVersionHook.getInstance().printProxyWarn();
try {
ProxyConfig config = ProxyServer.getInstance().getConfig();
if ((boolean) config.getClass().getMethod("isDisableTabListRewrite").invoke(config)) {
logWarn(new SimpleComponent("Waterfall's \"disable_tab_list_rewrite: true\" option may cause " +
"the plugin to not work correctly. Disable it to avoid issues."));
}
} catch (Exception e) {
// Not waterfall
}
for (ProxiedPlayer p : ProxyServer.getInstance().getPlayers()) {
TAB.getInstance().addPlayer(new BungeeTabPlayer(this, p));
}
@@ -49,13 +75,13 @@ public class BungeePlatform extends ProxyPlatform {
}
@Override
public void logInfo(@NotNull IChatBaseComponent message) {
public void logInfo(@NotNull TabComponent message) {
plugin.getLogger().info(message.toLegacyText());
}
@Override
public void logWarn(@NotNull IChatBaseComponent message) {
plugin.getLogger().warning(EnumChatFormat.RED.getFormat() + message.toLegacyText());
public void logWarn(@NotNull TabComponent message) {
plugin.getLogger().warning(EnumChatFormat.RED + message.toLegacyText());
}
@Override
@@ -76,7 +102,7 @@ public class BungeePlatform extends ProxyPlatform {
@Override
public void startMetrics() {
new Metrics(plugin, 10535).addCustomChart(new SimplePie(TabConstants.MetricsChart.GLOBAL_PLAYER_LIST_ENABLED,
new Metrics(plugin, TabConstants.BSTATS_PLUGIN_ID_BUNGEE).addCustomChart(new SimplePie(TabConstants.MetricsChart.GLOBAL_PLAYER_LIST_ENABLED,
() -> TAB.getInstance().getFeatureManager().isFeatureEnabled(TabConstants.Feature.GLOBAL_PLAYER_LIST) ? "Yes" : "No"));
}
@@ -96,4 +122,37 @@ public class BungeePlatform extends ProxyPlatform {
public void registerChannel() {
ProxyServer.getInstance().registerChannel(TabConstants.PLUGIN_MESSAGE_CHANNEL_NAME);
}
@Override
public BaseComponent convertComponent(@NotNull TabComponent component, boolean modern) {
if (component instanceof SimpleComponent) return new TextComponent(component.toLegacyText());
StructuredComponent iComponent = (StructuredComponent) component;
TextComponent textComponent = new TextComponent(iComponent.getText());
ChatModifier modifier = iComponent.getModifier();
if (modifier.getColor() != null) {
if (modern) {
textComponent.setColor(ChatColor.of("#" + modifier.getColor().getHexCode()));
} else {
textComponent.setColor(ChatColor.of(modifier.getColor().getLegacyColor().name()));
}
}
if (modifier.isBold()) textComponent.setBold(true);
if (modifier.isItalic()) textComponent.setItalic(true);
if (modifier.isObfuscated()) textComponent.setObfuscated(true);
if (modifier.isStrikethrough()) textComponent.setStrikethrough(true);
if (modifier.isUnderlined()) textComponent.setUnderlined(true);
textComponent.setFont(modifier.getFont());
if (!iComponent.getExtra().isEmpty()) {
List<BaseComponent> list = new ArrayList<>();
for (StructuredComponent extra : iComponent.getExtra()) {
list.add(convertComponent(extra, modern));
}
textComponent.setExtra(list);
}
return textComponent;
}
}
@@ -1,121 +1,168 @@
package me.neznamy.tab.platforms.bungeecord;
import lombok.SneakyThrows;
import com.google.common.collect.Lists;
import lombok.NonNull;
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.chat.TabComponent;
import me.neznamy.tab.shared.platform.Scoreboard;
import net.md_5.bungee.protocol.packet.ScoreboardObjective;
import net.md_5.bungee.protocol.packet.ScoreboardScore;
import net.md_5.bungee.protocol.packet.Team;
import org.jetbrains.annotations.NotNull;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.protocol.Either;
import net.md_5.bungee.protocol.NumberFormat;
import net.md_5.bungee.protocol.packet.*;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
/**
* Scoreboard handler for BungeeCord. Because it does not offer
* any Scoreboard API and the scoreboard class it has is just a
* downstream tracker, we need to use packets.
*/
public class BungeeScoreboard extends Scoreboard<BungeeTabPlayer> {
public class BungeeScoreboard extends Scoreboard<BungeeTabPlayer, BaseComponent> {
public BungeeScoreboard(@NotNull BungeeTabPlayer player) {
/** Version with a minor team recode */
private final int TEAM_REWORK_VERSION = 13;
/**
* Constructs new instance with given parameter
*
* @param player
* Player this scoreboard will belong to
*/
public BungeeScoreboard(@NonNull BungeeTabPlayer player) {
super(player);
}
@Override
@SneakyThrows
public void setDisplaySlot(@NotNull DisplaySlot slot, @NotNull String objective) {
player.sendPacket(BungeeMultiVersion.newScoreboardDisplay(slot.ordinal(), objective));
public void setDisplaySlot0(int slot, @NonNull String objective) {
player.sendPacket(new ScoreboardDisplay(slot, objective));
}
@Override
public void registerObjective0(@NotNull String objectiveName, @NotNull String title, @NotNull HealthDisplay display) {
player.sendPacket(BungeeMultiVersion.newScoreboardObjective(
public void registerObjective0(@NonNull String objectiveName, @NonNull String title, int display,
@Nullable BaseComponent numberFormat) {
player.sendPacket(new ScoreboardObjective(
objectiveName,
IChatBaseComponent.optimizedComponent(title),
ScoreboardObjective.HealthDisplay.valueOf(display.name()),
(byte) 0,
player.getVersion()
either(title),
ScoreboardObjective.HealthDisplay.values()[display],
(byte) ObjectiveAction.REGISTER,
numberFormat == null ? null : new NumberFormat(NumberFormat.Type.FIXED, numberFormat)
));
}
@Override
public void unregisterObjective0(@NotNull String objectiveName) {
player.sendPacket(BungeeMultiVersion.newScoreboardObjective(
public void unregisterObjective0(@NonNull String objectiveName) {
player.sendPacket(new ScoreboardObjective(
objectiveName,
new IChatBaseComponent(), // Empty value instead of null to prevent NPE kick on 1.7
either(""), // Empty value instead of null to prevent NPE kick on 1.7
null,
(byte) 1,
player.getVersion()
(byte) ObjectiveAction.UNREGISTER,
null
));
}
@Override
public void updateObjective0(@NotNull String objectiveName, @NotNull String title, @NotNull HealthDisplay display) {
player.sendPacket(BungeeMultiVersion.newScoreboardObjective(
public void updateObjective0(@NonNull String objectiveName, @NonNull String title, int display,
@Nullable BaseComponent numberFormat) {
player.sendPacket(new ScoreboardObjective(
objectiveName,
IChatBaseComponent.optimizedComponent(title),
ScoreboardObjective.HealthDisplay.valueOf(display.name()),
(byte) 2,
player.getVersion()
either(title),
ScoreboardObjective.HealthDisplay.values()[display],
(byte) ObjectiveAction.UPDATE,
numberFormat == null ? null : new NumberFormat(NumberFormat.Type.FIXED, numberFormat)
));
}
@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) {
int color = 0;
if (player.getVersion().getMinorVersion() >= 13) {
color = EnumChatFormat.lastColorsOf(prefix).ordinal();
}
player.sendPacket(BungeeMultiVersion.newTeam(
public void registerTeam0(@NonNull String name, @NonNull String prefix, @NonNull String suffix,
@NonNull NameVisibility visibility, @NonNull CollisionRule collision,
@NonNull Collection<String> players, int options, @NonNull EnumChatFormat color) {
player.sendPacket(new Team(
name,
(byte) 0,
prefix,
suffix,
(byte) TeamAction.CREATE,
either(name),
either(prefix),
either(suffix),
visibility.toString(),
collision.toString(),
color,
player.getVersion().getMinorVersion() >= TEAM_REWORK_VERSION ? color.ordinal() : 0,
(byte)options,
players.toArray(new String[0]),
player.getVersion()
players.toArray(new String[0])
));
}
@Override
public void unregisterTeam0(@NotNull String name) {
public void unregisterTeam0(@NonNull String name) {
player.sendPacket(new Team(name));
}
@Override
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(BungeeMultiVersion.newTeam(
public void updateTeam0(@NonNull String name, @NonNull String prefix, @NonNull String suffix,
@NonNull NameVisibility visibility, @NonNull CollisionRule collision,
int options, @NonNull EnumChatFormat color) {
player.sendPacket(new Team(
name,
(byte) 2,
prefix,
suffix,
(byte) TeamAction.UPDATE,
either(name),
either(prefix),
either(suffix),
visibility.toString(),
collision.toString(),
color,
player.getVersion().getMinorVersion() >= TEAM_REWORK_VERSION ? color.ordinal() : 0,
(byte)options,
null,
player.getVersion()
null
));
}
@Override
public void setScore0(@NotNull String objective, @NotNull String playerName, int score) {
player.sendPacket(new ScoreboardScore(playerName, (byte) 0, objective, score));
public void setScore0(@NonNull String objective, @NonNull String scoreHolder, int score,
@Nullable BaseComponent displayName, @Nullable BaseComponent numberFormat) {
player.sendPacket(new ScoreboardScore(
scoreHolder,
(byte) ScoreAction.CHANGE,
objective,
score,
displayName,
numberFormat == null ? null : new NumberFormat(NumberFormat.Type.FIXED, numberFormat)
));
}
@Override
public void removeScore0(@NotNull String objective, @NotNull String playerName) {
player.sendPacket(new ScoreboardScore(playerName, (byte) 1, objective, 0));
public void removeScore0(@NonNull String objective, @NonNull String scoreHolder) {
if (player.getVersion().getNetworkId() >= ProtocolVersion.V1_20_3.getNetworkId()) {
player.sendPacket(new ScoreboardScoreReset(scoreHolder, objective));
} else {
player.sendPacket(new ScoreboardScore(scoreHolder, (byte) ScoreAction.REMOVE, objective, 0, null, null));
}
}
private Either<String, BaseComponent> either(@NonNull String text) {
if (player.getVersion().getMinorVersion() >= TEAM_REWORK_VERSION) {
return Either.right(TabComponent.optimized(text).convert(player.getVersion()));
} else {
return Either.left(text);
}
}
@Override
public void onPacketSend(@NonNull Object packet) {
if (packet instanceof ScoreboardDisplay) {
ScoreboardDisplay display = (ScoreboardDisplay) packet;
TAB.getInstance().getFeatureManager().onDisplayObjective(player, display.getPosition(), display.getName());
}
if (packet instanceof ScoreboardObjective) {
ScoreboardObjective objective = (ScoreboardObjective) packet;
TAB.getInstance().getFeatureManager().onObjective(player, objective.getAction(), objective.getName());
}
if (isAntiOverrideTeams() && packet instanceof Team) {
Team team = (Team) packet;
if (team.getMode() == TeamAction.UPDATE) return;
List<String> players = team.getPlayers() == null ? Collections.emptyList() : Lists.newArrayList(team.getPlayers());
team.setPlayers(onTeamPacket(team.getMode(), team.getName(), players).toArray(new String[0]));
}
}
}
@@ -12,9 +12,9 @@ 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 " +
"(released on December 7th, 2022) and up (or an equivalent fork) to work."));
if (!ReflectionUtils.classExists("net.md_5.bungee.protocol.packet.ScoreboardScoreReset")) {
getLogger().warning(EnumChatFormat.RED + "The plugin requires BungeeCord build #1774 " +
"(released on November 25th, 2023) and up (or an equivalent fork) to work.");
return;
}
TAB.create(new BungeePlatform(this));
@@ -13,6 +13,9 @@ import org.jetbrains.annotations.NotNull;
import java.util.Collections;
/**
* Command handler for plugin's command on BungeeCord.
*/
public class BungeeTabCommand extends Command implements TabExecutor {
/**
@@ -1,22 +1,21 @@
package me.neznamy.tab.platforms.bungeecord;
import de.myzelyam.api.vanish.BungeeVanishAPI;
import lombok.Getter;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bungeecord.tablist.BungeeTabList;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.platforms.bungeecord.tablist.BungeeTabList1193;
import me.neznamy.tab.platforms.bungeecord.tablist.BungeeTabList17;
import me.neznamy.tab.platforms.bungeecord.tablist.BungeeTabList18;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.chat.TabComponent;
import me.neznamy.tab.shared.hook.PremiumVanishHook;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.bossbar.BossBar;
import me.neznamy.tab.shared.platform.BossBar;
import me.neznamy.tab.shared.proxy.ProxyTabPlayer;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.UserConnection;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.chat.ComponentSerializer;
import net.md_5.bungee.connection.InitialHandler;
import net.md_5.bungee.connection.LoginResult;
import net.md_5.bungee.protocol.DefinedPacket;
@@ -30,22 +29,19 @@ import org.jetbrains.annotations.Nullable;
@Getter
public class BungeeTabPlayer extends ProxyTabPlayer {
/** Flag tracking plugin presence */
private static final boolean premiumVanish = ProxyServer.getInstance().getPluginManager().getPlugin("PremiumVanish") != null;
/** Player's scoreboard */
@NotNull
private final Scoreboard<BungeeTabPlayer> scoreboard = new BungeeScoreboard(this);
private final BungeeScoreboard scoreboard = new BungeeScoreboard(this);
/** Player's tab list based on version */
@NotNull
private final TabList tabList1_7 = new BungeeTabList17(this);
private final BungeeTabList tabList1_7 = new BungeeTabList17(this);
@NotNull
private final TabList tabList1_8 = new BungeeTabList18(this);
private final BungeeTabList tabList1_8 = new BungeeTabList18(this);
@NotNull
private final TabList tabList1_19_3 = new BungeeTabList1193(this);
private final BungeeTabList tabList1_19_3 = new BungeeTabList1193(this);
/** Player's boss bar view */
@NotNull
@@ -60,7 +56,7 @@ public class BungeeTabPlayer extends ProxyTabPlayer {
* BungeeCord player
*/
public BungeeTabPlayer(@NotNull BungeePlatform platform, @NotNull ProxiedPlayer p) {
super(platform, p, p.getUniqueId(), p.getName(), p.getServer() != null ? p.getServer().getInfo().getName() : "-", -1);
super(platform, p, p.getUniqueId(), p.getName(), p.getServer() != null ? p.getServer().getInfo().getName() : "-", p.getPendingConnection().getVersion());
}
@Override
@@ -74,8 +70,21 @@ public class BungeeTabPlayer extends ProxyTabPlayer {
}
@Override
public void sendMessage(@NotNull IChatBaseComponent message) {
getPlayer().sendMessage(ComponentSerializer.parse(message.toString(getVersion())));
public void sendMessage(@NotNull TabComponent message) {
try {
getPlayer().sendMessage((BaseComponent) message.convert(getVersion()));
} catch (NullPointerException BungeeCordBug) {
// java.lang.NullPointerException: Cannot invoke "net.md_5.bungee.protocol.MinecraftEncoder.getProtocol()" because the return value of "io.netty.channel.ChannelPipeline.get(java.lang.Class)" is null
// at net.md_5.bungee.netty.ChannelWrapper.getEncodeProtocol(ChannelWrapper.java:51)
// at net.md_5.bungee.UserConnection.sendPacketQueued(UserConnection.java:198)
// at net.md_5.bungee.UserConnection.sendMessage(UserConnection.java:565)
// at net.md_5.bungee.UserConnection.sendMessage(UserConnection.java:520)
// at net.md_5.bungee.UserConnection.sendMessage(UserConnection.java:508)
if (TAB.getInstance().getConfiguration().isDebugMode()) {
TAB.getInstance().getErrorManager().printError("Failed to send message to player " + getName() +
" (online = " + getPlayer().isConnected() + "): " + message.convert(getVersion()), BungeeCordBug);
}
}
}
@Override
@@ -94,11 +103,6 @@ public class BungeeTabPlayer extends ProxyTabPlayer {
return (ProxiedPlayer) player;
}
@Override
public boolean isOnline() {
return getPlayer().isConnected();
}
@Override
public BungeePlatform getPlatform() {
return (BungeePlatform) platform;
@@ -121,33 +125,15 @@ public class BungeeTabPlayer extends ProxyTabPlayer {
}
}
/**
* If ViaVersion is installed on BungeeCord, it changes protocol to match version
* of server to which player is connected to. For that reason, we need to retrieve
* the field more often than just on join.
*
* @return Player's current protocol version
*/
@Override
@NotNull
public ProtocolVersion getVersion() {
return ProtocolVersion.fromNetworkId(getPlayer().getPendingConnection().getVersion());
}
@Override
public boolean isVanished() {
try {
//noinspection ConstantConditions
if (premiumVanish && BungeeVanishAPI.isInvisible(getPlayer())) return true;
} catch (IllegalStateException ignored) {
// PV Bug: PremiumVanish must be enabled to use its API
}
if (PremiumVanishHook.getInstance() != null && PremiumVanishHook.getInstance().isVanished(this)) return true;
return super.isVanished();
}
@Override
@NotNull
public TabList getTabList() {
public BungeeTabList 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;
@@ -161,9 +147,18 @@ public class BungeeTabPlayer extends ProxyTabPlayer {
* @param packet
* Packet to send
*/
@SneakyThrows
public void sendPacket(@NotNull DefinedPacket packet) {
if (!getPlayer().isConnected()) return;
BungeeMultiVersion.sendPacket(this, packet);
try {
((UserConnection)getPlayer()).sendPacketQueued(packet);
} catch (NullPointerException BungeeCordBug) {
// java.lang.NullPointerException: Cannot invoke "net.md_5.bungee.protocol.MinecraftEncoder.getProtocol()" because the return value of "io.netty.channel.ChannelPipeline.get(java.lang.Class)" is null
// at net.md_5.bungee.netty.ChannelWrapper.getEncodeProtocol(ChannelWrapper.java:51)
// at net.md_5.bungee.UserConnection.sendPacketQueued(UserConnection.java:194)
if (TAB.getInstance().getConfiguration().isDebugMode()) {
TAB.getInstance().getErrorManager().printError("Failed to deliver packet to player " + getName() +
" (online = " + getPlayer().isConnected() + "): " + packet, BungeeCordBug);
}
}
}
}
@@ -21,6 +21,12 @@ public class BungeeRedisSupport extends RedisSupport implements Listener {
@NotNull
private final BungeeTAB plugin;
/**
* Listens to messages coming from other proxies.
*
* @param e
* Message event
*/
@EventHandler
public void onMessage(@NotNull PubSubMessageEvent e) {
if (!e.getChannel().equals(TabConstants.REDIS_CHANNEL_NAME)) return;
@@ -0,0 +1,53 @@
package me.neznamy.tab.platforms.bungeecord.hook;
import de.myzelyam.api.vanish.BungeeVanishAPI;
import me.neznamy.tab.platforms.bungeecord.BungeeTabPlayer;
import me.neznamy.tab.shared.chat.SimpleComponent;
import me.neznamy.tab.shared.hook.PremiumVanishHook;
import me.neznamy.tab.shared.platform.Platform;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import org.jetbrains.annotations.NotNull;
/**
* PremiumVanish hook for BungeeCord.
*/
public class BungeePremiumVanishHook extends PremiumVanishHook {
private final boolean canSeeEnabled;
/**
* Constructs new instance. If using an old version of PV, warn
* is printed into the platform's logger.
*
* @param platform
* Platform to print potential warn to
*/
public BungeePremiumVanishHook(@NotNull Platform platform) {
if (ReflectionUtils.methodExists(BungeeVanishAPI.class, "canSee", ProxiedPlayer.class, ProxiedPlayer.class)) {
canSeeEnabled = true;
} else {
canSeeEnabled = false;
platform.logWarn(new SimpleComponent("Detected an outdated version of " +
"PremiumVanish with limited API. Vanish compatibility " +
"may not work as expected. Update PremiumVanish to version 2.7.11+ for optimal experience."));
}
}
@Override
public boolean canSee(@NotNull TabPlayer viewer, @NotNull TabPlayer target) {
//noinspection ConstantValue
return canSeeEnabled && BungeeVanishAPI.canSee(((BungeeTabPlayer)viewer).getPlayer(), ((BungeeTabPlayer)target).getPlayer());
}
@Override
public boolean isVanished(@NotNull TabPlayer player) {
try {
return BungeeVanishAPI.isInvisible(((BungeeTabPlayer)player).getPlayer());
} catch (IllegalStateException ignored) {
// PV Bug: PremiumVanish must be enabled to use its API
}
return false;
}
}
@@ -1,71 +1,155 @@
package me.neznamy.tab.platforms.bungeecord.tablist;
import lombok.NonNull;
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.TAB;
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.api.chat.BaseComponent;
import net.md_5.bungee.protocol.Property;
import net.md_5.bungee.protocol.packet.PlayerListHeaderFooter;
import net.md_5.bungee.protocol.packet.PlayerListItem;
import net.md_5.bungee.protocol.packet.PlayerListItem.Item;
import net.md_5.bungee.protocol.packet.PlayerListItemUpdate;
import net.md_5.bungee.tab.ServerUnique;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.UUID;
public abstract class BungeeTabList implements TabList {
/** Player this TabList belongs to */
@NotNull
protected final BungeeTabPlayer player;
/**
* Abstract TabList class for BungeeCord containing
* common code for all implementations.
*/
public abstract class BungeeTabList extends TabList<BungeeTabPlayer, BaseComponent> {
/** Pointer to UUIDs in player's TabList */
private final Collection<UUID> uuids;
/**
* Constructs new instance with given parameters.
*
* @param player
* Player this tablist will belong to
*/
@SneakyThrows
@SuppressWarnings("unchecked")
public BungeeTabList(@NotNull BungeeTabPlayer player) {
this.player = player;
protected BungeeTabList(@NonNull BungeeTabPlayer player) {
super(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()));
public void setPlayerListHeaderFooter0(@NonNull BaseComponent header, @NonNull BaseComponent footer) {
player.sendPacket(new PlayerListHeaderFooter(header, footer));
}
@NotNull
public Item item(@NotNull UUID id) {
/**
* Creates new {@link Item} with given UUID.
*
* @param id
* UUID to use
* @return New {@link Item} with given UUID.
*/
@NonNull
public Item item(@NonNull 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())});
/**
* Converts entry data to item.
*
* @param id
* Entry UUID
* @param name
* Entry name
* @param skin
* Entry skin
* @param listed
* Whether entry should be listed or not
* @param latency
* Entry latency
* @param gameMode
* Entry game mode
* @param displayName
* Entry display name
* @return Converted item from parameters
*/
@NonNull
public Item entryToItem(@NonNull UUID id, @NonNull String name, @Nullable Skin skin, boolean listed, int latency, int gameMode, @Nullable BaseComponent displayName) {
Item item = item(id);
item.setUsername(name);
item.setDisplayName(displayName);
item.setGamemode(gameMode);
item.setListed(listed);
item.setPing(latency);
if (skin != null) {
item.setProperties(new Property[]{new Property(TEXTURES_PROPERTY, skin.getValue(), skin.getSignature())});
} else {
item.setProperties(new Property[0]);
}
item.setUsername(entry.getName());
return item;
}
public void addUuid(UUID id) {
/**
* Adds given UUID to BungeeCord's tablist uuid tracker.
*
* @param id
* UUID to add
*/
public void addUuid(@NonNull UUID id) {
uuids.add(id);
}
public void removeUuid(UUID id) {
/**
* Removes given UUID from BungeeCord's tablist uuid tracker.
*
* @param id
* UUID to remove
*/
public void removeUuid(@NonNull UUID id) {
uuids.remove(id);
}
@Override
public void onPacketSend(@NonNull Object packet) {
if (packet instanceof PlayerListItem) {
PlayerListItem listItem = (PlayerListItem) packet;
for (PlayerListItem.Item item : listItem.getItems()) {
if (listItem.getAction() == PlayerListItem.Action.UPDATE_DISPLAY_NAME || listItem.getAction() == PlayerListItem.Action.ADD_PLAYER) {
BaseComponent expectedDisplayName = getExpectedDisplayName(item.getUuid());
if (expectedDisplayName != null) item.setDisplayName(expectedDisplayName);
}
if (listItem.getAction() == PlayerListItem.Action.UPDATE_LATENCY || listItem.getAction() == PlayerListItem.Action.ADD_PLAYER) {
item.setPing(TAB.getInstance().getFeatureManager().onLatencyChange(player, item.getUuid(), item.getPing()));
}
if (listItem.getAction() == PlayerListItem.Action.ADD_PLAYER) {
TAB.getInstance().getFeatureManager().onEntryAdd(player, item.getUuid(), item.getUsername());
}
}
} else if (packet instanceof PlayerListItemUpdate) {
PlayerListItemUpdate update = (PlayerListItemUpdate) packet;
for (PlayerListItem.Item item : update.getItems()) {
if (update.getActions().contains(PlayerListItemUpdate.Action.UPDATE_DISPLAY_NAME)) {
BaseComponent expectedDisplayName = getExpectedDisplayName(item.getUuid());
if (expectedDisplayName != null) item.setDisplayName(expectedDisplayName);
}
if (update.getActions().contains(PlayerListItemUpdate.Action.UPDATE_LATENCY)) {
item.setPing(TAB.getInstance().getFeatureManager().onLatencyChange(player, item.getUuid(), item.getPing()));
}
if (update.getActions().contains(PlayerListItemUpdate.Action.ADD_PLAYER)) {
TAB.getInstance().getFeatureManager().onEntryAdd(player, item.getUuid(), item.getUsername());
}
}
}
}
@Override
public boolean containsEntry(@NonNull UUID entry) {
return uuids.contains(entry);
}
}
@@ -1,16 +1,14 @@
package me.neznamy.tab.platforms.bungeecord.tablist;
import me.neznamy.tab.platforms.bungeecord.BungeeMultiVersion;
import lombok.NonNull;
import me.neznamy.tab.platforms.bungeecord.BungeeTabPlayer;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import net.md_5.bungee.api.chat.BaseComponent;
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;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.EnumSet;
import java.util.UUID;
import java.util.*;
/**
* TabList handler for 1.19.3+ players using the new tab list packets.
@@ -18,12 +16,29 @@ import java.util.UUID;
*/
public class BungeeTabList1193 extends BungeeTabList {
public BungeeTabList1193(@NotNull BungeeTabPlayer player) {
/** Map of actions to prevent creating new EnumSet on each packet send */
private static final Map<Action, EnumSet<PlayerListItemUpdate.Action>> actions = new EnumMap<>(Action.class);
static {
actions.put(Action.ADD_PLAYER, EnumSet.allOf(PlayerListItemUpdate.Action.class));
actions.put(Action.UPDATE_GAME_MODE, EnumSet.of(PlayerListItemUpdate.Action.UPDATE_GAMEMODE));
actions.put(Action.UPDATE_DISPLAY_NAME, EnumSet.of(PlayerListItemUpdate.Action.UPDATE_DISPLAY_NAME));
actions.put(Action.UPDATE_LATENCY, EnumSet.of(PlayerListItemUpdate.Action.UPDATE_LATENCY));
actions.put(Action.UPDATE_LISTED, EnumSet.of(PlayerListItemUpdate.Action.UPDATE_LISTED));
}
/**
* Constructs new instance with given parameter.
*
* @param player
* Player this tablist will belong to
*/
public BungeeTabList1193(@NonNull BungeeTabPlayer player) {
super(player);
}
@Override
public void removeEntry(@NotNull UUID entry) {
public void removeEntry(@NonNull UUID entry) {
removeUuid(entry);
PlayerListItemRemove remove = new PlayerListItemRemove();
remove.setUuids(new UUID[]{entry});
@@ -31,35 +46,42 @@ public class BungeeTabList1193 extends BungeeTabList {
}
@Override
public void updateDisplayName(@NotNull UUID entry, @Nullable IChatBaseComponent displayName) {
public void updateDisplayName0(@NonNull UUID entry, @Nullable BaseComponent displayName) {
Item item = item(entry);
if (displayName != null) BungeeMultiVersion.setDisplayName(item, displayName, player.getVersion());
sendPacket(EnumSet.of(PlayerListItemUpdate.Action.UPDATE_DISPLAY_NAME), item);
item.setDisplayName(displayName);
sendPacket(Action.UPDATE_DISPLAY_NAME, item);
}
@Override
public void updateLatency(@NotNull UUID entry, int latency) {
public void updateLatency(@NonNull UUID entry, int latency) {
Item item = item(entry);
item.setPing(latency);
sendPacket(EnumSet.of(PlayerListItemUpdate.Action.UPDATE_LATENCY), item);
sendPacket(Action.UPDATE_LATENCY, item);
}
@Override
public void updateGameMode(@NotNull UUID entry, int gameMode) {
public void updateGameMode(@NonNull UUID entry, int gameMode) {
Item item = item(entry);
item.setGamemode(gameMode);
sendPacket(EnumSet.of(PlayerListItemUpdate.Action.UPDATE_GAMEMODE), item);
sendPacket(Action.UPDATE_GAME_MODE, item);
}
@Override
public void addEntry(@NotNull Entry entry) {
addUuid(entry.getUniqueId());
sendPacket(EnumSet.allOf(PlayerListItemUpdate.Action.class), entryToItem(entry));
public void updateListed(@NonNull UUID entry, boolean listed) {
Item item = item(entry);
item.setListed(listed);
sendPacket(Action.UPDATE_LISTED, item);
}
private void sendPacket(@NotNull EnumSet<PlayerListItemUpdate.Action> actions, @NotNull Item item) {
@Override
public void addEntry0(@NonNull UUID id, @NonNull String name, @Nullable Skin skin, boolean listed, int latency, int gameMode, @Nullable BaseComponent displayName) {
addUuid(id);
sendPacket(Action.ADD_PLAYER, entryToItem(id, name, skin, listed, latency, gameMode, displayName));
}
private void sendPacket(@NonNull Action action, @NonNull Item item) {
PlayerListItemUpdate packet = new PlayerListItemUpdate();
packet.setActions(actions);
packet.setActions(actions.get(action));
packet.setItems(new Item[]{item});
player.sendPacket(packet);
}
@@ -1,11 +1,12 @@
package me.neznamy.tab.platforms.bungeecord.tablist;
import me.neznamy.tab.platforms.bungeecord.BungeeMultiVersion;
import lombok.NonNull;
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.chat.TabComponent;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.protocol.packet.PlayerListItem;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.HashMap;
@@ -24,18 +25,24 @@ import java.util.UUID;
public class BungeeTabList17 extends BungeeTabList {
/** Because entries are identified by names and not uuids on 1.7 */
@NotNull
@NonNull
private final Map<UUID, String> userNames = new HashMap<>();
@NotNull
private final Map<UUID, String> displayNames = new HashMap<>();
@NonNull
private final Map<UUID, BaseComponent> displayNames = new HashMap<>();
public BungeeTabList17(@NotNull BungeeTabPlayer player) {
/**
* Constructs new instance with given parameters.
*
* @param player
* Player this tablist will belong to
*/
public BungeeTabList17(@NonNull BungeeTabPlayer player) {
super(player);
}
@Override
public void removeEntry(@NotNull UUID entry) {
public void removeEntry(@NonNull 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));
@@ -46,46 +53,63 @@ public class BungeeTabList17 extends BungeeTabList {
}
@Override
public void updateDisplayName(@NotNull UUID entry, @Nullable IChatBaseComponent displayName) {
public void updateDisplayName0(@NonNull UUID entry, @Nullable BaseComponent displayName) {
if (!displayNames.containsKey(entry)) return; // Entry not tracked by TAB
update(PlayerListItem.Action.REMOVE_PLAYER, createItem(null, displayNames.get(entry), 0));
addEntry(new Entry.Builder(entry).displayName(displayName).name(userNames.get(entry)).build());
addEntry0(entry, userNames.get(entry), null, false, 0, 0, displayName);
}
@Override
public void updateLatency(@NotNull UUID entry, int latency) {
public void updateLatency(@NonNull UUID entry, int latency) {
if (!displayNames.containsKey(entry)) return; // Entry not tracked by TAB
update(PlayerListItem.Action.UPDATE_LATENCY, createItem(null, displayNames.get(entry), latency));
}
@Override
public void updateGameMode(@NotNull UUID entry, int gameMode) {/*Added in 1.8*/}
@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() > 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
userNames.put(entry.getUniqueId(), entry.getName());
displayNames.put(entry.getUniqueId(), displayNameString);
public void updateGameMode(@NonNull UUID entry, int gameMode) {
// Added in 1.8
}
private void update(@NotNull PlayerListItem.Action action, @NotNull PlayerListItem.Item item) {
@Override
public void updateListed(@NonNull UUID entry, boolean listed) {
// Added in 1.19.3
}
@Override
public void addEntry0(@NonNull UUID id, @NonNull String name, @Nullable Skin skin, boolean listed, int latency, int gameMode, @Nullable BaseComponent displayName) {
addUuid(id);
update(PlayerListItem.Action.ADD_PLAYER, createItem(name, displayName == null ? new TextComponent(name) : displayName, latency));
// Add to map
userNames.put(id, name);
displayNames.put(id, displayName);
}
@Override
public void setPlayerListHeaderFooter0(@NonNull BaseComponent header, @NonNull BaseComponent footer) {
// Not available on 1.7
}
@Override
public BaseComponent toComponent(@NonNull TabComponent component) {
String displayNameString = component.toLegacyText();
if (displayNameString.length() > Limitations.MAX_DISPLAY_NAME_LENGTH_1_7)
displayNameString = displayNameString.substring(0, Limitations.MAX_DISPLAY_NAME_LENGTH_1_7);
return TabComponent.optimized(displayNameString).convert(player.getVersion());
}
private void update(@NonNull PlayerListItem.Action action, @NonNull PlayerListItem.Item item) {
PlayerListItem packet = new PlayerListItem();
packet.setAction(action);
packet.setItems(new PlayerListItem.Item[]{item});
player.sendPacket(packet);
}
private PlayerListItem.Item createItem(@Nullable String username, @NotNull String displayName, int latency) {
private PlayerListItem.Item createItem(@Nullable String username, @NonNull BaseComponent displayName, int latency) {
PlayerListItem.Item item = new PlayerListItem.Item();
item.setUsername(username);
BungeeMultiVersion.setDisplayName(item, IChatBaseComponent.optimizedComponent(displayName), player.getVersion());
item.setPing(latency);
item.setDisplayName(displayName);
return item;
}
}
@@ -1,11 +1,10 @@
package me.neznamy.tab.platforms.bungeecord.tablist;
import me.neznamy.tab.platforms.bungeecord.BungeeMultiVersion;
import lombok.NonNull;
import me.neznamy.tab.platforms.bungeecord.BungeeTabPlayer;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.protocol.packet.PlayerListItem;
import net.md_5.bungee.protocol.packet.PlayerListItem.Item;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.UUID;
@@ -16,44 +15,55 @@ import java.util.UUID;
*/
public class BungeeTabList18 extends BungeeTabList {
public BungeeTabList18(@NotNull BungeeTabPlayer player) {
/**
* Constructs new instance with given parameter.
*
* @param player
* Player this tablist will belong to
*/
public BungeeTabList18(@NonNull BungeeTabPlayer player) {
super(player);
}
@Override
public void removeEntry(@NotNull UUID entry) {
public void removeEntry(@NonNull UUID entry) {
removeUuid(entry);
sendPacket(PlayerListItem.Action.REMOVE_PLAYER, item(entry));
}
@Override
public void updateDisplayName(@NotNull UUID entry, @Nullable IChatBaseComponent displayName) {
public void updateDisplayName0(@NonNull UUID entry, @Nullable BaseComponent displayName) {
Item item = item(entry);
if (displayName != null) BungeeMultiVersion.setDisplayName(item, displayName, player.getVersion());
item.setDisplayName(displayName);
sendPacket(PlayerListItem.Action.UPDATE_DISPLAY_NAME, item);
}
@Override
public void updateLatency(@NotNull UUID entry, int latency) {
public void updateLatency(@NonNull UUID entry, int latency) {
Item item = item(entry);
item.setPing(latency);
sendPacket(PlayerListItem.Action.UPDATE_LATENCY, item);
}
@Override
public void updateGameMode(@NotNull UUID entry, int gameMode) {
public void updateGameMode(@NonNull UUID entry, int gameMode) {
Item item = item(entry);
item.setGamemode(gameMode);
sendPacket(PlayerListItem.Action.UPDATE_GAMEMODE, item);
}
@Override
public void addEntry(@NotNull Entry entry) {
addUuid(entry.getUniqueId());
sendPacket(PlayerListItem.Action.ADD_PLAYER, entryToItem(entry));
public void updateListed(@NonNull UUID entry, boolean listed) {
// Added in 1.19.3
}
private void sendPacket(@NotNull PlayerListItem.Action action, @NotNull Item item) {
@Override
public void addEntry0(@NonNull UUID id, @NonNull String name, @Nullable Skin skin, boolean listed, int latency, int gameMode, @Nullable BaseComponent displayName) {
addUuid(id);
sendPacket(PlayerListItem.Action.ADD_PLAYER, entryToItem(id, name, skin, listed, latency, gameMode, displayName));
}
private void sendPacket(@NonNull PlayerListItem.Action action, @NonNull Item item) {
PlayerListItem packet = new PlayerListItem();
packet.setAction(action);
packet.setItems(new Item[]{item});
+13 -48
View File
@@ -1,34 +1,3 @@
val minecraftVersion = "1.20.2"
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",
"1.19.3" to "0.76.0+1.19.3",
"1.19.2" to "0.76.0+1.19.2",
"1.19.1" to "0.58.5+1.19.1",
"1.19" to "0.58.0+1.19",
"1.18.2" to "0.76.0+1.18.2",
"1.18.1" to "0.46.6+1.18",
"1.18" to "0.46.6+1.18",
"1.17.1" to "0.46.1+1.17",
"1.17" to "0.46.1+1.17",
"1.16.5" to v116,
"1.16.4" to v116,
"1.16.3" to v116,
"1.16.2" to v116,
"1.16.1" to v116,
"1.16" to v116,
"1.15.2" to v115,
"1.15.1" to v115,
"1.15" to v115,
"1.14.4" to "0.28.5+1.14",
)
plugins {
id("fabric-loom")
}
@@ -42,24 +11,22 @@ repositories {
}
dependencies {
implementation(projects.shared)
minecraft("com.mojang:minecraft:$minecraftVersion")
api(projects.shared)
minecraft("com.mojang:minecraft:1.21.1")
mappings(loom.officialMojangMappings())
modImplementation("net.fabricmc", "fabric-loader", "0.14.17")
modImplementation("me.lucko", "fabric-permissions-api", "0.2-SNAPSHOT")
modImplementation(fabricApi.module("fabric-api-base", fabricApiVersions[minecraftVersion]))
modImplementation(fabricApi.module("fabric-lifecycle-events-v1", fabricApiVersions[minecraftVersion]))
modImplementation(fabricApi.module("fabric-networking-api-v1", fabricApiVersions[minecraftVersion]))
modImplementation(fabricApi.module("fabric-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]))
}
modImplementation("me.lucko:fabric-permissions-api:0.2-SNAPSHOT")
modImplementation("net.fabricmc:fabric-loader:0.15.9")
val version = "0.100.1+1.21"
modImplementation(fabricApi.module("fabric-api-base", version))
modImplementation(fabricApi.module("fabric-lifecycle-events-v1", version))
modImplementation(fabricApi.module("fabric-networking-api-v1", version))
modImplementation(fabricApi.module("fabric-entity-events-v1", version))
modImplementation(fabricApi.module("fabric-command-api-v1", "0.77.0+1.18.2"))
modImplementation(fabricApi.module("fabric-command-api-v2", version))
}
loom {
accessWidenerPath.set(file("src/main/resources/tab.accesswidener"))
accessWidenerPath.set(file("src/main/resources/resources/tab.accesswidener"))
}
tasks {
@@ -67,8 +34,6 @@ tasks {
options.release.set(17)
}
validateAccessWidener {
// We don't want to validate the access wideners, as we have wideners for multiple versions, and the validation task will
// fail, as the classes for the old wideners don't exist in the new versions and vice versa.
enabled = false
enabled = true
}
}
@@ -1,77 +1,63 @@
package me.neznamy.tab.platforms.fabric;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.api.bossbar.BarColor;
import me.neznamy.tab.api.bossbar.BarStyle;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.bossbar.BossBar;
import me.neznamy.tab.shared.chat.TabComponent;
import me.neznamy.tab.shared.platform.BossBar;
import net.minecraft.server.level.ServerBossEvent;
import net.minecraft.world.BossEvent.BossBarColor;
import net.minecraft.world.BossEvent.BossBarOverlay;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* BossBar implementation for Fabric using packets.
*/
@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
};
/** Player this BossBar belongs to */
@NotNull
private final FabricTabPlayer player;
/** Map of BossBars visible to the 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(
player.getPlatform().toComponent(IChatBaseComponent.optimizedComponent(title), player.getVersion()),
colors[color.ordinal()],
overlays[style.ordinal()]
TabComponent.optimized(title).convert(player.getVersion()),
BossBarColor.valueOf(color.name()),
BossBarOverlay.valueOf(style.name())
);
FabricMultiVersion.setProgress(bar, progress);
bar.setProgress(progress); // Somehow the compiled method name is same despite method being renamed in 1.17
bars.put(id, bar);
bar.addPlayer(player.getPlayer());
}
@Override
public void update(@NotNull UUID id, @NotNull String title) {
bars.get(id).setName(player.getPlatform().toComponent(IChatBaseComponent.optimizedComponent(title), player.getVersion()));
bars.get(id).setName(TabComponent.optimized(title).convert(player.getVersion()));
}
@Override
public void update(@NotNull UUID id, float progress) {
FabricMultiVersion.setProgress(bars.get(id), progress);
bars.get(id).setProgress(progress); // Somehow the compiled method name is same despite method being renamed in 1.17
}
@Override
public void update(@NotNull UUID id, @NotNull BarStyle style) {
bars.get(id).setOverlay(overlays[style.ordinal()]);
bars.get(id).setOverlay(BossBarOverlay.valueOf(style.name()));
}
@Override
public void update(@NotNull UUID id, @NotNull BarColor color) {
bars.get(id).setColor(colors[color.ordinal()]);
bars.get(id).setColor(BossBarColor.valueOf(color.name()));
}
@Override
@@ -3,29 +3,53 @@ 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.network.protocol.Packet;
import net.minecraft.network.protocol.game.ClientboundMoveEntityPacket;
import net.minecraft.network.protocol.game.ClientboundRemoveEntitiesPacket;
import net.minecraft.network.protocol.game.ClientboundTeleportEntityPacket;
import net.minecraft.world.entity.decoration.ArmorStand;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
import java.util.UUID;
public record FabricEntityView(FabricTabPlayer player) implements EntityView {
/**
* EntityView implementation for Fabric using packets.
*/
public class FabricEntityView implements EntityView {
/** Player this view belongs to */
@NotNull
private final FabricTabPlayer player;
@NotNull
private static final Entity dummyEntity = new ArmorStand(null, 0, 0, 0);
private final ArmorStand dummyEntity;
/**
* Constructs new instance.
*
* @param player
* Player this view will belong to
*/
public FabricEntityView(@NotNull FabricTabPlayer player) {
this.player = player;
// Make level not null, because some mods hacking deep into the server code cause NPE
dummyEntity = new ArmorStand(FabricMultiVersion.getLevel(player.getPlayer()), 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);
FabricMultiVersion.sendPackets(player.getPlayer(), Arrays.asList(
FabricMultiVersion.spawnEntity(FabricMultiVersion.getLevel(player.getPlayer()), entityId, id, entityType, location),
FabricMultiVersion.newEntityMetadata(entityId, data)
));
}
@Override
public void updateEntityMetadata(int entityId, @NotNull EntityData data) {
player.sendPacket(FabricMultiVersion.newEntityMetadata(entityId, data));
}
@Override
@@ -37,12 +61,12 @@ public record FabricEntityView(FabricTabPlayer player) implements EntityView {
@Override
public void destroyEntities(int... entities) {
FabricMultiVersion.destroyEntities(player, entities);
player.sendPacket(new ClientboundRemoveEntitiesPacket(entities));
}
@Override
public boolean isDestroyPacket(@NotNull Object packet) {
return FabricMultiVersion.isEntityDestroyPacket(packet);
return packet instanceof ClientboundRemoveEntitiesPacket;
}
@Override
@@ -52,7 +76,7 @@ public record FabricEntityView(FabricTabPlayer player) implements EntityView {
@Override
public boolean isNamedEntitySpawnPacket(@NotNull Object packet) {
return packet instanceof ClientboundAddEntityPacket;
return FabricMultiVersion.isSpawnPlayerPacket((Packet<?>) packet);
}
@Override
@@ -67,32 +91,49 @@ public record FabricEntityView(FabricTabPlayer player) implements EntityView {
@Override
public int getTeleportEntityId(@NotNull Object teleportPacket) {
return ((ClientboundTeleportEntityPacket) teleportPacket).getId();
return ((ClientboundTeleportEntityPacket)teleportPacket).id;
}
@Override
public int getMoveEntityId(@NotNull Object movePacket) {
return ((ClientboundMoveEntityPacket) movePacket).entityId;
return ((ClientboundMoveEntityPacket)movePacket).entityId;
}
@Override
public int getSpawnedPlayer(@NotNull Object playerSpawnPacket) {
return ((ClientboundAddEntityPacket) playerSpawnPacket).getId();
return FabricMultiVersion.getSpawnedPlayerId((Packet<?>) playerSpawnPacket);
}
@Override
public int[] getDestroyedEntities(@NotNull Object destroyPacket) {
return FabricMultiVersion.getDestroyedEntities(destroyPacket);
return FabricMultiVersion.getDestroyedEntities((Packet<?>) destroyPacket);
}
@Override
public boolean isBundlePacket(@NotNull Object packet) {
return FabricMultiVersion.isBundlePacket(packet);
return FabricMultiVersion.isBundlePacket((Packet<?>) packet);
}
@SuppressWarnings("unchecked")
@Override
public Iterable<Object> getPackets(@NotNull Object bundlePacket) {
return (Iterable<Object>) (Object) FabricMultiVersion.getPackets(bundlePacket);
return FabricMultiVersion.getBundledPackets((Packet<?>) bundlePacket);
}
@Override
@NotNull
public Location getMoveDiff(@NotNull Object movePacket) {
ClientboundMoveEntityPacket packet = (ClientboundMoveEntityPacket) movePacket;
return new Location(packet.xa, packet.ya, packet.za);
}
@Override
public void moveEntity(int entityId, @NotNull Location moveDiff) {
player.sendPacket(new ClientboundMoveEntityPacket.Pos(
entityId,
(short) moveDiff.getX(),
(short) moveDiff.getY(),
(short) moveDiff.getZ(),
false
));
}
}
@@ -3,17 +3,36 @@ 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 me.neznamy.tab.shared.util.ReflectionUtils;
import net.fabricmc.fabric.api.entity.event.v1.ServerEntityWorldChangeEvents;
import net.fabricmc.fabric.api.entity.event.v1.ServerPlayerEvents;
import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents;
import net.minecraft.server.level.ServerPlayer;
import org.jetbrains.annotations.NotNull;
public class FabricEventListener extends EventListener<ServerPlayer> {
/**
* Event listener for Fabric.
*/
public class FabricEventListener implements EventListener<ServerPlayer> {
/**
* Registers all event listeners.
*/
public void register() {
ServerPlayConnectionEvents.DISCONNECT.register((connection, $) -> quit(connection.player.getUUID()));
ServerPlayConnectionEvents.JOIN.register((connection, $, $$) -> join(connection.player));
FabricMultiVersion.registerEntityEvents(this::replacePlayer, this::worldChange);
//TODO command preprocess
if (ReflectionUtils.classExists("net.fabricmc.fabric.api.entity.event.v1.ServerPlayerEvents")) {
// Added in 1.16
ServerPlayerEvents.AFTER_RESPAWN.register(
(oldPlayer, newPlayer, alive) -> {
replacePlayer(newPlayer.getUUID(), newPlayer);
// respawning from death & taking end portal in the end do not call world change event
worldChange(newPlayer.getUUID(), FabricMultiVersion.getLevelName(FabricMultiVersion.getLevel(newPlayer)));
});
ServerEntityWorldChangeEvents.AFTER_PLAYER_CHANGE_WORLD.register(
(player, origin, destination) -> worldChange(player.getUUID(), FabricMultiVersion.getLevelName(destination)));
} // TODO else
}
@Override
@@ -1,324 +1,571 @@
package me.neznamy.tab.platforms.fabric;
import com.mojang.authlib.properties.Property;
import me.neznamy.tab.platforms.fabric.features.FabricNameTagX;
import io.netty.channel.Channel;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.fabric.loader.Loader;
import me.neznamy.tab.platforms.fabric.loader.Loader_1_21_1;
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.chat.ChatModifier;
import me.neznamy.tab.shared.chat.TabComponent;
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.chat.Style;
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.network.protocol.game.ClientGamePacketListener;
import net.minecraft.network.protocol.game.ClientboundSetDisplayObjectivePacket;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.level.GameType;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.storage.ServerLevelData;
import net.minecraft.world.phys.Vec3;
import net.minecraft.world.scores.Objective;
import net.minecraft.world.scores.PlayerTeam;
import net.minecraft.world.scores.criteria.ObjectiveCriteria.RenderType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.UUID;
/**
* Class that contains (almost) all code that needs to be changed
* to set compatibility with any MC version that fabric supports (1.14.4+).
* Class managing cross-version code in shared module.
*/
public class FabricMultiVersion {
private static final int armorStandFlagsPosition = getArmorStandFlagsPosition();
/** Server version */
private static final ProtocolVersion serverVersion = ProtocolVersion.fromFriendlyName(FabricTAB.minecraftVersion);
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;
}
/** Method loader using latest supported MC version */
private static final Loader loaderNew = new Loader_1_21_1();
/** Method loader using 1.18.2 */
private static final Loader loader1_18_2 = createLoader("1_18_2");
/** Method loader using 1.14.4 */
private static final Loader loader1_14_4 = createLoader("1_14_4");
@SneakyThrows
private static Loader createLoader(@NotNull String version) {
return (Loader) Class.forName("me.neznamy.tab.platforms.fabric.loader.Loader_" + version)
.getConstructor(ProtocolVersion.class).newInstance(serverVersion);
}
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);
/**
* Returns {@code true} if player is sneaking, {@code false} if not.
*
* @param player
* Player to check sneak status of
* @return {@code true} if player is sneaking, {@code false} if not
*/
public static boolean isSneaking(@NotNull ServerPlayer player) {
if (serverVersion.getMinorVersion() >= 15) return loaderNew.isSneaking(player);
return loader1_14_4.isSneaking(player);
}
public static boolean isSneaking(ServerPlayer player) {
// 1.14.4-
// return player.isSneaking();
// 1.15+
return player.isCrouching();
/**
* Returns name of specified world.
*
* @param level
* World to get name of
* @return Name of the world
*/
@NotNull
public static String getLevelName(@NotNull Level level) {
if (serverVersion.getMinorVersion() >= 16) return loaderNew.getLevelName(level);
return loader1_14_4.getLevelName(level);
}
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
/**
* Adds sibling to a component.
*
* @param parent
* Parent to add sibling to
* @param child
* Sibling to add
*/
public static void addSibling(@NotNull Component parent, @NotNull Component child) {
if (serverVersion.getMinorVersion() >= 16) loaderNew.addSibling(parent, child);
else loader1_14_4.addSibling(parent, child);
}
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)));
/**
* Converts TAB's ChatModifier class to Minecraft Style class.
*
* @param modifier
* Modifier to convert
* @param modern
* Whether RGB should be supported or not
* @return Converted style
*/
@NotNull
public static Style convertModifier(@NotNull ChatModifier modifier, boolean modern) {
if (serverVersion.getMinorVersion() >= 16) return loaderNew.convertModifier(modifier, modern);
return loader1_14_4.convertModifier(modifier, modern);
}
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);
/**
* Creates new Header/Footer packet with given parameters.
*
* @param header
* Header to use
* @param footer
* Footer to use
* @return Packet with given parameters
*/
@NotNull
public static Packet<?> newHeaderFooter(@NotNull Component header, @NotNull Component footer) {
if (serverVersion.getMinorVersion() >= 17) return loaderNew.newHeaderFooter(header, footer);
return loader1_14_4.newHeaderFooter(header, footer);
}
public static ClientboundSetPlayerTeamPacket createTeam(PlayerTeam team) {
// 1.16.5-
// return new ClientboundSetPlayerTeamPacket(team, 0);
// 1.17+
return ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, true);
/**
* Checks outgoing team packet in pipeline to potentially remove players from it.
*
* @param packet
* Packet to check
* @param scoreboard
* Scoreboard of player who received the packet
*/
public static void checkTeamPacket(@NotNull Packet<?> packet, @NotNull FabricScoreboard scoreboard) {
if (serverVersion.getMinorVersion() >= 17) loaderNew.checkTeamPacket(packet, scoreboard);
else loader1_14_4.checkTeamPacket(packet, scoreboard);
}
public static ClientboundSetPlayerTeamPacket removeTeam(PlayerTeam team) {
// 1.16.5-
// return new ClientboundSetPlayerTeamPacket(team, 1);
// 1.17+
return ClientboundSetPlayerTeamPacket.createRemovePacket(team);
/**
* Creates team register packet using given team.
*
* @param team
* Team to register
* @return Team register packet using given team
*/
@NotNull
public static Packet<?> registerTeam(@NotNull PlayerTeam team) {
if (serverVersion.getMinorVersion() >= 17) return loaderNew.registerTeam(team);
return loader1_14_4.registerTeam(team);
}
public static ClientboundSetPlayerTeamPacket updateTeam(PlayerTeam team) {
// 1.16.5-
// return new ClientboundSetPlayerTeamPacket(team, 2);
// 1.17+
return ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, false);
/**
* Creates team unregister packet using given team.
*
* @param team
* Team to unregister
* @return Team unregister packet using given team
*/
@NotNull
public static Packet<?> unregisterTeam(@NotNull PlayerTeam team) {
if (serverVersion.getMinorVersion() >= 17) return loaderNew.unregisterTeam(team);
return loader1_14_4.unregisterTeam(team);
}
public static void setProgress(ServerBossEvent bar, float progress) {
// 1.16.5-
// bar.setPercent(progress);
// 1.17+
bar.setProgress(progress);
/**
* Creates team update packet using given team.
*
* @param team
* Team to update
* @return Team update packet using given team
*/
@NotNull
public static Packet<?> updateTeam(@NotNull PlayerTeam team) {
if (serverVersion.getMinorVersion() >= 17) return loaderNew.updateTeam(team);
return loader1_14_4.updateTeam(team);
}
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();
/**
* Returns destroyed entities from destroy entity packet.
*
* @param destroyPacket
* Entity destroy packet
* @return Destroyed entities
*/
public static int[] getDestroyedEntities(@NotNull Packet<?> destroyPacket) {
if (serverVersion.getMinorVersion() >= 17) return loaderNew.getDestroyedEntities(destroyPacket);
return loader1_14_4.getDestroyedEntities(destroyPacket);
}
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));
/**
* Logs console message as info.
*
* @param message
* Message to log
*/
public static void logInfo(@NotNull TabComponent message) {
if (serverVersion.getNetworkId() >= ProtocolVersion.V1_18_2.getNetworkId()) loaderNew.logInfo(message);
else loader1_14_4.logInfo(message);
}
public static boolean isEntityDestroyPacket(Object packet) {
// 1.17
// return packet instanceof ClientboundRemoveEntityPacket;
// every other version
return packet instanceof ClientboundRemoveEntitiesPacket;
/**
* Logs console message as warn.
*
* @param message
* Message to log
*/
public static void logWarn(@NotNull TabComponent message) {
if (serverVersion.getNetworkId() >= ProtocolVersion.V1_18_2.getNetworkId()) loaderNew.logWarn(message);
else loader1_14_4.logWarn(message);
}
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));
/**
* Creates new text component using given text.
*
* @param text
* Component text
* @return Text component with given text
*/
@NotNull
public static Component newTextComponent(@NotNull String text) {
if (serverVersion.getMinorVersion() >= 19) return loaderNew.newTextComponent(text);
return loader1_14_4.newTextComponent(text);
}
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);
/**
* Sends message to command source.
*
* @param source
* Command source to send message to
* @param message
* Message to send
*/
public static void sendMessage(@NotNull CommandSourceStack source, @NotNull Component message) {
if (serverVersion.getMinorVersion() >= 19) loaderNew.sendMessage(source, message);
else loader1_14_4.sendMessage(source, 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);
/**
* Creates spawn entity packet with given parameters.
*
* @param level
* World to fill for dummy entity
* @param id
* Entity ID
* @param uuid
* Entity UUID
* @param type
* Entity type
* @param location
* Spawn location
* @return Spawn entity packet with given parameters
*/
@NotNull
public static Packet<ClientGamePacketListener> spawnEntity(@NotNull Level level, int id, @NotNull UUID uuid, @NotNull Object type, @NotNull Location location) {
if (serverVersion.getMinorVersion() >= 19) return loaderNew.spawnEntity(level, id, uuid, type, location);
return loader1_14_4.spawnEntity(level, id, uuid, type, location);
}
public static boolean isPlayerInfo(Object packet) {
// 1.19.2-
// return packet instanceof ClientboundPlayerInfoPacket;
// 1.19.3+
return packet instanceof ClientboundPlayerInfoUpdatePacket;
/**
* Sets style in a component to specified style.
*
* @param component
* Component to change style of
* @param style
* Style to use
*/
@SneakyThrows
public static void setStyle(@NotNull Component component, @NotNull Style style) {
if (serverVersion.getMinorVersion() >= 19) loaderNew.setStyle(component, style);
else if (serverVersion.getMinorVersion() >= 16) loader1_18_2.setStyle(component, style);
else loader1_14_4.setStyle(component, style);
}
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;
/**
* Sends message to player.
*
* @param player
* Player to send message to
* @param message
* Message to send
*/
@SneakyThrows
public static void sendMessage(@NotNull ServerPlayer player, @NotNull Component message) {
if (serverVersion.getMinorVersion() >= 19) loaderNew.sendMessage(player, message);
else if (serverVersion.getMinorVersion() >= 16) loader1_18_2.sendMessage(player, message);
else loader1_14_4.sendMessage(player, message);
}
/**
* Creates entity metadata packet with given metadata.
*
* @param entityId
* Entity ID to change metadata of
* @param data
* Metadata to change
* @return Entity metadata packet with given parameters
*/
@NotNull
public static Packet<ClientGamePacketListener> newEntityMetadata(int entityId, @NotNull EntityData data) {
if (serverVersion.getNetworkId() >= ProtocolVersion.V1_19_3.getNetworkId()) return loaderNew.newEntityMetadata(entityId, data);
return loader1_14_4.newEntityMetadata(entityId, data);
}
/**
* Creates entity data with given parameters.
*
* @param viewer
* Viewer of custom name
* @param flags
* Entity flags
* @param displayName
* Custom name
* @param nameVisible
* Custom name visibility
* @return Entity data with given parameters
*/
@NotNull
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)
);
int position = EntityData.getArmorStandFlagsPosition(serverVersion.getMinorVersion());
if (serverVersion.getNetworkId() >= ProtocolVersion.V1_19_3.getNetworkId()) return loaderNew.createDataWatcher(viewer, flags, displayName, nameVisible, position);
return loader1_14_4.createDataWatcher(viewer, flags, displayName, nameVisible, position);
}
@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());
/**
* Returns {@code true} if packet is player info packet, {@code false} if not.
*
* @param packet
* Packet to check
* @return {@code true} if packet is player info packet, {@code false} if not
*/
public static boolean isPlayerInfo(@NotNull Packet<?> packet) {
if (serverVersion.getNetworkId() >= ProtocolVersion.V1_19_3.getNetworkId()) return loaderNew.isPlayerInfo(packet);
return loader1_14_4.isPlayerInfo(packet);
}
public static boolean isBundlePacket(Object packet) {
// 1.19.4+
return packet instanceof ClientboundBundlePacket;
/**
* Processed player info packet for anti-override and similar.
*
* @param receiver
* Player who received the packet
* @param packet
* Received packet
*/
public static void onPlayerInfo(@NotNull TabPlayer receiver, @NotNull Object packet) {
if (serverVersion.getNetworkId() >= ProtocolVersion.V1_19_3.getNetworkId()) loaderNew.onPlayerInfo(receiver, packet);
else if (serverVersion.getMinorVersion() >= 17) loader1_18_2.onPlayerInfo(receiver, packet);
else loader1_14_4.onPlayerInfo(receiver, packet);
}
public static Iterable<Packet<ClientGamePacketListener>> getPackets(Object bundlePacket) {
// 1.19.4+
return ((ClientboundBundlePacket)bundlePacket).subPackets();
/**
* Creates tablist entry packet using given parameters.
*
* @param action
* Tablist action
* @param builder
* Entry data
* @return Tablist entry packet with given parameters
*/
@NotNull
public static Packet<?> buildTabListPacket(@NotNull TabList.Action action, @NotNull FabricTabList.Builder builder) {
if (serverVersion.getNetworkId() >= ProtocolVersion.V1_19_3.getNetworkId()) return loaderNew.buildTabListPacket(action, builder);
else if (serverVersion.getMinorVersion() >= 17) return loader1_18_2.buildTabListPacket(action, builder);
return loader1_14_4.buildTabListPacket(action, builder);
}
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);
/**
* Returns {@code true} if packet is bundle packet, {@code false} if not.
*
* @param packet
* Packet to check
* @return {@code true} if packet is bundle packet, {@code false} if not
*/
public static boolean isBundlePacket(@NotNull Packet<?> packet) {
if (serverVersion.getNetworkId() >= ProtocolVersion.V1_19_4.getNetworkId()) return loaderNew.isBundlePacket(packet);
return loader1_14_4.isBundlePacket(packet);
}
public static int getSlot(ClientboundSetDisplayObjectivePacket packet) {
// 1.20.1-
// return packet.getSlot();
// 1.20.2+
return packet.getSlot().ordinal();
/**
* Returns packets bundled in given bundle packet.
*
* @param bundlePacket
* Bundle packet
* @return Bundled packets
*/
@NotNull
public static Iterable<Object> getBundledPackets(@NotNull Packet<?> bundlePacket) {
if (serverVersion.getNetworkId() >= ProtocolVersion.V1_19_4.getNetworkId()) return loaderNew.getBundledPackets(bundlePacket);
return loader1_14_4.getBundledPackets(bundlePacket);
}
public static int getPing(FabricTabPlayer player) {
// 1.20.1-
// return player.getPlayer().latency;
// 1.20.2+
return player.getPlayer().connection.latency();
/**
* Sends packets to player as a bundle.
*
* @param player
* Player to send packets to
* @param packets
* Packets to send
*/
public static void sendPackets(@NotNull ServerPlayer player, @NotNull Iterable<Packet<ClientGamePacketListener>> packets) {
if (serverVersion.getNetworkId() >= ProtocolVersion.V1_19_4.getNetworkId()) loaderNew.sendPackets(player, packets);
else loader1_14_4.sendPackets(player, packets);
}
public static TabList.Skin propertyToSkin(Property property) {
// 1.20.1-
// return new TabList.Skin(property.getValue(), property.getSignature());
/**
* Returns player's world
*
* @param player
* Player to get world of
* @return Player's world
*/
@NotNull
public static Level getLevel(@NotNull ServerPlayer player) {
if (serverVersion.getMinorVersion() >= 20) return loaderNew.getLevel(player);
return loader1_14_4.getLevel(player);
}
// 1.20.2+
return new TabList.Skin(property.value(), property.signature());
/**
* Converts minecraft property class into TAB skin class.
*
* @param property
* Property to convert
* @return Converted skin
*/
@NotNull
public static TabList.Skin propertyToSkin(@NotNull Property property) {
if (serverVersion.getNetworkId() >= ProtocolVersion.V1_20_2.getNetworkId()) return loaderNew.propertyToSkin(property);
return loader1_14_4.propertyToSkin(property);
}
/**
* Returns {@code true} if packet is player spawn packet, {@code false} if not.
*
* @param packet
* Packet to check
* @return {@code true} if packet is player spawn packet, {@code false} if not
*/
public static boolean isSpawnPlayerPacket(@NotNull Packet<?> packet) {
if (serverVersion.getNetworkId() >= ProtocolVersion.V1_20_2.getNetworkId()) return loaderNew.isSpawnPlayerPacket(packet);
return loader1_14_4.isSpawnPlayerPacket(packet);
}
/**
* Returns player ID of given player spawn packet.
*
* @param packet
* Player spawn packet
* @return Player ID
*/
public static int getSpawnedPlayerId(@NotNull Packet<?> packet) {
if (serverVersion.getNetworkId() >= ProtocolVersion.V1_20_2.getNetworkId()) return loaderNew.getSpawnedPlayerId(packet);
return loader1_14_4.getSpawnedPlayerId(packet);
}
/**
* Returns player's ping.
*
* @param player
* Player to get ping of
* @return Player's ping
*/
public static int getPing(@NotNull ServerPlayer player) {
if (serverVersion.getNetworkId() >= ProtocolVersion.V1_20_2.getNetworkId()) return loaderNew.getPing(player);
return loader1_14_4.getPing(player);
}
/**
* Returns display slot of given display objective packet.
*
* @param packet
* Display objective packet
* @return Display slot of packet
*/
public static int getDisplaySlot(@NotNull ClientboundSetDisplayObjectivePacket packet) {
if (serverVersion.getNetworkId() >= ProtocolVersion.V1_20_2.getNetworkId()) return loaderNew.getDisplaySlot(packet);
return loader1_14_4.getDisplaySlot(packet);
}
/**
* Creates display objective packet with given parameters.
*
* @param slot
* Display slot
* @param objective
* Objective to display
* @return Display objective packet with given parameters
*/
@NotNull
public static Packet<?> setDisplaySlot(int slot, @NotNull Objective objective) {
if (serverVersion.getNetworkId() >= ProtocolVersion.V1_20_2.getNetworkId()) return loaderNew.setDisplaySlot(slot, objective);
return loader1_14_4.setDisplaySlot(slot, objective);
}
/**
* Returns player's network channel.
*
* @param player
* Player to get channel of
* @return Player's channel
*/
@NotNull
public static Channel getChannel(@NotNull ServerPlayer player) {
if (serverVersion.getNetworkId() >= ProtocolVersion.V1_20_2.getNetworkId()) return loaderNew.getChannel(player);
return loader1_14_4.getChannel(player);
}
/**
* Returns server's current milliseconds per tick.
*
* @param server
* Server to get MSPT value from
* @return Server's milliseconds per tick
*/
public static float getMSPT(@NotNull MinecraftServer server) {
if (serverVersion.getNetworkId() >= ProtocolVersion.V1_20_3.getNetworkId()) return loaderNew.getMSPT(server);
return loader1_14_4.getMSPT(server);
}
/**
* Creates new remove score packet with given parameters.
*
* @param objective
* Objective to remove score from
* @param holder
* Score holder to remove
* @return Remove score packet with given parameters
*/
@NotNull
public static Packet<?> removeScore(@NotNull String objective, @NotNull String holder) {
if (serverVersion.getNetworkId() >= ProtocolVersion.V1_20_3.getNetworkId()) return loaderNew.removeScore(objective, holder);
return loader1_14_4.removeScore(objective, holder);
}
/**
* Creates new objective with given parameters.
*
* @param name
* Objective name
* @param displayName
* Objective display name
* @param renderType
* Score render type
* @param numberFormat
* Score number format (1.20.3+)
* @return New objective with given parameters
*/
@NotNull
public static Objective newObjective(@NotNull String name, @NotNull Component displayName,
@NotNull RenderType renderType, @Nullable Component numberFormat) {
if (serverVersion.getNetworkId() >= ProtocolVersion.V1_20_3.getNetworkId()) return loaderNew.newObjective(name, displayName, renderType, numberFormat);
return loader1_14_4.newObjective(name, displayName, renderType, numberFormat);
}
/**
* Creates a new set score packet with given parameters.
*
* @param objective
* Objective to set score in
* @param holder
* Score holder
* @param score
* Score value
* @param displayName
* Display name of score holder (1.20.3+)
* @param numberFormat
* Number format of score value (1.20.3+)
* @return New set score packet with given parameters
*/
@NotNull
public static Packet<?> setScore(@NotNull String objective, @NotNull String holder, int score,
@Nullable Component displayName, @Nullable Component numberFormat) {
if (serverVersion.getNetworkId() >= ProtocolVersion.V1_20_3.getNetworkId()) return loaderNew.setScore(objective, holder, score, displayName, numberFormat);
return loader1_14_4.setScore(objective, holder, score, displayName, numberFormat);
}
}
@@ -1,135 +1,25 @@
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;
/**
* Pipeline injector for Fabric.
*/
public class FabricPipelineInjector extends NettyPipelineInjector {
/**
* Constructs new instance.
*/
public FabricPipelineInjector() {
super("packet_handler");
}
@Override
@Nullable
@NotNull
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;
return FabricMultiVersion.getChannel(((FabricTabPlayer)player).getPlayer());
}
}
@@ -1,11 +1,16 @@
package me.neznamy.tab.platforms.fabric;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import me.lucko.fabric.api.permissions.v0.Permissions;
import me.neznamy.tab.platforms.fabric.features.FabricNameTagX;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.backend.BackendPlatform;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.chat.SimpleComponent;
import me.neznamy.tab.shared.chat.StructuredComponent;
import me.neznamy.tab.shared.chat.TabComponent;
import me.neznamy.tab.shared.features.injection.PipelineInjector;
import me.neznamy.tab.shared.features.nametags.NameTag;
import me.neznamy.tab.shared.features.types.TabFeature;
@@ -13,7 +18,7 @@ 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.commands.CommandSourceStack;
import net.minecraft.network.chat.Component;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerPlayer;
@@ -25,7 +30,18 @@ import java.io.File;
/**
* Platform implementation for Fabric
*/
public record FabricPlatform(MinecraftServer server) implements BackendPlatform {
@RequiredArgsConstructor
@Getter
public class FabricPlatform implements BackendPlatform {
/** Minecraft server reference */
private final MinecraftServer server;
/** Flag tracking presence of permission API */
private final boolean fabricPermissionsApi = FabricLoader.getInstance().isModLoaded("fabric-permissions-api-v0");
/** Server version */
private final ProtocolVersion serverVersion = ProtocolVersion.fromFriendlyName(FabricTAB.minecraftVersion);
@Override
public void registerUnknownPlaceholder(@NotNull String identifier) {
@@ -64,19 +80,19 @@ public record FabricPlatform(MinecraftServer server) implements BackendPlatform
}
@Override
public void logInfo(@NotNull IChatBaseComponent message) {
MinecraftServer.LOGGER.info("[TAB] " + message.toRawText());
public void logInfo(@NotNull TabComponent message) {
FabricMultiVersion.logInfo(message);
}
@Override
public void logWarn(@NotNull IChatBaseComponent message) {
MinecraftServer.LOGGER.warn("[TAB] " + message.toRawText()); // Fabric console does not support colors
public void logWarn(@NotNull TabComponent message) {
FabricMultiVersion.logWarn(message);
}
@Override
@NotNull
public String getServerVersionInfo() {
return "[Fabric] " + SharedConstants.getCurrentVersion().getName();
return "[Fabric] " + FabricTAB.minecraftVersion;
}
@Override
@@ -94,29 +110,24 @@ public record FabricPlatform(MinecraftServer server) implements BackendPlatform
// 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 Component convertComponent(@NotNull TabComponent component, boolean modern) {
if (component instanceof SimpleComponent) return FabricMultiVersion.newTextComponent(((SimpleComponent) component).getText());
StructuredComponent component1 = (StructuredComponent) component;
Component nmsComponent = FabricMultiVersion.newTextComponent(component1.getText());
FabricMultiVersion.setStyle(nmsComponent, FabricMultiVersion.convertModifier(component1.getModifier(), modern));
for (StructuredComponent extra : component1.getExtra()) {
FabricMultiVersion.addSibling(nmsComponent, convertComponent(extra, modern));
}
return nmsComponent;
}
@Override
@@ -126,6 +137,20 @@ public record FabricPlatform(MinecraftServer server) implements BackendPlatform
@Override
public double getMSPT() {
return server.getAverageTickTime();
return FabricMultiVersion.getMSPT(server);
}
/**
* Checks for permission and returns the result.
*
* @param source
* Source to check permission of
* @param permission
* Permission node to check
* @return {@code true} if has permission, {@code false} if not
*/
public boolean hasPermission(@NotNull CommandSourceStack source, @NotNull String permission) {
if (source.hasPermission(4)) return true;
return fabricPermissionsApi && Permissions.check(source, permission);
}
}
@@ -1,140 +1,138 @@
package me.neznamy.tab.platforms.fabric;
import java.util.Collection;
import java.util.Objects;
import lombok.NonNull;
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.chat.TabComponent;
import me.neznamy.tab.shared.platform.Scoreboard;
import net.minecraft.ChatFormatting;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.ClientboundSetDisplayObjectivePacket;
import net.minecraft.network.protocol.game.ClientboundSetObjectivePacket;
import net.minecraft.network.protocol.game.ClientboundSetScorePacket;
import net.minecraft.server.ServerScoreboard;
import net.minecraft.world.scores.Objective;
import net.minecraft.world.scores.PlayerTeam;
import net.minecraft.world.scores.Team;
import net.minecraft.world.scores.criteria.ObjectiveCriteria;
import org.jetbrains.annotations.NotNull;
import net.minecraft.world.scores.criteria.ObjectiveCriteria.RenderType;
import org.jetbrains.annotations.Nullable;
public class FabricScoreboard extends Scoreboard<FabricTabPlayer> {
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
/**
* Scoreboard implementation for Fabric using packets.
*/
public class FabricScoreboard extends Scoreboard<FabricTabPlayer, 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));
private final Map<String, Objective> objectives = new HashMap<>();
/**
* Constructs new instance with given player.
*
* @param player
* Player this scoreboard will belong to
*/
public FabricScoreboard(FabricTabPlayer player) {
super(player);
}
@Override
public void setDisplaySlot(@NotNull DisplaySlot slot, @NotNull String objective) {
player.sendPacket(
FabricMultiVersion.newDisplayObjective(
slot.ordinal(),
new Objective(
dummyScoreboard,
objective,
ObjectiveCriteria.DUMMY,
EMPTY_COMPONENT,
ObjectiveCriteria.RenderType.INTEGER
)
)
);
public void setDisplaySlot0(int slot, @NonNull String objective) {
player.sendPacket(FabricMultiVersion.setDisplaySlot(slot, objectives.get(objective)));
}
@Override
public void registerObjective0(@NotNull String objectiveName, @NotNull String title, @NotNull HealthDisplay display) {
player.sendPacket(
new ClientboundSetObjectivePacket(
new Objective(
dummyScoreboard,
objectiveName,
ObjectiveCriteria.DUMMY,
player.getPlatform().toComponent(IChatBaseComponent.optimizedComponent(title), player.getVersion()),
ObjectiveCriteria.RenderType.valueOf(display.name())
),
0
)
public void registerObjective0(@NonNull String objectiveName, @NonNull String title, int display,
@Nullable Component numberFormat) {
Objective obj = FabricMultiVersion.newObjective(
objectiveName,
toComponent(title),
RenderType.values()[display],
numberFormat
);
objectives.put(objectiveName, obj);
player.sendPacket(new ClientboundSetObjectivePacket(obj, ObjectiveAction.REGISTER));
}
@Override
public void unregisterObjective0(@NotNull String objectiveName) {
player.sendPacket(
new ClientboundSetObjectivePacket(
new Objective(
dummyScoreboard,
objectiveName,
ObjectiveCriteria.DUMMY,
EMPTY_COMPONENT,
ObjectiveCriteria.RenderType.INTEGER
),
1
)
);
public void unregisterObjective0(@NonNull String objectiveName) {
player.sendPacket(new ClientboundSetObjectivePacket(objectives.remove(objectiveName), ObjectiveAction.UNREGISTER));
}
@Override
public void updateObjective0(@NotNull String objectiveName, @NotNull String title, @NotNull HealthDisplay display) {
player.sendPacket(
new ClientboundSetObjectivePacket(
new Objective(
dummyScoreboard,
objectiveName,
ObjectiveCriteria.DUMMY,
player.getPlatform().toComponent(IChatBaseComponent.optimizedComponent(title), player.getVersion()),
ObjectiveCriteria.RenderType.valueOf(display.name())
),
2
)
);
public void updateObjective0(@NonNull String objectiveName, @NonNull String title, int display,
@Nullable Component numberFormat) {
Objective obj = objectives.get(objectiveName);
obj.setDisplayName(toComponent(title));
obj.setRenderType(RenderType.values()[display]);
player.sendPacket(new ClientboundSetObjectivePacket(obj, ObjectiveAction.UPDATE));
}
@Override
public void registerTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix,
@NotNull NameVisibility visibility, @NotNull CollisionRule collision,
@NotNull Collection<String> players, int options) {
public void registerTeam0(@NonNull String name, @NonNull String prefix, @NonNull String suffix,
@NonNull NameVisibility visibility, @NonNull CollisionRule collision,
@NonNull Collection<String> players, int options, @NonNull EnumChatFormat color) {
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.setColor(ChatFormatting.valueOf(color.name()));
team.setCollisionRule(Team.CollisionRule.valueOf(collision.name()));
team.setNameTagVisibility(Team.Visibility.valueOf(visibility.name()));
team.setPlayerPrefix(player.getPlatform().toComponent(IChatBaseComponent.optimizedComponent(prefix), player.getVersion()));
team.setPlayerSuffix(player.getPlatform().toComponent(IChatBaseComponent.optimizedComponent(suffix), player.getVersion()));
team.setPlayerPrefix(toComponent(prefix));
team.setPlayerSuffix(toComponent(suffix));
team.getPlayers().addAll(players);
player.sendPacket(FabricMultiVersion.createTeam(team));
player.sendPacket(FabricMultiVersion.registerTeam(team));
}
@Override
public void unregisterTeam0(@NotNull String name) {
player.sendPacket(FabricMultiVersion.removeTeam(new PlayerTeam(dummyScoreboard, name)));
public void unregisterTeam0(@NonNull String name) {
player.sendPacket(FabricMultiVersion.unregisterTeam(new PlayerTeam(dummyScoreboard, name)));
}
@Override
public void updateTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix,
@NotNull NameVisibility visibility, @NotNull CollisionRule collision, int options) {
public void updateTeam0(@NonNull String name, @NonNull String prefix, @NonNull String suffix,
@NonNull NameVisibility visibility, @NonNull CollisionRule collision,
int options, @NonNull EnumChatFormat color) {
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.setColor(ChatFormatting.valueOf(color.name()));
team.setCollisionRule(Team.CollisionRule.valueOf(collision.name()));
team.setNameTagVisibility(Team.Visibility.valueOf(visibility.name()));
team.setPlayerPrefix(player.getPlatform().toComponent(IChatBaseComponent.optimizedComponent(prefix), player.getVersion()));
team.setPlayerSuffix(player.getPlatform().toComponent(IChatBaseComponent.optimizedComponent(suffix), player.getVersion()));
team.setPlayerPrefix(toComponent(prefix));
team.setPlayerSuffix(toComponent(suffix));
player.sendPacket(FabricMultiVersion.updateTeam(team));
}
@Override
public void setScore0(@NotNull String objective, @NotNull String playerName, int score) {
player.sendPacket(new ClientboundSetScorePacket(ServerScoreboard.Method.CHANGE, objective, playerName, score));
public void setScore0(@NonNull String objective, @NonNull String scoreHolder, int score,
@Nullable Component displayName, @Nullable Component numberFormat) {
player.sendPacket(FabricMultiVersion.setScore(objective, scoreHolder, score, displayName, numberFormat));
}
@Override
public void removeScore0(@NotNull String objective, @NotNull String playerName) {
player.sendPacket(new ClientboundSetScorePacket(ServerScoreboard.Method.REMOVE, objective, playerName, 0));
public void removeScore0(@NonNull String objective, @NonNull String scoreHolder) {
player.sendPacket(FabricMultiVersion.removeScore(objective, scoreHolder));
}
@Override
public void onPacketSend(@NonNull Object packet) {
if (packet instanceof ClientboundSetDisplayObjectivePacket display) {
TAB.getInstance().getFeatureManager().onDisplayObjective(player, FabricMultiVersion.getDisplaySlot(display), display.objectiveName);
}
if (packet instanceof ClientboundSetObjectivePacket objective) {
TAB.getInstance().getFeatureManager().onObjective(player, objective.method, objective.objectiveName);
}
if (isAntiOverrideTeams()) {
FabricMultiVersion.checkTeamPacket((Packet<?>) packet, this);
}
}
@NonNull
private Component toComponent(@NonNull String string) {
return TabComponent.optimized(string).convert(player.getVersion());
}
}
Loaded 100 of 366 files, more files were not shown because too many files have changed in this diff. Show more