mirror of
https://github.com/NEZNAMY/TAB.git
synced 2026-11-13 12:42:06 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6f2b92e044 | ||
|
|
5da16e8055 | ||
|
|
a522ea9902 | ||
|
|
36801184a5 | ||
|
|
d212eb108d | ||
|
|
cbc1de0f33 | ||
|
|
fcdc04e228 | ||
|
|
890f1ee131 | ||
|
|
f3aec47ac3 | ||
|
|
bcf1c828b5 | ||
|
|
5fd319a152 | ||
|
|
abecb58a8d | ||
|
|
135166b01e | ||
|
|
0770293452 | ||
|
|
f3c40ad582 | ||
|
|
aacc115a36 | ||
|
|
b7ba07b78d | ||
|
|
e13445f55d | ||
|
|
bd527d3868 | ||
|
|
f288112eb2 | ||
|
|
6b415e64a8 | ||
|
|
cfb2cb6b0a | ||
|
|
622deb6fd7 | ||
|
|
7f41421d65 | ||
|
|
73e706465a | ||
|
|
a8c543ca9e | ||
|
|
9594ff452b | ||
|
|
d5c81298dd | ||
|
|
88faacb240 | ||
|
|
0d2a04b3a5 | ||
|
|
f8a90b28b5 | ||
|
|
a948febb18 | ||
|
|
8e33481b97 | ||
|
|
f54be214f9 | ||
|
|
d50b47acb5 | ||
|
|
31011c243a | ||
|
|
f4aa03f20d | ||
|
|
28aacaea45 | ||
|
|
d4fd9a2b78 | ||
|
|
663e0be357 | ||
|
|
14d29577c5 | ||
|
|
951f8072c1 | ||
|
|
05f042eda0 | ||
|
|
b00c2fbcb4 | ||
|
|
dde4866e50 | ||
|
|
9fa6b21c08 | ||
|
|
e95e6fc20c | ||
|
|
c7a3b00c3c | ||
|
|
74bc0e0cb7 | ||
|
|
6248008b4d | ||
|
|
2d91e165ce | ||
|
|
b1220550eb | ||
|
|
680ff3412f | ||
|
|
799f6b8215 | ||
|
|
1be224b7de | ||
|
|
a32adfbfc7 | ||
|
|
dda415351a | ||
|
|
685e4b9faa | ||
|
|
1f842563b3 | ||
|
|
f237b61065 | ||
|
|
19d6d06791 | ||
|
|
6873c59505 | ||
|
|
e7ae62db7b | ||
|
|
d4362f2ed2 | ||
|
|
9247f3d4fb | ||
|
|
b4993b657e | ||
|
|
b73aa93d01 | ||
|
|
bce689e672 | ||
|
|
f4468261cf | ||
|
|
cc926ff974 | ||
|
|
6c3831ded4 | ||
|
|
3c55866d46 | ||
|
|
dfc9359f04 | ||
|
|
ea0897db31 | ||
|
|
25cf6c95e8 | ||
|
|
f5e2910e0b | ||
|
|
fda35986a4 | ||
|
|
b97e86c2ed | ||
|
|
ebb02717b5 | ||
|
|
1b82ebfd25 | ||
|
|
3fb788ef6d | ||
|
|
11bc06182c | ||
|
|
2fe5dc833d | ||
|
|
317cb975ce | ||
|
|
9bb2a97e50 | ||
|
|
53dcc60587 | ||
|
|
db347bc192 | ||
|
|
608b8307dd | ||
|
|
4c027da093 | ||
|
|
bef60e688c | ||
|
|
b987ad99f4 | ||
|
|
3036e2d523 | ||
|
|
1bab2d0025 | ||
|
|
4cac6c659e | ||
|
|
c7c59238e1 | ||
|
|
f51e74c066 | ||
|
|
52f8801638 | ||
|
|
4e5c5a7073 | ||
|
|
436f3879c4 | ||
|
|
c1395e26d4 | ||
|
|
3750d6ddb1 | ||
|
|
45c3601b49 | ||
|
|
957da1d040 | ||
|
|
43932eb6b3 | ||
|
|
024ddeefb4 | ||
|
|
94190b5cd7 | ||
|
|
5d72f90a0f | ||
|
|
90c30023c2 | ||
|
|
d39a89659b | ||
|
|
79f4386b62 | ||
|
|
39c4d8ae96 | ||
|
|
c8b2abbb3e | ||
|
|
06b7bbd711 | ||
|
|
11afd15610 | ||
|
|
4951025e61 | ||
|
|
2337a122da | ||
|
|
072333bf48 | ||
|
|
2bd8924df7 | ||
|
|
23cdc3721d | ||
|
|
957e4ba0c0 |
No files matched your search
@@ -1,5 +1,5 @@
|
||||
# About
|
||||
This folder contains the plugin's description as advertised on various marketplaces which use various syntaxes
|
||||
This folder contains the plugin's description as advertised on various marketplaces where each one uses a different syntax.
|
||||
(bbcode, markdown).
|
||||
|
||||
While this is not an accurate folder to have this stored in (`.github`),
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
TAB is an all-in-one plugin for displaying information in various places, which aims to outperform all similar plugins in terms of features, compatibility and performance.
|
||||
The compact configuration allows you to get the plugin to work the way you want regardless of how simple or complex you want it to be or how experienced you are. The default configuration already contains useful values and examples to give you a better understanding and get you started quickly with instant results. The extensive [URL='https://github.com/NEZNAMY/TAB/wiki']wiki[/URL] answers any of your questions.
|
||||
|
||||
[IMG]https://img.shields.io/badge/Release-5.0.7-blue.svg[/IMG]
|
||||
[IMG]https://img.shields.io/badge/Minecraft-1.5%20--%201.21.4-blue.svg[/IMG]
|
||||
[IMG]https://img.shields.io/badge/Java-8+-blue.svg[/IMG]
|
||||
[URL='https://github.com/NEZNAMY/TAB'][IMG]https://img.shields.io/badge/GitHub-Source%20code-yellow.svg[/IMG][/URL]
|
||||
[IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Badge_Release.svg[/IMG]
|
||||
[IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Badge_Minecraft.svg[/IMG]
|
||||
[IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Badge_Java.svg[/IMG]
|
||||
[IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Badge_SourceCode.svg[/IMG]
|
||||
[URL='https://github.com/NEZNAMY/TAB/issues'][IMG]https://img.shields.io/github/issues/neznamy/tab?label=Issues[/IMG][/URL]
|
||||
[URL='https://github.com/NEZNAMY/TAB/wiki'][IMG]https://img.shields.io/badge/Documentation-Wiki-yellow.svg[/IMG][/URL]
|
||||
[IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Badge_Documentation.svg[/IMG]
|
||||
[CENTER][B][SIZE=7]Discord?[/SIZE][/B][/CENTER]
|
||||
Official support by me is no longer provided to free users due to limited time and extreme inefficiency (see [URL='https://github.com/NEZNAMY/TAB?tab=readme-ov-file#provided-services']provided services[/URL] for more info).
|
||||
However, you can join this [URL='https://discord.gg/YPqXt63YQj']fresh new community discord[/URL] made by a nice person who promises to help everyone with all plugins, including TAB.
|
||||
@@ -15,164 +15,20 @@ However, you can join this [URL='https://discord.gg/YPqXt63YQj']fresh new commun
|
||||
Before you consider requiring assistance, check out the [URL='https://github.com/NEZNAMY/TAB/wiki']wiki[/URL] first. It will answer any questions you may have and is also constantly getting improved.
|
||||
|
||||
[CENTER][IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Section_Features.png[/IMG]
|
||||
[IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Section_HeaderFooter.png[/IMG]
|
||||
[IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Example_HeaderFooter.png[/IMG][/CENTER]
|
||||
[LIST]
|
||||
[*]Per-world / per-server support
|
||||
[*]Per-group / per-player support
|
||||
[*]Disable if a specified condition is met
|
||||
[/LIST]
|
||||
[CENTER][IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Section_NameTags.png[/IMG]
|
||||
[IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Example_NameTags.png[/IMG][/CENTER]
|
||||
[LIST]
|
||||
[*]Configurable prefix and suffix
|
||||
[*]Per-world / per-server support
|
||||
[*]Per-group / per-player support
|
||||
[*]Disable if a specified condition is met
|
||||
[*]Make nametags completely invisible
|
||||
[*]Control collision rule
|
||||
[*]Automatic compensation for 1.8.x client sided bug making nametags of invisible players still visible
|
||||
[*]Anti-override to prevent other plugins from overriding this one
|
||||
[/LIST]
|
||||
[IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Section_Sorting.png[/IMG]
|
||||
[CENTER][IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Example_Sorting.png[/IMG][/CENTER]
|
||||
[LIST]
|
||||
[*]Sorting by primary permission group
|
||||
[*]Sorting by permission nodes
|
||||
[*]Sorting by a numeric placeholder from lowest to highest or highest to lowest
|
||||
[*]Alphabetically by a placeholder (A-Z or Z-A)
|
||||
[*]Sorting by pre-defined placeholder values
|
||||
[*]Combination of multiple sorting types
|
||||
[*]Enable / disable case-sensitive sorting
|
||||
[/LIST]
|
||||
[CENTER][IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Section_TablistFormatting.png[/IMG]
|
||||
[IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Example_TablistFormatting.png[/IMG][/CENTER]
|
||||
[LIST]
|
||||
[*]Configurable prefix, name and suffix
|
||||
[*]Per-world / per-server support
|
||||
[*]Per-group / per-player support
|
||||
[*]Disable if a specified condition is met
|
||||
[*]Anti-override to prevent other plugins from overriding this one
|
||||
[/LIST]
|
||||
[CENTER][IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Section_PlayerlistObjective.png[/IMG]
|
||||
[IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Example_PlayerlistObjectiveNumber.png[/IMG]
|
||||
[IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Example_PlayerlistObjectiveHealth.png[/IMG]
|
||||
[IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Example_PlayerlistObjectiveFancy.png[/IMG][/CENTER]
|
||||
[LIST]
|
||||
[*]Display value of any numeric placeholder or hearts
|
||||
[*]Display any text on 1.20.3+
|
||||
[*]Disable if a specified condition is met
|
||||
[/LIST]
|
||||
[CENTER][IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Section_Belowname.png[/IMG]
|
||||
[IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Example_BelownameComparison.png[/IMG][/CENTER]
|
||||
[LIST]
|
||||
[*]Display value of any numeric placeholder (<1.20.3) or any text (1.20.3+)
|
||||
[*]Configurable text
|
||||
[*]Disable if a specified condition is met
|
||||
[/LIST]
|
||||
[CENTER][IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Section_Bossbar.png[/IMG]
|
||||
[IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Example_BossBar.png[/IMG][/CENTER]
|
||||
[LIST]
|
||||
[*]Configurable text, progress, color and style
|
||||
[*]Define display condition to see a bossbar
|
||||
[*]Announce defined bossbar for specified amount of time
|
||||
[*]Configurable toggle command and message
|
||||
[*]Option to remember toggle choice between logins
|
||||
[/LIST]
|
||||
[CENTER][IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Section_GlobalPlayerlist.png[/IMG][/CENTER]
|
||||
[LIST]
|
||||
[*]Display players from all servers on a proxy network in tablist
|
||||
[*]Configurable server groups to share playerlist instead of with all servers
|
||||
[*]Ability to create isolated servers
|
||||
[*]Servers where people will see everyone
|
||||
[*]Compatible with vanish plugins
|
||||
[*]Option to display players from other servers as spectators
|
||||
[/LIST]
|
||||
[CENTER][IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Section_Layout.png[/IMG]
|
||||
[IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Example_Layout.png[/IMG][/CENTER]
|
||||
[LIST]
|
||||
[*]Customize all 80 tablist slots
|
||||
[*]Create static slots with any text and skin
|
||||
[*]Create player groups based on conditions
|
||||
[*]Multiple layouts based on conditions
|
||||
[/LIST]
|
||||
[CENTER][IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Section_Scoreboard.png[/IMG]
|
||||
[IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Example_ScoreboardComparison.png[/IMG][/CENTER]
|
||||
[LIST]
|
||||
[*]Display up to 15 lines
|
||||
[*]Option to show 0 (or any other static number) in every line instead of 1-15
|
||||
[*]Fully configurable text on right side of lines on 1.20.3+
|
||||
[*]Displaying different scoreboards based on conditions (output of placeholder, permissions)
|
||||
[*]Up to 64 characters per line for <1.13, unlimited for 1.13+ clients
|
||||
[*]Customizable toggle command and message
|
||||
[*]Option to remember toggle choice between logins
|
||||
[*]Automatic detection of scoreboards from other plugins to hide own and show it again once other plugin hides its scoreboard
|
||||
[/LIST]
|
||||
[CENTER][IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Section_PerWorldPlayerlist.png[/IMG][/CENTER]
|
||||
[LIST]
|
||||
[*]Only display players from current world in tablist
|
||||
[*]Group worlds which should share players
|
||||
[*]Worlds where players see everyone
|
||||
[*]Option to see all players in all worlds for players with permission
|
||||
[/LIST]
|
||||
[CENTER][IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Section_API.png[/IMG][/CENTER]
|
||||
[LIST]
|
||||
[*]Change player prefix/name/suffix in tablist
|
||||
[*]Change header/footer
|
||||
[*]Change nametag prefix / suffix
|
||||
[*]Change player collision rule and nametag visibility
|
||||
[*]Change sorting priority
|
||||
[*]Create custom scoreboards, show different scoreboard, toggle scoreboard
|
||||
[*]Create custom placeholders
|
||||
[*]Create custom boss bars, toggle bossbar visibility, announce bossbar
|
||||
[*]... and more!
|
||||
[/LIST]
|
||||
[LIST]
|
||||
[*]Placeholder support
|
||||
[LIST]
|
||||
[*]Internal placeholders with customizable output
|
||||
[*]Create and use custom animations
|
||||
[*]All placeholders and animations are supported in all features
|
||||
[*]Full PlaceholderAPI support including relational placeholders
|
||||
[*]PlaceholderAPI support on BungeeCord
|
||||
[/LIST]
|
||||
[*]Placeholder output replacements
|
||||
[LIST]
|
||||
[*]Modify output of any placeholder, including PlaceholderAPI placeholders
|
||||
[*]Replace exact text with another text
|
||||
[*]Change output if it's in a number interval
|
||||
[*]Set value if none of the above is met
|
||||
[*]Use original placeholder's output in new one
|
||||
[*]Support for nested placeholders
|
||||
[*]Can be used in other plugins as well
|
||||
[/LIST]
|
||||
[*]RGB Support
|
||||
[LIST]
|
||||
[*]5 supported RGB patterns
|
||||
[*]4 supported gradient patterns
|
||||
[*]Automatically display closest color to legacy clients
|
||||
[*]Configurable color for legacy clients
|
||||
[*]Supported in all features [B]where possible[/B]
|
||||
[/LIST]
|
||||
[*]Conditional placeholders
|
||||
[LIST]
|
||||
[*]Conditions based on placeholder output (comparing numeric value, exact output)
|
||||
[*]Conditions based on permission node
|
||||
[*]Combine conditions with AND or OR
|
||||
[*]Configurable output of both cases when condition is and isn't met
|
||||
[*]Placeholder support in condition output
|
||||
[/LIST]
|
||||
[*]High performance
|
||||
[LIST]
|
||||
[*]Full control of CPU usage by optimizing configuration and disabling unwanted features
|
||||
[*]Fully asynchronous - no impact on TPS
|
||||
[*]Check plugin's CPU usage using /tab cpu
|
||||
[/LIST]
|
||||
[*]RedisBungee support
|
||||
[*]MySQL support
|
||||
[*]Fully client-sided, server-side stays unaffected (scoreboards with their teams and objectives, bossbar entities, armor stands)
|
||||
[*]Disable vanilla feature making players in spectator gamemode appear on the bottom of tablist
|
||||
[*]Possible errors moved into a separate file to not spam your console
|
||||
[*]Yaml error assistant for more user-friendly messages and fix suggestions
|
||||
[*]Many misconfiguration checks to help solve issues much faster
|
||||
[/LIST]
|
||||
[IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/refs/heads/master/.github/description/images/Generated_HeaderFooter.png[/IMG][IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/refs/heads/master/.github/description/images/Generated_NameTags.png[/IMG]
|
||||
[IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/refs/heads/master/.github/description/images/Generated_Sorting.png[/IMG]
|
||||
[IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/refs/heads/master/.github/description/images/Generated_TablistFormatting.png[/IMG]
|
||||
[IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/refs/heads/master/.github/description/images/Generated_PlayerlistObjective.png[/IMG]
|
||||
[IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/refs/heads/master/.github/description/images/Generated_Belowname.png[/IMG]
|
||||
[IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/refs/heads/master/.github/description/images/Generated_Bossbar.png[/IMG]
|
||||
[IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/refs/heads/master/.github/description/images/Generated_GlobalPlayerlist.png[/IMG]
|
||||
[IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/refs/heads/master/.github/description/images/Generated_Layout.png[/IMG]
|
||||
[IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/refs/heads/master/.github/description/images/Generated_Scoreboard.png[/IMG]
|
||||
[IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/refs/heads/master/.github/description/images/Generated_PerWorldPlayerlist.png[/IMG]
|
||||
[IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/refs/heads/master/.github/description/images/Generated_API.png[/IMG]
|
||||
[IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/refs/heads/master/.github/description/images/Generated_Placeholders.png[/IMG]
|
||||
[IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/refs/heads/master/.github/description/images/Generated_PlaceholderOutputReplacements.png[/IMG]
|
||||
[IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/refs/heads/master/.github/description/images/Generated_RGB.png[/IMG]
|
||||
[IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/refs/heads/master/.github/description/images/Generated_ConditionalPlaceholders.png[/IMG]
|
||||
[IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/refs/heads/master/.github/description/images/Generated_HighPerformance.png[/IMG]
|
||||
[IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/refs/heads/master/.github/description/images/Generated_More.png[/IMG][/CENTER]
|
||||
@@ -4,12 +4,12 @@ The compact configuration allows you to get the plugin to work the way you want
|
||||
The default configuration already contains useful values and examples to give you a better understanding and get you started quickly with instant results.
|
||||
The extensive [wiki](https://github.com/NEZNAMY/TAB/wiki) answers any of your questions.
|
||||
|
||||

|
||||

|
||||

|
||||
[](https://github.com/NEZNAMY/TAB)
|
||||
[](https://github.com/NEZNAMY/TAB/issues)
|
||||
[](https://github.com/NEZNAMY/TAB/wiki)
|
||||

|
||||

|
||||

|
||||
[](https://github.com/NEZNAMY/TAB)
|
||||
[](https://github.com/NEZNAMY/TAB/issues)
|
||||
[](https://github.com/NEZNAMY/TAB/wiki)
|
||||
|
||||
# Discord?
|
||||
Official support by me is no longer provided to free users due to limited time and extreme inefficiency (see [provided services]([https://github.com/NEZNAMY/TAB?tab=readme-ov-file#provided-services](https://github.com/NEZNAMY/TAB?tab=readme-ov-file#provided-services)) for more info).
|
||||
@@ -18,148 +18,21 @@ However, you can join this [fresh new community discord]([https://discord.gg/YPq
|
||||
Before you consider requiring assistance, check out the [wiki](https://github.com/NEZNAMY/TAB/wiki) first. It will answer any questions you may have and is also constantly getting improved.
|
||||
|
||||

|
||||

|
||||

|
||||
* Per-world / per-server support
|
||||
* Per-group / per-player support
|
||||
* Disable if a specified condition is met
|
||||
|
||||

|
||||

|
||||
* Configurable prefix and suffix
|
||||
* Per-world / per-server support
|
||||
* Per-group / per-player support
|
||||
* Disable if a specified condition is met
|
||||
* Make nametags completely invisible
|
||||
* Control collision rule
|
||||
* Automatic compensation for 1.8.x client sided bug making nametags of invisible players still visible
|
||||
* Anti-override to prevent other plugins from overriding this one
|
||||
|
||||

|
||||

|
||||
* Sorting by primary permission group
|
||||
* Sorting by permission nodes
|
||||
* Sorting by a numeric placeholder from lowest to highest or highest to lowest
|
||||
* Alphabetically by a placeholder (A-Z or Z-A)
|
||||
* Sorting by pre-defined placeholder values
|
||||
* Combination of multiple sorting types
|
||||
* Enable / disable case-sensitive sorting
|
||||
|
||||

|
||||

|
||||
* Configurable prefix, name and suffix
|
||||
* Per-world / per-server support
|
||||
* Per-group / per-player support
|
||||
* Disable if a specified condition is met
|
||||
* Anti-override to prevent other plugins from overriding this one
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
* Display value of any numeric placeholder or hearts
|
||||
* Display any text on 1.20.3+
|
||||
* Disable if a specified condition is met
|
||||
|
||||

|
||||

|
||||
* Display value of any numeric placeholder (<1.20.3) or any text (1.20.3+)
|
||||
* Configurable text
|
||||
* Disable if a specified condition is met
|
||||
|
||||

|
||||

|
||||
* Configurable text, progress, color and style
|
||||
* Define display condition to see a bossbar
|
||||
* Announce defined bossbar for specified amount of time
|
||||
* Configurable toggle command and message
|
||||
* Option to remember toggle choice between logins
|
||||
|
||||

|
||||
* Display players from all servers on a proxy network in tablist
|
||||
* Configurable server groups to share playerlist instead of with all servers
|
||||
* Ability to create isolated servers
|
||||
* Servers where people will see everyone
|
||||
* Compatible with vanish plugins
|
||||
* Option to display players from other servers as spectators
|
||||
|
||||

|
||||

|
||||
* Customize all 80 tablist slots
|
||||
* Create static slots with any text and skin
|
||||
* Create player groups based on conditions
|
||||
* Multiple layouts based on conditions
|
||||
|
||||

|
||||

|
||||
* Display up to 15 lines
|
||||
* Option to show 0 (or any other static number) in every line instead of 1-15
|
||||
* Fully configurable text on the right side of lines on 1.20.3+
|
||||
* Displaying different scoreboards based on conditions (output of placeholder, permissions)
|
||||
* Up to 64 characters per line for <1.13, unlimited for 1.13+ clients
|
||||
* Customizable toggle command and message
|
||||
* Option to remember toggle choice between logins
|
||||
* Automatic detection of scoreboards from other plugins to hide own and show it again once the other plugin hides its scoreboard
|
||||
|
||||

|
||||
* Only display players from the same world in tablist
|
||||
* Group worlds which should share players
|
||||
* Worlds where players see everyone
|
||||
* Option to see all players in all worlds for players with permission
|
||||
|
||||

|
||||
* Change player prefix/name/suffix in tablist
|
||||
* Change header/footer
|
||||
* Change nametag prefix / suffix
|
||||
* Change player collision rule and nametag visibility
|
||||
* Change sorting priority
|
||||
* Create custom scoreboards, show different scoreboard, toggle scoreboard
|
||||
* Create custom placeholders
|
||||
* Create custom boss bars, toggle bossbar visibility, announce bossbar
|
||||
* ... and more!
|
||||
|
||||
.
|
||||
.
|
||||
.
|
||||
.
|
||||
.
|
||||
Placeholder support
|
||||
* Internal placeholders with customizable output
|
||||
* Create and use custom animations
|
||||
* All placeholders and animations are supported in all features
|
||||
* Full [PlaceholderAPI]('https://www.spigotmc.org/resources/placeholderapi.6245/') support including [relational placeholders]('https://www.spigotmc.org/resources/placeholderapi.6245/update?update=167267')
|
||||
* PlaceholderAPI support on BungeeCord / Velocity
|
||||
|
||||
Placeholder output replacements
|
||||
* Modify output of any placeholder, including PlaceholderAPI placeholders
|
||||
* Replace exact text with another text
|
||||
* Change output if it's in a number interval
|
||||
* Set value if none of the above is met
|
||||
* Use original placeholder's output in new one
|
||||
* Support for nested placeholders
|
||||
* Can be used in other plugins as well
|
||||
|
||||
RGB Support
|
||||
* 5 supported RGB patterns
|
||||
* 3 supported gradient patterns
|
||||
* Supported in all features **where possible**
|
||||
|
||||
Conditional placeholders
|
||||
* Conditions based on placeholder output (comparing numeric value, exact output)
|
||||
* Conditions based on permission node
|
||||
* Combine conditions with `AND` or `OR`
|
||||
* Configurable output of both cases when condition is and isn't met
|
||||
* Placeholder support in conditions
|
||||
|
||||
High performance
|
||||
* Full control of CPU usage by optimizing configuration and disabling unwanted features
|
||||
* Fully asynchronous - no impact on TPS
|
||||
* Check plugin's CPU usage using /tab cpu
|
||||
|
||||
RedisBungee support
|
||||
MySQL support
|
||||
Fully client-sided, server-side stays unaffected
|
||||
Disable vanilla feature making players in spectator gamemode appear on the bottom of tablist
|
||||
Possible errors moved into a separate file to not spam your console
|
||||
Yaml error assistant for more user-friendly messages and fix suggestions
|
||||
Many misconfiguration checks to help solve issues much faster
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="126" height="20" role="img" aria-label="Documentation: Wiki"><title>Documentation: Wiki</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="126" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="93" height="20" fill="#555"/><rect x="93" width="33" height="20" fill="#dfb317"/><rect width="126" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="475" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="830">Documentation</text><text x="475" y="140" transform="scale(.1)" fill="#fff" textLength="830">Documentation</text><text aria-hidden="true" x="1085" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="230">Wiki</text><text x="1085" y="140" transform="scale(.1)" fill="#fff" textLength="230">Wiki</text></g></svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="60" height="20" role="img" aria-label="Java: 8+"><title>Java: 8+</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="60" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="35" height="20" fill="#555"/><rect x="35" width="25" height="20" fill="#007ec6"/><rect width="60" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="185" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="250">Java</text><text x="185" y="140" transform="scale(.1)" fill="#fff" textLength="250">Java</text><text aria-hidden="true" x="465" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="150">8+</text><text x="465" y="140" transform="scale(.1)" fill="#fff" textLength="150">8+</text></g></svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="138" height="20" role="img" aria-label="Minecraft: 1.5 - 1.21.6"><title>Minecraft: 1.5 - 1.21.6</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="138" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="61" height="20" fill="#555"/><rect x="61" width="77" height="20" fill="#007ec6"/><rect width="138" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="315" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="510">Minecraft</text><text x="315" y="140" transform="scale(.1)" fill="#fff" textLength="510">Minecraft</text><text aria-hidden="true" x="985" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="670">1.5 - 1.21.6</text><text x="985" y="140" transform="scale(.1)" fill="#fff" textLength="670">1.5 - 1.21.6</text></g></svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="92" height="20" role="img" aria-label="Release: 5.2.3"><title>Release: 5.2.3</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="92" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="53" height="20" fill="#555"/><rect x="53" width="39" height="20" fill="#007ec6"/><rect width="92" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="275" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="430">Release</text><text x="275" y="140" transform="scale(.1)" fill="#fff" textLength="430">Release</text><text aria-hidden="true" x="715" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="290">5.2.3</text><text x="715" y="140" transform="scale(.1)" fill="#fff" textLength="290">5.2.3</text></g></svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="124" height="20" role="img" aria-label="GitHub: Source code"><title>GitHub: Source code</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="124" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="47" height="20" fill="#555"/><rect x="47" width="77" height="20" fill="#dfb317"/><rect width="124" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="245" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="370">GitHub</text><text x="245" y="140" transform="scale(.1)" fill="#fff" textLength="370">GitHub</text><text aria-hidden="true" x="845" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="670">Source code</text><text x="845" y="140" transform="scale(.1)" fill="#fff" textLength="670">Source code</text></g></svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -1,4 +1,5 @@
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
import requests
|
||||
|
||||
line_spacing = 10
|
||||
padding = 10
|
||||
@@ -85,6 +86,18 @@ def create_image(overlay_text, second_images, output_path, texts):
|
||||
# Save
|
||||
generator.save(final_img)
|
||||
|
||||
def download_image(url, save_path):
|
||||
try:
|
||||
response = requests.get(url, stream=True)
|
||||
response.raise_for_status()
|
||||
|
||||
with open(save_path, "wb") as file:
|
||||
for chunk in response.iter_content(1024):
|
||||
file.write(chunk)
|
||||
|
||||
except requests.RequestException as e:
|
||||
print(f"Error downloading image: {e}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
create_image("Header & Footer", ["Example_HeaderFooter.png"], "Generated_HeaderFooter.png",
|
||||
[
|
||||
@@ -260,3 +273,16 @@ if __name__ == "__main__":
|
||||
Text("Many misconfiguration checks to help solve issues much faster")
|
||||
]
|
||||
)
|
||||
|
||||
release = "5.2.3"
|
||||
download_image(f"https://img.shields.io/badge/Release-{release}-blue.svg", "Badge_Release.svg")
|
||||
|
||||
minecraft = "1.5 - 1.21.6".replace(" ", "%20").replace("-", "--")
|
||||
download_image(f"https://img.shields.io/badge/Minecraft-{minecraft}-blue.svg", "Badge_Minecraft.svg")
|
||||
|
||||
java = "8+"
|
||||
download_image(f"https://img.shields.io/badge/Java-{java}-blue.svg", "Badge_Java.svg")
|
||||
|
||||
download_image(f"https://img.shields.io/badge/GitHub-Source%20code-yellow.svg", "Badge_SourceCode.svg")
|
||||
|
||||
download_image(f"https://img.shields.io/badge/Documentation-Wiki-yellow.svg", "Badge_Documentation.svg")
|
||||
@@ -14,11 +14,11 @@
|
||||
* [Global playerlist](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Global-playerlist)
|
||||
* [Header/Footer](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Header-&-Footer)
|
||||
* [Layout](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Layout)
|
||||
* [Multi server support](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Multi-server-support)
|
||||
* [Nametags](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Nametags)
|
||||
* [Per world playerlist](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Per-world-playerlist)
|
||||
* [Ping spoof](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Ping-Spoof)
|
||||
* [Playerlist objective](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Playerlist-Objective)
|
||||
* [RedisBungee support](https://github.com/NEZNAMY/TAB/wiki/Feature-info:-RedisBungee-support)
|
||||
* [Scoreboard](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Scoreboard)
|
||||
* [Sorting in tablist](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Sorting-players-in-tablist)
|
||||
* [Spectator fix](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Spectator-fix)
|
||||
|
||||
@@ -11,19 +11,19 @@ This is a collection of random information about the plugin not necessarily limi
|
||||
* [Debug config option](#debug-config-option)
|
||||
|
||||
# Vanish detection
|
||||
Although TAB does not manage vanishing players in any way,
|
||||
it checks vanish status for features to be properly compatible.
|
||||
TAB is not a vanish plugin and does not manage vanishing players in any way, therefore, is not responsible for removing vanished players from the tablist.
|
||||
However, it checks vanish status for features to be properly compatible.
|
||||
At the moment, vanishing detection is used for:
|
||||
* `%vanished%` placeholder (used for internal functionality, but can be used to display vanish status if one wants to)
|
||||
* `%online%`, `%serveronline%`, `%worldonline%` and `%staffonline%` will not count vanished players
|
||||
* [Global playerlist](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Global-playerlist) makes vanish plugins unable to remove players on other servers from tablist, vanish check will make TAB remove vanished players.
|
||||
* Hide vanished players from [Layout](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Layout).
|
||||
* Unregister teams of vanished players to players who cannot see them to prevent third party client exploits from seeing vanished players.
|
||||
* [Unregister teams of vanished players](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Nametags#additional-note-6---vanish-hook) to players who cannot see them to prevent third party client exploits from seeing vanished players.
|
||||
|
||||
Permission `tab.seevanished` allows players to:
|
||||
* See vanished players in [Global playerlist](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Global-playerlist).
|
||||
* See vanished players in [Layout](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Layout).
|
||||
* Not have teams of vanished players unregistered, which would make them not appear sorted properly and have no nametag prefix/suffix.
|
||||
* Not have [teams of vanished players unregistered](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Nametags#additional-note-6---vanish-hook), which would make them not appear sorted properly and have no nametag prefix/suffix.
|
||||
|
||||
For a player to be detected, player has to have `vanished` metadata flag set to `true`.
|
||||
Most, (if not all) vanish plugins do this, including, but not limited to CMI,
|
||||
|
||||
@@ -8,22 +8,23 @@ Commands have required permissions associated with them.
|
||||
|
||||
# Content
|
||||
* [Configuration](#configuration-commands)
|
||||
* [/tab \<player/group/playeruuid\> \<name\> \<property\> [value] [options]](#tab-playergroupplayeruuid-name-property-value-options)
|
||||
* [/tab \<player/group/playeruuid\> \<name\> \<property\> \[value\] \[options\]](#tab-playergroupplayeruuid-name-property-value-options)
|
||||
* [/tab \<player/group/playeruuid\> \<name\> remove](#tab-playergroupplayeruuid-name-remove)
|
||||
* [/tab reload](#tab-reload)
|
||||
* [/tab debug [player]](#tab-debug-player)
|
||||
* [/tab debug \[player\]](#tab-debug-player)
|
||||
* [/tab group \<group\>](#tab-group-group)
|
||||
* [/tab groups](#tab-groups)
|
||||
* [Bossbar](#bossbar)
|
||||
* [/tab bossbar send \<name\> \[player\]](#tab-bossbar-show-name-player)
|
||||
* [/tab bossbar [on/off/toggle] [player] [options]](#tab-bossbar-onofftoggle-player-options)
|
||||
* [/tab bossbar \[on/off/toggle\] \[player\] \[options\]](#tab-bossbar-onofftoggle-player-options)
|
||||
* [/tab bossbar announce \<name\> \<length\>](#tab-bossbar-announce-name-duration)
|
||||
* [Scoreboard](#scoreboard)
|
||||
* [/tab scoreboard show \<name\> \[player\]](#tab-scoreboard-show-name-player)
|
||||
* [/tab scoreboard [on/off/toggle] [player] [options]](#tab-scoreboard-onofftoggle-player-options)
|
||||
* [/tab scoreboard \[on/off/toggle\] \[player\] \[options\]](#tab-scoreboard-onofftoggle-player-options)
|
||||
* [/tab scoreboard announce \<name\> \<length\>](#tab-scoreboard-announce-name-duration)
|
||||
* [Nametags / Teams](#nametags--teams)
|
||||
* [/tab nametag toggle](#tab-nametag-toggle)
|
||||
* [/tab nametag <show/hide/toggle> \[player\] \[viewer\] \[options\]](#tab-nametag-showhidetoggle-player-viewer-options)
|
||||
* [/tab nametag <showview/hideview/toggleview> \[viewer\] \[options\]](#tab-nametag-showviewhideviewtoggleview-viewer-options)
|
||||
* [/tab setcollision <player> <true/false>](#tab-setcollision-player-truefalse)
|
||||
* [MySQL](#mysql)
|
||||
* [/tab mysql upload](#tab-mysql-upload)
|
||||
@@ -34,7 +35,7 @@ Commands have required permissions associated with them.
|
||||
* [Additional permissions](#additional-permissions)
|
||||
|
||||
# Configuration commands
|
||||
## /tab \<player/group/playeruuid\> \<name\> \<property\> [value] [options]
|
||||
## /tab \<player/group/playeruuid\> \<name\> \<property\> \[value\] \[options\]
|
||||
* **Permission:** `tab.change.<property>`
|
||||
* **Description:** Changes a property of a group/player to the given value. No value argument will result in that property being deleted.
|
||||
|
||||
@@ -83,7 +84,7 @@ Commands have required permissions associated with them.
|
||||
* **Permission:** `tab.scoreboard.show` for showing to yourself, `tab.scoreboard.show.other` for showing to others.
|
||||
* **Description:** Shows the scoreboard with the given `name`, either to yourself if no `player` was given, or to the given `player`.
|
||||
|
||||
## /tab scoreboard [on/off/toggle] [player] [options]
|
||||
## /tab scoreboard \[on/off/toggle\] \[player\] \[options\]
|
||||
* **Permission:** `tab.scoreboard.toggle` for toggling for yourself, `tab.scoreboard.toggle.other` for toggling for others.
|
||||
* **Description:** Shows / hides / toggles scoreboard of specified player. If no player was given, command affects the sender.
|
||||
* **Options:**
|
||||
@@ -98,7 +99,7 @@ Commands have required permissions associated with them.
|
||||
* **Permission:** `tab.bossbar.show` for showing to yourself, `tab.bossbar.show.other` for showing to others.
|
||||
* **Description:** Shows the bossbar with the given `name`, either to yourself if no `player` was given, or to the given `player`.
|
||||
|
||||
## /tab bossbar [on/off/toggle] [player] [options]
|
||||
## /tab bossbar \[on/off/toggle\] \[player\] \[options\]
|
||||
* **Permission:** `tab.bossbar.toggle` for toggling for yourself, `tab.bossbar.toggle.other` for toggling for others.
|
||||
* **Description:** Shows / hides / toggles bossbar of specified player. If no player was given, command affects the sender.
|
||||
* **Options:**
|
||||
@@ -109,9 +110,17 @@ Commands have required permissions associated with them.
|
||||
* **Description:** Shows the bossbar with the given `name` to every player on the server for the given `duration`, in seconds.
|
||||
|
||||
# Nametags / Teams
|
||||
## /tab nametag toggle
|
||||
* **Permission:** `tab.nametag.toggle`
|
||||
* **Description:** Toggles nametag visibility on all players for player who ran the command. Running the command again will make nametags visible again.
|
||||
## /tab nametag <show/hide/toggle> \[player\] \[viewer\] \[options\]
|
||||
* **Permission:** `tab.nametag.visibility` (`tab.nametag.visibility.other` for toggling for other players)
|
||||
* **Description:** Shows / hides / toggles nametag of a specified player. If viewer is specified, view is only affected for the viewer.
|
||||
* **Options:**
|
||||
* `-s` for silent toggling (no chat message for affected player)
|
||||
|
||||
## /tab nametag <showview/hideview/toggleview> \[viewer\] \[options\]
|
||||
* **Permission:** `tab.nametag.view` (`tab.nametag.view.other` for toggling for other players)
|
||||
* **Description:** Shows / hides / toggles nametag VIEW a specified player.
|
||||
* **Options:**
|
||||
* `-s` for silent toggling (no chat message for affected player)
|
||||
|
||||
## /tab setcollision \<player\> \<true|false\>
|
||||
* **Permission:** `tab.setcollision`
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# Content
|
||||
* [Requirements](#requirements)
|
||||
* [Java](#java)
|
||||
* [Supported server software and versions](#supported-server-software-and-versions)
|
||||
* [Supported features per platform](#supported-features-per-platform)
|
||||
* [Plugin hooks](#plugin-hooks)
|
||||
@@ -7,13 +8,19 @@
|
||||
* [Bukkit](#bukkit)
|
||||
* [BungeeCord](#bungeecord)
|
||||
* [Fabric](#fabric)
|
||||
* [Forge / NeoForge](#forge--neoforge)
|
||||
* [Sponge](#sponge)
|
||||
* [Velocity](#velocity)
|
||||
* [Compatibility issues](#compatibility-issues)
|
||||
|
||||
# Requirements
|
||||
TAB does not depend on any other plugins.
|
||||
It is compiled with Java 8 and therefore supports any version of 8 and above.
|
||||
TAB does not depend on any other plugins. The experience can however be enhanced by installing other plugins, see below.
|
||||
|
||||
## Java
|
||||
TAB's shared module is compiled with Java 8. For the platforms themselves, TAB does have a higher requirement, however, it does not require a Java version higher than the server software itself does. Specifically:
|
||||
* Sponge, Fabric, Forge and NeoForge require Java 21
|
||||
* Velocity requires Java 17
|
||||
* Bukkit and BungeeCord are ok with Java 8
|
||||
|
||||
# Supported server software and versions
|
||||
<table>
|
||||
@@ -28,42 +35,44 @@ It is compiled with Java 8 and therefore supports any version of 8 and above.
|
||||
<tr>
|
||||
<td rowspan=2>Vanilla</td>
|
||||
<td rowspan=1><a href="https://getbukkit.org/">Bukkit</a> (+forge hybrids)</td>
|
||||
<td>✔ (1.5 - 1.21.4)</td>
|
||||
<td>✔ (1.5 - 1.21.5)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan=1><a href="https://www.spongepowered.org/">Sponge</a></td>
|
||||
<td>✔ (1.9<sup>1</sup> - 1.21.4)</td>
|
||||
<td>✔ (1.20.6 - 1.21.5)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td rowspan=2>Modded</td>
|
||||
<td rowspan=3>Modded</td>
|
||||
<td rowspan=1><a href="https://fabricmc.net">Fabric</a></td>
|
||||
<td>✔ (1.14 - 1.21.4)</td>
|
||||
<td>✔ (1.21.4 - 1.21.5)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan=1><a href="https://minecraftforge.net">Forge</a></td>
|
||||
<td>❌</td>
|
||||
<td>✔ (1.21.4 - 1.21.5)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan=1><a href="https://neoforged.net/">NeoForge</a></td>
|
||||
<td>✔ (1.21.4 - 1.21.5)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td rowspan=2>Proxies</td>
|
||||
<td rowspan=1><a href="https://ci.md-5.net/job/BungeeCord/">BungeeCord</a></td>
|
||||
<td>✔ (latest only<sup>2</sup>)</td>
|
||||
<td>✔ (latest only<sup>1</sup>)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan=1><a href="https://www.velocitypowered.com/">Velocity</a></td>
|
||||
<td>✔ (latest only<sup>2</sup>)</td>
|
||||
<td>✔ (latest only<sup>1</sup>)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<sup>1</sup> Due to a bug in Sponge 7, it cannot coexist in the same jar with classes compiled with Java higher than 8, which is required by some other platforms. To get the plugin to run on Sponge 7, you will need to do one of the following:
|
||||
|
||||
* Compile the plugin yourself and use the jar from `sponge7/build/libs`, which only contains sponge support instead of the universal jar from `jar/build/libs`.
|
||||
* Unzip the plugin jar, go to `me/neznamy/tab/platforms/` and delete everything except `sponge7`.
|
||||
Modded platforms only support the latest MC version(s). For older MC versions try [modrinth](https://modrinth.com/plugin/tab-was-taken)'s version download filter.
|
||||
|
||||
<sup>2</sup> Latest only doesn't mean only the latest build will work,
|
||||
<sup>1</sup> Latest only doesn't mean only the latest build will work,
|
||||
it means the plugin was made to be compatible with the latest version/build.
|
||||
Since breaking changes don't happen too often, it means a wide range of versions is usually supported.
|
||||
When a breaking change occurs, the plugin is updated to support the new version,
|
||||
@@ -72,19 +81,19 @@ Since proxies support all client versions, there is never a reason to stay outda
|
||||
so you can always safely update to new version/build of your proxy software if the plugin requires it.
|
||||
|
||||
# Supported features per platform
|
||||
| Feature \ Platform | Bukkit / Hybrid | Sponge | Fabric | BungeeCord | Velocity |
|
||||
| ------------- | ------------- | ------------- | ------------- | ------------- | ------------- |
|
||||
| [Belowname](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Belowname) | ✔ | ✔ | ✔ | ✔ | ✔ (via [VSAPI](https://github.com/NEZNAMY/VelocityScoreboardAPI/releases)) |
|
||||
| [BossBar](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Bossbar) | ✔ | ✔ | ✔ | ✔ | ✔ |
|
||||
| [Global Playerlist](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Global-playerlist) | ❌ | ❌ | ❌ | ✔ | ✔ |
|
||||
| [Header/Footer](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Header-&-Footer) | ✔ | ✔ | ✔ | ✔ | ✔ |
|
||||
| [Layout](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Layout) | ✔ | ✔ | ✔ | ✔ | ✔ |
|
||||
| [Nametags](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Nametags) & [Sorting](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Sorting-players-in-tablist) | ✔ | ❗ | ✔ | ✔ | ✔ (via [VSAPI](https://github.com/NEZNAMY/VelocityScoreboardAPI/releases)) |
|
||||
| [Per world playerlist](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Per-world-playerlist) | ✔ | ❌ | ❌ | ❌ | ❌ |
|
||||
| [Playerlist objective](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Playerlist-Objective) | ✔ | ✔ | ✔ | ✔ | ✔ (via [VSAPI](https://github.com/NEZNAMY/VelocityScoreboardAPI/releases)) |
|
||||
| [Scoreboard](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Scoreboard) | ✔ | ❗ | ✔ | ✔ | ✔ (via [VSAPI](https://github.com/NEZNAMY/VelocityScoreboardAPI/releases)) |
|
||||
| [Spectator fix](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Spectator-fix) | ✔ | ✔ | ✔ | ✔ | ✔ |
|
||||
| [Tablist names](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Tablist-name-formatting) | ✔ | ✔ | ✔ | ✔ | ✔ |
|
||||
| Feature \ Platform | Bukkit / Hybrid | Sponge | Fabric / Forge / NeoForge | BungeeCord | Velocity |
|
||||
|--------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------|--------|---------------------------|------------|----------------------------------------------------------------------------|
|
||||
| [Belowname](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Belowname) | ✔ | ✔ | ✔ | ✔ | ✔ (via [VSAPI](https://github.com/NEZNAMY/VelocityScoreboardAPI/releases)) |
|
||||
| [BossBar](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Bossbar) | ✔ | ✔ | ✔ | ✔ | ✔ |
|
||||
| [Global Playerlist](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Global-playerlist) | ✔ | ✔ | ✔ | ✔ | ✔ |
|
||||
| [Header/Footer](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Header-&-Footer) | ✔ | ✔ | ✔ | ✔ | ✔ |
|
||||
| [Layout](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Layout) | ✔ | ✔ | ✔ | ✔ | ✔ |
|
||||
| [Nametags](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Nametags) & [Sorting](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Sorting-players-in-tablist) | ✔ | ❗ | ✔ | ✔ | ✔ (via [VSAPI](https://github.com/NEZNAMY/VelocityScoreboardAPI/releases)) |
|
||||
| [Per world playerlist](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Per-world-playerlist) | ✔ | ❌ | ❌ | ❌ | ❌ |
|
||||
| [Playerlist objective](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Playerlist-Objective) | ✔ | ✔ | ✔ | ✔ | ✔ (via [VSAPI](https://github.com/NEZNAMY/VelocityScoreboardAPI/releases)) |
|
||||
| [Scoreboard](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Scoreboard) | ✔ | ❗ | ✔ | ✔ | ✔ (via [VSAPI](https://github.com/NEZNAMY/VelocityScoreboardAPI/releases)) |
|
||||
| [Spectator fix](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Spectator-fix) | ✔ | ✔ | ✔ | ✔ | ✔ |
|
||||
| [Tablist names](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Tablist-name-formatting) | ✔ | ✔ | ✔ | ✔ | ✔ |
|
||||
|
||||
✔ = Fully functional
|
||||
❗ = NameTags: Anti-override missing, Scoreboard: [compatibility with other plugins](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Scoreboard#compatibility-with-other-plugins) missing.
|
||||
@@ -112,7 +121,11 @@ Supporting PremiumVanish's vanishing levels instead of using a basic compatibili
|
||||
Communicating with other proxies to properly display visuals on players on another proxy.
|
||||
|
||||
## Fabric
|
||||
[**fabric-permissions-api**](https://github.com/lucko/fabric-permissions-api) - Supporting permission nodes instead of OP levels.
|
||||
[**fabric-permissions-api**](https://github.com/lucko/fabric-permissions-api) - Supporting permission nodes instead of OP levels.
|
||||
[**placeholder-api**](https://modrinth.com/mod/placeholder-api) - Displaying placeholders from that mod and offering TAB's placeholders into it.
|
||||
|
||||
## Forge / NeoForge
|
||||
[**LuckPerms**](https://github.com/LuckPerms/LuckPerms) - Permission checks with a string instead of using vanilla OP levels.
|
||||
|
||||
## Sponge
|
||||
*None*
|
||||
@@ -131,7 +144,7 @@ Sending scoreboard packets (scoreboard-teams, belowname-objective, playerlist-ob
|
||||
* **SkBee** (skript addon) sends empty scoreboard, causing TAB's to not show sometimes.
|
||||
* **Waterfall**'s `disable_tab_list_rewrite: true` **may** cause tablist to use offline UUIDs while TAB expects online uuids, causing various problems (most notably tablist formatting not working). Checking for this option is not an option either, because tablist rewrite might still be enabled despite being disabled (don't ask how, I have no idea). Set the option to `false` if you are experiencing issues.
|
||||
* **ViaVersion on proxy and TAB on backend** acts like a client-sided protocol hack, making it impossible for TAB to know player's real version and causing issues related to it, see [Per-version experience](https://github.com/NEZNAMY/TAB/wiki/Additional-information#per-version-experience) for more info. Avoid this combination. Either install ViaVersion on all backend servers instead or install TAB on the proxy as well.
|
||||
* **ViaVersion**'s `hide-scoreboard-numbers` and **Nexo**'s `hide_scoreboard_numbers` config options (may apply to ItemsAdder and Oraxen as well) make [Belowname](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Belowname)'s `value` and [Playerlist objective](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Playerlist-Objective)'s `value` not visible for 1.20.3+ clients on <1.20.3 server when enabled. When using this setup, you'll need to keep the option disabled (sadly scoreboard numbers will not be hidden, solving this would require an implementation on ViaVersion's side).
|
||||
* **Nexo**'s `hide_scoreboard_numbers` config option (may apply to ItemsAdder and Oraxen as well) makes [Belowname](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Belowname)'s `value` and [Playerlist objective](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Playerlist-Objective)'s `value` not visible. When using these features, you'll need to keep the option disabled.
|
||||
* **Custom clients / resource packs** - Unofficially modified minecraft clients often tend to break things. Just Lunar client has tons of bugs that can be reproduced with TAB. Resource packs may also contain modifications you are not aware of, making things not look the way you want them to. If you are experiencing any visual issue and are using a custom client or resource pack, try it with a clean vanilla client. If it works there, it's an issue with the client / resource pack and TAB cannot do anything about it.
|
||||
For example, here are a few bugs in LunarClient / FeatherClient that you may run into when using TAB:
|
||||
* They add their icon to players in tablist, but don't widen the entries. This results in player names overlapping with latency bar. You can avoid this by configuring some spaces in tabsuffix.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
# Page content
|
||||
* [About](#about)
|
||||
* [Adding the dependency](#adding-the-dependency)
|
||||
* [Getting started](#getting-started)
|
||||
* [Lifespan of API calls](#lifespan-of-api-calls)
|
||||
@@ -13,22 +14,31 @@
|
||||
* [Tablist name formatting](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Tablist-name-formatting#api)
|
||||
* [Placeholders](https://github.com/NEZNAMY/TAB/wiki/Placeholders#api)
|
||||
|
||||
# About
|
||||
This page explains how to import TAB's API into your project and together with other pages everything you can do with it.
|
||||
|
||||
Before starting please seriously reconsider if you really need this and cannot already achieve it using existing features (such as registering placeholders into PlaceholderAPI instead of TAB, using display conditions for scoreboards, ...). If not, consider making a request.
|
||||
|
||||
# Adding the dependency
|
||||
|
||||
For Maven users, you can add the API dependency like this:
|
||||
|
||||
```xml
|
||||
<repository>
|
||||
<id>jitpack.io</id>
|
||||
<url>https://jitpack.io</url>
|
||||
</repository>
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>jitpack.io</id>
|
||||
<url>https://jitpack.io</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.github.NEZNAMY</groupId>
|
||||
<artifactId>TAB-API</artifactId>
|
||||
<version>5.0.7</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.github.NEZNAMY</groupId>
|
||||
<artifactId>TAB-API</artifactId>
|
||||
<version>5.2.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
```
|
||||
|
||||
Or, for Gradle users, here are a few examples:
|
||||
@@ -40,7 +50,7 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly 'com.github.NEZNAMY:TAB-API:5.0.7'
|
||||
compileOnly 'com.github.NEZNAMY:TAB-API:5.2.1'
|
||||
}
|
||||
```
|
||||
|
||||
@@ -51,15 +61,18 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly("com.github.NEZNAMY", "TAB-API", "5.0.7")
|
||||
compileOnly("com.github.NEZNAMY", "TAB-API", "5.2.1")
|
||||
}
|
||||
```
|
||||
|
||||
**WARNING: DO NOT SHADE THE API IN TO YOUR JAR! DO NOT RELOCATE THE API FROM ITS ORIGINAL PACKAGE!**
|
||||
|
||||
If you relocate, the API that you are calling will also get relocated in your code, meaning that you will be trying to call the relocated API. If you shade the API in, your code could load that bundled API, causing class path conflicts.
|
||||
> [!WARNING]
|
||||
> DO NOT SHADE THE API IN TO YOUR JAR! DO NOT RELOCATE THE API FROM ITS ORIGINAL PACKAGE!**
|
||||
> If you relocate, the API that you are calling will also get relocated in your code, meaning that you will be trying to call the relocated API. If you shade the API in, your code could load that bundled API, causing class path conflicts.
|
||||
Ensure that you are using **`<scope>provided</scope>` for Maven**, or **`compileOnly` for Gradle**.
|
||||
|
||||
> [!WARNING]
|
||||
> If you are on Paper and your plugin is defined as a paper plugin (paper-plugin.yml), TAB's classes may not be visible to the class loader that loaded your plugin. To avoid this issue, do not use paper-plugin.yml in your plugin, just plugin.yml.
|
||||
|
||||
# Getting started
|
||||
First, you need to get an instance of the API.
|
||||
You can do this with `TabAPI.getInstance()`.
|
||||
|
||||
@@ -101,10 +101,12 @@ bossbar:
|
||||
`/tab bossbar announce <name> <time>`
|
||||
`name` is name of bossbar defined in config.yml under **bossbar** section, `time` is length of display time in seconds.
|
||||
|
||||
When using a bossbar announcement, you have a new placeholder available:
|
||||
`%countdown%` that shows remaining time of the announcement in seconds.
|
||||
You can further use this placeholder in progress, for example,
|
||||
using Math expansion from PlaceholderAPI, such as `%math_{tab_placeholder_countdown}/<total time>*100%`.
|
||||
When using a bossbar announcement, you have new placeholders available:
|
||||
* `%bossbar_announce_time_left_<bossbar>%` - Returns remaining time of the announcement in seconds.
|
||||
* `%bossbar_announce_time_total_<bossbar>%` - Returns total time of the announcement in seconds (the inputted number).
|
||||
|
||||
You can further use these placeholders in progress, for example,
|
||||
using Math expansion from PlaceholderAPI, such as `%math_{tab_placeholder_bossbar_announce_time_left_<bossbar>}/{tab_placeholder_bossbar_announce_time_total_<bossbar>}*100%`.
|
||||
|
||||
## Additional settings
|
||||
| Option name | Default value | Description |
|
||||
|
||||
@@ -25,21 +25,21 @@ They have 2 main uses in the plugin:
|
||||
|
||||
# Condition types
|
||||
## Number comparations
|
||||
| Operation | Description | Example |
|
||||
|-------------------|---------------------|---------|
|
||||
| `>=` | Greater than or equal to | `%ping%>=100` will pass if the player's ping is greater than or equal to `100` |
|
||||
| `>` | Greater than | `%ping%>100` will pass if the player's ping is greater than `100` |
|
||||
| `<=` | Less than or equal to | `%ping%<=100` will pass if the player's ping is less than or equal to `100` |
|
||||
| `<` | Less than | `%ping%<100` will pass if the player's ping is less than `100` |
|
||||
| Operation | Description | Example |
|
||||
|-----------|--------------------------|--------------------------------------------------------------------------------|
|
||||
| `>=` | Greater than or equal to | `%ping%>=100` will pass if the player's ping is greater than or equal to `100` |
|
||||
| `>` | Greater than | `%ping%>100` will pass if the player's ping is greater than `100` |
|
||||
| `<=` | Less than or equal to | `%ping%<=100` will pass if the player's ping is less than or equal to `100` |
|
||||
| `<` | Less than | `%ping%<100` will pass if the player's ping is less than `100` |
|
||||
|
||||
## Text operations
|
||||
| Operation | Description | Example |
|
||||
|-------------------|---------------------|---------|
|
||||
| `=` | Equal to | `%world%=world` will pass if player is in world `world` |
|
||||
| `!=` | Not equal to | `%world%!=world` will pass if player is in any world except `world` |
|
||||
| `<-` | Contains (left side for full text, right side text to contain) | `%world%<-lobby-` will pass if player is in any world that contains `lobby-` (such as `lobby-1` etc.) |
|
||||
| `\|-` | Starts with (left side for full text, right side text to start with) | `%world%\|-lobby-` will pass if player is in any world that starts with `lobby-` (such as `lobby-1` etc.) |
|
||||
| `-\|` | Ends with (left side for full text, right side text to end with) | `%world%-\|nether` will pass if player is in any world that ends with `nether` (such as `world_nether` etc.) |
|
||||
| Operation | Description | Example |
|
||||
|-----------|----------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------|
|
||||
| `=` | Equal to | `%world%=world` will pass if player is in world `world` |
|
||||
| `!=` | Not equal to | `%world%!=world` will pass if player is in any world except `world` |
|
||||
| `<-` | Contains (left side for full text, right side text to contain) | `%world%<-lobby-` will pass if player is in any world that contains `lobby-` (such as `lobby-1` etc.) |
|
||||
| `\|-` | Starts with (left side for full text, right side text to start with) | `%world%\|-lobby-` will pass if player is in any world that starts with `lobby-` (such as `lobby-1` etc.) |
|
||||
| `-\|` | Ends with (left side for full text, right side text to end with) | `%world%-\|nether` will pass if player is in any world that ends with `nether` (such as `world_nether` etc.) |
|
||||
|
||||
> [!NOTE]
|
||||
> For `=` and `!=` you can check for empty output of a placeholder using `%my_placeholder%=` and `%my_placeholder%!=`.
|
||||
@@ -51,9 +51,10 @@ condition must contain the altered output.
|
||||
To see the exact output of a placeholder including color codes, use `/tab parse <player> <placeholder>`.
|
||||
|
||||
## Permission
|
||||
| Operation | Description | Example |
|
||||
|-------------------|---------------------|---------|
|
||||
| `permission:<value>` | Permission requirement | `permission:my.permission` will pass if player has `my.permission` permission |
|
||||
| Operation | Description | Example |
|
||||
|-----------------------|---------------------------------|----------------------------------------------------------------------------------------------|
|
||||
| `permission:<value>` | Permission requirement | `permission:my.permission` will pass if player has `my.permission` permission |
|
||||
| `!permission:<value>` | Negative permission requirement | `!permission:my.permission` will pass if player **does not have** `my.permission` permission |
|
||||
|
||||
# Multiple condition requirements
|
||||
Each condition has a `conditions` parameter, which a list of conditions. If you define more than 1 condition, you must specify the condition type.
|
||||
|
||||
@@ -7,19 +7,19 @@
|
||||
* [Compatibility with vanish plugins](#compatibility-with-vanish-plugins)
|
||||
|
||||
# About
|
||||
This feature allows you to see players from other servers under a bungeecord network, instead of only seeing players on the same server.
|
||||
To use this feature, you need [TAB installed on bungeecord](https://github.com/NEZNAMY/TAB/wiki/Installation#bungeecord). This option is not available on bukkit and will not be added.
|
||||
This feature allows you to see players from other servers, instead of only seeing players on the same server.
|
||||
If TAB is installed on a backend server, you'll need to set up [Multi server support](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Multi-server-support) for proper linking of servers. On proxy installation, this is not required.
|
||||
|
||||
# Configuration
|
||||
| Option name | Default value | Description |
|
||||
|----------------------------------------|---------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| enabled | false | Enables / Disables the feature |
|
||||
| server-groups | *Map* | See [Grouping players only from certain servers](#grouping-players-only-from-certain-servers) |
|
||||
| spy-servers | *List* | See [Seeing all players on the network from some server](#seeing-all-players-on-the-network-from-some-server) |
|
||||
| display-others-as-spectators | false | When enabled, players on different servers will appear as having spectator gamemode in tablist. |
|
||||
| display-vanished-players-as-spectators | true | When enabled, vanished players will show as in spectator gamemode for those, who have permission to see vanished players (tab.seevanished) (others will obviously not see them at all). Vanished players will still see themselves in the gamemode they are actually in, since changing that would cause problems (client would think it's in spectator gamemode while it isn't). |
|
||||
| isolate-unlisted-servers | false | When enabled, servers not listed in any group will not share playerlist with any other server, instead of sharing it with other unlisted servers. |
|
||||
| update-latency | false | When **enabled**, plugin will send ping update to players on other servers, since backend servers only send ping updates of players on the same server. This is displayed as the green bar in tablist, unless altered by a mod / resource pack (?). <br /> <br />When **disabled**, ping of players on other servers will be set to 0 (5 bars). <br /> <br />Disabled by default, because it's not only not very useful, but also has a very high CPU usage to due spamming a large amount of packets (especially on large networks). |
|
||||
| Option name | Default value | Description |
|
||||
|----------------------------------------|---------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| enabled | false | Enables / Disables the feature |
|
||||
| server-groups | *Map* | See [Grouping players only from certain servers](#grouping-players-only-from-certain-servers) |
|
||||
| spy-servers | *List* | See [Seeing all players on the network from some server](#seeing-all-players-on-the-network-from-some-server) |
|
||||
| display-others-as-spectators | false | When enabled, players on different servers will appear as having spectator gamemode in tablist. |
|
||||
| display-vanished-players-as-spectators | true | When enabled, vanished players will show as in spectator gamemode for those, who have permission to see vanished players (tab.seevanished) (others will obviously not see them at all). Vanished players will still see themselves in the gamemode they are actually in, since changing that would cause problems (client would think it's in spectator gamemode while it isn't). |
|
||||
| isolate-unlisted-servers | false | When enabled, servers not listed in any group will not share playerlist with any other server, instead of sharing it with other unlisted servers. |
|
||||
| update-latency | false | When **enabled**, plugin will send ping update to players on other servers, since backend servers only send ping updates of players on the same server. This is displayed as the green bar in tablist, unless altered by a mod / resource pack (?). <br /> <br />When **disabled**, ping of players on other servers will be set to `0` (). <br /> <br />Disabled by default, because it's not only not very useful, but also has a very high CPU usage to due spamming a large amount of packets (especially on large networks). |
|
||||
|
||||
## Grouping players only from certain servers
|
||||
In case you want multiple servers to share playerlist,
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
* [Example 1 - Per-server columns](#example-1---per-server-columns)
|
||||
|
||||
# About
|
||||
This feature allows you to customize all 80 tablist slots. Displaying less than 4 columns is currently not supported. This feature can be enabled and configured in **config.yml** file under **layout** section.
|
||||
This feature allows you to customize all 80 tablist slots. Displaying less than 4 columns is currently not supported ([here's why](https://gist.github.com/NEZNAMY/3dfcbf7d44283735d3c18266a2851651)). This feature can be enabled and configured in **config.yml** file under **layout** section.
|
||||
|
||||
This feature is only available for versions **1.8** and up due to massive tablist changes, which would make 1.7- compatibility require a complete rewrite of the functionality and could still cause all kinds of visual issues, including, but not limited to compatibility with other plugins adding/removing players from the tablist.
|
||||
|
||||
@@ -170,10 +170,6 @@ Layout feature is capable of working with all online players connected to the se
|
||||
From 1.19.3 until 1.21.1 (inclusive), entries will also appear in chat complete.
|
||||
The mechanic used to hide them on <1.19.3 can no longer be used since 1.19.3.
|
||||
1.21.2 has added a new way of sorting players, which is being taken advantage of to restore empty chat complete.
|
||||
> [!NOTE]
|
||||
> To hide entries from chat complete on 1.21.2+,
|
||||
the server also has to be 1.21.2+ so TAB can send the new content to the players
|
||||
(having TAB installed on proxy counts as using the latest version).
|
||||
|
||||
## Additional note 4 - Per world playerlist incompatibility
|
||||
Layout works by adding 80 fake players into the tablist, pushing real player entries out of view.
|
||||
@@ -241,7 +237,7 @@ as a result, you won't see the second layers of skins.
|
||||
This can only be fixed by actually letting the real entries display in the tablist,
|
||||
which would require a rework of the feature, which is not planned.
|
||||
**1.21.4 has added a new tablist option called "show hats", which layout sets to true.
|
||||
They will be visible for 1.21.4+ players on 1.21.4+ servers (using a proxy counts as being latest).**
|
||||
They will be visible for 1.21.4+ players.**
|
||||
|
||||
# Examples
|
||||
## Example 1 - Per-server columns
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
# Enabling
|
||||
Find this section in your config:
|
||||
```
|
||||
proxy-support:
|
||||
enabled: true
|
||||
# Supported types: PLUGIN, REDIS, RABBITMQ
|
||||
type: PLUGIN
|
||||
plugin:
|
||||
# Compatible plugins: RedisBungee
|
||||
# If enabled and compatible plugin is found, hook is enabled to work with proxied players
|
||||
name: RedisBungee
|
||||
redis:
|
||||
url: 'redis://:password@localhost:6379/0'
|
||||
rabbitmq:
|
||||
exchange: 'plugin'
|
||||
url: 'amqp://guest:guest@localhost:5672/%2F'
|
||||
```
|
||||
Pick your desired way and configure it. I don't know anything about redis or rabbitmq, so good luck.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **BACKEND INSTALLATION ONLY**
|
||||
> You should also add this into your config and configure it:
|
||||
> ```
|
||||
> server-name: "server"
|
||||
> ```
|
||||
> Ideally, name it the same as servers in your BungeeCord / Velocity config. This value will be used for getting player's server name. Aside from %server% placeholder, it will be used for internal logic when checking for player's server. One of them is global playerlist not adding/removing players who are on the same server in the tablist, as backend servers already do this (and duplicating actions would cause problems).
|
||||
> **As a result, not configuring this value will result in everyone's server being "N/A", therefore global playerlist will think everyone is on the same server and not add anyone into the tablist, effectively making the feature not work.**
|
||||
|
||||
Supported plugins:
|
||||
* [RedisBungee](https://github.com/ProxioDev/RedisBungee)
|
||||
|
||||
# Supported features
|
||||
* [Belowname](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Belowname)
|
||||
* [Global playerlist](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Global-playerlist)
|
||||
* [Nametags](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Nametags) & [Sorting](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Sorting-players-in-tablist)
|
||||
* [Tablist name formatting](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Tablist-name-formatting)
|
||||
* [Playerlist Objective](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Playerlist-Objective)
|
||||
|
||||
The hook does not and will never support [Layout](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Layout), which would require drastic changes to the feature itself and wouldn't be worth it, since the amount of visible players is usually even lower due to fixed slots and RedisBungee is used on networks with many players, so there will be enough players to work with anyway.
|
||||
@@ -157,12 +157,9 @@ To avoid it, make their names not match any online player and use holograms to d
|
||||
Since 1.9 teams affect tamed animals as well, displaying prefix/suffix on them as well and if nametags are set to be invisible, they will be completely invisible as well. If you want to avoid it, install [this plugin](https://www.spigotmc.org/resources/109466/).
|
||||
|
||||
## Additional note 3 - Changing name itself
|
||||
Teams do not allow to change the nametag name itself.
|
||||
Changing name is a complicated process which is slightly out of scope of the plugin as well,
|
||||
this is what nick plugins are about.
|
||||
On top of that, it's a pain
|
||||
requiring a ton of different packets which change their structure every version and with how many versions the plugin supports,
|
||||
it doesn't sound like fun doing.
|
||||
Teams do not allow to change the nametag name itself. The only way to achieve this goal is to send & modify packets to replace the player's actual profile name with a fake one. This is very complicated and can have a lot of side effects. This is out of scope of TAB and is in fact something nick plugins are trying to achieve. As such, **TAB does not offer this**.
|
||||
|
||||
Once you find a nick plugin that supports this, you will need to [make sure TAB properly detects this](https://github.com/NEZNAMY/TAB/wiki/How-to-display-name-from-nickname-plugins#proper-compatibility-with-plugins-that-change-profile-name) to make name-bound features (such as nametags) work properly for nicked players.
|
||||
|
||||
## Additional note 4 - F1 view
|
||||
Using teams causes player nametags to remain visible when using F1 view.
|
||||
|
||||
@@ -18,12 +18,14 @@ Since 1.20.3, it can display any text.
|
||||
This feature can be configured in **config.yml** under **playerlist-objective** section.
|
||||
|
||||
# Configuration
|
||||
| Option name | Default value | Description |
|
||||
|-------------------|-----------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| enabled | true | Enables / Disables the feature |
|
||||
| value | "%ping%" | [1.20.2-] Defines value displayed. You can set this to any placeholder that outputs a number. Setting value to `%health%`, `%player_health%` or `%player_health_rounded%` will result in the display type being changed to hearts instead of number. If you want to display health but as a number and not hearts, set value to `0%health%` for example. Only visible on 1.20.2 and lower. <br/> **Note**: Even if you only support 1.20.3+, you still need to configure this value to properly evaluate to a number, because the value is still sent to the client (just not displayed). You can set it to `0` for simplicity. |
|
||||
| fancy-value | "&7Ping: %ping%" | [1.20.3+] Value to display for 1.20.3+ clients. Supports any text. |
|
||||
| disable-condition | %world%=disabledworld | A [condition](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Conditional-placeholders) that must be met for disabling the feature for players. Set to empty for not disabling the feature ever. |
|
||||
| Option name | Default value | Description |
|
||||
|-------------------|-----------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| enabled | true | Enables / Disables the feature |
|
||||
| value | "%ping%" | [1.20.2-] Defines value displayed. You can set this to any placeholder that outputs a number. Only visible on 1.20.2 and lower. <br/> **Note**: Even if you only support 1.20.3+, you still need to configure this value to properly evaluate to a number, because the value is still sent to the client (just not displayed). You can set it to `0` for simplicity. |
|
||||
| fancy-value | "&7Ping: %ping%" | [1.20.3+] Value to display for 1.20.3+ clients. Supports any text. |
|
||||
| title | "TAB" | Title to send. Only visible on Bedrock Edition. |
|
||||
| render-type | INTEGER | Render type of the value. Supported values are `INTEGER` and `HEARTS` |
|
||||
| disable-condition | %world%=disabledworld | A [condition](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Conditional-placeholders) that must be met for disabling the feature for players. Set to empty for not disabling the feature ever. |
|
||||
|
||||
# Limitations
|
||||
* [1.5 - 1.20.2] Only 2 display types are supported - yellow number and hearts.
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
* [Example 1 - Per-world scoreboards](#example-1---per-world-scoreboards)
|
||||
* [Example 2 - Periodical scoreboard switching](#example-2---periodical-scoreboard-switching)
|
||||
* [Example 3 - Per-version scoreboards](#example-3---per-version-scoreboards)
|
||||
* [Example 4 - Conditional lines](#example-4---conditional-lines)
|
||||
|
||||
# About
|
||||
Scoreboard objective with SIDEBAR display slot.
|
||||
@@ -51,10 +52,6 @@ Will give you the following:
|
||||

|
||||
|
||||
The right side text will not be visible at 1.20.2 and lower, and numbers will be displayed instead.
|
||||
To properly see the right side, not only the client has to be 1.20.3+, but the server as well,
|
||||
since the plugin cannot send a packet that does not exist (on older versions).
|
||||
Proxies always include new content and therefore count as the latest version.
|
||||
This can be used to send new content to new players while having an outdated server version.
|
||||
|
||||
### Alignment
|
||||
The left side of the text is automatically aligned to the left,
|
||||
@@ -113,15 +110,16 @@ time - number of seconds to display the scoreboard for
|
||||
| toggle-command | /sb | Command that can be used to toggle scoreboard for the player running the command. Players need `tab.scoreboard.toggle` permission to use it. <br />**Note:** This command will not appear in command auto-complete, because it's not a real registered command. Registered commands must be defined in plugin jar directly and cannot be dynamic. If you want to solve this, you can try getting a dummy private plugin made which registers that command. |
|
||||
| remember-toggle-choice | false | When enabled, toggling decision is saved into a file to remember it even after reloads/restarts/reconnects |
|
||||
| hidden-by-default | false | If enabled, scoreboard will automatically be hidden on join until toggle command is used to show it. |
|
||||
| use-numbers | false | If enabled, numbers 1-15 will be used in the scoreboard. If disabled, `static-number` is shown everywhere. |
|
||||
| use-numbers | false | If enabled, numbers 1-15 will be used in the scoreboard. If disabled, `static-number` is shown everywhere. Will not be visible for 1.20.3+ players, instead, you can configure any text to show using `\|\|text` in the lines (scroll up for more info). |
|
||||
| static-number | 0 | If `use-numbers` is disabled, this is number to be in all lines. |
|
||||
| delay-on-join-milliseconds | 0 | Delay in milliseconds to send scoreboard after joining. |
|
||||
|
||||
# Limitations
|
||||
* [1.5 - 1.12.2] The title is limited to 32 characters (including color codes).
|
||||
* [1.5 - 1.12.2] Line length is limited to 28 characters (including color codes) (68 on lines with static text / [Longer lines](#longer-lines)).
|
||||
* [1.5 - 1.12.2] Line length is limited to 28 characters (including color codes) (64 on lines with static text / [Longer lines](#longer-lines)).
|
||||
* [1.5 - 1.20.2] The red numbers on the right really cannot be removed from the plugin side (a client modification / resource pack is necessary).
|
||||
* The client only displays up to 15 lines. If a plugin sends more, only the top 15 scores will be displayed. Changing this would require a client modification.
|
||||
* The client only displays up to 15 lines. If a plugin sends more, only the top 15 scores will be displayed. Changing this would require a client modification (on versions below 1.8 the scoreboard won't appear at all if more than 15 lines are displayed).
|
||||
* [Bedrock] Scoreboard lines may be cut off and show `...`, this can be bypassed by using [GeyserOptionalPack](https://github.com/GeyserMC/GeyserOptionalPack)
|
||||
|
||||
# Longer lines
|
||||
To make sure the scoreboard never flickers, it's only using prefix/suffix components to display text.
|
||||
@@ -285,7 +283,7 @@ We don't need to define display condition for the second scoreboard,
|
||||
since if animation is not on the first frame, it has to be on the second one.
|
||||
If using more than 2, you will need to define display condition for all of them (except the last one).
|
||||
|
||||
# Example 3 - Per-version scoreboards
|
||||
## Example 3 - Per-version scoreboards
|
||||
1.20.3 has added new features to scoreboards, which are not visible to older clients. If you want to use new features for new players while still displaying things properly for older players, you'll need to create 2 different scoreboards.
|
||||
You'll find 2 placeholders useful for this:
|
||||
* `%player-version-id%` - Returns network ID of player's protocol version. Unlike with version names, you can perform numerical comparisons with these. You can find all version IDs [here](https://github.com/NEZNAMY/TAB/blob/master/shared/src/main/java/me/neznamy/tab/shared/ProtocolVersion.java). For example, 1.20.3 is 765.
|
||||
@@ -315,4 +313,29 @@ Let's go ahead and set it up (with proper spacing):
|
||||
- "* &bRank&7: &f%group%"
|
||||
- "* &bPing&7: &f%ping%&8ms"
|
||||
- "* &bWorld&7: &f%world%"
|
||||
```
|
||||
```
|
||||
|
||||
## Example 4 - Conditional lines
|
||||
Sometimes you may want a dynamic line that only shows sometimes. TAB is already made to hide lines if they only consist of a placeholder that returned empty value. However, you may want to add more text around it, such as
|
||||
```
|
||||
- "Faction: %factionsuuid_faction_name%"
|
||||
```
|
||||
Now, even if the placeholder returns empty string, text `Faction: ` will remain, and as such, the line will stay. In order to avoid this, create a [conditional placeholder](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Conditional-placeholders). If the placeholder returned empty value, show empty value. Otherwise, show result of the placeholder with the static text:
|
||||
```
|
||||
conditions:
|
||||
faction:
|
||||
conditions:
|
||||
- '%factionsuuid_faction_name%='
|
||||
yes: "" # No faction, return empty string to hide the line
|
||||
no: "Faction: %factionsuuid_faction_name%" # Player has a faction, show the static text before it as well
|
||||
```
|
||||
> [!WARNING]
|
||||
> DO NOT JUST RANDOMLY PASTE THIS ENTIRE "CONDITIONS" SECTION INTO YOUR CONFIG!
|
||||
> INSTEAD, EDIT YOUR EXISTING CONDITIONS SECTION TO PREVENT HAVING THE SECTION TWICE,
|
||||
> HAVING SECOND ONE COMPLETELY OVERRIDE THE FIRST ONE!
|
||||
|
||||
Now, configure your scoreboard line as:
|
||||
```
|
||||
- "%condition:faction%"
|
||||
```
|
||||
and it will only display if the placeholder returned non-empty value.
|
||||
@@ -1,13 +0,0 @@
|
||||
When a plugin for linking proxies is detected, TAB automatically enables its hook to add support for it.
|
||||
No configuration is necessary.
|
||||
Supported redis plugins:
|
||||
* [RedisBungee](https://github.com/ProxioDev/RedisBungee)
|
||||
|
||||
Supported features:
|
||||
* [Belowname](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Belowname)
|
||||
* [Global playerlist](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Global-playerlist)
|
||||
* [Nametags](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Nametags) & [Sorting](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Sorting-players-in-tablist)
|
||||
* [Tablist name formatting](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Tablist-name-formatting)
|
||||
* [Playerlist Objective](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Playerlist-Objective)
|
||||
|
||||
The hook does not and will never support [Layout](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Layout), which would require drastic changes to the feature itself and wouldn't be worth it, since the amount of visible players is usually even lower due to fixed slots and RedisBungee is used on networks with many players, so there will be enough players to work with anyway.
|
||||
@@ -2,8 +2,8 @@
|
||||
A collection of things people have requested that I might look into implementing when I get extremely bored sometime next decade.
|
||||
* Less than 4 columns in Layout
|
||||
* `invisible-nametags` supporting conditions like `enable-collision` does
|
||||
* Using ViaVersion API to send 1.20.3+ scoreboard content on <1.20.3 server.
|
||||
* Relational placeholder support in sorting
|
||||
* Conditional header/footer
|
||||
* Relational conditions for layout player groups
|
||||
* Improve bossbar announce placeholder(s)
|
||||
* 1.21.2+ sorting support
|
||||
* disable-condition support in multi-server-support
|
||||
@@ -24,7 +24,7 @@ See [Citizens FAQ](https://wiki.citizensnpcs.co/Frequently_Asked_Questions#Why_a
|
||||
## #2 - How to make TAB work with LuckPerms?
|
||||
* If you want to take prefixes/suffixes, check [this guide](https://github.com/NEZNAMY/TAB/wiki/Mini-guides-collection#taking-prefixessuffixes-from-permission-plugin) and use `%luckperms-prefix%` & `%luckperms-suffix%`.
|
||||
* Sorting:
|
||||
First, [Configure weights correctly in LuckPerms](https://github.com/NEZNAMY/TAB/wiki/How-to-setup-weights-priorities#luckperms). Then, you have 2 options:
|
||||
First, configure weights correctly in LuckPerms. Then, you have 2 options:
|
||||
* Configure [sorting by groups](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Sorting-players-in-tablist#groups) (recommended).
|
||||
* Sorting by weights directly: `PLACEHOLDER_HIGH_TO_LOW:%luckperms_highest_group_weight%`.
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# About the wiki
|
||||
The wiki explains the functionality of the plugin as of version **5.0.7**.
|
||||
The wiki explains the functionality of the plugin as of version **5.2.1**.
|
||||
If your config looks different or is missing some features, you are using an old version of the plugin.
|
||||
To get the latest version, check [releases](https://github.com/NEZNAMY/TAB/releases/).
|
||||
Wiki for older plugin versions is not available.
|
||||
|
||||
@@ -42,7 +42,13 @@ Most notably, it breaks all name-bound features,
|
||||
especially [nametags](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Nametags) and [sorting](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Sorting-players-in-tablist).
|
||||
For that reason, TAB must detect name change.
|
||||
This is done automatically on Bukkit, BungeeCord and Fabric.
|
||||
This detection is not available on Sponge and Velocity.
|
||||
**This detection is not available on Sponge and Velocity**.
|
||||
|
||||
> [!WARNING]
|
||||
> If you have TAB installed on backend server and a proxy plugin changes nickname, TAB is not able to detect this change, since the modified packets never actually go through the backend server where TAB is installed.
|
||||
|
||||
> [!TIP]
|
||||
> If automatic detection is not working for you (either you use TAB on Sponge / Velocity, or use proxy plugin for changing names while TAB is on backend), you can use [this API method](https://github.com/NEZNAMY/TAB/blob/master/api/src/main/java/me/neznamy/tab/api/TabPlayer.java#L88) to let TAB know player's name changed.
|
||||
|
||||
In order for TAB to properly detect name change, player UUID must remain the same,
|
||||
otherwise it's a random entry with a random uuid and random name, which TAB isn't able to match with actual players.
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
* [Notepad++](#notepad-1)
|
||||
* [Sublime Text 3](#sublime-text-3)
|
||||
* [WinSCP](#winscp)
|
||||
* [VSCode](#vscode)
|
||||
***
|
||||
|
||||
# Notepad
|
||||
@@ -25,4 +26,9 @@ Open the file > Click encoding > Click UTF-8 (if that didn't work, use Convert t
|
||||
# WinSCP
|
||||
Open the file in WinSCP and check if it is in UTF-8 encoding already (1). If it is not, click on ENCODING at the top (2), select UTF-8 (3) and then save the file. You can now edit the file as you like and paste in characters.
|
||||

|
||||

|
||||

|
||||
|
||||
# VSCode
|
||||
At the bottom right of VSCode, you can change the current file's encoding. It'll open a pop-up where you can select UTF-8
|
||||

|
||||

|
||||
@@ -7,6 +7,8 @@
|
||||
* [Fonts](#fonts)
|
||||
* [Usage](#usage-1)
|
||||
* [Compatibility with RGB and gradients](#compatibility-with-rgb-and-gradients)
|
||||
* [Tips & Tricks](#tips--tricks)
|
||||
* [Tip 1 - Manually defining legacy color](#tip-1---manually-defining-legacy-color)
|
||||
|
||||
# Introduction
|
||||
As of 1.16, mojang introduced RGB color support.
|
||||
@@ -36,7 +38,7 @@ You can use a tool like [this one](https://htmlcolorcodes.com/) to easily find s
|
||||
## Magic codes
|
||||
Just like with legacy colors, use color first, then magic codes (in any order). For example with **bold**: `#00FFFF&lText`.
|
||||
|
||||
For gradients, put the magic codes at the beginning of text. For example `<#00FF00>&lBold gradient text</#FF00FF>`.
|
||||
For gradients, put the magic codes inside the text. Example: `<#00FF00>&lBold gradient text</#FF00FF>`.
|
||||
|
||||
# Invalid code handling
|
||||
In case you accidentally use invalid hex color code, the plugin will simply ignore it and keep it in raw format.
|
||||
@@ -70,3 +72,21 @@ When trying to use both font and RGB or gradients, font must be outside and RGB/
|
||||
|
||||
**Result**
|
||||

|
||||
|
||||
# Tips & Tricks
|
||||
## Tip 1 - Manually defining legacy color
|
||||
For features that don't depend on any other players (header/footer, bossbar, scoreboard), you can easily specify legacy color instead of automatic rounding for <1.16 players. This can be done using [conditional placeholders](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Conditional-placeholders).
|
||||
|
||||
Check for `%player-version-id%`, which returns network protocol id of the version, which can be easily compared to. 1.16's version is `735`. Final condition may look like this:
|
||||
|
||||
```
|
||||
conditions:
|
||||
rgb:
|
||||
conditions:
|
||||
- '%player-version-id%>=735'
|
||||
yes: "#00FF00 RGB text"
|
||||
no: "&a Legacy text"
|
||||
```
|
||||
Then, use this condition with `%condition:rgb%`.
|
||||
|
||||
This will not work in nametags and tablist formatting, because this would parse the condition for target player, not the viewer. To achieve this, you'll need a custom-made [relational placeholder](https://github.com/NEZNAMY/TAB/wiki/Placeholders#relational-placeholders) that checks for viewer's version.
|
||||
@@ -33,8 +33,7 @@ install [VelocityScoreboardAPI](https://github.com/NEZNAMY/VelocityScoreboardAPI
|
||||
> With proxy installation, use **/btab** command instead of **/tab**.
|
||||
|
||||
Advantages of proxy installation compared to installing on all backend servers instead:
|
||||
* Can use the [Global player list](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Global-playerlist) and [Layout](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Layout) can work with all players connected to the proxy
|
||||
* Can use new content for new clients even if a backend server version is old (1.16+ RGB codes, 1.20.3+ scoreboard features)
|
||||
* Can use the [Global player list](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Global-playerlist) without requiring Redis set up and [Layout](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Layout) can work with all players connected to the proxy
|
||||
* Avoid [header/footer](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Header-&-Footer#additional-note-1---not-resetting-on-server-switch) and [bossbar](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Bossbar#additional-note-2---not-hiding-on-server-switch) not disappearing on server switch
|
||||
* Configuration files in a single place for easier editing / no need to use MySQL for syncing data
|
||||
|
||||
|
||||
@@ -6,12 +6,10 @@ The usage can be checked at any time using [**/tab cpu**](https://github.com/NEZ
|
||||
* [#2 - [All platforms] Placeholder refresh intervals](#2---all-platforms-placeholder-refresh-intervals)
|
||||
* [#3 - [All platforms] Don't use RGB](#3---all-platforms-dont-use-rgb)
|
||||
* [#4 - [All platforms] Don't use animations](#4---all-platforms-dont-use-animations)
|
||||
* [#5 - [All platforms] Disable anti-override](#5---all-platforms-disable-anti-override)
|
||||
* [#6 - [All platforms] Disable tablist name formatting](#6---all-platforms-disable-tablist-name-formatting)
|
||||
* [#7 - [All platforms] Increase permission refresh interval](#7---all-platforms-increase-permission-refresh-interval)
|
||||
* [#8 - [BungeeCord / Velocity] Disable TAB expansion](#8---bungeecord--velocity-disable-tab-expansion)
|
||||
* [#9 - [BungeeCord / Velocity] Disable update-latency option in global playerlist](#9---bungeecord--velocity-disable-update-latency-option-in-global-playerlist)
|
||||
* [#10 - [BungeeCord] Disable ByteBuf deserialization](#10---bungeecord-disable-bytebuf-deserialization)
|
||||
* [#5 - [All platforms] Disable tablist name formatting](#5---all-platforms-disable-tablist-name-formatting)
|
||||
* [#6 - [All platforms] Increase permission refresh interval](#6---all-platforms-increase-permission-refresh-interval)
|
||||
* [#7 - [BungeeCord / Velocity] Disable TAB expansion](#7---bungeecord--velocity-disable-tab-expansion)
|
||||
* [#8 - [BungeeCord / Velocity] Disable update-latency option in global playerlist](#8---bungeecord--velocity-disable-update-latency-option-in-global-playerlist)
|
||||
|
||||
# #1 - [All platforms] Static text instead of placeholders
|
||||
Static text doesn't need to be refreshed periodically, resulting in better performance.
|
||||
@@ -23,11 +21,11 @@ instead of using placeholders to take those values from your permission plugin.
|
||||
# #2 - [All platforms] Placeholder refresh intervals
|
||||
Some placeholders take a long time to process and result in high cpu usage. However, this can be optimized by configuring refresh interval of placeholders.
|
||||
|
||||
This only works for PlaceholderAPI placeholders. Internal ones have an interval hardcoded already with values I found the most appropriate.
|
||||
Internal placeholders use a value hardcoded into the plugin by default. PlaceholderAPI placeholders use `default-refresh-interval`.
|
||||
|
||||
To begin, find this part in your config
|
||||
```yml
|
||||
placeholderapi-refresh-intervals:
|
||||
placeholder-refresh-intervals:
|
||||
default-refresh-interval: 500
|
||||
"%server_uptime%": 1000
|
||||
"%server_tps_1_colored%": 1000
|
||||
@@ -56,27 +54,7 @@ This is because they work by spamming updates every time an animation switches t
|
||||
When animations are not used, no updates need to be sent (unless another placeholder changes value),
|
||||
not causing extra CPU usage.
|
||||
|
||||
# #5 - [All platforms] Disable anti-override
|
||||
By default, TAB prevents other plugins from overriding it.
|
||||
This is because many server owners are not competent enough to configure their plugins correctly,
|
||||
such as disabling CMI collision handling which breaks nametags or even disabling team handling in paper.
|
||||
This obviously causes higher CPU usage.
|
||||
TAB logs all override attempts of teams into [anti-override.log](https://github.com/NEZNAMY/TAB/wiki/Action-logging#anti-overridelog) file.
|
||||
If you didn't get the file, it looks like you configured your plugins correctly.
|
||||
In that case, you can disable anti-override for teams in TAB.
|
||||
```
|
||||
scoreboard-teams:
|
||||
anti-override: false
|
||||
```
|
||||
Tablist formatting is trickier, as override attempts are not logged because it would have a lot of false positives.
|
||||
You will need to try and see.
|
||||
|
||||
```
|
||||
tablist-name-formatting:
|
||||
anti-override: false
|
||||
```
|
||||
|
||||
# #6 - [All platforms] Disable tablist name formatting
|
||||
# #5 - [All platforms] Disable tablist name formatting
|
||||
As we already know from [Client-sided mechanics](https://github.com/NEZNAMY/TAB/wiki/Client%E2%80%90sided-mechanics#nametag-format-in-tablist) page,
|
||||
the nametag format appears in tablist if tablist name is not defined.
|
||||
You can take advantage of this to effectively disable one entire feature and its cpu usage,
|
||||
@@ -87,12 +65,11 @@ However, you can only do this if:
|
||||
|
||||
Whether you have another plugin attempting to handle them or not may not be easy to identify with a lot of plugins and not enough time spent configuring them. In that case, your best choice is to try it and see. If tablist names break, it means you have another plugin and cannot take advantage of this optimization option.
|
||||
|
||||
# #7 - [All platforms] Increase permission refresh interval
|
||||
# #6 - [All platforms] Increase permission refresh interval
|
||||
Plugin needs to constantly check permissions for:
|
||||
* Permission checks in conditions
|
||||
* Permission checks if sorting by permissions
|
||||
* Group retrieving from permission plugin (for sorting / per-group properties)
|
||||
* Prefix/suffix placeholders taking data from your permission plugin
|
||||
|
||||
default value
|
||||
```
|
||||
@@ -102,7 +79,7 @@ Refreshes them every second.
|
||||
If you don't need any permission/group changes to take effect within a second,
|
||||
you can increase this value for better performance.
|
||||
|
||||
# #8 - [BungeeCord / Velocity] Disable TAB expansion
|
||||
# #7 - [BungeeCord / Velocity] Disable TAB expansion
|
||||
To maximize the performance of TAB's response to PlaceholderAPI request,
|
||||
values are tracked in advance and then quickly returned.
|
||||
This process takes resources and is especially heavy on proxy installation,
|
||||
@@ -113,24 +90,5 @@ Disabling TAB's PlaceholderAPI expansion if you don't use it improves performanc
|
||||
placeholders:
|
||||
register-tab-expansion: false
|
||||
```
|
||||
# #9 - [BungeeCord / Velocity] Disable update-latency option in global playerlist
|
||||
When [Global playerlist](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Global-playerlist) is enabled, you can enable / disable [update-latency](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Global-playerlist#:~:text=other%20unlisted%20servers.-,update%2Dlatency,-false) option (open the link for detailed description of the option). Disabling this option massively improves performance.
|
||||
|
||||
# #10 - [BungeeCord] Disable ByteBuf deserialization
|
||||
In order for anti-override to work properly, some packets must be manually deserialized due to BungeeCord not doing it. If you don't need it, you can disable those functions, which will also disable the deserialization completely. To do so, you must disable 2 functions (only having 1 disabled is not enough):
|
||||
#1 - Anti-override for teams
|
||||
```
|
||||
scoreboard-teams:
|
||||
anti-override: false
|
||||
```
|
||||
Whether you need anti-override or not depends on the configuration of your plugins.
|
||||
The easiest way is to have it enabled and see if you get `anti-override.log` file in TAB folder.
|
||||
If you don't, you don't need it.
|
||||
If you do, you'll need to do some configuring.
|
||||
[Here](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Sorting-players-in-tablist#additional-note-3---compatibility-issues-with-other-plugins)
|
||||
are a few examples of software and their team names, which you may find helpful.
|
||||
|
||||
#2 - Scoreboard detection of other plugins
|
||||
TAB checks for packets sent by other plugins, and if another plugin sends a scoreboard,
|
||||
TAB hides its own and resends it after the other plugin removes it.
|
||||
This check cannot be disabled if the scoreboard is enabled.
|
||||
# #8 - [BungeeCord / Velocity] Disable update-latency option in global playerlist
|
||||
When [Global playerlist](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Global-playerlist) is enabled, you can enable / disable [update-latency](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Global-playerlist#:~:text=other%20unlisted%20servers.-,update%2Dlatency,-false) option (open the link for detailed description of the option). Disabling this option massively improves performance.
|
||||
@@ -28,39 +28,41 @@ Basic placeholders provided by the plugin. Most of these placeholders were added
|
||||
|
||||
Placeholders must be refreshed.
|
||||
All internal placeholders have a manually defined refresh interval,
|
||||
which I found the most optimal and cannot be changed.
|
||||
which I found the most optimal. You can [override the refresh intervals](https://github.com/NEZNAMY/TAB/wiki/Optimizing-the-plugin#2---all-platforms-placeholder-refresh-intervals) if you want to.
|
||||
Time is in milliseconds.
|
||||
It is used for placeholders which are in fact constants (such as %player%),
|
||||
`-1` is used for placeholders which are in fact constants (such as %player%),
|
||||
or fire an event each time they change, which can be listened to (such as %world%) and value updated.
|
||||
Refresh interval for conditions is set to be equal to the fastest refreshing placeholder used inside each condition.
|
||||
|
||||
## Universal
|
||||
These placeholders are available on all platforms.
|
||||
|
||||
| Identifier | Refresh interval | Description |
|
||||
|---------------------|------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| %online% | 1000 | Total online player count on server where plugin is installed (excluding [vanished players](https://github.com/NEZNAMY/TAB/wiki/Additional-information#vanish-detection)). If the plugin is installed on a proxy, it will show total player count on the whole network. |
|
||||
| %world% | -1 | Name of world where player is |
|
||||
| %worldonline% | 1000 | Amount of players in the same world (excluding [vanished players](https://github.com/NEZNAMY/TAB/wiki/Additional-information#vanish-detection)) as the player |
|
||||
| %ping% | 500 | Player's ping to the server where TAB is installed. <br />**Note**: This value is measured by the server and TAB is only retrieving that value. If it's wrong, it's not a TAB issue. |
|
||||
| %player% | -1 | Player's name |
|
||||
| %time% | 500 | Current real time, output is configurable in **config.yml** in `time-format` option |
|
||||
| %date% | 60000 | Current date, output is configurable in **config.yml** in `date-format` option |
|
||||
| %staffonline% | 2000 | Amount of online players with `tab.staff` permission |
|
||||
| %nonstaffonline% | 2000 | Amount of online players without `tab.staff` permission |
|
||||
| %memory-used% | 200 | Used RAM of server in MB |
|
||||
| %memory-max% | -1 | Total RAM of server in MB |
|
||||
| %memory-used-gb% | 200 | Used RAM of server in GB |
|
||||
| %memory-max-gb% | -1 | Total RAM of server in GB |
|
||||
| %player-version% | -1 | User-friendly version of player, such as `1.14.4`. For versions that share the same protocol version number, the higher one is returned (for example 1.20 & 1.20.1 -> 1.20.1 is returned) |
|
||||
| %player-version-id% | -1 | Network ID of player's version. You can see the full version map on [fan wiki](https://wiki.vg/Protocol_version_numbers) for all versions or [TAB's source code](https://github.com/NEZNAMY/TAB/blob/master/shared/src/main/java/me/neznamy/tab/shared/ProtocolVersion.java) for releases only. Useful for conditions to create per-version results. |
|
||||
| %luckperms-prefix% | 1000 | Prefix from LuckPerms from the server where TAB is installed |
|
||||
| %luckperms-suffix% | 1000 | Suffix from LuckPerms from the server where TAB is installed |
|
||||
| %displayname% | 500 | Display name variable set by permission plugin, typically prefix + name + suffix |
|
||||
| %group% | 1000 | Returns player's primary group name. Created for internal functionality, but can be used as a display placeholder as well. |
|
||||
| %vanished% | 1000 | Returns `true`/`false` based on player's vanish status. Created for internal functionality, but can be used as a display placeholder as well. |
|
||||
| %bedrock% | -1 | Returns `true`/`false` based on whether player is using Bedrock edition or not (Java) |
|
||||
| %% | -1 | Returns the `%` symbol. Useful to display `%` symbol without breaking all placeholders after it |
|
||||
| Identifier | Refresh interval | Description |
|
||||
|-------------------------------------------|------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| %online% | 1000 | Total online player count on server where plugin is installed (excluding [vanished players](https://github.com/NEZNAMY/TAB/wiki/Additional-information#vanish-detection)). If the plugin is installed on a proxy, it will show total player count on the whole network. |
|
||||
| %world% | -1 | Name of world where player is |
|
||||
| %worldonline% | 1000 | Amount of players in the same world (excluding [vanished players](https://github.com/NEZNAMY/TAB/wiki/Additional-information#vanish-detection)) as the player |
|
||||
| %ping% | 500 | Player's ping to the server where TAB is installed. <br />**Note**: This value is measured by the server and TAB is only retrieving that value. If it's wrong, it's not a TAB issue. |
|
||||
| %player% | -1 | Player's name |
|
||||
| %time% | 500 | Current real time, output is configurable in **config.yml** in `time-format` option |
|
||||
| %date% | 60000 | Current date, output is configurable in **config.yml** in `date-format` option |
|
||||
| %staffonline% | 2000 | Amount of online players with `tab.staff` permission |
|
||||
| %nonstaffonline% | 2000 | Amount of online players without `tab.staff` permission |
|
||||
| %memory-used% | 200 | Used RAM of server in MB |
|
||||
| %memory-max% | -1 | Total RAM of server in MB |
|
||||
| %memory-used-gb% | 200 | Used RAM of server in GB |
|
||||
| %memory-max-gb% | -1 | Total RAM of server in GB |
|
||||
| %player-version% | -1 | User-friendly version of player, such as `1.14.4`. For versions that share the same protocol version number, the higher one is returned (for example 1.20 & 1.20.1 -> 1.20.1 is returned) |
|
||||
| %player-version-id% | -1 | Network ID of player's version. You can see the full version map on [fan wiki](https://wiki.vg/Protocol_version_numbers) for all versions or [TAB's source code](https://github.com/NEZNAMY/TAB/blob/master/shared/src/main/java/me/neznamy/tab/shared/ProtocolVersion.java) for releases only. Useful for conditions to create per-version results. |
|
||||
| %luckperms-prefix% | 1000 | Prefix from LuckPerms from the server where TAB is installed |
|
||||
| %luckperms-suffix% | 1000 | Suffix from LuckPerms from the server where TAB is installed |
|
||||
| %displayname% | 500 | Display name variable set by permission plugin, typically prefix + name + suffix |
|
||||
| %group% | 1000 | Returns player's primary group name. Created for internal functionality, but can be used as a display placeholder as well. |
|
||||
| %vanished% | 1000 | Returns `true`/`false` based on player's vanish status. Created for internal functionality, but can be used as a display placeholder as well. |
|
||||
| %bedrock% | -1 | Returns `true`/`false` based on whether player is using Bedrock edition or not (Java) |
|
||||
| %bossbar_announce_time_total_\<bossbar\>% | -1 | Total time (inputted value) of an announced bossbar. |
|
||||
| %bossbar_announce_time_left_\<bossbar\>% | 100 | Remaining time of an announced bossbar. |
|
||||
| %% | -1 | Returns the `%` symbol. Useful to display `%` symbol without breaking all placeholders after it |
|
||||
|
||||
## Backend only
|
||||
These placeholders only work when TAB is installed on a backend server (Bukkit / Sponge / Fabric), they don't work when TAB is on BungeeCord. If you have TAB installed on BungeeCord and wish to use these, use their [PlaceholderAPI](https://github.com/NEZNAMY/TAB/wiki/Quick-PlaceholderAPI-startup-guide) alternative with [Bridge plugin](https://github.com/NEZNAMY/TAB/wiki/TAB-Bridge) installed for PlaceholderAPI support on BungeeCord.
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
# About
|
||||
When TAB is installed on BungeeCord, the plugin has very limited access to information. It is unable to retrieve internal server data, as well as hook into backend plugins, because it's a different server.
|
||||
Bridge is a backend plugin that sends this data to the proxy, allowing TAB to work better when installed on BungeeCord.
|
||||
When TAB is installed on BungeeCord / Velocity, the plugin has very limited access to information. It is unable to retrieve internal server data, as well as hook into backend plugins, because it's a different server.
|
||||
Bridge is a backend plugin that sends this data to the proxy, allowing TAB to work better when installed on a proxy.
|
||||
You can download it from [GitHub](https://github.com/NEZNAMY/TAB-Bridge/releases) or [Spigot](https://www.spigotmc.org/resources/83966).
|
||||
|
||||
# Installation
|
||||
Installation is as simple as it can be.
|
||||
Put it into the plugins folder of your backend servers.
|
||||
Bridge is NOT a BungeeCord plugin!
|
||||
Bridge is NOT a proxy plugin!
|
||||
Bridge itself has no config file, as everything is configured in the main plugin.
|
||||
|
||||
# Features
|
||||
@@ -20,7 +20,7 @@ Bridge itself has no config file, as everything is configured in the main plugin
|
||||
* Forward player's gamemode for spectator fix feature to work
|
||||
|
||||
# Compatibility
|
||||
Just like with Minecraft, when there is a change in the communication protocol,
|
||||
Just like with Minecraft, when there is a change in the communication protocol between TAB <-> TAB-Bridge,
|
||||
older software will not be compatible with the new one.
|
||||
You should always be running the latest versions of both TAB and Bridge.
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ More information can be found at [Why TAB?](https://github.com/NEZNAMY/TAB/wiki/
|
||||
# Download
|
||||
### Releases
|
||||
You can download full releases from [GitHub releases](https://github.com/NEZNAMY/TAB/releases),
|
||||
[SpigotMC](https://www.spigotmc.org/resources/57806/), [Modrinth](https://modrinth.com/plugin/tab-was-taken) or [BuiltByBit](https://builtbybit.com/resources/20631).
|
||||
[SpigotMC](https://www.spigotmc.org/resources/57806/), [Modrinth](https://modrinth.com/plugin/tab-was-taken), [BuiltByBit](https://builtbybit.com/resources/20631) or [CurseForge](https://www.curseforge.com/minecraft/mc-mods/tab).
|
||||
|
||||
### Dev builds
|
||||
Plugin's development is continuous and contains lots of small changes/fixes not worthy of being called a full
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
package me.neznamy.tab.api.bossbar;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.api.TabAPI;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* An interface allowing work with BossBars such as creating, sending
|
||||
* and toggling.
|
||||
@@ -78,6 +78,28 @@ public interface BossBarManager {
|
||||
*/
|
||||
@NotNull Map<String, BossBar> getRegisteredBossBars();
|
||||
|
||||
/**
|
||||
* Removes BossBar with specified name from the manager, making it no longer available.
|
||||
* If BossBar was displayed to any players, it is removed.
|
||||
*
|
||||
* @param name
|
||||
* Name of registered BossBar to remove
|
||||
* @throws IllegalArgumentException
|
||||
* if no BossBar with specified name exists
|
||||
*/
|
||||
void removeBossBar(@NonNull String name);
|
||||
|
||||
/**
|
||||
* Removes BossBar from the manager, making it no longer available.
|
||||
* If BossBar was displayed to any players, it is removed.
|
||||
*
|
||||
* @param bossBar
|
||||
* BossBar to remove
|
||||
* @throws IllegalArgumentException
|
||||
* if specified BossBar is not registered
|
||||
*/
|
||||
void removeBossBar(@NonNull BossBar bossBar);
|
||||
|
||||
/**
|
||||
* Toggles BossBar for this player and sends toggle message if {@code sendToggleMessage} is true.
|
||||
*
|
||||
|
||||
@@ -134,7 +134,7 @@ public interface NameTagManager {
|
||||
* @param prefix
|
||||
* new prefix value
|
||||
* @see #getCustomPrefix(TabPlayer)
|
||||
* @see #getOriginalPrefix(TabPlayer)
|
||||
* @see #getOriginalRawPrefix(TabPlayer)
|
||||
*/
|
||||
void setPrefix(@NonNull TabPlayer player, @Nullable String prefix);
|
||||
|
||||
@@ -148,7 +148,7 @@ public interface NameTagManager {
|
||||
* @param suffix
|
||||
* new suffix value
|
||||
* @see #getCustomSuffix(TabPlayer)
|
||||
* @see #getOriginalSuffix(TabPlayer)
|
||||
* @see #getOriginalRawSuffix(TabPlayer)
|
||||
*/
|
||||
void setSuffix(@NonNull TabPlayer player, @Nullable String suffix);
|
||||
|
||||
@@ -160,7 +160,7 @@ public interface NameTagManager {
|
||||
* Player to get custom prefix of
|
||||
* @return Custom prefix assigned using the API.
|
||||
* @see #setPrefix(TabPlayer, String)
|
||||
* @see #getOriginalPrefix(TabPlayer)
|
||||
* @see #getOriginalRawPrefix(TabPlayer)
|
||||
*/
|
||||
@Nullable
|
||||
String getCustomPrefix(@NonNull TabPlayer player);
|
||||
@@ -173,7 +173,7 @@ public interface NameTagManager {
|
||||
* Player to get custom suffix of
|
||||
* @return Custom suffix assigned using the API.
|
||||
* @see #setSuffix(TabPlayer, String)
|
||||
* @see #getOriginalSuffix(TabPlayer)
|
||||
* @see #getOriginalRawSuffix(TabPlayer)
|
||||
*/
|
||||
@Nullable
|
||||
String getCustomSuffix(@NonNull TabPlayer player);
|
||||
@@ -186,7 +186,7 @@ public interface NameTagManager {
|
||||
* @see #getCustomPrefix(TabPlayer)
|
||||
*/
|
||||
@NotNull
|
||||
String getOriginalPrefix(@NonNull TabPlayer player);
|
||||
String getOriginalRawPrefix(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Returns original suffix assigned by the plugin using internal logic.
|
||||
@@ -196,6 +196,50 @@ public interface NameTagManager {
|
||||
* @see #getCustomSuffix(TabPlayer)
|
||||
*/
|
||||
@NotNull
|
||||
String getOriginalRawSuffix(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Returns original prefix assigned by the plugin using internal logic with all placeholders replaced.
|
||||
* @param player
|
||||
* Player to get replaced original prefix of
|
||||
* @return Original prefix assigned by the plugin's internal logic.
|
||||
* @see #getCustomPrefix(TabPlayer)
|
||||
*/
|
||||
@NotNull
|
||||
String getOriginalReplacedPrefix(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Returns original suffix assigned by the plugin using internal logic with all placeholders replaced.
|
||||
* @param player
|
||||
* Player to get replaced original suffix of
|
||||
* @return Original suffix assigned by the plugin's internal logic.
|
||||
* @see #getCustomSuffix(TabPlayer)
|
||||
*/
|
||||
@NotNull
|
||||
String getOriginalReplacedSuffix(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Returns original suffix assigned by the plugin using internal logic.
|
||||
* @param player
|
||||
* Player to get replaced original prefix of
|
||||
* @return Original prefix assigned by the plugin's internal logic.
|
||||
* @see #getCustomPrefix(TabPlayer)
|
||||
* @deprecated Use {@link #getOriginalRawPrefix(TabPlayer)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
@NotNull
|
||||
String getOriginalPrefix(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Returns original suffix assigned by the plugin using internal logic.
|
||||
* @param player
|
||||
* Player to get replaced original suffix of
|
||||
* @return Original suffix assigned by the plugin's internal logic.
|
||||
* @see #getCustomSuffix(TabPlayer)
|
||||
* @deprecated Use {@link #getOriginalRawSuffix(TabPlayer)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
@NotNull
|
||||
String getOriginalSuffix(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package me.neznamy.tab.api.scoreboard;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Interface allowing work with scoreboards, such as creating,
|
||||
* sending, toggling and modifying.
|
||||
@@ -54,6 +54,29 @@ public interface ScoreboardManager {
|
||||
* @return map of registered scoreboards
|
||||
*/
|
||||
@NotNull Map<String, Scoreboard> getRegisteredScoreboards();
|
||||
|
||||
/**
|
||||
* Removes scoreboard with specified name from list of scoreboards. If it was displayed to anyone,
|
||||
* it will be removed from their display as well. Then, those players will receive a new scoreboard based
|
||||
* on configuration.
|
||||
*
|
||||
* @param name Name of scoreboard to remove
|
||||
* @throws IllegalArgumentException
|
||||
* if scoreboard with such name does not exist
|
||||
*/
|
||||
void removeScoreboard(@NonNull String name);
|
||||
|
||||
/**
|
||||
* Removes scoreboard from list of scoreboards. If it was displayed to anyone,
|
||||
* it will be removed from their display as well. Then, those players will receive a new scoreboard based
|
||||
* on configuration.
|
||||
*
|
||||
* @param scoreboard
|
||||
* Scoreboard to remove
|
||||
* @throws IllegalArgumentException
|
||||
* if specified scoreboard is not registered
|
||||
*/
|
||||
void removeScoreboard(@NonNull Scoreboard scoreboard);
|
||||
|
||||
/**
|
||||
* Displays scoreboard to defined player. This will disable all display
|
||||
|
||||
@@ -27,7 +27,7 @@ public interface TabListFormatManager {
|
||||
* @param prefix
|
||||
* new prefix value
|
||||
* @see #getCustomPrefix(TabPlayer)
|
||||
* @see #getOriginalPrefix(TabPlayer)
|
||||
* @see #getOriginalRawPrefix(TabPlayer)
|
||||
*/
|
||||
void setPrefix(@NonNull TabPlayer player, @Nullable String prefix);
|
||||
|
||||
@@ -41,7 +41,7 @@ public interface TabListFormatManager {
|
||||
* @param customName
|
||||
* new customName value
|
||||
* @see #getCustomName(TabPlayer)
|
||||
* @see #getOriginalName(TabPlayer)
|
||||
* @see #getOriginalRawName(TabPlayer)
|
||||
*/
|
||||
void setName(@NonNull TabPlayer player, @Nullable String customName);
|
||||
|
||||
@@ -55,7 +55,7 @@ public interface TabListFormatManager {
|
||||
* @param suffix
|
||||
* new suffix value
|
||||
* @see #getCustomSuffix(TabPlayer)
|
||||
* @see #getOriginalSuffix(TabPlayer)
|
||||
* @see #getOriginalRawSuffix(TabPlayer)
|
||||
*/
|
||||
void setSuffix(@NonNull TabPlayer player, @Nullable String suffix);
|
||||
|
||||
@@ -67,7 +67,7 @@ public interface TabListFormatManager {
|
||||
* player to get custom prefix of
|
||||
* @return player's custom prefix or {@code null} if not defined
|
||||
* @see #setPrefix(TabPlayer, String)
|
||||
* @see #getOriginalPrefix(TabPlayer)
|
||||
* @see #getOriginalRawPrefix(TabPlayer)
|
||||
*/
|
||||
@Nullable String getCustomPrefix(@NonNull TabPlayer player);
|
||||
|
||||
@@ -104,8 +104,11 @@ public interface TabListFormatManager {
|
||||
* @return player's original prefix
|
||||
* @see #setPrefix(TabPlayer, String)
|
||||
* @see #getCustomPrefix(TabPlayer)
|
||||
* @deprecated Use {@link #getOriginalRawPrefix(TabPlayer)} instead.
|
||||
*/
|
||||
@NotNull String getOriginalPrefix(@NonNull TabPlayer player);
|
||||
@NotNull
|
||||
@Deprecated
|
||||
String getOriginalPrefix(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Returns player's original name applied using plugin's internal logic.
|
||||
@@ -116,8 +119,11 @@ public interface TabListFormatManager {
|
||||
* @return player's original name
|
||||
* @see #setName(TabPlayer, String)
|
||||
* @see #getCustomName(TabPlayer)
|
||||
* @deprecated Use {@link #getOriginalRawName(TabPlayer)} instead.
|
||||
*/
|
||||
@NotNull String getOriginalName(@NonNull TabPlayer player);
|
||||
@NotNull
|
||||
@Deprecated
|
||||
String getOriginalName(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Returns player's original suffix applied using plugin's internal logic.
|
||||
@@ -128,6 +134,69 @@ public interface TabListFormatManager {
|
||||
* @return player's original suffix
|
||||
* @see #setSuffix(TabPlayer, String)
|
||||
* @see #getCustomSuffix(TabPlayer)
|
||||
* @deprecated Use {@link #getOriginalRawSuffix(TabPlayer)} instead.
|
||||
*/
|
||||
@NotNull String getOriginalSuffix(@NonNull TabPlayer player);
|
||||
@NotNull
|
||||
@Deprecated
|
||||
String getOriginalSuffix(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Returns original prefix assigned by the plugin using internal logic.
|
||||
* @param player
|
||||
* Player to get original prefix of
|
||||
* @return Original prefix assigned by the plugin's internal logic.
|
||||
* @see #getCustomPrefix(TabPlayer)
|
||||
*/
|
||||
@NotNull
|
||||
String getOriginalRawPrefix(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Returns original name assigned by the plugin using internal logic.
|
||||
* @param player
|
||||
* Player to get original name of
|
||||
* @return Original name assigned by the plugin's internal logic.
|
||||
* @see #getCustomName(TabPlayer)
|
||||
*/
|
||||
@NotNull
|
||||
String getOriginalRawName(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Returns original suffix assigned by the plugin using internal logic.
|
||||
* @param player
|
||||
* Player to get original suffix of
|
||||
* @return Original suffix assigned by the plugin's internal logic.
|
||||
* @see #getCustomSuffix(TabPlayer)
|
||||
*/
|
||||
@NotNull
|
||||
String getOriginalRawSuffix(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Returns original prefix assigned by the plugin using internal logic with all placeholders replaced.
|
||||
* @param player
|
||||
* Player to get replaced original prefix of
|
||||
* @return Original prefix assigned by the plugin's internal logic.
|
||||
* @see #getCustomPrefix(TabPlayer)
|
||||
*/
|
||||
@NotNull
|
||||
String getOriginalReplacedPrefix(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Returns original name assigned by the plugin using internal logic with all placeholders replaced.
|
||||
* @param player
|
||||
* Player to get replaced original name of
|
||||
* @return Original name assigned by the plugin's internal logic.
|
||||
* @see #getCustomName(TabPlayer)
|
||||
*/
|
||||
@NotNull
|
||||
String getOriginalReplacedName(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Returns original suffix assigned by the plugin using internal logic with all placeholders replaced.
|
||||
* @param player
|
||||
* Player to get replaced original suffix of
|
||||
* @return Original suffix assigned by the plugin's internal logic.
|
||||
* @see #getCustomSuffix(TabPlayer)
|
||||
*/
|
||||
@NotNull
|
||||
String getOriginalReplacedSuffix(@NonNull TabPlayer player);
|
||||
}
|
||||
+8
-2
@@ -4,7 +4,7 @@ plugins {
|
||||
|
||||
allprojects {
|
||||
group = "me.neznamy"
|
||||
version = "5.2.0"
|
||||
version = "5.2.4"
|
||||
description = "An all-in-one solution that works"
|
||||
|
||||
ext.set("id", "tab")
|
||||
@@ -15,9 +15,15 @@ allprojects {
|
||||
|
||||
val platforms = setOf(
|
||||
projects.bukkit,
|
||||
projects.bukkit.paper,
|
||||
projects.bukkit.paper1205,
|
||||
projects.bukkit.paper1212,
|
||||
projects.bukkit.paper1214,
|
||||
projects.bukkit.v18R3,
|
||||
projects.bukkit.v112R1,
|
||||
projects.bukkit.v116R3,
|
||||
projects.bukkit.v117R1,
|
||||
projects.bukkit.v118R2,
|
||||
projects.bukkit.v119R1,
|
||||
projects.bungeecord,
|
||||
projects.component,
|
||||
projects.velocity,
|
||||
|
||||
@@ -15,6 +15,4 @@ dependencies {
|
||||
exclude("net.md-5", "bungeecord-chat")
|
||||
}
|
||||
compileOnly("com.github.LeonMangler:PremiumVanishAPI:2.8.8")
|
||||
compileOnlyApi("com.viaversion:viaversion-api:5.2.1")
|
||||
compileOnly("com.viaversion:viaversion-common:5.2.1")
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
plugins {
|
||||
id("io.papermc.paperweight.userdev") version "2.0.0-beta.17"
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven("https://jitpack.io") // YamlAssist
|
||||
maven("https://repo.opencollab.dev/maven-snapshots/")
|
||||
maven("https://repo.viaversion.com/")
|
||||
maven("https://oss.sonatype.org/content/repositories/snapshots")
|
||||
}
|
||||
|
||||
val version = "1.20.6-R0.1-SNAPSHOT" // 1.20.5 has a broken dependency, but there were no changes, so it's ok
|
||||
|
||||
dependencies {
|
||||
implementation(projects.bukkit)
|
||||
paperweight.paperDevBundle(version)
|
||||
compileOnly("io.papermc.paper:paper-api:${version}")
|
||||
}
|
||||
|
||||
tasks.compileJava {
|
||||
options.release.set(21)
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
package me.neznamy.tab.platforms.paper_1_20_5;
|
||||
|
||||
import me.neznamy.chat.ChatModifier;
|
||||
import me.neznamy.tab.platforms.bukkit.provider.ComponentConverter;
|
||||
import net.minecraft.network.chat.*;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Component converter using direct mojang-mapped code.
|
||||
*/
|
||||
public class PaperComponentConverter extends ComponentConverter {
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Object newTextComponent(@NotNull String text) {
|
||||
return Component.literal(text);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Object newTranslatableComponent(@NotNull String key) {
|
||||
return Component.translatable(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Object newKeybindComponent(@NotNull String keybind) {
|
||||
return Component.keybind(keybind);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyStyle(@NotNull Object nmsComponent, @NotNull ChatModifier modifier) {
|
||||
Style style = Style.EMPTY
|
||||
.withColor(modifier.getColor() == null ? null : TextColor.fromRgb(modifier.getColor().getRgb()))
|
||||
.withBold(modifier.getBold())
|
||||
.withItalic(modifier.getItalic())
|
||||
.withUnderlined(modifier.getUnderlined())
|
||||
.withStrikethrough(modifier.getStrikethrough())
|
||||
.withObfuscated(modifier.getObfuscated())
|
||||
.withFont(modifier.getFont() == null ? null : ResourceLocation.tryParse(modifier.getFont()));
|
||||
((MutableComponent)nmsComponent).setStyle(style);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addSibling(@NotNull Object parent, @NotNull Object child) {
|
||||
((MutableComponent)parent).append((Component) child);
|
||||
}
|
||||
}
|
||||
+8
-3
@@ -1,9 +1,9 @@
|
||||
package me.neznamy.tab.platforms.paper;
|
||||
package me.neznamy.tab.platforms.paper_1_20_5;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import lombok.Getter;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.converter.ComponentConverter;
|
||||
import me.neznamy.tab.platforms.bukkit.provider.ComponentConverter;
|
||||
import me.neznamy.tab.platforms.bukkit.provider.ImplementationProvider;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
@@ -12,7 +12,7 @@ import org.bukkit.craftbukkit.entity.CraftPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Implementation provider using direct Mojang-mapped NMS code (1.20.5+).
|
||||
* Implementation provider using direct Mojang-mapped NMS code for versions 1.20.5 - 1.21.1.
|
||||
*/
|
||||
@Getter
|
||||
public class PaperImplementationProvider implements ImplementationProvider {
|
||||
@@ -37,4 +37,9 @@ public class PaperImplementationProvider implements ImplementationProvider {
|
||||
public FunctionWithException<BukkitTabPlayer, Channel> getChannelFunction() {
|
||||
return player -> ((CraftPlayer)player.getPlayer()).getHandle().connection.connection.channel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPing(@NotNull BukkitTabPlayer player) {
|
||||
return player.getPlayer().getPing();
|
||||
}
|
||||
}
|
||||
+22
-11
@@ -1,4 +1,4 @@
|
||||
package me.neznamy.tab.platforms.paper;
|
||||
package me.neznamy.tab.platforms.paper_1_20_5;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
||||
@@ -31,7 +31,6 @@ public class PaperPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
|
||||
private static final net.minecraft.world.scores.Team.CollisionRule[] collisions = net.minecraft.world.scores.Team.CollisionRule.values();
|
||||
private static final net.minecraft.world.scores.Team.Visibility[] visibilities = net.minecraft.world.scores.Team.Visibility.values();
|
||||
private static final Scoreboard dummyScoreboard = new Scoreboard();
|
||||
private static final Field method = ReflectionUtils.getInstanceFields(ClientboundSetPlayerTeamPacket.class, int.class).getFirst();
|
||||
private static final Field players = ReflectionUtils.getOnlyField(ClientboundSetPlayerTeamPacket.class, Collection.class);
|
||||
|
||||
/**
|
||||
@@ -135,21 +134,33 @@ public class PaperPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void onPacketSend(@NonNull Object packet) {
|
||||
if (isAntiOverrideScoreboard()) {
|
||||
if (packet instanceof ClientboundSetDisplayObjectivePacket display) {
|
||||
TAB.getInstance().getFeatureManager().onDisplayObjective(player, display.getSlot().ordinal(), display.getObjectiveName());
|
||||
}
|
||||
if (packet instanceof ClientboundSetObjectivePacket objective) {
|
||||
TAB.getInstance().getFeatureManager().onObjective(player, objective.getMethod(), objective.getObjectiveName());
|
||||
}
|
||||
if (packet instanceof ClientboundSetDisplayObjectivePacket display) {
|
||||
TAB.getInstance().getFeatureManager().onDisplayObjective(player, display.getSlot().ordinal(), display.getObjectiveName());
|
||||
}
|
||||
if (isAntiOverrideTeams() && packet instanceof ClientboundSetPlayerTeamPacket team) {
|
||||
int action = method.getInt(team);
|
||||
if (packet instanceof ClientboundSetObjectivePacket objective) {
|
||||
TAB.getInstance().getFeatureManager().onObjective(player, objective.getMethod(), objective.getObjectiveName());
|
||||
}
|
||||
if (packet instanceof ClientboundSetPlayerTeamPacket team) {
|
||||
int action = getMethod(team);
|
||||
if (action == TeamAction.UPDATE) return;
|
||||
players.set(team, onTeamPacket(action, team.getName(), team.getPlayers() == null ? Collections.emptyList() : team.getPlayers()));
|
||||
}
|
||||
}
|
||||
|
||||
private int getMethod(@NonNull ClientboundSetPlayerTeamPacket team) {
|
||||
if (team.getTeamAction() == ClientboundSetPlayerTeamPacket.Action.ADD) {
|
||||
return 0;
|
||||
} else if (team.getTeamAction() == ClientboundSetPlayerTeamPacket.Action.REMOVE) {
|
||||
return 1;
|
||||
} else if (team.getPlayerAction() == ClientboundSetPlayerTeamPacket.Action.ADD) {
|
||||
return 3;
|
||||
} else if (team.getPlayerAction() == ClientboundSetPlayerTeamPacket.Action.REMOVE) {
|
||||
return 4;
|
||||
} else {
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the packet to the player.
|
||||
*
|
||||
+190
@@ -0,0 +1,190 @@
|
||||
package me.neznamy.tab.platforms.paper_1_20_5;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import com.mojang.authlib.properties.Property;
|
||||
import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import me.neznamy.tab.shared.platform.decorators.TrackedTabList;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket;
|
||||
import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket;
|
||||
import net.minecraft.network.protocol.game.ClientboundTabListPacket;
|
||||
import net.minecraft.world.level.GameType;
|
||||
import org.bukkit.craftbukkit.entity.CraftPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* TabList implementation using direct mojang-mapped code.
|
||||
*/
|
||||
public class PaperPacketTabList extends TrackedTabList<BukkitTabPlayer> {
|
||||
|
||||
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> addPlayer = EnumSet.allOf(ClientboundPlayerInfoUpdatePacket.Action.class);
|
||||
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateDisplayName = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME);
|
||||
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateLatency = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY);
|
||||
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateGameMode = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE);
|
||||
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateListed = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED);
|
||||
|
||||
private static final Field entries = ReflectionUtils.getOnlyField(ClientboundPlayerInfoUpdatePacket.class, List.class);
|
||||
|
||||
/**
|
||||
* Constructs new instance.
|
||||
*
|
||||
* @param player
|
||||
* Player this tablist will belong to
|
||||
*/
|
||||
public PaperPacketTabList(@NotNull BukkitTabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeEntry(@NonNull UUID entry) {
|
||||
sendPacket(new ClientboundPlayerInfoRemovePacket(Collections.singletonList(entry)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDisplayName0(@NonNull UUID entry, @Nullable TabComponent displayName) {
|
||||
sendPacket(updateDisplayName, entry, "", null, false, 0, 0, displayName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateLatency(@NonNull UUID entry, int latency) {
|
||||
sendPacket(updateLatency, entry, "", null, false, latency, 0, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateGameMode(@NonNull UUID entry, int gameMode) {
|
||||
sendPacket(updateGameMode, entry, "", null, false, 0, gameMode, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateListed(@NonNull UUID entry, boolean listed) {
|
||||
sendPacket(updateListed, entry, "", null, listed, 0, 0, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateListOrder(@NonNull UUID entry, int listOrder) {
|
||||
// Added in 1.21.2
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateHat(@NonNull UUID entry, boolean showHat) {
|
||||
// Added in 1.21.4
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addEntry0(@NonNull Entry entry) {
|
||||
sendPacket(addPlayer, entry.getUniqueId(), entry.getName(), entry.getSkin(), entry.isListed(), entry.getLatency(),
|
||||
entry.getGameMode(), entry.getDisplayName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPlayerListHeaderFooter(@NonNull TabComponent header, @NonNull TabComponent footer) {
|
||||
sendPacket(new ClientboundTabListPacket(header.convert(), footer.convert()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsEntry(@NonNull UUID entry) {
|
||||
return true; // TODO?
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Skin getSkin() {
|
||||
Collection<Property> properties = ((CraftPlayer)player.getPlayer()).getProfile().getProperties().get(TEXTURES_PROPERTY);
|
||||
if (properties.isEmpty()) return null; // Offline mode
|
||||
Property property = properties.iterator().next();
|
||||
return new Skin(property.value(), property.signature());
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void onPacketSend(@NonNull Object packet) {
|
||||
if (packet instanceof ClientboundPlayerInfoUpdatePacket info) {
|
||||
EnumSet<ClientboundPlayerInfoUpdatePacket.Action> actions = info.actions();
|
||||
List<ClientboundPlayerInfoUpdatePacket.Entry> updatedList = new ArrayList<>();
|
||||
boolean rewritePacket = false;
|
||||
for (ClientboundPlayerInfoUpdatePacket.Entry nmsData : info.entries()) {
|
||||
boolean rewriteEntry = false;
|
||||
Component displayName = nmsData.displayName();
|
||||
int latency = nmsData.latency();
|
||||
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME)) {
|
||||
TabComponent expectedDisplayName = getExpectedDisplayNames().get(nmsData.profileId());
|
||||
if (expectedDisplayName != null && expectedDisplayName.convert() != displayName) {
|
||||
displayName = expectedDisplayName.convert();
|
||||
rewriteEntry = rewritePacket = true;
|
||||
}
|
||||
}
|
||||
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY)) {
|
||||
int newLatency = TAB.getInstance().getFeatureManager().onLatencyChange(player, nmsData.profileId(), latency);
|
||||
if (newLatency != latency) {
|
||||
latency = newLatency;
|
||||
rewriteEntry = rewritePacket = true;
|
||||
}
|
||||
}
|
||||
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER)) {
|
||||
TAB.getInstance().getFeatureManager().onEntryAdd(player, nmsData.profileId(), nmsData.profile().getName());
|
||||
}
|
||||
updatedList.add(rewriteEntry ? new ClientboundPlayerInfoUpdatePacket.Entry(
|
||||
nmsData.profileId(), nmsData.profile(), nmsData.listed(), latency, nmsData.gameMode(), displayName,
|
||||
nmsData.chatSession()
|
||||
) : nmsData);
|
||||
}
|
||||
if (rewritePacket) entries.set(info, updatedList);
|
||||
}
|
||||
}
|
||||
|
||||
private void sendPacket(@NonNull EnumSet<ClientboundPlayerInfoUpdatePacket.Action> action, @NonNull UUID id, @NonNull String name, @Nullable Skin skin,
|
||||
boolean listed, int latency, int gameMode, @Nullable TabComponent displayName) {
|
||||
ClientboundPlayerInfoUpdatePacket packet = new ClientboundPlayerInfoUpdatePacket(action, new ClientboundPlayerInfoUpdatePacket.Entry(
|
||||
id,
|
||||
action.contains(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER) ? createProfile(id, name, skin) : null,
|
||||
listed,
|
||||
latency,
|
||||
GameType.byId(gameMode),
|
||||
displayName == null ? null : displayName.convert(),
|
||||
null
|
||||
));
|
||||
sendPacket(packet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates GameProfile from given parameters.
|
||||
*
|
||||
* @param id
|
||||
* Profile ID
|
||||
* @param name
|
||||
* Profile name
|
||||
* @param skin
|
||||
* Player skin
|
||||
* @return GameProfile from given parameters
|
||||
*/
|
||||
@NotNull
|
||||
private GameProfile createProfile(@NonNull UUID id, @NonNull String name, @Nullable Skin skin) {
|
||||
GameProfile profile = new GameProfile(id, name);
|
||||
if (skin != null) {
|
||||
profile.getProperties().put(TabList.TEXTURES_PROPERTY,
|
||||
new Property(TabList.TEXTURES_PROPERTY, skin.getValue(), skin.getSignature()));
|
||||
}
|
||||
return profile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the packet to the player.
|
||||
*
|
||||
* @param packet
|
||||
* Packet to send
|
||||
*/
|
||||
private void sendPacket(@NotNull Packet<?> packet) {
|
||||
((CraftPlayer)player.getPlayer()).getHandle().connection.send(packet);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
plugins {
|
||||
id("io.papermc.paperweight.userdev") version "2.0.0-beta.17"
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven("https://jitpack.io") // YamlAssist
|
||||
maven("https://repo.opencollab.dev/maven-snapshots/")
|
||||
maven("https://repo.viaversion.com/")
|
||||
maven("https://oss.sonatype.org/content/repositories/snapshots")
|
||||
}
|
||||
|
||||
val version = "1.21.3-R0.1-SNAPSHOT" // 1.21.2 causes gradle error, but the versions are interchangeable
|
||||
|
||||
dependencies {
|
||||
implementation(projects.bukkit)
|
||||
paperweight.paperDevBundle(version)
|
||||
compileOnly("io.papermc.paper:paper-api:${version}")
|
||||
}
|
||||
|
||||
tasks.compileJava {
|
||||
options.release.set(21)
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
package me.neznamy.tab.platforms.paper_1_21_2;
|
||||
|
||||
import me.neznamy.chat.ChatModifier;
|
||||
import me.neznamy.tab.platforms.bukkit.provider.ComponentConverter;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.network.chat.MutableComponent;
|
||||
import net.minecraft.network.chat.Style;
|
||||
import net.minecraft.network.chat.TextColor;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Component converter using direct mojang-mapped code.
|
||||
*/
|
||||
public class PaperComponentConverter extends ComponentConverter {
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Object newTextComponent(@NotNull String text) {
|
||||
return Component.literal(text);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Object newTranslatableComponent(@NotNull String key) {
|
||||
return Component.translatable(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Object newKeybindComponent(@NotNull String keybind) {
|
||||
return Component.keybind(keybind);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyStyle(@NotNull Object nmsComponent, @NotNull ChatModifier modifier) {
|
||||
Style style = Style.EMPTY
|
||||
.withColor(modifier.getColor() == null ? null : TextColor.fromRgb(modifier.getColor().getRgb()))
|
||||
.withBold(modifier.getBold())
|
||||
.withItalic(modifier.getItalic())
|
||||
.withUnderlined(modifier.getUnderlined())
|
||||
.withStrikethrough(modifier.getStrikethrough())
|
||||
.withObfuscated(modifier.getObfuscated())
|
||||
.withFont(modifier.getFont() == null ? null : ResourceLocation.tryParse(modifier.getFont()));
|
||||
((MutableComponent)nmsComponent).setStyle(style);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addSibling(@NotNull Object parent, @NotNull Object child) {
|
||||
((MutableComponent)parent).append((Component) child);
|
||||
}
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
package me.neznamy.tab.platforms.paper_1_21_2;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import lombok.Getter;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.provider.ComponentConverter;
|
||||
import me.neznamy.tab.platforms.bukkit.provider.ImplementationProvider;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import me.neznamy.tab.shared.util.function.FunctionWithException;
|
||||
import org.bukkit.craftbukkit.entity.CraftPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Implementation provider using direct Mojang-mapped NMS code for versions 1.21.2 - 1.21.3.
|
||||
*/
|
||||
@Getter
|
||||
public class PaperImplementationProvider implements ImplementationProvider {
|
||||
|
||||
@NotNull
|
||||
private final ComponentConverter componentConverter = new PaperComponentConverter();
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Scoreboard newScoreboard(@NotNull BukkitTabPlayer player) {
|
||||
return new PaperPacketScoreboard(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public TabList newTabList(@NotNull BukkitTabPlayer player) {
|
||||
return new PaperPacketTabList(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public FunctionWithException<BukkitTabPlayer, Channel> getChannelFunction() {
|
||||
return player -> ((CraftPlayer)player.getPlayer()).getHandle().connection.connection.channel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPing(@NotNull BukkitTabPlayer player) {
|
||||
return player.getPlayer().getPing();
|
||||
}
|
||||
}
|
||||
+173
@@ -0,0 +1,173 @@
|
||||
package me.neznamy.tab.platforms.paper_1_21_2;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.platform.decorators.SafeScoreboard;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.network.chat.numbers.FixedFormat;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.network.protocol.game.*;
|
||||
import net.minecraft.world.scores.PlayerTeam;
|
||||
import net.minecraft.world.scores.Scoreboard;
|
||||
import net.minecraft.world.scores.criteria.ObjectiveCriteria;
|
||||
import net.minecraft.world.scores.criteria.ObjectiveCriteria.RenderType;
|
||||
import org.bukkit.craftbukkit.entity.CraftPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Scoreboard implementation using direct mojang-mapped code.
|
||||
*/
|
||||
public class PaperPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
|
||||
|
||||
private static final ChatFormatting[] formats = ChatFormatting.values();
|
||||
private static final net.minecraft.world.scores.Team.CollisionRule[] collisions = net.minecraft.world.scores.Team.CollisionRule.values();
|
||||
private static final net.minecraft.world.scores.Team.Visibility[] visibilities = net.minecraft.world.scores.Team.Visibility.values();
|
||||
private static final Scoreboard dummyScoreboard = new Scoreboard();
|
||||
private static final Field players = ReflectionUtils.getOnlyField(ClientboundSetPlayerTeamPacket.class, Collection.class);
|
||||
|
||||
/**
|
||||
* Constructs new instance with given player.
|
||||
*
|
||||
* @param player
|
||||
* Player this scoreboard will belong to
|
||||
*/
|
||||
public PaperPacketScoreboard(@NotNull BukkitTabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerObjective(@NonNull Objective objective) {
|
||||
net.minecraft.world.scores.Objective obj = new net.minecraft.world.scores.Objective(
|
||||
dummyScoreboard,
|
||||
objective.getName(),
|
||||
ObjectiveCriteria.DUMMY,
|
||||
objective.getTitle().convert(),
|
||||
RenderType.values()[objective.getHealthDisplay().ordinal()],
|
||||
false,
|
||||
objective.getNumberFormat() == null ? null : objective.getNumberFormat().toFixedFormat(FixedFormat::new)
|
||||
);
|
||||
objective.setPlatformObjective(obj);
|
||||
sendPacket(new ClientboundSetObjectivePacket(obj, ObjectiveAction.REGISTER));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDisplaySlot(@NonNull Objective objective) {
|
||||
sendPacket(new ClientboundSetDisplayObjectivePacket(
|
||||
net.minecraft.world.scores.DisplaySlot.values()[objective.getDisplaySlot().ordinal()],
|
||||
(net.minecraft.world.scores.Objective) objective.getPlatformObjective()
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterObjective(@NonNull Objective objective) {
|
||||
sendPacket(new ClientboundSetObjectivePacket((net.minecraft.world.scores.Objective) objective.getPlatformObjective(), ObjectiveAction.UNREGISTER));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateObjective(@NonNull Objective objective) {
|
||||
net.minecraft.world.scores.Objective obj = (net.minecraft.world.scores.Objective) objective.getPlatformObjective();
|
||||
obj.setDisplayName(objective.getTitle().convert());
|
||||
obj.setRenderType(RenderType.values()[objective.getHealthDisplay().ordinal()]);
|
||||
sendPacket(new ClientboundSetObjectivePacket(obj, ObjectiveAction.UPDATE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setScore(@NonNull Score score) {
|
||||
sendPacket(new ClientboundSetScorePacket(
|
||||
score.getHolder(),
|
||||
score.getObjective().getName(),
|
||||
score.getValue(),
|
||||
Optional.ofNullable(score.getDisplayName() == null ? null : score.getDisplayName().convert()),
|
||||
Optional.ofNullable(score.getNumberFormat() == null ? null : score.getNumberFormat().toFixedFormat(FixedFormat::new))
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeScore(@NonNull Score score) {
|
||||
sendPacket(new ClientboundResetScorePacket(score.getHolder(), score.getObjective().getName()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Object createTeam(@NonNull String name) {
|
||||
return new PlayerTeam(dummyScoreboard, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerTeam(@NonNull Team team) {
|
||||
updateTeamProperties(team);
|
||||
PlayerTeam t = (PlayerTeam) team.getPlatformTeam();
|
||||
t.getPlayers().addAll(team.getPlayers());
|
||||
sendPacket(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(t, true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterTeam(@NonNull Team team) {
|
||||
sendPacket(ClientboundSetPlayerTeamPacket.createRemovePacket((PlayerTeam) team.getPlatformTeam()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateTeam(@NonNull Team team) {
|
||||
updateTeamProperties(team);
|
||||
sendPacket(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket((PlayerTeam) team.getPlatformTeam(), false));
|
||||
}
|
||||
|
||||
private void updateTeamProperties(@NonNull Team team) {
|
||||
PlayerTeam t = (PlayerTeam) team.getPlatformTeam();
|
||||
t.setAllowFriendlyFire((team.getOptions() & 0x01) != 0);
|
||||
t.setSeeFriendlyInvisibles((team.getOptions() & 0x02) != 0);
|
||||
t.setColor(formats[team.getColor().getLegacyColor().ordinal()]);
|
||||
t.setCollisionRule(collisions[team.getCollision().ordinal()]);
|
||||
t.setNameTagVisibility(visibilities[team.getVisibility().ordinal()]);
|
||||
t.setPlayerPrefix(team.getPrefix().convert());
|
||||
t.setPlayerSuffix(team.getSuffix().convert());
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void onPacketSend(@NonNull Object packet) {
|
||||
if (packet instanceof ClientboundSetDisplayObjectivePacket display) {
|
||||
TAB.getInstance().getFeatureManager().onDisplayObjective(player, display.getSlot().ordinal(), display.getObjectiveName());
|
||||
}
|
||||
if (packet instanceof ClientboundSetObjectivePacket objective) {
|
||||
TAB.getInstance().getFeatureManager().onObjective(player, objective.getMethod(), objective.getObjectiveName());
|
||||
}
|
||||
if (packet instanceof ClientboundSetPlayerTeamPacket team) {
|
||||
int action = getMethod(team);
|
||||
if (action == TeamAction.UPDATE) return;
|
||||
players.set(team, onTeamPacket(action, team.getName(), team.getPlayers() == null ? Collections.emptyList() : team.getPlayers()));
|
||||
}
|
||||
}
|
||||
|
||||
private static int getMethod(@NonNull ClientboundSetPlayerTeamPacket team) {
|
||||
if (team.getTeamAction() == ClientboundSetPlayerTeamPacket.Action.ADD) {
|
||||
return 0;
|
||||
} else if (team.getTeamAction() == ClientboundSetPlayerTeamPacket.Action.REMOVE) {
|
||||
return 1;
|
||||
} else if (team.getPlayerAction() == ClientboundSetPlayerTeamPacket.Action.ADD) {
|
||||
return 3;
|
||||
} else if (team.getPlayerAction() == ClientboundSetPlayerTeamPacket.Action.REMOVE) {
|
||||
return 4;
|
||||
} else {
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the packet to the player.
|
||||
*
|
||||
* @param packet
|
||||
* Packet to send
|
||||
*/
|
||||
private void sendPacket(@NotNull Packet<?> packet) {
|
||||
((CraftPlayer)player.getPlayer()).getHandle().connection.sendPacket(packet);
|
||||
}
|
||||
}
|
||||
+192
@@ -0,0 +1,192 @@
|
||||
package me.neznamy.tab.platforms.paper_1_21_2;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import com.mojang.authlib.properties.Property;
|
||||
import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import me.neznamy.tab.shared.platform.decorators.TrackedTabList;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket;
|
||||
import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket;
|
||||
import net.minecraft.network.protocol.game.ClientboundTabListPacket;
|
||||
import net.minecraft.world.level.GameType;
|
||||
import org.bukkit.craftbukkit.entity.CraftPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* TabList implementation using direct mojang-mapped code.
|
||||
*/
|
||||
public class PaperPacketTabList extends TrackedTabList<BukkitTabPlayer> {
|
||||
|
||||
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> addPlayer = EnumSet.allOf(ClientboundPlayerInfoUpdatePacket.Action.class);
|
||||
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateDisplayName = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME);
|
||||
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateLatency = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY);
|
||||
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateGameMode = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE);
|
||||
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateListed = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED);
|
||||
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateListOrder = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LIST_ORDER);
|
||||
|
||||
private static final Field entries = ReflectionUtils.getOnlyField(ClientboundPlayerInfoUpdatePacket.class, List.class);
|
||||
|
||||
/**
|
||||
* Constructs new instance.
|
||||
*
|
||||
* @param player
|
||||
* Player this tablist will belong to
|
||||
*/
|
||||
public PaperPacketTabList(@NotNull BukkitTabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeEntry(@NonNull UUID entry) {
|
||||
sendPacket(new ClientboundPlayerInfoRemovePacket(Collections.singletonList(entry)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDisplayName0(@NonNull UUID entry, @Nullable TabComponent displayName) {
|
||||
sendPacket(updateDisplayName, entry, "", null, false, 0, 0, displayName, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateLatency(@NonNull UUID entry, int latency) {
|
||||
sendPacket(updateLatency, entry, "", null, false, latency, 0, null, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateGameMode(@NonNull UUID entry, int gameMode) {
|
||||
sendPacket(updateGameMode, entry, "", null, false, 0, gameMode, null, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateListed(@NonNull UUID entry, boolean listed) {
|
||||
sendPacket(updateListed, entry, "", null, listed, 0, 0, null, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateListOrder(@NonNull UUID entry, int listOrder) {
|
||||
sendPacket(updateListOrder, entry, "", null, false, 0, 0, null, listOrder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateHat(@NonNull UUID entry, boolean showHat) {
|
||||
// Added in 1.21.4
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addEntry0(@NonNull Entry entry) {
|
||||
sendPacket(addPlayer, entry.getUniqueId(), entry.getName(), entry.getSkin(), entry.isListed(), entry.getLatency(),
|
||||
entry.getGameMode(), entry.getDisplayName(), entry.getListOrder());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPlayerListHeaderFooter(@NonNull TabComponent header, @NonNull TabComponent footer) {
|
||||
sendPacket(new ClientboundTabListPacket(header.convert(), footer.convert()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsEntry(@NonNull UUID entry) {
|
||||
return true; // TODO?
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Skin getSkin() {
|
||||
Collection<Property> properties = ((CraftPlayer)player.getPlayer()).getProfile().getProperties().get(TEXTURES_PROPERTY);
|
||||
if (properties.isEmpty()) return null; // Offline mode
|
||||
Property property = properties.iterator().next();
|
||||
return new Skin(property.value(), property.signature());
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void onPacketSend(@NonNull Object packet) {
|
||||
if (packet instanceof ClientboundPlayerInfoUpdatePacket info) {
|
||||
EnumSet<ClientboundPlayerInfoUpdatePacket.Action> actions = info.actions();
|
||||
List<ClientboundPlayerInfoUpdatePacket.Entry> updatedList = new ArrayList<>();
|
||||
boolean rewritePacket = false;
|
||||
for (ClientboundPlayerInfoUpdatePacket.Entry nmsData : info.entries()) {
|
||||
boolean rewriteEntry = false;
|
||||
Component displayName = nmsData.displayName();
|
||||
int latency = nmsData.latency();
|
||||
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME)) {
|
||||
TabComponent expectedDisplayName = getExpectedDisplayNames().get(nmsData.profileId());
|
||||
if (expectedDisplayName != null && expectedDisplayName.convert() != displayName) {
|
||||
displayName = expectedDisplayName.convert();
|
||||
rewriteEntry = rewritePacket = true;
|
||||
}
|
||||
}
|
||||
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY)) {
|
||||
int newLatency = TAB.getInstance().getFeatureManager().onLatencyChange(player, nmsData.profileId(), latency);
|
||||
if (newLatency != latency) {
|
||||
latency = newLatency;
|
||||
rewriteEntry = rewritePacket = true;
|
||||
}
|
||||
}
|
||||
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER)) {
|
||||
TAB.getInstance().getFeatureManager().onEntryAdd(player, nmsData.profileId(), nmsData.profile().getName());
|
||||
}
|
||||
updatedList.add(rewriteEntry ? new ClientboundPlayerInfoUpdatePacket.Entry(
|
||||
nmsData.profileId(), nmsData.profile(), nmsData.listed(), latency, nmsData.gameMode(), displayName,
|
||||
nmsData.listOrder(), nmsData.chatSession()
|
||||
) : nmsData);
|
||||
}
|
||||
if (rewritePacket) entries.set(info, updatedList);
|
||||
}
|
||||
}
|
||||
|
||||
private void sendPacket(@NonNull EnumSet<ClientboundPlayerInfoUpdatePacket.Action> action, @NonNull UUID id, @NonNull String name, @Nullable Skin skin,
|
||||
boolean listed, int latency, int gameMode, @Nullable TabComponent displayName, int listOrder) {
|
||||
ClientboundPlayerInfoUpdatePacket packet = new ClientboundPlayerInfoUpdatePacket(action, new ClientboundPlayerInfoUpdatePacket.Entry(
|
||||
id,
|
||||
action.contains(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER) ? createProfile(id, name, skin) : null,
|
||||
listed,
|
||||
latency,
|
||||
GameType.byId(gameMode),
|
||||
displayName == null ? null : displayName.convert(),
|
||||
listOrder,
|
||||
null
|
||||
));
|
||||
sendPacket(packet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates GameProfile from given parameters.
|
||||
*
|
||||
* @param id
|
||||
* Profile ID
|
||||
* @param name
|
||||
* Profile name
|
||||
* @param skin
|
||||
* Player skin
|
||||
* @return GameProfile from given parameters
|
||||
*/
|
||||
@NotNull
|
||||
private GameProfile createProfile(@NonNull UUID id, @NonNull String name, @Nullable Skin skin) {
|
||||
GameProfile profile = new GameProfile(id, name);
|
||||
if (skin != null) {
|
||||
profile.getProperties().put(TabList.TEXTURES_PROPERTY,
|
||||
new Property(TabList.TEXTURES_PROPERTY, skin.getValue(), skin.getSignature()));
|
||||
}
|
||||
return profile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the packet to the player.
|
||||
*
|
||||
* @param packet
|
||||
* Packet to send
|
||||
*/
|
||||
private void sendPacket(@NotNull Packet<?> packet) {
|
||||
((CraftPlayer)player.getPlayer()).getHandle().connection.send(packet);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
plugins {
|
||||
id("io.papermc.paperweight.userdev") version "2.0.0-beta.16"
|
||||
id("io.papermc.paperweight.userdev") version "2.0.0-beta.17"
|
||||
}
|
||||
|
||||
repositories {
|
||||
@@ -9,11 +9,11 @@ repositories {
|
||||
maven("https://oss.sonatype.org/content/repositories/snapshots")
|
||||
}
|
||||
|
||||
val version = "1.21.5-R0.1-SNAPSHOT"
|
||||
val version = "1.21.4-R0.1-SNAPSHOT"
|
||||
|
||||
dependencies {
|
||||
implementation(projects.bukkit)
|
||||
paperweight.paperDevBundle("1.21.5-no-moonrise-SNAPSHOT") // Just temporary
|
||||
paperweight.paperDevBundle(version)
|
||||
compileOnly("io.papermc.paper:paper-api:${version}")
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
package me.neznamy.tab.platforms.paper;
|
||||
package me.neznamy.tab.platforms.paper_1_21_4;
|
||||
|
||||
import me.neznamy.chat.ChatModifier;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.converter.ComponentConverter;
|
||||
import me.neznamy.tab.platforms.bukkit.provider.ComponentConverter;
|
||||
import net.minecraft.network.chat.*;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
package me.neznamy.tab.platforms.paper_1_21_4;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import lombok.Getter;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.provider.ComponentConverter;
|
||||
import me.neznamy.tab.platforms.bukkit.provider.ImplementationProvider;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import me.neznamy.tab.shared.util.function.FunctionWithException;
|
||||
import org.bukkit.craftbukkit.entity.CraftPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Implementation provider using direct Mojang-mapped NMS code for versions 1.21.4 - 1.21.7.
|
||||
*/
|
||||
@Getter
|
||||
public class PaperImplementationProvider implements ImplementationProvider {
|
||||
|
||||
@NotNull
|
||||
private final ComponentConverter componentConverter = new PaperComponentConverter();
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Scoreboard newScoreboard(@NotNull BukkitTabPlayer player) {
|
||||
return new PaperPacketScoreboard(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public TabList newTabList(@NotNull BukkitTabPlayer player) {
|
||||
return new PaperPacketTabList(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public FunctionWithException<BukkitTabPlayer, Channel> getChannelFunction() {
|
||||
return player -> ((CraftPlayer)player.getPlayer()).getHandle().connection.connection.channel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPing(@NotNull BukkitTabPlayer player) {
|
||||
return player.getPlayer().getPing();
|
||||
}
|
||||
}
|
||||
+173
@@ -0,0 +1,173 @@
|
||||
package me.neznamy.tab.platforms.paper_1_21_4;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.platform.decorators.SafeScoreboard;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.network.chat.numbers.FixedFormat;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.network.protocol.game.*;
|
||||
import net.minecraft.world.scores.PlayerTeam;
|
||||
import net.minecraft.world.scores.Scoreboard;
|
||||
import net.minecraft.world.scores.criteria.ObjectiveCriteria;
|
||||
import net.minecraft.world.scores.criteria.ObjectiveCriteria.RenderType;
|
||||
import org.bukkit.craftbukkit.entity.CraftPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Scoreboard implementation using direct mojang-mapped code.
|
||||
*/
|
||||
public class PaperPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
|
||||
|
||||
private static final ChatFormatting[] formats = ChatFormatting.values();
|
||||
private static final net.minecraft.world.scores.Team.CollisionRule[] collisions = net.minecraft.world.scores.Team.CollisionRule.values();
|
||||
private static final net.minecraft.world.scores.Team.Visibility[] visibilities = net.minecraft.world.scores.Team.Visibility.values();
|
||||
private static final Scoreboard dummyScoreboard = new Scoreboard();
|
||||
private static final Field players = ReflectionUtils.getOnlyField(ClientboundSetPlayerTeamPacket.class, Collection.class);
|
||||
|
||||
/**
|
||||
* Constructs new instance with given player.
|
||||
*
|
||||
* @param player
|
||||
* Player this scoreboard will belong to
|
||||
*/
|
||||
public PaperPacketScoreboard(@NotNull BukkitTabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerObjective(@NonNull Objective objective) {
|
||||
net.minecraft.world.scores.Objective obj = new net.minecraft.world.scores.Objective(
|
||||
dummyScoreboard,
|
||||
objective.getName(),
|
||||
ObjectiveCriteria.DUMMY,
|
||||
objective.getTitle().convert(),
|
||||
RenderType.values()[objective.getHealthDisplay().ordinal()],
|
||||
false,
|
||||
objective.getNumberFormat() == null ? null : objective.getNumberFormat().toFixedFormat(FixedFormat::new)
|
||||
);
|
||||
objective.setPlatformObjective(obj);
|
||||
sendPacket(new ClientboundSetObjectivePacket(obj, ObjectiveAction.REGISTER));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDisplaySlot(@NonNull Objective objective) {
|
||||
sendPacket(new ClientboundSetDisplayObjectivePacket(
|
||||
net.minecraft.world.scores.DisplaySlot.values()[objective.getDisplaySlot().ordinal()],
|
||||
(net.minecraft.world.scores.Objective) objective.getPlatformObjective()
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterObjective(@NonNull Objective objective) {
|
||||
sendPacket(new ClientboundSetObjectivePacket((net.minecraft.world.scores.Objective) objective.getPlatformObjective(), ObjectiveAction.UNREGISTER));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateObjective(@NonNull Objective objective) {
|
||||
net.minecraft.world.scores.Objective obj = (net.minecraft.world.scores.Objective) objective.getPlatformObjective();
|
||||
obj.setDisplayName(objective.getTitle().convert());
|
||||
obj.setRenderType(RenderType.values()[objective.getHealthDisplay().ordinal()]);
|
||||
sendPacket(new ClientboundSetObjectivePacket(obj, ObjectiveAction.UPDATE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setScore(@NonNull Score score) {
|
||||
sendPacket(new ClientboundSetScorePacket(
|
||||
score.getHolder(),
|
||||
score.getObjective().getName(),
|
||||
score.getValue(),
|
||||
Optional.ofNullable(score.getDisplayName() == null ? null : score.getDisplayName().convert()),
|
||||
Optional.ofNullable(score.getNumberFormat() == null ? null : score.getNumberFormat().toFixedFormat(FixedFormat::new))
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeScore(@NonNull Score score) {
|
||||
sendPacket(new ClientboundResetScorePacket(score.getHolder(), score.getObjective().getName()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Object createTeam(@NonNull String name) {
|
||||
return new PlayerTeam(dummyScoreboard, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerTeam(@NonNull Team team) {
|
||||
updateTeamProperties(team);
|
||||
PlayerTeam t = (PlayerTeam) team.getPlatformTeam();
|
||||
t.getPlayers().addAll(team.getPlayers());
|
||||
sendPacket(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(t, true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterTeam(@NonNull Team team) {
|
||||
sendPacket(ClientboundSetPlayerTeamPacket.createRemovePacket((PlayerTeam) team.getPlatformTeam()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateTeam(@NonNull Team team) {
|
||||
updateTeamProperties(team);
|
||||
sendPacket(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket((PlayerTeam) team.getPlatformTeam(), false));
|
||||
}
|
||||
|
||||
private void updateTeamProperties(@NonNull Team team) {
|
||||
PlayerTeam t = (PlayerTeam) team.getPlatformTeam();
|
||||
t.setAllowFriendlyFire((team.getOptions() & 0x01) != 0);
|
||||
t.setSeeFriendlyInvisibles((team.getOptions() & 0x02) != 0);
|
||||
t.setColor(formats[team.getColor().getLegacyColor().ordinal()]);
|
||||
t.setCollisionRule(collisions[team.getCollision().ordinal()]);
|
||||
t.setNameTagVisibility(visibilities[team.getVisibility().ordinal()]);
|
||||
t.setPlayerPrefix(team.getPrefix().convert());
|
||||
t.setPlayerSuffix(team.getSuffix().convert());
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void onPacketSend(@NonNull Object packet) {
|
||||
if (packet instanceof ClientboundSetDisplayObjectivePacket display) {
|
||||
TAB.getInstance().getFeatureManager().onDisplayObjective(player, display.getSlot().ordinal(), display.getObjectiveName());
|
||||
}
|
||||
if (packet instanceof ClientboundSetObjectivePacket objective) {
|
||||
TAB.getInstance().getFeatureManager().onObjective(player, objective.getMethod(), objective.getObjectiveName());
|
||||
}
|
||||
if (packet instanceof ClientboundSetPlayerTeamPacket team) {
|
||||
int action = getMethod(team);
|
||||
if (action == TeamAction.UPDATE) return;
|
||||
players.set(team, onTeamPacket(action, team.getName(), team.getPlayers() == null ? Collections.emptyList() : team.getPlayers()));
|
||||
}
|
||||
}
|
||||
|
||||
private int getMethod(@NonNull ClientboundSetPlayerTeamPacket team) {
|
||||
if (team.getTeamAction() == ClientboundSetPlayerTeamPacket.Action.ADD) {
|
||||
return 0;
|
||||
} else if (team.getTeamAction() == ClientboundSetPlayerTeamPacket.Action.REMOVE) {
|
||||
return 1;
|
||||
} else if (team.getPlayerAction() == ClientboundSetPlayerTeamPacket.Action.ADD) {
|
||||
return 3;
|
||||
} else if (team.getPlayerAction() == ClientboundSetPlayerTeamPacket.Action.REMOVE) {
|
||||
return 4;
|
||||
} else {
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the packet to the player.
|
||||
*
|
||||
* @param packet
|
||||
* Packet to send
|
||||
*/
|
||||
private void sendPacket(@NotNull Packet<?> packet) {
|
||||
((CraftPlayer)player.getPlayer()).getHandle().connection.send(packet);
|
||||
}
|
||||
}
|
||||
+2
-10
@@ -1,4 +1,4 @@
|
||||
package me.neznamy.tab.platforms.paper;
|
||||
package me.neznamy.tab.platforms.paper_1_21_4;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import com.mojang.authlib.properties.Property;
|
||||
@@ -36,15 +36,7 @@ public class PaperPacketTabList extends TrackedTabList<BukkitTabPlayer> {
|
||||
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateListOrder = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LIST_ORDER);
|
||||
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateHat = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_HAT);
|
||||
|
||||
private static final Field entries;
|
||||
|
||||
static {
|
||||
try {
|
||||
entries = ReflectionUtils.setAccessible(ClientboundPlayerInfoUpdatePacket.class.getDeclaredField("entries"));
|
||||
} catch (NoSuchFieldException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
private static final Field entries = ReflectionUtils.getOnlyField(ClientboundPlayerInfoUpdatePacket.class, List.class);
|
||||
|
||||
/**
|
||||
* Constructs new instance.
|
||||
@@ -58,8 +58,9 @@ public class BukkitEventListener implements EventListener<Player>, Listener {
|
||||
* @param e
|
||||
* Command event
|
||||
*/
|
||||
@EventHandler
|
||||
@EventHandler(priority = EventPriority.HIGH) // HIGH sounds about fair
|
||||
public void onCommand(PlayerCommandPreprocessEvent e) {
|
||||
if (e.isCancelled()) return;
|
||||
if (command(e.getPlayer().getUniqueId(), e.getMessage())) e.setCancelled(true);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
package me.neznamy.tab.platforms.bukkit;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import lombok.Setter;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.platform.BukkitPlatform;
|
||||
import me.neznamy.tab.shared.features.injection.NettyPipelineInjector;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import me.neznamy.tab.shared.util.function.FunctionWithException;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
@@ -13,10 +12,6 @@ import org.jetbrains.annotations.NotNull;
|
||||
*/
|
||||
public class BukkitPipelineInjector extends NettyPipelineInjector {
|
||||
|
||||
/** Function for getting player's channel */
|
||||
@Setter
|
||||
private static FunctionWithException<BukkitTabPlayer, Channel> getChannel;
|
||||
|
||||
/**
|
||||
* Constructs new instance
|
||||
*/
|
||||
@@ -24,19 +19,10 @@ public class BukkitPipelineInjector extends NettyPipelineInjector {
|
||||
super("packet_handler");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if pipeline injection is available, {@code false} if not.
|
||||
*
|
||||
* @return {@code true} if pipeline injection is available, {@code false} if not
|
||||
*/
|
||||
public static boolean isAvailable() {
|
||||
return getChannel != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
@SneakyThrows
|
||||
protected Channel getChannel(@NotNull TabPlayer player) {
|
||||
return getChannel.apply((BukkitTabPlayer) player);
|
||||
return ((BukkitPlatform)player.getPlatform()).getImplementationProvider().getChannelFunction().apply((BukkitTabPlayer) player);
|
||||
}
|
||||
}
|
||||
+6
-33
@@ -1,9 +1,8 @@
|
||||
package me.neznamy.tab.platforms.bukkit.nms;
|
||||
package me.neznamy.tab.platforms.bukkit;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.shared.util.function.FunctionWithException;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -31,18 +30,11 @@ public class BukkitReflection {
|
||||
@Getter
|
||||
private static final boolean is1_20_2Plus = ReflectionUtils.classExists("net.minecraft.world.scores.DisplaySlot");
|
||||
|
||||
/** Flag determining whether the server version is at least 1.20.3 or not */
|
||||
@Getter
|
||||
private static final boolean is1_20_3Plus = ReflectionUtils.classExists("net.minecraft.network.protocol.game.ClientboundResetScorePacket");
|
||||
|
||||
/** Flag determining whether the server version is at least 1.21.4 or not */
|
||||
@Getter
|
||||
private static final boolean is1_21_4Plus = serverVersion.minorVersion >= 21 &&
|
||||
ReflectionUtils.getFields(getClass("network.chat.Style", "network.chat.ChatModifier"), Integer.class).size() == 1;
|
||||
|
||||
@Getter
|
||||
private static final boolean mojangMapped = ReflectionUtils.classExists("org.bukkit.craftbukkit.CraftServer");
|
||||
|
||||
@NotNull
|
||||
@SneakyThrows
|
||||
private static Method getHandle() {
|
||||
@@ -50,37 +42,19 @@ public class BukkitReflection {
|
||||
}
|
||||
|
||||
private static ServerVersion detectServerVersion() {
|
||||
FunctionWithException<String, Class<?>> classFunction = name -> Class.forName("net.minecraft." + name);
|
||||
String[] array = CRAFTBUKKIT_PACKAGE.split("\\.");
|
||||
int minorVersion;
|
||||
if (array.length > 3) {
|
||||
// Normal packaging
|
||||
String serverPackage = array[3];
|
||||
minorVersion = Integer.parseInt(serverPackage.split("_")[1]);
|
||||
if (minorVersion < 17) {
|
||||
ClassLoader loader = BukkitReflection.class.getClassLoader();
|
||||
classFunction = name -> loader.loadClass("net.minecraft.server." + serverPackage + "." + name);
|
||||
}
|
||||
return new ServerVersion(classFunction, minorVersion, serverPackage);
|
||||
return new ServerVersion(Integer.parseInt(array[3].split("_")[1]), array[3]);
|
||||
} else {
|
||||
// Paper without CB relocation
|
||||
minorVersion = Integer.parseInt(Bukkit.getBukkitVersion().split("-")[0].split("\\.")[1]);
|
||||
return new ServerVersion(classFunction, minorVersion, null);
|
||||
return new ServerVersion(Integer.parseInt(Bukkit.getBukkitVersion().split("-")[0].split("\\.")[1]), null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns server's minor version, such as 20 for 1.20.4
|
||||
*
|
||||
* @return server's minor version
|
||||
*/
|
||||
public static int getMinorVersion() {
|
||||
return serverVersion.getMinorVersion();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns class with given potential names in same order. For 1.17+ it takes packaged class names
|
||||
* without "net.minecraft." prefix, for <1.17 it takes class name only.
|
||||
* Returns class with given potential names in same order. It takes packaged class names
|
||||
* without "net.minecraft." prefix.
|
||||
*
|
||||
* @param names
|
||||
* possible class names
|
||||
@@ -92,7 +66,7 @@ public class BukkitReflection {
|
||||
public static Class<?> getClass(@NotNull String... names) {
|
||||
for (String name : names) {
|
||||
try {
|
||||
return serverVersion.getClass.apply(name);
|
||||
return Class.forName("net.minecraft." + name);
|
||||
} catch (ClassNotFoundException | NullPointerException ignored) {
|
||||
// not the first class name in array
|
||||
}
|
||||
@@ -107,7 +81,6 @@ public class BukkitReflection {
|
||||
@Getter
|
||||
public static class ServerVersion {
|
||||
|
||||
private final FunctionWithException<String, Class<?>> getClass;
|
||||
private final int minorVersion;
|
||||
|
||||
@Nullable
|
||||
@@ -4,8 +4,6 @@ import lombok.Getter;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import me.neznamy.tab.platforms.bukkit.hook.LibsDisguisesHook;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.PingRetriever;
|
||||
import me.neznamy.tab.platforms.bukkit.platform.BukkitPlatform;
|
||||
import me.neznamy.tab.shared.backend.BackendTabPlayer;
|
||||
import org.bukkit.entity.Player;
|
||||
@@ -50,7 +48,7 @@ public class BukkitTabPlayer extends BackendTabPlayer {
|
||||
|
||||
@Override
|
||||
public int getPing() {
|
||||
return PingRetriever.getPing(this);
|
||||
return getPlatform().getImplementationProvider().getPing(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Class containing static util methods used on Bukkit.
|
||||
*/
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public class BukkitUtils {
|
||||
|
||||
/** Whether compatibility exceptions should be printed or not, enabling when adding support for new versions */
|
||||
public static final boolean PRINT_EXCEPTIONS = false;
|
||||
|
||||
private static boolean compatibilityIssue;
|
||||
|
||||
/**
|
||||
* Prints a console warn that some compatibility issue was found.
|
||||
*
|
||||
* @param exception
|
||||
* Exception thrown when initializing fields, if applicable
|
||||
* @param failedCheck
|
||||
* Check that failed
|
||||
* @param fallback
|
||||
* Fallback implementation, if available
|
||||
* @param missingFeatures
|
||||
* Features that will be broken because of the incompatibility
|
||||
*/
|
||||
public static void compatibilityError(@NotNull Exception exception, @NotNull String failedCheck,
|
||||
@Nullable String fallback, @NotNull String... missingFeatures) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("§c[TAB] Failed to initialize minecraft fields for ");
|
||||
sb.append(failedCheck);
|
||||
sb.append(" due to a compatibility error. ");
|
||||
if (fallback != null) {
|
||||
sb.append("Using fallback solution using ").append(fallback).append(". ");
|
||||
} else {
|
||||
sb.append("No fallback solution was found. ");
|
||||
}
|
||||
if (missingFeatures.length > 0) sb.append("This will result in: ");
|
||||
for (int i=0; i<missingFeatures.length; i++) {
|
||||
sb.append("\n").append("#").append(i + 1).append(": ").append(missingFeatures[i]);
|
||||
}
|
||||
Bukkit.getConsoleSender().sendMessage(sb.toString());
|
||||
if (PRINT_EXCEPTIONS) exception.printStackTrace();
|
||||
compatibilityIssue = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a message asking user to update the plugin if some compatibility issue was found.
|
||||
*/
|
||||
public static void sendCompatibilityMessage() {
|
||||
if (!compatibilityIssue) return;
|
||||
Bukkit.getConsoleSender().sendMessage("§c[TAB] Please update the plugin to " +
|
||||
"a version with native support for your server version for optimal experience. This plugin version " +
|
||||
"was made for " + ProtocolVersion.V1_5.getFriendlyName() + " - " + ProtocolVersion.LATEST_KNOWN_VERSION.getFriendlyName() + ".");
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,13 @@
|
||||
package me.neznamy.tab.platforms.bukkit.bossbar;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
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.shared.platform.decorators.SafeBossBar;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.boss.BossBar;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -17,8 +19,12 @@ import org.jetbrains.annotations.NotNull;
|
||||
@RequiredArgsConstructor
|
||||
public class BukkitBossBar extends SafeBossBar<BossBar> {
|
||||
|
||||
/** Flag indicating whether this class is available or not */
|
||||
@Getter
|
||||
private static final boolean available = ReflectionUtils.classExists("org.bukkit.boss.BossBar");
|
||||
|
||||
/** Style array because names do not match */
|
||||
private static final org.bukkit.boss.BarStyle[] styles = org.bukkit.boss.BarStyle.values();
|
||||
private static final org.bukkit.boss.BarStyle[] styles = available ? org.bukkit.boss.BarStyle.values() : null;
|
||||
|
||||
/** Player this handler belongs to */
|
||||
@NotNull
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.header;
|
||||
|
||||
import lombok.Getter;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Header/footer sender using Bukkit API. Despite this feature getting
|
||||
* added into the game in 1.8, Bukkit team took 4 entire years to add
|
||||
* it to the API, which eventually made it there in 1.13. <p>
|
||||
* The input is just a String, meaning Bukkit will have to deserialize it
|
||||
* into components, which is a massive performance loss. It also does not
|
||||
* seem to support fonts.
|
||||
*/
|
||||
public class BukkitHeaderFooter extends HeaderFooter {
|
||||
|
||||
/** Flag tracking whether this class can be used on current server */
|
||||
@Getter
|
||||
private static final boolean available = ReflectionUtils.methodExists(Player.class, "setPlayerListHeaderFooter", String.class, String.class);
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("deprecation") // Marked as deprecated by Paper to make us use their methods instead
|
||||
public void set(@NotNull BukkitTabPlayer player, @NotNull TabComponent header, @NotNull TabComponent footer) {
|
||||
player.getPlayer().setPlayerListHeaderFooter(
|
||||
player.getPlatform().toBukkitFormat(header),
|
||||
player.getPlatform().toBukkitFormat(footer)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.header;
|
||||
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* An empty implementation that does not do anything.
|
||||
*/
|
||||
public class DummyHeaderFooter extends HeaderFooter {
|
||||
|
||||
@Override
|
||||
public void set(@NotNull BukkitTabPlayer player, @NotNull TabComponent header, @NotNull TabComponent footer) {
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.header;
|
||||
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* A class responsible for sending header/footer to players on request.
|
||||
*/
|
||||
public abstract class HeaderFooter {
|
||||
|
||||
/**
|
||||
* Sends header/footer to player.
|
||||
*
|
||||
* @param player
|
||||
* Player to send header/footer to.
|
||||
* @param header
|
||||
* Header to use.
|
||||
* @param footer
|
||||
* Footer to use.
|
||||
*/
|
||||
public abstract void set(@NotNull BukkitTabPlayer player, @NotNull TabComponent header, @NotNull TabComponent footer);
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.header;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.PacketSender;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import me.neznamy.tab.shared.util.function.BiFunctionWithException;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
/**
|
||||
* Header/footer sender that uses NMS to send packets. Available
|
||||
* on all versions since 1.8 when the feature was added into the game.
|
||||
*/
|
||||
public class PacketHeaderFooter extends HeaderFooter {
|
||||
|
||||
private final PacketSender packetSender = new PacketSender();
|
||||
private final BiFunctionWithException<Object, Object, Object> createPacket;
|
||||
|
||||
/**
|
||||
* Constructs new instance and loads all NMS content. Throws exception if something went wrong.
|
||||
*
|
||||
* @throws ReflectiveOperationException
|
||||
* If something went wrong
|
||||
*/
|
||||
public PacketHeaderFooter() throws ReflectiveOperationException {
|
||||
Class<?> Component = BukkitReflection.getClass("network.chat.Component", "network.chat.IChatBaseComponent", "IChatBaseComponent");
|
||||
Class<?> HeaderFooterClass = BukkitReflection.getClass("network.protocol.game.ClientboundTabListPacket",
|
||||
"network.protocol.game.PacketPlayOutPlayerListHeaderFooter", "PacketPlayOutPlayerListHeaderFooter");
|
||||
if (BukkitReflection.getMinorVersion() >= 17) {
|
||||
Constructor<?> newHeaderFooter = HeaderFooterClass.getConstructor(Component, Component);
|
||||
createPacket = newHeaderFooter::newInstance;
|
||||
} else {
|
||||
Constructor<?> newHeaderFooter = HeaderFooterClass.getConstructor();
|
||||
Field HEADER = ReflectionUtils.getFields(HeaderFooterClass, Component).get(0);
|
||||
Field FOOTER = ReflectionUtils.getFields(HeaderFooterClass, Component).get(1);
|
||||
createPacket = (header, footer) -> {
|
||||
Object packet = newHeaderFooter.newInstance();
|
||||
HEADER.set(packet, header);
|
||||
FOOTER.set(packet, footer);
|
||||
return packet;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void set(@NotNull BukkitTabPlayer player, @NotNull TabComponent header, @NotNull TabComponent footer) {
|
||||
packetSender.sendPacket(player, createPacket.apply(header.convert(), footer.convert()));
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.header;
|
||||
|
||||
import lombok.Getter;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Header/footer sender using Paper's API added in 1.16.5.
|
||||
*/
|
||||
public class PaperHeaderFooter extends HeaderFooter {
|
||||
|
||||
/** Flag tracking whether this class can be used on current server */
|
||||
@Getter
|
||||
private static final boolean available = ReflectionUtils.classExists("net.kyori.adventure.text.Component") &&
|
||||
ReflectionUtils.methodExists(Player.class, "sendPlayerListHeaderAndFooter", Component.class, Component.class);
|
||||
|
||||
@Override
|
||||
public void set(@NotNull BukkitTabPlayer player, @NotNull TabComponent header, @NotNull TabComponent footer) {
|
||||
player.getPlayer().sendPlayerListHeaderAndFooter(header.toAdventure(), footer.toAdventure());
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.nms;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitUtils;
|
||||
import me.neznamy.tab.shared.util.function.ToIntFunction;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
/**
|
||||
* Class for retrieving ping of players.
|
||||
*/
|
||||
public class PingRetriever {
|
||||
|
||||
/** Ping getter function */
|
||||
private static ToIntFunction<BukkitTabPlayer> getPing;
|
||||
|
||||
/**
|
||||
* Attempts to load required classes, fields and methods and marks class as available.
|
||||
* If something fails, error message is printed and class is not marked as available.
|
||||
*/
|
||||
public static void tryLoad() {
|
||||
try {
|
||||
if (ReflectionUtils.methodExists(Player.class, "getPing")) {
|
||||
getPing = p -> p.getPlayer().getPing();
|
||||
} else {
|
||||
Class<?> EntityPlayer = BukkitReflection.getClass("server.level.ServerPlayer", "server.level.EntityPlayer", "EntityPlayer");
|
||||
Field PING = ReflectionUtils.getField(EntityPlayer, "ping", "field_71138_i"); // 1.5.2 - 1.16.5, 1.7.10 Thermos
|
||||
getPing = player -> PING.getInt(player.getHandle());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
getPing = p -> -1;
|
||||
BukkitUtils.compatibilityError(e, "getting player's ping", null, "%ping% returning -1");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns player's ping. If ping getter is not available and fields
|
||||
* failed to load, returns {@code -1}. If an exception was throws by
|
||||
* reflective operation, it is re-thrown.
|
||||
*
|
||||
* @param player
|
||||
* Player to get ping of
|
||||
* @return Player's ping or {@code -1} if it failed
|
||||
*/
|
||||
@SneakyThrows
|
||||
public static int getPing(@NotNull BukkitTabPlayer player) {
|
||||
return getPing.apply(player);
|
||||
}
|
||||
}
|
||||
-91
@@ -1,91 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.nms.converter;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.chat.ChatModifier;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Class for converting TAB components into NMS components for versions 1.7 - 1.15.2.
|
||||
*/
|
||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
public class LegacyComponentConverter extends ComponentConverter {
|
||||
|
||||
private final Class<?> IChatBaseComponent = BukkitReflection.getClass("IChatBaseComponent");
|
||||
|
||||
private final Constructor<?> newTextComponent = BukkitReflection.getClass("ChatComponentText").getConstructor(String.class);
|
||||
private final Constructor<?> newTranslatableComponent = BukkitReflection.getClass("ChatMessage").getConstructor(String.class, Object[].class);
|
||||
private Constructor<?> newKeybindComponent;
|
||||
|
||||
private final Class<?> ChatModifierClass = BukkitReflection.getClass("ChatModifier");
|
||||
private final Constructor<?> newChatModifier = ChatModifierClass.getConstructor();
|
||||
private final Class<Enum> EnumChatFormat = (Class<Enum>) BukkitReflection.getClass("ChatFormatting", "EnumChatFormat");
|
||||
private final Class<?> ChatBaseComponent = BukkitReflection.getClass("ChatBaseComponent");
|
||||
private final Method ChatBaseComponent_addSibling = ReflectionUtils.getOnlyMethod(ChatBaseComponent, IChatBaseComponent, IChatBaseComponent);
|
||||
private final Field Component_modifier = ReflectionUtils.getOnlyField(ChatBaseComponent, ChatModifierClass);
|
||||
private final List<Field> magicCodes = ReflectionUtils.getFields(ChatModifierClass, Boolean.class);
|
||||
private final Method ChatModifier_setColor = ReflectionUtils.getOnlyMethod(ChatModifierClass, ChatModifierClass, EnumChatFormat);
|
||||
|
||||
/**
|
||||
* Constructs new instance and loads all NMS classes, constructors, and methods.
|
||||
*
|
||||
* @throws ReflectiveOperationException
|
||||
* If something failed
|
||||
*/
|
||||
public LegacyComponentConverter() throws ReflectiveOperationException {
|
||||
if (BukkitReflection.getMinorVersion() >= 12) {
|
||||
newKeybindComponent = BukkitReflection.getClass("ChatComponentKeybind").getConstructor(String.class);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
@SneakyThrows
|
||||
public Object newTextComponent(@NotNull String text) {
|
||||
return newTextComponent.newInstance(text);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
@SneakyThrows
|
||||
public Object newTranslatableComponent(@NotNull String key) {
|
||||
return newTranslatableComponent.newInstance(key, new Object[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
@SneakyThrows
|
||||
public Object newKeybindComponent(@NotNull String keybind) {
|
||||
if (BukkitReflection.getMinorVersion() >= 12) {
|
||||
return newKeybindComponent.newInstance(keybind);
|
||||
}
|
||||
throw new UnsupportedOperationException("Keybind components were added in 1.12");
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void applyStyle(@NotNull Object nmsComponent, @NotNull ChatModifier style) {
|
||||
Object nmsModifier = newChatModifier.newInstance();
|
||||
if (style.getColor() != null) {
|
||||
ChatModifier_setColor.invoke(nmsModifier, Enum.valueOf(EnumChatFormat, style.getColor().getLegacyColor().name()));
|
||||
}
|
||||
magicCodes.get(0).set(nmsModifier, style.getBold());
|
||||
magicCodes.get(1).set(nmsModifier, style.getItalic());
|
||||
magicCodes.get(2).set(nmsModifier, style.getStrikethrough());
|
||||
magicCodes.get(3).set(nmsModifier, style.getUnderlined());
|
||||
magicCodes.get(4).set(nmsModifier, style.getObfuscated());
|
||||
Component_modifier.set(nmsComponent, nmsModifier);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void addSibling(@NotNull Object parent, @NotNull Object child) {
|
||||
ChatBaseComponent_addSibling.invoke(parent, child);
|
||||
}
|
||||
}
|
||||
-94
@@ -1,94 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.nms.converter;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.chat.ChatModifier;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* Class for converting TAB components into NMS components for versions 1.16 - 1.18.2.
|
||||
*/
|
||||
public class ModerateComponentConverter extends ComponentConverter {
|
||||
|
||||
private final Class<?> IChatBaseComponent = BukkitReflection.getClass("network.chat.Component", "network.chat.IChatBaseComponent", "IChatBaseComponent");
|
||||
private final Class<?> ChatBaseComponent = BukkitReflection.getClass("network.chat.BaseComponent", "network.chat.ChatBaseComponent", "ChatBaseComponent");
|
||||
private final Class<?> TextColor = BukkitReflection.getClass("network.chat.TextColor", "network.chat.ChatHexColor", "ChatHexColor");
|
||||
private final Class<?> ResourceLocation = BukkitReflection.getClass("resources.ResourceLocation", "resources.MinecraftKey", "MinecraftKey");
|
||||
|
||||
private final Constructor<?> newTextComponent = BukkitReflection.getClass("network.chat.TextComponent", "network.chat.ChatComponentText", "ChatComponentText").getConstructor(String.class);
|
||||
private final Constructor<?> newTranslatableComponent = BukkitReflection.getClass("network.chat.TranslatableComponent", "network.chat.ChatMessage", "ChatMessage").getConstructor(String.class, Object[].class);
|
||||
private final Constructor<?> newKeybindComponent = BukkitReflection.getClass("network.chat.KeybindComponent", "network.chat.ChatComponentKeybind", "ChatComponentKeybind").getConstructor(String.class);
|
||||
|
||||
private final Class<?> ChatModifierClass = BukkitReflection.getClass("network.chat.Style", "network.chat.ChatModifier", "ChatModifier");
|
||||
private final Constructor<?> newChatModifier = ReflectionUtils.setAccessible(ChatModifierClass.getDeclaredConstructor(
|
||||
TextColor,
|
||||
Boolean.class, Boolean.class, Boolean.class, Boolean.class, Boolean.class,
|
||||
BukkitReflection.getClass("network.chat.ClickEvent", "network.chat.ChatClickable", "ChatClickable"),
|
||||
BukkitReflection.getClass("network.chat.HoverEvent", "network.chat.ChatHoverable", "ChatHoverable"),
|
||||
String.class,
|
||||
ResourceLocation
|
||||
));
|
||||
private final Method ChatBaseComponent_addSibling = ReflectionUtils.getOnlyMethod(ChatBaseComponent, IChatBaseComponent, IChatBaseComponent);
|
||||
private final Field Component_modifier = ReflectionUtils.getOnlyField(ChatBaseComponent, ChatModifierClass);
|
||||
|
||||
private final Method ChatHexColor_fromRGB = ReflectionUtils.getMethods(TextColor, TextColor, int.class).get(0); // There should only be 1, but some mods add more
|
||||
private final Method ResourceLocation_tryParse = ReflectionUtils.getMethod(ResourceLocation, new String[]{"tryParse", "m_135820_", "a"}, String.class);
|
||||
|
||||
/**
|
||||
* Constructs new instance and loads all NMS classes, constructors, and methods.
|
||||
*
|
||||
* @throws ReflectiveOperationException
|
||||
* If something failed
|
||||
*/
|
||||
public ModerateComponentConverter() throws ReflectiveOperationException {
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
@SneakyThrows
|
||||
public Object newTextComponent(@NotNull String text) {
|
||||
return newTextComponent.newInstance(text);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
@SneakyThrows
|
||||
public Object newTranslatableComponent(@NotNull String key) {
|
||||
return newTranslatableComponent.newInstance(key, new Object[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
@SneakyThrows
|
||||
public Object newKeybindComponent(@NotNull String keybind) {
|
||||
return newKeybindComponent.newInstance(keybind);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void applyStyle(@NotNull Object nmsComponent, @NotNull ChatModifier style) {
|
||||
Component_modifier.set(nmsComponent, newChatModifier.newInstance(
|
||||
style.getColor() == null ? null : ChatHexColor_fromRGB.invoke(null, style.getColor().getRgb()),
|
||||
style.getBold(),
|
||||
style.getItalic(),
|
||||
style.getUnderlined(),
|
||||
style.getStrikethrough(),
|
||||
style.getObfuscated(),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
style.getFont() == null ? null : ResourceLocation_tryParse.invoke(null, style.getFont())
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void addSibling(@NotNull Object parent, @NotNull Object child) {
|
||||
ChatBaseComponent_addSibling.invoke(parent, child);
|
||||
}
|
||||
}
|
||||
+105
-77
@@ -1,6 +1,7 @@
|
||||
package me.neznamy.tab.platforms.bukkit.platform;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.SneakyThrows;
|
||||
import me.clip.placeholderapi.PlaceholderAPI;
|
||||
import me.neznamy.chat.component.*;
|
||||
@@ -9,13 +10,11 @@ import me.neznamy.tab.platforms.bukkit.bossbar.BukkitBossBar;
|
||||
import me.neznamy.tab.platforms.bukkit.bossbar.ViaBossBar;
|
||||
import me.neznamy.tab.platforms.bukkit.features.BukkitTabExpansion;
|
||||
import me.neznamy.tab.platforms.bukkit.features.PerWorldPlayerList;
|
||||
import me.neznamy.tab.platforms.bukkit.header.*;
|
||||
import me.neznamy.tab.platforms.bukkit.hook.BukkitPremiumVanishHook;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.PingRetriever;
|
||||
import me.neznamy.tab.platforms.bukkit.provider.BukkitImplementationProvider;
|
||||
import me.neznamy.tab.platforms.bukkit.provider.ImplementationProvider;
|
||||
import me.neznamy.tab.platforms.bukkit.provider.ViaVersionImplementationProvider;
|
||||
import me.neznamy.tab.platforms.bukkit.provider.bukkit.BukkitImplementationProvider;
|
||||
import me.neznamy.tab.platforms.bukkit.provider.bukkit.PaperScoreboard;
|
||||
import me.neznamy.tab.platforms.bukkit.provider.reflection.ReflectionImplementationProvider;
|
||||
import me.neznamy.tab.shared.GroupManager;
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
@@ -52,9 +51,10 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Objects;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Implementation of Platform interface for Bukkit platform
|
||||
@@ -67,7 +67,6 @@ public class BukkitPlatform implements BackendPlatform {
|
||||
private final JavaPlugin plugin;
|
||||
|
||||
/** Server version */
|
||||
@Getter
|
||||
private final ProtocolVersion serverVersion = ProtocolVersion.fromFriendlyName(Bukkit.getBukkitVersion().split("-")[0]);
|
||||
|
||||
/** Variables checking presence of other plugins to hook into */
|
||||
@@ -82,24 +81,12 @@ public class BukkitPlatform implements BackendPlatform {
|
||||
/** Detection for presence of Paper's MSPT getter */
|
||||
private final boolean paperMspt = ReflectionUtils.methodExists(Bukkit.class, "getAverageTickTime");
|
||||
|
||||
/** Header/footer implementation */
|
||||
@Getter
|
||||
@NotNull
|
||||
private final HeaderFooter headerFooter;
|
||||
|
||||
/** Flag tracking if direct mojang-mapped code can be used or not */
|
||||
private final boolean canUseDirectNMS = BukkitReflection.isMojangMapped() &&
|
||||
(serverVersion == ProtocolVersion.V1_21_4 || serverVersion == ProtocolVersion.V1_21_5);
|
||||
|
||||
/** Implementation for creating new instances using content available on the server */
|
||||
@NotNull
|
||||
private final ImplementationProvider serverImplementationProvider;
|
||||
@Setter
|
||||
private ImplementationProvider implementationProvider = findImplementationProvider();
|
||||
|
||||
/** Implementation for sending new content to new players on old servers */
|
||||
@Nullable
|
||||
private final ViaVersionImplementationProvider viaVersionImplementationProvider =
|
||||
ReflectionUtils.classExists("com.viaversion.viaversion.protocols.v1_20_2to1_20_3.Protocol1_20_2To1_20_3") ?
|
||||
new ViaVersionImplementationProvider(serverVersion) : null;
|
||||
private final boolean modernOnlinePlayers;
|
||||
|
||||
/**
|
||||
* Constructs new instance with given plugin.
|
||||
@@ -110,7 +97,7 @@ public class BukkitPlatform implements BackendPlatform {
|
||||
@SneakyThrows
|
||||
public BukkitPlatform(@NotNull JavaPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
long time = System.currentTimeMillis();
|
||||
modernOnlinePlayers = Bukkit.class.getMethod("getOnlinePlayers").getReturnType() == Collection.class;
|
||||
try {
|
||||
Object server = Bukkit.getServer().getClass().getMethod("getServer").invoke(Bukkit.getServer());
|
||||
recentTps = ((double[]) server.getClass().getField("recentTps").get(server));
|
||||
@@ -120,42 +107,87 @@ public class BukkitPlatform implements BackendPlatform {
|
||||
if (Bukkit.getPluginManager().isPluginEnabled("PremiumVanish")) {
|
||||
new BukkitPremiumVanishHook().register();
|
||||
}
|
||||
if (canUseDirectNMS) {
|
||||
serverImplementationProvider = (ImplementationProvider) Class.forName("me.neznamy.tab.platforms.paper.PaperImplementationProvider").getConstructor().newInstance();
|
||||
} else if ("v1_12_R1".equals(BukkitReflection.getServerVersion().getServerPackage())) {
|
||||
serverImplementationProvider = (ImplementationProvider) Class.forName("me.neznamy.tab.platforms.bukkit.v1_12_R1.NMSImplementationProvider").getConstructor().newInstance();
|
||||
} else if ("v1_8_R3".equals(BukkitReflection.getServerVersion().getServerPackage())) {
|
||||
serverImplementationProvider = (ImplementationProvider) Class.forName("me.neznamy.tab.platforms.bukkit.v1_8_R3.NMSImplementationProvider").getConstructor().newInstance();
|
||||
} else {
|
||||
serverImplementationProvider = new BukkitImplementationProvider();
|
||||
}
|
||||
headerFooter = findHeaderFooter();
|
||||
PingRetriever.tryLoad();
|
||||
BukkitPipelineInjector.setGetChannel(serverImplementationProvider.getChannelFunction());
|
||||
BukkitUtils.sendCompatibilityMessage();
|
||||
Bukkit.getConsoleSender().sendMessage("[TAB] §7Loaded NMS hook in " + (System.currentTimeMillis()-time) + "ms");
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private HeaderFooter findHeaderFooter() {
|
||||
if (BukkitReflection.getMinorVersion() >= 8) {
|
||||
try {
|
||||
Objects.requireNonNull(serverImplementationProvider.getComponentConverter());
|
||||
return new PacketHeaderFooter();
|
||||
} catch (Exception e) {
|
||||
if (PaperHeaderFooter.isAvailable()) return new PaperHeaderFooter();
|
||||
if (BukkitHeaderFooter.isAvailable()) {
|
||||
BukkitUtils.compatibilityError(e, "sending Header/Footer", "Bukkit API",
|
||||
"Header/Footer having drastically increased CPU usage",
|
||||
"Header/Footer not supporting fonts (1.16+)");
|
||||
return new BukkitHeaderFooter();
|
||||
} else {
|
||||
BukkitUtils.compatibilityError(e, "sending Header/Footer", null,
|
||||
"Header/Footer feature not working");
|
||||
}
|
||||
}
|
||||
@SneakyThrows
|
||||
private ImplementationProvider findImplementationProvider() {
|
||||
// Check for mojang-mapped paper (1.20.5+)
|
||||
String paperModule = getPaperModule();
|
||||
if (paperModule != null) {
|
||||
return (ImplementationProvider) Class.forName("me.neznamy.tab.platforms.paper_" + paperModule + ".PaperImplementationProvider").getConstructor().newInstance();
|
||||
}
|
||||
|
||||
// Check for direct NMS on some supported versions
|
||||
String serverPackage = BukkitReflection.getServerVersion().getServerPackage();
|
||||
if (Arrays.asList("v1_8_R3", "v1_12_R1", "v1_16_R3", "v1_17_R1", "v1_18_R2", "v1_19_R1").contains(serverPackage) && serverVersion != ProtocolVersion.V1_19) {
|
||||
return (ImplementationProvider) Class.forName("me.neznamy.tab.platforms.bukkit." + serverPackage + ".NMSImplementationProvider").getConstructor().newInstance();
|
||||
}
|
||||
|
||||
// Try reflection
|
||||
try {
|
||||
return new ReflectionImplementationProvider();
|
||||
} catch (Throwable e) {
|
||||
if (serverVersion.getMinorVersion() >= 8) {
|
||||
List<String> missingFeatures = new ArrayList<>();
|
||||
|
||||
// Scoreboard
|
||||
missingFeatures.add("Compatibility with other scoreboard plugins being reduced");
|
||||
if (!PaperScoreboard.isAvailable()) {
|
||||
missingFeatures.add("Features receiving new artificial character limits");
|
||||
missingFeatures.add("1.20.3+ scoreboard visuals not working due to lack of API");
|
||||
}
|
||||
missingFeatures.add("Anti-override for nametags not working");
|
||||
|
||||
// Tablist
|
||||
missingFeatures.add("Layout feature will not work");
|
||||
missingFeatures.add("Prevent-spectator-effect feature will not work");
|
||||
missingFeatures.add("Ping spoof feature will not work");
|
||||
missingFeatures.add("Tablist formatting missing anti-override");
|
||||
missingFeatures.add("Tablist formatting not supporting relational placeholders");
|
||||
missingFeatures.add("Compatibility with nickname plugins changing player names will not work");
|
||||
missingFeatures.add("Anti-override for tablist not working");
|
||||
missingFeatures.add("Header/Footer may be limited or not work at all"); // Maybe add some checks for a more accurate message
|
||||
|
||||
Bukkit.getConsoleSender().sendMessage("§c[TAB] Your server version is not fully supported. This will result in:");
|
||||
for (String message : missingFeatures) {
|
||||
Bukkit.getConsoleSender().sendMessage("§c[TAB] - " + message);
|
||||
}
|
||||
Bukkit.getConsoleSender().sendMessage("§c[TAB] Please use " +
|
||||
"a plugin version with full support for your server version for optimal experience. This plugin version " +
|
||||
"has full support for 1.8.8, 1.12.x, 1.16.5, 1.17.x, 1.18.2 and 1.19.1 - 1.21.7.");
|
||||
}
|
||||
return new BukkitImplementationProvider();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns name of the paper module that can be used on this server.
|
||||
* If this server is not using paper or no module is available for any other reason,
|
||||
* {@code null} is returned.
|
||||
*
|
||||
* @return Name of the available paper module or {@code null} if not available
|
||||
*/
|
||||
@Nullable
|
||||
private String getPaperModule() {
|
||||
if (!ReflectionUtils.classExists("org.bukkit.craftbukkit.CraftServer")) return null;
|
||||
switch (serverVersion) {
|
||||
case V1_20_5:
|
||||
case V1_20_6:
|
||||
case V1_21:
|
||||
case V1_21_1:
|
||||
return "1_20_5";
|
||||
case V1_21_2:
|
||||
case V1_21_3:
|
||||
return "1_21_2";
|
||||
case V1_21_4:
|
||||
case V1_21_5:
|
||||
case V1_21_6:
|
||||
case V1_21_7:
|
||||
return "1_21_4";
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
return new DummyHeaderFooter();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -186,7 +218,7 @@ public class BukkitPlatform implements BackendPlatform {
|
||||
@Override
|
||||
@Nullable
|
||||
public PipelineInjector createPipelineInjector() {
|
||||
return BukkitReflection.getMinorVersion() >= 8 && BukkitPipelineInjector.isAvailable() ? new BukkitPipelineInjector() : null;
|
||||
return implementationProvider.getChannelFunction() != null ? new BukkitPipelineInjector() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -212,7 +244,6 @@ public class BukkitPlatform implements BackendPlatform {
|
||||
registerDummyPlaceholder(identifier);
|
||||
return;
|
||||
}
|
||||
PlaceholderManagerImpl pl = TAB.getInstance().getPlaceholderManager();
|
||||
if (identifier.startsWith("%rel_")) {
|
||||
//relational placeholder
|
||||
TAB.getInstance().getPlaceholderManager().registerRelationalPlaceholder(identifier, (viewer, target) ->
|
||||
@@ -241,7 +272,9 @@ public class BukkitPlatform implements BackendPlatform {
|
||||
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||
long time = System.nanoTime();
|
||||
ppl[0].updateValue(p, placeholderAPI ? PlaceholderAPI.setPlaceholders((Player) p.getPlayer(), syncedPlaceholder) : identifier);
|
||||
TAB.getInstance().getCPUManager().addPlaceholderTime(identifier, System.nanoTime() - time);
|
||||
long totalTime = System.nanoTime()-time;
|
||||
TAB.getInstance().getCPUManager().addPlaceholderTime(identifier, totalTime);
|
||||
TAB.getInstance().getCpu().addTime(TAB.getInstance().getPlaceholderManager().getFeatureName(), TabConstants.CpuUsageCategory.PLACEHOLDER_REQUEST, totalTime);
|
||||
});
|
||||
return null;
|
||||
});
|
||||
@@ -276,7 +309,7 @@ public class BukkitPlatform implements BackendPlatform {
|
||||
command.setExecutor(cmd);
|
||||
command.setTabCompleter(cmd);
|
||||
} else {
|
||||
logWarn(SimpleTextComponent.text("Failed to register command, is it defined in plugin.yml?"));
|
||||
logWarn(TabComponent.legacyText("Failed to register command, is it defined in plugin.yml?"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -286,7 +319,7 @@ public class BukkitPlatform implements BackendPlatform {
|
||||
metrics.addCustomChart(new SimplePie(TabConstants.MetricsChart.PERMISSION_SYSTEM,
|
||||
() -> TAB.getInstance().getGroupManager().getPermissionPlugin()));
|
||||
metrics.addCustomChart(new SimplePie(TabConstants.MetricsChart.SERVER_VERSION,
|
||||
() -> "1." + BukkitReflection.getMinorVersion() + ".x"));
|
||||
() -> "1." + serverVersion.getMinorVersion() + ".x"));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -298,8 +331,8 @@ public class BukkitPlatform implements BackendPlatform {
|
||||
@Override
|
||||
@NotNull
|
||||
public Object convertComponent(@NotNull TabComponent component) {
|
||||
if (serverImplementationProvider.getComponentConverter() != null) {
|
||||
return serverImplementationProvider.getComponentConverter().convert(component);
|
||||
if (implementationProvider.getComponentConverter() != null) {
|
||||
return implementationProvider.getComponentConverter().convert(component);
|
||||
} else {
|
||||
return component;
|
||||
}
|
||||
@@ -307,13 +340,8 @@ public class BukkitPlatform implements BackendPlatform {
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
@SneakyThrows
|
||||
public Scoreboard createScoreboard(@NotNull TabPlayer player) {
|
||||
if (viaVersionImplementationProvider != null) {
|
||||
Scoreboard scoreboard = viaVersionImplementationProvider.newScoreboard((BukkitTabPlayer) player);
|
||||
if (scoreboard != null) return scoreboard;
|
||||
}
|
||||
return serverImplementationProvider.newScoreboard((BukkitTabPlayer) player);
|
||||
return implementationProvider.newScoreboard((BukkitTabPlayer) player);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -323,7 +351,7 @@ public class BukkitPlatform implements BackendPlatform {
|
||||
if (AdventureBossBar.isAvailable() && Audience.class.isAssignableFrom(Player.class)) return new AdventureBossBar(player);
|
||||
|
||||
// 1.9+ server, handle using API, potential 1.8 players are handled by ViaVersion
|
||||
if (BukkitReflection.getMinorVersion() >= 9) return new BukkitBossBar((BukkitTabPlayer) player);
|
||||
if (BukkitBossBar.isAvailable()) return new BukkitBossBar((BukkitTabPlayer) player);
|
||||
|
||||
// 1.9+ player on 1.8 server, handle using ViaVersion API
|
||||
if (player.getVersion().getMinorVersion() >= 9) return new ViaBossBar((BukkitTabPlayer) player);
|
||||
@@ -334,13 +362,8 @@ public class BukkitPlatform implements BackendPlatform {
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
@SneakyThrows
|
||||
public TabList createTabList(@NotNull TabPlayer player) {
|
||||
if (viaVersionImplementationProvider != null) {
|
||||
TabList tabList = viaVersionImplementationProvider.newTabList((BukkitTabPlayer) player);
|
||||
if (tabList != null) return tabList;
|
||||
}
|
||||
return serverImplementationProvider.newTabList((BukkitTabPlayer) player);
|
||||
return implementationProvider.newTabList((BukkitTabPlayer) player);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -348,6 +371,11 @@ public class BukkitPlatform implements BackendPlatform {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsListOrder() {
|
||||
return serverVersion.getNetworkId() >= ProtocolVersion.V1_21_2.getNetworkId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSafeFromPacketEventsBug() {
|
||||
return serverVersion.getMinorVersion() >= 13;
|
||||
@@ -436,7 +464,7 @@ public class BukkitPlatform implements BackendPlatform {
|
||||
|
||||
/**
|
||||
* Returns online players from Bukkit API.
|
||||
* This method may use reflections, because the return type changed in 1.8,
|
||||
* This method may use reflections, because the return type changed in 1.7.10,
|
||||
* and we want to avoid errors.
|
||||
*
|
||||
* @return Online players from Bukkit API.
|
||||
@@ -444,7 +472,7 @@ public class BukkitPlatform implements BackendPlatform {
|
||||
@SneakyThrows
|
||||
@NotNull
|
||||
public Collection<? extends Player> getOnlinePlayers() {
|
||||
if (serverVersion.getMinorVersion() >= 8) {
|
||||
if (modernOnlinePlayers) {
|
||||
return Bukkit.getOnlinePlayers();
|
||||
}
|
||||
return Arrays.asList((Player[]) Bukkit.class.getMethod("getOnlinePlayers").invoke(null));
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
package me.neznamy.tab.platforms.bukkit.platform;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
||||
import me.clip.placeholderapi.PlaceholderAPI;
|
||||
import me.neznamy.tab.platforms.bukkit.features.PerWorldPlayerList;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.chat.component.SimpleTextComponent;
|
||||
import me.neznamy.tab.shared.cpu.TimedCaughtTask;
|
||||
import me.neznamy.tab.shared.placeholders.types.PlayerPlaceholderImpl;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.player.PlayerChangedWorldEvent;
|
||||
@@ -16,7 +17,10 @@ import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.DecimalFormatSymbols;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Platform override for Folia.
|
||||
@@ -37,10 +41,6 @@ public class FoliaPlatform extends BukkitPlatform {
|
||||
public void loadPlayers() {
|
||||
super.loadPlayers();
|
||||
|
||||
// Values are never updated in the API, warn users
|
||||
logWarn(SimpleTextComponent.text("Folia never updates MSPT and TPS values in the API, making " +
|
||||
"%mspt% and %tps% return the default values (0 and 20)."));
|
||||
|
||||
// Folia never calls PlayerChangedWorldEvent, this is a workaround
|
||||
TAB.getInstance().getCpu().getProcessingThread().repeatTask(new TimedCaughtTask(TAB.getInstance().getCpu(), () -> {
|
||||
for (TabPlayer player : TAB.getInstance().getOnlinePlayers()) {
|
||||
@@ -56,6 +56,17 @@ public class FoliaPlatform extends BukkitPlatform {
|
||||
}, "Folia compatibility", "Refreshing world"), 100);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerPlaceholders() {
|
||||
super.registerPlaceholders();
|
||||
DecimalFormat decimal2;
|
||||
DecimalFormatSymbols symbols = new DecimalFormatSymbols();
|
||||
symbols.setDecimalSeparator('.');
|
||||
decimal2 = new DecimalFormat("#.##", symbols);
|
||||
registerInternalSyncPlaceholder(TabConstants.Placeholder.MSPT, 1000, p -> decimal2.format(Bukkit.getAverageTickTime()));
|
||||
registerInternalSyncPlaceholder(TabConstants.Placeholder.TPS, 1000, p -> decimal2.format(Math.min(20, Bukkit.getTPS()[0])));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerSyncPlaceholder(@NotNull String identifier) {
|
||||
String syncedPlaceholder = "%" + identifier.substring(6);
|
||||
@@ -64,13 +75,42 @@ public class FoliaPlatform extends BukkitPlatform {
|
||||
runSync((Entity) p.getPlayer(), () -> {
|
||||
long time = System.nanoTime();
|
||||
String output = isPlaceholderAPI() ? PlaceholderAPI.setPlaceholders((Player) p.getPlayer(), syncedPlaceholder) : identifier;
|
||||
TAB.getInstance().getCPUManager().addPlaceholderTime(identifier, System.nanoTime()-time);
|
||||
long totalTime = System.nanoTime()-time;
|
||||
TAB.getInstance().getCPUManager().addPlaceholderTime(identifier, totalTime);
|
||||
TAB.getInstance().getCpu().addTime(TAB.getInstance().getPlaceholderManager().getFeatureName(), TabConstants.CpuUsageCategory.PLACEHOLDER_REQUEST, totalTime);
|
||||
TAB.getInstance().getCPUManager().runTask(() -> ppl[0].updateValue(p, output)); // To ensure player is loaded
|
||||
});
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
private void registerInternalSyncPlaceholder(@NonNull String identifier, int refresh, @NonNull Function<TabPlayer, String> function) {
|
||||
PlayerPlaceholderImpl[] ppl = new PlayerPlaceholderImpl[1];
|
||||
ppl[0] = TAB.getInstance().getPlaceholderManager().registerInternalPlayerPlaceholder(identifier, refresh, p -> {
|
||||
runSync((Entity) p.getPlayer(), () -> {
|
||||
long time = System.nanoTime();
|
||||
String output = function.apply((TabPlayer) p);
|
||||
long totalTime = System.nanoTime()-time;
|
||||
TAB.getInstance().getCPUManager().addPlaceholderTime(identifier, totalTime);
|
||||
TAB.getInstance().getCpu().addTime(TAB.getInstance().getPlaceholderManager().getFeatureName(), TabConstants.CpuUsageCategory.PLACEHOLDER_REQUEST, totalTime);
|
||||
TAB.getInstance().getCPUManager().runTask(() -> ppl[0].updateValue(p, output)); // To ensure player is loaded
|
||||
});
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Overriding the method to fix initial error caused by ServerPlaceholder implementation trying to
|
||||
* retrieve the value in constructor, but folia does not support that. Overriding this function to avoid the
|
||||
* MSPT function being called in the wrong thread.
|
||||
*
|
||||
* @return -1
|
||||
*/
|
||||
@Override
|
||||
public double getMSPT() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs task using player's entity scheduler. It's using reflection, because
|
||||
* Folia uses Java 17 while TAB maintains Java 8 compatibility for compatibility
|
||||
|
||||
-175
@@ -1,175 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.provider;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import io.netty.channel.Channel;
|
||||
import lombok.Getter;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitUtils;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.converter.ComponentConverter;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.converter.LegacyComponentConverter;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.converter.ModerateComponentConverter;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.converter.ModernComponentConverter;
|
||||
import me.neznamy.tab.platforms.bukkit.scoreboard.BukkitScoreboard;
|
||||
import me.neznamy.tab.platforms.bukkit.scoreboard.PaperScoreboard;
|
||||
import me.neznamy.tab.platforms.bukkit.scoreboard.packet.PacketScoreboard;
|
||||
import me.neznamy.tab.platforms.bukkit.tablist.BukkitTabList;
|
||||
import me.neznamy.tab.platforms.bukkit.tablist.PacketTabList1193;
|
||||
import me.neznamy.tab.platforms.bukkit.tablist.PacketTabList17;
|
||||
import me.neznamy.tab.platforms.bukkit.tablist.PacketTabList18;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import me.neznamy.tab.shared.platform.impl.DummyScoreboard;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import me.neznamy.tab.shared.util.function.FunctionWithException;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Implementation provider using reflection for NMS / Bukkit implementations as fallback options.
|
||||
*/
|
||||
@Getter
|
||||
public class BukkitImplementationProvider implements ImplementationProvider {
|
||||
|
||||
@Nullable
|
||||
private final FunctionWithException<BukkitTabPlayer, Channel> channelFunction = findChannelFunction();
|
||||
|
||||
@Nullable
|
||||
private final ComponentConverter componentConverter = findComponentConverter();
|
||||
|
||||
@NotNull
|
||||
private final Function<BukkitTabPlayer, Scoreboard> scoreboardProvider = findScoreboardProvider();
|
||||
|
||||
@NotNull
|
||||
private final Function<BukkitTabPlayer, TabList> tablistProvider = findTablistProvider();
|
||||
|
||||
@Nullable
|
||||
private FunctionWithException<BukkitTabPlayer, Channel> findChannelFunction() {
|
||||
if (BukkitReflection.getMinorVersion() < 8) return null;
|
||||
try {
|
||||
Class<?> NetworkManager = BukkitReflection.getClass("network.Connection", "network.NetworkManager", "NetworkManager");
|
||||
Class<?> PlayerConnection = BukkitReflection.getClass("server.network.ServerGamePacketListenerImpl",
|
||||
"server.network.PlayerConnection", "PlayerConnection");
|
||||
Class<?> EntityPlayer = BukkitReflection.getClass("server.level.ServerPlayer", "server.level.EntityPlayer", "EntityPlayer");
|
||||
Field PLAYER_CONNECTION = ReflectionUtils.getOnlyField(EntityPlayer, PlayerConnection);
|
||||
Field NETWORK_MANAGER;
|
||||
if (BukkitReflection.is1_20_2Plus()) {
|
||||
NETWORK_MANAGER = ReflectionUtils.getOnlyField(PlayerConnection.getSuperclass(), NetworkManager);
|
||||
} else {
|
||||
NETWORK_MANAGER = ReflectionUtils.getOnlyField(PlayerConnection, NetworkManager);
|
||||
}
|
||||
Field CHANNEL = ReflectionUtils.getOnlyField(NetworkManager, Channel.class);
|
||||
return player -> (Channel) CHANNEL.get(NETWORK_MANAGER.get(PLAYER_CONNECTION.get(player.getHandle())));
|
||||
} catch (Exception e) {
|
||||
BukkitUtils.compatibilityError(e, "network channel injection", null,
|
||||
"Anti-override for tablist & nametags not working",
|
||||
"Compatibility with nickname plugins changing player names will not work",
|
||||
"Scoreboard will not be checking for other plugins");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@SneakyThrows
|
||||
private ComponentConverter findComponentConverter() {
|
||||
try {
|
||||
if (BukkitReflection.getMinorVersion() >= 19) {
|
||||
// 1.19+
|
||||
return new ModernComponentConverter();
|
||||
} else if (BukkitReflection.getMinorVersion() >= 16) {
|
||||
// 1.16 - 1.18.2
|
||||
return new ModerateComponentConverter();
|
||||
} else {
|
||||
// 1.7 - 1.15.2
|
||||
return new LegacyComponentConverter();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Bukkit.getConsoleSender().sendMessage("§c[TAB] Failed to initialize converter from TAB components to Minecraft components. " +
|
||||
"This will negatively impact most features, see below.");
|
||||
if (BukkitUtils.PRINT_EXCEPTIONS) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private Function<BukkitTabPlayer, Scoreboard> findScoreboardProvider() {
|
||||
try {
|
||||
if (BukkitReflection.getMinorVersion() >= 7) Objects.requireNonNull(componentConverter);
|
||||
PacketScoreboard.load();
|
||||
return PacketScoreboard::new;
|
||||
} catch (Exception e) {
|
||||
if (PaperScoreboard.isAvailable()) {
|
||||
BukkitUtils.compatibilityError(e, "Scoreboards", "Paper API", "Compatibility with other plugins being reduced");
|
||||
return PaperScoreboard::new;
|
||||
} else if (BukkitScoreboard.isAvailable()) {
|
||||
List<String> missingFeatures = Lists.newArrayList(
|
||||
"Compatibility with other plugins being reduced",
|
||||
"Features receiving new artificial character limits"
|
||||
);
|
||||
if (BukkitReflection.is1_20_3Plus()) {
|
||||
missingFeatures.add("1.20.3+ visuals not working due to lack of API"); // soontm?
|
||||
}
|
||||
BukkitUtils.compatibilityError(e, "Scoreboards", "Bukkit API", missingFeatures.toArray(new String[0]));
|
||||
return BukkitScoreboard::new;
|
||||
} else if (BukkitReflection.getMinorVersion() >= 5) {
|
||||
BukkitUtils.compatibilityError(e, "Scoreboards", null,
|
||||
"Scoreboard feature will not work",
|
||||
"Belowname feature will not work",
|
||||
"Player objective feature will not work",
|
||||
"Scoreboard teams feature will not work (nametags & sorting)");
|
||||
}
|
||||
}
|
||||
return DummyScoreboard::new;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@SneakyThrows
|
||||
private Function<BukkitTabPlayer, TabList> findTablistProvider() {
|
||||
try {
|
||||
if (ReflectionUtils.classExists("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket")) {
|
||||
// 1.19.3+
|
||||
Objects.requireNonNull(componentConverter);
|
||||
PacketTabList1193.loadNew();
|
||||
return PacketTabList1193::new;
|
||||
} else if (BukkitReflection.getMinorVersion() >= 8) {
|
||||
// 1.8 - 1.19.2
|
||||
Objects.requireNonNull(componentConverter);
|
||||
PacketTabList18.load();
|
||||
return PacketTabList18::new;
|
||||
} else {
|
||||
// 1.7.10 and lower
|
||||
PacketTabList17.load();
|
||||
return PacketTabList17::new;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
BukkitUtils.compatibilityError(e, "tablist entry management", "Bukkit API",
|
||||
"Layout feature will not work",
|
||||
"Prevent-spectator-effect feature will not work",
|
||||
"Ping spoof feature will not work",
|
||||
"Tablist formatting missing anti-override",
|
||||
"Tablist formatting not supporting relational placeholders");
|
||||
return BukkitTabList::new;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Scoreboard newScoreboard(@NotNull BukkitTabPlayer player) {
|
||||
return scoreboardProvider.apply(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public TabList newTabList(@NotNull BukkitTabPlayer player) {
|
||||
return tablistProvider.apply(player);
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package me.neznamy.tab.platforms.bukkit.nms.converter;
|
||||
package me.neznamy.tab.platforms.bukkit.provider;
|
||||
|
||||
import me.neznamy.chat.ChatModifier;
|
||||
import me.neznamy.chat.component.KeybindComponent;
|
||||
+2
-1
@@ -2,7 +2,6 @@ package me.neznamy.tab.platforms.bukkit.provider;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.converter.ComponentConverter;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import me.neznamy.tab.shared.util.function.FunctionWithException;
|
||||
@@ -22,4 +21,6 @@ public interface ImplementationProvider {
|
||||
|
||||
@Nullable
|
||||
FunctionWithException<BukkitTabPlayer, Channel> getChannelFunction();
|
||||
|
||||
int getPing(@NotNull BukkitTabPlayer player);
|
||||
}
|
||||
-72
@@ -1,72 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.provider;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.scoreboard.viaversion.ViaScoreboard113;
|
||||
import me.neznamy.tab.platforms.bukkit.scoreboard.viaversion.ViaScoreboard116;
|
||||
import me.neznamy.tab.platforms.bukkit.scoreboard.viaversion.ViaScoreboard1203;
|
||||
import me.neznamy.tab.platforms.bukkit.tablist.viaversion.ViaTabList116;
|
||||
import me.neznamy.tab.platforms.bukkit.tablist.viaversion.ViaTabList1193;
|
||||
import me.neznamy.tab.platforms.bukkit.tablist.viaversion.ViaTabList1212;
|
||||
import me.neznamy.tab.platforms.bukkit.tablist.viaversion.ViaTabList1214;
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Implementation for sending new features on old servers using ViaVersion API.
|
||||
* If no implementation is required for a player, {@code null} is returned.
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class ViaVersionImplementationProvider {
|
||||
|
||||
/** Map of tablist implementations by each new version */
|
||||
private static final Map<ProtocolVersion, Function<BukkitTabPlayer, TabList>> TABLIST_IMPLEMENTATIONS = new LinkedHashMap<>();
|
||||
|
||||
/** Map of scoreboard implementations by each new version */
|
||||
private static final Map<ProtocolVersion, Function<BukkitTabPlayer, Scoreboard>> SCOREBOARD_IMPLEMENTATIONS = new LinkedHashMap<>();
|
||||
|
||||
static {
|
||||
TABLIST_IMPLEMENTATIONS.put(ProtocolVersion.V1_21_4, ViaTabList1214::new);
|
||||
TABLIST_IMPLEMENTATIONS.put(ProtocolVersion.V1_21_2, ViaTabList1212::new);
|
||||
TABLIST_IMPLEMENTATIONS.put(ProtocolVersion.V1_19_3, ViaTabList1193::new);
|
||||
TABLIST_IMPLEMENTATIONS.put(ProtocolVersion.V1_16, ViaTabList116::new);
|
||||
|
||||
SCOREBOARD_IMPLEMENTATIONS.put(ProtocolVersion.V1_20_3, ViaScoreboard1203::new);
|
||||
SCOREBOARD_IMPLEMENTATIONS.put(ProtocolVersion.V1_16, ViaScoreboard116::new);
|
||||
SCOREBOARD_IMPLEMENTATIONS.put(ProtocolVersion.V1_13, ViaScoreboard113::new);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private final ProtocolVersion serverVersion;
|
||||
|
||||
@Nullable
|
||||
public Scoreboard newScoreboard(@NotNull BukkitTabPlayer player) {
|
||||
int serverVer = serverVersion.getNetworkId();
|
||||
int playerVer = player.getVersion().getNetworkId();
|
||||
for (Map.Entry<ProtocolVersion, Function<BukkitTabPlayer, Scoreboard>> entry : SCOREBOARD_IMPLEMENTATIONS.entrySet()) {
|
||||
if (serverVer < entry.getKey().getNetworkId() && playerVer >= entry.getKey().getNetworkId()) {
|
||||
return entry.getValue().apply(player);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public TabList newTabList(@NotNull BukkitTabPlayer player) {
|
||||
int serverVer = serverVersion.getNetworkId();
|
||||
int playerVer = player.getVersion().getNetworkId();
|
||||
for (Map.Entry<ProtocolVersion, Function<BukkitTabPlayer, TabList>> entry : TABLIST_IMPLEMENTATIONS.entrySet()) {
|
||||
if (serverVer < entry.getKey().getNetworkId() && playerVer >= entry.getKey().getNetworkId()) {
|
||||
return entry.getValue().apply(player);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
package me.neznamy.tab.platforms.bukkit.provider.bukkit;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import lombok.Getter;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.provider.ComponentConverter;
|
||||
import me.neznamy.tab.platforms.bukkit.provider.ImplementationProvider;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import me.neznamy.tab.shared.platform.impl.DummyScoreboard;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import me.neznamy.tab.shared.util.function.FunctionWithException;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Implementation provider using Bukkit API.
|
||||
*/
|
||||
@Getter
|
||||
public class BukkitImplementationProvider implements ImplementationProvider {
|
||||
|
||||
@NotNull
|
||||
private final Function<BukkitTabPlayer, Integer> pingProvider = ReflectionUtils.methodExists(Player.class, "getPing") ? p -> p.getPlayer().getPing() : p -> -1;
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Scoreboard newScoreboard(@NotNull BukkitTabPlayer player) {
|
||||
if (PaperScoreboard.isAvailable()) {
|
||||
return new PaperScoreboard(player);
|
||||
} else if (BukkitScoreboard.isAvailable()) {
|
||||
return new BukkitScoreboard(player);
|
||||
} else {
|
||||
return new DummyScoreboard(player);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public TabList newTabList(@NotNull BukkitTabPlayer player) {
|
||||
return new BukkitTabList(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public ComponentConverter getComponentConverter() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public FunctionWithException<BukkitTabPlayer, Channel> getChannelFunction() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPing(@NotNull BukkitTabPlayer player) {
|
||||
return pingProvider.apply(player);
|
||||
}
|
||||
}
|
||||
+3
-4
@@ -1,13 +1,12 @@
|
||||
package me.neznamy.tab.platforms.bukkit.scoreboard;
|
||||
package me.neznamy.tab.platforms.bukkit.provider.bukkit;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
|
||||
import me.neznamy.tab.shared.Limitations;
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import me.neznamy.tab.shared.platform.decorators.SafeScoreboard;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.bukkit.Bukkit;
|
||||
@@ -64,7 +63,7 @@ public class BukkitScoreboard extends SafeScoreboard<BukkitTabPlayer> {
|
||||
private static org.bukkit.scoreboard.DisplaySlot[] slots;
|
||||
|
||||
/** Server's minor version */
|
||||
private final int serverMinorVersion = BukkitReflection.getMinorVersion();
|
||||
private final int serverMinorVersion = player.getPlatform().getServerVersion().getMinorVersion();
|
||||
|
||||
protected org.bukkit.scoreboard.Scoreboard scoreboard;
|
||||
|
||||
+26
-4
@@ -1,9 +1,12 @@
|
||||
package me.neznamy.tab.platforms.bukkit.tablist;
|
||||
package me.neznamy.tab.platforms.bukkit.provider.bukkit;
|
||||
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.shared.platform.decorators.TrackedTabList;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import me.neznamy.tab.shared.util.function.TriConsumer;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -13,8 +16,27 @@ import java.util.UUID;
|
||||
/**
|
||||
* TabList handler using the almighty Bukkit API.
|
||||
*/
|
||||
@SuppressWarnings("deprecation") // Marked as deprecated by Paper to make us use their method instead
|
||||
public class BukkitTabList extends TrackedTabList<BukkitTabPlayer> {
|
||||
|
||||
private static final TriConsumer<BukkitTabPlayer, TabComponent, TabComponent> sendHeaderFooter;
|
||||
|
||||
static {
|
||||
if (ReflectionUtils.classExists("net.kyori.adventure.text.Component") &&
|
||||
ReflectionUtils.methodExists(Player.class, "sendPlayerListHeaderAndFooter", Component.class, Component.class)) {
|
||||
sendHeaderFooter = (player, header, footer) -> player.getPlayer().sendPlayerListHeaderAndFooter(header.toAdventure(), footer.toAdventure());
|
||||
} else if (ReflectionUtils.methodExists(Player.class, "setPlayerListHeaderFooter", String.class, String.class)) {
|
||||
sendHeaderFooter = (player, header, footer) -> player.getPlayer().setPlayerListHeaderFooter(
|
||||
player.getPlatform().toBukkitFormat(header),
|
||||
player.getPlatform().toBukkitFormat(footer)
|
||||
);
|
||||
} else {
|
||||
sendHeaderFooter = (player, header, footer) -> {
|
||||
// Do nothing
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs new instance with given player.
|
||||
*
|
||||
@@ -35,7 +57,7 @@ public class BukkitTabList extends TrackedTabList<BukkitTabPlayer> {
|
||||
public void updateDisplayName0(@NonNull UUID entry, @Nullable TabComponent displayName) {
|
||||
Player p = Bukkit.getPlayer(entry);
|
||||
if (p == null) return;
|
||||
p.setPlayerListName(displayName == null ? null : displayName.toLegacyText());
|
||||
p.setPlayerListName(displayName == null ? null : player.getPlatform().toBukkitFormat(displayName));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -75,7 +97,7 @@ public class BukkitTabList extends TrackedTabList<BukkitTabPlayer> {
|
||||
|
||||
@Override
|
||||
public void setPlayerListHeaderFooter(@NonNull TabComponent header, @NonNull TabComponent footer) {
|
||||
player.getPlatform().getHeaderFooter().set(player, header, footer);
|
||||
sendHeaderFooter.accept(player, header, footer);
|
||||
}
|
||||
|
||||
@Override
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package me.neznamy.tab.platforms.bukkit.scoreboard;
|
||||
package me.neznamy.tab.platforms.bukkit.provider.bukkit;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
+3
-2
@@ -1,8 +1,9 @@
|
||||
package me.neznamy.tab.platforms.bukkit.nms.converter;
|
||||
package me.neznamy.tab.platforms.bukkit.provider.reflection;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.chat.ChatModifier;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitReflection;
|
||||
import me.neznamy.tab.platforms.bukkit.provider.ComponentConverter;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
+270
@@ -0,0 +1,270 @@
|
||||
package me.neznamy.tab.platforms.bukkit.provider.reflection;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitReflection;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.platform.decorators.SafeScoreboard;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import me.neznamy.tab.shared.util.function.BiFunctionWithException;
|
||||
import me.neznamy.tab.shared.util.function.FunctionWithException;
|
||||
import me.neznamy.tab.shared.util.function.QuintFunction;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Scoreboard implementation which uses packets
|
||||
* to send scoreboards to use the full potential on all versions
|
||||
* and server software without any artificial limits.
|
||||
*/
|
||||
public class PacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
|
||||
|
||||
private static final boolean is1_20_3Plus = ReflectionUtils.classExists("net.minecraft.network.protocol.game.ClientboundResetScorePacket");
|
||||
|
||||
private static Class<?> Component;
|
||||
private static Object emptyScoreboard;
|
||||
private static Class<?> NumberFormat;
|
||||
private static Constructor<?> newFixedFormat;
|
||||
|
||||
// Objective packet
|
||||
private static Class<?> ObjectivePacketClass;
|
||||
private static Constructor<?> newObjectivePacket;
|
||||
private static Field Objective_OBJECTIVE_NAME;
|
||||
private static Field Objective_METHOD;
|
||||
private static Constructor<?> newScoreboardObjective;
|
||||
private static Enum<?>[] healthDisplays;
|
||||
|
||||
private static TeamPacketData teamPacketData;
|
||||
private static PacketSender packetSender;
|
||||
|
||||
// DisplayObjective packet
|
||||
private static Class<?> DisplayObjectiveClass;
|
||||
private static Constructor<?> newDisplayObjective;
|
||||
private static Field DisplayObjective_OBJECTIVE_NAME;
|
||||
private static Object[] displaySlots;
|
||||
private static FunctionWithException<Object, Integer> packetToSlot;
|
||||
|
||||
// Score packet
|
||||
private static QuintFunction<String, String, Integer, Object, Object, Object> setScore;
|
||||
private static BiFunctionWithException<String, String, Object> removeScore;
|
||||
|
||||
public static void load() throws ReflectiveOperationException {
|
||||
Class<?> scoreboard = BukkitReflection.getClass("world.scores.Scoreboard");
|
||||
Class<?> scoreboardObjective = BukkitReflection.getClass("world.scores.Objective", "world.scores.ScoreboardObjective");
|
||||
ObjectivePacketClass = BukkitReflection.getClass(
|
||||
"network.protocol.game.ClientboundSetObjectivePacket", // Mojang mapped
|
||||
"network.protocol.game.PacketPlayOutScoreboardObjective" // Bukkit
|
||||
);
|
||||
emptyScoreboard = scoreboard.getConstructor().newInstance();
|
||||
Objective_OBJECTIVE_NAME = ReflectionUtils.getFields(ObjectivePacketClass, String.class).get(0);
|
||||
List<Field> list = ReflectionUtils.getFields(ObjectivePacketClass, int.class);
|
||||
Objective_METHOD = list.get(list.size()-1);
|
||||
newObjectivePacket = ObjectivePacketClass.getConstructor(scoreboardObjective, int.class);
|
||||
newScoreboardObjective = ReflectionUtils.getOnlyConstructor(scoreboardObjective);
|
||||
Component = BukkitReflection.getClass("network.chat.Component", "network.chat.IChatBaseComponent");
|
||||
Class<?> EnumScoreboardHealthDisplay = BukkitReflection.getClass(
|
||||
"world.scores.criteria.ObjectiveCriteria$RenderType",
|
||||
"world.scores.criteria.IScoreboardCriteria$EnumScoreboardHealthDisplay"
|
||||
);
|
||||
healthDisplays = (Enum<?>[]) EnumScoreboardHealthDisplay.getMethod("values").invoke(null);
|
||||
if (is1_20_3Plus) {
|
||||
NumberFormat = BukkitReflection.getClass("network.chat.numbers.NumberFormat");
|
||||
newFixedFormat = BukkitReflection.getClass("network.chat.numbers.FixedFormat").getConstructor(Component);
|
||||
}
|
||||
loadDisplayPacketData();
|
||||
loadScorePacketData();
|
||||
teamPacketData = new TeamPacketData();
|
||||
packetSender = new PacketSender();
|
||||
}
|
||||
|
||||
private static void loadDisplayPacketData() throws ReflectiveOperationException {
|
||||
Class<?> ScoreboardObjective = BukkitReflection.getClass("world.scores.Objective", "world.scores.ScoreboardObjective");
|
||||
DisplayObjectiveClass = BukkitReflection.getClass(
|
||||
"network.protocol.game.ClientboundSetDisplayObjectivePacket", // Mojang mapped
|
||||
"network.protocol.game.PacketPlayOutScoreboardDisplayObjective" // Bukkit
|
||||
);
|
||||
DisplayObjective_OBJECTIVE_NAME = ReflectionUtils.getOnlyField(DisplayObjectiveClass, String.class);
|
||||
if (BukkitReflection.is1_20_2Plus()) {
|
||||
Class<?> DisplaySlot = BukkitReflection.getClass("world.scores.DisplaySlot");
|
||||
displaySlots = (Object[]) DisplaySlot.getDeclaredMethod("values").invoke(null);
|
||||
Field DisplayObjective_POSITION = ReflectionUtils.getOnlyField(DisplayObjectiveClass, DisplaySlot);
|
||||
newDisplayObjective = DisplayObjectiveClass.getConstructor(DisplaySlot, ScoreboardObjective);
|
||||
packetToSlot = packet -> ((Enum<?>)DisplayObjective_POSITION.get(packet)).ordinal();
|
||||
} else {
|
||||
displaySlots = new Object[]{0, 1, 2};
|
||||
Field DisplayObjective_POSITION = ReflectionUtils.getOnlyField(DisplayObjectiveClass, int.class);
|
||||
newDisplayObjective = DisplayObjectiveClass.getConstructor(int.class, ScoreboardObjective);
|
||||
packetToSlot = DisplayObjective_POSITION::getInt;
|
||||
}
|
||||
}
|
||||
|
||||
private static void loadScorePacketData() throws ReflectiveOperationException {
|
||||
Class<?> SetScorePacket = BukkitReflection.getClass(
|
||||
"network.protocol.game.ClientboundSetScorePacket", // Mojang mapped
|
||||
"network.protocol.game.PacketPlayOutScoreboardScore" // Bukkit
|
||||
);
|
||||
if (is1_20_3Plus) {
|
||||
QuintFunction<String, String, Integer, Object, Object, Object> setScore0;
|
||||
try {
|
||||
// 1.20.5+
|
||||
Constructor<?> newSetScore = SetScorePacket.getConstructor(String.class, String.class, int.class, Optional.class, Optional.class);
|
||||
setScore0 = (objective, holder, score, displayName, numberFormat) ->
|
||||
newSetScore.newInstance(holder, objective, score, Optional.ofNullable(displayName), Optional.ofNullable(numberFormat));
|
||||
} catch (ReflectiveOperationException e) {
|
||||
// 1.20.3 - 1.20.4
|
||||
Constructor<?> newSetScore = SetScorePacket.getConstructor(String.class, String.class, int.class, PacketScoreboard.Component, PacketScoreboard.NumberFormat);
|
||||
setScore0 = (objective, holder, score, displayName, numberFormat) ->
|
||||
newSetScore.newInstance(holder, objective, score, displayName, numberFormat);
|
||||
}
|
||||
setScore = setScore0;
|
||||
Constructor<?> newResetScore = BukkitReflection.getClass("network.protocol.game.ClientboundResetScorePacket").getConstructor(String.class, String.class);
|
||||
removeScore = (objective, holder) -> newResetScore.newInstance(holder, objective);
|
||||
} else {
|
||||
Class<?> actionClass = BukkitReflection.getClass("server.ServerScoreboard$Method", "server.ScoreboardServer$Action", "ScoreboardServer$Action");
|
||||
Constructor<?> newSetScore = SetScorePacket.getConstructor(actionClass, String.class, String.class, int.class);
|
||||
Enum<?>[] scoreboardActions = (Enum<?>[]) actionClass.getMethod("values").invoke(null);
|
||||
setScore = (objective, holder, score, displayName, numberFormat) -> newSetScore.newInstance(scoreboardActions[0], objective, holder, score);
|
||||
removeScore = (objective, holder) -> newSetScore.newInstance(scoreboardActions[1], objective, holder, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs new instance with given player.
|
||||
*
|
||||
* @param player
|
||||
* Player this scoreboard will belong to
|
||||
*/
|
||||
public PacketScoreboard(@NonNull BukkitTabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerObjective(@NonNull Objective objective) {
|
||||
packetSender.sendPacket(player, newObjectivePacket(ObjectiveAction.REGISTER, objective));
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void setDisplaySlot(@NonNull Objective objective) {
|
||||
packetSender.sendPacket(player, newDisplayObjective.newInstance(displaySlots[objective.getDisplaySlot().ordinal()], newObjective(objective)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterObjective(@NonNull Objective objective) {
|
||||
packetSender.sendPacket(player, newObjectivePacket(ObjectiveAction.UNREGISTER, objective));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateObjective(@NonNull Objective objective) {
|
||||
packetSender.sendPacket(player, newObjectivePacket(ObjectiveAction.UPDATE, objective));
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void setScore(@NonNull Score score) {
|
||||
packetSender.sendPacket(player, setScore.apply(
|
||||
score.getObjective().getName(),
|
||||
score.getHolder(),
|
||||
score.getValue(),
|
||||
score.getDisplayName() == null ? null : score.getDisplayName().convert(),
|
||||
score.getNumberFormat() == null ? null : toFixedFormat(score.getNumberFormat())
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void removeScore(@NonNull Score score) {
|
||||
packetSender.sendPacket(player, removeScore.apply(score.getObjective().getName(), score.getHolder()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
@SneakyThrows
|
||||
public Object createTeam(@NonNull String name) {
|
||||
return teamPacketData.createTeam.apply(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void registerTeam(@NonNull Team team) {
|
||||
teamPacketData.ScoreboardTeam_players.set(team.getPlatformTeam(), new HashSet<>(team.getPlayers()));
|
||||
teamPacketData.updateTeamData(team);
|
||||
packetSender.sendPacket(player, teamPacketData.newRegisterTeamPacket.apply(team));
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void unregisterTeam(@NonNull Team team) {
|
||||
packetSender.sendPacket(player, teamPacketData.newUnregisterTeamPacket.apply(team));
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void updateTeam(@NonNull Team team) {
|
||||
teamPacketData.updateTeamData(team);
|
||||
packetSender.sendPacket(player, teamPacketData.newUpdateTeamPacket.apply(team));
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void onPacketSend(@NonNull Object packet) {
|
||||
if (DisplayObjectiveClass.isInstance(packet)) {
|
||||
TAB.getInstance().getFeatureManager().onDisplayObjective(player, packetToSlot.apply(packet),
|
||||
(String) DisplayObjective_OBJECTIVE_NAME.get(packet));
|
||||
}
|
||||
if (ObjectivePacketClass.isInstance(packet)) {
|
||||
TAB.getInstance().getFeatureManager().onObjective(player,
|
||||
Objective_METHOD.getInt(packet), (String) Objective_OBJECTIVE_NAME.get(packet));
|
||||
}
|
||||
teamPacketData.onPacketSend(player, packet);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private Object newObjectivePacket(int action, @NonNull Objective objective) {
|
||||
// TODO save objectives and reuse them for better performance
|
||||
return newObjectivePacket.newInstance(newObjective(objective), action);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private Object newObjective(@NonNull Objective objective) {
|
||||
if (is1_20_3Plus) {
|
||||
return newScoreboardObjective.newInstance(
|
||||
emptyScoreboard,
|
||||
objective.getName(),
|
||||
null, // Criteria
|
||||
objective.getTitle().convert(),
|
||||
healthDisplays[objective.getHealthDisplay().ordinal()],
|
||||
false, // Auto update
|
||||
objective.getNumberFormat() == null ? null : toFixedFormat(objective.getNumberFormat())
|
||||
);
|
||||
}
|
||||
return newScoreboardObjective.newInstance(
|
||||
emptyScoreboard,
|
||||
objective.getName(),
|
||||
null, // Criteria
|
||||
objective.getTitle().convert(),
|
||||
healthDisplays[objective.getHealthDisplay().ordinal()]
|
||||
);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@SneakyThrows
|
||||
private Object toFixedFormat(@NonNull TabComponent component) {
|
||||
if (newFixedFormat == null) return null;
|
||||
return component.toFixedFormat(this::convertFixedFormat);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@SneakyThrows
|
||||
private Object convertFixedFormat(@NotNull Object nmsComponent) {
|
||||
return newFixedFormat.newInstance(nmsComponent);
|
||||
}
|
||||
}
|
||||
+7
-13
@@ -1,7 +1,8 @@
|
||||
package me.neznamy.tab.platforms.bukkit.nms;
|
||||
package me.neznamy.tab.platforms.bukkit.provider.reflection;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitReflection;
|
||||
import me.neznamy.tab.shared.util.function.BiConsumerWithException;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -22,20 +23,13 @@ public class PacketSender {
|
||||
* Constructs new instance and attempts to load required classes, fields and methods.
|
||||
* If something fails, error is thrown.
|
||||
*
|
||||
* @throws ReflectiveOperationException
|
||||
* If something fails
|
||||
*/
|
||||
public PacketSender() throws ReflectiveOperationException {
|
||||
Class<?> Packet = BukkitReflection.getClass("network.protocol.Packet", "Packet");
|
||||
Class<?> EntityPlayer = BukkitReflection.getClass("server.level.ServerPlayer", "server.level.EntityPlayer", "EntityPlayer");
|
||||
Class<?> PlayerConnection = BukkitReflection.getClass("server.network.ServerGamePacketListenerImpl", "server.network.PlayerConnection", "PlayerConnection");
|
||||
public PacketSender() {
|
||||
Class<?> Packet = BukkitReflection.getClass("network.protocol.Packet");
|
||||
Class<?> EntityPlayer = BukkitReflection.getClass("server.level.ServerPlayer", "server.level.EntityPlayer");
|
||||
Class<?> PlayerConnection = BukkitReflection.getClass("server.network.ServerGamePacketListenerImpl", "server.network.PlayerConnection");
|
||||
Field PLAYER_CONNECTION = ReflectionUtils.getOnlyField(EntityPlayer, PlayerConnection);
|
||||
Method sendPacket;
|
||||
if (BukkitReflection.getMinorVersion() >= 7) {
|
||||
sendPacket = ReflectionUtils.getMethods(PlayerConnection, void.class, Packet).get(0);
|
||||
} else {
|
||||
sendPacket = ReflectionUtils.getMethod(PlayerConnection, new String[]{"sendPacket"}, Packet);
|
||||
}
|
||||
Method sendPacket = ReflectionUtils.getPublicMethods(PlayerConnection, void.class, Packet).get(0);
|
||||
send = (player, packet) -> {
|
||||
if (player.connection == null) player.connection = PLAYER_CONNECTION.get(player.getHandle());
|
||||
sendPacket.invoke(player.connection, packet);
|
||||
+152
-35
@@ -1,31 +1,51 @@
|
||||
package me.neznamy.tab.platforms.bukkit.tablist;
|
||||
package me.neznamy.tab.platforms.bukkit.provider.reflection;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import com.mojang.authlib.properties.Property;
|
||||
import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitReflection;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import me.neznamy.tab.shared.platform.decorators.TrackedTabList;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* TabList handler for 1.19.3+ servers using packets.
|
||||
*/
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
public class PacketTabList1193 extends PacketTabList18 {
|
||||
public class PacketTabList extends TrackedTabList<BukkitTabPlayer> {
|
||||
|
||||
/** Map of actions to prevent creating new EnumSet on each packet send */
|
||||
private static final Map<Action, EnumSet<?>> actionToEnumSet = new EnumMap<>(Action.class);
|
||||
private static final Method getProfile = ReflectionUtils.getMethods(BukkitReflection.getClass(
|
||||
"world.entity.player.Player", "world.entity.player.EntityHuman"), GameProfile.class).get(0);
|
||||
|
||||
private static Class<?> PlayerInfoClass;
|
||||
private static Constructor<?> newPlayerInfo;
|
||||
private static Field ACTION;
|
||||
private static Field PLAYERS;
|
||||
|
||||
private static Constructor<?> newHeaderFooter;
|
||||
|
||||
private static Constructor<?> newPlayerInfoData;
|
||||
private static Field PlayerInfoData_Profile;
|
||||
private static Field PlayerInfoData_Latency;
|
||||
private static Field PlayerInfoData_DisplayName;
|
||||
|
||||
private static Object[] gameModes;
|
||||
|
||||
private static PacketSender packetSender;
|
||||
|
||||
private static boolean v1_21_2Plus;
|
||||
private static boolean v1_21_4Plus;
|
||||
|
||||
private static Enum actionAddPlayer;
|
||||
private static Enum actionUpdateDisplayName;
|
||||
@@ -40,13 +60,21 @@ public class PacketTabList1193 extends PacketTabList18 {
|
||||
private static Field PlayerInfoData_ListOrder;
|
||||
private static Field PlayerInfoData_RemoteChatSession;
|
||||
|
||||
private static EnumSet<?> ADD_PLAYER;
|
||||
private static EnumSet<?> UPDATE_GAME_MODE;
|
||||
private static EnumSet<?> UPDATE_DISPLAY_NAME;
|
||||
private static EnumSet<?> UPDATE_LATENCY;
|
||||
private static EnumSet<?> UPDATE_LISTED;
|
||||
private static EnumSet<?> UPDATE_LIST_ORDER;
|
||||
private static EnumSet<?> UPDATE_HAT;
|
||||
|
||||
/**
|
||||
* Constructs new instance with given player.
|
||||
*
|
||||
* @param player
|
||||
* Player this tablist will belong to.
|
||||
*/
|
||||
public PacketTabList1193(@NonNull BukkitTabPlayer player) {
|
||||
public PacketTabList(@NonNull BukkitTabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@@ -57,11 +85,11 @@ public class PacketTabList1193 extends PacketTabList18 {
|
||||
* @throws ReflectiveOperationException
|
||||
* If something goes wrong
|
||||
*/
|
||||
public static void loadNew() throws ReflectiveOperationException {
|
||||
Class<?> IChatBaseComponent = BukkitReflection.getClass("network.chat.Component", "network.chat.IChatBaseComponent", "IChatBaseComponent");
|
||||
public static void load() throws ReflectiveOperationException {
|
||||
Class<?> IChatBaseComponent = BukkitReflection.getClass("network.chat.Component", "network.chat.IChatBaseComponent");
|
||||
Class<?> RemoteChatSession$Data = BukkitReflection.getClass("network.chat.RemoteChatSession$Data", "network.chat.RemoteChatSession$a");
|
||||
Class<Enum> EnumGamemodeClass = (Class<Enum>) BukkitReflection.getClass("world.level.GameType", "world.level.EnumGamemode");
|
||||
ActionClass = (Class<Enum>) BukkitReflection.getClass(
|
||||
Class<Enum> actionClass = (Class<Enum>) BukkitReflection.getClass(
|
||||
"network.protocol.game.ClientboundPlayerInfoUpdatePacket$Action", // Mojang
|
||||
"network.protocol.game.ClientboundPlayerInfoUpdatePacket$a" // Bukkit
|
||||
);
|
||||
@@ -74,7 +102,21 @@ public class PacketTabList1193 extends PacketTabList18 {
|
||||
newPlayerInfo = PlayerInfoClass.getConstructor(EnumSet.class, Collection.class);
|
||||
ACTION = ReflectionUtils.getOnlyField(PlayerInfoClass, EnumSet.class);
|
||||
|
||||
loadSharedContent(playerInfoDataClass, EnumGamemodeClass);
|
||||
PLAYERS = ReflectionUtils.getOnlyField(PlayerInfoClass, List.class);
|
||||
PlayerInfoData_Profile = ReflectionUtils.getOnlyField(playerInfoDataClass, GameProfile.class);
|
||||
PlayerInfoData_Latency = ReflectionUtils.getFields(playerInfoDataClass, int.class).get(0);
|
||||
PlayerInfoData_DisplayName = ReflectionUtils.getOnlyField(playerInfoDataClass, IChatBaseComponent);
|
||||
gameModes = new Object[] {
|
||||
Enum.valueOf(EnumGamemodeClass, "SURVIVAL"),
|
||||
Enum.valueOf(EnumGamemodeClass, "CREATIVE"),
|
||||
Enum.valueOf(EnumGamemodeClass, "ADVENTURE"),
|
||||
Enum.valueOf(EnumGamemodeClass, "SPECTATOR")
|
||||
};
|
||||
newHeaderFooter = BukkitReflection.getClass(
|
||||
"network.protocol.game.ClientboundTabListPacket",
|
||||
"network.protocol.game.PacketPlayOutPlayerListHeaderFooter"
|
||||
).getConstructor(IChatBaseComponent, IChatBaseComponent);
|
||||
packetSender = new PacketSender();
|
||||
|
||||
PlayerInfoData_Listed = ReflectionUtils.getFields(playerInfoDataClass, boolean.class).get(0);
|
||||
PlayerInfoData_GameMode = ReflectionUtils.getOnlyField(playerInfoDataClass, EnumGamemodeClass);
|
||||
@@ -82,31 +124,33 @@ public class PacketTabList1193 extends PacketTabList18 {
|
||||
PlayerInfoData_UUID = ReflectionUtils.getOnlyField(playerInfoDataClass, UUID.class);
|
||||
newRemovePacket = BukkitReflection.getClass("network.protocol.game.ClientboundPlayerInfoRemovePacket").getConstructor(List.class);
|
||||
|
||||
actionAddPlayer = Enum.valueOf(ActionClass, Action.ADD_PLAYER.name());
|
||||
actionUpdateDisplayName = Enum.valueOf(ActionClass, Action.UPDATE_DISPLAY_NAME.name());
|
||||
actionUpdateLatency = Enum.valueOf(ActionClass, Action.UPDATE_LATENCY.name());
|
||||
actionAddPlayer = Enum.valueOf(actionClass, Action.ADD_PLAYER.name());
|
||||
actionUpdateDisplayName = Enum.valueOf(actionClass, Action.UPDATE_DISPLAY_NAME.name());
|
||||
actionUpdateLatency = Enum.valueOf(actionClass, Action.UPDATE_LATENCY.name());
|
||||
|
||||
ADD_PLAYER = EnumSet.allOf(actionClass);
|
||||
UPDATE_GAME_MODE = EnumSet.of(Enum.valueOf(actionClass, Action.UPDATE_GAME_MODE.name()));
|
||||
UPDATE_DISPLAY_NAME = EnumSet.of(actionUpdateDisplayName);
|
||||
UPDATE_LATENCY = EnumSet.of(actionUpdateLatency);
|
||||
UPDATE_LISTED = EnumSet.of(Enum.valueOf(actionClass, Action.UPDATE_LISTED.name()));
|
||||
|
||||
actionToEnumSet.put(Action.ADD_PLAYER, EnumSet.allOf(ActionClass));
|
||||
actionToEnumSet.put(Action.UPDATE_GAME_MODE, EnumSet.of(Enum.valueOf(ActionClass, Action.UPDATE_GAME_MODE.name())));
|
||||
actionToEnumSet.put(Action.UPDATE_DISPLAY_NAME, EnumSet.of(actionUpdateDisplayName));
|
||||
actionToEnumSet.put(Action.UPDATE_LATENCY, EnumSet.of(actionUpdateLatency));
|
||||
actionToEnumSet.put(Action.UPDATE_LISTED, EnumSet.of(Enum.valueOf(ActionClass, Action.UPDATE_LISTED.name())));
|
||||
try {
|
||||
actionToEnumSet.put(Action.UPDATE_LIST_ORDER, EnumSet.of(Enum.valueOf(ActionClass, Action.UPDATE_LIST_ORDER.name())));
|
||||
UPDATE_LIST_ORDER = EnumSet.of(Enum.valueOf(actionClass, Action.UPDATE_LIST_ORDER.name()));
|
||||
PlayerInfoData_ListOrder = ReflectionUtils.getFields(playerInfoDataClass, int.class).get(1);
|
||||
v1_21_2Plus = true;
|
||||
if (BukkitReflection.is1_21_4Plus()) {
|
||||
try {
|
||||
// 1.21.4+
|
||||
actionToEnumSet.put(Action.UPDATE_HAT, EnumSet.of(Enum.valueOf(ActionClass, Action.UPDATE_HAT.name())));
|
||||
UPDATE_HAT = EnumSet.of(Enum.valueOf(actionClass, Action.UPDATE_HAT.name()));
|
||||
PlayerInfoData_ShowHat = ReflectionUtils.getFields(playerInfoDataClass, boolean.class).get(1);
|
||||
newPlayerInfoData = playerInfoDataClass.getConstructor(UUID.class, GameProfile.class, boolean.class, int.class,
|
||||
EnumGamemodeClass, IChatBaseComponent, boolean.class, int.class, RemoteChatSession$Data);
|
||||
} else {
|
||||
v1_21_4Plus = true;
|
||||
} catch (Exception e) {
|
||||
// 1.21.2 - 1.21.3
|
||||
newPlayerInfoData = playerInfoDataClass.getConstructor(UUID.class, GameProfile.class, boolean.class, int.class,
|
||||
EnumGamemodeClass, IChatBaseComponent, int.class, RemoteChatSession$Data);
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
} catch (Exception e) {
|
||||
// 1.21.1-, should have a better check
|
||||
newPlayerInfoData = playerInfoDataClass.getConstructor(UUID.class, GameProfile.class, boolean.class, int.class,
|
||||
EnumGamemodeClass, IChatBaseComponent, RemoteChatSession$Data);
|
||||
@@ -119,37 +163,67 @@ public class PacketTabList1193 extends PacketTabList18 {
|
||||
packetSender.sendPacket(player, newRemovePacket.newInstance(Collections.singletonList(entry)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDisplayName0(@NonNull UUID entry, @Nullable TabComponent displayName) {
|
||||
packetSender.sendPacket(player,
|
||||
createPacket(UPDATE_DISPLAY_NAME, entry, "", null, false, 0, 0, displayName, 0, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateLatency(@NonNull UUID entry, int latency) {
|
||||
packetSender.sendPacket(player,
|
||||
createPacket(UPDATE_LATENCY, entry, "", null, false, latency, 0, null, 0, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateGameMode(@NonNull UUID entry, int gameMode) {
|
||||
packetSender.sendPacket(player,
|
||||
createPacket(UPDATE_GAME_MODE, entry, "", null, false, 0, gameMode, null, 0, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateListed(@NonNull UUID entry, boolean listed) {
|
||||
packetSender.sendPacket(player,
|
||||
createPacket(Action.UPDATE_LISTED, entry, "", null, listed, 0, 0, null, 0, false));
|
||||
createPacket(UPDATE_LISTED, entry, "", null, listed, 0, 0, null, 0, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateListOrder(@NonNull UUID entry, int listOrder) {
|
||||
if (player.getPlatform().getServerVersion().getNetworkId() >= ProtocolVersion.V1_21_2.getNetworkId()) {
|
||||
if (v1_21_2Plus) {
|
||||
packetSender.sendPacket(player,
|
||||
createPacket(Action.UPDATE_LIST_ORDER, entry, "", null, false, 0, 0, null, listOrder, false));
|
||||
createPacket(UPDATE_LIST_ORDER, entry, "", null, false, 0, 0, null, listOrder, false));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateHat(@NonNull UUID entry, boolean showHat) {
|
||||
if (player.getPlatform().getServerVersion().getNetworkId() >= ProtocolVersion.V1_21_4.getNetworkId()) {
|
||||
if (v1_21_4Plus) {
|
||||
packetSender.sendPacket(player,
|
||||
createPacket(Action.UPDATE_HAT, entry, "", null, false, 0, 0, null, 0, showHat));
|
||||
createPacket(UPDATE_HAT, entry, "", null, false, 0, 0, null, 0, showHat));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addEntry0(@NonNull Entry entry) {
|
||||
packetSender.sendPacket(player,
|
||||
createPacket(ADD_PLAYER, entry.getUniqueId(), entry.getName(), entry.getSkin(), entry.isListed(),
|
||||
entry.getLatency(), entry.getGameMode(), entry.getDisplayName(), entry.getListOrder(), entry.isShowHat()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void setPlayerListHeaderFooter(@NonNull TabComponent header, @NonNull TabComponent footer) {
|
||||
packetSender.sendPacket(player, newHeaderFooter.newInstance(header.convert(), footer.convert()));
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
@NotNull
|
||||
@Override
|
||||
public Object createPacket(@NonNull Action action, @NonNull UUID id, @NonNull String name, @Nullable Skin skin,
|
||||
private Object createPacket(@NonNull EnumSet<?> actions, @NonNull UUID id, @NonNull String name, @Nullable Skin skin,
|
||||
boolean listed, int latency, int gameMode, @Nullable TabComponent displayName, int listOrder, boolean showHat) {
|
||||
Object packet = newPlayerInfo.newInstance(actionToEnumSet.get(action), Collections.emptyList());
|
||||
Object packet = newPlayerInfo.newInstance(actions, Collections.emptyList());
|
||||
PLAYERS.set(packet, Collections.singletonList(newPlayerInfoData(
|
||||
id,
|
||||
action == Action.ADD_PLAYER ? createProfile(id, name, skin) : null,
|
||||
actions.contains(actionAddPlayer) ? createProfile(id, name, skin) : null,
|
||||
listed,
|
||||
latency,
|
||||
gameModes[gameMode],
|
||||
@@ -175,7 +249,7 @@ public class PacketTabList1193 extends PacketTabList18 {
|
||||
Object displayName = PlayerInfoData_DisplayName.get(nmsData);
|
||||
int latency = PlayerInfoData_Latency.getInt(nmsData);
|
||||
int listOrder = v1_21_2Plus ? PlayerInfoData_ListOrder.getInt(nmsData) : 0;
|
||||
boolean showHat = BukkitReflection.is1_21_4Plus() && PlayerInfoData_ShowHat.getBoolean(nmsData);
|
||||
boolean showHat = v1_21_4Plus && PlayerInfoData_ShowHat.getBoolean(nmsData);
|
||||
if (actions.contains(actionUpdateDisplayName)) {
|
||||
TabComponent expectedName = getExpectedDisplayNames().get(id);
|
||||
if (expectedName != null && expectedName.convert() != displayName) {
|
||||
@@ -212,7 +286,7 @@ public class PacketTabList1193 extends PacketTabList18 {
|
||||
@SneakyThrows
|
||||
private static Object newPlayerInfoData(@NotNull UUID id, @Nullable GameProfile profile, boolean listed, int latency,
|
||||
@Nullable Object gameMode, @Nullable Object displayName, boolean showHat, int listOrder, @Nullable Object chatSession) {
|
||||
if (BukkitReflection.is1_21_4Plus()) {
|
||||
if (v1_21_4Plus) {
|
||||
return newPlayerInfoData.newInstance(id, profile, listed, latency, gameMode, displayName, showHat, listOrder, chatSession);
|
||||
} else if (v1_21_2Plus) {
|
||||
return newPlayerInfoData.newInstance(id, profile, listed, latency, gameMode, displayName, listOrder, chatSession);
|
||||
@@ -220,4 +294,47 @@ public class PacketTabList1193 extends PacketTabList18 {
|
||||
return newPlayerInfoData.newInstance(id, profile, listed, latency, gameMode, displayName, chatSession);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates GameProfile from given parameters.
|
||||
*
|
||||
* @param id
|
||||
* Profile ID
|
||||
* @param name
|
||||
* Profile name
|
||||
* @param skin
|
||||
* Player skin
|
||||
* @return GameProfile from given parameters
|
||||
*/
|
||||
@NotNull
|
||||
public GameProfile createProfile(@NonNull UUID id, @NonNull String name, @Nullable Skin skin) {
|
||||
GameProfile profile = new GameProfile(id, name);
|
||||
if (skin != null) {
|
||||
profile.getProperties().put(TabList.TEXTURES_PROPERTY,
|
||||
new Property(TabList.TEXTURES_PROPERTY, skin.getValue(), skin.getSignature()));
|
||||
}
|
||||
return profile;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsEntry(@NonNull UUID entry) {
|
||||
return true; // TODO?
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public Skin getSkin() {
|
||||
Collection<Property> col = ((GameProfile) getProfile.invoke(player.getHandle())).getProperties().get(TabList.TEXTURES_PROPERTY);
|
||||
if (col.isEmpty()) return null; //offline mode
|
||||
Property property = col.iterator().next();
|
||||
if (BukkitReflection.is1_20_2Plus()) {
|
||||
return new TabList.Skin(
|
||||
(String) property.getClass().getMethod("value").invoke(property),
|
||||
(String) property.getClass().getMethod("signature").invoke(property)
|
||||
);
|
||||
} else {
|
||||
return new TabList.Skin(property.getValue(), property.getSignature());
|
||||
}
|
||||
}
|
||||
}
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
package me.neznamy.tab.platforms.bukkit.provider.reflection;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import lombok.Getter;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitReflection;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.provider.ComponentConverter;
|
||||
import me.neznamy.tab.platforms.bukkit.provider.ImplementationProvider;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import me.neznamy.tab.shared.util.function.FunctionWithException;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
/**
|
||||
* Implementation provider using reflection for NMS for 1.19.3+.
|
||||
*/
|
||||
@Getter
|
||||
public class ReflectionImplementationProvider implements ImplementationProvider {
|
||||
|
||||
@Nullable
|
||||
private final FunctionWithException<BukkitTabPlayer, Channel> channelFunction = findChannelFunction();
|
||||
|
||||
@Nullable
|
||||
private final ComponentConverter componentConverter = new ModernComponentConverter();
|
||||
|
||||
/**
|
||||
* Constructs new instance and loads all NMS classes.
|
||||
*
|
||||
* @throws ReflectiveOperationException
|
||||
* If reflection fails
|
||||
*/
|
||||
public ReflectionImplementationProvider() throws ReflectiveOperationException {
|
||||
PacketScoreboard.load();
|
||||
PacketTabList.load();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private FunctionWithException<BukkitTabPlayer, Channel> findChannelFunction() {
|
||||
Class<?> NetworkManager = BukkitReflection.getClass("network.Connection", "network.NetworkManager");
|
||||
Class<?> PlayerConnection = BukkitReflection.getClass("server.network.ServerGamePacketListenerImpl",
|
||||
"server.network.PlayerConnection");
|
||||
Class<?> EntityPlayer = BukkitReflection.getClass("server.level.ServerPlayer", "server.level.EntityPlayer");
|
||||
Field PLAYER_CONNECTION = ReflectionUtils.getOnlyField(EntityPlayer, PlayerConnection);
|
||||
Field NETWORK_MANAGER;
|
||||
if (BukkitReflection.is1_20_2Plus()) {
|
||||
NETWORK_MANAGER = ReflectionUtils.getOnlyField(PlayerConnection.getSuperclass(), NetworkManager);
|
||||
} else {
|
||||
NETWORK_MANAGER = ReflectionUtils.getOnlyField(PlayerConnection, NetworkManager);
|
||||
}
|
||||
Field CHANNEL = ReflectionUtils.getOnlyField(NetworkManager, Channel.class);
|
||||
return player -> (Channel) CHANNEL.get(NETWORK_MANAGER.get(PLAYER_CONNECTION.get(player.getHandle())));
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Scoreboard newScoreboard(@NotNull BukkitTabPlayer player) {
|
||||
return new PacketScoreboard(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public TabList newTabList(@NotNull BukkitTabPlayer player) {
|
||||
return new PacketTabList(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPing(@NotNull BukkitTabPlayer player) {
|
||||
return player.getPlayer().getPing();
|
||||
}
|
||||
}
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
package me.neznamy.tab.platforms.bukkit.provider.reflection;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitReflection;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard.TeamAction;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import me.neznamy.tab.shared.platform.decorators.SafeScoreboard;
|
||||
import me.neznamy.tab.shared.platform.decorators.SafeScoreboard.Team;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import me.neznamy.tab.shared.util.function.FunctionWithException;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Class storing all team related fields and methods.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public class TeamPacketData {
|
||||
|
||||
// Classes
|
||||
private final Class<?> Component = BukkitReflection.getClass("network.chat.Component", "network.chat.IChatBaseComponent");
|
||||
private final Class<?> EnumChatFormatClass = BukkitReflection.getClass("ChatFormatting", "EnumChatFormat");
|
||||
private final Class<?> ScoreboardClass = BukkitReflection.getClass("world.scores.Scoreboard");
|
||||
private final Class<?> ScoreboardTeamClass = BukkitReflection.getClass("world.scores.PlayerTeam", "world.scores.ScoreboardTeam");
|
||||
private final Class<?> TeamPacketClass = BukkitReflection.getClass(
|
||||
"network.protocol.game.ClientboundSetPlayerTeamPacket", // Mojang mapped
|
||||
"network.protocol.game.PacketPlayOutScoreboardTeam" // Bukkit
|
||||
);
|
||||
private final Class<?> enumNameTagVisibility = BukkitReflection.getClass(
|
||||
"world.scores.Team$Visibility", // Mojang mapped
|
||||
"world.scores.ScoreboardTeamBase$EnumNameTagVisibility" // Bukkit
|
||||
);
|
||||
private final Class<?> enumTeamPush = BukkitReflection.getClass("world.scores.Team$CollisionRule",
|
||||
"world.scores.ScoreboardTeamBase$EnumTeamPush");
|
||||
|
||||
// Constructors
|
||||
private final Constructor<?> newScoreboardTeam = ScoreboardTeamClass.getConstructor(ScoreboardClass, String.class);
|
||||
|
||||
// Fields
|
||||
public final Field ScoreboardTeam_players = ReflectionUtils.getFields(ScoreboardTeamClass, Set.class).get(0);
|
||||
private final Field ScoreboardTeam_allowFriendlyFire = ReflectionUtils.getFields(ScoreboardTeamClass, boolean.class).get(0);
|
||||
private final Field ScoreboardTeam_seeFriendlyInvisibles = ReflectionUtils.getFields(ScoreboardTeamClass, boolean.class).get(1);
|
||||
private final Field ScoreboardTeam_prefix = ReflectionUtils.getFields(ScoreboardTeamClass, Component).get(1);
|
||||
private final Field ScoreboardTeam_suffix = ReflectionUtils.getFields(ScoreboardTeamClass, Component).get(2);
|
||||
private final Method ScoreboardTeam_setColor = ReflectionUtils.getOnlyMethod(ScoreboardTeamClass, void.class, EnumChatFormatClass);
|
||||
private final Field ScoreboardTeam_nameTagVisibility = ReflectionUtils.getFields(ScoreboardTeamClass, enumNameTagVisibility).get(0);
|
||||
private final Method ScoreboardTeam_setCollisionRule = ReflectionUtils.getOnlyMethod(ScoreboardTeamClass, void.class, enumTeamPush);
|
||||
private final Field TeamPacket_NAME = ReflectionUtils.getFields(TeamPacketClass, String.class).get(0);
|
||||
private final Field TeamPacket_ACTION = ReflectionUtils.getInstanceFields(TeamPacketClass, int.class).get(0);
|
||||
private final Field TeamPacket_PLAYERS = ReflectionUtils.getOnlyField(TeamPacketClass, Collection.class);
|
||||
|
||||
// Objects
|
||||
private final Object emptyScoreboard = ScoreboardClass.getConstructor().newInstance();
|
||||
private final Enum<?>[] chatFormats = (Enum<?>[]) EnumChatFormatClass.getMethod("values").invoke(null);
|
||||
private final Enum<?>[] nameVisibilities = (Enum<?>[]) enumNameTagVisibility.getMethod("values").invoke(null);
|
||||
private final Enum<?>[] collisionRules = (Enum<?>[]) enumTeamPush.getMethod("values").invoke(null);
|
||||
|
||||
// Team / packet creation
|
||||
private final Method TeamPacketConstructor_of = ReflectionUtils.getOnlyMethod(TeamPacketClass, TeamPacketClass, ScoreboardTeamClass);
|
||||
private final Method TeamPacketConstructor_ofBoolean = ReflectionUtils.getOnlyMethod(TeamPacketClass, TeamPacketClass, ScoreboardTeamClass, boolean.class);
|
||||
public final FunctionWithException<String, Object> createTeam = name -> newScoreboardTeam.newInstance(emptyScoreboard, name);
|
||||
public final FunctionWithException<Team, Object> newRegisterTeamPacket = team -> TeamPacketConstructor_ofBoolean.invoke(null, team.getPlatformTeam(), true);
|
||||
public final FunctionWithException<Team, Object> newUnregisterTeamPacket = team -> TeamPacketConstructor_of.invoke(null, team.getPlatformTeam());
|
||||
public final FunctionWithException<Team, Object> newUpdateTeamPacket = team -> TeamPacketConstructor_ofBoolean.invoke(null, team.getPlatformTeam(), false);
|
||||
|
||||
/**
|
||||
* Constructs new instance and loads all required NMS classes, fields, and methods.
|
||||
* If anything fails, exception is thrown.
|
||||
*
|
||||
* @throws ReflectiveOperationException
|
||||
* If anything fails
|
||||
*/
|
||||
public TeamPacketData() throws ReflectiveOperationException {
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates team properties.
|
||||
*
|
||||
* @param team
|
||||
* Team to update
|
||||
*/
|
||||
@SneakyThrows
|
||||
public void updateTeamData(@NonNull Team team) {
|
||||
Object nmsTeam = team.getPlatformTeam();
|
||||
ScoreboardTeam_allowFriendlyFire.set(nmsTeam, (team.getOptions() & 0x1) > 0);
|
||||
ScoreboardTeam_seeFriendlyInvisibles.set(nmsTeam, (team.getOptions() & 0x2) > 0);
|
||||
ScoreboardTeam_prefix.set(nmsTeam, team.getPrefix().convert());
|
||||
ScoreboardTeam_suffix.set(nmsTeam, team.getSuffix().convert());
|
||||
ScoreboardTeam_setColor.invoke(nmsTeam, chatFormats[team.getColor().getLegacyColor().ordinal()]);
|
||||
ScoreboardTeam_nameTagVisibility.set(nmsTeam, nameVisibilities[team.getVisibility().ordinal()]);
|
||||
ScoreboardTeam_setCollisionRule.invoke(nmsTeam, collisionRules[team.getCollision().ordinal()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the packet is a team packet and removes all real players from the team
|
||||
* if sent by other plugins and anti-override is fully active on a player.
|
||||
*
|
||||
* @param player
|
||||
* Player who received the packet
|
||||
* @param packet
|
||||
* The received packet
|
||||
*/
|
||||
@SneakyThrows
|
||||
public void onPacketSend(@NonNull TabPlayer player, @NonNull Object packet) {
|
||||
if (!TeamPacketClass.isInstance(packet)) return;
|
||||
int action = TeamPacket_ACTION.getInt(packet);
|
||||
if (action == TeamAction.UPDATE) return;
|
||||
Collection<String> players = (Collection<String>) TeamPacket_PLAYERS.get(packet);
|
||||
if (players == null) players = Collections.emptyList();
|
||||
TeamPacket_PLAYERS.set(packet, ((SafeScoreboard<?>)player.getScoreboard()).onTeamPacket(
|
||||
action, (String) TeamPacket_NAME.get(packet), players));
|
||||
}
|
||||
}
|
||||
-83
@@ -1,83 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.scoreboard.packet;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import me.neznamy.tab.shared.util.function.FunctionWithException;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
/**
|
||||
* Class storing NMS fields and methods of DisplayObjective packet.
|
||||
*/
|
||||
public class DisplayPacketData {
|
||||
|
||||
private final Class<?> DisplayObjectiveClass;
|
||||
private final Constructor<?> newDisplayObjective;
|
||||
private final Field DisplayObjective_OBJECTIVE_NAME;
|
||||
private final Object[] displaySlots;
|
||||
private final FunctionWithException<Object, Integer> packetToSlot;
|
||||
|
||||
/**
|
||||
* Constructs new instance and loads all required NMS fields.
|
||||
*
|
||||
* @throws ReflectiveOperationException
|
||||
* If something fails
|
||||
*/
|
||||
public DisplayPacketData() throws ReflectiveOperationException {
|
||||
Class<?> ScoreboardObjective = BukkitReflection.getClass("world.scores.Objective", "world.scores.ScoreboardObjective", "ScoreboardObjective");
|
||||
DisplayObjectiveClass = BukkitReflection.getClass(
|
||||
"network.protocol.game.ClientboundSetDisplayObjectivePacket", // Mojang mapped
|
||||
"network.protocol.game.PacketPlayOutScoreboardDisplayObjective", // Bukkit 1.17+
|
||||
"PacketPlayOutScoreboardDisplayObjective", // Bukkit 1.7 - 1.16.5
|
||||
"Packet208SetScoreboardDisplayObjective" // Bukkit 1.5 - 1.6.4
|
||||
);
|
||||
DisplayObjective_OBJECTIVE_NAME = ReflectionUtils.getOnlyField(DisplayObjectiveClass, String.class);
|
||||
if (BukkitReflection.is1_20_2Plus()) {
|
||||
Class<?> DisplaySlot = BukkitReflection.getClass("world.scores.DisplaySlot");
|
||||
displaySlots = (Object[]) DisplaySlot.getDeclaredMethod("values").invoke(null);
|
||||
Field DisplayObjective_POSITION = ReflectionUtils.getOnlyField(DisplayObjectiveClass, DisplaySlot);
|
||||
newDisplayObjective = DisplayObjectiveClass.getConstructor(DisplaySlot, ScoreboardObjective);
|
||||
packetToSlot = packet -> ((Enum<?>)DisplayObjective_POSITION.get(packet)).ordinal();
|
||||
} else {
|
||||
displaySlots = new Object[]{0, 1, 2};
|
||||
Field DisplayObjective_POSITION = ReflectionUtils.getOnlyField(DisplayObjectiveClass, int.class);
|
||||
newDisplayObjective = DisplayObjectiveClass.getConstructor(int.class, ScoreboardObjective);
|
||||
packetToSlot = DisplayObjective_POSITION::getInt;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a packet for setting display slot.
|
||||
*
|
||||
* @param slot
|
||||
* Objective slot
|
||||
* @param objective
|
||||
* Objective to display
|
||||
* @return Packet for setting display slot
|
||||
*/
|
||||
@SneakyThrows
|
||||
public Object setDisplaySlot(int slot, @NonNull Object objective) {
|
||||
return newDisplayObjective.newInstance(displaySlots[slot], objective);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if packet is display objective packet and forwards it to enabled features.
|
||||
*
|
||||
* @param player
|
||||
* Player who received packet
|
||||
* @param packet
|
||||
* Received packet
|
||||
*/
|
||||
@SneakyThrows
|
||||
public void onPacketSend(@NonNull TabPlayer player, @NonNull Object packet) {
|
||||
if (DisplayObjectiveClass.isInstance(packet)) {
|
||||
TAB.getInstance().getFeatureManager().onDisplayObjective(player, packetToSlot.apply(packet),
|
||||
(String) DisplayObjective_OBJECTIVE_NAME.get(packet));
|
||||
}
|
||||
}
|
||||
}
|
||||
-242
@@ -1,242 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.scoreboard.packet;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.PacketSender;
|
||||
import me.neznamy.tab.shared.Limitations;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.platform.decorators.SafeScoreboard;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Scoreboard implementation which uses packets
|
||||
* to send scoreboards to use the full potential on all versions
|
||||
* and server software without any artificial limits.
|
||||
*/
|
||||
public class PacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
|
||||
|
||||
static Class<?> Component;
|
||||
static Class<?> Scoreboard;
|
||||
static Class<?> ScoreboardObjective;
|
||||
static Object emptyScoreboard;
|
||||
static Class<?> NumberFormat;
|
||||
private static Constructor<?> newFixedFormat;
|
||||
|
||||
// Objective packet
|
||||
private static Class<?> ObjectivePacketClass;
|
||||
private static Constructor<?> newObjectivePacket;
|
||||
private static Field Objective_OBJECTIVE_NAME;
|
||||
private static Field Objective_METHOD;
|
||||
private static Field Objective_RENDER_TYPE;
|
||||
static Constructor<?> newScoreboardObjective;
|
||||
private static Method ScoreboardObjective_setDisplayName;
|
||||
private static Enum<?>[] healthDisplays;
|
||||
static Object IScoreboardCriteria_dummy;
|
||||
|
||||
private static ScorePacketData scorePacketData;
|
||||
@Getter private static TeamPacketData teamPacketData;
|
||||
@Getter private static DisplayPacketData displayPacketData;
|
||||
private static PacketSender packetSender;
|
||||
|
||||
public static void load() throws ReflectiveOperationException {
|
||||
int minorVersion = BukkitReflection.getMinorVersion();
|
||||
Scoreboard = BukkitReflection.getClass("world.scores.Scoreboard", "Scoreboard");
|
||||
ScoreboardObjective = BukkitReflection.getClass("world.scores.Objective", "world.scores.ScoreboardObjective", "ScoreboardObjective");
|
||||
Class<?> IScoreboardCriteria = BukkitReflection.getClass(
|
||||
"world.scores.criteria.ObjectiveCriteria", // Mojang mapped
|
||||
"world.scores.criteria.IScoreboardCriteria", // Bukkit 1.17.+
|
||||
"IScoreboardCriteria", // 1.5.1 - 1.16.5
|
||||
"IObjective" // 1.5.0
|
||||
);
|
||||
ObjectivePacketClass = BukkitReflection.getClass(
|
||||
"network.protocol.game.ClientboundSetObjectivePacket", // Mojang mapped
|
||||
"network.protocol.game.PacketPlayOutScoreboardObjective", // Bukkit 1.17+
|
||||
"PacketPlayOutScoreboardObjective", // 1.7 - 1.16.5
|
||||
"Packet206SetScoreboardObjective" // 1.5 - 1.6.4
|
||||
);
|
||||
emptyScoreboard = Scoreboard.getConstructor().newInstance();
|
||||
Objective_OBJECTIVE_NAME = ReflectionUtils.getFields(ObjectivePacketClass, String.class).get(0);
|
||||
List<Field> list = ReflectionUtils.getFields(ObjectivePacketClass, int.class);
|
||||
Objective_METHOD = list.get(list.size()-1);
|
||||
newObjectivePacket = ObjectivePacketClass.getConstructor(ScoreboardObjective, int.class);
|
||||
IScoreboardCriteria_dummy = ReflectionUtils.getFields(IScoreboardCriteria, IScoreboardCriteria).get(0).get(null);
|
||||
newScoreboardObjective = ReflectionUtils.getOnlyConstructor(ScoreboardObjective);
|
||||
if (minorVersion >= 7) {
|
||||
Component = BukkitReflection.getClass("network.chat.Component", "network.chat.IChatBaseComponent", "IChatBaseComponent");
|
||||
}
|
||||
if (minorVersion >= 8) {
|
||||
Class<?> EnumScoreboardHealthDisplay = BukkitReflection.getClass(
|
||||
"world.scores.criteria.ObjectiveCriteria$RenderType",
|
||||
"world.scores.criteria.IScoreboardCriteria$EnumScoreboardHealthDisplay",
|
||||
"IScoreboardCriteria$EnumScoreboardHealthDisplay",
|
||||
"EnumScoreboardHealthDisplay");
|
||||
healthDisplays = (Enum<?>[]) EnumScoreboardHealthDisplay.getMethod("values").invoke(null);
|
||||
if (minorVersion < 13) {
|
||||
Objective_RENDER_TYPE = ReflectionUtils.getOnlyField(ObjectivePacketClass, EnumScoreboardHealthDisplay);
|
||||
}
|
||||
}
|
||||
if (minorVersion < 13) {
|
||||
ScoreboardObjective_setDisplayName = ReflectionUtils.getOnlyMethod(ScoreboardObjective, void.class, String.class);
|
||||
}
|
||||
if (BukkitReflection.is1_20_3Plus()) {
|
||||
NumberFormat = BukkitReflection.getClass("network.chat.numbers.NumberFormat");
|
||||
newFixedFormat = BukkitReflection.getClass("network.chat.numbers.FixedFormat").getConstructor(Component);
|
||||
}
|
||||
scorePacketData = new ScorePacketData();
|
||||
teamPacketData = new TeamPacketData();
|
||||
displayPacketData = new DisplayPacketData();
|
||||
packetSender = new PacketSender();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs new instance with given player.
|
||||
*
|
||||
* @param player
|
||||
* Player this scoreboard will belong to
|
||||
*/
|
||||
public PacketScoreboard(@NonNull BukkitTabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerObjective(@NonNull Objective objective) {
|
||||
packetSender.sendPacket(player, newObjectivePacket(ObjectiveAction.REGISTER, objective));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDisplaySlot(@NonNull Objective objective) {
|
||||
packetSender.sendPacket(player, displayPacketData.setDisplaySlot(objective.getDisplaySlot().ordinal(), newObjective(objective)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterObjective(@NonNull Objective objective) {
|
||||
packetSender.sendPacket(player, newObjectivePacket(ObjectiveAction.UNREGISTER, objective));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateObjective(@NonNull Objective objective) {
|
||||
packetSender.sendPacket(player, newObjectivePacket(ObjectiveAction.UPDATE, objective));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setScore(@NonNull Score score) {
|
||||
packetSender.sendPacket(player, scorePacketData.setScore(score.getObjective().getName(), score.getHolder(), score.getValue(),
|
||||
score.getDisplayName() == null ? null : score.getDisplayName().convert(),
|
||||
score.getNumberFormat() == null ? null : toFixedFormat(score.getNumberFormat())));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeScore(@NonNull Score score) {
|
||||
packetSender.sendPacket(player, scorePacketData.removeScore(score.getObjective().getName(), score.getHolder()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
@SneakyThrows
|
||||
public Object createTeam(@NonNull String name) {
|
||||
return teamPacketData.createTeam.apply(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void registerTeam(@NonNull Team team) {
|
||||
teamPacketData.ScoreboardTeam_players.set(team.getPlatformTeam(), new HashSet<>(team.getPlayers()));
|
||||
teamPacketData.updateTeamData(team, player.getVersion());
|
||||
packetSender.sendPacket(player, teamPacketData.newRegisterTeamPacket.apply(team, player.getVersion()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void unregisterTeam(@NonNull Team team) {
|
||||
packetSender.sendPacket(player, teamPacketData.newUnregisterTeamPacket.apply(team));
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void updateTeam(@NonNull Team team) {
|
||||
teamPacketData.updateTeamData(team, player.getVersion());
|
||||
packetSender.sendPacket(player, teamPacketData.newUpdateTeamPacket.apply(team, player.getVersion()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void onPacketSend(@NonNull Object packet) {
|
||||
if (isAntiOverrideScoreboard()) {
|
||||
displayPacketData.onPacketSend(player, packet);
|
||||
if (ObjectivePacketClass.isInstance(packet)) {
|
||||
TAB.getInstance().getFeatureManager().onObjective(player,
|
||||
Objective_METHOD.getInt(packet), (String) Objective_OBJECTIVE_NAME.get(packet));
|
||||
}
|
||||
}
|
||||
if (isAntiOverrideTeams()) teamPacketData.onPacketSend(player, packet);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private Object newObjectivePacket(int action, @NonNull Objective objective) {
|
||||
// TODO save objectives and reuse them for better performance
|
||||
Object packet = newObjectivePacket.newInstance(newObjective(objective), action);
|
||||
if (BukkitReflection.getMinorVersion() >= 8 && BukkitReflection.getMinorVersion() < 13) {
|
||||
Objective_RENDER_TYPE.set(packet, healthDisplays[objective.getHealthDisplay().ordinal()]);
|
||||
}
|
||||
return packet;
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private Object newObjective(@NonNull Objective objective) {
|
||||
if (BukkitReflection.is1_20_3Plus()) {
|
||||
// 1.20.3+
|
||||
return newScoreboardObjective.newInstance(
|
||||
emptyScoreboard,
|
||||
objective.getName(),
|
||||
null, // Criteria
|
||||
objective.getTitle().convert(),
|
||||
healthDisplays[objective.getHealthDisplay().ordinal()],
|
||||
false, // Auto update
|
||||
objective.getNumberFormat() == null ? null : toFixedFormat(objective.getNumberFormat())
|
||||
);
|
||||
}
|
||||
if (BukkitReflection.getMinorVersion() >= 13) {
|
||||
// 1.13 - 1.20.2
|
||||
return newScoreboardObjective.newInstance(
|
||||
emptyScoreboard,
|
||||
objective.getName(),
|
||||
null, // Criteria
|
||||
objective.getTitle().convert(),
|
||||
healthDisplays[objective.getHealthDisplay().ordinal()]
|
||||
);
|
||||
}
|
||||
// 1.5 - 1.12.2
|
||||
Object nmsObjective = newScoreboardObjective.newInstance(emptyScoreboard, objective.getName(), IScoreboardCriteria_dummy);
|
||||
String title = objective.getTitle().toLegacyText();
|
||||
if (player.getVersion().getMinorVersion() < 13 || TAB.getInstance().getConfiguration().getConfig().isPacketEventsCompensation()) {
|
||||
title = cutTo(title, Limitations.SCOREBOARD_TITLE_PRE_1_13);
|
||||
}
|
||||
ScoreboardObjective_setDisplayName.invoke(nmsObjective, title);
|
||||
return nmsObjective;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@SneakyThrows
|
||||
private static Object toFixedFormat(@NonNull TabComponent component) {
|
||||
if (newFixedFormat == null) return null;
|
||||
return component.toFixedFormat(PacketScoreboard::convertFixedFormat);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@SneakyThrows
|
||||
private static Object convertFixedFormat(@NotNull Object nmsComponent) {
|
||||
return newFixedFormat.newInstance(nmsComponent);
|
||||
}
|
||||
}
|
||||
-123
@@ -1,123 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.scoreboard.packet;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard;
|
||||
import me.neznamy.tab.shared.util.function.BiFunctionWithException;
|
||||
import me.neznamy.tab.shared.util.function.QuintFunction;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Class containing data for scoreboard score set/reset packets.
|
||||
*/
|
||||
public class ScorePacketData {
|
||||
|
||||
private final QuintFunction<String, String, Integer, Object, Object, Object> setScore;
|
||||
private final BiFunctionWithException<String, String, Object> removeScore;
|
||||
|
||||
/**
|
||||
* Constructs new instance and loads all fields. Throws exception if something went wrong.
|
||||
*
|
||||
* @throws ReflectiveOperationException
|
||||
* If something went wrong
|
||||
*/
|
||||
public ScorePacketData() throws ReflectiveOperationException {
|
||||
Class<?> SetScorePacket = BukkitReflection.getClass(
|
||||
"network.protocol.game.ClientboundSetScorePacket", // Mojang mapped
|
||||
"network.protocol.game.PacketPlayOutScoreboardScore", // Bukkit 1.17+
|
||||
"PacketPlayOutScoreboardScore", // 1.7 - 1.16.5
|
||||
"Packet207SetScoreboardScore" // 1.5 - 1.6.4
|
||||
);
|
||||
if (BukkitReflection.is1_20_3Plus()) {
|
||||
QuintFunction<String, String, Integer, Object, Object, Object> setScore0;
|
||||
try {
|
||||
// 1.20.5+
|
||||
Constructor<?> newSetScore = SetScorePacket.getConstructor(String.class, String.class, int.class, Optional.class, Optional.class);
|
||||
setScore0 = (objective, holder, score, displayName, numberFormat) ->
|
||||
newSetScore.newInstance(holder, objective, score, Optional.ofNullable(displayName), Optional.ofNullable(numberFormat));
|
||||
} catch (ReflectiveOperationException e) {
|
||||
// 1.20.3 - 1.20.4
|
||||
Constructor<?> newSetScore = SetScorePacket.getConstructor(String.class, String.class, int.class, PacketScoreboard.Component, PacketScoreboard.NumberFormat);
|
||||
setScore0 = (objective, holder, score, displayName, numberFormat) ->
|
||||
newSetScore.newInstance(holder, objective, score, displayName, numberFormat);
|
||||
}
|
||||
setScore = setScore0;
|
||||
Constructor<?> newResetScore = BukkitReflection.getClass("network.protocol.game.ClientboundResetScorePacket").getConstructor(String.class, String.class);
|
||||
removeScore = (objective, holder) -> newResetScore.newInstance(holder, objective);
|
||||
} else if (BukkitReflection.getMinorVersion() >= 13) {
|
||||
Class<?> actionClass = BukkitReflection.getClass("server.ServerScoreboard$Method", "server.ScoreboardServer$Action", "ScoreboardServer$Action");
|
||||
Constructor<?> newSetScore = SetScorePacket.getConstructor(actionClass, String.class, String.class, int.class);
|
||||
Enum<?>[] scoreboardActions = (Enum<?>[]) actionClass.getMethod("values").invoke(null);
|
||||
setScore = (objective, holder, score, displayName, numberFormat) -> newSetScore.newInstance(scoreboardActions[0], objective, holder, score);
|
||||
removeScore = (objective, holder) -> newSetScore.newInstance(scoreboardActions[1], objective, holder, 0);
|
||||
} else {
|
||||
Class<?> ScoreboardScore = BukkitReflection.getClass("ScoreboardScore");
|
||||
Constructor<?> newResetScore = SetScorePacket.getConstructor(String.class);
|
||||
Field SetScorePacket_SCORE = ReflectionUtils.getFields(SetScorePacket, int.class).get(0);
|
||||
Constructor<?> newScoreboardScore = ScoreboardScore.getConstructor(PacketScoreboard.Scoreboard, PacketScoreboard.ScoreboardObjective, String.class);
|
||||
Constructor<?> newSetScore;
|
||||
if (BukkitReflection.getMinorVersion() >= 8) {
|
||||
newSetScore = SetScorePacket.getConstructor(ScoreboardScore);
|
||||
} else {
|
||||
newSetScore = SetScorePacket.getConstructor(ScoreboardScore, int.class);
|
||||
}
|
||||
setScore = (objective, holder, score, displayName, numberFormat) -> {
|
||||
Object scoreboardScore = newScoreboardScore.newInstance(
|
||||
PacketScoreboard.emptyScoreboard,
|
||||
PacketScoreboard.newScoreboardObjective.newInstance(PacketScoreboard.emptyScoreboard, objective, PacketScoreboard.IScoreboardCriteria_dummy),
|
||||
holder
|
||||
);
|
||||
Object packet;
|
||||
if (BukkitReflection.getMinorVersion() >= 8) {
|
||||
packet = newSetScore.newInstance(scoreboardScore);
|
||||
} else {
|
||||
packet = newSetScore.newInstance(scoreboardScore, Scoreboard.ScoreAction.CHANGE);
|
||||
}
|
||||
SetScorePacket_SCORE.set(packet, score);
|
||||
return packet;
|
||||
};
|
||||
removeScore = (objective, holder) -> newResetScore.newInstance(holder);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates set score packet.
|
||||
*
|
||||
* @param objective
|
||||
* Objective name
|
||||
* @param scoreHolder
|
||||
* Score holder
|
||||
* @param score
|
||||
* Score value
|
||||
* @param displayName
|
||||
* Score holder display name
|
||||
* @param numberFormat
|
||||
* Score value number format
|
||||
* @return Set score packet with given parameters
|
||||
*/
|
||||
@SneakyThrows
|
||||
public Object setScore(@NonNull String objective, @NonNull String scoreHolder, int score,
|
||||
@Nullable Object displayName, @Nullable Object numberFormat) {
|
||||
return setScore.apply(objective, scoreHolder, score, displayName, numberFormat);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates reset score packet.
|
||||
*
|
||||
* @param objective
|
||||
* Objective name
|
||||
* @param scoreHolder
|
||||
* Score holder
|
||||
* @return Reset score packet with given parameters.
|
||||
*/
|
||||
@SneakyThrows
|
||||
public Object removeScore(@NonNull String objective, @NonNull String scoreHolder) {
|
||||
return removeScore.apply(objective, scoreHolder);
|
||||
}
|
||||
}
|
||||
-224
@@ -1,224 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.scoreboard.packet;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
|
||||
import me.neznamy.tab.shared.Limitations;
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard.TeamAction;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import me.neznamy.tab.shared.platform.decorators.SafeScoreboard;
|
||||
import me.neznamy.tab.shared.platform.decorators.SafeScoreboard.Team;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import me.neznamy.tab.shared.util.function.BiConsumerWithException;
|
||||
import me.neznamy.tab.shared.util.function.BiFunctionWithException;
|
||||
import me.neznamy.tab.shared.util.function.ConsumerWithException;
|
||||
import me.neznamy.tab.shared.util.function.FunctionWithException;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Class storing all team related fields and methods.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public class TeamPacketData {
|
||||
|
||||
// Classes
|
||||
private final Class<?> ScoreboardClass = BukkitReflection.getClass("world.scores.Scoreboard", "Scoreboard");
|
||||
private final Class<?> ScoreboardTeamClass = BukkitReflection.getClass("world.scores.PlayerTeam", "world.scores.ScoreboardTeam", "ScoreboardTeam");
|
||||
private final Class<?> TeamPacketClass = BukkitReflection.getClass(
|
||||
"network.protocol.game.ClientboundSetPlayerTeamPacket", // Mojang mapped
|
||||
"network.protocol.game.PacketPlayOutScoreboardTeam", // Bukkit 1.17+
|
||||
"PacketPlayOutScoreboardTeam", // Bukkit 1.7 - 1.16.5
|
||||
"Packet209SetScoreboardTeam" // 1.5 - 1.6.4
|
||||
);
|
||||
|
||||
// Constructors
|
||||
private final Constructor<?> newScoreboardTeam = ScoreboardTeamClass.getConstructor(ScoreboardClass, String.class);
|
||||
|
||||
// Fields
|
||||
public final Field ScoreboardTeam_players = ReflectionUtils.getFields(ScoreboardTeamClass, Set.class).get(0);
|
||||
private final Field ScoreboardTeam_allowFriendlyFire = ReflectionUtils.getFields(ScoreboardTeamClass, boolean.class).get(0);
|
||||
private final Field ScoreboardTeam_seeFriendlyInvisibles = ReflectionUtils.getFields(ScoreboardTeamClass, boolean.class).get(1);
|
||||
private final Field TeamPacket_NAME = ReflectionUtils.getFields(TeamPacketClass, String.class).get(0);
|
||||
private final Field TeamPacket_ACTION;
|
||||
private final Field TeamPacket_PLAYERS = ReflectionUtils.getOnlyField(TeamPacketClass, Collection.class);
|
||||
|
||||
// Objects
|
||||
private final Object emptyScoreboard = ScoreboardClass.getConstructor().newInstance();
|
||||
|
||||
// Team / packet creation
|
||||
public final FunctionWithException<String, Object> createTeam = name -> newScoreboardTeam.newInstance(emptyScoreboard, name);
|
||||
public final BiFunctionWithException<Team, ProtocolVersion, Object> newRegisterTeamPacket;
|
||||
public final FunctionWithException<Team, Object> newUnregisterTeamPacket;
|
||||
public final BiFunctionWithException<Team, ProtocolVersion, Object> newUpdateTeamPacket;
|
||||
|
||||
// Team property setters
|
||||
private final BiConsumerWithException<Team, ProtocolVersion> updatePlatformTeamPrefix = loadPrefix();
|
||||
private final BiConsumerWithException<Team, ProtocolVersion> updatePlatformTeamSuffix = loadSuffix();
|
||||
private final ConsumerWithException<Team> updatePlatformTeamColor = loadColor();
|
||||
private final ConsumerWithException<Team> updatePlatformTeamVisibility = loadVisibility();
|
||||
private final ConsumerWithException<Team> updatePlatformTeamCollision = loadCollision();
|
||||
|
||||
/**
|
||||
* Constructs new instance and loads all required NMS classes, fields, and methods.
|
||||
* If anything fails, exception is thrown.
|
||||
*
|
||||
* @throws ReflectiveOperationException
|
||||
* If anything fails
|
||||
*/
|
||||
public TeamPacketData() throws ReflectiveOperationException {
|
||||
int minorVersion = BukkitReflection.getMinorVersion();
|
||||
List<Field> intFields = ReflectionUtils.getInstanceFields(TeamPacketClass, int.class);
|
||||
if (minorVersion >= 8 && minorVersion <= 12) {
|
||||
TeamPacket_ACTION = intFields.get(1);
|
||||
} else {
|
||||
TeamPacket_ACTION = intFields.get(0);
|
||||
}
|
||||
|
||||
// Packet constructors
|
||||
if (BukkitReflection.getMinorVersion() >= 17) {
|
||||
Method TeamPacketConstructor_of = ReflectionUtils.getOnlyMethod(TeamPacketClass, TeamPacketClass, ScoreboardTeamClass);
|
||||
Method TeamPacketConstructor_ofBoolean = ReflectionUtils.getOnlyMethod(TeamPacketClass, TeamPacketClass, ScoreboardTeamClass, boolean.class);
|
||||
newRegisterTeamPacket = (team, version) -> TeamPacketConstructor_ofBoolean.invoke(null, team.getPlatformTeam(), true);
|
||||
newUnregisterTeamPacket = team -> TeamPacketConstructor_of.invoke(null, team.getPlatformTeam());
|
||||
newUpdateTeamPacket = (team, version) -> TeamPacketConstructor_ofBoolean.invoke(null, team.getPlatformTeam(), false);
|
||||
} else {
|
||||
Constructor<?> newTeamPacket = TeamPacketClass.getConstructor(ScoreboardTeamClass, int.class);
|
||||
newRegisterTeamPacket = (team, version) -> newTeamPacket.newInstance(team.getPlatformTeam(), TeamAction.CREATE);
|
||||
newUnregisterTeamPacket = team -> newTeamPacket.newInstance(team.getPlatformTeam(), TeamAction.REMOVE);
|
||||
newUpdateTeamPacket = (team, version) -> newTeamPacket.newInstance(team.getPlatformTeam(), TeamAction.UPDATE);
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
@NotNull
|
||||
private BiConsumerWithException<Team, ProtocolVersion> loadPrefix() {
|
||||
if (BukkitReflection.getMinorVersion() >= 13) {
|
||||
Class<?> Component = BukkitReflection.getClass("network.chat.Component", "network.chat.IChatBaseComponent", "IChatBaseComponent");
|
||||
Field prefix = ReflectionUtils.getFields(ScoreboardTeamClass, Component).get(1);
|
||||
return (team, version) -> prefix.set(team.getPlatformTeam(), team.getPrefix().convert());
|
||||
} else {
|
||||
Field prefix = ReflectionUtils.getFields(ScoreboardTeamClass, String.class).get(2);
|
||||
return (team, version) -> {
|
||||
String legacy = team.getPrefix().toLegacyText();
|
||||
if (version.getMinorVersion() < 13 || TAB.getInstance().getConfiguration().getConfig().isPacketEventsCompensation()) {
|
||||
legacy = SafeScoreboard.cutTo(legacy, Limitations.TEAM_PREFIX_SUFFIX_PRE_1_13);
|
||||
}
|
||||
prefix.set(team.getPlatformTeam(), legacy);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
@NotNull
|
||||
private BiConsumerWithException<Team, ProtocolVersion> loadSuffix() {
|
||||
if (BukkitReflection.getMinorVersion() >= 13) {
|
||||
Class<?> Component = BukkitReflection.getClass("network.chat.Component", "network.chat.IChatBaseComponent", "IChatBaseComponent");
|
||||
Field suffix = ReflectionUtils.getFields(ScoreboardTeamClass, Component).get(2);
|
||||
return (team, version) -> suffix.set(team.getPlatformTeam(), team.getSuffix().convert());
|
||||
} else {
|
||||
Field suffix = ReflectionUtils.getFields(ScoreboardTeamClass, String.class).get(3);
|
||||
return (team, version) -> {
|
||||
String legacy = team.getSuffix().toLegacyText();
|
||||
if (version.getMinorVersion() < 13 || TAB.getInstance().getConfiguration().getConfig().isPacketEventsCompensation()) {
|
||||
legacy = SafeScoreboard.cutTo(legacy, Limitations.TEAM_PREFIX_SUFFIX_PRE_1_13);
|
||||
}
|
||||
suffix.set(team.getPlatformTeam(), legacy);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
@NotNull
|
||||
private ConsumerWithException<Team> loadColor() {
|
||||
if (BukkitReflection.getMinorVersion() >= 13) {
|
||||
Class<?> EnumChatFormatClass = BukkitReflection.getClass("ChatFormatting", "EnumChatFormat");
|
||||
Enum<?>[] chatFormats = (Enum<?>[]) EnumChatFormatClass.getMethod("values").invoke(null);
|
||||
Method setColor = ReflectionUtils.getOnlyMethod(ScoreboardTeamClass, void.class, EnumChatFormatClass);
|
||||
return team -> setColor.invoke(team.getPlatformTeam(), chatFormats[team.getColor().getLegacyColor().ordinal()]);
|
||||
} else {
|
||||
return team -> {};
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
@NotNull
|
||||
private ConsumerWithException<Team> loadVisibility() {
|
||||
if (BukkitReflection.getMinorVersion() >= 8) {
|
||||
Class<?> enumNameTagVisibility = BukkitReflection.getClass(
|
||||
"world.scores.Team$Visibility", // Mojang mapped
|
||||
"world.scores.ScoreboardTeamBase$EnumNameTagVisibility", // Bukkit 1.17+
|
||||
"ScoreboardTeamBase$EnumNameTagVisibility", // Bukkit 1.8.1 - 1.16.5
|
||||
"EnumNameTagVisibility" // Bukkit 1.8.0
|
||||
);
|
||||
Enum<?>[] nameVisibilities = (Enum<?>[]) enumNameTagVisibility.getMethod("values").invoke(null);
|
||||
Field ScoreboardTeam_nameTagVisibility = ReflectionUtils.getFields(ScoreboardTeamClass, enumNameTagVisibility).get(0);
|
||||
return team -> ScoreboardTeam_nameTagVisibility.set(team.getPlatformTeam(), nameVisibilities[team.getVisibility().ordinal()]);
|
||||
} else {
|
||||
return team -> {};
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
@NotNull
|
||||
private ConsumerWithException<Team> loadCollision() {
|
||||
if (BukkitReflection.getMinorVersion() >= 9) {
|
||||
Class<?> enumTeamPush = BukkitReflection.getClass("world.scores.Team$CollisionRule",
|
||||
"world.scores.ScoreboardTeamBase$EnumTeamPush", "ScoreboardTeamBase$EnumTeamPush");
|
||||
Enum<?>[] collisionRules = (Enum<?>[]) enumTeamPush.getMethod("values").invoke(null);
|
||||
Method setCollisionRule = ReflectionUtils.getOnlyMethod(ScoreboardTeamClass, void.class, enumTeamPush);
|
||||
return team -> setCollisionRule.invoke(team.getPlatformTeam(), collisionRules[team.getCollision().ordinal()]);
|
||||
} else {
|
||||
return team -> {};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates team properties.
|
||||
*
|
||||
* @param team
|
||||
* Team to update
|
||||
* @param clientVersion
|
||||
* Version to create the values for
|
||||
*/
|
||||
@SneakyThrows
|
||||
public void updateTeamData(@NonNull Team team, @NotNull ProtocolVersion clientVersion) {
|
||||
Object nmsTeam = team.getPlatformTeam();
|
||||
ScoreboardTeam_allowFriendlyFire.set(nmsTeam, (team.getOptions() & 0x1) > 0);
|
||||
ScoreboardTeam_seeFriendlyInvisibles.set(nmsTeam, (team.getOptions() & 0x2) > 0);
|
||||
updatePlatformTeamPrefix.accept(team, clientVersion);
|
||||
updatePlatformTeamSuffix.accept(team, clientVersion);
|
||||
updatePlatformTeamColor.accept(team);
|
||||
updatePlatformTeamVisibility.accept(team);
|
||||
updatePlatformTeamCollision.accept(team);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the packet is a team packet and removes all real players from the team
|
||||
* if sent by other plugins and anti-override is fully active on a player.
|
||||
*
|
||||
* @param player
|
||||
* Player who received the packet
|
||||
* @param packet
|
||||
* The received packet
|
||||
*/
|
||||
@SneakyThrows
|
||||
public void onPacketSend(@NonNull TabPlayer player, @NonNull Object packet) {
|
||||
if (!TeamPacketClass.isInstance(packet)) return;
|
||||
int action = TeamPacket_ACTION.getInt(packet);
|
||||
if (action == TeamAction.UPDATE) return;
|
||||
Collection<String> players = (Collection<String>) TeamPacket_PLAYERS.get(packet);
|
||||
if (players == null) players = Collections.emptyList();
|
||||
TeamPacket_PLAYERS.set(packet, ((SafeScoreboard<?>)player.getScoreboard()).onTeamPacket(
|
||||
action, (String) TeamPacket_NAME.get(packet), players));
|
||||
}
|
||||
}
|
||||
-189
@@ -1,189 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.scoreboard.viaversion;
|
||||
|
||||
import com.viaversion.viaversion.api.Via;
|
||||
import com.viaversion.viaversion.api.connection.UserConnection;
|
||||
import com.viaversion.viaversion.api.protocol.Protocol;
|
||||
import com.viaversion.viaversion.api.protocol.packet.PacketType;
|
||||
import com.viaversion.viaversion.api.protocol.packet.PacketWrapper;
|
||||
import com.viaversion.viaversion.api.protocol.packet.State;
|
||||
import com.viaversion.viaversion.api.type.Types;
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.chat.component.SimpleTextComponent;
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.shared.platform.decorators.SafeScoreboard;
|
||||
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Scoreboard abstract implementation using ViaVersion packets,
|
||||
* focused to unlock new features on older versions, and also
|
||||
* optimize packet conversion between versions.
|
||||
*/
|
||||
public abstract class ViaScoreboard extends SafeScoreboard<BukkitTabPlayer> {
|
||||
|
||||
private static final long CHECK_DELAY = 50;
|
||||
private static final Object DUMMY = new Object();
|
||||
|
||||
protected final Class<? extends Protocol> protocol;
|
||||
protected final PacketType setDisplayObjective;
|
||||
private final PacketType setObjective;
|
||||
protected final PacketType setScore;
|
||||
private final PacketType setPlayerTeam;
|
||||
/** User connection this scoreboard belongs to */
|
||||
protected final UserConnection connection;
|
||||
|
||||
private transient ScheduledFuture<?> task;
|
||||
private transient final Queue<PacketWrapper> queuedPackets = new ConcurrentLinkedQueue<>();
|
||||
|
||||
/**
|
||||
* Constructs new instance with given player.
|
||||
*
|
||||
* @param player
|
||||
* Player this scoreboard will belong to
|
||||
* @param protocol
|
||||
* Protocol to be sent packets through
|
||||
* @param setDisplayObjective
|
||||
* Objective creation packet
|
||||
* @param setObjective
|
||||
* Objective update packet
|
||||
* @param setScore
|
||||
* Score update packet
|
||||
* @param setPlayerTeam
|
||||
* Player team packet
|
||||
*/
|
||||
public ViaScoreboard(@NonNull BukkitTabPlayer player, @NonNull Class<? extends Protocol> protocol, @NonNull PacketType setDisplayObjective, @NonNull PacketType setObjective, @NonNull PacketType setScore, @NonNull PacketType setPlayerTeam) {
|
||||
super(player);
|
||||
this.protocol = protocol;
|
||||
this.setDisplayObjective = setDisplayObjective;
|
||||
this.setObjective = setObjective;
|
||||
this.setScore = setScore;
|
||||
this.setPlayerTeam = setPlayerTeam;
|
||||
this.connection = Via.getManager().getConnectionManager().getConnectedClient(player.getUniqueId());
|
||||
|
||||
// Queue packets until connection is available
|
||||
// Is important to use scheduleWithFixedDelay() to avoid task overlap
|
||||
task = connection.getChannel().eventLoop().scheduleWithFixedDelay(() -> {
|
||||
if (connection.getProtocolInfo().getClientState() == State.PLAY) {
|
||||
PacketWrapper packet;
|
||||
while ((packet = queuedPackets.poll()) != null) {
|
||||
packet.send(protocol);
|
||||
}
|
||||
task.cancel(true);
|
||||
}
|
||||
}, 0L, CHECK_DELAY, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerObjective(@NonNull Objective objective) {
|
||||
sendObjectiveUpdate(ObjectiveAction.REGISTER, objective);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterObjective(@NonNull Objective objective) {
|
||||
sendObjectiveUpdate(ObjectiveAction.UNREGISTER, objective);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateObjective(@NonNull Objective objective) {
|
||||
sendObjectiveUpdate(ObjectiveAction.UPDATE, objective);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull Object createTeam(@NonNull String name) {
|
||||
// This implementation does not use team objects
|
||||
return DUMMY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerTeam(@NonNull Team team) {
|
||||
sendTeamUpdate(TeamAction.CREATE, team);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterTeam(@NonNull Team team) {
|
||||
sendTeamUpdate(TeamAction.REMOVE, team);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateTeam(@NonNull Team team) {
|
||||
sendTeamUpdate(TeamAction.UPDATE, team);
|
||||
}
|
||||
|
||||
protected void sendObjectiveUpdate(int mode, @NonNull Objective objective) {
|
||||
final PacketWrapper packet = PacketWrapper.create(setObjective, null, connection);
|
||||
|
||||
// Objective name
|
||||
packet.write(Types.STRING, objective.getName());
|
||||
// Mode
|
||||
packet.write(Types.BYTE, (byte) mode);
|
||||
// Optional
|
||||
if (mode == ObjectiveAction.REGISTER || mode == ObjectiveAction.UPDATE) {
|
||||
writeObjectiveDisplay(packet, objective);
|
||||
}
|
||||
|
||||
send(packet);
|
||||
}
|
||||
|
||||
@SuppressWarnings("fallthrough")
|
||||
protected void sendTeamUpdate(int mode, @NonNull Team team) {
|
||||
final PacketWrapper packet = PacketWrapper.create(setPlayerTeam, null, connection);
|
||||
|
||||
// Team name (max 16 characters on pre 1.20.3)
|
||||
packet.write(Types.STRING, team.getName());
|
||||
// Method
|
||||
packet.write(Types.BYTE, (byte) mode);
|
||||
switch (mode) {
|
||||
case TeamAction.REMOVE:
|
||||
break;
|
||||
case TeamAction.CREATE:
|
||||
case TeamAction.UPDATE:
|
||||
// Team display name
|
||||
writeComponent(packet, SimpleTextComponent.text(team.getName()));
|
||||
// Friendly flags
|
||||
packet.write(Types.BYTE, (byte) team.getOptions());
|
||||
// Name tag visibility
|
||||
packet.write(Types.STRING, team.getVisibility().toString());
|
||||
// Collision rule
|
||||
packet.write(Types.STRING, team.getCollision().toString());
|
||||
// Team color
|
||||
packet.write(Types.VAR_INT, team.getColor().getLegacyColor().ordinal());
|
||||
// Team prefix
|
||||
writeComponent(packet, team.getPrefix());
|
||||
// Team suffix
|
||||
writeComponent(packet, team.getSuffix());
|
||||
if (mode == TeamAction.UPDATE) {
|
||||
break;
|
||||
}
|
||||
// Intentional fallthrough for CREATE mode
|
||||
case TeamAction.ADD_PLAYER:
|
||||
case TeamAction.REMOVE_PLAYER:
|
||||
// Entities
|
||||
packet.write(Types.VAR_INT, team.getPlayers().size());
|
||||
for (String entity : team.getPlayers()) {
|
||||
// Entity (player username in this case) (max 40 characters on pre 1.20.3)
|
||||
packet.write(Types.STRING, entity);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Invalid team update mode: " + mode);
|
||||
}
|
||||
|
||||
send(packet);
|
||||
}
|
||||
|
||||
protected abstract void writeComponent(@NonNull PacketWrapper packet, @NonNull TabComponent component);
|
||||
|
||||
protected abstract void writeObjectiveDisplay(@NonNull PacketWrapper packet, @NonNull Objective objective);
|
||||
|
||||
protected void send(@NonNull PacketWrapper packet) {
|
||||
if (!task.isCancelled()) {
|
||||
queuedPackets.add(packet);
|
||||
} else {
|
||||
packet.scheduleSend(protocol);
|
||||
}
|
||||
}
|
||||
}
|
||||
-82
@@ -1,82 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.scoreboard.viaversion;
|
||||
|
||||
import com.viaversion.viaversion.api.protocol.Protocol;
|
||||
import com.viaversion.viaversion.api.protocol.packet.PacketType;
|
||||
import com.viaversion.viaversion.api.protocol.packet.PacketWrapper;
|
||||
import com.viaversion.viaversion.api.type.Types;
|
||||
import com.viaversion.viaversion.protocols.v1_12_2to1_13.Protocol1_12_2To1_13;
|
||||
import com.viaversion.viaversion.protocols.v1_12_2to1_13.packet.ClientboundPackets1_13;
|
||||
import com.viaversion.viaversion.util.ComponentUtil;
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
|
||||
/**
|
||||
* Scoreboard implementation using ViaVersion packets
|
||||
* to unlock +1.13 player features on pre 1.13 servers.
|
||||
*/
|
||||
public class ViaScoreboard113 extends ViaScoreboard {
|
||||
|
||||
/**
|
||||
* Constructs new instance with given player.
|
||||
*
|
||||
* @param player Player this scoreboard will belong to
|
||||
*/
|
||||
public ViaScoreboard113(@NonNull BukkitTabPlayer player) {
|
||||
this(player, Protocol1_12_2To1_13.class, ClientboundPackets1_13.SET_DISPLAY_OBJECTIVE, ClientboundPackets1_13.SET_OBJECTIVE, ClientboundPackets1_13.SET_SCORE, ClientboundPackets1_13.SET_PLAYER_TEAM);
|
||||
}
|
||||
|
||||
protected ViaScoreboard113(@NonNull BukkitTabPlayer player, @NonNull Class<? extends Protocol> protocol, @NonNull PacketType setDisplayObjective, @NonNull PacketType setObjective, @NonNull PacketType setScore, @NonNull PacketType setPlayerTeam) {
|
||||
super(player, protocol, setDisplayObjective, setObjective, setScore, setPlayerTeam);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDisplaySlot(@NonNull Objective objective) {
|
||||
PacketWrapper packet = PacketWrapper.create(setDisplayObjective, null, connection);
|
||||
|
||||
packet.write(Types.BYTE, (byte) objective.getDisplaySlot().ordinal());
|
||||
packet.write(Types.STRING, objective.getName());
|
||||
|
||||
send(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setScore(@NonNull Score score) {
|
||||
sendScoreUpdate(ScoreAction.CHANGE, score);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeScore(@NonNull Score score) {
|
||||
sendScoreUpdate(ScoreAction.REMOVE, score);
|
||||
}
|
||||
|
||||
protected void sendScoreUpdate(int action, @NonNull Score score) {
|
||||
final PacketWrapper packet = PacketWrapper.create(setScore, null, connection);
|
||||
|
||||
// Entity name (player username in this case)
|
||||
packet.write(Types.STRING, score.getHolder());
|
||||
// Action
|
||||
packet.write(Types.VAR_INT, action);
|
||||
// Objective name (max 16 characters)
|
||||
packet.write(Types.STRING, score.getObjective().getName());
|
||||
if (action == 0) {
|
||||
// Value
|
||||
packet.write(Types.VAR_INT, score.getValue());
|
||||
}
|
||||
|
||||
send(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeComponent(@NonNull PacketWrapper packet, @NonNull TabComponent component) {
|
||||
packet.write(Types.COMPONENT, ComponentUtil.legacyToJson(component.toLegacyText()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeObjectiveDisplay(@NonNull PacketWrapper packet, @NonNull Objective objective) {
|
||||
// Objective value
|
||||
writeComponent(packet, objective.getTitle());
|
||||
// Type
|
||||
packet.write(Types.VAR_INT, objective.getHealthDisplay() == HealthDisplay.INTEGER ? 0 : 1);
|
||||
}
|
||||
}
|
||||
-36
@@ -1,36 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.scoreboard.viaversion;
|
||||
|
||||
import com.viaversion.viaversion.api.protocol.Protocol;
|
||||
import com.viaversion.viaversion.api.protocol.packet.PacketType;
|
||||
import com.viaversion.viaversion.api.protocol.packet.PacketWrapper;
|
||||
import com.viaversion.viaversion.api.type.Types;
|
||||
import com.viaversion.viaversion.protocols.v1_15_2to1_16.Protocol1_15_2To1_16;
|
||||
import com.viaversion.viaversion.protocols.v1_15_2to1_16.packet.ClientboundPackets1_16;
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
|
||||
/**
|
||||
* Scoreboard implementation using ViaVersion packets
|
||||
* to unlock +1.16 player features on pre 1.16 servers.
|
||||
*/
|
||||
public class ViaScoreboard116 extends ViaScoreboard113 {
|
||||
|
||||
/**
|
||||
* Constructs new instance with given player.
|
||||
*
|
||||
* @param player Player this scoreboard will belong to
|
||||
*/
|
||||
public ViaScoreboard116(@NonNull BukkitTabPlayer player) {
|
||||
this(player, Protocol1_15_2To1_16.class, ClientboundPackets1_16.SET_DISPLAY_OBJECTIVE, ClientboundPackets1_16.SET_OBJECTIVE, ClientboundPackets1_16.SET_SCORE, ClientboundPackets1_16.SET_PLAYER_TEAM);
|
||||
}
|
||||
|
||||
protected ViaScoreboard116(@NonNull BukkitTabPlayer player, @NonNull Class<? extends Protocol> protocol, @NonNull PacketType setDisplayObjective, @NonNull PacketType setObjective, @NonNull PacketType setScore, @NonNull PacketType setPlayerTeam) {
|
||||
super(player, protocol, setDisplayObjective, setObjective, setScore, setPlayerTeam);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeComponent(@NonNull PacketWrapper packet, @NonNull TabComponent component) {
|
||||
packet.write(Types.COMPONENT, component.toViaVersion());
|
||||
}
|
||||
}
|
||||
-101
@@ -1,101 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.scoreboard.viaversion;
|
||||
|
||||
import com.viaversion.viaversion.api.protocol.Protocol;
|
||||
import com.viaversion.viaversion.api.protocol.packet.PacketType;
|
||||
import com.viaversion.viaversion.api.protocol.packet.PacketWrapper;
|
||||
import com.viaversion.viaversion.api.type.Types;
|
||||
import com.viaversion.viaversion.protocols.v1_20_2to1_20_3.Protocol1_20_2To1_20_3;
|
||||
import com.viaversion.viaversion.protocols.v1_20_2to1_20_3.packet.ClientboundPackets1_20_3;
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Scoreboard implementation using ViaVersion packets
|
||||
* to unlock +1.20.3 player features on pre 1.20.3 servers.
|
||||
*/
|
||||
public class ViaScoreboard1203 extends ViaScoreboard116 {
|
||||
|
||||
private final PacketType resetScore;
|
||||
|
||||
/**
|
||||
* Constructs new instance with given player.
|
||||
*
|
||||
* @param player Player this scoreboard will belong to
|
||||
*/
|
||||
public ViaScoreboard1203(@NonNull BukkitTabPlayer player) {
|
||||
this(player, Protocol1_20_2To1_20_3.class, ClientboundPackets1_20_3.SET_DISPLAY_OBJECTIVE, ClientboundPackets1_20_3.SET_OBJECTIVE, ClientboundPackets1_20_3.SET_SCORE, ClientboundPackets1_20_3.RESET_SCORE, ClientboundPackets1_20_3.SET_PLAYER_TEAM);
|
||||
}
|
||||
|
||||
protected ViaScoreboard1203(@NonNull BukkitTabPlayer player, @NonNull Class<? extends Protocol> protocol, @NonNull PacketType setDisplayObjective, @NonNull PacketType setObjective, @NonNull PacketType setScore, @NonNull PacketType resetScore, @NonNull PacketType setPlayerTeam) {
|
||||
super(player, protocol, setDisplayObjective, setObjective, setScore, setPlayerTeam);
|
||||
this.resetScore = resetScore;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDisplaySlot(@NonNull Objective objective) {
|
||||
PacketWrapper packet = PacketWrapper.create(setDisplayObjective, null, connection);
|
||||
|
||||
packet.write(Types.VAR_INT, objective.getDisplaySlot().ordinal());
|
||||
packet.write(Types.STRING, objective.getName());
|
||||
|
||||
send(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setScore(@NonNull Score score) {
|
||||
final PacketWrapper packet = PacketWrapper.create(setScore, null, connection);
|
||||
|
||||
// Entity name (player username in this case)
|
||||
packet.write(Types.STRING, score.getHolder());
|
||||
// Objective name
|
||||
packet.write(Types.STRING, score.getObjective().getName());
|
||||
// Value
|
||||
packet.write(Types.VAR_INT, score.getValue());
|
||||
// Display name
|
||||
packet.write(Types.OPTIONAL_TAG, score.getDisplayName() == null ? null : score.getDisplayName().toViaVersionTag());
|
||||
// Number format
|
||||
writeNumberFormat(packet, score.getNumberFormat());
|
||||
|
||||
send(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeScore(@NonNull Score score) {
|
||||
final PacketWrapper packet = PacketWrapper.create(resetScore, null, connection);
|
||||
|
||||
// Entity name (player username in this case)
|
||||
packet.write(Types.STRING, score.getHolder());
|
||||
// Objective name
|
||||
packet.write(Types.OPTIONAL_STRING, score.getObjective().getName().isEmpty() ? null : score.getObjective().getName());
|
||||
|
||||
send(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeComponent(@NonNull PacketWrapper packet, @NonNull TabComponent component) {
|
||||
packet.write(Types.TAG, component.toViaVersionTag());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeObjectiveDisplay(@NonNull PacketWrapper packet, @NonNull Objective objective) {
|
||||
super.writeObjectiveDisplay(packet, objective);
|
||||
writeNumberFormat(packet, objective.getNumberFormat());
|
||||
}
|
||||
|
||||
protected void writeNumberFormat(@NonNull PacketWrapper packet, @Nullable TabComponent content) {
|
||||
// For now, only fixed format is supported by TAB
|
||||
if (content == null) {
|
||||
// Has number format
|
||||
packet.write(Types.BOOLEAN, false);
|
||||
} else {
|
||||
// Has number format
|
||||
packet.write(Types.BOOLEAN, true);
|
||||
// Fixed format
|
||||
packet.write(Types.VAR_INT, 2);
|
||||
// Content
|
||||
packet.write(Types.TAG, content.toViaVersionTag());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,150 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.tablist;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.PacketSender;
|
||||
import me.neznamy.tab.shared.Limitations;
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import me.neznamy.tab.shared.platform.decorators.TrackedTabList;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import me.neznamy.tab.shared.util.function.TriFunctionWithException;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* TabList handler for 1.7- servers using packets.
|
||||
*/
|
||||
public class PacketTabList17 extends TrackedTabList<BukkitTabPlayer> {
|
||||
|
||||
private static TriFunctionWithException<String, Boolean, Integer, Object> newPacket;
|
||||
private static PacketSender packetSender;
|
||||
|
||||
/** Because entries are identified by names and not uuids on 1.7- */
|
||||
@NotNull
|
||||
private final Map<UUID, String> userNames = new HashMap<>();
|
||||
|
||||
@NotNull
|
||||
private final Map<UUID, String> displayNames = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Constructs new instance with given player.
|
||||
*
|
||||
* @param player
|
||||
* Player this tablist will belong to.
|
||||
*/
|
||||
public PacketTabList17(@NonNull BukkitTabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to load all required NMS classes, fields and methods.
|
||||
* If anything fails, throws an exception.
|
||||
*
|
||||
* @throws ReflectiveOperationException
|
||||
* If something goes wrong
|
||||
*/
|
||||
public static void load() throws ReflectiveOperationException {
|
||||
Class<?> PlayerInfoClass = BukkitReflection.getClass("PacketPlayOutPlayerInfo", "Packet201PlayerInfo");
|
||||
try {
|
||||
Constructor<?> newPlayerInfo = PlayerInfoClass.getConstructor(String.class, boolean.class, int.class);
|
||||
newPacket = newPlayerInfo::newInstance;
|
||||
} catch (NoSuchMethodException e) {
|
||||
// 1.7.10 spigot with protocol hack
|
||||
Constructor<?> newPlayerInfo = PlayerInfoClass.getConstructor();
|
||||
Field USERNAME = ReflectionUtils.getField(PlayerInfoClass, "username");
|
||||
Field ACTION = ReflectionUtils.getField(PlayerInfoClass, "action");
|
||||
Field PING = ReflectionUtils.getField(PlayerInfoClass, "ping");
|
||||
newPacket = (name, addOrUpdate, latency) -> {
|
||||
Object packet = newPlayerInfo.newInstance();
|
||||
USERNAME.set(packet, name);
|
||||
ACTION.set(packet, addOrUpdate ? 3 : 4);
|
||||
PING.set(packet, latency);
|
||||
return packet;
|
||||
};
|
||||
}
|
||||
packetSender = new PacketSender();
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void removeEntry(@NonNull UUID entry) {
|
||||
if (!displayNames.containsKey(entry)) return; // Entry not tracked by TAB
|
||||
packetSender.sendPacket(player, newPacket.apply(displayNames.get(entry), false, 0));
|
||||
userNames.remove(entry);
|
||||
displayNames.remove(entry);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void updateDisplayName0(@NonNull UUID entry, @Nullable TabComponent displayName) {
|
||||
if (!displayNames.containsKey(entry)) return; // Entry not tracked by TAB
|
||||
packetSender.sendPacket(player, newPacket.apply(displayNames.get(entry), false, 0));
|
||||
addEntry0(new Entry(entry, userNames.get(entry), null, false, 0, 0, displayName, 0, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void updateLatency(@NonNull UUID entry, int latency) {
|
||||
if (!displayNames.containsKey(entry)) return; // Entry not tracked by TAB
|
||||
packetSender.sendPacket(player, newPacket.apply(displayNames.get(entry), true, latency));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateGameMode(@NonNull UUID entry, int gameMode) {
|
||||
// Added in 1.8
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateListed(@NonNull UUID entry, boolean listed) {
|
||||
// Added in 1.19.3
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateListOrder(@NonNull UUID entry, int listOrder) {
|
||||
// Added in 1.21.2
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateHat(@NonNull UUID entry, boolean showHat) {
|
||||
// Added in 1.21.4
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsEntry(@NonNull UUID entry) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPlayerListHeaderFooter(@NonNull TabComponent header, @NonNull TabComponent footer) {
|
||||
// Added in 1.8
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void addEntry0(@NonNull Entry entry) {
|
||||
String displayName;
|
||||
if (entry.getDisplayName() != null) {
|
||||
displayName = entry.getDisplayName().toLegacyText();
|
||||
if (displayName.length() > Limitations.MAX_DISPLAY_NAME_LENGTH_1_7) displayName = displayName.substring(0, Limitations.MAX_DISPLAY_NAME_LENGTH_1_7);
|
||||
} else {
|
||||
displayName = entry.getName();
|
||||
}
|
||||
packetSender.sendPacket(player, newPacket.apply(displayName, true, entry.getLatency()));
|
||||
userNames.put(entry.getUniqueId(), entry.getName());
|
||||
displayNames.put(entry.getUniqueId(), displayName);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Skin getSkin() {
|
||||
return null; // Added in 1.8
|
||||
}
|
||||
}
|
||||
@@ -1,266 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.tablist;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import com.mojang.authlib.properties.Property;
|
||||
import lombok.NonNull;
|
||||
import lombok.Setter;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitUtils;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.PacketSender;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import me.neznamy.tab.shared.platform.decorators.TrackedTabList;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* TabList handler for 1.8 - 1.19.2 servers using packets.
|
||||
*/
|
||||
@Setter
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
public class PacketTabList18 extends TrackedTabList<BukkitTabPlayer> {
|
||||
|
||||
@Nullable
|
||||
public static SkinData skinData;
|
||||
|
||||
protected static Class<?> PlayerInfoClass;
|
||||
protected static Constructor<?> newPlayerInfo;
|
||||
protected static Field ACTION;
|
||||
protected static Field PLAYERS;
|
||||
protected static Class<Enum> ActionClass;
|
||||
|
||||
protected static Constructor<?> newPlayerInfoData;
|
||||
protected static Field PlayerInfoData_Profile;
|
||||
protected static Field PlayerInfoData_Latency;
|
||||
protected static Field PlayerInfoData_DisplayName;
|
||||
|
||||
protected static Object[] gameModes;
|
||||
|
||||
protected static PacketSender packetSender;
|
||||
|
||||
/**
|
||||
* Constructs new instance with given player.
|
||||
*
|
||||
* @param player
|
||||
* Player this tablist will belong to.
|
||||
*/
|
||||
public PacketTabList18(@NonNull BukkitTabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to load all required NMS classes, fields and methods.
|
||||
* If anything fails, throws an exception.
|
||||
*
|
||||
* @throws ReflectiveOperationException
|
||||
* If something goes wrong
|
||||
*/
|
||||
public static void load() throws ReflectiveOperationException {
|
||||
Class<Enum> EnumGamemodeClass = (Class<Enum>) BukkitReflection.getClass("world.level.GameType",
|
||||
"world.level.EnumGamemode", "EnumGamemode", "WorldSettings$EnumGamemode");
|
||||
ActionClass = (Class<Enum>) BukkitReflection.getClass(
|
||||
"network.protocol.game.ClientboundPlayerInfoPacket$Action", // Mojang 1.17 - 1.19.2
|
||||
"network.protocol.game.PacketPlayOutPlayerInfo$EnumPlayerInfoAction", // Bukkit 1.17 - 1.19.2
|
||||
"PacketPlayOutPlayerInfo$EnumPlayerInfoAction", // Bukkit 1.8.1 - 1.16.5
|
||||
"EnumPlayerInfoAction" // Bukkit 1.8.0
|
||||
);
|
||||
PlayerInfoClass = BukkitReflection.getClass("network.protocol.game.ClientboundPlayerInfoUpdatePacket",
|
||||
"network.protocol.game.ClientboundPlayerInfoPacket",
|
||||
"network.protocol.game.PacketPlayOutPlayerInfo", "PacketPlayOutPlayerInfo");
|
||||
Class<?> playerInfoDataClass = BukkitReflection.getClass(
|
||||
"network.protocol.game.ClientboundPlayerInfoPacket$PlayerUpdate", // Mojang 1.17 - 1.19.2
|
||||
"network.protocol.game.PacketPlayOutPlayerInfo$PlayerInfoData", // Bukkit 1.17 - 1.19.2
|
||||
"PacketPlayOutPlayerInfo$PlayerInfoData", // Bukkit 1.8.1 - 1.16.5
|
||||
"PlayerInfoData" // Bukkit 1.8.0
|
||||
);
|
||||
|
||||
Class<?> classType = BukkitReflection.getMinorVersion() >= 17 ? Collection.class : Iterable.class;
|
||||
newPlayerInfo = PlayerInfoClass.getConstructor(ActionClass, classType);
|
||||
ACTION = ReflectionUtils.getOnlyField(PlayerInfoClass, ActionClass);
|
||||
|
||||
loadSharedContent(playerInfoDataClass, EnumGamemodeClass);
|
||||
|
||||
newPlayerInfoData = playerInfoDataClass.getConstructors()[0]; // #1105, a specific 1.8.8 fork has 2 constructors
|
||||
}
|
||||
|
||||
protected static void loadSharedContent(Class<?> infoData, Class<Enum> gameMode) throws ReflectiveOperationException {
|
||||
Class<?> IChatBaseComponent = BukkitReflection.getClass("network.chat.Component", "network.chat.IChatBaseComponent", "IChatBaseComponent");
|
||||
PLAYERS = ReflectionUtils.getOnlyField(PlayerInfoClass, List.class);
|
||||
PlayerInfoData_Profile = ReflectionUtils.getOnlyField(infoData, GameProfile.class);
|
||||
PlayerInfoData_Latency = ReflectionUtils.getFields(infoData, int.class).get(0);
|
||||
PlayerInfoData_DisplayName = ReflectionUtils.getOnlyField(infoData, IChatBaseComponent);
|
||||
gameModes = new Object[] {
|
||||
Enum.valueOf(gameMode, "SURVIVAL"),
|
||||
Enum.valueOf(gameMode, "CREATIVE"),
|
||||
Enum.valueOf(gameMode, "ADVENTURE"),
|
||||
Enum.valueOf(gameMode, "SPECTATOR")
|
||||
};
|
||||
packetSender = new PacketSender();
|
||||
try {
|
||||
skinData = new SkinData();
|
||||
} catch (Exception e) {
|
||||
BukkitUtils.compatibilityError(e, "getting player's game profile", null,
|
||||
"Player skins not working in layout feature");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeEntry(@NonNull UUID entry) {
|
||||
packetSender.sendPacket(player,
|
||||
createPacket(Action.REMOVE_PLAYER, entry, "", null, false, 0, 0, null, 0, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDisplayName0(@NonNull UUID entry, @Nullable TabComponent displayName) {
|
||||
packetSender.sendPacket(player,
|
||||
createPacket(Action.UPDATE_DISPLAY_NAME, entry, "", null, false, 0, 0, displayName, 0, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateLatency(@NonNull UUID entry, int latency) {
|
||||
packetSender.sendPacket(player,
|
||||
createPacket(Action.UPDATE_LATENCY, entry, "", null, false, latency, 0, null, 0, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateGameMode(@NonNull UUID entry, int gameMode) {
|
||||
packetSender.sendPacket(player,
|
||||
createPacket(Action.UPDATE_GAME_MODE, entry, "", null, false, 0, gameMode, null, 0, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateListed(@NonNull UUID entry, boolean listed) {
|
||||
// Added in 1.19.3
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateListOrder(@NonNull UUID entry, int listOrder) {
|
||||
// Added in 1.21.2
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateHat(@NonNull UUID entry, boolean showHat) {
|
||||
// Added in 1.21.4
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addEntry0(@NonNull Entry entry) {
|
||||
packetSender.sendPacket(player,
|
||||
createPacket(Action.ADD_PLAYER, entry.getUniqueId(), entry.getName(), entry.getSkin(), entry.isListed(),
|
||||
entry.getLatency(), entry.getGameMode(), entry.getDisplayName(), entry.getListOrder(), entry.isShowHat()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPlayerListHeaderFooter(@NonNull TabComponent header, @NonNull TabComponent footer) {
|
||||
player.getPlatform().getHeaderFooter().set(player, header, footer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates packet from given parameters.
|
||||
*
|
||||
* @param action
|
||||
* Packet action
|
||||
* @param id
|
||||
* Entry UUID
|
||||
* @param name
|
||||
* Entry name
|
||||
* @param skin
|
||||
* Entry skin
|
||||
* @param listed
|
||||
* Whether entry should be listed or not
|
||||
* @param latency
|
||||
* Entry latency
|
||||
* @param gameMode
|
||||
* Entry game mode
|
||||
* @param displayName
|
||||
* Entry display name
|
||||
* @param listOrder
|
||||
* Entry list order
|
||||
* @param showHat
|
||||
* Show hat flag
|
||||
* @return Packet from given parameters
|
||||
*/
|
||||
@SneakyThrows
|
||||
@NotNull
|
||||
public Object createPacket(@NonNull Action action, @NonNull UUID id, @NonNull String name, @Nullable Skin skin,
|
||||
boolean listed, int latency, int gameMode, @Nullable TabComponent displayName, int listOrder, boolean showHat) {
|
||||
Object packet = newPlayerInfo.newInstance(Enum.valueOf(ActionClass, action.name()), Collections.emptyList());
|
||||
List<Object> parameters = new ArrayList<>();
|
||||
if (newPlayerInfoData.getParameterTypes()[0] == PlayerInfoClass) {
|
||||
parameters.add(packet);
|
||||
}
|
||||
parameters.add(createProfile(id, name, skin));
|
||||
parameters.add(latency);
|
||||
parameters.add(gameModes[gameMode]);
|
||||
parameters.add(displayName == null ? null : displayName.convert());
|
||||
if (BukkitReflection.getMinorVersion() >= 19) parameters.add(null);
|
||||
PLAYERS.set(packet, Collections.singletonList(newPlayerInfoData.newInstance(parameters.toArray())));
|
||||
return packet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates GameProfile from given parameters.
|
||||
*
|
||||
* @param id
|
||||
* Profile ID
|
||||
* @param name
|
||||
* Profile name
|
||||
* @param skin
|
||||
* Player skin
|
||||
* @return GameProfile from given parameters
|
||||
*/
|
||||
@NotNull
|
||||
public GameProfile createProfile(@NonNull UUID id, @NonNull String name, @Nullable Skin skin) {
|
||||
GameProfile profile = new GameProfile(id, name);
|
||||
if (skin != null) {
|
||||
profile.getProperties().put(TabList.TEXTURES_PROPERTY,
|
||||
new Property(TabList.TEXTURES_PROPERTY, skin.getValue(), skin.getSignature()));
|
||||
}
|
||||
return profile;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void onPacketSend(@NonNull Object packet) {
|
||||
if (!(PlayerInfoClass.isInstance(packet))) return;
|
||||
String action = ACTION.get(packet).toString();
|
||||
for (Object nmsData : (List<?>) PLAYERS.get(packet)) {
|
||||
GameProfile profile = (GameProfile) PlayerInfoData_Profile.get(nmsData);
|
||||
UUID id = profile.getId();
|
||||
if (action.equals(Action.UPDATE_DISPLAY_NAME.name()) || action.equals(Action.ADD_PLAYER.name())) {
|
||||
TabComponent expectedName = getExpectedDisplayNames().get(id);
|
||||
if (expectedName != null) PlayerInfoData_DisplayName.set(nmsData, expectedName.convert());
|
||||
}
|
||||
if (action.equals(Action.UPDATE_LATENCY.name()) || action.equals(Action.ADD_PLAYER.name())) {
|
||||
int oldLatency = PlayerInfoData_Latency.getInt(nmsData);
|
||||
int newLatency = TAB.getInstance().getFeatureManager().onLatencyChange(player, id, oldLatency);
|
||||
if (oldLatency != newLatency) {
|
||||
PlayerInfoData_Latency.set(nmsData, newLatency);
|
||||
}
|
||||
}
|
||||
if (action.equals(Action.ADD_PLAYER.name())) {
|
||||
TAB.getInstance().getFeatureManager().onEntryAdd(player, id, profile.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsEntry(@NonNull UUID entry) {
|
||||
return true; // TODO?
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Skin getSkin() {
|
||||
if (skinData == null) return null;
|
||||
return skinData.getSkin(player);
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.tablist;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import com.mojang.authlib.properties.Property;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* Class for getting skin of players.
|
||||
*/
|
||||
public class SkinData {
|
||||
|
||||
// There is only supposed to be one, however there are exceptions:
|
||||
// #1 - CatServer adds another method
|
||||
// #2 - Random mods may perform deep hack into the server and add another one (see #1089)
|
||||
// Get first and hope for the best, alternatively players may not have correct skins in layout, but who cares
|
||||
private final Method getProfile = ReflectionUtils.getMethods(BukkitReflection.getClass(
|
||||
"world.entity.player.Player", "world.entity.player.EntityHuman", "EntityHuman"), GameProfile.class).get(0);
|
||||
|
||||
/**
|
||||
* Returns player's skin. If server is in offline mode, returns {@code null}.
|
||||
*
|
||||
* @param player
|
||||
* Player to get skin of
|
||||
* @return Player's skin or {@code null}.
|
||||
*/
|
||||
@Nullable
|
||||
@SneakyThrows
|
||||
public TabList.Skin getSkin(@NotNull BukkitTabPlayer player) {
|
||||
Collection<Property> col = ((GameProfile) getProfile.invoke(player.getHandle())).getProperties().get(TabList.TEXTURES_PROPERTY);
|
||||
if (col.isEmpty()) return null; //offline mode
|
||||
Property property = col.iterator().next();
|
||||
if (BukkitReflection.is1_20_2Plus()) {
|
||||
return new TabList.Skin(
|
||||
(String) property.getClass().getMethod("value").invoke(property),
|
||||
(String) property.getClass().getMethod("signature").invoke(property)
|
||||
);
|
||||
} else {
|
||||
return new TabList.Skin(property.getValue(), property.getSignature());
|
||||
}
|
||||
}
|
||||
}
|
||||
-178
@@ -1,178 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.tablist.viaversion;
|
||||
|
||||
import com.viaversion.viaversion.api.Via;
|
||||
import com.viaversion.viaversion.api.connection.UserConnection;
|
||||
import com.viaversion.viaversion.api.protocol.Protocol;
|
||||
import com.viaversion.viaversion.api.protocol.packet.PacketType;
|
||||
import com.viaversion.viaversion.api.protocol.packet.PacketWrapper;
|
||||
import com.viaversion.viaversion.api.protocol.packet.State;
|
||||
import com.viaversion.viaversion.api.type.Types;
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.tablist.PacketTabList18;
|
||||
import me.neznamy.tab.shared.platform.decorators.TrackedTabList;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.BitSet;
|
||||
import java.util.Queue;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* TabList abstract handler using ViaVersion packets,
|
||||
* focused to unlock new features on older versions,
|
||||
* and also optimize packet conversion between versions.
|
||||
*/
|
||||
public abstract class ViaTabList extends TrackedTabList<BukkitTabPlayer> {
|
||||
|
||||
private static final long CHECK_DELAY = 50;
|
||||
|
||||
protected final Class<? extends Protocol> protocol;
|
||||
private final PacketType playerInfoUpdate;
|
||||
private final PacketType tabList;
|
||||
/** User connection this tablist belongs to */
|
||||
protected final UserConnection connection;
|
||||
|
||||
private transient ScheduledFuture<?> task;
|
||||
private transient final Queue<PacketWrapper> queuedPackets = new ConcurrentLinkedQueue<>();
|
||||
|
||||
/**
|
||||
*
|
||||
* @param player
|
||||
* Player this tablist will belong to
|
||||
* @param protocol
|
||||
* Protocol to be sent packets through
|
||||
* @param playerInfoUpdate
|
||||
* Player information update packet
|
||||
* @param tabList
|
||||
* Tab list packet
|
||||
*/
|
||||
public ViaTabList(@NonNull BukkitTabPlayer player, @NonNull Class<? extends Protocol> protocol, @NonNull PacketType playerInfoUpdate, @NonNull PacketType tabList) {
|
||||
super(player);
|
||||
this.protocol = protocol;
|
||||
this.playerInfoUpdate = playerInfoUpdate;
|
||||
this.tabList = tabList;
|
||||
this.connection = Via.getManager().getConnectionManager().getConnectedClient(player.getUniqueId());
|
||||
|
||||
// Queue packets until connection is available
|
||||
// Is important to use scheduleWithFixedDelay() to avoid task overlap
|
||||
task = connection.getChannel().eventLoop().scheduleWithFixedDelay(() -> {
|
||||
if (connection.getProtocolInfo().getClientState() == State.PLAY) {
|
||||
PacketWrapper packet;
|
||||
while ((packet = queuedPackets.poll()) != null) {
|
||||
packet.send(protocol);
|
||||
}
|
||||
task.cancel(true);
|
||||
}
|
||||
}, 0L, CHECK_DELAY, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsEntry(@NonNull UUID entry) {
|
||||
return true; // TODO?
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addEntry0(@NonNull Entry entry) {
|
||||
sendInfoUpdate(Action.ADD_PLAYER, entry.getUniqueId(), entry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDisplayName0(@NonNull UUID entry, @Nullable TabComponent displayName) {
|
||||
sendInfoUpdate(Action.UPDATE_DISPLAY_NAME, entry, displayName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateLatency(@NonNull UUID entry, int latency) {
|
||||
sendInfoUpdate(Action.UPDATE_LATENCY, entry, latency);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateGameMode(@NonNull UUID entry, int gameMode) {
|
||||
sendInfoUpdate(Action.UPDATE_GAME_MODE, entry, gameMode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPlayerListHeaderFooter(@NonNull TabComponent header, @NonNull TabComponent footer) {
|
||||
final PacketWrapper packet = PacketWrapper.create(tabList, null, connection);
|
||||
|
||||
writeComponent(packet, header);
|
||||
writeComponent(packet, footer);
|
||||
|
||||
send(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Skin getSkin() {
|
||||
if (PacketTabList18.skinData == null) return null;
|
||||
return PacketTabList18.skinData.getSkin(player);
|
||||
}
|
||||
|
||||
protected void sendInfoUpdate(@NonNull Action action, @NonNull UUID uniqueId, Object value) {
|
||||
final PacketWrapper packet = PacketWrapper.create(playerInfoUpdate, null, connection);
|
||||
|
||||
// Action
|
||||
writeAction(packet, action);
|
||||
// Players size
|
||||
packet.write(Types.VAR_INT, 1);
|
||||
// Players
|
||||
packet.write(Types.UUID, uniqueId);
|
||||
switch (action) {
|
||||
case ADD_PLAYER:
|
||||
writeEntry(packet, (Entry) value);
|
||||
break;
|
||||
case REMOVE_PLAYER:
|
||||
break;
|
||||
case UPDATE_GAME_MODE:
|
||||
case UPDATE_LATENCY:
|
||||
case UPDATE_LIST_ORDER:
|
||||
packet.write(Types.VAR_INT, (int) value);
|
||||
break;
|
||||
case UPDATE_LISTED:
|
||||
case UPDATE_HAT:
|
||||
packet.write(Types.BOOLEAN, (boolean) value);
|
||||
break;
|
||||
case UPDATE_DISPLAY_NAME:
|
||||
writeOptionalComponent(packet, (TabComponent) value);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Cannot send info update with action " + action.name());
|
||||
}
|
||||
|
||||
send(packet);
|
||||
}
|
||||
|
||||
protected abstract void writeComponent(@NonNull PacketWrapper packet, @NonNull TabComponent component);
|
||||
|
||||
protected abstract void writeOptionalComponent(@NonNull PacketWrapper packet, @Nullable TabComponent component);
|
||||
|
||||
protected abstract void writeAction(@NonNull PacketWrapper packet, @NonNull Action action);
|
||||
|
||||
protected abstract void writeEntry(@NonNull PacketWrapper packet, @NonNull Entry entry);
|
||||
|
||||
@NotNull
|
||||
protected static BitSet bitSet(int nbits, int action) {
|
||||
final BitSet bitSet = new BitSet(nbits);
|
||||
bitSet.set(action);
|
||||
return bitSet;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
protected static BitSet bitSet(int nbits, int fromAction, int toAction) {
|
||||
final BitSet bit = new BitSet(nbits);
|
||||
bit.set(fromAction, toAction);
|
||||
return bit;
|
||||
}
|
||||
|
||||
protected void send(@NonNull PacketWrapper packet) {
|
||||
if (!task.isCancelled()) {
|
||||
queuedPackets.add(packet);
|
||||
} else {
|
||||
packet.scheduleSend(protocol);
|
||||
}
|
||||
}
|
||||
}
|
||||
-114
@@ -1,114 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.tablist.viaversion;
|
||||
|
||||
import com.viaversion.viaversion.api.protocol.Protocol;
|
||||
import com.viaversion.viaversion.api.protocol.packet.PacketType;
|
||||
import com.viaversion.viaversion.api.protocol.packet.PacketWrapper;
|
||||
import com.viaversion.viaversion.api.type.Types;
|
||||
import com.viaversion.viaversion.protocols.v1_15_2to1_16.Protocol1_15_2To1_16;
|
||||
import com.viaversion.viaversion.protocols.v1_15_2to1_16.packet.ClientboundPackets1_16;
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* TabList handler using ViaVersion packets,
|
||||
* to unlock +1.16 player features on pre 1.16 servers.
|
||||
*/
|
||||
public class ViaTabList116 extends ViaTabList {
|
||||
|
||||
private static final int ADD_PLAYER = 0;
|
||||
private static final int UPDATE_GAME_MODE = 1;
|
||||
private static final int UPDATE_LATENCY = 2;
|
||||
private static final int UPDATE_DISPLAY_NAME = 3;
|
||||
private static final int REMOVE_PLAYER = 4;
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters.
|
||||
*
|
||||
* @param player
|
||||
* Player this tablist will belong to
|
||||
*/
|
||||
public ViaTabList116(@NotNull BukkitTabPlayer player) {
|
||||
this(player, Protocol1_15_2To1_16.class, ClientboundPackets1_16.PLAYER_INFO, ClientboundPackets1_16.TAB_LIST);
|
||||
}
|
||||
|
||||
protected ViaTabList116(@NonNull BukkitTabPlayer player, @NonNull Class<? extends Protocol> protocol, @NonNull PacketType playerInfoUpdate, @NonNull PacketType tabList) {
|
||||
super(player, protocol, playerInfoUpdate, tabList);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeEntry(@NonNull UUID entry) {
|
||||
sendInfoUpdate(Action.REMOVE_PLAYER, entry, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateListed(@NonNull UUID entry, boolean listed) {
|
||||
// Added on 1.19.3
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateListOrder(@NonNull UUID entry, int listOrder) {
|
||||
// Added in 1.21.2
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateHat(@NonNull UUID entry, boolean showHat) {
|
||||
// Added in 1.21.4
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeComponent(@NonNull PacketWrapper packet, @NonNull TabComponent component) {
|
||||
packet.write(Types.COMPONENT, component.toViaVersion());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeOptionalComponent(@NonNull PacketWrapper packet, @Nullable TabComponent component) {
|
||||
packet.write(Types.OPTIONAL_COMPONENT, component == null ? null : component.toViaVersion());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeAction(@NonNull PacketWrapper packet, @NonNull Action action) {
|
||||
switch (action) {
|
||||
case ADD_PLAYER:
|
||||
packet.write(Types.VAR_INT, ADD_PLAYER);
|
||||
break;
|
||||
case REMOVE_PLAYER:
|
||||
packet.write(Types.VAR_INT, REMOVE_PLAYER);
|
||||
break;
|
||||
case UPDATE_DISPLAY_NAME:
|
||||
packet.write(Types.VAR_INT, UPDATE_DISPLAY_NAME);
|
||||
break;
|
||||
case UPDATE_LATENCY:
|
||||
packet.write(Types.VAR_INT, UPDATE_LATENCY);
|
||||
break;
|
||||
case UPDATE_GAME_MODE:
|
||||
packet.write(Types.VAR_INT, UPDATE_GAME_MODE);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Cannot write " + action.name() + " for 1.16 packet");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeEntry(@NonNull PacketWrapper packet, @NonNull Entry entry) {
|
||||
packet.write(Types.STRING, entry.getName());
|
||||
if (entry.getSkin() == null) {
|
||||
// Properties size
|
||||
packet.write(Types.VAR_INT, 0);
|
||||
} else {
|
||||
// Properties size
|
||||
packet.write(Types.VAR_INT, 1);
|
||||
// Properties
|
||||
packet.write(Types.STRING, entry.getName());
|
||||
packet.write(Types.STRING, entry.getSkin().getValue());
|
||||
packet.write(Types.OPTIONAL_STRING, entry.getSkin().getSignature());
|
||||
}
|
||||
packet.write(Types.VAR_INT, entry.getGameMode());
|
||||
packet.write(Types.VAR_INT, entry.getLatency());
|
||||
writeOptionalComponent(packet, entry.getDisplayName());
|
||||
}
|
||||
}
|
||||
-105
@@ -1,105 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.tablist.viaversion;
|
||||
|
||||
import com.viaversion.viaversion.api.protocol.Protocol;
|
||||
import com.viaversion.viaversion.api.protocol.packet.PacketType;
|
||||
import com.viaversion.viaversion.api.protocol.packet.PacketWrapper;
|
||||
import com.viaversion.viaversion.api.type.Types;
|
||||
import com.viaversion.viaversion.protocols.v1_19_1to1_19_3.packet.ClientboundPackets1_19_3;
|
||||
import com.viaversion.viaversion.protocols.v1_19_1to1_19_3.Protocol1_19_1To1_19_3;
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.BitSet;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* TabList handler using ViaVersion packets,
|
||||
* to unlock +1.19.3 player features on pre 1.19.3 servers.
|
||||
*/
|
||||
public class ViaTabList1193 extends ViaTabList116 {
|
||||
|
||||
private static final BitSet ADD_PLAYER = bitSet(6, 0, 6);
|
||||
//private static final BitSet INITIALIZE_CHAT = bitSet(6, 1);
|
||||
private static final BitSet UPDATE_GAME_MODE = bitSet(6, 2);
|
||||
private static final BitSet UPDATE_LISTED = bitSet(6, 3);
|
||||
private static final BitSet UPDATE_LATENCY = bitSet(6, 4);
|
||||
private static final BitSet UPDATE_DISPLAY_NAME = bitSet(6, 5);
|
||||
|
||||
private final PacketType playerInfoRemove;
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters.
|
||||
*
|
||||
* @param player
|
||||
* Player this tablist will belong to
|
||||
*/
|
||||
public ViaTabList1193(@NotNull BukkitTabPlayer player) {
|
||||
this(player, Protocol1_19_1To1_19_3.class, ClientboundPackets1_19_3.PLAYER_INFO_REMOVE, ClientboundPackets1_19_3.PLAYER_INFO_UPDATE, ClientboundPackets1_19_3.TAB_LIST);
|
||||
}
|
||||
|
||||
protected ViaTabList1193(@NonNull BukkitTabPlayer player, @NonNull Class<? extends Protocol> protocol, @Nullable PacketType playerInfoRemove, @NonNull PacketType playerInfoUpdate, @NonNull PacketType tabList) {
|
||||
super(player, protocol, playerInfoUpdate, tabList);
|
||||
this.playerInfoRemove = playerInfoRemove;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeEntry(@NonNull UUID entry) {
|
||||
final PacketWrapper packet = PacketWrapper.create(playerInfoRemove, null, connection);
|
||||
|
||||
// Players
|
||||
packet.write(Types.UUID_ARRAY, new UUID[] { entry });
|
||||
|
||||
send(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateListed(@NonNull UUID entry, boolean listed) {
|
||||
sendInfoUpdate(Action.UPDATE_LISTED, entry, listed);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeAction(@NonNull PacketWrapper packet, @NonNull Action action) {
|
||||
switch (action) {
|
||||
case ADD_PLAYER:
|
||||
packet.write(Types.PROFILE_ACTIONS_ENUM1_19_3, ADD_PLAYER);
|
||||
break;
|
||||
case UPDATE_DISPLAY_NAME:
|
||||
packet.write(Types.PROFILE_ACTIONS_ENUM1_19_3, UPDATE_DISPLAY_NAME);
|
||||
break;
|
||||
case UPDATE_LATENCY:
|
||||
packet.write(Types.PROFILE_ACTIONS_ENUM1_19_3, UPDATE_LATENCY);
|
||||
break;
|
||||
case UPDATE_GAME_MODE:
|
||||
packet.write(Types.PROFILE_ACTIONS_ENUM1_19_3, UPDATE_GAME_MODE);
|
||||
break;
|
||||
case UPDATE_LISTED:
|
||||
packet.write(Types.PROFILE_ACTIONS_ENUM1_19_3, UPDATE_LISTED);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Cannot write " + action.name() + " for 1.19.3 packet");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeEntry(@NonNull PacketWrapper packet, @NonNull Entry entry) {
|
||||
packet.write(Types.STRING, entry.getName());
|
||||
if (entry.getSkin() == null) {
|
||||
// Properties size
|
||||
packet.write(Types.VAR_INT, 0);
|
||||
} else {
|
||||
// Properties size
|
||||
packet.write(Types.VAR_INT, 1);
|
||||
// Properties
|
||||
packet.write(Types.STRING, entry.getName());
|
||||
packet.write(Types.STRING, entry.getSkin().getValue());
|
||||
packet.write(Types.OPTIONAL_STRING, entry.getSkin().getSignature());
|
||||
}
|
||||
packet.write(Types.BOOLEAN, false); // No chat session data
|
||||
packet.write(Types.VAR_INT, entry.getGameMode());
|
||||
packet.write(Types.BOOLEAN, entry.isListed());
|
||||
packet.write(Types.VAR_INT, entry.getLatency());
|
||||
writeOptionalComponent(packet, entry.getDisplayName());
|
||||
}
|
||||
}
|
||||
-92
@@ -1,92 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.tablist.viaversion;
|
||||
|
||||
import com.viaversion.viaversion.api.protocol.Protocol;
|
||||
import com.viaversion.viaversion.api.protocol.packet.PacketType;
|
||||
import com.viaversion.viaversion.api.protocol.packet.PacketWrapper;
|
||||
import com.viaversion.viaversion.api.type.Types;
|
||||
import com.viaversion.viaversion.protocols.v1_21to1_21_2.packet.ClientboundPackets1_21_2;
|
||||
import com.viaversion.viaversion.protocols.v1_21to1_21_2.Protocol1_21To1_21_2;
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.BitSet;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* TabList handler using ViaVersion packets,
|
||||
* to unlock +1.21.2 player features on pre 1.21.2 servers.
|
||||
*/
|
||||
public class ViaTabList1212 extends ViaTabList1193 {
|
||||
|
||||
private static final BitSet ADD_PLAYER = bitSet(7, 0, 7);
|
||||
//private static final BitSet INITIALIZE_CHAT = bitSet(7, 1);
|
||||
private static final BitSet UPDATE_GAME_MODE = bitSet(7, 2);
|
||||
private static final BitSet UPDATE_LISTED = bitSet(7, 3);
|
||||
private static final BitSet UPDATE_LATENCY = bitSet(7, 4);
|
||||
private static final BitSet UPDATE_DISPLAY_NAME = bitSet(7, 5);
|
||||
private static final BitSet UPDATE_LIST_ORDER = bitSet(7, 6);
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters.
|
||||
*
|
||||
* @param player
|
||||
* Player this tablist will belong to
|
||||
*/
|
||||
public ViaTabList1212(@NotNull BukkitTabPlayer player) {
|
||||
super(player, Protocol1_21To1_21_2.class, ClientboundPackets1_21_2.PLAYER_INFO_REMOVE, ClientboundPackets1_21_2.PLAYER_INFO_UPDATE, ClientboundPackets1_21_2.TAB_LIST);
|
||||
}
|
||||
|
||||
protected ViaTabList1212(@NonNull BukkitTabPlayer player, @NonNull Class<? extends Protocol> protocol, @Nullable PacketType playerInfoRemove, @NonNull PacketType playerInfoUpdate, @NonNull PacketType tabList) {
|
||||
super(player, protocol, playerInfoRemove, playerInfoUpdate, tabList);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateListOrder(@NonNull UUID entry, int listOrder) {
|
||||
sendInfoUpdate(Action.UPDATE_LIST_ORDER, entry, listOrder);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeComponent(@NonNull PacketWrapper packet, @NonNull TabComponent component) {
|
||||
packet.write(Types.TAG, component.toViaVersionTag());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeOptionalComponent(@NonNull PacketWrapper packet, @Nullable TabComponent component) {
|
||||
packet.write(Types.OPTIONAL_TAG, component == null ? null : component.toViaVersionTag());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeAction(@NonNull PacketWrapper packet, @NonNull Action action) {
|
||||
switch (action) {
|
||||
case ADD_PLAYER:
|
||||
packet.write(Types.PROFILE_ACTIONS_ENUM1_21_2, ADD_PLAYER);
|
||||
break;
|
||||
case UPDATE_DISPLAY_NAME:
|
||||
packet.write(Types.PROFILE_ACTIONS_ENUM1_21_2, UPDATE_DISPLAY_NAME);
|
||||
break;
|
||||
case UPDATE_LATENCY:
|
||||
packet.write(Types.PROFILE_ACTIONS_ENUM1_21_2, UPDATE_LATENCY);
|
||||
break;
|
||||
case UPDATE_GAME_MODE:
|
||||
packet.write(Types.PROFILE_ACTIONS_ENUM1_21_2, UPDATE_GAME_MODE);
|
||||
break;
|
||||
case UPDATE_LISTED:
|
||||
packet.write(Types.PROFILE_ACTIONS_ENUM1_21_2, UPDATE_LISTED);
|
||||
break;
|
||||
case UPDATE_LIST_ORDER:
|
||||
packet.write(Types.PROFILE_ACTIONS_ENUM1_21_2, UPDATE_LIST_ORDER);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Cannot write " + action.name() + " for 1.21.2 packet");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeEntry(@NonNull PacketWrapper packet, @NonNull Entry entry) {
|
||||
super.writeEntry(packet, entry);
|
||||
packet.write(Types.VAR_INT, entry.getListOrder());
|
||||
}
|
||||
}
|
||||
Loaded 100 of 251 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user