Compare commits

...
174 Commits
Author SHA1 Message Date
NEZNAMY 590286a6c2 4.0.5 2023-09-27 21:12:58 +02:00
NEZNAMY 6c96650dcc Add support for new Spigot build that uses mojang mappings for fields and methods 2023-09-27 21:12:22 +02:00
NEZNAMY 7c83a74fd8 Fix plugin trying to send scoreboard packets to players who left already 2023-09-27 19:20:32 +02:00
NEZNAMY 7b3f60cb3b 4.0.5-SNAPSHOT 2023-09-23 16:37:02 +02:00
NEZNAMY bfc845088a Merge pull request #1084 from GsTio86/master
Fix Arclight compatibility
2023-09-23 12:36:32 +02:00
Galen Huang e67d93a592 Update DataWatcher.java 2023-09-23 16:58:19 +08:00
gstio86 b518b980ae Fix Arclight compatibility 2023-09-23 14:35:06 +08:00
NEZNAMY 744bc1403a Remove tps placeholder from config entirely, the negation was wrong as well 2023-09-22 11:01:35 +02:00
NEZNAMY e11113073f 4.0.4 2023-09-22 09:00:37 +02:00
NEZNAMY 3615818812 Add condition to default config to prevent %tps% being displayed on BungeeCord in scoreboard, as the message is too long 2023-09-22 08:59:31 +02:00
NEZNAMY 0cdef12caf Add support for BungeeCord 1.20.2 2023-09-22 08:56:19 +02:00
NEZNAMY dda8c6c053 Fix another error on Bukkit 1.20.2 2023-09-21 22:21:46 +02:00
NEZNAMY 8c3327f553 Fix error on Bukkit 1.20.2 2023-09-21 20:49:29 +02:00
NEZNAMY c037a14e1f Update Bukkit to 1.20.2 2023-09-21 19:45:06 +02:00
NEZNAMY bd661360ee Delete %server_uptime% from default config as it's a PlaceholderAPI placeholder, which is not always guaranteed to work. (#1077) 2023-09-21 18:33:27 +02:00
NEZNAMY 317fabbc87 Update Fabric to 1.20.2 2023-09-21 18:31:49 +02:00
NEZNAMY 87161446c5 Add 1.20.2 to protocol versions 2023-09-21 18:25:22 +02:00
NEZNAMY 7db8b2e84b Send warn if trying to use parse command from the console with "me" player name argument 2023-09-21 15:04:03 +02:00
NEZNAMY 5f3fa2b317 Layout - Fix ROWS direction type not working properly & avoid real players appearing on top when teams are disabled 2023-09-19 22:14:37 +02:00
NEZNAMY 64c15730a6 Add missing correct Placeholder#update implementations (#1075) 2023-09-17 18:48:41 +02:00
NEZNAMY e5ab46b8ad Initialize scoreboard placeholders to avoid them rarely not parsing due to not being set 2023-09-17 15:01:51 +02:00
NEZNAMY 3060d8cc2b Add override for %player_ping% placeholder because 1.20.2 will have a breaking change to prevent github issues reporting server expansion not supporting new version 2023-09-16 10:30:05 +02:00
NEZNAMY f5d6ba462e Remove unnecessary fully qualified class names 2023-09-15 15:21:39 +02:00
NEZNAMY 81fdeece96 Port new Velocity anti-override to Sponge as well 2023-09-15 14:21:20 +02:00
NEZNAMY 8f60439ef3 Fix multiple sorting types with same priority separated with | not actually working 2023-09-14 16:13:44 +02:00
NEZNAMY c5420ead90 Send gamemode update packet on world switch when spectator fix is enabled as the server may resend gamemode on world switch back to spectator (#1069) 2023-09-13 16:55:21 +02:00
NEZNAMY 99d372d03c Fix refresh interval config conversion ignoring random invalid user-made maps and then causing error on load 2023-09-12 14:32:15 +02:00
NEZNAMY 1219a9822f Remove unused dependency 2023-09-10 10:42:37 +02:00
NEZNAMY d40c182af9 Clean up repositories 2023-09-10 10:39:12 +02:00
NEZNAMY 785e986a72 Fix component cache including child components 2023-09-09 21:04:41 +02:00
NEZNAMY e8b80f455b Replace converter methods with arrays for fast access 2023-09-09 11:33:16 +02:00
NEZNAMY 2d545f1bf1 Fix potential blocking of other plugins in new anti-override for Velocity 2023-09-09 08:22:45 +02:00
NEZNAMY d4713dc7f4 Use direct code for LibsDisguises hook instead of reflection and move it into a separate class 2023-09-08 23:54:21 +02:00
NEZNAMY cd96c13670 Remove unused imports 2023-09-08 23:07:13 +02:00
NEZNAMY 808607faf0 Add try/catch for Fabric loading to send warn on unsupported versions instead of throwing an error (#1068) 2023-09-08 21:30:54 +02:00
NEZNAMY bc403f5c23 Fix ConcurrentModificationException on Velocity 2023-09-08 19:04:00 +02:00
NEZNAMY bfc67e1953 Allow the new anti-override to be disabled with standard settings 2023-09-08 08:21:42 +02:00
NEZNAMY a186c16c34 Implement anti-override for tablist display names on Velocity 2023-09-07 21:37:33 +02:00
NEZNAMY f9be9d4c4b Improve check to reduce errors thrown 2023-09-07 18:09:38 +02:00
NEZNAMY 79fc3ce33f Fix BossBars getting resent when output of placeholder used in display condition changed even if condition result didn't change (#1065) 2023-09-06 21:14:35 +02:00
NEZNAMY 055ff8749e Add support for custom fonts in configuration 2023-09-05 21:38:38 +02:00
NEZNAMY d36410e851 Simplify 2023-09-05 21:32:01 +02:00
NEZNAMY 0fed5dca7f Add font field and conversion to component class and quality of life changes for component conversion 2023-09-05 15:30:42 +02:00
NEZNAMY dd34ffc5b2 Create internal enum for scoreboard display type instead of using a boolean 2023-09-04 21:00:17 +02:00
NEZNAMY 2cd5ac85f9 Simplify 2023-09-04 18:20:17 +02:00
NEZNAMY 571f949f50 Remove unnecessary player version check 2023-09-04 17:58:38 +02:00
NEZNAMY 81e32957b2 Fix startup error 2023-09-04 17:57:20 +02:00
NEZNAMY 4639016ffb Preload NMS GameModes instead of calling Enum#valueOf every time 2023-09-04 17:41:26 +02:00
NEZNAMY dabc2b49dc Remove unused fields and methods 2023-09-03 17:52:29 +02:00
NEZNAMY 2dec389c8c Load NMS method by parameter types instead of name 2023-09-01 11:26:05 +02:00
NEZNAMY bfe5b5960e Fix error in per world playerlist on <1.8 2023-08-31 21:04:36 +02:00
NEZNAMY e352967a17 Improve parse command and fix color-related issues on bukkit 2023-08-31 18:48:04 +02:00
NEZNAMY 930917f285 Fill auto-complete suggestions in plugin's PlaceholderAPI expansion 2023-08-30 21:04:49 +02:00
NEZNAMY b9747f7fbe Fix NPE when user deletes default-refresh-interval option from config (1052) 2023-08-28 13:39:39 +02:00
NEZNAMY 5ac9dcec37 Fix startup on Sponge 7 2023-08-28 13:00:11 +02:00
NEZNAMY 8328b43783 Fix NPE when using relational placeholder with multiple proxies 2023-08-25 22:28:23 +02:00
NEZNAMY 131fcfe144 Remove redundant annotation 2023-08-24 17:36:16 +02:00
NEZNAMY 709cb967b5 Fix plugin sometimes trying to format tablist entries for 1.7 players 2023-08-24 12:16:16 +02:00
NEZNAMY 3370b83087 Fix issue in config conversion 2023-08-24 12:06:04 +02:00
NEZNAMY 5c44bd500c Simplify 2023-08-24 10:05:21 +02:00
NEZNAMY ab8a345944 Small style changes 2023-08-24 09:45:23 +02:00
NEZNAMY 337dd56c87 Simplify 2023-08-24 09:38:54 +02:00
NEZNAMY de78ae30db Copy the previous fix to aligned playerlist as well 2023-08-22 14:29:04 +02:00
NEZNAMY 3f64185389 Fix disable condition not being respected in playerlist in one method (#1037) 2023-08-22 14:23:32 +02:00
NEZNAMY 5077cbbc9a Return empty list instead of new list 2023-08-22 14:01:50 +02:00
NEZNAMY 4d522ba5f5 Improve bossbar command to match scoreboard command features 2023-08-22 13:57:45 +02:00
NEZNAMY 4f1180b3f5 Boost performance of tablist getter on BungeeCord 2023-08-17 17:53:22 +02:00
NEZNAMY 577a3f2551 Add support for Mohist 1.18.2 2023-08-13 17:54:42 +02:00
NEZNAMY 8623ab053e Remove some unnecessary local variables 2023-08-12 20:51:25 +02:00
NEZNAMY 6d0f99eaf1 Change WeakHashMap key to use own player object instead of server's to prevent map getting big when another plugin has a memory leak 2023-08-12 19:04:30 +02:00
NEZNAMY f9490d2652 Move field into local variable 2023-08-12 15:08:15 +02:00
NEZNAMY 9d321e4a31 Remove unused parameter and simplify 2023-08-11 22:12:00 +02:00
NEZNAMY b8c2625b80 Only show placeholder retrieve time message if debug is enabled 2023-08-09 20:22:38 +02:00
NEZNAMY 8e42f328dc Register plugin's PlaceholderAPI expansion automatically without having to enable a config option 2023-08-09 20:13:26 +02:00
NEZNAMY 0c6b4efba5 Simplify 2023-08-09 17:02:25 +02:00
NEZNAMY 0821ed4dae Bukkit - Make NMS loading less dependent on the main NMS class 2023-08-09 17:00:20 +02:00
NEZNAMY 8bc38e25f9 Fix empty line detection sometimes not working 2023-08-07 21:51:43 +02:00
NEZNAMY 7a7a4e6da2 Global playerlist - Do not remove & add self on server switch 2023-08-07 14:04:25 +02:00
NEZNAMY f7d703ed1e Placeholders - Decrease return time warn threshold from 1000ms to 50ms 2023-08-07 13:40:35 +02:00
NEZNAMY 443d7fd082 Merge branch 'master' of https://github.com/NEZNAMY/TAB 2023-08-07 13:38:47 +02:00
NEZNAMY b6114625c1 Remove duplicate check 2023-08-07 12:18:13 +02:00
NEZNAMY 4a3eb78f12 Add back ping spoof feature 2023-08-06 11:32:02 +02:00
NEZNAMY fa117a671a Simplify placeholder registration 2023-08-05 23:04:36 +02:00
NEZNAMY 6448fb975a Fix scoreboard title limit 2023-08-05 14:41:40 +02:00
NEZNAMY 1c685ce0bb Bukkit - Properly play around Bukkit API limits in scoreboard 2023-08-05 14:36:14 +02:00
NEZNAMY 1662fa8d60 Bukkit - Implement scoreboard handler using Bukkit API (still unused) 2023-08-05 13:23:03 +02:00
NEZNAMY 2787b1735b Header/Footer - Fix being sent on server switch even if disable condition is met & fix being sent twice 2023-08-04 22:07:06 +02:00
NEZNAMY e58390e30b Add some missing NotNull / Nullable annotations 2023-08-04 20:43:58 +02:00
NEZNAMY 41a10a61bf Small refactoring 2023-08-03 22:56:05 +02:00
NEZNAMY fdcc7d5e7c Global Playerlist - Add support for * symbol in server-groups 2023-08-01 13:35:06 +02:00
NEZNAMY 50b39e462f Remove unnecessary full manual import 2023-07-28 23:18:49 +02:00
NEZNAMY 4bc781f0da Refresh placeholders in a different thread, so they can no longer kill the plugin 2023-07-28 22:33:33 +02:00
NEZNAMY aca4c00083 Do not refresh placeholders in PlaceholderManager on join, as it can cause error when updating nested placeholder in a feature that's not loaded yet 2023-07-28 17:17:07 +02:00
NEZNAMY 77b820ad4d Do not spawn armor stands of dead players on join (#1005) 2023-07-28 17:02:42 +02:00
NEZNAMY 328f604236 Add pipeline injection for fabric 2023-07-28 17:02:13 +02:00
NEZNAMY 2f77bf3d10 Create a separate class for CPU report 2023-07-28 13:05:04 +02:00
NEZNAMY 23eb55aa06 Add back support for mojang-mapped server jars 2023-07-26 16:19:21 +02:00
NEZNAMY 32b7356bc0 Send console hint if both layout and prevent-spectator-effect are enabled 2023-07-25 18:22:34 +02:00
NEZNAMY 6091641ed9 Add warn if both layout and yellow-number features are enabled 2023-07-25 18:16:01 +02:00
NEZNAMY e368213d18 Add misconfiguration check for scoreboard chaining 2023-07-25 15:17:41 +02:00
NEZNAMY 58f9bb6fca Add unlimited nametag mode to Fabric (with a few missing things) 2023-07-24 22:26:24 +02:00
NEZNAMY 79798199b8 More abstraction of unlimited nametag mode 2023-07-24 20:53:14 +02:00
NEZNAMY c8d20f8479 Move NMS loading of classes 2023-07-24 17:59:17 +02:00
NEZNAMY c0498a60b4 Remove some console messages on startup 2023-07-24 16:46:00 +02:00
NEZNAMY faa01ddfc2 Add back compatibility attempt for nick plugins (#974) 2023-07-23 12:47:16 +02:00
NEZNAMY a2f628e267 Add try/catch for PremiumVanish hook due to its bug (#993) 2023-07-23 11:54:04 +02:00
NEZNAMY 66007b9121 Decrease component cache size to 1000 entries 2023-07-22 01:25:28 +02:00
NEZNAMY 9e0056b603 Improve source code parity between platforms (Sponge 7, Sponge 8 and Fabric) and improvements to Fabric 2023-07-22 01:22:13 +02:00
NEZNAMY b15cc3a6af Improve source code parity between platforms (BungeeCord and Velocity) 2023-07-21 22:33:55 +02:00
NEZNAMY 5393f869e7 Remove option to disable global playerlist latency update. The performance drop is almost zero and updating is actually needed as initial ping value can be invalid ((#990) 2023-07-20 18:05:39 +02:00
NEZNAMY f8088402e8 Make it more obvious that %tps% is a backend-only placeholder (#990) 2023-07-20 17:48:27 +02:00
NEZNAMY 979ff7d2b8 Move %mspt% placeholder registration into backend package 2023-07-19 23:29:07 +02:00
NEZNAMY bdbde58f42 Drop krypton platform as author abandoned it 2023-07-19 21:33:07 +02:00
NEZNAMY 1e26e0bd13 Change backend placeholder registration method 2023-07-19 19:34:36 +02:00
NEZNAMY be3c00d17e Fix colors in error messages 2023-07-16 12:53:46 +02:00
NEZNAMY d542bb67de Add a console warn if anti-override is disabled 2023-07-16 12:09:16 +02:00
NEZNAMY a79cdfaa1c Add a console warn if placeholder used in numeric sorting did not return a valid number 2023-07-16 11:46:07 +02:00
NEZNAMY 42db715421 Fix NPE 2023-07-15 21:16:10 +02:00
NEZNAMY 2228d5a35d Small cleanup 2023-07-15 21:08:18 +02:00
NEZNAMY c434d0eafd Remove component cache from Sponge 7 2023-07-15 21:01:27 +02:00
NEZNAMY 414a3a971f Optimize placeholder high_to_low and low_to_high to only take 3 characters instead of 10 and slightly increase range as well 2023-07-15 18:32:58 +02:00
NEZNAMY b8a22a2561 Fix layout fixed slot updating issue when switching between layouts 2023-07-15 15:06:25 +02:00
NEZNAMY bc1dc2f84f Fix layout skins not working on sponge 8 2023-07-15 14:24:01 +02:00
NEZNAMY 904c4f27aa Add check if a placeholder's refresh interval is configured same as default interval 2023-07-14 16:28:21 +02:00
NEZNAMY b19e5435ef Add check for redundant per-server / per-world overrides for header/footer 2023-07-14 15:45:18 +02:00
NEZNAMY 78fbcd829a Construct player object async 2023-07-14 14:22:56 +02:00
NEZNAMY a255844a92 Create separate methods for logging info/warn and use them 2023-07-14 14:19:22 +02:00
NEZNAMY c7a548c575 Fix placeholders in tablist formatting getting stuck when on bungeecord and joining into disabled server 2023-07-14 11:50:18 +02:00
NEZNAMY 90a4bab114 Fix global playerlist formatting tablist names for players meeting disable condition 2023-07-13 23:47:28 +02:00
NEZNAMY a1633d146a Fix NPE when using relational placeholders on bungeecord 2023-07-12 22:42:21 +02:00
NEZNAMY 87bf4b03df Add misconfiguration checks for bossbar properties 2023-07-12 17:58:17 +02:00
NEZNAMY 7731aea0d6 Create %player_ping% placeholder on bukkit to override the PAPI one and avoid issues like #972 when ping field changes 2023-07-10 21:37:33 +02:00
NEZNAMY e66350111e Make detectPlaceholders method 7x faster when text only consists of 1 placeholder 2023-07-09 22:27:13 +02:00
NEZNAMY 7e5df68626 Round animation change interval instead of decreasing it when invalid value is used 2023-07-09 21:25:10 +02:00
NEZNAMY 8e3e7de4ee Send warn if a placeholder took longer than 1000ms to return value 2023-07-09 18:35:03 +02:00
NEZNAMY 0ee535fbd5 Small code style improvements from PR 2023-07-09 10:45:05 +02:00
NEZNAMY 1b471d266c Merge pull request #970 from TFSMads/master
Added support for different default skins depending on the slot index in the layout feature
2023-07-09 10:38:44 +02:00
NEZNAMY 19a4f6ea1f Merge pull request #971 from misteeka/master
@NotNull marked function actually can return null
2023-07-08 19:10:36 +02:00
misteeka 7cc8d71224 Fix typo 2023-07-08 19:42:30 +03:00
_Mads b843475a0a Remove unnecessary imports and debug message 2023-07-08 15:18:05 +02:00
_Mads 027c0fa771 Added support for different default skins depending on the slot index 2023-07-08 15:09:50 +02:00
NEZNAMY c08e76f110 Fix startup error when refresh intervals are misconfigured (#969) 2023-07-08 12:46:39 +02:00
NEZNAMY b837fe7d15 4.0.4-SNAPSHOT 2023-07-08 12:34:21 +02:00
NEZNAMY d4d1bed408 4.0.3 2023-07-07 22:51:41 +02:00
NEZNAMY 8870295d54 Fix compatibility with bukkit/forge hybrids if they use different mappings 2023-07-07 22:50:32 +02:00
NEZNAMY 79cd6e4ae2 Add null check for messages form bridge (for future updates to avoid NPE / protocol break) 2023-07-07 17:36:44 +02:00
NEZNAMY 8e5ea29c40 Define decimal separator in one more way to make sure it works properly (#967) 2023-07-07 15:25:11 +02:00
NEZNAMY 287b51a0bf Send warn if using debug command on player who is not loaded (#959) 2023-07-06 20:55:16 +02:00
NEZNAMY 28cbf952fa Send warn if both per world playerlist and layout are enabled 2023-07-06 15:12:28 +02:00
NEZNAMY 598c35a3aa Simplify 2023-07-06 12:37:24 +02:00
NEZNAMY b8b67a64ab Simplify nms loading 2023-07-06 12:33:47 +02:00
NEZNAMY 7f6020ef34 Fix loading on catserver 2023-07-06 12:13:12 +02:00
NEZNAMY aeed453e15 Move named field loading to respective classes 2023-07-05 23:41:54 +02:00
NEZNAMY 71dddcca4c Drop support for mojang mapped server jar 2023-07-05 21:49:54 +02:00
NEZNAMY 46cdf79e20 Use all arg constructor for spawn entity packet on 1.17+ (using the wrong packet still spawns the entity) 2023-07-05 21:12:47 +02:00
NEZNAMY d7af96823b Merge pull request #962 from MachineBreaker/patch-1
Fix ViaVersion bug returning -1 version
2023-07-04 23:11:08 +02:00
MachineBreaker 5a228bd6b3 Fix ViaVersion bug returning -1 version
Explanation:

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

There might be another way to workaround this since i don't know changing the priority would cause any issue with TAB itself.
2023-07-04 16:19:07 -04:00
NEZNAMY fcd95655ce Send warn when trying to use parse command without any placeholders 2023-07-04 11:09:14 +02:00
NEZNAMY 7f5b57f8d2 Merge pull request #956 from Tanguygab/master
Update skins in Layout's FixedSlots
2023-07-03 17:07:01 +02:00
NEZNAMY f596877716 Fix forwarding unlimited nametag disable status to backend 2023-07-02 17:30:58 +02:00
NEZNAMY 1802c8fd90 Fix debug command not showing properties from unlimited nametag as disabled when they are 2023-07-02 17:30:35 +02:00
Tanguygab e05856facc only update name if skin didn't update 2023-07-02 13:51:37 +02:00
Tanguygab 45cfc3abb0 simplified FixedSlot skin updating 2023-07-02 13:40:08 +02:00
Tanguygab 2d39579861 added support for placeholders in layout's fixed-slots' skins 2023-07-02 13:27:37 +02:00
NEZNAMY 431f4bb4d5 Change Velocity policy 2023-07-02 00:39:10 +02:00
NEZNAMY 7bc9d38176 Slightly improve debug messages 2023-07-01 20:15:50 +02:00
NEZNAMY 7536447a8f Update README.md 2023-06-29 20:47:33 +02:00
NEZNAMY 059b796061 Update CONTRIBUTING.md 2023-06-29 20:35:44 +02:00
NEZNAMY a89942484f Layout - Fix duplicate internal names causing issues 2023-06-28 21:03:13 +02:00
NEZNAMY c28be3fd65 Add (another) warn if plugin's CPU usage is high 2023-06-26 21:04:26 +02:00
NEZNAMY 5f1116795c Add Folia support (#919) 2023-06-25 12:11:41 +02:00
NEZNAMY 4f54d2f6b2 4.0.3-SNAPSHOT 2023-06-25 10:59:05 +02:00
NEZNAMY 6b7bedbe2d Fix NPE when placeholder returns null 2023-06-24 14:30:44 +02:00
NEZNAMY 20ff225c31 Add missing set team color 2023-06-23 22:03:32 +02:00
217 changed files with 6125 additions and 4355 deletions

No files matched your search

+1 -2
View File
@@ -6,5 +6,4 @@ is in quality of the report (typically providing correct steps to reproduce that
* Test **experimental builds**, which can be found in [GitHub Actions](https://github.com/NEZNAMY/TAB/actions) for problems of any kind including bugs.
This way you will help make the actual releases more stable / help get stable releases sooner.
* Help improve the wiki by opening a [Wiki change issue](https://github.com/NEZNAMY/TAB/issues/new?assignees=&labels=Wiki&template=wiki_change.yml),
where you can improve existing descriptions, add information you found missing, fix typos / grammar mistakes or add more examples of usage of functions.
* [Buy a copy](https://builtbybit.com/resources/14009/) of the plugin or [send a donation](https://paypal.me/neznamy1) to keep the plugin active.
where you can improve existing descriptions, add information you found missing, fix typos / grammar mistakes or add more examples.
+8 -55
View File
@@ -1,70 +1,23 @@
# About
TAB aims to be a superior all-in-one minecraft plugin for displaying information that outperforms all
similar plugins in terms of features, performance, compatibility and support.
similar plugins in terms of features, performance and compatibility.
More information can be found at [Why TAB?](https://github.com/NEZNAMY/TAB/wiki/Why-TAB%3F) wiki page.
# Distribution
As of TAB 3.0.0, released with MC 1.18 (November 30th, 2021), there is no free / premium
version anymore. Only one version of the plugin exists now, which contains all implemented features
and is available to everyone for free. However, customer support is only provided to
customers who have purchased the plugin (see [Customer support](#customer-support)).
# Compiling
Compilation requires JDK 17 and up.
To compile the plugin, run `./gradlew build` from the terminal.
Once the plugin compiles, grab the jar from `/jar/build/libs/` folder.
The universal jar contains all modules for all supported platforms.
# Customer support
Unlike with other projects where requirements and level of support provided are unclear,
we provide a clear answer to what kind of service you will or will not receive. Some projects don't
even clearly state if they are free or paid. While they are listed as paid on spigot, they have
a free download link in description, which can be confusing.
Support is also quite often "we have a discord server, but don't ping devs for support", suggesting
there is no official support, just the community.
In TAB, everything is clearly defined. **Downloads are free, but services are paid.**
The plugin is free to download for everyone. The only exception might be occasional update delays
for free users once a year.
Customer support, however, is only provided to customers who have formally purchased a copy
of the plugin on any of the websites the plugin is being sold on:
* [BuiltByBit](https://builtbybit.com/resources/14009/)
* [Polymart](https://polymart.org/resource/484)
After purchasing, you can request assistance via either private messages on the website where you
have purchased it, or using [TAB's Discord server](https://discord.gg/EaSvdk6) (which is highly
preferred, since the communication will be much faster thanks to notifications and more).
* We use a ticket system, which allows us to effectively track any amount of unresolved problems.
No more questions getting lost in the sea of messages from other people asking. Tickets are only closed
after user confirmed all of their questions were answered and then receive a transcript of the ticket
from the bot after ticket is closed into private messages (unless user is blocking them).
* We do not rely on the community helping, which often makes things even worse. We know people have
better things to do than helping random people with a random plugin, especially when most people only
have a very basic knowledge of the plugin, not being able to answer more complicated problems.
* We have notifications enabled and reply as soon as possible. We do not have the server muted unlike
most mods on most servers who only check it when they are really bored, give a wiki link and
disappear again.
* As the developer, I do not rely on helpers providing support instead of me. They are handy as
I am obviously not available 24/7, but I do not intentionally leave anything to them. I reply
whenever I am available without any intentional delays.
# Other links
Download the plugin for free:
* [GitHub releases](https://github.com/NEZNAMY/TAB/releases)
* [BuiltByBit](https://builtbybit.com/resources/20631/)
* [Modrinth](https://modrinth.com/plugin/tab-was-taken)
[Wiki](https://github.com/NEZNAMY/TAB/wiki)
# Documentation
You can find everything about the plugin on the [Wiki](https://github.com/NEZNAMY/TAB/wiki). This includes a detailed description
of all features, as well as information regarding compatibility or limitations of each feature.
# Issues
The [Issues](https://github.com/NEZNAMY/TAB/issues) section is **only** available for cases included in templates.
In other words, they **are not available for**:
* **Support** - support is provided on discord for buyers, where roles are given to verified users
allowing us to know who bought it and who didn't. It also serves as a much faster way of communication.
* **Feature requests** - closed indefinitely.
**If you're a customer, please use discord for reporting bugs, especially if you aren't good at making bug reports.
You will be provided with a much faster and higher quality service.**
The [Issues](https://github.com/NEZNAMY/TAB/issues) section is an issue tracker for all the kind of problems covered in issue templates.
Please refrain from attempting to use it to receive help with your server, such issues will be closed immediately.
When opening an issue, provide as much information as possible. The difference between a bug getting fixed in 1 week vs 1 hour
is in quality of the report. Bug reports where steps to reproduce are "idk" have no value and will not help to get the bug fixed at all.
# Contributing
See [CONTRIBUTING.md](https://github.com/NEZNAMY/TAB/blob/master/CONTRIBUTING.md)
@@ -193,6 +193,20 @@ public interface BossBar {
*/
@NotNull List<TabPlayer> getPlayers();
/**
* Returns {@code true} if this bossbar is visible to specified player based on
* display condition or adding via {@link #addPlayer(TabPlayer)}, {@code false} if not.
* <p>
* This may return {@code true} even if player cannot physically see the bossbar due to
* having it disabled via toggle command or {@link BossBarManager#toggleBossBar(TabPlayer, boolean)}
* (or {@link BossBarManager#setBossBarVisible(TabPlayer, boolean, boolean)}).
*
* @param player
* Player to check
* @return {@code true} if player is allowed to see this Boss Bar, {@code false} if not.
*/
boolean containsPlayer(TabPlayer player);
/**
* Returns {@code true} if this boss bar is configured as announcement-only bar,
* {@code false} if not.
@@ -6,6 +6,7 @@ import java.util.Map;
import lombok.NonNull;
import me.neznamy.tab.api.TabPlayer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Interface allowing work with scoreboards, such as creating,
@@ -164,5 +165,5 @@ public interface ScoreboardManager {
* player to get active scoreboard of
* @return player's active scoreboard or {@code null} if player has no scoreboard
*/
@NotNull Scoreboard getActiveScoreboard(@NonNull TabPlayer player);
@Nullable Scoreboard getActiveScoreboard(@NonNull TabPlayer player);
}
+1 -2
View File
@@ -4,7 +4,7 @@ plugins {
allprojects {
group = "me.neznamy"
version = "4.0.2"
version = "4.0.5"
description = "An all-in-one solution that works"
ext.set("id", "tab")
@@ -15,7 +15,6 @@ allprojects {
val platforms = setOf(
projects.bukkit,
projects.bungeecord,
projects.krypton,
projects.velocity,
projects.sponge7,
projects.sponge8,
+6 -1
View File
@@ -4,5 +4,10 @@ dependencies {
compileOnly("org.purpurmc.purpur:purpur-api:1.16.5-R0.1-SNAPSHOT")
compileOnly("me.clip:placeholderapi:2.11.2")
compileOnly("com.mojang:authlib:1.5.25")
compileOnly("com.github.MilkBowl:VaultAPI:1.7")
compileOnly("com.github.MilkBowl:VaultAPI:1.7") {
exclude("org.bukkit", "bukkit")
}
compileOnly("LibsDisguises:LibsDisguises:10.0.21") {
exclude("org.spigotmc", "spigot")
}
}
@@ -1,5 +1,7 @@
package me.neznamy.tab.platforms.bukkit;
import me.neznamy.tab.platforms.bukkit.platform.BukkitPlatform;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.platform.EventListener;
import me.neznamy.tab.shared.platform.TabPlayer;
import org.bukkit.entity.Player;
@@ -10,6 +12,7 @@ import org.bukkit.event.player.PlayerChangedWorldEvent;
import org.bukkit.event.player.PlayerCommandPreprocessEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.jetbrains.annotations.NotNull;
/**
* The core for bukkit forwarding events into all enabled features
@@ -21,7 +24,7 @@ public class BukkitEventListener extends EventListener<Player> implements Listen
quit(e.getPlayer().getUniqueId());
}
@EventHandler(priority = EventPriority.LOWEST)
@EventHandler(priority = EventPriority.LOW)
public void onJoin(PlayerJoinEvent e) {
join(e.getPlayer());
}
@@ -37,7 +40,8 @@ public class BukkitEventListener extends EventListener<Player> implements Listen
}
@Override
public TabPlayer createPlayer(Player player) {
return new BukkitTabPlayer(player);
@NotNull
public TabPlayer createPlayer(@NotNull Player player) {
return new BukkitTabPlayer((BukkitPlatform) TAB.getInstance().getPlatform(), player);
}
}
}
@@ -3,17 +3,19 @@ package me.neznamy.tab.platforms.bukkit;
import com.mojang.authlib.GameProfile;
import io.netty.channel.Channel;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
import me.neznamy.tab.platforms.bukkit.scoreboard.PacketScoreboard;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.features.nametags.NameTag;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import me.neznamy.tab.platforms.bukkit.nms.NMSStorage;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.features.injection.NettyPipelineInjector;
import me.neznamy.tab.shared.features.sorting.Sorting;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.stream.Collectors;
@@ -31,6 +33,7 @@ public class BukkitPipelineInjector extends NettyPipelineInjector {
}
@Override
@Nullable
protected Channel getChannel(@NotNull TabPlayer player) {
BukkitTabPlayer bukkit = (BukkitTabPlayer) player;
NMSStorage nms = NMSStorage.getInstance();
@@ -45,8 +48,13 @@ public class BukkitPipelineInjector extends NettyPipelineInjector {
@Override
@SneakyThrows
public void onDisplayObjective(@NotNull TabPlayer player, @NotNull Object packet) {
TAB.getInstance().getFeatureManager().onDisplayObjective(player,
PacketScoreboard.DisplayObjective_POSITION.getInt(packet),
int position;
if (BukkitReflection.is1_20_2Plus()) {
position = ((Enum<?>)PacketScoreboard.DisplayObjective_POSITION.get(packet)).ordinal();
} else {
position = PacketScoreboard.DisplayObjective_POSITION.getInt(packet);
}
TAB.getInstance().getFeatureManager().onDisplayObjective(player, position,
(String) PacketScoreboard.DisplayObjective_OBJECTIVE_NAME.get(packet));
}
@@ -75,13 +83,12 @@ public class BukkitPipelineInjector extends NettyPipelineInjector {
@Override
public boolean isPlayerInfo(@NotNull Object packet) {
return BukkitTabList.PacketPlayOutPlayerInfoClass.isInstance(packet);
return BukkitTabList.PlayerInfoClass.isInstance(packet);
}
@Override
@SneakyThrows
public void onPlayerInfo(@NotNull TabPlayer receiver, @NotNull Object packet) {
NMSStorage nms = NMSStorage.getInstance();
List<String> actions;
if (BukkitTabList.ClientboundPlayerInfoRemovePacket != null) {
//1.19.3+
@@ -92,27 +99,42 @@ public class BukkitPipelineInjector extends NettyPipelineInjector {
}
List<Object> updatedList = new ArrayList<>();
for (Object nmsData : (List<?>) BukkitTabList.PLAYERS.get(packet)) {
GameProfile profile = (GameProfile) BukkitTabList.PlayerInfoData_getProfile.invoke(nmsData);
GameProfile profile = (GameProfile) BukkitTabList.PlayerInfoData_Profile.get(nmsData);
UUID id;
if (BukkitReflection.is1_19_3Plus()) {
id = (UUID) BukkitTabList.PlayerInfoData_UUID.get(nmsData);
} else {
id = profile.getId();
}
Object displayName = null;
int latency = 0;
if (actions.contains(TabList.Action.UPDATE_DISPLAY_NAME.name()) || actions.contains(TabList.Action.ADD_PLAYER.name())) {
displayName = BukkitTabList.PlayerInfoData_DisplayName.get(nmsData);
IChatBaseComponent newDisplayName = TAB.getInstance().getFeatureManager().onDisplayNameChange(receiver, profile.getId());
if (newDisplayName != null) displayName = nms.toNMSComponent(newDisplayName, receiver.getVersion());
if (!nms.is1_19_3Plus()) BukkitTabList.PlayerInfoData_DisplayName.set(nmsData, displayName);
IChatBaseComponent newDisplayName = TAB.getInstance().getFeatureManager().onDisplayNameChange(receiver, id);
if (newDisplayName != null) displayName = TAB.getInstance().getPlatform().toComponent(newDisplayName, receiver.getVersion());
if (!BukkitReflection.is1_19_3Plus()) BukkitTabList.PlayerInfoData_DisplayName.set(nmsData, displayName);
}
if (nms.is1_19_3Plus()) {
if (actions.contains(TabList.Action.UPDATE_LATENCY.name()) || actions.contains(TabList.Action.ADD_PLAYER.name())) {
latency = BukkitTabList.PlayerInfoData_Latency.getInt(nmsData);
latency = TAB.getInstance().getFeatureManager().onLatencyChange(receiver, id, latency);
if (!BukkitReflection.is1_19_3Plus()) BukkitTabList.PlayerInfoData_Latency.set(nmsData, latency);
}
if (actions.contains(TabList.Action.ADD_PLAYER.name())) {
TAB.getInstance().getFeatureManager().onEntryAdd(receiver, id, profile.getName());
}
if (BukkitReflection.is1_19_3Plus()) {
// 1.19.3 is using records, which do not allow changing final fields, need to rewrite the list entirely
updatedList.add(BukkitTabList.newPlayerInfoData.newInstance(
profile.getId(),
id,
profile,
BukkitTabList.PlayerInfoData_Listed.getBoolean(nmsData),
BukkitTabList.PlayerInfoData_Latency.getInt(nmsData),
latency,
BukkitTabList.PlayerInfoData_GameMode.get(nmsData),
displayName,
BukkitTabList.PlayerInfoData_RemoteChatSession.get(nmsData)));
}
}
if (nms.is1_19_3Plus()) {
if (BukkitReflection.is1_19_3Plus()) {
BukkitTabList.PLAYERS.set(packet, updatedList);
}
}
@@ -130,7 +152,7 @@ public class BukkitPipelineInjector extends NettyPipelineInjector {
//creating a new list to prevent NoSuchFieldException in minecraft packet encoder when a player is removed
Collection<String> newList = new ArrayList<>();
for (String entry : players) {
TabPlayer p = TAB.getInstance().getPlayer(entry);
TabPlayer p = getPlayer(entry);
if (p == null) {
newList.add(entry);
continue;
@@ -1,82 +0,0 @@
package me.neznamy.tab.platforms.bukkit;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.api.placeholder.PlaceholderManager;
import me.neznamy.tab.shared.placeholders.UniversalPlaceholderRegistry;
import net.milkbowl.vault.chat.Chat;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.plugin.RegisteredServiceProvider;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* Bukkit registry to register bukkit-only and universal placeholders
*/
public class BukkitPlaceholderRegistry extends UniversalPlaceholderRegistry {
private Chat chat;
/** NMS server to get TPS from on spigot */
private Object server;
/** TPS field*/
private Field recentTps;
/** Detection for presence of Paper's TPS getter */
private Method paperTps;
/** Detection for presence of Paper's MSPT getter */
private Method paperMspt;
/**
* Constructs new instance and loads hooks
*/
public BukkitPlaceholderRegistry() {
try {
server = Bukkit.getServer().getClass().getMethod("getServer").invoke(Bukkit.getServer());
recentTps = server.getClass().getField("recentTps");
} catch (ReflectiveOperationException e) {
//not spigot
}
try { paperTps = Bukkit.class.getMethod("getTPS"); } catch (NoSuchMethodException ignored) {}
try { paperMspt = Bukkit.class.getMethod("getAverageTickTime"); } catch (NoSuchMethodException ignored) {}
if (Bukkit.getPluginManager().isPluginEnabled("Vault")) {
RegisteredServiceProvider<Chat> rspChat = Bukkit.getServicesManager().getRegistration(Chat.class);
if (rspChat != null) chat = rspChat.getProvider();
}
}
@SuppressWarnings("deprecation")
@Override
public void registerPlaceholders(@NotNull PlaceholderManager manager) {
manager.registerPlayerPlaceholder(TabConstants.Placeholder.DISPLAY_NAME, 500, p -> ((Player) p.getPlayer()).getDisplayName());
if (paperTps != null) {
manager.registerServerPlaceholder(TabConstants.Placeholder.TPS, 1000, () -> formatTPS(Bukkit.getTPS()[0]));
} else if (recentTps != null) {
manager.registerServerPlaceholder(TabConstants.Placeholder.TPS, 1000, () -> {
try {
return formatTPS(((double[]) recentTps.get(server))[0]);
} catch (IllegalAccessException e) {
return -1;
}
});
} else {
manager.registerServerPlaceholder(TabConstants.Placeholder.TPS, -1, () -> -1);
}
if (paperMspt != null) {
manager.registerServerPlaceholder(TabConstants.Placeholder.MSPT, 1000, () -> format(Bukkit.getAverageTickTime()));
}
manager.registerPlayerPlaceholder(TabConstants.Placeholder.HEALTH, 100, p -> (int) Math.ceil(((Player) p.getPlayer()).getHealth()));
if (chat != null) {
manager.registerPlayerPlaceholder("%vault-prefix%", 1000, p -> chat.getPlayerPrefix((Player) p.getPlayer()));
manager.registerPlayerPlaceholder("%vault-suffix%", 1000, p -> chat.getPlayerSuffix((Player) p.getPlayer()));
} else {
manager.registerServerPlaceholder("%vault-prefix%", -1, () -> "");
manager.registerServerPlaceholder("%vault-suffix%", -1, () -> "");
}
super.registerPlaceholders(manager);
}
}
@@ -1,168 +0,0 @@
package me.neznamy.tab.platforms.bukkit;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.SneakyThrows;
import me.clip.placeholderapi.PlaceholderAPI;
import me.neznamy.tab.shared.GroupManager;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.chat.rgb.RGBUtils;
import me.neznamy.tab.shared.features.injection.PipelineInjector;
import me.neznamy.tab.shared.features.types.TabFeature;
import me.neznamy.tab.platforms.bukkit.features.BukkitTabExpansion;
import me.neznamy.tab.platforms.bukkit.features.PerWorldPlayerList;
import me.neznamy.tab.platforms.bukkit.features.WitherBossBar;
import me.neznamy.tab.platforms.bukkit.features.BukkitNameTagX;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.backend.BackendPlatform;
import me.neznamy.tab.shared.features.PlaceholderManagerImpl;
import me.neznamy.tab.shared.features.bossbar.BossBarManagerImpl;
import me.neznamy.tab.shared.features.nametags.NameTag;
import me.neznamy.tab.shared.hook.LuckPermsHook;
import me.neznamy.tab.shared.placeholders.PlayerPlaceholderImpl;
import me.neznamy.tab.shared.placeholders.expansion.EmptyTabExpansion;
import me.neznamy.tab.shared.placeholders.expansion.TabExpansion;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.milkbowl.vault.permission.Permission;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.plugin.RegisteredServiceProvider;
import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
/**
* Implementation of Platform interface for Bukkit platform
*/
@RequiredArgsConstructor
@Getter
public class BukkitPlatform implements BackendPlatform {
/** Plugin instance for registering tasks and events */
private final JavaPlugin plugin;
/** Variables checking presence of other plugins to hook into */
private final boolean placeholderAPI = ReflectionUtils.classExists("me.clip.placeholderapi.PlaceholderAPI");
@Setter private boolean libsDisguisesEnabled = ReflectionUtils.classExists("me.libraryaddict.disguise.DisguiseAPI");
public @NotNull BossBarManagerImpl getLegacyBossBar() {
return new WitherBossBar(plugin);
}
@Override
public void loadPlayers() {
for (Player p : getOnlinePlayers()) {
TAB.getInstance().addPlayer(new BukkitTabPlayer(p));
}
}
@Override
public void registerPlaceholders() {
new BukkitPlaceholderRegistry().registerPlaceholders(TAB.getInstance().getPlaceholderManager());
}
@Override
public @Nullable PipelineInjector createPipelineInjector() {
return NMSStorage.getInstance().getMinorVersion() >= 8 ? new BukkitPipelineInjector() : null;
}
@Override
public @NotNull NameTag getUnlimitedNameTags() {
return new BukkitNameTagX(plugin);
}
@Override
public @NotNull TabExpansion createTabExpansion() {
if (placeholderAPI) {
BukkitTabExpansion expansion = new BukkitTabExpansion();
expansion.register();
return expansion;
}
return new EmptyTabExpansion();
}
@Override
public @Nullable TabFeature getPerWorldPlayerList() {
return new PerWorldPlayerList(plugin);
}
/**
* Returns online players from Bukkit API
*
* @return online players from Bukkit API
*/
@SuppressWarnings("unchecked")
@SneakyThrows
private @NotNull Player[] getOnlinePlayers() {
Object players = Bukkit.class.getMethod("getOnlinePlayers").invoke(null);
if (players instanceof Player[]) {
//1.7-
return (Player[]) players;
} else {
//1.8+
return ((Collection<Player>)players).toArray(new Player[0]);
}
}
@Override
public void registerUnknownPlaceholder(@NotNull String identifier) {
PlaceholderManagerImpl pl = TAB.getInstance().getPlaceholderManager();
int refresh = pl.getRefreshInterval(identifier);
if (identifier.startsWith("%rel_")) {
//relational placeholder
TAB.getInstance().getPlaceholderManager().registerRelationalPlaceholder(identifier, pl.getRefreshInterval(identifier), (viewer, target) ->
placeholderAPI ? PlaceholderAPI.setRelationalPlaceholders((Player) viewer.getPlayer(), (Player) target.getPlayer(), identifier) : identifier);
} else if (identifier.startsWith("%sync:")) {
String syncedPlaceholder = "%" + identifier.substring(6, identifier.length()-1) + "%";
PlayerPlaceholderImpl[] ppl = new PlayerPlaceholderImpl[1];
ppl[0] = pl.registerPlayerPlaceholder(identifier, refresh, p -> {
try {
Bukkit.getScheduler().runTask(plugin, () -> {
long time = System.nanoTime();
ppl[0].updateValue(p, placeholderAPI ? PlaceholderAPI.setPlaceholders((Player) p.getPlayer(), syncedPlaceholder) : identifier);
TAB.getInstance().getCPUManager().addPlaceholderTime(identifier, System.nanoTime()-time);
});
return null;
} catch (UnsupportedOperationException e) {
return "<Folia does not support sync placeholders>";
}
});
} else if (identifier.startsWith("%server_")) {
TAB.getInstance().getPlaceholderManager().registerServerPlaceholder(identifier, refresh, () ->
placeholderAPI ? PlaceholderAPI.setPlaceholders(null, identifier) : identifier);
} else {
TAB.getInstance().getPlaceholderManager().registerPlayerPlaceholder(identifier, refresh, p ->
placeholderAPI ? PlaceholderAPI.setPlaceholders((Player) p.getPlayer(), identifier) : identifier);
}
}
@Override
public void sendConsoleMessage(@NotNull IChatBaseComponent message) {
Bukkit.getConsoleSender().sendMessage("[TAB] " + RGBUtils.getInstance().convertToBukkitFormat(message.toFlatText(),
TAB.getInstance().getServerVersion().getMinorVersion() >= 16));
}
@Override
public String getServerVersionInfo() {
return "[Bukkit] " + Bukkit.getName() + " - " + Bukkit.getBukkitVersion().split("-")[0] + " (" + NMSStorage.getInstance().getServerPackage() + ")";
}
@Override
@NotNull public GroupManager detectPermissionPlugin() {
if (LuckPermsHook.getInstance().isInstalled()) {
return new GroupManager("LuckPerms", LuckPermsHook.getInstance().getGroupFunction());
}
if (Bukkit.getPluginManager().isPluginEnabled("Vault")) {
RegisteredServiceProvider<Permission> provider = Bukkit.getServicesManager().getRegistration(Permission.class);
if (provider != null && !provider.getProvider().getName().equals("SuperPerms")) {
return new GroupManager(provider.getProvider().getName(), p -> provider.getProvider().getPrimaryGroup((Player) p.getPlayer()));
}
}
return new GroupManager("None", p -> TabConstants.NO_GROUP);
}
}
@@ -1,52 +1,28 @@
package me.neznamy.tab.platforms.bukkit;
import me.neznamy.tab.platforms.bukkit.nms.NMSStorage;
import me.neznamy.tab.platforms.bukkit.platform.BukkitPlatform;
import me.neznamy.tab.platforms.bukkit.platform.FoliaPlatform;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.*;
import me.neznamy.tab.shared.TAB;
import org.bstats.bukkit.Metrics;
import org.bstats.charts.SimplePie;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.bukkit.Bukkit;
import org.bukkit.command.*;
import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Callable;
/**
* Main class for Bukkit platform
* Main class for Bukkit.
*/
public class BukkitTAB extends JavaPlugin {
@Override
public void onEnable() {
String version = Bukkit.getBukkitVersion().split("-")[0];
String serverPackage = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3];
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.color("[TAB] Server version: " + version + " (" + serverPackage + ")"));
if (!isVersionSupported()) {
Bukkit.getPluginManager().disablePlugin(this);
return;
}
BukkitPlatform platform = new BukkitPlatform(this);
TAB.setInstance(new TAB(platform, ProtocolVersion.fromFriendlyName(version), getDataFolder()));
if (TAB.getInstance().getServerVersion() == ProtocolVersion.UNKNOWN_SERVER_VERSION) {
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.color("&c[TAB] Unknown server version: " + Bukkit.getBukkitVersion() + "! Plugin may not work correctly."));
}
Bukkit.getPluginManager().registerEvents(new BukkitEventListener(), this);
TAB.getInstance().load();
Metrics metrics = new Metrics(this, 5304);
metrics.addCustomChart(new SimplePie(TabConstants.MetricsChart.UNLIMITED_NAME_TAG_MODE_ENABLED, () -> TAB.getInstance().getFeatureManager().isFeatureEnabled(TabConstants.Feature.UNLIMITED_NAME_TAGS) ? "Yes" : "No"));
metrics.addCustomChart(new SimplePie(TabConstants.MetricsChart.PLACEHOLDER_API, () -> platform.isPlaceholderAPI() ? "Yes" : "No"));
metrics.addCustomChart(new SimplePie(TabConstants.MetricsChart.PERMISSION_SYSTEM, () -> TAB.getInstance().getGroupManager().getPermissionPlugin()));
metrics.addCustomChart(new SimplePie(TabConstants.MetricsChart.SERVER_VERSION, () -> "1." + TAB.getInstance().getServerVersion().getMinorVersion() + ".x"));
PluginCommand cmd = Bukkit.getPluginCommand(TabConstants.COMMAND_BACKEND);
if (cmd == null) return;
BukkitTabCommand command = new BukkitTabCommand();
cmd.setExecutor(command);
cmd.setTabCompleter(command);
TAB.create(ReflectionUtils.classExists("io.papermc.paper.threadedregions.RegionizedServer") ?
new FoliaPlatform(this) : new BukkitPlatform(this));
}
@Override
@@ -64,11 +40,9 @@ public class BukkitTAB extends JavaPlugin {
*/
private boolean isVersionSupported() {
try {
long time = System.currentTimeMillis();
NMSStorage.setInstance(getNMSLoader());
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.color("[TAB] Loaded NMS hook in " + (System.currentTimeMillis()-time) + "ms"));
NMSStorage.setInstance(new NMSStorage());
return true;
} catch (IllegalStateException ex) {
} catch (Exception ex) {
if (ProtocolVersion.fromFriendlyName(Bukkit.getBukkitVersion().split("-")[0]) == ProtocolVersion.UNKNOWN_SERVER_VERSION) {
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.color("&c[TAB] Your server version is not compatible. " +
"This plugin version was made for " + ProtocolVersion.values()[ProtocolVersion.values().length-1].getFriendlyName() +
@@ -80,19 +54,4 @@ public class BukkitTAB extends JavaPlugin {
return false;
}
}
private @NotNull NMSStorage getNMSLoader() {
List<Callable<NMSStorage>> loaders = Arrays.asList(
BukkitLegacyNMSStorage::new,
BukkitModernNMSStorage::new,
MojangModernNMSStorage::new,
ThermosNMSStorage::new
);
for (Callable<NMSStorage> loader : loaders) {
try {
return loader.call();
} catch (Exception ignored) {}
}
throw new IllegalStateException("Unsupported server version");
}
}
@@ -11,7 +11,7 @@ import org.bukkit.command.TabCompleter;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
@@ -20,7 +20,7 @@ import java.util.List;
public class BukkitTabCommand implements CommandExecutor, TabCompleter {
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] args) {
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if (TAB.getInstance().isPluginDisabled()) {
for (String message : TAB.getInstance().getDisabledCommand().execute(args, sender.hasPermission(TabConstants.Permission.COMMAND_RELOAD), sender.hasPermission(TabConstants.Permission.COMMAND_ALL))) {
sender.sendMessage(EnumChatFormat.color(message));
@@ -37,11 +37,12 @@ public class BukkitTabCommand implements CommandExecutor, TabCompleter {
}
@Override
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, String[] args) {
@NotNull
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, @NotNull String[] args) {
TabPlayer p = null;
if (sender instanceof Player) {
p = TAB.getInstance().getPlayer(((Player)sender).getUniqueId());
if (p == null) return new ArrayList<>(); //player not loaded correctly
if (p == null) return Collections.emptyList(); //player not loaded correctly
}
return TAB.getInstance().getCommand().complete(p, args);
}
@@ -2,12 +2,13 @@ package me.neznamy.tab.platforms.bukkit;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.TabList.Entry.Builder;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -15,7 +16,6 @@ import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
/**
@@ -33,169 +33,224 @@ import java.util.*;
public class BukkitTabList implements TabList {
// NMS Fields
public static Class<?> PacketPlayOutPlayerListHeaderFooterClass;
public static Constructor<?> newPacketPlayOutPlayerListHeaderFooter;
public static Field HEADER;
public static Field FOOTER;
private static Class<?> HeaderFooterClass;
private static Constructor<?> newHeaderFooter;
private static Field HEADER;
private static Field FOOTER;
public static Class<?> PacketPlayOutPlayerInfoClass;
public static Constructor<?> newPacketPlayOutPlayerInfo;
public static Class<?> PlayerInfoClass;
private static Constructor<?> newPlayerInfo;
public static Field ACTION;
public static Field PLAYERS;
public static Class<Enum> EnumPlayerInfoActionClass;
public static Class<Enum> EnumGamemodeClass;
private static Class<Enum> ActionClass;
public static Class<?> ClientboundPlayerInfoRemovePacket;
public static Class<?> RemoteChatSession$Data;
public static Constructor<?> newClientboundPlayerInfoRemovePacket;
private static Class<?> RemoteChatSession$Data;
private static Constructor<?> newClientboundPlayerInfoRemovePacket;
private static Class<?> EntityPlayer;
public static Class<?> PlayerInfoDataClass;
public static Constructor<?> newPlayerInfoData;
public static Method PlayerInfoData_getProfile;
public static Field PlayerInfoData_UUID;
public static Field PlayerInfoData_Profile;
public static Field PlayerInfoData_Latency;
public static Field PlayerInfoData_GameMode;
public static Field PlayerInfoData_DisplayName;
public static Field PlayerInfoData_Listed;
public static Field PlayerInfoData_RemoteChatSession;
private static Object[] gameModes;
@Getter
private static boolean available;
/** Player this TabList belongs to */
private final BukkitTabPlayer player;
public static void load(NMSStorage nms) throws NoSuchMethodException {
if (nms.getMinorVersion() >= 17) {
newPacketPlayOutPlayerListHeaderFooter = PacketPlayOutPlayerListHeaderFooterClass.getConstructor(nms.IChatBaseComponent, nms.IChatBaseComponent);
public static void load() throws NoSuchMethodException, ClassNotFoundException {
if (BukkitReflection.getMinorVersion() < 8) return; // Not supported (yet?)
// Classes
Class<?> playerInfoDataClass;
Class<?> IChatBaseComponent;
Class<Enum> EnumGamemodeClass;
if (BukkitReflection.isMojangMapped()) {
IChatBaseComponent = Class.forName("net.minecraft.network.chat.Component");
EntityPlayer = Class.forName("net.minecraft.server.level.ServerPlayer");
EnumGamemodeClass = (Class<Enum>) Class.forName("net.minecraft.world.level.GameType");
HeaderFooterClass = Class.forName("net.minecraft.network.protocol.game.ClientboundTabListPacket");
if (BukkitReflection.is1_19_3Plus()) {
ClientboundPlayerInfoRemovePacket = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket");
PlayerInfoClass = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket");
ActionClass = (Class<Enum>) Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket$Action");
playerInfoDataClass = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket$Entry");
RemoteChatSession$Data = Class.forName("net.minecraft.network.chat.RemoteChatSession$Data");
} else {
PlayerInfoClass = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket");
ActionClass = (Class<Enum>) Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket$Action");
playerInfoDataClass = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket$PlayerUpdate");
}
} else if (BukkitReflection.getMinorVersion() >= 17) {
IChatBaseComponent = Class.forName("net.minecraft.network.chat.IChatBaseComponent");
EntityPlayer = Class.forName("net.minecraft.server.level.EntityPlayer");
EnumGamemodeClass = (Class<Enum>) Class.forName("net.minecraft.world.level.EnumGamemode");
HeaderFooterClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutPlayerListHeaderFooter");
if (BukkitReflection.is1_19_3Plus()) {
ClientboundPlayerInfoRemovePacket = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket");
PlayerInfoClass = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket");
ActionClass = (Class<Enum>) Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket$a");
playerInfoDataClass = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket$b");
RemoteChatSession$Data = Class.forName("net.minecraft.network.chat.RemoteChatSession$a");
} else {
PlayerInfoClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutPlayerInfo");
ActionClass = (Class<Enum>) Class.forName("net.minecraft.network.protocol.game.PacketPlayOutPlayerInfo$EnumPlayerInfoAction");
playerInfoDataClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutPlayerInfo$PlayerInfoData");
}
} else {
newPacketPlayOutPlayerListHeaderFooter = PacketPlayOutPlayerListHeaderFooterClass.getConstructor();
HEADER = ReflectionUtils.getFields(PacketPlayOutPlayerListHeaderFooterClass, nms.IChatBaseComponent).get(0);
FOOTER = ReflectionUtils.getFields(PacketPlayOutPlayerListHeaderFooterClass, nms.IChatBaseComponent).get(1);
IChatBaseComponent = BukkitReflection.getLegacyClass("IChatBaseComponent");
EntityPlayer = BukkitReflection.getLegacyClass("EntityPlayer");
HeaderFooterClass = BukkitReflection.getLegacyClass("PacketPlayOutPlayerListHeaderFooter");
PlayerInfoClass = BukkitReflection.getLegacyClass("PacketPlayOutPlayerInfo");
ActionClass = (Class<Enum>) BukkitReflection.getLegacyClass("PacketPlayOutPlayerInfo$EnumPlayerInfoAction", "EnumPlayerInfoAction");
playerInfoDataClass = BukkitReflection.getLegacyClass("PacketPlayOutPlayerInfo$PlayerInfoData", "PlayerInfoData");
EnumGamemodeClass = (Class<Enum>) BukkitReflection.getLegacyClass("EnumGamemode", "WorldSettings$EnumGamemode");
}
if (nms.is1_19_3Plus()) {
// Header & Footer
if (BukkitReflection.getMinorVersion() >= 17) {
newHeaderFooter = HeaderFooterClass.getConstructor(IChatBaseComponent, IChatBaseComponent);
} else {
newHeaderFooter = HeaderFooterClass.getConstructor();
HEADER = ReflectionUtils.getFields(HeaderFooterClass, IChatBaseComponent).get(0);
FOOTER = ReflectionUtils.getFields(HeaderFooterClass, IChatBaseComponent).get(1);
}
// Info packet
PLAYERS = ReflectionUtils.getOnlyField(PlayerInfoClass, List.class);
newPlayerInfoData = ReflectionUtils.getOnlyConstructor(playerInfoDataClass);
PlayerInfoData_Profile = ReflectionUtils.getOnlyField(playerInfoDataClass, GameProfile.class);
PlayerInfoData_Latency = ReflectionUtils.getOnlyField(playerInfoDataClass, int.class);
PlayerInfoData_GameMode = ReflectionUtils.getOnlyField(playerInfoDataClass, EnumGamemodeClass);
PlayerInfoData_DisplayName = ReflectionUtils.getOnlyField(playerInfoDataClass, IChatBaseComponent);
if (BukkitReflection.is1_19_3Plus()) {
newClientboundPlayerInfoRemovePacket = ClientboundPlayerInfoRemovePacket.getConstructor(List.class);
newPacketPlayOutPlayerInfo = PacketPlayOutPlayerInfoClass.getConstructor(EnumSet.class, Collection.class);
ACTION = ReflectionUtils.getOnlyField(PacketPlayOutPlayerInfoClass, EnumSet.class);
newPlayerInfo = PlayerInfoClass.getConstructor(EnumSet.class, Collection.class);
ACTION = ReflectionUtils.getOnlyField(PlayerInfoClass, EnumSet.class);
PlayerInfoData_Listed = ReflectionUtils.getOnlyField(playerInfoDataClass, boolean.class);
PlayerInfoData_RemoteChatSession = ReflectionUtils.getOnlyField(playerInfoDataClass, RemoteChatSession$Data);
PlayerInfoData_UUID = ReflectionUtils.getOnlyField(playerInfoDataClass, UUID.class);
} else {
newPacketPlayOutPlayerInfo = PacketPlayOutPlayerInfoClass.getConstructor(EnumPlayerInfoActionClass, Array.newInstance(nms.EntityPlayer, 0).getClass());
ACTION = ReflectionUtils.getOnlyField(PacketPlayOutPlayerInfoClass, EnumPlayerInfoActionClass);
}
PLAYERS = ReflectionUtils.getOnlyField(PacketPlayOutPlayerInfoClass, List.class);
newPlayerInfoData = ReflectionUtils.getOnlyConstructor(PlayerInfoDataClass);
PlayerInfoData_getProfile = ReflectionUtils.getOnlyMethod(PlayerInfoDataClass, GameProfile.class);
PlayerInfoData_Latency = ReflectionUtils.getOnlyField(PlayerInfoDataClass, int.class);
PlayerInfoData_GameMode = ReflectionUtils.getOnlyField(PlayerInfoDataClass, EnumGamemodeClass);
PlayerInfoData_DisplayName = ReflectionUtils.getOnlyField(PlayerInfoDataClass, nms.IChatBaseComponent);
if (nms.is1_19_3Plus()) {
PlayerInfoData_Listed = ReflectionUtils.getOnlyField(PlayerInfoDataClass, boolean.class);
PlayerInfoData_RemoteChatSession = ReflectionUtils.getOnlyField(PlayerInfoDataClass, RemoteChatSession$Data);
newPlayerInfo = PlayerInfoClass.getConstructor(ActionClass, Array.newInstance(EntityPlayer, 0).getClass());
ACTION = ReflectionUtils.getOnlyField(PlayerInfoClass, ActionClass);
}
gameModes = new Object[] {
Enum.valueOf(EnumGamemodeClass, "SURVIVAL"),
Enum.valueOf(EnumGamemodeClass, "CREATIVE"),
Enum.valueOf(EnumGamemodeClass, "ADVENTURE"),
Enum.valueOf(EnumGamemodeClass, "SPECTATOR")
};
available = true;
}
@Override
@SneakyThrows
public void removeEntry(@NotNull UUID entry) {
if (!available) return;
if (ClientboundPlayerInfoRemovePacket != null) {
//1.19.3+
player.sendPacket(newClientboundPlayerInfoRemovePacket.newInstance(Collections.singletonList(entry)));
} else {
//1.19.2-
player.sendPacket(createPacket(Action.REMOVE_PLAYER, new Entry.Builder(entry).build(), player.getVersion()));
player.sendPacket(createPacket(Action.REMOVE_PLAYER, new Builder(entry).build()));
}
}
@Override
public void updateDisplayName(@NotNull UUID entry, @Nullable IChatBaseComponent displayName) {
player.sendPacket(createPacket(Action.UPDATE_DISPLAY_NAME,
new Entry.Builder(entry).displayName(displayName).build(), player.getVersion())
);
if (!available) return;
player.sendPacket(createPacket(Action.UPDATE_DISPLAY_NAME, new Builder(entry).displayName(displayName).build()));
}
@Override
public void updateLatency(@NotNull UUID entry, int latency) {
player.sendPacket(createPacket(Action.UPDATE_LATENCY,
new Entry.Builder(entry).latency(latency).build(), player.getVersion())
);
if (!available) return;
player.sendPacket(createPacket(Action.UPDATE_LATENCY, new Builder(entry).latency(latency).build()));
}
@Override
public void updateGameMode(@NotNull UUID entry, int gameMode) {
player.sendPacket(createPacket(Action.UPDATE_GAME_MODE,
new Entry.Builder(entry).gameMode(gameMode).build(), player.getVersion())
);
if (!available) return;
player.sendPacket(createPacket(Action.UPDATE_GAME_MODE, new Builder(entry).gameMode(gameMode).build()));
}
@Override
public void addEntry(@NotNull Entry entry) {
player.sendPacket(createPacket(Action.ADD_PLAYER, entry, player.getVersion()));
if (!available) return;
player.sendPacket(createPacket(Action.ADD_PLAYER, entry));
}
@Override
@SneakyThrows
public void setPlayerListHeaderFooter(@NotNull IChatBaseComponent header, @NotNull IChatBaseComponent footer) {
if (PacketPlayOutPlayerListHeaderFooterClass == null) return;
NMSStorage nms = NMSStorage.getInstance();
Object packet;
if (nms.getMinorVersion() >= 17) {
packet = newPacketPlayOutPlayerListHeaderFooter.newInstance(
nms.toNMSComponent(header, player.getVersion()), nms.toNMSComponent(footer, player.getVersion()));
if (HeaderFooterClass == null) return;
if (BukkitReflection.getMinorVersion() >= 17) {
player.sendPacket(newHeaderFooter.newInstance(toComponent(header), toComponent(footer)));
} else {
packet = newPacketPlayOutPlayerListHeaderFooter.newInstance();
HEADER.set(packet, nms.toNMSComponent(header, player.getVersion()));
FOOTER.set(packet, nms.toNMSComponent(footer, player.getVersion()));
Object packet = newHeaderFooter.newInstance();
HEADER.set(packet, toComponent(header));
FOOTER.set(packet, toComponent(footer));
player.sendPacket(packet);
}
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;
@NotNull
private Object createPacket(@NotNull Action action, @NotNull Entry entry) {
Object packet;
List<Object> players = new ArrayList<>();
if (NMSStorage.getInstance().is1_19_3Plus()) {
if (BukkitReflection.is1_19_3Plus()) {
EnumSet<?> actions;
if (action == TabList.Action.ADD_PLAYER) {
actions = EnumSet.allOf(EnumPlayerInfoActionClass);
if (action == Action.ADD_PLAYER) {
actions = EnumSet.allOf(ActionClass);
} else {
actions = EnumSet.of(Enum.valueOf(EnumPlayerInfoActionClass, action.name()));
actions = EnumSet.of(Enum.valueOf(ActionClass, action.name()));
}
packet = newPacketPlayOutPlayerInfo.newInstance(actions, Collections.emptyList());
GameProfile profile = new GameProfile(entry.getUniqueId(), entry.getName());
if (entry.getSkin() != null) profile.getProperties().put(TabList.TEXTURES_PROPERTY,
new Property(TabList.TEXTURES_PROPERTY, entry.getSkin().getValue(), entry.getSkin().getSignature()));
packet = newPlayerInfo.newInstance(actions, Collections.emptyList());
players.add(newPlayerInfoData.newInstance(
entry.getUniqueId(),
profile,
createProfile(entry.getUniqueId(), entry.getName(), entry.getSkin()),
true,
entry.getLatency(),
int2GameMode(entry.getGameMode()),
entry.getDisplayName() == null ? null : nms.toNMSComponent(entry.getDisplayName(), clientVersion),
gameModes[entry.getGameMode()],
entry.getDisplayName() == null ? null : toComponent(entry.getDisplayName()),
null
));
} else {
packet = newPacketPlayOutPlayerInfo.newInstance(Enum.valueOf(EnumPlayerInfoActionClass, action.name()),
Array.newInstance(NMSStorage.getInstance().EntityPlayer, 0));
GameProfile profile = new GameProfile(entry.getUniqueId(), entry.getName());
if (entry.getSkin() != null) profile.getProperties().put(TabList.TEXTURES_PROPERTY,
new Property(TabList.TEXTURES_PROPERTY, entry.getSkin().getValue(), entry.getSkin().getSignature()));
packet = newPlayerInfo.newInstance(Enum.valueOf(ActionClass, action.name()), Array.newInstance(EntityPlayer, 0));
List<Object> parameters = new ArrayList<>();
if (newPlayerInfoData.getParameterTypes()[0] == PacketPlayOutPlayerInfoClass) {
if (newPlayerInfoData.getParameterTypes()[0] == PlayerInfoClass) {
parameters.add(packet);
}
parameters.add(profile);
parameters.add(createProfile(entry.getUniqueId(), entry.getName(), entry.getSkin()));
parameters.add(entry.getLatency());
parameters.add(int2GameMode(entry.getGameMode()));
parameters.add(entry.getDisplayName() == null ? null : nms.toNMSComponent(entry.getDisplayName(), clientVersion));
if (nms.getMinorVersion() >= 19) parameters.add(null);
parameters.add(gameModes[entry.getGameMode()]);
parameters.add(entry.getDisplayName() == null ? null : toComponent(entry.getDisplayName()));
if (BukkitReflection.getMinorVersion() >= 19) parameters.add(null);
players.add(newPlayerInfoData.newInstance(parameters.toArray()));
}
PLAYERS.set(packet, players);
return packet;
}
private Object int2GameMode(int gameMode) {
switch (gameMode) {
case 1: return Enum.valueOf(EnumGamemodeClass, "CREATIVE");
case 2: return Enum.valueOf(EnumGamemodeClass, "ADVENTURE");
case 3: return Enum.valueOf(EnumGamemodeClass, "SPECTATOR");
default: return Enum.valueOf(EnumGamemodeClass, "SURVIVAL");
private Object toComponent(IChatBaseComponent component) {
return player.getPlatform().toComponent(component, player.getVersion());
}
@NotNull
private GameProfile createProfile(@NotNull UUID id, @Nullable String name, @Nullable Skin skin) {
GameProfile profile = new GameProfile(id, name == null ? "" : name);
if (skin != null) {
profile.getProperties().put(TabList.TEXTURES_PROPERTY,
new Property(TabList.TEXTURES_PROPERTY, skin.getValue(), skin.getSignature()));
}
return profile;
}
}
@@ -4,24 +4,22 @@ import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
import lombok.Getter;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.hook.LibsDisguisesHook;
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
import me.neznamy.tab.platforms.bukkit.nms.PacketEntityView;
import me.neznamy.tab.platforms.bukkit.platform.BukkitPlatform;
import me.neznamy.tab.platforms.bukkit.scoreboard.PacketScoreboard;
import me.neznamy.tab.shared.chat.rgb.RGBUtils;
import me.neznamy.tab.shared.hook.ViaVersionHook;
import me.neznamy.tab.shared.backend.entityview.EntityView;
import me.neznamy.tab.shared.platform.bossbar.BossBar;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.platforms.bukkit.bossbar.EntityBossBar;
import me.neznamy.tab.platforms.bukkit.bossbar.BukkitBossBar;
import me.neznamy.tab.platforms.bukkit.bossbar.ViaBossBar;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.*;
import me.neznamy.tab.platforms.bukkit.nms.NMSStorage;
import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.backend.BackendTabPlayer;
import me.neznamy.tab.shared.backend.EntityData;
import me.neznamy.tab.shared.backend.Location;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.metadata.MetadataValue;
import org.bukkit.potion.PotionEffectType;
@@ -29,7 +27,6 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.UUID;
/**
* TabPlayer implementation for Bukkit platform
@@ -39,26 +36,37 @@ import java.util.UUID;
public class BukkitTabPlayer extends BackendTabPlayer {
/** Player's NMS handle (EntityPlayer), preloading for speed */
@NotNull
private final Object handle;
/** Player's connection for sending packets, preloading for speed */
@NotNull
private final Object playerConnection;
@NotNull
private final Scoreboard<BukkitTabPlayer> scoreboard = new PacketScoreboard(this);
@NotNull
private final TabList tabList = new BukkitTabList(this);
@NotNull
private final BossBar bossBar = TAB.getInstance().getServerVersion().getMinorVersion() >= 9 ?
new BukkitBossBar(this) : getVersion().getMinorVersion() >= 9 ? new ViaBossBar(this) : new EntityBossBar(this);
@NotNull
private final EntityView entityView = new PacketEntityView(this);
/**
* Constructs new instance with given bukkit player and protocol version
* Constructs new instance with given bukkit player
*
* @param platform
* Server platform
* @param p
* bukkit player
*/
@SneakyThrows
public BukkitTabPlayer(Player p) {
super(p, p.getUniqueId(), p.getName(), TAB.getInstance().getConfiguration().getServerName(),
p.getWorld().getName(), ViaVersionHook.getInstance().getPlayerVersion(p.getUniqueId(), p.getName()));
public BukkitTabPlayer(@NotNull BukkitPlatform platform, @NotNull Player p) {
super(platform, p, p.getUniqueId(), p.getName(), p.getWorld().getName());
handle = NMSStorage.getInstance().getHandle.invoke(player);
playerConnection = NMSStorage.getInstance().PLAYER_CONNECTION.get(handle);
}
@@ -69,27 +77,23 @@ public class BukkitTabPlayer extends BackendTabPlayer {
}
@Override
@SneakyThrows
public int getPing() {
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 17) {
if (BukkitReflection.getMinorVersion() >= 17) {
return getPlayer().getPing();
}
try {
return NMSStorage.getInstance().PING.getInt(handle);
} catch (IllegalAccessException e) {
return -1;
}
return NMSStorage.getInstance().PING.getInt(handle);
}
@SneakyThrows
public void sendPacket(@Nullable Object nmsPacket) {
if (nmsPacket == null || !getPlayer().isOnline()) return;
public void sendPacket(@NotNull Object nmsPacket) {
if (!getPlayer().isOnline()) return;
NMSStorage.getInstance().sendPacket.invoke(playerConnection, nmsPacket);
}
@Override
public void sendMessage(@NotNull IChatBaseComponent message) {
getPlayer().sendMessage(RGBUtils.getInstance().convertToBukkitFormat(message.toFlatText(),
getVersion().getMinorVersion() >= 16 && TAB.getInstance().getServerVersion().getMinorVersion() >= 16));
getPlayer().sendMessage(BukkitUtils.toBukkitFormat(message, getVersion().getMinorVersion() >= 16));
}
@Override
@@ -99,28 +103,29 @@ public class BukkitTabPlayer extends BackendTabPlayer {
@Override
public boolean isDisguised() {
try {
if (!((BukkitPlatform)TAB.getInstance().getPlatform()).isLibsDisguisesEnabled()) return false;
return (boolean) Class.forName("me.libraryaddict.disguise.DisguiseAPI").getMethod("isDisguised", Entity.class).invoke(null, getPlayer());
} catch (LinkageError | ReflectiveOperationException e) {
//java.lang.NoClassDefFoundError: Could not initialize class me.libraryaddict.disguise.DisguiseAPI
TAB.getInstance().getErrorManager().printError("Failed to check disguise status using LibsDisguises", e);
((BukkitPlatform)TAB.getInstance().getPlatform()).setLibsDisguisesEnabled(false);
return false;
}
return LibsDisguisesHook.isDisguised(this);
}
@Override
@SneakyThrows
@Nullable
public TabList.Skin getSkin() {
Collection<Property> col = ((GameProfile)NMSStorage.getInstance().getProfile.invoke(handle)).getProperties().get(TabList.TEXTURES_PROPERTY);
if (col.isEmpty()) return null; //offline mode
Property property = col.iterator().next();
return new TabList.Skin(property.getValue(), property.getSignature());
if (BukkitReflection.is1_20_2Plus()) {
return new TabList.Skin(
(String) property.getClass().getMethod("value").invoke(property),
(String) property.getClass().getMethod("signature").invoke(property)
);
} else {
return new TabList.Skin(property.getValue(), property.getSignature());
}
}
@Override
public @NotNull Player getPlayer() {
@NotNull
public Player getPlayer() {
return (Player) player;
}
@@ -129,6 +134,11 @@ public class BukkitTabPlayer extends BackendTabPlayer {
return getPlayer().isOnline();
}
@Override
public BukkitPlatform getPlatform() {
return (BukkitPlatform) platform;
}
@Override
public boolean isVanished() {
return getPlayer().getMetadata("vanished").stream().anyMatch(MetadataValue::asBoolean);
@@ -140,39 +150,13 @@ public class BukkitTabPlayer extends BackendTabPlayer {
}
@Override
public void spawnEntity(int entityId, @NotNull UUID id, @NotNull Object entityType, @NotNull Location location, @NotNull EntityData data) {
sendPacket(PacketPlayOutSpawnEntityLivingStorage.build(entityId, id, entityType, location, data));
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 15) {
updateEntityMetadata(entityId, data);
}
public double getHealth() {
return getPlayer().getHealth();
}
@Override
@SneakyThrows
public void updateEntityMetadata(int entityId, @NotNull EntityData data) {
if (PacketPlayOutEntityMetadataStorage.CONSTRUCTOR.getParameterCount() == 2) {
//1.19.3+
sendPacket(PacketPlayOutEntityMetadataStorage.CONSTRUCTOR.newInstance(entityId, DataWatcher.packDirty.invoke(data.build())));
} else {
sendPacket(PacketPlayOutEntityMetadataStorage.CONSTRUCTOR.newInstance(entityId, data.build(), true));
}
}
@Override
public void teleportEntity(int entityId, @NotNull Location location) {
sendPacket(PacketPlayOutEntityTeleportStorage.build(entityId, location));
}
@Override
@SneakyThrows
public void destroyEntities(int... entities) {
if (PacketPlayOutEntityDestroyStorage.CONSTRUCTOR.getParameterTypes()[0] != int.class) {
sendPacket(PacketPlayOutEntityDestroyStorage.CONSTRUCTOR.newInstance(new Object[]{entities}));
} else {
//1.17.0 Mojank
for (int entity : entities) {
sendPacket(PacketPlayOutEntityDestroyStorage.CONSTRUCTOR.newInstance(entity));
}
}
@NotNull
public String getDisplayName() {
return getPlayer().getDisplayName();
}
}
@@ -0,0 +1,76 @@
package me.neznamy.tab.platforms.bukkit;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
/**
* Class containing static util methods used on Bukkit.
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class BukkitUtils {
/**
* Returns online players from Bukkit API. This requires reflection, as return type changed in 1.8,
* and we want to avoid errors.
*
* @return Online players from Bukkit API.
*/
@SuppressWarnings("unchecked")
@SneakyThrows
@NotNull
public static Player[] getOnlinePlayers() {
Object players = Bukkit.class.getMethod("getOnlinePlayers").invoke(null);
if (players instanceof Player[]) {
//1.7-
return (Player[]) players;
} else {
//1.8+
return ((Collection<Player>)players).toArray(new Player[0]);
}
}
/**
* Converts component to legacy string using bukkit RGB format if supported by both server and client.
* If not, closest legacy color is used instead.
*
* @param component
* Component to convert
* @param rgbClient
* Whether client accepts RGB colors or not.
* @return Converted string using bukkit color format
*/
@NotNull
public static String toBukkitFormat(@NotNull IChatBaseComponent component, boolean rgbClient) {
StringBuilder sb = new StringBuilder();
if (component.getModifier().getColor() != null) {
if (BukkitReflection.getMinorVersion() >= 16 && rgbClient) {
String hexCode = component.getModifier().getColor().getHexCode();
char c = EnumChatFormat.COLOR_CHAR;
sb.append(c).append("x").append(c).append(hexCode.charAt(0)).append(c).append(hexCode.charAt(1))
.append(c).append(hexCode.charAt(2)).append(c).append(hexCode.charAt(3))
.append(c).append(hexCode.charAt(4)).append(c).append(hexCode.charAt(5));
} else {
sb.append(component.getModifier().getColor().getLegacyColor().getFormat());
}
}
if (component.getModifier().isBold()) sb.append(EnumChatFormat.BOLD.getFormat());
if (component.getModifier().isStrikethrough()) sb.append(EnumChatFormat.STRIKETHROUGH.getFormat());
if (component.getModifier().isItalic()) sb.append(EnumChatFormat.ITALIC.getFormat());
if (component.getModifier().isObfuscated()) sb.append(EnumChatFormat.OBFUSCATED.getFormat());
if (component.getModifier().isUnderlined()) sb.append(EnumChatFormat.UNDERLINE.getFormat());
if (component.getText() != null) sb.append(component.getText());
for (IChatBaseComponent extra : component.getExtra()) {
sb.append(toBukkitFormat(extra, rgbClient));
}
return sb.toString();
}
}
@@ -3,10 +3,10 @@ package me.neznamy.tab.platforms.bukkit.bossbar;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.api.bossbar.BarColor;
import me.neznamy.tab.api.bossbar.BarStyle;
import me.neznamy.tab.platforms.bukkit.BukkitUtils;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.bossbar.BossBar;
import me.neznamy.tab.shared.chat.rgb.RGBUtils;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.TAB;
import org.bukkit.Bukkit;
import org.jetbrains.annotations.NotNull;
@@ -22,17 +22,18 @@ import java.util.UUID;
public class BukkitBossBar implements BossBar {
/** Player this handler belongs to */
@NotNull
private final BukkitTabPlayer player;
/** Bukkit BossBars the player can currently see */
@NotNull
private final Map<UUID, org.bukkit.boss.BossBar> bossBars = new HashMap<>();
@Override
public void create(@NotNull UUID id, @NotNull String title, float progress, @NotNull BarColor color, @NotNull BarStyle style) {
if (bossBars.containsKey(id)) return;
org.bukkit.boss.BossBar bar = Bukkit.createBossBar(
RGBUtils.getInstance().convertToBukkitFormat(title,
player.getVersion().getMinorVersion() >= 16 && TAB.getInstance().getServerVersion().getMinorVersion() >= 16),
BukkitUtils.toBukkitFormat(IChatBaseComponent.optimizedComponent(title), player.getVersion().getMinorVersion() >= 16),
org.bukkit.boss.BarColor.valueOf(color.name()),
org.bukkit.boss.BarStyle.valueOf(style.getBukkitName()));
bar.setProgress(progress);
@@ -42,8 +43,7 @@ public class BukkitBossBar implements BossBar {
@Override
public void update(@NotNull UUID id, @NotNull String title) {
bossBars.get(id).setTitle(RGBUtils.getInstance().convertToBukkitFormat(title,
player.getVersion().getMinorVersion() >= 16 && TAB.getInstance().getServerVersion().getMinorVersion() >= 16));
bossBars.get(id).setTitle(BukkitUtils.toBukkitFormat(IChatBaseComponent.optimizedComponent(title), player.getVersion().getMinorVersion() >= 16));
}
@Override
@@ -5,7 +5,7 @@ import me.neznamy.tab.shared.platform.bossbar.BossBar;
import me.neznamy.tab.api.bossbar.BarColor;
import me.neznamy.tab.api.bossbar.BarStyle;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
import me.neznamy.tab.platforms.bukkit.nms.DataWatcher;
import me.neznamy.tab.shared.backend.Location;
import org.bukkit.entity.EntityType;
import org.jetbrains.annotations.NotNull;
@@ -21,6 +21,7 @@ import java.util.UUID;
public class EntityBossBar implements BossBar {
/** Player this handler belongs to */
@NotNull
private final BukkitTabPlayer player;
@Override
@@ -28,18 +29,18 @@ public class EntityBossBar implements BossBar {
DataWatcher w = new DataWatcher();
float health = 300*progress;
if (health == 0) health = 1;
w.getHelper().setHealth(health);
w.getHelper().setCustomName(title, player.getVersion());
w.getHelper().setEntityFlags((byte) 32);
w.getHelper().setWitherInvulnerableTime(880); // Magic number
player.spawnEntity(id.hashCode(), new UUID(0, 0), EntityType.WITHER, new Location(0, 0, 0, 0, 0), w);
w.setHealth(health);
w.setCustomName(title, player.getVersion());
w.setEntityFlags((byte) 32);
w.setWitherInvulnerableTime(880); // Magic number
player.getEntityView().spawnEntity(id.hashCode(), new UUID(0, 0), EntityType.WITHER, new Location(0, 0, 0), w);
}
@Override
public void update(@NotNull UUID id, @NotNull String title) {
DataWatcher w = new DataWatcher();
w.getHelper().setCustomName(title, player.getVersion());
player.updateEntityMetadata(id.hashCode(), w);
w.setCustomName(title, player.getVersion());
player.getEntityView().updateEntityMetadata(id.hashCode(), w);
}
@Override
@@ -47,8 +48,8 @@ public class EntityBossBar implements BossBar {
DataWatcher w = new DataWatcher();
float health = 300*progress;
if (health == 0) health = 1;
w.getHelper().setHealth(health);
player.updateEntityMetadata(id.hashCode(), w);
w.setHealth(health);
player.getEntityView().updateEntityMetadata(id.hashCode(), w);
}
@Override
@@ -59,6 +60,6 @@ public class EntityBossBar implements BossBar {
@Override
public void remove(@NotNull UUID id) {
player.destroyEntities(id.hashCode());
player.getEntityView().destroyEntities(id.hashCode());
}
}
@@ -4,10 +4,10 @@ import com.viaversion.viaversion.api.Via;
import com.viaversion.viaversion.api.legacy.bossbar.BossColor;
import com.viaversion.viaversion.api.legacy.bossbar.BossStyle;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.bossbar.BossBar;
import me.neznamy.tab.api.bossbar.BarColor;
import me.neznamy.tab.api.bossbar.BarStyle;
import me.neznamy.tab.shared.chat.rgb.RGBUtils;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import org.jetbrains.annotations.NotNull;
@@ -22,16 +22,18 @@ import java.util.UUID;
public class ViaBossBar implements BossBar {
/** Player this handler belongs to */
@NotNull
private final BukkitTabPlayer player;
/** ViaVersion BossBars this 1.9+ player can see on 1.8 server */
@NotNull
private final Map<UUID, com.viaversion.viaversion.api.legacy.bossbar.BossBar> viaBossBars = new HashMap<>();
@Override
public void create(@NotNull UUID id, @NotNull String title, float progress, @NotNull BarColor color, @NotNull BarStyle style) {
if (viaBossBars.containsKey(id)) return;
com.viaversion.viaversion.api.legacy.bossbar.BossBar bar = Via.getAPI().legacyAPI().createLegacyBossBar(
RGBUtils.getInstance().convertToBukkitFormat(title, player.getVersion().getMinorVersion() >= 16),
IChatBaseComponent.optimizedComponent(title).toString(player.getVersion()),
progress,
BossColor.valueOf(color.name()),
BossStyle.valueOf(style.getBukkitName()));
@@ -41,8 +43,7 @@ public class ViaBossBar implements BossBar {
@Override
public void update(@NotNull UUID id, @NotNull String title) {
viaBossBars.get(id).setTitle(RGBUtils.getInstance().convertToBukkitFormat(title,
player.getVersion().getMinorVersion() >= 16));
viaBossBars.get(id).setTitle(IChatBaseComponent.optimizedComponent(title).toString(player.getVersion()));
}
@Override
@@ -1,17 +1,12 @@
package me.neznamy.tab.platforms.bukkit.features;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.platforms.bukkit.nms.DataWatcher;
import me.neznamy.tab.platforms.bukkit.platform.BukkitPlatform;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.features.types.PacketSendListener;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutEntityDestroyStorage;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutEntityTeleportStorage;
import me.neznamy.tab.shared.backend.BackendTabPlayer;
import me.neznamy.tab.shared.backend.EntityData;
import me.neznamy.tab.shared.backend.features.unlimitedtags.BackendNameTagX;
import me.neznamy.tab.shared.platform.TabPlayer;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.entity.Entity;
@@ -35,10 +30,7 @@ import java.util.stream.Collectors;
/**
* The core class for unlimited NameTag mode on Bukkit
*/
public class BukkitNameTagX extends BackendNameTagX implements Listener, PacketSendListener {
/** Reference to NMS storage for quick access */
private final NMSStorage nms = NMSStorage.getInstance();
public class BukkitNameTagX extends BackendNameTagX implements Listener {
public BukkitNameTagX(@NotNull JavaPlugin plugin) {
Bukkit.getPluginManager().registerEvents(this, plugin);
@@ -46,45 +38,12 @@ public class BukkitNameTagX extends BackendNameTagX implements Listener, PacketS
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onSneak(PlayerToggleSneakEvent e) {
TabPlayer p = TAB.getInstance().getPlayer(e.getPlayer().getUniqueId());
if (p == null || isPlayerDisabled(p)) return;
TAB.getInstance().getCPUManager().runMeasuredTask(featureName, TabConstants.CpuUsageCategory.PLAYER_SNEAK,
() -> getArmorStandManager(p).sneak(e.isSneaking()));
sneak(e.getPlayer().getUniqueId(), e.isSneaking());
}
@EventHandler
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onRespawn(PlayerRespawnEvent e) {
TabPlayer respawned = TAB.getInstance().getPlayer(e.getPlayer().getUniqueId());
if (respawned == null || isPlayerDisabled(respawned)) return;
TAB.getInstance().getCPUManager().runMeasuredTask(featureName, TabConstants.CpuUsageCategory.PLAYER_RESPAWN,
() -> getArmorStandManager(respawned).teleport());
}
@SuppressWarnings("unchecked")
@Override
@SneakyThrows
public void onPacketSend(@NotNull TabPlayer receiver, @NotNull Object packet) {
if (receiver.getVersion().getMinorVersion() < 8) return;
if (!receiver.isLoaded() || getDisableChecker().isDisabledPlayer(receiver) || getUnlimitedDisableChecker().isDisabledPlayer(receiver)) return;
if (nms.PacketPlayOutEntity.isInstance(packet) && !nms.PacketPlayOutEntityLook.isInstance(packet)) { //ignoring head rotation only packets
packetListener.onEntityMove((BackendTabPlayer) receiver, nms.PacketPlayOutEntity_ENTITYID.getInt(packet));
} else if (PacketPlayOutEntityTeleportStorage.CLASS.isInstance(packet)) {
packetListener.onEntityMove((BackendTabPlayer) receiver, PacketPlayOutEntityTeleportStorage.ENTITY_ID.getInt(packet));
} else if (nms.PacketPlayOutNamedEntitySpawn.isInstance(packet)) {
packetListener.onEntitySpawn((BackendTabPlayer) receiver, nms.PacketPlayOutNamedEntitySpawn_ENTITYID.getInt(packet));
} else if (PacketPlayOutEntityDestroyStorage.CLASS.isInstance(packet)) {
if (nms.getMinorVersion() >= 17) {
Object entities = PacketPlayOutEntityDestroyStorage.ENTITIES.get(packet);
if (entities instanceof List) {
packetListener.onEntityDestroy((BackendTabPlayer) receiver, (List<Integer>) entities);
} else {
//1.17.0
packetListener.onEntityDestroy((BackendTabPlayer) receiver, (int) entities);
}
} else {
packetListener.onEntityDestroy((BackendTabPlayer) receiver, (int[]) PacketPlayOutEntityDestroyStorage.ENTITIES.get(packet));
}
}
respawn(e.getPlayer().getUniqueId());
}
@Override
@@ -111,7 +70,8 @@ public class BukkitNameTagX extends BackendNameTagX implements Listener, PacketS
@SuppressWarnings("deprecation")
@Override
public @NotNull List<Integer> getPassengers(@NotNull Object entity) {
@NotNull
public List<Integer> getPassengers(@NotNull Object entity) {
Entity vehicle = (Entity) entity;
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 11) {
return vehicle.getPassengers().stream().map(Entity::getEntityId).collect(Collectors.toList());
@@ -125,7 +85,8 @@ public class BukkitNameTagX extends BackendNameTagX implements Listener, PacketS
}
@Override
public @Nullable Object getVehicle(@NotNull TabPlayer player) {
@Nullable
public Object getVehicle(@NotNull TabPlayer player) {
return ((Player)player.getPlayer()).getVehicle();
}
@@ -135,7 +96,8 @@ public class BukkitNameTagX extends BackendNameTagX implements Listener, PacketS
}
@Override
public @NotNull String getEntityType(@NotNull Object entity) {
@NotNull
public String getEntityType(@NotNull Object entity) {
return ((Entity) entity).getType().toString().toLowerCase();
}
@@ -162,7 +124,8 @@ public class BukkitNameTagX extends BackendNameTagX implements Listener, PacketS
}
@Override
public @NotNull Object getArmorStandType() {
@NotNull
public Object getArmorStandType() {
return EntityType.ARMOR_STAND;
}
@@ -182,12 +145,23 @@ public class BukkitNameTagX extends BackendNameTagX implements Listener, PacketS
}
@Override
@NotNull
public EntityData createDataWatcher(@NotNull TabPlayer viewer, byte flags, @NotNull String displayName, boolean nameVisible) {
DataWatcher datawatcher = new DataWatcher();
datawatcher.getHelper().setEntityFlags(flags);
datawatcher.getHelper().setCustomName(displayName, viewer.getVersion());
datawatcher.getHelper().setCustomNameVisible(nameVisible);
datawatcher.getHelper().setArmorStandFlags((byte)16);
datawatcher.setEntityFlags(flags);
datawatcher.setCustomName(displayName, viewer.getVersion());
datawatcher.setCustomNameVisible(nameVisible);
datawatcher.setArmorStandFlags((byte)16);
return datawatcher;
}
@Override
public void runInEntityScheduler(@NotNull Object entity, @NotNull Runnable task) {
((BukkitPlatform)TAB.getInstance().getPlatform()).runEntityTask((Entity) entity, task);
}
@Override
public boolean isDead(@NotNull TabPlayer player) {
return ((BukkitTabPlayer)player).getPlayer().isDead();
}
}
@@ -11,7 +11,9 @@ import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
@@ -22,11 +24,36 @@ import java.util.WeakHashMap;
public class BukkitTabExpansion extends PlaceholderExpansion implements TabExpansion {
/** Map holding all values for all players for easy and high-performance access */
private final WeakHashMap<Player, Map<String, String>> values = new WeakHashMap<>();
@NotNull
private final WeakHashMap<TabPlayer, Map<String, String>> values = new WeakHashMap<>();
private final String author = TabConstants.PLUGIN_AUTHOR;
private final String identifier = TabConstants.PLUGIN_ID;
private final String version = TabConstants.PLUGIN_VERSION;
/** List of all placeholders offered by the plugin for command suggestions */
@NotNull
private final List<String> placeholders = Arrays.asList(
"%tab_tabprefix%",
"%tab_tabsuffix%",
"%tab_tagprefix%",
"%tab_tagsuffix%",
"%tab_customtabname%",
"%tab_customtagname%",
"%tab_belowname%",
"%tab_abovename%",
"%tab_tabprefix_raw%",
"%tab_tabsuffix_raw%",
"%tab_tagprefix_raw%",
"%tab_tagsuffix_raw%",
"%tab_customtabname_raw%",
"%tab_customtagname_raw%",
"%tab_belowname_raw%",
"%tab_abovename_raw%",
"%tab_scoreboard_name%",
"%tab_scoreboard_visible%",
"%tab_bossbar_visible%",
"%tab_nametag_preview%",
"%tab_nametag_visibility%",
"%tab_replace_<placeholder>%",
"%tab_placeholder_<placeholder>%"
);
@Override
public boolean persist() {
@@ -34,6 +61,31 @@ public class BukkitTabExpansion extends PlaceholderExpansion implements TabExpan
}
@Override
@NotNull
public String getAuthor() {
return TabConstants.PLUGIN_AUTHOR;
}
@Override
@NotNull
public String getIdentifier() {
return TabConstants.PLUGIN_ID;
}
@Override
@NotNull
public String getVersion() {
return TabConstants.PLUGIN_VERSION;
}
@Override
@NotNull
public List<String> getPlaceholders() {
return placeholders;
}
@Override
@Nullable
public String onPlaceholderRequest(@Nullable Player player, @NotNull String identifier) {
if (identifier.startsWith("replace_")) {
String text = "%" + identifier.substring(8) + "%";
@@ -50,11 +102,14 @@ public class BukkitTabExpansion extends PlaceholderExpansion implements TabExpan
if (identifier.startsWith("placeholder_")) {
TAB.getInstance().getPlaceholderManager().addUsedPlaceholder("%" + identifier.substring(12) + "%", TAB.getInstance().getPlaceholderManager());
}
return values.get(player).get(identifier);
if (player == null) return "<Player cannot be null>";
TabPlayer p = TAB.getInstance().getPlayer(player.getUniqueId());
if (p == null || !p.isLoaded()) return "<Player is not loaded>";
return values.get(p).get(identifier);
}
@Override
public void setValue(@NotNull TabPlayer player, @NotNull String key, @NotNull String value) {
values.computeIfAbsent((Player) player.getPlayer(), p -> new HashMap<>()).put(key, value);
values.computeIfAbsent(player, p -> new HashMap<>()).put(key, value);
}
}
@@ -5,8 +5,7 @@ import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.platforms.bukkit.BukkitUtils;
import me.neznamy.tab.shared.features.types.Loadable;
import me.neznamy.tab.shared.features.types.UnLoadable;
import org.bukkit.Bukkit;
@@ -27,7 +26,6 @@ import org.jetbrains.annotations.NotNull;
* Per-world-PlayerList feature handler
*/
@SuppressWarnings("deprecation")
@RequiredArgsConstructor
public class PerWorldPlayerList extends TabFeature implements Listener, Loadable, UnLoadable {
/** Config options */
@@ -35,21 +33,27 @@ public class PerWorldPlayerList extends TabFeature implements Listener, Loadable
private final List<String> ignoredWorlds = TAB.getInstance().getConfig().getStringList("per-world-playerlist.ignore-effect-in-worlds", Arrays.asList("ignored_world", "build"));
private final Map<String, List<String>> sharedWorlds = TAB.getInstance().getConfig().getConfigurationSection("per-world-playerlist.shared-playerlist-world-groups");
/** Plugin reference*/
@NotNull private final JavaPlugin plugin;
@Getter private final String featureName = "Per world PlayerList";
@Override
public void load() {
Bukkit.getOnlinePlayers().forEach(this::checkPlayer);
/**
* Constructs new instance and registers events
*
* @param plugin
* Plugin instance to register events
*/
public PerWorldPlayerList(JavaPlugin plugin) {
Bukkit.getPluginManager().registerEvents(this, plugin);
}
@Override
public void load() {
for (Player p : BukkitUtils.getOnlinePlayers()) {
checkPlayer(p);
}
}
@Override
public void unload() {
for (Player p : Bukkit.getOnlinePlayers()) {
for (Player pl : Bukkit.getOnlinePlayers()) {
for (Player p : BukkitUtils.getOnlinePlayers()) {
for (Player pl : BukkitUtils.getOnlinePlayers()) {
p.showPlayer(pl);
}
}
@@ -60,14 +64,14 @@ public class PerWorldPlayerList extends TabFeature implements Listener, Loadable
public void onJoin(PlayerJoinEvent e) {
long time = System.nanoTime();
checkPlayer(e.getPlayer());
TAB.getInstance().getCPUManager().addTime(featureName, TabConstants.CpuUsageCategory.PLAYER_JOIN, System.nanoTime()-time);
TAB.getInstance().getCPUManager().addTime(this, TabConstants.CpuUsageCategory.PLAYER_JOIN, System.nanoTime()-time);
}
@EventHandler
public void onWorldChange(PlayerChangedWorldEvent e) {
long time = System.nanoTime();
checkPlayer(e.getPlayer());
TAB.getInstance().getCPUManager().addTime(featureName, TabConstants.CpuUsageCategory.WORLD_SWITCH, System.nanoTime()-time);
TAB.getInstance().getCPUManager().addTime(this, TabConstants.CpuUsageCategory.WORLD_SWITCH, System.nanoTime()-time);
}
/**
@@ -79,7 +83,7 @@ public class PerWorldPlayerList extends TabFeature implements Listener, Loadable
* Player to update
*/
private void checkPlayer(@NotNull Player p) {
for (Player all : Bukkit.getOnlinePlayers()) {
for (Player all : BukkitUtils.getOnlinePlayers()) {
if (all == p) continue;
if (!shouldSee(p, all) && p.canSee(all)) p.hidePlayer(all);
if (shouldSee(p, all) && !p.canSee(all)) p.showPlayer(all);
@@ -109,4 +113,10 @@ public class PerWorldPlayerList extends TabFeature implements Listener, Loadable
}
return viewerWorldGroup.equals(targetWorldGroup);
}
@Override
@NotNull
public String getFeatureName() {
return "Per world PlayerList";
}
}
@@ -1,6 +1,7 @@
package me.neznamy.tab.platforms.bukkit.features;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.api.bossbar.BossBar;
@@ -10,7 +11,6 @@ import me.neznamy.tab.shared.backend.BackendTabPlayer;
import me.neznamy.tab.shared.features.bossbar.BossBarManagerImpl;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener;
@@ -24,15 +24,18 @@ import org.jetbrains.annotations.NotNull;
@RequiredArgsConstructor
public class WitherBossBar extends BossBarManagerImpl implements Listener, WorldSwitchListener {
/** Distance of the Wither in blocks */
private static final int WITHER_DISTANCE = 60;
/** Reference to plugin for registering listener */
private final @NotNull JavaPlugin plugin;
/**
* Constructs new instance and registers events
*
* @param plugin
* Plugin instance to register events
*/
public WitherBossBar(JavaPlugin plugin) {
Bukkit.getPluginManager().registerEvents(this, plugin);
}
@Override
public void load() {
Bukkit.getPluginManager().registerEvents(this, plugin);
//when MC is on fullscreen, BossBar disappears after 1 second of not being seen
//when in a small window, it's about 100ms
TAB.getInstance().getCPUManager().startRepeatingMeasuredTask(100,
@@ -49,9 +52,10 @@ public class WitherBossBar extends BossBarManagerImpl implements Listener, World
if (p.getVersion().getMinorVersion() > 8) continue; //sending VV packets to those
for (BossBar line : getRegisteredBossBars().values()) {
if (!line.getPlayers().contains(p)) continue;
Location loc = ((Player) p.getPlayer()).getEyeLocation().add(((Player) p.getPlayer()).getEyeLocation().getDirection().normalize().multiply(WITHER_DISTANCE));
Location eyeLocation = ((BukkitTabPlayer)p).getPlayer().getEyeLocation();
Location loc = eyeLocation.add(eyeLocation.getDirection().normalize().multiply(60)); // Wither distance
if (loc.getY() < 1) loc.setY(1);
((BackendTabPlayer)p).teleportEntity(line.getUniqueId().hashCode(), new me.neznamy.tab.shared.backend.Location(loc.getX(), loc.getY(), loc.getZ(), 0, 0));
((BackendTabPlayer)p).getEntityView().teleportEntity(line.getUniqueId().hashCode(), new me.neznamy.tab.shared.backend.Location(loc.getX(), loc.getY(), loc.getZ()));
}
}
}
@@ -0,0 +1,33 @@
package me.neznamy.tab.platforms.bukkit.hook;
import me.libraryaddict.disguise.DisguiseAPI;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.util.ReflectionUtils;
/**
* Class for hooking into LibsDisguises to get disguise status of players.
*/
public class LibsDisguisesHook {
/** Flag tracking if LibsDisguises is installed or not */
private static boolean installed = ReflectionUtils.classExists("me.libraryaddict.disguise.DisguiseAPI");
/**
* Returns {@code true} if LibsDisguises is installed and player is disguised,
* {@code false} otherwise.
*
* @param player
* Player to check
* @return {@code true} if disguised, {@code false} otherwise
*/
public static boolean isDisguised(TabPlayer player) {
try {
return installed && DisguiseAPI.isDisguised(((BukkitTabPlayer)player).getPlayer());
} catch (LinkageError e) {
//java.lang.NoClassDefFoundError: Could not initialize class me.libraryaddict.disguise.DisguiseAPI
installed = false;
return false;
}
}
}
@@ -0,0 +1,83 @@
package me.neznamy.tab.platforms.bukkit.nms;
import lombok.Getter;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.bukkit.Bukkit;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
public class BukkitReflection {
/** Server's NMS/CraftBukkit package */
@Getter
private static final String serverPackage = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3];
/** Server's minor version */
@Getter
private static final int minorVersion = Integer.parseInt(serverPackage.split("_")[1]);
/** Flag determining whether this server jar is mojang mapped or not */
@Getter
private static final boolean mojangMapped = checkMojangMapped();
/** Flag determining whether the server version is at least 1.19.3 or not */
@Getter
private static final boolean is1_19_3Plus = ReflectionUtils.classExists("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket");
/** Flag determining whether the server version is at least 1.19.4 or not */
@Getter
private static final boolean is1_19_4Plus = is1_19_3Plus && !serverPackage.equals("v1_19_R2");
/** Flag determining whether the server version is at least 1.20.2 or not */
@Getter
private static final boolean is1_20_2Plus = minorVersion >= 20 && !serverPackage.equals("v1_20_R1");
private static boolean checkMojangMapped() {
try {
Class.forName("net.minecraft.network.syncher.SynchedEntityData").getMethod("define",
Class.forName("net.minecraft.network.syncher.EntityDataAccessor"), Object.class);
return true;
} catch (ReflectiveOperationException | NullPointerException ex) {
return false;
}
}
/**
* Returns class with given potential names in same order
*
* @param names
* possible class names
* @return class for specified names
* @throws ClassNotFoundException
* if class does not exist
*/
public static Class<?> getLegacyClass(@NotNull String... names) throws ClassNotFoundException {
for (String name : names) {
try {
return getLegacyClass(name);
} catch (ClassNotFoundException e) {
//not the first class name in array
}
}
throw new ClassNotFoundException("No class found with possible names " + Arrays.toString(names));
}
/**
* Returns class from given name. Supports modded servers, such as Thermos.
*
* @param name
* class name
* @return class from given name
* @throws ClassNotFoundException
* if class was not found
*/
public static Class<?> getLegacyClass(@NotNull String name) throws ClassNotFoundException {
try {
return BukkitReflection.class.getClassLoader().loadClass("net.minecraft.server." + serverPackage + "." + name);
} catch (NullPointerException e) {
// nested class not found
throw new ClassNotFoundException(name);
}
}
}
@@ -0,0 +1,283 @@
package me.neznamy.tab.platforms.bukkit.nms;
import lombok.*;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.backend.EntityData;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
/**
* A class representing the n.m.s.DataWatcher class to make work with it much easier
*/
@ToString
public class DataWatcher implements EntityData {
/** NMS Fields */
public static Class<?> DataWatcher;
private static Constructor<?> newDataWatcher;
private static Method DataWatcher_register;
private static Method DataWatcher_markDirty;
public static Method DataWatcher_packDirty;
private static Class<?> DataWatcherObject;
private static Constructor<?> newDataWatcherObject;
private static Class<?> DataWatcherRegistry;
private static Class<?> DataWatcherSerializer;
private static Object DataWatcherSerializer_BYTE;
private static Object DataWatcherSerializer_FLOAT;
private static Object DataWatcherSerializer_STRING;
private static Object DataWatcherSerializer_OPTIONAL_COMPONENT;
private static Object DataWatcherSerializer_BOOLEAN;
private static final int armorStandFlagsPosition = getArmorStandFlagsPosition();
/** Watched data */
private final Map<Integer, Item> dataValues = new HashMap<>();
/**
* Returns armor stand flags position based on server version
*
* @return armor stand flags position based on server version
*/
private static int getArmorStandFlagsPosition() {
if (BukkitReflection.getMinorVersion() >= 17) {
//1.17.x, 1.18.x, 1.19.x, 1.20.x
return 15;
} else if (BukkitReflection.getMinorVersion() >= 15) {
//1.15.x, 1.16.x
return 14;
} else if (BukkitReflection.getMinorVersion() >= 14) {
//1.14.x
return 13;
} else if (BukkitReflection.getMinorVersion() >= 10) {
//1.10.x - 1.13.x
return 11;
} else {
//1.8.x - 1.9.x
return 10;
}
}
/**
* Loads all required Fields and throws Exception if something went wrong
*/
public static void load() throws ReflectiveOperationException {
int minorVersion = BukkitReflection.getMinorVersion();
if (BukkitReflection.isMojangMapped()) {
DataWatcher = Class.forName("net.minecraft.network.syncher.SynchedEntityData");
DataWatcherObject = Class.forName("net.minecraft.network.syncher.EntityDataAccessor");
DataWatcherRegistry = Class.forName("net.minecraft.network.syncher.EntityDataSerializers");
DataWatcherSerializer = Class.forName("net.minecraft.network.syncher.EntityDataSerializer");
} else if (minorVersion >= 17) {
DataWatcher = Class.forName("net.minecraft.network.syncher.DataWatcher");
DataWatcherObject = Class.forName("net.minecraft.network.syncher.DataWatcherObject");
DataWatcherRegistry = Class.forName("net.minecraft.network.syncher.DataWatcherRegistry");
DataWatcherSerializer = Class.forName("net.minecraft.network.syncher.DataWatcherSerializer");
} else {
DataWatcher = BukkitReflection.getLegacyClass("DataWatcher");
if (minorVersion >= 9) {
DataWatcherObject = BukkitReflection.getLegacyClass("DataWatcherObject");
DataWatcherRegistry = BukkitReflection.getLegacyClass("DataWatcherRegistry");
DataWatcherSerializer = BukkitReflection.getLegacyClass("DataWatcherSerializer");
}
}
newDataWatcher = ReflectionUtils.getOnlyConstructor(DataWatcher);
if (minorVersion >= 9) {
DataWatcher_register = ReflectionUtils.getMethod(
DataWatcher,
new String[]{"define", "register", "a", "m_135372_"}, // {Bukkit 1.20.2+, Bukkit, Bukkit 1.18+, Mohist 1.18.2}
DataWatcherObject, Object.class
);
} else {
DataWatcher_register = ReflectionUtils.getMethod(DataWatcher, new String[]{"func_75682_a", "a"}, int.class, Object.class); // {Thermos 1.7.10, Bukkit}
}
if (minorVersion >= 19) {
DataWatcher_packDirty = ReflectionUtils.getMethod(DataWatcher, new String[] {"packDirty", "b"}); // {Mojang | 1.20.2+, 1.20.2-}
}
if (BukkitReflection.is1_19_3Plus()) {
DataWatcher_markDirty = ReflectionUtils.getMethods(DataWatcher, void.class, DataWatcherObject).get(0);
}
if (minorVersion >= 9) {
newDataWatcherObject = DataWatcherObject.getConstructor(int.class, DataWatcherSerializer);
DataWatcherSerializer_BYTE = ReflectionUtils.getField(DataWatcherRegistry, "BYTE", "a", "f_135027_").get(null); // Mohist 1.18.2
DataWatcherSerializer_FLOAT = ReflectionUtils.getField(DataWatcherRegistry, "FLOAT", "c", "f_135029_").get(null); // Mohist 1.18.2
DataWatcherSerializer_STRING = ReflectionUtils.getField(DataWatcherRegistry, "STRING", "d", "f_135030_").get(null); // Mohist 1.18.2
if (BukkitReflection.is1_19_3Plus()) {
DataWatcherSerializer_OPTIONAL_COMPONENT = ReflectionUtils.getField(DataWatcherRegistry, "OPTIONAL_COMPONENT", "g").get(null);
if (BukkitReflection.is1_19_4Plus()) {
DataWatcherSerializer_BOOLEAN = ReflectionUtils.getField(DataWatcherRegistry, "BOOLEAN", "k").get(null);
} else {
DataWatcherSerializer_BOOLEAN = ReflectionUtils.getField(DataWatcherRegistry, "BOOLEAN", "j").get(null);
}
} else {
if (minorVersion >= 13) {
DataWatcherSerializer_OPTIONAL_COMPONENT = ReflectionUtils.getField(DataWatcherRegistry,
"OPTIONAL_COMPONENT", "f", "f_135032_").get(null); // Mohist 1.18.2
DataWatcherSerializer_BOOLEAN = ReflectionUtils.getField(DataWatcherRegistry,
"BOOLEAN", "i", "f_135035_").get(null); // Mohist 1.18.2
} else {
DataWatcherSerializer_BOOLEAN = DataWatcherRegistry.getDeclaredField("h").get(null);
}
}
}
}
/**
* Sets value into data values
*
* @param position
* data position
* @param serializer
* Object serializer (1.9+)
* @param value
* value
*/
public void setValue(int position, @Nullable Object serializer, @NotNull Object value) {
dataValues.put(position, new Item(position, serializer, value));
}
/**
* Writes entity byte flags
*
* @param flags
* flags to write
*/
public void setEntityFlags(byte flags) {
setValue(0, DataWatcherSerializer_BYTE, flags);
}
/**
* Writes entity custom name with position based on server version and value depending on client version (RGB or not)
*
* @param customName
* target custom name
* @param clientVersion
* client version
*/
public void setCustomName(@NotNull String customName, @NotNull ProtocolVersion clientVersion) {
if (BukkitReflection.getMinorVersion() >= 13) {
setValue(2, DataWatcherSerializer_OPTIONAL_COMPONENT,
Optional.of(TAB.getInstance().getPlatform().toComponent(IChatBaseComponent.optimizedComponent(customName), clientVersion)));
} else if (BukkitReflection.getMinorVersion() >= 8) {
setValue(2, DataWatcherSerializer_STRING, customName);
} else {
//name length is limited to 64 characters on <1.8
String cutName = (customName.length() > 64 ? customName.substring(0, 64) : customName);
if (BukkitReflection.getMinorVersion() >= 6) {
setValue(10, null, cutName);
} else {
setValue(5, null, cutName);
}
}
}
/**
* Writes custom name visibility boolean
*
* @param visible
* if visible or not
*/
public void setCustomNameVisible(boolean visible) {
if (BukkitReflection.getMinorVersion() >= 9) {
setValue(3, DataWatcherSerializer_BOOLEAN, visible);
} else {
setValue(3, null, (byte)(visible?1:0));
}
}
/**
* Writes entity health
*
* @param health
* health of entity
*/
public void setHealth(float health) {
if (BukkitReflection.getMinorVersion() >= 6) {
setValue(6, DataWatcherSerializer_FLOAT, health);
} else {
setValue(16, null, (int)health);
}
}
/**
* Writes armor stand flags
*
* @param flags
* flags to write
*/
public void setArmorStandFlags(byte flags) {
setValue(armorStandFlagsPosition, DataWatcherSerializer_BYTE, flags);
}
/**
* Writes wither invulnerable time
* @param time
* Time, apparently
*/
public void setWitherInvulnerableTime(int time) {
if (BukkitReflection.getMinorVersion() > 8)
throw new UnsupportedOperationException("Not supported on 1.9+");
setValue(20, null, time);
}
/**
* Converts the class into an instance of NMS.DataWatcher
*
* @return an instance of NMS.DataWatcher with same data
*/
@SneakyThrows
public @NotNull Object build() {
Object nmsWatcher;
if (newDataWatcher.getParameterCount() == 1) { //1.7+
nmsWatcher = newDataWatcher.newInstance(new Object[] {null});
} else {
nmsWatcher = newDataWatcher.newInstance();
}
for (Item item : dataValues.values()) {
Object nmsObject = item.createObject();
DataWatcher_register.invoke(nmsWatcher, nmsObject, item.getValue());
if (BukkitReflection.is1_19_3Plus()) DataWatcher_markDirty.invoke(nmsWatcher, nmsObject);
}
return nmsWatcher;
}
@RequiredArgsConstructor
@Getter
public static class Item {
/** Item position */
private final int position;
/** Item serializer (1.9+) */
@Nullable
private final Object serializer;
/** Item value */
@NotNull
private final Object value;
/**
* Creates data watcher object with position and serializer
*
* @return NMS data watcher object
*/
@SneakyThrows
public Object createObject() {
if (BukkitReflection.getMinorVersion() >= 9) {
return newDataWatcherObject.newInstance(position, serializer);
} else {
return position;
}
}
}
}
@@ -0,0 +1,120 @@
package me.neznamy.tab.platforms.bukkit.nms;
import com.mojang.authlib.GameProfile;
import io.netty.channel.Channel;
import lombok.Getter;
import lombok.Setter;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.BukkitTabList;
import me.neznamy.tab.platforms.bukkit.scoreboard.PacketScoreboard;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.util.ComponentCache;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.bukkit.Bukkit;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* Class holding all NMS classes, methods, fields and constructors used by TAB.
*/
public class NMSStorage {
/** Instance of this class */
@Getter @Setter private static NMSStorage instance;
/** Server's NMS/CraftBukkit package */
private final String serverPackage = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3];
/** Server's minor version */
@Getter private final int minorVersion = Integer.parseInt(serverPackage.split("_")[1]);
/** Basic universal values */
protected Class<?> Packet;
protected Class<?> NetworkManager;
public Class<?> EntityPlayer;
protected Class<?> EntityHuman;
protected Class<?> PlayerConnection;
// Ping field for 1.5.2 - 1.16.5, 1.17+ has Player#getPing()
public Field PING;
public Field PLAYER_CONNECTION;
public Field NETWORK_MANAGER;
public Field CHANNEL;
public Method getHandle;
public Method sendPacket;
public Method getProfile;
/** Chat components */
protected Class<?> ChatSerializer;
public Method ChatSerializer_DESERIALIZE;
public final ComponentCache<IChatBaseComponent, Object> componentCache = new ComponentCache<>(1000,
(component, clientVersion) -> ChatSerializer_DESERIALIZE.invoke(null, component.toString(clientVersion)));
/**
* Constructs new instance and attempts to load all fields, methods and constructors.
*/
@SneakyThrows
public NMSStorage() {
ProtocolVersion.UNKNOWN_SERVER_VERSION.setMinorVersion(minorVersion); //fixing compatibility with forks that set version field value to "Unknown"
loadClasses();
if (minorVersion >= 7) {
if (BukkitReflection.is1_20_2Plus()) {
NETWORK_MANAGER = ReflectionUtils.getOnlyField(PlayerConnection.getSuperclass(), NetworkManager);
} else {
NETWORK_MANAGER = ReflectionUtils.getOnlyField(PlayerConnection, NetworkManager);
}
sendPacket = ReflectionUtils.getMethods(PlayerConnection, void.class, Packet).get(0);
} else {
sendPacket = ReflectionUtils.getMethod(PlayerConnection, new String[]{"sendPacket"}, Packet);
}
if (minorVersion >= 8) {
ChatSerializer_DESERIALIZE = ReflectionUtils.getMethods(ChatSerializer, Object.class, String.class).get(0);
CHANNEL = ReflectionUtils.getOnlyField(NetworkManager, Channel.class);
try {
getProfile = ReflectionUtils.getOnlyMethod(EntityHuman, GameProfile.class);
} catch (IllegalStateException catServer) {
getProfile = ReflectionUtils.getMethod(EntityHuman, new String[] {"getProfile"});
}
}
PLAYER_CONNECTION = ReflectionUtils.getOnlyField(EntityPlayer, PlayerConnection);
getHandle = Class.forName("org.bukkit.craftbukkit." + serverPackage + ".entity.CraftPlayer").getMethod("getHandle");
BukkitTabList.load();
DataWatcher.load();
PacketEntityView.load();
PacketScoreboard.load();
if (minorVersion < 17) {
PING = ReflectionUtils.getField(EntityPlayer, "ping", "field_71138_i"); // 1.5.2 - 1.16.5, 1.7.10 Thermos
}
}
private void loadClasses() throws ClassNotFoundException {
if (minorVersion >= 17 && ReflectionUtils.classExists("net.minecraft.ChatFormatting")) {
ChatSerializer = Class.forName("net.minecraft.network.chat.Component$Serializer");
EntityHuman = Class.forName("net.minecraft.world.entity.player.Player");
NetworkManager = Class.forName("net.minecraft.network.Connection");
Packet = Class.forName("net.minecraft.network.protocol.Packet");
EntityPlayer = Class.forName("net.minecraft.server.level.ServerPlayer");
PlayerConnection = Class.forName("net.minecraft.server.network.ServerGamePacketListenerImpl");
} else if (minorVersion >= 17) {
ChatSerializer = Class.forName("net.minecraft.network.chat.IChatBaseComponent$ChatSerializer");
EntityHuman = Class.forName("net.minecraft.world.entity.player.EntityHuman");
NetworkManager = Class.forName("net.minecraft.network.NetworkManager");
Packet = Class.forName("net.minecraft.network.protocol.Packet");
EntityPlayer = Class.forName("net.minecraft.server.level.EntityPlayer");
PlayerConnection = Class.forName("net.minecraft.server.network.PlayerConnection");
} else {
EntityHuman = BukkitReflection.getLegacyClass("EntityHuman");
Packet = BukkitReflection.getLegacyClass("Packet");
EntityPlayer = BukkitReflection.getLegacyClass("EntityPlayer");
PlayerConnection = BukkitReflection.getLegacyClass("PlayerConnection");
NetworkManager = BukkitReflection.getLegacyClass("NetworkManager");
if (minorVersion >= 7) {
ChatSerializer = BukkitReflection.getLegacyClass("IChatBaseComponent$ChatSerializer", "ChatSerializer");
}
}
}
}
@@ -0,0 +1,392 @@
package me.neznamy.tab.platforms.bukkit.nms;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.backend.EntityData;
import me.neznamy.tab.shared.backend.Location;
import me.neznamy.tab.shared.backend.entityview.EntityView;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.bukkit.Bukkit;
import org.bukkit.entity.EntityType;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.EnumMap;
import java.util.List;
import java.util.UUID;
@RequiredArgsConstructor
@SuppressWarnings("unchecked")
public class PacketEntityView implements EntityView {
/** PacketPlayOutEntityDestroy */
private static Class<?> EntityDestroyClass;
private static Constructor<?> newEntityDestroy;
private static Field EntityDestroy_Entities;
/** PacketPlayOutEntityMetadata */
private static Constructor<?> newEntityMetadata;
/** PacketPlayOutEntityTeleport */
private static Class<?> EntityTeleportClass;
private static Constructor<?> newEntityTeleport;
private static Field EntityTeleport_EntityId;
private static Field EntityTeleport_X;
private static Field EntityTeleport_Y;
private static Field EntityTeleport_Z;
/** PacketPlayOutSpawnEntityLiving */
private static Class<?> SpawnEntityClass;
private static Constructor<?> newSpawnEntity;
/** 1.17+ */
private static Object Vec3D_Empty;
private static Object EntityTypes_ARMOR_STAND;
/** 1.16.5- */
private static Field SpawnEntity_EntityId;
private static Field SpawnEntity_EntityType;
private static Field SpawnEntity_UUID;
private static Field SpawnEntity_X;
private static Field SpawnEntity_Y;
private static Field SpawnEntity_Z;
private static Field SpawnEntity_DataWatcher;
private static final EnumMap<EntityType, Integer> entityIds = new EnumMap<>(EntityType.class);
/** Other entity packets */
private static Class<?> PacketPlayOutEntity;
private static Field PacketPlayOutEntity_ENTITYID;
private static Class<?> PacketPlayOutEntityLook;
private static Class<?> PacketPlayOutNamedEntitySpawn;
private static Field PacketPlayOutNamedEntitySpawn_ENTITYID;
private static Object dummyEntity;
@Getter
private static boolean available;
/** Player this view belongs to */
private final BukkitTabPlayer player;
/**
* Loads all required classes and fields and throws Exception if something went wrong
*
* @throws ReflectiveOperationException
* If something fails
*/
public static void load() throws ReflectiveOperationException {
loadEntityMetadata();
loadEntityDestroy();
loadEntityTeleport();
loadEntityMove();
loadEntitySpawn();
available = true;
}
private static void loadEntityMetadata() throws ReflectiveOperationException {
// Class
Class<?> entityMetadataClass = getClass("network.protocol.game.ClientboundSetEntityDataPacket",
"network.protocol.game.PacketPlayOutEntityMetadata", "PacketPlayOutEntityMetadata", "Packet40EntityMetadata");
// Constructor
if (BukkitReflection.is1_19_3Plus()) {
newEntityMetadata = entityMetadataClass.getConstructor(int.class, List.class);
} else {
newEntityMetadata = entityMetadataClass.getConstructor(int.class, DataWatcher.DataWatcher, boolean.class);
}
}
private static void loadEntityDestroy() throws ReflectiveOperationException {
// Class
EntityDestroyClass = getClass("network.protocol.game.ClientboundRemoveEntitiesPacket",
"network.protocol.game.PacketPlayOutEntityDestroy", "PacketPlayOutEntityDestroy", "Packet29DestroyEntity");
// Constructor
try {
newEntityDestroy = EntityDestroyClass.getConstructor(int[].class);
} catch (NoSuchMethodException e) {
//1.17.0
newEntityDestroy = EntityDestroyClass.getConstructor(int.class);
}
// Field
EntityDestroy_Entities = ReflectionUtils.getOnlyField(EntityDestroyClass);
}
private static void loadEntityTeleport() throws ReflectiveOperationException {
// Class
EntityTeleportClass = getClass("network.protocol.game.ClientboundTeleportEntityPacket",
"network.protocol.game.PacketPlayOutEntityTeleport", "PacketPlayOutEntityTeleport", "Packet34EntityTeleport");
// Constructor
if (BukkitReflection.getMinorVersion() >= 17) {
newEntityTeleport = EntityTeleportClass.getConstructor(getClass("world.entity.Entity"));
} else {
newEntityTeleport = EntityTeleportClass.getConstructor();
}
// Fields
EntityTeleport_EntityId = ReflectionUtils.getFields(EntityTeleportClass, int.class).get(0);
if (BukkitReflection.getMinorVersion() >= 9) {
EntityTeleport_X = ReflectionUtils.getFields(EntityTeleportClass, double.class).get(0);
EntityTeleport_Y = ReflectionUtils.getFields(EntityTeleportClass, double.class).get(1);
EntityTeleport_Z = ReflectionUtils.getFields(EntityTeleportClass, double.class).get(2);
} else {
EntityTeleport_X = ReflectionUtils.getFields(EntityTeleportClass, int.class).get(1);
EntityTeleport_Y = ReflectionUtils.getFields(EntityTeleportClass, int.class).get(2);
EntityTeleport_Z = ReflectionUtils.getFields(EntityTeleportClass, int.class).get(3);
}
// Dummy armor stand for constructor
if (BukkitReflection.getMinorVersion() >= 17) {
Class<?> world = getClass("world.level.Level", "world.level.World", "World");
Class<?> entityArmorStand = getClass("world.entity.decoration.ArmorStand",
"world.entity.decoration.EntityArmorStand", "EntityArmorStand");
Constructor<?> newEntityArmorStand = entityArmorStand.getConstructor(world, double.class, double.class, double.class);
Method World_getHandle = Class.forName("org.bukkit.craftbukkit." + BukkitReflection.getServerPackage() + ".CraftWorld").getMethod("getHandle");
dummyEntity = newEntityArmorStand.newInstance(World_getHandle.invoke(Bukkit.getWorlds().get(0)), 0, 0, 0);
}
}
private static void loadEntityMove() throws ReflectiveOperationException {
// Classes
PacketPlayOutEntity = getClass("network.protocol.game.ClientboundMoveEntityPacket",
"network.protocol.game.PacketPlayOutEntity", "PacketPlayOutEntity", "Packet30Entity");
PacketPlayOutEntityLook = getClass("network.protocol.game.ClientboundMoveEntityPacket$Rot",
"network.protocol.game.PacketPlayOutEntity$PacketPlayOutEntityLook", "PacketPlayOutEntity$PacketPlayOutEntityLook",
"PacketPlayOutEntityLook", "Packet32EntityLook");
// Field
PacketPlayOutEntity_ENTITYID = ReflectionUtils.getFields(PacketPlayOutEntity, int.class).get(0);
}
private static void loadEntitySpawn() throws ReflectiveOperationException {
SpawnEntityClass = getClass("network.protocol.game.ClientboundAddEntityPacket",
"network.protocol.game.PacketPlayOutSpawnEntity", "PacketPlayOutSpawnEntityLiving", "Packet24MobSpawn");
SpawnEntity_EntityId = ReflectionUtils.getFields(SpawnEntityClass, int.class).get(0);
if (BukkitReflection.getMinorVersion() >= 17) {
Class<?> Vec3D = getClass("world.phys.Vec3", "world.phys.Vec3D");
Vec3D_Empty = ReflectionUtils.getOnlyField(Vec3D, Vec3D).get(null);
Class<?> EntityTypes = getClass("world.entity.EntityType", "world.entity.EntityTypes");
if (BukkitReflection.getMinorVersion() >= 19) {
EntityTypes_ARMOR_STAND = ReflectionUtils.getField(EntityTypes, "ARMOR_STAND", "d").get(null);
} else {
EntityTypes_ARMOR_STAND = ReflectionUtils.getField(EntityTypes, "ARMOR_STAND", "c", "f_20529_").get(null); // Mohist 1.18.2
}
if (BukkitReflection.getMinorVersion() >= 19) {
newSpawnEntity = SpawnEntityClass.getConstructor(int.class, UUID.class, double.class, double.class, double.class, float.class, float.class, EntityTypes, int.class, Vec3D, double.class);
} else {
newSpawnEntity = SpawnEntityClass.getConstructor(int.class, UUID.class, double.class, double.class, double.class, float.class, float.class, EntityTypes, int.class, Vec3D);
}
} else {
newSpawnEntity = SpawnEntityClass.getConstructor();
if (BukkitReflection.getMinorVersion() >= 9) {
SpawnEntity_UUID = ReflectionUtils.getOnlyField(SpawnEntityClass, UUID.class);
SpawnEntity_X = ReflectionUtils.getFields(SpawnEntityClass, double.class).get(0);
SpawnEntity_Y = ReflectionUtils.getFields(SpawnEntityClass, double.class).get(1);
SpawnEntity_Z = ReflectionUtils.getFields(SpawnEntityClass, double.class).get(2);
} else {
SpawnEntity_X = ReflectionUtils.getFields(SpawnEntityClass, int.class).get(2);
SpawnEntity_Y = ReflectionUtils.getFields(SpawnEntityClass, int.class).get(3);
SpawnEntity_Z = ReflectionUtils.getFields(SpawnEntityClass, int.class).get(4);
}
SpawnEntity_EntityType = ReflectionUtils.getFields(SpawnEntityClass, int.class).get(1);
if (BukkitReflection.getMinorVersion() <= 14) {
SpawnEntity_DataWatcher = ReflectionUtils.getOnlyField(SpawnEntityClass, DataWatcher.DataWatcher);
}
}
if (BukkitReflection.getMinorVersion() >= 13) {
entityIds.put(EntityType.ARMOR_STAND, 1);
} else {
entityIds.put(EntityType.WITHER, 64);
if (BukkitReflection.getMinorVersion() >= 8) {
entityIds.put(EntityType.ARMOR_STAND, 30);
}
}
if (!BukkitReflection.is1_20_2Plus()) {
PacketPlayOutNamedEntitySpawn = getClass("network.protocol.game.ClientboundAddPlayerPacket",
"network.protocol.game.PacketPlayOutNamedEntitySpawn", "PacketPlayOutNamedEntitySpawn", "Packet20NamedEntitySpawn");
PacketPlayOutNamedEntitySpawn_ENTITYID = ReflectionUtils.getFields(PacketPlayOutNamedEntitySpawn, int.class).get(0);
}
}
private static int floor(double paramDouble) {
int i = (int)paramDouble;
return paramDouble < i ? i - 1 : i;
}
@SneakyThrows
@Override
public void spawnEntity(int entityId, @NotNull UUID id, @NotNull Object entityType, @NotNull Location l, @NotNull EntityData data) {
int minorVersion = BukkitReflection.getMinorVersion();
if (minorVersion >= 19) {
player.sendPacket(newSpawnEntity.newInstance(entityId, id, l.getX(), l.getY(), l.getZ(), 0, 0, EntityTypes_ARMOR_STAND, 0, Vec3D_Empty, 0d));
} else if (minorVersion >= 17) {
player.sendPacket(newSpawnEntity.newInstance(entityId, id, l.getX(), l.getY(), l.getZ(), 0, 0, EntityTypes_ARMOR_STAND, 0, Vec3D_Empty));
} else {
Object nmsPacket = newSpawnEntity.newInstance();
SpawnEntity_EntityId.set(nmsPacket, entityId);
if (minorVersion <= 14) {
SpawnEntity_DataWatcher.set(nmsPacket, data.build());
}
if (minorVersion >= 9) {
SpawnEntity_UUID.set(nmsPacket, id);
SpawnEntity_X.set(nmsPacket, l.getX());
SpawnEntity_Y.set(nmsPacket, l.getY());
SpawnEntity_Z.set(nmsPacket, l.getZ());
} else {
SpawnEntity_X.set(nmsPacket, floor(l.getX()*32));
SpawnEntity_Y.set(nmsPacket, floor(l.getY()*32));
SpawnEntity_Z.set(nmsPacket, floor(l.getZ()*32));
}
SpawnEntity_EntityType.set(nmsPacket, entityIds.get((EntityType) entityType));
player.sendPacket(nmsPacket);
}
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 15) {
updateEntityMetadata(entityId, data);
}
}
@SneakyThrows
@Override
public void updateEntityMetadata(int entityId, @NotNull EntityData data) {
if (newEntityMetadata.getParameterCount() == 2) {
//1.19.3+
player.sendPacket(newEntityMetadata.newInstance(entityId, DataWatcher.DataWatcher_packDirty.invoke(data.build())));
} else {
player.sendPacket(newEntityMetadata.newInstance(entityId, data.build(), true));
}
}
@SneakyThrows
@Override
public void teleportEntity(int entityId, @NotNull Location location) {
Object nmsPacket;
if (BukkitReflection.getMinorVersion() >= 17) {
nmsPacket = newEntityTeleport.newInstance(dummyEntity);
} else {
nmsPacket = newEntityTeleport.newInstance();
}
EntityTeleport_EntityId.set(nmsPacket, entityId);
if (BukkitReflection.getMinorVersion() >= 9) {
EntityTeleport_X.set(nmsPacket, location.getX());
EntityTeleport_Y.set(nmsPacket, location.getY());
EntityTeleport_Z.set(nmsPacket, location.getZ());
} else {
EntityTeleport_X.set(nmsPacket, floor(location.getX()*32));
EntityTeleport_Y.set(nmsPacket, floor(location.getY()*32));
EntityTeleport_Z.set(nmsPacket, floor(location.getZ()*32));
}
player.sendPacket(nmsPacket);
}
@SneakyThrows
@Override
public void destroyEntities(int... entities) {
if (newEntityDestroy.getParameterTypes()[0] != int.class) {
player.sendPacket(newEntityDestroy.newInstance(new Object[]{entities}));
} else {
//1.17.0 Mojank
for (int entity : entities) {
player.sendPacket(newEntityDestroy.newInstance(entity));
}
}
}
@Override
public boolean isDestroyPacket(@NotNull Object packet) {
return EntityDestroyClass.isInstance(packet);
}
@Override
public boolean isTeleportPacket(@NotNull Object packet) {
return EntityTeleportClass.isInstance(packet);
}
@Override
public boolean isNamedEntitySpawnPacket(@NotNull Object packet) {
if (BukkitReflection.is1_20_2Plus()) {
return SpawnEntityClass.isInstance(packet);
} else {
return PacketPlayOutNamedEntitySpawn.isInstance(packet);
}
}
@Override
public boolean isMovePacket(@NotNull Object packet) {
return PacketPlayOutEntity.isInstance(packet);
}
@Override
public boolean isLookPacket(@NotNull Object packet) {
return PacketPlayOutEntityLook.isInstance(packet);
}
@Override
@SneakyThrows
public int getTeleportEntityId(@NotNull Object teleportPacket) {
return EntityTeleport_EntityId.getInt(teleportPacket);
}
@Override
@SneakyThrows
public int getMoveEntityId(@NotNull Object movePacket) {
return PacketPlayOutEntity_ENTITYID.getInt(movePacket);
}
@Override
@SneakyThrows
public int getSpawnedPlayer(@NotNull Object playerSpawnPacket) {
if (BukkitReflection.is1_20_2Plus()) {
return SpawnEntity_EntityId.getInt(playerSpawnPacket);
} else {
return PacketPlayOutNamedEntitySpawn_ENTITYID.getInt(playerSpawnPacket);
}
}
@Override
@SneakyThrows
public int[] getDestroyedEntities(@NotNull Object destroyPacket) {
Object entities = PacketEntityView.EntityDestroy_Entities.get(destroyPacket);
if (BukkitReflection.getMinorVersion() >= 17) {
if (entities instanceof List) {
return ((List<Integer>)entities).stream().mapToInt(i -> i).toArray();
} else {
//1.17.0
return new int[]{(int) entities};
}
} else {
return (int[]) entities;
}
}
/**
* Returns class from given potential names. Supports modded servers, such as Thermos.
*
* @param names
* Potential class names
* @return class from given name
* @throws ClassNotFoundException
* if class was not found
*/
public static Class<?> getClass(@NotNull String... names) throws ClassNotFoundException {
for (String name : names) {
try {
if (BukkitReflection.getMinorVersion() >= 17) {
return PacketEntityView.class.getClassLoader().loadClass("net.minecraft." + name);
} else {
return PacketEntityView.class.getClassLoader().loadClass("net.minecraft.server." + BukkitReflection.getServerPackage() + "." + name);
}
} catch (ClassNotFoundException | NullPointerException e) {
// Wrong class name
}
}
throw new ClassNotFoundException("No class found with potential names " + Arrays.toString(names));
}
}
@@ -1,81 +0,0 @@
package me.neznamy.tab.platforms.bukkit.nms.datawatcher;
import lombok.Getter;
import lombok.SneakyThrows;
import lombok.ToString;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import me.neznamy.tab.shared.backend.EntityData;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
/**
* A class representing the n.m.s.DataWatcher class to make work with it much easier
*/
@ToString
public class DataWatcher implements EntityData {
/** NMS Fields */
public static Class<?> CLASS;
public static Constructor<?> CONSTRUCTOR;
public static Method REGISTER;
public static Method markDirty;
public static Method packDirty;
/** Watched data */
private final Map<Integer, DataWatcherItem> dataValues = new HashMap<>();
/** Helper for easier data write */
@Getter private final DataWatcherHelper helper = new DataWatcherHelper(this);
/**
* Loads all required Fields and throws Exception if something went wrong
*
* @param nms
* NMS storage reference
*/
public static void load(NMSStorage nms) {
CONSTRUCTOR = ReflectionUtils.getOnlyConstructor(CLASS);
if (nms.is1_19_3Plus()) {
markDirty = ReflectionUtils.getOnlyMethod(CLASS, void.class, DataWatcherObject.CLASS);
}
}
/**
* Sets value into data values
*
* @param type
* type of value
* @param value
* value
*/
public void setValue(@NotNull DataWatcherObject type, @NotNull Object value) {
dataValues.put(type.getPosition(), new DataWatcherItem(type, value));
}
/**
* Converts the class into an instance of NMS.DataWatcher
*
* @return an instance of NMS.DataWatcher with same data
*/
@SneakyThrows
public @NotNull Object build() {
NMSStorage nms = NMSStorage.getInstance();
Object nmsWatcher;
if (CONSTRUCTOR.getParameterCount() == 1) { //1.7+
nmsWatcher = CONSTRUCTOR.newInstance(new Object[] {null});
} else {
nmsWatcher = CONSTRUCTOR.newInstance();
}
for (DataWatcherItem item : dataValues.values()) {
Object nmsObject = item.getType().build();
REGISTER.invoke(nmsWatcher, nmsObject, item.getValue());
if (nms.is1_19_3Plus()) markDirty.invoke(nmsWatcher, nmsObject);
}
return nmsWatcher;
}
}
@@ -1,138 +0,0 @@
package me.neznamy.tab.platforms.bukkit.nms.datawatcher;
import lombok.AllArgsConstructor;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import me.neznamy.tab.shared.TAB;
import org.jetbrains.annotations.NotNull;
import java.util.Optional;
/**
* A class to help to assign DataWatcher items as positions often change per-version
*/
@AllArgsConstructor
public class DataWatcherHelper {
/** NMS Fields */
public static Class<?> DataWatcherRegistry;
public static Class<?> DataWatcherSerializer;
public static Object DataWatcherSerializer_BYTE;
public static Object DataWatcherSerializer_FLOAT;
public static Object DataWatcherSerializer_STRING;
public static Object DataWatcherSerializer_OPTIONAL_COMPONENT;
public static Object DataWatcherSerializer_BOOLEAN;
/** Instance fields */
private final int armorStandFlagsPosition = getArmorStandFlagsPosition();
@NotNull private final DataWatcher data;
/**
* Returns armor stand flags position based on server version
*
* @return armor stand flags position based on server version
*/
private int getArmorStandFlagsPosition() {
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 17) {
//1.17.x, 1.18.x, 1.19.x
return 15;
} else if (TAB.getInstance().getServerVersion().getMinorVersion() >= 15) {
//1.15.x, 1.16.x
return 14;
} else if (TAB.getInstance().getServerVersion().getMinorVersion() >= 14) {
//1.14.x
return 13;
} else if (TAB.getInstance().getServerVersion().getMinorVersion() >= 10) {
//1.10.x - 1.13.x
return 11;
} else {
//1.8.x - 1.9.x
return 10;
}
}
/**
* Writes entity byte flags
*
* @param flags
* flags to write
*/
public void setEntityFlags(byte flags) {
data.setValue(new DataWatcherObject(0, DataWatcherSerializer_BYTE), flags);
}
/**
* Writes entity custom name with position based on server version and value depending on client version (RGB or not)
*
* @param customName
* target custom name
* @param clientVersion
* client version
*/
public void setCustomName(@NotNull String customName, @NotNull ProtocolVersion clientVersion) {
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 13) {
data.setValue(new DataWatcherObject(2, DataWatcherSerializer_OPTIONAL_COMPONENT),
Optional.ofNullable(NMSStorage.getInstance().toNMSComponent(IChatBaseComponent.optimizedComponent(customName), clientVersion)));
} else if (TAB.getInstance().getServerVersion().getMinorVersion() >= 8) {
data.setValue(new DataWatcherObject(2, DataWatcherSerializer_STRING), customName);
} else {
//name length is limited to 64 characters on <1.8
String cutName = (customName.length() > 64 ? customName.substring(0, 64) : customName);
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 6) {
data.setValue(new DataWatcherObject(10, null), cutName);
} else {
data.setValue(new DataWatcherObject(5, null), cutName);
}
}
}
/**
* Writes custom name visibility boolean
*
* @param visible
* if visible or not
*/
public void setCustomNameVisible(boolean visible) {
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 9) {
data.setValue(new DataWatcherObject(3, DataWatcherSerializer_BOOLEAN), visible);
} else {
data.setValue(new DataWatcherObject(3, null), (byte)(visible?1:0));
}
}
/**
* Writes entity health
*
* @param health
* health of entity
*/
public void setHealth(float health) {
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 6) {
data.setValue(new DataWatcherObject(6, DataWatcherSerializer_FLOAT), health);
} else {
data.setValue(new DataWatcherObject(16, null), (int)health);
}
}
/**
* Writes armor stand flags
*
* @param flags
* flags to write
*/
public void setArmorStandFlags(byte flags) {
data.setValue(new DataWatcherObject(armorStandFlagsPosition, DataWatcherSerializer_BYTE), flags);
}
/**
* Writes wither invulnerable time
* @param time
* Time, apparently
*/
public void setWitherInvulnerableTime(int time) {
if (TAB.getInstance().getServerVersion().getMinorVersion() > 8)
throw new UnsupportedOperationException("Not supported on 1.9+");
data.setValue(new DataWatcherObject(20, null), time);
}
}
@@ -1,39 +0,0 @@
package me.neznamy.tab.platforms.bukkit.nms.datawatcher;
import lombok.Data;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
/**
* Class representing NMS Data Watcher Item
*/
@Data
public class DataWatcherItem {
/** NMS Fields */
public static Class<?> CLASS;
public static Field TYPE;
public static Field VALUE;
/** Instance fields */
@NotNull private final DataWatcherObject type;
@NotNull private final Object value;
/**
* Loads all required Fields
*
* @param nms
* NMS storage reference
*/
public static void load(NMSStorage nms) {
VALUE = ReflectionUtils.getFields(CLASS, Object.class).get(0);
if (nms.getMinorVersion() >= 9) {
TYPE = ReflectionUtils.getOnlyField(CLASS, DataWatcherObject.CLASS);
} else {
TYPE = ReflectionUtils.getFields(CLASS, int.class).get(1);
}
}
}
@@ -1,54 +0,0 @@
package me.neznamy.tab.platforms.bukkit.nms.datawatcher;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
/**
* A class representing the n.m.s.DataWatcherObject class to make work with it much easier
*/
@Data @AllArgsConstructor
public class DataWatcherObject {
/** NMS Fields */
public static Class<?> CLASS;
public static Constructor<?> CONSTRUCTOR;
public static Field SLOT;
public static Field SERIALIZER;
/** Instance fields */
private final int position;
@Nullable private final Object serializer;
/**
* Loads all required Fields and throws Exception if something went wrong
*
* @throws NoSuchMethodException
* If something fails
*/
public static void load() throws NoSuchMethodException {
CONSTRUCTOR = CLASS.getConstructor(int.class, DataWatcherHelper.DataWatcherSerializer);
SLOT = ReflectionUtils.getOnlyField(CLASS, int.class);
SERIALIZER = ReflectionUtils.getOnlyField(CLASS, DataWatcherHelper.DataWatcherSerializer);
}
/**
* Converts the object into NMS object
*
* @return NMS object
*/
@SneakyThrows
public Object build() {
if (NMSStorage.getInstance().getMinorVersion() >= 9) {
return DataWatcherObject.CONSTRUCTOR.newInstance(position, serializer);
} else {
return position;
}
}
}
@@ -1,139 +0,0 @@
package me.neznamy.tab.platforms.bukkit.nms.storage.nms;
import me.neznamy.tab.platforms.bukkit.BukkitTabList;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherHelper;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherItem;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherObject;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.*;
import me.neznamy.tab.platforms.bukkit.scoreboard.PacketScoreboard;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
/**
* NMS loader for Minecraft 1.5.2 - 1.16.5 using Bukkit mapping.
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public class BukkitLegacyNMSStorage extends NMSStorage {
/**
* Returns class with given potential names in same order
*
* @param names
* possible class names
* @return class for specified names
* @throws ClassNotFoundException
* if class does not exist
*/
private Class<?> getLegacyClass(@NotNull String... names) throws ClassNotFoundException {
for (String name : names) {
try {
return getLegacyClass(name);
} catch (ClassNotFoundException e) {
//not the first class name in array
}
}
throw new ClassNotFoundException("No class found with possible names " + Arrays.toString(names));
}
/**
* Returns class from given name
*
* @param name
* class name
* @return class from given name
* @throws ClassNotFoundException
* if class was not found
*/
public Class<?> getLegacyClass(@NotNull String name) throws ClassNotFoundException {
return Class.forName("net.minecraft.server." + serverPackage + "." + name);
}
@Override
public void loadNamedFieldsAndMethods() throws ReflectiveOperationException {
PacketScoreboard.ScoreboardScore_setScore = ReflectionUtils.getMethod(PacketScoreboard.ScoreboardScoreClass, new String[] {"setScore", "c"}, int.class); // 1.5.1+, 1.5
PacketScoreboard.ScoreboardTeam_setAllowFriendlyFire = ReflectionUtils.getMethod(PacketScoreboard.ScoreboardTeam, new String[] {"setAllowFriendlyFire", "a"}, boolean.class); // 1.5.1+, 1.5
PacketScoreboard.ScoreboardTeam_setCanSeeFriendlyInvisibles = ReflectionUtils.getMethod(PacketScoreboard.ScoreboardTeam, new String[] {"setCanSeeFriendlyInvisibles", "b"}, boolean.class); // 1.5.1+, 1.5
if (minorVersion >= 7) {
ChatSerializer_DESERIALIZE = ChatSerializer.getMethod("a", String.class);
}
if (minorVersion >= 8) {
PacketScoreboard.ScoreboardTeam_setNameTagVisibility = ReflectionUtils.getMethod(PacketScoreboard.ScoreboardTeam, new String[] {"setNameTagVisibility", "a"}, PacketScoreboard.EnumNameTagVisibility); // {1.8.1+, 1.8}
}
if (minorVersion >= 9) {
DataWatcher.REGISTER = DataWatcher.CLASS.getMethod("register", DataWatcherObject.CLASS, Object.class);
DataWatcherHelper.DataWatcherSerializer_BYTE = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("a").get(null);
DataWatcherHelper.DataWatcherSerializer_FLOAT = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("c").get(null);
DataWatcherHelper.DataWatcherSerializer_STRING = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("d").get(null);
if (getMinorVersion() >= 13) {
DataWatcherHelper.DataWatcherSerializer_OPTIONAL_COMPONENT = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("f").get(null);
DataWatcherHelper.DataWatcherSerializer_BOOLEAN = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("i").get(null);
} else {
DataWatcherHelper.DataWatcherSerializer_BOOLEAN = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("h").get(null);
}
} else {
DataWatcher.REGISTER = DataWatcher.CLASS.getMethod("a", int.class, Object.class);
}
if (minorVersion >= 13) {
PacketScoreboard.ScoreboardTeam_setPrefix = PacketScoreboard.ScoreboardTeam.getMethod("setPrefix", IChatBaseComponent);
PacketScoreboard.ScoreboardTeam_setSuffix = PacketScoreboard.ScoreboardTeam.getMethod("setSuffix", IChatBaseComponent);
} else {
PacketScoreboard.ScoreboardTeam_setPrefix = ReflectionUtils.getMethod(PacketScoreboard.ScoreboardTeam, new String[] {"setPrefix", "b"}, String.class); // 1.5.1+, 1.5
PacketScoreboard.ScoreboardTeam_setSuffix = ReflectionUtils.getMethod(PacketScoreboard.ScoreboardTeam, new String[] {"setSuffix", "c"}, String.class); // 1.5.1+, 1.5
}
}
@Override
public void loadClasses() throws ClassNotFoundException {
EntityHuman = getLegacyClass("EntityHuman");
World = getLegacyClass("World");
Packet = getLegacyClass("Packet");
EnumChatFormat = (Class<Enum>) getLegacyClass("EnumChatFormat");
EntityPlayer = getLegacyClass("EntityPlayer");
Entity = getLegacyClass("Entity");
EntityLiving = getLegacyClass("EntityLiving");
PlayerConnection = getLegacyClass("PlayerConnection");
NetworkManager = getLegacyClass("NetworkManager");
DataWatcher.CLASS = getLegacyClass("DataWatcher");
DataWatcherItem.CLASS = getLegacyClass("DataWatcher$Item", "DataWatcher$WatchableObject", "WatchableObject");
PacketPlayOutSpawnEntityLivingStorage.CLASS = getLegacyClass("PacketPlayOutSpawnEntityLiving", "Packet24MobSpawn");
PacketPlayOutEntityTeleportStorage.CLASS = getLegacyClass("PacketPlayOutEntityTeleport", "Packet34EntityTeleport");
PacketPlayOutEntity = getLegacyClass("PacketPlayOutEntity", "Packet30Entity");
PacketPlayOutEntityDestroyStorage.CLASS = getLegacyClass("PacketPlayOutEntityDestroy", "Packet29DestroyEntity");
PacketPlayOutEntityLook = getLegacyClass("PacketPlayOutEntity$PacketPlayOutEntityLook", "PacketPlayOutEntityLook", "Packet32EntityLook");
PacketPlayOutEntityMetadataStorage.CLASS = getLegacyClass("PacketPlayOutEntityMetadata", "Packet40EntityMetadata");
PacketPlayOutNamedEntitySpawn = getLegacyClass("PacketPlayOutNamedEntitySpawn", "Packet20NamedEntitySpawn");
PacketScoreboard.DisplayObjectiveClass = getLegacyClass("PacketPlayOutScoreboardDisplayObjective", "Packet208SetScoreboardDisplayObjective");
PacketScoreboard.ObjectivePacketClass = getLegacyClass("PacketPlayOutScoreboardObjective", "Packet206SetScoreboardObjective");
PacketScoreboard.TeamPacketClass = getLegacyClass("PacketPlayOutScoreboardTeam", "Packet209SetScoreboardTeam");
PacketScoreboard.ScorePacketClass = getLegacyClass("PacketPlayOutScoreboardScore", "Packet207SetScoreboardScore");
PacketScoreboard.Scoreboard = getLegacyClass("Scoreboard");
PacketScoreboard.ScoreboardObjective = getLegacyClass("ScoreboardObjective");
PacketScoreboard.ScoreboardScoreClass = getLegacyClass("ScoreboardScore");
PacketScoreboard.IScoreboardCriteria = getLegacyClass("IScoreboardCriteria", "IObjective"); // 1.5.1+, 1.5
PacketScoreboard.ScoreboardTeam = getLegacyClass("ScoreboardTeam");
if (minorVersion >= 7) {
IChatBaseComponent = getLegacyClass("IChatBaseComponent");
ChatSerializer = getLegacyClass("IChatBaseComponent$ChatSerializer", "ChatSerializer");
}
if (minorVersion >= 8) {
BukkitTabList.PacketPlayOutPlayerListHeaderFooterClass = getLegacyClass("PacketPlayOutPlayerListHeaderFooter");
EntityArmorStand = getLegacyClass("EntityArmorStand");
BukkitTabList.PacketPlayOutPlayerInfoClass = getLegacyClass("PacketPlayOutPlayerInfo");
BukkitTabList.EnumPlayerInfoActionClass = (Class<Enum>) getLegacyClass("PacketPlayOutPlayerInfo$EnumPlayerInfoAction", "EnumPlayerInfoAction");
BukkitTabList.PlayerInfoDataClass = getLegacyClass("PacketPlayOutPlayerInfo$PlayerInfoData", "PlayerInfoData");
BukkitTabList.EnumGamemodeClass = (Class<Enum>) getLegacyClass("EnumGamemode", "WorldSettings$EnumGamemode");
PacketScoreboard.EnumScoreboardHealthDisplay = (Class<Enum>) getLegacyClass("IScoreboardCriteria$EnumScoreboardHealthDisplay", "EnumScoreboardHealthDisplay");
PacketScoreboard.EnumScoreboardAction = (Class<Enum>) getLegacyClass("ScoreboardServer$Action", "PacketPlayOutScoreboardScore$EnumScoreboardAction", "EnumScoreboardAction");
PacketScoreboard.EnumNameTagVisibility = (Class<Enum>) getLegacyClass("ScoreboardTeamBase$EnumNameTagVisibility", "EnumNameTagVisibility");
}
if (minorVersion >= 9) {
DataWatcherObject.CLASS = getLegacyClass("DataWatcherObject");
DataWatcherHelper.DataWatcherRegistry = getLegacyClass("DataWatcherRegistry");
DataWatcherHelper.DataWatcherSerializer = getLegacyClass("DataWatcherSerializer");
PacketScoreboard.EnumTeamPush = (Class<Enum>) getLegacyClass("ScoreboardTeamBase$EnumTeamPush");
}
}
}
@@ -1,107 +0,0 @@
package me.neznamy.tab.platforms.bukkit.nms.storage.nms;
import me.neznamy.tab.platforms.bukkit.BukkitTabList;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherHelper;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherItem;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherObject;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.*;
import me.neznamy.tab.platforms.bukkit.scoreboard.PacketScoreboard;
import me.neznamy.tab.shared.util.ReflectionUtils;
/**
* NMS loader for minecraft 1.17+ using Mojang packaging and bukkit names.
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public class BukkitModernNMSStorage extends NMSStorage {
@Override
public void loadNamedFieldsAndMethods() throws ReflectiveOperationException {
ChatSerializer_DESERIALIZE = ChatSerializer.getMethod("a", String.class);
DataWatcher.REGISTER = ReflectionUtils.getMethod(DataWatcher.CLASS, new String[]{"register", "a"}, DataWatcherObject.CLASS, Object.class); // {Bukkit, Bukkit 1.18+}
PacketScoreboard.ScoreboardScore_setScore = ReflectionUtils.getMethod(PacketScoreboard.ScoreboardScoreClass, new String[]{"setScore", "b"}, int.class); // {Bukkit, Bukkit 1.18+}
PacketScoreboard.ScoreboardTeam_setAllowFriendlyFire = ReflectionUtils.getMethod(PacketScoreboard.ScoreboardTeam, new String[]{"setAllowFriendlyFire", "a"}, boolean.class); // {Bukkit, Bukkit 1.18+}
PacketScoreboard.ScoreboardTeam_setCanSeeFriendlyInvisibles = ReflectionUtils.getMethod(PacketScoreboard.ScoreboardTeam, new String[]{"setCanSeeFriendlyInvisibles", "b"}, boolean.class); // {Bukkit, Bukkit 1.18+}
PacketScoreboard.ScoreboardTeam_setPrefix = ReflectionUtils.getMethod(PacketScoreboard.ScoreboardTeam, new String[]{"setPrefix", "b"}, IChatBaseComponent); // {Bukkit, Bukkit 1.18+}
PacketScoreboard.ScoreboardTeam_setSuffix = ReflectionUtils.getMethod(PacketScoreboard.ScoreboardTeam, new String[]{"setSuffix", "c"}, IChatBaseComponent); // {Bukkit, Bukkit 1.18+}
PacketScoreboard.ScoreboardTeam_setNameTagVisibility = ReflectionUtils.getMethod(PacketScoreboard.ScoreboardTeam, new String[]{"setNameTagVisibility", "a"}, PacketScoreboard.EnumNameTagVisibility); // {Bukkit, Bukkit 1.18+}
DataWatcherHelper.DataWatcherSerializer_BYTE = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("a").get(null);
DataWatcherHelper.DataWatcherSerializer_FLOAT = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("c").get(null);
DataWatcherHelper.DataWatcherSerializer_STRING = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("d").get(null);
if (is1_19_3Plus()) {
DataWatcherHelper.DataWatcherSerializer_OPTIONAL_COMPONENT = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("g").get(null);
if (is1_19_4Plus()) {
DataWatcherHelper.DataWatcherSerializer_BOOLEAN = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("k").get(null);
} else {
DataWatcherHelper.DataWatcherSerializer_BOOLEAN = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("j").get(null);
}
} else {
DataWatcherHelper.DataWatcherSerializer_OPTIONAL_COMPONENT = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("f").get(null);
DataWatcherHelper.DataWatcherSerializer_BOOLEAN = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("i").get(null);
}
if (minorVersion >= 19) {
PacketPlayOutSpawnEntityLivingStorage.EntityTypes_ARMOR_STAND = PacketPlayOutSpawnEntityLivingStorage.EntityTypes.getDeclaredField("d").get(null);
(PacketPlayOutSpawnEntityLivingStorage.ENTITY_TYPE = PacketPlayOutSpawnEntityLivingStorage.CLASS.getDeclaredField("e")).setAccessible(true);
DataWatcher.packDirty = DataWatcher.CLASS.getMethod("b");
}
}
@Override
public void loadClasses() throws ClassNotFoundException {
ChatSerializer = Class.forName("net.minecraft.network.chat.IChatBaseComponent$ChatSerializer");
World = Class.forName("net.minecraft.world.level.World");
EntityArmorStand = Class.forName("net.minecraft.world.entity.decoration.EntityArmorStand");
EntityHuman = Class.forName("net.minecraft.world.entity.player.EntityHuman");
NetworkManager = Class.forName("net.minecraft.network.NetworkManager");
IChatBaseComponent = Class.forName("net.minecraft.network.chat.IChatBaseComponent");
Packet = Class.forName("net.minecraft.network.protocol.Packet");
EnumChatFormat = (Class<Enum>) Class.forName("net.minecraft.EnumChatFormat");
EntityPlayer = Class.forName("net.minecraft.server.level.EntityPlayer");
Entity = Class.forName("net.minecraft.world.entity.Entity");
EntityLiving = Class.forName("net.minecraft.world.entity.EntityLiving");
PlayerConnection = Class.forName("net.minecraft.server.network.PlayerConnection");
BukkitTabList.PacketPlayOutPlayerListHeaderFooterClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutPlayerListHeaderFooter");
PacketScoreboard.DisplayObjectiveClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutScoreboardDisplayObjective");
PacketScoreboard.ObjectivePacketClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutScoreboardObjective");
PacketScoreboard.Scoreboard = Class.forName("net.minecraft.world.scores.Scoreboard");
PacketScoreboard.ScorePacketClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutScoreboardScore");
PacketScoreboard.ScoreboardObjective = Class.forName("net.minecraft.world.scores.ScoreboardObjective");
PacketScoreboard.ScoreboardScoreClass = Class.forName("net.minecraft.world.scores.ScoreboardScore");
PacketScoreboard.IScoreboardCriteria = Class.forName("net.minecraft.world.scores.criteria.IScoreboardCriteria");
PacketScoreboard.EnumScoreboardHealthDisplay = (Class<Enum>) Class.forName("net.minecraft.world.scores.criteria.IScoreboardCriteria$EnumScoreboardHealthDisplay");
PacketScoreboard.EnumScoreboardAction = (Class<Enum>) Class.forName("net.minecraft.server.ScoreboardServer$Action");
PacketScoreboard.TeamPacketClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutScoreboardTeam");
PacketScoreboard.ScoreboardTeam = Class.forName("net.minecraft.world.scores.ScoreboardTeam");
PacketScoreboard.EnumNameTagVisibility = (Class<Enum>) Class.forName("net.minecraft.world.scores.ScoreboardTeamBase$EnumNameTagVisibility");
PacketScoreboard.EnumTeamPush = (Class<Enum>) Class.forName("net.minecraft.world.scores.ScoreboardTeamBase$EnumTeamPush");
DataWatcher.CLASS = Class.forName("net.minecraft.network.syncher.DataWatcher");
DataWatcherItem.CLASS = Class.forName("net.minecraft.network.syncher.DataWatcher$Item");
DataWatcherObject.CLASS = Class.forName("net.minecraft.network.syncher.DataWatcherObject");
DataWatcherHelper.DataWatcherRegistry = Class.forName("net.minecraft.network.syncher.DataWatcherRegistry");
DataWatcherHelper.DataWatcherSerializer = Class.forName("net.minecraft.network.syncher.DataWatcherSerializer");
PacketPlayOutEntityTeleportStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutEntityTeleport");
PacketPlayOutEntity = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutEntity");
PacketPlayOutEntityDestroyStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutEntityDestroy");
PacketPlayOutEntityLook = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutEntity$PacketPlayOutEntityLook");
PacketPlayOutEntityMetadataStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutEntityMetadata");
PacketPlayOutNamedEntitySpawn = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutNamedEntitySpawn");
BukkitTabList.EnumGamemodeClass = (Class<Enum>) Class.forName("net.minecraft.world.level.EnumGamemode");
if (minorVersion >= 19) {
PacketPlayOutSpawnEntityLivingStorage.EntityTypes = Class.forName("net.minecraft.world.entity.EntityTypes");
PacketPlayOutSpawnEntityLivingStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutSpawnEntity");
} else {
PacketPlayOutSpawnEntityLivingStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutSpawnEntityLiving");
}
if (is1_19_3Plus()) {
BukkitTabList.ClientboundPlayerInfoRemovePacket = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket");
BukkitTabList.PacketPlayOutPlayerInfoClass = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket");
BukkitTabList.EnumPlayerInfoActionClass = (Class<Enum>) Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket$a");
BukkitTabList.PlayerInfoDataClass = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket$b");
BukkitTabList.RemoteChatSession$Data = Class.forName("net.minecraft.network.chat.RemoteChatSession$a");
} else {
BukkitTabList.PacketPlayOutPlayerInfoClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutPlayerInfo");
BukkitTabList.EnumPlayerInfoActionClass = (Class<Enum>) Class.forName("net.minecraft.network.protocol.game.PacketPlayOutPlayerInfo$EnumPlayerInfoAction");
BukkitTabList.PlayerInfoDataClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutPlayerInfo$PlayerInfoData");
}
}
}
@@ -1,97 +0,0 @@
package me.neznamy.tab.platforms.bukkit.nms.storage.nms;
import me.neznamy.tab.platforms.bukkit.BukkitTabList;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherHelper;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherItem;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherObject;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.*;
import me.neznamy.tab.platforms.bukkit.scoreboard.PacketScoreboard;
/**
* NMS loader for minecraft 1.17+ using Mojang packaging and names.
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public class MojangModernNMSStorage extends NMSStorage {
@Override
public void loadNamedFieldsAndMethods() throws ReflectiveOperationException {
ChatSerializer_DESERIALIZE = ChatSerializer.getMethod("fromJson", String.class);
DataWatcher.REGISTER = DataWatcher.CLASS.getMethod("define", DataWatcherObject.CLASS, Object.class);
PacketScoreboard.ScoreboardScore_setScore = PacketScoreboard.ScoreboardScoreClass.getMethod("setScore", int.class);
PacketScoreboard.ScoreboardTeam_setAllowFriendlyFire = PacketScoreboard.ScoreboardTeam.getMethod("setAllowFriendlyFire", boolean.class);
PacketScoreboard.ScoreboardTeam_setCanSeeFriendlyInvisibles = PacketScoreboard.ScoreboardTeam.getMethod("setSeeFriendlyInvisibles", boolean.class);
PacketScoreboard.ScoreboardTeam_setPrefix = PacketScoreboard.ScoreboardTeam.getMethod("setPlayerPrefix", IChatBaseComponent);
PacketScoreboard.ScoreboardTeam_setSuffix = PacketScoreboard.ScoreboardTeam.getMethod("setPlayerSuffix", IChatBaseComponent);
PacketScoreboard.ScoreboardTeam_setNameTagVisibility = PacketScoreboard.ScoreboardTeam.getMethod("setNameTagVisibility", PacketScoreboard.EnumNameTagVisibility);
DataWatcherHelper.DataWatcherSerializer_BYTE = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("BYTE").get(null);
DataWatcherHelper.DataWatcherSerializer_FLOAT = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("FLOAT").get(null);
DataWatcherHelper.DataWatcherSerializer_STRING = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("STRING").get(null);
DataWatcherHelper.DataWatcherSerializer_OPTIONAL_COMPONENT = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("OPTIONAL_COMPONENT").get(null);
DataWatcherHelper.DataWatcherSerializer_BOOLEAN = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("BOOLEAN").get(null);
if (minorVersion >= 19) {
PacketPlayOutSpawnEntityLivingStorage.EntityTypes_ARMOR_STAND = PacketPlayOutSpawnEntityLivingStorage.EntityTypes.getDeclaredField("ARMOR_STAND").get(null);
(PacketPlayOutSpawnEntityLivingStorage.ENTITY_TYPE = PacketPlayOutSpawnEntityLivingStorage.CLASS.getDeclaredField("type")).setAccessible(true);
DataWatcher.packDirty = DataWatcher.CLASS.getMethod("packDirty");
}
}
@Override
public void loadClasses() throws ClassNotFoundException {
IChatBaseComponent = Class.forName("net.minecraft.network.chat.Component");
ChatSerializer = Class.forName("net.minecraft.network.chat.Component$Serializer");
World = Class.forName("net.minecraft.world.level.Level");
EntityArmorStand = Class.forName("net.minecraft.world.entity.decoration.ArmorStand");
EntityHuman = Class.forName("net.minecraft.world.entity.player.Player");
NetworkManager = Class.forName("net.minecraft.network.Connection");
Packet = Class.forName("net.minecraft.network.protocol.Packet");
EnumChatFormat = (Class<Enum>) Class.forName("net.minecraft.ChatFormatting");
EntityPlayer = Class.forName("net.minecraft.server.level.ServerPlayer");
Entity = Class.forName("net.minecraft.world.entity.Entity");
EntityLiving = Class.forName("net.minecraft.world.entity.LivingEntity");
PlayerConnection = Class.forName("net.minecraft.server.network.ServerGamePacketListenerImpl");
BukkitTabList.PacketPlayOutPlayerListHeaderFooterClass = Class.forName("net.minecraft.network.protocol.game.ClientboundTabListPacket");
DataWatcher.CLASS = Class.forName("net.minecraft.network.syncher.SynchedEntityData");
DataWatcherItem.CLASS = Class.forName("net.minecraft.network.syncher.SynchedEntityData$DataItem");
DataWatcherObject.CLASS = Class.forName("net.minecraft.network.syncher.EntityDataAccessor");
DataWatcherHelper.DataWatcherRegistry = Class.forName("net.minecraft.network.syncher.EntityDataSerializers");
DataWatcherHelper.DataWatcherSerializer = Class.forName("net.minecraft.network.syncher.EntityDataSerializer");
PacketPlayOutEntityTeleportStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.ClientboundTeleportEntityPacket");
PacketPlayOutEntity = Class.forName("net.minecraft.network.protocol.game.ClientboundMoveEntityPacket");
PacketPlayOutEntityDestroyStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.ClientboundRemoveEntitiesPacket");
PacketPlayOutEntityLook = Class.forName("net.minecraft.network.protocol.game.ClientboundMoveEntityPacket$Rot");
PacketPlayOutEntityMetadataStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket");
PacketPlayOutNamedEntitySpawn = Class.forName("net.minecraft.network.protocol.game.ClientboundAddPlayerPacket");
BukkitTabList.EnumGamemodeClass = (Class<Enum>) Class.forName("net.minecraft.world.level.GameType");
PacketScoreboard.DisplayObjectiveClass = Class.forName("net.minecraft.network.protocol.game.ClientboundSetDisplayObjectivePacket");
PacketScoreboard.ObjectivePacketClass = Class.forName("net.minecraft.network.protocol.game.ClientboundSetObjectivePacket");
PacketScoreboard.Scoreboard = Class.forName("net.minecraft.world.scores.Scoreboard");
PacketScoreboard.ScorePacketClass = Class.forName("net.minecraft.network.protocol.game.ClientboundSetScorePacket");
PacketScoreboard.ScoreboardObjective = Class.forName("net.minecraft.world.scores.Objective");
PacketScoreboard.ScoreboardScoreClass = Class.forName("net.minecraft.world.scores.Score");
PacketScoreboard.IScoreboardCriteria = Class.forName("net.minecraft.world.scores.criteria.ObjectiveCriteria");
PacketScoreboard.EnumScoreboardHealthDisplay = (Class<Enum>) Class.forName("net.minecraft.world.scores.criteria.ObjectiveCriteria$RenderType");
PacketScoreboard.EnumScoreboardAction = (Class<Enum>) Class.forName("net.minecraft.server.ServerScoreboard$Method");
PacketScoreboard.TeamPacketClass = Class.forName("net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket");
PacketScoreboard.ScoreboardTeam = Class.forName("net.minecraft.world.scores.PlayerTeam");
PacketScoreboard.EnumNameTagVisibility = (Class<Enum>) Class.forName("net.minecraft.world.scores.Team$Visibility");
PacketScoreboard.EnumTeamPush = (Class<Enum>) Class.forName("net.minecraft.world.scores.Team$CollisionRule");
if (minorVersion >= 19) {
PacketPlayOutSpawnEntityLivingStorage.EntityTypes = Class.forName("net.minecraft.world.entity.EntityType");
PacketPlayOutSpawnEntityLivingStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.ClientboundAddEntityPacket");
} else {
PacketPlayOutSpawnEntityLivingStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.ClientboundAddMobPacket");
}
if (is1_19_3Plus()) {
BukkitTabList.ClientboundPlayerInfoRemovePacket = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket");
BukkitTabList.PacketPlayOutPlayerInfoClass = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket");
BukkitTabList.EnumPlayerInfoActionClass = (Class<Enum>) Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket$Action");
BukkitTabList.PlayerInfoDataClass = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket$Entry");
BukkitTabList.RemoteChatSession$Data = Class.forName("net.minecraft.network.chat.RemoteChatSession$Data");
} else {
BukkitTabList.PacketPlayOutPlayerInfoClass = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket");
BukkitTabList.EnumPlayerInfoActionClass = (Class<Enum>) Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket$Action");
BukkitTabList.PlayerInfoDataClass = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket$PlayerUpdate");
}
}
}
@@ -1,162 +0,0 @@
package me.neznamy.tab.platforms.bukkit.nms.storage.nms;
import com.mojang.authlib.GameProfile;
import io.netty.channel.Channel;
import lombok.Getter;
import lombok.Setter;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.BukkitTabList;
import me.neznamy.tab.platforms.bukkit.scoreboard.PacketScoreboard;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.util.ComponentCache;
import me.neznamy.tab.shared.util.ReflectionUtils;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherItem;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherObject;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.*;
import org.bukkit.Bukkit;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* Class holding all NMS classes, methods, fields and constructors used by TAB.
*/
@SuppressWarnings("rawtypes")
public abstract class NMSStorage {
/** Instance of this class */
@Getter @Setter private static NMSStorage instance;
/** Server's NMS/CraftBukkit package */
@Getter protected final String serverPackage = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3];
/** Server's minor version */
@Getter protected final int minorVersion = Integer.parseInt(serverPackage.split("_")[1]);
/** Flag determining whether the server version is at least 1.19.3 or not */
@Getter private final boolean is1_19_3Plus = ReflectionUtils.classExists("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket");
@Getter private final boolean is1_19_4Plus = is1_19_3Plus && !serverPackage.equals("v1_19_R2");
/** Basic universal values */
protected Class<?> Packet;
protected Class<?> NetworkManager;
protected Class<?> EntityArmorStand;
protected Class<?> World;
public Class<Enum> EnumChatFormat;
public Class<?> EntityPlayer;
protected Class<?> EntityHuman;
public Class<?> Entity;
public Class<?> EntityLiving;
protected Class<?> PlayerConnection;
// Ping field for 1.5.2 - 1.16.5, 1.17+ has Player#getPing()
public Field PING;
public Field PLAYER_CONNECTION;
public Field NETWORK_MANAGER;
public Field CHANNEL;
public Method getHandle;
public Method sendPacket;
public Method getProfile;
/** Chat components */
public Class<?> IChatBaseComponent;
protected Class<?> ChatSerializer;
public Method ChatSerializer_DESERIALIZE;
/** Other entity packets */
public Class<?> PacketPlayOutEntity;
public Field PacketPlayOutEntity_ENTITYID;
public Class<?> PacketPlayOutEntityLook;
public Class<?> PacketPlayOutNamedEntitySpawn;
public Field PacketPlayOutNamedEntitySpawn_ENTITYID;
public Object dummyEntity;
private final ComponentCache<IChatBaseComponent, Object> componentCache = new ComponentCache<>(10000,
(component, clientVersion) -> ChatSerializer_DESERIALIZE.invoke(null, component.toString(clientVersion)));
/**
* Constructs new instance and attempts to load all fields, methods and constructors.
*/
@SneakyThrows
public NMSStorage() {
ProtocolVersion.UNKNOWN_SERVER_VERSION.setMinorVersion(minorVersion); //fixing compatibility with forks that set version field value to "Unknown"
loadClasses();
if (minorVersion >= 7) {
NETWORK_MANAGER = ReflectionUtils.getOnlyField(PlayerConnection, NetworkManager);
sendPacket = ReflectionUtils.getMethods(PlayerConnection, void.class, Packet).get(0);
} else {
sendPacket = ReflectionUtils.getMethod(PlayerConnection, new String[]{"sendPacket"}, Packet);
}
if (minorVersion >= 8) {
CHANNEL = ReflectionUtils.getOnlyField(NetworkManager, Channel.class);
getProfile = ReflectionUtils.getOnlyMethod(EntityHuman, GameProfile.class);
Constructor<?> newEntityArmorStand = EntityArmorStand.getConstructor(World, double.class, double.class, double.class);
Method World_getHandle = Class.forName("org.bukkit.craftbukkit." + serverPackage + ".CraftWorld").getMethod("getHandle");
dummyEntity = newEntityArmorStand.newInstance(World_getHandle.invoke(Bukkit.getWorlds().get(0)), 0, 0, 0);
BukkitTabList.load(this);
}
if (minorVersion >= 9) {
DataWatcherObject.load();
}
PLAYER_CONNECTION = ReflectionUtils.getOnlyField(EntityPlayer, PlayerConnection);
getHandle = Class.forName("org.bukkit.craftbukkit." + serverPackage + ".entity.CraftPlayer").getMethod("getHandle");
DataWatcher.load(this);
DataWatcherItem.load(this);
PacketPlayOutEntityDestroyStorage.load();
PacketPlayOutEntityMetadataStorage.load(this);
PacketPlayOutEntityTeleportStorage.load(this);
PacketPlayOutSpawnEntityLivingStorage.load(this);
PacketScoreboard.load(this);
PacketPlayOutEntity_ENTITYID = ReflectionUtils.getFields(PacketPlayOutEntity, int.class).get(0);
PacketPlayOutNamedEntitySpawn_ENTITYID = ReflectionUtils.getFields(PacketPlayOutNamedEntitySpawn, int.class).get(0);
loadNamedFieldsAndMethods();
if (minorVersion < 17) {
try {
(PING = EntityPlayer.getDeclaredField("ping")).setAccessible(true); // 1.5.2 - 1.16.5
} catch (NoSuchFieldException e) {
(PING = EntityPlayer.getDeclaredField("field_71138_i")).setAccessible(true); // 1.7.10 Thermos
}
}
}
/**
* Loads all classes used by the plugin
*
* @throws ClassNotFoundException
* If a class was not found
*/
public abstract void loadClasses() throws ClassNotFoundException;
/**
* Loads all fields and methods which must be loaded by their name due to
* lack of other methods of reliably retrieving them.
*
* @throws ReflectiveOperationException
* If a field or method was not found
*/
public abstract void loadNamedFieldsAndMethods() throws ReflectiveOperationException;
/**
* Converts TAB's IChatBaseComponent into minecraft's component using String deserialization.
* If the requested component is found in cache, it is returned. If not, it is created, added into cache and returned.
* If {@code component} is {@code null}, returns {@code null}
*
* @param component
* component to convert
* @param clientVersion
* client version used to decide RGB conversion
* @return converted component or {@code null} if {@code component} is {@code null}
*/
public @Nullable Object toNMSComponent(@NotNull IChatBaseComponent component, @NotNull ProtocolVersion clientVersion) {
return componentCache.get(component, clientVersion);
}
}
@@ -1,31 +0,0 @@
package me.neznamy.tab.platforms.bukkit.nms.storage.nms;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
import me.neznamy.tab.platforms.bukkit.scoreboard.PacketScoreboard;
import org.jetbrains.annotations.NotNull;
/**
* NMS loader for Thermos 1.7.10.
*/
public class ThermosNMSStorage extends BukkitLegacyNMSStorage {
@Override
public Class<?> getLegacyClass(@NotNull String name) throws ClassNotFoundException {
try {
return getClass().getClassLoader().loadClass("net.minecraft.server." + serverPackage + "." + name);
} catch (NullPointerException e) {
// nested class not found
throw new ClassNotFoundException(name);
}
}
@Override
public void loadNamedFieldsAndMethods() throws ReflectiveOperationException {
PacketScoreboard.ScoreboardScore_setScore = PacketScoreboard.ScoreboardScoreClass.getMethod("func_96647_c", int.class);
PacketScoreboard.ScoreboardTeam_setAllowFriendlyFire = PacketScoreboard.ScoreboardTeam.getMethod("func_96660_a", boolean.class);
PacketScoreboard.ScoreboardTeam_setCanSeeFriendlyInvisibles = PacketScoreboard.ScoreboardTeam.getMethod("func_98300_b", boolean.class);
DataWatcher.REGISTER = DataWatcher.CLASS.getMethod("func_75682_a", int.class, Object.class);
PacketScoreboard.ScoreboardTeam_setPrefix = PacketScoreboard.ScoreboardTeam.getMethod("func_96666_b", String.class);
PacketScoreboard.ScoreboardTeam_setSuffix = PacketScoreboard.ScoreboardTeam.getMethod("func_96662_c", String.class);
}
}
@@ -1,33 +0,0 @@
package me.neznamy.tab.platforms.bukkit.nms.storage.packet;
import me.neznamy.tab.shared.util.ReflectionUtils;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
/**
* Custom class for holding data used in PacketPlayOutEntityDestroy minecraft packet.
*/
public class PacketPlayOutEntityDestroyStorage {
/** NMS Fields */
public static Class<?> CLASS;
public static Constructor<?> CONSTRUCTOR;
public static Field ENTITIES;
/**
* Loads all required Fields and throws Exception if something went wrong
*
* @throws NoSuchMethodException
* If something fails
*/
public static void load() throws NoSuchMethodException {
ENTITIES = ReflectionUtils.getOnlyField(CLASS);
try {
CONSTRUCTOR = CLASS.getConstructor(int[].class);
} catch (NoSuchMethodException e) {
//1.17.0
CONSTRUCTOR = CLASS.getConstructor(int.class);
}
}
}
@@ -1,33 +0,0 @@
package me.neznamy.tab.platforms.bukkit.nms.storage.packet;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import java.lang.reflect.Constructor;
import java.util.List;
/**
* Custom class for holding data used in PacketPlayOutEntityMetadata minecraft packet.
*/
public class PacketPlayOutEntityMetadataStorage {
/** NMS Fields */
public static Class<?> CLASS;
public static Constructor<?> CONSTRUCTOR;
/**
* Loads all required Fields and throws Exception if something went wrong
*
* @param nms
* NMS storage reference
* @throws NoSuchMethodException
* If something fails
*/
public static void load(NMSStorage nms) throws NoSuchMethodException {
if (nms.is1_19_3Plus()) {
CONSTRUCTOR = CLASS.getConstructor(int.class, List.class);
} else {
CONSTRUCTOR = CLASS.getConstructor(int.class, DataWatcher.CLASS, boolean.class);
}
}
}
@@ -1,87 +0,0 @@
package me.neznamy.tab.platforms.bukkit.nms.storage.packet;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import me.neznamy.tab.shared.backend.Location;
import me.neznamy.tab.shared.util.ReflectionUtils;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
/**
* Custom class for holding data used in PacketPlayOutEntityTeleport minecraft packet.
*/
public class PacketPlayOutEntityTeleportStorage {
/** NMS Fields */
public static Class<?> CLASS;
public static Constructor<?> CONSTRUCTOR;
public static Field ENTITY_ID;
public static Field X;
public static Field Y;
public static Field Z;
public static Field YAW;
public static Field PITCH;
/**
* Loads all required Fields and throws Exception if something went wrong
*
* @param nms
* NMS storage reference
* @throws NoSuchMethodException
* If something fails
*/
public static void load(NMSStorage nms) throws NoSuchMethodException {
ENTITY_ID = ReflectionUtils.getFields(CLASS, int.class).get(0);
YAW = ReflectionUtils.getFields(CLASS, byte.class).get(0);
PITCH = ReflectionUtils.getFields(CLASS, byte.class).get(1);
if (nms.getMinorVersion() >= 17) {
CONSTRUCTOR = CLASS.getConstructor(nms.Entity);
} else {
CONSTRUCTOR = CLASS.getConstructor();
}
if (nms.getMinorVersion() >= 9) {
X = ReflectionUtils.getFields(CLASS, double.class).get(0);
Y = ReflectionUtils.getFields(CLASS, double.class).get(1);
Z = ReflectionUtils.getFields(CLASS, double.class).get(2);
} else {
X = ReflectionUtils.getFields(CLASS, int.class).get(1);
Y = ReflectionUtils.getFields(CLASS, int.class).get(2);
Z = ReflectionUtils.getFields(CLASS, int.class).get(3);
}
}
/**
* Converts this class into NMS packet
*
* @return NMS packet
*/
@SneakyThrows
public static Object build(int entityId, Location location) {
NMSStorage nms = NMSStorage.getInstance();
Object nmsPacket;
if (nms.getMinorVersion() >= 17) {
nmsPacket = CONSTRUCTOR.newInstance(nms.dummyEntity);
} else {
nmsPacket = CONSTRUCTOR.newInstance();
}
ENTITY_ID.set(nmsPacket, entityId);
if (nms.getMinorVersion() >= 9) {
X.set(nmsPacket, location.getX());
Y.set(nmsPacket, location.getY());
Z.set(nmsPacket, location.getZ());
} else {
X.set(nmsPacket, floor(location.getX()*32));
Y.set(nmsPacket, floor(location.getY()*32));
Z.set(nmsPacket, floor(location.getZ()*32));
}
YAW.set(nmsPacket, (byte) (location.getYaw()/360*256));
PITCH.set(nmsPacket, (byte) (location.getPitch()/360*256));
return nmsPacket;
}
private static int floor(double paramDouble) {
int i = (int)paramDouble;
return paramDouble < i ? i - 1 : i;
}
}
@@ -1,133 +0,0 @@
package me.neznamy.tab.platforms.bukkit.nms.storage.packet;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import me.neznamy.tab.shared.backend.EntityData;
import me.neznamy.tab.shared.backend.Location;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.bukkit.entity.EntityType;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.EnumMap;
import java.util.UUID;
/**
* Custom class for holding data used in PacketPlayOutSpawnEntityLiving minecraft packet.
*/
public class PacketPlayOutSpawnEntityLivingStorage {
/** NMS Fields */
public static Class<?> CLASS;
public static Class<?> EntityTypes;
public static Constructor<?> CONSTRUCTOR;
public static Field ENTITY_ID;
public static Field ENTITY_TYPE;
public static Field YAW;
public static Field PITCH;
public static Field UUID;
public static Field X;
public static Field Y;
public static Field Z;
public static Field DATA_WATCHER;
public static Object EntityTypes_ARMOR_STAND;
/** Entity types */
public static final EnumMap<EntityType, Integer> entityIds = new EnumMap<>(EntityType.class);
/**
* Loads all required Fields and throws Exception if something went wrong
*
* @param nms
* NMS storage reference
* @throws NoSuchMethodException
* If something fails
*/
public static void load(NMSStorage nms) throws NoSuchMethodException {
if (nms.getMinorVersion() >= 19) {
entityIds.put(EntityType.ARMOR_STAND, 2);
} else if (nms.getMinorVersion() >= 13) {
entityIds.put(EntityType.ARMOR_STAND, 1);
} else {
entityIds.put(EntityType.WITHER, 64);
if (nms.getMinorVersion() >= 8) {
entityIds.put(EntityType.ARMOR_STAND, 30);
}
}
if (nms.getMinorVersion() >= 17) {
CONSTRUCTOR = CLASS.getConstructor(nms.is1_19_3Plus() ? nms.Entity : nms.EntityLiving);
} else {
CONSTRUCTOR = CLASS.getConstructor();
}
ENTITY_ID = ReflectionUtils.getFields(CLASS, int.class).get(0);
YAW = ReflectionUtils.getFields(CLASS, byte.class).get(0);
PITCH = ReflectionUtils.getFields(CLASS, byte.class).get(1);
if (nms.getMinorVersion() >= 9) {
UUID = ReflectionUtils.getOnlyField(CLASS, UUID.class);
if (nms.getMinorVersion() >= 19) {
X = ReflectionUtils.getFields(CLASS, double.class).get(2);
Y = ReflectionUtils.getFields(CLASS, double.class).get(3);
Z = ReflectionUtils.getFields(CLASS, double.class).get(4);
} else {
X = ReflectionUtils.getFields(CLASS, double.class).get(0);
Y = ReflectionUtils.getFields(CLASS, double.class).get(1);
Z = ReflectionUtils.getFields(CLASS, double.class).get(2);
}
} else {
X = ReflectionUtils.getFields(CLASS, int.class).get(2);
Y = ReflectionUtils.getFields(CLASS, int.class).get(3);
Z = ReflectionUtils.getFields(CLASS, int.class).get(4);
}
if (nms.getMinorVersion() < 19) {
ENTITY_TYPE = ReflectionUtils.getFields(CLASS, int.class).get(1);
}
if (nms.getMinorVersion() <= 14) {
DATA_WATCHER = ReflectionUtils.getOnlyField(CLASS, DataWatcher.CLASS);
}
}
/**
* Converts this class into NMS packet
*
* @return NMS packet
*/
@SneakyThrows
public static Object build(int entityId, UUID id, Object entityType, Location location, EntityData data) {
NMSStorage nms = NMSStorage.getInstance();
Object nmsPacket;
if (nms.getMinorVersion() >= 17) {
nmsPacket = CONSTRUCTOR.newInstance(nms.dummyEntity);
} else {
nmsPacket = CONSTRUCTOR.newInstance();
}
ENTITY_ID.set(nmsPacket, entityId);
YAW.set(nmsPacket, (byte)(location.getYaw() * 256.0f / 360.0f));
PITCH.set(nmsPacket, (byte)(location.getPitch() * 256.0f / 360.0f));
if (nms.getMinorVersion() <= 14) {
DATA_WATCHER.set(nmsPacket, data.build());
}
if (nms.getMinorVersion() >= 9) {
UUID.set(nmsPacket, id);
X.set(nmsPacket, location.getX());
Y.set(nmsPacket, location.getY());
Z.set(nmsPacket, location.getZ());
} else {
X.set(nmsPacket, floor(location.getX()*32));
Y.set(nmsPacket, floor(location.getY()*32));
Z.set(nmsPacket, floor(location.getZ()*32));
}
if (nms.getMinorVersion() >= 19) {
ENTITY_TYPE.set(nmsPacket, EntityTypes_ARMOR_STAND); // :(
} else {
ENTITY_TYPE.set(nmsPacket, entityIds.get((EntityType) entityType));
}
return nmsPacket;
}
private static int floor(double paramDouble) {
int i = (int)paramDouble;
return paramDouble < i ? i - 1 : i;
}
}
@@ -0,0 +1,277 @@
package me.neznamy.tab.platforms.bukkit.platform;
import lombok.Getter;
import lombok.SneakyThrows;
import me.clip.placeholderapi.PlaceholderAPI;
import me.neznamy.tab.platforms.bukkit.*;
import me.neznamy.tab.shared.GroupManager;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.features.injection.PipelineInjector;
import me.neznamy.tab.shared.features.types.TabFeature;
import me.neznamy.tab.platforms.bukkit.features.BukkitTabExpansion;
import me.neznamy.tab.platforms.bukkit.features.PerWorldPlayerList;
import me.neznamy.tab.platforms.bukkit.features.WitherBossBar;
import me.neznamy.tab.platforms.bukkit.features.BukkitNameTagX;
import me.neznamy.tab.platforms.bukkit.nms.NMSStorage;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.backend.BackendPlatform;
import me.neznamy.tab.shared.features.PlaceholderManagerImpl;
import me.neznamy.tab.shared.features.bossbar.BossBarManagerImpl;
import me.neznamy.tab.shared.features.nametags.NameTag;
import me.neznamy.tab.shared.hook.LuckPermsHook;
import me.neznamy.tab.shared.placeholders.PlayerPlaceholderImpl;
import me.neznamy.tab.shared.placeholders.expansion.EmptyTabExpansion;
import me.neznamy.tab.shared.placeholders.expansion.TabExpansion;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.milkbowl.vault.chat.Chat;
import net.milkbowl.vault.permission.Permission;
import org.bstats.bukkit.Metrics;
import org.bstats.charts.SimplePie;
import org.bukkit.Bukkit;
import org.bukkit.command.PluginCommand;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.plugin.RegisteredServiceProvider;
import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.lang.reflect.Field;
/**
* Implementation of Platform interface for Bukkit platform
*/
@Getter
public class BukkitPlatform implements BackendPlatform<Object> {
/** Plugin instance for registering tasks and events */
@NotNull
private final JavaPlugin plugin;
/** Variables checking presence of other plugins to hook into */
private final boolean placeholderAPI = ReflectionUtils.classExists("me.clip.placeholderapi.PlaceholderAPI");
/** NMS server to get TPS from on spigot */
@Nullable private Object server;
/** TPS field */
@Nullable private Field spigotTps;
/** Detection for presence of Paper's TPS getter */
private final boolean paperTps = ReflectionUtils.methodExists(Bukkit.class, "getTPS");
/** Detection for presence of Paper's MSPT getter */
private final boolean paperMspt = ReflectionUtils.methodExists(Bukkit.class, "getAverageTickTime");
public BukkitPlatform(@NotNull JavaPlugin plugin) {
this.plugin = plugin;
try {
server = Bukkit.getServer().getClass().getMethod("getServer").invoke(Bukkit.getServer());
spigotTps = server.getClass().getField("recentTps");
} catch (ReflectiveOperationException e) {
//not spigot
}
}
@Override
@NotNull
public BossBarManagerImpl getLegacyBossBar() {
return new WitherBossBar(plugin);
}
@Override
public void loadPlayers() {
for (Player p : BukkitUtils.getOnlinePlayers()) {
TAB.getInstance().addPlayer(new BukkitTabPlayer(this, p));
}
}
@Override
public void registerPlaceholders() {
PlaceholderManagerImpl manager = TAB.getInstance().getPlaceholderManager();
manager.registerServerPlaceholder("%vault-prefix%", -1, () -> "");
manager.registerServerPlaceholder("%vault-suffix%", -1, () -> "");
if (Bukkit.getPluginManager().isPluginEnabled("Vault")) {
RegisteredServiceProvider<Chat> rspChat = Bukkit.getServicesManager().getRegistration(Chat.class);
if (rspChat != null) {
Chat chat = rspChat.getProvider();
manager.registerPlayerPlaceholder("%vault-prefix%", 1000, p -> chat.getPlayerPrefix((Player) p.getPlayer()));
manager.registerPlayerPlaceholder("%vault-suffix%", 1000, p -> chat.getPlayerSuffix((Player) p.getPlayer()));
}
}
// Override for the PAPI placeholder to prevent console errors on unsupported server versions when ping field changes
manager.registerPlayerPlaceholder("%player_ping%", manager.getRefreshInterval("%player_ping%"),
p -> ((TabPlayer)p).getPing());
BackendPlatform.super.registerPlaceholders();
}
@Override
@Nullable
public PipelineInjector createPipelineInjector() {
return NMSStorage.getInstance().getMinorVersion() >= 8 ? new BukkitPipelineInjector() : null;
}
@Override
@NotNull
public NameTag getUnlimitedNameTags() {
return new BukkitNameTagX(plugin);
}
@Override
@NotNull
public TabExpansion createTabExpansion() {
if (placeholderAPI) {
BukkitTabExpansion expansion = new BukkitTabExpansion();
expansion.register();
return expansion;
}
return new EmptyTabExpansion();
}
@Override
@Nullable
public TabFeature getPerWorldPlayerList() {
return new PerWorldPlayerList(plugin);
}
@Override
public void registerUnknownPlaceholder(@NotNull String identifier) {
if (!placeholderAPI) {
TAB.getInstance().getPlaceholderManager().registerServerPlaceholder(identifier, -1, () -> identifier);
return;
}
PlaceholderManagerImpl pl = TAB.getInstance().getPlaceholderManager();
int refresh = pl.getRefreshInterval(identifier);
if (identifier.startsWith("%rel_")) {
//relational placeholder
TAB.getInstance().getPlaceholderManager().registerRelationalPlaceholder(identifier, pl.getRefreshInterval(identifier), (viewer, target) ->
PlaceholderAPI.setRelationalPlaceholders((Player) viewer.getPlayer(), (Player) target.getPlayer(), identifier));
} else if (identifier.startsWith("%sync:")) {
registerSyncPlaceholder(identifier, refresh);
} else if (identifier.startsWith("%server_")) {
TAB.getInstance().getPlaceholderManager().registerServerPlaceholder(identifier, refresh,
() -> PlaceholderAPI.setPlaceholders(null, identifier));
} else {
TAB.getInstance().getPlaceholderManager().registerPlayerPlaceholder(identifier, refresh,
p -> PlaceholderAPI.setPlaceholders((Player) p.getPlayer(), identifier));
}
}
public void registerSyncPlaceholder(@NotNull String identifier, int refresh) {
String syncedPlaceholder = "%" + identifier.substring(6);
PlayerPlaceholderImpl[] ppl = new PlayerPlaceholderImpl[1];
ppl[0] = TAB.getInstance().getPlaceholderManager().registerPlayerPlaceholder(identifier, refresh, p -> {
Bukkit.getScheduler().runTask(plugin, () -> {
long time = System.nanoTime();
ppl[0].updateValue(p, placeholderAPI ? PlaceholderAPI.setPlaceholders((Player) p.getPlayer(), syncedPlaceholder) : identifier);
TAB.getInstance().getCPUManager().addPlaceholderTime(identifier, System.nanoTime()-time);
});
return null;
});
}
@Override
public void logInfo(@NotNull IChatBaseComponent message) {
Bukkit.getConsoleSender().sendMessage("[TAB] " + BukkitUtils.toBukkitFormat(message, true));
}
@Override
public void logWarn(@NotNull IChatBaseComponent message) {
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.RED.getFormat() + "[TAB] [WARN] " + BukkitUtils.toBukkitFormat(message, true));
}
@Override
@NotNull
public String getServerVersionInfo() {
return "[Bukkit] " + Bukkit.getName() + " - " +
Bukkit.getBukkitVersion().split("-")[0] +
" (" + Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3] + ")";
}
@Override
public void registerListener() {
Bukkit.getPluginManager().registerEvents(new BukkitEventListener(), plugin);
}
@Override
public void registerCommand() {
PluginCommand command = Bukkit.getPluginCommand(TabConstants.COMMAND_BACKEND);
if (command != null) {
BukkitTabCommand cmd = new BukkitTabCommand();
command.setExecutor(cmd);
command.setTabCompleter(cmd);
} else {
logWarn(new IChatBaseComponent("Failed to register command, is it defined in plugin.yml?"));
}
}
@Override
public void startMetrics() {
Metrics metrics = new Metrics(plugin, 5304);
metrics.addCustomChart(new SimplePie(TabConstants.MetricsChart.UNLIMITED_NAME_TAG_MODE_ENABLED,
() -> TAB.getInstance().getFeatureManager().isFeatureEnabled(TabConstants.Feature.UNLIMITED_NAME_TAGS) ? "Yes" : "No"));
metrics.addCustomChart(new SimplePie(TabConstants.MetricsChart.PERMISSION_SYSTEM,
() -> TAB.getInstance().getGroupManager().getPermissionPlugin()));
metrics.addCustomChart(new SimplePie(TabConstants.MetricsChart.SERVER_VERSION,
() -> "1." + TAB.getInstance().getServerVersion().getMinorVersion() + ".x"));
}
@Override
@NotNull
public ProtocolVersion getServerVersion() {
return ProtocolVersion.fromFriendlyName(Bukkit.getBukkitVersion().split("-")[0]);
}
@Override
@NotNull
public File getDataFolder() {
return plugin.getDataFolder();
}
@Override
public Object toComponent(@NotNull IChatBaseComponent component, @NotNull ProtocolVersion version) {
return NMSStorage.getInstance().componentCache.get(component, version);
}
@Override
@NotNull
public GroupManager detectPermissionPlugin() {
if (LuckPermsHook.getInstance().isInstalled()) {
return new GroupManager("LuckPerms", LuckPermsHook.getInstance().getGroupFunction());
}
if (Bukkit.getPluginManager().isPluginEnabled("Vault")) {
RegisteredServiceProvider<Permission> provider = Bukkit.getServicesManager().getRegistration(Permission.class);
if (provider != null && !provider.getProvider().getName().equals("SuperPerms")) {
return new GroupManager(provider.getProvider().getName(), p -> provider.getProvider().getPrimaryGroup((Player) p.getPlayer()));
}
}
return new GroupManager("None", p -> TabConstants.NO_GROUP);
}
@Override
@SneakyThrows
public double getTPS() {
if (paperTps) {
return Bukkit.getTPS()[0];
} else if (spigotTps != null) {
return ((double[]) spigotTps.get(server))[0];
} else {
return -1;
}
}
@Override
public double getMSPT() {
if (paperMspt) return Bukkit.getAverageTickTime();
return -1;
}
public void runEntityTask(@NotNull Entity entity, @NotNull Runnable task) {
task.run();
}
}
@@ -0,0 +1,90 @@
package me.neznamy.tab.platforms.bukkit.platform;
import lombok.SneakyThrows;
import me.clip.placeholderapi.PlaceholderAPI;
import me.neznamy.tab.platforms.bukkit.features.PerWorldPlayerList;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.placeholders.PlayerPlaceholderImpl;
import me.neznamy.tab.shared.platform.TabPlayer;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.event.player.PlayerChangedWorldEvent;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.NotNull;
import java.util.function.Consumer;
/**
* Platform override for Folia
*/
public class FoliaPlatform extends BukkitPlatform {
public FoliaPlatform(@NotNull JavaPlugin plugin) {
super(plugin);
}
@Override
public void loadPlayers() {
super.loadPlayers();
// Values are never updated in the API, warn users
logWarn(new IChatBaseComponent("Folia never updates MSPT and TPS values in the API, making " +
"%mspt% and %tps% return the default values (0 and 20)."));
// Folia never calls PlayerChangedWorldEvent, this is a workaround
TAB.getInstance().getCPUManager().startRepeatingMeasuredTask(100, "Folia compatibility", "Refreshing world", () -> {
for (TabPlayer player : TAB.getInstance().getOnlinePlayers()) {
String bukkitWorld = ((Player)player.getPlayer()).getWorld().getName();
if (!player.getWorld().equals(bukkitWorld)) {
TAB.getInstance().getFeatureManager().onWorldChange(player.getUniqueId(), bukkitWorld);
PerWorldPlayerList pwp = TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.PER_WORLD_PLAYER_LIST);
if (pwp != null) {
runSync((Entity) player.getPlayer(), () -> pwp.onWorldChange(new PlayerChangedWorldEvent((Player) player.getPlayer(), ((Player) player.getPlayer()).getWorld())));
}
}
}
});
}
@Override
public void registerSyncPlaceholder(@NotNull String identifier, int refresh) {
String syncedPlaceholder = "%" + identifier.substring(6);
PlayerPlaceholderImpl[] ppl = new PlayerPlaceholderImpl[1];
ppl[0] = TAB.getInstance().getPlaceholderManager().registerPlayerPlaceholder(identifier, refresh, p -> {
runSync((Entity) p.getPlayer(), () -> {
long time = System.nanoTime();
String output = isPlaceholderAPI() ? PlaceholderAPI.setPlaceholders((Player) p.getPlayer(), syncedPlaceholder) : identifier;
TAB.getInstance().getCPUManager().addPlaceholderTime(identifier, System.nanoTime()-time);
TAB.getInstance().getCPUManager().runTask(() -> ppl[0].updateValue(p, output)); // To ensure player is loaded
});
return null;
});
}
/**
* Runs task using player's entity scheduler. It's using reflection, because
* Folia uses Java 17 while TAB maintains Java 8 compatibility for compatibility
* with MC versions older than their player base.
*
* @param entity
* entity to run task for
* @param task
* Task to run
*/
@SneakyThrows
@SuppressWarnings("JavaReflectionMemberAccess")
private void runSync(@NotNull Entity entity, @NotNull Runnable task) {
Object entityScheduler = Entity.class.getMethod("getScheduler").invoke(entity);
Consumer<?> consumer = $ -> task.run(); // Reflection and lambdas don't go together
entityScheduler.getClass().getMethod("run", Plugin.class, Consumer.class, Runnable.class)
.invoke(entityScheduler, getPlugin(), consumer, null);
}
@Override
public void runEntityTask(@NotNull Entity entity, @NotNull Runnable task) {
runSync(entity, task);
}
}
@@ -0,0 +1,219 @@
package me.neznamy.tab.platforms.bukkit.scoreboard;
import lombok.Getter;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.platforms.bukkit.BukkitUtils;
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.scoreboard.NameTagVisibility;
import org.bukkit.scoreboard.Objective;
import org.bukkit.scoreboard.RenderType;
import org.bukkit.scoreboard.Team;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
/**
* Scoreboard implementation using Bukkit API (1.5.2+). It has several issues:
* #1 - Limitations on legacy versions are forced in the API.
* While this may not seem like a problem, it enforces those
* limits even for 1.13+ players (if using ViaVersion).<p>
* #2 - Modern versions no longer have any limits, but md_5
* decided to add some random limits for absolutely no reason
* at all. Scoreboard title received a random 128 characters
* limit including color codes. Together with the almighty bukkit
* RGB format using 14 characters for 1 color code, this makes
* gradients just impossible to use. Team prefix/suffix also
* received a 64 characters limit (excluding color codes at least),
* however that might not be enough for displaying a line of text
* in sidebar, which would require splitting the text into prefix
* and suffix, which is just begging for bugs to be introduced.<p>
* #3 - Other plugins can decide to put players into their own
* scoreboard, automatically destroying all visuals made by the
* plugin. They might also put all players into the same scoreboard,
* making per-player view of teams, especially sidebar not working.<p>
*/
// Throw the NPE if something is not as expected, parent class should ensure it anyway
@SuppressWarnings({"ConstantConditions", "deprecation"})
public class BukkitScoreboard extends Scoreboard<BukkitTabPlayer> {
/** Flag tracking whether this implementation is available for use */
@Getter
private static final boolean available = ReflectionUtils.methodExists(Bukkit.class, "getScoreboardManager");
/** Server's minor version */
private final int serverMinorVersion = BukkitReflection.getMinorVersion();
@NotNull
protected final org.bukkit.scoreboard.Scoreboard scoreboard = Bukkit.getScoreboardManager().getNewScoreboard();
public BukkitScoreboard(@NotNull BukkitTabPlayer player) {
super(player);
// Put player into a different scoreboard for per-player view
player.getPlayer().setScoreboard(scoreboard);
}
@Override
public void setDisplaySlot(@NotNull DisplaySlot slot, @NotNull String objective) {
checkPlayerScoreboard();
scoreboard.getObjective(objective).setDisplaySlot(org.bukkit.scoreboard.DisplaySlot.valueOf(slot.name()));
}
@Override
public void setScore0(@NotNull String objective, @NotNull String playerName, int score) {
checkPlayerScoreboard();
if (serverMinorVersion >= 7 && TAB.getInstance().getServerVersion().getNetworkId() >= ProtocolVersion.V1_7_8.getNetworkId()) {
scoreboard.getObjective(objective).getScore(playerName).setScore(score);
} else {
scoreboard.getObjective(objective).getScore(Bukkit.getOfflinePlayer(playerName)).setScore(score);
}
}
@Override
public void removeScore0(@NotNull String objective, @NotNull String playerName) {
checkPlayerScoreboard();
if (serverMinorVersion >= 7 && TAB.getInstance().getServerVersion().getNetworkId() >= ProtocolVersion.V1_7_8.getNetworkId()) {
scoreboard.resetScores(playerName);
} else {
scoreboard.resetScores(Bukkit.getOfflinePlayer(playerName));
}
}
@Override
public void registerObjective0(@NotNull String objectiveName, @NotNull String title, @NotNull HealthDisplay display) {
checkPlayerScoreboard();
newObjective(objectiveName, "dummy", title, display);
}
@Override
public void unregisterObjective0(@NotNull String objectiveName) {
checkPlayerScoreboard();
scoreboard.getObjective(objectiveName).unregister();
}
@Override
public void updateObjective0(@NotNull String objectiveName, @NotNull String title, @NotNull HealthDisplay display) {
checkPlayerScoreboard();
Objective obj = scoreboard.getObjective(objectiveName);
setDisplayName(obj, title);
if (serverMinorVersion >= 14) obj.setRenderType(RenderType.valueOf(display.name()));
}
@Override
public void registerTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix,
@NotNull NameVisibility visibility, @NotNull CollisionRule collision,
@NotNull Collection<String> players, int options) {
checkPlayerScoreboard();
Team team = scoreboard.registerNewTeam(name);
setPrefix(team, prefix);
setSuffix(team, suffix);
if (serverMinorVersion >= 8)
team.setNameTagVisibility(NameTagVisibility.valueOf(visibility.name()));
if (serverMinorVersion >= 9)
team.setOption(Team.Option.COLLISION_RULE, Team.OptionStatus.values()[collision.ordinal()]);
if (serverMinorVersion >= 13)
team.setColor(ChatColor.valueOf(EnumChatFormat.lastColorsOf(prefix).name()));
if (serverMinorVersion >= 7 && TAB.getInstance().getServerVersion().getNetworkId() >= ProtocolVersion.V1_7_8.getNetworkId()) {
players.forEach(team::addEntry);
} else {
players.forEach(player -> team.addPlayer(Bukkit.getOfflinePlayer(player)));
}
team.setAllowFriendlyFire((options & 0x01) != 0);
team.setCanSeeFriendlyInvisibles((options & 0x02) != 0);
}
@Override
public void unregisterTeam0(@NotNull String name) {
checkPlayerScoreboard();
scoreboard.getTeam(name).unregister();
}
@Override
public void updateTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix,
@NotNull NameVisibility visibility, @NotNull CollisionRule collision, int options) {
checkPlayerScoreboard();
Team team = scoreboard.getTeam(name);
setPrefix(team, prefix);
setSuffix(team, suffix);
if (serverMinorVersion >= 8)
team.setNameTagVisibility(NameTagVisibility.valueOf(visibility.name()));
if (serverMinorVersion >= 9)
team.setOption(Team.Option.COLLISION_RULE, Team.OptionStatus.values()[collision.ordinal()]);
if (serverMinorVersion >= 13)
team.setColor(ChatColor.valueOf(EnumChatFormat.lastColorsOf(prefix).name()));
team.setAllowFriendlyFire((options & 0x01) != 0);
team.setCanSeeFriendlyInvisibles((options & 0x02) != 0);
}
public void newObjective(String objectiveName, String criteria, String title, @NotNull HealthDisplay display) {
if (serverMinorVersion >= 14) {
scoreboard.registerNewObjective(objectiveName, criteria, transform(title, 128, 32), RenderType.valueOf(display.name()));
} else {
setDisplayName(scoreboard.registerNewObjective(objectiveName, display == HealthDisplay.HEARTS ? "health" : "dummy"), title);
}
}
public void setDisplayName(@NotNull Objective objective, @NotNull String displayName) {
objective.setDisplayName(transform(displayName, 128, 32));
}
public void setPrefix(@NotNull Team team, @NotNull String prefix) {
team.setPrefix(transform(prefix, 64, 16));
}
public void setSuffix(@NotNull Team team, @NotNull String suffix) {
team.setSuffix(transform(suffix, 64, 16));
}
/**
* Transforms text into format using bukkit RGB syntax if server version is 1.16+
*
* @param text
* Text to transform
* @param maxLengthModern
* Maximum text length defined by bukkit API on 1.13+
* @param maxLengthLegacy
* Maximum text length defined by bukkit API on 1.12-
* @return Converted text
*/
@NotNull
private String transform(@NotNull String text, int maxLengthModern, int maxLengthLegacy) {
String transformed = BukkitUtils.toBukkitFormat(IChatBaseComponent.optimizedComponent(text), player.getVersion().getMinorVersion() >= 16);
if (serverMinorVersion >= 16 && maxLengthModern < 128) { // Scoreboard title is not stripping colors
while (ChatColor.stripColor(transformed).length() > maxLengthModern)
transformed = transformed.substring(0, transformed.length()-1);
} else if (serverMinorVersion >= 13) {
if (transformed.length() > maxLengthModern)
transformed = transformed.substring(0, maxLengthModern);
} else {
if (transformed.length() > maxLengthLegacy)
transformed = transformed.substring(0, maxLengthLegacy);
}
return transformed;
}
/**
* Makes sure player is in the correct scoreboard.
* If not, puts the player into correct scoreboard and prints
* a warning.
* The only possible reason for this to happen is another plugin
* putting a player into a different scoreboard.
* Sadly there is no efficient solution to this, which md_5 fails
* to understand and keeps saying you don't need packets for scoreboards.
*/
private void checkPlayerScoreboard() {
if (player.getPlayer().getScoreboard() != scoreboard) {
player.getPlayer().setScoreboard(scoreboard);
TAB.getInstance().getErrorManager().printError("Player " + player.getName() + " was in a different scoreboard " +
"than expected. This means another plugin changed player's scoreboard.");
}
}
}
@@ -1,9 +1,9 @@
package me.neznamy.tab.platforms.bukkit.scoreboard;
import lombok.Getter;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.Scoreboard;
@@ -16,37 +16,19 @@ import java.lang.reflect.Method;
import java.util.Collection;
import java.util.List;
import static me.neznamy.tab.platforms.bukkit.nms.BukkitReflection.getLegacyClass;
/**
* Scoreboard implementation for Bukkit, which uses packets
* to send scoreboards. While md_5 keeps saying that you don't
* need packets for scoreboards in every single spigot update,
* this is not true. It may seem that way at first, but after
* investigating, fatal problems are found:<p>
* #1 - Limitations on legacy versions are forced in the API.
* While this may not seem like a problem, it enforces those
* limits even for 1.13+ players (if using ViaVersion).<p>
* #2 - Modern versions no longer have any limits, but md_5
* decided to add some random limits for absolutely no reason
* at all. Scoreboard title received a random 128 characters
* limit including color codes. Together with the almighty bukkit
* RGB format using 14 characters for 1 color code, this makes
* gradients just impossible to use. Team prefix/suffix also
* received a 64 characters limit (excluding color codes at least),
* however that might not be enough for displaying a line of text
* in sidebar, which would require splitting the text into prefix
* and suffix, which is just begging for bugs to be introduced.<p>
* #3 - Other plugins can decide to put players into their own
* scoreboard, automatically destroying all visuals made by the
* plugin. They might also put all players into the same scoreboard,
* making per-player view of teams, especially sidebar not working.<p>
* to send scoreboards to use the full potential on all versions
* and server software without any artificial limits.
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public class PacketScoreboard extends Scoreboard<BukkitTabPlayer> {
private static NMSStorage nms;
@Getter
private static boolean available;
public static Class<?> Scoreboard;
public static Class<?> IScoreboardCriteria;
private static Object emptyScoreboard;
private static Field IScoreboardCriteria_self;
@@ -55,16 +37,15 @@ public class PacketScoreboard extends Scoreboard<BukkitTabPlayer> {
private static Constructor<?> newDisplayObjective;
public static Field DisplayObjective_POSITION;
public static Field DisplayObjective_OBJECTIVE_NAME;
public static Enum[] DisplaySlot_values;
// PacketPlayOutScoreboardScore
public static Class<?> ScorePacketClass;
public static Class<?> ScoreboardScoreClass;
public static Class<Enum> EnumScoreboardAction;
private static Class<Enum> EnumScoreboardAction;
private static Constructor<?> newScorePacket_1_13;
private static Constructor<?> newScorePacket_String;
private static Constructor<?> newScorePacket;
private static Constructor<?> newScoreboardScore;
public static Method ScoreboardScore_setScore;
private static Method ScoreboardScore_setScore;
// PacketPlayOutScoreboardObjective
public static Class<?> ObjectivePacketClass;
@@ -73,8 +54,7 @@ public class PacketScoreboard extends Scoreboard<BukkitTabPlayer> {
public static Field Objective_METHOD;
private static Field Objective_RENDER_TYPE;
private static Field Objective_DISPLAY_NAME;
public static Class<Enum> EnumScoreboardHealthDisplay;
public static Class<?> ScoreboardObjective;
private static Class<Enum> EnumScoreboardHealthDisplay;
private static Constructor<?> newScoreboardObjective;
// PacketPlayOutScoreboardTeamStorage
@@ -85,113 +65,230 @@ public class PacketScoreboard extends Scoreboard<BukkitTabPlayer> {
public static Field TeamPacket_NAME;
public static Field TeamPacket_ACTION;
public static Field TeamPacket_PLAYERS;
public static Class<Enum> EnumNameTagVisibility;
public static Class<Enum> EnumTeamPush;
public static Class<?> ScoreboardTeam;
private static Class<Enum> EnumNameTagVisibility;
private static Class<Enum> EnumTeamPush;
private static Class<Enum> EnumChatFormatClass;
private static Constructor<?> newScoreboardTeam;
private static Method ScoreboardTeam_getPlayerNameSet;
public static Method ScoreboardTeam_setNameTagVisibility;
private static Method ScoreboardTeam_setNameTagVisibility;
private static Method ScoreboardTeam_setCollisionRule;
public static Method ScoreboardTeam_setPrefix;
public static Method ScoreboardTeam_setSuffix;
private static Method ScoreboardTeam_setPrefix;
private static Method ScoreboardTeam_setSuffix;
private static Method ScoreboardTeam_setColor;
public static Method ScoreboardTeam_setAllowFriendlyFire;
public static Method ScoreboardTeam_setCanSeeFriendlyInvisibles;
private static Method ScoreboardTeam_setAllowFriendlyFire;
private static Method ScoreboardTeam_setCanSeeFriendlyInvisibles;
public static void load(NMSStorage nms) throws ReflectiveOperationException {
PacketScoreboard.nms = nms;
emptyScoreboard = Scoreboard.getConstructor().newInstance();
public static void load() throws ReflectiveOperationException {
int minorVersion = BukkitReflection.getMinorVersion();
Class<?> scoreboardTeam;
Class<?> scoreboardObjective;
Class<?> scoreboardScoreClass;
Class<?> IScoreboardCriteria;
Class<?> scoreboard;
Class<?> scorePacketClass;
Class<?> IChatBaseComponent = null;
if (BukkitReflection.isMojangMapped()) {
IChatBaseComponent = Class.forName("net.minecraft.network.chat.Component");
EnumChatFormatClass = (Class<Enum>) Class.forName("net.minecraft.ChatFormatting");
DisplayObjectiveClass = Class.forName("net.minecraft.network.protocol.game.ClientboundSetDisplayObjectivePacket");
ObjectivePacketClass = Class.forName("net.minecraft.network.protocol.game.ClientboundSetObjectivePacket");
scoreboard = Class.forName("net.minecraft.world.scores.Scoreboard");
scorePacketClass = Class.forName("net.minecraft.network.protocol.game.ClientboundSetScorePacket");
scoreboardObjective = Class.forName("net.minecraft.world.scores.Objective");
scoreboardScoreClass = Class.forName("net.minecraft.world.scores.Score");
IScoreboardCriteria = Class.forName("net.minecraft.world.scores.criteria.ObjectiveCriteria");
EnumScoreboardHealthDisplay = (Class<Enum>) Class.forName("net.minecraft.world.scores.criteria.ObjectiveCriteria$RenderType");
EnumScoreboardAction = (Class<Enum>) Class.forName("net.minecraft.server.ServerScoreboard$Method");
TeamPacketClass = Class.forName("net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket");
scoreboardTeam = Class.forName("net.minecraft.world.scores.PlayerTeam");
EnumNameTagVisibility = (Class<Enum>) Class.forName("net.minecraft.world.scores.Team$Visibility");
EnumTeamPush = (Class<Enum>) Class.forName("net.minecraft.world.scores.Team$CollisionRule");
} else if (minorVersion >= 17) {
IChatBaseComponent = Class.forName("net.minecraft.network.chat.IChatBaseComponent");
EnumChatFormatClass = (Class<Enum>) Class.forName("net.minecraft.EnumChatFormat");
DisplayObjectiveClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutScoreboardDisplayObjective");
ObjectivePacketClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutScoreboardObjective");
scoreboard = Class.forName("net.minecraft.world.scores.Scoreboard");
scorePacketClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutScoreboardScore");
scoreboardObjective = Class.forName("net.minecraft.world.scores.ScoreboardObjective");
scoreboardScoreClass = Class.forName("net.minecraft.world.scores.ScoreboardScore");
IScoreboardCriteria = Class.forName("net.minecraft.world.scores.criteria.IScoreboardCriteria");
EnumScoreboardHealthDisplay = (Class<Enum>) Class.forName("net.minecraft.world.scores.criteria.IScoreboardCriteria$EnumScoreboardHealthDisplay");
EnumScoreboardAction = (Class<Enum>) Class.forName("net.minecraft.server.ScoreboardServer$Action");
TeamPacketClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutScoreboardTeam");
scoreboardTeam = Class.forName("net.minecraft.world.scores.ScoreboardTeam");
EnumNameTagVisibility = (Class<Enum>) Class.forName("net.minecraft.world.scores.ScoreboardTeamBase$EnumNameTagVisibility");
EnumTeamPush = (Class<Enum>) Class.forName("net.minecraft.world.scores.ScoreboardTeamBase$EnumTeamPush");
} else {
EnumChatFormatClass = (Class<Enum>) getLegacyClass("EnumChatFormat");
DisplayObjectiveClass = getLegacyClass("PacketPlayOutScoreboardDisplayObjective", "Packet208SetScoreboardDisplayObjective");
ObjectivePacketClass = getLegacyClass("PacketPlayOutScoreboardObjective", "Packet206SetScoreboardObjective");
TeamPacketClass = getLegacyClass("PacketPlayOutScoreboardTeam", "Packet209SetScoreboardTeam");
scorePacketClass = getLegacyClass("PacketPlayOutScoreboardScore", "Packet207SetScoreboardScore");
scoreboard = getLegacyClass("Scoreboard");
scoreboardObjective = getLegacyClass("ScoreboardObjective");
scoreboardScoreClass = getLegacyClass("ScoreboardScore");
IScoreboardCriteria = getLegacyClass("IScoreboardCriteria", "IObjective"); // 1.5.1+, 1.5
scoreboardTeam = getLegacyClass("ScoreboardTeam");
if (minorVersion >= 7) {
IChatBaseComponent = getLegacyClass("IChatBaseComponent");
}
if (minorVersion >= 8) {
EnumScoreboardHealthDisplay = (Class<Enum>) getLegacyClass("IScoreboardCriteria$EnumScoreboardHealthDisplay", "EnumScoreboardHealthDisplay");
EnumScoreboardAction = (Class<Enum>) getLegacyClass("ScoreboardServer$Action", "PacketPlayOutScoreboardScore$EnumScoreboardAction", "EnumScoreboardAction");
EnumNameTagVisibility = (Class<Enum>) getLegacyClass("ScoreboardTeamBase$EnumNameTagVisibility", "EnumNameTagVisibility");
}
if (minorVersion >= 9) {
EnumTeamPush = (Class<Enum>) getLegacyClass("ScoreboardTeamBase$EnumTeamPush");
}
}
emptyScoreboard = scoreboard.getConstructor().newInstance();
IScoreboardCriteria_self = ReflectionUtils.getFields(IScoreboardCriteria, IScoreboardCriteria).get(0);
newDisplayObjective = DisplayObjectiveClass.getConstructor(int.class, ScoreboardObjective);
DisplayObjective_POSITION = ReflectionUtils.getOnlyField(DisplayObjectiveClass, int.class);
if (BukkitReflection.is1_20_2Plus()) {
Class<?> DisplaySlot = Class.forName("net.minecraft.world.scores.DisplaySlot");
DisplayObjective_POSITION = ReflectionUtils.getOnlyField(DisplayObjectiveClass, DisplaySlot);
newDisplayObjective = DisplayObjectiveClass.getConstructor(DisplaySlot, scoreboardObjective);
DisplaySlot_values = (Enum[]) DisplaySlot.getDeclaredMethod("values").invoke(null);
} else {
DisplayObjective_POSITION = ReflectionUtils.getOnlyField(DisplayObjectiveClass, int.class);
newDisplayObjective = DisplayObjectiveClass.getConstructor(int.class, scoreboardObjective);
}
DisplayObjective_OBJECTIVE_NAME = ReflectionUtils.getOnlyField(DisplayObjectiveClass, String.class);
newScoreboardObjective = ReflectionUtils.getOnlyConstructor(ScoreboardObjective);
newScoreboardObjective = ReflectionUtils.getOnlyConstructor(scoreboardObjective);
Objective_OBJECTIVE_NAME = ReflectionUtils.getFields(ObjectivePacketClass, String.class).get(0);
List<Field> list = ReflectionUtils.getFields(ObjectivePacketClass, int.class);
Objective_METHOD = list.get(list.size()-1);
newScoreboardScore = ScoreboardScoreClass.getConstructor(Scoreboard, ScoreboardObjective, String.class);
newScoreboardTeam = ScoreboardTeam.getConstructor(Scoreboard, String.class);
newScoreboardScore = scoreboardScoreClass.getConstructor(scoreboard, scoreboardObjective, String.class);
newScoreboardTeam = scoreboardTeam.getConstructor(scoreboard, String.class);
TeamPacket_NAME = ReflectionUtils.getFields(TeamPacketClass, String.class).get(0);
TeamPacket_ACTION = ReflectionUtils.getInstanceFields(TeamPacketClass, int.class).get(0);
TeamPacket_PLAYERS = ReflectionUtils.getOnlyField(TeamPacketClass, Collection.class);
ScoreboardTeam_getPlayerNameSet = ReflectionUtils.getOnlyMethod(ScoreboardTeam, Collection.class);
if (nms.getMinorVersion() >= 13) {
newScorePacket_1_13 = ScorePacketClass.getConstructor(EnumScoreboardAction, String.class, String.class, int.class);
newObjectivePacket = ObjectivePacketClass.getConstructor(ScoreboardObjective, int.class);
Objective_DISPLAY_NAME = ReflectionUtils.getOnlyField(ObjectivePacketClass, nms.IChatBaseComponent);
ScoreboardTeam_setColor = ReflectionUtils.getOnlyMethod(ScoreboardTeam, void.class, nms.EnumChatFormat);
ScoreboardTeam_getPlayerNameSet = ReflectionUtils.getOnlyMethod(scoreboardTeam, Collection.class);
if (minorVersion >= 13) {
newScorePacket_1_13 = scorePacketClass.getConstructor(EnumScoreboardAction, String.class, String.class, int.class);
newObjectivePacket = ObjectivePacketClass.getConstructor(scoreboardObjective, int.class);
Objective_DISPLAY_NAME = ReflectionUtils.getOnlyField(ObjectivePacketClass, IChatBaseComponent);
ScoreboardTeam_setColor = ReflectionUtils.getOnlyMethod(scoreboardTeam, void.class, EnumChatFormatClass);
} else {
newScorePacket_String = ScorePacketClass.getConstructor(String.class);
newScorePacket_String = scorePacketClass.getConstructor(String.class);
newObjectivePacket = ObjectivePacketClass.getConstructor();
Objective_DISPLAY_NAME = ReflectionUtils.getFields(ObjectivePacketClass, String.class).get(1);
if (nms.getMinorVersion() >= 8) {
newScorePacket = ScorePacketClass.getConstructor(ScoreboardScoreClass);
if (minorVersion >= 8) {
newScorePacket = scorePacketClass.getConstructor(scoreboardScoreClass);
Objective_RENDER_TYPE = ReflectionUtils.getOnlyField(ObjectivePacketClass, EnumScoreboardHealthDisplay);
} else {
newScorePacket = ScorePacketClass.getConstructor(ScoreboardScoreClass, int.class);
newScorePacket = scorePacketClass.getConstructor(scoreboardScoreClass, int.class);
}
}
if (nms.getMinorVersion() >= 9) {
ScoreboardTeam_setCollisionRule = ReflectionUtils.getOnlyMethod(ScoreboardTeam, void.class, EnumTeamPush);
if (minorVersion >= 9) {
ScoreboardTeam_setCollisionRule = ReflectionUtils.getOnlyMethod(scoreboardTeam, void.class, EnumTeamPush);
}
if (nms.getMinorVersion() >= 17) {
TeamPacketConstructor_of = ReflectionUtils.getOnlyMethod(TeamPacketClass, TeamPacketClass, ScoreboardTeam);
TeamPacketConstructor_ofBoolean = ReflectionUtils.getOnlyMethod(TeamPacketClass, TeamPacketClass, ScoreboardTeam, boolean.class);
if (minorVersion >= 17) {
TeamPacketConstructor_of = ReflectionUtils.getOnlyMethod(TeamPacketClass, TeamPacketClass, scoreboardTeam);
TeamPacketConstructor_ofBoolean = ReflectionUtils.getOnlyMethod(TeamPacketClass, TeamPacketClass, scoreboardTeam, boolean.class);
} else {
newTeamPacket = TeamPacketClass.getConstructor(ScoreboardTeam, int.class);
newTeamPacket = TeamPacketClass.getConstructor(scoreboardTeam, int.class);
}
ScoreboardScore_setScore = ReflectionUtils.getMethod(
scoreboardScoreClass,
new String[] {"func_96647_c", "setScore", "b", "c", "m_83402_"}, // {Thermos, 1.5.1 - 1.17.1 & 1.20.2+, 1.18 - 1.20.1, 1.5, Mohist 1.18.2}
int.class
);
ScoreboardTeam_setAllowFriendlyFire = ReflectionUtils.getMethod(
scoreboardTeam,
new String[] {"func_96660_a", "setAllowFriendlyFire", "a", "m_83355_"}, // {Thermos, 1.5.1+, 1.5 & 1.18+, Mohist 1.18.2}
boolean.class
);
ScoreboardTeam_setCanSeeFriendlyInvisibles = ReflectionUtils.getMethod(
scoreboardTeam,
new String[] {"func_98300_b", "setCanSeeFriendlyInvisibles", "b", "m_83362_", "setSeeFriendlyInvisibles"}, // {Thermos, 1.5.1+, 1.5 & 1.18+, Mohist 1.18.2, 1.20.2+}
boolean.class
);
if (minorVersion >= 13) {
ScoreboardTeam_setPrefix = ReflectionUtils.getMethod(
scoreboardTeam,
new String[]{"setPrefix", "b", "m_83360_", "setPlayerPrefix"}, // {1.17.1-, 1.18 - 1.20.1, Mohist 1.18.2, 1.20.2+}
IChatBaseComponent
);
ScoreboardTeam_setSuffix = ReflectionUtils.getMethod(
scoreboardTeam,
new String[]{"setSuffix", "c", "m_83365_", "setPlayerSuffix"}, // {1.17.1-, 1.18 - 1.20.1, Mohist 1.18.2, 1.20.2+}
IChatBaseComponent
);
} else {
ScoreboardTeam_setPrefix = ReflectionUtils.getMethod(
scoreboardTeam,
new String[] {"func_96666_b", "setPrefix", "b"}, // {Thermos, 1.5.1+, 1.5}
String.class
);
ScoreboardTeam_setSuffix = ReflectionUtils.getMethod(
scoreboardTeam,
new String[] {"func_96662_c", "setSuffix", "c"}, // {Thermos, 1.5.1+, 1.5}
String.class
);
}
if (minorVersion >= 8) {
ScoreboardTeam_setNameTagVisibility = ReflectionUtils.getMethod(
scoreboardTeam,
new String[] {"setNameTagVisibility", "a", "m_83346_"}, // {1.8.1+, 1.8 & 1.18+, Mohist 1.18.2}
EnumNameTagVisibility
);
}
available = true;
}
public PacketScoreboard(BukkitTabPlayer player) {
public PacketScoreboard(@NotNull BukkitTabPlayer player) {
super(player);
}
@Override
@SneakyThrows
public void setDisplaySlot(@NotNull DisplaySlot slot, @NotNull String objective) {
player.sendPacket(newDisplayObjective.newInstance(slot.ordinal(), newScoreboardObjective(objective)));
Object displaySlot;
if (BukkitReflection.is1_20_2Plus()) {
displaySlot = DisplaySlot_values[slot.ordinal()];
} else {
displaySlot = slot.ordinal();
}
player.sendPacket(newDisplayObjective.newInstance(displaySlot, newScoreboardObjective(objective)));
}
@Override
@SneakyThrows
public void registerObjective0(@NotNull String objectiveName, @NotNull String title, boolean hearts) {
player.sendPacket(buildObjective(0, objectiveName, title, hearts));
public void registerObjective0(@NotNull String objectiveName, @NotNull String title, @NotNull HealthDisplay display) {
player.sendPacket(buildObjective(0, objectiveName, title, display));
}
@Override
public void unregisterObjective0(@NotNull String objectiveName) {
player.sendPacket(buildObjective(1, objectiveName, "", false));
player.sendPacket(buildObjective(1, objectiveName, "", HealthDisplay.INTEGER));
}
@Override
public void updateObjective0(@NotNull String objectiveName, @NotNull String title, boolean hearts) {
player.sendPacket(buildObjective(2, objectiveName, title, hearts));
public void updateObjective0(@NotNull String objectiveName, @NotNull String title, @NotNull HealthDisplay display) {
player.sendPacket(buildObjective(2, objectiveName, title, display));
}
@SneakyThrows
private Object buildObjective(int action, String objectiveName, String title, boolean hearts) {
if (nms.getMinorVersion() >= 13) {
private Object buildObjective(int action, String objectiveName, String title, @NotNull HealthDisplay display) {
if (BukkitReflection.getMinorVersion() >= 13) {
return newObjectivePacket.newInstance(
newScoreboardObjective.newInstance(null, objectiveName, null,
nms.toNMSComponent(IChatBaseComponent.optimizedComponent(title), player.getVersion()),
asDisplayType(hearts)),
toComponent(IChatBaseComponent.optimizedComponent(title)),
Enum.valueOf(EnumScoreboardHealthDisplay, display.name())),
action
);
}
Object nmsPacket = newObjectivePacket.newInstance();
Objective_OBJECTIVE_NAME.set(nmsPacket, objectiveName);
Objective_DISPLAY_NAME.set(nmsPacket, title);
if (nms.getMinorVersion() >= 8) {
Objective_RENDER_TYPE.set(nmsPacket, asDisplayType(hearts));
if (BukkitReflection.getMinorVersion() >= 8) {
Objective_RENDER_TYPE.set(nmsPacket, Enum.valueOf(EnumScoreboardHealthDisplay, display.name()));
}
Objective_METHOD.set(nmsPacket, action);
return nmsPacket;
}
private Object asDisplayType(boolean hearts) {
return Enum.valueOf(EnumScoreboardHealthDisplay, hearts ? "HEARTS" : "INTEGER");
}
@Override
@SneakyThrows
public void registerTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix,
@@ -199,7 +296,7 @@ public class PacketScoreboard extends Scoreboard<BukkitTabPlayer> {
@NotNull Collection<String> players, int options) {
Object team = createTeam(name, prefix, suffix, visibility, collision, options);
((Collection<String>)ScoreboardTeam_getPlayerNameSet.invoke(team)).addAll(players);
if (nms.getMinorVersion() >= 17) {
if (BukkitReflection.getMinorVersion() >= 17) {
player.sendPacket(TeamPacketConstructor_ofBoolean.invoke(null, team, true));
} else {
player.sendPacket(newTeamPacket.newInstance(team, 0));
@@ -210,7 +307,7 @@ public class PacketScoreboard extends Scoreboard<BukkitTabPlayer> {
@SneakyThrows
public void unregisterTeam0(@NotNull String name) {
Object team = newScoreboardTeam.newInstance(emptyScoreboard, name);
if (nms.getMinorVersion() >= 17) {
if (BukkitReflection.getMinorVersion() >= 17) {
player.sendPacket(TeamPacketConstructor_of.invoke(null, team));
} else {
player.sendPacket(newTeamPacket.newInstance(team, 1));
@@ -222,7 +319,7 @@ public class PacketScoreboard extends Scoreboard<BukkitTabPlayer> {
public void updateTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix,
@NotNull NameVisibility visibility, @NotNull CollisionRule collision, int options) {
Object team = createTeam(name, prefix, suffix, visibility, collision, options);
if (nms.getMinorVersion() >= 17) {
if (BukkitReflection.getMinorVersion() >= 17) {
player.sendPacket(TeamPacketConstructor_ofBoolean.invoke(null, team, false));
} else {
player.sendPacket(newTeamPacket.newInstance(team, 2));
@@ -230,45 +327,44 @@ public class PacketScoreboard extends Scoreboard<BukkitTabPlayer> {
}
@SneakyThrows
private Object createTeam(String teamName, String prefix, String suffix, NameVisibility visibility, CollisionRule collision, int options) {
private Object createTeam(@NotNull String teamName, @NotNull String prefix, @NotNull String suffix,
@NotNull NameVisibility visibility, @NotNull CollisionRule collision, int options) {
Object team = newScoreboardTeam.newInstance(emptyScoreboard, teamName);
ScoreboardTeam_setAllowFriendlyFire.invoke(team, (options & 0x1) > 0);
ScoreboardTeam_setCanSeeFriendlyInvisibles.invoke(team, (options & 0x2) > 0);
if (nms.getMinorVersion() >= 13) {
ScoreboardTeam_setPrefix.invoke(team, nms.toNMSComponent(IChatBaseComponent.optimizedComponent(prefix), player.getVersion()));
ScoreboardTeam_setSuffix.invoke(team, nms.toNMSComponent(IChatBaseComponent.optimizedComponent(suffix), player.getVersion()));
ScoreboardTeam_setColor.invoke(team, Enum.valueOf(nms.EnumChatFormat, EnumChatFormat.lastColorsOf(prefix).toString()));
if (BukkitReflection.getMinorVersion() >= 13) {
ScoreboardTeam_setPrefix.invoke(team, toComponent(IChatBaseComponent.optimizedComponent(prefix)));
ScoreboardTeam_setSuffix.invoke(team, toComponent(IChatBaseComponent.optimizedComponent(suffix)));
ScoreboardTeam_setColor.invoke(team, Enum.valueOf(EnumChatFormatClass, EnumChatFormat.lastColorsOf(prefix).toString()));
} else {
ScoreboardTeam_setPrefix.invoke(team, prefix);
ScoreboardTeam_setSuffix.invoke(team, suffix);
}
if (nms.getMinorVersion() >= 8) ScoreboardTeam_setNameTagVisibility.invoke(team, Enum.valueOf(EnumNameTagVisibility, visibility.name()));
if (nms.getMinorVersion() >= 9) ScoreboardTeam_setCollisionRule.invoke(team, Enum.valueOf(EnumTeamPush, collision.name()));
if (BukkitReflection.getMinorVersion() >= 8) ScoreboardTeam_setNameTagVisibility.invoke(team, Enum.valueOf(EnumNameTagVisibility, visibility.name()));
if (BukkitReflection.getMinorVersion() >= 9) ScoreboardTeam_setCollisionRule.invoke(team, Enum.valueOf(EnumTeamPush, collision.name()));
return team;
}
@Override
@SneakyThrows
public void setScore0(@NotNull String objective, @NotNull String playerName, int score) {
Object packet;
if (nms.getMinorVersion() >= 13) {
packet = newScorePacket_1_13.newInstance(Enum.valueOf(EnumScoreboardAction, "CHANGE"), objective, playerName, score);
if (BukkitReflection.getMinorVersion() >= 13) {
player.sendPacket(newScorePacket_1_13.newInstance(Enum.valueOf(EnumScoreboardAction, "CHANGE"), objective, playerName, score));
} else {
Object scoreboardScore = newScoreboardScore.newInstance(emptyScoreboard, newScoreboardObjective(objective), playerName);
ScoreboardScore_setScore.invoke(scoreboardScore, score);
if (nms.getMinorVersion() >= 8) {
packet = newScorePacket.newInstance(scoreboardScore);
if (BukkitReflection.getMinorVersion() >= 8) {
player.sendPacket(newScorePacket.newInstance(scoreboardScore));
} else {
packet = newScorePacket.newInstance(scoreboardScore, 0);
player.sendPacket(newScorePacket.newInstance(scoreboardScore, 0));
}
}
player.sendPacket(packet);
}
@Override
@SneakyThrows
public void removeScore0(@NotNull String objective, @NotNull String playerName) {
if (nms.getMinorVersion() >= 13) {
if (BukkitReflection.getMinorVersion() >= 13) {
player.sendPacket(newScorePacket_1_13.newInstance(Enum.valueOf(EnumScoreboardAction, "REMOVE"), objective, playerName, 0));
} else {
player.sendPacket(newScorePacket_String.newInstance(playerName));
@@ -284,12 +380,12 @@ public class PacketScoreboard extends Scoreboard<BukkitTabPlayer> {
*/
@SneakyThrows
public Object newScoreboardObjective(@NotNull String objectiveName) {
if (nms.getMinorVersion() >= 13) {
if (BukkitReflection.getMinorVersion() >= 13) {
return newScoreboardObjective.newInstance(
null,
objectiveName,
null,
nms.toNMSComponent(new IChatBaseComponent(""), TAB.getInstance().getServerVersion()),
toComponent(new IChatBaseComponent("")),
null
);
}
@@ -299,4 +395,8 @@ public class PacketScoreboard extends Scoreboard<BukkitTabPlayer> {
IScoreboardCriteria_self.get(null)
);
}
private Object toComponent(IChatBaseComponent component) {
return player.getPlatform().toComponent(component, player.getVersion());
}
}
@@ -1,18 +1,16 @@
package me.neznamy.tab.platforms.bukkit.scoreboard;
import lombok.Getter;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.hook.AdventureHook;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.kyori.adventure.text.Component;
import org.bukkit.Bukkit;
import org.bukkit.scoreboard.Objective;
import org.bukkit.scoreboard.RenderType;
import org.bukkit.scoreboard.Team;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
/**
* Scoreboard handler using Paper API, which got added
* in early 1.16.5 builds. Unlike the Bukkit API, the new
@@ -21,94 +19,36 @@ import java.util.Collection;
* <p>
* However, it still inherits the issue with compatibility
* with other plugins that create scoreboards.
* <p>
* Currently, this class is never used. In the future, it might
* be used as a fallback solution for unsupported versions if NMS
* changes and fields fail to load. Or as a complete replacement
* if compatibility with other plugins turns out not to be a problem.
*/
@SuppressWarnings("ConstantConditions") // Throw the NPE if something is not as expected, parent class should ensure it anyway
public class PaperScoreboard extends Scoreboard<BukkitTabPlayer> {
public class PaperScoreboard extends BukkitScoreboard {
private final org.bukkit.scoreboard.Scoreboard scoreboard = Bukkit.getScoreboardManager().getNewScoreboard();
/** Flag tracking whether this implementation is available for use */
@Getter
private static final boolean available = ReflectionUtils.classExists("net.kyori.adventure.text.Component") &&
ReflectionUtils.methodExists(Team.class, "prefix", Component.class);
public PaperScoreboard(BukkitTabPlayer player) {
public PaperScoreboard(@NotNull BukkitTabPlayer player) {
super(player);
// Put player into a different scoreboard for per-player view
player.getPlayer().setScoreboard(scoreboard);
}
@Override
public void setDisplaySlot(@NotNull DisplaySlot slot, @NotNull String objective) {
checkPlayerScoreboard();
scoreboard.getObjective(objective).setDisplaySlot(org.bukkit.scoreboard.DisplaySlot.valueOf(slot.name()));
public void newObjective(String objectiveName, String criteria, String title, @NotNull HealthDisplay display) {
scoreboard.registerNewObjective(objectiveName, criteria, toAdventure(title), RenderType.valueOf(display.name()));
}
@Override
public void setScore0(@NotNull String objective, @NotNull String playerName, int score) {
checkPlayerScoreboard();
scoreboard.getObjective(objective).getScore(playerName).setScore(score);
public void setDisplayName(@NotNull Objective objective, @NotNull String displayName) {
objective.displayName(toAdventure(displayName));
}
@Override
public void removeScore0(@NotNull String objective, @NotNull String playerName) {
checkPlayerScoreboard();
scoreboard.resetScores(playerName);
}
@Override
public void registerObjective0(@NotNull String objectiveName, @NotNull String title, boolean hearts) {
checkPlayerScoreboard();
scoreboard.registerNewObjective(objectiveName, "dummy", toAdventure(title), hearts ? RenderType.HEARTS : RenderType.INTEGER);
}
@Override
public void unregisterObjective0(@NotNull String objectiveName) {
checkPlayerScoreboard();
scoreboard.getObjective(objectiveName).unregister();
}
@Override
public void updateObjective0(@NotNull String objectiveName, @NotNull String title, boolean hearts) {
checkPlayerScoreboard();
Objective obj = scoreboard.getObjective(objectiveName);
obj.setRenderType(hearts ? RenderType.HEARTS : RenderType.INTEGER);
obj.displayName(toAdventure(title));
}
@Override
public void registerTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix,
@NotNull NameVisibility visibility, @NotNull CollisionRule collision,
@NotNull Collection<String> players, int options) {
checkPlayerScoreboard();
Team team = scoreboard.registerNewTeam(name);
public void setPrefix(@NotNull Team team, @NotNull String prefix) {
team.prefix(toAdventure(prefix));
team.suffix(toAdventure(suffix));
team.setOption(Team.Option.NAME_TAG_VISIBILITY, Team.OptionStatus.values()[visibility.ordinal()]);
team.setOption(Team.Option.COLLISION_RULE, Team.OptionStatus.values()[collision.ordinal()]);
players.forEach(team::addEntry);
team.setAllowFriendlyFire((options & 0x01) != 0);
team.setCanSeeFriendlyInvisibles((options & 0x02) != 0);
}
@Override
public void unregisterTeam0(@NotNull String name) {
checkPlayerScoreboard();
scoreboard.getTeam(name).unregister();
}
@Override
public void updateTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix,
@NotNull NameVisibility visibility, @NotNull CollisionRule collision, int options) {
checkPlayerScoreboard();
Team team = scoreboard.getTeam(name);
team.prefix(toAdventure(prefix));
public void setSuffix(@NotNull Team team, @NotNull String suffix) {
team.suffix(toAdventure(suffix));
team.setOption(Team.Option.NAME_TAG_VISIBILITY, Team.OptionStatus.values()[visibility.ordinal()]);
team.setOption(Team.Option.COLLISION_RULE, Team.OptionStatus.values()[collision.ordinal()]);
team.setAllowFriendlyFire((options & 0x01) != 0);
team.setCanSeeFriendlyInvisibles((options & 0x02) != 0);
}
/**
@@ -118,24 +58,8 @@ public class PaperScoreboard extends Scoreboard<BukkitTabPlayer> {
* Text to convert
* @return Converted component
*/
private @NotNull Component toAdventure(@NotNull String text) {
return IChatBaseComponent.optimizedComponent(text).toAdventureComponent(player.getVersion());
}
/**
* Makes sure player is in the correct scoreboard.
* If not, puts the player into correct scoreboard and prints
* a warning.
* The only possible reason for this to happen is another plugin
* putting a player into a different scoreboard.
* Sadly there is no efficient solution to this, which md_5 fails
* to understand and keeps saying you don't need packets for scoreboards.
*/
private void checkPlayerScoreboard() {
if (player.getPlayer().getScoreboard() != scoreboard) {
player.getPlayer().setScoreboard(scoreboard);
TAB.getInstance().getErrorManager().printError("Player " + player.getName() + " was in a different scoreboard " +
"than expected. This means another plugin changed player's scoreboard.");
}
@NotNull
private Component toAdventure(@NotNull String text) {
return AdventureHook.toAdventureComponent(IChatBaseComponent.optimizedComponent(text), player.getVersion());
}
}
+1 -1
View File
@@ -11,7 +11,7 @@ softdepend: [PlaceholderAPI, LuckPerms, LibsDisguises, ViaVersion, ViaRewind, fl
loadbefore: []
prefix: TAB
libraries: []
folia-supported: false
folia-supported: true
commands:
tab:
description: Plugin's main command
@@ -18,11 +18,15 @@ import java.util.UUID;
*/
@RequiredArgsConstructor
public class BungeeBossBar implements BossBar {
private final @NotNull BungeeTabPlayer player;
@NotNull
private final BungeeTabPlayer player;
/** Both are included in the same action, need to remember and write them both */
@NotNull
private final Map<UUID, Integer> colors = new HashMap<>();
@NotNull
private final Map<UUID, Integer> styles = new HashMap<>();
@Override
@@ -1,5 +1,6 @@
package me.neznamy.tab.platforms.bungeecord;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.platform.EventListener;
import me.neznamy.tab.shared.platform.TabPlayer;
@@ -10,6 +11,7 @@ import net.md_5.bungee.api.event.PluginMessageEvent;
import net.md_5.bungee.api.event.ServerSwitchEvent;
import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.event.EventHandler;
import org.jetbrains.annotations.NotNull;
/**
* The core for BungeeCord forwarding events into all enabled features
@@ -27,23 +29,24 @@ public class BungeeEventListener extends EventListener<ProxiedPlayer> implements
}
@EventHandler
public void onChat(ChatEvent e) {
public void onCommand(ChatEvent e) {
if (e.isCommand() && command(((ProxiedPlayer)e.getSender()).getUniqueId(), e.getMessage())) {
e.setCancelled(true);
}
}
@EventHandler
public void on(PluginMessageEvent event) {
if (!event.getTag().equals(TabConstants.PLUGIN_MESSAGE_CHANNEL_NAME)) return;
if (event.getReceiver() instanceof ProxiedPlayer) {
event.setCancelled(true);
pluginMessage(((ProxiedPlayer) event.getReceiver()).getUniqueId(), event.getData());
public void on(PluginMessageEvent e) {
if (!e.getTag().equals(TabConstants.PLUGIN_MESSAGE_CHANNEL_NAME)) return;
if (e.getReceiver() instanceof ProxiedPlayer) {
e.setCancelled(true);
pluginMessage(((ProxiedPlayer) e.getReceiver()).getUniqueId(), e.getData());
}
}
@Override
public TabPlayer createPlayer(ProxiedPlayer player) {
return new BungeeTabPlayer(player);
@NotNull
public TabPlayer createPlayer(@NotNull ProxiedPlayer player) {
return new BungeeTabPlayer((BungeePlatform) TAB.getInstance().getPlatform(), player);
}
}
@@ -8,6 +8,7 @@ import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
import java.lang.reflect.Field;
import lombok.SneakyThrows;
import me.neznamy.tab.shared.features.nametags.NameTag;
import me.neznamy.tab.shared.features.redis.RedisSupport;
import me.neznamy.tab.shared.features.redis.feature.RedisTeams;
@@ -18,14 +19,17 @@ import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.features.injection.NettyPipelineInjector;
import me.neznamy.tab.shared.features.redis.RedisPlayer;
import me.neznamy.tab.shared.features.sorting.Sorting;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.connection.InitialHandler;
import net.md_5.bungee.netty.ChannelWrapper;
import net.md_5.bungee.protocol.DefinedPacket;
import net.md_5.bungee.protocol.Protocol;
import net.md_5.bungee.protocol.packet.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.function.Function;
import java.util.function.Supplier;
@@ -38,41 +42,60 @@ import java.util.function.Supplier;
@SuppressWarnings("unchecked")
public class BungeePipelineInjector extends NettyPipelineInjector {
private static @Nullable Field wrapperField;
/** Inaccessible bungee internals */
@Nullable
private static Field wrapperField;
@Nullable
private static Object directionData;
@Nullable
private static Method getId;
static {
try {
(wrapperField = InitialHandler.class.getDeclaredField("ch")).setAccessible(true);
directionData = ReflectionUtils.setAccessible(Protocol.class.getDeclaredField("TO_CLIENT")).get(Protocol.GAME);
getId = ReflectionUtils.setAccessible(directionData.getClass().getDeclaredMethod("getId", Class.class, int.class));
} catch (ReflectiveOperationException exception) {
TAB.getInstance().getErrorManager().criticalError("Failed to initialize bungee internal fields", exception);
}
}
/** Packets used by the plugin that must be deserialized and BungeeCord does not do it automatically */
@NotNull
private final Class<? extends DefinedPacket>[] extraPacketClasses = new Class[]{Team.class, ScoreboardDisplay.class, ScoreboardObjective.class};
@NotNull
private final Supplier<DefinedPacket>[] extraPacketSuppliers = new Supplier[]{Team::new, ScoreboardDisplay::new, ScoreboardObjective::new};
@Override
@NotNull
public Function<TabPlayer, ChannelDuplexHandler> getChannelFunction() {
return byteBufDeserialization ? DeserializableBungeeChannelDuplexHandler::new : TabChannelDuplexHandler::new;
}
@Override
protected @Nullable Channel getChannel(@NotNull TabPlayer player) {
@Nullable
protected Channel getChannel(@NotNull TabPlayer player) {
if (wrapperField == null) return null;
final BungeeTabPlayer bungee = (BungeeTabPlayer) player;
try {
return ((ChannelWrapper) wrapperField.get(bungee.getPlayer().getPendingConnection())).getHandle();
} catch (final IllegalAccessException exception) {
} catch (IllegalAccessException exception) {
TAB.getInstance().getErrorManager().criticalError("Failed to get channel of " + bungee.getPlayer().getName(), exception);
}
return null;
}
@Override
@SneakyThrows
public void onDisplayObjective(@NotNull TabPlayer player, @NotNull Object packet) {
TAB.getInstance().getFeatureManager().onDisplayObjective(player,
((ScoreboardDisplay) packet).getPosition(), ((ScoreboardDisplay) packet).getName());
TAB.getInstance().getFeatureManager().onDisplayObjective(
player,
((Number) ScoreboardDisplay.class.getMethod("getPosition").invoke(packet)).intValue(),
((ScoreboardDisplay) packet).getName()
);
}
@Override
@@ -110,10 +133,11 @@ public class BungeePipelineInjector extends NettyPipelineInjector {
for (TabPlayer p : TAB.getInstance().getOnlinePlayers()) {
Sorting sorting = TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.SORTING);
String expectedTeam = sorting.getShortTeamName(p);
if (expectedTeam != null && col.contains(p.getName()) &&
if (expectedTeam != null && (col.contains(p.getNickname()) || col.contains(p.getName())) &&
!((NameTag)TAB.getInstance().getNameTagManager()).getDisableChecker().isDisabledPlayer(p) &&
!TAB.getInstance().getNameTagManager().hasTeamHandlingPaused(p) && !packet.getName().equals(expectedTeam)) {
logTeamOverride(packet.getName(), p.getName(), expectedTeam);
col.remove(p.getNickname());
col.remove(p.getName());
}
}
@@ -122,9 +146,9 @@ public class BungeePipelineInjector extends NettyPipelineInjector {
RedisTeams teams = redis.getRedisTeams();
if (teams != null) {
for (RedisPlayer p : redis.getRedisPlayers().values()) {
if (col.contains(p.getName()) && !packet.getName().equals(teams.getTeamNames().get(p))) {
logTeamOverride(packet.getName(), p.getName(), teams.getTeamNames().get(p));
col.remove(p.getName());
if (col.contains(p.getNickname()) && !packet.getName().equals(teams.getTeamNames().get(p))) {
logTeamOverride(packet.getName(), p.getNickname(), teams.getTeamNames().get(p));
col.remove(p.getNickname());
}
}
}
@@ -141,6 +165,12 @@ public class BungeePipelineInjector extends NettyPipelineInjector {
IChatBaseComponent newDisplayName = TAB.getInstance().getFeatureManager().onDisplayNameChange(receiver, item.getUuid());
if (newDisplayName != null) item.setDisplayName(newDisplayName.toString(receiver.getVersion()));
}
if (update.getActions().contains(PlayerListItemUpdate.Action.UPDATE_LATENCY)) {
item.setPing(TAB.getInstance().getFeatureManager().onLatencyChange(receiver, item.getUuid(), item.getPing()));
}
if (update.getActions().contains(PlayerListItemUpdate.Action.ADD_PLAYER)) {
TAB.getInstance().getFeatureManager().onEntryAdd(receiver, item.getUuid(), item.getUsername());
}
}
} else {
PlayerListItem listItem = (PlayerListItem) packet;
@@ -149,6 +179,12 @@ public class BungeePipelineInjector extends NettyPipelineInjector {
IChatBaseComponent newDisplayName = TAB.getInstance().getFeatureManager().onDisplayNameChange(receiver, item.getUuid());
if (newDisplayName != null) item.setDisplayName(newDisplayName.toString(receiver.getVersion()));
}
if (listItem.getAction() == PlayerListItem.Action.UPDATE_LATENCY || listItem.getAction() == PlayerListItem.Action.ADD_PLAYER) {
item.setPing(TAB.getInstance().getFeatureManager().onLatencyChange(receiver, item.getUuid(), item.getPing()));
}
if (listItem.getAction() == PlayerListItem.Action.ADD_PLAYER) {
TAB.getInstance().getFeatureManager().onEntryAdd(receiver, item.getUuid(), item.getUsername());
}
}
}
}
@@ -173,12 +209,12 @@ public class BungeePipelineInjector extends NettyPipelineInjector {
* @param player
* player to inject
*/
public DeserializableBungeeChannelDuplexHandler(TabPlayer player) {
public DeserializableBungeeChannelDuplexHandler(@NotNull TabPlayer player) {
super(player);
}
@Override
public void write(ChannelHandlerContext context, Object packet, ChannelPromise channelPromise) {
public void write(@NotNull ChannelHandlerContext context, @NotNull Object packet, @NotNull ChannelPromise channelPromise) {
long time = System.nanoTime();
Object modifiedPacket = packet instanceof ByteBuf ? deserialize((ByteBuf) packet) : packet;
TAB.getInstance().getCPUManager().addTime(TabConstants.Feature.PACKET_DESERIALIZING, TabConstants.CpuUsageCategory.BYTE_BUF, System.nanoTime()-time);
@@ -193,12 +229,13 @@ public class BungeePipelineInjector extends NettyPipelineInjector {
* byte buf to deserialize
* @return deserialized packet or input byte buf if packet is not tracked
*/
private @NotNull Object deserialize(@NotNull ByteBuf buf) {
@NotNull
private Object deserialize(@NotNull ByteBuf buf) {
int marker = buf.readerIndex();
try {
int packetId = buf.readByte();
for (int i=0; i<extraPacketClasses.length; i++) {
if (packetId == ((BungeeTabPlayer)player).getPacketId(extraPacketClasses[i])) {
if (packetId == getPacketId(((BungeeTabPlayer)player).getPlayer().getPendingConnection().getVersion(), extraPacketClasses[i])) {
DefinedPacket packet = extraPacketSuppliers[i].get();
packet.read(buf, null, ((ProxiedPlayer)player.getPlayer()).getPendingConnection().getVersion());
buf.release();
@@ -211,5 +248,25 @@ public class BungeePipelineInjector extends NettyPipelineInjector {
buf.readerIndex(marker);
return buf;
}
/**
* Returns packet ID of specified packet on the protocol version
*
* @param protocolVersion
* Protocol version to get packet id for
* @param clazz
* packet class
* @return packet ID
*/
private int getPacketId(int protocolVersion, @NotNull Class<? extends DefinedPacket> clazz) {
if (getId == null) return -1;
try {
return (int) getId.invoke(directionData, clazz, protocolVersion);
} catch (ReflectiveOperationException e) {
TAB.getInstance().getErrorManager().printError("Failed to get packet id for packet " + clazz +
" with client version " + protocolVersion, e);
return -1;
}
}
}
}
@@ -2,53 +2,131 @@ package me.neznamy.tab.platforms.bungeecord;
import com.imaginarycode.minecraft.redisbungee.RedisBungeeAPI;
import lombok.AllArgsConstructor;
import me.neznamy.tab.platforms.bungeecord.features.BungeeRedisSupport;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.chat.ChatModifier;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.features.injection.PipelineInjector;
import me.neznamy.tab.shared.features.redis.RedisSupport;
import me.neznamy.tab.shared.hook.ViaVersionHook;
import me.neznamy.tab.shared.proxy.ProxyPlatform;
import me.neznamy.tab.shared.util.ComponentCache;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import org.bstats.bungeecord.Metrics;
import org.bstats.charts.SimplePie;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.util.stream.Collectors;
/**
* BungeeCord implementation of Platform
*/
@AllArgsConstructor
public class BungeePlatform extends ProxyPlatform {
public class BungeePlatform extends ProxyPlatform<BaseComponent> {
@NotNull private final BungeeTAB plugin;
@NotNull
private final BungeeTAB plugin;
/** Component cache for BungeeCord components */
@NotNull
private final ComponentCache<IChatBaseComponent, BaseComponent> bungeeCache =
new ComponentCache<>(1000, this::toComponent0);
@Override
public @Nullable RedisSupport getRedisSupport() {
public void loadPlayers() {
ViaVersionHook.getInstance().printProxyWarn();
for (ProxiedPlayer p : ProxyServer.getInstance().getPlayers()) {
TAB.getInstance().addPlayer(new BungeeTabPlayer(this, p));
}
}
@Override
@Nullable
public RedisSupport getRedisSupport() {
if (ReflectionUtils.classExists("com.imaginarycode.minecraft.redisbungee.RedisBungeeAPI") &&
RedisBungeeAPI.getRedisBungeeApi() != null) {
return new RedisBungeeSupport(plugin);
return new BungeeRedisSupport(plugin);
}
return null;
}
@Override
public void sendConsoleMessage(@NotNull IChatBaseComponent message) {
public void logInfo(@NotNull IChatBaseComponent message) {
plugin.getLogger().info(message.toLegacyText());
}
@Override
public void logWarn(@NotNull IChatBaseComponent message) {
plugin.getLogger().warning(EnumChatFormat.RED.getFormat() + message.toLegacyText());
}
@Override
@NotNull
public String getServerVersionInfo() {
return "[BungeeCord] " + plugin.getProxy().getName() + " - " + plugin.getProxy().getVersion();
}
@Override
public void loadPlayers() {
for (ProxiedPlayer p : ProxyServer.getInstance().getPlayers()) {
TAB.getInstance().addPlayer(new BungeeTabPlayer(p));
}
public void registerListener() {
ProxyServer.getInstance().getPluginManager().registerListener(plugin, new BungeeEventListener());
}
@Override
public @Nullable PipelineInjector createPipelineInjector() {
public void registerCommand() {
ProxyServer.getInstance().getPluginManager().registerCommand(plugin, new BungeeTabCommand());
}
@Override
public void startMetrics() {
new Metrics(plugin, 10535).addCustomChart(new SimplePie(TabConstants.MetricsChart.GLOBAL_PLAYER_LIST_ENABLED,
() -> TAB.getInstance().getFeatureManager().isFeatureEnabled(TabConstants.Feature.GLOBAL_PLAYER_LIST) ? "Yes" : "No"));
}
@Override
@NotNull
public File getDataFolder() {
return plugin.getDataFolder();
}
@Override
public BaseComponent toComponent(@NotNull IChatBaseComponent component, @NotNull ProtocolVersion version) {
return bungeeCache.get(component, version);
}
private BaseComponent toComponent0(IChatBaseComponent component, ProtocolVersion version) {
ChatModifier modifier = component.getModifier();
TextComponent textComponent = new TextComponent(component.getText());
if (modifier.getColor() != null) textComponent.setColor(ChatColor.of(
modifier.getColor().toString(version.getMinorVersion() >= 16)));
if (modifier.isBold()) textComponent.setBold(true);
if (modifier.isItalic()) textComponent.setItalic(true);
if (modifier.isObfuscated()) textComponent.setObfuscated(true);
if (modifier.isStrikethrough()) textComponent.setStrikethrough(true);
if (modifier.isUnderlined()) textComponent.setUnderlined(true);
if (modifier.getFont() != null) textComponent.setFont(modifier.getFont());
if (!component.getExtra().isEmpty()) textComponent.setExtra(
component.getExtra().stream().map(c -> toComponent0(c, version)).collect(Collectors.toList()));
return textComponent;
}
@Override
@Nullable
public PipelineInjector createPipelineInjector() {
return new BungeePipelineInjector();
}
}
@Override
public void registerChannel() {
ProxyServer.getInstance().registerChannel(TabConstants.PLUGIN_MESSAGE_CHANNEL_NAME);
}
}
@@ -1,6 +1,6 @@
package me.neznamy.tab.platforms.bungeecord;
import me.neznamy.tab.shared.ProtocolVersion;
import lombok.SneakyThrows;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.Scoreboard;
@@ -10,6 +10,8 @@ import net.md_5.bungee.protocol.packet.ScoreboardScore;
import net.md_5.bungee.protocol.packet.Team;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Constructor;
import java.util.Arrays;
import java.util.Collection;
/**
@@ -19,84 +21,105 @@ import java.util.Collection;
*/
public class BungeeScoreboard extends Scoreboard<BungeeTabPlayer> {
public BungeeScoreboard(BungeeTabPlayer player) {
/** Constructor to support both <1.20.2 and 1.20.2+ builds */
@SuppressWarnings("unchecked")
private static final Constructor<ScoreboardDisplay> newScoreboardDisplay = (Constructor<ScoreboardDisplay>)
Arrays.stream(ScoreboardDisplay.class.getConstructors()).filter(c -> c.getParameterCount() == 2).findAny().orElse(null);
public BungeeScoreboard(@NotNull BungeeTabPlayer player) {
super(player);
}
@Override
@SneakyThrows
public void setDisplaySlot(@NotNull DisplaySlot slot, @NotNull String objective) {
player.sendPacket(new ScoreboardDisplay((byte)slot.ordinal(), objective));
player.getPlayer().unsafe().sendPacket(newScoreboardDisplay.newInstance((byte)slot.ordinal(), objective));
}
@Override
public void registerObjective0(@NotNull String objectiveName, @NotNull String title, boolean hearts) {
player.sendPacket(new ScoreboardObjective(objectiveName, jsonOrRaw(title, player.getVersion()), hearts ? ScoreboardObjective.HealthDisplay.HEARTS : ScoreboardObjective.HealthDisplay.INTEGER, (byte) 0));
public void registerObjective0(@NotNull String objectiveName, @NotNull String title, @NotNull HealthDisplay display) {
player.getPlayer().unsafe().sendPacket(new ScoreboardObjective(
objectiveName,
jsonOrRaw(title),
ScoreboardObjective.HealthDisplay.valueOf(display.name()),
(byte) 0
));
}
@Override
public void unregisterObjective0(@NotNull String objectiveName) {
player.sendPacket(new ScoreboardObjective(objectiveName, "", null, (byte) 1)); // Empty string to prevent kick on 1.7
player.getPlayer().unsafe().sendPacket(new ScoreboardObjective(
objectiveName,
"", // Empty string to prevent kick on 1.7
null,
(byte) 1
));
}
@Override
public void updateObjective0(@NotNull String objectiveName, @NotNull String title, boolean hearts) {
player.sendPacket(new ScoreboardObjective(objectiveName, jsonOrRaw(title, player.getVersion()), hearts ? ScoreboardObjective.HealthDisplay.HEARTS : ScoreboardObjective.HealthDisplay.INTEGER, (byte) 2));
public void updateObjective0(@NotNull String objectiveName, @NotNull String title, @NotNull HealthDisplay display) {
player.getPlayer().unsafe().sendPacket(new ScoreboardObjective(
objectiveName,
jsonOrRaw(title),
ScoreboardObjective.HealthDisplay.valueOf(display.name()),
(byte) 2
));
}
@Override
public void registerTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix, @NotNull NameVisibility visibility, @NotNull CollisionRule collision, @NotNull Collection<String> players, int options) {
public void registerTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix,
@NotNull NameVisibility visibility, @NotNull CollisionRule collision,
@NotNull Collection<String> players, int options) {
int color = 0;
if (player.getVersion().getMinorVersion() >= 13) {
color = EnumChatFormat.lastColorsOf(prefix).ordinal();
}
player.sendPacket(new Team(name, (byte) 0, jsonOrRaw(name, player.getVersion()),
jsonOrRaw(prefix, player.getVersion()), jsonOrRaw(suffix, player.getVersion()),
player.getPlayer().unsafe().sendPacket(new Team(name, (byte) 0, jsonOrRaw(name), jsonOrRaw(prefix), jsonOrRaw(suffix),
visibility.toString(), collision.toString(), color, (byte)options, players.toArray(new String[0])));
}
@Override
public void unregisterTeam0(@NotNull String name) {
player.sendPacket(new Team(name));
player.getPlayer().unsafe().sendPacket(new Team(name));
}
@Override
public void updateTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix, @NotNull NameVisibility visibility, @NotNull CollisionRule collision, int options) {
public void updateTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix,
@NotNull NameVisibility visibility, @NotNull CollisionRule collision, int options) {
int color = 0;
if (player.getVersion().getMinorVersion() >= 13) {
color = EnumChatFormat.lastColorsOf(prefix).ordinal();
}
player.sendPacket(new Team(name, (byte) 2, jsonOrRaw(name, player.getVersion()),
jsonOrRaw(prefix, player.getVersion()), jsonOrRaw(suffix, player.getVersion()),
visibility.toString(), collision.toString(), color, (byte)options, null));
}
/**
* If {@code clientVersion} is &gt;= 1.13, creates a component from given text and returns
* it as a serialized component, which BungeeCord uses.
* <p>
* If {@code clientVersion} is &lt; 1.12, the text is returned
*
* @param text
* Text to convert
* @param clientVersion
* Version of player to convert text for
* @return serialized component for 1.13+ clients, cut string for 1.12-
*/
private String jsonOrRaw(@NotNull String text, @NotNull ProtocolVersion clientVersion) {
if (clientVersion.getMinorVersion() >= 13) {
return IChatBaseComponent.optimizedComponent(text).toString(clientVersion);
} else {
return text;
}
player.getPlayer().unsafe().sendPacket(new Team(name, (byte) 2, jsonOrRaw(name), jsonOrRaw(prefix),
jsonOrRaw(suffix), visibility.toString(), collision.toString(), color, (byte)options, null));
}
@Override
public void setScore0(@NotNull String objective, @NotNull String playerName, int score) {
player.sendPacket(new ScoreboardScore(playerName, (byte) 0, objective, score));
player.getPlayer().unsafe().sendPacket(new ScoreboardScore(playerName, (byte) 0, objective, score));
}
@Override
public void removeScore0(@NotNull String objective, @NotNull String playerName) {
player.sendPacket(new ScoreboardScore(playerName, (byte) 1, objective, 0));
player.getPlayer().unsafe().sendPacket(new ScoreboardScore(playerName, (byte) 1, objective, 0));
}
/**
* If player's version is 1.13+, creates a component from given text and returns
* it as a serialized component, which BungeeCord uses.
* <p>
* If player's version is 1.12-, the text is returned
*
* @param text
* Text to convert
* @return serialized component for 1.13+ clients, cut string for 1.12-
*/
@NotNull
private String jsonOrRaw(@NotNull String text) {
if (player.getVersion().getMinorVersion() >= 13) {
return IChatBaseComponent.optimizedComponent(text).toString(player.getVersion());
} else {
return text;
}
}
}
@@ -1,33 +1,23 @@
package me.neznamy.tab.platforms.bungeecord;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.md_5.bungee.api.plugin.Plugin;
import org.bstats.bungeecord.Metrics;
import org.bstats.charts.SimplePie;
/**
* Main class for BungeeCord platform
* Main class for BungeeCord.
*/
public class BungeeTAB extends Plugin {
@Override
public void onEnable() {
if (!ReflectionUtils.classExists("net.md_5.bungee.protocol.packet.PlayerListItemUpdate")) {
getLogger().info(EnumChatFormat.color("&cThe plugin requires BungeeCord build #1671 and up (or an equivalent fork) to work."));
getLogger().info(EnumChatFormat.color("&cThe plugin requires BungeeCord build #1671 " +
"(with 1.19.3 support) and up (or an equivalent fork) to work."));
return;
}
getProxy().registerChannel(TabConstants.PLUGIN_MESSAGE_CHANNEL_NAME);
getProxy().getPluginManager().registerListener(this, new BungeeEventListener());
getProxy().getPluginManager().registerCommand(this, new BungeeTabCommand());
TAB.setInstance(new TAB(new BungeePlatform(this), ProtocolVersion.PROXY, getDataFolder()));
TAB.getInstance().load();
Metrics metrics = new Metrics(this, 10535);
metrics.addCustomChart(new SimplePie(TabConstants.MetricsChart.PERMISSION_SYSTEM, () -> TAB.getInstance().getGroupManager().getPermissionPlugin()));
metrics.addCustomChart(new SimplePie(TabConstants.MetricsChart.GLOBAL_PLAYER_LIST_ENABLED, () -> TAB.getInstance().getFeatureManager().isFeatureEnabled(TabConstants.Feature.GLOBAL_PLAYER_LIST) ? "Yes" : "No"));
TAB.create(new BungeePlatform(this));
}
@Override
@@ -9,8 +9,9 @@ import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.plugin.Command;
import net.md_5.bungee.api.plugin.TabExecutor;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Collections;
public class BungeeTabCommand extends Command implements TabExecutor {
@@ -22,7 +23,7 @@ public class BungeeTabCommand extends Command implements TabExecutor {
}
@Override
public void execute(CommandSender sender, String[] args) {
public void execute(@NotNull CommandSender sender, @NotNull String[] args) {
if (TAB.getInstance().isPluginDisabled()) {
for (String message : TAB.getInstance().getDisabledCommand().execute(args, sender.hasPermission(TabConstants.Permission.COMMAND_RELOAD), sender.hasPermission(TabConstants.Permission.COMMAND_ALL))) {
sender.sendMessage(new TextComponent(EnumChatFormat.color(message)));
@@ -38,11 +39,12 @@ public class BungeeTabCommand extends Command implements TabExecutor {
}
@Override
public Iterable<String> onTabComplete(CommandSender sender, String[] args) {
@NotNull
public Iterable<String> onTabComplete(@NotNull CommandSender sender, @NotNull String[] args) {
TabPlayer p = null;
if (sender instanceof ProxiedPlayer) {
p = TAB.getInstance().getPlayer(((ProxiedPlayer)sender).getUniqueId());
if (p == null) return new ArrayList<>(); //player not loaded correctly
if (p == null) return Collections.emptyList(); //player not loaded correctly
}
return TAB.getInstance().getCommand().complete(p, args);
}
@@ -6,66 +6,57 @@ import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.platforms.bungeecord.tablist.BungeeTabList1193;
import me.neznamy.tab.platforms.bungeecord.tablist.BungeeTabList17;
import me.neznamy.tab.platforms.bungeecord.tablist.BungeeTabList18;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.bossbar.BossBar;
import me.neznamy.tab.shared.proxy.ProxyTabPlayer;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.chat.ComponentSerializer;
import net.md_5.bungee.connection.InitialHandler;
import net.md_5.bungee.connection.LoginResult;
import net.md_5.bungee.protocol.DefinedPacket;
import net.md_5.bungee.protocol.Property;
import net.md_5.bungee.protocol.Protocol;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Method;
/**
* TabPlayer implementation for BungeeCord
*/
@Getter
public class BungeeTabPlayer extends ProxyTabPlayer {
/** Inaccessible bungee internals */
private static @Nullable Object directionData;
private static @Nullable Method getId;
/** Flag tracking plugin presence */
private static final boolean premiumVanish = ProxyServer.getInstance().getPluginManager().getPlugin("PremiumVanish") != null;
static {
try {
directionData = ReflectionUtils.setAccessible(Protocol.class.getDeclaredField("TO_CLIENT")).get(Protocol.GAME);
getId = ReflectionUtils.setAccessible(directionData.getClass().getDeclaredMethod("getId", Class.class, int.class));
} catch (ReflectiveOperationException e) {
TAB.getInstance().getErrorManager().criticalError("Failed to initialize bungee internal fields", e);
}
}
/** Player's scoreboard */
@Getter private final @NotNull Scoreboard<BungeeTabPlayer> scoreboard = new BungeeScoreboard(this);
@NotNull
private final Scoreboard<BungeeTabPlayer> scoreboard = new BungeeScoreboard(this);
/** Player's tab list based on version */
private final @NotNull TabList tabList1_7 = new BungeeTabList17(this);
private final @NotNull TabList tabList1_8 = new BungeeTabList18(this);
private final @NotNull TabList tabList1_19_3 = new BungeeTabList1193(this);
@NotNull
private final TabList tabList1_7 = new BungeeTabList17(this);
@Getter private final @NotNull BossBar bossBar = new BungeeBossBar(this);
@NotNull
private final TabList tabList1_8 = new BungeeTabList18(this);
@NotNull
private final TabList tabList1_19_3 = new BungeeTabList1193(this);
/** Player's boss bar view */
@NotNull
private final BossBar bossBar = new BungeeBossBar(this);
/**
* Constructs new instance for given player
*
* @param platform
* Server platform
* @param p
* BungeeCord player
*/
public BungeeTabPlayer(@NotNull ProxiedPlayer p) {
super(p, p.getUniqueId(), p.getName(), p.getServer() != null ? p.getServer().getInfo().getName() : "-", -1);
public BungeeTabPlayer(@NotNull BungeePlatform platform, @NotNull ProxiedPlayer p) {
super(platform, p, p.getUniqueId(), p.getName(), p.getServer() != null ? p.getServer().getInfo().getName() : "-", -1);
}
@Override
@@ -78,45 +69,44 @@ public class BungeeTabPlayer extends ProxyTabPlayer {
return getPlayer().getPing();
}
public void sendPacket(@NotNull Object nmsPacket) {
getPlayer().unsafe().sendPacket((DefinedPacket) nmsPacket);
}
@Override
public void sendMessage(@NotNull IChatBaseComponent message) {
getPlayer().sendMessage(ComponentSerializer.parse(message.toString(getVersion())));
getPlayer().sendMessage(getPlatform().toComponent(message, getVersion()));
}
@Override
public @Nullable TabList.Skin getSkin() {
@Nullable
public TabList.Skin getSkin() {
LoginResult loginResult = ((InitialHandler)getPlayer().getPendingConnection()).getLoginProfile();
if (loginResult == null) return null;
Property[] properties = loginResult.getProperties();
if (properties == null || properties.length == 0) return null;
if (properties == null || properties.length == 0) return null; //Offline mode
return new TabList.Skin(properties[0].getValue(), properties[0].getSignature());
}
@Override
public @NotNull ProxiedPlayer getPlayer() {
@NotNull
public ProxiedPlayer getPlayer() {
return (ProxiedPlayer) player;
}
/**
* Returns packet ID for this player of provided packet class
*
* @param clazz
* packet class
* @return packet ID
*/
public int getPacketId(@NotNull Class<? extends DefinedPacket> clazz) {
if (getId == null) return -1;
try {
return (int) getId.invoke(directionData, clazz, getPlayer().getPendingConnection().getVersion());
} catch (ReflectiveOperationException e) {
TAB.getInstance().getErrorManager().printError("Failed to get packet id for packet " + clazz +
" with client version " + getPlayer().getPendingConnection().getVersion(), e);
return -1;
@Override
public boolean isOnline() {
return getPlayer().isConnected();
}
@Override
public BungeePlatform getPlatform() {
return (BungeePlatform) platform;
}
@Override
public void sendPluginMessage(byte[] message) {
if (getPlayer().getServer() == null) {
errorNoServer(message);
return;
}
getPlayer().getServer().sendData(TabConstants.PLUGIN_MESSAGE_CHANNEL_NAME, message);
}
/**
@@ -127,35 +117,28 @@ public class BungeeTabPlayer extends ProxyTabPlayer {
* @return Player's current protocol version
*/
@Override
public @NotNull ProtocolVersion getVersion() {
@NotNull
public ProtocolVersion getVersion() {
return ProtocolVersion.fromNetworkId(getPlayer().getPendingConnection().getVersion());
}
@Override
public boolean isVanished() {
//noinspection ConstantConditions
if (premiumVanish && BungeeVanishAPI.isInvisible(getPlayer())) return true;
try {
//noinspection ConstantConditions
if (premiumVanish && BungeeVanishAPI.isInvisible(getPlayer())) return true;
} catch (IllegalStateException ignored) {
// PV Bug: PremiumVanish must be enabled to use its API
}
return super.isVanished();
}
@Override
public boolean isOnline() {
return getPlayer().isConnected();
}
@Override
public @NotNull TabList getTabList() {
return getVersion().getNetworkId() >= ProtocolVersion.V1_19_3.getNetworkId() ?
tabList1_19_3 : getVersion().getMinorVersion() >= 8 ? tabList1_8 : tabList1_7;
}
@Override
public void sendPluginMessage(byte[] message) {
if (getPlayer().getServer() == null) {
TAB.getInstance().getErrorManager().printError("Skipped plugin message send to " + getName() + ", because player is not" +
"connected to any server (message=" + new String(message) + ")");
return;
}
getPlayer().getServer().sendData(TabConstants.PLUGIN_MESSAGE_CHANNEL_NAME, message);
@NotNull
public TabList getTabList() {
int version = getPlayer().getPendingConnection().getVersion();
if (version >= ProtocolVersion.V1_19_3.getNetworkId()) return tabList1_19_3;
if (version >= ProtocolVersion.V1_8.getNetworkId()) return tabList1_8;
return tabList1_7;
}
}
@@ -1,24 +1,25 @@
package me.neznamy.tab.platforms.bungeecord;
package me.neznamy.tab.platforms.bungeecord.features;
import com.imaginarycode.minecraft.redisbungee.RedisBungeeAPI;
import com.imaginarycode.minecraft.redisbungee.events.PubSubMessageEvent;
import lombok.AllArgsConstructor;
import me.neznamy.tab.platforms.bungeecord.BungeeTAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.features.redis.RedisSupport;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.api.plugin.Plugin;
import net.md_5.bungee.event.EventHandler;
import org.jetbrains.annotations.NotNull;
/**
* Redis implementation for BungeeCord
* RedisBungee implementation for BungeeCord
*/
@AllArgsConstructor
public class RedisBungeeSupport extends RedisSupport implements Listener {
public class BungeeRedisSupport extends RedisSupport implements Listener {
/** Plugin reference for registering listener */
private final @NotNull Plugin plugin;
@NotNull
private final BungeeTAB plugin;
@EventHandler
public void onMessage(@NotNull PubSubMessageEvent e) {
@@ -25,6 +25,7 @@ import java.util.UUID;
public class BungeeTabList1193 implements TabList {
/** Player this TabList belongs to */
@NotNull
private final BungeeTabPlayer player;
@Override
@@ -71,7 +72,8 @@ public class BungeeTabList1193 implements TabList {
sendPacket(EnumSet.allOf(PlayerListItemUpdate.Action.class), item);
}
public @NotNull Item item(@NotNull UUID id) {
@NotNull
public Item item(@NotNull UUID id) {
Item item = new Item();
item.setUuid(id);
return item;
@@ -86,6 +88,9 @@ public class BungeeTabList1193 implements TabList {
@Override
public void setPlayerListHeaderFooter(@NotNull IChatBaseComponent header, @NotNull IChatBaseComponent footer) {
player.getPlayer().setTabHeader(header.toBungeeComponent(player.getVersion()), footer.toBungeeComponent(player.getVersion()));
player.getPlayer().setTabHeader(
player.getPlatform().toComponent(header, player.getVersion()),
player.getPlatform().toComponent(footer, player.getVersion())
);
}
}
@@ -28,10 +28,14 @@ import java.util.UUID;
public class BungeeTabList17 implements TabList {
/** Player this TabList belongs to */
@NotNull
private final BungeeTabPlayer player;
/** Because entries are identified by names and not uuids on 1.7 */
@NotNull
private final Map<UUID, String> userNames = new HashMap<>();
@NotNull
private final Map<UUID, String> displayNames = new HashMap<>();
@Override
@@ -23,6 +23,7 @@ import java.util.UUID;
public class BungeeTabList18 implements TabList {
/** Player this TabList belongs to */
@NotNull
private final BungeeTabPlayer player;
@Override
@@ -66,7 +67,8 @@ public class BungeeTabList18 implements TabList {
sendPacket(PlayerListItem.Action.ADD_PLAYER, item);
}
public @NotNull Item item(@NotNull UUID id) {
@NotNull
public Item item(@NotNull UUID id) {
Item item = new Item();
item.setUuid(id);
return item;
@@ -81,6 +83,9 @@ public class BungeeTabList18 implements TabList {
@Override
public void setPlayerListHeaderFooter(@NotNull IChatBaseComponent header, @NotNull IChatBaseComponent footer) {
player.getPlayer().setTabHeader(header.toBungeeComponent(player.getVersion()), footer.toBungeeComponent(player.getVersion()));
player.getPlayer().setTabHeader(
player.getPlatform().toComponent(header, player.getVersion()),
player.getPlatform().toComponent(footer, player.getVersion())
);
}
}
+3 -4
View File
@@ -1,12 +1,11 @@
// Versions that don't require any code changes:
// 1.19.3, 1.19.4, 1.20, 1.20.1
val minecraftVersion = "1.20.1"
val minecraftVersion = "1.20.2"
val v114 = "0.28.5+1.14"
val v115 = "0.28.5+1.15"
val v116 = "0.42.0+1.16"
val fabricApiVersions = mapOf(
"1.20.2" to "0.88.5+1.20.2",
"1.20.1" to "0.83.0+1.20.1",
"1.20" to "0.83.0+1.20",
"1.19.4" to "0.77.0+1.19.4",
@@ -41,7 +40,7 @@ plugins {
repositories {
// Gradle doesn't support combining settings and project repositories, so we have to re-declare all the settings repos we need
maven("https://repo.kryptonmc.org/releases")
maven("https://jitpack.io") // YamlAssist
maven("https://repo.opencollab.dev/maven-snapshots/")
maven("https://repo.viaversion.com/")
maven("https://oss.sonatype.org/content/repositories/snapshots")
@@ -15,13 +15,16 @@ import org.jetbrains.annotations.NotNull;
@RequiredArgsConstructor
public class FabricBossBar implements BossBar {
@NotNull
private final FabricTabPlayer player;
@NotNull
private final Map<UUID, ServerBossEvent> bars = new HashMap<>();
@Override
public void create(@NotNull UUID id, @NotNull String title, float progress, @NotNull BarColor color, @NotNull BarStyle style) {
ServerBossEvent bar = new ServerBossEvent(
FabricTAB.getInstance().toComponent(IChatBaseComponent.optimizedComponent(title), player.getVersion()),
player.getPlatform().toComponent(IChatBaseComponent.optimizedComponent(title), player.getVersion()),
BossEvent.BossBarColor.valueOf(color.name()),
BossEvent.BossBarOverlay.valueOf(style.name())
);
@@ -32,7 +35,7 @@ public class FabricBossBar implements BossBar {
@Override
public void update(@NotNull UUID id, @NotNull String title) {
bars.get(id).setName(FabricTAB.getInstance().toComponent(IChatBaseComponent.optimizedComponent(title), player.getVersion()));
bars.get(id).setName(player.getPlatform().toComponent(IChatBaseComponent.optimizedComponent(title), player.getVersion()));
}
@Override
@@ -0,0 +1,94 @@
package me.neznamy.tab.platforms.fabric;
import me.neznamy.tab.shared.backend.EntityData;
import me.neznamy.tab.shared.backend.Location;
import me.neznamy.tab.shared.backend.entityview.EntityView;
import net.minecraft.network.protocol.game.*;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.decoration.ArmorStand;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import java.util.UUID;
public record FabricEntityView(FabricTabPlayer player) implements EntityView {
@NotNull
private static final Entity dummyEntity = new ArmorStand(null, 0, 0, 0);
@Override
public void spawnEntity(int entityId, @NotNull UUID id, @NotNull Object entityType, @NotNull Location location,
@NotNull EntityData data) {
player.sendPacket(new ClientboundAddEntityPacket(entityId, id,
location.getX(), location.getY(), location.getZ(), 0, 0,
(EntityType<?>) entityType, 0, Vec3.ZERO, 0));
updateEntityMetadata(entityId, data);
}
@Override
@SuppressWarnings("unchecked")
public void updateEntityMetadata(int entityId, @NotNull EntityData data) {
player.sendPacket(new ClientboundSetEntityDataPacket(entityId, (List<SynchedEntityData.DataValue<?>>) data.build()));
}
@Override
public void teleportEntity(int entityId, @NotNull Location location) {
dummyEntity.setId(entityId);
dummyEntity.setPos(location.getX(), location.getY(), location.getZ());
player.sendPacket(new ClientboundTeleportEntityPacket(dummyEntity));
}
@Override
public void destroyEntities(int... entities) {
player.sendPacket(new ClientboundRemoveEntitiesPacket(entities));
}
@Override
public boolean isDestroyPacket(@NotNull Object packet) {
return packet instanceof ClientboundRemoveEntitiesPacket;
}
@Override
public boolean isTeleportPacket(@NotNull Object packet) {
return packet instanceof ClientboundTeleportEntityPacket;
}
@Override
public boolean isNamedEntitySpawnPacket(@NotNull Object packet) {
return packet instanceof ClientboundAddEntityPacket;
}
@Override
public boolean isMovePacket(@NotNull Object packet) {
return packet instanceof ClientboundMoveEntityPacket;
}
@Override
public boolean isLookPacket(@NotNull Object packet) {
return packet instanceof ClientboundMoveEntityPacket.Rot;
}
@Override
public int getTeleportEntityId(@NotNull Object teleportPacket) {
return ((ClientboundTeleportEntityPacket) teleportPacket).getId();
}
@Override
public int getMoveEntityId(@NotNull Object movePacket) {
return ((ClientboundMoveEntityPacket) movePacket).entityId;
}
@Override
public int getSpawnedPlayer(@NotNull Object playerSpawnPacket) {
return ((ClientboundAddEntityPacket) playerSpawnPacket).getId();
}
@Override
public int[] getDestroyedEntities(@NotNull Object destroyPacket) {
return ((ClientboundRemoveEntitiesPacket) destroyPacket).getEntityIds().toIntArray();
}
}
@@ -1,23 +1,28 @@
package me.neznamy.tab.platforms.fabric;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.platform.EventListener;
import me.neznamy.tab.shared.platform.TabPlayer;
import net.fabricmc.fabric.api.entity.event.v1.ServerEntityWorldChangeEvents;
import net.fabricmc.fabric.api.entity.event.v1.ServerPlayerEvents;
import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents;
import net.minecraft.server.level.ServerPlayer;
import org.jetbrains.annotations.NotNull;
public class FabricEventListener extends EventListener<ServerPlayer> {
public void register() {
ServerPlayConnectionEvents.JOIN.register((connection, $, $$) -> join(connection.player));
ServerPlayConnectionEvents.DISCONNECT.register((connection, $) -> quit(connection.player.getUUID()));
ServerPlayConnectionEvents.JOIN.register((connection, $, $$) -> join(connection.player));
ServerEntityWorldChangeEvents.AFTER_PLAYER_CHANGE_WORLD.register((player, origin, destination) ->
worldChange(player.getUUID(), destination.dimension().location().toString()));
//TODO command
ServerPlayerEvents.AFTER_RESPAWN.register((oldPlayer, newPlayer, alive) -> replacePlayer(oldPlayer.getUUID(), newPlayer));
}
@Override
public TabPlayer createPlayer(ServerPlayer player) {
return new FabricTabPlayer(player);
@NotNull
public TabPlayer createPlayer(@NotNull ServerPlayer player) {
return new FabricTabPlayer((FabricPlatform) TAB.getInstance().getPlatform(), player);
}
//TODO world switch
}
@@ -0,0 +1,129 @@
package me.neznamy.tab.platforms.fabric;
import com.mojang.authlib.GameProfile;
import io.netty.channel.Channel;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.features.injection.NettyPipelineInjector;
import me.neznamy.tab.shared.features.nametags.NameTag;
import me.neznamy.tab.shared.features.sorting.Sorting;
import me.neznamy.tab.shared.platform.TabPlayer;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket.Action;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket.Entry;
import net.minecraft.network.protocol.game.ClientboundSetDisplayObjectivePacket;
import net.minecraft.network.protocol.game.ClientboundSetObjectivePacket;
import net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumSet;
import java.util.List;
public class FabricPipelineInjector extends NettyPipelineInjector {
public FabricPipelineInjector() {
super("packet_handler");
}
@Override
@Nullable
protected Channel getChannel(@NotNull TabPlayer player) {
return ((FabricTabPlayer)player).getPlayer().connection.connection.channel;
}
@Override
public void onDisplayObjective(@NotNull TabPlayer player, @NotNull Object packet) {
TAB.getInstance().getFeatureManager().onDisplayObjective(player,
((ClientboundSetDisplayObjectivePacket)packet).getSlot().ordinal(),
String.valueOf(((ClientboundSetDisplayObjectivePacket)packet).getObjectiveName()));
}
@Override
public void onObjective(@NotNull TabPlayer player, @NotNull Object packet) {
TAB.getInstance().getFeatureManager().onObjective(player,
((ClientboundSetObjectivePacket)packet).getMethod(),
((ClientboundSetObjectivePacket)packet).getObjectiveName());
}
@Override
public boolean isDisplayObjective(@NotNull Object packet) {
return packet instanceof ClientboundSetDisplayObjectivePacket;
}
@Override
public boolean isObjective(@NotNull Object packet) {
return packet instanceof ClientboundSetObjectivePacket;
}
@Override
public boolean isTeam(@NotNull Object packet) {
return packet instanceof ClientboundSetPlayerTeamPacket;
}
@Override
public boolean isPlayerInfo(@NotNull Object packet) {
return packet instanceof ClientboundPlayerInfoUpdatePacket;
}
@Override
public void onPlayerInfo(@NotNull TabPlayer receiver, @NotNull Object packet0) {
ClientboundPlayerInfoUpdatePacket packet = (ClientboundPlayerInfoUpdatePacket) packet0;
EnumSet<Action> actions = packet.actions();
List<Entry> updatedList = new ArrayList<>();
for (Entry nmsData : packet.entries()) {
GameProfile profile = nmsData.profile();
Component displayName = nmsData.displayName();
int latency = nmsData.latency();
if (actions.contains(Action.UPDATE_DISPLAY_NAME)) {
IChatBaseComponent newDisplayName = TAB.getInstance().getFeatureManager().onDisplayNameChange(receiver, nmsData.profileId());
if (newDisplayName != null) displayName = ((FabricTabPlayer)receiver).getPlatform().toComponent(newDisplayName, receiver.getVersion());
}
if (actions.contains(Action.UPDATE_LATENCY)) {
latency = TAB.getInstance().getFeatureManager().onLatencyChange(receiver, nmsData.profileId(), latency);
}
if (actions.contains(Action.ADD_PLAYER)) {
TAB.getInstance().getFeatureManager().onEntryAdd(receiver, nmsData.profileId(), profile.getName());
}
updatedList.add(new Entry(nmsData.profileId(), profile, nmsData.listed(), latency, nmsData.gameMode(), displayName, nmsData.chatSession()));
}
packet.entries = updatedList;
}
@Override
public void modifyPlayers(@NotNull Object teamPacket) {
if (TAB.getInstance().getNameTagManager() == null) return;
ClientboundSetPlayerTeamPacket packet = (ClientboundSetPlayerTeamPacket) teamPacket;
int action = packet.method;
if (action == 1 || action == 2 || action == 4) return;
Collection<String> players = packet.getPlayers();
String teamName = packet.getName();
if (players == null) return;
//creating a new list to prevent NoSuchFieldException in minecraft packet encoder when a player is removed
Collection<String> newList = new ArrayList<>();
for (String entry : players) {
TabPlayer p = getPlayer(entry);
if (p == null) {
newList.add(entry);
continue;
}
Sorting sorting = TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.SORTING);
String expectedTeam = sorting.getShortTeamName(p);
if (expectedTeam == null) {
newList.add(entry);
continue;
}
if (!((NameTag)TAB.getInstance().getNameTagManager()).getDisableChecker().isDisabledPlayer(p) &&
!TAB.getInstance().getNameTagManager().hasTeamHandlingPaused(p) && !teamName.equals(expectedTeam)) {
logTeamOverride(teamName, p.getName(), expectedTeam);
} else {
newList.add(entry);
}
}
packet.players = newList;
}
}
@@ -1,16 +0,0 @@
package me.neznamy.tab.platforms.fabric;
import me.neznamy.tab.api.placeholder.PlaceholderManager;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.placeholders.UniversalPlaceholderRegistry;
import net.minecraft.server.level.ServerPlayer;
import org.jetbrains.annotations.NotNull;
public class FabricPlaceholderRegistry extends UniversalPlaceholderRegistry {
@Override
public void registerPlaceholders(@NotNull PlaceholderManager manager) {
manager.registerPlayerPlaceholder(TabConstants.Placeholder.HEALTH, 100, p -> (int) Math.ceil(((ServerPlayer) p.getPlayer()).getHealth()));
super.registerPlaceholders(manager);
}
}
@@ -1,6 +1,9 @@
package me.neznamy.tab.platforms.fabric;
import me.neznamy.tab.platforms.fabric.features.FabricNameTagX;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.backend.BackendPlatform;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.features.injection.PipelineInjector;
@@ -9,13 +12,27 @@ import me.neznamy.tab.shared.features.types.TabFeature;
import me.neznamy.tab.shared.placeholders.expansion.EmptyTabExpansion;
import me.neznamy.tab.shared.placeholders.expansion.TabExpansion;
import net.fabricmc.fabric.api.networking.v1.PlayerLookup;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.ChatFormatting;
import net.minecraft.SharedConstants;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.MutableComponent;
import net.minecraft.network.chat.Style;
import net.minecraft.network.chat.contents.LiteralContents;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerPlayer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public final class FabricPlatform implements BackendPlatform {
import java.io.File;
import java.util.ArrayList;
import java.util.List;
/**
* Platform implementation for Fabric
*/
public record FabricPlatform(MinecraftServer server) implements BackendPlatform<Component> {
@Override
public void registerUnknownPlaceholder(@NotNull String identifier) {
@@ -24,43 +41,115 @@ public final class FabricPlatform implements BackendPlatform {
@Override
public void loadPlayers() {
for (ServerPlayer player : PlayerLookup.all(FabricTAB.getInstance().getServer())) {
TAB.getInstance().addPlayer(new FabricTabPlayer(player));
for (ServerPlayer player : PlayerLookup.all(server)) {
TAB.getInstance().addPlayer(new FabricTabPlayer(this, player));
}
}
@Override
public void registerPlaceholders() {
new FabricPlaceholderRegistry().registerPlaceholders(TAB.getInstance().getPlaceholderManager());
@NotNull
public PipelineInjector createPipelineInjector() {
return new FabricPipelineInjector();
}
@Override
public @NotNull NameTag getUnlimitedNameTags() {
return new NameTag();
@NotNull
public NameTag getUnlimitedNameTags() {
return new FabricNameTagX();
}
@Override
public @Nullable PipelineInjector createPipelineInjector() {
return null;
}
@Override
public @NotNull TabExpansion createTabExpansion() {
@NotNull
public TabExpansion createTabExpansion() {
return new EmptyTabExpansion();
}
@Override
public @Nullable TabFeature getPerWorldPlayerList() {
@Nullable
public TabFeature getPerWorldPlayerList() {
return null;
}
@Override
public void sendConsoleMessage(@NotNull IChatBaseComponent message) {
public void logInfo(@NotNull IChatBaseComponent message) {
MinecraftServer.LOGGER.info("[TAB] " + message.toRawText());
}
@Override
public void logWarn(@NotNull IChatBaseComponent message) {
MinecraftServer.LOGGER.warn("[TAB] " + message.toRawText()); // Fabric console does not support colors
}
@Override
@NotNull
public String getServerVersionInfo() {
return "[Fabric] " + SharedConstants.getCurrentVersion().getName();
}
@Override
public void registerListener() {
new FabricEventListener().register();
}
@Override
public void registerCommand() {
// Event listener must be registered in main class
}
@Override
public void startMetrics() {
// Not available
}
@Override
@NotNull
public ProtocolVersion getServerVersion() {
return ProtocolVersion.fromFriendlyName(SharedConstants.getCurrentVersion().getName());
}
@Override
@NotNull
public File getDataFolder() {
return FabricLoader.getInstance().getConfigDir().resolve(TabConstants.PLUGIN_ID).toFile();
}
@Override
public Component toComponent(@NotNull IChatBaseComponent component, @NotNull ProtocolVersion version) {
// Text
MutableComponent comp = MutableComponent.create(new LiteralContents(component.getText()));
// Color and style
List<ChatFormatting> formats = new ArrayList<>();
if (component.getModifier().isUnderlined()) formats.add(ChatFormatting.UNDERLINE);
if (component.getModifier().isObfuscated()) formats.add(ChatFormatting.OBFUSCATED);
if (component.getModifier().isStrikethrough()) formats.add(ChatFormatting.STRIKETHROUGH);
if (component.getModifier().isItalic()) formats.add(ChatFormatting.ITALIC);
if (component.getModifier().isBold()) formats.add(ChatFormatting.BOLD);
Style style = comp.getStyle().applyFormats(formats.toArray(new ChatFormatting[0]));
if (component.getModifier().getColor() != null) {
if (version.getMinorVersion() >= 16) {
style = style.withColor(component.getModifier().getColor().getRgb());
} else {
style = style.withColor(ChatFormatting.valueOf(component.getModifier().getColor().getLegacyColor().name()));
}
}
if (component.getModifier().getFont() != null)
style = style.withFont(new ResourceLocation(component.getModifier().getFont()));
comp.withStyle(style);
// Extra
comp.getSiblings().addAll(component.getExtra().stream().map(c -> toComponent(c, version)).toList());
return comp;
}
@Override
public double getTPS() {
return -1; // Not available
}
@Override
public double getMSPT() {
return server.getAverageTickTime();
}
}
@@ -1,7 +1,6 @@
package me.neznamy.tab.platforms.fabric;
import java.util.Collection;
import java.util.Objects;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
@@ -21,8 +20,8 @@ import org.jetbrains.annotations.NotNull;
public class FabricScoreboard extends Scoreboard<FabricTabPlayer> {
private final @NotNull net.minecraft.world.scores.Scoreboard dummyScoreboard = new net.minecraft.world.scores.Scoreboard();
private final @NotNull Component EMPTY_COMPONENT = Objects.requireNonNull(Component.Serializer.fromJson(IChatBaseComponent.EMPTY_COMPONENT));
@NotNull
private static final net.minecraft.world.scores.Scoreboard dummyScoreboard = new net.minecraft.world.scores.Scoreboard();
public FabricScoreboard(FabricTabPlayer player) {
super(player);
@@ -32,12 +31,12 @@ public class FabricScoreboard extends Scoreboard<FabricTabPlayer> {
public void setDisplaySlot(@NotNull DisplaySlot slot, @NotNull String objective) {
player.sendPacket(
new ClientboundSetDisplayObjectivePacket(
slot.ordinal(),
net.minecraft.world.scores.DisplaySlot.values()[slot.ordinal()],
new Objective(
dummyScoreboard,
objective,
ObjectiveCriteria.DUMMY,
EMPTY_COMPONENT,
Component.empty(),
ObjectiveCriteria.RenderType.INTEGER
)
)
@@ -45,15 +44,15 @@ public class FabricScoreboard extends Scoreboard<FabricTabPlayer> {
}
@Override
public void registerObjective0(@NotNull String objectiveName, @NotNull String title, boolean hearts) {
public void registerObjective0(@NotNull String objectiveName, @NotNull String title, @NotNull HealthDisplay display) {
player.sendPacket(
new ClientboundSetObjectivePacket(
new Objective(
dummyScoreboard,
objectiveName,
ObjectiveCriteria.DUMMY,
FabricTAB.getInstance().toComponent(IChatBaseComponent.optimizedComponent(title), player.getVersion()),
hearts ? ObjectiveCriteria.RenderType.HEARTS : ObjectiveCriteria.RenderType.INTEGER
dummyScoreboard,
objectiveName,
ObjectiveCriteria.DUMMY,
player.getPlatform().toComponent(IChatBaseComponent.optimizedComponent(title), player.getVersion()),
ObjectiveCriteria.RenderType.valueOf(display.name())
),
0
)
@@ -68,7 +67,7 @@ public class FabricScoreboard extends Scoreboard<FabricTabPlayer> {
dummyScoreboard,
objectiveName,
ObjectiveCriteria.DUMMY,
EMPTY_COMPONENT,
Component.empty(),
ObjectiveCriteria.RenderType.INTEGER
),
1
@@ -77,15 +76,15 @@ public class FabricScoreboard extends Scoreboard<FabricTabPlayer> {
}
@Override
public void updateObjective0(@NotNull String objectiveName, @NotNull String title, boolean hearts) {
public void updateObjective0(@NotNull String objectiveName, @NotNull String title, @NotNull HealthDisplay display) {
player.sendPacket(
new ClientboundSetObjectivePacket(
new Objective(
dummyScoreboard,
objectiveName,
ObjectiveCriteria.DUMMY,
FabricTAB.getInstance().toComponent(IChatBaseComponent.optimizedComponent(title), player.getVersion()),
hearts ? ObjectiveCriteria.RenderType.HEARTS : ObjectiveCriteria.RenderType.INTEGER
dummyScoreboard,
objectiveName,
ObjectiveCriteria.DUMMY,
player.getPlatform().toComponent(IChatBaseComponent.optimizedComponent(title), player.getVersion()),
ObjectiveCriteria.RenderType.valueOf(display.name())
),
2
)
@@ -93,15 +92,17 @@ public class FabricScoreboard extends Scoreboard<FabricTabPlayer> {
}
@Override
public void registerTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix, @NotNull NameVisibility visibility, @NotNull CollisionRule collision, @NotNull Collection<String> players, int options) {
public void registerTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix,
@NotNull NameVisibility visibility, @NotNull CollisionRule collision,
@NotNull Collection<String> players, int options) {
PlayerTeam team = new PlayerTeam(dummyScoreboard, name);
team.setAllowFriendlyFire((options & 0x01) > 0);
team.setSeeFriendlyInvisibles((options & 0x02) > 0);
team.setColor(ChatFormatting.valueOf(EnumChatFormat.lastColorsOf(prefix).name()));
team.setCollisionRule(Team.CollisionRule.valueOf(collision.name()));
team.setNameTagVisibility(Team.Visibility.valueOf(visibility.name()));
team.setPlayerPrefix(FabricTAB.getInstance().toComponent(IChatBaseComponent.optimizedComponent(prefix), player.getVersion()));
team.setPlayerSuffix(FabricTAB.getInstance().toComponent(IChatBaseComponent.optimizedComponent(suffix), player.getVersion()));
team.setPlayerPrefix(player.getPlatform().toComponent(IChatBaseComponent.optimizedComponent(prefix), player.getVersion()));
team.setPlayerSuffix(player.getPlatform().toComponent(IChatBaseComponent.optimizedComponent(suffix), player.getVersion()));
team.getPlayers().addAll(players);
player.sendPacket(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, true));
}
@@ -112,15 +113,16 @@ public class FabricScoreboard extends Scoreboard<FabricTabPlayer> {
}
@Override
public void updateTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix, @NotNull NameVisibility visibility, @NotNull CollisionRule collision, int options) {
public void updateTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix,
@NotNull NameVisibility visibility, @NotNull CollisionRule collision, int options) {
PlayerTeam team = new PlayerTeam(dummyScoreboard, name);
team.setAllowFriendlyFire((options & 0x01) > 0);
team.setSeeFriendlyInvisibles((options & 0x02) > 0);
team.setAllowFriendlyFire((options & 0x01) != 0);
team.setSeeFriendlyInvisibles((options & 0x02) != 0);
team.setColor(ChatFormatting.valueOf(EnumChatFormat.lastColorsOf(prefix).name()));
team.setCollisionRule(Team.CollisionRule.valueOf(collision.name()));
team.setNameTagVisibility(Team.Visibility.valueOf(visibility.name()));
team.setPlayerPrefix(FabricTAB.getInstance().toComponent(IChatBaseComponent.optimizedComponent(prefix), player.getVersion()));
team.setPlayerSuffix(FabricTAB.getInstance().toComponent(IChatBaseComponent.optimizedComponent(suffix), player.getVersion()));
team.setPlayerPrefix(player.getPlatform().toComponent(IChatBaseComponent.optimizedComponent(prefix), player.getVersion()));
team.setPlayerSuffix(player.getPlatform().toComponent(IChatBaseComponent.optimizedComponent(suffix), player.getVersion()));
player.sendPacket(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, false));
}
@@ -1,53 +1,26 @@
package me.neznamy.tab.platforms.fabric;
import lombok.Getter;
import me.lucko.fabric.api.permissions.v0.Permissions;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.util.ComponentCache;
import net.fabricmc.api.DedicatedServerModInitializer;
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.SharedConstants;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.network.chat.Component;
import net.minecraft.server.MinecraftServer;
import org.jetbrains.annotations.NotNull;
import java.io.File;
public class FabricTAB implements DedicatedServerModInitializer {
private final ComponentCache<IChatBaseComponent, Component> componentCache = new ComponentCache<>(1000,
(text, version) -> Component.Serializer.fromJson(text.toString(version)));
@Getter private static FabricTAB instance;
private final boolean fabricPermissionsApi = FabricLoader.getInstance().isModLoaded("fabric-permissions-api-v0");
@Getter private MinecraftServer server;
private static final boolean fabricPermissionsApi = FabricLoader.getInstance().isModLoaded("fabric-permissions-api-v0");
@Override
public void onInitializeServer() {
instance = this;
ProtocolVersion protocolVersion = ProtocolVersion.fromNetworkId(SharedConstants.getCurrentVersion().getProtocolVersion());
File folder = FabricLoader.getInstance().getConfigDir().resolve(TabConstants.PLUGIN_ID).toFile();
TAB.setInstance(new TAB(new FabricPlatform(), protocolVersion, folder));
new FabricEventListener().register();
ServerLifecycleEvents.SERVER_STARTING.register(server -> {
this.server = server;
TAB.getInstance().load();
});
ServerLifecycleEvents.SERVER_STOPPING.register(server -> TAB.getInstance().unload());
CommandRegistrationCallback.EVENT.register((dispatcher, $, $$) -> new FabricTabCommand().onRegisterCommands(dispatcher));
ServerLifecycleEvents.SERVER_STARTING.register(server -> TAB.create(new FabricPlatform(server)));
ServerLifecycleEvents.SERVER_STOPPING.register($ -> TAB.getInstance().unload());
}
public Component toComponent(@NotNull IChatBaseComponent component, @NotNull ProtocolVersion clientVersion) {
return componentCache.get(component, clientVersion);
}
public boolean hasPermission(@NotNull CommandSourceStack source, @NotNull String permission) {
public static boolean hasPermission(@NotNull CommandSourceStack source, @NotNull String permission) {
if (source.hasPermission(4)) return true;
return fabricPermissionsApi && Permissions.check(source, permission);
}
@@ -13,7 +13,6 @@ import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.TabPlayer;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands;
import net.minecraft.network.chat.Component;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
@@ -33,6 +32,7 @@ public class FabricTabCommand {
dispatcher.getRoot().addChild(command);
}
@NotNull
private String[] getArguments(@NotNull CommandContext<CommandSourceStack> context) {
String input = context.getInput();
int firstSpace = input.indexOf(' ');
@@ -48,10 +48,10 @@ public class FabricTabCommand {
private int executeCommand(@NotNull CommandSourceStack source, @NotNull String[] args) {
if (TAB.getInstance().isPluginDisabled()) {
boolean hasReloadPermission = FabricTAB.getInstance().hasPermission(source, TabConstants.Permission.COMMAND_RELOAD);
boolean hasAdminPermission = FabricTAB.getInstance().hasPermission(source, TabConstants.Permission.COMMAND_ALL);
boolean hasReloadPermission = FabricTAB.hasPermission(source, TabConstants.Permission.COMMAND_RELOAD);
boolean hasAdminPermission = FabricTAB.hasPermission(source, TabConstants.Permission.COMMAND_ALL);
for (String message : TAB.getInstance().getDisabledCommand().execute(args, hasReloadPermission, hasAdminPermission)) {
source.sendSystemMessage(Component.Serializer.fromJson(IChatBaseComponent.optimizedComponent(message).toString()));
source.sendSystemMessage(((FabricPlatform)TAB.getInstance().getPlatform()).toComponent(IChatBaseComponent.optimizedComponent(message), TAB.getInstance().getServerVersion()));
}
} else {
if (source.getEntity() == null) {
@@ -64,20 +64,23 @@ public class FabricTabCommand {
return 0;
}
private @NotNull CompletableFuture<Suggestions> getSuggestions(@NotNull CommandSourceStack source, @NotNull String[] args, @NotNull SuggestionsBuilder builder) {
@NotNull
private CompletableFuture<Suggestions> getSuggestions(@NotNull CommandSourceStack source, @NotNull String[] args,
@NotNull SuggestionsBuilder builder) {
TabPlayer player = null;
if (source.getEntity() != null) {
player = TAB.getInstance().getPlayer(source.getEntity().getUUID());
if (player == null) return Suggestions.empty();
}
int lastSpace = builder.getRemaining().lastIndexOf(' ');
SuggestionsBuilder newBuilder = builder;
int lastSpace = newBuilder.getRemaining().lastIndexOf(' ');
if (lastSpace != -1) {
builder = builder.createOffset(lastSpace + 1 + builder.getStart());
newBuilder = newBuilder.createOffset(lastSpace + 1 + newBuilder.getStart());
}
for (String suggestion : TAB.getInstance().getCommand().complete(player, args)) {
builder.suggest(suggestion);
newBuilder.suggest(suggestion);
}
return builder.buildFuture();
return newBuilder.buildFuture();
}
}
@@ -17,7 +17,7 @@ import org.jetbrains.annotations.Nullable;
import java.util.*;
public record FabricTabList(FabricTabPlayer player) implements TabList {
public record FabricTabList(@NotNull FabricTabPlayer player) implements TabList {
@Override
public void removeEntry(@NotNull UUID entry) {
@@ -27,7 +27,7 @@ public record FabricTabList(FabricTabPlayer player) implements TabList {
@Override
public void updateDisplayName(@NotNull UUID entry, @Nullable IChatBaseComponent displayName) {
player.sendPacket(build(EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME),
new Builder(entry).setDisplayName(displayName == null ? null : FabricTAB.getInstance().toComponent(displayName, player.getVersion()))));
new Builder(entry).setDisplayName(displayName == null ? null : player.getPlatform().toComponent(displayName, player.getVersion()))));
}
@Override
@@ -47,18 +47,19 @@ public record FabricTabList(FabricTabPlayer player) implements TabList {
.setSkin(entry.getSkin())
.setGameMode(entry.getGameMode())
.setLatency(entry.getLatency())
.setDisplayName(entry.getDisplayName() == null ? null : FabricTAB.getInstance().toComponent(entry.getDisplayName(), player.getVersion()))));
.setDisplayName(entry.getDisplayName() == null ? null : player.getPlatform().toComponent(entry.getDisplayName(), player.getVersion()))));
}
@Override
public void setPlayerListHeaderFooter(@NotNull IChatBaseComponent header, @NotNull IChatBaseComponent footer) {
player.getPlayer().connection.send(new ClientboundTabListPacket(
FabricTAB.getInstance().toComponent(header, player.getVersion()),
FabricTAB.getInstance().toComponent(footer, player.getVersion()))
player.getPlatform().toComponent(header, player.getVersion()),
player.getPlatform().toComponent(footer, player.getVersion()))
);
}
private Packet<?> build(EnumSet<ClientboundPlayerInfoUpdatePacket.Action> actions, FabricTabList.Builder entry) {
@NotNull
private Packet<?> build(@NotNull EnumSet<ClientboundPlayerInfoUpdatePacket.Action> actions, @NotNull FabricTabList.Builder entry) {
ClientboundPlayerInfoUpdatePacket packet = new ClientboundPlayerInfoUpdatePacket(actions, Collections.emptyList());
packet.entries = Collections.singletonList(new ClientboundPlayerInfoUpdatePacket.Entry(
entry.getId(),
@@ -83,32 +84,39 @@ public record FabricTabList(FabricTabPlayer player) implements TabList {
private int gameMode;
@Nullable private Component displayName;
public Builder setName(String name) {
@NotNull
public Builder setName(@Nullable String name) {
this.name = name;
return this;
}
public Builder setSkin(Skin skin) {
@NotNull
public Builder setSkin(@Nullable Skin skin) {
this.skin = skin;
return this;
}
@NotNull
public Builder setLatency(int latency) {
this.latency = latency;
return this;
}
@NotNull
public Builder setGameMode(int gameMode) {
this.gameMode = gameMode;
return this;
}
public Builder setDisplayName(Component displayName) {
@NotNull
public Builder setDisplayName(@Nullable Component displayName) {
this.displayName = displayName;
return this;
}
@Nullable
public GameProfile createProfile() {
if (name == null) return null;
GameProfile profile = new GameProfile(id, name);
if (skin != null) {
profile.getProperties().put(TabList.TEXTURES_PROPERTY,
@@ -1,46 +1,52 @@
package me.neznamy.tab.platforms.fabric;
import com.mojang.authlib.properties.Property;
import java.util.Collection;
import lombok.Getter;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.backend.BackendTabPlayer;
import me.neznamy.tab.shared.backend.entityview.EntityView;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.hook.ViaVersionHook;
import me.neznamy.tab.shared.platform.bossbar.BossBar;
import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.bossbar.BossBar;
import net.minecraft.network.protocol.Packet;
import net.minecraft.server.level.ServerPlayer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@Getter
public class FabricTabPlayer extends TabPlayer {
import java.util.Collection;
@Getter
public class FabricTabPlayer extends BackendTabPlayer {
@NotNull
private final Scoreboard<FabricTabPlayer> scoreboard = new FabricScoreboard(this);
@NotNull
private final TabList tabList = new FabricTabList(this);
@NotNull
private final BossBar bossBar = new FabricBossBar(this);
public FabricTabPlayer(ServerPlayer player) {
super(player, player.getUUID(), player.getGameProfile().getName(), TAB.getInstance().getConfiguration().getServerName(),
"N/A", ViaVersionHook.getInstance().getPlayerVersion(player.getUUID(), player.getGameProfile().getName()), true);
@NotNull
private final EntityView entityView = new FabricEntityView(this);
public FabricTabPlayer(@NotNull FabricPlatform platform, @NotNull ServerPlayer player) {
super(platform, player, player.getUUID(), player.getGameProfile().getName(), player.level().dimension().location().toString());
}
@Override
public boolean hasPermission(@NotNull String permission) {
return FabricTAB.getInstance().hasPermission(getPlayer().createCommandSourceStack(), permission);
return FabricTAB.hasPermission(getPlayer().createCommandSourceStack(), permission);
}
@Override
public int getPing() {
return getPlayer().latency;
return getPlayer().connection.latency();
}
@Override
public void sendMessage(@NotNull IChatBaseComponent message) {
getPlayer().sendSystemMessage(FabricTAB.getInstance().toComponent(message, getVersion()));
getPlayer().sendSystemMessage(getPlatform().toComponent(message, getVersion()));
}
@Override
@@ -54,27 +60,30 @@ public class FabricTabPlayer extends TabPlayer {
}
@Override
public @Nullable TabList.Skin getSkin() {
@Nullable
public TabList.Skin getSkin() {
Collection<Property> properties = getPlayer().getGameProfile().getProperties().get(TabList.TEXTURES_PROPERTY);
if (properties.isEmpty()) return null;
if (properties.isEmpty()) return null; // Offline mode
Property skinProperty = properties.iterator().next();
return new TabList.Skin(skinProperty.getValue(), skinProperty.getSignature());
return new TabList.Skin(skinProperty.value(), skinProperty.signature());
}
@Override
public @NotNull ServerPlayer getPlayer() {
@NotNull
public ServerPlayer getPlayer() {
return (ServerPlayer) player;
}
public void sendPacket(Packet<?> packet) {
getPlayer().connection.send(packet);
}
@Override
public boolean isOnline() {
return true;
}
@Override
public FabricPlatform getPlatform() {
return (FabricPlatform) platform;
}
@Override
public boolean isVanished() {
return false;
@@ -84,4 +93,25 @@ public class FabricTabPlayer extends TabPlayer {
public int getGamemode() {
return getPlayer().gameMode.getGameModeForPlayer().getId();
}
@Override
public double getHealth() {
return getPlayer().getHealth();
}
@Override
@NotNull
public String getDisplayName() {
return getPlayer().getDisplayName().getString(); // Will make it work properly if someone asks
}
/**
* Sends packet to the player
*
* @param packet
* Packet to send
*/
public void sendPacket(@NotNull Packet<?> packet) {
getPlayer().connection.send(packet);
}
}
@@ -0,0 +1,140 @@
package me.neznamy.tab.platforms.fabric.features;
import me.neznamy.tab.platforms.fabric.FabricTabPlayer;
import me.neznamy.tab.shared.backend.EntityData;
import me.neznamy.tab.shared.backend.features.unlimitedtags.BackendNameTagX;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.TabPlayer;
import net.fabricmc.fabric.api.entity.event.v1.ServerPlayerEvents;
import net.minecraft.network.syncher.EntityDataSerializers;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.phys.Vec3;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
public class FabricNameTagX extends BackendNameTagX {
private boolean enabled = true;
public FabricNameTagX() {
ServerPlayerEvents.AFTER_RESPAWN.register((oldPlayer, newPlayer, alive) -> {
if (enabled) respawn(oldPlayer.getUUID());
});
// no sneaking for fabric //TODO
}
@Override
public double getDistance(@NotNull TabPlayer player1, @NotNull TabPlayer player2) {
Vec3 loc1 = ((FabricTabPlayer)player1).getPlayer().position();
Vec3 loc2 = ((FabricTabPlayer)player2).getPlayer().position();
return Math.sqrt(Math.pow(loc1.x-loc2.x, 2) + Math.pow(loc1.z-loc2.z, 2));
}
@Override
public boolean areInSameWorld(@NotNull TabPlayer player1, @NotNull TabPlayer player2) {
return ((FabricTabPlayer)player1).getPlayer().level() == ((FabricTabPlayer)player2).getPlayer().level();
}
@Override
public boolean canSee(@NotNull TabPlayer viewer, @NotNull TabPlayer target) {
return true;
}
@Override
public void unregisterListener() {
enabled = false;
}
@Override
@NotNull
public List<Integer> getPassengers(@NotNull Object vehicle) {
return ((Entity)vehicle).getPassengers().stream().map(Entity::getId).collect(Collectors.toList());
}
@Override
@Nullable
public Object getVehicle(@NotNull TabPlayer player) {
return ((FabricTabPlayer)player).getPlayer().getVehicle();
}
@Override
public int getEntityId(@NotNull Object entity) {
return ((Entity)entity).getId();
}
@Override
@NotNull
public String getEntityType(@NotNull Object entity) {
return ((Entity)entity).getType().toString(); // TODO test/fix
}
@Override
public boolean isSneaking(@NotNull TabPlayer player) {
return ((FabricTabPlayer)player).getPlayer().isCrouching();
}
@Override
public boolean isSwimming(@NotNull TabPlayer player) {
return ((FabricTabPlayer)player).getPlayer().isSwimming();
}
@Override
public boolean isGliding(@NotNull TabPlayer player) {
return false; // TODO
}
@Override
public boolean isSleeping(@NotNull TabPlayer player) {
return ((FabricTabPlayer)player).getPlayer().isSleeping();
}
@Override
@NotNull
public Object getArmorStandType() {
return EntityType.ARMOR_STAND;
}
@Override
public double getX(@NotNull TabPlayer player) {
return ((FabricTabPlayer)player).getPlayer().position().x;
}
@Override
public double getY(@NotNull Object entity) {
return ((Entity)entity).position().y;
}
@Override
public double getZ(@NotNull TabPlayer player) {
return ((FabricTabPlayer)player).getPlayer().position().z;
}
@Override
@NotNull
public EntityData createDataWatcher(@NotNull TabPlayer viewer, byte flags, @NotNull String displayName, boolean nameVisible) {
return () -> Arrays.asList(
new SynchedEntityData.DataValue<>(0, EntityDataSerializers.BYTE, flags),
new SynchedEntityData.DataValue<>(2, EntityDataSerializers.OPTIONAL_COMPONENT,
Optional.of(((FabricTabPlayer)viewer).getPlatform().toComponent(IChatBaseComponent.optimizedComponent(displayName), viewer.getVersion()))),
new SynchedEntityData.DataValue<>(3, EntityDataSerializers.BOOLEAN, nameVisible),
new SynchedEntityData.DataValue<>(15, EntityDataSerializers.BYTE, (byte)16)
);
}
@Override
public void runInEntityScheduler(@NotNull Object entity, @NotNull Runnable task) {
task.run();
}
@Override
public boolean isDead(@NotNull TabPlayer player) {
return !((FabricTabPlayer)player).getPlayer().isAlive();
}
}
+2 -1
View File
@@ -11,7 +11,8 @@
},
"depends": {
"fabricloader": ">=0.9.0",
"fabric-api-base": "*"
"fabric-api-base": "*",
"minecraft": "1.20.2"
},
"recommends": {
"fabric-permissions-api-v0": "*"
@@ -1,5 +1,13 @@
accessWidener v1 named
accessible field net/minecraft/network/protocol/game/ClientboundMoveEntityPacket entityId I
accessible field net/minecraft/server/network/ServerGamePacketListenerImpl connection Lnet/minecraft/network/Connection;
accessible field net/minecraft/network/Connection channel Lio/netty/channel/Channel;
accessible field net/minecraft/network/protocol/game/ClientboundSetPlayerTeamPacket method I
accessible field net/minecraft/network/protocol/game/ClientboundSetPlayerTeamPacket players Ljava/util/Collection;
mutable field net/minecraft/network/protocol/game/ClientboundSetPlayerTeamPacket players Ljava/util/Collection;
accessible field net/minecraft/server/network/ServerCommonPacketListenerImpl connection Lnet/minecraft/network/Connection;
# new
accessible field net/minecraft/server/MinecraftServer LOGGER Lorg/slf4j/Logger;
mutable field net/minecraft/server/MinecraftServer LOGGER Lorg/slf4j/Logger;
-2
View File
@@ -7,11 +7,9 @@ plugins {
val platforms = setOf(
rootProject.projects.bukkit,
rootProject.projects.bungeecord,
rootProject.projects.krypton,
rootProject.projects.velocity,
rootProject.projects.sponge7,
rootProject.projects.sponge8,
// rootProject.projects.fabric
).map { it.dependencyProject }
tasks {
-10
View File
@@ -1,10 +0,0 @@
dependencies {
implementation(projects.shared)
compileOnly("me.lucko:spark-api:0.1-SNAPSHOT")
compileOnly("org.kryptonmc:krypton-api:f93a12382f")
annotationProcessor("org.kryptonmc:krypton-annotation-processor:f93a12382f")
}
tasks.compileJava {
options.release.set(17)
}
@@ -1,36 +0,0 @@
package me.neznamy.tab.platforms.krypton;
import me.neznamy.tab.shared.platform.EventListener;
import me.neznamy.tab.shared.platform.TabPlayer;
import org.kryptonmc.api.entity.player.Player;
import org.kryptonmc.api.event.Listener;
import org.kryptonmc.api.event.command.CommandExecuteEvent;
import org.kryptonmc.api.event.player.PlayerJoinEvent;
import org.kryptonmc.api.event.player.PlayerQuitEvent;
public class KryptonEventListener extends EventListener<Player> {
@Listener
public void onJoin(PlayerJoinEvent event) {
join(event.getPlayer());
}
@Listener
public void onQuit(PlayerQuitEvent event) {
quit(event.getPlayer().getUuid());
}
//TODO world switch
@Listener
public void onCommand(CommandExecuteEvent event) {
if (event.getSender() instanceof Player && command(((Player) event.getSender()).getUuid(), event.getCommand())) {
event.deny();
}
}
@Override
public TabPlayer createPlayer(Player player) {
return new KryptonTabPlayer(player);
}
}
@@ -1,84 +0,0 @@
package me.neznamy.tab.platforms.krypton;
import lombok.RequiredArgsConstructor;
import me.lucko.spark.api.Spark;
import me.lucko.spark.api.statistic.StatisticWindow.*;
import me.lucko.spark.api.statistic.misc.DoubleAverageInfo;
import me.lucko.spark.api.statistic.types.DoubleStatistic;
import me.lucko.spark.api.statistic.types.GenericStatistic;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.api.placeholder.PlaceholderManager;
import me.neznamy.tab.shared.placeholders.UniversalPlaceholderRegistry;
import org.jetbrains.annotations.NotNull;
import org.kryptonmc.api.entity.player.Player;
@RequiredArgsConstructor
public class KryptonPlaceholderRegistry extends UniversalPlaceholderRegistry {
private final @NotNull KryptonTAB plugin;
@Override
public void registerPlaceholders(@NotNull PlaceholderManager manager) {
// Built-in stuff
manager.registerPlayerPlaceholder(TabConstants.Placeholder.DISPLAY_NAME, 500, p -> ((Player) p.getPlayer()).getDisplayName());
manager.registerPlayerPlaceholder(TabConstants.Placeholder.HEALTH, 100, p -> Math.ceil(((Player) p.getPlayer()).getHealth()));
// Spark placeholders, registered if Spark is present
if (plugin.getServer().getPluginManager().isLoaded("spark")) SparkPlaceholders.register(this, manager);
super.registerPlaceholders(manager);
}
/*
* This class is a massive hack. The issue is that the lambdas in here create InvokeDynamic instructions, which have
* references to the Spark classes that will be resolved on class load, causing class not found errors when the registry
* is loaded.
*
* This class solves this, as the InvokeDynamic instructions are now part of this class, rather than the registry class,
* which means we can have this class only load if Spark is present, solving the issue.
*/
private static final class SparkPlaceholders {
static void register(KryptonPlaceholderRegistry registry, PlaceholderManager manager) {
Spark spark = registry.plugin.getServer().getServicesManager().provide(Spark.class);
if (spark == null) return;
// TPS
DoubleStatistic<TicksPerSecond> tps = spark.tps();
if (tps != null) {
manager.registerServerPlaceholder("%tps_5s%", 1000, () -> registry.format(tps.poll(TicksPerSecond.SECONDS_5)));
manager.registerServerPlaceholder("%tps_10s%", 1000, () -> registry.format(tps.poll(TicksPerSecond.SECONDS_10)));
manager.registerServerPlaceholder("%tps_1m%", 1000, () -> registry.format(tps.poll(TicksPerSecond.MINUTES_1)));
manager.registerServerPlaceholder("%tps_5m%", 1000, () -> registry.format(tps.poll(TicksPerSecond.MINUTES_5)));
manager.registerServerPlaceholder("%tps_15m%", 1000, () -> registry.format(tps.poll(TicksPerSecond.MINUTES_15)));
}
// MSPT
GenericStatistic<DoubleAverageInfo, MillisPerTick> mspt = spark.mspt();
if (mspt != null) {
manager.registerServerPlaceholder("%mspt_min_10s%", 1000, () -> registry.format(mspt.poll(MillisPerTick.SECONDS_10).min()));
manager.registerServerPlaceholder("%mspt_max_10s%", 1000, () -> registry.format(mspt.poll(MillisPerTick.SECONDS_10).max()));
manager.registerServerPlaceholder("%mspt_mean_10s%", 1000, () -> registry.format(mspt.poll(MillisPerTick.SECONDS_10).mean()));
manager.registerServerPlaceholder("%mspt_median_10s%", 1000, () -> registry.format(mspt.poll(MillisPerTick.SECONDS_10).median()));
manager.registerServerPlaceholder("%mspt_percentile95_10s%", 1000, () -> registry.format(mspt.poll(MillisPerTick.SECONDS_10).percentile95th()));
manager.registerServerPlaceholder("%mspt_min_1m%", 1000, () -> registry.format(mspt.poll(MillisPerTick.MINUTES_1).min()));
manager.registerServerPlaceholder("%mspt_max_1m%", 1000, () -> registry.format(mspt.poll(MillisPerTick.MINUTES_1).max()));
manager.registerServerPlaceholder("%mspt_mean_1m%", 1000, () -> registry.format(mspt.poll(MillisPerTick.MINUTES_1).mean()));
manager.registerServerPlaceholder("%mspt_median_1m%", 1000, () -> registry.format(mspt.poll(MillisPerTick.MINUTES_1).median()));
manager.registerServerPlaceholder("%mspt_percentile95_1m%", 1000, () -> registry.format(mspt.poll(MillisPerTick.SECONDS_10).percentile95th()));
}
// CPU process
DoubleStatistic<CpuUsage> process = spark.cpuProcess();
manager.registerServerPlaceholder("%cpu_process_10s%", 1000, () -> registry.format(process.poll(CpuUsage.SECONDS_10)));
manager.registerServerPlaceholder("%cpu_process_1m%", 1000, () -> registry.format(process.poll(CpuUsage.MINUTES_1)));
manager.registerServerPlaceholder("%cpu_process_15m%", 1000, () -> registry.format(process.poll(CpuUsage.MINUTES_15)));
// CPU system
DoubleStatistic<CpuUsage> system = spark.cpuSystem();
manager.registerServerPlaceholder("%cpu_system_10s%", 1000, () -> registry.format(system.poll(CpuUsage.SECONDS_10)));
manager.registerServerPlaceholder("%cpu_system_1m%", 1000, () -> registry.format(system.poll(CpuUsage.MINUTES_1)));
manager.registerServerPlaceholder("%cpu_system_15m%", 1000, () -> registry.format(system.poll(CpuUsage.MINUTES_15)));
}
}
}
@@ -1,73 +0,0 @@
package me.neznamy.tab.platforms.krypton;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.features.types.TabFeature;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.backend.BackendPlatform;
import me.neznamy.tab.shared.features.injection.PipelineInjector;
import me.neznamy.tab.shared.placeholders.expansion.TabExpansion;
import me.neznamy.tab.shared.features.nametags.NameTag;
import me.neznamy.tab.shared.placeholders.expansion.EmptyTabExpansion;
import net.kyori.adventure.text.Component;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.kryptonmc.api.Server;
import org.kryptonmc.api.entity.player.Player;
public class KryptonPlatform implements BackendPlatform {
@NotNull private final KryptonTAB plugin;
@NotNull private final Server server;
public KryptonPlatform(@NotNull KryptonTAB plugin) {
this.plugin = plugin;
server = plugin.getServer();
}
@Override
public void sendConsoleMessage(@NotNull IChatBaseComponent message) {
server.getConsole().sendMessage(Component.text("[TAB] ").append(message.toAdventureComponent(TAB.getInstance().getServerVersion())));
}
@Override
public String getServerVersionInfo() {
return "[Krypton] " + server.getPlatform().version();
}
@Override
public void registerUnknownPlaceholder(@NotNull String identifier) {
TAB.getInstance().getPlaceholderManager().registerServerPlaceholder(identifier, -1, () -> "");
}
@Override
public void loadPlayers() {
for (Player player : server.getPlayers()) {
TAB.getInstance().addPlayer(new KryptonTabPlayer(player));
}
}
@Override
public void registerPlaceholders() {
new KryptonPlaceholderRegistry(plugin).registerPlaceholders(TAB.getInstance().getPlaceholderManager());
}
@Override
public @Nullable PipelineInjector createPipelineInjector() {
return null;
}
@Override
public @NotNull NameTag getUnlimitedNameTags() {
return new NameTag();
}
@Override
public @NotNull TabExpansion createTabExpansion() {
return new EmptyTabExpansion();
}
@Override
public @Nullable TabFeature getPerWorldPlayerList() {
return null;
}
}
@@ -1,108 +0,0 @@
package me.neznamy.tab.platforms.krypton;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.Scoreboard;
import org.jetbrains.annotations.NotNull;
import org.kryptonmc.api.scoreboard.Objective;
import org.kryptonmc.api.scoreboard.ObjectiveRenderType;
import org.kryptonmc.api.scoreboard.Team;
import org.kryptonmc.api.scoreboard.Visibility;
import org.kryptonmc.api.scoreboard.criteria.Criteria;
import java.util.Collection;
public class KryptonScoreboard extends Scoreboard<KryptonTabPlayer> {
public KryptonScoreboard(@NotNull KryptonTabPlayer player) {
super(player);
// Create a new, blank scoreboard for each player to avoid conflicts
player.getPlayer().showScoreboard(org.kryptonmc.api.scoreboard.Scoreboard.create());
}
@Override
public void setDisplaySlot(@NotNull DisplaySlot slot, @NotNull String objectiveName) {
Objective objective = getScoreboard().getObjective(objectiveName);
if (objective != null) getScoreboard().updateSlot(objective, convertDisplaySlot(slot));
}
private org.kryptonmc.api.scoreboard.DisplaySlot convertDisplaySlot(DisplaySlot slot) {
return org.kryptonmc.api.scoreboard.DisplaySlot.values()[slot.ordinal()];
}
@Override
public void registerObjective0(@NotNull String objectiveName, @NotNull String title, boolean hearts) {
Objective ignored = getScoreboard().createObjectiveBuilder()
.name(objectiveName)
.criterion(Criteria.DUMMY.get())
.displayName(IChatBaseComponent.optimizedComponent(title).toAdventureComponent(player.getVersion()))
.renderType(hearts ? ObjectiveRenderType.HEARTS : ObjectiveRenderType.INTEGER)
.buildAndRegister();
}
@Override
public void unregisterObjective0(@NotNull String objectiveName) {
Objective objective = getScoreboard().getObjective(objectiveName);
if (objective != null) getScoreboard().removeObjective(objective);
}
@Override
public void updateObjective0(@NotNull String objectiveName, @NotNull String title, boolean hearts) {
Objective objective = getScoreboard().getObjective(objectiveName);
if (objective == null) return;
objective.setDisplayName(IChatBaseComponent.optimizedComponent(title).toAdventureComponent(player.getVersion()));
objective.setRenderType(hearts ? ObjectiveRenderType.HEARTS : ObjectiveRenderType.INTEGER);
}
@Override
public void registerTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix, @NotNull NameVisibility visibility, @NotNull CollisionRule collision, @NotNull Collection<String> players, int options) {
Team team = getScoreboard().createTeamBuilder(name)
.displayName(IChatBaseComponent.optimizedComponent(name).toAdventureComponent(player.getVersion()))
.prefix(IChatBaseComponent.optimizedComponent(prefix).toAdventureComponent(player.getVersion()))
.suffix(IChatBaseComponent.optimizedComponent(suffix).toAdventureComponent(player.getVersion()))
.friendlyFire((options & 0x01) != 0)
.canSeeInvisibleMembers((options & 0x02) != 0)
.collisionRule(org.kryptonmc.api.scoreboard.CollisionRule.valueOf(collision.name()))
.nameTagVisibility(Visibility.valueOf(visibility.name()))
.buildAndRegister();
for (String member : players) {
team.addMember(IChatBaseComponent.optimizedComponent(member).toAdventureComponent(player.getVersion()));
}
}
@Override
public void unregisterTeam0(@NotNull String name) {
Team team = getScoreboard().getTeam(name);
if (team != null) getScoreboard().removeTeam(team);
}
@Override
public void updateTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix, @NotNull NameVisibility visibility, @NotNull CollisionRule collision, int options) {
Team team = getScoreboard().getTeam(name);
if (team == null) return;
team.setDisplayName(IChatBaseComponent.optimizedComponent(name).toAdventureComponent(player.getVersion()));
team.setPrefix(IChatBaseComponent.optimizedComponent(prefix).toAdventureComponent(player.getVersion()));
team.setSuffix(IChatBaseComponent.optimizedComponent(suffix).toAdventureComponent(player.getVersion()));
team.setAllowFriendlyFire((options & 0x01) != 0);
team.setCanSeeInvisibleMembers((options & 0x02) != 0);
team.setCollisionRule(org.kryptonmc.api.scoreboard.CollisionRule.valueOf(collision.name()));
team.setNameTagVisibility(Visibility.valueOf(visibility.name()));
}
@Override
public void setScore0(@NotNull String objectiveName, @NotNull String playerName, int score) {
Objective objective = getScoreboard().getObjective(objectiveName);
if (objective != null)
objective.getOrCreateScore(IChatBaseComponent.optimizedComponent(playerName)
.toAdventureComponent(player.getVersion())).setScore(score);
}
@Override
public void removeScore0(@NotNull String objectiveName, @NotNull String playerName) {
Objective objective = getScoreboard().getObjective(objectiveName);
if (objective != null) objective.removeScore(IChatBaseComponent.optimizedComponent(playerName).toAdventureComponent(player.getVersion()));
}
private org.kryptonmc.api.scoreboard.Scoreboard getScoreboard() {
return player.getPlayer().getScoreboard();
}
}
@@ -1,57 +0,0 @@
package me.neznamy.tab.platforms.krypton;
import com.google.inject.Inject;
import lombok.Getter;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import org.kryptonmc.api.Server;
import org.kryptonmc.api.command.CommandMeta;
import org.kryptonmc.api.event.Event;
import org.kryptonmc.api.event.EventFilter;
import org.kryptonmc.api.event.EventNode;
import org.kryptonmc.api.event.Listener;
import org.kryptonmc.api.event.server.ServerStartEvent;
import org.kryptonmc.api.event.server.ServerStopEvent;
import org.kryptonmc.api.plugin.annotation.Dependency;
import org.kryptonmc.api.plugin.annotation.Plugin;
import java.nio.file.Path;
/**
* Main class for Krypton platform
*/
@Plugin(
id = TabConstants.PLUGIN_ID,
name = TabConstants.PLUGIN_NAME,
version = TabConstants.PLUGIN_VERSION,
description = TabConstants.PLUGIN_DESCRIPTION,
authors = {TabConstants.PLUGIN_AUTHOR, "BomBardyGamer"},
dependencies = {
@Dependency(id = "luckperms", optional = true),
@Dependency(id = "viaversion", optional = true)
}
)
public class KryptonTAB {
@Inject @Getter private Server server;
@Inject private EventNode<Event> pluginEventNode;
@Inject private Path folder;
private final EventNode<Event> eventNode = EventNode.Companion.filteredForEvent("tab_events", EventFilter.ALL,
(it) -> !TAB.getInstance().isPluginDisabled());
@Listener
public final void onStart(ServerStartEvent event) {
pluginEventNode.addChild(eventNode);
eventNode.registerListeners(new KryptonEventListener());
server.getCommandManager().register(new KryptonTabCommand(), CommandMeta.builder(TabConstants.COMMAND_BACKEND).build());
TAB.setInstance(new TAB(new KryptonPlatform(this), ProtocolVersion.fromNetworkId(server.getPlatform().protocolVersion()),
folder.toFile()));
TAB.getInstance().load();
}
@Listener
public final void onStop(ServerStopEvent event) {
TAB.getInstance().unload();
}
}
@@ -1,44 +0,0 @@
package me.neznamy.tab.platforms.krypton;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.platform.TabPlayer;
import net.kyori.adventure.text.Component;
import org.jetbrains.annotations.NotNull;
import org.kryptonmc.api.command.Sender;
import org.kryptonmc.api.command.SimpleCommand;
import org.kryptonmc.api.entity.player.Player;
import java.util.Collections;
import java.util.List;
public class KryptonTabCommand implements SimpleCommand {
@Override
public void execute(@NotNull Sender sender, String[] args) {
if (TAB.getInstance().isPluginDisabled()) {
boolean canReload = sender.hasPermission(TabConstants.Permission.COMMAND_RELOAD);
boolean isAdmin = sender.hasPermission(TabConstants.Permission.COMMAND_ALL);
for (String message : TAB.getInstance().getDisabledCommand().execute(args, canReload, isAdmin)) {
sender.sendMessage(Component.text(message));
}
return;
}
TabPlayer player = null;
if (sender instanceof Player) {
player = TAB.getInstance().getPlayer(((Player) sender).getUuid());
if (player == null) return;
}
TAB.getInstance().getCommand().execute(player, args);
}
@Override
public @NotNull List<String> suggest(@NotNull Sender sender, String[] args) {
TabPlayer player = null;
if (sender instanceof Player) {
player = TAB.getInstance().getPlayer(((Player) sender).getUuid());
if (player == null) return Collections.emptyList();
}
return TAB.getInstance().getCommand().complete(player, args);
}
}
@@ -1,65 +0,0 @@
package me.neznamy.tab.platforms.krypton;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.TabList;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.kryptonmc.api.auth.GameProfile;
import org.kryptonmc.api.auth.ProfileProperty;
import org.kryptonmc.api.entity.player.TabListEntry;
import org.kryptonmc.api.world.GameMode;
import java.util.Collections;
import java.util.UUID;
public record KryptonTabList(KryptonTabPlayer player) implements TabList {
@Override
public void removeEntry(@NotNull UUID entryId) {
player.getPlayer().getTabList().removeEntry(entryId);
}
@Override
public void updateDisplayName(@NotNull UUID entryId, @Nullable IChatBaseComponent displayName) {
TabListEntry entry = player.getPlayer().getTabList().getEntry(entryId);
if (entry != null) entry.setDisplayName(displayName == null ? null : displayName.toAdventureComponent(player.getVersion()));
}
@Override
public void updateLatency(@NotNull UUID entryId, int latency) {
TabListEntry entry = player.getPlayer().getTabList().getEntry(entryId);
if (entry != null) entry.setLatency(latency);
}
@Override
public void updateGameMode(@NotNull UUID entryId, int gameMode) {
TabListEntry entry = player.getPlayer().getTabList().getEntry(entryId);
if (entry != null) entry.setGameMode(GameMode.values()[gameMode]);
}
@Override
public void addEntry(@NotNull Entry entry) {
GameProfile profile = createGameProfile(entry.getUniqueId(), entry.getName(), entry.getSkin());
player.getPlayer().getTabList().createEntryBuilder(entry.getUniqueId(), profile)
.displayName(entry.getDisplayName() == null ? null : entry.getDisplayName().toAdventureComponent(player.getVersion()))
.gameMode(GameMode.values()[entry.getGameMode()])
.latency(entry.getLatency())
.listed(true)
.buildAndRegister();
}
@Override
public void setPlayerListHeaderFooter(@NotNull IChatBaseComponent header, @NotNull IChatBaseComponent footer) {
player.getPlayer().getTabList().setHeaderAndFooter(
header.toAdventureComponent(player.getVersion()),
footer.toAdventureComponent(player.getVersion())
);
}
private @NotNull
GameProfile createGameProfile(@NotNull UUID uuid, @Nullable String name, @Nullable Skin skin) {
String newName = name == null ? "" : name;
if (skin == null) return GameProfile.of(newName, uuid);
ProfileProperty property = ProfileProperty.of(TabList.TEXTURES_PROPERTY, skin.getValue(), skin.getSignature());
return GameProfile.of(newName, uuid, Collections.singletonList(property));
}
}
@@ -1,81 +0,0 @@
package me.neznamy.tab.platforms.krypton;
import lombok.Getter;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.platform.bossbar.AdventureBossBar;
import me.neznamy.tab.shared.platform.bossbar.BossBar;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.Scoreboard;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.kryptonmc.api.auth.ProfileProperty;
import org.kryptonmc.api.entity.player.Player;
import java.util.List;
@Getter
public class KryptonTabPlayer extends TabPlayer {
private final Scoreboard<KryptonTabPlayer> scoreboard = new KryptonScoreboard(this);
private final TabList tabList = new KryptonTabList(this);
private final BossBar bossBar = new AdventureBossBar(this);
public KryptonTabPlayer(Player player) {
super(player, player.getUuid(), player.getProfile().name(), TAB.getInstance().getConfiguration().getServerName(),
player.getWorld().getName(), TAB.getInstance().getServerVersion().getNetworkId(), true);
}
@Override
public boolean hasPermission(@NotNull String permission) {
return getPlayer().hasPermission(permission);
}
@Override
public int getPing() {
return getPlayer().getPing();
}
@Override
public void sendMessage(@NotNull IChatBaseComponent message) {
getPlayer().sendMessage(message.toAdventureComponent(getVersion()));
}
@Override
public boolean hasInvisibilityPotion() {
return false;
}
@Override
public boolean isDisguised() {
return false;
}
@Override
public @Nullable TabList.Skin getSkin() {
List<ProfileProperty> list = getPlayer().getProfile().properties();
if (list.isEmpty()) return null;
return new TabList.Skin(list.get(0).value(), list.get(0).signature());
}
@Override
public @NotNull Player getPlayer() {
return (Player) player;
}
@Override
public boolean isOnline() {
return getPlayer().isOnline();
}
@Override
public boolean isVanished() {
return false;
}
@Override
public int getGamemode() {
return getPlayer().getGameMode().ordinal();
}
}
+5 -10
View File
@@ -2,19 +2,15 @@ enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
dependencyResolutionManagement {
repositories {
mavenCentral() // Netty, SnakeYaml, json-simple, slf4j, Guava, Kyori event, bStats, AuthLib
mavenCentral() // Netty, SnakeYaml, json-simple, Guava, Kyori event, bStats, AuthLib, LuckPerms
maven("https://repo.extendedclip.com/content/repositories/placeholderapi/") // PlaceholderAPI
maven("https://repo.viaversion.com/") // ViaVersion
maven("https://repo.codemc.org/repository/maven-public/") // RedisBungee
maven("https://repo.kryptonmc.org/releases") // Krypton, YamlAssist, LuckPerms
maven("https://repo.kryptonmc.org/snapshots") // BungeeCord
maven("https://repo.codemc.org/repository/maven-public/") // RedisBungee, BungeeCord-proxy
maven("https://repo.opencollab.dev/maven-snapshots/") // Floodgate
maven("https://repo.purpurmc.org/snapshots") // Purpur
maven("https://repo.papermc.io/repository/maven-public/") // Velocity
maven("https://repo.spongepowered.org/repository/maven-public/") // Sponge
maven("https://oss.sonatype.org/content/repositories/snapshots/") // Spark
maven("https://maven.fabricmc.net/") // Fabric
maven("https://jitpack.io") // PremiumVanish, Vault
maven("https://jitpack.io") // PremiumVanish, Vault, YamlAssist
maven("https://repo.md-5.net/content/groups/public/") // LibsDisguises
}
}
@@ -31,11 +27,10 @@ rootProject.name = "TAB"
include(":api")
include(":shared")
include(":krypton")
include(":velocity")
include(":bukkit")
include(":bungeecord")
include(":sponge7")
include(":sponge8")
include(":fabric")
include(":jar")
include(":jar")
+1 -3
View File
@@ -5,7 +5,7 @@ plugins {
dependencies {
api(projects.api)
api("org.yaml:snakeyaml:1.33")
api("me.neznamy:yamlassist:1.0.5")
api("com.github.NEZNAMY:yamlassist:1.0.5")
api("com.googlecode.json-simple:json-simple:1.1.1") {
exclude("junit", "junit")
}
@@ -13,13 +13,11 @@ dependencies {
exclude("com.google.guava", "guava")
exclude("org.checkerframework", "checker-qual")
}
compileOnlyApi("net.md-5:bungeecord-chat:1.19-R0.1-SNAPSHOT")
compileOnlyApi("com.viaversion:viaversion-api:4.5.1")
compileOnlyApi("io.netty:netty-all:4.1.90.Final")
compileOnlyApi("net.luckperms:api:5.4")
compileOnlyApi("com.google.guava:guava:31.1-jre")
compileOnlyApi("org.geysermc.floodgate:api:2.2.0-SNAPSHOT")
compileOnlyApi("org.slf4j:slf4j-api:2.0.6")
compileOnlyApi("net.kyori:adventure-api:4.13.0")
compileOnlyApi("net.kyori:adventure-text-serializer-legacy:4.13.0")
compileOnlyApi("net.kyori:adventure-text-minimessage:4.13.0")
@@ -1,248 +0,0 @@
package me.neznamy.tab.shared;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.*;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import me.neznamy.tab.shared.features.types.TabFeature;
import org.jetbrains.annotations.NotNull;
/**
* A class which measures CPU usage of all tasks inserted into it and shows usage
*/
public class CpuManager {
private final int UPDATE_RATE_SECONDS = 10;
private final long TIME_PERCENT = TimeUnit.SECONDS.toNanos(1) / UPDATE_RATE_SECONDS;
/** Active time in current time period saved as nanoseconds from features */
private volatile Map<String, Map<String, Long>> featureUsageCurrent = new ConcurrentHashMap<>();
/** Active time in current time period saved as nanoseconds from placeholders */
private volatile Map<String, Long> placeholderUsageCurrent = new ConcurrentHashMap<>();
/** Active time in previous time period saved as nanoseconds from features */
private volatile Map<String, Map<String, Long>> featureUsagePrevious = new HashMap<>();
/** Active time in previous time period saved as nanoseconds from placeholders */
private volatile Map<String, Long> placeholderUsagePrevious = new HashMap<>();
// Scheduler for scheduling delayed and repeating tasks
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(
new ThreadFactoryBuilder().setNameFormat("TAB Processing Thread").build());
/** Tasks submitted to main thread before plugin was fully enabled */
private final Queue<Runnable> taskQueue = new ConcurrentLinkedQueue<>();
/** Enabled flag used to queue incoming tasks if plugin is not enabled yet */
private volatile boolean enabled = false;
/**
* Constructs new instance and starts repeating task that resets values in configured interval
*/
public CpuManager() {
startRepeatingTask((int) TimeUnit.SECONDS.toMillis(UPDATE_RATE_SECONDS), () -> {
featureUsagePrevious = Collections.unmodifiableMap(featureUsageCurrent);
placeholderUsagePrevious = Collections.unmodifiableMap(placeholderUsageCurrent);
featureUsageCurrent = new ConcurrentHashMap<>();
placeholderUsageCurrent = new ConcurrentHashMap<>();
for (Entry<String, Long> entry : placeholderUsagePrevious.entrySet()) {
float usagePercent = nanosToPercent(entry.getValue());
if (usagePercent > 50) {
TAB.getInstance().sendConsoleMessage("&c[WARN] CPU usage of placeholder " + entry.getKey() +
" is " + (int)usagePercent + "%. It will most likely cause problems. Try increasing refresh interval.", true);
}
}
});
}
/**
* Cancels all tasks and shuts down thread pools
*/
public void cancelAllTasks() {
scheduler.shutdownNow();
}
/**
* Marks cpu manager as loaded and submits all queued tasks
*/
public void enable() {
enabled = true;
Runnable r;
while ((r = taskQueue.poll()) != null) {
submit(r);
}
}
/**
* Submits task to TAB's main thread. If plugin is not enabled yet,
* queues the task instead and executes once it's loaded.
*
* @param task task to execute
*/
private void submit(@NotNull Runnable task) {
if (scheduler.isShutdown()) return;
if (!enabled) {
taskQueue.add(task);
return;
}
scheduler.submit(() -> run(task));
}
/**
* Returns cpu usage map of placeholders from previous time period
*
* @return cpu usage map of placeholders
*/
public Map<String, Float> getPlaceholderUsage() {
return getUsage(placeholderUsagePrevious);
}
/**
* Converts nano map to percent and sorts it from highest to lowest usage.
*
* @param map map to convert
* @return converted and sorted map
*/
private @NotNull Map<String, Float> getUsage(@NotNull Map<String, Long> map) {
return map
.entrySet()
.stream()
.sorted(Entry.comparingByValue((o1, o2) -> Long.compare(o2, o1)))
.collect(LinkedHashMap::new,
(m, e) -> m.put(e.getKey(), nanosToPercent(e.getValue())),
Map::putAll
);
}
/**
* Returns map of CPU usage per feature and type in the previous time period
*
* @return map of CPU usage per feature and type
*/
public @NotNull Map<String, Map<String, Float>> getFeatureUsage() {
final Map<String, Map<String, Long>> map = featureUsagePrevious;
TreeMap<Long, Map.Entry<String, Map<String, Float>>> sorted
= new TreeMap<>((o1, o2) -> Long.compare(o2, o1));
map.forEach((key, val) -> {
Set<Map.Entry<String, Long>> entries = val.entrySet();
Map<String, Float> percent = new LinkedHashMap<>(entries.size());
long sum = entries
.stream()
.sorted(Map.Entry.comparingByValue((o1, o2) -> Long.compare(o2, o1)))
.peek(e -> percent.put(e.getKey(), nanosToPercent(e.getValue())))
.mapToLong(Map.Entry::getValue)
.sum();
sorted.put(sum, // Map.entry(key, percent) inline type java9+
new AbstractMap.SimpleImmutableEntry<>(key, percent));
});
// we will also try to get rid of O(log(n)) for random reading
int assumeCapacity = map.size();
return sorted
.values()
.stream()
.collect(() -> new LinkedHashMap<>(assumeCapacity),
(m, e) -> m.put(e.getKey(), e.getValue()),
Map::putAll
);
}
/**
* Converts nanoseconds to percent usage.
*
* @param nanos nanoseconds of cpu time
* @return usage in % (0-100)
*/
private float nanosToPercent(long nanos) {
return (float) nanos / TIME_PERCENT;
}
/**
* Adds cpu time to specified feature and usage type
*
* @param feature feature to add time to
* @param type sub-feature to add time to
* @param nanoseconds time to add
*/
public void addTime(@NotNull TabFeature feature, @NotNull String type, long nanoseconds) {
addTime(feature.getFeatureName(), type, nanoseconds);
}
/**
* Adds cpu time to specified feature and usage type
*
* @param feature feature to add time to
* @param type sub-feature to add time to
* @param nanoseconds time to add
*/
public void addTime(@NotNull String feature, @NotNull String type, long nanoseconds) {
featureUsageCurrent
.computeIfAbsent(feature, f -> new ConcurrentHashMap<>())
.merge(type, nanoseconds, Long::sum);
}
/**
* Adds used time to specified key into specified map
*
* @param map map to add usage to
* @param key usage key
* @param time nanoseconds to add
*/
private void addTime(@NotNull Map<String, Long> map, @NotNull String key, long time) {
map.merge(key, time, Long::sum);
}
/**
* Adds placeholder time to specified placeholder
*
* @param placeholder placeholder to add time to
* @param nanoseconds time to add
*/
public void addPlaceholderTime(@NotNull String placeholder, long nanoseconds) {
addTime(placeholderUsageCurrent, placeholder, nanoseconds);
}
public void runMeasuredTask(@NotNull String feature, @NotNull String type, @NotNull Runnable task) {
submit(() -> {
long time = System.nanoTime();
task.run();
addTime(feature, type, System.nanoTime() - time);
});
}
public void runTask(@NotNull Runnable task) {
submit(task);
}
public void startRepeatingMeasuredTask(int intervalMilliseconds, @NotNull String feature, @NotNull String type, @NotNull Runnable task) {
if (scheduler.isShutdown()) return;
scheduler.scheduleAtFixedRate(() -> runMeasuredTask(feature, type, task), intervalMilliseconds, intervalMilliseconds, TimeUnit.MILLISECONDS);
}
public void startRepeatingTask(int intervalMilliseconds, @NotNull Runnable task) {
if (scheduler.isShutdown()) return;
scheduler.scheduleAtFixedRate(() -> run(task), intervalMilliseconds, intervalMilliseconds, TimeUnit.MILLISECONDS);
}
public void runTaskLater(int delayMilliseconds, @NotNull String feature, @NotNull String type, @NotNull Runnable task) {
if (scheduler.isShutdown()) return;
scheduler.schedule(() -> runMeasuredTask(feature, type, task), delayMilliseconds, TimeUnit.MILLISECONDS);
}
private void run(@NotNull Runnable task) {
try {
task.run();
} catch (Exception | LinkageError | StackOverflowError e) {
TAB.getInstance().getErrorManager().printError("An error was thrown when executing task", e);
}
}
}
@@ -2,6 +2,7 @@ package me.neznamy.tab.shared;
import lombok.Getter;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -123,24 +124,24 @@ public class ErrorManager {
if (file.length() < 1000000)
buf.write(dateFormat.format(new Date()) + "[TAB v" + TabConstants.PLUGIN_VERSION + "] " + EnumChatFormat.decolor(message) + System.getProperty("line.separator"));
if (intoConsoleToo || TAB.getInstance().getConfiguration().isDebugMode())
TAB.getInstance().sendConsoleMessage(EnumChatFormat.color("&c[TAB v" + TabConstants.PLUGIN_VERSION + "] ") + EnumChatFormat.decolor(message), false);
TAB.getInstance().getPlatform().logWarn(new IChatBaseComponent(message));
}
for (String line : error) {
if (file.length() < 1000000)
buf.write(dateFormat.format(new Date()) + line + System.getProperty("line.separator"));
if (intoConsoleToo || TAB.getInstance().getConfiguration().isDebugMode())
TAB.getInstance().sendConsoleMessage(EnumChatFormat.color("&c") + line, false);
TAB.getInstance().getPlatform().logWarn(new IChatBaseComponent(line));
}
}
} catch (IOException ex) {
TAB.getInstance().sendConsoleMessage("&cAn error occurred when printing error message into file", true);
TAB.getInstance().sendConsoleMessage(ex.getClass().getName() + ": " + ex.getMessage(), true);
TAB.getInstance().getPlatform().logWarn(new IChatBaseComponent("An error occurred when printing error message into file"));
TAB.getInstance().getPlatform().logWarn(new IChatBaseComponent(ex.getClass().getName() + ": " + ex.getMessage()));
for (StackTraceElement e : ex.getStackTrace()) {
TAB.getInstance().sendConsoleMessage("\t" + e.toString(), true);
TAB.getInstance().getPlatform().logWarn(new IChatBaseComponent("\t" + e.toString()));
}
TAB.getInstance().sendConsoleMessage("&cOriginal error: " + message, true);
TAB.getInstance().getPlatform().logWarn(new IChatBaseComponent("Original error: " + message));
for (String line : error) {
TAB.getInstance().sendConsoleMessage(line, true);
TAB.getInstance().getPlatform().logWarn(new IChatBaseComponent(line));
}
}
}
@@ -211,7 +212,7 @@ public class ErrorManager {
*/
public double parseDouble(@NotNull String string, double defaultValue) {
try {
return Double.parseDouble(string);
return Double.parseDouble(string.replace(",", "."));
} catch (NumberFormatException e) {
return defaultValue;
}
@@ -251,6 +251,26 @@ public class FeatureManager {
}
}
public void onEntryAdd(TabPlayer packetReceiver, UUID id, String name) {
for (TabFeature f : values) {
if (!(f instanceof EntryAddListener)) continue;
long time = System.nanoTime();
((EntryAddListener)f).onEntryAdd(packetReceiver, id, name);
TAB.getInstance().getCPUManager().addTime(f, TabConstants.CpuUsageCategory.PACKET_PLAYER_INFO, System.nanoTime() - time);
}
}
public int onLatencyChange(TabPlayer packetReceiver, UUID id, int latency) {
int newLatency = latency;
for (TabFeature f : values) {
if (!(f instanceof LatencyListener)) continue;
long time = System.nanoTime();
newLatency = ((LatencyListener)f).onLatencyChange(packetReceiver, id, newLatency);
TAB.getInstance().getCPUManager().addTime(f, TabConstants.CpuUsageCategory.PACKET_PLAYER_INFO, System.nanoTime() - time);
}
return newLatency;
}
public void registerFeature(@NotNull String featureName, @NotNull TabFeature featureHandler) {
features.put(featureName, featureHandler);
values = features.values().toArray(new TabFeature[0]);
@@ -284,6 +304,9 @@ public class FeatureManager {
FeatureManager featureManager = TAB.getInstance().getFeatureManager();
int minorVersion = TAB.getInstance().getServerVersion().getMinorVersion();
if (configuration.getConfig().getBoolean("ping-spoof.enabled", false))
featureManager.registerFeature(TabConstants.Feature.PING_SPOOF, new PingSpoof());
if (configuration.getConfig().getBoolean("bossbar.enabled", false)) {
featureManager.registerFeature(TabConstants.Feature.BOSS_BAR,
minorVersion >= 9 ? new BossBarManagerImpl() : TAB.getInstance().getPlatform().getLegacyBossBar());
@@ -306,6 +329,9 @@ public class FeatureManager {
if (configuration.getConfig().getBoolean("per-world-playerlist.enabled", false)) {
TabFeature pwp = TAB.getInstance().getPlatform().getPerWorldPlayerList();
if (pwp != null) featureManager.registerFeature(TabConstants.Feature.PER_WORLD_PLAYER_LIST, pwp);
if (configuration.getConfig().getBoolean("layout.enabled", false)) {
TAB.getInstance().getMisconfigurationHelper().bothPerWorldPlayerListAndLayoutEnabled();
}
}
if (configuration.getConfig().getBoolean("yellow-number-in-tablist.enabled", true))
@@ -337,6 +363,12 @@ public class FeatureManager {
// Must be loaded after: Sorting
if (minorVersion >= 8 && configuration.getConfig().getBoolean("layout.enabled", false)) {
featureManager.registerFeature(TabConstants.Feature.LAYOUT, new LayoutManagerImpl());
if (configuration.getConfig().getBoolean("yellow-number-in-tablist.enabled", true)) {
TAB.getInstance().getMisconfigurationHelper().layoutBreaksYellowNumber();
}
if (configuration.getConfig().getBoolean("prevent-spectator-effect.enabled", false)) {
TAB.getInstance().getMisconfigurationHelper().layoutIncludesPreventSpectatorEffect();
}
}
// Must be loaded after: Layout
@@ -360,5 +392,7 @@ public class FeatureManager {
// Must be loaded after: Global PlayerList, PlayerList, NameTags, YellowNumber, BelowName
RedisSupport redis = TAB.getInstance().getPlatform().getRedisSupport();
if (redis != null) TAB.getInstance().getFeatureManager().registerFeature(TabConstants.Feature.REDIS_BUNGEE, redis);
featureManager.registerFeature(TabConstants.Feature.NICK_COMPATIBILITY, new NickCompatibility());
}
}
@@ -2,9 +2,13 @@ package me.neznamy.tab.shared;
import java.util.*;
import me.neznamy.tab.api.bossbar.BarColor;
import me.neznamy.tab.api.bossbar.BarStyle;
import me.neznamy.tab.shared.TabConstants.Placeholder;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.features.layout.LayoutManagerImpl;
import me.neznamy.tab.shared.features.sorting.types.SortingType;
import me.neznamy.tab.shared.platform.TabPlayer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -30,15 +34,25 @@ public class MisconfigurationHelper {
* Configured refresh intervals
*/
public void fixRefreshIntervals(@NotNull Map<String, Integer> refreshIntervals) {
int defaultRefresh = refreshIntervals.getOrDefault("default-refresh-interval", 500);
LinkedHashMap<String, Integer> valuesToFix = new LinkedHashMap<>();
for (Map.Entry<String, Integer> entry : refreshIntervals.entrySet()) {
for (Map.Entry<String, ?> entry : refreshIntervals.entrySet()) {
if (entry.getValue() == null) {
startupWarn("Refresh interval of " + entry.getKey() +
" is set to null. Define a valid value or remove it if you don't want to override default value.");
valuesToFix.put(entry.getKey(), Placeholder.MINIMUM_REFRESH_INTERVAL);
continue;
}
int interval = entry.getValue();
if (!(entry.getValue() instanceof Integer)) {
startupWarn("Refresh interval configured for \"" + entry.getKey() +
"\" is not a valid number.");
valuesToFix.put(entry.getKey(), 500);
continue;
}
int interval = (int) entry.getValue();
if (!entry.getKey().equals("default-refresh-interval") && interval == defaultRefresh) {
hint("Refresh interval of " + entry.getKey() + " is same as default interval, therefore there is no need to override it.");
}
if (interval < 0) {
startupWarn("Invalid refresh interval configured for " + entry.getKey() +
" (" + interval + "). Value cannot be negative.");
@@ -74,10 +88,11 @@ public class MisconfigurationHelper {
return 1000;
}
if (interval % TabConstants.Placeholder.MINIMUM_REFRESH_INTERVAL != 0) {
int newInterval = interval - interval % TabConstants.Placeholder.MINIMUM_REFRESH_INTERVAL;
if (newInterval == 0) newInterval = TabConstants.Placeholder.MINIMUM_REFRESH_INTERVAL;
startupWarn(String.format("Animation \"&e%s&c\" has refresh interval of %s, which is not divisible by " +
TabConstants.Placeholder.MINIMUM_REFRESH_INTERVAL + "! &bUsing %s.", name, interval, newInterval));
int min = TabConstants.Placeholder.MINIMUM_REFRESH_INTERVAL;
int newInterval = Math.round((float) interval / min) * min; // rounding
if (newInterval == 0) newInterval = min;
startupWarn(String.format("Animation \"&e%s&c\" has refresh interval of %s, which is not divisible by %s! &bUsing %s.",
name, interval, min, newInterval));
return newInterval;
}
return interval;
@@ -187,6 +202,13 @@ public class MisconfigurationHelper {
"Disable global playerlist for the same result, but with better performance.");
}
public void bothPerWorldPlayerListAndLayoutEnabled() {
startupWarn("Both per world playerlist and layout features are enabled, but layout makes per world playerlist redundant.",
"Layout automatically works with all connected players and replaces real player entries with" +
" fake players, making per world playerlist completely useless as real players are pushed out of the playerlist.",
"Disable per world playerlist for the same result, but with better performance.");
}
public void checkLayoutMap(@NotNull String layoutName, @NotNull Map<String, Object> map) {
List<String> expectedKeys = Arrays.asList("condition", "fixed-slots", "groups");
for (String mapKey : map.keySet()) {
@@ -215,6 +237,103 @@ public class MisconfigurationHelper {
}
}
/**
* Checks bossbar section configuration for: <p>
* - Unknown keys, to let people know if they made a typo <p>
* - Missing required properties (text, color, style, progress) and adding them with some default values <p>
* - Evaluating static values of color, style and progress if they can represent the required data type
*
* @param bossbarSection
* Map section of a bossbar in config
* @param name
* Name of the bossbar defined in config
*/
public void checkBossBarProperties(Map<String, Object> bossbarSection, String name) {
// Unknown properties
List<String> validProperties = Arrays.asList("style", "color", "progress", "text", "announcement-bar", "display-condition");
for (String mapKey : bossbarSection.keySet()) {
if (!validProperties.contains(mapKey)) {
startupWarn("Unknown property \"" + mapKey + "\" in bossbar \"" + name + "\". Valid properties: " + validProperties);
}
}
// Text
if (!bossbarSection.containsKey("text")) {
startupWarn("Bossbar \"" + name + "\" is missing \"text\" property.");
bossbarSection.put("text", "Text is not defined!");
}
// Color
if (bossbarSection.containsKey("color")) {
String color = bossbarSection.get("color").toString();
if (!color.contains("%")) {
try {
BarColor.valueOf(color.toUpperCase(Locale.US));
} catch (IllegalArgumentException e) {
startupWarn("Bossbar \"" + name + " has color set to \"" + color + "\", which is not one of the supported colors " +
Arrays.toString(BarColor.values()) + " or a placeholder evaluating to one.");
bossbarSection.put("color", "PURPLE");
}
}
} else {
startupWarn("Bossbar \"" + name + "\" is missing \"color\" property.");
bossbarSection.put("color", "PURPLE");
}
// Style
if (bossbarSection.containsKey("style")) {
String style = bossbarSection.get("style").toString();
if (!style.contains("%")) {
try {
BarStyle.valueOf(style.toUpperCase(Locale.US));
} catch (IllegalArgumentException e) {
startupWarn("Bossbar \"" + name + " has style set to \"" + style + "\", which is not one of the supported styles " +
Arrays.toString(BarStyle.values()) + " or a placeholder evaluating to one.");
bossbarSection.put("style", "PROGRESS");
}
}
} else {
startupWarn("Bossbar \"" + name + "\" is missing \"style\" property.");
bossbarSection.put("style", "PROGRESS");
}
// Progress
if (bossbarSection.containsKey("progress")) {
String progress = bossbarSection.get("progress").toString();
if (!progress.contains("%")) {
try {
Float.parseFloat(progress);
} catch (IllegalArgumentException e) {
startupWarn("Bossbar \"" + name + " has progress set to \"" + progress + "\", which is not a valid number between 0 and 100 or a placeholder evaluating to one.");
bossbarSection.put("progress", "100");
}
}
} else {
startupWarn("Bossbar \"" + name + "\" is missing \"progress\" property.");
bossbarSection.put("progress", "100");
}
}
public void teamAntiOverrideDisabled() {
startupWarn("anti-override for scoreboard-teams is disabled in config. This is usually a mistake. If you notice the" +
" feature randomly breaking, enable it back.");
}
public void tablistAntiOverrideDisabled() {
startupWarn("anti-override for tablist-name-formatting is disabled in config. This is usually a mistake. If you notice the" +
" feature randomly breaking, enable it back.");
}
public void nonLastNoConditionScoreboard(@NotNull String noConditionScoreboard, @NotNull String nextScoreboard) {
startupWarn("Scoreboard \"" + noConditionScoreboard + "\" has no display condition set, however, there is" +
" another scoreboard in the chain (" + nextScoreboard + "). Scoreboards are checked from top to bottom" +
" until a scoreboard with meeting condition or no condition is found. Because of this, the scoreboard (" +
nextScoreboard + ") after the no-condition scoreboard (" + noConditionScoreboard + ") will never be displayed. " +
"Unless this is intentional to externally display the scoreboard (commands, API), this is a mistake.");
}
public void layoutBreaksYellowNumber() {
startupWarn("Layout feature breaks yellow-number feature, because it replaces real player with fake slots " +
"with different usernames for more reliable functionality. Disable yellow-number feature, as it will only look bad " +
"and consume resources.");
}
/**
* Sends a startup warn message into console
*
@@ -224,7 +343,7 @@ public class MisconfigurationHelper {
private void startupWarn(@NotNull String... messages) {
warnCount++;
for (String message : messages) {
TAB.getInstance().sendConsoleMessage("&c[WARN] " + message, true);
TAB.getInstance().getPlatform().logWarn(IChatBaseComponent.fromColoredText(message));
}
}
@@ -233,7 +352,7 @@ public class MisconfigurationHelper {
*/
public void printWarnCount() {
if (warnCount == 0) return;
TAB.getInstance().sendConsoleMessage("&eFound a total of " + warnCount + " issues.", true);
TAB.getInstance().getPlatform().logWarn(new IChatBaseComponent("Found a total of " + warnCount + " issues."));
// Reset after printing to prevent count going up on each reload
warnCount = 0;
}
@@ -244,12 +363,64 @@ public class MisconfigurationHelper {
public void invalidNumberForBossBarProgress(@NotNull String bossBar, @NotNull String input, @NotNull String configuredValue) {
if (configuredValue.contains("%")) {
TAB.getInstance().sendConsoleMessage("&c[WARN] Placeholder \"" + configuredValue +
"\" used in BossBar progress of \"" + bossBar + "\" returned value, which cannot be evaluated to a number between 0 and 100 (\"" + input + "\")", true);
TAB.getInstance().getPlatform().logWarn(new IChatBaseComponent("Placeholder \"" + configuredValue +
"\" used in BossBar progress of \"" + bossBar + "\" returned value, which cannot be evaluated to a number between 0 and 100 (\"" + input + "\")"));
} else {
TAB.getInstance().sendConsoleMessage("&c[WARN] BossBar \"" + bossBar +
"\" has invalid input configured for progress (\"" + configuredValue + "\"). Expecting a number between 0 and 100 or a placeholder returning one.", true);
TAB.getInstance().getPlatform().logWarn(new IChatBaseComponent("BossBar \"" + bossBar +
"\" has invalid input configured for progress (\"" + configuredValue + "\"). Expecting a number between 0 and 100 or a placeholder returning one."));
}
}
public void invalidInputForNumericSorting(SortingType type, String placeholder, String output, TabPlayer player) {
TAB.getInstance().getPlatform().logWarn(new IChatBaseComponent("Placeholder " + placeholder + " used in sorting type " +
type + " returned \"" + output + "\" for player " + player.getName() + ", which is not a valid number."));
}
// ------------------------
// Hints
// ------------------------
@SuppressWarnings("unchecked")
public void checkHeaderFooterForRedundancy(Map<String, Object> configSection) {
String defaultHeader = String.valueOf(configSection.get("header"));
String defaultFooter = String.valueOf(configSection.get("footer"));
if (configSection.get("per-world") instanceof Map) {
Map<String, Map<String, Object>> map = (Map<String, Map<String, Object>>) configSection.get("per-world");
for (Map.Entry<String, Map<String, Object>> entry : map.entrySet()) {
String world = entry.getKey();
if (String.valueOf(entry.getValue().getOrDefault("header", "-")).equals(defaultHeader)) {
hint("Per-world header for world \"" + world + "\" is identical to default header. " +
"This is redundant and can be removed for cleaner config.");
}
if (String.valueOf(entry.getValue().getOrDefault("footer", "-")).equals(defaultFooter)) {
hint("Per-world footer for world \"" + world + "\" is identical to default footer. " +
"This is redundant and can be removed for cleaner config.");
}
}
}
if (configSection.get("per-server") instanceof Map) {
Map<String, Map<String, Object>> map = (Map<String, Map<String, Object>>) configSection.get("per-server");
for (Map.Entry<String, Map<String, Object>> entry : map.entrySet()) {
String server = entry.getKey();
if (String.valueOf(entry.getValue().getOrDefault("header", "-")).equals(defaultHeader)) {
hint("Per-server header for server \"" + server + "\" is identical to default header. " +
"This is redundant and can be removed for cleaner config.");
}
if (String.valueOf(entry.getValue().getOrDefault("footer", "-")).equals(defaultFooter)) {
hint("Per-server footer for server \"" + server + "\" is identical to default footer. " +
"This is redundant and can be removed for cleaner config.");
}
}
}
}
public void layoutIncludesPreventSpectatorEffect() {
hint("Layout feature automatically includes prevent-spectator-effect, therefore the feature can be disabled " +
"for better performance, as it is not needed at all (assuming it is configured to always display some layout).");
}
public void hint(@NotNull String message) {
TAB.getInstance().getPlatform().logInfo(IChatBaseComponent.fromColoredText("&6[Hint] " + message));
}
}
@@ -14,6 +14,7 @@ public enum ProtocolVersion {
UNKNOWN_SERVER_VERSION ("Unknown"),
UNKNOWN_CLIENT_VERSION ("Unknown"),
PROXY ("Proxy"),
V1_20_2 (764),
V1_20_1 (763),
V1_20 (763),
V1_19_4 (762),
@@ -12,7 +12,7 @@ import me.neznamy.tab.api.scoreboard.ScoreboardManager;
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.shared.hook.ViaVersionHook;
import me.neznamy.tab.shared.cpu.CpuManager;
import me.neznamy.tab.shared.platform.Platform;
import me.neznamy.tab.shared.command.DisabledCommand;
import me.neznamy.tab.shared.command.TabCommand;
@@ -21,6 +21,7 @@ import me.neznamy.tab.shared.event.EventBusImpl;
import me.neznamy.tab.shared.event.impl.TabLoadEventImpl;
import me.neznamy.tab.shared.features.PlaceholderManagerImpl;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.proxy.ProxyPlatform;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.yaml.snakeyaml.error.YAMLException;
@@ -36,7 +37,7 @@ import java.util.concurrent.ConcurrentHashMap;
public class TAB extends TabAPI {
/** Instance of this class */
@Getter @Setter private static TAB instance;
@Getter private static TAB instance;
/** Player data storage */
private final Map<UUID, TabPlayer> data = new ConcurrentHashMap<>();
@@ -54,7 +55,7 @@ public class TAB extends TabAPI {
@Getter private final DisabledCommand disabledCommand = new DisabledCommand();
/** Implementation of platform the plugin is installed on for platform-specific calls */
@Getter private final Platform platform;
@Getter private final Platform<?> platform;
/**
* CPU manager for thread and task management as well as
@@ -100,19 +101,28 @@ public class TAB extends TabAPI {
/** Helper for detecting misconfiguration in configs and send it to user */
@Getter private final MisconfigurationHelper misconfigurationHelper = new MisconfigurationHelper();
/**
* Creates new instance using given platform and loads it
*
* @param platform
* Platform interface
*/
public static void create(@NotNull Platform<?> platform) {
instance = new TAB(platform);
instance.load();
}
/**
* Constructs new instance with given parameters and sets this
* new instance as {@link me.neznamy.tab.api.TabAPI} instance.
*
* @param platform
* Platform interface
* @param serverVersion
* Version the server is running on
*/
public TAB(@NotNull Platform platform, @NotNull ProtocolVersion serverVersion, @NotNull File dataFolder) {
private TAB(@NotNull Platform<?> platform) {
this.platform = platform;
this.serverVersion = serverVersion;
this.dataFolder = dataFolder;
this.serverVersion = platform.getServerVersion();
this.dataFolder = platform.getDataFolder();
this.errorManager = new ErrorManager(this);
try {
eventBus = new EventBusImpl();
@@ -120,6 +130,12 @@ public class TAB extends TabAPI {
//1.7.10 or lower
}
TabAPI.setInstance(this);
platform.registerListener();
platform.registerCommand();
platform.startMetrics();
if (platform instanceof ProxyPlatform) {
((ProxyPlatform<?>) platform).registerChannel();
}
}
/**
@@ -157,12 +173,11 @@ public class TAB extends TabAPI {
if (eventBus != null) eventBus.fire(TabLoadEventImpl.getInstance());
pluginDisabled = false;
cpu.enable();
ViaVersionHook.getInstance().printProxyWarn();
misconfigurationHelper.printWarnCount();
sendConsoleMessage("&aEnabled in " + (System.currentTimeMillis()-time) + "ms", true);
platform.logInfo(IChatBaseComponent.fromColoredText("&aEnabled in " + (System.currentTimeMillis()-time) + "ms"));
return configuration.getMessages().getReloadSuccess();
} catch (YAMLException e) {
sendConsoleMessage("&cDid not enable due to a broken configuration file.", true);
platform.logWarn(IChatBaseComponent.fromColoredText("&cDid not enable due to a broken configuration file."));
kill();
return (configuration == null ? "&4Failed to reload, file %file% has broken syntax. Check console for more info."
: configuration.getMessages().getReloadFailBrokenFile()).replace("%file%", brokenFile);
@@ -183,7 +198,7 @@ public class TAB extends TabAPI {
long time = System.currentTimeMillis();
if (configuration.getMysql() != null) configuration.getMysql().closeConnection();
featureManager.unload();
sendConsoleMessage("&aDisabled in " + (System.currentTimeMillis()-time) + "ms", true);
platform.logInfo(IChatBaseComponent.fromColoredText("&aDisabled in " + (System.currentTimeMillis()-time) + "ms"));
} catch (Exception | NoClassDefFoundError e) {
errorManager.criticalError("Failed to disable", e);
}
@@ -277,10 +292,6 @@ public class TAB extends TabAPI {
return data.get(uniqueId);
}
public void sendConsoleMessage(@NotNull String message, boolean translateColors) {
platform.sendConsoleMessage(translateColors ? IChatBaseComponent.fromColoredText(message) : new IChatBaseComponent(message));
}
@Override
public @Nullable HeaderFooterManager getHeaderFooterManager() {
return featureManager.getFeature(TabConstants.Feature.HEADER_FOOTER);
@@ -313,6 +324,7 @@ public class TAB extends TabAPI {
* Message to send
*/
public void debug(@NotNull String message) {
if (configuration != null && configuration.isDebugMode()) sendConsoleMessage("&9[DEBUG] " + message, true);
if (configuration != null && configuration.isDebugMode())
platform.logInfo(IChatBaseComponent.fromColoredText("&9[DEBUG] " + message));
}
}
@@ -82,7 +82,7 @@ public class TabConstants {
public static final String COMMAND_BOSSBAR_ANNOUNCE = "tab.announce.bar";
public static final String COMMAND_BOSSBAR_TOGGLE = "tab.bossbar.toggle";
public static final String COMMAND_BOSSBAR_SEND = "tab.send.bar";
public static final String COMMAND_BOSSBAR_TOGGLE_OTHER = "tab.bossbar.toggle.other";
public static final String COMMAND_MYSQL_DOWNLOAD = "tab.mysql.download";
public static final String COMMAND_MYSQL_UPLOAD = "tab.mysql.upload";
@@ -192,6 +192,7 @@ public class TabConstants {
public static final String UNLIMITED_NAME_TAGS = "NameTagX";
public static final String UNLIMITED_NAME_TAGS_PACKET_LISTENER = "nametagx-packet";
public static final String UNLIMITED_NAME_TAGS_VEHICLE_REFRESHER = "nametagx-vehicle";
public static final String PING_SPOOF = "PingSpoof";
//Bukkit only
public static final String PER_WORLD_PLAYER_LIST = "PerWorldPlayerList";
@@ -203,6 +204,7 @@ public class TabConstants {
//additional info displayed in cpu command
public static final String PACKET_DESERIALIZING = "Packet deserializing";
public static final String NICK_COMPATIBILITY = "Nick";
public static String scoreboardLine(String line) {
return "scoreboard-" + line;
@@ -280,6 +282,7 @@ public class TabConstants {
public static final String HEALTH = "%health%";
public static final int MINIMUM_REFRESH_INTERVAL = 50;
public static final int RETURN_TIME_WARN_THRESHOLD = 50;
public static String condition(String name) {
return "%condition:" + name + "%";
@@ -302,7 +305,6 @@ public class TabConstants {
public static final String PERMISSION_SYSTEM = "permission_system";
public static final String GLOBAL_PLAYER_LIST_ENABLED = "global_playerlist_enabled";
public static final String PLACEHOLDER_API = "placeholderapi";
public static final String SERVER_VERSION = "server_version";
public static final String UNLIMITED_NAME_TAG_MODE_ENABLED = "unlimited_nametag_mode_enabled";
@@ -1,13 +1,16 @@
package me.neznamy.tab.shared.backend;
import me.neznamy.tab.api.placeholder.PlaceholderManager;
import me.neznamy.tab.shared.GroupManager;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.hook.LuckPermsHook;
import me.neznamy.tab.shared.placeholders.UniversalPlaceholderRegistry;
import me.neznamy.tab.shared.platform.Platform;
import me.neznamy.tab.shared.features.redis.RedisSupport;
import org.jetbrains.annotations.NotNull;
public interface BackendPlatform extends Platform {
public interface BackendPlatform<T> extends Platform<T> {
@Override
@NotNull default GroupManager detectPermissionPlugin() {
@@ -18,4 +21,33 @@ public interface BackendPlatform extends Platform {
}
default RedisSupport getRedisSupport() { return null; }
@Override
default void registerPlaceholders() {
UniversalPlaceholderRegistry registry = new UniversalPlaceholderRegistry();
PlaceholderManager manager = TAB.getInstance().getPlaceholderManager();
manager.registerPlayerPlaceholder(TabConstants.Placeholder.HEALTH, 100,
p -> (int) Math.ceil(((BackendTabPlayer)p).getHealth()));
manager.registerPlayerPlaceholder(TabConstants.Placeholder.DISPLAY_NAME, 500,
p -> ((BackendTabPlayer)p).getDisplayName());
manager.registerServerPlaceholder(TabConstants.Placeholder.TPS, 1000,
() -> registry.formatTPS(getTPS()));
manager.registerServerPlaceholder(TabConstants.Placeholder.MSPT, 1000,
() -> registry.format(getMSPT()));
registry.registerPlaceholders(manager);
}
/**
* Returns server's TPS for {@link TabConstants.Placeholder#TPS} placeholder
*
* @return server's TPS
*/
double getTPS();
/**
* Returns server's MSPT for {@link TabConstants.Placeholder#MSPT} placeholder
*
* @return server's MSPT
*/
double getMSPT();
}
@@ -1,32 +1,55 @@
package me.neznamy.tab.shared.backend;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.backend.entityview.EntityView;
import me.neznamy.tab.shared.hook.ViaVersionHook;
import me.neznamy.tab.shared.platform.TabPlayer;
import org.jetbrains.annotations.NotNull;
import java.util.UUID;
/**
* TabPlayer extension for backend platforms, which have access to
* more data and can display it.
*/
public abstract class BackendTabPlayer extends TabPlayer {
/**
* Constructs new instance with given parameters
*
* @param player platform-specific player object
* @param uniqueId Player's unique ID
* @param name Player's name
* @param server Player's server
* @param world Player's world
* @param protocolVersion Player's game version
* @param player
* platform-specific player object
* @param uniqueId
* Player's unique ID
* @param name
* Player's name
* @param world
* Player's world
*/
protected BackendTabPlayer(@NotNull Object player, @NotNull UUID uniqueId, @NotNull String name,
@NotNull String server, @NotNull String world, int protocolVersion) {
super(player, uniqueId, name, server, world, protocolVersion, true);
protected BackendTabPlayer(@NotNull BackendPlatform<?> platform, @NotNull Object player, @NotNull UUID uniqueId,
@NotNull String name, @NotNull String world) {
super(platform, player, uniqueId, name, TAB.getInstance().getConfiguration().getServerName(),
world, ViaVersionHook.getInstance().getPlayerVersion(uniqueId, name), true);
}
public abstract void spawnEntity(int entityId, @NotNull UUID id, @NotNull Object entityType, @NotNull Location location, @NotNull EntityData data);
/**
* Returns player's health for {@link me.neznamy.tab.shared.TabConstants.Placeholder#HEALTH} placeholder.
*
* @return player's health
*/
public abstract double getHealth();
public abstract void updateEntityMetadata(int entityId, @NotNull EntityData data);
/**
* Returns player's display name for {@link me.neznamy.tab.shared.TabConstants.Placeholder#DISPLAY_NAME} placeholder.
*
* @return player's display name
*/
public abstract String getDisplayName();
public abstract void teleportEntity(int entityId, @NotNull Location location);
public abstract void destroyEntities(int... entities);
/**
* Returns player's entity view
*
* @return player's entity view
*/
public abstract EntityView getEntityView();
}
@@ -9,6 +9,4 @@ public class Location {
private double x;
private double y;
private double z;
private float yaw;
private float pitch;
}
@@ -0,0 +1,75 @@
package me.neznamy.tab.shared.backend.entityview;
import me.neznamy.tab.shared.backend.EntityData;
import me.neznamy.tab.shared.backend.Location;
import org.jetbrains.annotations.NotNull;
import java.util.UUID;
public class DummyEntityView implements EntityView {
@Override
public void spawnEntity(int entityId, @NotNull UUID id, @NotNull Object entityType, @NotNull Location location, @NotNull EntityData data) {
throw new UnsupportedOperationException("Not available on this platform");
}
@Override
public void updateEntityMetadata(int entityId, @NotNull EntityData data) {
throw new UnsupportedOperationException("Not available on this platform");
}
@Override
public void teleportEntity(int entityId, @NotNull Location location) {
throw new UnsupportedOperationException("Not available on this platform");
}
@Override
public void destroyEntities(int... entities) {
throw new UnsupportedOperationException("Not available on this platform");
}
@Override
public boolean isDestroyPacket(Object packet) {
throw new UnsupportedOperationException("Not available on this platform");
}
@Override
public boolean isTeleportPacket(Object packet) {
throw new UnsupportedOperationException("Not available on this platform");
}
@Override
public boolean isNamedEntitySpawnPacket(Object packet) {
throw new UnsupportedOperationException("Not available on this platform");
}
@Override
public boolean isMovePacket(Object packet) {
throw new UnsupportedOperationException("Not available on this platform");
}
@Override
public boolean isLookPacket(Object packet) {
throw new UnsupportedOperationException("Not available on this platform");
}
@Override
public int getTeleportEntityId(Object teleportPacket) {
throw new UnsupportedOperationException("Not available on this platform");
}
@Override
public int getMoveEntityId(Object movePacket) {
throw new UnsupportedOperationException("Not available on this platform");
}
@Override
public int getSpawnedPlayer(Object playerSpawnPacket) {
throw new UnsupportedOperationException("Not available on this platform");
}
@Override
public int[] getDestroyedEntities(Object destroyPacket) {
throw new UnsupportedOperationException("Not available on this platform");
}
}
@@ -0,0 +1,36 @@
package me.neznamy.tab.shared.backend.entityview;
import me.neznamy.tab.shared.backend.EntityData;
import me.neznamy.tab.shared.backend.Location;
import org.jetbrains.annotations.NotNull;
import java.util.UUID;
public interface EntityView {
void spawnEntity(int entityId, @NotNull UUID id, @NotNull Object entityType, @NotNull Location location, @NotNull EntityData data);
void updateEntityMetadata(int entityId, @NotNull EntityData data);
void teleportEntity(int entityId, @NotNull Location location);
void destroyEntities(int... entities);
boolean isDestroyPacket(Object packet);
boolean isTeleportPacket(Object packet);
boolean isNamedEntitySpawnPacket(Object packet);
boolean isMovePacket(Object packet);
boolean isLookPacket(Object packet);
int getTeleportEntityId(Object teleportPacket);
int getMoveEntityId(Object movePacket);
int getSpawnedPlayer(Object playerSpawnPacket);
int[] getDestroyedEntities(Object destroyPacket);
}
@@ -110,8 +110,8 @@ public class ArmorStand {
public void spawn(BackendTabPlayer viewer) {
visible = calculateVisibility();
viewer.spawnEntity(entityId, uuid, manager.getArmorStandType(),
new Location(manager.getX(owner), getYLocation(viewer), manager.getZ(owner), 0, 0),
viewer.getEntityView().spawnEntity(entityId, uuid, manager.getArmorStandType(),
new Location(manager.getX(owner), getYLocation(viewer), manager.getZ(owner)),
createDataWatcher(property.getFormat(viewer), viewer));
}
@@ -137,10 +137,9 @@ public class ArmorStand {
*/
protected boolean isNameVisiblyEmpty(@NotNull String displayName) {
if (displayName.length() == 0) return true;
if (!displayName.startsWith(EnumChatFormat.COLOR_STRING) && !displayName.startsWith("&") && !displayName.startsWith("#")) return false;
String text = IChatBaseComponent.fromColoredText(displayName).toRawText();
if (text.contains(" ")) text = text.replace(" ", "");
return text.length() == 0;
String rawText = displayName.contains(" ") ? displayName.replace(" ", "") : displayName;
if (!rawText.startsWith(EnumChatFormat.COLOR_STRING) && !rawText.startsWith("&") && !rawText.startsWith("#")) return false;
return IChatBaseComponent.fromColoredText(rawText).toRawText().length() == 0;
}
/**
@@ -179,7 +178,7 @@ public class ArmorStand {
*/
public void updateMetadata() {
for (BackendTabPlayer viewer : asm.getNearbyPlayers()) {
viewer.updateEntityMetadata(entityId, createDataWatcher(property.getFormat(viewer), viewer));
viewer.getEntityView().updateEntityMetadata(entityId, createDataWatcher(property.getFormat(viewer), viewer));
}
}
@@ -190,7 +189,7 @@ public class ArmorStand {
}
public void sendTeleportPacket(@NotNull BackendTabPlayer viewer) {
viewer.teleportEntity(entityId, new Location(manager.getX(owner), getYLocation(viewer), manager.getZ(owner), 0, 0));
viewer.getEntityView().teleportEntity(entityId, new Location(manager.getX(owner), getYLocation(viewer), manager.getZ(owner)));
}
/**
@@ -117,7 +117,7 @@ public class BackendArmorStandManager implements ArmorStandManager {
// creating new delayed task every time someone sneaks can be abused and cause OOM
// RIP 1.8.0
for (ArmorStand as : armorStandArray) {
viewer.destroyEntities(as.getEntityId());
viewer.getEntityView().destroyEntities(as.getEntityId());
}
for (ArmorStand a : armorStandArray) {
a.spawn(viewer);
@@ -173,7 +173,7 @@ public class BackendArmorStandManager implements ArmorStandManager {
*/
public void destroy(@NotNull BackendTabPlayer viewer) {
for (ArmorStand as : armorStandArray) {
viewer.destroyEntities(as.getEntityId());
viewer.getEntityView().destroyEntities(as.getEntityId());
}
unregisterPlayer(viewer);
}
@@ -182,7 +182,7 @@ public class BackendArmorStandManager implements ArmorStandManager {
public void destroy() {
for (BackendTabPlayer viewer : nearbyPlayers) {
for (ArmorStand as : armorStandArray) {
viewer.destroyEntities(as.getEntityId());
viewer.getEntityView().destroyEntities(as.getEntityId());
}
}
nearbyPlayerList.clear();
@@ -203,7 +203,7 @@ public class BackendArmorStandManager implements ArmorStandManager {
public void updateMetadata(@NotNull BackendTabPlayer viewer) {
for (ArmorStand a : armorStandArray) {
viewer.updateEntityMetadata(a.entityId, a.createDataWatcher(a.getProperty().getFormat(viewer), viewer));
viewer.getEntityView().updateEntityMetadata(a.entityId, a.createDataWatcher(a.getProperty().getFormat(viewer), viewer));
}
}
}
@@ -4,6 +4,7 @@ import lombok.Getter;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.features.types.GameModeListener;
import me.neznamy.tab.shared.features.types.PacketSendListener;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.backend.BackendTabPlayer;
import me.neznamy.tab.shared.backend.EntityData;
@@ -12,8 +13,9 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.UUID;
public abstract class BackendNameTagX extends NameTagX implements GameModeListener {
public abstract class BackendNameTagX extends NameTagX implements GameModeListener, PacketSendListener {
/** Vehicle manager reference */
@Getter private final VehicleRefresher vehicleManager = new VehicleRefresher(this);
@@ -90,7 +92,7 @@ public abstract class BackendNameTagX extends NameTagX implements GameModeListen
*/
private void spawnArmorStands(@NotNull TabPlayer viewer, @NotNull TabPlayer target) {
if (viewer.getVersion().getMinorVersion() < 8) return;
if (target == viewer || isPlayerDisabled(target)) return;
if (target == viewer || isPlayerDisabled(target) || isDead(target)) return;
if (!areInSameWorld(viewer, target)) return;
if (getDistance(viewer, target) <= 48 && canSee(viewer, target) && !target.isVanished())
getArmorStandManager(target).spawn((BackendTabPlayer) viewer);
@@ -156,6 +158,36 @@ public abstract class BackendNameTagX extends NameTagX implements GameModeListen
}
}
@Override
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;
BackendTabPlayer player = (BackendTabPlayer) receiver;
if (player.getEntityView().isMovePacket(packet) && !player.getEntityView().isLookPacket(packet)) { //ignoring head rotation only packets
packetListener.onEntityMove(player, player.getEntityView().getMoveEntityId(packet));
} else if (player.getEntityView().isTeleportPacket(packet)) {
packetListener.onEntityMove(player, player.getEntityView().getTeleportEntityId(packet));
} else if (player.getEntityView().isNamedEntitySpawnPacket(packet)) {
packetListener.onEntitySpawn(player, player.getEntityView().getSpawnedPlayer(packet));
} else if (player.getEntityView().isDestroyPacket(packet)) {
packetListener.onEntityDestroy(player, player.getEntityView().getDestroyedEntities(packet));
}
}
public void sneak(UUID playerUUID, boolean sneaking) {
TabPlayer p = TAB.getInstance().getPlayer(playerUUID);
if (p == null || isPlayerDisabled(p)) return;
TAB.getInstance().getCPUManager().runMeasuredTask(featureName, TabConstants.CpuUsageCategory.PLAYER_SNEAK,
() -> getArmorStandManager(p).sneak(sneaking));
}
public void respawn(UUID playerUUID) {
TabPlayer respawned = TAB.getInstance().getPlayer(playerUUID);
if (respawned == null || isPlayerDisabled(respawned)) return;
TAB.getInstance().getCPUManager().runMeasuredTask(featureName, TabConstants.CpuUsageCategory.PLAYER_RESPAWN,
() -> getArmorStandManager(respawned).teleport());
}
public int getEntityId(@NotNull TabPlayer player) {
return getEntityId(player.getPlayer());
}
@@ -202,4 +234,8 @@ public abstract class BackendNameTagX extends NameTagX implements GameModeListen
public abstract double getZ(@NotNull TabPlayer player);
public abstract EntityData createDataWatcher(@NotNull TabPlayer viewer, byte flags, @NotNull String displayName, boolean nameVisible);
public abstract void runInEntityScheduler(Object entity, Runnable task);
public abstract boolean isDead(TabPlayer player);
}
Loaded 100 of 217 files, more files were not shown because too many files have changed in this diff. Show more