mirror of
https://github.com/NEZNAMY/TAB.git
synced 2026-11-12 20:22:01 -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 |
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,8 +1,8 @@
|
||||
[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.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]
|
||||
@@ -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,10 +1,10 @@
|
||||

|
||||
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)
|
||||
@@ -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.
|
||||
|
||||
@@ -32,7 +32,7 @@ It is compiled with Java 8 and therefore supports any version of 8 and above.
|
||||
</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>
|
||||
@@ -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,6 +131,7 @@ 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.
|
||||
* **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.
|
||||
@@ -133,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.3</version>
|
||||
<version>5.0.5</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
```
|
||||
@@ -40,7 +40,7 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly 'com.github.NEZNAMY:TAB-API:5.0.3'
|
||||
compileOnly 'com.github.NEZNAMY:TAB-API:5.0.5'
|
||||
}
|
||||
```
|
||||
|
||||
@@ -51,7 +51,7 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly("com.github.NEZNAMY", "TAB-API", "5.0.3")
|
||||
compileOnly("com.github.NEZNAMY", "TAB-API", "5.0.5")
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -28,14 +28,14 @@ The feature's properties are displayed as `[score]` + space + `title` (no, the s
|
||||
* `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+). 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 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.
|
||||
|
||||
@@ -107,12 +107,12 @@ 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
|
||||
|
||||
@@ -45,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.
|
||||
@@ -62,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 `-`.
|
||||
@@ -140,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:
|
||||
```
|
||||
@@ -162,7 +172,7 @@ to achieve colored ping.
|
||||
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 would be 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.
|
||||
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>
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
@@ -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
|
||||
|
||||
@@ -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.3**.
|
||||
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.
|
||||
|
||||
@@ -16,7 +16,7 @@ However, plugins hooking into TAB may break when you use such ways to (re)load i
|
||||
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)
|
||||
* Installing TAB everywhere and disabling features, so they don't conflict (not recommended)
|
||||
|
||||
See below for more information regarding each way.
|
||||
|
||||
@@ -24,11 +24,13 @@ See below for more information regarding each way.
|
||||
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**:
|
||||
If you are using **Velocity**, 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) and [Layout](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Layout) can work with all players connected to the proxy
|
||||
|
||||
@@ -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)).
|
||||
@@ -102,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 |
|
||||
@@ -156,19 +156,28 @@ Configured refresh intervals and CPU usage of placeholders can be checked using
|
||||
# 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:** 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%`.
|
||||
> [!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. A 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.*
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# About
|
||||
When TAB is installed on BungeeCord, the plugin has very limited access to information. It is unable to retrieve internal server data, as well as hook into backend plugins, because it's a different server.
|
||||
Bridge is a backend plugin that sends this data to the proxy, allowing TAB to work better when installed on BungeeCord.
|
||||
You can download it from [GitHub](https://github.com/NEZNAMY/TAB-Bridge/releases) or [Spigot](https://www.spigotmc.org/resources/83966).
|
||||
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.
|
||||
|
||||
@@ -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** | ✔ | ✔ | ✔ | ❌ | ✔ |
|
||||
@@ -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
|
||||
|
||||
+2
-2
@@ -4,7 +4,7 @@ plugins {
|
||||
|
||||
allprojects {
|
||||
group = "me.neznamy"
|
||||
version = "5.0.4"
|
||||
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 "2.0.0-beta.8"
|
||||
}
|
||||
|
||||
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.4-R0.1-SNAPSHOT"
|
||||
|
||||
dependencies {
|
||||
implementation(projects.bukkit)
|
||||
paperweight.paperDevBundle(version)
|
||||
compileOnly("io.papermc.paper:paper-api:${version}")
|
||||
}
|
||||
|
||||
tasks.compileJava {
|
||||
options.release.set(21)
|
||||
}
|
||||
-106
@@ -1,106 +0,0 @@
|
||||
package me.neznamy.tab.platforms.paper;
|
||||
|
||||
import me.neznamy.tab.platforms.bukkit.nms.converter.ComponentConverter;
|
||||
import me.neznamy.tab.shared.chat.*;
|
||||
import net.kyori.adventure.key.Key;
|
||||
import net.kyori.adventure.text.KeybindComponent;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import net.kyori.adventure.text.TranslatableComponent;
|
||||
import net.kyori.adventure.text.format.TextDecoration;
|
||||
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 org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
switch (component) {
|
||||
case SimpleComponent simpleComponent -> {
|
||||
return Component.literal(simpleComponent.getText());
|
||||
}
|
||||
case StructuredComponent component1 -> {
|
||||
MutableComponent nmsComponent = Component.literal(component1.getText());
|
||||
ChatModifier modifier = component1.getModifier();
|
||||
TextColor color = null;
|
||||
if (modifier.getColor() != null) {
|
||||
color = TextColor.fromRgb(modifier.getColor().getRgb());
|
||||
}
|
||||
nmsComponent.setStyle(newStyle(
|
||||
color,
|
||||
modifier.getBold(),
|
||||
modifier.getItalic(),
|
||||
modifier.getUnderlined(),
|
||||
modifier.getStrikethrough(),
|
||||
modifier.getObfuscated(),
|
||||
modifier.getFont()
|
||||
));
|
||||
for (StructuredComponent extra : component1.getExtra()) {
|
||||
nmsComponent.append(convert(extra));
|
||||
}
|
||||
return nmsComponent;
|
||||
}
|
||||
case AdventureComponent component1 -> {
|
||||
return fromAdventure(component1.getComponent());
|
||||
}
|
||||
default -> throw new IllegalStateException("Unexpected component type: " + component.getClass().getName());
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private Component fromAdventure(@NotNull net.kyori.adventure.text.Component component) {
|
||||
MutableComponent nmsComponent = switch (component) {
|
||||
case TextComponent text -> Component.literal(text.content());
|
||||
case TranslatableComponent translate -> Component.translatable(translate.key());
|
||||
case KeybindComponent keyBind -> Component.keybind(keyBind.keybind());
|
||||
default -> throw new IllegalStateException("Cannot convert " + component.getClass().getName());
|
||||
};
|
||||
|
||||
net.kyori.adventure.text.format.TextColor color = component.color();
|
||||
Key font = component.style().font();
|
||||
Map<TextDecoration, TextDecoration.State> decorations = component.style().decorations();
|
||||
nmsComponent.setStyle(newStyle(
|
||||
color == null ? null : TextColor.fromRgb(color.value()),
|
||||
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)),
|
||||
font == null ? null : font.asString()
|
||||
));
|
||||
for (net.kyori.adventure.text.Component extra : component.children()) {
|
||||
nmsComponent.append(fromAdventure(extra));
|
||||
}
|
||||
return nmsComponent;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Boolean getDecoration(@Nullable TextDecoration.State state) {
|
||||
if (state == null || state == TextDecoration.State.NOT_SET) return null;
|
||||
return state == TextDecoration.State.TRUE;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private Style newStyle(@Nullable TextColor color, @Nullable Boolean bold, @Nullable Boolean italic, @Nullable Boolean underlined,
|
||||
@Nullable Boolean strikethrough, @Nullable Boolean obfuscated, @Nullable String font) {
|
||||
return Style.EMPTY
|
||||
.withColor(color)
|
||||
.withBold(bold)
|
||||
.withItalic(italic)
|
||||
.withUnderlined(underlined)
|
||||
.withStrikethrough(strikethrough)
|
||||
.withObfuscated(obfuscated)
|
||||
.withFont(font == null ? null : ResourceLocation.tryParse(font));
|
||||
}
|
||||
}
|
||||
@@ -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(),
|
||||
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());
|
||||
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()),
|
||||
Optional.ofNullable(score.getNumberFormat() == null ? null : score.getNumberFormat().toFixedFormat(FixedFormat::new))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeScore(@NonNull Score score) {
|
||||
sendPacket(new ClientboundResetScorePacket(score.getHolder(), score.getObjective().getName()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Object createTeam(@NonNull String name) {
|
||||
return new PlayerTeam(dummyScoreboard, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerTeam(@NonNull Team team) {
|
||||
updateTeamProperties(team);
|
||||
PlayerTeam t = (PlayerTeam) team.getPlatformTeam();
|
||||
t.getPlayers().addAll(team.getPlayers());
|
||||
sendPacket(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(t, true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterTeam(@NonNull Team team) {
|
||||
sendPacket(ClientboundSetPlayerTeamPacket.createRemovePacket((PlayerTeam) team.getPlatformTeam()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateTeam(@NonNull Team team) {
|
||||
updateTeamProperties(team);
|
||||
sendPacket(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket((PlayerTeam) team.getPlatformTeam(), false));
|
||||
}
|
||||
|
||||
private void updateTeamProperties(@NonNull Team team) {
|
||||
PlayerTeam t = (PlayerTeam) team.getPlatformTeam();
|
||||
t.setAllowFriendlyFire((team.getOptions() & 0x01) != 0);
|
||||
t.setSeeFriendlyInvisibles((team.getOptions() & 0x02) != 0);
|
||||
t.setColor(formats[team.getColor().getLegacyColor().ordinal()]);
|
||||
t.setCollisionRule(collisions[team.getCollision().ordinal()]);
|
||||
t.setNameTagVisibility(visibilities[team.getVisibility().ordinal()]);
|
||||
t.setPlayerPrefix(team.getPrefix().convert());
|
||||
t.setPlayerSuffix(team.getSuffix().convert());
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void onPacketSend(@NonNull Object packet) {
|
||||
if (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,179 +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.4+.
|
||||
*/
|
||||
@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> updateHat = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_HAT);
|
||||
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, false, 0, null
|
||||
)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateLatency(@NonNull UUID entry, int latency) {
|
||||
sendPacket(new ClientboundPlayerInfoUpdatePacket(updateLatency, new ClientboundPlayerInfoUpdatePacket.Entry(
|
||||
entry, null, false, latency, null, null, false, 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, false, 0, null
|
||||
)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateListed(@NonNull UUID entry, boolean listed) {
|
||||
sendPacket(new ClientboundPlayerInfoUpdatePacket(updateListed, new ClientboundPlayerInfoUpdatePacket.Entry(
|
||||
entry, null, listed, 0, null, null, false, 0, null
|
||||
)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateListOrder(@NonNull UUID entry, int listOrder) {
|
||||
sendPacket(new ClientboundPlayerInfoUpdatePacket(updateListOrder, new ClientboundPlayerInfoUpdatePacket.Entry(
|
||||
entry, null, false, 0, null, null, false, listOrder, null
|
||||
)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateHat(@NonNull UUID entry, boolean showHat) {
|
||||
sendPacket(new ClientboundPlayerInfoUpdatePacket(updateHat, new ClientboundPlayerInfoUpdatePacket.Entry(
|
||||
entry, null, false, 0, null, null, showHat, 0, null
|
||||
)));
|
||||
}
|
||||
|
||||
@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, showHat, listOrder, null
|
||||
)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPlayerListHeaderFooter(@NonNull TabComponent header, @NonNull TabComponent footer) {
|
||||
sendPacket(new ClientboundTabListPacket(header.convert(), footer.convert()));
|
||||
}
|
||||
|
||||
@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.showHat(), 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,32 +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,
|
||||
ProtocolVersion.V1_21_4
|
||||
);
|
||||
|
||||
/** Function for getting player's channel */
|
||||
@Setter
|
||||
private static FunctionWithException<BukkitTabPlayer, Channel> getChannel;
|
||||
@@ -43,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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
-25
@@ -1,30 +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,
|
||||
ProtocolVersion.V1_21_4
|
||||
);
|
||||
|
||||
/** Instance of this class */
|
||||
@Nullable
|
||||
public static ComponentConverter INSTANCE;
|
||||
@@ -37,20 +27,94 @@ public abstract class ComponentConverter {
|
||||
* @return Converted component
|
||||
*/
|
||||
@NotNull
|
||||
public abstract Object convert(@NotNull TabComponent component);
|
||||
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);
|
||||
}
|
||||
}
|
||||
-228
@@ -1,228 +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.*;
|
||||
import me.neznamy.tab.shared.util.FunctionWithException;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import net.kyori.adventure.key.Key;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.KeybindComponent;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import net.kyori.adventure.text.TranslatableComponent;
|
||||
import net.kyori.adventure.text.format.TextDecoration;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* 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 FunctionWithException<String, Object> newTranslatableComponent;
|
||||
private final FunctionWithException<String, Object> newKeybindComponent;
|
||||
private final Function<ChatModifier, 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);
|
||||
|
||||
Method IChatBaseComponent_c = ReflectionUtils.getMethod(IChatBaseComponent, new String[] {"c", "translatable"}, String.class);
|
||||
newTranslatableComponent = text -> IChatBaseComponent_c.invoke(null, text);
|
||||
|
||||
Method IChatBaseComponent_d = ReflectionUtils.getMethod(IChatBaseComponent, new String[] {"d", "keybind"}, String.class);
|
||||
newKeybindComponent = text -> IChatBaseComponent_d.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<?> ChatMessage = BukkitReflection.getClass("network.chat.TranslatableComponent", "network.chat.ChatMessage", "ChatMessage");
|
||||
Constructor<?> newChatMessage = ChatMessage.getConstructor(String.class, Object[].class);
|
||||
newTranslatableComponent = text -> newChatMessage.newInstance(text, new Object[0]);
|
||||
|
||||
newKeybindComponent = text -> {
|
||||
throw new UnsupportedOperationException("Keybind component conversion is not implemented");
|
||||
};
|
||||
|
||||
Class<?> ChatBaseComponent = BukkitReflection.getClass("network.chat.BaseComponent", "network.chat.ChatBaseComponent", "ChatBaseComponent");
|
||||
Component_modifier = ReflectionUtils.getOnlyField(ChatBaseComponent, ChatModifier);
|
||||
ChatBaseComponent_addSibling = ReflectionUtils.getOnlyMethod(ChatBaseComponent, 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 = this::createModifierLegacy;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
@NotNull
|
||||
public Object convert(@NotNull TabComponent component) {
|
||||
if (component instanceof SimpleComponent) {
|
||||
return newTextComponent.apply(((SimpleComponent) component).getText());
|
||||
} else if (component instanceof StructuredComponent) {
|
||||
StructuredComponent component1 = (StructuredComponent) component;
|
||||
Object nmsComponent = newTextComponent.apply(component1.getText());
|
||||
Component_modifier.set(nmsComponent, convertModifier.apply(component1.getModifier()));
|
||||
for (StructuredComponent extra : component1.getExtra()) {
|
||||
ChatBaseComponent_addSibling.invoke(nmsComponent, convert(extra));
|
||||
}
|
||||
return nmsComponent;
|
||||
} else {
|
||||
return fromAdventure(((AdventureComponent)component).getComponent());
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
@NotNull
|
||||
private Object fromAdventure(@NotNull Component component) {
|
||||
Object nmsComponent;
|
||||
if (component instanceof TextComponent) {
|
||||
nmsComponent = newTextComponent.apply(((TextComponent) component).content());
|
||||
} else if (component instanceof TranslatableComponent) {
|
||||
nmsComponent = newTranslatableComponent.apply(((TranslatableComponent)component).key());
|
||||
} else if (component instanceof KeybindComponent) {
|
||||
nmsComponent = newKeybindComponent.apply(((KeybindComponent)component).keybind());
|
||||
} else {
|
||||
throw new IllegalStateException("Cannot convert " + component.getClass().getName());
|
||||
}
|
||||
|
||||
net.kyori.adventure.text.format.TextColor color = component.color();
|
||||
Key font = component.style().font();
|
||||
Map<TextDecoration, TextDecoration.State> decorations = component.style().decorations();
|
||||
Component_modifier.set(nmsComponent, newStyleModern(
|
||||
color == null ? null : ChatHexColor_fromRGB.invoke(null, color.value()),
|
||||
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)),
|
||||
font == null ? null : font.asString()
|
||||
));
|
||||
for (Component extra : component.children()) {
|
||||
ChatBaseComponent_addSibling.invoke(nmsComponent, fromAdventure(extra));
|
||||
}
|
||||
return nmsComponent;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Boolean getDecoration(@Nullable TextDecoration.State state) {
|
||||
if (state == null || state == TextDecoration.State.NOT_SET) return null;
|
||||
return state == TextDecoration.State.TRUE;
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private Object createModifierModern(@NotNull ChatModifier modifier) {
|
||||
return newStyleModern(
|
||||
modifier.getColor() == null ? null : ChatHexColor_fromRGB.invoke(null, modifier.getColor().getRgb()),
|
||||
modifier.getBold(),
|
||||
modifier.getItalic(),
|
||||
modifier.getUnderlined(),
|
||||
modifier.getStrikethrough(),
|
||||
modifier.getObfuscated(),
|
||||
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()));
|
||||
}
|
||||
magicCodes.get(0).set(nmsModifier, modifier.getBold());
|
||||
magicCodes.get(1).set(nmsModifier, modifier.getItalic());
|
||||
magicCodes.get(2).set(nmsModifier, modifier.getStrikethrough());
|
||||
magicCodes.get(3).set(nmsModifier, modifier.getUnderlined());
|
||||
magicCodes.get(4).set(nmsModifier, modifier.getObfuscated());
|
||||
return nmsModifier;
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
@NotNull
|
||||
private Object newStyleModern(@Nullable Object color, @Nullable Boolean bold, @Nullable Boolean italic, @Nullable Boolean underlined,
|
||||
@Nullable Boolean strikethrough, @Nullable Boolean obfuscated, @Nullable String font) {
|
||||
if (BukkitReflection.is1_21_4Plus()) {
|
||||
return newChatModifier.newInstance(
|
||||
color,
|
||||
null,
|
||||
bold,
|
||||
italic,
|
||||
underlined,
|
||||
strikethrough,
|
||||
obfuscated,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
font == null ? null : ResourceLocation_tryParse.invoke(null, font)
|
||||
);
|
||||
} else {
|
||||
return newChatModifier.newInstance(
|
||||
color,
|
||||
bold,
|
||||
italic,
|
||||
underlined,
|
||||
strikethrough,
|
||||
obfuscated,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
font == null ? null : ResourceLocation_tryParse.invoke(null, font)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,7 +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.*;
|
||||
import me.neznamy.tab.shared.features.PerWorldPlayerListConfiguration;
|
||||
import me.neznamy.tab.shared.features.PlaceholderManagerImpl;
|
||||
import me.neznamy.tab.shared.features.injection.PipelineInjector;
|
||||
@@ -38,7 +38,6 @@ import me.neznamy.tab.shared.platform.impl.DummyBossBar;
|
||||
import me.neznamy.tab.shared.util.PerformanceUtil;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import net.kyori.adventure.audience.Audience;
|
||||
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
|
||||
import net.milkbowl.vault.chat.Chat;
|
||||
import net.milkbowl.vault.permission.Permission;
|
||||
import org.bstats.bukkit.Metrics;
|
||||
@@ -99,12 +98,12 @@ 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] §7Loaded NMS hook in " + (System.currentTimeMillis()-time) + "ms");
|
||||
@@ -233,7 +232,7 @@ public class BukkitPlatform implements BackendPlatform {
|
||||
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?"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -254,7 +253,7 @@ 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);
|
||||
} else {
|
||||
@@ -307,6 +306,11 @@ public class BukkitPlatform implements BackendPlatform {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSafeFromPacketEventsBug() {
|
||||
return serverVersion.getMinorVersion() >= 13;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public GroupManager detectPermissionPlugin() {
|
||||
@@ -361,29 +365,30 @@ public class BukkitPlatform implements BackendPlatform {
|
||||
*/
|
||||
@NotNull
|
||||
public String toBukkitFormat(@NotNull TabComponent component) {
|
||||
if (component instanceof SimpleComponent) {
|
||||
return ((SimpleComponent) component).getText();
|
||||
}
|
||||
if (component instanceof StructuredComponent) {
|
||||
StructuredComponent iComponent = (StructuredComponent) component;
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (iComponent.getModifier().getColor() != null) {
|
||||
if (serverVersion.supportsRGB()) {
|
||||
String hexCode = iComponent.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().getFormat());
|
||||
}
|
||||
StringBuilder sb = new StringBuilder();
|
||||
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('§').append(component.getModifier().getColor().getLegacyColor().getCharacter());
|
||||
}
|
||||
sb.append(iComponent.getModifier().getMagicCodes());
|
||||
sb.append(iComponent.getText());
|
||||
for (StructuredComponent extra : iComponent.getExtra()) {
|
||||
sb.append(toBukkitFormat(extra));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
return LegacyComponentSerializer.builder().hexColors().useUnusualXRepeatedCharacterHexFormat().build().serialize(((AdventureComponent)component).getComponent());
|
||||
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
|
||||
|
||||
+1
-1
@@ -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;
|
||||
|
||||
+1
-1
@@ -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;
|
||||
|
||||
+3
-26
@@ -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,17 +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,
|
||||
ProtocolVersion.V1_21_4
|
||||
);
|
||||
|
||||
/** Instance function */
|
||||
@Getter
|
||||
@Setter
|
||||
@@ -43,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;
|
||||
|
||||
+8
-2
@@ -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;
|
||||
@@ -232,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;
|
||||
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ 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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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,11 +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_4
|
||||
);
|
||||
|
||||
/** Instance function */
|
||||
@Getter
|
||||
@Setter
|
||||
@@ -52,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,7 +10,8 @@ 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.platform.BossBar;
|
||||
@@ -20,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
|
||||
@@ -43,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.
|
||||
*
|
||||
@@ -62,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) {
|
||||
@@ -134,42 +137,80 @@ public class BungeePlatform extends ProxyPlatform {
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public BaseComponent convertComponent(@NotNull TabComponent component, boolean modern) {
|
||||
if (component instanceof SimpleComponent) {
|
||||
return new TextComponent(((SimpleComponent) component).getText());
|
||||
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);
|
||||
}
|
||||
if (component instanceof StructuredComponent) {
|
||||
StructuredComponent iComponent = (StructuredComponent) component;
|
||||
TextComponent textComponent = new TextComponent(iComponent.getText());
|
||||
ChatModifier modifier = iComponent.getModifier();
|
||||
if (modifier.getColor() != null) {
|
||||
if (modern) {
|
||||
textComponent.setColor(ChatColor.of("#" + modifier.getColor().getHexCode()));
|
||||
} else {
|
||||
textComponent.setColor(ChatColor.of(modifier.getColor().getLegacyColor().name()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
textComponent.setBold(modifier.getBold());
|
||||
textComponent.setItalic(modifier.getItalic());
|
||||
textComponent.setObfuscated(modifier.getObfuscated());
|
||||
textComponent.setStrikethrough(modifier.getStrikethrough());
|
||||
textComponent.setUnderlined(modifier.getUnderlined());
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
return textComponent;
|
||||
/**
|
||||
* 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());
|
||||
}
|
||||
throw new UnsupportedOperationException("Adventure components created using MiniMessage syntax are not supported on BungeeCord. " +
|
||||
"You can request the implementation if you ran into this error.");
|
||||
|
||||
// Component style
|
||||
ChatModifier modifier = component.getModifier();
|
||||
if (modifier.getColor() != null) {
|
||||
if (modern) {
|
||||
bComponent.setColor(ChatColor.of("#" + modifier.getColor().getHexCode()));
|
||||
} else {
|
||||
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());
|
||||
|
||||
// Extra
|
||||
for (TabComponent extra : component.getExtra()) {
|
||||
bComponent.addExtra(createComponent(extra, modern));
|
||||
}
|
||||
|
||||
return bComponent;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -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;
|
||||
@@ -74,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())
|
||||
));
|
||||
}
|
||||
@@ -113,11 +113,11 @@ public class BungeeScoreboard extends SafeScoreboard<BungeeTabPlayer> {
|
||||
player.sendPacket(new net.md_5.bungee.protocol.packet.Team(
|
||||
team.getName(),
|
||||
action,
|
||||
either(new SimpleComponent(team.getName()), Limitations.TEAM_PREFIX_SUFFIX_PRE_1_13),
|
||||
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(),
|
||||
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(),
|
||||
team.getPlayers().toArray(new String[0])
|
||||
@@ -147,7 +147,7 @@ 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,8 +1,8 @@
|
||||
package me.neznamy.tab.platforms.bungeecord;
|
||||
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
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.
|
||||
@@ -11,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("§cThe 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;
|
||||
@@ -32,7 +33,14 @@ public class BungeeTabCommand extends Command implements TabExecutor {
|
||||
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")
|
||||
}
|
||||
+10
-20
@@ -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;
|
||||
@Nullable private Integer shadowColor; // ARGB
|
||||
@Nullable private Boolean bold;
|
||||
@Nullable private Boolean italic;
|
||||
@Nullable private Boolean obfuscated;
|
||||
@Nullable private Boolean strikethrough;
|
||||
@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;
|
||||
@@ -51,19 +56,4 @@ public class ChatModifier {
|
||||
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 (Boolean.TRUE.equals(bold)) mask += 1;
|
||||
if (Boolean.TRUE.equals(italic)) mask += 2;
|
||||
if (Boolean.TRUE.equals(obfuscated)) mask += 4;
|
||||
if (Boolean.TRUE.equals(strikethrough)) mask += 8;
|
||||
if (Boolean.TRUE.equals(underlined)) mask += 16;
|
||||
return mask;
|
||||
}
|
||||
}
|
||||
+3
-53
@@ -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,58 +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();
|
||||
|
||||
/** 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 = "§" + 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.
|
||||
@@ -130,9 +85,4 @@ public enum EnumChatFormat {
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return format;
|
||||
}
|
||||
}
|
||||
+50
-12
@@ -1,17 +1,43 @@
|
||||
package me.neznamy.tab.shared.chat;
|
||||
package me.neznamy.chat;
|
||||
|
||||
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).
|
||||
@@ -50,6 +76,17 @@ public class TextColor {
|
||||
rgb = legacyColor.getRgb();
|
||||
this.legacyColor = legacyColor;
|
||||
hexCode = String.format("%06X", legacyColor.getRgb());
|
||||
LEGACY_COLORS.put(legacyColor.getCharacter(), this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs new instance with given RGB value.
|
||||
*
|
||||
* @param rgb
|
||||
* RGB value
|
||||
*/
|
||||
public TextColor(int rgb) {
|
||||
this.rgb = rgb;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -73,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());
|
||||
@@ -82,7 +119,7 @@ public class TextColor {
|
||||
if (bDiff > maxDist) maxDist = bDiff;
|
||||
if (maxDist < minMaxDist) {
|
||||
minMaxDist = maxDist;
|
||||
closestColor = color;
|
||||
closestColor = color.legacyColor;
|
||||
}
|
||||
}
|
||||
return closestColor;
|
||||
@@ -150,13 +187,14 @@ public class TextColor {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
+127
-82
@@ -1,16 +1,20 @@
|
||||
package me.neznamy.tab.shared.chat;
|
||||
package me.neznamy.chat.component;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.chat.rgb.RGBUtils;
|
||||
import me.neznamy.tab.shared.hook.AdventureHook;
|
||||
import me.neznamy.tab.shared.util.FunctionWithException;
|
||||
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;
|
||||
@@ -21,17 +25,48 @@ import java.util.regex.Pattern;
|
||||
*/
|
||||
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 convertedModern;
|
||||
|
||||
@Nullable
|
||||
private Object convertedLegacy;
|
||||
private Object converted;
|
||||
|
||||
/** Adventure component from this component */
|
||||
@Nullable
|
||||
@Setter
|
||||
private Component adventureComponent;
|
||||
|
||||
@Nullable
|
||||
@@ -49,25 +84,34 @@ public abstract class TabComponent {
|
||||
@Nullable
|
||||
private TextColor lastColor;
|
||||
|
||||
/**
|
||||
* Converts this component to platform's component.
|
||||
*
|
||||
* @param clientVersion
|
||||
* Client version
|
||||
* @return Converted component
|
||||
* @param <T>
|
||||
* Platform's component class
|
||||
*/
|
||||
/** Chat modifier containing color, magic codes, hover and click event */
|
||||
@NotNull
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T convert(@NotNull ProtocolVersion clientVersion) {
|
||||
if (clientVersion.supportsRGB()) {
|
||||
if (convertedModern == null) convertedModern = TAB.getInstance().getPlatform().convertComponent(this, true);
|
||||
return (T) convertedModern;
|
||||
} else {
|
||||
if (convertedLegacy == null) convertedLegacy = TAB.getInstance().getPlatform().convertComponent(this, false);
|
||||
return (T) convertedLegacy;
|
||||
}
|
||||
@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);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -80,8 +124,9 @@ public abstract class TabComponent {
|
||||
@NotNull
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T convert() {
|
||||
if (convertedModern == null) convertedModern = TAB.getInstance().getPlatform().convertComponent(this, true);
|
||||
return (T) convertedModern;
|
||||
if (CONVERT_FUNCTION == null) throw new IllegalStateException("Convert function is not initialized");
|
||||
if (converted == null) converted = CONVERT_FUNCTION.apply(this);
|
||||
return (T) converted;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -90,7 +135,7 @@ public abstract class TabComponent {
|
||||
*/
|
||||
@NotNull
|
||||
public Component toAdventure() {
|
||||
if (adventureComponent == null) adventureComponent = AdventureHook.toAdventureComponent(this);
|
||||
if (adventureComponent == null) adventureComponent = AdventureHook.convert(this);
|
||||
return adventureComponent;
|
||||
}
|
||||
|
||||
@@ -108,7 +153,7 @@ public abstract class TabComponent {
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@SneakyThrows
|
||||
public <F, C> F toFixedFormat(@NotNull FunctionWithException<C, F> createFunction) {
|
||||
public <F, C> F toFixedFormat(@NotNull Function<C, F> createFunction) {
|
||||
if (fixedFormat == null) fixedFormat = createFunction.apply(convert());
|
||||
return (F) fixedFormat;
|
||||
}
|
||||
@@ -138,7 +183,7 @@ public abstract class TabComponent {
|
||||
public TextColor getLastColor() {
|
||||
if (lastColor == null) {
|
||||
lastColor = fetchLastColor();
|
||||
if (lastColor == null) lastColor = TextColor.legacy(EnumChatFormat.WHITE);
|
||||
if (lastColor == null) lastColor = TextColor.WHITE;
|
||||
}
|
||||
return lastColor;
|
||||
}
|
||||
@@ -158,7 +203,16 @@ public abstract class TabComponent {
|
||||
* @return Last color of this component, {@code null} if no colors are used
|
||||
*/
|
||||
@Nullable
|
||||
protected abstract TextColor fetchLastColor();
|
||||
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.
|
||||
@@ -167,11 +221,14 @@ public abstract class TabComponent {
|
||||
*/
|
||||
@NotNull
|
||||
public String toRawText() {
|
||||
String text = toLegacyText();
|
||||
for (EnumChatFormat format : EnumChatFormat.VALUES) {
|
||||
if (text.contains(format.toString())) text = text.replace(format.toString(), "");
|
||||
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 text;
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -182,9 +239,9 @@ public abstract class TabComponent {
|
||||
* @return organized component from colored text
|
||||
*/
|
||||
@NotNull
|
||||
public static TabComponent fromColoredText(@NotNull String originalText) {
|
||||
public static TextComponent fromColoredText(@NotNull String originalText) {
|
||||
String remainingText = originalText;
|
||||
List<StructuredComponent> components = new ArrayList<>();
|
||||
List<TabComponent> components = new ArrayList<>();
|
||||
while (!remainingText.isEmpty()) {
|
||||
Matcher m = fontPattern.matcher(remainingText);
|
||||
if (m.find()) {
|
||||
@@ -205,20 +262,16 @@ public abstract class TabComponent {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (components.isEmpty()) {
|
||||
return new SimpleComponent("");
|
||||
} else {
|
||||
return new StructuredComponent("", components);
|
||||
}
|
||||
return new TextComponent("", components);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static List<StructuredComponent> toComponentArray(@NotNull String originalText, @Nullable String font) {
|
||||
String text = RGBUtils.getInstance().applyFormats(EnumChatFormat.color(originalText));
|
||||
List<StructuredComponent> components = new ArrayList<>();
|
||||
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();
|
||||
StructuredComponent component = new StructuredComponent();
|
||||
component.getModifier().setFont(font);
|
||||
TextComponent component = new TextComponent();
|
||||
component.modifier.setFont(font);
|
||||
for (int i = 0; i < text.length(); i++) {
|
||||
char c = text.charAt(i);
|
||||
if (c == '§') {
|
||||
@@ -230,42 +283,34 @@ public abstract class TabComponent {
|
||||
if ((c >= 'A') && (c <= 'Z')) {
|
||||
c = (char)(c + ' ');
|
||||
}
|
||||
EnumChatFormat format = EnumChatFormat.getByChar(c);
|
||||
TextColor format = TextColor.getLegacyByChar(c);
|
||||
if (format != null) {
|
||||
if (builder.length() > 0) {
|
||||
component.setText(builder.toString());
|
||||
components.add(component);
|
||||
component = new StructuredComponent(component);
|
||||
component = new TextComponent(component);
|
||||
component.setText("");
|
||||
component.getModifier().setFont(font);
|
||||
component.modifier.setFont(font);
|
||||
builder = new StringBuilder();
|
||||
}
|
||||
switch (format) {
|
||||
case BOLD:
|
||||
component.getModifier().setBold(true);
|
||||
break;
|
||||
case ITALIC:
|
||||
component.getModifier().setItalic(true);
|
||||
break;
|
||||
case UNDERLINE:
|
||||
component.getModifier().setUnderlined(true);
|
||||
break;
|
||||
case STRIKETHROUGH:
|
||||
component.getModifier().setStrikethrough(true);
|
||||
break;
|
||||
case OBFUSCATED:
|
||||
component.getModifier().setObfuscated(true);
|
||||
break;
|
||||
case RESET:
|
||||
component = new StructuredComponent();
|
||||
component.getModifier().setColor(TextColor.legacy(EnumChatFormat.WHITE));
|
||||
component.getModifier().setFont(font);
|
||||
break;
|
||||
default:
|
||||
component = new StructuredComponent();
|
||||
component.getModifier().setColor(TextColor.legacy(format));
|
||||
component.getModifier().setFont(font);
|
||||
break;
|
||||
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) {
|
||||
@@ -278,9 +323,9 @@ public abstract class TabComponent {
|
||||
components.add(component);
|
||||
builder = new StringBuilder();
|
||||
}
|
||||
component = new StructuredComponent();
|
||||
component.getModifier().setColor(color);
|
||||
component.getModifier().setFont(font);
|
||||
component = new TextComponent();
|
||||
component.modifier.setColor(color);
|
||||
component.modifier.setFont(font);
|
||||
} else {
|
||||
builder.append('#');
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package me.neznamy.chat.rgb.format;
|
||||
|
||||
import me.neznamy.chat.TextColor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Abstract class for different RGB patterns
|
||||
*/
|
||||
public interface RGBFormatter {
|
||||
|
||||
/**
|
||||
* Re-formats RGB codes in the provided text using given formatter.
|
||||
*
|
||||
* @param text
|
||||
* text to format
|
||||
* @param rgbFunction
|
||||
* Function for converting RGB codes to desired format
|
||||
* @return reformatted text
|
||||
*/
|
||||
@NotNull
|
||||
String reformat(@NotNull String text, @NotNull Function<TextColor, String> rgbFunction);
|
||||
}
|
||||
+6
-3
@@ -1,5 +1,7 @@
|
||||
package me.neznamy.tab.shared.chat.rgb.gradient;
|
||||
package me.neznamy.chat.rgb.gradient;
|
||||
|
||||
import me.neznamy.chat.TextColor;
|
||||
import me.neznamy.chat.util.TriFunction;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
@@ -21,7 +23,8 @@ public class CMIGradient extends CommonGradient {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String applyPattern(@NotNull String text, boolean ignorePlaceholders) {
|
||||
@NotNull
|
||||
public String applyPattern(@NotNull String text, @NotNull TriFunction<TextColor, String, TextColor, String> gradientFunction) {
|
||||
String replaced = text;
|
||||
if (replaced.contains("<>}")) {
|
||||
Matcher m = shortcutPattern.matcher(replaced);
|
||||
@@ -31,6 +34,6 @@ public class CMIGradient extends CommonGradient {
|
||||
replaced = replaced.replace(format, "{#" + code + "<}{#" + code + ">}");
|
||||
}
|
||||
}
|
||||
return super.applyPattern(replaced, ignorePlaceholders);
|
||||
return super.applyPattern(replaced, gradientFunction);
|
||||
}
|
||||
}
|
||||
+6
-6
@@ -1,7 +1,8 @@
|
||||
package me.neznamy.tab.shared.chat.rgb.gradient;
|
||||
package me.neznamy.chat.rgb.gradient;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import me.neznamy.tab.shared.chat.TextColor;
|
||||
import me.neznamy.chat.TextColor;
|
||||
import me.neznamy.chat.util.TriFunction;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
@@ -20,18 +21,17 @@ public class CommonGradient implements GradientPattern {
|
||||
private final int endColorStartSub;
|
||||
|
||||
@Override
|
||||
public String applyPattern(@NotNull String text, boolean ignorePlaceholders) {
|
||||
@NotNull
|
||||
public String applyPattern(@NotNull String text, @NotNull TriFunction<TextColor, String, TextColor, String> gradientFunction) {
|
||||
if (!text.contains(containCheck)) return text;
|
||||
String replaced = text;
|
||||
Matcher m = pattern.matcher(replaced);
|
||||
while (m.find()) {
|
||||
String format = m.group();
|
||||
if (ignorePlaceholders && format.contains("%")) continue;
|
||||
TextColor start = new TextColor(format.substring(startColorStart, startColorStart+6));
|
||||
String message = format.substring(messageStart, format.length()-10);
|
||||
TextColor end = new TextColor(format.substring(format.length()-endColorStartSub, format.length()-endColorStartSub+6));
|
||||
String applied = asGradient(start, message, end);
|
||||
replaced = replaced.replace(format, applied);
|
||||
replaced = replaced.replace(format, gradientFunction.apply(start, message, end));
|
||||
}
|
||||
return replaced;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package me.neznamy.chat.rgb.gradient;
|
||||
|
||||
import me.neznamy.chat.TextColor;
|
||||
import me.neznamy.chat.util.TriFunction;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Abstract class for applying different gradient patterns
|
||||
*/
|
||||
public interface GradientPattern {
|
||||
|
||||
/**
|
||||
* Applies gradients in provided text and returns text using only #RRGGBB
|
||||
*
|
||||
* @param text
|
||||
* text to be reformatted
|
||||
* @param gradientFunction
|
||||
* Function for reformatting gradient to new text
|
||||
* @return reformatted text
|
||||
*/
|
||||
@NotNull
|
||||
String applyPattern(@NotNull String text, @NotNull TriFunction<TextColor, String, TextColor, String> gradientFunction);
|
||||
}
|
||||
+6
-6
@@ -1,6 +1,7 @@
|
||||
package me.neznamy.tab.shared.chat.rgb.gradient;
|
||||
package me.neznamy.chat.rgb.gradient;
|
||||
|
||||
import me.neznamy.tab.shared.chat.TextColor;
|
||||
import me.neznamy.chat.TextColor;
|
||||
import me.neznamy.chat.util.TriFunction;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
@@ -12,18 +13,17 @@ public class NexEngineGradient implements GradientPattern {
|
||||
private final Pattern pattern = Pattern.compile("<gradient:#([A-Fa-f0-9]{6})>(.*?)</gradient:#([A-Fa-f0-9]{6})>");
|
||||
|
||||
@Override
|
||||
public String applyPattern(@NotNull String text, boolean ignorePlaceholders) {
|
||||
@NotNull
|
||||
public String applyPattern(@NotNull String text, @NotNull TriFunction<TextColor, String, TextColor, String> gradientFunction) {
|
||||
if (!text.contains("<grad")) return text;
|
||||
String replaced = text;
|
||||
Matcher matcher = pattern.matcher(replaced);
|
||||
while (matcher.find()) {
|
||||
String format = matcher.group();
|
||||
if ((ignorePlaceholders && format.contains("%"))) continue;
|
||||
TextColor start = new TextColor(matcher.group(1));
|
||||
String content = matcher.group(2);
|
||||
TextColor end = new TextColor(matcher.group(3));
|
||||
String applied = asGradient(start, content, end);
|
||||
replaced = replaced.replace(format, applied);
|
||||
replaced = replaced.replace(format, gradientFunction.apply(start, content, end));
|
||||
}
|
||||
return replaced;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package me.neznamy.chat.util;
|
||||
|
||||
/**
|
||||
* An interface for a function with 3 input arguments.
|
||||
* @param <A>
|
||||
* First function argument
|
||||
* @param <B>
|
||||
* Second function argument
|
||||
* @param <C>
|
||||
* Third function argument
|
||||
* @param <D>
|
||||
* Function return type
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface TriFunction<A, B, C, D> {
|
||||
|
||||
/**
|
||||
* Runs the function and returns the result
|
||||
*
|
||||
* @param a
|
||||
* First argument
|
||||
* @param b
|
||||
* Second argument
|
||||
* @param c
|
||||
* Third argument
|
||||
* @return Output from function
|
||||
*/
|
||||
D apply(A a, B b, C c);
|
||||
}
|
||||
@@ -13,7 +13,7 @@ repositories {
|
||||
|
||||
dependencies {
|
||||
api(projects.shared)
|
||||
minecraft("com.mojang:minecraft:25w04a")
|
||||
minecraft("com.mojang:minecraft:25w07a")
|
||||
mappings(loom.officialMojangMappings())
|
||||
modImplementation("me.lucko:fabric-permissions-api:0.2-SNAPSHOT")
|
||||
modImplementation("eu.pb4:placeholder-api:2.5.0+1.21.2")
|
||||
@@ -35,6 +35,6 @@ tasks {
|
||||
options.release.set(17)
|
||||
}
|
||||
validateAccessWidener {
|
||||
enabled = true
|
||||
enabled = false
|
||||
}
|
||||
}
|
||||
Loaded 100 of 203 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user