mirror of
https://github.com/NEZNAMY/TAB.git
synced 2026-11-13 12:42:06 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ea79c9c5dd | ||
|
|
650d13ed71 | ||
|
|
169b8b8e94 | ||
|
|
8cef4a5bcb | ||
|
|
70b40802cc | ||
|
|
416e707cda | ||
|
|
12578293bc | ||
|
|
042ab6850b | ||
|
|
09fc042630 | ||
|
|
c73d351bf1 | ||
|
|
4d4e0a9c2a | ||
|
|
2fd29b2347 | ||
|
|
f332068318 | ||
|
|
186c79f93c | ||
|
|
3770066485 | ||
|
|
17b0ffe52b | ||
|
|
d259b5bb78 | ||
|
|
f6c8d24177 | ||
|
|
ab3bd61d3c | ||
|
|
6a5cee8f0e | ||
|
|
549732fc8b | ||
|
|
09d400c6fb | ||
|
|
7e1cdafa78 | ||
|
|
434c0216d5 | ||
|
|
d5a0ae8e71 | ||
|
|
79e56c6a30 | ||
|
|
11cc1c3f25 | ||
|
|
8b343bcfb6 | ||
|
|
ecd83beb7d | ||
|
|
438f21fb11 | ||
|
|
dd1524cf31 | ||
|
|
0f85aa5384 | ||
|
|
4644a0ae96 | ||
|
|
84b3efe0fc | ||
|
|
511acbd5ff | ||
|
|
8749b3b354 | ||
|
|
b66e63d242 | ||
|
|
ba951238c3 | ||
|
|
a4eb76f1cf | ||
|
|
c486899dcf | ||
|
|
b212f0d5be | ||
|
|
caa534ff7f | ||
|
|
9c2fac6749 | ||
|
|
2a4f918a10 | ||
|
|
b45e2afe4c | ||
|
|
2c7237e5d9 | ||
|
|
26477e3150 | ||
|
|
cba8cb57d5 | ||
|
|
599b4822e5 | ||
|
|
fbd150b353 | ||
|
|
10b252e934 | ||
|
|
e9dfcdeb10 | ||
|
|
9cb2f27754 | ||
|
|
a090a83b7d | ||
|
|
992e661e4c | ||
|
|
3b993d114e | ||
|
|
d181d62466 | ||
|
|
968cef7a12 | ||
|
|
390c2395eb | ||
|
|
adc9444794 | ||
|
|
f086318082 | ||
|
|
1cef4400b9 | ||
|
|
1f879ac91e | ||
|
|
bec129d98e | ||
|
|
152c04e68f | ||
|
|
d6cdacef43 | ||
|
|
09891a075d | ||
|
|
9500bab5b7 | ||
|
|
231edd4bd4 | ||
|
|
433572e759 | ||
|
|
3ec772e87f | ||
|
|
ae8287157c | ||
|
|
f005907b41 | ||
|
|
8ea16044f1 | ||
|
|
e787a28353 | ||
|
|
6925d9564c | ||
|
|
ccd4ea785a | ||
|
|
63e88c09d4 | ||
|
|
296191b502 | ||
|
|
af0151f446 | ||
|
|
2bdfaa167e | ||
|
|
cd96476f9e | ||
|
|
9b0545aa1a | ||
|
|
63823123f8 | ||
|
|
e91728be98 | ||
|
|
e7ecbf1245 | ||
|
|
28c2363b67 | ||
|
|
21a3d4fb55 | ||
|
|
4b459a3ed1 | ||
|
|
ebc6a1b842 | ||
|
|
b72b0f269a | ||
|
|
4cbcd69ed2 | ||
|
|
6ef8b7748c | ||
|
|
8b567cb976 | ||
|
|
d8c231b9ce | ||
|
|
cc4347c881 | ||
|
|
7cc572feb5 | ||
|
|
96d1d1feb4 | ||
|
|
a7d9bd9ab5 | ||
|
|
1cdfdf1d43 | ||
|
|
0762a5e689 | ||
|
|
3e2906ccf8 | ||
|
|
13761e032c | ||
|
|
fb8fa3fa54 | ||
|
|
b89bf5933b | ||
|
|
1c4f404fd2 | ||
|
|
9fe1560f1a | ||
|
|
6a9cca37c6 | ||
|
|
bfde75d4b1 | ||
|
|
a83f98580d | ||
|
|
673542d8e4 | ||
|
|
81c5d89a25 | ||
|
|
bf0bcb0b96 | ||
|
|
ac7a664303 | ||
|
|
182dc67a49 |
No files matched your search
@@ -1,7 +1,9 @@
|
||||
# About
|
||||
This folder contains the plugin's description as advertised on various marketplaces which use various syntax's (bbcode, markdown).
|
||||
This folder contains the plugin's description as advertised on various marketplaces which use various syntaxes
|
||||
(bbcode, markdown).
|
||||
|
||||
While this is not accurate folder to have this stored in (".github"), it is the closest if we don't want to create a folder for this in the root.
|
||||
While this is not an accurate folder to have this stored in (`.github`),
|
||||
it is the closest if we don't want to create a folder for this in the root.
|
||||
|
||||
The file structure of this folder is as following:
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
[IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Section_About.png[/IMG]
|
||||
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. 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 and all of your questions.
|
||||
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.1-blue.svg[/IMG]
|
||||
[IMG]https://img.shields.io/badge/Minecraft-1.5%20--%201.21.3-blue.svg[/IMG]
|
||||
[IMG]https://img.shields.io/badge/Release-5.0.5-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]
|
||||
[URL='https://github.com/NEZNAMY/TAB/issues'][IMG]https://img.shields.io/github/issues/neznamy/tab?label=Issues[/IMG][/URL]
|
||||
@@ -57,7 +57,7 @@ Before you consider requiring assistance, check out the [URL='https://github.com
|
||||
[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]
|
||||
[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+
|
||||
@@ -97,7 +97,7 @@ Before you consider requiring assistance, check out the [URL='https://github.com
|
||||
[*]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_ScoreboardLegacy.png[/IMG][/CENTER]
|
||||
[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
|
||||
@@ -127,12 +127,6 @@ Before you consider requiring assistance, check out the [URL='https://github.com
|
||||
[*]Create custom boss bars, toggle bossbar visibility, announce bossbar
|
||||
[*]... and more!
|
||||
[/LIST]
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
[LIST]
|
||||
[*]Placeholder support
|
||||
[LIST]
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||

|
||||
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.
|
||||
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 and all of your questions.
|
||||
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 [wiki](https://github.com/NEZNAMY/TAB/wiki) answers any of your questions.
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
[](https://github.com/NEZNAMY/TAB)
|
||||
[](https://github.com/NEZNAMY/TAB/issues)
|
||||
@@ -13,8 +13,8 @@ The extensive [wiki](https://github.com/NEZNAMY/TAB/wiki) answers any and all of
|
||||
|
||||
# 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).
|
||||
However, you can join this [fresh new community discord]([https://discord.gg/YPqXt63YQj](https://discord.gg/YPqXt63YQj)) made by a nice person who promises to help everyone with all plugins, including TAB.
|
||||
|
||||
However, you can join this [fresh new community discord]([https://discord.gg/YPqXt63YQj](https://discord.gg/YPqXt63YQj)) made by a nice person who promises to help everyone with all plugins, including TAB.
|
||||
|
||||
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.
|
||||
|
||||

|
||||
@@ -56,7 +56,7 @@ Before you consider requiring assistance, check out the [wiki](https://github.co
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
* Display value of any numeric placeholder or hearts
|
||||
* Display any text on 1.20.3+
|
||||
* Disable if a specified condition is met
|
||||
@@ -91,18 +91,18 @@ Before you consider requiring assistance, check out the [wiki](https://github.co
|
||||
* 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 right side of lines on 1.20.3+
|
||||
* 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 other plugin hides its scoreboard
|
||||
* 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 current world in tablist
|
||||
* 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
|
||||
@@ -117,7 +117,7 @@ Before you consider requiring assistance, check out the [wiki](https://github.co
|
||||
* Create custom placeholders
|
||||
* Create custom boss bars, toggle bossbar visibility, announce bossbar
|
||||
* ... and more!
|
||||
|
||||
|
||||
.
|
||||
.
|
||||
.
|
||||
@@ -142,25 +142,24 @@ Placeholder output replacements
|
||||
RGB Support
|
||||
* 5 supported RGB patterns
|
||||
* 3 supported gradient patterns
|
||||
* Automatically display the closest color to legacy clients, can also be configured manually
|
||||
* 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
|
||||
* 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
|
||||
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
|
||||
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
|
||||
@@ -34,13 +34,13 @@
|
||||
* [RGB/font usage](https://github.com/NEZNAMY/TAB/wiki/How-to-use-RGB-color-codes)
|
||||
|
||||
## Other
|
||||
* [Client-sided mechanics](https://github.com/NEZNAMY/TAB/wiki/Client%E2%80%90sided-mechanics)
|
||||
* [Quick PlaceholderAPI startup guide](https://github.com/NEZNAMY/TAB/wiki/Quick-PlaceholderAPI-startup-guide)
|
||||
* [How to set up compatibility with glow plugins](https://github.com/NEZNAMY/TAB/wiki/How-to-make-TAB-compatible-with-glow-plugins)
|
||||
* [How to display name from nickname plugins](https://github.com/NEZNAMY/TAB/wiki/How-to-display-name-from-nickname-plugins)
|
||||
* [How to configure weights in LuckPerms](https://github.com/NEZNAMY/TAB/wiki/How-to-setup-weights-priorities)
|
||||
* [How to save config in UTF-8 encoding](https://github.com/NEZNAMY/TAB/wiki/How-to-save-the-config-in-UTF8-encoding)
|
||||
* [Mini guides collection](https://github.com/NEZNAMY/TAB/wiki/Mini-guides-collection)
|
||||
* [Additional information](https://github.com/NEZNAMY/TAB/wiki/Additional-information)
|
||||
* [Bedrock compatibility](https://github.com/NEZNAMY/TAB/wiki/Bedrock-compatibility)
|
||||
* [Client-sided mechanics](https://github.com/NEZNAMY/TAB/wiki/Client%E2%80%90sided-mechanics)
|
||||
* [Developer API](https://github.com/NEZNAMY/TAB/wiki/Developer-API)
|
||||
* [How to display name from nickname plugins](https://github.com/NEZNAMY/TAB/wiki/How-to-display-name-from-nickname-plugins)
|
||||
* [How to save config in UTF-8 encoding](https://github.com/NEZNAMY/TAB/wiki/How-to-save-the-config-in-UTF8-encoding)
|
||||
* [How to set up compatibility with glow plugins](https://github.com/NEZNAMY/TAB/wiki/How-to-make-TAB-compatible-with-glow-plugins)
|
||||
* [Mini guides collection](https://github.com/NEZNAMY/TAB/wiki/Mini-guides-collection)
|
||||
* [Optimizing plugin's CPU usage](https://github.com/NEZNAMY/TAB/wiki/Optimizing-the-plugin)
|
||||
* [Developer API](https://github.com/NEZNAMY/TAB/wiki/Developer-API)
|
||||
* [Quick PlaceholderAPI startup guide](https://github.com/NEZNAMY/TAB/wiki/Quick-PlaceholderAPI-startup-guide)
|
||||
@@ -2,52 +2,16 @@
|
||||
This is a collection of random information about the plugin not necessarily limited to a single feature or function.
|
||||
|
||||
# Content
|
||||
* [Bedrock compatibility](#bedrock-compatibility)
|
||||
* [Detection](#detection)
|
||||
* [Differences](#differences)
|
||||
* [Issues with `.` in name](#issues-with--in-name)
|
||||
* [Vanish detection](#vanish-detection)
|
||||
* [Async player loading](#async-player-loading)
|
||||
* [Scoreboard objective and team naming](#scoreboard-objective-and-team-naming)
|
||||
* [Per-version experience](#per-version-experience)
|
||||
* [Detection](#detection-1)
|
||||
* [Differences](#differences-1)
|
||||
* [Detection](#detection)
|
||||
* [Differences](#differences)
|
||||
* [Debug config option](#debug-config-option)
|
||||
|
||||
# Bedrock compatibility
|
||||
## Detection
|
||||
For TAB to properly detect player as a bedrock player, floodgate and TAB must be installed on the same server instance.
|
||||
If TAB is installed on bukkit and floodgate is on both bungeecord and bukkit,
|
||||
make sure [they are connected](https://geysermc.org/wiki/floodgate/setup/?platform=proxy-servers).
|
||||
|
||||
To make sure floodgate is configured correctly, you have two options:
|
||||
* Set `debug: true` in TAB config and check console output on join/reload. It should say `Floodgate returned bedrock status TRUE for player <player>`.
|
||||
* Run `/tab parse <player> %bedrock%`. It should return `true`.
|
||||
|
||||
If any of them say `false` for an actual bedrock player, you didn't connect floodgate correctly.
|
||||
|
||||
## Differences
|
||||
It is a job of plugin allowing bedrock clients to connect (probably Geyser) to correctly translate all packets.
|
||||
If you are experiencing an issue on bedrock but not java, it is most likely not an issue in TAB code.
|
||||
|
||||
Currently, TAB does check for bedrock clients for:
|
||||
* Displaying scoreboard numbers as 1–15 instead of all 0 even if it's set to all 0, because lines would not be ordered correctly.
|
||||
* Disabling [Layout](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Layout) for bedrock players.
|
||||
* Disabling [Playerlist objective](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Playerlist-Objective) for bedrock players due to many issues with the feature on bedrock.
|
||||
|
||||
These are the only 3 known changes that are required on TAB side.
|
||||
|
||||
## Issues with `.` in name
|
||||
If you configured Geyser to prefix bedrock player names with `.`, it will mess up config loading,
|
||||
because `.` is used to split a section path.
|
||||
This means that using `/tab player <playername containing .> ...` will not work.
|
||||
There are three solutions available:
|
||||
* Use player UUID if commands are executed via another plugin (`/tab player <uuid> ...`).
|
||||
* Use `playeruuid` type, which accepts player name and saves the online player's UUID (`/tab playeruuid <player name> ...`).
|
||||
* Use a different username prefix than `.`.
|
||||
|
||||
# Vanish detection
|
||||
Although TAB does not manage player vanishing in any way,
|
||||
Although TAB does not manage vanishing players in any way,
|
||||
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)
|
||||
@@ -61,14 +25,19 @@ Permission `tab.seevanished` allows players to:
|
||||
* 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.
|
||||
|
||||
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, Essentials, SuperVanish, PremiumVanish and probably more. If TAB is installed on BungeeCord, [Bridge](https://github.com/NEZNAMY/TAB/wiki/TAB-Bridge) must be installed on backend servers to forward vanish status to the proxy.
|
||||
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,
|
||||
Essentials, SuperVanish, PremiumVanish and probably more.
|
||||
If TAB is installed on BungeeCord,
|
||||
[Bridge](https://github.com/NEZNAMY/TAB/wiki/TAB-Bridge) must be installed on backend servers
|
||||
to forward vanish status to the proxy.
|
||||
|
||||
Additionally, if PremiumVanish is installed and vanish status of a player changes,
|
||||
it is asked if the vanished player can be seen by each player.
|
||||
Players who are marked as being able to see that vanished player by PV do not require `tab.seevaninished` permission.
|
||||
|
||||
# Async player loading
|
||||
In order to not slow down the server, TAB loads players asynchronously on join.
|
||||
To not slow down the server, TAB loads players asynchronously on join.
|
||||
As a casual user, this does not impact you, but there are some cases where you'll feel it:
|
||||
* Players will not be accessible in the [API](https://github.com/NEZNAMY/TAB/wiki/Developer-API) during PlayerJoinEvent
|
||||
* [TAB's PlaceholderAPI expansion](https://github.com/NEZNAMY/TAB/wiki/Placeholders#placeholderapi) will not be accessible during the join process, such as when trying to use it in plugins editing join messages (the exception is `replace_` placeholder, which doesn't directly use any TAB code).
|
||||
@@ -84,7 +53,7 @@ TAB's objective and team naming:
|
||||
* `TAB-Scoreboard` for [Scoreboard](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Scoreboard)
|
||||
* `TAB-PlayerList` for [Playerlist objective](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Playerlist-Objective)
|
||||
* Teams ("Team ... already exists in this scoreboard")
|
||||
* For sidebar scoreboard: `TAB-SB-TM-x` where `x` is a number from 1 to 15+ (for each line)
|
||||
* For sidebar scoreboard: `TAB-Sidebar-x` where `x` is a number from 1 to 15+ (for each line)
|
||||
* For players: This is more complicated to use the 16-character limit to the max and heavily depends on configuration. If you are sorting by player names, part of player name will be there. It also often contains random non-alphanumeric symbols. The most reliable way is the team name (almost) always ending with `A` (or `B`, `C` etc. if you tried to sort several players into the same slot).
|
||||
|
||||
# Per-version experience
|
||||
@@ -110,34 +79,29 @@ Player game version detection may fail. Here are a few possible scenarios why:
|
||||
When a player's game version is not detected correctly, player's experience will be very limited, see below.
|
||||
|
||||
## Differences
|
||||
To ensure the best possible experience, TAB is currently checking for player's version to:
|
||||
* [Layout](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Layout):
|
||||
* Disabled for 1.7 and lower due to massive tablist differences and plugin not being adapted to those
|
||||
* Use alternate method of sorting the entries for 1.19.3+ using entry names, as the previous, better option was removed
|
||||
* Use RGB color codes for 1.16+ and use closest (or forced) legacy color for 1.15-
|
||||
* Use 1.9+ bossbar on 1.8 servers for 1.9+ players using ViaVersion API
|
||||
* Disable [Tablist name formatting](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Tablist-name-formatting) for 1.7 and lower due to a 16-character limit total to take advantage of the mechanic where nametags are displayed in tablist, which extends the limit to up to 48 characters (16 prefix, 16 name, 16 suffix)
|
||||
* Play around <1.13 limitations of [Scoreboard](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Scoreboard)
|
||||
* Cutting nametag prefix/suffix to 16 characters for <1.13, but keeping it full for 1.13+
|
||||
To ensure the best possible experience, TAB is currently checking for player's version to alter its logic in some way.
|
||||
If a player's version is not detected correctly, a side effect might occur instead, making the situation worse.
|
||||
You will not experience all of these, only those that affect the version difference between the server and client.
|
||||
|
||||
When version detection fails, plugin's workarounds will be wrong and cause problems, such as:
|
||||
* Nametag prefix/suffix being cut to 16 characters, even for 1.13+ clients
|
||||
* Scoreboard lines limited to ~26 characters, even for 1.13+ clients
|
||||
* Scoreboard lines limited to 14 characters for < 1.13 clients
|
||||
* Scoreboard lines might be out of order
|
||||
* RGB colors displayed as legacy colors for 1.16+ clients
|
||||
* Layout being out of order for 1.19.3+ clients
|
||||
| Change of logic | Negative side effect if version is not detected correctly |
|
||||
|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| Disabling [Layout](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Layout) for 1.7 players | [Layout](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Layout) entries being sent into the tablist of the player, breaking it as the layout is not designed support 1.7 |
|
||||
| use alternate way of sorting [Layout](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Layout) entries for 1.19.3 - 1.21.1 as the optimal sorting way cannot be used | [Layout](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Layout) entries being out of order for 1.19.3 - 1.21.1 <br />Layout entries appearing in chat complete for <1.19.3 even if they don't have to |
|
||||
| Show 1.9+ bossbar on 1.8 servers for 1.9+ players using ViaVersion API | No 1.9+ bossbar for 1.9+ players on 1.8 server |
|
||||
| Disabling [Tablist name formatting](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Tablist-name-formatting) for 1.7 and lower due to a 16-character limit total to take advantage of the mechanic where nametags are displayed in tablist, which extends the limit to up to 48 characters (16 prefix, 16 name, 16 suffix) | Trying to fit the entire formatting into 16 characters, resulting in massive cutting and nametag feature not working |
|
||||
| Play around <1.13 limitations of [Scoreboard](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Scoreboard) | Scoreboard lines limited to ~26 characters, even for 1.13+ clients <br />Scoreboard lines limited to 14 characters for < 1.13 clients |
|
||||
| Cutting nametag prefix/suffix to 16 characters for <1.13, but keeping it full for 1.13+ | Nametag prefix/suffix being cut to 16 characters, even for 1.13+ clients |
|
||||
|
||||
# Debug config option
|
||||
You may have noticed `debug: false` in your config.
|
||||
Enabling it will unlock extra console messages beyond the usual misconfiguration / errors.
|
||||
They give deeper information about what's going on in the plugin in case a more complex issue comes up.
|
||||
Currently, it offers the following:
|
||||
* Prints thrown errors into console as well, instead of just errors.log file. They are printed into the console even if the error log reaches the 16MB limit.
|
||||
* Sends a message about how long each feature + total it took to process player join/quit or plugin reload. High values may suggest something is not optimized correctly. Originally added for myself when optimizing, but adding/removing it each time would be too much of a hassle, so it remains as a debug message.
|
||||
* Makes plugin print thrown errors into console as well, instead of just errors.log file. They are printed into the console even if the error log reaches the 16MB limit.
|
||||
* Sends a message saying how long each feature + total it took to process player join/quit or plugin reload. High values may suggest something is not optimized correctly. Originally added for myself when optimizing, but adding/removing it each time would be too much of a hassle, so it remains as a debug message.
|
||||
* Sends a message when plugin detects player name change by a nick plugin. Since lots of things are bound to player names, having players change their name and plugin not detecting it would result in plugin not working correctly them. Therefore, every time a player changes their nickname (or resets it), a message should pop up.
|
||||
* Sends a message whenever another plugin sends a scoreboard (such as a minigame plugin) and TAB hides its own. Then, when that other plugin removes its scoreboard, TAB sends its own back (and sends a console message).
|
||||
* Sends a message regarding bedrock client status of players who join in using Floodgate API. This includes a message if floodgate is not enabled correctly (API instance is null), as well as true/false bedrock status of players.
|
||||
* Shows network id of protocol version of players who join when ViaVersion is installed. It should show the version players are actually using. If not, plugin may not work correctly for those players. This is usually caused by having ViaVersion on proxy and backend.
|
||||
* Sends a message whenever another plugin sends a scoreboard (such as a mini-game plugin) and TAB hides its own. Then, when that other plugin removes its scoreboard, TAB sends its own back (and sends a console message).
|
||||
* Sends a message regarding the bedrock client status of players who join in using Floodgate API. This includes a message if floodgate is not enabled correctly (API instance is null), as well as true/false bedrock status of players.
|
||||
* Shows network id of protocol version of players who join when ViaVersion is installed. It should show the version players are actually using. If not, the plugin may not work correctly for those players. This is usually caused by having ViaVersion on proxy and backend.
|
||||
* Sends a message whenever a placeholder takes more than 50 milliseconds to retrieve value. If it only happens once, it may be a random hiccup and can be ignored. In case of consistent problems, this may cause plugin overload.
|
||||
* On proxy installation, shows how long did Bridge take to answer player join plugin message. This should be below 100ms. If it's continuously significantly higher, it means bridge is overloaded with the most likely cause being an inefficient placeholder.
|
||||
* On proxy installation, shows how long did Bridge take to answer player join plugin message. This should be below 100ms. If it's continuously significantly higher, it suggests something bad is going on, either bridge being overloaded or the server itself.
|
||||
@@ -56,5 +56,5 @@ If you want an animation too complicated to be done manually, you can check out
|
||||
All animations start at the same time, which is time of plugin being (re)loaded.
|
||||
Thanks to this, all animations with the same number of frames and the same change interval will be synchronized.
|
||||
If you want to synchronize multiple animations with a different change interval and different number of frames,
|
||||
you need to make sure `frame count`*`change interval` is same for both animations,
|
||||
you need to make sure `(frame count)*(change interval)` is same for both animations,
|
||||
for example one animation with 10 frames and 1000ms and second animation with 2 frames and 5000ms.
|
||||
@@ -0,0 +1,33 @@
|
||||
# Content
|
||||
* [Detection](#detection)
|
||||
* [Differences](#differences)
|
||||
* [Issues with `.` in name](#issues-with--in-name)
|
||||
|
||||
# Detection
|
||||
For TAB to properly detect player as a bedrock player, floodgate and TAB must be installed on the same server instance.
|
||||
If TAB is installed on bukkit and floodgate is on both bungeecord and bukkit,
|
||||
make sure [they are connected](https://geysermc.org/wiki/floodgate/setup/?platform=proxy-servers).
|
||||
|
||||
To make sure floodgate is configured correctly, you have two options:
|
||||
* Set `debug: true` in TAB config and check console output on join/reload. It should say `Floodgate returned bedrock status TRUE for player <player>`.
|
||||
* Run `/tab parse <player> %bedrock%`. It should return `true`.
|
||||
|
||||
If any of them say `false` for an actual bedrock player, you didn't connect floodgate correctly.
|
||||
|
||||
# Differences
|
||||
It is a job of plugin allowing bedrock clients to connect (probably Geyser) to correctly translate all packets.
|
||||
If you are experiencing an issue on bedrock but not java, it is most likely not an issue in TAB code.
|
||||
|
||||
Currently, TAB does check for bedrock clients for:
|
||||
* Disabling [Layout](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Layout) for bedrock players.
|
||||
|
||||
This is the only known change required on the TAB's side.
|
||||
|
||||
# Issues with `.` in name
|
||||
If you configured Geyser to prefix bedrock player names with `.`, it will mess up config loading,
|
||||
because `.` is used to split a section path.
|
||||
This means that using `/tab player <playername containing .> ...` will not work.
|
||||
There are three solutions available:
|
||||
* Use player UUID if commands are executed via another plugin (`/tab player <uuid> ...`).
|
||||
* Use `playeruuid` type, which accepts player name and saves the online player's UUID (`/tab playeruuid <player name> ...`).
|
||||
* Use a different username prefix than `.`.
|
||||
@@ -49,7 +49,7 @@ Commands have required permissions associated with them.
|
||||
#### Names for each command type
|
||||
* player: Supports both player name and raw uuid. Player doesn't need to be online.
|
||||
* playeruuid: Requires name of an online player. Equal to using `player` with player's uuid.
|
||||
* group: Uses group names from permission plugin. `_DEFAULT_` is used for default settings for all groups.
|
||||
* group: Using group names from permission plugin. `_DEFAULT_` is used for default settings for all groups.
|
||||
|
||||
## /tab \<player/group/playeruuid\> \<name\> remove
|
||||
* **Permission:** `tab.remove`
|
||||
@@ -133,7 +133,7 @@ Commands have required permissions associated with them.
|
||||
|
||||
## /tab parse \<player\> \<text\>
|
||||
* **Permission:** `tab.parse`
|
||||
* **Description:** Replaces all placeholders (both the plugin's internal ones, and those from PlaceholderAPI if it is installed) in the given `text`. It can be used to test if a placeholder works as expected.
|
||||
* **Description:** Replaces all placeholders (both the plugin's internal ones, and those from PlaceholderAPI if it is installed) in the given `text`. It can be used to verify if a placeholder works as expected.
|
||||
|
||||
# Additional permissions
|
||||
* `tab.admin` - Allows the player to execute all commands.
|
||||
|
||||
@@ -28,18 +28,18 @@ 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.3)</td>
|
||||
<td>✔ (1.5 - 1.21.4)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan=1><a href="https://www.spongepowered.org/">Sponge</a></td>
|
||||
<td>✔ (1.9<sup>1</sup> - 1.21.3)</td>
|
||||
<td>✔ (1.9<sup>1</sup> - 1.21.4)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td rowspan=2>Modded</td>
|
||||
<td rowspan=1><a href="https://fabricmc.net">Fabric</a></td>
|
||||
<td>✔ (1.14 - 1.21.3)</td>
|
||||
<td>✔ (1.14 - 1.21.4)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan=1><a href="https://minecraftforge.net">Forge</a></td>
|
||||
@@ -91,7 +91,9 @@ so you can always safely update to new version/build of your proxy software if t
|
||||
❌ = Completely missing
|
||||
|
||||
# Plugin hooks
|
||||
In order to enhance user experience, TAB hooks into other plugins for better experience. This set of plugins is different for each platform based on their availability. Some are available on all platforms, some only in a few.
|
||||
To enhance user experience, TAB hooks into other plugins for better experience.
|
||||
This set of plugins is different for each platform based on their availability.
|
||||
Some are available on all platforms, some only in a few.
|
||||
## All platforms
|
||||
[**Floodgate**](https://github.com/GeyserMC/Floodgate) - For properly detecting bedrock players to adapt features for the best possible user experience.
|
||||
[**LuckPerms**](https://github.com/LuckPerms/LuckPerms) - Detecting permission groups of players for per-group settings.
|
||||
@@ -100,12 +102,14 @@ In order to enhance user experience, TAB hooks into other plugins for better exp
|
||||
## Bukkit
|
||||
[**LibsDisguises**](https://github.com/libraryaddict/LibsDisguises) - Detecting disguised players to disable collision to avoid endless push by colliding with own copy created by LibsDisguises.
|
||||
[**PlaceholderAPI**](https://github.com/PlaceholderAPI/PlaceholderAPI) - Allows users to use its placeholders inside TAB.
|
||||
[**PremiumVanish**](https://www.spigotmc.org/resources/14404) - Supporting PremiumVanish's vanish levels instead of using a basic vanish compatibility system.
|
||||
[**PremiumVanish**](https://www.spigotmc.org/resources/14404) - Supporting PremiumVanish's vanishing levels instead of using a basic compatibility system.
|
||||
[**Vault**](https://github.com/milkbowl/Vault) - Detecting permission groups of players for per-group settings.
|
||||
|
||||
## BungeeCord
|
||||
[**PremiumVanish**](https://www.spigotmc.org/resources/14404) - Supporting PremiumVanish's vanish levels instead of using a basic vanish compatibility system.
|
||||
[**RedisBungee**](https://github.com/ProxioDev/RedisBungee) - Communicating with other proxies to properly display visuals on players on another proxy.
|
||||
[**PremiumVanish**](https://www.spigotmc.org/resources/14404) -
|
||||
Supporting PremiumVanish's vanishing levels instead of using a basic compatibility system.
|
||||
[**RedisBungee**](https://github.com/ProxioDev/RedisBungee) -
|
||||
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.
|
||||
@@ -114,9 +118,12 @@ In order to enhance user experience, TAB hooks into other plugins for better exp
|
||||
*None*
|
||||
|
||||
## Velocity
|
||||
[**PremiumVanish**](https://www.spigotmc.org/resources/14404) - Supporting PV's vanish levels instead of using a basic vanish compatibility system.
|
||||
[**RedisBungee**](https://github.com/ProxioDev/RedisBungee) - Communicating with other proxies to properly display visuals on players on another proxy.
|
||||
[**VelocityScoreboardAPI**](https://github.com/NEZNAMY/VelocityScoreboardAPI) - Sending scoreboard packets (scoreboard-teams, belowname-objective, playerlist-objective, scoreboard)
|
||||
[**PremiumVanish**](https://www.spigotmc.org/resources/14404) -
|
||||
Supporting PremiumVanish's vanishing levels instead of using a basic compatibility system.
|
||||
[**RedisBungee**](https://github.com/ProxioDev/RedisBungee) -
|
||||
Communicating with other proxies to properly display visuals on players on another proxy.
|
||||
[**VelocityScoreboardAPI**](https://github.com/NEZNAMY/VelocityScoreboardAPI) -
|
||||
Sending scoreboard packets (scoreboard-teams, belowname-objective, playerlist-objective, scoreboard)
|
||||
|
||||
# Compatibility issues
|
||||
* **Glow plugins** will fail to apply glow color correctly. Check [How to make the plugin compatible with glow plugins](https://github.com/NEZNAMY/TAB/wiki/How-to-make-TAB-compatible-with-glow-plugins) for more information.
|
||||
@@ -124,7 +131,9 @@ In order to enhance user experience, TAB hooks into other plugins for better exp
|
||||
* **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 BungeeCord 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 BungeeCord instead.
|
||||
* **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. Here are a few bugs in LunarClient / FeatherClient that you may run into when using TAB:
|
||||
* **ViaVersion**'s `hide-scoreboard-numbers` config option 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 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).
|
||||
* **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.
|
||||
* They don't support color field in chat components, which means they don't support RGB codes and will display bossbar without colors as well.
|
||||
* Bossbar is not visible on 1.8.
|
||||
@@ -132,4 +141,4 @@ In order to enhance user experience, TAB hooks into other plugins for better exp
|
||||
* They don't respect nametag visibility rule, showing their own nametag using F5 even if set to invisible by the plugin.
|
||||
* When the scoreboard is set to use all 0s, lines are rendered in opposite order on 1.20.3+.
|
||||
* They don't display 1.20.3+ NumberFormat feature in scoreboard objectives.
|
||||
* **Random Spigot/BungeeCord forks** - All safe patches for improving security & performance are present in well-known public opensource projects, such as Paper, Purpur or Waterfall. Using a random overpriced closed-source (and probably obfuscated) fork from BuiltByBit may not be safe, since they likely include unsafe patches that may break compatibility with plugins in an attempt to fix things that are not broken. Before spending your entire budget on such fork, reconsider it first. Paper (and its forks) is a performance-oriented fork used by 2/3 of all MC servers worldwide, while the rest is still stuck on Spigot. It is highly unlikely your needs are so specific you need every single "improvement" anyone can come up with. If you need a feature, Purpur is a feature-oriented fork. Together with plugins you should achieve what you are looking for.
|
||||
* **Random Spigot/BungeeCord forks** - All safe patches for improving security & performance are present in well-known public opensource projects, such as Paper, Purpur or Waterfall. Using a random overpriced closed-source (and probably obfuscated) fork from BuiltByBit may not be safe, since they likely include unsafe patches that may break compatibility with plugins in an attempt to fix things that are not broken. Before spending your entire budget on such a fork, reconsider it first. Paper (and its forks) is a performance-oriented fork used by 2/3 of all MC servers worldwide, while the rest is still stuck on Spigot. It is highly unlikely your needs are so specific you need every single "improvement" anyone can come up with. If you need a feature, Purpur is a feature-oriented fork. Together with plugins you should achieve what you are looking for.
|
||||
@@ -26,7 +26,7 @@ For Maven users, you can add the API dependency like this:
|
||||
<dependency>
|
||||
<groupId>com.github.NEZNAMY</groupId>
|
||||
<artifactId>TAB-API</artifactId>
|
||||
<version>5.0.2</version>
|
||||
<version>5.0.5</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
```
|
||||
@@ -40,7 +40,7 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly 'com.github.NEZNAMY:TAB-API:5.0.2'
|
||||
compileOnly 'com.github.NEZNAMY:TAB-API:5.0.5'
|
||||
}
|
||||
```
|
||||
|
||||
@@ -51,7 +51,7 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly("com.github.NEZNAMY", "TAB-API", "5.0.2")
|
||||
compileOnly("com.github.NEZNAMY", "TAB-API", "5.0.5")
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -23,19 +23,19 @@ This line automatically appears on all player entities when enabled. **Therefore
|
||||
|
||||
The feature can be configured in **config.yml** under **belowname-objective** section.
|
||||
|
||||
The feature's properties are displayed as `[score]` + space + `title`, where `[score]` is:
|
||||
The feature's properties are displayed as `[score]` + space + `title` (no, the space cannot be removed), where `[score]` is:
|
||||
* `value` for 1.20.2- (will show `0` on NPCs)
|
||||
* `fancy-value` for 1.20.3+ (will show `fancy-value-default` on NPCs)
|
||||
|
||||
# Configuration
|
||||
| Option name | Default value | Description |
|
||||
| ------------- | ------------- | ------------- |
|
||||
| enabled | true | Enables / Disables the feature |
|
||||
| value | %health% | [1.20.2-] An integer from -2147483648 to 2147483647, doesn't support decimal values. The number is always white. Supports placeholders with player-specific output, such as player health. 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 | &c%health% | [1.20.3+] Any text, supports placeholders with per-player output. Only visible on 1.20.3+. |
|
||||
| fancy-value-default | NPC | [1.20.3+] Default display for all player entities. Plugin uses the value above for every real player, therefore this default value will only appear on player entities which are not actual players, a.k.a. NPCs. Only visible on 1.20.3+. |
|
||||
| title | Health | Text displayed after `value`. It is formally called scoreboard title, which is displayed on all player entities and value is shared on all players and therefore not bound to specific players, which makes it unable to display the value per-player. It should only contain static text or placeholders which return same value for all players. All inserted placeholders are parsed for the player viewing the text. This is intentional to allow advanced configuration with conditions for per-world values for example. |
|
||||
| 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 | %health% | [1.20.2-] An integer from -2147483648 to 2147483647, doesn't support decimal values. The number is always white. Supports placeholders with player-specific output, such as player health. 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 | &c%health% | [1.20.3+] Any text, supports placeholders with per-player output. Only visible on 1.20.3+. |
|
||||
| fancy-value-default | NPC | [1.20.3+] Default display for all player entities. Plugin uses the value above for every real player, therefore this default value will only appear on player entities which are not actual players, a.k.a. NPCs. Only visible on 1.20.3+. |
|
||||
| title | Health | Text displayed after `value`. It is formally called scoreboard title, which is displayed on all player entities and value is shared on all players and therefore not bound to specific players, which makes it unable to display the value per-player. It should only contain static text or placeholders which return same value for all players. All inserted placeholders are parsed for the player viewing the text. This is intentional to allow advanced configuration with conditions for per-world values for example. |
|
||||
| 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. |
|
||||
|
||||
# Additional Info
|
||||
## Additional note 1 - Copying nametag visibility rule
|
||||
@@ -64,12 +64,12 @@ This is how you can achieve it using the following popular NPC plugins:
|
||||
# Tips & Tricks
|
||||
## Tip 1 - Heart symbol
|
||||
If you want to show health of players and display a heart symbol as text, you can use this one: `❤`.
|
||||
Make sure you [save config in UTF-8 encoding](https://github.com/NEZNAMY/TAB/wiki/How-to-save-the-config-in-UTF8-encoding),
|
||||
so it's loaded correctly.
|
||||
```
|
||||
belowname-objective:
|
||||
title: "&4❤"
|
||||
```
|
||||
> [!IMPORTANT]
|
||||
> Make sure your config is saved in UTF-8 encoding to properly read the heart symbol.
|
||||
|
||||
## Tip 2 - Displaying health as 0-10 or in %
|
||||
If you want health to display health as 0-10 instead of 0-20, you can achieve it with PlaceholderAPI:
|
||||
@@ -77,10 +77,11 @@ If you want health to display health as 0-10 instead of 0-20, you can achieve it
|
||||
* `%math_0_{player_health}*5%` for 0-100
|
||||
|
||||
# Limitations
|
||||
* The format is a **[score] + space + shared title**, where [score] is either a white number (1.20.2-) or any text (1.20.3+).
|
||||
* The title is the same on all players, therefore, it cannot be personalized (such as player's faction). Only the [score] can be per-player.
|
||||
* [1.5 - 1.12.2] Title length is limited to 32 characters (including color codes).
|
||||
* [1.5 - 1.20.2] `value` is limited to a white number.
|
||||
* The format is a **[score] + space + shared title**. No, the space cannot be removed.
|
||||
* The title is the same on all players, therefore, it cannot be personalized (such as player's faction). Only [score] can be per-player.
|
||||
* It appears on all entities of player type. **This includes player NPCs.**
|
||||
* Title length is limited to 32 characters on <1.13
|
||||
|
||||
# Examples
|
||||
## Example 1 - Per-world values
|
||||
@@ -105,7 +106,10 @@ conditions:
|
||||
true: "Text to display in world world2" # Player is in world world2, display the text
|
||||
false: "Text to display in other worlds" # Player is not in any of the 2 worlds
|
||||
```
|
||||
**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!!!**
|
||||
> [!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!
|
||||
|
||||
Finally, use this condition as value in `fancy-value`:
|
||||
```
|
||||
@@ -115,7 +119,11 @@ belowname-objective:
|
||||
If you are under 1.20.3 and want to make `value` and `title` conditional,
|
||||
create two condition chains, one for each value and use them.
|
||||
|
||||
*Note: This is just an example, the plugin is not limited to displaying different values only per world. If you want per server values on BungeeCord, use %server% with server names. If you want WorldGuard regions, use %worldguard_region_name% with region names. This works for any placeholder offered by the plugin or by PlaceholderAPI.*
|
||||
> [!NOTE]
|
||||
> This is just an example, the plugin is not limited to displaying different values only per world.
|
||||
> If you want per server values on BungeeCord, use %server% with server names.
|
||||
> If you want WorldGuard regions, use %worldguard_region_name% with region names.
|
||||
> This works for any placeholder offered by the plugin or by PlaceholderAPI.
|
||||
|
||||
## Example 2 - Hiding `title` for 1.20.3+ players
|
||||
1.20.3 has replaced `value` field,
|
||||
@@ -139,7 +147,10 @@ conditions:
|
||||
true: "" # Show empty value for 1.20.3+
|
||||
false: "Some text to display"
|
||||
```
|
||||
**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!!!**
|
||||
> [!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!
|
||||
```
|
||||
belowname-objective:
|
||||
title: "%condition:version%"
|
||||
|
||||
@@ -61,8 +61,8 @@ A number from 0 to 100. Accepts decimals.
|
||||

|
||||
If you want to show the progress of a placeholder's value relative to a maximum one, you can use the [Math Expansion](https://github.com/PlaceholderAPI/PlaceholderAPI/wiki/Placeholders#math) from PlaceholderAPI and use a placeholder like this one: `%math_{placeholder_current_value}/{placeholder_max_value}*100%` or `%math_{placeholder_current_value}/<max_value>*100%` if your max value is fixed.
|
||||
|
||||
Notes:
|
||||
Replace the text in between the `{}` with the actual placeholder you want to use and `<max_value>` with a valid number
|
||||
> [!NOTE]
|
||||
> Replace the text in between the `{}` with the actual placeholder you want to use and `<max_value>` with a valid number
|
||||
(i.e., 20).
|
||||
You cannot use the `%placeholder%` format inside the math placeholder.
|
||||
|
||||
@@ -102,17 +102,17 @@ bossbar:
|
||||
`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.
|
||||
`%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%`.
|
||||
|
||||
## Additional settings
|
||||
| Option name | Default value | Description |
|
||||
| ------------- | ------------- | ------------- |
|
||||
| enabled | false | Enables / Disables the feature |
|
||||
| toggle-command | /bossbar | A command to toggle bossbar visibility for player running the command. This command requires `tab.bossbar.toggle` permission. <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, disabling bossbar using `toggle-command` is remembered between reloads and restarts. |
|
||||
| hidden-by-default | false | When enabled, bossbar will automatically be hidden until toggle command is used. |
|
||||
| Option name | Default value | Description |
|
||||
|------------------------|---------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| enabled | false | Enables / Disables the feature |
|
||||
| toggle-command | /bossbar | A command to toggle bossbar visibility for player running the command. This command requires `tab.bossbar.toggle` permission. <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, disabling bossbar using `toggle-command` is remembered between reloads and restarts. |
|
||||
| hidden-by-default | false | When enabled, bossbar will automatically be hidden until toggle command is used. |
|
||||
|
||||
# Limitations
|
||||
* Does not support newlines. If you want to display more lines, you'll need to create multiple bossbars.
|
||||
@@ -121,11 +121,14 @@ using Math expansion from PlaceholderAPI, such as `%math_{tab_placeholder_countd
|
||||
# Additional info
|
||||
## Additional note 1 - Hiding bar itself
|
||||
If you want to hide the bossbar itself and only show text, this is possible, but not from the plugin's side.
|
||||
You'll need to create a custom resource pack where the bar doesn't show up and only the text does.
|
||||
You'll need to create a custom resource pack where the bar is invisible.
|
||||
Then, you need to force your players to get the resource pack.
|
||||
|
||||
## Additional note 2 - Not hiding on server switch
|
||||
When under a BungeeCord network and having TAB installed on backend server and switching to another server, the bossbar will not hide. This is because BungeeCord makes it look like a world switch to the client. To avoid this, the only way is to install TAB on BungeeCord and disable bossbar on that server.
|
||||
When under a proxy network and having TAB installed on backend server and switching to another server,
|
||||
the bossbar will not hide.
|
||||
This is because BungeeCord makes it look like a world switch to the client.
|
||||
To avoid this, the only way is to install TAB on BungeeCord and disable bossbar on that server.
|
||||
|
||||
# API
|
||||
*To get started with the API, see [Developer API](https://github.com/NEZNAMY/TAB/wiki/Developer-API) page.*
|
||||
|
||||
@@ -11,15 +11,15 @@ This feature allows you to see players from other servers under a bungeecord net
|
||||
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.
|
||||
|
||||
# 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 (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). |
|
||||
|
||||
## Grouping players only from certain servers
|
||||
In case you want multiple servers to share playerlist,
|
||||
|
||||
@@ -16,9 +16,9 @@ Minecraft feature **introduced in 1.8** showing text above and below playerlist.
|
||||

|
||||
|
||||
# Configuration
|
||||
| Option name | Default value | Description |
|
||||
| ------------- | ------------- | ------------- |
|
||||
| enabled | true | Enables / Disables the feature |
|
||||
| Option name | Default value | Description |
|
||||
|-------------------|-----------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| enabled | true | Enables / Disables the feature |
|
||||
| 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. |
|
||||
|
||||
## Global settings
|
||||
@@ -65,7 +65,9 @@ header-footer:
|
||||
- "Header in all worlds starting with lobby-"
|
||||
```
|
||||
|
||||
**Note**: To make per-world work on BungeeCord, install the [TAB-Bridge](https://www.mc-market.org/resources/21641) plugin on your backend servers.
|
||||
> [!NOTE]
|
||||
> To make per-world work on proxy installation,
|
||||
> install the [TAB-Bridge](https://www.mc-market.org/resources/21641) plugin on your backend servers.
|
||||
|
||||
## Per-group / per-player
|
||||
**groups.yml**
|
||||
|
||||
@@ -11,9 +11,9 @@
|
||||
* [Additional info](#additional-info)
|
||||
* [Additional note 1 - Playerlist objective incompatibility](#additional-note-1---playerlist-objective-incompatibility)
|
||||
* [Additional note 2 - Global playerlist incompatibility](#additional-note-2---global-playerlist-incompatibility)
|
||||
* [Additional note 3 - Entries in chat complete [1.19.3 - 1.21.1]](#additional-note-3---entries-in-chat-complete-1193---1211)
|
||||
* [Additional note 3 - [1.19.3 - 1.21.1] Entries in chat complete](#additional-note-3---1193---1211-entries-in-chat-complete)
|
||||
* [Additional note 4 - Per world playerlist incompatibility](#additional-note-4---per-world-playerlist-incompatibility)
|
||||
* [Additional note 5 - Second layer of skin missing](#additional-note-5---second-layer-of-skin-missing)
|
||||
* [Additional note 5 - [1.8 - 1.21.3] Second layer of skin missing](#additional-note-5---18---1213-second-layer-of-skin-missing)
|
||||
* [Examples](#examples)
|
||||
* [Example 1 - Per-server columns](#example-1---per-server-columns)
|
||||
|
||||
@@ -144,13 +144,13 @@ Yellow: 1307755006
|
||||
```
|
||||
|
||||
# Additional settings
|
||||
| Option name | Default value | Description |
|
||||
| ------------- | ------------- | ------------- |
|
||||
| direction | COLUMNS | Defines direction of slots. Options are COLUMNS (top to bottom, left to right) and ROWS (left to right, top to bottom). This does not only change the slot numbers in configuration, but will also affect the way players are being filled into player groups. |
|
||||
| enable-remaining-players-text | true | When enabled, the last slot of player group will show how many more players there are, instead of using the last slot for one more player. |
|
||||
| remaining-players-text | "... and %s more" | Text to show if option above is enabled. |
|
||||
| default-skin | "mineskin:383747683" | Default skin to display for fixed slots that do not define a skin, empty slots and fixed slots with invalid skin. |
|
||||
| empty-slot-ping-value | 1000 | Ping value to use for fixed slots and empty slots. The ping intervals for bars are client sided and are as following: <br />- Negative value: ✖ <br />- 0 - 149: 5 bars <br />- 150 - 299: 4 bars <br />- 300 - 599: 3 bars <br />- 600 - 999: 2 bars <br />- 1000+: 1 bar |
|
||||
| Option name | Default value | Description |
|
||||
|-------------------------------|----------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| direction | COLUMNS | Defines direction of slots. Options are COLUMNS (top to bottom, left to right) and ROWS (left to right, top to bottom). This does not only change the slot numbers in configuration, but will also affect the way players are being filled into player groups. |
|
||||
| enable-remaining-players-text | true | When enabled, the last slot of player group will show how many more players there are, instead of using the last slot for one more player. |
|
||||
| remaining-players-text | "... and %s more" | Text to show if option above is enabled. |
|
||||
| default-skin | "mineskin:383747683" | Default skin to display for fixed slots that do not define a skin, empty slots and fixed slots with invalid skin. |
|
||||
| empty-slot-ping-value | 1000 | Ping value to use for fixed slots and empty slots. The ping intervals for bars are client sided and are as following: <br />- Negative value: ✖ <br />- 0 - 149: 5 bars <br />- 150 - 299: 4 bars <br />- 300 - 599: 3 bars <br />- 600 - 999: 2 bars <br />- 1000+: 1 bar |
|
||||
|
||||

|
||||

|
||||
@@ -166,11 +166,12 @@ The way this feature works is by pushing all real players out of the tablist and
|
||||
As a result, enabling [Global playerlist](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Global-playerlist) feature won't make any difference, since real players aren't visible in the tablist.
|
||||
Layout feature is capable of working with all online players connected to the server where TAB is installed, meaning it can show players from all servers when installed on BungeeCord. Because of this, if you use layout, you can disable global playerlist to reduce resource usage.
|
||||
|
||||
## Additional note 3 - Entries in chat complete [1.19.3 - 1.21.1]
|
||||
## Additional note 3 - [1.19.3 - 1.21.1] Entries in chat complete
|
||||
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+,
|
||||
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).
|
||||
|
||||
@@ -232,13 +233,15 @@ You can also replicate "ignore-effect-in-worlds" by creating a layout with condi
|
||||
```
|
||||
</details>
|
||||
|
||||
## Additional note 5 - Second layer of skin missing
|
||||
## Additional note 5 - [1.8 - 1.21.3] Second layer of skin missing
|
||||
Layout works by creating 80 fake entries, pushing real players out of the tablist.
|
||||
Unfortunately, players must be spawned around the player viewing the tablist to see the second layer of their skin.
|
||||
Since those fake entries you see are not the actual players and aren't spawned,
|
||||
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.
|
||||
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).**
|
||||
|
||||
# Examples
|
||||
## Example 1 - Per-server columns
|
||||
|
||||
@@ -119,14 +119,14 @@ All values fully support [TAB's internal placeholders](https://github.com/NEZNAM
|
||||
The number of placeholders is not limited, and they can be used in combination with static text as well.
|
||||
|
||||
## Additional settings
|
||||
| Option name | Default value | Description |
|
||||
| ------------- | ------------- | ------------- |
|
||||
| enabled | true | Enables / Disables the feature |
|
||||
| enable-collision | true | Controls collision rule. Available values are: <br />- `true` - Collision will be enabled permanently. <br />- `false` - Collision will be disabled permanently. <br /> - *Conditional expression* - Collision will be enabled if player meets the condition (for example `%world%=world` will result in collision only being enabled for players in world `world`). <br /> - *Condition name* - Uses name of a [defined condition](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Conditional-placeholders) that must be met for a player to have collision enabled. |
|
||||
| invisible-nametags | false| When enabled, everyone will have invisible nametag all the time. Option to hide nametags was added in minecraft **1.8**, therefore it will not work on 1.7 and lower. |
|
||||
| anti-override | true | While enabled, prevents other plugins from assigning online players into teams and making TAB not work correctly. They should be configured to not use teams, however many users fail to disable features in other plugins that they don't want, making this option required. Some plugins don't even say they use teams to achieve their goals. Did you know even Paper uses teams? |
|
||||
| can-see-friendly-invisibles | false | Controlling value of the team flag. It allows you to see invisible players in the same team as transparent instead of completely invisible. Since TAB places each player into an individual team, this option will only take effect in combination with plugins that spawn a dummy clone of the player (sit or disguise plugins). |
|
||||
| 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 |
|
||||
| enable-collision | true | Controls collision rule. Available values are: <br />- `true` - Collision will be enabled permanently. <br />- `false` - Collision will be disabled permanently. <br /> - *Conditional expression* - Collision will be enabled if player meets the condition (for example `%world%=world` will result in collision only being enabled for players in world `world`). <br /> - *Condition name* - Uses name of a [defined condition](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Conditional-placeholders) that must be met for a player to have collision enabled. |
|
||||
| invisible-nametags | false | When enabled, everyone will have invisible nametag all the time. Option to hide nametags was added in minecraft **1.8**, therefore it will not work on 1.7 and lower. |
|
||||
| anti-override | true | While enabled, prevents other plugins from assigning online players into teams and making TAB not work correctly. They should be configured to not use teams, however many users fail to disable features in other plugins that they don't want, making this option required. Some plugins don't even say they use teams to achieve their goals. Did you know even Paper uses teams? |
|
||||
| can-see-friendly-invisibles | false | Controlling value of the team flag. It allows you to see invisible players in the same team as transparent instead of completely invisible. Since TAB places each player into an individual team, this option will only take effect in combination with plugins that spawn a dummy clone of the player (sit or disguise plugins). |
|
||||
| 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. |
|
||||
|
||||
# Tips & Tricks
|
||||
If you want TAB to only take prefixes/suffixes from the permission plugin,
|
||||
@@ -138,9 +138,9 @@ _DEFAULT_:
|
||||
```
|
||||
|
||||
# Limitations
|
||||
* Prefix/suffix length is limited to 16 characters (including color codes) on <1.13. There is no reachable limit on 1.13+.
|
||||
* [1.5 - 1.12.2] Prefix/suffix length is limited to 16 characters (including color codes).
|
||||
* [1.13+] The name can only have one code. That is either color or magic code (such as &4 or &l), but not both.
|
||||
* The name cannot be effectively changed and the plugin doesn't offer it.
|
||||
* Since 1.13, the name can only have one code. That is either color or magic code (such as &4 or &l), but not both.
|
||||
* Name does not support RGB codes. Any used RGB colors will be rounded to the nearest legacy code.
|
||||
* The same value manages name color and glow color, which means they cannot be different.
|
||||
|
||||
@@ -202,7 +202,11 @@ To get the original value set by the plugin based on configuration:
|
||||
* `NameTagManager#getOriginalPrefix(TabPlayer)`
|
||||
* `NameTagManager#getOriginalSuffix(TabPlayer)`
|
||||
|
||||
**Note**: These values are only temporary, meaning they won't get saved anywhere and will get reset on player quit or plugin reload. If you wish to save these values into file, use [commands](https://github.com/NEZNAMY/TAB/wiki/Commands-&-Permissions#tab-playergroupplayeruuid-name-property-value-options).
|
||||
> [!NOTE]
|
||||
> These values are only temporary,
|
||||
> meaning they won't get saved anywhere and will get reset on player quit or plugin reload.
|
||||
> If you wish to save these values into file,
|
||||
> use [commands](https://github.com/NEZNAMY/TAB/wiki/Commands-&-Permissions#tab-playergroupplayeruuid-name-property-value-options).
|
||||
|
||||
## Collision
|
||||
* `NameTagManager#getCollisionRule(TabPlayer)` - Returns forced collision rule using the API, `null` if no value was forced and player follows the configuration.
|
||||
|
||||
@@ -9,12 +9,12 @@ This feature can be configured in **config.yml** under **per-world-playerlist**
|
||||
To use it, you need TAB installed on bukkit server. This feature is not available on BungeeCord.
|
||||
|
||||
# Configuration
|
||||
| Option name | Default value | Description |
|
||||
| ------------- | ------------- | ------------- |
|
||||
| enabled | false | Enables / Disables the feature. |
|
||||
| allow-bypass-permission | false | If enabled, players with `tab.bypass` permission will see everyone in tablist despite this feature enabled. |
|
||||
| ignore-effect-in-worlds | *List* | List of worlds where players will see everyone in tablist, not just players in the same world / world group |
|
||||
| shared-playerlist-world-groups | *Map* | See [Grouping playerlist from multiple worlds](#grouping-playerlist-from-multiple-worlds) |
|
||||
| Option name | Default value | Description |
|
||||
|--------------------------------|---------------|-------------------------------------------------------------------------------------------------------------|
|
||||
| enabled | false | Enables / Disables the feature. |
|
||||
| allow-bypass-permission | false | If enabled, players with `tab.bypass` permission will see everyone in tablist despite this feature enabled. |
|
||||
| ignore-effect-in-worlds | *List* | List of worlds where players will see everyone in tablist, not just players in the same world / world group |
|
||||
| shared-playerlist-world-groups | *Map* | See [Grouping playerlist from multiple worlds](#grouping-playerlist-from-multiple-worlds) |
|
||||
|
||||
## Grouping playerlist from multiple worlds
|
||||
In case you want multiple worlds to share playerlist despite this option enabled, create a group and put world names there. The default example
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
* [Examples](#examples)
|
||||
* [Example 1 - Adding brackets to placeholder output if it returns non-empty value](#example-1---adding-brackets-to-placeholder-output-if-it-returns-non-empty-value)
|
||||
* [Example 2 - Replicating PlaceholderAPI's %player_colored_ping%](#example-2---replicating-placeholderapis-player_colored_ping)
|
||||
* [Example 3 - Using both original and replaced output](#example-3---using-both-original-and-replaced-output)
|
||||
|
||||
# About
|
||||
Most placeholders don't have fully configurable output, making you unable to customize it as you'd like.
|
||||
@@ -44,8 +45,14 @@ placeholder-output-replacements:
|
||||
|
||||
</details>
|
||||
|
||||
**Note 1:** If you want to replace empty output, use `""` or `''` on the left side.
|
||||
**Note 2:** The text must match exactly, including color codes. To check color codes a placeholder returns use [`/tab parse <player> <placeholder>`](https://github.com/NEZNAMY/TAB/wiki/Commands-&-Permissions#tab-parse-text) and check "Without colors" part.
|
||||
> [!NOTE]
|
||||
> If you want to replace empty output, use `""` or `''` on the left side.
|
||||
|
||||
> [!NOTE]
|
||||
> The text must match exactly, including color codes.
|
||||
> To check the color codes a placeholder returns use
|
||||
> [`/tab parse <player> <placeholder>`](https://github.com/NEZNAMY/TAB/wiki/Commands-&-Permissions#tab-parse-text)
|
||||
> and check "Without colors" part.
|
||||
|
||||
### Pattern 2: Number intervals
|
||||
Output will be replaced if it's numeric and belongs in a configured interval.
|
||||
@@ -61,7 +68,9 @@ placeholder-output-replacements:
|
||||
```
|
||||
</details>
|
||||
|
||||
**Note:** If a number is included in more than 1 interval (just like 5 and 15 from the example above), the one higher is used. In this case, 15 would give `Solid` and 5 would give `Bad`.
|
||||
> [!NOTE]
|
||||
> If a number is included in more than 1 interval (just like 5 and 15 from the example above), the one higher is used.
|
||||
> In this case, 15 would give `Solid` and 5 would give `Bad`.
|
||||
|
||||
Intervals can be defined using both `-` and `~`.
|
||||
If you need support for negative values, use `~` for an interval instead of `-`.
|
||||
@@ -139,7 +148,9 @@ placeholder-output-replacements:
|
||||
```
|
||||
**Don't forget that parse command applies replacements, so when trying to see what placeholder returns, you should disable any previously configured replacements for that placeholder to see the correct raw output**.
|
||||
## Example 2 - Replicating PlaceholderAPI's %player_colored_ping%
|
||||
*Please note that this is just an example to demonstrate the plugin's functionality. It is not supposed to discourage anyone from using PlaceholderAPI or any of its placeholders.*
|
||||
> [!NOTE]
|
||||
> Please note that this is just an example to demonstrate the plugin's functionality.
|
||||
> It is not supposed to discourage anyone from using PlaceholderAPI or any of its placeholders.*
|
||||
|
||||
The placeholder allows you to configure intervals in config.yml. Default values are <50 for good, <100 for medium and 100+ for high. We can achieve this using [Pattern 2: Number intervals](#pattern-2-number-intervals) and [Pattern 3: else](#pattern-3-else). When put together, it looks as following:
|
||||
```
|
||||
@@ -155,4 +166,31 @@ which require a numeric input (for versions prior to 1.20.3).
|
||||
If you want that feature and ping to be displayed there, replacements won't work for you,
|
||||
and you'll need
|
||||
to use [Conditional placeholders](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Conditional-placeholders) instead
|
||||
to achieve colored ping.
|
||||
to achieve colored ping.
|
||||
|
||||
# Example 3 - Using both original and replaced output
|
||||
Once configured, replacements are applied as the placeholder output everywhere. It is not directly possible to use original output in one place and replaced in another.
|
||||
|
||||
If you need this, you should consider [conditional placeholders](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Conditional-placeholders) instead.
|
||||
If conditional placeholders are too complicated to use, there are alternate ways. The main point of the solution is to create different placeholder that eventually calls the same function and returns the same result. This can be done by "wrapping" the placeholder into another one. The easiest way using existing TAB's functions is using animations. Create an animation with a single frame that contains the placeholder and configure replacements for that placeholder instead.
|
||||
<details>
|
||||
<summary>Example for %ping%</summary>
|
||||
|
||||
**animations.yml**:
|
||||
```
|
||||
ping:
|
||||
change-interval: 50 # Actual value doesn't matter
|
||||
texts:
|
||||
- "%ping%"
|
||||
```
|
||||
Then, configure replacements for the animation in **config.yml**:
|
||||
```
|
||||
placeholder-output-replacements:
|
||||
"%animation:ping%":
|
||||
"0-50": "&a%animation:ping%" # %ping% would work too
|
||||
"51-100": "&e%animation:ping%"
|
||||
"else": "&c%animation:ping%"
|
||||
```
|
||||
Now, you can use `%ping%` for original output and `%animation:ping%` for replaced.
|
||||
|
||||
</details>
|
||||
@@ -18,15 +18,16 @@ 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. 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. |
|
||||
|
||||
# Limitations
|
||||
At 1.20.2 and lower, only 2 display types are supported - number and hearts. The number's color is yellow and cannot be changed.
|
||||
* [1.5 - 1.20.2] Only 2 display types are supported - yellow number and hearts.
|
||||
* [1.5 - 1.20.2] A yellow 0 will appear on all tablist entries that aren't actual players (such as NPCs and [layout](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Layout) entries) and it cannot be removed.
|
||||
|
||||
# Additional info
|
||||
## Additional note 1 - Spectator gamemode
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
* [Toggling scoreboard visibility](#toggling-scoreboard-visibility)
|
||||
* [Announcing a scoreboard](#announcing-a-scoreboard)
|
||||
* [Examples](#examples)
|
||||
* [Example 1 - per-world scoreboards](#example-1---per-world-scoreboards)
|
||||
* [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)
|
||||
|
||||
@@ -52,7 +52,7 @@ 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).
|
||||
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.
|
||||
|
||||
@@ -107,21 +107,21 @@ name - name of scoreboard to be displayed
|
||||
time - number of seconds to display the scoreboard for
|
||||
|
||||
# Additional settings
|
||||
| Option name | Default value | Description |
|
||||
| ------------- | ------------- | ------------- |
|
||||
| enabled | false | Enables / Disables the feature |
|
||||
| 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. |
|
||||
| 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. |
|
||||
| Option name | Default value | Description |
|
||||
|----------------------------|---------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| enabled | false | Enables / Disables the feature |
|
||||
| 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. |
|
||||
| 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
|
||||
* The title is limited to 32 characters (including color codes) on <1.13.
|
||||
* Line length is limited to 28 characters (including color codes) on <1.13 (68 on lines with static text / [Longer lines](#longer-lines)).
|
||||
* Only displays up to 15 lines.
|
||||
* The red numbers on the right really cannot be removed from the plugin side (a client modification / resource pack is necessary) at 1.20.2 and lower. They no longer appear on 1.20.3+, and instead any text can be configured (see above).
|
||||
* [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.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.
|
||||
|
||||
# Longer lines
|
||||
To make sure the scoreboard never flickers, it's only using prefix/suffix components to display text.
|
||||
@@ -215,7 +215,7 @@ which will look up a scoreboard with the given `String` name,
|
||||
and if one is found, send it to all players for the given `int` ticks.
|
||||
|
||||
# Examples
|
||||
## Example 1 - per-world scoreboards
|
||||
## Example 1 - Per-world scoreboards
|
||||
We will be using a condition of "equals" (=) type and check for output of `%world%` placeholder.
|
||||
If condition is met, it means player is in that world and the scoreboard is displayed.
|
||||
If not, check for the next scoreboard's condition.
|
||||
@@ -246,7 +246,11 @@ If none of the conditions are met, display the default scoreboard.
|
||||
lines:
|
||||
- 'You are not in any of the worlds listed above'
|
||||
```
|
||||
*Note: This is just an example, the plugin is not limited to displaying scoreboard only per world. If you want per server scoreboards on BungeeCord, use %server% with server names. If you want worldguard regions, use %worldguard_region_name% with region names. This works for any placeholder offered by the plugin or by PlaceholderAPI.*
|
||||
> [!NOTE]
|
||||
> This is just an example, the plugin is not limited to displaying scoreboard only per world.
|
||||
> If you want per server scoreboards on proxy, use %server% with server names.
|
||||
> If you want worldguard regions, use %worldguard_region_name% with region names.
|
||||
> This works for any placeholder offered by the plugin or by PlaceholderAPI.
|
||||
|
||||
## Example 2 - Periodical scoreboard switching
|
||||
We can use [animations](https://github.com/NEZNAMY/TAB/wiki/Animations) to make the plugin switch between scoreboards using display condition. Let's say we want to switch between 2 scoreboards. First, we create an animation:
|
||||
|
||||
@@ -21,18 +21,20 @@
|
||||
# Enabling
|
||||
To enable sorting, you must have either [Nametags](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Nametags) or [Layout](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Layout) (or both) enabled.
|
||||
|
||||
To **verify** you enabled sorting, run `/tab debug`. It will say `Sorting type:` followed by anything except `DISABLED`.
|
||||
> [!TIP]
|
||||
> To verify you enabled sorting, run `/tab debug`. It will say `Sorting type:` followed by anything except `DISABLED`.
|
||||
|
||||
# Methods of sorting
|
||||
Below are all the different methods that you can use to sort players in TAB.
|
||||
Below are all the different methods that you can use to sort players.
|
||||
|
||||
## GROUPS
|
||||
This is the default and recommended method. Players will be sorted by their primary permission group, according to the configured group list.
|
||||
|
||||
First, put your players into groups in your permission plugin.
|
||||
All the ways to do it can be found
|
||||
at [How to assign players into groups](https://github.com/NEZNAMY/TAB/wiki/How-to-assign-players-into-groups).
|
||||
**Verify** player's group using `/tab debug <player>`.
|
||||
at [How to assign players into groups](https://github.com/NEZNAMY/TAB/wiki/How-to-assign-players-into-groups).
|
||||
> [!TIP]
|
||||
> Verify player's group using `/tab debug <player>`.
|
||||
It should say `Primary permission group:` followed by group you configured.
|
||||
If not, you did not assign players into groups correctly.
|
||||
|
||||
@@ -42,7 +44,8 @@ scoreboard-teams:
|
||||
sorting-types:
|
||||
- "GROUPS:owner,admin,mod,default"
|
||||
```
|
||||
**Verify** configured sorting priority of players using `/tab debug <player>`.
|
||||
> [!TIP]
|
||||
> Verify configured sorting priority of players using `/tab debug <player>`.
|
||||
|
||||
It should show a message like this:
|
||||

|
||||
@@ -52,8 +55,12 @@ If a group is not in the list, you will get the following:
|
||||
If that's the case, you forgot to add that group into the list.
|
||||
|
||||
## PERMISSIONS
|
||||
**This method is not recommended, as many low quality servers give OP to every staff member and don't know how to negate permissions, making this option not function as intended. If you think you can pull it off, and sorting by primary group is not an option, you can give this a try.
|
||||
This method is, however, handy when trying to sort specific players **without** assigning them into a group.**
|
||||
> [!CAUTION]
|
||||
> This method is not recommended,
|
||||
> as many low quality servers give OP to every staff member and don't know how to negate permissions,
|
||||
> making this option not function as intended.
|
||||
> If you think you can pull it off, and sorting by primary group is not an option, you can give this a try.
|
||||
This method is, however, handy when trying to sort specific players without assigning them into a group.
|
||||
|
||||
This method will sort players based on permission nodes they have.
|
||||
Place all of your permissions in to a comma separated list **in order of priority**.
|
||||
@@ -64,6 +71,8 @@ scoreboard-teams:
|
||||
sorting-types:
|
||||
- "PERMISSIONS:my.permission.1,my.permission.2,my.permission.3"
|
||||
```
|
||||
> [!TIP]
|
||||
> Verify sorting permissions of a player using `/tab debug <player>`
|
||||
|
||||
## PLACEHOLDER
|
||||
This method sorts players using the output of a placeholder and comparing that to pre-defined values.
|
||||
@@ -164,9 +173,9 @@ you will need to use outputs respectively when defining sorting as well.
|
||||
Although you can theoretically use as many sorting types as you want, there are still strict limits set by mojang. See more about these limits and how to avoid them as much as possible at [Additional note 1 - Limitations](#additional-note-1---limitations).
|
||||
|
||||
# Additional settings
|
||||
| Option name | Default value | Description |
|
||||
| ------------- | ------------- | ------------- |
|
||||
| case-sensitive-sorting | true | When enabled, players will be sorted as A-Z a-z. When disabled, Aa-Zz. |
|
||||
| Option name | Default value | Description |
|
||||
|------------------------|---------------|------------------------------------------------------------------------|
|
||||
| case-sensitive-sorting | true | When enabled, players will be sorted as A-Z a-z. When disabled, Aa-Zz. |
|
||||
|
||||
# Additional info
|
||||
## Additional note 1 - Limitations
|
||||
@@ -226,7 +235,7 @@ The most common mistakes include:
|
||||
* Not configuring group weights in LuckPerms.
|
||||
* Accidentally enabling `use-bukkit-permission-manager` option when on BungeeCord without knowing what it does.
|
||||
* Installing TAB on BungeeCord without having any permission plugin on BungeeCord.
|
||||
* Enabling `assign-groups-by-permissions` without giving those permissions.
|
||||
* Enabling `assign-groups-by-permissions` without giving those permissions (or the opposite - giving away OP to users, resulting in the highest group being taken).
|
||||
|
||||
[Debug command](https://github.com/NEZNAMY/TAB/wiki/Commands-&-Permissions#tab-debug-player) will help you identify if this is your case.
|
||||
* Using a plugin that causes TAB to fail to apply teams (such as Tablisknu). This includes installing TAB plugin on both bukkit and bungeecord, causing the installations to conflict.
|
||||
|
||||
@@ -18,9 +18,9 @@ prevent-spectator-effect:
|
||||
```
|
||||
|
||||
# Permissions
|
||||
| Permission | Description |
|
||||
| ------------- | ------------- |
|
||||
| `tab.spectatorbypass` | Players with this permission will see spectators the usual way despite this feature being enabled. If you don't want players (or even staff members) to bypass this feature, don't give them that permission. |
|
||||
| Permission | Description |
|
||||
|-----------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `tab.spectatorbypass` | Players with this permission will see spectators the usual way despite this feature being enabled. If you don't want players (or even staff members) to bypass this feature, don't give them that permission. |
|
||||
|
||||
# Additional info
|
||||
## Additional note 1 - Seeing yourself still as spectator
|
||||
|
||||
@@ -123,11 +123,11 @@ All values fully support [TAB's internal placeholders](https://github.com/NEZNAM
|
||||
The number of placeholders is not limited, and they can be used in combination with static text as well.
|
||||
|
||||
## Additional settings
|
||||
| Option name | Default value | Description |
|
||||
| ------------- | ------------- | ------------- |
|
||||
| enabled | true | Enables / Disables the feature |
|
||||
| anti-override | true | When enabled, prevents other plugins from overriding TAB. This results in slightly higher CPU usage, but will make sure the function works as expected. <br />Unlike with teams, attempts to override TAB are not logged into anti-override.log file. This is due to high amount of false positives from TAB's own features, as well as bungeecord server switch, higher CPU usage and more. Whether you need this or not depends on how properly your plugins are configured. Some of them attempt to format tablist even if you'd not expect it from such plugin. Keep this enabled to stay safe. |
|
||||
| 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 |
|
||||
| anti-override | true | When enabled, prevents other plugins from overriding TAB. This results in slightly higher CPU usage, but will make sure the function works as expected. <br />Unlike with teams, attempts to override TAB are not logged into anti-override.log file. This is due to high amount of false positives from TAB's own features, as well as bungeecord server switch, higher CPU usage and more. Whether you need this or not depends on how properly your plugins are configured. Some of them attempt to format tablist even if you'd not expect it from such plugin. Keep this enabled to stay safe. |
|
||||
| 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. |
|
||||
|
||||
# Tips & Tricks
|
||||
If you want TAB to only take prefixes/suffixes from your permission plugin,
|
||||
|
||||
@@ -112,15 +112,11 @@ When using the symbol in configuration using \u format, remember to use `""` in
|
||||
## #16 - Is MiniMessage supported?
|
||||
Kind of.
|
||||
MiniMessage support **is** included; however, this doesn't mean it is guaranteed to work.
|
||||
Here are a few reasons why it may not work for you:
|
||||
* MiniMessage library must be included in your server software. TAB does not include this library.
|
||||
* It **is** included in Paper 1.16.5 and higher, Velocity and Sponge 8.
|
||||
* It **is not** included in Spigot, any server software for 1.16.4 and lower, BungeeCord (and any of its forks), Sponge 7 and Fabric.
|
||||
* You may not use any legacy color codes (&) in the text where you want MiniMessage support. If you use them, MiniMessage parser will throw an error. Therefore, if trying to use both legacy colors and MiniMessage syntax, MiniMessage syntax will not work.
|
||||
* A plugin/mod might be shading an outdated/incomplete version of Adventure without relocating it, and java class loader might make TAB use the wrong source of the duplicated library.
|
||||
MiniMessage library must be included in your server software.
|
||||
TAB does not include this library.
|
||||
* It **is** included in **Paper 1.18.2** and higher, **Velocity** and **Sponge 8**.
|
||||
* It **is not** included in **Spigot**, any server software for 1.18.1 and lower, **BungeeCord**, **Sponge 7** and **Fabric**.
|
||||
|
||||
This list is not final.
|
||||
There might still be other reasons why MiniMessage won't work for you, which are not known yet.
|
||||
If you believe MiniMessage should be supported on your server but doesn't work, open a bug report.
|
||||
|
||||
## #17 - Is it possible to show the number of players in a specific group, similar to staffonline placeholder?
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# About the wiki
|
||||
The wiki explains the functionality of the plugin as of version **5.0.2**.
|
||||
The wiki explains the functionality of the plugin as of version **5.0.5**.
|
||||
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.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* [About](#about)
|
||||
* [Option 1: Primary group from permission plugin](#option-1-primary-group-from-permission-plugin)
|
||||
* [Option 2: Permission nodes](#option-2-permission-nodes)
|
||||
* [Taking groups from backend servers when TAB is on BungeeCord](#taking-groups-from-backend-servers-when-tab-is-on-bungeecord)
|
||||
* [Taking groups from backend servers when TAB is on proxy](#taking-groups-from-backend-servers-when-tab-is-on-proxy)
|
||||
|
||||
## About
|
||||
TAB uses groups to assign properties (such as tabprefix) and sort players in tablist (if sorting by groups). You have 2 ways of configuring primary groups of players in TAB.
|
||||
@@ -12,13 +12,17 @@ First option hooks into your permission plugin and takes groups from it. Therefo
|
||||
If your permission plugin is not supported, you can use [Option 2](#option-2-permission-nodes).
|
||||
|
||||
## Option 1: Primary group from permission plugin
|
||||
This is the default method and recommended. TAB will ask your permission plugin for player's primary group.
|
||||
To use it, keep `assign-groups-by-permissions: false` in your config.
|
||||
This is the default method and recommended.
|
||||
TAB will ask your permission plugin for player's primary group.
|
||||
There are 2 simple steps to this:
|
||||
* Keep `assign-groups-by-permissions: false` in your config
|
||||
* Put players into groups in your permission plugin
|
||||
> [!CAUTION]
|
||||
> If assigning players into multiple groups, make sure their weights are correct
|
||||
> (for luckperms /lp group \<group\> setweight \<number\>).
|
||||
|
||||
Put players into groups in your permission plugin
|
||||
(remember to configure weights - for luckperms /lp group \<group\> setweight \<number\>).
|
||||
|
||||
**Verify** group using `/tab debug <player>`. It should say `Primary permission group: <your group>`.
|
||||
> [!TIP]
|
||||
> Verify player's group using `/tab debug <player>`. It should say `Primary permission group: <your group>`.
|
||||
|
||||
## Option 2: Permission nodes
|
||||
Instead of taking whole groups, TAB will check for permission nodes and assign groups based on the highest permission.
|
||||
@@ -31,9 +35,14 @@ Instead of taking whole groups, TAB will check for permission nodes and assign g
|
||||
#2 - if a player has permission for more than one group, the list will be used to pick the correct one
|
||||
(the one higher in the list).
|
||||
|
||||
**Remember that OP = all permissions!** That's the whole point of OP function. To grant all permissions. In that case, you need to negate permissions in your permission plugin for other groups which are higher than player's wanted group (depending on your permission plugin it can be achieved using `-tab.group.groupname` or setting the permission value to false).
|
||||
> [!WARNING]
|
||||
> Remember that OP = all permissions!
|
||||
> That's the whole point of OP function.
|
||||
> To grant all permissions.
|
||||
> In that case,
|
||||
> you need to negate permissions in your permission plugin for other groups which are higher than player's wanted group.
|
||||
|
||||
# Taking groups from backend servers when TAB is on BungeeCord
|
||||
# Taking groups from backend servers when TAB is on proxy
|
||||
By default, TAB takes groups from the permission plugin installed on the same server instance.
|
||||
In the case of proxy installation, groups are by default taken from the proxy.
|
||||
To see how is your proxy permission plugin configured, use its respective command
|
||||
|
||||
@@ -22,7 +22,10 @@ or using in-game command `/tab group _DEFAULT_ customtabname %essentials_nicknam
|
||||
|
||||
This example uses `%essentials_nickname%` placeholder from Essentials. Giving it to `_DEFAULT_` group keyword makes it a default value for all groups that do not override this property.
|
||||
|
||||
**WARNING: Do NOT put nickname placeholder into tabprefix! You will end up with double names!**
|
||||
> [!CAUTION]
|
||||
> Do NOT put nickname placeholder into tabprefix!
|
||||
> You will end up with double names!
|
||||
> Put the nickname placeholder into customtabname instead.
|
||||
|
||||
# Nametag
|
||||
Nametags are more complicated because they consist of player's username, team prefix and team suffix.
|
||||
|
||||
@@ -11,11 +11,10 @@ Glow color is managed by scoreboard teams, which also handle nametag formatting
|
||||
|
||||
## Solution
|
||||
The solution to get around this issue is to use the glow plugin's placeholder (if it has any).
|
||||
This placeholder has to be at the end of the player's `tagprefix` as it has to be the last color in the prefix.
|
||||
**NOTE:
|
||||
For Minecraft version 1.9 -
|
||||
1.12 the prefix can't be longer than 14 characters due to [16-character limit](https://github.com/NEZNAMY/TAB/wiki/Limitations#name-tags) total
|
||||
(glow placeholder adds 2 characters - `&` and the color code)**
|
||||
This placeholder has to be at the end of the player's `tagprefix` as it has to be the last color in the prefix.
|
||||
> [!WARNING]
|
||||
> For Minecraft versions below 1.13 the prefix can't be longer than 14 characters due to [16-character limit](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Nametags#limitations) total
|
||||
(glow placeholder adds 2 characters - `&` and the color code)
|
||||
|
||||
**Example:**
|
||||
This example uses the placeholder of the [eGlow](https://www.spigotmc.org/resources/63295/) plugin.
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
* [Magic codes](#magic-codes)
|
||||
* [Invalid code handling](#invalid-code-handling)
|
||||
* [Compatibility with <1.16](#compatibility-with-116)
|
||||
* [Manually defining legacy color](#manually-defining-legacy-color)
|
||||
* [Fonts](#fonts)
|
||||
* [Usage](#usage-1)
|
||||
* [Compatibility with RGB and gradients](#compatibility-with-rgb-and-gradients)
|
||||
@@ -29,8 +28,8 @@ Gradients:
|
||||
|
||||
TAB uses the first by default, but is able to accept the other types as well without leaving any characters (such as `{}`) behind.
|
||||
**Gradients only support 2 colors, using more will result in an invalid syntax and won't work.**
|
||||
**Remember to replace `RRGGBB` with actual hexadecimal numbers, for example `FFAA00`** (yes, some people straight up copy "RRGGBB" and ask why it is not working):
|
||||
This color code has 256 red, 170 green and 0 blue. It is equal to the legacy color `6` (gold/orange).
|
||||
> [!CAUTION]
|
||||
> Remember to replace `RRGGBB` with actual hexadecimal numbers (0-9, a-f)!
|
||||
|
||||
You can use a tool like [this one](https://htmlcolorcodes.com/) to easily find suitable colors.
|
||||
|
||||
@@ -43,31 +42,15 @@ For gradients, put the magic codes at the beginning of text. For example `<#00FF
|
||||
In case you accidentally use invalid hex color code, the plugin will simply ignore it and keep it in raw format.
|
||||
|
||||
# Compatibility with <1.16
|
||||
Since <1.16 clients do not understand these color codes, something else must be displayed instead. The plugin will find the closest legacy color to the desired RGB combination and will use that color instead.
|
||||
|
||||
## Manually defining legacy color
|
||||
If you want to specify a legacy color to use instead of plugin finding the closest one, you can define it by adding `|L` after RGB code, where `L` is a legacy color to use (0-9 a-f).
|
||||
Usage for classic codes:
|
||||
* `#RRGGBB|L`
|
||||
* `&#RRGGBB|L`
|
||||
* `{#RRGGBB|L}`
|
||||
* `#<RRGGBB|L>`
|
||||
* `<#RRGGBB|L>`
|
||||
* `&x&R&R&G&G&B&B|L`
|
||||
|
||||
For gradients,
|
||||
defining color will result in the whole text
|
||||
having the same color instead of each character converting to the closest colors,
|
||||
resulting in color groups.
|
||||
To define it, put `|L` after the beginning color of gradient.
|
||||
Usage:
|
||||
* `<#RRGGBB|L>Text</#RRGGBB>`
|
||||
* `{#RRGGBB|L>}Text{#RRGGBB<}`
|
||||
* `<$#RRGGBB|L>Text<$#RRGGBB>`
|
||||
Since <1.16 clients do not understand these color codes, something else must be displayed instead.
|
||||
TAB, server software or viaversion (depending on your setup)
|
||||
will find the closest legacy color to the desired RGB combination and will use that color instead.
|
||||
|
||||
# Fonts
|
||||
The plugin offers usage of the font feature introduced in Minecraft 1.16.
|
||||
**Warning!** This does NOT refer to different character sets! This refers to different fonts defined in resource packs or MC itself.
|
||||
The plugin offers usage of the font feature introduced in Minecraft 1.16.
|
||||
> [!IMPORTANT]
|
||||
> This does NOT refer to different character sets!
|
||||
> This refers to different fonts defined in resource packs or MC itself.
|
||||
|
||||
## Usage
|
||||
The syntax is `<font:FONT_NAME>Text</font>` where `FONT_NAME` is name of your desired font.
|
||||
|
||||
@@ -1,30 +1,39 @@
|
||||
# Content
|
||||
* [Bukkit/Spigot](#bukkitspigot)
|
||||
* [BungeeCord](#bungeecord)
|
||||
* [Backend](#backend)
|
||||
* [BungeeCord / Velocity](#bungeecord--velocity)
|
||||
* [Proxy installation](#proxy-installation)
|
||||
* [Backend installation](#backend-installation)
|
||||
* [Mixed installation](#mixed-installation)
|
||||
* [Updating the plugin](#updating-the-plugin)
|
||||
|
||||
# Bukkit/Spigot
|
||||
Installation on Bukkit/Spigot/Paper is as simple as it can be.
|
||||
# Backend
|
||||
Installation on Bukkit / Fabric / Sponge is as simple as it can be.
|
||||
Put the plugin into the plugins folder and restart the server.
|
||||
Dynamic (re)loads at runtime such as `/plugman load tab` to load the plugin are fully supported.
|
||||
However, plugins hooking into TAB may break when you use plugman to reload it.
|
||||
However, plugins hooking into TAB may break when you use such ways to (re)load it.
|
||||
|
||||
# BungeeCord / Velocity
|
||||
When running a network, you have 3 options:
|
||||
* Installing TAB only on the proxy (recommended)
|
||||
* Installing TAB on all backend servers instead
|
||||
* Installing TAB everywhere and disabling features, so they don't conflict (not recommended)
|
||||
|
||||
See below for more information regarding each way.
|
||||
|
||||
# BungeeCord
|
||||
## Proxy installation
|
||||
This is the recommended setup.
|
||||
Put the plugin into the plugins folder of the proxy server and restart it.
|
||||
**Recommended**:
|
||||
Install [TAB-Bridge](https://github.com/NEZNAMY/TAB/wiki/TAB-Bridge) on your backend servers for PlaceholderAPI support and more.
|
||||
Install [TAB-Bridge](https://github.com/NEZNAMY/TAB/wiki/TAB-Bridge) on your **backend servers** for PlaceholderAPI support and more.
|
||||
**Recommended**:
|
||||
On **Velocity** installation install [VelocityScoreboardAPI](https://github.com/NEZNAMY/VelocityScoreboardAPI/) plugin for scoreboard-related features to work.
|
||||
If you are using **Velocity**,
|
||||
install [VelocityScoreboardAPI](https://github.com/NEZNAMY/VelocityScoreboardAPI/) plugin **on the proxy** for scoreboard-related features to work.
|
||||
|
||||
**Note**: With proxy installation use **/btab** command instead of **/tab**.
|
||||
> [!NOTE]
|
||||
> 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)
|
||||
* 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)
|
||||
* 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
|
||||
@@ -34,7 +43,7 @@ Disadvantages compared to installing on all backend servers instead:
|
||||
* No [per-world player list](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Per-world-playerlist) (can be achieved by installing the plugin on backend servers and disabling all features except the per-world player list)
|
||||
|
||||
## Backend installation
|
||||
Just like when not using BungeeCord, install the plugin on all servers where you want the plugin. However, this setup is not recommended (see advantages of proxy installation).
|
||||
Just like when not running a network, install the plugin on all servers where you want the plugin. However, this setup is not as good (see advantages of proxy installation).
|
||||
|
||||
## Mixed installation
|
||||
Mixed installation is absolutely not recommended and should be avoided.
|
||||
|
||||
@@ -4,7 +4,7 @@ This is collection if minecraft issues/bugs you may experience when using TAB, w
|
||||
# 1.5.x - 1.12.x - Unwanted space
|
||||
You may see unwanted space at the end of a line of text even if you didn't configure any.
|
||||
This is a client sided bug.
|
||||
To avoid it, add &r after ending bold segment (for example, prefix &4&lAdmin&r&6).
|
||||
To avoid it, add `&r` after ending bold segment (for example, prefix `&4&lAdmin&r&6`).
|
||||
|
||||
# 1.15.x - Strikethrough and underline
|
||||
Using &m (strikethrough) and &n (underline) does not work in header,
|
||||
@@ -12,8 +12,4 @@ last line of the scoreboard and possibly other places
|
||||
([MC-180110](https://bugs.mojang.com/browse/MC-180110)).
|
||||
|
||||
# 1.20 - 1.20.4 - Chat over tablist
|
||||
Visual bug rendering chat over the tablist ([MC-263256](https://bugs.mojang.com/browse/MC-263256)).
|
||||
|
||||
# 1.20.3+ - Text not bold
|
||||
Since 1.20.3 text configured to be bold (using &l) may not appear bold. Steps to reproduce are currently unknown, if you have any information that may help, let us know.
|
||||
Clue #1 - It might be a BungeeCord bug breaking bold text. It may or may not have been fixed.
|
||||
Visual bug rendering chat over the tablist ([MC-263256](https://bugs.mojang.com/browse/MC-263256)).
|
||||
@@ -22,13 +22,13 @@ Before attempting to use the function, make sure your server software has an int
|
||||
TAB does not contain it by itself.
|
||||
See the compatibility table below for current information about compatibility:
|
||||
|
||||
| Platform | Available |
|
||||
| ------------- | ------------- |
|
||||
| Bukkit | ✔ |
|
||||
| BungeeCord | ✔ |
|
||||
| Fabric | ❌ |
|
||||
| Sponge | ❌ |
|
||||
| Velocity | ❌ |
|
||||
| Platform | Available |
|
||||
|------------|-----------|
|
||||
| Bukkit | ✔ |
|
||||
| BungeeCord | ✔ |
|
||||
| Fabric | ❌ |
|
||||
| Sponge | ❌ |
|
||||
| Velocity | ❌ |
|
||||
|
||||
# Enabling
|
||||
Find this section in **config.yml**:
|
||||
|
||||
@@ -5,13 +5,13 @@ The usage can be checked at any time using [**/tab cpu**](https://github.com/NEZ
|
||||
* [#1 - [All platforms] Static text instead of placeholders](#1---all-platforms-static-text-instead-of-placeholders)
|
||||
* [#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] Disable anti-override](#4---all-platforms-disable-anti-override)
|
||||
* [#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)
|
||||
* [#9 - [BungeeCord] Disable ByteBuf deserialization](#9---bungeecord-disable-bytebuf-deserialization)
|
||||
* [#10 - [Velocity] Disable header/footer translation](#10---velocity-disable-headerfooter-translation)
|
||||
* [#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)
|
||||
|
||||
# #1 - [All platforms] Static text instead of placeholders
|
||||
Static text doesn't need to be refreshed periodically, resulting in better performance.
|
||||
@@ -50,7 +50,13 @@ Using a higher interval will help if needed, but may lead to misleading info in
|
||||
# #3 - [All platforms] Don't use RGB
|
||||
Though RGB colors look good, they require the text to be split into several components with `color` field used instead of the whole text pasted into the `text` field. This is a complicated process that takes quite some time. Sticking to legacy colors results in better performance.
|
||||
|
||||
# #4 - [All platforms] Disable anti-override
|
||||
# #4 - [All platforms] Don't use animations
|
||||
While animations look cool, they also have a high resource usage.
|
||||
This is because they work by spamming updates every time an animation switches to a new frame.
|
||||
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.
|
||||
@@ -70,7 +76,7 @@ tablist-name-formatting:
|
||||
anti-override: false
|
||||
```
|
||||
|
||||
# #5 - [All platforms] Disable tablist name formatting
|
||||
# #6 - [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,
|
||||
@@ -81,7 +87,7 @@ 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.
|
||||
|
||||
# #6 - [All platforms] Increase permission refresh interval
|
||||
# #7 - [All platforms] Increase permission refresh interval
|
||||
Plugin needs to constantly check permissions for:
|
||||
* Permission checks in conditions
|
||||
* Permission checks if sorting by permissions
|
||||
@@ -92,9 +98,11 @@ default value
|
||||
```
|
||||
permission-refresh-interval: 1000
|
||||
```
|
||||
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.
|
||||
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.
|
||||
|
||||
# #7 - [BungeeCord / Velocity] Disable TAB expansion
|
||||
# #8 - [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,
|
||||
@@ -105,36 +113,24 @@ Disabling TAB's PlaceholderAPI expansion if you don't use it improves performanc
|
||||
placeholders:
|
||||
register-tab-expansion: false
|
||||
```
|
||||
# #8 - [BungeeCord / Velocity] Disable update-latency option in global playerlist
|
||||
# #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.
|
||||
|
||||
# #9 - [BungeeCord] Disable ByteBuf deserialization
|
||||
# #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.
|
||||
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)
|
||||
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.
|
||||
|
||||
# #10 - [Velocity] Disable header/footer translation
|
||||
Velocity offers header/footer translation of translatable components.
|
||||
However, TAB does not use these.
|
||||
Despite that, Velocity's attempt to "translate" the components is still there,
|
||||
causing the whole feature to use up to 3x more CPU than it would without it.
|
||||
There is no way to disable this inside Velocity itself,
|
||||
however, there is a third party [fork](https://github.com/GemstoneGG/Velocity-CTD/) that offers it.
|
||||
**Use at your own risk**.
|
||||
After installing, default velocity.toml file will contain `translate-header-footer = true` option.
|
||||
If just upgrading, you will need to add it there yourself.
|
||||
Setting it to `false` will disable this function.
|
||||
This check cannot be disabled if the scoreboard is enabled.
|
||||
@@ -26,59 +26,69 @@ TAB offers various placeholders to display the most common information. It also
|
||||
|
||||
Basic placeholders provided by the plugin. Most of these placeholders were added because they were requested by people who don't want to install another plugin just to show username. Some of them have configurable output in config.yml in `Placeholders` section. Unused placeholders do not affect performance in any way.
|
||||
|
||||
Placeholders must be refreshed. All internal placeholders have manually defined refresh interval, which I found the most optimal and cannot be changed. Time is in milliseconds. `-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 fastest refreshing placeholder used inside each condition.
|
||||
Placeholders must be refreshed.
|
||||
All internal placeholders have a manually defined refresh interval,
|
||||
which I found the most optimal and cannot be changed.
|
||||
Time is in milliseconds.
|
||||
It 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) |
|
||||
| %% | -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.
|
||||
| Identifier | Refresh interval | Description | PlaceholderAPI alternative |
|
||||
| Identifier | Refresh interval | Description | PlaceholderAPI alternative |
|
||||
| ------------- | ------------- | ------------- | ------------- |
|
||||
| %health% | 100 | Player's health, rounded up to match Minecraft's heart display. | %player_health_rounded% |
|
||||
| %displayname% | 500 | Player's display name value set by permission plugin. | %player_displayname% |
|
||||
| %health% | 100 | Player's health, rounded up to match Minecraft's heart display. | %player_health_rounded% |
|
||||
| %displayname% | 500 | Player's display name value set by permission plugin. | %player_displayname% |
|
||||
| %tps% | 1000 | TPS of server from the last minute (measured by server, not available on Fabric as it doesn't measure the value) | %server_tps_1% |
|
||||
| %mspt% | 1000 | Server's current milliseconds per tick (Paper / Sponge 8+ / Fabric only) | - |
|
||||
|
||||
## BungeeCord only
|
||||
| Identifier | Refresh interval | Description |
|
||||
| ------------- | ------------- | ------------- |
|
||||
| %server% | -1 | Name of server where player is, defined in proxy's config. |
|
||||
| %serveronline% | 1000 | Amount of online players on server where the player is (excluding vanished players) |
|
||||
| %online_\<servername\>% | 1000 | Amount of online players on specified server (excluding vanished players) |
|
||||
| %playerlist-group_\<group\>% | 1000 | Amount of online players in specified [global playerlist](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Global-playerlist) group (therefore the feature must be enabled) |
|
||||
| Identifier | Refresh interval | Description |
|
||||
|------------------------------|------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| %server% | -1 | Name of server where player is, defined in proxy's config. |
|
||||
| %serveronline% | 1000 | Amount of online players on server where the player is (excluding vanished players) |
|
||||
| %online_\<server name\>% | 1000 | Amount of online players on specified server (excluding vanished players) |
|
||||
| %playerlist-group_\<group\>% | 1000 | Amount of online players in specified [global playerlist](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Global-playerlist) group (therefore the feature must be enabled) |
|
||||
|
||||
# PlaceholderAPI
|
||||
PlaceholderAPI is supported and relational placeholders are supported too. When parsing relational placeholders, TAB puts viewer as the first player argument and target player as second argument.
|
||||
PlaceholderAPI is supported and relational placeholders are supported too.
|
||||
When parsing relational placeholders,
|
||||
TAB puts viewer as the first player argument and target player as the second argument.
|
||||
|
||||
You can find most of placeholders that PlaceholderAPI offers on [PlaceholderAPI wiki](https://github.com/PlaceholderAPI/PlaceholderAPI/wiki/Placeholders). Some plugins are not listed there, for those you'll need to check their own wiki for placeholders.
|
||||
You can find most of the placeholders
|
||||
that PlaceholderAPI offers on [PlaceholderAPI wiki](https://github.com/PlaceholderAPI/PlaceholderAPI/wiki/Placeholders).
|
||||
Some plugins are not listed there, for those you'll need to check their own wiki for placeholders.
|
||||
|
||||
TAB also offers its own placeholders into PlaceholderAPI. In order to enable TAB's expansion, set
|
||||
TAB also offers its own placeholders into PlaceholderAPI. To enable TAB's expansion, set
|
||||
```
|
||||
placeholders:
|
||||
register-tab-expansion: true
|
||||
@@ -92,11 +102,11 @@ Full list of placeholders:
|
||||
| %tab_tagprefix% | Current tagprefix with replaced placeholders |
|
||||
| %tab_tagsuffix% | Current tagsuffix with replaced placeholders |
|
||||
| %tab_customtabname% | Current customtabname with replaced placeholders |
|
||||
| %tab_tabprefix_raw% | Raw tabprefix containing unparsed placeholders |
|
||||
| %tab_tabsuffix_raw% | Raw tabsuffix containing unparsed placeholders |
|
||||
| %tab_tagprefix_raw% | Raw tagprefix containing unparsed placeholders |
|
||||
| %tab_tagsuffix_raw% | Raw tagsuffix containing unparsed placeholders |
|
||||
| %tab_customtabname_raw% | Raw customtabname containing unparsed placeholders |
|
||||
| %tab_tabprefix_raw% | Raw tabprefix containing raw placeholders |
|
||||
| %tab_tabsuffix_raw% | Raw tabsuffix containing raw placeholders |
|
||||
| %tab_tagprefix_raw% | Raw tagprefix containing raw placeholders |
|
||||
| %tab_tagsuffix_raw% | Raw tagsuffix containing raw placeholders |
|
||||
| %tab_customtabname_raw% | Raw customtabname containing raw placeholders |
|
||||
| %tab_scoreboard_name% | Returns name of player's currently displayed scoreboard or empty string if none is displayed due to no display condition being met |
|
||||
| %tab_scoreboard_visible% | "Enabled" if visible, "Disabled" if not |
|
||||
| %tab_bossbar_visible% | "Enabled" if visible, "Disabled" if not |
|
||||
@@ -107,7 +117,13 @@ Full list of placeholders:
|
||||
|
||||
# Relational placeholders
|
||||
## About
|
||||
Unlike classic placeholders which take either 0 (placeholders with same output for all players) or 1 (per-player placeholders) players, relational placeholders take in 2 players. The first usage was to display relation between two players, specifically enemies / allies (red / green) for factions, allowing players to see faction names of other players colored based on relation. While the usage is not limited to this, the name of the 2-player placeholder type has been established as "relational" and will be called that everywhere.
|
||||
Unlike classic placeholders which take either 0
|
||||
(placeholders with the same output for all players) or 1 (per-player placeholders) players,
|
||||
relational placeholders take in 2 players.
|
||||
The first usage was to display relation between two players, specifically enemies / allies (red / green) for factions,
|
||||
allowing players to see faction names of other players colored based on relation.
|
||||
While the usage is not limited to this, the name of the 2-player placeholder type has been established as "relational"
|
||||
and will be called that everywhere.
|
||||
|
||||
## Usage
|
||||
TAB does not offer any internal relational placeholders. However, it has full support for PlaceholderAPI placeholders. You can also register your own relational placeholders using the [API](#relational-placeholders-1). In PlaceholderAPI, all relational placeholder identifiers must start with `%rel_`. This rule is adopted and used in TAB as well (including those registered via API).
|
||||
@@ -120,31 +136,48 @@ public String onPlaceholderRequest(Player viewer, Player target, String identifi
|
||||
where `viewer` is the player looking and `target` is the player the value is being displayed on.
|
||||
|
||||
When trying to use placeholders from other plugins, you will need to check their documentation and search for relational placeholders. If your plugin does not appear to offer any, you ran out of luck and TAB cannot do anything about it.
|
||||
In order to properly distinguish viewer/target (if needed), your relational placeholder expansion must follow the same order as TAB does. Since PlaceholderAPI never created any convention regarding this and instead calls the players `one` and `two`, I had to decide and this is the decision.
|
||||
To properly distinguish viewer/target (if needed), your relational placeholder expansion must follow the same order as TAB does. Since PlaceholderAPI never created any convention regarding this, and instead it calls the players `one` and `two`, I had to decide and this is the decision.
|
||||
|
||||
# Refreshing
|
||||
## Refresh intervals
|
||||
TAB is refreshing placeholders in intervals. For internal placeholders it's using values I found the most optimal and set directly in the plugin. Refresh intervals for PlaceholderAPI placeholders can be [configured](https://github.com/NEZNAMY/TAB/wiki/Optimizing-the-plugin#2---placeholder-refresh-intervals).
|
||||
|
||||
## Sync refreshing
|
||||
By default, all placeholders are refreshed asynchronously so they don't slow down the server, since reading should be a thread-safe operation. Some placeholders, however, require to be refreshed in the main thread, causing problems or throwing errors when refreshed asynchronously. For these, replace them with `%sync:<original placeholder>%` and they will be refreshed in the main thread, for example `%server_total_entities%` -> `%sync:server_total_entities%`.
|
||||
Keep in mind that now you should configure reasonable refresh interval for these placeholders to not cause TPS drops (with async placeholders it doesn't really matter). Configured refresh intervals and CPU usage of placeholders can be checked using `/tab cpu`.
|
||||
By default, all placeholders are refreshed asynchronously,
|
||||
so they don't slow down the server, since reading should be a thread-safe operation.
|
||||
Some placeholders, however, require to be refreshed in the main thread,
|
||||
causing problems or throwing errors when refreshed asynchronously.
|
||||
For these, replace them with `%sync:<original placeholder>%` and they will be refreshed in the main thread,
|
||||
for example `%server_total_entities%` -> `%sync:server_total_entities%`.
|
||||
Keep in mind that now you should configure a reasonable refresh interval for these placeholders to not cause TPS drops
|
||||
(with async placeholders it doesn't really matter).
|
||||
Configured refresh intervals and CPU usage of placeholders can be checked using `/tab cpu`.
|
||||
|
||||
# Placeholder is not working
|
||||
When using a placeholder, you should know if it's a [TAB's internal placeholder](#internal-placeholders) or a [PlaceholderAPI placeholder](https://github.com/PlaceholderAPI/PlaceholderAPI/wiki/Placeholders). If you don't, that's the problem.
|
||||
|
||||
**Note:** Don't forget that using `%` symbol will mess up placeholder starts and ends, breaking all placeholders after it. To make the symbol display correctly without messing up placeholders, use `%%` to display the symbol.
|
||||
**Example:** `Sale 100% OFF! %some_placeholder%` -> `Sale 100%% OFF! %some_placeholder%`.
|
||||
> [!CAUTION]
|
||||
> Remember that using `%` symbol will mess up placeholder starts and ends, breaking all placeholders after it.
|
||||
> To make the symbol display correctly without messing up placeholders, use `%%` to display the symbol.
|
||||
> **Example:** `Sale 100% OFF! %some_placeholder%` -> `Sale 100%% OFF! %some_placeholder%`.
|
||||
|
||||
## Internal placeholder is not working
|
||||
All TAB's internal placeholders are listed above. Some of them are bukkit only (meaning they won't work on bungeecord) and some are bungee only. **Trying to use a bukkit-only placeholder on bungeecord will not work.**
|
||||
If you want to use those, [set up PlaceholderAPI support on bungeecord](https://github.com/NEZNAMY/TAB/wiki/TAB-Bridge) and find their PlaceholderAPI equivalent. Large portion of those are included on [PlaceholderAPI's wiki](https://github.com/PlaceholderAPI/PlaceholderAPI/wiki/Placeholders).
|
||||
For example you can use %vault_prefix% instead of %vault-prefix%, %player_health% instead of %health% and so on.
|
||||
All TAB's internal placeholders are listed above.
|
||||
Some of them are bukkit only (meaning they won't work on bungeecord) and some are bungee only.
|
||||
> [!IMPORTANT]
|
||||
> Trying to use a bukkit-only placeholder on bungeecord will not work.
|
||||
> If you want to use those,
|
||||
> [set up PlaceholderAPI support on bungeecord](https://github.com/NEZNAMY/TAB/wiki/TAB-Bridge)
|
||||
> and find their PlaceholderAPI equivalent.
|
||||
> For example, you can use %vault_prefix% instead of %vault-prefix%,
|
||||
> %player_health_rounded% instead of %health% and so on.
|
||||
|
||||
## PlaceholderAPI placeholder is not working
|
||||
If you have TAB installed on bungeecord, make sure you [set up PlaceholderAPI support on bungeecord](https://github.com/NEZNAMY/TAB/wiki/TAB-Bridge) as there is no other way to retrieve data from technically a different server.
|
||||
|
||||
If you have TAB on bukkit or the previous step did not work, make sure it works when using `/papi parse me <placeholder>`. If it does not work, the issue is not on TAB's end.
|
||||
> [!TIP]
|
||||
> Use `/papi parse me <placeholder>` to check if the placeholder is working.
|
||||
> If it does not work, the issue is not on TAB's end.
|
||||
|
||||
# API
|
||||
*To get started with the API, see [Developer API](https://github.com/NEZNAMY/TAB/wiki/Developer-API) page.*
|
||||
@@ -153,7 +186,7 @@ For working with placeholders, you will need the `PlaceholderManager`. Get it us
|
||||
|
||||
Placeholders have two attributes:
|
||||
* An identifier, which is used to uniquely identify the placeholder. This is also what's usually used for replacement. For example, in `%uptime%`, the identifier is `%uptime%`.
|
||||
* A refresh rate in milliseconds, which is how often the placeholder's refresh function will be called, to get a new value to display. Use `-1` to make the placeholder never refresh periodically. This can be used for either constants or placeholders that update on events and you wish to update their value manually using functions in respective placeholder interface.
|
||||
* A refresh rate in milliseconds, which is how often the placeholder's refresh function will be called, to get a new value to display. Use `-1` to make the placeholder never refresh periodically. This can be used for either constants or placeholders that update on events, and you wish to update their value manually using functions in the respective placeholder interface.
|
||||
|
||||
## Server placeholders
|
||||
Server placeholders are global to the entire plugin. Their values are not dependent on player.
|
||||
@@ -172,7 +205,7 @@ This placeholder will show current system time and update every 50 milliseconds.
|
||||
|
||||
## Player placeholders
|
||||
Player placeholders are calculated on a per-player basis. These depend on the player that we want to get the value for.
|
||||
Examples of player placeholders would be nickname and prefix.
|
||||
Examples of player placeholders would be username and prefix.
|
||||
|
||||
To register a new player placeholder, use `PlaceholderManager#registerPlayerPlaceholder(String, int, Function<TabPlayer, Object>)`. The first two parameters are explained above, and the last parameter is a function that will be called to refresh the value of the placeholder.
|
||||
<details>
|
||||
@@ -202,5 +235,7 @@ TabAPI.getInstance().getPlaceholderManager().registerRelationalPlaceholder("%rel
|
||||
});
|
||||
```
|
||||
|
||||
This placeholder will show version of players, but only to those, who have `tab.staff` permission. First player in the method is viewer, second player is target. Same order is applied when hooking into PlaceholderAPI as well.
|
||||
This placeholder will show the version of players, but only to those, who have `tab.staff` permission.
|
||||
The first player in the method is viewer, the second player is target.
|
||||
The same order is applied when hooking into PlaceholderAPI as well.
|
||||
</details>
|
||||
@@ -2,13 +2,26 @@
|
||||
This is a quick guide for [PlaceholderAPI](https://www.spigotmc.org/resources/placeholderapi.6245/) plugin, which is often referred to on the wiki, and we also get many assistance requests with TAB which turn out to be asking for help with PlaceholderAPI. Whether its documentation is not clear enough, missing information or users just don't read it isn't the topic here to judge, instead, a quick guide with everything needed is provided.
|
||||
|
||||
# Installation
|
||||
PlaceholderAPI is a standalone bukkit plugin which can be installed just like any other plugin. It does not support BungeeCord. If you have TAB on BungeeCord and wish to use PlaceholderAPI placeholders, you'll need to install [Bridge](https://www.spigotmc.org/resources/83966) plugin on backend servers. Alongside other features, it forwards PlaceholderAPI support to the proxy.
|
||||
PlaceholderAPI is a standalone bukkit plugin that can be installed just like any other plugin.
|
||||
It does not support BungeeCord.
|
||||
If you have TAB on BungeeCord and wish to use PlaceholderAPI placeholders,
|
||||
you'll need to install [Bridge](https://www.spigotmc.org/resources/83966) plugin on backend servers.
|
||||
Alongside other features, it forwards PlaceholderAPI support to the proxy.
|
||||
|
||||
# Expansions
|
||||
In order to effectively scale and allow the level of modification it does, PlaceholderAPI is using placeholder packages called expansions. They are loaded into the plugin on startup. Each expansion can be created and modified independently of the main plugin's update frequency. Each expansion has a name, which is used to identify its placeholders.
|
||||
The syntax is `%<expansion>_<placeholder>%`, where `<expansion>` is name of the expansion, for example `player` and `<placeholder>` is placeholder of the expansion, for example `health`, which returns player's health. The full placeholder syntax will then be `%player_health%`.
|
||||
To effectively scale and allow the level of modification it does,
|
||||
PlaceholderAPI is using placeholder packages called expansions.
|
||||
They are loaded into the plugin on startup.
|
||||
Each expansion can be created and modified independently of the main plugin's update frequency.
|
||||
Each expansion has a name, which is used to identify its placeholders.
|
||||
The syntax is `%<expansion>_<placeholder>%`, where `<expansion>` is name of the expansion,
|
||||
for example `player` and `<placeholder>` is placeholder of the expansion, for example `health`,
|
||||
which returns player's health.
|
||||
The full placeholder syntax will then be `%player_health%`.
|
||||
|
||||
Some plugins offer their own expansions, which is either integrated into the plugin itself or external on PlaceholderAPI cloud. Integrated expansions are not downloaded, instead, plugins register them automatically.
|
||||
Some plugins offer their own expansions,
|
||||
which are either integrated into the plugin itself or external on PlaceholderAPI cloud.
|
||||
Integrated expansions are not downloaded; instead, plugins register them automatically.
|
||||
|
||||
You can download / update external expansions using `/papi ecloud download <expansion>`. After downloading it, run `/papi reload` to enable it.
|
||||
|
||||
|
||||
@@ -2,21 +2,27 @@
|
||||
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.
|
||||
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 plugins folder of your backend servers. **Bridge is NOT a BungeeCord plugin!** Bridge itself has no config file, as everything is configured in the main plugin.
|
||||
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 itself has no config file, as everything is configured in the main plugin.
|
||||
|
||||
# Features
|
||||
* Forwarding PlaceholderAPI support to the proxy
|
||||
* Allow detection if player is invisible to make nametag invisible (compensation for 1.8.x client sided bug)
|
||||
* Allow detection if player is disguised to disable collision to prevent endless push
|
||||
* Forwarding permission groups of players if no permission plugin is installed on BungeeCord
|
||||
* Forwarding vanish status for compatibility with global playerlist / layout features
|
||||
* Forwarding PlaceholderAPI support to the proxy
|
||||
* Allow detection if player is invisible to make their nametag invisible (compensation for 1.8.x client sided bug)
|
||||
* Allow detection if a player is disguised to disable collision to prevent endless push
|
||||
* Forwarding permission groups of players if no permission plugin is installed on BungeeCord
|
||||
* Forwarding vanish status for compatibility with global playerlist / layout features
|
||||
* Forward player's world for %world% and per-world settings
|
||||
* Allow TAB's PlaceholderAPI expansion with TAB on BungeeCord
|
||||
* Allow TAB's PlaceholderAPI expansion with TAB on BungeeCord
|
||||
* Forward player's gamemode for spectator fix feature to work
|
||||
|
||||
# Compatibility
|
||||
Just like with Minecraft, when there is a change in the communication protocol, older software will not be compatible with the new one. You should always be running latest versions of both TAB and Bridge.
|
||||
Just like with Minecraft, when there is a change in the communication protocol,
|
||||
older software will not be compatible with the new one.
|
||||
You should always be running the latest versions of both TAB and Bridge.
|
||||
|
||||
# Confirming it works
|
||||
You have many ways to verify TAB and Bridge are properly connected. Besides checking if the features above work, the easiest one is to run `/btab debug <player>`.
|
||||
@@ -24,4 +30,4 @@ If it's properly connected, you'll get a message saying this:
|
||||

|
||||
If it's not properly connected, you'll get this message:
|
||||

|
||||
If this is the case, it means Bridge is either not installed or versions are not compatible. Just update both to the latest version.
|
||||
If this is the case, it means Bridge is either not installed or versions are not compatible. Update both to the latest version.
|
||||
@@ -1,18 +1,18 @@
|
||||
# ---------------------------------------
|
||||
# THIS PAGE IS WORK IN PROGRESS
|
||||
# THIS PAGE IS A WORK IN PROGRESS
|
||||
# ---------------------------------------
|
||||
|
||||
# About
|
||||
This page compares TAB to other plugins offering similar features based on various parameters.
|
||||
|
||||
### Open issues
|
||||
Amount of open issues on GitHub. Keep in mind this number shouldn't be taken 100% seriously, because:
|
||||
Number of open issues on GitHub. Keep in mind this number shouldn't be taken 100% seriously, because:
|
||||
* People often open issues to ask for help instead of reporting an issue.
|
||||
* If nobody is using a plugin, nobody can find bugs. Bug not found doesn't mean it's not there.
|
||||
* Some of the issues may be feature requests, which may not be present elsewhere as those are not accepted.
|
||||
* Some issues may be feature requests, which may not be present elsewhere as those are not accepted.
|
||||
|
||||
### Price
|
||||
The price to obtain the jar. Most plugins are free, some are paid.
|
||||
The price to get the jar. Most plugins are free, some are paid.
|
||||
|
||||
### Minecraft
|
||||
Supported Minecraft versions by the plugin.
|
||||
@@ -30,118 +30,118 @@ Supported platforms by the plugin. Full tracked list includes:
|
||||
Whether the plugin runs in server's main thread (sync) or in a different thread (async). If a plugin is running sync, it may slow down the server's TPS. All of these features can be done async.
|
||||
|
||||
# Belowname
|
||||
| Attribute | [TAB](https://github.com/NEZNAMY/TAB) |
|
||||
| ------------- | ------------- |
|
||||
| **Open issues** |  |
|
||||
| **Price** |  |
|
||||
| **Minecraft** | 1.5+ |
|
||||
| **Platforms** |      |
|
||||
| **Async** | ✔ |
|
||||
| PlaceholderAPI support | ✔ |
|
||||
| Configurable number | ✔ |
|
||||
| Configurable text | ✔ |
|
||||
| 1.20.3+ NumberFormat support | ✔ |
|
||||
| Conditionally disable the feature | If any specified condition is met |
|
||||
| Attribute | [TAB](https://github.com/NEZNAMY/TAB) |
|
||||
|-----------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| **Open issues** |  |
|
||||
| **Price** |  |
|
||||
| **Minecraft** | 1.5+ |
|
||||
| **Platforms** |      |
|
||||
| **Async** | ✔ |
|
||||
| PlaceholderAPI support | ✔ |
|
||||
| Configurable number | ✔ |
|
||||
| Configurable text | ✔ |
|
||||
| 1.20.3+ NumberFormat support | ✔ |
|
||||
| Conditionally disable the feature | If any specified condition is met |
|
||||
|
||||
# Bossbar
|
||||
| Attribute | [TAB](https://github.com/NEZNAMY/TAB)| [UltraBar](https://github.com/ryandw11/UltraBar) | [AnimaBossBar](https://www.spigotmc.org/resources/bossbar-spigot-free-bossbar-plugin-1-9-1-18-1.59135/) |
|
||||
| ------------- | ------------- | ------------- | ------------- |
|
||||
| **Open issues** |  |  | - |
|
||||
| **Price** |  |  |  |
|
||||
| **Minecraft** | 1.5+ | 1.12+ | 1.9 - 1.18.1 |
|
||||
| **Platforms** |      |  |  |
|
||||
| **Async** | ✔ | ❌ | ❌ |
|
||||
| Announcements | ✔ (defined in config) | ✔ (defined at runtime) | ✔ (defined at runtime) |
|
||||
| Toggle command | ✔ (customizable) | ❌ | ❌ |
|
||||
| Display condition | Anything that has a placeholder | On join / death / command / in worldguard region | ❌ |
|
||||
| Attribute | [TAB](https://github.com/NEZNAMY/TAB) | [UltraBar](https://github.com/ryandw11/UltraBar) | [AnimaBossBar](https://www.spigotmc.org/resources/bossbar-spigot-free-bossbar-plugin-1-9-1-18-1.59135/) |
|
||||
|-------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------|
|
||||
| **Open issues** |  |  | - |
|
||||
| **Price** |  |  |  |
|
||||
| **Minecraft** | 1.5+ | 1.12+ | 1.9 - 1.18.1 |
|
||||
| **Platforms** |      |  |  |
|
||||
| **Async** | ✔ | ❌ | ❌ |
|
||||
| Announcements | ✔ (defined in config) | ✔ (defined at runtime) | ✔ (defined at runtime) |
|
||||
| Toggle command | ✔ (customizable) | ❌ | ❌ |
|
||||
| Display condition | Anything that has a placeholder | On join / death / command / in worldguard region | ❌ |
|
||||
|
||||
# Global Playerlist
|
||||
| Attribute | [TAB](https://github.com/NEZNAMY/TAB)| [BungeeTabListPlus](https://github.com/CodeCrafter47/BungeeTabListPlus) | [Velocitab](https://github.com/WiIIiam278/Velocitab) |
|
||||
| ------------- | ------------- | ------------- | ------------- |
|
||||
| **Open issues** |  |  |  |
|
||||
| **Price** |  |  |  |
|
||||
| **Platforms** |   |  |  |
|
||||
| Attribute | [TAB](https://github.com/NEZNAMY/TAB) | [BungeeTabListPlus](https://github.com/CodeCrafter47/BungeeTabListPlus) | [Velocitab](https://github.com/WiIIiam278/Velocitab) |
|
||||
|-----------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------|
|
||||
| **Open issues** |  |  |  |
|
||||
| **Price** |  |  |  |
|
||||
| **Platforms** |   |  |  |
|
||||
|
||||
# Header/Footer
|
||||
| Attribute | [TAB](https://github.com/NEZNAMY/TAB)| [TabList](https://github.com/montlikadani/TabList) |
|
||||
| ------------- | ------------- | ------------- |
|
||||
| **Open issues** |  |  |
|
||||
| **Price** |  |  |
|
||||
| **Platforms** |      |    |
|
||||
| Attribute | [TAB](https://github.com/NEZNAMY/TAB) | [TabList](https://github.com/montlikadani/TabList) |
|
||||
|-----------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| **Open issues** |  |  |
|
||||
| **Price** |  |  |
|
||||
| **Platforms** |      |    |
|
||||
|
||||
# Layout
|
||||
| Attribute | [TAB](https://github.com/NEZNAMY/TAB)| [BungeeTabListPlus](https://github.com/CodeCrafter47/BungeeTabListPlus) |
|
||||
| ------------- | ------------- | ------------- |
|
||||
| **Open issues** |  |  |
|
||||
| **Price** |  |  |
|
||||
| **Platforms** |      |   |
|
||||
| Attribute | [TAB](https://github.com/NEZNAMY/TAB) | [BungeeTabListPlus](https://github.com/CodeCrafter47/BungeeTabListPlus) |
|
||||
|-----------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| **Open issues** |  |  |
|
||||
| **Price** |  |  |
|
||||
| **Platforms** |      |   |
|
||||
|
||||
# Nametags
|
||||
| Attribute | [TAB](https://github.com/NEZNAMY/TAB)| [NametagEdit](https://github.com/sgtcaze/NametagEdit) |
|
||||
| ------------- | ------------- | ------------- |
|
||||
| **Open issues** |  |  |
|
||||
| **Price** |  |  |
|
||||
| **Minecraft** | 1.5+ | 1.7+ |
|
||||
| **Platforms** |      |  |
|
||||
| **Async** | ✔ | ❌ |
|
||||
| Customization | Per-group<br />Per-player<br />Per-world<br />Per-server | Per-group<br />Per-player |
|
||||
| PlaceholderAPI support | ✔ | ✔ |
|
||||
| Configurable collision | ✔ | ✔ |
|
||||
| Configurable nametag visibility | ✔ | ❌ |
|
||||
| Conditionally disable the feature | If any specified condition is met | ❌ |
|
||||
| Bonus | - Option to use armor stands to bypass<br /> all limits and support multiple lines<br />- Anti-override to avoid random plugins breaking it | - |
|
||||
| Attribute | [TAB](https://github.com/NEZNAMY/TAB) | [NametagEdit](https://github.com/sgtcaze/NametagEdit) |
|
||||
|-----------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------|
|
||||
| **Open issues** |  |  |
|
||||
| **Price** |  |  |
|
||||
| **Minecraft** | 1.5+ | 1.7+ |
|
||||
| **Platforms** |      |  |
|
||||
| **Async** | ✔ | ❌ |
|
||||
| Customization | Per-group<br />Per-player<br />Per-world<br />Per-server | Per-group<br />Per-player |
|
||||
| PlaceholderAPI support | ✔ | ✔ |
|
||||
| Configurable collision | ✔ | ✔ |
|
||||
| Configurable nametag visibility | ✔ | ❌ |
|
||||
| Conditionally disable the feature | If any specified condition is met | ❌ |
|
||||
| Bonus | - Option to use armor stands to bypass<br /> all limits and support multiple lines<br />- Anti-override to avoid random plugins breaking it | - |
|
||||
|
||||
# Per world playerlist
|
||||
|
||||
# Playerlist objective
|
||||
| Attribute | [TAB](https://github.com/NEZNAMY/TAB)| [TabList](https://github.com/montlikadani/TabList) |
|
||||
| ------------- | ------------- | ------------- |
|
||||
| **Open issues** |  |  |
|
||||
| **Price** |  |  |
|
||||
| **Platforms** |      |   |
|
||||
| Display any number | ✔ | ✔ |
|
||||
| Supports hearts display type | ✔ | ✔ |
|
||||
| 1.20.3+ NumberFormat support | ✔ | ❌ |
|
||||
| Conditionally disable the feature | When any specified condition is met | Disable in worlds |
|
||||
| Attribute | [TAB](https://github.com/NEZNAMY/TAB) | [TabList](https://github.com/montlikadani/TabList) |
|
||||
|-----------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| **Open issues** |  |  |
|
||||
| **Price** |  |  |
|
||||
| **Platforms** |      |   |
|
||||
| Display any number | ✔ | ✔ |
|
||||
| Supports hearts display type | ✔ | ✔ |
|
||||
| 1.20.3+ NumberFormat support | ✔ | ❌ |
|
||||
| Conditionally disable the feature | When any specified condition is met | Disable in worlds |
|
||||
|
||||
# Scoreboard
|
||||
| Attribute | [TAB](https://github.com/NEZNAMY/TAB)| [RealScoreboard](https://github.com/joserodpt/RealScoreboard) | [AnimatedScoreboard](https://www.spigotmc.org/resources/20848/) | [SternalBoard](https://github.com/ShieldCommunity/SternalBoard) | [KiteBoard](https://github.com/Niall7459/KiteBoard-Documentation) |
|
||||
| ------------- | ------------- | ------------- | ------------- | ------------- | ------------- |
|
||||
| **Open issues** |  |  | - |  |  |
|
||||
| **Price** |  |  |  |  |  |
|
||||
| **Minecraft** | 1.5+ | 1.13+ | 1.8+ | 1.7+ | 1.8 - 1.20.1 |
|
||||
| **Platforms** |      |  |  |  |  |
|
||||
| **Async** | ✔ | ✔ | ✔ | ✔ | ✔ |
|
||||
| Customizable toggle command | ✔ | ❌ | ❌ | ❌ | ❌ |
|
||||
| 1.20.3+ NumberFormat support | ✔ | ❌ | ❌ | ❌ | ❌ |
|
||||
| Max line length limit (<1.13 / 1.13+) |  /  | - /  |  /  |  /  |  /  |
|
||||
| Attribute | [TAB](https://github.com/NEZNAMY/TAB) | [RealScoreboard](https://github.com/joserodpt/RealScoreboard) | [AnimatedScoreboard](https://www.spigotmc.org/resources/20848/) | [SternalBoard](https://github.com/ShieldCommunity/SternalBoard) | [KiteBoard](https://github.com/Niall7459/KiteBoard-Documentation) |
|
||||
|---------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| **Open issues** |  |  | - |  |  |
|
||||
| **Price** |  |  |  |  |  |
|
||||
| **Minecraft** | 1.5+ | 1.13+ | 1.8+ | 1.7+ | 1.8 - 1.20.1 |
|
||||
| **Platforms** |      |  |  |  |  |
|
||||
| **Async** | ✔ | ✔ | ✔ | ✔ | ✔ |
|
||||
| Customizable toggle command | ✔ | ❌ | ❌ | ❌ | ❌ |
|
||||
| 1.20.3+ NumberFormat support | ✔ | ❌ | ❌ | ❌ | ❌ |
|
||||
| Max line length limit (<1.13 / 1.13+) |  /  | - /  |  /  |  /  |  /  |
|
||||
|
||||
| Attribute | [FeatherBoard](https://www.spigotmc.org/resources/featherboard.2691/) | [UltraScoreboards](https://www.spigotmc.org/resources/ultra-scoreboards.93726/) | [QuickBoard](https://github.com/tadeas-drab/QuickBoard) | [SimpleScore](https://github.com/r4g3baby/SimpleScore) | [InfiniteScoreboard](https://www.spigotmc.org/resources/infinite-scoreboard.87425/) |
|
||||
| ------------- | ------------- | ------------- | ------------- | ------------- | ------------- |
|
||||
| **Open issues** | - | - |  |  | - |
|
||||
| **Price** |  |  |  |  |  |
|
||||
| **Minecraft** | 1.7+ | 1.8+ | 1.8 - 1.16.1 | 1.8+ | 1.8 - 1.19 |
|
||||
| **Platforms** |  |   |  |  |  |
|
||||
| **Async** | ✔ | ❌ | ❌ | ❌ | ✔ |
|
||||
| Customizable toggle command | ❌ | ❌ | ❌ | ❌ | ❌ |
|
||||
| 1.20.3+ NumberFormat support | ❌ | ❌ | ❌ | ❌ | ❌ |
|
||||
| Attribute | [FeatherBoard](https://www.spigotmc.org/resources/featherboard.2691/) | [UltraScoreboards](https://www.spigotmc.org/resources/ultra-scoreboards.93726/) | [QuickBoard](https://github.com/tadeas-drab/QuickBoard) | [SimpleScore](https://github.com/r4g3baby/SimpleScore) | [InfiniteScoreboard](https://www.spigotmc.org/resources/infinite-scoreboard.87425/) |
|
||||
|---------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| **Open issues** | - | - |  |  | - |
|
||||
| **Price** |  |  |  |  |  |
|
||||
| **Minecraft** | 1.7+ | 1.8+ | 1.8 - 1.16.1 | 1.8+ | 1.8 - 1.19 |
|
||||
| **Platforms** |  |   |  |  |  |
|
||||
| **Async** | ✔ | ❌ | ❌ | ❌ | ✔ |
|
||||
| Customizable toggle command | ❌ | ❌ | ❌ | ❌ | ❌ |
|
||||
| 1.20.3+ NumberFormat support | ❌ | ❌ | ❌ | ❌ | ❌ |
|
||||
| Max line length limit (<1.13 / 1.13+) | - /  |  /  |  /  |  /  |  /  |
|
||||
|
||||
# Sorting
|
||||
| Attribute | [TAB](https://github.com/NEZNAMY/TAB)| [NametagEdit](https://github.com/sgtcaze/NametagEdit) | [TabList](https://github.com/montlikadani/TabList) | [Velocitab](https://github.com/WiIIiam278/Velocitab) |
|
||||
| ------------- | ------------- | ------------- | ------------- | ------------- |
|
||||
| **Open issues** |  |  |  |  |
|
||||
| **Price** |  |  |  |  |
|
||||
| **Minecraft** | 1.8+ | 1.8+ | 1.8+ | 1.8+ |
|
||||
| **Platforms** |      |  |   |  |
|
||||
| **Async** | ✔ | ❌ | ✔ | ✔ |
|
||||
| Sorting options | Groups<br />Permissions<br />Placeholder alphabetically<br />Placeholder numerically<br />Pre-defined placeholder values | Groups | Groups | Placeholder alphabetically<br />Placeholder numerically |
|
||||
| Attribute | [TAB](https://github.com/NEZNAMY/TAB) | [NametagEdit](https://github.com/sgtcaze/NametagEdit) | [TabList](https://github.com/montlikadani/TabList) | [Velocitab](https://github.com/WiIIiam278/Velocitab) |
|
||||
|-----------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------|
|
||||
| **Open issues** |  |  |  |  |
|
||||
| **Price** |  |  |  |  |
|
||||
| **Minecraft** | 1.8+ | 1.8+ | 1.8+ | 1.8+ |
|
||||
| **Platforms** |      |  |   |  |
|
||||
| **Async** | ✔ | ❌ | ✔ | ✔ |
|
||||
| Sorting options | Groups<br />Permissions<br />Placeholder alphabetically<br />Placeholder numerically<br />Pre-defined placeholder values | Groups | Groups | Placeholder alphabetically<br />Placeholder numerically |
|
||||
|
||||
# Tablist name formatting
|
||||
| Attribute | [TAB](https://github.com/NEZNAMY/TAB)| [TabList](https://github.com/montlikadani/TabList) | [BungeeTabListPlus](https://github.com/CodeCrafter47/BungeeTabListPlus) | [SimpleTabList](https://github.com/TheYIMIR/SimpleTabList) | [Velocitab](https://github.com/WiIIiam278/Velocitab) |
|
||||
| ------------- | ------------- | ------------- | ------------- | ------------- | ------------- |
|
||||
| **Open issues** |  |  |  |  |  |
|
||||
| **Price** |  |  |  |  |  |
|
||||
| **Minecraft** | 1.8+ | 1.8+ | 1.8+ | 1.16+ | 1.8+ |
|
||||
| **Platforms** |      |    |   |  |  |
|
||||
| **Async** | ✔ | ✔ | ✔ | ❌ | ✔ |
|
||||
| Attribute | [TAB](https://github.com/NEZNAMY/TAB) | [TabList](https://github.com/montlikadani/TabList) | [BungeeTabListPlus](https://github.com/CodeCrafter47/BungeeTabListPlus) | [SimpleTabList](https://github.com/TheYIMIR/SimpleTabList) | [Velocitab](https://github.com/WiIIiam278/Velocitab) |
|
||||
|-----------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------|
|
||||
| **Open issues** |  |  |  |  |  |
|
||||
| **Price** |  |  |  |  |  |
|
||||
| **Minecraft** | 1.8+ | 1.8+ | 1.8+ | 1.16+ | 1.8+ |
|
||||
| **Platforms** |      |    |   |  |  |
|
||||
| **Async** | ✔ | ✔ | ✔ | ❌ | ✔ |
|
||||
+32
-5
@@ -1,20 +1,47 @@
|
||||
## Easy to install
|
||||
Support for all possible Minecraft versions containing features the plugin is able to take advantage of, support for multiple platforms (Bukkit, BungeeCord, Velocity, Fabric, Sponge), with everything contained in a single jar which you can download directly. No need to browse version history, ask what plugin version you need for your server version, visit random websites looking for the download button, or anything similar. The same jar supports all versions & platforms, and the latest plugin version will always work for you.
|
||||
Support for all possible Minecraft versions containing features the plugin is able to take advantage of.
|
||||
Support for multiple platforms (Bukkit, BungeeCord, Velocity, Fabric, Sponge),
|
||||
with everything contained in a single jar which you can download directly.
|
||||
No need to browse version history, ask what plugin version you need for your server version,
|
||||
visit random websites looking for the download button, or anything similar.
|
||||
The same jar supports all versions & platforms, and the latest plugin version will always work for you.
|
||||
|
||||
## It works
|
||||
The plugin doesn't spam your console with errors, doesn't have compatibility issues with half of the plugins, and doesn't just decide to randomly stop working for no reason. It just works. Bug reports are fixed with highest priority and preferred over adding new features. It's not unusual for a plugin of this size to have a 3-digit open issue count on github. Meanwhile TAB is usually at 1-digit count.
|
||||
The plugin doesn't spam your console with errors, doesn't have compatibility issues with half of the plugins,
|
||||
and doesn't just decide to randomly stop working for no reason.
|
||||
It just works.
|
||||
Bug reports are fixed with the highest priority and preferred over adding new features.
|
||||
It's not unusual for a plugin of this size to have a 3-digit open issue count on GitHub.
|
||||
Meanwhile, TAB is always at 1-digit count.
|
||||
|
||||
## Feature packed
|
||||
All of the most requested features in a single plugin. No need to install many plugins, disable features so they don't conflict with each other, or be forced to run your server on a version that all of the plugins support in common. All features, all server versions in a single plugin.
|
||||
All the most requested features in a single plugin.
|
||||
No need to install many plugins, disable features, so they don't conflict with each other,
|
||||
or be forced to run your server on a version that all the plugins support in common.
|
||||
All features, all server versions in a single plugin.
|
||||
|
||||
## Beyond the limits
|
||||
Mojang decided to put some kind of limitation on almost every feature TAB offers. Other developers simply say "Mojang limit, nothing we can do about it, goodbye". This is not the case here. With every limitation I find, I look for ways to bypass it, and I've found most of them. These bypasses require a ton of work involved, and it's much easier to say "not possible" than actually doing it.
|
||||
|
||||
## Up to date
|
||||
The plugin is not abandoned. It is constantly getting updated to fix reported bugs, add support for new MC versions as they come out and get new optimizations that can help it run as smoothly as possible, whilst still working the way you want it to.
|
||||
The plugin is not abandoned.
|
||||
It is constantly getting updated to fix reported bugs,
|
||||
add support for new MC versions as they come out and get new optimizations.
|
||||
All that can help it run as smoothly as possible, whilst still working the way you want it to.
|
||||
|
||||
## High performance
|
||||
Unlike with other plugins where "high performance" often means your CPU usage will be high, TAB has a very low CPU footprint. There is a [command](https://github.com/NEZNAMY/TAB/wiki/Commands-&-Permissions#tab-cpu) available that shows the entire plugin's CPU usage. Here you can see which part of TAB is occupying the most of your CPU, and there are [many ways to optimize TAB](https://github.com/NEZNAMY/TAB/wiki/Optimizing-the-plugin) to get the most out of it, the most common being increasing refresh rates for individual placeholders if they're inefficient. This is done on a per-placeholder basis, instead of slowing down the refresh rate of an entire feature, which may break animations. Performance is also actively monitored on large servers, and observations show that it performs very well, meaning it will almost certainly perform even better on smaller servers.
|
||||
Unlike with other plugins where "high performance"
|
||||
often means your CPU usage will be high, TAB has a very low CPU footprint.
|
||||
There is a [command](https://github.com/NEZNAMY/TAB/wiki/Commands-&-Permissions#tab-cpu) available
|
||||
that shows the entire plugin's CPU usage.
|
||||
Here you can see which part of TAB is occupying the most of your CPU.
|
||||
There are [many ways to optimize TAB](https://github.com/NEZNAMY/TAB/wiki/Optimizing-the-plugin)
|
||||
to get the most out of it,
|
||||
the most common being increasing refresh rates for individual placeholders if they're inefficient.
|
||||
This is done on a per-placeholder basis,
|
||||
instead of slowing down the refresh rate of an entire feature, which may break animations.
|
||||
Performance is also actively monitored on large servers, and observations show that it performs very well,
|
||||
meaning it will almost certainly perform even better on smaller servers.
|
||||
|
||||
## Simple yet powerful configuration
|
||||
Compared to other plugins, each feature is easier to configure and actually offers more.
|
||||
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
distribution: 'temurin'
|
||||
java-version: 21
|
||||
- name: Cache Gradle
|
||||
uses: actions/cache@v4.0.2 # https://github.com/actions/cache/releases
|
||||
uses: actions/cache@v4.2.0 # https://github.com/actions/cache/releases
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
|
||||
@@ -17,21 +17,24 @@ public interface TabPlayer {
|
||||
*
|
||||
* @return Player's name
|
||||
*/
|
||||
@NotNull String getName();
|
||||
@NotNull
|
||||
String getName();
|
||||
|
||||
/**
|
||||
* Returns player's UUID
|
||||
*
|
||||
* @return Player's UUID
|
||||
*/
|
||||
@NotNull UUID getUniqueId();
|
||||
@NotNull
|
||||
UUID getUniqueId();
|
||||
|
||||
/**
|
||||
* Returns platform-specific entity
|
||||
*
|
||||
* @return platform's player object
|
||||
*/
|
||||
@NotNull Object getPlayer();
|
||||
@NotNull
|
||||
Object getPlayer();
|
||||
|
||||
/**
|
||||
* Returns true once the player is successfully loaded (onJoin method ran through all methods)
|
||||
@@ -47,7 +50,8 @@ public interface TabPlayer {
|
||||
*
|
||||
* @return player's primary permission group
|
||||
*/
|
||||
@NotNull String getGroup();
|
||||
@NotNull
|
||||
String getGroup();
|
||||
|
||||
/**
|
||||
* Temporarily overrides player's group and applies all changes coming from new group.
|
||||
@@ -91,4 +95,32 @@ public interface TabPlayer {
|
||||
*/
|
||||
@NotNull
|
||||
String getExpectedProfileName();
|
||||
|
||||
|
||||
/**
|
||||
* Returns name of the server the player is connected to.
|
||||
* This requires TAB on proxy.
|
||||
* On backend installation, this method returns {@code "N/A"}.
|
||||
*
|
||||
* @return Player's server on proxy or {@code "N/A"} on backend installation
|
||||
*/
|
||||
@NotNull
|
||||
String getServer();
|
||||
|
||||
/**
|
||||
* Returns name of the world the player is in.
|
||||
* On proxy installation, TAB-Bridge must be installed to forward this info to the proxy.
|
||||
* Otherwise, it will return {@code "N/A"}.
|
||||
*
|
||||
* @return Player's world or {@code "N/A"} on proxy installation without TAB-Bridge installed
|
||||
*/
|
||||
@NotNull
|
||||
String getWorld();
|
||||
|
||||
/**
|
||||
* Returns {@code true} if this player is a bedrock player, {@code false} if not.
|
||||
*
|
||||
* @return {@code true} if this player is a bedrock player, {@code false} if not
|
||||
*/
|
||||
boolean isBedrockPlayer();
|
||||
}
|
||||
+2
-2
@@ -4,7 +4,7 @@ plugins {
|
||||
|
||||
allprojects {
|
||||
group = "me.neznamy"
|
||||
version = "5.0.3"
|
||||
version = "5.0.7"
|
||||
description = "An all-in-one solution that works"
|
||||
|
||||
ext.set("id", "tab")
|
||||
@@ -14,8 +14,8 @@ allprojects {
|
||||
|
||||
val platforms = setOf(
|
||||
projects.bukkit,
|
||||
projects.bukkit.paper,
|
||||
projects.bungeecord,
|
||||
projects.component,
|
||||
projects.velocity,
|
||||
projects.sponge7,
|
||||
projects.sponge8,
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
plugins {
|
||||
id("io.papermc.paperweight.userdev") version "1.7.1"
|
||||
}
|
||||
|
||||
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"
|
||||
|
||||
dependencies {
|
||||
implementation(projects.bukkit)
|
||||
paperweight.paperDevBundle(version)
|
||||
compileOnly("io.papermc.paper:paper-api:${version}")
|
||||
}
|
||||
|
||||
tasks.compileJava {
|
||||
options.release.set(21)
|
||||
}
|
||||
-62
@@ -1,62 +0,0 @@
|
||||
package me.neznamy.tab.platforms.paper;
|
||||
|
||||
import me.neznamy.tab.platforms.bukkit.nms.converter.ComponentConverter;
|
||||
import me.neznamy.tab.shared.chat.ChatModifier;
|
||||
import me.neznamy.tab.shared.chat.SimpleComponent;
|
||||
import me.neznamy.tab.shared.chat.StructuredComponent;
|
||||
import me.neznamy.tab.shared.chat.TabComponent;
|
||||
import net.minecraft.ChatFormatting;
|
||||
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;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Component converter using direct mojang-mapped code for versions 1.20.5+.
|
||||
*/
|
||||
@SuppressWarnings("unused") // Used via reflection
|
||||
public class PaperComponentConverter extends ComponentConverter {
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Component convert(@NotNull TabComponent component, boolean modern) {
|
||||
if (component instanceof SimpleComponent) return Component.literal(((SimpleComponent) component).getText());
|
||||
|
||||
StructuredComponent component1 = (StructuredComponent) component;
|
||||
MutableComponent nmsComponent = Component.literal(component1.getText());
|
||||
nmsComponent.setStyle(createModifierModern(component1.getModifier(), modern));
|
||||
for (StructuredComponent extra : component1.getExtra()) {
|
||||
nmsComponent.append(convert(extra, modern));
|
||||
}
|
||||
return nmsComponent;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private Style createModifierModern(@NotNull ChatModifier modifier, boolean modern) {
|
||||
TextColor color = null;
|
||||
if (modifier.getColor() != null) {
|
||||
if (modern) {
|
||||
color = TextColor.fromRgb(modifier.getColor().getRgb());
|
||||
} else {
|
||||
color = TextColor.fromRgb(modifier.getColor().getLegacyColor().getRgb());
|
||||
}
|
||||
}
|
||||
List<ChatFormatting> formats = new ArrayList<>();
|
||||
if (modifier.isBold()) formats.add(ChatFormatting.BOLD);
|
||||
if (modifier.isItalic()) formats.add(ChatFormatting.ITALIC);
|
||||
if (modifier.isUnderlined()) formats.add(ChatFormatting.UNDERLINE);
|
||||
if (modifier.isStrikethrough()) formats.add(ChatFormatting.STRIKETHROUGH);
|
||||
if (modifier.isObfuscated()) formats.add(ChatFormatting.OBFUSCATED);
|
||||
|
||||
Style style = Style.EMPTY;
|
||||
if (color != null) style = style.withColor(color);
|
||||
if (!formats.isEmpty()) style = style.applyFormats(formats.toArray(new ChatFormatting[0]));
|
||||
if (modifier.getFont() != null) style = style.withFont(ResourceLocation.tryParse(modifier.getFont()));
|
||||
return style;
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package me.neznamy.tab.platforms.paper;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.shared.util.FunctionWithException;
|
||||
import org.bukkit.craftbukkit.entity.CraftPlayer;
|
||||
|
||||
/**
|
||||
* Loader for 1.20.5+ Paper to speed up functions by using direct NMS code instead of reflection.
|
||||
*/
|
||||
@SuppressWarnings("unused") // Via reflection
|
||||
public class PaperLoader {
|
||||
|
||||
/** Channel getter using direct NMS code */
|
||||
public static final FunctionWithException<BukkitTabPlayer, Channel> getChannel =
|
||||
player -> ((CraftPlayer)player.getPlayer()).getHandle().connection.connection.channel;
|
||||
}
|
||||
@@ -1,159 +0,0 @@
|
||||
package me.neznamy.tab.platforms.paper;
|
||||
|
||||
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 org.bukkit.craftbukkit.entity.CraftPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Collections;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Scoreboard implementation using direct mojang-mapped code for versions 1.20.5+.
|
||||
*/
|
||||
@SuppressWarnings("unused") // Used via reflection
|
||||
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 method;
|
||||
private static final Field players;
|
||||
|
||||
static {
|
||||
try {
|
||||
method = ReflectionUtils.setAccessible(ClientboundSetPlayerTeamPacket.class.getDeclaredField("method"));
|
||||
players = ReflectionUtils.setAccessible(ClientboundSetPlayerTeamPacket.class.getDeclaredField("players"));
|
||||
} catch (NoSuchFieldException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(player.getVersion()),
|
||||
ObjectiveCriteria.RenderType.values()[objective.getHealthDisplay().ordinal()],
|
||||
false,
|
||||
objective.getNumberFormat() == null ? null : objective.getNumberFormat().toFixedFormat(FixedFormat::new)
|
||||
);
|
||||
objective.setPlatformObjective(obj);
|
||||
sendPacket(new ClientboundSetObjectivePacket(obj, ObjectiveAction.REGISTER));
|
||||
sendPacket(new ClientboundSetDisplayObjectivePacket(net.minecraft.world.scores.DisplaySlot.values()[objective.getDisplaySlot().ordinal()], obj));
|
||||
}
|
||||
|
||||
@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(player.getVersion()));
|
||||
obj.setRenderType(ObjectiveCriteria.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(player.getVersion())),
|
||||
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().ordinal()]);
|
||||
t.setCollisionRule(collisions[team.getCollision().ordinal()]);
|
||||
t.setNameTagVisibility(visibilities[team.getVisibility().ordinal()]);
|
||||
t.setPlayerPrefix(team.getPrefix().convert(player.getVersion()));
|
||||
t.setPlayerSuffix(team.getSuffix().convert(player.getVersion()));
|
||||
}
|
||||
|
||||
@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 (isAntiOverrideTeams() && packet instanceof ClientboundSetPlayerTeamPacket team) {
|
||||
int action = method.getInt(team);
|
||||
if (action == TeamAction.UPDATE) return;
|
||||
players.set(team, onTeamPacket(action, team.getName(), team.getPlayers() == null ? Collections.emptyList() : team.getPlayers()));
|
||||
}
|
||||
}
|
||||
|
||||
private void sendPacket(@NotNull Packet<?> packet) {
|
||||
((CraftPlayer)player.getPlayer()).getHandle().connection.sendPacket(packet);
|
||||
}
|
||||
}
|
||||
@@ -1,176 +0,0 @@
|
||||
package me.neznamy.tab.platforms.paper;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import com.mojang.authlib.properties.Property;
|
||||
import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.tablist.TabListBase;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.chat.TabComponent;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
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 for versions 1.21.2+.
|
||||
*/
|
||||
@SuppressWarnings("unused") // Used via reflection
|
||||
public class PaperPacketTabList extends TabListBase<Component> {
|
||||
|
||||
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 EnumSet<ClientboundPlayerInfoUpdatePacket.Action> addPlayer = EnumSet.allOf(ClientboundPlayerInfoUpdatePacket.Action.class);
|
||||
|
||||
private static final Field entries;
|
||||
|
||||
static {
|
||||
try {
|
||||
entries = ReflectionUtils.setAccessible(ClientboundPlayerInfoUpdatePacket.class.getDeclaredField("entries"));
|
||||
} catch (NoSuchFieldException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 updateDisplayName(@NonNull UUID entry, @Nullable Component displayName) {
|
||||
sendPacket(new ClientboundPlayerInfoUpdatePacket(updateDisplayName, new ClientboundPlayerInfoUpdatePacket.Entry(
|
||||
entry, null, false, 0, null, displayName, 0, null
|
||||
)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateLatency(@NonNull UUID entry, int latency) {
|
||||
sendPacket(new ClientboundPlayerInfoUpdatePacket(updateLatency, new ClientboundPlayerInfoUpdatePacket.Entry(
|
||||
entry, null, false, latency, null, null, 0, null
|
||||
)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateGameMode(@NonNull UUID entry, int gameMode) {
|
||||
sendPacket(new ClientboundPlayerInfoUpdatePacket(updateGameMode, new ClientboundPlayerInfoUpdatePacket.Entry(
|
||||
entry, null, false, 0, GameType.byId(gameMode), null, 0, null
|
||||
)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateListed(@NonNull UUID entry, boolean listed) {
|
||||
sendPacket(new ClientboundPlayerInfoUpdatePacket(updateListed, new ClientboundPlayerInfoUpdatePacket.Entry(
|
||||
entry, null, listed, 0, null, null, 0, null
|
||||
)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateListOrder(@NonNull UUID entry, int listOrder) {
|
||||
sendPacket(new ClientboundPlayerInfoUpdatePacket(updateListOrder, new ClientboundPlayerInfoUpdatePacket.Entry(
|
||||
entry, null, false, 0, null, null, listOrder, null
|
||||
)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateHat(@NonNull UUID entry, boolean showHat) {
|
||||
// TODO update module to 1.21.4 when it comes out
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addEntry(@NonNull UUID id, @NonNull String name, @Nullable Skin skin, boolean listed, int latency,
|
||||
int gameMode, @Nullable Component displayName, int listOrder, boolean showHat) {
|
||||
sendPacket(new ClientboundPlayerInfoUpdatePacket(addPlayer, new ClientboundPlayerInfoUpdatePacket.Entry(
|
||||
id, createProfile(id, name, skin), listed, latency, GameType.byId(gameMode), displayName, listOrder, null
|
||||
)));
|
||||
// TODO update module to 1.21.4 when it comes out
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPlayerListHeaderFooter(@NonNull TabComponent header, @NonNull TabComponent footer) {
|
||||
sendPacket(new ClientboundTabListPacket(header.convert(player.getVersion()), footer.convert(player.getVersion())));
|
||||
}
|
||||
|
||||
@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)) {
|
||||
Component expectedDisplayName = getExpectedDisplayNames().get(nmsData.profileId());
|
||||
if (expectedDisplayName != null) {
|
||||
displayName = expectedDisplayName;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Skin getSkin() {
|
||||
Collection<Property> col = ((CraftPlayer)player.getPlayer()).getProfile().getProperties().get(TEXTURES_PROPERTY);
|
||||
if (col.isEmpty()) return null; //offline mode
|
||||
Property property = col.iterator().next();
|
||||
return new TabList.Skin(property.value(), property.signature());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private GameProfile createProfile(@NotNull UUID id, @NotNull 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;
|
||||
}
|
||||
|
||||
private void sendPacket(@NotNull Packet<?> packet) {
|
||||
((CraftPlayer)player.getPlayer()).getHandle().connection.sendPacket(packet);
|
||||
}
|
||||
}
|
||||
@@ -4,31 +4,19 @@ import io.netty.channel.Channel;
|
||||
import lombok.Setter;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.shared.features.injection.NettyPipelineInjector;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import me.neznamy.tab.shared.util.FunctionWithException;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import me.neznamy.tab.shared.util.function.FunctionWithException;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.EnumSet;
|
||||
|
||||
/**
|
||||
* Pipeline injection for Bukkit 1.8+.
|
||||
*/
|
||||
public class BukkitPipelineInjector extends NettyPipelineInjector {
|
||||
|
||||
/** Versions supported by paper module that uses direct mojang-mapped NMS for latest MC version */
|
||||
private static final EnumSet<ProtocolVersion> paperNativeVersions = EnumSet.of(
|
||||
ProtocolVersion.V1_20_5,
|
||||
ProtocolVersion.V1_20_6,
|
||||
ProtocolVersion.V1_21,
|
||||
ProtocolVersion.V1_21_1,
|
||||
ProtocolVersion.V1_21_2,
|
||||
ProtocolVersion.V1_21_3
|
||||
);
|
||||
|
||||
/** Function for getting player's channel */
|
||||
@Setter
|
||||
private static FunctionWithException<BukkitTabPlayer, Channel> getChannel;
|
||||
@@ -42,18 +30,10 @@ public class BukkitPipelineInjector extends NettyPipelineInjector {
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @param serverVersion
|
||||
* Server version
|
||||
* If something fails, an error message is printed and class is not marked as available.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static void tryLoad(@NotNull ProtocolVersion serverVersion) {
|
||||
public static void tryLoad() {
|
||||
try {
|
||||
if (ReflectionUtils.classExists("org.bukkit.craftbukkit.CraftServer") && paperNativeVersions.contains(serverVersion)) {
|
||||
getChannel = (FunctionWithException<BukkitTabPlayer, Channel>) Class.forName("me.neznamy.tab.platforms.paper.PaperLoader").getDeclaredField("getChannel").get(null);
|
||||
return;
|
||||
}
|
||||
Class<?> NetworkManager = BukkitReflection.getClass("network.Connection", "network.NetworkManager", "NetworkManager");
|
||||
Class<?> PlayerConnection = BukkitReflection.getClass("server.network.ServerGamePacketListenerImpl",
|
||||
"server.network.PlayerConnection", "PlayerConnection");
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
package me.neznamy.tab.platforms.bukkit;
|
||||
|
||||
import me.neznamy.tab.platforms.bukkit.platform.BukkitPlatform;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.chat.EnumChatFormat;
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
@@ -23,7 +24,7 @@ public class BukkitTabCommand implements CommandExecutor, TabCompleter {
|
||||
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
|
||||
if (TAB.getInstance().isPluginDisabled()) {
|
||||
for (String message : TAB.getInstance().getDisabledCommand().execute(args, sender.hasPermission(TabConstants.Permission.COMMAND_RELOAD), sender.hasPermission(TabConstants.Permission.COMMAND_ALL))) {
|
||||
sender.sendMessage(EnumChatFormat.color(message));
|
||||
sender.sendMessage(((BukkitPlatform)TAB.getInstance().getPlatform()).toBukkitFormat(TabComponent.fromColoredText(message)));
|
||||
}
|
||||
} else {
|
||||
TabPlayer p = null;
|
||||
|
||||
@@ -8,7 +8,7 @@ import me.neznamy.tab.platforms.bukkit.nms.PingRetriever;
|
||||
import me.neznamy.tab.platforms.bukkit.platform.BukkitPlatform;
|
||||
import me.neznamy.tab.platforms.bukkit.tablist.TabListBase;
|
||||
import me.neznamy.tab.shared.backend.BackendTabPlayer;
|
||||
import me.neznamy.tab.shared.chat.TabComponent;
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.metadata.MetadataValue;
|
||||
@@ -57,7 +57,7 @@ public class BukkitTabPlayer extends BackendTabPlayer {
|
||||
|
||||
@Override
|
||||
public void sendMessage(@NotNull TabComponent message) {
|
||||
getPlayer().sendMessage(getPlatform().toBukkitFormat(message, getVersion().supportsRGB()));
|
||||
getPlayer().sendMessage(getPlatform().toBukkitFormat(message));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -4,7 +4,6 @@ import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.shared.chat.EnumChatFormat;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -58,8 +57,7 @@ public class BukkitUtils {
|
||||
public static void compatibilityError(@NotNull Exception exception, @NotNull String failedCheck,
|
||||
@Nullable String fallback, @NotNull String... missingFeatures) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(EnumChatFormat.RED);
|
||||
sb.append("[TAB] Failed to initialize minecraft fields for ");
|
||||
sb.append("§c[TAB] Failed to initialize minecraft fields for ");
|
||||
sb.append(failedCheck);
|
||||
sb.append(" due to a compatibility error. ");
|
||||
if (fallback != null) {
|
||||
@@ -81,7 +79,7 @@ public class BukkitUtils {
|
||||
*/
|
||||
public static void sendCompatibilityMessage() {
|
||||
if (!compatibilityIssue) return;
|
||||
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.RED + "[TAB] Please update the plugin to " +
|
||||
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,9 +1,9 @@
|
||||
package me.neznamy.tab.platforms.bukkit.bossbar;
|
||||
|
||||
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.shared.chat.TabComponent;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.shared.platform.decorators.SafeBossBar;
|
||||
import org.bukkit.Bukkit;
|
||||
@@ -28,7 +28,7 @@ public class BukkitBossBar extends SafeBossBar<BossBar> {
|
||||
@NotNull
|
||||
public BossBar constructBossBar(@NotNull TabComponent title, float progress, @NotNull BarColor color, @NotNull BarStyle style) {
|
||||
BossBar bar = Bukkit.createBossBar(
|
||||
player.getPlatform().toBukkitFormat(title, player.getVersion().supportsRGB()),
|
||||
player.getPlatform().toBukkitFormat(title),
|
||||
org.bukkit.boss.BarColor.valueOf(color.name()),
|
||||
styles[style.ordinal()]
|
||||
);
|
||||
@@ -43,7 +43,7 @@ public class BukkitBossBar extends SafeBossBar<BossBar> {
|
||||
|
||||
@Override
|
||||
public void updateTitle(SafeBossBar<BossBar>.@NotNull BossBarInfo bar) {
|
||||
bar.getBossBar().setTitle(player.getPlatform().toBukkitFormat(bar.getTitle(), player.getVersion().supportsRGB()));
|
||||
bar.getBossBar().setTitle(player.getPlatform().toBukkitFormat(bar.getTitle()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -8,7 +8,7 @@ import lombok.RequiredArgsConstructor;
|
||||
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.chat.TabComponent;
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import me.neznamy.tab.shared.platform.decorators.SafeBossBar;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ package me.neznamy.tab.platforms.bukkit.header;
|
||||
|
||||
import lombok.Getter;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.shared.chat.TabComponent;
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -25,8 +25,8 @@ public class BukkitHeaderFooter extends HeaderFooter {
|
||||
@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.getVersion().supportsRGB()),
|
||||
player.getPlatform().toBukkitFormat(footer, player.getVersion().supportsRGB())
|
||||
player.getPlatform().toBukkitFormat(header),
|
||||
player.getPlatform().toBukkitFormat(footer)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ package me.neznamy.tab.platforms.bukkit.header;
|
||||
import lombok.Getter;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitUtils;
|
||||
import me.neznamy.tab.shared.chat.TabComponent;
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@ import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.converter.ComponentConverter;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.PacketSender;
|
||||
import me.neznamy.tab.shared.chat.TabComponent;
|
||||
import me.neznamy.tab.shared.util.BiFunctionWithException;
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import me.neznamy.tab.shared.util.function.BiFunctionWithException;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@@ -52,6 +52,6 @@ public class PacketHeaderFooter extends HeaderFooter {
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void set(@NotNull BukkitTabPlayer player, @NotNull TabComponent header, @NotNull TabComponent footer) {
|
||||
packetSender.sendPacket(player, createPacket.apply(header.convert(player.getVersion()), footer.convert(player.getVersion())));
|
||||
packetSender.sendPacket(player, createPacket.apply(header.convert(), footer.convert()));
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ package me.neznamy.tab.platforms.bukkit.header;
|
||||
|
||||
import lombok.Getter;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.shared.chat.TabComponent;
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.bukkit.entity.Player;
|
||||
@@ -20,6 +20,6 @@ public class PaperHeaderFooter extends HeaderFooter {
|
||||
|
||||
@Override
|
||||
public void set(@NotNull BukkitTabPlayer player, @NotNull TabComponent header, @NotNull TabComponent footer) {
|
||||
player.getPlayer().sendPlayerListHeaderAndFooter(header.toAdventure(player.getVersion()), footer.toAdventure(player.getVersion()));
|
||||
player.getPlayer().sendPlayerListHeaderAndFooter(header.toAdventure(), footer.toAdventure());
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ package me.neznamy.tab.platforms.bukkit.nms;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.shared.util.FunctionWithException;
|
||||
import me.neznamy.tab.shared.util.function.FunctionWithException;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@@ -2,7 +2,7 @@ package me.neznamy.tab.platforms.bukkit.nms;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.shared.util.BiConsumerWithException;
|
||||
import me.neznamy.tab.shared.util.function.BiConsumerWithException;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ 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.ToIntFunction;
|
||||
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;
|
||||
|
||||
+89
-26
@@ -1,29 +1,20 @@
|
||||
package me.neznamy.tab.platforms.bukkit.nms.converter;
|
||||
|
||||
import me.neznamy.chat.ChatModifier;
|
||||
import me.neznamy.chat.component.KeybindComponent;
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import me.neznamy.chat.component.TextComponent;
|
||||
import me.neznamy.chat.component.TranslatableComponent;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitUtils;
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.shared.chat.TabComponent;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.EnumSet;
|
||||
|
||||
/**
|
||||
* Interface for converting TAB component into NMS components (1.7+).
|
||||
* Interface for converting TAB components into NMS components (1.7+).
|
||||
*/
|
||||
public abstract class ComponentConverter {
|
||||
|
||||
/** Versions supported by paper module that uses direct mojang-mapped NMS for latest MC version */
|
||||
private static final EnumSet<ProtocolVersion> paperNativeVersions = EnumSet.of(
|
||||
ProtocolVersion.V1_20_5,
|
||||
ProtocolVersion.V1_20_6,
|
||||
ProtocolVersion.V1_21,
|
||||
ProtocolVersion.V1_21_1,
|
||||
ProtocolVersion.V1_21_2,
|
||||
ProtocolVersion.V1_21_3
|
||||
);
|
||||
|
||||
/** Instance of this class */
|
||||
@Nullable
|
||||
public static ComponentConverter INSTANCE;
|
||||
@@ -33,25 +24,97 @@ public abstract class ComponentConverter {
|
||||
*
|
||||
* @param component
|
||||
* Component to convert
|
||||
* @param modern
|
||||
* Whether client supports RGB or not
|
||||
* @return Converted component
|
||||
*/
|
||||
@NotNull
|
||||
public abstract Object convert(@NotNull TabComponent component, boolean modern);
|
||||
public Object convert(@NotNull TabComponent component) {
|
||||
// Component type
|
||||
Object nmsComponent;
|
||||
if (component instanceof TextComponent) {
|
||||
nmsComponent = newTextComponent(((TextComponent) component).getText());
|
||||
} else if (component instanceof TranslatableComponent) {
|
||||
nmsComponent = newTranslatableComponent(((TranslatableComponent) component).getKey());
|
||||
} else if (component instanceof KeybindComponent) {
|
||||
nmsComponent = newKeybindComponent(((KeybindComponent)component).getKeybind());
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unexpected component type: " + component.getClass().getName());
|
||||
}
|
||||
|
||||
// Component style
|
||||
applyStyle(nmsComponent, component.getModifier());
|
||||
|
||||
// Extra
|
||||
for (TabComponent extra : component.getExtra()) {
|
||||
addSibling(nmsComponent, convert(extra));
|
||||
}
|
||||
|
||||
return nmsComponent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new text component with given text.
|
||||
*
|
||||
* @param text
|
||||
* Text to display
|
||||
* @return Text component with given text
|
||||
*/
|
||||
@NotNull
|
||||
public abstract Object newTextComponent(@NotNull String text);
|
||||
|
||||
/**
|
||||
* Creates a new translatable component with the given key.
|
||||
*
|
||||
* @param key
|
||||
* Key to translate
|
||||
* @return Translatable component with the given key
|
||||
*/
|
||||
@NotNull
|
||||
public abstract Object newTranslatableComponent(@NotNull String key);
|
||||
|
||||
/**
|
||||
* Creates a new keybind component with given keybind.
|
||||
*
|
||||
* @param keybind
|
||||
* Keybind to show
|
||||
* @return Keybind component with given keybind
|
||||
*/
|
||||
@NotNull
|
||||
public abstract Object newKeybindComponent(@NotNull String keybind);
|
||||
|
||||
/**
|
||||
* Converts given chat modifier to minecraft style and applies it to the component.
|
||||
*
|
||||
* @param nmsComponent
|
||||
* Component to apply style to
|
||||
* @param style
|
||||
* Style to convert and apply
|
||||
*/
|
||||
public abstract void applyStyle(@NotNull Object nmsComponent, @NotNull ChatModifier style);
|
||||
|
||||
/**
|
||||
* Appends child to the given parent component.
|
||||
*
|
||||
* @param parent
|
||||
* Parent to append the child to
|
||||
* @param child
|
||||
* Child component to append
|
||||
*/
|
||||
public abstract void addSibling(@NotNull Object parent, @NotNull Object child);
|
||||
|
||||
/**
|
||||
* Attempts to load component converter.
|
||||
*
|
||||
* @param serverVersion
|
||||
* Server version
|
||||
*/
|
||||
public static void tryLoad(@NotNull ProtocolVersion serverVersion) {
|
||||
public static void tryLoad() {
|
||||
try {
|
||||
if (ReflectionUtils.classExists("org.bukkit.craftbukkit.CraftServer") && paperNativeVersions.contains(serverVersion)) {
|
||||
INSTANCE = (ComponentConverter) Class.forName("me.neznamy.tab.platforms.paper.PaperComponentConverter").getConstructor().newInstance();
|
||||
if (BukkitReflection.getMinorVersion() >= 19) {
|
||||
// 1.19+
|
||||
INSTANCE = new ModernComponentConverter();
|
||||
} else if (BukkitReflection.getMinorVersion() >= 16) {
|
||||
// 1.16 - 1.18.2
|
||||
INSTANCE = new ModerateComponentConverter();
|
||||
} else {
|
||||
INSTANCE = new ReflectionComponentConverter();
|
||||
// 1.7 - 1.15.2
|
||||
INSTANCE = new LegacyComponentConverter();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
if (BukkitUtils.PRINT_EXCEPTIONS) {
|
||||
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
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
@@ -0,0 +1,94 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
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.19 and up.
|
||||
*/
|
||||
public class ModernComponentConverter extends ComponentConverter {
|
||||
|
||||
private final Class<?> IChatBaseComponent = BukkitReflection.getClass("network.chat.Component", "network.chat.IChatBaseComponent");
|
||||
private final Class<?> IChatMutableComponent = BukkitReflection.getClass("network.chat.MutableComponent", "network.chat.IChatMutableComponent");
|
||||
private final Class<?> TextColor = BukkitReflection.getClass("network.chat.TextColor", "network.chat.ChatHexColor");
|
||||
private final Class<?> ResourceLocation = BukkitReflection.getClass("resources.ResourceLocation", "resources.MinecraftKey");
|
||||
|
||||
private final Method newTextComponent = ReflectionUtils.getMethod(IChatBaseComponent, new String[] {"b", "literal"}, String.class);
|
||||
private final Method newTranslatableComponent = ReflectionUtils.getMethod(IChatBaseComponent, new String[] {"c", "translatable"}, String.class);
|
||||
private final Method newKeybindComponent = ReflectionUtils.getMethod(IChatBaseComponent, new String[] {"d", "keybind"}, String.class);
|
||||
|
||||
private final Class<?> ChatModifierClass = BukkitReflection.getClass("network.chat.Style", "network.chat.ChatModifier");
|
||||
private final Constructor<?> newChatModifier;
|
||||
private final Method ChatBaseComponent_addSibling = ReflectionUtils.getOnlyMethod(IChatMutableComponent, IChatMutableComponent, IChatBaseComponent);
|
||||
private final Field Component_modifier = ReflectionUtils.getOnlyField(IChatMutableComponent, 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 ModernComponentConverter() throws ReflectiveOperationException {
|
||||
Class<?> chatClickable = BukkitReflection.getClass("network.chat.ClickEvent", "network.chat.ChatClickable");
|
||||
Class<?> chatHoverable = BukkitReflection.getClass("network.chat.HoverEvent", "network.chat.ChatHoverable");
|
||||
if (BukkitReflection.is1_21_4Plus()) {
|
||||
// 1.21.4+
|
||||
newChatModifier = ReflectionUtils.setAccessible(ChatModifierClass.getDeclaredConstructor(TextColor, Integer.class, Boolean.class, Boolean.class,
|
||||
Boolean.class, Boolean.class, Boolean.class, chatClickable, chatHoverable, String.class, ResourceLocation));
|
||||
} else {
|
||||
// 1.21.3-
|
||||
newChatModifier = ReflectionUtils.setAccessible(ChatModifierClass.getDeclaredConstructor(TextColor, Boolean.class, Boolean.class, Boolean.class,
|
||||
Boolean.class, Boolean.class, chatClickable, chatHoverable, String.class, ResourceLocation));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
@SneakyThrows
|
||||
public Object newTextComponent(@NotNull String text) {
|
||||
return newTextComponent.invoke(null, text);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
@SneakyThrows
|
||||
public Object newTranslatableComponent(@NotNull String key) {
|
||||
return newTranslatableComponent.invoke(null, key);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
@SneakyThrows
|
||||
public Object newKeybindComponent(@NotNull String keybind) {
|
||||
return newKeybindComponent.invoke(null, keybind);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void applyStyle(@NotNull Object nmsComponent, @NotNull ChatModifier style) {
|
||||
if (BukkitReflection.is1_21_4Plus()) {
|
||||
Component_modifier.set(nmsComponent, newChatModifier.newInstance(
|
||||
style.getColor() == null ? null : ChatHexColor_fromRGB.invoke(null, style.getColor().getRgb()),
|
||||
style.getShadowColor(),
|
||||
style.getBold(),
|
||||
style.getItalic(),
|
||||
style.getUnderlined(),
|
||||
style.getStrikethrough(),
|
||||
style.getObfuscated(),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
style.getFont() == null ? null : ResourceLocation_tryParse.invoke(null, style.getFont())
|
||||
));
|
||||
} else {
|
||||
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);
|
||||
}
|
||||
}
|
||||
-156
@@ -1,156 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.nms.converter;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
|
||||
import me.neznamy.tab.shared.chat.ChatModifier;
|
||||
import me.neznamy.tab.shared.chat.SimpleComponent;
|
||||
import me.neznamy.tab.shared.chat.StructuredComponent;
|
||||
import me.neznamy.tab.shared.chat.TabComponent;
|
||||
import me.neznamy.tab.shared.util.FunctionWithException;
|
||||
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;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
/**
|
||||
* Class for converting TAB component into NMS components (1.7+).
|
||||
*/
|
||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
public class ReflectionComponentConverter extends ComponentConverter {
|
||||
|
||||
private final FunctionWithException<String, Object> newTextComponent;
|
||||
private final BiFunction<ChatModifier, Boolean, Object> convertModifier;
|
||||
|
||||
private final Class<?> ChatModifier = BukkitReflection.getClass("network.chat.Style", "network.chat.ChatModifier", "ChatModifier");
|
||||
private final Class<Enum> EnumChatFormat = (Class<Enum>) BukkitReflection.getClass("ChatFormatting", "EnumChatFormat");
|
||||
private final Constructor<?> newChatModifier;
|
||||
private final Method ChatBaseComponent_addSibling;
|
||||
private final Field Component_modifier;
|
||||
private final List<Field> magicCodes = ReflectionUtils.getFields(ChatModifier, Boolean.class);
|
||||
|
||||
// 1.15-
|
||||
private Method ChatModifier_setColor;
|
||||
|
||||
// 1.16+
|
||||
private Method ChatHexColor_fromRGB;
|
||||
private Method ResourceLocation_tryParse;
|
||||
|
||||
/**
|
||||
* Constructs new instance and loads all NMS classes, constructors and methods.
|
||||
*
|
||||
* @throws ReflectiveOperationException
|
||||
* If something failed
|
||||
*/
|
||||
public ReflectionComponentConverter() throws ReflectiveOperationException {
|
||||
Class<?> IChatBaseComponent = BukkitReflection.getClass("network.chat.Component", "network.chat.IChatBaseComponent", "IChatBaseComponent");
|
||||
if (BukkitReflection.getMinorVersion() >= 19) {
|
||||
Method IChatBaseComponent_b = ReflectionUtils.getMethod(IChatBaseComponent, new String[] {"b", "literal"}, String.class);
|
||||
newTextComponent = text -> IChatBaseComponent_b.invoke(null, text);
|
||||
Class<?> IChatMutableComponent = BukkitReflection.getClass("network.chat.MutableComponent", "network.chat.IChatMutableComponent", "IChatMutableComponent");
|
||||
Component_modifier = ReflectionUtils.getOnlyField(IChatMutableComponent, ChatModifier);
|
||||
ChatBaseComponent_addSibling = ReflectionUtils.getOnlyMethod(IChatMutableComponent, IChatMutableComponent, IChatBaseComponent);
|
||||
} else {
|
||||
Class<?> ChatComponentText = BukkitReflection.getClass("network.chat.TextComponent", "network.chat.ChatComponentText", "ChatComponentText");
|
||||
Constructor<?> newChatComponentText = ChatComponentText.getConstructor(String.class);
|
||||
newTextComponent = newChatComponentText::newInstance;
|
||||
Class<?> ChatBaseComponent = BukkitReflection.getClass("network.chat.BaseComponent", "network.chat.ChatBaseComponent", "ChatBaseComponent");
|
||||
Component_modifier = ReflectionUtils.getOnlyField(ChatBaseComponent, ChatModifier);
|
||||
ChatBaseComponent_addSibling = ReflectionUtils.getOnlyMethod(ChatComponentText, IChatBaseComponent, IChatBaseComponent);
|
||||
}
|
||||
if (BukkitReflection.getMinorVersion() >= 16) {
|
||||
Class<?> chatHexColor = BukkitReflection.getClass("network.chat.TextColor", "network.chat.ChatHexColor", "ChatHexColor");
|
||||
Class<?> ResourceLocation = BukkitReflection.getClass("resources.ResourceLocation", "resources.MinecraftKey", "MinecraftKey");
|
||||
Class<?> chatClickable = BukkitReflection.getClass("network.chat.ClickEvent", "network.chat.ChatClickable", "ChatClickable");
|
||||
Class<?> chatHoverable = BukkitReflection.getClass("network.chat.HoverEvent", "network.chat.ChatHoverable", "ChatHoverable");
|
||||
ResourceLocation_tryParse = ReflectionUtils.getMethod(ResourceLocation, new String[]{"tryParse", "m_135820_", "a"}, String.class);
|
||||
ChatHexColor_fromRGB = ReflectionUtils.getMethods(chatHexColor, chatHexColor, int.class).get(0); // There should only be 1, but some mods add more
|
||||
if (BukkitReflection.is1_21_4Plus()) {
|
||||
// 1.21.4+
|
||||
newChatModifier = ReflectionUtils.setAccessible(ChatModifier.getDeclaredConstructor(chatHexColor, Integer.class, Boolean.class, Boolean.class,
|
||||
Boolean.class, Boolean.class, Boolean.class, chatClickable, chatHoverable, String.class, ResourceLocation));
|
||||
} else {
|
||||
// 1.21.3-
|
||||
newChatModifier = ReflectionUtils.setAccessible(ChatModifier.getDeclaredConstructor(chatHexColor, Boolean.class, Boolean.class, Boolean.class,
|
||||
Boolean.class, Boolean.class, chatClickable, chatHoverable, String.class, ResourceLocation));
|
||||
}
|
||||
convertModifier = this::createModifierModern;
|
||||
} else {
|
||||
newChatModifier = ChatModifier.getConstructor();
|
||||
ChatModifier_setColor = ReflectionUtils.getOnlyMethod(ChatModifier, ChatModifier, EnumChatFormat);
|
||||
convertModifier = (modifier, protocolVersion) -> createModifierLegacy(modifier);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
@NotNull
|
||||
public Object convert(@NotNull TabComponent component, boolean modern) {
|
||||
if (component instanceof SimpleComponent) return newTextComponent.apply(((SimpleComponent) component).getText());
|
||||
|
||||
StructuredComponent component1 = (StructuredComponent) component;
|
||||
Object nmsComponent = newTextComponent.apply(component1.getText());
|
||||
Component_modifier.set(nmsComponent, convertModifier.apply(component1.getModifier(), modern));
|
||||
for (StructuredComponent extra : component1.getExtra()) {
|
||||
ChatBaseComponent_addSibling.invoke(nmsComponent, convert(extra, modern));
|
||||
}
|
||||
return nmsComponent;
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private Object createModifierModern(@NotNull ChatModifier modifier, boolean modern) {
|
||||
Object color = null;
|
||||
if (modifier.getColor() != null) {
|
||||
if (modern) {
|
||||
color = ChatHexColor_fromRGB.invoke(null, modifier.getColor().getRgb());
|
||||
} else {
|
||||
color = ChatHexColor_fromRGB.invoke(null, modifier.getColor().getLegacyColor().getRgb());
|
||||
}
|
||||
}
|
||||
if (BukkitReflection.is1_21_4Plus()) {
|
||||
return newChatModifier.newInstance(
|
||||
color,
|
||||
0,
|
||||
modifier.isBold(),
|
||||
modifier.isItalic(),
|
||||
modifier.isUnderlined(),
|
||||
modifier.isStrikethrough(),
|
||||
modifier.isObfuscated(),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
modifier.getFont() == null ? null : ResourceLocation_tryParse.invoke(null, modifier.getFont())
|
||||
);
|
||||
} else {
|
||||
return newChatModifier.newInstance(
|
||||
color,
|
||||
modifier.isBold(),
|
||||
modifier.isItalic(),
|
||||
modifier.isUnderlined(),
|
||||
modifier.isStrikethrough(),
|
||||
modifier.isObfuscated(),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
modifier.getFont() == null ? null : ResourceLocation_tryParse.invoke(null, modifier.getFont())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private Object createModifierLegacy(@NotNull ChatModifier modifier) {
|
||||
Object nmsModifier = newChatModifier.newInstance();
|
||||
if (modifier.getColor() != null) {
|
||||
ChatModifier_setColor.invoke(nmsModifier, Enum.valueOf(EnumChatFormat, modifier.getColor().getLegacyColor().name()));
|
||||
}
|
||||
if (modifier.isBold()) magicCodes.get(0).set(nmsModifier, true);
|
||||
if (modifier.isItalic()) magicCodes.get(1).set(nmsModifier, true);
|
||||
if (modifier.isStrikethrough()) magicCodes.get(2).set(nmsModifier, true);
|
||||
if (modifier.isUnderlined()) magicCodes.get(3).set(nmsModifier, true);
|
||||
if (modifier.isObfuscated()) magicCodes.get(4).set(nmsModifier, true);
|
||||
return nmsModifier;
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package me.neznamy.tab.platforms.bukkit.platform;
|
||||
import lombok.Getter;
|
||||
import lombok.SneakyThrows;
|
||||
import me.clip.placeholderapi.PlaceholderAPI;
|
||||
import me.neznamy.chat.component.*;
|
||||
import me.neznamy.tab.platforms.bukkit.*;
|
||||
import me.neznamy.tab.platforms.bukkit.bossbar.BukkitBossBar;
|
||||
import me.neznamy.tab.platforms.bukkit.bossbar.ViaBossBar;
|
||||
@@ -20,10 +21,6 @@ import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.backend.BackendPlatform;
|
||||
import me.neznamy.tab.shared.chat.EnumChatFormat;
|
||||
import me.neznamy.tab.shared.chat.SimpleComponent;
|
||||
import me.neznamy.tab.shared.chat.StructuredComponent;
|
||||
import me.neznamy.tab.shared.chat.TabComponent;
|
||||
import me.neznamy.tab.shared.features.PerWorldPlayerListConfiguration;
|
||||
import me.neznamy.tab.shared.features.PlaceholderManagerImpl;
|
||||
import me.neznamy.tab.shared.features.injection.PipelineInjector;
|
||||
@@ -101,15 +98,15 @@ public class BukkitPlatform implements BackendPlatform {
|
||||
new BukkitPremiumVanishHook().register();
|
||||
}
|
||||
PingRetriever.tryLoad();
|
||||
ComponentConverter.tryLoad(serverVersion);
|
||||
ScoreboardLoader.findInstance(serverVersion);
|
||||
TabListBase.findInstance(serverVersion);
|
||||
ComponentConverter.tryLoad();
|
||||
ScoreboardLoader.findInstance();
|
||||
TabListBase.findInstance();
|
||||
if (BukkitReflection.getMinorVersion() >= 8) {
|
||||
HeaderFooter.findInstance();
|
||||
BukkitPipelineInjector.tryLoad(serverVersion);
|
||||
BukkitPipelineInjector.tryLoad();
|
||||
}
|
||||
BukkitUtils.sendCompatibilityMessage();
|
||||
Bukkit.getConsoleSender().sendMessage("[TAB] " + EnumChatFormat.GRAY + "Loaded NMS hook in " + (System.currentTimeMillis()-time) + "ms");
|
||||
Bukkit.getConsoleSender().sendMessage("[TAB] §7Loaded NMS hook in " + (System.currentTimeMillis()-time) + "ms");
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -208,12 +205,12 @@ public class BukkitPlatform implements BackendPlatform {
|
||||
|
||||
@Override
|
||||
public void logInfo(@NotNull TabComponent message) {
|
||||
Bukkit.getConsoleSender().sendMessage("[TAB] " + toBukkitFormat(message, true));
|
||||
Bukkit.getConsoleSender().sendMessage("[TAB] " + toBukkitFormat(message));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logWarn(@NotNull TabComponent message) {
|
||||
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.RED + "[TAB] [WARN] " + toBukkitFormat(message, true));
|
||||
Bukkit.getConsoleSender().sendMessage("§c[TAB] [WARN] " + toBukkitFormat(message));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -229,13 +226,13 @@ public class BukkitPlatform implements BackendPlatform {
|
||||
|
||||
@Override
|
||||
public void registerCommand() {
|
||||
PluginCommand command = Bukkit.getPluginCommand(TabConstants.COMMAND_BACKEND);
|
||||
PluginCommand command = Bukkit.getPluginCommand(getCommand());
|
||||
if (command != null) {
|
||||
BukkitTabCommand cmd = new BukkitTabCommand();
|
||||
command.setExecutor(cmd);
|
||||
command.setTabCompleter(cmd);
|
||||
} else {
|
||||
logWarn(TabComponent.fromColoredText("Failed to register command, is it defined in plugin.yml?"));
|
||||
logWarn(new SimpleTextComponent("Failed to register command, is it defined in plugin.yml?"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -256,9 +253,9 @@ public class BukkitPlatform implements BackendPlatform {
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Object convertComponent(@NotNull TabComponent component, boolean modern) {
|
||||
public Object convertComponent(@NotNull TabComponent component) {
|
||||
if (ComponentConverter.INSTANCE != null) {
|
||||
return ComponentConverter.INSTANCE.convert(component, modern);
|
||||
return ComponentConverter.INSTANCE.convert(component);
|
||||
} else {
|
||||
return component;
|
||||
}
|
||||
@@ -304,6 +301,16 @@ public class BukkitPlatform implements BackendPlatform {
|
||||
return serverVersion.getNetworkId() >= ProtocolVersion.V1_21_2.getNetworkId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsScoreboards() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSafeFromPacketEventsBug() {
|
||||
return serverVersion.getMinorVersion() >= 13;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public GroupManager detectPermissionPlugin() {
|
||||
@@ -348,47 +355,39 @@ public class BukkitPlatform implements BackendPlatform {
|
||||
Bukkit.getScheduler().runTask(plugin, task);
|
||||
}
|
||||
|
||||
// Disabling this check, because vanish plugins might process hiding with a delay on join, resulting
|
||||
// in wrong result when called by TAB, causing vanished players to appear in layout for players who
|
||||
// just joined, in the worst case scenario not hiding shortly after if vanish plugin takes too long.
|
||||
/*
|
||||
@Override
|
||||
public boolean canSee(@NotNull TabPlayer viewer, @NotNull TabPlayer target) {
|
||||
if (BackendPlatform.super.canSee(viewer, target)) return true;
|
||||
return ((BukkitTabPlayer)viewer).getPlayer().canSee(((BukkitTabPlayer)target).getPlayer());
|
||||
}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Converts component to legacy string using bukkit RGB format if supported by both server and client.
|
||||
* Converts component to string using bukkit RGB format if supported by the server.
|
||||
* If not, closest legacy color is used instead.
|
||||
*
|
||||
* @param component
|
||||
* Component to convert
|
||||
* @param rgbClient
|
||||
* Whether client accepts RGB colors or not.
|
||||
* @return Converted string using bukkit color format
|
||||
*/
|
||||
@NotNull
|
||||
public String toBukkitFormat(@NotNull TabComponent component, boolean rgbClient) {
|
||||
if (component instanceof SimpleComponent) return component.toLegacyText();
|
||||
StructuredComponent iComponent = (StructuredComponent) component;
|
||||
public String toBukkitFormat(@NotNull TabComponent component) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (iComponent.getModifier().getColor() != null) {
|
||||
if (serverVersion.supportsRGB() && rgbClient) {
|
||||
String hexCode = iComponent.getModifier().getColor().getHexCode();
|
||||
char c = EnumChatFormat.COLOR_CHAR;
|
||||
sb.append(c).append("x").append(c).append(hexCode.charAt(0)).append(c).append(hexCode.charAt(1))
|
||||
.append(c).append(hexCode.charAt(2)).append(c).append(hexCode.charAt(3))
|
||||
.append(c).append(hexCode.charAt(4)).append(c).append(hexCode.charAt(5));
|
||||
if (component.getModifier().getColor() != null) {
|
||||
if (serverVersion.supportsRGB()) {
|
||||
String hexCode = component.getModifier().getColor().getHexCode();
|
||||
sb.append('§').append("x").append('§').append(hexCode.charAt(0)).append('§').append(hexCode.charAt(1))
|
||||
.append('§').append(hexCode.charAt(2)).append('§').append(hexCode.charAt(3))
|
||||
.append('§').append(hexCode.charAt(4)).append('§').append(hexCode.charAt(5));
|
||||
} else {
|
||||
sb.append(iComponent.getModifier().getColor().getLegacyColor());
|
||||
sb.append('§').append(component.getModifier().getColor().getLegacyColor().getCharacter());
|
||||
}
|
||||
}
|
||||
sb.append(iComponent.getModifier().getMagicCodes());
|
||||
sb.append(iComponent.getText());
|
||||
for (StructuredComponent extra : iComponent.getExtra()) {
|
||||
sb.append(toBukkitFormat(extra, rgbClient));
|
||||
sb.append(component.getModifier().getMagicCodes());
|
||||
if (component instanceof TextComponent) {
|
||||
sb.append(((TextComponent) component).getText());
|
||||
} else if (component instanceof TranslatableComponent) {
|
||||
sb.append(((TranslatableComponent) component).getKey());
|
||||
} else if (component instanceof KeybindComponent) {
|
||||
sb.append(((KeybindComponent) component).getKeybind());
|
||||
} else {
|
||||
throw new IllegalStateException("Unexpected component type: " + component.getClass().getName());
|
||||
}
|
||||
for (TabComponent extra : component.getExtra()) {
|
||||
sb.append(toBukkitFormat(extra));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import me.clip.placeholderapi.PlaceholderAPI;
|
||||
import me.neznamy.tab.platforms.bukkit.features.PerWorldPlayerList;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.chat.TabComponent;
|
||||
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;
|
||||
@@ -38,7 +38,7 @@ public class FoliaPlatform extends BukkitPlatform {
|
||||
super.loadPlayers();
|
||||
|
||||
// Values are never updated in the API, warn users
|
||||
logWarn(TabComponent.fromColoredText("Folia never updates MSPT and TPS values in the API, making " +
|
||||
logWarn(new SimpleTextComponent("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
|
||||
|
||||
+4
-4
@@ -7,7 +7,7 @@ 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.tab.shared.chat.TabComponent;
|
||||
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;
|
||||
@@ -160,7 +160,7 @@ public class BukkitScoreboard extends SafeScoreboard<BukkitTabPlayer> {
|
||||
if (serverMinorVersion >= 9)
|
||||
t.setOption(org.bukkit.scoreboard.Team.Option.COLLISION_RULE, OptionStatus.values()[team.getCollision().ordinal()]);
|
||||
if (serverMinorVersion >= TEAM_COLOR_VERSION)
|
||||
t.setColor(ChatColor.valueOf(team.getColor().name()));
|
||||
t.setColor(ChatColor.valueOf(team.getColor().getLegacyColor().name()));
|
||||
if (serverMinorVersion >= 7 && player.getPlatform().getServerVersion().getNetworkId() >= ProtocolVersion.V1_7_8.getNetworkId()) {
|
||||
for (String player : team.getPlayers()) {
|
||||
t.addEntry(player);
|
||||
@@ -192,7 +192,7 @@ public class BukkitScoreboard extends SafeScoreboard<BukkitTabPlayer> {
|
||||
if (serverMinorVersion >= 9)
|
||||
t.setOption(org.bukkit.scoreboard.Team.Option.COLLISION_RULE, OptionStatus.values()[team.getCollision().ordinal()]);
|
||||
if (serverMinorVersion >= TEAM_COLOR_VERSION)
|
||||
t.setColor(ChatColor.valueOf(team.getColor().name()));
|
||||
t.setColor(ChatColor.valueOf(team.getColor().getLegacyColor().name()));
|
||||
t.setAllowFriendlyFire((team.getOptions() & 0x01) != 0);
|
||||
t.setCanSeeFriendlyInvisibles((team.getOptions() & 0x02) != 0);
|
||||
}
|
||||
@@ -274,7 +274,7 @@ public class BukkitScoreboard extends SafeScoreboard<BukkitTabPlayer> {
|
||||
*/
|
||||
@NotNull
|
||||
private String transform(@NonNull TabComponent text, int maxLengthModern, int maxLengthLegacy) {
|
||||
String transformed = player.getPlatform().toBukkitFormat(text, player.getVersion().supportsRGB());
|
||||
String transformed = player.getPlatform().toBukkitFormat(text);
|
||||
if (player.getPlatform().getServerVersion().supportsRGB() && maxLengthModern < TITLE_LIMIT_MODERN) { // Scoreboard title is not stripping colors
|
||||
while (ChatColor.stripColor(transformed).length() > maxLengthModern)
|
||||
transformed = transformed.substring(0, transformed.length()-1);
|
||||
|
||||
+7
-7
@@ -4,7 +4,7 @@ import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.shared.chat.TabComponent;
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.ComponentLike;
|
||||
@@ -47,29 +47,29 @@ public class PaperScoreboard extends BukkitScoreboard {
|
||||
public org.bukkit.scoreboard.Objective newObjective(@NonNull String objectiveName, @NonNull String criteria,
|
||||
@NonNull TabComponent title, @NonNull HealthDisplay display) {
|
||||
return scoreboard.registerNewObjective(objectiveName, criteria,
|
||||
title.toAdventure(player.getVersion()), RenderType.values()[display.ordinal()]);
|
||||
title.toAdventure(), RenderType.values()[display.ordinal()]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDisplayName(@NonNull org.bukkit.scoreboard.Objective objective, @NonNull TabComponent displayName) {
|
||||
objective.displayName(displayName.toAdventure(player.getVersion()));
|
||||
objective.displayName(displayName.toAdventure());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPrefix(@NonNull org.bukkit.scoreboard.Team team, @NonNull TabComponent prefix) {
|
||||
team.prefix(prefix.toAdventure(player.getVersion()));
|
||||
team.prefix(prefix.toAdventure());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSuffix(@NonNull org.bukkit.scoreboard.Team team, @NonNull TabComponent suffix) {
|
||||
team.suffix(suffix.toAdventure(player.getVersion()));
|
||||
team.suffix(suffix.toAdventure());
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void setScoreDisplayName(@NotNull org.bukkit.scoreboard.Score s, @Nullable TabComponent displayName) {
|
||||
if (numberFormatAPI) {
|
||||
Component component = displayName == null ? null : displayName.toAdventure(player.getVersion());
|
||||
Component component = displayName == null ? null : displayName.toAdventure();
|
||||
Method m = s.getClass().getMethod("customName", Component.class);
|
||||
m.setAccessible(true); // Why is this needed? The method is public!
|
||||
m.invoke(s, component);
|
||||
@@ -103,6 +103,6 @@ public class PaperScoreboard extends BukkitScoreboard {
|
||||
private Object numberFormat(@Nullable TabComponent numberFormat) {
|
||||
if (numberFormat == null) return null;
|
||||
return Class.forName("io.papermc.paper.scoreboard.numbers.NumberFormat").getMethod("fixed", ComponentLike.class)
|
||||
.invoke(null, numberFormat.toAdventure(player.getVersion()));
|
||||
.invoke(null, numberFormat.toAdventure());
|
||||
}
|
||||
}
|
||||
+3
-25
@@ -3,20 +3,15 @@ package me.neznamy.tab.platforms.bukkit.scoreboard;
|
||||
import com.google.common.collect.Lists;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
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.scoreboard.packet.PacketScoreboard;
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard;
|
||||
import me.neznamy.tab.shared.platform.impl.DummyScoreboard;
|
||||
import me.neznamy.tab.shared.util.FunctionWithException;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import me.neznamy.tab.shared.util.function.FunctionWithException;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -24,16 +19,6 @@ import java.util.List;
|
||||
*/
|
||||
public class ScoreboardLoader {
|
||||
|
||||
/** Versions supported by paper module that uses direct mojang-mapped NMS for latest MC version */
|
||||
private static final EnumSet<ProtocolVersion> paperNativeVersions = EnumSet.of(
|
||||
ProtocolVersion.V1_20_5,
|
||||
ProtocolVersion.V1_20_6,
|
||||
ProtocolVersion.V1_21,
|
||||
ProtocolVersion.V1_21_1,
|
||||
ProtocolVersion.V1_21_2,
|
||||
ProtocolVersion.V1_21_3
|
||||
);
|
||||
|
||||
/** Instance function */
|
||||
@Getter
|
||||
@Setter
|
||||
@@ -42,16 +27,9 @@ public class ScoreboardLoader {
|
||||
|
||||
/**
|
||||
* Finds the best available instance for current server software.
|
||||
*
|
||||
* @param serverVersion
|
||||
* Server version
|
||||
*/
|
||||
@SneakyThrows
|
||||
public static void findInstance(@NotNull ProtocolVersion serverVersion) {
|
||||
if (ReflectionUtils.classExists("org.bukkit.craftbukkit.CraftServer") && paperNativeVersions.contains(serverVersion)) {
|
||||
Constructor<?> constructor = Class.forName("me.neznamy.tab.platforms.paper.PaperPacketScoreboard").getConstructor(BukkitTabPlayer.class);
|
||||
instance = player -> (Scoreboard) constructor.newInstance(player);
|
||||
} else if (PacketScoreboard.isAvailable()) {
|
||||
public static void findInstance() {
|
||||
if (PacketScoreboard.isAvailable()) {
|
||||
instance = PacketScoreboard::new;
|
||||
} else if (PaperScoreboard.isAvailable()) {
|
||||
instance = PaperScoreboard::new;
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ 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.FunctionWithException;
|
||||
import me.neznamy.tab.shared.util.function.FunctionWithException;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
|
||||
+16
-7
@@ -9,7 +9,7 @@ import me.neznamy.tab.platforms.bukkit.nms.converter.ComponentConverter;
|
||||
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.chat.TabComponent;
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import me.neznamy.tab.shared.platform.decorators.SafeScoreboard;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -141,7 +141,7 @@ public class PacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
|
||||
@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(player.getVersion()),
|
||||
score.getDisplayName() == null ? null : score.getDisplayName().convert(),
|
||||
score.getNumberFormat() == null ? null : toFixedFormat(score.getNumberFormat())));
|
||||
}
|
||||
|
||||
@@ -202,7 +202,7 @@ public class PacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
|
||||
emptyScoreboard,
|
||||
objective.getName(),
|
||||
null, // Criteria
|
||||
objective.getTitle().convert(player.getVersion()),
|
||||
objective.getTitle().convert(),
|
||||
healthDisplays[objective.getHealthDisplay().ordinal()],
|
||||
false, // Auto update
|
||||
objective.getNumberFormat() == null ? null : toFixedFormat(objective.getNumberFormat())
|
||||
@@ -214,14 +214,17 @@ public class PacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
|
||||
emptyScoreboard,
|
||||
objective.getName(),
|
||||
null, // Criteria
|
||||
objective.getTitle().convert(player.getVersion()),
|
||||
objective.getTitle().convert(),
|
||||
healthDisplays[objective.getHealthDisplay().ordinal()]
|
||||
);
|
||||
}
|
||||
// 1.5 - 1.12.2
|
||||
Object nmsObjective = newScoreboardObjective.newInstance(emptyScoreboard, objective.getName(), IScoreboardCriteria_dummy);
|
||||
String cutTitle = player.getVersion().getMinorVersion() >= 13 ? objective.getTitle().toLegacyText() : cutTo(objective.getTitle().toLegacyText(), Limitations.SCOREBOARD_TITLE_PRE_1_13);
|
||||
ScoreboardObjective_setDisplayName.invoke(nmsObjective, cutTitle);
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -229,6 +232,12 @@ public class PacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
|
||||
@SneakyThrows
|
||||
private static Object toFixedFormat(@NonNull TabComponent component) {
|
||||
if (newFixedFormat == null) return null;
|
||||
return component.toFixedFormat(nmsComponent -> newFixedFormat.newInstance(nmsComponent));
|
||||
return component.toFixedFormat(PacketScoreboard::convertFixedFormat);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@SneakyThrows
|
||||
private static Object convertFixedFormat(@NotNull Object nmsComponent) {
|
||||
return newFixedFormat.newInstance(nmsComponent);
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -4,8 +4,8 @@ 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.BiFunctionWithException;
|
||||
import me.neznamy.tab.shared.util.QuintFunction;
|
||||
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;
|
||||
|
||||
|
||||
+6
-5
@@ -6,12 +6,13 @@ 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.decorators.SafeScoreboard;
|
||||
import me.neznamy.tab.shared.platform.decorators.SafeScoreboard.Team;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard.TeamAction;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import me.neznamy.tab.shared.util.BiConsumerWithException;
|
||||
import me.neznamy.tab.shared.util.function.BiConsumerWithException;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@@ -224,13 +225,13 @@ public class TeamPacketData {
|
||||
ScoreboardTeam_setAllowFriendlyFire.invoke(nmsTeam, (team.getOptions() & 0x1) > 0);
|
||||
ScoreboardTeam_setCanSeeFriendlyInvisibles.invoke(nmsTeam, (team.getOptions() & 0x2) > 0);
|
||||
if (MODERN_TEAM_DATA_VERSION) {
|
||||
ScoreboardTeam_setPrefix.invoke(nmsTeam, (Object) team.getPrefix().convert(clientVersion));
|
||||
ScoreboardTeam_setSuffix.invoke(nmsTeam, (Object) team.getSuffix().convert(clientVersion));
|
||||
ScoreboardTeam_setColor.invoke(nmsTeam, chatFormats[team.getColor().ordinal()]);
|
||||
ScoreboardTeam_setPrefix.invoke(nmsTeam, (Object) team.getPrefix().convert());
|
||||
ScoreboardTeam_setSuffix.invoke(nmsTeam, (Object) team.getSuffix().convert());
|
||||
ScoreboardTeam_setColor.invoke(nmsTeam, chatFormats[team.getColor().getLegacyColor().ordinal()]);
|
||||
} else {
|
||||
String prefix = team.getPrefix().toLegacyText();
|
||||
String suffix = team.getSuffix().toLegacyText();
|
||||
if (clientVersion.getMinorVersion() < 13) {
|
||||
if (clientVersion.getMinorVersion() < 13 || TAB.getInstance().getConfiguration().getConfig().isPacketEventsCompensation()) {
|
||||
prefix = SafeScoreboard.cutTo(prefix, Limitations.TEAM_PREFIX_SUFFIX_PRE_1_13);
|
||||
suffix = SafeScoreboard.cutTo(suffix, Limitations.TEAM_PREFIX_SUFFIX_PRE_1_13);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ package me.neznamy.tab.platforms.bukkit.tablist;
|
||||
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.shared.chat.TabComponent;
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -70,6 +70,6 @@ public class BukkitTabList extends TabListBase<String> {
|
||||
|
||||
@Override
|
||||
public String toComponent(@NonNull TabComponent component) {
|
||||
return player.getPlatform().toBukkitFormat(component, true);
|
||||
return player.getPlatform().toBukkitFormat(component);
|
||||
}
|
||||
}
|
||||
@@ -6,9 +6,9 @@ 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.chat.TabComponent;
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import me.neznamy.tab.shared.util.TriFunctionWithException;
|
||||
import me.neznamy.tab.shared.util.function.TriFunctionWithException;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
|
||||
@@ -25,14 +25,6 @@ public class SkinData {
|
||||
private final Method getProfile = ReflectionUtils.getMethods(BukkitReflection.getClass(
|
||||
"world.entity.player.Player", "world.entity.player.EntityHuman", "EntityHuman"), GameProfile.class).get(0);
|
||||
|
||||
/**
|
||||
* Constructs new instance and loads NMS fields. If it fails, throws an Exception.
|
||||
*
|
||||
* @throws ReflectiveOperationException
|
||||
* If something goes wrong
|
||||
*/
|
||||
public SkinData() throws ReflectiveOperationException {}
|
||||
|
||||
/**
|
||||
* Returns player's skin. If server is in offline mode, returns {@code null}.
|
||||
*
|
||||
|
||||
@@ -3,20 +3,17 @@ package me.neznamy.tab.platforms.bukkit.tablist;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.Setter;
|
||||
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.header.HeaderFooter;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.shared.chat.TabComponent;
|
||||
import me.neznamy.tab.shared.platform.decorators.TrackedTabList;
|
||||
import me.neznamy.tab.shared.util.FunctionWithException;
|
||||
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.Constructor;
|
||||
import java.util.EnumSet;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
@@ -27,12 +24,6 @@ import java.util.UUID;
|
||||
*/
|
||||
public abstract class TabListBase<C> extends TrackedTabList<BukkitTabPlayer, C> {
|
||||
|
||||
/** Versions supported by paper module that uses direct mojang-mapped NMS for latest MC version */
|
||||
private static final EnumSet<ProtocolVersion> paperNativeVersions = EnumSet.of(
|
||||
ProtocolVersion.V1_21_2,
|
||||
ProtocolVersion.V1_21_3
|
||||
);
|
||||
|
||||
/** Instance function */
|
||||
@Getter
|
||||
@Setter
|
||||
@@ -53,16 +44,10 @@ public abstract class TabListBase<C> extends TrackedTabList<BukkitTabPlayer, C>
|
||||
|
||||
/**
|
||||
* Finds the best available instance for current server software.
|
||||
*
|
||||
* @param serverVersion
|
||||
* Server version
|
||||
*/
|
||||
public static void findInstance(@NotNull ProtocolVersion serverVersion) {
|
||||
public static void findInstance() {
|
||||
try {
|
||||
if (ReflectionUtils.classExists("org.bukkit.craftbukkit.CraftServer") && paperNativeVersions.contains(serverVersion)) {
|
||||
Constructor<?> constructor = Class.forName("me.neznamy.tab.platforms.paper.PaperPacketTabList").getConstructor(BukkitTabPlayer.class);
|
||||
instance = player -> (TabListBase<?>) constructor.newInstance(player);
|
||||
} else if (ReflectionUtils.classExists("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket")) {
|
||||
if (ReflectionUtils.classExists("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket")) {
|
||||
PacketTabList1193.loadNew();
|
||||
instance = PacketTabList1193::new;
|
||||
} else if (BukkitReflection.getMinorVersion() >= 8) {
|
||||
|
||||
@@ -3,7 +3,7 @@ package me.neznamy.tab.platforms.bungeecord;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import me.neznamy.tab.api.bossbar.BarColor;
|
||||
import me.neznamy.tab.api.bossbar.BarStyle;
|
||||
import me.neznamy.tab.shared.chat.TabComponent;
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import me.neznamy.tab.shared.platform.decorators.SafeBossBar;
|
||||
import net.md_5.bungee.protocol.packet.BossBar;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -32,7 +32,7 @@ public class BungeeBossBar extends SafeBossBar<UUID> {
|
||||
public void create(@NotNull BossBarInfo bar) {
|
||||
BossBar packet = new BossBar(bar.getBossBar(), 0);
|
||||
packet.setHealth(bar.getProgress());
|
||||
packet.setTitle(bar.getTitle().convert(player.getVersion()));
|
||||
packet.setTitle(player.getPlatform().transformComponent(bar.getTitle(), player.getVersion()));
|
||||
packet.setColor(bar.getColor().ordinal());
|
||||
packet.setDivision(bar.getStyle().ordinal());
|
||||
player.sendPacket(packet);
|
||||
@@ -41,7 +41,7 @@ public class BungeeBossBar extends SafeBossBar<UUID> {
|
||||
@Override
|
||||
public void updateTitle(@NotNull BossBarInfo bar) {
|
||||
BossBar packet = new BossBar(bar.getBossBar(), 3);
|
||||
packet.setTitle(bar.getTitle().convert(player.getVersion()));
|
||||
packet.setTitle(player.getPlatform().transformComponent(bar.getTitle(), player.getVersion()));
|
||||
player.sendPacket(packet);
|
||||
}
|
||||
|
||||
|
||||
@@ -10,10 +10,10 @@ import me.neznamy.tab.platforms.bungeecord.tablist.BungeeTabList18;
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.chat.*;
|
||||
import me.neznamy.chat.ChatModifier;
|
||||
import me.neznamy.chat.component.*;
|
||||
import me.neznamy.tab.shared.features.injection.PipelineInjector;
|
||||
import me.neznamy.tab.shared.features.redis.RedisSupport;
|
||||
import me.neznamy.tab.shared.hook.PremiumVanishHook;
|
||||
import me.neznamy.tab.shared.platform.BossBar;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
@@ -21,20 +21,19 @@ import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import me.neznamy.tab.shared.platform.impl.DummyBossBar;
|
||||
import me.neznamy.tab.shared.proxy.ProxyPlatform;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import me.neznamy.tab.shared.util.cache.Cache;
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import net.md_5.bungee.api.ProxyConfig;
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
import net.md_5.bungee.api.chat.BaseComponent;
|
||||
import net.md_5.bungee.api.chat.TextComponent;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import org.bstats.bungeecord.Metrics;
|
||||
import org.bstats.charts.SimplePie;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* BungeeCord implementation of Platform
|
||||
@@ -44,6 +43,9 @@ public class BungeePlatform extends ProxyPlatform {
|
||||
@NotNull
|
||||
private final BungeeTAB plugin;
|
||||
|
||||
/** Cache for legacy components for <1.16 players, as we need 2 different components for each tab component */
|
||||
private final Cache<TabComponent, BaseComponent> legacyComponentCache = new Cache<>("Bungee legacy component cache", 1000, tab -> createComponent(tab, false));
|
||||
|
||||
/**
|
||||
* Constructs new instance with given plugin instance.
|
||||
*
|
||||
@@ -63,7 +65,7 @@ public class BungeePlatform extends ProxyPlatform {
|
||||
try {
|
||||
ProxyConfig config = ProxyServer.getInstance().getConfig();
|
||||
if ((boolean) config.getClass().getMethod("isDisableTabListRewrite").invoke(config)) {
|
||||
logWarn(TabComponent.fromColoredText("Waterfall's \"disable_tab_list_rewrite: true\" option may cause " +
|
||||
logWarn(new SimpleTextComponent("Waterfall's \"disable_tab_list_rewrite: true\" option may cause " +
|
||||
"the plugin to not work correctly. Disable it to avoid issues."));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
@@ -91,7 +93,7 @@ public class BungeePlatform extends ProxyPlatform {
|
||||
|
||||
@Override
|
||||
public void logWarn(@NotNull TabComponent message) {
|
||||
plugin.getLogger().warning(EnumChatFormat.RED + message.toLegacyText());
|
||||
plugin.getLogger().warning("§c" + message.toLegacyText());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -107,7 +109,7 @@ public class BungeePlatform extends ProxyPlatform {
|
||||
|
||||
@Override
|
||||
public void registerCommand() {
|
||||
ProxyServer.getInstance().getPluginManager().registerCommand(plugin, new BungeeTabCommand());
|
||||
ProxyServer.getInstance().getPluginManager().registerCommand(plugin, new BungeeTabCommand(getCommand()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -135,36 +137,80 @@ public class BungeePlatform extends ProxyPlatform {
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public BaseComponent convertComponent(@NotNull TabComponent component, boolean modern) {
|
||||
if (component instanceof SimpleComponent) return new TextComponent(component.toLegacyText());
|
||||
StructuredComponent iComponent = (StructuredComponent) component;
|
||||
TextComponent textComponent = new TextComponent(iComponent.getText());
|
||||
ChatModifier modifier = iComponent.getModifier();
|
||||
public BaseComponent convertComponent(@NotNull TabComponent component) {
|
||||
return createComponent(component, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms the TAB component into a bungee component depending on player's version.
|
||||
*
|
||||
* @param component
|
||||
* Component to transform
|
||||
* @param version
|
||||
* Version to transform the component to
|
||||
* @return Bungee component for the specified client version
|
||||
*/
|
||||
@NotNull
|
||||
public BaseComponent transformComponent(@NotNull TabComponent component, @NotNull ProtocolVersion version) {
|
||||
if (version.getMinorVersion() >= 16) {
|
||||
return component.convert();
|
||||
} else {
|
||||
// Convert color to legacy for <1.16 players
|
||||
return legacyComponentCache.get(component);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a bungee component using the given TAB component and modern flag for an RGB/legacy color decision.
|
||||
*
|
||||
* @param component
|
||||
* Component to convert
|
||||
* @param modern
|
||||
* {@code true} if colors should be as RGB, {@code false} if legacy
|
||||
* @return Converted component
|
||||
*/
|
||||
@NotNull
|
||||
private BaseComponent createComponent(@NotNull TabComponent component, boolean modern) {
|
||||
// Component type
|
||||
BaseComponent bComponent;
|
||||
if (component instanceof TextComponent) {
|
||||
bComponent = new net.md_5.bungee.api.chat.TextComponent(((TextComponent) component).getText());
|
||||
} else if (component instanceof TranslatableComponent) {
|
||||
bComponent = new net.md_5.bungee.api.chat.TranslatableComponent(((TranslatableComponent) component).getKey());
|
||||
} else if (component instanceof KeybindComponent) {
|
||||
bComponent = new net.md_5.bungee.api.chat.KeybindComponent(((KeybindComponent) component).getKeybind());
|
||||
} else {
|
||||
throw new IllegalStateException("Unexpected component type: " + component.getClass().getName());
|
||||
}
|
||||
|
||||
// Component style
|
||||
ChatModifier modifier = component.getModifier();
|
||||
if (modifier.getColor() != null) {
|
||||
if (modern) {
|
||||
textComponent.setColor(ChatColor.of("#" + modifier.getColor().getHexCode()));
|
||||
bComponent.setColor(ChatColor.of("#" + modifier.getColor().getHexCode()));
|
||||
} else {
|
||||
textComponent.setColor(ChatColor.of(modifier.getColor().getLegacyColor().name()));
|
||||
bComponent.setColor(ChatColor.of(modifier.getColor().getLegacyColor().name()));
|
||||
}
|
||||
}
|
||||
bComponent.setShadowColor(modifier.getShadowColor() == null ? null : new Color(
|
||||
(modifier.getShadowColor() >> 16) & 0xFF,
|
||||
(modifier.getShadowColor() >> 8) & 0xFF,
|
||||
(modifier.getShadowColor()) & 0xFF,
|
||||
(modifier.getShadowColor() >> 24) & 0xFF
|
||||
));
|
||||
bComponent.setBold(modifier.getBold());
|
||||
bComponent.setItalic(modifier.getItalic());
|
||||
bComponent.setObfuscated(modifier.getObfuscated());
|
||||
bComponent.setStrikethrough(modifier.getStrikethrough());
|
||||
bComponent.setUnderlined(modifier.getUnderlined());
|
||||
bComponent.setFont(modifier.getFont());
|
||||
|
||||
if (modifier.isBold()) textComponent.setBold(true);
|
||||
if (modifier.isItalic()) textComponent.setItalic(true);
|
||||
if (modifier.isObfuscated()) textComponent.setObfuscated(true);
|
||||
if (modifier.isStrikethrough()) textComponent.setStrikethrough(true);
|
||||
if (modifier.isUnderlined()) textComponent.setUnderlined(true);
|
||||
|
||||
textComponent.setFont(modifier.getFont());
|
||||
|
||||
if (!iComponent.getExtra().isEmpty()) {
|
||||
List<BaseComponent> list = new ArrayList<>();
|
||||
for (StructuredComponent extra : iComponent.getExtra()) {
|
||||
list.add(convertComponent(extra, modern));
|
||||
}
|
||||
textComponent.setExtra(list);
|
||||
// Extra
|
||||
for (TabComponent extra : component.getExtra()) {
|
||||
bComponent.addExtra(createComponent(extra, modern));
|
||||
}
|
||||
|
||||
return textComponent;
|
||||
return bComponent;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -204,4 +250,15 @@ public class BungeePlatform extends ProxyPlatform {
|
||||
public boolean supportsListOrder() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsScoreboards() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String getCommand() {
|
||||
return "btab";
|
||||
}
|
||||
}
|
||||
+24
-38
@@ -5,8 +5,8 @@ import lombok.NonNull;
|
||||
import me.neznamy.tab.shared.Limitations;
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.chat.SimpleComponent;
|
||||
import me.neznamy.tab.shared.chat.TabComponent;
|
||||
import me.neznamy.chat.component.SimpleTextComponent;
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import me.neznamy.tab.shared.platform.decorators.SafeScoreboard;
|
||||
import net.md_5.bungee.api.chat.BaseComponent;
|
||||
import net.md_5.bungee.protocol.Either;
|
||||
@@ -43,34 +43,26 @@ public class BungeeScoreboard extends SafeScoreboard<BungeeTabPlayer> {
|
||||
|
||||
@Override
|
||||
public void registerObjective(@NonNull Objective objective) {
|
||||
player.sendPacket(new ScoreboardObjective(
|
||||
objective.getName(),
|
||||
either(objective.getTitle(), Limitations.SCOREBOARD_TITLE_PRE_1_13),
|
||||
ScoreboardObjective.HealthDisplay.values()[objective.getHealthDisplay().ordinal()],
|
||||
(byte) ObjectiveAction.REGISTER,
|
||||
numberFormat(objective.getNumberFormat())
|
||||
));
|
||||
sendObjectivePacket(objective, (byte) ObjectiveAction.REGISTER);
|
||||
player.sendPacket(new ScoreboardDisplay(objective.getDisplaySlot().ordinal(), objective.getName()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterObjective(@NonNull Objective objective) {
|
||||
player.sendPacket(new ScoreboardObjective(
|
||||
objective.getName(),
|
||||
either(objective.getTitle(), Limitations.SCOREBOARD_TITLE_PRE_1_13), // Empty value instead of null to prevent NPE kick on 1.7
|
||||
null,
|
||||
(byte) ObjectiveAction.UNREGISTER,
|
||||
null
|
||||
));
|
||||
sendObjectivePacket(objective, (byte) ObjectiveAction.UNREGISTER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateObjective(@NonNull Objective objective) {
|
||||
sendObjectivePacket(objective, (byte) ObjectiveAction.UPDATE);
|
||||
}
|
||||
|
||||
private void sendObjectivePacket(@NonNull Objective objective, byte action) {
|
||||
player.sendPacket(new ScoreboardObjective(
|
||||
objective.getName(),
|
||||
either(objective.getTitle(), Limitations.SCOREBOARD_TITLE_PRE_1_13),
|
||||
ScoreboardObjective.HealthDisplay.values()[objective.getHealthDisplay().ordinal()],
|
||||
(byte) ObjectiveAction.UPDATE,
|
||||
action,
|
||||
numberFormat(objective.getNumberFormat())
|
||||
));
|
||||
}
|
||||
@@ -82,7 +74,7 @@ public class BungeeScoreboard extends SafeScoreboard<BungeeTabPlayer> {
|
||||
(byte) ScoreAction.CHANGE,
|
||||
score.getObjective().getName(),
|
||||
score.getValue(),
|
||||
score.getDisplayName() == null ? null : score.getDisplayName().convert(player.getVersion()),
|
||||
score.getDisplayName() == null ? null : player.getPlatform().transformComponent(score.getDisplayName(), player.getVersion()),
|
||||
numberFormat(score.getNumberFormat())
|
||||
));
|
||||
}
|
||||
@@ -104,38 +96,31 @@ public class BungeeScoreboard extends SafeScoreboard<BungeeTabPlayer> {
|
||||
|
||||
@Override
|
||||
public void registerTeam(@NonNull Team team) {
|
||||
player.sendPacket(new net.md_5.bungee.protocol.packet.Team(
|
||||
team.getName(),
|
||||
(byte) TeamAction.CREATE,
|
||||
either(new SimpleComponent(team.getName()), Limitations.TEAM_PREFIX_SUFFIX_PRE_1_13),
|
||||
either(team.getPrefix(), Limitations.TEAM_PREFIX_SUFFIX_PRE_1_13),
|
||||
either(team.getSuffix(), Limitations.TEAM_PREFIX_SUFFIX_PRE_1_13),
|
||||
team.getVisibility().toString(),
|
||||
team.getCollision().toString(),
|
||||
player.getVersion().getMinorVersion() >= TEAM_REWORK_VERSION ? team.getColor().ordinal() : 0,
|
||||
(byte) team.getOptions(),
|
||||
team.getPlayers().toArray(new String[0])
|
||||
));
|
||||
sendTeamPacket(team, (byte) TeamAction.CREATE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterTeam(@NonNull Team team) {
|
||||
player.sendPacket(new net.md_5.bungee.protocol.packet.Team(team.getName()));
|
||||
sendTeamPacket(team, (byte) TeamAction.REMOVE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateTeam(@NonNull Team team) {
|
||||
sendTeamPacket(team, (byte) TeamAction.UPDATE);
|
||||
}
|
||||
|
||||
private void sendTeamPacket(@NonNull Team team, byte action) {
|
||||
player.sendPacket(new net.md_5.bungee.protocol.packet.Team(
|
||||
team.getName(),
|
||||
(byte) TeamAction.UPDATE,
|
||||
either(new SimpleComponent(team.getName()), Limitations.TEAM_PREFIX_SUFFIX_PRE_1_13),
|
||||
action,
|
||||
either(new SimpleTextComponent(team.getName()), Limitations.TEAM_PREFIX_SUFFIX_PRE_1_13),
|
||||
either(team.getPrefix(), Limitations.TEAM_PREFIX_SUFFIX_PRE_1_13),
|
||||
either(team.getSuffix(), Limitations.TEAM_PREFIX_SUFFIX_PRE_1_13),
|
||||
team.getVisibility().toString(),
|
||||
team.getCollision().toString(),
|
||||
player.getVersion().getMinorVersion() >= TEAM_REWORK_VERSION ? team.getColor().ordinal() : 0,
|
||||
net.md_5.bungee.protocol.packet.Team.NameTagVisibility.valueOf(team.getVisibility().name()),
|
||||
net.md_5.bungee.protocol.packet.Team.CollisionRule.valueOf(team.getCollision().name()),
|
||||
player.getVersion().getMinorVersion() >= TEAM_REWORK_VERSION ? team.getColor().getLegacyColor().ordinal() : 0,
|
||||
(byte) team.getOptions(),
|
||||
null
|
||||
team.getPlayers().toArray(new String[0])
|
||||
));
|
||||
}
|
||||
|
||||
@@ -159,9 +144,10 @@ public class BungeeScoreboard extends SafeScoreboard<BungeeTabPlayer> {
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private Either<String, BaseComponent> either(@NonNull TabComponent text, int legacyLimit) {
|
||||
if (player.getVersion().getMinorVersion() >= TEAM_REWORK_VERSION) {
|
||||
return Either.right(text.convert(player.getVersion()));
|
||||
return Either.right(player.getPlatform().transformComponent(text, player.getVersion()));
|
||||
} else {
|
||||
return Either.left(cutTo(text.toLegacyText(), legacyLimit));
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
package me.neznamy.tab.platforms.bungeecord;
|
||||
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.chat.EnumChatFormat;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import net.md_5.bungee.api.plugin.Plugin;
|
||||
import net.md_5.bungee.protocol.packet.PlayerListItem;
|
||||
|
||||
/**
|
||||
* Main class for BungeeCord.
|
||||
@@ -12,13 +11,14 @@ public class BungeeTAB extends Plugin {
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
try {
|
||||
PlayerListItem.Item.class.getDeclaredField("showHat");
|
||||
if (ReflectionUtils.classExists("net.md_5.bungee.protocol.packet.Team$NameTagVisibility")) {
|
||||
TAB.create(new BungeePlatform(this));
|
||||
} catch (NoSuchFieldException e) {
|
||||
getLogger().warning(EnumChatFormat.RED + "The plugin requires BungeeCord build #1885 " +
|
||||
"(released on November 23rd, 2024) and up (or an equivalent fork) to work. If you are using a fork that did not" +
|
||||
" update to the new BungeeCord version yet, stay on TAB v5.0.2, which supports older builds.");
|
||||
} else {
|
||||
getLogger().warning("§c====================================================================================================");
|
||||
getLogger().warning("§cThe plugin requires BungeeCord build #1899 " +
|
||||
"(released on February 1st, 2025) and up (or an equivalent fork) to work. If you are using a fork that did not" +
|
||||
" update to the new BungeeCord version yet, stay on TAB v5.0.5, which supports older builds.");
|
||||
getLogger().warning("§c====================================================================================================");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
package me.neznamy.tab.platforms.bungeecord;
|
||||
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.chat.EnumChatFormat;
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import net.md_5.bungee.api.CommandSender;
|
||||
import net.md_5.bungee.api.chat.TextComponent;
|
||||
import net.md_5.bungee.api.chat.BaseComponent;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.api.plugin.Command;
|
||||
import net.md_5.bungee.api.plugin.TabExecutor;
|
||||
@@ -19,17 +20,27 @@ import java.util.Collections;
|
||||
public class BungeeTabCommand extends Command implements TabExecutor {
|
||||
|
||||
/**
|
||||
* Constructs new instance
|
||||
* Constructs new instance.
|
||||
*
|
||||
* @param command
|
||||
* Command to register
|
||||
*/
|
||||
public BungeeTabCommand() {
|
||||
super(TabConstants.COMMAND_PROXY, null);
|
||||
public BungeeTabCommand(@NotNull String command) {
|
||||
super(command, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(@NotNull CommandSender sender, @NotNull String[] args) {
|
||||
if (TAB.getInstance().isPluginDisabled()) {
|
||||
for (String message : TAB.getInstance().getDisabledCommand().execute(args, sender.hasPermission(TabConstants.Permission.COMMAND_RELOAD), sender.hasPermission(TabConstants.Permission.COMMAND_ALL))) {
|
||||
sender.sendMessage(new TextComponent(EnumChatFormat.color(message)));
|
||||
if (sender instanceof ProxiedPlayer) {
|
||||
sender.sendMessage(((BungeePlatform)TAB.getInstance().getPlatform()).transformComponent(
|
||||
TabComponent.fromColoredText(message),
|
||||
ProtocolVersion.fromNetworkId(((ProxiedPlayer)sender).getPendingConnection().getVersion())
|
||||
));
|
||||
} else {
|
||||
sender.sendMessage((BaseComponent) TabComponent.fromColoredText(message).convert());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
TabPlayer p = null;
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
package me.neznamy.tab.platforms.bungeecord;
|
||||
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.chat.TabComponent;
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import me.neznamy.tab.shared.proxy.ProxyTabPlayer;
|
||||
import net.md_5.bungee.UserConnection;
|
||||
import net.md_5.bungee.api.chat.BaseComponent;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.connection.InitialHandler;
|
||||
import net.md_5.bungee.connection.LoginResult;
|
||||
@@ -43,7 +42,7 @@ public class BungeeTabPlayer extends ProxyTabPlayer {
|
||||
|
||||
@Override
|
||||
public void sendMessage(@NotNull TabComponent message) {
|
||||
getPlayer().sendMessage((BaseComponent) message.convert(getVersion()));
|
||||
getPlayer().sendMessage(getPlatform().transformComponent(message, getVersion()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+2
-2
@@ -3,7 +3,7 @@ package me.neznamy.tab.platforms.bungeecord.hook;
|
||||
import de.myzelyam.api.vanish.BungeeVanishAPI;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import me.neznamy.tab.platforms.bungeecord.BungeeTabPlayer;
|
||||
import me.neznamy.tab.shared.chat.TabComponent;
|
||||
import me.neznamy.chat.component.SimpleTextComponent;
|
||||
import me.neznamy.tab.shared.hook.PremiumVanishHook;
|
||||
import me.neznamy.tab.shared.platform.Platform;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
@@ -29,7 +29,7 @@ public class BungeePremiumVanishHook extends PremiumVanishHook {
|
||||
canSeeEnabled = true;
|
||||
} else {
|
||||
canSeeEnabled = false;
|
||||
platform.logWarn(TabComponent.fromColoredText("Detected an outdated version of " +
|
||||
platform.logWarn(new SimpleTextComponent("Detected an outdated version of " +
|
||||
"PremiumVanish with limited API. Vanish compatibility " +
|
||||
"may not work as expected. Update PremiumVanish to version 2.7.11+ for optimal experience."));
|
||||
}
|
||||
|
||||
+6
-1
@@ -4,7 +4,7 @@ import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bungeecord.BungeeTabPlayer;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.chat.TabComponent;
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import me.neznamy.tab.shared.platform.decorators.TrackedTabList;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import net.md_5.bungee.UserConnection;
|
||||
@@ -161,4 +161,9 @@ public abstract class BungeeTabList extends TrackedTabList<BungeeTabPlayer, Base
|
||||
public boolean containsEntry(@NonNull UUID entry) {
|
||||
return uuids.contains(entry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BaseComponent toComponent(@NonNull TabComponent component) {
|
||||
return player.getPlatform().transformComponent(component, player.getVersion());
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -3,7 +3,7 @@ package me.neznamy.tab.platforms.bungeecord.tablist;
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.platforms.bungeecord.BungeeTabPlayer;
|
||||
import me.neznamy.tab.shared.Limitations;
|
||||
import me.neznamy.tab.shared.chat.TabComponent;
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import net.md_5.bungee.api.chat.BaseComponent;
|
||||
import net.md_5.bungee.api.chat.TextComponent;
|
||||
import net.md_5.bungee.protocol.packet.PlayerListItem;
|
||||
@@ -107,7 +107,7 @@ public class BungeeTabList17 extends BungeeTabList {
|
||||
String displayNameString = component.toLegacyText();
|
||||
if (displayNameString.length() > Limitations.MAX_DISPLAY_NAME_LENGTH_1_7)
|
||||
displayNameString = displayNameString.substring(0, Limitations.MAX_DISPLAY_NAME_LENGTH_1_7);
|
||||
return TabComponent.fromColoredText(displayNameString).convert(player.getVersion());
|
||||
return player.getPlatform().transformComponent(new me.neznamy.chat.component.TextComponent(displayNameString), player.getVersion());
|
||||
}
|
||||
|
||||
private void update(@NonNull PlayerListItem.Action action, @NonNull PlayerListItem.Item item) {
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
dependencies {
|
||||
compileOnlyApi("net.kyori:adventure-api:4.18.0")
|
||||
}
|
||||
+18
-28
@@ -1,5 +1,6 @@
|
||||
package me.neznamy.tab.shared.chat;
|
||||
package me.neznamy.chat;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -8,25 +9,29 @@ import org.jetbrains.annotations.Nullable;
|
||||
/**
|
||||
* Class representing a component style.
|
||||
*/
|
||||
@Data @NoArgsConstructor
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class ChatModifier {
|
||||
|
||||
@Nullable private TextColor color;
|
||||
private boolean bold;
|
||||
private boolean italic;
|
||||
private boolean obfuscated;
|
||||
private boolean strikethrough;
|
||||
private boolean underlined;
|
||||
@Nullable private Integer shadowColor; // ARGB
|
||||
@Nullable private Boolean bold;
|
||||
@Nullable private Boolean italic;
|
||||
@Nullable private Boolean underlined;
|
||||
@Nullable private Boolean strikethrough;
|
||||
@Nullable private Boolean obfuscated;
|
||||
@Nullable private String font;
|
||||
|
||||
/**
|
||||
* Constructs a copy of provided modifier.
|
||||
* Constructs a copy of the provided modifier.
|
||||
*
|
||||
* @param modifier
|
||||
* Modifier to copy
|
||||
*/
|
||||
public ChatModifier(@NotNull ChatModifier modifier) {
|
||||
color = modifier.color;
|
||||
shadowColor = modifier.shadowColor;
|
||||
bold = modifier.bold;
|
||||
italic = modifier.italic;
|
||||
obfuscated = modifier.obfuscated;
|
||||
@@ -44,26 +49,11 @@ public class ChatModifier {
|
||||
@NotNull
|
||||
public String getMagicCodes() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
if (bold) builder.append(EnumChatFormat.BOLD);
|
||||
if (italic) builder.append(EnumChatFormat.ITALIC);
|
||||
if (obfuscated) builder.append(EnumChatFormat.OBFUSCATED);
|
||||
if (strikethrough) builder.append(EnumChatFormat.STRIKETHROUGH);
|
||||
if (underlined) builder.append(EnumChatFormat.UNDERLINE);
|
||||
if (Boolean.TRUE.equals(bold)) builder.append("§l");
|
||||
if (Boolean.TRUE.equals(italic)) builder.append("§o");
|
||||
if (Boolean.TRUE.equals(obfuscated)) builder.append("§k");
|
||||
if (Boolean.TRUE.equals(strikethrough)) builder.append("§m");
|
||||
if (Boolean.TRUE.equals(underlined)) builder.append("§n");
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns bitmask of magic codes.
|
||||
*
|
||||
* @return Bitmask of magic codes
|
||||
*/
|
||||
public int getMagicCodeBitMask() {
|
||||
int mask = 0;
|
||||
if (bold) mask += 1;
|
||||
if (italic) mask += 2;
|
||||
if (obfuscated) mask += 4;
|
||||
if (strikethrough) mask += 8;
|
||||
if (underlined) mask += 16;
|
||||
return mask;
|
||||
}
|
||||
}
|
||||
+6
-74
@@ -1,13 +1,14 @@
|
||||
package me.neznamy.tab.shared.chat;
|
||||
package me.neznamy.chat;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* An enum containing all possible legacy color codes and magic codes. Also contains handy color-related methods.
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum EnumChatFormat {
|
||||
|
||||
BLACK('0', 0x000000),
|
||||
@@ -33,64 +34,12 @@ public enum EnumChatFormat {
|
||||
ITALIC('o', 0),
|
||||
RESET('r', 0);
|
||||
|
||||
/** Creating a constant to avoid memory allocations on each request */
|
||||
public static final EnumChatFormat[] VALUES = values();
|
||||
|
||||
/** The symbol minecraft uses to colorize text */
|
||||
public static final char COLOR_CHAR = 0x00a7;
|
||||
|
||||
/** The color symbol in form of a string */
|
||||
public static final String COLOR_STRING = String.valueOf(COLOR_CHAR);
|
||||
|
||||
/** Character representing the color or magic code */
|
||||
private final char character;
|
||||
|
||||
/** Red value of this constant, 0 for magic codes */
|
||||
private final short red;
|
||||
|
||||
/** Green value of this constant, 0 for magic codes */
|
||||
private final short green;
|
||||
|
||||
/** Blue value of this constant, 0 for magic codes */
|
||||
private final short blue;
|
||||
|
||||
/** Color as a hex code, 0 for magic codes */
|
||||
private final int rgb;
|
||||
|
||||
/** Color symbol followed by constant's character */
|
||||
private final String format;
|
||||
|
||||
/**
|
||||
* Constructs new color instance with given character and hex code
|
||||
*
|
||||
* @param character
|
||||
* character which the color goes by
|
||||
* @param rgb
|
||||
* hex code of the color
|
||||
*/
|
||||
EnumChatFormat(char character, int rgb) {
|
||||
this.character = character;
|
||||
this.rgb = rgb;
|
||||
format = String.valueOf(COLOR_CHAR) + character;
|
||||
red = (short) ((rgb >> 16) & 0xFF);
|
||||
green = (short) ((rgb >> 8) & 0xFF);
|
||||
blue = (short) (rgb & 0xFF);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns enum value based on provided character or null if character is not valid
|
||||
*
|
||||
* @param c
|
||||
* color code character (0-9, a-f, k-o, r)
|
||||
* @return instance from the character or null if character is not valid
|
||||
*/
|
||||
public static @Nullable EnumChatFormat getByChar(char c) {
|
||||
for (EnumChatFormat format : VALUES) {
|
||||
if (format.character == c) return format;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Color translation method taken from bukkit, which converts '&' symbol into
|
||||
* the actual color character if followed by a valid color character.
|
||||
@@ -104,25 +53,13 @@ public enum EnumChatFormat {
|
||||
char[] b = textToTranslate.toCharArray();
|
||||
for (int i = 0; i < b.length - 1; i++) {
|
||||
if ((b[i] == '&') && ("0123456789AaBbCcDdEeFfKkLlMmNnOoRrXx#".indexOf(b[(i + 1)]) > -1)) {
|
||||
b[i] = COLOR_CHAR;
|
||||
b[i] = '§';
|
||||
b[(i + 1)] = Character.toLowerCase(b[(i + 1)]);
|
||||
}
|
||||
}
|
||||
return new String(b);
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns back the color symbol into '&' symbol in provided text.
|
||||
*
|
||||
* @param text
|
||||
* text to revert colors in
|
||||
* @return reverted text
|
||||
*/
|
||||
public static @NotNull String decolor(@NotNull String text) {
|
||||
if (!text.contains(COLOR_STRING)) return text;
|
||||
return text.replace(COLOR_CHAR, '&');
|
||||
}
|
||||
|
||||
/**
|
||||
* Code taken from bukkit, which returns last color codes used in provided text.
|
||||
*
|
||||
@@ -135,10 +72,10 @@ public enum EnumChatFormat {
|
||||
int length = input.length();
|
||||
for (int index = length - 1; index > -1; index--) {
|
||||
char section = input.charAt(index);
|
||||
if ((section == COLOR_CHAR || section == '&') && (index < length - 1)) {
|
||||
if ((section == '§' || section == '&') && (index < length - 1)) {
|
||||
char c = input.charAt(index + 1);
|
||||
if ("0123456789AaBbCcDdEeFfKkLlMmNnOoRr".contains(String.valueOf(c))) {
|
||||
result.insert(0, COLOR_CHAR);
|
||||
result.insert(0, '§');
|
||||
result.insert(1, c);
|
||||
if ("0123456789AaBbCcDdEeFfRr".contains(String.valueOf(c))) {
|
||||
break;
|
||||
@@ -148,9 +85,4 @@ public enum EnumChatFormat {
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return format;
|
||||
}
|
||||
}
|
||||
+58
-44
@@ -1,17 +1,43 @@
|
||||
package me.neznamy.tab.shared.chat;
|
||||
package me.neznamy.chat;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A class representing a component color, which can be either RGB or legacy code.
|
||||
*/
|
||||
public class TextColor {
|
||||
|
||||
/** Instances from legacy colors to avoid new class initialization each time */
|
||||
private static final TextColor[] legacyColors = Arrays.stream(EnumChatFormat.VALUES).map(TextColor::new).toArray(TextColor[]::new);
|
||||
/** Map of legacy colors by their character */
|
||||
public static final Map<Character, TextColor> LEGACY_COLORS = new LinkedHashMap<>();
|
||||
|
||||
public static final TextColor BLACK = new TextColor(EnumChatFormat.BLACK);
|
||||
public static final TextColor DARK_BLUE = new TextColor(EnumChatFormat.DARK_BLUE);
|
||||
public static final TextColor DARK_GREEN = new TextColor(EnumChatFormat.DARK_GREEN);
|
||||
public static final TextColor DARK_AQUA = new TextColor(EnumChatFormat.DARK_AQUA);
|
||||
public static final TextColor DARK_RED = new TextColor(EnumChatFormat.DARK_RED);
|
||||
public static final TextColor DARK_PURPLE = new TextColor(EnumChatFormat.DARK_PURPLE);
|
||||
public static final TextColor GOLD = new TextColor(EnumChatFormat.GOLD);
|
||||
public static final TextColor GRAY = new TextColor(EnumChatFormat.GRAY);
|
||||
public static final TextColor DARK_GRAY = new TextColor(EnumChatFormat.DARK_GRAY);
|
||||
public static final TextColor BLUE = new TextColor(EnumChatFormat.BLUE);
|
||||
public static final TextColor GREEN = new TextColor(EnumChatFormat.GREEN);
|
||||
public static final TextColor AQUA = new TextColor(EnumChatFormat.AQUA);
|
||||
public static final TextColor RED = new TextColor(EnumChatFormat.RED);
|
||||
public static final TextColor LIGHT_PURPLE = new TextColor(EnumChatFormat.LIGHT_PURPLE);
|
||||
public static final TextColor YELLOW = new TextColor(EnumChatFormat.YELLOW);
|
||||
public static final TextColor WHITE = new TextColor(EnumChatFormat.WHITE);
|
||||
public static final TextColor OBFUSCATED = new TextColor(EnumChatFormat.OBFUSCATED);
|
||||
public static final TextColor BOLD = new TextColor(EnumChatFormat.BOLD);
|
||||
public static final TextColor STRIKETHROUGH = new TextColor(EnumChatFormat.STRIKETHROUGH);
|
||||
public static final TextColor UNDERLINE = new TextColor(EnumChatFormat.UNDERLINE);
|
||||
public static final TextColor ITALIC = new TextColor(EnumChatFormat.ITALIC);
|
||||
public static final TextColor RESET = new TextColor(EnumChatFormat.RESET);
|
||||
|
||||
private static final TextColor[] legacyColorArray = LEGACY_COLORS.values().toArray(new TextColor[0]);
|
||||
|
||||
/**
|
||||
* RGB values as a single number of 3 8-bit numbers (0-255).
|
||||
@@ -21,21 +47,13 @@ public class TextColor {
|
||||
private int rgb = -1;
|
||||
|
||||
/** Closest legacy color to this color object. */
|
||||
@Nullable
|
||||
private EnumChatFormat legacyColor;
|
||||
|
||||
/** HEX code of this color as a 6-digit hexadecimal string without "#" prefix. */
|
||||
@Nullable
|
||||
private String hexCode;
|
||||
|
||||
/**
|
||||
* Boolean value whether the legacy color was forced with constructor or should be
|
||||
* automatically assigned as closest color.
|
||||
* This value is used in gradients when converting text for legacy players.
|
||||
*/
|
||||
@Getter private boolean legacyColorForced;
|
||||
|
||||
/** Flag tracking whether this color is actually a legacy color or not */
|
||||
@Getter private boolean legacy;
|
||||
|
||||
/**
|
||||
* Constructs new instance from provided 6-digit hex code string
|
||||
*
|
||||
@@ -48,22 +66,6 @@ public class TextColor {
|
||||
this.hexCode = hexCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs new instance from provided 6-digit hex code and forced legacy color
|
||||
*
|
||||
* @param hexCode
|
||||
* 6-digit combination of hex numbers as a string
|
||||
* @param legacyColor
|
||||
* color to use for legacy clients instead of using the closest legacy color
|
||||
* @throws IllegalArgumentException
|
||||
* if {@code hexCode} is {@code null} or {@code legacyColor} is {@code null}
|
||||
*/
|
||||
public TextColor(@NotNull String hexCode, @NotNull EnumChatFormat legacyColor) {
|
||||
this.hexCode = hexCode;
|
||||
legacyColorForced = true;
|
||||
this.legacyColor = legacyColor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs new instance from provided legacy color
|
||||
*
|
||||
@@ -74,8 +76,17 @@ public class TextColor {
|
||||
rgb = legacyColor.getRgb();
|
||||
this.legacyColor = legacyColor;
|
||||
hexCode = String.format("%06X", legacyColor.getRgb());
|
||||
legacyColorForced = true;
|
||||
legacy = true;
|
||||
LEGACY_COLORS.put(legacyColor.getCharacter(), this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs new instance with given RGB value.
|
||||
*
|
||||
* @param rgb
|
||||
* RGB value
|
||||
*/
|
||||
public TextColor(int rgb) {
|
||||
this.rgb = rgb;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -99,7 +110,7 @@ public class TextColor {
|
||||
double minMaxDist = 9999;
|
||||
double maxDist;
|
||||
EnumChatFormat closestColor = EnumChatFormat.WHITE;
|
||||
for (EnumChatFormat color : EnumChatFormat.VALUES) {
|
||||
for (TextColor color : legacyColorArray) {
|
||||
int rDiff = Math.abs(color.getRed() - getRed());
|
||||
int gDiff = Math.abs(color.getGreen() - getGreen());
|
||||
int bDiff = Math.abs(color.getBlue() - getBlue());
|
||||
@@ -108,7 +119,7 @@ public class TextColor {
|
||||
if (bDiff > maxDist) maxDist = bDiff;
|
||||
if (maxDist < minMaxDist) {
|
||||
minMaxDist = maxDist;
|
||||
closestColor = color;
|
||||
closestColor = color.legacyColor;
|
||||
}
|
||||
}
|
||||
return closestColor;
|
||||
@@ -147,7 +158,7 @@ public class TextColor {
|
||||
* @return rgb value
|
||||
*/
|
||||
public int getRgb() {
|
||||
if (rgb == -1) rgb = Integer.parseInt(hexCode, 16);
|
||||
if (rgb == -1) rgb = Integer.parseInt(getHexCode(), 16);
|
||||
return rgb;
|
||||
}
|
||||
|
||||
@@ -158,7 +169,8 @@ public class TextColor {
|
||||
*
|
||||
* @return closest legacy color
|
||||
*/
|
||||
public @NotNull EnumChatFormat getLegacyColor() {
|
||||
@NotNull
|
||||
public EnumChatFormat getLegacyColor() {
|
||||
if (legacyColor == null) legacyColor = loadClosestColor();
|
||||
return legacyColor;
|
||||
}
|
||||
@@ -168,19 +180,21 @@ public class TextColor {
|
||||
*
|
||||
* @return the rgb combination as a 6-digit hex code string
|
||||
*/
|
||||
public @NotNull String getHexCode() {
|
||||
@NotNull
|
||||
public String getHexCode() {
|
||||
if (hexCode == null) hexCode = String.format("%06X", rgb);
|
||||
return hexCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns color from given legacy color
|
||||
* Returns legacy colors based on provided character or {@code null} if character is not valid
|
||||
*
|
||||
* @param format
|
||||
* Legacy color
|
||||
* @return Instance from legacy color
|
||||
* @param c
|
||||
* color code character (0-9, a-f, k-o, r)
|
||||
* @return instance from the character or {@code null} if character is not valid
|
||||
*/
|
||||
public static TextColor legacy(@NotNull EnumChatFormat format) {
|
||||
return legacyColors[format.ordinal()];
|
||||
@Nullable
|
||||
public static TextColor getLegacyByChar(char c) {
|
||||
return LEGACY_COLORS.get(c);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package me.neznamy.chat.component;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* A component of "keybind" type that contains bound key.
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public class KeybindComponent extends TabComponent {
|
||||
|
||||
@NotNull
|
||||
protected final String keybind;
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String toLegacyText() {
|
||||
return keybind;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package me.neznamy.chat.component;
|
||||
|
||||
import me.neznamy.chat.TextColor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* An implementation that only uses the "text" field with legacy colors in it, without using
|
||||
* any component style or extra.
|
||||
*/
|
||||
public class SimpleTextComponent extends TextComponent {
|
||||
|
||||
/**
|
||||
* Constructs new instance with given text.
|
||||
*
|
||||
* @param text
|
||||
* Component text
|
||||
*/
|
||||
public SimpleTextComponent(@NotNull String text) {
|
||||
super(text);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String toLegacyText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
protected TextColor fetchLastColor() {
|
||||
char[] chars = text.toCharArray();
|
||||
for (int index = chars.length - 2; index >= 0; index--) {
|
||||
if (chars[index] != '§') continue;
|
||||
TextColor color = TextColor.getLegacyByChar(chars[index + 1]);
|
||||
if (color != null) return color;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,355 @@
|
||||
package me.neznamy.chat.component;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.chat.ChatModifier;
|
||||
import me.neznamy.chat.EnumChatFormat;
|
||||
import me.neznamy.chat.TextColor;
|
||||
import me.neznamy.chat.hook.AdventureHook;
|
||||
import me.neznamy.chat.rgb.RGBUtils;
|
||||
import me.neznamy.chat.util.TriFunction;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Base class for managing minecraft components.
|
||||
*/
|
||||
public abstract class TabComponent {
|
||||
|
||||
/** Function for converting this class into platform's actual component */
|
||||
@Nullable
|
||||
public static Function<TabComponent, Object> CONVERT_FUNCTION;
|
||||
|
||||
/** Formatter to convert gradient into TAB's #RRGGBB spam */
|
||||
private static final TriFunction<TextColor, String, TextColor, String> TABGradientFormatter = (start, text, end) -> {
|
||||
//lazy support for magic codes in gradients
|
||||
String magicCodes = EnumChatFormat.getLastColors(text);
|
||||
String deColorized = text.substring(magicCodes.length());
|
||||
StringBuilder sb = new StringBuilder();
|
||||
int length = deColorized.length();
|
||||
if (length == 1) {
|
||||
sb.append("#");
|
||||
sb.append(start.getHexCode());
|
||||
sb.append(magicCodes);
|
||||
sb.append(deColorized);
|
||||
return sb.toString();
|
||||
}
|
||||
for (int i=0; i<length; i++) {
|
||||
int red = (int) (start.getRed() + (float)(end.getRed() - start.getRed())/(length-1)*i);
|
||||
int green = (int) (start.getGreen() + (float)(end.getGreen() - start.getGreen())/(length-1)*i);
|
||||
int blue = (int) (start.getBlue() + (float)(end.getBlue() - start.getBlue())/(length-1)*i);
|
||||
sb.append("#");
|
||||
sb.append(new TextColor(red, green, blue).getHexCode());
|
||||
sb.append(magicCodes);
|
||||
sb.append(deColorized.charAt(i));
|
||||
}
|
||||
return sb.toString();
|
||||
};
|
||||
|
||||
/** Formatter to convert RGB code to use TAB's #RRGGBB */
|
||||
private static final Function<TextColor, String> TABRGBFormatter = color -> "#" + color.getHexCode();
|
||||
|
||||
/** Pattern for detecting fonts */
|
||||
private static final Pattern fontPattern = Pattern.compile("<font:(.*?)>(.*?)</font>");
|
||||
|
||||
@Nullable
|
||||
private Object converted;
|
||||
|
||||
/** Adventure component from this component */
|
||||
@Nullable
|
||||
@Setter
|
||||
private Component adventureComponent;
|
||||
|
||||
@Nullable
|
||||
private Object fixedFormat;
|
||||
|
||||
/** TextHolder object for Velocity */
|
||||
@Nullable
|
||||
private Object textHolder;
|
||||
|
||||
/**
|
||||
* Last color of this component.
|
||||
* Used to determine team color based on the last color of prefix.
|
||||
* Saved as TextColor instead of EnumChatFormat to have things ready if Mojang adds RGB support to team color.
|
||||
*/
|
||||
@Nullable
|
||||
private TextColor lastColor;
|
||||
|
||||
/** Chat modifier containing color, magic codes, hover and click event */
|
||||
@NotNull
|
||||
@Getter
|
||||
@Setter
|
||||
protected ChatModifier modifier = new ChatModifier();
|
||||
|
||||
/** Extra components used in "extra" field */
|
||||
protected List<TabComponent> extra;
|
||||
|
||||
/**
|
||||
* Returns list of extra components. If no extra components are defined, returns empty list.
|
||||
*
|
||||
* @return list of extra components
|
||||
*/
|
||||
public List<TabComponent> getExtra() {
|
||||
if (extra == null) return Collections.emptyList();
|
||||
return extra;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds extra component to this component.
|
||||
*
|
||||
* @param extra
|
||||
* Extra component to append
|
||||
*/
|
||||
public void addExtra(@NotNull TabComponent extra) {
|
||||
if (this.extra == null) this.extra = new ArrayList<>();
|
||||
this.extra.add(extra);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts this component to platform's component.
|
||||
*
|
||||
* @return Converted component
|
||||
* @param <T>
|
||||
* Platform's component class
|
||||
*/
|
||||
@NotNull
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T convert() {
|
||||
if (CONVERT_FUNCTION == null) throw new IllegalStateException("Convert function is not initialized");
|
||||
if (converted == null) converted = CONVERT_FUNCTION.apply(this);
|
||||
return (T) converted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts this component to an Adventure component.
|
||||
* @return Converted component
|
||||
*/
|
||||
@NotNull
|
||||
public Component toAdventure() {
|
||||
if (adventureComponent == null) adventureComponent = AdventureHook.convert(this);
|
||||
return adventureComponent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a FixedFormat using given platform-specific create function.
|
||||
* If the value is already initialized, it is returned immediately instead.
|
||||
*
|
||||
* @param createFunction
|
||||
* Platform's function to convert platform component to FixedFormat
|
||||
* @return Platform's FixedFormat from this component
|
||||
* @param <F>
|
||||
* Platform's FixedFormat type
|
||||
* @param <C>
|
||||
* Platform's Component type
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@SneakyThrows
|
||||
public <F, C> F toFixedFormat(@NotNull Function<C, F> createFunction) {
|
||||
if (fixedFormat == null) fixedFormat = createFunction.apply(convert());
|
||||
return (F) fixedFormat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a text holder object using provided function if it does not exist and returns it.
|
||||
*
|
||||
* @param convertFunction
|
||||
* Function for converting adventure Component to TextHolder
|
||||
* @return Converted TextHolder
|
||||
* @param <T>
|
||||
* TextHolder type
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@NotNull
|
||||
public <T> T toTextHolder(@NotNull Function<TabComponent, T> convertFunction) {
|
||||
if (textHolder == null) textHolder = convertFunction.apply(this);
|
||||
return (T) textHolder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns last color of this component. This value is cached. If no color is used, WHITE color is returned.
|
||||
*
|
||||
* @return Last color of this component
|
||||
*/
|
||||
@NotNull
|
||||
public TextColor getLastColor() {
|
||||
if (lastColor == null) {
|
||||
lastColor = fetchLastColor();
|
||||
if (lastColor == null) lastColor = TextColor.WHITE;
|
||||
}
|
||||
return lastColor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts this component into a simple text with legacy colors (the closest match if color is set to RGB)
|
||||
*
|
||||
* @return The simple text format using legacy colors
|
||||
*/
|
||||
@NotNull
|
||||
public abstract String toLegacyText();
|
||||
|
||||
/**
|
||||
* Computes and returns the last used color code in this component.
|
||||
* If no color is present, {@code null} is returned.
|
||||
*
|
||||
* @return Last color of this component, {@code null} if no colors are used
|
||||
*/
|
||||
@Nullable
|
||||
protected TextColor fetchLastColor() {
|
||||
TextColor lastColor = modifier.getColor();
|
||||
for (TabComponent extra : getExtra()) {
|
||||
TextColor color = extra.fetchLastColor();
|
||||
if (color != null) {
|
||||
lastColor = color;
|
||||
}
|
||||
}
|
||||
return lastColor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts this component into a string that only consists of text without any formatting.
|
||||
*
|
||||
* @return String containing text of the component and extras
|
||||
*/
|
||||
@NotNull
|
||||
public String toRawText() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
if (this instanceof TextComponent) builder.append(((TextComponent)this).getText());
|
||||
for (TabComponent extra : getExtra()) {
|
||||
if (extra instanceof TextComponent) {
|
||||
builder.append(((TextComponent) extra).getText());
|
||||
}
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns organized component from colored text
|
||||
*
|
||||
* @param originalText
|
||||
* text to convert
|
||||
* @return organized component from colored text
|
||||
*/
|
||||
@NotNull
|
||||
public static TextComponent fromColoredText(@NotNull String originalText) {
|
||||
String remainingText = originalText;
|
||||
List<TabComponent> components = new ArrayList<>();
|
||||
while (!remainingText.isEmpty()) {
|
||||
Matcher m = fontPattern.matcher(remainingText);
|
||||
if (m.find()) {
|
||||
if (m.start() > 0) {
|
||||
// Something is before the text with font, process normally
|
||||
components.addAll(toComponentArray(remainingText.substring(0, m.start()), null));
|
||||
}
|
||||
// Process text with font
|
||||
String match = m.group();
|
||||
components.addAll(toComponentArray(
|
||||
match.substring(match.indexOf('>')+1, match.length()-7),
|
||||
match.substring(6, match.indexOf('>'))
|
||||
));
|
||||
// Prepare the rest for next loop
|
||||
remainingText = remainingText.substring(m.start() + match.length());
|
||||
} else {
|
||||
components.addAll(toComponentArray(remainingText, null));
|
||||
break;
|
||||
}
|
||||
}
|
||||
return new TextComponent("", components);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static List<TextComponent> toComponentArray(@NotNull String originalText, @Nullable String font) {
|
||||
String text = RGBUtils.getInstance().applyFormats(EnumChatFormat.color(originalText), TABGradientFormatter, TABRGBFormatter);
|
||||
List<TextComponent> components = new ArrayList<>();
|
||||
StringBuilder builder = new StringBuilder();
|
||||
TextComponent component = new TextComponent();
|
||||
component.modifier.setFont(font);
|
||||
for (int i = 0; i < text.length(); i++) {
|
||||
char c = text.charAt(i);
|
||||
if (c == '§') {
|
||||
i++;
|
||||
if (i >= text.length()) {
|
||||
break;
|
||||
}
|
||||
c = text.charAt(i);
|
||||
if ((c >= 'A') && (c <= 'Z')) {
|
||||
c = (char)(c + ' ');
|
||||
}
|
||||
TextColor format = TextColor.getLegacyByChar(c);
|
||||
if (format != null) {
|
||||
if (builder.length() > 0) {
|
||||
component.setText(builder.toString());
|
||||
components.add(component);
|
||||
component = new TextComponent(component);
|
||||
component.setText("");
|
||||
component.modifier.setFont(font);
|
||||
builder = new StringBuilder();
|
||||
}
|
||||
if (format == TextColor.BOLD) {
|
||||
component.modifier.setBold(true);
|
||||
} else if (format == TextColor.ITALIC) {
|
||||
component.modifier.setItalic(true);
|
||||
} else if (format == TextColor.UNDERLINE) {
|
||||
component.modifier.setUnderlined(true);
|
||||
} else if (format == TextColor.STRIKETHROUGH) {
|
||||
component.modifier.setStrikethrough(true);
|
||||
} else if (format == TextColor.OBFUSCATED) {
|
||||
component.modifier.setObfuscated(true);
|
||||
} else if (format == TextColor.RESET) {
|
||||
component = new TextComponent();
|
||||
component.modifier.setColor(TextColor.WHITE);
|
||||
component.modifier.setFont(font);
|
||||
} else {
|
||||
component = new TextComponent();
|
||||
component.modifier.setColor(format);
|
||||
component.modifier.setFont(font);
|
||||
}
|
||||
}
|
||||
} else if (c == '#' && text.length() > i+6) {
|
||||
String hex = text.substring(i+1, i+7);
|
||||
if (isHexCode(hex)) {
|
||||
TextColor color = new TextColor(hex);
|
||||
i += 6;
|
||||
if (builder.length() > 0) {
|
||||
component.setText(builder.toString());
|
||||
components.add(component);
|
||||
builder = new StringBuilder();
|
||||
}
|
||||
component = new TextComponent();
|
||||
component.modifier.setColor(color);
|
||||
component.modifier.setFont(font);
|
||||
} else {
|
||||
builder.append('#');
|
||||
}
|
||||
} else {
|
||||
builder.append(c);
|
||||
}
|
||||
}
|
||||
component.setText(builder.toString());
|
||||
components.add(component);
|
||||
return components;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if entered string is a valid 6-digit combination of
|
||||
* hexadecimal numbers, false if not
|
||||
*
|
||||
* @param string
|
||||
* string to check
|
||||
* @return {@code true} if valid, {@code false} if not
|
||||
*/
|
||||
private static boolean isHexCode(@NotNull String string) {
|
||||
for (int i=0; i<string.length(); i++) {
|
||||
if ("0123456789AaBbCcDdEeFf".indexOf(string.charAt(i)) == -1) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
package me.neznamy.chat.component;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import me.neznamy.chat.ChatModifier;
|
||||
import me.neznamy.chat.EnumChatFormat;
|
||||
import me.neznamy.chat.TextColor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A component of "text" type that contains text to display.
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class TextComponent extends TabComponent {
|
||||
|
||||
@NotNull
|
||||
protected String text;
|
||||
|
||||
/**
|
||||
* Constructs new instance using given text and extra components.
|
||||
*
|
||||
* @param text
|
||||
* Component text
|
||||
* @param extra
|
||||
* Extra components
|
||||
*/
|
||||
public TextComponent(@NotNull String text, List<TabComponent> extra) {
|
||||
this.text = text;
|
||||
super.extra = extra;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new instance which clones existing component.
|
||||
*
|
||||
* @param component
|
||||
* Component to clone
|
||||
*/
|
||||
public TextComponent(@NotNull TextComponent component) {
|
||||
text = component.text;
|
||||
modifier = new ChatModifier(component.modifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs new instance with given text and color.
|
||||
*
|
||||
* @param text
|
||||
* Component text
|
||||
* @param color
|
||||
* Text color
|
||||
*/
|
||||
public TextComponent(@NotNull String text, @Nullable TextColor color) {
|
||||
this.text = text;
|
||||
modifier.setColor(color);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String toLegacyText() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
append(builder, "");
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends text to string builder, might also add color and magic codes if they are different
|
||||
* from the previous component in the chain.
|
||||
*
|
||||
* @param builder
|
||||
* builder to append text to
|
||||
* @param previousFormatting
|
||||
* colors and magic codes in previous component
|
||||
* @return the new formatting, it might be identical to the previous one
|
||||
*/
|
||||
@NotNull
|
||||
private String append(@NotNull StringBuilder builder, @NotNull String previousFormatting) {
|
||||
String formatting = getFormatting();
|
||||
if (!formatting.equals(previousFormatting)) {
|
||||
builder.append(formatting);
|
||||
}
|
||||
builder.append(text);
|
||||
for (TabComponent component : getExtra()) {
|
||||
if (component instanceof TextComponent) {
|
||||
formatting = ((TextComponent) component).append(builder, formatting);
|
||||
}
|
||||
}
|
||||
return formatting;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns colors and magic codes of this component
|
||||
*
|
||||
* @return used colors and magic codes
|
||||
*/
|
||||
private @NotNull String getFormatting() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
if (modifier.getColor() != null) {
|
||||
builder.append("§");
|
||||
if (modifier.getColor().getLegacyColor() == EnumChatFormat.WHITE) {
|
||||
//preventing unwanted &r → &f conversion and stopping the <1.13 client bug fix from working
|
||||
builder.append("r");
|
||||
} else {
|
||||
builder.append(modifier.getColor().getLegacyColor().getCharacter());
|
||||
}
|
||||
}
|
||||
builder.append(modifier.getMagicCodes());
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package me.neznamy.chat.component;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* A component of "translate" type that contains key to translate.
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public class TranslatableComponent extends TabComponent {
|
||||
|
||||
@NotNull
|
||||
protected final String key;
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String toLegacyText() {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
package me.neznamy.chat.hook;
|
||||
|
||||
import me.neznamy.chat.ChatModifier;
|
||||
import me.neznamy.chat.component.KeybindComponent;
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import me.neznamy.chat.component.TextComponent;
|
||||
import me.neznamy.chat.component.TranslatableComponent;
|
||||
import net.kyori.adventure.key.Key;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.Style;
|
||||
import net.kyori.adventure.text.format.TextColor;
|
||||
import net.kyori.adventure.text.format.TextDecoration;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Class for Adventure component conversion.
|
||||
*/
|
||||
public class AdventureHook {
|
||||
|
||||
/** Flag for tracking presence of shadow color parameter in current included adventure library (added in 1.21.4) */
|
||||
private static final boolean SHADOW_COLOR_AVAILABLE;
|
||||
|
||||
static {
|
||||
boolean value;
|
||||
try {
|
||||
Component.class.getDeclaredMethod("shadowColor");
|
||||
value = true;
|
||||
} catch (Throwable t) {
|
||||
value = false;
|
||||
}
|
||||
SHADOW_COLOR_AVAILABLE = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts TAB component to adventure component
|
||||
*
|
||||
* @param component
|
||||
* Component to convert
|
||||
* @return Adventure component from TAB component.
|
||||
*/
|
||||
@NotNull
|
||||
public static Component convert(@NotNull TabComponent component) {
|
||||
// Component style
|
||||
Style.Builder style = Style.style()
|
||||
.color(component.getModifier().getColor() == null ? null : TextColor.color(component.getModifier().getColor().getRgb()))
|
||||
.decoration(TextDecoration.BOLD, getDecoration(component.getModifier().getBold()))
|
||||
.decoration(TextDecoration.ITALIC, getDecoration(component.getModifier().getItalic()))
|
||||
.decoration(TextDecoration.UNDERLINED, getDecoration(component.getModifier().getUnderlined()))
|
||||
.decoration(TextDecoration.STRIKETHROUGH, getDecoration(component.getModifier().getStrikethrough()))
|
||||
.decoration(TextDecoration.OBFUSCATED, getDecoration(component.getModifier().getObfuscated()))
|
||||
.font(component.getModifier().getFont() == null ? null : Key.key(component.getModifier().getFont()));
|
||||
if (SHADOW_COLOR_AVAILABLE) {
|
||||
AdventureShadowHook.setShadowColor(style, component.getModifier().getShadowColor());
|
||||
}
|
||||
|
||||
// Extra
|
||||
List<Component> list = new ArrayList<>();
|
||||
for (TabComponent extra : component.getExtra()) {
|
||||
list.add(convert(extra));
|
||||
}
|
||||
|
||||
// Component type & return
|
||||
if (component instanceof TextComponent) {
|
||||
return Component.text(((TextComponent) component).getText(), style.build()).children(list);
|
||||
}
|
||||
if (component instanceof TranslatableComponent) {
|
||||
return Component.translatable(((TranslatableComponent) component).getKey(), style.build()).children(list);
|
||||
}
|
||||
if (component instanceof KeybindComponent) {
|
||||
return Component.keybind(((KeybindComponent) component).getKeybind(), style.build()).children(list);
|
||||
}
|
||||
throw new UnsupportedOperationException(component.getClass().getName() + " component type is not supported");
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static TextDecoration.State getDecoration(@Nullable Boolean state) {
|
||||
if (state == null) return TextDecoration.State.NOT_SET;
|
||||
return state ? TextDecoration.State.TRUE : TextDecoration.State.FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts adventure component to TAB component
|
||||
*
|
||||
* @param component
|
||||
* Component to convert
|
||||
* @return TAB component from adventure component.
|
||||
*/
|
||||
@NotNull
|
||||
public static TabComponent convert(@NotNull Component component) {
|
||||
// Component type
|
||||
TabComponent tabComponent;
|
||||
if (component instanceof net.kyori.adventure.text.TextComponent) {
|
||||
tabComponent = new TextComponent(((net.kyori.adventure.text.TextComponent) component).content());
|
||||
} else if (component instanceof net.kyori.adventure.text.TranslatableComponent) {
|
||||
tabComponent = new TranslatableComponent(((net.kyori.adventure.text.TranslatableComponent) component).key());
|
||||
} else if (component instanceof net.kyori.adventure.text.KeybindComponent) {
|
||||
tabComponent = new KeybindComponent(((net.kyori.adventure.text.KeybindComponent) component).keybind());
|
||||
} else {
|
||||
throw new UnsupportedOperationException(component.getClass().getName() + " component type is not supported");
|
||||
}
|
||||
|
||||
// Component style
|
||||
Map<TextDecoration, TextDecoration.State> decorations = component.style().decorations();
|
||||
tabComponent.setModifier(new ChatModifier(
|
||||
component.color() == null ? null : new me.neznamy.chat.TextColor(component.color().value()),
|
||||
SHADOW_COLOR_AVAILABLE ? AdventureShadowHook.getShadowColor(component) : null,
|
||||
getDecoration(decorations.get(TextDecoration.BOLD)),
|
||||
getDecoration(decorations.get(TextDecoration.ITALIC)),
|
||||
getDecoration(decorations.get(TextDecoration.UNDERLINED)),
|
||||
getDecoration(decorations.get(TextDecoration.STRIKETHROUGH)),
|
||||
getDecoration(decorations.get(TextDecoration.OBFUSCATED)),
|
||||
component.font() == null ? null : component.font().asString()
|
||||
));
|
||||
|
||||
// Extra
|
||||
for (Component extra : component.children()) {
|
||||
tabComponent.addExtra(convert(extra));
|
||||
}
|
||||
|
||||
// Save original component to prevent potential data loss and avoid redundant conversion
|
||||
tabComponent.setAdventureComponent(component);
|
||||
|
||||
return tabComponent;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static Boolean getDecoration(@Nullable TextDecoration.State state) {
|
||||
if (state == null || state == TextDecoration.State.NOT_SET) return null;
|
||||
return state == TextDecoration.State.TRUE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package me.neznamy.chat.hook;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.ShadowColor;
|
||||
import net.kyori.adventure.text.format.Style;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* A separate class for handling shadow color in adventure components.
|
||||
* It is required to be separate. Using static functions on interfaces causes
|
||||
* it to load on class initialization even if the code never runs, resulting in an error
|
||||
* when using an older version of the library before shadow color class was added.
|
||||
*/
|
||||
public class AdventureShadowHook {
|
||||
|
||||
/**
|
||||
* Returns shadow color of the component in ARGB format or {@code null} if not defined.
|
||||
*
|
||||
* @param component
|
||||
* Component to get shadow color of
|
||||
* @return Shadow color of the component in ARGB format or {@code null} if not defined
|
||||
*/
|
||||
@Nullable
|
||||
public static Integer getShadowColor(@NotNull Component component) {
|
||||
ShadowColor color = component.shadowColor();
|
||||
return color == null ? null : color.value();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets shadow color for the style in AGB format if it is not {@code null}.
|
||||
*
|
||||
* @param style
|
||||
* Style to set shadow color of
|
||||
* @param shadowColor
|
||||
* Shadow color in ARGB format or {@code null} for default
|
||||
*/
|
||||
public static void setShadowColor(@NotNull Style.Builder style, @Nullable Integer shadowColor) {
|
||||
if (shadowColor != null) {
|
||||
style.shadowColor(ShadowColor.shadowColor(shadowColor));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package me.neznamy.chat.rgb;
|
||||
|
||||
import lombok.Getter;
|
||||
import me.neznamy.chat.TextColor;
|
||||
import me.neznamy.chat.rgb.format.BukkitFormat;
|
||||
import me.neznamy.chat.rgb.format.CommonFormatter;
|
||||
import me.neznamy.chat.rgb.format.RGBFormatter;
|
||||
import me.neznamy.chat.rgb.gradient.CMIGradient;
|
||||
import me.neznamy.chat.rgb.gradient.CommonGradient;
|
||||
import me.neznamy.chat.rgb.gradient.GradientPattern;
|
||||
import me.neznamy.chat.rgb.gradient.NexEngineGradient;
|
||||
import me.neznamy.chat.util.TriFunction;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.function.Function;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* A helper class to reformat all RGB formats into the default #RRGGBB and apply gradients
|
||||
*/
|
||||
public class RGBUtils {
|
||||
|
||||
/** Instance of the class */
|
||||
@Getter private static final RGBUtils instance = new RGBUtils();
|
||||
|
||||
/** Registered RGB formatters */
|
||||
private final RGBFormatter[] formats = {
|
||||
new BukkitFormat(), // &x&r&r&g&g&b&b
|
||||
new CommonFormatter(Pattern.compile("\\{#[0-9a-fA-F]{6}}"), "{#"), // CMI's {#RRGGBB}
|
||||
new CommonFormatter(Pattern.compile("§#[0-9a-fA-F]{6}"), "§#"), // &#RRGGBB
|
||||
new CommonFormatter(Pattern.compile("#<[0-9a-fA-F]{6}>"), "#<") // #<RRGGBB> // "HTML"
|
||||
};
|
||||
|
||||
/** Registered gradient patterns */
|
||||
private final GradientPattern[] gradients = {
|
||||
new CMIGradient(), //{#RRGGBB>}Text{#RRGGBB<}
|
||||
new CommonGradient(Pattern.compile("<#[0-9a-fA-F]{6}>.*?</#[0-9a-fA-F]{6}>"), "<#", 2, 9, 7), //<#RRGGBB>Text</#RRGGBB> // "HTML"
|
||||
new CommonGradient(Pattern.compile("<\\$#[0-9a-fA-F]{6}>.*?<\\$#[0-9a-fA-F]{6}>"), "<$", 3, 10, 7), //<$#RRGGBB>Text<$#RRGGBB> // ?
|
||||
new NexEngineGradient() // <gradient:#RRGGBB>Text</gradient:#RRGGBB>
|
||||
};
|
||||
|
||||
/**
|
||||
* Applies all gradient formats to text and returns it.
|
||||
*
|
||||
* @param text
|
||||
* original text
|
||||
* @param gradientFunction
|
||||
* Function for reformatting gradient to new text
|
||||
* @param rgbFunction
|
||||
* Function for converting RGB codes to desired format
|
||||
* @return text where all gradients are converted to desired format
|
||||
*/
|
||||
@NotNull
|
||||
public String applyFormats(@NotNull String text, @NotNull TriFunction<TextColor, String, TextColor, String> gradientFunction,
|
||||
@NotNull Function<TextColor, String> rgbFunction) {
|
||||
String replaced = text;
|
||||
for (GradientPattern pattern : gradients) {
|
||||
replaced = pattern.applyPattern(replaced, gradientFunction);
|
||||
}
|
||||
for (RGBFormatter formatter : formats) {
|
||||
replaced = formatter.reformat(replaced, rgbFunction);
|
||||
}
|
||||
return replaced;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package me.neznamy.chat.rgb.format;
|
||||
|
||||
import me.neznamy.chat.TextColor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.function.Function;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Formatter for &x&R&R&G&G&B&B
|
||||
*/
|
||||
public class BukkitFormat implements RGBFormatter {
|
||||
|
||||
private final Pattern pattern = Pattern.compile("§x[§\\p{XDigit}]{12}");
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String reformat(@NotNull String text, @NotNull Function<TextColor, String> rgbFunction) {
|
||||
if (!text.contains("§x")) return text;
|
||||
String replaced = text;
|
||||
Matcher m = pattern.matcher(replaced);
|
||||
while (m.find()) {
|
||||
String group = m.group();
|
||||
String hexCode = new String(new char[] {group.charAt(3), group.charAt(5), group.charAt(7), group.charAt(9), group.charAt(11), group.charAt(13)});
|
||||
replaced = replaced.replace(group, rgbFunction.apply(new TextColor(hexCode)));
|
||||
}
|
||||
return replaced;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package me.neznamy.chat.rgb.format;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import me.neznamy.chat.TextColor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.function.Function;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Common class for various RGB formatters.
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class CommonFormatter implements RGBFormatter {
|
||||
|
||||
/** Pattern for finding the RGB code */
|
||||
private final Pattern pattern;
|
||||
|
||||
/** String to check if the text contains to possibly skip pattern matching for better performance */
|
||||
private final String stringCheck;
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String reformat(@NotNull String text, @NotNull Function<TextColor, String> rgbFunction) {
|
||||
if (!text.contains(stringCheck)) return text;
|
||||
String replaced = text;
|
||||
Matcher m = pattern.matcher(replaced);
|
||||
while (m.find()) {
|
||||
String group = m.group();
|
||||
String hexCode = group.substring(2, 8);
|
||||
replaced = replaced.replace(group, rgbFunction.apply(new TextColor(hexCode)));
|
||||
}
|
||||
return replaced;
|
||||
}
|
||||
}
|
||||
Loaded 100 of 249 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user