Compare commits

..
421 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
NEZNAMY 51635a7322 3.3.2 2023-03-31 20:44:09 +02:00
NEZNAMY 481b33ba55 Print error message if plugin message could not be sent 2023-03-31 15:30:30 +02:00
NEZNAMY 22c785fc99 Add error message when plugin message could not be processed 2023-03-31 15:21:53 +02:00
NEZNAMY 42d4159b69 Global PlayerList - Register placeholders in constructor to avoid it being registered as PAPI placeholder 2023-03-31 14:41:21 +02:00
NEZNAMY 6d95a2f06d Fix error on concurrent access 2023-03-31 13:40:50 +02:00
NEZNAMY 7f0b1d8a2e Fix rare error on /btab reload 2023-03-31 13:40:37 +02:00
NEZNAMY a3fecd0fea Unregister tab expansion on unload to fix compatibility with latest PAPI release 2023-03-30 21:17:09 +02:00
NEZNAMY f3d6a54a2a Merge remote-tracking branch 'origin/master' 2023-03-29 17:24:21 +02:00
NEZNAMY 1d434f2557 Add more documentation 2023-03-29 17:24:11 +02:00
NEZNAMY b3500086e4 Merge pull request #856 from zelvalea/master
optimizations in CPUManager
2023-03-28 23:23:48 +02:00
Amoeba bf9202af38 added time constants 2023-03-28 20:31:29 +03:00
Amoeba bd2c683568 fixed happens before and cpu usage statistics 2023-03-28 20:09:51 +03:00
Amoeba c03feaab23 fixed nanosToPercent 2023-03-28 19:44:11 +03:00
Amoeba 9cb9ccbd2a optimize 2023-03-28 19:21:48 +03:00
Callum Seabrook 4606ebd588 Use single thread for tasks and remove ThreadManager from API 2023-03-28 12:32:13 +01:00
Callum Seabrook 679be83c5a Merge pull request #854 from zelvalea/master 2023-03-28 11:51:03 +01:00
NEZNAMY afe868bb95 Fix group update through bridge after recoding group updating 2023-03-28 01:21:29 +02:00
ZelvaLea c9ce3aa23e reduced number of threads in the pool, fixed races 2023-03-28 01:15:04 +03:00
NEZNAMY 11a49c4874 Remove unnecessary full import 2023-03-27 21:21:27 +02:00
NEZNAMY 65d2a0fba9 Remove unused field 2023-03-27 21:19:15 +02:00
NEZNAMY c1219195c7 Remove workarounds for fixing compilation, which are no longer needed 2023-03-26 17:06:57 +02:00
NEZNAMY fca0dfc91b More constant extraction to plugin.yml & bungee.yml 2023-03-26 17:02:32 +02:00
NEZNAMY 7f1cc3063c Move text cutting to 16/32 characters to avoid duplication 2023-03-26 16:53:47 +02:00
NEZNAMY e444c25287 Convert krypton to java 2023-03-26 16:36:51 +02:00
NEZNAMY 476354e407 Change thrown exception 2023-03-25 23:25:07 +01:00
NEZNAMY 9f84d3f22f Remove cpu measurement of chosen methods 2023-03-25 23:22:28 +01:00
NEZNAMY 4608ff360c Simplify 2023-03-25 23:12:48 +01:00
NEZNAMY 68b12e199d Placeholders - Measure per-placeholder cpu usage on join as well 2023-03-25 20:54:53 +01:00
NEZNAMY 4b1151f769 Use defined value instead of duplicating it in build settings 2023-03-25 20:25:27 +01:00
NEZNAMY 6687214f2a Sponge - Add support all the way down to Sponge 5 2023-03-25 19:22:07 +01:00
NEZNAMY a57757258e Sponge - Use API for scoreboards instead of packets (also small bug fix) 2023-03-25 18:41:56 +01:00
NEZNAMY 3a6060c45d Fix display name change on 1.7 2023-03-25 14:29:27 +01:00
NEZNAMY ee375c1b60 Unify tablist management between proxies 2023-03-25 14:22:40 +01:00
NEZNAMY c9d870cca0 Use placeholder refreshing engine for refreshing player groups instead of creating another thread 2023-03-23 14:52:19 +01:00
NEZNAMY 3ad8015cc1 Placeholder manager - Initialize task with the lowest refresh interval immediately instead of re-initializing 2023-03-23 14:19:42 +01:00
NEZNAMY 33609c322f Remove duplicated Field#setAccessible 2023-03-22 00:59:02 +01:00
NEZNAMY 431b89c487 Create constant for "textures" String in skin data 2023-03-22 00:33:16 +01:00
NEZNAMY 35a146992c Remove redundant checks and simplify 2023-03-22 00:27:46 +01:00
NEZNAMY dedee114c4 BungeeCord - Split boss bar handler into 2 classes 2023-03-21 20:07:11 +01:00
NEZNAMY 9d42af0388 Bukkit - Split boss bar handler into 3 classes 2023-03-21 18:39:08 +01:00
NEZNAMY 8f5d81bc33 Remove redundant annotation 2023-03-21 18:38:41 +01:00
NEZNAMY b17f5563dd Remove unused method 2023-03-21 14:09:57 +01:00
NEZNAMY 0ab8bb363a Unlimited NameTags - Force marker tag for 1.8.x and remove config option 2023-03-20 22:33:56 +01:00
NEZNAMY ac5fca86bd Split TabFeature into multiple interfaces based on used methods 2023-03-20 19:36:40 +01:00
NEZNAMY 28c2c2e193 Fix armor stand metadata on spawn 2023-03-20 16:25:37 +01:00
NEZNAMY 904c0b6a17 Fix anti-override for tablist name formatting (and possibly ping spoof & spectator fix) 2023-03-20 16:21:23 +01:00
NEZNAMY f3c31bc92c Fix error 2023-03-20 12:42:42 +01:00
NEZNAMY 6016b84889 BungeeCord - Fix bossbar color & style update 2023-03-19 22:24:13 +01:00
NEZNAMY cfb5cb3a9f Create object for calling bossbar methods 2023-03-19 22:07:45 +01:00
NEZNAMY 1746f6b1d0 Bukkit - Send all tablist packets grouped, ViaVersion splits them properly for 1.7 players 2023-03-19 21:21:33 +01:00
NEZNAMY 9c3979a0d9 More tablist fixes on 1.7 2023-03-19 21:04:52 +01:00
NEZNAMY 810676c69f Bukkit - Fix NPE on <1.8 servers 2023-03-19 16:44:55 +01:00
NEZNAMY d0892a245a BungeeCord - Fix NPE kick on 1.7 2023-03-19 16:40:53 +01:00
NEZNAMY f539b1b93b BungeeCord - Split TabList class into 3 based on version & make 1.7 actually work (potential future use?) 2023-03-19 14:50:02 +01:00
NEZNAMY adfa64a29f Scoreboard - Fix unregistering teams of lines that aren't displayed due to placeholder output being empty 2023-03-19 14:07:44 +01:00
NEZNAMY d4f86ee79d Remove redundant declaration 2023-03-19 13:43:06 +01:00
NEZNAMY 6ebe31b737 Add @NonNull / @Nullable annotations to TeamManager (#843) 2023-03-19 13:38:58 +01:00
NEZNAMY dcbd5e62d4 Fix packet un-cancel breaking PetFix (#842) 2023-03-19 13:26:24 +01:00
NEZNAMY a7e6e49363 Fix #845 (1.7 requires non-null title in scoreboard unregister action) 2023-03-19 12:52:56 +01:00
NEZNAMY d115257ff7 Move DisplaySlot class inside Scoreboard 2023-03-18 23:53:49 +01:00
NEZNAMY d578be276c Remove duplicated caches 2023-03-18 22:35:16 +01:00
NEZNAMY e338f57441 Completely get rid of PacketBuilder class 2023-03-18 22:25:05 +01:00
Callum Seabrook c87321b5b0 Rewrote packet injection for Krypton 2023-03-18 19:37:34 +00:00
NEZNAMY 84bb9b449b Add methods for manipulating entities instead of using sendPacket 2023-03-18 19:22:29 +01:00
NEZNAMY 9d613fc24d Rework tablist packet management to make it more API-style 2023-03-18 17:44:43 +01:00
NEZNAMY 3f2ae3131e Add back a default animation, which is still used in default scoreboard 2023-03-18 00:14:56 +01:00
NEZNAMY 0e9f53bd58 Fix wrong action on objective unregister 2023-03-17 18:20:47 +01:00
NEZNAMY 3981815b63 Create Scoreboard interface for calling scoreboard-related methods 2023-03-17 15:04:55 +01:00
NEZNAMY 3ef99e4e7c Better abstraction of pipeline injection 2023-03-16 15:16:24 +01:00
NEZNAMY 76767a0958 Use API-style managing of scoreboard teams 2023-03-16 13:53:50 +01:00
NEZNAMY 1683943d58 Use API-style managing of scoreboard objectives 2023-03-16 01:07:21 +01:00
NEZNAMY cc0dbe8623 Move sendPluginMessage method to ProxyTabPlayer 2023-03-16 00:03:13 +01:00
NEZNAMY b056251e07 Remove calculating of packets sent by the plugin 2023-03-15 23:50:23 +01:00
NEZNAMY 3b352c66e2 Change way how scoreboard score and display objective packets are sent 2023-03-15 23:31:30 +01:00
NEZNAMY b4f0ea73c1 Supplier with Exception is Callable 2023-03-15 21:43:27 +01:00
NEZNAMY 1c683788b5 BungeeCord - Create cleaner methods for player info action conversion 2023-03-15 21:29:58 +01:00
NEZNAMY 250597df2d Sponge 8 - Fix class cast exception 2023-03-15 21:07:20 +01:00
NEZNAMY 0d2d1d5aaa Sponge 8 - Use direct code instead of reflection for entity teleport packet 2023-03-15 21:00:42 +01:00
NEZNAMY 061375d1e7 3.3.2-SNAPSHOT 2023-03-15 21:00:14 +01:00
NEZNAMY 5b05614305 Remove unused field 2023-03-15 20:13:29 +01:00
NEZNAMY 69db2e0e53 Use API style code for bossbars since packets were not used anymore 2023-03-15 20:03:10 +01:00
NEZNAMY 00ef364324 Use APIs for header/footer instead of packets where possible (rip bukkit) 2023-03-15 16:13:31 +01:00
NEZNAMY de68312b6b Use APIs for chat messages instead of packets 2023-03-15 14:36:25 +01:00
NEZNAMY ddac08bcbc Fix bungee compilation (did md5 break something?) 2023-03-15 14:31:59 +01:00
NEZNAMY 517e285bb3 Fix bossbar error on <1.9 from recode 2023-03-15 14:29:13 +01:00
NEZNAMY 81a08a6b51 3.3.1 2023-03-14 19:18:01 +01:00
NEZNAMY 4dfef1d032 1.19.4 support 2023-03-14 19:15:15 +01:00
NEZNAMY 5d38e28cae Extract more constants and add config option 2023-03-14 18:35:25 +01:00
NEZNAMY 66a12d10c4 Layout - Resend on server switch on Velocity 2023-03-14 17:27:27 +01:00
NEZNAMY 1a4027c8d5 Remove unused fields and methods 2023-03-14 16:15:55 +01:00
NEZNAMY 2a0243c51a RedisSupport - Add missing packet source 2023-03-14 15:24:13 +01:00
NEZNAMY 4851d09e70 More abstraction of unlimited nametag mode & packets it uses 2023-03-14 15:20:49 +01:00
NEZNAMY 3cdf3accfd Remove redundant checks 2023-03-14 10:46:27 +01:00
NEZNAMY fd1f094216 Layout - Small cleanup in code 2023-03-14 00:22:02 +01:00
NEZNAMY 25ece1ecb8 Move ViaVersion hook (and remove safety checks, hopefully it works correctly now) 2023-03-13 23:31:10 +01:00
NEZNAMY 802e65c280 Change constructor fields to getters in features 2023-03-13 23:04:22 +01:00
NEZNAMY 882688f056 Layout - Cache remove packet 2023-03-13 21:52:29 +01:00
NEZNAMY 97d60d0ab3 Bungee - Remove unused field (and simplify) 2023-03-13 21:33:48 +01:00
NEZNAMY 9ef99daed1 Revert feature loading as references were not null when they were supposed to be 2023-03-13 21:31:48 +01:00
NEZNAMY 71c85d9d39 NameTags - Speed up unloading 2023-03-13 19:36:05 +01:00
NEZNAMY b981ab46be Add debug messages about feature unloading times 2023-03-13 19:35:45 +01:00
NEZNAMY 016633e494 BelowName - Speed up reloads by caching values for players (tested with 100 bots, reloads were 2x faster) 2023-03-13 19:22:00 +01:00
NEZNAMY 1db862ab56 NameTags - Speed up reloading (tested with 100 bots, reload was 3x faster) 2023-03-13 19:15:45 +01:00
NEZNAMY 3563786310 PlayerList - Drastically speed up reloads (up to 10+ times with high player counts) 2023-03-13 19:01:26 +01:00
NEZNAMY ac8851dd6a Cache EnumSets for info packet for better performance 2023-03-13 18:22:16 +01:00
NEZNAMY 53ab2d9793 Commands - Do not load widths for widthcheck command unless command is used 2023-03-13 18:03:55 +01:00
NEZNAMY 2a58c86ffc Move code from initializer to load 2023-03-13 16:35:30 +01:00
NEZNAMY fa46d527ae Spectator Fix - Don't send useless packets on load/unload 2023-03-13 15:17:13 +01:00
NEZNAMY 667bd38305 Velocity - Fix Velocity being Velocity exception 2023-03-13 14:51:52 +01:00
NEZNAMY faba439466 Global PlayerList - Send all affected entries in a single packet, reducing amount of packets sent and improving performance 2023-03-13 14:47:29 +01:00
NEZNAMY c301157758 Global PlayerList - No longer delay task on quit as I was not able to reproduce the bug; Update placeholders periodically as they didn't update properly on vanish change anyway 2023-03-13 14:14:06 +01:00
NEZNAMY 8d09d425a4 Global PlayerList - No longer prevent other sources from removing players, better organize adding on server switch as well 2023-03-13 13:26:12 +01:00
NEZNAMY 11f03d5a10 Simplify 2023-03-13 12:58:19 +01:00
NEZNAMY 83625202a1 Merge pull request #841 from NEZNAMY/better-pipeline-injection
Better pipeline injection
2023-03-12 14:39:19 +01:00
Callum Seabrook eb4b715a88 Move pipeline injection to its own package 2023-03-12 13:35:47 +00:00
Callum Seabrook 89dce941cf Add RequiredArgsConstructor to NettyPipelineInjector 2023-03-12 13:31:58 +00:00
Callum Seabrook 6371b67e9e Removed player Channel and moved Netty injection to NettyPipelineInjector 2023-03-12 13:23:16 +00:00
NEZNAMY 966e27418a Bukkit - Attempt to fix error on ArrayList#remove 2023-03-11 15:35:31 +01:00
NEZNAMY 4adf7d1734 Bukkit - Fix NoSuchElementException that doesn't make sense 2023-03-11 15:25:06 +01:00
NEZNAMY 1a4c4a6fe8 Add more debug messages about feature loading times 2023-03-11 15:14:12 +01:00
NEZNAMY 7ff36592ae Do not re-initialize ErrorManager 2023-03-11 14:27:23 +01:00
NEZNAMY 1b513412d2 Remove redundant import 2023-03-10 13:20:14 +01:00
NEZNAMY f2bb43fbcd Make tabExpansion variable not null and add default empty implementation 2023-03-10 13:16:22 +01:00
NEZNAMY df5d07d885 Include forgotten code from previous commit 2023-03-10 13:13:21 +01:00
NEZNAMY 0ad2f33c4c Further clean up feature loading and mutual hooks 2023-03-10 12:45:28 +01:00
NEZNAMY dee48fccb3 Partially de-spaghetti feature loading and mutual hooking 2023-03-09 00:13:15 +01:00
NEZNAMY 9ca372ab13 Velocity is XD 2023-03-07 23:07:26 +01:00
NEZNAMY 58a849a902 Update README.md 2023-03-06 18:15:04 +01:00
NEZNAMY 895072ec35 Update README.md 2023-03-06 18:07:14 +01:00
NEZNAMY 2b2c4d2c39 Update README.md 2023-03-06 12:03:45 +01:00
NEZNAMY d534b6e2e8 Layout - Make fixed slots take priority over player groups if a slot is used in both 2023-03-05 15:31:14 +01:00
NEZNAMY 783e9d9931 Fix wrong values in widths.txt 2023-03-02 21:06:15 +01:00
NEZNAMY a109d9b55b Enable widthcheck command and fix error in it 2023-03-02 20:54:28 +01:00
NEZNAMY 5ce59ce44f Fix compilation 2023-03-02 20:33:37 +01:00
NEZNAMY 1e9195a9bc Velocity - Add try/catch for calling new methods on old versions 2023-03-01 18:49:41 +01:00
NEZNAMY 3099d16468 Create SECURITY.md 2023-03-01 12:23:39 +01:00
NEZNAMY c9c6af346c Add new issue templates 2023-03-01 12:18:23 +01:00
NEZNAMY af80a43628 Catch Throwable in pipeline instead of Exception to reduce chance of plugin causing player disconnect in future unsupported versions 2023-02-28 23:33:31 +01:00
NEZNAMY dc3c98ec91 Bukkit - Change how ViaVersion is detected (to avoid wrong detection due to a plugin order load bug in 1.17.1) 2023-02-25 18:41:25 +01:00
NEZNAMY dffa2c83f6 Typo 2023-02-24 23:45:40 +01:00
NEZNAMY dc66f253a7 Config converter - Remove placeholder that got deleted 2023-02-24 23:42:35 +01:00
NEZNAMY 17498cbfc2 BungeeCord - Fix bytebuf deserialization from previous commits 2023-02-24 23:15:01 +01:00
NEZNAMY 96e4d30168 Include 1.5 in supported version range 2023-02-24 16:48:56 +01:00
NEZNAMY c9d1934bb9 Use lombok for empty private constructor 2023-02-24 16:33:39 +01:00
NEZNAMY 8bec083194 Remove redundant duplicated checks 2023-02-24 16:30:34 +01:00
NEZNAMY abf047be23 Fix global playerlist class being instantiated twice 2023-02-24 16:22:02 +01:00
NEZNAMY 4a95399d56 3.3.1-SNAPSHOT 2023-02-24 16:19:01 +01:00
NEZNAMY 885bf5154e NameTags - Fix updating nametag visibility of players in disabled world/server 2023-02-24 15:43:58 +01:00
NEZNAMY 3e146fc496 Simplify 2023-02-24 15:41:30 +01:00
NEZNAMY 95710bcaf5 Sorting - Fix team name change registering teams for players in disabled worlds/servers 2023-02-24 15:37:40 +01:00
NEZNAMY edd620f17d BelowName - Fix text refreshing packets being sent to players in disabled worlds/servers 2023-02-24 15:25:09 +01:00
NEZNAMY 9c466d8b1e Update comment 2023-02-24 14:57:56 +01:00
NEZNAMY b385ab70fb Simplify 2023-02-24 14:52:22 +01:00
NEZNAMY 140224840b Remove double registration of sorting feature 2023-02-23 21:24:10 +01:00
NEZNAMY 2078cd5a5f Downgrade sponge API dependencies to confirm support 2023-02-21 18:46:43 +01:00
426 changed files with 14949 additions and 16780 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
+56
View File
@@ -0,0 +1,56 @@
name: Memory leak
description: Memory leak caused by TAB.
labels: ["Type: Memory leak"]
body:
- type: input
attributes:
label: Server version
description: Version and type of used server software where TAB is installed.
placeholder: Spigot 1.18.1
validations:
required: true
- type: input
attributes:
label: TAB version
description: Full version in numbers, "latest" is not a version.
placeholder: 3.0.2
validations:
required: true
- type: textarea
attributes:
label: Heap dump
description: Link to uploaded memory dump which I can look into.
validations:
required: true
- type: textarea
attributes:
label: Uncleared object(s)
description: Objects you believe should have been removed from the memory but are not
placeholder: |
There is only 1 player online, but heap dump has 100 instances of TabPlayer
validations:
required: false
- type: textarea
attributes:
label: Additional info
description: Any other info you want to provide.
validations:
required: false
- type: checkboxes
attributes:
label: Checklist
description: Let's make sure this report is valid
options:
- label: I am running latest version of the plugin
required: true
- label: I have included download link of my heap dump
required: true
- label: I ticked all of the boxes without actually reading them
required: false
- label: I have explained what I believe is wrong with the heap dump
required: true
@@ -0,0 +1,55 @@
name: Performance issue
description: Performance issue in TAB or in server caused by TAB
labels: ["Type: Performance"]
body:
- type: input
attributes:
label: Server version
description: Version and type of used server software where TAB is installed.
placeholder: Spigot 1.18.1
validations:
required: true
- type: input
attributes:
label: TAB version
description: Full version in numbers, "latest" is not a version.
placeholder: 3.0.2
validations:
required: true
- type: textarea
attributes:
label: Evidence
description: Any evidence proving the performance issue, such as output of /tab cpu or timings.
validations:
required: true
- type: textarea
attributes:
label: Steps to reproduce (if known)
description: What to do in order to reproduce this issue
placeholder: |
Step 1: ..
Step 2: ..
validations:
required: false
- type: textarea
attributes:
label: Additional info
description: Any other info you want to provide.
validations:
required: false
- type: checkboxes
attributes:
label: Checklist
description: Let's make sure this report is valid
options:
- label: I am running latest version of the plugin
required: true
- label: I have included sufficient evidence of the issue
required: true
- label: I ticked all of the boxes without actually reading them
required: false
+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.
+11 -22
View File
@@ -1,34 +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.
# 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.
Once the plugin compiles, grab the jar from `/jar/build/libs/` folder.
The universal jar contains all modules for all supported platforms.
# 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)
Support my work & receive customer support: [BuiltByBit](https://builtbybit.com/resources/14009/), [Polymart](https://polymart.org/resource/484)
Other links: [Discord support for customers](https://discord.gg/EaSvdk6), [Wiki](https://github.com/NEZNAMY/TAB/wiki)
# Documentation
You can find everything about the plugin on the [Wiki](https://github.com/NEZNAMY/TAB/wiki). This includes a detailed description
of all features, as well as information regarding compatibility or limitations of each feature.
# Issues
The [Issues](https://github.com/NEZNAMY/TAB/issues) section is **only** available for cases included in templates.
In other words, they **are not available for**:
* **Support** - support is provided on discord for buyers, where roles are given to verified users
allowing us to know who bought it and who didn't. It also serves as a much faster way of communication.
* **Feature requests** - closed indefinitely.
**If you're a customer, please use discord for reporting bugs, especially if you aren't good at making bug reports.
You will be provided with a much faster and higher quality service.**
The [Issues](https://github.com/NEZNAMY/TAB/issues) section is an issue tracker for all the kind of problems covered in issue templates.
Please refrain from attempting to use it to receive help with your server, such issues will be closed immediately.
When opening an issue, provide as much information as possible. The difference between a bug getting fixed in 1 week vs 1 hour
is in quality of the report. Bug reports where steps to reproduce are "idk" have no value and will not help to get the bug fixed at all.
# Contributing
See [CONTRIBUTING.md](https://github.com/NEZNAMY/TAB/blob/master/CONTRIBUTING.md)
+2
View File
@@ -0,0 +1,2 @@
# Security Policy
If you found a security issue, send me a private message anywhere.
+1 -22
View File
@@ -1,26 +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)
}
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())
replaceTokenIn("src/main/java/me/neznamy/tab/api/TabConstants.java")
compileOnlyApi("org.jetbrains:annotations:24.0.1")
}
tasks.javadoc {
@@ -1,108 +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();
/**
* Teleports armor stand to owner's current location for everyone in range
*/
void teleport();
/**
* Teleports armor stand to owner's current location for specified player
*
* @param viewer
* player to send packet to
*/
void teleport(TabPlayer viewer);
/**
* Changes sneaking flag and sends packets to everyone in range
*
* @param sneaking
* new sneaking status
*/
void sneak(boolean sneaking);
/**
* DeSpawns armor stand for everyone
*/
void destroy();
/**
* DeSpawns armor stand for specified player
*
* @param viewer
* player to send packet to
*/
void destroy(TabPlayer viewer);
/**
* 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();
/**
* Spawns armor stand for specified player
*
* @param viewer
* player to spawn for
*/
void spawn(TabPlayer viewer);
/**
* Sends respawn packets to specified player to make position
* change instant instead of teleport animation
*
* @param viewer
* Player to send armor stand respawn packets to
*/
void respawn(TabPlayer viewer);
}
@@ -1,116 +0,0 @@
package me.neznamy.tab.api;
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);
/**
* Internal function that marks objective packet deserialization
* when at least 1 feature uses it.
* <p>
* Internal use only.
*/
void markObjective();
/**
* Internal function that marks display objective packet deserialization
* when at least 1 feature uses it.
* <p>
* Internal use only.
*/
void markDisplayObjective();
/**
* 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);
}
+35 -117
View File
@@ -2,13 +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.task.ThreadManager;
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
@@ -16,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
@@ -35,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.");
@@ -52,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
@@ -61,157 +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 Thread manager, which allows task submitting
*
* @return ThreadManager instance
*/
public abstract ThreadManager getThreadManager();
/**
* 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,391 +0,0 @@
package me.neznamy.tab.api;
import java.util.*;
import lombok.Getter;
import me.neznamy.tab.api.protocol.PacketPlayOutPlayerInfo;
import me.neznamy.tab.api.protocol.PacketPlayOutScoreboardDisplayObjective;
import me.neznamy.tab.api.protocol.PacketPlayOutScoreboardObjective;
/**
* Abstract class representing a core feature of the plugin.
* <p>
* It receives all kinds of events and can react to them.
*/
public abstract class TabFeature {
/** Feature's name displayed in /tab cpu */
@Getter private final String featureName;
/** Feature's function name displayed in place of refreshing in /tab cpu */
@Getter private final String refreshDisplayName;
/** 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<>());
/**
* List of methods overridden (implemented) by each feature.
* If a method is not overridden, it is not called at all.
* This avoids a massive spam for every single method
* in every single feature in /tab cpu output
*/
private final List<String> methodOverrides = new ArrayList<>();
/**
* Constructs new instance with given parameters and loads method overrides
*
* @param featureName
* Feature's name in /tab cpu
* @param refreshDisplayName
* "refreshing" cpu display type name of the feature
*/
protected TabFeature(String featureName, String refreshDisplayName) {
this.featureName = featureName;
this.refreshDisplayName = refreshDisplayName;
try {
if (getClass().getMethod("onCommand", TabPlayer.class, String.class).getDeclaringClass() != TabFeature.class)
methodOverrides.add("onCommand");
if (getClass().getMethod("onJoin", TabPlayer.class).getDeclaringClass() != TabFeature.class)
methodOverrides.add("onJoin");
if (getClass().getMethod("onQuit", TabPlayer.class).getDeclaringClass() != TabFeature.class)
methodOverrides.add("onQuit");
if (getClass().getMethod("onWorldChange", TabPlayer.class, String.class, String.class).getDeclaringClass() != TabFeature.class)
methodOverrides.add("onWorldChange");
if (getClass().getMethod("onServerChange", TabPlayer.class, String.class, String.class).getDeclaringClass() != TabFeature.class)
methodOverrides.add("onServerChange");
if (getClass().getMethod("onDisplayObjective", TabPlayer.class, PacketPlayOutScoreboardDisplayObjective.class).getDeclaringClass() != TabFeature.class) {
methodOverrides.add("onDisplayObjective");
TabAPI.getInstance().getFeatureManager().markDisplayObjective();
}
if (getClass().getMethod("onLoginPacket", TabPlayer.class).getDeclaringClass() != TabFeature.class)
methodOverrides.add("onLoginPacket");
if (getClass().getMethod("onObjective", TabPlayer.class, PacketPlayOutScoreboardObjective.class).getDeclaringClass() != TabFeature.class) {
methodOverrides.add("onObjective");
TabAPI.getInstance().getFeatureManager().markObjective();
}
if (getClass().getMethod("onPlayerInfo", TabPlayer.class, PacketPlayOutPlayerInfo.class).getDeclaringClass() != TabFeature.class)
methodOverrides.add("onPlayerInfo");
if (getClass().getMethod("onPacketReceive", TabPlayer.class, Object.class).getDeclaringClass() != TabFeature.class)
methodOverrides.add("onPacketReceive");
if (getClass().getMethod("onPacketSend", TabPlayer.class, Object.class).getDeclaringClass() != TabFeature.class)
methodOverrides.add("onPacketSend");
if (getClass().getMethod("refresh", TabPlayer.class, boolean.class).getDeclaringClass() != TabFeature.class)
methodOverrides.add("refresh");
if (getClass().getMethod("onVanishStatusChange", TabPlayer.class).getDeclaringClass() != TabFeature.class)
methodOverrides.add("onVanishStatusChange");
} catch (NoSuchMethodException e) {
//this will never happen
}
}
/**
* 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 featureName
* Feature's name in /tab cpu
* @param refreshDisplayName
* "refreshing" cpu display type name of the feature
* @param configSection
* Configuration section of the feature to load disabled
* servers / worlds from
*/
protected TabFeature(String featureName, String refreshDisplayName, String configSection) {
this(featureName, refreshDisplayName);
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");
}
}
/**
* Loads all online players and sends packets
*/
public void load() {
//empty by default
}
/**
* Unloads all players and sends clear packets
*/
public void unload() {
//empty by default
}
/**
* Processes command from player. This is typically a toggle command.
*
* @param sender
* command sender
* @param message
* command line
* @return {@code true} if event should be cancelled, {@code true} if not
*/
public boolean onCommand(TabPlayer sender, String message) {
return false;
}
/**
* Processes join event
*
* @param connectedPlayer
* player who connected
*/
public void onJoin(TabPlayer connectedPlayer) {
//empty by default
}
/**
* Processes quit event
*
* @param disconnectedPlayer
* player who disconnected
*/
public void onQuit(TabPlayer disconnectedPlayer) {
//empty by default
}
/**
* Processes world switch
*
* @param changed
* player who switched world
* @param from
* world player changed from
* @param to
* world player changed to
*/
public void onWorldChange(TabPlayer changed, String from, String to) {
//empty by default
}
/**
* Processes server switch
*
* @param changed
* player who switched server
* @param from
* server player changed from
* @param to
* server player changed to
*/
public void onServerChange(TabPlayer changed, String from, String to) {
//empty by default
}
/**
* Processes the packet send and returns true if packet should be cancelled
*
* @param receiver
* player receiving packet
* @param packet
* received packet
*/
public void onDisplayObjective(TabPlayer receiver, PacketPlayOutScoreboardDisplayObjective packet) {
//empty by default
}
/**
* Processes login packet, only available on BungeeCord
*
* @param packetReceiver
* player receiving client reset packet
*/
public void onLoginPacket(TabPlayer packetReceiver) {
//empty by default
}
/**
* Processes the packet send
*
* @param receiver
* player receiving packet
* @param packet
* received packet
*/
public void onObjective(TabPlayer receiver, PacketPlayOutScoreboardObjective packet) {
//empty by default
}
/**
* Processes the packet send and possibly modifies it
*
* @param receiver
* player receiving packet
* @param info
* received packet
*/
public void onPlayerInfo(TabPlayer receiver, PacketPlayOutPlayerInfo info) {
//empty by default
}
/**
* Processes raw packet sent by client
*
* @param sender
* packet sender
* @param packet
* packet received
* @return true if false should be cancelled, false if not
* @throws ReflectiveOperationException
* if thrown by reflective operation
*/
public boolean onPacketReceive(TabPlayer sender, Object packet) throws ReflectiveOperationException {
return false;
}
/**
* Processes raw packet sent to client
*
* @param receiver
* packet receiver
* @param packet
* the packet
* @throws ReflectiveOperationException
* if thrown by reflective operation
*/
public void onPacketSend(TabPlayer receiver, Object packet) throws ReflectiveOperationException {
//empty by default
}
/**
* Performs refresh of specified player
*
* @param refreshed
* player to refresh
* @param force
* if refresh should be forced despite refresh seemingly not needed
*/
public void refresh(TabPlayer refreshed, boolean force) {
//empty by default
}
/**
* Processes vanish status change of player
*
* @param player
* Player who changed vanish status
*/
public void onVanishStatusChange(TabPlayer player) {
//empty by default
}
/**
* Registers this feature as one using specified placeholders
*
* @param placeholders
* placeholders to add as used in this feature
*/
public void addUsedPlaceholders(Collection<String> placeholders) {
if (placeholders.isEmpty()) return;
placeholders.forEach(p -> TabAPI.getInstance().getPlaceholderManager().addUsedPlaceholder(p, this));
}
/**
* 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 method with specified name is overridden
* in the implementation, {@code false} if not
*
* @param method
* Method to check
* @return {@code true} if overridden, {@code false} if not
*/
public boolean overridesMethod(String method) {
return methodOverrides.contains(method);
}
/**
* 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);
}
}
@@ -1,166 +1,35 @@
package me.neznamy.tab.api;
import java.util.UUID;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import io.netty.channel.Channel;
import me.neznamy.tab.api.chat.IChatBaseComponent;
import me.neznamy.tab.api.protocol.Skin;
import me.neznamy.tab.api.protocol.TabPacket;
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 custom universal packet
*
* @param packet
* packet to send
*/
void sendCustomPacket(TabPacket packet);
/**
* Sends the player a custom universal packet and adds that packet into counter that
* is displayed in /tab cpu
*
* @param packet
* packet to send
* @param feature
* feature to increment sent packet counter of
*/
void sendCustomPacket(TabPacket packet, TabFeature feature);
/**
* Sends the player a custom universal packet and adds that packet into counter that
* is displayed in /tab cpu
*
* @param packet
* packet to send
* @param feature
* feature to increment sent packet counter of
*/
void sendCustomPacket(TabPacket packet, String feature);
/**
* 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 netty channel. On servers running version 1.7 or older returns {@code null}.
*
* @return player's channel
*/
Channel getChannel();
/**
* 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)
@@ -169,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(TabFeature 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(TabFeature 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(TabFeature 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
@@ -262,45 +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();
}
@@ -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);
}
@@ -0,0 +1,183 @@
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;
/**
* 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 name tag of
* @see #showNameTag(TabPlayer)
* @see #hasHiddenNameTag(TabPlayer)
*/
void hideNameTag(@NonNull TabPlayer player);
/**
* Hides player's NameTag for specified player until it's shown again
*
* @param player
* player to hide name tag of
* @param viewer
* player to hide name tag for
*/
void hideNameTag(@NonNull TabPlayer player, @NonNull TabPlayer viewer);
/**
* Makes player's NameTag visible again
*
* @param player
* player to show name tag of
* @see #hideNameTag(TabPlayer)
* @see #hasHiddenNameTag(TabPlayer)
*/
void showNameTag(@NonNull TabPlayer player);
/**
* Shows player's NameTag for specified viewer if it was hidden before
*
* @param player
* player to show name tag of
* @param viewer
* player to show NameTag back for
*/
void showNameTag(@NonNull TabPlayer player, @NonNull TabPlayer viewer);
/**
* Return whether player has hidden NameTag or not
*
* @param player
* player to check name tag visibility status of
* @return Whether player has hidden NameTag or not
* @see #hideNameTag(TabPlayer)
* @see #showNameTag(TabPlayer)
*/
boolean hasHiddenNameTag(@NonNull TabPlayer player);
/**
* Returns true if NameTag is hidden for specified viewer, false if not
*
* @param player
* player to check visibility status of
* @param viewer
* player to check visibility status for
* @return true if hidden, false if not
*/
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.
* This can be resumed using resumeTeamHandling(). If team handling was paused already, nothing happens.
*
* @param player
* player to pause team handling of
*/
void pauseTeamHandling(@NonNull TabPlayer player);
/**
* Resumes team handling if it was before paused using pauseTeamHandling(), if not, nothing happens
*
* @param player
* player to resume team handling of
*/
void resumeTeamHandling(@NonNull TabPlayer player);
/**
* Returns true if team handling is paused for this player using pauseTeamHandling(), false if not, or
* it was resumed already using resumeTeamHandling
*
* @param player
* player to check handling status of
* @return true if paused, false if not
*/
boolean hasTeamHandlingPaused(@NonNull TabPlayer player);
/**
* Forces collision rule for the player. Setting it to null will remove forced value
*
* @param player
* player to set collision rule of
* @param collision
* forced collision rule
*/
void setCollisionRule(@NonNull TabPlayer player, @Nullable Boolean collision);
/**
* Returns forced collision rule or null if collision is not forced using setCollisionRule
*
* @param player
* player to get forced collision of
* @return forced value or null if not forced
*/
@Nullable Boolean getCollisionRule(@NonNull TabPlayer player);
/**
* 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 change prefix of
* @param prefix
* new prefix value
* @see #getCustomPrefix(TabPlayer)
* @see #getOriginalPrefix(TabPlayer)
*/
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);
@Nullable String getCustomPrefix(@NonNull TabPlayer player);
@Nullable String getCustomSuffix(@NonNull TabPlayer player);
@NonNull String getOriginalPrefix(@NonNull TabPlayer player);
@NonNull String getOriginalSuffix(@NonNull TabPlayer player);
/**
* 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 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 name tags by either calling
* {@link #toggleNameTagVisibilityView(TabPlayer, boolean)} or using a command,
* {@code false} if not.
*
* @param player
* player to check
* @return {@code true} if hidden, {@code false} if not
*/
boolean hasHiddenNameTagVisibilityView(@NonNull TabPlayer player);
}
@@ -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.TabFeature;
import me.neznamy.tab.api.TabPlayer;
/**
* 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, TabFeature 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,255 +0,0 @@
package me.neznamy.tab.api.protocol;
import lombok.NonNull;
import me.neznamy.tab.api.ProtocolVersion;
import me.neznamy.tab.api.chat.EnumChatFormat;
import me.neznamy.tab.api.chat.IChatBaseComponent;
import me.neznamy.tab.api.chat.rgb.RGBUtils;
import me.neznamy.tab.api.util.BiFunctionWithException;
import java.util.HashMap;
import java.util.Map;
/**
* A class for packet building, methods are overridden in
* platform-specific builders which extend this class to build actual packets.
* Packets which don't have a method override are forwarded into
* sendPacket method, where they are handled using platform's API instead.
*/
public class PacketBuilder {
/** Function map turning custom packet class into platform-specific packets */
protected final Map<Class<? extends TabPacket>, BiFunctionWithException<TabPacket, ProtocolVersion, Object>> buildMap = new HashMap<>();
/**
* Constructs new instance and fills {@link #buildMap} with abstract build methods
* of packets present on all platforms.
*/
public PacketBuilder() {
buildMap.put(PacketPlayOutBoss.class, (packet, version) -> build((PacketPlayOutBoss)packet, version));
buildMap.put(PacketPlayOutChat.class, (packet, version) -> build((PacketPlayOutChat)packet, version));
buildMap.put(PacketPlayOutPlayerInfo.class, (packet, version) -> build((PacketPlayOutPlayerInfo)packet, version));
buildMap.put(PacketPlayOutPlayerListHeaderFooter.class, (packet, version) -> build((PacketPlayOutPlayerListHeaderFooter)packet, version));
buildMap.put(PacketPlayOutScoreboardDisplayObjective.class, (packet, version) -> build((PacketPlayOutScoreboardDisplayObjective)packet, version));
buildMap.put(PacketPlayOutScoreboardObjective.class, (packet, version) -> build((PacketPlayOutScoreboardObjective)packet, version));
buildMap.put(PacketPlayOutScoreboardScore.class, (packet, version) -> build((PacketPlayOutScoreboardScore)packet, version));
buildMap.put(PacketPlayOutScoreboardTeam.class, (packet, version) -> build((PacketPlayOutScoreboardTeam)packet, version));
}
/**
* Converts custom packet into platform-specific packet by calling a function from
* {@link #buildMap}.
*
* @param packet
* Packet to build
* @param clientVersion
* Protocol version of player to build the packet for
* @return Platform-specific packet
* @throws ReflectiveOperationException
* if reflection fails
*/
public Object build(@NonNull TabPacket packet, @NonNull ProtocolVersion clientVersion) throws ReflectiveOperationException {
return buildMap.get(packet.getClass()).apply(packet, clientVersion);
}
/**
* Constructs platform-specific PacketPlayOutBoss class based on custom packet class
*
* @param packet
* Custom packet to be built
* @param clientVersion
* Protocol version of player to build the packet for
* @return Platform-specific packet
* @throws ReflectiveOperationException
* if thrown by reflective operation
*/
public Object build(PacketPlayOutBoss packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
return packet;
}
/**
* Constructs platform-specific PacketPlayOutChat class based on custom packet class
*
* @param packet
* Custom packet to be built
* @param clientVersion
* Protocol version of player to build the packet for
* @return Platform-specific packet
*/
public Object build(PacketPlayOutChat packet, ProtocolVersion clientVersion) {
return packet;
}
/**
* Constructs platform-specific PacketPlayOutPlayerInfo class based on custom packet class
*
* @param packet
* Custom packet to be built
* @param clientVersion
* Protocol version of player to build the packet for
* @return Platform-specific packet
* @throws ReflectiveOperationException
* if thrown by reflective operation
*/
public Object build(PacketPlayOutPlayerInfo packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
return packet;
}
/**
* Constructs platform-specific PacketPlayOutPlayerListHeaderFooter class based on custom packet class
*
* @param packet
* Custom packet to be built
* @param clientVersion
* Protocol version of player to build the packet for
* @return Platform-specific packet
*/
public Object build(PacketPlayOutPlayerListHeaderFooter packet, ProtocolVersion clientVersion) {
return packet;
}
/**
* Constructs platform-specific PacketPlayOutScoreboardDisplayObjective class based on custom packet class
*
* @param packet
* Custom packet to be built
* @param clientVersion
* Protocol version of player to build the packet for
* @return Platform-specific packet
*/
public Object build(PacketPlayOutScoreboardDisplayObjective packet, ProtocolVersion clientVersion) {
return packet;
}
/**
* Constructs platform-specific PacketPlayOutScoreboardObjective class based on custom packet class
*
* @param packet
* Custom packet to be built
* @param clientVersion
* Protocol version of player to build the packet for
* @return Platform-specific packet
*/
public Object build(PacketPlayOutScoreboardObjective packet, ProtocolVersion clientVersion) {
return packet;
}
/**
* Constructs platform-specific PacketPlayOutScoreboardScore class based on custom packet class
*
* @param packet
* Custom packet to be built
* @param clientVersion
* Protocol version of player to build the packet for
* @return Platform-specific packet
*/
public Object build(PacketPlayOutScoreboardScore packet, ProtocolVersion clientVersion) {
return packet;
}
/**
* Constructs platform-specific PacketPlayOutScoreboardTeam class based on custom packet class
*
* @param packet
* Custom packet to be built
* @param clientVersion
* Protocol version of player to build the packet for
* @return Platform-specific packet
*/
public Object build(PacketPlayOutScoreboardTeam packet, ProtocolVersion clientVersion) {
return packet;
}
/**
* Cuts given string to specified character length (or length-1 if last character is a color character)
* and translates RGB to legacy colors. If string is not that long, the original string is returned.
* RGB codes are converted into legacy, since cutting is only needed for &lt;1.13.
* If {@code string} is {@code null}, empty string is returned.
*
* @param string
* String to cut
* @param length
* Length to cut to
* @return string cut to {@code length} characters
*/
public String cutTo(String string, int length) {
if (string == null) return "";
String legacyText = string;
if (string.contains("#")) {
//converting RGB to legacy colors
legacyText = RGBUtils.getInstance().convertRGBtoLegacy(string);
}
if (legacyText.length() <= length) return legacyText;
if (legacyText.charAt(length-1) == EnumChatFormat.COLOR_CHAR) {
return legacyText.substring(0, length-1); //cutting one extra character to prevent prefix ending with "&"
} else {
return legacyText.substring(0, length);
}
}
/**
* If {@code clientVersion} is &gt;= 1.13, creates a component from given text and returns
* it as a serialized component, which BungeeCord uses.
* <p>
* If {@code clientVersion} is &lt; 1.12, the text is cut to {@code length} characters if
* needed and returned.
*
* @param text
* Text to convert
* @param clientVersion
* Version of player to convert text for
* @return serialized component for 1.13+ clients, cut string for 1.12-
*/
public String jsonOrCut(String text, ProtocolVersion clientVersion, int length) {
if (text == null) return null;
if (clientVersion.getMinorVersion() >= 13) {
return IChatBaseComponent.optimizedComponent(text).toString(clientVersion);
} else {
return cutTo(text, length);
}
}
/**
* Converts platform-specific instance of player info packet into
* {@link PacketPlayOutPlayerInfo} object.
*
* @param packet
* platform-specific info packet
* @param clientVersion
* Version of client receiving the packet
* @return The packet converted into {@link PacketPlayOutPlayerInfo}
* @throws ReflectiveOperationException
* if thrown by reflective operation
*/
public PacketPlayOutPlayerInfo readPlayerInfo(Object packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
return null;
}
/**
* Converts platform-specific instance of objective packet into
* {@link PacketPlayOutScoreboardObjective} object.
*
* @param packet
* platform-specific objective packet
* @return The packet converted into {@link PacketPlayOutScoreboardObjective}
* @throws ReflectiveOperationException
* if thrown by reflective operation
*/
public PacketPlayOutScoreboardObjective readObjective(Object packet) throws ReflectiveOperationException {
return null;
}
/**
* Converts platform-specific instance of display objective packet into
* {@link PacketPlayOutScoreboardDisplayObjective} object.
*
* @param packet
* platform-specific display objective packet
* @return The packet converted into {@link PacketPlayOutScoreboardDisplayObjective}
* @throws ReflectiveOperationException
* if thrown by reflective operation
*/
public PacketPlayOutScoreboardDisplayObjective readDisplayObjective(Object packet) throws ReflectiveOperationException {
return null;
}
}
@@ -1,175 +0,0 @@
package me.neznamy.tab.api.protocol;
import lombok.Data;
import lombok.NonNull;
import me.neznamy.tab.api.bossbar.BarColor;
import me.neznamy.tab.api.bossbar.BarStyle;
import java.util.UUID;
/**
* A class representing platform specific packet class
*/
@Data
public class PacketPlayOutBoss implements TabPacket {
/** UUID of the BossBar */
private final UUID id;
/** Action of this packet */
private Action action;
/** BossBar title */
private String name;
/** BossBar progress (0-1)*/
private float pct;
/** BossBar color */
private BarColor color;
/** BossBar style */
private BarStyle overlay;
/** Darken screen flag */
private boolean darkenScreen;
/** Play music flag */
private boolean playMusic;
/** Create fog flag */
private boolean createWorldFog;
/**
* Constructs new instance with given parameters and
* {@link me.neznamy.tab.api.protocol.PacketPlayOutBoss.Action}.ADD action
*
* @param id
* BossBar uuid
* @param name
* BossBar title
* @param pct
* BossBar progress
* @param color
* BossBar color
* @param overlay
* BossBar style
*/
public PacketPlayOutBoss(@NonNull UUID id, @NonNull String name, float pct, @NonNull BarColor color, @NonNull BarStyle overlay) {
this.action = Action.ADD;
this.id = id;
this.name = name;
this.pct = pct;
this.color = color;
this.overlay = overlay;
}
/**
* Constructs new instance with given parameters and
* {@link me.neznamy.tab.api.protocol.PacketPlayOutBoss.Action#REMOVE} action
*
* @param id
* BossBar uuid
*/
public PacketPlayOutBoss(@NonNull UUID id) {
this.action = Action.REMOVE;
this.id = id;
}
/**
* Constructs new instance with given parameters and
* {@link me.neznamy.tab.api.protocol.PacketPlayOutBoss.Action#UPDATE_PCT} action
*
* @param id
* BossBar uuid
* @param pct
* BossBar progress
*/
public PacketPlayOutBoss(@NonNull UUID id, float pct) {
this.action = Action.UPDATE_PCT;
this.id = id;
this.pct = pct;
}
/**
* Constructs new instance with given parameters and
* {@link me.neznamy.tab.api.protocol.PacketPlayOutBoss.Action#UPDATE_NAME} action
*
* @param id
* BossBar uuid
* @param name
* BossBar title
*/
public PacketPlayOutBoss(@NonNull UUID id, @NonNull String name) {
this.action = Action.UPDATE_NAME;
this.id = id;
this.name = name;
}
/**
* Constructs new instance with given parameters and
* {@link me.neznamy.tab.api.protocol.PacketPlayOutBoss.Action#UPDATE_STYLE} action
*
* @param id
* BossBar uuid
* @param color
* BossBar color
* @param overlay
* BossBar style
*/
public PacketPlayOutBoss(@NonNull UUID id, @NonNull BarColor color, @NonNull BarStyle overlay) {
this.action = Action.UPDATE_STYLE;
this.id = id;
this.color = color;
this.overlay = overlay;
}
/**
* Constructs new instance with given parameters and
* {@link me.neznamy.tab.api.protocol.PacketPlayOutBoss.Action#UPDATE_PROPERTIES} action
*
* @param darkenScreen
* Darken screen flag
* @param playMusic
* Play music flag
* @param createWorldFog
* Create fog flag
*/
public PacketPlayOutBoss(@NonNull UUID id, boolean darkenScreen, boolean playMusic, boolean createWorldFog) {
this.action = Action.UPDATE_PROPERTIES;
this.id = id;
this.darkenScreen = darkenScreen;
this.playMusic = playMusic;
this.createWorldFog = createWorldFog;
}
/**
* Returns bitmask based on {@link #darkenScreen}, {@link #playMusic} and {@link #darkenScreen} values.
* <p>
* {@link #darkenScreen} adds {@code 1}, {@link #playMusic} {@code 2} and {@link #darkenScreen} {@code 4}
* to the final value.
*
* @return the bitmask
*/
public byte getFlags() {
byte value = 0;
if (darkenScreen) value += 1;
if (playMusic) value += 2;
if (createWorldFog) value += 4;
return value;
}
/**
* An enum representing all valid boss packet actions.
* Calling ordinal() will return action's network ID.
*/
public enum Action {
ADD,
REMOVE,
UPDATE_PCT,
UPDATE_NAME,
UPDATE_STYLE,
UPDATE_PROPERTIES
}
}
@@ -1,29 +0,0 @@
package me.neznamy.tab.api.protocol;
import lombok.Data;
import lombok.NonNull;
import me.neznamy.tab.api.chat.IChatBaseComponent;
/**
* A class representing platform specific packet class
*/
@Data
public class PacketPlayOutChat implements TabPacket {
/** Message to be sent */
@NonNull private final IChatBaseComponent message;
/** Message position */
@NonNull private final ChatMessageType type;
/**
* An enum representing positions of a chat message
* Calling ordinal() will return type's network ID.
*/
public enum ChatMessageType {
CHAT,
SYSTEM,
GAME_INFO
}
}
@@ -1,190 +0,0 @@
package me.neznamy.tab.api.protocol;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NonNull;
import me.neznamy.tab.api.chat.IChatBaseComponent;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.List;
import java.util.UUID;
/**
* A class representing platform specific packet class
*/
@Data @AllArgsConstructor
public class PacketPlayOutPlayerInfo implements TabPacket {
/** Packet actions */
private final EnumSet<EnumPlayerInfoAction> actions;
/** List of affected entries */
private final List<PlayerInfoData> entries;
/**
* Constructs new instance with given parameters.
*
* @param action
* Packet action
* @param entries
* Affected entries
*/
public PacketPlayOutPlayerInfo(EnumPlayerInfoAction action, PlayerInfoData... entries) {
this(action, Arrays.asList(entries));
}
/**
* Constructs new instance with given parameters.
*
* @param action
* Packet action
* @param entries
* Affected entries
*/
public PacketPlayOutPlayerInfo(@NonNull EnumPlayerInfoAction action, @NonNull List<PlayerInfoData> entries) {
if (action == EnumPlayerInfoAction.ADD_PLAYER) {
actions = EnumSet.of(EnumPlayerInfoAction.ADD_PLAYER,
EnumPlayerInfoAction.INITIALIZE_CHAT,
EnumPlayerInfoAction.UPDATE_GAME_MODE,
EnumPlayerInfoAction.UPDATE_LISTED,
EnumPlayerInfoAction.UPDATE_LATENCY,
EnumPlayerInfoAction.UPDATE_DISPLAY_NAME);
} else {
this.actions = EnumSet.of(action);
}
this.entries = entries;
}
/**
* Constructs new instance with given parameters.
*
* @param actions
* Packet actions
* @param entries
* Affected entries
*/
public PacketPlayOutPlayerInfo(@NonNull EnumSet<EnumPlayerInfoAction> actions, @NonNull PlayerInfoData... entries) {
this.actions = actions;
this.entries = Arrays.asList(entries);
}
/**
* A subclass representing player list entry
*/
@Data @AllArgsConstructor
public static class PlayerInfoData {
/** Real name of affected player */
private String name;
/** Player UUID */
@NonNull private UUID uniqueId;
/** Player's skin, null for empty skin */
private Skin skin;
/** I have no idea what this is */
private boolean listed;
/** Latency */
private int latency;
/** GameMode */
private EnumGamemode gameMode = EnumGamemode.SURVIVAL; //ProtocolLib causes NPE even when action does not use GameMode
/**
* Display name displayed in TabList. Using {@code null} results in no display name
* and scoreboard team prefix/suffix being visible in TabList instead.
*/
private IChatBaseComponent displayName;
/** Chat session ID */
private UUID chatSessionId;
/** Player's chat signing key */
private Object profilePublicKey;
/**
* Constructs new instance with given parameters. Suitable for
* {@link EnumPlayerInfoAction#UPDATE_GAME_MODE} action
*
* @param uniqueId
* Player's uuid
* @param gameMode
* Player's GameMode
*/
public PlayerInfoData(@NonNull UUID uniqueId, EnumGamemode gameMode) {
this.uniqueId = uniqueId;
this.gameMode = gameMode;
}
/**
* Constructs new instance with given parameters. Suitable for
* {@link EnumPlayerInfoAction#UPDATE_LATENCY} action
*
* @param uniqueId
* Player's uuid
* @param latency
* Player's ping
*/
public PlayerInfoData(@NonNull UUID uniqueId, int latency) {
this.uniqueId = uniqueId;
this.latency = latency;
}
/**
* Constructs new instance with given parameters. Suitable for
* {@link EnumPlayerInfoAction#UPDATE_DISPLAY_NAME} action
*
* @param uniqueId
* Player's uuid
* @param displayName
* Player's display name
*/
public PlayerInfoData(@NonNull UUID uniqueId, IChatBaseComponent displayName) {
this.uniqueId = uniqueId;
this.displayName = displayName;
}
/**
* Constructs new instance with given parameter. Suitable for
* {@link EnumPlayerInfoAction#REMOVE_PLAYER} action
*
* @param uniqueId
* Player's uuid
*/
public PlayerInfoData(@NonNull UUID uniqueId) {
this.uniqueId = uniqueId;
}
}
/**
* En enum representing packet action
*/
public enum EnumPlayerInfoAction {
ADD_PLAYER,
INITIALIZE_CHAT,
UPDATE_GAME_MODE,
UPDATE_LISTED,
UPDATE_LATENCY,
UPDATE_DISPLAY_NAME,
REMOVE_PLAYER
}
/**
* An enum representing GameMode
*/
public enum EnumGamemode {
NOT_SET,
SURVIVAL,
CREATIVE,
ADVENTURE,
SPECTATOR;
/** Value array to iterate over to avoid array creations on each call */
public static final EnumGamemode[] VALUES = values();
}
}
@@ -1,33 +0,0 @@
package me.neznamy.tab.api.protocol;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NonNull;
import me.neznamy.tab.api.chat.IChatBaseComponent;
/**
* A class representing platform specific packet class
*/
@Data @AllArgsConstructor
public class PacketPlayOutPlayerListHeaderFooter implements TabPacket {
/** TabList header */
@NonNull private final IChatBaseComponent header;
/** TabList footer */
@NonNull private final IChatBaseComponent footer;
/**
* Constructs new instance with given parameters. They are converted to {@link IChatBaseComponent}
* using {@link IChatBaseComponent#optimizedComponent(String)} method.
*
* @param header
* TabList header
* @param footer
* TabList footer
*/
public PacketPlayOutPlayerListHeaderFooter(@NonNull String header, @NonNull String footer) {
this.header = IChatBaseComponent.optimizedComponent(header);
this.footer = IChatBaseComponent.optimizedComponent(footer);
}
}
@@ -1,22 +0,0 @@
package me.neznamy.tab.api.protocol;
import lombok.Data;
import lombok.NonNull;
/**
* A class representing platform specific packet class
*/
@Data
public class PacketPlayOutScoreboardDisplayObjective implements TabPacket {
/**
* Display slot.
* 0 = PlayerList,
* 1 = SideBar,
* 2 = BelowName.
*/
private final int slot;
/** Up to 16 characters long objective name */
@NonNull private final String objectiveName;
}
@@ -1,55 +0,0 @@
package me.neznamy.tab.api.protocol;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NonNull;
import me.neznamy.tab.api.util.Preconditions;
/**
* A class representing platform specific packet class
*/
@Data @AllArgsConstructor
public class PacketPlayOutScoreboardObjective implements TabPacket {
/**
* Packet action.
* 0 = register,
* 1 = unregister,
* 2 = update title
*/
private final int action;
/** Up to 16 characters long objective name */
@NonNull private final String objectiveName;
/** Display name */
private final String displayName;
/** Display type, only takes effect in PlayerList */
private final EnumScoreboardHealthDisplay renderType;
/**
* Constructs new packet with given objective name and 1 (unregister) action.
*
* @param objectiveName
* objective name, up to 16 characters long
* @throws IllegalArgumentException
* if {@code objectiveName} is null or longer than 16 characters
*/
public PacketPlayOutScoreboardObjective(@NonNull String objectiveName) {
Preconditions.checkMaxLength(objectiveName, 16, "objective name");
this.objectiveName = objectiveName;
this.displayName = ""; //avoiding NPE on <1.7
this.action = 1;
this.renderType = null;
}
/**
* An enum representing available display types.
* They only take effect in PlayerList position.
*/
public enum EnumScoreboardHealthDisplay {
INTEGER, HEARTS
}
}
@@ -1,32 +0,0 @@
package me.neznamy.tab.api.protocol;
import lombok.Data;
import lombok.NonNull;
/**
* A class representing platform specific packet class
*/
@Data
public class PacketPlayOutScoreboardScore implements TabPacket {
/** Packet action */
@NonNull private final Action action;
/** Objective name */
@NonNull private final String objectiveName;
/** Affected player */
@NonNull private final String player;
/** Player's score */
private final int score;
/**
* An enum representing action types
*/
public enum Action {
CHANGE,
REMOVE
}
}
@@ -1,180 +0,0 @@
package me.neznamy.tab.api.protocol;
import lombok.Data;
import lombok.NonNull;
import me.neznamy.tab.api.chat.EnumChatFormat;
import me.neznamy.tab.api.util.Preconditions;
import java.util.Collection;
import java.util.Collections;
/**
* A class representing platform specific packet class
*/
@Data
public class PacketPlayOutScoreboardTeam implements TabPacket {
/**
* Up to 16 characters long team name.
* It is used to sort players alphabetically in TabList by team names.
* Team-less players appear on top, sorted by usernames.
* Players with teams are below them, players of the same team are
* sorted alphabetically by usernames.
*/
@NonNull private final String name;
/** Team prefix, limited to 16 characters, on &lt;1.13 */
private String playerPrefix;
/** Team suffix, limited to 16 characters, on &lt;1.13 */
private String playerSuffix;
/**
* NameTag visibility rule since 1.8. Possible options are:
* {@code always}, {@code hideForOtherTeams}, {@code hideForOwnTeam}, {@code never}
*/
private String nameTagVisibility;
/**
* Collision rule added in 1.9. Possible options are:
* {@code always}, {@code pushOtherTeams}, {@code pushOwnTeam}, {@code never}
*/
private String collisionRule;
/**
* Team color field added in 1.13. It determines name color,
* prefix and suffix color start.
*/
private EnumChatFormat color;
/**
* Affected entities. For players, it is their name, for other entities their UUID
* Player name length is limited to 16 characters for &lt;1.8, 40 characters since 1.8.
* Entity UUID option was added in 1.8.
*/
@NonNull private Collection<String> players = Collections.emptyList();
/**
* Packet action.
* 0 = create team,
* 1 = remove team,
* 2 = update team info,
* 3 = add entries,
* 4 = remove entries
*/
private final int action;
/**
* Bit mask.
* 0x01: Allow friendly fire,
* 0x02: Can see invisible players on the same team.
*/
private int options;
/**
* Constructs new instance with given parameters. Private constructor used
* internally to validate team name and set action.
*
* @param action
* Packet action (0 = create team, 1 = remove team, 2 = update team info,
* 3 = add entries, 4 = remove entries)
* @param name
* Team name, up to 16 characters long
* @throws IllegalArgumentException
* if {@code name} is null, empty or longer than 16 characters
*/
private PacketPlayOutScoreboardTeam(int action, @NonNull String name) {
Preconditions.checkMaxLength(name, 16, "team name");
this.action = action;
this.name = name;
}
/**
* Constructs new instance with given parameters and 0 (CREATE) action
*
* @param team
* Team name, up to 16 characters long
* @param prefix
* Prefix of players in team
* @param suffix
* Suffix of players in team
* @param visibility
* NameTag visibility rule
* @param collision
* Collision rule
* @param players
* Affected entities
* @param options
* bitmask of team options
* @throws IllegalArgumentException
* if {@code name} is null, empty or longer than 16 characters
*/
public PacketPlayOutScoreboardTeam(String team, @NonNull String prefix, @NonNull String suffix, String visibility,
String collision, @NonNull Collection<String> players, int options) {
this(0, team);
this.playerPrefix = prefix;
this.playerSuffix = suffix;
this.nameTagVisibility = visibility;
this.collisionRule = collision;
this.players = players;
this.options = options;
}
/**
* Constructs new instance with given parameters and 1 (REMOVE) action
*
* @param team
* Team name, up to 16 characters long
* @throws IllegalArgumentException
* if {@code name} is null, empty or longer than 16 characters
*/
public PacketPlayOutScoreboardTeam(String team) {
this(1, team);
}
/**
* Constructs new instance with given parameters and 2 (UPDATE_TEAM_INFO) action
*
* @param team
* Team name, up to 16 characters long
* @param prefix
* Prefix of players in team
* @param suffix
* Suffix of players in team
* @param visibility
* NameTag visibility rule
* @param collision
* Collision rule
* @param options
* bitmask of team options
* @throws IllegalArgumentException
* if {@code name} is null, empty or longer than 16 characters
*/
public PacketPlayOutScoreboardTeam(String team, @NonNull String prefix, @NonNull String suffix,
String visibility, String collision, int options) {
this(2, team);
this.playerPrefix = prefix;
this.playerSuffix = suffix;
this.nameTagVisibility = visibility;
this.collisionRule = collision;
this.options = options;
}
/**
* Constructs new instance with given parameters and 3 (ADD_PLAYERS) if {@code add} is {@code true},
* 4 (REMOVE_PLAYERS) if {@code add} is {@code false} action.
*
* @param team
* Team name, up to 16 characters long
* @param players
* Affected entities
* @param add
* {@code true} if players should be added, {@code false} if removed from team
* @throws IllegalArgumentException
* if {@code name} is null, empty or longer than 16 characters
*/
public PacketPlayOutScoreboardTeam(String team, @NonNull Collection<String> players, boolean add) {
this(add ? 3 : 4, team);
this.players = players;
}
}
@@ -1,18 +0,0 @@
package me.neznamy.tab.api.protocol;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NonNull;
/**
* Class representing a minecraft skin as a value - signature pair.
*/
@Data @AllArgsConstructor
public class Skin {
/** Skin value */
@NonNull private final String value;
/** Skin signature, can be null */
private final String signature;
}
@@ -1,17 +0,0 @@
package me.neznamy.tab.api.protocol;
/**
* Interface representing a packet that can be sent to client independently of platform.
* It is technically empty, existence is only present to restrict type access
* when sending packets to player, so {@link me.neznamy.tab.api.TabPlayer#sendCustomPacket(TabPacket)} cannot
* be called with an actual packet.
*/
public interface TabPacket {
/**
* Returns user-friendly string representation of the packet
*
* @return user-friendly string representation of the packet
*/
String toString();
}
@@ -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);
}
@@ -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,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,96 +0,0 @@
package me.neznamy.tab.api.task;
import java.util.concurrent.Future;
import me.neznamy.tab.api.TabFeature;
/**
* Manager for scheduling and executing tasks in TAB's
* main thread to avoid concurrency issues.
*/
public interface ThreadManager {
/**
* Starts a task in new thread and measures how long it took to process
*
* @param feature
* feature to add cpu usage to
* @param type
* usage type to add cpu usage to
* @param task
* the task
*/
Future<Void> runMeasuredTask(TabFeature feature, String type, Runnable task);
/**
* Starts a task in new thread and measures how long it took to process
*
* @param feature
* feature to add cpu usage to
* @param type
* usage type to add cpu usage to
* @param task
* the task
*/
Future<Void> runMeasuredTask(String feature, String type, Runnable task);
/**
* Runs task in a new thread
*
* @param task
* the task
*/
Future<Void> runTask(Runnable task);
/**
* Starts a new task with defined repeat interval that measures cpu usage
*
* @param intervalMilliseconds
* task interval
* @param feature
* feature to add cpu usage to
* @param type
* usage type to add cpu usage to
* @param task
* the task
* @return Future representing the submitted task
*/
Future<?> startRepeatingMeasuredTask(int intervalMilliseconds, TabFeature feature, String type, Runnable task);
/**
* Starts a new task with defined repeat interval
*
* @param intervalMilliseconds
* task interval
* @param task
* the task
* @return Future representing the submitted task
*/
Future<?> startRepeatingTask(int intervalMilliseconds, Runnable task);
/**
* Runs task with a delay and measures how long it took to process
*
* @param delayMilliseconds
* how long to run the task after
* @param feature
* feature to add cpu usage to
* @param type
* usage type to add cpu usage to
* @param task
* the task
* @return Future representing the submitted task
*/
Future<?> runTaskLater(int delayMilliseconds, TabFeature feature, String type, Runnable task);
/**
* Runs task with a delay
*
* @param delayMilliseconds
* how long to run the task after
* @param task
* the task
* @return Future representing the submitted task
*/
Future<?> runTaskLater(int delayMilliseconds, Runnable task);
}
@@ -1,182 +0,0 @@
package me.neznamy.tab.api.team;
import me.neznamy.tab.api.TabPlayer;
public interface TeamManager {
/**
* Makes player's NameTag globally invisible
*
* @param player
* player to hide nametag of
* @see #showNametag(TabPlayer)
* @see #hasHiddenNametag(TabPlayer)
*/
void hideNametag(TabPlayer player);
/**
* Hides player's NameTag for specified player until it's shown again
*
* @param player
* player to hide nametag of
* @param viewer
* player to hide NameTag for
*/
void hideNametag(TabPlayer player, TabPlayer viewer);
/**
* Makes player's NameTag visible again
*
* @param player
* player to show nametag of
* @see #hideNametag(TabPlayer)
* @see #hasHiddenNametag(TabPlayer)
*/
void showNametag(TabPlayer player);
/**
* Shows player's NameTag for specified viewer if it was hidden before
*
* @param player
* player to show nametag of
* @param viewer
* player to show NameTag back for
*/
void showNametag(TabPlayer player, TabPlayer viewer);
/**
* Return whether player has hidden NameTag or not
*
* @param player
* player to check nametag visibility status of
* @return Whether player has hidden NameTag or not
* @see #hideNametag(TabPlayer)
* @see #showNametag(TabPlayer)
*/
boolean hasHiddenNametag(TabPlayer player);
/**
* Returns true if NameTag is hidden for specified viewer, false if not
*
* @param player
* player to check visibility status of
* @param viewer
* player to check visibility status for
* @return true if hidden, false if not
*/
boolean hasHiddenNametag(TabPlayer player, TabPlayer viewer);
/**
* Unregisters player's team and no longer handles it, as well as disables anti-override for teams.
* This can be resumed using resumeTeamHandling(). If team handling was paused already, nothing happens.
*
* @param player
* player to pause team handling of
*/
void pauseTeamHandling(TabPlayer player);
/**
* Resumes team handling if it was before paused using pauseTeamHandling(), if not, nothing happens
*
* @param player
* player to resume team handling of
*/
void resumeTeamHandling(TabPlayer player);
/**
* Returns true if team handling is paused for this player using pauseTeamHandling(), false if not, or
* it was resumed already using resumeTeamHandling
*
* @param player
* player to check handling status of
* @return true if paused, false if not
*/
boolean hasTeamHandlingPaused(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(TabPlayer player, 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
*/
String getForcedTeamName(TabPlayer player);
/**
* Forces collision rule for the player. Setting it to null will remove forced value
*
* @param player
* player to set collision rule of
* @param collision
* forced collision rule
*/
void setCollisionRule(TabPlayer player, Boolean collision);
/**
* Returns forced collision rule or null if collision is not forced using setCollisionRule
*
* @param player
* player to get forced collision of
* @return forced value or null if not forced
*/
Boolean getCollisionRule(TabPlayer player);
/**
* Sends update team's properties packet of player's team to everyone
*
* @param player
* player to update team data of
*/
void updateTeamData(TabPlayer player);
void setPrefix(TabPlayer player, String prefix);
void setSuffix(TabPlayer player, String suffix);
void resetPrefix(TabPlayer player);
void resetSuffix(TabPlayer player);
String getCustomPrefix(TabPlayer player);
String getCustomSuffix(TabPlayer player);
String getOriginalPrefix(TabPlayer player);
String getOriginalSuffix(TabPlayer player);
/**
* Toggles nametag visibility view on all players for specified player.
* On first call, nametags 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
* @param sendToggleMessage
* {@code true} if configured toggle message should be sent, {@code false} if not
*/
void toggleNameTagVisibilityView(TabPlayer player, boolean sendToggleMessage);
/**
* Returns {@code true} if player has hidden nametags by either calling
* {@link #toggleNameTagVisibilityView(TabPlayer, boolean)} or using a command,
* {@code false} if not.
*
* @param player
* player to check
* @return {@code true} if hidden, {@code false} if not
*/
boolean hasHiddenNameTagVisibilityView(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,64 +0,0 @@
package me.neznamy.tab.api.util;
import lombok.NonNull;
import me.neznamy.tab.api.TabPlayer;
/**
* Class with utility methods used in the plugin
*/
public class Preconditions {
/**
* Private constructor preventing the class from being instantiated
*/
private 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;
}
}
}
@@ -1,12 +0,0 @@
package me.neznamy.tab.api.util;
/**
* Supplier that can throw an exception
*
* @param <T>
* Return class type
*/
public interface SupplierWithException<T> {
T get() throws Exception;
}
+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,10 +8,12 @@ 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,
"website" to rootProject.ext.get("website")!!.toString()
"website" to rootProject.ext.get("website")!!.toString(),
"author" to rootProject.ext.get("author")!!.toString()
))
}
javadoc {
@@ -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")
}
}
+4 -2
View File
@@ -4,11 +4,12 @@ plugins {
allprojects {
group = "me.neznamy"
version = "3.3.0"
version = "4.0.3-SNAPSHOT"
description = "An all-in-one solution that works"
ext.set("id", "tab")
ext.set("website", "https://github.com/NEZNAMY/TAB")
ext.set("author", "NEZNAMY")
}
val platforms = setOf(
@@ -17,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,7 +1,8 @@
package me.neznamy.tab.platforms.bukkit;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.api.TabAPI;
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;
@@ -13,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 (TabAPI.getInstance().isPluginDisabled()) return;
TabAPI.getInstance().getThreadManager().runTask(() -> TabAPI.getInstance().getFeatureManager().onQuit(TabAPI.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 (TabAPI.getInstance().isPluginDisabled()) return;
TabAPI.getInstance().getThreadManager().runTask(() ->
TabAPI.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 (TabAPI.getInstance().isPluginDisabled()) return;
TabAPI.getInstance().getThreadManager().runTask(() ->
TabAPI.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);
}
}
@@ -1,98 +0,0 @@
package me.neznamy.tab.platforms.bukkit;
import me.neznamy.tab.api.ProtocolVersion;
import me.neznamy.tab.api.protocol.*;
import me.neznamy.tab.api.protocol.PacketPlayOutBoss.Action;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityDestroy;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityMetadata;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityTeleport;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutSpawnEntityLiving;
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 org.bukkit.Location;
import org.bukkit.entity.EntityType;
import java.util.UUID;
public class BukkitPacketBuilder extends PacketBuilder {
{
buildMap.put(PacketPlayOutEntityMetadata.class, (packet, version) -> ((PacketPlayOutEntityMetadata)packet).build());
buildMap.put(PacketPlayOutEntityTeleport.class, (packet, version) -> ((PacketPlayOutEntityTeleport)packet).build());
buildMap.put(PacketPlayOutEntityDestroy.class, (packet, version) -> ((PacketPlayOutEntityDestroy)packet).build());
buildMap.put(PacketPlayOutSpawnEntityLiving.class, (packet, version) -> ((PacketPlayOutSpawnEntityLiving)packet).build());
buildMap.put(PacketPlayOutPlayerListHeaderFooter.class, (packet, version) -> PacketPlayOutPlayerListHeaderFooterStorage.build((PacketPlayOutPlayerListHeaderFooter) packet, version));
buildMap.put(PacketPlayOutChat.class, (packet, version) -> PacketPlayOutChatStorage.build((PacketPlayOutChat) packet, version));
buildMap.put(PacketPlayOutScoreboardObjective.class, (packet, version) -> PacketPlayOutScoreboardObjectiveStorage.build((PacketPlayOutScoreboardObjective) packet, version));
buildMap.put(PacketPlayOutScoreboardDisplayObjective.class, (packet, version) -> PacketPlayOutScoreboardDisplayObjectiveStorage.build((PacketPlayOutScoreboardDisplayObjective) packet, version));
buildMap.put(PacketPlayOutScoreboardTeam.class, (packet, version) -> PacketPlayOutScoreboardTeamStorage.build((PacketPlayOutScoreboardTeam) packet, version));
buildMap.put(PacketPlayOutScoreboardScore.class, (packet, version) -> PacketPlayOutScoreboardScoreStorage.build((PacketPlayOutScoreboardScore) packet, version));
}
@Override
public Object build(PacketPlayOutBoss packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
if (NMSStorage.getInstance().getMinorVersion() >= 9 || clientVersion.getMinorVersion() >= 9) {
//1.9+ server or client, handled by bukkit api or ViaVersion
return packet;
}
//<1.9 client and server
return buildBossPacketEntity(packet, clientVersion);
}
@Override
public Object build(PacketPlayOutPlayerInfo packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
return PacketPlayOutPlayerInfoStorage.build(packet, clientVersion);
}
@Override
public PacketPlayOutPlayerInfo readPlayerInfo(Object nmsPacket, ProtocolVersion clientVersion) throws ReflectiveOperationException {
return PacketPlayOutPlayerInfoStorage.read(nmsPacket);
}
@Override
public PacketPlayOutScoreboardObjective readObjective(Object nmsPacket) throws ReflectiveOperationException {
return PacketPlayOutScoreboardObjectiveStorage.read(nmsPacket);
}
@Override
public PacketPlayOutScoreboardDisplayObjective readDisplayObjective(Object nmsPacket) throws ReflectiveOperationException {
return PacketPlayOutScoreboardDisplayObjectiveStorage.read(nmsPacket);
}
/**
* Builds entity packet representing requested BossBar packet using Wither on 1.8- clients.
*
* @param packet
* packet to build
* @param clientVersion
* client version
* @return entity BossBar packet
* @throws ReflectiveOperationException
* if thrown by reflective operation
*/
private Object buildBossPacketEntity(PacketPlayOutBoss packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
if (packet.getAction() == Action.UPDATE_STYLE) return null; //nothing to do here
int entityId = packet.getId().hashCode();
if (packet.getAction() == Action.REMOVE) {
return new PacketPlayOutEntityDestroy(entityId).build();
}
DataWatcher w = new DataWatcher();
if (packet.getAction() == Action.UPDATE_PCT || packet.getAction() == Action.ADD) {
float health = 300*packet.getPct();
if (health == 0) health = 1;
w.getHelper().setHealth(health);
}
if (packet.getAction() == Action.UPDATE_NAME || packet.getAction() == Action.ADD) {
w.getHelper().setCustomName(packet.getName(), clientVersion);
}
if (packet.getAction() == Action.ADD) {
w.getHelper().setEntityFlags((byte) 32);
w.getHelper().setWitherInvulnerableTime(880); // Magic number
return new PacketPlayOutSpawnEntityLiving(entityId, new UUID(0, 0), EntityType.WITHER, new Location(null, 0,0,0), w).build();
} else {
return new PacketPlayOutEntityMetadata(entityId, w).build();
}
}
}
@@ -1,36 +1,27 @@
package me.neznamy.tab.platforms.bukkit;
import java.util.ArrayList;
import java.util.Collection;
import java.util.function.Function;
import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
import lombok.AllArgsConstructor;
import lombok.Getter;
import me.neznamy.tab.api.TabFeature;
import me.neznamy.tab.api.TabPlayer;
import com.mojang.authlib.GameProfile;
import io.netty.channel.Channel;
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.api.TabConstants;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutPlayerInfoStorage;
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.PipelineInjector;
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;
/**
* Pipeline injection for bukkit
*/
public class BukkitPipelineInjector extends PipelineInjector {
/** NMS data storage */
private final NMSStorage nms = NMSStorage.getInstance();
@Getter private final Function<TabPlayer, ChannelDuplexHandler> channelFunction = BukkitChannelDuplexHandler::new;
public class BukkitPipelineInjector extends NettyPipelineInjector {
/**
* Constructs new instance
@@ -39,106 +30,124 @@ public class BukkitPipelineInjector extends PipelineInjector {
super("packet_handler");
}
/**
* Custom channel duplex handler override
*/
@AllArgsConstructor
public class BukkitChannelDuplexHandler extends ChannelDuplexHandler {
@Override
protected Channel getChannel(@NotNull TabPlayer player) {
BukkitTabPlayer bukkit = (BukkitTabPlayer) player;
NMSStorage nms = NMSStorage.getInstance();
try {
if (nms.CHANNEL != null) return (Channel) nms.CHANNEL.get(nms.NETWORK_MANAGER.get(bukkit.getPlayerConnection()));
} catch (IllegalAccessException exception) {
TAB.getInstance().getErrorManager().printError("Failed to get channel of " + bukkit.getName(), exception);
}
return null;
}
/** Injected player */
private final TabPlayer player;
@Override
@SneakyThrows
public void onDisplayObjective(@NotNull TabPlayer player, @NotNull Object packet) {
TAB.getInstance().getFeatureManager().onDisplayObjective(player,
PacketScoreboard.DisplayObjective_POSITION.getInt(packet),
(String) PacketScoreboard.DisplayObjective_OBJECTIVE_NAME.get(packet));
}
@Override
public void channelRead(@NotNull ChannelHandlerContext context, @NotNull Object packet) {
try {
if (TAB.getInstance().getFeatureManager().onPacketReceive(player, packet)) return;
super.channelRead(context, packet);
} catch (Exception e) {
TAB.getInstance().getErrorManager().printError("An error occurred when reading packets", e);
@Override
@SneakyThrows
public void onObjective(@NotNull TabPlayer player, @NotNull Object packet) {
TAB.getInstance().getFeatureManager().onObjective(player,
PacketScoreboard.Objective_METHOD.getInt(packet),
(String) PacketScoreboard.Objective_OBJECTIVE_NAME.get(packet));
}
@Override
public boolean isDisplayObjective(@NotNull Object packet) {
return PacketScoreboard.DisplayObjectiveClass.isInstance(packet);
}
@Override
public boolean isObjective(@NotNull Object packet) {
return PacketScoreboard.ObjectivePacketClass.isInstance(packet);
}
@Override
public boolean isTeam(@NotNull Object packet) {
return PacketScoreboard.TeamPacketClass.isInstance(packet);
}
@Override
public boolean isPlayerInfo(@NotNull Object packet) {
return BukkitTabList.PacketPlayOutPlayerInfoClass.isInstance(packet);
}
@Override
@SneakyThrows
public void onPlayerInfo(@NotNull TabPlayer receiver, @NotNull Object packet) {
NMSStorage nms = NMSStorage.getInstance();
List<String> actions;
if (BukkitTabList.ClientboundPlayerInfoRemovePacket != null) {
//1.19.3+
actions = ((EnumSet<?>)BukkitTabList.ACTION.get(packet)).stream().map(Enum::name).collect(Collectors.toList());
} else {
//1.19.2-
actions = Collections.singletonList(BukkitTabList.ACTION.get(packet).toString());
}
List<Object> updatedList = new ArrayList<>();
for (Object nmsData : (List<?>) BukkitTabList.PLAYERS.get(packet)) {
GameProfile profile = (GameProfile) BukkitTabList.PlayerInfoData_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
updatedList.add(BukkitTabList.newPlayerInfoData.newInstance(
profile.getId(),
profile,
BukkitTabList.PlayerInfoData_Listed.getBoolean(nmsData),
BukkitTabList.PlayerInfoData_Latency.getInt(nmsData),
BukkitTabList.PlayerInfoData_GameMode.get(nmsData),
displayName,
BukkitTabList.PlayerInfoData_RemoteChatSession.get(nmsData)));
}
}
@Override
public void write(ChannelHandlerContext context, Object packet, ChannelPromise channelPromise) {
try {
if (PacketPlayOutPlayerInfoStorage.CLASS.isInstance(packet) ||
(PacketPlayOutPlayerInfoStorage.ClientboundPlayerInfoRemovePacket != null &&
PacketPlayOutPlayerInfoStorage.ClientboundPlayerInfoRemovePacket.isInstance(packet))) {
super.write(context, TAB.getInstance().getFeatureManager().onPacketPlayOutPlayerInfo(player, packet), channelPromise);
return;
}
if (antiOverrideTeams && PacketPlayOutScoreboardTeamStorage.CLASS.isInstance(packet)) {
long time = System.nanoTime();
modifyPlayers(packet);
TAB.getInstance().getCPUManager().addTime("NameTags", TabConstants.CpuUsageCategory.ANTI_OVERRIDE, System.nanoTime()-time);
super.write(context, packet, channelPromise);
return;
}
if (PacketPlayOutScoreboardDisplayObjectiveStorage.CLASS.isInstance(packet)) {
TAB.getInstance().getFeatureManager().onDisplayObjective(player, packet);
}
if (PacketPlayOutScoreboardObjectiveStorage.CLASS.isInstance(packet)) {
TAB.getInstance().getFeatureManager().onObjective(player, packet);
}
TAB.getInstance().getFeatureManager().onPacketSend(player, packet);
} catch (Exception e) {
TAB.getInstance().getErrorManager().printError("An error occurred when reading packets", e);
}
try {
super.write(context, packet, channelPromise);
} catch (Exception e) {
TAB.getInstance().getErrorManager().printError("Failed to forward packet " + packet.getClass().getSimpleName() + " to " + player.getName(), e);
}
}
/**
* Removes all real players from team if packet does not come from TAB and reports this to override log
*
* @param packetPlayOutScoreboardTeam
* team packet
* @throws ReflectiveOperationException
* nmsGameMode
*/
@SuppressWarnings("unchecked")
private void modifyPlayers(Object packetPlayOutScoreboardTeam) throws ReflectiveOperationException {
int action = PacketPlayOutScoreboardTeamStorage.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);
if (players == null) return;
//creating a new list to prevent NoSuchFieldException in minecraft packet encoder when a player is removed
Collection<String> newList = new ArrayList<>();
for (String entry : players) {
TabPlayer p = getPlayer(entry);
if (p == null) {
newList.add(entry);
continue;
}
Sorting sorting = (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));
} else {
newList.add(entry);
}
}
PacketPlayOutScoreboardTeamStorage.PLAYERS.set(packetPlayOutScoreboardTeam, newList);
}
/**
* Returns player with matching game profile name. This is different from
* real name when a nick plugin changing names of players is used. If no
* player was found, returns {@code null}.
*
* @param name
* Game profile name
* @return Player with matching game profile name
*/
private TabPlayer getPlayer(String name) {
for (TabPlayer p : TAB.getInstance().getOnlinePlayers()) {
if (p.getNickname().equals(name)) return p;
}
return null;
if (nms.is1_19_3Plus()) {
BukkitTabList.PLAYERS.set(packet, updatedList);
}
}
@SuppressWarnings("unchecked")
@Override
@SneakyThrows
public void modifyPlayers(@NotNull Object packetPlayOutScoreboardTeam) {
if (TAB.getInstance().getNameTagManager() == null) return;
int action = PacketScoreboard.TeamPacket_ACTION.getInt(packetPlayOutScoreboardTeam);
if (action == 1 || action == 2 || action == 4) return;
Collection<String> players = (Collection<String>) PacketScoreboard.TeamPacket_PLAYERS.get(packetPlayOutScoreboardTeam);
String teamName = (String) PacketScoreboard.TeamPacket_NAME.get(packetPlayOutScoreboardTeam);
if (players == null) return;
//creating a new list to prevent NoSuchFieldException in minecraft packet encoder when a player is removed
Collection<String> newList = new ArrayList<>();
for (String entry : players) {
TabPlayer p = TAB.getInstance().getPlayer(entry);
if (p == null) {
newList.add(entry);
continue;
}
Sorting sorting = TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.SORTING);
String expectedTeam = sorting.getShortTeamName(p);
if (expectedTeam == null) {
newList.add(entry);
continue;
}
if (!((NameTag)TAB.getInstance().getNameTagManager()).getDisableChecker().isDisabledPlayer(p) &&
!TAB.getInstance().getNameTagManager().hasTeamHandlingPaused(p) && !teamName.equals(expectedTeam)) {
logTeamOverride(teamName, p.getName(), expectedTeam);
} else {
newList.add(entry);
}
}
PacketScoreboard.TeamPacket_PLAYERS.set(packetPlayOutScoreboardTeam, newList);
}
}
@@ -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,250 +0,0 @@
package me.neznamy.tab.platforms.bukkit;
import lombok.Getter;
import lombok.Setter;
import me.clip.placeholderapi.PlaceholderAPI;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.api.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.features.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.Nullable;
import java.util.Collection;
/**
* Implementation of Platform interface for Bukkit platform
*/
public class BukkitPlatform extends BackendPlatform {
@Getter private final BukkitPacketBuilder packetBuilder = new BukkitPacketBuilder();
@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 Plugin viaVersion;
private final boolean protocolSupport = Bukkit.getPluginManager().isPluginEnabled(TabConstants.Plugin.PROTOCOL_SUPPORT);
/**
* Constructs new instance with given plugin parameter
*
* @param plugin
* plugin instance
*/
public BukkitPlatform(JavaPlugin plugin) {
this.plugin = plugin;
if (Bukkit.getPluginManager().isPluginEnabled(TabConstants.Plugin.VIAVERSION)) {
if (ReflectionUtils.classExists("com.viaversion.viaversion.api.Via")) {
viaVersion = Bukkit.getPluginManager().getPlugin(TabConstants.Plugin.VIAVERSION);
} else {
TAB.getInstance().sendConsoleMessage("&cAn outdated version of ViaVersion (" + getPluginVersion(TabConstants.Plugin.VIAVERSION) + ") was detected.", true);
TAB.getInstance().sendConsoleMessage("&cTAB only supports ViaVersion 4.0.0 and above. Disabling ViaVersion hook.", true);
TAB.getInstance().sendConsoleMessage("&cThis might cause problems, such as limitations still being present for latest MC clients as well as RGB not working.", true);
}
}
}
@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 TabExpansion getTabExpansion() {
if (placeholderAPI) {
BukkitTabExpansion expansion = new BukkitTabExpansion();
expansion.register();
return expansion;
}
return null;
}
@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 != null) {
return getProtocolVersionVia(player.getUniqueId(), player.getName(), 0);
}
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,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.api.util.SupplierWithException;
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;
/**
* 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,82 +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");
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 1.5.2 - 1.19.3. 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 1.5.2 - 1.19.3. Disabling."));
}
}
return false;
}
private NMSStorage getNMSLoader() {
List<SupplierWithException<NMSStorage>> loaders = new ArrayList<>();
loaders.add(BukkitLegacyNMSStorage::new);
loaders.add(BukkitModernNMSStorage::new);
loaders.add(MojangModernNMSStorage::new);
loaders.add(ThermosNMSStorage::new);
for (SupplierWithException<NMSStorage> loader : loaders) {
try {
return loader.get();
} 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);
}
}
@@ -0,0 +1,201 @@
package me.neznamy.tab.platforms.bukkit;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.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.*;
/**
* TabList which support modifying many entries at once
* for significantly better performance. For 1.7 players,
* ViaVersion properly splits the packet into multiple, so
* we don't need to worry about that here.
* <p>
* This class does not support server versions of 1.7 and
* below, because of the massive differences in tab list
* and packet fields.
*/
@RequiredArgsConstructor
@SuppressWarnings({"unchecked", "rawtypes"})
public class BukkitTabList implements TabList {
// NMS Fields
public static Class<?> 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;
public static void load(NMSStorage nms) throws NoSuchMethodException {
if (nms.getMinorVersion() >= 17) {
newPacketPlayOutPlayerListHeaderFooter = PacketPlayOutPlayerListHeaderFooterClass.getConstructor(nms.IChatBaseComponent, nms.IChatBaseComponent);
} else {
newPacketPlayOutPlayerListHeaderFooter = PacketPlayOutPlayerListHeaderFooterClass.getConstructor();
HEADER = ReflectionUtils.getFields(PacketPlayOutPlayerListHeaderFooterClass, nms.IChatBaseComponent).get(0);
FOOTER = ReflectionUtils.getFields(PacketPlayOutPlayerListHeaderFooterClass, nms.IChatBaseComponent).get(1);
}
if (nms.is1_19_3Plus()) {
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
@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 updateLatency(@NotNull UUID entry, int latency) {
player.sendPacket(createPacket(Action.UPDATE_LATENCY,
new Entry.Builder(entry).latency(latency).build(), player.getVersion())
);
}
@Override
public void updateGameMode(@NotNull UUID entry, int gameMode) {
player.sendPacket(createPacket(Action.UPDATE_GAME_MODE,
new Entry.Builder(entry).gameMode(gameMode).build(), player.getVersion())
);
}
@Override
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");
}
}
}
@@ -2,235 +2,95 @@ package me.neznamy.tab.platforms.bukkit;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
import com.viaversion.viaversion.api.Via;
import com.viaversion.viaversion.api.legacy.bossbar.BossColor;
import com.viaversion.viaversion.api.legacy.bossbar.BossFlag;
import com.viaversion.viaversion.api.legacy.bossbar.BossStyle;
import io.netty.channel.Channel;
import lombok.NonNull;
import me.neznamy.tab.api.chat.rgb.RGBUtils;
import me.neznamy.tab.api.protocol.PacketPlayOutBoss;
import me.neznamy.tab.api.protocol.PacketPlayOutChat;
import me.neznamy.tab.api.protocol.Skin;
import lombok.Getter;
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.shared.ITabPlayer;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.*;
import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.TAB;
import org.bukkit.Bukkit;
import org.bukkit.boss.BarColor;
import org.bukkit.boss.BarFlag;
import org.bukkit.boss.BarStyle;
import org.bukkit.boss.BossBar;
import me.neznamy.tab.shared.backend.BackendTabPlayer;
import me.neznamy.tab.shared.backend.EntityData;
import me.neznamy.tab.shared.backend.Location;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.metadata.MetadataValue;
import org.bukkit.potion.PotionEffectType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* TabPlayer implementation for Bukkit platform
*/
public class BukkitTabPlayer extends ITabPlayer {
@SuppressWarnings("deprecation")
@Getter
public class BukkitTabPlayer extends BackendTabPlayer {
/** Player's NMS handle (EntityPlayer), preloading for speed */
private Object handle;
private final Object handle;
/** Player's connection for sending packets, preloading for speed */
private Object playerConnection;
/** Bukkit BossBars the player can currently see */
private final Map<UUID, BossBar> bossBars = new HashMap<>();
private final Object playerConnection;
/** ViaVersion BossBars this 1.9+ player can see on 1.8 server */
private final Map<UUID, com.viaversion.viaversion.api.legacy.bossbar.BossBar> viaBossBars = new HashMap<>();
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, true);
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) {
long time = System.nanoTime();
boolean value = getPlayer().hasPermission(permission);
TAB.getInstance().getCPUManager().addMethodTime("hasPermission", System.nanoTime()-time);
return value;
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;
long time = System.nanoTime();
try {
if (nmsPacket instanceof PacketPlayOutBoss) {
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 9) {
handle((PacketPlayOutBoss) nmsPacket);
} else {
handleVia((PacketPlayOutBoss) nmsPacket);
}
} else if (nmsPacket instanceof PacketPlayOutChat) {
getPlayer().sendMessage(((PacketPlayOutChat) nmsPacket).getMessage().toLegacyText());
} else {
NMSStorage.getInstance().sendPacket.invoke(playerConnection, nmsPacket);
}
} catch (ReflectiveOperationException e) {
TAB.getInstance().getErrorManager().printError("An error occurred when sending " + nmsPacket.getClass().getSimpleName(), e);
}
TAB.getInstance().getCPUManager().addMethodTime("sendPacket", System.nanoTime()-time);
NMSStorage.getInstance().sendPacket.invoke(playerConnection, nmsPacket);
}
/**
* Handles PacketPlayOutBoss packet send request using Bukkit API,
* since the API offers everything we need and makes us not need to
* deal with NMS code at all.
*
* @param packet
* packet request to handle using Bukkit API
*/
private void handle(PacketPlayOutBoss packet) {
BossBar bar = bossBars.get(packet.getId());
if (packet.getAction() == PacketPlayOutBoss.Action.ADD) {
if (bossBars.containsKey(packet.getId())) return;
bar = Bukkit.createBossBar(RGBUtils.getInstance().convertToBukkitFormat(packet.getName(), getVersion().getMinorVersion() >= 16 && TAB.getInstance().getServerVersion().getMinorVersion() >= 16),
BarColor.valueOf(packet.getColor().name()),
BarStyle.valueOf(packet.getOverlay().getBukkitName()));
if (packet.isCreateWorldFog()) bar.addFlag(BarFlag.CREATE_FOG);
if (packet.isDarkenScreen()) bar.addFlag(BarFlag.DARKEN_SKY);
if (packet.isPlayMusic()) bar.addFlag(BarFlag.PLAY_BOSS_MUSIC);
bar.setProgress(packet.getPct());
bossBars.put(packet.getId(), bar);
bar.addPlayer(getPlayer());
return;
}
if (bar == null) return; //no idea how
switch (packet.getAction()) {
case REMOVE:
bar.removePlayer(getPlayer());
bossBars.remove(packet.getId());
break;
case UPDATE_PCT:
bar.setProgress(packet.getPct());
break;
case UPDATE_NAME:
bar.setTitle(RGBUtils.getInstance().convertToBukkitFormat(packet.getName(), getVersion().getMinorVersion() >= 16 && TAB.getInstance().getServerVersion().getMinorVersion() >= 16));
break;
case UPDATE_STYLE:
bar.setColor(BarColor.valueOf(packet.getColor().name()));
bar.setStyle(BarStyle.valueOf(packet.getOverlay().getBukkitName()));
break;
case UPDATE_PROPERTIES:
bar = bossBars.get(packet.getId());
processFlag(bar, packet.isCreateWorldFog(), BarFlag.CREATE_FOG);
processFlag(bar, packet.isDarkenScreen(), BarFlag.DARKEN_SKY);
processFlag(bar, packet.isPlayMusic(), BarFlag.PLAY_BOSS_MUSIC);
break;
default:
break;
}
}
/**
* Handles PacketPlayOutBoss packet request for 1.9+ clients on
* 1.8 servers using ViaVersion API instead of using Wither.
*
* @param packet
* packet request to handle using ViaVersion API
*/
private void handleVia(PacketPlayOutBoss packet) {
com.viaversion.viaversion.api.legacy.bossbar.BossBar bar;
switch (packet.getAction()) {
case ADD:
if (viaBossBars.containsKey(packet.getId())) return;
bar = Via.getAPI().legacyAPI().createLegacyBossBar(RGBUtils.getInstance().convertToBukkitFormat(packet.getName(), getVersion().getMinorVersion() >= 16),
packet.getPct(),
BossColor.valueOf(packet.getColor().name()),
BossStyle.valueOf(packet.getOverlay().getBukkitName()));
//fog missing from via API
if (packet.isDarkenScreen()) bar.addFlag(BossFlag.DARKEN_SKY);
if (packet.isPlayMusic()) bar.addFlag(BossFlag.PLAY_BOSS_MUSIC);
viaBossBars.put(packet.getId(), bar);
bar.addPlayer(getPlayer().getUniqueId());
break;
case REMOVE:
viaBossBars.get(packet.getId()).removePlayer(getPlayer().getUniqueId());
viaBossBars.remove(packet.getId());
break;
case UPDATE_PCT:
viaBossBars.get(packet.getId()).setHealth(packet.getPct());
break;
case UPDATE_NAME:
viaBossBars.get(packet.getId()).setTitle(RGBUtils.getInstance().convertToBukkitFormat(packet.getName(), getVersion().getMinorVersion() >= 16));
break;
case UPDATE_STYLE:
viaBossBars.get(packet.getId()).setColor(BossColor.valueOf(packet.getColor().name()));
viaBossBars.get(packet.getId()).setStyle(BossStyle.valueOf(packet.getOverlay().getBukkitName()));
break;
case UPDATE_PROPERTIES:
bar = viaBossBars.get(packet.getId());
//fog missing from via API
processFlagVia(bar, packet.isDarkenScreen(), BossFlag.DARKEN_SKY);
processFlagVia(bar, packet.isPlayMusic(), BossFlag.PLAY_BOSS_MUSIC);
break;
default:
break;
}
}
/**
* Sets BossBar flag to requested target value.
*
* @param bar
* BossBar to set flag of
* @param targetValue
* Target value of the flag
* @param flag
* Flag to set value of
*/
private void processFlag(BossBar bar, boolean targetValue, BarFlag flag) {
if (targetValue && !bar.hasFlag(flag)) bar.addFlag(flag);
if (!targetValue && bar.hasFlag(flag)) bar.removeFlag(flag);
}
/**
* Sets BossBar flag to requested target value.
*
* @param bar
* BossBar to set flag of
* @param targetValue
* Target value of the flag
* @param flag
* Flag to set value of
*/
private void processFlagVia(com.viaversion.viaversion.api.legacy.bossbar.BossBar bar, boolean targetValue, BossFlag flag) {
if (targetValue && !bar.hasFlag(flag)) bar.addFlag(flag);
if (!targetValue && bar.hasFlag(flag)) bar.removeFlag(flag);
@Override
public void sendMessage(@NotNull IChatBaseComponent message) {
getPlayer().sendMessage(RGBUtils.getInstance().convertToBukkitFormat(message.toFlatText(),
getVersion().getMinorVersion() >= 16 && TAB.getInstance().getServerVersion().getMinorVersion() >= 16));
}
@Override
@@ -252,20 +112,16 @@ public class BukkitTabPlayer extends ITabPlayer {
}
@Override
public Skin getSkin() {
try {
Collection<Property> col = ((GameProfile)NMSStorage.getInstance().getProfile.invoke(handle)).getProperties().get("textures");
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;
}
@@ -279,20 +135,45 @@ public class BukkitTabPlayer extends ITabPlayer {
return getPlayer().getMetadata("vanished").stream().anyMatch(MetadataValue::asBoolean);
}
@SuppressWarnings("deprecation")
@Override
public int getGamemode() {
return getPlayer().getGameMode().getValue();
}
@Override
public Channel getChannel() {
try {
if (NMSStorage.getInstance().CHANNEL != null)
return (Channel) NMSStorage.getInstance().CHANNEL.get(NMSStorage.getInstance().NETWORK_MANAGER.get(playerConnection));
} catch (IllegalAccessException e) {
TAB.getInstance().getErrorManager().printError("Failed to get channel of " + getName(), 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
@SneakyThrows
public void updateEntityMetadata(int entityId, @NotNull EntityData data) {
if (PacketPlayOutEntityMetadataStorage.CONSTRUCTOR.getParameterCount() == 2) {
//1.19.3+
sendPacket(PacketPlayOutEntityMetadataStorage.CONSTRUCTOR.newInstance(entityId, DataWatcher.packDirty.invoke(data.build())));
} else {
sendPacket(PacketPlayOutEntityMetadataStorage.CONSTRUCTOR.newInstance(entityId, data.build(), true));
}
}
@Override
public void teleportEntity(int entityId, @NotNull Location location) {
sendPacket(PacketPlayOutEntityTeleportStorage.build(entityId, location));
}
@Override
@SneakyThrows
public void destroyEntities(int... entities) {
if (PacketPlayOutEntityDestroyStorage.CONSTRUCTOR.getParameterTypes()[0] != int.class) {
sendPacket(PacketPlayOutEntityDestroyStorage.CONSTRUCTOR.newInstance(new Object[]{entities}));
} else {
//1.17.0 Mojank
for (int entity : entities) {
sendPacket(PacketPlayOutEntityDestroyStorage.CONSTRUCTOR.newInstance(entity));
}
}
return null;
}
}
@@ -0,0 +1,68 @@
package me.neznamy.tab.platforms.bukkit.bossbar;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.api.bossbar.BarColor;
import me.neznamy.tab.api.bossbar.BarStyle;
import me.neznamy.tab.shared.platform.bossbar.BossBar;
import me.neznamy.tab.shared.chat.rgb.RGBUtils;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.TAB;
import org.bukkit.Bukkit;
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
* to allow 1.8 players, it will handle the entity and teleporting by itself.
*/
@RequiredArgsConstructor
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, org.bukkit.boss.BossBar> bossBars = new HashMap<>();
@Override
public void create(@NotNull UUID id, @NotNull String title, float progress, @NotNull BarColor color, @NotNull BarStyle style) {
if (bossBars.containsKey(id)) return;
org.bukkit.boss.BossBar bar = Bukkit.createBossBar(
RGBUtils.getInstance().convertToBukkitFormat(title,
player.getVersion().getMinorVersion() >= 16 && TAB.getInstance().getServerVersion().getMinorVersion() >= 16),
org.bukkit.boss.BarColor.valueOf(color.name()),
org.bukkit.boss.BarStyle.valueOf(style.getBukkitName()));
bar.setProgress(progress);
bar.addPlayer(player.getPlayer());
bossBars.put(id, bar);
}
@Override
public void update(@NotNull UUID id, @NotNull String title) {
bossBars.get(id).setTitle(RGBUtils.getInstance().convertToBukkitFormat(title,
player.getVersion().getMinorVersion() >= 16 && TAB.getInstance().getServerVersion().getMinorVersion() >= 16));
}
@Override
public void update(@NotNull UUID id, float progress) {
bossBars.get(id).setProgress(progress);
}
@Override
public void update(@NotNull UUID id, @NotNull BarStyle style) {
bossBars.get(id).setStyle(org.bukkit.boss.BarStyle.valueOf(style.getBukkitName()));
}
@Override
public void update(@NotNull UUID id, @NotNull BarColor color) {
bossBars.get(id).setColor(org.bukkit.boss.BarColor.valueOf(color.name()));
}
@Override
public void remove(@NotNull UUID id) {
bossBars.remove(id).removePlayer(player.getPlayer());
}
}
@@ -0,0 +1,64 @@
package me.neznamy.tab.platforms.bukkit.bossbar;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.shared.platform.bossbar.BossBar;
import me.neznamy.tab.api.bossbar.BarColor;
import me.neznamy.tab.api.bossbar.BarStyle;
import me.neznamy.tab.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;
/**
* BossBar using wither entity for <1.9 players on <1.9 servers.
* Additional logic, such as teleporting the entity must be done
* separately, as this class does not handle it.
*/
@RequiredArgsConstructor
public class EntityBossBar implements BossBar {
/** Player this handler belongs to */
private final BukkitTabPlayer player;
@Override
public void create(@NotNull UUID id, @NotNull String title, float progress, @NotNull BarColor color, @NotNull BarStyle style) {
DataWatcher w = new DataWatcher();
float health = 300*progress;
if (health == 0) health = 1;
w.getHelper().setHealth(health);
w.getHelper().setCustomName(title, player.getVersion());
w.getHelper().setEntityFlags((byte) 32);
w.getHelper().setWitherInvulnerableTime(880); // Magic number
player.spawnEntity(id.hashCode(), new UUID(0, 0), EntityType.WITHER, new Location(0, 0, 0, 0, 0), w);
}
@Override
public void update(@NotNull UUID id, @NotNull String title) {
DataWatcher w = new DataWatcher();
w.getHelper().setCustomName(title, player.getVersion());
player.updateEntityMetadata(id.hashCode(), w);
}
@Override
public void update(@NotNull UUID id, float progress) {
DataWatcher w = new DataWatcher();
float health = 300*progress;
if (health == 0) health = 1;
w.getHelper().setHealth(health);
player.updateEntityMetadata(id.hashCode(), w);
}
@Override
public void update(@NotNull UUID id, @NotNull BarStyle style) {/*Added in 1.9*/}
@Override
public void update(@NotNull UUID id, @NotNull BarColor color) {/*Added in 1.9*/}
@Override
public void remove(@NotNull UUID id) {
player.destroyEntities(id.hashCode());
}
}
@@ -0,0 +1,67 @@
package me.neznamy.tab.platforms.bukkit.bossbar;
import com.viaversion.viaversion.api.Via;
import com.viaversion.viaversion.api.legacy.bossbar.BossColor;
import com.viaversion.viaversion.api.legacy.bossbar.BossStyle;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.shared.platform.bossbar.BossBar;
import me.neznamy.tab.api.bossbar.BarColor;
import me.neznamy.tab.api.bossbar.BarStyle;
import me.neznamy.tab.shared.chat.rgb.RGBUtils;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* Handler for 1.9+ players on 1.8 server using ViaVersion API.
*/
@RequiredArgsConstructor
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, com.viaversion.viaversion.api.legacy.bossbar.BossBar> viaBossBars = new HashMap<>();
@Override
public void create(@NotNull UUID id, @NotNull String title, float progress, @NotNull BarColor color, @NotNull BarStyle style) {
if (viaBossBars.containsKey(id)) return;
com.viaversion.viaversion.api.legacy.bossbar.BossBar bar = Via.getAPI().legacyAPI().createLegacyBossBar(
RGBUtils.getInstance().convertToBukkitFormat(title, player.getVersion().getMinorVersion() >= 16),
progress,
BossColor.valueOf(color.name()),
BossStyle.valueOf(style.getBukkitName()));
viaBossBars.put(id, bar);
bar.addPlayer(player.getPlayer().getUniqueId());
}
@Override
public void update(@NotNull UUID id, @NotNull String title) {
viaBossBars.get(id).setTitle(RGBUtils.getInstance().convertToBukkitFormat(title,
player.getVersion().getMinorVersion() >= 16));
}
@Override
public void update(@NotNull UUID id, float progress) {
viaBossBars.get(id).setHealth(progress);
}
@Override
public void update(@NotNull UUID id, @NotNull BarStyle style) {
viaBossBars.get(id).setStyle(BossStyle.valueOf(style.getBukkitName()));
}
@Override
public void update(@NotNull UUID id, @NotNull BarColor color) {
viaBossBars.get(id).setColor(BossColor.valueOf(color.name()));
}
@Override
public void remove(@NotNull UUID id) {
viaBossBars.remove(id).removePlayer(player.getPlayer().getUniqueId());
}
}
@@ -0,0 +1,199 @@
package me.neznamy.tab.platforms.bukkit.features;
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;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutEntityTeleportStorage;
import me.neznamy.tab.shared.backend.BackendTabPlayer;
import me.neznamy.tab.shared.backend.EntityData;
import me.neznamy.tab.shared.backend.features.unlimitedtags.BackendNameTagX;
import org.bukkit.Bukkit;
import org.bukkit.Location;
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;
import java.util.stream.Collectors;
/**
* The core class for unlimited NameTag mode on Bukkit
*/
public class BukkitNameTagX extends BackendNameTagX implements Listener, PacketSendListener {
/** Reference to NMS storage for quick access */
private final NMSStorage nms = NMSStorage.getInstance();
public BukkitNameTagX(@NotNull JavaPlugin plugin) {
Bukkit.getPluginManager().registerEvents(this, 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()));
}
@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
@SneakyThrows
public void onPacketSend(@NotNull TabPlayer receiver, @NotNull Object packet) {
if (receiver.getVersion().getMinorVersion() < 8) return;
if (!receiver.isLoaded() || getDisableChecker().isDisabledPlayer(receiver) || getUnlimitedDisableChecker().isDisabledPlayer(receiver)) return;
if (nms.PacketPlayOutEntity.isInstance(packet) && !nms.PacketPlayOutEntityLook.isInstance(packet)) { //ignoring head rotation only packets
packetListener.onEntityMove((BackendTabPlayer) receiver, nms.PacketPlayOutEntity_ENTITYID.getInt(packet));
} else if (PacketPlayOutEntityTeleportStorage.CLASS.isInstance(packet)) {
packetListener.onEntityMove((BackendTabPlayer) receiver, PacketPlayOutEntityTeleportStorage.ENTITY_ID.getInt(packet));
} else if (nms.PacketPlayOutNamedEntitySpawn.isInstance(packet)) {
packetListener.onEntitySpawn((BackendTabPlayer) receiver, nms.PacketPlayOutNamedEntitySpawn_ENTITYID.getInt(packet));
} else if (PacketPlayOutEntityDestroyStorage.CLASS.isInstance(packet)) {
if (nms.getMinorVersion() >= 17) {
Object entities = PacketPlayOutEntityDestroyStorage.ENTITIES.get(packet);
if (entities instanceof List) {
packetListener.onEntityDestroy((BackendTabPlayer) receiver, (List<Integer>) entities);
} else {
//1.17.0
packetListener.onEntityDestroy((BackendTabPlayer) receiver, (int) entities);
}
} else {
packetListener.onEntityDestroy((BackendTabPlayer) receiver, (int[]) PacketPlayOutEntityDestroyStorage.ENTITIES.get(packet));
}
}
}
@Override
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(@NotNull TabPlayer player1, @NotNull TabPlayer player2) {
return ((Player) player1.getPlayer()).getWorld() == ((Player) player2.getPlayer()).getWorld();
}
@Override
public boolean canSee(@NotNull TabPlayer viewer, @NotNull TabPlayer target) {
return ((Player)viewer.getPlayer()).canSee((Player) target.getPlayer());
}
@Override
public void unregisterListener() {
HandlerList.unregisterAll(this);
}
@SuppressWarnings("deprecation")
@Override
public @NotNull List<Integer> getPassengers(@NotNull Object entity) {
Entity vehicle = (Entity) entity;
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 11) {
return vehicle.getPassengers().stream().map(Entity::getEntityId).collect(Collectors.toList());
} else {
if (vehicle.getPassenger() != null) {
return Collections.singletonList(vehicle.getPassenger().getEntityId());
} else {
return Collections.emptyList();
}
}
}
@Override
public @Nullable Object getVehicle(@NotNull TabPlayer player) {
return ((Player)player.getPlayer()).getVehicle();
}
@Override
public int getEntityId(@NotNull Object entity) {
return ((Entity)entity).getEntityId();
}
@Override
public @NotNull String getEntityType(@NotNull Object entity) {
return ((Entity) entity).getType().toString().toLowerCase();
}
@Override
public boolean isSneaking(@NotNull TabPlayer player) {
return ((Player)player.getPlayer()).isSneaking();
}
@Override
public boolean isSwimming(@NotNull TabPlayer player) {
Player p = (Player) player.getPlayer();
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 14 && p.getPose() == Pose.SWIMMING) return true;
return TAB.getInstance().getServerVersion().getMinorVersion() == 13 && p.isSwimming();
}
@Override
public boolean isGliding(@NotNull TabPlayer player) {
return TAB.getInstance().getServerVersion().getMinorVersion() >= 9 && ((Player)player.getPlayer()).isGliding();
}
@Override
public boolean isSleeping(@NotNull TabPlayer player) {
return ((Player)player.getPlayer()).isSleeping();
}
@Override
public @NotNull Object getArmorStandType() {
return EntityType.ARMOR_STAND;
}
@Override
public double getX(@NotNull TabPlayer player) {
return ((Player)player.getPlayer()).getLocation().getX();
}
@Override
public double getY(@NotNull Object entity) {
return ((Entity)entity).getLocation().getY();
}
@Override
public double getZ(@NotNull TabPlayer player) {
return ((Player)player.getPlayer()).getLocation().getZ();
}
@Override
public EntityData createDataWatcher(@NotNull TabPlayer viewer, byte flags, @NotNull String displayName, boolean nameVisible) {
DataWatcher datawatcher = new DataWatcher();
datawatcher.getHelper().setEntityFlags(flags);
datawatcher.getHelper().setCustomName(displayName, viewer.getVersion());
datawatcher.getHelper().setCustomNameVisible(nameVisible);
datawatcher.getHelper().setArmorStandFlags((byte)16);
return datawatcher;
}
@Override
public void runInEntityScheduler(Object entity, Runnable task) {
((BukkitPlatform)TAB.getInstance().getPlatform()).runEntityTask((Entity) entity, task);
}
}
@@ -3,14 +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.TabFeature;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.features.TabExpansion;
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;
@@ -19,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 Map<TabPlayer, Map<String, String>> values = new WeakHashMap<>();
private final WeakHashMap<Player, Map<String, String>> values = new WeakHashMap<>();
@Getter private final String author = "NEZNAMY";
@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() {
@@ -34,30 +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) + "%", (TabFeature) TabAPI.getInstance().getPlaceholderManager());
TAB.getInstance().getPlaceholderManager().addUsedPlaceholder("%" + identifier.substring(12) + "%", TAB.getInstance().getPlaceholderManager());
}
if (player == null) return "<Player cannot be null>";
TabPlayer p = TabAPI.getInstance().getPlayer(player.getUniqueId());
if (p == null || !p.isLoaded()) return "<Player is not loaded>";
return values.get(p).get(identifier);
return values.get(player).get(identifier);
}
@Override
public void setValue(TabPlayer player, String key, String value) {
values.computeIfAbsent(player, p -> new HashMap<>()).put(key, value);
public void setValue(@NotNull TabPlayer player, @NotNull String key, @NotNull String value) {
values.computeIfAbsent((Player) player.getPlayer(), p -> new HashMap<>()).put(key, value);
}
}
@@ -5,7 +5,10 @@ import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import me.neznamy.tab.api.TabAPI;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
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;
@@ -15,36 +18,32 @@ import org.bukkit.event.player.PlayerChangedWorldEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.plugin.java.JavaPlugin;
import me.neznamy.tab.api.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
*/
@SuppressWarnings("deprecation")
public class PerWorldPlayerList extends TabFeature implements Listener {
@RequiredArgsConstructor
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");
/**
* Constructs new instance with given parameters and loads config options
*
* @param plugin
* plugin instance
*/
public PerWorldPlayerList(JavaPlugin plugin) {
super("Per world PlayerList", null);
Bukkit.getPluginManager().registerEvents(this, plugin);
TabAPI.getInstance().debug(String.format("Loaded PerWorldPlayerList feature with parameters allowBypass=%s, ignoredWorlds=%s, sharedWorlds=%s", allowBypass, ignoredWorlds, sharedWorlds));
}
/** Plugin reference*/
@NotNull private final JavaPlugin plugin;
@Getter private final String featureName = "Per world PlayerList";
@Override
public void load() {
Bukkit.getOnlinePlayers().forEach(this::checkPlayer);
Bukkit.getPluginManager().registerEvents(this, plugin);
}
@Override
@@ -61,14 +60,14 @@ public class PerWorldPlayerList extends TabFeature implements Listener {
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);
}
/**
@@ -79,7 +78,7 @@ public class PerWorldPlayerList extends TabFeature implements Listener {
* @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);
@@ -97,7 +96,7 @@ public class PerWorldPlayerList extends TabFeature implements Listener {
* 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,161 +0,0 @@
package me.neznamy.tab.platforms.bukkit.features;
import me.neznamy.tab.api.TabAPI;
import me.neznamy.tab.api.TabFeature;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityMetadata;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutSpawnEntityLiving;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherObject;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherItem;
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 {
/** 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;
/**
* Constructs new instance with given parameter
*/
public PetFix() {
super("Pet name fix", null);
TabAPI.getInstance().debug("Loaded PetFix feature");
}
/**
* 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 (PacketPlayOutEntityMetadata.CLASS.isInstance(packet)) {
Object removedEntry = null;
List<Object> items = (List<Object>) PacketPlayOutEntityMetadata.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;
}
}
}
} 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);
}
if (removedEntry != null) items.remove(removedEntry);
} else if (PacketPlayOutSpawnEntityLiving.CLASS.isInstance(packet) && PacketPlayOutSpawnEntityLiving.DATA_WATCHER != null) {
//<1.15
DataWatcher watcher = DataWatcher.fromNMS(PacketPlayOutSpawnEntityLiving.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);
PacketPlayOutSpawnEntityLiving.DATA_WATCHER.set(packet, watcher.build());
}
}
}
}
@@ -1,5 +1,13 @@
package me.neznamy.tab.platforms.bukkit.features;
import lombok.RequiredArgsConstructor;
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;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.entity.Player;
@@ -8,53 +16,42 @@ import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerRespawnEvent;
import org.bukkit.plugin.java.JavaPlugin;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.api.bossbar.BossBar;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityTeleport;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.features.bossbar.BossBarManagerImpl;
import org.jetbrains.annotations.NotNull;
/**
* An additional class with additional code for &lt;1.9 servers due to an entity being required
*/
public class WitherBossBar extends BossBarManagerImpl implements Listener {
@RequiredArgsConstructor
public class WitherBossBar extends BossBarManagerImpl implements Listener, WorldSwitchListener {
/** Distance of the Wither in blocks */
private static final int WITHER_DISTANCE = 60;
/**
* Constructs a new instance of the class and registers events and task
*
* @param plugin
* plugin instance
*/
public WitherBossBar(JavaPlugin plugin) {
/** Reference to plugin for registering listener */
private final @NotNull JavaPlugin plugin;
@Override
public void load() {
Bukkit.getPluginManager().registerEvents(this, plugin);
//when MC is on fullscreen, BossBar disappears after 1 second of not being seen
//when in a small window, it's about 100ms
TAB.getInstance().getCPUManager().startRepeatingMeasuredTask(100,
this, TabConstants.CpuUsageCategory.TELEPORTING_WITHER, this::teleport);
}
@Override
public void load() {
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);
p.sendCustomPacket(new PacketPlayOutEntityTeleport(line.getUniqueId().hashCode(), loc), TabConstants.PacketCategory.BOSSBAR_WITHER_TELEPORT);
((BackendTabPlayer)p).teleportEntity(line.getUniqueId().hashCode(), new me.neznamy.tab.shared.backend.Location(loc.getX(), loc.getY(), loc.getZ(), 0, 0));
}
}
}
@@ -70,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,155 +0,0 @@
package me.neznamy.tab.platforms.bukkit.features.unlimitedtags;
import me.neznamy.tab.api.TabAPI;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.api.protocol.TabPacket;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityDestroy;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityMetadata;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityTeleport;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutSpawnEntityLiving;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
import me.neznamy.tab.shared.backend.features.unlimitedtags.BackendArmorStand;
import me.neznamy.tab.shared.backend.features.unlimitedtags.BackendArmorStandManager;
import me.neznamy.tab.shared.backend.features.unlimitedtags.BackendNameTagX;
import org.bukkit.Location;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.entity.Pose;
/**
* A class representing an armor stand attached to a player
*/
public class BukkitArmorStand extends BackendArmorStand {
/** Owner as a Bukkit player */
private final Player player;
/** Entity destroy packet */
protected final TabPacket destroyPacket = new PacketPlayOutEntityDestroy(entityId);
/**
* Constructs new instance with given parameters.
*
* @param asm
* Armor stand manager which this armor stand belongs to
* @param owner
* Owner of the armor stand
* @param propertyName
* Name of refresh property to use
* @param yOffset
* Offset in blocks
* @param staticOffset
* {@code true} if offset is static, {@code false} if not
*/
public BukkitArmorStand(BackendNameTagX feature, BackendArmorStandManager asm, TabPlayer owner, String propertyName, double yOffset, boolean staticOffset) {
super(feature, asm, owner, propertyName, yOffset, staticOffset);
player = (Player) owner.getPlayer();
sneaking = player.isSneaking();
}
@Override
public void spawn(TabPlayer viewer) {
for (TabPacket packet : getSpawnPackets(viewer)) {
viewer.sendCustomPacket(packet, TabConstants.PacketCategory.UNLIMITED_NAMETAGS_SPAWN);
}
}
@Override
public void destroy(TabPlayer viewer) {
viewer.sendCustomPacket(destroyPacket, TabConstants.PacketCategory.UNLIMITED_NAMETAGS_DESPAWN);
}
/**
* Returns location where armor stand should be at time of calling.
* This takes into account everything that affects height, including
* viewer's game version.
*
* @param viewer
* Player looking at the armor stand
* @return Location where armor stand should be for specified viewer
*/
public Location getLocation(TabPlayer viewer) {
Location loc = player.getLocation();
double y = loc.getY();
//1.14+ server sided bug
Entity vehicle = player.getVehicle();
if (vehicle != null) {
if (vehicle.getType().toString().contains("horse")) { //covering all 3 horse types
y = vehicle.getLocation().getY() + 0.85;
}
if (vehicle.getType().toString().equals("donkey")) { //1.11+
y = vehicle.getLocation().getY() + 0.525;
}
if (vehicle.getType() == EntityType.PIG) {
y = vehicle.getLocation().getY() + 0.325;
}
if (vehicle.getType().toString().equals("strider")) { //1.16+
y = vehicle.getLocation().getY() + 1.15;
}
} else {
//1.13+ swimming or 1.9+ flying with elytra
if (isSwimming() || (TabAPI.getInstance().getServerVersion().getMinorVersion() >= 9 && player.isGliding())) {
y = loc.getY()-1.22;
}
}
y += getYAdd(player.isSleeping(), sneaking, viewer);
return new Location(null, loc.getX(), y, loc.getZ());
}
@Override
public void updateMetadata(TabPlayer viewer) {
viewer.sendCustomPacket(new PacketPlayOutEntityMetadata(entityId, createDataWatcher(property.getFormat(viewer), viewer)), TabConstants.PacketCategory.UNLIMITED_NAMETAGS_METADATA);
}
@Override
public void sendTeleportPacket(TabPlayer viewer) {
viewer.sendCustomPacket(new PacketPlayOutEntityTeleport(entityId, getLocation(viewer)),
TabConstants.PacketCategory.UNLIMITED_NAMETAGS_TELEPORT);
}
/**
* Returns {@code true} if owner is swimming, {@code false} if not
* @return {@code true} if owner is swimming, {@code false} if not
*/
private boolean isSwimming() {
if (TabAPI.getInstance().getServerVersion().getMinorVersion() >= 14 && player.getPose() == Pose.SWIMMING) return true;
return TabAPI.getInstance().getServerVersion().getMinorVersion() == 13 && player.isSwimming();
}
private TabPacket[] getSpawnPackets(TabPlayer viewer) {
visible = calculateVisibility();
DataWatcher dataWatcher = createDataWatcher(property.getFormat(viewer), viewer);
if (TabAPI.getInstance().getServerVersion().getMinorVersion() >= 15) {
return new TabPacket[] {
new PacketPlayOutSpawnEntityLiving(entityId, uuid, EntityType.ARMOR_STAND, getLocation(viewer), null),
new PacketPlayOutEntityMetadata(entityId, dataWatcher)
};
} else {
return new TabPacket[] {
new PacketPlayOutSpawnEntityLiving(entityId, uuid, EntityType.ARMOR_STAND, getLocation(viewer), dataWatcher),
};
}
}
/**
* Creates data watcher with specified display name for viewer
*
* @param displayName
* armor stand name
* @param viewer
* player to apply checks against
* @return DataWatcher for viewer
*/
public DataWatcher createDataWatcher(String displayName, TabPlayer viewer) {
DataWatcher datawatcher = new DataWatcher();
datawatcher.getHelper().setEntityFlags((byte) (sneaking ? 34 : 32));
datawatcher.getHelper().setCustomName(displayName, viewer.getVersion());
datawatcher.getHelper().setCustomNameVisible(!shouldBeInvisibleFor(viewer, displayName) && visible);
if (viewer.getVersion().getMinorVersion() > 8 || manager.isMarkerFor18x())
datawatcher.getHelper().setArmorStandFlags((byte)16);
return datawatcher;
}
}
@@ -1,154 +0,0 @@
package me.neznamy.tab.platforms.bukkit.features.unlimitedtags;
import me.neznamy.tab.api.TabAPI;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityDestroy;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityTeleport;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import me.neznamy.tab.shared.backend.features.unlimitedtags.BackendArmorStand;
import me.neznamy.tab.shared.backend.features.unlimitedtags.BackendArmorStandManager;
import me.neznamy.tab.shared.backend.features.unlimitedtags.BackendNameTagX;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.event.HandlerList;
import org.bukkit.plugin.java.JavaPlugin;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
/**
* The core class for unlimited NameTag mode on Bukkit
*/
public class BukkitNameTagX extends BackendNameTagX {
/** Reference to NMS storage for quick access */
private final NMSStorage nms = NMSStorage.getInstance();
/** Bukkit event listener */
private final EventListener eventListener = new EventListener(this);
/**
* Constructs new instance with given parameter, loads config options, registers events
* and registers sub-features.
*
* @param plugin
* plugin instance
*/
public BukkitNameTagX(JavaPlugin plugin) {
Bukkit.getPluginManager().registerEvents(eventListener, plugin);
}
@Override
public boolean onPacketReceive(TabPlayer sender, Object packet) throws ReflectiveOperationException {
if (sender.getVersion().getMinorVersion() == 8 && nms.PacketPlayInUseEntity.isInstance(packet)) {
int entityId = nms.PacketPlayInUseEntity_ENTITY.getInt(packet);
TabPlayer attacked = null;
for (TabPlayer all : TabAPI.getInstance().getOnlinePlayers()) {
if (all.isLoaded() && getArmorStandManager(all).hasArmorStandWithID(entityId)) {
attacked = all;
break;
}
}
if (attacked != null && attacked != sender) {
nms.PacketPlayInUseEntity_ENTITY.set(packet, getEntityId(attacked));
}
}
return false;
}
@SuppressWarnings("unchecked")
@Override
public void onPacketSend(TabPlayer receiver, Object packet) throws ReflectiveOperationException {
if (receiver.getVersion().getMinorVersion() < 8) return;
if (!receiver.isLoaded() || isDisabledPlayer(receiver) || getDisabledUnlimitedPlayers().contains(receiver)) return;
if (nms.PacketPlayOutEntity.isInstance(packet) && !nms.PacketPlayOutEntityLook.isInstance(packet)) { //ignoring head rotation only packets
packetListener.onEntityMove(receiver, nms.PacketPlayOutEntity_ENTITYID.getInt(packet));
} else if (PacketPlayOutEntityTeleport.CLASS.isInstance(packet)) {
packetListener.onEntityMove(receiver, PacketPlayOutEntityTeleport.ENTITY_ID.getInt(packet));
} else if (nms.PacketPlayOutNamedEntitySpawn.isInstance(packet)) {
packetListener.onEntitySpawn(receiver, nms.PacketPlayOutNamedEntitySpawn_ENTITYID.getInt(packet));
} else if (PacketPlayOutEntityDestroy.CLASS.isInstance(packet)) {
if (nms.getMinorVersion() >= 17) {
Object entities = PacketPlayOutEntityDestroy.ENTITIES.get(packet);
if (entities instanceof List) {
packetListener.onEntityDestroy(receiver, (List<Integer>) entities);
} else {
//1.17.0
packetListener.onEntityDestroy(receiver, (int) entities);
}
} else {
packetListener.onEntityDestroy(receiver, (int[]) PacketPlayOutEntityDestroy.ENTITIES.get(packet));
}
}
}
@Override
public double getDistance(TabPlayer player1, 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();
}
@Override
public boolean canSee(TabPlayer viewer, TabPlayer target) {
return ((Player)viewer.getPlayer()).canSee((Player)target.getPlayer());
}
@Override
public void unregisterListener() {
HandlerList.unregisterAll(eventListener);
}
@SuppressWarnings("deprecation")
@Override
public List<Integer> getPassengers(Object entity) {
Entity vehicle = (Entity) entity;
if (TabAPI.getInstance().getServerVersion().getMinorVersion() >= 11) {
return vehicle.getPassengers().stream().map(Entity::getEntityId).collect(Collectors.toList());
} else {
if (vehicle.getPassenger() != null) {
return Collections.singletonList(vehicle.getPassenger().getEntityId());
} else {
return Collections.emptyList();
}
}
}
@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) {
return ((Player)player.getPlayer()).getVehicle();
}
@Override
public int getEntityId(Object entity) {
return ((Entity)entity).getEntityId();
}
@Override
public String getEntityType(Object entity) {
return ((Entity) entity).getType().toString().toLowerCase();
}
@Override
public BackendArmorStand createArmorStand(BackendArmorStandManager feature, TabPlayer owner, String lineName, double yOffset, boolean staticOffset) {
return new BukkitArmorStand(this, feature, owner, lineName, yOffset, staticOffset);
}
}
@@ -1,50 +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 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;
TabAPI.getInstance().getThreadManager().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) {
TabAPI.getInstance().getThreadManager().runMeasuredTask(feature, TabConstants.CpuUsageCategory.PLAYER_RESPAWN, () -> {
TabPlayer respawned = TabAPI.getInstance().getPlayer(e.getPlayer().getUniqueId());
if (feature.isPlayerDisabled(respawned)) return;
feature.getArmorStandManager(respawned).teleport();
});
}
}
@@ -1,67 +0,0 @@
package me.neznamy.tab.platforms.bukkit.nms;
import lombok.Data;
import me.neznamy.tab.api.protocol.TabPacket;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
/**
* Custom class for holding data used in PacketPlayOutEntityDestroy minecraft packet.
*/
@Data
public class PacketPlayOutEntityDestroy implements TabPacket {
/** NMS Fields */
public static Class<?> CLASS;
public static Constructor<?> CONSTRUCTOR;
public static Field ENTITIES;
/** Packet's instance fields */
private final int[] entities;
/**
* 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);
try {
CONSTRUCTOR = CLASS.getConstructor(int[].class);
} catch (NoSuchMethodException e) {
//1.17.0
CONSTRUCTOR = CLASS.getConstructor(int.class);
}
}
/**
* Constructs new instance with given parameter
*
* @param entities
* Destroyed entities
*/
public PacketPlayOutEntityDestroy(int... entities) {
this.entities = entities;
}
/**
* Converts this class into NMS packet
*
* @return NMS packet
* @throws ReflectiveOperationException
* If something went wrong
*/
public Object build() throws ReflectiveOperationException {
if (CONSTRUCTOR.getParameterTypes()[0] != int.class) {
return CONSTRUCTOR.newInstance(new Object[]{entities});
} else {
//1.17.0
return CONSTRUCTOR.newInstance(entities[0]);
}
}
}
@@ -1,61 +0,0 @@
package me.neznamy.tab.platforms.bukkit.nms;
import lombok.AllArgsConstructor;
import lombok.NonNull;
import lombok.ToString;
import me.neznamy.tab.api.protocol.TabPacket;
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;
/**
* Custom class for holding data used in PacketPlayOutEntityMetadata minecraft packet.
*/
@AllArgsConstructor @ToString
public class PacketPlayOutEntityMetadata implements TabPacket {
/** NMS Fields */
public static Class<?> CLASS;
public static Constructor<?> CONSTRUCTOR;
public static Field LIST;
/** Packet's instance fields */
private final int entityId;
@NonNull private final DataWatcher dataWatcher;
/**
* Loads all required Fields and throws Exception if something went wrong
*
* @param nms
* NMS storage reference
* @throws NoSuchMethodException
* If something fails
*/
public static void load(NMSStorage nms) throws NoSuchMethodException {
if (nms.is1_19_3Plus()) {
CONSTRUCTOR = CLASS.getConstructor(int.class, List.class);
} else {
CONSTRUCTOR = CLASS.getConstructor(int.class, DataWatcher.CLASS, boolean.class);
}
LIST = nms.getFields(CLASS, List.class).get(0);
}
/**
* Converts this class into NMS packet
*
* @return NMS packet
* @throws ReflectiveOperationException
* If something went wrong
*/
public Object build() throws ReflectiveOperationException {
if (CONSTRUCTOR.getParameterCount() == 2) {
//1.19.3+
return CONSTRUCTOR.newInstance(entityId, DataWatcher.packDirty.invoke(dataWatcher.build()));
} else {
return CONSTRUCTOR.newInstance(entityId, dataWatcher.build(), true);
}
}
}
@@ -1,148 +0,0 @@
package me.neznamy.tab.platforms.bukkit.nms;
import lombok.AllArgsConstructor;
import lombok.NonNull;
import lombok.ToString;
import me.neznamy.tab.api.protocol.TabPacket;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import org.bukkit.Location;
import org.bukkit.entity.EntityType;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.EnumMap;
import java.util.UUID;
/**
* Custom class for holding data used in PacketPlayOutSpawnEntityLiving minecraft packet.
*/
@AllArgsConstructor @ToString
public class PacketPlayOutSpawnEntityLiving implements TabPacket {
/** NMS Fields */
public static Class<?> CLASS;
public static Class<?> EntityTypes;
public static Constructor<?> CONSTRUCTOR;
public static Field ENTITY_ID;
public static Field ENTITY_TYPE;
public static Field YAW;
public static Field PITCH;
public static Field UUID;
public static Field X;
public static Field Y;
public static Field Z;
public static Field DATA_WATCHER;
public static Object EntityTypes_ARMOR_STAND;
/** Entity types */
public static final EnumMap<EntityType, Integer> entityIds = new EnumMap<>(EntityType.class);
/** Packet's instance fields */
private final int entityId;
@NonNull private final UUID uniqueId;
@NonNull private final EntityType entityType;
@NonNull private final Location location;
private final DataWatcher dataWatcher;
/**
* Loads all required Fields and throws Exception if something went wrong
*
* @param nms
* NMS storage reference
* @throws NoSuchMethodException
* If something fails
*/
public static void load(NMSStorage nms) throws NoSuchMethodException {
if (nms.getMinorVersion() >= 19) {
entityIds.put(EntityType.ARMOR_STAND, 2);
} else if (nms.getMinorVersion() >= 13) {
entityIds.put(EntityType.ARMOR_STAND, 1);
} else {
entityIds.put(EntityType.WITHER, 64);
if (nms.getMinorVersion() >= 8) {
entityIds.put(EntityType.ARMOR_STAND, 30);
}
}
if (nms.getMinorVersion() >= 17) {
if (nms.is1_19_3Plus()) {
CONSTRUCTOR = CLASS.getConstructor(nms.Entity);
} else {
CONSTRUCTOR = CLASS.getConstructor(nms.EntityLiving);
}
} 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);
} 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);
}
} 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);
}
}
/**
* Converts this class into NMS packet
*
* @return NMS packet
* @throws ReflectiveOperationException
* If something went wrong
*/
public Object build() throws ReflectiveOperationException {
NMSStorage nms = NMSStorage.getInstance();
Object nmsPacket;
if (nms.getMinorVersion() >= 17) {
nmsPacket = CONSTRUCTOR.newInstance(nms.dummyEntity);
} else {
nmsPacket = CONSTRUCTOR.newInstance();
}
ENTITY_ID.set(nmsPacket, entityId);
YAW.set(nmsPacket, (byte)(location.getYaw() * 256.0f / 360.0f));
PITCH.set(nmsPacket, (byte)(location.getPitch() * 256.0f / 360.0f));
if (nms.getMinorVersion() <= 14) {
DATA_WATCHER.set(nmsPacket, dataWatcher.build());
}
if (nms.getMinorVersion() >= 9) {
UUID.set(nmsPacket, uniqueId);
X.set(nmsPacket, location.getX());
Y.set(nmsPacket, location.getY());
Z.set(nmsPacket, location.getZ());
} else {
X.set(nmsPacket, floor(location.getX()*32));
Y.set(nmsPacket, floor(location.getY()*32));
Z.set(nmsPacket, floor(location.getZ()*32));
}
int id = entityIds.get(entityType);
if (nms.getMinorVersion() >= 19) {
ENTITY_TYPE.set(nmsPacket, EntityTypes_ARMOR_STAND); // :(
} else {
ENTITY_TYPE.set(nmsPacket, id);
}
return nmsPacket;
}
private int floor(double paramDouble) {
int i = (int)paramDouble;
return paramDouble < i ? i - 1 : i;
}
}
@@ -1,33 +1,30 @@
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;
/**
* A class representing the n.m.s.DataWatcher class to make work with it much easier
*/
@ToString
public class DataWatcher {
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<>();
@@ -41,12 +38,18 @@ public class DataWatcher {
* @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);
}
}
@@ -58,39 +61,17 @@ public class DataWatcher {
* @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
* @throws ReflectiveOperationException
* if thrown by reflective operation
*/
public Object build() throws ReflectiveOperationException {
@SneakyThrows
public @NotNull Object build() {
NMSStorage nms = NMSStorage.getInstance();
Object nmsWatcher;
if (CONSTRUCTOR.getParameterCount() == 1) { //1.7+
@@ -105,26 +86,4 @@ public class DataWatcher {
}
return nmsWatcher;
}
/**
* 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());
}
}
return watcher;
}
}
@@ -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,33 +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 {
NMSStorage nms = NMSStorage.getInstance();
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,14 +1,13 @@
package me.neznamy.tab.platforms.bukkit.nms.storage.nms;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityDestroy;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityMetadata;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityTeleport;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutSpawnEntityLiving;
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;
@@ -18,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
*
@@ -29,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);
@@ -41,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
@@ -49,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);
}
}
@@ -98,67 +65,45 @@ public class BukkitLegacyNMSStorage extends NMSStorage {
Entity = getLegacyClass("Entity");
EntityLiving = getLegacyClass("EntityLiving");
PlayerConnection = getLegacyClass("PlayerConnection");
PacketPlayOutChatStorage.CLASS = getLegacyClass("PacketPlayOutChat", "Packet3Chat");
NetworkManager = getLegacyClass("NetworkManager");
DataWatcher.CLASS = getLegacyClass("DataWatcher");
DataWatcherItem.CLASS = getLegacyClass("DataWatcher$Item", "DataWatcher$WatchableObject", "WatchableObject");
PacketPlayOutSpawnEntityLivingStorage.CLASS = getLegacyClass("PacketPlayOutSpawnEntityLiving", "Packet24MobSpawn");
PacketPlayOutEntityTeleportStorage.CLASS = getLegacyClass("PacketPlayOutEntityTeleport", "Packet34EntityTeleport");
PacketPlayOutEntity = getLegacyClass("PacketPlayOutEntity", "Packet30Entity");
PacketPlayOutEntityDestroyStorage.CLASS = getLegacyClass("PacketPlayOutEntityDestroy", "Packet29DestroyEntity");
PacketPlayOutEntityLook = getLegacyClass("PacketPlayOutEntity$PacketPlayOutEntityLook", "PacketPlayOutEntityLook", "Packet32EntityLook");
PacketPlayOutEntityMetadataStorage.CLASS = getLegacyClass("PacketPlayOutEntityMetadata", "Packet40EntityMetadata");
PacketPlayOutNamedEntitySpawn = getLegacyClass("PacketPlayOutNamedEntitySpawn", "Packet20NamedEntitySpawn");
PacketScoreboard.DisplayObjectiveClass = getLegacyClass("PacketPlayOutScoreboardDisplayObjective", "Packet208SetScoreboardDisplayObjective");
PacketScoreboard.ObjectivePacketClass = getLegacyClass("PacketPlayOutScoreboardObjective", "Packet206SetScoreboardObjective");
PacketScoreboard.TeamPacketClass = getLegacyClass("PacketPlayOutScoreboardTeam", "Packet209SetScoreboardTeam");
PacketScoreboard.ScorePacketClass = getLegacyClass("PacketPlayOutScoreboardScore", "Packet207SetScoreboardScore");
PacketScoreboard.Scoreboard = getLegacyClass("Scoreboard");
PacketScoreboard.ScoreboardObjective = getLegacyClass("ScoreboardObjective");
PacketScoreboard.ScoreboardScoreClass = getLegacyClass("ScoreboardScore");
PacketScoreboard.IScoreboardCriteria = getLegacyClass("IScoreboardCriteria", "IObjective"); // 1.5.1+, 1.5
PacketScoreboard.ScoreboardTeam = getLegacyClass("ScoreboardTeam");
if (minorVersion >= 7) {
IChatBaseComponent = getLegacyClass("IChatBaseComponent");
ChatSerializer = getLegacyClass("IChatBaseComponent$ChatSerializer", "ChatSerializer");
}
if (minorVersion >= 8) {
PacketPlayOutPlayerListHeaderFooterStorage.CLASS = getLegacyClass("PacketPlayOutPlayerListHeaderFooter");
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 >= 12) {
PacketPlayOutChatStorage.ChatMessageTypeClass = (Class<Enum>) getLegacyClass("ChatMessageType");
}
// 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
PacketPlayOutSpawnEntityLiving.CLASS = getLegacyClass("PacketPlayOutSpawnEntityLiving", "Packet24MobSpawn");
PacketPlayOutEntityTeleport.CLASS = getLegacyClass("PacketPlayOutEntityTeleport", "Packet34EntityTeleport");
PacketPlayInUseEntity = getLegacyClass("PacketPlayInUseEntity", "Packet7UseEntity");
PacketPlayOutEntity = getLegacyClass("PacketPlayOutEntity", "Packet30Entity");
PacketPlayOutEntityDestroy.CLASS = getLegacyClass("PacketPlayOutEntityDestroy", "Packet29DestroyEntity");
PacketPlayOutEntityLook = getLegacyClass("PacketPlayOutEntity$PacketPlayOutEntityLook", "PacketPlayOutEntityLook", "Packet32EntityLook");
PacketPlayOutEntityMetadata.CLASS = getLegacyClass("PacketPlayOutEntityMetadata", "Packet40EntityMetadata");
PacketPlayOutNamedEntitySpawn = getLegacyClass("PacketPlayOutNamedEntitySpawn", "Packet20NamedEntitySpawn");
if (minorVersion >= 7) {
EnumEntityUseAction = (Class<Enum>) getLegacyClass("PacketPlayInUseEntity$EnumEntityUseAction", "EnumEntityUseAction");
}
// 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");
}
if (minorVersion >= 9) {
PacketPlayOutScoreboardTeamStorage.EnumTeamPush = (Class<Enum>) getLegacyClass("ScoreboardTeamBase$EnumTeamPush");
PacketScoreboard.EnumTeamPush = (Class<Enum>) getLegacyClass("ScoreboardTeamBase$EnumTeamPush");
}
}
}
@@ -1,14 +1,12 @@
package me.neznamy.tab.platforms.bukkit.nms.storage.nms;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityDestroy;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityMetadata;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityTeleport;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutSpawnEntityLiving;
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.
@@ -16,36 +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);
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) {
PacketPlayOutSpawnEntityLiving.EntityTypes_ARMOR_STAND = PacketPlayOutSpawnEntityLiving.EntityTypes.getDeclaredField("d").get(null);
(PacketPlayOutSpawnEntityLiving.ENTITY_TYPE = PacketPlayOutSpawnEntityLiving.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,77 +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");
if (minorVersion >= 19) {
PacketPlayOutChatStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.ClientboundSystemChatPacket");
} else {
PacketPlayOutChatStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutChat");
PacketPlayOutChatStorage.ChatMessageTypeClass = (Class<Enum>) Class.forName("net.minecraft.network.chat.ChatMessageType");
}
// 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) {
PacketPlayOutSpawnEntityLiving.CLASS = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutSpawnEntity");
} else {
PacketPlayOutSpawnEntityLiving.CLASS = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutSpawnEntityLiving");
}
PacketPlayOutEntityTeleport.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");
PacketPlayOutEntityTeleportStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutEntityTeleport");
PacketPlayOutEntity = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutEntity");
PacketPlayOutEntityDestroy.CLASS = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutEntityDestroy");
PacketPlayOutEntityDestroyStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutEntityDestroy");
PacketPlayOutEntityLook = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutEntity$PacketPlayOutEntityLook");
PacketPlayOutEntityMetadata.CLASS = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutEntityMetadata");
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) {
PacketPlayOutSpawnEntityLiving.EntityTypes = Class.forName("net.minecraft.world.entity.EntityTypes");
}
// Player Info
if (minorVersion >= 19) {
PacketPlayOutPlayerInfoStorage.ProfilePublicKey = Class.forName("net.minecraft.world.entity.player.ProfilePublicKey");
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 = Class.forName("net.minecraft.network.chat.RemoteChatSession");
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,130 +0,0 @@
package me.neznamy.tab.platforms.bukkit.nms.storage.nms;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityDestroy;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityMetadata;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityTeleport;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutSpawnEntityLiving;
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) {
PacketPlayOutSpawnEntityLiving.EntityTypes_ARMOR_STAND = PacketPlayOutSpawnEntityLiving.EntityTypes.getDeclaredField("ARMOR_STAND").get(null);
(PacketPlayOutSpawnEntityLiving.ENTITY_TYPE = PacketPlayOutSpawnEntityLiving.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");
if (minorVersion >= 19) {
PacketPlayOutChatStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.ClientboundSystemChatPacket");
} else {
PacketPlayOutChatStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.ClientboundChatPacket");
PacketPlayOutChatStorage.ChatMessageTypeClass = (Class<Enum>) Class.forName("net.minecraft.network.chat.ChatType");
}
// 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) {
PacketPlayOutSpawnEntityLiving.CLASS = Class.forName("net.minecraft.network.protocol.game.ClientboundAddEntityPacket");
} else {
PacketPlayOutSpawnEntityLiving.CLASS = Class.forName("net.minecraft.network.protocol.game.ClientboundAddMobPacket");
}
PacketPlayOutEntityTeleport.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");
PacketPlayOutEntityDestroy.CLASS = Class.forName("net.minecraft.network.protocol.game.ClientboundRemoveEntitiesPacket");
PacketPlayOutEntityLook = Class.forName("net.minecraft.network.protocol.game.ClientboundMoveEntityPacket$Rot");
PacketPlayOutEntityMetadata.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) {
PacketPlayOutSpawnEntityLiving.EntityTypes = Class.forName("net.minecraft.world.entity.EntityType");
}
// Player Info
if (minorVersion >= 19) {
PacketPlayOutPlayerInfoStorage.ProfilePublicKey = Class.forName("net.minecraft.world.entity.player.ProfilePublicKey");
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 = Class.forName("net.minecraft.network.chat.RemoteChatSession");
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,29 +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 me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityDestroy;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityMetadata;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityTeleport;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutSpawnEntityLiving;
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.
@@ -38,13 +34,14 @@ 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]);
/** Flag determining whether the server version is at least 1.19.3 or not */
@Getter private final boolean is1_19_3Plus = ReflectionUtils.classExists("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket");
@Getter private final boolean is1_19_4Plus = is1_19_3Plus && !serverPackage.equals("v1_19_R2");
/** Basic universal values */
protected Class<?> Packet;
@@ -57,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;
@@ -71,12 +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_ENTITY;
public Field PacketPlayInUseEntity_ACTION;
public Class<?> PacketPlayOutEntity;
public Field PacketPlayOutEntity_ENTITYID;
@@ -85,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,
@@ -99,57 +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);
PacketPlayOutEntityDestroy.load(this);
PacketPlayOutEntityMetadata.load(this);
PacketPlayOutEntityTeleport.load(this);
PacketPlayOutSpawnEntityLiving.load(this);
PacketPlayOutChatStorage.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_ENTITY = getFields(PacketPlayInUseEntity, int.class).get(0);
PacketPlayInUseEntity_ACTION = getFields(PacketPlayInUseEntity, EnumEntityUseAction).get(0);
DataWatcherHelper.load(this);
PacketPlayOutEntityDestroyStorage.load();
PacketPlayOutEntityMetadataStorage.load(this);
PacketPlayOutEntityTeleportStorage.load(this);
PacketPlayOutSpawnEntityLivingStorage.load(this);
PacketScoreboard.load(this);
PacketPlayOutEntity_ENTITYID = ReflectionUtils.getFields(PacketPlayOutEntity, int.class).get(0);
PacketPlayOutNamedEntitySpawn_ENTITYID = ReflectionUtils.getFields(PacketPlayOutNamedEntitySpawn, int.class).get(0);
if (minorVersion < 17) {
try {
(PING = EntityPlayer.getDeclaredField("ping")).setAccessible(true); // 1.5.2 - 1.16.5
} catch (NoSuchFieldException e) {
(PING = EntityPlayer.getDeclaredField("field_71138_i")).setAccessible(true); // 1.7.10 Thermos
}
}
}
@@ -161,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.
@@ -267,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,56 +0,0 @@
package me.neznamy.tab.platforms.bukkit.nms.storage.packet;
import me.neznamy.tab.api.ProtocolVersion;
import me.neznamy.tab.api.protocol.PacketPlayOutChat;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import java.lang.reflect.Constructor;
import java.util.UUID;
@SuppressWarnings({"unchecked", "rawtypes"})
public class PacketPlayOutChatStorage {
public static Class<?> CLASS;
public static Class<Enum> ChatMessageTypeClass;
public static Constructor<?> CONSTRUCTOR;
public static void load(NMSStorage nms) throws NoSuchMethodException {
if (nms.getMinorVersion() >= 19) {
try {
CONSTRUCTOR = CLASS.getConstructor(nms.IChatBaseComponent, boolean.class);
} catch (NoSuchMethodException e) {
//1.19.0
CONSTRUCTOR = CLASS.getConstructor(nms.IChatBaseComponent, int.class);
}
} else if (nms.getMinorVersion() >= 16) {
CONSTRUCTOR = CLASS.getConstructor(nms.IChatBaseComponent, ChatMessageTypeClass, UUID.class);
} else if (nms.getMinorVersion() >= 12) {
CONSTRUCTOR = CLASS.getConstructor(nms.IChatBaseComponent, ChatMessageTypeClass);
} else if (nms.getMinorVersion() >= 8) {
CONSTRUCTOR = CLASS.getConstructor(nms.IChatBaseComponent, byte.class);
} else if (nms.getMinorVersion() >= 7) {
CONSTRUCTOR = CLASS.getConstructor(nms.IChatBaseComponent);
}
}
public static Object build(PacketPlayOutChat packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
NMSStorage nms = NMSStorage.getInstance();
Object component = nms.toNMSComponent(packet.getMessage(), clientVersion);
if (nms.getMinorVersion() >= 19)
try {
return CONSTRUCTOR.newInstance(component, packet.getType() == PacketPlayOutChat.ChatMessageType.GAME_INFO);
} catch (Exception e) {
//1.19.0
return CONSTRUCTOR.newInstance(component, packet.getType().ordinal());
}
if (nms.getMinorVersion() >= 16)
return CONSTRUCTOR.newInstance(component, Enum.valueOf(ChatMessageTypeClass, packet.getType().toString()), UUID.randomUUID());
if (nms.getMinorVersion() >= 12)
return CONSTRUCTOR.newInstance(component, Enum.valueOf(ChatMessageTypeClass, packet.getType().toString()));
if (nms.getMinorVersion() >= 8)
return CONSTRUCTOR.newInstance(component, (byte) packet.getType().ordinal());
if (nms.getMinorVersion() == 7)
return CONSTRUCTOR.newInstance(component);
return packet;
}
}
Loaded 100 of 426 files, more files were not shown because too many files have changed in this diff. Show more