Compare commits

..
419 Commits
Author SHA1 Message Date
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
364 changed files with 14271 additions and 11993 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
+4 -4
View File
@@ -8,14 +8,14 @@ jobs:
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:
+8 -7
View File
@@ -6,16 +6,16 @@ More information can be found at [Why TAB?](https://github.com/NEZNAMY/TAB/wiki/
# Download
### Releases
You can download full releases from [GitHub releases](https://github.com/NEZNAMY/TAB/releases),
[SpigotMC](https://www.spigotmc.org/resources/57806/) or [Modrinth](https://modrinth.com/plugin/tab-was-taken).
[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).
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.
@@ -46,12 +46,13 @@ Here is the full list of potential services and their status:
| Wiki updates<sup>1</sup> | ✔ |
| Feature requests | ❌ |
| Customer support<sup>2</sup> | ✔ |
| Free user support<sup>3</sup> | ❌ |
| Free user support<sup>3</sup> | ❌* |
| Optimization<sup>4</sup> | ✔ |
<sup>1</sup> - Adding new examples, guides and other kind of useful information people ask about.
<sup>2</sup> - Assistance with the plugin on discord, including answering any questions and helping with
configuration to those, who bought a copy of the plugin when it was purchasable.
<sup>3</sup> - Like <sup>2</sup>, but for anyone in the world who asks for it.
<sup>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 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))
}
}
+2 -11
View File
@@ -4,7 +4,7 @@ plugins {
allprojects {
group = "me.neznamy"
version = "4.1.0"
version = "4.1.5"
description = "An all-in-one solution that works"
ext.set("id", "tab")
@@ -20,16 +20,7 @@ val platforms = setOf(
projects.sponge8,
projects.fabric,
projects.fabric.v1144,
projects.fabric.v1152,
projects.fabric.v1165,
projects.fabric.v117,
projects.fabric.v1171,
projects.fabric.v1182,
projects.fabric.v1192,
projects.fabric.v1193,
projects.fabric.v1201,
projects.fabric.v1202,
projects.fabric.v1203
projects.fabric.v1182
).map { it.dependencyProject }
val special = setOf(
@@ -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,42 +1,25 @@
package me.neznamy.tab.platforms.bukkit;
import com.mojang.authlib.GameProfile;
import io.netty.channel.Channel;
import lombok.Getter;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
import me.neznamy.tab.platforms.bukkit.scoreboard.PacketScoreboard;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.features.nametags.NameTag;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.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.bukkit.Bukkit;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;
/**
* Pipeline injection for bukkit
* Pipeline injection for Bukkit 1.8+.
*/
public class BukkitPipelineInjector extends NettyPipelineInjector {
private static Method getHandle;
private static Field PLAYER_CONNECTION;
private static Field NETWORK_MANAGER;
private static Field CHANNEL;
@Getter
private static boolean available;
/** Function for getting player's channel */
private static FunctionWithException<TabPlayer, Channel> getChannel;
/**
* Constructs new instance
@@ -45,185 +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");
getHandle = Class.forName("org.bukkit.craftbukkit." + BukkitReflection.getServerPackage() + ".entity.CraftPlayer").getMethod("getHandle");
PLAYER_CONNECTION = ReflectionUtils.getOnlyField(EntityPlayer, PlayerConnection);
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);
}
CHANNEL = ReflectionUtils.getOnlyField(NetworkManager, Channel.class);
available = true;
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) {
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.RED.getFormat() + "[TAB] Failed to initialize NMS fields for " +
"network channel injection due to a compatibility error. This will make the following features not work: " +
"Unlimited nametag mode, anti-override for tablist formatting & nametags, detecting nickname change for compatibility " +
"with nick plugins and compatibility with other scoreboard plugins. " +
"Please update the plugin a to version with native support for your server version to unlock the features.");
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) {
return (Channel) CHANNEL.get(NETWORK_MANAGER.get(PLAYER_CONNECTION.get(getHandle.invoke(player.getPlayer()))));
}
@Override
@SneakyThrows
public void onDisplayObjective(@NotNull TabPlayer player, @NotNull Object packet) {
if (!PacketScoreboard.isAvailable()) return;
int position;
if (BukkitReflection.is1_20_2Plus()) {
position = ((Enum<?>)PacketScoreboard.displayPacketData.DisplayObjective_POSITION.get(packet)).ordinal();
} else {
position = PacketScoreboard.displayPacketData.DisplayObjective_POSITION.getInt(packet);
}
TAB.getInstance().getFeatureManager().onDisplayObjective(player, position,
(String) PacketScoreboard.displayPacketData.DisplayObjective_OBJECTIVE_NAME.get(packet));
}
@Override
@SneakyThrows
public void onObjective(@NotNull TabPlayer player, @NotNull Object packet) {
if (!PacketScoreboard.isAvailable()) return;
TAB.getInstance().getFeatureManager().onObjective(player,
PacketScoreboard.Objective_METHOD.getInt(packet),
(String) PacketScoreboard.Objective_OBJECTIVE_NAME.get(packet));
}
@Override
public boolean isDisplayObjective(@NotNull Object packet) {
if (!PacketScoreboard.isAvailable()) return false;
return PacketScoreboard.displayPacketData.DisplayObjectiveClass.isInstance(packet);
}
@Override
public boolean isObjective(@NotNull Object packet) {
if (!PacketScoreboard.isAvailable()) return false;
return PacketScoreboard.ObjectivePacketClass.isInstance(packet);
}
@Override
public boolean isTeam(@NotNull Object packet) {
if (!PacketScoreboard.isAvailable()) return false;
return PacketScoreboard.teamPacketData.TeamPacketClass.isInstance(packet);
}
@Override
public boolean isPlayerInfo(@NotNull Object packet) {
return BukkitTabList.PlayerInfoClass.isInstance(packet);
}
@Override
public void onPlayerInfo(@NotNull TabPlayer receiver, @NotNull Object packet) {
if (BukkitReflection.is1_19_3Plus()) {
onPlayerInfo1_19_3(receiver, packet);
} else {
onPlayerInfo1_19_2(receiver, packet);
}
}
@SneakyThrows
private void onPlayerInfo1_19_3(@NotNull TabPlayer receiver, @NotNull Object packet) {
List<String> actions = ((EnumSet<?>)BukkitTabList.ACTION.get(packet)).stream().map(Enum::name).collect(Collectors.toList());
List<Object> updatedList = new ArrayList<>();
for (Object nmsData : (List<?>) BukkitTabList.PLAYERS.get(packet)) {
GameProfile profile = (GameProfile) BukkitTabList.PlayerInfoData_Profile.get(nmsData);
UUID id;
id = (UUID) BukkitTabList.PlayerInfoData_UUID.get(nmsData);
Object displayName = null;
int latency = 0;
if (actions.contains(TabList.Action.UPDATE_DISPLAY_NAME.name())) {
displayName = BukkitTabList.PlayerInfoData_DisplayName.get(nmsData);
IChatBaseComponent newDisplayName = TAB.getInstance().getFeatureManager().onDisplayNameChange(receiver, id);
if (newDisplayName != null) displayName = ((BukkitTabPlayer)receiver).getTabList().toComponent(newDisplayName);
}
if (actions.contains(TabList.Action.UPDATE_LATENCY.name())) {
latency = TAB.getInstance().getFeatureManager().onLatencyChange(receiver, id, BukkitTabList.PlayerInfoData_Latency.getInt(nmsData));
}
if (actions.contains(TabList.Action.ADD_PLAYER.name())) {
TAB.getInstance().getFeatureManager().onEntryAdd(receiver, id, profile.getName());
}
// 1.19.3 is using records, which do not allow changing final fields, need to rewrite the list entirely
updatedList.add(BukkitTabList.newPlayerInfoData.newInstance(
id,
profile,
BukkitTabList.PlayerInfoData_Listed.getBoolean(nmsData),
latency,
BukkitTabList.PlayerInfoData_GameMode.get(nmsData),
displayName,
BukkitTabList.PlayerInfoData_RemoteChatSession.get(nmsData)));
}
BukkitTabList.PLAYERS.set(packet, updatedList);
}
@SneakyThrows
private void onPlayerInfo1_19_2(@NotNull TabPlayer receiver, @NotNull Object packet) {
String action = BukkitTabList.ACTION.get(packet).toString();
for (Object nmsData : (List<?>) BukkitTabList.PLAYERS.get(packet)) {
GameProfile profile = (GameProfile) BukkitTabList.PlayerInfoData_Profile.get(nmsData);
UUID id = profile.getId();
if (action.equals(TabList.Action.UPDATE_DISPLAY_NAME.name()) || action.equals(TabList.Action.ADD_PLAYER.name())) {
Object displayName = BukkitTabList.PlayerInfoData_DisplayName.get(nmsData);
IChatBaseComponent newDisplayName = TAB.getInstance().getFeatureManager().onDisplayNameChange(receiver, id);
if (newDisplayName != null) displayName = ((BukkitTabPlayer)receiver).getTabList().toComponent(newDisplayName);
BukkitTabList.PlayerInfoData_DisplayName.set(nmsData, displayName);
}
if (action.equals(TabList.Action.UPDATE_LATENCY.name()) || action.equals(TabList.Action.ADD_PLAYER.name())) {
int latency = TAB.getInstance().getFeatureManager().onLatencyChange(receiver, id, BukkitTabList.PlayerInfoData_Latency.getInt(nmsData));
BukkitTabList.PlayerInfoData_Latency.set(nmsData, latency);
}
if (action.equals(TabList.Action.ADD_PLAYER.name())) {
TAB.getInstance().getFeatureManager().onEntryAdd(receiver, id, profile.getName());
}
}
}
@Override
public boolean isLogin(@NotNull Object packet) {
return false;
}
@SuppressWarnings("unchecked")
@Override
@SneakyThrows
public void modifyPlayers(@NotNull Object packetPlayOutScoreboardTeam) {
if (!PacketScoreboard.isAvailable()) return;
if (TAB.getInstance().getNameTagManager() == null) return;
int action = PacketScoreboard.teamPacketData.TeamPacket_ACTION.getInt(packetPlayOutScoreboardTeam);
if (action == 1 || action == 2 || action == 4) return;
Collection<String> players = (Collection<String>) PacketScoreboard.teamPacketData.TeamPacket_PLAYERS.get(packetPlayOutScoreboardTeam);
String teamName = (String) PacketScoreboard.teamPacketData.TeamPacket_NAME.get(packetPlayOutScoreboardTeam);
if (players == null) return;
//creating a new list to prevent NoSuchFieldException in minecraft packet encoder when a player is removed
Collection<String> newList = new ArrayList<>();
for (String entry : players) {
TabPlayer p = 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.teamPacketData.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.*;
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,44 +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 {
DataWatcher.load();
PacketEntityView.load();
BukkitTabList.load();
return true;
} catch (Exception ex) {
if (ProtocolVersion.fromFriendlyName(Bukkit.getBukkitVersion().split("-")[0]) == ProtocolVersion.UNKNOWN_SERVER_VERSION) {
Bukkit.getConsoleSender().sendMessage(String.format(
"%s[TAB] Your server version is not compatible. This plugin version was made for %s - %s. Disabling.",
EnumChatFormat.RED.getFormat(), ProtocolVersion.V1_5.getFriendlyName(), ProtocolVersion.LATEST_KNOWN_VERSION.getFriendlyName()
));
} else {
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.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,269 +0,0 @@
package me.neznamy.tab.platforms.bukkit;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
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.EnumChatFormat;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.TabList.Entry.Builder;
import me.neznamy.tab.shared.util.ComponentCache;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.bukkit.Bukkit;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
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 {
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 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;
@Nullable
private static SkinData skinData;
private static Method ChatSerializer_DESERIALIZE;
private static final ComponentCache<IChatBaseComponent, Object> componentCache = new ComponentCache<>(1000,
(component, clientVersion) -> ChatSerializer_DESERIALIZE.invoke(null, component.toString(clientVersion)));
/** Player this TabList belongs to */
private final BukkitTabPlayer player;
public static void load() throws NoSuchMethodException, ClassNotFoundException {
if (BukkitReflection.getMinorVersion() < 8) return; // Not supported
Class<?> ChatSerializer = BukkitReflection.getClass("network.chat.Component$Serializer",
"network.chat.IChatBaseComponent$ChatSerializer", "IChatBaseComponent$ChatSerializer", "ChatSerializer");
ChatSerializer_DESERIALIZE = ReflectionUtils.getMethods(ChatSerializer, Object.class, String.class).get(0);
Class<?> IChatBaseComponent = BukkitReflection.getClass("network.chat.Component", "network.chat.IChatBaseComponent", "IChatBaseComponent");
Class<Enum> EnumGamemodeClass = (Class<Enum>) BukkitReflection.getClass("world.level.GameType",
"world.level.EnumGamemode", "EnumGamemode", "WorldSettings$EnumGamemode");
EntityPlayer = BukkitReflection.getClass("server.level.ServerPlayer", "server.level.EntityPlayer", "EntityPlayer");
ActionClass = (Class<Enum>) BukkitReflection.getClass(
"network.protocol.game.ClientboundPlayerInfoUpdatePacket$Action", // Mojang 1.19.3+
"network.protocol.game.ClientboundPlayerInfoPacket$Action", // Mojang 1.17 - 1.19.2
"network.protocol.game.ClientboundPlayerInfoUpdatePacket$a", // Bukkit 1.19.3+
"network.protocol.game.PacketPlayOutPlayerInfo$EnumPlayerInfoAction", // Bukkit 1.17 - 1.19.2
"PacketPlayOutPlayerInfo$EnumPlayerInfoAction", // Bukkit 1.8.1 - 1.16.5
"EnumPlayerInfoAction" // Bukkit 1.8.0
);
PlayerInfoClass = BukkitReflection.getClass("network.protocol.game.ClientboundPlayerInfoUpdatePacket",
"network.protocol.game.ClientboundPlayerInfoPacket",
"network.protocol.game.PacketPlayOutPlayerInfo", "PacketPlayOutPlayerInfo");
Class<?> playerInfoDataClass = BukkitReflection.getClass(
"network.protocol.game.ClientboundPlayerInfoUpdatePacket$Entry", // Mojang 1.19.3
"network.protocol.game.ClientboundPlayerInfoPacket$PlayerUpdate", // Mojang 1.17 - 1.19.2
"network.protocol.game.ClientboundPlayerInfoUpdatePacket$b", // Bukkit 1.19.3+
"network.protocol.game.PacketPlayOutPlayerInfo$PlayerInfoData", // Bukkit 1.17 - 1.19.2
"PacketPlayOutPlayerInfo$PlayerInfoData", // Bukkit 1.8.1 - 1.16.5
"PlayerInfoData" // Bukkit 1.8.0
);
PLAYERS = ReflectionUtils.getOnlyField(PlayerInfoClass, List.class);
newPlayerInfoData = playerInfoDataClass.getConstructors()[0]; // #1105, a specific 1.8.8 fork has 2 constructors
PlayerInfoData_Profile = ReflectionUtils.getOnlyField(playerInfoDataClass, GameProfile.class);
PlayerInfoData_Latency = ReflectionUtils.getOnlyField(playerInfoDataClass, int.class);
PlayerInfoData_GameMode = ReflectionUtils.getOnlyField(playerInfoDataClass, EnumGamemodeClass);
PlayerInfoData_DisplayName = ReflectionUtils.getOnlyField(playerInfoDataClass, IChatBaseComponent);
if (BukkitReflection.is1_19_3Plus()) {
ClientboundPlayerInfoRemovePacket = BukkitReflection.getClass("network.protocol.game.ClientboundPlayerInfoRemovePacket");
Class<?> RemoteChatSession$Data = BukkitReflection.getClass("network.chat.RemoteChatSession$Data", "network.chat.RemoteChatSession$a");
newClientboundPlayerInfoRemovePacket = ClientboundPlayerInfoRemovePacket.getConstructor(List.class);
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")
};
try {
skinData = new SkinData();
} catch (Exception e) {
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.RED.getFormat() + "[TAB] Failed to initialize NMS fields for " +
"getting player's game profile due to a compatibility error. This will " +
"result in player skins not properly working in layout feature. " +
"Please update the plugin a to version with native support for your server version for optimal experience.");
}
}
@Override
@SneakyThrows
public void removeEntry(@NotNull UUID entry) {
if (PlayerInfoClass == null) return;
if (ClientboundPlayerInfoRemovePacket != null) {
//1.19.3+
player.sendPacket(newClientboundPlayerInfoRemovePacket.newInstance(Collections.singletonList(entry)));
} else {
//1.19.2-
player.sendPacket(createPacket(Action.REMOVE_PLAYER, new Builder(entry).build()));
}
}
@Override
public void updateDisplayName(@NotNull UUID entry, @Nullable IChatBaseComponent displayName) {
if (PlayerInfoClass == null) return;
player.sendPacket(createPacket(Action.UPDATE_DISPLAY_NAME, new Builder(entry).displayName(displayName).build()));
}
@Override
public void updateLatency(@NotNull UUID entry, int latency) {
if (PlayerInfoClass == null) return;
player.sendPacket(createPacket(Action.UPDATE_LATENCY, new Builder(entry).latency(latency).build()));
}
@Override
public void updateGameMode(@NotNull UUID entry, int gameMode) {
if (PlayerInfoClass == null) return;
player.sendPacket(createPacket(Action.UPDATE_GAME_MODE, new Builder(entry).gameMode(gameMode).build()));
}
@Override
public void addEntry(@NotNull Entry entry) {
if (PlayerInfoClass == null) 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;
}
public Object toComponent(IChatBaseComponent component) {
return componentCache.get(component, player.getVersion());
}
@NotNull
private GameProfile createProfile(@NotNull UUID id, @Nullable String name, @Nullable Skin skin) {
GameProfile profile = new GameProfile(id, name == null ? "" : name);
if (skin != null) {
profile.getProperties().put(TabList.TEXTURES_PROPERTY,
new Property(TabList.TEXTURES_PROPERTY, skin.getValue(), skin.getSignature()));
}
return profile;
}
@Nullable
public Skin getSkin() {
if (skinData == null) return null;
return skinData.getSkin(player);
}
private static class SkinData {
private final Method getHandle;
private final Method getProfile;
@SneakyThrows
public SkinData() {
Class<?> EntityHuman = BukkitReflection.getClass("world.entity.player.Player", "world.entity.player.EntityHuman", "EntityHuman");
getHandle = Class.forName("org.bukkit.craftbukkit." + BukkitReflection.getServerPackage() + ".entity.CraftPlayer").getMethod("getHandle");
// There is only supposed to be one, however there are exceptions:
// #1 - CatServer adds another method
// #2 - Random mods may perform deep hack into the server and add another one (see #1089)
// Get first and hope for the best, alternatively players may not have correct skins in layout, but who cares
getProfile = ReflectionUtils.getMethods(EntityHuman, GameProfile.class).get(0);
}
@Nullable
@SneakyThrows
public Skin getSkin(@NotNull BukkitTabPlayer player) {
Collection<Property> col = ((GameProfile) getProfile.invoke(getHandle.invoke(player.getPlayer()))).getProperties().get(TEXTURES_PROPERTY);
if (col.isEmpty()) return null; //offline mode
Property property = col.iterator().next();
if (BukkitReflection.is1_20_2Plus()) {
return new Skin(
(String) property.getClass().getMethod("value").invoke(property),
(String) property.getClass().getMethod("signature").invoke(property)
);
} else {
return new Skin(property.getValue(), property.getSignature());
}
}
}
}
@@ -1,25 +1,20 @@
package me.neznamy.tab.platforms.bukkit;
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.nms.PacketSender;
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.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;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
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.shared.platform.Scoreboard;
import me.neznamy.tab.shared.backend.BackendTabPlayer;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.md_5.bungee.chat.ComponentSerializer;
import org.bukkit.entity.Player;
import org.bukkit.metadata.MetadataValue;
import org.bukkit.potion.PotionEffectType;
@@ -33,20 +28,17 @@ import org.jetbrains.annotations.Nullable;
@Getter
public class BukkitTabPlayer extends BackendTabPlayer {
private static final boolean spigot = ReflectionUtils.classExists("org.bukkit.entity.Player$Spigot");
@NotNull
private final Scoreboard<BukkitTabPlayer, ?> scoreboard = ScoreboardLoader.getInstance().apply(this);
@NotNull
private final Scoreboard<BukkitTabPlayer> scoreboard = ScoreboardLoader.getInstance().apply(this);
private final TabListBase<?> tabList = TabListBase.getInstance().apply(this);
@NotNull
private final BukkitTabList tabList = new BukkitTabList(this);
private final BossBar bossBar = BossBarLoader.findInstance(this);
@NotNull
private final BossBar bossBar = BukkitReflection.getMinorVersion() >= 9 ? new BukkitBossBar(this) :
getVersion().getMinorVersion() >= 9 ? new ViaBossBar(this) : new EntityBossBar(this);
@NotNull
private final EntityView entityView = new PacketEntityView(this);
private final EntityView entityView = PacketEntityView.isAvailable() ? new PacketEntityView(this) : new DummyEntityView();
/**
* Constructs new instance with given bukkit player
@@ -57,7 +49,7 @@ public class BukkitTabPlayer extends BackendTabPlayer {
* bukkit player
*/
public BukkitTabPlayer(@NotNull BukkitPlatform platform, @NotNull Player p) {
super(platform, p, p.getUniqueId(), p.getName(), p.getWorld().getName());
super(platform, p, p.getUniqueId(), p.getName(), p.getWorld().getName(), platform.getServerVersion().getNetworkId());
}
@Override
@@ -66,23 +58,13 @@ public class BukkitTabPlayer extends BackendTabPlayer {
}
@Override
@SneakyThrows
public int getPing() {
return PingRetriever.getPing(getPlayer());
}
@SneakyThrows
public void sendPacket(@NotNull Object nmsPacket) {
PacketSender.sendPacket(getPlayer(), nmsPacket);
}
@Override
public void sendMessage(@NotNull IChatBaseComponent message) {
if (spigot) {
getPlayer().spigot().sendMessage(ComponentSerializer.parse(message.toString(getVersion())));
} else {
getPlayer().sendMessage(BukkitUtils.toBukkitFormat(message, getVersion().getMinorVersion() >= 16));
}
public void sendMessage(@NotNull TabComponent message) {
getPlayer().sendMessage(getPlatform().toBukkitFormat(message, getVersion().supportsRGB()));
}
@Override
@@ -107,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;
@@ -119,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,8 +3,7 @@ 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.chat.TabComponent;
import me.neznamy.tab.shared.platform.BossBar;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import org.bukkit.Bukkit;
@@ -36,7 +35,7 @@ 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),
player.getPlatform().toBukkitFormat(TabComponent.optimized(title), player.getVersion().supportsRGB()),
org.bukkit.boss.BarColor.valueOf(color.name()),
styles[style.ordinal()]
);
@@ -47,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
@@ -5,7 +5,7 @@ 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,7 +4,7 @@ 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.chat.TabComponent;
import me.neznamy.tab.shared.platform.BossBar;
import me.neznamy.tab.api.bossbar.BarColor;
import me.neznamy.tab.api.bossbar.BarStyle;
@@ -36,7 +36,7 @@ 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,
BossColor.valueOf(color.name()),
styles[style.ordinal()]
@@ -47,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
@@ -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()));
}
}
@@ -2,7 +2,7 @@ package me.neznamy.tab.platforms.bukkit.features;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
import me.neznamy.tab.platforms.bukkit.nms.DataWatcher;
import me.neznamy.tab.platforms.bukkit.entity.DataWatcher;
import me.neznamy.tab.platforms.bukkit.platform.BukkitPlatform;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.backend.EntityData;
@@ -26,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;
/**
@@ -33,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());
@@ -74,7 +96,7 @@ public class BukkitNameTagX extends BackendNameTagX implements Listener {
@NotNull
public List<Integer> getPassengers(@NotNull Object entity) {
Entity vehicle = (Entity) entity;
if (BukkitReflection.getMinorVersion() >= 11) {
if (BukkitReflection.getMinorVersion() >= MULTI_PASSENGER_VERSION) {
return vehicle.getPassengers().stream().map(Entity::getEntityId).collect(Collectors.toList());
} else {
if (vehicle.getPassenger() != null) {
@@ -99,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
@@ -152,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;
}
@@ -165,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);
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
@@ -5,7 +5,6 @@ import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import lombok.Getter;
import me.neznamy.tab.platforms.bukkit.BukkitUtils;
import me.neznamy.tab.shared.features.types.Loadable;
import me.neznamy.tab.shared.features.types.UnLoadable;
@@ -29,12 +28,10 @@ import org.jetbrains.annotations.NotNull;
@SuppressWarnings("deprecation")
public class PerWorldPlayerList extends TabFeature implements Listener, Loadable, UnLoadable {
@Getter private final String featureName = "Per world PlayerList";
/** Config options */
private final boolean allowBypass = TAB.getInstance().getConfig().getBoolean("per-world-playerlist.allow-bypass-permission", false);
private final List<String> ignoredWorlds = TAB.getInstance().getConfig().getStringList("per-world-playerlist.ignore-effect-in-worlds", Arrays.asList("ignored_world", "build"));
private final Map<String, List<String>> sharedWorlds = TAB.getInstance().getConfig().getConfigurationSection("per-world-playerlist.shared-playerlist-world-groups");
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
@@ -63,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);
}
/**
@@ -116,4 +125,10 @@ public class PerWorldPlayerList extends TabFeature implements Listener, Loadable
}
return viewerWorldGroup.equals(targetWorldGroup);
}
@Override
@NotNull
public String getFeatureName() {
return "Per world PlayerList";
}
}
@@ -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.PacketSender;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import org.bukkit.Bukkit;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* A class responsible for sending header/footer to players on request.
*/
public abstract class HeaderFooter {
/** Instance of the class, null if no implementation is available */
@Getter
@Nullable
private static HeaderFooter instance;
/**
* Finds the best available instance for current server software.
*/
public static void findInstance() {
if (PaperHeaderFooter.isAvailable()) {
instance = new PaperHeaderFooter();
} else if (PacketHeaderFooter.isAvailable() && PacketSender.isAvailable()) {
instance = new PacketHeaderFooter();
} else if (BukkitHeaderFooter.isAvailable()) {
instance = new BukkitHeaderFooter();
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.RED.getFormat() + "[TAB] Failed to initialize NMS fields for " +
"sending Header/Footer due to a compatibility error. Using fallback solution using Bukkit API. " +
"This will drastically drop performance, as well as miss support for fonts (added in 1.16). " +
"Please update the plugin to version with proper support for your server version.");
} else {
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.RED.getFormat() + "[TAB] Failed to initialize NMS fields for " +
"sending Header/Footer due to a compatibility error. No fallback solution was found either. This will " +
"result in the feature not working. " +
"Please update the plugin to version with proper support for your server version.");
}
}
/**
* Sends header/footer to player.
*
* @param player
* Player to send header/footer to.
* @param header
* Header to use.
* @param footer
* Footer to use.
*/
public abstract void set(@NotNull BukkitTabPlayer player, @NotNull IChatBaseComponent header, @NotNull IChatBaseComponent footer);
}
@@ -1,73 +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.platform.TabPlayer;
import me.neznamy.tab.shared.util.ComponentCache;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* Header/footer sender that uses NMS to send packets. Available
* on all versions since 1.8 when the feature was added into the game.
*/
public class PacketHeaderFooter extends HeaderFooter {
private static Constructor<?> newHeaderFooter;
private static Field HEADER;
private static Field FOOTER;
private static Method ChatSerializer_DESERIALIZE;
private static final ComponentCache<IChatBaseComponent, Object> componentCache = new ComponentCache<>(1000,
(component, clientVersion) -> ChatSerializer_DESERIALIZE.invoke(null, component.toString(clientVersion)));
@Getter
private static boolean available;
static {
try {
if (BukkitReflection.getMinorVersion() >= 8) {
Class<?> IChatBaseComponent = BukkitReflection.getClass("network.chat.Component",
"network.chat.IChatBaseComponent", "IChatBaseComponent");
Class<?> HeaderFooterClass = BukkitReflection.getClass("network.protocol.game.ClientboundTabListPacket",
"network.protocol.game.PacketPlayOutPlayerListHeaderFooter", "PacketPlayOutPlayerListHeaderFooter");
Class<?> ChatSerializer = BukkitReflection.getClass("network.chat.Component$Serializer",
"network.chat.IChatBaseComponent$ChatSerializer", "IChatBaseComponent$ChatSerializer", "ChatSerializer");
ChatSerializer_DESERIALIZE = ReflectionUtils.getMethods(ChatSerializer, Object.class, String.class).get(0);
if (BukkitReflection.getMinorVersion() >= 17) {
newHeaderFooter = HeaderFooterClass.getConstructor(IChatBaseComponent, IChatBaseComponent);
} else {
newHeaderFooter = HeaderFooterClass.getConstructor();
HEADER = ReflectionUtils.getFields(HeaderFooterClass, IChatBaseComponent).get(0);
FOOTER = ReflectionUtils.getFields(HeaderFooterClass, IChatBaseComponent).get(1);
}
available = true;
}
} catch (Exception ignored) {
// Print exception to find out what went wrong
}
}
@SneakyThrows
@Override
public void set(@NotNull BukkitTabPlayer player, @NotNull IChatBaseComponent header, @NotNull IChatBaseComponent footer) {
if (BukkitReflection.getMinorVersion() >= 17) {
player.sendPacket(newHeaderFooter.newInstance(convert(header, player), convert(footer, player)));
} else {
Object packet = newHeaderFooter.newInstance();
HEADER.set(packet, convert(header, player));
FOOTER.set(packet, convert(footer, player));
player.sendPacket(packet);
}
}
private Object convert(IChatBaseComponent component, TabPlayer player) {
return componentCache.get(component, player.getVersion());
}
}
@@ -1,32 +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.
* Thanks to both cache and direct component conversion, this
* implementation is even faster than using NMS components
* and is therefore preferred if available.
*/
public class PaperHeaderFooter extends HeaderFooter {
/** Flag tracking whether this class can be used on current server */
@Getter
private static final boolean available = ReflectionUtils.classExists("net.kyori.adventure.text.Component") &&
ReflectionUtils.methodExists(Player.class, "sendPlayerListHeaderAndFooter", Component.class, Component.class);
@Override
public void set(@NotNull BukkitTabPlayer player, @NotNull IChatBaseComponent header, @NotNull IChatBaseComponent footer) {
player.getPlayer().sendPlayerListHeaderAndFooter(
AdventureHook.toAdventureComponent(header, player.getVersion()),
AdventureHook.toAdventureComponent(footer, player.getVersion())
);
}
}
@@ -0,0 +1,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,21 +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]);
/** Server version data */
private static final ServerVersion serverVersion = detectServerVersion();
/** Flag determining whether the server version is at least 1.19.3 or not */
@Getter
@@ -23,7 +27,7 @@ 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
@@ -33,6 +37,34 @@ public class BukkitReflection {
@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 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.
@@ -43,19 +75,39 @@ public class BukkitReflection {
* @throws ClassNotFoundException
* if class does not exist
*/
@SneakyThrows
public static Class<?> getClass(@NotNull String... names) throws ClassNotFoundException {
ClassLoader loader = BukkitReflection.class.getClassLoader();
for (String name : names) {
try {
if (minorVersion >= 17) {
return Class.forName("net.minecraft." + name);
} else {
return loader.loadClass("net.minecraft.server." + serverPackage + "." + name);
}
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 CraftBukkit class with given package and name.
*
* @param name
* Package and name of the class
* @return CraftBukkit class
* @throws ClassNotFoundException
* If class does not exist
*/
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.ChatModifier", "ChatModifier");
private final Class<Enum> EnumChatFormat = (Class<Enum>) BukkitReflection.getClass("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.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.IChatMutableComponent", "IChatMutableComponent");
Component_modifier = ReflectionUtils.getOnlyField(IChatMutableComponent, ChatModifier);
ChatBaseComponent_addSibling = ReflectionUtils.getOnlyMethod(IChatMutableComponent, IChatMutableComponent, IChatBaseComponent);
} else {
Class<?> ChatComponentText = BukkitReflection.getClass("network.chat.ChatComponentText", "ChatComponentText");
Constructor<?> newChatComponentText = ChatComponentText.getConstructor(String.class);
newTextComponent = newChatComponentText::newInstance;
Class<?> ChatBaseComponent = BukkitReflection.getClass("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.ChatHexColor", "ChatHexColor");
Class<?> ResourceLocation = BukkitReflection.getClass("resources.MinecraftKey", "MinecraftKey");
Class<?> chatClickable = BukkitReflection.getClass("network.chat.ChatClickable", "ChatClickable");
Class<?> chatHoverable = BukkitReflection.getClass("network.chat.ChatHoverable", "ChatHoverable");
ResourceLocation_tryParse = ReflectionUtils.getMethod(ResourceLocation, new String[]{"tryParse", "m_135820_", "a"}, String.class);
ChatHexColor_fromRGB = ReflectionUtils.getOnlyMethod(chatHexColor, chatHexColor, int.class);
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,283 +0,0 @@
package me.neznamy.tab.platforms.bukkit.nms;
import lombok.*;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.backend.EntityData;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.util.ComponentCache;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
/**
* A class representing the n.m.s.DataWatcher class to make work with it much easier
*/
@ToString
public class DataWatcher implements EntityData {
/** NMS Fields */
public static Class<?> DataWatcher;
private static Constructor<?> newDataWatcher;
private static Method DataWatcher_register;
private static Method DataWatcher_markDirty;
public static Method DataWatcher_packDirty;
private static Class<?> DataWatcherObject;
private static Constructor<?> newDataWatcherObject;
private static Object DataWatcherSerializer_BYTE;
private static Object DataWatcherSerializer_FLOAT;
private static Object DataWatcherSerializer_STRING;
private static Object DataWatcherSerializer_OPTIONAL_COMPONENT;
private static Object DataWatcherSerializer_BOOLEAN;
private static final int armorStandFlagsPosition = getArmorStandFlagsPosition();
private static Method ChatSerializer_DESERIALIZE;
private static final ComponentCache<IChatBaseComponent, Object> componentCache = new ComponentCache<>(1000,
(component, clientVersion) -> ChatSerializer_DESERIALIZE.invoke(null, component.toString(clientVersion)));
/** Watched data */
private final Map<Integer, Item> dataValues = new HashMap<>();
/**
* Returns armor stand flags position based on server version
*
* @return armor stand flags position based on server version
*/
private static int getArmorStandFlagsPosition() {
if (BukkitReflection.getMinorVersion() >= 17) {
//1.17.x, 1.18.x, 1.19.x, 1.20.x
return 15;
} else if (BukkitReflection.getMinorVersion() >= 15) {
//1.15.x, 1.16.x
return 14;
} else if (BukkitReflection.getMinorVersion() == 14) {
//1.14.x
return 13;
} else if (BukkitReflection.getMinorVersion() >= 10) {
//1.10.x - 1.13.x
return 11;
} else {
//1.8.x - 1.9.x
return 10;
}
}
/**
* Loads all required Fields and throws Exception if something went wrong
*/
public static void load() throws ReflectiveOperationException {
int minorVersion = BukkitReflection.getMinorVersion();
DataWatcher = BukkitReflection.getClass("network.syncher.SynchedEntityData", "network.syncher.DataWatcher", "DataWatcher");
if (minorVersion >= 7) {
Class<?> ChatSerializer = BukkitReflection.getClass("network.chat.Component$Serializer",
"network.chat.IChatBaseComponent$ChatSerializer", "IChatBaseComponent$ChatSerializer", "ChatSerializer");
ChatSerializer_DESERIALIZE = ReflectionUtils.getMethods(ChatSerializer, Object.class, String.class).get(0);
newDataWatcher = DataWatcher.getConstructor(BukkitReflection.getClass("world.entity.Entity", "Entity"));
} else {
newDataWatcher = DataWatcher.getConstructor();
}
if (minorVersion >= 9) {
DataWatcherObject = BukkitReflection.getClass("network.syncher.EntityDataAccessor",
"network.syncher.DataWatcherObject", "DataWatcherObject");
Class<?> dataWatcherRegistry = BukkitReflection.getClass("network.syncher.EntityDataSerializers",
"network.syncher.DataWatcherRegistry", "DataWatcherRegistry");
Class<?> dataWatcherSerializer = BukkitReflection.getClass("network.syncher.EntityDataSerializer",
"network.syncher.DataWatcherSerializer", "DataWatcherSerializer");
DataWatcher_register = ReflectionUtils.getMethod(
DataWatcher,
new String[]{"define", "register", "a", "m_135372_"}, // {Mojang, Bukkit, Bukkit 1.18+, Mohist 1.18.2}
DataWatcherObject, Object.class
);
newDataWatcherObject = DataWatcherObject.getConstructor(int.class, dataWatcherSerializer);
DataWatcherSerializer_BYTE = ReflectionUtils.getField(dataWatcherRegistry, "BYTE", "a", "f_135027_").get(null); // Mohist 1.18.2
DataWatcherSerializer_FLOAT = ReflectionUtils.getField(dataWatcherRegistry, "FLOAT", "c", "f_135029_").get(null); // Mohist 1.18.2
DataWatcherSerializer_STRING = ReflectionUtils.getField(dataWatcherRegistry, "STRING", "d", "f_135030_").get(null); // Mohist 1.18.2
if (BukkitReflection.is1_19_3Plus()) {
DataWatcherSerializer_OPTIONAL_COMPONENT = ReflectionUtils.getField(dataWatcherRegistry, "OPTIONAL_COMPONENT", "g").get(null);
if (BukkitReflection.is1_19_4Plus()) {
DataWatcherSerializer_BOOLEAN = ReflectionUtils.getField(dataWatcherRegistry, "BOOLEAN", "k").get(null);
} else {
DataWatcherSerializer_BOOLEAN = ReflectionUtils.getField(dataWatcherRegistry, "BOOLEAN", "j").get(null);
}
} else {
if (minorVersion >= 13) {
DataWatcherSerializer_OPTIONAL_COMPONENT = ReflectionUtils.getField(dataWatcherRegistry,
"OPTIONAL_COMPONENT", "f", "f_135032_").get(null); // Mohist 1.18.2
DataWatcherSerializer_BOOLEAN = ReflectionUtils.getField(dataWatcherRegistry,
"BOOLEAN", "i", "f_135035_").get(null); // Mohist 1.18.2
} else {
DataWatcherSerializer_BOOLEAN = dataWatcherRegistry.getDeclaredField("h").get(null);
}
}
} else {
DataWatcher_register = ReflectionUtils.getMethod(
DataWatcher,
new String[]{"func_75682_a", "a"}, int.class, // {Thermos 1.7.10, Bukkit}
Object.class
);
}
if (minorVersion >= 19) {
DataWatcher_packDirty = ReflectionUtils.getMethod(DataWatcher, new String[] {"packDirty", "b"}); // {Mojang, Bukkit}
}
if (BukkitReflection.is1_19_3Plus()) {
DataWatcher_markDirty = ReflectionUtils.getMethods(DataWatcher, void.class, DataWatcherObject).get(0);
}
}
/**
* Sets value into data values
*
* @param position
* data position
* @param serializer
* Object serializer (1.9+)
* @param value
* value
*/
public void setValue(int position, @Nullable Object serializer, @NotNull Object value) {
dataValues.put(position, new Item(position, serializer, value));
}
/**
* Writes entity byte flags
*
* @param flags
* flags to write
*/
public void setEntityFlags(byte flags) {
setValue(0, DataWatcherSerializer_BYTE, flags);
}
/**
* Writes entity custom name with position based on server version and value depending on client version (RGB or not)
*
* @param customName
* target custom name
* @param clientVersion
* client version
*/
public void setCustomName(@NotNull String customName, @NotNull ProtocolVersion clientVersion) {
if (BukkitReflection.getMinorVersion() >= 13) {
setValue(2, DataWatcherSerializer_OPTIONAL_COMPONENT,
Optional.of(componentCache.get(IChatBaseComponent.optimizedComponent(customName), clientVersion)));
} else if (BukkitReflection.getMinorVersion() >= 8) {
setValue(2, DataWatcherSerializer_STRING, customName);
} else {
//name length is limited to 64 characters on <1.8
String cutName = (customName.length() > 64 ? customName.substring(0, 64) : customName);
if (BukkitReflection.getMinorVersion() >= 6) {
setValue(10, null, cutName);
} else {
setValue(5, null, cutName);
}
}
}
/**
* Writes custom name visibility boolean
*
* @param visible
* if visible or not
*/
public void setCustomNameVisible(boolean visible) {
if (BukkitReflection.getMinorVersion() >= 9) {
setValue(3, DataWatcherSerializer_BOOLEAN, visible);
} else {
setValue(3, null, (byte)(visible?1:0));
}
}
/**
* Writes entity health
*
* @param health
* health of entity
*/
public void setHealth(float health) {
if (BukkitReflection.getMinorVersion() >= 6) {
setValue(6, DataWatcherSerializer_FLOAT, health);
} else {
setValue(16, null, (int)health);
}
}
/**
* Writes armor stand flags
*
* @param flags
* flags to write
*/
public void setArmorStandFlags(byte flags) {
setValue(armorStandFlagsPosition, DataWatcherSerializer_BYTE, flags);
}
/**
* Writes wither invulnerable time
* @param time
* Time, apparently
*/
public void setWitherInvulnerableTime(int time) {
if (BukkitReflection.getMinorVersion() > 8)
throw new UnsupportedOperationException("Not supported on 1.9+");
setValue(20, null, time);
}
/**
* Converts the class into an instance of NMS.DataWatcher
*
* @return an instance of NMS.DataWatcher with same data
*/
@SneakyThrows
public @NotNull Object build() {
Object nmsWatcher;
if (newDataWatcher.getParameterCount() == 1) { //1.7+
nmsWatcher = newDataWatcher.newInstance(new Object[] {null});
} else {
nmsWatcher = newDataWatcher.newInstance();
}
for (Item item : dataValues.values()) {
Object nmsObject = item.createObject();
DataWatcher_register.invoke(nmsWatcher, nmsObject, item.getValue());
if (BukkitReflection.is1_19_3Plus()) DataWatcher_markDirty.invoke(nmsWatcher, nmsObject);
}
return nmsWatcher;
}
@RequiredArgsConstructor
@Getter
public static class Item {
/** Item position */
private final int position;
/** Item serializer (1.9+) */
@Nullable
private final Object serializer;
/** Item value */
@NotNull
private final Object value;
/**
* Creates data watcher object with position and serializer
*
* @return NMS data watcher object
*/
@SneakyThrows
public Object createObject() {
if (BukkitReflection.getMinorVersion() >= 9) {
return newDataWatcherObject.newInstance(position, serializer);
} else {
return position;
}
}
}
}
@@ -1,409 +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.backend.EntityData;
import me.neznamy.tab.shared.backend.Location;
import me.neznamy.tab.shared.backend.entityview.EntityView;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.bukkit.Bukkit;
import org.bukkit.entity.EntityType;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.EnumMap;
import java.util.List;
import java.util.UUID;
@RequiredArgsConstructor
@SuppressWarnings("unchecked")
public class PacketEntityView implements EntityView {
/** PacketPlayOutEntityDestroy */
private static Class<?> EntityDestroyClass;
private static Constructor<?> newEntityDestroy;
private static Field EntityDestroy_Entities;
/** PacketPlayOutEntityMetadata */
private static Constructor<?> newEntityMetadata;
/** PacketPlayOutEntityTeleport */
private static Class<?> EntityTeleportClass;
private static Constructor<?> newEntityTeleport;
private static Field EntityTeleport_EntityId;
private static Field EntityTeleport_X;
private static Field EntityTeleport_Y;
private static Field EntityTeleport_Z;
/** PacketPlayOutSpawnEntityLiving */
private static Class<?> SpawnEntityClass;
private static Constructor<?> newSpawnEntity;
/** 1.17+ */
private static Object Vec3D_Empty;
private static Object EntityTypes_ARMOR_STAND;
/** 1.16.5- */
private static Field SpawnEntity_EntityId;
private static Field SpawnEntity_EntityType;
private static Field SpawnEntity_UUID;
private static Field SpawnEntity_X;
private static Field SpawnEntity_Y;
private static Field SpawnEntity_Z;
private static Field SpawnEntity_DataWatcher;
private static final EnumMap<EntityType, Integer> entityIds = new EnumMap<>(EntityType.class);
/** Other entity packets */
private static Class<?> PacketPlayOutEntity;
private static Field PacketPlayOutEntity_ENTITYID;
private static Class<?> PacketPlayOutEntityLook;
private static Class<?> PacketPlayOutNamedEntitySpawn;
private static Field PacketPlayOutNamedEntitySpawn_ENTITYID;
private static Class<?> ClientboundBundlePacket;
private static Field ClientboundBundlePacket_packets;
private static Object dummyEntity;
@Getter
private static boolean available;
/** Player this view belongs to */
private final BukkitTabPlayer player;
/**
* Loads all required classes and fields and throws Exception if something went wrong
*
* @throws ReflectiveOperationException
* If something fails
*/
public static void load() throws ReflectiveOperationException {
loadEntityMetadata();
loadEntityDestroy();
loadEntityTeleport();
loadEntityMove();
loadEntitySpawn();
if (BukkitReflection.is1_19_4Plus()) {
ClientboundBundlePacket = Class.forName("net.minecraft.network.protocol.game.ClientboundBundlePacket");
ClientboundBundlePacket_packets = ReflectionUtils.getOnlyField(ClientboundBundlePacket.getSuperclass(), Iterable.class);
}
available = true;
}
private static void loadEntityMetadata() throws ReflectiveOperationException {
// Class
Class<?> entityMetadataClass = getClass("network.protocol.game.ClientboundSetEntityDataPacket",
"network.protocol.game.PacketPlayOutEntityMetadata", "PacketPlayOutEntityMetadata", "Packet40EntityMetadata");
// Constructor
if (BukkitReflection.is1_19_3Plus()) {
newEntityMetadata = entityMetadataClass.getConstructor(int.class, List.class);
} else {
newEntityMetadata = entityMetadataClass.getConstructor(int.class, DataWatcher.DataWatcher, boolean.class);
}
}
private static void loadEntityDestroy() throws ReflectiveOperationException {
// Class
EntityDestroyClass = getClass("network.protocol.game.ClientboundRemoveEntitiesPacket",
"network.protocol.game.PacketPlayOutEntityDestroy", "PacketPlayOutEntityDestroy", "Packet29DestroyEntity");
// Constructor
try {
newEntityDestroy = EntityDestroyClass.getConstructor(int[].class);
} catch (NoSuchMethodException e) {
//1.17.0
newEntityDestroy = EntityDestroyClass.getConstructor(int.class);
}
// Field
EntityDestroy_Entities = ReflectionUtils.getOnlyField(EntityDestroyClass);
}
private static void loadEntityTeleport() throws ReflectiveOperationException {
// Class
EntityTeleportClass = getClass("network.protocol.game.ClientboundTeleportEntityPacket",
"network.protocol.game.PacketPlayOutEntityTeleport", "PacketPlayOutEntityTeleport", "Packet34EntityTeleport");
// Constructor
if (BukkitReflection.getMinorVersion() >= 17) {
newEntityTeleport = EntityTeleportClass.getConstructor(getClass("world.entity.Entity"));
} else {
newEntityTeleport = EntityTeleportClass.getConstructor();
}
// Fields
EntityTeleport_EntityId = ReflectionUtils.getFields(EntityTeleportClass, int.class).get(0);
if (BukkitReflection.getMinorVersion() >= 9) {
EntityTeleport_X = ReflectionUtils.getFields(EntityTeleportClass, double.class).get(0);
EntityTeleport_Y = ReflectionUtils.getFields(EntityTeleportClass, double.class).get(1);
EntityTeleport_Z = ReflectionUtils.getFields(EntityTeleportClass, double.class).get(2);
} else {
EntityTeleport_X = ReflectionUtils.getFields(EntityTeleportClass, int.class).get(1);
EntityTeleport_Y = ReflectionUtils.getFields(EntityTeleportClass, int.class).get(2);
EntityTeleport_Z = ReflectionUtils.getFields(EntityTeleportClass, int.class).get(3);
}
// Dummy armor stand for constructor
if (BukkitReflection.getMinorVersion() >= 17) {
Class<?> world = getClass("world.level.Level", "world.level.World", "World");
Class<?> entityArmorStand = getClass("world.entity.decoration.ArmorStand",
"world.entity.decoration.EntityArmorStand", "EntityArmorStand");
Constructor<?> newEntityArmorStand = entityArmorStand.getConstructor(world, double.class, double.class, double.class);
Method World_getHandle = Class.forName("org.bukkit.craftbukkit." + BukkitReflection.getServerPackage() + ".CraftWorld").getMethod("getHandle");
dummyEntity = newEntityArmorStand.newInstance(World_getHandle.invoke(Bukkit.getWorlds().get(0)), 0, 0, 0);
}
}
private static void loadEntityMove() throws ReflectiveOperationException {
// Classes
PacketPlayOutEntity = getClass("network.protocol.game.ClientboundMoveEntityPacket",
"network.protocol.game.PacketPlayOutEntity", "PacketPlayOutEntity", "Packet30Entity");
PacketPlayOutEntityLook = getClass("network.protocol.game.ClientboundMoveEntityPacket$Rot",
"network.protocol.game.PacketPlayOutEntity$PacketPlayOutEntityLook", "PacketPlayOutEntity$PacketPlayOutEntityLook",
"PacketPlayOutEntityLook", "Packet32EntityLook");
// Field
PacketPlayOutEntity_ENTITYID = ReflectionUtils.getFields(PacketPlayOutEntity, int.class).get(0);
}
private static void loadEntitySpawn() throws ReflectiveOperationException {
SpawnEntityClass = getClass("network.protocol.game.ClientboundAddEntityPacket",
"network.protocol.game.PacketPlayOutSpawnEntity", "PacketPlayOutSpawnEntityLiving", "Packet24MobSpawn");
SpawnEntity_EntityId = ReflectionUtils.getFields(SpawnEntityClass, int.class).get(0);
if (BukkitReflection.getMinorVersion() >= 17) {
Class<?> Vec3D = getClass("world.phys.Vec3", "world.phys.Vec3D");
Vec3D_Empty = ReflectionUtils.getOnlyField(Vec3D, Vec3D).get(null);
Class<?> EntityTypes = getClass("world.entity.EntityType", "world.entity.EntityTypes");
if (BukkitReflection.getMinorVersion() >= 19) {
EntityTypes_ARMOR_STAND = ReflectionUtils.getField(EntityTypes, "ARMOR_STAND", "d").get(null);
} else {
EntityTypes_ARMOR_STAND = ReflectionUtils.getField(EntityTypes, "ARMOR_STAND", "c", "f_20529_").get(null); // Mohist 1.18.2
}
if (BukkitReflection.getMinorVersion() >= 19) {
newSpawnEntity = SpawnEntityClass.getConstructor(int.class, UUID.class, double.class, double.class, double.class, float.class, float.class, EntityTypes, int.class, Vec3D, double.class);
} else {
newSpawnEntity = SpawnEntityClass.getConstructor(int.class, UUID.class, double.class, double.class, double.class, float.class, float.class, EntityTypes, int.class, Vec3D);
}
} else {
newSpawnEntity = SpawnEntityClass.getConstructor();
if (BukkitReflection.getMinorVersion() >= 9) {
SpawnEntity_UUID = ReflectionUtils.getOnlyField(SpawnEntityClass, UUID.class);
SpawnEntity_X = ReflectionUtils.getFields(SpawnEntityClass, double.class).get(0);
SpawnEntity_Y = ReflectionUtils.getFields(SpawnEntityClass, double.class).get(1);
SpawnEntity_Z = ReflectionUtils.getFields(SpawnEntityClass, double.class).get(2);
} else {
SpawnEntity_X = ReflectionUtils.getFields(SpawnEntityClass, int.class).get(2);
SpawnEntity_Y = ReflectionUtils.getFields(SpawnEntityClass, int.class).get(3);
SpawnEntity_Z = ReflectionUtils.getFields(SpawnEntityClass, int.class).get(4);
}
SpawnEntity_EntityType = ReflectionUtils.getFields(SpawnEntityClass, int.class).get(1);
if (BukkitReflection.getMinorVersion() <= 14) {
SpawnEntity_DataWatcher = ReflectionUtils.getOnlyField(SpawnEntityClass, DataWatcher.DataWatcher);
}
}
if (BukkitReflection.getMinorVersion() >= 13) {
entityIds.put(EntityType.ARMOR_STAND, 1);
} else {
entityIds.put(EntityType.WITHER, 64);
if (BukkitReflection.getMinorVersion() >= 8) {
entityIds.put(EntityType.ARMOR_STAND, 30);
}
}
if (!BukkitReflection.is1_20_2Plus()) {
PacketPlayOutNamedEntitySpawn = getClass("network.protocol.game.ClientboundAddPlayerPacket",
"network.protocol.game.PacketPlayOutNamedEntitySpawn", "PacketPlayOutNamedEntitySpawn", "Packet20NamedEntitySpawn");
PacketPlayOutNamedEntitySpawn_ENTITYID = ReflectionUtils.getFields(PacketPlayOutNamedEntitySpawn, int.class).get(0);
}
}
private static int floor(double paramDouble) {
int i = (int)paramDouble;
return paramDouble < i ? i - 1 : i;
}
@SneakyThrows
@Override
public void spawnEntity(int entityId, @NotNull UUID id, @NotNull Object entityType, @NotNull Location l, @NotNull EntityData data) {
int minorVersion = BukkitReflection.getMinorVersion();
if (minorVersion >= 19) {
player.sendPacket(newSpawnEntity.newInstance(entityId, id, l.getX(), l.getY(), l.getZ(), 0, 0, EntityTypes_ARMOR_STAND, 0, Vec3D_Empty, 0d));
} else if (minorVersion >= 17) {
player.sendPacket(newSpawnEntity.newInstance(entityId, id, l.getX(), l.getY(), l.getZ(), 0, 0, EntityTypes_ARMOR_STAND, 0, Vec3D_Empty));
} else {
Object nmsPacket = newSpawnEntity.newInstance();
SpawnEntity_EntityId.set(nmsPacket, entityId);
if (minorVersion <= 14) {
SpawnEntity_DataWatcher.set(nmsPacket, data.build());
}
if (minorVersion >= 9) {
SpawnEntity_UUID.set(nmsPacket, id);
SpawnEntity_X.set(nmsPacket, l.getX());
SpawnEntity_Y.set(nmsPacket, l.getY());
SpawnEntity_Z.set(nmsPacket, l.getZ());
} else {
SpawnEntity_X.set(nmsPacket, floor(l.getX()*32));
SpawnEntity_Y.set(nmsPacket, floor(l.getY()*32));
SpawnEntity_Z.set(nmsPacket, floor(l.getZ()*32));
}
SpawnEntity_EntityType.set(nmsPacket, entityIds.get((EntityType) entityType));
player.sendPacket(nmsPacket);
}
if (BukkitReflection.getMinorVersion() >= 15) {
updateEntityMetadata(entityId, data);
}
}
@SneakyThrows
@Override
public void updateEntityMetadata(int entityId, @NotNull EntityData data) {
if (newEntityMetadata.getParameterCount() == 2) {
//1.19.3+
player.sendPacket(newEntityMetadata.newInstance(entityId, DataWatcher.DataWatcher_packDirty.invoke(data.build())));
} else {
player.sendPacket(newEntityMetadata.newInstance(entityId, data.build(), true));
}
}
@SneakyThrows
@Override
public void teleportEntity(int entityId, @NotNull Location location) {
Object nmsPacket;
if (BukkitReflection.getMinorVersion() >= 17) {
nmsPacket = newEntityTeleport.newInstance(dummyEntity);
} else {
nmsPacket = newEntityTeleport.newInstance();
}
EntityTeleport_EntityId.set(nmsPacket, entityId);
if (BukkitReflection.getMinorVersion() >= 9) {
EntityTeleport_X.set(nmsPacket, location.getX());
EntityTeleport_Y.set(nmsPacket, location.getY());
EntityTeleport_Z.set(nmsPacket, location.getZ());
} else {
EntityTeleport_X.set(nmsPacket, floor(location.getX()*32));
EntityTeleport_Y.set(nmsPacket, floor(location.getY()*32));
EntityTeleport_Z.set(nmsPacket, floor(location.getZ()*32));
}
player.sendPacket(nmsPacket);
}
@SneakyThrows
@Override
public void destroyEntities(int... entities) {
if (newEntityDestroy.getParameterTypes()[0] != int.class) {
player.sendPacket(newEntityDestroy.newInstance(new Object[]{entities}));
} else {
//1.17.0 Mojank
for (int entity : entities) {
player.sendPacket(newEntityDestroy.newInstance(entity));
}
}
}
@Override
public boolean isDestroyPacket(@NotNull Object packet) {
return EntityDestroyClass.isInstance(packet);
}
@Override
public boolean isTeleportPacket(@NotNull Object packet) {
return EntityTeleportClass.isInstance(packet);
}
@Override
public boolean isNamedEntitySpawnPacket(@NotNull Object packet) {
if (BukkitReflection.is1_20_2Plus()) {
return SpawnEntityClass.isInstance(packet);
} else {
return PacketPlayOutNamedEntitySpawn.isInstance(packet);
}
}
@Override
public boolean isMovePacket(@NotNull Object packet) {
return PacketPlayOutEntity.isInstance(packet);
}
@Override
public boolean isLookPacket(@NotNull Object packet) {
return PacketPlayOutEntityLook.isInstance(packet);
}
@Override
@SneakyThrows
public int getTeleportEntityId(@NotNull Object teleportPacket) {
return EntityTeleport_EntityId.getInt(teleportPacket);
}
@Override
@SneakyThrows
public int getMoveEntityId(@NotNull Object movePacket) {
return PacketPlayOutEntity_ENTITYID.getInt(movePacket);
}
@Override
@SneakyThrows
public int getSpawnedPlayer(@NotNull Object playerSpawnPacket) {
if (BukkitReflection.is1_20_2Plus()) {
return SpawnEntity_EntityId.getInt(playerSpawnPacket);
} else {
return PacketPlayOutNamedEntitySpawn_ENTITYID.getInt(playerSpawnPacket);
}
}
@Override
@SneakyThrows
public int[] getDestroyedEntities(@NotNull Object destroyPacket) {
Object entities = PacketEntityView.EntityDestroy_Entities.get(destroyPacket);
if (BukkitReflection.getMinorVersion() >= 17) {
if (entities instanceof List) {
return ((List<Integer>)entities).stream().mapToInt(i -> i).toArray();
} else {
//1.17.0
return new int[]{(int) entities};
}
} else {
return (int[]) entities;
}
}
@Override
public boolean isBundlePacket(@NotNull Object packet) {
return BukkitReflection.is1_19_4Plus() && ClientboundBundlePacket.isInstance(packet);
}
@Override
@SneakyThrows
public Iterable<Object> getPackets(@NotNull Object bundlePacket) {
return (Iterable<Object>) ClientboundBundlePacket_packets.get(bundlePacket);
}
/**
* Returns class from given potential names. Supports modded servers, such as Thermos.
*
* @param names
* Potential class names
* @return class from given name
* @throws ClassNotFoundException
* if class was not found
*/
public static Class<?> getClass(@NotNull String... names) throws ClassNotFoundException {
for (String name : names) {
try {
if (BukkitReflection.getMinorVersion() >= 17) {
return PacketEntityView.class.getClassLoader().loadClass("net.minecraft." + name);
} else {
return PacketEntityView.class.getClassLoader().loadClass("net.minecraft.server." + BukkitReflection.getServerPackage() + "." + name);
}
} catch (ClassNotFoundException | NullPointerException e) {
// Wrong class name
}
}
throw new ClassNotFoundException("No class found with potential names " + Arrays.toString(names));
}
}
@@ -1,47 +1,56 @@
package me.neznamy.tab.platforms.bukkit.nms;
import lombok.Getter;
import lombok.SneakyThrows;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.util.BiConsumerWithException;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* Class for sending packets to players.
*/
public class PacketSender {
private static Method getHandle;
private static Field PLAYER_CONNECTION;
private static Method sendPacket;
@Getter private static boolean available;
/** Function for sending packet */
@NotNull
private final BiConsumerWithException<Player, Object> send;
public static void tryLoad() {
try {
Class<?> Packet = BukkitReflection.getClass("network.protocol.Packet", "Packet");
Class<?> EntityPlayer = BukkitReflection.getClass("server.level.ServerPlayer", "server.level.EntityPlayer", "EntityPlayer");
Class<?> PlayerConnection = BukkitReflection.getClass("server.network.ServerGamePacketListenerImpl",
"server.network.PlayerConnection", "PlayerConnection");
getHandle = Class.forName("org.bukkit.craftbukkit." + BukkitReflection.getServerPackage() + ".entity.CraftPlayer").getMethod("getHandle");
PLAYER_CONNECTION = ReflectionUtils.getOnlyField(EntityPlayer, PlayerConnection);
if (BukkitReflection.getMinorVersion() >= 7) {
sendPacket = ReflectionUtils.getMethods(PlayerConnection, void.class, Packet).get(0);
} else {
sendPacket = ReflectionUtils.getMethod(PlayerConnection, new String[]{"sendPacket"}, Packet);
}
available = true;
} catch (Exception e) {
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.RED.getFormat() + "[TAB] Failed to initialize NMS fields for " +
"sending packets due to a compatibility error. Majority of the features will not work / be very limited. " +
"Please update the plugin to a version with proper support for your server version.");
/**
* 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 static void sendPacket(@NotNull Player player, @NotNull Object packet) {
if (!player.isOnline() || sendPacket == null) return;
sendPacket.invoke(PLAYER_CONNECTION.get(getHandle.invoke(player)), packet);
public void sendPacket(@NotNull Player player, @NotNull Object packet) {
send.accept(player, packet);
}
}
@@ -1,41 +1,54 @@
package me.neznamy.tab.platforms.bukkit.nms;
import lombok.SneakyThrows;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.platforms.bukkit.BukkitUtils;
import me.neznamy.tab.shared.util.FunctionWithException;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* Class for retrieving ping of players.
*/
public class PingRetriever {
private static Method getHandle;
private static Field PING;
/** 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 (BukkitReflection.getMinorVersion() < 17) {
getHandle = Class.forName("org.bukkit.craftbukkit." + BukkitReflection.getServerPackage() + ".entity.CraftPlayer").getMethod("getHandle");
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");
PING = ReflectionUtils.getField(EntityPlayer, "ping", "field_71138_i"); // 1.5.2 - 1.16.5, 1.7.10 Thermos
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) {
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.RED.getFormat() + "[TAB] Failed to initialize NMS fields for " +
"getting player's ping due to a compatibility error. This will " +
"result in ping showing \"-1\". " +
"Please update the plugin a to version with native support for your server version to properly show ping.");
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) {
if (BukkitReflection.getMinorVersion() >= 17) {
return player.getPing();
}
if (PING == null) return -1;
return PING.getInt(getHandle.invoke(player));
return getPing.apply(player);
}
}
@@ -1,20 +1,22 @@
package me.neznamy.tab.platforms.bukkit.platform;
import de.myzelyam.api.vanish.VanishAPI;
import lombok.Getter;
import lombok.SneakyThrows;
import me.clip.placeholderapi.PlaceholderAPI;
import me.neznamy.tab.platforms.bukkit.*;
import me.neznamy.tab.platforms.bukkit.header.HeaderFooter;
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.PacketSender;
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;
@@ -27,7 +29,8 @@ 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;
@@ -46,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
@@ -54,63 +56,61 @@ import java.lang.reflect.Field;
@Getter
public class BukkitPlatform implements BackendPlatform {
/** Flag tracking plugin presence */
private final boolean premiumVanish = Bukkit.getPluginManager().isPluginEnabled("PremiumVanish");
/**
* Plugin instance for registering tasks and events
*/
/** Plugin instance for registering tasks and events */
@NotNull
private final JavaPlugin plugin;
/**
* Variables checking presence of other plugins to hook into
*/
/** 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;
/** Spigot field for tracking TPS, the array is final and only being modified instead of re-instantiated */
private double[] recentTps;
/**
* TPS field
*/
@Nullable
private Field spigotTps;
/**
* Detection for presence of Paper's TPS getter
*/
/** Detection for presence of Paper's TPS getter */
private final boolean paperTps = ReflectionUtils.methodExists(Bukkit.class, "getTPS");
/**
* Detection for presence of Paper's MSPT getter
*/
/** 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();
PacketSender.tryLoad();
ScoreboardLoader.findInstance();
TabListBase.findInstance();
ScoreboardLoader.tryLoad();
if (BukkitReflection.getMinorVersion() >= 8) {
BukkitPipelineInjector.tryLoad();
HeaderFooter.findInstance();
}
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
@@ -129,8 +129,9 @@ 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
@@ -148,7 +149,10 @@ public class BukkitPlatform implements BackendPlatform {
@Override
@NotNull
public NameTag getUnlimitedNameTags() {
return BukkitReflection.getMinorVersion() >= 8 ? new BukkitNameTagX(plugin) : new NameTag();
return BukkitReflection.getMinorVersion() >= 8 &&
PacketEntityView.isAvailable() &&
BukkitPipelineInjector.isAvailable() ?
new BukkitNameTagX(plugin) : new NameTag();
}
@Override
@@ -191,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];
@@ -205,21 +217,19 @@ public class BukkitPlatform implements BackendPlatform {
}
@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
@@ -235,13 +245,13 @@ 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,
@@ -252,14 +262,17 @@ public class BukkitPlatform implements BackendPlatform {
@Override
@NotNull
public ProtocolVersion getServerVersion() {
return ProtocolVersion.fromFriendlyName(Bukkit.getBukkitVersion().split("-")[0]);
public File getDataFolder() {
return plugin.getDataFolder();
}
@Override
@NotNull
public File getDataFolder() {
return plugin.getDataFolder();
public Object convertComponent(@NotNull TabComponent component, boolean modern) {
if (ComponentConverter.INSTANCE != null) {
return ComponentConverter.INSTANCE.convert(component, modern);
} else {
return component;
}
}
@Override
@@ -278,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;
}
@@ -295,22 +307,55 @@ public class BukkitPlatform implements BackendPlatform {
return -1;
}
public void runSync(@NotNull Entity entity, @NotNull Runnable task) {
Bukkit.getScheduler().runTask(plugin, task);
}
/**
* 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) {
Player bViewer = ((BukkitTabPlayer)viewer).getPlayer();
Player bTarget = ((BukkitTabPlayer)target).getPlayer();
if (BackendPlatform.super.canSee(viewer, target)) return true;
return ((BukkitTabPlayer)viewer).getPlayer().canSee(((BukkitTabPlayer)target).getPlayer());
}
//noinspection ConstantConditions
if (premiumVanish && VanishAPI.canSee(bViewer, bTarget)) return true;
if (bViewer.canSee(bTarget)) return true;
return BackendPlatform.super.canSee(viewer, target);
/**
* 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
@@ -74,7 +80,6 @@ public class FoliaPlatform extends BukkitPlatform {
* @param task
* Task to run
*/
@Override
@SneakyThrows
@SuppressWarnings("JavaReflectionMemberAccess")
public void runSync(@NotNull Entity entity, @NotNull Runnable task) {
@@ -1,268 +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.BukkitUtils;
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
import me.neznamy.tab.shared.Limitations;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.scoreboard.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.concurrent.CountDownLatch;
/**
* Scoreboard implementation using Bukkit API (1.5.2+). It has several issues:
* #1 - Limitations on legacy versions are forced in the API.
* While this may not seem like a problem, it enforces those
* limits even for 1.13+ players (if using ViaVersion).<p>
* #2 - Modern versions no longer have any limits, but md_5
* decided to add some random limits for absolutely no reason
* at all. Scoreboard title received a random 128 characters
* limit including color codes. Together with the almighty bukkit
* RGB format using 14 characters for 1 color code, this makes
* gradients just impossible to use. Team prefix/suffix also
* received a 64 characters limit (excluding color codes at least),
* however that might not be enough for displaying a line of text
* in sidebar, which would require splitting the text into prefix
* and suffix, which is just begging for bugs to be introduced.<p>
* #3 - Other plugins can decide to put players into their own
* scoreboard, automatically destroying all visuals made by the
* plugin. They might also put all players into the same scoreboard,
* making per-player view of teams, especially sidebar not working.<p>
*/
// Throw the NPE if something is not as expected, parent class should ensure it anyway
@SuppressWarnings({"ConstantConditions", "deprecation"})
public class BukkitScoreboard extends Scoreboard<BukkitTabPlayer> {
/** Flag tracking whether this implementation is available for use */
@Getter
private static final boolean available;
/** Array of display slots, because the Bukkit order does not match network ordinals */
private static org.bukkit.scoreboard.DisplaySlot[] slots;
/** Server's minor version */
private final int serverMinorVersion = BukkitReflection.getMinorVersion();
protected org.bukkit.scoreboard.Scoreboard scoreboard;
static {
available = ReflectionUtils.methodExists(Bukkit.class, "getScoreboardManager");
if (available) {
slots = new org.bukkit.scoreboard.DisplaySlot[]{
org.bukkit.scoreboard.DisplaySlot.PLAYER_LIST,
org.bukkit.scoreboard.DisplaySlot.SIDEBAR,
org.bukkit.scoreboard.DisplaySlot.BELOW_NAME
};
}
}
@SneakyThrows
public BukkitScoreboard(@NotNull BukkitTabPlayer player) {
super(player);
// Put player into a different scoreboard for per-player view
runSync(() -> {
scoreboard = Bukkit.getScoreboardManager().getNewScoreboard();
player.getPlayer().setScoreboard(scoreboard);
});
}
@Override
public void setDisplaySlot0(int slot, @NotNull String objective) {
checkPlayerScoreboard();
scoreboard.getObjective(objective).setDisplaySlot(slots[slot]);
}
@Override
public void setScore0(@NotNull String objective, @NotNull String scoreHolder, int score,
@Nullable IChatBaseComponent displayName, @Nullable IChatBaseComponent numberFormat) {
checkPlayerScoreboard();
if (serverMinorVersion >= 7 && TAB.getInstance().getServerVersion().getNetworkId() >= ProtocolVersion.V1_7_8.getNetworkId()) {
scoreboard.getObjective(objective).getScore(scoreHolder).setScore(score);
} else {
scoreboard.getObjective(objective).getScore(Bukkit.getOfflinePlayer(scoreHolder)).setScore(score);
}
}
@Override
public void removeScore0(@NotNull String objective, @NotNull String scoreHolder) {
checkPlayerScoreboard();
if (serverMinorVersion >= 7 && TAB.getInstance().getServerVersion().getNetworkId() >= ProtocolVersion.V1_7_8.getNetworkId()) {
scoreboard.resetScores(scoreHolder);
} else {
scoreboard.resetScores(Bukkit.getOfflinePlayer(scoreHolder));
}
}
@Override
public void registerObjective0(@NotNull String objectiveName, @NotNull String title, int display,
@Nullable IChatBaseComponent numberFormat) {
checkPlayerScoreboard();
newObjective(objectiveName, "dummy", title, display);
}
@Override
public void unregisterObjective0(@NotNull String objectiveName) {
checkPlayerScoreboard();
scoreboard.getObjective(objectiveName).unregister();
}
@Override
public void updateObjective0(@NotNull String objectiveName, @NotNull String title, int display,
@Nullable IChatBaseComponent numberFormat) {
checkPlayerScoreboard();
Objective obj = scoreboard.getObjective(objectiveName);
setDisplayName(obj, title);
if (serverMinorVersion >= 14) obj.setRenderType(RenderType.values()[display]);
}
@Override
public void registerTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix,
@NotNull NameVisibility visibility, @NotNull CollisionRule collision,
@NotNull Collection<String> players, int options) {
checkPlayerScoreboard();
Team team = scoreboard.registerNewTeam(name);
setPrefix(team, prefix);
setSuffix(team, suffix);
if (serverMinorVersion >= 8)
team.setNameTagVisibility(NameTagVisibility.valueOf(visibility.name()));
if (serverMinorVersion >= 9)
team.setOption(Team.Option.COLLISION_RULE, Team.OptionStatus.values()[collision.ordinal()]);
if (serverMinorVersion >= 13)
team.setColor(ChatColor.valueOf(EnumChatFormat.lastColorsOf(prefix).name()));
if (serverMinorVersion >= 7 && TAB.getInstance().getServerVersion().getNetworkId() >= ProtocolVersion.V1_7_8.getNetworkId()) {
players.forEach(team::addEntry);
} else {
players.forEach(player -> team.addPlayer(Bukkit.getOfflinePlayer(player)));
}
team.setAllowFriendlyFire((options & 0x01) != 0);
team.setCanSeeFriendlyInvisibles((options & 0x02) != 0);
}
@Override
public void unregisterTeam0(@NotNull String name) {
checkPlayerScoreboard();
scoreboard.getTeam(name).unregister();
}
@Override
public void updateTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix,
@NotNull NameVisibility visibility, @NotNull CollisionRule collision, int options) {
checkPlayerScoreboard();
Team team = scoreboard.getTeam(name);
setPrefix(team, prefix);
setSuffix(team, suffix);
if (serverMinorVersion >= 8)
team.setNameTagVisibility(NameTagVisibility.valueOf(visibility.name()));
if (serverMinorVersion >= 9)
team.setOption(Team.Option.COLLISION_RULE, Team.OptionStatus.values()[collision.ordinal()]);
if (serverMinorVersion >= 13)
team.setColor(ChatColor.valueOf(EnumChatFormat.lastColorsOf(prefix).name()));
team.setAllowFriendlyFire((options & 0x01) != 0);
team.setCanSeeFriendlyInvisibles((options & 0x02) != 0);
}
public void newObjective(String objectiveName, String criteria, String title, int display) {
if (serverMinorVersion >= 14) {
scoreboard.registerNewObjective(
objectiveName,
criteria,
transform(title, 128, Limitations.SCOREBOARD_TITLE_PRE_1_13),
RenderType.values()[display]
);
} else {
setDisplayName(scoreboard.registerNewObjective(objectiveName, display == HealthDisplay.HEARTS ? "health" : "dummy"), title);
}
}
public void setDisplayName(@NotNull Objective objective, @NotNull String displayName) {
objective.setDisplayName(transform(displayName, 128, Limitations.SCOREBOARD_TITLE_PRE_1_13));
}
public void setPrefix(@NotNull Team team, @NotNull String prefix) {
team.setPrefix(transform(prefix, 64, Limitations.TEAM_PREFIX_SUFFIX_PRE_1_13));
}
public void setSuffix(@NotNull Team team, @NotNull String suffix) {
team.setSuffix(transform(suffix, 64, Limitations.TEAM_PREFIX_SUFFIX_PRE_1_13));
}
/**
* Transforms text into format using bukkit RGB syntax if server version is 1.16+
*
* @param text
* Text to transform
* @param maxLengthModern
* Maximum text length defined by bukkit API on 1.13+
* @param maxLengthLegacy
* Maximum text length defined by bukkit API on 1.12-
* @return Converted text
*/
@NotNull
private String transform(@NotNull String text, int maxLengthModern, int maxLengthLegacy) {
String transformed = BukkitUtils.toBukkitFormat(IChatBaseComponent.optimizedComponent(text), player.getVersion().getMinorVersion() >= 16);
if (serverMinorVersion >= 16 && maxLengthModern < 128) { // Scoreboard title is not stripping colors
while (ChatColor.stripColor(transformed).length() > maxLengthModern)
transformed = transformed.substring(0, transformed.length()-1);
} else if (serverMinorVersion >= 13) {
if (transformed.length() > maxLengthModern)
transformed = transformed.substring(0, maxLengthModern);
} else {
if (transformed.length() > maxLengthLegacy)
transformed = transformed.substring(0, maxLengthLegacy);
}
return transformed;
}
/**
* Makes sure player is in the correct scoreboard.
* If not, puts the player into correct scoreboard and prints
* a warning.
* The only possible reason for this to happen is another plugin
* putting a player into a different scoreboard.
* Sadly there is no efficient solution to this, which md_5 fails
* to understand and keeps saying you don't need packets for scoreboards.
*/
@SneakyThrows
private void checkPlayerScoreboard() {
if (player.getPlayer().getScoreboard() != scoreboard) {
TAB.getInstance().getErrorManager().printError("Player " + player.getName() + " was in a different scoreboard " +
"than expected. This means another plugin changed player's scoreboard.");
runSync(() -> player.getPlayer().setScoreboard(scoreboard));
}
}
/**
* Runs the task if in main thread. If not, submits the task and waits for the result.
*
* @param task
* Task to run
*/
@SneakyThrows
private void runSync(@NotNull Runnable task) {
if (Bukkit.isPrimaryThread()) {
// Server thread (plugin reload)
task.run();
} else {
// Plugin thread (player join)
CountDownLatch c = new CountDownLatch(1);
player.getPlatform().runSync(player.getPlayer(), () -> {
task.run();
c.countDown();
});
c.await();
}
}
}
@@ -1,67 +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.IChatBaseComponent;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.platform.Scoreboard;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
public class NullScoreboard extends Scoreboard<BukkitTabPlayer> {
/**
* Dummy implementation if no scoreboard implementation is available.
*/
public class NullScoreboard extends Scoreboard<BukkitTabPlayer, Object> {
public NullScoreboard(@NotNull BukkitTabPlayer player) {
/**
* 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, @NotNull String objective) {
protected void setDisplaySlot0(int slot, @NonNull String objective) {
// Do nothing
}
@Override
protected void setScore0(@NotNull String objective, @NotNull String scoreHolder, int score,
@Nullable IChatBaseComponent displayName, @Nullable IChatBaseComponent numberFormat) {
protected void setScore0(@NonNull String objective, @NonNull String scoreHolder, int score,
@Nullable Object displayName, @Nullable Object numberFormat) {
// Do nothing
}
@Override
protected void removeScore0(@NotNull String objective, @NotNull String scoreHolder) {
protected void removeScore0(@NonNull String objective, @NonNull String scoreHolder) {
// Do nothing
}
@Override
protected void registerObjective0(@NotNull String objectiveName, @NotNull String title, int display,
@Nullable IChatBaseComponent numberFormat) {
protected void registerObjective0(@NonNull String objectiveName, @NonNull String title, int display,
@Nullable Object numberFormat) {
// Do nothing
}
@Override
protected void unregisterObjective0(@NotNull String objectiveName) {
protected void unregisterObjective0(@NonNull String objectiveName) {
// Do nothing
}
@Override
protected void updateObjective0(@NotNull String objectiveName, @NotNull String title, int display,
@Nullable IChatBaseComponent numberFormat) {
protected void updateObjective0(@NonNull String objectiveName, @NonNull String title, int display,
@Nullable Object numberFormat) {
// Do nothing
}
@Override
protected void registerTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix,
@NotNull NameVisibility visibility, @NotNull CollisionRule collision,
@NotNull Collection<String> players, int options) {
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(@NotNull String name) {
protected void unregisterTeam0(@NonNull String name) {
// Do nothing
}
@Override
protected void updateTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix,
@NotNull NameVisibility visibility, @NotNull CollisionRule collision, int options) {
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,517 +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.ComponentCache;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.List;
/**
* 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")
public class PacketScoreboard extends Scoreboard<BukkitTabPlayer> {
@Getter
private static boolean available;
private static Class<?> Component;
private static Class<?> Scoreboard;
private static Class<?> ScoreboardObjective;
private static Object emptyScoreboard;
private static Class<?> NumberFormat;
private static Constructor<?> newFixedFormat;
// Objective packet
public static Class<?> ObjectivePacketClass;
private static Constructor<?> newObjectivePacket;
public static Field Objective_OBJECTIVE_NAME;
public static Field Objective_METHOD;
private static Field Objective_RENDER_TYPE;
private static Constructor<?> newScoreboardObjective;
private static Method ScoreboardObjective_setDisplayName;
private static Enum<?>[] healthDisplays;
private static Object IScoreboardCriteria_dummy;
private static ScorePacketData scorePacketData;
public static TeamPacketData teamPacketData;
public static DisplayPacketData displayPacketData;
private static Method ChatSerializer_DESERIALIZE;
private static final ComponentCache<IChatBaseComponent, Object> componentCache = new ComponentCache<>(1000,
(component, clientVersion) -> ChatSerializer_DESERIALIZE.invoke(null, component.toString(clientVersion)));
static {
try {
int minorVersion = BukkitReflection.getMinorVersion();
Scoreboard = BukkitReflection.getClass("world.scores.Scoreboard", "Scoreboard");
ScoreboardObjective = BukkitReflection.getClass("world.scores.Objective", "world.scores.ScoreboardObjective", "ScoreboardObjective");
Class<?> IScoreboardCriteria = BukkitReflection.getClass(
"world.scores.criteria.ObjectiveCriteria", // Mojang mapped
"world.scores.criteria.IScoreboardCriteria", // Bukkit 1.17.+
"IScoreboardCriteria", // 1.5.1 - 1.16.5
"IObjective" // 1.5.0
);
ObjectivePacketClass = BukkitReflection.getClass(
"network.protocol.game.ClientboundSetObjectivePacket", // Mojang mapped
"network.protocol.game.PacketPlayOutScoreboardObjective", // Bukkit 1.17+
"PacketPlayOutScoreboardObjective", // 1.7 - 1.16.5
"Packet206SetScoreboardObjective" // 1.5 - 1.6.4
);
emptyScoreboard = Scoreboard.getConstructor().newInstance();
Objective_OBJECTIVE_NAME = ReflectionUtils.getFields(ObjectivePacketClass, String.class).get(0);
List<Field> list = ReflectionUtils.getFields(ObjectivePacketClass, int.class);
Objective_METHOD = list.get(list.size()-1);
newObjectivePacket = ObjectivePacketClass.getConstructor(ScoreboardObjective, int.class);
IScoreboardCriteria_dummy = ReflectionUtils.getFields(IScoreboardCriteria, IScoreboardCriteria).get(0).get(null);
newScoreboardObjective = ReflectionUtils.getOnlyConstructor(ScoreboardObjective);
if (minorVersion >= 7) {
Component = BukkitReflection.getClass("network.chat.Component", "network.chat.IChatBaseComponent", "IChatBaseComponent");
Class<?> ChatSerializer = BukkitReflection.getClass("network.chat.Component$Serializer",
"network.chat.IChatBaseComponent$ChatSerializer", "IChatBaseComponent$ChatSerializer", "ChatSerializer");
ChatSerializer_DESERIALIZE = ReflectionUtils.getMethods(ChatSerializer, Object.class, String.class).get(0);
}
if (minorVersion >= 8) {
Class<?> EnumScoreboardHealthDisplay = BukkitReflection.getClass(
"world.scores.criteria.ObjectiveCriteria$RenderType",
"world.scores.criteria.IScoreboardCriteria$EnumScoreboardHealthDisplay",
"IScoreboardCriteria$EnumScoreboardHealthDisplay",
"EnumScoreboardHealthDisplay");
healthDisplays = (Enum<?>[]) EnumScoreboardHealthDisplay.getMethod("values").invoke(null);
if (minorVersion < 13) {
Objective_RENDER_TYPE = ReflectionUtils.getOnlyField(ObjectivePacketClass, EnumScoreboardHealthDisplay);
}
}
if (minorVersion < 13) {
ScoreboardObjective_setDisplayName = ReflectionUtils.getOnlyMethod(ScoreboardObjective, void.class, String.class);
}
if (BukkitReflection.is1_20_3Plus()) {
NumberFormat = BukkitReflection.getClass("network.chat.numbers.NumberFormat");
newFixedFormat = BukkitReflection.getClass("network.chat.numbers.FixedFormat").getConstructor(Component);
}
scorePacketData = new ScorePacketData();
teamPacketData = new TeamPacketData();
displayPacketData = new DisplayPacketData();
available = true;
} catch (Exception ignored) {
// Print exception to find out what went wrong
}
}
public PacketScoreboard(@NotNull BukkitTabPlayer player) {
super(player);
}
@Override
@SneakyThrows
public void setDisplaySlot0(int slot, @NotNull String objective) {
player.sendPacket(displayPacketData.setDisplaySlot(slot, newObjective(objective, "", 0, null)));
}
@Override
@SneakyThrows
public void registerObjective0(@NotNull String objectiveName, @NotNull String title, int display,
@Nullable IChatBaseComponent numberFormat) {
player.sendPacket(newObjectivePacket(ObjectiveAction.REGISTER, objectiveName, title, display, numberFormat));
}
@Override
public void unregisterObjective0(@NotNull String objectiveName) {
player.sendPacket(newObjectivePacket(ObjectiveAction.UNREGISTER, objectiveName, "", 0, null));
}
@Override
public void updateObjective0(@NotNull String objectiveName, @NotNull String title, int display,
@Nullable IChatBaseComponent numberFormat) {
player.sendPacket(newObjectivePacket(ObjectiveAction.UPDATE, objectiveName, title, display, numberFormat));
}
@SneakyThrows
private Object newObjectivePacket(int action, @NotNull String objectiveName, @NotNull String title, int display,
@Nullable IChatBaseComponent numberFormat) {
Object packet = newObjectivePacket.newInstance(newObjective(objectiveName, title, display, numberFormat), action);
if (BukkitReflection.getMinorVersion() >= 8 && BukkitReflection.getMinorVersion() < 13) {
Objective_RENDER_TYPE.set(packet, healthDisplays[display]);
}
return packet;
}
@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) {
player.sendPacket(teamPacketData.registerTeam(name, prefix, toComponent(prefix), suffix,
toComponent(suffix), visibility, collision, players, options));
}
@Override
@SneakyThrows
public void unregisterTeam0(@NotNull String name) {
player.sendPacket(teamPacketData.unregisterTeam(name));
}
@Override
@SneakyThrows
public void updateTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix,
@NotNull NameVisibility visibility, @NotNull CollisionRule collision, int options) {
player.sendPacket(teamPacketData.updateTeam(name, prefix, toComponent(prefix), suffix,
toComponent(suffix), visibility, collision, options));
}
@Override
public void setScore0(@NotNull String objective, @NotNull String scoreHolder, int score,
@Nullable IChatBaseComponent displayName, @Nullable IChatBaseComponent numberFormat) {
player.sendPacket(scorePacketData.setScore(objective, scoreHolder, score, toComponent(displayName), toFixedFormat(numberFormat)));
}
@Override
public void removeScore0(@NotNull String objective, @NotNull String scoreHolder) {
player.sendPacket(scorePacketData.removeScore(objective, scoreHolder));
}
/**
* 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(@NotNull String objectiveName, @NotNull String title, int renderType,
@Nullable IChatBaseComponent 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;
}
@Nullable
private Object toComponent(@NotNull String text) {
return toComponent(IChatBaseComponent.optimizedComponent(text));
}
@Nullable
private Object toComponent(@Nullable IChatBaseComponent component) {
if (component == null || BukkitReflection.getMinorVersion() < 8) return null;
return componentCache.get(component, player.getVersion());
}
@Nullable
@SneakyThrows
private Object toFixedFormat(@Nullable IChatBaseComponent numberFormat) {
if (numberFormat == null || newFixedFormat == null) return null;
return newFixedFormat.newInstance(toComponent(numberFormat));
}
private static class ScorePacketData {
private final Constructor<?> newSetScorePacket;
private Constructor<?> newResetScorePacket; // 1.20.3+
private Constructor<?> newSetScorePacket_String; // 1.12-
private Constructor<?> newScoreboardScore; // 1.12-
private Field SetScorePacket_SCORE; // 1.12-
private Enum<?>[] scoreboardActions; // 1.20.2-
@SneakyThrows
public ScorePacketData() {
Class<?> SetScorePacket = BukkitReflection.getClass(
"network.protocol.game.ClientboundSetScorePacket", // Mojang mapped
"network.protocol.game.PacketPlayOutScoreboardScore", // Bukkit 1.17+
"PacketPlayOutScoreboardScore", // 1.7 - 1.16.5
"Packet207SetScoreboardScore" // 1.5 - 1.6.4
);
if (BukkitReflection.is1_20_3Plus()) {
newResetScorePacket = BukkitReflection.getClass("network.protocol.game.ClientboundResetScorePacket").getConstructor(String.class, String.class);
newSetScorePacket = SetScorePacket.getConstructor(String.class, String.class, int.class, Component, NumberFormat);
} else if (BukkitReflection.getMinorVersion() >= 13) {
Class<?> EnumScoreboardAction = BukkitReflection.getClass("server.ServerScoreboard$Method",
"server.ScoreboardServer$Action", "ScoreboardServer$Action");
newSetScorePacket = SetScorePacket.getConstructor(EnumScoreboardAction, String.class, String.class, int.class);
scoreboardActions = (Enum<?>[]) EnumScoreboardAction.getMethod("values").invoke(null);
} else {
Class<?> ScoreboardScore = BukkitReflection.getClass("ScoreboardScore");
newSetScorePacket_String = SetScorePacket.getConstructor(String.class);
SetScorePacket_SCORE = ReflectionUtils.getFields(SetScorePacket, int.class).get(0);
newScoreboardScore = ScoreboardScore.getConstructor(Scoreboard, ScoreboardObjective, String.class);
if (BukkitReflection.getMinorVersion() >= 8) {
newSetScorePacket = SetScorePacket.getConstructor(ScoreboardScore);
} else {
newSetScorePacket = SetScorePacket.getConstructor(ScoreboardScore, int.class);
}
}
}
@SneakyThrows
public Object setScore(@NotNull String objective, @NotNull String scoreHolder, int score,
@Nullable Object displayName, @Nullable Object numberFormat) {
if (BukkitReflection.is1_20_3Plus()) {
return newSetScorePacket.newInstance(scoreHolder, objective, score, displayName, numberFormat);
} else if (BukkitReflection.getMinorVersion() >= 13) {
return newSetScorePacket.newInstance(scoreboardActions[0], objective, scoreHolder, score);
} else {
Object scoreboardScore = newScoreboardScore.newInstance(
emptyScoreboard,
newScoreboardObjective.newInstance(emptyScoreboard, objective, IScoreboardCriteria_dummy),
scoreHolder
);
Object packet;
if (BukkitReflection.getMinorVersion() >= 8) {
packet = newSetScorePacket.newInstance(scoreboardScore);
} else {
packet = newSetScorePacket.newInstance(scoreboardScore, ScoreAction.CHANGE);
}
SetScorePacket_SCORE.set(packet, score);
return packet;
}
}
@SneakyThrows
public Object removeScore(@NotNull String objective, @NotNull String scoreHolder) {
if (BukkitReflection.is1_20_3Plus()) {
return newResetScorePacket.newInstance(scoreHolder, objective);
} else if (BukkitReflection.getMinorVersion() >= 13) {
return newSetScorePacket.newInstance(scoreboardActions[1], objective, scoreHolder, 0);
} else {
return newSetScorePacket_String.newInstance(scoreHolder);
}
}
}
public static class TeamPacketData {
public Class<?> TeamPacketClass;
private Constructor<?> newTeamPacket;
private final Constructor<?> newScoreboardTeam;
private Method TeamPacketConstructor_of;
private Method TeamPacketConstructor_ofBoolean;
public Field TeamPacket_NAME;
public Field TeamPacket_ACTION;
public Field TeamPacket_PLAYERS;
private final Method ScoreboardTeam_getPlayerNameSet;
private Method ScoreboardTeam_setNameTagVisibility;
private Method ScoreboardTeam_setCollisionRule;
private final Method ScoreboardTeam_setPrefix;
private final Method ScoreboardTeam_setSuffix;
private Method ScoreboardTeam_setColor;
private final Method ScoreboardTeam_setAllowFriendlyFire;
private final Method ScoreboardTeam_setCanSeeFriendlyInvisibles;
private final Enum<?>[] chatFormats;
private Enum<?>[] nameVisibilities;
private Enum<?>[] collisionRules;
@SneakyThrows
public TeamPacketData() {
int minorVersion = BukkitReflection.getMinorVersion();
Class<?> scoreboardTeam = BukkitReflection.getClass("world.scores.PlayerTeam", "world.scores.ScoreboardTeam", "ScoreboardTeam");
Class<?> enumChatFormatClass = BukkitReflection.getClass("ChatFormatting", "EnumChatFormat", "EnumChatFormat");
TeamPacketClass = BukkitReflection.getClass(
"network.protocol.game.ClientboundSetPlayerTeamPacket", // Mojang mapped
"network.protocol.game.PacketPlayOutScoreboardTeam", // Bukkit 1.17+
"PacketPlayOutScoreboardTeam", // Bukkit 1.7 - 1.16.5
"Packet209SetScoreboardTeam" // 1.5 - 1.6.4
);
newScoreboardTeam = scoreboardTeam.getConstructor(Scoreboard, String.class);
TeamPacket_NAME = ReflectionUtils.getFields(TeamPacketClass, String.class).get(0);
TeamPacket_ACTION = ReflectionUtils.getInstanceFields(TeamPacketClass, int.class).get(0);
TeamPacket_PLAYERS = ReflectionUtils.getOnlyField(TeamPacketClass, Collection.class);
ScoreboardTeam_getPlayerNameSet = ReflectionUtils.getOnlyMethod(scoreboardTeam, Collection.class);
chatFormats = (Enum<?>[]) enumChatFormatClass.getMethod("values").invoke(null);
ScoreboardTeam_setAllowFriendlyFire = ReflectionUtils.getMethod(
scoreboardTeam,
new String[] {"func_96660_a", "setAllowFriendlyFire", "a", "m_83355_"}, // {Thermos, 1.5.1+, 1.5 & 1.18+, Mohist 1.18.2}
boolean.class
);
ScoreboardTeam_setCanSeeFriendlyInvisibles = ReflectionUtils.getMethod(
scoreboardTeam,
new String[] {"func_98300_b", "setCanSeeFriendlyInvisibles", "b", "m_83362_", "setSeeFriendlyInvisibles"}, // {Thermos, 1.5.1+, 1.5 & 1.18+, Mohist 1.18.2, 1.20.2+}
boolean.class
);
if (minorVersion >= 8) {
Class<?> enumNameTagVisibility = BukkitReflection.getClass(
"world.scores.Team$Visibility", // Mojang mapped
"world.scores.ScoreboardTeamBase$EnumNameTagVisibility", // Bukkit 1.17+
"ScoreboardTeamBase$EnumNameTagVisibility", // Bukkit 1.8.1 - 1.16.5
"EnumNameTagVisibility" // Bukkit 1.8.0
);
nameVisibilities = (Enum<?>[]) enumNameTagVisibility.getMethod("values").invoke(null);
ScoreboardTeam_setNameTagVisibility = ReflectionUtils.getMethod(
scoreboardTeam,
new String[] {"setNameTagVisibility", "a", "m_83346_"}, // {1.8.1+, 1.8 & 1.18+, Mohist 1.18.2}
enumNameTagVisibility
);
}
if (minorVersion >= 9) {
Class<?> enumTeamPush = BukkitReflection.getClass("world.scores.Team$CollisionRule",
"world.scores.ScoreboardTeamBase$EnumTeamPush", "ScoreboardTeamBase$EnumTeamPush");
ScoreboardTeam_setCollisionRule = ReflectionUtils.getOnlyMethod(scoreboardTeam, void.class, enumTeamPush);
collisionRules = (Enum<?>[]) enumTeamPush.getMethod("values").invoke(null);
}
if (minorVersion >= 13) {
ScoreboardTeam_setColor = ReflectionUtils.getOnlyMethod(scoreboardTeam, void.class, enumChatFormatClass);
ScoreboardTeam_setPrefix = ReflectionUtils.getMethod(
scoreboardTeam,
new String[]{"setPrefix", "b", "m_83360_", "setPlayerPrefix"}, // {1.17.1-, 1.18 - 1.20.1, Mohist 1.18.2, 1.20.2+}
Component
);
ScoreboardTeam_setSuffix = ReflectionUtils.getMethod(
scoreboardTeam,
new String[]{"setSuffix", "c", "m_83365_", "setPlayerSuffix"}, // {1.17.1-, 1.18 - 1.20.1, Mohist 1.18.2, 1.20.2+}
Component
);
} else {
ScoreboardTeam_setPrefix = ReflectionUtils.getMethod(
scoreboardTeam,
new String[] {"func_96666_b", "setPrefix", "b"}, // {Thermos, 1.5.1+, 1.5}
String.class
);
ScoreboardTeam_setSuffix = ReflectionUtils.getMethod(
scoreboardTeam,
new String[] {"func_96662_c", "setSuffix", "c"}, // {Thermos, 1.5.1+, 1.5}
String.class
);
}
if (minorVersion >= 17) {
TeamPacketConstructor_of = ReflectionUtils.getOnlyMethod(TeamPacketClass, TeamPacketClass, scoreboardTeam);
TeamPacketConstructor_ofBoolean = ReflectionUtils.getOnlyMethod(TeamPacketClass, TeamPacketClass, scoreboardTeam, boolean.class);
} else {
newTeamPacket = TeamPacketClass.getConstructor(scoreboardTeam, int.class);
}
}
@SneakyThrows
public Object registerTeam(@NotNull String name, @NotNull String prefix, @Nullable Object prefixComponent,
@NotNull String suffix, @Nullable Object suffixComponent,
@NotNull NameVisibility visibility, @NotNull CollisionRule collision,
@NotNull Collection<String> players, int options) {
Object team = createTeam(name, prefix, prefixComponent, suffix, suffixComponent, visibility, collision, options);
((Collection<String>)ScoreboardTeam_getPlayerNameSet.invoke(team)).addAll(players);
if (BukkitReflection.getMinorVersion() >= 17) {
return TeamPacketConstructor_ofBoolean.invoke(null, team, true);
} else {
return newTeamPacket.newInstance(team, TeamAction.CREATE);
}
}
@SneakyThrows
public Object unregisterTeam(@NotNull String name) {
Object team = newScoreboardTeam.newInstance(emptyScoreboard, name);
if (BukkitReflection.getMinorVersion() >= 17) {
return TeamPacketConstructor_of.invoke(null, team);
} else {
return newTeamPacket.newInstance(team, TeamAction.REMOVE);
}
}
@SneakyThrows
public Object updateTeam(@NotNull String name, @NotNull String prefix, @Nullable Object prefixComponent,
@NotNull String suffix, @Nullable Object suffixComponent,
@NotNull NameVisibility visibility, @NotNull CollisionRule collision, int options) {
Object team = createTeam(name, prefix, prefixComponent, suffix, suffixComponent, visibility, collision, options);
if (BukkitReflection.getMinorVersion() >= 17) {
return TeamPacketConstructor_ofBoolean.invoke(null, team, false);
} else {
return newTeamPacket.newInstance(team, TeamAction.UPDATE);
}
}
@SneakyThrows
private Object createTeam(@NotNull String teamName, @NotNull String prefix, @Nullable Object prefixComponent,
@NotNull String suffix, @Nullable Object suffixComponent,
@NotNull NameVisibility visibility, @NotNull CollisionRule collision, int options) {
Object team = newScoreboardTeam.newInstance(emptyScoreboard, teamName);
ScoreboardTeam_setAllowFriendlyFire.invoke(team, (options & 0x1) > 0);
ScoreboardTeam_setCanSeeFriendlyInvisibles.invoke(team, (options & 0x2) > 0);
if (BukkitReflection.getMinorVersion() >= 13) {
ScoreboardTeam_setPrefix.invoke(team, prefixComponent);
ScoreboardTeam_setSuffix.invoke(team, suffixComponent);
ScoreboardTeam_setColor.invoke(team, chatFormats[EnumChatFormat.lastColorsOf(prefix).ordinal()]);
} else {
ScoreboardTeam_setPrefix.invoke(team, prefix);
ScoreboardTeam_setSuffix.invoke(team, suffix);
}
if (BukkitReflection.getMinorVersion() >= 8)
ScoreboardTeam_setNameTagVisibility.invoke(team, nameVisibilities[visibility.ordinal()]);
if (BukkitReflection.getMinorVersion() >= 9)
ScoreboardTeam_setCollisionRule.invoke(team, collisionRules[collision.ordinal()]);
return team;
}
}
public static class DisplayPacketData {
public final Class<?> DisplayObjectiveClass;
private final Constructor<?> newDisplayObjective;
public final Field DisplayObjective_POSITION;
public final Field DisplayObjective_OBJECTIVE_NAME;
private final Object[] displaySlots;
@SneakyThrows
public DisplayPacketData() {
DisplayObjectiveClass = BukkitReflection.getClass(
"network.protocol.game.ClientboundSetDisplayObjectivePacket", // Mojang mapped
"network.protocol.game.PacketPlayOutScoreboardDisplayObjective", // Bukkit 1.17+
"PacketPlayOutScoreboardDisplayObjective", // Bukkit 1.7 - 1.16.5
"Packet208SetScoreboardDisplayObjective" // Bukkit 1.5 - 1.6.4
);
DisplayObjective_OBJECTIVE_NAME = ReflectionUtils.getOnlyField(DisplayObjectiveClass, String.class);
if (BukkitReflection.is1_20_2Plus()) {
Class<?> DisplaySlot = BukkitReflection.getClass("world.scores.DisplaySlot");
displaySlots = (Object[]) DisplaySlot.getDeclaredMethod("values").invoke(null);
DisplayObjective_POSITION = ReflectionUtils.getOnlyField(DisplayObjectiveClass, DisplaySlot);
newDisplayObjective = DisplayObjectiveClass.getConstructor(DisplaySlot, ScoreboardObjective);
} else {
displaySlots = new Object[]{0, 1, 2};
DisplayObjective_POSITION = ReflectionUtils.getOnlyField(DisplayObjectiveClass, int.class);
newDisplayObjective = DisplayObjectiveClass.getConstructor(int.class, ScoreboardObjective);
}
}
@SneakyThrows
public Object setDisplaySlot(int slot, @NotNull Object objective) {
return newDisplayObjective.newInstance(displaySlots[slot], objective);
}
}
}
@@ -1,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, int display) {
scoreboard.registerNewObjective(objectiveName, criteria, toAdventure(title), RenderType.values()[display]);
}
@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());
}
}
@@ -2,44 +2,35 @@ package me.neznamy.tab.platforms.bukkit.scoreboard;
import lombok.Getter;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.platforms.bukkit.nms.PacketSender;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.platforms.bukkit.BukkitUtils;
import me.neznamy.tab.platforms.bukkit.scoreboard.packet.PacketScoreboard;
import me.neznamy.tab.shared.platform.Scoreboard;
import org.bukkit.Bukkit;
import org.jetbrains.annotations.NotNull;
import java.util.function.Function;
/**
* Loader finding the best available Scoreboard implementation.
*/
public class ScoreboardLoader {
/** Instance function */
@Getter
private static Function<BukkitTabPlayer, Scoreboard<BukkitTabPlayer>> instance;
@NotNull
private static Function<BukkitTabPlayer, Scoreboard<BukkitTabPlayer, ?>> instance = NullScoreboard::new;
/**
* Finds the best available instance for current server software.
* Tries to load Scoreboard packets.
*/
public static void findInstance() {
if (PacketScoreboard.isAvailable() && PacketSender.isAvailable()) {
public static void tryLoad() {
if (PacketScoreboard.isAvailable()) {
instance = PacketScoreboard::new;
} else if (PaperScoreboard.isAvailable()) {
instance = PaperScoreboard::new;
sendMessage("Paper");
} else if (BukkitScoreboard.isAvailable()) {
instance = BukkitScoreboard::new;
sendMessage("Bukkit");
} else {
instance = NullScoreboard::new;
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.RED.getFormat() + "[TAB] Failed to initialize NMS fields for " +
"Scoreboards due to a compatibility error. No fallback solution was found either. This will " +
"result in the features not working. " +
"Please update the plugin a to version with native support for your server version to unlock the features.");
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)");
}
}
private static void sendMessage(String implementationName) {
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.RED.getFormat() + "[TAB] Failed to initialize NMS fields for " +
"Scoreboards due to a compatibility error. Using fallback solution using " + implementationName + " API. " +
"This may result in features not working properly, mainly compatibility with other plugins. " +
"Please update the plugin to a version with native support for your server version for optimal experience.");
}
}
@@ -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,309 @@
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;
/**
* 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);
TeamPacket_ACTION = ReflectionUtils.getInstanceFields(TeamPacketClass, int.class).get(0);
TeamPacket_PLAYERS = ReflectionUtils.getOnlyField(TeamPacketClass, Collection.class);
ScoreboardTeam_getPlayerNameSet = ReflectionUtils.getOnlyMethod(scoreboardTeam, Collection.class);
chatFormats = (Enum<?>[]) enumChatFormatClass.getMethod("values").invoke(null);
ScoreboardTeam_setAllowFriendlyFire = ReflectionUtils.getMethod(
scoreboardTeam,
new String[]{"func_96660_a", "setAllowFriendlyFire", "a", "m_83355_"}, // {Thermos, 1.5.1+, 1.5 & 1.18+, Mohist 1.18.2}
boolean.class
);
ScoreboardTeam_setCanSeeFriendlyInvisibles = ReflectionUtils.getMethod(
scoreboardTeam,
new String[]{"func_98300_b", "setCanSeeFriendlyInvisibles", "b", "m_83362_", "setSeeFriendlyInvisibles"}, // {Thermos, 1.5.1+, 1.5 & 1.18+, Mohist 1.18.2, 1.20.2+}
boolean.class
);
if (minorVersion >= 8) 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.REMOVE || 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,7 +1,7 @@
package me.neznamy.tab.platforms.bungeecord;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
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;
@@ -29,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);
packet.setTitle(player.getPlatform().toComponent(IChatBaseComponent.optimizedComponent(title), player.getVersion()));
packet.setTitle(TabComponent.optimized(title).convert(player.getVersion()));
packet.setColor(color.ordinal());
packet.setDivision(style.ordinal());
player.sendPacket(packet);
@@ -46,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);
packet.setTitle(player.getPlatform().toComponent(IChatBaseComponent.optimizedComponent(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);
@@ -64,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));
@@ -76,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());
@@ -88,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,22 +17,37 @@ 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.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_3.getNetworkId()) {
if (p != null && p.getVersion().getNetworkId() >= ProtocolVersion.V1_20_2.getNetworkId()) {
p.getScoreboard().freeze();
p.getBossBar().freeze();
}
tab.getCPUManager().runTask(() -> {
@@ -58,6 +73,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())) {
@@ -65,8 +86,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,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,7 +44,6 @@ 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(Protocol.DirectionData.class.getDeclaredMethod("getId", Class.class, int.class));
} catch (ReflectiveOperationException 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,108 +80,9 @@ public class BungeePipelineInjector extends NettyPipelineInjector {
}
@Override
@Nullable
@SneakyThrows
@NotNull
protected Channel getChannel(@NotNull TabPlayer player) {
if (wrapperField == null) return null;
return ((ChannelWrapper) wrapperField.get(((BungeeTabPlayer) player).getPlayer().getPendingConnection())).getHandle();
}
@Override
public void onDisplayObjective(@NotNull TabPlayer player, @NotNull Object packet) {
TAB.getInstance().getFeatureManager().onDisplayObjective(player,
((ScoreboardDisplay)packet).getPosition(), ((ScoreboardDisplay) packet).getName());
}
@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 PlayerListItem) {
PlayerListItem listItem = (PlayerListItem) packet;
for (PlayerListItem.Item item : listItem.getItems()) {
if (listItem.getAction() == PlayerListItem.Action.UPDATE_DISPLAY_NAME || listItem.getAction() == PlayerListItem.Action.ADD_PLAYER) {
IChatBaseComponent newDisplayName = TAB.getInstance().getFeatureManager().onDisplayNameChange(receiver, item.getUuid());
if (newDisplayName != null) item.setDisplayName(((BungeeTabPlayer)receiver).getPlatform().toComponent(newDisplayName, receiver.getVersion()));
}
if (listItem.getAction() == PlayerListItem.Action.UPDATE_LATENCY || listItem.getAction() == PlayerListItem.Action.ADD_PLAYER) {
item.setPing(TAB.getInstance().getFeatureManager().onLatencyChange(receiver, item.getUuid(), item.getPing()));
}
if (listItem.getAction() == PlayerListItem.Action.ADD_PLAYER) {
TAB.getInstance().getFeatureManager().onEntryAdd(receiver, item.getUuid(), item.getUsername());
}
}
} else {
PlayerListItemUpdate update = (PlayerListItemUpdate) packet;
for (PlayerListItem.Item item : update.getItems()) {
if (update.getActions().contains(PlayerListItemUpdate.Action.UPDATE_DISPLAY_NAME)) {
IChatBaseComponent newDisplayName = TAB.getInstance().getFeatureManager().onDisplayNameChange(receiver, item.getUuid());
if (newDisplayName != null) item.setDisplayName(((BungeeTabPlayer)receiver).getPlatform().toComponent(newDisplayName, receiver.getVersion()));
}
if (update.getActions().contains(PlayerListItemUpdate.Action.UPDATE_LATENCY)) {
item.setPing(TAB.getInstance().getFeatureManager().onLatencyChange(receiver, item.getUuid(), item.getPing()));
}
if (update.getActions().contains(PlayerListItemUpdate.Action.ADD_PLAYER)) {
TAB.getInstance().getFeatureManager().onEntryAdd(receiver, item.getUuid(), item.getUsername());
}
}
}
return ((UserConnection) ((BungeeTabPlayer) player).getPlayer()).getCh().getHandle();
}
@Override
@@ -185,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
@@ -205,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);
}
@@ -227,12 +125,11 @@ public class BungeePipelineInjector extends NettyPipelineInjector {
*/
@NotNull
private Object deserialize(@NotNull ByteBuf buf) {
if (wrapperField == null) return buf;
int marker = buf.readerIndex();
try {
int packetId = buf.readByte();
for (int i=0; i<extraPacketClasses.length; i++) {
ChannelWrapper ch = (ChannelWrapper) wrapperField.get(((BungeeTabPlayer) player).getPlayer().getPendingConnection());
ChannelWrapper ch = ((UserConnection) ((BungeeTabPlayer) player).getPlayer()).getCh();
if (!ch.getEncodeProtocol().TO_CLIENT.hasPacket(extraPacketClasses[i], ((ProxiedPlayer)player.getPlayer()).getPendingConnection().getVersion())) {
continue;
}
@@ -1,27 +1,20 @@
package me.neznamy.tab.platforms.bungeecord;
import com.imaginarycode.minecraft.redisbungee.RedisBungeeAPI;
import de.myzelyam.api.vanish.BungeeVanishAPI;
import lombok.AllArgsConstructor;
import lombok.Getter;
import me.neznamy.tab.platforms.bungeecord.features.BungeeRedisSupport;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.platforms.bungeecord.hook.BungeePremiumVanishHook;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.chat.ChatModifier;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.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.platform.TabPlayer;
import me.neznamy.tab.shared.hook.PremiumVanishHook;
import me.neznamy.tab.shared.proxy.ProxyPlatform;
import me.neznamy.tab.shared.util.ComponentCache;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.ProxyConfig;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.ClickEvent;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import org.bstats.bungeecord.Metrics;
@@ -30,27 +23,42 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.util.stream.Collectors;
import java.util.ArrayList;
import java.util.List;
/**
* BungeeCord implementation of Platform
*/
@AllArgsConstructor
public class BungeePlatform extends ProxyPlatform {
/** Flag tracking plugin presence */
@Getter
private final boolean premiumVanish = ProxyServer.getInstance().getPluginManager().getPlugin("PremiumVanish") != null;
/** Component cache for better performance */
private final ComponentCache<IChatBaseComponent, BaseComponent> cache = new ComponentCache<>(1000, this::toComponent0);
@NotNull
private final BungeeTAB plugin;
/**
* 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));
}
@@ -67,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
@@ -94,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"));
}
@@ -116,42 +124,18 @@ public class BungeePlatform extends ProxyPlatform {
}
@Override
public boolean canSee(@NotNull TabPlayer viewer, @NotNull TabPlayer target) {
//noinspection ConstantConditions
if (premiumVanish && BungeeVanishAPI.canSee(
((BungeeTabPlayer)viewer).getPlayer(),
((BungeeTabPlayer)target).getPlayer())) return true;
return super.canSee(viewer, target);
}
/**
* Converts internal component class to platform's component class. If the component is
* present in the cache, it is taken from it.
*
* @param component
* Component to convert
* @param version
* Game version to convert component for
* @return Converted component
*/
public BaseComponent toComponent(@NotNull IChatBaseComponent component, @NotNull ProtocolVersion version) {
return cache.get(component, version);
}
/**
* Converts internal component class to platform's component class
*
* @param component
* Component to convert
* @param version
* Game version to convert component for
* @return Converted component
*/
private BaseComponent toComponent0(@NotNull IChatBaseComponent component, @NotNull ProtocolVersion version) {
TextComponent textComponent = new TextComponent(component.getText());
ChatModifier modifier = component.getModifier();
if (modifier.getColor() != null) textComponent.setColor(ChatColor.of(
modifier.getColor().toString(version.getMinorVersion() >= 16)));
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);
@@ -161,15 +145,14 @@ public class BungeePlatform extends ProxyPlatform {
textComponent.setFont(modifier.getFont());
if (modifier.getClickEvent() != null) {
textComponent.setClickEvent(new ClickEvent(
ClickEvent.Action.valueOf(modifier.getClickEvent().getAction().name()),
modifier.getClickEvent().getValue()
));
if (!iComponent.getExtra().isEmpty()) {
List<BaseComponent> list = new ArrayList<>();
for (StructuredComponent extra : iComponent.getExtra()) {
list.add(convertComponent(extra, modern));
}
textComponent.setExtra(list);
}
if (!component.getExtra().isEmpty()) textComponent.setExtra(
component.getExtra().stream().map(c -> toComponent(c, version)).collect(Collectors.toList()));
return textComponent;
}
}
@@ -1,14 +1,16 @@
package me.neznamy.tab.platforms.bungeecord;
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.api.chat.BaseComponent;
import net.md_5.bungee.protocol.Either;
import net.md_5.bungee.protocol.NumberFormat;
import net.md_5.bungee.protocol.packet.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
@@ -18,32 +20,40 @@ import java.util.Collection;
* 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
public void setDisplaySlot0(int slot, @NotNull String 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, int display,
@Nullable IChatBaseComponent numberFormat) {
public void registerObjective0(@NonNull String objectiveName, @NonNull String title, int display,
@Nullable BaseComponent numberFormat) {
player.sendPacket(new ScoreboardObjective(
objectiveName,
either(title),
ScoreboardObjective.HealthDisplay.values()[display],
(byte) ObjectiveAction.REGISTER,
numberFormat == null ? null : new NumberFormat(NumberFormat.Type.FIXED,
player.getPlatform().toComponent(numberFormat, player.getVersion()))
numberFormat == null ? null : new NumberFormat(NumberFormat.Type.FIXED, numberFormat)
));
}
@Override
public void unregisterObjective0(@NotNull String objectiveName) {
public void unregisterObjective0(@NonNull String objectiveName) {
player.sendPacket(new ScoreboardObjective(
objectiveName,
either(""), // Empty value instead of null to prevent NPE kick on 1.7
@@ -54,26 +64,21 @@ public class BungeeScoreboard extends Scoreboard<BungeeTabPlayer> {
}
@Override
public void updateObjective0(@NotNull String objectiveName, @NotNull String title, int display,
@Nullable IChatBaseComponent numberFormat) {
public void updateObjective0(@NonNull String objectiveName, @NonNull String title, int display,
@Nullable BaseComponent numberFormat) {
player.sendPacket(new ScoreboardObjective(
objectiveName,
either(title),
ScoreboardObjective.HealthDisplay.values()[display],
(byte) ObjectiveAction.UPDATE,
numberFormat == null ? null : new NumberFormat(NumberFormat.Type.FIXED,
player.getPlatform().toComponent(numberFormat, player.getVersion()))
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();
}
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) TeamAction.CREATE,
@@ -82,24 +87,21 @@ public class BungeeScoreboard extends Scoreboard<BungeeTabPlayer> {
either(suffix),
visibility.toString(),
collision.toString(),
color,
player.getVersion().getMinorVersion() >= TEAM_REWORK_VERSION ? color.ordinal() : 0,
(byte)options,
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();
}
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) TeamAction.UPDATE,
@@ -108,28 +110,27 @@ public class BungeeScoreboard extends Scoreboard<BungeeTabPlayer> {
either(suffix),
visibility.toString(),
collision.toString(),
color,
player.getVersion().getMinorVersion() >= TEAM_REWORK_VERSION ? color.ordinal() : 0,
(byte)options,
null
));
}
@Override
public void setScore0(@NotNull String objective, @NotNull String scoreHolder, int score,
@Nullable IChatBaseComponent displayName, @Nullable IChatBaseComponent numberFormat) {
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 == null ? null : player.getPlatform().toComponent(displayName, player.getVersion()),
numberFormat == null ? null : new NumberFormat(NumberFormat.Type.FIXED,
player.getPlatform().toComponent(numberFormat, player.getVersion()))
displayName,
numberFormat == null ? null : new NumberFormat(NumberFormat.Type.FIXED, numberFormat)
));
}
@Override
public void removeScore0(@NotNull String objective, @NotNull String scoreHolder) {
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 {
@@ -137,11 +138,28 @@ public class BungeeScoreboard extends Scoreboard<BungeeTabPlayer> {
}
}
private Either<String, BaseComponent> either(@NotNull String text) {
if (player.getVersion().getMinorVersion() >= 13) {
return Either.right(player.getPlatform().toComponent(IChatBaseComponent.optimizedComponent(text), player.getVersion()));
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.REMOVE || team.getMode() == TeamAction.UPDATE) return;
team.setPlayers(onTeamPacket(team.getMode(), team.getName(), Lists.newArrayList(team.getPlayers())).toArray(new String[0]));
}
}
}
@@ -13,8 +13,8 @@ public class BungeeTAB extends Plugin {
@Override
public void onEnable() {
if (!ReflectionUtils.classExists("net.md_5.bungee.protocol.packet.ScoreboardScoreReset")) {
getLogger().warning(EnumChatFormat.color("&cThe plugin requires BungeeCord build #1774 " +
"(released on November 25th, 2023) and up (or an equivalent fork) to work."));
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,19 +1,20 @@
package me.neznamy.tab.platforms.bungeecord;
import de.myzelyam.api.vanish.BungeeVanishAPI;
import lombok.Getter;
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;
import me.neznamy.tab.shared.proxy.ProxyTabPlayer;
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.connection.InitialHandler;
import net.md_5.bungee.connection.LoginResult;
@@ -30,17 +31,17 @@ public class BungeeTabPlayer extends ProxyTabPlayer {
/** 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
@@ -55,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
@@ -69,8 +70,8 @@ public class BungeeTabPlayer extends ProxyTabPlayer {
}
@Override
public void sendMessage(@NotNull IChatBaseComponent message) {
getPlayer().sendMessage(getPlatform().toComponent(message, getVersion()));
public void sendMessage(@NotNull TabComponent message) {
getPlayer().sendMessage((BaseComponent) message.convert(getVersion()));
}
@Override
@@ -89,11 +90,6 @@ public class BungeeTabPlayer extends ProxyTabPlayer {
return (ProxiedPlayer) player;
}
@Override
public boolean isOnline() {
return getPlayer().isConnected();
}
@Override
public BungeePlatform getPlatform() {
return (BungeePlatform) platform;
@@ -116,33 +112,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 (getPlatform().isPremiumVanish() && 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;
@@ -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,74 +1,155 @@
package me.neznamy.tab.platforms.bungeecord.tablist;
import lombok.NonNull;
import lombok.SneakyThrows;
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(new PlayerListHeaderFooter(
player.getPlatform().toComponent(header, player.getVersion()),
player.getPlatform().toComponent(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) {
item.setDisplayName(player.getPlatform().toComponent(entry.getDisplayName(), player.getVersion()));
}
item.setGamemode(entry.getGameMode());
item.setListed(true);
item.setPing(entry.getLatency());
if (entry.getSkin() != null) {
item.setProperties(new Property[]{new Property(TEXTURES_PROPERTY, entry.getSkin().getValue(), entry.getSkin().getSignature())});
/**
* 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,15 +1,14 @@
package me.neznamy.tab.platforms.bungeecord.tablist;
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.
@@ -17,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});
@@ -30,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) item.setDisplayName(player.getPlatform().toComponent(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,10 +1,12 @@
package me.neznamy.tab.platforms.bungeecord.tablist;
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;
@@ -23,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));
@@ -45,51 +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
}
@Override
public void setPlayerListHeaderFooter(@NotNull IChatBaseComponent header, @NotNull IChatBaseComponent footer) {
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
}
private void update(@NotNull PlayerListItem.Action action, @NotNull PlayerListItem.Item item) {
@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);
item.setPing(latency);
item.setDisplayName(player.getPlatform().toComponent(IChatBaseComponent.optimizedComponent(displayName), player.getVersion()));
item.setDisplayName(displayName);
return item;
}
}
@@ -1,10 +1,10 @@
package me.neznamy.tab.platforms.bungeecord.tablist;
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;
@@ -15,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) item.setDisplayName(player.getPlatform().toComponent(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});
+19 -6
View File
@@ -1,5 +1,3 @@
val minecraftVersion = "1.20.3"
plugins {
id("fabric-loom")
}
@@ -14,13 +12,28 @@ repositories {
dependencies {
api(projects.shared)
minecraft("com.mojang:minecraft:$minecraftVersion")
minecraft("com.mojang:minecraft:24w19a")
mappings(loom.officialMojangMappings())
modImplementation("me.lucko:fabric-permissions-api:0.2-SNAPSHOT")
modImplementation("net.fabricmc:fabric-loader:0.14.17")
val version = "0.90.4+1.20.3"
modImplementation("net.fabricmc:fabric-loader:0.15.9")
val version = "0.97.8+1.20.6"
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/resources/tab.accesswidener"))
}
tasks {
compileJava {
options.release.set(17)
}
validateAccessWidener {
enabled = true
}
}
@@ -3,7 +3,7 @@ package me.neznamy.tab.platforms.fabric;
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.chat.TabComponent;
import me.neznamy.tab.shared.platform.BossBar;
import net.minecraft.server.level.ServerBossEvent;
import net.minecraft.world.BossEvent.BossBarColor;
@@ -14,19 +14,24 @@ import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* BossBar implementation for Fabric using packets.
*/
@RequiredArgsConstructor
public class FabricBossBar implements BossBar {
/** 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()),
TabComponent.optimized(title).convert(player.getVersion()),
BossBarColor.valueOf(color.name()),
BossBarOverlay.valueOf(style.name())
);
@@ -37,7 +42,7 @@ public class FabricBossBar implements BossBar {
@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
@@ -1,37 +1,55 @@
package me.neznamy.tab.platforms.fabric;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import me.neznamy.tab.shared.backend.EntityData;
import me.neznamy.tab.shared.backend.Location;
import me.neznamy.tab.shared.backend.entityview.EntityView;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.minecraft.network.protocol.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;
@RequiredArgsConstructor
/**
* 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 ArmorStand 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(FabricTAB.getVersion().spawnEntity(entityId, id, entityType, location));
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(FabricTAB.getVersion().newEntityMetadata(entityId, data));
player.sendPacket(FabricMultiVersion.newEntityMetadata(entityId, data));
}
@Override
@@ -43,7 +61,7 @@ public class FabricEntityView implements EntityView {
@Override
public void destroyEntities(int... entities) {
FabricTAB.getVersion().destroyEntities(player, entities);
player.sendPacket(new ClientboundRemoveEntitiesPacket(entities));
}
@Override
@@ -58,7 +76,7 @@ public class FabricEntityView implements EntityView {
@Override
public boolean isNamedEntitySpawnPacket(@NotNull Object packet) {
return FabricTAB.getVersion().isSpawnPlayerPacket(packet);
return FabricMultiVersion.isSpawnPlayerPacket((Packet<?>) packet);
}
@Override
@@ -72,35 +90,50 @@ public class FabricEntityView implements EntityView {
}
@Override
@SneakyThrows
public int getTeleportEntityId(@NotNull Object teleportPacket) {
// Reflection because on 1.16.5 there is no getter and per-version code would only add unnecessary code
return ReflectionUtils.getFields(ClientboundTeleportEntityPacket.class, int.class).get(0).getInt(teleportPacket);
return ((ClientboundTeleportEntityPacket)teleportPacket).id;
}
@Override
@SneakyThrows
public int getMoveEntityId(@NotNull Object movePacket) {
return (int) ReflectionUtils.getFields(ClientboundMoveEntityPacket.class, int.class).get(0).get(movePacket);
return ((ClientboundMoveEntityPacket)movePacket).entityId;
}
@Override
public int getSpawnedPlayer(@NotNull Object playerSpawnPacket) {
return FabricTAB.getVersion().getSpawnedPlayer(playerSpawnPacket);
return FabricMultiVersion.getSpawnedPlayerId((Packet<?>) playerSpawnPacket);
}
@Override
public int[] getDestroyedEntities(@NotNull Object destroyPacket) {
return FabricTAB.getVersion().getDestroyedEntities(destroyPacket);
return FabricMultiVersion.getDestroyedEntities((Packet<?>) destroyPacket);
}
@Override
public boolean isBundlePacket(@NotNull Object packet) {
return FabricTAB.getVersion().isBundlePacket(packet);
return FabricMultiVersion.isBundlePacket((Packet<?>) packet);
}
@Override
public Iterable<Object> getPackets(@NotNull Object bundlePacket) {
return FabricTAB.getVersion().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
));
}
}
@@ -1,31 +1,37 @@
package me.neznamy.tab.platforms.fabric;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.platform.EventListener;
import me.neznamy.tab.shared.platform.TabPlayer;
import 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));
//TODO command preprocess
if (ProtocolVersion.fromFriendlyName(FabricTAB.getVersion().getServerVersion()).getMinorVersion() >= 16) {
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(), FabricTAB.getVersion().getName(FabricTAB.getVersion().getLevel(newPlayer)));
worldChange(newPlayer.getUUID(), FabricMultiVersion.getLevelName(FabricMultiVersion.getLevel(newPlayer)));
});
ServerEntityWorldChangeEvents.AFTER_PLAYER_CHANGE_WORLD.register(
(player, origin, destination) -> worldChange(player.getUUID(), FabricTAB.getVersion().getName(destination)));
(player, origin, destination) -> worldChange(player.getUUID(), FabricMultiVersion.getLevelName(destination)));
} // TODO else
}
@@ -0,0 +1,571 @@
package me.neznamy.tab.platforms.fabric;
import com.mojang.authlib.properties.Property;
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_20_5;
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.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.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.ClientGamePacketListener;
import net.minecraft.network.protocol.game.ClientboundSetDisplayObjectivePacket;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.level.Level;
import net.minecraft.world.scores.Objective;
import net.minecraft.world.scores.PlayerTeam;
import net.minecraft.world.scores.criteria.ObjectiveCriteria.RenderType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.UUID;
/**
* Class managing cross-version code in shared module.
*/
public class FabricMultiVersion {
/** Server version */
private static final ProtocolVersion serverVersion = ProtocolVersion.fromFriendlyName(FabricTAB.minecraftVersion);
/** Method loader using latest supported MC version */
private static final Loader loaderNew = new Loader_1_20_5();
/** 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);
}
/**
* 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);
}
/**
* 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);
}
/**
* 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);
}
/**
* 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);
}
/**
* 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);
}
/**
* 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);
}
/**
* 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);
}
/**
* 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);
}
/**
* 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);
}
/**
* 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);
}
/**
* 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);
}
/**
* 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);
}
/**
* 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);
}
/**
* 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);
}
/**
* 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);
}
/**
* 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);
}
/**
* 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) {
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);
}
/**
* 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);
}
/**
* 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);
}
/**
* 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);
}
/**
* 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);
}
/**
* 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);
}
/**
* 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);
}
/**
* 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);
}
/**
* 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);
}
}
@@ -0,0 +1,25 @@
package me.neznamy.tab.platforms.fabric;
import io.netty.channel.Channel;
import me.neznamy.tab.shared.features.injection.NettyPipelineInjector;
import me.neznamy.tab.shared.platform.TabPlayer;
import org.jetbrains.annotations.NotNull;
/**
* Pipeline injector for Fabric.
*/
public class FabricPipelineInjector extends NettyPipelineInjector {
/**
* Constructs new instance.
*/
public FabricPipelineInjector() {
super("packet_handler");
}
@Override
@NotNull
protected Channel getChannel(@NotNull TabPlayer player) {
return FabricMultiVersion.getChannel(((FabricTabPlayer)player).getPlayer());
}
}
@@ -2,20 +2,20 @@ package me.neznamy.tab.platforms.fabric;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import me.lucko.fabric.api.permissions.v0.Permissions;
import me.neznamy.tab.platforms.fabric.features.FabricNameTagX;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.backend.BackendPlatform;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.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;
import me.neznamy.tab.shared.placeholders.expansion.EmptyTabExpansion;
import me.neznamy.tab.shared.placeholders.expansion.TabExpansion;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.fabricmc.fabric.api.networking.v1.PlayerLookup;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.commands.CommandSourceStack;
@@ -34,10 +34,15 @@ import java.io.File;
@Getter
public class FabricPlatform implements BackendPlatform {
private static final boolean fabricPermissionsApi = FabricLoader.getInstance().isModLoaded("fabric-permissions-api-v0");
/** 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) {
registerDummyPlaceholder(identifier);
@@ -53,7 +58,7 @@ public class FabricPlatform implements BackendPlatform {
@Override
@NotNull
public PipelineInjector createPipelineInjector() {
return FabricTAB.getVersion().createPipelineInjector();
return new FabricPipelineInjector();
}
@Override
@@ -75,34 +80,19 @@ public class FabricPlatform implements BackendPlatform {
}
@Override
@SneakyThrows
public void logInfo(@NotNull IChatBaseComponent message) {
Object logger = getLogger();
logger.getClass().getMethod("info", String.class).invoke(logger, message.toRawText());
public void logInfo(@NotNull TabComponent message) {
FabricMultiVersion.logInfo(message);
}
@Override
@SneakyThrows
public void logWarn(@NotNull IChatBaseComponent message) {
Object logger = getLogger();
logger.getClass().getMethod("warn", String.class).invoke(logger, message.toRawText());
}
@SneakyThrows
private Object getLogger() {
Class<?> loggerClass;
if (getServerVersion().getNetworkId() >= ProtocolVersion.V1_18_2.getNetworkId()) {
loggerClass = Class.forName("org.slf4j.Logger");
} else {
loggerClass = Class.forName("org.apache.logging.log4j.Logger");
}
return ReflectionUtils.getFields(MinecraftServer.class, loggerClass).get(0).get(null);
public void logWarn(@NotNull TabComponent message) {
FabricMultiVersion.logWarn(message);
}
@Override
@NotNull
public String getServerVersionInfo() {
return "[Fabric] " + FabricTAB.getVersion().getServerVersion();
return "[Fabric] " + FabricTAB.minecraftVersion;
}
@Override
@@ -120,29 +110,24 @@ public class FabricPlatform implements BackendPlatform {
// Not available
}
@Override
@NotNull
public ProtocolVersion getServerVersion() {
return ProtocolVersion.fromFriendlyName(FabricTAB.getVersion().getServerVersion());
}
@Override
@NotNull
public File getDataFolder() {
return FabricLoader.getInstance().getConfigDir().resolve(TabConstants.PLUGIN_ID).toFile();
}
/**
* Converts internal component class to platform's component class
*
* @param component
* Component to convert
* @param version
* Game version to convert component for
* @return Converted component
*/
public Component toComponent(@NotNull IChatBaseComponent component, @NotNull ProtocolVersion version) {
return FabricTAB.getVersion().deserialize(component.toString(version));
@Override
public 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
@@ -152,9 +137,18 @@ public class FabricPlatform implements BackendPlatform {
@Override
public double getMSPT() {
return FabricTAB.getVersion().getMSPT();
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,59 +1,70 @@
package me.neznamy.tab.platforms.fabric;
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.world.scores.Objective;
import net.minecraft.world.scores.PlayerTeam;
import net.minecraft.world.scores.Team;
import net.minecraft.world.scores.criteria.ObjectiveCriteria.RenderType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
public class FabricScoreboard extends Scoreboard<FabricTabPlayer> {
/**
* 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();
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 setDisplaySlot0(int slot, @NotNull String objective) {
player.sendPacket(FabricTAB.getVersion().setDisplaySlot(slot, objectives.get(objective)));
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, int display,
@Nullable IChatBaseComponent numberFormat) {
Objective obj = FabricTAB.getVersion().newObjective(
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 == null ? null : toComponent(numberFormat)
numberFormat
);
objectives.put(objectiveName, obj);
player.sendPacket(new ClientboundSetObjectivePacket(obj, ObjectiveAction.REGISTER));
}
@Override
public void unregisterObjective0(@NotNull String objectiveName) {
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, int display,
@Nullable IChatBaseComponent numberFormat) {
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]);
@@ -61,64 +72,67 @@ public class FabricScoreboard extends Scoreboard<FabricTabPlayer> {
}
@Override
public void registerTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix,
@NotNull NameVisibility visibility, @NotNull CollisionRule collision,
@NotNull Collection<String> players, int options) {
public void registerTeam0(@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(toComponent(prefix));
team.setPlayerSuffix(toComponent(suffix));
team.getPlayers().addAll(players);
player.sendPacket(FabricTAB.getVersion().registerTeam(team));
player.sendPacket(FabricMultiVersion.registerTeam(team));
}
@Override
public void unregisterTeam0(@NotNull String name) {
player.sendPacket(FabricTAB.getVersion().unregisterTeam(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(toComponent(prefix));
team.setPlayerSuffix(toComponent(suffix));
player.sendPacket(FabricTAB.getVersion().updateTeam(team));
player.sendPacket(FabricMultiVersion.updateTeam(team));
}
@Override
public void setScore0(@NotNull String objective, @NotNull String scoreHolder, int score,
@Nullable IChatBaseComponent displayName, @Nullable IChatBaseComponent numberFormat) {
player.sendPacket(FabricTAB.getVersion().setScore(
objective,
scoreHolder,
score,
displayName == null ? null : toComponent(displayName),
numberFormat == null ? null : toComponent(numberFormat)
));
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 scoreHolder) {
player.sendPacket(FabricTAB.getVersion().removeScore(objective, scoreHolder));
public void removeScore0(@NonNull String objective, @NonNull String scoreHolder) {
player.sendPacket(FabricMultiVersion.removeScore(objective, scoreHolder));
}
@NotNull
private Component toComponent(@NotNull String string) {
return toComponent(IChatBaseComponent.optimizedComponent(string));
@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);
}
}
@NotNull
private Component toComponent(@NotNull IChatBaseComponent component) {
return player.getPlatform().toComponent(component, player.getVersion());
@NonNull
private Component toComponent(@NonNull String string) {
return TabComponent.optimized(string).convert(player.getVersion());
}
}
@@ -1,40 +1,41 @@
package me.neznamy.tab.platforms.fabric;
import lombok.Getter;
import lombok.SneakyThrows;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.fabricmc.api.DedicatedServerModInitializer;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
import net.minecraft.SharedConstants;
/**
* Main class for Fabric.
*/
public class FabricTAB implements DedicatedServerModInitializer {
@Getter
private static VersionLoader version;
/** Minecraft version string */
public static final String minecraftVersion = getServerVersion();
@Override
public void onInitializeServer() {
String[] modules = {
"v1_14_4",
"v1_15_2",
"v1_16_5",
"v1_17",
"v1_17_1",
"v1_18_2", // 1.18, 1.18.1, 1.18.2
"v1_19_2", // 1.19, 1.19.1, 1.19.2
"v1_19_3",
"v1_20_1", // 1.19.4, 1.20, 1.20.1
"v1_20_2",
"v1_20_3"
};
for (String module : modules) {
try {
version = (VersionLoader) Class.forName("me.neznamy.tab.platforms.fabric." + module + ".FabricTAB").getConstructor().newInstance();
if (!version.getSupportedVersions().contains(version.getServerVersion())) continue;
version.registerCommandCallback();
ServerLifecycleEvents.SERVER_STARTING.register(server -> TAB.create(new FabricPlatform(server)));
ServerLifecycleEvents.SERVER_STOPPING.register($ -> TAB.getInstance().unload());
return;
} catch (Throwable ignored) {}
if (ReflectionUtils.classExists("net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback")) {
net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback.EVENT.register((dispatcher, $, $$) -> new FabricTabCommand().onRegisterCommands(dispatcher));
} else {
net.fabricmc.fabric.api.command.v1.CommandRegistrationCallback.EVENT.register((dispatcher, $) -> new FabricTabCommand().onRegisterCommands(dispatcher));
}
ServerLifecycleEvents.SERVER_STARTING.register(server -> TAB.create(new FabricPlatform(server)));
ServerLifecycleEvents.SERVER_STOPPING.register($ -> TAB.getInstance().unload());
}
@SneakyThrows
private static String getServerVersion() {
try {
// 1.19.4+
return SharedConstants.getCurrentVersion().getName();
} catch (Throwable e) {
// 1.19.3-
@SuppressWarnings("JavaReflectionMemberAccess") // Fabric-mapped method name
Object gameVersion = SharedConstants.class.getMethod("method_16673").invoke(null);
return (String) gameVersion.getClass().getMethod("getName").invoke(gameVersion);
}
throw new IllegalStateException("Your server version is marked as compatible, but a compatibility issue was found.");
}
}
@@ -9,7 +9,7 @@ import com.mojang.brigadier.tree.ArgumentCommandNode;
import com.mojang.brigadier.tree.LiteralCommandNode;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.chat.TabComponent;
import me.neznamy.tab.shared.platform.TabPlayer;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
@@ -18,8 +18,17 @@ import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
import java.util.concurrent.CompletableFuture;
/**
* Command handler for plugin's command for Fabric.
*/
public class FabricTabCommand {
/**
* Handles command register request from server and registers own command.
*
* @param dispatcher
* Dispatcher to register command to
*/
public void onRegisterCommands(@NotNull CommandDispatcher<CommandSourceStack> dispatcher) {
LiteralCommandNode<CommandSourceStack> command = Commands.literal(TabConstants.COMMAND_BACKEND)
.executes(context -> executeCommand(context.getSource(), new String[0]))
@@ -46,13 +55,14 @@ public class FabricTabCommand {
return args;
}
@SuppressWarnings("SameReturnValue") // Unused by plugin
private int executeCommand(@NotNull CommandSourceStack source, @NotNull String[] args) {
FabricPlatform platform = (FabricPlatform) TAB.getInstance().getPlatform();
if (TAB.getInstance().isPluginDisabled()) {
boolean hasReloadPermission = ((FabricPlatform)TAB.getInstance().getPlatform()).hasPermission(source, TabConstants.Permission.COMMAND_RELOAD);
boolean hasAdminPermission = ((FabricPlatform)TAB.getInstance().getPlatform()).hasPermission(source, TabConstants.Permission.COMMAND_ALL);
boolean hasReloadPermission = platform.hasPermission(source, TabConstants.Permission.COMMAND_RELOAD);
boolean hasAdminPermission = platform.hasPermission(source, TabConstants.Permission.COMMAND_ALL);
for (String message : TAB.getInstance().getDisabledCommand().execute(args, hasReloadPermission, hasAdminPermission)) {
FabricTAB.getVersion().sendMessage(source, ((FabricPlatform)TAB.getInstance().getPlatform()).toComponent(
IChatBaseComponent.optimizedComponent(message), TAB.getInstance().getServerVersion()));
FabricMultiVersion.sendMessage(source, TabComponent.optimized(message).convert(platform.getServerVersion()));
}
} else {
if (source.getEntity() == null) {
@@ -2,110 +2,107 @@ package me.neznamy.tab.platforms.fabric;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import lombok.*;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.platform.TabList;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.Packet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.UUID;
@RequiredArgsConstructor
public class FabricTabList implements TabList {
/**
* TabList implementation for Fabric using packets.
*/
public class FabricTabList extends TabList<FabricTabPlayer, Component> {
@NotNull
private final FabricTabPlayer player;
@Override
public void removeEntry(@NotNull UUID entry) {
player.sendPacket(FabricTAB.getVersion().build(Action.UPDATE_DISPLAY_NAME, new Builder(entry)));
/**
* Constructs new instance.
*
* @param player
* Player this tablist will belong to
*/
public FabricTabList(@NotNull FabricTabPlayer player) {
super(player);
}
@Override
public void updateDisplayName(@NotNull UUID entry, @Nullable IChatBaseComponent displayName) {
player.sendPacket(FabricTAB.getVersion().build(Action.UPDATE_DISPLAY_NAME,
new Builder(entry).setDisplayName(displayName == null ? null : player.getPlatform().toComponent(displayName, player.getVersion()))
));
public void removeEntry(@NonNull UUID entry) {
player.sendPacket(FabricMultiVersion.buildTabListPacket(Action.REMOVE_PLAYER,
new Builder(entry, "", null, false, 0, 0, null)));
}
@Override
public void updateLatency(@NotNull UUID entry, int latency) {
player.sendPacket(FabricTAB.getVersion().build(Action.UPDATE_LATENCY,
new Builder(entry).setLatency(latency)));
public void updateDisplayName0(@NonNull UUID entry, @Nullable Component displayName) {
player.sendPacket(FabricMultiVersion.buildTabListPacket(Action.UPDATE_DISPLAY_NAME,
new Builder(entry, "", null, false, 0, 0, displayName)));
}
@Override
public void updateGameMode(@NotNull UUID entry, int gameMode) {
player.sendPacket(FabricTAB.getVersion().build(Action.UPDATE_GAME_MODE,
new Builder(entry).setGameMode(gameMode)));
public void updateLatency(@NonNull UUID entry, int latency) {
player.sendPacket(FabricMultiVersion.buildTabListPacket(Action.UPDATE_LATENCY,
new Builder(entry, "", null, false, latency, 0, null)));
}
@Override
public void addEntry(@NotNull Entry entry) {
player.sendPacket(FabricTAB.getVersion().build(Action.ADD_PLAYER,
new Builder(entry.getUniqueId())
.setName(entry.getName())
.setSkin(entry.getSkin())
.setGameMode(entry.getGameMode())
.setLatency(entry.getLatency())
.setDisplayName(entry.getDisplayName() == null ? null : player.getPlatform().toComponent(entry.getDisplayName(), player.getVersion()))
));
public void updateGameMode(@NonNull UUID entry, int gameMode) {
player.sendPacket(FabricMultiVersion.buildTabListPacket(Action.UPDATE_GAME_MODE,
new Builder(entry, "", null, false, 0, gameMode, null)));
}
@Override
@SneakyThrows
public void setPlayerListHeaderFooter(@NotNull IChatBaseComponent header, @NotNull IChatBaseComponent footer) {
player.sendPacket(FabricTAB.getVersion().newHeaderFooter(
player.getPlatform().toComponent(header, player.getVersion()),
player.getPlatform().toComponent(footer, player.getVersion())
));
public void updateListed(@NonNull UUID entry, boolean listed) {
if (player.getPlatform().getServerVersion().getNetworkId() >= ProtocolVersion.V1_19_3.getNetworkId()) {
player.sendPacket(FabricMultiVersion.buildTabListPacket(Action.UPDATE_LISTED,
new Builder(entry, "", null, listed, 0, 0, null)));
}
}
@RequiredArgsConstructor
@Override
public void addEntry0(@NonNull UUID id, @NonNull String name, @Nullable Skin skin, boolean listed, int latency, int gameMode, @Nullable Component displayName) {
player.sendPacket(FabricMultiVersion.buildTabListPacket(Action.ADD_PLAYER,
new Builder(id, name, skin, listed, latency, gameMode, displayName)));
}
@Override
public void setPlayerListHeaderFooter0(@NonNull Component header, @NonNull Component footer) {
player.sendPacket(FabricMultiVersion.newHeaderFooter(header, footer));
}
@Override
public boolean containsEntry(@NonNull UUID entry) {
return true; // TODO?
}
@Override
public void onPacketSend(@NonNull Object packet) {
if (FabricMultiVersion.isPlayerInfo((Packet<?>) packet)) {
FabricMultiVersion.onPlayerInfo(player, packet);
}
}
/**
* TabList entry builder.
*/
@AllArgsConstructor
@Getter
public static class Builder {
@NotNull private final UUID id;
@NotNull private String name = ""; // Avoid nullability issues as things are changing over versions
@NonNull private final UUID id;
@NonNull private String name;
@Nullable private Skin skin;
private boolean listed;
private int latency;
private int gameMode;
@Nullable private Component displayName;
@NotNull
public Builder setName(@NotNull String name) {
this.name = name;
return this;
}
@NotNull
public Builder setSkin(@Nullable Skin skin) {
this.skin = skin;
return this;
}
@NotNull
public Builder setLatency(int latency) {
this.latency = latency;
return this;
}
@NotNull
public Builder setGameMode(int gameMode) {
this.gameMode = gameMode;
return this;
}
@NotNull
public Builder setDisplayName(@Nullable Component displayName) {
this.displayName = displayName;
return this;
}
@NotNull
/**
* Creates profile of this entry.
*
* @return Profile of this entry
*/
@NonNull
public GameProfile createProfile() {
GameProfile profile = new GameProfile(id, name);
if (skin != null) {
@@ -4,8 +4,7 @@ import com.mojang.authlib.properties.Property;
import lombok.Getter;
import me.neznamy.tab.shared.backend.BackendTabPlayer;
import me.neznamy.tab.shared.backend.entityview.EntityView;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.chat.TabComponent;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.BossBar;
import net.minecraft.network.protocol.Packet;
@@ -15,14 +14,17 @@ import org.jetbrains.annotations.Nullable;
import java.util.Collection;
/**
* TabPlayer implementation for Fabric.
*/
@Getter
public class FabricTabPlayer extends BackendTabPlayer {
@NotNull
private final Scoreboard<FabricTabPlayer> scoreboard = new FabricScoreboard(this);
private final FabricScoreboard scoreboard = new FabricScoreboard(this);
@NotNull
private final TabList tabList = new FabricTabList(this);
private final FabricTabList tabList = new FabricTabList(this);
@NotNull
private final BossBar bossBar = new FabricBossBar(this);
@@ -30,9 +32,17 @@ public class FabricTabPlayer extends BackendTabPlayer {
@NotNull
private final EntityView entityView = new FabricEntityView(this);
/**
* Constructs new instance with given parameters.
*
* @param platform
* Server platform
* @param player
* Platform's player object
*/
public FabricTabPlayer(@NotNull FabricPlatform platform, @NotNull ServerPlayer player) {
super(platform, player, player.getUUID(), player.getGameProfile().getName(),
FabricTAB.getVersion().getName(FabricTAB.getVersion().getLevel(player)));
FabricMultiVersion.getLevelName(FabricMultiVersion.getLevel(player)), platform.getServerVersion().getNetworkId());
}
@Override
@@ -42,12 +52,12 @@ public class FabricTabPlayer extends BackendTabPlayer {
@Override
public int getPing() {
return FabricTAB.getVersion().getPing(getPlayer());
return FabricMultiVersion.getPing(getPlayer());
}
@Override
public void sendMessage(@NotNull IChatBaseComponent message) {
FabricTAB.getVersion().sendMessage(getPlayer(), getPlatform().toComponent(message, getVersion()));
public void sendMessage(@NotNull TabComponent message) {
FabricMultiVersion.sendMessage(getPlayer(), message.convert(getVersion()));
}
@Override
@@ -65,7 +75,7 @@ public class FabricTabPlayer extends BackendTabPlayer {
public TabList.Skin getSkin() {
Collection<Property> properties = getPlayer().getGameProfile().getProperties().get(TabList.TEXTURES_PROPERTY);
if (properties.isEmpty()) return null; // Offline mode
return FabricTAB.getVersion().propertyToSkin(properties.iterator().next());
return FabricMultiVersion.propertyToSkin(properties.iterator().next());
}
@Override
@@ -74,11 +84,6 @@ public class FabricTabPlayer extends BackendTabPlayer {
return (ServerPlayer) player;
}
@Override
public boolean isOnline() {
return true;
}
@Override
public FabricPlatform getPlatform() {
return (FabricPlatform) platform;
@@ -102,7 +107,7 @@ public class FabricTabPlayer extends BackendTabPlayer {
@Override
@NotNull
public String getDisplayName() {
return getPlayer().getDisplayName().getString(); // Will make it work properly if someone asks
return getPlayer().getGameProfile().getName(); // Will make it work properly if someone asks
}
/**
@@ -1,90 +0,0 @@
package me.neznamy.tab.platforms.fabric;
import com.mojang.authlib.properties.Property;
import me.neznamy.tab.shared.backend.EntityData;
import me.neznamy.tab.shared.backend.Location;
import me.neznamy.tab.shared.features.injection.PipelineInjector;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.TabPlayer;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.Packet;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.decoration.ArmorStand;
import net.minecraft.world.level.Level;
import net.minecraft.world.scores.Objective;
import net.minecraft.world.scores.PlayerTeam;
import net.minecraft.world.scores.Scoreboard;
import net.minecraft.world.scores.criteria.ObjectiveCriteria.RenderType;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.UUID;
public interface VersionLoader {
Scoreboard dummyScoreboard = new Scoreboard();
ArmorStand dummyEntity = new ArmorStand(null, 0, 0, 0);
String getName(Level level);
int getPing(ServerPlayer player);
void sendMessage(ServerPlayer player, Component message);
TabList.Skin propertyToSkin(Property property);
void sendMessage(CommandSourceStack sender, Component message);
String getServerVersion();
double getMSPT();
Component deserialize(String string);
Packet<?> build(TabList.Action action, FabricTabList.Builder entry);
Level getLevel(ServerPlayer player);
boolean isSneaking(ServerPlayer player);
EntityData createDataWatcher(TabPlayer viewer, byte flags, String displayName, boolean nameVisible);
Packet<?> spawnEntity(int entityId, UUID id, Object entityType, Location location);
Packet<?> newEntityMetadata(int entityId, EntityData data);
boolean isSpawnPlayerPacket(Object packet);
int getSpawnedPlayer(Object spawnPacket);
int[] getDestroyedEntities(Object entityDestroyPacket);
boolean isBundlePacket(Object packet);
Iterable<Object> getPackets(Object bundlePacket);
PipelineInjector createPipelineInjector();
void destroyEntities(FabricTabPlayer player, int... entities);
void registerCommandCallback();
List<String> getSupportedVersions();
Packet<?> newHeaderFooter(Component header, Component footer);
Packet<?> setDisplaySlot(int slot, Objective objective);
Objective newObjective(String name, Component displayName, RenderType renderType, @Nullable Component numberFormat);
Packet<?> registerTeam(PlayerTeam team);
Packet<?> unregisterTeam(PlayerTeam team);
Packet<?> updateTeam(PlayerTeam team);
Packet<?> setScore(String objective, String scoreHolder, int score, Component displayName, @Nullable Component numberFormat);
Packet<?> removeScore(String objective, String scoreHolder);
}
@@ -1,11 +1,11 @@
package me.neznamy.tab.platforms.fabric.features;
import me.neznamy.tab.platforms.fabric.FabricTAB;
import me.neznamy.tab.platforms.fabric.FabricMultiVersion;
import me.neznamy.tab.platforms.fabric.FabricTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.backend.EntityData;
import me.neznamy.tab.shared.backend.features.unlimitedtags.BackendNameTagX;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.fabricmc.fabric.api.entity.event.v1.ServerPlayerEvents;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
@@ -16,12 +16,19 @@ import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.stream.Collectors;
/**
* Unlimited nametag mode implementation for Fabric.
*/
public class FabricNameTagX extends BackendNameTagX {
public boolean enabled = true;
/** Flag tracking whether this instance is still running or not */
private boolean enabled = true;
/**
* Constructs new instance and registers event listener.
*/
public FabricNameTagX() {
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 16) {
if (ReflectionUtils.classExists("net.fabricmc.fabric.api.entity.event.v1.ServerPlayerEvents")) {
// Added in 1.16
ServerPlayerEvents.AFTER_RESPAWN.register((oldPlayer, newPlayer, alive) -> {
if (enabled) respawn(oldPlayer.getUUID());
@@ -39,8 +46,8 @@ public class FabricNameTagX extends BackendNameTagX {
@Override
public boolean areInSameWorld(@NotNull TabPlayer player1, @NotNull TabPlayer player2) {
return FabricTAB.getVersion().getLevel(((FabricTabPlayer)player1).getPlayer()) ==
FabricTAB.getVersion().getLevel(((FabricTabPlayer)player2).getPlayer());
return FabricMultiVersion.getLevel(((FabricTabPlayer)player1).getPlayer()) ==
FabricMultiVersion.getLevel(((FabricTabPlayer)player2).getPlayer());
}
@Override
@@ -78,7 +85,7 @@ public class FabricNameTagX extends BackendNameTagX {
@Override
public boolean isSneaking(@NotNull TabPlayer player) {
return FabricTAB.getVersion().isSneaking(((FabricTabPlayer)player).getPlayer());
return FabricMultiVersion.isSneaking(((FabricTabPlayer)player).getPlayer());
}
@Override
@@ -120,7 +127,7 @@ public class FabricNameTagX extends BackendNameTagX {
@Override
@NotNull
public EntityData createDataWatcher(@NotNull TabPlayer viewer, byte flags, @NotNull String displayName, boolean nameVisible) {
return FabricTAB.getVersion().createDataWatcher(viewer, flags, displayName, nameVisible);
return FabricMultiVersion.createDataWatcher(viewer, flags, displayName, nameVisible);
}
@Override
@@ -132,4 +139,9 @@ public class FabricNameTagX extends BackendNameTagX {
public boolean isDead(@NotNull TabPlayer player) {
return !((FabricTabPlayer)player).getPlayer().isAlive();
}
@Override
public boolean isFlying(@NotNull TabPlayer player) {
return ((FabricTabPlayer)player).getPlayer().getAbilities().flying;
}
}
Loaded 100 of 364 files, more files were not shown because too many files have changed in this diff. Show more