mirror of
https://github.com/NEZNAMY/TAB.git
synced 2026-11-12 20:22:01 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
08f0d7f635 | ||
|
|
73a5b14084 | ||
|
|
da8befca86 | ||
|
|
33cc64a441 | ||
|
|
a13fb02c75 | ||
|
|
c9d9a8f04a | ||
|
|
5de6c850ed | ||
|
|
d98044c51c | ||
|
|
0575bf0239 | ||
|
|
992c96dd52 | ||
|
|
4d969ab320 | ||
|
|
283713ff8a | ||
|
|
231aa89686 | ||
|
|
d67372b72f | ||
|
|
93046154a9 | ||
|
|
2966f57d42 | ||
|
|
951cb260c5 | ||
|
|
2256e9ef77 | ||
|
|
188e0ea932 | ||
|
|
6e6848e007 | ||
|
|
fc83f0fec6 | ||
|
|
034a4b6b7c | ||
|
|
30593c1bfd | ||
|
|
89e69591e0 | ||
|
|
147279a8ca |
No files matched your search
@@ -0,0 +1,20 @@
|
||||
name: Problem
|
||||
description: A problem with the plugin that is not a bug
|
||||
labels: ["Type: Problem"]
|
||||
body:
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Description
|
||||
description: Describe the problem
|
||||
placeholder: |
|
||||
Describe the problem here. This is not limited to the plugin jar itself. You may report issues with the wiki, plugin description on marketplaces, or anything else related to TAB.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Checklist
|
||||
description: Let's make sure this report is valid
|
||||
options:
|
||||
- label: This is not an error, bug report, compatibility issue or anything of that kind
|
||||
required: true
|
||||
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="92" height="20" role="img" aria-label="Release: 6.1.0"><title>Release: 6.1.0</title><filter id="blur"><feGaussianBlur stdDeviation="16"/></filter><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="92" height="20" rx="3"/></clipPath><g clip-path="url(#r)"><rect width="53" height="20" fill="#555"/><rect x="53" width="39" height="20" fill="#007ec6"/><rect width="92" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><g transform="scale(.1)"><g aria-hidden="true" fill="#010101"><text x="275" y="150" fill-opacity=".8" filter="url(#blur)" textLength="430">Release</text><text x="275" y="150" fill-opacity=".3" textLength="430">Release</text></g><text x="275" y="140" textLength="430">Release</text></g><g transform="scale(.1)"><g aria-hidden="true" fill="#010101"><text x="715" y="150" fill-opacity=".8" filter="url(#blur)" textLength="290">6.1.0</text><text x="715" y="150" fill-opacity=".3" textLength="290">6.1.0</text></g><text x="715" y="140" textLength="290">6.1.0</text></g></g></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="92" height="20" role="img" aria-label="Release: 6.1.2"><title>Release: 6.1.2</title><filter id="blur"><feGaussianBlur stdDeviation="16"/></filter><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="92" height="20" rx="3"/></clipPath><g clip-path="url(#r)"><rect width="53" height="20" fill="#555"/><rect x="53" width="39" height="20" fill="#007ec6"/><rect width="92" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><g transform="scale(.1)"><g aria-hidden="true" fill="#010101"><text x="275" y="150" fill-opacity=".8" filter="url(#blur)" textLength="430">Release</text><text x="275" y="150" fill-opacity=".3" textLength="430">Release</text></g><text x="275" y="140" textLength="430">Release</text></g><g transform="scale(.1)"><g aria-hidden="true" fill="#010101"><text x="715" y="150" fill-opacity=".8" filter="url(#blur)" textLength="290">6.1.2</text><text x="715" y="150" fill-opacity=".3" textLength="290">6.1.2</text></g><text x="715" y="140" textLength="290">6.1.2</text></g></g></svg>
|
||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
@@ -274,7 +274,7 @@ if __name__ == "__main__":
|
||||
]
|
||||
)
|
||||
|
||||
release = "6.1.0"
|
||||
release = "6.1.2"
|
||||
download_image(f"https://img.shields.io/badge/Release-{release}-blue.svg", "Badge_Release.svg")
|
||||
|
||||
java = "8+"
|
||||
|
||||
@@ -8,9 +8,10 @@ For TAB to properly detect player as a bedrock player, floodgate and TAB must be
|
||||
If TAB is installed on backend and floodgate is on both proxy and backend,
|
||||
make sure [they are connected](https://geysermc.org/wiki/floodgate/setup/?platform=proxy-servers).
|
||||
|
||||
To make sure floodgate is configured correctly, you have two options:
|
||||
To make sure floodgate is configured correctly, you can do any of the following:
|
||||
* Set `debug: true` in TAB config and check console output on join/reload. It should say `Floodgate returned bedrock status TRUE for player <player>`.
|
||||
* Run `/tab parse <player> %bedrock%`. It should return `true`.
|
||||
* Run `/tab dump <player>`, open the link and see ` bedrockPlayer: '<true|false> (floodgate is installed: <true|false>)'`
|
||||
|
||||
If any of them say `false` for an actual bedrock player, you didn't connect floodgate correctly.
|
||||
|
||||
|
||||
@@ -11,12 +11,12 @@ TAB does not depend on any other plugins. The experience can however be enhanced
|
||||
|
||||
# Supported server software and versions
|
||||
With the introduction of new Minecraft versions, it is becoming harder and harder to support many versions in the same jar, eventually making it impossible since Minecraft 26.1.
|
||||
**As a general rule, if you want the latest TAB version for your server please use [Modrinth](https://modrinth.com/plugin/tab-was-taken)'s download filter where you can select your server software and version and you'll get the latest jar.**
|
||||
**As a general rule, if you want the latest TAB version for your server, please use [Modrinth](https://modrinth.com/plugin/tab-was-taken)'s download filter, where you can select your server software and version and you'll get the latest jar.**
|
||||
|
||||
Currently, the latest Minecraft version supported by TAB is **26.2**. When new versions come out, TAB is always updated soon after to support them.
|
||||
|
||||
If your server software and version is reasonably popular and TAB hasn't been backported to that version in a very long time, you may [request a backport](https://github.com/NEZNAMY/TAB/issues/new/choose) (unless backports to that version are no longer offered, see below).
|
||||
Since TAB 6.0.0, Minecraft 1.x started taking hits with each subsequent versions - Bukkit versions started disappearing and **modded platforms no longer offer backports of TAB 6.0.0+ to MC 1.x** (this is because the build scripts had to be massively changed for MC 26+).
|
||||
Since TAB 6.0.0, Minecraft 1.x started taking hits with each subsequent TAB versions - Bukkit versions started disappearing and **modded platforms no longer offer backports of TAB 6.0.0+ to MC 1.x** (this is because the build scripts had to be massively changed for MC 26+).
|
||||
|
||||
The sections below provide **additional** information for each platform.
|
||||
|
||||
@@ -38,8 +38,7 @@ Every TAB update only supports the latest Fabric version(s) at the time of relea
|
||||
Minecraft 1.x - TAB 5.5.0 was backported to 1.20 - 1.21.11 and the jars were uploaded to Modrinth. TAB 5.0.7 supports all Fabric versions from 1.14 to 1.19.4. Backports to 1.x are no longer offered.
|
||||
|
||||
## Forge
|
||||
Every TAB update only supports the latest Forge version(s) at the time of release.
|
||||
**Currently, Forge 26.x is not supported because the modding tool is broken**.
|
||||
Every TAB update only supports the latest Forge version(s) at the time of release.
|
||||
|
||||
Minecraft 1.x - TAB 5.5.0 was backported to 1.20 - 1.21.11 and the jars were uploaded to Modrinth, along with a few other random versions people requested. If your version is not available, you may request a backport.
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
# About
|
||||
This page explains how to import TAB's API into your project and together with other pages everything you can do with it.
|
||||
|
||||
Before starting please seriously reconsider if you really need this and cannot already achieve it using existing features (such as registering placeholders into PlaceholderAPI instead of TAB, using display conditions for scoreboards, ...). If not, consider making a request.
|
||||
Before starting, please seriously reconsider if you really need this and cannot already achieve it using existing features (such as registering placeholders into PlaceholderAPI instead of TAB, using display conditions for scoreboards, ...). If not, consider making a request.
|
||||
|
||||
# Adding the dependency
|
||||
|
||||
@@ -35,7 +35,7 @@ For Maven users, you can add the API dependency like this:
|
||||
<dependency>
|
||||
<groupId>com.github.NEZNAMY</groupId>
|
||||
<artifactId>TAB-API</artifactId>
|
||||
<version>6.0.0</version>
|
||||
<version>6.1.2</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
@@ -50,7 +50,7 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly 'com.github.NEZNAMY:TAB-API:6.0.0'
|
||||
compileOnly 'com.github.NEZNAMY:TAB-API:6.1.2'
|
||||
}
|
||||
```
|
||||
|
||||
@@ -61,7 +61,7 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly("com.github.NEZNAMY", "TAB-API", "6.0.0")
|
||||
compileOnly("com.github.NEZNAMY", "TAB-API", "6.1.2")
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -55,10 +55,11 @@ All the options are explained in the following table.
|
||||
|---------------------|-----------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| enabled | true | Enables / Disables the feature |
|
||||
| value | %health% | [1.20.2-] An integer from -2147483648 to 2147483647, doesn't support decimal values. The number is always white. Supports placeholders with player-specific output, such as player health. Only visible on 1.20.2 and lower. <br/> **Note**: Even if you only support 1.20.3+, you still need to configure this value to properly evaluate to a number, because the value is still sent to the client (just not displayed). You can set it to `0` for simplicity. |
|
||||
| fancy-value | &c%health% | [1.20.3+] Any text, supports placeholders with per-player output. Only visible on 1.20.3+, where it completely replaces `value`. When evaluates to an empty string, the entire belowname is hidden for viewers using MC 26.2+ (when it was made possible). |
|
||||
| fancy-value-default | NPC | [1.20.3 - 26.1.2] Default number format (`fancy-value`) for all player entities. `fancy-value` is displayed on every real player, therefore this default value will only appear on player entities which are not actual players, a.k.a. NPCs. Only visible on since 1.20.3 (the function did not exist prior) until 26.1.2 (player entities without scores no longer display any belowname since 26.2). |
|
||||
| fancy-value | &c%health% | [1.20.3+] Any text, supports placeholders with per-player output. Only visible on 1.20.3+, where it completely replaces `value`. When evaluates to an empty string, the entire belowname is hidden for viewers using MC 26.2+ (when it was made possible). |
|
||||
| fancy-value-default | NPC | [1.20.3 - 26.1.2] Default number format (`fancy-value`) for all player entities. `fancy-value` is displayed on every real player, therefore this default value will only appear on player entities which are not actual players, a.k.a. NPCs. Only visible on since 1.20.3 (the function did not exist prior) until 26.1.2 (player entities without scores no longer display any belowname since 26.2). |
|
||||
| title | Health | Shared label shown after the score for every player entity. Player sees the same text on everyone (= placeholders are parsed for the viewer). Use the `value`/`fancy-value` fields for per‑player data; use `title` only for static labels like `Health` or placeholders which are supposed to be parsed for the viewing player. |
|
||||
| disable-condition | %world%=disabledworld | A [condition](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Conditional-placeholders) (either name of a condition or a conditional expression) that must be met for disabling the feature for players. Set to empty for not disabling the feature ever. <br/> **Note**: Disabling the feature for a player means sending objective unregister packet to them, which results in player not seeing belowname on anyone anymore. It doesn't work the other way around - you cannot disable this feature on target players, only for viewers. |
|
||||
| view-distance | 10 | [26.1+] How close (in blocks) to a player one must be to see their belowname. This value is configurable since Minecraft 26.1, so this option won't work on older versions. |
|
||||
|
||||
# Limitations
|
||||
* [1.12.2-] Title length is limited to 32 characters (including color codes).
|
||||
|
||||
@@ -52,7 +52,7 @@ Most commonly used plugins for this are [FastLogin](https://www.spigotmc.org/res
|
||||
which change connections of premium players who enabled it to online connections.
|
||||
The same goes for disabling heads - you cannot disable them if you have online mode enabled.
|
||||
|
||||
Minecraft 1.21.9 has added [object components](https://github.com/NEZNAMY/TAB/wiki/How-to-use-Minecraft-components#object-components-1219), which allow you to display player heads. You can put for example `<head:name:%player%>` at the beginning of tabprefix to simulate this.
|
||||
Minecraft 1.21.9 has added [object components](https://github.com/NEZNAMY/TAB/wiki/How-to-use-Minecraft-components#object-components-1219), which allow you to display player heads. You can put for example `<head:%player%>` at the beginning of tabprefix to simulate this (you will need a plugin like SkinsRestorer for the players to properly be able to recognize skins by player name).
|
||||
|
||||
## #7 - Where can I find current default config files?
|
||||
You can do any of the following:
|
||||
@@ -118,16 +118,14 @@ Using `''` will result in the text being display literally, not as a code.
|
||||
**Note:** Minecraft does not support every single UTF symbol and displays unsupported symbols as a box. You can try sending your symbol into chat and see if it works or not. If not, it's not supported by MC.
|
||||
|
||||
## #14 - Is there a way to remove all players from tablist?
|
||||
In Minecraft, for a player to be visible in-game, they also need to appear in the tablist.
|
||||
Remember the issue with NPCs showing up in the tablist? It's the same underlying mechanic.
|
||||
Here are various ways to achieve this goal:
|
||||
|
||||
If you're okay with players not being visible in the world, you can use a plugin that hides all players completely.
|
||||
* **Option 1: Hide players entirely** - In Minecraft, for a player to be visible in-game, they also need to added into the tablist. If you don't mind hiding players from game as well, there are plugins that offer this (TAB is, however, not one of them).
|
||||
In 1.19.3, the player info packet received a new field that allows us to explicitly hide players from tablist while all the necessary data is still sent to players.
|
||||
|
||||
Since Minecraft 1.19.3, this limitation has been lifted — it's now possible to have entities in the game without showing them in the tablist.
|
||||
However, the TAB plugin does **not** support removing all players from the tablist in any version of Minecraft.
|
||||
* **Option 2: Layout with 0 slots** - Since Minecraft 1.19.3, the limitation explained above has been lifted — it's now possible to have entities in the game without showing them in the tablist. TAB does not have an explicit feature for doing this. However, since 1.19.3, the [Layout](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Layout) feature uses this mechanic to allow less than 80 slots. This feature can be "abused" by enabling the feature, removing fixed-slots and adding `slot-count: 0` to the displayed layout. The final effect will be a playerless tablist.
|
||||
|
||||
An alternative solution using TAB is putting a lot of empty lines into header,
|
||||
which will push all players out of the screen.
|
||||
* **Option 3: Pushing players out of the screen with header** - You can add a ton of empty lines into the header. As a result, players will be pushed down out of the screen and effectively not visible.
|
||||
|
||||
## #15 - How to add images to tablist?
|
||||
You can check out [this Reddit post](https://www.reddit.com/r/admincraft/comments/llrgty/comment/gnswdcz/?utm_source=share&utm_medium=web2x&context=3).
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# About the wiki
|
||||
The wiki explains the functionality of the plugin as of version **6.1.0**.
|
||||
The wiki explains the functionality of the plugin as of version **6.1.2**.
|
||||
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.
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
* [Object components (1.21.9+)](#object-components-1219)
|
||||
* [Atlas sprite](#atlas-sprite)
|
||||
* [Player sprite](#player-sprite)
|
||||
* [Compatibility with < 1.21.9](#compatibility-with--1219)
|
||||
* [Compatibility with <1.21.9](#compatibility-with-1219)
|
||||
* [MiniMessage](#minimessage)
|
||||
* [Config options](#config-options)
|
||||
* [Tips & Tricks](#tips--tricks)
|
||||
@@ -121,13 +121,6 @@ Example:
|
||||
|
||||
|
||||
## Player sprite
|
||||
> [!WARNING]
|
||||
> In order to use these, you will need to disable MiniMessage support, because head components were not added to MiniMessage yet. Even when they are added, they will have a different syntax, so TAB will be updated to match the format to avoid complications. For now, use the existing syntax for prototyping and testing. To disable MiniMessage support, set:
|
||||
> ```
|
||||
> components:
|
||||
> minimessage-support: false
|
||||
> ```
|
||||
> in **config.yml**.
|
||||
|
||||
This type allows you to display Minecraft heads. There are 3 ways to define a head:
|
||||
| Type | Functionality |
|
||||
@@ -139,27 +132,25 @@ This type allows you to display Minecraft heads. There are 3 ways to define a he
|
||||
Here are all the ways you can display player skins using TAB and their implementation (using `ALL CAPS` for "placeholder" text instead of `<>` to avoid confusion as `<>` is actual part of the syntax):
|
||||
| Syntax | Explanation | Implementation |
|
||||
|-----|-------------|------------|
|
||||
| `<head:name:NAME>` | Shows head defined by player name | name |
|
||||
| `<head:id:UUID>` | Shows head defined by player UUID | uuid |
|
||||
| `<head:signed_texture:VALUE;SIGNATURE>` | Shows head defined by value and signature | value and signature |
|
||||
| `<head:player:NAME` | Connects to Mojang and retrieves value and signature of defined player | value and signature |
|
||||
| `<head:mineskin:ID>` | Connects to Mineskin and retrieves value and signature of specified upload ID | value and signature |
|
||||
| `<head:texture:TEXTURE>` | Gets the texture from `http://textures.minecraft.net/texture/TEXTURE` | value and signature |
|
||||
| `<head:NAME>` | Shows head defined by player name | name |
|
||||
| `<head:UUID>` | Shows head defined by player UUID | uuid |
|
||||
| `<mineskin:ID>` | Connects to Mineskin and retrieves value and signature of specified upload ID | value and signature |
|
||||
| `<head_texture:TEXTURE>` | Gets the texture from `http://textures.minecraft.net/texture/TEXTURE` | value and signature |
|
||||
|
||||
All these syntaxes are identical to MiniMessage syntax for convenience. MiniMessage only supports the first two types by default, but TAB adds tag resolvers for `mineskin` and `head_texture` as well.
|
||||
|
||||
Here is an example for all types:
|
||||
```
|
||||
footer:
|
||||
- 'Head by id: <head:id:%uuid%>' # This will obviously not work in offline mode
|
||||
- 'Head by name: <head:name:%player%>' # May or may not work in offline mode (see above for full explanation)
|
||||
- 'Head by raw texture: <head:signed_texture:ewogICJ0aW1lc3RhbXAiIDogMTc1NjMxNjc0OTk4MywKICAicHJvZmlsZUlkIiA6ICIyMzdkOGI1NTNmOTc0NzQ5YWE2MGU5ZmU5N2I0NTA2MiIsCiAgInByb2ZpbGVOYW1lIiA6ICJfTkVaTkFNWV8iLAogICJzaWduYXR1cmVSZXF1aXJlZCIgOiB0cnVlLAogICJ0ZXh0dXJlcyIgOiB7CiAgICAiU0tJTiIgOiB7CiAgICAgICJ1cmwiIDogImh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZmY1ZTNhNWRmODkxYzEzMTdhZjUzMmMzNmY4NDgxNDBhNDBmMGZhMGNlZGRmZmEwNWU5NGU1NzU1OGQxY2Q4YyIKICAgIH0KICB9Cn0=;YFVoqA0CqWgnZhme5s/M7xtadURyLkyLWVsh0vN8BqdfwzN32nqlBJb2pRjkxIs0V21EOeKbGFjgdKQKzPRQdf6sTFQ/x0KOjUg8A/neAiwCvDSDrsTQ2Yf7yoVP6M2PpuB7te19N+I139mGY5psxU100x6GiV/uFfpUfG3XBJog43JtXzRJ9fRtoSeIEzqLrkpCmt6o5Mzo6GZFZc4CtI76OU90Mg9ZvTZTYelvtjFtllxTtkNZCcglzvh5R19+qtzLOzEr+N8m4Ed+5yZyezEb3LeeSgmbSLIjuOKuUupE+2F6yVYP3eKhgGLZ2G+cg9TZZjTCsNMzHqewM/1+qStzTQCdNmggXCGcIfC8HcYsBfdv4SicxBq8ff+BDyveFZMFyREpQNqX/fLmlz16cmxHvBQ9qqA+IzIsBJ7i/mrG78jBhkJsvtcHMHvTviXascCSQP1TCn58D6HJ/Agww6HFTJe/B6sX0Utzm0URE4jZK7wnhrx0q2H8OkCXc5ZwuXALqtvk0uWmZo2RMnIGQNi4nb5AUsGV8pNirhS16MfqZeJ4q0HGaIVscRp4jazab6kMVKusbuqQB1cZNbaao7mP1HAUVCd5geWQL4CQQIc6gv0q3KT2E45d0YeYqpy/RppKMWmg1+aQ5wVGQW4/p2mqXos71FKy6vP0ur6txd8=>'
|
||||
- 'Head by player: <head:player:%player%>'
|
||||
- 'Head by mineskin: <head:mineskin:37e93c8e12cd426cb28fce31969e0674>'
|
||||
- 'Head by texture: <head:texture:ff9bb9e56125c8227b94bbda9f6e0f862931c229255ba8f1205d13c44c1bb561>'
|
||||
- 'Head by id: <head:%uuid%>' # This will obviously not work in offline mode
|
||||
- 'Head by name: <head:%player%>' # May or may not work in offline mode (see above for full explanation)
|
||||
- 'Head by mineskin: <mineskin:37e93c8e12cd426cb28fce31969e0674>'
|
||||
- 'Head by texture: <head_texture:ff9bb9e56125c8227b94bbda9f6e0f862931c229255ba8f1205d13c44c1bb561>'
|
||||
```
|
||||
|
||||
<img width="331" height="111" alt="image" src="https://github.com/user-attachments/assets/5912975a-a892-42c3-b9ab-7eab33ee42b6" />
|
||||
|
||||
## Compatibility with < 1.21.9
|
||||
## Compatibility with <1.21.9
|
||||
You may be wondering what happens when you try to use these and the client is below 1.21.9.
|
||||
See table below for behavior based on your setup.
|
||||
|
||||
@@ -177,8 +168,7 @@ TAB has [MiniMessage](https://docs.advntr.dev/minimessage/format.html) hook, how
|
||||
|
||||
When MiniMessage is detected on your server, it is automatically used unless support is disabled. All codes are translated to MiniMessage syntax and then parsed by MiniMessage.
|
||||
|
||||
> [!WARNING]
|
||||
> TAB's gradient syntax (`<#RRGGBB>Text</#RRGGBB>`) is a compatibility layer that TAB rewrites before parsing, it is more fragile than native [MiniMessage](https://docs.advntr.dev/minimessage/format.html) gradients (`<gradient:#RRGGBB:#RRGGBB>Text</gradient>`). On **Velocity**, this is especially problematic because [MiniPlaceholders](https://github.com/MiniPlaceholders/MiniPlaceholders) (see [TAB integration PR #1677](https://github.com/NEZNAMY/TAB/pull/1677)) parses MiniMessage *before* TAB can rewrite its gradient syntax. For example, placeholder output like `<#E0B11E>MyServer</#FF0000>` is interpreted by MiniPlaceholders as a hex color tag (`<#E0B11E>`), but `</#FF0000>` is not recognized as a gradient closing tag, so the formatting breaks. Prefer native MiniMessage syntax in placeholder output (e.g. `<gradient:#E0B11E:#FF0000>MyServer</gradient>`). See [Placeholders - MiniPlaceholders](https://github.com/NEZNAMY/TAB/wiki/Placeholders#miniplaceholders) for more information.
|
||||
TAB creates a modified MiniMessage parser that also accepts `<head_texture:TEXTURE>` and `<mineskin:ID>` head syntaxes just like TAB's own component parser.
|
||||
|
||||
# Config options
|
||||
| Option name | Default value | Description |
|
||||
|
||||
@@ -4,9 +4,11 @@ import java.util.UUID;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.Setter;
|
||||
import me.neznamy.tab.api.belowname.BelowNameManager;
|
||||
import me.neznamy.tab.api.bossbar.BossBarManager;
|
||||
import me.neznamy.tab.api.event.EventBus;
|
||||
import me.neznamy.tab.api.placeholder.PlaceholderManager;
|
||||
import me.neznamy.tab.api.playerlistobjective.PlayerListObjectiveManager;
|
||||
import me.neznamy.tab.api.scoreboard.ScoreboardManager;
|
||||
import me.neznamy.tab.api.tablist.SortingManager;
|
||||
import me.neznamy.tab.api.tablist.HeaderFooterManager;
|
||||
@@ -83,6 +85,22 @@ public abstract class TabAPI {
|
||||
*/
|
||||
public abstract @Nullable ScoreboardManager getScoreboardManager();
|
||||
|
||||
/**
|
||||
* Returns belowname manager instance. Unlike other features, this one is returned even if
|
||||
* it is disabled in config, which allows enabling it for specific players using the API.
|
||||
*
|
||||
* @return belowname manager
|
||||
*/
|
||||
public abstract @Nullable BelowNameManager getBelowNameManager();
|
||||
|
||||
/**
|
||||
* Returns playerlist objective manager instance. Unlike other features, this one is returned
|
||||
* even if it is disabled in config, which allows enabling it for specific players using the API.
|
||||
*
|
||||
* @return playerlist objective manager
|
||||
*/
|
||||
public abstract @Nullable PlayerListObjectiveManager getPlayerListObjectiveManager();
|
||||
|
||||
/**
|
||||
* Returns team manager instance if the feature is enabled. If not, returns {@code null}.
|
||||
*
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
package me.neznamy.tab.api.belowname;
|
||||
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.api.TabAPI;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Interface for manipulating the belowname objective of individual players.
|
||||
* <p>
|
||||
* Instance can be obtained using {@link TabAPI#getBelowNameManager()}.
|
||||
* Unlike other features, this one is available even if it is disabled in config,
|
||||
* which allows enabling it for specific players using {@link #setEnabled(TabPlayer, Boolean)}.
|
||||
*/
|
||||
@SuppressWarnings("unused") // API class
|
||||
public interface BelowNameManager {
|
||||
|
||||
/**
|
||||
* Forces the objective to be visible or hidden for specified player, overriding
|
||||
* both the config option and the disable condition. This only affects what the
|
||||
* player sees, not how the player is displayed to others. Use {@code null} to
|
||||
* give the control back to configuration.
|
||||
*
|
||||
* @param player
|
||||
* Player to set visibility for
|
||||
* @param enabled
|
||||
* Forced visibility or {@code null} to reset back to configuration
|
||||
* @see #getCustomEnabled(TabPlayer)
|
||||
*/
|
||||
void setEnabled(@NonNull TabPlayer player, @Nullable Boolean enabled);
|
||||
|
||||
/**
|
||||
* Returns forced visibility assigned using {@link #setEnabled(TabPlayer, Boolean)}.
|
||||
* If no visibility is forced, returns {@code null}.
|
||||
*
|
||||
* @param player
|
||||
* Player to get forced visibility of
|
||||
* @return Forced visibility or {@code null} if not forced
|
||||
* @see #setEnabled(TabPlayer, Boolean)
|
||||
*/
|
||||
@Nullable Boolean getCustomEnabled(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Changes score value of specified player to provided value. Supports placeholders,
|
||||
* however, the value must evaluate to a number. Only 1.20.2 and lower clients display
|
||||
* this value, newer clients display the number format instead. Use {@code null} to
|
||||
* reset value back to the configured one.
|
||||
*
|
||||
* @param player
|
||||
* Player to change score value of
|
||||
* @param value
|
||||
* New score value or {@code null} to reset back to configuration
|
||||
* @see #getCustomValue(TabPlayer)
|
||||
* @see #setFancyValue(TabPlayer, String)
|
||||
*/
|
||||
void setValue(@NonNull TabPlayer player, @Nullable String value);
|
||||
|
||||
/**
|
||||
* Returns score value assigned using {@link #setValue(TabPlayer, String)}.
|
||||
* If no value is assigned, returns {@code null}.
|
||||
*
|
||||
* @param player
|
||||
* Player to get custom score value of
|
||||
* @return Custom score value assigned using the API
|
||||
* @see #setValue(TabPlayer, String)
|
||||
*/
|
||||
@Nullable String getCustomValue(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Changes number format of specified player to provided value. Supports placeholders,
|
||||
* as well as any supported RGB formats. Only 1.20.3+ clients display the number format,
|
||||
* older clients display the score value instead. If it evaluates to an empty string, the
|
||||
* score is not displayed at all. Use {@code null} to reset value back to the configured one.
|
||||
*
|
||||
* @param player
|
||||
* Player to change number format of
|
||||
* @param fancyValue
|
||||
* New number format or {@code null} to reset back to configuration
|
||||
* @see #getCustomFancyValue(TabPlayer)
|
||||
* @see #setValue(TabPlayer, String)
|
||||
*/
|
||||
void setFancyValue(@NonNull TabPlayer player, @Nullable String fancyValue);
|
||||
|
||||
/**
|
||||
* Returns number format assigned using {@link #setFancyValue(TabPlayer, String)}.
|
||||
* If no number format is assigned, returns {@code null}.
|
||||
*
|
||||
* @param player
|
||||
* Player to get custom number format of
|
||||
* @return Custom number format assigned using the API
|
||||
* @see #setFancyValue(TabPlayer, String)
|
||||
*/
|
||||
@Nullable String getCustomFancyValue(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Changes objective title of specified player to provided value. Supports placeholders,
|
||||
* as well as any supported RGB formats. Use {@code null} to reset value back to the
|
||||
* configured one.
|
||||
*
|
||||
* @param player
|
||||
* Player to change objective title of
|
||||
* @param title
|
||||
* New objective title or {@code null} to reset back to configuration
|
||||
* @see #getCustomTitle(TabPlayer)
|
||||
*/
|
||||
void setTitle(@NonNull TabPlayer player, @Nullable String title);
|
||||
|
||||
/**
|
||||
* Returns objective title assigned using {@link #setTitle(TabPlayer, String)}.
|
||||
* If no title is assigned, returns {@code null}.
|
||||
*
|
||||
* @param player
|
||||
* Player to get custom objective title of
|
||||
* @return Custom objective title assigned using the API
|
||||
* @see #setTitle(TabPlayer, String)
|
||||
*/
|
||||
@Nullable String getCustomTitle(@NonNull TabPlayer player);
|
||||
}
|
||||
+153
@@ -0,0 +1,153 @@
|
||||
package me.neznamy.tab.api.playerlistobjective;
|
||||
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.api.TabAPI;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Interface for manipulating the playerlist objective of individual players.
|
||||
* <p>
|
||||
* Instance can be obtained using {@link TabAPI#getPlayerListObjectiveManager()}.
|
||||
* Unlike other features, this one is available even if it is disabled in config,
|
||||
* which allows enabling it for specific players using {@link #setEnabled(TabPlayer, Boolean)}.
|
||||
*/
|
||||
@SuppressWarnings("unused") // API class
|
||||
public interface PlayerListObjectiveManager {
|
||||
|
||||
/**
|
||||
* Forces the objective to be visible or hidden for specified player, overriding
|
||||
* both the config option and the disable condition. This only affects what the
|
||||
* player sees, not how the player is displayed to others. Use {@code null} to
|
||||
* give the control back to configuration.
|
||||
*
|
||||
* @param player
|
||||
* Player to set visibility for
|
||||
* @param enabled
|
||||
* Forced visibility or {@code null} to reset back to configuration
|
||||
* @see #getCustomEnabled(TabPlayer)
|
||||
*/
|
||||
void setEnabled(@NonNull TabPlayer player, @Nullable Boolean enabled);
|
||||
|
||||
/**
|
||||
* Returns forced visibility assigned using {@link #setEnabled(TabPlayer, Boolean)}.
|
||||
* If no visibility is forced, returns {@code null}.
|
||||
*
|
||||
* @param player
|
||||
* Player to get forced visibility of
|
||||
* @return Forced visibility or {@code null} if not forced
|
||||
* @see #setEnabled(TabPlayer, Boolean)
|
||||
*/
|
||||
@Nullable Boolean getCustomEnabled(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Changes score value of specified player to provided value. Supports placeholders,
|
||||
* however, the value must evaluate to a number. Only 1.20.2 and lower clients display
|
||||
* this value, newer clients display the number format instead. Use {@code null} to
|
||||
* reset value back to the configured one.
|
||||
*
|
||||
* @param player
|
||||
* Player to change score value of
|
||||
* @param value
|
||||
* New score value or {@code null} to reset back to configuration
|
||||
* @see #getCustomValue(TabPlayer)
|
||||
* @see #setFancyValue(TabPlayer, String)
|
||||
*/
|
||||
void setValue(@NonNull TabPlayer player, @Nullable String value);
|
||||
|
||||
/**
|
||||
* Returns score value assigned using {@link #setValue(TabPlayer, String)}.
|
||||
* If no value is assigned, returns {@code null}.
|
||||
*
|
||||
* @param player
|
||||
* Player to get custom score value of
|
||||
* @return Custom score value assigned using the API
|
||||
* @see #setValue(TabPlayer, String)
|
||||
*/
|
||||
@Nullable String getCustomValue(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Changes number format of specified player to provided value. Supports placeholders,
|
||||
* as well as any supported RGB formats. Only 1.20.3+ clients display the number format,
|
||||
* older clients display the score value instead. Use {@code null} to reset value back
|
||||
* to the configured one.
|
||||
*
|
||||
* @param player
|
||||
* Player to change number format of
|
||||
* @param fancyValue
|
||||
* New number format or {@code null} to reset back to configuration
|
||||
* @see #getCustomFancyValue(TabPlayer)
|
||||
* @see #setValue(TabPlayer, String)
|
||||
*/
|
||||
void setFancyValue(@NonNull TabPlayer player, @Nullable String fancyValue);
|
||||
|
||||
/**
|
||||
* Returns number format assigned using {@link #setFancyValue(TabPlayer, String)}.
|
||||
* If no number format is assigned, returns {@code null}.
|
||||
*
|
||||
* @param player
|
||||
* Player to get custom number format of
|
||||
* @return Custom number format assigned using the API
|
||||
* @see #setFancyValue(TabPlayer, String)
|
||||
*/
|
||||
@Nullable String getCustomFancyValue(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Changes objective title of specified player to provided value. Supports placeholders,
|
||||
* as well as any supported RGB formats. The title is only visible on Bedrock Edition.
|
||||
* Use {@code null} to reset value back to the configured one.
|
||||
*
|
||||
* @param player
|
||||
* Player to change objective title of
|
||||
* @param title
|
||||
* New objective title or {@code null} to reset back to configuration
|
||||
* @see #getCustomTitle(TabPlayer)
|
||||
*/
|
||||
void setTitle(@NonNull TabPlayer player, @Nullable String title);
|
||||
|
||||
/**
|
||||
* Returns objective title assigned using {@link #setTitle(TabPlayer, String)}.
|
||||
* If no title is assigned, returns {@code null}.
|
||||
*
|
||||
* @param player
|
||||
* Player to get custom objective title of
|
||||
* @return Custom objective title assigned using the API
|
||||
* @see #setTitle(TabPlayer, String)
|
||||
*/
|
||||
@Nullable String getCustomTitle(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Changes render type of the objective for specified player. Use {@code null} to reset
|
||||
* value back to the configured one.
|
||||
*
|
||||
* @param player
|
||||
* Player to change render type of
|
||||
* @param renderType
|
||||
* New render type or {@code null} to reset back to configuration
|
||||
* @see #getCustomRenderType(TabPlayer)
|
||||
*/
|
||||
void setRenderType(@NonNull TabPlayer player, @Nullable RenderType renderType);
|
||||
|
||||
/**
|
||||
* Returns render type assigned using {@link #setRenderType(TabPlayer, RenderType)}.
|
||||
* If no render type is assigned, returns {@code null}.
|
||||
*
|
||||
* @param player
|
||||
* Player to get custom render type of
|
||||
* @return Custom render type assigned using the API
|
||||
* @see #setRenderType(TabPlayer, RenderType)
|
||||
*/
|
||||
@Nullable RenderType getCustomRenderType(@NonNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Render type of the objective, defining how the score is displayed.
|
||||
*/
|
||||
enum RenderType {
|
||||
|
||||
/** Score is displayed as a number */
|
||||
INTEGER,
|
||||
|
||||
/** Score is displayed as hearts */
|
||||
HEARTS
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -4,7 +4,7 @@ plugins {
|
||||
|
||||
allprojects {
|
||||
group = "me.neznamy"
|
||||
version = "6.1.1"
|
||||
version = "6.1.3"
|
||||
description = "An all-in-one solution that works"
|
||||
|
||||
ext.set("id", "tab")
|
||||
|
||||
@@ -2,10 +2,12 @@ package me.neznamy.tab.platforms.bukkit;
|
||||
|
||||
import me.neznamy.tab.platforms.bukkit.platform.BukkitPlatform;
|
||||
import me.neznamy.tab.platforms.bukkit.platform.FoliaPlatform;
|
||||
import me.neznamy.tab.shared.ProjectVariables;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Main class for Bukkit.
|
||||
@@ -19,15 +21,32 @@ public class BukkitTAB extends JavaPlugin {
|
||||
TAB.create(folia ? new FoliaPlatform(this) : new BukkitPlatform(this));
|
||||
} catch (IllegalStateException e) {
|
||||
Bukkit.getConsoleSender().sendMessage("§c[TAB] ================================================================================");
|
||||
Bukkit.getConsoleSender().sendMessage("§c[TAB] Your server version (" + Bukkit.getBukkitVersion() + ") is not supported.");
|
||||
Bukkit.getConsoleSender().sendMessage("§c[TAB] This jar only supports 1.7.10, 1.8.8, 1.12.2, 1.16.5, 1.17.1 and 1.18.2 - 26.2");
|
||||
Bukkit.getConsoleSender().sendMessage("§c[TAB] If you just updated to a new Minecraft version, check for TAB updates.");
|
||||
Bukkit.getConsoleSender().sendMessage("§c[TAB] If you are using an unsupported 1.x version, use an older version of TAB (latest TAB 5.x supports all MC 1.x versions).");
|
||||
Bukkit.getConsoleSender().sendMessage("§c[TAB] Thrown error message: " + e.getMessage());
|
||||
if (isSpecialJar()) {
|
||||
Bukkit.getConsoleSender().sendMessage("§c[TAB] The " + ProjectVariables.PLUGIN_VERSION + " release consists of 2 jars due to Java version problems.");
|
||||
Bukkit.getConsoleSender().sendMessage("§c[TAB] One jar is for Paper 1.20.5 - 1.21.4, the other jar is for everything else.");
|
||||
Bukkit.getConsoleSender().sendMessage("§c[TAB] You have installed the jar that explicitly says \"Paper 1.20.5 - 1.21.4\", which does not support your server version (" + Bukkit.getBukkitVersion() + ").");
|
||||
Bukkit.getConsoleSender().sendMessage("§c[TAB] Use the other jar in the release instead.");
|
||||
} else {
|
||||
Bukkit.getConsoleSender().sendMessage("§c[TAB] Your server version (" + Bukkit.getBukkitVersion() + ") is not supported.");
|
||||
Bukkit.getConsoleSender().sendMessage("§c[TAB] This jar only supports 1.7.10, 1.8.8, 1.12.2, 1.16.5, 1.17.1 and 1.18.2 - 26.2");
|
||||
Bukkit.getConsoleSender().sendMessage("§c[TAB] If you just updated to a new Minecraft version, check for TAB updates.");
|
||||
Bukkit.getConsoleSender().sendMessage("§c[TAB] If you are using an unsupported 1.x version, use an older version of TAB (latest TAB 5.x supports all MC 1.x versions).");
|
||||
Bukkit.getConsoleSender().sendMessage("§c[TAB] Thrown error message: " + e.getMessage());
|
||||
}
|
||||
Bukkit.getConsoleSender().sendMessage("§c[TAB] ================================================================================");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isSpecialJar() {
|
||||
return !moduleExists("v1_8_R3") &&
|
||||
moduleExists("paper_1_20_5") && moduleExists("paper_1_21_2") && moduleExists("paper_1_21_4") &&
|
||||
!moduleExists("paper_1_21_9");
|
||||
}
|
||||
|
||||
private boolean moduleExists(@NotNull String module) {
|
||||
return ReflectionUtils.classExists("me.neznamy.tab.platforms.bukkit." + module + ".NMSImplementationProvider");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
if (TAB.getInstance() == null) return;
|
||||
|
||||
@@ -25,7 +25,7 @@ public class BukkitTabPlayer extends BackendTabPlayer {
|
||||
static {
|
||||
try {
|
||||
BELOW_NAME_DISTANCE = (Attribute) Attribute.class.getDeclaredField("BELOW_NAME_DISTANCE").get(null);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
} catch (Throwable e) {
|
||||
// 1.21.11 and lower
|
||||
}
|
||||
}
|
||||
|
||||
+5
-5
@@ -4,10 +4,10 @@ import lombok.Getter;
|
||||
import me.clip.placeholderapi.PlaceholderAPI;
|
||||
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
|
||||
import me.clip.placeholderapi.expansion.Relational;
|
||||
import me.neznamy.tab.api.placeholder.Placeholder;
|
||||
import me.neznamy.tab.shared.ProjectVariables;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.features.PlaceholderManagerImpl;
|
||||
import me.neznamy.tab.shared.placeholders.PlaceholderReference;
|
||||
import me.neznamy.tab.shared.placeholders.expansion.TabExpansion;
|
||||
import me.neznamy.tab.shared.placeholders.types.RelationalPlaceholderImpl;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
@@ -98,7 +98,7 @@ public class BukkitTabExpansion extends PlaceholderExpansion implements TabExpan
|
||||
String requestedPlaceholder = "%" + identifier.substring("placeholder_".length()) + "%";
|
||||
PlaceholderManagerImpl pm = TAB.getInstance().getPlaceholderManager();
|
||||
pm.addUsedPlaceholder(requestedPlaceholder, pm);
|
||||
return pm.getPlaceholder(requestedPlaceholder).parse(p);
|
||||
return pm.getPlaceholderReference(requestedPlaceholder).getHandle().parse(p);
|
||||
}
|
||||
return p.expansionData.getValue(identifier);
|
||||
}
|
||||
@@ -121,9 +121,9 @@ public class BukkitTabExpansion extends PlaceholderExpansion implements TabExpan
|
||||
String requestedPlaceholder = "%" + identifier.substring("placeholder_".length()) + "%";
|
||||
PlaceholderManagerImpl pm = TAB.getInstance().getPlaceholderManager();
|
||||
pm.addUsedPlaceholder(requestedPlaceholder, pm);
|
||||
Placeholder placeholder = pm.getPlaceholder(requestedPlaceholder);
|
||||
if (placeholder instanceof RelationalPlaceholderImpl) {
|
||||
RelationalPlaceholderImpl rel = (RelationalPlaceholderImpl) placeholder;
|
||||
PlaceholderReference placeholder = pm.getPlaceholderReference(requestedPlaceholder);
|
||||
if (placeholder.getHandle() instanceof RelationalPlaceholderImpl) {
|
||||
RelationalPlaceholderImpl rel = (RelationalPlaceholderImpl) placeholder.getHandle();
|
||||
TabPlayer v = TAB.getInstance().getPlayer(viewer.getUniqueId());
|
||||
TabPlayer t = TAB.getInstance().getPlayer(target.getUniqueId());
|
||||
if (v == null || t == null) return "<Player is not loaded>";
|
||||
|
||||
@@ -74,7 +74,7 @@ public class FabricTabExpansion implements TabExpansion {
|
||||
String placeholder = "%"+arg+"%";
|
||||
PlaceholderManagerImpl manager = TAB.getInstance().getPlaceholderManager();
|
||||
manager.addUsedPlaceholder(placeholder, manager);
|
||||
return PlaceholderResult.value(manager.getPlaceholder(placeholder).getLastValue(player));
|
||||
return PlaceholderResult.value(manager.getPlaceholderReference(placeholder).getHandle().getLastValue(player));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -5,9 +5,6 @@ import io.fand.api.placeholder.PlaceholderContext;
|
||||
import io.fand.api.placeholder.PlaceholderProvider;
|
||||
import io.fand.api.placeholder.PlaceholderRegistration;
|
||||
import io.fand.api.plugin.PluginContext;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.function.UnaryOperator;
|
||||
import me.neznamy.tab.api.placeholder.Placeholder;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.features.PlaceholderManagerImpl;
|
||||
@@ -17,6 +14,10 @@ import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.function.UnaryOperator;
|
||||
|
||||
/** Exposes TAB's placeholder expansion through Fand's placeholder service. */
|
||||
public final class FandTabExpansion implements TabExpansion {
|
||||
|
||||
@@ -64,7 +65,7 @@ public final class FandTabExpansion implements TabExpansion {
|
||||
String requestedPlaceholder = "%" + expansionIdentifier.substring("placeholder_".length()) + "%";
|
||||
PlaceholderManagerImpl manager = TAB.getInstance().getPlaceholderManager();
|
||||
manager.addUsedPlaceholder(requestedPlaceholder, manager);
|
||||
return manager.getPlaceholder(requestedPlaceholder).parse(player);
|
||||
return manager.getPlaceholderReference(requestedPlaceholder).getHandle().parse(player);
|
||||
}
|
||||
return player.expansionData.getValue(expansionIdentifier);
|
||||
}
|
||||
|
||||
@@ -596,12 +596,8 @@ public class FeatureManager {
|
||||
if (config.getScoreboard() != null) {
|
||||
registerFeature(TabConstants.Feature.SCOREBOARD, new ScoreboardManagerImpl(config.getScoreboard()));
|
||||
}
|
||||
if (config.getPlayerlistObjective() != null) {
|
||||
registerFeature(TabConstants.Feature.YELLOW_NUMBER, new YellowNumber(config.getPlayerlistObjective()));
|
||||
}
|
||||
if (config.getBelowname() != null) {
|
||||
registerFeature(TabConstants.Feature.BELOW_NAME, new BelowName(config.getBelowname()));
|
||||
}
|
||||
registerFeature(TabConstants.Feature.YELLOW_NUMBER, new YellowNumber(config.getPlayerlistObjective()));
|
||||
registerFeature(TabConstants.Feature.BELOW_NAME, new BelowName(config.getBelowname()));
|
||||
if (config.getSorting() != null) {
|
||||
registerFeature(TabConstants.Feature.SORTING, new Sorting(config.getSorting()));
|
||||
}
|
||||
|
||||
@@ -3,7 +3,9 @@ package me.neznamy.tab.shared;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import me.neznamy.tab.api.TabAPI;
|
||||
import me.neznamy.tab.api.belowname.BelowNameManager;
|
||||
import me.neznamy.tab.api.bossbar.BossBarManager;
|
||||
import me.neznamy.tab.api.playerlistobjective.PlayerListObjectiveManager;
|
||||
import me.neznamy.tab.api.scoreboard.ScoreboardManager;
|
||||
import me.neznamy.tab.api.tablist.HeaderFooterManager;
|
||||
import me.neznamy.tab.api.tablist.SortingManager;
|
||||
@@ -315,6 +317,16 @@ public class TAB extends TabAPI {
|
||||
return featureManager.getFeature(TabConstants.Feature.SCOREBOARD);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable BelowNameManager getBelowNameManager() {
|
||||
return featureManager.getFeature(TabConstants.Feature.BELOW_NAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable PlayerListObjectiveManager getPlayerListObjectiveManager() {
|
||||
return featureManager.getFeature(TabConstants.Feature.YELLOW_NUMBER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable NameTag getNameTagManager() {
|
||||
return featureManager.getFeature(TabConstants.Feature.NAME_TAGS);
|
||||
|
||||
@@ -26,17 +26,10 @@ public class BossBarCommand extends SubCommand {
|
||||
|
||||
@Override
|
||||
public void execute(@Nullable TabPlayer sender, @NotNull String[] args) {
|
||||
SubCommand command;
|
||||
if (args.length == 0) {
|
||||
command = getSubcommands().get("toggle");
|
||||
if (command.hasPermission(sender)) {
|
||||
command.execute(sender, new String[0]);
|
||||
} else {
|
||||
sendMessage(sender, getMessages().getNoPermission());
|
||||
}
|
||||
return;
|
||||
args = new String[]{"toggle"};
|
||||
}
|
||||
command = getSubcommands().get(args[0].toLowerCase());
|
||||
SubCommand command = getSubcommands().get(args[0].toLowerCase());
|
||||
if (command != null) {
|
||||
if (command.hasPermission(sender)) {
|
||||
command.execute(sender, Arrays.copyOfRange(args, 1, args.length));
|
||||
|
||||
@@ -8,6 +8,7 @@ import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@@ -31,11 +32,14 @@ public class BossBarOffCommand extends SubCommand {
|
||||
return;
|
||||
}
|
||||
TabPlayer target = sender;
|
||||
if (args.length > 0) {
|
||||
boolean silent = args.length > 0 && args[args.length-1].equals("-s");
|
||||
int providedArgs = args.length - (silent ? 1 : 0);
|
||||
if (providedArgs > 0) {
|
||||
if (hasPermission(sender, TabConstants.Permission.COMMAND_BOSSBAR_TOGGLE_OTHER)) {
|
||||
target = TAB.getInstance().getPlayer(args[0]);
|
||||
String playerName = args[0];
|
||||
target = TAB.getInstance().getPlayer(playerName);
|
||||
if (target == null) {
|
||||
sendMessage(sender, getMessages().getPlayerNotFound(args[0]));
|
||||
sendMessage(sender, getMessages().getPlayerNotFound(playerName));
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
@@ -46,13 +50,17 @@ public class BossBarOffCommand extends SubCommand {
|
||||
sendMessage(null, getMessages().getCommandOnlyFromGame());
|
||||
return;
|
||||
}
|
||||
boolean silent = args.length == 2 && args[1].equals("-s");
|
||||
feature.setBossBarVisible(target, false, !silent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull List<String> complete(@Nullable TabPlayer sender, @NotNull String[] arguments) {
|
||||
if (arguments.length == 1) return getOnlinePlayers(arguments[0]);
|
||||
if (arguments.length == 1) {
|
||||
List<String> suggestions = new ArrayList<>();
|
||||
suggestions.addAll(getOnlinePlayers(arguments[0]));
|
||||
suggestions.addAll(getStartingArgument(Collections.singletonList("-s"), arguments[0]));
|
||||
return suggestions;
|
||||
}
|
||||
if (arguments.length == 2) return getStartingArgument(Collections.singletonList("-s"), arguments[1]);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@@ -31,11 +32,14 @@ public class BossBarOnCommand extends SubCommand {
|
||||
return;
|
||||
}
|
||||
TabPlayer target = sender;
|
||||
if (args.length > 0) {
|
||||
boolean silent = args.length > 0 && args[args.length-1].equals("-s");
|
||||
int providedArgs = args.length - (silent ? 1 : 0);
|
||||
if (providedArgs > 0) {
|
||||
if (hasPermission(sender, TabConstants.Permission.COMMAND_BOSSBAR_TOGGLE_OTHER)) {
|
||||
target = TAB.getInstance().getPlayer(args[0]);
|
||||
String playerName = args[0];
|
||||
target = TAB.getInstance().getPlayer(playerName);
|
||||
if (target == null) {
|
||||
sendMessage(sender, getMessages().getPlayerNotFound(args[0]));
|
||||
sendMessage(sender, getMessages().getPlayerNotFound(playerName));
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
@@ -46,13 +50,17 @@ public class BossBarOnCommand extends SubCommand {
|
||||
sendMessage(null, getMessages().getCommandOnlyFromGame());
|
||||
return;
|
||||
}
|
||||
boolean silent = args.length == 2 && args[1].equals("-s");
|
||||
feature.setBossBarVisible(target, true, !silent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull List<String> complete(@Nullable TabPlayer sender, @NotNull String[] arguments) {
|
||||
if (arguments.length == 1) return getOnlinePlayers(arguments[0]);
|
||||
if (arguments.length == 1) {
|
||||
List<String> suggestions = new ArrayList<>();
|
||||
suggestions.addAll(getOnlinePlayers(arguments[0]));
|
||||
suggestions.addAll(getStartingArgument(Collections.singletonList("-s"), arguments[0]));
|
||||
return suggestions;
|
||||
}
|
||||
if (arguments.length == 2) return getStartingArgument(Collections.singletonList("-s"), arguments[1]);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
+13
-5
@@ -8,6 +8,7 @@ import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@@ -31,11 +32,14 @@ public class BossBarToggleCommand extends SubCommand {
|
||||
return;
|
||||
}
|
||||
TabPlayer target = sender;
|
||||
if (args.length > 0) {
|
||||
boolean silent = args.length > 0 && args[args.length-1].equals("-s");
|
||||
int providedArgs = args.length - (silent ? 1 : 0);
|
||||
if (providedArgs > 0) {
|
||||
if (hasPermission(sender, TabConstants.Permission.COMMAND_BOSSBAR_TOGGLE_OTHER)) {
|
||||
target = TAB.getInstance().getPlayer(args[0]);
|
||||
String playerName = args[0];
|
||||
target = TAB.getInstance().getPlayer(playerName);
|
||||
if (target == null) {
|
||||
sendMessage(sender, getMessages().getPlayerNotFound(args[0]));
|
||||
sendMessage(sender, getMessages().getPlayerNotFound(playerName));
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
@@ -46,13 +50,17 @@ public class BossBarToggleCommand extends SubCommand {
|
||||
sendMessage(null, getMessages().getCommandOnlyFromGame());
|
||||
return;
|
||||
}
|
||||
boolean silent = args.length == 2 && args[1].equals("-s");
|
||||
feature.toggleBossBar(target, !silent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull List<String> complete(@Nullable TabPlayer sender, @NotNull String[] arguments) {
|
||||
if (arguments.length == 1) return getOnlinePlayers(arguments[0]);
|
||||
if (arguments.length == 1) {
|
||||
List<String> suggestions = new ArrayList<>();
|
||||
suggestions.addAll(getOnlinePlayers(arguments[0]));
|
||||
suggestions.addAll(getStartingArgument(Collections.singletonList("-s"), arguments[0]));
|
||||
return suggestions;
|
||||
}
|
||||
if (arguments.length == 2) return getStartingArgument(Collections.singletonList("-s"), arguments[1]);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
+2
-9
@@ -26,17 +26,10 @@ public class ScoreboardCommand extends SubCommand {
|
||||
|
||||
@Override
|
||||
public void execute(@Nullable TabPlayer sender, @NotNull String[] args) {
|
||||
SubCommand command;
|
||||
if (args.length == 0) {
|
||||
command = getSubcommands().get("toggle");
|
||||
if (command.hasPermission(sender)) {
|
||||
command.execute(sender, new String[0]);
|
||||
} else {
|
||||
sendMessage(sender, getMessages().getNoPermission());
|
||||
}
|
||||
return;
|
||||
args = new String[]{"toggle"};
|
||||
}
|
||||
command = getSubcommands().get(args[0].toLowerCase());
|
||||
SubCommand command = getSubcommands().get(args[0].toLowerCase());
|
||||
if (command != null) {
|
||||
if (command.hasPermission(sender)) {
|
||||
command.execute(sender, Arrays.copyOfRange(args, 1, args.length));
|
||||
|
||||
+13
-5
@@ -8,6 +8,7 @@ import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@@ -31,11 +32,14 @@ public class ScoreboardOffCommand extends SubCommand {
|
||||
return;
|
||||
}
|
||||
TabPlayer target = sender;
|
||||
if (args.length > 0) {
|
||||
boolean silent = args.length > 0 && args[args.length-1].equals("-s");
|
||||
int providedArgs = args.length - (silent ? 1 : 0);
|
||||
if (providedArgs > 0) {
|
||||
if (hasPermission(sender, TabConstants.Permission.COMMAND_SCOREBOARD_TOGGLE_OTHER)) {
|
||||
target = TAB.getInstance().getPlayer(args[0]);
|
||||
String playerName = args[0];
|
||||
target = TAB.getInstance().getPlayer(playerName);
|
||||
if (target == null) {
|
||||
sendMessage(sender, getMessages().getPlayerNotFound(args[0]));
|
||||
sendMessage(sender, getMessages().getPlayerNotFound(playerName));
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
@@ -46,13 +50,17 @@ public class ScoreboardOffCommand extends SubCommand {
|
||||
sendMessage(null, getMessages().getCommandOnlyFromGame());
|
||||
return;
|
||||
}
|
||||
boolean silent = args.length == 2 && args[1].equals("-s");
|
||||
scoreboard.setScoreboardVisible(target, false, !silent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull List<String> complete(@Nullable TabPlayer sender, @NotNull String[] arguments) {
|
||||
if (arguments.length == 1) return getOnlinePlayers(arguments[0]);
|
||||
if (arguments.length == 1) {
|
||||
List<String> suggestions = new ArrayList<>();
|
||||
suggestions.addAll(getOnlinePlayers(arguments[0]));
|
||||
suggestions.addAll(getStartingArgument(Collections.singletonList("-s"), arguments[0]));
|
||||
return suggestions;
|
||||
}
|
||||
if (arguments.length == 2) return getStartingArgument(Collections.singletonList("-s"), arguments[1]);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
+13
-5
@@ -8,6 +8,7 @@ import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@@ -31,11 +32,14 @@ public class ScoreboardOnCommand extends SubCommand {
|
||||
return;
|
||||
}
|
||||
TabPlayer target = sender;
|
||||
if (args.length > 0) {
|
||||
boolean silent = args.length > 0 && args[args.length-1].equals("-s");
|
||||
int providedArgs = args.length - (silent ? 1 : 0);
|
||||
if (providedArgs > 0) {
|
||||
if (hasPermission(sender, TabConstants.Permission.COMMAND_SCOREBOARD_TOGGLE_OTHER)) {
|
||||
target = TAB.getInstance().getPlayer(args[0]);
|
||||
String playerName = args[0];
|
||||
target = TAB.getInstance().getPlayer(playerName);
|
||||
if (target == null) {
|
||||
sendMessage(sender, getMessages().getPlayerNotFound(args[0]));
|
||||
sendMessage(sender, getMessages().getPlayerNotFound(playerName));
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
@@ -46,13 +50,17 @@ public class ScoreboardOnCommand extends SubCommand {
|
||||
sendMessage(null, getMessages().getCommandOnlyFromGame());
|
||||
return;
|
||||
}
|
||||
boolean silent = args.length == 2 && args[1].equals("-s");
|
||||
scoreboard.setScoreboardVisible(target, true, !silent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull List<String> complete(@Nullable TabPlayer sender, @NotNull String[] arguments) {
|
||||
if (arguments.length == 1) return getOnlinePlayers(arguments[0]);
|
||||
if (arguments.length == 1) {
|
||||
List<String> suggestions = new ArrayList<>();
|
||||
suggestions.addAll(getOnlinePlayers(arguments[0]));
|
||||
suggestions.addAll(getStartingArgument(Collections.singletonList("-s"), arguments[0]));
|
||||
return suggestions;
|
||||
}
|
||||
if (arguments.length == 2) return getStartingArgument(Collections.singletonList("-s"), arguments[1]);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
+13
-5
@@ -8,6 +8,7 @@ import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@@ -31,11 +32,14 @@ public class ScoreboardToggleCommand extends SubCommand {
|
||||
return;
|
||||
}
|
||||
TabPlayer target = sender;
|
||||
if (args.length > 0) {
|
||||
boolean silent = args.length > 0 && args[args.length-1].equals("-s");
|
||||
int providedArgs = args.length - (silent ? 1 : 0);
|
||||
if (providedArgs > 0) {
|
||||
if (hasPermission(sender, TabConstants.Permission.COMMAND_SCOREBOARD_TOGGLE_OTHER)) {
|
||||
target = TAB.getInstance().getPlayer(args[0]);
|
||||
String playerName = args[0];
|
||||
target = TAB.getInstance().getPlayer(playerName);
|
||||
if (target == null) {
|
||||
sendMessage(sender, getMessages().getPlayerNotFound(args[0]));
|
||||
sendMessage(sender, getMessages().getPlayerNotFound(playerName));
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
@@ -46,13 +50,17 @@ public class ScoreboardToggleCommand extends SubCommand {
|
||||
sendMessage(null, getMessages().getCommandOnlyFromGame());
|
||||
return;
|
||||
}
|
||||
boolean silent = args.length == 2 && args[1].equals("-s");
|
||||
scoreboard.toggleScoreboard(target, !silent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull List<String> complete(@Nullable TabPlayer sender, @NotNull String[] arguments) {
|
||||
if (arguments.length == 1) return getOnlinePlayers(arguments[0]);
|
||||
if (arguments.length == 1) {
|
||||
List<String> suggestions = new ArrayList<>();
|
||||
suggestions.addAll(getOnlinePlayers(arguments[0]));
|
||||
suggestions.addAll(getStartingArgument(Collections.singletonList("-s"), arguments[0]));
|
||||
return suggestions;
|
||||
}
|
||||
if (arguments.length == 2) return getStartingArgument(Collections.singletonList("-s"), arguments[1]);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@ public class ModernConverter {
|
||||
defaultDesign.put("display-condition", "%condition:" + namedCondition.getName() + "%=" + namedCondition.getNo());
|
||||
} else {
|
||||
// Short format
|
||||
defaultDesign.put("display-condition", new Condition(disableCondition).invert().toShortFormat());
|
||||
defaultDesign.put("display-condition", Condition.fromShortFormat(disableCondition).invert().toShortFormat());
|
||||
}
|
||||
}
|
||||
defaultDesign.put("header", defaultHeader);
|
||||
|
||||
@@ -40,7 +40,7 @@ public class Config {
|
||||
@NotNull private final ConfigurationFile config = new YamlConfigurationFile(getClass().getClassLoader().getResourceAsStream("config/config.yml"),
|
||||
new File(TAB.getInstance().getDataFolder(), "config.yml"));
|
||||
|
||||
@Nullable private BelowNameConfiguration belowname;
|
||||
@NotNull private final BelowNameConfiguration belowname;
|
||||
@Nullable private BossBarConfiguration bossbar;
|
||||
@NotNull private final ConditionsSection conditions;
|
||||
@Nullable private GlobalPlayerListConfiguration globalPlayerList;
|
||||
@@ -52,7 +52,7 @@ public class Config {
|
||||
@NotNull private final PlaceholderRefreshConfiguration refresh;
|
||||
@NotNull private final PlaceholderReplacementsConfiguration replacements;
|
||||
@NotNull private final PlaceholdersConfiguration placeholders;
|
||||
@Nullable private PlayerListObjectiveConfiguration playerlistObjective;
|
||||
@NotNull private final PlayerListObjectiveConfiguration playerlistObjective;
|
||||
@Nullable private ScoreboardConfiguration scoreboard;
|
||||
@Nullable private SortingConfiguration sorting;
|
||||
@Nullable private TablistFormattingConfiguration tablistFormatting;
|
||||
@@ -95,8 +95,9 @@ public class Config {
|
||||
replacements = PlaceholderReplacementsConfiguration.fromSection(config.getConfigurationSection("placeholder-output-replacements"));
|
||||
placeholders = PlaceholdersConfiguration.fromSection(config.getConfigurationSection("placeholders"));
|
||||
components = ComponentConfiguration.fromSection(config.getConfigurationSection("components"));
|
||||
belowname = BelowNameConfiguration.fromSection(config.getConfigurationSection("belowname-objective"));
|
||||
playerlistObjective = PlayerListObjectiveConfiguration.fromSection(config.getConfigurationSection("playerlist-objective"));
|
||||
|
||||
if (config.getBoolean("belowname-objective.enabled", false)) belowname = BelowNameConfiguration.fromSection(config.getConfigurationSection("belowname-objective"));
|
||||
if (config.getBoolean("bossbar.enabled", false)) bossbar = BossBarConfiguration.fromSection(config.getConfigurationSection("bossbar"));
|
||||
if (config.getBoolean("global-playerlist.enabled", false)) globalPlayerList = GlobalPlayerListConfiguration.fromSection(config.getConfigurationSection("global-playerlist"));
|
||||
if (config.getBoolean("header-footer.enabled", true)) headerFooter = HeaderFooterConfiguration.fromSection(config.getConfigurationSection("header-footer"));
|
||||
@@ -104,7 +105,6 @@ public class Config {
|
||||
if (config.getBoolean("mysql.enabled", false)) mysql = MySQLConfiguration.fromSection(config.getConfigurationSection("mysql"));
|
||||
if (config.getBoolean("per-world-playerlist.enabled", false)) perWorldPlayerList = PerWorldPlayerListConfiguration.fromSection(config.getConfigurationSection("per-world-playerlist"));
|
||||
if (config.getBoolean("ping-spoof.enabled", false)) pingSpoof = PingSpoofConfiguration.fromSection(config.getConfigurationSection("ping-spoof"));
|
||||
if (config.getBoolean("playerlist-objective.enabled", true)) playerlistObjective = PlayerListObjectiveConfiguration.fromSection(config.getConfigurationSection("playerlist-objective"));
|
||||
if (config.getBoolean("scoreboard.enabled", false)) scoreboard = ScoreboardConfiguration.fromSection(config.getConfigurationSection("scoreboard"));
|
||||
if (config.getBoolean("scoreboard-teams.enabled", true) || config.getBoolean("layout.enabled", false)) sorting = SortingConfiguration.fromSection(config.getConfigurationSection("scoreboard-teams"));
|
||||
if (config.getBoolean("tablist-name-formatting.enabled", false)) tablistFormatting = TablistFormattingConfiguration.fromSection(config.getConfigurationSection("tablist-name-formatting"));
|
||||
@@ -118,7 +118,7 @@ public class Config {
|
||||
" fake players, making per world playerlist completely useless as real players are pushed out of the playerlist. " +
|
||||
"Disable per world playerlist for the same result, but with better performance.");
|
||||
}
|
||||
if (playerlistObjective != null) {
|
||||
if (playerlistObjective.isEnabled()) {
|
||||
TAB.getInstance().getConfigHelper().startup().startupWarn(config.getFile(), "Layout feature breaks playerlist-objective feature, because it replaces real player with fake slots " +
|
||||
"with different usernames for more reliable functionality. Disable playerlist-objective feature, as it will only look bad " +
|
||||
"and consume resources.");
|
||||
|
||||
@@ -152,7 +152,7 @@ public class PlaceholderManagerImpl extends RefreshableFeature implements Placeh
|
||||
if (!target.isOnline()) continue; // Player disconnected in the meantime while refreshing in another thread
|
||||
if (placeholder.hasValueChanged(viewer, target, targetResult.getValue())) {
|
||||
placeholder.updateParents(target);
|
||||
for (RefreshableFeature f : placeholder.getUsedByFeatures()) {
|
||||
for (RefreshableFeature f : placeholder.getReference().getUsedByFeatures()) {
|
||||
update.computeIfAbsent(f, c -> new HashSet<>()).add(target);
|
||||
}
|
||||
}
|
||||
@@ -172,7 +172,7 @@ public class PlaceholderManagerImpl extends RefreshableFeature implements Placeh
|
||||
if (!player.isOnline()) continue; // Player disconnected in the meantime while refreshing in another thread
|
||||
if (placeholder.hasValueChanged(player, playerResult.getValue(), true)) {
|
||||
placeholder.updateParents(player);
|
||||
for (RefreshableFeature f : placeholder.getUsedByFeatures()) {
|
||||
for (RefreshableFeature f : placeholder.getReference().getUsedByFeatures()) {
|
||||
update.computeIfAbsent(f, c -> new HashSet<>()).add(player);
|
||||
}
|
||||
if (placeholder.getIdentifier().equals(TabConstants.Placeholder.VANISHED)) {
|
||||
@@ -192,7 +192,7 @@ public class PlaceholderManagerImpl extends RefreshableFeature implements Placeh
|
||||
for (Entry<ServerPlaceholderImpl, String> entry : results.entrySet()) {
|
||||
ServerPlaceholderImpl placeholder = entry.getKey();
|
||||
if (placeholder.hasValueChanged(entry.getValue())) {
|
||||
set.addAll(placeholder.getUsedByFeatures());
|
||||
set.addAll(placeholder.getReference().getUsedByFeatures());
|
||||
for (TabPlayer all : TAB.getInstance().getOnlinePlayers()) {
|
||||
placeholder.updateParents(all);
|
||||
}
|
||||
@@ -226,7 +226,7 @@ public class PlaceholderManagerImpl extends RefreshableFeature implements Placeh
|
||||
existing.setHandle((TabPlaceholder) placeholder);
|
||||
for (TabPlayer p : TAB.getInstance().getOnlinePlayers()) {
|
||||
if (!p.isLoaded()) continue;
|
||||
for (RefreshableFeature f : ((TabPlaceholder)placeholder).getUsedByFeatures()) {
|
||||
for (RefreshableFeature f : existing.getUsedByFeatures()) {
|
||||
TimedCaughtTask task = new TimedCaughtTask(cpu, () -> f.refresh(p, true), f.getFeatureName(), f.getRefreshDisplayName());
|
||||
if (f instanceof CustomThreaded) {
|
||||
((CustomThreaded) f).getCustomThread().execute(task);
|
||||
@@ -235,10 +235,12 @@ public class PlaceholderManagerImpl extends RefreshableFeature implements Placeh
|
||||
}
|
||||
}
|
||||
}
|
||||
((TabPlaceholder) placeholder).setReference(existing);
|
||||
return existing;
|
||||
} else {
|
||||
PlaceholderReference reference = new PlaceholderReference(placeholder.getIdentifier(), (TabPlaceholder) placeholder);
|
||||
registeredPlaceholders.put(placeholder.getIdentifier(), reference);
|
||||
((TabPlaceholder) placeholder).setReference(reference);
|
||||
return reference;
|
||||
}
|
||||
}
|
||||
@@ -337,7 +339,7 @@ public class PlaceholderManagerImpl extends RefreshableFeature implements Placeh
|
||||
* Feature using the placeholder
|
||||
*/
|
||||
public synchronized void addUsedPlaceholder(@NonNull String identifier, @NonNull RefreshableFeature feature) {
|
||||
if (getPlaceholder(identifier).addUsedFeature(feature)) {
|
||||
if (getPlaceholder(identifier).getReference().addUsedFeature(feature)) {
|
||||
recalculateUsedPlaceholders();
|
||||
TabPlaceholder p = getPlaceholder(identifier);
|
||||
for (TabPlayer all : TAB.getInstance().getOnlinePlayers()) {
|
||||
@@ -355,7 +357,7 @@ public class PlaceholderManagerImpl extends RefreshableFeature implements Placeh
|
||||
* Feature using the placeholder
|
||||
*/
|
||||
public synchronized void addUsedPlaceholder(@NonNull TabPlaceholder placeholder, @NonNull RefreshableFeature feature) {
|
||||
if (placeholder.addUsedFeature(feature)) {
|
||||
if (placeholder.getReference().addUsedFeature(feature)) {
|
||||
recalculateUsedPlaceholders();
|
||||
for (TabPlayer all : TAB.getInstance().getOnlinePlayers()) {
|
||||
all.expansionData.setPlaceholderValue(placeholder.getIdentifier(), placeholder.getLastValueSafe(all));
|
||||
@@ -367,7 +369,7 @@ public class PlaceholderManagerImpl extends RefreshableFeature implements Placeh
|
||||
* Updates array of used placeholders.
|
||||
*/
|
||||
private void recalculateUsedPlaceholders() {
|
||||
usedPlaceholders = registeredPlaceholders.values().stream().filter(p -> !p.getHandle().getUsedByFeatures().isEmpty()).toArray(PlaceholderReference[]::new);
|
||||
usedPlaceholders = registeredPlaceholders.values().stream().filter(p -> !p.getUsedByFeatures().isEmpty()).toArray(PlaceholderReference[]::new);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -389,7 +391,7 @@ public class PlaceholderManagerImpl extends RefreshableFeature implements Placeh
|
||||
public void onJoin(@NotNull TabPlayer connectedPlayer) {
|
||||
for (PlaceholderReference p : usedPlaceholders) {
|
||||
if (p.getHandle() instanceof ServerPlaceholderImpl) { // server placeholders don't update on join
|
||||
connectedPlayer.expansionData.setPlaceholderValue(p.getIdentifier(), ((ServerPlaceholderImpl) p.getHandle()).getLastValue());
|
||||
connectedPlayer.expansionData.setPlaceholderValue(p.getIdentifier(), p.getHandle().getLastValue(null));
|
||||
}
|
||||
}
|
||||
// Initialize to avoid onVanishStatusChange being called in the loop after joining because previous value was null
|
||||
@@ -584,7 +586,7 @@ public class PlaceholderManagerImpl extends RefreshableFeature implements Placeh
|
||||
TabPlaceholder p = reference.getHandle();
|
||||
if (p.getIdentifier().contains("AnonymousCondition")) continue; // These are ugly, don't show them
|
||||
if (p instanceof ServerPlaceholderImpl) {
|
||||
serverPlaceholders.add(Arrays.asList(p.getIdentifier(), String.valueOf(reference.getRefresh()), ((ServerPlaceholderImpl) p).getLastValue()));
|
||||
serverPlaceholders.add(Arrays.asList(p.getIdentifier(), String.valueOf(reference.getRefresh()), p.getLastValue(null)));
|
||||
} else if (p instanceof PlayerPlaceholderImpl) {
|
||||
playerPlaceholders.add(Arrays.asList(p.getIdentifier(), String.valueOf(reference.getRefresh()), p.getLastValueSafe(player)));
|
||||
} else if (p instanceof RelationalPlaceholderImpl) {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package me.neznamy.tab.shared.features.belowname;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.api.belowname.BelowNameManager;
|
||||
import me.neznamy.tab.shared.Property;
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
@@ -28,7 +30,7 @@ import java.util.stream.Collectors;
|
||||
*/
|
||||
@Getter
|
||||
public class BelowName extends RefreshableFeature implements JoinListener, QuitListener, Loadable, UnLoadable,
|
||||
WorldSwitchListener, ServerSwitchListener, CustomThreaded, ProxyFeature, VanishListener, Dumpable {
|
||||
WorldSwitchListener, ServerSwitchListener, CustomThreaded, ProxyFeature, VanishListener, Dumpable, BelowNameManager {
|
||||
|
||||
/** Objective name used by this feature */
|
||||
public static final String OBJECTIVE_NAME = "TAB-BelowName";
|
||||
@@ -70,7 +72,7 @@ public class BelowName extends RefreshableFeature implements JoinListener, QuitL
|
||||
for (TabPlayer loaded : onlinePlayers.getPlayers()) {
|
||||
loaded.setBelowNameDistance(configuration.getViewDistance());
|
||||
loadProperties(loaded);
|
||||
if (disableChecker.isDisableConditionMet(loaded)) {
|
||||
if (configuredDisabled(loaded)) {
|
||||
loaded.belowNameData.disabled.set(true);
|
||||
} else {
|
||||
register(loaded);
|
||||
@@ -104,7 +106,7 @@ public class BelowName extends RefreshableFeature implements JoinListener, QuitL
|
||||
onlinePlayers.addPlayer(connectedPlayer);
|
||||
connectedPlayer.setBelowNameDistance(configuration.getViewDistance());
|
||||
loadProperties(connectedPlayer);
|
||||
if (disableChecker.isDisableConditionMet(connectedPlayer)) {
|
||||
if (configuredDisabled(connectedPlayer)) {
|
||||
connectedPlayer.belowNameData.disabled.set(true);
|
||||
} else {
|
||||
register(connectedPlayer);
|
||||
@@ -143,6 +145,28 @@ public class BelowName extends RefreshableFeature implements JoinListener, QuitL
|
||||
* Whether the feature is disabled now or not
|
||||
*/
|
||||
public void onDisableConditionChange(TabPlayer p, boolean disabledNow) {
|
||||
Boolean forced = p.belowNameData.forcedEnabled;
|
||||
if (forced != null) {
|
||||
p.belowNameData.disabled.set(!forced);
|
||||
return;
|
||||
}
|
||||
if (!configuration.isEnabled()) {
|
||||
p.belowNameData.disabled.set(true);
|
||||
return;
|
||||
}
|
||||
updateVisibility(p, disabledNow);
|
||||
}
|
||||
|
||||
private boolean configuredDisabled(@NotNull TabPlayer p) {
|
||||
return !configuration.isEnabled() || disableChecker.isDisableConditionMet(p);
|
||||
}
|
||||
|
||||
private void applyDisabledState(@NotNull TabPlayer p, boolean disabled) {
|
||||
if (p.belowNameData.disabled.getAndSet(disabled) == disabled) return;
|
||||
updateVisibility(p, disabled);
|
||||
}
|
||||
|
||||
private void updateVisibility(@NotNull TabPlayer p, boolean disabledNow) {
|
||||
if (disabledNow) {
|
||||
p.getScoreboard().unregisterObjective(OBJECTIVE_NAME);
|
||||
} else {
|
||||
@@ -220,6 +244,15 @@ public class BelowName extends RefreshableFeature implements JoinListener, QuitL
|
||||
player.getScoreboard().setDisplaySlot(OBJECTIVE_NAME, Scoreboard.DisplaySlot.BELOW_NAME);
|
||||
}
|
||||
|
||||
private void updateObjective(@NotNull TabPlayer player) {
|
||||
player.getScoreboard().updateObjective(
|
||||
OBJECTIVE_NAME,
|
||||
cache.get(player.belowNameData.title.updateAndGet()),
|
||||
Scoreboard.HealthDisplay.INTEGER,
|
||||
cache.get(player.belowNameData.defaultNumberFormat.updateAndGet())
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates score of specified entry to player.
|
||||
*
|
||||
@@ -302,6 +335,85 @@ public class BelowName extends RefreshableFeature implements JoinListener, QuitL
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------
|
||||
// API Implementation
|
||||
// ------------------
|
||||
|
||||
@Override
|
||||
public void setEnabled(@NonNull me.neznamy.tab.api.TabPlayer player, @Nullable Boolean enabled) {
|
||||
ensureActive();
|
||||
customThread.execute(new TimedCaughtTask(TAB.getInstance().getCpu(), () -> {
|
||||
TabPlayer p = (TabPlayer) player;
|
||||
p.ensureLoaded();
|
||||
p.belowNameData.forcedEnabled = enabled;
|
||||
applyDisabledState(p, enabled == null ? configuredDisabled(p) : !enabled);
|
||||
}, getFeatureName(), "Processing API call (setEnabled)"));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Boolean getCustomEnabled(@NonNull me.neznamy.tab.api.TabPlayer player) {
|
||||
ensureActive();
|
||||
return ((TabPlayer) player).belowNameData.forcedEnabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(@NonNull me.neznamy.tab.api.TabPlayer player, @Nullable String value) {
|
||||
ensureActive();
|
||||
customThread.execute(new TimedCaughtTask(TAB.getInstance().getCpu(), () -> {
|
||||
TabPlayer p = (TabPlayer) player;
|
||||
p.ensureLoaded();
|
||||
if (Objects.equals(p.belowNameData.value.getTemporaryValue(), value)) return;
|
||||
p.belowNameData.value.setTemporaryValue(value);
|
||||
refresh(p, true);
|
||||
}, getFeatureName(), "Processing API call (setValue)"));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public String getCustomValue(@NonNull me.neznamy.tab.api.TabPlayer player) {
|
||||
ensureActive();
|
||||
return ((TabPlayer) player).belowNameData.value.getTemporaryValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFancyValue(@NonNull me.neznamy.tab.api.TabPlayer player, @Nullable String fancyValue) {
|
||||
ensureActive();
|
||||
customThread.execute(new TimedCaughtTask(TAB.getInstance().getCpu(), () -> {
|
||||
TabPlayer p = (TabPlayer) player;
|
||||
p.ensureLoaded();
|
||||
if (Objects.equals(p.belowNameData.fancyValue.getTemporaryValue(), fancyValue)) return;
|
||||
p.belowNameData.fancyValue.setTemporaryValue(fancyValue);
|
||||
refresh(p, true);
|
||||
}, getFeatureName(), "Processing API call (setFancyValue)"));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public String getCustomFancyValue(@NonNull me.neznamy.tab.api.TabPlayer player) {
|
||||
ensureActive();
|
||||
return ((TabPlayer) player).belowNameData.fancyValue.getTemporaryValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTitle(@NonNull me.neznamy.tab.api.TabPlayer player, @Nullable String title) {
|
||||
ensureActive();
|
||||
customThread.execute(new TimedCaughtTask(TAB.getInstance().getCpu(), () -> {
|
||||
TabPlayer p = (TabPlayer) player;
|
||||
p.ensureLoaded();
|
||||
if (Objects.equals(p.belowNameData.title.getTemporaryValue(), title)) return;
|
||||
p.belowNameData.title.setTemporaryValue(title);
|
||||
if (!p.belowNameData.disabled.get()) updateObjective(p);
|
||||
}, getFeatureName(), "Processing API call (setTitle)"));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public String getCustomTitle(@NonNull me.neznamy.tab.api.TabPlayer player) {
|
||||
ensureActive();
|
||||
return ((TabPlayer) player).belowNameData.title.getTemporaryValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Object dump(@NotNull TabPlayer analyzed) {
|
||||
|
||||
+2
@@ -17,6 +17,7 @@ import java.util.Arrays;
|
||||
public class BelowNameConfiguration {
|
||||
|
||||
@NotNull private final ConfigurationSection section;
|
||||
private final boolean enabled;
|
||||
@NotNull private final String value;
|
||||
@NotNull private final String title;
|
||||
@NotNull private final String fancyValue;
|
||||
@@ -70,6 +71,7 @@ public class BelowNameConfiguration {
|
||||
|
||||
return new BelowNameConfiguration(
|
||||
section,
|
||||
section.getBoolean("enabled", false),
|
||||
value,
|
||||
title,
|
||||
section.getString("fancy-value", "&c" + TabConstants.Placeholder.HEALTH),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package me.neznamy.tab.shared.features.belowname;
|
||||
|
||||
import me.neznamy.tab.shared.Property;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
@@ -23,4 +24,7 @@ public class BelowNamePlayerData {
|
||||
|
||||
/** Flag tracking whether this feature is disabled for the player with condition or not */
|
||||
public final AtomicBoolean disabled = new AtomicBoolean();
|
||||
|
||||
/** Visibility forced using the API, {@code null} if not forced */
|
||||
@Nullable public volatile Boolean forcedEnabled;
|
||||
}
|
||||
@@ -50,7 +50,7 @@ public class LayoutManagerImpl extends RefreshableFeature implements LayoutManag
|
||||
this.configuration = configuration;
|
||||
skinManager = new LayoutSkinManager(TAB.getInstance().getConfiguration().getSkinManager(), configuration.getDefaultSkin(), configuration.getDefaultSkinHashMap());
|
||||
for (int slot=1; slot<=80; slot++) {
|
||||
UUID id = new UUID(1, configuration.getDirection().translateSlot(slot));
|
||||
UUID id = new UUID(0, configuration.getDirection().translateSlot(slot));
|
||||
uuids[slot-1] = id;
|
||||
UUIDS_SET.add(id);
|
||||
}
|
||||
|
||||
+2
@@ -18,6 +18,7 @@ import java.util.Arrays;
|
||||
public class PlayerListObjectiveConfiguration {
|
||||
|
||||
@NotNull private final ConfigurationSection section;
|
||||
private final boolean enabled;
|
||||
@NotNull private final String value;
|
||||
@NotNull private final String fancyValue;
|
||||
@NotNull private final String title;
|
||||
@@ -71,6 +72,7 @@ public class PlayerListObjectiveConfiguration {
|
||||
|
||||
return new PlayerListObjectiveConfiguration(
|
||||
section,
|
||||
section.getBoolean("enabled", true),
|
||||
value,
|
||||
section.getString("fancy-value", "&7Ping: " + Placeholder.PING),
|
||||
section.getString("title", "TAB"),
|
||||
|
||||
+8
@@ -1,6 +1,8 @@
|
||||
package me.neznamy.tab.shared.features.playerlistobjective;
|
||||
|
||||
import me.neznamy.tab.api.playerlistobjective.PlayerListObjectiveManager.RenderType;
|
||||
import me.neznamy.tab.shared.Property;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
@@ -20,4 +22,10 @@ public class PlayerListObjectivePlayerData {
|
||||
|
||||
/** Flag tracking whether this feature is disabled for the player with condition or not */
|
||||
public final AtomicBoolean disabled = new AtomicBoolean();
|
||||
|
||||
/** Visibility forced using the API, {@code null} if not forced */
|
||||
@Nullable public volatile Boolean forcedEnabled;
|
||||
|
||||
/** Render type forced using the API, {@code null} if not forced */
|
||||
@Nullable public volatile RenderType forcedRenderType;
|
||||
}
|
||||
+141
-4
@@ -1,6 +1,8 @@
|
||||
package me.neznamy.tab.shared.features.playerlistobjective;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.api.playerlistobjective.PlayerListObjectiveManager;
|
||||
import me.neznamy.tab.shared.Property;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
@@ -26,7 +28,7 @@ import java.util.stream.Collectors;
|
||||
*/
|
||||
@Getter
|
||||
public class YellowNumber extends RefreshableFeature implements JoinListener, QuitListener, Loadable,
|
||||
CustomThreaded, ProxyFeature, VanishListener, Dumpable {
|
||||
CustomThreaded, ProxyFeature, VanishListener, Dumpable, PlayerListObjectiveManager {
|
||||
|
||||
/** Objective name used by this feature */
|
||||
public static final String OBJECTIVE_NAME = "TAB-PlayerList";
|
||||
@@ -67,7 +69,7 @@ public class YellowNumber extends RefreshableFeature implements JoinListener, Qu
|
||||
Map<TabPlayer, Integer> values = new HashMap<>();
|
||||
for (TabPlayer loaded : onlinePlayers.getPlayers()) {
|
||||
loadProperties(loaded);
|
||||
if (disableChecker.isDisableConditionMet(loaded)) {
|
||||
if (configuredDisabled(loaded)) {
|
||||
loaded.playerlistObjectiveData.disabled.set(true);
|
||||
} else {
|
||||
register(loaded);
|
||||
@@ -92,7 +94,7 @@ public class YellowNumber extends RefreshableFeature implements JoinListener, Qu
|
||||
public void onJoin(@NotNull TabPlayer connectedPlayer) {
|
||||
onlinePlayers.addPlayer(connectedPlayer);
|
||||
loadProperties(connectedPlayer);
|
||||
if (disableChecker.isDisableConditionMet(connectedPlayer)) {
|
||||
if (configuredDisabled(connectedPlayer)) {
|
||||
connectedPlayer.playerlistObjectiveData.disabled.set(true);
|
||||
} else {
|
||||
register(connectedPlayer);
|
||||
@@ -131,6 +133,28 @@ public class YellowNumber extends RefreshableFeature implements JoinListener, Qu
|
||||
* Whether the feature is disabled now or not
|
||||
*/
|
||||
public void onDisableConditionChange(TabPlayer p, boolean disabledNow) {
|
||||
Boolean forced = p.playerlistObjectiveData.forcedEnabled;
|
||||
if (forced != null) {
|
||||
p.playerlistObjectiveData.disabled.set(!forced);
|
||||
return;
|
||||
}
|
||||
if (!configuration.isEnabled()) {
|
||||
p.playerlistObjectiveData.disabled.set(true);
|
||||
return;
|
||||
}
|
||||
updateVisibility(p, disabledNow);
|
||||
}
|
||||
|
||||
private boolean configuredDisabled(@NotNull TabPlayer p) {
|
||||
return !configuration.isEnabled() || disableChecker.isDisableConditionMet(p);
|
||||
}
|
||||
|
||||
private void applyDisabledState(@NotNull TabPlayer p, boolean disabled) {
|
||||
if (p.playerlistObjectiveData.disabled.getAndSet(disabled) == disabled) return;
|
||||
updateVisibility(p, disabled);
|
||||
}
|
||||
|
||||
private void updateVisibility(@NotNull TabPlayer p, boolean disabledNow) {
|
||||
if (disabledNow) {
|
||||
p.getScoreboard().unregisterObjective(OBJECTIVE_NAME);
|
||||
} else {
|
||||
@@ -202,12 +226,27 @@ public class YellowNumber extends RefreshableFeature implements JoinListener, Qu
|
||||
player.getScoreboard().registerObjective(
|
||||
OBJECTIVE_NAME,
|
||||
cache.get(player.playerlistObjectiveData.title.updateAndGet()),
|
||||
configuration.getHealthDisplay(),
|
||||
getHealthDisplay(player),
|
||||
TabComponent.empty()
|
||||
);
|
||||
player.getScoreboard().setDisplaySlot(OBJECTIVE_NAME, Scoreboard.DisplaySlot.PLAYER_LIST);
|
||||
}
|
||||
|
||||
private void updateObjective(@NotNull TabPlayer player) {
|
||||
player.getScoreboard().updateObjective(
|
||||
OBJECTIVE_NAME,
|
||||
cache.get(player.playerlistObjectiveData.title.updateAndGet()),
|
||||
getHealthDisplay(player),
|
||||
TabComponent.empty()
|
||||
);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private Scoreboard.HealthDisplay getHealthDisplay(@NotNull TabPlayer player) {
|
||||
RenderType forced = player.playerlistObjectiveData.forcedRenderType;
|
||||
return forced == null ? configuration.getHealthDisplay() : Scoreboard.HealthDisplay.valueOf(forced.name());
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates score of specified entry to player.
|
||||
*
|
||||
@@ -267,6 +306,104 @@ public class YellowNumber extends RefreshableFeature implements JoinListener, Qu
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------
|
||||
// API Implementation
|
||||
// ------------------
|
||||
|
||||
@Override
|
||||
public void setEnabled(@NonNull me.neznamy.tab.api.TabPlayer player, @Nullable Boolean enabled) {
|
||||
ensureActive();
|
||||
customThread.execute(new TimedCaughtTask(TAB.getInstance().getCpu(), () -> {
|
||||
TabPlayer p = (TabPlayer) player;
|
||||
p.ensureLoaded();
|
||||
p.playerlistObjectiveData.forcedEnabled = enabled;
|
||||
applyDisabledState(p, enabled == null ? configuredDisabled(p) : !enabled);
|
||||
}, getFeatureName(), "Processing API call (setEnabled)"));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Boolean getCustomEnabled(@NonNull me.neznamy.tab.api.TabPlayer player) {
|
||||
ensureActive();
|
||||
return ((TabPlayer) player).playerlistObjectiveData.forcedEnabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(@NonNull me.neznamy.tab.api.TabPlayer player, @Nullable String value) {
|
||||
ensureActive();
|
||||
customThread.execute(new TimedCaughtTask(TAB.getInstance().getCpu(), () -> {
|
||||
TabPlayer p = (TabPlayer) player;
|
||||
p.ensureLoaded();
|
||||
if (Objects.equals(p.playerlistObjectiveData.value.getTemporaryValue(), value)) return;
|
||||
p.playerlistObjectiveData.value.setTemporaryValue(value);
|
||||
refresh(p, true);
|
||||
}, getFeatureName(), "Processing API call (setValue)"));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public String getCustomValue(@NonNull me.neznamy.tab.api.TabPlayer player) {
|
||||
ensureActive();
|
||||
return ((TabPlayer) player).playerlistObjectiveData.value.getTemporaryValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setFancyValue(@NonNull me.neznamy.tab.api.TabPlayer player, @Nullable String fancyValue) {
|
||||
ensureActive();
|
||||
customThread.execute(new TimedCaughtTask(TAB.getInstance().getCpu(), () -> {
|
||||
TabPlayer p = (TabPlayer) player;
|
||||
p.ensureLoaded();
|
||||
if (Objects.equals(p.playerlistObjectiveData.fancyValue.getTemporaryValue(), fancyValue)) return;
|
||||
p.playerlistObjectiveData.fancyValue.setTemporaryValue(fancyValue);
|
||||
refresh(p, true);
|
||||
}, getFeatureName(), "Processing API call (setFancyValue)"));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public String getCustomFancyValue(@NonNull me.neznamy.tab.api.TabPlayer player) {
|
||||
ensureActive();
|
||||
return ((TabPlayer) player).playerlistObjectiveData.fancyValue.getTemporaryValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTitle(@NonNull me.neznamy.tab.api.TabPlayer player, @Nullable String title) {
|
||||
ensureActive();
|
||||
customThread.execute(new TimedCaughtTask(TAB.getInstance().getCpu(), () -> {
|
||||
TabPlayer p = (TabPlayer) player;
|
||||
p.ensureLoaded();
|
||||
if (Objects.equals(p.playerlistObjectiveData.title.getTemporaryValue(), title)) return;
|
||||
p.playerlistObjectiveData.title.setTemporaryValue(title);
|
||||
if (!p.playerlistObjectiveData.disabled.get()) updateObjective(p);
|
||||
}, getFeatureName(), "Processing API call (setTitle)"));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public String getCustomTitle(@NonNull me.neznamy.tab.api.TabPlayer player) {
|
||||
ensureActive();
|
||||
return ((TabPlayer) player).playerlistObjectiveData.title.getTemporaryValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRenderType(@NonNull me.neznamy.tab.api.TabPlayer player, @Nullable RenderType renderType) {
|
||||
ensureActive();
|
||||
customThread.execute(new TimedCaughtTask(TAB.getInstance().getCpu(), () -> {
|
||||
TabPlayer p = (TabPlayer) player;
|
||||
p.ensureLoaded();
|
||||
if (p.playerlistObjectiveData.forcedRenderType == renderType) return;
|
||||
p.playerlistObjectiveData.forcedRenderType = renderType;
|
||||
if (!p.playerlistObjectiveData.disabled.get()) updateObjective(p);
|
||||
}, getFeatureName(), "Processing API call (setRenderType)"));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public RenderType getCustomRenderType(@NonNull me.neznamy.tab.api.TabPlayer player) {
|
||||
ensureActive();
|
||||
return ((TabPlayer) player).playerlistObjectiveData.forcedRenderType;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Object dump(@NotNull TabPlayer analyzed) {
|
||||
|
||||
@@ -1,123 +0,0 @@
|
||||
package me.neznamy.tab.shared.features.scoreboard;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.NonNull;
|
||||
import lombok.Setter;
|
||||
import me.neznamy.tab.shared.Property;
|
||||
import me.neznamy.tab.shared.chat.component.TabComponent;
|
||||
import me.neznamy.tab.shared.cpu.ThreadExecutor;
|
||||
import me.neznamy.tab.shared.features.scoreboard.lines.ScoreboardLine;
|
||||
import me.neznamy.tab.shared.features.types.CustomThreaded;
|
||||
import me.neznamy.tab.shared.features.types.RefreshableFeature;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import me.neznamy.tab.shared.util.cache.StringToComponentCache;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Feature refreshing score / NumberFormat in a scoreboard line for players.
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
public class ScoreRefresher extends RefreshableFeature implements CustomThreaded {
|
||||
|
||||
private static final StringToComponentCache cache = new StringToComponentCache("Scoreboard NumberFormat", 1000);
|
||||
|
||||
/** Line this score belongs to */
|
||||
@NonNull private final ScoreboardLine line;
|
||||
|
||||
/** Line number to use if nothing is configured (1-15) */
|
||||
@Setter private int lineNumber;
|
||||
|
||||
/** Configured score for <1.20.3 (can be null if not configured) */
|
||||
@Nullable private final String score;
|
||||
|
||||
/** Configured number format (can be null if not configured) */
|
||||
@Nullable private final String numberFormat;
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getRefreshDisplayName() {
|
||||
return "Updating NumberFormat";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refresh(@NotNull TabPlayer refreshed, boolean force) {
|
||||
if (refreshed.scoreboardData.activeScoreboard != line.getParent()) return; //player has different scoreboard displayed
|
||||
if (refreshed.scoreboardData.numberFormatProperties.get(line) == null) return; // Shrug
|
||||
refreshed.getScoreboard().setScore(
|
||||
ScoreboardManagerImpl.OBJECTIVE_NAME,
|
||||
line.getPlayerName(refreshed),
|
||||
getScore(refreshed),
|
||||
null,
|
||||
getNumberFormat(refreshed)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers properties for player.
|
||||
*
|
||||
* @param player
|
||||
* Player to register properties for
|
||||
*/
|
||||
public void registerProperties(@NotNull TabPlayer player) {
|
||||
player.scoreboardData.scoreProperties.put(line, new Property(this, player, score == null ? "" : score));
|
||||
player.scoreboardData.numberFormatProperties.put(line, new Property(this, player, numberFormat == null ? "" : numberFormat));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns new score value for specified player.
|
||||
*
|
||||
* @param player
|
||||
* Player to get score for
|
||||
* @return New score based on current placeholder results
|
||||
*/
|
||||
public int getScore(@NotNull TabPlayer player) {
|
||||
return getScore(player, line.getParent().getLines().size() + 1 - lineNumber);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns new score value for specified player.
|
||||
* If score is not configured, returns provided fallback number.
|
||||
*
|
||||
* @param player
|
||||
* Player to get score for
|
||||
* @param fallbackNumber
|
||||
* Fallback number to return if score is not configured
|
||||
* @return New score based on current placeholder results
|
||||
*/
|
||||
public int getScore(@NotNull TabPlayer player, int fallbackNumber) {
|
||||
// Check if score is configured (for <1.20.3)
|
||||
if (score != null) {
|
||||
try {
|
||||
return Integer.parseInt(player.scoreboardData.scoreProperties.get(line).updateAndGet());
|
||||
} catch (NumberFormatException e) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return fallbackNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns new number format value for specified player.
|
||||
*
|
||||
* @param player
|
||||
* Player to get number format for
|
||||
* @return New number format based on current placeholder results
|
||||
*/
|
||||
@Nullable
|
||||
public TabComponent getNumberFormat(@NotNull TabPlayer player) {
|
||||
return cache.get(player.scoreboardData.numberFormatProperties.get(line).updateAndGet());
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public ThreadExecutor getCustomThread() {
|
||||
return line.getCustomThread();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getFeatureName() {
|
||||
return line.getFeatureName();
|
||||
}
|
||||
}
|
||||
+12
-78
@@ -9,9 +9,7 @@ import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.chat.component.TabComponent;
|
||||
import me.neznamy.tab.shared.cpu.ThreadExecutor;
|
||||
import me.neznamy.tab.shared.features.scoreboard.ScoreboardConfiguration.ScoreboardDefinition;
|
||||
import me.neznamy.tab.shared.features.scoreboard.lines.LongLine;
|
||||
import me.neznamy.tab.shared.features.scoreboard.lines.ScoreboardLine;
|
||||
import me.neznamy.tab.shared.features.scoreboard.lines.StableDynamicLine;
|
||||
import me.neznamy.tab.shared.features.scoreboard.lines.ScoreboardLineHolder;
|
||||
import me.neznamy.tab.shared.features.types.Conditional;
|
||||
import me.neznamy.tab.shared.features.types.CustomThreaded;
|
||||
import me.neznamy.tab.shared.features.types.RefreshableFeature;
|
||||
@@ -19,7 +17,6 @@ import me.neznamy.tab.shared.placeholders.conditions.Condition;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
@@ -66,7 +63,7 @@ public class ScoreboardImpl extends RefreshableFeature implements me.neznamy.tab
|
||||
* Scoreboard properties
|
||||
*/
|
||||
public ScoreboardImpl(@NonNull ScoreboardManagerImpl manager, @NonNull String name, @NonNull ScoreboardDefinition definition) {
|
||||
this(manager, name, definition, false, false);
|
||||
this(manager, name, definition, false);
|
||||
displayCondition = TAB.getInstance().getPlaceholderManager().getConditionManager().getByNameOrExpression(definition.getDisplayCondition());
|
||||
if (displayCondition != null) {
|
||||
manager.addUsedPlaceholder(displayCondition.getPlaceholderIdentifier());
|
||||
@@ -82,12 +79,10 @@ public class ScoreboardImpl extends RefreshableFeature implements me.neznamy.tab
|
||||
* name of this scoreboard
|
||||
* @param definition
|
||||
* Scoreboard properties
|
||||
* @param dynamicLinesOnly
|
||||
* Whether this scoreboard should only use dynamic lines or not
|
||||
* @param api
|
||||
* Whether this scoreboard was created using API or not
|
||||
*/
|
||||
public ScoreboardImpl(@NonNull ScoreboardManagerImpl manager, @NonNull String name, @NonNull ScoreboardDefinition definition, boolean dynamicLinesOnly, boolean api) {
|
||||
public ScoreboardImpl(@NonNull ScoreboardManagerImpl manager, @NonNull String name, @NonNull ScoreboardDefinition definition, boolean api) {
|
||||
this.manager = manager;
|
||||
this.name = name;
|
||||
this.api = api;
|
||||
@@ -95,37 +90,12 @@ public class ScoreboardImpl extends RefreshableFeature implements me.neznamy.tab
|
||||
for (int i = 0; i< definition.getLines().size(); i++) {
|
||||
String line = definition.getLines().get(i);
|
||||
if (line == null) line = "";
|
||||
ScoreboardLine score;
|
||||
if (dynamicLinesOnly) {
|
||||
score = new StableDynamicLine(this, i+1, line);
|
||||
} else {
|
||||
score = registerLine(i+1, line);
|
||||
}
|
||||
ScoreboardLineHolder score = new ScoreboardLineHolder(this, line, i+1);
|
||||
lines.add(score);
|
||||
TAB.getInstance().getFeatureManager().registerFeature(TabConstants.Feature.scoreboardLine(name, i), score);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers line with given text and line number
|
||||
*
|
||||
* @param lineNumber
|
||||
* ID of line
|
||||
* @param text
|
||||
* text to display
|
||||
* @return most optimal line from provided text
|
||||
*/
|
||||
private @NotNull ScoreboardLine registerLine(int lineNumber, @Nullable String text) {
|
||||
if (text == null) return new LongLine(this, lineNumber, "");
|
||||
if (text.startsWith("Long|")) {
|
||||
return new LongLine(this, lineNumber, text.substring(5));
|
||||
}
|
||||
if (text.contains("%")) {
|
||||
return new StableDynamicLine(this, lineNumber, text);
|
||||
}
|
||||
return new LongLine(this, lineNumber, text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the player into scoreboard. This includes registering properties,
|
||||
* as well as scoreboard and all lines.
|
||||
@@ -146,11 +116,10 @@ public class ScoreboardImpl extends RefreshableFeature implements me.neznamy.tab
|
||||
p.getScoreboard().setDisplaySlot(ScoreboardManagerImpl.OBJECTIVE_NAME, Scoreboard.DisplaySlot.SIDEBAR);
|
||||
}
|
||||
for (Line s : lines) {
|
||||
((ScoreboardLine)s).register(p);
|
||||
((ScoreboardLineHolder)s).register(p);
|
||||
}
|
||||
players.add(p);
|
||||
p.scoreboardData.activeScoreboard = this;
|
||||
recalculateScores(p);
|
||||
p.expansionData.setScoreboardName(name);
|
||||
}
|
||||
|
||||
@@ -164,18 +133,12 @@ public class ScoreboardImpl extends RefreshableFeature implements me.neznamy.tab
|
||||
if (p.scoreboardData.activeScoreboard != this) return; // not registered
|
||||
p.getScoreboard().unregisterObjective(ScoreboardManagerImpl.OBJECTIVE_NAME);
|
||||
for (Line l : lines) {
|
||||
ScoreboardLine line = (ScoreboardLine) l;
|
||||
if (line.isShownTo(p)) {
|
||||
p.getScoreboard().unregisterTeam(line.getTeamName());
|
||||
line.removePlayerSilently(p);
|
||||
}
|
||||
p.getScoreboard().unregisterTeam(((ScoreboardLineHolder) l).getTeamName());
|
||||
}
|
||||
players.remove(p);
|
||||
p.scoreboardData.activeScoreboard = null;
|
||||
p.scoreboardData.titleProperty = null;
|
||||
p.scoreboardData.lineProperties.clear();
|
||||
p.scoreboardData.lineNameProperties.clear();
|
||||
p.scoreboardData.numberFormatProperties.clear();
|
||||
p.expansionData.setScoreboardName("");
|
||||
}
|
||||
|
||||
@@ -196,30 +159,6 @@ public class ScoreboardImpl extends RefreshableFeature implements me.neznamy.tab
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Recalculate scores for each line if using numbers. This takes into
|
||||
* consideration lines that are not visible.
|
||||
*
|
||||
* @param p
|
||||
* Player to recalculate scores for
|
||||
*/
|
||||
public void recalculateScores(@NonNull TabPlayer p) {
|
||||
int score = 1;
|
||||
for (Line line : lines) {
|
||||
Property pr = p.scoreboardData.lineProperties.get((ScoreboardLine) line);
|
||||
if (pr.getCurrentRawValue().isEmpty() || (!pr.getCurrentRawValue().isEmpty() && !pr.get().isEmpty())) {
|
||||
((ScoreboardLine)line).getScoreRefresher().setLineNumber(score++);
|
||||
p.getScoreboard().setScore(
|
||||
ScoreboardManagerImpl.OBJECTIVE_NAME,
|
||||
((ScoreboardLine)line).getPlayerName(p),
|
||||
((ScoreboardLine)line).getScoreRefresher().getScore(p),
|
||||
null, // Makes no sense for TAB
|
||||
((ScoreboardLine) line).getScoreRefresher().getNumberFormat(p)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes this player from list of players who can see it.
|
||||
*
|
||||
@@ -228,9 +167,6 @@ public class ScoreboardImpl extends RefreshableFeature implements me.neznamy.tab
|
||||
*/
|
||||
public void removePlayerFromSet(@NonNull TabPlayer player) {
|
||||
players.remove(player);
|
||||
for (Line line : lines) {
|
||||
((ScoreboardLine)line).removePlayerSilently(player);
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -261,12 +197,11 @@ public class ScoreboardImpl extends RefreshableFeature implements me.neznamy.tab
|
||||
@Override
|
||||
public void addLine(@NonNull String text) {
|
||||
ensureActive();
|
||||
StableDynamicLine line = new StableDynamicLine(this, lines.size()+1, text);
|
||||
ScoreboardLineHolder line = new ScoreboardLineHolder(this, text, lines.size()+1);
|
||||
TAB.getInstance().getFeatureManager().registerFeature(TabConstants.Feature.scoreboardLine(name, lines.size()), line);
|
||||
lines.add(line);
|
||||
for (TabPlayer p : players) {
|
||||
line.register(p);
|
||||
recalculateScores(p);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -274,11 +209,10 @@ public class ScoreboardImpl extends RefreshableFeature implements me.neznamy.tab
|
||||
public void removeLine(int index) {
|
||||
ensureActive();
|
||||
if (index < 0 || index >= lines.size()) throw new IndexOutOfBoundsException("Index " + index + " is out of range (0 - " + (lines.size()-1) + ")");
|
||||
ScoreboardLine line = (ScoreboardLine) lines.get(index);
|
||||
ScoreboardLineHolder line = (ScoreboardLineHolder) lines.get(index);
|
||||
lines.remove(line);
|
||||
for (TabPlayer p : players) {
|
||||
line.unregister(p);
|
||||
recalculateScores(p);
|
||||
}
|
||||
TAB.getInstance().getFeatureManager().unregisterFeature(TabConstants.Feature.scoreboardLine(name, index));
|
||||
}
|
||||
@@ -286,13 +220,13 @@ public class ScoreboardImpl extends RefreshableFeature implements me.neznamy.tab
|
||||
@Override
|
||||
public void setLines(@NonNull List<String> newLines) {
|
||||
ensureActive();
|
||||
int commonSize = Math.min(this.lines.size(), newLines.size());
|
||||
int commonSize = Math.min(lines.size(), newLines.size());
|
||||
for (int i = 0; i < commonSize; i++) {
|
||||
String newText = newLines.get(i) == null ? "" : newLines.get(i);
|
||||
if (!this.lines.get(i).getText().equals(newText))
|
||||
this.lines.get(i).setText(newText);
|
||||
if (!lines.get(i).getText().equals(newText))
|
||||
lines.get(i).setText(newText);
|
||||
}
|
||||
for (int i = this.lines.size() - 1; i >= commonSize; i--)
|
||||
for (int i = lines.size() - 1; i >= commonSize; i--)
|
||||
removeLine(i);
|
||||
for (int i = commonSize; i < newLines.size(); i++)
|
||||
addLine(newLines.get(i) == null ? "" : newLines.get(i));
|
||||
|
||||
+57
-4
@@ -11,7 +11,7 @@ import me.neznamy.tab.shared.cpu.TimedCaughtTask;
|
||||
import me.neznamy.tab.shared.data.Server;
|
||||
import me.neznamy.tab.shared.features.ToggleManager;
|
||||
import me.neznamy.tab.shared.features.scoreboard.ScoreboardConfiguration.ScoreboardDefinition;
|
||||
import me.neznamy.tab.shared.features.scoreboard.lines.ScoreboardLine;
|
||||
import me.neznamy.tab.shared.features.scoreboard.lines.ScoreboardLineHolder;
|
||||
import me.neznamy.tab.shared.features.types.*;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
@@ -34,6 +34,7 @@ public class ScoreboardManagerImpl extends RefreshableFeature implements Scorebo
|
||||
public static final String OBJECTIVE_NAME = "TAB-Scoreboard";
|
||||
|
||||
private final StringToComponentCache cache = new StringToComponentCache("Scoreboard", 1000);
|
||||
private final StringToComponentCache numberFormatCache = new StringToComponentCache("Scoreboard NumberFormat", 1000);
|
||||
|
||||
private final ThreadExecutor customThread = new ThreadExecutor("TAB Scoreboard Thread");
|
||||
|
||||
@@ -189,6 +190,34 @@ public class ScoreboardManagerImpl extends RefreshableFeature implements Scorebo
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a sidebar display sent through VelocityScoreboardAPI.
|
||||
*
|
||||
* <p>VelocityScoreboardAPI gives proxy objectives priority over downstream
|
||||
* objectives. Its display event is therefore the last opportunity to remove
|
||||
* TAB's proxy objective before the downstream display packet is cancelled.
|
||||
* This method is intentionally called synchronously by the Velocity hook.</p>
|
||||
*
|
||||
* @param receiver
|
||||
* player receiving the packet
|
||||
* @param slot
|
||||
* display slot
|
||||
* @param objective
|
||||
* objective assigned to the slot, or {@code null}
|
||||
*/
|
||||
public void onVelocityScoreboardDisplay(@NotNull TabPlayer receiver, int slot, @Nullable String objective) {
|
||||
if (slot != Scoreboard.DisplaySlot.SIDEBAR.ordinal()) {
|
||||
return;
|
||||
}
|
||||
if (objective == null) {
|
||||
restoreAfterVelocityScoreboard(receiver);
|
||||
} else if (!objective.equals(OBJECTIVE_NAME)) {
|
||||
TAB.getInstance().debug("Player " + receiver.getName() + " received scoreboard called " + objective + ", releasing TAB's proxy scoreboard.");
|
||||
receiver.scoreboardData.otherPluginScoreboard = objective;
|
||||
unregisterScoreboard(receiver);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onObjective(@NotNull TabPlayer receiver, int action, @NotNull String objective) {
|
||||
if (action == Scoreboard.ObjectiveAction.UNREGISTER && objective.equals(receiver.scoreboardData.otherPluginScoreboard)) {
|
||||
@@ -200,6 +229,30 @@ public class ScoreboardManagerImpl extends RefreshableFeature implements Scorebo
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles removal of a downstream objective reported by VelocityScoreboardAPI.
|
||||
* This is synchronous for the same reason as {@link #onVelocityScoreboardDisplay(TabPlayer, int, String)}.
|
||||
*
|
||||
* @param receiver
|
||||
* player receiving the packet
|
||||
* @param objective
|
||||
* removed objective
|
||||
*/
|
||||
public void onVelocityScoreboardUnregister(@NotNull TabPlayer receiver, @NotNull String objective) {
|
||||
if (objective.equals(receiver.scoreboardData.otherPluginScoreboard)) {
|
||||
restoreAfterVelocityScoreboard(receiver);
|
||||
}
|
||||
}
|
||||
|
||||
private void restoreAfterVelocityScoreboard(@NotNull TabPlayer receiver) {
|
||||
if (receiver.scoreboardData.otherPluginScoreboard == null) {
|
||||
return;
|
||||
}
|
||||
TAB.getInstance().debug("Player " + receiver.getName() + " no longer has another scoreboard, restoring TAB's proxy scoreboard.");
|
||||
receiver.scoreboardData.otherPluginScoreboard = null;
|
||||
sendHighestScoreboard(receiver);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onQuit(@NotNull TabPlayer disconnectedPlayer) {
|
||||
ScoreboardImpl sb = disconnectedPlayer.scoreboardData.activeScoreboard;
|
||||
@@ -230,8 +283,8 @@ public class ScoreboardManagerImpl extends RefreshableFeature implements Scorebo
|
||||
put("title", player.scoreboardData.titleProperty.get());
|
||||
List<String> lines = new ArrayList<>();
|
||||
for (Line line : player.scoreboardData.activeScoreboard.getLines()) {
|
||||
lines.add(player.scoreboardData.lineProperties.get((ScoreboardLine) line).get() + " || " +
|
||||
player.scoreboardData.numberFormatProperties.get((ScoreboardLine) line).get());
|
||||
lines.add(player.scoreboardData.lineProperties.get((ScoreboardLineHolder) line).textProperty.get() + " || " +
|
||||
player.scoreboardData.lineProperties.get((ScoreboardLineHolder) line).numberFormatProperty.get());
|
||||
}
|
||||
put("lines", lines);
|
||||
}});
|
||||
@@ -249,7 +302,7 @@ public class ScoreboardManagerImpl extends RefreshableFeature implements Scorebo
|
||||
@NotNull
|
||||
public me.neznamy.tab.api.scoreboard.Scoreboard createScoreboard(@NonNull String name, @NonNull String title, @NonNull List<String> lines) {
|
||||
ensureActive();
|
||||
ScoreboardImpl sb = new ScoreboardImpl(this, name, new ScoreboardDefinition(null, title, lines), true, true);
|
||||
ScoreboardImpl sb = new ScoreboardImpl(this, name, new ScoreboardDefinition(null, title, lines), true);
|
||||
registeredScoreboards.put(name, sb);
|
||||
return sb;
|
||||
}
|
||||
|
||||
+24
-12
@@ -1,7 +1,8 @@
|
||||
package me.neznamy.tab.shared.features.scoreboard;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import me.neznamy.tab.shared.Property;
|
||||
import me.neznamy.tab.shared.features.scoreboard.lines.ScoreboardLine;
|
||||
import me.neznamy.tab.shared.features.scoreboard.lines.ScoreboardLineHolder;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -35,19 +36,30 @@ public class ScoreboardPlayerData {
|
||||
@Nullable
|
||||
public Property titleProperty;
|
||||
|
||||
/** Map of line text properties */
|
||||
/** Properties of scoreboard lines of scoreboard the player can currently see */
|
||||
@NotNull
|
||||
public final Map<ScoreboardLine, Property> lineProperties = new IdentityHashMap<>();
|
||||
public final Map<ScoreboardLineHolder, LineProperties> lineProperties = new IdentityHashMap<>();
|
||||
|
||||
/** Map of line player name properties (used in long lines) */
|
||||
@NotNull
|
||||
public final Map<ScoreboardLine, Property> lineNameProperties = new IdentityHashMap<>();
|
||||
/**
|
||||
* Data class for storing properties of a scoreboard line for a player.
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
public static class LineProperties {
|
||||
|
||||
/** Map of line score properties */
|
||||
@NotNull
|
||||
public final Map<ScoreboardLine, Property> scoreProperties = new IdentityHashMap<>();
|
||||
/** Property of line text */
|
||||
@NotNull
|
||||
public final Property textProperty;
|
||||
|
||||
/** Map of line NumberFormat properties */
|
||||
@NotNull
|
||||
public final Map<ScoreboardLine, Property> numberFormatProperties = new IdentityHashMap<>();
|
||||
/** Name of the score (used in long lines) */
|
||||
@NotNull
|
||||
public String scoreName;
|
||||
|
||||
/** Property of line NumberFormat */
|
||||
@NotNull
|
||||
public final Property numberFormatProperty;
|
||||
|
||||
/** Property of line score */
|
||||
@NotNull
|
||||
public final Property scoreProperty;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package me.neznamy.tab.shared.features.scoreboard.lines;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import me.neznamy.tab.shared.features.scoreboard.ScoreboardManagerImpl;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Implementation for empty lines. They are treated as a special case to make them actually
|
||||
* appear, as refresh logic hides lines that evaluated to an empty string, which we do not want
|
||||
* if a line is configured to be empty (spacer line).
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class EmptyLine implements ScoreboardLine {
|
||||
|
||||
/** Holder of this line */
|
||||
@NotNull
|
||||
private final ScoreboardLineHolder holder;
|
||||
|
||||
@Override
|
||||
public void register(@NonNull TabPlayer player) {
|
||||
holder.setScore(player, holder.getForcedPlayerNameStart());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refresh(@NotNull TabPlayer player) {
|
||||
player.scoreboardData.lineProperties.get(holder).scoreProperty.update();
|
||||
holder.setScore(player, holder.getForcedPlayerNameStart());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregister(@NotNull TabPlayer player) {
|
||||
player.getScoreboard().removeScore(ScoreboardManagerImpl.OBJECTIVE_NAME, holder.getForcedPlayerNameStart());
|
||||
}
|
||||
}
|
||||
+115
-62
@@ -1,14 +1,19 @@
|
||||
package me.neznamy.tab.shared.features.scoreboard.lines;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import me.neznamy.tab.shared.Limitations;
|
||||
import me.neznamy.tab.shared.Property;
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.chat.TabTextColor;
|
||||
import me.neznamy.tab.shared.features.scoreboard.ScoreboardManagerImpl;
|
||||
import me.neznamy.tab.shared.features.scoreboard.ScoreboardPlayerData;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import me.neznamy.tab.shared.features.scoreboard.ScoreboardImpl;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* Line using all 3 values - prefix, name and suffix. Line may flicker when placeholder changes value.
|
||||
* Limitations:
|
||||
@@ -16,81 +21,129 @@ import org.jetbrains.annotations.NotNull;
|
||||
* 1.8.x - 1.12.x: up to 66 characters
|
||||
* 1.13+: unlimited
|
||||
*/
|
||||
public class LongLine extends ScoreboardLine {
|
||||
@RequiredArgsConstructor
|
||||
public class LongLine implements ScoreboardLine {
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters
|
||||
*
|
||||
* @param parent
|
||||
* scoreboard this line belongs to
|
||||
* @param lineNumber
|
||||
* ID of this line
|
||||
* @param text
|
||||
* line text
|
||||
*/
|
||||
public LongLine(@NonNull ScoreboardImpl parent, int lineNumber, @NonNull String text) {
|
||||
super(parent, lineNumber, text);
|
||||
/** Holder of this line */
|
||||
@NotNull
|
||||
private final ScoreboardLineHolder holder;
|
||||
|
||||
@Override
|
||||
public void register(@NotNull TabPlayer player) {
|
||||
ScoreboardPlayerData.LineProperties properties = player.scoreboardData.lineProperties.get(holder);
|
||||
if (properties.textProperty.get().isEmpty() && properties.numberFormatProperty.get().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if (player.getVersion().getMinorVersion() >= 13 && !TAB.getInstance().getConfiguration().getConfig().isPacketEventsCompensation()) {
|
||||
holder.updateTeam(player, properties.textProperty.get(), "");
|
||||
holder.setScore(player, holder.getForcedPlayerNameStart());
|
||||
} else {
|
||||
String[] values = splitText(
|
||||
holder.getForcedPlayerNameStart(),
|
||||
holder.getParent().getManager().getCache().get(properties.textProperty.get()).toLegacyText(),
|
||||
player.getVersion().getNetworkId() >= ProtocolVersion.V1_8.getNetworkId() ? Limitations.SCOREBOARD_SCORE_LENGTH_1_8 : Limitations.SCOREBOARD_SCORE_LENGTH_1_7
|
||||
);
|
||||
properties.scoreName = values[1];
|
||||
updateTeam(player, values);
|
||||
holder.setScore(player, values[1]);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refresh(@NotNull TabPlayer refreshed, boolean force) {
|
||||
if (refreshed.scoreboardData.activeScoreboard != parent) return; //player has different scoreboard displayed
|
||||
Property lineProperty = refreshed.scoreboardData.lineProperties.get(this);
|
||||
if (lineProperty.update()) {
|
||||
if (refreshed.getVersion().getMinorVersion() >= 13 && !TAB.getInstance().getConfiguration().getConfig().isPacketEventsCompensation()) {
|
||||
updateTeam(refreshed, lineProperty.get(), "");
|
||||
public void refresh(@NotNull TabPlayer player) {
|
||||
if (player.scoreboardData.activeScoreboard != holder.getParent()) return; // Player has different scoreboard displayed
|
||||
ScoreboardPlayerData.LineProperties properties = player.scoreboardData.lineProperties.get(holder);
|
||||
boolean textUpdated = properties.textProperty.update();
|
||||
boolean numberFormatUpdated = properties.numberFormatProperty.update();
|
||||
boolean scoreUpdated = properties.scoreProperty.update();
|
||||
if (properties.textProperty.get().isEmpty() && properties.numberFormatProperty.get().isEmpty()) {
|
||||
player.getScoreboard().removeScore(ScoreboardManagerImpl.OBJECTIVE_NAME, properties.scoreName);
|
||||
} else if (textUpdated || numberFormatUpdated || scoreUpdated) {
|
||||
if (player.getVersion().getMinorVersion() >= 13 && !TAB.getInstance().getConfiguration().getConfig().isPacketEventsCompensation()) {
|
||||
holder.updateTeam(player, properties.textProperty.get(), "");
|
||||
holder.setScore(player, holder.getForcedPlayerNameStart());
|
||||
} else {
|
||||
removeLine(refreshed, refreshed.scoreboardData.lineNameProperties.get(this).get());
|
||||
player.getScoreboard().removeScore(ScoreboardManagerImpl.OBJECTIVE_NAME, properties.scoreName);
|
||||
String[] values = splitText(
|
||||
forcedPlayerNameStart,
|
||||
parent.getManager().getCache().get(lineProperty.get()).toLegacyText(),
|
||||
refreshed.getVersion().getNetworkId() >= ProtocolVersion.V1_8.getNetworkId() ? Limitations.SCOREBOARD_SCORE_LENGTH_1_8 : Limitations.SCOREBOARD_SCORE_LENGTH_1_7
|
||||
holder.getForcedPlayerNameStart(),
|
||||
holder.getParent().getManager().getCache().get(properties.textProperty.get()).toLegacyText(),
|
||||
player.getVersion().getNetworkId() >= ProtocolVersion.V1_8.getNetworkId() ? Limitations.SCOREBOARD_SCORE_LENGTH_1_8 : Limitations.SCOREBOARD_SCORE_LENGTH_1_7
|
||||
);
|
||||
addLine(refreshed, values[1], values[0], values[2]);
|
||||
refreshed.scoreboardData.lineNameProperties.get(this).changeRawValue(values[1]);
|
||||
updateTeam(player, values);
|
||||
holder.setScore(player, values[1]);
|
||||
properties.scoreName = values[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void register(@NonNull TabPlayer p) {
|
||||
p.scoreboardData.lineProperties.put(this, new Property(this, p, text));
|
||||
getScoreRefresher().registerProperties(p);
|
||||
String value = p.scoreboardData.lineProperties.get(this).get();
|
||||
if (p.getVersion().getMinorVersion() >= 13 && !TAB.getInstance().getConfiguration().getConfig().isPacketEventsCompensation()) {
|
||||
addLine(p, forcedPlayerNameStart, value, "");
|
||||
p.scoreboardData.lineNameProperties.put(this, new Property(this, p, forcedPlayerNameStart));
|
||||
} else {
|
||||
String[] values = splitText(
|
||||
forcedPlayerNameStart,
|
||||
parent.getManager().getCache().get(value).toLegacyText(),
|
||||
p.getVersion().getNetworkId() >= ProtocolVersion.V1_8.getNetworkId() ? Limitations.SCOREBOARD_SCORE_LENGTH_1_8 : Limitations.SCOREBOARD_SCORE_LENGTH_1_7
|
||||
);
|
||||
addLine(p, values[1], values[0], values[2]);
|
||||
p.scoreboardData.lineNameProperties.put(this, new Property(this, p, values[1]));
|
||||
}
|
||||
private void updateTeam(@NotNull TabPlayer player, @NotNull String[] values) {
|
||||
player.getScoreboard().unregisterTeam(holder.getTeamName());
|
||||
player.getScoreboard().registerTeam(
|
||||
holder.getTeamName(),
|
||||
holder.getParent().getManager().getCache().get(values[0]),
|
||||
holder.getParent().getManager().getCache().get(values[2]),
|
||||
Scoreboard.NameVisibility.NEVER,
|
||||
Scoreboard.CollisionRule.NEVER,
|
||||
Collections.singletonList(values[1]),
|
||||
0,
|
||||
TabTextColor.RESET.getLegacyColor()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregister(@NonNull TabPlayer p) {
|
||||
if (p.scoreboardData.activeScoreboard == parent) {
|
||||
removeLine(p, p.scoreboardData.lineNameProperties.get(this).get());
|
||||
}
|
||||
public void unregister(@NotNull TabPlayer player) {
|
||||
player.getScoreboard().removeScore(
|
||||
ScoreboardManagerImpl.OBJECTIVE_NAME,
|
||||
player.scoreboardData.lineProperties.get(holder).scoreName
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setText(@NonNull String text) {
|
||||
ensureActive();
|
||||
initializeText(text);
|
||||
for (TabPlayer p : parent.getPlayers()) {
|
||||
p.scoreboardData.lineProperties.get(this).changeRawValue(text);
|
||||
refresh(p, false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
/**
|
||||
* Splits entered text into 3 parts - prefix, name and suffix respecting all limits.
|
||||
* Returns the values as an array of 3 elements.
|
||||
*
|
||||
* @param playerNameStart
|
||||
* forced start of name field (used to secure unique names and line order)
|
||||
* @param text
|
||||
* text to display
|
||||
* @param maxNameLength
|
||||
* maximum length of name field
|
||||
* @return Split text as an array of 3 elements
|
||||
*/
|
||||
@NotNull
|
||||
public String getPlayerName(@NonNull TabPlayer viewer) {
|
||||
return viewer.scoreboardData.lineNameProperties.get(this).get();
|
||||
private String[] splitText(@NonNull String playerNameStart, @NonNull String text, int maxNameLength) {
|
||||
String prefixValue;
|
||||
String nameValue;
|
||||
String suffixValue;
|
||||
if (text.length() <= (maxNameLength - playerNameStart.length())) {
|
||||
prefixValue = "";
|
||||
nameValue = playerNameStart + text;
|
||||
suffixValue = "";
|
||||
} else {
|
||||
String[] prefixOther = split(text, Limitations.TEAM_PREFIX_SUFFIX_PRE_1_13);
|
||||
prefixValue = prefixOther[0];
|
||||
String other = prefixOther[1];
|
||||
other = playerNameStart + getLastColors(prefixValue) + other;
|
||||
String[] nameSuffix = split(other, maxNameLength);
|
||||
nameValue = nameSuffix[0];
|
||||
suffixValue = nameSuffix[1];
|
||||
}
|
||||
return new String[]{prefixValue, nameValue, suffixValue};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits the text into 2 with given max length of first string
|
||||
*
|
||||
* @param string
|
||||
* string to split
|
||||
* @param firstElementMaxLength
|
||||
* max length of first string
|
||||
* @return array of 2 strings where second one might be empty
|
||||
*/
|
||||
private String[] split(@NonNull String string, int firstElementMaxLength) {
|
||||
if (string.length() <= firstElementMaxLength) return new String[] {string, ""};
|
||||
int splitIndex = firstElementMaxLength;
|
||||
if (string.charAt(splitIndex-1) == '§') splitIndex--;
|
||||
return new String[] {string.substring(0, splitIndex), string.substring(splitIndex)};
|
||||
}
|
||||
}
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
package me.neznamy.tab.shared.features.scoreboard.lines;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import me.neznamy.tab.shared.Limitations;
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.features.scoreboard.ScoreboardManagerImpl;
|
||||
import me.neznamy.tab.shared.features.scoreboard.ScoreboardPlayerData;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Line of text that uses only team prefix and suffix to display text.
|
||||
* This allows for changes without having to remove a score and add a new one, possibly
|
||||
* causing a flicker on update.
|
||||
* Limitations:
|
||||
* 1.12-: Up to 32 characters depending on color / magic codes
|
||||
* 1.13+: Unlimited
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class NoFlickerLine implements ScoreboardLine {
|
||||
|
||||
/** Holder of this line */
|
||||
@NotNull
|
||||
private final ScoreboardLineHolder holder;
|
||||
|
||||
@Override
|
||||
public void register(@NonNull TabPlayer player) {
|
||||
ScoreboardPlayerData.LineProperties properties = player.scoreboardData.lineProperties.get(holder);
|
||||
if (properties.textProperty.get().isEmpty() && properties.numberFormatProperty.get().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
String[] prefixSuffix = split(player, properties.textProperty.get());
|
||||
holder.updateTeam(player, prefixSuffix[0], prefixSuffix[1]);
|
||||
holder.setScore(player, holder.getForcedPlayerNameStart());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refresh(@NotNull TabPlayer player) {
|
||||
if (player.scoreboardData.activeScoreboard != holder.getParent()) return; // Player has different scoreboard displayed
|
||||
ScoreboardPlayerData.LineProperties properties = player.scoreboardData.lineProperties.get(holder);
|
||||
boolean emptyBefore = properties.textProperty.get().isEmpty() && properties.numberFormatProperty.get().isEmpty();
|
||||
if (properties.textProperty.update()) {
|
||||
String[] prefixSuffix = split(player, properties.textProperty.get());
|
||||
holder.updateTeam(player, prefixSuffix[0], prefixSuffix[1]);
|
||||
}
|
||||
boolean numberFormatUpdated = properties.numberFormatProperty.update();
|
||||
boolean scoreUpdated = properties.scoreProperty.update();
|
||||
if (properties.textProperty.get().isEmpty() && properties.numberFormatProperty.get().isEmpty()) {
|
||||
player.getScoreboard().removeScore(ScoreboardManagerImpl.OBJECTIVE_NAME, holder.getForcedPlayerNameStart());
|
||||
} else if (emptyBefore || numberFormatUpdated || scoreUpdated) {
|
||||
holder.setScore(player, holder.getForcedPlayerNameStart());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregister(@NotNull TabPlayer player) {
|
||||
player.getScoreboard().removeScore(ScoreboardManagerImpl.OBJECTIVE_NAME, holder.getForcedPlayerNameStart());
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits text into 2 values (prefix/suffix) based on client version and text itself
|
||||
*
|
||||
* @param p
|
||||
* player to split text for
|
||||
* @param text
|
||||
* text to split
|
||||
* @return array of 2 elements for prefix and suffix
|
||||
*/
|
||||
@NotNull
|
||||
private String[] split(@NonNull TabPlayer p, @NonNull String text) {
|
||||
if (p.getVersion().getNetworkId() < ProtocolVersion.V1_16.getNetworkId()) {
|
||||
text = holder.getParent().getManager().getCache().get(text).toLegacyText(); //converting RGB to legacy here to avoid splitting in the middle of RGB code
|
||||
}
|
||||
if (p.getVersion().getMinorVersion() >= 13 && !TAB.getInstance().getConfiguration().getConfig().isPacketEventsCompensation()) return new String[] {text, ""};
|
||||
int charLimit = Limitations.TEAM_PREFIX_SUFFIX_PRE_1_13;
|
||||
if (text.length() > charLimit) {
|
||||
StringBuilder prefix = new StringBuilder(text);
|
||||
StringBuilder suffix = new StringBuilder(text);
|
||||
prefix.setLength(charLimit);
|
||||
suffix.delete(0, charLimit);
|
||||
if (prefix.charAt(charLimit-1) == '§') {
|
||||
prefix.setLength(prefix.length()-1);
|
||||
suffix.insert(0, '§');
|
||||
}
|
||||
String prefixString = prefix.toString();
|
||||
suffix.insert(0, getLastColors(holder.getParent().getManager().getCache().get(prefixString).toLegacyText()));
|
||||
return new String[] {prefixString, suffix.toString()};
|
||||
} else {
|
||||
return new String[] {text, ""};
|
||||
}
|
||||
}
|
||||
}
|
||||
+13
-255
@@ -1,278 +1,36 @@
|
||||
package me.neznamy.tab.shared.features.scoreboard.lines;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.shared.chat.TabTextColor;
|
||||
import me.neznamy.tab.api.scoreboard.Line;
|
||||
import me.neznamy.tab.shared.Limitations;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.cpu.ThreadExecutor;
|
||||
import me.neznamy.tab.shared.features.scoreboard.ScoreRefresher;
|
||||
import me.neznamy.tab.shared.features.scoreboard.ScoreboardImpl;
|
||||
import me.neznamy.tab.shared.features.scoreboard.ScoreboardManagerImpl;
|
||||
import me.neznamy.tab.shared.features.types.CustomThreaded;
|
||||
import me.neznamy.tab.shared.features.types.RefreshableFeature;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* Abstract class representing a line of scoreboard
|
||||
* An interface representing a line in the scoreboard.
|
||||
*/
|
||||
@Getter
|
||||
public abstract class ScoreboardLine extends RefreshableFeature implements Line, CustomThreaded {
|
||||
|
||||
/** Text to display in this line */
|
||||
@NotNull
|
||||
protected String text;
|
||||
|
||||
/** Score to use instead of line number (can be null if not configured) */
|
||||
@Nullable
|
||||
protected String score;
|
||||
|
||||
/** Number format to use (can be null if not configured) */
|
||||
@Nullable
|
||||
protected String numberFormat;
|
||||
|
||||
//scoreboard this line belongs to
|
||||
protected final ScoreboardImpl parent;
|
||||
|
||||
//scoreboard team name of player in this line
|
||||
protected final String teamName;
|
||||
|
||||
/** Forced player name start to make lines unique & sort them by names */
|
||||
protected final String forcedPlayerNameStart;
|
||||
|
||||
private final ScoreRefresher scoreRefresher;
|
||||
|
||||
private final Set<TabPlayer> shownPlayers = Collections.newSetFromMap(new ConcurrentHashMap<>());
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters
|
||||
*
|
||||
* @param parent
|
||||
* scoreboard this line belongs to
|
||||
* @param lineNumber
|
||||
* ID of this line
|
||||
*/
|
||||
protected ScoreboardLine(@NonNull ScoreboardImpl parent, int lineNumber, @NotNull String text) {
|
||||
initializeText(text);
|
||||
this.parent = parent;
|
||||
teamName = "TAB-Sidebar-" + lineNumber;
|
||||
if (lineNumber > 99) throw new IllegalStateException("Internal code does not support more than 99 lines per scoreboard.");
|
||||
forcedPlayerNameStart = String.format("§%d§%d§r", (lineNumber / 10) % 10, lineNumber % 10);
|
||||
scoreRefresher = new ScoreRefresher(this, lineNumber, score, numberFormat);
|
||||
TAB.getInstance().getFeatureManager().registerFeature(TabConstants.Feature.scoreboardScore(parent.getName(), lineNumber), scoreRefresher);
|
||||
}
|
||||
public interface ScoreboardLine {
|
||||
|
||||
/**
|
||||
* Registers this line to the player
|
||||
*
|
||||
* @param p
|
||||
* player to register line to
|
||||
*/
|
||||
public abstract void register(@NonNull TabPlayer p);
|
||||
|
||||
/**
|
||||
* Unregisters this line to the player
|
||||
*
|
||||
* @param p
|
||||
* player to unregister line to
|
||||
*/
|
||||
public abstract void unregister(@NonNull TabPlayer p);
|
||||
|
||||
/**
|
||||
* Returns player name of this line for specified viewer.
|
||||
*
|
||||
* @param viewer
|
||||
* Player to get line name for
|
||||
* @return Name of this line for specified viewer
|
||||
*/
|
||||
@NotNull
|
||||
public String getPlayerName(@NonNull TabPlayer viewer) {
|
||||
return forcedPlayerNameStart;
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits the text into 2 with given max length of first string
|
||||
*
|
||||
* @param string
|
||||
* string to split
|
||||
* @param firstElementMaxLength
|
||||
* max length of first string
|
||||
* @return array of 2 strings where second one might be empty
|
||||
*/
|
||||
protected String[] split(@NonNull String string, int firstElementMaxLength) {
|
||||
if (string.length() <= firstElementMaxLength) return new String[] {string, ""};
|
||||
int splitIndex = firstElementMaxLength;
|
||||
if (string.charAt(splitIndex-1) == '§') splitIndex--;
|
||||
return new String[] {string.substring(0, splitIndex), string.substring(splitIndex)};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends this line to player
|
||||
*
|
||||
* @param p
|
||||
* player to send line to
|
||||
* @param fakePlayer
|
||||
* player name
|
||||
* @param prefix
|
||||
* prefix
|
||||
* @param suffix
|
||||
* suffix
|
||||
*/
|
||||
protected void addLine(@NonNull TabPlayer p, @NonNull String fakePlayer, @NonNull String prefix, @NonNull String suffix) {
|
||||
p.getScoreboard().setScore(
|
||||
ScoreboardManagerImpl.OBJECTIVE_NAME,
|
||||
fakePlayer,
|
||||
scoreRefresher.getScore(p),
|
||||
null, // Makes no sense for TAB
|
||||
scoreRefresher.getNumberFormat(p)
|
||||
);
|
||||
p.getScoreboard().registerTeam(
|
||||
teamName,
|
||||
parent.getManager().getCache().get(prefix),
|
||||
parent.getManager().getCache().get(suffix),
|
||||
Scoreboard.NameVisibility.NEVER,
|
||||
Scoreboard.CollisionRule.NEVER,
|
||||
Collections.singletonList(fakePlayer),
|
||||
0,
|
||||
TabTextColor.RESET.getLegacyColor()
|
||||
);
|
||||
shownPlayers.add(p);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes this line from player
|
||||
*
|
||||
* @param p
|
||||
* player to remove line from
|
||||
* @param fakePlayer
|
||||
* player name
|
||||
*/
|
||||
protected void removeLine(@NonNull TabPlayer p, @NonNull String fakePlayer) {
|
||||
p.getScoreboard().removeScore(ScoreboardManagerImpl.OBJECTIVE_NAME, fakePlayer);
|
||||
p.getScoreboard().unregisterTeam(teamName);
|
||||
shownPlayers.remove(p);
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits entered text into 3 parts - prefix, name and suffix respecting all limits.
|
||||
* Returns the values as an array of 3 elements.
|
||||
*
|
||||
* @param playerNameStart
|
||||
* forced start of name field (used to secure unique names and line order)
|
||||
* @param text
|
||||
* text to display
|
||||
* @param maxNameLength
|
||||
* maximum length of name field
|
||||
* @return Split text as an array of 3 elements
|
||||
*/
|
||||
protected String[] splitText(@NonNull String playerNameStart, @NonNull String text, int maxNameLength) {
|
||||
String prefixValue;
|
||||
String nameValue;
|
||||
String suffixValue;
|
||||
if (text.length() <= (maxNameLength - playerNameStart.length())) {
|
||||
prefixValue = "";
|
||||
nameValue = playerNameStart + text;
|
||||
suffixValue = "";
|
||||
} else {
|
||||
String[] prefixOther = split(text, Limitations.TEAM_PREFIX_SUFFIX_PRE_1_13);
|
||||
prefixValue = prefixOther[0];
|
||||
String other = prefixOther[1];
|
||||
other = playerNameStart + getLastColors(prefixValue) + other;
|
||||
String[] nameSuffix = split(other, maxNameLength);
|
||||
nameValue = nameSuffix[0];
|
||||
suffixValue = nameSuffix[1];
|
||||
}
|
||||
return new String[]{prefixValue, nameValue, suffixValue};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if this line is visible to specified player,
|
||||
* {@code false} if not.
|
||||
* Registers the line for the specified player.
|
||||
*
|
||||
* @param player
|
||||
* Player to check
|
||||
* @return {@code true} if shown, {@code false} if not
|
||||
* the player for whom the line should be registered
|
||||
*/
|
||||
public boolean isShownTo(@NonNull TabPlayer player) {
|
||||
return shownPlayers.contains(player);
|
||||
}
|
||||
void register(@NotNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Splits text using {@code "||"} string, where first part is text to display,
|
||||
* second part is number format (optional) and third part is score (optional).
|
||||
*
|
||||
* @param text
|
||||
* Inputted text to categorize
|
||||
*/
|
||||
protected void initializeText(@NotNull String text) {
|
||||
numberFormat = null;
|
||||
score = null;
|
||||
|
||||
if (text.contains("||")) {
|
||||
String[] split = text.split("\\|\\|", -1);
|
||||
this.text = split.length > 0 ? split[0] : "";
|
||||
if (split.length > 1) numberFormat = split[1];
|
||||
if (split.length > 2) score = split[2];
|
||||
} else {
|
||||
this.text = text;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates prefix/suffix of the fake player.
|
||||
* Refreshes the line for the specified player.
|
||||
*
|
||||
* @param player
|
||||
* Player to send the update to
|
||||
* @param prefix
|
||||
* Prefix to use
|
||||
* @param suffix
|
||||
* Suffix to use
|
||||
* the player for whom the line should be refreshed
|
||||
*/
|
||||
protected void updateTeam(@NotNull TabPlayer player, @NotNull String prefix, @NotNull String suffix) {
|
||||
player.getScoreboard().updateTeam(
|
||||
teamName,
|
||||
parent.getManager().getCache().get(prefix),
|
||||
parent.getManager().getCache().get(suffix),
|
||||
TabTextColor.RESET.getLegacyColor()
|
||||
);
|
||||
}
|
||||
void refresh(@NotNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Silently removes players from the list of players this line is shown to.
|
||||
* Unregisters the line for the specified player.
|
||||
*
|
||||
* @param player
|
||||
* Player to remove
|
||||
* the player for whom the line should be unregistered
|
||||
*/
|
||||
public void removePlayerSilently(@NonNull TabPlayer player) {
|
||||
shownPlayers.remove(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public ThreadExecutor getCustomThread() {
|
||||
return parent.getCustomThread();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getFeatureName() {
|
||||
return parent.getFeatureName();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String getRefreshDisplayName() {
|
||||
return "Updating Scoreboard lines";
|
||||
}
|
||||
void unregister(@NotNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Returns last color codes used in provided text.
|
||||
@@ -282,7 +40,7 @@ public abstract class ScoreboardLine extends RefreshableFeature implements Line,
|
||||
* @return last colors used in provided text or empty string if nothing was found
|
||||
*/
|
||||
@NotNull
|
||||
protected String getLastColors(@NotNull String input) {
|
||||
default String getLastColors(@NotNull String input) {
|
||||
StringBuilder result = new StringBuilder();
|
||||
int length = input.length();
|
||||
for (int index = length - 1; index > -1; index--) {
|
||||
@@ -300,4 +58,4 @@ public abstract class ScoreboardLine extends RefreshableFeature implements Line,
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
+242
@@ -0,0 +1,242 @@
|
||||
package me.neznamy.tab.shared.features.scoreboard.lines;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.api.scoreboard.Line;
|
||||
import me.neznamy.tab.shared.Property;
|
||||
import me.neznamy.tab.shared.chat.TabTextColor;
|
||||
import me.neznamy.tab.shared.chat.component.TabComponent;
|
||||
import me.neznamy.tab.shared.cpu.ThreadExecutor;
|
||||
import me.neznamy.tab.shared.features.scoreboard.ScoreboardImpl;
|
||||
import me.neznamy.tab.shared.features.scoreboard.ScoreboardManagerImpl;
|
||||
import me.neznamy.tab.shared.features.scoreboard.ScoreboardPlayerData;
|
||||
import me.neznamy.tab.shared.features.types.CustomThreaded;
|
||||
import me.neznamy.tab.shared.features.types.RefreshableFeature;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* A class representing a line in the scoreboard.
|
||||
* It holds all information about a line and is able to swap between implementations
|
||||
* if text is changed via API.
|
||||
*/
|
||||
@Getter
|
||||
public class ScoreboardLineHolder extends RefreshableFeature implements Line, CustomThreaded {
|
||||
|
||||
/** The scoreboard this line belongs to */
|
||||
@NotNull
|
||||
private final ScoreboardImpl parent;
|
||||
|
||||
/** The configured text to display in raw form (including || separators) */
|
||||
@NotNull
|
||||
private String text;
|
||||
|
||||
@NotNull
|
||||
private String leftText;
|
||||
|
||||
@NotNull
|
||||
private String numberFormat;
|
||||
|
||||
@NotNull
|
||||
private String score;
|
||||
|
||||
/** Number of this line (1-15+) */
|
||||
private final int lineNumber;
|
||||
|
||||
@NotNull
|
||||
private final String forcedPlayerNameStart;
|
||||
|
||||
/** The team name for this line */
|
||||
@NotNull
|
||||
private final String teamName;
|
||||
|
||||
/** The handle for the scoreboard line based on current text */
|
||||
@NotNull
|
||||
private ScoreboardLine handle;
|
||||
|
||||
/**
|
||||
* Constructs a new instance with given parameters.
|
||||
*
|
||||
* @param parent
|
||||
* the scoreboard this line belongs to
|
||||
* @param text
|
||||
* the configured text to display in raw form (including || separators)
|
||||
* @param lineNumber
|
||||
* number of this line (1-15+)
|
||||
*/
|
||||
public ScoreboardLineHolder(@NonNull ScoreboardImpl parent, @NonNull String text, int lineNumber) {
|
||||
if (lineNumber > 99) throw new IllegalStateException("Internal code does not support more than 99 lines per scoreboard.");
|
||||
this.parent = parent;
|
||||
this.text = text;
|
||||
this.lineNumber = lineNumber;
|
||||
forcedPlayerNameStart = String.format("§%d§%d§r", (lineNumber / 10) % 10, lineNumber % 10);
|
||||
teamName = "TAB-Sidebar-" + lineNumber;
|
||||
splitText();
|
||||
handle = createHandle();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setText(@NonNull String text) {
|
||||
if (this.text.equals(text)) return;
|
||||
for (TabPlayer player : parent.getPlayers()) {
|
||||
handle.unregister(player);
|
||||
}
|
||||
this.text = text;
|
||||
splitText();
|
||||
handle = createHandle();
|
||||
for (TabPlayer player : parent.getPlayers()) {
|
||||
player.scoreboardData.lineProperties.put(this, new ScoreboardPlayerData.LineProperties(
|
||||
new Property(this, player, leftText),
|
||||
forcedPlayerNameStart,
|
||||
new Property(this, player, numberFormat),
|
||||
new Property(this, player, score)
|
||||
));
|
||||
handle.register(player);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public ThreadExecutor getCustomThread() {
|
||||
return parent.getCustomThread();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String getRefreshDisplayName() {
|
||||
return "Updating Scoreboard lines";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refresh(@NotNull TabPlayer refreshed, boolean force) {
|
||||
handle.refresh(refreshed);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String getFeatureName() {
|
||||
return parent.getFeatureName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers this line to the player
|
||||
*
|
||||
* @param player
|
||||
* player to register line to
|
||||
*/
|
||||
public void register(@NotNull TabPlayer player) {
|
||||
player.scoreboardData.lineProperties.put(this, new ScoreboardPlayerData.LineProperties(
|
||||
new Property(this, player, leftText),
|
||||
forcedPlayerNameStart,
|
||||
new Property(this, player, numberFormat),
|
||||
new Property(this, player, score)
|
||||
));
|
||||
player.getScoreboard().registerTeam(
|
||||
teamName,
|
||||
TabComponent.empty(),
|
||||
TabComponent.empty(),
|
||||
Scoreboard.NameVisibility.NEVER,
|
||||
Scoreboard.CollisionRule.NEVER,
|
||||
Collections.singletonList(forcedPlayerNameStart),
|
||||
0,
|
||||
TabTextColor.RESET.getLegacyColor()
|
||||
);
|
||||
handle.register(player);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the team prefix and suffix for the player
|
||||
*
|
||||
* @param player
|
||||
* player to update team for
|
||||
* @param prefix
|
||||
* new prefix to set
|
||||
* @param suffix
|
||||
* new suffix to set
|
||||
*/
|
||||
public void updateTeam(@NotNull TabPlayer player, @NotNull String prefix, @NotNull String suffix) {
|
||||
player.getScoreboard().updateTeam(
|
||||
teamName,
|
||||
parent.getManager().getCache().get(prefix),
|
||||
parent.getManager().getCache().get(suffix),
|
||||
TabTextColor.RESET.getLegacyColor()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the score for player. Created as a separate method to avoid duplication.
|
||||
*
|
||||
* @param player
|
||||
* player to set score for
|
||||
* @param scoreHolder
|
||||
* score holder to set score for
|
||||
*/
|
||||
public void setScore(@NotNull TabPlayer player, @NotNull String scoreHolder) {
|
||||
int score;
|
||||
if (!this.score.isEmpty()) {
|
||||
try {
|
||||
score = Integer.parseInt(player.scoreboardData.lineProperties.get(this).scoreProperty.get());
|
||||
} catch (NumberFormatException e) {
|
||||
score = -1;
|
||||
}
|
||||
} else {
|
||||
score = parent.getLines().size() + 1 - lineNumber;
|
||||
}
|
||||
player.getScoreboard().setScore(
|
||||
ScoreboardManagerImpl.OBJECTIVE_NAME,
|
||||
scoreHolder,
|
||||
score,
|
||||
null,
|
||||
parent.getManager().getNumberFormatCache().get(player.scoreboardData.lineProperties.get(this).numberFormatProperty.get())
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters this line to the player
|
||||
*
|
||||
* @param player
|
||||
* player to unregister line to
|
||||
*/
|
||||
public void unregister(@NotNull TabPlayer player) {
|
||||
player.getScoreboard().unregisterTeam(teamName);
|
||||
handle.unregister(player);
|
||||
}
|
||||
|
||||
private void splitText() {
|
||||
leftText = "";
|
||||
numberFormat = "";
|
||||
score = "";
|
||||
|
||||
String realText = text;
|
||||
if (realText.startsWith("Long|")) {
|
||||
realText = realText.substring(5);
|
||||
}
|
||||
String[] split = realText.split("\\|\\|", -1);
|
||||
if (split.length > 0) leftText = split[0];
|
||||
if (split.length > 1) numberFormat = split[1];
|
||||
if (split.length > 2) score = split[2];
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private ScoreboardLine createHandle() {
|
||||
if (leftText.isEmpty() && numberFormat.isEmpty()) {
|
||||
return new EmptyLine(this);
|
||||
}
|
||||
if (text.startsWith("Long|")) {
|
||||
return new LongLine(this);
|
||||
}
|
||||
if (text.contains("%") || text.contains("<")) {
|
||||
return new NoFlickerLine(this);
|
||||
}
|
||||
return new LongLine(this);
|
||||
}
|
||||
}
|
||||
-147
@@ -1,147 +0,0 @@
|
||||
package me.neznamy.tab.shared.features.scoreboard.lines;
|
||||
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.shared.Limitations;
|
||||
import me.neznamy.tab.shared.Property;
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.features.scoreboard.ScoreboardImpl;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Line of text with placeholder support
|
||||
* Limitations:
|
||||
* 1.5.x - 1.12.x: 28 - 32 characters (depending on used magic codes)
|
||||
* 1.13+: unlimited
|
||||
*/
|
||||
public class StableDynamicLine extends ScoreboardLine {
|
||||
|
||||
private final String[] EMPTY_ARRAY = new String[0];
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters
|
||||
*
|
||||
* @param parent
|
||||
* scoreboard this line belongs to
|
||||
* @param lineNumber
|
||||
* ID of this line
|
||||
* @param text
|
||||
* text to display
|
||||
*/
|
||||
public StableDynamicLine(@NonNull ScoreboardImpl parent, int lineNumber, @NonNull String text) {
|
||||
super(parent, lineNumber, text);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refresh(@NotNull TabPlayer refreshed, boolean force) {
|
||||
if (refreshed.scoreboardData.activeScoreboard != parent) return; //player has different scoreboard displayed
|
||||
String[] prefixSuffix = replaceText(refreshed, force, false);
|
||||
if (prefixSuffix.length == 0) return;
|
||||
updateTeam(refreshed, prefixSuffix[0], prefixSuffix[1]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void register(@NonNull TabPlayer p) {
|
||||
p.scoreboardData.lineProperties.put(this, new Property(this, p, text));
|
||||
getScoreRefresher().registerProperties(p);
|
||||
String[] prefixSuffix = replaceText(p, true, true);
|
||||
if (prefixSuffix.length == 0) return;
|
||||
addLine(p, forcedPlayerNameStart, prefixSuffix[0], prefixSuffix[1]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregister(@NonNull TabPlayer p) {
|
||||
if (p.scoreboardData.activeScoreboard == parent && !p.scoreboardData.lineProperties.get(this).get().isEmpty()) {
|
||||
removeLine(p, forcedPlayerNameStart);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies all placeholders and splits the result into prefix/suffix based on client version
|
||||
* or hides the line entirely if result is empty (and shows back once it's not)
|
||||
*
|
||||
* @param p
|
||||
* player to replace text for
|
||||
* @param force
|
||||
* if action should be done despite update seemingly not needed
|
||||
* @param suppressToggle
|
||||
* if line should NOT be removed despite being empty
|
||||
* @return array of 2 elements for prefix/suffix
|
||||
*/
|
||||
private String[] replaceText(TabPlayer p, boolean force, boolean suppressToggle) {
|
||||
Property scoreProperty = p.scoreboardData.lineProperties.get(this);
|
||||
if (scoreProperty == null) return EMPTY_ARRAY; //not actually loaded yet (force refresh called from placeholder manager register method)
|
||||
boolean emptyBefore = scoreProperty.get().isEmpty();
|
||||
if (!scoreProperty.update() && !force) return EMPTY_ARRAY;
|
||||
String replaced = scoreProperty.get();
|
||||
if (p.getVersion().getNetworkId() < ProtocolVersion.V1_16.getNetworkId()) {
|
||||
replaced = parent.getManager().getCache().get(replaced).toLegacyText(); //converting RGB to legacy here to avoid splitting in the middle of RGB code
|
||||
}
|
||||
String[] split = split(p, replaced);
|
||||
if (!replaced.isEmpty()) {
|
||||
if (emptyBefore) {
|
||||
//was "", now it is not
|
||||
addLine(p, forcedPlayerNameStart, split[0], split[1]);
|
||||
parent.recalculateScores(p);
|
||||
return EMPTY_ARRAY;
|
||||
} else {
|
||||
return split;
|
||||
}
|
||||
} else {
|
||||
if (!suppressToggle) {
|
||||
//new string is "", but before it was not
|
||||
removeLine(p, forcedPlayerNameStart);
|
||||
parent.recalculateScores(p);
|
||||
}
|
||||
return EMPTY_ARRAY;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits text into 2 values (prefix/suffix) based on client version and text itself
|
||||
*
|
||||
* @param p
|
||||
* player to split text for
|
||||
* @param text
|
||||
* text to split
|
||||
* @return array of 2 elements for prefix and suffix
|
||||
*/
|
||||
private String[] split(@NonNull TabPlayer p, @NonNull String text) {
|
||||
if (p.getVersion().getMinorVersion() >= 13 && !TAB.getInstance().getConfiguration().getConfig().isPacketEventsCompensation()) return new String[] {text, ""};
|
||||
int charLimit = Limitations.TEAM_PREFIX_SUFFIX_PRE_1_13;
|
||||
if (text.length() > charLimit) {
|
||||
StringBuilder prefix = new StringBuilder(text);
|
||||
StringBuilder suffix = new StringBuilder(text);
|
||||
prefix.setLength(charLimit);
|
||||
suffix.delete(0, charLimit);
|
||||
if (prefix.charAt(charLimit-1) == '§') {
|
||||
prefix.setLength(prefix.length()-1);
|
||||
suffix.insert(0, '§');
|
||||
}
|
||||
String prefixString = prefix.toString();
|
||||
suffix.insert(0, getLastColors(parent.getManager().getCache().get(prefixString).toLegacyText()));
|
||||
return new String[] {prefixString, suffix.toString()};
|
||||
} else {
|
||||
return new String[] {text, ""};
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setText(@NonNull String text) {
|
||||
ensureActive();
|
||||
initializeText(text);
|
||||
for (TabPlayer p : parent.getPlayers()) {
|
||||
p.scoreboardData.lineProperties.get(this).changeRawValue(text);
|
||||
String[] prefixSuffix = replaceText(p, true, true);
|
||||
if (prefixSuffix.length == 0) {
|
||||
if (text.isEmpty()) {
|
||||
prefixSuffix = new String[]{"", ""};
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
updateTeam(p, prefixSuffix[0], prefixSuffix[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,10 +2,14 @@ package me.neznamy.tab.shared.placeholders;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.Setter;
|
||||
import me.neznamy.tab.shared.features.types.RefreshableFeature;
|
||||
import me.neznamy.tab.shared.placeholders.types.TabPlaceholder;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* This class holds a reference to a placeholder.
|
||||
* Placeholder implementations may change at runtime, breaking all existing
|
||||
@@ -30,6 +34,16 @@ public class PlaceholderReference {
|
||||
@NotNull
|
||||
private TabPlaceholder handle;
|
||||
|
||||
/**
|
||||
* List of placeholders using this placeholder as a nested placeholder,
|
||||
* mutual tracking allows faster parent placeholder changes when a nested
|
||||
* placeholder changed value.
|
||||
*/
|
||||
private final List<PlaceholderReference> parents = new ArrayList<>();
|
||||
|
||||
/** Set of features using this placeholder, used to call refresh on them */
|
||||
private final Set<RefreshableFeature> usedByFeatures = Collections.synchronizedSet(new HashSet<>());
|
||||
|
||||
/**
|
||||
* Returns refresh interval of the placeholder.
|
||||
*
|
||||
@@ -38,4 +52,31 @@ public class PlaceholderReference {
|
||||
public int getRefresh() {
|
||||
return handle.getRefresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a feature that is using this placeholder, to allow
|
||||
* refreshing it when this placeholder changes.
|
||||
*
|
||||
* @param feature
|
||||
* Feature using this placeholder
|
||||
* @return true if feature was not already registered
|
||||
*/
|
||||
public boolean addUsedFeature(@NonNull RefreshableFeature feature) {
|
||||
return usedByFeatures.add(feature);
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal method used to mark placeholders as parents who use this placeholder
|
||||
* inside their outputs for faster updates.
|
||||
*
|
||||
* @param parent
|
||||
* parent placeholder using this placeholder in output
|
||||
*/
|
||||
public void addParent(@NonNull PlaceholderReference parent) {
|
||||
if (parent == this) return; // ???
|
||||
if (!parents.contains(parent)) {
|
||||
usedByFeatures.addAll(parent.usedByFeatures);
|
||||
parents.add(parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
+5
-14
@@ -1,11 +1,11 @@
|
||||
package me.neznamy.tab.shared.placeholders;
|
||||
|
||||
import lombok.Getter;
|
||||
import me.neznamy.tab.shared.chat.EnumChatFormat;
|
||||
import me.neznamy.tab.shared.features.PlaceholderManagerImpl;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
/**
|
||||
@@ -30,12 +30,6 @@ public class PlaceholderReplacementPattern {
|
||||
*/
|
||||
private final Map<float[], String> numberIntervals = new HashMap<>();
|
||||
|
||||
/** Set of all used placeholders in replacement values */
|
||||
@Getter private final Set<String> nestedPlaceholders = new HashSet<>();
|
||||
|
||||
/** Flag tracking if this replacement map is empty */
|
||||
private final boolean empty;
|
||||
|
||||
/**
|
||||
* Constructs new instance from given replacement map from config
|
||||
*
|
||||
@@ -45,15 +39,12 @@ public class PlaceholderReplacementPattern {
|
||||
* replacement map from config
|
||||
*/
|
||||
private PlaceholderReplacementPattern(@NotNull String identifier, @NotNull Map<Object, Object> map) {
|
||||
empty = map.isEmpty();
|
||||
for (Entry<Object, Object> entry : map.entrySet()) {
|
||||
String key = String.valueOf(entry.getKey());
|
||||
String value = String.valueOf(entry.getValue()).replace(identifier, "%value%");
|
||||
replacements.put(EnumChatFormat.color(key), EnumChatFormat.color(value));
|
||||
replacements.put(key, value);
|
||||
nestedPlaceholders.addAll(PlaceholderManagerImpl.detectPlaceholders(value));
|
||||
nestedPlaceholders.remove("%value%"); //not a real placeholder
|
||||
//snakeyaml converts yes & no to booleans, making them not work when used without "
|
||||
// SnakeYAML converts yes & no to booleans, making them not work when used without "
|
||||
if ("true".equals(key)) {
|
||||
replacements.put("yes", value);
|
||||
replacements.put("Yes", value);
|
||||
@@ -81,7 +72,7 @@ public class PlaceholderReplacementPattern {
|
||||
*/
|
||||
@NotNull
|
||||
public String findReplacement(@NotNull String output) {
|
||||
if (empty) return output;
|
||||
if (this == EMPTY) return output;
|
||||
String replacement = findReplacement0(output);
|
||||
if (replacement.contains("%value%")) {
|
||||
replacement = replacement.replace("%value%", output);
|
||||
|
||||
+1
-2
@@ -182,9 +182,8 @@ public class UniversalPlaceholderRegistry {
|
||||
manager.registerPlayerPlaceholder(TabConstants.Placeholder.animation(a.getName()), a.getRefresh(), p -> a.getMessage());
|
||||
}
|
||||
for (Entry<String, ConditionDefinition> condition : TAB.getInstance().getConfiguration().getConfig().getConditions().getConditions().entrySet()) {
|
||||
Condition c = new Condition(condition.getValue());
|
||||
Condition c = Condition.fromDefinition(condition.getValue());
|
||||
TAB.getInstance().getPlaceholderManager().getConditionManager().registerCondition(c);
|
||||
}
|
||||
manager.getConditionManager().finishSetups();
|
||||
}
|
||||
}
|
||||
+124
-112
@@ -1,7 +1,6 @@
|
||||
package me.neznamy.tab.shared.placeholders.conditions;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.features.PlaceholderManagerImpl;
|
||||
import me.neznamy.tab.shared.placeholders.PlaceholderReference;
|
||||
@@ -9,7 +8,6 @@ import me.neznamy.tab.shared.placeholders.types.RelationalPlaceholderImpl;
|
||||
import me.neznamy.tab.shared.placeholders.types.TabPlaceholder;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@@ -24,7 +22,6 @@ import java.util.stream.Collectors;
|
||||
* condition types that must be met in order to display specified
|
||||
* text or make a condition requirement for a visual to be displayed.
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@Getter
|
||||
public class Condition {
|
||||
|
||||
@@ -53,68 +50,36 @@ public class Condition {
|
||||
*/
|
||||
private int refresh = -1;
|
||||
|
||||
/** List of all placeholders used inside this condition */
|
||||
@NotNull
|
||||
private final List<PlaceholderReference> placeholdersInConditions = new ArrayList<>();
|
||||
|
||||
@Nullable
|
||||
private TabPlaceholder placeholder;
|
||||
|
||||
@Nullable
|
||||
private RelationalPlaceholderImpl relationalPlaceholder;
|
||||
|
||||
/**
|
||||
* Constructs new instance with given definition and registers
|
||||
* this condition to list as well as the placeholder.
|
||||
* Constructs new instance with given parameters and analyzes content
|
||||
* to determine refresh interval.
|
||||
*
|
||||
* @param definition
|
||||
* Condition definition from configuration
|
||||
* @param name
|
||||
* Name of this condition defined in configuration
|
||||
* @param expressions
|
||||
* All defined expressions inside this condition
|
||||
* @param type
|
||||
* Condition type, {@code true} for AND type and {@code false} for OR type
|
||||
* @param yes
|
||||
* Text to display if condition passed
|
||||
* @param no
|
||||
* Text to display if condition failed
|
||||
* @param primitive
|
||||
* Whether this is a primitive condition (TrueCondition or FalseCondition with no nested placeholders)
|
||||
*/
|
||||
public Condition(@NotNull ConditionsSection.ConditionDefinition definition) {
|
||||
type = definition.isType();
|
||||
name = definition.getName();
|
||||
expressions = definition.getConditions().stream().map(expressionString -> {
|
||||
ConditionalExpression expression = ConditionalExpression.compile(expressionString.trim());
|
||||
if (expression == null) {
|
||||
TAB.getInstance().getConfigHelper().startup().startupWarn("Line \"" + expressionString + "\" is not a valid conditional expression.");
|
||||
}
|
||||
return expression;
|
||||
}).filter(Objects::nonNull).collect(Collectors.toList());
|
||||
yes = definition.getYes();
|
||||
no = definition.getNo();
|
||||
analyzeContent();
|
||||
}
|
||||
public Condition(@NotNull String name, @NotNull List<ConditionalExpression> expressions, boolean type, @NotNull String yes, @NotNull String no, boolean primitive) {
|
||||
this.name = name;
|
||||
this.expressions = expressions;
|
||||
this.type = type;
|
||||
this.yes = yes;
|
||||
this.no = no;
|
||||
|
||||
/**
|
||||
* Constructs new instance from a condition string in short format.
|
||||
* This constructor is used for anonymous conditions only.
|
||||
*
|
||||
* @param shortFormat
|
||||
* Condition in short format
|
||||
*/
|
||||
public Condition(@NotNull String shortFormat) {
|
||||
name = "AnonymousCondition[" + shortFormat + "]";
|
||||
yes = "true";
|
||||
no = "false";
|
||||
List<String> conditions;
|
||||
if (shortFormat.contains(";")) {
|
||||
type = true;
|
||||
conditions = Arrays.asList(shortFormat.split(";"));
|
||||
} else {
|
||||
type = false;
|
||||
conditions = splitString(shortFormat);
|
||||
}
|
||||
expressions = conditions.stream().map(expressionString -> {
|
||||
ConditionalExpression expression = ConditionalExpression.compile(expressionString.trim());
|
||||
if (expression == null) {
|
||||
TAB.getInstance().getConfigHelper().startup().startupWarn("Line \"" + expressionString + "\" is not a valid conditional expression.");
|
||||
}
|
||||
return expression;
|
||||
}).filter(Objects::nonNull).collect(Collectors.toList());
|
||||
analyzeContent();
|
||||
}
|
||||
if (primitive) return;
|
||||
|
||||
private void analyzeContent() {
|
||||
List<PlaceholderReference> placeholdersInConditions = new ArrayList<>();
|
||||
PlaceholderManagerImpl manager = TAB.getInstance().getPlaceholderManager();
|
||||
placeholdersInConditions.addAll(manager.detectPlaceholders(yes).stream().map(manager::getPlaceholderReference).collect(Collectors.toList()));
|
||||
placeholdersInConditions.addAll(manager.detectPlaceholders(no).stream().map(manager::getPlaceholderReference).collect(Collectors.toList()));
|
||||
for (ConditionalExpression expression : expressions) {
|
||||
if (expression instanceof Permission || expression instanceof NotPermission) {
|
||||
int permissionRefresh = TAB.getInstance().getConfiguration().getConfig().getPermissionRefreshInterval();
|
||||
@@ -125,50 +90,11 @@ public class Condition {
|
||||
placeholdersInConditions.addAll(Arrays.stream(comparator.getRightSide().getPlaceholders()).map(ConditionPlaceholder::getRealPlaceholder).collect(Collectors.toList()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits string using `|` symbol except cases where it is used as |- or -|.
|
||||
* This method was 100% made by ChatGPT!
|
||||
*
|
||||
* @param input
|
||||
* String to split
|
||||
* @return Split string
|
||||
*/
|
||||
private List<String> splitString(@NotNull String input) {
|
||||
List<String> result = new ArrayList<>();
|
||||
|
||||
// Define a regular expression pattern to match the desired delimiter
|
||||
Pattern pattern = Pattern.compile("(?<!-)[|](?!-)");
|
||||
|
||||
// Use a Matcher to split the input string
|
||||
Matcher matcher = pattern.matcher(input);
|
||||
int start = 0;
|
||||
|
||||
while (matcher.find()) {
|
||||
int end = matcher.start();
|
||||
result.add(input.substring(start, end));
|
||||
start = matcher.end();
|
||||
}
|
||||
|
||||
// Add the remaining part of the string
|
||||
result.add(input.substring(start));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures refresh interval and registers nested placeholders
|
||||
*/
|
||||
public void finishSetup() {
|
||||
for (PlaceholderReference placeholder : placeholdersInConditions) {
|
||||
if (placeholder.getRefresh() != -1 && (placeholder.getRefresh() < refresh || refresh == -1)) {
|
||||
refresh = placeholder.getRefresh();
|
||||
}
|
||||
}
|
||||
PlaceholderManagerImpl manager = TAB.getInstance().getPlaceholderManager();
|
||||
manager.addUsedPlaceholderReferences(placeholdersInConditions);
|
||||
String identifier = getPlaceholderIdentifier();
|
||||
String relIdentifier = getRelationalPlaceholderIdentifier();
|
||||
TabPlaceholder placeholder;
|
||||
RelationalPlaceholderImpl relationalPlaceholder;
|
||||
if (hasRelationalContent()) {
|
||||
relationalPlaceholder = manager.registerRelationalPlaceholder(
|
||||
relIdentifier,
|
||||
@@ -193,15 +119,11 @@ public class Condition {
|
||||
);
|
||||
}
|
||||
for (PlaceholderReference reference : placeholdersInConditions) {
|
||||
TabPlaceholder placeholder = reference.getHandle();
|
||||
placeholder.addParent(this.placeholder);
|
||||
reference.addParent(placeholder.getReference());
|
||||
if (hasRelationalContent()) {
|
||||
placeholder.addParent(relationalPlaceholder);
|
||||
reference.addParent(relationalPlaceholder.getReference());
|
||||
}
|
||||
this.placeholder.addChild(placeholder);
|
||||
relationalPlaceholder.addChild(placeholder);
|
||||
}
|
||||
TAB.getInstance().getPlaceholderManager().addUsedPlaceholderReferences(placeholdersInConditions);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -260,13 +182,14 @@ public class Condition {
|
||||
*/
|
||||
@NotNull
|
||||
public Condition invert() {
|
||||
return new Condition(new ConditionsSection.ConditionDefinition(
|
||||
return new Condition(
|
||||
"inverted:" + name,
|
||||
expressions.stream().map(expr -> expr.invert().toShortFormat()).collect(Collectors.toList()),
|
||||
expressions.stream().map(ConditionalExpression::invert).collect(Collectors.toList()),
|
||||
!type,
|
||||
yes,
|
||||
no
|
||||
));
|
||||
no,
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -312,4 +235,93 @@ public class Condition {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new condition instance based on definition.
|
||||
*
|
||||
* @param definition
|
||||
* Condition definition from configuration
|
||||
* @return A new Condition instance based on the provided definition
|
||||
*/
|
||||
@NotNull
|
||||
public static Condition fromDefinition(@NotNull ConditionsSection.ConditionDefinition definition) {
|
||||
return new Condition(
|
||||
definition.getName(),
|
||||
definition.getConditions().stream().map(expressionString -> {
|
||||
ConditionalExpression expression = ConditionalExpression.compile(expressionString.trim());
|
||||
if (expression == null) {
|
||||
TAB.getInstance().getConfigHelper().startup().startupWarn("Line \"" + expressionString + "\" is not a valid conditional expression.");
|
||||
}
|
||||
return expression;
|
||||
}).filter(Objects::nonNull).collect(Collectors.toList()),
|
||||
definition.isType(),
|
||||
definition.getYes(),
|
||||
definition.getNo(),
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new condition instance from a short format string.
|
||||
*
|
||||
* @param shortFormat
|
||||
* Condition in short format
|
||||
* @return A new Condition instance based on the provided short format string
|
||||
*/
|
||||
@NotNull
|
||||
public static Condition fromShortFormat(@NotNull String shortFormat) {
|
||||
boolean type;
|
||||
List<String> conditions;
|
||||
if (shortFormat.contains(";")) {
|
||||
type = true;
|
||||
conditions = Arrays.asList(shortFormat.split(";"));
|
||||
} else {
|
||||
type = false;
|
||||
conditions = splitString(shortFormat);
|
||||
}
|
||||
return new Condition(
|
||||
"AnonymousCondition[" + shortFormat + "]",
|
||||
conditions.stream().map(expressionString -> {
|
||||
ConditionalExpression expression = ConditionalExpression.compile(expressionString.trim());
|
||||
if (expression == null) {
|
||||
TAB.getInstance().getConfigHelper().startup().startupWarn("Line \"" + expressionString + "\" is not a valid conditional expression.");
|
||||
}
|
||||
return expression;
|
||||
}).filter(Objects::nonNull).collect(Collectors.toList()),
|
||||
type,
|
||||
"true",
|
||||
"false",
|
||||
false
|
||||
);
|
||||
}
|
||||
/**
|
||||
* Splits string using `|` symbol except cases where it is used as |- or -|.
|
||||
*
|
||||
* @param input
|
||||
* String to split
|
||||
* @return Split string
|
||||
*/
|
||||
@NotNull
|
||||
private static List<String> splitString(@NotNull String input) {
|
||||
List<String> result = new ArrayList<>();
|
||||
|
||||
// Define a regular expression pattern to match the desired delimiter
|
||||
Pattern pattern = Pattern.compile("(?<!-)[|](?!-)");
|
||||
|
||||
// Use a Matcher to split the input string
|
||||
Matcher matcher = pattern.matcher(input);
|
||||
int start = 0;
|
||||
|
||||
while (matcher.find()) {
|
||||
int end = matcher.start();
|
||||
result.add(input.substring(start, end));
|
||||
start = matcher.end();
|
||||
}
|
||||
|
||||
// Add the remaining part of the string
|
||||
result.add(input.substring(start));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
+1
-13
@@ -53,21 +53,9 @@ public class ConditionManager {
|
||||
} else if (registeredConditions.containsKey(anonVersion)) {
|
||||
return registeredConditions.get(anonVersion);
|
||||
} else {
|
||||
Condition c = new Condition(string);
|
||||
c.finishSetup();
|
||||
Condition c = Condition.fromShortFormat(string);
|
||||
registerCondition(c);
|
||||
return c;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks all placeholders used in the condition as used and registers them.
|
||||
* Using a separate method to avoid premature registration of nested conditional placeholders
|
||||
* before they are registered properly.
|
||||
*/
|
||||
public void finishSetups() {
|
||||
for (Condition c : registeredConditions.values()) {
|
||||
c.finishSetup();
|
||||
}
|
||||
}
|
||||
}
|
||||
+5
-5
@@ -25,17 +25,17 @@ public abstract class ConditionalExpression {
|
||||
|
||||
static {
|
||||
registerConditionType(">=", Operator.GREATER_THAN_OR_EQUAL);
|
||||
registerConditionType(">", Operator.GREATER_THAN);
|
||||
registerConditionType("!=", Operator.NOT_EQUALS);
|
||||
registerConditionType("<=", Operator.LESS_THAN_OR_EQUAL);
|
||||
registerConditionType("=", Operator.EQUALS);
|
||||
registerConditionType("!<-", Operator.NOT_CONTAINS);
|
||||
registerConditionType("!|-", Operator.NOT_STARTS_WITH);
|
||||
registerConditionType("!-|", Operator.NOT_ENDS_WITH);
|
||||
registerConditionType("<-", Operator.CONTAINS);
|
||||
registerConditionType("<", Operator.LESS_THAN);
|
||||
registerConditionType("-|", Operator.ENDS_WITH);
|
||||
registerConditionType("|-", Operator.STARTS_WITH);
|
||||
registerConditionType("!=", Operator.NOT_EQUALS);
|
||||
registerConditionType("=", Operator.EQUALS);
|
||||
registerConditionType("<-", Operator.CONTAINS);
|
||||
registerConditionType("<", Operator.LESS_THAN);
|
||||
registerConditionType(">", Operator.GREATER_THAN);
|
||||
conditionTypes.put("!permission:", line -> {
|
||||
String node = splitAndTrim(line, ":")[1];
|
||||
return new NotPermission(node);
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ public class FalseCondition extends Condition {
|
||||
public static final FalseCondition INSTANCE = new FalseCondition();
|
||||
|
||||
private FalseCondition() {
|
||||
super("false", Collections.emptyList(), true, "true", "false");
|
||||
super("false", Collections.emptyList(), true, "true", "false", true);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ public class TrueCondition extends Condition {
|
||||
public static final TrueCondition INSTANCE = new TrueCondition();
|
||||
|
||||
private TrueCondition() {
|
||||
super("true", Collections.emptyList(), true, "true", "false");
|
||||
super("true", Collections.emptyList(), true, "true", "false", true);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+36
-19
@@ -23,7 +23,8 @@ import java.util.function.Function;
|
||||
public class PlayerPlaceholderImpl extends TabPlaceholder implements PlayerPlaceholder {
|
||||
|
||||
/** Placeholder function returning fresh output on request */
|
||||
@NonNull private final Function<me.neznamy.tab.api.TabPlayer, String> function;
|
||||
@NonNull
|
||||
private final Function<me.neznamy.tab.api.TabPlayer, String> function;
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters
|
||||
@@ -48,10 +49,10 @@ public class PlayerPlaceholderImpl extends TabPlaceholder implements PlayerPlace
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateValue(@NonNull me.neznamy.tab.api.TabPlayer player, @Nullable String value) {
|
||||
if (hasValueChanged((TabPlayer) player, value, true)) {
|
||||
public void updateValue(@NonNull me.neznamy.tab.api.TabPlayer player, @Nullable String returnedValue) {
|
||||
if (hasValueChanged((TabPlayer) player, returnedValue, true)) {
|
||||
if (!player.isLoaded()) return; // Updated on join
|
||||
for (RefreshableFeature r : getUsedByFeatures()) {
|
||||
for (RefreshableFeature r : reference.getUsedByFeatures()) {
|
||||
TimedCaughtTask task = new TimedCaughtTask(TAB.getInstance().getCpu(), () -> r.refresh((TabPlayer) player, false),
|
||||
r.getFeatureName(), r.getRefreshDisplayName());
|
||||
if (r instanceof CustomThreaded) {
|
||||
@@ -77,7 +78,7 @@ public class PlayerPlaceholderImpl extends TabPlaceholder implements PlayerPlace
|
||||
Set<RefreshableFeature> features = new HashSet<>();
|
||||
for (Map.Entry<PlayerPlaceholderImpl, String> entry : values.entrySet()) {
|
||||
if (entry.getKey().hasValueChanged(player, entry.getValue(), true)) {
|
||||
features.addAll(entry.getKey().getUsedByFeatures());
|
||||
features.addAll(entry.getKey().getReference().getUsedByFeatures());
|
||||
}
|
||||
}
|
||||
if (!player.isLoaded()) return;
|
||||
@@ -97,20 +98,21 @@ public class PlayerPlaceholderImpl extends TabPlaceholder implements PlayerPlace
|
||||
*
|
||||
* @param p
|
||||
* Player to update value for
|
||||
* @param value
|
||||
* New value
|
||||
* @param returnedValue
|
||||
* New value returned by the placeholder function
|
||||
* @param updateParents
|
||||
* Whether parents should be updated or not
|
||||
* @return {@code true} if value changed, {@code false} if not
|
||||
*/
|
||||
public boolean hasValueChanged(@NotNull TabPlayer p, @Nullable String value, boolean updateParents) {
|
||||
if (value == null) return false; //bridge placeholders, they are updated using updateValue method
|
||||
if (ERROR_VALUE.equals(value)) return false;
|
||||
String newValue = replacements.findReplacement(setPlaceholders(value, p));
|
||||
String lastValue = p.lastPlaceholderValues.put(this, newValue);
|
||||
if (!newValue.equals(lastValue)) {
|
||||
public boolean hasValueChanged(@NotNull TabPlayer p, @Nullable String returnedValue, boolean updateParents) {
|
||||
if (returnedValue == null || returnedValue.equals(ERROR_VALUE)) return false;
|
||||
p.lastPlaceholderReturnedValues.put(this, returnedValue);
|
||||
String newEvaluatedValue = evaluate(returnedValue, p);
|
||||
String lastEvaluatedValue = p.lastPlaceholderEvaluatedValues.put(this, newEvaluatedValue);
|
||||
|
||||
if (!newEvaluatedValue.equals(lastEvaluatedValue)) {
|
||||
if (updateParents) updateParents(p);
|
||||
p.expansionData.setPlaceholderValue(identifier, newValue);
|
||||
p.expansionData.setPlaceholderValue(identifier, newEvaluatedValue);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -118,25 +120,40 @@ public class PlayerPlaceholderImpl extends TabPlaceholder implements PlayerPlace
|
||||
|
||||
@Override
|
||||
public void updateFromNested(@NonNull TabPlayer player) {
|
||||
hasValueChanged(player, request(player), true);
|
||||
updateValue(player, request(player));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public synchronized String getLastValue(@Nullable TabPlayer p) {
|
||||
if (p == null) return identifier;
|
||||
String value = p.lastPlaceholderValues.get(this);
|
||||
String value = p.lastPlaceholderEvaluatedValues.get(this);
|
||||
if (value != null) return value;
|
||||
|
||||
// Value not present, initialize
|
||||
p.lastPlaceholderValues.put(this, replacements.findReplacement(identifier));
|
||||
p.lastPlaceholderReturnedValues.put(this, identifier);
|
||||
p.lastPlaceholderEvaluatedValues.put(this, replacements.findReplacement(identifier));
|
||||
hasValueChanged(p, request(p), false);
|
||||
return p.lastPlaceholderValues.get(this);
|
||||
return p.lastPlaceholderEvaluatedValues.get(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String getLastReturnedValue(@Nullable TabPlayer player) {
|
||||
if (player == null) return identifier;
|
||||
String value = player.lastPlaceholderReturnedValues.get(this);
|
||||
if (value != null) return value;
|
||||
|
||||
// Value not present, initialize
|
||||
player.lastPlaceholderReturnedValues.put(this, identifier);
|
||||
player.lastPlaceholderEvaluatedValues.put(this, replacements.findReplacement(identifier));
|
||||
hasValueChanged(player, request(player), false);
|
||||
return player.lastPlaceholderReturnedValues.get(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String getLastValueSafe(@NotNull TabPlayer player) {
|
||||
return player.lastPlaceholderValues.getOrDefault(this, identifier);
|
||||
return player.lastPlaceholderEvaluatedValues.getOrDefault(this, identifier);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+9
-4
@@ -53,7 +53,7 @@ public class RelationalPlaceholderImpl extends TabPlaceholder implements Relatio
|
||||
@Override
|
||||
public void updateValue(@NonNull me.neznamy.tab.api.TabPlayer viewer, @NonNull me.neznamy.tab.api.TabPlayer target, @Nullable String value) {
|
||||
if (hasValueChanged((TabPlayer) viewer, (TabPlayer) target, value)) {
|
||||
for (RefreshableFeature r : getUsedByFeatures()) {
|
||||
for (RefreshableFeature r : reference.getUsedByFeatures()) {
|
||||
TimedCaughtTask task = new TimedCaughtTask(TAB.getInstance().getCpu(), () -> r.refresh((TabPlayer) target, true),
|
||||
r.getFeatureName(), r.getRefreshDisplayName());
|
||||
if (r instanceof CustomThreaded) {
|
||||
@@ -96,7 +96,7 @@ public class RelationalPlaceholderImpl extends TabPlaceholder implements Relatio
|
||||
String s = replacements.findReplacement(String.valueOf(value));
|
||||
viewer.lastRelationalValues.computeIfAbsent(this, v -> Collections.synchronizedMap(new WeakHashMap<>())).put(target, s);
|
||||
if (!target.isLoaded()) return; // Updated on join
|
||||
for (RefreshableFeature f : getUsedByFeatures()) {
|
||||
for (RefreshableFeature f : reference.getUsedByFeatures()) {
|
||||
TimedCaughtTask task = new TimedCaughtTask(TAB.getInstance().getCpu(), () -> f.refresh(target, true),
|
||||
f.getFeatureName(), f.getRefreshDisplayName());
|
||||
if (f instanceof CustomThreaded) {
|
||||
@@ -108,7 +108,7 @@ public class RelationalPlaceholderImpl extends TabPlaceholder implements Relatio
|
||||
updateParents(target);
|
||||
}
|
||||
if (!viewer.isLoaded()) return; // Updated on join
|
||||
for (RefreshableFeature f : getUsedByFeatures()) {
|
||||
for (RefreshableFeature f : reference.getUsedByFeatures()) {
|
||||
TimedCaughtTask task = new TimedCaughtTask(TAB.getInstance().getCpu(), () -> f.refresh(viewer, true),
|
||||
f.getFeatureName(), f.getRefreshDisplayName());
|
||||
if (f instanceof CustomThreaded) {
|
||||
@@ -144,7 +144,7 @@ public class RelationalPlaceholderImpl extends TabPlaceholder implements Relatio
|
||||
private String retrieveValue(@NotNull TabPlayer viewer, @NotNull TabPlayer target) {
|
||||
String output = request(viewer, target);
|
||||
if (output == null) output = identifier;
|
||||
return replacements.findReplacement(output);
|
||||
return evaluate(output, target);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -152,6 +152,11 @@ public class RelationalPlaceholderImpl extends TabPlaceholder implements Relatio
|
||||
return identifier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String getLastReturnedValue(@Nullable TabPlayer player) {
|
||||
return identifier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String getLastValueSafe(@NotNull TabPlayer player) {
|
||||
return identifier;
|
||||
|
||||
+23
-11
@@ -20,12 +20,17 @@ import java.util.function.Supplier;
|
||||
public class ServerPlaceholderImpl extends TabPlaceholder implements ServerPlaceholder {
|
||||
|
||||
/** Placeholder function returning fresh output on request */
|
||||
@NotNull
|
||||
private final Supplier<String> supplier;
|
||||
|
||||
/** Last known output of the placeholder */
|
||||
@Getter
|
||||
@NotNull
|
||||
private String lastValue = identifier;
|
||||
private String lastReturnedValue = identifier;
|
||||
|
||||
/** Last known value with all nested placeholders replaced and output replacements applied */
|
||||
@NotNull
|
||||
private String lastEvaluatedValue = identifier;
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters
|
||||
@@ -53,7 +58,7 @@ public class ServerPlaceholderImpl extends TabPlaceholder implements ServerPlace
|
||||
@Override
|
||||
public void updateValue(@Nullable String value) {
|
||||
if (hasValueChanged(value)) {
|
||||
for (RefreshableFeature r : getUsedByFeatures()) {
|
||||
for (RefreshableFeature r : reference.getUsedByFeatures()) {
|
||||
for (TabPlayer all : TAB.getInstance().getOnlinePlayers()) {
|
||||
if (!all.isLoaded()) return; // Updated on join
|
||||
TimedCaughtTask task = new TimedCaughtTask(TAB.getInstance().getCpu(), () -> r.refresh(all, false), r.getFeatureName(), r.getRefreshDisplayName());
|
||||
@@ -67,15 +72,16 @@ public class ServerPlaceholderImpl extends TabPlaceholder implements ServerPlace
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasValueChanged(@Nullable String value) {
|
||||
if (value == null) return false;
|
||||
String newValue = setPlaceholders(replacements.findReplacement(value), null);
|
||||
public boolean hasValueChanged(@Nullable String returnedValue) {
|
||||
if (returnedValue == null || returnedValue.equals(ERROR_VALUE)) return false;
|
||||
lastReturnedValue = returnedValue;
|
||||
String newEvaluatedValue = evaluate(returnedValue, null);
|
||||
|
||||
if (!ERROR_VALUE.equals(newValue) && !lastValue.equals(newValue)) {
|
||||
lastValue = newValue;
|
||||
if (!lastEvaluatedValue.equals(newEvaluatedValue)) {
|
||||
lastEvaluatedValue = newEvaluatedValue;
|
||||
for (TabPlayer player : TAB.getInstance().getOnlinePlayers()) {
|
||||
updateParents(player);
|
||||
player.expansionData.setPlaceholderValue(identifier, newValue);
|
||||
player.expansionData.setPlaceholderValue(identifier, newEvaluatedValue);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -84,19 +90,25 @@ public class ServerPlaceholderImpl extends TabPlaceholder implements ServerPlace
|
||||
|
||||
@Override
|
||||
public void updateFromNested(@NonNull TabPlayer unused) {
|
||||
hasValueChanged(request());
|
||||
updateValue(request());
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String getLastValue(@Nullable TabPlayer p) {
|
||||
return lastValue;
|
||||
return lastEvaluatedValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String getLastReturnedValue(@Nullable TabPlayer player) {
|
||||
return lastReturnedValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String getLastValueSafe(@NotNull TabPlayer player) {
|
||||
return lastValue;
|
||||
return lastEvaluatedValue;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,18 +2,20 @@ package me.neznamy.tab.shared.placeholders.types;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.Setter;
|
||||
import me.neznamy.tab.api.placeholder.Placeholder;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.features.PlaceholderManagerImpl;
|
||||
import me.neznamy.tab.shared.features.types.RefreshableFeature;
|
||||
import me.neznamy.tab.shared.placeholders.PlaceholderIdentifier;
|
||||
import me.neznamy.tab.shared.placeholders.PlaceholderReference;
|
||||
import me.neznamy.tab.shared.placeholders.PlaceholderReplacementPattern;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* General collection of variables and functions shared between all placeholder types
|
||||
@@ -36,17 +38,8 @@ public abstract class TabPlaceholder implements Placeholder {
|
||||
/** Configured placeholder output replacements */
|
||||
@NonNull protected final PlaceholderReplacementPattern replacements;
|
||||
|
||||
/**
|
||||
* List of placeholders using this placeholder as a nested placeholder,
|
||||
* mutual tracking allows faster parent placeholder changes when a nested
|
||||
* placeholder changed value.
|
||||
*/
|
||||
protected final List<TabPlaceholder> parents = new ArrayList<>();
|
||||
|
||||
private final List<TabPlaceholder> children = new ArrayList<>();
|
||||
|
||||
/** Set of features using this placeholder, used to call refresh on them */
|
||||
private final Set<RefreshableFeature> usedByFeatures = Collections.synchronizedSet(new HashSet<>());
|
||||
@Setter
|
||||
protected PlaceholderReference reference;
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters and loads placeholder output replacements
|
||||
@@ -66,14 +59,11 @@ public abstract class TabPlaceholder implements Placeholder {
|
||||
this.refresh = refresh;
|
||||
Map<Object, Object> map = TAB.getInstance().getConfiguration().getConfig().getReplacements().getValues().get(identifier);
|
||||
replacements = map == null ? PlaceholderReplacementPattern.EMPTY : PlaceholderReplacementPattern.create(identifier, map);
|
||||
for (String nested : replacements.getNestedPlaceholders()) {
|
||||
TAB.getInstance().getPlaceholderManager().getPlaceholder(nested).addParent(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the placeholder for defined player, applying all nested placeholders
|
||||
* found in output.
|
||||
* found in returned placeholder output as well as output replacements.
|
||||
*
|
||||
* @param player
|
||||
* player to parse placeholder for
|
||||
@@ -81,9 +71,29 @@ public abstract class TabPlaceholder implements Placeholder {
|
||||
*/
|
||||
@NotNull
|
||||
public String parse(@Nullable TabPlayer player) {
|
||||
String value = getLastValue(player);
|
||||
if (!value.contains("%") && !value.contains("<")) return value; // No nested placeholders, return immediately
|
||||
return setPlaceholders(value, player);
|
||||
return evaluate(getLastReturnedValue(player), player);
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluates given placeholder result string by applying all nested placeholders and output replacements for defined player.
|
||||
*
|
||||
* @param returnedValue
|
||||
* placeholder result string to evaluate
|
||||
* @param player
|
||||
* player to parse placeholders for
|
||||
* @return evaluated string with all nested placeholders and output replacements applied
|
||||
*/
|
||||
@NotNull
|
||||
protected String evaluate(@NonNull String returnedValue, @Nullable TabPlayer player) {
|
||||
String value = returnedValue;
|
||||
if (value.contains("%") || value.contains("<")) {
|
||||
value = setPlaceholders(value, player);
|
||||
}
|
||||
value = replacements.findReplacement(value);
|
||||
if (value.contains("%") || value.contains("<")) {
|
||||
value = setPlaceholders(value, player);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -100,40 +110,17 @@ public abstract class TabPlaceholder implements Placeholder {
|
||||
String string = value;
|
||||
if (identifier.equals(string)) return string; // Placeholder returned itself (probably invalid)
|
||||
|
||||
// Known children first for better performance thanks to skipped checks
|
||||
for (TabPlaceholder child : children) {
|
||||
if (string.contains(child.identifier)) {
|
||||
string = string.replace(child.identifier, child.parse(player));
|
||||
}
|
||||
}
|
||||
|
||||
for (String s : PlaceholderManagerImpl.detectPlaceholders(string)) {
|
||||
if (s.equals(identifier)) continue; // Prevent infinite loop when placeholder returns itself
|
||||
if (PlaceholderIdentifier.isRelational(s)) continue; // Relational placeholders are handled separately
|
||||
if ((identifier.startsWith("%sync:") && ("%" + identifier.substring(6)).equals(s))) continue; // Self, but as sync variant
|
||||
TabPlaceholder nested = TAB.getInstance().getPlaceholderManager().getPlaceholder(s);
|
||||
nested.addParent(this);
|
||||
addChild(nested);
|
||||
string = string.replace(s, nested.parse(player));
|
||||
PlaceholderReference nested = TAB.getInstance().getPlaceholderManager().getPlaceholderReference(s);
|
||||
nested.addParent(reference);
|
||||
string = string.replace(s, nested.getHandle().parse(player));
|
||||
}
|
||||
return string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal method used to mark placeholders as parents who use this placeholder
|
||||
* inside their outputs for faster updates.
|
||||
*
|
||||
* @param parent
|
||||
* parent placeholder using this placeholder in output
|
||||
*/
|
||||
public void addParent(@NonNull TabPlaceholder parent) {
|
||||
if (parent == this) return; // ???
|
||||
if (!parents.contains(parent)) {
|
||||
usedByFeatures.addAll(parent.usedByFeatures);
|
||||
parents.add(parent);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates all placeholders that use this placeholder
|
||||
* as a nested placeholder
|
||||
@@ -142,37 +129,11 @@ public abstract class TabPlaceholder implements Placeholder {
|
||||
* Player to update placeholders for.
|
||||
*/
|
||||
public void updateParents(@NonNull TabPlayer player) {
|
||||
if (parents.isEmpty()) return;
|
||||
for (TabPlaceholder pl : new ArrayList<>(parents)) {
|
||||
pl.updateFromNested(player);
|
||||
pl.updateParents(player);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a feature that is using this placeholder, to allow
|
||||
* refreshing it when this placeholder changes.
|
||||
*
|
||||
* @param feature
|
||||
* Feature using this placeholder
|
||||
* @return true if feature was not already registered
|
||||
*/
|
||||
public boolean addUsedFeature(@NonNull RefreshableFeature feature) {
|
||||
boolean added = usedByFeatures.add(feature);
|
||||
if (added) {
|
||||
for (TabPlaceholder child : children) {
|
||||
child.addUsedFeature(feature);
|
||||
}
|
||||
}
|
||||
return added;
|
||||
}
|
||||
|
||||
public void addChild(@NonNull TabPlaceholder child) {
|
||||
if (!children.contains(child)) {
|
||||
children.add(child);
|
||||
for (RefreshableFeature feature : usedByFeatures) {
|
||||
child.addUsedFeature(feature);
|
||||
}
|
||||
if (reference == null) return;
|
||||
if (reference.getParents().isEmpty()) return;
|
||||
for (PlaceholderReference pl : new ArrayList<>(reference.getParents())) {
|
||||
pl.getHandle().updateFromNested(player);
|
||||
pl.getHandle().updateParents(player);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,6 +154,17 @@ public abstract class TabPlaceholder implements Placeholder {
|
||||
*/
|
||||
public abstract @NotNull String getLastValue(@Nullable TabPlayer player);
|
||||
|
||||
/**
|
||||
* Returns the last returned value of the placeholder for the given player, without applying any replacements or nested placeholders.
|
||||
* This is the last known raw result of the given placeholder function.
|
||||
*
|
||||
* @param player
|
||||
* player to get value of
|
||||
* @return Last known raw value for specified player
|
||||
*/
|
||||
@NotNull
|
||||
public abstract String getLastReturnedValue(@Nullable TabPlayer player);
|
||||
|
||||
/**
|
||||
* Returns last known value of defined player without refreshing it if not present.
|
||||
*
|
||||
|
||||
@@ -132,8 +132,11 @@ public abstract class TabPlayer implements me.neznamy.tab.api.TabPlayer {
|
||||
/** LuckPerms user for fast access */
|
||||
@Nullable public User luckPermsUser;
|
||||
|
||||
/** Last known values for each player placeholder as returned by function */
|
||||
public final Map<PlayerPlaceholder, String> lastPlaceholderReturnedValues = new ConcurrentHashMap<>();
|
||||
|
||||
/** Last known values for each player placeholder after applying replacements and nested placeholders */
|
||||
public final Map<PlayerPlaceholder, String> lastPlaceholderValues = new ConcurrentHashMap<>();
|
||||
public final Map<PlayerPlaceholder, String> lastPlaceholderEvaluatedValues = new ConcurrentHashMap<>();
|
||||
|
||||
/** Last known values for each relational placeholder after applying replacements and nested placeholders */
|
||||
public final Map<RelationalPlaceholder, Map<TabPlayer, String>> lastRelationalValues = new ConcurrentHashMap<>();
|
||||
|
||||
@@ -22,6 +22,7 @@ import me.neznamy.tab.shared.chat.component.TabComponent;
|
||||
import me.neznamy.tab.shared.chat.component.TabTextComponent;
|
||||
import me.neznamy.tab.shared.features.injection.PipelineInjector;
|
||||
import me.neznamy.tab.shared.features.proxy.ProxySupport;
|
||||
import me.neznamy.tab.shared.features.scoreboard.ScoreboardManagerImpl;
|
||||
import me.neznamy.tab.shared.placeholders.expansion.EmptyTabExpansion;
|
||||
import me.neznamy.tab.shared.placeholders.expansion.TabExpansion;
|
||||
import me.neznamy.tab.shared.platform.BossBar;
|
||||
@@ -147,18 +148,22 @@ public class VelocityPlatform extends ProxyPlatform {
|
||||
plugin.getServer().getEventManager().register(plugin, ObjectiveEvent.Display.class, e -> {
|
||||
TAB tab = TAB.getInstance();
|
||||
if (tab.isPluginDisabled()) return;
|
||||
tab.getCPUManager().runTask(() -> {
|
||||
TabPlayer player = tab.getPlayer(e.getPlayer().getUniqueId());
|
||||
if (player != null) tab.getFeatureManager().onDisplayObjective(player, e.getNewSlot().ordinal(), e.getObjectiveName());
|
||||
});
|
||||
TabPlayer player = tab.getPlayer(e.getPlayer().getUniqueId());
|
||||
if (player == null) return;
|
||||
ScoreboardManagerImpl scoreboard = tab.getFeatureManager().getFeature(TabConstants.Feature.SCOREBOARD);
|
||||
if (scoreboard != null) {
|
||||
scoreboard.onVelocityScoreboardDisplay(player, e.getNewSlot().ordinal(), e.getObjectiveName());
|
||||
}
|
||||
});
|
||||
plugin.getServer().getEventManager().register(plugin, ObjectiveEvent.Unregister.class, e -> {
|
||||
TAB tab = TAB.getInstance();
|
||||
if (tab.isPluginDisabled()) return;
|
||||
tab.getCPUManager().runTask(() -> {
|
||||
TabPlayer player = tab.getPlayer(e.getPlayer().getUniqueId());
|
||||
if (player != null) tab.getFeatureManager().onObjective(player, Scoreboard.ObjectiveAction.UNREGISTER, e.getObjectiveName());
|
||||
});
|
||||
TabPlayer player = tab.getPlayer(e.getPlayer().getUniqueId());
|
||||
if (player == null) return;
|
||||
ScoreboardManagerImpl scoreboard = tab.getFeatureManager().getFeature(TabConstants.Feature.SCOREBOARD);
|
||||
if (scoreboard != null) {
|
||||
scoreboard.onVelocityScoreboardUnregister(player, e.getObjectiveName());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -103,7 +103,7 @@ public class VelocityTabExpansion implements TabExpansion {
|
||||
if (tabPlayer == null) {
|
||||
return Tag.selfClosingInserting(Component.text("<Player is not loaded>"));
|
||||
}
|
||||
return createTag(pm.getPlaceholder(requestedPlaceholder).parse(tabPlayer));
|
||||
return createTag(pm.getPlaceholderReference(requestedPlaceholder).getHandle().parse(tabPlayer));
|
||||
});
|
||||
|
||||
builder.relationalPlaceholder("replace", (viewer, target, queue, ctx) -> {
|
||||
|
||||
Reference in new issue
Block a user