mirror of
https://github.com/NEZNAMY/TAB.git
synced 2026-11-13 12:42:06 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cba8cb57d5 | ||
|
|
599b4822e5 | ||
|
|
fbd150b353 | ||
|
|
10b252e934 | ||
|
|
e9dfcdeb10 | ||
|
|
9cb2f27754 | ||
|
|
a090a83b7d | ||
|
|
992e661e4c | ||
|
|
3b993d114e | ||
|
|
d181d62466 | ||
|
|
968cef7a12 | ||
|
|
390c2395eb | ||
|
|
adc9444794 | ||
|
|
f086318082 | ||
|
|
1cef4400b9 | ||
|
|
1f879ac91e | ||
|
|
bec129d98e | ||
|
|
152c04e68f | ||
|
|
d6cdacef43 | ||
|
|
09891a075d | ||
|
|
9500bab5b7 | ||
|
|
231edd4bd4 | ||
|
|
433572e759 | ||
|
|
3ec772e87f | ||
|
|
ae8287157c | ||
|
|
f005907b41 | ||
|
|
8ea16044f1 | ||
|
|
e787a28353 | ||
|
|
6925d9564c | ||
|
|
ccd4ea785a | ||
|
|
63e88c09d4 | ||
|
|
296191b502 | ||
|
|
af0151f446 | ||
|
|
2bdfaa167e | ||
|
|
cd96476f9e | ||
|
|
9b0545aa1a | ||
|
|
63823123f8 | ||
|
|
e91728be98 | ||
|
|
e7ecbf1245 | ||
|
|
28c2363b67 | ||
|
|
21a3d4fb55 | ||
|
|
4b459a3ed1 | ||
|
|
ebc6a1b842 | ||
|
|
b72b0f269a | ||
|
|
4cbcd69ed2 | ||
|
|
6ef8b7748c | ||
|
|
8b567cb976 | ||
|
|
d8c231b9ce | ||
|
|
cc4347c881 | ||
|
|
7cc572feb5 | ||
|
|
96d1d1feb4 | ||
|
|
a7d9bd9ab5 | ||
|
|
1cdfdf1d43 | ||
|
|
0762a5e689 | ||
|
|
3e2906ccf8 | ||
|
|
13761e032c | ||
|
|
fb8fa3fa54 | ||
|
|
b89bf5933b | ||
|
|
1c4f404fd2 | ||
|
|
9fe1560f1a | ||
|
|
6a9cca37c6 | ||
|
|
bfde75d4b1 | ||
|
|
a83f98580d | ||
|
|
673542d8e4 | ||
|
|
81c5d89a25 | ||
|
|
bf0bcb0b96 | ||
|
|
ac7a664303 | ||
|
|
182dc67a49 |
No files matched your search
@@ -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]
|
||||
|
||||
@@ -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://github.com/NEZNAMY/TAB)
|
||||
[](https://github.com/NEZNAMY/TAB/issues)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
@@ -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.
|
||||
|
||||
@@ -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,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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -22,13 +22,13 @@ Before attempting to use the function, make sure your server software has an int
|
||||
TAB does not contain it by itself.
|
||||
See the compatibility table below for current information about compatibility:
|
||||
|
||||
| Platform | Available |
|
||||
| ------------- | ------------- |
|
||||
| Bukkit | ✔ |
|
||||
| BungeeCord | ✔ |
|
||||
| Fabric | ❌ |
|
||||
| Sponge | ❌ |
|
||||
| Velocity | ❌ |
|
||||
| Platform | Available |
|
||||
|------------|-----------|
|
||||
| Bukkit | ✔ |
|
||||
| BungeeCord | ✔ |
|
||||
| Fabric | ❌ |
|
||||
| Sponge | ❌ |
|
||||
| Velocity | ❌ |
|
||||
|
||||
# Enabling
|
||||
Find this section in **config.yml**:
|
||||
|
||||
@@ -5,13 +5,13 @@ The usage can be checked at any time using [**/tab cpu**](https://github.com/NEZ
|
||||
* [#1 - [All platforms] Static text instead of placeholders](#1---all-platforms-static-text-instead-of-placeholders)
|
||||
* [#2 - [All platforms] Placeholder refresh intervals](#2---all-platforms-placeholder-refresh-intervals)
|
||||
* [#3 - [All platforms] Don't use RGB](#3---all-platforms-dont-use-rgb)
|
||||
* [#4 - [All platforms] Disable anti-override](#4---all-platforms-disable-anti-override)
|
||||
* [#5 - [All platforms] Disable tablist name formatting](#5---all-platforms-disable-tablist-name-formatting)
|
||||
* [#6 - [All platforms] Increase permission refresh interval](#6---all-platforms-increase-permission-refresh-interval)
|
||||
* [#7 - [BungeeCord / Velocity] Disable TAB expansion](#7---bungeecord--velocity-disable-tab-expansion)
|
||||
* [#8 - [BungeeCord / Velocity] Disable update-latency option in global playerlist](#8---bungeecord--velocity-disable-update-latency-option-in-global-playerlist)
|
||||
* [#9 - [BungeeCord] Disable ByteBuf deserialization](#9---bungeecord-disable-bytebuf-deserialization)
|
||||
* [#10 - [Velocity] Disable header/footer translation](#10---velocity-disable-headerfooter-translation)
|
||||
* [#4 - [All platforms] Don't use animations](#4---all-platforms-dont-use-animations)
|
||||
* [#5 - [All platforms] Disable anti-override](#5---all-platforms-disable-anti-override)
|
||||
* [#6 - [All platforms] Disable tablist name formatting](#6---all-platforms-disable-tablist-name-formatting)
|
||||
* [#7 - [All platforms] Increase permission refresh interval](#7---all-platforms-increase-permission-refresh-interval)
|
||||
* [#8 - [BungeeCord / Velocity] Disable TAB expansion](#8---bungeecord--velocity-disable-tab-expansion)
|
||||
* [#9 - [BungeeCord / Velocity] Disable update-latency option in global playerlist](#9---bungeecord--velocity-disable-update-latency-option-in-global-playerlist)
|
||||
* [#10 - [BungeeCord] Disable ByteBuf deserialization](#10---bungeecord-disable-bytebuf-deserialization)
|
||||
|
||||
# #1 - [All platforms] Static text instead of placeholders
|
||||
Static text doesn't need to be refreshed periodically, resulting in better performance.
|
||||
@@ -50,7 +50,13 @@ Using a higher interval will help if needed, but may lead to misleading info in
|
||||
# #3 - [All platforms] Don't use RGB
|
||||
Though RGB colors look good, they require the text to be split into several components with `color` field used instead of the whole text pasted into the `text` field. This is a complicated process that takes quite some time. Sticking to legacy colors results in better performance.
|
||||
|
||||
# #4 - [All platforms] Disable anti-override
|
||||
# #4 - [All platforms] Don't use animations
|
||||
While animations look cool, they also have a high resource usage.
|
||||
This is because they work by spamming updates every time an animation switches to a new frame.
|
||||
When animations are not used, no updates need to be sent (unless another placeholder changes value),
|
||||
not causing extra CPU usage.
|
||||
|
||||
# #5 - [All platforms] Disable anti-override
|
||||
By default, TAB prevents other plugins from overriding it.
|
||||
This is because many server owners are not competent enough to configure their plugins correctly,
|
||||
such as disabling CMI collision handling which breaks nametags or even disabling team handling in paper.
|
||||
@@ -70,7 +76,7 @@ tablist-name-formatting:
|
||||
anti-override: false
|
||||
```
|
||||
|
||||
# #5 - [All platforms] Disable tablist name formatting
|
||||
# #6 - [All platforms] Disable tablist name formatting
|
||||
As we already know from [Client-sided mechanics](https://github.com/NEZNAMY/TAB/wiki/Client%E2%80%90sided-mechanics#nametag-format-in-tablist) page,
|
||||
the nametag format appears in tablist if tablist name is not defined.
|
||||
You can take advantage of this to effectively disable one entire feature and its cpu usage,
|
||||
@@ -81,7 +87,7 @@ However, you can only do this if:
|
||||
|
||||
Whether you have another plugin attempting to handle them or not may not be easy to identify with a lot of plugins and not enough time spent configuring them. In that case, your best choice is to try it and see. If tablist names break, it means you have another plugin and cannot take advantage of this optimization option.
|
||||
|
||||
# #6 - [All platforms] Increase permission refresh interval
|
||||
# #7 - [All platforms] Increase permission refresh interval
|
||||
Plugin needs to constantly check permissions for:
|
||||
* Permission checks in conditions
|
||||
* Permission checks if sorting by permissions
|
||||
@@ -92,9 +98,11 @@ default value
|
||||
```
|
||||
permission-refresh-interval: 1000
|
||||
```
|
||||
refreshes them every second. If you don't need any permission/group changes to take effect within a second, you can increase this value for better performance.
|
||||
Refreshes them every second.
|
||||
If you don't need any permission/group changes to take effect within a second,
|
||||
you can increase this value for better performance.
|
||||
|
||||
# #7 - [BungeeCord / Velocity] Disable TAB expansion
|
||||
# #8 - [BungeeCord / Velocity] Disable TAB expansion
|
||||
To maximize the performance of TAB's response to PlaceholderAPI request,
|
||||
values are tracked in advance and then quickly returned.
|
||||
This process takes resources and is especially heavy on proxy installation,
|
||||
@@ -105,36 +113,24 @@ Disabling TAB's PlaceholderAPI expansion if you don't use it improves performanc
|
||||
placeholders:
|
||||
register-tab-expansion: false
|
||||
```
|
||||
# #8 - [BungeeCord / Velocity] Disable update-latency option in global playerlist
|
||||
# #9 - [BungeeCord / Velocity] Disable update-latency option in global playerlist
|
||||
When [Global playerlist](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Global-playerlist) is enabled, you can enable / disable [update-latency](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Global-playerlist#:~:text=other%20unlisted%20servers.-,update%2Dlatency,-false) option (open the link for detailed description of the option). Disabling this option massively improves performance.
|
||||
|
||||
# #9 - [BungeeCord] Disable ByteBuf deserialization
|
||||
# #10 - [BungeeCord] Disable ByteBuf deserialization
|
||||
In order for anti-override to work properly, some packets must be manually deserialized due to BungeeCord not doing it. If you don't need it, you can disable those functions, which will also disable the deserialization completely. To do so, you must disable 2 functions (only having 1 disabled is not enough):
|
||||
#1 - Anti-override for teams
|
||||
```
|
||||
scoreboard-teams:
|
||||
anti-override: false
|
||||
```
|
||||
Whether you need anti-override or not depends on the configuration of your plugins.
|
||||
The easiest way is to have it enabled and see if you get `anti-override.log` file in TAB folder.
|
||||
Whether you need anti-override or not depends on the configuration of your plugins.
|
||||
The easiest way is to have it enabled and see if you get `anti-override.log` file in TAB folder.
|
||||
If you don't, you don't need it.
|
||||
If you do, you'll need to do some configuring.
|
||||
[Here](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Sorting-players-in-tablist#additional-note-3---compatibility-issues-with-other-plugins)
|
||||
If you do, you'll need to do some configuring.
|
||||
[Here](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Sorting-players-in-tablist#additional-note-3---compatibility-issues-with-other-plugins)
|
||||
are a few examples of software and their team names, which you may find helpful.
|
||||
|
||||
#2 - Scoreboard detection of other plugins
|
||||
TAB checks for packets sent by other plugins, and if another plugin sends a scoreboard,
|
||||
TAB hides its own and resends it after the other plugin removes it.
|
||||
This check cannot be disabled if the scoreboard is enabled.
|
||||
|
||||
# #10 - [Velocity] Disable header/footer translation
|
||||
Velocity offers header/footer translation of translatable components.
|
||||
However, TAB does not use these.
|
||||
Despite that, Velocity's attempt to "translate" the components is still there,
|
||||
causing the whole feature to use up to 3x more CPU than it would without it.
|
||||
There is no way to disable this inside Velocity itself,
|
||||
however, there is a third party [fork](https://github.com/GemstoneGG/Velocity-CTD/) that offers it.
|
||||
**Use at your own risk**.
|
||||
After installing, default velocity.toml file will contain `translate-header-footer = true` option.
|
||||
If just upgrading, you will need to add it there yourself.
|
||||
Setting it to `false` will disable this function.
|
||||
This check cannot be disabled if the scoreboard is enabled.
|
||||
@@ -26,59 +26,69 @@ TAB offers various placeholders to display the most common information. It also
|
||||
|
||||
Basic placeholders provided by the plugin. Most of these placeholders were added because they were requested by people who don't want to install another plugin just to show username. Some of them have configurable output in config.yml in `Placeholders` section. Unused placeholders do not affect performance in any way.
|
||||
|
||||
Placeholders must be refreshed. All internal placeholders have manually defined refresh interval, which I found the most optimal and cannot be changed. Time is in milliseconds. `-1` is used for placeholders which are in fact constants (such as %player%), or fire an event each time they change, which can be listened to (such as %world%) and value updated. Refresh interval for conditions is set to be equal to fastest refreshing placeholder used inside each condition.
|
||||
Placeholders must be refreshed.
|
||||
All internal placeholders have a manually defined refresh interval,
|
||||
which I found the most optimal and cannot be changed.
|
||||
Time is in milliseconds.
|
||||
It is used for placeholders which are in fact constants (such as %player%),
|
||||
or fire an event each time they change, which can be listened to (such as %world%) and value updated.
|
||||
Refresh interval for conditions is set to be equal to the fastest refreshing placeholder used inside each condition.
|
||||
|
||||
## Universal
|
||||
These placeholders are available on all platforms.
|
||||
|
||||
| Identifier | Refresh interval | Description |
|
||||
| ------------- | ------------- | ------------- |
|
||||
| %online% | 1000 | Total online player count on server where plugin is installed (excluding [vanished players](https://github.com/NEZNAMY/TAB/wiki/Additional-information#vanish-detection)). If the plugin is installed on a proxy, it will show total player count on the whole network. |
|
||||
| %world% | -1 | Name of world where player is |
|
||||
| %worldonline% | 1000 | Amount of players in the same world (excluding [vanished players](https://github.com/NEZNAMY/TAB/wiki/Additional-information#vanish-detection)) as the player |
|
||||
| %ping% | 500 | Player's ping to the server where TAB is installed. <br />**Note**: This value is measured by the server and TAB is only retrieving that value. If it's wrong, it's not a TAB issue. |
|
||||
| %player% | -1 | Player's name |
|
||||
| %time% | 500 | Current real time, output is configurable in **config.yml** in `time-format` option |
|
||||
| %date% | 60000 | Current date, output is configurable in **config.yml** in `date-format` option |
|
||||
| %staffonline% | 2000 | Amount of online players with `tab.staff` permission |
|
||||
| %nonstaffonline% | 2000 | Amount of online players without `tab.staff` permission |
|
||||
| %memory-used% | 200 | Used RAM of server in MB |
|
||||
| %memory-max% | -1 | Total RAM of server in MB |
|
||||
| %memory-used-gb% | 200 | Used RAM of server in GB |
|
||||
| %memory-max-gb% | -1 | Total RAM of server in GB |
|
||||
| %player-version% | -1 | User-friendly version of player, such as `1.14.4`. For versions that share the same protocol version number, the higher one is returned (for example 1.20 & 1.20.1 -> 1.20.1 is returned) |
|
||||
| %player-version-id% | -1 | Network ID of player's version. You can see the full version map on [fan wiki](https://wiki.vg/Protocol_version_numbers) for all versions or [TAB's source code](https://github.com/NEZNAMY/TAB/blob/master/shared/src/main/java/me/neznamy/tab/shared/ProtocolVersion.java) for releases only. Useful for conditions to create per-version results. |
|
||||
| %luckperms-prefix% | 1000 | Prefix from LuckPerms from the server where TAB is installed |
|
||||
| %luckperms-suffix% | 1000 | Suffix from LuckPerms from the server where TAB is installed |
|
||||
| %displayname% | 500 | Display name variable set by permission plugin, typically prefix + name + suffix |
|
||||
| %group% | 1000 | Returns player's primary group name. Created for internal functionality, but can be used as a display placeholder as well. |
|
||||
| %vanished% | 1000 | Returns `true`/`false` based on player's vanish status. Created for internal functionality, but can be used as a display placeholder as well. |
|
||||
| %bedrock% | -1 | Returns `true`/`false` based on whether player is using Bedrock edition or not (Java) |
|
||||
| %% | -1 | Returns the `%` symbol. Useful to display `%` symbol without breaking all placeholders after it |
|
||||
| Identifier | Refresh interval | Description |
|
||||
|---------------------|------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| %online% | 1000 | Total online player count on server where plugin is installed (excluding [vanished players](https://github.com/NEZNAMY/TAB/wiki/Additional-information#vanish-detection)). If the plugin is installed on a proxy, it will show total player count on the whole network. |
|
||||
| %world% | -1 | Name of world where player is |
|
||||
| %worldonline% | 1000 | Amount of players in the same world (excluding [vanished players](https://github.com/NEZNAMY/TAB/wiki/Additional-information#vanish-detection)) as the player |
|
||||
| %ping% | 500 | Player's ping to the server where TAB is installed. <br />**Note**: This value is measured by the server and TAB is only retrieving that value. If it's wrong, it's not a TAB issue. |
|
||||
| %player% | -1 | Player's name |
|
||||
| %time% | 500 | Current real time, output is configurable in **config.yml** in `time-format` option |
|
||||
| %date% | 60000 | Current date, output is configurable in **config.yml** in `date-format` option |
|
||||
| %staffonline% | 2000 | Amount of online players with `tab.staff` permission |
|
||||
| %nonstaffonline% | 2000 | Amount of online players without `tab.staff` permission |
|
||||
| %memory-used% | 200 | Used RAM of server in MB |
|
||||
| %memory-max% | -1 | Total RAM of server in MB |
|
||||
| %memory-used-gb% | 200 | Used RAM of server in GB |
|
||||
| %memory-max-gb% | -1 | Total RAM of server in GB |
|
||||
| %player-version% | -1 | User-friendly version of player, such as `1.14.4`. For versions that share the same protocol version number, the higher one is returned (for example 1.20 & 1.20.1 -> 1.20.1 is returned) |
|
||||
| %player-version-id% | -1 | Network ID of player's version. You can see the full version map on [fan wiki](https://wiki.vg/Protocol_version_numbers) for all versions or [TAB's source code](https://github.com/NEZNAMY/TAB/blob/master/shared/src/main/java/me/neznamy/tab/shared/ProtocolVersion.java) for releases only. Useful for conditions to create per-version results. |
|
||||
| %luckperms-prefix% | 1000 | Prefix from LuckPerms from the server where TAB is installed |
|
||||
| %luckperms-suffix% | 1000 | Suffix from LuckPerms from the server where TAB is installed |
|
||||
| %displayname% | 500 | Display name variable set by permission plugin, typically prefix + name + suffix |
|
||||
| %group% | 1000 | Returns player's primary group name. Created for internal functionality, but can be used as a display placeholder as well. |
|
||||
| %vanished% | 1000 | Returns `true`/`false` based on player's vanish status. Created for internal functionality, but can be used as a display placeholder as well. |
|
||||
| %bedrock% | -1 | Returns `true`/`false` based on whether player is using Bedrock edition or not (Java) |
|
||||
| %% | -1 | Returns the `%` symbol. Useful to display `%` symbol without breaking all placeholders after it |
|
||||
|
||||
## Backend only
|
||||
These placeholders only work when TAB is installed on a backend server (Bukkit / Sponge / Fabric), they don't work when TAB is on BungeeCord. If you have TAB installed on BungeeCord and wish to use these, use their [PlaceholderAPI](https://github.com/NEZNAMY/TAB/wiki/Quick-PlaceholderAPI-startup-guide) alternative with [Bridge plugin](https://github.com/NEZNAMY/TAB/wiki/TAB-Bridge) installed for PlaceholderAPI support on BungeeCord.
|
||||
| Identifier | Refresh interval | Description | PlaceholderAPI alternative |
|
||||
| Identifier | Refresh interval | Description | PlaceholderAPI alternative |
|
||||
| ------------- | ------------- | ------------- | ------------- |
|
||||
| %health% | 100 | Player's health, rounded up to match Minecraft's heart display. | %player_health_rounded% |
|
||||
| %displayname% | 500 | Player's display name value set by permission plugin. | %player_displayname% |
|
||||
| %health% | 100 | Player's health, rounded up to match Minecraft's heart display. | %player_health_rounded% |
|
||||
| %displayname% | 500 | Player's display name value set by permission plugin. | %player_displayname% |
|
||||
| %tps% | 1000 | TPS of server from the last minute (measured by server, not available on Fabric as it doesn't measure the value) | %server_tps_1% |
|
||||
| %mspt% | 1000 | Server's current milliseconds per tick (Paper / Sponge 8+ / Fabric only) | - |
|
||||
|
||||
## BungeeCord only
|
||||
| Identifier | Refresh interval | Description |
|
||||
| ------------- | ------------- | ------------- |
|
||||
| %server% | -1 | Name of server where player is, defined in proxy's config. |
|
||||
| %serveronline% | 1000 | Amount of online players on server where the player is (excluding vanished players) |
|
||||
| %online_\<servername\>% | 1000 | Amount of online players on specified server (excluding vanished players) |
|
||||
| %playerlist-group_\<group\>% | 1000 | Amount of online players in specified [global playerlist](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Global-playerlist) group (therefore the feature must be enabled) |
|
||||
| Identifier | Refresh interval | Description |
|
||||
|------------------------------|------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| %server% | -1 | Name of server where player is, defined in proxy's config. |
|
||||
| %serveronline% | 1000 | Amount of online players on server where the player is (excluding vanished players) |
|
||||
| %online_\<server name\>% | 1000 | Amount of online players on specified server (excluding vanished players) |
|
||||
| %playerlist-group_\<group\>% | 1000 | Amount of online players in specified [global playerlist](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Global-playerlist) group (therefore the feature must be enabled) |
|
||||
|
||||
# PlaceholderAPI
|
||||
PlaceholderAPI is supported and relational placeholders are supported too. When parsing relational placeholders, TAB puts viewer as the first player argument and target player as second argument.
|
||||
PlaceholderAPI is supported and relational placeholders are supported too.
|
||||
When parsing relational placeholders,
|
||||
TAB puts viewer as the first player argument and target player as the second argument.
|
||||
|
||||
You can find most of placeholders that PlaceholderAPI offers on [PlaceholderAPI wiki](https://github.com/PlaceholderAPI/PlaceholderAPI/wiki/Placeholders). Some plugins are not listed there, for those you'll need to check their own wiki for placeholders.
|
||||
You can find most of the placeholders
|
||||
that PlaceholderAPI offers on [PlaceholderAPI wiki](https://github.com/PlaceholderAPI/PlaceholderAPI/wiki/Placeholders).
|
||||
Some plugins are not listed there, for those you'll need to check their own wiki for placeholders.
|
||||
|
||||
TAB also offers its own placeholders into PlaceholderAPI. In order to enable TAB's expansion, set
|
||||
TAB also offers its own placeholders into PlaceholderAPI. To enable TAB's expansion, set
|
||||
```
|
||||
placeholders:
|
||||
register-tab-expansion: true
|
||||
@@ -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
@@ -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:
|
||||

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

|
||||
If this is the case, it means Bridge is either not installed or versions are not compatible. Just update both to the latest version.
|
||||
If this is the case, it means Bridge is either not installed or versions are not compatible. Update both to the latest version.
|
||||
+32
-5
@@ -1,20 +1,47 @@
|
||||
## Easy to install
|
||||
Support for all possible Minecraft versions containing features the plugin is able to take advantage of, support for multiple platforms (Bukkit, BungeeCord, Velocity, Fabric, Sponge), with everything contained in a single jar which you can download directly. No need to browse version history, ask what plugin version you need for your server version, visit random websites looking for the download button, or anything similar. The same jar supports all versions & platforms, and the latest plugin version will always work for you.
|
||||
Support for all possible Minecraft versions containing features the plugin is able to take advantage of.
|
||||
Support for multiple platforms (Bukkit, BungeeCord, Velocity, Fabric, Sponge),
|
||||
with everything contained in a single jar which you can download directly.
|
||||
No need to browse version history, ask what plugin version you need for your server version,
|
||||
visit random websites looking for the download button, or anything similar.
|
||||
The same jar supports all versions & platforms, and the latest plugin version will always work for you.
|
||||
|
||||
## It works
|
||||
The plugin doesn't spam your console with errors, doesn't have compatibility issues with half of the plugins, and doesn't just decide to randomly stop working for no reason. It just works. Bug reports are fixed with highest priority and preferred over adding new features. It's not unusual for a plugin of this size to have a 3-digit open issue count on github. Meanwhile TAB is usually at 1-digit count.
|
||||
The plugin doesn't spam your console with errors, doesn't have compatibility issues with half of the plugins,
|
||||
and doesn't just decide to randomly stop working for no reason.
|
||||
It just works.
|
||||
Bug reports are fixed with the highest priority and preferred over adding new features.
|
||||
It's not unusual for a plugin of this size to have a 3-digit open issue count on GitHub.
|
||||
Meanwhile, TAB is always at 1-digit count.
|
||||
|
||||
## Feature packed
|
||||
All of the most requested features in a single plugin. No need to install many plugins, disable features so they don't conflict with each other, or be forced to run your server on a version that all of the plugins support in common. All features, all server versions in a single plugin.
|
||||
All the most requested features in a single plugin.
|
||||
No need to install many plugins, disable features, so they don't conflict with each other,
|
||||
or be forced to run your server on a version that all the plugins support in common.
|
||||
All features, all server versions in a single plugin.
|
||||
|
||||
## Beyond the limits
|
||||
Mojang decided to put some kind of limitation on almost every feature TAB offers. Other developers simply say "Mojang limit, nothing we can do about it, goodbye". This is not the case here. With every limitation I find, I look for ways to bypass it, and I've found most of them. These bypasses require a ton of work involved, and it's much easier to say "not possible" than actually doing it.
|
||||
|
||||
## Up to date
|
||||
The plugin is not abandoned. It is constantly getting updated to fix reported bugs, add support for new MC versions as they come out and get new optimizations that can help it run as smoothly as possible, whilst still working the way you want it to.
|
||||
The plugin is not abandoned.
|
||||
It is constantly getting updated to fix reported bugs,
|
||||
add support for new MC versions as they come out and get new optimizations.
|
||||
All that can help it run as smoothly as possible, whilst still working the way you want it to.
|
||||
|
||||
## High performance
|
||||
Unlike with other plugins where "high performance" often means your CPU usage will be high, TAB has a very low CPU footprint. There is a [command](https://github.com/NEZNAMY/TAB/wiki/Commands-&-Permissions#tab-cpu) available that shows the entire plugin's CPU usage. Here you can see which part of TAB is occupying the most of your CPU, and there are [many ways to optimize TAB](https://github.com/NEZNAMY/TAB/wiki/Optimizing-the-plugin) to get the most out of it, the most common being increasing refresh rates for individual placeholders if they're inefficient. This is done on a per-placeholder basis, instead of slowing down the refresh rate of an entire feature, which may break animations. Performance is also actively monitored on large servers, and observations show that it performs very well, meaning it will almost certainly perform even better on smaller servers.
|
||||
Unlike with other plugins where "high performance"
|
||||
often means your CPU usage will be high, TAB has a very low CPU footprint.
|
||||
There is a [command](https://github.com/NEZNAMY/TAB/wiki/Commands-&-Permissions#tab-cpu) available
|
||||
that shows the entire plugin's CPU usage.
|
||||
Here you can see which part of TAB is occupying the most of your CPU.
|
||||
There are [many ways to optimize TAB](https://github.com/NEZNAMY/TAB/wiki/Optimizing-the-plugin)
|
||||
to get the most out of it,
|
||||
the most common being increasing refresh rates for individual placeholders if they're inefficient.
|
||||
This is done on a per-placeholder basis,
|
||||
instead of slowing down the refresh rate of an entire feature, which may break animations.
|
||||
Performance is also actively monitored on large servers, and observations show that it performs very well,
|
||||
meaning it will almost certainly perform even better on smaller servers.
|
||||
|
||||
## Simple yet powerful configuration
|
||||
Compared to other plugins, each feature is easier to configure and actually offers more.
|
||||
|
||||
@@ -17,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
@@ -4,7 +4,7 @@ plugins {
|
||||
|
||||
allprojects {
|
||||
group = "me.neznamy"
|
||||
version = "5.0.3"
|
||||
version = "5.0.4"
|
||||
description = "An all-in-one solution that works"
|
||||
|
||||
ext.set("id", "tab")
|
||||
|
||||
@@ -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)
|
||||
|
||||
+79
-35
@@ -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));
|
||||
}
|
||||
}
|
||||
+6
-6
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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
-4
@@ -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.
|
||||
|
||||
+129
-57
@@ -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.chat.*;
|
||||
import me.neznamy.tab.shared.util.FunctionWithException;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import net.kyori.adventure.key.Key;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.KeybindComponent;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import net.kyori.adventure.text.TranslatableComponent;
|
||||
import net.kyori.adventure.text.format.TextDecoration;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
import java.util.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());
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -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);
|
||||
|
||||
+6
-6
@@ -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());
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -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 */
|
||||
|
||||
+8
-5
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+5
-4
@@ -6,6 +6,7 @@ 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;
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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}.
|
||||
*
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
+17
-31
@@ -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
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
|
||||
|
||||
@@ -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": {
|
||||
|
||||
+6
-6
@@ -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
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 '&' 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
|
||||
*
|
||||
@@ -79,48 +75,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;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ 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;
|
||||
|
||||
@@ -30,13 +30,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 +42,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 +71,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 +109,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 +118,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 +152,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 +166,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
|
||||
@@ -218,7 +221,7 @@ public abstract class TabComponent {
|
||||
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 +271,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,15 @@
|
||||
package me.neznamy.tab.shared.chat.rgb;
|
||||
|
||||
import lombok.Getter;
|
||||
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.*;
|
||||
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 org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* A helper class to reformat all RGB formats into the default #RRGGBB and apply gradients
|
||||
*/
|
||||
@@ -28,41 +19,21 @@ public class RGBUtils {
|
||||
@Getter private static final RGBUtils instance = new RGBUtils();
|
||||
|
||||
/** Registered RGB formatters */
|
||||
private final RGBFormatter[] formats;
|
||||
private final RGBFormatter[] formats = {
|
||||
new BukkitFormat(),
|
||||
new CMIFormat(),
|
||||
new UnnamedFormat1(),
|
||||
new HtmlFormat(),
|
||||
new KyoriFormat()
|
||||
};
|
||||
|
||||
/** Registered gradient patterns */
|
||||
private final GradientPattern[] gradients;
|
||||
|
||||
/**
|
||||
* 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()
|
||||
};
|
||||
}
|
||||
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>
|
||||
new CommonGradient(Pattern.compile("<\\$#[0-9a-fA-F]{6}>.*?<\\$#[0-9a-fA-F]{6}>"), "<$", 3, 10, 7), //<$#RRGGBB>Text<$#RRGGBB>
|
||||
new NexEngineGradient() // <gradient:#RRGGBB>Text</gradient:#RRGGBB>
|
||||
};
|
||||
|
||||
/**
|
||||
* Applies all RGB formats and gradients to text and returns it.
|
||||
|
||||
@@ -1,21 +1,20 @@
|
||||
package me.neznamy.tab.shared.chat.rgb.format;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import me.neznamy.tab.shared.chat.EnumChatFormat;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Formatter for &x&R&R&G&G&B&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;
|
||||
if (!text.contains("&x") && !text.contains("§x")) return text;
|
||||
String replaced = text;
|
||||
Matcher m = pattern.matcher(replaced);
|
||||
while (m.find()) {
|
||||
|
||||
@@ -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 &x&R&R&G&G&B&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,6 +1,5 @@
|
||||
package me.neznamy.tab.shared.chat.rgb.format;
|
||||
|
||||
import me.neznamy.tab.shared.chat.EnumChatFormat;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
@@ -8,10 +7,8 @@ import org.jetbrains.annotations.NotNull;
|
||||
*/
|
||||
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;
|
||||
return text.contains("§#") ? text.replace("§#", "#") : text;
|
||||
}
|
||||
}
|
||||
@@ -17,9 +17,7 @@ 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
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
package me.neznamy.tab.shared.chat.rgb.gradient;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import me.neznamy.tab.shared.chat.TextColor;
|
||||
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,9 +14,7 @@ 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;
|
||||
@@ -26,18 +23,7 @@ public class CommonGradient implements GradientPattern {
|
||||
public String applyPattern(@NotNull String text, boolean ignorePlaceholders) {
|
||||
if (!text.contains(containCheck)) return text;
|
||||
String replaced = text;
|
||||
Matcher m = legacyPattern.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);
|
||||
Matcher m = pattern.matcher(replaced);
|
||||
while (m.find()) {
|
||||
String format = m.group();
|
||||
if (ignorePlaceholders && format.contains("%")) continue;
|
||||
|
||||
@@ -40,7 +40,6 @@ public interface GradientPattern {
|
||||
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();
|
||||
@@ -51,7 +50,6 @@ public interface GradientPattern {
|
||||
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));
|
||||
}
|
||||
|
||||
+1
-18
@@ -1,6 +1,5 @@
|
||||
package me.neznamy.tab.shared.chat.rgb.gradient;
|
||||
|
||||
import me.neznamy.tab.shared.chat.EnumChatFormat;
|
||||
import me.neznamy.tab.shared.chat.TextColor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@@ -12,27 +11,11 @@ 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) {
|
||||
if (!text.contains("<grad")) return text;
|
||||
String replaced = text;
|
||||
Matcher matcher = patternLegacy.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);
|
||||
Matcher matcher = pattern.matcher(replaced);
|
||||
while (matcher.find()) {
|
||||
String format = matcher.group();
|
||||
if ((ignorePlaceholders && format.contains("%"))) continue;
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -133,7 +133,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,7 +190,7 @@ 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 rawValue = property.getCurrentRawValue().replace('§', '&');
|
||||
String value = String.format((EnumChatFormat.color("&a%s: &e\"&r%s&r&e\" &7(Source: %s)")), property.getName(), rawValue, property.getSource());
|
||||
sendRawMessage(sender, value);
|
||||
}
|
||||
|
||||
@@ -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,20 +1,20 @@
|
||||
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.TabComponent;
|
||||
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 <player> <placeholder>" subcommand
|
||||
*/
|
||||
@@ -64,13 +64,13 @@ public class ParseCommand extends SubCommand {
|
||||
TAB.getInstance().getErrorManager().parseCommandError(replaced, target, e);
|
||||
return;
|
||||
}
|
||||
TabComponent colored = TabComponent.fromColoredText(EnumChatFormat.color("&3Colored output: &e\"&r" + replaced + "&e\""));
|
||||
TabComponent colored = TabComponent.fromColoredText("&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\""));
|
||||
sendRawMessage(sender, EnumChatFormat.color("&3Raw colors: &e\"&r") + replaced.replace('§', '&') + EnumChatFormat.color("&e\""));
|
||||
sendMessage(sender, "&3Output length: &e" + replaced.length() + " &3characters");
|
||||
}
|
||||
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-1
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -47,7 +47,6 @@ public class ScoreboardOnCommand 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, true, !silent);
|
||||
}
|
||||
|
||||
|
||||
-1
@@ -47,7 +47,6 @@ public class ScoreboardToggleCommand extends SubCommand {
|
||||
return;
|
||||
}
|
||||
boolean silent = args.length == 2 && args[1].equals("-s");
|
||||
if (target.scoreboardData.otherPluginScoreboard != null) return; //not overriding other plugins
|
||||
scoreboard.toggleScoreboard(target, !silent);
|
||||
}
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ public class Configs {
|
||||
public Configs() throws IOException {
|
||||
File errorLog = TAB.getInstance().getErrorManager().getErrorLog();
|
||||
if (errorLog.length() > TabConstants.MAX_LOG_SIZE) {
|
||||
TAB.getInstance().getConfigHelper().startup().startupWarn(errorLog, "The file has reached its size limit (1MB). No new errors will be logged. " +
|
||||
TAB.getInstance().getConfigHelper().startup().startupWarn(errorLog, "The file has reached its size limit (16MB). No new errors will be logged. " +
|
||||
"Take a look at the existing logged errors, as they may have caused the plugin to not work properly " +
|
||||
"in the past and if not fixed, will most likely cause problems in the future as well. If you are using latest version " +
|
||||
"of the plugin, consider reporting them.");
|
||||
|
||||
@@ -2,7 +2,6 @@ package me.neznamy.tab.shared.config;
|
||||
|
||||
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.TabComponent;
|
||||
import me.neznamy.tab.shared.config.file.ConfigurationFile;
|
||||
import me.neznamy.tab.shared.config.file.YamlConfigurationFile;
|
||||
@@ -32,7 +31,7 @@ public class Converter {
|
||||
*/
|
||||
public void convert2810to290(@NotNull ConfigurationFile animations) {
|
||||
if (animations.getValues().size() == 1 && animations.getValues().containsKey("animations")) {
|
||||
TAB.getInstance().getPlatform().logInfo(TabComponent.fromColoredText(EnumChatFormat.YELLOW + "Performing configuration conversion from 2.8.10 to 2.9.0"));
|
||||
TAB.getInstance().getPlatform().logInfo(TabComponent.fromColoredText("&ePerforming configuration conversion from 2.8.10 to 2.9.0"));
|
||||
animations.setValues(animations.getMap("animations"));
|
||||
animations.save();
|
||||
}
|
||||
@@ -49,7 +48,7 @@ public class Converter {
|
||||
*/
|
||||
public void convert292to300(@NotNull ConfigurationFile currentConfig) throws IOException {
|
||||
if (!currentConfig.hasConfigOption("change-nametag-prefix-suffix")) return;
|
||||
TAB.getInstance().getPlatform().logInfo(TabComponent.fromColoredText(EnumChatFormat.YELLOW + "Performing configuration conversion from 2.9.2 to 3.0.0"));
|
||||
TAB.getInstance().getPlatform().logInfo(TabComponent.fromColoredText("&ePerforming configuration conversion from 2.9.2 to 3.0.0"));
|
||||
|
||||
File folder = TAB.getInstance().getDataFolder();
|
||||
moveOldFiles();
|
||||
@@ -350,7 +349,7 @@ public class Converter {
|
||||
*/
|
||||
public void convert301to302(@NotNull ConfigurationFile config) {
|
||||
if (config.removeOption("placeholders.remove-strings")) {
|
||||
TAB.getInstance().getPlatform().logInfo(TabComponent.fromColoredText(EnumChatFormat.YELLOW + "Performing configuration conversion from 3.0.1 to 3.0.2"));
|
||||
TAB.getInstance().getPlatform().logInfo(TabComponent.fromColoredText("&ePerforming configuration conversion from 3.0.1 to 3.0.2"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -375,7 +374,7 @@ public class Converter {
|
||||
public void convert332to400(@NotNull ConfigurationFile config) throws IOException {
|
||||
// Removed config options
|
||||
if (config.hasConfigOption("fix-pet-names")) {
|
||||
TAB.getInstance().getPlatform().logInfo(TabComponent.fromColoredText(EnumChatFormat.YELLOW + "Performing configuration conversion from 3.3.2 to 4.0.0"));
|
||||
TAB.getInstance().getPlatform().logInfo(TabComponent.fromColoredText("&ePerforming configuration conversion from 3.3.2 to 4.0.0"));
|
||||
config.set("fix-pet-names", null);
|
||||
config.set("bossbar.disable-in-worlds", null);
|
||||
config.set("bossbar.disable-in-servers", null);
|
||||
@@ -450,7 +449,7 @@ public class Converter {
|
||||
*/
|
||||
public void convert409to410(@NotNull ConfigurationFile config) {
|
||||
if (config.hasConfigOption("yellow-number-in-tablist")) {
|
||||
TAB.getInstance().getPlatform().logInfo(TabComponent.fromColoredText(EnumChatFormat.YELLOW + "Performing configuration conversion from 4.0.9 to 4.1.0"));
|
||||
TAB.getInstance().getPlatform().logInfo(TabComponent.fromColoredText("&ePerforming configuration conversion from 4.0.9 to 4.1.0"));
|
||||
Map<Object, Object> section = config.getMap("yellow-number-in-tablist");
|
||||
section.put("fancy-value", "&7Ping: %ping%");
|
||||
config.set("yellow-number-in-tablist", null);
|
||||
@@ -471,7 +470,7 @@ public class Converter {
|
||||
*/
|
||||
public void convert419to500(@NotNull ConfigurationFile config) {
|
||||
if (config.removeOption("scoreboard-teams.unlimited-nametag-mode")) {
|
||||
TAB.getInstance().getPlatform().logInfo(TabComponent.fromColoredText(EnumChatFormat.YELLOW + "Performing configuration conversion from 4.1.9 to 5.0.0"));
|
||||
TAB.getInstance().getPlatform().logInfo(TabComponent.fromColoredText("&ePerforming configuration conversion from 4.1.9 to 5.0.0"));
|
||||
config.removeOption("scoreboard.respect-other-plugins");
|
||||
}
|
||||
if (!config.hasConfigOption("global-playerlist.update-latency")) {
|
||||
@@ -488,7 +487,7 @@ public class Converter {
|
||||
*/
|
||||
public void convert501to502(@NotNull ConfigurationFile config) {
|
||||
if (config.rename("belowname-objective.number", "belowname-objective.value")) {
|
||||
TAB.getInstance().getPlatform().logInfo(TabComponent.fromColoredText(EnumChatFormat.YELLOW + "Performing configuration conversion from 5.0.1 to 5.0.2"));
|
||||
TAB.getInstance().getPlatform().logInfo(TabComponent.fromColoredText("&ePerforming configuration conversion from 5.0.1 to 5.0.2"));
|
||||
}
|
||||
config.rename("belowname-objective.text", "belowname-objective.title");
|
||||
config.rename("belowname-objective.fancy-display-players", "belowname-objective.fancy-value");
|
||||
|
||||
@@ -24,7 +24,7 @@ public abstract class ConfigurationFile {
|
||||
protected List<String> header;
|
||||
|
||||
/** Configuration file content */
|
||||
@Getter @Setter protected Map<String, Object> values;
|
||||
@Getter @Setter protected Map<Object, Object> values;
|
||||
|
||||
/** File to use */
|
||||
@Getter protected final File file;
|
||||
@@ -51,7 +51,6 @@ public abstract class ConfigurationFile {
|
||||
if (source == null) throw new IllegalStateException("File does not exist and source is null");
|
||||
Files.copy(source, file.toPath());
|
||||
}
|
||||
detectHeader();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -338,14 +337,14 @@ public abstract class ConfigurationFile {
|
||||
* Value to save
|
||||
* @return the first argument to allow chaining
|
||||
*/
|
||||
private Map<String, Object> set(@NonNull Map<String, Object> map, @NonNull String path, @Nullable Object value) {
|
||||
private Map<Object, Object> set(@NonNull Map<Object, Object> map, @NonNull String path, @Nullable Object value) {
|
||||
if (path.contains(".")) {
|
||||
String keyWord = getRealKey(map, path.split("\\.")[0]);
|
||||
Object subMap = map.get(keyWord);
|
||||
if (!(subMap instanceof Map)) {
|
||||
subMap = new LinkedHashMap<>();
|
||||
}
|
||||
map.put(keyWord, set((Map<String, Object>) subMap, path.substring(keyWord.length()+1), value));
|
||||
map.put(keyWord, set((Map<Object, Object>) subMap, path.substring(keyWord.length()+1), value));
|
||||
} else {
|
||||
if (value == null) {
|
||||
map.remove(getRealKey(map, path));
|
||||
@@ -373,40 +372,6 @@ public abstract class ConfigurationFile {
|
||||
return key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects header of a file (first lines of file starting with #).
|
||||
*
|
||||
* @throws IOException
|
||||
* if I/O operation fails
|
||||
*/
|
||||
private void detectHeader() throws IOException {
|
||||
header = new ArrayList<>();
|
||||
for (String line : Files.readAllLines(file.toPath())) {
|
||||
if (line.startsWith("#")) {
|
||||
header.add(line);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts header back into file. This is required after calling {@link #save()}, because
|
||||
* it destroys the header.
|
||||
*
|
||||
* @throws IOException
|
||||
* if I/O operation fails
|
||||
*/
|
||||
public void fixHeader() throws IOException {
|
||||
if (header == null) return;
|
||||
List<String> content = new ArrayList<>(header);
|
||||
content.addAll(Files.readAllLines(file.toPath()));
|
||||
Files.delete(file.toPath());
|
||||
if (file.createNewFile()) {
|
||||
Files.write(file.toPath(), content);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets value to specified key if key does not exist.
|
||||
*
|
||||
|
||||
@@ -1,26 +1,19 @@
|
||||
package me.neznamy.tab.shared.config.file;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.Writer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.chat.EnumChatFormat;
|
||||
import me.neznamy.tab.shared.chat.TabComponent;
|
||||
import me.neznamy.yamlassist.YamlAssist;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.yaml.snakeyaml.DumperOptions;
|
||||
import org.yaml.snakeyaml.Yaml;
|
||||
import org.yaml.snakeyaml.error.YAMLException;
|
||||
|
||||
import me.neznamy.yamlassist.YamlAssist;
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* YAML implementation of ConfigurationFile
|
||||
@@ -57,12 +50,12 @@ public class YamlConfigurationFile extends ConfigurationFile {
|
||||
TAB tab = TAB.getInstance();
|
||||
tab.setBrokenFile(destination.getName());
|
||||
tab.getPlatform().logWarn(TabComponent.fromColoredText("File " + destination + " has broken syntax."));
|
||||
tab.getPlatform().logInfo(TabComponent.fromColoredText(EnumChatFormat.GOLD + "Error message from yaml parser: " + e.getMessage()));
|
||||
tab.getPlatform().logInfo(TabComponent.fromColoredText("&6Error message from yaml parser: " + e.getMessage()));
|
||||
List<String> suggestions = YamlAssist.getSuggestions(file);
|
||||
if (!suggestions.isEmpty()) {
|
||||
tab.getPlatform().logInfo(TabComponent.fromColoredText(EnumChatFormat.LIGHT_PURPLE + "Suggestions to fix yaml syntax:"));
|
||||
tab.getPlatform().logInfo(TabComponent.fromColoredText("&dSuggestions to fix yaml syntax:"));
|
||||
for (String suggestion : suggestions) {
|
||||
tab.getPlatform().logInfo(TabComponent.fromColoredText(EnumChatFormat.LIGHT_PURPLE + "- " + suggestion));
|
||||
tab.getPlatform().logInfo(TabComponent.fromColoredText("&d- " + suggestion));
|
||||
}
|
||||
}
|
||||
throw e;
|
||||
@@ -70,16 +63,15 @@ public class YamlConfigurationFile extends ConfigurationFile {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save() {
|
||||
public synchronized void save() {
|
||||
try {
|
||||
Writer writer = new OutputStreamWriter(Files.newOutputStream(file.toPath()), StandardCharsets.UTF_8);
|
||||
DumperOptions options = new DumperOptions();
|
||||
options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
|
||||
new Yaml(options).dump(values, writer);
|
||||
writer.close();
|
||||
fixHeader();
|
||||
} catch (IOException e) {
|
||||
TAB.getInstance().getPlatform().logWarn(TabComponent.fromColoredText("Failed to save yaml file " + file.getPath() + " with content " + values.toString()));
|
||||
TAB.getInstance().getPlatform().logWarn(TabComponent.fromColoredText("Failed to save yaml file " + file.getPath() + " with content " + values.toString() + ": " + e.getMessage()));
|
||||
}
|
||||
}
|
||||
}
|
||||
+14
-13
@@ -1,17 +1,17 @@
|
||||
package me.neznamy.tab.shared.config.file;
|
||||
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.config.PropertyConfiguration;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.yaml.snakeyaml.error.YAMLException;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.*;
|
||||
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import me.neznamy.tab.shared.config.PropertyConfiguration;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import org.yaml.snakeyaml.error.YAMLException;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* This class represents a configuration file for properties (groups.yml or users.yml).
|
||||
@@ -45,7 +45,7 @@ public class YamlPropertyConfigurationFile extends YamlConfigurationFile impleme
|
||||
public YamlPropertyConfigurationFile(@Nullable InputStream source, @NotNull File destination) throws IOException {
|
||||
super(source, destination);
|
||||
category = destination.getName().contains("groups") ? "group" : "user";
|
||||
for (Map.Entry<String, Object> entry : getValues().entrySet()) {
|
||||
for (Map.Entry<Object, Object> entry : getValues().entrySet()) {
|
||||
if (entry.getKey().equals(PER_SERVER)) {
|
||||
for (String server : serverGroups) {
|
||||
for (String name : this.<String, Object>getMap(PER_SERVER + "." + server).keySet()) {
|
||||
@@ -63,8 +63,8 @@ public class YamlPropertyConfigurationFile extends YamlConfigurationFile impleme
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (String property : this.<String, Object>getMap(entry.getKey()).keySet()) {
|
||||
checkProperty(destination.getName(), category, entry.getKey(), property, null, null, true);
|
||||
for (String property : this.<String, Object>getMap(entry.getKey().toString()).keySet()) {
|
||||
checkProperty(destination.getName(), category, entry.getKey().toString(), property, null, null, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -126,15 +126,16 @@ public class YamlPropertyConfigurationFile extends YamlConfigurationFile impleme
|
||||
public @NotNull Map<String, Map<String, Object>> getPerServerSettings(@NotNull String name) {
|
||||
return convertMap(getMap(PER_SERVER), name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Set<String> getAllEntries() {
|
||||
Set<String> set = new HashSet<>(values.keySet());
|
||||
Set<Object> set = new HashSet<>(values.keySet());
|
||||
set.remove(PER_WORLD);
|
||||
set.remove(PER_SERVER);
|
||||
Map<String, Map<String, Map<String, String>>> perWorld = getMap(PER_WORLD);
|
||||
perWorld.values().forEach(m -> set.addAll(m.keySet()));
|
||||
Map<String, Map<String, Map<String, String>>> perServer = getMap(PER_SERVER);
|
||||
perServer.values().forEach(m -> set.addAll(m.keySet()));
|
||||
return set;
|
||||
return set.stream().map(Object::toString).collect(Collectors.toSet());
|
||||
}
|
||||
}
|
||||
@@ -62,6 +62,7 @@ public class Config {
|
||||
@NotNull private final String serverName = getSecretOption("server-name", "N/A");
|
||||
private final int permissionRefreshInterval = config.getInt("permission-refresh-interval", 1000);
|
||||
private final boolean enableRedisHook = config.getBoolean("enable-redisbungee-support", true);
|
||||
private final boolean packetEventsCompensation = config.getBoolean("compensate-for-packetevents-bug", false);
|
||||
|
||||
/** If enabled, groups are assigned via permissions instead of permission plugin */
|
||||
private final boolean groupsByPermissions = config.getBoolean("assign-groups-by-permissions", false);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package me.neznamy.tab.shared.config.helper;
|
||||
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.chat.EnumChatFormat;
|
||||
import me.neznamy.tab.shared.chat.TabComponent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@@ -46,7 +45,7 @@ public class ConfigHelper {
|
||||
* Hint message to print
|
||||
*/
|
||||
public void hint(@NotNull File file, @NotNull String message) {
|
||||
TAB.getInstance().getPlatform().logInfo(TabComponent.fromColoredText(EnumChatFormat.GOLD + "[Hint] [" + file.getName() + "] " + message));
|
||||
TAB.getInstance().getPlatform().logInfo(TabComponent.fromColoredText("&6[Hint] [" + file.getName() + "] " + message));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -58,6 +57,6 @@ public class ConfigHelper {
|
||||
* Hint message to print
|
||||
*/
|
||||
public void hint(@NotNull String file, @NotNull String message) {
|
||||
TAB.getInstance().getPlatform().logInfo(TabComponent.fromColoredText(EnumChatFormat.GOLD + "[" + file + "] [Hint] " + message));
|
||||
TAB.getInstance().getPlatform().logInfo(TabComponent.fromColoredText("&6[" + file + "] [Hint] " + message));
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@ package me.neznamy.tab.shared.config.helper;
|
||||
|
||||
import me.neznamy.tab.api.bossbar.BossBar;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.chat.EnumChatFormat;
|
||||
import me.neznamy.tab.shared.chat.SimpleComponent;
|
||||
import me.neznamy.tab.shared.features.sorting.types.SortingType;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
@@ -199,6 +198,6 @@ public class RuntimeErrorPrinter {
|
||||
* Message to log
|
||||
*/
|
||||
public void error(@NotNull String message) {
|
||||
TAB.getInstance().getPlatform().logWarn(new SimpleComponent(EnumChatFormat.decolor(message)));
|
||||
TAB.getInstance().getPlatform().logWarn(new SimpleComponent(message.replace('§', '&')));
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package me.neznamy.tab.shared.config.helper;
|
||||
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.chat.TabComponent;
|
||||
import me.neznamy.tab.shared.chat.SimpleComponent;
|
||||
import me.neznamy.tab.shared.features.sorting.types.SortingType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@@ -56,13 +56,13 @@ public class StartupWarnPrinter {
|
||||
public void startupWarn(@NotNull String... messages) {
|
||||
warnCount++;
|
||||
for (String message : messages) {
|
||||
TAB.getInstance().getPlatform().logWarn(TabComponent.fromColoredText(message));
|
||||
TAB.getInstance().getPlatform().logWarn(new SimpleComponent(message));
|
||||
}
|
||||
}
|
||||
|
||||
public void startupWarn(@NotNull File file, @NotNull String message) {
|
||||
warnCount++;
|
||||
TAB.getInstance().getPlatform().logWarn(TabComponent.fromColoredText("[" + file.getName() + "] " + message));
|
||||
TAB.getInstance().getPlatform().logWarn(new SimpleComponent("[" + file.getName() + "] " + message));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -70,7 +70,7 @@ public class StartupWarnPrinter {
|
||||
*/
|
||||
public void printWarnCount() {
|
||||
if (warnCount == 0) return;
|
||||
TAB.getInstance().getPlatform().logWarn(TabComponent.fromColoredText("Found a total of " + warnCount + " issues."));
|
||||
TAB.getInstance().getPlatform().logWarn(new SimpleComponent("Found a total of " + warnCount + " issues."));
|
||||
// Reset after printing to prevent count going up on each reload
|
||||
warnCount = 0;
|
||||
}
|
||||
|
||||
@@ -1,23 +1,17 @@
|
||||
package me.neznamy.tab.shared.config.mysql;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Properties;
|
||||
|
||||
import javax.sql.rowset.CachedRowSet;
|
||||
import javax.sql.rowset.RowSetProvider;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.chat.EnumChatFormat;
|
||||
import me.neznamy.tab.shared.chat.TabComponent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.sql.rowset.CachedRowSet;
|
||||
import javax.sql.rowset.RowSetProvider;
|
||||
import java.sql.*;
|
||||
import java.util.Properties;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class MySQL {
|
||||
|
||||
@@ -32,7 +26,7 @@ public class MySQL {
|
||||
properties.setProperty("useSSL", String.valueOf(configuration.isUseSSL()));
|
||||
properties.setProperty("characterEncoding", "UTF-8");
|
||||
con = DriverManager.getConnection(String.format("jdbc:mysql://%s:%d/%s", configuration.getHost(), configuration.getPort(), configuration.getDatabase()), properties);
|
||||
TAB.getInstance().getPlatform().logInfo(TabComponent.fromColoredText(EnumChatFormat.GREEN + "Successfully connected to MySQL"));
|
||||
TAB.getInstance().getPlatform().logInfo(TabComponent.fromColoredText("&aSuccessfully connected to MySQL"));
|
||||
}
|
||||
|
||||
public void closeConnection() throws SQLException {
|
||||
|
||||
@@ -22,6 +22,12 @@ public class MySQLGroupConfiguration implements PropertyConfiguration {
|
||||
this.mysql = mysql;
|
||||
mysql.execute("create table if not exists tab_groups (`group` varchar(64), `property` varchar(16), `value` varchar(1024), world varchar(64), server varchar(64))");
|
||||
CachedRowSet crs = mysql.getCRS("select * from tab_groups");
|
||||
if (crs.size() == 0) {
|
||||
TAB.getInstance().getConfigHelper().startup().startupWarn("[MySQL] Using MySQL to store groups and users, however, the database is empty. " +
|
||||
"You can get started by uploading existing data in files using \"/" + TAB.getInstance().getPlatform().getCommand() + " mysql upload\". Further modifications can " +
|
||||
"be done using property commands (/" + TAB.getInstance().getPlatform().getCommand() + " <group / player> <name> <property> <value...>).");
|
||||
return;
|
||||
}
|
||||
while (crs.next()) {
|
||||
String group = crs.getString("group");
|
||||
if (!group.equals(TabConstants.DEFAULT_GROUP)) group = group.toLowerCase(Locale.US);
|
||||
|
||||
@@ -79,7 +79,7 @@ public class NickCompatibility extends TabFeature implements EntryAddListener {
|
||||
player.teamData.getCollisionRule() ? Scoreboard.CollisionRule.ALWAYS : Scoreboard.CollisionRule.NEVER,
|
||||
Collections.singletonList(player.getNickname()),
|
||||
nameTags.getTeamOptions(),
|
||||
prefix.getLastColor().getLegacyColor()
|
||||
prefix.getLastColor()
|
||||
);
|
||||
}
|
||||
if (belowname != null) belowname.processNicknameChange(player);
|
||||
@@ -103,7 +103,7 @@ public class NickCompatibility extends TabFeature implements EntryAddListener {
|
||||
Scoreboard.CollisionRule.ALWAYS,
|
||||
Collections.singletonList(player.getNickname()),
|
||||
nameTags.getTeamOptions(),
|
||||
prefix.getLastColor().getLegacyColor()
|
||||
prefix.getLastColor()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,6 +56,10 @@ public class PlaceholderManagerImpl extends RefreshableFeature implements Placeh
|
||||
|
||||
private final CpuManager cpu;
|
||||
|
||||
/** Placeholders which are refreshed on backend server */
|
||||
@Getter
|
||||
private final Map<String, Integer> bridgePlaceholders = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* Constructs new instance.
|
||||
*
|
||||
@@ -387,6 +391,7 @@ public class PlaceholderManagerImpl extends RefreshableFeature implements Placeh
|
||||
@Override
|
||||
public @NotNull ServerPlaceholderImpl registerServerPlaceholder(@NonNull String identifier, int refresh, @NonNull Supplier<String> supplier) {
|
||||
ensureActive();
|
||||
bridgePlaceholders.remove(identifier);
|
||||
return registerPlaceholder(new ServerPlaceholderImpl(identifier, refresh, supplier));
|
||||
}
|
||||
|
||||
@@ -394,6 +399,7 @@ public class PlaceholderManagerImpl extends RefreshableFeature implements Placeh
|
||||
public @NotNull PlayerPlaceholderImpl registerPlayerPlaceholder(@NonNull String identifier, int refresh,
|
||||
@NonNull Function<me.neznamy.tab.api.TabPlayer, String> function) {
|
||||
ensureActive();
|
||||
bridgePlaceholders.remove(identifier);
|
||||
return registerPlaceholder(new PlayerPlaceholderImpl(identifier, refresh, function));
|
||||
}
|
||||
|
||||
@@ -401,9 +407,24 @@ public class PlaceholderManagerImpl extends RefreshableFeature implements Placeh
|
||||
public @NotNull RelationalPlaceholderImpl registerRelationalPlaceholder(
|
||||
@NonNull String identifier, int refresh, @NonNull BiFunction<me.neznamy.tab.api.TabPlayer, me.neznamy.tab.api.TabPlayer, String> function) {
|
||||
ensureActive();
|
||||
bridgePlaceholders.remove(identifier);
|
||||
return registerPlaceholder(new RelationalPlaceholderImpl(identifier, refresh, function));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public PlayerPlaceholderImpl registerBridgePlaceholder(@NonNull String identifier, int backendRefresh) {
|
||||
ensureActive();
|
||||
bridgePlaceholders.put(identifier, backendRefresh);
|
||||
return registerPlaceholder(new PlayerPlaceholderImpl(identifier, -1, player -> null));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public RelationalPlaceholderImpl registerRelationalBridgePlaceholder(@NonNull String identifier, int backendRefresh) {
|
||||
ensureActive();
|
||||
bridgePlaceholders.put(identifier, backendRefresh);
|
||||
return registerPlaceholder(new RelationalPlaceholderImpl(identifier, -1, (viewer, target) -> null));
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public synchronized TabPlaceholder getPlaceholder(@NonNull String identifier) {
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
package me.neznamy.tab.shared.features;
|
||||
|
||||
import me.neznamy.tab.shared.config.file.ConfigurationFile;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* This class tracks toggling of a feature using commands when "remember-toggle-choice" is enabled
|
||||
* in the configuration of that specific feature.
|
||||
*/
|
||||
public class ToggleManager {
|
||||
|
||||
/** Player data file to store toggle status in */
|
||||
@NotNull
|
||||
private final ConfigurationFile playerDataFile;
|
||||
|
||||
/** Name of the section to save the toggled players list as in the player data file */
|
||||
@NotNull
|
||||
private final String sectionName;
|
||||
|
||||
/** List of players who toggled the feature */
|
||||
@NotNull
|
||||
private final Set<String> toggledPlayers;
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters and loads list of toggled players from the file.
|
||||
*
|
||||
* @param playerDataFile
|
||||
* File where toggled players are saved
|
||||
* @param sectionName
|
||||
* Name of the section to save the toggled players list as in the player data file
|
||||
*/
|
||||
public ToggleManager(@NotNull ConfigurationFile playerDataFile, @NotNull String sectionName) {
|
||||
this.playerDataFile = playerDataFile;
|
||||
this.sectionName = sectionName;
|
||||
toggledPlayers = new HashSet<>(playerDataFile.getStringList(sectionName, Collections.emptyList()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for converting player to use UUID instead of name (from the old system).
|
||||
*
|
||||
* @param player
|
||||
* Player to check to convert
|
||||
*/
|
||||
public void convert(@NotNull TabPlayer player) {
|
||||
if (toggledPlayers.remove(player.getName())) {
|
||||
toggledPlayers.add(player.getUniqueId().toString());
|
||||
save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if the player has toggled this feature, {@code false} if not.
|
||||
*
|
||||
* @param player
|
||||
* Player to check for
|
||||
* @return {@code true} if player has toggled the feature, {@code false} if not
|
||||
*/
|
||||
public boolean contains(@NotNull TabPlayer player) {
|
||||
return toggledPlayers.contains(player.getUniqueId().toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds player to list of toggled players.
|
||||
*
|
||||
* @param player
|
||||
* Player to add
|
||||
*/
|
||||
public void add(@NotNull TabPlayer player) {
|
||||
if (toggledPlayers.add(player.getUniqueId().toString())) {
|
||||
save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes player from the list of toggled players.
|
||||
*
|
||||
* @param player
|
||||
* Player to remove
|
||||
*/
|
||||
public void remove(@NotNull TabPlayer player) {
|
||||
if (toggledPlayers.remove(player.getUniqueId().toString())) {
|
||||
save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves list of toggled players into file.
|
||||
*/
|
||||
private void save() {
|
||||
playerDataFile.set(sectionName, new ArrayList<>(toggledPlayers));
|
||||
}
|
||||
}
|
||||
Loaded 100 of 150 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user