Compare commits

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

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

There might be another way to workaround this since i don't know changing the priority would cause any issue with TAB itself.
2023-07-04 16:19:07 -04:00
NEZNAMY fcd95655ce Send warn when trying to use parse command without any placeholders 2023-07-04 11:09:14 +02:00
NEZNAMY 7f5b57f8d2 Merge pull request #956 from Tanguygab/master
Update skins in Layout's FixedSlots
2023-07-03 17:07:01 +02:00
NEZNAMY f596877716 Fix forwarding unlimited nametag disable status to backend 2023-07-02 17:30:58 +02:00
NEZNAMY 1802c8fd90 Fix debug command not showing properties from unlimited nametag as disabled when they are 2023-07-02 17:30:35 +02:00
Tanguygab e05856facc only update name if skin didn't update 2023-07-02 13:51:37 +02:00
Tanguygab 45cfc3abb0 simplified FixedSlot skin updating 2023-07-02 13:40:08 +02:00
Tanguygab 2d39579861 added support for placeholders in layout's fixed-slots' skins 2023-07-02 13:27:37 +02:00
NEZNAMY 431f4bb4d5 Change Velocity policy 2023-07-02 00:39:10 +02:00
NEZNAMY 7bc9d38176 Slightly improve debug messages 2023-07-01 20:15:50 +02:00
NEZNAMY 7536447a8f Update README.md 2023-06-29 20:47:33 +02:00
NEZNAMY 059b796061 Update CONTRIBUTING.md 2023-06-29 20:35:44 +02:00
NEZNAMY a89942484f Layout - Fix duplicate internal names causing issues 2023-06-28 21:03:13 +02:00
NEZNAMY c28be3fd65 Add (another) warn if plugin's CPU usage is high 2023-06-26 21:04:26 +02:00
NEZNAMY 5f1116795c Add Folia support (#919) 2023-06-25 12:11:41 +02:00
NEZNAMY 4f54d2f6b2 4.0.3-SNAPSHOT 2023-06-25 10:59:05 +02:00
NEZNAMY 6b7bedbe2d Fix NPE when placeholder returns null 2023-06-24 14:30:44 +02:00
NEZNAMY 20ff225c31 Add missing set team color 2023-06-23 22:03:32 +02:00
NEZNAMY af2700798e 4.0.2 2023-06-22 13:07:44 +02:00
NEZNAMY f9c280bbdc Add back vault, so people stop asking 2023-06-22 12:50:36 +02:00
NEZNAMY 6ee31c2c3f Fix unlimited nametag property changes on world switch if per-world settings are used 2023-06-22 11:10:21 +02:00
NEZNAMY cbd944f6f2 Remove redundant field initialization 2023-06-19 21:19:48 +02:00
NEZNAMY cc20748f31 Move ping field initialization 2023-06-19 21:16:20 +02:00
NEZNAMY c9545165aa Rename a few classes 2023-06-19 20:38:19 +02:00
NEZNAMY 05c10bf2e7 Move all scoreboard packet fields & methods to PacketScoreboard class 2023-06-19 19:18:53 +02:00
NEZNAMY 67aeae2094 Create scoreboard implementation using paper api (not used yet) 2023-06-19 17:53:20 +02:00
NEZNAMY 1cc85a0647 Add API function for getting player's team name (#939) 2023-06-19 17:29:04 +02:00
NEZNAMY 988c0f8b4c Construct player object sync 2023-06-19 16:34:03 +02:00
NEZNAMY 62306fa5d6 More cleanup using @SneakyThrows annotation 2023-06-19 12:44:06 +02:00
NEZNAMY 641c5a49c3 Remove unused import 2023-06-19 11:12:16 +02:00
NEZNAMY 5637402435 Fix startup on some bukkit versions & improve compatibility error message 2023-06-18 20:54:25 +02:00
NEZNAMY 728d6c9a25 Make compatibility checks slightly more strict and remove package check. 2023-06-17 23:55:20 +02:00
NEZNAMY 22d3ba2877 Remove layout view on quit to avoid permission checks for offline players 2023-06-17 22:26:38 +02:00
NEZNAMY d1e032e010 Rename methods to reflect on recent change 2023-06-17 21:09:58 +02:00
NEZNAMY 80c4316826 Use Player#getPing() instead of NMS for 1.17+ where it was added 2023-06-17 21:01:02 +02:00
NEZNAMY 3addeba256 Bump bridge protocol 2023-06-17 17:43:21 +02:00
NEZNAMY c52db733b7 Fix potential issue with spectator fix not working if joining with spectator gamemode 2023-06-17 17:42:46 +02:00
NEZNAMY 07a492e353 Forward gamemode from bridge for spectator fix to work on online mode bungee (#934) 2023-06-17 17:42:03 +02:00
NEZNAMY 3c1ec478c2 Small code cleanup using lombok 2023-06-17 11:25:42 +02:00
NEZNAMY f3f614c3de Fix spacing 2023-06-16 17:27:50 +02:00
NEZNAMY a6627ac6c6 Merge pull request #933 from Golfing7/master
Fix loading on velocity
2023-06-16 17:26:14 +02:00
Golfing8 8d1040518f Fix loading on velocity 2023-06-16 11:11:20 -04:00
NEZNAMY ca5d17e6ec Merge pull request #930 from NEZNAMY/v4
Merge v4
2023-06-16 09:08:55 +02:00
NEZNAMY b9fab42510 Update supported version range in error message 2023-06-15 17:31:18 +02:00
NEZNAMY 22f1ef43c4 Add missing return to fix error (#927) 2023-06-15 09:44:14 +02:00
NEZNAMY ffbd4bf26b Move bungee component conversion to shared 2023-06-14 17:59:13 +02:00
NEZNAMY 4d6f93e975 Build adventure components with ProtocolVersion for manually defined legacy colors to actually work 2023-06-14 12:23:51 +02:00
NEZNAMY cb45cff77f 4.0.2-SNAPSHOT 2023-06-14 12:22:29 +02:00
NEZNAMY 4718040345 4.0.1 2023-06-13 00:11:20 +02:00
NEZNAMY afffefcce1 1.20.1 2023-06-13 00:11:10 +02:00
NEZNAMY 4580f47563 Fix bungee kick on 1.7 (#921) 2023-06-12 22:41:07 +02:00
NEZNAMY e51f4bb3a1 Remove unused methods 2023-06-12 13:34:30 +02:00
NEZNAMY e8c8d1dee7 Fix TabComplete on Sponge 7 2023-06-11 19:21:54 +02:00
NEZNAMY 3559afaef7 Fix TabComplete on Fabric 2023-06-11 18:26:39 +02:00
NEZNAMY fc29420eb3 Fix TabComplete on Sponge 8+ 2023-06-11 17:09:42 +02:00
NEZNAMY 023cd3fb75 4.0.0 2023-06-08 14:16:19 +02:00
NEZNAMY 2781d9f232 Fix errors when platform doesn't support unlimited nametags, but it's enabled in config 2023-06-08 14:15:34 +02:00
NEZNAMY d31e5de449 Fix NPE 2023-06-08 13:59:36 +02:00
NEZNAMY 245fcdf917 Update to 1.20 2023-06-08 13:50:47 +02:00
NEZNAMY e3ef3ffa68 Refactor scoreboard command and change /tab announce scoreboard to /tab scoreboard announce 2023-06-07 14:04:36 +02:00
NEZNAMY 95c3608c3f Fix typo 2023-06-07 14:03:34 +02:00
NEZNAMY 7c2236dc96 Not ready yet 2023-06-07 14:03:23 +02:00
NEZNAMY b83ef0e352 API - Add functions for force-updating placeholder values 2023-06-06 11:49:49 +02:00
NEZNAMY 2865871d04 Add check for ViaVersion bug returning -1 version 2023-06-06 11:30:42 +02:00
NEZNAMY 7f7b76a11e Fabric - Remove multi version class 2023-05-28 20:30:20 +02:00
NEZNAMY 52569e4c65 Create method in event listener class for replacing player instead of manually creating it in every class 2023-05-28 20:18:06 +02:00
NEZNAMY 0a0ae6c83b Fabric - Replace player object on respawn because it changes 2023-05-28 20:13:40 +02:00
NEZNAMY 6c8a41388e Unlimited NameTags - Fix disable-condition not working properly when set for the main feature 2023-05-28 16:43:52 +02:00
NEZNAMY fe94e33d85 Use PremiumVanish API code directly instead of reflection 2023-05-28 15:40:33 +02:00
NEZNAMY ca1be457cf Remove error message that was never actually sent 2023-05-25 21:20:22 +02:00
NEZNAMY 550b6350ea Change server version info into a method instead of field and provide more info 2023-05-25 13:38:42 +02:00
NEZNAMY 76470c880d Create player object on join async (broke it in a recode) 2023-05-25 12:23:48 +02:00
NEZNAMY a315bd88b3 Fix MySQL 2023-05-24 21:25:05 +02:00
NEZNAMY bb62930525 Simplify permission plugin data structure 2023-05-24 19:05:43 +02:00
NEZNAMY b7490f0002 Rename a few methods and fields 2023-05-24 13:21:56 +02:00
NEZNAMY 42f3851339 API - Small improvements/fixes and more documentation 2023-05-24 12:37:20 +02:00
NEZNAMY 34f39ac8d5 Fix compilation 2023-05-23 13:02:34 +02:00
NEZNAMY 8c16330119 API - Remove getApiVersion method, since everyone should be up-to-date and users are unlikely to check for it anyway 2023-05-23 12:56:33 +02:00
NEZNAMY 8323c21c3e Move ProtocolVersion enum out of API 2023-05-23 12:49:56 +02:00
NEZNAMY 2a3ca932a4 API - Add functions for unregistering placeholders 2023-05-23 12:41:48 +02:00
NEZNAMY e339928caa Conditions - Fix startsWith and endsWith types (how could they even have worked before?) 2023-05-23 12:40:22 +02:00
NEZNAMY 3eeb025f23 Add API for Layout and move team names to a new sorting class 2023-05-22 21:03:28 +02:00
NEZNAMY f19c1f1baa Another way of breaking the plugin was found by a customer 2023-05-20 18:56:17 +02:00
NEZNAMY 003e6a6adb Forward placeholder output replacements to bridge 2023-05-18 12:01:19 +02:00
NEZNAMY 95d435d61c Create field for viewer instead of passing as parameter 2023-05-17 14:25:19 +02:00
NEZNAMY bbc4e1d767 Unlimited nametags - Compensate for new 1.19.4 bug calling sneak event on vehicle leave with value "true", but never calling with "false" (#894) 2023-05-17 14:01:41 +02:00
NEZNAMY ae46adf8b9 Layout - Make layouts per-player, allowing hiding vanished players and opening new possibilities for the future 2023-05-17 13:31:02 +02:00
NEZNAMY 461ef988d3 Make Platform class an interface and make each implementation override sendConsoleMessage 2023-05-16 20:33:57 +02:00
NEZNAMY 307d21cc85 Remove nickname field, since plugin doesn't track name changes anymore 2023-05-16 19:01:51 +02:00
NEZNAMY e2beac6570 A couple of fixes for disable-conditions 2023-05-16 18:48:02 +02:00
NEZNAMY 75e455f078 Replace NonNull annotation with NotNull for internal methods 2023-05-16 12:52:04 +02:00
NEZNAMY 19c832d370 Rename a few classes 2023-05-16 11:34:02 +02:00
NEZNAMY 9cd4afe1d4 Rework disabled worlds/servers into disable conditions 2023-05-16 11:32:37 +02:00
NEZNAMY 3b00f07f78 Unlimited NameTags - Update visibility on vehicle enter/leave instantly (also maybe fix #894?) 2023-05-16 11:25:15 +02:00
NEZNAMY 0b5008c087 Remove unused fields 2023-05-14 21:59:44 +02:00
NEZNAMY 76f210db03 Print console warn if some placeholder's usage exceeds 50% 2023-05-14 17:43:29 +02:00
NEZNAMY 85c8d9f1b9 Bukkit - Add ViaRewind to softdepend to avoid console warn 2023-05-14 17:12:02 +02:00
NEZNAMY a795fc7f77 BossBar - Send console warn if progress could not be evaluated to a number between 0 and 100 2023-05-14 17:10:39 +02:00
NEZNAMY 7742ab8220 Add native support for multi-character symbols for aligned tabsuffix 2023-05-13 13:18:12 +02:00
NEZNAMY 3e235a8f18 Construct packet class with null title instead of empty string for unregister 2023-05-11 23:56:50 +02:00
NEZNAMY 30f9888f6a Split layout loading into multiple methods for better readability 2023-05-11 19:14:58 +02:00
NEZNAMY d1f8886e2b Layout - Add misconfiguration checks 2023-05-11 16:56:01 +02:00
NEZNAMY 3c037f9aa6 Create and use enums for team visibility and collision 2023-05-11 15:16:45 +02:00
NEZNAMY c70346c57c Fix NPE in injection when player is not loaded yet (#891) 2023-05-09 22:16:36 +02:00
NEZNAMY bd40dd85af Global PlayerList - Properly display players on other servers as spectators if enabled (#890) 2023-05-09 16:42:21 +02:00
NEZNAMY eacd929b0f Remove redundant fully qualified name of imported classes 2023-05-04 01:04:31 +02:00
NEZNAMY 0ff5f17b7d Create and use more constants instead of literal strings 2023-05-04 00:57:46 +02:00
NEZNAMY 57fa01151e Move command implementation for platforms into separate classes instead of being a nested class inside main class 2023-05-04 00:29:51 +02:00
NEZNAMY e7bc1fc74b Move sendPlayerListHeaderFooter method from TabPlayer to TabList 2023-04-29 21:32:40 +02:00
NEZNAMY 897f35ab53 Remove BulkUpdateTabList class and make all implementations update entries one by one 2023-04-29 16:32:51 +02:00
NEZNAMY 7f8b738a69 Add more NotNull / NonNull / Nullable annotations to methods 2023-04-29 15:10:30 +02:00
NEZNAMY 079302fb05 Small code cleanup 2023-04-28 23:31:31 +02:00
NEZNAMY 630fcc9e50 Remove underscore from class names 2023-04-27 16:08:20 +02:00
NEZNAMY c1601ca487 Remove nick compatibility hook, as it should no longer be needed 2023-04-27 14:03:47 +02:00
NEZNAMY d035b9cb80 Fix NPE 2023-04-27 13:20:49 +02:00
NEZNAMY 8db5e4d8ba Add NotNull / NonNull / Nullable annotations to fields and methods 2023-04-26 15:33:06 +02:00
NEZNAMY 4d51bb39d7 Print warn if both global playerlist and layout are enabled 2023-04-25 23:19:16 +02:00
NEZNAMY 7eb0231e08 Move floodgate hook into a separate class 2023-04-25 22:57:30 +02:00
NEZNAMY 6412461576 Simplify 2023-04-25 19:46:07 +02:00
NEZNAMY 5732eb366b Remove unused fields and methods 2023-04-25 19:36:03 +02:00
NEZNAMY ed3df8bb68 Recode redisbungee support and fix many bugs 2023-04-25 19:07:13 +02:00
NEZNAMY 787f531203 Remove unused fields 2023-04-25 19:00:57 +02:00
NEZNAMY cf5d888901 Remove unused parameter 2023-04-23 22:08:59 +02:00
NEZNAMY 1969e5f755 Global playerlist - Remove redundant entry removal on server switch (proxy removes it) 2023-04-23 15:57:43 +02:00
NEZNAMY 8d12ee0958 Remove unused import 2023-04-23 15:57:10 +02:00
NEZNAMY c3bbc89c5b Add final modifier where possible 2023-04-23 15:56:57 +02:00
NEZNAMY 625c7cf3c2 Fix issue with legacy color codes 2023-04-23 11:58:35 +02:00
NEZNAMY 14f0321992 Reduce static usage 2023-04-22 22:43:25 +02:00
NEZNAMY e5294c596a Change way of detection for presence of other plugins 2023-04-22 21:04:57 +02:00
NEZNAMY c83290a99a Fix error in RedisBungee 2023-04-22 20:56:48 +02:00
NEZNAMY 8c66310af0 Add RedisBungee support to Velocity as well 2023-04-22 19:17:05 +02:00
NEZNAMY 5ae91bf89e Update config comment (global playerlist will no longer duplicate players if this issue occurs) 2023-04-22 13:44:13 +02:00
NEZNAMY d5dc1b7bdb Simplify 2023-04-22 13:39:12 +02:00
NEZNAMY 864eee3349 Spectator Fix - Resend on server switch 2023-04-22 13:32:43 +02:00
NEZNAMY 88932e3a5e Move bungee component conversion out of shared to avoid class not found error 2023-04-20 21:59:04 +02:00
NEZNAMY 4ebc60ff00 Print original error instead of the current one 2023-04-20 21:43:05 +02:00
NEZNAMY b0cb63b05d Remove weird code in error printing 2023-04-20 21:19:08 +02:00
NEZNAMY 1db40ee2d5 Fix layout loading 2023-04-19 21:20:50 +02:00
NEZNAMY fd3f0ebd21 Move classes into packages 2023-04-19 11:46:22 +02:00
NEZNAMY ce73069345 Remove hover/click event from chat components 2023-04-18 13:54:06 +02:00
NEZNAMY 088f2748e3 Print amount of startup warns into the console 2023-04-17 20:39:35 +02:00
NEZNAMY d9a8c69f43 Refactor ViaVersion hook and drop ProtocolSupport hook as the plugin is abandoned 2023-04-17 19:23:54 +02:00
NEZNAMY 16ff503b6a Add back task queue while plugin is loading 2023-04-17 18:20:54 +02:00
NEZNAMY 68c78b9c73 Make field public 2023-04-17 18:04:17 +02:00
NEZNAMY 0c9d78a644 Fix method name 2023-04-15 18:59:54 +02:00
NEZNAMY 99d22642e2 Scoreboard - Fix placeholders not being updated in title on scoreboard switch 2023-04-15 15:22:02 +02:00
NEZNAMY dc762eacf8 Fill all fields in plugin.yml 2023-04-13 20:47:01 +02:00
NEZNAMY 5a44f30140 Reorder permission constants 2023-04-13 20:37:53 +02:00
NEZNAMY 82e86b0f8a Fix NPE on reload 2023-04-13 18:02:21 +02:00
NEZNAMY 9c4eb4359c Document empty methods 2023-04-12 21:53:32 +02:00
NEZNAMY 827c380463 Move more reflection related methods to ReflectionUtils 2023-04-12 21:50:01 +02:00
NEZNAMY 7c4fc324c8 Simplify 2023-04-12 21:18:29 +02:00
NEZNAMY 46215f3304 Fix indentation 2023-04-12 21:15:16 +02:00
NEZNAMY 1accc21365 Avoid possible NPE 2023-04-12 21:11:29 +02:00
NEZNAMY b74c2f62f7 Replace star import with explicit imports 2023-04-12 21:08:38 +02:00
NEZNAMY 9d14c0c923 Resend header/footer on server switch because velocity clears it 2023-04-12 16:17:12 +02:00
NEZNAMY 650fda5781 Reorder class loading to make it cleaner 2023-04-11 16:43:40 +02:00
NEZNAMY 286ccf5836 Reorder class loading to make it cleaner 2023-04-10 19:08:54 +02:00
NEZNAMY dfa310666c Move reflection related methods to ReflectionUtils 2023-04-10 19:02:35 +02:00
NEZNAMY 06595241ab Remove plugins from softdepend which are no longer being hooked into 2023-04-10 18:38:57 +02:00
NEZNAMY 0f80dd1cfd Simplify 2023-04-10 18:33:02 +02:00
NEZNAMY 7bc5be264c Define dependencies directly instead of using versions file 2023-04-10 16:23:44 +02:00
NEZNAMY e31784fb1e Include initial task delay to prevent errors while plugin is still loading 2023-04-10 13:15:14 +02:00
NEZNAMY ae156f424b Move layout to config.yml 2023-04-10 13:11:41 +02:00
NEZNAMY cfe0b3883e Remove deleted placeholder from default configuration 2023-04-10 12:49:54 +02:00
NEZNAMY fc6beaa0a5 Add removing of removed config option to converter 2023-04-10 12:45:11 +02:00
NEZNAMY 5f2140b147 Make placeholder output replacements no longer require splitting config section with each "." used in placeholder 2023-04-10 12:43:28 +02:00
NEZNAMY c497bdff39 Swap condition registration order for proper detection of type 2023-04-10 12:22:16 +02:00
NEZNAMY 7c84b6432e Simplify condition classes (or maybe just make them more complicated?) 2023-04-10 01:21:14 +02:00
NEZNAMY 3f787c3b0b Create separate Platform class for consistency 2023-04-10 00:00:32 +02:00
NEZNAMY 2046a39e99 Simplify 2023-04-09 23:42:32 +02:00
NEZNAMY 2ccf89e396 Further simplify Fabric code changes for older server versions 2023-04-08 22:45:43 +02:00
NEZNAMY efaebc3cb0 Simplify code changes for older versions on Fabric 2023-04-08 17:03:56 +02:00
NEZNAMY d7c952cc36 Fix placeholders from bridge not updating if player is not loaded yet when response is sent 2023-04-08 15:19:56 +02:00
NEZNAMY 1d3ea37129 Prevent bridge form re-registering placeholders that got deleted from config 2023-04-08 15:06:58 +02:00
NEZNAMY 1e9c47398d Add bridge connection status to debug command 2023-04-08 14:43:05 +02:00
NEZNAMY 5a1f48f1a5 Apparently it cannot be null 2023-04-08 14:25:03 +02:00
NEZNAMY ed65554d30 Remove duplication in player/playeruuid/group commands 2023-04-07 22:36:35 +02:00
NEZNAMY 0f19970401 Clean up ChatModifier serialization 2023-04-07 22:04:57 +02:00
NEZNAMY b419499710 Remove unnecessary fully qualified name 2023-04-07 21:58:38 +02:00
NEZNAMY 14a5f30b74 Split method for processing plugin messages into multiple 2023-04-07 21:48:38 +02:00
NEZNAMY 302cdf200f Small refactor of classes implemented by platforms 2023-04-07 21:32:15 +02:00
NEZNAMY bdde257cac Remove unused fields 2023-04-07 13:01:25 +02:00
NEZNAMY 73af013bca Change usage of methods which didn't exist before on fabric 2023-04-06 18:26:26 +02:00
Callum Seabrook e421427007 Disabled access widener validation task for Fabric 2023-04-06 16:17:51 +01:00
NEZNAMY 169a180d68 Remove unused imports 2023-04-06 16:28:28 +02:00
Callum Seabrook 16a19bf578 Switched to access wideners for player info entries on Fabric 2023-04-06 14:29:43 +01:00
Callum Seabrook 5b39204a8a Cleaned up Krypton code 2023-04-06 13:54:54 +01:00
Callum Seabrook b2bb125248 Switched Krypton tab list to API and removed server dependency 2023-04-06 13:35:08 +01:00
NEZNAMY 980354104b Remove fabric constant that is only used once 2023-04-06 00:18:15 +02:00
NEZNAMY b27571dcaa Catch UnsupportedOperationException 2023-04-05 22:27:44 +02:00
NEZNAMY b215bf9000 Fix dumb mistake 2023-04-05 21:10:04 +02:00
NEZNAMY ab75b13b3f Add option to choose target minecraft version on Fabric all the way down to 1.17 2023-04-05 20:42:51 +02:00
Callum Seabrook 31cd2aeb08 Made getFeature generic and removed casts 2023-04-05 19:18:39 +01:00
Callum Seabrook 8b6b33b314 Fixed compile 2023-04-05 18:40:10 +01:00
Callum Seabrook 1b66257ece Removed chat session from ProxyTabPlayer 2023-04-05 18:30:52 +01:00
Callum Seabrook bed9fcf212 Show new blank scoreboard to joining players on Krypton 2023-04-05 18:22:01 +01:00
Callum Seabrook 692f309750 Switched to API instead of packets for Krypton scoreboard 2023-04-05 16:30:19 +01:00
Callum Seabrook 2c9c9b502a Renamed Main to KryptonTAB for consistency 2023-04-05 15:48:50 +01:00
Callum Seabrook 3da8fd2a77 Fixed failed startup without Spark present on Krypton 2023-04-05 15:45:56 +01:00
Callum Seabrook ba6826f3d1 Added variable to change target Minecraft version for Fabric 2023-04-05 12:19:59 +01:00
Callum Seabrook 68b3629437 Added permissions API support for Fabric 2023-04-05 12:08:32 +01:00
Callum Seabrook 8bc5962d1c Use reflection for changing player info entries 2023-04-05 11:55:25 +01:00
NEZNAMY b8aa1982e4 Improve ViaVersion check on Fabric 2023-04-05 00:57:35 +02:00
NEZNAMY e0a427732e Update README.md 2023-04-04 23:25:38 +02:00
NEZNAMY 8d1f60f5fa Fix github actions 2023-04-04 23:16:15 +02:00
NEZNAMY 5cd7fdf385 Small improvements and fixes to fabric 2023-04-04 23:14:51 +02:00
Callum Seabrook e54d342ac5 Made relocations have a consistent pattern 2023-04-04 21:30:52 +01:00
Callum Seabrook 9019261c2a Fabric support 2023-04-04 21:27:55 +01:00
Callum Seabrook b1a5092053 Added Adventure-based boss bar handler for platforms using Adventure 2023-04-04 20:11:27 +01:00
NEZNAMY ef8bdbb40f Spectator fix - Add delay on join to avoid being too early 2023-04-04 21:08:19 +02:00
Callum Seabrook 0d67826b3d Removed message component cache on Bukkit 2023-04-04 19:34:24 +01:00
NEZNAMY 6c99e28d51 Improve code style in Scoreboard class 2023-04-04 17:21:38 +02:00
NEZNAMY 13931aafa5 Rethrow exceptions as IllegalStateException instead of RuntimeException 2023-04-04 17:01:17 +02:00
NEZNAMY 089e7ce8b5 Move class out of package 2023-04-04 15:52:01 +02:00
NEZNAMY 54287c8a29 Fix color getter 2023-04-04 15:41:18 +02:00
NEZNAMY 5ebc5dee21 Make gamemode change detection a periodic task instead of listening to packets 2023-04-04 15:29:34 +02:00
NEZNAMY bedbdbcca9 Remove redundant interface class 2023-04-04 14:44:37 +02:00
NEZNAMY a1c35adf4c Convert to adventure components directly instead of deserializing string 2023-04-04 14:34:56 +02:00
NEZNAMY 81b6a9be8b Remove redundant component deserialization attempts 2023-04-04 13:50:21 +02:00
Callum Seabrook 88eee58329 Moved chat system out of API 2023-04-02 22:49:15 +01:00
Callum Seabrook 2f02a5bda4 Removed unlimited nametags from Krypton 2023-04-02 21:11:05 +01:00
Callum Seabrook ba716df634 Removed unlimited nametags from Sponge 8 2023-04-02 20:12:30 +01:00
NEZNAMY 988292b82a XD 2023-04-02 16:30:06 +02:00
Callum Seabrook d52cfdd6e6 Start of Fabric 2023-04-02 13:55:38 +01:00
Callum Seabrook b13a49539f Updated to Gradle 8 2023-04-02 13:47:37 +01:00
NEZNAMY 32a39a361c Improve marking vanish placeholder as used for features listening to vanish status change 2023-04-02 13:46:20 +02:00
NEZNAMY 0e2e9b9ae6 Fix playerlist formatting not updating on unvanish 2023-04-02 13:41:14 +02:00
NEZNAMY 331faefde2 Fix compilation 2023-04-02 12:57:46 +02:00
NEZNAMY 1603ebe9fc Remove unused parameter 2023-04-02 12:43:15 +02:00
NEZNAMY bad4359469 Remove usage of separated initializers 2023-04-02 12:17:16 +02:00
NEZNAMY a860b463ed Merge ifs 2023-04-02 12:13:01 +02:00
NEZNAMY b3b0a96bb8 Remove commented out code that will never come back 2023-04-02 12:11:20 +02:00
NEZNAMY c211724d13 RedisBungee - Don't send yellow number / belowname if feature is disabled (#861) 2023-04-02 12:02:01 +02:00
NEZNAMY 0fb9a832ca Fix raw generic use 2023-04-02 00:34:42 +02:00
NEZNAMY 7ce1a97c93 Remove player from map on quit 2023-04-02 00:10:33 +02:00
NEZNAMY 1d461c1adf Make Scoreboard class generic to avoid massive casting 2023-04-02 00:02:31 +02:00
NEZNAMY 5ec09c37bc Remove things from API which shouldn't be there 2023-04-01 22:10:11 +02:00
NEZNAMY 95a20766c3 Simplify redisbungee belowname/yellow number forwarding 2023-04-01 17:41:25 +02:00
NEZNAMY 87feb1c2bf Improve misconfiguration checks and management 2023-04-01 17:33:34 +02:00
NEZNAMY 8c732ac7e9 Use defined constant instead of literal string 2023-04-01 13:01:26 +02:00
NEZNAMY 977c1f4d74 Remove unused field 2023-04-01 12:46:04 +02:00
NEZNAMY 65ac22aadb Perform scoreboard objective & team re-registration on server switch instead of listening to Login packet, which is assumed to be sent always 2023-04-01 12:37:02 +02:00
NEZNAMY 8e7a14bdb8 PlayerList - Format players after they un-vanish 2023-04-01 12:07:19 +02:00
NEZNAMY 4693a297b6 Remove unused field 2023-04-01 01:41:03 +02:00
NEZNAMY af69dcd9b5 Add this branch to github actions 2023-04-01 01:20:56 +02:00
NEZNAMY 71110fc722 Remove redundant conversion from older versions 2023-04-01 01:16:50 +02:00
NEZNAMY c2af5632a2 Remove hidden ghost player fix feature 2023-04-01 01:13:21 +02:00
NEZNAMY 399e813ca4 Remove placeholder refresh intervals categories to reduce confusion, split them in code based on identifier 2023-04-01 01:10:05 +02:00
NEZNAMY daac7c6171 Rename config conversion methods 2023-04-01 00:52:30 +02:00
NEZNAMY 940352ef2c Replace null checks with @NonNull 2023-04-01 00:47:21 +02:00
NEZNAMY c70b909c0f Remove disabled worlds/servers from bossbar and scoreboard, since conditions can achieve the same 2023-04-01 00:43:36 +02:00
NEZNAMY dcc2eb9217 Remove vault hook, vault placeholders and %afk% placeholder 2023-04-01 00:34:12 +02:00
NEZNAMY 5425b3b6ea Finish previous commit and remove pet fix feature 2023-04-01 00:17:48 +02:00
NEZNAMY a90ea3e3fa Remove ping spoof feature 2023-04-01 00:03:21 +02:00
NEZNAMY 6d52b7b615 4.0.0-SNAPSHOT 2023-04-01 00:01:43 +02:00
405 changed files with 11622 additions and 13062 deletions

No files matched your search

+7
View File
@@ -0,0 +1,7 @@
* text=auto
*.sh text eol=lf
gradlew text eol=lf
*.bat text eol=crlf
*.jar binary
+3 -2
View File
@@ -3,6 +3,7 @@ on:
push:
branches:
- master
- v4
pull_request:
types: [opened, synchronize, reopened]
jobs:
@@ -13,11 +14,11 @@ jobs:
- uses: actions/checkout@v3.3.0 #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 11
- name: Set up JDK 17
uses: actions/setup-java@v3.9.0 #https://github.com/actions/setup-java/releases
with:
distribution: 'temurin'
java-version: 11
java-version: 17
- name: Cache SonarCloud packages
uses: actions/cache@v3.2.3 #https://github.com/actions/cache/releases
with:
-1
View File
@@ -33,7 +33,6 @@ hs_err_pid*
.idea/
# other
.gitattributes
*.iml
run/
+1 -2
View File
@@ -6,5 +6,4 @@ is in quality of the report (typically providing correct steps to reproduce that
* Test **experimental builds**, which can be found in [GitHub Actions](https://github.com/NEZNAMY/TAB/actions) for problems of any kind including bugs.
This way you will help make the actual releases more stable / help get stable releases sooner.
* Help improve the wiki by opening a [Wiki change issue](https://github.com/NEZNAMY/TAB/issues/new?assignees=&labels=Wiki&template=wiki_change.yml),
where you can improve existing descriptions, add information you found missing, fix typos / grammar mistakes or add more examples of usage of functions.
* [Buy a copy](https://builtbybit.com/resources/14009/) of the plugin or [send a donation](https://paypal.me/neznamy1) to keep the plugin active.
where you can improve existing descriptions, add information you found missing, fix typos / grammar mistakes or add more examples.
+9 -72
View File
@@ -1,86 +1,23 @@
# About
TAB aims to be a superior all-in-one minecraft plugin for displaying information that outperforms all
similar plugins in terms of features, performance, compatibility and support.
similar plugins in terms of features, performance and compatibility.
More information can be found at [Why TAB?](https://github.com/NEZNAMY/TAB/wiki/Why-TAB%3F) wiki page.
# Distribution
As of TAB 3.0.0, released with MC 1.18 (November 30th, 2021), there is no free / premium
version anymore. Only one version of the plugin exists now, which contains all implemented features
and is available to everyone for free. However, customer support is only provided to
customers who have purchased the plugin (see [Customer support](#customer-support)).
# Compiling
Compilation requires JDK 8 and up.
Compilation requires JDK 17 and up.
To compile the plugin, run `./gradlew build` from the terminal.
Once the plugin compiles, grab the jar from `/jar/build/libs/` folder.
The universal jar contains all modules for all supported platforms.
# Customer support
Unlike with other projects where requirements and level of support provided are unclear,
we provide a clear answer to what kind of service you will or will not receive. Some don't
even clearly state if they are free or paid. While they are listed as paid on spigot, they have
a free download link in description, which can be confusing.
Free plugins typically do have a support discord, however you will rarely receive
official support from the developers, especially for large projects with tons of users, where
helping everyone in the world (theoretically) would consume too much time, leaving authors
no time left to actually work on the project.
In the end, they are free, made by people in their free time, and they are allowed to choose
what they do or don't want to do in their free time. They often leave support to other people,
possibly staff members, who are, however, in the end just more volunteers who may not even know
the answer.
With paid plugins you are more likely to receive support, but unless explicitly said, nothing is
guaranteed. There are lots of paid plugins where authors don't provide any support or redirect
it to helpers, where again they are unpaid volunteers who might not even know the answer.
In TAB, everything is clearly defined. The plugin is free to download for everyone.
Customer support, however, is only provided to customers who have formally purchased a copy
of the plugin on any of the websites the plugin is being sold on:
* [BuiltByBit](https://builtbybit.com/resources/14009/) (accepts any payment method)
* [Polymart](https://polymart.org/resource/484) (only PayPal)
After purchasing, you can request assistance via either private messages on the website where you
have purchased it, or using [TAB's Discord server](https://discord.gg/EaSvdk6) (which is highly
preferred, since the communication will be much faster thanks to notifications and more).
* We use a ticket system, which allows us to effectively track any amount of unresolved problems.
No more questions getting lost in the sea of messages from other people asking. Tickets are only closed
after user confirmed all of their questions were answered and then receive a transcript of the ticket
from the bot after ticket is closed into private messages (unless user is blocking them).
* We do not rely on the community helping, which often makes things even worse. We know people have
better things to do than helping random people with a random plugin, especially when most people only
have a very basic knowledge of the plugin, not being able to answer more complicated problems.
* We have notifications enabled and reply as soon as possible. We do not have the server muted unlike
most mods on most servers who only check it when they are really bored, give a wiki link and
disappear again.
* As the developer, I do not rely on helpers providing support instead of me. They are handy as
I am obviously not available 24/7, but I do not intentionally leave anything to them. I reply
whenever I am available without any intentional delays.
The wiki contains everything about the plugin in one way or another, so support should not
even be needed. It also contains a few examples, but obviously cannot contain a pre-made config
for every possible idea anyone can come up with.
Support is provided to people who decided paying for support is more time efficient than reading the wiki,
especially for serious servers, where price of the plugin is nothing compared to other server costs.
# Other links
Download the plugin for free:
* [GitHub releases](https://github.com/NEZNAMY/TAB/releases)
* [BuiltByBit](https://builtbybit.com/resources/20631/)
* [Modrinth](https://modrinth.com/plugin/tab-was-taken)
[Wiki](https://github.com/NEZNAMY/TAB/wiki)
[Donate](https://paypal.me/neznamy1) if you are feeling extra generous
# Documentation
You can find everything about the plugin on the [Wiki](https://github.com/NEZNAMY/TAB/wiki). This includes a detailed description
of all features, as well as information regarding compatibility or limitations of each feature.
# Issues
The [Issues](https://github.com/NEZNAMY/TAB/issues) section is **only** available for cases included in templates.
In other words, they **are not available for**:
* **Support** - support is provided on discord for buyers, where roles are given to verified users
allowing us to know who bought it and who didn't. It also serves as a much faster way of communication.
* **Feature requests** - closed indefinitely.
**If you're a customer, please use discord for reporting bugs, especially if you aren't good at making bug reports.
You will be provided with a much faster and higher quality service.**
The [Issues](https://github.com/NEZNAMY/TAB/issues) section is an issue tracker for all the kind of problems covered in issue templates.
Please refrain from attempting to use it to receive help with your server, such issues will be closed immediately.
When opening an issue, provide as much information as possible. The difference between a bug getting fixed in 1 week vs 1 hour
is in quality of the report. Bug reports where steps to reproduce are "idk" have no value and will not help to get the bug fixed at all.
# Contributing
See [CONTRIBUTING.md](https://github.com/NEZNAMY/TAB/blob/master/CONTRIBUTING.md)
+1 -24
View File
@@ -1,28 +1,5 @@
plugins {
id("net.kyori.blossom") version "1.2.0"
}
dependencies {
compileOnlyApi(libs.netty)
api(libs.jsonSimple) {
exclude("junit", "junit")
}
compileOnlyApi(libs.snakeyaml)
api(libs.yamlAssist)
compileOnlyApi(libs.adventureMiniMessage)
compileOnlyApi(libs.adventureApi)
compileOnlyApi(libs.adventureLegacy)
compileOnlyApi(libs.via)
}
blossom {
replaceToken("@name@", rootProject.name)
replaceToken("@id@", rootProject.ext.get("id")!!.toString())
replaceToken("@version@", project.version)
replaceToken("@description@", project.description)
replaceToken("@website@", rootProject.ext.get("website")!!.toString())
replaceToken("@author@", rootProject.ext.get("author")!!.toString())
replaceTokenIn("src/main/java/me/neznamy/tab/api/TabConstants.java")
compileOnlyApi("org.jetbrains:annotations:24.0.1")
}
tasks.javadoc {
@@ -1,57 +0,0 @@
package me.neznamy.tab.api;
/**
* Interface representing an armor stand attached to player
* for displaying text in game.
*/
public interface ArmorStand {
/**
* Returns true if offset is static, false if not
*
* @return true if static, false if dynamic
*/
boolean isStaticOffset();
/**
* Changes offset of the armor stand
*
* @param offset
* new offset
*/
void setOffset(double offset);
/**
* Returns offset of the armor stand
*
* @return offset from base
*/
double getOffset();
/**
* Returns property for armor stand's name
*
* @return property for armor stand's name
*/
Property getProperty();
/**
* Updates armor stand's name if needed
*/
void refresh();
/**
* Updates visibility if needed
*
* @param force
* if refresh should be forced
*/
void updateVisibility(boolean force);
/**
* Returns entity ID of this armor stand
*
* @return entity ID of this armor stand
*/
int getEntityId();
}
@@ -1,102 +0,0 @@
package me.neznamy.tab.api;
import me.neznamy.tab.api.feature.TabFeature;
import java.util.UUID;
/**
* Feature manager allows registration and work with features.
*/
public interface FeatureManager {
/**
* Registers a feature, which will start receiving events
*
* @param name
* name of feature
* @param feature
* the handler
*/
void registerFeature(String name, TabFeature feature);
/**
* Unregisters feature making it no longer receive events. This does not run unload method nor cancel
* tasks created by the feature
*
* @param name
* feature name defined in registerFeature
*/
void unregisterFeature(String name);
/**
* Returns whether a feature with said name is registered or not
*
* @param name
* name of feature defined in registerFeature method
* @return true if feature exists, false if not
*/
boolean isFeatureEnabled(String name);
/**
* Returns feature handler by its name
*
* @param name
* name of feature defined in registerFeature method
* @return the feature or null if feature does not exist
*/
TabFeature getFeature(String name);
/**
* Calls onQuit(TabPlayer) to all enabled features
*
* @param disconnectedPlayer
* player who disconnected
*/
void onQuit(TabPlayer disconnectedPlayer);
/**
* Calls onJoin(TabPlayer) to all enabled features
*
* @param connectedPlayer
* player who connected
*/
void onJoin(TabPlayer connectedPlayer);
/**
* Calls onServerChange(TabPlayer, String, String) to all enabled features
*
* @param playerUUID
* player who switched server
* @param to
* name of the new server
*/
void onServerChange(UUID playerUUID, String to);
/**
* Calls onWorldChange(TabPlayer, String, String) to all enabled features
*
* @param playerUUID
* player who switched world
* @param to
* name of the new world
*/
void onWorldChange(UUID playerUUID, String to);
/**
* Calls onCommand(TabPlayer, String) to all enabled features
*
* @param sender
* command sender
* @param command
* command line including /
* @return {@code true} if some feature marked the command for cancel, {@code false} if not
*/
boolean onCommand(TabPlayer sender, String command);
/**
* Returns array of all currently enabled features
*
* @return array of all currently enabled features
*/
TabFeature[] getValues();
}
@@ -1,68 +0,0 @@
package me.neznamy.tab.api;
/**
* An interface representing a property assigned to a player,
* which may contain placeholders and can be changed using the API.
*/
public interface Property {
/**
* Returns original raw value ignoring API calls
*
* @return original raw value
*/
String getOriginalRawValue();
/**
* Temporarily overrides current raw value with an API call
*
* @param temporaryValue
* temporary value to be assigned
*/
void setTemporaryValue(String temporaryValue);
/**
* Returns current temporary value
*
* @return temporary value or null if not set
*/
String getTemporaryValue();
/**
* Returns temporary value (via API) if present, raw value otherwise
*
* @return current raw value
*/
String getCurrentRawValue();
/**
* Returns last known value
*
* @return last known value
*/
String get();
/**
* Replaces all placeholders in current raw value, colorizes it, removes remove-strings and returns whether value changed or not
*
* @return if updating changed value or not
*/
boolean update();
/**
* Replaces all placeholders in current raw value, colorizes it, removes remove-strings and returns it.
* Equal to calling update() and then get().
*
* @return updated value
*/
String updateAndGet();
/**
* Returns value for defined viewer by applying relational placeholders to last known value
*
* @param viewer
* the viewer
* @return format for the viewer
*/
String getFormat(TabPlayer viewer);
}
@@ -1,57 +0,0 @@
package me.neznamy.tab.api;
import lombok.NonNull;
import java.util.Collection;
public interface Scoreboard {
/**
* Sets objective display slot of specified objective
*
* @param slot
* Display slot
* @param objective
* Objective name
*/
void setDisplaySlot(DisplaySlot slot, @NonNull String objective);
/**
* Sets scoreboard score
* @param objective
* Objective name
* @param player
* Affected player
* @param score
* New score value
*/
void setScore(@NonNull String objective, @NonNull String player, int score);
/**
* Removes scoreboard score
*
* @param objective
* Objective to remove from
* @param player
* Player to remove from sidebar
*/
void removeScore(@NonNull String objective, @NonNull String player);
void registerObjective(@NonNull String objectiveName, @NonNull String title, boolean hearts);
void unregisterObjective(@NonNull String objectiveName);
void updateObjective(@NonNull String objectiveName, @NonNull String title, boolean hearts);
void registerTeam(@NonNull String name, String prefix, String suffix, String visibility,
String collision, Collection<String> players, int options);
void unregisterTeam(@NonNull String name);
void updateTeam(@NonNull String name, String prefix, String suffix, String visibility, String collision, int options);
enum DisplaySlot {
PLAYER_LIST, SIDEBAR, BELOW_NAME
}
}
+35 -109
View File
@@ -2,12 +2,19 @@ package me.neznamy.tab.api;
import java.util.UUID;
import lombok.NonNull;
import lombok.Setter;
import me.neznamy.tab.api.bossbar.BossBarManager;
import me.neznamy.tab.api.config.ConfigurationFile;
import me.neznamy.tab.api.event.EventBus;
import me.neznamy.tab.api.placeholder.PlaceholderManager;
import me.neznamy.tab.api.scoreboard.ScoreboardManager;
import me.neznamy.tab.api.team.TeamManager;
import me.neznamy.tab.api.tablist.SortingManager;
import me.neznamy.tab.api.tablist.HeaderFooterManager;
import me.neznamy.tab.api.tablist.TabListFormatManager;
import me.neznamy.tab.api.nametag.NameTagManager;
import me.neznamy.tab.api.tablist.layout.LayoutManager;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* The primary API class to get instances of other API classes
@@ -15,14 +22,7 @@ import me.neznamy.tab.api.team.TeamManager;
public abstract class TabAPI {
/** Instance of the API */
private static TabAPI instance;
/**
* Constructs new instance and sets it as new API instance
*/
public TabAPI() {
instance = this;
}
@Setter private static TabAPI instance;
/**
* Returns API instance. If instance was not set by the plugin, throws
@@ -34,10 +34,10 @@ public abstract class TabAPI {
* @throws IllegalStateException
* If instance is {@code null}
*/
public static TabAPI getInstance() {
public static @NotNull TabAPI getInstance() {
if (instance == null) throw new IllegalStateException("The API instance is null. This can have 2 possible causes: \n" +
"#1 - API was called before TAB was loaded. This means your plugin was loaded before TAB was. To make sure your " +
"plugin loads after TAB, add it as a depend or softdepend of your plugin.\n" +
"plugin loads after TAB, add it as a depend or soft depend of your plugin.\n" +
"#2 - You shaded TAB's classes into your plugin, instead of only using them. This is not allowed. To verify this " +
"is your case, unzip your plugin and check for TAB's classes. If they are there, you will need to fix your compiler " +
"to not include them, such as scope provided for maven compilation.");
@@ -51,7 +51,7 @@ public abstract class TabAPI {
* Player UUID
* @return player object from given UUID
*/
public abstract TabPlayer getPlayer(UUID id);
public abstract @Nullable TabPlayer getPlayer(@NonNull UUID id);
/**
* Returns player object from given name
@@ -60,150 +60,76 @@ public abstract class TabAPI {
* Player name
* @return player object from given name
*/
public abstract TabPlayer getPlayer(String name);
public abstract @Nullable TabPlayer getPlayer(@NonNull String name);
/**
* Returns collection of all online players. Will return empty list if plugin is disabled (due to a broken configuration file for example).
*
* @return collection of online players
*/
public abstract TabPlayer[] getOnlinePlayers();
public abstract @NotNull TabPlayer[] getOnlinePlayers();
/**
* Return BossBar manager instance if the feature is enabled. If not, returns {@code null}.
*
* @return BossBar manager
*/
public abstract BossBarManager getBossBarManager();
public abstract @Nullable BossBarManager getBossBarManager();
/**
* Returns scoreboard manager instance if the feature is enabled. If not, returns {@code null}.
*
* @return scoreboard manager
*/
public abstract ScoreboardManager getScoreboardManager();
public abstract @Nullable ScoreboardManager getScoreboardManager();
/**
* Returns team manager instance if the feature is enabled. If not, returns {@code null}.
*
* @return team manager
*/
public abstract TeamManager getTeamManager();
public abstract @Nullable NameTagManager getNameTagManager();
/**
* Returns header/footer manager instance if the feature is enabled. If not, returns {@code null}.
*
* @return Header/footer manager
*/
public abstract HeaderFooterManager getHeaderFooterManager();
public abstract @Nullable HeaderFooterManager getHeaderFooterManager();
/**
* Returns PlaceholderManager instance
*
* @return PlaceholderManager instance
*/
public abstract PlaceholderManager getPlaceholderManager();
public abstract @NotNull PlaceholderManager getPlaceholderManager();
/**
* Returns feature manager instance
* Returns TabList name format manager instance if the feature is enabled. If not, returns {@code null}.
*
* @return feature manager instance
* @return TabList name format manager
*/
public abstract FeatureManager getFeatureManager();
public abstract @Nullable TabListFormatManager getTabListFormatManager();
/**
* Returns Tablist name format manager instance if the feature is enabled. If not, returns {@code null}.
* Returns Layout manager instance if the feature is enabled. If not, returns {@code null}.
*
* @return Tablist name format manager
* @return Layout manager
*/
public abstract TablistFormatManager getTablistFormatManager();
public abstract @Nullable LayoutManager getLayoutManager();
/**
* Returns Sorting manager instance if at least one feature capable of sorting players is enabled.
* If not, returns {@code null}.
*
* @return Sorting manager
*/
public abstract @Nullable SortingManager getSortingManager();
/**
* Gets the event bus for registering listeners for TAB events.
*
* @return the event bus
*/
public abstract EventBus getEventBus();
/**
* Returns server version. On proxy installation returns PROXY.
*
* @return server version
*/
public abstract ProtocolVersion getServerVersion();
/**
* Prints message into console
*
* @param message
* message to print
* @param translateColors
* {@code true} if colors should be translated, {@code false} if not
*/
public abstract void sendConsoleMessage(String message, boolean translateColors);
/**
* Returns TAB's cache file used to store player toggle data
*
* @return TAB's player cache file
*/
public abstract ConfigurationFile getPlayerCache();
/**
* Returns TAB's config.yml file
*
* @return config.yml file
*/
public abstract ConfigurationFile getConfig();
/**
* Returns TAB's group configuration
*
* @return TAB's group configuration
*/
public abstract PropertyConfiguration getGroups();
/**
* Returns TAB's user configuration
*
* @return TAB's user configuration
*/
public abstract PropertyConfiguration getUsers();
/**
* Sends a debug message into console if the option
* is enabled in config.
*
* @param message
* Message to send
*/
public abstract void debug(String message);
/**
* Logs an error into errors.log file
*
* @param message
* Error message
* @param t
* Thrown error
*/
public abstract void logError(String message, Throwable t);
/**
* Sets name of file with syntax error, which prevented
* the plugin from enabling. Internal use only.
*
* @param file
* Name of file with syntax error
*/
public abstract void setBrokenFile(String file);
/**
* Returns {@code true} if the plugin is currently disabled due to an error,
* either a yaml syntax error or an internal startup error. Returns {@code false}
* if the plugin is properly running.
*
* @return {@code true} if disabled, {@code false} if running correctly
*/
public abstract boolean isPluginDisabled();
public abstract @Nullable EventBus getEventBus();
}
@@ -1,130 +1,35 @@
package me.neznamy.tab.api;
import java.util.UUID;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import lombok.NonNull;
import me.neznamy.tab.api.chat.IChatBaseComponent;
import me.neznamy.tab.api.feature.Refreshable;
import me.neznamy.tab.api.tablist.Skin;
import me.neznamy.tab.api.tablist.TabList;
import java.util.UUID;
/**
* An interface representing a player
*/
public interface TabPlayer {
/**
* Refreshes all visuals on the player
*/
void forceRefresh();
/**
* Returns player's name
*
* @return Player's name
*/
String getName();
@NotNull String getName();
/**
* Returns player's UUID
*
* @return Player's UUID
*/
UUID getUniqueId();
/**
* Returns player's uuid used in TabList. This may only be different from real uuid if
* TAB is installed on a proxy which does not rewrite UUIDs.
*
* @return player's uuid in TabList
*/
UUID getTablistId();
/**
* Returns player's protocol version
*
* @return player's protocol version
*/
ProtocolVersion getVersion();
@NotNull UUID getUniqueId();
/**
* Returns platform-specific entity
*
* @return an instance of bukkit/bungee player depending on platform
*/
Object getPlayer();
/**
* Returns player's current world name (on BungeeCord this requires bridge installed)
*
* @return name of world where player is currently in, "N/A" on BungeeCord if bridge is not installed
*/
String getWorld();
/**
* Returns player's current server name on BungeeCord (on bukkit this returns "N/A")
*
* @return name of server where player is currently in, "N/A" on bukkit
*/
String getServer();
/**
* Performs platform-specific API call to check for permission and returns the result
*
* @param permission
* the permission to check for
* @return true if player has permission, false if not
*/
boolean hasPermission(String permission);
/**
* Sends the player a platform-specific packet
*
* @param packet
* an instance of packet depending on platform
*/
void sendPacket(Object packet);
/**
* Returns player's property by name
*
* @param name
* name of property
* @return the property or {@code null} if not found
*/
Property getProperty(String name);
/**
* Sends a message to the player
*
* @param message
* message to be sent
* @param translateColors
* whether colors should be translated or not
*/
void sendMessage(String message, boolean translateColors);
/**
* Sends specified component as a chat message
*
* @param message
* message to send
*/
void sendMessage(IChatBaseComponent message);
/**
* Returns player's ping calculated by server
*
* @return player's ping
*/
int getPing();
/**
* Returns player's skin data
*
* @return player's skin
*/
Skin getSkin();
@NotNull Object getPlayer();
/**
* Returns true once the player is successfully loaded (onJoin method ran through all methods)
@@ -133,92 +38,6 @@ public interface TabPlayer {
*/
boolean isLoaded();
/**
* Sets property with specified name to new value. If property did not exist before, it is
* created and {@code true} is returned. If it existed, it is overridden and {@code true} is returned.
* {@code false} is returned otherwise.
*
* @param feature
* feature using this property to get placeholders registered
* @param identifier
* property name
* @param rawValue
* new raw value
* @return {@code true} if value changed / did not exist, {@code false} if value did not change
*/
boolean setProperty(Refreshable feature, String identifier, String rawValue);
/**
* Loads property from config using standard property loading algorithm
*
* @param property
* property name to load
* @return {@code true} if value did not exist or changed, {@code false} otherwise
*/
boolean loadPropertyFromConfig(Refreshable feature, String property);
/**
* Loads property from config using standard property loading algorithm. If the property is
* not set in config, {@code ifNotSet} value is used.
*
* @param property
* property name to load
* @param ifNotSet
* value to use if property is not defined in config
* @return {@code true} if value did not exist or changed, {@code false} otherwise
*/
boolean loadPropertyFromConfig(Refreshable feature, String property, String ifNotSet);
/**
* Returns name of player's scoreboard team or {@code null} if NameTag feature is disabled
*
* @return name of player's team
*/
String getTeamName();
/**
* Returns {@code true} if player is disguised using LibsDisguises, {@code false} if not
*
* @return {@code true} if player is disguised, {@code false} if not
*/
boolean isDisguised();
/**
* Returns {@code true} if player has invisibility potion, {@code false} if not.
* For bukkit, API is used, for BungeeCord, bridge is used.
*
* @return {@code true} if player has invisibility potion, {@code false} if not
*/
boolean hasInvisibilityPotion();
/**
* Checks if player is vanished using any supported vanish plugin and returns the result.
*
* @return {@code true} if player is vanished, {@code false} if not
*/
boolean isVanished();
/**
* Calls platform-specific method and returns the result
*
* @return {@code true} if player is online, {@code false} if not
*/
boolean isOnline();
/**
* Returns GameMode of the player (0 for survival, 1 creative, 2 adventure, 3 spectator)
*
* @return GameMode of the player
*/
int getGamemode();
/**
* Returns {@code true} if this is a bedrock player, {@code false} if not
*
* @return {@code true} if player is a bedrock player, {@code false} if not
*/
boolean isBedrockPlayer();
/**
* Returns player's primary permission group. If group has been changed using
* {@link #setTemporaryGroup(String)}, returns that value. Otherwise, returns group
@@ -226,74 +45,25 @@ public interface TabPlayer {
*
* @return player's primary permission group
*/
String getGroup();
@NotNull String getGroup();
/**
* Temporarily overrides player's group and applies all changes coming from new group.
* This includes all properties and sorting, if used.
* Set to {@code null} to reset back.
*
* @param group
* New group to use
* @see #hasTemporaryGroup()
* @see #resetTemporaryGroup()
*/
void setTemporaryGroup(String group);
void setTemporaryGroup(@Nullable String group);
/**
* Returns temporary group applied to the player using {@link #setTemporaryGroup(String)}.
* If no group was set, returns {@code null}
* Returns {@code true} if a temporary group was applied to the player using {@link #setTemporaryGroup(String)}.
* If no group was set, returns {@code false}
*
* @return Temporary group assigned to player or {@code null} if not set
* @return {@code true} if group is set, {@code false} if not
* @see #setTemporaryGroup(String)
* @see #resetTemporaryGroup()
*/
boolean hasTemporaryGroup();
/**
* Resets temporary group assigned using {@link #setTemporaryGroup(String)}.
* If no temporary group is set, doesn't do anything.
*
* @see #setTemporaryGroup(String)
* @see #hasTemporaryGroup()
*/
void resetTemporaryGroup();
/**
* Returns player's game profile name as seen by other players. By default,
* this returns player's name. If using a nickname / disguise plugin that changes
* game profile name for other players and change was successfully detected, returns
* that value.
*
* @return player's game profile name as seen by other players
*/
String getNickname();
/**
* Sets header and footer to specified values
*
* @param header
* Header to use
* @param footer
* Footer to use
*/
void setPlayerListHeaderFooter(@NonNull IChatBaseComponent header, @NonNull IChatBaseComponent footer);
/**
* Returns scoreboard interface for calling scoreboard-related methods
*
* @return scoreboard interface for calling scoreboard-related methods
*/
Scoreboard getScoreboard();
/**
* Returns TabList interface for calling tablist-related methods
* @return TabList interface for calling tablist-related methods
*/
TabList getTabList();
/**
* Returns handler for calling bossbar-related methods
* @return handler for calling bossbar-related methods
*/
BossBarHandler getBossBarHandler();
}
@@ -5,12 +5,5 @@ package me.neznamy.tab.api.bossbar;
* Calling ordinal() will return color's network ID.
*/
public enum BarColor {
PINK,
BLUE,
RED,
GREEN,
YELLOW,
PURPLE,
WHITE
PINK, BLUE, RED, GREEN, YELLOW, PURPLE, WHITE
}
@@ -3,7 +3,9 @@ package me.neznamy.tab.api.bossbar;
import java.util.List;
import java.util.UUID;
import lombok.NonNull;
import me.neznamy.tab.api.TabPlayer;
import org.jetbrains.annotations.NotNull;
/**
* An interface representing a BossBar line.
@@ -26,7 +28,7 @@ public interface BossBar {
*
* @return name of BossBar
*/
String getName();
@NotNull String getName();
/**
* Returns randomly generated uuid of this BossBar used internally to match
@@ -34,7 +36,7 @@ public interface BossBar {
*
* @return uuid of this BossBar
*/
UUID getUniqueId();
@NotNull UUID getUniqueId();
/**
* Changes BossBar title to specified string, supporting placeholders.
@@ -48,7 +50,7 @@ public interface BossBar {
* @param title
* New title to display in this BossBar
*/
void setTitle(String title);
void setTitle(@NonNull String title);
/**
* Changes BossBar progress to specified string, supporting placeholders.
@@ -64,7 +66,7 @@ public interface BossBar {
* @param progress
* New progress to use in this BossBar
*/
void setProgress(String progress);
void setProgress(@NonNull String progress);
/**
* Changes BossBar progress to specified value ranging from 0 to 100.
@@ -90,7 +92,7 @@ public interface BossBar {
* @param color
* New color to use in this BossBar
*/
void setColor(String color);
void setColor(@NonNull String color);
/**
* Changes BossBar color to specified enum constant.
@@ -100,7 +102,7 @@ public interface BossBar {
* @param color
* New color to use in this BossBar
*/
void setColor(BarColor color);
void setColor(@NonNull BarColor color);
/**
* Changes BossBar style to specified string, supporting placeholders.
@@ -116,7 +118,7 @@ public interface BossBar {
* @param style
* New style to use in this BossBar
*/
void setStyle(String style);
void setStyle(@NonNull String style);
/**
* Changes BossBar style to specified enum constant.
@@ -126,7 +128,7 @@ public interface BossBar {
* @param style
* New style to use in this BossBar
*/
void setStyle(BarStyle style);
void setStyle(@NonNull BarStyle style);
/**
* Returns current title of the BossBar in raw format. If it contains placeholders,
@@ -134,7 +136,7 @@ public interface BossBar {
*
* @return title of the BossBar
*/
String getTitle();
@NotNull String getTitle();
/**
* Returns progress of the BossBar as a string, which is either entered string
@@ -142,7 +144,7 @@ public interface BossBar {
*
* @return entered progress as a string
*/
String getProgress();
@NotNull String getProgress();
/**
* Returns color of the BossBar as a string, which is either entered string
@@ -150,7 +152,7 @@ public interface BossBar {
*
* @return entered color as a string
*/
String getColor();
@NotNull String getColor();
/**
* Returns style of the BossBar as a string, which is either entered string
@@ -158,7 +160,7 @@ public interface BossBar {
*
* @return entered style as a string
*/
String getStyle();
@NotNull String getStyle();
/**
* Registers this BossBar to specified player.
@@ -168,7 +170,7 @@ public interface BossBar {
* @param player
* Player to register this BossBar to
*/
void addPlayer(TabPlayer player);
void addPlayer(@NonNull TabPlayer player);
/**
* Unregisters this BossBar from specified player.
@@ -178,7 +180,7 @@ public interface BossBar {
* @param player
* Player to unregister this BossBar from
*/
void removePlayer(TabPlayer player);
void removePlayer(@NonNull TabPlayer player);
/**
* Returns list of players who can see this BossBar.
@@ -189,19 +191,10 @@ public interface BossBar {
*
* @return List of players seeing this BossBar
*/
List<TabPlayer> getPlayers();
@NotNull List<TabPlayer> getPlayers();
/**
* Returns {@code true} if specified player can see this BossBar, {@code false} if not
*
* @param player
* Player to check for
* @return {@code true} if contains, {@code false} if not
*/
boolean containsPlayer(TabPlayer player);
/**
* Returns {@code true} if this bossbar in configured as announcement-only bar,
* Returns {@code true} if this boss bar is configured as announcement-only bar,
* {@code false} if not.
*
* @return {@code true} if bar is announcement only, {@code false} if not
@@ -2,10 +2,12 @@ package me.neznamy.tab.api.bossbar;
import java.util.List;
import java.util.Map;
import java.util.UUID;
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;
/**
* An interface allowing work with BossBars such as creating, sending
@@ -31,7 +33,7 @@ public interface BossBarManager {
* Style to use
* @return The newly created BossBar
*/
BossBar createBossBar(String title, float progress, BarColor color, BarStyle style);
@NotNull BossBar createBossBar(@NonNull String title, float progress, @NonNull BarColor color, @NonNull BarStyle style);
/**
* Creates BossBar with specified parameters and registers it to BossBar manager,
@@ -50,30 +52,19 @@ public interface BossBarManager {
* a placeholder that returns one of them
* @return The newly created BossBar
*/
BossBar createBossBar(String title, String progress, String color, String style);
@NotNull BossBar createBossBar(@NonNull String title, @NonNull String progress, @NonNull String color, @NonNull String style);
/**
* Returns registered BossBar with given name. For BossBars from config it is the name
* they were created with, for API BossBars their randomly generated name, which can be
* retrieved using {@link me.neznamy.tab.api.bossbar.BossBar#getName()}.
* Returns null if no such BossBar exists.
* Returns {@code null} if no such BossBar exists.
*
* @param name
* Name of registered BossBar
* @return BossBar with specified name
*/
BossBar getBossBar(String name);
/**
* Returns registered BossBar with specified UUID. This UUID is randomly generated when
* BossBar is created and used in packets / to match which the one from Bukkit API.
* Returns null if no such BossBar exists.
*
* @param id
* UUID to return BossBar by
* @return BossBar with specified uuid
*/
BossBar getBossBar(UUID id);
@Nullable BossBar getBossBar(@NonNull String name);
/**
* Returns a map of all registered BossBars. This includes both BossBars from config
@@ -84,7 +75,7 @@ public interface BossBarManager {
*
* @return all registered BossBars
*/
Map<String, BossBar> getRegisteredBossBars();
@NotNull Map<String, BossBar> getRegisteredBossBars();
/**
* Toggles BossBar for this player and sends toggle message if {@code sendToggleMessage} is true.
@@ -96,7 +87,7 @@ public interface BossBarManager {
* @see #hasBossBarVisible(TabPlayer)
* @see #setBossBarVisible(TabPlayer, boolean, boolean)
*/
void toggleBossBar(TabPlayer player, boolean sendToggleMessage);
void toggleBossBar(@NonNull TabPlayer player, boolean sendToggleMessage);
/**
* Returns true if player can see BossBars, false if toggled via command or API.
@@ -107,7 +98,7 @@ public interface BossBarManager {
* @see #toggleBossBar(TabPlayer, boolean)
* @see #setBossBarVisible(TabPlayer, boolean, boolean)
*/
boolean hasBossBarVisible(TabPlayer player);
boolean hasBossBarVisible(@NonNull TabPlayer player);
/**
* Sets BossBar visibility of player to set value. If value did not change, nothing happens.
@@ -122,7 +113,7 @@ public interface BossBarManager {
* @see #toggleBossBar(TabPlayer, boolean)
* @see #hasBossBarVisible(TabPlayer)
*/
void setBossBarVisible(TabPlayer player, boolean visible, boolean sendToggleMessage);
void setBossBarVisible(@NonNull TabPlayer player, boolean visible, boolean sendToggleMessage);
/**
* Temporarily displays registered BossBar to player for specified amount of time in milliseconds.
@@ -138,7 +129,7 @@ public interface BossBarManager {
* @see #announceBossBar(String, int)
* @see #getAnnouncedBossBars()
*/
void sendBossBarTemporarily(TabPlayer player, String bossBar, int duration);
void sendBossBarTemporarily(@NonNull TabPlayer player, @NonNull String bossBar, int duration);
/**
* Temporarily displays registered BossBar to all players for specified amount of time in milliseconds.
@@ -152,7 +143,7 @@ public interface BossBarManager {
* @see #sendBossBarTemporarily(TabPlayer, String, int)
* @see #getAnnouncedBossBars()
*/
void announceBossBar(String bossBar, int duration);
void announceBossBar(@NonNull String bossBar, int duration);
/**
* Returns list of BossBars which are currently being announced.
@@ -161,5 +152,5 @@ public interface BossBarManager {
* @see #sendBossBarTemporarily(TabPlayer, String, int)
* @see #announceBossBar(String, int)
*/
List<BossBar> getAnnouncedBossBars();
@NotNull List<BossBar> getAnnouncedBossBars();
}
@@ -1,29 +0,0 @@
package me.neznamy.tab.api.chat;
import lombok.Data;
import lombok.NonNull;
/**
* Class for click event action in chat component
*/
@Data
public class ChatClickable {
/** Click action */
@NonNull private final EnumClickAction action;
/** Click value */
@NonNull private final String value;
/**
* Enum for all possible click actions
*/
public enum EnumClickAction {
OPEN_URL,
RUN_COMMAND,
CHANGE_PAGE, //since 1.8
SUGGEST_COMMAND,
COPY_TO_CLIPBOARD //since 1.15
}
}
@@ -1,35 +0,0 @@
package me.neznamy.tab.api.chat;
import lombok.NonNull;
import me.neznamy.tab.api.TabAPI;
import java.util.UUID;
public class ChatComponentEntity extends IChatBaseComponent {
private final String CONTENTS;
private final String VALUE_1_13;
private final String VALUE_1_12;
private final String VALUE_1_8;
public ChatComponentEntity(@NonNull String type, @NonNull UUID id, String name) {
this.CONTENTS = String.format("{\"type\":\"%s\",\"id\":\"%s\",\"name\":{\"text\":\"%s\"}}", type, id, name);
this.VALUE_1_13 = String.format("{type:\"%s\",id:\"%s\",name:\"{\\\"text\\\":\\\"%s\\\"}\"}", type, id, name);
this.VALUE_1_12 = String.format("{type:\"%s\",id:\"%s\",name:\"%s\"}", type, id, name);
this.VALUE_1_8 = String.format("{type:%s,id:%s,name:%s}", type, id, name);
}
@Override
public String toRawText() {
return toString();
}
@Override
public String toString() {
if (TabAPI.getInstance().getServerVersion().getMinorVersion() >= 16) return CONTENTS;
if (TabAPI.getInstance().getServerVersion().getMinorVersion() >= 13) return VALUE_1_13;
if (TabAPI.getInstance().getServerVersion().getMinorVersion() == 12) return VALUE_1_12;
if (TabAPI.getInstance().getServerVersion().getMinorVersion() >= 8) return VALUE_1_8;
throw new IllegalStateException("show_entity hover action is not supported on <1.8");
}
}
@@ -1,32 +0,0 @@
package me.neznamy.tab.api.chat;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
import lombok.NonNull;
/**
* Class for hover event action in chat component
*/
@Data
public class ChatHoverable {
/** Hover action */
@NonNull private final EnumHoverAction action;
/** Hover value */
@NonNull private final IChatBaseComponent value;
/**
* Enum for all possible hover actions
*/
@AllArgsConstructor
public enum EnumHoverAction {
SHOW_TEXT("contents"),
SHOW_ITEM("value"),
SHOW_ENTITY("contents");
@Getter private final String preferredKey;
}
}
@@ -1,219 +0,0 @@
package me.neznamy.tab.api.chat;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import me.neznamy.tab.api.ProtocolVersion;
import me.neznamy.tab.api.TabAPI;
import me.neznamy.tab.api.chat.ChatClickable.EnumClickAction;
import me.neznamy.tab.api.chat.ChatHoverable.EnumHoverAction;
import org.json.simple.JSONObject;
import java.util.UUID;
@Data @NoArgsConstructor
public class ChatModifier {
private TextColor color;
private Boolean bold;
private Boolean italic;
private Boolean underlined;
private Boolean strikethrough;
private Boolean obfuscated;
private ChatClickable clickEvent;
private ChatHoverable hoverEvent;
private String font;
private ProtocolVersion targetVersion;
public ChatModifier(@NonNull ChatModifier modifier) {
this.color = modifier.color == null ? null : new TextColor(modifier.color);
this.bold = modifier.bold;
this.italic = modifier.italic;
this.underlined = modifier.underlined;
this.strikethrough = modifier.strikethrough;
this.obfuscated = modifier.obfuscated;
this.clickEvent = modifier.clickEvent == null ? null : new ChatClickable(modifier.clickEvent.getAction(), modifier.clickEvent.getValue());
this.hoverEvent = modifier.hoverEvent == null ? null : new ChatHoverable(modifier.hoverEvent.getAction(), modifier.hoverEvent.getValue());
this.font = modifier.font;
this.targetVersion = modifier.targetVersion;
}
/**
* Returns true if bold is defined and set to true, false otherwise
*
* @return true if bold is defined and set to true, false otherwise
*/
public boolean isBold() {
return Boolean.TRUE.equals(bold);
}
/**
* Returns true if italic is defined and set to true, false otherwise
*
* @return true if italic is defined and set to true, false otherwise
*/
public boolean isItalic() {
return Boolean.TRUE.equals(italic);
}
/**
* Returns true if underlined is defined and set to true, false otherwise
*
* @return true if underlined is defined and set to true, false otherwise
*/
public boolean isUnderlined() {
return Boolean.TRUE.equals(underlined);
}
/**
* Returns true if strikethrough is defined and set to true, false otherwise
*
* @return true if strikethrough is defined and set to true, false otherwise
*/
public boolean isStrikethrough() {
return Boolean.TRUE.equals(strikethrough);
}
/**
* Returns true if obfuscation is defined and set to true, false otherwise
*
* @return true if obfuscation is defined and set to true, false otherwise
*/
public boolean isObfuscated() {
return Boolean.TRUE.equals(obfuscated);
}
/**
* Sets click action to OPEN_URL and url to given value
*
* @param url
* url to open
*/
public void onClickOpenUrl(@NonNull String url) {
clickEvent = new ChatClickable(EnumClickAction.OPEN_URL, url);
}
/**
* Sets click action to RUN_COMMAND and command to given value
*
* @param command
* command to perform, might be without / to send a chat message
*/
public void onClickRunCommand(@NonNull String command) {
clickEvent = new ChatClickable(EnumClickAction.RUN_COMMAND, command);
}
/**
* Sets click action to SUGGEST_COMMAND and command to given value
*
* @param command
* command to suggest
*/
public void onClickSuggestCommand(@NonNull String command) {
clickEvent = new ChatClickable(EnumClickAction.SUGGEST_COMMAND, command);
}
/**
* Sets click action to CHANGE_PAGE and page id to given value
*
* @param newPage
* id of new page
*/
public void onClickChangePage(int newPage) {
if (TabAPI.getInstance().getServerVersion().getMinorVersion() < 8) throw new UnsupportedOperationException("change_page click action is not supported on <1.8");
clickEvent = new ChatClickable(EnumClickAction.CHANGE_PAGE, String.valueOf(newPage));
}
/**
* Sets click action to COPY_TO_CLIPBOARD and text to provided value
*
* @param text
* text to copy to clipboard on click
*/
public void onClickCopyToClipBoard(@NonNull String text) {
clickEvent = new ChatClickable(EnumClickAction.COPY_TO_CLIPBOARD, text);
}
public void onClick(@NonNull EnumClickAction action, @NonNull String value) {
clickEvent = new ChatClickable(action, value);
}
/**
* Sets hover action to SHOW_TEXT and text to given value
*
* @param text
* text to show
*/
public void onHoverShowText(@NonNull IChatBaseComponent text) {
hoverEvent = new ChatHoverable(EnumHoverAction.SHOW_TEXT, text);
}
/**
* Sets hover action to SHOW_ITEM and item to given value
*
* @param serializedItem
* item to show
*/
public void onHoverShowItem(@NonNull String serializedItem) {
hoverEvent = new ChatHoverable(EnumHoverAction.SHOW_ITEM, new IChatBaseComponent(serializedItem));
}
/**
* Sets hover action to SHOW_ENTITY and entity data to given values
*
* @param type
* entity type
* @param id
* entity uuid
* @param name
* entity custom name
*/
public void onHoverShowEntity(@NonNull String type, @NonNull UUID id, String name) {
hoverEvent = new ChatHoverable(EnumHoverAction.SHOW_ENTITY, new ChatComponentEntity(type, id, name));
}
public void onHover(@NonNull EnumHoverAction action, @NonNull IChatBaseComponent value) {
hoverEvent = new ChatHoverable(action, value);
}
@SuppressWarnings("unchecked")
public JSONObject serialize() {
JSONObject json = new JSONObject();
if (color != null) json.put("color", targetVersion.getMinorVersion() >= 16 ? color.toString() : color.getLegacyColor().toString().toLowerCase());
if (bold != null) json.put("bold", bold);
if (italic != null) json.put("italic", italic);
if (underlined != null) json.put("underlined", underlined);
if (strikethrough != null) json.put("strikethrough", strikethrough);
if (obfuscated != null) json.put("obfuscated", obfuscated);
if (clickEvent != null) {
JSONObject click = new JSONObject();
click.put("action", clickEvent.getAction().toString().toLowerCase());
click.put("value", clickEvent.getValue());
json.put("clickEvent", click);
}
if (hoverEvent != null) {
JSONObject hover = new JSONObject();
hover.put("action", hoverEvent.getAction().toString().toLowerCase());
if (TabAPI.getInstance().getServerVersion().getMinorVersion() >= 16) {
hover.put(hoverEvent.getAction().getPreferredKey(), hoverEvent.getValue());
} else {
hover.put("value", TabAPI.getInstance().getServerVersion().getMinorVersion() >= 9 ?
hoverEvent.getValue() : hoverEvent.getValue().toRawText());
}
json.put("hoverEvent", hover);
}
if (font != null) json.put("font", font);
return json;
}
public String getMagicCodes() {
StringBuilder builder = new StringBuilder();
if (isBold()) builder.append(EnumChatFormat.BOLD.getFormat());
if (isItalic()) builder.append(EnumChatFormat.ITALIC.getFormat());
if (isUnderlined()) builder.append(EnumChatFormat.UNDERLINE.getFormat());
if (isStrikethrough()) builder.append(EnumChatFormat.STRIKETHROUGH.getFormat());
if (isObfuscated()) builder.append(EnumChatFormat.OBFUSCATED.getFormat());
return builder.toString();
}
}
@@ -1,161 +0,0 @@
package me.neznamy.tab.api.chat;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.api.ProtocolVersion;
import me.neznamy.tab.api.TabAPI;
import org.jetbrains.annotations.NotNull;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
import java.util.List;
/**
* A class representing a chat component that was serialized before,
* but deserialize was requested in PacketPlayOutPlayerInfo packet.
* This is used to read and modify the packet and then rewrite it.
* However, display name components must be deserialized to properly
* forward them in the new packet, but its value is not actually
* used anywhere.
* <p>
* To avoid this heavy and completely unnecessary operation, the original
* string is saved and returned during serialization. If any read/write
* operation is performed, the string is fully deserialized and this object
* will act like a normal chat component.
*/
@SuppressWarnings("unchecked")
@RequiredArgsConstructor
public class DeserializedChatComponent extends IChatBaseComponent {
/** The original serialized component string */
@NonNull private final String json;
/**
* Flag tracking whether this component was fully deserialized
* in case of a read operation to avoid repeating the deserialization
* process on every request.
*/
private boolean deserialized;
/**
* Flag tracking whether this component was modified, which means
* a new string has to be created instead of returning the input one.
*/
private boolean modified;
@Override
public String toString() {
if (modified) return super.toString();
return json;
}
@Override
public String toString(ProtocolVersion clientVersion) {
if (modified) return super.toString(clientVersion);
return json;
}
@Override
public List<IChatBaseComponent> getExtra() {
if (!deserialized) deserialize();
return super.getExtra();
}
@Override
public String getText() {
if (!deserialized) deserialize();
return super.getText();
}
@Override
public @NotNull ChatModifier getModifier() {
if (!deserialized) deserialize();
return super.getModifier();
}
@Override
public IChatBaseComponent setExtra(List<IChatBaseComponent> components) {
if (!deserialized) deserialize();
modified = true;
return super.setExtra(components);
}
@Override
public void addExtra(@NotNull IChatBaseComponent child) {
if (!deserialized) deserialize();
modified = true;
super.addExtra(child);
}
@Override
public void setModifier(@NotNull ChatModifier modifier) {
if (!deserialized) deserialize();
modified = true;
super.setModifier(modifier);
}
/**
* Performs a full deserialization process on this component.
*/
void deserialize() {
deserialized = true;
if (json.startsWith("\"") && json.endsWith("\"") && json.length() > 1) {
//simple component with only text used, minecraft serializer outputs the text in quotes instead of full json
setText(json.substring(1, json.length()-1));
return;
}
JSONObject jsonObject;
try {
jsonObject = (JSONObject) new JSONParser().parse(json);
} catch (ParseException e) {
TabAPI.getInstance().logError("Failed to deserialize json component " + json, e);
return;
}
/* if (jsonObject.containsKey("type")) {
return new ChatComponentEntity((String) jsonObject.get("type"), UUID.fromString((String) jsonObject.get("id")), IChatBaseComponent.deserialize(jsonObject.get("name").toString()).toFlatText());
}*/
setText((String) jsonObject.get("text"));
getModifier().setBold(getBoolean(jsonObject, "bold"));
getModifier().setItalic(getBoolean(jsonObject, "italic"));
getModifier().setUnderlined(getBoolean(jsonObject, "underlined"));
getModifier().setStrikethrough(getBoolean(jsonObject, "strikethrough"));
getModifier().setObfuscated(getBoolean(jsonObject, "obfuscated"));
getModifier().setColor(TextColor.fromString(((String) jsonObject.get("color"))));
if (jsonObject.containsKey("clickEvent")) {
JSONObject clickEvent = (JSONObject) jsonObject.get("clickEvent");
String action = (String) clickEvent.get("action");
String value = clickEvent.get("value").toString();
getModifier().onClick(ChatClickable.EnumClickAction.valueOf(action.toUpperCase()), value);
}
if (jsonObject.containsKey("hoverEvent")) {
JSONObject hoverEvent = (JSONObject) jsonObject.get("hoverEvent");
String action = (String) hoverEvent.get("action");
String value = (String) hoverEvent.get("value");
getModifier().onHover(ChatHoverable.EnumHoverAction.valueOf(action.toUpperCase()), deserialize(value));
}
if (jsonObject.containsKey("extra")) {
List<Object> list = (List<Object>) jsonObject.get("extra");
for (Object extra : list) {
String string = extra.toString();
//reverting .toString() removing "" for simple text
if (!string.startsWith("{")) string = "\"" + string + "\"";
addExtra(IChatBaseComponent.deserialize(string));
}
}
}
/**
* Returns boolean value of requested key from map
*
* @param jsonObject
* map to get value from
* @param key
* name of key
* @return value from json object or null if not present
*/
private static Boolean getBoolean(@NonNull JSONObject jsonObject, @NonNull String key) {
String value = String.valueOf(jsonObject.getOrDefault(key, null));
return "null".equals(value) ? null : Boolean.parseBoolean(value);
}
}
@@ -1,62 +0,0 @@
package me.neznamy.tab.api.chat;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NonNull;
import me.neznamy.tab.api.ProtocolVersion;
import org.jetbrains.annotations.NotNull;
import java.util.List;
/**
* A class extending IChatBaseComponent that holds the original value
* to avoid deserializing it, which is an unnecessary operation that
* slows down the performance, as well as makes code more complicated
* and open to problems.
*/
@AllArgsConstructor
public class WrappedChatComponent extends IChatBaseComponent {
/** Original NMS component */
@Getter private final Object originalComponent;
@Override
public String toString() {
throw new UnsupportedOperationException("Not supported for " + getClass().getSimpleName());
}
@Override
public String toString(ProtocolVersion clientVersion) {
throw new UnsupportedOperationException("Not supported for " + getClass().getSimpleName());
}
@Override
public List<IChatBaseComponent> getExtra() {
throw new UnsupportedOperationException("Not supported for " + getClass().getSimpleName());
}
@Override
public String getText() {
throw new UnsupportedOperationException("Not supported for " + getClass().getSimpleName());
}
@Override
public @NotNull ChatModifier getModifier() {
throw new UnsupportedOperationException("Not supported for " + getClass().getSimpleName());
}
@Override
public IChatBaseComponent setExtra(List<IChatBaseComponent> components) {
throw new UnsupportedOperationException("Not supported for " + getClass().getSimpleName());
}
@Override
public void addExtra(@NonNull IChatBaseComponent child) {
throw new UnsupportedOperationException("Not supported for " + getClass().getSimpleName());
}
@Override
public void setModifier(@NotNull ChatModifier modifier) {
throw new UnsupportedOperationException("Not supported for " + getClass().getSimpleName());
}
}
@@ -1,5 +1,7 @@
package me.neznamy.tab.api.event;
import lombok.NonNull;
/**
* The event bus. This is used to register event listeners/handlers for
* handling events that may be fired by TAB.
@@ -10,7 +12,7 @@ public interface EventBus {
* Scans the given listener for any method that is annotated with
* {@link Subscribe} and registers it as a listener to this event bus.
*
* All methods in this class that are annotated with {@link Subscribe}
* <p>All methods in this class that are annotated with {@link Subscribe}
* must meet the following criteria:
* <ul>
* <li>It must be <strong>public</strong>.</li>
@@ -24,7 +26,7 @@ public interface EventBus {
* @param listener the listener to register
* @throws RuntimeException if a method does not meet the criteria
*/
void register(Object listener);
void register(@NonNull Object listener);
/**
* Registers the given handler to this event bus.
@@ -33,7 +35,7 @@ public interface EventBus {
* @param handler the handler to register
* @param <E> the type of the event
*/
<E extends TabEvent> void register(Class<E> type, EventHandler<E> handler);
<E extends TabEvent> void register(@NonNull Class<E> type, @NonNull EventHandler<E> handler);
/**
* Scans the given listener for any method that is annotated with
@@ -41,7 +43,7 @@ public interface EventBus {
*
* @param listener the listener to unregister
*/
void unregister(Object listener);
void unregister(@NonNull Object listener);
/**
* Unregisters the given handler from this event bus.
@@ -49,5 +51,5 @@ public interface EventBus {
* @param handler the handler to unregister
* @param <E> the type of the event
*/
<E extends TabEvent> void unregister(EventHandler<E> handler);
<E extends TabEvent> void unregister(@NonNull EventHandler<E> handler);
}
@@ -3,7 +3,7 @@ package me.neznamy.tab.api.event;
/**
* An event that may be fired by TAB when something occurs.
*
* This is a marker interface. All events <strong>must</strong> implement this
* <p>This is a marker interface. All events <strong>must</strong> implement this
* interface.
*/
public interface TabEvent {
@@ -2,6 +2,7 @@ package me.neznamy.tab.api.event.player;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.api.event.TabEvent;
import org.jetbrains.annotations.NotNull;
/**
* Called when the player has been fully loaded by TAB.
@@ -13,13 +14,13 @@ public interface PlayerLoadEvent extends TabEvent {
*
* @return the player that was loaded
*/
TabPlayer getPlayer();
@NotNull TabPlayer getPlayer();
/**
* Returns true if player was loaded because they joined,
* false if the player was loaded because tab reload command was executed
* Returns {@code true} if player was loaded because they joined,
* {@code false} if the player was loaded because tab reload command was executed
*
* @return true if player joined, false if plugin was reloaded
* @return {@code true} if player joined, {@code false} if plugin was reloaded
*/
boolean isJoin();
}
@@ -1,11 +1,48 @@
package me.neznamy.tab.api.event.plugin;
import lombok.NonNull;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.api.event.TabEvent;
import me.neznamy.tab.api.placeholder.Placeholder;
import org.jetbrains.annotations.NotNull;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;
/**
* Called when an unknown placeholder is about to be registered.
* It defaults to a PlaceholderAPI implementation, but can be overridden.
*/
public interface PlaceholderRegisterEvent extends TabEvent {
String getIdentifier();
/**
* Returns placeholder identifier starting and ending with {@code %}
*
* @return placeholder identifier starting and ending with {@code %}
*/
@NotNull String getIdentifier();
void setPlaceholder(Placeholder placeholder);
/**
* Sets placeholder to specified server placeholder implementation.
*
* @param supplier
* Placeholder replacer
*/
void setServerPlaceholder(@NonNull Supplier<Object> supplier);
/**
* Sets placeholder to specified player placeholder implementation.
*
* @param function
* Placeholder replacer
*/
void setPlayerPlaceholder(@NonNull Function<TabPlayer, Object> function);
/**
* Sets placeholder to specified relational placeholder implementation.
*
* @param function
* Placeholder replacer
*/
void setRelationalPlaceholder(@NonNull BiFunction<TabPlayer, TabPlayer, Object> function);
}
@@ -1,23 +0,0 @@
package me.neznamy.tab.api.feature;
import me.neznamy.tab.api.TabPlayer;
import java.util.UUID;
/**
* Interface for features listening to tablist entry add
*/
public interface EntryAddListener {
/**
* Called when player is about to receive packet for adding a tablist entry
*
* @param packetReceiver
* Player who received the packet
* @param id
* UUID of added player
* @param name
* Username of added player
*/
void onEntryAdd(TabPlayer packetReceiver, UUID id, String name);
}
@@ -1,24 +0,0 @@
package me.neznamy.tab.api.feature;
import me.neznamy.tab.api.TabPlayer;
import java.util.UUID;
/**
* Interface for features listening to gamemode change packet
*/
public interface GameModeListener {
/**
* Called when player receives packet to change gamemode
*
* @param packetReceiver
* Player who received the packet
* @param id
* UUID of affected player
* @param gameMode
* gamemode in the packet
* @return New gamemode to write to the packet
*/
int onGameModeChange(TabPlayer packetReceiver, UUID id, int gameMode);
}
@@ -1,24 +0,0 @@
package me.neznamy.tab.api.feature;
import me.neznamy.tab.api.TabPlayer;
import java.util.UUID;
/**
* Called when a packet to change player's latency is sent.
*/
public interface LatencyListener {
/**
* Called when a player receives a packet for latency change.
*
* @param packetReceiver
* Player who received the packet
* @param id
* UUID of affected player
* @param latency
* Latency in the packet
* @return New latency
*/
int onLatencyChange(TabPlayer packetReceiver, UUID id, int latency);
}
@@ -1,18 +0,0 @@
package me.neznamy.tab.api.feature;
import me.neznamy.tab.api.TabPlayer;
/**
* Interface for features listening to Login packet
* usually sent by proxy on server switch
*/
public interface LoginPacketListener {
/**
* Called when player receives Login packet
*
* @param packetReceiver
* Player who received the packet
*/
void onLoginPacket(TabPlayer packetReceiver);
}
@@ -1,22 +0,0 @@
package me.neznamy.tab.api.feature;
import me.neznamy.tab.api.TabPlayer;
/**
* Interface for features listening to packets sent by players
*/
public interface PacketReceiveListener {
/**
* Called when a player is about to send a packet to the server
*
* @param sender
* Packet sender
* @param packet
* The packet
* @return Whether the packet should be cancelled or not
* @throws ReflectiveOperationException
* If thrown by reflective operation
*/
boolean onPacketReceive(TabPlayer sender, Object packet) throws ReflectiveOperationException;
}
@@ -1,136 +0,0 @@
package me.neznamy.tab.api.feature;
import java.util.*;
import lombok.NonNull;
import me.neznamy.tab.api.TabAPI;
import me.neznamy.tab.api.TabPlayer;
/**
* Abstract class representing a core feature of the plugin.
*/
public abstract class TabFeature {
/** Servers where the feature is disabled (or enabled if using whitelist mode) */
protected String[] disabledServers = new String[0];
/** Flag tracking whether disabled server list is a whitelist or blacklist */
private boolean serverWhitelistMode = false;
/** Worlds where the feature is disabled (or enabled if using whitelist mode) */
protected String[] disabledWorlds = new String[0];
/** Flag tracking whether disabled world list is a whitelist or blacklist */
private boolean worldWhitelistMode = false;
/** Players located in currently disabled worlds / servers */
private final Set<TabPlayer> disabledPlayers = Collections.newSetFromMap(new WeakHashMap<>());
protected TabFeature() {}
/**
* Constructs new instance with given parameters and loads method overrides.
* Also loads lists of disabled worlds and servers of this feature with config
* section path specified with {@code configSection} parameter.
*
* @param configSection
* Configuration section of the feature to load disabled
* servers / worlds from
*/
protected TabFeature(@NonNull String configSection) {
List<String> disabledServers = TabAPI.getInstance().getConfig().getStringList(configSection + ".disable-in-servers");
List<String> disabledWorlds = TabAPI.getInstance().getConfig().getStringList(configSection + ".disable-in-worlds");
if (disabledServers != null) {
this.disabledServers = disabledServers.toArray(new String[0]);
serverWhitelistMode = disabledServers.contains("WHITELIST");
}
if (disabledWorlds != null) {
this.disabledWorlds = disabledWorlds.toArray(new String[0]);
worldWhitelistMode = disabledWorlds.contains("WHITELIST");
}
}
/**
* Returns {@code true} if world or server is disabled, {@code false} if not
*
* @param server
* server to check
* @param world
* world to check
* @return {@code true} if feature should be disabled, {@code false} if not
*/
public boolean isDisabled(String server, String world) {
boolean contains = contains(disabledWorlds, world);
if (worldWhitelistMode) contains = !contains;
if (contains) return true;
contains = contains(disabledServers, server);
if (serverWhitelistMode) contains = !contains;
return contains;
}
/**
* Returns {@code true} if list contains the specified element or element
* ends with {@code "*"} and element meeting that requirement is present,
* {@code false} otherwise.
*
* @param list
* List to check
* @param element
* Element to find
* @return {@code true} if element was found, {@code false} if not
*/
protected boolean contains(String[] list, String element) {
if (element == null) return false;
for (String s : list) {
if (s.endsWith("*")) {
if (element.toLowerCase().startsWith(s.substring(0, s.length()-1).toLowerCase())) return true;
} else {
if (element.equalsIgnoreCase(s)) return true;
}
}
return false;
}
/**
* Returns {@code true} if player is currently located in a server
* or world, which is marked as disabled, {@code false} if not
*
* @param p
* Player to check
* @return {@code true} if player is in disabled server / world,
* {@code false} if not
*/
public boolean isDisabledPlayer(TabPlayer p) {
return disabledPlayers.contains(p);
}
/**
* Adds specified player into list of players in disabled
* servers / worlds.
*
* @param p
* Player to add
*/
public void addDisabledPlayer(TabPlayer p) {
if (disabledPlayers.contains(p)) return;
disabledPlayers.add(p);
}
/**
* Removes specified player from list of players in disabled
* servers / worlds
*
* @param p
* Player to check
*/
public void removeDisabledPlayer(TabPlayer p) {
disabledPlayers.remove(p);
}
/**
* Returns name of this feature displayed in /tab cpu
*
* @return name of this feature display in /tab cpu
*/
public abstract String getFeatureName();
}
@@ -1,61 +1,69 @@
package me.neznamy.tab.api.team;
package me.neznamy.tab.api.nametag;
import lombok.NonNull;
import me.neznamy.tab.api.TabAPI;
import me.neznamy.tab.api.TabPlayer;
import org.jetbrains.annotations.Nullable;
public interface TeamManager {
/**
* Interface for manipulating player name tags.
* <p>
* Instance can be obtained using {@link TabAPI#getNameTagManager()}.
* This requires the Team feature to be enabled in config, otherwise the method will
* return {@code null}.
*/
public interface NameTagManager {
/**
* Makes player's NameTag globally invisible
*
* @param player
* player to hide nametag of
* @see #showNametag(TabPlayer)
* @see #hasHiddenNametag(TabPlayer)
* player to hide name tag of
* @see #showNameTag(TabPlayer)
* @see #hasHiddenNameTag(TabPlayer)
*/
void hideNametag(@NonNull TabPlayer player);
void hideNameTag(@NonNull TabPlayer player);
/**
* Hides player's NameTag for specified player until it's shown again
*
* @param player
* player to hide nametag of
* player to hide name tag of
* @param viewer
* player to hide NameTag for
* player to hide name tag for
*/
void hideNametag(@NonNull TabPlayer player, @NonNull TabPlayer viewer);
void hideNameTag(@NonNull TabPlayer player, @NonNull TabPlayer viewer);
/**
* Makes player's NameTag visible again
*
* @param player
* player to show nametag of
* @see #hideNametag(TabPlayer)
* @see #hasHiddenNametag(TabPlayer)
* player to show name tag of
* @see #hideNameTag(TabPlayer)
* @see #hasHiddenNameTag(TabPlayer)
*/
void showNametag(@NonNull TabPlayer player);
void showNameTag(@NonNull TabPlayer player);
/**
* Shows player's NameTag for specified viewer if it was hidden before
*
* @param player
* player to show nametag of
* player to show name tag of
* @param viewer
* player to show NameTag back for
*/
void showNametag(@NonNull TabPlayer player, @NonNull TabPlayer viewer);
void showNameTag(@NonNull TabPlayer player, @NonNull TabPlayer viewer);
/**
* Return whether player has hidden NameTag or not
*
* @param player
* player to check nametag visibility status of
* player to check name tag visibility status of
* @return Whether player has hidden NameTag or not
* @see #hideNametag(TabPlayer)
* @see #showNametag(TabPlayer)
* @see #hideNameTag(TabPlayer)
* @see #showNameTag(TabPlayer)
*/
boolean hasHiddenNametag(@NonNull TabPlayer player);
boolean hasHiddenNameTag(@NonNull TabPlayer player);
/**
* Returns true if NameTag is hidden for specified viewer, false if not
@@ -66,7 +74,7 @@ public interface TeamManager {
* player to check visibility status for
* @return true if hidden, false if not
*/
boolean hasHiddenNametag(@NonNull TabPlayer player, @NonNull TabPlayer viewer);
boolean hasHiddenNameTag(@NonNull TabPlayer player, @NonNull TabPlayer viewer);
/**
* Unregisters player's team and no longer handles it, as well as disables anti-override for teams.
@@ -95,27 +103,6 @@ public interface TeamManager {
*/
boolean hasTeamHandlingPaused(@NonNull TabPlayer player);
/**
* Forces new team name for the player until this method is called again with null argument and
* performs all actions to change player's team name
*
* @param player
* player to set team name of
* @param teamName
* forced team name
*/
void forceTeamName(@NonNull TabPlayer player, @Nullable String teamName);
/**
* Returns forced team name of player or null if not forced
*
* @param player
* player to check forced team name of
* @return forced team name of player or null if not forced
*/
@Nullable String getForcedTeamName(@NonNull TabPlayer player);
/**
* Forces collision rule for the player. Setting it to null will remove forced value
*
@@ -136,21 +123,33 @@ public interface TeamManager {
@Nullable Boolean getCollisionRule(@NonNull TabPlayer player);
/**
* Sends update team's properties packet of player's team to everyone
* Changes player's prefix to provided value. Supports placeholders,
* as well as any supported RGB formats. Use {@code null} to reset
* value back to original.
*
* @param player
* player to update team data of
* player to change prefix of
* @param prefix
* new prefix value
* @see #getCustomPrefix(TabPlayer)
* @see #getOriginalPrefix(TabPlayer)
*/
void updateTeamData(@NonNull TabPlayer player);
void setPrefix(@NonNull TabPlayer player, @Nullable String prefix);
/**
* Changes player's suffix to provided value. Supports placeholders,
* as well as any supported RGB formats. Use {@code null} to reset
* value back to original.
*
* @param player
* player to change prefix of
* @param suffix
* new suffix value
* @see #getCustomSuffix(TabPlayer)
* @see #getOriginalSuffix(TabPlayer)
*/
void setSuffix(@NonNull TabPlayer player, @Nullable String suffix);
void resetPrefix(@NonNull TabPlayer player);
void resetSuffix(@NonNull TabPlayer player);
@Nullable String getCustomPrefix(@NonNull TabPlayer player);
@Nullable String getCustomSuffix(@NonNull TabPlayer player);
@@ -160,19 +159,19 @@ public interface TeamManager {
@NonNull String getOriginalSuffix(@NonNull TabPlayer player);
/**
* Toggles nametag visibility view on all players for specified player.
* On first call, nametags of all players will become invisible for specified
* Toggles name tag visibility view on all players for specified player.
* On first call, name tags of all players will become invisible for specified
* player. On second call, they will become visible again.
*
* @param player
* player to toggle nametag visibility view for
* player to toggle name tag visibility view for
* @param sendToggleMessage
* {@code true} if configured toggle message should be sent, {@code false} if not
*/
void toggleNameTagVisibilityView(@NonNull TabPlayer player, boolean sendToggleMessage);
/**
* Returns {@code true} if player has hidden nametags by either calling
* Returns {@code true} if player has hidden name tags by either calling
* {@link #toggleNameTagVisibilityView(TabPlayer, boolean)} or using a command,
* {@code false} if not.
*
@@ -0,0 +1,128 @@
package me.neznamy.tab.api.nametag;
import java.util.List;
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 manipulating player name tags.
* <p>
* Instance can be obtained using {@link TabAPI#getNameTagManager()} and then casting it.
* 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.
*/
public interface UnlimitedNameTagManager extends NameTagManager {
/**
* Disables armor stands of specified player and displays vanilla name tags instead.
*
* @param player
* Player to disable armor stands of
* @see #enableArmorStands(TabPlayer)
* @see #hasDisabledArmorStands(TabPlayer)
*/
void disableArmorStands(@NonNull TabPlayer player);
/**
* Enabled back armor stands of player, who was disabled previously using
* {@link #disableArmorStands(TabPlayer)}.
*
* @param player
* Player to enable back
* @see #disableArmorStands(TabPlayer)
* @see #hasDisabledArmorStands(TabPlayer)
*/
void enableArmorStands(@NonNull TabPlayer player);
/**
* Returns {@code true} if player has armor stands disabled using
* {@link #disableArmorStands(TabPlayer)}, {@code false} otherwise.
*
* @param player
* Player to check
* @return {@code true} if disabled, {@code false} if not.
* @see #disableArmorStands(TabPlayer)
* @see #enableArmorStands(TabPlayer)
*/
boolean hasDisabledArmorStands(@NonNull TabPlayer player);
/**
* Changes name to specified value. Set to {@code null} to
* reset back to original value.
*
* @param player
* Player to change name of
* @param customName
* New name, or {@code null} to reset to original value
*/
void setName(@NonNull TabPlayer player, @Nullable String customName);
/**
* Changes line to specified value. Set to {@code null} to reset back to original value.
*
* @param player
* Player to change line of
* @param line
* Name of defined line
* @param value
* Value to use, or {@code null} to reset back to original value
* @see #getDefinedLines()
*/
void setLine(@NonNull TabPlayer player, @NonNull String line, @Nullable String value);
/**
* Returns custom name value set using {@link #setName(TabPlayer, String)}.
*
* @param player
* Player to check
* @return Custom name set previously or {@code null} if not set
* @see #setName(TabPlayer, String)
*/
@Nullable String getCustomName(@NonNull TabPlayer player);
/**
* Returns custom line value set using {@link #setLine(TabPlayer, String, String)}.
*
* @param player
* Player to check
* @param line
* Defined line
* @return Custom name set previously or {@code null} if not set
* @see #setName(TabPlayer, String)
* @see #getDefinedLines()
*/
@Nullable String getCustomLineValue(@NonNull TabPlayer player, @NonNull String line);
/**
* Returns original name value configured in files.
*
* @param player
* Player to check
* @return Original configured name
* @see #setName(TabPlayer, String)
*/
@NotNull String getOriginalName(@NonNull TabPlayer player);
/**
* Returns original line value configured in files.
*
* @param player
* Player to check
* @param line
* Line name
* @return Original configured line
* @see #setLine(TabPlayer, String, String)
*/
@NotNull String getOriginalLineValue(@NonNull TabPlayer player, @NonNull String line);
/**
* Returns list of all defined lines in config, both static and dynamic lines.
*
* @return List of all defined lines in config
*/
@NotNull List<String> getDefinedLines();
}
@@ -1,14 +1,23 @@
package me.neznamy.tab.api.placeholder;
import org.jetbrains.annotations.NotNull;
/**
* An interface representing some kind of placeholder
*/
public interface Placeholder {
/**
* Returns refresh interval of this placeholder
*
* @return refresh interval of this placeholder
*/
int getRefresh();
String getIdentifier();
void enableTriggerMode(Runnable onActivation, Runnable onDisable);
void unload();
boolean isUsed();
/**
* Returns placeholder's identifier
*
* @return placeholder's identifier
*/
@NotNull String getIdentifier();
}
@@ -1,53 +1,92 @@
package me.neznamy.tab.api.placeholder;
import java.util.List;
import lombok.NonNull;
import me.neznamy.tab.api.TabAPI;
import me.neznamy.tab.api.TabPlayer;
import org.jetbrains.annotations.NotNull;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;
import me.neznamy.tab.api.feature.Refreshable;
import me.neznamy.tab.api.TabPlayer;
public interface PlaceholderManager extends Refreshable {
/**
* An interface allowing placeholder registration. Instance can be
* obtained using {@link TabAPI#getPlaceholderManager()}
*/
public interface PlaceholderManager {
/**
* Registers a server placeholder (placeholder with same output for all players)
*/
ServerPlaceholder registerServerPlaceholder(String identifier, int refresh, Supplier<Object> supplier);
/**
* Registers a player placeholder (placeholder with player-specific output)
*/
PlayerPlaceholder registerPlayerPlaceholder(String identifier, int refresh, Function<TabPlayer, Object> function);
/**
* Registers a relational placeholder (different output for each player pair)
*/
RelationalPlaceholder registerRelationalPlaceholder(String identifier, int refresh, BiFunction<TabPlayer, TabPlayer, Object> function);
/**
* Detects placeholders in text using %% pattern and returns list of all detected identifiers
*
* @param text
* text to detect placeholders in
* @return list of detected identifiers
*/
List<String> detectPlaceholders(String text);
/**
* Adds placeholder to list of used placeholders and assigns this feature as using it,
* which will then receive refresh() if values changes
*
* @param identifier
* placeholder identifier
* @param feature
* feature using the placeholder
* Placeholder identifier
* @param refresh
* Refresh interval
* @param supplier
* Supplier for placeholder output
* @return Registered placeholder for further use
* @throws IllegalArgumentException
* If {@code identifier} does not start and end with {@code %} or
* {@code refresh} is not divisible by 50
*/
void addUsedPlaceholder(String identifier, Refreshable feature);
@NotNull ServerPlaceholder registerServerPlaceholder(@NonNull String identifier, int refresh, @NonNull Supplier<Object> supplier);
String findReplacement(String placeholder, String output);
/**
* Registers a player placeholder (placeholder different output per player)
*
* @param identifier
* Placeholder identifier
* @param refresh
* Refresh interval
* @param function
* Function for placeholder output
* @return Registered placeholder for further use
* @throws IllegalArgumentException
* If {@code identifier} does not start and end with {@code %} or
* {@code refresh} is not divisible by 50
*/
@NotNull PlayerPlaceholder registerPlayerPlaceholder(@NonNull String identifier, int refresh, @NonNull Function<TabPlayer, Object> function);
List<String> getUsedPlaceholders();
/**
* Registers a relational placeholder (placeholder with output different for each player duo)
*
* @param identifier
* Placeholder identifier
* @param refresh
* Refresh interval
* @param function
* Function for placeholder output
* @return Registered placeholder for further use
* @throws IllegalArgumentException
* If {@code identifier} does not start and end with {@code %},
* does not start with {@code %rel_} or
* {@code refresh} is not divisible by 50
*/
@NotNull RelationalPlaceholder registerRelationalPlaceholder(@NonNull String identifier, int refresh, @NonNull BiFunction<TabPlayer, TabPlayer, Object> function);
Placeholder getPlaceholder(String identifier);
/**
* Returns placeholder from specified identifier. If it does not exist, it is registered
* as a PlaceholderAPI placeholder and returned.
*
* @param identifier
* Placeholder identifier
* @return Placeholder with specified identifier
*/
@NotNull Placeholder getPlaceholder(@NonNull String identifier);
/**
* Unregisters placeholder and makes plugin no longer refresh it.
*
* @param placeholder
* Placeholder to unregister
*/
void unregisterPlaceholder(@NonNull Placeholder placeholder);
/**
* Unregisters placeholder and makes plugin no longer refresh it.
*
* @param identifier
* identifier of placeholder to unregister
*/
void unregisterPlaceholder(@NonNull String identifier);
}
@@ -1,28 +1,30 @@
package me.neznamy.tab.api.placeholder;
import lombok.NonNull;
import me.neznamy.tab.api.TabPlayer;
/**
* Interface for player placeholders (placeholders returning different value for each player)
*/
public interface PlayerPlaceholder extends Placeholder {
void updateValue(TabPlayer player, Object value);
/**
* Calls the placeholder request function and returns the output.
* If the placeholder threw an exception, it is logged in {@code placeholder-errors.log}
* file and "ERROR" is returned.
*
* @param p
* player to get placeholder value for
* @return value placeholder returned or "ERROR" if it threw an error
*/
Object request(TabPlayer p);
/**
* Returns last known value for given player.
* Force-updates placeholder value. It will still be overridden by periodic refresh later,
* so this is (only) useful for force an update when value is supposed to change, and you have
* a high refresh interval set.
*
* @param player
* player to get last value for
* @return last known value for entered player
* Player to update value for
* @param value
* New value
*/
String getLastValue(TabPlayer player);
void updateValue(@NonNull TabPlayer player, @NonNull Object value);
/**
* Force-updates placeholder value using previously provided update function.
*
* @param player
* Player to update value for
*/
void update(@NonNull TabPlayer player);
}
@@ -1,33 +1,34 @@
package me.neznamy.tab.api.placeholder;
import lombok.NonNull;
import me.neznamy.tab.api.TabPlayer;
/**
* An interface for relational placeholders (returning different value for each player duo)
*/
public interface RelationalPlaceholder extends Placeholder {
void updateValue(TabPlayer viewer, TabPlayer target, Object value);
/**
* Calls the placeholder request function and returns the output.
* If the placeholder threw an exception, it is logged in {@code placeholder-errors.log}
* file and "ERROR" is returned.
* Force-updates placeholder value. It will still be overridden by periodic refresh later,
* so this is (only) useful for force an update when value is supposed to change, and you have
* a high refresh interval set.
*
* @param viewer
* player looking at output of the placeholder
* Placeholder viewer
* @param target
* player the placeholder is displayed on
* @return value placeholder returned or "ERROR" if it threw an error
* Placeholder target
* @param value
* New value
*/
Object request(TabPlayer viewer, TabPlayer target);
void updateValue(@NonNull TabPlayer viewer, @NonNull TabPlayer target, @NonNull Object value);
/**
* Returns last known value for given players. First player is viewer,
* second player is target.
* Force-updates placeholder value using previously provided update function.
*
* @param viewer
* viewer of the placeholder
* Placeholder viewer
* @param target
* target who is the text displayed on
* @return last known value for entered player duo
* Placeholder target
*/
String getLastValue(TabPlayer viewer, TabPlayer target);
void update(@NonNull TabPlayer viewer, @NonNull TabPlayer target);
}
@@ -1,22 +1,24 @@
package me.neznamy.tab.api.placeholder;
import lombok.NonNull;
/**
* An interface for server placeholders (returning same value for all players)
*/
public interface ServerPlaceholder extends Placeholder {
void updateValue(Object value);
/**
* Force-updates placeholder value. It will still be overridden by periodic refresh later,
* so this is (only) useful for force an update when value is supposed to change, and you have
* a high refresh interval set.
*
* @param value
* New value
*/
void updateValue(@NonNull Object value);
/**
* Calls the placeholder request function and returns the output.
* If the placeholder threw an exception, it is logged in {@code placeholder-errors.log}
* file and "ERROR" is returned.
*
* @return value placeholder returned or "ERROR" if it threw an error
* Force-updates placeholder value using previously provided update function.
*/
Object request();
/**
* Returns last known value of the placeholder.
*
* @return last known value
*/
String getLastValue();
void update();
}
@@ -1,5 +1,8 @@
package me.neznamy.tab.api.scoreboard;
import lombok.NonNull;
import org.jetbrains.annotations.NotNull;
/**
* An interface allowing to work with a line of
* text in scoreboard.
@@ -13,7 +16,7 @@ public interface Line {
*
* @return raw text of this line
*/
String getText();
@NotNull String getText();
/**
* Changes text to new value. Supports placeholders, which are
@@ -25,5 +28,5 @@ public interface Line {
* @param text
* Raw text to display in the line
*/
void setText(String text);
void setText(@NonNull String text);
}
@@ -1,5 +1,8 @@
package me.neznamy.tab.api.scoreboard;
import lombok.NonNull;
import org.jetbrains.annotations.NotNull;
import java.util.List;
/**
@@ -17,7 +20,7 @@ public interface Scoreboard {
*
* @return custom name of this scoreboard
*/
String getName();
@NotNull String getName();
/**
* Returns raw title of this scoreboard. Placeholders stay in their
@@ -26,7 +29,7 @@ public interface Scoreboard {
* @return scoreboard title
* @see #setTitle(String)
*/
String getTitle();
@NotNull String getTitle();
/**
* Sets title to specified value. Placeholders are refreshed automatically
@@ -43,7 +46,7 @@ public interface Scoreboard {
* New title to use with placeholder support
* @see #getTitle()
*/
void setTitle(String title);
void setTitle(@NonNull String title);
/**
* Returns list of lines in this scoreboard in the order they appear
@@ -54,7 +57,7 @@ public interface Scoreboard {
* @see #addLine(String)
* @see #removeLine(int)
*/
List<Line> getLines();
@NotNull List<Line> getLines();
/**
* Adds line with specified text on the bottom of scoreboard. Supports
@@ -65,7 +68,7 @@ public interface Scoreboard {
* @see #getLines()
* @see #removeLine(int)
*/
void addLine(String text);
void addLine(@NonNull String text);
/**
* Removes line with specified index. Index starts at {@code 0} and ends at
@@ -3,7 +3,9 @@ package me.neznamy.tab.api.scoreboard;
import java.util.List;
import java.util.Map;
import lombok.NonNull;
import me.neznamy.tab.api.TabPlayer;
import org.jetbrains.annotations.NotNull;
/**
* Interface allowing work with scoreboards, such as creating,
@@ -11,7 +13,7 @@ import me.neznamy.tab.api.TabPlayer;
* <p>
* Instance can be obtained using {@link me.neznamy.tab.api.TabAPI#getScoreboardManager()}.
* This requires the scoreboard feature to be enabled in config,
* the method will return null otherwise.
* the method will return {@code null} otherwise.
*/
public interface ScoreboardManager {
@@ -39,7 +41,7 @@ public interface ScoreboardManager {
* @throws NullPointerException
* if {@code name} or {@code lines} is {@code null}
*/
Scoreboard createScoreboard(String name, String title, List<String> lines);
@NotNull Scoreboard createScoreboard(@NonNull String name, @NonNull String title, @NonNull List<String> lines);
/**
* Returns map of registered scoreboards via config and API.
@@ -49,7 +51,7 @@ public interface ScoreboardManager {
*
* @return map of registered scoreboards
*/
Map<String, Scoreboard> getRegisteredScoreboards();
@NotNull Map<String, Scoreboard> getRegisteredScoreboards();
/**
* Displays scoreboard to defined player. This will disable all display
@@ -66,7 +68,7 @@ public interface ScoreboardManager {
* @see #hasCustomScoreboard(TabPlayer)
* @see #resetScoreboard(TabPlayer)
*/
void showScoreboard(TabPlayer player, Scoreboard scoreboard);
void showScoreboard(@NonNull TabPlayer player, @NonNull Scoreboard scoreboard);
/**
* Returns {@code true} if player has custom scoreboard set using
@@ -79,7 +81,7 @@ public interface ScoreboardManager {
* @see #showScoreboard(TabPlayer, Scoreboard)
* @see #resetScoreboard(TabPlayer)
*/
boolean hasCustomScoreboard(TabPlayer player);
boolean hasCustomScoreboard(@NonNull TabPlayer player);
/**
* Hides custom scoreboard sent using {@link #showScoreboard(TabPlayer, Scoreboard)}
@@ -91,7 +93,7 @@ public interface ScoreboardManager {
* @see #showScoreboard(TabPlayer, Scoreboard)
* @see #hasCustomScoreboard(TabPlayer)
*/
void resetScoreboard(TabPlayer player);
void resetScoreboard(@NonNull TabPlayer player);
/**
* Returns {@code true} if player has scoreboard enabled, {@code false}
@@ -105,7 +107,7 @@ public interface ScoreboardManager {
* @see #setScoreboardVisible(TabPlayer, boolean, boolean)
* @see #toggleScoreboard(TabPlayer, boolean)
*/
boolean hasScoreboardVisible(TabPlayer player);
boolean hasScoreboardVisible(@NonNull TabPlayer player);
/**
* Sets scoreboard visibility of player to defined value. If visibility status
@@ -122,7 +124,7 @@ public interface ScoreboardManager {
* @see #toggleScoreboard(TabPlayer, boolean)
* @see #hasScoreboardVisible(TabPlayer)
*/
void setScoreboardVisible(TabPlayer player, boolean visible, boolean sendToggleMessage);
void setScoreboardVisible(@NonNull TabPlayer player, boolean visible, boolean sendToggleMessage);
/**
* Toggles scoreboard for specified player. If player had scoreboard visible,
@@ -136,7 +138,7 @@ public interface ScoreboardManager {
* @see #hasScoreboardVisible(TabPlayer)
* @see #setScoreboardVisible(TabPlayer, boolean, boolean)
*/
void toggleScoreboard(TabPlayer player, boolean sendToggleMessage);
void toggleScoreboard(@NonNull TabPlayer player, boolean sendToggleMessage);
/**
* Temporarily displays scoreboard to all players for specified amount of
@@ -151,7 +153,7 @@ public interface ScoreboardManager {
* if no scoreboard was found with such name or {@code duration}
* is &lt; 0.
*/
void announceScoreboard(String scoreboard, int duration);
void announceScoreboard(@NonNull String scoreboard, int duration);
/**
* Returns player's currently displayed scoreboard. This can be either with
@@ -162,5 +164,5 @@ public interface ScoreboardManager {
* player to get active scoreboard of
* @return player's active scoreboard or {@code null} if player has no scoreboard
*/
Scoreboard getActiveScoreboard(TabPlayer player);
@NotNull Scoreboard getActiveScoreboard(@NonNull TabPlayer player);
}
@@ -1,4 +1,9 @@
package me.neznamy.tab.api;
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.Nullable;
/**
* Interface allowing to send header/footer to players.
@@ -13,33 +18,33 @@ public interface HeaderFooterManager {
* Sets raw value of header to specified value. This overrides plugin
* configuration until reset is called. Has full placeholder support
* and will send update packet whenever a placeholder used inside
* changes its value.
* changes its value. Use {@code null} to reset value back to original.
*
* @param player
* Player to set header for
* @param header
* Raw header value
*/
void setHeader(TabPlayer player, String header);
void setHeader(@NonNull TabPlayer player, @Nullable String header);
/**
* Sets raw value of footer to specified value. This overrides plugin
* configuration until reset is called. Has full placeholder support
* and will send update packet whenever a placeholder used inside
* changes its value.
* changes its value. Use {@code null} to reset value back to original.
*
* @param player
* Player to set header for
* @param footer
* Raw footer value
*/
void setFooter(TabPlayer player, String footer);
void setFooter(@NonNull TabPlayer player, @Nullable String footer);
/**
* Sets raw value of header and footer to specified values. This overrides plugin
* configuration until reset is called. Has full placeholder support
* and will send update packet whenever a placeholder used inside
* changes its value.
* changes its value. Use {@code null} to reset value back to original.
*
* @param player
* Player to set header for
@@ -48,35 +53,5 @@ public interface HeaderFooterManager {
* @param footer
* Raw footer value
*/
void setHeaderAndFooter(TabPlayer player, String header, String footer);
/**
* Resets header value previously set using {@link #setHeader(TabPlayer, String)}
* or {@link #setHeaderAndFooter(TabPlayer, String, String)} and restores internal
* logic using configuration files.
*
* @param player
* Player to reset header for
*/
void resetHeader(TabPlayer player);
/**
* Resets footer value previously set using {@link #setFooter(TabPlayer, String)}
* or {@link #setHeaderAndFooter(TabPlayer, String, String)} and restores internal
* logic using configuration files.
*
* @param player
* Player to reset header for
*/
void resetFooter(TabPlayer player);
/**
* Resets values previously set using {@link #setHeader(TabPlayer, String)},
* {@link #setFooter(TabPlayer, String)} or {@link #setHeaderAndFooter(TabPlayer, String, String)}
* and restores internal logic using configuration files.
*
* @param player
* Player to reset header and footer for
*/
void resetHeaderAndFooter(TabPlayer player);
void setHeaderAndFooter(@NonNull TabPlayer player, @Nullable String header, @Nullable String footer);
}
@@ -1,19 +0,0 @@
package me.neznamy.tab.api.tablist;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NonNull;
import org.jetbrains.annotations.Nullable;
/**
* Class representing a minecraft skin as a value - signature pair.
*/
@Data @AllArgsConstructor
public class Skin {
/** Skin value */
@NonNull private final String value;
/** Skin signature */
@Nullable private final String signature;
}
@@ -0,0 +1,38 @@
package me.neznamy.tab.api.tablist;
import lombok.NonNull;
import me.neznamy.tab.api.TabPlayer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public interface SortingManager {
/**
* Forces new team name for the player until this method is called again with null argument and
* performs all actions to change player's team name
*
* @param player
* player to set team name of
* @param teamName
* forced team name
*/
void forceTeamName(@NonNull TabPlayer player, @Nullable String teamName);
/**
* Returns forced team name of player or null if not forced
*
* @param player
* player to check forced team name of
* @return forced team name of player or null if not forced
*/
@Nullable String getForcedTeamName(@NonNull TabPlayer player);
/**
* Returns original team name of the player set by the plugin.
*
* @param player
* Player to get team name of
* @return Player's team name based on configuration.
*/
@NotNull String getOriginalTeamName(@NonNull TabPlayer player);
}
@@ -1,34 +0,0 @@
package me.neznamy.tab.api.tablist;
import lombok.NonNull;
import me.neznamy.tab.api.chat.IChatBaseComponent;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.Map;
import java.util.UUID;
public interface TabList {
String TEXTURES_PROPERTY = "textures";
void removeEntry(@NonNull UUID entry);
void removeEntries(@NonNull Collection<UUID> entries);
void updateDisplayName(@NonNull UUID entry, @Nullable IChatBaseComponent displayName);
void updateDisplayNames(@NonNull Map<UUID, IChatBaseComponent> entries);
void updateLatency(@NonNull UUID entry, int latency);
void updateLatencies(@NonNull Map<UUID, Integer> entries);
void updateGameMode(@NonNull UUID entry, int gameMode);
void updateGameModes(@NonNull Map<UUID, Integer> entries);
void addEntry(@NonNull TabListEntry entry);
void addEntries(@NonNull Collection<TabListEntry> entries);
}
@@ -1,70 +0,0 @@
package me.neznamy.tab.api.tablist;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.api.chat.IChatBaseComponent;
import org.jetbrains.annotations.Nullable;
import java.util.UUID;
/**
* A subclass representing player list entry
*/
@Data
@AllArgsConstructor
public class TabListEntry {
/** Player UUID */
@NonNull private UUID uniqueId;
/** Real name of affected player */
@Nullable private String name;
/** Player's skin, null for empty skin */
@Nullable private Skin skin;
/** If player should appear in tablist or not */
private boolean listed;
/** Latency */
private int latency;
/** GameMode */
private int gameMode;
/**
* Display name displayed in TabList. Using {@code null} results in no display name
* and scoreboard team prefix/suffix being visible in TabList instead.
*/
@Nullable private IChatBaseComponent displayName;
/** Player's chat signing key (1.19+), remote chat session (1.19.3+) */
@Nullable private Object chatSession;
@RequiredArgsConstructor
public static class Builder {
@NonNull private UUID uniqueId;
@Nullable private String name;
@Nullable private Skin skin;
private boolean listed;
private int latency;
private int gameMode;
@Nullable private IChatBaseComponent displayName;
@Nullable private Object chatSession;
public Builder name(String name) { this.name = name; return this; }
public Builder skin(Skin skin) { this.skin = skin; return this; }
public Builder listed(boolean listed) { this.listed = listed; return this; }
public Builder latency(int latency) { this.latency = latency; return this; }
public Builder gameMode(int gameMode) { this.gameMode = gameMode; return this; }
public Builder displayName(IChatBaseComponent displayName) { this.displayName = displayName; return this; }
public Builder chatSession(Object chatSession) { this.chatSession = chatSession; return this; }
public TabListEntry build() {
return new TabListEntry(uniqueId, name, skin, listed, latency, gameMode, displayName, chatSession);
}
}
}
@@ -1,92 +1,62 @@
package me.neznamy.tab.api;
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;
/**
* An interface allowing to modify tablist display names
* An interface allowing to modify TabList display names
* of players.
* <p>
* Instance can be obtained using {@link TabAPI#getTablistFormatManager()}.
* Instance can be obtained using {@link TabAPI#getTabListFormatManager()}.
* This requires the feature to be enabled in config, otherwise the method will
* return {@code null}.
*/
public interface TablistFormatManager {
public interface TabListFormatManager {
/**
* Changes player's prefix to provided value. Supports placeholders,
* as well as any supported RGB formats.
* as well as any supported RGB formats. Use {@code null} to reset
* value back to original.
*
* @param player
* player to change prefix of
* @param prefix
* new prefix value
* @see #resetPrefix(TabPlayer)
* @see #getCustomPrefix(TabPlayer)
* @see #getOriginalPrefix(TabPlayer)
*/
void setPrefix(TabPlayer player, String prefix);
void setPrefix(@NonNull TabPlayer player, @Nullable String prefix);
/**
* Changes player's name to provided value. Supports placeholders,
* as well as any supported RGB formats.
* as well as any supported RGB formats. Use {@code null} to reset
* value back to original.
*
* @param player
* player to change prefix of
* @param customName
* new customName value
* @see #resetName(TabPlayer)
* @see #getCustomName(TabPlayer)
* @see #getOriginalName(TabPlayer)
*/
void setName(TabPlayer player, String customName);
void setName(@NonNull TabPlayer player, @Nullable String customName);
/**
* Changes player's suffix to provided value. Supports placeholders,
* as well as any supported RGB formats.
* as well as any supported RGB formats. Use {@code null} to reset
* value back to original.
*
* @param player
* player to change prefix of
* @param suffix
* new suffix value
* @see #resetSuffix(TabPlayer)
* @see #getCustomSuffix(TabPlayer)
* @see #getOriginalSuffix(TabPlayer)
*/
void setSuffix(TabPlayer player, String suffix);
/**
* Resets player's prefix previously assigned using {@link #setPrefix(TabPlayer, String)}
* method. If nothing was set, this method will not do anything.
*
* @param player
* player to reset prefix of
* @see #setPrefix(TabPlayer, String)
* @see #getCustomPrefix(TabPlayer)
* @see #getOriginalPrefix(TabPlayer)
*/
void resetPrefix(TabPlayer player);
/**
* Resets player's custom name previously assigned using {@link #setName(TabPlayer, String)}
* method. If nothing was set, this method will not do anything.
*
* @param player
* player to reset name of
* @see #setName(TabPlayer, String)
* @see #getCustomName(TabPlayer)
* @see #getOriginalName(TabPlayer)
*/
void resetName(TabPlayer player);
/**
* Resets player's suffix previously assigned using {@link #setSuffix(TabPlayer, String)}
* method. If nothing was set, this method will not do anything.
*
* @param player
* player to reset suffix of
* @see #setSuffix(TabPlayer, String)
* @see #getCustomSuffix(TabPlayer)
* @see #getOriginalSuffix(TabPlayer)
*/
void resetSuffix(TabPlayer player);
void setSuffix(@NonNull TabPlayer player, @Nullable String suffix);
/**
* Returns prefix assigned using {@link #setPrefix(TabPlayer, String)} method.
@@ -96,10 +66,9 @@ public interface TablistFormatManager {
* player to get custom prefix of
* @return player's custom prefix or {@code null} if not defined
* @see #setPrefix(TabPlayer, String)
* @see #resetPrefix(TabPlayer)
* @see #getOriginalPrefix(TabPlayer)
*/
String getCustomPrefix(TabPlayer player);
@Nullable String getCustomPrefix(@NonNull TabPlayer player);
/**
* Returns custom name assigned using {@link #setName(TabPlayer, String)} method.
@@ -109,10 +78,9 @@ public interface TablistFormatManager {
* player to get custom name of
* @return player's custom name or {@code null} if not defined
* @see #setName(TabPlayer, String)
* @see #resetName(TabPlayer)
* @see #getOriginalName(TabPlayer)
*/
String getCustomName(TabPlayer player);
@Nullable String getCustomName(@NonNull TabPlayer player);
/**
* Returns suffix assigned using {@link #setSuffix(TabPlayer, String)} method.
@@ -122,10 +90,9 @@ public interface TablistFormatManager {
* player to get custom suffix of
* @return player's custom suffix or {@code null} if not defined
* @see #setSuffix(TabPlayer, String)
* @see #resetSuffix(TabPlayer)
* @see #getOriginalSuffix(TabPlayer)
*/
String getCustomSuffix(TabPlayer player);
@Nullable String getCustomSuffix(@NonNull TabPlayer player);
/**
* Returns player's original prefix applied using plugin's internal logic.
@@ -135,10 +102,9 @@ public interface TablistFormatManager {
* player to get original prefix of
* @return player's original prefix
* @see #setPrefix(TabPlayer, String)
* @see #resetPrefix(TabPlayer)
* @see #getCustomPrefix(TabPlayer)
*/
String getOriginalPrefix(TabPlayer player);
@NotNull String getOriginalPrefix(@NonNull TabPlayer player);
/**
* Returns player's original name applied using plugin's internal logic.
@@ -148,10 +114,9 @@ public interface TablistFormatManager {
* player to get original name of
* @return player's original name
* @see #setName(TabPlayer, String)
* @see #resetName(TabPlayer)
* @see #getCustomName(TabPlayer)
*/
String getOriginalName(TabPlayer player);
@NotNull String getOriginalName(@NonNull TabPlayer player);
/**
* Returns player's original suffix applied using plugin's internal logic.
@@ -161,8 +126,7 @@ public interface TablistFormatManager {
* player to get original suffix of
* @return player's original suffix
* @see #setSuffix(TabPlayer, String)
* @see #resetSuffix(TabPlayer)
* @see #getCustomSuffix(TabPlayer)
*/
String getOriginalSuffix(TabPlayer player);
@NotNull String getOriginalSuffix(@NonNull TabPlayer player);
}
@@ -0,0 +1,78 @@
package me.neznamy.tab.api.tablist.layout;
import lombok.NonNull;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Interface representing a layout.
*/
public interface Layout {
/**
* Returns name of the layout.
*
* @return name of the layout
*/
@NotNull String getName();
/**
* Adds fixed slot with specified parameters
*
* @param slot
* Slot (1-80)
* @param text
* Text to display
*/
void addFixedSlot(int slot, @NonNull String text);
/**
* Adds fixed slot with specified parameters
*
* @param slot
* Slot (1-80)
* @param text
* Text to display
* @param skin
* Skin definition like in config
*/
void addFixedSlot(int slot, @NonNull String text, @NonNull String skin);
/**
* Adds fixed slot with specified parameters
*
* @param slot
* Slot (1-80)
* @param text
* Text to display
* @param ping
* Slot's ping
*/
void addFixedSlot(int slot, @NonNull String text, int ping);
/**
* Adds fixed slot with specified parameters
*
* @param slot
* Slot (1-80)
* @param text
* Text to display
* @param skin
* Skin definition like in config
* @param ping
* Slot's ping
*/
void addFixedSlot(int slot, @NonNull String text, @NonNull String skin, int ping);
/**
* Adds a player group.
*
* @param condition
* Condition that must be met for player to appear in the group.
* May be null for no requirement. Syntax is like in config -
* either condition name or short format.
* @param slots
* Slots for this player group
*/
void addGroup(@Nullable String condition, int[] slots);
}
@@ -0,0 +1,44 @@
package me.neznamy.tab.api.tablist.layout;
import lombok.NonNull;
import me.neznamy.tab.api.TabAPI;
import me.neznamy.tab.api.TabPlayer;
import org.jetbrains.annotations.Nullable;
/**
* Interface for working with layouts. Instance can be obtained using
* {@link TabAPI#getLayoutManager()}. If the feature is disabled in
* config, it will return {@code null}.
*/
public interface LayoutManager {
/**
* Creates new layout
*
* @param name
* Unique layout name
* @return Created layout
*/
Layout createNewLayout(String name);
/**
* Sends layout to player. Set to {@code null} to make player not see
* any layout, but default TabList instead.
*
* @param player
* Player to send layout to
* @param layout
* Layout to send
* @see #resetLayout(TabPlayer)
*/
void sendLayout(@NonNull TabPlayer player, @Nullable Layout layout);
/**
* Reset layout back to original one based on configuration.
*
* @param player
* Player to reset layout for
* @see #sendLayout(TabPlayer, Layout)
*/
void resetLayout(@NonNull TabPlayer player);
}
@@ -1,32 +0,0 @@
package me.neznamy.tab.api.team;
import java.util.List;
import me.neznamy.tab.api.TabPlayer;
public interface UnlimitedNametagManager extends TeamManager {
void disableArmorStands(TabPlayer player);
void enableArmorStands(TabPlayer player);
boolean hasDisabledArmorStands(TabPlayer player);
void setName(TabPlayer player, String customName);
void setLine(TabPlayer player, String line, String value);
void resetName(TabPlayer player);
void resetLine(TabPlayer player, String line);
String getCustomName(TabPlayer player);
String getCustomLineValue(TabPlayer player, String line);
String getOriginalName(TabPlayer player);
String getOriginalLineValue(TabPlayer player, String line);
List<String> getDefinedLines();
}
@@ -1,30 +0,0 @@
package me.neznamy.tab.api.util;
import lombok.AllArgsConstructor;
import me.neznamy.tab.api.ProtocolVersion;
import java.util.HashMap;
import java.util.Map;
@AllArgsConstructor
public class ComponentCache<K, V> {
private final int cacheSize;
private final BiFunctionWithException<K, ProtocolVersion, V> function;
private final Map<K, V> cacheModern = new HashMap<>();
private final Map<K, V> cacheLegacy = new HashMap<>();
public V get(K key, ProtocolVersion clientVersion) {
try {
if (key == null) return null;
Map<K, V> cache = clientVersion == null || clientVersion.getMinorVersion() >= 16 ? cacheModern : cacheLegacy;
if (cache.containsKey(key)) return cache.get(key);
V value = function.apply(key, clientVersion);
if (cache.size() > cacheSize) cache.clear();
cache.put(key, value);
return value;
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
}
@@ -1,62 +0,0 @@
package me.neznamy.tab.api.util;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import me.neznamy.tab.api.TabPlayer;
/**
* Class with utility methods used in the plugin
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class Preconditions {
/**
* Checks length of String and throws {@code IllegalArgumentException} if it is
* longer than limit.
*
* @param string
* String to check
* @param maxLength
* Maximum length of the string
* @param name
* Name of the variable to use in exception message
* @throws IllegalArgumentException
* if {@code string} is {@code null} or longer than {@code maxLength} characters
*/
public static void checkMaxLength(@NonNull String string, int maxLength, String name) {
if (string.length() > maxLength) throw new IllegalArgumentException(name + " is longer than " + maxLength + " characters (" + string.length() + ")");
}
/**
* Checks number range and throws {@code IllegalArgumentException} if number
* is out of range.
*
* @param number
* Number to check
* @param min
* Minimum allowed value
* @param max
* Maximum allowed value
* @param variable
* Variable name to use in exception message
* @throws IllegalArgumentException
* if {@code number} is out of range ({@code number < min || number > max})
*/
public static void checkRange(Number number, Number min, Number max, String variable) {
if (number.doubleValue() < min.doubleValue() || number.doubleValue() > max.doubleValue())
throw new IllegalArgumentException(variable + " index out of range (" + min + " - " + max + "), was " + number);
}
/**
* Checks if player is loaded and throws {@code IllegalStateException} if not.
*
* @param player
* Player to check
* @throws IllegalStateException
* If player is not loaded
*/
public static void checkLoaded(TabPlayer player) {
if (!player.isLoaded()) throw new IllegalStateException("Player is not loaded yet. Try again later.");
}
}
@@ -1,24 +0,0 @@
package me.neznamy.tab.api.util;
/**
* Utility class storing methods working with reflection
*/
public class ReflectionUtils {
/**
* Returns {@code true} if class with given full name exists,
* {@code false} if not.
*
* @param path
* Full class path and name
* @return {@code true} if exists, {@code false} if not
*/
public static boolean classExists(String path) {
try {
Class.forName(path);
return true;
} catch (ClassNotFoundException | NullPointerException e) {
return false;
}
}
}
+3 -11
View File
@@ -3,15 +3,7 @@ plugins {
}
dependencies {
implementation(libs.plugin.shadow)
implementation(libs.plugin.lombok)
}
dependencies {
compileOnly(files(libs::class.java.protectionDomain.codeSource.location))
}
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
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")
}
+1 -8
View File
@@ -2,15 +2,8 @@ rootProject.name = "build-logic"
dependencyResolutionManagement {
repositories {
maven("https://maven.fabricmc.net/")
gradlePluginPortal()
mavenCentral()
}
// You're a great IDE IntelliJ, but sometimes you really annoy me. >:(
// repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
versionCatalogs {
register("libs") {
from(files("../gradle/libs.versions.toml"))
}
}
}
@@ -8,6 +8,7 @@ plugins {
tasks {
processResources {
filter<ReplaceTokens>("tokens" to mapOf(
"id" to rootProject.ext.get("id")!!.toString(),
"name" to rootProject.name,
"version" to project.version,
"description" to project.description,
@@ -9,6 +9,7 @@ tasks {
relocate("org.bstats", "me.neznamy.tab.libs.org.bstats")
relocate("org.json.simple", "me.neznamy.tab.libs.org.json.simple")
relocate("net.kyori.event", "me.neznamy.tab.libs.net.kyori.event")
relocate("me.neznamy.yamlassist", "me.neznamy.tab.libs.yamlassist")
relocate("me.neznamy.yamlassist", "me.neznamy.tab.libs.me.neznamy.yamlassist")
relocate("org.yaml.snakeyaml", "me.neznamy.tab.libs.org.yaml.snakeyaml")
}
}
+3 -2
View File
@@ -4,7 +4,7 @@ plugins {
allprojects {
group = "me.neznamy"
version = "3.3.2"
version = "4.0.3-SNAPSHOT"
description = "An all-in-one solution that works"
ext.set("id", "tab")
@@ -18,7 +18,8 @@ val platforms = setOf(
projects.krypton,
projects.velocity,
projects.sponge7,
projects.sponge8
projects.sponge8,
projects.fabric
).map { it.dependencyProject }
val special = setOf(
+5 -5
View File
@@ -1,8 +1,8 @@
dependencies {
implementation(projects.shared)
implementation(libs.bstats.bukkit)
compileOnly(libs.bukkit)
compileOnly(libs.placeholderapi)
compileOnly(libs.vault)
compileOnly(libs.authlib)
implementation("org.bstats:bstats-bukkit:3.0.1")
compileOnly("org.purpurmc.purpur:purpur-api:1.16.5-R0.1-SNAPSHOT")
compileOnly("me.clip:placeholderapi:2.11.2")
compileOnly("com.mojang:authlib:1.5.25")
compileOnly("com.github.MilkBowl:VaultAPI:1.7")
}
@@ -1,8 +1,8 @@
package me.neznamy.tab.platforms.bukkit;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.api.TabAPI;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.platform.EventListener;
import me.neznamy.tab.shared.platform.TabPlayer;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
@@ -14,60 +14,30 @@ import org.bukkit.event.player.PlayerQuitEvent;
/**
* The core for bukkit forwarding events into all enabled features
*/
@RequiredArgsConstructor
public class BukkitEventListener implements Listener {
public class BukkitEventListener extends EventListener<Player> implements Listener {
/** Platform instance */
private final BukkitPlatform platform;
/**
* Listener to PlayerQuitEvent to remove player data and forward the event to features
*
* @param e
* quit event
*/
@EventHandler(priority = EventPriority.HIGHEST)
public void onQuit(PlayerQuitEvent e) {
if (TAB.getInstance().isPluginDisabled()) return;
TAB.getInstance().getCPUManager().runTask(() -> TAB.getInstance().getFeatureManager().onQuit(TAB.getInstance().getPlayer(e.getPlayer().getUniqueId())));
}
/**
* Listener to PlayerJoinEvent to create player data and forward the event to features
*
* @param e
* join event
*/
@EventHandler(priority = EventPriority.LOWEST)
public void onJoin(PlayerJoinEvent e) {
if (TAB.getInstance().isPluginDisabled()) return;
TAB.getInstance().getCPUManager().runTask(() ->
TAB.getInstance().getFeatureManager().onJoin(new BukkitTabPlayer(e.getPlayer(), platform.getProtocolVersion(e.getPlayer()))));
quit(e.getPlayer().getUniqueId());
}
@EventHandler(priority = EventPriority.LOW)
public void onJoin(PlayerJoinEvent e) {
join(e.getPlayer());
}
/**
* Listener to PlayerChangedWorldEvent to forward the event to features
*
* @param e
* world changed event
*/
@EventHandler(priority = EventPriority.LOWEST)
public void onWorldChange(PlayerChangedWorldEvent e) {
if (TAB.getInstance().isPluginDisabled()) return;
TAB.getInstance().getCPUManager().runTask(() ->
TAB.getInstance().getFeatureManager().onWorldChange(e.getPlayer().getUniqueId(), e.getPlayer().getWorld().getName()));
worldChange(e.getPlayer().getUniqueId(), e.getPlayer().getWorld().getName());
}
/**
* Listener to PlayerChangedWorldEvent to forward the event to features
*
* @param e
* command preprocess event
*/
@EventHandler
public void onCommand(PlayerCommandPreprocessEvent e) {
if (TabAPI.getInstance().isPluginDisabled()) return;
if (TabAPI.getInstance().getFeatureManager().onCommand(
TabAPI.getInstance().getPlayer(e.getPlayer().getUniqueId()), e.getMessage())) e.setCancelled(true);
if (command(e.getPlayer().getUniqueId(), e.getMessage())) e.setCancelled(true);
}
}
@Override
public TabPlayer createPlayer(Player player) {
return new BukkitTabPlayer(player);
}
}
@@ -2,20 +2,18 @@ package me.neznamy.tab.platforms.bukkit;
import com.mojang.authlib.GameProfile;
import io.netty.channel.Channel;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.api.feature.TabFeature;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.api.chat.IChatBaseComponent;
import me.neznamy.tab.api.chat.WrappedChatComponent;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.scoreboard.PacketScoreboard;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.features.nametags.NameTag;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutPlayerInfoStorage;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutPlayerInfoStorage.PlayerInfoDataStorage;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutScoreboardDisplayObjectiveStorage;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutScoreboardObjectiveStorage;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutScoreboardTeamStorage;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.features.injection.NettyPipelineInjector;
import me.neznamy.tab.shared.features.sorting.Sorting;
import org.jetbrains.annotations.NotNull;
import java.util.*;
import java.util.stream.Collectors;
@@ -33,135 +31,123 @@ public class BukkitPipelineInjector extends NettyPipelineInjector {
}
@Override
protected Channel getChannel(TabPlayer player) {
protected Channel getChannel(@NotNull TabPlayer player) {
BukkitTabPlayer bukkit = (BukkitTabPlayer) player;
NMSStorage nms = NMSStorage.getInstance();
try {
if (nms.CHANNEL != null) return (Channel) nms.CHANNEL.get(nms.NETWORK_MANAGER.get(bukkit.getPlayerConnection()));
} catch (final IllegalAccessException exception) {
} catch (IllegalAccessException exception) {
TAB.getInstance().getErrorManager().printError("Failed to get channel of " + bukkit.getName(), exception);
}
return null;
}
@Override
public void onDisplayObjective(TabPlayer player, Object packet) throws IllegalAccessException {
@SneakyThrows
public void onDisplayObjective(@NotNull TabPlayer player, @NotNull Object packet) {
TAB.getInstance().getFeatureManager().onDisplayObjective(player,
PacketPlayOutScoreboardDisplayObjectiveStorage.POSITION.getInt(packet),
(String) PacketPlayOutScoreboardDisplayObjectiveStorage.OBJECTIVE_NAME.get(packet));
PacketScoreboard.DisplayObjective_POSITION.getInt(packet),
(String) PacketScoreboard.DisplayObjective_OBJECTIVE_NAME.get(packet));
}
@Override
public void onObjective(TabPlayer player, Object packet) throws IllegalAccessException {
@SneakyThrows
public void onObjective(@NotNull TabPlayer player, @NotNull Object packet) {
TAB.getInstance().getFeatureManager().onObjective(player,
PacketPlayOutScoreboardObjectiveStorage.METHOD.getInt(packet),
(String) PacketPlayOutScoreboardObjectiveStorage.OBJECTIVE_NAME.get(packet));
PacketScoreboard.Objective_METHOD.getInt(packet),
(String) PacketScoreboard.Objective_OBJECTIVE_NAME.get(packet));
}
@Override
public boolean isDisplayObjective(Object packet) {
return PacketPlayOutScoreboardDisplayObjectiveStorage.CLASS.isInstance(packet);
public boolean isDisplayObjective(@NotNull Object packet) {
return PacketScoreboard.DisplayObjectiveClass.isInstance(packet);
}
@Override
public boolean isObjective(Object packet) {
return PacketPlayOutScoreboardObjectiveStorage.CLASS.isInstance(packet);
public boolean isObjective(@NotNull Object packet) {
return PacketScoreboard.ObjectivePacketClass.isInstance(packet);
}
@Override
public boolean isTeam(Object packet) {
return PacketPlayOutScoreboardTeamStorage.CLASS.isInstance(packet);
public boolean isTeam(@NotNull Object packet) {
return PacketScoreboard.TeamPacketClass.isInstance(packet);
}
@Override
public boolean isPlayerInfo(Object packet) {
return PacketPlayOutPlayerInfoStorage.CLASS.isInstance(packet);
public boolean isPlayerInfo(@NotNull Object packet) {
return BukkitTabList.PacketPlayOutPlayerInfoClass.isInstance(packet);
}
@Override
public boolean isLogin(Object packet) {
return false;
}
@Override
public void onPlayerInfo(TabPlayer receiver, Object packet) throws ReflectiveOperationException {
@SneakyThrows
public void onPlayerInfo(@NotNull TabPlayer receiver, @NotNull Object packet) {
NMSStorage nms = NMSStorage.getInstance();
List<String> actions;
if (PacketPlayOutPlayerInfoStorage.ClientboundPlayerInfoRemovePacket != null) {
if (BukkitTabList.ClientboundPlayerInfoRemovePacket != null) {
//1.19.3+
actions = ((EnumSet<?>)PacketPlayOutPlayerInfoStorage.ACTION.get(packet)).stream().map(Enum::name).collect(Collectors.toList());
actions = ((EnumSet<?>)BukkitTabList.ACTION.get(packet)).stream().map(Enum::name).collect(Collectors.toList());
} else {
//1.19.2-
actions = Collections.singletonList(PacketPlayOutPlayerInfoStorage.ACTION.get(packet).toString());
actions = Collections.singletonList(BukkitTabList.ACTION.get(packet).toString());
}
List<Object> updatedList = new ArrayList<>();
for (Object nmsData : (List<?>) PacketPlayOutPlayerInfoStorage.PLAYERS.get(packet)) {
GameProfile profile = (GameProfile) PlayerInfoDataStorage.PlayerInfoData_getProfile.invoke(nmsData);
int gameMode = 0;
int latency = 0;
IChatBaseComponent displayName = null;
if (actions.contains("UPDATE_GAME_MODE") || actions.contains("ADD_PLAYER")) {
gameMode = PacketPlayOutPlayerInfoStorage.gameMode2Int(PlayerInfoDataStorage.PlayerInfoData_GameMode.get(nmsData));
gameMode = TAB.getInstance().getFeatureManager().onGameModeChange(receiver, profile.getId(), gameMode);
if (!nms.is1_19_3Plus()) PlayerInfoDataStorage.PlayerInfoData_GameMode.set(nmsData, PacketPlayOutPlayerInfoStorage.int2GameMode(gameMode));
}
if (actions.contains("UPDATE_LATENCY") || actions.contains("ADD_PLAYER")) {
latency = PlayerInfoDataStorage.PlayerInfoData_Latency.getInt(nmsData);
latency = TAB.getInstance().getFeatureManager().onLatencyChange(receiver, profile.getId(), latency);
if (!nms.is1_19_3Plus()) PlayerInfoDataStorage.PlayerInfoData_Latency.set(nmsData, latency);
}
if (actions.contains("UPDATE_DISPLAY_NAME") || actions.contains("ADD_PLAYER")) {
Object nmsComponent = PlayerInfoDataStorage.PlayerInfoData_DisplayName.get(nmsData);
displayName = nmsComponent == null ? null : new WrappedChatComponent(nmsComponent);
displayName = TAB.getInstance().getFeatureManager().onDisplayNameChange(receiver, profile.getId(), displayName);
if (!nms.is1_19_3Plus()) PlayerInfoDataStorage.PlayerInfoData_DisplayName.set(nmsData, nms.toNMSComponent(displayName, receiver.getVersion()));
}
if (actions.contains("ADD_PLAYER")) {
TAB.getInstance().getFeatureManager().onEntryAdd(receiver, profile.getId(), profile.getName());
for (Object nmsData : (List<?>) BukkitTabList.PLAYERS.get(packet)) {
GameProfile profile = (GameProfile) BukkitTabList.PlayerInfoData_getProfile.invoke(nmsData);
Object displayName = null;
if (actions.contains(TabList.Action.UPDATE_DISPLAY_NAME.name()) || actions.contains(TabList.Action.ADD_PLAYER.name())) {
displayName = BukkitTabList.PlayerInfoData_DisplayName.get(nmsData);
IChatBaseComponent newDisplayName = TAB.getInstance().getFeatureManager().onDisplayNameChange(receiver, profile.getId());
if (newDisplayName != null) displayName = nms.toNMSComponent(newDisplayName, receiver.getVersion());
if (!nms.is1_19_3Plus()) BukkitTabList.PlayerInfoData_DisplayName.set(nmsData, displayName);
}
if (nms.is1_19_3Plus()) {
// 1.19.3 is using records, which do not allow changing final fields, need to rewrite the list entirely
boolean listed = PlayerInfoDataStorage.PlayerInfoData_Listed.getBoolean(nmsData);
Object chatSession = PlayerInfoDataStorage.PlayerInfoData_RemoteChatSession.get(nmsData);
updatedList.add(PlayerInfoDataStorage.newPlayerInfoData.newInstance(
updatedList.add(BukkitTabList.newPlayerInfoData.newInstance(
profile.getId(),
profile,
listed,
latency,
PacketPlayOutPlayerInfoStorage.int2GameMode(gameMode),
nms.toNMSComponent(displayName, receiver.getVersion()),
chatSession));
BukkitTabList.PlayerInfoData_Listed.getBoolean(nmsData),
BukkitTabList.PlayerInfoData_Latency.getInt(nmsData),
BukkitTabList.PlayerInfoData_GameMode.get(nmsData),
displayName,
BukkitTabList.PlayerInfoData_RemoteChatSession.get(nmsData)));
}
}
if (nms.is1_19_3Plus()) {
PacketPlayOutPlayerInfoStorage.PLAYERS.set(packet, updatedList);
BukkitTabList.PLAYERS.set(packet, updatedList);
}
}
@SuppressWarnings("unchecked")
@Override
public void modifyPlayers(Object packetPlayOutScoreboardTeam) throws ReflectiveOperationException {
int action = PacketPlayOutScoreboardTeamStorage.ACTION.getInt(packetPlayOutScoreboardTeam);
@SneakyThrows
public void modifyPlayers(@NotNull Object packetPlayOutScoreboardTeam) {
if (TAB.getInstance().getNameTagManager() == null) return;
int action = PacketScoreboard.TeamPacket_ACTION.getInt(packetPlayOutScoreboardTeam);
if (action == 1 || action == 2 || action == 4) return;
Collection<String> players = (Collection<String>) PacketPlayOutScoreboardTeamStorage.PLAYERS.get(packetPlayOutScoreboardTeam);
String teamName = (String) PacketPlayOutScoreboardTeamStorage.NAME.get(packetPlayOutScoreboardTeam);
Collection<String> players = (Collection<String>) PacketScoreboard.TeamPacket_PLAYERS.get(packetPlayOutScoreboardTeam);
String teamName = (String) PacketScoreboard.TeamPacket_NAME.get(packetPlayOutScoreboardTeam);
if (players == null) return;
//creating a new list to prevent NoSuchFieldException in minecraft packet encoder when a player is removed
Collection<String> newList = new ArrayList<>();
for (String entry : players) {
TabPlayer p = getPlayer(entry);
TabPlayer p = TAB.getInstance().getPlayer(entry);
if (p == null) {
newList.add(entry);
continue;
}
Sorting sorting = (Sorting) TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.SORTING);
if (!((TabFeature)TAB.getInstance().getTeamManager()).isDisabledPlayer(p) &&
!TAB.getInstance().getTeamManager().hasTeamHandlingPaused(p) && !teamName.equals(sorting.getShortTeamName(p))) {
logTeamOverride(teamName, p.getName(), sorting.getShortTeamName(p));
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);
}
}
PacketPlayOutScoreboardTeamStorage.PLAYERS.set(packetPlayOutScoreboardTeam, newList);
PacketScoreboard.TeamPacket_PLAYERS.set(packetPlayOutScoreboardTeam, newList);
}
}
@@ -1,26 +1,22 @@
package me.neznamy.tab.platforms.bukkit;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.api.placeholder.PlaceholderManager;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.placeholders.UniversalPlaceholderRegistry;
import net.milkbowl.vault.chat.Chat;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.RegisteredServiceProvider;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.UUID;
/**
* Bukkit registry to register bukkit-only and universal placeholders
*/
public class BukkitPlaceholderRegistry extends UniversalPlaceholderRegistry {
/** Vault Chat hook */
private Chat chat;
/** NMS server to get TPS from on spigot */
@@ -35,17 +31,10 @@ public class BukkitPlaceholderRegistry extends UniversalPlaceholderRegistry {
/** Detection for presence of Paper's MSPT getter */
private Method paperMspt;
/** Detection for presence of Purpur's AFK getter */
private Method purpurIsAfk;
/**
* Constructs new instance and loads hooks
*/
public BukkitPlaceholderRegistry() {
if (Bukkit.getPluginManager().isPluginEnabled(TabConstants.Plugin.VAULT)) {
RegisteredServiceProvider<Chat> rspChat = Bukkit.getServicesManager().getRegistration(Chat.class);
if (rspChat != null) chat = rspChat.getProvider();
}
try {
server = Bukkit.getServer().getClass().getMethod("getServer").invoke(Bukkit.getServer());
recentTps = server.getClass().getField("recentTps");
@@ -54,12 +43,15 @@ public class BukkitPlaceholderRegistry extends UniversalPlaceholderRegistry {
}
try { paperTps = Bukkit.class.getMethod("getTPS"); } catch (NoSuchMethodException ignored) {}
try { paperMspt = Bukkit.class.getMethod("getAverageTickTime"); } catch (NoSuchMethodException ignored) {}
try { purpurIsAfk = Player.class.getMethod("isAfk"); } catch (NoSuchMethodException ignored) {}
if (Bukkit.getPluginManager().isPluginEnabled("Vault")) {
RegisteredServiceProvider<Chat> rspChat = Bukkit.getServicesManager().getRegistration(Chat.class);
if (rspChat != null) chat = rspChat.getProvider();
}
}
@SuppressWarnings("deprecation")
@Override
public void registerPlaceholders(PlaceholderManager manager) {
public void registerPlaceholders(@NotNull PlaceholderManager manager) {
manager.registerPlayerPlaceholder(TabConstants.Placeholder.DISPLAY_NAME, 500, p -> ((Player) p.getPlayer()).getDisplayName());
if (paperTps != null) {
manager.registerServerPlaceholder(TabConstants.Placeholder.TPS, 1000, () -> formatTPS(Bukkit.getTPS()[0]));
@@ -77,29 +69,14 @@ public class BukkitPlaceholderRegistry extends UniversalPlaceholderRegistry {
if (paperMspt != null) {
manager.registerServerPlaceholder(TabConstants.Placeholder.MSPT, 1000, () -> format(Bukkit.getAverageTickTime()));
}
Plugin essentials = Bukkit.getPluginManager().getPlugin(TabConstants.Plugin.ESSENTIALS);
manager.registerPlayerPlaceholder(TabConstants.Placeholder.AFK, 500, p -> {
if (essentials != null) {
try {
Object user = essentials.getClass().getMethod("getUser", UUID.class).invoke(essentials, p.getUniqueId());
if ((boolean) user.getClass().getMethod("isAfk").invoke(user)) return true;
} catch (ReflectiveOperationException e) {
TAB.getInstance().getErrorManager().printError("Failed to get AFK status of " + p.getName() + " using Essentials", e);
}
}
return purpurIsAfk != null && ((Player)p.getPlayer()).isAfk();
});
// Removed placeholder, keeping the implementation to avoid placeholder breaking for users on update
manager.registerPlayerPlaceholder(TabConstants.Placeholder.ESSENTIALS_NICK, -1, TabPlayer::getName);
if (chat != null) {
manager.registerPlayerPlaceholder(TabConstants.Placeholder.VAULT_PREFIX, 1000, p -> chat.getPlayerPrefix((Player) p.getPlayer()));
manager.registerPlayerPlaceholder(TabConstants.Placeholder.VAULT_SUFFIX, 1000, p -> chat.getPlayerSuffix((Player) p.getPlayer()));
} else {
manager.registerServerPlaceholder(TabConstants.Placeholder.VAULT_PREFIX, -1, () -> "");
manager.registerServerPlaceholder(TabConstants.Placeholder.VAULT_SUFFIX, -1, () -> "");
}
manager.registerPlayerPlaceholder(TabConstants.Placeholder.HEALTH, 100, p -> (int) Math.ceil(((Player) p.getPlayer()).getHealth()));
if (chat != null) {
manager.registerPlayerPlaceholder("%vault-prefix%", 1000, p -> chat.getPlayerPrefix((Player) p.getPlayer()));
manager.registerPlayerPlaceholder("%vault-suffix%", 1000, p -> chat.getPlayerSuffix((Player) p.getPlayer()));
} else {
manager.registerServerPlaceholder("%vault-prefix%", -1, () -> "");
manager.registerServerPlaceholder("%vault-suffix%", -1, () -> "");
}
super.registerPlaceholders(manager);
}
}
@@ -1,235 +0,0 @@
package me.neznamy.tab.platforms.bukkit;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import me.clip.placeholderapi.PlaceholderAPI;
import me.neznamy.tab.api.ProtocolVersion;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.api.feature.TabFeature;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.api.chat.EnumChatFormat;
import me.neznamy.tab.api.chat.rgb.RGBUtils;
import me.neznamy.tab.api.util.ReflectionUtils;
import me.neznamy.tab.platforms.bukkit.features.BukkitTabExpansion;
import me.neznamy.tab.platforms.bukkit.features.PerWorldPlayerList;
import me.neznamy.tab.platforms.bukkit.features.PetFix;
import me.neznamy.tab.platforms.bukkit.features.WitherBossBar;
import me.neznamy.tab.platforms.bukkit.features.unlimitedtags.BukkitNameTagX;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import me.neznamy.tab.platforms.bukkit.permission.Vault;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.backend.BackendPlatform;
import me.neznamy.tab.shared.features.PlaceholderManagerImpl;
import me.neznamy.tab.shared.placeholders.expansion.EmptyTabExpansion;
import me.neznamy.tab.shared.placeholders.expansion.TabExpansion;
import me.neznamy.tab.shared.features.bossbar.BossBarManagerImpl;
import me.neznamy.tab.shared.features.nametags.NameTag;
import me.neznamy.tab.shared.permission.LuckPerms;
import me.neznamy.tab.shared.permission.None;
import me.neznamy.tab.shared.permission.PermissionPlugin;
import me.neznamy.tab.shared.placeholders.PlayerPlaceholderImpl;
import net.milkbowl.vault.permission.Permission;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.RegisteredServiceProvider;
import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
/**
* Implementation of Platform interface for Bukkit platform
*/
@RequiredArgsConstructor
public class BukkitPlatform extends BackendPlatform {
@Getter private final BukkitPipelineInjector pipelineInjector = NMSStorage.getInstance().getMinorVersion() >= 8 ? new BukkitPipelineInjector() : null;
/** Plugin instance for registering tasks and events */
private final JavaPlugin plugin;
/** Variables checking presence of other plugins to hook into */
private final boolean placeholderAPI = Bukkit.getPluginManager().isPluginEnabled(TabConstants.Plugin.PLACEHOLDER_API);
@Getter @Setter private boolean libsDisguisesEnabled = Bukkit.getPluginManager().isPluginEnabled(TabConstants.Plugin.LIBS_DISGUISES);
private final boolean viaVersion = ReflectionUtils.classExists("com.viaversion.viaversion.api.Via");
private final boolean protocolSupport = Bukkit.getPluginManager().isPluginEnabled(TabConstants.Plugin.PROTOCOL_SUPPORT);
@Override
public PermissionPlugin detectPermissionPlugin() {
if (Bukkit.getPluginManager().isPluginEnabled(TabConstants.Plugin.LUCKPERMS)) {
return new LuckPerms(getPluginVersion(TabConstants.Plugin.LUCKPERMS));
} else if (Bukkit.getPluginManager().isPluginEnabled(TabConstants.Plugin.VAULT)) {
RegisteredServiceProvider<Permission> provider = Bukkit.getServicesManager().getRegistration(Permission.class);
if (provider == null) return new None();
return new Vault(provider.getProvider(), getPluginVersion(TabConstants.Plugin.VAULT));
} else {
return new None();
}
}
public BossBarManagerImpl getLegacyBossBar() {
return new WitherBossBar(plugin);
}
@Override
public String getPluginVersion(String plugin) {
Plugin pl = Bukkit.getPluginManager().getPlugin(plugin);
return pl == null ? null : pl.getDescription().getVersion();
}
@Override
public void loadPlayers() {
for (Player p : getOnlinePlayers()) {
TAB.getInstance().addPlayer(new BukkitTabPlayer(p, getProtocolVersion(p)));
}
}
@Override
public void registerPlaceholders() {
new BukkitPlaceholderRegistry().registerPlaceholders(TAB.getInstance().getPlaceholderManager());
}
@Override
public NameTag getUnlimitedNametags() {
return new BukkitNameTagX(plugin);
}
@Override
public @NotNull TabExpansion getTabExpansion() {
if (placeholderAPI) {
BukkitTabExpansion expansion = new BukkitTabExpansion();
expansion.register();
return expansion;
}
return new EmptyTabExpansion();
}
@Override
public TabFeature getPetFix() {
return new PetFix();
}
@Override
public @Nullable TabFeature getPerWorldPlayerlist() {
return new PerWorldPlayerList(plugin);
}
/**
* Returns online players from Bukkit API
*
* @return online players from Bukkit API
*/
@SuppressWarnings("unchecked")
private Player[] getOnlinePlayers() {
try {
Object players = Bukkit.class.getMethod("getOnlinePlayers").invoke(null);
if (players instanceof Player[]) {
//1.7-
return (Player[]) players;
} else {
//1.8+
return ((Collection<Player>)players).toArray(new Player[0]);
}
} catch (ReflectiveOperationException e) {
TAB.getInstance().getErrorManager().printError("Failed to get online players", e);
return new Player[0];
}
}
@Override
public void registerUnknownPlaceholder(String identifier) {
PlaceholderManagerImpl pl = TAB.getInstance().getPlaceholderManager();
if (identifier.startsWith("%rel_")) {
//relational placeholder
TAB.getInstance().getPlaceholderManager().registerRelationalPlaceholder(identifier, pl.getRelationalRefresh(identifier), (viewer, target) ->
placeholderAPI ? PlaceholderAPI.setRelationalPlaceholders((Player) viewer.getPlayer(), (Player) target.getPlayer(), identifier) : identifier);
} else {
//normal placeholder
if (identifier.startsWith("%sync:")) {
int refresh = pl.getServerPlaceholderRefreshIntervals().getOrDefault(identifier,
pl.getPlayerPlaceholderRefreshIntervals().getOrDefault(identifier, pl.getDefaultRefresh()));
String syncedPlaceholder = "%" + identifier.substring(6, identifier.length()-1) + "%";
pl.registerPlaceholder(new PlayerPlaceholderImpl(identifier, refresh, null) {
@Override
public Object request(TabPlayer p) {
Bukkit.getScheduler().runTask(plugin, () -> {
long time = System.nanoTime();
updateValue(p, placeholderAPI ? PlaceholderAPI.setPlaceholders((Player) p.getPlayer(), syncedPlaceholder) : identifier);
TAB.getInstance().getCPUManager().addPlaceholderTime(getIdentifier(), System.nanoTime()-time);
});
return null;
}
});
return;
}
if (pl.getServerPlaceholderRefreshIntervals().containsKey(identifier)) {
TAB.getInstance().getPlaceholderManager().registerServerPlaceholder(identifier, pl.getServerPlaceholderRefreshIntervals().get(identifier), () ->
placeholderAPI ? PlaceholderAPI.setPlaceholders(null, identifier) : identifier);
} else {
int refresh = pl.getPlayerPlaceholderRefreshIntervals().getOrDefault(identifier, pl.getDefaultRefresh());
TAB.getInstance().getPlaceholderManager().registerPlayerPlaceholder(identifier, refresh, p ->
placeholderAPI ? PlaceholderAPI.setPlaceholders((Player) p.getPlayer(), identifier) : identifier);
}
}
}
/**
* Gets protocol version of requested player and returns it.
*
* @param player
* Player to get protocol version of
* @return protocol version of the player
*/
public int getProtocolVersion(Player player) {
if (protocolSupport) {
int version = getProtocolVersionPS(player);
//some PS versions return -1 on unsupported server versions instead of throwing exception
if (version != -1 && version < TAB.getInstance().getServerVersion().getNetworkId()) return version;
}
if (viaVersion) {
return ProtocolVersion.getPlayerVersionVia(player.getUniqueId(), player.getName());
}
return TAB.getInstance().getServerVersion().getNetworkId();
}
/**
* Returns protocol version of requested player using ProtocolSupport
*
* @param player
* Player to get protocol version of
* @return protocol version of the player using ProtocolSupport
*/
private int getProtocolVersionPS(Player player) {
try {
Object protocolVersion = Class.forName("protocolsupport.api.ProtocolSupportAPI").getMethod("getProtocolVersion", Player.class).invoke(null, player);
int version = (int) protocolVersion.getClass().getMethod("getId").invoke(protocolVersion);
TAB.getInstance().debug("ProtocolSupport returned protocol version " + version + " for " + player.getName() + " (online=" + player.isOnline() + ")");
return version;
} catch (ReflectiveOperationException e) {
TAB.getInstance().getErrorManager().printError(String.format("Failed to get protocol version of %s using ProtocolSupport", player.getName()), e);
return TAB.getInstance().getServerVersion().getNetworkId();
}
}
/**
* Sends console message using ConsoleCommandSender, due to
* Paper not translating colors correctly in Logger messages
* and to allow RGB (at least on Paper, doesn't work on spigot)
*
* @param message
* Message to send
* @param translateColors
* Whether color codes should be translated or not
*/
@Override
public void sendConsoleMessage(String message, boolean translateColors) {
Bukkit.getConsoleSender().sendMessage("[TAB] " + (translateColors ?
EnumChatFormat.color(RGBUtils.getInstance().convertToBukkitFormat(message,
TAB.getInstance().getServerVersion().getMinorVersion() >= 16))
: message));
}
}
@@ -1,96 +0,0 @@
package me.neznamy.tab.platforms.bukkit;
import lombok.NonNull;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutScoreboardDisplayObjectiveStorage;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutScoreboardObjectiveStorage;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutScoreboardScoreStorage;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutScoreboardTeamStorage;
import me.neznamy.tab.shared.TabScoreboard;
import java.util.Collection;
/**
* Scoreboard implementation for Bukkit, which uses packets
* to send scoreboards. While md_5 keeps saying that you don't
* need packets for scoreboards in every single spigot update,
* this is not true. It may seem that way at first, but after
* investigating, fatal problems are found:<p>
* #1 - Limitations on legacy versions are forced in the API.
* While this may not seem like a problem, it enforces those
* limits even for 1.13+ players (if using ViaVersion).<p>
* #2 - Modern versions no longer have any limits, but md_5
* decided to add some random limits for absolutely no reason
* at all. Scoreboard title received a random 128 characters
* limit including color codes. Together with the almighty bukkit
* RGB format using 14 characters for 1 color code, this makes
* gradients just impossible to use. Team prefix/suffix also
* received a 64 characters limit (excluding color codes at least),
* however that might not be enough for displaying a line of text
* in sidebar, which would require splitting the text into prefix
* and suffix, which is just begging for bugs to be introduced.<p>
* #3 - Other plugins can decide to put players into their own
* scoreboard, automatically destroying all visuals made by the
* plugin. They might also put all players into the same scoreboard,
* making per-player view of teams, especially sidebar not working.<p>
* #4 - API made scoreboards get saved into the world folder and then
* resent on join with everything that was ever registered to it,
* which is very poor performance-wise, especially since it does
* that in the main server thread. It might also send conflicting
* information, such as putting a player into team they were on
* previously, but are not supposed to be anymore. Removing the plugin
* would cause all visuals to still be present on the server,
* requiring owners to properly clear scoreboards, which is not something
* everyone knows how to do.
*/
public class BukkitScoreboard extends TabScoreboard {
public BukkitScoreboard(TabPlayer player) {
super(player);
}
@Override
public void setDisplaySlot(DisplaySlot slot, @NonNull String objective) {
player.sendPacket(PacketPlayOutScoreboardDisplayObjectiveStorage.buildSilent(slot.ordinal(), objective));
}
@Override
public void registerObjective0(@NonNull String objectiveName, @NonNull String title, boolean hearts) {
player.sendPacket(PacketPlayOutScoreboardObjectiveStorage.buildSilent(0, objectiveName, title, hearts, player.getVersion()));
}
@Override
public void unregisterObjective0(@NonNull String objectiveName) {
player.sendPacket(PacketPlayOutScoreboardObjectiveStorage.buildSilent(1, objectiveName, "", false, player.getVersion()));
}
@Override
public void updateObjective0(@NonNull String objectiveName, @NonNull String title, boolean hearts) {
player.sendPacket(PacketPlayOutScoreboardObjectiveStorage.buildSilent(2, objectiveName, title, hearts, player.getVersion()));
}
@Override
public void registerTeam0(@NonNull String name, String prefix, String suffix, String visibility, String collision, Collection<String> players, int options) {
player.sendPacket(PacketPlayOutScoreboardTeamStorage.register(name, prefix, suffix, visibility, collision, players, options, player.getVersion()));
}
@Override
public void unregisterTeam0(@NonNull String name) {
player.sendPacket(PacketPlayOutScoreboardTeamStorage.unregister(name));
}
@Override
public void updateTeam0(@NonNull String name, String prefix, String suffix, String visibility, String collision, int options) {
player.sendPacket(PacketPlayOutScoreboardTeamStorage.update(name, prefix, suffix, visibility, collision, options, player.getVersion()));
}
@Override
public void setScore0(@NonNull String objective, @NonNull String playerName, int score) {
player.sendPacket(PacketPlayOutScoreboardScoreStorage.change(objective, playerName, score));
}
@Override
public void removeScore0(@NonNull String objective, @NonNull String playerName) {
player.sendPacket(PacketPlayOutScoreboardScoreStorage.remove(objective, playerName));
}
}
@@ -1,24 +1,18 @@
package me.neznamy.tab.platforms.bukkit;
import me.neznamy.tab.api.ProtocolVersion;
import me.neznamy.tab.api.TabAPI;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.api.chat.EnumChatFormat;
import me.neznamy.tab.platforms.bukkit.platform.BukkitPlatform;
import me.neznamy.tab.platforms.bukkit.platform.FoliaPlatform;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.*;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.bstats.bukkit.Metrics;
import org.bstats.charts.SimplePie;
import org.bukkit.Bukkit;
import org.bukkit.command.*;
import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Callable;
/**
* Main class for Bukkit platform
@@ -34,21 +28,22 @@ public class BukkitTAB extends JavaPlugin {
Bukkit.getPluginManager().disablePlugin(this);
return;
}
BukkitPlatform platform = new BukkitPlatform(this);
TAB.setInstance(new TAB(platform, ProtocolVersion.fromFriendlyName(version), version + " (" + serverPackage + ")", getDataFolder(), getLogger()));
BukkitPlatform platform = ReflectionUtils.classExists("io.papermc.paper.threadedregions.RegionizedServer") ?
new FoliaPlatform(this) : new BukkitPlatform(this);
TAB.setInstance(new TAB(platform, ProtocolVersion.fromFriendlyName(version), getDataFolder()));
if (TAB.getInstance().getServerVersion() == ProtocolVersion.UNKNOWN_SERVER_VERSION) {
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.color("&c[TAB] Unknown server version: " + Bukkit.getBukkitVersion() + "! Plugin may not work correctly."));
}
Bukkit.getPluginManager().registerEvents(new BukkitEventListener(platform), this);
Bukkit.getPluginManager().registerEvents(new BukkitEventListener(), this);
TAB.getInstance().load();
Metrics metrics = new Metrics(this, 5304);
metrics.addCustomChart(new SimplePie(TabConstants.MetricsChart.UNLIMITED_NAME_TAG_MODE_ENABLED, () -> TAB.getInstance().getFeatureManager().isFeatureEnabled(TabConstants.Feature.UNLIMITED_NAME_TAGS) ? "Yes" : "No"));
metrics.addCustomChart(new SimplePie(TabConstants.MetricsChart.PLACEHOLDER_API, () -> Bukkit.getPluginManager().isPluginEnabled(TabConstants.Plugin.PLACEHOLDER_API) ? "Yes" : "No"));
metrics.addCustomChart(new SimplePie(TabConstants.MetricsChart.PERMISSION_SYSTEM, () -> TAB.getInstance().getGroupManager().getPlugin().getName()));
metrics.addCustomChart(new SimplePie(TabConstants.MetricsChart.PLACEHOLDER_API, () -> platform.isPlaceholderAPI() ? "Yes" : "No"));
metrics.addCustomChart(new SimplePie(TabConstants.MetricsChart.PERMISSION_SYSTEM, () -> TAB.getInstance().getGroupManager().getPermissionPlugin()));
metrics.addCustomChart(new SimplePie(TabConstants.MetricsChart.SERVER_VERSION, () -> "1." + TAB.getInstance().getServerVersion().getMinorVersion() + ".x"));
PluginCommand cmd = Bukkit.getPluginCommand("tab");
PluginCommand cmd = Bukkit.getPluginCommand(TabConstants.COMMAND_BACKEND);
if (cmd == null) return;
TABCommand command = new TABCommand();
BukkitTabCommand command = new BukkitTabCommand();
cmd.setExecutor(command);
cmd.setTabCompleter(command);
}
@@ -61,83 +56,29 @@ public class BukkitTAB extends JavaPlugin {
/**
* Initializes all used NMS classes, constructors, fields and methods.
* Returns {@code true} if everything went successfully and version is marked as compatible,
* {@code false} if anything went wrong or version is not marked as compatible.
* 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() {
List<String> supportedVersions = Arrays.asList(
"v1_5_R1", "v1_5_R2", "v1_5_R3", "v1_6_R1", "v1_6_R2", "v1_6_R3",
"v1_7_R1", "v1_7_R2", "v1_7_R3", "v1_7_R4", "v1_8_R1", "v1_8_R2", "v1_8_R3",
"v1_9_R1", "v1_9_R2", "v1_10_R1", "v1_11_R1", "v1_12_R1", "v1_13_R1", "v1_13_R2",
"v1_14_R1", "v1_15_R1", "v1_16_R1", "v1_16_R2", "v1_16_R3", "v1_17_R1", "v1_18_R1",
"v1_18_R2", "v1_19_R1", "v1_19_R2", "v1_19_R3");
String supportedVersionRange = "1.5 - 1.19.4";
String serverPackage = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3];
try {
long time = System.currentTimeMillis();
NMSStorage.setInstance(getNMSLoader());
if (supportedVersions.contains(serverPackage)) {
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.color("[TAB] Loaded NMS hook in " + (System.currentTimeMillis()-time) + "ms"));
return true;
String serverPackage = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3];
int minorVersion = Integer.parseInt(serverPackage.split("_")[1]);
NMSStorage.setInstance(minorVersion >= 17 ? new BukkitModernNMSStorage() : new BukkitLegacyNMSStorage());
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.color("[TAB] Loaded NMS hook in " + (System.currentTimeMillis()-time) + "ms"));
return true;
} catch (Exception ex) {
if (ProtocolVersion.fromFriendlyName(Bukkit.getBukkitVersion().split("-")[0]) == ProtocolVersion.UNKNOWN_SERVER_VERSION) {
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.color("&c[TAB] Your server version is not compatible. " +
"This plugin version was made for " + ProtocolVersion.values()[ProtocolVersion.values().length-1].getFriendlyName() +
" - " + ProtocolVersion.values()[3].getFriendlyName() + ". Disabling."));
} else {
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.color("&c[TAB] No compatibility issue was found, but this plugin version does not claim to support your server package (" + serverPackage + "). This jar has only been tested on " + supportedVersionRange + ". Disabling just to stay safe."));
}
} catch (IllegalStateException ex) {
if (supportedVersions.contains(serverPackage)) {
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.color("&c[TAB] Your server version is marked as compatible, but a compatibility issue was found. Please report this issue (include your server version & fork too)"));
} else {
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.color("&c[TAB] Your server version is completely unsupported. This plugin version only supports " + supportedVersionRange + ". Disabling."));
}
}
return false;
}
private NMSStorage getNMSLoader() {
List<Callable<NMSStorage>> loaders = new ArrayList<>();
loaders.add(BukkitLegacyNMSStorage::new);
loaders.add(BukkitModernNMSStorage::new);
loaders.add(MojangModernNMSStorage::new);
loaders.add(ThermosNMSStorage::new);
for (Callable<NMSStorage> loader : loaders) {
try {
return loader.call();
} catch (Exception ignored) {}
}
throw new IllegalStateException("Unsupported server version");
}
/**
* Command handler for /tab command
*/
private static class TABCommand implements CommandExecutor, TabCompleter {
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] args) {
if (TabAPI.getInstance().isPluginDisabled()) {
for (String message : TAB.getInstance().getDisabledCommand().execute(args, sender.hasPermission(TabConstants.Permission.COMMAND_RELOAD), sender.hasPermission(TabConstants.Permission.COMMAND_ALL))) {
sender.sendMessage(EnumChatFormat.color(message));
}
} else {
TabPlayer p = null;
if (sender instanceof Player) {
p = TAB.getInstance().getPlayer(((Player)sender).getUniqueId());
if (p == null) return true; //player not loaded correctly
}
TAB.getInstance().getCommand().execute(p, args);
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.color("&c[TAB] A compatibility issue " +
"with your server was found. Unless you are running some really weird server software, this is a bug."));
}
return false;
}
@Override
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, String[] args) {
TabPlayer p = null;
if (sender instanceof Player) {
p = TAB.getInstance().getPlayer(((Player)sender).getUniqueId());
if (p == null) return new ArrayList<>(); //player not loaded correctly
}
return TAB.getInstance().getCommand().complete(p, args);
}
}
}
@@ -0,0 +1,48 @@
package me.neznamy.tab.platforms.bukkit;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.platform.TabPlayer;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabCompleter;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
/**
* Command handler for /tab command
*/
public class BukkitTabCommand implements CommandExecutor, TabCompleter {
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] args) {
if (TAB.getInstance().isPluginDisabled()) {
for (String message : TAB.getInstance().getDisabledCommand().execute(args, sender.hasPermission(TabConstants.Permission.COMMAND_RELOAD), sender.hasPermission(TabConstants.Permission.COMMAND_ALL))) {
sender.sendMessage(EnumChatFormat.color(message));
}
} else {
TabPlayer p = null;
if (sender instanceof Player) {
p = TAB.getInstance().getPlayer(((Player)sender).getUniqueId());
if (p == null) return true; //player not loaded correctly
}
TAB.getInstance().getCommand().execute(p, args);
}
return false;
}
@Override
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, String[] args) {
TabPlayer p = null;
if (sender instanceof Player) {
p = TAB.getInstance().getPlayer(((Player)sender).getUniqueId());
if (p == null) return new ArrayList<>(); //player not loaded correctly
}
return TAB.getInstance().getCommand().complete(p, args);
}
}
@@ -1,14 +1,22 @@
package me.neznamy.tab.platforms.bukkit;
import lombok.NonNull;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.api.chat.IChatBaseComponent;
import me.neznamy.tab.api.tablist.TabListEntry;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutPlayerInfoStorage;
import me.neznamy.tab.shared.tablist.BulkUpdateTabList;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;
/**
* TabList which support modifying many entries at once
@@ -17,68 +25,177 @@ import java.util.stream.Collectors;
* 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 tablist
* below, because of the massive differences in tab list
* and packet fields.
*/
@RequiredArgsConstructor
public class BukkitTabList extends BulkUpdateTabList {
@SuppressWarnings({"unchecked", "rawtypes"})
public class BukkitTabList implements TabList {
// NMS Fields
public static Class<?> PacketPlayOutPlayerListHeaderFooterClass;
public static Constructor<?> newPacketPlayOutPlayerListHeaderFooter;
public static Field HEADER;
public static Field FOOTER;
public static Class<?> PacketPlayOutPlayerInfoClass;
public static Constructor<?> newPacketPlayOutPlayerInfo;
public static Field ACTION;
public static Field PLAYERS;
public static Class<Enum> EnumPlayerInfoActionClass;
public static Class<Enum> EnumGamemodeClass;
public static Class<?> ClientboundPlayerInfoRemovePacket;
public static Class<?> RemoteChatSession$Data;
public static Constructor<?> newClientboundPlayerInfoRemovePacket;
public static Class<?> PlayerInfoDataClass;
public static Constructor<?> newPlayerInfoData;
public static Method PlayerInfoData_getProfile;
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;
/** Player this TabList belongs to */
private final BukkitTabPlayer player;
@Override
public void removeEntries(@NonNull Collection<UUID> entries) {
if (PacketPlayOutPlayerInfoStorage.ClientboundPlayerInfoRemovePacket != null) {
//1.19.3+
try {
player.sendPacket(PacketPlayOutPlayerInfoStorage.newClientboundPlayerInfoRemovePacket.newInstance(new ArrayList<>(entries)));
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
public static void load(NMSStorage nms) throws NoSuchMethodException {
if (nms.getMinorVersion() >= 17) {
newPacketPlayOutPlayerListHeaderFooter = PacketPlayOutPlayerListHeaderFooterClass.getConstructor(nms.IChatBaseComponent, nms.IChatBaseComponent);
} else {
//1.19.2-
player.sendPacket(PacketPlayOutPlayerInfoStorage.createPacket("REMOVE_PLAYER",
entries.stream().map(id ->
new TabListEntry.Builder(id).build()).collect(Collectors.toList()),
player.getVersion())
);
newPacketPlayOutPlayerListHeaderFooter = PacketPlayOutPlayerListHeaderFooterClass.getConstructor();
HEADER = ReflectionUtils.getFields(PacketPlayOutPlayerListHeaderFooterClass, nms.IChatBaseComponent).get(0);
FOOTER = ReflectionUtils.getFields(PacketPlayOutPlayerListHeaderFooterClass, nms.IChatBaseComponent).get(1);
}
if (nms.is1_19_3Plus()) {
newClientboundPlayerInfoRemovePacket = ClientboundPlayerInfoRemovePacket.getConstructor(List.class);
newPacketPlayOutPlayerInfo = PacketPlayOutPlayerInfoClass.getConstructor(EnumSet.class, Collection.class);
ACTION = ReflectionUtils.getOnlyField(PacketPlayOutPlayerInfoClass, EnumSet.class);
} else {
newPacketPlayOutPlayerInfo = PacketPlayOutPlayerInfoClass.getConstructor(EnumPlayerInfoActionClass, Array.newInstance(nms.EntityPlayer, 0).getClass());
ACTION = ReflectionUtils.getOnlyField(PacketPlayOutPlayerInfoClass, EnumPlayerInfoActionClass);
}
PLAYERS = ReflectionUtils.getOnlyField(PacketPlayOutPlayerInfoClass, List.class);
newPlayerInfoData = ReflectionUtils.getOnlyConstructor(PlayerInfoDataClass);
PlayerInfoData_getProfile = ReflectionUtils.getOnlyMethod(PlayerInfoDataClass, GameProfile.class);
PlayerInfoData_Latency = ReflectionUtils.getOnlyField(PlayerInfoDataClass, int.class);
PlayerInfoData_GameMode = ReflectionUtils.getOnlyField(PlayerInfoDataClass, EnumGamemodeClass);
PlayerInfoData_DisplayName = ReflectionUtils.getOnlyField(PlayerInfoDataClass, nms.IChatBaseComponent);
if (nms.is1_19_3Plus()) {
PlayerInfoData_Listed = ReflectionUtils.getOnlyField(PlayerInfoDataClass, boolean.class);
PlayerInfoData_RemoteChatSession = ReflectionUtils.getOnlyField(PlayerInfoDataClass, RemoteChatSession$Data);
}
}
@Override
public void updateDisplayNames(@NonNull Map<UUID, IChatBaseComponent> entries) {
player.sendPacket(PacketPlayOutPlayerInfoStorage.createPacket("UPDATE_DISPLAY_NAME",
entries.entrySet().stream().map(entry ->
new TabListEntry.Builder(entry.getKey()).displayName(entry.getValue()).build()).collect(Collectors.toList()),
player.getVersion())
@SneakyThrows
public void removeEntry(@NotNull UUID entry) {
if (ClientboundPlayerInfoRemovePacket != null) {
//1.19.3+
player.sendPacket(newClientboundPlayerInfoRemovePacket.newInstance(Collections.singletonList(entry)));
} else {
//1.19.2-
player.sendPacket(createPacket(Action.REMOVE_PLAYER, new Entry.Builder(entry).build(), player.getVersion()));
}
}
@Override
public void updateDisplayName(@NotNull UUID entry, @Nullable IChatBaseComponent displayName) {
player.sendPacket(createPacket(Action.UPDATE_DISPLAY_NAME,
new Entry.Builder(entry).displayName(displayName).build(), player.getVersion())
);
}
@Override
public void updateLatencies(@NonNull Map<UUID, Integer> entries) {
player.sendPacket(PacketPlayOutPlayerInfoStorage.createPacket("UPDATE_LATENCY",
entries.entrySet().stream().map(entry ->
new TabListEntry.Builder(entry.getKey()).latency(entry.getValue()).build()).collect(Collectors.toList()),
player.getVersion())
public void updateLatency(@NotNull UUID entry, int latency) {
player.sendPacket(createPacket(Action.UPDATE_LATENCY,
new Entry.Builder(entry).latency(latency).build(), player.getVersion())
);
}
@Override
public void updateGameModes(@NonNull Map<UUID, Integer> entries) {
player.sendPacket(PacketPlayOutPlayerInfoStorage.createPacket("UPDATE_GAME_MODE",
entries.entrySet().stream().map(entry ->
new TabListEntry.Builder(entry.getKey()).gameMode(entry.getValue()).build()).collect(Collectors.toList()),
player.getVersion())
public void updateGameMode(@NotNull UUID entry, int gameMode) {
player.sendPacket(createPacket(Action.UPDATE_GAME_MODE,
new Entry.Builder(entry).gameMode(gameMode).build(), player.getVersion())
);
}
@Override
public void addEntries(@NonNull Collection<TabListEntry> entries) {
player.sendPacket(PacketPlayOutPlayerInfoStorage.createPacket("ADD_PLAYER",
entries.stream().map(entry ->
new TabListEntry(entry.getUniqueId(), entry.getName(), entry.getSkin(), entry.isListed(),
entry.getLatency(), entry.getGameMode(), entry.getDisplayName(), entry.getChatSession())).collect(Collectors.toList()),
player.getVersion())
);
public void addEntry(@NotNull Entry entry) {
player.sendPacket(createPacket(Action.ADD_PLAYER, entry, player.getVersion()));
}
@Override
@SneakyThrows
public void setPlayerListHeaderFooter(@NotNull IChatBaseComponent header, @NotNull IChatBaseComponent footer) {
if (PacketPlayOutPlayerListHeaderFooterClass == null) return;
NMSStorage nms = NMSStorage.getInstance();
Object packet;
if (nms.getMinorVersion() >= 17) {
packet = newPacketPlayOutPlayerListHeaderFooter.newInstance(
nms.toNMSComponent(header, player.getVersion()), nms.toNMSComponent(footer, player.getVersion()));
} else {
packet = newPacketPlayOutPlayerListHeaderFooter.newInstance();
HEADER.set(packet, nms.toNMSComponent(header, player.getVersion()));
FOOTER.set(packet, nms.toNMSComponent(footer, player.getVersion()));
}
player.sendPacket(packet);
}
@SneakyThrows
private Object createPacket(TabList.Action action, TabList.Entry entry, ProtocolVersion clientVersion) {
NMSStorage nms = NMSStorage.getInstance();
if (nms.getMinorVersion() < 8) return null;
Object packet;
List<Object> players = new ArrayList<>();
if (NMSStorage.getInstance().is1_19_3Plus()) {
EnumSet<?> actions;
if (action == TabList.Action.ADD_PLAYER) {
actions = EnumSet.allOf(EnumPlayerInfoActionClass);
} else {
actions = EnumSet.of(Enum.valueOf(EnumPlayerInfoActionClass, action.name()));
}
packet = newPacketPlayOutPlayerInfo.newInstance(actions, Collections.emptyList());
GameProfile profile = new GameProfile(entry.getUniqueId(), entry.getName());
if (entry.getSkin() != null) profile.getProperties().put(TabList.TEXTURES_PROPERTY,
new Property(TabList.TEXTURES_PROPERTY, entry.getSkin().getValue(), entry.getSkin().getSignature()));
players.add(newPlayerInfoData.newInstance(
entry.getUniqueId(),
profile,
true,
entry.getLatency(),
int2GameMode(entry.getGameMode()),
entry.getDisplayName() == null ? null : nms.toNMSComponent(entry.getDisplayName(), clientVersion),
null
));
} else {
packet = newPacketPlayOutPlayerInfo.newInstance(Enum.valueOf(EnumPlayerInfoActionClass, action.name()),
Array.newInstance(NMSStorage.getInstance().EntityPlayer, 0));
GameProfile profile = new GameProfile(entry.getUniqueId(), entry.getName());
if (entry.getSkin() != null) profile.getProperties().put(TabList.TEXTURES_PROPERTY,
new Property(TabList.TEXTURES_PROPERTY, entry.getSkin().getValue(), entry.getSkin().getSignature()));
List<Object> parameters = new ArrayList<>();
if (newPlayerInfoData.getParameterTypes()[0] == PacketPlayOutPlayerInfoClass) {
parameters.add(packet);
}
parameters.add(profile);
parameters.add(entry.getLatency());
parameters.add(int2GameMode(entry.getGameMode()));
parameters.add(entry.getDisplayName() == null ? null : nms.toNMSComponent(entry.getDisplayName(), clientVersion));
if (nms.getMinorVersion() >= 19) parameters.add(null);
players.add(newPlayerInfoData.newInstance(parameters.toArray()));
}
PLAYERS.set(packet, players);
return packet;
}
private Object int2GameMode(int gameMode) {
switch (gameMode) {
case 1: return Enum.valueOf(EnumGamemodeClass, "CREATIVE");
case 2: return Enum.valueOf(EnumGamemodeClass, "ADVENTURE");
case 3: return Enum.valueOf(EnumGamemodeClass, "SPECTATOR");
default: return Enum.valueOf(EnumGamemodeClass, "SURVIVAL");
}
}
}
@@ -3,30 +3,31 @@ package me.neznamy.tab.platforms.bukkit;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
import lombok.Getter;
import lombok.NonNull;
import me.neznamy.tab.api.BossBarHandler;
import me.neznamy.tab.api.Scoreboard;
import me.neznamy.tab.api.chat.IChatBaseComponent;
import me.neznamy.tab.api.tablist.Skin;
import me.neznamy.tab.api.tablist.TabList;
import me.neznamy.tab.api.util.ComponentCache;
import me.neznamy.tab.api.util.ReflectionUtils;
import me.neznamy.tab.platforms.bukkit.bossbar.BukkitBossBar1_8;
import me.neznamy.tab.platforms.bukkit.bossbar.BukkitBossBar1_9;
import me.neznamy.tab.platforms.bukkit.bossbar.BukkitBossBarVia;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.platform.BukkitPlatform;
import me.neznamy.tab.platforms.bukkit.scoreboard.PacketScoreboard;
import me.neznamy.tab.shared.chat.rgb.RGBUtils;
import me.neznamy.tab.shared.hook.ViaVersionHook;
import me.neznamy.tab.shared.platform.bossbar.BossBar;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.platforms.bukkit.bossbar.EntityBossBar;
import me.neznamy.tab.platforms.bukkit.bossbar.BukkitBossBar;
import me.neznamy.tab.platforms.bukkit.bossbar.ViaBossBar;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.*;
import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.backend.BackendTabPlayer;
import me.neznamy.tab.shared.backend.EntityData;
import me.neznamy.tab.shared.backend.Location;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.chat.ComponentSerializer;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.metadata.MetadataValue;
import org.bukkit.potion.PotionEffectType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.UUID;
@@ -35,77 +36,61 @@ import java.util.UUID;
* TabPlayer implementation for Bukkit platform
*/
@SuppressWarnings("deprecation")
@Getter
public class BukkitTabPlayer extends BackendTabPlayer {
/** Spigot check */
private static final boolean spigot = ReflectionUtils.classExists("org.bukkit.entity.Player$Spigot");
/** Component cache to save CPU when creating components */
private static final ComponentCache<IChatBaseComponent, BaseComponent[]> componentCache = new ComponentCache<>(10000,
(component, clientVersion) -> ComponentSerializer.parse(component.toString(clientVersion)));
/** Player's NMS handle (EntityPlayer), preloading for speed */
private Object handle;
private final Object handle;
/** Player's connection for sending packets, preloading for speed */
@Getter private Object playerConnection;
private final Object playerConnection;
@Getter private final Scoreboard scoreboard = new BukkitScoreboard(this);
@Getter private final TabList tabList = new BukkitTabList(this);
@Getter private final BossBarHandler bossBarHandler = TAB.getInstance().getServerVersion().getMinorVersion() >= 9 ?
new BukkitBossBar1_9(this) : getVersion().getMinorVersion() >= 9 ? new BukkitBossBarVia(this) : new BukkitBossBar1_8(this);
private final Scoreboard<BukkitTabPlayer> scoreboard = new PacketScoreboard(this);
private final TabList tabList = new BukkitTabList(this);
private final BossBar bossBar = TAB.getInstance().getServerVersion().getMinorVersion() >= 9 ?
new BukkitBossBar(this) : getVersion().getMinorVersion() >= 9 ? new ViaBossBar(this) : new EntityBossBar(this);
/**
* Constructs new instance with given bukkit player and protocol version
*
* @param p
* bukkit player
* @param protocolVersion
* Player's protocol network id
*/
public BukkitTabPlayer(Player p, int protocolVersion) {
super(p, p.getUniqueId(), p.getName(), TAB.getInstance().getConfiguration().getServerName(), p.getWorld().getName(), protocolVersion);
try {
handle = NMSStorage.getInstance().getHandle.invoke(player);
playerConnection = NMSStorage.getInstance().PLAYER_CONNECTION.get(handle);
} catch (ReflectiveOperationException e) {
TAB.getInstance().getErrorManager().printError("Failed to get playerConnection of " + p.getName(), e);
}
@SneakyThrows
public BukkitTabPlayer(Player p) {
super(p, p.getUniqueId(), p.getName(), TAB.getInstance().getConfiguration().getServerName(),
p.getWorld().getName(), ViaVersionHook.getInstance().getPlayerVersion(p.getUniqueId(), p.getName()));
handle = NMSStorage.getInstance().getHandle.invoke(player);
playerConnection = NMSStorage.getInstance().PLAYER_CONNECTION.get(handle);
}
@Override
public boolean hasPermission(@NonNull String permission) {
public boolean hasPermission(@NotNull String permission) {
return getPlayer().hasPermission(permission);
}
@Override
public int getPing() {
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 17) {
return getPlayer().getPing();
}
try {
int ping = NMSStorage.getInstance().PING.getInt(handle);
if (ping > 10000 || ping < 0) ping = -1;
return ping;
return NMSStorage.getInstance().PING.getInt(handle);
} catch (IllegalAccessException e) {
return -1;
}
}
@Override
public void sendPacket(Object nmsPacket) {
@SneakyThrows
public void sendPacket(@Nullable Object nmsPacket) {
if (nmsPacket == null || !getPlayer().isOnline()) return;
try {
NMSStorage.getInstance().sendPacket.invoke(playerConnection, nmsPacket);
} catch (ReflectiveOperationException e) {
TAB.getInstance().getErrorManager().printError("An error occurred when sending " + nmsPacket.getClass().getSimpleName(), e);
}
NMSStorage.getInstance().sendPacket.invoke(playerConnection, nmsPacket);
}
@Override
public void sendMessage(IChatBaseComponent message) {
if (spigot) {
getPlayer().spigot().sendMessage(componentCache.get(message, version));
} else {
getPlayer().sendMessage(message.toLegacyText());
}
public void sendMessage(@NotNull IChatBaseComponent message) {
getPlayer().sendMessage(RGBUtils.getInstance().convertToBukkitFormat(message.toFlatText(),
getVersion().getMinorVersion() >= 16 && TAB.getInstance().getServerVersion().getMinorVersion() >= 16));
}
@Override
@@ -127,20 +112,16 @@ public class BukkitTabPlayer extends BackendTabPlayer {
}
@Override
public Skin getSkin() {
try {
Collection<Property> col = ((GameProfile)NMSStorage.getInstance().getProfile.invoke(handle)).getProperties().get(TabList.TEXTURES_PROPERTY);
if (col.isEmpty()) return null; //offline mode
Property property = col.iterator().next();
return new Skin(property.getValue(), property.getSignature());
} catch (ReflectiveOperationException e) {
TAB.getInstance().getErrorManager().printError("Failed to get skin of " + getName(), e);
return null;
}
@SneakyThrows
public TabList.Skin getSkin() {
Collection<Property> col = ((GameProfile)NMSStorage.getInstance().getProfile.invoke(handle)).getProperties().get(TabList.TEXTURES_PROPERTY);
if (col.isEmpty()) return null; //offline mode
Property property = col.iterator().next();
return new TabList.Skin(property.getValue(), property.getSignature());
}
@Override
public Player getPlayer() {
public @NotNull Player getPlayer() {
return (Player) player;
}
@@ -160,75 +141,39 @@ public class BukkitTabPlayer extends BackendTabPlayer {
}
@Override
public void setPlayerListHeaderFooter(@NonNull IChatBaseComponent header, @NonNull IChatBaseComponent footer) {
// Method was added to Bukkit API in 1.13.1, however despite that it's just a String one
// Using it would cause high CPU usage and massive memory allocations on RGB & animations
// Send packet instead for performance & older server version support
/*if (TAB.getInstance().getServerVersion().getNetworkId() >= ProtocolVersion.V1_13_1.getNetworkId()) {
String bukkitHeader = RGBUtils.getInstance().convertToBukkitFormat(header.toFlatText(),
getVersion().getMinorVersion() >= 16 && TAB.getInstance().getServerVersion().getMinorVersion() >= 16);
String bukkitFooter = RGBUtils.getInstance().convertToBukkitFormat(footer.toFlatText(),
getVersion().getMinorVersion() >= 16 && TAB.getInstance().getServerVersion().getMinorVersion() >= 16);
getPlayer().setPlayerListHeaderFooter(bukkitHeader, bukkitFooter);
return;
}*/
try {
sendPacket(PacketPlayOutPlayerListHeaderFooterStorage.build(header, footer, version));
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
public void spawnEntity(int entityId, @NotNull UUID id, @NotNull Object entityType, @NotNull Location location, @NotNull EntityData data) {
sendPacket(PacketPlayOutSpawnEntityLivingStorage.build(entityId, id, entityType, location, data));
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 15) {
updateEntityMetadata(entityId, data);
}
}
@Override
public void spawnEntity(int entityId, UUID id, Object entityType, Location location, EntityData data) {
try {
sendPacket(PacketPlayOutSpawnEntityLivingStorage.build(entityId, id, entityType, location, data));
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 15) {
updateEntityMetadata(entityId, data);
}
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
@SneakyThrows
public void updateEntityMetadata(int entityId, @NotNull EntityData data) {
if (PacketPlayOutEntityMetadataStorage.CONSTRUCTOR.getParameterCount() == 2) {
//1.19.3+
sendPacket(PacketPlayOutEntityMetadataStorage.CONSTRUCTOR.newInstance(entityId, DataWatcher.packDirty.invoke(data.build())));
} else {
sendPacket(PacketPlayOutEntityMetadataStorage.CONSTRUCTOR.newInstance(entityId, data.build(), true));
}
}
@Override
public void updateEntityMetadata(int entityId, EntityData data) {
try {
if (PacketPlayOutEntityMetadataStorage.CONSTRUCTOR.getParameterCount() == 2) {
//1.19.3+
sendPacket(PacketPlayOutEntityMetadataStorage.CONSTRUCTOR.newInstance(entityId, DataWatcher.packDirty.invoke(data.build())));
} else {
sendPacket(PacketPlayOutEntityMetadataStorage.CONSTRUCTOR.newInstance(entityId, data.build(), true));
}
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
@Override
public void teleportEntity(int entityId, Location location) {
try {
sendPacket(PacketPlayOutEntityTeleportStorage.build(entityId, location));
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
public void teleportEntity(int entityId, @NotNull Location location) {
sendPacket(PacketPlayOutEntityTeleportStorage.build(entityId, location));
}
@Override
@SneakyThrows
public void destroyEntities(int... entities) {
try {
if (PacketPlayOutEntityDestroyStorage.CONSTRUCTOR.getParameterTypes()[0] != int.class) {
sendPacket(PacketPlayOutEntityDestroyStorage.CONSTRUCTOR.newInstance(new Object[]{entities}));
} else {
//1.17.0 Mojank
for (int entity : entities) {
sendPacket(PacketPlayOutEntityDestroyStorage.CONSTRUCTOR.newInstance(entity));
}
if (PacketPlayOutEntityDestroyStorage.CONSTRUCTOR.getParameterTypes()[0] != int.class) {
sendPacket(PacketPlayOutEntityDestroyStorage.CONSTRUCTOR.newInstance(new Object[]{entities}));
} else {
//1.17.0 Mojank
for (int entity : entities) {
sendPacket(PacketPlayOutEntityDestroyStorage.CONSTRUCTOR.newInstance(entity));
}
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
}
@@ -1,35 +1,36 @@
package me.neznamy.tab.platforms.bukkit.bossbar;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.api.BossBarHandler;
import me.neznamy.tab.api.chat.rgb.RGBUtils;
import me.neznamy.tab.api.bossbar.BarColor;
import me.neznamy.tab.api.bossbar.BarStyle;
import me.neznamy.tab.shared.platform.bossbar.BossBar;
import me.neznamy.tab.shared.chat.rgb.RGBUtils;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.TAB;
import org.bukkit.Bukkit;
import org.bukkit.boss.BossBar;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* Bossbar for 1.9+ servers where Bukkit API is used. If ViaVersion is used
* BossBar for 1.9+ servers where Bukkit API is used. If ViaVersion is used
* to allow 1.8 players, it will handle the entity and teleporting by itself.
*/
@RequiredArgsConstructor
public class BukkitBossBar1_9 implements BossBarHandler {
public class BukkitBossBar implements BossBar {
/** Player this handler belongs to */
private final BukkitTabPlayer player;
/** Bukkit BossBars the player can currently see */
private final Map<UUID, BossBar> bossBars = new HashMap<>();
private final Map<UUID, org.bukkit.boss.BossBar> bossBars = new HashMap<>();
@Override
public void create(@NonNull UUID id, @NonNull String title, float progress, me.neznamy.tab.api.bossbar.@NonNull BarColor color, me.neznamy.tab.api.bossbar.@NonNull BarStyle style) {
public void create(@NotNull UUID id, @NotNull String title, float progress, @NotNull BarColor color, @NotNull BarStyle style) {
if (bossBars.containsKey(id)) return;
BossBar bar = Bukkit.createBossBar(
org.bukkit.boss.BossBar bar = Bukkit.createBossBar(
RGBUtils.getInstance().convertToBukkitFormat(title,
player.getVersion().getMinorVersion() >= 16 && TAB.getInstance().getServerVersion().getMinorVersion() >= 16),
org.bukkit.boss.BarColor.valueOf(color.name()),
@@ -40,28 +41,28 @@ public class BukkitBossBar1_9 implements BossBarHandler {
}
@Override
public void update(@NonNull UUID id, @NonNull String title) {
public void update(@NotNull UUID id, @NotNull String title) {
bossBars.get(id).setTitle(RGBUtils.getInstance().convertToBukkitFormat(title,
player.getVersion().getMinorVersion() >= 16 && TAB.getInstance().getServerVersion().getMinorVersion() >= 16));
}
@Override
public void update(@NonNull UUID id, float progress) {
public void update(@NotNull UUID id, float progress) {
bossBars.get(id).setProgress(progress);
}
@Override
public void update(@NonNull UUID id, me.neznamy.tab.api.bossbar.@NonNull BarStyle style) {
public void update(@NotNull UUID id, @NotNull BarStyle style) {
bossBars.get(id).setStyle(org.bukkit.boss.BarStyle.valueOf(style.getBukkitName()));
}
@Override
public void update(@NonNull UUID id, me.neznamy.tab.api.bossbar.@NonNull BarColor color) {
public void update(@NotNull UUID id, @NotNull BarColor color) {
bossBars.get(id).setColor(org.bukkit.boss.BarColor.valueOf(color.name()));
}
@Override
public void remove(@NonNull UUID id) {
public void remove(@NotNull UUID id) {
bossBars.remove(id).removePlayer(player.getPlayer());
}
}
@@ -1,14 +1,14 @@
package me.neznamy.tab.platforms.bukkit.bossbar;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.api.BossBarHandler;
import me.neznamy.tab.shared.platform.bossbar.BossBar;
import me.neznamy.tab.api.bossbar.BarColor;
import me.neznamy.tab.api.bossbar.BarStyle;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
import me.neznamy.tab.shared.backend.Location;
import org.bukkit.entity.EntityType;
import org.jetbrains.annotations.NotNull;
import java.util.UUID;
@@ -18,13 +18,13 @@ import java.util.UUID;
* separately, as this class does not handle it.
*/
@RequiredArgsConstructor
public class BukkitBossBar1_8 implements BossBarHandler {
public class EntityBossBar implements BossBar {
/** Player this handler belongs to */
private final BukkitTabPlayer player;
@Override
public void create(@NonNull UUID id, @NonNull String title, float progress, @NonNull BarColor color, @NonNull BarStyle style) {
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;
if (health == 0) health = 1;
@@ -36,14 +36,14 @@ public class BukkitBossBar1_8 implements BossBarHandler {
}
@Override
public void update(@NonNull UUID id, @NonNull String title) {
public void update(@NotNull UUID id, @NotNull String title) {
DataWatcher w = new DataWatcher();
w.getHelper().setCustomName(title, player.getVersion());
player.updateEntityMetadata(id.hashCode(), w);
}
@Override
public void update(@NonNull UUID id, float progress) {
public void update(@NotNull UUID id, float progress) {
DataWatcher w = new DataWatcher();
float health = 300*progress;
if (health == 0) health = 1;
@@ -52,13 +52,13 @@ public class BukkitBossBar1_8 implements BossBarHandler {
}
@Override
public void update(@NonNull UUID id, @NonNull BarStyle style) {} // Added in 1.9
public void update(@NotNull UUID id, @NotNull BarStyle style) {/*Added in 1.9*/}
@Override
public void update(@NonNull UUID id, @NonNull BarColor color) {} // Added in 1.9
public void update(@NotNull UUID id, @NotNull BarColor color) {/*Added in 1.9*/}
@Override
public void remove(@NonNull UUID id) {
public void remove(@NotNull UUID id) {
player.destroyEntities(id.hashCode());
}
}
@@ -1,16 +1,15 @@
package me.neznamy.tab.platforms.bukkit.bossbar;
import com.viaversion.viaversion.api.Via;
import com.viaversion.viaversion.api.legacy.bossbar.BossBar;
import com.viaversion.viaversion.api.legacy.bossbar.BossColor;
import com.viaversion.viaversion.api.legacy.bossbar.BossStyle;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.api.BossBarHandler;
import me.neznamy.tab.shared.platform.bossbar.BossBar;
import me.neznamy.tab.api.bossbar.BarColor;
import me.neznamy.tab.api.bossbar.BarStyle;
import me.neznamy.tab.api.chat.rgb.RGBUtils;
import me.neznamy.tab.shared.chat.rgb.RGBUtils;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
import java.util.Map;
@@ -20,18 +19,18 @@ import java.util.UUID;
* Handler for 1.9+ players on 1.8 server using ViaVersion API.
*/
@RequiredArgsConstructor
public class BukkitBossBarVia implements BossBarHandler {
public class ViaBossBar implements BossBar {
/** Player this handler belongs to */
private final BukkitTabPlayer player;
/** ViaVersion BossBars this 1.9+ player can see on 1.8 server */
private final Map<UUID, BossBar> viaBossBars = new HashMap<>();
private final Map<UUID, com.viaversion.viaversion.api.legacy.bossbar.BossBar> viaBossBars = new HashMap<>();
@Override
public void create(@NonNull UUID id, @NonNull String title, float progress, @NonNull BarColor color, @NonNull BarStyle style) {
public void create(@NotNull UUID id, @NotNull String title, float progress, @NotNull BarColor color, @NotNull BarStyle style) {
if (viaBossBars.containsKey(id)) return;
BossBar bar = Via.getAPI().legacyAPI().createLegacyBossBar(
com.viaversion.viaversion.api.legacy.bossbar.BossBar bar = Via.getAPI().legacyAPI().createLegacyBossBar(
RGBUtils.getInstance().convertToBukkitFormat(title, player.getVersion().getMinorVersion() >= 16),
progress,
BossColor.valueOf(color.name()),
@@ -41,28 +40,28 @@ public class BukkitBossBarVia implements BossBarHandler {
}
@Override
public void update(@NonNull UUID id, @NonNull String title) {
public void update(@NotNull UUID id, @NotNull String title) {
viaBossBars.get(id).setTitle(RGBUtils.getInstance().convertToBukkitFormat(title,
player.getVersion().getMinorVersion() >= 16));
}
@Override
public void update(@NonNull UUID id, float progress) {
public void update(@NotNull UUID id, float progress) {
viaBossBars.get(id).setHealth(progress);
}
@Override
public void update(@NonNull UUID id, @NonNull BarStyle style) {
public void update(@NotNull UUID id, @NotNull BarStyle style) {
viaBossBars.get(id).setStyle(BossStyle.valueOf(style.getBukkitName()));
}
@Override
public void update(@NonNull UUID id, @NonNull BarColor color) {
public void update(@NotNull UUID id, @NotNull BarColor color) {
viaBossBars.get(id).setColor(BossColor.valueOf(color.name()));
}
@Override
public void remove(@NonNull UUID id) {
public void remove(@NotNull UUID id) {
viaBossBars.remove(id).removePlayer(player.getPlayer().getUniqueId());
}
}
@@ -1,10 +1,11 @@
package me.neznamy.tab.platforms.bukkit.features.unlimitedtags;
package me.neznamy.tab.platforms.bukkit.features;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.api.TabAPI;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.api.feature.PacketSendListener;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.platform.BukkitPlatform;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.features.types.PacketSendListener;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutEntityDestroyStorage;
@@ -18,8 +19,15 @@ import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.entity.Pose;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerRespawnEvent;
import org.bukkit.event.player.PlayerToggleSneakEvent;
import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collections;
import java.util.List;
@@ -28,29 +36,37 @@ import java.util.stream.Collectors;
/**
* The core class for unlimited NameTag mode on Bukkit
*/
@RequiredArgsConstructor
public class BukkitNameTagX extends BackendNameTagX implements PacketSendListener {
public class BukkitNameTagX extends BackendNameTagX implements Listener, PacketSendListener {
/** Reference to NMS storage for quick access */
private final NMSStorage nms = NMSStorage.getInstance();
/** Bukkit event listener */
private final EventListener eventListener = new EventListener(this);
public BukkitNameTagX(@NotNull JavaPlugin plugin) {
Bukkit.getPluginManager().registerEvents(this, plugin);
}
/** Plugin reference */
private final JavaPlugin plugin;
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onSneak(PlayerToggleSneakEvent e) {
TabPlayer p = TAB.getInstance().getPlayer(e.getPlayer().getUniqueId());
if (p == null || isPlayerDisabled(p)) return;
TAB.getInstance().getCPUManager().runMeasuredTask(featureName, TabConstants.CpuUsageCategory.PLAYER_SNEAK,
() -> getArmorStandManager(p).sneak(e.isSneaking()));
}
@Override
public void load() {
Bukkit.getPluginManager().registerEvents(eventListener, plugin);
super.load();
@EventHandler
public void onRespawn(PlayerRespawnEvent e) {
TabPlayer respawned = TAB.getInstance().getPlayer(e.getPlayer().getUniqueId());
if (respawned == null || isPlayerDisabled(respawned)) return;
TAB.getInstance().getCPUManager().runMeasuredTask(featureName, TabConstants.CpuUsageCategory.PLAYER_RESPAWN,
() -> getArmorStandManager(respawned).teleport());
}
@SuppressWarnings("unchecked")
@Override
public void onPacketSend(TabPlayer receiver, Object packet) throws ReflectiveOperationException {
@SneakyThrows
public void onPacketSend(@NotNull TabPlayer receiver, @NotNull Object packet) {
if (receiver.getVersion().getMinorVersion() < 8) return;
if (!receiver.isLoaded() || isDisabledPlayer(receiver) || getDisabledUnlimitedPlayers().contains(receiver)) return;
if (!receiver.isLoaded() || getDisableChecker().isDisabledPlayer(receiver) || getUnlimitedDisableChecker().isDisabledPlayer(receiver)) return;
if (nms.PacketPlayOutEntity.isInstance(packet) && !nms.PacketPlayOutEntityLook.isInstance(packet)) { //ignoring head rotation only packets
packetListener.onEntityMove((BackendTabPlayer) receiver, nms.PacketPlayOutEntity_ENTITYID.getInt(packet));
} else if (PacketPlayOutEntityTeleportStorage.CLASS.isInstance(packet)) {
@@ -73,32 +89,32 @@ public class BukkitNameTagX extends BackendNameTagX implements PacketSendListene
}
@Override
public double getDistance(TabPlayer player1, TabPlayer player2) {
public double getDistance(@NotNull TabPlayer player1, @NotNull TabPlayer player2) {
Location loc1 = ((Player) player1.getPlayer()).getLocation();
Location loc2 = ((Player) player2.getPlayer()).getLocation();
return Math.sqrt(Math.pow(loc1.getX()-loc2.getX(), 2) + Math.pow(loc1.getZ()-loc2.getZ(), 2));
}
@Override
public boolean areInSameWorld(TabPlayer player1, TabPlayer player2) {
return ((Player) player1.getPlayer()).getWorld() == ((Player) player1.getPlayer()).getWorld();
public boolean areInSameWorld(@NotNull TabPlayer player1, @NotNull TabPlayer player2) {
return ((Player) player1.getPlayer()).getWorld() == ((Player) player2.getPlayer()).getWorld();
}
@Override
public boolean canSee(TabPlayer viewer, TabPlayer target) {
return ((Player)viewer.getPlayer()).canSee((Player)target.getPlayer());
public boolean canSee(@NotNull TabPlayer viewer, @NotNull TabPlayer target) {
return ((Player)viewer.getPlayer()).canSee((Player) target.getPlayer());
}
@Override
public void unregisterListener() {
HandlerList.unregisterAll(eventListener);
HandlerList.unregisterAll(this);
}
@SuppressWarnings("deprecation")
@Override
public List<Integer> getPassengers(Object entity) {
public @NotNull List<Integer> getPassengers(@NotNull Object entity) {
Entity vehicle = (Entity) entity;
if (TabAPI.getInstance().getServerVersion().getMinorVersion() >= 11) {
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 11) {
return vehicle.getPassengers().stream().map(Entity::getEntityId).collect(Collectors.toList());
} else {
if (vehicle.getPassenger() != null) {
@@ -110,73 +126,64 @@ public class BukkitNameTagX extends BackendNameTagX implements PacketSendListene
}
@Override
public void registerVehiclePlaceholder() {
TabAPI.getInstance().getPlaceholderManager().registerPlayerPlaceholder(TabConstants.Placeholder.VEHICLE, 100, p -> {
Entity v = ((Player)p.getPlayer()).getVehicle();
//There's a bug in Bukkit 1.19.3 throwing NPE on .toString(), use default toString implementation
return v == null ? "" : v.getClass().getName() + "@" + Integer.toHexString(v.hashCode());
});
}
@Override
public Object getVehicle(TabPlayer player) {
public @Nullable Object getVehicle(@NotNull TabPlayer player) {
return ((Player)player.getPlayer()).getVehicle();
}
@Override
public int getEntityId(Object entity) {
public int getEntityId(@NotNull Object entity) {
return ((Entity)entity).getEntityId();
}
@Override
public String getEntityType(Object entity) {
public @NotNull String getEntityType(@NotNull Object entity) {
return ((Entity) entity).getType().toString().toLowerCase();
}
@Override
public boolean isSneaking(TabPlayer player) {
public boolean isSneaking(@NotNull TabPlayer player) {
return ((Player)player.getPlayer()).isSneaking();
}
@Override
public boolean isSwimming(TabPlayer player) {
public boolean isSwimming(@NotNull TabPlayer player) {
Player p = (Player) player.getPlayer();
if (TabAPI.getInstance().getServerVersion().getMinorVersion() >= 14 && p.getPose() == Pose.SWIMMING) return true;
return TabAPI.getInstance().getServerVersion().getMinorVersion() == 13 && p.isSwimming();
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 14 && p.getPose() == Pose.SWIMMING) return true;
return TAB.getInstance().getServerVersion().getMinorVersion() == 13 && p.isSwimming();
}
@Override
public boolean isGliding(TabPlayer player) {
return TabAPI.getInstance().getServerVersion().getMinorVersion() >= 9 && ((Player)player.getPlayer()).isGliding();
public boolean isGliding(@NotNull TabPlayer player) {
return TAB.getInstance().getServerVersion().getMinorVersion() >= 9 && ((Player)player.getPlayer()).isGliding();
}
@Override
public boolean isSleeping(TabPlayer player) {
public boolean isSleeping(@NotNull TabPlayer player) {
return ((Player)player.getPlayer()).isSleeping();
}
@Override
public Object getArmorStandType() {
public @NotNull Object getArmorStandType() {
return EntityType.ARMOR_STAND;
}
@Override
public double getX(TabPlayer player) {
public double getX(@NotNull TabPlayer player) {
return ((Player)player.getPlayer()).getLocation().getX();
}
@Override
public double getY(Object entity) {
public double getY(@NotNull Object entity) {
return ((Entity)entity).getLocation().getY();
}
@Override
public double getZ(TabPlayer player) {
public double getZ(@NotNull TabPlayer player) {
return ((Player)player.getPlayer()).getLocation().getZ();
}
@Override
public EntityData createDataWatcher(TabPlayer viewer, byte flags, String displayName, boolean nameVisible) {
public EntityData createDataWatcher(@NotNull TabPlayer viewer, byte flags, @NotNull String displayName, boolean nameVisible) {
DataWatcher datawatcher = new DataWatcher();
datawatcher.getHelper().setEntityFlags(flags);
datawatcher.getHelper().setCustomName(displayName, viewer.getVersion());
@@ -184,4 +191,9 @@ public class BukkitNameTagX extends BackendNameTagX implements PacketSendListene
datawatcher.getHelper().setArmorStandFlags((byte)16);
return datawatcher;
}
@Override
public void runInEntityScheduler(Object entity, Runnable task) {
((BukkitPlatform)TAB.getInstance().getPlatform()).runEntityTask((Entity) entity, task);
}
}
@@ -3,13 +3,13 @@ package me.neznamy.tab.platforms.bukkit.features;
import lombok.Getter;
import me.clip.placeholderapi.PlaceholderAPI;
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
import me.neznamy.tab.api.TabAPI;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.placeholders.expansion.TabExpansion;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.HashMap;
import java.util.Map;
@@ -18,14 +18,15 @@ import java.util.WeakHashMap;
/**
* TAB's expansion for PlaceholderAPI
*/
@Getter
public class BukkitTabExpansion extends PlaceholderExpansion implements TabExpansion {
/** Map holding all values for all players for easy and high-performance access */
private final WeakHashMap<Player, Map<String, String>> values = new WeakHashMap<>();
@Getter private final String author = TabConstants.PLUGIN_AUTHOR;
@Getter private final String identifier = TabConstants.PLUGIN_ID;
@Getter private final String version = TabConstants.PLUGIN_VERSION;
private final String author = TabConstants.PLUGIN_AUTHOR;
private final String identifier = TabConstants.PLUGIN_ID;
private final String version = TabConstants.PLUGIN_VERSION;
@Override
public boolean persist() {
@@ -33,27 +34,27 @@ public class BukkitTabExpansion extends PlaceholderExpansion implements TabExpan
}
@Override
public String onPlaceholderRequest(Player player, @NotNull String identifier) {
public String onPlaceholderRequest(@Nullable Player player, @NotNull String identifier) {
if (identifier.startsWith("replace_")) {
String text = "%" + identifier.substring(8) + "%";
String textBefore;
do {
textBefore = text;
for (String placeholder : TAB.getInstance().getPlaceholderManager().detectPlaceholders(text)) {
text = text.replace(placeholder, TabAPI.getInstance().getPlaceholderManager().findReplacement(placeholder,
text = text.replace(placeholder, TAB.getInstance().getPlaceholderManager().findReplacement(placeholder,
PlaceholderAPI.setPlaceholders(player, placeholder)));
}
} while (!textBefore.equals(text));
return text;
}
if (identifier.startsWith("placeholder_")) {
TabAPI.getInstance().getPlaceholderManager().addUsedPlaceholder("%" + identifier.substring(12) + "%", TabAPI.getInstance().getPlaceholderManager());
TAB.getInstance().getPlaceholderManager().addUsedPlaceholder("%" + identifier.substring(12) + "%", TAB.getInstance().getPlaceholderManager());
}
return values.get(player).get(identifier);
}
@Override
public void setValue(TabPlayer player, String key, String value) {
public void setValue(@NotNull TabPlayer player, @NotNull String key, @NotNull String value) {
values.computeIfAbsent((Player) player.getPlayer(), p -> new HashMap<>()).put(key, value);
}
}
@@ -7,9 +7,8 @@ import java.util.Map.Entry;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.api.TabAPI;
import me.neznamy.tab.api.feature.Loadable;
import me.neznamy.tab.api.feature.UnLoadable;
import me.neznamy.tab.shared.features.types.Loadable;
import me.neznamy.tab.shared.features.types.UnLoadable;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
@@ -19,9 +18,10 @@ import org.bukkit.event.player.PlayerChangedWorldEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.plugin.java.JavaPlugin;
import me.neznamy.tab.api.feature.TabFeature;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.shared.features.types.TabFeature;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.TAB;
import org.jetbrains.annotations.NotNull;
/**
* Per-world-PlayerList feature handler
@@ -31,12 +31,12 @@ import me.neznamy.tab.shared.TAB;
public class PerWorldPlayerList extends TabFeature implements Listener, Loadable, UnLoadable {
/** Config options */
private final boolean allowBypass = TabAPI.getInstance().getConfig().getBoolean("per-world-playerlist.allow-bypass-permission", false);
private final List<String> ignoredWorlds = TabAPI.getInstance().getConfig().getStringList("per-world-playerlist.ignore-effect-in-worlds", Arrays.asList("ignoredworld", "build"));
private final Map<String, List<String>> sharedWorlds = TabAPI.getInstance().getConfig().getConfigurationSection("per-world-playerlist.shared-playerlist-world-groups");
private final boolean allowBypass = TAB.getInstance().getConfig().getBoolean("per-world-playerlist.allow-bypass-permission", false);
private final List<String> ignoredWorlds = TAB.getInstance().getConfig().getStringList("per-world-playerlist.ignore-effect-in-worlds", Arrays.asList("ignored_world", "build"));
private final Map<String, List<String>> sharedWorlds = TAB.getInstance().getConfig().getConfigurationSection("per-world-playerlist.shared-playerlist-world-groups");
/** Plugin reference*/
private final JavaPlugin plugin;
@NotNull private final JavaPlugin plugin;
@Getter private final String featureName = "Per world PlayerList";
@@ -60,14 +60,14 @@ public class PerWorldPlayerList extends TabFeature implements Listener, Loadable
public void onJoin(PlayerJoinEvent e) {
long time = System.nanoTime();
checkPlayer(e.getPlayer());
TAB.getInstance().getCPUManager().addTime(getFeatureName(), TabConstants.CpuUsageCategory.PLAYER_JOIN, System.nanoTime()-time);
TAB.getInstance().getCPUManager().addTime(featureName, TabConstants.CpuUsageCategory.PLAYER_JOIN, System.nanoTime()-time);
}
@EventHandler
public void onWorldChange(PlayerChangedWorldEvent e) {
long time = System.nanoTime();
checkPlayer(e.getPlayer());
TAB.getInstance().getCPUManager().addTime(getFeatureName(), TabConstants.CpuUsageCategory.WORLD_SWITCH, System.nanoTime()-time);
TAB.getInstance().getCPUManager().addTime(featureName, TabConstants.CpuUsageCategory.WORLD_SWITCH, System.nanoTime()-time);
}
/**
@@ -78,7 +78,7 @@ public class PerWorldPlayerList extends TabFeature implements Listener, Loadable
* @param p
* Player to update
*/
private void checkPlayer(Player p) {
private void checkPlayer(@NotNull Player p) {
for (Player all : Bukkit.getOnlinePlayers()) {
if (all == p) continue;
if (!shouldSee(p, all) && p.canSee(all)) p.hidePlayer(all);
@@ -96,7 +96,7 @@ public class PerWorldPlayerList extends TabFeature implements Listener, Loadable
* Target displayed in the TabList
* @return {@code true} if viewer should see target, {@code false} if not.
*/
private boolean shouldSee(Player viewer, Player target) {
private boolean shouldSee(@NotNull Player viewer, @NotNull Player target) {
if (target == viewer) return true;
if ((allowBypass && viewer.hasPermission(TabConstants.Permission.PER_WORLD_PLAYERLIST_BYPASS)) || ignoredWorlds.contains(viewer.getWorld().getName())) return true;
String viewerWorldGroup = viewer.getWorld().getName() + "-default"; //preventing unwanted behavior when some group is called exactly like a world
@@ -1,157 +0,0 @@
package me.neznamy.tab.platforms.bukkit.features;
import lombok.Getter;
import me.neznamy.tab.api.feature.PacketReceiveListener;
import me.neznamy.tab.api.feature.PacketSendListener;
import me.neznamy.tab.api.feature.TabFeature;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutEntityMetadataStorage;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutSpawnEntityLivingStorage;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherItem;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherObject;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import java.util.ConcurrentModificationException;
import java.util.List;
import java.util.Optional;
import java.util.WeakHashMap;
/**
* A feature to disable minecraft 1.9+ feature making tamed animals
* with custom names copy NameTag properties of their owner.
* This is achieved by listening to entity spawn (&lt;1.15) / entity metadata
* packets and removing owner field from the DataWatcher list.
* Since 1.16 this results in client sending entity use packet twice,
* so we must cancel the 2nd one to prevent double toggle.
*/
public class PetFix extends TabFeature implements PacketReceiveListener, PacketSendListener {
/** NMS Storage reference for quick access */
private final NMSStorage nms = NMSStorage.getInstance();
/** DataWatcher position of pet owner field */
private final int petOwnerPosition = getPetOwnerPosition();
/** Logger of last interacts to prevent feature not working on 1.16 */
private final WeakHashMap<TabPlayer, Long> lastInteractFix = new WeakHashMap<>();
/**
* Since 1.16, client sends interact packet twice for entities affected
* by removed owner field. Because of that, we need to cancel the duplicated
* packet to avoid double toggle and preventing the entity from getting
* its pose changed. The duplicated packet is usually sent instantly in the
* same millisecond, however, when installing ProtocolLib with MyPet, the delay
* is up to 3 ticks. When holding right-click on an entity, interact is sent every
* 200 milliseconds, which is the value we should not go above. Optimal value is
* therefore between 150 and 200 milliseconds.
*/
private static final int INTERACT_COOLDOWN = 160;
@Getter private final String featureName = "Pet name fix";
/**
* Returns position of pet owner field based on server version
*
* @return position of pet owner field based on server version
*/
private int getPetOwnerPosition() {
if (nms.getMinorVersion() >= 17) {
//1.17.x, 1.18.x, 1.19.x
return 18;
} else if (nms.getMinorVersion() >= 15) {
//1.15.x, 1.16.x
return 17;
} else if (nms.getMinorVersion() >= 14) {
//1.14.x
return 16;
} else if (nms.getMinorVersion() >= 10) {
//1.10.x - 1.13.x
return 14;
} else {
//1.9.x
return 13;
}
}
/**
* Cancels a packet if previous one arrived with no delay to prevent double toggle since 1.16
*
* @throws ReflectiveOperationException
* if thrown by reflective operation
*/
@Override
public boolean onPacketReceive(TabPlayer sender, Object packet) throws ReflectiveOperationException {
if (nms.PacketPlayInUseEntity.isInstance(packet) && isInteract(nms.PacketPlayInUseEntity_ACTION.get(packet))) {
if (System.currentTimeMillis() - lastInteractFix.getOrDefault(sender, 0L) < INTERACT_COOLDOWN) {
//last interact packet was sent right now, cancelling to prevent double-toggle due to this feature enabled
return true;
}
//this is the first packet, saving player so the next packet can be cancelled
lastInteractFix.put(sender, System.currentTimeMillis());
}
return false;
}
/**
* Checks if the provided entity use action is INTERACT or not.
*
* @param action
* Action to check
* @return {@code true} if action is INTERACT, {@code false} if not.
*/
private boolean isInteract(Object action) {
if (nms.getMinorVersion() >= 17) {
return nms.PacketPlayInUseEntity$d.isInstance(action);
} else {
return action.toString().equals("INTERACT");
}
}
/**
* Removes pet owner field from DataWatcher
*
* @throws ReflectiveOperationException
* if thrown by reflective operation
*/
@SuppressWarnings("unchecked")
@Override
public void onPacketSend(TabPlayer receiver, Object packet) throws ReflectiveOperationException {
if (PacketPlayOutEntityMetadataStorage.CLASS.isInstance(packet)) {
Object removedEntry = null;
List<Object> items = (List<Object>) PacketPlayOutEntityMetadataStorage.LIST.get(packet);
if (items == null) return;
try {
for (Object item : items) {
if (item == null) continue;
int slot;
Object value;
if (nms.is1_19_3Plus()) {
slot = DataWatcher.DataValue_POSITION.getInt(item);
value = DataWatcher.DataValue_VALUE.get(item);
} else {
slot = DataWatcherObject.SLOT.getInt(DataWatcherItem.TYPE.get(item));
value = DataWatcherItem.VALUE.get(item);
}
if (slot == petOwnerPosition) {
if (value instanceof Optional || value instanceof com.google.common.base.Optional) {
removedEntry = item;
}
}
}
if (removedEntry != null) items.remove(removedEntry);
} catch (ConcurrentModificationException e) {
//no idea how can this list change in another thread since it's created for the packet but whatever, try again
onPacketSend(receiver, packet);
}
} else if (PacketPlayOutSpawnEntityLivingStorage.CLASS.isInstance(packet) && PacketPlayOutSpawnEntityLivingStorage.DATA_WATCHER != null) {
//<1.15
DataWatcher watcher = DataWatcher.fromNMS(PacketPlayOutSpawnEntityLivingStorage.DATA_WATCHER.get(packet));
DataWatcherItem petOwner = watcher.getItem(petOwnerPosition);
if (petOwner != null && (petOwner.getValue() instanceof Optional || petOwner.getValue() instanceof com.google.common.base.Optional)) {
watcher.removeValue(petOwnerPosition);
PacketPlayOutSpawnEntityLivingStorage.DATA_WATCHER.set(packet, watcher.build());
}
}
}
}
@@ -1,9 +1,10 @@
package me.neznamy.tab.platforms.bukkit.features;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.api.bossbar.BossBar;
import me.neznamy.tab.shared.features.types.WorldSwitchListener;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.backend.BackendTabPlayer;
import me.neznamy.tab.shared.features.bossbar.BossBarManagerImpl;
@@ -15,18 +16,19 @@ import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerRespawnEvent;
import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.NotNull;
/**
* An additional class with additional code for &lt;1.9 servers due to an entity being required
*/
@RequiredArgsConstructor
public class WitherBossBar extends BossBarManagerImpl implements Listener {
public class WitherBossBar extends BossBarManagerImpl implements Listener, WorldSwitchListener {
/** Distance of the Wither in blocks */
private static final int WITHER_DISTANCE = 60;
/** Reference to plugin for registering listener */
private final JavaPlugin plugin;
private final @NotNull JavaPlugin plugin;
@Override
public void load() {
@@ -34,19 +36,19 @@ public class WitherBossBar extends BossBarManagerImpl implements Listener {
//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,
this, TabConstants.CpuUsageCategory.TELEPORTING_WITHER, this::teleport);
featureName, TabConstants.CpuUsageCategory.TELEPORTING_WITHER, this::teleport);
super.load();
teleport();
}
/**
* Updates Wither's location for all online players
* Updates Wither location for all online players
*/
private void teleport() {
for (TabPlayer p : TAB.getInstance().getOnlinePlayers()) {
if (p.getVersion().getMinorVersion() > 8) continue; //sending VV packets to those
for (BossBar line : getRegisteredBossBars().values()) {
if (!line.containsPlayer(p)) continue;
if (!line.getPlayers().contains(p)) continue;
Location loc = ((Player) p.getPlayer()).getEyeLocation().add(((Player) p.getPlayer()).getEyeLocation().getDirection().normalize().multiply(WITHER_DISTANCE));
if (loc.getY() < 1) loc.setY(1);
((BackendTabPlayer)p).teleportEntity(line.getUniqueId().hashCode(), new me.neznamy.tab.shared.backend.Location(loc.getX(), loc.getY(), loc.getZ(), 0, 0));
@@ -65,7 +67,17 @@ public class WitherBossBar extends BossBarManagerImpl implements Listener {
*/
@EventHandler
public void onRespawn(PlayerRespawnEvent e) {
TAB.getInstance().getCPUManager().runMeasuredTask(this, TabConstants.CpuUsageCategory.PLAYER_RESPAWN,
() -> detectBossBarsAndSend(TAB.getInstance().getPlayer(e.getPlayer().getUniqueId())));
TabPlayer player = TAB.getInstance().getPlayer(e.getPlayer().getUniqueId());
if (player == null) return;
TAB.getInstance().getCPUManager().runMeasuredTask(featureName, TabConstants.CpuUsageCategory.PLAYER_RESPAWN,
() -> detectBossBarsAndSend(player));
}
@Override
public void onWorldChange(@NotNull TabPlayer p, @NotNull String from, @NotNull String to) {
for (BossBar line : lineValues) {
line.removePlayer(p);
}
detectBossBarsAndSend(p);
}
}
@@ -1,51 +0,0 @@
package me.neznamy.tab.platforms.bukkit.features.unlimitedtags;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.api.TabAPI;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.shared.TAB;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerRespawnEvent;
import org.bukkit.event.player.PlayerToggleSneakEvent;
/**
* Class listening to Bukkit events which the feature requires and are
* sufficient, without packets being requires.
*/
@RequiredArgsConstructor
public class EventListener implements Listener {
/** Reference to the main feature */
private final BukkitNameTagX feature;
/**
* Sneak event listener to de-spawn and spawn armor stands to skip animation
*
* @param e
* sneak event
*/
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onSneak(PlayerToggleSneakEvent e) {
TabPlayer p = TabAPI.getInstance().getPlayer(e.getPlayer().getUniqueId());
if (p == null || feature.isPlayerDisabled(p)) return;
TAB.getInstance().getCPUManager().runMeasuredTask(feature, TabConstants.CpuUsageCategory.PLAYER_SNEAK, () -> feature.getArmorStandManager(p).sneak(e.isSneaking()));
}
/**
* Respawning armor stands as respawn screen destroys all entities in client
*
* @param e
* respawn event
*/
@EventHandler
public void onRespawn(PlayerRespawnEvent e) {
TAB.getInstance().getCPUManager().runMeasuredTask(feature, TabConstants.CpuUsageCategory.PLAYER_RESPAWN, () -> {
TabPlayer respawned = TabAPI.getInstance().getPlayer(e.getPlayer().getUniqueId());
if (feature.isPlayerDisabled(respawned)) return;
feature.getArmorStandManager(respawned).teleport();
});
}
}
@@ -1,16 +1,16 @@
package me.neznamy.tab.platforms.bukkit.nms.datawatcher;
import lombok.Getter;
import lombok.NonNull;
import lombok.SneakyThrows;
import lombok.ToString;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import me.neznamy.tab.shared.backend.EntityData;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
@@ -21,14 +21,10 @@ public class DataWatcher implements EntityData {
/** NMS Fields */
public static Class<?> CLASS;
public static Constructor<?> CONSTRUCTOR;
public static Method REGISTER;
private static Constructor<?> CONSTRUCTOR;
private static Method REGISTER;
public static Method markDirty;
public static Method packDirty;
public static Class<?> DataValue;
public static Field DataValue_POSITION;
public static Field DataValue_VALUE;
/** Watched data */
private final Map<Integer, DataWatcherItem> dataValues = new HashMap<>();
@@ -42,12 +38,18 @@ public class DataWatcher implements EntityData {
* @param nms
* NMS storage reference
*/
public static void load(NMSStorage nms) {
CONSTRUCTOR = CLASS.getConstructors()[0];
public static void load(NMSStorage nms) throws NoSuchMethodException {
CONSTRUCTOR = ReflectionUtils.getOnlyConstructor(CLASS);
if (nms.getMinorVersion() >= 9) {
REGISTER = ReflectionUtils.getMethod(CLASS, new String[]{"register", "a"}, DataWatcherObject.CLASS, Object.class); // {Bukkit, Bukkit 1.18+}
} else {
REGISTER = ReflectionUtils.getMethod(CLASS, new String[]{"func_75682_a", "a"}, int.class, Object.class); // {Thermos 1.7.10, Bukkit}
}
if (nms.getMinorVersion() >= 19) {
packDirty = CLASS.getMethod("b");
}
if (nms.is1_19_3Plus()) {
markDirty = nms.getMethods(CLASS, void.class, DataWatcherObject.CLASS).get(0);
DataValue_POSITION = nms.getFields(DataValue, int.class).get(0);
DataValue_VALUE = nms.getFields(DataValue, Object.class).get(0);
markDirty = ReflectionUtils.getOnlyMethod(CLASS, void.class, DataWatcherObject.CLASS);
}
}
@@ -59,75 +61,29 @@ public class DataWatcher implements EntityData {
* @param value
* value
*/
public void setValue(@NonNull DataWatcherObject type, @NonNull Object value) {
public void setValue(@NotNull DataWatcherObject type, @NotNull Object value) {
dataValues.put(type.getPosition(), new DataWatcherItem(type, value));
}
/**
* Removes value by position
*
* @param position
* position of value to remove
*/
public void removeValue(int position) {
dataValues.remove(position);
}
/**
* Returns item with given position
*
* @param position
* position of item
* @return item or null if not set
*/
public DataWatcherItem getItem(int position) {
return dataValues.get(position);
}
/**
* Converts the class into an instance of NMS.DataWatcher
*
* @return an instance of NMS.DataWatcher with same data
*/
public Object build() {
try {
NMSStorage nms = NMSStorage.getInstance();
Object nmsWatcher;
if (CONSTRUCTOR.getParameterCount() == 1) { //1.7+
nmsWatcher = CONSTRUCTOR.newInstance(new Object[] {null});
} else {
nmsWatcher = CONSTRUCTOR.newInstance();
}
for (DataWatcherItem item : dataValues.values()) {
Object nmsObject = item.getType().build();
REGISTER.invoke(nmsWatcher, nmsObject, item.getValue());
if (nms.is1_19_3Plus()) markDirty.invoke(nmsWatcher, nmsObject);
}
return nmsWatcher;
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
@SneakyThrows
public @NotNull Object build() {
NMSStorage nms = NMSStorage.getInstance();
Object nmsWatcher;
if (CONSTRUCTOR.getParameterCount() == 1) { //1.7+
nmsWatcher = CONSTRUCTOR.newInstance(new Object[] {null});
} else {
nmsWatcher = CONSTRUCTOR.newInstance();
}
}
/**
* Reads NMS data watcher and returns and instance of this class with same data
*
* @param nmsWatcher
* NMS DataWatcher to read
* @return an instance of this class with same values
* @throws ReflectiveOperationException
* if thrown by reflective operation
*/
@SuppressWarnings("unchecked")
public static DataWatcher fromNMS(@NonNull Object nmsWatcher) throws ReflectiveOperationException {
DataWatcher watcher = new DataWatcher();
List<Object> items = (List<Object>) nmsWatcher.getClass().getMethod("c").invoke(nmsWatcher);
if (items != null) {
for (Object watchableObject : items) {
DataWatcherItem w = DataWatcherItem.fromNMS(watchableObject);
watcher.setValue(w.getType(), w.getValue());
}
for (DataWatcherItem item : dataValues.values()) {
Object nmsObject = item.getType().build();
REGISTER.invoke(nmsWatcher, nmsObject, item.getValue());
if (nms.is1_19_3Plus()) markDirty.invoke(nmsWatcher, nmsObject);
}
return watcher;
return nmsWatcher;
}
}
@@ -1,11 +1,11 @@
package me.neznamy.tab.platforms.bukkit.nms.datawatcher;
import lombok.AllArgsConstructor;
import me.neznamy.tab.api.ProtocolVersion;
import me.neznamy.tab.api.TabAPI;
import me.neznamy.tab.api.chat.IChatBaseComponent;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import me.neznamy.tab.shared.TAB;
import org.jetbrains.annotations.NotNull;
import java.util.Optional;
@@ -26,7 +26,7 @@ public class DataWatcherHelper {
/** Instance fields */
private final int armorStandFlagsPosition = getArmorStandFlagsPosition();
private final DataWatcher data;
@NotNull private final DataWatcher data;
/**
* Returns armor stand flags position based on server version
@@ -34,16 +34,16 @@ public class DataWatcherHelper {
* @return armor stand flags position based on server version
*/
private int getArmorStandFlagsPosition() {
if (TabAPI.getInstance().getServerVersion().getMinorVersion() >= 17) {
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 17) {
//1.17.x, 1.18.x, 1.19.x
return 15;
} else if (TabAPI.getInstance().getServerVersion().getMinorVersion() >= 15) {
} else if (TAB.getInstance().getServerVersion().getMinorVersion() >= 15) {
//1.15.x, 1.16.x
return 14;
} else if (TabAPI.getInstance().getServerVersion().getMinorVersion() >= 14) {
} else if (TAB.getInstance().getServerVersion().getMinorVersion() >= 14) {
//1.14.x
return 13;
} else if (TabAPI.getInstance().getServerVersion().getMinorVersion() >= 10) {
} else if (TAB.getInstance().getServerVersion().getMinorVersion() >= 10) {
//1.10.x - 1.13.x
return 11;
} else {
@@ -52,6 +52,28 @@ public class DataWatcherHelper {
}
}
public static void load(NMSStorage nms) throws NoSuchFieldException, IllegalAccessException {
if (nms.getMinorVersion() < 9) return;
DataWatcherSerializer_BYTE = DataWatcherRegistry.getDeclaredField("a").get(null);
DataWatcherSerializer_FLOAT = DataWatcherRegistry.getDeclaredField("c").get(null);
DataWatcherSerializer_STRING = DataWatcherRegistry.getDeclaredField("d").get(null);
if (nms.is1_19_3Plus()) {
DataWatcherSerializer_OPTIONAL_COMPONENT = DataWatcherRegistry.getDeclaredField("g").get(null);
if (nms.is1_19_4Plus()) {
DataWatcherSerializer_BOOLEAN = DataWatcherRegistry.getDeclaredField("k").get(null);
} else {
DataWatcherSerializer_BOOLEAN = DataWatcherRegistry.getDeclaredField("j").get(null);
}
} else {
if (nms.getMinorVersion() >= 13) {
DataWatcherSerializer_OPTIONAL_COMPONENT = DataWatcherRegistry.getDeclaredField("f").get(null);
DataWatcherSerializer_BOOLEAN = DataWatcherRegistry.getDeclaredField("i").get(null);
} else {
DataWatcherSerializer_BOOLEAN = DataWatcherRegistry.getDeclaredField("h").get(null);
}
}
}
/**
* Writes entity byte flags
*
@@ -70,16 +92,16 @@ public class DataWatcherHelper {
* @param clientVersion
* client version
*/
public void setCustomName(String customName, ProtocolVersion clientVersion) {
if (TabAPI.getInstance().getServerVersion().getMinorVersion() >= 13) {
public void setCustomName(@NotNull String customName, @NotNull ProtocolVersion clientVersion) {
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 13) {
data.setValue(new DataWatcherObject(2, DataWatcherSerializer_OPTIONAL_COMPONENT),
Optional.ofNullable(NMSStorage.getInstance().toNMSComponent(IChatBaseComponent.optimizedComponent(customName), clientVersion)));
} else if (TabAPI.getInstance().getServerVersion().getMinorVersion() >= 8) {
} else if (TAB.getInstance().getServerVersion().getMinorVersion() >= 8) {
data.setValue(new DataWatcherObject(2, DataWatcherSerializer_STRING), customName);
} else {
//name length is limited to 64 characters on <1.8
String cutName = (customName.length() > 64 ? customName.substring(0, 64) : customName);
if (TabAPI.getInstance().getServerVersion().getMinorVersion() >= 6) {
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 6) {
data.setValue(new DataWatcherObject(10, null), cutName);
} else {
data.setValue(new DataWatcherObject(5, null), cutName);
@@ -94,7 +116,7 @@ public class DataWatcherHelper {
* if visible or not
*/
public void setCustomNameVisible(boolean visible) {
if (TabAPI.getInstance().getServerVersion().getMinorVersion() >= 9) {
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 9) {
data.setValue(new DataWatcherObject(3, DataWatcherSerializer_BOOLEAN), visible);
} else {
data.setValue(new DataWatcherObject(3, null), (byte)(visible?1:0));
@@ -108,7 +130,7 @@ public class DataWatcherHelper {
* health of entity
*/
public void setHealth(float health) {
if (TabAPI.getInstance().getServerVersion().getMinorVersion() >= 6) {
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 6) {
data.setValue(new DataWatcherObject(6, DataWatcherSerializer_FLOAT), health);
} else {
data.setValue(new DataWatcherObject(16, null), (int)health);
@@ -1,8 +1,9 @@
package me.neznamy.tab.platforms.bukkit.nms.datawatcher;
import lombok.Data;
import me.neznamy.tab.api.TabAPI;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
@@ -18,8 +19,8 @@ public class DataWatcherItem {
public static Field VALUE;
/** Instance fields */
private final DataWatcherObject type;
private final Object value;
@NotNull private final DataWatcherObject type;
@NotNull private final Object value;
/**
* Loads all required Fields
@@ -28,32 +29,11 @@ public class DataWatcherItem {
* NMS storage reference
*/
public static void load(NMSStorage nms) {
VALUE = nms.getFields(CLASS, Object.class).get(0);
VALUE = ReflectionUtils.getFields(CLASS, Object.class).get(0);
if (nms.getMinorVersion() >= 9) {
TYPE = nms.getFields(CLASS, DataWatcherObject.CLASS).get(0);
TYPE = ReflectionUtils.getOnlyField(CLASS, DataWatcherObject.CLASS);
} else {
TYPE = nms.getFields(CLASS, int.class).get(1);
TYPE = ReflectionUtils.getFields(CLASS, int.class).get(1);
}
}
/**
* Returns and instance of this class from given NMS item
*
* @param nmsItem
* NMS item
* @return instance of this class with same data
* @throws ReflectiveOperationException
* if thrown by reflective operation
*/
public static DataWatcherItem fromNMS(Object nmsItem) throws ReflectiveOperationException {
Object value = VALUE.get(nmsItem);
DataWatcherObject object;
if (TabAPI.getInstance().getServerVersion().getMinorVersion() >= 9) {
Object nmsObject = TYPE.get(nmsItem);
object = new DataWatcherObject(DataWatcherObject.SLOT.getInt(nmsObject), DataWatcherObject.SERIALIZER.get(nmsObject));
} else {
object = new DataWatcherObject(TYPE.getInt(nmsItem), null);
}
return new DataWatcherItem(object, value);
}
}
@@ -2,7 +2,10 @@ package me.neznamy.tab.platforms.bukkit.nms.datawatcher;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
@@ -21,31 +24,27 @@ public class DataWatcherObject {
/** Instance fields */
private final int position;
private final Object serializer;
@Nullable private final Object serializer;
/**
* Loads all required Fields and throws Exception if something went wrong
*
* @param nms
* NMS storage reference
* @throws NoSuchMethodException
* If something fails
*/
public static void load(NMSStorage nms) throws NoSuchMethodException {
if (nms.getMinorVersion() < 9) return;
public static void load() throws NoSuchMethodException {
CONSTRUCTOR = CLASS.getConstructor(int.class, DataWatcherHelper.DataWatcherSerializer);
SLOT = nms.getFields(CLASS, int.class).get(0);
SERIALIZER = nms.getFields(CLASS, DataWatcherHelper.DataWatcherSerializer).get(0);
SLOT = ReflectionUtils.getOnlyField(CLASS, int.class);
SERIALIZER = ReflectionUtils.getOnlyField(CLASS, DataWatcherHelper.DataWatcherSerializer);
}
/**
* Converts the object into NMS object
*
* @return NMS object
* @throws ReflectiveOperationException
* If thrown by reflective operation
*/
public Object build() throws ReflectiveOperationException {
@SneakyThrows
public Object build() {
if (NMSStorage.getInstance().getMinorVersion() >= 9) {
return DataWatcherObject.CONSTRUCTOR.newInstance(position, serializer);
} else {
@@ -1,10 +1,13 @@
package me.neznamy.tab.platforms.bukkit.nms.storage.nms;
import me.neznamy.tab.platforms.bukkit.BukkitTabList;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherHelper;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherItem;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherObject;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.*;
import me.neznamy.tab.platforms.bukkit.scoreboard.PacketScoreboard;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
@@ -14,8 +17,6 @@ import java.util.Arrays;
@SuppressWarnings({"unchecked", "rawtypes"})
public class BukkitLegacyNMSStorage extends NMSStorage {
public BukkitLegacyNMSStorage() throws ReflectiveOperationException {}
/**
* Returns class with given potential names in same order
*
@@ -25,7 +26,7 @@ public class BukkitLegacyNMSStorage extends NMSStorage {
* @throws ClassNotFoundException
* if class does not exist
*/
private Class<?> getLegacyClass(String... names) throws ClassNotFoundException {
private Class<?> getLegacyClass(@NotNull String... names) throws ClassNotFoundException {
for (String name : names) {
try {
return getLegacyClass(name);
@@ -37,7 +38,7 @@ public class BukkitLegacyNMSStorage extends NMSStorage {
}
/**
* Returns class from given name
* Returns class from given name. Supports modded servers, such as Thermos.
*
* @param name
* class name
@@ -45,42 +46,12 @@ public class BukkitLegacyNMSStorage extends NMSStorage {
* @throws ClassNotFoundException
* if class was not found
*/
public Class<?> getLegacyClass(String name) throws ClassNotFoundException {
return Class.forName("net.minecraft.server." + serverPackage + "." + name);
}
@Override
public void loadNamedFieldsAndMethods() throws ReflectiveOperationException {
(PING = EntityPlayer.getDeclaredField("ping")).setAccessible(true);
PacketPlayOutScoreboardScoreStorage.ScoreboardScore_setScore = getMethod(PacketPlayOutScoreboardScoreStorage.ScoreboardScore, new String[] {"setScore", "c"}, int.class); // 1.5.1+, 1.5
PacketPlayOutScoreboardTeamStorage.ScoreboardTeam_setAllowFriendlyFire = getMethod(PacketPlayOutScoreboardTeamStorage.ScoreboardTeam, new String[] {"setAllowFriendlyFire", "a"}, boolean.class); // 1.5.1+, 1.5
PacketPlayOutScoreboardTeamStorage.ScoreboardTeam_setCanSeeFriendlyInvisibles = getMethod(PacketPlayOutScoreboardTeamStorage.ScoreboardTeam, new String[] {"setCanSeeFriendlyInvisibles", "b"}, boolean.class); // 1.5.1+, 1.5
if (minorVersion >= 7) {
ChatSerializer_DESERIALIZE = ChatSerializer.getMethod("a", String.class);
}
if (minorVersion >= 8) {
PacketPlayOutScoreboardTeamStorage.ScoreboardTeam_setNameTagVisibility = getMethod(PacketPlayOutScoreboardTeamStorage.ScoreboardTeam, new String[] {"setNameTagVisibility", "a"}, PacketPlayOutScoreboardTeamStorage.EnumNameTagVisibility); // {1.8.1+, 1.8}
}
if (minorVersion >= 9) {
DataWatcher.REGISTER = DataWatcher.CLASS.getMethod("register", DataWatcherObject.CLASS, Object.class);
DataWatcherHelper.DataWatcherSerializer_BYTE = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("a").get(null);
DataWatcherHelper.DataWatcherSerializer_FLOAT = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("c").get(null);
DataWatcherHelper.DataWatcherSerializer_STRING = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("d").get(null);
if (getMinorVersion() >= 13) {
DataWatcherHelper.DataWatcherSerializer_OPTIONAL_COMPONENT = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("f").get(null);
DataWatcherHelper.DataWatcherSerializer_BOOLEAN = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("i").get(null);
} else {
DataWatcherHelper.DataWatcherSerializer_BOOLEAN = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("h").get(null);
}
} else {
DataWatcher.REGISTER = DataWatcher.CLASS.getMethod("a", int.class, Object.class);
}
if (minorVersion >= 13) {
PacketPlayOutScoreboardTeamStorage.ScoreboardTeam_setPrefix = PacketPlayOutScoreboardTeamStorage.ScoreboardTeam.getMethod("setPrefix", IChatBaseComponent);
PacketPlayOutScoreboardTeamStorage.ScoreboardTeam_setSuffix = PacketPlayOutScoreboardTeamStorage.ScoreboardTeam.getMethod("setSuffix", IChatBaseComponent);
} else {
PacketPlayOutScoreboardTeamStorage.ScoreboardTeam_setPrefix = getMethod(PacketPlayOutScoreboardTeamStorage.ScoreboardTeam, new String[] {"setPrefix", "b"}, String.class); // 1.5.1+, 1.5
PacketPlayOutScoreboardTeamStorage.ScoreboardTeam_setSuffix = getMethod(PacketPlayOutScoreboardTeamStorage.ScoreboardTeam, new String[] {"setSuffix", "c"}, String.class); // 1.5.1+, 1.5
public Class<?> getLegacyClass(@NotNull String name) throws ClassNotFoundException {
try {
return getClass().getClassLoader().loadClass("net.minecraft.server." + serverPackage + "." + name);
} catch (NullPointerException e) {
// nested class not found
throw new ClassNotFoundException(name);
}
}
@@ -95,62 +66,44 @@ public class BukkitLegacyNMSStorage extends NMSStorage {
EntityLiving = getLegacyClass("EntityLiving");
PlayerConnection = getLegacyClass("PlayerConnection");
NetworkManager = getLegacyClass("NetworkManager");
if (minorVersion >= 7) {
IChatBaseComponent = getLegacyClass("IChatBaseComponent");
ChatSerializer = getLegacyClass("IChatBaseComponent$ChatSerializer", "ChatSerializer");
}
if (minorVersion >= 8) {
PacketPlayOutPlayerListHeaderFooterStorage.CLASS = getLegacyClass("PacketPlayOutPlayerListHeaderFooter");
EntityArmorStand = getLegacyClass("EntityArmorStand");
}
// DataWatcher
DataWatcher.CLASS = getLegacyClass("DataWatcher");
DataWatcherItem.CLASS = getLegacyClass("DataWatcher$Item", "DataWatcher$WatchableObject", "WatchableObject");
if (minorVersion >= 9) {
DataWatcherObject.CLASS = getLegacyClass("DataWatcherObject");
DataWatcherHelper.DataWatcherRegistry = getLegacyClass("DataWatcherRegistry");
DataWatcherHelper.DataWatcherSerializer = getLegacyClass("DataWatcherSerializer");
}
// Entities
PacketPlayOutSpawnEntityLivingStorage.CLASS = getLegacyClass("PacketPlayOutSpawnEntityLiving", "Packet24MobSpawn");
PacketPlayOutEntityTeleportStorage.CLASS = getLegacyClass("PacketPlayOutEntityTeleport", "Packet34EntityTeleport");
PacketPlayInUseEntity = getLegacyClass("PacketPlayInUseEntity", "Packet7UseEntity");
PacketPlayOutEntity = getLegacyClass("PacketPlayOutEntity", "Packet30Entity");
PacketPlayOutEntityDestroyStorage.CLASS = getLegacyClass("PacketPlayOutEntityDestroy", "Packet29DestroyEntity");
PacketPlayOutEntityLook = getLegacyClass("PacketPlayOutEntity$PacketPlayOutEntityLook", "PacketPlayOutEntityLook", "Packet32EntityLook");
PacketPlayOutEntityMetadataStorage.CLASS = getLegacyClass("PacketPlayOutEntityMetadata", "Packet40EntityMetadata");
PacketPlayOutNamedEntitySpawn = getLegacyClass("PacketPlayOutNamedEntitySpawn", "Packet20NamedEntitySpawn");
PacketScoreboard.DisplayObjectiveClass = getLegacyClass("PacketPlayOutScoreboardDisplayObjective", "Packet208SetScoreboardDisplayObjective");
PacketScoreboard.ObjectivePacketClass = getLegacyClass("PacketPlayOutScoreboardObjective", "Packet206SetScoreboardObjective");
PacketScoreboard.TeamPacketClass = getLegacyClass("PacketPlayOutScoreboardTeam", "Packet209SetScoreboardTeam");
PacketScoreboard.ScorePacketClass = getLegacyClass("PacketPlayOutScoreboardScore", "Packet207SetScoreboardScore");
PacketScoreboard.Scoreboard = getLegacyClass("Scoreboard");
PacketScoreboard.ScoreboardObjective = getLegacyClass("ScoreboardObjective");
PacketScoreboard.ScoreboardScoreClass = getLegacyClass("ScoreboardScore");
PacketScoreboard.IScoreboardCriteria = getLegacyClass("IScoreboardCriteria", "IObjective"); // 1.5.1+, 1.5
PacketScoreboard.ScoreboardTeam = getLegacyClass("ScoreboardTeam");
if (minorVersion >= 7) {
EnumEntityUseAction = (Class<Enum>) getLegacyClass("PacketPlayInUseEntity$EnumEntityUseAction", "EnumEntityUseAction");
IChatBaseComponent = getLegacyClass("IChatBaseComponent");
ChatSerializer = getLegacyClass("IChatBaseComponent$ChatSerializer", "ChatSerializer");
}
// Player Info
if (minorVersion >= 8) {
PacketPlayOutPlayerInfoStorage.CLASS = getLegacyClass("PacketPlayOutPlayerInfo");
PacketPlayOutPlayerInfoStorage.EnumPlayerInfoActionClass = (Class<Enum>) getLegacyClass("PacketPlayOutPlayerInfo$EnumPlayerInfoAction", "EnumPlayerInfoAction");
PacketPlayOutPlayerInfoStorage.PlayerInfoDataStorage.CLASS = getLegacyClass("PacketPlayOutPlayerInfo$PlayerInfoData", "PlayerInfoData");
PacketPlayOutPlayerInfoStorage.EnumGamemodeClass = (Class<Enum>) getLegacyClass("EnumGamemode", "WorldSettings$EnumGamemode");
}
// Scoreboard
PacketPlayOutScoreboardDisplayObjectiveStorage.CLASS = getLegacyClass("PacketPlayOutScoreboardDisplayObjective", "Packet208SetScoreboardDisplayObjective");
PacketPlayOutScoreboardObjectiveStorage.CLASS = getLegacyClass("PacketPlayOutScoreboardObjective", "Packet206SetScoreboardObjective");
PacketPlayOutScoreboardTeamStorage.CLASS = getLegacyClass("PacketPlayOutScoreboardTeam", "Packet209SetScoreboardTeam");
PacketPlayOutScoreboardScoreStorage.CLASS = getLegacyClass("PacketPlayOutScoreboardScore", "Packet207SetScoreboardScore");
Scoreboard = getLegacyClass("Scoreboard");
PacketPlayOutScoreboardObjectiveStorage.ScoreboardObjective = getLegacyClass("ScoreboardObjective");
PacketPlayOutScoreboardScoreStorage.ScoreboardScore = getLegacyClass("ScoreboardScore");
IScoreboardCriteria = getLegacyClass("IScoreboardCriteria", "IObjective"); // 1.5.1+, 1.5
PacketPlayOutScoreboardTeamStorage.ScoreboardTeam = getLegacyClass("ScoreboardTeam");
if (minorVersion >= 8) {
PacketPlayOutScoreboardObjectiveStorage.EnumScoreboardHealthDisplay = (Class<Enum>) getLegacyClass("IScoreboardCriteria$EnumScoreboardHealthDisplay", "EnumScoreboardHealthDisplay");
PacketPlayOutScoreboardScoreStorage.EnumScoreboardAction = (Class<Enum>) getLegacyClass("ScoreboardServer$Action", "PacketPlayOutScoreboardScore$EnumScoreboardAction", "EnumScoreboardAction");
PacketPlayOutScoreboardTeamStorage.EnumNameTagVisibility = (Class<Enum>) getLegacyClass("ScoreboardTeamBase$EnumNameTagVisibility", "EnumNameTagVisibility");
BukkitTabList.PacketPlayOutPlayerListHeaderFooterClass = getLegacyClass("PacketPlayOutPlayerListHeaderFooter");
EntityArmorStand = getLegacyClass("EntityArmorStand");
BukkitTabList.PacketPlayOutPlayerInfoClass = getLegacyClass("PacketPlayOutPlayerInfo");
BukkitTabList.EnumPlayerInfoActionClass = (Class<Enum>) getLegacyClass("PacketPlayOutPlayerInfo$EnumPlayerInfoAction", "EnumPlayerInfoAction");
BukkitTabList.PlayerInfoDataClass = getLegacyClass("PacketPlayOutPlayerInfo$PlayerInfoData", "PlayerInfoData");
BukkitTabList.EnumGamemodeClass = (Class<Enum>) getLegacyClass("EnumGamemode", "WorldSettings$EnumGamemode");
PacketScoreboard.EnumScoreboardHealthDisplay = (Class<Enum>) getLegacyClass("IScoreboardCriteria$EnumScoreboardHealthDisplay", "EnumScoreboardHealthDisplay");
PacketScoreboard.EnumScoreboardAction = (Class<Enum>) getLegacyClass("ScoreboardServer$Action", "PacketPlayOutScoreboardScore$EnumScoreboardAction", "EnumScoreboardAction");
PacketScoreboard.EnumNameTagVisibility = (Class<Enum>) getLegacyClass("ScoreboardTeamBase$EnumNameTagVisibility", "EnumNameTagVisibility");
}
if (minorVersion >= 9) {
PacketPlayOutScoreboardTeamStorage.EnumTeamPush = (Class<Enum>) getLegacyClass("ScoreboardTeamBase$EnumTeamPush");
DataWatcherObject.CLASS = getLegacyClass("DataWatcherObject");
DataWatcherHelper.DataWatcherRegistry = getLegacyClass("DataWatcherRegistry");
DataWatcherHelper.DataWatcherSerializer = getLegacyClass("DataWatcherSerializer");
PacketScoreboard.EnumTeamPush = (Class<Enum>) getLegacyClass("ScoreboardTeamBase$EnumTeamPush");
}
}
}
@@ -1,10 +1,12 @@
package me.neznamy.tab.platforms.bukkit.nms.storage.nms;
import me.neznamy.tab.platforms.bukkit.BukkitTabList;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherHelper;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherItem;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherObject;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.*;
import me.neznamy.tab.platforms.bukkit.scoreboard.PacketScoreboard;
/**
* NMS loader for minecraft 1.17+ using Mojang packaging and bukkit names.
@@ -12,40 +14,6 @@ import me.neznamy.tab.platforms.bukkit.nms.storage.packet.*;
@SuppressWarnings({"unchecked", "rawtypes"})
public class BukkitModernNMSStorage extends NMSStorage {
public BukkitModernNMSStorage() throws ReflectiveOperationException {}
@Override
public void loadNamedFieldsAndMethods() throws ReflectiveOperationException {
(PING = EntityPlayer.getDeclaredField("e")).setAccessible(true);
ChatSerializer_DESERIALIZE = ChatSerializer.getMethod("a", String.class);
DataWatcher.REGISTER = getMethod(DataWatcher.CLASS, new String[]{"register", "a"}, DataWatcherObject.CLASS, Object.class); // {Bukkit, Bukkit 1.18+}
PacketPlayOutScoreboardScoreStorage.ScoreboardScore_setScore = getMethod(PacketPlayOutScoreboardScoreStorage.ScoreboardScore, new String[]{"setScore", "b"}, int.class); // {Bukkit, Bukkit 1.18+}
PacketPlayOutScoreboardTeamStorage.ScoreboardTeam_setAllowFriendlyFire = getMethod(PacketPlayOutScoreboardTeamStorage.ScoreboardTeam, new String[]{"setAllowFriendlyFire", "a"}, boolean.class); // {Bukkit, Bukkit 1.18+}
PacketPlayOutScoreboardTeamStorage.ScoreboardTeam_setCanSeeFriendlyInvisibles = getMethod(PacketPlayOutScoreboardTeamStorage.ScoreboardTeam, new String[]{"setCanSeeFriendlyInvisibles", "b"}, boolean.class); // {Bukkit, Bukkit 1.18+}
PacketPlayOutScoreboardTeamStorage.ScoreboardTeam_setPrefix = getMethod(PacketPlayOutScoreboardTeamStorage.ScoreboardTeam, new String[]{"setPrefix", "b"}, IChatBaseComponent); // {Bukkit, Bukkit 1.18+}
PacketPlayOutScoreboardTeamStorage.ScoreboardTeam_setSuffix = getMethod(PacketPlayOutScoreboardTeamStorage.ScoreboardTeam, new String[]{"setSuffix", "c"}, IChatBaseComponent); // {Bukkit, Bukkit 1.18+}
PacketPlayOutScoreboardTeamStorage.ScoreboardTeam_setNameTagVisibility = getMethod(PacketPlayOutScoreboardTeamStorage.ScoreboardTeam, new String[]{"setNameTagVisibility", "a"}, PacketPlayOutScoreboardTeamStorage.EnumNameTagVisibility); // {Bukkit, Bukkit 1.18+}
DataWatcherHelper.DataWatcherSerializer_BYTE = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("a").get(null);
DataWatcherHelper.DataWatcherSerializer_FLOAT = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("c").get(null);
DataWatcherHelper.DataWatcherSerializer_STRING = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("d").get(null);
if (is1_19_3Plus()) {
DataWatcherHelper.DataWatcherSerializer_OPTIONAL_COMPONENT = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("g").get(null);
if (is1_19_4Plus()) {
DataWatcherHelper.DataWatcherSerializer_BOOLEAN = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("k").get(null);
} else {
DataWatcherHelper.DataWatcherSerializer_BOOLEAN = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("j").get(null);
}
} else {
DataWatcherHelper.DataWatcherSerializer_OPTIONAL_COMPONENT = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("f").get(null);
DataWatcherHelper.DataWatcherSerializer_BOOLEAN = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("i").get(null);
}
if (minorVersion >= 19) {
PacketPlayOutSpawnEntityLivingStorage.EntityTypes_ARMOR_STAND = PacketPlayOutSpawnEntityLivingStorage.EntityTypes.getDeclaredField("d").get(null);
(PacketPlayOutSpawnEntityLivingStorage.ENTITY_TYPE = PacketPlayOutSpawnEntityLivingStorage.CLASS.getDeclaredField("e")).setAccessible(true);
DataWatcher.packDirty = DataWatcher.CLASS.getMethod("b");
}
}
@Override
public void loadClasses() throws ClassNotFoundException {
ChatSerializer = Class.forName("net.minecraft.network.chat.IChatBaseComponent$ChatSerializer");
@@ -60,69 +28,45 @@ public class BukkitModernNMSStorage extends NMSStorage {
Entity = Class.forName("net.minecraft.world.entity.Entity");
EntityLiving = Class.forName("net.minecraft.world.entity.EntityLiving");
PlayerConnection = Class.forName("net.minecraft.server.network.PlayerConnection");
PacketPlayOutPlayerListHeaderFooterStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutPlayerListHeaderFooter");
// DataWatcher
BukkitTabList.PacketPlayOutPlayerListHeaderFooterClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutPlayerListHeaderFooter");
PacketScoreboard.DisplayObjectiveClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutScoreboardDisplayObjective");
PacketScoreboard.ObjectivePacketClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutScoreboardObjective");
PacketScoreboard.Scoreboard = Class.forName("net.minecraft.world.scores.Scoreboard");
PacketScoreboard.ScorePacketClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutScoreboardScore");
PacketScoreboard.ScoreboardObjective = Class.forName("net.minecraft.world.scores.ScoreboardObjective");
PacketScoreboard.ScoreboardScoreClass = Class.forName("net.minecraft.world.scores.ScoreboardScore");
PacketScoreboard.IScoreboardCriteria = Class.forName("net.minecraft.world.scores.criteria.IScoreboardCriteria");
PacketScoreboard.EnumScoreboardHealthDisplay = (Class<Enum>) Class.forName("net.minecraft.world.scores.criteria.IScoreboardCriteria$EnumScoreboardHealthDisplay");
PacketScoreboard.EnumScoreboardAction = (Class<Enum>) Class.forName("net.minecraft.server.ScoreboardServer$Action");
PacketScoreboard.TeamPacketClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutScoreboardTeam");
PacketScoreboard.ScoreboardTeam = Class.forName("net.minecraft.world.scores.ScoreboardTeam");
PacketScoreboard.EnumNameTagVisibility = (Class<Enum>) Class.forName("net.minecraft.world.scores.ScoreboardTeamBase$EnumNameTagVisibility");
PacketScoreboard.EnumTeamPush = (Class<Enum>) Class.forName("net.minecraft.world.scores.ScoreboardTeamBase$EnumTeamPush");
DataWatcher.CLASS = Class.forName("net.minecraft.network.syncher.DataWatcher");
DataWatcherItem.CLASS = Class.forName("net.minecraft.network.syncher.DataWatcher$Item");
DataWatcherObject.CLASS = Class.forName("net.minecraft.network.syncher.DataWatcherObject");
DataWatcherHelper.DataWatcherRegistry = Class.forName("net.minecraft.network.syncher.DataWatcherRegistry");
DataWatcherHelper.DataWatcherSerializer = Class.forName("net.minecraft.network.syncher.DataWatcherSerializer");
if (is1_19_3Plus()) {
DataWatcher.DataValue = Class.forName("net.minecraft.network.syncher.DataWatcher$b");
}
// Entities
if (minorVersion >= 19) {
PacketPlayOutSpawnEntityLivingStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutSpawnEntity");
} else {
PacketPlayOutSpawnEntityLivingStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutSpawnEntityLiving");
}
PacketPlayOutEntityTeleportStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutEntityTeleport");
PacketPlayInUseEntity = Class.forName("net.minecraft.network.protocol.game.PacketPlayInUseEntity");
PacketPlayInUseEntity$d = Class.forName("net.minecraft.network.protocol.game.PacketPlayInUseEntity$d");
PacketPlayOutEntity = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutEntity");
PacketPlayOutEntityDestroyStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutEntityDestroy");
PacketPlayOutEntityLook = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutEntity$PacketPlayOutEntityLook");
PacketPlayOutEntityMetadataStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutEntityMetadata");
PacketPlayOutNamedEntitySpawn = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutNamedEntitySpawn");
EnumEntityUseAction = (Class<Enum>) Class.forName("net.minecraft.network.protocol.game.PacketPlayInUseEntity$EnumEntityUseAction");
if (minorVersion >= 19) {
PacketPlayOutSpawnEntityLivingStorage.EntityTypes = Class.forName("net.minecraft.world.entity.EntityTypes");
}
// Player Info
if (minorVersion >= 19) {
PacketPlayOutPlayerInfoStorage.ProfilePublicKey$a = Class.forName("net.minecraft.world.entity.player.ProfilePublicKey$a");
}
BukkitTabList.EnumGamemodeClass = (Class<Enum>) Class.forName("net.minecraft.world.level.EnumGamemode");
PacketPlayOutSpawnEntityLivingStorage.Vec3D = Class.forName("net.minecraft.world.phys.Vec3D");
PacketPlayOutSpawnEntityLivingStorage.EntityTypes = Class.forName("net.minecraft.world.entity.EntityTypes");
PacketPlayOutSpawnEntityLivingStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutSpawnEntity");
if (is1_19_3Plus()) {
PacketPlayOutPlayerInfoStorage.ClientboundPlayerInfoRemovePacket = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket");
PacketPlayOutPlayerInfoStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket");
PacketPlayOutPlayerInfoStorage.EnumPlayerInfoActionClass = (Class<Enum>) Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket$a");
PacketPlayOutPlayerInfoStorage.PlayerInfoDataStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket$b");
PacketPlayOutPlayerInfoStorage.RemoteChatSession$Data = Class.forName("net.minecraft.network.chat.RemoteChatSession$a");
BukkitTabList.ClientboundPlayerInfoRemovePacket = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket");
BukkitTabList.PacketPlayOutPlayerInfoClass = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket");
BukkitTabList.EnumPlayerInfoActionClass = (Class<Enum>) Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket$a");
BukkitTabList.PlayerInfoDataClass = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket$b");
BukkitTabList.RemoteChatSession$Data = Class.forName("net.minecraft.network.chat.RemoteChatSession$a");
} else {
PacketPlayOutPlayerInfoStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutPlayerInfo");
PacketPlayOutPlayerInfoStorage.EnumPlayerInfoActionClass = (Class<Enum>) Class.forName("net.minecraft.network.protocol.game.PacketPlayOutPlayerInfo$EnumPlayerInfoAction");
PacketPlayOutPlayerInfoStorage.PlayerInfoDataStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutPlayerInfo$PlayerInfoData");
BukkitTabList.PacketPlayOutPlayerInfoClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutPlayerInfo");
BukkitTabList.EnumPlayerInfoActionClass = (Class<Enum>) Class.forName("net.minecraft.network.protocol.game.PacketPlayOutPlayerInfo$EnumPlayerInfoAction");
BukkitTabList.PlayerInfoDataClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutPlayerInfo$PlayerInfoData");
}
PacketPlayOutPlayerInfoStorage.EnumGamemodeClass = (Class<Enum>) Class.forName("net.minecraft.world.level.EnumGamemode");
// Scoreboard
PacketPlayOutScoreboardDisplayObjectiveStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutScoreboardDisplayObjective");
PacketPlayOutScoreboardObjectiveStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutScoreboardObjective");
Scoreboard = Class.forName("net.minecraft.world.scores.Scoreboard");
PacketPlayOutScoreboardScoreStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutScoreboardScore");
PacketPlayOutScoreboardObjectiveStorage.ScoreboardObjective = Class.forName("net.minecraft.world.scores.ScoreboardObjective");
PacketPlayOutScoreboardScoreStorage.ScoreboardScore = Class.forName("net.minecraft.world.scores.ScoreboardScore");
IScoreboardCriteria = Class.forName("net.minecraft.world.scores.criteria.IScoreboardCriteria");
PacketPlayOutScoreboardObjectiveStorage.EnumScoreboardHealthDisplay = (Class<Enum>) Class.forName("net.minecraft.world.scores.criteria.IScoreboardCriteria$EnumScoreboardHealthDisplay");
PacketPlayOutScoreboardScoreStorage.EnumScoreboardAction = (Class<Enum>) Class.forName("net.minecraft.server.ScoreboardServer$Action");
PacketPlayOutScoreboardTeamStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutScoreboardTeam");
PacketPlayOutScoreboardTeamStorage.ScoreboardTeam = Class.forName("net.minecraft.world.scores.ScoreboardTeam");
PacketPlayOutScoreboardTeamStorage.EnumNameTagVisibility = (Class<Enum>) Class.forName("net.minecraft.world.scores.ScoreboardTeamBase$EnumNameTagVisibility");
PacketPlayOutScoreboardTeamStorage.EnumTeamPush = (Class<Enum>) Class.forName("net.minecraft.world.scores.ScoreboardTeamBase$EnumTeamPush");
PacketPlayOutScoreboardTeamStorage.PlayerAction = (Class<Enum>) Class.forName("net.minecraft.network.protocol.game.PacketPlayOutScoreboardTeam$a");
}
}
@@ -1,118 +0,0 @@
package me.neznamy.tab.platforms.bukkit.nms.storage.nms;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherHelper;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherItem;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherObject;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.*;
/**
* NMS loader for minecraft 1.17+ using Mojang packaging and names.
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public class MojangModernNMSStorage extends NMSStorage {
public MojangModernNMSStorage() throws ReflectiveOperationException {}
@Override
public void loadNamedFieldsAndMethods() throws ReflectiveOperationException {
(PING = EntityPlayer.getDeclaredField("latency")).setAccessible(true);
ChatSerializer_DESERIALIZE = ChatSerializer.getMethod("fromJson", String.class);
DataWatcher.REGISTER = DataWatcher.CLASS.getMethod("define", DataWatcherObject.CLASS, Object.class);
PacketPlayOutScoreboardScoreStorage.ScoreboardScore_setScore = PacketPlayOutScoreboardScoreStorage.ScoreboardScore.getMethod("setScore", int.class);
PacketPlayOutScoreboardTeamStorage.ScoreboardTeam_setAllowFriendlyFire = PacketPlayOutScoreboardTeamStorage.ScoreboardTeam.getMethod("setAllowFriendlyFire", boolean.class);
PacketPlayOutScoreboardTeamStorage.ScoreboardTeam_setCanSeeFriendlyInvisibles = PacketPlayOutScoreboardTeamStorage.ScoreboardTeam.getMethod("setSeeFriendlyInvisibles", boolean.class);
PacketPlayOutScoreboardTeamStorage.ScoreboardTeam_setPrefix = PacketPlayOutScoreboardTeamStorage.ScoreboardTeam.getMethod("setPlayerPrefix", IChatBaseComponent);
PacketPlayOutScoreboardTeamStorage.ScoreboardTeam_setSuffix = PacketPlayOutScoreboardTeamStorage.ScoreboardTeam.getMethod("setPlayerSuffix", IChatBaseComponent);
PacketPlayOutScoreboardTeamStorage.ScoreboardTeam_setNameTagVisibility = PacketPlayOutScoreboardTeamStorage.ScoreboardTeam.getMethod("setNameTagVisibility", PacketPlayOutScoreboardTeamStorage.EnumNameTagVisibility);
DataWatcherHelper.DataWatcherSerializer_BYTE = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("BYTE").get(null);
DataWatcherHelper.DataWatcherSerializer_FLOAT = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("FLOAT").get(null);
DataWatcherHelper.DataWatcherSerializer_STRING = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("STRING").get(null);
DataWatcherHelper.DataWatcherSerializer_OPTIONAL_COMPONENT = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("OPTIONAL_COMPONENT").get(null);
DataWatcherHelper.DataWatcherSerializer_BOOLEAN = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("BOOLEAN").get(null);
if (minorVersion >= 19) {
PacketPlayOutSpawnEntityLivingStorage.EntityTypes_ARMOR_STAND = PacketPlayOutSpawnEntityLivingStorage.EntityTypes.getDeclaredField("ARMOR_STAND").get(null);
(PacketPlayOutSpawnEntityLivingStorage.ENTITY_TYPE = PacketPlayOutSpawnEntityLivingStorage.CLASS.getDeclaredField("type")).setAccessible(true);
DataWatcher.packDirty = DataWatcher.CLASS.getMethod("packDirty");
}
}
@Override
public void loadClasses() throws ClassNotFoundException {
IChatBaseComponent = Class.forName("net.minecraft.network.chat.Component");
ChatSerializer = Class.forName("net.minecraft.network.chat.Component$Serializer");
World = Class.forName("net.minecraft.world.level.Level");
EntityArmorStand = Class.forName("net.minecraft.world.entity.decoration.ArmorStand");
EntityHuman = Class.forName("net.minecraft.world.entity.player.Player");
NetworkManager = Class.forName("net.minecraft.network.Connection");
Packet = Class.forName("net.minecraft.network.protocol.Packet");
EnumChatFormat = (Class<Enum>) Class.forName("net.minecraft.ChatFormatting");
EntityPlayer = Class.forName("net.minecraft.server.level.ServerPlayer");
Entity = Class.forName("net.minecraft.world.entity.Entity");
EntityLiving = Class.forName("net.minecraft.world.entity.LivingEntity");
PlayerConnection = Class.forName("net.minecraft.server.network.ServerGamePacketListenerImpl");
PacketPlayOutPlayerListHeaderFooterStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.ClientboundTabListPacket");
// DataWatcher
DataWatcher.CLASS = Class.forName("net.minecraft.network.syncher.SynchedEntityData");
DataWatcherItem.CLASS = Class.forName("net.minecraft.network.syncher.SynchedEntityData$DataItem");
DataWatcherObject.CLASS = Class.forName("net.minecraft.network.syncher.EntityDataAccessor");
DataWatcherHelper.DataWatcherRegistry = Class.forName("net.minecraft.network.syncher.EntityDataSerializers");
DataWatcherHelper.DataWatcherSerializer = Class.forName("net.minecraft.network.syncher.EntityDataSerializer");
if (is1_19_3Plus()) {
DataWatcher.DataValue = Class.forName("net.minecraft.network.syncher.SynchedEntityData$DataValue");
}
// Entities
if (minorVersion >= 19) {
PacketPlayOutSpawnEntityLivingStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.ClientboundAddEntityPacket");
} else {
PacketPlayOutSpawnEntityLivingStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.ClientboundAddMobPacket");
}
PacketPlayOutEntityTeleportStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.ClientboundTeleportEntityPacket");
PacketPlayInUseEntity = Class.forName("net.minecraft.network.protocol.game.ServerboundInteractPacket");
PacketPlayInUseEntity$d = Class.forName("net.minecraft.network.protocol.game.ServerboundInteractPacket$InteractionAction");
PacketPlayOutEntity = Class.forName("net.minecraft.network.protocol.game.ClientboundMoveEntityPacket");
PacketPlayOutEntityDestroyStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.ClientboundRemoveEntitiesPacket");
PacketPlayOutEntityLook = Class.forName("net.minecraft.network.protocol.game.ClientboundMoveEntityPacket$Rot");
PacketPlayOutEntityMetadataStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket");
PacketPlayOutNamedEntitySpawn = Class.forName("net.minecraft.network.protocol.game.ClientboundAddPlayerPacket");
EnumEntityUseAction = (Class<Enum>) Class.forName("net.minecraft.network.protocol.game.ServerboundInteractPacket$Action");
if (minorVersion >= 19) {
PacketPlayOutSpawnEntityLivingStorage.EntityTypes = Class.forName("net.minecraft.world.entity.EntityType");
}
// Player Info
if (minorVersion >= 19) {
PacketPlayOutPlayerInfoStorage.ProfilePublicKey$a = Class.forName("net.minecraft.world.entity.player.ProfilePublicKey$Data");
}
if (is1_19_3Plus()) {
PacketPlayOutPlayerInfoStorage.ClientboundPlayerInfoRemovePacket = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket");
PacketPlayOutPlayerInfoStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket");
PacketPlayOutPlayerInfoStorage.EnumPlayerInfoActionClass = (Class<Enum>) Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket$Action");
PacketPlayOutPlayerInfoStorage.PlayerInfoDataStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket$Entry");
PacketPlayOutPlayerInfoStorage.RemoteChatSession$Data = Class.forName("net.minecraft.network.chat.RemoteChatSession$Data");
} else {
PacketPlayOutPlayerInfoStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket");
PacketPlayOutPlayerInfoStorage.EnumPlayerInfoActionClass = (Class<Enum>) Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket$Action");
PacketPlayOutPlayerInfoStorage.PlayerInfoDataStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket$PlayerUpdate");
}
PacketPlayOutPlayerInfoStorage.EnumGamemodeClass = (Class<Enum>) Class.forName("net.minecraft.world.level.GameType");
// Scoreboard
PacketPlayOutScoreboardDisplayObjectiveStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.ClientboundSetDisplayObjectivePacket");
PacketPlayOutScoreboardObjectiveStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.ClientboundSetObjectivePacket");
Scoreboard = Class.forName("net.minecraft.world.scores.Scoreboard");
PacketPlayOutScoreboardScoreStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.ClientboundSetScorePacket");
PacketPlayOutScoreboardObjectiveStorage.ScoreboardObjective = Class.forName("net.minecraft.world.scores.Objective");
PacketPlayOutScoreboardScoreStorage.ScoreboardScore = Class.forName("net.minecraft.world.scores.Score");
IScoreboardCriteria = Class.forName("net.minecraft.world.scores.criteria.ObjectiveCriteria");
PacketPlayOutScoreboardObjectiveStorage.EnumScoreboardHealthDisplay = (Class<Enum>) Class.forName("net.minecraft.world.scores.criteria.ObjectiveCriteria$RenderType");
PacketPlayOutScoreboardScoreStorage.EnumScoreboardAction = (Class<Enum>) Class.forName("net.minecraft.server.ServerScoreboard$Method");
PacketPlayOutScoreboardTeamStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket");
PacketPlayOutScoreboardTeamStorage.ScoreboardTeam = Class.forName("net.minecraft.world.scores.PlayerTeam");
PacketPlayOutScoreboardTeamStorage.EnumNameTagVisibility = (Class<Enum>) Class.forName("net.minecraft.world.scores.Team$Visibility");
PacketPlayOutScoreboardTeamStorage.EnumTeamPush = (Class<Enum>) Class.forName("net.minecraft.world.scores.Team$CollisionRule");
PacketPlayOutScoreboardTeamStorage.PlayerAction = (Class<Enum>) Class.forName("net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket$Action");
}
}
@@ -4,25 +4,25 @@ import com.mojang.authlib.GameProfile;
import io.netty.channel.Channel;
import lombok.Getter;
import lombok.Setter;
import me.neznamy.tab.api.ProtocolVersion;
import me.neznamy.tab.api.TabAPI;
import me.neznamy.tab.api.chat.IChatBaseComponent;
import me.neznamy.tab.api.chat.WrappedChatComponent;
import me.neznamy.tab.api.util.ComponentCache;
import me.neznamy.tab.api.util.ReflectionUtils;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.BukkitTabList;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherHelper;
import me.neznamy.tab.platforms.bukkit.scoreboard.PacketScoreboard;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.util.ComponentCache;
import me.neznamy.tab.shared.util.ReflectionUtils;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherItem;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherObject;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.*;
import org.bukkit.Bukkit;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Class holding all NMS classes, methods, fields and constructors used by TAB.
@@ -34,7 +34,7 @@ public abstract class NMSStorage {
@Getter @Setter private static NMSStorage instance;
/** Server's NMS/CraftBukkit package */
protected final String serverPackage = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3];
@Getter protected final String serverPackage = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3];
/** Server's minor version */
@Getter protected final int minorVersion = Integer.parseInt(serverPackage.split("_")[1]);
@@ -54,7 +54,10 @@ public abstract class NMSStorage {
public Class<?> Entity;
public Class<?> EntityLiving;
protected Class<?> PlayerConnection;
// Ping field for 1.5.2 - 1.16.5, 1.17+ has Player#getPing()
public Field PING;
public Field PLAYER_CONNECTION;
public Field NETWORK_MANAGER;
public Field CHANNEL;
@@ -68,11 +71,6 @@ public abstract class NMSStorage {
public Method ChatSerializer_DESERIALIZE;
/** Other entity packets */
public Class<?> PacketPlayInUseEntity;
public Class<?> PacketPlayInUseEntity$d;
protected Class<Enum> EnumEntityUseAction;
public Field PacketPlayInUseEntity_ACTION;
public Class<?> PacketPlayOutEntity;
public Field PacketPlayOutEntity_ENTITYID;
@@ -81,13 +79,6 @@ public abstract class NMSStorage {
public Class<?> PacketPlayOutNamedEntitySpawn;
public Field PacketPlayOutNamedEntitySpawn_ENTITYID;
/** Scoreboard objectives */
public Class<?> Scoreboard;
protected Constructor<?> newScoreboard;
protected Class<?> IScoreboardCriteria;
public Field IScoreboardCriteria_self;
public Object emptyScoreboard;
public Object dummyEntity;
private final ComponentCache<IChatBaseComponent, Object> componentCache = new ComponentCache<>(10000,
@@ -95,55 +86,51 @@ public abstract class NMSStorage {
/**
* Constructs new instance and attempts to load all fields, methods and constructors.
*
* @throws ReflectiveOperationException
* If some field, constructor or method was not found
*/
public NMSStorage() throws ReflectiveOperationException {
@SneakyThrows
public NMSStorage() {
ProtocolVersion.UNKNOWN_SERVER_VERSION.setMinorVersion(minorVersion); //fixing compatibility with forks that set version field value to "Unknown"
loadClasses();
if (minorVersion >= 7) {
NETWORK_MANAGER = getFields(PlayerConnection, NetworkManager).get(0);
NETWORK_MANAGER = ReflectionUtils.getOnlyField(PlayerConnection, NetworkManager);
sendPacket = ReflectionUtils.getMethods(PlayerConnection, void.class, Packet).get(0);
} else {
sendPacket = ReflectionUtils.getMethod(PlayerConnection, new String[]{"sendPacket"}, Packet);
}
if (minorVersion >= 8) {
CHANNEL = getFields(NetworkManager, Channel.class).get(0);
getProfile = getMethods(EntityHuman, GameProfile.class).get(0);
ChatSerializer_DESERIALIZE = ChatSerializer.getMethod("a", String.class);
CHANNEL = ReflectionUtils.getOnlyField(NetworkManager, Channel.class);
try {
getProfile = ReflectionUtils.getOnlyMethod(EntityHuman, GameProfile.class);
} catch (IllegalStateException catServer) {
getProfile = EntityHuman.getMethod("getProfile");
}
Constructor<?> newEntityArmorStand = EntityArmorStand.getConstructor(World, double.class, double.class, double.class);
Method World_getHandle = Class.forName("org.bukkit.craftbukkit." + serverPackage + ".CraftWorld").getMethod("getHandle");
dummyEntity = newEntityArmorStand.newInstance(World_getHandle.invoke(Bukkit.getWorlds().get(0)), 0, 0, 0);
BukkitTabList.load(this);
}
PLAYER_CONNECTION = getFields(EntityPlayer, PlayerConnection).get(0);
if (minorVersion >= 9) {
DataWatcherObject.load();
}
PLAYER_CONNECTION = ReflectionUtils.getOnlyField(EntityPlayer, PlayerConnection);
getHandle = Class.forName("org.bukkit.craftbukkit." + serverPackage + ".entity.CraftPlayer").getMethod("getHandle");
sendPacket = getMethods(PlayerConnection, void.class, Packet).get(0);
newScoreboard = Scoreboard.getConstructor();
emptyScoreboard = newScoreboard.newInstance();
DataWatcher.load(this);
DataWatcherItem.load(this);
DataWatcherObject.load(this);
PacketPlayOutEntityDestroyStorage.load(this);
DataWatcherHelper.load(this);
PacketPlayOutEntityDestroyStorage.load();
PacketPlayOutEntityMetadataStorage.load(this);
PacketPlayOutEntityTeleportStorage.load(this);
PacketPlayOutSpawnEntityLivingStorage.load(this);
PacketPlayOutPlayerListHeaderFooterStorage.load(this);
PacketPlayOutPlayerInfoStorage.load(this);
PacketPlayOutScoreboardObjectiveStorage.load(this);
PacketPlayOutScoreboardDisplayObjectiveStorage.load(this);
otherEntity();
PacketPlayOutScoreboardTeamStorage.load(this);
PacketPlayOutScoreboardScoreStorage.load(this);
IScoreboardCriteria_self = getFields(IScoreboardCriteria, IScoreboardCriteria).get(0);
loadNamedFieldsAndMethods();
}
/**
* Loads fields for other entity packets
*
*/
protected void otherEntity() {
PacketPlayOutEntity_ENTITYID = getFields(PacketPlayOutEntity, int.class).get(0);
PacketPlayOutNamedEntitySpawn_ENTITYID = getFields(PacketPlayOutNamedEntitySpawn, int.class).get(0);
if (minorVersion >= 7) {
PacketPlayInUseEntity_ACTION = getFields(PacketPlayInUseEntity, EnumEntityUseAction).get(0);
PacketScoreboard.load(this);
PacketPlayOutEntity_ENTITYID = ReflectionUtils.getFields(PacketPlayOutEntity, int.class).get(0);
PacketPlayOutNamedEntitySpawn_ENTITYID = ReflectionUtils.getFields(PacketPlayOutNamedEntitySpawn, int.class).get(0);
if (minorVersion < 17) {
try {
(PING = EntityPlayer.getDeclaredField("ping")).setAccessible(true); // 1.5.2 - 1.16.5
} catch (NoSuchFieldException e) {
(PING = EntityPlayer.getDeclaredField("field_71138_i")).setAccessible(true); // 1.7.10 Thermos
}
}
}
@@ -155,101 +142,6 @@ public abstract class NMSStorage {
*/
public abstract void loadClasses() throws ClassNotFoundException;
/**
* Loads all fields and methods which must be loaded by their name due to
* lack of other methods of reliably retrieving them.
*
* @throws ReflectiveOperationException
* If a field or method was not found
*/
public abstract void loadNamedFieldsAndMethods() throws ReflectiveOperationException;
/**
* Returns method with specified possible names and parameters. Throws exception if no such method was found
*
* @param clazz
* lass to get method from
* @param names
* possible method names
* @param parameterTypes
* parameter types of the method
* @return method with specified name and parameters
* @throws NoSuchMethodException
* if no such method exists
*/
protected Method getMethod(Class<?> clazz, String[] names, Class<?>... parameterTypes) throws NoSuchMethodException {
for (String name : names) {
try {
return clazz.getMethod(name, parameterTypes);
} catch (NoSuchMethodException e) {
//not the first method in array
}
}
List<String> list = new ArrayList<>();
for (Method m : clazz.getMethods()) {
if (m.getParameterCount() != parameterTypes.length) continue;
Class<?>[] types = m.getParameterTypes();
boolean valid = true;
for (int i=0; i<types.length; i++) {
if (types[i] != parameterTypes[i]) {
valid = false;
break;
}
}
if (valid) list.add(m.getName());
}
throw new NoSuchMethodException("No method found with possible names " + Arrays.toString(names) + " with parameters " +
Arrays.toString(parameterTypes) + " in class " + clazz.getName() + ". Methods with matching parameters: " + list);
}
/**
* Returns all methods from class which return specified class type and have specified parameter types.
*
* @param clazz
* Class to get methods from
* @param returnType
* Return type of methods
* @param parameterTypes
* Parameter types of methods
* @return List of found methods matching requirements. If nothing is found, empty list is returned.
*/
public List<Method> getMethods(Class<?> clazz, Class<?> returnType, Class<?>... parameterTypes) {
List<Method> list = new ArrayList<>();
for (Method m : clazz.getDeclaredMethods()) {
if (m.getReturnType() != returnType || m.getParameterCount() != parameterTypes.length || !Modifier.isPublic(m.getModifiers())) continue;
Class<?>[] types = m.getParameterTypes();
boolean valid = true;
for (int i=0; i<types.length; i++) {
if (types[i] != parameterTypes[i]) {
valid = false;
break;
}
}
if (valid) list.add(m);
}
return list;
}
/**
* Returns all fields of class with defined class type
*
* @param clazz
* class to check fields of
* @param type
* field type to check for
* @return list of all fields with specified class type
*/
public List<Field> getFields(Class<?> clazz, Class<?> type) {
List<Field> list = new ArrayList<>();
for (Field field : clazz.getDeclaredFields()) {
if (field.getType() == type) {
field.setAccessible(true);
list.add(field);
}
}
return list;
}
/**
* Converts TAB's IChatBaseComponent into minecraft's component using String deserialization.
* If the requested component is found in cache, it is returned. If not, it is created, added into cache and returned.
@@ -261,24 +153,7 @@ public abstract class NMSStorage {
* client version used to decide RGB conversion
* @return converted component or {@code null} if {@code component} is {@code null}
*/
public Object toNMSComponent(IChatBaseComponent component, ProtocolVersion clientVersion) {
if (component instanceof WrappedChatComponent) return ((WrappedChatComponent) component).getOriginalComponent();
public @Nullable Object toNMSComponent(@NotNull IChatBaseComponent component, @NotNull ProtocolVersion clientVersion) {
return componentCache.get(component, clientVersion);
}
/**
* Creates a new Scoreboard Objective with given name.
*
* @param objectiveName
* Objective name
* @return NMS Objective
* @throws ReflectiveOperationException
* if thrown by reflective operation
*/
public Object newScoreboardObjective(String objectiveName) throws ReflectiveOperationException {
if (minorVersion >= 13) {
return PacketPlayOutScoreboardObjectiveStorage.newScoreboardObjective.newInstance(null, objectiveName, null, toNMSComponent(new IChatBaseComponent(""), TabAPI.getInstance().getServerVersion()), null);
}
return PacketPlayOutScoreboardObjectiveStorage.newScoreboardObjective.newInstance(null, objectiveName, IScoreboardCriteria_self.get(null));
}
}
@@ -1,35 +0,0 @@
package me.neznamy.tab.platforms.bukkit.nms.storage.nms;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutScoreboardScoreStorage;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutScoreboardTeamStorage;
/**
* NMS loader for Thermos 1.7.10.
*/
public class ThermosNMSStorage extends BukkitLegacyNMSStorage {
public ThermosNMSStorage() throws ReflectiveOperationException {}
@Override
public Class<?> getLegacyClass(String name) throws ClassNotFoundException {
try {
return getClass().getClassLoader().loadClass("net.minecraft.server." + serverPackage + "." + name);
} catch (NullPointerException e) {
// nested class not found
throw new ClassNotFoundException(name);
}
}
@Override
public void loadNamedFieldsAndMethods() throws ReflectiveOperationException {
(PING = EntityPlayer.getDeclaredField("field_71138_i")).setAccessible(true);
PacketPlayOutScoreboardScoreStorage.ScoreboardScore_setScore = PacketPlayOutScoreboardScoreStorage.ScoreboardScore.getMethod("func_96647_c", int.class);
PacketPlayOutScoreboardTeamStorage.ScoreboardTeam_setAllowFriendlyFire = PacketPlayOutScoreboardTeamStorage.ScoreboardTeam.getMethod("func_96660_a", boolean.class);
PacketPlayOutScoreboardTeamStorage.ScoreboardTeam_setCanSeeFriendlyInvisibles = PacketPlayOutScoreboardTeamStorage.ScoreboardTeam.getMethod("func_98300_b", boolean.class);
ChatSerializer_DESERIALIZE = ChatSerializer.getMethod("func_150699_a", String.class);
DataWatcher.REGISTER = DataWatcher.CLASS.getMethod("func_75682_a", int.class, Object.class);
PacketPlayOutScoreboardTeamStorage.ScoreboardTeam_setPrefix = PacketPlayOutScoreboardTeamStorage.ScoreboardTeam.getMethod("func_96666_b", String.class);
PacketPlayOutScoreboardTeamStorage.ScoreboardTeam_setSuffix = PacketPlayOutScoreboardTeamStorage.ScoreboardTeam.getMethod("func_96662_c", String.class);
}
}
@@ -1,6 +1,6 @@
package me.neznamy.tab.platforms.bukkit.nms.storage.packet;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import me.neznamy.tab.shared.util.ReflectionUtils;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
@@ -18,13 +18,11 @@ public class PacketPlayOutEntityDestroyStorage {
/**
* Loads all required Fields and throws Exception if something went wrong
*
* @param nms
* NMS storage reference
* @throws NoSuchMethodException
* If something fails
*/
public static void load(NMSStorage nms) throws NoSuchMethodException {
(ENTITIES = CLASS.getDeclaredFields()[0]).setAccessible(true);
public static void load() throws NoSuchMethodException {
ENTITIES = ReflectionUtils.getOnlyField(CLASS);
try {
CONSTRUCTOR = CLASS.getConstructor(int[].class);
} catch (NoSuchMethodException e) {
@@ -4,7 +4,6 @@ import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.List;
/**
@@ -15,7 +14,6 @@ public class PacketPlayOutEntityMetadataStorage {
/** NMS Fields */
public static Class<?> CLASS;
public static Constructor<?> CONSTRUCTOR;
public static Field LIST;
/**
* Loads all required Fields and throws Exception if something went wrong
@@ -31,6 +29,5 @@ public class PacketPlayOutEntityMetadataStorage {
} else {
CONSTRUCTOR = CLASS.getConstructor(int.class, DataWatcher.CLASS, boolean.class);
}
LIST = nms.getFields(CLASS, List.class).get(0);
}
}
@@ -1,7 +1,9 @@
package me.neznamy.tab.platforms.bukkit.nms.storage.packet;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import me.neznamy.tab.shared.backend.Location;
import me.neznamy.tab.shared.util.ReflectionUtils;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
@@ -30,22 +32,22 @@ public class PacketPlayOutEntityTeleportStorage {
* If something fails
*/
public static void load(NMSStorage nms) throws NoSuchMethodException {
ENTITY_ID = nms.getFields(CLASS, int.class).get(0);
YAW = nms.getFields(CLASS, byte.class).get(0);
PITCH = nms.getFields(CLASS, byte.class).get(1);
ENTITY_ID = ReflectionUtils.getFields(CLASS, int.class).get(0);
YAW = ReflectionUtils.getFields(CLASS, byte.class).get(0);
PITCH = ReflectionUtils.getFields(CLASS, byte.class).get(1);
if (nms.getMinorVersion() >= 17) {
CONSTRUCTOR = CLASS.getConstructor(nms.Entity);
} else {
CONSTRUCTOR = CLASS.getConstructor();
}
if (nms.getMinorVersion() >= 9) {
X = nms.getFields(CLASS, double.class).get(0);
Y = nms.getFields(CLASS, double.class).get(1);
Z = nms.getFields(CLASS, double.class).get(2);
X = ReflectionUtils.getFields(CLASS, double.class).get(0);
Y = ReflectionUtils.getFields(CLASS, double.class).get(1);
Z = ReflectionUtils.getFields(CLASS, double.class).get(2);
} else {
X = nms.getFields(CLASS, int.class).get(1);
Y = nms.getFields(CLASS, int.class).get(2);
Z = nms.getFields(CLASS, int.class).get(3);
X = ReflectionUtils.getFields(CLASS, int.class).get(1);
Y = ReflectionUtils.getFields(CLASS, int.class).get(2);
Z = ReflectionUtils.getFields(CLASS, int.class).get(3);
}
}
@@ -53,10 +55,9 @@ public class PacketPlayOutEntityTeleportStorage {
* Converts this class into NMS packet
*
* @return NMS packet
* @throws ReflectiveOperationException
* If something went wrong
*/
public static Object build(int entityId, Location location) throws ReflectiveOperationException {
@SneakyThrows
public static Object build(int entityId, Location location) {
NMSStorage nms = NMSStorage.getInstance();
Object nmsPacket;
if (nms.getMinorVersion() >= 17) {
@@ -1,143 +0,0 @@
package me.neznamy.tab.platforms.bukkit.nms.storage.packet;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
import me.neznamy.tab.api.ProtocolVersion;
import me.neznamy.tab.api.tablist.TabList;
import me.neznamy.tab.api.tablist.TabListEntry;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import java.lang.reflect.*;
import java.util.*;
@SuppressWarnings({"unchecked", "rawtypes"})
public class PacketPlayOutPlayerInfoStorage {
public static Class<?> CLASS;
public static Constructor<?> CONSTRUCTOR;
public static Field ACTION;
public static Field PLAYERS;
public static Class<Enum> EnumPlayerInfoActionClass;
public static Class<Enum> EnumGamemodeClass;
public static Class<?> ProfilePublicKey$a;
//1.19.3+
public static Class<?> ClientboundPlayerInfoRemovePacket;
public static Class<?> RemoteChatSession$Data;
public static Constructor<?> newClientboundPlayerInfoRemovePacket;
public static Constructor<?> newRemoteChatSession$Data;
public static void load(NMSStorage nms) throws ReflectiveOperationException {
if (nms.getMinorVersion() < 8) return;
if (nms.is1_19_3Plus()) {
newClientboundPlayerInfoRemovePacket = ClientboundPlayerInfoRemovePacket.getConstructor(List.class);
CONSTRUCTOR = CLASS.getConstructor(EnumSet.class, Collection.class);
ACTION = nms.getFields(CLASS, EnumSet.class).get(0);
newRemoteChatSession$Data = RemoteChatSession$Data.getConstructor(UUID.class, ProfilePublicKey$a);
} else {
CONSTRUCTOR = CLASS.getConstructor(EnumPlayerInfoActionClass, Array.newInstance(nms.EntityPlayer, 0).getClass());
ACTION = nms.getFields(CLASS, EnumPlayerInfoActionClass).get(0);
}
PLAYERS = nms.getFields(CLASS, List.class).get(0);
PlayerInfoDataStorage.load(nms);
}
public static Object createPacket(String action, Collection<TabListEntry> entries, ProtocolVersion clientVersion) {
NMSStorage nms = NMSStorage.getInstance();
if (nms.getMinorVersion() < 8) return null;
try {
Object packet;
List<Object> players = new ArrayList<>();
if (NMSStorage.getInstance().is1_19_3Plus()) {
EnumSet<?> actions;
if (action.equals("ADD_PLAYER")) {
actions = EnumSet.allOf(EnumPlayerInfoActionClass);
} else {
actions = EnumSet.of(Enum.valueOf(EnumPlayerInfoActionClass, action));
}
packet = CONSTRUCTOR.newInstance(actions, Collections.emptyList());
for (TabListEntry entry : entries) {
GameProfile profile = new GameProfile(entry.getUniqueId(), entry.getName());
if (entry.getSkin() != null) profile.getProperties().put(TabList.TEXTURES_PROPERTY,
new Property(TabList.TEXTURES_PROPERTY, entry.getSkin().getValue(), entry.getSkin().getSignature()));
players.add(PlayerInfoDataStorage.newPlayerInfoData.newInstance(
entry.getUniqueId(),
profile,
entry.isListed(),
entry.getLatency(),
int2GameMode(entry.getGameMode()),
nms.toNMSComponent(entry.getDisplayName(), clientVersion),
entry.getChatSession()
));
}
} else {
packet = CONSTRUCTOR.newInstance(Enum.valueOf(EnumPlayerInfoActionClass, action),
Array.newInstance(NMSStorage.getInstance().EntityPlayer, 0));
for (TabListEntry entry : entries) {
GameProfile profile = new GameProfile(entry.getUniqueId(), entry.getName());
if (entry.getSkin() != null) profile.getProperties().put(TabList.TEXTURES_PROPERTY,
new Property(TabList.TEXTURES_PROPERTY, entry.getSkin().getValue(), entry.getSkin().getSignature()));
List<Object> parameters = new ArrayList<>();
if (PlayerInfoDataStorage.newPlayerInfoData.getParameterTypes()[0] == CLASS) {
parameters.add(packet);
}
parameters.add(profile);
parameters.add(entry.getLatency());
parameters.add(int2GameMode(entry.getGameMode()));
parameters.add(nms.toNMSComponent(entry.getDisplayName(), clientVersion));
if (nms.getMinorVersion() >= 19) parameters.add(entry.getChatSession());
players.add(PlayerInfoDataStorage.newPlayerInfoData.newInstance(parameters.toArray()));
}
}
PLAYERS.set(packet, players);
return packet;
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
public static Object int2GameMode(int gameMode) {
switch (gameMode) {
case 1: return Enum.valueOf(EnumGamemodeClass, "CREATIVE");
case 2: return Enum.valueOf(EnumGamemodeClass, "ADVENTURE");
case 3: return Enum.valueOf(EnumGamemodeClass, "SPECTATOR");
default: return Enum.valueOf(EnumGamemodeClass, "SURVIVAL");
}
}
public static int gameMode2Int(Object gameMode) {
switch (String.valueOf(gameMode)) {
case "CREATIVE": return 1;
case "ADVENTURE": return 2;
case "SPECTATOR": return 3;
default: return 0;
}
}
public static class PlayerInfoDataStorage {
public static Class<?> CLASS;
public static Constructor<?> newPlayerInfoData;
public static Method PlayerInfoData_getProfile;
public static Field PlayerInfoData_Latency;
public static Field PlayerInfoData_GameMode;
public static Field PlayerInfoData_DisplayName;
//1.19.3+
public static Field PlayerInfoData_Listed;
public static Field PlayerInfoData_RemoteChatSession;
public static void load(NMSStorage nms) {
if (nms.getMinorVersion() < 8) return;
newPlayerInfoData = CLASS.getConstructors()[0];
PlayerInfoData_getProfile = nms.getMethods(CLASS, GameProfile.class).get(0);
PlayerInfoData_Latency = nms.getFields(CLASS, int.class).get(0);
PlayerInfoData_GameMode = nms.getFields(CLASS, EnumGamemodeClass).get(0);
PlayerInfoData_DisplayName = nms.getFields(CLASS, nms.IChatBaseComponent).get(0);
if (nms.is1_19_3Plus()) {
PlayerInfoData_Listed = nms.getFields(CLASS, boolean.class).get(0);
PlayerInfoData_RemoteChatSession = nms.getFields(CLASS, RemoteChatSession$Data).get(0);
}
}
}
}
@@ -1,39 +0,0 @@
package me.neznamy.tab.platforms.bukkit.nms.storage.packet;
import me.neznamy.tab.api.ProtocolVersion;
import me.neznamy.tab.api.chat.IChatBaseComponent;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
public class PacketPlayOutPlayerListHeaderFooterStorage {
public static Class<?> CLASS;
public static Constructor<?> CONSTRUCTOR;
public static Field HEADER;
public static Field FOOTER;
public static void load(NMSStorage nms) throws NoSuchMethodException {
if (nms.getMinorVersion() < 8) return;
if (nms.getMinorVersion() >= 17) {
CONSTRUCTOR = CLASS.getConstructor(nms.IChatBaseComponent, nms.IChatBaseComponent);
} else {
CONSTRUCTOR = CLASS.getConstructor();
HEADER = nms.getFields(CLASS, nms.IChatBaseComponent).get(0);
FOOTER = nms.getFields(CLASS, nms.IChatBaseComponent).get(1);
}
}
public static Object build(IChatBaseComponent header, IChatBaseComponent footer, ProtocolVersion clientVersion) throws ReflectiveOperationException {
NMSStorage nms = NMSStorage.getInstance();
if (nms.getMinorVersion() < 8) return null;
if (CONSTRUCTOR.getParameterCount() == 2) {
return CONSTRUCTOR.newInstance(nms.toNMSComponent(header, clientVersion), nms.toNMSComponent(footer, clientVersion));
}
Object nmsPacket = CONSTRUCTOR.newInstance();
HEADER.set(nmsPacket, nms.toNMSComponent(header, clientVersion));
FOOTER.set(nmsPacket, nms.toNMSComponent(footer, clientVersion));
return nmsPacket;
}
}
@@ -1,28 +0,0 @@
package me.neznamy.tab.platforms.bukkit.nms.storage.packet;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
public class PacketPlayOutScoreboardDisplayObjectiveStorage {
public static Class<?> CLASS;
public static Constructor<?> CONSTRUCTOR;
public static Field POSITION;
public static Field OBJECTIVE_NAME;
public static void load(NMSStorage nms) throws NoSuchMethodException {
CONSTRUCTOR = CLASS.getConstructor(int.class, PacketPlayOutScoreboardObjectiveStorage.ScoreboardObjective);
POSITION = nms.getFields(CLASS, int.class).get(0);
OBJECTIVE_NAME = nms.getFields(CLASS, String.class).get(0);
}
public static Object buildSilent(int slot, String objective) {
try {
return CONSTRUCTOR.newInstance(slot, NMSStorage.getInstance().newScoreboardObjective(objective));
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
}
@@ -1,66 +0,0 @@
package me.neznamy.tab.platforms.bukkit.nms.storage.packet;
import me.neznamy.tab.api.ProtocolVersion;
import me.neznamy.tab.api.chat.IChatBaseComponent;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.List;
@SuppressWarnings({"unchecked", "rawtypes"})
public class PacketPlayOutScoreboardObjectiveStorage {
public static Class<?> CLASS;
public static Constructor<?> CONSTRUCTOR;
public static Field OBJECTIVE_NAME;
public static Field METHOD;
public static Field RENDER_TYPE;
public static Field DISPLAY_NAME;
public static Class<Enum> EnumScoreboardHealthDisplay;
public static Class<?> ScoreboardObjective;
public static Constructor<?> newScoreboardObjective;
public static void load(NMSStorage nms) throws NoSuchMethodException {
newScoreboardObjective = ScoreboardObjective.getConstructors()[0];
OBJECTIVE_NAME = nms.getFields(CLASS, String.class).get(0);
List<Field> list = nms.getFields(CLASS, int.class);
METHOD = list.get(list.size()-1);
if (nms.getMinorVersion() >= 8) {
RENDER_TYPE = nms.getFields(CLASS, EnumScoreboardHealthDisplay).get(0);
}
if (nms.getMinorVersion() >= 13) {
CONSTRUCTOR = CLASS.getConstructor(ScoreboardObjective, int.class);
DISPLAY_NAME = nms.getFields(CLASS, nms.IChatBaseComponent).get(0);
} else {
CONSTRUCTOR = CLASS.getConstructor();
DISPLAY_NAME = nms.getFields(CLASS, String.class).get(1);
}
}
public static Object buildSilent(int action, String objectiveName, String title, boolean hearts, ProtocolVersion clientVersion) {
try {
NMSStorage nms = NMSStorage.getInstance();
if (nms.getMinorVersion() >= 13) {
return CONSTRUCTOR.newInstance(newScoreboardObjective.newInstance(null, objectiveName, null,
nms.toNMSComponent(IChatBaseComponent.optimizedComponent(title), clientVersion),
asDisplayType(hearts)), action);
}
Object nmsPacket = CONSTRUCTOR.newInstance();
OBJECTIVE_NAME.set(nmsPacket, objectiveName);
DISPLAY_NAME.set(nmsPacket, title);
if (nms.getMinorVersion() >= 8) {
RENDER_TYPE.set(nmsPacket, asDisplayType(hearts));
}
METHOD.set(nmsPacket, action);
return nmsPacket;
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
private static Object asDisplayType(boolean hearts) {
return Enum.valueOf(EnumScoreboardHealthDisplay, hearts ? "HEARTS" : "INTEGER");
}
}
@@ -1,61 +0,0 @@
package me.neznamy.tab.platforms.bukkit.nms.storage.packet;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
@SuppressWarnings({"unchecked", "rawtypes"})
public class PacketPlayOutScoreboardScoreStorage {
public static Class<?> CLASS;
public static Constructor<?> CONSTRUCTOR_1_13;
public static Constructor<?> CONSTRUCTOR_String;
public static Constructor<?> CONSTRUCTOR;
public static Class<?> ScoreboardScore;
public static Constructor<?> newScoreboardScore;
public static Method ScoreboardScore_setScore;
public static Class<Enum> EnumScoreboardAction;
public static void load(NMSStorage nms) throws NoSuchMethodException {
newScoreboardScore = ScoreboardScore.getConstructor(nms.Scoreboard, PacketPlayOutScoreboardObjectiveStorage.ScoreboardObjective, String.class);
if (nms.getMinorVersion() >= 13) {
CONSTRUCTOR_1_13 = CLASS.getConstructor(EnumScoreboardAction, String.class, String.class, int.class);
} else {
CONSTRUCTOR_String = CLASS.getConstructor(String.class);
if (nms.getMinorVersion() >= 8) {
CONSTRUCTOR = CLASS.getConstructor(ScoreboardScore);
} else {
CONSTRUCTOR = CLASS.getConstructor(ScoreboardScore, int.class);
}
}
}
public static Object change(String objective, String player, int score) {
try {
NMSStorage nms = NMSStorage.getInstance();
if (nms.getMinorVersion() >= 13) {
return CONSTRUCTOR_1_13.newInstance(Enum.valueOf(EnumScoreboardAction, "CHANGE"), objective, player, score);
}
Object scoreboardScore = newScoreboardScore.newInstance(nms.emptyScoreboard, nms.newScoreboardObjective(objective), player);
ScoreboardScore_setScore.invoke(scoreboardScore, score);
if (nms.getMinorVersion() >= 8) {
return CONSTRUCTOR.newInstance(scoreboardScore);
}
return CONSTRUCTOR.newInstance(scoreboardScore, 0);
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
public static Object remove(String objective, String player) {
try {
if (NMSStorage.getInstance().getMinorVersion() >= 13) {
return CONSTRUCTOR_1_13.newInstance(Enum.valueOf(EnumScoreboardAction, "REMOVE"), objective, player, 0);
}
return CONSTRUCTOR_String.newInstance(player);
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
}
@@ -1,146 +0,0 @@
package me.neznamy.tab.platforms.bukkit.nms.storage.packet;
import me.neznamy.tab.api.ProtocolVersion;
import me.neznamy.tab.api.chat.EnumChatFormat;
import me.neznamy.tab.api.chat.IChatBaseComponent;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@SuppressWarnings({"unchecked", "rawtypes"})
public class PacketPlayOutScoreboardTeamStorage {
public static Class<?> CLASS;
public static Constructor<?> CONSTRUCTOR;
public static Method Constructor_of;
public static Method Constructor_ofBoolean;
public static Method Constructor_ofString;
public static Field NAME;
public static Field ACTION;
public static Field PLAYERS;
public static Class<Enum> PlayerAction;
public static Class<Enum> EnumNameTagVisibility;
public static Class<Enum> EnumTeamPush;
public static Class<?> ScoreboardTeam;
public static Constructor<?> newScoreboardTeam;
public static Method ScoreboardTeam_getPlayerNameSet;
public static Method ScoreboardTeam_setNameTagVisibility;
public static Method ScoreboardTeam_setCollisionRule;
public static Method ScoreboardTeam_setPrefix;
public static Method ScoreboardTeam_setSuffix;
public static Method ScoreboardTeam_setColor;
public static Method ScoreboardTeam_setAllowFriendlyFire;
public static Method ScoreboardTeam_setCanSeeFriendlyInvisibles;
public static void load(NMSStorage nms) throws NoSuchMethodException {
newScoreboardTeam = ScoreboardTeam.getConstructor(nms.Scoreboard, String.class);
NAME = nms.getFields(CLASS, String.class).get(0);
ACTION = getInstanceFields(CLASS, int.class).get(0);
PLAYERS = nms.getFields(CLASS, Collection.class).get(0);
ScoreboardTeam_getPlayerNameSet = nms.getMethods(ScoreboardTeam, Collection.class).get(0);
if (nms.getMinorVersion() >= 9) {
ScoreboardTeam_setCollisionRule = nms.getMethods(ScoreboardTeam, void.class, EnumTeamPush).get(0);
}
if (nms.getMinorVersion() >= 13) {
ScoreboardTeam_setColor = nms.getMethods(ScoreboardTeam, void.class, nms.EnumChatFormat).get(0);
}
if (nms.getMinorVersion() >= 17) {
Constructor_of = nms.getMethods(CLASS, CLASS, ScoreboardTeam).get(0);
Constructor_ofBoolean = nms.getMethods(CLASS, CLASS, ScoreboardTeam, boolean.class).get(0);
Constructor_ofString = nms.getMethods(CLASS, CLASS, ScoreboardTeam, String.class, PlayerAction).get(0);
} else {
CONSTRUCTOR = CLASS.getConstructor(ScoreboardTeam, int.class);
}
}
private static void createTeamModern(Object team, ProtocolVersion clientVersion, String prefix, String suffix, String visibility, String collision) throws ReflectiveOperationException {
NMSStorage nms = NMSStorage.getInstance();
if (prefix != null) ScoreboardTeam_setPrefix.invoke(team, nms.toNMSComponent(IChatBaseComponent.optimizedComponent(prefix), clientVersion));
if (suffix != null) ScoreboardTeam_setSuffix.invoke(team, nms.toNMSComponent(IChatBaseComponent.optimizedComponent(suffix), clientVersion));
ScoreboardTeam_setColor.invoke(team, Enum.valueOf(nms.EnumChatFormat, EnumChatFormat.lastColorsOf(prefix).toString()));
ScoreboardTeam_setNameTagVisibility.invoke(team, Enum.valueOf(EnumNameTagVisibility, String.valueOf(visibility).equals("always") ? "ALWAYS" : "NEVER"));
ScoreboardTeam_setCollisionRule.invoke(team, Enum.valueOf(EnumTeamPush, String.valueOf(collision).equals("always") ? "ALWAYS" : "NEVER"));
}
private static void createTeamLegacy(Object team, String prefix, String suffix, String visibility, String collision) throws ReflectiveOperationException {
NMSStorage nms = NMSStorage.getInstance();
if (prefix != null) ScoreboardTeam_setPrefix.invoke(team, prefix);
if (suffix != null) ScoreboardTeam_setSuffix.invoke(team, suffix);
if (nms.getMinorVersion() >= 8) ScoreboardTeam_setNameTagVisibility.invoke(team, Enum.valueOf(EnumNameTagVisibility, String.valueOf(visibility).equals("always") ? "ALWAYS" : "NEVER"));
if (nms.getMinorVersion() >= 9) ScoreboardTeam_setCollisionRule.invoke(team, Enum.valueOf(EnumTeamPush, String.valueOf(collision).equals("always") ? "ALWAYS" : "NEVER"));
}
private static List<Field> getInstanceFields(Class<?> clazz, Class<?> type) {
List<Field> list = new ArrayList<>();
for (Field field : clazz.getDeclaredFields()) {
if (field.getType() == type && !Modifier.isStatic(field.getModifiers())) {
field.setAccessible(true);
list.add(field);
}
}
return list;
}
public static Object register(String name, String prefix, String suffix, String visibility, String collision, Collection<String> players, int options, ProtocolVersion clientVersion) {
try {
NMSStorage nms = NMSStorage.getInstance();
Object team = newScoreboardTeam.newInstance(nms.emptyScoreboard, name);
((Collection<String>)ScoreboardTeam_getPlayerNameSet.invoke(team)).addAll(players);
ScoreboardTeam_setAllowFriendlyFire.invoke(team, (options & 0x1) > 0);
ScoreboardTeam_setCanSeeFriendlyInvisibles.invoke(team, (options & 0x2) > 0);
if (nms.getMinorVersion() >= 13) {
createTeamModern(team, clientVersion, prefix, suffix, visibility, collision);
} else {
createTeamLegacy(team, prefix, suffix, visibility, collision);
}
if (nms.getMinorVersion() >= 17) {
return Constructor_ofBoolean.invoke(null, team, true);
}
return CONSTRUCTOR.newInstance(team, 0);
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
public static Object unregister(String name) {
try {
NMSStorage nms = NMSStorage.getInstance();
Object team = newScoreboardTeam.newInstance(nms.emptyScoreboard, name);
if (nms.getMinorVersion() >= 17) {
return Constructor_of.invoke(null, team);
} else {
return CONSTRUCTOR.newInstance(team, 1);
}
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
public static Object update(String name, String prefix, String suffix, String visibility, String collision, int options, ProtocolVersion clientVersion) {
try {
NMSStorage nms = NMSStorage.getInstance();
Object team = newScoreboardTeam.newInstance(nms.emptyScoreboard, name);
ScoreboardTeam_setAllowFriendlyFire.invoke(team, (options & 0x1) > 0);
ScoreboardTeam_setCanSeeFriendlyInvisibles.invoke(team, (options & 0x2) > 0);
if (nms.getMinorVersion() >= 13) {
createTeamModern(team, clientVersion, prefix, suffix, visibility, collision);
} else {
createTeamLegacy(team, prefix, suffix, visibility, collision);
}
if (nms.getMinorVersion() >= 17) {
return Constructor_ofBoolean.invoke(null, team, false);
}
return CONSTRUCTOR.newInstance(team, 2);
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
}
@@ -1,9 +1,11 @@
package me.neznamy.tab.platforms.bukkit.nms.storage.packet;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import me.neznamy.tab.shared.backend.EntityData;
import me.neznamy.tab.shared.backend.Location;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.bukkit.entity.EntityType;
import java.lang.reflect.Constructor;
@@ -18,8 +20,15 @@ public class PacketPlayOutSpawnEntityLivingStorage {
/** NMS Fields */
public static Class<?> CLASS;
public static Class<?> EntityTypes;
public static Constructor<?> CONSTRUCTOR;
/** 1.17+ */
public static Class<?> Vec3D;
public static Class<?> EntityTypes;
public static Object Vec3D_Empty;
public static Object EntityTypes_ARMOR_STAND;
/** 1.16.5- */
public static Field ENTITY_ID;
public static Field ENTITY_TYPE;
public static Field YAW;
@@ -29,9 +38,6 @@ public class PacketPlayOutSpawnEntityLivingStorage {
public static Field Y;
public static Field Z;
public static Field DATA_WATCHER;
public static Object EntityTypes_ARMOR_STAND;
/** Entity types */
public static final EnumMap<EntityType, Integer> entityIds = new EnumMap<>(EntityType.class);
/**
@@ -42,51 +48,44 @@ public class PacketPlayOutSpawnEntityLivingStorage {
* @throws NoSuchMethodException
* If something fails
*/
public static void load(NMSStorage nms) throws NoSuchMethodException {
if (nms.getMinorVersion() >= 19) {
entityIds.put(EntityType.ARMOR_STAND, 2);
} else if (nms.getMinorVersion() >= 13) {
public static void load(NMSStorage nms) throws NoSuchMethodException, IllegalAccessException, NoSuchFieldException {
int minorVersion = nms.getMinorVersion();
if (minorVersion >= 13) {
entityIds.put(EntityType.ARMOR_STAND, 1);
} else {
entityIds.put(EntityType.WITHER, 64);
if (nms.getMinorVersion() >= 8) {
if (minorVersion >= 8) {
entityIds.put(EntityType.ARMOR_STAND, 30);
}
}
if (nms.getMinorVersion() >= 17) {
if (nms.is1_19_3Plus()) {
CONSTRUCTOR = CLASS.getConstructor(nms.Entity);
} else {
CONSTRUCTOR = CLASS.getConstructor(nms.EntityLiving);
}
if (minorVersion >= 19) {
EntityTypes_ARMOR_STAND = EntityTypes.getDeclaredField("d").get(null);
CONSTRUCTOR = CLASS.getConstructor(int.class, UUID.class, double.class, double.class, double.class, float.class, float.class, EntityTypes, int.class, Vec3D, double.class);
Vec3D_Empty = ReflectionUtils.getOnlyField(Vec3D, Vec3D).get(null);
} else if (minorVersion >= 17) {
EntityTypes_ARMOR_STAND = EntityTypes.getDeclaredField("c").get(null);
CONSTRUCTOR = CLASS.getConstructor(int.class, UUID.class, double.class, double.class, double.class, float.class, float.class, EntityTypes, int.class, Vec3D);
Vec3D_Empty = ReflectionUtils.getOnlyField(Vec3D, Vec3D).get(null);
} else {
CONSTRUCTOR = CLASS.getConstructor();
}
ENTITY_ID = nms.getFields(CLASS, int.class).get(0);
YAW = nms.getFields(CLASS, byte.class).get(0);
PITCH = nms.getFields(CLASS, byte.class).get(1);
if (nms.getMinorVersion() >= 9) {
UUID = nms.getFields(CLASS, UUID.class).get(0);
if (nms.getMinorVersion() >= 19) {
X = nms.getFields(CLASS, double.class).get(2);
Y = nms.getFields(CLASS, double.class).get(3);
Z = nms.getFields(CLASS, double.class).get(4);
ENTITY_ID = ReflectionUtils.getFields(CLASS, int.class).get(0);
YAW = ReflectionUtils.getFields(CLASS, byte.class).get(0);
PITCH = ReflectionUtils.getFields(CLASS, byte.class).get(1);
if (minorVersion >= 9) {
UUID = ReflectionUtils.getOnlyField(CLASS, UUID.class);
X = ReflectionUtils.getFields(CLASS, double.class).get(0);
Y = ReflectionUtils.getFields(CLASS, double.class).get(1);
Z = ReflectionUtils.getFields(CLASS, double.class).get(2);
} else {
X = nms.getFields(CLASS, double.class).get(0);
Y = nms.getFields(CLASS, double.class).get(1);
Z = nms.getFields(CLASS, double.class).get(2);
X = ReflectionUtils.getFields(CLASS, int.class).get(2);
Y = ReflectionUtils.getFields(CLASS, int.class).get(3);
Z = ReflectionUtils.getFields(CLASS, int.class).get(4);
}
ENTITY_TYPE = ReflectionUtils.getFields(CLASS, int.class).get(1);
if (minorVersion <= 14) {
DATA_WATCHER = ReflectionUtils.getOnlyField(CLASS, DataWatcher.CLASS);
}
} else {
X = nms.getFields(CLASS, int.class).get(2);
Y = nms.getFields(CLASS, int.class).get(3);
Z = nms.getFields(CLASS, int.class).get(4);
}
if (nms.getMinorVersion() < 19) {
ENTITY_TYPE = nms.getFields(CLASS, int.class).get(1);
}
if (nms.getMinorVersion() <= 14) {
DATA_WATCHER = nms.getFields(CLASS, DataWatcher.CLASS).get(0);
}
}
@@ -94,38 +93,34 @@ public class PacketPlayOutSpawnEntityLivingStorage {
* Converts this class into NMS packet
*
* @return NMS packet
* @throws ReflectiveOperationException
* If something went wrong
*/
public static Object build(int entityId, UUID id, Object entityType, Location location, EntityData data) throws ReflectiveOperationException {
NMSStorage nms = NMSStorage.getInstance();
Object nmsPacket;
if (nms.getMinorVersion() >= 17) {
nmsPacket = CONSTRUCTOR.newInstance(nms.dummyEntity);
} else {
nmsPacket = CONSTRUCTOR.newInstance();
@SneakyThrows
public static Object build(int id, UUID uuid, Object entityType, Location l, EntityData data) {
int minorVersion = NMSStorage.getInstance().getMinorVersion();
if (minorVersion >= 19) {
return CONSTRUCTOR.newInstance(id, uuid, l.getX(), l.getY(), l.getZ(), l.getYaw(), l.getPitch(), EntityTypes_ARMOR_STAND, 0, Vec3D_Empty, 0d);
}
ENTITY_ID.set(nmsPacket, entityId);
YAW.set(nmsPacket, (byte)(location.getYaw() * 256.0f / 360.0f));
PITCH.set(nmsPacket, (byte)(location.getPitch() * 256.0f / 360.0f));
if (nms.getMinorVersion() <= 14) {
if (minorVersion >= 17) {
return CONSTRUCTOR.newInstance(id, uuid, l.getX(), l.getY(), l.getZ(), l.getYaw(), l.getPitch(), EntityTypes_ARMOR_STAND, 0, Vec3D_Empty);
}
Object nmsPacket = CONSTRUCTOR.newInstance();
ENTITY_ID.set(nmsPacket, id);
YAW.set(nmsPacket, (byte)(l.getYaw() * 256.0f / 360.0f));
PITCH.set(nmsPacket, (byte)(l.getPitch() * 256.0f / 360.0f));
if (minorVersion <= 14) {
DATA_WATCHER.set(nmsPacket, data.build());
}
if (nms.getMinorVersion() >= 9) {
UUID.set(nmsPacket, id);
X.set(nmsPacket, location.getX());
Y.set(nmsPacket, location.getY());
Z.set(nmsPacket, location.getZ());
if (minorVersion >= 9) {
UUID.set(nmsPacket, uuid);
X.set(nmsPacket, l.getX());
Y.set(nmsPacket, l.getY());
Z.set(nmsPacket, l.getZ());
} else {
X.set(nmsPacket, floor(location.getX()*32));
Y.set(nmsPacket, floor(location.getY()*32));
Z.set(nmsPacket, floor(location.getZ()*32));
}
if (nms.getMinorVersion() >= 19) {
ENTITY_TYPE.set(nmsPacket, EntityTypes_ARMOR_STAND); // :(
} else {
ENTITY_TYPE.set(nmsPacket, entityIds.get((EntityType) entityType));
X.set(nmsPacket, floor(l.getX()*32));
Y.set(nmsPacket, floor(l.getY()*32));
Z.set(nmsPacket, floor(l.getZ()*32));
}
ENTITY_TYPE.set(nmsPacket, entityIds.get((EntityType) entityType));
return nmsPacket;
}
@@ -1,44 +0,0 @@
package me.neznamy.tab.platforms.bukkit.permission;
import lombok.Getter;
import me.neznamy.tab.api.TabConstants;
import org.bukkit.entity.Player;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.shared.permission.PermissionPlugin;
import net.milkbowl.vault.permission.Permission;
/**
* Vault permission hook
*/
public class Vault extends PermissionPlugin {
/** Permission plugin */
private final Permission permission;
@Getter private final String version = "Vault " + super.getVersion();
/**
* Constructs new instance with given parameters
*
* @param permission
* permission plugin implementation
* @param vaultVersion
* vault version
*/
public Vault(Permission permission, String vaultVersion) {
super(vaultVersion);
this.permission = permission;
}
@Override
public String getPrimaryGroup(TabPlayer p) {
if (getName().equals("SuperPerms")) return TabConstants.NO_GROUP; //Vault's dummy implementation throws exception on every request
return permission.getPrimaryGroup((Player) p.getPlayer());
}
@Override
public String getName() {
return permission.getName();
}
}
@@ -0,0 +1,176 @@
package me.neznamy.tab.platforms.bukkit.platform;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.SneakyThrows;
import me.clip.placeholderapi.PlaceholderAPI;
import me.neznamy.tab.platforms.bukkit.BukkitPipelineInjector;
import me.neznamy.tab.platforms.bukkit.BukkitPlaceholderRegistry;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.GroupManager;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.chat.rgb.RGBUtils;
import me.neznamy.tab.shared.features.injection.PipelineInjector;
import me.neznamy.tab.shared.features.types.TabFeature;
import me.neznamy.tab.platforms.bukkit.features.BukkitTabExpansion;
import me.neznamy.tab.platforms.bukkit.features.PerWorldPlayerList;
import me.neznamy.tab.platforms.bukkit.features.WitherBossBar;
import me.neznamy.tab.platforms.bukkit.features.BukkitNameTagX;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.backend.BackendPlatform;
import me.neznamy.tab.shared.features.PlaceholderManagerImpl;
import me.neznamy.tab.shared.features.bossbar.BossBarManagerImpl;
import me.neznamy.tab.shared.features.nametags.NameTag;
import me.neznamy.tab.shared.hook.LuckPermsHook;
import me.neznamy.tab.shared.placeholders.PlayerPlaceholderImpl;
import me.neznamy.tab.shared.placeholders.expansion.EmptyTabExpansion;
import me.neznamy.tab.shared.placeholders.expansion.TabExpansion;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.milkbowl.vault.permission.Permission;
import org.bukkit.Bukkit;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.plugin.RegisteredServiceProvider;
import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
/**
* Implementation of Platform interface for Bukkit platform
*/
@RequiredArgsConstructor
@Getter
public class BukkitPlatform implements BackendPlatform {
/** Plugin instance for registering tasks and events */
private final JavaPlugin plugin;
/** Variables checking presence of other plugins to hook into */
private final boolean placeholderAPI = ReflectionUtils.classExists("me.clip.placeholderapi.PlaceholderAPI");
@Setter private boolean libsDisguisesEnabled = ReflectionUtils.classExists("me.libraryaddict.disguise.DisguiseAPI");
public @NotNull BossBarManagerImpl getLegacyBossBar() {
return new WitherBossBar(plugin);
}
@Override
public void loadPlayers() {
for (Player p : getOnlinePlayers()) {
TAB.getInstance().addPlayer(new BukkitTabPlayer(p));
}
}
@Override
public void registerPlaceholders() {
new BukkitPlaceholderRegistry().registerPlaceholders(TAB.getInstance().getPlaceholderManager());
}
@Override
public @Nullable PipelineInjector createPipelineInjector() {
return NMSStorage.getInstance().getMinorVersion() >= 8 ? new BukkitPipelineInjector() : null;
}
@Override
public @NotNull NameTag getUnlimitedNameTags() {
return new BukkitNameTagX(plugin);
}
@Override
public @NotNull TabExpansion createTabExpansion() {
if (placeholderAPI) {
BukkitTabExpansion expansion = new BukkitTabExpansion();
expansion.register();
return expansion;
}
return new EmptyTabExpansion();
}
@Override
public @Nullable TabFeature getPerWorldPlayerList() {
return new PerWorldPlayerList(plugin);
}
/**
* Returns online players from Bukkit API
*
* @return online players from Bukkit API
*/
@SuppressWarnings("unchecked")
@SneakyThrows
private @NotNull Player[] getOnlinePlayers() {
Object players = Bukkit.class.getMethod("getOnlinePlayers").invoke(null);
if (players instanceof Player[]) {
//1.7-
return (Player[]) players;
} else {
//1.8+
return ((Collection<Player>)players).toArray(new Player[0]);
}
}
@Override
public void registerUnknownPlaceholder(@NotNull String identifier) {
PlaceholderManagerImpl pl = TAB.getInstance().getPlaceholderManager();
int refresh = pl.getRefreshInterval(identifier);
if (identifier.startsWith("%rel_")) {
//relational placeholder
TAB.getInstance().getPlaceholderManager().registerRelationalPlaceholder(identifier, pl.getRefreshInterval(identifier), (viewer, target) ->
placeholderAPI ? PlaceholderAPI.setRelationalPlaceholders((Player) viewer.getPlayer(), (Player) target.getPlayer(), identifier) : identifier);
} else if (identifier.startsWith("%sync:")) {
registerSyncPlaceholder(identifier, refresh);
} else if (identifier.startsWith("%server_")) {
TAB.getInstance().getPlaceholderManager().registerServerPlaceholder(identifier, refresh, () ->
placeholderAPI ? PlaceholderAPI.setPlaceholders(null, identifier) : identifier);
} else {
TAB.getInstance().getPlaceholderManager().registerPlayerPlaceholder(identifier, refresh, p ->
placeholderAPI ? PlaceholderAPI.setPlaceholders((Player) p.getPlayer(), identifier) : identifier);
}
}
public void registerSyncPlaceholder(String identifier, int refresh) {
String syncedPlaceholder = "%" + identifier.substring(6);
PlayerPlaceholderImpl[] ppl = new PlayerPlaceholderImpl[1];
ppl[0] = TAB.getInstance().getPlaceholderManager().registerPlayerPlaceholder(identifier, refresh, p -> {
Bukkit.getScheduler().runTask(plugin, () -> {
long time = System.nanoTime();
ppl[0].updateValue(p, placeholderAPI ? PlaceholderAPI.setPlaceholders((Player) p.getPlayer(), syncedPlaceholder) : identifier);
TAB.getInstance().getCPUManager().addPlaceholderTime(identifier, System.nanoTime()-time);
});
return null;
});
}
@Override
public void sendConsoleMessage(@NotNull IChatBaseComponent message) {
Bukkit.getConsoleSender().sendMessage("[TAB] " + RGBUtils.getInstance().convertToBukkitFormat(message.toFlatText(),
TAB.getInstance().getServerVersion().getMinorVersion() >= 16));
}
@Override
public String getServerVersionInfo() {
return "[Bukkit] " + Bukkit.getName() + " - " + Bukkit.getBukkitVersion().split("-")[0] + " (" + NMSStorage.getInstance().getServerPackage() + ")";
}
@Override
@NotNull public GroupManager detectPermissionPlugin() {
if (LuckPermsHook.getInstance().isInstalled()) {
return new GroupManager("LuckPerms", LuckPermsHook.getInstance().getGroupFunction());
}
if (Bukkit.getPluginManager().isPluginEnabled("Vault")) {
RegisteredServiceProvider<Permission> provider = Bukkit.getServicesManager().getRegistration(Permission.class);
if (provider != null && !provider.getProvider().getName().equals("SuperPerms")) {
return new GroupManager(provider.getProvider().getName(), p -> provider.getProvider().getPrimaryGroup((Player) p.getPlayer()));
}
}
return new GroupManager("None", p -> TabConstants.NO_GROUP);
}
public void runEntityTask(Entity entity, Runnable task) {
task.run();
}
}
@@ -0,0 +1,88 @@
package me.neznamy.tab.platforms.bukkit.platform;
import lombok.SneakyThrows;
import me.clip.placeholderapi.PlaceholderAPI;
import me.neznamy.tab.platforms.bukkit.features.PerWorldPlayerList;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.placeholders.PlayerPlaceholderImpl;
import me.neznamy.tab.shared.platform.TabPlayer;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.event.player.PlayerChangedWorldEvent;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.java.JavaPlugin;
import java.util.function.Consumer;
/**
* Platform override for Folia
*/
public class FoliaPlatform extends BukkitPlatform {
public FoliaPlatform(JavaPlugin plugin) {
super(plugin);
}
@Override
public void loadPlayers() {
super.loadPlayers();
// Values are never updated in the API, warn users
TAB.getInstance().sendConsoleMessage("&cFolia never updates MSPT and TPS values in the API, making " +
"%mspt% and %tps% return the default values (0 and 20).", true);
// Folia never calls PlayerChangedWorldEvent, this is a workaround
TAB.getInstance().getCPUManager().startRepeatingMeasuredTask(100, "Folia compatibility", "Refreshing world", () -> {
for (TabPlayer player : TAB.getInstance().getOnlinePlayers()) {
String bukkitWorld = ((Player)player.getPlayer()).getWorld().getName();
if (!player.getWorld().equals(bukkitWorld)) {
TAB.getInstance().getFeatureManager().onWorldChange(player.getUniqueId(), bukkitWorld);
PerWorldPlayerList pwp = TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.PER_WORLD_PLAYER_LIST);
if (pwp != null) {
runSync((Entity) player.getPlayer(), () -> pwp.onWorldChange(new PlayerChangedWorldEvent((Player) player.getPlayer(), ((Player) player.getPlayer()).getWorld())));
}
}
}
});
}
@Override
public void registerSyncPlaceholder(String identifier, int refresh) {
String syncedPlaceholder = "%" + identifier.substring(6);
PlayerPlaceholderImpl[] ppl = new PlayerPlaceholderImpl[1];
ppl[0] = TAB.getInstance().getPlaceholderManager().registerPlayerPlaceholder(identifier, refresh, p -> {
runSync((Entity) p.getPlayer(), () -> {
long time = System.nanoTime();
String output = isPlaceholderAPI() ? PlaceholderAPI.setPlaceholders((Player) p.getPlayer(), syncedPlaceholder) : identifier;
TAB.getInstance().getCPUManager().addPlaceholderTime(identifier, System.nanoTime()-time);
TAB.getInstance().getCPUManager().runTask(() -> ppl[0].updateValue(p, output)); // To ensure player is loaded
});
return null;
});
}
/**
* Runs task using player's entity scheduler. It's using reflection, because
* Folia uses Java 17 while TAB maintains Java 8 compatibility for compatibility
* with MC versions older than their player base.
*
* @param entity
* entity to run task for
* @param task
* Task to run
*/
@SneakyThrows
@SuppressWarnings("JavaReflectionMemberAccess")
private void runSync(Entity entity, Runnable task) {
Object entityScheduler = Entity.class.getMethod("getScheduler").invoke(entity);
Consumer<?> consumer = $ -> task.run(); // Reflection and lambdas don't go together
entityScheduler.getClass().getMethod("run", Plugin.class, Consumer.class, Runnable.class)
.invoke(entityScheduler, getPlugin(), consumer, null);
}
@Override
public void runEntityTask(Entity entity, Runnable task) {
runSync(entity, task);
}
}
@@ -0,0 +1,312 @@
package me.neznamy.tab.platforms.bukkit.scoreboard;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.List;
/**
* Scoreboard implementation for Bukkit, which uses packets
* to send scoreboards. While md_5 keeps saying that you don't
* need packets for scoreboards in every single spigot update,
* this is not true. It may seem that way at first, but after
* investigating, fatal problems are found:<p>
* #1 - Limitations on legacy versions are forced in the API.
* While this may not seem like a problem, it enforces those
* limits even for 1.13+ players (if using ViaVersion).<p>
* #2 - Modern versions no longer have any limits, but md_5
* decided to add some random limits for absolutely no reason
* at all. Scoreboard title received a random 128 characters
* limit including color codes. Together with the almighty bukkit
* RGB format using 14 characters for 1 color code, this makes
* gradients just impossible to use. Team prefix/suffix also
* received a 64 characters limit (excluding color codes at least),
* however that might not be enough for displaying a line of text
* in sidebar, which would require splitting the text into prefix
* and suffix, which is just begging for bugs to be introduced.<p>
* #3 - Other plugins can decide to put players into their own
* scoreboard, automatically destroying all visuals made by the
* plugin. They might also put all players into the same scoreboard,
* making per-player view of teams, especially sidebar not working.<p>
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public class PacketScoreboard extends Scoreboard<BukkitTabPlayer> {
private static NMSStorage nms;
public static Class<?> Scoreboard;
public static Class<?> IScoreboardCriteria;
private static Object emptyScoreboard;
private static Field IScoreboardCriteria_self;
// PacketPlayOutScoreboardDisplayObjective
public static Class<?> DisplayObjectiveClass;
private static Constructor<?> newDisplayObjective;
public static Field DisplayObjective_POSITION;
public static Field DisplayObjective_OBJECTIVE_NAME;
// PacketPlayOutScoreboardScore
public static Class<?> ScorePacketClass;
public static Class<?> ScoreboardScoreClass;
public static Class<Enum> EnumScoreboardAction;
private static Constructor<?> newScorePacket_1_13;
private static Constructor<?> newScorePacket_String;
private static Constructor<?> newScorePacket;
private static Constructor<?> newScoreboardScore;
public static Method ScoreboardScore_setScore;
// PacketPlayOutScoreboardObjective
public static Class<?> ObjectivePacketClass;
private static Constructor<?> newObjectivePacket;
public static Field Objective_OBJECTIVE_NAME;
public static Field Objective_METHOD;
private static Field Objective_RENDER_TYPE;
private static Field Objective_DISPLAY_NAME;
public static Class<Enum> EnumScoreboardHealthDisplay;
public static Class<?> ScoreboardObjective;
private static Constructor<?> newScoreboardObjective;
// PacketPlayOutScoreboardTeamStorage
public static Class<?> TeamPacketClass;
private static Constructor<?> newTeamPacket;
private static Method TeamPacketConstructor_of;
private static Method TeamPacketConstructor_ofBoolean;
public static Field TeamPacket_NAME;
public static Field TeamPacket_ACTION;
public static Field TeamPacket_PLAYERS;
public static Class<Enum> EnumNameTagVisibility;
public static Class<Enum> EnumTeamPush;
public static Class<?> ScoreboardTeam;
private static Constructor<?> newScoreboardTeam;
private static Method ScoreboardTeam_getPlayerNameSet;
public static Method ScoreboardTeam_setNameTagVisibility;
private static Method ScoreboardTeam_setCollisionRule;
public static Method ScoreboardTeam_setPrefix;
public static Method ScoreboardTeam_setSuffix;
private static Method ScoreboardTeam_setColor;
public static Method ScoreboardTeam_setAllowFriendlyFire;
public static Method ScoreboardTeam_setCanSeeFriendlyInvisibles;
public static void load(NMSStorage nms) throws ReflectiveOperationException {
PacketScoreboard.nms = nms;
emptyScoreboard = Scoreboard.getConstructor().newInstance();
IScoreboardCriteria_self = ReflectionUtils.getFields(IScoreboardCriteria, IScoreboardCriteria).get(0);
newDisplayObjective = DisplayObjectiveClass.getConstructor(int.class, ScoreboardObjective);
DisplayObjective_POSITION = ReflectionUtils.getOnlyField(DisplayObjectiveClass, int.class);
DisplayObjective_OBJECTIVE_NAME = ReflectionUtils.getOnlyField(DisplayObjectiveClass, String.class);
newScoreboardObjective = ReflectionUtils.getOnlyConstructor(ScoreboardObjective);
Objective_OBJECTIVE_NAME = ReflectionUtils.getFields(ObjectivePacketClass, String.class).get(0);
List<Field> list = ReflectionUtils.getFields(ObjectivePacketClass, int.class);
Objective_METHOD = list.get(list.size()-1);
newScoreboardScore = ScoreboardScoreClass.getConstructor(Scoreboard, ScoreboardObjective, String.class);
newScoreboardTeam = ScoreboardTeam.getConstructor(Scoreboard, String.class);
TeamPacket_NAME = ReflectionUtils.getFields(TeamPacketClass, String.class).get(0);
TeamPacket_ACTION = ReflectionUtils.getInstanceFields(TeamPacketClass, int.class).get(0);
TeamPacket_PLAYERS = ReflectionUtils.getOnlyField(TeamPacketClass, Collection.class);
ScoreboardTeam_getPlayerNameSet = ReflectionUtils.getOnlyMethod(ScoreboardTeam, Collection.class);
ScoreboardScore_setScore = ReflectionUtils.getMethod(ScoreboardScoreClass, new String[] {"func_96647_c", "setScore", "b", "c"}, int.class); // {Thermos, 1.5.1 - 1.17.1, 1.18+, 1.5}
ScoreboardTeam_setAllowFriendlyFire = ReflectionUtils.getMethod(ScoreboardTeam, new String[] {"func_96660_a", "setAllowFriendlyFire", "a"}, boolean.class); // {Thermos, 1.5.1+, 1.5 & 1.18+}
ScoreboardTeam_setCanSeeFriendlyInvisibles = ReflectionUtils.getMethod(ScoreboardTeam, new String[] {"func_98300_b", "setCanSeeFriendlyInvisibles", "b"}, boolean.class); // {Thermos, 1.5.1+, 1.5 & 1.18+}
if (nms.getMinorVersion() >= 13) {
newScorePacket_1_13 = ScorePacketClass.getConstructor(EnumScoreboardAction, String.class, String.class, int.class);
newObjectivePacket = ObjectivePacketClass.getConstructor(ScoreboardObjective, int.class);
Objective_DISPLAY_NAME = ReflectionUtils.getOnlyField(ObjectivePacketClass, nms.IChatBaseComponent);
ScoreboardTeam_setColor = ReflectionUtils.getOnlyMethod(ScoreboardTeam, void.class, nms.EnumChatFormat);
ScoreboardTeam_setPrefix = ReflectionUtils.getMethod(ScoreboardTeam, new String[]{"setPrefix", "b"}, nms.IChatBaseComponent); // {1.17.1-, 1.18+}
ScoreboardTeam_setSuffix = ReflectionUtils.getMethod(ScoreboardTeam, new String[]{"setSuffix", "c"}, nms.IChatBaseComponent); // {1.17.1-, 1.18+}
} else {
newScorePacket_String = ScorePacketClass.getConstructor(String.class);
newObjectivePacket = ObjectivePacketClass.getConstructor();
Objective_DISPLAY_NAME = ReflectionUtils.getFields(ObjectivePacketClass, String.class).get(1);
ScoreboardTeam_setPrefix = ReflectionUtils.getMethod(ScoreboardTeam, new String[] {"func_96666_b", "setPrefix", "b"}, String.class); // {Thermos, 1.5.1+, 1.5}
ScoreboardTeam_setSuffix = ReflectionUtils.getMethod(ScoreboardTeam, new String[] {"func_96662_c", "setSuffix", "c"}, String.class); // {Thermos, 1.5.1+, 1.5}
if (nms.getMinorVersion() >= 8) {
newScorePacket = ScorePacketClass.getConstructor(ScoreboardScoreClass);
Objective_RENDER_TYPE = ReflectionUtils.getOnlyField(ObjectivePacketClass, EnumScoreboardHealthDisplay);
} else {
newScorePacket = ScorePacketClass.getConstructor(ScoreboardScoreClass, int.class);
}
}
if (nms.getMinorVersion() >= 8) {
ScoreboardTeam_setNameTagVisibility = ReflectionUtils.getMethod(ScoreboardTeam, new String[] {"setNameTagVisibility", "a"}, EnumNameTagVisibility); // {1.8.1+, 1.8 & 1.18+}
}
if (nms.getMinorVersion() >= 9) {
ScoreboardTeam_setCollisionRule = ReflectionUtils.getOnlyMethod(ScoreboardTeam, void.class, EnumTeamPush);
}
if (nms.getMinorVersion() >= 17) {
TeamPacketConstructor_of = ReflectionUtils.getOnlyMethod(TeamPacketClass, TeamPacketClass, ScoreboardTeam);
TeamPacketConstructor_ofBoolean = ReflectionUtils.getOnlyMethod(TeamPacketClass, TeamPacketClass, ScoreboardTeam, boolean.class);
} else {
newTeamPacket = TeamPacketClass.getConstructor(ScoreboardTeam, int.class);
}
}
public PacketScoreboard(BukkitTabPlayer player) {
super(player);
}
@Override
@SneakyThrows
public void setDisplaySlot(@NotNull DisplaySlot slot, @NotNull String objective) {
player.sendPacket(newDisplayObjective.newInstance(slot.ordinal(), newScoreboardObjective(objective)));
}
@Override
@SneakyThrows
public void registerObjective0(@NotNull String objectiveName, @NotNull String title, boolean hearts) {
player.sendPacket(buildObjective(0, objectiveName, title, hearts));
}
@Override
public void unregisterObjective0(@NotNull String objectiveName) {
player.sendPacket(buildObjective(1, objectiveName, "", false));
}
@Override
public void updateObjective0(@NotNull String objectiveName, @NotNull String title, boolean hearts) {
player.sendPacket(buildObjective(2, objectiveName, title, hearts));
}
@SneakyThrows
private Object buildObjective(int action, String objectiveName, String title, boolean hearts) {
if (nms.getMinorVersion() >= 13) {
return newObjectivePacket.newInstance(
newScoreboardObjective.newInstance(null, objectiveName, null,
nms.toNMSComponent(IChatBaseComponent.optimizedComponent(title), player.getVersion()),
asDisplayType(hearts)),
action
);
}
Object nmsPacket = newObjectivePacket.newInstance();
Objective_OBJECTIVE_NAME.set(nmsPacket, objectiveName);
Objective_DISPLAY_NAME.set(nmsPacket, title);
if (nms.getMinorVersion() >= 8) {
Objective_RENDER_TYPE.set(nmsPacket, asDisplayType(hearts));
}
Objective_METHOD.set(nmsPacket, action);
return nmsPacket;
}
private Object asDisplayType(boolean hearts) {
return Enum.valueOf(EnumScoreboardHealthDisplay, hearts ? "HEARTS" : "INTEGER");
}
@Override
@SneakyThrows
public void registerTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix,
@NotNull NameVisibility visibility, @NotNull CollisionRule collision,
@NotNull Collection<String> players, int options) {
Object team = createTeam(name, prefix, suffix, visibility, collision, options);
((Collection<String>)ScoreboardTeam_getPlayerNameSet.invoke(team)).addAll(players);
if (nms.getMinorVersion() >= 17) {
player.sendPacket(TeamPacketConstructor_ofBoolean.invoke(null, team, true));
} else {
player.sendPacket(newTeamPacket.newInstance(team, 0));
}
}
@Override
@SneakyThrows
public void unregisterTeam0(@NotNull String name) {
Object team = newScoreboardTeam.newInstance(emptyScoreboard, name);
if (nms.getMinorVersion() >= 17) {
player.sendPacket(TeamPacketConstructor_of.invoke(null, team));
} else {
player.sendPacket(newTeamPacket.newInstance(team, 1));
}
}
@Override
@SneakyThrows
public void updateTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix,
@NotNull NameVisibility visibility, @NotNull CollisionRule collision, int options) {
Object team = createTeam(name, prefix, suffix, visibility, collision, options);
if (nms.getMinorVersion() >= 17) {
player.sendPacket(TeamPacketConstructor_ofBoolean.invoke(null, team, false));
} else {
player.sendPacket(newTeamPacket.newInstance(team, 2));
}
}
@SneakyThrows
private Object createTeam(String teamName, String prefix, String suffix, NameVisibility visibility, CollisionRule collision, int options) {
Object team = newScoreboardTeam.newInstance(emptyScoreboard, teamName);
ScoreboardTeam_setAllowFriendlyFire.invoke(team, (options & 0x1) > 0);
ScoreboardTeam_setCanSeeFriendlyInvisibles.invoke(team, (options & 0x2) > 0);
if (nms.getMinorVersion() >= 13) {
ScoreboardTeam_setPrefix.invoke(team, nms.toNMSComponent(IChatBaseComponent.optimizedComponent(prefix), player.getVersion()));
ScoreboardTeam_setSuffix.invoke(team, nms.toNMSComponent(IChatBaseComponent.optimizedComponent(suffix), player.getVersion()));
ScoreboardTeam_setColor.invoke(team, Enum.valueOf(nms.EnumChatFormat, EnumChatFormat.lastColorsOf(prefix).toString()));
} else {
ScoreboardTeam_setPrefix.invoke(team, prefix);
ScoreboardTeam_setSuffix.invoke(team, suffix);
}
if (nms.getMinorVersion() >= 8) ScoreboardTeam_setNameTagVisibility.invoke(team, Enum.valueOf(EnumNameTagVisibility, visibility.name()));
if (nms.getMinorVersion() >= 9) ScoreboardTeam_setCollisionRule.invoke(team, Enum.valueOf(EnumTeamPush, collision.name()));
return team;
}
@Override
@SneakyThrows
public void setScore0(@NotNull String objective, @NotNull String playerName, int score) {
Object packet;
if (nms.getMinorVersion() >= 13) {
packet = newScorePacket_1_13.newInstance(Enum.valueOf(EnumScoreboardAction, "CHANGE"), objective, playerName, score);
} else {
Object scoreboardScore = newScoreboardScore.newInstance(emptyScoreboard, newScoreboardObjective(objective), playerName);
ScoreboardScore_setScore.invoke(scoreboardScore, score);
if (nms.getMinorVersion() >= 8) {
packet = newScorePacket.newInstance(scoreboardScore);
} else {
packet = newScorePacket.newInstance(scoreboardScore, 0);
}
}
player.sendPacket(packet);
}
@Override
@SneakyThrows
public void removeScore0(@NotNull String objective, @NotNull String playerName) {
if (nms.getMinorVersion() >= 13) {
player.sendPacket(newScorePacket_1_13.newInstance(Enum.valueOf(EnumScoreboardAction, "REMOVE"), objective, playerName, 0));
} else {
player.sendPacket(newScorePacket_String.newInstance(playerName));
}
}
/**
* Creates a new Scoreboard Objective with given name.
*
* @param objectiveName
* Objective name
* @return NMS Objective
*/
@SneakyThrows
public Object newScoreboardObjective(@NotNull String objectiveName) {
if (nms.getMinorVersion() >= 13) {
return newScoreboardObjective.newInstance(
null,
objectiveName,
null,
nms.toNMSComponent(new IChatBaseComponent(""), TAB.getInstance().getServerVersion()),
null
);
}
return newScoreboardObjective.newInstance(
null,
objectiveName,
IScoreboardCriteria_self.get(null)
);
}
}
Loaded 100 of 405 files, more files were not shown because too many files have changed in this diff. Show more