Compare commits

..
155 Commits
Author SHA1 Message Date
NEZNAMY 51635a7322 3.3.2 2023-03-31 20:44:09 +02:00
NEZNAMY 481b33ba55 Print error message if plugin message could not be sent 2023-03-31 15:30:30 +02:00
NEZNAMY 22c785fc99 Add error message when plugin message could not be processed 2023-03-31 15:21:53 +02:00
NEZNAMY 42d4159b69 Global PlayerList - Register placeholders in constructor to avoid it being registered as PAPI placeholder 2023-03-31 14:41:21 +02:00
NEZNAMY 6d95a2f06d Fix error on concurrent access 2023-03-31 13:40:50 +02:00
NEZNAMY 7f0b1d8a2e Fix rare error on /btab reload 2023-03-31 13:40:37 +02:00
NEZNAMY a3fecd0fea Unregister tab expansion on unload to fix compatibility with latest PAPI release 2023-03-30 21:17:09 +02:00
NEZNAMY f3d6a54a2a Merge remote-tracking branch 'origin/master' 2023-03-29 17:24:21 +02:00
NEZNAMY 1d434f2557 Add more documentation 2023-03-29 17:24:11 +02:00
NEZNAMY b3500086e4 Merge pull request #856 from zelvalea/master
optimizations in CPUManager
2023-03-28 23:23:48 +02:00
Amoeba bf9202af38 added time constants 2023-03-28 20:31:29 +03:00
Amoeba bd2c683568 fixed happens before and cpu usage statistics 2023-03-28 20:09:51 +03:00
Amoeba c03feaab23 fixed nanosToPercent 2023-03-28 19:44:11 +03:00
Amoeba 9cb9ccbd2a optimize 2023-03-28 19:21:48 +03:00
Callum Seabrook 4606ebd588 Use single thread for tasks and remove ThreadManager from API 2023-03-28 12:32:13 +01:00
Callum Seabrook 679be83c5a Merge pull request #854 from zelvalea/master 2023-03-28 11:51:03 +01:00
NEZNAMY afe868bb95 Fix group update through bridge after recoding group updating 2023-03-28 01:21:29 +02:00
ZelvaLea c9ce3aa23e reduced number of threads in the pool, fixed races 2023-03-28 01:15:04 +03:00
NEZNAMY 11a49c4874 Remove unnecessary full import 2023-03-27 21:21:27 +02:00
NEZNAMY 65d2a0fba9 Remove unused field 2023-03-27 21:19:15 +02:00
NEZNAMY c1219195c7 Remove workarounds for fixing compilation, which are no longer needed 2023-03-26 17:06:57 +02:00
NEZNAMY fca0dfc91b More constant extraction to plugin.yml & bungee.yml 2023-03-26 17:02:32 +02:00
NEZNAMY 7f1cc3063c Move text cutting to 16/32 characters to avoid duplication 2023-03-26 16:53:47 +02:00
NEZNAMY e444c25287 Convert krypton to java 2023-03-26 16:36:51 +02:00
NEZNAMY 476354e407 Change thrown exception 2023-03-25 23:25:07 +01:00
NEZNAMY 9f84d3f22f Remove cpu measurement of chosen methods 2023-03-25 23:22:28 +01:00
NEZNAMY 4608ff360c Simplify 2023-03-25 23:12:48 +01:00
NEZNAMY 68b12e199d Placeholders - Measure per-placeholder cpu usage on join as well 2023-03-25 20:54:53 +01:00
NEZNAMY 4b1151f769 Use defined value instead of duplicating it in build settings 2023-03-25 20:25:27 +01:00
NEZNAMY 6687214f2a Sponge - Add support all the way down to Sponge 5 2023-03-25 19:22:07 +01:00
NEZNAMY a57757258e Sponge - Use API for scoreboards instead of packets (also small bug fix) 2023-03-25 18:41:56 +01:00
NEZNAMY 3a6060c45d Fix display name change on 1.7 2023-03-25 14:29:27 +01:00
NEZNAMY ee375c1b60 Unify tablist management between proxies 2023-03-25 14:22:40 +01:00
NEZNAMY c9d870cca0 Use placeholder refreshing engine for refreshing player groups instead of creating another thread 2023-03-23 14:52:19 +01:00
NEZNAMY 3ad8015cc1 Placeholder manager - Initialize task with the lowest refresh interval immediately instead of re-initializing 2023-03-23 14:19:42 +01:00
NEZNAMY 33609c322f Remove duplicated Field#setAccessible 2023-03-22 00:59:02 +01:00
NEZNAMY 431b89c487 Create constant for "textures" String in skin data 2023-03-22 00:33:16 +01:00
NEZNAMY 35a146992c Remove redundant checks and simplify 2023-03-22 00:27:46 +01:00
NEZNAMY dedee114c4 BungeeCord - Split boss bar handler into 2 classes 2023-03-21 20:07:11 +01:00
NEZNAMY 9d42af0388 Bukkit - Split boss bar handler into 3 classes 2023-03-21 18:39:08 +01:00
NEZNAMY 8f5d81bc33 Remove redundant annotation 2023-03-21 18:38:41 +01:00
NEZNAMY b17f5563dd Remove unused method 2023-03-21 14:09:57 +01:00
NEZNAMY 0ab8bb363a Unlimited NameTags - Force marker tag for 1.8.x and remove config option 2023-03-20 22:33:56 +01:00
NEZNAMY ac5fca86bd Split TabFeature into multiple interfaces based on used methods 2023-03-20 19:36:40 +01:00
NEZNAMY 28c2c2e193 Fix armor stand metadata on spawn 2023-03-20 16:25:37 +01:00
NEZNAMY 904c0b6a17 Fix anti-override for tablist name formatting (and possibly ping spoof & spectator fix) 2023-03-20 16:21:23 +01:00
NEZNAMY f3c31bc92c Fix error 2023-03-20 12:42:42 +01:00
NEZNAMY 6016b84889 BungeeCord - Fix bossbar color & style update 2023-03-19 22:24:13 +01:00
NEZNAMY cfb5cb3a9f Create object for calling bossbar methods 2023-03-19 22:07:45 +01:00
NEZNAMY 1746f6b1d0 Bukkit - Send all tablist packets grouped, ViaVersion splits them properly for 1.7 players 2023-03-19 21:21:33 +01:00
NEZNAMY 9c3979a0d9 More tablist fixes on 1.7 2023-03-19 21:04:52 +01:00
NEZNAMY 810676c69f Bukkit - Fix NPE on <1.8 servers 2023-03-19 16:44:55 +01:00
NEZNAMY d0892a245a BungeeCord - Fix NPE kick on 1.7 2023-03-19 16:40:53 +01:00
NEZNAMY f539b1b93b BungeeCord - Split TabList class into 3 based on version & make 1.7 actually work (potential future use?) 2023-03-19 14:50:02 +01:00
NEZNAMY adfa64a29f Scoreboard - Fix unregistering teams of lines that aren't displayed due to placeholder output being empty 2023-03-19 14:07:44 +01:00
NEZNAMY d4f86ee79d Remove redundant declaration 2023-03-19 13:43:06 +01:00
NEZNAMY 6ebe31b737 Add @NonNull / @Nullable annotations to TeamManager (#843) 2023-03-19 13:38:58 +01:00
NEZNAMY dcbd5e62d4 Fix packet un-cancel breaking PetFix (#842) 2023-03-19 13:26:24 +01:00
NEZNAMY a7e6e49363 Fix #845 (1.7 requires non-null title in scoreboard unregister action) 2023-03-19 12:52:56 +01:00
NEZNAMY d115257ff7 Move DisplaySlot class inside Scoreboard 2023-03-18 23:53:49 +01:00
NEZNAMY d578be276c Remove duplicated caches 2023-03-18 22:35:16 +01:00
NEZNAMY e338f57441 Completely get rid of PacketBuilder class 2023-03-18 22:25:05 +01:00
Callum Seabrook c87321b5b0 Rewrote packet injection for Krypton 2023-03-18 19:37:34 +00:00
NEZNAMY 84bb9b449b Add methods for manipulating entities instead of using sendPacket 2023-03-18 19:22:29 +01:00
NEZNAMY 9d613fc24d Rework tablist packet management to make it more API-style 2023-03-18 17:44:43 +01:00
NEZNAMY 3f2ae3131e Add back a default animation, which is still used in default scoreboard 2023-03-18 00:14:56 +01:00
NEZNAMY 0e9f53bd58 Fix wrong action on objective unregister 2023-03-17 18:20:47 +01:00
NEZNAMY 3981815b63 Create Scoreboard interface for calling scoreboard-related methods 2023-03-17 15:04:55 +01:00
NEZNAMY 3ef99e4e7c Better abstraction of pipeline injection 2023-03-16 15:16:24 +01:00
NEZNAMY 76767a0958 Use API-style managing of scoreboard teams 2023-03-16 13:53:50 +01:00
NEZNAMY 1683943d58 Use API-style managing of scoreboard objectives 2023-03-16 01:07:21 +01:00
NEZNAMY cc0dbe8623 Move sendPluginMessage method to ProxyTabPlayer 2023-03-16 00:03:13 +01:00
NEZNAMY b056251e07 Remove calculating of packets sent by the plugin 2023-03-15 23:50:23 +01:00
NEZNAMY 3b352c66e2 Change way how scoreboard score and display objective packets are sent 2023-03-15 23:31:30 +01:00
NEZNAMY b4f0ea73c1 Supplier with Exception is Callable 2023-03-15 21:43:27 +01:00
NEZNAMY 1c683788b5 BungeeCord - Create cleaner methods for player info action conversion 2023-03-15 21:29:58 +01:00
NEZNAMY 250597df2d Sponge 8 - Fix class cast exception 2023-03-15 21:07:20 +01:00
NEZNAMY 0d2d1d5aaa Sponge 8 - Use direct code instead of reflection for entity teleport packet 2023-03-15 21:00:42 +01:00
NEZNAMY 061375d1e7 3.3.2-SNAPSHOT 2023-03-15 21:00:14 +01:00
NEZNAMY 5b05614305 Remove unused field 2023-03-15 20:13:29 +01:00
NEZNAMY 69db2e0e53 Use API style code for bossbars since packets were not used anymore 2023-03-15 20:03:10 +01:00
NEZNAMY 00ef364324 Use APIs for header/footer instead of packets where possible (rip bukkit) 2023-03-15 16:13:31 +01:00
NEZNAMY de68312b6b Use APIs for chat messages instead of packets 2023-03-15 14:36:25 +01:00
NEZNAMY ddac08bcbc Fix bungee compilation (did md5 break something?) 2023-03-15 14:31:59 +01:00
NEZNAMY 517e285bb3 Fix bossbar error on <1.9 from recode 2023-03-15 14:29:13 +01:00
NEZNAMY 81a08a6b51 3.3.1 2023-03-14 19:18:01 +01:00
NEZNAMY 4dfef1d032 1.19.4 support 2023-03-14 19:15:15 +01:00
NEZNAMY 5d38e28cae Extract more constants and add config option 2023-03-14 18:35:25 +01:00
NEZNAMY 66a12d10c4 Layout - Resend on server switch on Velocity 2023-03-14 17:27:27 +01:00
NEZNAMY 1a4027c8d5 Remove unused fields and methods 2023-03-14 16:15:55 +01:00
NEZNAMY 2a0243c51a RedisSupport - Add missing packet source 2023-03-14 15:24:13 +01:00
NEZNAMY 4851d09e70 More abstraction of unlimited nametag mode & packets it uses 2023-03-14 15:20:49 +01:00
NEZNAMY 3cdf3accfd Remove redundant checks 2023-03-14 10:46:27 +01:00
NEZNAMY fd1f094216 Layout - Small cleanup in code 2023-03-14 00:22:02 +01:00
NEZNAMY 25ece1ecb8 Move ViaVersion hook (and remove safety checks, hopefully it works correctly now) 2023-03-13 23:31:10 +01:00
NEZNAMY 802e65c280 Change constructor fields to getters in features 2023-03-13 23:04:22 +01:00
NEZNAMY 882688f056 Layout - Cache remove packet 2023-03-13 21:52:29 +01:00
NEZNAMY 97d60d0ab3 Bungee - Remove unused field (and simplify) 2023-03-13 21:33:48 +01:00
NEZNAMY 9ef99daed1 Revert feature loading as references were not null when they were supposed to be 2023-03-13 21:31:48 +01:00
NEZNAMY 71c85d9d39 NameTags - Speed up unloading 2023-03-13 19:36:05 +01:00
NEZNAMY b981ab46be Add debug messages about feature unloading times 2023-03-13 19:35:45 +01:00
NEZNAMY 016633e494 BelowName - Speed up reloads by caching values for players (tested with 100 bots, reloads were 2x faster) 2023-03-13 19:22:00 +01:00
NEZNAMY 1db862ab56 NameTags - Speed up reloading (tested with 100 bots, reload was 3x faster) 2023-03-13 19:15:45 +01:00
NEZNAMY 3563786310 PlayerList - Drastically speed up reloads (up to 10+ times with high player counts) 2023-03-13 19:01:26 +01:00
NEZNAMY ac8851dd6a Cache EnumSets for info packet for better performance 2023-03-13 18:22:16 +01:00
NEZNAMY 53ab2d9793 Commands - Do not load widths for widthcheck command unless command is used 2023-03-13 18:03:55 +01:00
NEZNAMY 2a58c86ffc Move code from initializer to load 2023-03-13 16:35:30 +01:00
NEZNAMY fa46d527ae Spectator Fix - Don't send useless packets on load/unload 2023-03-13 15:17:13 +01:00
NEZNAMY 667bd38305 Velocity - Fix Velocity being Velocity exception 2023-03-13 14:51:52 +01:00
NEZNAMY faba439466 Global PlayerList - Send all affected entries in a single packet, reducing amount of packets sent and improving performance 2023-03-13 14:47:29 +01:00
NEZNAMY c301157758 Global PlayerList - No longer delay task on quit as I was not able to reproduce the bug; Update placeholders periodically as they didn't update properly on vanish change anyway 2023-03-13 14:14:06 +01:00
NEZNAMY 8d09d425a4 Global PlayerList - No longer prevent other sources from removing players, better organize adding on server switch as well 2023-03-13 13:26:12 +01:00
NEZNAMY 11f03d5a10 Simplify 2023-03-13 12:58:19 +01:00
NEZNAMY 83625202a1 Merge pull request #841 from NEZNAMY/better-pipeline-injection
Better pipeline injection
2023-03-12 14:39:19 +01:00
Callum Seabrook eb4b715a88 Move pipeline injection to its own package 2023-03-12 13:35:47 +00:00
Callum Seabrook 89dce941cf Add RequiredArgsConstructor to NettyPipelineInjector 2023-03-12 13:31:58 +00:00
Callum Seabrook 6371b67e9e Removed player Channel and moved Netty injection to NettyPipelineInjector 2023-03-12 13:23:16 +00:00
NEZNAMY 966e27418a Bukkit - Attempt to fix error on ArrayList#remove 2023-03-11 15:35:31 +01:00
NEZNAMY 4adf7d1734 Bukkit - Fix NoSuchElementException that doesn't make sense 2023-03-11 15:25:06 +01:00
NEZNAMY 1a4c4a6fe8 Add more debug messages about feature loading times 2023-03-11 15:14:12 +01:00
NEZNAMY 7ff36592ae Do not re-initialize ErrorManager 2023-03-11 14:27:23 +01:00
NEZNAMY 1b513412d2 Remove redundant import 2023-03-10 13:20:14 +01:00
NEZNAMY f2bb43fbcd Make tabExpansion variable not null and add default empty implementation 2023-03-10 13:16:22 +01:00
NEZNAMY df5d07d885 Include forgotten code from previous commit 2023-03-10 13:13:21 +01:00
NEZNAMY 0ad2f33c4c Further clean up feature loading and mutual hooks 2023-03-10 12:45:28 +01:00
NEZNAMY dee48fccb3 Partially de-spaghetti feature loading and mutual hooking 2023-03-09 00:13:15 +01:00
NEZNAMY 9ca372ab13 Velocity is XD 2023-03-07 23:07:26 +01:00
NEZNAMY 58a849a902 Update README.md 2023-03-06 18:15:04 +01:00
NEZNAMY 895072ec35 Update README.md 2023-03-06 18:07:14 +01:00
NEZNAMY 2b2c4d2c39 Update README.md 2023-03-06 12:03:45 +01:00
NEZNAMY d534b6e2e8 Layout - Make fixed slots take priority over player groups if a slot is used in both 2023-03-05 15:31:14 +01:00
NEZNAMY 783e9d9931 Fix wrong values in widths.txt 2023-03-02 21:06:15 +01:00
NEZNAMY a109d9b55b Enable widthcheck command and fix error in it 2023-03-02 20:54:28 +01:00
NEZNAMY 5ce59ce44f Fix compilation 2023-03-02 20:33:37 +01:00
NEZNAMY 1e9195a9bc Velocity - Add try/catch for calling new methods on old versions 2023-03-01 18:49:41 +01:00
NEZNAMY 3099d16468 Create SECURITY.md 2023-03-01 12:23:39 +01:00
NEZNAMY c9c6af346c Add new issue templates 2023-03-01 12:18:23 +01:00
NEZNAMY af80a43628 Catch Throwable in pipeline instead of Exception to reduce chance of plugin causing player disconnect in future unsupported versions 2023-02-28 23:33:31 +01:00
NEZNAMY dc3c98ec91 Bukkit - Change how ViaVersion is detected (to avoid wrong detection due to a plugin order load bug in 1.17.1) 2023-02-25 18:41:25 +01:00
NEZNAMY dffa2c83f6 Typo 2023-02-24 23:45:40 +01:00
NEZNAMY dc66f253a7 Config converter - Remove placeholder that got deleted 2023-02-24 23:42:35 +01:00
NEZNAMY 17498cbfc2 BungeeCord - Fix bytebuf deserialization from previous commits 2023-02-24 23:15:01 +01:00
NEZNAMY 96e4d30168 Include 1.5 in supported version range 2023-02-24 16:48:56 +01:00
NEZNAMY c9d1934bb9 Use lombok for empty private constructor 2023-02-24 16:33:39 +01:00
NEZNAMY 8bec083194 Remove redundant duplicated checks 2023-02-24 16:30:34 +01:00
NEZNAMY abf047be23 Fix global playerlist class being instantiated twice 2023-02-24 16:22:02 +01:00
NEZNAMY 4a95399d56 3.3.1-SNAPSHOT 2023-02-24 16:19:01 +01:00
NEZNAMY 885bf5154e NameTags - Fix updating nametag visibility of players in disabled world/server 2023-02-24 15:43:58 +01:00
NEZNAMY 3e146fc496 Simplify 2023-02-24 15:41:30 +01:00
NEZNAMY 95710bcaf5 Sorting - Fix team name change registering teams for players in disabled worlds/servers 2023-02-24 15:37:40 +01:00
NEZNAMY edd620f17d BelowName - Fix text refreshing packets being sent to players in disabled worlds/servers 2023-02-24 15:25:09 +01:00
NEZNAMY 9c466d8b1e Update comment 2023-02-24 14:57:56 +01:00
NEZNAMY b385ab70fb Simplify 2023-02-24 14:52:22 +01:00
NEZNAMY 140224840b Remove double registration of sorting feature 2023-02-23 21:24:10 +01:00
NEZNAMY 2078cd5a5f Downgrade sponge API dependencies to confirm support 2023-02-21 18:46:43 +01:00
244 changed files with 7152 additions and 7543 deletions

No files matched your search

+56
View File
@@ -0,0 +1,56 @@
name: Memory leak
description: Memory leak caused by TAB.
labels: ["Type: Memory leak"]
body:
- type: input
attributes:
label: Server version
description: Version and type of used server software where TAB is installed.
placeholder: Spigot 1.18.1
validations:
required: true
- type: input
attributes:
label: TAB version
description: Full version in numbers, "latest" is not a version.
placeholder: 3.0.2
validations:
required: true
- type: textarea
attributes:
label: Heap dump
description: Link to uploaded memory dump which I can look into.
validations:
required: true
- type: textarea
attributes:
label: Uncleared object(s)
description: Objects you believe should have been removed from the memory but are not
placeholder: |
There is only 1 player online, but heap dump has 100 instances of TabPlayer
validations:
required: false
- type: textarea
attributes:
label: Additional info
description: Any other info you want to provide.
validations:
required: false
- type: checkboxes
attributes:
label: Checklist
description: Let's make sure this report is valid
options:
- label: I am running latest version of the plugin
required: true
- label: I have included download link of my heap dump
required: true
- label: I ticked all of the boxes without actually reading them
required: false
- label: I have explained what I believe is wrong with the heap dump
required: true
@@ -0,0 +1,55 @@
name: Performance issue
description: Performance issue in TAB or in server caused by TAB
labels: ["Type: Performance"]
body:
- type: input
attributes:
label: Server version
description: Version and type of used server software where TAB is installed.
placeholder: Spigot 1.18.1
validations:
required: true
- type: input
attributes:
label: TAB version
description: Full version in numbers, "latest" is not a version.
placeholder: 3.0.2
validations:
required: true
- type: textarea
attributes:
label: Evidence
description: Any evidence proving the performance issue, such as output of /tab cpu or timings.
validations:
required: true
- type: textarea
attributes:
label: Steps to reproduce (if known)
description: What to do in order to reproduce this issue
placeholder: |
Step 1: ..
Step 2: ..
validations:
required: false
- type: textarea
attributes:
label: Additional info
description: Any other info you want to provide.
validations:
required: false
- type: checkboxes
attributes:
label: Checklist
description: Let's make sure this report is valid
options:
- label: I am running latest version of the plugin
required: true
- label: I have included sufficient evidence of the issue
required: true
- label: I ticked all of the boxes without actually reading them
required: false
+59 -7
View File
@@ -7,18 +7,70 @@ More information can be found at [Why TAB?](https://github.com/NEZNAMY/TAB/wiki/
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.
customers who have purchased the plugin (see [Customer support](#customer-support)).
# Compiling
Compilation requires JDK 8 and up.
To compile the plugin, run `./gradlew build` from the terminal.
Once the plugin compiles, grab the jar from `/jar/build/libs/` folder.
Once the plugin compiles, grab the jar from `/jar/build/libs/` folder.
The universal jar contains all modules for all supported platforms.
# Links
Download the plugin for free: [GitHub releases](https://github.com/NEZNAMY/TAB/releases),
[BuiltByBit](https://builtbybit.com/resources/20631/), [Modrinth](https://modrinth.com/plugin/tab-was-taken)
Support my work & receive customer support: [BuiltByBit](https://builtbybit.com/resources/14009/), [Polymart](https://polymart.org/resource/484)
Other links: [Discord support for customers](https://discord.gg/EaSvdk6), [Wiki](https://github.com/NEZNAMY/TAB/wiki)
# Customer support
Unlike with other projects where requirements and level of support provided are unclear,
we provide a clear answer to what kind of service you will or will not receive. Some don't
even clearly state if they are free or paid. While they are listed as paid on spigot, they have
a free download link in description, which can be confusing.
Free plugins typically do have a support discord, however you will rarely receive
official support from the developers, especially for large projects with tons of users, where
helping everyone in the world (theoretically) would consume too much time, leaving authors
no time left to actually work on the project.
In the end, they are free, made by people in their free time, and they are allowed to choose
what they do or don't want to do in their free time. They often leave support to other people,
possibly staff members, who are, however, in the end just more volunteers who may not even know
the answer.
With paid plugins you are more likely to receive support, but unless explicitly said, nothing is
guaranteed. There are lots of paid plugins where authors don't provide any support or redirect
it to helpers, where again they are unpaid volunteers who might not even know the answer.
In TAB, everything is clearly defined. The plugin is free to download for everyone.
Customer support, however, is only provided to customers who have formally purchased a copy
of the plugin on any of the websites the plugin is being sold on:
* [BuiltByBit](https://builtbybit.com/resources/14009/) (accepts any payment method)
* [Polymart](https://polymart.org/resource/484) (only PayPal)
After purchasing, you can request assistance via either private messages on the website where you
have purchased it, or using [TAB's Discord server](https://discord.gg/EaSvdk6) (which is highly
preferred, since the communication will be much faster thanks to notifications and more).
* We use a ticket system, which allows us to effectively track any amount of unresolved problems.
No more questions getting lost in the sea of messages from other people asking. Tickets are only closed
after user confirmed all of their questions were answered and then receive a transcript of the ticket
from the bot after ticket is closed into private messages (unless user is blocking them).
* We do not rely on the community helping, which often makes things even worse. We know people have
better things to do than helping random people with a random plugin, especially when most people only
have a very basic knowledge of the plugin, not being able to answer more complicated problems.
* We have notifications enabled and reply as soon as possible. We do not have the server muted unlike
most mods on most servers who only check it when they are really bored, give a wiki link and
disappear again.
* As the developer, I do not rely on helpers providing support instead of me. They are handy as
I am obviously not available 24/7, but I do not intentionally leave anything to them. I reply
whenever I am available without any intentional delays.
The wiki contains everything about the plugin in one way or another, so support should not
even be needed. It also contains a few examples, but obviously cannot contain a pre-made config
for every possible idea anyone can come up with.
Support is provided to people who decided paying for support is more time efficient than reading the wiki,
especially for serious servers, where price of the plugin is nothing compared to other server costs.
# Other links
Download the plugin for free:
* [GitHub releases](https://github.com/NEZNAMY/TAB/releases)
* [BuiltByBit](https://builtbybit.com/resources/20631/)
* [Modrinth](https://modrinth.com/plugin/tab-was-taken)
[Wiki](https://github.com/NEZNAMY/TAB/wiki)
[Donate](https://paypal.me/neznamy1) if you are feeling extra generous
# Issues
The [Issues](https://github.com/NEZNAMY/TAB/issues) section is **only** available for cases included in templates.
+2
View File
@@ -0,0 +1,2 @@
# Security Policy
If you found a security issue, send me a private message anywhere.
+2
View File
@@ -12,6 +12,7 @@ dependencies {
compileOnlyApi(libs.adventureMiniMessage)
compileOnlyApi(libs.adventureApi)
compileOnlyApi(libs.adventureLegacy)
compileOnlyApi(libs.via)
}
blossom {
@@ -20,6 +21,7 @@ blossom {
replaceToken("@version@", project.version)
replaceToken("@description@", project.description)
replaceToken("@website@", rootProject.ext.get("website")!!.toString())
replaceToken("@author@", rootProject.ext.get("author")!!.toString())
replaceTokenIn("src/main/java/me/neznamy/tab/api/TabConstants.java")
}
@@ -34,41 +34,7 @@ public interface ArmorStand {
* @return property for armor stand's name
*/
Property getProperty();
/**
* Teleports armor stand to owner's current location for everyone in range
*/
void teleport();
/**
* Teleports armor stand to owner's current location for specified player
*
* @param viewer
* player to send packet to
*/
void teleport(TabPlayer viewer);
/**
* Changes sneaking flag and sends packets to everyone in range
*
* @param sneaking
* new sneaking status
*/
void sneak(boolean sneaking);
/**
* DeSpawns armor stand for everyone
*/
void destroy();
/**
* DeSpawns armor stand for specified player
*
* @param viewer
* player to send packet to
*/
void destroy(TabPlayer viewer);
/**
* Updates armor stand's name if needed
*/
@@ -88,21 +54,4 @@ public interface ArmorStand {
* @return entity ID of this armor stand
*/
int getEntityId();
/**
* Spawns armor stand for specified player
*
* @param viewer
* player to spawn for
*/
void spawn(TabPlayer viewer);
/**
* Sends respawn packets to specified player to make position
* change instant instead of teleport animation
*
* @param viewer
* Player to send armor stand respawn packets to
*/
void respawn(TabPlayer viewer);
}
@@ -0,0 +1,74 @@
package me.neznamy.tab.api;
import lombok.NonNull;
import me.neznamy.tab.api.bossbar.BarColor;
import me.neznamy.tab.api.bossbar.BarStyle;
import java.util.UUID;
public interface BossBarHandler {
/**
* Creates boss bar and sends it to the player
*
* @param id
* Unique identifier to match the bossbar with other functions
* @param title
* BossBar title
* @param progress
* BossBar progress (0-1)
* @param color
* BossBar color
* @param style
* BossBar style
*/
void create(@NonNull UUID id, @NonNull String title, float progress, @NonNull BarColor color, @NonNull BarStyle style);
/**
* Updates title
*
* @param id
* Unique identifier to match the bossbar with other functions
* @param title
* New title
*/
void update(@NonNull UUID id, @NonNull String title);
/**
* Updates progress
*
* @param id
* Unique identifier to match the bossbar with other functions
* @param progress
* New progress (0-1)
*/
void update(@NonNull UUID id, float progress);
/**
* Updates style
*
* @param id
* Unique identifier to match the bossbar with other functions
* @param style
* New style
*/
void update(@NonNull UUID id, @NonNull BarStyle style);
/**
* Updates color
*
* @param id
* Unique identifier to match the bossbar with other functions
* @param color
* New color
*/
void update(@NonNull UUID id, @NonNull BarColor color);
/**
* Removes boss bar
*
* @param id
* Unique identifier to match the bossbar with other functions
*/
void remove(@NonNull UUID id);
}
@@ -1,5 +1,7 @@
package me.neznamy.tab.api;
import me.neznamy.tab.api.feature.TabFeature;
import java.util.UUID;
/**
@@ -91,22 +93,6 @@ public interface FeatureManager {
*/
boolean onCommand(TabPlayer sender, String command);
/**
* Internal function that marks objective packet deserialization
* when at least 1 feature uses it.
* <p>
* Internal use only.
*/
void markObjective();
/**
* Internal function that marks display objective packet deserialization
* when at least 1 feature uses it.
* <p>
* Internal use only.
*/
void markDisplayObjective();
/**
* Returns array of all currently enabled features
*
@@ -1,8 +1,11 @@
package me.neznamy.tab.api;
import com.viaversion.viaversion.api.Via;
import lombok.Getter;
import lombok.Setter;
import java.util.UUID;
/**
* Enum class representing all possibly used protocol versions
*/
@@ -11,6 +14,7 @@ public enum ProtocolVersion {
UNKNOWN_SERVER_VERSION ("Unknown"),
UNKNOWN_CLIENT_VERSION ("Unknown"),
PROXY ("Proxy"),
V1_19_4 (762),
V1_19_3 (761),
V1_19_2 (760),
V1_19_1 (760),
@@ -137,4 +141,19 @@ public enum ProtocolVersion {
}
return UNKNOWN_CLIENT_VERSION;
}
/**
* Gets player's network version using ViaVersion API
*
* @param player
* Player's UUID
* @param playerName
* Player's name for debug messages
* @return Player's network version
*/
public static int getPlayerVersionVia(UUID player, String playerName) {
int version = Via.getAPI().getPlayerVersion(player);
TabAPI.getInstance().debug("ViaVersion returned protocol version " + version + " for " + playerName);
return version;
}
}
@@ -0,0 +1,57 @@
package me.neznamy.tab.api;
import lombok.NonNull;
import java.util.Collection;
public interface Scoreboard {
/**
* Sets objective display slot of specified objective
*
* @param slot
* Display slot
* @param objective
* Objective name
*/
void setDisplaySlot(DisplaySlot slot, @NonNull String objective);
/**
* Sets scoreboard score
* @param objective
* Objective name
* @param player
* Affected player
* @param score
* New score value
*/
void setScore(@NonNull String objective, @NonNull String player, int score);
/**
* Removes scoreboard score
*
* @param objective
* Objective to remove from
* @param player
* Player to remove from sidebar
*/
void removeScore(@NonNull String objective, @NonNull String player);
void registerObjective(@NonNull String objectiveName, @NonNull String title, boolean hearts);
void unregisterObjective(@NonNull String objectiveName);
void updateObjective(@NonNull String objectiveName, @NonNull String title, boolean hearts);
void registerTeam(@NonNull String name, String prefix, String suffix, String visibility,
String collision, Collection<String> players, int options);
void unregisterTeam(@NonNull String name);
void updateTeam(@NonNull String name, String prefix, String suffix, String visibility, String collision, int options);
enum DisplaySlot {
PLAYER_LIST, SIDEBAR, BELOW_NAME
}
}
@@ -7,7 +7,6 @@ import me.neznamy.tab.api.config.ConfigurationFile;
import me.neznamy.tab.api.event.EventBus;
import me.neznamy.tab.api.placeholder.PlaceholderManager;
import me.neznamy.tab.api.scoreboard.ScoreboardManager;
import me.neznamy.tab.api.task.ThreadManager;
import me.neznamy.tab.api.team.TeamManager;
/**
@@ -143,13 +142,6 @@ public abstract class TabAPI {
*/
public abstract void sendConsoleMessage(String message, boolean translateColors);
/**
* Returns TAB's Thread manager, which allows task submitting
*
* @return ThreadManager instance
*/
public abstract ThreadManager getThreadManager();
/**
* Returns TAB's cache file used to store player toggle data
*
@@ -15,6 +15,7 @@ public class TabConstants {
public static final String PLUGIN_VERSION = "@version@";
public static final String PLUGIN_DESCRIPTION = "@description@";
public static final String PLUGIN_WEBSITE = "@website@";
public static final String PLUGIN_AUTHOR = "@author@";
public static final String NO_GROUP = "NONE";
public static final String DEFAULT_GROUP = "_DEFAULT_";
@@ -62,42 +63,6 @@ public class TabConstants {
public static final String TELEPORTING_WITHER = "Teleporting wither";
}
/**
* Information why a specific feature sent a packet
* and what the packet sent is supposed to do.
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public static class PacketCategory {
public static final String BOSSBAR_COLOR_STYLE = "BossBar (Color and style)";
public static final String BOSSBAR_PROGRESS = "BossBar (Progress)";
public static final String BOSSBAR_TEXT = "BossBar (Text)";
public static final String BOSSBAR_WITHER_TELEPORT = "BossBar (Teleporting Wither)";
public static final String GLOBAL_PLAYERLIST_LATENCY = "Global PlayerList (Updating latency)";
public static final String GLOBAL_PLAYERLIST_VANISH = "Global PlayerList (Vanishing)";
public static final String LAYOUT_FIXED_SLOTS = "Layout (Fixed slots)";
public static final String LAYOUT_PLAYER_SLOTS = "Layout (Player slots)";
public static final String LAYOUT_LATENCY = "Layout (Updating latency)";
public static final String NAMETAGS_TEAM_REGISTER = "NameTags (Team register)";
public static final String NAMETAGS_TEAM_UNREGISTER = "NameTags (Team unregister)";
public static final String NAMETAGS_TEAM_UPDATE = "NameTags (Team update)";
public static final String SORTING_CHANGING_TEAM_NAME = "Sorting (Changing team name)";
public static final String SCOREBOARD_TITLE = "Scoreboard (Title)";
public static final String SCOREBOARD_LINES = "Scoreboard (Lines)";
public static final String UNLIMITED_NAMETAGS_OFFSET_CHANGE = "Unlimited NameTags (Changing offset)";
public static final String UNLIMITED_NAMETAGS_SPAWN = "Unlimited NameTags (Spawning)";
public static final String UNLIMITED_NAMETAGS_DESPAWN = "Unlimited NameTags (Despawning)";
public static final String UNLIMITED_NAMETAGS_TELEPORT = "Unlimited NameTags (Teleporting)";
public static final String UNLIMITED_NAMETAGS_SNEAK = "Unlimited NameTags (Sneaking)";
public static final String UNLIMITED_NAMETAGS_METADATA = "Unlimited NameTags (Metadata)";
}
/**
* Permission nodes used by the plugin
*/
@@ -272,8 +237,12 @@ public class TabConstants {
return "BossBar-progress-" + name;
}
public static String bossBarColorStyle(String name) {
return "BossBar-color-style-" + name;
public static String bossBarColor(String color) {
return "BossBar-color-" + color;
}
public static String bossBarStyle(String style) {
return "BossBar-style-" + style;
}
}
@@ -347,6 +316,7 @@ public class TabConstants {
public static final String VAULT = "Vault";
public static final String PLACEHOLDER_API = "PlaceholderAPI";
public static final String LIBS_DISGUISES = "LibsDisguises";
public static final String VIAREWIND = "ViaRewind";
public static final String VIAVERSION = "ViaVersion";
public static final String PROTOCOL_SUPPORT = "ProtocolSupport";
public static final String ESSENTIALS = "Essentials";
@@ -1,391 +0,0 @@
package me.neznamy.tab.api;
import java.util.*;
import lombok.Getter;
import me.neznamy.tab.api.protocol.PacketPlayOutPlayerInfo;
import me.neznamy.tab.api.protocol.PacketPlayOutScoreboardDisplayObjective;
import me.neznamy.tab.api.protocol.PacketPlayOutScoreboardObjective;
/**
* Abstract class representing a core feature of the plugin.
* <p>
* It receives all kinds of events and can react to them.
*/
public abstract class TabFeature {
/** Feature's name displayed in /tab cpu */
@Getter private final String featureName;
/** Feature's function name displayed in place of refreshing in /tab cpu */
@Getter private final String refreshDisplayName;
/** Servers where the feature is disabled (or enabled if using whitelist mode) */
protected String[] disabledServers = new String[0];
/** Flag tracking whether disabled server list is a whitelist or blacklist */
private boolean serverWhitelistMode = false;
/** Worlds where the feature is disabled (or enabled if using whitelist mode) */
protected String[] disabledWorlds = new String[0];
/** Flag tracking whether disabled world list is a whitelist or blacklist */
private boolean worldWhitelistMode = false;
/** Players located in currently disabled worlds / servers */
private final Set<TabPlayer> disabledPlayers = Collections.newSetFromMap(new WeakHashMap<>());
/**
* List of methods overridden (implemented) by each feature.
* If a method is not overridden, it is not called at all.
* This avoids a massive spam for every single method
* in every single feature in /tab cpu output
*/
private final List<String> methodOverrides = new ArrayList<>();
/**
* Constructs new instance with given parameters and loads method overrides
*
* @param featureName
* Feature's name in /tab cpu
* @param refreshDisplayName
* "refreshing" cpu display type name of the feature
*/
protected TabFeature(String featureName, String refreshDisplayName) {
this.featureName = featureName;
this.refreshDisplayName = refreshDisplayName;
try {
if (getClass().getMethod("onCommand", TabPlayer.class, String.class).getDeclaringClass() != TabFeature.class)
methodOverrides.add("onCommand");
if (getClass().getMethod("onJoin", TabPlayer.class).getDeclaringClass() != TabFeature.class)
methodOverrides.add("onJoin");
if (getClass().getMethod("onQuit", TabPlayer.class).getDeclaringClass() != TabFeature.class)
methodOverrides.add("onQuit");
if (getClass().getMethod("onWorldChange", TabPlayer.class, String.class, String.class).getDeclaringClass() != TabFeature.class)
methodOverrides.add("onWorldChange");
if (getClass().getMethod("onServerChange", TabPlayer.class, String.class, String.class).getDeclaringClass() != TabFeature.class)
methodOverrides.add("onServerChange");
if (getClass().getMethod("onDisplayObjective", TabPlayer.class, PacketPlayOutScoreboardDisplayObjective.class).getDeclaringClass() != TabFeature.class) {
methodOverrides.add("onDisplayObjective");
TabAPI.getInstance().getFeatureManager().markDisplayObjective();
}
if (getClass().getMethod("onLoginPacket", TabPlayer.class).getDeclaringClass() != TabFeature.class)
methodOverrides.add("onLoginPacket");
if (getClass().getMethod("onObjective", TabPlayer.class, PacketPlayOutScoreboardObjective.class).getDeclaringClass() != TabFeature.class) {
methodOverrides.add("onObjective");
TabAPI.getInstance().getFeatureManager().markObjective();
}
if (getClass().getMethod("onPlayerInfo", TabPlayer.class, PacketPlayOutPlayerInfo.class).getDeclaringClass() != TabFeature.class)
methodOverrides.add("onPlayerInfo");
if (getClass().getMethod("onPacketReceive", TabPlayer.class, Object.class).getDeclaringClass() != TabFeature.class)
methodOverrides.add("onPacketReceive");
if (getClass().getMethod("onPacketSend", TabPlayer.class, Object.class).getDeclaringClass() != TabFeature.class)
methodOverrides.add("onPacketSend");
if (getClass().getMethod("refresh", TabPlayer.class, boolean.class).getDeclaringClass() != TabFeature.class)
methodOverrides.add("refresh");
if (getClass().getMethod("onVanishStatusChange", TabPlayer.class).getDeclaringClass() != TabFeature.class)
methodOverrides.add("onVanishStatusChange");
} catch (NoSuchMethodException e) {
//this will never happen
}
}
/**
* Constructs new instance with given parameters and loads method overrides.
* Also loads lists of disabled worlds and servers of this feature with config
* section path specified with {@code configSection} parameter.
*
* @param featureName
* Feature's name in /tab cpu
* @param refreshDisplayName
* "refreshing" cpu display type name of the feature
* @param configSection
* Configuration section of the feature to load disabled
* servers / worlds from
*/
protected TabFeature(String featureName, String refreshDisplayName, String configSection) {
this(featureName, refreshDisplayName);
List<String> disabledServers = TabAPI.getInstance().getConfig().getStringList(configSection + ".disable-in-servers");
List<String> disabledWorlds = TabAPI.getInstance().getConfig().getStringList(configSection + ".disable-in-worlds");
if (disabledServers != null) {
this.disabledServers = disabledServers.toArray(new String[0]);
serverWhitelistMode = disabledServers.contains("WHITELIST");
}
if (disabledWorlds != null) {
this.disabledWorlds = disabledWorlds.toArray(new String[0]);
worldWhitelistMode = disabledWorlds.contains("WHITELIST");
}
}
/**
* Loads all online players and sends packets
*/
public void load() {
//empty by default
}
/**
* Unloads all players and sends clear packets
*/
public void unload() {
//empty by default
}
/**
* Processes command from player. This is typically a toggle command.
*
* @param sender
* command sender
* @param message
* command line
* @return {@code true} if event should be cancelled, {@code true} if not
*/
public boolean onCommand(TabPlayer sender, String message) {
return false;
}
/**
* Processes join event
*
* @param connectedPlayer
* player who connected
*/
public void onJoin(TabPlayer connectedPlayer) {
//empty by default
}
/**
* Processes quit event
*
* @param disconnectedPlayer
* player who disconnected
*/
public void onQuit(TabPlayer disconnectedPlayer) {
//empty by default
}
/**
* Processes world switch
*
* @param changed
* player who switched world
* @param from
* world player changed from
* @param to
* world player changed to
*/
public void onWorldChange(TabPlayer changed, String from, String to) {
//empty by default
}
/**
* Processes server switch
*
* @param changed
* player who switched server
* @param from
* server player changed from
* @param to
* server player changed to
*/
public void onServerChange(TabPlayer changed, String from, String to) {
//empty by default
}
/**
* Processes the packet send and returns true if packet should be cancelled
*
* @param receiver
* player receiving packet
* @param packet
* received packet
*/
public void onDisplayObjective(TabPlayer receiver, PacketPlayOutScoreboardDisplayObjective packet) {
//empty by default
}
/**
* Processes login packet, only available on BungeeCord
*
* @param packetReceiver
* player receiving client reset packet
*/
public void onLoginPacket(TabPlayer packetReceiver) {
//empty by default
}
/**
* Processes the packet send
*
* @param receiver
* player receiving packet
* @param packet
* received packet
*/
public void onObjective(TabPlayer receiver, PacketPlayOutScoreboardObjective packet) {
//empty by default
}
/**
* Processes the packet send and possibly modifies it
*
* @param receiver
* player receiving packet
* @param info
* received packet
*/
public void onPlayerInfo(TabPlayer receiver, PacketPlayOutPlayerInfo info) {
//empty by default
}
/**
* Processes raw packet sent by client
*
* @param sender
* packet sender
* @param packet
* packet received
* @return true if false should be cancelled, false if not
* @throws ReflectiveOperationException
* if thrown by reflective operation
*/
public boolean onPacketReceive(TabPlayer sender, Object packet) throws ReflectiveOperationException {
return false;
}
/**
* Processes raw packet sent to client
*
* @param receiver
* packet receiver
* @param packet
* the packet
* @throws ReflectiveOperationException
* if thrown by reflective operation
*/
public void onPacketSend(TabPlayer receiver, Object packet) throws ReflectiveOperationException {
//empty by default
}
/**
* Performs refresh of specified player
*
* @param refreshed
* player to refresh
* @param force
* if refresh should be forced despite refresh seemingly not needed
*/
public void refresh(TabPlayer refreshed, boolean force) {
//empty by default
}
/**
* Processes vanish status change of player
*
* @param player
* Player who changed vanish status
*/
public void onVanishStatusChange(TabPlayer player) {
//empty by default
}
/**
* Registers this feature as one using specified placeholders
*
* @param placeholders
* placeholders to add as used in this feature
*/
public void addUsedPlaceholders(Collection<String> placeholders) {
if (placeholders.isEmpty()) return;
placeholders.forEach(p -> TabAPI.getInstance().getPlaceholderManager().addUsedPlaceholder(p, this));
}
/**
* Returns {@code true} if world or server is disabled, {@code false} if not
*
* @param server
* server to check
* @param world
* world to check
* @return {@code true} if feature should be disabled, {@code false} if not
*/
public boolean isDisabled(String server, String world) {
boolean contains = contains(disabledWorlds, world);
if (worldWhitelistMode) contains = !contains;
if (contains) return true;
contains = contains(disabledServers, server);
if (serverWhitelistMode) contains = !contains;
return contains;
}
/**
* Returns {@code true} if list contains the specified element or element
* ends with {@code "*"} and element meeting that requirement is present,
* {@code false} otherwise.
*
* @param list
* List to check
* @param element
* Element to find
* @return {@code true} if element was found, {@code false} if not
*/
protected boolean contains(String[] list, String element) {
if (element == null) return false;
for (String s : list) {
if (s.endsWith("*")) {
if (element.toLowerCase().startsWith(s.substring(0, s.length()-1).toLowerCase())) return true;
} else {
if (element.equalsIgnoreCase(s)) return true;
}
}
return false;
}
/**
* Returns {@code true} if method with specified name is overridden
* in the implementation, {@code false} if not
*
* @param method
* Method to check
* @return {@code true} if overridden, {@code false} if not
*/
public boolean overridesMethod(String method) {
return methodOverrides.contains(method);
}
/**
* Returns {@code true} if player is currently located in a server
* or world, which is marked as disabled, {@code false} if not
*
* @param p
* Player to check
* @return {@code true} if player is in disabled server / world,
* {@code false} if not
*/
public boolean isDisabledPlayer(TabPlayer p) {
return disabledPlayers.contains(p);
}
/**
* Adds specified player into list of players in disabled
* servers / worlds.
*
* @param p
* Player to add
*/
public void addDisabledPlayer(TabPlayer p) {
if (disabledPlayers.contains(p)) return;
disabledPlayers.add(p);
}
/**
* Removes specified player from list of players in disabled
* servers / worlds
*
* @param p
* Player to check
*/
public void removeDisabledPlayer(TabPlayer p) {
disabledPlayers.remove(p);
}
}
@@ -2,10 +2,11 @@ package me.neznamy.tab.api;
import java.util.UUID;
import io.netty.channel.Channel;
import lombok.NonNull;
import me.neznamy.tab.api.chat.IChatBaseComponent;
import me.neznamy.tab.api.protocol.Skin;
import me.neznamy.tab.api.protocol.TabPacket;
import me.neznamy.tab.api.feature.Refreshable;
import me.neznamy.tab.api.tablist.Skin;
import me.neznamy.tab.api.tablist.TabList;
/**
* An interface representing a player
@@ -76,36 +77,6 @@ public interface TabPlayer {
*/
boolean hasPermission(String permission);
/**
* Sends the player a custom universal packet
*
* @param packet
* packet to send
*/
void sendCustomPacket(TabPacket packet);
/**
* Sends the player a custom universal packet and adds that packet into counter that
* is displayed in /tab cpu
*
* @param packet
* packet to send
* @param feature
* feature to increment sent packet counter of
*/
void sendCustomPacket(TabPacket packet, TabFeature feature);
/**
* Sends the player a custom universal packet and adds that packet into counter that
* is displayed in /tab cpu
*
* @param packet
* packet to send
* @param feature
* feature to increment sent packet counter of
*/
void sendCustomPacket(TabPacket packet, String feature);
/**
* Sends the player a platform-specific packet
*
@@ -141,13 +112,6 @@ public interface TabPlayer {
*/
void sendMessage(IChatBaseComponent message);
/**
* Returns player's netty channel. On servers running version 1.7 or older returns {@code null}.
*
* @return player's channel
*/
Channel getChannel();
/**
* Returns player's ping calculated by server
*
@@ -182,7 +146,7 @@ public interface TabPlayer {
* new raw value
* @return {@code true} if value changed / did not exist, {@code false} if value did not change
*/
boolean setProperty(TabFeature feature, String identifier, String rawValue);
boolean setProperty(Refreshable feature, String identifier, String rawValue);
/**
* Loads property from config using standard property loading algorithm
@@ -191,7 +155,7 @@ public interface TabPlayer {
* property name to load
* @return {@code true} if value did not exist or changed, {@code false} otherwise
*/
boolean loadPropertyFromConfig(TabFeature feature, String property);
boolean loadPropertyFromConfig(Refreshable feature, String property);
/**
* Loads property from config using standard property loading algorithm. If the property is
@@ -203,7 +167,7 @@ public interface TabPlayer {
* value to use if property is not defined in config
* @return {@code true} if value did not exist or changed, {@code false} otherwise
*/
boolean loadPropertyFromConfig(TabFeature feature, String property, String ifNotSet);
boolean loadPropertyFromConfig(Refreshable feature, String property, String ifNotSet);
/**
* Returns name of player's scoreboard team or {@code null} if NameTag feature is disabled
@@ -303,4 +267,33 @@ public interface TabPlayer {
* @return player's game profile name as seen by other players
*/
String getNickname();
/**
* Sets header and footer to specified values
*
* @param header
* Header to use
* @param footer
* Footer to use
*/
void setPlayerListHeaderFooter(@NonNull IChatBaseComponent header, @NonNull IChatBaseComponent footer);
/**
* Returns scoreboard interface for calling scoreboard-related methods
*
* @return scoreboard interface for calling scoreboard-related methods
*/
Scoreboard getScoreboard();
/**
* Returns TabList interface for calling tablist-related methods
* @return TabList interface for calling tablist-related methods
*/
TabList getTabList();
/**
* Returns handler for calling bossbar-related methods
* @return handler for calling bossbar-related methods
*/
BossBarHandler getBossBarHandler();
}
@@ -161,18 +161,4 @@ public class RGBUtils {
}
return true;
}
/**
* Returns true if text contains legacy color request at defined RGB index start
*
* @param text
* text to check
* @param i
* current index start
* @return {@code true} if legacy color is defined and valid, {@code false} otherwise
*/
private static boolean containsLegacyCode(String text, int i) {
if (text.length() - i < 9 || text.charAt(i+7) != '|') return false;
return EnumChatFormat.getByChar(text.charAt(i+8)) != null;
}
}
@@ -0,0 +1,21 @@
package me.neznamy.tab.api.feature;
import me.neznamy.tab.api.TabPlayer;
/**
* Interface for features listening to command preprocess event
* possibly cancelling it.
*/
public interface CommandListener {
/**
* Called when player is about to run a command
*
* @param sender
* Command sender
* @param message
* Command line including /
* @return Whether the command should be cancelled or not
*/
boolean onCommand(TabPlayer sender, String message);
}
@@ -0,0 +1,25 @@
package me.neznamy.tab.api.feature;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.api.chat.IChatBaseComponent;
import java.util.UUID;
/**
* Interface for features listening to display name change
*/
public interface DisplayNameListener {
/**
* Called when player is about to receive a packet for changing display name
*
* @param packetReceiver
* Player who is about to receive the packet
* @param id
* Affected entry
* @param displayName
* Display name in the packet
* @return New display name to write to the packet
*/
IChatBaseComponent onDisplayNameChange(TabPlayer packetReceiver, UUID id, IChatBaseComponent displayName);
}
@@ -0,0 +1,21 @@
package me.neznamy.tab.api.feature;
import me.neznamy.tab.api.TabPlayer;
/**
* Listener for features listening to display objective packet.
*/
public interface DisplayObjectiveListener {
/**
* Called when player is about to receive display objective packet
*
* @param receiver
* Packet receiver
* @param slot
* Objective slot
* @param objective
* Objective name
*/
void onDisplayObjective(TabPlayer receiver, int slot, String objective);
}
@@ -0,0 +1,23 @@
package me.neznamy.tab.api.feature;
import me.neznamy.tab.api.TabPlayer;
import java.util.UUID;
/**
* Interface for features listening to tablist entry add
*/
public interface EntryAddListener {
/**
* Called when player is about to receive packet for adding a tablist entry
*
* @param packetReceiver
* Player who received the packet
* @param id
* UUID of added player
* @param name
* Username of added player
*/
void onEntryAdd(TabPlayer packetReceiver, UUID id, String name);
}
@@ -0,0 +1,24 @@
package me.neznamy.tab.api.feature;
import me.neznamy.tab.api.TabPlayer;
import java.util.UUID;
/**
* Interface for features listening to gamemode change packet
*/
public interface GameModeListener {
/**
* Called when player receives packet to change gamemode
*
* @param packetReceiver
* Player who received the packet
* @param id
* UUID of affected player
* @param gameMode
* gamemode in the packet
* @return New gamemode to write to the packet
*/
int onGameModeChange(TabPlayer packetReceiver, UUID id, int gameMode);
}
@@ -0,0 +1,18 @@
package me.neznamy.tab.api.feature;
import me.neznamy.tab.api.TabPlayer;
/**
* Interface for features listening to player join event
*/
public interface JoinListener {
/**
* Called when player connects to the server. The player is
* already added to online player list.
*
* @param connectedPlayer
* Player who connected
*/
void onJoin(TabPlayer connectedPlayer);
}
@@ -0,0 +1,24 @@
package me.neznamy.tab.api.feature;
import me.neznamy.tab.api.TabPlayer;
import java.util.UUID;
/**
* Called when a packet to change player's latency is sent.
*/
public interface LatencyListener {
/**
* Called when a player receives a packet for latency change.
*
* @param packetReceiver
* Player who received the packet
* @param id
* UUID of affected player
* @param latency
* Latency in the packet
* @return New latency
*/
int onLatencyChange(TabPlayer packetReceiver, UUID id, int latency);
}
@@ -0,0 +1,12 @@
package me.neznamy.tab.api.feature;
/**
* Interface for features looking to load players on plugin enable
*/
public interface Loadable {
/**
* Called on plugin (re)load
*/
void load();
}
@@ -0,0 +1,18 @@
package me.neznamy.tab.api.feature;
import me.neznamy.tab.api.TabPlayer;
/**
* Interface for features listening to Login packet
* usually sent by proxy on server switch
*/
public interface LoginPacketListener {
/**
* Called when player receives Login packet
*
* @param packetReceiver
* Player who received the packet
*/
void onLoginPacket(TabPlayer packetReceiver);
}
@@ -0,0 +1,21 @@
package me.neznamy.tab.api.feature;
import me.neznamy.tab.api.TabPlayer;
/**
* Listener for features listening to objective packet.
*/
public interface ObjectiveListener {
/**
* Called when player is about to receive display objective packet
*
* @param receiver
* Packet receiver
* @param action
* Packet action (0 = register, 1 = unregister, 2 = update)
* @param objective
* Objective name
*/
void onObjective(TabPlayer receiver, int action, String objective);
}
@@ -0,0 +1,22 @@
package me.neznamy.tab.api.feature;
import me.neznamy.tab.api.TabPlayer;
/**
* Interface for features listening to packets sent by players
*/
public interface PacketReceiveListener {
/**
* Called when a player is about to send a packet to the server
*
* @param sender
* Packet sender
* @param packet
* The packet
* @return Whether the packet should be cancelled or not
* @throws ReflectiveOperationException
* If thrown by reflective operation
*/
boolean onPacketReceive(TabPlayer sender, Object packet) throws ReflectiveOperationException;
}
@@ -0,0 +1,21 @@
package me.neznamy.tab.api.feature;
import me.neznamy.tab.api.TabPlayer;
/**
* Interface for features listening to packets sent to players
*/
public interface PacketSendListener {
/**
* Called when a player is about to receive a packet from the server
*
* @param receiver
* Player who is about to receive a packet
* @param packet
* The packet
* @throws ReflectiveOperationException
* If thrown by reflective operation
*/
void onPacketSend(TabPlayer receiver, Object packet) throws ReflectiveOperationException;
}
@@ -0,0 +1,18 @@
package me.neznamy.tab.api.feature;
import me.neznamy.tab.api.TabPlayer;
/**
* Interface for features listening to player quit event
*/
public interface QuitListener {
/**
* Called when player disconnected from the server. The player is
* still present in online player list.
*
* @param disconnectedPlayer
* Player who disconnected
*/
void onQuit(TabPlayer disconnectedPlayer);
}
@@ -0,0 +1,48 @@
package me.neznamy.tab.api.feature;
import me.neznamy.tab.api.TabAPI;
import me.neznamy.tab.api.TabPlayer;
import java.util.Collection;
/**
* Interface for features periodically refreshing visuals
*/
public interface Refreshable {
/**
* Called when a placeholder used by this feature changes value
*
* @param refreshed
* Player which a placeholder changed value for
* @param force
* Whether refresh should be forced
*/
void refresh(TabPlayer refreshed, boolean force);
/**
* Returns display name of {@link #refresh(TabPlayer, boolean)}
* called for this feature in /tab cpu
*
* @return Display name of refresh function in this feature
*/
String getRefreshDisplayName();
/**
* Registers this feature as one using specified placeholders
*
* @param placeholders
* placeholders to add as used in this feature
*/
default void addUsedPlaceholders(Collection<String> placeholders) {
if (placeholders.isEmpty()) return;
placeholders.forEach(p -> TabAPI.getInstance().getPlaceholderManager().addUsedPlaceholder(p, this));
}
/**
* Returns name of this feature displayed in /tab cpu
*
* @return name of this feature display in /tab cpu
*/
String getFeatureName();
}
@@ -0,0 +1,21 @@
package me.neznamy.tab.api.feature;
import me.neznamy.tab.api.TabPlayer;
/**
* Interface for features listening to players switching servers
*/
public interface ServerSwitchListener {
/**
* Called when player switched server
*
* @param changed
* Player who changed server
* @param from
* Name of previous server
* @param to
* Name of new server
*/
void onServerChange(TabPlayer changed, String from, String to);
}
@@ -0,0 +1,136 @@
package me.neznamy.tab.api.feature;
import java.util.*;
import lombok.NonNull;
import me.neznamy.tab.api.TabAPI;
import me.neznamy.tab.api.TabPlayer;
/**
* Abstract class representing a core feature of the plugin.
*/
public abstract class TabFeature {
/** Servers where the feature is disabled (or enabled if using whitelist mode) */
protected String[] disabledServers = new String[0];
/** Flag tracking whether disabled server list is a whitelist or blacklist */
private boolean serverWhitelistMode = false;
/** Worlds where the feature is disabled (or enabled if using whitelist mode) */
protected String[] disabledWorlds = new String[0];
/** Flag tracking whether disabled world list is a whitelist or blacklist */
private boolean worldWhitelistMode = false;
/** Players located in currently disabled worlds / servers */
private final Set<TabPlayer> disabledPlayers = Collections.newSetFromMap(new WeakHashMap<>());
protected TabFeature() {}
/**
* Constructs new instance with given parameters and loads method overrides.
* Also loads lists of disabled worlds and servers of this feature with config
* section path specified with {@code configSection} parameter.
*
* @param configSection
* Configuration section of the feature to load disabled
* servers / worlds from
*/
protected TabFeature(@NonNull String configSection) {
List<String> disabledServers = TabAPI.getInstance().getConfig().getStringList(configSection + ".disable-in-servers");
List<String> disabledWorlds = TabAPI.getInstance().getConfig().getStringList(configSection + ".disable-in-worlds");
if (disabledServers != null) {
this.disabledServers = disabledServers.toArray(new String[0]);
serverWhitelistMode = disabledServers.contains("WHITELIST");
}
if (disabledWorlds != null) {
this.disabledWorlds = disabledWorlds.toArray(new String[0]);
worldWhitelistMode = disabledWorlds.contains("WHITELIST");
}
}
/**
* Returns {@code true} if world or server is disabled, {@code false} if not
*
* @param server
* server to check
* @param world
* world to check
* @return {@code true} if feature should be disabled, {@code false} if not
*/
public boolean isDisabled(String server, String world) {
boolean contains = contains(disabledWorlds, world);
if (worldWhitelistMode) contains = !contains;
if (contains) return true;
contains = contains(disabledServers, server);
if (serverWhitelistMode) contains = !contains;
return contains;
}
/**
* Returns {@code true} if list contains the specified element or element
* ends with {@code "*"} and element meeting that requirement is present,
* {@code false} otherwise.
*
* @param list
* List to check
* @param element
* Element to find
* @return {@code true} if element was found, {@code false} if not
*/
protected boolean contains(String[] list, String element) {
if (element == null) return false;
for (String s : list) {
if (s.endsWith("*")) {
if (element.toLowerCase().startsWith(s.substring(0, s.length()-1).toLowerCase())) return true;
} else {
if (element.equalsIgnoreCase(s)) return true;
}
}
return false;
}
/**
* Returns {@code true} if player is currently located in a server
* or world, which is marked as disabled, {@code false} if not
*
* @param p
* Player to check
* @return {@code true} if player is in disabled server / world,
* {@code false} if not
*/
public boolean isDisabledPlayer(TabPlayer p) {
return disabledPlayers.contains(p);
}
/**
* Adds specified player into list of players in disabled
* servers / worlds.
*
* @param p
* Player to add
*/
public void addDisabledPlayer(TabPlayer p) {
if (disabledPlayers.contains(p)) return;
disabledPlayers.add(p);
}
/**
* Removes specified player from list of players in disabled
* servers / worlds
*
* @param p
* Player to check
*/
public void removeDisabledPlayer(TabPlayer p) {
disabledPlayers.remove(p);
}
/**
* Returns name of this feature displayed in /tab cpu
*
* @return name of this feature display in /tab cpu
*/
public abstract String getFeatureName();
}
@@ -0,0 +1,12 @@
package me.neznamy.tab.api.feature;
/**
* Interface for features looking to unload players on plugin disable
*/
public interface UnLoadable {
/**
* Called on plugin unload
*/
void unload();
}
@@ -0,0 +1,18 @@
package me.neznamy.tab.api.feature;
import me.neznamy.tab.api.TabPlayer;
/**
* Interface for features listening to vanish status change
*/
public interface VanishListener {
/**
* Called when player's vanish status changes. New status
* is available in {@link TabPlayer#isVanished()}.
*
* @param player
* Player who changed vanish status
*/
void onVanishStatusChange(TabPlayer player);
}
@@ -0,0 +1,21 @@
package me.neznamy.tab.api.feature;
import me.neznamy.tab.api.TabPlayer;
/**
* Interface for features listening to players switching worlds
*/
public interface WorldSwitchListener {
/**
* Called when player switched world
*
* @param changed
* Player who changed world
* @param from
* Name of previous world
* @param to
* Name of new world
*/
void onWorldChange(TabPlayer changed, String from, String to);
}
@@ -5,10 +5,10 @@ import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;
import me.neznamy.tab.api.TabFeature;
import me.neznamy.tab.api.feature.Refreshable;
import me.neznamy.tab.api.TabPlayer;
public interface PlaceholderManager {
public interface PlaceholderManager extends Refreshable {
/**
* Registers a server placeholder (placeholder with same output for all players)
@@ -43,7 +43,7 @@ public interface PlaceholderManager {
* @param feature
* feature using the placeholder
*/
void addUsedPlaceholder(String identifier, TabFeature feature);
void addUsedPlaceholder(String identifier, Refreshable feature);
String findReplacement(String placeholder, String output);
@@ -1,255 +0,0 @@
package me.neznamy.tab.api.protocol;
import lombok.NonNull;
import me.neznamy.tab.api.ProtocolVersion;
import me.neznamy.tab.api.chat.EnumChatFormat;
import me.neznamy.tab.api.chat.IChatBaseComponent;
import me.neznamy.tab.api.chat.rgb.RGBUtils;
import me.neznamy.tab.api.util.BiFunctionWithException;
import java.util.HashMap;
import java.util.Map;
/**
* A class for packet building, methods are overridden in
* platform-specific builders which extend this class to build actual packets.
* Packets which don't have a method override are forwarded into
* sendPacket method, where they are handled using platform's API instead.
*/
public class PacketBuilder {
/** Function map turning custom packet class into platform-specific packets */
protected final Map<Class<? extends TabPacket>, BiFunctionWithException<TabPacket, ProtocolVersion, Object>> buildMap = new HashMap<>();
/**
* Constructs new instance and fills {@link #buildMap} with abstract build methods
* of packets present on all platforms.
*/
public PacketBuilder() {
buildMap.put(PacketPlayOutBoss.class, (packet, version) -> build((PacketPlayOutBoss)packet, version));
buildMap.put(PacketPlayOutChat.class, (packet, version) -> build((PacketPlayOutChat)packet, version));
buildMap.put(PacketPlayOutPlayerInfo.class, (packet, version) -> build((PacketPlayOutPlayerInfo)packet, version));
buildMap.put(PacketPlayOutPlayerListHeaderFooter.class, (packet, version) -> build((PacketPlayOutPlayerListHeaderFooter)packet, version));
buildMap.put(PacketPlayOutScoreboardDisplayObjective.class, (packet, version) -> build((PacketPlayOutScoreboardDisplayObjective)packet, version));
buildMap.put(PacketPlayOutScoreboardObjective.class, (packet, version) -> build((PacketPlayOutScoreboardObjective)packet, version));
buildMap.put(PacketPlayOutScoreboardScore.class, (packet, version) -> build((PacketPlayOutScoreboardScore)packet, version));
buildMap.put(PacketPlayOutScoreboardTeam.class, (packet, version) -> build((PacketPlayOutScoreboardTeam)packet, version));
}
/**
* Converts custom packet into platform-specific packet by calling a function from
* {@link #buildMap}.
*
* @param packet
* Packet to build
* @param clientVersion
* Protocol version of player to build the packet for
* @return Platform-specific packet
* @throws ReflectiveOperationException
* if reflection fails
*/
public Object build(@NonNull TabPacket packet, @NonNull ProtocolVersion clientVersion) throws ReflectiveOperationException {
return buildMap.get(packet.getClass()).apply(packet, clientVersion);
}
/**
* Constructs platform-specific PacketPlayOutBoss class based on custom packet class
*
* @param packet
* Custom packet to be built
* @param clientVersion
* Protocol version of player to build the packet for
* @return Platform-specific packet
* @throws ReflectiveOperationException
* if thrown by reflective operation
*/
public Object build(PacketPlayOutBoss packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
return packet;
}
/**
* Constructs platform-specific PacketPlayOutChat class based on custom packet class
*
* @param packet
* Custom packet to be built
* @param clientVersion
* Protocol version of player to build the packet for
* @return Platform-specific packet
*/
public Object build(PacketPlayOutChat packet, ProtocolVersion clientVersion) {
return packet;
}
/**
* Constructs platform-specific PacketPlayOutPlayerInfo class based on custom packet class
*
* @param packet
* Custom packet to be built
* @param clientVersion
* Protocol version of player to build the packet for
* @return Platform-specific packet
* @throws ReflectiveOperationException
* if thrown by reflective operation
*/
public Object build(PacketPlayOutPlayerInfo packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
return packet;
}
/**
* Constructs platform-specific PacketPlayOutPlayerListHeaderFooter class based on custom packet class
*
* @param packet
* Custom packet to be built
* @param clientVersion
* Protocol version of player to build the packet for
* @return Platform-specific packet
*/
public Object build(PacketPlayOutPlayerListHeaderFooter packet, ProtocolVersion clientVersion) {
return packet;
}
/**
* Constructs platform-specific PacketPlayOutScoreboardDisplayObjective class based on custom packet class
*
* @param packet
* Custom packet to be built
* @param clientVersion
* Protocol version of player to build the packet for
* @return Platform-specific packet
*/
public Object build(PacketPlayOutScoreboardDisplayObjective packet, ProtocolVersion clientVersion) {
return packet;
}
/**
* Constructs platform-specific PacketPlayOutScoreboardObjective class based on custom packet class
*
* @param packet
* Custom packet to be built
* @param clientVersion
* Protocol version of player to build the packet for
* @return Platform-specific packet
*/
public Object build(PacketPlayOutScoreboardObjective packet, ProtocolVersion clientVersion) {
return packet;
}
/**
* Constructs platform-specific PacketPlayOutScoreboardScore class based on custom packet class
*
* @param packet
* Custom packet to be built
* @param clientVersion
* Protocol version of player to build the packet for
* @return Platform-specific packet
*/
public Object build(PacketPlayOutScoreboardScore packet, ProtocolVersion clientVersion) {
return packet;
}
/**
* Constructs platform-specific PacketPlayOutScoreboardTeam class based on custom packet class
*
* @param packet
* Custom packet to be built
* @param clientVersion
* Protocol version of player to build the packet for
* @return Platform-specific packet
*/
public Object build(PacketPlayOutScoreboardTeam packet, ProtocolVersion clientVersion) {
return packet;
}
/**
* Cuts given string to specified character length (or length-1 if last character is a color character)
* and translates RGB to legacy colors. If string is not that long, the original string is returned.
* RGB codes are converted into legacy, since cutting is only needed for &lt;1.13.
* If {@code string} is {@code null}, empty string is returned.
*
* @param string
* String to cut
* @param length
* Length to cut to
* @return string cut to {@code length} characters
*/
public String cutTo(String string, int length) {
if (string == null) return "";
String legacyText = string;
if (string.contains("#")) {
//converting RGB to legacy colors
legacyText = RGBUtils.getInstance().convertRGBtoLegacy(string);
}
if (legacyText.length() <= length) return legacyText;
if (legacyText.charAt(length-1) == EnumChatFormat.COLOR_CHAR) {
return legacyText.substring(0, length-1); //cutting one extra character to prevent prefix ending with "&"
} else {
return legacyText.substring(0, length);
}
}
/**
* If {@code clientVersion} is &gt;= 1.13, creates a component from given text and returns
* it as a serialized component, which BungeeCord uses.
* <p>
* If {@code clientVersion} is &lt; 1.12, the text is cut to {@code length} characters if
* needed and returned.
*
* @param text
* Text to convert
* @param clientVersion
* Version of player to convert text for
* @return serialized component for 1.13+ clients, cut string for 1.12-
*/
public String jsonOrCut(String text, ProtocolVersion clientVersion, int length) {
if (text == null) return null;
if (clientVersion.getMinorVersion() >= 13) {
return IChatBaseComponent.optimizedComponent(text).toString(clientVersion);
} else {
return cutTo(text, length);
}
}
/**
* Converts platform-specific instance of player info packet into
* {@link PacketPlayOutPlayerInfo} object.
*
* @param packet
* platform-specific info packet
* @param clientVersion
* Version of client receiving the packet
* @return The packet converted into {@link PacketPlayOutPlayerInfo}
* @throws ReflectiveOperationException
* if thrown by reflective operation
*/
public PacketPlayOutPlayerInfo readPlayerInfo(Object packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
return null;
}
/**
* Converts platform-specific instance of objective packet into
* {@link PacketPlayOutScoreboardObjective} object.
*
* @param packet
* platform-specific objective packet
* @return The packet converted into {@link PacketPlayOutScoreboardObjective}
* @throws ReflectiveOperationException
* if thrown by reflective operation
*/
public PacketPlayOutScoreboardObjective readObjective(Object packet) throws ReflectiveOperationException {
return null;
}
/**
* Converts platform-specific instance of display objective packet into
* {@link PacketPlayOutScoreboardDisplayObjective} object.
*
* @param packet
* platform-specific display objective packet
* @return The packet converted into {@link PacketPlayOutScoreboardDisplayObjective}
* @throws ReflectiveOperationException
* if thrown by reflective operation
*/
public PacketPlayOutScoreboardDisplayObjective readDisplayObjective(Object packet) throws ReflectiveOperationException {
return null;
}
}
@@ -1,175 +0,0 @@
package me.neznamy.tab.api.protocol;
import lombok.Data;
import lombok.NonNull;
import me.neznamy.tab.api.bossbar.BarColor;
import me.neznamy.tab.api.bossbar.BarStyle;
import java.util.UUID;
/**
* A class representing platform specific packet class
*/
@Data
public class PacketPlayOutBoss implements TabPacket {
/** UUID of the BossBar */
private final UUID id;
/** Action of this packet */
private Action action;
/** BossBar title */
private String name;
/** BossBar progress (0-1)*/
private float pct;
/** BossBar color */
private BarColor color;
/** BossBar style */
private BarStyle overlay;
/** Darken screen flag */
private boolean darkenScreen;
/** Play music flag */
private boolean playMusic;
/** Create fog flag */
private boolean createWorldFog;
/**
* Constructs new instance with given parameters and
* {@link me.neznamy.tab.api.protocol.PacketPlayOutBoss.Action}.ADD action
*
* @param id
* BossBar uuid
* @param name
* BossBar title
* @param pct
* BossBar progress
* @param color
* BossBar color
* @param overlay
* BossBar style
*/
public PacketPlayOutBoss(@NonNull UUID id, @NonNull String name, float pct, @NonNull BarColor color, @NonNull BarStyle overlay) {
this.action = Action.ADD;
this.id = id;
this.name = name;
this.pct = pct;
this.color = color;
this.overlay = overlay;
}
/**
* Constructs new instance with given parameters and
* {@link me.neznamy.tab.api.protocol.PacketPlayOutBoss.Action#REMOVE} action
*
* @param id
* BossBar uuid
*/
public PacketPlayOutBoss(@NonNull UUID id) {
this.action = Action.REMOVE;
this.id = id;
}
/**
* Constructs new instance with given parameters and
* {@link me.neznamy.tab.api.protocol.PacketPlayOutBoss.Action#UPDATE_PCT} action
*
* @param id
* BossBar uuid
* @param pct
* BossBar progress
*/
public PacketPlayOutBoss(@NonNull UUID id, float pct) {
this.action = Action.UPDATE_PCT;
this.id = id;
this.pct = pct;
}
/**
* Constructs new instance with given parameters and
* {@link me.neznamy.tab.api.protocol.PacketPlayOutBoss.Action#UPDATE_NAME} action
*
* @param id
* BossBar uuid
* @param name
* BossBar title
*/
public PacketPlayOutBoss(@NonNull UUID id, @NonNull String name) {
this.action = Action.UPDATE_NAME;
this.id = id;
this.name = name;
}
/**
* Constructs new instance with given parameters and
* {@link me.neznamy.tab.api.protocol.PacketPlayOutBoss.Action#UPDATE_STYLE} action
*
* @param id
* BossBar uuid
* @param color
* BossBar color
* @param overlay
* BossBar style
*/
public PacketPlayOutBoss(@NonNull UUID id, @NonNull BarColor color, @NonNull BarStyle overlay) {
this.action = Action.UPDATE_STYLE;
this.id = id;
this.color = color;
this.overlay = overlay;
}
/**
* Constructs new instance with given parameters and
* {@link me.neznamy.tab.api.protocol.PacketPlayOutBoss.Action#UPDATE_PROPERTIES} action
*
* @param darkenScreen
* Darken screen flag
* @param playMusic
* Play music flag
* @param createWorldFog
* Create fog flag
*/
public PacketPlayOutBoss(@NonNull UUID id, boolean darkenScreen, boolean playMusic, boolean createWorldFog) {
this.action = Action.UPDATE_PROPERTIES;
this.id = id;
this.darkenScreen = darkenScreen;
this.playMusic = playMusic;
this.createWorldFog = createWorldFog;
}
/**
* Returns bitmask based on {@link #darkenScreen}, {@link #playMusic} and {@link #darkenScreen} values.
* <p>
* {@link #darkenScreen} adds {@code 1}, {@link #playMusic} {@code 2} and {@link #darkenScreen} {@code 4}
* to the final value.
*
* @return the bitmask
*/
public byte getFlags() {
byte value = 0;
if (darkenScreen) value += 1;
if (playMusic) value += 2;
if (createWorldFog) value += 4;
return value;
}
/**
* An enum representing all valid boss packet actions.
* Calling ordinal() will return action's network ID.
*/
public enum Action {
ADD,
REMOVE,
UPDATE_PCT,
UPDATE_NAME,
UPDATE_STYLE,
UPDATE_PROPERTIES
}
}
@@ -1,29 +0,0 @@
package me.neznamy.tab.api.protocol;
import lombok.Data;
import lombok.NonNull;
import me.neznamy.tab.api.chat.IChatBaseComponent;
/**
* A class representing platform specific packet class
*/
@Data
public class PacketPlayOutChat implements TabPacket {
/** Message to be sent */
@NonNull private final IChatBaseComponent message;
/** Message position */
@NonNull private final ChatMessageType type;
/**
* An enum representing positions of a chat message
* Calling ordinal() will return type's network ID.
*/
public enum ChatMessageType {
CHAT,
SYSTEM,
GAME_INFO
}
}
@@ -1,190 +0,0 @@
package me.neznamy.tab.api.protocol;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NonNull;
import me.neznamy.tab.api.chat.IChatBaseComponent;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.List;
import java.util.UUID;
/**
* A class representing platform specific packet class
*/
@Data @AllArgsConstructor
public class PacketPlayOutPlayerInfo implements TabPacket {
/** Packet actions */
private final EnumSet<EnumPlayerInfoAction> actions;
/** List of affected entries */
private final List<PlayerInfoData> entries;
/**
* Constructs new instance with given parameters.
*
* @param action
* Packet action
* @param entries
* Affected entries
*/
public PacketPlayOutPlayerInfo(EnumPlayerInfoAction action, PlayerInfoData... entries) {
this(action, Arrays.asList(entries));
}
/**
* Constructs new instance with given parameters.
*
* @param action
* Packet action
* @param entries
* Affected entries
*/
public PacketPlayOutPlayerInfo(@NonNull EnumPlayerInfoAction action, @NonNull List<PlayerInfoData> entries) {
if (action == EnumPlayerInfoAction.ADD_PLAYER) {
actions = EnumSet.of(EnumPlayerInfoAction.ADD_PLAYER,
EnumPlayerInfoAction.INITIALIZE_CHAT,
EnumPlayerInfoAction.UPDATE_GAME_MODE,
EnumPlayerInfoAction.UPDATE_LISTED,
EnumPlayerInfoAction.UPDATE_LATENCY,
EnumPlayerInfoAction.UPDATE_DISPLAY_NAME);
} else {
this.actions = EnumSet.of(action);
}
this.entries = entries;
}
/**
* Constructs new instance with given parameters.
*
* @param actions
* Packet actions
* @param entries
* Affected entries
*/
public PacketPlayOutPlayerInfo(@NonNull EnumSet<EnumPlayerInfoAction> actions, @NonNull PlayerInfoData... entries) {
this.actions = actions;
this.entries = Arrays.asList(entries);
}
/**
* A subclass representing player list entry
*/
@Data @AllArgsConstructor
public static class PlayerInfoData {
/** Real name of affected player */
private String name;
/** Player UUID */
@NonNull private UUID uniqueId;
/** Player's skin, null for empty skin */
private Skin skin;
/** I have no idea what this is */
private boolean listed;
/** Latency */
private int latency;
/** GameMode */
private EnumGamemode gameMode = EnumGamemode.SURVIVAL; //ProtocolLib causes NPE even when action does not use GameMode
/**
* Display name displayed in TabList. Using {@code null} results in no display name
* and scoreboard team prefix/suffix being visible in TabList instead.
*/
private IChatBaseComponent displayName;
/** Chat session ID */
private UUID chatSessionId;
/** Player's chat signing key */
private Object profilePublicKey;
/**
* Constructs new instance with given parameters. Suitable for
* {@link EnumPlayerInfoAction#UPDATE_GAME_MODE} action
*
* @param uniqueId
* Player's uuid
* @param gameMode
* Player's GameMode
*/
public PlayerInfoData(@NonNull UUID uniqueId, EnumGamemode gameMode) {
this.uniqueId = uniqueId;
this.gameMode = gameMode;
}
/**
* Constructs new instance with given parameters. Suitable for
* {@link EnumPlayerInfoAction#UPDATE_LATENCY} action
*
* @param uniqueId
* Player's uuid
* @param latency
* Player's ping
*/
public PlayerInfoData(@NonNull UUID uniqueId, int latency) {
this.uniqueId = uniqueId;
this.latency = latency;
}
/**
* Constructs new instance with given parameters. Suitable for
* {@link EnumPlayerInfoAction#UPDATE_DISPLAY_NAME} action
*
* @param uniqueId
* Player's uuid
* @param displayName
* Player's display name
*/
public PlayerInfoData(@NonNull UUID uniqueId, IChatBaseComponent displayName) {
this.uniqueId = uniqueId;
this.displayName = displayName;
}
/**
* Constructs new instance with given parameter. Suitable for
* {@link EnumPlayerInfoAction#REMOVE_PLAYER} action
*
* @param uniqueId
* Player's uuid
*/
public PlayerInfoData(@NonNull UUID uniqueId) {
this.uniqueId = uniqueId;
}
}
/**
* En enum representing packet action
*/
public enum EnumPlayerInfoAction {
ADD_PLAYER,
INITIALIZE_CHAT,
UPDATE_GAME_MODE,
UPDATE_LISTED,
UPDATE_LATENCY,
UPDATE_DISPLAY_NAME,
REMOVE_PLAYER
}
/**
* An enum representing GameMode
*/
public enum EnumGamemode {
NOT_SET,
SURVIVAL,
CREATIVE,
ADVENTURE,
SPECTATOR;
/** Value array to iterate over to avoid array creations on each call */
public static final EnumGamemode[] VALUES = values();
}
}
@@ -1,33 +0,0 @@
package me.neznamy.tab.api.protocol;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NonNull;
import me.neznamy.tab.api.chat.IChatBaseComponent;
/**
* A class representing platform specific packet class
*/
@Data @AllArgsConstructor
public class PacketPlayOutPlayerListHeaderFooter implements TabPacket {
/** TabList header */
@NonNull private final IChatBaseComponent header;
/** TabList footer */
@NonNull private final IChatBaseComponent footer;
/**
* Constructs new instance with given parameters. They are converted to {@link IChatBaseComponent}
* using {@link IChatBaseComponent#optimizedComponent(String)} method.
*
* @param header
* TabList header
* @param footer
* TabList footer
*/
public PacketPlayOutPlayerListHeaderFooter(@NonNull String header, @NonNull String footer) {
this.header = IChatBaseComponent.optimizedComponent(header);
this.footer = IChatBaseComponent.optimizedComponent(footer);
}
}
@@ -1,22 +0,0 @@
package me.neznamy.tab.api.protocol;
import lombok.Data;
import lombok.NonNull;
/**
* A class representing platform specific packet class
*/
@Data
public class PacketPlayOutScoreboardDisplayObjective implements TabPacket {
/**
* Display slot.
* 0 = PlayerList,
* 1 = SideBar,
* 2 = BelowName.
*/
private final int slot;
/** Up to 16 characters long objective name */
@NonNull private final String objectiveName;
}
@@ -1,55 +0,0 @@
package me.neznamy.tab.api.protocol;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NonNull;
import me.neznamy.tab.api.util.Preconditions;
/**
* A class representing platform specific packet class
*/
@Data @AllArgsConstructor
public class PacketPlayOutScoreboardObjective implements TabPacket {
/**
* Packet action.
* 0 = register,
* 1 = unregister,
* 2 = update title
*/
private final int action;
/** Up to 16 characters long objective name */
@NonNull private final String objectiveName;
/** Display name */
private final String displayName;
/** Display type, only takes effect in PlayerList */
private final EnumScoreboardHealthDisplay renderType;
/**
* Constructs new packet with given objective name and 1 (unregister) action.
*
* @param objectiveName
* objective name, up to 16 characters long
* @throws IllegalArgumentException
* if {@code objectiveName} is null or longer than 16 characters
*/
public PacketPlayOutScoreboardObjective(@NonNull String objectiveName) {
Preconditions.checkMaxLength(objectiveName, 16, "objective name");
this.objectiveName = objectiveName;
this.displayName = ""; //avoiding NPE on <1.7
this.action = 1;
this.renderType = null;
}
/**
* An enum representing available display types.
* They only take effect in PlayerList position.
*/
public enum EnumScoreboardHealthDisplay {
INTEGER, HEARTS
}
}
@@ -1,32 +0,0 @@
package me.neznamy.tab.api.protocol;
import lombok.Data;
import lombok.NonNull;
/**
* A class representing platform specific packet class
*/
@Data
public class PacketPlayOutScoreboardScore implements TabPacket {
/** Packet action */
@NonNull private final Action action;
/** Objective name */
@NonNull private final String objectiveName;
/** Affected player */
@NonNull private final String player;
/** Player's score */
private final int score;
/**
* An enum representing action types
*/
public enum Action {
CHANGE,
REMOVE
}
}
@@ -1,180 +0,0 @@
package me.neznamy.tab.api.protocol;
import lombok.Data;
import lombok.NonNull;
import me.neznamy.tab.api.chat.EnumChatFormat;
import me.neznamy.tab.api.util.Preconditions;
import java.util.Collection;
import java.util.Collections;
/**
* A class representing platform specific packet class
*/
@Data
public class PacketPlayOutScoreboardTeam implements TabPacket {
/**
* Up to 16 characters long team name.
* It is used to sort players alphabetically in TabList by team names.
* Team-less players appear on top, sorted by usernames.
* Players with teams are below them, players of the same team are
* sorted alphabetically by usernames.
*/
@NonNull private final String name;
/** Team prefix, limited to 16 characters, on &lt;1.13 */
private String playerPrefix;
/** Team suffix, limited to 16 characters, on &lt;1.13 */
private String playerSuffix;
/**
* NameTag visibility rule since 1.8. Possible options are:
* {@code always}, {@code hideForOtherTeams}, {@code hideForOwnTeam}, {@code never}
*/
private String nameTagVisibility;
/**
* Collision rule added in 1.9. Possible options are:
* {@code always}, {@code pushOtherTeams}, {@code pushOwnTeam}, {@code never}
*/
private String collisionRule;
/**
* Team color field added in 1.13. It determines name color,
* prefix and suffix color start.
*/
private EnumChatFormat color;
/**
* Affected entities. For players, it is their name, for other entities their UUID
* Player name length is limited to 16 characters for &lt;1.8, 40 characters since 1.8.
* Entity UUID option was added in 1.8.
*/
@NonNull private Collection<String> players = Collections.emptyList();
/**
* Packet action.
* 0 = create team,
* 1 = remove team,
* 2 = update team info,
* 3 = add entries,
* 4 = remove entries
*/
private final int action;
/**
* Bit mask.
* 0x01: Allow friendly fire,
* 0x02: Can see invisible players on the same team.
*/
private int options;
/**
* Constructs new instance with given parameters. Private constructor used
* internally to validate team name and set action.
*
* @param action
* Packet action (0 = create team, 1 = remove team, 2 = update team info,
* 3 = add entries, 4 = remove entries)
* @param name
* Team name, up to 16 characters long
* @throws IllegalArgumentException
* if {@code name} is null, empty or longer than 16 characters
*/
private PacketPlayOutScoreboardTeam(int action, @NonNull String name) {
Preconditions.checkMaxLength(name, 16, "team name");
this.action = action;
this.name = name;
}
/**
* Constructs new instance with given parameters and 0 (CREATE) action
*
* @param team
* Team name, up to 16 characters long
* @param prefix
* Prefix of players in team
* @param suffix
* Suffix of players in team
* @param visibility
* NameTag visibility rule
* @param collision
* Collision rule
* @param players
* Affected entities
* @param options
* bitmask of team options
* @throws IllegalArgumentException
* if {@code name} is null, empty or longer than 16 characters
*/
public PacketPlayOutScoreboardTeam(String team, @NonNull String prefix, @NonNull String suffix, String visibility,
String collision, @NonNull Collection<String> players, int options) {
this(0, team);
this.playerPrefix = prefix;
this.playerSuffix = suffix;
this.nameTagVisibility = visibility;
this.collisionRule = collision;
this.players = players;
this.options = options;
}
/**
* Constructs new instance with given parameters and 1 (REMOVE) action
*
* @param team
* Team name, up to 16 characters long
* @throws IllegalArgumentException
* if {@code name} is null, empty or longer than 16 characters
*/
public PacketPlayOutScoreboardTeam(String team) {
this(1, team);
}
/**
* Constructs new instance with given parameters and 2 (UPDATE_TEAM_INFO) action
*
* @param team
* Team name, up to 16 characters long
* @param prefix
* Prefix of players in team
* @param suffix
* Suffix of players in team
* @param visibility
* NameTag visibility rule
* @param collision
* Collision rule
* @param options
* bitmask of team options
* @throws IllegalArgumentException
* if {@code name} is null, empty or longer than 16 characters
*/
public PacketPlayOutScoreboardTeam(String team, @NonNull String prefix, @NonNull String suffix,
String visibility, String collision, int options) {
this(2, team);
this.playerPrefix = prefix;
this.playerSuffix = suffix;
this.nameTagVisibility = visibility;
this.collisionRule = collision;
this.options = options;
}
/**
* Constructs new instance with given parameters and 3 (ADD_PLAYERS) if {@code add} is {@code true},
* 4 (REMOVE_PLAYERS) if {@code add} is {@code false} action.
*
* @param team
* Team name, up to 16 characters long
* @param players
* Affected entities
* @param add
* {@code true} if players should be added, {@code false} if removed from team
* @throws IllegalArgumentException
* if {@code name} is null, empty or longer than 16 characters
*/
public PacketPlayOutScoreboardTeam(String team, @NonNull Collection<String> players, boolean add) {
this(add ? 3 : 4, team);
this.players = players;
}
}
@@ -1,17 +0,0 @@
package me.neznamy.tab.api.protocol;
/**
* Interface representing a packet that can be sent to client independently of platform.
* It is technically empty, existence is only present to restrict type access
* when sending packets to player, so {@link me.neznamy.tab.api.TabPlayer#sendCustomPacket(TabPacket)} cannot
* be called with an actual packet.
*/
public interface TabPacket {
/**
* Returns user-friendly string representation of the packet
*
* @return user-friendly string representation of the packet
*/
String toString();
}
@@ -1,8 +1,9 @@
package me.neznamy.tab.api.protocol;
package me.neznamy.tab.api.tablist;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NonNull;
import org.jetbrains.annotations.Nullable;
/**
* Class representing a minecraft skin as a value - signature pair.
@@ -13,6 +14,6 @@ public class Skin {
/** Skin value */
@NonNull private final String value;
/** Skin signature, can be null */
private final String signature;
/** Skin signature */
@Nullable private final String signature;
}
@@ -0,0 +1,34 @@
package me.neznamy.tab.api.tablist;
import lombok.NonNull;
import me.neznamy.tab.api.chat.IChatBaseComponent;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.Map;
import java.util.UUID;
public interface TabList {
String TEXTURES_PROPERTY = "textures";
void removeEntry(@NonNull UUID entry);
void removeEntries(@NonNull Collection<UUID> entries);
void updateDisplayName(@NonNull UUID entry, @Nullable IChatBaseComponent displayName);
void updateDisplayNames(@NonNull Map<UUID, IChatBaseComponent> entries);
void updateLatency(@NonNull UUID entry, int latency);
void updateLatencies(@NonNull Map<UUID, Integer> entries);
void updateGameMode(@NonNull UUID entry, int gameMode);
void updateGameModes(@NonNull Map<UUID, Integer> entries);
void addEntry(@NonNull TabListEntry entry);
void addEntries(@NonNull Collection<TabListEntry> entries);
}
@@ -0,0 +1,70 @@
package me.neznamy.tab.api.tablist;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.api.chat.IChatBaseComponent;
import org.jetbrains.annotations.Nullable;
import java.util.UUID;
/**
* A subclass representing player list entry
*/
@Data
@AllArgsConstructor
public class TabListEntry {
/** Player UUID */
@NonNull private UUID uniqueId;
/** Real name of affected player */
@Nullable private String name;
/** Player's skin, null for empty skin */
@Nullable private Skin skin;
/** If player should appear in tablist or not */
private boolean listed;
/** Latency */
private int latency;
/** GameMode */
private int gameMode;
/**
* Display name displayed in TabList. Using {@code null} results in no display name
* and scoreboard team prefix/suffix being visible in TabList instead.
*/
@Nullable private IChatBaseComponent displayName;
/** Player's chat signing key (1.19+), remote chat session (1.19.3+) */
@Nullable private Object chatSession;
@RequiredArgsConstructor
public static class Builder {
@NonNull private UUID uniqueId;
@Nullable private String name;
@Nullable private Skin skin;
private boolean listed;
private int latency;
private int gameMode;
@Nullable private IChatBaseComponent displayName;
@Nullable private Object chatSession;
public Builder name(String name) { this.name = name; return this; }
public Builder skin(Skin skin) { this.skin = skin; return this; }
public Builder listed(boolean listed) { this.listed = listed; return this; }
public Builder latency(int latency) { this.latency = latency; return this; }
public Builder gameMode(int gameMode) { this.gameMode = gameMode; return this; }
public Builder displayName(IChatBaseComponent displayName) { this.displayName = displayName; return this; }
public Builder chatSession(Object chatSession) { this.chatSession = chatSession; return this; }
public TabListEntry build() {
return new TabListEntry(uniqueId, name, skin, listed, latency, gameMode, displayName, chatSession);
}
}
}
@@ -1,96 +0,0 @@
package me.neznamy.tab.api.task;
import java.util.concurrent.Future;
import me.neznamy.tab.api.TabFeature;
/**
* Manager for scheduling and executing tasks in TAB's
* main thread to avoid concurrency issues.
*/
public interface ThreadManager {
/**
* Starts a task in new thread and measures how long it took to process
*
* @param feature
* feature to add cpu usage to
* @param type
* usage type to add cpu usage to
* @param task
* the task
*/
Future<Void> runMeasuredTask(TabFeature feature, String type, Runnable task);
/**
* Starts a task in new thread and measures how long it took to process
*
* @param feature
* feature to add cpu usage to
* @param type
* usage type to add cpu usage to
* @param task
* the task
*/
Future<Void> runMeasuredTask(String feature, String type, Runnable task);
/**
* Runs task in a new thread
*
* @param task
* the task
*/
Future<Void> runTask(Runnable task);
/**
* Starts a new task with defined repeat interval that measures cpu usage
*
* @param intervalMilliseconds
* task interval
* @param feature
* feature to add cpu usage to
* @param type
* usage type to add cpu usage to
* @param task
* the task
* @return Future representing the submitted task
*/
Future<?> startRepeatingMeasuredTask(int intervalMilliseconds, TabFeature feature, String type, Runnable task);
/**
* Starts a new task with defined repeat interval
*
* @param intervalMilliseconds
* task interval
* @param task
* the task
* @return Future representing the submitted task
*/
Future<?> startRepeatingTask(int intervalMilliseconds, Runnable task);
/**
* Runs task with a delay and measures how long it took to process
*
* @param delayMilliseconds
* how long to run the task after
* @param feature
* feature to add cpu usage to
* @param type
* usage type to add cpu usage to
* @param task
* the task
* @return Future representing the submitted task
*/
Future<?> runTaskLater(int delayMilliseconds, TabFeature feature, String type, Runnable task);
/**
* Runs task with a delay
*
* @param delayMilliseconds
* how long to run the task after
* @param task
* the task
* @return Future representing the submitted task
*/
Future<?> runTaskLater(int delayMilliseconds, Runnable task);
}
@@ -1,6 +1,8 @@
package me.neznamy.tab.api.team;
import lombok.NonNull;
import me.neznamy.tab.api.TabPlayer;
import org.jetbrains.annotations.Nullable;
public interface TeamManager {
@@ -12,7 +14,7 @@ public interface TeamManager {
* @see #showNametag(TabPlayer)
* @see #hasHiddenNametag(TabPlayer)
*/
void hideNametag(TabPlayer player);
void hideNametag(@NonNull TabPlayer player);
/**
* Hides player's NameTag for specified player until it's shown again
@@ -22,7 +24,7 @@ public interface TeamManager {
* @param viewer
* player to hide NameTag for
*/
void hideNametag(TabPlayer player, TabPlayer viewer);
void hideNametag(@NonNull TabPlayer player, @NonNull TabPlayer viewer);
/**
* Makes player's NameTag visible again
@@ -32,7 +34,7 @@ public interface TeamManager {
* @see #hideNametag(TabPlayer)
* @see #hasHiddenNametag(TabPlayer)
*/
void showNametag(TabPlayer player);
void showNametag(@NonNull TabPlayer player);
/**
* Shows player's NameTag for specified viewer if it was hidden before
@@ -42,7 +44,7 @@ public interface TeamManager {
* @param viewer
* player to show NameTag back for
*/
void showNametag(TabPlayer player, TabPlayer viewer);
void showNametag(@NonNull TabPlayer player, @NonNull TabPlayer viewer);
/**
* Return whether player has hidden NameTag or not
@@ -53,7 +55,7 @@ public interface TeamManager {
* @see #hideNametag(TabPlayer)
* @see #showNametag(TabPlayer)
*/
boolean hasHiddenNametag(TabPlayer player);
boolean hasHiddenNametag(@NonNull TabPlayer player);
/**
* Returns true if NameTag is hidden for specified viewer, false if not
@@ -64,7 +66,7 @@ public interface TeamManager {
* player to check visibility status for
* @return true if hidden, false if not
*/
boolean hasHiddenNametag(TabPlayer player, TabPlayer viewer);
boolean hasHiddenNametag(@NonNull TabPlayer player, @NonNull TabPlayer viewer);
/**
* Unregisters player's team and no longer handles it, as well as disables anti-override for teams.
@@ -73,7 +75,7 @@ public interface TeamManager {
* @param player
* player to pause team handling of
*/
void pauseTeamHandling(TabPlayer player);
void pauseTeamHandling(@NonNull TabPlayer player);
/**
* Resumes team handling if it was before paused using pauseTeamHandling(), if not, nothing happens
@@ -81,7 +83,7 @@ public interface TeamManager {
* @param player
* player to resume team handling of
*/
void resumeTeamHandling(TabPlayer player);
void resumeTeamHandling(@NonNull TabPlayer player);
/**
* Returns true if team handling is paused for this player using pauseTeamHandling(), false if not, or
@@ -91,7 +93,7 @@ public interface TeamManager {
* player to check handling status of
* @return true if paused, false if not
*/
boolean hasTeamHandlingPaused(TabPlayer player);
boolean hasTeamHandlingPaused(@NonNull TabPlayer player);
/**
* Forces new team name for the player until this method is called again with null argument and
@@ -102,7 +104,7 @@ public interface TeamManager {
* @param teamName
* forced team name
*/
void forceTeamName(TabPlayer player, String teamName);
void forceTeamName(@NonNull TabPlayer player, @Nullable String teamName);
/**
* Returns forced team name of player or null if not forced
@@ -111,7 +113,7 @@ public interface TeamManager {
* player to check forced team name of
* @return forced team name of player or null if not forced
*/
String getForcedTeamName(TabPlayer player);
@Nullable String getForcedTeamName(@NonNull TabPlayer player);
/**
@@ -122,7 +124,7 @@ public interface TeamManager {
* @param collision
* forced collision rule
*/
void setCollisionRule(TabPlayer player, Boolean collision);
void setCollisionRule(@NonNull TabPlayer player, @Nullable Boolean collision);
/**
* Returns forced collision rule or null if collision is not forced using setCollisionRule
@@ -131,7 +133,7 @@ public interface TeamManager {
* player to get forced collision of
* @return forced value or null if not forced
*/
Boolean getCollisionRule(TabPlayer player);
@Nullable Boolean getCollisionRule(@NonNull TabPlayer player);
/**
* Sends update team's properties packet of player's team to everyone
@@ -139,23 +141,23 @@ public interface TeamManager {
* @param player
* player to update team data of
*/
void updateTeamData(TabPlayer player);
void updateTeamData(@NonNull TabPlayer player);
void setPrefix(TabPlayer player, String prefix);
void setPrefix(@NonNull TabPlayer player, @Nullable String prefix);
void setSuffix(TabPlayer player, String suffix);
void setSuffix(@NonNull TabPlayer player, @Nullable String suffix);
void resetPrefix(TabPlayer player);
void resetPrefix(@NonNull TabPlayer player);
void resetSuffix(TabPlayer player);
void resetSuffix(@NonNull TabPlayer player);
String getCustomPrefix(TabPlayer player);
@Nullable String getCustomPrefix(@NonNull TabPlayer player);
String getCustomSuffix(TabPlayer player);
@Nullable String getCustomSuffix(@NonNull TabPlayer player);
String getOriginalPrefix(TabPlayer player);
@NonNull String getOriginalPrefix(@NonNull TabPlayer player);
String getOriginalSuffix(TabPlayer player);
@NonNull String getOriginalSuffix(@NonNull TabPlayer player);
/**
* Toggles nametag visibility view on all players for specified player.
@@ -167,7 +169,7 @@ public interface TeamManager {
* @param sendToggleMessage
* {@code true} if configured toggle message should be sent, {@code false} if not
*/
void toggleNameTagVisibilityView(TabPlayer player, boolean sendToggleMessage);
void toggleNameTagVisibilityView(@NonNull TabPlayer player, boolean sendToggleMessage);
/**
* Returns {@code true} if player has hidden nametags by either calling
@@ -178,5 +180,5 @@ public interface TeamManager {
* player to check
* @return {@code true} if hidden, {@code false} if not
*/
boolean hasHiddenNameTagVisibilityView(TabPlayer player);
boolean hasHiddenNameTagVisibilityView(@NonNull TabPlayer player);
}
@@ -2,9 +2,6 @@ package me.neznamy.tab.api.util;
/**
* An interface for BiFunction which can throw an exception.
* Used in {@link me.neznamy.tab.api.protocol.PacketBuilder} to
* call build method for packet classes, which may throw an
* exception due to reflection.
* @param <A>
* First function argument
* @param <B>
@@ -1,18 +1,16 @@
package me.neznamy.tab.api.util;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import me.neznamy.tab.api.TabPlayer;
/**
* Class with utility methods used in the plugin
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class Preconditions {
/**
* Private constructor preventing the class from being instantiated
*/
private Preconditions() {}
/**
* Checks length of String and throws {@code IllegalArgumentException} if it is
* longer than limit.
@@ -1,12 +0,0 @@
package me.neznamy.tab.api.util;
/**
* Supplier that can throw an exception
*
* @param <T>
* Return class type
*/
public interface SupplierWithException<T> {
T get() throws Exception;
}
@@ -11,7 +11,8 @@ tasks {
"name" to rootProject.name,
"version" to project.version,
"description" to project.description,
"website" to rootProject.ext.get("website")!!.toString()
"website" to rootProject.ext.get("website")!!.toString(),
"author" to rootProject.ext.get("author")!!.toString()
))
}
javadoc {
+2 -1
View File
@@ -4,11 +4,12 @@ plugins {
allprojects {
group = "me.neznamy"
version = "3.3.0"
version = "3.3.2"
description = "An all-in-one solution that works"
ext.set("id", "tab")
ext.set("website", "https://github.com/NEZNAMY/TAB")
ext.set("author", "NEZNAMY")
}
val platforms = setOf(
@@ -2,6 +2,7 @@ package me.neznamy.tab.platforms.bukkit;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.api.TabAPI;
import me.neznamy.tab.shared.TAB;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
@@ -27,8 +28,8 @@ public class BukkitEventListener implements Listener {
*/
@EventHandler(priority = EventPriority.HIGHEST)
public void onQuit(PlayerQuitEvent e) {
if (TabAPI.getInstance().isPluginDisabled()) return;
TabAPI.getInstance().getThreadManager().runTask(() -> TabAPI.getInstance().getFeatureManager().onQuit(TabAPI.getInstance().getPlayer(e.getPlayer().getUniqueId())));
if (TAB.getInstance().isPluginDisabled()) return;
TAB.getInstance().getCPUManager().runTask(() -> TAB.getInstance().getFeatureManager().onQuit(TAB.getInstance().getPlayer(e.getPlayer().getUniqueId())));
}
/**
@@ -39,9 +40,9 @@ public class BukkitEventListener implements Listener {
*/
@EventHandler(priority = EventPriority.LOWEST)
public void onJoin(PlayerJoinEvent e) {
if (TabAPI.getInstance().isPluginDisabled()) return;
TabAPI.getInstance().getThreadManager().runTask(() ->
TabAPI.getInstance().getFeatureManager().onJoin(new BukkitTabPlayer(e.getPlayer(), platform.getProtocolVersion(e.getPlayer()))));
if (TAB.getInstance().isPluginDisabled()) return;
TAB.getInstance().getCPUManager().runTask(() ->
TAB.getInstance().getFeatureManager().onJoin(new BukkitTabPlayer(e.getPlayer(), platform.getProtocolVersion(e.getPlayer()))));
}
/**
@@ -52,9 +53,9 @@ public class BukkitEventListener implements Listener {
*/
@EventHandler(priority = EventPriority.LOWEST)
public void onWorldChange(PlayerChangedWorldEvent e) {
if (TabAPI.getInstance().isPluginDisabled()) return;
TabAPI.getInstance().getThreadManager().runTask(() ->
TabAPI.getInstance().getFeatureManager().onWorldChange(e.getPlayer().getUniqueId(), e.getPlayer().getWorld().getName()));
if (TAB.getInstance().isPluginDisabled()) return;
TAB.getInstance().getCPUManager().runTask(() ->
TAB.getInstance().getFeatureManager().onWorldChange(e.getPlayer().getUniqueId(), e.getPlayer().getWorld().getName()));
}
/**
@@ -1,98 +0,0 @@
package me.neznamy.tab.platforms.bukkit;
import me.neznamy.tab.api.ProtocolVersion;
import me.neznamy.tab.api.protocol.*;
import me.neznamy.tab.api.protocol.PacketPlayOutBoss.Action;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityDestroy;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityMetadata;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityTeleport;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutSpawnEntityLiving;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.*;
import org.bukkit.Location;
import org.bukkit.entity.EntityType;
import java.util.UUID;
public class BukkitPacketBuilder extends PacketBuilder {
{
buildMap.put(PacketPlayOutEntityMetadata.class, (packet, version) -> ((PacketPlayOutEntityMetadata)packet).build());
buildMap.put(PacketPlayOutEntityTeleport.class, (packet, version) -> ((PacketPlayOutEntityTeleport)packet).build());
buildMap.put(PacketPlayOutEntityDestroy.class, (packet, version) -> ((PacketPlayOutEntityDestroy)packet).build());
buildMap.put(PacketPlayOutSpawnEntityLiving.class, (packet, version) -> ((PacketPlayOutSpawnEntityLiving)packet).build());
buildMap.put(PacketPlayOutPlayerListHeaderFooter.class, (packet, version) -> PacketPlayOutPlayerListHeaderFooterStorage.build((PacketPlayOutPlayerListHeaderFooter) packet, version));
buildMap.put(PacketPlayOutChat.class, (packet, version) -> PacketPlayOutChatStorage.build((PacketPlayOutChat) packet, version));
buildMap.put(PacketPlayOutScoreboardObjective.class, (packet, version) -> PacketPlayOutScoreboardObjectiveStorage.build((PacketPlayOutScoreboardObjective) packet, version));
buildMap.put(PacketPlayOutScoreboardDisplayObjective.class, (packet, version) -> PacketPlayOutScoreboardDisplayObjectiveStorage.build((PacketPlayOutScoreboardDisplayObjective) packet, version));
buildMap.put(PacketPlayOutScoreboardTeam.class, (packet, version) -> PacketPlayOutScoreboardTeamStorage.build((PacketPlayOutScoreboardTeam) packet, version));
buildMap.put(PacketPlayOutScoreboardScore.class, (packet, version) -> PacketPlayOutScoreboardScoreStorage.build((PacketPlayOutScoreboardScore) packet, version));
}
@Override
public Object build(PacketPlayOutBoss packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
if (NMSStorage.getInstance().getMinorVersion() >= 9 || clientVersion.getMinorVersion() >= 9) {
//1.9+ server or client, handled by bukkit api or ViaVersion
return packet;
}
//<1.9 client and server
return buildBossPacketEntity(packet, clientVersion);
}
@Override
public Object build(PacketPlayOutPlayerInfo packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
return PacketPlayOutPlayerInfoStorage.build(packet, clientVersion);
}
@Override
public PacketPlayOutPlayerInfo readPlayerInfo(Object nmsPacket, ProtocolVersion clientVersion) throws ReflectiveOperationException {
return PacketPlayOutPlayerInfoStorage.read(nmsPacket);
}
@Override
public PacketPlayOutScoreboardObjective readObjective(Object nmsPacket) throws ReflectiveOperationException {
return PacketPlayOutScoreboardObjectiveStorage.read(nmsPacket);
}
@Override
public PacketPlayOutScoreboardDisplayObjective readDisplayObjective(Object nmsPacket) throws ReflectiveOperationException {
return PacketPlayOutScoreboardDisplayObjectiveStorage.read(nmsPacket);
}
/**
* Builds entity packet representing requested BossBar packet using Wither on 1.8- clients.
*
* @param packet
* packet to build
* @param clientVersion
* client version
* @return entity BossBar packet
* @throws ReflectiveOperationException
* if thrown by reflective operation
*/
private Object buildBossPacketEntity(PacketPlayOutBoss packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
if (packet.getAction() == Action.UPDATE_STYLE) return null; //nothing to do here
int entityId = packet.getId().hashCode();
if (packet.getAction() == Action.REMOVE) {
return new PacketPlayOutEntityDestroy(entityId).build();
}
DataWatcher w = new DataWatcher();
if (packet.getAction() == Action.UPDATE_PCT || packet.getAction() == Action.ADD) {
float health = 300*packet.getPct();
if (health == 0) health = 1;
w.getHelper().setHealth(health);
}
if (packet.getAction() == Action.UPDATE_NAME || packet.getAction() == Action.ADD) {
w.getHelper().setCustomName(packet.getName(), clientVersion);
}
if (packet.getAction() == Action.ADD) {
w.getHelper().setEntityFlags((byte) 32);
w.getHelper().setWitherInvulnerableTime(880); // Magic number
return new PacketPlayOutSpawnEntityLiving(entityId, new UUID(0, 0), EntityType.WITHER, new Location(null, 0,0,0), w).build();
} else {
return new PacketPlayOutEntityMetadata(entityId, w).build();
}
}
}
@@ -1,36 +1,29 @@
package me.neznamy.tab.platforms.bukkit;
import java.util.ArrayList;
import java.util.Collection;
import java.util.function.Function;
import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
import lombok.AllArgsConstructor;
import lombok.Getter;
import me.neznamy.tab.api.TabFeature;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import com.mojang.authlib.GameProfile;
import io.netty.channel.Channel;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.api.feature.TabFeature;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.api.chat.IChatBaseComponent;
import me.neznamy.tab.api.chat.WrappedChatComponent;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutPlayerInfoStorage;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutPlayerInfoStorage.PlayerInfoDataStorage;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutScoreboardDisplayObjectiveStorage;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutScoreboardObjectiveStorage;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutScoreboardTeamStorage;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.features.PipelineInjector;
import me.neznamy.tab.shared.features.injection.NettyPipelineInjector;
import me.neznamy.tab.shared.features.sorting.Sorting;
import org.jetbrains.annotations.NotNull;
import java.util.*;
import java.util.stream.Collectors;
/**
* Pipeline injection for bukkit
*/
public class BukkitPipelineInjector extends PipelineInjector {
/** NMS data storage */
private final NMSStorage nms = NMSStorage.getInstance();
@Getter private final Function<TabPlayer, ChannelDuplexHandler> channelFunction = BukkitChannelDuplexHandler::new;
public class BukkitPipelineInjector extends NettyPipelineInjector {
/**
* Constructs new instance
@@ -39,106 +32,136 @@ public class BukkitPipelineInjector extends PipelineInjector {
super("packet_handler");
}
/**
* Custom channel duplex handler override
*/
@AllArgsConstructor
public class BukkitChannelDuplexHandler extends ChannelDuplexHandler {
@Override
protected Channel getChannel(TabPlayer player) {
BukkitTabPlayer bukkit = (BukkitTabPlayer) player;
NMSStorage nms = NMSStorage.getInstance();
try {
if (nms.CHANNEL != null) return (Channel) nms.CHANNEL.get(nms.NETWORK_MANAGER.get(bukkit.getPlayerConnection()));
} catch (final IllegalAccessException exception) {
TAB.getInstance().getErrorManager().printError("Failed to get channel of " + bukkit.getName(), exception);
}
return null;
}
/** Injected player */
private final TabPlayer player;
@Override
public void onDisplayObjective(TabPlayer player, Object packet) throws IllegalAccessException {
TAB.getInstance().getFeatureManager().onDisplayObjective(player,
PacketPlayOutScoreboardDisplayObjectiveStorage.POSITION.getInt(packet),
(String) PacketPlayOutScoreboardDisplayObjectiveStorage.OBJECTIVE_NAME.get(packet));
}
@Override
public void channelRead(@NotNull ChannelHandlerContext context, @NotNull Object packet) {
try {
if (TAB.getInstance().getFeatureManager().onPacketReceive(player, packet)) return;
super.channelRead(context, packet);
} catch (Exception e) {
TAB.getInstance().getErrorManager().printError("An error occurred when reading packets", e);
@Override
public void onObjective(TabPlayer player, Object packet) throws IllegalAccessException {
TAB.getInstance().getFeatureManager().onObjective(player,
PacketPlayOutScoreboardObjectiveStorage.METHOD.getInt(packet),
(String) PacketPlayOutScoreboardObjectiveStorage.OBJECTIVE_NAME.get(packet));
}
@Override
public boolean isDisplayObjective(Object packet) {
return PacketPlayOutScoreboardDisplayObjectiveStorage.CLASS.isInstance(packet);
}
@Override
public boolean isObjective(Object packet) {
return PacketPlayOutScoreboardObjectiveStorage.CLASS.isInstance(packet);
}
@Override
public boolean isTeam(Object packet) {
return PacketPlayOutScoreboardTeamStorage.CLASS.isInstance(packet);
}
@Override
public boolean isPlayerInfo(Object packet) {
return PacketPlayOutPlayerInfoStorage.CLASS.isInstance(packet);
}
@Override
public boolean isLogin(Object packet) {
return false;
}
@Override
public void onPlayerInfo(TabPlayer receiver, Object packet) throws ReflectiveOperationException {
NMSStorage nms = NMSStorage.getInstance();
List<String> actions;
if (PacketPlayOutPlayerInfoStorage.ClientboundPlayerInfoRemovePacket != null) {
//1.19.3+
actions = ((EnumSet<?>)PacketPlayOutPlayerInfoStorage.ACTION.get(packet)).stream().map(Enum::name).collect(Collectors.toList());
} else {
//1.19.2-
actions = Collections.singletonList(PacketPlayOutPlayerInfoStorage.ACTION.get(packet).toString());
}
List<Object> updatedList = new ArrayList<>();
for (Object nmsData : (List<?>) PacketPlayOutPlayerInfoStorage.PLAYERS.get(packet)) {
GameProfile profile = (GameProfile) PlayerInfoDataStorage.PlayerInfoData_getProfile.invoke(nmsData);
int gameMode = 0;
int latency = 0;
IChatBaseComponent displayName = null;
if (actions.contains("UPDATE_GAME_MODE") || actions.contains("ADD_PLAYER")) {
gameMode = PacketPlayOutPlayerInfoStorage.gameMode2Int(PlayerInfoDataStorage.PlayerInfoData_GameMode.get(nmsData));
gameMode = TAB.getInstance().getFeatureManager().onGameModeChange(receiver, profile.getId(), gameMode);
if (!nms.is1_19_3Plus()) PlayerInfoDataStorage.PlayerInfoData_GameMode.set(nmsData, PacketPlayOutPlayerInfoStorage.int2GameMode(gameMode));
}
if (actions.contains("UPDATE_LATENCY") || actions.contains("ADD_PLAYER")) {
latency = PlayerInfoDataStorage.PlayerInfoData_Latency.getInt(nmsData);
latency = TAB.getInstance().getFeatureManager().onLatencyChange(receiver, profile.getId(), latency);
if (!nms.is1_19_3Plus()) PlayerInfoDataStorage.PlayerInfoData_Latency.set(nmsData, latency);
}
if (actions.contains("UPDATE_DISPLAY_NAME") || actions.contains("ADD_PLAYER")) {
Object nmsComponent = PlayerInfoDataStorage.PlayerInfoData_DisplayName.get(nmsData);
displayName = nmsComponent == null ? null : new WrappedChatComponent(nmsComponent);
displayName = TAB.getInstance().getFeatureManager().onDisplayNameChange(receiver, profile.getId(), displayName);
if (!nms.is1_19_3Plus()) PlayerInfoDataStorage.PlayerInfoData_DisplayName.set(nmsData, nms.toNMSComponent(displayName, receiver.getVersion()));
}
if (actions.contains("ADD_PLAYER")) {
TAB.getInstance().getFeatureManager().onEntryAdd(receiver, profile.getId(), profile.getName());
}
if (nms.is1_19_3Plus()) {
// 1.19.3 is using records, which do not allow changing final fields, need to rewrite the list entirely
boolean listed = PlayerInfoDataStorage.PlayerInfoData_Listed.getBoolean(nmsData);
Object chatSession = PlayerInfoDataStorage.PlayerInfoData_RemoteChatSession.get(nmsData);
updatedList.add(PlayerInfoDataStorage.newPlayerInfoData.newInstance(
profile.getId(),
profile,
listed,
latency,
PacketPlayOutPlayerInfoStorage.int2GameMode(gameMode),
nms.toNMSComponent(displayName, receiver.getVersion()),
chatSession));
}
}
@Override
public void write(ChannelHandlerContext context, Object packet, ChannelPromise channelPromise) {
try {
if (PacketPlayOutPlayerInfoStorage.CLASS.isInstance(packet) ||
(PacketPlayOutPlayerInfoStorage.ClientboundPlayerInfoRemovePacket != null &&
PacketPlayOutPlayerInfoStorage.ClientboundPlayerInfoRemovePacket.isInstance(packet))) {
super.write(context, TAB.getInstance().getFeatureManager().onPacketPlayOutPlayerInfo(player, packet), channelPromise);
return;
}
if (antiOverrideTeams && PacketPlayOutScoreboardTeamStorage.CLASS.isInstance(packet)) {
long time = System.nanoTime();
modifyPlayers(packet);
TAB.getInstance().getCPUManager().addTime("NameTags", TabConstants.CpuUsageCategory.ANTI_OVERRIDE, System.nanoTime()-time);
super.write(context, packet, channelPromise);
return;
}
if (PacketPlayOutScoreboardDisplayObjectiveStorage.CLASS.isInstance(packet)) {
TAB.getInstance().getFeatureManager().onDisplayObjective(player, packet);
}
if (PacketPlayOutScoreboardObjectiveStorage.CLASS.isInstance(packet)) {
TAB.getInstance().getFeatureManager().onObjective(player, packet);
}
TAB.getInstance().getFeatureManager().onPacketSend(player, packet);
} catch (Exception e) {
TAB.getInstance().getErrorManager().printError("An error occurred when reading packets", e);
}
try {
super.write(context, packet, channelPromise);
} catch (Exception e) {
TAB.getInstance().getErrorManager().printError("Failed to forward packet " + packet.getClass().getSimpleName() + " to " + player.getName(), e);
}
}
/**
* Removes all real players from team if packet does not come from TAB and reports this to override log
*
* @param packetPlayOutScoreboardTeam
* team packet
* @throws ReflectiveOperationException
* nmsGameMode
*/
@SuppressWarnings("unchecked")
private void modifyPlayers(Object packetPlayOutScoreboardTeam) throws ReflectiveOperationException {
int action = PacketPlayOutScoreboardTeamStorage.ACTION.getInt(packetPlayOutScoreboardTeam);
if (action == 1 || action == 2 || action == 4) return;
Collection<String> players = (Collection<String>) PacketPlayOutScoreboardTeamStorage.PLAYERS.get(packetPlayOutScoreboardTeam);
String teamName = (String) PacketPlayOutScoreboardTeamStorage.NAME.get(packetPlayOutScoreboardTeam);
if (players == null) return;
//creating a new list to prevent NoSuchFieldException in minecraft packet encoder when a player is removed
Collection<String> newList = new ArrayList<>();
for (String entry : players) {
TabPlayer p = getPlayer(entry);
if (p == null) {
newList.add(entry);
continue;
}
Sorting sorting = (Sorting) TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.SORTING);
if (!((TabFeature)TAB.getInstance().getTeamManager()).isDisabledPlayer(p) &&
!TAB.getInstance().getTeamManager().hasTeamHandlingPaused(p) && !teamName.equals(sorting.getShortTeamName(p))) {
logTeamOverride(teamName, p.getName(), sorting.getShortTeamName(p));
} else {
newList.add(entry);
}
}
PacketPlayOutScoreboardTeamStorage.PLAYERS.set(packetPlayOutScoreboardTeam, newList);
}
/**
* Returns player with matching game profile name. This is different from
* real name when a nick plugin changing names of players is used. If no
* player was found, returns {@code null}.
*
* @param name
* Game profile name
* @return Player with matching game profile name
*/
private TabPlayer getPlayer(String name) {
for (TabPlayer p : TAB.getInstance().getOnlinePlayers()) {
if (p.getNickname().equals(name)) return p;
}
return null;
if (nms.is1_19_3Plus()) {
PacketPlayOutPlayerInfoStorage.PLAYERS.set(packet, updatedList);
}
}
@SuppressWarnings("unchecked")
@Override
public void modifyPlayers(Object packetPlayOutScoreboardTeam) throws ReflectiveOperationException {
int action = PacketPlayOutScoreboardTeamStorage.ACTION.getInt(packetPlayOutScoreboardTeam);
if (action == 1 || action == 2 || action == 4) return;
Collection<String> players = (Collection<String>) PacketPlayOutScoreboardTeamStorage.PLAYERS.get(packetPlayOutScoreboardTeam);
String teamName = (String) PacketPlayOutScoreboardTeamStorage.NAME.get(packetPlayOutScoreboardTeam);
if (players == null) return;
//creating a new list to prevent NoSuchFieldException in minecraft packet encoder when a player is removed
Collection<String> newList = new ArrayList<>();
for (String entry : players) {
TabPlayer p = getPlayer(entry);
if (p == null) {
newList.add(entry);
continue;
}
Sorting sorting = (Sorting) TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.SORTING);
if (!((TabFeature)TAB.getInstance().getTeamManager()).isDisabledPlayer(p) &&
!TAB.getInstance().getTeamManager().hasTeamHandlingPaused(p) && !teamName.equals(sorting.getShortTeamName(p))) {
logTeamOverride(teamName, p.getName(), sorting.getShortTeamName(p));
} else {
newList.add(entry);
}
}
PacketPlayOutScoreboardTeamStorage.PLAYERS.set(packetPlayOutScoreboardTeam, newList);
}
}
@@ -1,10 +1,12 @@
package me.neznamy.tab.platforms.bukkit;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import me.clip.placeholderapi.PlaceholderAPI;
import me.neznamy.tab.api.ProtocolVersion;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.api.TabFeature;
import me.neznamy.tab.api.feature.TabFeature;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.api.chat.EnumChatFormat;
import me.neznamy.tab.api.chat.rgb.RGBUtils;
@@ -19,7 +21,8 @@ import me.neznamy.tab.platforms.bukkit.permission.Vault;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.backend.BackendPlatform;
import me.neznamy.tab.shared.features.PlaceholderManagerImpl;
import me.neznamy.tab.shared.features.TabExpansion;
import me.neznamy.tab.shared.placeholders.expansion.EmptyTabExpansion;
import me.neznamy.tab.shared.placeholders.expansion.TabExpansion;
import me.neznamy.tab.shared.features.bossbar.BossBarManagerImpl;
import me.neznamy.tab.shared.features.nametags.NameTag;
import me.neznamy.tab.shared.permission.LuckPerms;
@@ -32,6 +35,7 @@ import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.RegisteredServiceProvider;
import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
@@ -39,9 +43,9 @@ import java.util.Collection;
/**
* Implementation of Platform interface for Bukkit platform
*/
@RequiredArgsConstructor
public class BukkitPlatform extends BackendPlatform {
@Getter private final BukkitPacketBuilder packetBuilder = new BukkitPacketBuilder();
@Getter private final BukkitPipelineInjector pipelineInjector = NMSStorage.getInstance().getMinorVersion() >= 8 ? new BukkitPipelineInjector() : null;
/** Plugin instance for registering tasks and events */
@@ -50,28 +54,9 @@ public class BukkitPlatform extends BackendPlatform {
/** Variables checking presence of other plugins to hook into */
private final boolean placeholderAPI = Bukkit.getPluginManager().isPluginEnabled(TabConstants.Plugin.PLACEHOLDER_API);
@Getter @Setter private boolean libsDisguisesEnabled = Bukkit.getPluginManager().isPluginEnabled(TabConstants.Plugin.LIBS_DISGUISES);
private Plugin viaVersion;
private final boolean viaVersion = ReflectionUtils.classExists("com.viaversion.viaversion.api.Via");
private final boolean protocolSupport = Bukkit.getPluginManager().isPluginEnabled(TabConstants.Plugin.PROTOCOL_SUPPORT);
/**
* Constructs new instance with given plugin parameter
*
* @param plugin
* plugin instance
*/
public BukkitPlatform(JavaPlugin plugin) {
this.plugin = plugin;
if (Bukkit.getPluginManager().isPluginEnabled(TabConstants.Plugin.VIAVERSION)) {
if (ReflectionUtils.classExists("com.viaversion.viaversion.api.Via")) {
viaVersion = Bukkit.getPluginManager().getPlugin(TabConstants.Plugin.VIAVERSION);
} else {
TAB.getInstance().sendConsoleMessage("&cAn outdated version of ViaVersion (" + getPluginVersion(TabConstants.Plugin.VIAVERSION) + ") was detected.", true);
TAB.getInstance().sendConsoleMessage("&cTAB only supports ViaVersion 4.0.0 and above. Disabling ViaVersion hook.", true);
TAB.getInstance().sendConsoleMessage("&cThis might cause problems, such as limitations still being present for latest MC clients as well as RGB not working.", true);
}
}
}
@Override
public PermissionPlugin detectPermissionPlugin() {
if (Bukkit.getPluginManager().isPluginEnabled(TabConstants.Plugin.LUCKPERMS)) {
@@ -113,13 +98,13 @@ public class BukkitPlatform extends BackendPlatform {
}
@Override
public TabExpansion getTabExpansion() {
public @NotNull TabExpansion getTabExpansion() {
if (placeholderAPI) {
BukkitTabExpansion expansion = new BukkitTabExpansion();
expansion.register();
return expansion;
}
return null;
return new EmptyTabExpansion();
}
@Override
@@ -205,8 +190,8 @@ public class BukkitPlatform extends BackendPlatform {
//some PS versions return -1 on unsupported server versions instead of throwing exception
if (version != -1 && version < TAB.getInstance().getServerVersion().getNetworkId()) return version;
}
if (viaVersion != null) {
return getProtocolVersionVia(player.getUniqueId(), player.getName(), 0);
if (viaVersion) {
return ProtocolVersion.getPlayerVersionVia(player.getUniqueId(), player.getName());
}
return TAB.getInstance().getServerVersion().getNetworkId();
}
@@ -0,0 +1,96 @@
package me.neznamy.tab.platforms.bukkit;
import lombok.NonNull;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutScoreboardDisplayObjectiveStorage;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutScoreboardObjectiveStorage;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutScoreboardScoreStorage;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutScoreboardTeamStorage;
import me.neznamy.tab.shared.TabScoreboard;
import java.util.Collection;
/**
* Scoreboard implementation for Bukkit, which uses packets
* to send scoreboards. While md_5 keeps saying that you don't
* need packets for scoreboards in every single spigot update,
* this is not true. It may seem that way at first, but after
* investigating, fatal problems are found:<p>
* #1 - Limitations on legacy versions are forced in the API.
* While this may not seem like a problem, it enforces those
* limits even for 1.13+ players (if using ViaVersion).<p>
* #2 - Modern versions no longer have any limits, but md_5
* decided to add some random limits for absolutely no reason
* at all. Scoreboard title received a random 128 characters
* limit including color codes. Together with the almighty bukkit
* RGB format using 14 characters for 1 color code, this makes
* gradients just impossible to use. Team prefix/suffix also
* received a 64 characters limit (excluding color codes at least),
* however that might not be enough for displaying a line of text
* in sidebar, which would require splitting the text into prefix
* and suffix, which is just begging for bugs to be introduced.<p>
* #3 - Other plugins can decide to put players into their own
* scoreboard, automatically destroying all visuals made by the
* plugin. They might also put all players into the same scoreboard,
* making per-player view of teams, especially sidebar not working.<p>
* #4 - API made scoreboards get saved into the world folder and then
* resent on join with everything that was ever registered to it,
* which is very poor performance-wise, especially since it does
* that in the main server thread. It might also send conflicting
* information, such as putting a player into team they were on
* previously, but are not supposed to be anymore. Removing the plugin
* would cause all visuals to still be present on the server,
* requiring owners to properly clear scoreboards, which is not something
* everyone knows how to do.
*/
public class BukkitScoreboard extends TabScoreboard {
public BukkitScoreboard(TabPlayer player) {
super(player);
}
@Override
public void setDisplaySlot(DisplaySlot slot, @NonNull String objective) {
player.sendPacket(PacketPlayOutScoreboardDisplayObjectiveStorage.buildSilent(slot.ordinal(), objective));
}
@Override
public void registerObjective0(@NonNull String objectiveName, @NonNull String title, boolean hearts) {
player.sendPacket(PacketPlayOutScoreboardObjectiveStorage.buildSilent(0, objectiveName, title, hearts, player.getVersion()));
}
@Override
public void unregisterObjective0(@NonNull String objectiveName) {
player.sendPacket(PacketPlayOutScoreboardObjectiveStorage.buildSilent(1, objectiveName, "", false, player.getVersion()));
}
@Override
public void updateObjective0(@NonNull String objectiveName, @NonNull String title, boolean hearts) {
player.sendPacket(PacketPlayOutScoreboardObjectiveStorage.buildSilent(2, objectiveName, title, hearts, player.getVersion()));
}
@Override
public void registerTeam0(@NonNull String name, String prefix, String suffix, String visibility, String collision, Collection<String> players, int options) {
player.sendPacket(PacketPlayOutScoreboardTeamStorage.register(name, prefix, suffix, visibility, collision, players, options, player.getVersion()));
}
@Override
public void unregisterTeam0(@NonNull String name) {
player.sendPacket(PacketPlayOutScoreboardTeamStorage.unregister(name));
}
@Override
public void updateTeam0(@NonNull String name, String prefix, String suffix, String visibility, String collision, int options) {
player.sendPacket(PacketPlayOutScoreboardTeamStorage.update(name, prefix, suffix, visibility, collision, options, player.getVersion()));
}
@Override
public void setScore0(@NonNull String objective, @NonNull String playerName, int score) {
player.sendPacket(PacketPlayOutScoreboardScoreStorage.change(objective, playerName, score));
}
@Override
public void removeScore0(@NonNull String objective, @NonNull String playerName) {
player.sendPacket(PacketPlayOutScoreboardScoreStorage.remove(objective, playerName));
}
}
@@ -5,7 +5,6 @@ import me.neznamy.tab.api.TabAPI;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.api.chat.EnumChatFormat;
import me.neznamy.tab.api.util.SupplierWithException;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.*;
import me.neznamy.tab.shared.TAB;
import org.bstats.bukkit.Metrics;
@@ -19,6 +18,7 @@ import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Callable;
/**
* Main class for Bukkit platform
@@ -72,7 +72,8 @@ public class BukkitTAB extends JavaPlugin {
"v1_7_R1", "v1_7_R2", "v1_7_R3", "v1_7_R4", "v1_8_R1", "v1_8_R2", "v1_8_R3",
"v1_9_R1", "v1_9_R2", "v1_10_R1", "v1_11_R1", "v1_12_R1", "v1_13_R1", "v1_13_R2",
"v1_14_R1", "v1_15_R1", "v1_16_R1", "v1_16_R2", "v1_16_R3", "v1_17_R1", "v1_18_R1",
"v1_18_R2", "v1_19_R1", "v1_19_R2");
"v1_18_R2", "v1_19_R1", "v1_19_R2", "v1_19_R3");
String supportedVersionRange = "1.5 - 1.19.4";
String serverPackage = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3];
try {
long time = System.currentTimeMillis();
@@ -81,27 +82,27 @@ public class BukkitTAB extends JavaPlugin {
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.color("[TAB] Loaded NMS hook in " + (System.currentTimeMillis()-time) + "ms"));
return true;
} else {
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.color("&c[TAB] No compatibility issue was found, but this plugin version does not claim to support your server package (" + serverPackage + "). This jar has only been tested on 1.5.2 - 1.19.3. Disabling just to stay safe."));
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.color("&c[TAB] No compatibility issue was found, but this plugin version does not claim to support your server package (" + serverPackage + "). This jar has only been tested on " + supportedVersionRange + ". Disabling just to stay safe."));
}
} catch (IllegalStateException ex) {
if (supportedVersions.contains(serverPackage)) {
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.color("&c[TAB] Your server version is marked as compatible, but a compatibility issue was found. Please report this issue (include your server version & fork too)"));
} else {
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.color("&c[TAB] Your server version is completely unsupported. This plugin version only supports 1.5.2 - 1.19.3. Disabling."));
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.color("&c[TAB] Your server version is completely unsupported. This plugin version only supports " + supportedVersionRange + ". Disabling."));
}
}
return false;
}
private NMSStorage getNMSLoader() {
List<SupplierWithException<NMSStorage>> loaders = new ArrayList<>();
List<Callable<NMSStorage>> loaders = new ArrayList<>();
loaders.add(BukkitLegacyNMSStorage::new);
loaders.add(BukkitModernNMSStorage::new);
loaders.add(MojangModernNMSStorage::new);
loaders.add(ThermosNMSStorage::new);
for (SupplierWithException<NMSStorage> loader : loaders) {
for (Callable<NMSStorage> loader : loaders) {
try {
return loader.get();
return loader.call();
} catch (Exception ignored) {}
}
throw new IllegalStateException("Unsupported server version");
@@ -0,0 +1,84 @@
package me.neznamy.tab.platforms.bukkit;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.api.chat.IChatBaseComponent;
import me.neznamy.tab.api.tablist.TabListEntry;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutPlayerInfoStorage;
import me.neznamy.tab.shared.tablist.BulkUpdateTabList;
import java.util.*;
import java.util.stream.Collectors;
/**
* TabList which support modifying many entries at once
* for significantly better performance. For 1.7 players,
* ViaVersion properly splits the packet into multiple, so
* we don't need to worry about that here.
* <p>
* This class does not support server versions of 1.7 and
* below, because of the massive differences in tablist
* and packet fields.
*/
@RequiredArgsConstructor
public class BukkitTabList extends BulkUpdateTabList {
/** Player this TabList belongs to */
private final BukkitTabPlayer player;
@Override
public void removeEntries(@NonNull Collection<UUID> entries) {
if (PacketPlayOutPlayerInfoStorage.ClientboundPlayerInfoRemovePacket != null) {
//1.19.3+
try {
player.sendPacket(PacketPlayOutPlayerInfoStorage.newClientboundPlayerInfoRemovePacket.newInstance(new ArrayList<>(entries)));
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
} else {
//1.19.2-
player.sendPacket(PacketPlayOutPlayerInfoStorage.createPacket("REMOVE_PLAYER",
entries.stream().map(id ->
new TabListEntry.Builder(id).build()).collect(Collectors.toList()),
player.getVersion())
);
}
}
@Override
public void updateDisplayNames(@NonNull Map<UUID, IChatBaseComponent> entries) {
player.sendPacket(PacketPlayOutPlayerInfoStorage.createPacket("UPDATE_DISPLAY_NAME",
entries.entrySet().stream().map(entry ->
new TabListEntry.Builder(entry.getKey()).displayName(entry.getValue()).build()).collect(Collectors.toList()),
player.getVersion())
);
}
@Override
public void updateLatencies(@NonNull Map<UUID, Integer> entries) {
player.sendPacket(PacketPlayOutPlayerInfoStorage.createPacket("UPDATE_LATENCY",
entries.entrySet().stream().map(entry ->
new TabListEntry.Builder(entry.getKey()).latency(entry.getValue()).build()).collect(Collectors.toList()),
player.getVersion())
);
}
@Override
public void updateGameModes(@NonNull Map<UUID, Integer> entries) {
player.sendPacket(PacketPlayOutPlayerInfoStorage.createPacket("UPDATE_GAME_MODE",
entries.entrySet().stream().map(entry ->
new TabListEntry.Builder(entry.getKey()).gameMode(entry.getValue()).build()).collect(Collectors.toList()),
player.getVersion())
);
}
@Override
public void addEntries(@NonNull Collection<TabListEntry> entries) {
player.sendPacket(PacketPlayOutPlayerInfoStorage.createPacket("ADD_PLAYER",
entries.stream().map(entry ->
new TabListEntry(entry.getUniqueId(), entry.getName(), entry.getSkin(), entry.isListed(),
entry.getLatency(), entry.getGameMode(), entry.getDisplayName(), entry.getChatSession())).collect(Collectors.toList()),
player.getVersion())
);
}
}
@@ -2,50 +2,58 @@ package me.neznamy.tab.platforms.bukkit;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
import com.viaversion.viaversion.api.Via;
import com.viaversion.viaversion.api.legacy.bossbar.BossColor;
import com.viaversion.viaversion.api.legacy.bossbar.BossFlag;
import com.viaversion.viaversion.api.legacy.bossbar.BossStyle;
import io.netty.channel.Channel;
import lombok.Getter;
import lombok.NonNull;
import me.neznamy.tab.api.chat.rgb.RGBUtils;
import me.neznamy.tab.api.protocol.PacketPlayOutBoss;
import me.neznamy.tab.api.protocol.PacketPlayOutChat;
import me.neznamy.tab.api.protocol.Skin;
import me.neznamy.tab.api.BossBarHandler;
import me.neznamy.tab.api.Scoreboard;
import me.neznamy.tab.api.chat.IChatBaseComponent;
import me.neznamy.tab.api.tablist.Skin;
import me.neznamy.tab.api.tablist.TabList;
import me.neznamy.tab.api.util.ComponentCache;
import me.neznamy.tab.api.util.ReflectionUtils;
import me.neznamy.tab.platforms.bukkit.bossbar.BukkitBossBar1_8;
import me.neznamy.tab.platforms.bukkit.bossbar.BukkitBossBar1_9;
import me.neznamy.tab.platforms.bukkit.bossbar.BukkitBossBarVia;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import me.neznamy.tab.shared.ITabPlayer;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.*;
import me.neznamy.tab.shared.TAB;
import org.bukkit.Bukkit;
import org.bukkit.boss.BarColor;
import org.bukkit.boss.BarFlag;
import org.bukkit.boss.BarStyle;
import org.bukkit.boss.BossBar;
import me.neznamy.tab.shared.backend.BackendTabPlayer;
import me.neznamy.tab.shared.backend.EntityData;
import me.neznamy.tab.shared.backend.Location;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.chat.ComponentSerializer;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.metadata.MetadataValue;
import org.bukkit.potion.PotionEffectType;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* TabPlayer implementation for Bukkit platform
*/
public class BukkitTabPlayer extends ITabPlayer {
@SuppressWarnings("deprecation")
public class BukkitTabPlayer extends BackendTabPlayer {
/** Spigot check */
private static final boolean spigot = ReflectionUtils.classExists("org.bukkit.entity.Player$Spigot");
/** Component cache to save CPU when creating components */
private static final ComponentCache<IChatBaseComponent, BaseComponent[]> componentCache = new ComponentCache<>(10000,
(component, clientVersion) -> ComponentSerializer.parse(component.toString(clientVersion)));
/** Player's NMS handle (EntityPlayer), preloading for speed */
private Object handle;
/** Player's connection for sending packets, preloading for speed */
private Object playerConnection;
/** Bukkit BossBars the player can currently see */
private final Map<UUID, BossBar> bossBars = new HashMap<>();
@Getter private Object playerConnection;
/** ViaVersion BossBars this 1.9+ player can see on 1.8 server */
private final Map<UUID, com.viaversion.viaversion.api.legacy.bossbar.BossBar> viaBossBars = new HashMap<>();
@Getter private final Scoreboard scoreboard = new BukkitScoreboard(this);
@Getter private final TabList tabList = new BukkitTabList(this);
@Getter private final BossBarHandler bossBarHandler = TAB.getInstance().getServerVersion().getMinorVersion() >= 9 ?
new BukkitBossBar1_9(this) : getVersion().getMinorVersion() >= 9 ? new BukkitBossBarVia(this) : new BukkitBossBar1_8(this);
/**
* Constructs new instance with given bukkit player and protocol version
@@ -56,7 +64,7 @@ public class BukkitTabPlayer extends ITabPlayer {
* Player's protocol network id
*/
public BukkitTabPlayer(Player p, int protocolVersion) {
super(p, p.getUniqueId(), p.getName(), TAB.getInstance().getConfiguration().getServerName(), p.getWorld().getName(), protocolVersion, true);
super(p, p.getUniqueId(), p.getName(), TAB.getInstance().getConfiguration().getServerName(), p.getWorld().getName(), protocolVersion);
try {
handle = NMSStorage.getInstance().getHandle.invoke(player);
playerConnection = NMSStorage.getInstance().PLAYER_CONNECTION.get(handle);
@@ -67,10 +75,7 @@ public class BukkitTabPlayer extends ITabPlayer {
@Override
public boolean hasPermission(@NonNull String permission) {
long time = System.nanoTime();
boolean value = getPlayer().hasPermission(permission);
TAB.getInstance().getCPUManager().addMethodTime("hasPermission", System.nanoTime()-time);
return value;
return getPlayer().hasPermission(permission);
}
@Override
@@ -87,150 +92,20 @@ public class BukkitTabPlayer extends ITabPlayer {
@Override
public void sendPacket(Object nmsPacket) {
if (nmsPacket == null || !getPlayer().isOnline()) return;
long time = System.nanoTime();
try {
if (nmsPacket instanceof PacketPlayOutBoss) {
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 9) {
handle((PacketPlayOutBoss) nmsPacket);
} else {
handleVia((PacketPlayOutBoss) nmsPacket);
}
} else if (nmsPacket instanceof PacketPlayOutChat) {
getPlayer().sendMessage(((PacketPlayOutChat) nmsPacket).getMessage().toLegacyText());
} else {
NMSStorage.getInstance().sendPacket.invoke(playerConnection, nmsPacket);
}
NMSStorage.getInstance().sendPacket.invoke(playerConnection, nmsPacket);
} catch (ReflectiveOperationException e) {
TAB.getInstance().getErrorManager().printError("An error occurred when sending " + nmsPacket.getClass().getSimpleName(), e);
}
TAB.getInstance().getCPUManager().addMethodTime("sendPacket", System.nanoTime()-time);
}
/**
* Handles PacketPlayOutBoss packet send request using Bukkit API,
* since the API offers everything we need and makes us not need to
* deal with NMS code at all.
*
* @param packet
* packet request to handle using Bukkit API
*/
private void handle(PacketPlayOutBoss packet) {
BossBar bar = bossBars.get(packet.getId());
if (packet.getAction() == PacketPlayOutBoss.Action.ADD) {
if (bossBars.containsKey(packet.getId())) return;
bar = Bukkit.createBossBar(RGBUtils.getInstance().convertToBukkitFormat(packet.getName(), getVersion().getMinorVersion() >= 16 && TAB.getInstance().getServerVersion().getMinorVersion() >= 16),
BarColor.valueOf(packet.getColor().name()),
BarStyle.valueOf(packet.getOverlay().getBukkitName()));
if (packet.isCreateWorldFog()) bar.addFlag(BarFlag.CREATE_FOG);
if (packet.isDarkenScreen()) bar.addFlag(BarFlag.DARKEN_SKY);
if (packet.isPlayMusic()) bar.addFlag(BarFlag.PLAY_BOSS_MUSIC);
bar.setProgress(packet.getPct());
bossBars.put(packet.getId(), bar);
bar.addPlayer(getPlayer());
return;
@Override
public void sendMessage(IChatBaseComponent message) {
if (spigot) {
getPlayer().spigot().sendMessage(componentCache.get(message, version));
} else {
getPlayer().sendMessage(message.toLegacyText());
}
if (bar == null) return; //no idea how
switch (packet.getAction()) {
case REMOVE:
bar.removePlayer(getPlayer());
bossBars.remove(packet.getId());
break;
case UPDATE_PCT:
bar.setProgress(packet.getPct());
break;
case UPDATE_NAME:
bar.setTitle(RGBUtils.getInstance().convertToBukkitFormat(packet.getName(), getVersion().getMinorVersion() >= 16 && TAB.getInstance().getServerVersion().getMinorVersion() >= 16));
break;
case UPDATE_STYLE:
bar.setColor(BarColor.valueOf(packet.getColor().name()));
bar.setStyle(BarStyle.valueOf(packet.getOverlay().getBukkitName()));
break;
case UPDATE_PROPERTIES:
bar = bossBars.get(packet.getId());
processFlag(bar, packet.isCreateWorldFog(), BarFlag.CREATE_FOG);
processFlag(bar, packet.isDarkenScreen(), BarFlag.DARKEN_SKY);
processFlag(bar, packet.isPlayMusic(), BarFlag.PLAY_BOSS_MUSIC);
break;
default:
break;
}
}
/**
* Handles PacketPlayOutBoss packet request for 1.9+ clients on
* 1.8 servers using ViaVersion API instead of using Wither.
*
* @param packet
* packet request to handle using ViaVersion API
*/
private void handleVia(PacketPlayOutBoss packet) {
com.viaversion.viaversion.api.legacy.bossbar.BossBar bar;
switch (packet.getAction()) {
case ADD:
if (viaBossBars.containsKey(packet.getId())) return;
bar = Via.getAPI().legacyAPI().createLegacyBossBar(RGBUtils.getInstance().convertToBukkitFormat(packet.getName(), getVersion().getMinorVersion() >= 16),
packet.getPct(),
BossColor.valueOf(packet.getColor().name()),
BossStyle.valueOf(packet.getOverlay().getBukkitName()));
//fog missing from via API
if (packet.isDarkenScreen()) bar.addFlag(BossFlag.DARKEN_SKY);
if (packet.isPlayMusic()) bar.addFlag(BossFlag.PLAY_BOSS_MUSIC);
viaBossBars.put(packet.getId(), bar);
bar.addPlayer(getPlayer().getUniqueId());
break;
case REMOVE:
viaBossBars.get(packet.getId()).removePlayer(getPlayer().getUniqueId());
viaBossBars.remove(packet.getId());
break;
case UPDATE_PCT:
viaBossBars.get(packet.getId()).setHealth(packet.getPct());
break;
case UPDATE_NAME:
viaBossBars.get(packet.getId()).setTitle(RGBUtils.getInstance().convertToBukkitFormat(packet.getName(), getVersion().getMinorVersion() >= 16));
break;
case UPDATE_STYLE:
viaBossBars.get(packet.getId()).setColor(BossColor.valueOf(packet.getColor().name()));
viaBossBars.get(packet.getId()).setStyle(BossStyle.valueOf(packet.getOverlay().getBukkitName()));
break;
case UPDATE_PROPERTIES:
bar = viaBossBars.get(packet.getId());
//fog missing from via API
processFlagVia(bar, packet.isDarkenScreen(), BossFlag.DARKEN_SKY);
processFlagVia(bar, packet.isPlayMusic(), BossFlag.PLAY_BOSS_MUSIC);
break;
default:
break;
}
}
/**
* Sets BossBar flag to requested target value.
*
* @param bar
* BossBar to set flag of
* @param targetValue
* Target value of the flag
* @param flag
* Flag to set value of
*/
private void processFlag(BossBar bar, boolean targetValue, BarFlag flag) {
if (targetValue && !bar.hasFlag(flag)) bar.addFlag(flag);
if (!targetValue && bar.hasFlag(flag)) bar.removeFlag(flag);
}
/**
* Sets BossBar flag to requested target value.
*
* @param bar
* BossBar to set flag of
* @param targetValue
* Target value of the flag
* @param flag
* Flag to set value of
*/
private void processFlagVia(com.viaversion.viaversion.api.legacy.bossbar.BossBar bar, boolean targetValue, BossFlag flag) {
if (targetValue && !bar.hasFlag(flag)) bar.addFlag(flag);
if (!targetValue && bar.hasFlag(flag)) bar.removeFlag(flag);
}
@Override
@@ -254,7 +129,7 @@ public class BukkitTabPlayer extends ITabPlayer {
@Override
public Skin getSkin() {
try {
Collection<Property> col = ((GameProfile)NMSStorage.getInstance().getProfile.invoke(handle)).getProperties().get("textures");
Collection<Property> col = ((GameProfile)NMSStorage.getInstance().getProfile.invoke(handle)).getProperties().get(TabList.TEXTURES_PROPERTY);
if (col.isEmpty()) return null; //offline mode
Property property = col.iterator().next();
return new Skin(property.getValue(), property.getSignature());
@@ -279,20 +154,81 @@ public class BukkitTabPlayer extends ITabPlayer {
return getPlayer().getMetadata("vanished").stream().anyMatch(MetadataValue::asBoolean);
}
@SuppressWarnings("deprecation")
@Override
public int getGamemode() {
return getPlayer().getGameMode().getValue();
}
@Override
public Channel getChannel() {
public void setPlayerListHeaderFooter(@NonNull IChatBaseComponent header, @NonNull IChatBaseComponent footer) {
// Method was added to Bukkit API in 1.13.1, however despite that it's just a String one
// Using it would cause high CPU usage and massive memory allocations on RGB & animations
// Send packet instead for performance & older server version support
/*if (TAB.getInstance().getServerVersion().getNetworkId() >= ProtocolVersion.V1_13_1.getNetworkId()) {
String bukkitHeader = RGBUtils.getInstance().convertToBukkitFormat(header.toFlatText(),
getVersion().getMinorVersion() >= 16 && TAB.getInstance().getServerVersion().getMinorVersion() >= 16);
String bukkitFooter = RGBUtils.getInstance().convertToBukkitFormat(footer.toFlatText(),
getVersion().getMinorVersion() >= 16 && TAB.getInstance().getServerVersion().getMinorVersion() >= 16);
getPlayer().setPlayerListHeaderFooter(bukkitHeader, bukkitFooter);
return;
}*/
try {
if (NMSStorage.getInstance().CHANNEL != null)
return (Channel) NMSStorage.getInstance().CHANNEL.get(NMSStorage.getInstance().NETWORK_MANAGER.get(playerConnection));
} catch (IllegalAccessException e) {
TAB.getInstance().getErrorManager().printError("Failed to get channel of " + getName(), e);
sendPacket(PacketPlayOutPlayerListHeaderFooterStorage.build(header, footer, version));
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
@Override
public void spawnEntity(int entityId, UUID id, Object entityType, Location location, EntityData data) {
try {
sendPacket(PacketPlayOutSpawnEntityLivingStorage.build(entityId, id, entityType, location, data));
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 15) {
updateEntityMetadata(entityId, data);
}
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
@Override
public void updateEntityMetadata(int entityId, EntityData data) {
try {
if (PacketPlayOutEntityMetadataStorage.CONSTRUCTOR.getParameterCount() == 2) {
//1.19.3+
sendPacket(PacketPlayOutEntityMetadataStorage.CONSTRUCTOR.newInstance(entityId, DataWatcher.packDirty.invoke(data.build())));
} else {
sendPacket(PacketPlayOutEntityMetadataStorage.CONSTRUCTOR.newInstance(entityId, data.build(), true));
}
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
@Override
public void teleportEntity(int entityId, Location location) {
try {
sendPacket(PacketPlayOutEntityTeleportStorage.build(entityId, location));
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
@Override
public void destroyEntities(int... entities) {
try {
if (PacketPlayOutEntityDestroyStorage.CONSTRUCTOR.getParameterTypes()[0] != int.class) {
sendPacket(PacketPlayOutEntityDestroyStorage.CONSTRUCTOR.newInstance(new Object[]{entities}));
} else {
//1.17.0 Mojank
for (int entity : entities) {
sendPacket(PacketPlayOutEntityDestroyStorage.CONSTRUCTOR.newInstance(entity));
}
}
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
return null;
}
}
@@ -0,0 +1,64 @@
package me.neznamy.tab.platforms.bukkit.bossbar;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.api.BossBarHandler;
import me.neznamy.tab.api.bossbar.BarColor;
import me.neznamy.tab.api.bossbar.BarStyle;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
import me.neznamy.tab.shared.backend.Location;
import org.bukkit.entity.EntityType;
import java.util.UUID;
/**
* BossBar using wither entity for <1.9 players on <1.9 servers.
* Additional logic, such as teleporting the entity must be done
* separately, as this class does not handle it.
*/
@RequiredArgsConstructor
public class BukkitBossBar1_8 implements BossBarHandler {
/** Player this handler belongs to */
private final BukkitTabPlayer player;
@Override
public void create(@NonNull UUID id, @NonNull String title, float progress, @NonNull BarColor color, @NonNull BarStyle style) {
DataWatcher w = new DataWatcher();
float health = 300*progress;
if (health == 0) health = 1;
w.getHelper().setHealth(health);
w.getHelper().setCustomName(title, player.getVersion());
w.getHelper().setEntityFlags((byte) 32);
w.getHelper().setWitherInvulnerableTime(880); // Magic number
player.spawnEntity(id.hashCode(), new UUID(0, 0), EntityType.WITHER, new Location(0, 0, 0, 0, 0), w);
}
@Override
public void update(@NonNull UUID id, @NonNull String title) {
DataWatcher w = new DataWatcher();
w.getHelper().setCustomName(title, player.getVersion());
player.updateEntityMetadata(id.hashCode(), w);
}
@Override
public void update(@NonNull UUID id, float progress) {
DataWatcher w = new DataWatcher();
float health = 300*progress;
if (health == 0) health = 1;
w.getHelper().setHealth(health);
player.updateEntityMetadata(id.hashCode(), w);
}
@Override
public void update(@NonNull UUID id, @NonNull BarStyle style) {} // Added in 1.9
@Override
public void update(@NonNull UUID id, @NonNull BarColor color) {} // Added in 1.9
@Override
public void remove(@NonNull UUID id) {
player.destroyEntities(id.hashCode());
}
}
@@ -0,0 +1,67 @@
package me.neznamy.tab.platforms.bukkit.bossbar;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.api.BossBarHandler;
import me.neznamy.tab.api.chat.rgb.RGBUtils;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.TAB;
import org.bukkit.Bukkit;
import org.bukkit.boss.BossBar;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* Bossbar for 1.9+ servers where Bukkit API is used. If ViaVersion is used
* to allow 1.8 players, it will handle the entity and teleporting by itself.
*/
@RequiredArgsConstructor
public class BukkitBossBar1_9 implements BossBarHandler {
/** Player this handler belongs to */
private final BukkitTabPlayer player;
/** Bukkit BossBars the player can currently see */
private final Map<UUID, BossBar> bossBars = new HashMap<>();
@Override
public void create(@NonNull UUID id, @NonNull String title, float progress, me.neznamy.tab.api.bossbar.@NonNull BarColor color, me.neznamy.tab.api.bossbar.@NonNull BarStyle style) {
if (bossBars.containsKey(id)) return;
BossBar bar = Bukkit.createBossBar(
RGBUtils.getInstance().convertToBukkitFormat(title,
player.getVersion().getMinorVersion() >= 16 && TAB.getInstance().getServerVersion().getMinorVersion() >= 16),
org.bukkit.boss.BarColor.valueOf(color.name()),
org.bukkit.boss.BarStyle.valueOf(style.getBukkitName()));
bar.setProgress(progress);
bar.addPlayer(player.getPlayer());
bossBars.put(id, bar);
}
@Override
public void update(@NonNull UUID id, @NonNull String title) {
bossBars.get(id).setTitle(RGBUtils.getInstance().convertToBukkitFormat(title,
player.getVersion().getMinorVersion() >= 16 && TAB.getInstance().getServerVersion().getMinorVersion() >= 16));
}
@Override
public void update(@NonNull UUID id, float progress) {
bossBars.get(id).setProgress(progress);
}
@Override
public void update(@NonNull UUID id, me.neznamy.tab.api.bossbar.@NonNull BarStyle style) {
bossBars.get(id).setStyle(org.bukkit.boss.BarStyle.valueOf(style.getBukkitName()));
}
@Override
public void update(@NonNull UUID id, me.neznamy.tab.api.bossbar.@NonNull BarColor color) {
bossBars.get(id).setColor(org.bukkit.boss.BarColor.valueOf(color.name()));
}
@Override
public void remove(@NonNull UUID id) {
bossBars.remove(id).removePlayer(player.getPlayer());
}
}
@@ -0,0 +1,68 @@
package me.neznamy.tab.platforms.bukkit.bossbar;
import com.viaversion.viaversion.api.Via;
import com.viaversion.viaversion.api.legacy.bossbar.BossBar;
import com.viaversion.viaversion.api.legacy.bossbar.BossColor;
import com.viaversion.viaversion.api.legacy.bossbar.BossStyle;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.api.BossBarHandler;
import me.neznamy.tab.api.bossbar.BarColor;
import me.neznamy.tab.api.bossbar.BarStyle;
import me.neznamy.tab.api.chat.rgb.RGBUtils;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* Handler for 1.9+ players on 1.8 server using ViaVersion API.
*/
@RequiredArgsConstructor
public class BukkitBossBarVia implements BossBarHandler {
/** Player this handler belongs to */
private final BukkitTabPlayer player;
/** ViaVersion BossBars this 1.9+ player can see on 1.8 server */
private final Map<UUID, BossBar> viaBossBars = new HashMap<>();
@Override
public void create(@NonNull UUID id, @NonNull String title, float progress, @NonNull BarColor color, @NonNull BarStyle style) {
if (viaBossBars.containsKey(id)) return;
BossBar bar = Via.getAPI().legacyAPI().createLegacyBossBar(
RGBUtils.getInstance().convertToBukkitFormat(title, player.getVersion().getMinorVersion() >= 16),
progress,
BossColor.valueOf(color.name()),
BossStyle.valueOf(style.getBukkitName()));
viaBossBars.put(id, bar);
bar.addPlayer(player.getPlayer().getUniqueId());
}
@Override
public void update(@NonNull UUID id, @NonNull String title) {
viaBossBars.get(id).setTitle(RGBUtils.getInstance().convertToBukkitFormat(title,
player.getVersion().getMinorVersion() >= 16));
}
@Override
public void update(@NonNull UUID id, float progress) {
viaBossBars.get(id).setHealth(progress);
}
@Override
public void update(@NonNull UUID id, @NonNull BarStyle style) {
viaBossBars.get(id).setStyle(BossStyle.valueOf(style.getBukkitName()));
}
@Override
public void update(@NonNull UUID id, @NonNull BarColor color) {
viaBossBars.get(id).setColor(BossColor.valueOf(color.name()));
}
@Override
public void remove(@NonNull UUID id) {
viaBossBars.remove(id).removePlayer(player.getPlayer().getUniqueId());
}
}
@@ -5,10 +5,9 @@ import me.clip.placeholderapi.PlaceholderAPI;
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
import me.neznamy.tab.api.TabAPI;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.api.TabFeature;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.features.TabExpansion;
import me.neznamy.tab.shared.placeholders.expansion.TabExpansion;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
@@ -22,9 +21,9 @@ 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 Map<TabPlayer, Map<String, String>> values = new WeakHashMap<>();
private final WeakHashMap<Player, Map<String, String>> values = new WeakHashMap<>();
@Getter private final String author = "NEZNAMY";
@Getter private final String author = TabConstants.PLUGIN_AUTHOR;
@Getter private final String identifier = TabConstants.PLUGIN_ID;
@Getter private final String version = TabConstants.PLUGIN_VERSION;
@@ -48,16 +47,13 @@ public class BukkitTabExpansion extends PlaceholderExpansion implements TabExpan
return text;
}
if (identifier.startsWith("placeholder_")) {
TabAPI.getInstance().getPlaceholderManager().addUsedPlaceholder("%" + identifier.substring(12) + "%", (TabFeature) TabAPI.getInstance().getPlaceholderManager());
TabAPI.getInstance().getPlaceholderManager().addUsedPlaceholder("%" + identifier.substring(12) + "%", TabAPI.getInstance().getPlaceholderManager());
}
if (player == null) return "<Player cannot be null>";
TabPlayer p = TabAPI.getInstance().getPlayer(player.getUniqueId());
if (p == null || !p.isLoaded()) return "<Player is not loaded>";
return values.get(p).get(identifier);
return values.get(player).get(identifier);
}
@Override
public void setValue(TabPlayer player, String key, String value) {
values.computeIfAbsent(player, p -> new HashMap<>()).put(key, value);
values.computeIfAbsent((Player) player.getPlayer(), p -> new HashMap<>()).put(key, value);
}
}
@@ -5,7 +5,11 @@ import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.api.TabAPI;
import me.neznamy.tab.api.feature.Loadable;
import me.neznamy.tab.api.feature.UnLoadable;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
@@ -15,7 +19,7 @@ import org.bukkit.event.player.PlayerChangedWorldEvent;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.plugin.java.JavaPlugin;
import me.neznamy.tab.api.TabFeature;
import me.neznamy.tab.api.feature.TabFeature;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.shared.TAB;
@@ -23,28 +27,23 @@ import me.neznamy.tab.shared.TAB;
* Per-world-PlayerList feature handler
*/
@SuppressWarnings("deprecation")
public class PerWorldPlayerList extends TabFeature implements Listener {
@RequiredArgsConstructor
public class PerWorldPlayerList extends TabFeature implements Listener, Loadable, UnLoadable {
/** Config options */
private final boolean allowBypass = TabAPI.getInstance().getConfig().getBoolean("per-world-playerlist.allow-bypass-permission", false);
private final List<String> ignoredWorlds = TabAPI.getInstance().getConfig().getStringList("per-world-playerlist.ignore-effect-in-worlds", Arrays.asList("ignoredworld", "build"));
private final Map<String, List<String>> sharedWorlds = TabAPI.getInstance().getConfig().getConfigurationSection("per-world-playerlist.shared-playerlist-world-groups");
/**
* Constructs new instance with given parameters and loads config options
*
* @param plugin
* plugin instance
*/
public PerWorldPlayerList(JavaPlugin plugin) {
super("Per world PlayerList", null);
Bukkit.getPluginManager().registerEvents(this, plugin);
TabAPI.getInstance().debug(String.format("Loaded PerWorldPlayerList feature with parameters allowBypass=%s, ignoredWorlds=%s, sharedWorlds=%s", allowBypass, ignoredWorlds, sharedWorlds));
}
/** Plugin reference*/
private final JavaPlugin plugin;
@Getter private final String featureName = "Per world PlayerList";
@Override
public void load() {
Bukkit.getOnlinePlayers().forEach(this::checkPlayer);
Bukkit.getPluginManager().registerEvents(this, plugin);
}
@Override
@@ -1,14 +1,16 @@
package me.neznamy.tab.platforms.bukkit.features;
import me.neznamy.tab.api.TabAPI;
import me.neznamy.tab.api.TabFeature;
import lombok.Getter;
import me.neznamy.tab.api.feature.PacketReceiveListener;
import me.neznamy.tab.api.feature.PacketSendListener;
import me.neznamy.tab.api.feature.TabFeature;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityMetadata;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutSpawnEntityLiving;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherObject;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutEntityMetadataStorage;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutSpawnEntityLivingStorage;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherItem;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherObject;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import java.util.ConcurrentModificationException;
import java.util.List;
@@ -23,7 +25,7 @@ import java.util.WeakHashMap;
* Since 1.16 this results in client sending entity use packet twice,
* so we must cancel the 2nd one to prevent double toggle.
*/
public class PetFix extends TabFeature {
public class PetFix extends TabFeature implements PacketReceiveListener, PacketSendListener {
/** NMS Storage reference for quick access */
private final NMSStorage nms = NMSStorage.getInstance();
@@ -46,13 +48,7 @@ public class PetFix extends TabFeature {
*/
private static final int INTERACT_COOLDOWN = 160;
/**
* Constructs new instance with given parameter
*/
public PetFix() {
super("Pet name fix", null);
TabAPI.getInstance().debug("Loaded PetFix feature");
}
@Getter private final String featureName = "Pet name fix";
/**
* Returns position of pet owner field based on server version
@@ -121,9 +117,9 @@ public class PetFix extends TabFeature {
@SuppressWarnings("unchecked")
@Override
public void onPacketSend(TabPlayer receiver, Object packet) throws ReflectiveOperationException {
if (PacketPlayOutEntityMetadata.CLASS.isInstance(packet)) {
if (PacketPlayOutEntityMetadataStorage.CLASS.isInstance(packet)) {
Object removedEntry = null;
List<Object> items = (List<Object>) PacketPlayOutEntityMetadata.LIST.get(packet);
List<Object> items = (List<Object>) PacketPlayOutEntityMetadataStorage.LIST.get(packet);
if (items == null) return;
try {
for (Object item : items) {
@@ -143,18 +139,18 @@ public class PetFix extends TabFeature {
}
}
}
if (removedEntry != null) items.remove(removedEntry);
} catch (ConcurrentModificationException e) {
//no idea how can this list change in another thread since it's created for the packet but whatever, try again
onPacketSend(receiver, packet);
}
if (removedEntry != null) items.remove(removedEntry);
} else if (PacketPlayOutSpawnEntityLiving.CLASS.isInstance(packet) && PacketPlayOutSpawnEntityLiving.DATA_WATCHER != null) {
} else if (PacketPlayOutSpawnEntityLivingStorage.CLASS.isInstance(packet) && PacketPlayOutSpawnEntityLivingStorage.DATA_WATCHER != null) {
//<1.15
DataWatcher watcher = DataWatcher.fromNMS(PacketPlayOutSpawnEntityLiving.DATA_WATCHER.get(packet));
DataWatcher watcher = DataWatcher.fromNMS(PacketPlayOutSpawnEntityLivingStorage.DATA_WATCHER.get(packet));
DataWatcherItem petOwner = watcher.getItem(petOwnerPosition);
if (petOwner != null && (petOwner.getValue() instanceof Optional || petOwner.getValue() instanceof com.google.common.base.Optional)) {
watcher.removeValue(petOwnerPosition);
PacketPlayOutSpawnEntityLiving.DATA_WATCHER.set(packet, watcher.build());
PacketPlayOutSpawnEntityLivingStorage.DATA_WATCHER.set(packet, watcher.build());
}
}
}
@@ -1,5 +1,12 @@
package me.neznamy.tab.platforms.bukkit.features;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.api.bossbar.BossBar;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.backend.BackendTabPlayer;
import me.neznamy.tab.shared.features.bossbar.BossBarManagerImpl;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.entity.Player;
@@ -9,37 +16,25 @@ import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerRespawnEvent;
import org.bukkit.plugin.java.JavaPlugin;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.api.bossbar.BossBar;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityTeleport;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.features.bossbar.BossBarManagerImpl;
/**
* An additional class with additional code for &lt;1.9 servers due to an entity being required
*/
@RequiredArgsConstructor
public class WitherBossBar extends BossBarManagerImpl implements Listener {
/** Distance of the Wither in blocks */
private static final int WITHER_DISTANCE = 60;
/**
* Constructs a new instance of the class and registers events and task
*
* @param plugin
* plugin instance
*/
public WitherBossBar(JavaPlugin plugin) {
/** Reference to plugin for registering listener */
private final JavaPlugin plugin;
@Override
public void load() {
Bukkit.getPluginManager().registerEvents(this, plugin);
//when MC is on fullscreen, BossBar disappears after 1 second of not being seen
//when in a small window, it's about 100ms
TAB.getInstance().getCPUManager().startRepeatingMeasuredTask(100,
this, TabConstants.CpuUsageCategory.TELEPORTING_WITHER, this::teleport);
}
@Override
public void load() {
super.load();
teleport();
}
@@ -54,7 +49,7 @@ public class WitherBossBar extends BossBarManagerImpl implements Listener {
if (!line.containsPlayer(p)) continue;
Location loc = ((Player) p.getPlayer()).getEyeLocation().add(((Player) p.getPlayer()).getEyeLocation().getDirection().normalize().multiply(WITHER_DISTANCE));
if (loc.getY() < 1) loc.setY(1);
p.sendCustomPacket(new PacketPlayOutEntityTeleport(line.getUniqueId().hashCode(), loc), TabConstants.PacketCategory.BOSSBAR_WITHER_TELEPORT);
((BackendTabPlayer)p).teleportEntity(line.getUniqueId().hashCode(), new me.neznamy.tab.shared.backend.Location(loc.getX(), loc.getY(), loc.getZ(), 0, 0));
}
}
}
@@ -1,155 +0,0 @@
package me.neznamy.tab.platforms.bukkit.features.unlimitedtags;
import me.neznamy.tab.api.TabAPI;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.api.protocol.TabPacket;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityDestroy;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityMetadata;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityTeleport;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutSpawnEntityLiving;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
import me.neznamy.tab.shared.backend.features.unlimitedtags.BackendArmorStand;
import me.neznamy.tab.shared.backend.features.unlimitedtags.BackendArmorStandManager;
import me.neznamy.tab.shared.backend.features.unlimitedtags.BackendNameTagX;
import org.bukkit.Location;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.entity.Pose;
/**
* A class representing an armor stand attached to a player
*/
public class BukkitArmorStand extends BackendArmorStand {
/** Owner as a Bukkit player */
private final Player player;
/** Entity destroy packet */
protected final TabPacket destroyPacket = new PacketPlayOutEntityDestroy(entityId);
/**
* Constructs new instance with given parameters.
*
* @param asm
* Armor stand manager which this armor stand belongs to
* @param owner
* Owner of the armor stand
* @param propertyName
* Name of refresh property to use
* @param yOffset
* Offset in blocks
* @param staticOffset
* {@code true} if offset is static, {@code false} if not
*/
public BukkitArmorStand(BackendNameTagX feature, BackendArmorStandManager asm, TabPlayer owner, String propertyName, double yOffset, boolean staticOffset) {
super(feature, asm, owner, propertyName, yOffset, staticOffset);
player = (Player) owner.getPlayer();
sneaking = player.isSneaking();
}
@Override
public void spawn(TabPlayer viewer) {
for (TabPacket packet : getSpawnPackets(viewer)) {
viewer.sendCustomPacket(packet, TabConstants.PacketCategory.UNLIMITED_NAMETAGS_SPAWN);
}
}
@Override
public void destroy(TabPlayer viewer) {
viewer.sendCustomPacket(destroyPacket, TabConstants.PacketCategory.UNLIMITED_NAMETAGS_DESPAWN);
}
/**
* Returns location where armor stand should be at time of calling.
* This takes into account everything that affects height, including
* viewer's game version.
*
* @param viewer
* Player looking at the armor stand
* @return Location where armor stand should be for specified viewer
*/
public Location getLocation(TabPlayer viewer) {
Location loc = player.getLocation();
double y = loc.getY();
//1.14+ server sided bug
Entity vehicle = player.getVehicle();
if (vehicle != null) {
if (vehicle.getType().toString().contains("horse")) { //covering all 3 horse types
y = vehicle.getLocation().getY() + 0.85;
}
if (vehicle.getType().toString().equals("donkey")) { //1.11+
y = vehicle.getLocation().getY() + 0.525;
}
if (vehicle.getType() == EntityType.PIG) {
y = vehicle.getLocation().getY() + 0.325;
}
if (vehicle.getType().toString().equals("strider")) { //1.16+
y = vehicle.getLocation().getY() + 1.15;
}
} else {
//1.13+ swimming or 1.9+ flying with elytra
if (isSwimming() || (TabAPI.getInstance().getServerVersion().getMinorVersion() >= 9 && player.isGliding())) {
y = loc.getY()-1.22;
}
}
y += getYAdd(player.isSleeping(), sneaking, viewer);
return new Location(null, loc.getX(), y, loc.getZ());
}
@Override
public void updateMetadata(TabPlayer viewer) {
viewer.sendCustomPacket(new PacketPlayOutEntityMetadata(entityId, createDataWatcher(property.getFormat(viewer), viewer)), TabConstants.PacketCategory.UNLIMITED_NAMETAGS_METADATA);
}
@Override
public void sendTeleportPacket(TabPlayer viewer) {
viewer.sendCustomPacket(new PacketPlayOutEntityTeleport(entityId, getLocation(viewer)),
TabConstants.PacketCategory.UNLIMITED_NAMETAGS_TELEPORT);
}
/**
* Returns {@code true} if owner is swimming, {@code false} if not
* @return {@code true} if owner is swimming, {@code false} if not
*/
private boolean isSwimming() {
if (TabAPI.getInstance().getServerVersion().getMinorVersion() >= 14 && player.getPose() == Pose.SWIMMING) return true;
return TabAPI.getInstance().getServerVersion().getMinorVersion() == 13 && player.isSwimming();
}
private TabPacket[] getSpawnPackets(TabPlayer viewer) {
visible = calculateVisibility();
DataWatcher dataWatcher = createDataWatcher(property.getFormat(viewer), viewer);
if (TabAPI.getInstance().getServerVersion().getMinorVersion() >= 15) {
return new TabPacket[] {
new PacketPlayOutSpawnEntityLiving(entityId, uuid, EntityType.ARMOR_STAND, getLocation(viewer), null),
new PacketPlayOutEntityMetadata(entityId, dataWatcher)
};
} else {
return new TabPacket[] {
new PacketPlayOutSpawnEntityLiving(entityId, uuid, EntityType.ARMOR_STAND, getLocation(viewer), dataWatcher),
};
}
}
/**
* Creates data watcher with specified display name for viewer
*
* @param displayName
* armor stand name
* @param viewer
* player to apply checks against
* @return DataWatcher for viewer
*/
public DataWatcher createDataWatcher(String displayName, TabPlayer viewer) {
DataWatcher datawatcher = new DataWatcher();
datawatcher.getHelper().setEntityFlags((byte) (sneaking ? 34 : 32));
datawatcher.getHelper().setCustomName(displayName, viewer.getVersion());
datawatcher.getHelper().setCustomNameVisible(!shouldBeInvisibleFor(viewer, displayName) && visible);
if (viewer.getVersion().getMinorVersion() > 8 || manager.isMarkerFor18x())
datawatcher.getHelper().setArmorStandFlags((byte)16);
return datawatcher;
}
}
@@ -1,18 +1,23 @@
package me.neznamy.tab.platforms.bukkit.features.unlimitedtags;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.api.TabAPI;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityDestroy;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityTeleport;
import me.neznamy.tab.api.feature.PacketSendListener;
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.features.unlimitedtags.BackendArmorStand;
import me.neznamy.tab.shared.backend.features.unlimitedtags.BackendArmorStandManager;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutEntityDestroyStorage;
import me.neznamy.tab.platforms.bukkit.nms.storage.packet.PacketPlayOutEntityTeleportStorage;
import me.neznamy.tab.shared.backend.BackendTabPlayer;
import me.neznamy.tab.shared.backend.EntityData;
import me.neznamy.tab.shared.backend.features.unlimitedtags.BackendNameTagX;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.entity.Pose;
import org.bukkit.event.HandlerList;
import org.bukkit.plugin.java.JavaPlugin;
@@ -23,7 +28,8 @@ import java.util.stream.Collectors;
/**
* The core class for unlimited NameTag mode on Bukkit
*/
public class BukkitNameTagX extends BackendNameTagX {
@RequiredArgsConstructor
public class BukkitNameTagX extends BackendNameTagX implements PacketSendListener {
/** Reference to NMS storage for quick access */
private final NMSStorage nms = NMSStorage.getInstance();
@@ -31,33 +37,13 @@ public class BukkitNameTagX extends BackendNameTagX {
/** Bukkit event listener */
private final EventListener eventListener = new EventListener(this);
/**
* Constructs new instance with given parameter, loads config options, registers events
* and registers sub-features.
*
* @param plugin
* plugin instance
*/
public BukkitNameTagX(JavaPlugin plugin) {
Bukkit.getPluginManager().registerEvents(eventListener, plugin);
}
/** Plugin reference */
private final JavaPlugin plugin;
@Override
public boolean onPacketReceive(TabPlayer sender, Object packet) throws ReflectiveOperationException {
if (sender.getVersion().getMinorVersion() == 8 && nms.PacketPlayInUseEntity.isInstance(packet)) {
int entityId = nms.PacketPlayInUseEntity_ENTITY.getInt(packet);
TabPlayer attacked = null;
for (TabPlayer all : TabAPI.getInstance().getOnlinePlayers()) {
if (all.isLoaded() && getArmorStandManager(all).hasArmorStandWithID(entityId)) {
attacked = all;
break;
}
}
if (attacked != null && attacked != sender) {
nms.PacketPlayInUseEntity_ENTITY.set(packet, getEntityId(attacked));
}
}
return false;
public void load() {
Bukkit.getPluginManager().registerEvents(eventListener, plugin);
super.load();
}
@SuppressWarnings("unchecked")
@@ -66,22 +52,22 @@ public class BukkitNameTagX extends BackendNameTagX {
if (receiver.getVersion().getMinorVersion() < 8) return;
if (!receiver.isLoaded() || isDisabledPlayer(receiver) || getDisabledUnlimitedPlayers().contains(receiver)) return;
if (nms.PacketPlayOutEntity.isInstance(packet) && !nms.PacketPlayOutEntityLook.isInstance(packet)) { //ignoring head rotation only packets
packetListener.onEntityMove(receiver, nms.PacketPlayOutEntity_ENTITYID.getInt(packet));
} else if (PacketPlayOutEntityTeleport.CLASS.isInstance(packet)) {
packetListener.onEntityMove(receiver, PacketPlayOutEntityTeleport.ENTITY_ID.getInt(packet));
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(receiver, nms.PacketPlayOutNamedEntitySpawn_ENTITYID.getInt(packet));
} else if (PacketPlayOutEntityDestroy.CLASS.isInstance(packet)) {
packetListener.onEntitySpawn((BackendTabPlayer) receiver, nms.PacketPlayOutNamedEntitySpawn_ENTITYID.getInt(packet));
} else if (PacketPlayOutEntityDestroyStorage.CLASS.isInstance(packet)) {
if (nms.getMinorVersion() >= 17) {
Object entities = PacketPlayOutEntityDestroy.ENTITIES.get(packet);
Object entities = PacketPlayOutEntityDestroyStorage.ENTITIES.get(packet);
if (entities instanceof List) {
packetListener.onEntityDestroy(receiver, (List<Integer>) entities);
packetListener.onEntityDestroy((BackendTabPlayer) receiver, (List<Integer>) entities);
} else {
//1.17.0
packetListener.onEntityDestroy(receiver, (int) entities);
packetListener.onEntityDestroy((BackendTabPlayer) receiver, (int) entities);
}
} else {
packetListener.onEntityDestroy(receiver, (int[]) PacketPlayOutEntityDestroy.ENTITIES.get(packet));
packetListener.onEntityDestroy((BackendTabPlayer) receiver, (int[]) PacketPlayOutEntityDestroyStorage.ENTITIES.get(packet));
}
}
}
@@ -148,7 +134,54 @@ public class BukkitNameTagX extends BackendNameTagX {
}
@Override
public BackendArmorStand createArmorStand(BackendArmorStandManager feature, TabPlayer owner, String lineName, double yOffset, boolean staticOffset) {
return new BukkitArmorStand(this, feature, owner, lineName, yOffset, staticOffset);
public boolean isSneaking(TabPlayer player) {
return ((Player)player.getPlayer()).isSneaking();
}
@Override
public boolean isSwimming(TabPlayer player) {
Player p = (Player) player.getPlayer();
if (TabAPI.getInstance().getServerVersion().getMinorVersion() >= 14 && p.getPose() == Pose.SWIMMING) return true;
return TabAPI.getInstance().getServerVersion().getMinorVersion() == 13 && p.isSwimming();
}
@Override
public boolean isGliding(TabPlayer player) {
return TabAPI.getInstance().getServerVersion().getMinorVersion() >= 9 && ((Player)player.getPlayer()).isGliding();
}
@Override
public boolean isSleeping(TabPlayer player) {
return ((Player)player.getPlayer()).isSleeping();
}
@Override
public Object getArmorStandType() {
return EntityType.ARMOR_STAND;
}
@Override
public double getX(TabPlayer player) {
return ((Player)player.getPlayer()).getLocation().getX();
}
@Override
public double getY(Object entity) {
return ((Entity)entity).getLocation().getY();
}
@Override
public double getZ(TabPlayer player) {
return ((Player)player.getPlayer()).getLocation().getZ();
}
@Override
public EntityData createDataWatcher(TabPlayer viewer, byte flags, String displayName, boolean nameVisible) {
DataWatcher datawatcher = new DataWatcher();
datawatcher.getHelper().setEntityFlags(flags);
datawatcher.getHelper().setCustomName(displayName, viewer.getVersion());
datawatcher.getHelper().setCustomNameVisible(nameVisible);
datawatcher.getHelper().setArmorStandFlags((byte)16);
return datawatcher;
}
}
@@ -4,6 +4,7 @@ import lombok.RequiredArgsConstructor;
import me.neznamy.tab.api.TabAPI;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.shared.TAB;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
@@ -30,7 +31,7 @@ public class EventListener implements Listener {
public void onSneak(PlayerToggleSneakEvent e) {
TabPlayer p = TabAPI.getInstance().getPlayer(e.getPlayer().getUniqueId());
if (p == null || feature.isPlayerDisabled(p)) return;
TabAPI.getInstance().getThreadManager().runMeasuredTask(feature, TabConstants.CpuUsageCategory.PLAYER_SNEAK, () -> feature.getArmorStandManager(p).sneak(e.isSneaking()));
TAB.getInstance().getCPUManager().runMeasuredTask(feature, TabConstants.CpuUsageCategory.PLAYER_SNEAK, () -> feature.getArmorStandManager(p).sneak(e.isSneaking()));
}
/**
@@ -41,7 +42,7 @@ public class EventListener implements Listener {
*/
@EventHandler
public void onRespawn(PlayerRespawnEvent e) {
TabAPI.getInstance().getThreadManager().runMeasuredTask(feature, TabConstants.CpuUsageCategory.PLAYER_RESPAWN, () -> {
TAB.getInstance().getCPUManager().runMeasuredTask(feature, TabConstants.CpuUsageCategory.PLAYER_RESPAWN, () -> {
TabPlayer respawned = TabAPI.getInstance().getPlayer(e.getPlayer().getUniqueId());
if (feature.isPlayerDisabled(respawned)) return;
feature.getArmorStandManager(respawned).teleport();
@@ -1,67 +0,0 @@
package me.neznamy.tab.platforms.bukkit.nms;
import lombok.Data;
import me.neznamy.tab.api.protocol.TabPacket;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
/**
* Custom class for holding data used in PacketPlayOutEntityDestroy minecraft packet.
*/
@Data
public class PacketPlayOutEntityDestroy implements TabPacket {
/** NMS Fields */
public static Class<?> CLASS;
public static Constructor<?> CONSTRUCTOR;
public static Field ENTITIES;
/** Packet's instance fields */
private final int[] entities;
/**
* Loads all required Fields and throws Exception if something went wrong
*
* @param nms
* NMS storage reference
* @throws NoSuchMethodException
* If something fails
*/
public static void load(NMSStorage nms) throws NoSuchMethodException {
(ENTITIES = CLASS.getDeclaredFields()[0]).setAccessible(true);
try {
CONSTRUCTOR = CLASS.getConstructor(int[].class);
} catch (NoSuchMethodException e) {
//1.17.0
CONSTRUCTOR = CLASS.getConstructor(int.class);
}
}
/**
* Constructs new instance with given parameter
*
* @param entities
* Destroyed entities
*/
public PacketPlayOutEntityDestroy(int... entities) {
this.entities = entities;
}
/**
* Converts this class into NMS packet
*
* @return NMS packet
* @throws ReflectiveOperationException
* If something went wrong
*/
public Object build() throws ReflectiveOperationException {
if (CONSTRUCTOR.getParameterTypes()[0] != int.class) {
return CONSTRUCTOR.newInstance(new Object[]{entities});
} else {
//1.17.0
return CONSTRUCTOR.newInstance(entities[0]);
}
}
}
@@ -4,6 +4,7 @@ import lombok.Getter;
import lombok.NonNull;
import lombok.ToString;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import me.neznamy.tab.shared.backend.EntityData;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
@@ -16,7 +17,7 @@ import java.util.Map;
* A class representing the n.m.s.DataWatcher class to make work with it much easier
*/
@ToString
public class DataWatcher {
public class DataWatcher implements EntityData {
/** NMS Fields */
public static Class<?> CLASS;
@@ -87,23 +88,25 @@ public class DataWatcher {
* Converts the class into an instance of NMS.DataWatcher
*
* @return an instance of NMS.DataWatcher with same data
* @throws ReflectiveOperationException
* if thrown by reflective operation
*/
public Object build() throws ReflectiveOperationException {
NMSStorage nms = NMSStorage.getInstance();
Object nmsWatcher;
if (CONSTRUCTOR.getParameterCount() == 1) { //1.7+
nmsWatcher = CONSTRUCTOR.newInstance(new Object[] {null});
} else {
nmsWatcher = CONSTRUCTOR.newInstance();
public Object build() {
try {
NMSStorage nms = NMSStorage.getInstance();
Object nmsWatcher;
if (CONSTRUCTOR.getParameterCount() == 1) { //1.7+
nmsWatcher = CONSTRUCTOR.newInstance(new Object[] {null});
} else {
nmsWatcher = CONSTRUCTOR.newInstance();
}
for (DataWatcherItem item : dataValues.values()) {
Object nmsObject = item.getType().build();
REGISTER.invoke(nmsWatcher, nmsObject, item.getValue());
if (nms.is1_19_3Plus()) markDirty.invoke(nmsWatcher, nmsObject);
}
return nmsWatcher;
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
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;
}
/**
@@ -46,7 +46,6 @@ public class DataWatcherItem {
* if thrown by reflective operation
*/
public static DataWatcherItem fromNMS(Object nmsItem) throws ReflectiveOperationException {
NMSStorage nms = NMSStorage.getInstance();
Object value = VALUE.get(nmsItem);
DataWatcherObject object;
if (TabAPI.getInstance().getServerVersion().getMinorVersion() >= 9) {
@@ -1,9 +1,5 @@
package me.neznamy.tab.platforms.bukkit.nms.storage.nms;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityDestroy;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityMetadata;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityTeleport;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutSpawnEntityLiving;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherHelper;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherItem;
@@ -98,7 +94,6 @@ public class BukkitLegacyNMSStorage extends NMSStorage {
Entity = getLegacyClass("Entity");
EntityLiving = getLegacyClass("EntityLiving");
PlayerConnection = getLegacyClass("PlayerConnection");
PacketPlayOutChatStorage.CLASS = getLegacyClass("PacketPlayOutChat", "Packet3Chat");
NetworkManager = getLegacyClass("NetworkManager");
if (minorVersion >= 7) {
IChatBaseComponent = getLegacyClass("IChatBaseComponent");
@@ -108,9 +103,6 @@ public class BukkitLegacyNMSStorage extends NMSStorage {
PacketPlayOutPlayerListHeaderFooterStorage.CLASS = getLegacyClass("PacketPlayOutPlayerListHeaderFooter");
EntityArmorStand = getLegacyClass("EntityArmorStand");
}
if (minorVersion >= 12) {
PacketPlayOutChatStorage.ChatMessageTypeClass = (Class<Enum>) getLegacyClass("ChatMessageType");
}
// DataWatcher
DataWatcher.CLASS = getLegacyClass("DataWatcher");
@@ -122,13 +114,13 @@ public class BukkitLegacyNMSStorage extends NMSStorage {
}
// Entities
PacketPlayOutSpawnEntityLiving.CLASS = getLegacyClass("PacketPlayOutSpawnEntityLiving", "Packet24MobSpawn");
PacketPlayOutEntityTeleport.CLASS = getLegacyClass("PacketPlayOutEntityTeleport", "Packet34EntityTeleport");
PacketPlayOutSpawnEntityLivingStorage.CLASS = getLegacyClass("PacketPlayOutSpawnEntityLiving", "Packet24MobSpawn");
PacketPlayOutEntityTeleportStorage.CLASS = getLegacyClass("PacketPlayOutEntityTeleport", "Packet34EntityTeleport");
PacketPlayInUseEntity = getLegacyClass("PacketPlayInUseEntity", "Packet7UseEntity");
PacketPlayOutEntity = getLegacyClass("PacketPlayOutEntity", "Packet30Entity");
PacketPlayOutEntityDestroy.CLASS = getLegacyClass("PacketPlayOutEntityDestroy", "Packet29DestroyEntity");
PacketPlayOutEntityDestroyStorage.CLASS = getLegacyClass("PacketPlayOutEntityDestroy", "Packet29DestroyEntity");
PacketPlayOutEntityLook = getLegacyClass("PacketPlayOutEntity$PacketPlayOutEntityLook", "PacketPlayOutEntityLook", "Packet32EntityLook");
PacketPlayOutEntityMetadata.CLASS = getLegacyClass("PacketPlayOutEntityMetadata", "Packet40EntityMetadata");
PacketPlayOutEntityMetadataStorage.CLASS = getLegacyClass("PacketPlayOutEntityMetadata", "Packet40EntityMetadata");
PacketPlayOutNamedEntitySpawn = getLegacyClass("PacketPlayOutNamedEntitySpawn", "Packet20NamedEntitySpawn");
if (minorVersion >= 7) {
EnumEntityUseAction = (Class<Enum>) getLegacyClass("PacketPlayInUseEntity$EnumEntityUseAction", "EnumEntityUseAction");
@@ -1,9 +1,5 @@
package me.neznamy.tab.platforms.bukkit.nms.storage.nms;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityDestroy;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityMetadata;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityTeleport;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutSpawnEntityLiving;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherHelper;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherItem;
@@ -34,14 +30,18 @@ public class BukkitModernNMSStorage extends NMSStorage {
DataWatcherHelper.DataWatcherSerializer_STRING = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("d").get(null);
if (is1_19_3Plus()) {
DataWatcherHelper.DataWatcherSerializer_OPTIONAL_COMPONENT = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("g").get(null);
DataWatcherHelper.DataWatcherSerializer_BOOLEAN = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("j").get(null);
} else {
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) {
PacketPlayOutSpawnEntityLiving.EntityTypes_ARMOR_STAND = PacketPlayOutSpawnEntityLiving.EntityTypes.getDeclaredField("d").get(null);
(PacketPlayOutSpawnEntityLiving.ENTITY_TYPE = PacketPlayOutSpawnEntityLiving.CLASS.getDeclaredField("e")).setAccessible(true);
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");
}
}
@@ -62,12 +62,6 @@ public class BukkitModernNMSStorage extends NMSStorage {
PlayerConnection = Class.forName("net.minecraft.server.network.PlayerConnection");
PacketPlayOutPlayerListHeaderFooterStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutPlayerListHeaderFooter");
if (minorVersion >= 19) {
PacketPlayOutChatStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.ClientboundSystemChatPacket");
} else {
PacketPlayOutChatStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutChat");
PacketPlayOutChatStorage.ChatMessageTypeClass = (Class<Enum>) Class.forName("net.minecraft.network.chat.ChatMessageType");
}
// DataWatcher
DataWatcher.CLASS = Class.forName("net.minecraft.network.syncher.DataWatcher");
@@ -81,26 +75,25 @@ public class BukkitModernNMSStorage extends NMSStorage {
// Entities
if (minorVersion >= 19) {
PacketPlayOutSpawnEntityLiving.CLASS = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutSpawnEntity");
PacketPlayOutSpawnEntityLivingStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutSpawnEntity");
} else {
PacketPlayOutSpawnEntityLiving.CLASS = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutSpawnEntityLiving");
PacketPlayOutSpawnEntityLivingStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutSpawnEntityLiving");
}
PacketPlayOutEntityTeleport.CLASS = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutEntityTeleport");
PacketPlayOutEntityTeleportStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutEntityTeleport");
PacketPlayInUseEntity = Class.forName("net.minecraft.network.protocol.game.PacketPlayInUseEntity");
PacketPlayInUseEntity$d = Class.forName("net.minecraft.network.protocol.game.PacketPlayInUseEntity$d");
PacketPlayOutEntity = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutEntity");
PacketPlayOutEntityDestroy.CLASS = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutEntityDestroy");
PacketPlayOutEntityDestroyStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutEntityDestroy");
PacketPlayOutEntityLook = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutEntity$PacketPlayOutEntityLook");
PacketPlayOutEntityMetadata.CLASS = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutEntityMetadata");
PacketPlayOutEntityMetadataStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutEntityMetadata");
PacketPlayOutNamedEntitySpawn = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutNamedEntitySpawn");
EnumEntityUseAction = (Class<Enum>) Class.forName("net.minecraft.network.protocol.game.PacketPlayInUseEntity$EnumEntityUseAction");
if (minorVersion >= 19) {
PacketPlayOutSpawnEntityLiving.EntityTypes = Class.forName("net.minecraft.world.entity.EntityTypes");
PacketPlayOutSpawnEntityLivingStorage.EntityTypes = Class.forName("net.minecraft.world.entity.EntityTypes");
}
// Player Info
if (minorVersion >= 19) {
PacketPlayOutPlayerInfoStorage.ProfilePublicKey = Class.forName("net.minecraft.world.entity.player.ProfilePublicKey");
PacketPlayOutPlayerInfoStorage.ProfilePublicKey$a = Class.forName("net.minecraft.world.entity.player.ProfilePublicKey$a");
}
if (is1_19_3Plus()) {
@@ -108,7 +101,6 @@ public class BukkitModernNMSStorage extends NMSStorage {
PacketPlayOutPlayerInfoStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket");
PacketPlayOutPlayerInfoStorage.EnumPlayerInfoActionClass = (Class<Enum>) Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket$a");
PacketPlayOutPlayerInfoStorage.PlayerInfoDataStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket$b");
PacketPlayOutPlayerInfoStorage.RemoteChatSession = Class.forName("net.minecraft.network.chat.RemoteChatSession");
PacketPlayOutPlayerInfoStorage.RemoteChatSession$Data = Class.forName("net.minecraft.network.chat.RemoteChatSession$a");
} else {
PacketPlayOutPlayerInfoStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutPlayerInfo");
@@ -1,9 +1,5 @@
package me.neznamy.tab.platforms.bukkit.nms.storage.nms;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityDestroy;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityMetadata;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityTeleport;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutSpawnEntityLiving;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherHelper;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcherItem;
@@ -35,8 +31,8 @@ public class MojangModernNMSStorage extends NMSStorage {
DataWatcherHelper.DataWatcherSerializer_OPTIONAL_COMPONENT = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("OPTIONAL_COMPONENT").get(null);
DataWatcherHelper.DataWatcherSerializer_BOOLEAN = DataWatcherHelper.DataWatcherRegistry.getDeclaredField("BOOLEAN").get(null);
if (minorVersion >= 19) {
PacketPlayOutSpawnEntityLiving.EntityTypes_ARMOR_STAND = PacketPlayOutSpawnEntityLiving.EntityTypes.getDeclaredField("ARMOR_STAND").get(null);
(PacketPlayOutSpawnEntityLiving.ENTITY_TYPE = PacketPlayOutSpawnEntityLiving.CLASS.getDeclaredField("type")).setAccessible(true);
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");
}
}
@@ -56,12 +52,6 @@ public class MojangModernNMSStorage extends NMSStorage {
EntityLiving = Class.forName("net.minecraft.world.entity.LivingEntity");
PlayerConnection = Class.forName("net.minecraft.server.network.ServerGamePacketListenerImpl");
PacketPlayOutPlayerListHeaderFooterStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.ClientboundTabListPacket");
if (minorVersion >= 19) {
PacketPlayOutChatStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.ClientboundSystemChatPacket");
} else {
PacketPlayOutChatStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.ClientboundChatPacket");
PacketPlayOutChatStorage.ChatMessageTypeClass = (Class<Enum>) Class.forName("net.minecraft.network.chat.ChatType");
}
// DataWatcher
DataWatcher.CLASS = Class.forName("net.minecraft.network.syncher.SynchedEntityData");
@@ -75,26 +65,25 @@ public class MojangModernNMSStorage extends NMSStorage {
// Entities
if (minorVersion >= 19) {
PacketPlayOutSpawnEntityLiving.CLASS = Class.forName("net.minecraft.network.protocol.game.ClientboundAddEntityPacket");
PacketPlayOutSpawnEntityLivingStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.ClientboundAddEntityPacket");
} else {
PacketPlayOutSpawnEntityLiving.CLASS = Class.forName("net.minecraft.network.protocol.game.ClientboundAddMobPacket");
PacketPlayOutSpawnEntityLivingStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.ClientboundAddMobPacket");
}
PacketPlayOutEntityTeleport.CLASS = Class.forName("net.minecraft.network.protocol.game.ClientboundTeleportEntityPacket");
PacketPlayOutEntityTeleportStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.ClientboundTeleportEntityPacket");
PacketPlayInUseEntity = Class.forName("net.minecraft.network.protocol.game.ServerboundInteractPacket");
PacketPlayInUseEntity$d = Class.forName("net.minecraft.network.protocol.game.ServerboundInteractPacket$InteractionAction");
PacketPlayOutEntity = Class.forName("net.minecraft.network.protocol.game.ClientboundMoveEntityPacket");
PacketPlayOutEntityDestroy.CLASS = Class.forName("net.minecraft.network.protocol.game.ClientboundRemoveEntitiesPacket");
PacketPlayOutEntityDestroyStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.ClientboundRemoveEntitiesPacket");
PacketPlayOutEntityLook = Class.forName("net.minecraft.network.protocol.game.ClientboundMoveEntityPacket$Rot");
PacketPlayOutEntityMetadata.CLASS = Class.forName("net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket");
PacketPlayOutEntityMetadataStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket");
PacketPlayOutNamedEntitySpawn = Class.forName("net.minecraft.network.protocol.game.ClientboundAddPlayerPacket");
EnumEntityUseAction = (Class<Enum>) Class.forName("net.minecraft.network.protocol.game.ServerboundInteractPacket$Action");
if (minorVersion >= 19) {
PacketPlayOutSpawnEntityLiving.EntityTypes = Class.forName("net.minecraft.world.entity.EntityType");
PacketPlayOutSpawnEntityLivingStorage.EntityTypes = Class.forName("net.minecraft.world.entity.EntityType");
}
// Player Info
if (minorVersion >= 19) {
PacketPlayOutPlayerInfoStorage.ProfilePublicKey = Class.forName("net.minecraft.world.entity.player.ProfilePublicKey");
PacketPlayOutPlayerInfoStorage.ProfilePublicKey$a = Class.forName("net.minecraft.world.entity.player.ProfilePublicKey$Data");
}
if (is1_19_3Plus()) {
@@ -102,7 +91,6 @@ public class MojangModernNMSStorage extends NMSStorage {
PacketPlayOutPlayerInfoStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket");
PacketPlayOutPlayerInfoStorage.EnumPlayerInfoActionClass = (Class<Enum>) Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket$Action");
PacketPlayOutPlayerInfoStorage.PlayerInfoDataStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket$Entry");
PacketPlayOutPlayerInfoStorage.RemoteChatSession = Class.forName("net.minecraft.network.chat.RemoteChatSession");
PacketPlayOutPlayerInfoStorage.RemoteChatSession$Data = Class.forName("net.minecraft.network.chat.RemoteChatSession$Data");
} else {
PacketPlayOutPlayerInfoStorage.CLASS = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket");
@@ -10,10 +10,6 @@ import me.neznamy.tab.api.chat.IChatBaseComponent;
import me.neznamy.tab.api.chat.WrappedChatComponent;
import me.neznamy.tab.api.util.ComponentCache;
import me.neznamy.tab.api.util.ReflectionUtils;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityDestroy;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityMetadata;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutEntityTeleport;
import me.neznamy.tab.platforms.bukkit.nms.PacketPlayOutSpawnEntityLiving;
import 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;
@@ -45,6 +41,7 @@ public abstract class NMSStorage {
/** 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;
@@ -74,7 +71,6 @@ public abstract class NMSStorage {
public Class<?> PacketPlayInUseEntity;
public Class<?> PacketPlayInUseEntity$d;
protected Class<Enum> EnumEntityUseAction;
public Field PacketPlayInUseEntity_ENTITY;
public Field PacketPlayInUseEntity_ACTION;
public Class<?> PacketPlayOutEntity;
@@ -124,11 +120,10 @@ public abstract class NMSStorage {
DataWatcher.load(this);
DataWatcherItem.load(this);
DataWatcherObject.load(this);
PacketPlayOutEntityDestroy.load(this);
PacketPlayOutEntityMetadata.load(this);
PacketPlayOutEntityTeleport.load(this);
PacketPlayOutSpawnEntityLiving.load(this);
PacketPlayOutChatStorage.load(this);
PacketPlayOutEntityDestroyStorage.load(this);
PacketPlayOutEntityMetadataStorage.load(this);
PacketPlayOutEntityTeleportStorage.load(this);
PacketPlayOutSpawnEntityLivingStorage.load(this);
PacketPlayOutPlayerListHeaderFooterStorage.load(this);
PacketPlayOutPlayerInfoStorage.load(this);
PacketPlayOutScoreboardObjectiveStorage.load(this);
@@ -148,7 +143,6 @@ public abstract class NMSStorage {
PacketPlayOutEntity_ENTITYID = getFields(PacketPlayOutEntity, int.class).get(0);
PacketPlayOutNamedEntitySpawn_ENTITYID = getFields(PacketPlayOutNamedEntitySpawn, int.class).get(0);
if (minorVersion >= 7) {
PacketPlayInUseEntity_ENTITY = getFields(PacketPlayInUseEntity, int.class).get(0);
PacketPlayInUseEntity_ACTION = getFields(PacketPlayInUseEntity, EnumEntityUseAction).get(0);
}
}
@@ -1,56 +0,0 @@
package me.neznamy.tab.platforms.bukkit.nms.storage.packet;
import me.neznamy.tab.api.ProtocolVersion;
import me.neznamy.tab.api.protocol.PacketPlayOutChat;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import java.lang.reflect.Constructor;
import java.util.UUID;
@SuppressWarnings({"unchecked", "rawtypes"})
public class PacketPlayOutChatStorage {
public static Class<?> CLASS;
public static Class<Enum> ChatMessageTypeClass;
public static Constructor<?> CONSTRUCTOR;
public static void load(NMSStorage nms) throws NoSuchMethodException {
if (nms.getMinorVersion() >= 19) {
try {
CONSTRUCTOR = CLASS.getConstructor(nms.IChatBaseComponent, boolean.class);
} catch (NoSuchMethodException e) {
//1.19.0
CONSTRUCTOR = CLASS.getConstructor(nms.IChatBaseComponent, int.class);
}
} else if (nms.getMinorVersion() >= 16) {
CONSTRUCTOR = CLASS.getConstructor(nms.IChatBaseComponent, ChatMessageTypeClass, UUID.class);
} else if (nms.getMinorVersion() >= 12) {
CONSTRUCTOR = CLASS.getConstructor(nms.IChatBaseComponent, ChatMessageTypeClass);
} else if (nms.getMinorVersion() >= 8) {
CONSTRUCTOR = CLASS.getConstructor(nms.IChatBaseComponent, byte.class);
} else if (nms.getMinorVersion() >= 7) {
CONSTRUCTOR = CLASS.getConstructor(nms.IChatBaseComponent);
}
}
public static Object build(PacketPlayOutChat packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
NMSStorage nms = NMSStorage.getInstance();
Object component = nms.toNMSComponent(packet.getMessage(), clientVersion);
if (nms.getMinorVersion() >= 19)
try {
return CONSTRUCTOR.newInstance(component, packet.getType() == PacketPlayOutChat.ChatMessageType.GAME_INFO);
} catch (Exception e) {
//1.19.0
return CONSTRUCTOR.newInstance(component, packet.getType().ordinal());
}
if (nms.getMinorVersion() >= 16)
return CONSTRUCTOR.newInstance(component, Enum.valueOf(ChatMessageTypeClass, packet.getType().toString()), UUID.randomUUID());
if (nms.getMinorVersion() >= 12)
return CONSTRUCTOR.newInstance(component, Enum.valueOf(ChatMessageTypeClass, packet.getType().toString()));
if (nms.getMinorVersion() >= 8)
return CONSTRUCTOR.newInstance(component, (byte) packet.getType().ordinal());
if (nms.getMinorVersion() == 7)
return CONSTRUCTOR.newInstance(component);
return packet;
}
}
@@ -0,0 +1,35 @@
package me.neznamy.tab.platforms.bukkit.nms.storage.packet;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
/**
* 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
*
* @param nms
* NMS storage reference
* @throws NoSuchMethodException
* If something fails
*/
public static void load(NMSStorage nms) throws NoSuchMethodException {
(ENTITIES = CLASS.getDeclaredFields()[0]).setAccessible(true);
try {
CONSTRUCTOR = CLASS.getConstructor(int[].class);
} catch (NoSuchMethodException e) {
//1.17.0
CONSTRUCTOR = CLASS.getConstructor(int.class);
}
}
}
@@ -1,9 +1,5 @@
package me.neznamy.tab.platforms.bukkit.nms;
package me.neznamy.tab.platforms.bukkit.nms.storage.packet;
import lombok.AllArgsConstructor;
import lombok.NonNull;
import lombok.ToString;
import me.neznamy.tab.api.protocol.TabPacket;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
@@ -14,18 +10,13 @@ import java.util.List;
/**
* Custom class for holding data used in PacketPlayOutEntityMetadata minecraft packet.
*/
@AllArgsConstructor @ToString
public class PacketPlayOutEntityMetadata implements TabPacket {
public class PacketPlayOutEntityMetadataStorage {
/** NMS Fields */
public static Class<?> CLASS;
public static Constructor<?> CONSTRUCTOR;
public static Field LIST;
/** Packet's instance fields */
private final int entityId;
@NonNull private final DataWatcher dataWatcher;
/**
* Loads all required Fields and throws Exception if something went wrong
*
@@ -42,20 +33,4 @@ public class PacketPlayOutEntityMetadata implements TabPacket {
}
LIST = nms.getFields(CLASS, List.class).get(0);
}
/**
* Converts this class into NMS packet
*
* @return NMS packet
* @throws ReflectiveOperationException
* If something went wrong
*/
public Object build() throws ReflectiveOperationException {
if (CONSTRUCTOR.getParameterCount() == 2) {
//1.19.3+
return CONSTRUCTOR.newInstance(entityId, DataWatcher.packDirty.invoke(dataWatcher.build()));
} else {
return CONSTRUCTOR.newInstance(entityId, dataWatcher.build(), true);
}
}
}
@@ -1,11 +1,7 @@
package me.neznamy.tab.platforms.bukkit.nms;
package me.neznamy.tab.platforms.bukkit.nms.storage.packet;
import lombok.AllArgsConstructor;
import lombok.NonNull;
import lombok.ToString;
import me.neznamy.tab.api.protocol.TabPacket;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import org.bukkit.Location;
import me.neznamy.tab.shared.backend.Location;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
@@ -13,8 +9,7 @@ import java.lang.reflect.Field;
/**
* Custom class for holding data used in PacketPlayOutEntityTeleport minecraft packet.
*/
@AllArgsConstructor @ToString
public class PacketPlayOutEntityTeleport implements TabPacket {
public class PacketPlayOutEntityTeleportStorage {
/** NMS Fields */
public static Class<?> CLASS;
@@ -26,10 +21,6 @@ public class PacketPlayOutEntityTeleport implements TabPacket {
public static Field YAW;
public static Field PITCH;
/** Packet's instance fields */
private final int entityId;
@NonNull private final Location location;
/**
* Loads all required Fields and throws Exception if something went wrong
*
@@ -65,7 +56,7 @@ public class PacketPlayOutEntityTeleport implements TabPacket {
* @throws ReflectiveOperationException
* If something went wrong
*/
public Object build() throws ReflectiveOperationException {
public static Object build(int entityId, Location location) throws ReflectiveOperationException {
NMSStorage nms = NMSStorage.getInstance();
Object nmsPacket;
if (nms.getMinorVersion() >= 17) {
@@ -88,7 +79,7 @@ public class PacketPlayOutEntityTeleport implements TabPacket {
return nmsPacket;
}
private int floor(double paramDouble) {
private static int floor(double paramDouble) {
int i = (int)paramDouble;
return paramDouble < i ? i - 1 : i;
}
@@ -3,14 +3,12 @@ package me.neznamy.tab.platforms.bukkit.nms.storage.packet;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
import me.neznamy.tab.api.ProtocolVersion;
import me.neznamy.tab.api.chat.WrappedChatComponent;
import me.neznamy.tab.api.protocol.PacketPlayOutPlayerInfo;
import me.neznamy.tab.api.protocol.Skin;
import me.neznamy.tab.api.tablist.TabList;
import me.neznamy.tab.api.tablist.TabListEntry;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import java.lang.reflect.*;
import java.util.*;
import java.util.stream.Collectors;
@SuppressWarnings({"unchecked", "rawtypes"})
public class PacketPlayOutPlayerInfoStorage {
@@ -21,29 +19,21 @@ public class PacketPlayOutPlayerInfoStorage {
public static Field PLAYERS;
public static Class<Enum> EnumPlayerInfoActionClass;
public static Class<Enum> EnumGamemodeClass;
public static Class<?> ProfilePublicKey;
public static Class<?> ProfilePublicKey$a;
//1.19.3+
public static Class<?> ClientboundPlayerInfoRemovePacket;
public static Class<?> RemoteChatSession;
public static Class<?> RemoteChatSession$Data;
public static Constructor<?> newClientboundPlayerInfoRemovePacket;
public static Constructor<?> newRemoteChatSession$Data;
public static Method ClientboundPlayerInfoRemovePacket_getEntries;
public static Method RemoteChatSession$Data_getSessionId;
public static Method RemoteChatSession$Data_getProfilePublicKey;
public static void load(NMSStorage nms) throws NoSuchMethodException {
public static void load(NMSStorage nms) throws ReflectiveOperationException {
if (nms.getMinorVersion() < 8) return;
if (nms.is1_19_3Plus()) {
newClientboundPlayerInfoRemovePacket = ClientboundPlayerInfoRemovePacket.getConstructor(List.class);
CONSTRUCTOR = CLASS.getConstructor(EnumSet.class, Collection.class);
ClientboundPlayerInfoRemovePacket_getEntries = nms.getMethods(ClientboundPlayerInfoRemovePacket, List.class).get(0);
ACTION = nms.getFields(CLASS, EnumSet.class).get(0);
newRemoteChatSession$Data = RemoteChatSession$Data.getConstructor(UUID.class, ProfilePublicKey$a);
RemoteChatSession$Data_getSessionId = nms.getMethods(RemoteChatSession$Data, UUID.class).get(0);
RemoteChatSession$Data_getProfilePublicKey = nms.getMethods(RemoteChatSession$Data, ProfilePublicKey$a).get(0);
} else {
CONSTRUCTOR = CLASS.getConstructor(EnumPlayerInfoActionClass, Array.newInstance(nms.EntityPlayer, 0).getClass());
ACTION = nms.getFields(CLASS, EnumPlayerInfoActionClass).get(0);
@@ -52,56 +42,75 @@ public class PacketPlayOutPlayerInfoStorage {
PlayerInfoDataStorage.load(nms);
}
public static Object build(PacketPlayOutPlayerInfo packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
public static Object createPacket(String action, Collection<TabListEntry> entries, ProtocolVersion clientVersion) {
NMSStorage nms = NMSStorage.getInstance();
if (nms.getMinorVersion() < 8) return null;
Object nmsPacket;
if (ClientboundPlayerInfoRemovePacket != null) {
//1.19.3+
if (packet.getActions().contains(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.REMOVE_PLAYER)) {
return newClientboundPlayerInfoRemovePacket.newInstance(
packet.getEntries().stream().map(PacketPlayOutPlayerInfo.PlayerInfoData::getUniqueId).collect(Collectors.toList())
);
try {
Object packet;
List<Object> players = new ArrayList<>();
if (NMSStorage.getInstance().is1_19_3Plus()) {
EnumSet<?> actions;
if (action.equals("ADD_PLAYER")) {
actions = EnumSet.allOf(EnumPlayerInfoActionClass);
} else {
actions = EnumSet.of(Enum.valueOf(EnumPlayerInfoActionClass, action));
}
packet = CONSTRUCTOR.newInstance(actions, Collections.emptyList());
for (TabListEntry entry : entries) {
GameProfile profile = new GameProfile(entry.getUniqueId(), entry.getName());
if (entry.getSkin() != null) profile.getProperties().put(TabList.TEXTURES_PROPERTY,
new Property(TabList.TEXTURES_PROPERTY, entry.getSkin().getValue(), entry.getSkin().getSignature()));
players.add(PlayerInfoDataStorage.newPlayerInfoData.newInstance(
entry.getUniqueId(),
profile,
entry.isListed(),
entry.getLatency(),
int2GameMode(entry.getGameMode()),
nms.toNMSComponent(entry.getDisplayName(), clientVersion),
entry.getChatSession()
));
}
} else {
packet = CONSTRUCTOR.newInstance(Enum.valueOf(EnumPlayerInfoActionClass, action),
Array.newInstance(NMSStorage.getInstance().EntityPlayer, 0));
for (TabListEntry entry : entries) {
GameProfile profile = new GameProfile(entry.getUniqueId(), entry.getName());
if (entry.getSkin() != null) profile.getProperties().put(TabList.TEXTURES_PROPERTY,
new Property(TabList.TEXTURES_PROPERTY, entry.getSkin().getValue(), entry.getSkin().getSignature()));
List<Object> parameters = new ArrayList<>();
if (PlayerInfoDataStorage.newPlayerInfoData.getParameterTypes()[0] == CLASS) {
parameters.add(packet);
}
parameters.add(profile);
parameters.add(entry.getLatency());
parameters.add(int2GameMode(entry.getGameMode()));
parameters.add(nms.toNMSComponent(entry.getDisplayName(), clientVersion));
if (nms.getMinorVersion() >= 19) parameters.add(entry.getChatSession());
players.add(PlayerInfoDataStorage.newPlayerInfoData.newInstance(parameters.toArray()));
}
}
Enum[] array = packet.getActions().stream().map(action -> Enum.valueOf(EnumPlayerInfoActionClass, action.toString())).toArray(Enum[]::new);
nmsPacket = CONSTRUCTOR.newInstance(EnumSet.of(array[0], array), Collections.emptyList());
} else {
//1.19.2-
PacketPlayOutPlayerInfo.EnumPlayerInfoAction action = packet.getActions().contains(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.ADD_PLAYER) ?
PacketPlayOutPlayerInfo.EnumPlayerInfoAction.ADD_PLAYER : packet.getActions().iterator().next();
nmsPacket = CONSTRUCTOR.newInstance(Enum.valueOf(EnumPlayerInfoActionClass, action.toString()), Array.newInstance(nms.EntityPlayer, 0));
PLAYERS.set(packet, players);
return packet;
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
List<Object> items = new ArrayList<>();
for (PacketPlayOutPlayerInfo.PlayerInfoData data : packet.getEntries()) {
items.add(PlayerInfoDataStorage.build(nmsPacket, data, clientVersion));
}
PLAYERS.set(nmsPacket, items);
return nmsPacket;
}
public static PacketPlayOutPlayerInfo read(Object nmsPacket) throws ReflectiveOperationException {
NMSStorage nms = NMSStorage.getInstance();
if (nms.getMinorVersion() < 8) return null;
if (ClientboundPlayerInfoRemovePacket != null && ClientboundPlayerInfoRemovePacket.isInstance(nmsPacket)) {
List<UUID> entries = (List<UUID>) ClientboundPlayerInfoRemovePacket_getEntries.invoke(nmsPacket);
return new PacketPlayOutPlayerInfo(
PacketPlayOutPlayerInfo.EnumPlayerInfoAction.REMOVE_PLAYER,
entries.stream().map(PacketPlayOutPlayerInfo.PlayerInfoData::new).collect(Collectors.toList())
);
public static Object int2GameMode(int gameMode) {
switch (gameMode) {
case 1: return Enum.valueOf(EnumGamemodeClass, "CREATIVE");
case 2: return Enum.valueOf(EnumGamemodeClass, "ADVENTURE");
case 3: return Enum.valueOf(EnumGamemodeClass, "SPECTATOR");
default: return Enum.valueOf(EnumGamemodeClass, "SURVIVAL");
}
List<PacketPlayOutPlayerInfo.PlayerInfoData> listData = new ArrayList<>();
for (Object nmsData : (List<?>) PLAYERS.get(nmsPacket)) {
listData.add(PlayerInfoDataStorage.read(nmsData));
}
if (ClientboundPlayerInfoRemovePacket != null) {
//1.19.3+
PacketPlayOutPlayerInfo.EnumPlayerInfoAction[] array = ((EnumSet<?>) ACTION.get(nmsPacket)).stream().map(action ->
PacketPlayOutPlayerInfo.EnumPlayerInfoAction.valueOf(action.toString())).toArray(
PacketPlayOutPlayerInfo.EnumPlayerInfoAction[]::new);
return new PacketPlayOutPlayerInfo(EnumSet.of(array[0], array), listData);
} else {
//1.19.2-
return new PacketPlayOutPlayerInfo(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.valueOf(ACTION.get(nmsPacket).toString()), listData);
}
public static int gameMode2Int(Object gameMode) {
switch (String.valueOf(gameMode)) {
case "CREATIVE": return 1;
case "ADVENTURE": return 2;
case "SPECTATOR": return 3;
default: return 0;
}
}
@@ -110,98 +119,25 @@ public class PacketPlayOutPlayerInfoStorage {
public static Class<?> CLASS;
public static Constructor<?> newPlayerInfoData;
public static Method PlayerInfoData_getProfile;
public static Method PlayerInfoData_isListed;
public static Method PlayerInfoData_getLatency;
public static Method PlayerInfoData_getGamemode;
public static Method PlayerInfoData_getDisplayName;
public static Method PlayerInfoData_getProfilePublicKeyRecord;
public static Field PlayerInfoData_Latency;
public static Field PlayerInfoData_GameMode;
public static Field PlayerInfoData_DisplayName;
//1.19.3+
public static Field PlayerInfoData_Listed;
public static Field PlayerInfoData_RemoteChatSession;
public static void load(NMSStorage nms) {
if (nms.getMinorVersion() < 8) return;
newPlayerInfoData = CLASS.getConstructors()[0];
PlayerInfoData_getProfile = nms.getMethods(CLASS, GameProfile.class).get(0);
PlayerInfoData_getGamemode = nms.getMethods(CLASS, EnumGamemodeClass).get(0);
PlayerInfoData_getDisplayName = nms.getMethods(CLASS, nms.IChatBaseComponent).get(0);
for (Method m: nms.getMethods(CLASS, int.class)) {
// do not take .hashCode(), which is final
if (!Modifier.isFinal(m.getModifiers())) PlayerInfoData_getLatency = m;
}
PlayerInfoData_Latency = nms.getFields(CLASS, int.class).get(0);
PlayerInfoData_GameMode = nms.getFields(CLASS, EnumGamemodeClass).get(0);
PlayerInfoData_DisplayName = nms.getFields(CLASS, nms.IChatBaseComponent).get(0);
if (nms.is1_19_3Plus()) {
PlayerInfoData_getProfilePublicKeyRecord = nms.getMethods(CLASS, RemoteChatSession$Data).get(0);
PlayerInfoData_isListed = nms.getMethods(CLASS, boolean.class).get(0);
} else {
if (nms.getMinorVersion() >= 19) {
PlayerInfoData_getProfilePublicKeyRecord = nms.getMethods(CLASS, ProfilePublicKey$a).get(0);
}
PlayerInfoData_Listed = nms.getFields(CLASS, boolean.class).get(0);
PlayerInfoData_RemoteChatSession = nms.getFields(CLASS, RemoteChatSession$Data).get(0);
}
}
public static Object build(Object nmsPacket, PacketPlayOutPlayerInfo.PlayerInfoData data, ProtocolVersion clientVersion) throws ReflectiveOperationException {
NMSStorage nms = NMSStorage.getInstance();
if (ClientboundPlayerInfoRemovePacket != null) {
//1.19.3+
GameProfile profile = new GameProfile(data.getUniqueId(), data.getName());
if (data.getSkin() != null) profile.getProperties().put("textures",
new Property("textures", data.getSkin().getValue(), data.getSkin().getSignature()));
return newPlayerInfoData.newInstance(
data.getUniqueId(),
profile,
data.isListed(),
data.getLatency(),
data.getGameMode() == null ? null : Enum.valueOf(EnumGamemodeClass, data.getGameMode().toString()),
data.getDisplayName() == null ? null : nms.toNMSComponent(data.getDisplayName(), clientVersion),
data.getProfilePublicKey() == null ? null : newRemoteChatSession$Data.newInstance(data.getChatSessionId(), data.getProfilePublicKey()));
} else {
//1.19.2-
GameProfile profile = new GameProfile(data.getUniqueId(), data.getName());
if (data.getSkin() != null) profile.getProperties().put("textures", new Property("textures", data.getSkin().getValue(), data.getSkin().getSignature()));
List<Object> parameters = new ArrayList<>();
if (newPlayerInfoData.getParameterTypes()[0] == PacketPlayOutPlayerInfoStorage.CLASS) {
parameters.add(nmsPacket);
}
parameters.add(profile);
parameters.add(data.getLatency());
parameters.add(data.getGameMode() == null ? null : Enum.valueOf(EnumGamemodeClass, data.getGameMode().toString()));
parameters.add(data.getDisplayName() == null ? null : nms.toNMSComponent(data.getDisplayName(), clientVersion));
if (nms.getMinorVersion() >= 19) parameters.add(data.getProfilePublicKey());
return newPlayerInfoData.newInstance(parameters.toArray());
}
}
public static PacketPlayOutPlayerInfo.PlayerInfoData read(Object nmsData) throws ReflectiveOperationException {
Object nmsGameMode = PlayerInfoData_getGamemode.invoke(nmsData);
PacketPlayOutPlayerInfo.EnumGamemode gameMode = (nmsGameMode == null) ? null : PacketPlayOutPlayerInfo.EnumGamemode.valueOf(nmsGameMode.toString());
GameProfile profile = (GameProfile) PlayerInfoData_getProfile.invoke(nmsData);
Object nmsComponent = PlayerInfoData_getDisplayName.invoke(nmsData);
Skin skin = null;
if (!profile.getProperties().get("textures").isEmpty()) {
Property pr = profile.getProperties().get("textures").iterator().next();
skin = new Skin(pr.getValue(), pr.getSignature());
}
boolean listed = true;
UUID chatSessionId = null;
Object profilePublicKey;
if (ClientboundPlayerInfoRemovePacket != null) {
//1.19.3+
listed = (boolean) PlayerInfoData_isListed.invoke(nmsData);
Object remoteChatSession = PlayerInfoData_getProfilePublicKeyRecord.invoke(nmsData);
chatSessionId = remoteChatSession == null ? null : (UUID) RemoteChatSession$Data_getSessionId.invoke(remoteChatSession);
profilePublicKey = remoteChatSession == null ? null : RemoteChatSession$Data_getProfilePublicKey.invoke(remoteChatSession);
} else {
//1.19.2-
profilePublicKey = NMSStorage.getInstance().getMinorVersion() >= 19 ? PlayerInfoData_getProfilePublicKeyRecord.invoke(nmsData) : null;
}
return new PacketPlayOutPlayerInfo.PlayerInfoData(
profile.getName(),
profile.getId(),
skin,
listed,
(int) PlayerInfoData_getLatency.invoke(nmsData),
gameMode,
nmsComponent == null ? null : new WrappedChatComponent(nmsComponent),
chatSessionId,
profilePublicKey);
}
}
}
@@ -1,7 +1,7 @@
package me.neznamy.tab.platforms.bukkit.nms.storage.packet;
import me.neznamy.tab.api.ProtocolVersion;
import me.neznamy.tab.api.protocol.PacketPlayOutPlayerListHeaderFooter;
import me.neznamy.tab.api.chat.IChatBaseComponent;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import java.lang.reflect.Constructor;
@@ -16,25 +16,24 @@ public class PacketPlayOutPlayerListHeaderFooterStorage {
public static void load(NMSStorage nms) throws NoSuchMethodException {
if (nms.getMinorVersion() < 8) return;
HEADER = nms.getFields(CLASS, nms.IChatBaseComponent).get(0);
FOOTER = nms.getFields(CLASS, nms.IChatBaseComponent).get(1);
if (nms.getMinorVersion() >= 17) {
CONSTRUCTOR = CLASS.getConstructor(nms.IChatBaseComponent, nms.IChatBaseComponent);
} else {
CONSTRUCTOR = CLASS.getConstructor();
HEADER = nms.getFields(CLASS, nms.IChatBaseComponent).get(0);
FOOTER = nms.getFields(CLASS, nms.IChatBaseComponent).get(1);
}
}
public static Object build(PacketPlayOutPlayerListHeaderFooter packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
public static Object build(IChatBaseComponent header, IChatBaseComponent footer, ProtocolVersion clientVersion) throws ReflectiveOperationException {
NMSStorage nms = NMSStorage.getInstance();
if (nms.getMinorVersion() < 8) return null;
if (CONSTRUCTOR.getParameterCount() == 2) {
return CONSTRUCTOR.newInstance(nms.toNMSComponent(packet.getHeader(), clientVersion),
nms.toNMSComponent(packet.getFooter(), clientVersion));
return CONSTRUCTOR.newInstance(nms.toNMSComponent(header, clientVersion), nms.toNMSComponent(footer, clientVersion));
}
Object nmsPacket = CONSTRUCTOR.newInstance();
HEADER.set(nmsPacket, nms.toNMSComponent(packet.getHeader(), clientVersion));
FOOTER.set(nmsPacket, nms.toNMSComponent(packet.getFooter(), clientVersion));
HEADER.set(nmsPacket, nms.toNMSComponent(header, clientVersion));
FOOTER.set(nmsPacket, nms.toNMSComponent(footer, clientVersion));
return nmsPacket;
}
}
@@ -1,7 +1,5 @@
package me.neznamy.tab.platforms.bukkit.nms.storage.packet;
import me.neznamy.tab.api.ProtocolVersion;
import me.neznamy.tab.api.protocol.PacketPlayOutScoreboardDisplayObjective;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import java.lang.reflect.Constructor;
@@ -20,14 +18,11 @@ public class PacketPlayOutScoreboardDisplayObjectiveStorage {
OBJECTIVE_NAME = nms.getFields(CLASS, String.class).get(0);
}
public static Object build(PacketPlayOutScoreboardDisplayObjective packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
return CONSTRUCTOR.newInstance(packet.getSlot(), NMSStorage.getInstance().newScoreboardObjective(packet.getObjectiveName()));
}
public static PacketPlayOutScoreboardDisplayObjective read(Object nmsPacket) throws ReflectiveOperationException {
return new PacketPlayOutScoreboardDisplayObjective(
POSITION.getInt(nmsPacket),
(String) OBJECTIVE_NAME.get(nmsPacket)
);
public static Object buildSilent(int slot, String objective) {
try {
return CONSTRUCTOR.newInstance(slot, NMSStorage.getInstance().newScoreboardObjective(objective));
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
}
@@ -2,9 +2,7 @@ package me.neznamy.tab.platforms.bukkit.nms.storage.packet;
import me.neznamy.tab.api.ProtocolVersion;
import me.neznamy.tab.api.chat.IChatBaseComponent;
import me.neznamy.tab.api.protocol.PacketPlayOutScoreboardObjective;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import me.neznamy.tab.shared.TAB;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
@@ -41,30 +39,28 @@ public class PacketPlayOutScoreboardObjectiveStorage {
}
}
public static Object build(PacketPlayOutScoreboardObjective packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
NMSStorage nms = NMSStorage.getInstance();
String displayName = clientVersion.getMinorVersion() < 13 ? TAB.getInstance().getPlatform().getPacketBuilder().cutTo(packet.getDisplayName(), 32) : packet.getDisplayName();
if (nms.getMinorVersion() >= 13) {
return CONSTRUCTOR.newInstance(newScoreboardObjective.newInstance(null, packet.getObjectiveName(), null,
nms.toNMSComponent(IChatBaseComponent.optimizedComponent(displayName), clientVersion),
packet.getRenderType() == null ? null : Enum.valueOf(EnumScoreboardHealthDisplay, packet.getRenderType().toString())),
packet.getAction());
public static Object buildSilent(int action, String objectiveName, String title, boolean hearts, ProtocolVersion clientVersion) {
try {
NMSStorage nms = NMSStorage.getInstance();
if (nms.getMinorVersion() >= 13) {
return CONSTRUCTOR.newInstance(newScoreboardObjective.newInstance(null, objectiveName, null,
nms.toNMSComponent(IChatBaseComponent.optimizedComponent(title), clientVersion),
asDisplayType(hearts)), action);
}
Object nmsPacket = CONSTRUCTOR.newInstance();
OBJECTIVE_NAME.set(nmsPacket, objectiveName);
DISPLAY_NAME.set(nmsPacket, title);
if (nms.getMinorVersion() >= 8) {
RENDER_TYPE.set(nmsPacket, asDisplayType(hearts));
}
METHOD.set(nmsPacket, action);
return nmsPacket;
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
Object nmsPacket = CONSTRUCTOR.newInstance();
OBJECTIVE_NAME.set(nmsPacket, packet.getObjectiveName());
DISPLAY_NAME.set(nmsPacket, displayName);
if (nms.getMinorVersion() >= 8 && packet.getRenderType() != null) {
RENDER_TYPE.set(nmsPacket, Enum.valueOf(EnumScoreboardHealthDisplay, packet.getRenderType().toString()));
}
METHOD.set(nmsPacket, packet.getAction());
return nmsPacket;
}
public static PacketPlayOutScoreboardObjective read(Object nmsPacket) throws ReflectiveOperationException {
return new PacketPlayOutScoreboardObjective(METHOD.getInt(nmsPacket),
(String) OBJECTIVE_NAME.get(nmsPacket), null,
PacketPlayOutScoreboardObjective.EnumScoreboardHealthDisplay.INTEGER
);
private static Object asDisplayType(boolean hearts) {
return Enum.valueOf(EnumScoreboardHealthDisplay, hearts ? "HEARTS" : "INTEGER");
}
}
@@ -1,7 +1,5 @@
package me.neznamy.tab.platforms.bukkit.nms.storage.packet;
import me.neznamy.tab.api.ProtocolVersion;
import me.neznamy.tab.api.protocol.PacketPlayOutScoreboardScore;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import java.lang.reflect.Constructor;
@@ -33,19 +31,31 @@ public class PacketPlayOutScoreboardScoreStorage {
}
}
public static Object build(PacketPlayOutScoreboardScore packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
NMSStorage nms = NMSStorage.getInstance();
if (nms.getMinorVersion() >= 13) {
return CONSTRUCTOR_1_13.newInstance(Enum.valueOf(EnumScoreboardAction, packet.getAction().toString()), packet.getObjectiveName(), packet.getPlayer(), packet.getScore());
public static Object change(String objective, String player, int score) {
try {
NMSStorage nms = NMSStorage.getInstance();
if (nms.getMinorVersion() >= 13) {
return CONSTRUCTOR_1_13.newInstance(Enum.valueOf(EnumScoreboardAction, "CHANGE"), objective, player, score);
}
Object scoreboardScore = newScoreboardScore.newInstance(nms.emptyScoreboard, nms.newScoreboardObjective(objective), player);
ScoreboardScore_setScore.invoke(scoreboardScore, score);
if (nms.getMinorVersion() >= 8) {
return CONSTRUCTOR.newInstance(scoreboardScore);
}
return CONSTRUCTOR.newInstance(scoreboardScore, 0);
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
if (packet.getAction() == PacketPlayOutScoreboardScore.Action.REMOVE) {
return CONSTRUCTOR_String.newInstance(packet.getPlayer());
}
public static Object remove(String objective, String player) {
try {
if (NMSStorage.getInstance().getMinorVersion() >= 13) {
return CONSTRUCTOR_1_13.newInstance(Enum.valueOf(EnumScoreboardAction, "REMOVE"), objective, player, 0);
}
return CONSTRUCTOR_String.newInstance(player);
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
Object score = newScoreboardScore.newInstance(nms.emptyScoreboard, nms.newScoreboardObjective(packet.getObjectiveName()), packet.getPlayer());
ScoreboardScore_setScore.invoke(score, packet.getScore());
if (nms.getMinorVersion() >= 8) {
return CONSTRUCTOR.newInstance(score);
}
return CONSTRUCTOR.newInstance(score, 0);
}
}
@@ -3,9 +3,7 @@ package me.neznamy.tab.platforms.bukkit.nms.storage.packet;
import me.neznamy.tab.api.ProtocolVersion;
import me.neznamy.tab.api.chat.EnumChatFormat;
import me.neznamy.tab.api.chat.IChatBaseComponent;
import me.neznamy.tab.api.protocol.PacketPlayOutScoreboardTeam;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import me.neznamy.tab.shared.TAB;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
@@ -63,56 +61,21 @@ public class PacketPlayOutScoreboardTeamStorage {
}
}
public static Object build(PacketPlayOutScoreboardTeam packet, ProtocolVersion clientVersion) throws ReflectiveOperationException {
private static void createTeamModern(Object team, ProtocolVersion clientVersion, String prefix, String suffix, String visibility, String collision) throws ReflectiveOperationException {
NMSStorage nms = NMSStorage.getInstance();
Object team = newScoreboardTeam.newInstance(nms.emptyScoreboard, packet.getName());
String prefix = packet.getPlayerPrefix();
String suffix = packet.getPlayerSuffix();
if (clientVersion.getMinorVersion() < 13) {
prefix = TAB.getInstance().getPlatform().getPacketBuilder().cutTo(prefix, 16);
suffix = TAB.getInstance().getPlatform().getPacketBuilder().cutTo(suffix, 16);
}
((Collection<String>)ScoreboardTeam_getPlayerNameSet.invoke(team)).addAll(packet.getPlayers());
ScoreboardTeam_setAllowFriendlyFire.invoke(team, (packet.getOptions() & 0x1) > 0);
ScoreboardTeam_setCanSeeFriendlyInvisibles.invoke(team, (packet.getOptions() & 0x2) > 0);
if (nms.getMinorVersion() >= 13) {
createTeamModern(packet, clientVersion, team, prefix, suffix, nms);
} else {
createTeamLegacy(packet, team, prefix, suffix, nms);
}
if (nms.getMinorVersion() >= 17) {
switch (packet.getAction()) {
case 0:
return Constructor_ofBoolean.invoke(null, team, true);
case 1:
return Constructor_of.invoke(null, team);
case 2:
return Constructor_ofBoolean.invoke(null, team, false);
case 3:
return Constructor_ofString.invoke(null, team, packet.getPlayers().iterator().next(), Enum.valueOf(PlayerAction, "ADD"));
case 4:
return Constructor_ofString.invoke(null, team, packet.getPlayers().iterator().next(), Enum.valueOf(PlayerAction, "REMOVE"));
default:
throw new IllegalArgumentException("Invalid action: " + packet.getAction());
}
}
return CONSTRUCTOR.newInstance(team, packet.getAction());
}
private static void createTeamModern(PacketPlayOutScoreboardTeam packet, ProtocolVersion clientVersion, Object team, String prefix, String suffix, NMSStorage nms) throws ReflectiveOperationException {
if (prefix != null) ScoreboardTeam_setPrefix.invoke(team, nms.toNMSComponent(IChatBaseComponent.optimizedComponent(prefix), clientVersion));
if (suffix != null) ScoreboardTeam_setSuffix.invoke(team, nms.toNMSComponent(IChatBaseComponent.optimizedComponent(suffix), clientVersion));
EnumChatFormat format = packet.getColor() != null ? packet.getColor() : EnumChatFormat.lastColorsOf(prefix);
ScoreboardTeam_setColor.invoke(team, Enum.valueOf(nms.EnumChatFormat, format.toString()));
ScoreboardTeam_setNameTagVisibility.invoke(team, Enum.valueOf(EnumNameTagVisibility, String.valueOf(packet.getNameTagVisibility()).equals("always") ? "ALWAYS" : "NEVER"));
ScoreboardTeam_setCollisionRule.invoke(team, Enum.valueOf(EnumTeamPush, String.valueOf(packet.getCollisionRule()).equals("always") ? "ALWAYS" : "NEVER"));
ScoreboardTeam_setColor.invoke(team, Enum.valueOf(nms.EnumChatFormat, EnumChatFormat.lastColorsOf(prefix).toString()));
ScoreboardTeam_setNameTagVisibility.invoke(team, Enum.valueOf(EnumNameTagVisibility, String.valueOf(visibility).equals("always") ? "ALWAYS" : "NEVER"));
ScoreboardTeam_setCollisionRule.invoke(team, Enum.valueOf(EnumTeamPush, String.valueOf(collision).equals("always") ? "ALWAYS" : "NEVER"));
}
private static void createTeamLegacy(PacketPlayOutScoreboardTeam packet, Object team, String prefix, String suffix, NMSStorage nms) throws ReflectiveOperationException {
private static void createTeamLegacy(Object team, String prefix, String suffix, String visibility, String collision) throws ReflectiveOperationException {
NMSStorage nms = NMSStorage.getInstance();
if (prefix != null) ScoreboardTeam_setPrefix.invoke(team, prefix);
if (suffix != null) ScoreboardTeam_setSuffix.invoke(team, suffix);
if (nms.getMinorVersion() >= 8) ScoreboardTeam_setNameTagVisibility.invoke(team, Enum.valueOf(EnumNameTagVisibility, String.valueOf(packet.getNameTagVisibility()).equals("always") ? "ALWAYS" : "NEVER"));
if (nms.getMinorVersion() >= 9) ScoreboardTeam_setCollisionRule.invoke(team, Enum.valueOf(EnumTeamPush, String.valueOf(packet.getCollisionRule()).equals("always") ? "ALWAYS" : "NEVER"));
if (nms.getMinorVersion() >= 8) ScoreboardTeam_setNameTagVisibility.invoke(team, Enum.valueOf(EnumNameTagVisibility, String.valueOf(visibility).equals("always") ? "ALWAYS" : "NEVER"));
if (nms.getMinorVersion() >= 9) ScoreboardTeam_setCollisionRule.invoke(team, Enum.valueOf(EnumTeamPush, String.valueOf(collision).equals("always") ? "ALWAYS" : "NEVER"));
}
private static List<Field> getInstanceFields(Class<?> clazz, Class<?> type) {
@@ -125,4 +88,59 @@ public class PacketPlayOutScoreboardTeamStorage {
}
return list;
}
public static Object register(String name, String prefix, String suffix, String visibility, String collision, Collection<String> players, int options, ProtocolVersion clientVersion) {
try {
NMSStorage nms = NMSStorage.getInstance();
Object team = newScoreboardTeam.newInstance(nms.emptyScoreboard, name);
((Collection<String>)ScoreboardTeam_getPlayerNameSet.invoke(team)).addAll(players);
ScoreboardTeam_setAllowFriendlyFire.invoke(team, (options & 0x1) > 0);
ScoreboardTeam_setCanSeeFriendlyInvisibles.invoke(team, (options & 0x2) > 0);
if (nms.getMinorVersion() >= 13) {
createTeamModern(team, clientVersion, prefix, suffix, visibility, collision);
} else {
createTeamLegacy(team, prefix, suffix, visibility, collision);
}
if (nms.getMinorVersion() >= 17) {
return Constructor_ofBoolean.invoke(null, team, true);
}
return CONSTRUCTOR.newInstance(team, 0);
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
public static Object unregister(String name) {
try {
NMSStorage nms = NMSStorage.getInstance();
Object team = newScoreboardTeam.newInstance(nms.emptyScoreboard, name);
if (nms.getMinorVersion() >= 17) {
return Constructor_of.invoke(null, team);
} else {
return CONSTRUCTOR.newInstance(team, 1);
}
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
public static Object update(String name, String prefix, String suffix, String visibility, String collision, int options, ProtocolVersion clientVersion) {
try {
NMSStorage nms = NMSStorage.getInstance();
Object team = newScoreboardTeam.newInstance(nms.emptyScoreboard, name);
ScoreboardTeam_setAllowFriendlyFire.invoke(team, (options & 0x1) > 0);
ScoreboardTeam_setCanSeeFriendlyInvisibles.invoke(team, (options & 0x2) > 0);
if (nms.getMinorVersion() >= 13) {
createTeamModern(team, clientVersion, prefix, suffix, visibility, collision);
} else {
createTeamLegacy(team, prefix, suffix, visibility, collision);
}
if (nms.getMinorVersion() >= 17) {
return Constructor_ofBoolean.invoke(null, team, false);
}
return CONSTRUCTOR.newInstance(team, 2);
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
}
@@ -1,12 +1,9 @@
package me.neznamy.tab.platforms.bukkit.nms;
package me.neznamy.tab.platforms.bukkit.nms.storage.packet;
import lombok.AllArgsConstructor;
import lombok.NonNull;
import lombok.ToString;
import me.neznamy.tab.api.protocol.TabPacket;
import me.neznamy.tab.platforms.bukkit.nms.datawatcher.DataWatcher;
import me.neznamy.tab.platforms.bukkit.nms.storage.nms.NMSStorage;
import org.bukkit.Location;
import me.neznamy.tab.shared.backend.EntityData;
import me.neznamy.tab.shared.backend.Location;
import org.bukkit.entity.EntityType;
import java.lang.reflect.Constructor;
@@ -17,8 +14,7 @@ import java.util.UUID;
/**
* Custom class for holding data used in PacketPlayOutSpawnEntityLiving minecraft packet.
*/
@AllArgsConstructor @ToString
public class PacketPlayOutSpawnEntityLiving implements TabPacket {
public class PacketPlayOutSpawnEntityLivingStorage {
/** NMS Fields */
public static Class<?> CLASS;
@@ -37,13 +33,6 @@ public class PacketPlayOutSpawnEntityLiving implements TabPacket {
/** Entity types */
public static final EnumMap<EntityType, Integer> entityIds = new EnumMap<>(EntityType.class);
/** Packet's instance fields */
private final int entityId;
@NonNull private final UUID uniqueId;
@NonNull private final EntityType entityType;
@NonNull private final Location location;
private final DataWatcher dataWatcher;
/**
* Loads all required Fields and throws Exception if something went wrong
@@ -108,7 +97,7 @@ public class PacketPlayOutSpawnEntityLiving implements TabPacket {
* @throws ReflectiveOperationException
* If something went wrong
*/
public Object build() throws ReflectiveOperationException {
public static Object build(int entityId, UUID id, Object entityType, Location location, EntityData data) throws ReflectiveOperationException {
NMSStorage nms = NMSStorage.getInstance();
Object nmsPacket;
if (nms.getMinorVersion() >= 17) {
@@ -120,10 +109,10 @@ public class PacketPlayOutSpawnEntityLiving implements TabPacket {
YAW.set(nmsPacket, (byte)(location.getYaw() * 256.0f / 360.0f));
PITCH.set(nmsPacket, (byte)(location.getPitch() * 256.0f / 360.0f));
if (nms.getMinorVersion() <= 14) {
DATA_WATCHER.set(nmsPacket, dataWatcher.build());
DATA_WATCHER.set(nmsPacket, data.build());
}
if (nms.getMinorVersion() >= 9) {
UUID.set(nmsPacket, uniqueId);
UUID.set(nmsPacket, id);
X.set(nmsPacket, location.getX());
Y.set(nmsPacket, location.getY());
Z.set(nmsPacket, location.getZ());
@@ -132,16 +121,15 @@ public class PacketPlayOutSpawnEntityLiving implements TabPacket {
Y.set(nmsPacket, floor(location.getY()*32));
Z.set(nmsPacket, floor(location.getZ()*32));
}
int id = entityIds.get(entityType);
if (nms.getMinorVersion() >= 19) {
ENTITY_TYPE.set(nmsPacket, EntityTypes_ARMOR_STAND); // :(
} else {
ENTITY_TYPE.set(nmsPacket, id);
ENTITY_TYPE.set(nmsPacket, entityIds.get((EntityType) entityType));
}
return nmsPacket;
}
private int floor(double paramDouble) {
private static int floor(double paramDouble) {
int i = (int)paramDouble;
return paramDouble < i ? i - 1 : i;
}
+1 -1
View File
@@ -1,6 +1,6 @@
name: @name@
version: @version@
author: NEZNAMY
author: @author@
main: me.neznamy.tab.platforms.bukkit.BukkitTAB
api-version: 1.13
softdepend: [PlaceholderAPI, Vault, LuckPerms, Essentials, LibsDisguises, ProtocolSupport, ViaVersion, floodgate]
@@ -26,7 +26,7 @@ public class BungeeEventListener implements Listener {
@EventHandler
public void onQuit(PlayerDisconnectEvent e) {
if (TabAPI.getInstance().isPluginDisabled()) return;
TabAPI.getInstance().getThreadManager().runTask(() ->
TAB.getInstance().getCPUManager().runTask(() ->
TabAPI.getInstance().getFeatureManager().onQuit(TabAPI.getInstance().getPlayer(e.getPlayer().getUniqueId())));
}
@@ -39,7 +39,7 @@ public class BungeeEventListener implements Listener {
@EventHandler
public void onSwitch(ServerSwitchEvent e) {
if (TabAPI.getInstance().isPluginDisabled()) return;
TabAPI.getInstance().getThreadManager().runTask(() -> {
TAB.getInstance().getCPUManager().runTask(() -> {
if (TabAPI.getInstance().getPlayer(e.getPlayer().getUniqueId()) == null) {
TabAPI.getInstance().getFeatureManager().onJoin(new BungeeTabPlayer(e.getPlayer()));
} else {
@@ -73,7 +73,7 @@ public class BungeeEventListener implements Listener {
if (event.getReceiver() instanceof ProxiedPlayer) {
event.setCancelled(true);
((ProxyPlatform)TAB.getInstance().getPlatform()).getPluginMessageHandler().onPluginMessage(
((ProxiedPlayer) event.getReceiver()).getUniqueId(), event.getData());
((ProxiedPlayer) event.getReceiver()).getUniqueId(), ((ProxiedPlayer) event.getReceiver()).getName(), event.getData());
}
}
}
@@ -1,171 +0,0 @@
package me.neznamy.tab.platforms.bungeecord;
import java.util.*;
import java.util.stream.Collectors;
import me.neznamy.tab.api.ProtocolVersion;
import me.neznamy.tab.api.chat.EnumChatFormat;
import me.neznamy.tab.api.chat.IChatBaseComponent;
import me.neznamy.tab.api.protocol.*;
import me.neznamy.tab.api.protocol.PacketPlayOutPlayerInfo.EnumGamemode;
import me.neznamy.tab.api.protocol.PacketPlayOutPlayerInfo.EnumPlayerInfoAction;
import me.neznamy.tab.api.protocol.PacketPlayOutPlayerInfo.PlayerInfoData;
import net.md_5.bungee.protocol.PlayerPublicKey;
import net.md_5.bungee.protocol.Property;
import net.md_5.bungee.protocol.packet.*;
import net.md_5.bungee.protocol.packet.PlayerListItem.Item;
import net.md_5.bungee.protocol.packet.ScoreboardObjective.HealthDisplay;
/**
* Packet builder for BungeeCord platform
*/
public class BungeePacketBuilder extends PacketBuilder {
@Override
public Object build(PacketPlayOutBoss packet, ProtocolVersion clientVersion) {
if (clientVersion.getMinorVersion() < 9) return null;
BossBar bungeePacket = new BossBar(packet.getId(), packet.getAction().ordinal());
bungeePacket.setHealth(packet.getPct());
bungeePacket.setTitle(packet.getName() == null ? null : IChatBaseComponent.optimizedComponent(packet.getName()).toString(clientVersion));
bungeePacket.setColor(packet.getColor() == null ? 0 : packet.getColor().ordinal());
bungeePacket.setDivision(packet.getOverlay() == null ? 0: packet.getOverlay().ordinal());
bungeePacket.setFlags(packet.getFlags());
return bungeePacket;
}
@Override
public Object build(PacketPlayOutChat packet, ProtocolVersion clientVersion) {
if (clientVersion.getMinorVersion() >= 19) {
return new SystemChat(packet.getMessage().toString(clientVersion), (byte) packet.getType().ordinal());
} else {
return new Chat(packet.getMessage().toString(clientVersion), (byte) packet.getType().ordinal());
}
}
@Override
public Object build(PacketPlayOutPlayerInfo packet, ProtocolVersion clientVersion) {
if (clientVersion.getNetworkId() >= ProtocolVersion.V1_19_3.getNetworkId() &&
packet.getActions().contains(EnumPlayerInfoAction.REMOVE_PLAYER)) {
PlayerListItemRemove remove = new PlayerListItemRemove();
remove.setUuids(packet.getEntries().stream().map(PlayerInfoData::getUniqueId).toArray(UUID[]::new));
return remove;
}
List<Item> items = new ArrayList<>();
for (PlayerInfoData data : packet.getEntries()) {
Item item = new Item();
if (data.getDisplayName() != null) {
if (clientVersion.getMinorVersion() >= 8) {
item.setDisplayName(data.getDisplayName().toString(clientVersion));
} else {
item.setDisplayName(data.getDisplayName().toLegacyText());
}
} else if (clientVersion.getMinorVersion() < 8) {
item.setDisplayName(String.valueOf(data.getName())); //avoiding NPE, 1.7 client requires this, 1.8 added a leading boolean
}
if (data.getGameMode() != null) item.setGamemode(data.getGameMode().ordinal() - 1);
item.setListed(data.isListed());
item.setPing(data.getLatency());
if (data.getSkin() != null) {
item.setProperties(new Property[]{new Property("textures", data.getSkin().getValue(), data.getSkin().getSignature())});
} else {
item.setProperties(new Property[0]);
}
item.setUsername(data.getName());
item.setUuid(data.getUniqueId());
item.setChatSessionId(data.getChatSessionId());
item.setPublicKey((PlayerPublicKey) data.getProfilePublicKey());
items.add(item);
}
if (clientVersion.getNetworkId() >= ProtocolVersion.V1_19_3.getNetworkId()) {
PlayerListItemUpdate bungeePacket = new PlayerListItemUpdate();
PlayerListItemUpdate.Action[] array = packet.getActions().stream().map(action -> PlayerListItemUpdate.Action.valueOf(
action.toString().replace("GAME_MODE", "GAMEMODE"))).toArray(PlayerListItemUpdate.Action[]::new);
bungeePacket.setActions(EnumSet.of(array[0], array));
bungeePacket.setItems(items.toArray(new Item[0]));
return bungeePacket;
} else {
PlayerListItem bungeePacket = new PlayerListItem();
EnumPlayerInfoAction action = packet.getActions().contains(EnumPlayerInfoAction.ADD_PLAYER) ?
EnumPlayerInfoAction.ADD_PLAYER : packet.getActions().iterator().next();
bungeePacket.setAction(PlayerListItem.Action.valueOf(action.toString().replace("GAME_MODE", "GAMEMODE")));
bungeePacket.setItems(items.toArray(new Item[0]));
return bungeePacket;
}
}
@Override
public Object build(PacketPlayOutPlayerListHeaderFooter packet, ProtocolVersion clientVersion) {
return new PlayerListHeaderFooter(packet.getHeader().toString(clientVersion, true), packet.getFooter().toString(clientVersion, true));
}
@Override
public Object build(PacketPlayOutScoreboardDisplayObjective packet, ProtocolVersion clientVersion) {
return new ScoreboardDisplay((byte)packet.getSlot(), packet.getObjectiveName());
}
@Override
public Object build(PacketPlayOutScoreboardObjective packet, ProtocolVersion clientVersion) {
return new ScoreboardObjective(packet.getObjectiveName(), jsonOrCut(packet.getDisplayName(), clientVersion, 32), packet.getRenderType() == null ? null : HealthDisplay.valueOf(packet.getRenderType().toString()), (byte)packet.getAction());
}
@Override
public Object build(PacketPlayOutScoreboardScore packet, ProtocolVersion clientVersion) {
return new ScoreboardScore(packet.getPlayer(), (byte) packet.getAction().ordinal(), packet.getObjectiveName(), packet.getScore());
}
@Override
public Object build(PacketPlayOutScoreboardTeam packet, ProtocolVersion clientVersion) {
int color = 0;
if (clientVersion.getMinorVersion() >= 13) {
color = (packet.getColor() != null ? packet.getColor() : EnumChatFormat.lastColorsOf(packet.getPlayerPrefix())).ordinal();
}
return new Team(packet.getName(), (byte)packet.getAction(), jsonOrCut(packet.getName(), clientVersion, 16), jsonOrCut(packet.getPlayerPrefix(), clientVersion, 16), jsonOrCut(packet.getPlayerSuffix(), clientVersion, 16),
packet.getNameTagVisibility(), packet.getCollisionRule(), color, (byte)packet.getOptions(), packet.getPlayers().toArray(new String[0]));
}
@Override
public PacketPlayOutPlayerInfo readPlayerInfo(Object bungeePacket, ProtocolVersion clientVersion) {
if (bungeePacket instanceof PlayerListItemRemove) {
return new PacketPlayOutPlayerInfo(
EnumPlayerInfoAction.REMOVE_PLAYER,
Arrays.stream(((PlayerListItemRemove) bungeePacket).getUuids()).map(PlayerInfoData::new).collect(Collectors.toList())
);
}
Item[] items = bungeePacket instanceof PlayerListItemUpdate ? ((PlayerListItemUpdate) bungeePacket).getItems() : ((PlayerListItem) bungeePacket).getItems();
List<PlayerInfoData> listData = new ArrayList<>();
for (Item i : items) {
Skin skin = i.getProperties() == null || i.getProperties().length == 0 ? null : new Skin(i.getProperties()[0].getValue(), i.getProperties()[0].getSignature());
listData.add(new PlayerInfoData(
i.getUsername(),
i.getUuid(),
skin,
Boolean.TRUE.equals(i.getListed()),
i.getPing() == null ? 0 : i.getPing(),
i.getGamemode() == null ? null : EnumGamemode.VALUES[i.getGamemode()+1],
IChatBaseComponent.deserialize(i.getDisplayName()),
i.getChatSessionId(),
i.getPublicKey()));
}
if (bungeePacket instanceof PlayerListItemUpdate) {
EnumPlayerInfoAction[] array = ((PlayerListItemUpdate) bungeePacket).getActions().stream().map(this::toCustomAction).toArray(EnumPlayerInfoAction[]::new);
return new PacketPlayOutPlayerInfo(EnumSet.of(array[0], array), listData);
} else {
return new PacketPlayOutPlayerInfo(toCustomAction(((PlayerListItem) bungeePacket).getAction()), listData);
}
}
private EnumPlayerInfoAction toCustomAction(Object action) {
return EnumPlayerInfoAction.valueOf(action.toString().replace("GAMEMODE", "GAME_MODE"));
}
@Override
public PacketPlayOutScoreboardObjective readObjective(Object bungeePacket) {
return new PacketPlayOutScoreboardObjective(((ScoreboardObjective) bungeePacket).getAction(), ((ScoreboardObjective) bungeePacket).getName(),
null, PacketPlayOutScoreboardObjective.EnumScoreboardHealthDisplay.INTEGER);
}
@Override
public PacketPlayOutScoreboardDisplayObjective readDisplayObjective(Object bungeePacket) {
return new PacketPlayOutScoreboardDisplayObjective(((ScoreboardDisplay) bungeePacket).getPosition(), ((ScoreboardDisplay) bungeePacket).getName());
}
}
@@ -2,23 +2,24 @@ package me.neznamy.tab.platforms.bungeecord;
import com.google.common.collect.Lists;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
import lombok.AllArgsConstructor;
import lombok.Getter;
import me.neznamy.tab.api.TabFeature;
import java.lang.reflect.Field;
import me.neznamy.tab.api.feature.TabFeature;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.api.chat.IChatBaseComponent;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.shared.features.PipelineInjector;
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 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.packet.ScoreboardDisplay;
import net.md_5.bungee.protocol.packet.ScoreboardObjective;
import net.md_5.bungee.protocol.packet.Team;
import net.md_5.bungee.protocol.packet.*;
import java.util.Collection;
import java.util.function.Function;
@@ -30,13 +31,138 @@ import java.util.function.Supplier;
* from overriding it.
*/
@SuppressWarnings("unchecked")
public class BungeePipelineInjector extends PipelineInjector {
public class BungeePipelineInjector extends NettyPipelineInjector {
private static Field wrapperField;
static {
try {
(wrapperField = InitialHandler.class.getDeclaredField("ch")).setAccessible(true);
} 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 */
private final Class<? extends DefinedPacket>[] extraPacketClasses = new Class[]{Team.class, ScoreboardDisplay.class, ScoreboardObjective.class};
private final Supplier<DefinedPacket>[] extraPacketSuppliers = new Supplier[]{Team::new, ScoreboardDisplay::new, ScoreboardObjective::new};
@Getter private final Function<TabPlayer, ChannelDuplexHandler> channelFunction = BungeeChannelDuplexHandler::new;
@Override
public Function<TabPlayer, ChannelDuplexHandler> getChannelFunction() {
return byteBufDeserialization ? DeserializableBungeeChannelDuplexHandler::new : TabChannelDuplexHandler::new;
}
@Override
protected Channel getChannel(TabPlayer player) {
final BungeeTabPlayer bungee = (BungeeTabPlayer) player;
try {
return ((ChannelWrapper) wrapperField.get(bungee.getPlayer().getPendingConnection())).getHandle();
} catch (final IllegalAccessException exception) {
TAB.getInstance().getErrorManager().criticalError("Failed to get channel of " + bungee.getPlayer().getName(), exception);
}
return null;
}
@Override
public void onDisplayObjective(TabPlayer player, Object packet) {
TAB.getInstance().getFeatureManager().onDisplayObjective(player,
((ScoreboardDisplay) packet).getPosition(), ((ScoreboardDisplay) packet).getName());
}
@Override
public void onObjective(TabPlayer player, Object packet) {
TAB.getInstance().getFeatureManager().onObjective(player,
((ScoreboardObjective) packet).getAction(), ((ScoreboardObjective) packet).getName());
}
@Override
public boolean isDisplayObjective(Object packet) {
return packet instanceof ScoreboardDisplay;
}
@Override
public boolean isObjective(Object packet) {
return packet instanceof ScoreboardObjective;
}
@Override
public boolean isTeam(Object packet) {
return packet instanceof Team;
}
@Override
public boolean isPlayerInfo(Object packet) {
return packet instanceof PlayerListItem || packet instanceof PlayerListItemUpdate;
}
@Override
public void modifyPlayers(Object team) {
Team packet = (Team) team;
if (packet.getMode() == 1 || packet.getMode() == 2 || packet.getMode() == 4) return;
Collection<String> col = Lists.newArrayList(packet.getPlayers());
for (TabPlayer p : TAB.getInstance().getOnlinePlayers()) {
Sorting sorting = (Sorting) TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.SORTING);
if (col.contains(p.getNickname()) && !((TabFeature)TAB.getInstance().getTeamManager()).isDisabledPlayer(p) &&
!TAB.getInstance().getTeamManager().hasTeamHandlingPaused(p) && !packet.getName().equals(sorting.getShortTeamName(p))) {
logTeamOverride(packet.getName(), p.getName(), sorting.getShortTeamName(p));
col.remove(p.getNickname());
}
}
RedisBungeeSupport redis = (RedisBungeeSupport) TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.REDIS_BUNGEE);
if (redis != null) {
for (RedisPlayer p : redis.getRedisPlayers().values()) {
if (col.contains(p.getNickname()) && !packet.getName().equals(p.getTeamName())) {
logTeamOverride(packet.getName(), p.getName(), p.getTeamName());
col.remove(p.getNickname());
}
}
}
packet.setPlayers(col.toArray(new String[0]));
}
@Override
public boolean isLogin(Object packet) {
return packet instanceof Login;
}
@Override
public void onPlayerInfo(TabPlayer receiver, Object packet) {
PlayerListItem.Item[] items;
if (packet instanceof PlayerListItemUpdate) {
PlayerListItemUpdate update = (PlayerListItemUpdate) packet;
for (PlayerListItem.Item item : update.getItems()) {
if (update.getActions().contains(PlayerListItemUpdate.Action.UPDATE_GAMEMODE)) {
item.setGamemode(TAB.getInstance().getFeatureManager().onGameModeChange(receiver, item.getUuid(), item.getGamemode()));
}
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.UPDATE_DISPLAY_NAME)) {
IChatBaseComponent displayName = IChatBaseComponent.deserialize(item.getDisplayName());
displayName = TAB.getInstance().getFeatureManager().onDisplayNameChange(receiver, item.getUuid(), displayName);
item.setDisplayName(displayName == null ? null : displayName.toString(receiver.getVersion()));
}
if (update.getActions().contains(PlayerListItemUpdate.Action.ADD_PLAYER)) {
TAB.getInstance().getFeatureManager().onEntryAdd(receiver, item.getUuid(), item.getUsername());
}
}
} else {
PlayerListItem listItem = (PlayerListItem) packet;
for (PlayerListItem.Item item : listItem.getItems()) {
if (listItem.getAction() == PlayerListItem.Action.UPDATE_GAMEMODE || listItem.getAction() == PlayerListItem.Action.ADD_PLAYER) {
item.setGamemode(TAB.getInstance().getFeatureManager().onGameModeChange(receiver, item.getUuid(), item.getGamemode()));
}
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.UPDATE_DISPLAY_NAME || listItem.getAction() == PlayerListItem.Action.ADD_PLAYER) {
IChatBaseComponent displayName = IChatBaseComponent.deserialize(item.getDisplayName());
displayName = TAB.getInstance().getFeatureManager().onDisplayNameChange(receiver, item.getUuid(), displayName);
item.setDisplayName(displayName == null ? null : displayName.toString(receiver.getVersion()));
}
}
}
}
/**
* Constructs new instance of the feature
@@ -45,91 +171,12 @@ public class BungeePipelineInjector extends PipelineInjector {
super("inbound-boss");
}
/**
* Custom channel duplex handler override
*/
@AllArgsConstructor
public class BungeeChannelDuplexHandler extends ChannelDuplexHandler {
/** Injected player */
protected final TabPlayer player;
@Override
public void write(ChannelHandlerContext context, Object packet, ChannelPromise channelPromise) {
try {
switch(packet.getClass().getSimpleName()) {
case "PlayerListItem":
case "PlayerListItemUpdate":
case "PlayerListItemRemove":
super.write(context, TAB.getInstance().getFeatureManager().onPacketPlayOutPlayerInfo(player, packet), channelPromise);
return;
case "Team":
if (antiOverrideTeams) {
long time = System.nanoTime();
modifyPlayers((Team) packet);
TAB.getInstance().getCPUManager().addTime("NameTags", TabConstants.CpuUsageCategory.ANTI_OVERRIDE, System.nanoTime()-time);
}
break;
case "ScoreboardDisplay":
TAB.getInstance().getFeatureManager().onDisplayObjective(player, packet);
break;
case "ScoreboardObjective":
TAB.getInstance().getFeatureManager().onObjective(player, packet);
break;
case "Login":
//making sure to not send own packets before login packet is actually sent
super.write(context, packet, channelPromise);
TAB.getInstance().getFeatureManager().onLoginPacket(player);
return;
default:
break;
}
} catch (Exception | NoSuchMethodError e) {
TAB.getInstance().getErrorManager().printError("An error occurred when analyzing packets for player " + player.getName() + " with client version " + player.getVersion().getFriendlyName(), e);
}
try {
super.write(context, packet, channelPromise);
} catch (Exception e) {
TAB.getInstance().getErrorManager().printError(String.format("Failed to forward packet %s to %s", packet.getClass().getSimpleName(), player.getName()), e);
}
}
/**
* Removes all real players from packet if the packet doesn't come from TAB
*
* @param packet
* packet to modify
*/
private void modifyPlayers(Team packet) {
if (packet.getMode() == 1 || packet.getMode() == 2 || packet.getMode() == 4) return;
Collection<String> col = Lists.newArrayList(packet.getPlayers());
for (TabPlayer p : TAB.getInstance().getOnlinePlayers()) {
Sorting sorting = (Sorting) TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.SORTING);
if (col.contains(p.getNickname()) && !((TabFeature)TAB.getInstance().getTeamManager()).isDisabledPlayer(p) &&
!TAB.getInstance().getTeamManager().hasTeamHandlingPaused(p) && !packet.getName().equals(sorting.getShortTeamName(p))) {
logTeamOverride(packet.getName(), p.getName(), sorting.getShortTeamName(p));
col.remove(p.getNickname());
}
}
RedisBungeeSupport redis = (RedisBungeeSupport) TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.REDIS_BUNGEE);
if (redis != null) {
for (RedisPlayer p : redis.getRedisPlayers().values()) {
if (col.contains(p.getNickname()) && !packet.getName().equals(p.getTeamName())) {
logTeamOverride(packet.getName(), p.getName(), p.getTeamName());
col.remove(p.getNickname());
}
}
}
packet.setPlayers(col.toArray(new String[0]));
}
}
/**
* Channel duplex handler override if features using packets that must be
* deserialized manually are used. If they are disabled, deserialization is
* disabled for better performance.
*/
public class DeserializableBungeeChannelDuplexHandler extends BungeeChannelDuplexHandler {
public class DeserializableBungeeChannelDuplexHandler extends TabChannelDuplexHandler {
/**
* Constructs new instance with given player
@@ -19,7 +19,6 @@ import org.jetbrains.annotations.Nullable;
public class BungeePlatform extends ProxyPlatform {
private final Plugin plugin;
@Getter private final BungeePacketBuilder packetBuilder = new BungeePacketBuilder();
@Getter private final BungeePipelineInjector pipelineInjector = new BungeePipelineInjector();
@Override
@@ -0,0 +1,104 @@
package me.neznamy.tab.platforms.bungeecord;
import lombok.NonNull;
import me.neznamy.tab.api.ProtocolVersion;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.api.chat.EnumChatFormat;
import me.neznamy.tab.api.chat.IChatBaseComponent;
import me.neznamy.tab.shared.TabScoreboard;
import net.md_5.bungee.protocol.packet.ScoreboardDisplay;
import net.md_5.bungee.protocol.packet.ScoreboardObjective;
import net.md_5.bungee.protocol.packet.ScoreboardScore;
import net.md_5.bungee.protocol.packet.Team;
import java.util.Collection;
/**
* Scoreboard handler for BungeeCord. Because it does not offer
* any Scoreboard API and the scoreboard class it has is just a
* downstream tracker, we need to use packets.
*/
public class BungeeScoreboard extends TabScoreboard {
public BungeeScoreboard(TabPlayer player) {
super(player);
}
@Override
public void setDisplaySlot(DisplaySlot slot, @NonNull String objective) {
player.sendPacket(new ScoreboardDisplay((byte)slot.ordinal(), objective));
}
@Override
public void registerObjective0(@NonNull String objectiveName, @NonNull String title, boolean hearts) {
player.sendPacket(new ScoreboardObjective(objectiveName, jsonOrRaw(title, player.getVersion()), hearts ? ScoreboardObjective.HealthDisplay.HEARTS : ScoreboardObjective.HealthDisplay.INTEGER, (byte) 0));
}
@Override
public void unregisterObjective0(@NonNull String objectiveName) {
player.sendPacket(new ScoreboardObjective(objectiveName, "", null, (byte) 1));
}
@Override
public void updateObjective0(@NonNull String objectiveName, @NonNull String title, boolean hearts) {
player.sendPacket(new ScoreboardObjective(objectiveName, jsonOrRaw(title, player.getVersion()), hearts ? ScoreboardObjective.HealthDisplay.HEARTS : ScoreboardObjective.HealthDisplay.INTEGER, (byte) 2));
}
@Override
public void registerTeam0(@NonNull String name, String prefix, String suffix, String visibility, String collision, 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()),
visibility, collision, color, (byte)options, players.toArray(new String[0])));
}
@Override
public void unregisterTeam0(@NonNull String name) {
player.sendPacket(new Team(name));
}
@Override
public void updateTeam0(@NonNull String name, String prefix, String suffix, String visibility, String 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, collision, 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(String text, ProtocolVersion clientVersion) {
if (text == null) return null;
if (clientVersion.getMinorVersion() >= 13) {
return IChatBaseComponent.optimizedComponent(text).toString(clientVersion);
} else {
return text;
}
}
@Override
public void setScore0(@NonNull String objective, @NonNull String playerName, int score) {
player.sendPacket(new ScoreboardScore(playerName, (byte) 0, objective, score));
}
@Override
public void removeScore0(@NonNull String objective, @NonNull String playerName) {
player.sendPacket(new ScoreboardScore(playerName, (byte) 1, objective, 0));
}
}
@@ -1,32 +1,46 @@
package me.neznamy.tab.platforms.bungeecord;
import lombok.Getter;
import lombok.NonNull;
import me.neznamy.tab.api.BossBarHandler;
import me.neznamy.tab.api.ProtocolVersion;
import me.neznamy.tab.api.Scoreboard;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.api.protocol.Skin;
import me.neznamy.tab.api.chat.IChatBaseComponent;
import me.neznamy.tab.api.tablist.Skin;
import me.neznamy.tab.api.tablist.TabList;
import me.neznamy.tab.api.util.ComponentCache;
import me.neznamy.tab.platforms.bungeecord.bossbar.BungeeBossBarHandler;
import me.neznamy.tab.platforms.bungeecord.tablist.BungeeTabList1_19_3;
import me.neznamy.tab.platforms.bungeecord.tablist.BungeeTabList1_7;
import me.neznamy.tab.platforms.bungeecord.tablist.BungeeTabList1_8;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.proxy.ProxyTabPlayer;
import net.md_5.bungee.UserConnection;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.chat.ComponentSerializer;
import net.md_5.bungee.connection.InitialHandler;
import net.md_5.bungee.connection.LoginResult;
import net.md_5.bungee.netty.ChannelWrapper;
import net.md_5.bungee.protocol.DefinedPacket;
import net.md_5.bungee.protocol.Property;
import net.md_5.bungee.protocol.Protocol;
import net.md_5.bungee.protocol.packet.LoginRequest;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.UUID;
/**
* TabPlayer implementation for BungeeCord
*/
public class BungeeTabPlayer extends ProxyTabPlayer {
/** Component cache to save CPU when creating components */
private static final ComponentCache<IChatBaseComponent, BaseComponent[]> componentCache = new ComponentCache<>(10000,
(component, clientVersion) -> ComponentSerializer.parse(component.toString(clientVersion)));
/** Inaccessible bungee internals */
private static Field wrapperField;
private static Object directionData;
private static Method getId;
@@ -35,15 +49,22 @@ public class BungeeTabPlayer extends ProxyTabPlayer {
Field f = Protocol.class.getDeclaredField("TO_CLIENT");
f.setAccessible(true);
directionData = f.get(Protocol.GAME);
getId = directionData.getClass().getDeclaredMethod("getId", Class.class, int.class);
getId.setAccessible(true);
wrapperField = InitialHandler.class.getDeclaredField("ch");
wrapperField.setAccessible(true);
(getId = directionData.getClass().getDeclaredMethod("getId", Class.class, int.class)).setAccessible(true);
} catch (ReflectiveOperationException e) {
TAB.getInstance().getErrorManager().criticalError("Failed to initialize bungee internal fields", e);
}
}
/** Player's scoreboard */
@Getter private final Scoreboard scoreboard = new BungeeScoreboard(this);
/** Player's tablist based on version */
private final TabList tabList1_7 = new BungeeTabList1_7(this);
private final TabList tabList1_8 = new BungeeTabList1_8(this);
private final TabList tabList1_19_3 = new BungeeTabList1_19_3(this);
@Getter private final BossBarHandler bossBarHandler = new BungeeBossBarHandler(this);
/**
* Constructs new instance for given player
*
@@ -51,20 +72,12 @@ public class BungeeTabPlayer extends ProxyTabPlayer {
* BungeeCord player
*/
public BungeeTabPlayer(ProxiedPlayer p) {
super(p, p.getUniqueId(), p.getName(), p.getServer() != null ? p.getServer().getInfo().getName() : "-", -1, true);
try {
channel = ((ChannelWrapper)wrapperField.get(getPlayer().getPendingConnection())).getHandle();
} catch (IllegalAccessException e) {
TAB.getInstance().getErrorManager().criticalError("Failed to get channel of " + getPlayer().getName(), e);
}
super(p, p.getUniqueId(), p.getName(), p.getServer() != null ? p.getServer().getInfo().getName() : "-", -1);
}
@Override
public boolean hasPermission0(@NonNull String permission) {
long time = System.nanoTime();
boolean value = getPlayer().hasPermission(permission);
TAB.getInstance().getCPUManager().addMethodTime("hasPermission", System.nanoTime()-time);
return value;
return getPlayer().hasPermission(permission);
}
@Override
@@ -74,9 +87,12 @@ public class BungeeTabPlayer extends ProxyTabPlayer {
@Override
public void sendPacket(Object nmsPacket) {
long time = System.nanoTime();
if (nmsPacket != null && getPlayer().isConnected()) getPlayer().unsafe().sendPacket((DefinedPacket) nmsPacket);
TAB.getInstance().getCPUManager().addMethodTime("sendPacket", System.nanoTime()-time);
getPlayer().unsafe().sendPacket((DefinedPacket) nmsPacket);
}
@Override
public void sendMessage(IChatBaseComponent message) {
getPlayer().sendMessage(componentCache.get(message, getVersion()));
}
@Override
@@ -109,6 +125,13 @@ public class BungeeTabPlayer extends ProxyTabPlayer {
}
}
/**
* If ViaVersion is installed on BungeeCord, it changes protocol to match version
* of server to which player is connected to. For that reason, we need to retrieve
* the field more often than just on join.
*
* @return Player's current protocol version
*/
@Override
public ProtocolVersion getVersion() {
return ProtocolVersion.fromNetworkId(getPlayer().getPendingConnection().getVersion());
@@ -137,19 +160,29 @@ public class BungeeTabPlayer extends ProxyTabPlayer {
}
@Override
public Object getProfilePublicKey() {
return ((InitialHandler)getPlayer().getPendingConnection()).getLoginRequest().getPublicKey();
public void setPlayerListHeaderFooter(@NonNull IChatBaseComponent header, @NonNull IChatBaseComponent footer) {
getPlayer().setTabHeader(componentCache.get(header, getVersion()), componentCache.get(footer, getVersion()));
}
@Override
public UUID getChatSessionId() {
return ((InitialHandler)getPlayer().getPendingConnection()).getLoginRequest().getUuid();
public TabList getTabList() {
return getVersion().getNetworkId() >= ProtocolVersion.V1_19_3.getNetworkId() ?
tabList1_19_3 : getVersion().getMinorVersion() >= 8 ? tabList1_8 : tabList1_7;
}
@Override
public Object getChatSession() {
LoginRequest login = ((InitialHandler)getPlayer().getPendingConnection()).getLoginRequest();
return new Object[]{login.getUuid(), login.getPublicKey()}; // BungeeCord has no single object for this
}
@Override
public void sendPluginMessage(byte[] message) {
if (getPlayer().getServer() == null) return;
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);
TAB.getInstance().getCPUManager().packetSent("Plugin Message");
}
}
@@ -2,6 +2,7 @@ package me.neznamy.tab.platforms.bungeecord;
import com.imaginarycode.minecraft.redisbungee.RedisBungeeAPI;
import com.imaginarycode.minecraft.redisbungee.events.PubSubMessageEvent;
import lombok.AllArgsConstructor;
import me.neznamy.tab.api.TabConstants;
import me.neznamy.tab.shared.features.redis.RedisSupport;
import net.md_5.bungee.api.ProxyServer;
@@ -12,14 +13,17 @@ import net.md_5.bungee.event.EventHandler;
/**
* Redis implementation for BungeeCord
*/
@AllArgsConstructor
public class RedisBungeeSupport extends RedisSupport implements Listener {
/**
* Constructs new instance, registers listeners and overrides placeholders
*/
public RedisBungeeSupport(Plugin plugin) {
/** Plugin reference for registering listener */
private final Plugin plugin;
@Override
public void load() {
ProxyServer.getInstance().getPluginManager().registerListener(plugin, this);
RedisBungeeAPI.getRedisBungeeApi().registerPubSubChannels(TabConstants.REDIS_CHANNEL_NAME);
super.load();
}
@EventHandler
@@ -0,0 +1,96 @@
package me.neznamy.tab.platforms.bungeecord.bossbar;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.api.BossBarHandler;
import me.neznamy.tab.api.bossbar.BarColor;
import me.neznamy.tab.api.bossbar.BarStyle;
import me.neznamy.tab.api.chat.IChatBaseComponent;
import me.neznamy.tab.platforms.bungeecord.BungeeTabPlayer;
import net.md_5.bungee.protocol.packet.BossBar;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* BossBar handler for BungeeCord. It uses packets, since
* BungeeCord does not have a BossBar API. Only supports
* 1.9+ players, as dealing with entities would be simply impossible.
*/
@RequiredArgsConstructor
public class BungeeBossBarHandler implements BossBarHandler {
private final BungeeTabPlayer player;
/** Both are included in the same action, need to remember and write them both */
private final Map<UUID, Integer> colors = new HashMap<>();
private final Map<UUID, Integer> styles = new HashMap<>();
@Override
public void create(@NonNull UUID id, @NonNull String title, float progress, @NonNull BarColor color, @NonNull BarStyle style) {
if (player.getVersion().getMinorVersion() < 9) return;
BossBar bossbar = new BossBar(id, 0);
bossbar.setHealth(progress);
bossbar.setTitle(IChatBaseComponent.optimizedComponent(title).toString(player.getVersion()));
bossbar.setColor(color.ordinal());
bossbar.setDivision(style.ordinal());
player.getPlayer().unsafe().sendPacket(bossbar);
colors.put(id, color.ordinal());
styles.put(id, style.ordinal());
}
@Override
public void update(@NonNull UUID id, @NonNull String title) {
if (player.getVersion().getMinorVersion() < 9) return;
BossBar bossbar = new BossBar(id, 3);
bossbar.setTitle(IChatBaseComponent.optimizedComponent(title).toString(player.getVersion()));
player.getPlayer().unsafe().sendPacket(bossbar);
}
@Override
public void update(@NonNull UUID id, float progress) {
if (player.getVersion().getMinorVersion() < 9) return;
BossBar bossbar = new BossBar(id, 2);
bossbar.setHealth(progress);
player.getPlayer().unsafe().sendPacket(bossbar);
}
@Override
public void update(@NonNull UUID id, @NonNull BarStyle style) {
if (player.getVersion().getMinorVersion() < 9) return;
BossBar bossbar = new BossBar(id, 4);
bossbar.setDivision(style.ordinal());
bossbar.setColor(colors.get(id));
player.getPlayer().unsafe().sendPacket(bossbar);
styles.put(id, style.ordinal());
}
@Override
public void update(@NonNull UUID id, @NonNull BarColor color) {
if (player.getVersion().getMinorVersion() < 9) return;
BossBar bossbar = new BossBar(id, 4);
bossbar.setDivision(styles.get(id));
bossbar.setColor(color.ordinal());
player.getPlayer().unsafe().sendPacket(bossbar);
colors.put(id, color.ordinal());
}
@Override
public void remove(@NonNull UUID id) {
if (player.getVersion().getMinorVersion() < 9) return;
player.getPlayer().unsafe().sendPacket(new BossBar(id, 1));
colors.remove(id);
styles.remove(id);
}
}
Loaded 100 of 244 files, more files were not shown because too many files have changed in this diff. Show more