Compare commits

...
76 Commits
Author SHA1 Message Date
NEZNAMY c486899dcf 5.0.5 2025-01-29 17:37:10 +01:00
NEZNAMY b212f0d5be [Velocity] Fix some console messages not being colored correctly 2025-01-27 18:44:55 +01:00
NEZNAMY caa534ff7f [MySQL] Fix upload command using wrong file path 2025-01-26 12:15:41 +01:00
NEZNAMY 9c2fac6749 [MiniMessage] Fix critical error when an ancient MM version is present 2025-01-25 19:12:47 +01:00
NEZNAMY 2a4f918a10 [RGB] Convert all RGB/Gradients for MiniMessage parser (#1408, #1414, #1421) 2025-01-25 08:55:22 +01:00
NEZNAMY b45e2afe4c [Code style] Move classes to a different package 2025-01-24 19:16:07 +01:00
NEZNAMY 2c7237e5d9 [RGB] Remove early gradient conversion (for future commits) 2025-01-24 19:10:59 +01:00
NEZNAMY 26477e3150 5.0.5-SNAPSHOT 2025-01-23 18:27:20 +01:00
NEZNAMY cba8cb57d5 5.0.4 2025-01-23 17:24:16 +01:00
NEZNAMY 599b4822e5 [Fabric] Update to 25w04a 2025-01-22 18:31:03 +01:00
NEZNAMY fbd150b353 [API] Expose TabPlayer#getServer, TabPlayer#getWorld and TabPlayer#isBedrockPlayer 2025-01-21 17:33:10 +01:00
NEZNAMY 10b252e934 [Placeholders] Fix placeholders staying incorrectly marked as bridge placeholders under very specific conditions (#1416) 2025-01-19 21:36:27 +01:00
NEZNAMY e9dfcdeb10 [Layout] If trying to use skin of an online player in fixed slot, try to get it from the player's profile if online 2025-01-19 18:16:04 +01:00
NEZNAMY 9cb2f27754 [Error management] Do not print full stack traces when a skin fails to download 2025-01-19 17:41:48 +01:00
NEZNAMY a090a83b7d [Debug] Replace debug messages about downloading skin with downloaded and include time it took 2025-01-19 17:04:44 +01:00
NEZNAMY 992e661e4c [Placeholders] Fix bridge overriding placeholders if they did not exist before a reload (such as conditions) (#1416) 2025-01-19 15:59:51 +01:00
NEZNAMY 3b993d114e [Velocity] Fix errors when plugin is disabled due to a broken configuration file 2025-01-19 15:21:34 +01:00
NEZNAMY d181d62466 [Code style] Call EnumChatFormat#getFormat instead of toString 2025-01-19 14:55:46 +01:00
NEZNAMY 968cef7a12 [Scoreboard & Nametags] Add a config option to enforce <1.13 limits even for 1.13+ players (#1412, #1413) 2025-01-19 13:48:39 +01:00
NEZNAMY 390c2395eb Fix compilation error from previous commits 2025-01-19 13:34:18 +01:00
NEZNAMY adc9444794 [Code style] Make code more readable 2025-01-19 13:25:20 +01:00
NEZNAMY f086318082 [Chat components] Remove redundant checks for player's version 2025-01-19 13:21:18 +01:00
NEZNAMY 1cef4400b9 [Scoreboard] Fix ConcurrentModificationException when using API (#1410) 2025-01-18 14:23:13 +01:00
NEZNAMY 1f879ac91e [Scoreboard] Fix setText API method breaking a line if setting it to empty value (#1410) 2025-01-18 14:12:00 +01:00
NEZNAMY bec129d98e [Scoreboard & Bossbar] Save toggled players by UUID instead of names 2025-01-18 10:44:22 +01:00
NEZNAMY 152c04e68f [Configuration] Remove file header restoration on file modification 2025-01-18 10:41:13 +01:00
NEZNAMY d6cdacef43 [Layout] Fix skins not being saved into memory when downloaded 2025-01-16 19:02:38 +01:00
NEZNAMY 09891a075d [Layout] Add debug messages when a skin is about to be downloaded 2025-01-16 19:02:00 +01:00
NEZNAMY 9500bab5b7 [Scoreboard] Fix resetScoreboard not applying scoreboard correctly (#1411) 2025-01-16 18:15:31 +01:00
NEZNAMY 231edd4bd4 [Chat components] Fix magic code null values being converted to false 2025-01-15 18:55:03 +01:00
NEZNAMY 433572e759 [Fabric] Update to 25w03a 2025-01-15 18:06:51 +01:00
NEZNAMY 3ec772e87f [Feature management] Load all features in plugin's main thread to avoid API calls from load event before a feature actually loads (#1409) 2025-01-10 21:55:55 +01:00
NEZNAMY ae8287157c [Scoreboard] Do not add API scoreboards to the chain of scoreboards to go through (#1409) 2025-01-10 21:32:42 +01:00
NEZNAMY f005907b41 [Fabric] Update to 25w02a 2025-01-09 18:44:55 +01:00
NEZNAMY 8ea16044f1 [Debug] Send a correct message if player does not have any group permission 2025-01-08 18:34:06 +01:00
NEZNAMY e787a28353 [Tablist formatting] Fix formatting applying even if disabled with condition sometimes 2025-01-06 18:59:32 +01:00
NEZNAMY 6925d9564c [Code style] Create Platform#getCommand method instead of storing it in TabConstants 2025-01-04 18:43:37 +01:00
NEZNAMY ccd4ea785a [Logging] Fix outdated value in a message 2025-01-04 18:27:29 +01:00
NEZNAMY 63e88c09d4 [MySQL] Add a startup warn message if database is empty 2025-01-04 18:24:51 +01:00
NEZNAMY 296191b502 [Code style] Replace uses of EnumChatFormat for color codes with the codes directly 2025-01-02 19:20:49 +01:00
NEZNAMY af0151f446 [RGB] Remove now redundant check for modern/legacy colors (partially) 2025-01-02 16:26:06 +01:00
NEZNAMY 2bdfaa167e [RGB] Remove support for |L for manually defining legacy colors 2025-01-02 15:56:49 +01:00
NEZNAMY cd96476f9e [Belowname] Improve a warning message 2025-01-01 19:16:12 +01:00
NEZNAMY 9b0545aa1a [Layout] Fix players on top of tablist if team implementation is not available 2025-01-01 18:57:49 +01:00
NEZNAMY 63823123f8 [BungeeCord] Simplify code 2025-01-01 14:27:43 +01:00
NEZNAMY e91728be98 [Sponge 8] Update sponge gradle plugin to fix warning 2025-01-01 14:02:00 +01:00
NEZNAMY e7ecbf1245 [Velocity] Remove usage of deprecated API 2025-01-01 13:54:17 +01:00
NEZNAMY 28c2363b67 [Wiki] Monthly wiki backup for January 2025 2025-01-01 13:12:21 +01:00
NEZNAMY 21a3d4fb55 [Paper] Fix mistake in magic codes (#1404) 2024-12-30 14:10:52 +01:00
NEZNAMY 4b459a3ed1 [Commands] Add a red message to debug command if assigning groups by permissions and the player has all permissions 2024-12-29 20:09:31 +01:00
NEZNAMY ebc6a1b842 Fix paper compilation 2024-12-29 20:08:10 +01:00
NEZNAMY b72b0f269a [Code style] Remove redundant warning suppression 2024-12-21 17:54:06 +01:00
NEZNAMY 4cbcd69ed2 [Code style] Remove redundant constructor 2024-12-21 17:45:13 +01:00
NEZNAMY 6ef8b7748c [Code style] Remove redundant double coloring 2024-12-21 17:05:35 +01:00
NEZNAMY 8b567cb976 [Code style] Optimize last color of component calculation 2024-12-21 16:15:04 +01:00
NEZNAMY d8c231b9ce [Code style] Further reduce usage of EnumChatFormat class 2024-12-21 15:49:33 +01:00
NEZNAMY cc4347c881 [Code style] Make TabComponent#fetchLastColor Nullable 2024-12-21 13:19:20 +01:00
NEZNAMY 7cc572feb5 [Code style] Replace some references to EnumChatFormat with the actual color codes 2024-12-21 11:58:37 +01:00
NEZNAMY 96d1d1feb4 [Code style] Use TextColor as team color internally to prepare for a potential future MC update 2024-12-21 11:39:36 +01:00
NEZNAMY a7d9bd9ab5 [Code style] Simplify TabComponent#toRawText implementation 2024-12-21 11:15:47 +01:00
NEZNAMY 1cdfdf1d43 [Code style] Simplify 2024-12-18 17:32:54 +01:00
NEZNAMY 0762a5e689 [Code style] Remove redundant method call 2024-12-18 17:22:55 +01:00
NEZNAMY 3e2906ccf8 [Dependencies] Version bump and remove unused 2024-12-18 17:18:09 +01:00
NEZNAMY 13761e032c [Bukkit] Set shadowColor to null instead of 0 (no shadow) (#1400) 2024-12-16 18:51:02 +01:00
NEZNAMY fb8fa3fa54 [MiniMessage] Implement Keybind component support as well (on 1.19+) 2024-12-14 11:34:02 +01:00
NEZNAMY b89bf5933b [MiniMessage] Add support for its translatable components (paper / velocity only) (#1399) 2024-12-13 22:40:20 +01:00
NEZNAMY 1c4f404fd2 [Code style] Remove unused method 2024-12-13 22:22:51 +01:00
NEZNAMY 9fe1560f1a [Sorting] Remove runtime warn spam when configured sorting placeholder is invalid (only keep startup warn) 2024-12-12 18:58:49 +01:00
NEZNAMY 6a9cca37c6 [Scoreboard] Fix toggle command being completely ignored when another plugin has sent scoreboard instead of processing it (to some degree) 2024-12-10 21:30:20 +01:00
NEZNAMY bfde75d4b1 [Configuration] Fix startup error when group name is a number (#1398) 2024-12-09 18:12:03 +01:00
NEZNAMY a83f98580d [Bukkit] Update Paper submodule to 1.21.4 2024-12-07 13:58:47 +01:00
NEZNAMY 673542d8e4 [Code style] Move canSee method into TabPlayer 2024-12-07 12:23:51 +01:00
NEZNAMY 81c5d89a25 Fix broken PR 2024-12-07 12:08:26 +01:00
NEZNAMY bf0bcb0b96 Merge pull request #1394 from WheelerShigley/master
Fabric now has a getTPS() approximation.
2024-12-07 09:57:25 +01:00
WheelerShigley ac7a664303 Fabric now has a getTPS() approximation.
Added Fabric TPS approximation, based on mspt.
2024-12-06 19:58:38 -06:00
NEZNAMY 182dc67a49 5.0.4-SNAPSHOT 2024-12-03 20:27:24 +01:00
175 changed files with 1765 additions and 1451 deletions

No files matched your search

+2 -2
View File
@@ -2,8 +2,8 @@
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.
[IMG]https://img.shields.io/badge/Release-5.0.1-blue.svg[/IMG]
[IMG]https://img.shields.io/badge/Minecraft-1.5%20--%201.21.3-blue.svg[/IMG]
[IMG]https://img.shields.io/badge/Release-5.0.3-blue.svg[/IMG]
[IMG]https://img.shields.io/badge/Minecraft-1.5%20--%201.21.4-blue.svg[/IMG]
[IMG]https://img.shields.io/badge/Java-8+-blue.svg[/IMG]
[URL='https://github.com/NEZNAMY/TAB'][IMG]https://img.shields.io/badge/GitHub-Source%20code-yellow.svg[/IMG][/URL]
[URL='https://github.com/NEZNAMY/TAB/issues'][IMG]https://img.shields.io/github/issues/neznamy/tab?label=Issues[/IMG][/URL]
+2 -2
View File
@@ -4,8 +4,8 @@ The compact configuration allows you to get the plugin to work the way you want
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.
![https://img.shields.io/badge/Release-5.0.1-blue.svg](https://img.shields.io/badge/Release-5.0.1-blue.svg)
![https://img.shields.io/badge/Minecraft-1.5%20--%201.21.3-blue.svg](https://img.shields.io/badge/Minecraft-1.5%20--%201.21.3-blue.svg)
![https://img.shields.io/badge/Release-5.0.3-blue.svg](https://img.shields.io/badge/Release-5.0.3-blue.svg)
![https://img.shields.io/badge/Minecraft-1.5%20--%201.21.4-blue.svg](https://img.shields.io/badge/Minecraft-1.5%20--%201.21.4-blue.svg)
![https://img.shields.io/badge/Java-8+-blue.svg](https://img.shields.io/badge/Java-8+-blue.svg)
[![https://img.shields.io/badge/GitHub-Source%20code-yellow.svg](https://img.shields.io/badge/GitHub-Source%20code-yellow.svg)](https://github.com/NEZNAMY/TAB)
[![https://img.shields.io/github/issues/neznamy/tab?label=Issues](https://img.shields.io/github/issues/neznamy/tab?label=Issues)](https://github.com/NEZNAMY/TAB/issues)
+4 -3
View File
@@ -28,7 +28,7 @@ It is compiled with Java 8 and therefore supports any version of 8 and above.
<tr>
<td rowspan=2>Vanilla</td>
<td rowspan=1><a href="https://getbukkit.org/">Bukkit</a> (+forge hybrids)</td>
<td>✔ (1.5 - 1.21.3)</td>
<td>✔ (1.5 - 1.21.4)</td>
</tr>
<tr>
<td rowspan=1><a href="https://www.spongepowered.org/">Sponge</a></td>
@@ -39,7 +39,7 @@ It is compiled with Java 8 and therefore supports any version of 8 and above.
<tr>
<td rowspan=2>Modded</td>
<td rowspan=1><a href="https://fabricmc.net">Fabric</a></td>
<td>✔ (1.14 - 1.21.3)</td>
<td>✔ (1.14 - 1.21.4)</td>
</tr>
<tr>
<td rowspan=1><a href="https://minecraftforge.net">Forge</a></td>
@@ -124,7 +124,8 @@ In order to enhance user experience, TAB hooks into other plugins for better exp
* **SkBee** (skript addon) sends empty scoreboard, causing TAB's to not show sometimes.
* **Waterfall**'s `disable_tab_list_rewrite: true` **may** cause tablist to use offline UUIDs while TAB expects online uuids, causing various problems (most notably tablist formatting not working). Checking for this option is not an option either, because tablist rewrite might still be enabled despite being disabled (don't ask how, I have no idea). Set the option to `false` if you are experiencing issues.
* **ViaVersion on BungeeCord and TAB on backend** acts like a client-sided protocol hack, making it impossible for TAB to know player's real version and causing issues related to it, see [Per-version experience](https://github.com/NEZNAMY/TAB/wiki/Additional-information#per-version-experience) for more info. Avoid this combination. Either install ViaVersion on all backend servers instead or install TAB on BungeeCord instead.
* **Custom clients / resource packs** - Unofficially modified minecraft clients often tend to break things. Just Lunar client has tons of bugs that can be reproduced with TAB. Resource packs may also contain modifications you are not aware of, making things not look the way you want them to. If you are experiencing any visual issue and are using a custom client or resource pack, try it with a clean vanilla client. If it works there, it's an issue with the client / resource pack and TAB cannot do anything about it. Here are a few bugs in LunarClient / FeatherClient that you may run into when using TAB:
* **Custom clients / resource packs** - Unofficially modified minecraft clients often tend to break things. Just Lunar client has tons of bugs that can be reproduced with TAB. Resource packs may also contain modifications you are not aware of, making things not look the way you want them to. If you are experiencing any visual issue and are using a custom client or resource pack, try it with a clean vanilla client. If it works there, it's an issue with the client / resource pack and TAB cannot do anything about it.
For example, here are a few bugs in LunarClient / FeatherClient that you may run into when using TAB:
* They add their icon to players in tablist, but don't widen the entries. This results in player names overlapping with latency bar. You can avoid this by configuring some spaces in tabsuffix.
* They don't support color field in chat components, which means they don't support RGB codes and will display bossbar without colors as well.
* Bossbar is not visible on 1.8.
+3 -3
View File
@@ -26,7 +26,7 @@ For Maven users, you can add the API dependency like this:
<dependency>
<groupId>com.github.NEZNAMY</groupId>
<artifactId>TAB-API</artifactId>
<version>5.0.2</version>
<version>5.0.3</version>
<scope>provided</scope>
</dependency>
```
@@ -40,7 +40,7 @@ repositories {
}
dependencies {
compileOnly 'com.github.NEZNAMY:TAB-API:5.0.2'
compileOnly 'com.github.NEZNAMY:TAB-API:5.0.3'
}
```
@@ -51,7 +51,7 @@ repositories {
}
dependencies {
compileOnly("com.github.NEZNAMY", "TAB-API", "5.0.2")
compileOnly("com.github.NEZNAMY", "TAB-API", "5.0.3")
}
```
+2 -2
View File
@@ -23,7 +23,7 @@ This line automatically appears on all player entities when enabled. **Therefore
The feature can be configured in **config.yml** under **belowname-objective** section.
The feature's properties are displayed as `[score]` + space + `title`, where `[score]` is:
The feature's properties are displayed as `[score]` + space + `title` (no, the space cannot be removed), where `[score]` is:
* `value` for 1.20.2- (will show `0` on NPCs)
* `fancy-value` for 1.20.3+ (will show `fancy-value-default` on NPCs)
@@ -77,7 +77,7 @@ If you want health to display health as 0-10 instead of 0-20, you can achieve it
* `%math_0_{player_health}*5%` for 0-100
# Limitations
* The format is a **[score] + space + shared title**, where [score] is either a white number (1.20.2-) or any text (1.20.3+).
* The 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.
* It appears on all entities of player type. **This includes player NPCs.**
* Title length is limited to 32 characters on <1.13
+1 -1
View File
@@ -102,7 +102,7 @@ bossbar:
`name` is name of bossbar defined in config.yml under **bossbar** section, `time` is length of display time in seconds.
When using a bossbar announcement, you have a new placeholder available:
`%countdown%` that shows remaining time of the announcement in seconds.
`%countdown%` that shows remaining time of the announcement in seconds.
You can further use this placeholder in progress, for example,
using Math expansion from PlaceholderAPI, such as `%math_{tab_placeholder_countdown}/<total time>*100%`.
@@ -10,6 +10,7 @@
* [Examples](#examples)
* [Example 1 - Adding brackets to placeholder output if it returns non-empty value](#example-1---adding-brackets-to-placeholder-output-if-it-returns-non-empty-value)
* [Example 2 - Replicating PlaceholderAPI's %player_colored_ping%](#example-2---replicating-placeholderapis-player_colored_ping)
* [Example 3 - Using both original and replaced output](#example-3---using-both-original-and-replaced-output)
# About
Most placeholders don't have fully configurable output, making you unable to customize it as you'd like.
@@ -155,4 +156,31 @@ which require a numeric input (for versions prior to 1.20.3).
If you want that feature and ping to be displayed there, replacements won't work for you,
and you'll need
to use [Conditional placeholders](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Conditional-placeholders) instead
to achieve colored ping.
to achieve colored ping.
# Example 3 - Using both original and replaced output
Once configured, replacements are applied as the placeholder output everywhere. It is not directly possible to use original output in one place and replaced in another.
If you need this, you should consider [conditional placeholders](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Conditional-placeholders) instead.
If conditional placeholders 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.
<details>
<summary>Example for %ping%</summary>
**animations.yml**:
```
ping:
change-interval: 50 # Actual value doesn't matter
texts:
- "%ping%"
```
Then, configure replacements for the animation in **config.yml**:
```
placeholder-output-replacements:
"%animation:ping%":
"0-50": "&a%animation:ping%" # %ping% would work too
"51-100": "&e%animation:ping%"
"else": "&c%animation:ping%"
```
Now, you can use `%ping%` for original output and `%animation:ping%` for replaced.
</details>
+3 -3
View File
@@ -18,7 +18,7 @@
* [Toggling scoreboard visibility](#toggling-scoreboard-visibility)
* [Announcing a scoreboard](#announcing-a-scoreboard)
* [Examples](#examples)
* [Example 1 - per-world scoreboards](#example-1---per-world-scoreboards)
* [Example 1 - Per-world scoreboards](#example-1---per-world-scoreboards)
* [Example 2 - Periodical scoreboard switching](#example-2---periodical-scoreboard-switching)
* [Example 3 - Per-version scoreboards](#example-3---per-version-scoreboards)
@@ -52,7 +52,7 @@ Will give you the following:
The right side text will not be visible at 1.20.2 and lower, and numbers will be displayed instead.
To properly see the right side, not only the client has to be 1.20.3+, but the server as well,
since the plugin cannot send a packet that does not exist (on older versions).
since the plugin cannot send a packet that does not exist (on older versions).
Proxies always include new content and therefore count as the latest version.
This can be used to send new content to new players while having an outdated server version.
@@ -215,7 +215,7 @@ which will look up a scoreboard with the given `String` name,
and if one is found, send it to all players for the given `int` ticks.
# Examples
## Example 1 - per-world scoreboards
## Example 1 - Per-world scoreboards
We will be using a condition of "equals" (=) type and check for output of `%world%` placeholder.
If condition is met, it means player is in that world and the scoreboard is displayed.
If not, check for the next scoreboard's condition.
+1 -1
View File
@@ -226,7 +226,7 @@ The most common mistakes include:
* Not configuring group weights in LuckPerms.
* Accidentally enabling `use-bukkit-permission-manager` option when on BungeeCord without knowing what it does.
* Installing TAB on BungeeCord without having any permission plugin on BungeeCord.
* Enabling `assign-groups-by-permissions` without giving those permissions.
* Enabling `assign-groups-by-permissions` without giving those permissions (or the opposite - giving away OP to users, resulting in the highest group being taken).
[Debug command](https://github.com/NEZNAMY/TAB/wiki/Commands-&-Permissions#tab-debug-player) will help you identify if this is your case.
* Using a plugin that causes TAB to fail to apply teams (such as Tablisknu). This includes installing TAB plugin on both bukkit and bungeecord, causing the installations to conflict.
+1 -1
View File
@@ -1,5 +1,5 @@
# About the wiki
The wiki explains the functionality of the plugin as of version **5.0.2**.
The wiki explains the functionality of the plugin as of version **5.0.3**.
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.
+16 -9
View File
@@ -1,30 +1,37 @@
# Content
* [Bukkit/Spigot](#bukkitspigot)
* [BungeeCord](#bungeecord)
* [Backend](#backend)
* [BungeeCord / Velocity](#bungeecord--velocity)
* [Proxy installation](#proxy-installation)
* [Backend installation](#backend-installation)
* [Mixed installation](#mixed-installation)
* [Updating the plugin](#updating-the-plugin)
# Bukkit/Spigot
Installation on Bukkit/Spigot/Paper is as simple as it can be.
# Backend
Installation on Bukkit / Fabric / Sponge is as simple as it can be.
Put the plugin into the plugins folder and restart the server.
Dynamic (re)loads at runtime such as `/plugman load tab` to load the plugin are fully supported.
However, plugins hooking into TAB may break when you use plugman to reload it.
However, plugins hooking into TAB may break when you use such ways to (re)load it.
# BungeeCord / Velocity
When running a network, you have 3 options:
* Installing TAB only on the proxy (recommended)
* Installing TAB on all backend servers instead
* Installing TAB everywhere and disabling features so they don't conflict (not recommended)
See below for more information regarding each way.
# BungeeCord
## Proxy installation
This is the recommended setup.
Put the plugin into the plugins folder of the proxy server and restart it.
**Recommended**:
Install [TAB-Bridge](https://github.com/NEZNAMY/TAB/wiki/TAB-Bridge) on your backend servers for PlaceholderAPI support and more.
**Recommended**:
On **Velocity** installation install [VelocityScoreboardAPI](https://github.com/NEZNAMY/VelocityScoreboardAPI/) plugin for scoreboard-related features to work.
If you are using **Velocity**, install [VelocityScoreboardAPI](https://github.com/NEZNAMY/VelocityScoreboardAPI/) plugin for scoreboard-related features to work.
**Note**: With proxy installation use **/btab** command instead of **/tab**.
Advantages of proxy installation compared to installing on all backend servers instead:
* Can use the [Global player list](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Global-playerlist)
* Can use the [Global player list](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Global-playerlist) and [Layout](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Layout) can work with all players connected to the proxy
* Can use new content for new clients even if a backend server version is old (1.16+ RGB codes, 1.20.3+ scoreboard features)
* Avoid [header/footer](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Header-&-Footer#additional-note-1---not-resetting-on-server-switch) and [bossbar](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Bossbar#additional-note-2---not-hiding-on-server-switch) not disappearing on server switch
* Configuration files in a single place for easier editing / no need to use MySQL for syncing data
@@ -34,7 +41,7 @@ Disadvantages compared to installing on all backend servers instead:
* No [per-world player list](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Per-world-playerlist) (can be achieved by installing the plugin on backend servers and disabling all features except the per-world player list)
## Backend installation
Just like when not using BungeeCord, install the plugin on all servers where you want the plugin. However, this setup is not recommended (see advantages of proxy installation).
Just like when not running a network, install the plugin on all servers where you want the plugin. However, this setup is not as good (see advantages of proxy installation).
## Mixed installation
Mixed installation is absolutely not recommended and should be avoided.
+7 -7
View File
@@ -22,13 +22,13 @@ Before attempting to use the function, make sure your server software has an int
TAB does not contain it by itself.
See the compatibility table below for current information about compatibility:
| Platform | Available |
| ------------- | ------------- |
| Bukkit | ✔ |
| BungeeCord | ✔ |
| Fabric | ❌ |
| Sponge | ❌ |
| Velocity | ❌ |
| Platform | Available |
|------------|-----------|
| Bukkit | ✔ |
| BungeeCord | ✔ |
| Fabric | ❌ |
| Sponge | ❌ |
| Velocity | ❌ |
# Enabling
Find this section in **config.yml**:
+27 -31
View File
@@ -5,13 +5,13 @@ The usage can be checked at any time using [**/tab cpu**](https://github.com/NEZ
* [#1 - [All platforms] Static text instead of placeholders](#1---all-platforms-static-text-instead-of-placeholders)
* [#2 - [All platforms] Placeholder refresh intervals](#2---all-platforms-placeholder-refresh-intervals)
* [#3 - [All platforms] Don't use RGB](#3---all-platforms-dont-use-rgb)
* [#4 - [All platforms] Disable anti-override](#4---all-platforms-disable-anti-override)
* [#5 - [All platforms] Disable tablist name formatting](#5---all-platforms-disable-tablist-name-formatting)
* [#6 - [All platforms] Increase permission refresh interval](#6---all-platforms-increase-permission-refresh-interval)
* [#7 - [BungeeCord / Velocity] Disable TAB expansion](#7---bungeecord--velocity-disable-tab-expansion)
* [#8 - [BungeeCord / Velocity] Disable update-latency option in global playerlist](#8---bungeecord--velocity-disable-update-latency-option-in-global-playerlist)
* [#9 - [BungeeCord] Disable ByteBuf deserialization](#9---bungeecord-disable-bytebuf-deserialization)
* [#10 - [Velocity] Disable header/footer translation](#10---velocity-disable-headerfooter-translation)
* [#4 - [All platforms] Don't use animations](#4---all-platforms-dont-use-animations)
* [#5 - [All platforms] Disable anti-override](#5---all-platforms-disable-anti-override)
* [#6 - [All platforms] Disable tablist name formatting](#6---all-platforms-disable-tablist-name-formatting)
* [#7 - [All platforms] Increase permission refresh interval](#7---all-platforms-increase-permission-refresh-interval)
* [#8 - [BungeeCord / Velocity] Disable TAB expansion](#8---bungeecord--velocity-disable-tab-expansion)
* [#9 - [BungeeCord / Velocity] Disable update-latency option in global playerlist](#9---bungeecord--velocity-disable-update-latency-option-in-global-playerlist)
* [#10 - [BungeeCord] Disable ByteBuf deserialization](#10---bungeecord-disable-bytebuf-deserialization)
# #1 - [All platforms] Static text instead of placeholders
Static text doesn't need to be refreshed periodically, resulting in better performance.
@@ -50,7 +50,13 @@ Using a higher interval will help if needed, but may lead to misleading info in
# #3 - [All platforms] Don't use RGB
Though RGB colors look good, they require the text to be split into several components with `color` field used instead of the whole text pasted into the `text` field. This is a complicated process that takes quite some time. Sticking to legacy colors results in better performance.
# #4 - [All platforms] Disable anti-override
# #4 - [All platforms] Don't use animations
While animations look cool, they also have a high resource usage.
This is because they work by spamming updates every time an animation switches to a new frame.
When animations are not used, no updates need to be sent (unless another placeholder changes value),
not causing extra CPU usage.
# #5 - [All platforms] Disable anti-override
By default, TAB prevents other plugins from overriding it.
This is because many server owners are not competent enough to configure their plugins correctly,
such as disabling CMI collision handling which breaks nametags or even disabling team handling in paper.
@@ -70,7 +76,7 @@ tablist-name-formatting:
anti-override: false
```
# #5 - [All platforms] Disable tablist name formatting
# #6 - [All platforms] Disable tablist name formatting
As we already know from [Client-sided mechanics](https://github.com/NEZNAMY/TAB/wiki/Client%E2%80%90sided-mechanics#nametag-format-in-tablist) page,
the nametag format appears in tablist if tablist name is not defined.
You can take advantage of this to effectively disable one entire feature and its cpu usage,
@@ -81,7 +87,7 @@ However, you can only do this if:
Whether you have another plugin attempting to handle them or not may not be easy to identify with a lot of plugins and not enough time spent configuring them. In that case, your best choice is to try it and see. If tablist names break, it means you have another plugin and cannot take advantage of this optimization option.
# #6 - [All platforms] Increase permission refresh interval
# #7 - [All platforms] Increase permission refresh interval
Plugin needs to constantly check permissions for:
* Permission checks in conditions
* Permission checks if sorting by permissions
@@ -92,9 +98,11 @@ default value
```
permission-refresh-interval: 1000
```
refreshes them every second. If you don't need any permission/group changes to take effect within a second, you can increase this value for better performance.
Refreshes them every second.
If you don't need any permission/group changes to take effect within a second,
you can increase this value for better performance.
# #7 - [BungeeCord / Velocity] Disable TAB expansion
# #8 - [BungeeCord / Velocity] Disable TAB expansion
To maximize the performance of TAB's response to PlaceholderAPI request,
values are tracked in advance and then quickly returned.
This process takes resources and is especially heavy on proxy installation,
@@ -105,36 +113,24 @@ Disabling TAB's PlaceholderAPI expansion if you don't use it improves performanc
placeholders:
register-tab-expansion: false
```
# #8 - [BungeeCord / Velocity] Disable update-latency option in global playerlist
# #9 - [BungeeCord / Velocity] Disable update-latency option in global playerlist
When [Global playerlist](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Global-playerlist) is enabled, you can enable / disable [update-latency](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Global-playerlist#:~:text=other%20unlisted%20servers.-,update%2Dlatency,-false) option (open the link for detailed description of the option). Disabling this option massively improves performance.
# #9 - [BungeeCord] Disable ByteBuf deserialization
# #10 - [BungeeCord] Disable ByteBuf deserialization
In order for anti-override to work properly, some packets must be manually deserialized due to BungeeCord not doing it. If you don't need it, you can disable those functions, which will also disable the deserialization completely. To do so, you must disable 2 functions (only having 1 disabled is not enough):
#1 - Anti-override for teams
```
scoreboard-teams:
anti-override: false
```
Whether you need anti-override or not depends on the configuration of your plugins.
The easiest way is to have it enabled and see if you get `anti-override.log` file in TAB folder.
Whether you need anti-override or not depends on the configuration of your plugins.
The easiest way is to have it enabled and see if you get `anti-override.log` file in TAB folder.
If you don't, you don't need it.
If you do, you'll need to do some configuring.
[Here](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Sorting-players-in-tablist#additional-note-3---compatibility-issues-with-other-plugins)
If you do, you'll need to do some configuring.
[Here](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Sorting-players-in-tablist#additional-note-3---compatibility-issues-with-other-plugins)
are a few examples of software and their team names, which you may find helpful.
#2 - Scoreboard detection of other plugins
TAB checks for packets sent by other plugins, and if another plugin sends a scoreboard,
TAB hides its own and resends it after the other plugin removes it.
This check cannot be disabled if the scoreboard is enabled.
# #10 - [Velocity] Disable header/footer translation
Velocity offers header/footer translation of translatable components.
However, TAB does not use these.
Despite that, Velocity's attempt to "translate" the components is still there,
causing the whole feature to use up to 3x more CPU than it would without it.
There is no way to disable this inside Velocity itself,
however, there is a third party [fork](https://github.com/GemstoneGG/Velocity-CTD/) that offers it.
**Use at your own risk**.
After installing, default velocity.toml file will contain `translate-header-footer = true` option.
If just upgrading, you will need to add it there yourself.
Setting it to `false` will disable this function.
This check cannot be disabled if the scoreboard is enabled.
+73 -47
View File
@@ -26,59 +26,69 @@ TAB offers various placeholders to display the most common information. It also
Basic placeholders provided by the plugin. Most of these placeholders were added because they were requested by people who don't want to install another plugin just to show username. Some of them have configurable output in config.yml in `Placeholders` section. Unused placeholders do not affect performance in any way.
Placeholders must be refreshed. All internal placeholders have manually defined refresh interval, which I found the most optimal and cannot be changed. Time is in milliseconds. `-1` is used for placeholders which are in fact constants (such as %player%), or fire an event each time they change, which can be listened to (such as %world%) and value updated. Refresh interval for conditions is set to be equal to fastest refreshing placeholder used inside each condition.
Placeholders must be refreshed.
All internal placeholders have a manually defined refresh interval,
which I found the most optimal and cannot be changed.
Time is in milliseconds.
It is used for placeholders which are in fact constants (such as %player%),
or fire an event each time they change, which can be listened to (such as %world%) and value updated.
Refresh interval for conditions is set to be equal to the fastest refreshing placeholder used inside each condition.
## Universal
These placeholders are available on all platforms.
| Identifier | Refresh interval | Description |
| ------------- | ------------- | ------------- |
| %online% | 1000 | Total online player count on server where plugin is installed (excluding [vanished players](https://github.com/NEZNAMY/TAB/wiki/Additional-information#vanish-detection)). If the plugin is installed on a proxy, it will show total player count on the whole network. |
| %world% | -1 | Name of world where player is |
| %worldonline% | 1000 | Amount of players in the same world (excluding [vanished players](https://github.com/NEZNAMY/TAB/wiki/Additional-information#vanish-detection)) as the player |
| %ping% | 500 | Player's ping to the server where TAB is installed. <br />**Note**: This value is measured by the server and TAB is only retrieving that value. If it's wrong, it's not a TAB issue. |
| %player% | -1 | Player's name |
| %time% | 500 | Current real time, output is configurable in **config.yml** in `time-format` option |
| %date% | 60000 | Current date, output is configurable in **config.yml** in `date-format` option |
| %staffonline% | 2000 | Amount of online players with `tab.staff` permission |
| %nonstaffonline% | 2000 | Amount of online players without `tab.staff` permission |
| %memory-used% | 200 | Used RAM of server in MB |
| %memory-max% | -1 | Total RAM of server in MB |
| %memory-used-gb% | 200 | Used RAM of server in GB |
| %memory-max-gb% | -1 | Total RAM of server in GB |
| %player-version% | -1 | User-friendly version of player, such as `1.14.4`. For versions that share the same protocol version number, the higher one is returned (for example 1.20 & 1.20.1 -> 1.20.1 is returned) |
| %player-version-id% | -1 | Network ID of player's version. You can see the full version map on [fan wiki](https://wiki.vg/Protocol_version_numbers) for all versions or [TAB's source code](https://github.com/NEZNAMY/TAB/blob/master/shared/src/main/java/me/neznamy/tab/shared/ProtocolVersion.java) for releases only. Useful for conditions to create per-version results. |
| %luckperms-prefix% | 1000 | Prefix from LuckPerms from the server where TAB is installed |
| %luckperms-suffix% | 1000 | Suffix from LuckPerms from the server where TAB is installed |
| %displayname% | 500 | Display name variable set by permission plugin, typically prefix + name + suffix |
| %group% | 1000 | Returns player's primary group name. Created for internal functionality, but can be used as a display placeholder as well. |
| %vanished% | 1000 | Returns `true`/`false` based on player's vanish status. Created for internal functionality, but can be used as a display placeholder as well. |
| %bedrock% | -1 | Returns `true`/`false` based on whether player is using Bedrock edition or not (Java) |
| %% | -1 | Returns the `%` symbol. Useful to display `%` symbol without breaking all placeholders after it |
| Identifier | Refresh interval | Description |
|---------------------|------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| %online% | 1000 | Total online player count on server where plugin is installed (excluding [vanished players](https://github.com/NEZNAMY/TAB/wiki/Additional-information#vanish-detection)). If the plugin is installed on a proxy, it will show total player count on the whole network. |
| %world% | -1 | Name of world where player is |
| %worldonline% | 1000 | Amount of players in the same world (excluding [vanished players](https://github.com/NEZNAMY/TAB/wiki/Additional-information#vanish-detection)) as the player |
| %ping% | 500 | Player's ping to the server where TAB is installed. <br />**Note**: This value is measured by the server and TAB is only retrieving that value. If it's wrong, it's not a TAB issue. |
| %player% | -1 | Player's name |
| %time% | 500 | Current real time, output is configurable in **config.yml** in `time-format` option |
| %date% | 60000 | Current date, output is configurable in **config.yml** in `date-format` option |
| %staffonline% | 2000 | Amount of online players with `tab.staff` permission |
| %nonstaffonline% | 2000 | Amount of online players without `tab.staff` permission |
| %memory-used% | 200 | Used RAM of server in MB |
| %memory-max% | -1 | Total RAM of server in MB |
| %memory-used-gb% | 200 | Used RAM of server in GB |
| %memory-max-gb% | -1 | Total RAM of server in GB |
| %player-version% | -1 | User-friendly version of player, such as `1.14.4`. For versions that share the same protocol version number, the higher one is returned (for example 1.20 & 1.20.1 -> 1.20.1 is returned) |
| %player-version-id% | -1 | Network ID of player's version. You can see the full version map on [fan wiki](https://wiki.vg/Protocol_version_numbers) for all versions or [TAB's source code](https://github.com/NEZNAMY/TAB/blob/master/shared/src/main/java/me/neznamy/tab/shared/ProtocolVersion.java) for releases only. Useful for conditions to create per-version results. |
| %luckperms-prefix% | 1000 | Prefix from LuckPerms from the server where TAB is installed |
| %luckperms-suffix% | 1000 | Suffix from LuckPerms from the server where TAB is installed |
| %displayname% | 500 | Display name variable set by permission plugin, typically prefix + name + suffix |
| %group% | 1000 | Returns player's primary group name. Created for internal functionality, but can be used as a display placeholder as well. |
| %vanished% | 1000 | Returns `true`/`false` based on player's vanish status. Created for internal functionality, but can be used as a display placeholder as well. |
| %bedrock% | -1 | Returns `true`/`false` based on whether player is using Bedrock edition or not (Java) |
| %% | -1 | Returns the `%` symbol. Useful to display `%` symbol without breaking all placeholders after it |
## Backend only
These placeholders only work when TAB is installed on a backend server (Bukkit / Sponge / Fabric), they don't work when TAB is on BungeeCord. If you have TAB installed on BungeeCord and wish to use these, use their [PlaceholderAPI](https://github.com/NEZNAMY/TAB/wiki/Quick-PlaceholderAPI-startup-guide) alternative with [Bridge plugin](https://github.com/NEZNAMY/TAB/wiki/TAB-Bridge) installed for PlaceholderAPI support on BungeeCord.
| Identifier | Refresh interval | Description | PlaceholderAPI alternative |
| Identifier | Refresh interval | Description | PlaceholderAPI alternative |
| ------------- | ------------- | ------------- | ------------- |
| %health% | 100 | Player's health, rounded up to match Minecraft's heart display. | %player_health_rounded% |
| %displayname% | 500 | Player's display name value set by permission plugin. | %player_displayname% |
| %health% | 100 | Player's health, rounded up to match Minecraft's heart display. | %player_health_rounded% |
| %displayname% | 500 | Player's display name value set by permission plugin. | %player_displayname% |
| %tps% | 1000 | TPS of server from the last minute (measured by server, not available on Fabric as it doesn't measure the value) | %server_tps_1% |
| %mspt% | 1000 | Server's current milliseconds per tick (Paper / Sponge 8+ / Fabric only) | - |
## BungeeCord only
| Identifier | Refresh interval | Description |
| ------------- | ------------- | ------------- |
| %server% | -1 | Name of server where player is, defined in proxy's config. |
| %serveronline% | 1000 | Amount of online players on server where the player is (excluding vanished players) |
| %online_\<servername\>% | 1000 | Amount of online players on specified server (excluding vanished players) |
| %playerlist-group_\<group\>% | 1000 | Amount of online players in specified [global playerlist](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Global-playerlist) group (therefore the feature must be enabled) |
| Identifier | Refresh interval | Description |
|------------------------------|------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| %server% | -1 | Name of server where player is, defined in proxy's config. |
| %serveronline% | 1000 | Amount of online players on server where the player is (excluding vanished players) |
| %online_\<server name\>% | 1000 | Amount of online players on specified server (excluding vanished players) |
| %playerlist-group_\<group\>% | 1000 | Amount of online players in specified [global playerlist](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Global-playerlist) group (therefore the feature must be enabled) |
# PlaceholderAPI
PlaceholderAPI is supported and relational placeholders are supported too. When parsing relational placeholders, TAB puts viewer as the first player argument and target player as second argument.
PlaceholderAPI is supported and relational placeholders are supported too.
When parsing relational placeholders,
TAB puts viewer as the first player argument and target player as the second argument.
You can find most of placeholders that PlaceholderAPI offers on [PlaceholderAPI wiki](https://github.com/PlaceholderAPI/PlaceholderAPI/wiki/Placeholders). Some plugins are not listed there, for those you'll need to check their own wiki for placeholders.
You can find most of the placeholders
that PlaceholderAPI offers on [PlaceholderAPI wiki](https://github.com/PlaceholderAPI/PlaceholderAPI/wiki/Placeholders).
Some plugins are not listed there, for those you'll need to check their own wiki for placeholders.
TAB also offers its own placeholders into PlaceholderAPI. In order to enable TAB's expansion, set
TAB also offers its own placeholders into PlaceholderAPI. To enable TAB's expansion, set
```
placeholders:
register-tab-expansion: true
@@ -107,7 +117,13 @@ Full list of placeholders:
# Relational placeholders
## About
Unlike classic placeholders which take either 0 (placeholders with same output for all players) or 1 (per-player placeholders) players, relational placeholders take in 2 players. The first usage was to display relation between two players, specifically enemies / allies (red / green) for factions, allowing players to see faction names of other players colored based on relation. While the usage is not limited to this, the name of the 2-player placeholder type has been established as "relational" and will be called that everywhere.
Unlike classic placeholders which take either 0
(placeholders with the same output for all players) or 1 (per-player placeholders) players,
relational placeholders take in 2 players.
The first usage was to display relation between two players, specifically enemies / allies (red / green) for factions,
allowing players to see faction names of other players colored based on relation.
While the usage is not limited to this, the name of the 2-player placeholder type has been established as "relational"
and will be called that everywhere.
## Usage
TAB does not offer any internal relational placeholders. However, it has full support for PlaceholderAPI placeholders. You can also register your own relational placeholders using the [API](#relational-placeholders-1). In PlaceholderAPI, all relational placeholder identifiers must start with `%rel_`. This rule is adopted and used in TAB as well (including those registered via API).
@@ -120,26 +136,34 @@ public String onPlaceholderRequest(Player viewer, Player target, String identifi
where `viewer` is the player looking and `target` is the player the value is being displayed on.
When trying to use placeholders from other plugins, you will need to check their documentation and search for relational placeholders. If your plugin does not appear to offer any, you ran out of luck and TAB cannot do anything about it.
In order to properly distinguish viewer/target (if needed), your relational placeholder expansion must follow the same order as TAB does. Since PlaceholderAPI never created any convention regarding this and instead calls the players `one` and `two`, I had to decide and this is the decision.
To properly distinguish viewer/target (if needed), your relational placeholder expansion must follow the same order as TAB does. Since PlaceholderAPI never created any convention regarding this, and instead it calls the players `one` and `two`, I had to decide and this is the decision.
# Refreshing
## Refresh intervals
TAB is refreshing placeholders in intervals. For internal placeholders it's using values I found the most optimal and set directly in the plugin. Refresh intervals for PlaceholderAPI placeholders can be [configured](https://github.com/NEZNAMY/TAB/wiki/Optimizing-the-plugin#2---placeholder-refresh-intervals).
## Sync refreshing
By default, all placeholders are refreshed asynchronously so they don't slow down the server, since reading should be a thread-safe operation. Some placeholders, however, require to be refreshed in the main thread, causing problems or throwing errors when refreshed asynchronously. For these, replace them with `%sync:<original placeholder>%` and they will be refreshed in the main thread, for example `%server_total_entities%` -> `%sync:server_total_entities%`.
Keep in mind that now you should configure reasonable refresh interval for these placeholders to not cause TPS drops (with async placeholders it doesn't really matter). Configured refresh intervals and CPU usage of placeholders can be checked using `/tab cpu`.
By default, all placeholders are refreshed asynchronously,
so they don't slow down the server, since reading should be a thread-safe operation.
Some placeholders, however, require to be refreshed in the main thread,
causing problems or throwing errors when refreshed asynchronously.
For these, replace them with `%sync:<original placeholder>%` and they will be refreshed in the main thread,
for example `%server_total_entities%` -> `%sync:server_total_entities%`.
Keep in mind that now you should configure a reasonable refresh interval for these placeholders to not cause TPS drops
(with async placeholders it doesn't really matter).
Configured refresh intervals and CPU usage of placeholders can be checked using `/tab cpu`.
# Placeholder is not working
When using a placeholder, you should know if it's a [TAB's internal placeholder](#internal-placeholders) or a [PlaceholderAPI placeholder](https://github.com/PlaceholderAPI/PlaceholderAPI/wiki/Placeholders). If you don't, that's the problem.
**Note:** Don't forget that using `%` symbol will mess up placeholder starts and ends, breaking all placeholders after it. To make the symbol display correctly without messing up placeholders, use `%%` to display the symbol.
**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%`.
## Internal placeholder is not working
All TAB's internal placeholders are listed above. Some of them are bukkit only (meaning they won't work on bungeecord) and some are bungee only. **Trying to use a bukkit-only placeholder on bungeecord will not work.**
If you want to use those, [set up PlaceholderAPI support on bungeecord](https://github.com/NEZNAMY/TAB/wiki/TAB-Bridge) and find their PlaceholderAPI equivalent. Large portion of those are included on [PlaceholderAPI's wiki](https://github.com/PlaceholderAPI/PlaceholderAPI/wiki/Placeholders).
For example you can use %vault_prefix% instead of %vault-prefix%, %player_health% instead of %health% and so on.
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.
## 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.
@@ -153,7 +177,7 @@ For working with placeholders, you will need the `PlaceholderManager`. Get it us
Placeholders have two attributes:
* An identifier, which is used to uniquely identify the placeholder. This is also what's usually used for replacement. For example, in `%uptime%`, the identifier is `%uptime%`.
* A refresh rate in milliseconds, which is how often the placeholder's refresh function will be called, to get a new value to display. Use `-1` to make the placeholder never refresh periodically. This can be used for either constants or placeholders that update on events and you wish to update their value manually using functions in respective placeholder interface.
* A refresh rate in milliseconds, which is how often the placeholder's refresh function will be called, to get a new value to display. Use `-1` to make the placeholder never refresh periodically. This can be used for either constants or placeholders that update on events, and you wish to update their value manually using functions in the respective placeholder interface.
## Server placeholders
Server placeholders are global to the entire plugin. Their values are not dependent on player.
@@ -172,7 +196,7 @@ This placeholder will show current system time and update every 50 milliseconds.
## Player placeholders
Player placeholders are calculated on a per-player basis. These depend on the player that we want to get the value for.
Examples of player placeholders would be nickname and prefix.
Examples of player placeholders would be username and prefix.
To register a new player placeholder, use `PlaceholderManager#registerPlayerPlaceholder(String, int, Function<TabPlayer, Object>)`. The first two parameters are explained above, and the last parameter is a function that will be called to refresh the value of the placeholder.
<details>
@@ -202,5 +226,7 @@ TabAPI.getInstance().getPlaceholderManager().registerRelationalPlaceholder("%rel
});
```
This placeholder will show version of players, but only to those, who have `tab.staff` permission. First player in the method is viewer, second player is target. Same order is applied when hooking into PlaceholderAPI as well.
This placeholder will show the version of players, but only to those, who have `tab.staff` permission.
The first player in the method is viewer, the second player is target.
The same order is applied when hooking into PlaceholderAPI as well.
</details>
@@ -2,13 +2,26 @@
This is a quick guide for [PlaceholderAPI](https://www.spigotmc.org/resources/placeholderapi.6245/) plugin, which is often referred to on the wiki, and we also get many assistance requests with TAB which turn out to be asking for help with PlaceholderAPI. Whether its documentation is not clear enough, missing information or users just don't read it isn't the topic here to judge, instead, a quick guide with everything needed is provided.
# Installation
PlaceholderAPI is a standalone bukkit plugin which can be installed just like any other plugin. It does not support BungeeCord. If you have TAB on BungeeCord and wish to use PlaceholderAPI placeholders, you'll need to install [Bridge](https://www.spigotmc.org/resources/83966) plugin on backend servers. Alongside other features, it forwards PlaceholderAPI support to the proxy.
PlaceholderAPI is a standalone bukkit plugin that can be installed just like any other plugin.
It does not support BungeeCord.
If you have TAB on BungeeCord and wish to use PlaceholderAPI placeholders,
you'll need to install [Bridge](https://www.spigotmc.org/resources/83966) plugin on backend servers.
Alongside other features, it forwards PlaceholderAPI support to the proxy.
# Expansions
In order to effectively scale and allow the level of modification it does, PlaceholderAPI is using placeholder packages called expansions. They are loaded into the plugin on startup. Each expansion can be created and modified independently of the main plugin's update frequency. Each expansion has a name, which is used to identify its placeholders.
The syntax is `%<expansion>_<placeholder>%`, where `<expansion>` is name of the expansion, for example `player` and `<placeholder>` is placeholder of the expansion, for example `health`, which returns player's health. The full placeholder syntax will then be `%player_health%`.
To effectively scale and allow the level of modification it does,
PlaceholderAPI is using placeholder packages called expansions.
They are loaded into the plugin on startup.
Each expansion can be created and modified independently of the main plugin's update frequency.
Each expansion has a name, which is used to identify its placeholders.
The syntax is `%<expansion>_<placeholder>%`, where `<expansion>` is name of the expansion,
for example `player` and `<placeholder>` is placeholder of the expansion, for example `health`,
which returns player's health.
The full placeholder syntax will then be `%player_health%`.
Some plugins offer their own expansions, which is either integrated into the plugin itself or external on PlaceholderAPI cloud. Integrated expansions are not downloaded, instead, plugins register them automatically.
Some plugins offer their own expansions,
which are either integrated into the plugin itself or external on PlaceholderAPI cloud.
Integrated expansions are not downloaded; instead, plugins register them automatically.
You can download / update external expansions using `/papi ecloud download <expansion>`. After downloading it, run `/papi reload` to enable it.
+16 -10
View File
@@ -1,22 +1,28 @@
# 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. Put it into plugins folder of your backend servers. **Bridge is NOT a BungeeCord plugin!** Bridge itself has no config file, as everything is configured in the main plugin.
Installation is as simple as it can be.
Put it into the plugins folder of your backend servers.
Bridge is NOT a BungeeCord plugin!
Bridge itself has no config file, as everything is configured in the main plugin.
# Features
* Forwarding PlaceholderAPI support to the proxy​
* Allow detection if player is invisible to make nametag invisible (compensation for 1.8.x client sided bug)​
* Allow detection if player is disguised to disable collision to prevent endless push​
* Forwarding permission groups of players if no permission plugin is installed on BungeeCord​
* Forwarding vanish status for compatibility with global playerlist / layout features​
* Forwarding PlaceholderAPI support to the proxy
* Allow detection if player is invisible to make their nametag invisible (compensation for 1.8.x client sided bug)
* Allow detection if a player is disguised to disable collision to prevent endless push
* Forwarding permission groups of players if no permission plugin is installed on BungeeCord
* Forwarding vanish status for compatibility with global playerlist / layout features
* Forward player's world for %world% and per-world settings
* Allow TAB's PlaceholderAPI expansion with TAB on BungeeCord​
* Allow TAB's PlaceholderAPI expansion with TAB on BungeeCord
* Forward player's gamemode for spectator fix feature to work
# Compatibility
Just like with Minecraft, when there is a change in the communication protocol, older software will not be compatible with the new one. You should always be running latest versions of both TAB and Bridge.
Just like with Minecraft, when there is a change in the communication protocol,
older software will not be compatible with the new one.
You should always be running the latest versions of both TAB and Bridge.
# Confirming it works
You have many ways to verify TAB and Bridge are properly connected. Besides checking if the features above work, the easiest one is to run `/btab debug <player>`.
@@ -24,4 +30,4 @@ If it's properly connected, you'll get a message saying this:
![image](https://github.com/NEZNAMY/TAB/assets/6338394/3f8dd978-a8bd-4613-9ea1-a56340194859)
If it's not properly connected, you'll get this message:
![image](https://github.com/NEZNAMY/TAB/assets/6338394/a90018bc-2c3b-4bf5-b54c-56621178ce80)
If this is the case, it means Bridge is either not installed or versions are not compatible. Just update both to the latest version.
If this is the case, it means Bridge is either not installed or versions are not compatible. Update both to the latest version.
+32 -5
View File
@@ -1,20 +1,47 @@
## Easy to install
Support for all possible Minecraft versions containing features the plugin is able to take advantage of, support for multiple platforms (Bukkit, BungeeCord, Velocity, Fabric, Sponge), with everything contained in a single jar which you can download directly. No need to browse version history, ask what plugin version you need for your server version, visit random websites looking for the download button, or anything similar. The same jar supports all versions & platforms, and the latest plugin version will always work for you.
Support for all possible Minecraft versions containing features the plugin is able to take advantage of.
Support for multiple platforms (Bukkit, BungeeCord, Velocity, Fabric, Sponge),
with everything contained in a single jar which you can download directly.
No need to browse version history, ask what plugin version you need for your server version,
visit random websites looking for the download button, or anything similar.
The same jar supports all versions & platforms, and the latest plugin version will always work for you.
## It works
The plugin doesn't spam your console with errors, doesn't have compatibility issues with half of the plugins, and doesn't just decide to randomly stop working for no reason. It just works. Bug reports are fixed with highest priority and preferred over adding new features. It's not unusual for a plugin of this size to have a 3-digit open issue count on github. Meanwhile TAB is usually at 1-digit count.
The plugin doesn't spam your console with errors, doesn't have compatibility issues with half of the plugins,
and doesn't just decide to randomly stop working for no reason.
It just works.
Bug reports are fixed with the highest priority and preferred over adding new features.
It's not unusual for a plugin of this size to have a 3-digit open issue count on GitHub.
Meanwhile, TAB is always at 1-digit count.
## Feature packed
All of the most requested features in a single plugin. No need to install many plugins, disable features so they don't conflict with each other, or be forced to run your server on a version that all of the plugins support in common. All features, all server versions in a single plugin.
All the most requested features in a single plugin.
No need to install many plugins, disable features, so they don't conflict with each other,
or be forced to run your server on a version that all the plugins support in common.
All features, all server versions in a single plugin.
## Beyond the limits
Mojang decided to put some kind of limitation on almost every feature TAB offers. Other developers simply say "Mojang limit, nothing we can do about it, goodbye". This is not the case here. With every limitation I find, I look for ways to bypass it, and I've found most of them. These bypasses require a ton of work involved, and it's much easier to say "not possible" than actually doing it.
## Up to date
The plugin is not abandoned. It is constantly getting updated to fix reported bugs, add support for new MC versions as they come out and get new optimizations that can help it run as smoothly as possible, whilst still working the way you want it to.
The plugin is not abandoned.
It is constantly getting updated to fix reported bugs,
add support for new MC versions as they come out and get new optimizations.
All that can help it run as smoothly as possible, whilst still working the way you want it to.
## High performance
Unlike with other plugins where "high performance" often means your CPU usage will be high, TAB has a very low CPU footprint. There is a [command](https://github.com/NEZNAMY/TAB/wiki/Commands-&-Permissions#tab-cpu) available that shows the entire plugin's CPU usage. Here you can see which part of TAB is occupying the most of your CPU, and there are [many ways to optimize TAB](https://github.com/NEZNAMY/TAB/wiki/Optimizing-the-plugin) to get the most out of it, the most common being increasing refresh rates for individual placeholders if they're inefficient. This is done on a per-placeholder basis, instead of slowing down the refresh rate of an entire feature, which may break animations. Performance is also actively monitored on large servers, and observations show that it performs very well, meaning it will almost certainly perform even better on smaller servers.
Unlike with other plugins where "high performance"
often means your CPU usage will be high, TAB has a very low CPU footprint.
There is a [command](https://github.com/NEZNAMY/TAB/wiki/Commands-&-Permissions#tab-cpu) available
that shows the entire plugin's CPU usage.
Here you can see which part of TAB is occupying the most of your CPU.
There are [many ways to optimize TAB](https://github.com/NEZNAMY/TAB/wiki/Optimizing-the-plugin)
to get the most out of it,
the most common being increasing refresh rates for individual placeholders if they're inefficient.
This is done on a per-placeholder basis,
instead of slowing down the refresh rate of an entire feature, which may break animations.
Performance is also actively monitored on large servers, and observations show that it performs very well,
meaning it will almost certainly perform even better on smaller servers.
## Simple yet powerful configuration
Compared to other plugins, each feature is easier to configure and actually offers more.
@@ -17,21 +17,24 @@ public interface TabPlayer {
*
* @return Player's name
*/
@NotNull String getName();
@NotNull
String getName();
/**
* Returns player's UUID
*
* @return Player's UUID
*/
@NotNull UUID getUniqueId();
@NotNull
UUID getUniqueId();
/**
* Returns platform-specific entity
*
* @return platform's player object
*/
@NotNull Object getPlayer();
@NotNull
Object getPlayer();
/**
* Returns true once the player is successfully loaded (onJoin method ran through all methods)
@@ -47,7 +50,8 @@ public interface TabPlayer {
*
* @return player's primary permission group
*/
@NotNull String getGroup();
@NotNull
String getGroup();
/**
* Temporarily overrides player's group and applies all changes coming from new group.
@@ -91,4 +95,32 @@ public interface TabPlayer {
*/
@NotNull
String getExpectedProfileName();
/**
* Returns name of the server the player is connected to.
* This requires TAB on proxy.
* On backend installation, this method returns {@code "N/A"}.
*
* @return Player's server on proxy or {@code "N/A"} on backend installation
*/
@NotNull
String getServer();
/**
* Returns name of the world the player is in.
* On proxy installation, TAB-Bridge must be installed to forward this info to the proxy.
* Otherwise, it will return {@code "N/A"}.
*
* @return Player's world or {@code "N/A"} on proxy installation without TAB-Bridge installed
*/
@NotNull
String getWorld();
/**
* Returns {@code true} if this player is a bedrock player, {@code false} if not.
*
* @return {@code true} if this player is a bedrock player, {@code false} if not
*/
boolean isBedrockPlayer();
}
+1 -1
View File
@@ -4,7 +4,7 @@ plugins {
allprojects {
group = "me.neznamy"
version = "5.0.3"
version = "5.0.5"
description = "An all-in-one solution that works"
ext.set("id", "tab")
+2 -2
View File
@@ -1,5 +1,5 @@
plugins {
id("io.papermc.paperweight.userdev") version "1.7.1"
id("io.papermc.paperweight.userdev") version "2.0.0-beta.8"
}
repositories {
@@ -9,7 +9,7 @@ repositories {
maven("https://oss.sonatype.org/content/repositories/snapshots")
}
val version = "1.21.3-R0.1-SNAPSHOT"
val version = "1.21.4-R0.1-SNAPSHOT"
dependencies {
implementation(projects.bukkit)
@@ -1,20 +1,21 @@
package me.neznamy.tab.platforms.paper;
import me.neznamy.tab.platforms.bukkit.nms.converter.ComponentConverter;
import me.neznamy.tab.shared.chat.ChatModifier;
import me.neznamy.tab.shared.chat.SimpleComponent;
import me.neznamy.tab.shared.chat.StructuredComponent;
import me.neznamy.tab.shared.chat.TabComponent;
import net.minecraft.ChatFormatting;
import 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.ArrayList;
import java.util.List;
import java.util.Map;
/**
* Component converter using direct mojang-mapped code for versions 1.20.5+.
@@ -24,39 +25,82 @@ public class PaperComponentConverter extends ComponentConverter {
@Override
@NotNull
public Component convert(@NotNull TabComponent component, boolean modern) {
if (component instanceof SimpleComponent) return Component.literal(((SimpleComponent) component).getText());
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());
}
}
StructuredComponent component1 = (StructuredComponent) component;
MutableComponent nmsComponent = Component.literal(component1.getText());
nmsComponent.setStyle(createModifierModern(component1.getModifier(), modern));
for (StructuredComponent extra : component1.getExtra()) {
nmsComponent.append(convert(extra, modern));
@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;
}
@NotNull
private Style createModifierModern(@NotNull ChatModifier modifier, boolean modern) {
TextColor color = null;
if (modifier.getColor() != null) {
if (modern) {
color = TextColor.fromRgb(modifier.getColor().getRgb());
} else {
color = TextColor.fromRgb(modifier.getColor().getLegacyColor().getRgb());
}
}
List<ChatFormatting> formats = new ArrayList<>();
if (modifier.isBold()) formats.add(ChatFormatting.BOLD);
if (modifier.isItalic()) formats.add(ChatFormatting.ITALIC);
if (modifier.isUnderlined()) formats.add(ChatFormatting.UNDERLINE);
if (modifier.isStrikethrough()) formats.add(ChatFormatting.STRIKETHROUGH);
if (modifier.isObfuscated()) formats.add(ChatFormatting.OBFUSCATED);
@Nullable
private Boolean getDecoration(@Nullable TextDecoration.State state) {
if (state == null || state == TextDecoration.State.NOT_SET) return null;
return state == TextDecoration.State.TRUE;
}
Style style = Style.EMPTY;
if (color != null) style = style.withColor(color);
if (!formats.isEmpty()) style = style.applyFormats(formats.toArray(new ChatFormatting[0]));
if (modifier.getFont() != null) style = style.withFont(ResourceLocation.tryParse(modifier.getFont()));
return style;
@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));
}
}
@@ -2,7 +2,7 @@ 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 me.neznamy.tab.shared.util.function.FunctionWithException;
import org.bukkit.craftbukkit.entity.CraftPlayer;
/**
@@ -58,7 +58,7 @@ public class PaperPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
dummyScoreboard,
objective.getName(),
ObjectiveCriteria.DUMMY,
objective.getTitle().convert(player.getVersion()),
objective.getTitle().convert(),
ObjectiveCriteria.RenderType.values()[objective.getHealthDisplay().ordinal()],
false,
objective.getNumberFormat() == null ? null : objective.getNumberFormat().toFixedFormat(FixedFormat::new)
@@ -76,7 +76,7 @@ public class PaperPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
@Override
public void updateObjective(@NonNull Objective objective) {
net.minecraft.world.scores.Objective obj = (net.minecraft.world.scores.Objective) objective.getPlatformObjective();
obj.setDisplayName(objective.getTitle().convert(player.getVersion()));
obj.setDisplayName(objective.getTitle().convert());
obj.setRenderType(ObjectiveCriteria.RenderType.values()[objective.getHealthDisplay().ordinal()]);
sendPacket(new ClientboundSetObjectivePacket(obj, ObjectiveAction.UPDATE));
}
@@ -88,7 +88,7 @@ public class PaperPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
score.getHolder(),
score.getObjective().getName(),
score.getValue(),
Optional.ofNullable(score.getDisplayName() == null ? null : score.getDisplayName().convert(player.getVersion())),
Optional.ofNullable(score.getDisplayName() == null ? null : score.getDisplayName().convert()),
Optional.ofNullable(score.getNumberFormat() == null ? null : score.getNumberFormat().toFixedFormat(FixedFormat::new))
)
);
@@ -128,11 +128,11 @@ public class PaperPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
PlayerTeam t = (PlayerTeam) team.getPlatformTeam();
t.setAllowFriendlyFire((team.getOptions() & 0x01) != 0);
t.setSeeFriendlyInvisibles((team.getOptions() & 0x02) != 0);
t.setColor(formats[team.getColor().ordinal()]);
t.setColor(formats[team.getColor().getLegacyColor().ordinal()]);
t.setCollisionRule(collisions[team.getCollision().ordinal()]);
t.setNameTagVisibility(visibilities[team.getVisibility().ordinal()]);
t.setPlayerPrefix(team.getPrefix().convert(player.getVersion()));
t.setPlayerSuffix(team.getSuffix().convert(player.getVersion()));
t.setPlayerPrefix(team.getPrefix().convert());
t.setPlayerSuffix(team.getSuffix().convert());
}
@Override
@@ -24,7 +24,7 @@ import java.lang.reflect.Field;
import java.util.*;
/**
* TabList implementation using direct mojang-mapped code for versions 1.21.2+.
* TabList implementation using direct mojang-mapped code for versions 1.21.4+.
*/
@SuppressWarnings("unused") // Used via reflection
public class PaperPacketTabList extends TabListBase<Component> {
@@ -34,6 +34,7 @@ public class PaperPacketTabList extends TabListBase<Component> {
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;
@@ -64,55 +65,56 @@ public class PaperPacketTabList extends TabListBase<Component> {
@Override
public void updateDisplayName(@NonNull UUID entry, @Nullable Component displayName) {
sendPacket(new ClientboundPlayerInfoUpdatePacket(updateDisplayName, new ClientboundPlayerInfoUpdatePacket.Entry(
entry, null, false, 0, null, displayName, 0, null
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, 0, null
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, 0, null
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, 0, null
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, listOrder, null
entry, null, false, 0, null, null, false, listOrder, null
)));
}
@Override
public void updateHat(@NonNull UUID entry, boolean showHat) {
// TODO update module to 1.21.4 when it comes out
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, listOrder, null
id, createProfile(id, name, skin), listed, latency, GameType.byId(gameMode), displayName, showHat, listOrder, null
)));
// TODO update module to 1.21.4 when it comes out
}
@Override
public void setPlayerListHeaderFooter(@NonNull TabComponent header, @NonNull TabComponent footer) {
sendPacket(new ClientboundTabListPacket(header.convert(player.getVersion()), footer.convert(player.getVersion())));
sendPacket(new ClientboundTabListPacket(header.convert(), footer.convert()));
}
@Override
@@ -144,7 +146,8 @@ public class PaperPacketTabList extends TabListBase<Component> {
TAB.getInstance().getFeatureManager().onEntryAdd(player, nmsData.profileId(), nmsData.profile().getName());
}
updatedList.add(rewriteEntry ? new ClientboundPlayerInfoUpdatePacket.Entry(
nmsData.profileId(), nmsData.profile(), nmsData.listed(), latency, nmsData.gameMode(), displayName, nmsData.listOrder(), nmsData.chatSession()
nmsData.profileId(), nmsData.profile(), nmsData.listed(), latency, nmsData.gameMode(), displayName,
nmsData.showHat(), nmsData.listOrder(), nmsData.chatSession()
) : nmsData);
}
if (rewritePacket) entries.set(info, updatedList);
@@ -7,7 +7,7 @@ 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.function.FunctionWithException;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.jetbrains.annotations.NotNull;
@@ -26,7 +26,8 @@ public class BukkitPipelineInjector extends NettyPipelineInjector {
ProtocolVersion.V1_21,
ProtocolVersion.V1_21_1,
ProtocolVersion.V1_21_2,
ProtocolVersion.V1_21_3
ProtocolVersion.V1_21_3,
ProtocolVersion.V1_21_4
);
/** Function for getting player's channel */
@@ -57,7 +57,7 @@ public class BukkitTabPlayer extends BackendTabPlayer {
@Override
public void sendMessage(@NotNull TabComponent message) {
getPlayer().sendMessage(getPlatform().toBukkitFormat(message, getVersion().supportsRGB()));
getPlayer().sendMessage(getPlatform().toBukkitFormat(message));
}
@Override
@@ -4,7 +4,6 @@ import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.SneakyThrows;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
@@ -58,8 +57,7 @@ public class BukkitUtils {
public static void compatibilityError(@NotNull Exception exception, @NotNull String failedCheck,
@Nullable String fallback, @NotNull String... missingFeatures) {
StringBuilder sb = new StringBuilder();
sb.append(EnumChatFormat.RED);
sb.append("[TAB] Failed to initialize minecraft fields for ");
sb.append("§c[TAB] Failed to initialize minecraft fields for ");
sb.append(failedCheck);
sb.append(" due to a compatibility error. ");
if (fallback != null) {
@@ -81,7 +79,7 @@ public class BukkitUtils {
*/
public static void sendCompatibilityMessage() {
if (!compatibilityIssue) return;
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.RED + "[TAB] Please update the plugin to " +
Bukkit.getConsoleSender().sendMessage("§c[TAB] Please update the plugin to " +
"a version with native support for your server version for optimal experience. This plugin version " +
"was made for " + ProtocolVersion.V1_5.getFriendlyName() + " - " + ProtocolVersion.LATEST_KNOWN_VERSION.getFriendlyName() + ".");
}
@@ -28,7 +28,7 @@ public class BukkitBossBar extends SafeBossBar<BossBar> {
@NotNull
public BossBar constructBossBar(@NotNull TabComponent title, float progress, @NotNull BarColor color, @NotNull BarStyle style) {
BossBar bar = Bukkit.createBossBar(
player.getPlatform().toBukkitFormat(title, player.getVersion().supportsRGB()),
player.getPlatform().toBukkitFormat(title),
org.bukkit.boss.BarColor.valueOf(color.name()),
styles[style.ordinal()]
);
@@ -43,7 +43,7 @@ public class BukkitBossBar extends SafeBossBar<BossBar> {
@Override
public void updateTitle(SafeBossBar<BossBar>.@NotNull BossBarInfo bar) {
bar.getBossBar().setTitle(player.getPlatform().toBukkitFormat(bar.getTitle(), player.getVersion().supportsRGB()));
bar.getBossBar().setTitle(player.getPlatform().toBukkitFormat(bar.getTitle()));
}
@Override
@@ -25,8 +25,8 @@ public class BukkitHeaderFooter extends HeaderFooter {
@SuppressWarnings("deprecation") // Marked as deprecated by Paper to make us use their methods instead
public void set(@NotNull BukkitTabPlayer player, @NotNull TabComponent header, @NotNull TabComponent footer) {
player.getPlayer().setPlayerListHeaderFooter(
player.getPlatform().toBukkitFormat(header, player.getVersion().supportsRGB()),
player.getPlatform().toBukkitFormat(footer, player.getVersion().supportsRGB())
player.getPlatform().toBukkitFormat(header),
player.getPlatform().toBukkitFormat(footer)
);
}
}
@@ -6,7 +6,7 @@ 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.tab.shared.util.function.BiFunctionWithException;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.jetbrains.annotations.NotNull;
@@ -52,6 +52,6 @@ public class PacketHeaderFooter extends HeaderFooter {
@Override
@SneakyThrows
public void set(@NotNull BukkitTabPlayer player, @NotNull TabComponent header, @NotNull TabComponent footer) {
packetSender.sendPacket(player, createPacket.apply(header.convert(player.getVersion()), footer.convert(player.getVersion())));
packetSender.sendPacket(player, createPacket.apply(header.convert(), footer.convert()));
}
}
@@ -20,6 +20,6 @@ public class PaperHeaderFooter extends HeaderFooter {
@Override
public void set(@NotNull BukkitTabPlayer player, @NotNull TabComponent header, @NotNull TabComponent footer) {
player.getPlayer().sendPlayerListHeaderAndFooter(header.toAdventure(player.getVersion()), footer.toAdventure(player.getVersion()));
player.getPlayer().sendPlayerListHeaderAndFooter(header.toAdventure(), footer.toAdventure());
}
}
@@ -3,7 +3,7 @@ package me.neznamy.tab.platforms.bukkit.nms;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import me.neznamy.tab.shared.util.FunctionWithException;
import me.neznamy.tab.shared.util.function.FunctionWithException;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.bukkit.Bukkit;
import org.jetbrains.annotations.NotNull;
@@ -2,7 +2,7 @@ package me.neznamy.tab.platforms.bukkit.nms;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.util.BiConsumerWithException;
import me.neznamy.tab.shared.util.function.BiConsumerWithException;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.jetbrains.annotations.NotNull;
@@ -3,7 +3,7 @@ package me.neznamy.tab.platforms.bukkit.nms;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.platforms.bukkit.BukkitUtils;
import me.neznamy.tab.shared.util.ToIntFunction;
import me.neznamy.tab.shared.util.function.ToIntFunction;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
@@ -21,7 +21,8 @@ public abstract class ComponentConverter {
ProtocolVersion.V1_21,
ProtocolVersion.V1_21_1,
ProtocolVersion.V1_21_2,
ProtocolVersion.V1_21_3
ProtocolVersion.V1_21_3,
ProtocolVersion.V1_21_4
);
/** Instance of this class */
@@ -33,12 +34,10 @@ public abstract class ComponentConverter {
*
* @param component
* Component to convert
* @param modern
* Whether client supports RGB or not
* @return Converted component
*/
@NotNull
public abstract Object convert(@NotNull TabComponent component, boolean modern);
public abstract Object convert(@NotNull TabComponent component);
/**
* Attempts to load component converter.
@@ -2,19 +2,24 @@ package me.neznamy.tab.platforms.bukkit.nms.converter;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
import me.neznamy.tab.shared.chat.ChatModifier;
import me.neznamy.tab.shared.chat.SimpleComponent;
import me.neznamy.tab.shared.chat.StructuredComponent;
import me.neznamy.tab.shared.chat.TabComponent;
import me.neznamy.tab.shared.util.FunctionWithException;
import me.neznamy.tab.shared.chat.*;
import me.neznamy.tab.shared.util.function.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.function.BiFunction;
import java.util.Map;
import java.util.function.Function;
/**
* Class for converting TAB component into NMS components (1.7+).
@@ -23,7 +28,9 @@ import java.util.function.BiFunction;
public class ReflectionComponentConverter extends ComponentConverter {
private final FunctionWithException<String, Object> newTextComponent;
private final BiFunction<ChatModifier, Boolean, Object> convertModifier;
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");
@@ -50,6 +57,13 @@ public class ReflectionComponentConverter extends ComponentConverter {
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);
@@ -57,9 +71,18 @@ public class ReflectionComponentConverter extends ComponentConverter {
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(ChatComponentText, IChatBaseComponent, IChatBaseComponent);
ChatBaseComponent_addSibling = ReflectionUtils.getOnlyMethod(ChatBaseComponent, IChatBaseComponent, IChatBaseComponent);
}
if (BukkitReflection.getMinorVersion() >= 16) {
Class<?> chatHexColor = BukkitReflection.getClass("network.chat.TextColor", "network.chat.ChatHexColor", "ChatHexColor");
@@ -81,63 +104,78 @@ public class ReflectionComponentConverter extends ComponentConverter {
} else {
newChatModifier = ChatModifier.getConstructor();
ChatModifier_setColor = ReflectionUtils.getOnlyMethod(ChatModifier, ChatModifier, EnumChatFormat);
convertModifier = (modifier, protocolVersion) -> createModifierLegacy(modifier);
convertModifier = this::createModifierLegacy;
}
}
@Override
@SneakyThrows
@NotNull
public Object convert(@NotNull TabComponent component, boolean modern) {
if (component instanceof SimpleComponent) return newTextComponent.apply(((SimpleComponent) component).getText());
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());
}
}
StructuredComponent component1 = (StructuredComponent) component;
Object nmsComponent = newTextComponent.apply(component1.getText());
Component_modifier.set(nmsComponent, convertModifier.apply(component1.getModifier(), modern));
for (StructuredComponent extra : component1.getExtra()) {
ChatBaseComponent_addSibling.invoke(nmsComponent, convert(extra, modern));
@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, boolean modern) {
Object color = null;
if (modifier.getColor() != null) {
if (modern) {
color = ChatHexColor_fromRGB.invoke(null, modifier.getColor().getRgb());
} else {
color = ChatHexColor_fromRGB.invoke(null, modifier.getColor().getLegacyColor().getRgb());
}
}
if (BukkitReflection.is1_21_4Plus()) {
return newChatModifier.newInstance(
color,
0,
modifier.isBold(),
modifier.isItalic(),
modifier.isUnderlined(),
modifier.isStrikethrough(),
modifier.isObfuscated(),
null,
null,
null,
modifier.getFont() == null ? null : ResourceLocation_tryParse.invoke(null, modifier.getFont())
);
} else {
return newChatModifier.newInstance(
color,
modifier.isBold(),
modifier.isItalic(),
modifier.isUnderlined(),
modifier.isStrikethrough(),
modifier.isObfuscated(),
null,
null,
null,
modifier.getFont() == null ? null : ResourceLocation_tryParse.invoke(null, modifier.getFont())
);
}
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
@@ -146,11 +184,45 @@ public class ReflectionComponentConverter extends ComponentConverter {
if (modifier.getColor() != null) {
ChatModifier_setColor.invoke(nmsModifier, Enum.valueOf(EnumChatFormat, modifier.getColor().getLegacyColor().name()));
}
if (modifier.isBold()) magicCodes.get(0).set(nmsModifier, true);
if (modifier.isItalic()) magicCodes.get(1).set(nmsModifier, true);
if (modifier.isStrikethrough()) magicCodes.get(2).set(nmsModifier, true);
if (modifier.isUnderlined()) magicCodes.get(3).set(nmsModifier, true);
if (modifier.isObfuscated()) magicCodes.get(4).set(nmsModifier, true);
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)
);
}
}
}
@@ -20,10 +20,7 @@ import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.backend.BackendPlatform;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.chat.SimpleComponent;
import me.neznamy.tab.shared.chat.StructuredComponent;
import me.neznamy.tab.shared.chat.TabComponent;
import me.neznamy.tab.shared.chat.*;
import me.neznamy.tab.shared.features.PerWorldPlayerListConfiguration;
import me.neznamy.tab.shared.features.PlaceholderManagerImpl;
import me.neznamy.tab.shared.features.injection.PipelineInjector;
@@ -41,6 +38,7 @@ 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;
@@ -109,7 +107,7 @@ public class BukkitPlatform implements BackendPlatform {
BukkitPipelineInjector.tryLoad(serverVersion);
}
BukkitUtils.sendCompatibilityMessage();
Bukkit.getConsoleSender().sendMessage("[TAB] " + EnumChatFormat.GRAY + "Loaded NMS hook in " + (System.currentTimeMillis()-time) + "ms");
Bukkit.getConsoleSender().sendMessage("[TAB] §7Loaded NMS hook in " + (System.currentTimeMillis()-time) + "ms");
}
@Override
@@ -208,12 +206,12 @@ public class BukkitPlatform implements BackendPlatform {
@Override
public void logInfo(@NotNull TabComponent message) {
Bukkit.getConsoleSender().sendMessage("[TAB] " + toBukkitFormat(message, true));
Bukkit.getConsoleSender().sendMessage("[TAB] " + toBukkitFormat(message));
}
@Override
public void logWarn(@NotNull TabComponent message) {
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.RED + "[TAB] [WARN] " + toBukkitFormat(message, true));
Bukkit.getConsoleSender().sendMessage("§c[TAB] [WARN] " + toBukkitFormat(message));
}
@Override
@@ -229,7 +227,7 @@ public class BukkitPlatform implements BackendPlatform {
@Override
public void registerCommand() {
PluginCommand command = Bukkit.getPluginCommand(TabConstants.COMMAND_BACKEND);
PluginCommand command = Bukkit.getPluginCommand(getCommand());
if (command != null) {
BukkitTabCommand cmd = new BukkitTabCommand();
command.setExecutor(cmd);
@@ -258,7 +256,7 @@ public class BukkitPlatform implements BackendPlatform {
@NotNull
public Object convertComponent(@NotNull TabComponent component, boolean modern) {
if (ComponentConverter.INSTANCE != null) {
return ComponentConverter.INSTANCE.convert(component, modern);
return ComponentConverter.INSTANCE.convert(component);
} else {
return component;
}
@@ -304,6 +302,11 @@ public class BukkitPlatform implements BackendPlatform {
return serverVersion.getNetworkId() >= ProtocolVersion.V1_21_2.getNetworkId();
}
@Override
public boolean supportsScoreboards() {
return true;
}
@Override
@NotNull
public GroupManager detectPermissionPlugin() {
@@ -348,48 +351,39 @@ public class BukkitPlatform implements BackendPlatform {
Bukkit.getScheduler().runTask(plugin, task);
}
// Disabling this check, because vanish plugins might process hiding with a delay on join, resulting
// in wrong result when called by TAB, causing vanished players to appear in layout for players who
// just joined, in the worst case scenario not hiding shortly after if vanish plugin takes too long.
/*
@Override
public boolean canSee(@NotNull TabPlayer viewer, @NotNull TabPlayer target) {
if (BackendPlatform.super.canSee(viewer, target)) return true;
return ((BukkitTabPlayer)viewer).getPlayer().canSee(((BukkitTabPlayer)target).getPlayer());
}
*/
/**
* Converts component to legacy string using bukkit RGB format if supported by both server and client.
* Converts component to string using bukkit RGB format if supported by the server.
* If not, closest legacy color is used instead.
*
* @param component
* Component to convert
* @param rgbClient
* Whether client accepts RGB colors or not.
* @return Converted string using bukkit color format
*/
@NotNull
public String toBukkitFormat(@NotNull TabComponent component, boolean rgbClient) {
if (component instanceof SimpleComponent) return component.toLegacyText();
StructuredComponent iComponent = (StructuredComponent) component;
StringBuilder sb = new StringBuilder();
if (iComponent.getModifier().getColor() != null) {
if (serverVersion.supportsRGB() && rgbClient) {
String hexCode = iComponent.getModifier().getColor().getHexCode();
char c = EnumChatFormat.COLOR_CHAR;
sb.append(c).append("x").append(c).append(hexCode.charAt(0)).append(c).append(hexCode.charAt(1))
.append(c).append(hexCode.charAt(2)).append(c).append(hexCode.charAt(3))
.append(c).append(hexCode.charAt(4)).append(c).append(hexCode.charAt(5));
} else {
sb.append(iComponent.getModifier().getColor().getLegacyColor());
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());
}
}
sb.append(iComponent.getModifier().getMagicCodes());
sb.append(iComponent.getText());
for (StructuredComponent extra : iComponent.getExtra()) {
sb.append(toBukkitFormat(extra));
}
return sb.toString();
}
sb.append(iComponent.getModifier().getMagicCodes());
sb.append(iComponent.getText());
for (StructuredComponent extra : iComponent.getExtra()) {
sb.append(toBukkitFormat(extra, rgbClient));
}
return sb.toString();
return LegacyComponentSerializer.builder().hexColors().useUnusualXRepeatedCharacterHexFormat().build().serialize(((AdventureComponent)component).getComponent());
}
}
@@ -160,7 +160,7 @@ public class BukkitScoreboard extends SafeScoreboard<BukkitTabPlayer> {
if (serverMinorVersion >= 9)
t.setOption(org.bukkit.scoreboard.Team.Option.COLLISION_RULE, OptionStatus.values()[team.getCollision().ordinal()]);
if (serverMinorVersion >= TEAM_COLOR_VERSION)
t.setColor(ChatColor.valueOf(team.getColor().name()));
t.setColor(ChatColor.valueOf(team.getColor().getLegacyColor().name()));
if (serverMinorVersion >= 7 && player.getPlatform().getServerVersion().getNetworkId() >= ProtocolVersion.V1_7_8.getNetworkId()) {
for (String player : team.getPlayers()) {
t.addEntry(player);
@@ -192,7 +192,7 @@ public class BukkitScoreboard extends SafeScoreboard<BukkitTabPlayer> {
if (serverMinorVersion >= 9)
t.setOption(org.bukkit.scoreboard.Team.Option.COLLISION_RULE, OptionStatus.values()[team.getCollision().ordinal()]);
if (serverMinorVersion >= TEAM_COLOR_VERSION)
t.setColor(ChatColor.valueOf(team.getColor().name()));
t.setColor(ChatColor.valueOf(team.getColor().getLegacyColor().name()));
t.setAllowFriendlyFire((team.getOptions() & 0x01) != 0);
t.setCanSeeFriendlyInvisibles((team.getOptions() & 0x02) != 0);
}
@@ -274,7 +274,7 @@ public class BukkitScoreboard extends SafeScoreboard<BukkitTabPlayer> {
*/
@NotNull
private String transform(@NonNull TabComponent text, int maxLengthModern, int maxLengthLegacy) {
String transformed = player.getPlatform().toBukkitFormat(text, player.getVersion().supportsRGB());
String transformed = player.getPlatform().toBukkitFormat(text);
if (player.getPlatform().getServerVersion().supportsRGB() && maxLengthModern < TITLE_LIMIT_MODERN) { // Scoreboard title is not stripping colors
while (ChatColor.stripColor(transformed).length() > maxLengthModern)
transformed = transformed.substring(0, transformed.length()-1);
@@ -47,29 +47,29 @@ public class PaperScoreboard extends BukkitScoreboard {
public org.bukkit.scoreboard.Objective newObjective(@NonNull String objectiveName, @NonNull String criteria,
@NonNull TabComponent title, @NonNull HealthDisplay display) {
return scoreboard.registerNewObjective(objectiveName, criteria,
title.toAdventure(player.getVersion()), RenderType.values()[display.ordinal()]);
title.toAdventure(), RenderType.values()[display.ordinal()]);
}
@Override
public void setDisplayName(@NonNull org.bukkit.scoreboard.Objective objective, @NonNull TabComponent displayName) {
objective.displayName(displayName.toAdventure(player.getVersion()));
objective.displayName(displayName.toAdventure());
}
@Override
public void setPrefix(@NonNull org.bukkit.scoreboard.Team team, @NonNull TabComponent prefix) {
team.prefix(prefix.toAdventure(player.getVersion()));
team.prefix(prefix.toAdventure());
}
@Override
public void setSuffix(@NonNull org.bukkit.scoreboard.Team team, @NonNull TabComponent suffix) {
team.suffix(suffix.toAdventure(player.getVersion()));
team.suffix(suffix.toAdventure());
}
@Override
@SneakyThrows
public void setScoreDisplayName(@NotNull org.bukkit.scoreboard.Score s, @Nullable TabComponent displayName) {
if (numberFormatAPI) {
Component component = displayName == null ? null : displayName.toAdventure(player.getVersion());
Component component = displayName == null ? null : displayName.toAdventure();
Method m = s.getClass().getMethod("customName", Component.class);
m.setAccessible(true); // Why is this needed? The method is public!
m.invoke(s, component);
@@ -103,6 +103,6 @@ public class PaperScoreboard extends BukkitScoreboard {
private Object numberFormat(@Nullable TabComponent numberFormat) {
if (numberFormat == null) return null;
return Class.forName("io.papermc.paper.scoreboard.numbers.NumberFormat").getMethod("fixed", ComponentLike.class)
.invoke(null, numberFormat.toAdventure(player.getVersion()));
.invoke(null, numberFormat.toAdventure());
}
}
@@ -11,7 +11,7 @@ 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.function.FunctionWithException;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.jetbrains.annotations.NotNull;
@@ -31,7 +31,8 @@ public class ScoreboardLoader {
ProtocolVersion.V1_21,
ProtocolVersion.V1_21_1,
ProtocolVersion.V1_21_2,
ProtocolVersion.V1_21_3
ProtocolVersion.V1_21_3,
ProtocolVersion.V1_21_4
);
/** Instance function */
@@ -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;
@@ -141,7 +141,7 @@ public class PacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
@Override
public void setScore(@NonNull Score score) {
packetSender.sendPacket(player, scorePacketData.setScore(score.getObjective().getName(), score.getHolder(), score.getValue(),
score.getDisplayName() == null ? null : score.getDisplayName().convert(player.getVersion()),
score.getDisplayName() == null ? null : score.getDisplayName().convert(),
score.getNumberFormat() == null ? null : toFixedFormat(score.getNumberFormat())));
}
@@ -202,7 +202,7 @@ public class PacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
emptyScoreboard,
objective.getName(),
null, // Criteria
objective.getTitle().convert(player.getVersion()),
objective.getTitle().convert(),
healthDisplays[objective.getHealthDisplay().ordinal()],
false, // Auto update
objective.getNumberFormat() == null ? null : toFixedFormat(objective.getNumberFormat())
@@ -214,14 +214,17 @@ public class PacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
emptyScoreboard,
objective.getName(),
null, // Criteria
objective.getTitle().convert(player.getVersion()),
objective.getTitle().convert(),
healthDisplays[objective.getHealthDisplay().ordinal()]
);
}
// 1.5 - 1.12.2
Object nmsObjective = newScoreboardObjective.newInstance(emptyScoreboard, objective.getName(), IScoreboardCriteria_dummy);
String cutTitle = player.getVersion().getMinorVersion() >= 13 ? objective.getTitle().toLegacyText() : cutTo(objective.getTitle().toLegacyText(), Limitations.SCOREBOARD_TITLE_PRE_1_13);
ScoreboardObjective_setDisplayName.invoke(nmsObjective, cutTitle);
String title = objective.getTitle().toLegacyText();
if (player.getVersion().getMinorVersion() < 13 || TAB.getInstance().getConfiguration().getConfig().isPacketEventsCompensation()) {
title = cutTo(title, Limitations.SCOREBOARD_TITLE_PRE_1_13);
}
ScoreboardObjective_setDisplayName.invoke(nmsObjective, title);
return nmsObjective;
}
@@ -4,8 +4,8 @@ import lombok.NonNull;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.util.BiFunctionWithException;
import me.neznamy.tab.shared.util.QuintFunction;
import me.neznamy.tab.shared.util.function.BiFunctionWithException;
import me.neznamy.tab.shared.util.function.QuintFunction;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.jetbrains.annotations.Nullable;
@@ -6,12 +6,13 @@ import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
import me.neznamy.tab.shared.Limitations;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.platform.decorators.SafeScoreboard;
import me.neznamy.tab.shared.platform.decorators.SafeScoreboard.Team;
import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.platform.Scoreboard.TeamAction;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.util.BiConsumerWithException;
import me.neznamy.tab.shared.util.function.BiConsumerWithException;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.jetbrains.annotations.NotNull;
@@ -224,13 +225,13 @@ public class TeamPacketData {
ScoreboardTeam_setAllowFriendlyFire.invoke(nmsTeam, (team.getOptions() & 0x1) > 0);
ScoreboardTeam_setCanSeeFriendlyInvisibles.invoke(nmsTeam, (team.getOptions() & 0x2) > 0);
if (MODERN_TEAM_DATA_VERSION) {
ScoreboardTeam_setPrefix.invoke(nmsTeam, (Object) team.getPrefix().convert(clientVersion));
ScoreboardTeam_setSuffix.invoke(nmsTeam, (Object) team.getSuffix().convert(clientVersion));
ScoreboardTeam_setColor.invoke(nmsTeam, chatFormats[team.getColor().ordinal()]);
ScoreboardTeam_setPrefix.invoke(nmsTeam, (Object) team.getPrefix().convert());
ScoreboardTeam_setSuffix.invoke(nmsTeam, (Object) team.getSuffix().convert());
ScoreboardTeam_setColor.invoke(nmsTeam, chatFormats[team.getColor().getLegacyColor().ordinal()]);
} else {
String prefix = team.getPrefix().toLegacyText();
String suffix = team.getSuffix().toLegacyText();
if (clientVersion.getMinorVersion() < 13) {
if (clientVersion.getMinorVersion() < 13 || TAB.getInstance().getConfiguration().getConfig().isPacketEventsCompensation()) {
prefix = SafeScoreboard.cutTo(prefix, Limitations.TEAM_PREFIX_SUFFIX_PRE_1_13);
suffix = SafeScoreboard.cutTo(suffix, Limitations.TEAM_PREFIX_SUFFIX_PRE_1_13);
}
@@ -70,6 +70,6 @@ public class BukkitTabList extends TabListBase<String> {
@Override
public String toComponent(@NonNull TabComponent component) {
return player.getPlatform().toBukkitFormat(component, true);
return player.getPlatform().toBukkitFormat(component);
}
}
@@ -8,7 +8,7 @@ import me.neznamy.tab.platforms.bukkit.nms.PacketSender;
import me.neznamy.tab.shared.Limitations;
import me.neznamy.tab.shared.chat.TabComponent;
import me.neznamy.tab.shared.util.ReflectionUtils;
import me.neznamy.tab.shared.util.TriFunctionWithException;
import me.neznamy.tab.shared.util.function.TriFunctionWithException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -25,14 +25,6 @@ public class SkinData {
private final Method getProfile = ReflectionUtils.getMethods(BukkitReflection.getClass(
"world.entity.player.Player", "world.entity.player.EntityHuman", "EntityHuman"), GameProfile.class).get(0);
/**
* Constructs new instance and loads NMS fields. If it fails, throws an Exception.
*
* @throws ReflectiveOperationException
* If something goes wrong
*/
public SkinData() throws ReflectiveOperationException {}
/**
* Returns player's skin. If server is in offline mode, returns {@code null}.
*
@@ -10,7 +10,7 @@ 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.function.FunctionWithException;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -29,8 +29,7 @@ public abstract class TabListBase<C> extends TrackedTabList<BukkitTabPlayer, C>
/** Versions supported by paper module that uses direct mojang-mapped NMS for latest MC version */
private static final EnumSet<ProtocolVersion> paperNativeVersions = EnumSet.of(
ProtocolVersion.V1_21_2,
ProtocolVersion.V1_21_3
ProtocolVersion.V1_21_4
);
/** Instance function */
@@ -13,7 +13,6 @@ import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.chat.*;
import me.neznamy.tab.shared.features.injection.PipelineInjector;
import me.neznamy.tab.shared.features.redis.RedisSupport;
import me.neznamy.tab.shared.hook.PremiumVanishHook;
import me.neznamy.tab.shared.platform.BossBar;
import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.platform.TabList;
@@ -91,7 +90,7 @@ public class BungeePlatform extends ProxyPlatform {
@Override
public void logWarn(@NotNull TabComponent message) {
plugin.getLogger().warning(EnumChatFormat.RED + message.toLegacyText());
plugin.getLogger().warning("§c" + message.toLegacyText());
}
@Override
@@ -107,7 +106,7 @@ public class BungeePlatform extends ProxyPlatform {
@Override
public void registerCommand() {
ProxyServer.getInstance().getPluginManager().registerCommand(plugin, new BungeeTabCommand());
ProxyServer.getInstance().getPluginManager().registerCommand(plugin, new BungeeTabCommand(getCommand()));
}
@Override
@@ -136,35 +135,41 @@ public class BungeePlatform extends ProxyPlatform {
@Override
@NotNull
public BaseComponent convertComponent(@NotNull TabComponent component, boolean modern) {
if (component instanceof SimpleComponent) return new TextComponent(component.toLegacyText());
StructuredComponent iComponent = (StructuredComponent) component;
TextComponent textComponent = new TextComponent(iComponent.getText());
ChatModifier modifier = iComponent.getModifier();
if (modifier.getColor() != null) {
if (modern) {
textComponent.setColor(ChatColor.of("#" + modifier.getColor().getHexCode()));
} else {
textComponent.setColor(ChatColor.of(modifier.getColor().getLegacyColor().name()));
}
if (component instanceof SimpleComponent) {
return new TextComponent(((SimpleComponent) component).getText());
}
if (modifier.isBold()) textComponent.setBold(true);
if (modifier.isItalic()) textComponent.setItalic(true);
if (modifier.isObfuscated()) textComponent.setObfuscated(true);
if (modifier.isStrikethrough()) textComponent.setStrikethrough(true);
if (modifier.isUnderlined()) textComponent.setUnderlined(true);
textComponent.setFont(modifier.getFont());
if (!iComponent.getExtra().isEmpty()) {
List<BaseComponent> list = new ArrayList<>();
for (StructuredComponent extra : iComponent.getExtra()) {
list.add(convertComponent(extra, modern));
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.setExtra(list);
}
return textComponent;
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;
}
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.");
}
@Override
@@ -204,4 +209,15 @@ public class BungeePlatform extends ProxyPlatform {
public boolean supportsListOrder() {
return true;
}
@Override
public boolean supportsScoreboards() {
return true;
}
@Override
@NotNull
public String getCommand() {
return "btab";
}
}
@@ -43,34 +43,26 @@ public class BungeeScoreboard extends SafeScoreboard<BungeeTabPlayer> {
@Override
public void registerObjective(@NonNull Objective objective) {
player.sendPacket(new ScoreboardObjective(
objective.getName(),
either(objective.getTitle(), Limitations.SCOREBOARD_TITLE_PRE_1_13),
ScoreboardObjective.HealthDisplay.values()[objective.getHealthDisplay().ordinal()],
(byte) ObjectiveAction.REGISTER,
numberFormat(objective.getNumberFormat())
));
sendObjectivePacket(objective, (byte) ObjectiveAction.REGISTER);
player.sendPacket(new ScoreboardDisplay(objective.getDisplaySlot().ordinal(), objective.getName()));
}
@Override
public void unregisterObjective(@NonNull Objective objective) {
player.sendPacket(new ScoreboardObjective(
objective.getName(),
either(objective.getTitle(), Limitations.SCOREBOARD_TITLE_PRE_1_13), // Empty value instead of null to prevent NPE kick on 1.7
null,
(byte) ObjectiveAction.UNREGISTER,
null
));
sendObjectivePacket(objective, (byte) ObjectiveAction.UNREGISTER);
}
@Override
public void updateObjective(@NonNull Objective objective) {
sendObjectivePacket(objective, (byte) ObjectiveAction.UPDATE);
}
private void sendObjectivePacket(@NonNull Objective objective, byte action) {
player.sendPacket(new ScoreboardObjective(
objective.getName(),
either(objective.getTitle(), Limitations.SCOREBOARD_TITLE_PRE_1_13),
ScoreboardObjective.HealthDisplay.values()[objective.getHealthDisplay().ordinal()],
(byte) ObjectiveAction.UPDATE,
action,
numberFormat(objective.getNumberFormat())
));
}
@@ -104,38 +96,31 @@ public class BungeeScoreboard extends SafeScoreboard<BungeeTabPlayer> {
@Override
public void registerTeam(@NonNull Team team) {
player.sendPacket(new net.md_5.bungee.protocol.packet.Team(
team.getName(),
(byte) TeamAction.CREATE,
either(new SimpleComponent(team.getName()), Limitations.TEAM_PREFIX_SUFFIX_PRE_1_13),
either(team.getPrefix(), Limitations.TEAM_PREFIX_SUFFIX_PRE_1_13),
either(team.getSuffix(), Limitations.TEAM_PREFIX_SUFFIX_PRE_1_13),
team.getVisibility().toString(),
team.getCollision().toString(),
player.getVersion().getMinorVersion() >= TEAM_REWORK_VERSION ? team.getColor().ordinal() : 0,
(byte) team.getOptions(),
team.getPlayers().toArray(new String[0])
));
sendTeamPacket(team, (byte) TeamAction.CREATE);
}
@Override
public void unregisterTeam(@NonNull Team team) {
player.sendPacket(new net.md_5.bungee.protocol.packet.Team(team.getName()));
sendTeamPacket(team, (byte) TeamAction.REMOVE);
}
@Override
public void updateTeam(@NonNull Team team) {
sendTeamPacket(team, (byte) TeamAction.UPDATE);
}
private void sendTeamPacket(@NonNull Team team, byte action) {
player.sendPacket(new net.md_5.bungee.protocol.packet.Team(
team.getName(),
(byte) TeamAction.UPDATE,
action,
either(new SimpleComponent(team.getName()), Limitations.TEAM_PREFIX_SUFFIX_PRE_1_13),
either(team.getPrefix(), Limitations.TEAM_PREFIX_SUFFIX_PRE_1_13),
either(team.getSuffix(), Limitations.TEAM_PREFIX_SUFFIX_PRE_1_13),
team.getVisibility().toString(),
team.getCollision().toString(),
player.getVersion().getMinorVersion() >= TEAM_REWORK_VERSION ? team.getColor().ordinal() : 0,
player.getVersion().getMinorVersion() >= TEAM_REWORK_VERSION ? team.getColor().getLegacyColor().ordinal() : 0,
(byte) team.getOptions(),
null
team.getPlayers().toArray(new String[0])
));
}
@@ -159,6 +144,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()));
@@ -1,7 +1,6 @@
package me.neznamy.tab.platforms.bungeecord;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import net.md_5.bungee.api.plugin.Plugin;
import net.md_5.bungee.protocol.packet.PlayerListItem;
@@ -16,7 +15,7 @@ public class BungeeTAB extends Plugin {
PlayerListItem.Item.class.getDeclaredField("showHat");
TAB.create(new BungeePlatform(this));
} catch (NoSuchFieldException e) {
getLogger().warning(EnumChatFormat.RED + "The plugin requires BungeeCord build #1885 " +
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.");
}
@@ -19,10 +19,13 @@ import java.util.Collections;
public class BungeeTabCommand extends Command implements TabExecutor {
/**
* Constructs new instance
* Constructs new instance.
*
* @param command
* Command to register
*/
public BungeeTabCommand() {
super(TabConstants.COMMAND_PROXY, null);
public BungeeTabCommand(@NotNull String command) {
super(command, null);
}
@Override
+1 -1
View File
@@ -13,7 +13,7 @@ repositories {
dependencies {
api(projects.shared)
minecraft("com.mojang:minecraft:1.21.4")
minecraft("com.mojang:minecraft:25w04a")
mappings(loom.officialMojangMappings())
modImplementation("me.lucko:fabric-permissions-api:0.2-SNAPSHOT")
modImplementation("eu.pb4:placeholder-api:2.5.0+1.21.2")
@@ -24,7 +24,7 @@ public class FabricBossBar extends SafeBossBar<ServerBossEvent> {
@NotNull
public ServerBossEvent constructBossBar(@NotNull TabComponent title, float progress, @NotNull BarColor color, @NotNull BarStyle style) {
ServerBossEvent bar = new ServerBossEvent(
title.convert(player.getVersion()),
title.convert(),
BossBarColor.valueOf(color.name()),
BossBarOverlay.valueOf(style.name())
);
@@ -39,7 +39,7 @@ public class FabricBossBar extends SafeBossBar<ServerBossEvent> {
@Override
public void updateTitle(@NotNull BossBarInfo bar) {
bar.getBossBar().setName(bar.getTitle().convert(player.getVersion()));
bar.getBossBar().setName(bar.getTitle().convert());
}
@Override
@@ -84,14 +84,12 @@ public class FabricMultiVersion {
*
* @param modifier
* Modifier to convert
* @param modern
* Whether RGB should be supported or not
* @return Converted style
*/
@NotNull
public static Style convertModifier(@NotNull ChatModifier modifier, boolean modern) {
if (serverVersion.getMinorVersion() >= 16) return loaderLatest.convertModifier(modifier, modern);
return loader1_14_4.convertModifier(modifier, modern);
public static Style convertModifier(@NotNull ChatModifier modifier) {
if (serverVersion.getMinorVersion() >= 16) return loaderLatest.convertModifier(modifier);
return loader1_14_4.convertModifier(modifier);
}
/**
@@ -135,16 +135,20 @@ public class FabricPlatform implements BackendPlatform {
@Override
@NotNull
public Component convertComponent(@NotNull TabComponent component, boolean modern) {
if (component instanceof SimpleComponent) return FabricMultiVersion.newTextComponent(((SimpleComponent) component).getText());
StructuredComponent component1 = (StructuredComponent) component;
Component nmsComponent = FabricMultiVersion.newTextComponent(component1.getText());
FabricMultiVersion.setStyle(nmsComponent, FabricMultiVersion.convertModifier(component1.getModifier(), modern));
for (StructuredComponent extra : component1.getExtra()) {
FabricMultiVersion.addSibling(nmsComponent, convertComponent(extra, modern));
if (component instanceof SimpleComponent component1) {
return FabricMultiVersion.newTextComponent(component1.getText());
}
return nmsComponent;
if (component instanceof StructuredComponent component1) {
Component nmsComponent = FabricMultiVersion.newTextComponent(component1.getText());
FabricMultiVersion.setStyle(nmsComponent, FabricMultiVersion.convertModifier(component1.getModifier()));
for (StructuredComponent extra : component1.getExtra()) {
FabricMultiVersion.addSibling(nmsComponent, convertComponent(extra, modern));
}
return nmsComponent;
}
throw new UnsupportedOperationException("Adventure components created using MiniMessage syntax are not supported on Fabric. " +
"You can request the implementation if you ran into this error.");
}
@Override
@@ -175,9 +179,16 @@ public class FabricPlatform implements BackendPlatform {
return serverVersion.getNetworkId() >= ProtocolVersion.V1_21_2.getNetworkId();
}
@Override
public boolean supportsScoreboards() {
return true;
}
@Override
public double getTPS() {
return -1; // Not available
double mspt = getMSPT();
if (mspt < 50) return 20;
return Math.round(1000 / mspt);
}
@Override
@@ -36,7 +36,7 @@ public class FabricScoreboard extends SafeScoreboard<FabricTabPlayer> {
public void registerObjective(@NonNull Objective objective) {
net.minecraft.world.scores.Objective obj = FabricMultiVersion.newObjective(
objective.getName(),
objective.getTitle().convert(player.getVersion()),
objective.getTitle().convert(),
RenderType.values()[objective.getHealthDisplay().ordinal()],
objective.getNumberFormat()
);
@@ -53,7 +53,7 @@ public class FabricScoreboard extends SafeScoreboard<FabricTabPlayer> {
@Override
public void updateObjective(@NonNull Objective objective) {
net.minecraft.world.scores.Objective obj = (net.minecraft.world.scores.Objective) objective.getPlatformObjective();
obj.setDisplayName(objective.getTitle().convert(player.getVersion()));
obj.setDisplayName(objective.getTitle().convert());
obj.setRenderType(RenderType.values()[objective.getHealthDisplay().ordinal()]);
player.sendPacket(new ClientboundSetObjectivePacket(obj, ObjectiveAction.UPDATE));
}
@@ -61,7 +61,7 @@ public class FabricScoreboard extends SafeScoreboard<FabricTabPlayer> {
@Override
public void setScore(@NonNull Score score) {
player.sendPacket(FabricMultiVersion.setScore(score.getObjective().getName(), score.getHolder(), score.getValue(),
score.getDisplayName() == null ? null : score.getDisplayName().convert(player.getVersion()),
score.getDisplayName() == null ? null : score.getDisplayName().convert(),
score.getNumberFormat()));
}
@@ -99,11 +99,11 @@ public class FabricScoreboard extends SafeScoreboard<FabricTabPlayer> {
PlayerTeam t = (PlayerTeam) team.getPlatformTeam();
t.setAllowFriendlyFire((team.getOptions() & 0x01) != 0);
t.setSeeFriendlyInvisibles((team.getOptions() & 0x02) != 0);
t.setColor(formats[team.getColor().ordinal()]);
t.setColor(formats[team.getColor().getLegacyColor().ordinal()]);
t.setCollisionRule(collisions[team.getCollision().ordinal()]);
t.setNameTagVisibility(visibilities[team.getVisibility().ordinal()]);
t.setPlayerPrefix(team.getPrefix().convert(player.getVersion()));
t.setPlayerSuffix(team.getSuffix().convert(player.getVersion()));
t.setPlayerPrefix(team.getPrefix().convert());
t.setPlayerSuffix(team.getSuffix().convert());
}
@Override
@@ -31,7 +31,7 @@ public class FabricTabCommand {
* Dispatcher to register command to
*/
public void onRegisterCommands(@NotNull CommandDispatcher<CommandSourceStack> dispatcher) {
LiteralCommandNode<CommandSourceStack> command = Commands.literal(TabConstants.COMMAND_BACKEND)
LiteralCommandNode<CommandSourceStack> command = Commands.literal("tab") // TODO extract it from Platform somehow
.executes(context -> executeCommand(context.getSource(), new String[0]))
.build();
ArgumentCommandNode<CommandSourceStack, String> args = Commands.argument("args", StringArgumentType.greedyString())
@@ -62,7 +62,7 @@ public class FabricTabCommand {
boolean hasReloadPermission = PermissionsAPIHook.hasPermission(source, TabConstants.Permission.COMMAND_RELOAD);
boolean hasAdminPermission = PermissionsAPIHook.hasPermission(source, TabConstants.Permission.COMMAND_ALL);
for (String message : TAB.getInstance().getDisabledCommand().execute(args, hasReloadPermission, hasAdminPermission)) {
FabricMultiVersion.sendMessage(source, TabComponent.fromColoredText(message).convert(((FabricPlatform) TAB.getInstance().getPlatform()).getServerVersion()));
FabricMultiVersion.sendMessage(source, TabComponent.fromColoredText(message).convert());
}
} else {
if (source.getEntity() == null) {
@@ -42,7 +42,7 @@ public class FabricTabPlayer extends BackendTabPlayer {
@Override
public void sendMessage(@NotNull TabComponent message) {
FabricMultiVersion.sendMessage(getPlayer(), message.convert(getVersion()));
FabricMultiVersion.sendMessage(getPlayer(), message.convert());
}
@Override
@@ -75,12 +75,10 @@ public interface Loader {
*
* @param modifier
* Modifier to convert
* @param modern
* Whether RGB should be supported or not
* @return Converted style
*/
@NotNull
default Style convertModifier(@NotNull ChatModifier modifier, boolean modern) {
default Style convertModifier(@NotNull ChatModifier modifier) {
throw new UnsupportedOperationException("Not implemented.");
}
@@ -64,22 +64,14 @@ public class Loader_Latest implements Loader {
@Override
@NotNull
public Style convertModifier(@NotNull ChatModifier modifier, boolean modern) {
TextColor color = null;
if (modifier.getColor() != null) {
if (modern) {
color = TextColor.fromRgb(modifier.getColor().getRgb());
} else {
color = TextColor.fromRgb(modifier.getColor().getLegacyColor().getRgb());
}
}
public Style convertModifier(@NotNull ChatModifier modifier) {
return Style.EMPTY
.withColor(color)
.withBold(modifier.isBold())
.withItalic(modifier.isItalic())
.withUnderlined(modifier.isUnderlined())
.withStrikethrough(modifier.isStrikethrough())
.withObfuscated(modifier.isObfuscated())
.withColor(modifier.getColor() == null ? null : TextColor.fromRgb(modifier.getColor().getRgb()))
.withBold(modifier.getBold())
.withItalic(modifier.getItalic())
.withUnderlined(modifier.getUnderlined())
.withStrikethrough(modifier.getStrikethrough())
.withObfuscated(modifier.getObfuscated())
.withFont(modifier.getFont() == null ? null : ResourceLocation.tryParse(modifier.getFont()));
}
+1 -1
View File
@@ -21,7 +21,7 @@
"1.18", "1.18.1", "1.18.2",
"1.19", "1.19.1", "1.19.2", "1.19.3", "1.19.4",
"1.20", "1.20.1", "1.20.2", "1.20.3", "1.20.4", "1.20.5", "1.20.6",
"1.21", "1.21.1", "1.21.2", "1.21.3", "1.21.4"
"1.21", "1.21.1", "1.21.2", "1.21.3", "1.21.4", "1.21.5-alpha.25.2.a", "1.21.5-alpha.25.3.a", "1.21.5-alpha.25.4.a"
]
},
"recommends": {
@@ -73,16 +73,16 @@ public class Loader_1_14_4 implements Loader {
@Override
@NotNull
public Style convertModifier(@NotNull ChatModifier modifier, boolean modern) {
public Style convertModifier(@NotNull ChatModifier modifier) {
Style style = new Style();
if (modifier.getColor() != null) {
style.setColor(ChatFormatting.valueOf(modifier.getColor().getLegacyColor().name()));
}
if (modifier.isBold()) style.setBold(true);
if (modifier.isItalic()) style.setItalic(true);
if (modifier.isStrikethrough()) style.setStrikethrough(true);
if (modifier.isUnderlined()) style.setUnderlined(true);
if (modifier.isObfuscated()) style.setObfuscated(true);
style.setBold(modifier.getBold());
style.setItalic(modifier.getItalic());
style.setStrikethrough(modifier.getStrikethrough());
style.setUnderlined(modifier.getUnderlined());
style.setObfuscated(modifier.getObfuscated());
return style;
}
+1 -1
View File
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
+3 -4
View File
@@ -18,10 +18,9 @@ dependencies {
compileOnlyApi("net.luckperms:api:5.4")
compileOnlyApi("com.google.guava:guava:31.1-jre")
compileOnlyApi("org.geysermc.floodgate:api:2.2.0-SNAPSHOT")
compileOnlyApi("net.kyori:adventure-api:4.13.0")
compileOnlyApi("net.kyori:adventure-text-serializer-legacy:4.13.0")
compileOnlyApi("net.kyori:adventure-text-serializer-gson:4.13.0")
compileOnlyApi("net.kyori:adventure-text-minimessage:4.13.0")
compileOnlyApi("net.kyori:adventure-api:4.17.0")
compileOnlyApi("net.kyori:adventure-text-serializer-legacy:4.17.0")
compileOnlyApi("net.kyori:adventure-text-minimessage:4.17.0")
}
blossom {
@@ -2,7 +2,6 @@ package me.neznamy.tab.shared;
import lombok.Getter;
import me.neznamy.tab.api.event.TabEvent;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.chat.TabComponent;
import me.neznamy.tab.shared.platform.TabPlayer;
import org.jetbrains.annotations.NotNull;
@@ -116,7 +115,7 @@ public class ErrorManager {
try (BufferedWriter buf = new BufferedWriter(new FileWriter(file, true))) {
if (message != null) {
if (file.length() < TabConstants.MAX_LOG_SIZE)
buf.write(dateFormat.format(new Date()) + "[TAB v" + TabConstants.PLUGIN_VERSION + "] " + EnumChatFormat.decolor(message) + System.lineSeparator());
buf.write(dateFormat.format(new Date()) + "[TAB v" + TabConstants.PLUGIN_VERSION + "] " + message + System.lineSeparator());
if (intoConsoleToo || TAB.getInstance().getConfiguration().getConfig().isDebugMode())
TAB.getInstance().getPlatform().logWarn(TabComponent.fromColoredText(message));
}
@@ -249,7 +248,7 @@ public class ErrorManager {
*/
public void mineSkinDownloadError(@NotNull String id, @NotNull Throwable t) {
printError("Failed to download skin \"" + id + "\" from MineSkin: " + t.getMessage(),
t, true, errorLog);
Collections.emptyList(), true, errorLog);
}
/**
@@ -262,7 +261,7 @@ public class ErrorManager {
*/
public void playerSkinDownloadError(@NotNull String name, @NotNull Throwable t) {
printError("Failed to download skin of player \"" + name + "\": " + t.getMessage(),
t, true, errorLog);
Collections.emptyList(), true, errorLog);
}
/**
@@ -275,7 +274,7 @@ public class ErrorManager {
*/
public void textureSkinDownloadError(@NotNull String texture, @NotNull Throwable t) {
printError("Failed to download skin from texture \"" + texture + "\": " + t.getMessage(),
t, true, errorLog);
Collections.emptyList(), true, errorLog);
}
/**
@@ -60,16 +60,9 @@ public class FeatureManager {
public void load() {
for (TabFeature f : values) {
if (!(f instanceof Loadable)) continue;
TimedCaughtTask task = new TimedCaughtTask(TAB.getInstance().getCpu(), () -> {
long time = System.currentTimeMillis();
((Loadable) f).load();
TAB.getInstance().debug("Feature " + f.getClass().getSimpleName() + " processed load in " + (System.currentTimeMillis()-time) + "ms");
}, f.getFeatureName(), CpuUsageCategory.PLUGIN_LOAD);
if (f instanceof CustomThreaded) {
((CustomThreaded) f).getCustomThread().execute(task);
} else {
task.run();
}
long time = System.currentTimeMillis();
((Loadable) f).load();
TAB.getInstance().debug("Feature " + f.getClass().getSimpleName() + " processed load in " + (System.currentTimeMillis()-time) + "ms");
}
if (TAB.getInstance().getConfiguration().getUsers() instanceof MySQLUserConfiguration) {
MySQLUserConfiguration users = (MySQLUserConfiguration) TAB.getInstance().getConfiguration().getUsers();
@@ -143,9 +143,6 @@ public class Property {
}
}
// Apply gradients that do not include placeholders to avoid applying them on every refresh
rawFormattedValue0 = RGBUtils.getInstance().applyCleanGradients(rawFormattedValue0);
// Make \n work even if used in '', which snakeyaml does not convert to newline
if (rawFormattedValue0.contains("\\n")) {
rawFormattedValue0 = rawFormattedValue0.replace("\\n", "\n");
@@ -4,23 +4,22 @@ import lombok.Getter;
import lombok.Setter;
import me.neznamy.tab.api.TabAPI;
import me.neznamy.tab.api.bossbar.BossBarManager;
import me.neznamy.tab.api.tablist.SortingManager;
import me.neznamy.tab.api.tablist.layout.LayoutManager;
import me.neznamy.tab.api.scoreboard.ScoreboardManager;
import me.neznamy.tab.api.tablist.HeaderFooterManager;
import me.neznamy.tab.api.tablist.SortingManager;
import me.neznamy.tab.api.tablist.TabListFormatManager;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.api.tablist.layout.LayoutManager;
import me.neznamy.tab.shared.chat.TabComponent;
import me.neznamy.tab.shared.config.helper.ConfigHelper;
import me.neznamy.tab.shared.cpu.CpuManager;
import me.neznamy.tab.shared.features.nametags.NameTag;
import me.neznamy.tab.shared.platform.Platform;
import me.neznamy.tab.shared.command.DisabledCommand;
import me.neznamy.tab.shared.command.TabCommand;
import me.neznamy.tab.shared.config.Configs;
import me.neznamy.tab.shared.config.helper.ConfigHelper;
import me.neznamy.tab.shared.cpu.CpuManager;
import me.neznamy.tab.shared.event.EventBusImpl;
import me.neznamy.tab.shared.event.impl.TabLoadEventImpl;
import me.neznamy.tab.shared.features.PlaceholderManagerImpl;
import me.neznamy.tab.shared.features.nametags.NameTag;
import me.neznamy.tab.shared.platform.Platform;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.proxy.ProxyPlatform;
import org.jetbrains.annotations.NotNull;
@@ -186,10 +185,10 @@ public class TAB extends TabAPI {
pluginDisabled = false;
cpu.enable();
configHelper.startup().printWarnCount();
platform.logInfo(TabComponent.fromColoredText(EnumChatFormat.GREEN + "Enabled in " + (System.currentTimeMillis()-time) + "ms"));
platform.logInfo(TabComponent.fromColoredText("&aEnabled in " + (System.currentTimeMillis()-time) + "ms"));
return configuration.getMessages().getReloadSuccess();
} catch (YAMLException e) {
platform.logWarn(TabComponent.fromColoredText(EnumChatFormat.RED + "Did not enable due to a broken configuration file."));
platform.logWarn(TabComponent.fromColoredText("&cDid not enable due to a broken configuration file."));
kill();
return (configuration == null ? "&4Failed to reload, file %file% has broken syntax. Check console for more info."
: configuration.getMessages().getReloadFailBrokenFile()).replace("%file%", brokenFile);
@@ -210,7 +209,7 @@ public class TAB extends TabAPI {
long time = System.currentTimeMillis();
if (configuration.getMysql() != null) configuration.getMysql().closeConnection();
featureManager.unload();
platform.logInfo(TabComponent.fromColoredText(EnumChatFormat.GREEN + "Disabled in " + (System.currentTimeMillis()-time) + "ms"));
platform.logInfo(TabComponent.fromColoredText("&aDisabled in " + (System.currentTimeMillis()-time) + "ms"));
} catch (Throwable e) {
errorManager.criticalError("Failed to disable", e);
}
@@ -318,6 +317,6 @@ public class TAB extends TabAPI {
*/
public void debug(@NotNull String message) {
if (configuration != null && configuration.getConfig().isDebugMode())
platform.logInfo(TabComponent.fromColoredText(EnumChatFormat.BLUE + "[DEBUG] " + message));
platform.logInfo(TabComponent.fromColoredText("&9[DEBUG] " + message));
}
}
@@ -23,9 +23,6 @@ public class TabConstants {
public static final String REDIS_CHANNEL_NAME = PLUGIN_NAME;
public static final String PIPELINE_HANDLER_NAME = PLUGIN_NAME;
public static final String COMMAND_BACKEND = "tab";
public static final String COMMAND_PROXY = "btab";
public static final int BSTATS_PLUGIN_ID_BUKKIT = 5304;
public static final int BSTATS_PLUGIN_ID_BUNGEE = 10535;
public static final int BSTATS_PLUGIN_ID_SPONGE = 17732;
@@ -46,6 +46,12 @@ public interface BackendPlatform extends Platform {
return false;
}
@Override
@NotNull
default String getCommand() {
return "tab";
}
/**
* Registers a dummy placeholder implementation for specified identifier in case
* no placeholder plugin was found.
@@ -0,0 +1,40 @@
package me.neznamy.tab.shared.chat;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* This class is a wrapper for Adventure component created by MiniMessage. This not only
* speeds up the performance by not having to do unnecessary conversions, but also supports
* translatable components, which TAB components do not support.
*/
@RequiredArgsConstructor
@Getter
public class AdventureComponent extends TabComponent {
/** The wrapped component */
@NotNull
private final Component component;
@Override
@NotNull
public String toLegacyText() {
return LegacyComponentSerializer.legacySection().serialize(component);
}
@Override
@Nullable
protected TextColor fetchLastColor() {
net.kyori.adventure.text.format.TextColor lastColor = component.color();
for (Component extra : component.children()) {
if (extra.color() != null) {
lastColor = extra.color();
}
}
return lastColor == null ? null : new TextColor(lastColor.red(), lastColor.green(), lastColor.blue());
}
}
@@ -12,11 +12,11 @@ import org.jetbrains.annotations.Nullable;
public class ChatModifier {
@Nullable private TextColor color;
private boolean bold;
private boolean italic;
private boolean obfuscated;
private boolean strikethrough;
private boolean underlined;
@Nullable private Boolean bold;
@Nullable private Boolean italic;
@Nullable private Boolean obfuscated;
@Nullable private Boolean strikethrough;
@Nullable private Boolean underlined;
@Nullable private String font;
/**
@@ -44,11 +44,11 @@ public class ChatModifier {
@NotNull
public String getMagicCodes() {
StringBuilder builder = new StringBuilder();
if (bold) builder.append(EnumChatFormat.BOLD);
if (italic) builder.append(EnumChatFormat.ITALIC);
if (obfuscated) builder.append(EnumChatFormat.OBFUSCATED);
if (strikethrough) builder.append(EnumChatFormat.STRIKETHROUGH);
if (underlined) builder.append(EnumChatFormat.UNDERLINE);
if (Boolean.TRUE.equals(bold)) builder.append("§l");
if (Boolean.TRUE.equals(italic)) builder.append("§o");
if (Boolean.TRUE.equals(obfuscated)) builder.append("§k");
if (Boolean.TRUE.equals(strikethrough)) builder.append("§m");
if (Boolean.TRUE.equals(underlined)) builder.append("§n");
return builder.toString();
}
@@ -59,11 +59,11 @@ public class ChatModifier {
*/
public int getMagicCodeBitMask() {
int mask = 0;
if (bold) mask += 1;
if (italic) mask += 2;
if (obfuscated) mask += 4;
if (strikethrough) mask += 8;
if (underlined) mask += 16;
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;
}
}
@@ -36,12 +36,6 @@ public enum EnumChatFormat {
/** Creating a constant to avoid memory allocations on each request */
public static final EnumChatFormat[] VALUES = values();
/** The symbol minecraft uses to colorize text */
public static final char COLOR_CHAR = 0x00a7;
/** The color symbol in form of a string */
public static final String COLOR_STRING = String.valueOf(COLOR_CHAR);
/** Character representing the color or magic code */
private final char character;
@@ -71,7 +65,7 @@ public enum EnumChatFormat {
EnumChatFormat(char character, int rgb) {
this.character = character;
this.rgb = rgb;
format = String.valueOf(COLOR_CHAR) + character;
format = "§" + character;
red = (short) ((rgb >> 16) & 0xFF);
green = (short) ((rgb >> 8) & 0xFF);
blue = (short) (rgb & 0xFF);
@@ -104,25 +98,13 @@ public enum EnumChatFormat {
char[] b = textToTranslate.toCharArray();
for (int i = 0; i < b.length - 1; i++) {
if ((b[i] == '&') && ("0123456789AaBbCcDdEeFfKkLlMmNnOoRrXx#".indexOf(b[(i + 1)]) > -1)) {
b[i] = COLOR_CHAR;
b[i] = '§';
b[(i + 1)] = Character.toLowerCase(b[(i + 1)]);
}
}
return new String(b);
}
/**
* Turns back the color symbol into '&amp;' symbol in provided text.
*
* @param text
* text to revert colors in
* @return reverted text
*/
public static @NotNull String decolor(@NotNull String text) {
if (!text.contains(COLOR_STRING)) return text;
return text.replace(COLOR_CHAR, '&');
}
/**
* Code taken from bukkit, which returns last color codes used in provided text.
*
@@ -135,10 +117,10 @@ public enum EnumChatFormat {
int length = input.length();
for (int index = length - 1; index > -1; index--) {
char section = input.charAt(index);
if ((section == COLOR_CHAR || section == '&') && (index < length - 1)) {
if ((section == '§' || section == '&') && (index < length - 1)) {
char c = input.charAt(index + 1);
if ("0123456789AaBbCcDdEeFfKkLlMmNnOoRr".contains(String.valueOf(c))) {
result.insert(0, COLOR_CHAR);
result.insert(0, '§');
result.insert(1, c);
if ("0123456789AaBbCcDdEeFfRr".contains(String.valueOf(c))) {
break;
@@ -3,6 +3,7 @@ package me.neznamy.tab.shared.chat;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Simple component with only text using legacy colors and nothing else.
@@ -21,28 +22,14 @@ public class SimpleComponent extends TabComponent {
}
@Override
@NotNull
public String toFlatText() {
return text;
}
@Override
@NotNull
public String toRawText() {
return text;
}
@Override
@NotNull
@Nullable
protected TextColor fetchLastColor() {
if (text.isEmpty()) return TextColor.legacy(EnumChatFormat.WHITE);
String last = EnumChatFormat.getLastColors(text);
if (!last.isEmpty()) {
char c = last.toCharArray()[1];
for (EnumChatFormat e : EnumChatFormat.VALUES) {
if (e.getCharacter() == c) return TextColor.legacy(e);
}
char[] chars = text.toCharArray();
for (int index = chars.length - 2; index >= 0; index--) {
if (chars[index] != '§') continue;
EnumChatFormat color = EnumChatFormat.getByChar(chars[index + 1]);
if (color != null) return TextColor.legacy(color);
}
return TextColor.legacy(EnumChatFormat.WHITE);
return null;
}
}
@@ -30,10 +30,6 @@ public class StructuredComponent extends TabComponent {
@Nullable
private List<StructuredComponent> extra;
/** Component turned into flat text. RGB colors are represented as #RRGGBB. */
@Nullable
private String flatText;
/**
* Constructs a new component which is a clone of provided component
*
@@ -56,8 +52,22 @@ public class StructuredComponent extends TabComponent {
*/
public StructuredComponent(@NotNull String text, @NotNull List<StructuredComponent> components) {
this.text = text;
if (components.isEmpty()) throw new IllegalArgumentException("Unexpected empty array of components"); //exception taken from minecraft
extra = components;
if (!components.isEmpty()) {
extra = components;
}
}
/**
* Constructs new instance with given text and color.
*
* @param text
* Component text
* @param color
* Text color
*/
public StructuredComponent(@NotNull String text, @Nullable TextColor color) {
this.text = text;
modifier.setColor(color);
}
/**
@@ -65,7 +75,8 @@ public class StructuredComponent extends TabComponent {
*
* @return list of extra components
*/
public @NotNull List<StructuredComponent> getExtra() {
@NotNull
public List<StructuredComponent> getExtra() {
if (extra == null) return Collections.emptyList();
return extra;
}
@@ -79,48 +90,15 @@ public class StructuredComponent extends TabComponent {
}
@Override
@NotNull
public String toFlatText() {
if (flatText == null) {
StringBuilder builder = new StringBuilder();
TextColor color = modifier.getColor();
if (color != null) {
if (color.isLegacy()) {
builder.append(color.getLegacyColor());
} else {
builder.append("#").append(color.getHexCode());
}
}
builder.append(modifier.getMagicCodes());
builder.append(text);
for (StructuredComponent child : getExtra()) {
builder.append(child.toFlatText());
}
flatText = builder.toString();
}
return flatText;
}
@Override
@NotNull
public String toRawText() {
StringBuilder builder = new StringBuilder(text);
for (StructuredComponent extra : getExtra()) {
builder.append(extra.toRawText());
}
return builder.toString();
}
@Override
@NotNull
@Nullable
protected TextColor fetchLastColor() {
TextColor lastColor = modifier.getColor();
for (StructuredComponent extra : getExtra()) {
if (extra.modifier.getColor() != null) {
lastColor = extra.modifier.getColor();
TextColor color = extra.fetchLastColor();
if (color != null) {
lastColor = color;
}
}
if (lastColor == null) lastColor = TextColor.legacy(EnumChatFormat.WHITE);
return lastColor;
}
@@ -5,14 +5,15 @@ 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.tab.shared.util.function.FunctionWithException;
import me.neznamy.tab.shared.util.function.TriFunction;
import net.kyori.adventure.text.Component;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -21,6 +22,35 @@ import java.util.regex.Pattern;
*/
public abstract class TabComponent {
/** 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>");
@@ -30,13 +60,9 @@ public abstract class TabComponent {
@Nullable
private Object convertedLegacy;
/** Adventure component from this component for 1.16+ players */
/** Adventure component from this component */
@Nullable
private Component adventureModern;
/** Adventure component from this component for 1.15- players */
@Nullable
private Component adventureLegacy;
private Component adventureComponent;
@Nullable
private Object fixedFormat;
@@ -46,8 +72,9 @@ public abstract class TabComponent {
private Object textHolder;
/**
* Last color of this component. Used to determine team color based on last color of prefix.
* Saves as TextColor instead of EnumChatFormat to have things ready if Mojang adds RGB support to team color.
* Last color of this component.
* Used to determine team color based on the last color of prefix.
* Saved as TextColor instead of EnumChatFormat to have things ready if Mojang adds RGB support to team color.
*/
@Nullable
private TextColor lastColor;
@@ -74,21 +101,27 @@ public abstract class TabComponent {
}
/**
* Converts this component to adventure component.
* Converts this component to platform's component.
*
* @param clientVersion
* Client version
* @return Converted component
* @param <T>
* Platform's component class
*/
@NotNull
@SuppressWarnings("unchecked")
public <T> T convert() {
if (convertedModern == null) convertedModern = TAB.getInstance().getPlatform().convertComponent(this, true);
return (T) convertedModern;
}
/**
* Converts this component to an Adventure component.
* @return Converted component
*/
@NotNull
public Component toAdventure(@NotNull ProtocolVersion clientVersion) {
if (clientVersion.supportsRGB()) {
if (adventureModern == null) adventureModern = AdventureHook.toAdventureComponent(this, true);
return adventureModern;
} else {
if (adventureLegacy == null) adventureLegacy = AdventureHook.toAdventureComponent(this, false);
return adventureLegacy;
}
public Component toAdventure() {
if (adventureComponent == null) adventureComponent = AdventureHook.toAdventureComponent(this);
return adventureComponent;
}
/**
@@ -106,7 +139,7 @@ public abstract class TabComponent {
@SuppressWarnings("unchecked")
@SneakyThrows
public <F, C> F toFixedFormat(@NotNull FunctionWithException<C, F> createFunction) {
if (fixedFormat == null) fixedFormat = createFunction.apply(convert(ProtocolVersion.LATEST_KNOWN_VERSION)); // Numbers formats are 1.20.3+, which is above 1.16
if (fixedFormat == null) fixedFormat = createFunction.apply(convert());
return (F) fixedFormat;
}
@@ -115,27 +148,28 @@ public abstract class TabComponent {
*
* @param convertFunction
* Function for converting adventure Component to TextHolder
* @param version
* Player version
* @return Converted TextHolder
* @param <T>
* TextHolder type
*/
@SuppressWarnings("unchecked")
@NotNull
public <T> T toTextHolder(@NotNull BiFunction<TabComponent, ProtocolVersion, T> convertFunction, @NotNull ProtocolVersion version) {
if (textHolder == null) textHolder = convertFunction.apply(this, version);
public <T> T toTextHolder(@NotNull Function<TabComponent, T> convertFunction) {
if (textHolder == null) textHolder = convertFunction.apply(this);
return (T) textHolder;
}
/**
* Returns last color of this component. This value is cached.
* Returns last color of this component. This value is cached. If no color is used, WHITE color is returned.
*
* @return Last color of this component
*/
@NotNull
public TextColor getLastColor() {
if (lastColor == null) lastColor = fetchLastColor();
if (lastColor == null) {
lastColor = fetchLastColor();
if (lastColor == null) lastColor = TextColor.legacy(EnumChatFormat.WHITE);
}
return lastColor;
}
@@ -148,12 +182,13 @@ public abstract class TabComponent {
public abstract String toLegacyText();
/**
* Converts this component into a string. RGB colors are represented as #RRGGBB.
* Computes and returns the last used color code in this component.
* If no color is present, {@code null} is returned.
*
* @return String version of this component
* @return Last color of this component, {@code null} if no colors are used
*/
@NotNull
public abstract String toFlatText();
@Nullable
protected abstract TextColor fetchLastColor();
/**
* Converts this component into a string that only consists of text without any formatting.
@@ -161,15 +196,13 @@ public abstract class TabComponent {
* @return String containing text of the component and extras
*/
@NotNull
public abstract String toRawText();
/**
* Returns last color of this component.
*
* @return Last color of this component
*/
@NotNull
protected abstract TextColor fetchLastColor();
public String toRawText() {
String text = toLegacyText();
for (EnumChatFormat format : EnumChatFormat.VALUES) {
if (text.contains(format.toString())) text = text.replace(format.toString(), "");
}
return text;
}
/**
* Returns organized component from colored text
@@ -179,7 +212,7 @@ public abstract class TabComponent {
* @return organized component from colored text
*/
@NotNull
public static TabComponent fromColoredText(@NotNull String originalText) {
public static StructuredComponent fromColoredText(@NotNull String originalText) {
String remainingText = originalText;
List<StructuredComponent> components = new ArrayList<>();
while (!remainingText.isEmpty()) {
@@ -202,23 +235,19 @@ public abstract class TabComponent {
break;
}
}
if (components.isEmpty()) {
return new SimpleComponent("");
} else {
return new StructuredComponent("", components);
}
return new StructuredComponent("", components);
}
@NotNull
private static List<StructuredComponent> toComponentArray(@NotNull String originalText, @Nullable String font) {
String text = RGBUtils.getInstance().applyFormats(EnumChatFormat.color(originalText));
String text = RGBUtils.getInstance().applyFormats(EnumChatFormat.color(originalText), TABGradientFormatter, TABRGBFormatter);
List<StructuredComponent> components = new ArrayList<>();
StringBuilder builder = new StringBuilder();
StructuredComponent component = new StructuredComponent();
component.getModifier().setFont(font);
for (int i = 0; i < text.length(); i++) {
char c = text.charAt(i);
if (c == EnumChatFormat.COLOR_CHAR) {
if (c == '§') {
i++;
if (i >= text.length()) {
break;
@@ -268,15 +297,8 @@ public abstract class TabComponent {
} else if (c == '#' && text.length() > i+6) {
String hex = text.substring(i+1, i+7);
if (isHexCode(hex)) {
TextColor color;
EnumChatFormat code = text.length() - i >= 9 ? EnumChatFormat.getByChar(text.charAt(i+8)) : null;
if (code != null && text.charAt(i+7) == '|') {
color = new TextColor(hex, code);
i += 8;
} else {
color = new TextColor(hex);
i += 6;
}
TextColor color = new TextColor(hex);
i += 6;
if (builder.length() > 0) {
component.setText(builder.toString());
components.add(component);
@@ -1,7 +1,7 @@
package me.neznamy.tab.shared.chat;
import lombok.Getter;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
@@ -21,21 +21,13 @@ public class TextColor {
private int rgb = -1;
/** Closest legacy color to this color object. */
@Nullable
private EnumChatFormat legacyColor;
/** HEX code of this color as a 6-digit hexadecimal string without "#" prefix. */
@Nullable
private String hexCode;
/**
* Boolean value whether the legacy color was forced with constructor or should be
* automatically assigned as closest color.
* This value is used in gradients when converting text for legacy players.
*/
@Getter private boolean legacyColorForced;
/** Flag tracking whether this color is actually a legacy color or not */
@Getter private boolean legacy;
/**
* Constructs new instance from provided 6-digit hex code string
*
@@ -48,22 +40,6 @@ public class TextColor {
this.hexCode = hexCode;
}
/**
* Constructs new instance from provided 6-digit hex code and forced legacy color
*
* @param hexCode
* 6-digit combination of hex numbers as a string
* @param legacyColor
* color to use for legacy clients instead of using the closest legacy color
* @throws IllegalArgumentException
* if {@code hexCode} is {@code null} or {@code legacyColor} is {@code null}
*/
public TextColor(@NotNull String hexCode, @NotNull EnumChatFormat legacyColor) {
this.hexCode = hexCode;
legacyColorForced = true;
this.legacyColor = legacyColor;
}
/**
* Constructs new instance from provided legacy color
*
@@ -74,8 +50,6 @@ public class TextColor {
rgb = legacyColor.getRgb();
this.legacyColor = legacyColor;
hexCode = String.format("%06X", legacyColor.getRgb());
legacyColorForced = true;
legacy = true;
}
/**
@@ -147,7 +121,7 @@ public class TextColor {
* @return rgb value
*/
public int getRgb() {
if (rgb == -1) rgb = Integer.parseInt(hexCode, 16);
if (rgb == -1) rgb = Integer.parseInt(getHexCode(), 16);
return rgb;
}
@@ -158,7 +132,8 @@ public class TextColor {
*
* @return closest legacy color
*/
public @NotNull EnumChatFormat getLegacyColor() {
@NotNull
public EnumChatFormat getLegacyColor() {
if (legacyColor == null) legacyColor = loadClosestColor();
return legacyColor;
}
@@ -168,7 +143,8 @@ public class TextColor {
*
* @return the rgb combination as a 6-digit hex code string
*/
public @NotNull String getHexCode() {
@NotNull
public String getHexCode() {
if (hexCode == null) hexCode = String.format("%06X", rgb);
return hexCode;
}
@@ -1,24 +1,20 @@
package me.neznamy.tab.shared.chat.rgb;
import lombok.Getter;
import me.neznamy.tab.shared.chat.TextColor;
import me.neznamy.tab.shared.chat.rgb.format.BukkitFormat;
import me.neznamy.tab.shared.chat.rgb.format.HtmlFormat;
import me.neznamy.tab.shared.chat.rgb.format.KyoriFormat;
import me.neznamy.tab.shared.chat.rgb.format.MiniMessageFormat;
import me.neznamy.tab.shared.chat.rgb.format.UnnamedFormat1;
import me.neznamy.tab.shared.chat.rgb.format.CommonFormatter;
import me.neznamy.tab.shared.chat.rgb.format.RGBFormatter;
import me.neznamy.tab.shared.chat.rgb.gradient.CMIGradient;
import me.neznamy.tab.shared.chat.rgb.gradient.CommonGradient;
import me.neznamy.tab.shared.chat.rgb.gradient.GradientPattern;
import me.neznamy.tab.shared.chat.rgb.gradient.NexEngineGradient;
import me.neznamy.tab.shared.util.ReflectionUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
import me.neznamy.tab.shared.chat.rgb.format.CMIFormat;
import me.neznamy.tab.shared.chat.rgb.format.RGBFormatter;
import me.neznamy.tab.shared.util.function.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
*/
@@ -28,72 +24,41 @@ public class RGBUtils {
@Getter private static final RGBUtils instance = new RGBUtils();
/** Registered RGB formatters */
private final RGBFormatter[] formats;
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;
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>
};
/**
* Constructs new instance and loads all RGB patterns and gradients
*/
public RGBUtils() {
List<RGBFormatter> list = new ArrayList<>();
if (ReflectionUtils.classExists("net.kyori.adventure.text.minimessage.MiniMessage") &&
ReflectionUtils.classExists("net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer")) {
list.add(new MiniMessageFormat());
}
list.add(new BukkitFormat());
list.add(new CMIFormat());
list.add(new UnnamedFormat1());
list.add(new HtmlFormat());
list.add(new KyoriFormat());
formats = list.toArray(new RGBFormatter[0]);
gradients = new GradientPattern[] {
//{#RRGGBB>}text{#RRGGBB<}
new CMIGradient(),
//<#RRGGBB>Text</#RRGGBB>
new CommonGradient(Pattern.compile("<#[0-9a-fA-F]{6}>.*?</#[0-9a-fA-F]{6}>"),
Pattern.compile("<#[0-9a-fA-F]{6}\\|.>.*?</#[0-9a-fA-F]{6}>"),
"<#", 9, 2, 9, 7),
//<$#RRGGBB>Text<$#RRGGBB>
new CommonGradient(Pattern.compile("<\\$#[0-9a-fA-F]{6}>.*?<\\$#[0-9a-fA-F]{6}>"),
Pattern.compile("<\\$#[0-9a-fA-F]{6}\\|.>.*?<\\$#[0-9a-fA-F]{6}>"),
"<$", 10, 3, 10, 7),
new NexEngineGradient()
};
}
/**
* Applies all RGB formats and gradients to text and returns it.
* Applies all gradient formats to text and returns it.
*
* @param text
* original text
* @return text where everything is converted to #RRGGBB
* @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
*/
public @NotNull String applyFormats(@NotNull String text) {
@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, false);
replaced = pattern.applyPattern(replaced, gradientFunction);
}
for (RGBFormatter formatter : formats) {
replaced = formatter.reformat(replaced);
}
return replaced;
}
/**
* Applies all gradient formats to text and returns it. This only affects
* usage where no placeholder is used inside.
*
* @param text
* original text
* @return text where all gradients with static text are converted to #RRGGBB
*/
public @NotNull String applyCleanGradients(@NotNull String text) {
String replaced = text;
for (GradientPattern pattern : gradients) {
replaced = pattern.applyPattern(replaced, true);
replaced = formatter.reformat(replaced, rgbFunction);
}
return replaced;
}
@@ -1,27 +1,29 @@
package me.neznamy.tab.shared.chat.rgb.format;
import me.neznamy.tab.shared.chat.TextColor;
import org.jetbrains.annotations.NotNull;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import org.jetbrains.annotations.NotNull;
/**
* Formatter for &amp;x&amp;R&amp;R&amp;G&amp;G&amp;B&amp;B
*/
public class BukkitFormat implements RGBFormatter {
private final Pattern pattern = Pattern.compile("[" + EnumChatFormat.COLOR_CHAR + "&]x[" + EnumChatFormat.COLOR_CHAR + "&\\p{XDigit}]{12}");
private final Pattern pattern = Pattern.compile("§x[§\\p{XDigit}]{12}");
@Override
public @NotNull String reformat(@NotNull String text) {
if (!text.contains("&x") && !text.contains(EnumChatFormat.COLOR_CHAR + "x")) return text;
@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 hexCode = m.group();
String fixed = new String(new char[] {'#', hexCode.charAt(3), hexCode.charAt(5), hexCode.charAt(7), hexCode.charAt(9), hexCode.charAt(11), hexCode.charAt(13)});
replaced = replaced.replace(hexCode, fixed);
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;
}
@@ -1,27 +0,0 @@
package me.neznamy.tab.shared.chat.rgb.format;
import org.jetbrains.annotations.NotNull;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Formatter for {#RRGGBB}
*/
public class CMIFormat implements RGBFormatter {
private final Pattern pattern = Pattern.compile("\\{#[0-9a-fA-F]{6}}");
@Override
public @NotNull String reformat(@NotNull String text) {
if (!text.contains("{#")) return text;
String replaced = text;
Matcher m = pattern.matcher(replaced);
while (m.find()) {
String hexCode = m.group();
String fixed = hexCode.substring(2, 8);
replaced = replaced.replace(hexCode, "#" + fixed);
}
return replaced;
}
}
@@ -0,0 +1,36 @@
package me.neznamy.tab.shared.chat.rgb.format;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.shared.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;
}
}
@@ -1,27 +0,0 @@
package me.neznamy.tab.shared.chat.rgb.format;
import org.jetbrains.annotations.NotNull;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Formatter for #&lt;RRGGBB&gt;
*/
public class HtmlFormat implements RGBFormatter {
private final Pattern pattern = Pattern.compile("#<[0-9a-fA-F]{6}>");
@Override
public @NotNull String reformat(@NotNull String text) {
if (!text.contains("#<")) return text;
Matcher m = pattern.matcher(text);
String replaced = text;
while (m.find()) {
String hexCode = m.group();
String fixed = hexCode.substring(2, 8);
replaced = replaced.replace(hexCode, "#" + fixed);
}
return replaced;
}
}
@@ -1,27 +0,0 @@
package me.neznamy.tab.shared.chat.rgb.format;
import org.jetbrains.annotations.NotNull;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Formatter for &lt;#RRGGBB&gt;
*/
public class KyoriFormat implements RGBFormatter {
private final Pattern pattern = Pattern.compile("<#[0-9a-fA-F]{6}>");
@Override
public @NotNull String reformat(@NotNull String text) {
if (!text.contains("<#")) return text;
String replaced = text;
Matcher m = pattern.matcher(replaced);
while (m.find()) {
String hexCode = m.group();
String fixed = hexCode.substring(2, 8);
replaced = replaced.replace(hexCode, "#" + fixed);
}
return replaced;
}
}
@@ -1,32 +0,0 @@
package me.neznamy.tab.shared.chat.rgb.format;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import org.jetbrains.annotations.NotNull;
/**
* Call to MiniMessage API to reformat text to &amp;x&amp;R&amp;R&amp;G&amp;G&amp;B&amp;B
*/
public class MiniMessageFormat implements RGBFormatter {
/** Serializer that uses &x format on all platforms, even those that do not have it enabled by default */
private static final LegacyComponentSerializer SERIALIZER = LegacyComponentSerializer.builder()
.hexColors().useUnusualXRepeatedCharacterHexFormat().build();
/** Dummy character to append to disable MiniMessage's color compacting that prevents team color from being detected as last color of prefix */
private static final char dummyChar = Character.MAX_VALUE;
@Override
@NotNull
public String reformat(@NotNull String text) {
if (!text.contains("<")) return text; // User did not even attempt to use MiniMessage
if (text.contains(EnumChatFormat.COLOR_STRING)) return text;
try {
String serialized = SERIALIZER.serialize(MiniMessage.miniMessage().deserialize(text + dummyChar));
return serialized.substring(0, serialized.length()-1); // Remove the dummy char back
} catch (Throwable ignored) {
return text;
}
}
}
@@ -1,18 +1,24 @@
package me.neznamy.tab.shared.chat.rgb.format;
import me.neznamy.tab.shared.chat.TextColor;
import org.jetbrains.annotations.NotNull;
import java.util.function.Function;
/**
* Abstract class for different RGB patterns
*/
public interface RGBFormatter {
/**
* Reformats RGB codes in provided text into #RRGGBB format
* 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
String reformat(@NotNull String text, @NotNull Function<TextColor, String> rgbFunction);
}
@@ -1,17 +0,0 @@
package me.neznamy.tab.shared.chat.rgb.format;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import org.jetbrains.annotations.NotNull;
/**
* Formatter for {@code &#RRGGBB}.
*/
public class UnnamedFormat1 implements RGBFormatter {
private final String identifier = EnumChatFormat.COLOR_STRING + "#";
@Override
public @NotNull String reformat(@NotNull String text) {
return text.contains(identifier) ? text.replace(identifier, "#") : text;
}
}
@@ -1,5 +1,7 @@
package me.neznamy.tab.shared.chat.rgb.gradient;
import me.neznamy.tab.shared.chat.TextColor;
import me.neznamy.tab.shared.util.function.TriFunction;
import org.jetbrains.annotations.NotNull;
import java.util.regex.Matcher;
@@ -17,13 +19,12 @@ public class CMIGradient extends CommonGradient {
* Constructs new instance.
*/
public CMIGradient() {
super(Pattern.compile("\\{#[0-9a-fA-F]{6}>}[^{]*\\{#[0-9a-fA-F]{6}<}"),
Pattern.compile("\\{#[0-9a-fA-F]{6}\\|.>}[^{]*\\{#[0-9a-fA-F]{6}<}"),
"{#", 9, 2, 10, 8);
super(Pattern.compile("\\{#[0-9a-fA-F]{6}>}[^{]*\\{#[0-9a-fA-F]{6}<}"), "{#", 2, 10, 8);
}
@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);
@@ -33,6 +34,6 @@ public class CMIGradient extends CommonGradient {
replaced = replaced.replace(format, "{#" + code + "<}{#" + code + ">}");
}
}
return super.applyPattern(replaced, ignorePlaceholders);
return super.applyPattern(replaced, gradientFunction);
}
}
@@ -1,13 +1,13 @@
package me.neznamy.tab.shared.chat.rgb.gradient;
import lombok.AllArgsConstructor;
import me.neznamy.tab.shared.chat.TextColor;
import me.neznamy.tab.shared.util.function.TriFunction;
import org.jetbrains.annotations.NotNull;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import lombok.AllArgsConstructor;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.chat.TextColor;
import org.jetbrains.annotations.NotNull;
/**
* Common class for all gradient patterns.
*/
@@ -15,37 +15,23 @@ import org.jetbrains.annotations.NotNull;
public class CommonGradient implements GradientPattern {
@NotNull private final Pattern pattern;
@NotNull private final Pattern legacyPattern;
@NotNull private final String containCheck;
private final int legacyCharPosition;
private final int startColorStart;
private final int messageStart;
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 = legacyPattern.matcher(replaced);
Matcher m = pattern.matcher(replaced);
while (m.find()) {
String format = m.group();
EnumChatFormat legacyColor = EnumChatFormat.getByChar(format.charAt(legacyCharPosition));
if ((ignorePlaceholders && format.contains("%")) || legacyColor == null) continue;
TextColor start = new TextColor(format.substring(startColorStart, startColorStart+6), legacyColor);
String message = format.substring(messageStart+2, 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);
}
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;
}
@@ -1,7 +1,7 @@
package me.neznamy.tab.shared.chat.rgb.gradient;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.chat.TextColor;
import me.neznamy.tab.shared.util.function.TriFunction;
import org.jetbrains.annotations.NotNull;
/**
@@ -14,47 +14,10 @@ public interface GradientPattern {
*
* @param text
* text to be reformatted
* @param ignorePlaceholders
* Whether text containing "%" should be ignored or not
* @param gradientFunction
* Function for reformatting gradient to new text
* @return reformatted text
*/
String applyPattern(@NotNull String text, boolean ignorePlaceholders);
/**
* Returns gradient text based on start color, text and end color
*
* @param start
* start color
* @param text
* text to be reformatted
* @param end
* end color
* @return reformatted text
*/
default String asGradient(@NotNull TextColor start, @NotNull String text, @NotNull TextColor 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());
if (start.isLegacyColorForced()) sb.append("|").append(start.getLegacyColor().getCharacter());
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());
if (start.isLegacyColorForced()) sb.append("|").append(start.getLegacyColor().getCharacter());
sb.append(magicCodes);
sb.append(deColorized.charAt(i));
}
return sb.toString();
}
@NotNull
String applyPattern(@NotNull String text, @NotNull TriFunction<TextColor, String, TextColor, String> gradientFunction);
}
@@ -1,7 +1,7 @@
package me.neznamy.tab.shared.chat.rgb.gradient;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.chat.TextColor;
import me.neznamy.tab.shared.util.function.TriFunction;
import org.jetbrains.annotations.NotNull;
import java.util.regex.Matcher;
@@ -12,35 +12,18 @@ public class NexEngineGradient implements GradientPattern {
//pattern for <gradient:#RRGGBB></gradient:#RRGGBB>
private final Pattern pattern = Pattern.compile("<gradient:#([A-Fa-f0-9]{6})>(.*?)</gradient:#([A-Fa-f0-9]{6})>");
//pattern for <gradient:#RRGGBB|L></gradient:#RRGGBB>
private final Pattern patternLegacy = 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 = patternLegacy.matcher(replaced);
Matcher matcher = pattern.matcher(replaced);
while (matcher.find()) {
String format = matcher.group();
EnumChatFormat legacyColor = EnumChatFormat.getByChar(matcher.group(2).charAt(0));
if ((ignorePlaceholders && format.contains("%")) || legacyColor == null) continue;
TextColor start = new TextColor(matcher.group(1), legacyColor);
String content = matcher.group(3);
TextColor end = new TextColor(matcher.group(4));
String applied = asGradient(start, content, end);
replaced = replaced.replace(format, applied);
}
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;
}
@@ -1,19 +1,18 @@
package me.neznamy.tab.shared.command;
import me.neznamy.tab.api.placeholder.Placeholder;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.chat.TabComponent;
import me.neznamy.tab.shared.cpu.CpuReport;
import me.neznamy.tab.shared.platform.TabPlayer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.text.DecimalFormat;
import java.util.Map;
import java.util.Map.Entry;
import me.neznamy.tab.shared.chat.TabComponent;
import me.neznamy.tab.shared.cpu.CpuReport;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.api.placeholder.Placeholder;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Handler for "/tab cpu" subcommand
*/
@@ -74,14 +73,14 @@ public class CpuCommand extends SubCommand {
}
private void sendToConsole(@NotNull Map<String, Map<String, Float>> features) {
TAB.getInstance().getPlatform().logInfo(TabComponent.fromColoredText(EnumChatFormat.color("&8&l" + LINE_CHAR + " &6Features:")));
TAB.getInstance().getPlatform().logInfo(TabComponent.fromColoredText("&8&l" + LINE_CHAR + " &6Features:"));
for (Entry<String, Map<String, Float>> entry : features.entrySet()) {
TAB.getInstance().getPlatform().logInfo(TabComponent.fromColoredText(EnumChatFormat.color(
TAB.getInstance().getPlatform().logInfo(TabComponent.fromColoredText(
String.format("&8&l%s &7%s &7(%s%%&7):", LINE_CHAR, entry.getKey(),
colorize(decimal3.format(entry.getValue().values().stream().mapToDouble(Float::floatValue).sum()), 5, 1)))));
colorize(decimal3.format(entry.getValue().values().stream().mapToDouble(Float::floatValue).sum()), 5, 1))));
for (Entry<String, Float> type : entry.getValue().entrySet()) {
TAB.getInstance().getPlatform().logInfo(TabComponent.fromColoredText(EnumChatFormat.color(
String.format("&8&l%s &7%s - %s%%", LINE_CHAR, type.getKey(), colorize(decimal3.format(type.getValue()), 5, 1)))));
TAB.getInstance().getPlatform().logInfo(TabComponent.fromColoredText(
String.format("&8&l%s &7%s - %s%%", LINE_CHAR, type.getKey(), colorize(decimal3.format(type.getValue()), 5, 1))));
}
}
}
@@ -91,7 +90,7 @@ public class CpuCommand extends SubCommand {
for (Entry<String, Map<String, Float>> entry : features.entrySet()) {
double featureTotal = entry.getValue().values().stream().mapToDouble(Float::floatValue).sum();
String core = String.format("&8&l%s &7%s &7(%s%%&7):", LINE_CHAR, entry.getKey(), colorize(decimal3.format(featureTotal), 5, 1));
sender.sendMessage(TabComponent.fromColoredText(EnumChatFormat.color(core)));
sender.sendMessage(TabComponent.fromColoredText(core));
}
}
@@ -4,6 +4,8 @@ import me.neznamy.tab.shared.Property;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.chat.StructuredComponent;
import me.neznamy.tab.shared.chat.TextColor;
import me.neznamy.tab.shared.config.file.ConfigurationFile;
import me.neznamy.tab.shared.features.sorting.Sorting;
import me.neznamy.tab.shared.platform.TabPlayer;
@@ -12,6 +14,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
@@ -133,7 +136,14 @@ public class DebugCommand extends SubCommand {
*/
private @NotNull String getGroup(@NotNull TabPlayer analyzed) {
if (TAB.getInstance().getConfiguration().getConfig().isGroupsByPermissions()) {
return "&eHighest group permission: &8tab.group.&a" + analyzed.getGroup();
if (analyzed.getGroup().equals(TabConstants.NO_GROUP)) {
return "&cPlayer does not have tab.group.<name> permission for any of the listed groups";
}
String s = "&eHighest group permission: &8tab.group.&a" + analyzed.getGroup();
if (analyzed.hasPermission(TabConstants.Permission.TEST_PERMISSION)) {
s += " &c| This user appears to have all permissions. Are they OP? &r";
}
return s;
}
return "&ePrimary permission group: &a" + analyzed.getGroup();
}
@@ -183,9 +193,14 @@ public class DebugCommand extends SubCommand {
if (disabled) {
sendMessage(sender, "&a" + property.getName() + ": &cDisabled for player with condition");
} else {
String rawValue = EnumChatFormat.decolor(property.getCurrentRawValue());
String value = String.format((EnumChatFormat.color("&a%s: &e\"&r%s&r&e\" &7(Source: %s)")), property.getName(), rawValue, property.getSource());
sendRawMessage(sender, value);
// Do it this way to avoid sending the "§" symbol to the console to try to color the text (does not work on Velocity)
sendMessage(sender, new StructuredComponent("", Arrays.asList(
new StructuredComponent(property.getName() + ": ", TextColor.legacy(EnumChatFormat.GREEN)),
new StructuredComponent("\"", TextColor.legacy(EnumChatFormat.YELLOW)),
new StructuredComponent(property.getCurrentRawValue().replace('§', '&'), TextColor.legacy(EnumChatFormat.WHITE)),
new StructuredComponent("\" ", TextColor.legacy(EnumChatFormat.YELLOW)),
new StructuredComponent("(Source: " + property.getSource() + ")", TextColor.legacy(EnumChatFormat.GRAY))
)));
}
}
@@ -1,12 +1,11 @@
package me.neznamy.tab.shared.command;
import me.neznamy.tab.shared.TAB;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import org.jetbrains.annotations.NotNull;
/**
* Command processor when TAB is disabled due to broken configuration file
*/
@@ -34,10 +33,9 @@ public class DisabledCommand {
}
} else {
if (hasAdminPermission) {
String command = "/" + (TAB.getInstance().getPlatform().isProxy() ? TabConstants.COMMAND_PROXY : TabConstants.COMMAND_BACKEND);
messages.add("&m ");
messages.add(" &cPlugin is disabled due to an error. Check console for more details.");
messages.add(" &8>> &3&l" + command + " reload");
messages.add(" &8>> &3&l/" + TAB.getInstance().getPlatform().getCommand() + " reload");
messages.add(" - &7Reloads plugin and config");
messages.add("&m ");
}
@@ -1,14 +1,17 @@
package me.neznamy.tab.shared.command;
import java.util.*;
import java.util.stream.Collectors;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.platform.TabPlayer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
/**
* Handler for "/tab group" subcommand
*/
@@ -60,20 +63,20 @@ public class GroupCommand extends PropertyCommand {
private void sendGroupInfo(@Nullable TabPlayer sender, @NotNull String group) {
sendMessage(sender, "&f=== Group &9" + group + "&f ===");
for (Map.Entry<String, Object> entry : TAB.getInstance().getConfiguration().getGroups().getGlobalSettings(group).entrySet()) {
sendRawMessage(sender, " " + entry.getKey() + ": " + entry.getValue());
sendMessage(sender, " " + entry.getKey() + ": " + entry.getValue());
}
for (Map.Entry<String, Map<String, Object>> entry : TAB.getInstance().getConfiguration().getGroups().getPerWorldSettings(group).entrySet()) {
if (entry.getValue() == null) continue;
sendMessage(sender, "&6World " + entry.getKey() + ":&e");
for (Map.Entry<String, Object> properties : entry.getValue().entrySet()) {
sendRawMessage(sender, " " + properties.getKey() + ": " + properties.getValue());
sendMessage(sender, " " + properties.getKey() + ": " + properties.getValue());
}
}
for (Map.Entry<String, Map<String, Object>> entry : TAB.getInstance().getConfiguration().getGroups().getPerServerSettings(group).entrySet()) {
if (entry.getValue() == null) continue;
sendMessage(sender, "&3Server " + entry.getKey() + ":&b");
for (Map.Entry<String, Object> properties : entry.getValue().entrySet()) {
sendRawMessage(sender, " " + properties.getKey() + ": " + properties.getValue());
sendMessage(sender, " " + properties.getKey() + ": " + properties.getValue());
}
}
}
@@ -90,8 +90,8 @@ public class MySQLCommand extends SubCommand {
}
TAB.getInstance().getCPUManager().getMysqlThread().execute(() -> {
try {
YamlPropertyConfigurationFile groupFile = new YamlPropertyConfigurationFile(Configs.class.getClassLoader().getResourceAsStream("config/groups.yml"), new File(TAB.getInstance().getDataFolder(), "config/groups.yml"));
YamlPropertyConfigurationFile userFile = new YamlPropertyConfigurationFile(Configs.class.getClassLoader().getResourceAsStream("config/users.yml"), new File(TAB.getInstance().getDataFolder(), "config/users.yml"));
YamlPropertyConfigurationFile groupFile = new YamlPropertyConfigurationFile(Configs.class.getClassLoader().getResourceAsStream("config/groups.yml"), new File(TAB.getInstance().getDataFolder(), "groups.yml"));
YamlPropertyConfigurationFile userFile = new YamlPropertyConfigurationFile(Configs.class.getClassLoader().getResourceAsStream("config/users.yml"), new File(TAB.getInstance().getDataFolder(), "users.yml"));
upload(groupFile, TAB.getInstance().getConfiguration().getGroups());
upload(userFile, TAB.getInstance().getConfiguration().getUsers());
sendMessage(sender, getMessages().getMySQLUploadSuccess());
@@ -1,20 +1,22 @@
package me.neznamy.tab.shared.command;
import me.neznamy.tab.api.placeholder.Placeholder;
import me.neznamy.tab.shared.Property;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.chat.StructuredComponent;
import me.neznamy.tab.shared.chat.TabComponent;
import me.neznamy.tab.shared.chat.TextColor;
import me.neznamy.tab.shared.platform.TabPlayer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import me.neznamy.tab.shared.chat.TabComponent;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.api.placeholder.Placeholder;
import me.neznamy.tab.shared.Property;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Handler for "/tab parse &lt;player&gt; &lt;placeholder&gt;" subcommand
*/
@@ -50,28 +52,37 @@ public class ParseCommand extends SubCommand {
return;
}
}
String replaced = String.join(" ", Arrays.copyOfRange(args, 1, args.length));
if (!replaced.contains("%")) {
sendMessage(sender, "&cThe provided input (" + replaced + ") does not contain any placeholders, therefore there's nothing to test.");
String textToParse = String.join(" ", Arrays.copyOfRange(args, 1, args.length));
if (!textToParse.contains("%")) {
sendMessage(sender, "&cThe provided input (" + textToParse + ") does not contain any placeholders, therefore there's nothing to test.");
return;
}
String message = EnumChatFormat.color("&6Replacing placeholder &e%placeholder% &6for player &e" + target.getName()).replace("%placeholder%", replaced);
sendRawMessage(sender, message);
// Do it this way to avoid sending the "§" symbol to the console to try to color the text (does not work on Velocity)
sendMessage(sender, new StructuredComponent("", Arrays.asList(
new StructuredComponent("Replacing placeholder ", TextColor.legacy(EnumChatFormat.GOLD)),
new StructuredComponent(textToParse, TextColor.legacy(EnumChatFormat.YELLOW)),
new StructuredComponent(" for player ", TextColor.legacy(EnumChatFormat.GOLD)),
new StructuredComponent(target.getName(), TextColor.legacy(EnumChatFormat.YELLOW))
)));
try {
replaced = new Property(null, null, target, replaced, null).get();
String replaced = new Property(null, null, target, textToParse, null).get();
TabComponent colored = TabComponent.fromColoredText("&3Colored output: &e\"&r" + replaced + "&e\"");
if (sender != null) {
sender.sendMessage(colored);
} else {
TAB.getInstance().getPlatform().logInfo(colored);
}
sendMessage(sender, new StructuredComponent("", Arrays.asList(
new StructuredComponent("Raw colors: ", TextColor.legacy(EnumChatFormat.DARK_AQUA)),
new StructuredComponent("\"", TextColor.legacy(EnumChatFormat.YELLOW)),
new StructuredComponent(replaced.replace('§', '&'), TextColor.legacy(EnumChatFormat.WHITE)),
new StructuredComponent("\"", TextColor.legacy(EnumChatFormat.YELLOW))
)));
sendMessage(sender, "&3Output length: &e" + replaced.length() + " &3characters");
} catch (Exception e) {
sendMessage(sender, "&cThe placeholder threw an exception when parsing. Check console for more info.");
TAB.getInstance().getErrorManager().parseCommandError(replaced, target, e);
return;
TAB.getInstance().getErrorManager().parseCommandError(textToParse, target, e);
}
TabComponent colored = TabComponent.fromColoredText(EnumChatFormat.color("&3Colored output: &e\"&r" + replaced + "&e\""));
if (sender != null) {
sender.sendMessage(colored);
} else {
TAB.getInstance().getPlatform().logInfo(colored);
}
sendRawMessage(sender, EnumChatFormat.color("&3Raw colors: &e\"&r") + EnumChatFormat.decolor(replaced) + EnumChatFormat.color("&e\""));
sendMessage(sender, "&3Output length: &e" + replaced.length() + " &3characters");
}
@Override
@@ -1,19 +1,18 @@
package me.neznamy.tab.shared.command;
import java.util.*;
import java.util.stream.Collectors;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.shared.chat.SimpleComponent;
import me.neznamy.tab.shared.chat.TabComponent;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.chat.TabComponent;
import me.neznamy.tab.shared.config.MessageFile;
import me.neznamy.tab.shared.platform.TabPlayer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.stream.Collectors;
/**
* Abstract class representing a subcommand of "/tab" command
*/
@@ -87,7 +86,7 @@ public abstract class SubCommand {
* @param sender
* player or console to send the message to
* @param message
* the message to sent
* the message to send
*/
public void sendMessage(@Nullable TabPlayer sender, @NotNull String message) {
if (message.isEmpty()) return;
@@ -99,19 +98,19 @@ public abstract class SubCommand {
}
/**
* Sends message to the command sender without colors translated
* Sends the message component to the player.
* If player is {@code null}, the message is sent to the console instead.
*
* @param sender
* player or console to send the message to
* @param message
* the message to sent
* the message to send
*/
public void sendRawMessage(@Nullable TabPlayer sender, @NotNull String message) {
if (message.isEmpty()) return;
public void sendMessage(@Nullable TabPlayer sender, @NotNull TabComponent message) {
if (sender != null) {
sender.sendMessage(message, false);
sender.sendMessage(message);
} else {
TAB.getInstance().getPlatform().logInfo(new SimpleComponent(message));
TAB.getInstance().getPlatform().logInfo(message);
}
}
@@ -1,17 +1,17 @@
package me.neznamy.tab.shared.command;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.command.bossbar.BossBarCommand;
import me.neznamy.tab.shared.command.scoreboard.ScoreboardCommand;
import me.neznamy.tab.shared.platform.TabPlayer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import me.neznamy.tab.shared.command.bossbar.BossBarCommand;
import me.neznamy.tab.shared.command.scoreboard.ScoreboardCommand;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* The core command handler
*/
@@ -66,9 +66,7 @@ public class TabCommand extends SubCommand {
if (hasPermission(sender, TabConstants.Permission.COMMAND_ALL)) {
sendMessage(sender, "&3TAB v" + TabConstants.PLUGIN_VERSION);
for (String message : getMessages().getHelpMenu()) {
if (TAB.getInstance().getPlatform().isProxy())
message = message.replace("/" + TabConstants.COMMAND_BACKEND, "/" + TabConstants.COMMAND_PROXY);
sendMessage(sender, message);
sendMessage(sender, message.replace("/tab", "/" + TAB.getInstance().getPlatform().getCommand()));
}
}
}
@@ -47,7 +47,6 @@ public class ScoreboardOffCommand extends SubCommand {
return;
}
boolean silent = args.length == 2 && args[1].equals("-s");
if (target.scoreboardData.otherPluginScoreboard != null) return; //not overriding other plugins
scoreboard.setScoreboardVisible(target, false, !silent);
}
Loaded 100 of 175 files, more files were not shown because too many files have changed in this diff. Show more