mirror of
https://github.com/NEZNAMY/TAB.git
synced 2026-11-12 20:22:01 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7d6206211c | ||
|
|
c8aef50f4b | ||
|
|
4745301c18 | ||
|
|
b9f0abe751 | ||
|
|
025e23aedd | ||
|
|
83334b97f2 | ||
|
|
c51d9898ce | ||
|
|
4b116a80bc | ||
|
|
a0eb00c3c2 | ||
|
|
186d5cebcd | ||
|
|
d9bfbba565 | ||
|
|
49afdcc156 | ||
|
|
6e3c1e61df | ||
|
|
d13b83a6d6 | ||
|
|
f9cc5b79dd | ||
|
|
06733e5a12 | ||
|
|
31c5fdf387 | ||
|
|
d440a28164 | ||
|
|
5b08bd70c1 | ||
|
|
22a7a8a76a | ||
|
|
a46f75b8be | ||
|
|
a748a6b2c0 | ||
|
|
5f8eb14c92 | ||
|
|
1fc5034a77 | ||
|
|
5130d1659e | ||
|
|
f68cba21f0 | ||
|
|
6c7d1eba32 | ||
|
|
9713db862a | ||
|
|
fbee22d785 | ||
|
|
0ee39728e5 | ||
|
|
37aa69b64e | ||
|
|
22ddad85ab | ||
|
|
261a81586f | ||
|
|
f0abc99b61 | ||
|
|
b669871ff1 | ||
|
|
174c58b642 | ||
|
|
28431ef136 | ||
|
|
5f25bf97ba | ||
|
|
f6359d2f77 | ||
|
|
727c500b17 | ||
|
|
bebf2988fb | ||
|
|
e3cde1968b | ||
|
|
ef6b668c70 | ||
|
|
fef62e796b | ||
|
|
0d4b1dbce2 | ||
|
|
43d210dbf8 | ||
|
|
e0d2209e97 | ||
|
|
720cae3e94 | ||
|
|
f2ea34a717 | ||
|
|
2c33747e1b | ||
|
|
5f9d72eb90 | ||
|
|
3abcc1d96f | ||
|
|
7d458422c7 | ||
|
|
0feee14bfb | ||
|
|
0023cfcafb |
No files matched your search
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="92" height="20" role="img" aria-label="Release: 6.1.2"><title>Release: 6.1.2</title><filter id="blur"><feGaussianBlur stdDeviation="16"/></filter><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="92" height="20" rx="3"/></clipPath><g clip-path="url(#r)"><rect width="53" height="20" fill="#555"/><rect x="53" width="39" height="20" fill="#007ec6"/><rect width="92" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><g transform="scale(.1)"><g aria-hidden="true" fill="#010101"><text x="275" y="150" fill-opacity=".8" filter="url(#blur)" textLength="430">Release</text><text x="275" y="150" fill-opacity=".3" textLength="430">Release</text></g><text x="275" y="140" textLength="430">Release</text></g><g transform="scale(.1)"><g aria-hidden="true" fill="#010101"><text x="715" y="150" fill-opacity=".8" filter="url(#blur)" textLength="290">6.1.2</text><text x="715" y="150" fill-opacity=".3" textLength="290">6.1.2</text></g><text x="715" y="140" textLength="290">6.1.2</text></g></g></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="92" height="20" role="img" aria-label="Release: 6.2.1"><title>Release: 6.2.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.2.1</text><text x="715" y="150" fill-opacity=".3" textLength="290">6.2.1</text></g><text x="715" y="140" textLength="290">6.2.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.1.2"
|
||||
release = "6.2.1"
|
||||
download_image(f"https://img.shields.io/badge/Release-{release}-blue.svg", "Badge_Release.svg")
|
||||
|
||||
java = "8+"
|
||||
|
||||
@@ -13,7 +13,7 @@ TAB does not depend on any other plugins. The experience can however be enhanced
|
||||
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.**
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
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+).
|
||||
@@ -21,8 +21,17 @@ Since TAB 6.0.0, Minecraft 1.x started taking hits with each subsequent TAB vers
|
||||
The sections below provide **additional** information for each platform.
|
||||
|
||||
## Bukkit
|
||||
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.
|
||||
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.
|
||||
|
||||
## BungeeCord / Velocity
|
||||
For proxies, the plugin is made to work with the latest build.
|
||||
|
||||
@@ -51,15 +51,15 @@ belowname-objective:
|
||||
```
|
||||
All the options are explained in the following table.
|
||||
|
||||
| Option name | Default value | Description |
|
||||
|---------------------|-----------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| enabled | true | Enables / Disables the feature |
|
||||
| value | %health% | [1.20.2-] An integer from -2147483648 to 2147483647, doesn't support decimal values. The number is always white. Supports placeholders with player-specific output, such as player health. Only visible on 1.20.2 and lower. <br/> **Note**: Even if you only support 1.20.3+, you still need to configure this value to properly evaluate to a number, because the value is still sent to the client (just not displayed). You can set it to `0` for simplicity. |
|
||||
| fancy-value | &c%health% | [1.20.3+] Any text, supports placeholders with per-player output. Only visible on 1.20.3+, 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. |
|
||||
| Option name | Default value | Description |
|
||||
|---------------------|-----------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| enabled | true | Enables / Disables the feature |
|
||||
| value | %health% | [1.20.2-] An integer from -2147483648 to 2147483647, doesn't support decimal values. The number is always white. Supports placeholders with player-specific output, such as player health. Only visible on 1.20.2 and lower. <br/> **Note**: Even if you only support 1.20.3+, you still need to configure this value to properly evaluate to a number, because the value is still sent to the client (just not displayed). You can set it to `0` for simplicity. |
|
||||
| fancy-value | &c%health% | [1.20.3+] Any text, supports placeholders with per-player output. Only visible on 1.20.3+, 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 (at least not until 26.2+, in which case see `fancy-value` description to see how to do it). |
|
||||
| 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,6 +15,7 @@
|
||||
* [Examples](#examples)
|
||||
* [Example 1 - Chaining conditional placeholders](#example-1---chaining-conditional-placeholders)
|
||||
* [Example 2 - Combining AND and OR](#example-2---combining-and-and-or)
|
||||
* [Example 3 - Showing vanish status only for staff members](#example-3---showing-vanish-status-only-for-staff-members)
|
||||
|
||||
# About
|
||||
Conditions / conditional placeholders allow you
|
||||
@@ -216,3 +217,26 @@ conditions:
|
||||
type: AND
|
||||
```
|
||||
Then, use condition `main` as the display condition (or as a placeholder - `%condition:main%`). Note that `true`/`false` values were not defined, as such, they default to `true` and `false`, respectively. Therefore, we use the placeholder from the condition and check if the result is `true`. Then, check if player is also in the specified server.
|
||||
|
||||
## Example 3 - Showing vanish status only for staff members
|
||||
This example shows you how to show some text on vanished players, but only available to see to staff members. For that, we will use a relational condition.
|
||||
We want to create a condition that checks for 2 things - target player is vanished and the viewer has permission. If both of them are met (`AND`), some text should display. Otherwise, nothing should display.
|
||||
Let's build the individual components of the condition:
|
||||
* Vanish placeholder - in this example, we will use `%essentials_vanished%`. The placeholder returns `yes` if player is vanished, otherwise `no`. If you configured [placeholder output replacements](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Placeholder-output-replacements) for the vanish placeholder, you will either need to use the replaced output, or remove the replacement (since you aren't going to use the placeholder directly anyway). To check what exactly a placeholder returns, you can run `/tab parse <player> <placeholder>`.
|
||||
* Staff check - for the lack of a better idea, let's name our permission `my.permission`. The important part is that we want to check this permission for the player who is looking at the tablist, not the target player who is vanished. Following the guide on relational placeholders, we will use `%viewer:player_has_permission_my.permission%`. We need to compare it to the "true" value, (typically `yes`), just like with vanish placeholder. See above for more info.
|
||||
* true / false texts - The `true` value will be set to the value to show if target player is vanished and viewer has the permission. `false` value will be displayed otherwise. This will probably remain empty.
|
||||
|
||||
After combining all of this, here is what the condition may look like:
|
||||
```
|
||||
conditions:
|
||||
vanish:
|
||||
conditions:
|
||||
- '%viewer:player_has_permission_my.permission%=yes'
|
||||
- '%essentials_vanished%=yes'
|
||||
type: AND
|
||||
true: ' &7&l[V]&r'
|
||||
false: ''
|
||||
```
|
||||
You may replace the leading space in `true` value with a trailing one based on your needs. Afterwards, use the placeholder using `%rel_condition:vanish%` in `tabprefix` / `tabsuffix` for example.
|
||||
|
||||
The usefulness of this example is questionable, but the purpose of examples is to teach afterall, not to just be copied over to your config (I know you have been doing that!). This one is, however, useful, because unvanishing will result in TAB instantly formatting the player name before placeholders are periodically updated, which would otherwise result in a small flash of vanished text visible for everyone for a split second after unvanishing.
|
||||
@@ -15,8 +15,8 @@
|
||||
* ~Examples~
|
||||
|
||||
# About
|
||||
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.
|
||||
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.
|
||||
|
||||
# Configuration
|
||||
The feature can be configured in **config.yml** under **global-playerlist** section.
|
||||
@@ -118,6 +118,8 @@ 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,6 +284,22 @@ 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
|
||||
```
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# About the wiki
|
||||
The wiki explains the functionality of the plugin as of version **6.1.2**.
|
||||
The wiki explains the functionality of the plugin as of version **6.2.1**.
|
||||
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.
|
||||
|
||||
@@ -28,31 +28,8 @@ Another option is to configure prefixes per-group in config
|
||||
instead of using placeholders to take those values from your permission plugin.
|
||||
|
||||
## #2 - [All platforms] Placeholder refresh intervals
|
||||
Some placeholders take a long time to process and result in high cpu usage. However, this can be optimized by configuring refresh interval of placeholders.
|
||||
|
||||
Internal placeholders use a value hardcoded into the plugin by default. PlaceholderAPI placeholders use `default-refresh-interval`.
|
||||
|
||||
To begin, find this part in your config
|
||||
```yml
|
||||
placeholder-refresh-intervals:
|
||||
default-refresh-interval: 500
|
||||
"%server_uptime%": 1000
|
||||
"%server_tps_1_colored%": 1000
|
||||
"%player_health%": 200
|
||||
"%player_ping%": 1000
|
||||
"%vault_prefix%": 1000
|
||||
"%rel_factionsuuid_relation_color%": 500
|
||||
```
|
||||
Refresh interval is in milliseconds. It must be divisible by 50.
|
||||
|
||||
`default-refresh-interval` is refresh interval for all unlisted PlaceholderAPI placeholders.
|
||||
|
||||
Keep in mind that CPU measurement is reset every 10 seconds.
|
||||
This means that if you configure a higher refresh interval than 10000,
|
||||
there will be intervals when placeholder didn't refresh at all,
|
||||
making it look like the CPU is perfect, while on the next refresh it may be high.
|
||||
For this reason, if you want to cleanly track usage, keep the interval at 10000 at most.
|
||||
Using a higher interval will help if needed, but may lead to misleading info in cpu output.
|
||||
Some placeholders take a long time to process and result in high cpu usage. However, this can be optimized by configuring refresh interval of placeholders.
|
||||
See [Placeholders - Refreshing](https://github.com/NEZNAMY/TAB/wiki/Placeholders#refreshing) for information about modifying refresh intervals of placeholders.
|
||||
|
||||
## #3 - [All platforms] Don't use RGB
|
||||
Though RGB colors look good, they require the text to be split into several components with `color` field used instead of the whole text pasted into the `text` field. This is a complicated process that takes quite some time. Sticking to legacy colors results in better performance.
|
||||
|
||||
@@ -157,13 +157,13 @@ On **Velocity**, TAB integrates with [MiniPlaceholders](https://github.com/MiniP
|
||||
Unlike classic placeholders which take either 0
|
||||
(placeholders with the same output for all players) or 1 (per-player placeholders) players,
|
||||
relational placeholders take in 2 players.
|
||||
The first usage was to display relation between two players, specifically enemies / allies (red / green) for factions,
|
||||
The first ever recorded usage was to display relation between two players, specifically enemies / allies (red / green) for factions,
|
||||
allowing players to see faction names of other players colored based on relation.
|
||||
While the usage is not limited to this, the name of the 2-player placeholder type has been established as "relational"
|
||||
and will be called that everywhere.
|
||||
and will be called that everywhere. These placeholders only make sense in functions that allow 2 players to see each other - chat, nametags (+ belowname), tablist formatting (+ objective). Other places, such as scoreboard, bossbar or header/footer don't make sense and don't support these placeholders, as there is no other player to use.
|
||||
|
||||
## Usage
|
||||
TAB does not offer any internal relational placeholders. However, it has full support for PlaceholderAPI placeholders. You can also register your own relational placeholders using the [API](#relational-placeholders-1). In PlaceholderAPI, all relational placeholder identifiers must start with `%rel_`. This rule is adopted and used in TAB as well (including those registered via API).
|
||||
TAB offers [relational conditions](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Conditional-placeholders#relational-conditions) as well as full support for PlaceholderAPI placeholders. You can also register your own relational placeholders using the [API](#relational-placeholders-1). In PlaceholderAPI, all relational placeholder identifiers must start with `%rel_`. This rule is adopted and used in TAB as well (including those registered via API).
|
||||
|
||||
When passing players into the function, first player is the player viewing the text, second player is the one text is being displayed on.
|
||||
Code wise, the PlaceholderAPI request function becomes
|
||||
@@ -177,7 +177,27 @@ To properly distinguish viewer/target (if needed), your relational placeholder e
|
||||
|
||||
# Refreshing
|
||||
## Refresh intervals
|
||||
TAB is refreshing placeholders in intervals. For internal placeholders it's using values I found the most optimal and set directly in the plugin. Refresh intervals for PlaceholderAPI placeholders can be [configured](https://github.com/NEZNAMY/TAB/wiki/Optimizing-the-plugin#2---all-platforms-placeholder-refresh-intervals).
|
||||
Internal placeholders use a value hardcoded into the plugin by default. PlaceholderAPI placeholders use `default-refresh-interval` by default.
|
||||
|
||||
To begin, find this part in your config
|
||||
```yml
|
||||
placeholder-refresh-intervals:
|
||||
default-refresh-interval: 500
|
||||
"%server_uptime%": 1000
|
||||
"%server_tps_1_colored%": 1000
|
||||
"%player_health%": 200
|
||||
"%player_ping%": 1000
|
||||
"%vault_prefix%": 1000
|
||||
"%rel_factionsuuid_relation_color%": 500
|
||||
```
|
||||
Refresh interval is in milliseconds. It must be divisible by 50.
|
||||
|
||||
Keep in mind that CPU measurement is reset every 10 seconds.
|
||||
This means that if you configure a higher refresh interval than 10000,
|
||||
there will be intervals when placeholder didn't refresh at all,
|
||||
making it look like the CPU is perfect, while on the next refresh it may be high.
|
||||
For this reason, if you want to cleanly track usage, keep the interval at 10000 at most.
|
||||
Using a higher interval will help if needed, but may lead to misleading info in cpu output.
|
||||
|
||||
## Sync refreshing
|
||||
By default, all placeholders are refreshed asynchronously,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
dependencies {
|
||||
compileOnlyApi("org.jetbrains:annotations:26.0.2")
|
||||
compileOnlyApi("org.jetbrains:annotations:26.1.0")
|
||||
}
|
||||
|
||||
tasks.javadoc {
|
||||
|
||||
@@ -48,14 +48,12 @@ public interface LayoutManager {
|
||||
Layout getLayout(@NonNull String name);
|
||||
|
||||
/**
|
||||
* Sends layout to player. Set to {@code null} to make player not see
|
||||
* any layout, but default TabList instead.
|
||||
* Sends layout to player. Set to {@code null} to reset layout back to original one based on configuration.
|
||||
*
|
||||
* @param player
|
||||
* Player to send layout to
|
||||
* @param layout
|
||||
* Layout to send
|
||||
* @see #resetLayout(TabPlayer)
|
||||
*/
|
||||
void sendLayout(@NonNull TabPlayer player, @Nullable Layout layout);
|
||||
|
||||
@@ -65,6 +63,8 @@ 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,7 +3,7 @@ plugins {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("com.gradleup.shadow:shadow-gradle-plugin:9.4.2")
|
||||
implementation("io.freefair.gradle:lombok-plugin:9.5.0")
|
||||
implementation("com.gradleup.shadow:shadow-gradle-plugin:9.6.1")
|
||||
implementation("io.freefair.gradle:lombok-plugin:9.8.3")
|
||||
implementation("dev.architectury.loom-no-remap:dev.architectury.loom-no-remap.gradle.plugin:1.17-SNAPSHOT")
|
||||
}
|
||||
+1
-3
@@ -4,7 +4,7 @@ plugins {
|
||||
|
||||
allprojects {
|
||||
group = "me.neznamy"
|
||||
version = "6.1.3"
|
||||
version = "6.2.2-SNAPSHOT"
|
||||
description = "An all-in-one solution that works"
|
||||
|
||||
ext.set("id", "tab")
|
||||
@@ -27,8 +27,6 @@ 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",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
dependencies {
|
||||
api(projects.shared)
|
||||
implementation("org.bstats:bstats-bukkit:3.1.0")
|
||||
implementation("org.bstats:bstats-bukkit:3.2.1")
|
||||
compileOnly("org.purpurmc.purpur:purpur-api:1.16.5-R0.1-SNAPSHOT")
|
||||
compileOnly("me.clip:placeholderapi:2.11.6")
|
||||
compileOnly("com.github.MilkBowl:VaultAPI:1.7") {
|
||||
|
||||
+5
-3
@@ -14,14 +14,16 @@ import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Implementation provider using direct Mojang-mapped NMS code for Paper 26.2.
|
||||
* Implementation provider using direct Mojang-mapped NMS code for Paper 26.2 - 26.3.
|
||||
*/
|
||||
@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");
|
||||
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");
|
||||
|
||||
@NotNull
|
||||
private final ComponentConverter<?> componentConverter = new NMSComponentConverter();
|
||||
|
||||
@@ -28,7 +28,7 @@ public class BukkitTAB extends JavaPlugin {
|
||||
Bukkit.getConsoleSender().sendMessage("§c[TAB] Use the other jar in the release instead.");
|
||||
} else {
|
||||
Bukkit.getConsoleSender().sendMessage("§c[TAB] Your server version (" + Bukkit.getBukkitVersion() + ") is not supported.");
|
||||
Bukkit.getConsoleSender().sendMessage("§c[TAB] This jar only supports 1.7.10, 1.8.8, 1.12.2, 1.16.5, 1.17.1 and 1.18.2 - 26.2");
|
||||
Bukkit.getConsoleSender().sendMessage("§c[TAB] 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());
|
||||
|
||||
@@ -96,6 +96,7 @@ 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");
|
||||
@@ -116,6 +117,7 @@ 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(
|
||||
|
||||
@@ -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_1_0_servers",
|
||||
metrics.addCustomChart(new SimplePie("tab_6_2_x_servers",
|
||||
() -> serverVersionInfo.getServerName() + " " + serverVersionInfo.getServerVersion().getFriendlyName()));
|
||||
metrics.addCustomChart(new SimplePie("tab_6_1_0_package", serverVersionInfo::getImplementationPackage));
|
||||
metrics.addCustomChart(new SimplePie("tab_6_2_x_package", serverVersionInfo::getImplementationPackage));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -9,8 +9,10 @@ import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.cpu.TimedCaughtTask;
|
||||
import me.neznamy.tab.shared.data.World;
|
||||
import me.neznamy.tab.shared.placeholders.types.PlayerPlaceholderImpl;
|
||||
import me.neznamy.tab.shared.platform.LineOfSightCheck;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.player.PlayerChangedWorldEvent;
|
||||
@@ -21,8 +23,15 @@ import org.jetbrains.annotations.NotNull;
|
||||
import java.lang.reflect.Method;
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.DecimalFormatSymbols;
|
||||
import java.util.ArrayList;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.logging.Level;
|
||||
|
||||
/**
|
||||
* Platform override for Folia.
|
||||
@@ -36,6 +45,11 @@ public class FoliaPlatform extends BukkitPlatform {
|
||||
/** FoliaGlobalRegionScheduler#execute(Plugin, Runnable) method */
|
||||
private final Method globalScheduler_execute;
|
||||
|
||||
/** Location overload avoids accessing a target entity from the viewer's region. */
|
||||
private final Method hasLineOfSightLocation;
|
||||
|
||||
private final AtomicBoolean lineOfSightErrorLogged = new AtomicBoolean();
|
||||
|
||||
/**
|
||||
* Constructs new instance with given plugin.
|
||||
*
|
||||
@@ -48,6 +62,7 @@ public class FoliaPlatform extends BukkitPlatform {
|
||||
super(plugin);
|
||||
globalScheduler = Bukkit.class.getMethod("getGlobalRegionScheduler").invoke(null);
|
||||
globalScheduler_execute = globalScheduler.getClass().getMethod("execute", Plugin.class, Runnable.class);
|
||||
hasLineOfSightLocation = Player.class.getMethod("hasLineOfSight", Location.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -172,8 +187,93 @@ public class FoliaPlatform extends BukkitPlatform {
|
||||
globalScheduler_execute.invoke(globalScheduler, getPlugin(), task);
|
||||
}
|
||||
|
||||
/**
|
||||
* Captures target positions on their owning regions, then ray traces from each viewer's
|
||||
* owning region. No target entity is read while running a viewer's check.
|
||||
*/
|
||||
@Override
|
||||
public boolean hasLineOfSight(@NotNull TabPlayer viewer, @NotNull TabPlayer target) {
|
||||
return true; // Cross-entity line-of-sight checks are not safe from Folia's global scheduler.
|
||||
public void requestLineOfSight(@NotNull List<LineOfSightCheck> checks, @NotNull Consumer<boolean[]> callback) {
|
||||
boolean[] visible = new boolean[checks.size()];
|
||||
if (checks.isEmpty() || !getPlugin().isEnabled()) {
|
||||
callback.accept(visible);
|
||||
return;
|
||||
}
|
||||
|
||||
Map<TabPlayer, CompletableFuture<Location>> targetLocations = new IdentityHashMap<>();
|
||||
for (LineOfSightCheck check : checks) {
|
||||
targetLocations.computeIfAbsent(check.getTarget(), target -> {
|
||||
CompletableFuture<Location> location = new CompletableFuture<>();
|
||||
scheduleEntity((Player) target.getPlayer(), () -> {
|
||||
try {
|
||||
Player player = (Player) target.getPlayer();
|
||||
location.complete(player.isOnline() ? player.getEyeLocation() : null);
|
||||
} catch (RuntimeException e) {
|
||||
location.complete(null);
|
||||
logLineOfSightError(e);
|
||||
}
|
||||
}, () -> location.complete(null));
|
||||
return location;
|
||||
});
|
||||
}
|
||||
|
||||
CompletableFuture.allOf(targetLocations.values().toArray(new CompletableFuture[0])).whenComplete((ignored, error) -> {
|
||||
Map<TabPlayer, List<Integer>> checksByViewer = new IdentityHashMap<>();
|
||||
for (int i = 0; i < checks.size(); i++) {
|
||||
checksByViewer.computeIfAbsent(checks.get(i).getViewer(), viewer -> new ArrayList<>()).add(i);
|
||||
}
|
||||
|
||||
List<CompletableFuture<Void>> viewerTasks = new ArrayList<>(checksByViewer.size());
|
||||
for (Map.Entry<TabPlayer, List<Integer>> entry : checksByViewer.entrySet()) {
|
||||
CompletableFuture<Void> finished = new CompletableFuture<>();
|
||||
viewerTasks.add(finished);
|
||||
scheduleEntity((Player) entry.getKey().getPlayer(), () -> {
|
||||
try {
|
||||
Player viewer = (Player) entry.getKey().getPlayer();
|
||||
if (viewer.isOnline()) {
|
||||
for (int index : entry.getValue()) {
|
||||
Location target = targetLocations.get(checks.get(index).getTarget()).getNow(null);
|
||||
if (target == null || target.getWorld() != viewer.getWorld()) continue;
|
||||
try {
|
||||
visible[index] = (boolean) hasLineOfSightLocation.invoke(viewer, target);
|
||||
} catch (ReflectiveOperationException | RuntimeException e) {
|
||||
visible[index] = false;
|
||||
logLineOfSightError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
for (int index : entry.getValue()) visible[index] = false;
|
||||
logLineOfSightError(e);
|
||||
} finally {
|
||||
finished.complete(null);
|
||||
}
|
||||
}, () -> finished.complete(null));
|
||||
}
|
||||
CompletableFuture.allOf(viewerTasks.toArray(new CompletableFuture[0]))
|
||||
.whenComplete((done, failure) -> callback.accept(visible));
|
||||
});
|
||||
}
|
||||
|
||||
private void scheduleEntity(@NotNull Player player, @NotNull Runnable task, @NotNull Runnable retired) {
|
||||
if (!getPlugin().isEnabled()) {
|
||||
retired.run();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
Object scheduler = Entity.class.getMethod("getScheduler").invoke(player);
|
||||
Consumer<?> consumer = ignored -> task.run();
|
||||
Object scheduled = scheduler.getClass().getMethod("run", Plugin.class, Consumer.class, Runnable.class)
|
||||
.invoke(scheduler, getPlugin(), consumer, retired);
|
||||
if (scheduled == null) retired.run();
|
||||
} catch (ReflectiveOperationException | RuntimeException e) {
|
||||
retired.run();
|
||||
logLineOfSightError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void logLineOfSightError(@NotNull Exception error) {
|
||||
if (lineOfSightErrorLogged.compareAndSet(false, true)) {
|
||||
getPlugin().getLogger().log(Level.WARNING, "Unable to check opaque nametag line of sight on Folia", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
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
@@ -1,64 +0,0 @@
|
||||
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
@@ -1,52 +0,0 @@
|
||||
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
@@ -1,207 +0,0 @@
|
||||
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
@@ -1,210 +0,0 @@
|
||||
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
@@ -1,35 +0,0 @@
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
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
@@ -1,64 +0,0 @@
|
||||
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
@@ -1,52 +0,0 @@
|
||||
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
@@ -1,206 +0,0 @@
|
||||
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
@@ -1,223 +0,0 @@
|
||||
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
@@ -1,40 +0,0 @@
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
dependencies {
|
||||
implementation(projects.shared)
|
||||
implementation("org.bstats:bstats-bungeecord:3.1.0")
|
||||
implementation("org.bstats:bstats-bungeecord:3.2.1")
|
||||
compileOnly("net.md-5:bungeecord-api:26.1-R0.1-SNAPSHOT")
|
||||
compileOnly("net.md-5:bungeecord-proxy:1.21-R0.4-SNAPSHOT")
|
||||
compileOnly("com.github.limework.redisbungee:RedisBungee-Bungee:0.11.0")
|
||||
|
||||
@@ -14,11 +14,12 @@ repositories {
|
||||
maven("https://repo.papermc.io/repository/maven-public/") // Adventure
|
||||
}
|
||||
|
||||
val minecraftVersion = "26.2"
|
||||
val minecraftVersion = "26.3"
|
||||
|
||||
// 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,7 +133,12 @@ public class FabricScoreboard extends SafeScoreboard<FabricTabPlayer> {
|
||||
if (packet instanceof ClientboundSetPlayerTeamPacket team) {
|
||||
int method = getMethod(team);
|
||||
if (method != TeamAction.UPDATE) {
|
||||
team.players = onTeamPacket(method, team.getName(), team.getPlayers());
|
||||
return new ClientboundSetPlayerTeamPacket(
|
||||
team.getName(),
|
||||
method,
|
||||
team.getParameters(),
|
||||
onTeamPacket(method, team.getName(), team.getPlayers())
|
||||
);
|
||||
}
|
||||
}
|
||||
return packet;
|
||||
|
||||
@@ -14,7 +14,8 @@
|
||||
"fabric-api-base": "*",
|
||||
"java": ">=25",
|
||||
"minecraft": [
|
||||
"26.2"
|
||||
"26.2",
|
||||
"26.3"
|
||||
]
|
||||
},
|
||||
"recommends": {
|
||||
|
||||
@@ -5,8 +5,7 @@ accessible field net/minecraft/server/network/ServerCommonPacketListenerImpl con
|
||||
accessible field net/minecraft/network/Connection channel Lio/netty/channel/Channel;
|
||||
|
||||
# Scoreboard team
|
||||
accessible field net/minecraft/network/protocol/game/ClientboundSetPlayerTeamPacket players Ljava/util/Collection;
|
||||
mutable field net/minecraft/network/protocol/game/ClientboundSetPlayerTeamPacket players Ljava/util/Collection;
|
||||
accessible method net/minecraft/network/protocol/game/ClientboundSetPlayerTeamPacket <init> (Ljava/lang/String;ILjava/util/Optional;Ljava/util/Collection;)V
|
||||
|
||||
# Player info
|
||||
accessible field net/minecraft/network/protocol/game/ClientboundPlayerInfoUpdatePacket entries Ljava/util/List;
|
||||
|
||||
@@ -15,13 +15,14 @@ repositories {
|
||||
maven("https://repo.papermc.io/repository/maven-public/") // Adventure
|
||||
}
|
||||
|
||||
val minecraftVersion = "26.2"
|
||||
val minecraftVersion = "26.3"
|
||||
|
||||
// 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.2" to "26.2-65.0.0",
|
||||
"26.1.2" to "26.1.2-64.0.4"
|
||||
"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"
|
||||
)
|
||||
|
||||
dependencies {
|
||||
|
||||
@@ -4,7 +4,6 @@ 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.*;
|
||||
@@ -15,9 +14,7 @@ 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;
|
||||
|
||||
/**
|
||||
@@ -29,7 +26,6 @@ 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.
|
||||
@@ -139,7 +135,12 @@ public class ForgeScoreboard extends SafeScoreboard<ForgeTabPlayer> {
|
||||
if (packet instanceof ClientboundSetPlayerTeamPacket team) {
|
||||
int method = getMethod(team);
|
||||
if (method != TeamAction.UPDATE) {
|
||||
players.set(team, onTeamPacket(method, team.getName(), team.getPlayers()));
|
||||
return new ClientboundSetPlayerTeamPacket(
|
||||
team.getName(),
|
||||
method,
|
||||
team.getParameters(),
|
||||
onTeamPacket(method, team.getName(), team.getPlayers())
|
||||
);
|
||||
}
|
||||
}
|
||||
return packet;
|
||||
|
||||
@@ -5,12 +5,10 @@ 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;
|
||||
@@ -20,7 +18,6 @@ import net.minecraft.world.level.GameType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
@@ -36,8 +33,6 @@ 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.
|
||||
*
|
||||
@@ -104,7 +99,6 @@ public class ForgeTabList extends TrackedTabList<ForgeTabPlayer> {
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
@NotNull
|
||||
public Object onPacketSend(@NonNull Object packet) {
|
||||
if (packet instanceof ClientboundTabListPacket tablist) {
|
||||
@@ -158,18 +152,17 @@ public class ForgeTabList extends TrackedTabList<ForgeTabPlayer> {
|
||||
}
|
||||
if (rewritePacket) {
|
||||
ClientboundPlayerInfoUpdatePacket newPacket = new ClientboundPlayerInfoUpdatePacket(actions, Collections.emptyList());
|
||||
entries.set(newPacket, updatedList);
|
||||
newPacket.entries = 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());
|
||||
entries.set(packet, Collections.singletonList(new ClientboundPlayerInfoUpdatePacket.Entry(
|
||||
packet.entries = Collections.singletonList(new ClientboundPlayerInfoUpdatePacket.Entry(
|
||||
id,
|
||||
action.contains(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER) ? createProfile(id, name, skin) : null,
|
||||
listed,
|
||||
@@ -179,7 +172,7 @@ public class ForgeTabList extends TrackedTabList<ForgeTabPlayer> {
|
||||
showHat,
|
||||
listOrder,
|
||||
null
|
||||
)));
|
||||
));
|
||||
sendPacket(packet);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
# 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]"
|
||||
versionRange = "[26.2],[26.3]"
|
||||
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.6.0-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.1-bin.zip
|
||||
networkTimeout=10000
|
||||
retries=0
|
||||
retryBackOffMs=500
|
||||
|
||||
@@ -27,8 +27,6 @@ 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,13 +15,14 @@ repositories {
|
||||
maven("https://repo.papermc.io/repository/maven-public/") // Adventure
|
||||
}
|
||||
|
||||
val minecraftVersion = "26.2"
|
||||
val minecraftVersion = "26.3"
|
||||
|
||||
// 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.2" to "26.2.0.1-beta",
|
||||
"26.1.2" to "26.1.2.17-beta"
|
||||
"26.3" to "26.3.0.1-beta",
|
||||
"26.2" to "26.2.0.88",
|
||||
"26.1.2" to "26.1.2.109"
|
||||
)
|
||||
|
||||
dependencies {
|
||||
|
||||
@@ -133,7 +133,12 @@ public class NeoForgeScoreboard extends SafeScoreboard<NeoForgeTabPlayer> {
|
||||
if (packet instanceof ClientboundSetPlayerTeamPacket team) {
|
||||
int method = getMethod(team);
|
||||
if (method != TeamAction.UPDATE) {
|
||||
team.players = onTeamPacket(method, team.getName(), team.getPlayers());
|
||||
return new ClientboundSetPlayerTeamPacket(
|
||||
team.getName(),
|
||||
method,
|
||||
team.getParameters(),
|
||||
onTeamPacket(method, team.getName(), team.getPlayers())
|
||||
);
|
||||
}
|
||||
}
|
||||
return packet;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Scoreboard team
|
||||
public-f net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket players
|
||||
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 @@ description = '''@description@'''
|
||||
[[dependencies.tab]]
|
||||
modId = "minecraft"
|
||||
type = "required"
|
||||
versionRange = "[26.2]"
|
||||
versionRange = "[26.2],[26.3]"
|
||||
ordering = "NONE"
|
||||
side = "SERVER"
|
||||
@@ -52,8 +52,6 @@ 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")
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
plugins {
|
||||
id("net.kyori.blossom") version "2.1.0"
|
||||
id("net.kyori.blossom") version "2.2.0"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api(projects.api)
|
||||
api("org.yaml:snakeyaml:2.0")
|
||||
api("org.yaml:snakeyaml:2.7")
|
||||
api("com.github.NEZNAMY:yamlassist:1.0.8")
|
||||
api("com.googlecode.json-simple:json-simple:1.1.1") {
|
||||
exclude("junit", "junit")
|
||||
@@ -14,13 +14,13 @@ dependencies {
|
||||
exclude("org.checkerframework", "checker-qual")
|
||||
}
|
||||
api("com.mysql:mysql-connector-j:9.6.0")
|
||||
compileOnlyApi("com.viaversion:viaversion-api:5.2.1")
|
||||
compileOnlyApi("com.viaversion:viaversion-api:5.12.2")
|
||||
compileOnlyApi("io.netty:netty-all:4.1.90.Final")
|
||||
compileOnlyApi("net.luckperms:api:5.4")
|
||||
compileOnlyApi("net.luckperms:api:5.5")
|
||||
compileOnlyApi("com.google.guava:guava:31.1-jre")
|
||||
compileOnlyApi("org.geysermc.floodgate:api:2.2.0-SNAPSHOT")
|
||||
compileOnlyApi("net.kyori:adventure-api:4.25.0-SNAPSHOT")
|
||||
compileOnlyApi("net.kyori:adventure-text-minimessage:4.25.0-SNAPSHOT")
|
||||
compileOnlyApi("net.kyori:adventure-api:4.26.1")
|
||||
compileOnlyApi("net.kyori:adventure-text-minimessage:4.26.1")
|
||||
implementation("com.saicone.delivery4j:delivery4j:1.1.1")
|
||||
implementation("com.saicone.delivery4j:broker-rabbitmq:1.1.1") {
|
||||
exclude("org.slf4j", "slf4j-api")
|
||||
@@ -38,7 +38,7 @@ sourceSets.main {
|
||||
property("name", rootProject.name)
|
||||
property("id", rootProject.ext.get("id")!!.toString())
|
||||
property("version", project.version.toString())
|
||||
property("description", project.description)
|
||||
property("description", project.description.orEmpty())
|
||||
property("website", rootProject.ext.get("website")!!.toString())
|
||||
property("author", rootProject.ext.get("author")!!.toString())
|
||||
property("credits", rootProject.ext.get("credits")!!.toString())
|
||||
|
||||
@@ -596,8 +596,12 @@ public class FeatureManager {
|
||||
if (config.getScoreboard() != null) {
|
||||
registerFeature(TabConstants.Feature.SCOREBOARD, new ScoreboardManagerImpl(config.getScoreboard()));
|
||||
}
|
||||
registerFeature(TabConstants.Feature.YELLOW_NUMBER, new YellowNumber(config.getPlayerlistObjective()));
|
||||
registerFeature(TabConstants.Feature.BELOW_NAME, new BelowName(config.getBelowname()));
|
||||
if (config.getPlayerlistObjective() != null) {
|
||||
registerFeature(TabConstants.Feature.YELLOW_NUMBER, new YellowNumber(config.getPlayerlistObjective()));
|
||||
}
|
||||
if (config.getBelowname() != null) {
|
||||
registerFeature(TabConstants.Feature.BELOW_NAME, new BelowName(config.getBelowname()));
|
||||
}
|
||||
if (config.getSorting() != null) {
|
||||
registerFeature(TabConstants.Feature.SORTING, new Sorting(config.getSorting()));
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import org.jetbrains.annotations.NotNull;
|
||||
public enum ProtocolVersion {
|
||||
|
||||
UNKNOWN,
|
||||
V26_3 (777),
|
||||
V26_2 (776),
|
||||
V26_1_2 (775),
|
||||
V26_1_1 (775),
|
||||
|
||||
@@ -85,6 +85,7 @@ public class TabConstants {
|
||||
public static final String COMMAND_PROPERTY_CHANGE_PREFIX = "tab.change.";
|
||||
|
||||
public static final String COMMAND_BOSSBAR_ANNOUNCE = "tab.announce.bar";
|
||||
public static final String COMMAND_BOSSBAR_SEND = "tab.send.bar";
|
||||
public static final String COMMAND_BOSSBAR_TOGGLE = "tab.bossbar.toggle";
|
||||
public static final String COMMAND_BOSSBAR_TOGGLE_OTHER = "tab.bossbar.toggle.other";
|
||||
|
||||
|
||||
@@ -149,9 +149,13 @@ 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");
|
||||
redis.put("url", "*CENSORED*");
|
||||
if (redis != null) {
|
||||
redis.put("url", "*CENSORED*");
|
||||
}
|
||||
Map<String, Object> rabbitmq = (Map<String, Object>) proxySupport.get("rabbitmq");
|
||||
rabbitmq.put("url", "*CENSORED*");
|
||||
if (rabbitmq != null) {
|
||||
rabbitmq.put("url", "*CENSORED*");
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
|
||||
@@ -22,7 +22,7 @@ public class BossBarSendCommand extends SubCommand {
|
||||
* Constructs new instance
|
||||
*/
|
||||
public BossBarSendCommand() {
|
||||
super("send", TabConstants.Permission.COMMAND_SCOREBOARD_SHOW);
|
||||
super("send", TabConstants.Permission.COMMAND_BOSSBAR_SEND);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -46,7 +46,7 @@ public class BossBarSendCommand extends SubCommand {
|
||||
try {
|
||||
duration = Integer.parseInt(args[2]);
|
||||
} catch (NumberFormatException e) {
|
||||
sendMessage(sender, getMessages().getInvalidNumber(args[1]));
|
||||
sendMessage(sender, getMessages().getInvalidNumber(args[2]));
|
||||
return;
|
||||
}
|
||||
BossBar bar = feature.getBossBar(barName);
|
||||
|
||||
@@ -512,15 +512,16 @@ public class LegacyConverter {
|
||||
* Config file
|
||||
*/
|
||||
public void convert507to510(@NonNull ConfigurationFile config) {
|
||||
if (config.rename("enable-redisbungee-support", "proxy-support.enabled")) {
|
||||
config.rename("enable-redisbungee-support", "proxy-support.enabled");
|
||||
config.setIfMissing("proxy-support.enabled", true);
|
||||
config.setIfMissing("proxy-support.type", "PLUGIN");
|
||||
config.setIfMissing("proxy-support.plugin.name", "RedisBungee");
|
||||
config.setIfMissing("proxy-support.redis.url", "redis://:password@localhost:6379/0");
|
||||
config.setIfMissing("proxy-support.rabbitmq.exchange", "plugin");
|
||||
config.setIfMissing("proxy-support.rabbitmq.url", "amqp://guest:guest@localhost:5672/%2F");
|
||||
if (config.rename("placeholderapi-refresh-intervals", "placeholder-refresh-intervals")) {
|
||||
TAB.getInstance().getPlatform().logInfo(new TabTextComponent("Performing configuration conversion from 5.0.7 to 5.1.0", TabTextColor.YELLOW));
|
||||
config.set("proxy-support.type", "PLUGIN");
|
||||
config.set("proxy-support.plugin.name", "RedisBungee");
|
||||
config.set("proxy-support.redis.url", "redis://:password@localhost:6379/0");
|
||||
config.set("proxy-support.rabbitmq.exchange", "plugin");
|
||||
config.set("proxy-support.rabbitmq.url", "amqp://guest:guest@localhost:5672/%2F");
|
||||
}
|
||||
config.rename("placeholderapi-refresh-intervals", "placeholder-refresh-intervals");
|
||||
config.setIfMissing("playerlist-objective.title", "TAB");
|
||||
config.setIfMissing("playerlist-objective.render-type", Arrays.asList("%health%", "%player_health%", "%player_health_rounded%").contains(config.getString("playerlist-objective.value", "")) ? "HEARTS" : "INTEGER");
|
||||
}
|
||||
|
||||
+82
-33
@@ -25,8 +25,9 @@ public class YamlPropertyConfigurationFile extends YamlConfigurationFile impleme
|
||||
private final String PER_WORLD = "per-world";
|
||||
|
||||
private final String category;
|
||||
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());
|
||||
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());
|
||||
|
||||
/**
|
||||
* Constructs new instance and attempts to load specified configuration file.
|
||||
@@ -48,26 +49,29 @@ 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 (Map.Entry<Object, Object> entry : getValues().entrySet()) {
|
||||
if (entry.getKey().equals(PER_SERVER)) {
|
||||
for (Object key : root.getKeys()) {
|
||||
String entry = key.toString();
|
||||
if (entry.equals(PER_SERVER)) {
|
||||
for (String server : serverGroups) {
|
||||
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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (entry.getKey().equals(PER_WORLD)) {
|
||||
} else if (entry.equals(PER_WORLD)) {
|
||||
for (String world : worldGroups) {
|
||||
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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (String property : this.<String, Object>getMap(entry.getKey().toString()).keySet()) {
|
||||
checkProperty(destination.getName(), category, entry.getKey().toString(), property, null, null, true);
|
||||
for (Object property : root.getConfigurationSection(entry).getKeys()) {
|
||||
checkProperty(destination.getName(), category, entry, property.toString(), null, null, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -75,13 +79,16 @@ 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) {
|
||||
set(String.format("%s.%s.%s.%s", PER_WORLD, world.getName(), name, property), fromString(value));
|
||||
target = getOrCreateSection(PER_WORLD, world.getName(), name);
|
||||
} else if (server != null) {
|
||||
set(String.format("%s.%s.%s.%s", PER_SERVER, server.getName(), name, property), fromString(value));
|
||||
target = getOrCreateSection(PER_SERVER, server.getName(), name);
|
||||
} else {
|
||||
set(String.format("%s.%s", name, property), fromString(value));
|
||||
target = getOrCreateSection(name);
|
||||
}
|
||||
target.put(property, fromString(value));
|
||||
save();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -89,22 +96,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 = getObject(new String[] {PER_WORLD, TAB.getInstance().getConfiguration().getGroup(worldGroups, worldName), name, property})) != null) {
|
||||
if ((value = section(PER_WORLD, TAB.getInstance().getConfiguration().getGroup(worldGroups, worldName), name).getObject(property)) != null) {
|
||||
return new String[] {toString(value), category + "=" + name + ", world=" + worldName};
|
||||
}
|
||||
if ((value = getObject(new String[] {PER_WORLD, TAB.getInstance().getConfiguration().getGroup(worldGroups, worldName), TabConstants.DEFAULT_GROUP, property})) != null) {
|
||||
if ((value = section(PER_WORLD, TAB.getInstance().getConfiguration().getGroup(worldGroups, worldName), TabConstants.DEFAULT_GROUP).getObject(property)) != null) {
|
||||
return new String[] {toString(value), category + "=" + TabConstants.DEFAULT_GROUP + ", world=" + worldName};
|
||||
}
|
||||
if ((value = getObject(new String[] {PER_SERVER, TAB.getInstance().getConfiguration().getServerGroup(serverGroups, server), name, property})) != null) {
|
||||
if ((value = section(PER_SERVER, TAB.getInstance().getConfiguration().getServerGroup(serverGroups, server), name).getObject(property)) != null) {
|
||||
return new String[] {toString(value), category + "=" + name + ", server=" + serverName};
|
||||
}
|
||||
if ((value = getObject(new String[] {PER_SERVER, TAB.getInstance().getConfiguration().getServerGroup(serverGroups, server), TabConstants.DEFAULT_GROUP, property})) != null) {
|
||||
if ((value = section(PER_SERVER, TAB.getInstance().getConfiguration().getServerGroup(serverGroups, server), TabConstants.DEFAULT_GROUP).getObject(property)) != null) {
|
||||
return new String[] {toString(value), category + "=" + TabConstants.DEFAULT_GROUP + ", server=" + serverName};
|
||||
}
|
||||
if ((value = getObject(new String[] {name, property})) != null) {
|
||||
if ((value = section(name).getObject(property)) != null) {
|
||||
return new String[] {toString(value), category + "=" + name};
|
||||
}
|
||||
if ((value = getObject(new String[] {TabConstants.DEFAULT_GROUP, property})) != null) {
|
||||
if ((value = section(TabConstants.DEFAULT_GROUP).getObject(property)) != null) {
|
||||
return new String[] {toString(value), category + "=" + TabConstants.DEFAULT_GROUP};
|
||||
}
|
||||
return new String[0];
|
||||
@@ -112,39 +119,81 @@ public class YamlPropertyConfigurationFile extends YamlConfigurationFile impleme
|
||||
|
||||
@Override
|
||||
public void remove(@NonNull String name) {
|
||||
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));
|
||||
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();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Map<String, Object> getGlobalSettings(@NonNull String name) {
|
||||
return getMap(name);
|
||||
Map<String, Object> settings = root.getMap(name);
|
||||
return settings == null ? new LinkedHashMap<>() : settings;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Map<String, Map<String, Object>> getPerWorldSettings(@NonNull String name) {
|
||||
return convertMap(getMap(PER_WORLD), name);
|
||||
return getSettingsByGroup(PER_WORLD, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Map<String, Map<String, Object>> getPerServerSettings(@NonNull String name) {
|
||||
return convertMap(getMap(PER_SERVER), name);
|
||||
return getSettingsByGroup(PER_SERVER, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Set<String> getAllEntries() {
|
||||
Set<Object> set = new HashSet<>(values.keySet());
|
||||
Set<Object> set = new HashSet<>(root.getKeys());
|
||||
set.remove(PER_WORLD);
|
||||
set.remove(PER_SERVER);
|
||||
Map<String, Map<String, Map<String, String>>> perWorld = getMap(PER_WORLD);
|
||||
perWorld.values().forEach(m -> set.addAll(m.keySet()));
|
||||
Map<String, Map<String, Map<String, String>>> perServer = getMap(PER_SERVER);
|
||||
perServer.values().forEach(m -> set.addAll(m.keySet()));
|
||||
addNestedEntryNames(set, PER_WORLD);
|
||||
addNestedEntryNames(set, PER_SERVER);
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -40,7 +40,7 @@ public class Config {
|
||||
@NotNull private final ConfigurationFile config = new YamlConfigurationFile(getClass().getClassLoader().getResourceAsStream("config/config.yml"),
|
||||
new File(TAB.getInstance().getDataFolder(), "config.yml"));
|
||||
|
||||
@NotNull private final BelowNameConfiguration belowname;
|
||||
@Nullable private BelowNameConfiguration belowname;
|
||||
@Nullable private BossBarConfiguration bossbar;
|
||||
@NotNull private final ConditionsSection conditions;
|
||||
@Nullable private GlobalPlayerListConfiguration globalPlayerList;
|
||||
@@ -52,7 +52,7 @@ public class Config {
|
||||
@NotNull private final PlaceholderRefreshConfiguration refresh;
|
||||
@NotNull private final PlaceholderReplacementsConfiguration replacements;
|
||||
@NotNull private final PlaceholdersConfiguration placeholders;
|
||||
@NotNull private final PlayerListObjectiveConfiguration playerlistObjective;
|
||||
@Nullable private PlayerListObjectiveConfiguration playerlistObjective;
|
||||
@Nullable private ScoreboardConfiguration scoreboard;
|
||||
@Nullable private SortingConfiguration sorting;
|
||||
@Nullable private TablistFormattingConfiguration tablistFormatting;
|
||||
@@ -95,10 +95,10 @@ public class Config {
|
||||
replacements = PlaceholderReplacementsConfiguration.fromSection(config.getConfigurationSection("placeholder-output-replacements"));
|
||||
placeholders = PlaceholdersConfiguration.fromSection(config.getConfigurationSection("placeholders"));
|
||||
components = ComponentConfiguration.fromSection(config.getConfigurationSection("components"));
|
||||
belowname = BelowNameConfiguration.fromSection(config.getConfigurationSection("belowname-objective"));
|
||||
playerlistObjective = PlayerListObjectiveConfiguration.fromSection(config.getConfigurationSection("playerlist-objective"));
|
||||
|
||||
if (config.getBoolean("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("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"));
|
||||
@@ -118,7 +118,7 @@ public class Config {
|
||||
" fake players, making per world playerlist completely useless as real players are pushed out of the playerlist. " +
|
||||
"Disable per world playerlist for the same result, but with better performance.");
|
||||
}
|
||||
if (playerlistObjective.isEnabled()) {
|
||||
if (playerlistObjective != null) {
|
||||
TAB.getInstance().getConfigHelper().startup().startupWarn(config.getFile(), "Layout feature breaks playerlist-objective feature, because it replaces real player with fake slots " +
|
||||
"with different usernames for more reliable functionality. Disable playerlist-objective feature, as it will only look bad " +
|
||||
"and consume resources.");
|
||||
|
||||
@@ -495,6 +495,8 @@ 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
|
||||
@@ -502,6 +504,8 @@ 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
|
||||
@@ -509,6 +513,18 @@ 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
|
||||
|
||||
@@ -150,15 +150,11 @@ public class BelowName extends RefreshableFeature implements JoinListener, QuitL
|
||||
p.belowNameData.disabled.set(!forced);
|
||||
return;
|
||||
}
|
||||
if (!configuration.isEnabled()) {
|
||||
p.belowNameData.disabled.set(true);
|
||||
return;
|
||||
}
|
||||
updateVisibility(p, disabledNow);
|
||||
}
|
||||
|
||||
private boolean configuredDisabled(@NotNull TabPlayer p) {
|
||||
return !configuration.isEnabled() || disableChecker.isDisableConditionMet(p);
|
||||
return disableChecker.isDisableConditionMet(p);
|
||||
}
|
||||
|
||||
private void applyDisabledState(@NotNull TabPlayer p, boolean disabled) {
|
||||
@@ -294,9 +290,10 @@ public class BelowName extends RefreshableFeature implements JoinListener, QuitL
|
||||
}
|
||||
|
||||
private void updatePlayer(@NotNull TabPlayer player) {
|
||||
int value = getValue(player);
|
||||
for (TabPlayer all : onlinePlayers.getPlayers()) {
|
||||
setScore(player, all, getValue(all), all.belowNameData.fancyValue.getFormat(player));
|
||||
if (all != player) setScore(all, player, getValue(player), player.belowNameData.fancyValue.getFormat(all));
|
||||
if (all != player) setScore(player, all, getValue(all), all.belowNameData.fancyValue.getFormat(player));
|
||||
setScore(all, player, value, player.belowNameData.fancyValue.getFormat(all));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -330,8 +327,9 @@ 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, getValue(player), player.belowNameData.fancyValue.getFormat(viewer));
|
||||
setScore(viewer, player, value, player.belowNameData.fancyValue.getFormat(viewer));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
-2
@@ -17,7 +17,6 @@ import java.util.Arrays;
|
||||
public class BelowNameConfiguration {
|
||||
|
||||
@NotNull private final ConfigurationSection section;
|
||||
private final boolean enabled;
|
||||
@NotNull private final String value;
|
||||
@NotNull private final String title;
|
||||
@NotNull private final String fancyValue;
|
||||
@@ -71,7 +70,6 @@ public class BelowNameConfiguration {
|
||||
|
||||
return new BelowNameConfiguration(
|
||||
section,
|
||||
section.getBoolean("enabled", false),
|
||||
value,
|
||||
title,
|
||||
section.getString("fancy-value", "&c" + TabConstants.Placeholder.HEALTH),
|
||||
|
||||
@@ -64,7 +64,8 @@ public class LayoutConfiguration {
|
||||
ConfigurationSection groupSection = defaultSkins.getConfigurationSection(asString);
|
||||
|
||||
String skin = groupSection.getString("skin");
|
||||
for (String line : section.getStringList("slots", Collections.emptyList())) {
|
||||
if (skin == null) continue;
|
||||
for (String line : groupSection.getStringList("slots", Collections.emptyList())) {
|
||||
String[] arr = line.split("-");
|
||||
int from = Integer.parseInt(arr[0]);
|
||||
int to = arr.length == 1 ? from : Integer.parseInt(arr[1]);
|
||||
|
||||
@@ -77,23 +77,16 @@ public class LayoutManagerImpl extends RefreshableFeature implements LayoutManag
|
||||
p.layoutData.sortingString = p.sortingData.fullTeamName;
|
||||
sortedPlayers.put(p, p.sortingData.fullTeamName);
|
||||
LayoutPattern highest = getHighestLayout(p);
|
||||
if (highest != null) {
|
||||
sendLayout(p, highest);
|
||||
}
|
||||
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
|
||||
public void onQuit(@NotNull TabPlayer p) {
|
||||
sortedPlayers.remove(p);
|
||||
p.layoutData.currentLayout = null;
|
||||
for (TabPlayer all : TAB.getInstance().getOnlinePlayers()) {
|
||||
if (all == p) continue;
|
||||
if (all.layoutData.currentLayout != null) all.layoutData.currentLayout.view.tick();
|
||||
@@ -112,17 +105,30 @@ 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();
|
||||
p.layoutData.currentLayout = null;
|
||||
if (highest != null) {
|
||||
sendLayout(p, highest);
|
||||
}
|
||||
sendLayout(p, highest);
|
||||
}
|
||||
}
|
||||
|
||||
private void sendLayout(@NotNull TabPlayer player, @NotNull LayoutPattern pattern) {
|
||||
LayoutBase view = new FakeEntryLayout(this, pattern, player);
|
||||
player.layoutData.currentLayout = new LayoutData(view);
|
||||
view.send();
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -210,11 +216,7 @@ public class LayoutManagerImpl extends RefreshableFeature implements LayoutManag
|
||||
|
||||
@Override
|
||||
public void resetLayout(@NonNull me.neznamy.tab.api.TabPlayer player) {
|
||||
ensureActive();
|
||||
TabPlayer p = (TabPlayer) player;
|
||||
p.ensureLoaded();
|
||||
p.layoutData.forcedLayout = null;
|
||||
refresh(p, false);
|
||||
sendLayout(player, null);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
|
||||
+13
-2
@@ -122,7 +122,7 @@ public class NameTagProxyPlayerData extends ProxyMessage {
|
||||
// Property update
|
||||
if (viewer.teamData.hasTeamRegistered(target)) {
|
||||
viewer.getScoreboard().updateTeam(
|
||||
oldData.teamName,
|
||||
oldData.resolvedTeamName,
|
||||
prefix,
|
||||
suffix,
|
||||
nameVisibility,
|
||||
@@ -165,7 +165,18 @@ public class NameTagProxyPlayerData extends ProxyMessage {
|
||||
if (!nameTaken && feature.getProxy() != null) {
|
||||
for (ProxyPlayer all : feature.getProxy().getProxyPlayers().values()) {
|
||||
if (all == player) continue;
|
||||
if (all.getNametag() != null && potentialTeamName.equals(all.getNametag().teamName)) {
|
||||
NameTagProxyPlayerData nametag = all.getNametag();
|
||||
if (nametag != null && (potentialTeamName.equals(nametag.teamName) || potentialTeamName.equals(nametag.resolvedTeamName))) {
|
||||
nameTaken = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!nameTaken && feature.getProxy() != null) {
|
||||
// Players still queued have a resolved name that will be used once they are known
|
||||
for (QueuedData queued : feature.getProxy().getQueuedData().values()) {
|
||||
NameTagProxyPlayerData nametag = queued.getNametag();
|
||||
if (nametag != null && nametag != this && !nametag.playerId.equals(playerId) && potentialTeamName.equals(nametag.resolvedTeamName)) {
|
||||
nameTaken = true;
|
||||
break;
|
||||
}
|
||||
|
||||
+35
-30
@@ -11,6 +11,7 @@ import me.neznamy.tab.shared.features.types.JoinListener;
|
||||
import me.neznamy.tab.shared.features.types.Loadable;
|
||||
import me.neznamy.tab.shared.features.types.RefreshableFeature;
|
||||
import me.neznamy.tab.shared.placeholders.conditions.Condition;
|
||||
import me.neznamy.tab.shared.platform.LineOfSightCheck;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -21,6 +22,7 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.WeakHashMap;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
/**
|
||||
* Sub-feature for NameTag feature that manages nametag visibility.
|
||||
@@ -39,6 +41,10 @@ public class VisibilityManager extends RefreshableFeature implements JoinListene
|
||||
@NotNull
|
||||
private final Set<TabPlayer> opaqueNameTagViewers = Collections.newSetFromMap(new WeakHashMap<>());
|
||||
|
||||
/** Prevents an older asynchronous occlusion check from overwriting a newer result. */
|
||||
private final AtomicLong opaqueRefreshSequence = new AtomicLong();
|
||||
private long lastAppliedOpaqueRefresh;
|
||||
|
||||
/**
|
||||
* Constructs new instance.
|
||||
*
|
||||
@@ -271,30 +277,41 @@ public class VisibilityManager extends RefreshableFeature implements JoinListene
|
||||
}
|
||||
|
||||
private void requestOpaqueOcclusionRefresh() {
|
||||
List<OpaqueVisibilityCheck> checks = new ArrayList<>();
|
||||
List<LineOfSightCheck> checks = new ArrayList<>();
|
||||
List<OpaqueVisibilityResult> immediateResults = new ArrayList<>();
|
||||
for (TabPlayer player : nameTags.getOnlinePlayers().getPlayers()) {
|
||||
if (!hasOpaqueNameTagMode(player) || player.teamData.isDisabled()) continue;
|
||||
for (TabPlayer viewer : nameTags.getOnlinePlayers().getPlayers()) {
|
||||
checks.add(new OpaqueVisibilityCheck(player, viewer, !isOpaqueNameTagMode(player, viewer)));
|
||||
if (!isOpaqueNameTagMode(player, viewer) || viewer == player || !viewer.isOnline() || !player.isOnline()) {
|
||||
immediateResults.add(new OpaqueVisibilityResult(player, viewer, true));
|
||||
} else if (!canSeeForOpaque(viewer, player)) {
|
||||
immediateResults.add(new OpaqueVisibilityResult(player, viewer, false));
|
||||
} else {
|
||||
checks.add(new LineOfSightCheck(viewer, player));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (checks.isEmpty()) return;
|
||||
TAB.getInstance().getPlatform().runSyncGlobal(() -> {
|
||||
List<OpaqueVisibilityResult> results = new ArrayList<>(checks.size());
|
||||
for (OpaqueVisibilityCheck check : checks) {
|
||||
if (!check.player.isOnline() || !check.viewer.isOnline()) {
|
||||
results.add(new OpaqueVisibilityResult(check.player, check.viewer, true));
|
||||
continue;
|
||||
}
|
||||
results.add(new OpaqueVisibilityResult(check.player, check.viewer,
|
||||
check.forceVisible || check.viewer == check.player || hasLineOfSight(check.viewer, check.player)));
|
||||
if (checks.isEmpty() && immediateResults.isEmpty()) return;
|
||||
long sequence = opaqueRefreshSequence.incrementAndGet();
|
||||
if (checks.isEmpty()) {
|
||||
getCustomThread().execute(new TimedCaughtTask(TAB.getInstance().getCpu(),
|
||||
() -> applyOpaqueOcclusionResults(immediateResults, sequence), getFeatureName(), "Applying opaque nametag occlusion"));
|
||||
return;
|
||||
}
|
||||
TAB.getInstance().getPlatform().requestLineOfSight(checks, visible -> {
|
||||
List<OpaqueVisibilityResult> results = new ArrayList<>(immediateResults);
|
||||
for (int i = 0; i < checks.size(); i++) {
|
||||
LineOfSightCheck check = checks.get(i);
|
||||
results.add(new OpaqueVisibilityResult(check.getTarget(), check.getViewer(), visible[i]));
|
||||
}
|
||||
getCustomThread().execute(new TimedCaughtTask(TAB.getInstance().getCpu(),
|
||||
() -> applyOpaqueOcclusionResults(results), getFeatureName(), "Applying opaque nametag occlusion"));
|
||||
() -> applyOpaqueOcclusionResults(results, sequence), getFeatureName(), "Applying opaque nametag occlusion"));
|
||||
});
|
||||
}
|
||||
|
||||
private void applyOpaqueOcclusionResults(@NonNull List<OpaqueVisibilityResult> results) {
|
||||
private void applyOpaqueOcclusionResults(@NonNull List<OpaqueVisibilityResult> results, long sequence) {
|
||||
if (sequence <= lastAppliedOpaqueRefresh) return;
|
||||
lastAppliedOpaqueRefresh = sequence;
|
||||
for (OpaqueVisibilityResult result : results) {
|
||||
if (!nameTags.getOnlinePlayers().contains(result.player) || !nameTags.getOnlinePlayers().contains(result.viewer)) continue;
|
||||
boolean visible = result.visible;
|
||||
@@ -302,6 +319,8 @@ public class VisibilityManager extends RefreshableFeature implements JoinListene
|
||||
visible = true;
|
||||
} else if (!isOpaqueNameTagMode(result.player, result.viewer)) {
|
||||
visible = true;
|
||||
} else if (!canSeeForOpaque(result.viewer, result.player)) {
|
||||
visible = false;
|
||||
}
|
||||
boolean changed = visible
|
||||
? result.player.teamData.showNametag(result.viewer, NameTagInvisibilityReason.OPAQUE_OCCLUSION)
|
||||
@@ -333,24 +352,10 @@ public class VisibilityManager extends RefreshableFeature implements JoinListene
|
||||
return player.teamData.isOpaqueNameTagMode(viewer) || opaqueNameTagViewers.contains(viewer);
|
||||
}
|
||||
|
||||
private boolean hasLineOfSight(@NonNull TabPlayer viewer, @NonNull TabPlayer target) {
|
||||
private boolean canSeeForOpaque(@NonNull TabPlayer viewer, @NonNull TabPlayer target) {
|
||||
if (!viewer.server.canSee(target.server)) return false;
|
||||
if (viewer.world != target.world) return false;
|
||||
if (!viewer.canSee(target)) return false;
|
||||
return TAB.getInstance().getPlatform().hasLineOfSight(viewer, target);
|
||||
}
|
||||
|
||||
private static class OpaqueVisibilityCheck {
|
||||
|
||||
@NotNull private final TabPlayer player;
|
||||
@NotNull private final TabPlayer viewer;
|
||||
private final boolean forceVisible;
|
||||
|
||||
private OpaqueVisibilityCheck(@NotNull TabPlayer player, @NotNull TabPlayer viewer, boolean forceVisible) {
|
||||
this.player = player;
|
||||
this.viewer = viewer;
|
||||
this.forceVisible = forceVisible;
|
||||
}
|
||||
return viewer.canSee(target);
|
||||
}
|
||||
|
||||
private static class OpaqueVisibilityResult {
|
||||
|
||||
-2
@@ -18,7 +18,6 @@ import java.util.Arrays;
|
||||
public class PlayerListObjectiveConfiguration {
|
||||
|
||||
@NotNull private final ConfigurationSection section;
|
||||
private final boolean enabled;
|
||||
@NotNull private final String value;
|
||||
@NotNull private final String fancyValue;
|
||||
@NotNull private final String title;
|
||||
@@ -72,7 +71,6 @@ public class PlayerListObjectiveConfiguration {
|
||||
|
||||
return new PlayerListObjectiveConfiguration(
|
||||
section,
|
||||
section.getBoolean("enabled", true),
|
||||
value,
|
||||
section.getString("fancy-value", "&7Ping: " + Placeholder.PING),
|
||||
section.getString("title", "TAB"),
|
||||
|
||||
+3
-6
@@ -138,15 +138,11 @@ public class YellowNumber extends RefreshableFeature implements JoinListener, Qu
|
||||
p.playerlistObjectiveData.disabled.set(!forced);
|
||||
return;
|
||||
}
|
||||
if (!configuration.isEnabled()) {
|
||||
p.playerlistObjectiveData.disabled.set(true);
|
||||
return;
|
||||
}
|
||||
updateVisibility(p, disabledNow);
|
||||
}
|
||||
|
||||
private boolean configuredDisabled(@NotNull TabPlayer p) {
|
||||
return !configuration.isEnabled() || disableChecker.isDisableConditionMet(p);
|
||||
return disableChecker.isDisableConditionMet(p);
|
||||
}
|
||||
|
||||
private void applyDisabledState(@NotNull TabPlayer p, boolean disabled) {
|
||||
@@ -301,8 +297,9 @@ 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, getValue(player), player.playerlistObjectiveData.fancyValue.getFormat(viewer));
|
||||
setScore(viewer, player, value, player.playerlistObjectiveData.fancyValue.getFormat(viewer));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -34,8 +34,9 @@ public class QueuedData {
|
||||
@Nullable
|
||||
private NameTagProxyPlayerData nametag;
|
||||
|
||||
/** Whether player is vanished or not */
|
||||
private boolean vanished;
|
||||
/** Whether player is vanished or not, {@code null} if no vanish status update was received */
|
||||
@Nullable
|
||||
private Boolean vanished;
|
||||
|
||||
/** Name of server the player is connected to */
|
||||
@NotNull
|
||||
|
||||
@@ -87,7 +87,7 @@ public class PlayerJoin extends ProxyMessage {
|
||||
decodedPlayer.setTabFormat(data.getTabFormat());
|
||||
decodedPlayer.setNametag(data.getNametag());
|
||||
decodedPlayer.setPlayerlist(data.getPlayerlist());
|
||||
decodedPlayer.setVanished(data.isVanished());
|
||||
if (data.getVanished() != null) decodedPlayer.setVanished(data.getVanished());
|
||||
}
|
||||
if (TAB.getInstance().getPlayer(decodedPlayer.getUniqueId()) == null) {
|
||||
TAB.getInstance().getFeatureManager().onJoin(decodedPlayer);
|
||||
|
||||
+2
-2
@@ -314,7 +314,7 @@ public class ScoreboardManagerImpl extends RefreshableFeature implements Scorebo
|
||||
throw new IllegalArgumentException("No registered scoreboard found with name " + name);
|
||||
}
|
||||
|
||||
Set<TabPlayer> players = removed.getPlayers();
|
||||
Set<TabPlayer> players = new HashSet<>(removed.getPlayers()); // Copy, unregister() clears the set
|
||||
removed.unregister();
|
||||
for (TabPlayer p : players) {
|
||||
p.scoreboardData.forcedScoreboard = null;
|
||||
@@ -328,7 +328,7 @@ public class ScoreboardManagerImpl extends RefreshableFeature implements Scorebo
|
||||
throw new IllegalArgumentException("This scoreboard (" + scoreboard.getName() + ") is not registered.");
|
||||
}
|
||||
|
||||
Set<TabPlayer> players = ((ScoreboardImpl)scoreboard).getPlayers();
|
||||
Set<TabPlayer> players = new HashSet<>(((ScoreboardImpl)scoreboard).getPlayers()); // Copy, unregister() clears the set
|
||||
scoreboard.unregister();
|
||||
for (TabPlayer p : players) {
|
||||
p.scoreboardData.forcedScoreboard = null;
|
||||
|
||||
@@ -78,6 +78,7 @@ public class Condition {
|
||||
|
||||
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()));
|
||||
for (ConditionalExpression expression : expressions) {
|
||||
|
||||
+1
-1
@@ -138,7 +138,7 @@ public class PlayerPlaceholderImpl extends TabPlaceholder implements PlayerPlace
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String getLastReturnedValue(@Nullable TabPlayer player) {
|
||||
public synchronized String getLastReturnedValue(@Nullable TabPlayer player) {
|
||||
if (player == null) return identifier;
|
||||
String value = player.lastPlaceholderReturnedValues.get(this);
|
||||
if (value != null) return value;
|
||||
|
||||
+5
-16
@@ -90,26 +90,15 @@ public class RelationalPlaceholderImpl extends TabPlaceholder implements Relatio
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateFromNested(@NonNull TabPlayer viewer) {
|
||||
for (TabPlayer target : TAB.getInstance().getOnlinePlayers()) {
|
||||
public void updateFromNested(@NonNull TabPlayer target) {
|
||||
for (TabPlayer viewer : TAB.getInstance().getOnlinePlayers()) {
|
||||
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()) return; // Updated on join
|
||||
for (RefreshableFeature f : reference.getUsedByFeatures()) {
|
||||
TimedCaughtTask task = new TimedCaughtTask(TAB.getInstance().getCpu(), () -> f.refresh(target, true),
|
||||
f.getFeatureName(), f.getRefreshDisplayName());
|
||||
if (f instanceof CustomThreaded) {
|
||||
((CustomThreaded) f).getCustomThread().execute(task);
|
||||
} else {
|
||||
task.run();
|
||||
}
|
||||
}
|
||||
updateParents(target);
|
||||
}
|
||||
if (!viewer.isLoaded()) return; // Updated on join
|
||||
if (!target.isLoaded()) return; // Updated on join
|
||||
for (RefreshableFeature f : reference.getUsedByFeatures()) {
|
||||
TimedCaughtTask task = new TimedCaughtTask(TAB.getInstance().getCpu(), () -> f.refresh(viewer, true),
|
||||
TimedCaughtTask task = new TimedCaughtTask(TAB.getInstance().getCpu(), () -> f.refresh(target, true),
|
||||
f.getFeatureName(), f.getRefreshDisplayName());
|
||||
if (f instanceof CustomThreaded) {
|
||||
((CustomThreaded) f).getCustomThread().execute(task);
|
||||
@@ -117,7 +106,7 @@ public class RelationalPlaceholderImpl extends TabPlaceholder implements Relatio
|
||||
task.run();
|
||||
}
|
||||
}
|
||||
updateParents(viewer);
|
||||
updateParents(target);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+1
-1
@@ -60,7 +60,7 @@ public class ServerPlaceholderImpl extends TabPlaceholder implements ServerPlace
|
||||
if (hasValueChanged(value)) {
|
||||
for (RefreshableFeature r : reference.getUsedByFeatures()) {
|
||||
for (TabPlayer all : TAB.getInstance().getOnlinePlayers()) {
|
||||
if (!all.isLoaded()) return; // Updated on join
|
||||
if (!all.isLoaded()) continue; // Updated on join
|
||||
TimedCaughtTask task = new TimedCaughtTask(TAB.getInstance().getCpu(), () -> r.refresh(all, false), r.getFeatureName(), r.getRefreshDisplayName());
|
||||
if (r instanceof CustomThreaded) {
|
||||
((CustomThreaded) r).getCustomThread().execute(task);
|
||||
|
||||
@@ -115,7 +115,9 @@ public abstract class TabPlaceholder implements Placeholder {
|
||||
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);
|
||||
nested.addParent(reference);
|
||||
if (reference != null) {
|
||||
nested.addParent(reference);
|
||||
}
|
||||
string = string.replace(s, nested.getHandle().parse(player));
|
||||
}
|
||||
return string;
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package me.neznamy.tab.shared.platform;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* A viewer and target whose block line of sight must be checked.
|
||||
* Results are returned in the same order as the submitted checks.
|
||||
*/
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public class LineOfSightCheck {
|
||||
|
||||
@NotNull private final TabPlayer viewer;
|
||||
@NotNull private final TabPlayer target;
|
||||
}
|
||||
@@ -15,6 +15,7 @@ import java.io.File;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* An interface with methods that are called in universal code,
|
||||
@@ -277,4 +278,21 @@ public interface Platform {
|
||||
default boolean hasLineOfSight(@NotNull TabPlayer viewer, @NotNull TabPlayer target) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks block line of sight without assuming that all entities share a server thread.
|
||||
* The callback receives one result per input check, in the same order. Platforms with
|
||||
* region-owned entities can override this to complete the checks asynchronously.
|
||||
*/
|
||||
default void requestLineOfSight(@NotNull List<LineOfSightCheck> checks, @NotNull Consumer<boolean[]> callback) {
|
||||
runSyncGlobal(() -> {
|
||||
boolean[] results = new boolean[checks.size()];
|
||||
for (int i = 0; i < checks.size(); i++) {
|
||||
LineOfSightCheck check = checks.get(i);
|
||||
results[i] = !check.getViewer().isOnline() || !check.getTarget().isOnline() ||
|
||||
hasLineOfSight(check.getViewer(), check.getTarget());
|
||||
}
|
||||
callback.accept(results);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -347,7 +347,9 @@ public abstract class TabPlayer implements me.neznamy.tab.api.TabPlayer {
|
||||
if (!VanishIntegration.getHandlers().isEmpty()) {
|
||||
try {
|
||||
for (VanishIntegration i : VanishIntegration.getHandlers()) {
|
||||
if (!i.canSee(this, target)) return false;
|
||||
// if (!i.canSee(this, target)) return false;
|
||||
// #1739 - Vanish integration should have an absolute say
|
||||
return i.canSee(this, target);
|
||||
}
|
||||
} catch (ConcurrentModificationException e) {
|
||||
// PV error, try again
|
||||
|
||||
+14
-13
@@ -49,6 +49,9 @@ 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) {
|
||||
@@ -143,6 +146,9 @@ 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);
|
||||
}
|
||||
@@ -154,6 +160,9 @@ 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);
|
||||
}
|
||||
@@ -324,14 +333,14 @@ public abstract class SafeScoreboard<T extends TabPlayer> implements Scoreboard
|
||||
* Team name in the packet
|
||||
* @param players
|
||||
* Players in the packet
|
||||
* @return Modified collection of players
|
||||
* @return Modified list of players
|
||||
*/
|
||||
@NotNull
|
||||
public Collection<String> onTeamPacket(int action, @NonNull String teamName, @NonNull Collection<String> players) {
|
||||
Collection<String> newList = new ArrayList<>();
|
||||
public List<String> onTeamPacket(int action, @NonNull String teamName, @NonNull Collection<String> players) {
|
||||
List<String> newList = new ArrayList<>();
|
||||
if (action == TeamAction.CREATE || action == TeamAction.ADD_PLAYER) {
|
||||
for (String entry : players) {
|
||||
Team expectedTeam = getExpectedTeam(entry);
|
||||
Team expectedTeam = teamsByEntries.get(entry);
|
||||
if (expectedTeam == null) {
|
||||
blockedTeamAdds.remove(entry);
|
||||
allowedTeamAdds.put(entry, teamName);
|
||||
@@ -350,7 +359,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 = getExpectedTeam(entry);
|
||||
Team expectedTeam = teamsByEntries.get(entry);
|
||||
if (expectedTeam != null) {
|
||||
allowedTeamAdds.remove(entry);
|
||||
blockedTeamAdds.remove(entry);
|
||||
@@ -371,14 +380,6 @@ 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 (target.getVersion().getNetworkId() < ProtocolVersion.V1_8.getNetworkId()) {
|
||||
if (player.getVersion().getNetworkId() < ProtocolVersion.V1_8.getNetworkId()) {
|
||||
return; // Display names are not supported on 1.7 and below
|
||||
}
|
||||
if (containsEntry(target.getTablistId())) {
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
dependencies {
|
||||
implementation(projects.shared)
|
||||
implementation("org.bstats:bstats-velocity:3.1.0")
|
||||
implementation("org.bstats:bstats-velocity:3.2.1")
|
||||
compileOnly("com.github.limework.redisbungee:RedisBungee-Velocity:0.11.0")
|
||||
compileOnly("com.velocitypowered:velocity-api:3.4.0-SNAPSHOT")
|
||||
compileOnly("com.velocitypowered:velocity-proxy:3.4.0-SNAPSHOT")
|
||||
compileOnlyApi("net.kyori:adventure-nbt:4.17.0")
|
||||
annotationProcessor("com.velocitypowered:velocity-api:3.4.0-SNAPSHOT")
|
||||
compileOnly("com.github.LeonMangler:PremiumVanishAPI:2.9.0-4")
|
||||
compileOnly("net.william278:velocityscoreboardapi:2.0.0")
|
||||
compileOnly("net.william278:velocityscoreboardapi:2.1.1")
|
||||
compileOnly("io.github.miniplaceholders:miniplaceholders-api:3.1.0")
|
||||
}
|
||||
|
||||
|
||||
+16
-13
@@ -15,18 +15,11 @@ import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import me.neznamy.tab.shared.platform.decorators.SafeBossBar;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* The core for Velocity forwarding events into all enabled features
|
||||
*/
|
||||
public class VelocityEventListener implements EventListener<Player> {
|
||||
|
||||
/** Map for tracking online players */
|
||||
private final Map<Player, UUID> players = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* Listens to player disconnecting from the server.
|
||||
*
|
||||
@@ -35,11 +28,16 @@ public class VelocityEventListener implements EventListener<Player> {
|
||||
*/
|
||||
@Subscribe
|
||||
public void onQuit(@NotNull DisconnectEvent e) {
|
||||
if (TAB.getInstance().isPluginDisabled()) return;
|
||||
// Check if the player was actually connected to the server in the first place to avoid processing
|
||||
// disconnect of an existing player who is still there (because players are mapped by UUID in TAB)
|
||||
UUID id = players.remove(e.getPlayer());
|
||||
if (id != null) quit(id);
|
||||
TAB tab = TAB.getInstance();
|
||||
if (tab.isPluginDisabled()) return;
|
||||
tab.getCPUManager().runTask(() -> {
|
||||
// Only process the disconnect if the loaded player belongs to this exact connection to avoid processing
|
||||
// disconnect of a different session with the same UUID that is still connected (because players are mapped by UUID in TAB)
|
||||
TabPlayer player = tab.getPlayer(e.getPlayer().getUniqueId());
|
||||
if (player != null && player.getPlayer() == e.getPlayer()) {
|
||||
tab.getFeatureManager().onQuit(player);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -73,8 +71,13 @@ public class VelocityEventListener implements EventListener<Player> {
|
||||
if (tab.isPluginDisabled()) return;
|
||||
tab.getCPUManager().runTask(() -> {
|
||||
TabPlayer player = tab.getPlayer(e.getPlayer().getUniqueId());
|
||||
if (player != null && player.getPlayer() != e.getPlayer()) {
|
||||
// Player from a previous session with the same UUID whose disconnect was not processed yet, remove it
|
||||
tab.getFeatureManager().onQuit(player);
|
||||
player = null;
|
||||
}
|
||||
if (player == null) {
|
||||
players.put(e.getPlayer(), e.getPlayer().getUniqueId());
|
||||
if (!e.getPlayer().isActive()) return; // Player disconnected before this task ran, do not create a ghost player
|
||||
tab.getFeatureManager().onJoin(createPlayer(e.getPlayer()));
|
||||
} else {
|
||||
if (!(player.getScoreboard() instanceof VelocityScoreboard)) player.getScoreboard().resend();
|
||||
|
||||
Reference in new issue
Block a user