mirror of
https://github.com/NEZNAMY/TAB.git
synced 2026-11-12 20:22:01 -05:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
814ad0f9d5 |
No files matched your search
@@ -1,20 +0,0 @@
|
||||
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.2.0"><title>Release: 6.2.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.2.0</text><text x="715" y="150" fill-opacity=".3" textLength="290">6.2.0</text></g><text x="715" y="140" textLength="290">6.2.0</text></g></g></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="92" height="20" role="img" aria-label="Release: 6.1.1"><title>Release: 6.1.1</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.1</text><text x="715" y="150" fill-opacity=".3" textLength="290">6.1.1</text></g><text x="715" y="140" textLength="290">6.1.1</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.2.0"
|
||||
release = "6.1.1"
|
||||
download_image(f"https://img.shields.io/badge/Release-{release}-blue.svg", "Badge_Release.svg")
|
||||
|
||||
java = "8+"
|
||||
|
||||
@@ -8,10 +8,9 @@ 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 can do any of the following:
|
||||
To make sure floodgate is configured correctly, you have two options:
|
||||
* Set `debug: true` in TAB config and check console output on join/reload. It should say `Floodgate returned bedrock status TRUE for player <player>`.
|
||||
* Run `/tab parse <player> %bedrock%`. It should return `true`.
|
||||
* 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,27 +11,18 @@ 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.3**. When new versions come out, TAB is always updated soon after to support them.
|
||||
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 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+).
|
||||
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+).
|
||||
|
||||
The sections below provide **additional** information for each platform.
|
||||
|
||||
## Bukkit
|
||||
Since TAB 6.0.0, support for unused 1.x versions started disappearing and will continue to disappear with each subsequent Minecraft release.
|
||||
Currently, the latest release supports the following versions:
|
||||
* 1.7.10
|
||||
* 1.8.8
|
||||
* 1.12.2
|
||||
* 1.16.5
|
||||
* 1.17.1
|
||||
* 1.18.2
|
||||
* 1.19.4 - 26.3
|
||||
|
||||
If you want a jar with support for a 1.x version that is no longer supported, you can use TAB v5.5.0, which supports all MC 1.x versions. For 1.5.2 - 1.6.4, use TAB v5.2.5.
|
||||
TAB 5.5.0 supports all 1.x versions starting with 1.7. For 1.5.2 - 1.6.4, use TAB v5.2.5.
|
||||
Since TAB 6.0.0, support for unused 1.x versions will continue to disappear with each subsequent Minecraft release.
|
||||
|
||||
## BungeeCord / Velocity
|
||||
For proxies, the plugin is made to work with the latest build.
|
||||
@@ -47,7 +38,8 @@ 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.
|
||||
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**.
|
||||
|
||||
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.1.2</version>
|
||||
<version>6.0.0</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
@@ -50,7 +50,7 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly 'com.github.NEZNAMY:TAB-API:6.1.2'
|
||||
compileOnly 'com.github.NEZNAMY:TAB-API:6.0.0'
|
||||
}
|
||||
```
|
||||
|
||||
@@ -61,7 +61,7 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly("com.github.NEZNAMY", "TAB-API", "6.1.2")
|
||||
compileOnly("com.github.NEZNAMY", "TAB-API", "6.0.0")
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -55,11 +55,10 @@ 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).
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
* ~Examples~
|
||||
|
||||
# About
|
||||
This feature allows you to see players from different servers, instead of only seeing players on the same server. The typical use is to show players under the whole proxy network.
|
||||
If TAB is installed on a backend server, you'll need to set up [Multi server support](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Multi-server-support) to properly make the servers communicate. On proxy installation, this is not needed, unless you have multiple proxies.
|
||||
This feature allows you to see players from other servers, instead of only seeing players on the same server.
|
||||
If TAB is installed on a backend server, you'll need to set up [Multi server support](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Multi-server-support) for proper linking of servers. On proxy installation, this is not required.
|
||||
|
||||
# Configuration
|
||||
The feature can be configured in **config.yml** under **global-playerlist** section.
|
||||
@@ -118,8 +118,6 @@ global-playerlist:
|
||||
- isolatedServer2
|
||||
```
|
||||
|
||||
This is not needed when `isolate-unlisted-servers` is set to `true`, in which case all unlisted servers are isolated by default.
|
||||
|
||||
## Seeing all players on the network from some server
|
||||
If, despite configuring server groups,
|
||||
you want players to see everyone on the network if they're connected to specific server(s),
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
* [Additional note 2 - [1.8 - 1.21.3] Second layer of skin missing](#additional-note-2---18---1213-second-layer-of-skin-missing)
|
||||
* [Additional note 3 - Entry overlap](#additional-note-3---entry-overlap)
|
||||
* ~Troubleshooting~
|
||||
* [API](#api)
|
||||
* ~API~
|
||||
* [Examples](#examples)
|
||||
* [Example 1 - Per-server columns](#example-1---per-server-columns)
|
||||
* [Example 2 - Per-world playerlist](#example-2---per-world-playerlist)
|
||||
@@ -284,22 +284,6 @@ Here are a few tips how to do that:
|
||||
* Remove unnecessary brackets from prefix
|
||||
|
||||
|
||||
# API
|
||||
*To get started with the API, see [Developer API](https://github.com/NEZNAMY/TAB/wiki/Developer-API) page.*
|
||||
|
||||
To access this feature, you'll need to obtain `LayoutManager` instance. Get it using `TabAPI.getInstance().getLayoutManager()`. If this feature is disabled, the method will return `null`.
|
||||
|
||||
The `LayoutManager` has the following methods:
|
||||
* `Layout createNewLayout(String name)` - Creates new layout with given internal name and 80 slots.
|
||||
* `Layout createNewLayout(String name, int slotCount)` - Creates new layout with given internal name and slot count (for 1.19.3+, <1.19.3 players will still see 80 slots).
|
||||
* `Layout getLayout(String name)` - Returns layout by given internal name. Works for layouts created in both configuration and through the API.
|
||||
* `sendLayout(TabPlayer player, Layout layout)` - Sends given layout to the player, overriding configuration. Sending `null` as layout will restore layout from configuration.
|
||||
|
||||
After creating a `Layout` using the API or getting it from configuration, you can use the following methods:
|
||||
* `String getName()` - Returns layout's name
|
||||
* `addFixedSlot(int slot, String text)` / `addFixedSlot(int slot, String text, String skin)` / `addFixedSlot(int slot, String text, int ping)` / `addFixedSlot(int slot, String text, String skin, int ping)` / `addFixedSlot(int slot, String text, String skin, String ping)` - Adds a fixed slot with gieven properties - slot, text, skin (optional) and ping (optional)
|
||||
* `addGroup(String condition, int[] slots)` - Adds a new player group with given condition (nullable for no condition) and slots. Condition can be either a conditional expression, or a condition created in config.
|
||||
|
||||
# Examples
|
||||
## Example 1 - Per-server columns
|
||||
```
|
||||
|
||||
@@ -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:%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).
|
||||
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.
|
||||
|
||||
## #7 - Where can I find current default config files?
|
||||
You can do any of the following:
|
||||
@@ -118,14 +118,16 @@ 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?
|
||||
Here are various ways to achieve this goal:
|
||||
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.
|
||||
|
||||
* **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.
|
||||
If you're okay with players not being visible in the world, you can use a plugin that hides all players completely.
|
||||
|
||||
* **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.
|
||||
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 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.
|
||||
An alternative solution using TAB is putting a lot of empty lines into header,
|
||||
which will push all players out of the screen.
|
||||
|
||||
## #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.2.0**.
|
||||
The wiki explains the functionality of the plugin as of version **6.1.0**.
|
||||
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,6 +121,13 @@ 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 |
|
||||
@@ -132,25 +139,27 @@ 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>` | 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.
|
||||
| `<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 |
|
||||
|
||||
Here is an example for all types:
|
||||
```
|
||||
footer:
|
||||
- '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>'
|
||||
- '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>'
|
||||
```
|
||||
|
||||
<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.
|
||||
|
||||
@@ -168,7 +177,8 @@ 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.
|
||||
|
||||
TAB creates a modified MiniMessage parser that also accepts `<head_texture:TEXTURE>` and `<mineskin:ID>` head syntaxes just like TAB's own component parser.
|
||||
> [!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.
|
||||
|
||||
# Config options
|
||||
| Option name | Default value | Description |
|
||||
|
||||
@@ -1,55 +1 @@
|
||||
# About
|
||||
TAB aims to be a superior all-in-one minecraft plugin for displaying information that outperforms all
|
||||
similar plugins in terms of features, performance and compatibility.
|
||||
More information can be found at [Why TAB?](https://github.com/NEZNAMY/TAB/wiki/Why-TAB%3F) wiki page.
|
||||
|
||||
# Download
|
||||
### Releases
|
||||
You can download full releases from [GitHub releases](https://github.com/NEZNAMY/TAB/releases),
|
||||
[SpigotMC](https://www.spigotmc.org/resources/57806/), [Modrinth](https://modrinth.com/plugin/tab-was-taken), [BuiltByBit](https://builtbybit.com/resources/20631) or [CurseForge](https://www.curseforge.com/minecraft/mc-mods/tab).
|
||||
|
||||
### Dev builds
|
||||
Plugin's development is continuous and contains lots of small changes/fixes not worthy of being called a full
|
||||
update, therefore development builds are being created and eventually a full update is released when enough changes are made.
|
||||
If you want to benefit from a recent change in the code before official release, you can download development builds
|
||||
from [GitHub actions](https://github.com/NEZNAMY/TAB/actions) (you must be logged into GitHub to be able to download it).
|
||||
|
||||
# Compiling
|
||||
Compilation requires JDK 25 and up.
|
||||
To compile the plugin, run `./gradlew build` from the terminal.
|
||||
Once the plugin compiles, grab the jar from `/jar/build/libs/` folder.
|
||||
The universal jar contains all modules for all supported platforms.
|
||||
|
||||
# Documentation
|
||||
You can find everything about the plugin on the [Wiki](https://github.com/NEZNAMY/TAB/wiki). This includes a detailed description
|
||||
of all features, as well as information regarding compatibility or limitations of each feature.
|
||||
|
||||
# Issues
|
||||
The [Issues](https://github.com/NEZNAMY/TAB/issues) section is an issue tracker for all the kind of problems covered in issue templates.
|
||||
Please refrain from attempting to use it to receive help with your server, such issues will be closed immediately.
|
||||
When opening an issue, provide as much information as possible. The difference between a bug getting fixed in 1 week vs 1 hour
|
||||
is in quality of the report. Bug reports where steps to reproduce are "idk" have no value and will not help to get the bug fixed at all.
|
||||
|
||||
# Contributing
|
||||
See [CONTRIBUTING.md](https://github.com/NEZNAMY/TAB/blob/master/CONTRIBUTING.md)
|
||||
|
||||
# Provided services
|
||||
The plugin is being used by a large amount of servers, which also means the demand for my attention is high. Unfortunately,
|
||||
I am no longer able to keep up with the demand since I don't want to dedicate my whole existence to a free plugin.
|
||||
In order to provide the best quality of all provided services, the amount of services provided may vary over time.
|
||||
Here is the full list of potential services and their status:
|
||||
|
||||
| Service type | Availability |
|
||||
|-------------------------------|--------------|
|
||||
| Updates for new MC versions | ✔ |
|
||||
| Bug fixes | ✔ |
|
||||
| Wiki improvements | ✔ |
|
||||
| Performance optimizations | ✔ |
|
||||
| Feature requests | ❌ |
|
||||
| Customer support<sup>1</sup> | ✔ |
|
||||
| Free user support<sup>2</sup> | ❌* |
|
||||
|
||||
<sup>1</sup> - Unrestricted assistance with the plugin for those, who bought a copy of the plugin when it was purchasable.
|
||||
<sup>2</sup> - Unlimited access to my free time for everyone in the world.
|
||||
|
||||
*Instead, you can use [this fresh new community discord](https://discord.gg/YPqXt63YQj) made by a nice person who will provide this service.
|
||||
This is a backup of the repository at the state of 6.1.1 release.
|
||||
@@ -4,11 +4,9 @@ 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;
|
||||
@@ -85,22 +83,6 @@ 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}.
|
||||
*
|
||||
|
||||
@@ -1,118 +0,0 @@
|
||||
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
@@ -1,153 +0,0 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -48,12 +48,14 @@ public interface LayoutManager {
|
||||
Layout getLayout(@NonNull String name);
|
||||
|
||||
/**
|
||||
* Sends layout to player. Set to {@code null} to reset layout back to original one based on configuration.
|
||||
* Sends layout to player. Set to {@code null} to make player not see
|
||||
* any layout, but default TabList instead.
|
||||
*
|
||||
* @param player
|
||||
* Player to send layout to
|
||||
* @param layout
|
||||
* Layout to send
|
||||
* @see #resetLayout(TabPlayer)
|
||||
*/
|
||||
void sendLayout(@NonNull TabPlayer player, @Nullable Layout layout);
|
||||
|
||||
@@ -63,8 +65,6 @@ public interface LayoutManager {
|
||||
* @param player
|
||||
* Player to reset layout for
|
||||
* @see #sendLayout(TabPlayer, Layout)
|
||||
* @deprecated Use {@link #sendLayout(TabPlayer, Layout)} with {@code null} instead
|
||||
*/
|
||||
@Deprecated
|
||||
void resetLayout(@NonNull TabPlayer player);
|
||||
}
|
||||
+3
-1
@@ -4,7 +4,7 @@ plugins {
|
||||
|
||||
allprojects {
|
||||
group = "me.neznamy"
|
||||
version = "6.2.1"
|
||||
version = "6.1.1"
|
||||
description = "An all-in-one solution that works"
|
||||
|
||||
ext.set("id", "tab")
|
||||
@@ -27,6 +27,8 @@ val platformPaths = setOf(
|
||||
":bukkit:v1_16_R3",
|
||||
":bukkit:v1_17_R1",
|
||||
":bukkit:v1_18_R2",
|
||||
":bukkit:v1_19_R1",
|
||||
":bukkit:v1_19_R2",
|
||||
":bukkit:v1_19_R3",
|
||||
":bukkit:v1_20_R1",
|
||||
":bukkit:v1_20_R2",
|
||||
|
||||
+3
-5
@@ -14,16 +14,14 @@ import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Implementation provider using direct Mojang-mapped NMS code for Paper 26.2 - 26.3.
|
||||
* Implementation provider using direct Mojang-mapped NMS code for Paper 26.2.
|
||||
*/
|
||||
@Getter
|
||||
public class NMSImplementationProvider implements ImplementationProvider {
|
||||
|
||||
/** Flag tracking if this server is Canvas 26.1.2+ with new scoreboard checks */
|
||||
private final boolean isCanvas =
|
||||
ReflectionUtils.classExists("io.canvasmc.canvas.world.scores.TeamData") ||
|
||||
ReflectionUtils.classExists("io.canvasmc.canvas.threadedregions.scores.TeamData") ||
|
||||
ReflectionUtils.classExists("io.canvasmc.canvas.threadedregions.world.scores.TeamData");
|
||||
private final boolean isCanvas = ReflectionUtils.classExists("io.canvasmc.canvas.world.scores.TeamData") ||
|
||||
ReflectionUtils.classExists("io.canvasmc.canvas.threadedregions.scores.TeamData");
|
||||
|
||||
@NotNull
|
||||
private final ComponentConverter<?> componentConverter = new NMSComponentConverter();
|
||||
|
||||
@@ -2,12 +2,10 @@ 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.
|
||||
@@ -21,32 +19,15 @@ public class BukkitTAB extends JavaPlugin {
|
||||
TAB.create(folia ? new FoliaPlatform(this) : new BukkitPlatform(this));
|
||||
} catch (IllegalStateException e) {
|
||||
Bukkit.getConsoleSender().sendMessage("§c[TAB] ================================================================================");
|
||||
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, 1.18.2 and 1.19.4 - 26.3");
|
||||
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] 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 (Throwable e) {
|
||||
} catch (ReflectiveOperationException e) {
|
||||
// 1.21.11 and lower
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,7 +96,6 @@ public class ServerVersionInfo {
|
||||
spigotVersions.put(ProtocolVersion.V26_1_1, "v26_1");
|
||||
spigotVersions.put(ProtocolVersion.V26_1_2, "v26_1");
|
||||
spigotVersions.put(ProtocolVersion.V26_2, "v26_2");
|
||||
spigotVersions.put(ProtocolVersion.V26_3, "v26_2");
|
||||
|
||||
Map<ProtocolVersion, String> paperVersions = new LinkedHashMap<>();
|
||||
paperVersions.put(ProtocolVersion.V1_20_5, "paper_1_20_5");
|
||||
@@ -117,7 +116,6 @@ public class ServerVersionInfo {
|
||||
paperVersions.put(ProtocolVersion.V26_1_1, "paper_1_21_11"); // v26_1 works too
|
||||
paperVersions.put(ProtocolVersion.V26_1_2, "paper_1_21_11"); // v26_1 works too
|
||||
paperVersions.put(ProtocolVersion.V26_2, "paper_26_2"); // v26_2 works too
|
||||
paperVersions.put(ProtocolVersion.V26_3, "paper_26_2");
|
||||
|
||||
if (serverVersion == ProtocolVersion.UNKNOWN) {
|
||||
throw new IllegalStateException(String.format(
|
||||
|
||||
+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.getPlaceholderReference(requestedPlaceholder).getHandle().parse(p);
|
||||
return pm.getPlaceholder(requestedPlaceholder).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);
|
||||
PlaceholderReference placeholder = pm.getPlaceholderReference(requestedPlaceholder);
|
||||
if (placeholder.getHandle() instanceof RelationalPlaceholderImpl) {
|
||||
RelationalPlaceholderImpl rel = (RelationalPlaceholderImpl) placeholder.getHandle();
|
||||
Placeholder placeholder = pm.getPlaceholder(requestedPlaceholder);
|
||||
if (placeholder instanceof RelationalPlaceholderImpl) {
|
||||
RelationalPlaceholderImpl rel = (RelationalPlaceholderImpl) placeholder;
|
||||
TabPlayer v = TAB.getInstance().getPlayer(viewer.getUniqueId());
|
||||
TabPlayer t = TAB.getInstance().getPlayer(target.getUniqueId());
|
||||
if (v == null || t == null) return "<Player is not loaded>";
|
||||
|
||||
@@ -238,9 +238,9 @@ public class BukkitPlatform implements BackendPlatform {
|
||||
Metrics metrics = new Metrics(plugin, TabConstants.BSTATS_PLUGIN_ID_BUKKIT);
|
||||
metrics.addCustomChart(new SimplePie(TabConstants.MetricsChart.PERMISSION_SYSTEM,
|
||||
() -> TAB.getInstance().getGroupManager().getPermissionPlugin()));
|
||||
metrics.addCustomChart(new SimplePie("tab_6_2_x_servers",
|
||||
metrics.addCustomChart(new SimplePie("tab_6_1_0_servers",
|
||||
() -> serverVersionInfo.getServerName() + " " + serverVersionInfo.getServerVersion().getFriendlyName()));
|
||||
metrics.addCustomChart(new SimplePie("tab_6_2_x_package", serverVersionInfo::getImplementationPackage));
|
||||
metrics.addCustomChart(new SimplePie("tab_6_1_0_package", serverVersionInfo::getImplementationPackage));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
dependencies {
|
||||
implementation(projects.bukkit)
|
||||
compileOnly("org.spigotmc:spigot:1.19.2-R0.1-SNAPSHOT") {
|
||||
// Broken dependencies
|
||||
exclude("com.mojang", "authlib")
|
||||
exclude("com.mojang", "logging")
|
||||
exclude("com.mojang", "datafixerupper")
|
||||
}
|
||||
compileOnly("com.mojang:authlib:1.5.25")
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
package me.neznamy.tab.platforms.bukkit.v1_19_R1;
|
||||
|
||||
import me.neznamy.tab.shared.chat.TabStyle;
|
||||
import me.neznamy.tab.shared.chat.component.object.TabAtlasSprite;
|
||||
import me.neznamy.tab.shared.chat.component.object.TabObjectComponent;
|
||||
import me.neznamy.tab.platforms.bukkit.provider.ComponentConverter;
|
||||
import me.neznamy.tab.shared.chat.component.object.TabPlayerSprite;
|
||||
import net.minecraft.network.chat.*;
|
||||
import net.minecraft.resources.MinecraftKey;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Component converter using direct NMS code.
|
||||
*/
|
||||
public class NMSComponentConverter extends ComponentConverter<IChatBaseComponent> {
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public IChatBaseComponent newTextComponent(@NotNull String text) {
|
||||
return IChatBaseComponent.b(text);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public IChatBaseComponent newTranslatableComponent(@NotNull String key) {
|
||||
return IChatBaseComponent.c(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public IChatBaseComponent newKeybindComponent(@NotNull String keybind) {
|
||||
return IChatBaseComponent.d(keybind);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public IChatBaseComponent newObjectComponent(@NotNull TabAtlasSprite sprite) {
|
||||
return IChatBaseComponent.b(TabObjectComponent.ERROR_MESSAGE);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public IChatBaseComponent newObjectComponent(@NotNull TabPlayerSprite sprite) {
|
||||
return IChatBaseComponent.b(TabObjectComponent.ERROR_MESSAGE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyStyle(@NotNull IChatBaseComponent nmsComponent, @NotNull TabStyle modifier) {
|
||||
((IChatMutableComponent)nmsComponent).c(ChatModifier.a
|
||||
.a(modifier.getColor() == null ? null : ChatHexColor.a(modifier.getColor().getRgb()))
|
||||
.a(modifier.getBold())
|
||||
.b(modifier.getItalic())
|
||||
.c(modifier.getUnderlined())
|
||||
.d(modifier.getStrikethrough())
|
||||
.e(modifier.getObfuscated())
|
||||
.a(modifier.getFont() == null ? null : MinecraftKey.a(modifier.getFont()))
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addSibling(@NotNull IChatBaseComponent parent, @NotNull IChatBaseComponent child) {
|
||||
((IChatMutableComponent)parent).b(child);
|
||||
}
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
package me.neznamy.tab.platforms.bukkit.v1_19_R1;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import lombok.Getter;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.provider.ComponentConverter;
|
||||
import me.neznamy.tab.platforms.bukkit.provider.ImplementationProvider;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import me.neznamy.tab.shared.platform.TabListEntryTracker;
|
||||
import org.bukkit.craftbukkit.v1_19_R1.entity.CraftPlayer;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Implementation provider using direct NMS code for 1.19.1 - 1.19.2.
|
||||
*/
|
||||
@Getter
|
||||
public class NMSImplementationProvider implements ImplementationProvider {
|
||||
|
||||
@NotNull
|
||||
private final ComponentConverter<?> componentConverter = new NMSComponentConverter();
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Scoreboard newScoreboard(@NotNull BukkitTabPlayer player) {
|
||||
return new NMSPacketScoreboard(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public TabList newTabList(@NotNull BukkitTabPlayer player) {
|
||||
return new NMSPacketTabList(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Channel getChannel(@NotNull Player player) {
|
||||
return ((CraftPlayer)player).getHandle().b.b.m;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public TabListEntryTracker newTabListEntryTracker(@NotNull Player player) {
|
||||
return new NMSTabListEntryTracker(getChannel(player));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPing(@NotNull BukkitTabPlayer player) {
|
||||
return player.getPlayer().getPing();
|
||||
}
|
||||
}
|
||||
+207
@@ -0,0 +1,207 @@
|
||||
package me.neznamy.tab.platforms.bukkit.v1_19_R1;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.platform.decorators.SafeScoreboard;
|
||||
import net.minecraft.EnumChatFormat;
|
||||
import net.minecraft.network.chat.IChatBaseComponent;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.network.protocol.game.PacketPlayOutScoreboardDisplayObjective;
|
||||
import net.minecraft.network.protocol.game.PacketPlayOutScoreboardObjective;
|
||||
import net.minecraft.network.protocol.game.PacketPlayOutScoreboardScore;
|
||||
import net.minecraft.network.protocol.game.PacketPlayOutScoreboardTeam;
|
||||
import net.minecraft.server.ScoreboardServer;
|
||||
import net.minecraft.world.scores.Scoreboard;
|
||||
import net.minecraft.world.scores.ScoreboardObjective;
|
||||
import net.minecraft.world.scores.ScoreboardTeam;
|
||||
import net.minecraft.world.scores.ScoreboardTeamBase;
|
||||
import net.minecraft.world.scores.criteria.IScoreboardCriteria;
|
||||
import org.bukkit.craftbukkit.v1_19_R1.entity.CraftPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.Collection;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Scoreboard implementation using direct NMS code.
|
||||
*/
|
||||
public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
|
||||
|
||||
private static final ScoreboardTeamBase.EnumNameTagVisibility[] visibilities = ScoreboardTeamBase.EnumNameTagVisibility.values();
|
||||
private static final ScoreboardTeamBase.EnumTeamPush[] collisions = ScoreboardTeamBase.EnumTeamPush.values();
|
||||
private static final Scoreboard dummyScoreboard = new Scoreboard();
|
||||
|
||||
private static final Constructor<PacketPlayOutScoreboardTeam> teamConstructor;
|
||||
|
||||
static {
|
||||
try {
|
||||
teamConstructor = PacketPlayOutScoreboardTeam.class.getDeclaredConstructor(String.class, int.class, Optional.class, Collection.class);
|
||||
teamConstructor.setAccessible(true);
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs new instance with given player.
|
||||
*
|
||||
* @param player
|
||||
* Player this scoreboard will belong to
|
||||
*/
|
||||
public NMSPacketScoreboard(@NotNull BukkitTabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerObjective(@NonNull Objective objective) {
|
||||
ScoreboardObjective obj = new ScoreboardObjective(
|
||||
dummyScoreboard,
|
||||
objective.getName(),
|
||||
IScoreboardCriteria.a,
|
||||
objective.getTitle().convert(),
|
||||
IScoreboardCriteria.EnumScoreboardHealthDisplay.values()[objective.getHealthDisplay().ordinal()]
|
||||
);
|
||||
objective.setPlatformObjective(obj);
|
||||
sendPacket(new PacketPlayOutScoreboardObjective(obj, ObjectiveAction.REGISTER));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDisplaySlot(@NonNull Objective objective) {
|
||||
sendPacket(new PacketPlayOutScoreboardDisplayObjective(
|
||||
objective.getDisplaySlot().ordinal(),
|
||||
(ScoreboardObjective) objective.getPlatformObjective()
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterObjective(@NonNull Objective objective) {
|
||||
sendPacket(new PacketPlayOutScoreboardObjective((ScoreboardObjective) objective.getPlatformObjective(), ObjectiveAction.UNREGISTER));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateObjective(@NonNull Objective objective) {
|
||||
ScoreboardObjective obj = (ScoreboardObjective) objective.getPlatformObjective();
|
||||
obj.a((IChatBaseComponent) objective.getTitle().convert());
|
||||
obj.a(IScoreboardCriteria.EnumScoreboardHealthDisplay.valueOf(objective.getHealthDisplay().name()));
|
||||
sendPacket(new PacketPlayOutScoreboardObjective(obj, ObjectiveAction.UPDATE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setScore(@NonNull Score score) {
|
||||
sendPacket(new PacketPlayOutScoreboardScore(
|
||||
ScoreboardServer.Action.a,
|
||||
score.getObjective().getName(),
|
||||
score.getHolder(),
|
||||
score.getValue()
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeScore(@NonNull Score score) {
|
||||
sendPacket(new PacketPlayOutScoreboardScore(
|
||||
ScoreboardServer.Action.b,
|
||||
score.getObjective().getName(),
|
||||
score.getHolder(),
|
||||
score.getValue()
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Object createTeam(@NonNull String name) {
|
||||
return new ScoreboardTeam(dummyScoreboard, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerTeam(@NonNull Team team) {
|
||||
updateTeamProperties(team);
|
||||
ScoreboardTeam t = (ScoreboardTeam) team.getPlatformTeam();
|
||||
t.g().addAll(team.getPlayers());
|
||||
sendPacket(PacketPlayOutScoreboardTeam.a(t, true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterTeam(@NonNull Team team) {
|
||||
sendPacket(PacketPlayOutScoreboardTeam.a((ScoreboardTeam) team.getPlatformTeam()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateTeam(@NonNull Team team) {
|
||||
updateTeamProperties(team);
|
||||
sendPacket(PacketPlayOutScoreboardTeam.a((ScoreboardTeam) team.getPlatformTeam(), false));
|
||||
}
|
||||
|
||||
private void updateTeamProperties(@NonNull Team team) {
|
||||
ScoreboardTeam t = (ScoreboardTeam) team.getPlatformTeam();
|
||||
t.a((team.getOptions() & 0x01) != 0);
|
||||
t.b((team.getOptions() & 0x02) != 0);
|
||||
t.a(visibilities[team.getVisibility().ordinal()]);
|
||||
t.a(collisions[team.getCollision().ordinal()]);
|
||||
t.b((IChatBaseComponent) team.getPrefix().convert());
|
||||
t.c(team.getSuffix().convert());
|
||||
t.a(EnumChatFormat.valueOf(team.getColor().name()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
@NotNull
|
||||
public Object onPacketSend(@NonNull Object packet) {
|
||||
if (packet instanceof PacketPlayOutScoreboardDisplayObjective) {
|
||||
TAB.getInstance().getFeatureManager().onDisplayObjective(
|
||||
player,
|
||||
((PacketPlayOutScoreboardDisplayObjective)packet).b(),
|
||||
((PacketPlayOutScoreboardDisplayObjective)packet).c()
|
||||
);
|
||||
}
|
||||
if (packet instanceof PacketPlayOutScoreboardObjective) {
|
||||
TAB.getInstance().getFeatureManager().onObjective(
|
||||
player,
|
||||
((PacketPlayOutScoreboardObjective)packet).d(),
|
||||
((PacketPlayOutScoreboardObjective)packet).b()
|
||||
);
|
||||
}
|
||||
if (packet instanceof PacketPlayOutScoreboardTeam) {
|
||||
int action = getMethod((PacketPlayOutScoreboardTeam) packet);
|
||||
if (action != TeamAction.UPDATE) {
|
||||
Collection<String> players = ((PacketPlayOutScoreboardTeam) packet).e();
|
||||
if (players != null) {
|
||||
String name = ((PacketPlayOutScoreboardTeam) packet).d();
|
||||
return teamConstructor.newInstance(
|
||||
name,
|
||||
action,
|
||||
((PacketPlayOutScoreboardTeam) packet).f(),
|
||||
onTeamPacket(action, name, players)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
return packet;
|
||||
}
|
||||
|
||||
private int getMethod(@NonNull PacketPlayOutScoreboardTeam team) {
|
||||
if (team.c() == PacketPlayOutScoreboardTeam.a.a) {
|
||||
return 0;
|
||||
} else if (team.c() == PacketPlayOutScoreboardTeam.a.b) {
|
||||
return 1;
|
||||
} else if (team.b() == PacketPlayOutScoreboardTeam.a.a) {
|
||||
return 3;
|
||||
} else if (team.b() == PacketPlayOutScoreboardTeam.a.b) {
|
||||
return 4;
|
||||
} else {
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the packet to the player.
|
||||
*
|
||||
* @param packet
|
||||
* Packet to send
|
||||
*/
|
||||
private void sendPacket(@NotNull Packet<?> packet) {
|
||||
((CraftPlayer)player.getPlayer()).getHandle().b.a(packet);
|
||||
}
|
||||
}
|
||||
+210
@@ -0,0 +1,210 @@
|
||||
package me.neznamy.tab.platforms.bukkit.v1_19_R1;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import com.mojang.authlib.properties.Property;
|
||||
import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.chat.component.TabComponent;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import me.neznamy.tab.shared.platform.decorators.TrackedTabList;
|
||||
import net.minecraft.network.chat.IChatBaseComponent;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.network.protocol.game.PacketPlayOutPlayerInfo;
|
||||
import net.minecraft.network.protocol.game.PacketPlayOutPlayerInfo.EnumPlayerInfoAction;
|
||||
import net.minecraft.network.protocol.game.PacketPlayOutPlayerInfo.PlayerInfoData;
|
||||
import net.minecraft.network.protocol.game.PacketPlayOutPlayerListHeaderFooter;
|
||||
import net.minecraft.world.entity.player.ProfilePublicKey;
|
||||
import net.minecraft.world.level.EnumGamemode;
|
||||
import org.bukkit.craftbukkit.v1_19_R1.entity.CraftPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* TabList implementation using direct NMS code.
|
||||
*/
|
||||
public class NMSPacketTabList extends TrackedTabList<BukkitTabPlayer> {
|
||||
|
||||
private static final EnumPlayerInfoAction ADD_PLAYER = EnumPlayerInfoAction.a;
|
||||
private static final EnumPlayerInfoAction UPDATE_GAME_MODE = EnumPlayerInfoAction.b;
|
||||
private static final EnumPlayerInfoAction UPDATE_LATENCY = EnumPlayerInfoAction.c;
|
||||
private static final EnumPlayerInfoAction UPDATE_DISPLAY_NAME = EnumPlayerInfoAction.d;
|
||||
private static final EnumPlayerInfoAction REMOVE_PLAYER = EnumPlayerInfoAction.e;
|
||||
|
||||
/**
|
||||
* Constructs new instance.
|
||||
*
|
||||
* @param player
|
||||
* Player this tablist will belong to
|
||||
*/
|
||||
public NMSPacketTabList(@NotNull BukkitTabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void removeEntry(@NonNull UUID entry) {
|
||||
sendPacket(EnumPlayerInfoAction.e, entry, "", null, 0, 0, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDisplayName0(@NonNull UUID entry, @Nullable TabComponent displayName) {
|
||||
sendPacket(EnumPlayerInfoAction.d, entry, "", null, 0, 0, displayName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateLatency(@NonNull UUID entry, int latency) {
|
||||
sendPacket(EnumPlayerInfoAction.c, entry, "", null, latency, 0, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateGameMode(@NonNull UUID entry, int gameMode) {
|
||||
sendPacket(EnumPlayerInfoAction.b, entry, "", null, 0, gameMode, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateListed(@NonNull UUID entry, boolean listed) {
|
||||
// Added in 1.19.3
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateListOrder(@NonNull UUID entry, int listOrder) {
|
||||
// Added in 1.21.2
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateHat(@NonNull UUID entry, boolean showHat) {
|
||||
// Added in 1.21.4
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addEntry0(@NonNull Entry entry) {
|
||||
sendPacket(EnumPlayerInfoAction.a, entry.getUniqueId(), entry.getName(), entry.getSkin(), entry.getLatency(),
|
||||
entry.getGameMode(), entry.getDisplayName());
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void setPlayerListHeaderFooter0(@NonNull TabComponent header, @NonNull TabComponent footer) {
|
||||
sendPacket(new PacketPlayOutPlayerListHeaderFooter(header.convert(), footer.convert()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Skin getSkin() {
|
||||
Collection<Property> properties = ((CraftPlayer)player.getPlayer()).getProfile().getProperties().get(TEXTURES_PROPERTY);
|
||||
if (properties.isEmpty()) return null; // Offline mode
|
||||
Property property = properties.iterator().next();
|
||||
return new Skin(property.getValue(), property.getSignature());
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
@NotNull
|
||||
public Object onPacketSend(@NonNull Object packet) {
|
||||
if (packet instanceof PacketPlayOutPlayerListHeaderFooter) {
|
||||
PacketPlayOutPlayerListHeaderFooter tablist = (PacketPlayOutPlayerListHeaderFooter) packet;
|
||||
if (header == null || footer == null) return packet;
|
||||
if (tablist.a != header.convert() || tablist.b != footer.convert()) {
|
||||
return new PacketPlayOutPlayerListHeaderFooter(header.convert(), footer.convert());
|
||||
}
|
||||
}
|
||||
if (!(packet instanceof PacketPlayOutPlayerInfo)) return packet;
|
||||
PacketPlayOutPlayerInfo info = (PacketPlayOutPlayerInfo) packet;
|
||||
EnumPlayerInfoAction action = info.c();
|
||||
List<PlayerInfoData> updatedList = new ArrayList<>();
|
||||
boolean rewritePacket = false;
|
||||
for (PlayerInfoData nmsData : info.b()) {
|
||||
boolean rewriteEntry = false;
|
||||
GameProfile profile = nmsData.a();
|
||||
UUID id = profile.getId();
|
||||
IChatBaseComponent displayName = nmsData.d();
|
||||
int latency = nmsData.b();
|
||||
int gameMode = nmsData.c().a();
|
||||
ProfilePublicKey.a publicKey = nmsData.e();
|
||||
if (action == UPDATE_DISPLAY_NAME || action == ADD_PLAYER) {
|
||||
TabComponent forcedDisplayName = getForcedDisplayNames().get(id);
|
||||
if (forcedDisplayName != null && forcedDisplayName.convert() != displayName) {
|
||||
displayName = forcedDisplayName.convert();
|
||||
rewriteEntry = rewritePacket = true;
|
||||
}
|
||||
}
|
||||
if (action == UPDATE_GAME_MODE || action == ADD_PLAYER) {
|
||||
if (getBlockedSpectators().contains(id) && gameMode == 3) {
|
||||
gameMode = 0;
|
||||
rewriteEntry = rewritePacket = true;
|
||||
}
|
||||
}
|
||||
if (action == UPDATE_LATENCY || action == ADD_PLAYER) {
|
||||
if (getForcedLatency() != null) {
|
||||
latency = getForcedLatency();
|
||||
rewriteEntry = rewritePacket = true;
|
||||
}
|
||||
}
|
||||
if (action == ADD_PLAYER) {
|
||||
TAB.getInstance().getFeatureManager().onEntryAdd(player, id, profile.getName());
|
||||
}
|
||||
updatedList.add(rewriteEntry ? new PlayerInfoData(
|
||||
profile,
|
||||
latency,
|
||||
EnumGamemode.a(gameMode),
|
||||
displayName,
|
||||
publicKey
|
||||
) : nmsData);
|
||||
}
|
||||
if (rewritePacket) {
|
||||
PacketPlayOutPlayerInfo newPacket = new PacketPlayOutPlayerInfo(action, Collections.emptyList());
|
||||
newPacket.b().clear();
|
||||
newPacket.b().addAll(updatedList);
|
||||
return newPacket;
|
||||
}
|
||||
return packet;
|
||||
}
|
||||
|
||||
private void sendPacket(@NonNull EnumPlayerInfoAction action, @NonNull UUID id, @NonNull String name,
|
||||
@Nullable Skin skin, int latency, int gameMode, @Nullable TabComponent displayName) {
|
||||
PacketPlayOutPlayerInfo packet = new PacketPlayOutPlayerInfo(action);
|
||||
packet.b().add(new PlayerInfoData(
|
||||
createProfile(id, name, skin),
|
||||
latency,
|
||||
EnumGamemode.a(gameMode),
|
||||
displayName == null ? null : displayName.convert(),
|
||||
null
|
||||
));
|
||||
sendPacket(packet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates GameProfile from given parameters.
|
||||
*
|
||||
* @param id
|
||||
* Profile ID
|
||||
* @param name
|
||||
* Profile name
|
||||
* @param skin
|
||||
* Player skin
|
||||
* @return GameProfile from given parameters
|
||||
*/
|
||||
@NotNull
|
||||
private GameProfile createProfile(@NonNull UUID id, @NonNull String name, @Nullable Skin skin) {
|
||||
GameProfile profile = new GameProfile(id, name);
|
||||
if (skin != null) {
|
||||
profile.getProperties().put(TabList.TEXTURES_PROPERTY,
|
||||
new Property(TabList.TEXTURES_PROPERTY, skin.getValue(), skin.getSignature()));
|
||||
}
|
||||
return profile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the packet to the player.
|
||||
*
|
||||
* @param packet
|
||||
* Packet to send
|
||||
*/
|
||||
private void sendPacket(@NotNull Packet<?> packet) {
|
||||
((CraftPlayer)player.getPlayer()).getHandle().b.a(packet);
|
||||
}
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
package me.neznamy.tab.platforms.bukkit.v1_19_R1;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import me.neznamy.tab.shared.platform.NettyTabListEntryTracker;
|
||||
import net.minecraft.network.protocol.game.PacketPlayOutPlayerInfo;
|
||||
import net.minecraft.network.protocol.game.PacketPlayOutPlayerInfo.EnumPlayerInfoAction;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Implementation of TabListEntryTracker.
|
||||
*/
|
||||
public class NMSTabListEntryTracker extends NettyTabListEntryTracker {
|
||||
|
||||
private static final EnumPlayerInfoAction ADD_PLAYER = EnumPlayerInfoAction.valueOf("ADD_PLAYER");
|
||||
private static final EnumPlayerInfoAction REMOVE_PLAYER = EnumPlayerInfoAction.valueOf("REMOVE_PLAYER");
|
||||
|
||||
public NMSTabListEntryTracker(@NotNull Channel channel) {
|
||||
super(channel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketSend(@NotNull Object packet) {
|
||||
if (packet instanceof PacketPlayOutPlayerInfo) {
|
||||
PacketPlayOutPlayerInfo info = (PacketPlayOutPlayerInfo) packet;
|
||||
for (PacketPlayOutPlayerInfo.PlayerInfoData nmsData : info.b()) {
|
||||
if (info.c() == ADD_PLAYER) {
|
||||
tablistEntries.add(nmsData.a().getId());
|
||||
}
|
||||
if (info.c() == REMOVE_PLAYER) {
|
||||
tablistEntries.remove(nmsData.a().getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
dependencies {
|
||||
implementation(projects.bukkit)
|
||||
compileOnly("org.spigotmc:spigot:1.19.3-R0.1-SNAPSHOT") {
|
||||
// Broken dependencies
|
||||
exclude("com.mojang", "authlib")
|
||||
exclude("com.mojang", "logging")
|
||||
exclude("com.mojang", "datafixerupper")
|
||||
}
|
||||
compileOnly("com.mojang:authlib:1.5.25")
|
||||
}
|
||||
|
||||
tasks.compileJava {
|
||||
options.release.set(14) // Records
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
package me.neznamy.tab.platforms.bukkit.v1_19_R2;
|
||||
|
||||
import me.neznamy.tab.shared.chat.TabStyle;
|
||||
import me.neznamy.tab.shared.chat.component.object.TabAtlasSprite;
|
||||
import me.neznamy.tab.shared.chat.component.object.TabObjectComponent;
|
||||
import me.neznamy.tab.platforms.bukkit.provider.ComponentConverter;
|
||||
import me.neznamy.tab.shared.chat.component.object.TabPlayerSprite;
|
||||
import net.minecraft.network.chat.*;
|
||||
import net.minecraft.resources.MinecraftKey;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Component converter using direct NMS code.
|
||||
*/
|
||||
public class NMSComponentConverter extends ComponentConverter<IChatBaseComponent> {
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public IChatBaseComponent newTextComponent(@NotNull String text) {
|
||||
return IChatBaseComponent.b(text);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public IChatBaseComponent newTranslatableComponent(@NotNull String key) {
|
||||
return IChatBaseComponent.c(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public IChatBaseComponent newKeybindComponent(@NotNull String keybind) {
|
||||
return IChatBaseComponent.d(keybind);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public IChatBaseComponent newObjectComponent(@NotNull TabAtlasSprite sprite) {
|
||||
return IChatBaseComponent.b(TabObjectComponent.ERROR_MESSAGE);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public IChatBaseComponent newObjectComponent(@NotNull TabPlayerSprite sprite) {
|
||||
return IChatBaseComponent.b(TabObjectComponent.ERROR_MESSAGE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyStyle(@NotNull IChatBaseComponent nmsComponent, @NotNull TabStyle modifier) {
|
||||
((IChatMutableComponent)nmsComponent).c(ChatModifier.a
|
||||
.a(modifier.getColor() == null ? null : ChatHexColor.a(modifier.getColor().getRgb()))
|
||||
.a(modifier.getBold())
|
||||
.b(modifier.getItalic())
|
||||
.c(modifier.getUnderlined())
|
||||
.d(modifier.getStrikethrough())
|
||||
.e(modifier.getObfuscated())
|
||||
.a(modifier.getFont() == null ? null : MinecraftKey.a(modifier.getFont()))
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addSibling(@NotNull IChatBaseComponent parent, @NotNull IChatBaseComponent child) {
|
||||
((IChatMutableComponent)parent).b(child);
|
||||
}
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
package me.neznamy.tab.platforms.bukkit.v1_19_R2;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import lombok.Getter;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.provider.ComponentConverter;
|
||||
import me.neznamy.tab.platforms.bukkit.provider.ImplementationProvider;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import me.neznamy.tab.shared.platform.TabListEntryTracker;
|
||||
import org.bukkit.craftbukkit.v1_19_R2.entity.CraftPlayer;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Implementation provider using direct NMS code for 1.19.3.
|
||||
*/
|
||||
@Getter
|
||||
public class NMSImplementationProvider implements ImplementationProvider {
|
||||
|
||||
@NotNull
|
||||
private final ComponentConverter<?> componentConverter = new NMSComponentConverter();
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Scoreboard newScoreboard(@NotNull BukkitTabPlayer player) {
|
||||
return new NMSPacketScoreboard(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public TabList newTabList(@NotNull BukkitTabPlayer player) {
|
||||
return new NMSPacketTabList(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Channel getChannel(@NotNull Player player) {
|
||||
return ((CraftPlayer)player).getHandle().b.b.m;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public TabListEntryTracker newTabListEntryTracker(@NotNull Player player) {
|
||||
return new NMSTabListEntryTracker(getChannel(player));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPing(@NotNull BukkitTabPlayer player) {
|
||||
return player.getPlayer().getPing();
|
||||
}
|
||||
}
|
||||
+206
@@ -0,0 +1,206 @@
|
||||
package me.neznamy.tab.platforms.bukkit.v1_19_R2;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.platform.decorators.SafeScoreboard;
|
||||
import net.minecraft.EnumChatFormat;
|
||||
import net.minecraft.network.chat.IChatBaseComponent;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.network.protocol.game.PacketPlayOutScoreboardDisplayObjective;
|
||||
import net.minecraft.network.protocol.game.PacketPlayOutScoreboardObjective;
|
||||
import net.minecraft.network.protocol.game.PacketPlayOutScoreboardScore;
|
||||
import net.minecraft.network.protocol.game.PacketPlayOutScoreboardTeam;
|
||||
import net.minecraft.server.ScoreboardServer;
|
||||
import net.minecraft.world.scores.Scoreboard;
|
||||
import net.minecraft.world.scores.ScoreboardObjective;
|
||||
import net.minecraft.world.scores.ScoreboardTeam;
|
||||
import net.minecraft.world.scores.ScoreboardTeamBase;
|
||||
import net.minecraft.world.scores.criteria.IScoreboardCriteria;
|
||||
import org.bukkit.craftbukkit.v1_19_R2.entity.CraftPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.Collection;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Scoreboard implementation using direct NMS code.
|
||||
*/
|
||||
public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
|
||||
|
||||
private static final ScoreboardTeamBase.EnumNameTagVisibility[] visibilities = ScoreboardTeamBase.EnumNameTagVisibility.values();
|
||||
private static final ScoreboardTeamBase.EnumTeamPush[] collisions = ScoreboardTeamBase.EnumTeamPush.values();
|
||||
private static final Scoreboard dummyScoreboard = new Scoreboard();
|
||||
|
||||
private static final Constructor<PacketPlayOutScoreboardTeam> teamConstructor;
|
||||
|
||||
static {
|
||||
try {
|
||||
teamConstructor = PacketPlayOutScoreboardTeam.class.getDeclaredConstructor(String.class, int.class, Optional.class, Collection.class);
|
||||
teamConstructor.setAccessible(true);
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Constructs new instance with given player.
|
||||
*
|
||||
* @param player
|
||||
* Player this scoreboard will belong to
|
||||
*/
|
||||
public NMSPacketScoreboard(@NotNull BukkitTabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerObjective(@NonNull Objective objective) {
|
||||
ScoreboardObjective obj = new ScoreboardObjective(
|
||||
dummyScoreboard,
|
||||
objective.getName(),
|
||||
IScoreboardCriteria.a,
|
||||
objective.getTitle().convert(),
|
||||
IScoreboardCriteria.EnumScoreboardHealthDisplay.values()[objective.getHealthDisplay().ordinal()]
|
||||
);
|
||||
objective.setPlatformObjective(obj);
|
||||
sendPacket(new PacketPlayOutScoreboardObjective(obj, ObjectiveAction.REGISTER));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDisplaySlot(@NonNull Objective objective) {
|
||||
sendPacket(new PacketPlayOutScoreboardDisplayObjective(
|
||||
objective.getDisplaySlot().ordinal(),
|
||||
(ScoreboardObjective) objective.getPlatformObjective()
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterObjective(@NonNull Objective objective) {
|
||||
sendPacket(new PacketPlayOutScoreboardObjective((ScoreboardObjective) objective.getPlatformObjective(), ObjectiveAction.UNREGISTER));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateObjective(@NonNull Objective objective) {
|
||||
ScoreboardObjective obj = (ScoreboardObjective) objective.getPlatformObjective();
|
||||
obj.a((IChatBaseComponent) objective.getTitle().convert());
|
||||
obj.a(IScoreboardCriteria.EnumScoreboardHealthDisplay.valueOf(objective.getHealthDisplay().name()));
|
||||
sendPacket(new PacketPlayOutScoreboardObjective(obj, ObjectiveAction.UPDATE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setScore(@NonNull Score score) {
|
||||
sendPacket(new PacketPlayOutScoreboardScore(
|
||||
ScoreboardServer.Action.a,
|
||||
score.getObjective().getName(),
|
||||
score.getHolder(),
|
||||
score.getValue()
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeScore(@NonNull Score score) {
|
||||
sendPacket(new PacketPlayOutScoreboardScore(
|
||||
ScoreboardServer.Action.b,
|
||||
score.getObjective().getName(),
|
||||
score.getHolder(),
|
||||
score.getValue()
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Object createTeam(@NonNull String name) {
|
||||
return new ScoreboardTeam(dummyScoreboard, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerTeam(@NonNull Team team) {
|
||||
updateTeamProperties(team);
|
||||
ScoreboardTeam t = (ScoreboardTeam) team.getPlatformTeam();
|
||||
t.g().addAll(team.getPlayers());
|
||||
sendPacket(PacketPlayOutScoreboardTeam.a(t, true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterTeam(@NonNull Team team) {
|
||||
sendPacket(PacketPlayOutScoreboardTeam.a((ScoreboardTeam) team.getPlatformTeam()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateTeam(@NonNull Team team) {
|
||||
updateTeamProperties(team);
|
||||
sendPacket(PacketPlayOutScoreboardTeam.a((ScoreboardTeam) team.getPlatformTeam(), false));
|
||||
}
|
||||
|
||||
private void updateTeamProperties(@NonNull Team team) {
|
||||
ScoreboardTeam t = (ScoreboardTeam) team.getPlatformTeam();
|
||||
t.a((team.getOptions() & 0x01) != 0);
|
||||
t.b((team.getOptions() & 0x02) != 0);
|
||||
t.a(visibilities[team.getVisibility().ordinal()]);
|
||||
t.a(collisions[team.getCollision().ordinal()]);
|
||||
t.b((IChatBaseComponent) team.getPrefix().convert());
|
||||
t.c(team.getSuffix().convert());
|
||||
t.a(EnumChatFormat.valueOf(team.getColor().name()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
@NotNull
|
||||
public Object onPacketSend(@NonNull Object packet) {
|
||||
if (packet instanceof PacketPlayOutScoreboardDisplayObjective) {
|
||||
TAB.getInstance().getFeatureManager().onDisplayObjective(
|
||||
player,
|
||||
((PacketPlayOutScoreboardDisplayObjective)packet).b(),
|
||||
((PacketPlayOutScoreboardDisplayObjective)packet).c()
|
||||
);
|
||||
}
|
||||
if (packet instanceof PacketPlayOutScoreboardObjective) {
|
||||
TAB.getInstance().getFeatureManager().onObjective(
|
||||
player,
|
||||
((PacketPlayOutScoreboardObjective)packet).d(),
|
||||
((PacketPlayOutScoreboardObjective)packet).b()
|
||||
);
|
||||
}
|
||||
if (packet instanceof PacketPlayOutScoreboardTeam) {
|
||||
int action = getMethod((PacketPlayOutScoreboardTeam) packet);
|
||||
if (action != TeamAction.UPDATE) {
|
||||
Collection<String> players = ((PacketPlayOutScoreboardTeam) packet).e();
|
||||
if (players != null) {
|
||||
String name = ((PacketPlayOutScoreboardTeam) packet).d();
|
||||
return teamConstructor.newInstance(
|
||||
name,
|
||||
action,
|
||||
((PacketPlayOutScoreboardTeam) packet).f(),
|
||||
onTeamPacket(action, name, players)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
return packet;
|
||||
}
|
||||
|
||||
private int getMethod(@NonNull PacketPlayOutScoreboardTeam team) {
|
||||
if (team.c() == PacketPlayOutScoreboardTeam.a.a) {
|
||||
return 0;
|
||||
} else if (team.c() == PacketPlayOutScoreboardTeam.a.b) {
|
||||
return 1;
|
||||
} else if (team.b() == PacketPlayOutScoreboardTeam.a.a) {
|
||||
return 3;
|
||||
} else if (team.b() == PacketPlayOutScoreboardTeam.a.b) {
|
||||
return 4;
|
||||
} else {
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the packet to the player.
|
||||
*
|
||||
* @param packet
|
||||
* Packet to send
|
||||
*/
|
||||
private void sendPacket(@NotNull Packet<?> packet) {
|
||||
((CraftPlayer)player.getPlayer()).getHandle().b.a(packet);
|
||||
}
|
||||
}
|
||||
+223
@@ -0,0 +1,223 @@
|
||||
package me.neznamy.tab.platforms.bukkit.v1_19_R2;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import com.mojang.authlib.properties.Property;
|
||||
import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.chat.component.TabComponent;
|
||||
import me.neznamy.tab.shared.features.layout.LayoutManagerImpl;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import me.neznamy.tab.shared.platform.decorators.TrackedTabList;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import net.minecraft.network.chat.IChatBaseComponent;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket;
|
||||
import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket;
|
||||
import net.minecraft.network.protocol.game.PacketPlayOutPlayerListHeaderFooter;
|
||||
import net.minecraft.world.level.EnumGamemode;
|
||||
import org.bukkit.craftbukkit.v1_19_R2.entity.CraftPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* TabList implementation using direct NMS code.
|
||||
*/
|
||||
public class NMSPacketTabList extends TrackedTabList<BukkitTabPlayer> {
|
||||
|
||||
private static final Field PLAYERS = ReflectionUtils.getOnlyField(ClientboundPlayerInfoUpdatePacket.class, List.class);
|
||||
|
||||
private static final ClientboundPlayerInfoUpdatePacket.a ADD_PLAYER = ClientboundPlayerInfoUpdatePacket.a.valueOf("ADD_PLAYER");
|
||||
private static final ClientboundPlayerInfoUpdatePacket.a UPDATE_GAME_MODE = ClientboundPlayerInfoUpdatePacket.a.valueOf("UPDATE_GAME_MODE");
|
||||
private static final ClientboundPlayerInfoUpdatePacket.a UPDATE_LATENCY = ClientboundPlayerInfoUpdatePacket.a.valueOf("UPDATE_LATENCY");
|
||||
private static final ClientboundPlayerInfoUpdatePacket.a UPDATE_DISPLAY_NAME = ClientboundPlayerInfoUpdatePacket.a.valueOf("UPDATE_DISPLAY_NAME");
|
||||
private static final ClientboundPlayerInfoUpdatePacket.a UPDATE_LISTED = ClientboundPlayerInfoUpdatePacket.a.valueOf("UPDATE_LISTED");
|
||||
|
||||
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.a> ADD_PLAYER_SET = EnumSet.allOf(ClientboundPlayerInfoUpdatePacket.a.class);
|
||||
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.a> UPDATE_GAME_MODE_SET = EnumSet.of(UPDATE_GAME_MODE);
|
||||
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.a> UPDATE_DISPLAY_NAME_SET = EnumSet.of(UPDATE_DISPLAY_NAME);
|
||||
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.a> UPDATE_LATENCY_SET = EnumSet.of(UPDATE_LATENCY);
|
||||
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.a> UPDATE_LISTED_SET = EnumSet.of(UPDATE_LISTED);
|
||||
|
||||
/**
|
||||
* Constructs new instance.
|
||||
*
|
||||
* @param player
|
||||
* Player this tablist will belong to
|
||||
*/
|
||||
public NMSPacketTabList(@NotNull BukkitTabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void removeEntry(@NonNull UUID entry) {
|
||||
sendPacket(new ClientboundPlayerInfoRemovePacket(Collections.singletonList(entry)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDisplayName0(@NonNull UUID entry, @Nullable TabComponent displayName) {
|
||||
sendPacket(UPDATE_DISPLAY_NAME_SET, entry, "", null, false, 0, 0, displayName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateLatency(@NonNull UUID entry, int latency) {
|
||||
sendPacket(UPDATE_LATENCY_SET, entry, "", null, false, latency, 0, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateGameMode(@NonNull UUID entry, int gameMode) {
|
||||
sendPacket(UPDATE_GAME_MODE_SET, entry, "", null, false, 0, gameMode, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateListed(@NonNull UUID entry, boolean listed) {
|
||||
sendPacket(UPDATE_LISTED_SET, entry, "", null, listed, 0, 0, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateListOrder(@NonNull UUID entry, int listOrder) {
|
||||
// Added in 1.21.2
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateHat(@NonNull UUID entry, boolean showHat) {
|
||||
// Added in 1.21.4
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addEntry0(@NonNull Entry entry) {
|
||||
sendPacket(ADD_PLAYER_SET, entry.getUniqueId(), entry.getName(), entry.getSkin(), entry.isListed(), entry.getLatency(),
|
||||
entry.getGameMode(), entry.getDisplayName());
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void setPlayerListHeaderFooter0(@NonNull TabComponent header, @NonNull TabComponent footer) {
|
||||
sendPacket(new PacketPlayOutPlayerListHeaderFooter(header.convert(), footer.convert()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Skin getSkin() {
|
||||
Collection<Property> properties = ((CraftPlayer)player.getPlayer()).getProfile().getProperties().get(TEXTURES_PROPERTY);
|
||||
if (properties.isEmpty()) return null; // Offline mode
|
||||
Property property = properties.iterator().next();
|
||||
return new Skin(property.getValue(), property.getSignature());
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
@Override
|
||||
@NotNull
|
||||
public Object onPacketSend(@NonNull Object packet) {
|
||||
if (packet instanceof PacketPlayOutPlayerListHeaderFooter) {
|
||||
PacketPlayOutPlayerListHeaderFooter tablist = (PacketPlayOutPlayerListHeaderFooter) packet;
|
||||
if (header == null || footer == null) return packet;
|
||||
if (tablist.a != header.convert() || tablist.b != footer.convert()) {
|
||||
return new PacketPlayOutPlayerListHeaderFooter(header.convert(), footer.convert());
|
||||
}
|
||||
}
|
||||
if (!(packet instanceof ClientboundPlayerInfoUpdatePacket)) return packet;
|
||||
ClientboundPlayerInfoUpdatePacket info = (ClientboundPlayerInfoUpdatePacket) packet;
|
||||
EnumSet<ClientboundPlayerInfoUpdatePacket.a> actions = info.b();
|
||||
List<ClientboundPlayerInfoUpdatePacket.b> updatedList = new ArrayList<>();
|
||||
boolean rewritePacket = false;
|
||||
for (ClientboundPlayerInfoUpdatePacket.b nmsData : info.c()) {
|
||||
UUID profileId = nmsData.a();
|
||||
boolean rewriteEntry = false;
|
||||
IChatBaseComponent displayName = nmsData.f();
|
||||
int latency = nmsData.d();
|
||||
int gameMode = nmsData.e().a();
|
||||
boolean listed = nmsData.c();
|
||||
if (actions.contains(UPDATE_DISPLAY_NAME)) {
|
||||
TabComponent forcedDisplayName = getForcedDisplayNames().get(profileId);
|
||||
if (forcedDisplayName != null && forcedDisplayName.convert() != displayName) {
|
||||
displayName = forcedDisplayName.convert();
|
||||
rewriteEntry = rewritePacket = true;
|
||||
}
|
||||
}
|
||||
if (actions.contains(UPDATE_GAME_MODE)) {
|
||||
if (getBlockedSpectators().contains(profileId) && gameMode == 3) {
|
||||
gameMode = 0;
|
||||
rewriteEntry = rewritePacket = true;
|
||||
}
|
||||
}
|
||||
if (actions.contains(UPDATE_LATENCY)) {
|
||||
if (getForcedLatency() != null) {
|
||||
latency = getForcedLatency();
|
||||
rewriteEntry = rewritePacket = true;
|
||||
}
|
||||
}
|
||||
if (actions.contains(UPDATE_LISTED)) {
|
||||
if (allPlayersHidden && !LayoutManagerImpl.UUIDS_SET.contains(nmsData.a())) { // Filter out layout entries
|
||||
listed = false;
|
||||
rewriteEntry = rewritePacket = true;
|
||||
}
|
||||
}
|
||||
if (actions.contains(ADD_PLAYER)) {
|
||||
TAB.getInstance().getFeatureManager().onEntryAdd(player, profileId, nmsData.b().getName());
|
||||
}
|
||||
updatedList.add(rewriteEntry ? new ClientboundPlayerInfoUpdatePacket.b(
|
||||
profileId, nmsData.b(), listed, latency, EnumGamemode.a(gameMode), displayName,
|
||||
nmsData.g()
|
||||
) : nmsData);
|
||||
}
|
||||
if (rewritePacket) {
|
||||
ClientboundPlayerInfoUpdatePacket newPacket = new ClientboundPlayerInfoUpdatePacket(actions, Collections.emptyList());
|
||||
PLAYERS.set(newPacket, updatedList);
|
||||
return newPacket;
|
||||
}
|
||||
return packet;
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private void sendPacket(@NonNull EnumSet<ClientboundPlayerInfoUpdatePacket.a> action, @NonNull UUID id, @NonNull String name,
|
||||
@Nullable Skin skin, boolean listed, int latency, int gameMode, @Nullable TabComponent displayName) {
|
||||
ClientboundPlayerInfoUpdatePacket packet = new ClientboundPlayerInfoUpdatePacket(action, Collections.emptyList());
|
||||
PLAYERS.set(packet, Collections.singletonList(new ClientboundPlayerInfoUpdatePacket.b(
|
||||
id,
|
||||
createProfile(id, name, skin),
|
||||
listed,
|
||||
latency,
|
||||
EnumGamemode.a(gameMode),
|
||||
displayName == null ? null : displayName.convert(),
|
||||
null
|
||||
)));
|
||||
sendPacket(packet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates GameProfile from given parameters.
|
||||
*
|
||||
* @param id
|
||||
* Profile ID
|
||||
* @param name
|
||||
* Profile name
|
||||
* @param skin
|
||||
* Player skin
|
||||
* @return GameProfile from given parameters
|
||||
*/
|
||||
@NotNull
|
||||
private GameProfile createProfile(@NonNull UUID id, @NonNull String name, @Nullable Skin skin) {
|
||||
GameProfile profile = new GameProfile(id, name);
|
||||
if (skin != null) {
|
||||
profile.getProperties().put(TabList.TEXTURES_PROPERTY,
|
||||
new Property(TabList.TEXTURES_PROPERTY, skin.getValue(), skin.getSignature()));
|
||||
}
|
||||
return profile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the packet to the player.
|
||||
*
|
||||
* @param packet
|
||||
* Packet to send
|
||||
*/
|
||||
private void sendPacket(@NotNull Packet<?> packet) {
|
||||
((CraftPlayer)player.getPlayer()).getHandle().b.a(packet);
|
||||
}
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
package me.neznamy.tab.platforms.bukkit.v1_19_R2;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import me.neznamy.tab.shared.platform.NettyTabListEntryTracker;
|
||||
import net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket;
|
||||
import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket;
|
||||
import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket.a;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Implementation of TabListEntryTracker.
|
||||
*/
|
||||
public class NMSTabListEntryTracker extends NettyTabListEntryTracker {
|
||||
|
||||
private static final a ADD_PLAYER = a.valueOf("ADD_PLAYER");
|
||||
|
||||
public NMSTabListEntryTracker(@NotNull Channel channel) {
|
||||
super(channel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketSend(@NotNull Object packet) {
|
||||
if (packet instanceof ClientboundPlayerInfoRemovePacket) {
|
||||
ClientboundPlayerInfoRemovePacket remove = (ClientboundPlayerInfoRemovePacket) packet;
|
||||
for (UUID id : remove.b()) {
|
||||
tablistEntries.remove(id);
|
||||
}
|
||||
}
|
||||
if (packet instanceof ClientboundPlayerInfoUpdatePacket) {
|
||||
ClientboundPlayerInfoUpdatePacket update = (ClientboundPlayerInfoUpdatePacket) packet;
|
||||
if (update.b().contains(ADD_PLAYER)) {
|
||||
for (ClientboundPlayerInfoUpdatePacket.b nmsData : update.c()) {
|
||||
tablistEntries.add(nmsData.a());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,12 +14,11 @@ repositories {
|
||||
maven("https://repo.papermc.io/repository/maven-public/") // Adventure
|
||||
}
|
||||
|
||||
val minecraftVersion = "26.3"
|
||||
val minecraftVersion = "26.2"
|
||||
|
||||
// Fabric API versions for each Minecraft version for easier backporting
|
||||
// Official website (for updating in the future): https://fabricmc.net/develop/
|
||||
val fabricApiVersions = mapOf(
|
||||
"26.3" to "0.160.5+26.3",
|
||||
"26.2" to "0.150.3+26.2",
|
||||
"26.1.2" to "0.150.0+26.1.2"
|
||||
)
|
||||
|
||||
@@ -133,12 +133,7 @@ public class FabricScoreboard extends SafeScoreboard<FabricTabPlayer> {
|
||||
if (packet instanceof ClientboundSetPlayerTeamPacket team) {
|
||||
int method = getMethod(team);
|
||||
if (method != TeamAction.UPDATE) {
|
||||
return new ClientboundSetPlayerTeamPacket(
|
||||
team.getName(),
|
||||
method,
|
||||
team.getParameters(),
|
||||
onTeamPacket(method, team.getName(), team.getPlayers())
|
||||
);
|
||||
team.players = onTeamPacket(method, team.getName(), team.getPlayers());
|
||||
}
|
||||
}
|
||||
return packet;
|
||||
|
||||
@@ -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.getPlaceholderReference(placeholder).getHandle().getLastValue(player));
|
||||
return PlaceholderResult.value(manager.getPlaceholder(placeholder).getLastValue(player));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -14,8 +14,7 @@
|
||||
"fabric-api-base": "*",
|
||||
"java": ">=25",
|
||||
"minecraft": [
|
||||
"26.2",
|
||||
"26.3"
|
||||
"26.2"
|
||||
]
|
||||
},
|
||||
"recommends": {
|
||||
|
||||
@@ -5,7 +5,8 @@ accessible field net/minecraft/server/network/ServerCommonPacketListenerImpl con
|
||||
accessible field net/minecraft/network/Connection channel Lio/netty/channel/Channel;
|
||||
|
||||
# Scoreboard team
|
||||
accessible method net/minecraft/network/protocol/game/ClientboundSetPlayerTeamPacket <init> (Ljava/lang/String;ILjava/util/Optional;Ljava/util/Collection;)V
|
||||
accessible field net/minecraft/network/protocol/game/ClientboundSetPlayerTeamPacket players Ljava/util/Collection;
|
||||
mutable field net/minecraft/network/protocol/game/ClientboundSetPlayerTeamPacket players Ljava/util/Collection;
|
||||
|
||||
# Player info
|
||||
accessible field net/minecraft/network/protocol/game/ClientboundPlayerInfoUpdatePacket entries Ljava/util/List;
|
||||
|
||||
@@ -5,6 +5,9 @@ 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;
|
||||
@@ -14,10 +17,6 @@ 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 {
|
||||
|
||||
@@ -65,7 +64,7 @@ public final class FandTabExpansion implements TabExpansion {
|
||||
String requestedPlaceholder = "%" + expansionIdentifier.substring("placeholder_".length()) + "%";
|
||||
PlaceholderManagerImpl manager = TAB.getInstance().getPlaceholderManager();
|
||||
manager.addUsedPlaceholder(requestedPlaceholder, manager);
|
||||
return manager.getPlaceholderReference(requestedPlaceholder).getHandle().parse(player);
|
||||
return manager.getPlaceholder(requestedPlaceholder).parse(player);
|
||||
}
|
||||
return player.expansionData.getValue(expansionIdentifier);
|
||||
}
|
||||
|
||||
@@ -15,14 +15,13 @@ repositories {
|
||||
maven("https://repo.papermc.io/repository/maven-public/") // Adventure
|
||||
}
|
||||
|
||||
val minecraftVersion = "26.3"
|
||||
val minecraftVersion = "26.2"
|
||||
|
||||
// Forge API versions for each Minecraft version for easier backporting
|
||||
// Official website (for updating in the future): https://files.minecraftforge.net/net/minecraftforge/forge/
|
||||
val forgeApiVersions = mapOf(
|
||||
"26.3" to "26.3-66.0.2",
|
||||
"26.2" to "26.2-65.1.3",
|
||||
"26.1.2" to "26.1.2-64.1.3"
|
||||
"26.2" to "26.2-65.0.0",
|
||||
"26.1.2" to "26.1.2-64.0.4"
|
||||
)
|
||||
|
||||
dependencies {
|
||||
|
||||
@@ -4,6 +4,7 @@ import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.platform.decorators.SafeScoreboard;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import net.minecraft.network.chat.numbers.FixedFormat;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.network.protocol.game.*;
|
||||
@@ -14,7 +15,9 @@ import net.minecraft.world.scores.criteria.ObjectiveCriteria;
|
||||
import net.minecraft.world.scores.criteria.ObjectiveCriteria.RenderType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
@@ -26,6 +29,7 @@ public class ForgeScoreboard extends SafeScoreboard<ForgeTabPlayer> {
|
||||
private static final net.minecraft.world.scores.Team.CollisionRule[] collisions = net.minecraft.world.scores.Team.CollisionRule.values();
|
||||
private static final net.minecraft.world.scores.Team.Visibility[] visibilities = net.minecraft.world.scores.Team.Visibility.values();
|
||||
private static final Scoreboard dummyScoreboard = new Scoreboard();
|
||||
private static final Field players = ReflectionUtils.getOnlyField(ClientboundSetPlayerTeamPacket.class, Collection.class);
|
||||
|
||||
/**
|
||||
* Constructs new instance with given player.
|
||||
@@ -135,12 +139,7 @@ public class ForgeScoreboard extends SafeScoreboard<ForgeTabPlayer> {
|
||||
if (packet instanceof ClientboundSetPlayerTeamPacket team) {
|
||||
int method = getMethod(team);
|
||||
if (method != TeamAction.UPDATE) {
|
||||
return new ClientboundSetPlayerTeamPacket(
|
||||
team.getName(),
|
||||
method,
|
||||
team.getParameters(),
|
||||
onTeamPacket(method, team.getName(), team.getPlayers())
|
||||
);
|
||||
players.set(team, onTeamPacket(method, team.getName(), team.getPlayers()));
|
||||
}
|
||||
}
|
||||
return packet;
|
||||
|
||||
@@ -5,10 +5,12 @@ import com.mojang.authlib.GameProfile;
|
||||
import com.mojang.authlib.properties.Property;
|
||||
import com.mojang.authlib.properties.PropertyMap;
|
||||
import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.chat.component.TabComponent;
|
||||
import me.neznamy.tab.shared.features.layout.LayoutManagerImpl;
|
||||
import me.neznamy.tab.shared.platform.decorators.TrackedTabList;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket;
|
||||
@@ -18,6 +20,7 @@ import net.minecraft.world.level.GameType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
@@ -33,6 +36,8 @@ public class ForgeTabList extends TrackedTabList<ForgeTabPlayer> {
|
||||
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateListOrder = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LIST_ORDER);
|
||||
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateHat = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_HAT);
|
||||
|
||||
private static final Field entries = ReflectionUtils.getOnlyField(ClientboundPlayerInfoUpdatePacket.class, List.class);
|
||||
|
||||
/**
|
||||
* Constructs new instance.
|
||||
*
|
||||
@@ -99,6 +104,7 @@ public class ForgeTabList extends TrackedTabList<ForgeTabPlayer> {
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
@NotNull
|
||||
public Object onPacketSend(@NonNull Object packet) {
|
||||
if (packet instanceof ClientboundTabListPacket tablist) {
|
||||
@@ -152,17 +158,18 @@ public class ForgeTabList extends TrackedTabList<ForgeTabPlayer> {
|
||||
}
|
||||
if (rewritePacket) {
|
||||
ClientboundPlayerInfoUpdatePacket newPacket = new ClientboundPlayerInfoUpdatePacket(actions, Collections.emptyList());
|
||||
newPacket.entries = updatedList;
|
||||
entries.set(newPacket, updatedList);
|
||||
return newPacket;
|
||||
}
|
||||
}
|
||||
return packet;
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private void sendPacket(@NonNull EnumSet<ClientboundPlayerInfoUpdatePacket.Action> action, @NonNull UUID id, @NonNull String name, @Nullable Skin skin,
|
||||
boolean listed, int latency, int gameMode, @Nullable TabComponent displayName, int listOrder, boolean showHat) {
|
||||
ClientboundPlayerInfoUpdatePacket packet = new ClientboundPlayerInfoUpdatePacket(action, Collections.emptyList());
|
||||
packet.entries = Collections.singletonList(new ClientboundPlayerInfoUpdatePacket.Entry(
|
||||
entries.set(packet, Collections.singletonList(new ClientboundPlayerInfoUpdatePacket.Entry(
|
||||
id,
|
||||
action.contains(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER) ? createProfile(id, name, skin) : null,
|
||||
listed,
|
||||
@@ -172,7 +179,7 @@ public class ForgeTabList extends TrackedTabList<ForgeTabPlayer> {
|
||||
showHat,
|
||||
listOrder,
|
||||
null
|
||||
));
|
||||
)));
|
||||
sendPacket(packet);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
# Scoreboard team
|
||||
public net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket <init>(Ljava/lang/String;ILjava/util/Optional;Ljava/util/Collection;)V
|
||||
|
||||
# Player info
|
||||
public-f net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket entries
|
||||
@@ -15,6 +15,6 @@ displayURL = "@website@"
|
||||
[[dependencies.tab]]
|
||||
modId = "minecraft"
|
||||
mandatory = true
|
||||
versionRange = "[26.2],[26.3]"
|
||||
versionRange = "[26.2]"
|
||||
ordering = "NONE"
|
||||
side = "SERVER"
|
||||
Vendored
BIN
Binary file not shown.
+1
-1
@@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.1-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.6.0-bin.zip
|
||||
networkTimeout=10000
|
||||
retries=0
|
||||
retryBackOffMs=500
|
||||
|
||||
@@ -27,6 +27,8 @@ val platformPaths = setOf(
|
||||
":bukkit:v1_16_R3",
|
||||
":bukkit:v1_17_R1",
|
||||
":bukkit:v1_18_R2",
|
||||
":bukkit:v1_19_R1",
|
||||
":bukkit:v1_19_R2",
|
||||
":bukkit:v1_19_R3",
|
||||
":bukkit:v1_20_R1",
|
||||
":bukkit:v1_20_R2",
|
||||
|
||||
@@ -15,14 +15,13 @@ repositories {
|
||||
maven("https://repo.papermc.io/repository/maven-public/") // Adventure
|
||||
}
|
||||
|
||||
val minecraftVersion = "26.3"
|
||||
val minecraftVersion = "26.2"
|
||||
|
||||
// NeoForge API versions for each Minecraft version for easier backporting
|
||||
// Official website (for updating in the future): https://projects.neoforged.net/neoforged/neoforge
|
||||
val neoforgeApiVersions = mapOf(
|
||||
"26.3" to "26.3.0.1-beta",
|
||||
"26.2" to "26.2.0.88",
|
||||
"26.1.2" to "26.1.2.109"
|
||||
"26.2" to "26.2.0.1-beta",
|
||||
"26.1.2" to "26.1.2.17-beta"
|
||||
)
|
||||
|
||||
dependencies {
|
||||
|
||||
@@ -133,12 +133,7 @@ public class NeoForgeScoreboard extends SafeScoreboard<NeoForgeTabPlayer> {
|
||||
if (packet instanceof ClientboundSetPlayerTeamPacket team) {
|
||||
int method = getMethod(team);
|
||||
if (method != TeamAction.UPDATE) {
|
||||
return new ClientboundSetPlayerTeamPacket(
|
||||
team.getName(),
|
||||
method,
|
||||
team.getParameters(),
|
||||
onTeamPacket(method, team.getName(), team.getPlayers())
|
||||
);
|
||||
team.players = onTeamPacket(method, team.getName(), team.getPlayers());
|
||||
}
|
||||
}
|
||||
return packet;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Scoreboard team
|
||||
public net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket <init>(Ljava/lang/String;ILjava/util/Optional;Ljava/util/Collection;)V
|
||||
public-f net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket players
|
||||
|
||||
# Player info
|
||||
public-f net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket entries
|
||||
@@ -15,6 +15,6 @@ description = '''@description@'''
|
||||
[[dependencies.tab]]
|
||||
modId = "minecraft"
|
||||
type = "required"
|
||||
versionRange = "[26.2],[26.3]"
|
||||
versionRange = "[26.2]"
|
||||
ordering = "NONE"
|
||||
side = "SERVER"
|
||||
@@ -52,6 +52,8 @@ include(":bukkit:v1_12_R1")
|
||||
include(":bukkit:v1_16_R3")
|
||||
include(":bukkit:v1_17_R1")
|
||||
include(":bukkit:v1_18_R2")
|
||||
include(":bukkit:v1_19_R1")
|
||||
include(":bukkit:v1_19_R2")
|
||||
include(":bukkit:v1_19_R3")
|
||||
include(":bukkit:v1_20_R1")
|
||||
include(":bukkit:v1_20_R2")
|
||||
|
||||
@@ -11,7 +11,6 @@ import org.jetbrains.annotations.NotNull;
|
||||
public enum ProtocolVersion {
|
||||
|
||||
UNKNOWN,
|
||||
V26_3 (777),
|
||||
V26_2 (776),
|
||||
V26_1_2 (775),
|
||||
V26_1_1 (775),
|
||||
|
||||
@@ -3,9 +3,7 @@ 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;
|
||||
@@ -317,16 +315,6 @@ 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);
|
||||
|
||||
@@ -149,13 +149,9 @@ public class DumpCommand extends SubCommand {
|
||||
mysql.put("password", "*CENSORED*");
|
||||
Map<String, Object> proxySupport = (Map<String, Object>) config.get("proxy-support");
|
||||
Map<String, Object> redis = (Map<String, Object>) proxySupport.get("redis");
|
||||
if (redis != null) {
|
||||
redis.put("url", "*CENSORED*");
|
||||
}
|
||||
redis.put("url", "*CENSORED*");
|
||||
Map<String, Object> rabbitmq = (Map<String, Object>) proxySupport.get("rabbitmq");
|
||||
if (rabbitmq != null) {
|
||||
rabbitmq.put("url", "*CENSORED*");
|
||||
}
|
||||
rabbitmq.put("url", "*CENSORED*");
|
||||
}
|
||||
|
||||
@NotNull
|
||||
|
||||
@@ -26,10 +26,17 @@ public class BossBarCommand extends SubCommand {
|
||||
|
||||
@Override
|
||||
public void execute(@Nullable TabPlayer sender, @NotNull String[] args) {
|
||||
SubCommand command;
|
||||
if (args.length == 0) {
|
||||
args = new String[]{"toggle"};
|
||||
command = getSubcommands().get("toggle");
|
||||
if (command.hasPermission(sender)) {
|
||||
command.execute(sender, new String[0]);
|
||||
} else {
|
||||
sendMessage(sender, getMessages().getNoPermission());
|
||||
}
|
||||
return;
|
||||
}
|
||||
SubCommand command = getSubcommands().get(args[0].toLowerCase());
|
||||
command = getSubcommands().get(args[0].toLowerCase());
|
||||
if (command != null) {
|
||||
if (command.hasPermission(sender)) {
|
||||
command.execute(sender, Arrays.copyOfRange(args, 1, args.length));
|
||||
|
||||
@@ -8,7 +8,6 @@ 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;
|
||||
|
||||
@@ -32,14 +31,11 @@ public class BossBarOffCommand extends SubCommand {
|
||||
return;
|
||||
}
|
||||
TabPlayer target = sender;
|
||||
boolean silent = args.length > 0 && args[args.length-1].equals("-s");
|
||||
int providedArgs = args.length - (silent ? 1 : 0);
|
||||
if (providedArgs > 0) {
|
||||
if (args.length > 0) {
|
||||
if (hasPermission(sender, TabConstants.Permission.COMMAND_BOSSBAR_TOGGLE_OTHER)) {
|
||||
String playerName = args[0];
|
||||
target = TAB.getInstance().getPlayer(playerName);
|
||||
target = TAB.getInstance().getPlayer(args[0]);
|
||||
if (target == null) {
|
||||
sendMessage(sender, getMessages().getPlayerNotFound(playerName));
|
||||
sendMessage(sender, getMessages().getPlayerNotFound(args[0]));
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
@@ -50,17 +46,13 @@ 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) {
|
||||
List<String> suggestions = new ArrayList<>();
|
||||
suggestions.addAll(getOnlinePlayers(arguments[0]));
|
||||
suggestions.addAll(getStartingArgument(Collections.singletonList("-s"), arguments[0]));
|
||||
return suggestions;
|
||||
}
|
||||
if (arguments.length == 1) return getOnlinePlayers(arguments[0]);
|
||||
if (arguments.length == 2) return getStartingArgument(Collections.singletonList("-s"), arguments[1]);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ 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;
|
||||
|
||||
@@ -32,14 +31,11 @@ public class BossBarOnCommand extends SubCommand {
|
||||
return;
|
||||
}
|
||||
TabPlayer target = sender;
|
||||
boolean silent = args.length > 0 && args[args.length-1].equals("-s");
|
||||
int providedArgs = args.length - (silent ? 1 : 0);
|
||||
if (providedArgs > 0) {
|
||||
if (args.length > 0) {
|
||||
if (hasPermission(sender, TabConstants.Permission.COMMAND_BOSSBAR_TOGGLE_OTHER)) {
|
||||
String playerName = args[0];
|
||||
target = TAB.getInstance().getPlayer(playerName);
|
||||
target = TAB.getInstance().getPlayer(args[0]);
|
||||
if (target == null) {
|
||||
sendMessage(sender, getMessages().getPlayerNotFound(playerName));
|
||||
sendMessage(sender, getMessages().getPlayerNotFound(args[0]));
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
@@ -50,17 +46,13 @@ 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) {
|
||||
List<String> suggestions = new ArrayList<>();
|
||||
suggestions.addAll(getOnlinePlayers(arguments[0]));
|
||||
suggestions.addAll(getStartingArgument(Collections.singletonList("-s"), arguments[0]));
|
||||
return suggestions;
|
||||
}
|
||||
if (arguments.length == 1) return getOnlinePlayers(arguments[0]);
|
||||
if (arguments.length == 2) return getStartingArgument(Collections.singletonList("-s"), arguments[1]);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
+5
-13
@@ -8,7 +8,6 @@ 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;
|
||||
|
||||
@@ -32,14 +31,11 @@ public class BossBarToggleCommand extends SubCommand {
|
||||
return;
|
||||
}
|
||||
TabPlayer target = sender;
|
||||
boolean silent = args.length > 0 && args[args.length-1].equals("-s");
|
||||
int providedArgs = args.length - (silent ? 1 : 0);
|
||||
if (providedArgs > 0) {
|
||||
if (args.length > 0) {
|
||||
if (hasPermission(sender, TabConstants.Permission.COMMAND_BOSSBAR_TOGGLE_OTHER)) {
|
||||
String playerName = args[0];
|
||||
target = TAB.getInstance().getPlayer(playerName);
|
||||
target = TAB.getInstance().getPlayer(args[0]);
|
||||
if (target == null) {
|
||||
sendMessage(sender, getMessages().getPlayerNotFound(playerName));
|
||||
sendMessage(sender, getMessages().getPlayerNotFound(args[0]));
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
@@ -50,17 +46,13 @@ 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) {
|
||||
List<String> suggestions = new ArrayList<>();
|
||||
suggestions.addAll(getOnlinePlayers(arguments[0]));
|
||||
suggestions.addAll(getStartingArgument(Collections.singletonList("-s"), arguments[0]));
|
||||
return suggestions;
|
||||
}
|
||||
if (arguments.length == 1) return getOnlinePlayers(arguments[0]);
|
||||
if (arguments.length == 2) return getStartingArgument(Collections.singletonList("-s"), arguments[1]);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
+9
-2
@@ -26,10 +26,17 @@ public class ScoreboardCommand extends SubCommand {
|
||||
|
||||
@Override
|
||||
public void execute(@Nullable TabPlayer sender, @NotNull String[] args) {
|
||||
SubCommand command;
|
||||
if (args.length == 0) {
|
||||
args = new String[]{"toggle"};
|
||||
command = getSubcommands().get("toggle");
|
||||
if (command.hasPermission(sender)) {
|
||||
command.execute(sender, new String[0]);
|
||||
} else {
|
||||
sendMessage(sender, getMessages().getNoPermission());
|
||||
}
|
||||
return;
|
||||
}
|
||||
SubCommand command = getSubcommands().get(args[0].toLowerCase());
|
||||
command = getSubcommands().get(args[0].toLowerCase());
|
||||
if (command != null) {
|
||||
if (command.hasPermission(sender)) {
|
||||
command.execute(sender, Arrays.copyOfRange(args, 1, args.length));
|
||||
|
||||
+5
-13
@@ -8,7 +8,6 @@ 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;
|
||||
|
||||
@@ -32,14 +31,11 @@ public class ScoreboardOffCommand extends SubCommand {
|
||||
return;
|
||||
}
|
||||
TabPlayer target = sender;
|
||||
boolean silent = args.length > 0 && args[args.length-1].equals("-s");
|
||||
int providedArgs = args.length - (silent ? 1 : 0);
|
||||
if (providedArgs > 0) {
|
||||
if (args.length > 0) {
|
||||
if (hasPermission(sender, TabConstants.Permission.COMMAND_SCOREBOARD_TOGGLE_OTHER)) {
|
||||
String playerName = args[0];
|
||||
target = TAB.getInstance().getPlayer(playerName);
|
||||
target = TAB.getInstance().getPlayer(args[0]);
|
||||
if (target == null) {
|
||||
sendMessage(sender, getMessages().getPlayerNotFound(playerName));
|
||||
sendMessage(sender, getMessages().getPlayerNotFound(args[0]));
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
@@ -50,17 +46,13 @@ 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) {
|
||||
List<String> suggestions = new ArrayList<>();
|
||||
suggestions.addAll(getOnlinePlayers(arguments[0]));
|
||||
suggestions.addAll(getStartingArgument(Collections.singletonList("-s"), arguments[0]));
|
||||
return suggestions;
|
||||
}
|
||||
if (arguments.length == 1) return getOnlinePlayers(arguments[0]);
|
||||
if (arguments.length == 2) return getStartingArgument(Collections.singletonList("-s"), arguments[1]);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
+5
-13
@@ -8,7 +8,6 @@ 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;
|
||||
|
||||
@@ -32,14 +31,11 @@ public class ScoreboardOnCommand extends SubCommand {
|
||||
return;
|
||||
}
|
||||
TabPlayer target = sender;
|
||||
boolean silent = args.length > 0 && args[args.length-1].equals("-s");
|
||||
int providedArgs = args.length - (silent ? 1 : 0);
|
||||
if (providedArgs > 0) {
|
||||
if (args.length > 0) {
|
||||
if (hasPermission(sender, TabConstants.Permission.COMMAND_SCOREBOARD_TOGGLE_OTHER)) {
|
||||
String playerName = args[0];
|
||||
target = TAB.getInstance().getPlayer(playerName);
|
||||
target = TAB.getInstance().getPlayer(args[0]);
|
||||
if (target == null) {
|
||||
sendMessage(sender, getMessages().getPlayerNotFound(playerName));
|
||||
sendMessage(sender, getMessages().getPlayerNotFound(args[0]));
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
@@ -50,17 +46,13 @@ 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) {
|
||||
List<String> suggestions = new ArrayList<>();
|
||||
suggestions.addAll(getOnlinePlayers(arguments[0]));
|
||||
suggestions.addAll(getStartingArgument(Collections.singletonList("-s"), arguments[0]));
|
||||
return suggestions;
|
||||
}
|
||||
if (arguments.length == 1) return getOnlinePlayers(arguments[0]);
|
||||
if (arguments.length == 2) return getStartingArgument(Collections.singletonList("-s"), arguments[1]);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
+5
-13
@@ -8,7 +8,6 @@ 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;
|
||||
|
||||
@@ -32,14 +31,11 @@ public class ScoreboardToggleCommand extends SubCommand {
|
||||
return;
|
||||
}
|
||||
TabPlayer target = sender;
|
||||
boolean silent = args.length > 0 && args[args.length-1].equals("-s");
|
||||
int providedArgs = args.length - (silent ? 1 : 0);
|
||||
if (providedArgs > 0) {
|
||||
if (args.length > 0) {
|
||||
if (hasPermission(sender, TabConstants.Permission.COMMAND_SCOREBOARD_TOGGLE_OTHER)) {
|
||||
String playerName = args[0];
|
||||
target = TAB.getInstance().getPlayer(playerName);
|
||||
target = TAB.getInstance().getPlayer(args[0]);
|
||||
if (target == null) {
|
||||
sendMessage(sender, getMessages().getPlayerNotFound(playerName));
|
||||
sendMessage(sender, getMessages().getPlayerNotFound(args[0]));
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
@@ -50,17 +46,13 @@ 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) {
|
||||
List<String> suggestions = new ArrayList<>();
|
||||
suggestions.addAll(getOnlinePlayers(arguments[0]));
|
||||
suggestions.addAll(getStartingArgument(Collections.singletonList("-s"), arguments[0]));
|
||||
return suggestions;
|
||||
}
|
||||
if (arguments.length == 1) return getOnlinePlayers(arguments[0]);
|
||||
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", Condition.fromShortFormat(disableCondition).invert().toShortFormat());
|
||||
defaultDesign.put("display-condition", new Condition(disableCondition).invert().toShortFormat());
|
||||
}
|
||||
}
|
||||
defaultDesign.put("header", defaultHeader);
|
||||
|
||||
+33
-82
@@ -25,9 +25,8 @@ public class YamlPropertyConfigurationFile extends YamlConfigurationFile impleme
|
||||
private final String PER_WORLD = "per-world";
|
||||
|
||||
private final String category;
|
||||
private final ConfigurationSection root = getConfigurationSection("");
|
||||
private final Collection<String> worldGroups = section(PER_WORLD).getKeys().stream().map(Object::toString).collect(Collectors.toList());
|
||||
private final Collection<String> serverGroups = section(PER_SERVER).getKeys().stream().map(Object::toString).collect(Collectors.toList());
|
||||
private final Collection<String> worldGroups = new ArrayList<>(this.<String, Object>getMap(PER_WORLD).keySet());
|
||||
private final Collection<String> serverGroups = new ArrayList<>(this.<String, Object>getMap(PER_SERVER).keySet());
|
||||
|
||||
/**
|
||||
* Constructs new instance and attempts to load specified configuration file.
|
||||
@@ -49,29 +48,26 @@ public class YamlPropertyConfigurationFile extends YamlConfigurationFile impleme
|
||||
public YamlPropertyConfigurationFile(@Nullable InputStream source, @NonNull File destination) throws IOException {
|
||||
super(source, destination);
|
||||
category = destination.getName().contains("groups") ? "group" : "user";
|
||||
for (Object key : root.getKeys()) {
|
||||
String entry = key.toString();
|
||||
if (entry.equals(PER_SERVER)) {
|
||||
for (Map.Entry<Object, Object> entry : getValues().entrySet()) {
|
||||
if (entry.getKey().equals(PER_SERVER)) {
|
||||
for (String server : serverGroups) {
|
||||
ConfigurationSection serverSection = section(PER_SERVER, server);
|
||||
for (Object name : serverSection.getKeys()) {
|
||||
for (Object property : serverSection.getConfigurationSection(name.toString()).getKeys()) {
|
||||
checkProperty(destination.getName(), category, name.toString(), property.toString(), server, null, true);
|
||||
for (String name : this.<String, Object>getMap(PER_SERVER + "." + server).keySet()) {
|
||||
for (String property : this.<String, Object>getMap(PER_SERVER + "." + server + "." + name).keySet()) {
|
||||
checkProperty(destination.getName(), category, name, property, server, null, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (entry.equals(PER_WORLD)) {
|
||||
} else if (entry.getKey().equals(PER_WORLD)) {
|
||||
for (String world : worldGroups) {
|
||||
ConfigurationSection worldSection = section(PER_WORLD, world);
|
||||
for (Object name : worldSection.getKeys()) {
|
||||
for (Object property : worldSection.getConfigurationSection(name.toString()).getKeys()) {
|
||||
checkProperty(destination.getName(), category, name.toString(), property.toString(), null, world, true);
|
||||
for (String name : this.<String, Object>getMap(PER_WORLD + "." + world).keySet()) {
|
||||
for (String property : this.<String, Object>getMap(PER_WORLD + "." + world + "." + name).keySet()) {
|
||||
checkProperty(destination.getName(), category, name, property, null, world, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (Object property : root.getConfigurationSection(entry).getKeys()) {
|
||||
checkProperty(destination.getName(), category, entry, property.toString(), null, null, true);
|
||||
for (String property : this.<String, Object>getMap(entry.getKey().toString()).keySet()) {
|
||||
checkProperty(destination.getName(), category, entry.getKey().toString(), property, null, null, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -79,16 +75,13 @@ public class YamlPropertyConfigurationFile extends YamlConfigurationFile impleme
|
||||
|
||||
@Override
|
||||
public void setProperty(@NonNull String name, @NonNull String property, @Nullable Server server, @Nullable World world, @Nullable String value) {
|
||||
ConfigurationSection target;
|
||||
if (world != null) {
|
||||
target = getOrCreateSection(PER_WORLD, world.getName(), name);
|
||||
set(String.format("%s.%s.%s.%s", PER_WORLD, world.getName(), name, property), fromString(value));
|
||||
} else if (server != null) {
|
||||
target = getOrCreateSection(PER_SERVER, server.getName(), name);
|
||||
set(String.format("%s.%s.%s.%s", PER_SERVER, server.getName(), name, property), fromString(value));
|
||||
} else {
|
||||
target = getOrCreateSection(name);
|
||||
set(String.format("%s.%s", name, property), fromString(value));
|
||||
}
|
||||
target.put(property, fromString(value));
|
||||
save();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -96,22 +89,22 @@ public class YamlPropertyConfigurationFile extends YamlConfigurationFile impleme
|
||||
String worldName = world == null ? null : world.getName();
|
||||
String serverName = server == null ? null : server.getName();
|
||||
Object value;
|
||||
if ((value = section(PER_WORLD, TAB.getInstance().getConfiguration().getGroup(worldGroups, worldName), name).getObject(property)) != null) {
|
||||
if ((value = getObject(new String[] {PER_WORLD, TAB.getInstance().getConfiguration().getGroup(worldGroups, worldName), name, property})) != null) {
|
||||
return new String[] {toString(value), category + "=" + name + ", world=" + worldName};
|
||||
}
|
||||
if ((value = section(PER_WORLD, TAB.getInstance().getConfiguration().getGroup(worldGroups, worldName), TabConstants.DEFAULT_GROUP).getObject(property)) != null) {
|
||||
if ((value = getObject(new String[] {PER_WORLD, TAB.getInstance().getConfiguration().getGroup(worldGroups, worldName), TabConstants.DEFAULT_GROUP, property})) != null) {
|
||||
return new String[] {toString(value), category + "=" + TabConstants.DEFAULT_GROUP + ", world=" + worldName};
|
||||
}
|
||||
if ((value = section(PER_SERVER, TAB.getInstance().getConfiguration().getServerGroup(serverGroups, server), name).getObject(property)) != null) {
|
||||
if ((value = getObject(new String[] {PER_SERVER, TAB.getInstance().getConfiguration().getServerGroup(serverGroups, server), name, property})) != null) {
|
||||
return new String[] {toString(value), category + "=" + name + ", server=" + serverName};
|
||||
}
|
||||
if ((value = section(PER_SERVER, TAB.getInstance().getConfiguration().getServerGroup(serverGroups, server), TabConstants.DEFAULT_GROUP).getObject(property)) != null) {
|
||||
if ((value = getObject(new String[] {PER_SERVER, TAB.getInstance().getConfiguration().getServerGroup(serverGroups, server), TabConstants.DEFAULT_GROUP, property})) != null) {
|
||||
return new String[] {toString(value), category + "=" + TabConstants.DEFAULT_GROUP + ", server=" + serverName};
|
||||
}
|
||||
if ((value = section(name).getObject(property)) != null) {
|
||||
if ((value = getObject(new String[] {name, property})) != null) {
|
||||
return new String[] {toString(value), category + "=" + name};
|
||||
}
|
||||
if ((value = section(TabConstants.DEFAULT_GROUP).getObject(property)) != null) {
|
||||
if ((value = getObject(new String[] {TabConstants.DEFAULT_GROUP, property})) != null) {
|
||||
return new String[] {toString(value), category + "=" + TabConstants.DEFAULT_GROUP};
|
||||
}
|
||||
return new String[0];
|
||||
@@ -119,81 +112,39 @@ public class YamlPropertyConfigurationFile extends YamlConfigurationFile impleme
|
||||
|
||||
@Override
|
||||
public void remove(@NonNull String name) {
|
||||
root.remove(name);
|
||||
for (Object world : section(PER_WORLD).getKeys()) {
|
||||
section(PER_WORLD, world.toString()).remove(name);
|
||||
}
|
||||
for (Object server : section(PER_SERVER).getKeys()) {
|
||||
section(PER_SERVER, server.toString()).remove(name);
|
||||
}
|
||||
save();
|
||||
set(name, null);
|
||||
getMap(PER_WORLD).keySet().forEach(world -> set(PER_WORLD + "." + world + "." + name, null));
|
||||
getMap(PER_SERVER).keySet().forEach(server -> set(PER_SERVER + "." + server + "." + name, null));
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Map<String, Object> getGlobalSettings(@NonNull String name) {
|
||||
Map<String, Object> settings = root.getMap(name);
|
||||
return settings == null ? new LinkedHashMap<>() : settings;
|
||||
return getMap(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Map<String, Map<String, Object>> getPerWorldSettings(@NonNull String name) {
|
||||
return getSettingsByGroup(PER_WORLD, name);
|
||||
return convertMap(getMap(PER_WORLD), name);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Map<String, Map<String, Object>> getPerServerSettings(@NonNull String name) {
|
||||
return getSettingsByGroup(PER_SERVER, name);
|
||||
return convertMap(getMap(PER_SERVER), name);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Set<String> getAllEntries() {
|
||||
Set<Object> set = new HashSet<>(root.getKeys());
|
||||
Set<Object> set = new HashSet<>(values.keySet());
|
||||
set.remove(PER_WORLD);
|
||||
set.remove(PER_SERVER);
|
||||
addNestedEntryNames(set, PER_WORLD);
|
||||
addNestedEntryNames(set, PER_SERVER);
|
||||
Map<String, Map<String, Map<String, String>>> perWorld = getMap(PER_WORLD);
|
||||
perWorld.values().forEach(m -> set.addAll(m.keySet()));
|
||||
Map<String, Map<String, Map<String, String>>> perServer = getMap(PER_SERVER);
|
||||
perServer.values().forEach(m -> set.addAll(m.keySet()));
|
||||
return set.stream().map(Object::toString).collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private ConfigurationSection section(@NotNull String... path) {
|
||||
ConfigurationSection section = root;
|
||||
for (String key : path) {
|
||||
section = section.getConfigurationSection(key);
|
||||
}
|
||||
return section;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private ConfigurationSection getOrCreateSection(@NotNull String... path) {
|
||||
ConfigurationSection section = root;
|
||||
for (String key : path) {
|
||||
if (section.getMap(key) == null) {
|
||||
section.put(key, new LinkedHashMap<>());
|
||||
}
|
||||
section = section.getConfigurationSection(key);
|
||||
}
|
||||
return section;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private Map<String, Map<String, Object>> getSettingsByGroup(@NotNull String sectionName, @NotNull String name) {
|
||||
Map<String, Map<String, Object>> settings = new HashMap<>();
|
||||
ConfigurationSection sections = section(sectionName);
|
||||
for (Object key : sections.getKeys()) {
|
||||
settings.put(key.toString(), sections.getConfigurationSection(key.toString()).getMap(name));
|
||||
}
|
||||
return settings;
|
||||
}
|
||||
|
||||
private void addNestedEntryNames(@NotNull Set<Object> entries, @NotNull String sectionName) {
|
||||
ConfigurationSection sections = section(sectionName);
|
||||
for (Object key : sections.getKeys()) {
|
||||
entries.addAll(sections.getConfigurationSection(key.toString()).getKeys());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -96,15 +96,15 @@ public class Config {
|
||||
placeholders = PlaceholdersConfiguration.fromSection(config.getConfigurationSection("placeholders"));
|
||||
components = ComponentConfiguration.fromSection(config.getConfigurationSection("components"));
|
||||
|
||||
if (config.getBoolean("bossbar.enabled", false)) bossbar = BossBarConfiguration.fromSection(config.getConfigurationSection("bossbar"));
|
||||
if (config.getBoolean("belowname-objective.enabled", false)) belowname = BelowNameConfiguration.fromSection(config.getConfigurationSection("belowname-objective"));
|
||||
if (config.getBoolean("playerlist-objective.enabled", true)) playerlistObjective = PlayerListObjectiveConfiguration.fromSection(config.getConfigurationSection("playerlist-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"));
|
||||
if (config.getBoolean("layout.enabled", false)) layout = LayoutConfiguration.fromSection(config.getConfigurationSection("layout"));
|
||||
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"));
|
||||
|
||||
@@ -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.getReference().getUsedByFeatures()) {
|
||||
for (RefreshableFeature f : placeholder.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.getReference().getUsedByFeatures()) {
|
||||
for (RefreshableFeature f : placeholder.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.getReference().getUsedByFeatures());
|
||||
set.addAll(placeholder.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 : existing.getUsedByFeatures()) {
|
||||
for (RefreshableFeature f : ((TabPlaceholder)placeholder).getUsedByFeatures()) {
|
||||
TimedCaughtTask task = new TimedCaughtTask(cpu, () -> f.refresh(p, true), f.getFeatureName(), f.getRefreshDisplayName());
|
||||
if (f instanceof CustomThreaded) {
|
||||
((CustomThreaded) f).getCustomThread().execute(task);
|
||||
@@ -235,12 +235,10 @@ 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;
|
||||
}
|
||||
}
|
||||
@@ -339,7 +337,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).getReference().addUsedFeature(feature)) {
|
||||
if (getPlaceholder(identifier).addUsedFeature(feature)) {
|
||||
recalculateUsedPlaceholders();
|
||||
TabPlaceholder p = getPlaceholder(identifier);
|
||||
for (TabPlayer all : TAB.getInstance().getOnlinePlayers()) {
|
||||
@@ -357,7 +355,7 @@ public class PlaceholderManagerImpl extends RefreshableFeature implements Placeh
|
||||
* Feature using the placeholder
|
||||
*/
|
||||
public synchronized void addUsedPlaceholder(@NonNull TabPlaceholder placeholder, @NonNull RefreshableFeature feature) {
|
||||
if (placeholder.getReference().addUsedFeature(feature)) {
|
||||
if (placeholder.addUsedFeature(feature)) {
|
||||
recalculateUsedPlaceholders();
|
||||
for (TabPlayer all : TAB.getInstance().getOnlinePlayers()) {
|
||||
all.expansionData.setPlaceholderValue(placeholder.getIdentifier(), placeholder.getLastValueSafe(all));
|
||||
@@ -369,7 +367,7 @@ public class PlaceholderManagerImpl extends RefreshableFeature implements Placeh
|
||||
* Updates array of used placeholders.
|
||||
*/
|
||||
private void recalculateUsedPlaceholders() {
|
||||
usedPlaceholders = registeredPlaceholders.values().stream().filter(p -> !p.getUsedByFeatures().isEmpty()).toArray(PlaceholderReference[]::new);
|
||||
usedPlaceholders = registeredPlaceholders.values().stream().filter(p -> !p.getHandle().getUsedByFeatures().isEmpty()).toArray(PlaceholderReference[]::new);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -391,7 +389,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(), p.getHandle().getLastValue(null));
|
||||
connectedPlayer.expansionData.setPlaceholderValue(p.getIdentifier(), ((ServerPlaceholderImpl) p.getHandle()).getLastValue());
|
||||
}
|
||||
}
|
||||
// Initialize to avoid onVanishStatusChange being called in the loop after joining because previous value was null
|
||||
@@ -495,8 +493,6 @@ public class PlaceholderManagerImpl extends RefreshableFeature implements Placeh
|
||||
public void registerServerPlaceholder(@NonNull Pattern identifier, int refresh, @NonNull Function<Matcher, Supplier<String>> function) {
|
||||
ensureActive();
|
||||
registeredDynamicPlaceholders.put(identifier, id -> groups -> new ServerPlaceholderImpl(id, refresh, function.apply(groups)));
|
||||
replaceMatchingPlaceholders(identifier,
|
||||
(id, groups) -> new ServerPlaceholderImpl(id, refresh, function.apply(groups)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -504,8 +500,6 @@ public class PlaceholderManagerImpl extends RefreshableFeature implements Placeh
|
||||
@NonNull Function<Matcher, Function<me.neznamy.tab.api.TabPlayer, String>> function) {
|
||||
ensureActive();
|
||||
registeredDynamicPlaceholders.put(identifier, id -> groups -> new PlayerPlaceholderImpl(id, refresh, function.apply(groups)));
|
||||
replaceMatchingPlaceholders(identifier,
|
||||
(id, groups) -> new PlayerPlaceholderImpl(id, refresh, function.apply(groups)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -513,18 +507,6 @@ public class PlaceholderManagerImpl extends RefreshableFeature implements Placeh
|
||||
@NonNull Function<Matcher, BiFunction<me.neznamy.tab.api.TabPlayer, me.neznamy.tab.api.TabPlayer, String>> function) {
|
||||
ensureActive();
|
||||
registeredDynamicPlaceholders.put(identifier, id -> groups -> new RelationalPlaceholderImpl(id, refresh, function.apply(groups)));
|
||||
replaceMatchingPlaceholders(identifier,
|
||||
(id, groups) -> new RelationalPlaceholderImpl(id, refresh, function.apply(groups)));
|
||||
}
|
||||
|
||||
private void replaceMatchingPlaceholders(@NotNull Pattern identifier,
|
||||
@NotNull BiFunction<String, Matcher, ? extends Placeholder> factory) {
|
||||
for (Entry<String, PlaceholderReference> entry : new ArrayList<>(registeredPlaceholders.entrySet())) {
|
||||
Matcher matcher = identifier.matcher(entry.getKey());
|
||||
if (!matcher.matches()) continue;
|
||||
bridgePlaceholders.remove(entry.getKey());
|
||||
registerPlaceholder(factory.apply(entry.getKey(), matcher));
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -602,7 +584,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()), p.getLastValue(null)));
|
||||
serverPlaceholders.add(Arrays.asList(p.getIdentifier(), String.valueOf(reference.getRefresh()), ((ServerPlaceholderImpl) p).getLastValue()));
|
||||
} else if (p instanceof PlayerPlaceholderImpl) {
|
||||
playerPlaceholders.add(Arrays.asList(p.getIdentifier(), String.valueOf(reference.getRefresh()), p.getLastValueSafe(player)));
|
||||
} else if (p instanceof RelationalPlaceholderImpl) {
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
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;
|
||||
@@ -30,7 +28,7 @@ import java.util.stream.Collectors;
|
||||
*/
|
||||
@Getter
|
||||
public class BelowName extends RefreshableFeature implements JoinListener, QuitListener, Loadable, UnLoadable,
|
||||
WorldSwitchListener, ServerSwitchListener, CustomThreaded, ProxyFeature, VanishListener, Dumpable, BelowNameManager {
|
||||
WorldSwitchListener, ServerSwitchListener, CustomThreaded, ProxyFeature, VanishListener, Dumpable {
|
||||
|
||||
/** Objective name used by this feature */
|
||||
public static final String OBJECTIVE_NAME = "TAB-BelowName";
|
||||
@@ -72,7 +70,7 @@ public class BelowName extends RefreshableFeature implements JoinListener, QuitL
|
||||
for (TabPlayer loaded : onlinePlayers.getPlayers()) {
|
||||
loaded.setBelowNameDistance(configuration.getViewDistance());
|
||||
loadProperties(loaded);
|
||||
if (configuredDisabled(loaded)) {
|
||||
if (disableChecker.isDisableConditionMet(loaded)) {
|
||||
loaded.belowNameData.disabled.set(true);
|
||||
} else {
|
||||
register(loaded);
|
||||
@@ -106,7 +104,7 @@ public class BelowName extends RefreshableFeature implements JoinListener, QuitL
|
||||
onlinePlayers.addPlayer(connectedPlayer);
|
||||
connectedPlayer.setBelowNameDistance(configuration.getViewDistance());
|
||||
loadProperties(connectedPlayer);
|
||||
if (configuredDisabled(connectedPlayer)) {
|
||||
if (disableChecker.isDisableConditionMet(connectedPlayer)) {
|
||||
connectedPlayer.belowNameData.disabled.set(true);
|
||||
} else {
|
||||
register(connectedPlayer);
|
||||
@@ -145,24 +143,6 @@ 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;
|
||||
}
|
||||
updateVisibility(p, disabledNow);
|
||||
}
|
||||
|
||||
private boolean configuredDisabled(@NotNull TabPlayer p) {
|
||||
return 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 {
|
||||
@@ -240,15 +220,6 @@ 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.
|
||||
*
|
||||
@@ -290,10 +261,9 @@ public class BelowName extends RefreshableFeature implements JoinListener, QuitL
|
||||
}
|
||||
|
||||
private void updatePlayer(@NotNull TabPlayer player) {
|
||||
int value = getValue(player);
|
||||
for (TabPlayer all : onlinePlayers.getPlayers()) {
|
||||
if (all != player) setScore(player, all, getValue(all), all.belowNameData.fancyValue.getFormat(player));
|
||||
setScore(all, player, value, player.belowNameData.fancyValue.getFormat(all));
|
||||
setScore(player, all, getValue(all), all.belowNameData.fancyValue.getFormat(player));
|
||||
if (all != player) setScore(all, player, getValue(player), player.belowNameData.fancyValue.getFormat(all));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -327,91 +297,11 @@ public class BelowName extends RefreshableFeature implements JoinListener, QuitL
|
||||
@Override
|
||||
public void onVanishStatusChange(@NotNull TabPlayer player) {
|
||||
if (player.isVanished()) return;
|
||||
int value = getValue(player);
|
||||
for (TabPlayer viewer : onlinePlayers.getPlayers()) {
|
||||
setScore(viewer, player, value, player.belowNameData.fancyValue.getFormat(viewer));
|
||||
setScore(viewer, player, getValue(player), player.belowNameData.fancyValue.getFormat(viewer));
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------
|
||||
// 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) {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package me.neznamy.tab.shared.features.belowname;
|
||||
|
||||
import me.neznamy.tab.shared.Property;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
@@ -24,7 +23,4 @@ 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(0, configuration.getDirection().translateSlot(slot));
|
||||
UUID id = new UUID(1, configuration.getDirection().translateSlot(slot));
|
||||
uuids[slot-1] = id;
|
||||
UUIDS_SET.add(id);
|
||||
}
|
||||
@@ -77,10 +77,18 @@ public class LayoutManagerImpl extends RefreshableFeature implements LayoutManag
|
||||
p.layoutData.sortingString = p.sortingData.fullTeamName;
|
||||
sortedPlayers.put(p, p.sortingData.fullTeamName);
|
||||
LayoutPattern highest = getHighestLayout(p);
|
||||
sendLayout(p, highest);
|
||||
if (highest != null) {
|
||||
sendLayout(p, highest);
|
||||
}
|
||||
for (TabPlayer all : TAB.getInstance().getOnlinePlayers()) {
|
||||
if (all.layoutData.currentLayout != null) all.layoutData.currentLayout.view.onJoin(p);
|
||||
}
|
||||
|
||||
// Unformat original entries for players who can see a layout to avoid spaces due to unparsed placeholders and such
|
||||
if (highest == null) return;
|
||||
for (TabPlayer all : TAB.getInstance().getOnlinePlayers()) {
|
||||
p.getTabList().updateDisplayName(all, null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -104,30 +112,17 @@ public class LayoutManagerImpl extends RefreshableFeature implements LayoutManag
|
||||
LayoutPattern current = p.layoutData.currentLayout == null ? null : p.layoutData.currentLayout.view.getPattern();
|
||||
if (highest != current) {
|
||||
if (current != null) p.layoutData.currentLayout.view.destroy();
|
||||
sendLayout(p, highest);
|
||||
p.layoutData.currentLayout = null;
|
||||
if (highest != null) {
|
||||
sendLayout(p, highest);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void sendLayout(@NotNull TabPlayer player, @Nullable LayoutPattern pattern) {
|
||||
if (pattern != null) {
|
||||
LayoutBase view = new FakeEntryLayout(this, pattern, player);
|
||||
player.layoutData.currentLayout = new LayoutData(view);
|
||||
view.send();
|
||||
if (playerList != null) {
|
||||
// Unformat original entries for players who can see a layout to avoid spaces due to unparsed placeholders and such
|
||||
for (TabPlayer all : TAB.getInstance().getOnlinePlayers()) {
|
||||
player.getTabList().updateDisplayName(all, null);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
player.layoutData.currentLayout = null;
|
||||
if (playerList != null) {
|
||||
// Reformat original entries for players without any layout
|
||||
for (TabPlayer all : TAB.getInstance().getOnlinePlayers()) {
|
||||
player.getTabList().updateDisplayName(all, playerList.getTabFormat(all, player));
|
||||
}
|
||||
}
|
||||
}
|
||||
private void sendLayout(@NotNull TabPlayer player, @NotNull LayoutPattern pattern) {
|
||||
LayoutBase view = new FakeEntryLayout(this, pattern, player);
|
||||
player.layoutData.currentLayout = new LayoutData(view);
|
||||
view.send();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -215,7 +210,11 @@ public class LayoutManagerImpl extends RefreshableFeature implements LayoutManag
|
||||
|
||||
@Override
|
||||
public void resetLayout(@NonNull me.neznamy.tab.api.TabPlayer player) {
|
||||
sendLayout(player, null);
|
||||
ensureActive();
|
||||
TabPlayer p = (TabPlayer) player;
|
||||
p.ensureLoaded();
|
||||
p.layoutData.forcedLayout = null;
|
||||
refresh(p, false);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
|
||||
-8
@@ -1,8 +1,6 @@
|
||||
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;
|
||||
|
||||
@@ -22,10 +20,4 @@ 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;
|
||||
}
|
||||
+5
-139
@@ -1,8 +1,6 @@
|
||||
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;
|
||||
@@ -28,7 +26,7 @@ import java.util.stream.Collectors;
|
||||
*/
|
||||
@Getter
|
||||
public class YellowNumber extends RefreshableFeature implements JoinListener, QuitListener, Loadable,
|
||||
CustomThreaded, ProxyFeature, VanishListener, Dumpable, PlayerListObjectiveManager {
|
||||
CustomThreaded, ProxyFeature, VanishListener, Dumpable {
|
||||
|
||||
/** Objective name used by this feature */
|
||||
public static final String OBJECTIVE_NAME = "TAB-PlayerList";
|
||||
@@ -69,7 +67,7 @@ public class YellowNumber extends RefreshableFeature implements JoinListener, Qu
|
||||
Map<TabPlayer, Integer> values = new HashMap<>();
|
||||
for (TabPlayer loaded : onlinePlayers.getPlayers()) {
|
||||
loadProperties(loaded);
|
||||
if (configuredDisabled(loaded)) {
|
||||
if (disableChecker.isDisableConditionMet(loaded)) {
|
||||
loaded.playerlistObjectiveData.disabled.set(true);
|
||||
} else {
|
||||
register(loaded);
|
||||
@@ -94,7 +92,7 @@ public class YellowNumber extends RefreshableFeature implements JoinListener, Qu
|
||||
public void onJoin(@NotNull TabPlayer connectedPlayer) {
|
||||
onlinePlayers.addPlayer(connectedPlayer);
|
||||
loadProperties(connectedPlayer);
|
||||
if (configuredDisabled(connectedPlayer)) {
|
||||
if (disableChecker.isDisableConditionMet(connectedPlayer)) {
|
||||
connectedPlayer.playerlistObjectiveData.disabled.set(true);
|
||||
} else {
|
||||
register(connectedPlayer);
|
||||
@@ -133,24 +131,6 @@ 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;
|
||||
}
|
||||
updateVisibility(p, disabledNow);
|
||||
}
|
||||
|
||||
private boolean configuredDisabled(@NotNull TabPlayer p) {
|
||||
return 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 {
|
||||
@@ -222,27 +202,12 @@ public class YellowNumber extends RefreshableFeature implements JoinListener, Qu
|
||||
player.getScoreboard().registerObjective(
|
||||
OBJECTIVE_NAME,
|
||||
cache.get(player.playerlistObjectiveData.title.updateAndGet()),
|
||||
getHealthDisplay(player),
|
||||
configuration.getHealthDisplay(),
|
||||
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.
|
||||
*
|
||||
@@ -297,110 +262,11 @@ public class YellowNumber extends RefreshableFeature implements JoinListener, Qu
|
||||
@Override
|
||||
public void onVanishStatusChange(@NotNull TabPlayer player) {
|
||||
if (player.isVanished()) return;
|
||||
int value = getValue(player);
|
||||
for (TabPlayer viewer : onlinePlayers.getPlayers()) {
|
||||
setScore(viewer, player, value, player.playerlistObjectiveData.fancyValue.getFormat(viewer));
|
||||
setScore(viewer, player, getValue(player), player.playerlistObjectiveData.fancyValue.getFormat(viewer));
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------
|
||||
// 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) {
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
+78
-12
@@ -9,7 +9,9 @@ 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.ScoreboardLineHolder;
|
||||
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.types.Conditional;
|
||||
import me.neznamy.tab.shared.features.types.CustomThreaded;
|
||||
import me.neznamy.tab.shared.features.types.RefreshableFeature;
|
||||
@@ -17,6 +19,7 @@ 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;
|
||||
@@ -63,7 +66,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);
|
||||
this(manager, name, definition, false, false);
|
||||
displayCondition = TAB.getInstance().getPlaceholderManager().getConditionManager().getByNameOrExpression(definition.getDisplayCondition());
|
||||
if (displayCondition != null) {
|
||||
manager.addUsedPlaceholder(displayCondition.getPlaceholderIdentifier());
|
||||
@@ -79,10 +82,12 @@ 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 api) {
|
||||
public ScoreboardImpl(@NonNull ScoreboardManagerImpl manager, @NonNull String name, @NonNull ScoreboardDefinition definition, boolean dynamicLinesOnly, boolean api) {
|
||||
this.manager = manager;
|
||||
this.name = name;
|
||||
this.api = api;
|
||||
@@ -90,12 +95,37 @@ 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 = "";
|
||||
ScoreboardLineHolder score = new ScoreboardLineHolder(this, line, i+1);
|
||||
ScoreboardLine score;
|
||||
if (dynamicLinesOnly) {
|
||||
score = new StableDynamicLine(this, i+1, line);
|
||||
} else {
|
||||
score = registerLine(i+1, line);
|
||||
}
|
||||
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.
|
||||
@@ -116,10 +146,11 @@ public class ScoreboardImpl extends RefreshableFeature implements me.neznamy.tab
|
||||
p.getScoreboard().setDisplaySlot(ScoreboardManagerImpl.OBJECTIVE_NAME, Scoreboard.DisplaySlot.SIDEBAR);
|
||||
}
|
||||
for (Line s : lines) {
|
||||
((ScoreboardLineHolder)s).register(p);
|
||||
((ScoreboardLine)s).register(p);
|
||||
}
|
||||
players.add(p);
|
||||
p.scoreboardData.activeScoreboard = this;
|
||||
recalculateScores(p);
|
||||
p.expansionData.setScoreboardName(name);
|
||||
}
|
||||
|
||||
@@ -133,12 +164,18 @@ 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) {
|
||||
p.getScoreboard().unregisterTeam(((ScoreboardLineHolder) l).getTeamName());
|
||||
ScoreboardLine line = (ScoreboardLine) l;
|
||||
if (line.isShownTo(p)) {
|
||||
p.getScoreboard().unregisterTeam(line.getTeamName());
|
||||
line.removePlayerSilently(p);
|
||||
}
|
||||
}
|
||||
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("");
|
||||
}
|
||||
|
||||
@@ -159,6 +196,30 @@ 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.
|
||||
*
|
||||
@@ -167,6 +228,9 @@ 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
|
||||
@@ -197,11 +261,12 @@ public class ScoreboardImpl extends RefreshableFeature implements me.neznamy.tab
|
||||
@Override
|
||||
public void addLine(@NonNull String text) {
|
||||
ensureActive();
|
||||
ScoreboardLineHolder line = new ScoreboardLineHolder(this, text, lines.size()+1);
|
||||
StableDynamicLine line = new StableDynamicLine(this, lines.size()+1, text);
|
||||
TAB.getInstance().getFeatureManager().registerFeature(TabConstants.Feature.scoreboardLine(name, lines.size()), line);
|
||||
lines.add(line);
|
||||
for (TabPlayer p : players) {
|
||||
line.register(p);
|
||||
recalculateScores(p);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,10 +274,11 @@ 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) + ")");
|
||||
ScoreboardLineHolder line = (ScoreboardLineHolder) lines.get(index);
|
||||
ScoreboardLine line = (ScoreboardLine) lines.get(index);
|
||||
lines.remove(line);
|
||||
for (TabPlayer p : players) {
|
||||
line.unregister(p);
|
||||
recalculateScores(p);
|
||||
}
|
||||
TAB.getInstance().getFeatureManager().unregisterFeature(TabConstants.Feature.scoreboardLine(name, index));
|
||||
}
|
||||
@@ -220,13 +286,13 @@ public class ScoreboardImpl extends RefreshableFeature implements me.neznamy.tab
|
||||
@Override
|
||||
public void setLines(@NonNull List<String> newLines) {
|
||||
ensureActive();
|
||||
int commonSize = Math.min(lines.size(), newLines.size());
|
||||
int commonSize = Math.min(this.lines.size(), newLines.size());
|
||||
for (int i = 0; i < commonSize; i++) {
|
||||
String newText = newLines.get(i) == null ? "" : newLines.get(i);
|
||||
if (!lines.get(i).getText().equals(newText))
|
||||
lines.get(i).setText(newText);
|
||||
if (!this.lines.get(i).getText().equals(newText))
|
||||
this.lines.get(i).setText(newText);
|
||||
}
|
||||
for (int i = lines.size() - 1; i >= commonSize; i--)
|
||||
for (int i = this.lines.size() - 1; i >= commonSize; i--)
|
||||
removeLine(i);
|
||||
for (int i = commonSize; i < newLines.size(); i++)
|
||||
addLine(newLines.get(i) == null ? "" : newLines.get(i));
|
||||
|
||||
+4
-57
@@ -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.ScoreboardLineHolder;
|
||||
import me.neznamy.tab.shared.features.scoreboard.lines.ScoreboardLine;
|
||||
import me.neznamy.tab.shared.features.types.*;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
@@ -34,7 +34,6 @@ 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");
|
||||
|
||||
@@ -190,34 +189,6 @@ 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)) {
|
||||
@@ -229,30 +200,6 @@ 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;
|
||||
@@ -283,8 +230,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((ScoreboardLineHolder) line).textProperty.get() + " || " +
|
||||
player.scoreboardData.lineProperties.get((ScoreboardLineHolder) line).numberFormatProperty.get());
|
||||
lines.add(player.scoreboardData.lineProperties.get((ScoreboardLine) line).get() + " || " +
|
||||
player.scoreboardData.numberFormatProperties.get((ScoreboardLine) line).get());
|
||||
}
|
||||
put("lines", lines);
|
||||
}});
|
||||
@@ -302,7 +249,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);
|
||||
ScoreboardImpl sb = new ScoreboardImpl(this, name, new ScoreboardDefinition(null, title, lines), true, true);
|
||||
registeredScoreboards.put(name, sb);
|
||||
return sb;
|
||||
}
|
||||
|
||||
+12
-24
@@ -1,8 +1,7 @@
|
||||
package me.neznamy.tab.shared.features.scoreboard;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import me.neznamy.tab.shared.Property;
|
||||
import me.neznamy.tab.shared.features.scoreboard.lines.ScoreboardLineHolder;
|
||||
import me.neznamy.tab.shared.features.scoreboard.lines.ScoreboardLine;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -36,30 +35,19 @@ public class ScoreboardPlayerData {
|
||||
@Nullable
|
||||
public Property titleProperty;
|
||||
|
||||
/** Properties of scoreboard lines of scoreboard the player can currently see */
|
||||
/** Map of line text properties */
|
||||
@NotNull
|
||||
public final Map<ScoreboardLineHolder, LineProperties> lineProperties = new IdentityHashMap<>();
|
||||
public final Map<ScoreboardLine, Property> lineProperties = new IdentityHashMap<>();
|
||||
|
||||
/**
|
||||
* Data class for storing properties of a scoreboard line for a player.
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
public static class LineProperties {
|
||||
/** Map of line player name properties (used in long lines) */
|
||||
@NotNull
|
||||
public final Map<ScoreboardLine, Property> lineNameProperties = new IdentityHashMap<>();
|
||||
|
||||
/** Property of line text */
|
||||
@NotNull
|
||||
public final Property textProperty;
|
||||
/** Map of line score properties */
|
||||
@NotNull
|
||||
public final Map<ScoreboardLine, Property> scoreProperties = 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;
|
||||
}
|
||||
/** Map of line NumberFormat properties */
|
||||
@NotNull
|
||||
public final Map<ScoreboardLine, Property> numberFormatProperties = new IdentityHashMap<>();
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
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());
|
||||
}
|
||||
}
|
||||
+62
-115
@@ -1,19 +1,14 @@
|
||||
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:
|
||||
@@ -21,129 +16,81 @@ import java.util.Collections;
|
||||
* 1.8.x - 1.12.x: up to 66 characters
|
||||
* 1.13+: unlimited
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class LongLine implements ScoreboardLine {
|
||||
public class LongLine extends ScoreboardLine {
|
||||
|
||||
/** 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]);
|
||||
}
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
@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 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());
|
||||
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(), "");
|
||||
} else {
|
||||
player.getScoreboard().removeScore(ScoreboardManagerImpl.OBJECTIVE_NAME, properties.scoreName);
|
||||
removeLine(refreshed, refreshed.scoreboardData.lineNameProperties.get(this).get());
|
||||
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
|
||||
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
|
||||
);
|
||||
updateTeam(player, values);
|
||||
holder.setScore(player, values[1]);
|
||||
properties.scoreName = values[1];
|
||||
addLine(refreshed, values[1], values[0], values[2]);
|
||||
refreshed.scoreboardData.lineNameProperties.get(this).changeRawValue(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 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]));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregister(@NotNull TabPlayer player) {
|
||||
player.getScoreboard().removeScore(
|
||||
ScoreboardManagerImpl.OBJECTIVE_NAME,
|
||||
player.scoreboardData.lineProperties.get(holder).scoreName
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
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];
|
||||
public void unregister(@NonNull TabPlayer p) {
|
||||
if (p.scoreboardData.activeScoreboard == parent) {
|
||||
removeLine(p, p.scoreboardData.lineNameProperties.get(this).get());
|
||||
}
|
||||
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)};
|
||||
@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
|
||||
@NotNull
|
||||
public String getPlayerName(@NonNull TabPlayer viewer) {
|
||||
return viewer.scoreboardData.lineNameProperties.get(this).get();
|
||||
}
|
||||
}
|
||||
-94
@@ -1,94 +0,0 @@
|
||||
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, ""};
|
||||
}
|
||||
}
|
||||
}
|
||||
+257
-15
@@ -1,36 +1,278 @@
|
||||
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;
|
||||
|
||||
/**
|
||||
* An interface representing a line in the scoreboard.
|
||||
* Abstract class representing a line of scoreboard
|
||||
*/
|
||||
public interface ScoreboardLine {
|
||||
@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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the line for the specified player.
|
||||
* Registers this line to the player
|
||||
*
|
||||
* @param player
|
||||
* the player for whom the line should be registered
|
||||
* @param p
|
||||
* player to register line to
|
||||
*/
|
||||
void register(@NotNull TabPlayer player);
|
||||
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);
|
||||
|
||||
/**
|
||||
* Refreshes the line for the specified player.
|
||||
* Returns player name of this line for specified viewer.
|
||||
*
|
||||
* @param player
|
||||
* the player for whom the line should be refreshed
|
||||
* @param viewer
|
||||
* Player to get line name for
|
||||
* @return Name of this line for specified viewer
|
||||
*/
|
||||
void refresh(@NotNull TabPlayer player);
|
||||
@NotNull
|
||||
public String getPlayerName(@NonNull TabPlayer viewer) {
|
||||
return forcedPlayerNameStart;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters the line for the specified player.
|
||||
* 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.
|
||||
*
|
||||
* @param player
|
||||
* the player for whom the line should be unregistered
|
||||
* Player to check
|
||||
* @return {@code true} if shown, {@code false} if not
|
||||
*/
|
||||
void unregister(@NotNull TabPlayer player);
|
||||
public boolean isShownTo(@NonNull TabPlayer player) {
|
||||
return shownPlayers.contains(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.
|
||||
*
|
||||
* @param player
|
||||
* Player to send the update to
|
||||
* @param prefix
|
||||
* Prefix to use
|
||||
* @param suffix
|
||||
* Suffix to use
|
||||
*/
|
||||
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()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Silently removes players from the list of players this line is shown to.
|
||||
*
|
||||
* @param player
|
||||
* Player to remove
|
||||
*/
|
||||
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";
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns last color codes used in provided text.
|
||||
@@ -40,7 +282,7 @@ public interface ScoreboardLine {
|
||||
* @return last colors used in provided text or empty string if nothing was found
|
||||
*/
|
||||
@NotNull
|
||||
default String getLastColors(@NotNull String input) {
|
||||
protected String getLastColors(@NotNull String input) {
|
||||
StringBuilder result = new StringBuilder();
|
||||
int length = input.length();
|
||||
for (int index = length - 1; index > -1; index--) {
|
||||
@@ -58,4 +300,4 @@ public interface ScoreboardLine {
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
-242
@@ -1,242 +0,0 @@
|
||||
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
@@ -0,0 +1,147 @@
|
||||
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,14 +2,10 @@ 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
|
||||
@@ -34,16 +30,6 @@ 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.
|
||||
*
|
||||
@@ -52,31 +38,4 @@ 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+14
-5
@@ -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.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
/**
|
||||
@@ -30,6 +30,12 @@ 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
|
||||
*
|
||||
@@ -39,12 +45,15 @@ 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);
|
||||
// SnakeYAML converts yes & no to booleans, making them not work when used without "
|
||||
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 "
|
||||
if ("true".equals(key)) {
|
||||
replacements.put("yes", value);
|
||||
replacements.put("Yes", value);
|
||||
@@ -72,7 +81,7 @@ public class PlaceholderReplacementPattern {
|
||||
*/
|
||||
@NotNull
|
||||
public String findReplacement(@NotNull String output) {
|
||||
if (this == EMPTY) return output;
|
||||
if (empty) return output;
|
||||
String replacement = findReplacement0(output);
|
||||
if (replacement.contains("%value%")) {
|
||||
replacement = replacement.replace("%value%", output);
|
||||
|
||||
+2
-1
@@ -182,8 +182,9 @@ 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 = Condition.fromDefinition(condition.getValue());
|
||||
Condition c = new Condition(condition.getValue());
|
||||
TAB.getInstance().getPlaceholderManager().getConditionManager().registerCondition(c);
|
||||
}
|
||||
manager.getConditionManager().finishSetups();
|
||||
}
|
||||
}
|
||||
+112
-125
@@ -1,6 +1,7 @@
|
||||
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;
|
||||
@@ -8,6 +9,7 @@ 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;
|
||||
@@ -22,6 +24,7 @@ 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 {
|
||||
|
||||
@@ -50,37 +53,68 @@ 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 parameters and analyzes content
|
||||
* to determine refresh interval.
|
||||
* Constructs new instance with given definition and registers
|
||||
* this condition to list as well as the placeholder.
|
||||
*
|
||||
* @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)
|
||||
* @param definition
|
||||
* Condition definition from configuration
|
||||
*/
|
||||
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;
|
||||
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();
|
||||
}
|
||||
|
||||
if (primitive) return;
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
|
||||
List<PlaceholderReference> placeholdersInConditions = new ArrayList<>();
|
||||
PlaceholderManagerImpl manager = TAB.getInstance().getPlaceholderManager();
|
||||
if (manager == null) return; // Header / footer config conversion, the placeholder manager is not initialized yet
|
||||
placeholdersInConditions.addAll(manager.detectPlaceholders(yes).stream().map(manager::getPlaceholderReference).collect(Collectors.toList()));
|
||||
placeholdersInConditions.addAll(manager.detectPlaceholders(no).stream().map(manager::getPlaceholderReference).collect(Collectors.toList()));
|
||||
private void analyzeContent() {
|
||||
for (ConditionalExpression expression : expressions) {
|
||||
if (expression instanceof Permission || expression instanceof NotPermission) {
|
||||
int permissionRefresh = TAB.getInstance().getConfiguration().getConfig().getPermissionRefreshInterval();
|
||||
@@ -91,11 +125,50 @@ public class Condition {
|
||||
placeholdersInConditions.addAll(Arrays.stream(comparator.getRightSide().getPlaceholders()).map(ConditionPlaceholder::getRealPlaceholder).collect(Collectors.toList()));
|
||||
}
|
||||
}
|
||||
manager.addUsedPlaceholderReferences(placeholdersInConditions);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
String identifier = getPlaceholderIdentifier();
|
||||
String relIdentifier = getRelationalPlaceholderIdentifier();
|
||||
TabPlaceholder placeholder;
|
||||
RelationalPlaceholderImpl relationalPlaceholder;
|
||||
if (hasRelationalContent()) {
|
||||
relationalPlaceholder = manager.registerRelationalPlaceholder(
|
||||
relIdentifier,
|
||||
@@ -120,11 +193,15 @@ public class Condition {
|
||||
);
|
||||
}
|
||||
for (PlaceholderReference reference : placeholdersInConditions) {
|
||||
reference.addParent(placeholder.getReference());
|
||||
TabPlaceholder placeholder = reference.getHandle();
|
||||
placeholder.addParent(this.placeholder);
|
||||
if (hasRelationalContent()) {
|
||||
reference.addParent(relationalPlaceholder.getReference());
|
||||
placeholder.addParent(relationalPlaceholder);
|
||||
}
|
||||
this.placeholder.addChild(placeholder);
|
||||
relationalPlaceholder.addChild(placeholder);
|
||||
}
|
||||
TAB.getInstance().getPlaceholderManager().addUsedPlaceholderReferences(placeholdersInConditions);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -183,14 +260,13 @@ public class Condition {
|
||||
*/
|
||||
@NotNull
|
||||
public Condition invert() {
|
||||
return new Condition(
|
||||
return new Condition(new ConditionsSection.ConditionDefinition(
|
||||
"inverted:" + name,
|
||||
expressions.stream().map(ConditionalExpression::invert).collect(Collectors.toList()),
|
||||
expressions.stream().map(expr -> expr.invert().toShortFormat()).collect(Collectors.toList()),
|
||||
!type,
|
||||
yes,
|
||||
no,
|
||||
false
|
||||
);
|
||||
no
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -236,93 +312,4 @@ 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;
|
||||
}
|
||||
|
||||
}
|
||||
+13
-1
@@ -53,9 +53,21 @@ public class ConditionManager {
|
||||
} else if (registeredConditions.containsKey(anonVersion)) {
|
||||
return registeredConditions.get(anonVersion);
|
||||
} else {
|
||||
Condition c = Condition.fromShortFormat(string);
|
||||
Condition c = new Condition(string);
|
||||
c.finishSetup();
|
||||
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.NOT_EQUALS);
|
||||
registerConditionType(">", Operator.GREATER_THAN);
|
||||
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.ENDS_WITH);
|
||||
registerConditionType("|-", Operator.STARTS_WITH);
|
||||
registerConditionType("<-", Operator.CONTAINS);
|
||||
registerConditionType("<", Operator.LESS_THAN);
|
||||
registerConditionType(">", Operator.GREATER_THAN);
|
||||
registerConditionType("-|", Operator.ENDS_WITH);
|
||||
registerConditionType("|-", Operator.STARTS_WITH);
|
||||
registerConditionType("!=", Operator.NOT_EQUALS);
|
||||
registerConditionType("=", Operator.EQUALS);
|
||||
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", true);
|
||||
super("false", Collections.emptyList(), true, "true", "false");
|
||||
}
|
||||
|
||||
@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", true);
|
||||
super("true", Collections.emptyList(), true, "true", "false");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+19
-36
@@ -23,8 +23,7 @@ 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
|
||||
@@ -49,10 +48,10 @@ public class PlayerPlaceholderImpl extends TabPlaceholder implements PlayerPlace
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateValue(@NonNull me.neznamy.tab.api.TabPlayer player, @Nullable String returnedValue) {
|
||||
if (hasValueChanged((TabPlayer) player, returnedValue, true)) {
|
||||
public void updateValue(@NonNull me.neznamy.tab.api.TabPlayer player, @Nullable String value) {
|
||||
if (hasValueChanged((TabPlayer) player, value, true)) {
|
||||
if (!player.isLoaded()) return; // Updated on join
|
||||
for (RefreshableFeature r : reference.getUsedByFeatures()) {
|
||||
for (RefreshableFeature r : getUsedByFeatures()) {
|
||||
TimedCaughtTask task = new TimedCaughtTask(TAB.getInstance().getCpu(), () -> r.refresh((TabPlayer) player, false),
|
||||
r.getFeatureName(), r.getRefreshDisplayName());
|
||||
if (r instanceof CustomThreaded) {
|
||||
@@ -78,7 +77,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().getReference().getUsedByFeatures());
|
||||
features.addAll(entry.getKey().getUsedByFeatures());
|
||||
}
|
||||
}
|
||||
if (!player.isLoaded()) return;
|
||||
@@ -98,21 +97,20 @@ public class PlayerPlaceholderImpl extends TabPlaceholder implements PlayerPlace
|
||||
*
|
||||
* @param p
|
||||
* Player to update value for
|
||||
* @param returnedValue
|
||||
* New value returned by the placeholder function
|
||||
* @param value
|
||||
* New value
|
||||
* @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 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)) {
|
||||
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)) {
|
||||
if (updateParents) updateParents(p);
|
||||
p.expansionData.setPlaceholderValue(identifier, newEvaluatedValue);
|
||||
p.expansionData.setPlaceholderValue(identifier, newValue);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -120,40 +118,25 @@ public class PlayerPlaceholderImpl extends TabPlaceholder implements PlayerPlace
|
||||
|
||||
@Override
|
||||
public void updateFromNested(@NonNull TabPlayer player) {
|
||||
updateValue(player, request(player));
|
||||
hasValueChanged(player, request(player), true);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public synchronized String getLastValue(@Nullable TabPlayer p) {
|
||||
if (p == null) return identifier;
|
||||
String value = p.lastPlaceholderEvaluatedValues.get(this);
|
||||
String value = p.lastPlaceholderValues.get(this);
|
||||
if (value != null) return value;
|
||||
|
||||
// Value not present, initialize
|
||||
p.lastPlaceholderReturnedValues.put(this, identifier);
|
||||
p.lastPlaceholderEvaluatedValues.put(this, replacements.findReplacement(identifier));
|
||||
p.lastPlaceholderValues.put(this, replacements.findReplacement(identifier));
|
||||
hasValueChanged(p, request(p), false);
|
||||
return p.lastPlaceholderEvaluatedValues.get(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public synchronized 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);
|
||||
return p.lastPlaceholderValues.get(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String getLastValueSafe(@NotNull TabPlayer player) {
|
||||
return player.lastPlaceholderEvaluatedValues.getOrDefault(this, identifier);
|
||||
return player.lastPlaceholderValues.getOrDefault(this, identifier);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+5
-10
@@ -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 : reference.getUsedByFeatures()) {
|
||||
for (RefreshableFeature r : getUsedByFeatures()) {
|
||||
TimedCaughtTask task = new TimedCaughtTask(TAB.getInstance().getCpu(), () -> r.refresh((TabPlayer) target, true),
|
||||
r.getFeatureName(), r.getRefreshDisplayName());
|
||||
if (r instanceof CustomThreaded) {
|
||||
@@ -95,8 +95,8 @@ public class RelationalPlaceholderImpl extends TabPlaceholder implements Relatio
|
||||
String value = request(viewer, target);
|
||||
String s = replacements.findReplacement(String.valueOf(value));
|
||||
viewer.lastRelationalValues.computeIfAbsent(this, v -> Collections.synchronizedMap(new WeakHashMap<>())).put(target, s);
|
||||
if (!target.isLoaded()) continue; // Updated on join
|
||||
for (RefreshableFeature f : reference.getUsedByFeatures()) {
|
||||
if (!target.isLoaded()) return; // Updated on join
|
||||
for (RefreshableFeature f : 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 : reference.getUsedByFeatures()) {
|
||||
for (RefreshableFeature f : 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 evaluate(output, target);
|
||||
return replacements.findReplacement(output);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -152,11 +152,6 @@ 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;
|
||||
|
||||
+11
-23
@@ -20,17 +20,12 @@ 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 lastReturnedValue = identifier;
|
||||
|
||||
/** Last known value with all nested placeholders replaced and output replacements applied */
|
||||
@NotNull
|
||||
private String lastEvaluatedValue = identifier;
|
||||
private String lastValue = identifier;
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters
|
||||
@@ -58,7 +53,7 @@ public class ServerPlaceholderImpl extends TabPlaceholder implements ServerPlace
|
||||
@Override
|
||||
public void updateValue(@Nullable String value) {
|
||||
if (hasValueChanged(value)) {
|
||||
for (RefreshableFeature r : reference.getUsedByFeatures()) {
|
||||
for (RefreshableFeature r : 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());
|
||||
@@ -72,16 +67,15 @@ public class ServerPlaceholderImpl extends TabPlaceholder implements ServerPlace
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasValueChanged(@Nullable String returnedValue) {
|
||||
if (returnedValue == null || returnedValue.equals(ERROR_VALUE)) return false;
|
||||
lastReturnedValue = returnedValue;
|
||||
String newEvaluatedValue = evaluate(returnedValue, null);
|
||||
public boolean hasValueChanged(@Nullable String value) {
|
||||
if (value == null) return false;
|
||||
String newValue = setPlaceholders(replacements.findReplacement(value), null);
|
||||
|
||||
if (!lastEvaluatedValue.equals(newEvaluatedValue)) {
|
||||
lastEvaluatedValue = newEvaluatedValue;
|
||||
if (!ERROR_VALUE.equals(newValue) && !lastValue.equals(newValue)) {
|
||||
lastValue = newValue;
|
||||
for (TabPlayer player : TAB.getInstance().getOnlinePlayers()) {
|
||||
updateParents(player);
|
||||
player.expansionData.setPlaceholderValue(identifier, newEvaluatedValue);
|
||||
player.expansionData.setPlaceholderValue(identifier, newValue);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -90,25 +84,19 @@ public class ServerPlaceholderImpl extends TabPlaceholder implements ServerPlace
|
||||
|
||||
@Override
|
||||
public void updateFromNested(@NonNull TabPlayer unused) {
|
||||
updateValue(request());
|
||||
hasValueChanged(request());
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String getLastValue(@Nullable TabPlayer p) {
|
||||
return lastEvaluatedValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String getLastReturnedValue(@Nullable TabPlayer player) {
|
||||
return lastReturnedValue;
|
||||
return lastValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String getLastValueSafe(@NotNull TabPlayer player) {
|
||||
return lastEvaluatedValue;
|
||||
return lastValue;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,20 +2,18 @@ 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.ArrayList;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* General collection of variables and functions shared between all placeholder types
|
||||
@@ -38,8 +36,17 @@ public abstract class TabPlaceholder implements Placeholder {
|
||||
/** Configured placeholder output replacements */
|
||||
@NonNull protected final PlaceholderReplacementPattern replacements;
|
||||
|
||||
@Setter
|
||||
protected PlaceholderReference reference;
|
||||
/**
|
||||
* 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<>());
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters and loads placeholder output replacements
|
||||
@@ -59,11 +66,14 @@ 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 returned placeholder output as well as output replacements.
|
||||
* found in output.
|
||||
*
|
||||
* @param player
|
||||
* player to parse placeholder for
|
||||
@@ -71,29 +81,9 @@ public abstract class TabPlaceholder implements Placeholder {
|
||||
*/
|
||||
@NotNull
|
||||
public String parse(@Nullable TabPlayer 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;
|
||||
String value = getLastValue(player);
|
||||
if (!value.contains("%") && !value.contains("<")) return value; // No nested placeholders, return immediately
|
||||
return setPlaceholders(value, player);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -110,19 +100,40 @@ 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
|
||||
PlaceholderReference nested = TAB.getInstance().getPlaceholderManager().getPlaceholderReference(s);
|
||||
if (reference != null) {
|
||||
nested.addParent(reference);
|
||||
}
|
||||
string = string.replace(s, nested.getHandle().parse(player));
|
||||
TabPlaceholder nested = TAB.getInstance().getPlaceholderManager().getPlaceholder(s);
|
||||
nested.addParent(this);
|
||||
addChild(nested);
|
||||
string = string.replace(s, nested.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
|
||||
@@ -131,11 +142,37 @@ public abstract class TabPlaceholder implements Placeholder {
|
||||
* Player to update placeholders for.
|
||||
*/
|
||||
public void updateParents(@NonNull TabPlayer player) {
|
||||
if (reference == null) return;
|
||||
if (reference.getParents().isEmpty()) return;
|
||||
for (PlaceholderReference pl : new ArrayList<>(reference.getParents())) {
|
||||
pl.getHandle().updateFromNested(player);
|
||||
pl.getHandle().updateParents(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,17 +193,6 @@ 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,11 +132,8 @@ 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> lastPlaceholderEvaluatedValues = new ConcurrentHashMap<>();
|
||||
public final Map<PlayerPlaceholder, String> lastPlaceholderValues = 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<>();
|
||||
|
||||
+13
-14
@@ -49,9 +49,6 @@ public abstract class SafeScoreboard<T extends TabPlayer> implements Scoreboard
|
||||
/** Registered teams */
|
||||
private final Map<String, Team> teams = new ConcurrentHashMap<>();
|
||||
|
||||
/** Teams indexed by entries */
|
||||
private final Map<String, Team> teamsByEntries = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public synchronized void registerObjective(@NonNull String objectiveName, @NonNull TabComponent title,
|
||||
@NonNull HealthDisplay display, @Nullable TabComponent numberFormat) {
|
||||
@@ -146,9 +143,6 @@ public abstract class SafeScoreboard<T extends TabPlayer> implements Scoreboard
|
||||
}
|
||||
Team team = new Team(createTeam(name), name, prefix, suffix, visibility, collision, players, options, color);
|
||||
teams.put(name, team);
|
||||
for (String entry : players) {
|
||||
teamsByEntries.put(entry, team);
|
||||
}
|
||||
if (frozen) return;
|
||||
registerTeam(team);
|
||||
}
|
||||
@@ -160,9 +154,6 @@ public abstract class SafeScoreboard<T extends TabPlayer> implements Scoreboard
|
||||
error("Tried to unregister non-existing team %s for player ", teamName);
|
||||
return;
|
||||
}
|
||||
for (String entry : team.getPlayers()) {
|
||||
teamsByEntries.remove(entry, team);
|
||||
}
|
||||
if (frozen) return;
|
||||
unregisterTeam(team);
|
||||
}
|
||||
@@ -333,14 +324,14 @@ public abstract class SafeScoreboard<T extends TabPlayer> implements Scoreboard
|
||||
* Team name in the packet
|
||||
* @param players
|
||||
* Players in the packet
|
||||
* @return Modified list of players
|
||||
* @return Modified collection of players
|
||||
*/
|
||||
@NotNull
|
||||
public List<String> onTeamPacket(int action, @NonNull String teamName, @NonNull Collection<String> players) {
|
||||
List<String> newList = new ArrayList<>();
|
||||
public Collection<String> onTeamPacket(int action, @NonNull String teamName, @NonNull Collection<String> players) {
|
||||
Collection<String> newList = new ArrayList<>();
|
||||
if (action == TeamAction.CREATE || action == TeamAction.ADD_PLAYER) {
|
||||
for (String entry : players) {
|
||||
Team expectedTeam = teamsByEntries.get(entry);
|
||||
Team expectedTeam = getExpectedTeam(entry);
|
||||
if (expectedTeam == null) {
|
||||
blockedTeamAdds.remove(entry);
|
||||
allowedTeamAdds.put(entry, teamName);
|
||||
@@ -359,7 +350,7 @@ public abstract class SafeScoreboard<T extends TabPlayer> implements Scoreboard
|
||||
if (action == TeamAction.REMOVE_PLAYER) {
|
||||
// TAB does not send remove player, making checks easier
|
||||
for (String entry : players) {
|
||||
Team expectedTeam = teamsByEntries.get(entry);
|
||||
Team expectedTeam = getExpectedTeam(entry);
|
||||
if (expectedTeam != null) {
|
||||
allowedTeamAdds.remove(entry);
|
||||
blockedTeamAdds.remove(entry);
|
||||
@@ -380,6 +371,14 @@ public abstract class SafeScoreboard<T extends TabPlayer> implements Scoreboard
|
||||
return newList;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Team getExpectedTeam(@NotNull String player) {
|
||||
for (Team team : teams.values()) {
|
||||
if (team.getPlayers().contains(player)) return team;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs a message into anti-override log when blocking attempt to add
|
||||
* a player into a team.
|
||||
|
||||
@@ -86,7 +86,7 @@ public abstract class TrackedTabList<P extends TabPlayer> implements TabList {
|
||||
} else {
|
||||
forcedDisplayNames.remove(target.getTablistId());
|
||||
}
|
||||
if (player.getVersion().getNetworkId() < ProtocolVersion.V1_8.getNetworkId()) {
|
||||
if (target.getVersion().getNetworkId() < ProtocolVersion.V1_8.getNetworkId()) {
|
||||
return; // Display names are not supported on 1.7 and below
|
||||
}
|
||||
if (containsEntry(target.getTablistId())) {
|
||||
|
||||
Loaded 100 of 102 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user