Compare commits

..
54 Commits
Author SHA1 Message Date
NEZNAMY fcdc04e228 5.2.3 2025-06-23 18:36:49 +02:00
NEZNAMY 890f1ee131 [Velocity] Improve message telling users to install VelocityScoreboardAPI 2025-06-23 18:36:37 +02:00
NEZNAMY f3aec47ac3 [BungeeCord] Send header/footer using API since it no longer throws an error 2025-06-22 10:48:31 +02:00
NEZNAMY bcf1c828b5 [BungeeCord] Update to build 1980 (older versions are no longer supported) 2025-06-22 10:40:34 +02:00
NEZNAMY 5fd319a152 [BossBar] Fix %tab_bossbar_visible% not loading on join when player has bossbar disabled and toggling is saved 2025-06-22 10:04:38 +02:00
NEZNAMY abecb58a8d [API] Add methods for removing bossbars and scoreboards (#1507) 2025-06-21 20:35:22 +02:00
NEZNAMY 135166b01e [Bukkit] Remove ViaVersion hook for sending packets due to many unfixable problems 2025-06-21 18:13:46 +02:00
NEZNAMY 0770293452 [Placeholders] Fix placeholder updating when placeholder returned its identifier not being skipped properly (#1502) 2025-06-21 13:57:16 +02:00
NEZNAMY f3c40ad582 [Bukkit] Disable ViaVersion hook for sending raw packets to newer players due to issues 2025-06-21 13:09:44 +02:00
NEZNAMY aacc115a36 [API] Add functions for getting original prefix/suffix values with placeholders parsed 2025-06-21 12:51:03 +02:00
NEZNAMY b7ba07b78d [Bukkit] Remove redundant SneakyThrows annotations 2025-06-21 10:45:27 +02:00
NEZNAMY e13445f55d [Bukkit] Fix skin writing in ViaVersion hook 2025-06-21 10:41:11 +02:00
NEZNAMY bd527d3868 [Configuration] Fix false positive in configuration checking (#1506) 2025-06-20 18:34:24 +02:00
NEZNAMY f288112eb2 [Bukkit] Fix error in ViaVersion hook 2025-06-20 16:18:03 +02:00
NEZNAMY 6b415e64a8 5.2.3-SNAPSHOT 2025-06-19 17:29:10 +02:00
NEZNAMY cfb2cb6b0a [Description] Update badges 2025-06-18 19:20:23 +02:00
NEZNAMY 622deb6fd7 5.2.2 2025-06-18 18:48:14 +02:00
NEZNAMY 7f41421d65 [Bukkit] Add 1.21.6 to supported versions 2025-06-18 17:34:51 +02:00
NEZNAMY 73e706465a Add 1.21.6 protocol constant 2025-06-18 17:31:54 +02:00
NEZNAMY a8c543ca9e [Paper] Update paperweight to 2.0.0-beta.17 2025-06-18 17:23:38 +02:00
NEZNAMY 9594ff452b Update Gradle to 8.14.2 2025-06-18 17:18:09 +02:00
NEZNAMY d5c81298dd [Modded platforms] Update to 1.21.6 2025-06-18 16:50:36 +02:00
NEZNAMY 88faacb240 [Proxy support] Properly handle out of order redis messages (#1465) 2025-06-17 21:00:49 +02:00
NEZNAMY 0d2a04b3a5 [Proxy support] Make method synchronized to try to avoid concurrent access 2025-06-17 18:06:38 +02:00
NEZNAMY f8a90b28b5 [Code style] Create static methods for initializing components 2025-06-16 19:57:58 +02:00
NEZNAMY a948febb18 [Belowname & Playerlist objective] Suggest a PlaceholderAPI expansion might be missing when a placeholder did not parse 2025-06-16 18:44:58 +02:00
NEZNAMY 8e33481b97 [Proxy support] Massive fixes to handling players who switch servers and temporarily cause the player to be loaded twice (#1465) 2025-06-15 19:03:50 +02:00
NEZNAMY f54be214f9 [Vanish compatibility] Assume a player can always see themselves 2025-06-15 15:03:14 +02:00
NEZNAMY d50b47acb5 [Configuration] Remove case sensitivity when checking for valid properties / configuration option names 2025-06-14 19:35:55 +02:00
NEZNAMY 31011c243a [Fabric] Update to 1.21.6-rc1 2025-06-12 18:52:20 +02:00
NEZNAMY f4aa03f20d [Conditions] Fix issue from recent commits 2025-06-12 18:51:56 +02:00
NEZNAMY 28aacaea45 [Fabric] Update to 1.21.6-pre4 2025-06-10 19:52:30 +02:00
NEZNAMY d4fd9a2b78 [Sorting] Fix PLACEHOLDER type not loading properly when ":" symbol is used in values (#1504) 2025-06-09 18:41:06 +02:00
NEZNAMY 663e0be357 [Tablist] Do not send update packets of vanished players, exposing them to players 2025-06-07 18:43:17 +02:00
NEZNAMY 14d29577c5 [Conditions] Fix condition compiling when delimiter is a part of used placeholder (#1503) 2025-06-07 15:22:48 +02:00
NEZNAMY 951f8072c1 [Placeholders] Add %luckperms-prefixes% and %luckperms-suffixes% placeholders listing all values instead of just the ones with the highest weight 2025-06-07 14:54:13 +02:00
NEZNAMY 05f042eda0 [Conditions] Improve condition type detection (#1503) 2025-06-07 11:37:32 +02:00
NEZNAMY b00c2fbcb4 [Fabric] Update to 1.21.6-pre3 2025-06-07 10:29:37 +02:00
NEZNAMY dde4866e50 [Belowname] Improve warning message 2025-06-05 19:17:42 +02:00
NEZNAMY 9fa6b21c08 [Code style] Extract code into a method 2025-06-03 19:51:04 +02:00
NEZNAMY e95e6fc20c Remove anti-override from config and make it permanently enabled 2025-06-03 17:14:18 +02:00
NEZNAMY c7a3b00c3c Merge pull request #1499 from R00tB33rMan/master
Remove printline when testing TPS & MSPT getter
2025-06-03 07:11:07 +02:00
R00tB33rMan 74bc0e0cb7 Remove printline when testing TPS & MSPT getter 2025-06-02 23:30:02 -04:00
NEZNAMY 6248008b4d [Fabric] Update to 1.21.6-pre2 2025-06-02 18:32:42 +02:00
NEZNAMY 2d91e165ce [Wiki] Monthly wiki backup for June 2025 2025-06-01 18:00:37 +02:00
NEZNAMY b1220550eb [Multi-server support] Code improvements for future issue debugging 2025-06-01 16:52:12 +02:00
NEZNAMY 680ff3412f [Bukkit] Fix wrong supported version range of a module 2025-06-01 16:09:45 +02:00
NEZNAMY 799f6b8215 [Folia] Implement %tps% and %mspt% (#1498) 2025-05-31 16:52:18 +02:00
NEZNAMY 1be224b7de [Fabric] Update to 1.21.6-pre1 2025-05-29 17:56:03 +02:00
NEZNAMY a32adfbfc7 [Bukkit] Add implementations for all paper versions with mojang mappings 2025-05-27 19:48:01 +02:00
NEZNAMY dda415351a Revert "[Fabric] Downgrade to 1.21.5 for release"
This reverts commit 6873c59505.
2025-05-23 17:08:57 +02:00
NEZNAMY 685e4b9faa 5.2.2-SNAPSHOT 2025-05-23 17:08:48 +02:00
NEZNAMY 1f842563b3 [Bukkit] Hotfix error 2025-05-22 21:45:09 +02:00
NEZNAMY f237b61065 [Description] Update badge to 5.2.1 2025-05-22 17:56:44 +02:00
170 changed files with 3075 additions and 2664 deletions

No files matched your search

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="138" height="20" role="img" aria-label="Minecraft: 1.5 - 1.21.5"><title>Minecraft: 1.5 - 1.21.5</title><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="138" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="61" height="20" fill="#555"/><rect x="61" width="77" height="20" fill="#007ec6"/><rect width="138" 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"><text aria-hidden="true" x="315" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="510">Minecraft</text><text x="315" y="140" transform="scale(.1)" fill="#fff" textLength="510">Minecraft</text><text aria-hidden="true" x="985" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="670">1.5 - 1.21.5</text><text x="985" y="140" transform="scale(.1)" fill="#fff" textLength="670">1.5 - 1.21.5</text></g></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="138" height="20" role="img" aria-label="Minecraft: 1.5 - 1.21.6"><title>Minecraft: 1.5 - 1.21.6</title><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="138" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="61" height="20" fill="#555"/><rect x="61" width="77" height="20" fill="#007ec6"/><rect width="138" 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"><text aria-hidden="true" x="315" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="510">Minecraft</text><text x="315" y="140" transform="scale(.1)" fill="#fff" textLength="510">Minecraft</text><text aria-hidden="true" x="985" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="670">1.5 - 1.21.6</text><text x="985" y="140" transform="scale(.1)" fill="#fff" textLength="670">1.5 - 1.21.6</text></g></svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

+1 -1
View File
@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="92" height="20" role="img" aria-label="Release: 5.2.0"><title>Release: 5.2.0</title><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" fill="#fff"/></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"><text aria-hidden="true" x="275" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="430">Release</text><text x="275" y="140" transform="scale(.1)" fill="#fff" textLength="430">Release</text><text aria-hidden="true" x="715" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="290">5.2.0</text><text x="715" y="140" transform="scale(.1)" fill="#fff" textLength="290">5.2.0</text></g></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="92" height="20" role="img" aria-label="Release: 5.2.2"><title>Release: 5.2.2</title><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" fill="#fff"/></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"><text aria-hidden="true" x="275" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="430">Release</text><text x="275" y="140" transform="scale(.1)" fill="#fff" textLength="430">Release</text><text aria-hidden="true" x="715" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="290">5.2.2</text><text x="715" y="140" transform="scale(.1)" fill="#fff" textLength="290">5.2.2</text></g></svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

+2 -2
View File
@@ -274,10 +274,10 @@ if __name__ == "__main__":
]
)
release = "5.2.0"
release = "5.2.2"
download_image(f"https://img.shields.io/badge/Release-{release}-blue.svg", "Badge_Release.svg")
minecraft = "1.5 - 1.21.5".replace(" ", "%20").replace("-", "--")
minecraft = "1.5 - 1.21.6".replace(" ", "%20").replace("-", "--")
download_image(f"https://img.shields.io/badge/Minecraft-{minecraft}-blue.svg", "Badge_Minecraft.svg")
java = "8+"
+1 -1
View File
@@ -70,7 +70,7 @@ TAB's shared module is compiled with Java 8. For the platforms themselves, TAB d
</tbody>
</table>
Modded platforms only support the latest MC version(s). For older MC versions try [modrinth](https://modrinth.com/plugin/tab-was-taken)'s version download filter. If you need a backport and no older version of TAB supports your desired server version, you'll need to edit it in the source code. Customers can ask for a backport on the customer discord.
Modded platforms only support the latest MC version(s). For older MC versions try [modrinth](https://modrinth.com/plugin/tab-was-taken)'s version download filter.
<sup>1</sup> Latest only doesn't mean only the latest build will work,
it means the plugin was made to be compatible with the latest version/build.
+3 -3
View File
@@ -35,7 +35,7 @@ For Maven users, you can add the API dependency like this:
<dependency>
<groupId>com.github.NEZNAMY</groupId>
<artifactId>TAB-API</artifactId>
<version>5.2.0</version>
<version>5.2.1</version>
<scope>provided</scope>
</dependency>
</dependencies>
@@ -50,7 +50,7 @@ repositories {
}
dependencies {
compileOnly 'com.github.NEZNAMY:TAB-API:5.2.0'
compileOnly 'com.github.NEZNAMY:TAB-API:5.2.1'
}
```
@@ -61,7 +61,7 @@ repositories {
}
dependencies {
compileOnly("com.github.NEZNAMY", "TAB-API", "5.2.0")
compileOnly("com.github.NEZNAMY", "TAB-API", "5.2.1")
}
```
@@ -51,9 +51,10 @@ condition must contain the altered output.
To see the exact output of a placeholder including color codes, use `/tab parse <player> <placeholder>`.
## Permission
| Operation | Description | Example |
|----------------------|------------------------|-------------------------------------------------------------------------------|
| `permission:<value>` | Permission requirement | `permission:my.permission` will pass if player has `my.permission` permission |
| Operation | Description | Example |
|-----------------------|---------------------------------|----------------------------------------------------------------------------------------------|
| `permission:<value>` | Permission requirement | `permission:my.permission` will pass if player has `my.permission` permission |
| `!permission:<value>` | Negative permission requirement | `!permission:my.permission` will pass if player **does not have** `my.permission` permission |
# Multiple condition requirements
Each condition has a `conditions` parameter, which a list of conditions. If you define more than 1 condition, you must specify the condition type.
@@ -11,15 +11,15 @@ This feature allows you to see players from other servers, instead of only seein
If TAB is installed on a backend server, you'll need to set up [Multi server support](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Multi-server-support) for proper linking of servers. On proxy installation, this is not required.
# Configuration
| Option name | Default value | Description |
|----------------------------------------|---------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| enabled | false | Enables / Disables the feature |
| server-groups | *Map* | See [Grouping players only from certain servers](#grouping-players-only-from-certain-servers) |
| spy-servers | *List* | See [Seeing all players on the network from some server](#seeing-all-players-on-the-network-from-some-server) |
| display-others-as-spectators | false | When enabled, players on different servers will appear as having spectator gamemode in tablist. |
| display-vanished-players-as-spectators | true | When enabled, vanished players will show as in spectator gamemode for those, who have permission to see vanished players (tab.seevanished) (others will obviously not see them at all). Vanished players will still see themselves in the gamemode they are actually in, since changing that would cause problems (client would think it's in spectator gamemode while it isn't). |
| isolate-unlisted-servers | false | When enabled, servers not listed in any group will not share playerlist with any other server, instead of sharing it with other unlisted servers. |
| update-latency | false | When **enabled**, plugin will send ping update to players on other servers, since backend servers only send ping updates of players on the same server. This is displayed as the green bar in tablist, unless altered by a mod / resource pack (?). <br /> <br />When **disabled**, ping of players on other servers will be set to 0 (5 bars). <br /> <br />Disabled by default, because it's not only not very useful, but also has a very high CPU usage to due spamming a large amount of packets (especially on large networks). |
| Option name | Default value | Description |
|----------------------------------------|---------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| enabled | false | Enables / Disables the feature |
| server-groups | *Map* | See [Grouping players only from certain servers](#grouping-players-only-from-certain-servers) |
| spy-servers | *List* | See [Seeing all players on the network from some server](#seeing-all-players-on-the-network-from-some-server) |
| display-others-as-spectators | false | When enabled, players on different servers will appear as having spectator gamemode in tablist. |
| display-vanished-players-as-spectators | true | When enabled, vanished players will show as in spectator gamemode for those, who have permission to see vanished players (tab.seevanished) (others will obviously not see them at all). Vanished players will still see themselves in the gamemode they are actually in, since changing that would cause problems (client would think it's in spectator gamemode while it isn't). |
| isolate-unlisted-servers | false | When enabled, servers not listed in any group will not share playerlist with any other server, instead of sharing it with other unlisted servers. |
| update-latency | false | When **enabled**, plugin will send ping update to players on other servers, since backend servers only send ping updates of players on the same server. This is displayed as the green bar in tablist, unless altered by a mod / resource pack (?). <br /> <br />When **disabled**, ping of players on other servers will be set to `0` (![image](https://user-images.githubusercontent.com/6338394/179717531-3c6409b6-6bf8-41c1-a150-ce0ed615e5a5.png)). <br /> <br />Disabled by default, because it's not only not very useful, but also has a very high CPU usage to due spamming a large amount of packets (especially on large networks). |
## Grouping players only from certain servers
In case you want multiple servers to share playerlist,
+3 -6
View File
@@ -157,12 +157,9 @@ To avoid it, make their names not match any online player and use holograms to d
Since 1.9 teams affect tamed animals as well, displaying prefix/suffix on them as well and if nametags are set to be invisible, they will be completely invisible as well. If you want to avoid it, install [this plugin](https://www.spigotmc.org/resources/109466/).
## Additional note 3 - Changing name itself
Teams do not allow to change the nametag name itself.
Changing name is a complicated process which is slightly out of scope of the plugin as well,
this is what nick plugins are about.
On top of that, it's a pain
requiring a ton of different packets which change their structure every version and with how many versions the plugin supports,
it doesn't sound like fun doing.
Teams do not allow to change the nametag name itself. The only way to achieve this goal is to send & modify packets to replace the player's actual profile name with a fake one. This is very complicated and can have a lot of side effects. This is out of scope of TAB and is in fact something nick plugins are trying to achieve. As such, **TAB does not offer this**.
Once you find a nick plugin that supports this, you will need to [make sure TAB properly detects this](https://github.com/NEZNAMY/TAB/wiki/How-to-display-name-from-nickname-plugins#proper-compatibility-with-plugins-that-change-profile-name) to make name-bound features (such as nametags) work properly for nicked players.
## Additional note 4 - F1 view
Using teams causes player nametags to remain visible when using F1 view.
@@ -18,14 +18,14 @@ Since 1.20.3, it can display any text.
This feature can be configured in **config.yml** under **playerlist-objective** section.
# Configuration
| Option name | Default value | Description |
|-------------------|--------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| enabled | true | Enables / Disables the feature |
| value | "%ping%" | [1.20.2-] Defines value displayed. You can set this to any placeholder that outputs a number. 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 | "&7Ping: %ping%" | [1.20.3+] Value to display for 1.20.3+ clients. Supports any text. |
| title | "Java Edition is better" | Title to send. Only visible on Bedrock Edition. |
| render-type | INTEGER | Render type of the value. Supported values are `INTEGER` and `HEARTS` |
| disable-condition | %world%=disabledworld | A [condition](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Conditional-placeholders) that must be met for disabling the feature for players. Set to empty for not disabling the feature ever. |
| Option name | Default value | Description |
|-------------------|-----------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| enabled | true | Enables / Disables the feature |
| value | "%ping%" | [1.20.2-] Defines value displayed. You can set this to any placeholder that outputs a number. 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 | "&7Ping: %ping%" | [1.20.3+] Value to display for 1.20.3+ clients. Supports any text. |
| title | "TAB" | Title to send. Only visible on Bedrock Edition. |
| render-type | INTEGER | Render type of the value. Supported values are `INTEGER` and `HEARTS` |
| disable-condition | %world%=disabledworld | A [condition](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Conditional-placeholders) that must be met for disabling the feature for players. Set to empty for not disabling the feature ever. |
# Limitations
* [1.5 - 1.20.2] Only 2 display types are supported - yellow number and hearts.
+3 -1
View File
@@ -4,4 +4,6 @@ A collection of things people have requested that I might look into implementing
* `invisible-nametags` supporting conditions like `enable-collision` does
* Relational placeholder support in sorting
* Conditional header/footer
* Relational conditions for layout player groups
* Relational conditions for layout player groups
* 1.21.2+ sorting support
* disable-condition support in multi-server-support
+1 -1
View File
@@ -1,5 +1,5 @@
# About the wiki
The wiki explains the functionality of the plugin as of version **5.2.0**.
The wiki explains the functionality of the plugin as of version **5.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.
@@ -42,7 +42,13 @@ Most notably, it breaks all name-bound features,
especially [nametags](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Nametags) and [sorting](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Sorting-players-in-tablist).
For that reason, TAB must detect name change.
This is done automatically on Bukkit, BungeeCord and Fabric.
This detection is not available on Sponge and Velocity.
**This detection is not available on Sponge and Velocity**.
> [!WARNING]
> If you have TAB installed on backend server and a proxy plugin changes nickname, TAB is not able to detect this change, since the modified packets never actually go through the backend server where TAB is installed.
> [!TIP]
> If automatic detection is not working for you (either you use TAB on Sponge / Velocity, or use proxy plugin for changing names while TAB is on backend), you can use [this API method](https://github.com/NEZNAMY/TAB/blob/master/api/src/main/java/me/neznamy/tab/api/TabPlayer.java#L88) to let TAB know player's name changed.
In order for TAB to properly detect name change, player UUID must remain the same,
otherwise it's a random entry with a random uuid and random name, which TAB isn't able to match with actual players.
+1 -1
View File
@@ -89,4 +89,4 @@ conditions:
```
Then, use this condition with `%condition:rgb%`.
This will not work in nametags and tablist formatting, because this would parse the condition for target player, not the viewer. To achieve this, you'll need a custom made [relational placeholder](https://github.com/NEZNAMY/TAB/wiki/Placeholders#relational-placeholders) that checks for viewer's version.
This will not work in nametags and tablist formatting, because this would parse the condition for target player, not the viewer. To achieve this, you'll need a custom-made [relational placeholder](https://github.com/NEZNAMY/TAB/wiki/Placeholders#relational-placeholders) that checks for viewer's version.
+1 -2
View File
@@ -33,8 +33,7 @@ install [VelocityScoreboardAPI](https://github.com/NEZNAMY/VelocityScoreboardAPI
> With proxy installation, use **/btab** command instead of **/tab**.
Advantages of proxy installation compared to installing on all backend servers instead:
* Can use the [Global player list](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Global-playerlist) and [Layout](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Layout) can work with all players connected to the proxy
* Can use new content for new clients even if a backend server version is old (1.16+ RGB codes, 1.20.3+ scoreboard features)
* Can use the [Global player list](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Global-playerlist) without requiring Redis set up and [Layout](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Layout) can work with all players connected to the proxy
* Avoid [header/footer](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Header-&-Footer#additional-note-1---not-resetting-on-server-switch) and [bossbar](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Bossbar#additional-note-2---not-hiding-on-server-switch) not disappearing on server switch
* Configuration files in a single place for easier editing / no need to use MySQL for syncing data
+9 -51
View File
@@ -6,12 +6,10 @@ The usage can be checked at any time using [**/tab cpu**](https://github.com/NEZ
* [#2 - [All platforms] Placeholder refresh intervals](#2---all-platforms-placeholder-refresh-intervals)
* [#3 - [All platforms] Don't use RGB](#3---all-platforms-dont-use-rgb)
* [#4 - [All platforms] Don't use animations](#4---all-platforms-dont-use-animations)
* [#5 - [All platforms] Disable anti-override](#5---all-platforms-disable-anti-override)
* [#6 - [All platforms] Disable tablist name formatting](#6---all-platforms-disable-tablist-name-formatting)
* [#7 - [All platforms] Increase permission refresh interval](#7---all-platforms-increase-permission-refresh-interval)
* [#8 - [BungeeCord / Velocity] Disable TAB expansion](#8---bungeecord--velocity-disable-tab-expansion)
* [#9 - [BungeeCord / Velocity] Disable update-latency option in global playerlist](#9---bungeecord--velocity-disable-update-latency-option-in-global-playerlist)
* [#10 - [BungeeCord] Disable ByteBuf deserialization](#10---bungeecord-disable-bytebuf-deserialization)
* [#5 - [All platforms] Disable tablist name formatting](#5---all-platforms-disable-tablist-name-formatting)
* [#6 - [All platforms] Increase permission refresh interval](#6---all-platforms-increase-permission-refresh-interval)
* [#7 - [BungeeCord / Velocity] Disable TAB expansion](#7---bungeecord--velocity-disable-tab-expansion)
* [#8 - [BungeeCord / Velocity] Disable update-latency option in global playerlist](#8---bungeecord--velocity-disable-update-latency-option-in-global-playerlist)
# #1 - [All platforms] Static text instead of placeholders
Static text doesn't need to be refreshed periodically, resulting in better performance.
@@ -56,27 +54,7 @@ This is because they work by spamming updates every time an animation switches t
When animations are not used, no updates need to be sent (unless another placeholder changes value),
not causing extra CPU usage.
# #5 - [All platforms] Disable anti-override
By default, TAB prevents other plugins from overriding it.
This is because many server owners are not competent enough to configure their plugins correctly,
such as disabling CMI collision handling which breaks nametags or even disabling team handling in paper.
This obviously causes higher CPU usage.
TAB logs all override attempts of teams into [anti-override.log](https://github.com/NEZNAMY/TAB/wiki/Action-logging#anti-overridelog) file.
If you didn't get the file, it looks like you configured your plugins correctly.
In that case, you can disable anti-override for teams in TAB.
```
scoreboard-teams:
anti-override: false
```
Tablist formatting is trickier, as override attempts are not logged because it would have a lot of false positives.
You will need to try and see.
```
tablist-name-formatting:
anti-override: false
```
# #6 - [All platforms] Disable tablist name formatting
# #5 - [All platforms] Disable tablist name formatting
As we already know from [Client-sided mechanics](https://github.com/NEZNAMY/TAB/wiki/Client%E2%80%90sided-mechanics#nametag-format-in-tablist) page,
the nametag format appears in tablist if tablist name is not defined.
You can take advantage of this to effectively disable one entire feature and its cpu usage,
@@ -87,12 +65,11 @@ However, you can only do this if:
Whether you have another plugin attempting to handle them or not may not be easy to identify with a lot of plugins and not enough time spent configuring them. In that case, your best choice is to try it and see. If tablist names break, it means you have another plugin and cannot take advantage of this optimization option.
# #7 - [All platforms] Increase permission refresh interval
# #6 - [All platforms] Increase permission refresh interval
Plugin needs to constantly check permissions for:
* Permission checks in conditions
* Permission checks if sorting by permissions
* Group retrieving from permission plugin (for sorting / per-group properties)
* Prefix/suffix placeholders taking data from your permission plugin
default value
```
@@ -102,7 +79,7 @@ Refreshes them every second.
If you don't need any permission/group changes to take effect within a second,
you can increase this value for better performance.
# #8 - [BungeeCord / Velocity] Disable TAB expansion
# #7 - [BungeeCord / Velocity] Disable TAB expansion
To maximize the performance of TAB's response to PlaceholderAPI request,
values are tracked in advance and then quickly returned.
This process takes resources and is especially heavy on proxy installation,
@@ -113,24 +90,5 @@ Disabling TAB's PlaceholderAPI expansion if you don't use it improves performanc
placeholders:
register-tab-expansion: false
```
# #9 - [BungeeCord / Velocity] Disable update-latency option in global playerlist
When [Global playerlist](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Global-playerlist) is enabled, you can enable / disable [update-latency](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Global-playerlist#:~:text=other%20unlisted%20servers.-,update%2Dlatency,-false) option (open the link for detailed description of the option). Disabling this option massively improves performance.
# #10 - [BungeeCord] Disable ByteBuf deserialization
In order for anti-override to work properly, some packets must be manually deserialized due to BungeeCord not doing it. If you don't need it, you can disable those functions, which will also disable the deserialization completely. To do so, you must disable 2 functions (only having 1 disabled is not enough):
#1 - Anti-override for teams
```
scoreboard-teams:
anti-override: false
```
Whether you need anti-override or not depends on the configuration of your plugins.
The easiest way is to have it enabled and see if you get `anti-override.log` file in TAB folder.
If you don't, you don't need it.
If you do, you'll need to do some configuring.
[Here](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Sorting-players-in-tablist#additional-note-3---compatibility-issues-with-other-plugins)
are a few examples of software and their team names, which you may find helpful.
#2 - Scoreboard detection of other plugins
TAB checks for packets sent by other plugins, and if another plugin sends a scoreboard,
TAB hides its own and resends it after the other plugin removes it.
This check cannot be disabled if the scoreboard is enabled.
# #8 - [BungeeCord / Velocity] Disable update-latency option in global playerlist
When [Global playerlist](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Global-playerlist) is enabled, you can enable / disable [update-latency](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Global-playerlist#:~:text=other%20unlisted%20servers.-,update%2Dlatency,-false) option (open the link for detailed description of the option). Disabling this option massively improves performance.
+1 -1
View File
@@ -28,7 +28,7 @@ Basic placeholders provided by the plugin. Most of these placeholders were added
Placeholders must be refreshed.
All internal placeholders have a manually defined refresh interval,
which I found the most optimal. You can [override the refresh intervals](https://github.com/NEZNAMY/TAB/wiki/Optimizing-the-plugin#2---all-platforms-placeholder-refresh-intervals) if want to.
which I found the most optimal. You can [override the refresh intervals](https://github.com/NEZNAMY/TAB/wiki/Optimizing-the-plugin#2---all-platforms-placeholder-refresh-intervals) if you want to.
Time is in milliseconds.
`-1` is used for placeholders which are in fact constants (such as %player%),
or fire an event each time they change, which can be listened to (such as %world%) and value updated.
+4 -4
View File
@@ -1,12 +1,12 @@
# About
When TAB is installed on BungeeCord, the plugin has very limited access to information. It is unable to retrieve internal server data, as well as hook into backend plugins, because it's a different server.
Bridge is a backend plugin that sends this data to the proxy, allowing TAB to work better when installed on BungeeCord.
When TAB is installed on BungeeCord / Velocity, the plugin has very limited access to information. It is unable to retrieve internal server data, as well as hook into backend plugins, because it's a different server.
Bridge is a backend plugin that sends this data to the proxy, allowing TAB to work better when installed on a proxy.
You can download it from [GitHub](https://github.com/NEZNAMY/TAB-Bridge/releases) or [Spigot](https://www.spigotmc.org/resources/83966).
# Installation
Installation is as simple as it can be.
Put it into the plugins folder of your backend servers.
Bridge is NOT a BungeeCord plugin!
Bridge is NOT a proxy plugin!
Bridge itself has no config file, as everything is configured in the main plugin.
# Features
@@ -20,7 +20,7 @@ Bridge itself has no config file, as everything is configured in the main plugin
* Forward player's gamemode for spectator fix feature to work
# Compatibility
Just like with Minecraft, when there is a change in the communication protocol,
Just like with Minecraft, when there is a change in the communication protocol between TAB <-> TAB-Bridge,
older software will not be compatible with the new one.
You should always be running the latest versions of both TAB and Bridge.
@@ -1,14 +1,14 @@
package me.neznamy.tab.api.bossbar;
import java.util.List;
import java.util.Map;
import lombok.NonNull;
import me.neznamy.tab.api.TabAPI;
import me.neznamy.tab.api.TabPlayer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.Map;
/**
* An interface allowing work with BossBars such as creating, sending
* and toggling.
@@ -78,6 +78,28 @@ public interface BossBarManager {
*/
@NotNull Map<String, BossBar> getRegisteredBossBars();
/**
* Removes BossBar with specified name from the manager, making it no longer available.
* If BossBar was displayed to any players, it is removed.
*
* @param name
* Name of registered BossBar to remove
* @throws IllegalArgumentException
* if no BossBar with specified name exists
*/
void removeBossBar(@NonNull String name);
/**
* Removes BossBar from the manager, making it no longer available.
* If BossBar was displayed to any players, it is removed.
*
* @param bossBar
* BossBar to remove
* @throws IllegalArgumentException
* if specified BossBar is not registered
*/
void removeBossBar(@NonNull BossBar bossBar);
/**
* Toggles BossBar for this player and sends toggle message if {@code sendToggleMessage} is true.
*
@@ -134,7 +134,7 @@ public interface NameTagManager {
* @param prefix
* new prefix value
* @see #getCustomPrefix(TabPlayer)
* @see #getOriginalPrefix(TabPlayer)
* @see #getOriginalRawPrefix(TabPlayer)
*/
void setPrefix(@NonNull TabPlayer player, @Nullable String prefix);
@@ -148,7 +148,7 @@ public interface NameTagManager {
* @param suffix
* new suffix value
* @see #getCustomSuffix(TabPlayer)
* @see #getOriginalSuffix(TabPlayer)
* @see #getOriginalRawSuffix(TabPlayer)
*/
void setSuffix(@NonNull TabPlayer player, @Nullable String suffix);
@@ -160,7 +160,7 @@ public interface NameTagManager {
* Player to get custom prefix of
* @return Custom prefix assigned using the API.
* @see #setPrefix(TabPlayer, String)
* @see #getOriginalPrefix(TabPlayer)
* @see #getOriginalRawPrefix(TabPlayer)
*/
@Nullable
String getCustomPrefix(@NonNull TabPlayer player);
@@ -173,7 +173,7 @@ public interface NameTagManager {
* Player to get custom suffix of
* @return Custom suffix assigned using the API.
* @see #setSuffix(TabPlayer, String)
* @see #getOriginalSuffix(TabPlayer)
* @see #getOriginalRawSuffix(TabPlayer)
*/
@Nullable
String getCustomSuffix(@NonNull TabPlayer player);
@@ -186,7 +186,7 @@ public interface NameTagManager {
* @see #getCustomPrefix(TabPlayer)
*/
@NotNull
String getOriginalPrefix(@NonNull TabPlayer player);
String getOriginalRawPrefix(@NonNull TabPlayer player);
/**
* Returns original suffix assigned by the plugin using internal logic.
@@ -196,6 +196,50 @@ public interface NameTagManager {
* @see #getCustomSuffix(TabPlayer)
*/
@NotNull
String getOriginalRawSuffix(@NonNull TabPlayer player);
/**
* Returns original prefix assigned by the plugin using internal logic with all placeholders replaced.
* @param player
* Player to get replaced original prefix of
* @return Original prefix assigned by the plugin's internal logic.
* @see #getCustomPrefix(TabPlayer)
*/
@NotNull
String getOriginalReplacedPrefix(@NonNull TabPlayer player);
/**
* Returns original suffix assigned by the plugin using internal logic with all placeholders replaced.
* @param player
* Player to get replaced original suffix of
* @return Original suffix assigned by the plugin's internal logic.
* @see #getCustomSuffix(TabPlayer)
*/
@NotNull
String getOriginalReplacedSuffix(@NonNull TabPlayer player);
/**
* Returns original suffix assigned by the plugin using internal logic.
* @param player
* Player to get replaced original prefix of
* @return Original prefix assigned by the plugin's internal logic.
* @see #getCustomPrefix(TabPlayer)
* @deprecated Use {@link #getOriginalRawPrefix(TabPlayer)} instead.
*/
@Deprecated
@NotNull
String getOriginalPrefix(@NonNull TabPlayer player);
/**
* Returns original suffix assigned by the plugin using internal logic.
* @param player
* Player to get replaced original suffix of
* @return Original suffix assigned by the plugin's internal logic.
* @see #getCustomSuffix(TabPlayer)
* @deprecated Use {@link #getOriginalRawSuffix(TabPlayer)} instead.
*/
@Deprecated
@NotNull
String getOriginalSuffix(@NonNull TabPlayer player);
/**
@@ -1,13 +1,13 @@
package me.neznamy.tab.api.scoreboard;
import java.util.List;
import java.util.Map;
import lombok.NonNull;
import me.neznamy.tab.api.TabPlayer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.Map;
/**
* Interface allowing work with scoreboards, such as creating,
* sending, toggling and modifying.
@@ -54,6 +54,29 @@ public interface ScoreboardManager {
* @return map of registered scoreboards
*/
@NotNull Map<String, Scoreboard> getRegisteredScoreboards();
/**
* Removes scoreboard with specified name from list of scoreboards. If it was displayed to anyone,
* it will be removed from their display as well. Then, those players will receive a new scoreboard based
* on configuration.
*
* @param name Name of scoreboard to remove
* @throws IllegalArgumentException
* if scoreboard with such name does not exist
*/
void removeScoreboard(@NonNull String name);
/**
* Removes scoreboard from list of scoreboards. If it was displayed to anyone,
* it will be removed from their display as well. Then, those players will receive a new scoreboard based
* on configuration.
*
* @param scoreboard
* Scoreboard to remove
* @throws IllegalArgumentException
* if specified scoreboard is not registered
*/
void removeScoreboard(@NonNull Scoreboard scoreboard);
/**
* Displays scoreboard to defined player. This will disable all display
@@ -27,7 +27,7 @@ public interface TabListFormatManager {
* @param prefix
* new prefix value
* @see #getCustomPrefix(TabPlayer)
* @see #getOriginalPrefix(TabPlayer)
* @see #getOriginalRawPrefix(TabPlayer)
*/
void setPrefix(@NonNull TabPlayer player, @Nullable String prefix);
@@ -41,7 +41,7 @@ public interface TabListFormatManager {
* @param customName
* new customName value
* @see #getCustomName(TabPlayer)
* @see #getOriginalName(TabPlayer)
* @see #getOriginalRawName(TabPlayer)
*/
void setName(@NonNull TabPlayer player, @Nullable String customName);
@@ -55,7 +55,7 @@ public interface TabListFormatManager {
* @param suffix
* new suffix value
* @see #getCustomSuffix(TabPlayer)
* @see #getOriginalSuffix(TabPlayer)
* @see #getOriginalRawSuffix(TabPlayer)
*/
void setSuffix(@NonNull TabPlayer player, @Nullable String suffix);
@@ -67,7 +67,7 @@ public interface TabListFormatManager {
* player to get custom prefix of
* @return player's custom prefix or {@code null} if not defined
* @see #setPrefix(TabPlayer, String)
* @see #getOriginalPrefix(TabPlayer)
* @see #getOriginalRawPrefix(TabPlayer)
*/
@Nullable String getCustomPrefix(@NonNull TabPlayer player);
@@ -104,8 +104,11 @@ public interface TabListFormatManager {
* @return player's original prefix
* @see #setPrefix(TabPlayer, String)
* @see #getCustomPrefix(TabPlayer)
* @deprecated Use {@link #getOriginalRawPrefix(TabPlayer)} instead.
*/
@NotNull String getOriginalPrefix(@NonNull TabPlayer player);
@NotNull
@Deprecated
String getOriginalPrefix(@NonNull TabPlayer player);
/**
* Returns player's original name applied using plugin's internal logic.
@@ -116,8 +119,11 @@ public interface TabListFormatManager {
* @return player's original name
* @see #setName(TabPlayer, String)
* @see #getCustomName(TabPlayer)
* @deprecated Use {@link #getOriginalRawName(TabPlayer)} instead.
*/
@NotNull String getOriginalName(@NonNull TabPlayer player);
@NotNull
@Deprecated
String getOriginalName(@NonNull TabPlayer player);
/**
* Returns player's original suffix applied using plugin's internal logic.
@@ -128,6 +134,69 @@ public interface TabListFormatManager {
* @return player's original suffix
* @see #setSuffix(TabPlayer, String)
* @see #getCustomSuffix(TabPlayer)
* @deprecated Use {@link #getOriginalRawSuffix(TabPlayer)} instead.
*/
@NotNull String getOriginalSuffix(@NonNull TabPlayer player);
@NotNull
@Deprecated
String getOriginalSuffix(@NonNull TabPlayer player);
/**
* Returns original prefix assigned by the plugin using internal logic.
* @param player
* Player to get original prefix of
* @return Original prefix assigned by the plugin's internal logic.
* @see #getCustomPrefix(TabPlayer)
*/
@NotNull
String getOriginalRawPrefix(@NonNull TabPlayer player);
/**
* Returns original name assigned by the plugin using internal logic.
* @param player
* Player to get original name of
* @return Original name assigned by the plugin's internal logic.
* @see #getCustomName(TabPlayer)
*/
@NotNull
String getOriginalRawName(@NonNull TabPlayer player);
/**
* Returns original suffix assigned by the plugin using internal logic.
* @param player
* Player to get original suffix of
* @return Original suffix assigned by the plugin's internal logic.
* @see #getCustomSuffix(TabPlayer)
*/
@NotNull
String getOriginalRawSuffix(@NonNull TabPlayer player);
/**
* Returns original prefix assigned by the plugin using internal logic with all placeholders replaced.
* @param player
* Player to get replaced original prefix of
* @return Original prefix assigned by the plugin's internal logic.
* @see #getCustomPrefix(TabPlayer)
*/
@NotNull
String getOriginalReplacedPrefix(@NonNull TabPlayer player);
/**
* Returns original name assigned by the plugin using internal logic with all placeholders replaced.
* @param player
* Player to get replaced original name of
* @return Original name assigned by the plugin's internal logic.
* @see #getCustomName(TabPlayer)
*/
@NotNull
String getOriginalReplacedName(@NonNull TabPlayer player);
/**
* Returns original suffix assigned by the plugin using internal logic with all placeholders replaced.
* @param player
* Player to get replaced original suffix of
* @return Original suffix assigned by the plugin's internal logic.
* @see #getCustomSuffix(TabPlayer)
*/
@NotNull
String getOriginalReplacedSuffix(@NonNull TabPlayer player);
}
+4 -2
View File
@@ -4,7 +4,7 @@ plugins {
allprojects {
group = "me.neznamy"
version = "5.2.1"
version = "5.2.3"
description = "An all-in-one solution that works"
ext.set("id", "tab")
@@ -15,7 +15,9 @@ allprojects {
val platforms = setOf(
projects.bukkit,
projects.bukkit.paper,
projects.bukkit.paper1205,
projects.bukkit.paper1212,
projects.bukkit.paper1214,
projects.bukkit.v18R3,
projects.bukkit.v112R1,
projects.bukkit.v116R3,
-2
View File
@@ -15,6 +15,4 @@ dependencies {
exclude("net.md-5", "bungeecord-chat")
}
compileOnly("com.github.LeonMangler:PremiumVanishAPI:2.8.8")
compileOnlyApi("com.viaversion:viaversion-api:5.2.1")
compileOnly("com.viaversion:viaversion-common:5.2.1")
}
+22
View File
@@ -0,0 +1,22 @@
plugins {
id("io.papermc.paperweight.userdev") version "2.0.0-beta.17"
}
repositories {
maven("https://jitpack.io") // YamlAssist
maven("https://repo.opencollab.dev/maven-snapshots/")
maven("https://repo.viaversion.com/")
maven("https://oss.sonatype.org/content/repositories/snapshots")
}
val version = "1.20.6-R0.1-SNAPSHOT" // 1.20.5 has a broken dependency, but there were no changes, so it's ok
dependencies {
implementation(projects.bukkit)
paperweight.paperDevBundle(version)
compileOnly("io.papermc.paper:paper-api:${version}")
}
tasks.compileJava {
options.release.set(21)
}
@@ -0,0 +1,49 @@
package me.neznamy.tab.platforms.paper_1_20_5;
import me.neznamy.chat.ChatModifier;
import me.neznamy.tab.platforms.bukkit.provider.ComponentConverter;
import net.minecraft.network.chat.*;
import net.minecraft.resources.ResourceLocation;
import org.jetbrains.annotations.NotNull;
/**
* Component converter using direct mojang-mapped code.
*/
public class PaperComponentConverter extends ComponentConverter {
@Override
@NotNull
public Object newTextComponent(@NotNull String text) {
return Component.literal(text);
}
@Override
@NotNull
public Object newTranslatableComponent(@NotNull String key) {
return Component.translatable(key);
}
@Override
@NotNull
public Object newKeybindComponent(@NotNull String keybind) {
return Component.keybind(keybind);
}
@Override
public void applyStyle(@NotNull Object nmsComponent, @NotNull ChatModifier modifier) {
Style style = Style.EMPTY
.withColor(modifier.getColor() == null ? null : TextColor.fromRgb(modifier.getColor().getRgb()))
.withBold(modifier.getBold())
.withItalic(modifier.getItalic())
.withUnderlined(modifier.getUnderlined())
.withStrikethrough(modifier.getStrikethrough())
.withObfuscated(modifier.getObfuscated())
.withFont(modifier.getFont() == null ? null : ResourceLocation.tryParse(modifier.getFont()));
((MutableComponent)nmsComponent).setStyle(style);
}
@Override
public void addSibling(@NotNull Object parent, @NotNull Object child) {
((MutableComponent)parent).append((Component) child);
}
}
@@ -1,13 +1,10 @@
package me.neznamy.tab.platforms.paper;
package me.neznamy.tab.platforms.paper_1_20_5;
import io.netty.channel.Channel;
import lombok.Getter;
import lombok.NonNull;
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.platforms.bukkit.provider.viaversion.ViaScoreboard;
import me.neznamy.tab.platforms.bukkit.provider.viaversion.ViaTabList;
import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.util.function.FunctionWithException;
@@ -15,7 +12,7 @@ import org.bukkit.craftbukkit.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
/**
* Implementation provider using direct Mojang-mapped NMS code (1.20.5+).
* Implementation provider using direct Mojang-mapped NMS code for versions 1.20.5 - 1.21.1.
*/
@Getter
public class PaperImplementationProvider implements ImplementationProvider {
@@ -29,16 +26,6 @@ public class PaperImplementationProvider implements ImplementationProvider {
return new PaperPacketScoreboard(player);
}
@Override
public void onPacketSend(@NonNull Object packet, @NonNull ViaScoreboard scoreboard) {
PaperPacketScoreboard.onPacketSend(packet, scoreboard);
}
@Override
public void onPacketSend(@NonNull Object packet, @NonNull ViaTabList tabList) {
PaperPacketTabList.onPacketSend(packet, tabList);
}
@Override
@NotNull
public TabList newTabList(@NotNull BukkitTabPlayer player) {
@@ -0,0 +1,173 @@
package me.neznamy.tab.platforms.paper_1_20_5;
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 me.neznamy.tab.shared.util.ReflectionUtils;
import net.minecraft.ChatFormatting;
import net.minecraft.network.chat.numbers.FixedFormat;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.*;
import net.minecraft.world.scores.PlayerTeam;
import net.minecraft.world.scores.Scoreboard;
import net.minecraft.world.scores.criteria.ObjectiveCriteria;
import net.minecraft.world.scores.criteria.ObjectiveCriteria.RenderType;
import org.bukkit.craftbukkit.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.Collections;
import java.util.Optional;
/**
* Scoreboard implementation using direct mojang-mapped code.
*/
public class PaperPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
private static final ChatFormatting[] formats = ChatFormatting.values();
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.
*
* @param player
* Player this scoreboard will belong to
*/
public PaperPacketScoreboard(@NotNull BukkitTabPlayer player) {
super(player);
}
@Override
public void registerObjective(@NonNull Objective objective) {
net.minecraft.world.scores.Objective obj = new net.minecraft.world.scores.Objective(
dummyScoreboard,
objective.getName(),
ObjectiveCriteria.DUMMY,
objective.getTitle().convert(),
RenderType.values()[objective.getHealthDisplay().ordinal()],
false,
objective.getNumberFormat() == null ? null : objective.getNumberFormat().toFixedFormat(FixedFormat::new)
);
objective.setPlatformObjective(obj);
sendPacket(new ClientboundSetObjectivePacket(obj, ObjectiveAction.REGISTER));
}
@Override
public void setDisplaySlot(@NonNull Objective objective) {
sendPacket(new ClientboundSetDisplayObjectivePacket(
net.minecraft.world.scores.DisplaySlot.values()[objective.getDisplaySlot().ordinal()],
(net.minecraft.world.scores.Objective) objective.getPlatformObjective()
));
}
@Override
public void unregisterObjective(@NonNull Objective objective) {
sendPacket(new ClientboundSetObjectivePacket((net.minecraft.world.scores.Objective) objective.getPlatformObjective(), ObjectiveAction.UNREGISTER));
}
@Override
public void updateObjective(@NonNull Objective objective) {
net.minecraft.world.scores.Objective obj = (net.minecraft.world.scores.Objective) objective.getPlatformObjective();
obj.setDisplayName(objective.getTitle().convert());
obj.setRenderType(RenderType.values()[objective.getHealthDisplay().ordinal()]);
sendPacket(new ClientboundSetObjectivePacket(obj, ObjectiveAction.UPDATE));
}
@Override
public void setScore(@NonNull Score score) {
sendPacket(new ClientboundSetScorePacket(
score.getHolder(),
score.getObjective().getName(),
score.getValue(),
Optional.ofNullable(score.getDisplayName() == null ? null : score.getDisplayName().convert()),
Optional.ofNullable(score.getNumberFormat() == null ? null : score.getNumberFormat().toFixedFormat(FixedFormat::new))
));
}
@Override
public void removeScore(@NonNull Score score) {
sendPacket(new ClientboundResetScorePacket(score.getHolder(), score.getObjective().getName()));
}
@Override
@NotNull
public Object createTeam(@NonNull String name) {
return new PlayerTeam(dummyScoreboard, name);
}
@Override
public void registerTeam(@NonNull Team team) {
updateTeamProperties(team);
PlayerTeam t = (PlayerTeam) team.getPlatformTeam();
t.getPlayers().addAll(team.getPlayers());
sendPacket(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(t, true));
}
@Override
public void unregisterTeam(@NonNull Team team) {
sendPacket(ClientboundSetPlayerTeamPacket.createRemovePacket((PlayerTeam) team.getPlatformTeam()));
}
@Override
public void updateTeam(@NonNull Team team) {
updateTeamProperties(team);
sendPacket(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket((PlayerTeam) team.getPlatformTeam(), false));
}
private void updateTeamProperties(@NonNull Team team) {
PlayerTeam t = (PlayerTeam) team.getPlatformTeam();
t.setAllowFriendlyFire((team.getOptions() & 0x01) != 0);
t.setSeeFriendlyInvisibles((team.getOptions() & 0x02) != 0);
t.setColor(formats[team.getColor().getLegacyColor().ordinal()]);
t.setCollisionRule(collisions[team.getCollision().ordinal()]);
t.setNameTagVisibility(visibilities[team.getVisibility().ordinal()]);
t.setPlayerPrefix(team.getPrefix().convert());
t.setPlayerSuffix(team.getSuffix().convert());
}
@Override
@SneakyThrows
public void onPacketSend(@NonNull Object packet) {
if (packet instanceof ClientboundSetDisplayObjectivePacket display) {
TAB.getInstance().getFeatureManager().onDisplayObjective(player, display.getSlot().ordinal(), display.getObjectiveName());
}
if (packet instanceof ClientboundSetObjectivePacket objective) {
TAB.getInstance().getFeatureManager().onObjective(player, objective.getMethod(), objective.getObjectiveName());
}
if (packet instanceof ClientboundSetPlayerTeamPacket team) {
int action = getMethod(team);
if (action == TeamAction.UPDATE) return;
players.set(team, onTeamPacket(action, team.getName(), team.getPlayers() == null ? Collections.emptyList() : team.getPlayers()));
}
}
private int getMethod(@NonNull ClientboundSetPlayerTeamPacket team) {
if (team.getTeamAction() == ClientboundSetPlayerTeamPacket.Action.ADD) {
return 0;
} else if (team.getTeamAction() == ClientboundSetPlayerTeamPacket.Action.REMOVE) {
return 1;
} else if (team.getPlayerAction() == ClientboundSetPlayerTeamPacket.Action.ADD) {
return 3;
} else if (team.getPlayerAction() == ClientboundSetPlayerTeamPacket.Action.REMOVE) {
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().connection.sendPacket(packet);
}
}
@@ -0,0 +1,190 @@
package me.neznamy.tab.platforms.paper_1_20_5;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
import lombok.NonNull;
import lombok.SneakyThrows;
import me.neznamy.chat.component.TabComponent;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.TAB;
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.Component;
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.ClientboundTabListPacket;
import net.minecraft.world.level.GameType;
import org.bukkit.craftbukkit.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Field;
import java.util.*;
/**
* TabList implementation using direct mojang-mapped code.
*/
public class PaperPacketTabList extends TrackedTabList<BukkitTabPlayer> {
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> addPlayer = EnumSet.allOf(ClientboundPlayerInfoUpdatePacket.Action.class);
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateDisplayName = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME);
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateLatency = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY);
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateGameMode = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE);
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateListed = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED);
private static final Field entries = ReflectionUtils.getOnlyField(ClientboundPlayerInfoUpdatePacket.class, List.class);
/**
* Constructs new instance.
*
* @param player
* Player this tablist will belong to
*/
public PaperPacketTabList(@NotNull BukkitTabPlayer player) {
super(player);
}
@Override
public void removeEntry(@NonNull UUID entry) {
sendPacket(new ClientboundPlayerInfoRemovePacket(Collections.singletonList(entry)));
}
@Override
public void updateDisplayName0(@NonNull UUID entry, @Nullable TabComponent displayName) {
sendPacket(updateDisplayName, entry, "", null, false, 0, 0, displayName);
}
@Override
public void updateLatency(@NonNull UUID entry, int latency) {
sendPacket(updateLatency, entry, "", null, false, latency, 0, null);
}
@Override
public void updateGameMode(@NonNull UUID entry, int gameMode) {
sendPacket(updateGameMode, entry, "", null, false, 0, gameMode, null);
}
@Override
public void updateListed(@NonNull UUID entry, boolean listed) {
sendPacket(updateListed, 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(addPlayer, entry.getUniqueId(), entry.getName(), entry.getSkin(), entry.isListed(), entry.getLatency(),
entry.getGameMode(), entry.getDisplayName());
}
@Override
public void setPlayerListHeaderFooter(@NonNull TabComponent header, @NonNull TabComponent footer) {
sendPacket(new ClientboundTabListPacket(header.convert(), footer.convert()));
}
@Override
public boolean containsEntry(@NonNull UUID entry) {
return true; // TODO?
}
@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.value(), property.signature());
}
@Override
@SneakyThrows
public void onPacketSend(@NonNull Object packet) {
if (packet instanceof ClientboundPlayerInfoUpdatePacket info) {
EnumSet<ClientboundPlayerInfoUpdatePacket.Action> actions = info.actions();
List<ClientboundPlayerInfoUpdatePacket.Entry> updatedList = new ArrayList<>();
boolean rewritePacket = false;
for (ClientboundPlayerInfoUpdatePacket.Entry nmsData : info.entries()) {
boolean rewriteEntry = false;
Component displayName = nmsData.displayName();
int latency = nmsData.latency();
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME)) {
TabComponent expectedDisplayName = getExpectedDisplayNames().get(nmsData.profileId());
if (expectedDisplayName != null && expectedDisplayName.convert() != displayName) {
displayName = expectedDisplayName.convert();
rewriteEntry = rewritePacket = true;
}
}
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY)) {
int newLatency = TAB.getInstance().getFeatureManager().onLatencyChange(player, nmsData.profileId(), latency);
if (newLatency != latency) {
latency = newLatency;
rewriteEntry = rewritePacket = true;
}
}
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER)) {
TAB.getInstance().getFeatureManager().onEntryAdd(player, nmsData.profileId(), nmsData.profile().getName());
}
updatedList.add(rewriteEntry ? new ClientboundPlayerInfoUpdatePacket.Entry(
nmsData.profileId(), nmsData.profile(), nmsData.listed(), latency, nmsData.gameMode(), displayName,
nmsData.chatSession()
) : nmsData);
}
if (rewritePacket) entries.set(info, updatedList);
}
}
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) {
ClientboundPlayerInfoUpdatePacket packet = new ClientboundPlayerInfoUpdatePacket(action, new ClientboundPlayerInfoUpdatePacket.Entry(
id,
action.contains(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER) ? createProfile(id, name, skin) : null,
listed,
latency,
GameType.byId(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().connection.send(packet);
}
}
+22
View File
@@ -0,0 +1,22 @@
plugins {
id("io.papermc.paperweight.userdev") version "2.0.0-beta.17"
}
repositories {
maven("https://jitpack.io") // YamlAssist
maven("https://repo.opencollab.dev/maven-snapshots/")
maven("https://repo.viaversion.com/")
maven("https://oss.sonatype.org/content/repositories/snapshots")
}
val version = "1.21.3-R0.1-SNAPSHOT" // 1.21.2 causes gradle error, but the versions are interchangeable
dependencies {
implementation(projects.bukkit)
paperweight.paperDevBundle(version)
compileOnly("io.papermc.paper:paper-api:${version}")
}
tasks.compileJava {
options.release.set(21)
}
@@ -0,0 +1,52 @@
package me.neznamy.tab.platforms.paper_1_21_2;
import me.neznamy.chat.ChatModifier;
import me.neznamy.tab.platforms.bukkit.provider.ComponentConverter;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.MutableComponent;
import net.minecraft.network.chat.Style;
import net.minecraft.network.chat.TextColor;
import net.minecraft.resources.ResourceLocation;
import org.jetbrains.annotations.NotNull;
/**
* Component converter using direct mojang-mapped code.
*/
public class PaperComponentConverter extends ComponentConverter {
@Override
@NotNull
public Object newTextComponent(@NotNull String text) {
return Component.literal(text);
}
@Override
@NotNull
public Object newTranslatableComponent(@NotNull String key) {
return Component.translatable(key);
}
@Override
@NotNull
public Object newKeybindComponent(@NotNull String keybind) {
return Component.keybind(keybind);
}
@Override
public void applyStyle(@NotNull Object nmsComponent, @NotNull ChatModifier modifier) {
Style style = Style.EMPTY
.withColor(modifier.getColor() == null ? null : TextColor.fromRgb(modifier.getColor().getRgb()))
.withBold(modifier.getBold())
.withItalic(modifier.getItalic())
.withUnderlined(modifier.getUnderlined())
.withStrikethrough(modifier.getStrikethrough())
.withObfuscated(modifier.getObfuscated())
.withFont(modifier.getFont() == null ? null : ResourceLocation.tryParse(modifier.getFont()));
((MutableComponent)nmsComponent).setStyle(style);
}
@Override
public void addSibling(@NotNull Object parent, @NotNull Object child) {
((MutableComponent)parent).append((Component) child);
}
}
@@ -0,0 +1,45 @@
package me.neznamy.tab.platforms.paper_1_21_2;
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.util.function.FunctionWithException;
import org.bukkit.craftbukkit.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
/**
* Implementation provider using direct Mojang-mapped NMS code for versions 1.21.2 - 1.21.3.
*/
@Getter
public class PaperImplementationProvider implements ImplementationProvider {
@NotNull
private final ComponentConverter componentConverter = new PaperComponentConverter();
@Override
@NotNull
public Scoreboard newScoreboard(@NotNull BukkitTabPlayer player) {
return new PaperPacketScoreboard(player);
}
@Override
@NotNull
public TabList newTabList(@NotNull BukkitTabPlayer player) {
return new PaperPacketTabList(player);
}
@Override
@NotNull
public FunctionWithException<BukkitTabPlayer, Channel> getChannelFunction() {
return player -> ((CraftPlayer)player.getPlayer()).getHandle().connection.connection.channel;
}
@Override
public int getPing(@NotNull BukkitTabPlayer player) {
return player.getPlayer().getPing();
}
}
@@ -1,4 +1,4 @@
package me.neznamy.tab.platforms.paper;
package me.neznamy.tab.platforms.paper_1_21_2;
import lombok.NonNull;
import lombok.SneakyThrows;
@@ -33,37 +33,6 @@ public class PaperPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
private static final Scoreboard dummyScoreboard = new Scoreboard();
private static final Field players = ReflectionUtils.getOnlyField(ClientboundSetPlayerTeamPacket.class, Collection.class);
@SneakyThrows
public static void onPacketSend(@NonNull Object packet, @NonNull SafeScoreboard<BukkitTabPlayer> scoreboard) {
if (scoreboard.isAntiOverrideScoreboard()) {
if (packet instanceof ClientboundSetDisplayObjectivePacket display) {
TAB.getInstance().getFeatureManager().onDisplayObjective(scoreboard.getPlayer(), display.getSlot().ordinal(), display.getObjectiveName());
}
if (packet instanceof ClientboundSetObjectivePacket objective) {
TAB.getInstance().getFeatureManager().onObjective(scoreboard.getPlayer(), objective.getMethod(), objective.getObjectiveName());
}
}
if (scoreboard.isAntiOverrideTeams() && packet instanceof ClientboundSetPlayerTeamPacket team) {
int action = getMethod(team);
if (action == TeamAction.UPDATE) return;
players.set(team, scoreboard.onTeamPacket(action, team.getName(), team.getPlayers() == null ? Collections.emptyList() : team.getPlayers()));
}
}
private static int getMethod(@NonNull ClientboundSetPlayerTeamPacket team) {
if (team.getTeamAction() == ClientboundSetPlayerTeamPacket.Action.ADD) {
return 0;
} else if (team.getTeamAction() == ClientboundSetPlayerTeamPacket.Action.REMOVE) {
return 1;
} else if (team.getPlayerAction() == ClientboundSetPlayerTeamPacket.Action.ADD) {
return 3;
} else if (team.getPlayerAction() == ClientboundSetPlayerTeamPacket.Action.REMOVE) {
return 4;
} else {
return 2;
}
}
/**
* Constructs new instance with given player.
*
@@ -163,8 +132,33 @@ public class PaperPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
}
@Override
@SneakyThrows
public void onPacketSend(@NonNull Object packet) {
onPacketSend(packet, this);
if (packet instanceof ClientboundSetDisplayObjectivePacket display) {
TAB.getInstance().getFeatureManager().onDisplayObjective(player, display.getSlot().ordinal(), display.getObjectiveName());
}
if (packet instanceof ClientboundSetObjectivePacket objective) {
TAB.getInstance().getFeatureManager().onObjective(player, objective.getMethod(), objective.getObjectiveName());
}
if (packet instanceof ClientboundSetPlayerTeamPacket team) {
int action = getMethod(team);
if (action == TeamAction.UPDATE) return;
players.set(team, onTeamPacket(action, team.getName(), team.getPlayers() == null ? Collections.emptyList() : team.getPlayers()));
}
}
private static int getMethod(@NonNull ClientboundSetPlayerTeamPacket team) {
if (team.getTeamAction() == ClientboundSetPlayerTeamPacket.Action.ADD) {
return 0;
} else if (team.getTeamAction() == ClientboundSetPlayerTeamPacket.Action.REMOVE) {
return 1;
} else if (team.getPlayerAction() == ClientboundSetPlayerTeamPacket.Action.ADD) {
return 3;
} else if (team.getPlayerAction() == ClientboundSetPlayerTeamPacket.Action.REMOVE) {
return 4;
} else {
return 2;
}
}
/**
@@ -0,0 +1,192 @@
package me.neznamy.tab.platforms.paper_1_21_2;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
import lombok.NonNull;
import lombok.SneakyThrows;
import me.neznamy.chat.component.TabComponent;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.TAB;
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.Component;
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.ClientboundTabListPacket;
import net.minecraft.world.level.GameType;
import org.bukkit.craftbukkit.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Field;
import java.util.*;
/**
* TabList implementation using direct mojang-mapped code.
*/
public class PaperPacketTabList extends TrackedTabList<BukkitTabPlayer> {
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> addPlayer = EnumSet.allOf(ClientboundPlayerInfoUpdatePacket.Action.class);
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateDisplayName = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME);
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateLatency = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY);
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateGameMode = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE);
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateListed = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED);
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateListOrder = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LIST_ORDER);
private static final Field entries = ReflectionUtils.getOnlyField(ClientboundPlayerInfoUpdatePacket.class, List.class);
/**
* Constructs new instance.
*
* @param player
* Player this tablist will belong to
*/
public PaperPacketTabList(@NotNull BukkitTabPlayer player) {
super(player);
}
@Override
public void removeEntry(@NonNull UUID entry) {
sendPacket(new ClientboundPlayerInfoRemovePacket(Collections.singletonList(entry)));
}
@Override
public void updateDisplayName0(@NonNull UUID entry, @Nullable TabComponent displayName) {
sendPacket(updateDisplayName, entry, "", null, false, 0, 0, displayName, 0);
}
@Override
public void updateLatency(@NonNull UUID entry, int latency) {
sendPacket(updateLatency, entry, "", null, false, latency, 0, null, 0);
}
@Override
public void updateGameMode(@NonNull UUID entry, int gameMode) {
sendPacket(updateGameMode, entry, "", null, false, 0, gameMode, null, 0);
}
@Override
public void updateListed(@NonNull UUID entry, boolean listed) {
sendPacket(updateListed, entry, "", null, listed, 0, 0, null, 0);
}
@Override
public void updateListOrder(@NonNull UUID entry, int listOrder) {
sendPacket(updateListOrder, entry, "", null, false, 0, 0, null, listOrder);
}
@Override
public void updateHat(@NonNull UUID entry, boolean showHat) {
// Added in 1.21.4
}
@Override
public void addEntry0(@NonNull Entry entry) {
sendPacket(addPlayer, entry.getUniqueId(), entry.getName(), entry.getSkin(), entry.isListed(), entry.getLatency(),
entry.getGameMode(), entry.getDisplayName(), entry.getListOrder());
}
@Override
public void setPlayerListHeaderFooter(@NonNull TabComponent header, @NonNull TabComponent footer) {
sendPacket(new ClientboundTabListPacket(header.convert(), footer.convert()));
}
@Override
public boolean containsEntry(@NonNull UUID entry) {
return true; // TODO?
}
@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.value(), property.signature());
}
@Override
@SneakyThrows
public void onPacketSend(@NonNull Object packet) {
if (packet instanceof ClientboundPlayerInfoUpdatePacket info) {
EnumSet<ClientboundPlayerInfoUpdatePacket.Action> actions = info.actions();
List<ClientboundPlayerInfoUpdatePacket.Entry> updatedList = new ArrayList<>();
boolean rewritePacket = false;
for (ClientboundPlayerInfoUpdatePacket.Entry nmsData : info.entries()) {
boolean rewriteEntry = false;
Component displayName = nmsData.displayName();
int latency = nmsData.latency();
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME)) {
TabComponent expectedDisplayName = getExpectedDisplayNames().get(nmsData.profileId());
if (expectedDisplayName != null && expectedDisplayName.convert() != displayName) {
displayName = expectedDisplayName.convert();
rewriteEntry = rewritePacket = true;
}
}
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY)) {
int newLatency = TAB.getInstance().getFeatureManager().onLatencyChange(player, nmsData.profileId(), latency);
if (newLatency != latency) {
latency = newLatency;
rewriteEntry = rewritePacket = true;
}
}
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER)) {
TAB.getInstance().getFeatureManager().onEntryAdd(player, nmsData.profileId(), nmsData.profile().getName());
}
updatedList.add(rewriteEntry ? new ClientboundPlayerInfoUpdatePacket.Entry(
nmsData.profileId(), nmsData.profile(), nmsData.listed(), latency, nmsData.gameMode(), displayName,
nmsData.listOrder(), nmsData.chatSession()
) : nmsData);
}
if (rewritePacket) entries.set(info, updatedList);
}
}
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) {
ClientboundPlayerInfoUpdatePacket packet = new ClientboundPlayerInfoUpdatePacket(action, new ClientboundPlayerInfoUpdatePacket.Entry(
id,
action.contains(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER) ? createProfile(id, name, skin) : null,
listed,
latency,
GameType.byId(gameMode),
displayName == null ? null : displayName.convert(),
listOrder,
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().connection.send(packet);
}
}
@@ -1,5 +1,5 @@
plugins {
id("io.papermc.paperweight.userdev") version "2.0.0-beta.16"
id("io.papermc.paperweight.userdev") version "2.0.0-beta.17"
}
repositories {
@@ -1,4 +1,4 @@
package me.neznamy.tab.platforms.paper;
package me.neznamy.tab.platforms.paper_1_21_4;
import me.neznamy.chat.ChatModifier;
import me.neznamy.tab.platforms.bukkit.provider.ComponentConverter;
@@ -0,0 +1,45 @@
package me.neznamy.tab.platforms.paper_1_21_4;
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.util.function.FunctionWithException;
import org.bukkit.craftbukkit.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
/**
* Implementation provider using direct Mojang-mapped NMS code for versions 1.21.4 - 1.21.6.
*/
@Getter
public class PaperImplementationProvider implements ImplementationProvider {
@NotNull
private final ComponentConverter componentConverter = new PaperComponentConverter();
@Override
@NotNull
public Scoreboard newScoreboard(@NotNull BukkitTabPlayer player) {
return new PaperPacketScoreboard(player);
}
@Override
@NotNull
public TabList newTabList(@NotNull BukkitTabPlayer player) {
return new PaperPacketTabList(player);
}
@Override
@NotNull
public FunctionWithException<BukkitTabPlayer, Channel> getChannelFunction() {
return player -> ((CraftPlayer)player.getPlayer()).getHandle().connection.connection.channel;
}
@Override
public int getPing(@NotNull BukkitTabPlayer player) {
return player.getPlayer().getPing();
}
}
@@ -0,0 +1,173 @@
package me.neznamy.tab.platforms.paper_1_21_4;
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 me.neznamy.tab.shared.util.ReflectionUtils;
import net.minecraft.ChatFormatting;
import net.minecraft.network.chat.numbers.FixedFormat;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.*;
import net.minecraft.world.scores.PlayerTeam;
import net.minecraft.world.scores.Scoreboard;
import net.minecraft.world.scores.criteria.ObjectiveCriteria;
import net.minecraft.world.scores.criteria.ObjectiveCriteria.RenderType;
import org.bukkit.craftbukkit.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.Collections;
import java.util.Optional;
/**
* Scoreboard implementation using direct mojang-mapped code.
*/
public class PaperPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
private static final ChatFormatting[] formats = ChatFormatting.values();
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.
*
* @param player
* Player this scoreboard will belong to
*/
public PaperPacketScoreboard(@NotNull BukkitTabPlayer player) {
super(player);
}
@Override
public void registerObjective(@NonNull Objective objective) {
net.minecraft.world.scores.Objective obj = new net.minecraft.world.scores.Objective(
dummyScoreboard,
objective.getName(),
ObjectiveCriteria.DUMMY,
objective.getTitle().convert(),
RenderType.values()[objective.getHealthDisplay().ordinal()],
false,
objective.getNumberFormat() == null ? null : objective.getNumberFormat().toFixedFormat(FixedFormat::new)
);
objective.setPlatformObjective(obj);
sendPacket(new ClientboundSetObjectivePacket(obj, ObjectiveAction.REGISTER));
}
@Override
public void setDisplaySlot(@NonNull Objective objective) {
sendPacket(new ClientboundSetDisplayObjectivePacket(
net.minecraft.world.scores.DisplaySlot.values()[objective.getDisplaySlot().ordinal()],
(net.minecraft.world.scores.Objective) objective.getPlatformObjective()
));
}
@Override
public void unregisterObjective(@NonNull Objective objective) {
sendPacket(new ClientboundSetObjectivePacket((net.minecraft.world.scores.Objective) objective.getPlatformObjective(), ObjectiveAction.UNREGISTER));
}
@Override
public void updateObjective(@NonNull Objective objective) {
net.minecraft.world.scores.Objective obj = (net.minecraft.world.scores.Objective) objective.getPlatformObjective();
obj.setDisplayName(objective.getTitle().convert());
obj.setRenderType(RenderType.values()[objective.getHealthDisplay().ordinal()]);
sendPacket(new ClientboundSetObjectivePacket(obj, ObjectiveAction.UPDATE));
}
@Override
public void setScore(@NonNull Score score) {
sendPacket(new ClientboundSetScorePacket(
score.getHolder(),
score.getObjective().getName(),
score.getValue(),
Optional.ofNullable(score.getDisplayName() == null ? null : score.getDisplayName().convert()),
Optional.ofNullable(score.getNumberFormat() == null ? null : score.getNumberFormat().toFixedFormat(FixedFormat::new))
));
}
@Override
public void removeScore(@NonNull Score score) {
sendPacket(new ClientboundResetScorePacket(score.getHolder(), score.getObjective().getName()));
}
@Override
@NotNull
public Object createTeam(@NonNull String name) {
return new PlayerTeam(dummyScoreboard, name);
}
@Override
public void registerTeam(@NonNull Team team) {
updateTeamProperties(team);
PlayerTeam t = (PlayerTeam) team.getPlatformTeam();
t.getPlayers().addAll(team.getPlayers());
sendPacket(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(t, true));
}
@Override
public void unregisterTeam(@NonNull Team team) {
sendPacket(ClientboundSetPlayerTeamPacket.createRemovePacket((PlayerTeam) team.getPlatformTeam()));
}
@Override
public void updateTeam(@NonNull Team team) {
updateTeamProperties(team);
sendPacket(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket((PlayerTeam) team.getPlatformTeam(), false));
}
private void updateTeamProperties(@NonNull Team team) {
PlayerTeam t = (PlayerTeam) team.getPlatformTeam();
t.setAllowFriendlyFire((team.getOptions() & 0x01) != 0);
t.setSeeFriendlyInvisibles((team.getOptions() & 0x02) != 0);
t.setColor(formats[team.getColor().getLegacyColor().ordinal()]);
t.setCollisionRule(collisions[team.getCollision().ordinal()]);
t.setNameTagVisibility(visibilities[team.getVisibility().ordinal()]);
t.setPlayerPrefix(team.getPrefix().convert());
t.setPlayerSuffix(team.getSuffix().convert());
}
@Override
@SneakyThrows
public void onPacketSend(@NonNull Object packet) {
if (packet instanceof ClientboundSetDisplayObjectivePacket display) {
TAB.getInstance().getFeatureManager().onDisplayObjective(player, display.getSlot().ordinal(), display.getObjectiveName());
}
if (packet instanceof ClientboundSetObjectivePacket objective) {
TAB.getInstance().getFeatureManager().onObjective(player, objective.getMethod(), objective.getObjectiveName());
}
if (packet instanceof ClientboundSetPlayerTeamPacket team) {
int action = getMethod(team);
if (action == TeamAction.UPDATE) return;
players.set(team, onTeamPacket(action, team.getName(), team.getPlayers() == null ? Collections.emptyList() : team.getPlayers()));
}
}
private int getMethod(@NonNull ClientboundSetPlayerTeamPacket team) {
if (team.getTeamAction() == ClientboundSetPlayerTeamPacket.Action.ADD) {
return 0;
} else if (team.getTeamAction() == ClientboundSetPlayerTeamPacket.Action.REMOVE) {
return 1;
} else if (team.getPlayerAction() == ClientboundSetPlayerTeamPacket.Action.ADD) {
return 3;
} else if (team.getPlayerAction() == ClientboundSetPlayerTeamPacket.Action.REMOVE) {
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().connection.sendPacket(packet);
}
}
@@ -1,4 +1,4 @@
package me.neznamy.tab.platforms.paper;
package me.neznamy.tab.platforms.paper_1_21_4;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
@@ -38,42 +38,6 @@ public class PaperPacketTabList extends TrackedTabList<BukkitTabPlayer> {
private static final Field entries = ReflectionUtils.getOnlyField(ClientboundPlayerInfoUpdatePacket.class, List.class);
@SneakyThrows
public static void onPacketSend(@NonNull Object packet, TrackedTabList<BukkitTabPlayer> tabList) {
if (packet instanceof ClientboundPlayerInfoUpdatePacket info) {
EnumSet<ClientboundPlayerInfoUpdatePacket.Action> actions = info.actions();
List<ClientboundPlayerInfoUpdatePacket.Entry> updatedList = new ArrayList<>();
boolean rewritePacket = false;
for (ClientboundPlayerInfoUpdatePacket.Entry nmsData : info.entries()) {
boolean rewriteEntry = false;
Component displayName = nmsData.displayName();
int latency = nmsData.latency();
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME)) {
TabComponent expectedDisplayName = tabList.getExpectedDisplayNames().get(nmsData.profileId());
if (expectedDisplayName != null && expectedDisplayName.convert() != displayName) {
displayName = expectedDisplayName.convert();
rewriteEntry = rewritePacket = true;
}
}
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY)) {
int newLatency = TAB.getInstance().getFeatureManager().onLatencyChange(tabList.getPlayer(), nmsData.profileId(), latency);
if (newLatency != latency) {
latency = newLatency;
rewriteEntry = rewritePacket = true;
}
}
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER)) {
TAB.getInstance().getFeatureManager().onEntryAdd(tabList.getPlayer(), nmsData.profileId(), nmsData.profile().getName());
}
updatedList.add(rewriteEntry ? new ClientboundPlayerInfoUpdatePacket.Entry(
nmsData.profileId(), nmsData.profile(), nmsData.listed(), latency, nmsData.gameMode(), displayName,
nmsData.showHat(), nmsData.listOrder(), nmsData.chatSession()
) : nmsData);
}
if (rewritePacket) entries.set(info, updatedList);
}
}
/**
* Constructs new instance.
*
@@ -145,8 +109,40 @@ public class PaperPacketTabList extends TrackedTabList<BukkitTabPlayer> {
}
@Override
@SneakyThrows
public void onPacketSend(@NonNull Object packet) {
onPacketSend(packet, this);
if (packet instanceof ClientboundPlayerInfoUpdatePacket info) {
EnumSet<ClientboundPlayerInfoUpdatePacket.Action> actions = info.actions();
List<ClientboundPlayerInfoUpdatePacket.Entry> updatedList = new ArrayList<>();
boolean rewritePacket = false;
for (ClientboundPlayerInfoUpdatePacket.Entry nmsData : info.entries()) {
boolean rewriteEntry = false;
Component displayName = nmsData.displayName();
int latency = nmsData.latency();
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME)) {
TabComponent expectedDisplayName = getExpectedDisplayNames().get(nmsData.profileId());
if (expectedDisplayName != null && expectedDisplayName.convert() != displayName) {
displayName = expectedDisplayName.convert();
rewriteEntry = rewritePacket = true;
}
}
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY)) {
int newLatency = TAB.getInstance().getFeatureManager().onLatencyChange(player, nmsData.profileId(), latency);
if (newLatency != latency) {
latency = newLatency;
rewriteEntry = rewritePacket = true;
}
}
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER)) {
TAB.getInstance().getFeatureManager().onEntryAdd(player, nmsData.profileId(), nmsData.profile().getName());
}
updatedList.add(rewriteEntry ? new ClientboundPlayerInfoUpdatePacket.Entry(
nmsData.profileId(), nmsData.profile(), nmsData.listed(), latency, nmsData.gameMode(), displayName,
nmsData.showHat(), nmsData.listOrder(), nmsData.chatSession()
) : nmsData);
}
if (rewritePacket) entries.set(info, updatedList);
}
}
private void sendPacket(@NonNull EnumSet<ClientboundPlayerInfoUpdatePacket.Action> action, @NonNull UUID id, @NonNull String name, @Nullable Skin skin,
@@ -23,6 +23,6 @@ public class BukkitPipelineInjector extends NettyPipelineInjector {
@NotNull
@SneakyThrows
protected Channel getChannel(@NotNull TabPlayer player) {
return ((BukkitPlatform)player.getPlatform()).getServerImplementationProvider().getChannelFunction().apply((BukkitTabPlayer) player);
return ((BukkitPlatform)player.getPlatform()).getImplementationProvider().getChannelFunction().apply((BukkitTabPlayer) player);
}
}
@@ -48,7 +48,7 @@ public class BukkitTabPlayer extends BackendTabPlayer {
@Override
public int getPing() {
return getPlatform().getServerImplementationProvider().getPing(this);
return getPlatform().getImplementationProvider().getPing(this);
}
@Override
@@ -1,6 +1,7 @@
package me.neznamy.tab.platforms.bukkit.platform;
import lombok.Getter;
import lombok.Setter;
import lombok.SneakyThrows;
import me.clip.placeholderapi.PlaceholderAPI;
import me.neznamy.chat.component.*;
@@ -14,7 +15,6 @@ import me.neznamy.tab.platforms.bukkit.provider.ImplementationProvider;
import me.neznamy.tab.platforms.bukkit.provider.bukkit.BukkitImplementationProvider;
import me.neznamy.tab.platforms.bukkit.provider.bukkit.PaperScoreboard;
import me.neznamy.tab.platforms.bukkit.provider.reflection.ReflectionImplementationProvider;
import me.neznamy.tab.platforms.bukkit.provider.viaversion.ViaVersionProvider;
import me.neznamy.tab.shared.GroupManager;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.TAB;
@@ -67,7 +67,6 @@ public class BukkitPlatform implements BackendPlatform {
private final JavaPlugin plugin;
/** Server version */
@Getter
private final ProtocolVersion serverVersion = ProtocolVersion.fromFriendlyName(Bukkit.getBukkitVersion().split("-")[0]);
/** Variables checking presence of other plugins to hook into */
@@ -84,13 +83,8 @@ public class BukkitPlatform implements BackendPlatform {
/** Implementation for creating new instances using content available on the server */
@NotNull
private final ImplementationProvider serverImplementationProvider = findImplementationProvider();
/** Implementation for sending new content to new players on old servers */
@Nullable
private final ViaVersionProvider viaVersionProvider =
ReflectionUtils.classExists("com.viaversion.viaversion.protocols.v1_20_2to1_20_3.Protocol1_20_2To1_20_3") ?
new ViaVersionProvider(serverVersion) : null;
@Setter
private ImplementationProvider implementationProvider = findImplementationProvider();
private final boolean modernOnlinePlayers;
@@ -118,16 +112,19 @@ public class BukkitPlatform implements BackendPlatform {
@NotNull
@SneakyThrows
private ImplementationProvider findImplementationProvider() {
if (ReflectionUtils.classExists("org.bukkit.craftbukkit.CraftServer") &&
(serverVersion == ProtocolVersion.V1_21_4 || serverVersion == ProtocolVersion.V1_21_5)) {
return (ImplementationProvider) Class.forName("me.neznamy.tab.platforms.paper.PaperImplementationProvider").getConstructor().newInstance();
// Check for mojang-mapped paper (1.20.5+)
String paperModule = getPaperModule();
if (paperModule != null) {
return (ImplementationProvider) Class.forName("me.neznamy.tab.platforms.paper_" + paperModule + ".PaperImplementationProvider").getConstructor().newInstance();
}
// Check for direct NMS on some supported versions
String serverPackage = BukkitReflection.getServerVersion().getServerPackage();
if (Arrays.asList("v1_8_R3", "v1_12_R1", "v1_16_R3", "v1_17_R1", "v1_18_R2", "v1_19_R1").contains(serverPackage) && serverVersion != ProtocolVersion.V1_19) {
return (ImplementationProvider) Class.forName("me.neznamy.tab.platforms.bukkit." + serverPackage + ".NMSImplementationProvider").getConstructor().newInstance();
}
// Try reflection
try {
return new ReflectionImplementationProvider();
} catch (Throwable e) {
@@ -158,12 +155,40 @@ public class BukkitPlatform implements BackendPlatform {
}
Bukkit.getConsoleSender().sendMessage("§c[TAB] Please use " +
"a plugin version with full support for your server version for optimal experience. This plugin version " +
"has full support for 1.8.8, 1.12.x, 1.16.5, 1.17.x, 1.18.2 and 1.19.1 - 1.21.5.");
"has full support for 1.8.8, 1.12.x, 1.16.5, 1.17.x, 1.18.2 and 1.19.1 - 1.21.6.");
}
return new BukkitImplementationProvider();
}
}
/**
* Returns name of the paper module that can be used on this server.
* If this server is not using paper or no module is available for any other reason,
* {@code null} is returned.
*
* @return Name of the available paper module or {@code null} if not available
*/
@Nullable
private String getPaperModule() {
if (!ReflectionUtils.classExists("org.bukkit.craftbukkit.CraftServer")) return null;
switch (serverVersion) {
case V1_20_5:
case V1_20_6:
case V1_21:
case V1_21_1:
return "1_20_5";
case V1_21_2:
case V1_21_3:
return "1_21_2";
case V1_21_4:
case V1_21_5:
case V1_21_6:
return "1_21_4";
default:
return null;
}
}
@Override
public void loadPlayers() {
for (Player p : getOnlinePlayers()) {
@@ -192,7 +217,7 @@ public class BukkitPlatform implements BackendPlatform {
@Override
@Nullable
public PipelineInjector createPipelineInjector() {
return serverImplementationProvider.getChannelFunction() != null ? new BukkitPipelineInjector() : null;
return implementationProvider.getChannelFunction() != null ? new BukkitPipelineInjector() : null;
}
@Override
@@ -246,7 +271,9 @@ public class BukkitPlatform implements BackendPlatform {
Bukkit.getScheduler().runTask(plugin, () -> {
long time = System.nanoTime();
ppl[0].updateValue(p, placeholderAPI ? PlaceholderAPI.setPlaceholders((Player) p.getPlayer(), syncedPlaceholder) : identifier);
TAB.getInstance().getCPUManager().addPlaceholderTime(identifier, System.nanoTime() - time);
long totalTime = System.nanoTime()-time;
TAB.getInstance().getCPUManager().addPlaceholderTime(identifier, totalTime);
TAB.getInstance().getCpu().addTime(TAB.getInstance().getPlaceholderManager().getFeatureName(), TabConstants.CpuUsageCategory.PLACEHOLDER_REQUEST, totalTime);
});
return null;
});
@@ -281,7 +308,7 @@ public class BukkitPlatform implements BackendPlatform {
command.setExecutor(cmd);
command.setTabCompleter(cmd);
} else {
logWarn(SimpleTextComponent.text("Failed to register command, is it defined in plugin.yml?"));
logWarn(TabComponent.legacyText("Failed to register command, is it defined in plugin.yml?"));
}
}
@@ -303,8 +330,8 @@ public class BukkitPlatform implements BackendPlatform {
@Override
@NotNull
public Object convertComponent(@NotNull TabComponent component) {
if (serverImplementationProvider.getComponentConverter() != null) {
return serverImplementationProvider.getComponentConverter().convert(component);
if (implementationProvider.getComponentConverter() != null) {
return implementationProvider.getComponentConverter().convert(component);
} else {
return component;
}
@@ -312,13 +339,8 @@ public class BukkitPlatform implements BackendPlatform {
@Override
@NotNull
@SneakyThrows
public Scoreboard createScoreboard(@NotNull TabPlayer player) {
if (viaVersionProvider != null) {
Scoreboard scoreboard = viaVersionProvider.newScoreboard((BukkitTabPlayer) player);
if (scoreboard != null) return scoreboard;
}
return serverImplementationProvider.newScoreboard((BukkitTabPlayer) player);
return implementationProvider.newScoreboard((BukkitTabPlayer) player);
}
@Override
@@ -339,13 +361,8 @@ public class BukkitPlatform implements BackendPlatform {
@Override
@NotNull
@SneakyThrows
public TabList createTabList(@NotNull TabPlayer player) {
if (viaVersionProvider != null) {
TabList tabList = viaVersionProvider.newTabList((BukkitTabPlayer) player);
if (tabList != null) return tabList;
}
return serverImplementationProvider.newTabList((BukkitTabPlayer) player);
return implementationProvider.newTabList((BukkitTabPlayer) player);
}
@Override
@@ -353,6 +370,11 @@ public class BukkitPlatform implements BackendPlatform {
return true;
}
@Override
public boolean supportsListOrder() {
return serverVersion.getNetworkId() >= ProtocolVersion.V1_21_2.getNetworkId();
}
@Override
public boolean isSafeFromPacketEventsBug() {
return serverVersion.getMinorVersion() >= 13;
@@ -1,14 +1,15 @@
package me.neznamy.tab.platforms.bukkit.platform;
import lombok.NonNull;
import lombok.SneakyThrows;
import me.clip.placeholderapi.PlaceholderAPI;
import me.neznamy.tab.platforms.bukkit.features.PerWorldPlayerList;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.chat.component.SimpleTextComponent;
import me.neznamy.tab.shared.cpu.TimedCaughtTask;
import me.neznamy.tab.shared.placeholders.types.PlayerPlaceholderImpl;
import me.neznamy.tab.shared.platform.TabPlayer;
import org.bukkit.Bukkit;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.event.player.PlayerChangedWorldEvent;
@@ -16,7 +17,10 @@ import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.NotNull;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.function.Consumer;
import java.util.function.Function;
/**
* Platform override for Folia.
@@ -37,10 +41,6 @@ public class FoliaPlatform extends BukkitPlatform {
public void loadPlayers() {
super.loadPlayers();
// Values are never updated in the API, warn users
logWarn(SimpleTextComponent.text("Folia never updates MSPT and TPS values in the API, making " +
"%mspt% and %tps% return the default values (0 and 20)."));
// Folia never calls PlayerChangedWorldEvent, this is a workaround
TAB.getInstance().getCpu().getProcessingThread().repeatTask(new TimedCaughtTask(TAB.getInstance().getCpu(), () -> {
for (TabPlayer player : TAB.getInstance().getOnlinePlayers()) {
@@ -56,6 +56,17 @@ public class FoliaPlatform extends BukkitPlatform {
}, "Folia compatibility", "Refreshing world"), 100);
}
@Override
public void registerPlaceholders() {
super.registerPlaceholders();
DecimalFormat decimal2;
DecimalFormatSymbols symbols = new DecimalFormatSymbols();
symbols.setDecimalSeparator('.');
decimal2 = new DecimalFormat("#.##", symbols);
registerInternalSyncPlaceholder(TabConstants.Placeholder.MSPT, 1000, p -> decimal2.format(Bukkit.getAverageTickTime()));
registerInternalSyncPlaceholder(TabConstants.Placeholder.TPS, 1000, p -> decimal2.format(Math.min(20, Bukkit.getTPS()[0])));
}
@Override
public void registerSyncPlaceholder(@NotNull String identifier) {
String syncedPlaceholder = "%" + identifier.substring(6);
@@ -64,13 +75,42 @@ public class FoliaPlatform extends BukkitPlatform {
runSync((Entity) p.getPlayer(), () -> {
long time = System.nanoTime();
String output = isPlaceholderAPI() ? PlaceholderAPI.setPlaceholders((Player) p.getPlayer(), syncedPlaceholder) : identifier;
TAB.getInstance().getCPUManager().addPlaceholderTime(identifier, System.nanoTime()-time);
long totalTime = System.nanoTime()-time;
TAB.getInstance().getCPUManager().addPlaceholderTime(identifier, totalTime);
TAB.getInstance().getCpu().addTime(TAB.getInstance().getPlaceholderManager().getFeatureName(), TabConstants.CpuUsageCategory.PLACEHOLDER_REQUEST, totalTime);
TAB.getInstance().getCPUManager().runTask(() -> ppl[0].updateValue(p, output)); // To ensure player is loaded
});
return null;
});
}
private void registerInternalSyncPlaceholder(@NonNull String identifier, int refresh, @NonNull Function<TabPlayer, String> function) {
PlayerPlaceholderImpl[] ppl = new PlayerPlaceholderImpl[1];
ppl[0] = TAB.getInstance().getPlaceholderManager().registerInternalPlayerPlaceholder(identifier, refresh, p -> {
runSync((Entity) p.getPlayer(), () -> {
long time = System.nanoTime();
String output = function.apply((TabPlayer) p);
long totalTime = System.nanoTime()-time;
TAB.getInstance().getCPUManager().addPlaceholderTime(identifier, totalTime);
TAB.getInstance().getCpu().addTime(TAB.getInstance().getPlaceholderManager().getFeatureName(), TabConstants.CpuUsageCategory.PLACEHOLDER_REQUEST, totalTime);
TAB.getInstance().getCPUManager().runTask(() -> ppl[0].updateValue(p, output)); // To ensure player is loaded
});
return null;
});
}
/**
* Overriding the method to fix initial error caused by ServerPlaceholder implementation trying to
* retrieve the value in constructor, but folia does not support that. Overriding this function to avoid the
* MSPT function being called in the wrong thread.
*
* @return -1
*/
@Override
public double getMSPT() {
return -1;
}
/**
* Runs task using player's entity scheduler. It's using reflection, because
* Folia uses Java 17 while TAB maintains Java 8 compatibility for compatibility
@@ -1,10 +1,7 @@
package me.neznamy.tab.platforms.bukkit.provider;
import io.netty.channel.Channel;
import lombok.NonNull;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.platforms.bukkit.provider.viaversion.ViaScoreboard;
import me.neznamy.tab.platforms.bukkit.provider.viaversion.ViaTabList;
import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.util.function.FunctionWithException;
@@ -16,10 +13,6 @@ public interface ImplementationProvider {
@NotNull
Scoreboard newScoreboard(@NotNull BukkitTabPlayer player);
void onPacketSend(@NonNull Object packet, @NonNull ViaScoreboard scoreboard);
void onPacketSend(@NonNull Object packet, @NonNull ViaTabList tabList);
@NotNull
TabList newTabList(@NotNull BukkitTabPlayer player);
@@ -2,12 +2,9 @@ package me.neznamy.tab.platforms.bukkit.provider.bukkit;
import io.netty.channel.Channel;
import lombok.Getter;
import lombok.NonNull;
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.platforms.bukkit.provider.viaversion.ViaScoreboard;
import me.neznamy.tab.platforms.bukkit.provider.viaversion.ViaTabList;
import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.impl.DummyScoreboard;
@@ -40,16 +37,6 @@ public class BukkitImplementationProvider implements ImplementationProvider {
}
}
@Override
public void onPacketSend(@NonNull Object packet, @NonNull ViaScoreboard scoreboard) {
// Do nothing
}
@Override
public void onPacketSend(@NonNull Object packet, @NonNull ViaTabList tabList) {
// Do nothing
}
@Override
@NotNull
public TabList newTabList(@NotNull BukkitTabPlayer player) {
@@ -136,21 +136,6 @@ public class PacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
}
}
@SneakyThrows
public static void onPacketSend(@NonNull Object packet, @NonNull SafeScoreboard<BukkitTabPlayer> scoreboard) {
if (scoreboard.isAntiOverrideScoreboard()) {
if (DisplayObjectiveClass.isInstance(packet)) {
TAB.getInstance().getFeatureManager().onDisplayObjective(scoreboard.getPlayer(), packetToSlot.apply(packet),
(String) DisplayObjective_OBJECTIVE_NAME.get(packet));
}
if (ObjectivePacketClass.isInstance(packet)) {
TAB.getInstance().getFeatureManager().onObjective(scoreboard.getPlayer(),
Objective_METHOD.getInt(packet), (String) Objective_OBJECTIVE_NAME.get(packet));
}
}
if (scoreboard.isAntiOverrideTeams()) teamPacketData.onPacketSend(scoreboard.getPlayer(), packet);
}
/**
* Constructs new instance with given player.
*
@@ -229,8 +214,17 @@ public class PacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
}
@Override
@SneakyThrows
public void onPacketSend(@NonNull Object packet) {
onPacketSend(packet, this);
if (DisplayObjectiveClass.isInstance(packet)) {
TAB.getInstance().getFeatureManager().onDisplayObjective(player, packetToSlot.apply(packet),
(String) DisplayObjective_OBJECTIVE_NAME.get(packet));
}
if (ObjectivePacketClass.isInstance(packet)) {
TAB.getInstance().getFeatureManager().onObjective(player,
Objective_METHOD.getInt(packet), (String) Objective_OBJECTIVE_NAME.get(packet));
}
teamPacketData.onPacketSend(player, packet);
}
@SneakyThrows
@@ -29,7 +29,7 @@ public class PacketSender {
Class<?> EntityPlayer = BukkitReflection.getClass("server.level.ServerPlayer", "server.level.EntityPlayer");
Class<?> PlayerConnection = BukkitReflection.getClass("server.network.ServerGamePacketListenerImpl", "server.network.PlayerConnection");
Field PLAYER_CONNECTION = ReflectionUtils.getOnlyField(EntityPlayer, PlayerConnection);
Method sendPacket = ReflectionUtils.getMethods(PlayerConnection, void.class, Packet).get(0);
Method sendPacket = ReflectionUtils.getPublicMethods(PlayerConnection, void.class, Packet).get(0);
send = (player, packet) -> {
if (player.connection == null) player.connection = PLAYER_CONNECTION.get(player.getHandle());
sendPacket.invoke(player.connection, packet);
@@ -157,52 +157,6 @@ public class PacketTabList extends TrackedTabList<BukkitTabPlayer> {
}
}
@SneakyThrows
public static void onPacketSend(@NonNull Object packet, @NonNull TrackedTabList<BukkitTabPlayer> tabList) {
if (!(PlayerInfoClass.isInstance(packet))) return;
EnumSet<?> actions = (EnumSet<?>) ACTION.get(packet);
List<Object> updatedList = new ArrayList<>();
boolean rewritePacket = false;
for (Object nmsData : (List<?>) PLAYERS.get(packet)) {
boolean rewriteEntry = false;
UUID id = (UUID) PlayerInfoData_UUID.get(nmsData);
GameProfile profile = (GameProfile) PlayerInfoData_Profile.get(nmsData);
Object displayName = PlayerInfoData_DisplayName.get(nmsData);
int latency = PlayerInfoData_Latency.getInt(nmsData);
int listOrder = v1_21_2Plus ? PlayerInfoData_ListOrder.getInt(nmsData) : 0;
boolean showHat = v1_21_4Plus && PlayerInfoData_ShowHat.getBoolean(nmsData);
if (actions.contains(actionUpdateDisplayName)) {
TabComponent expectedName = tabList.getExpectedDisplayNames().get(id);
if (expectedName != null && expectedName.convert() != displayName) {
displayName = expectedName.convert();
rewriteEntry = rewritePacket = true;
}
}
if (actions.contains(actionUpdateLatency)) {
int newLatency = TAB.getInstance().getFeatureManager().onLatencyChange(tabList.getPlayer(), id, latency);
if (newLatency != latency) {
latency = newLatency;
rewriteEntry = rewritePacket = true;
}
}
if (actions.contains(actionAddPlayer)) {
TAB.getInstance().getFeatureManager().onEntryAdd(tabList.getPlayer(), id, profile.getName());
}
// 1.19.3 is using records, which do not allow changing final fields, need to rewrite the list entirely
updatedList.add(rewriteEntry ? newPlayerInfoData(
id,
profile,
PlayerInfoData_Listed.getBoolean(nmsData),
latency,
PlayerInfoData_GameMode.get(nmsData),
displayName,
showHat,
listOrder,
PlayerInfoData_RemoteChatSession.get(nmsData)) : nmsData);
}
if (rewritePacket) PLAYERS.set(packet, updatedList);
}
@Override
@SneakyThrows
public void removeEntry(@NonNull UUID entry) {
@@ -282,8 +236,50 @@ public class PacketTabList extends TrackedTabList<BukkitTabPlayer> {
}
@Override
@SneakyThrows
public void onPacketSend(@NonNull Object packet) {
onPacketSend(packet, this);
if (!(PlayerInfoClass.isInstance(packet))) return;
EnumSet<?> actions = (EnumSet<?>) ACTION.get(packet);
List<Object> updatedList = new ArrayList<>();
boolean rewritePacket = false;
for (Object nmsData : (List<?>) PLAYERS.get(packet)) {
boolean rewriteEntry = false;
UUID id = (UUID) PlayerInfoData_UUID.get(nmsData);
GameProfile profile = (GameProfile) PlayerInfoData_Profile.get(nmsData);
Object displayName = PlayerInfoData_DisplayName.get(nmsData);
int latency = PlayerInfoData_Latency.getInt(nmsData);
int listOrder = v1_21_2Plus ? PlayerInfoData_ListOrder.getInt(nmsData) : 0;
boolean showHat = v1_21_4Plus && PlayerInfoData_ShowHat.getBoolean(nmsData);
if (actions.contains(actionUpdateDisplayName)) {
TabComponent expectedName = getExpectedDisplayNames().get(id);
if (expectedName != null && expectedName.convert() != displayName) {
displayName = expectedName.convert();
rewriteEntry = rewritePacket = true;
}
}
if (actions.contains(actionUpdateLatency)) {
int newLatency = TAB.getInstance().getFeatureManager().onLatencyChange(player, id, latency);
if (newLatency != latency) {
latency = newLatency;
rewriteEntry = rewritePacket = true;
}
}
if (actions.contains(actionAddPlayer)) {
TAB.getInstance().getFeatureManager().onEntryAdd(player, id, profile.getName());
}
// 1.19.3 is using records, which do not allow changing final fields, need to rewrite the list entirely
updatedList.add(rewriteEntry ? newPlayerInfoData(
id,
profile,
PlayerInfoData_Listed.getBoolean(nmsData),
latency,
PlayerInfoData_GameMode.get(nmsData),
displayName,
showHat,
listOrder,
PlayerInfoData_RemoteChatSession.get(nmsData)) : nmsData);
}
if (rewritePacket) PLAYERS.set(packet, updatedList);
}
@NotNull
@@ -327,13 +323,8 @@ public class PacketTabList extends TrackedTabList<BukkitTabPlayer> {
@Nullable
@Override
public Skin getSkin() {
return getSkin(player);
}
@Nullable
@SneakyThrows
public static Skin getSkin(@NotNull BukkitTabPlayer player) {
public Skin getSkin() {
Collection<Property> col = ((GameProfile) getProfile.invoke(player.getHandle())).getProperties().get(TabList.TEXTURES_PROPERTY);
if (col.isEmpty()) return null; //offline mode
Property property = col.iterator().next();
@@ -2,13 +2,10 @@ package me.neznamy.tab.platforms.bukkit.provider.reflection;
import io.netty.channel.Channel;
import lombok.Getter;
import lombok.NonNull;
import me.neznamy.tab.platforms.bukkit.BukkitReflection;
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.platforms.bukkit.provider.viaversion.ViaScoreboard;
import me.neznamy.tab.platforms.bukkit.provider.viaversion.ViaTabList;
import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.util.ReflectionUtils;
@@ -64,16 +61,6 @@ public class ReflectionImplementationProvider implements ImplementationProvider
return new PacketScoreboard(player);
}
@Override
public void onPacketSend(@NonNull Object packet, @NonNull ViaScoreboard scoreboard) {
PacketScoreboard.onPacketSend(packet, scoreboard);
}
@Override
public void onPacketSend(@NonNull Object packet, @NonNull ViaTabList tabList) {
PacketTabList.onPacketSend(packet, tabList);
}
@Override
@NotNull
public TabList newTabList(@NotNull BukkitTabPlayer player) {
@@ -1,196 +0,0 @@
package me.neznamy.tab.platforms.bukkit.provider.viaversion;
import com.viaversion.viaversion.api.Via;
import com.viaversion.viaversion.api.connection.UserConnection;
import com.viaversion.viaversion.api.protocol.Protocol;
import com.viaversion.viaversion.api.protocol.packet.PacketType;
import com.viaversion.viaversion.api.protocol.packet.PacketWrapper;
import com.viaversion.viaversion.api.protocol.packet.State;
import com.viaversion.viaversion.api.type.Types;
import lombok.NonNull;
import me.neznamy.chat.component.SimpleTextComponent;
import me.neznamy.chat.component.TabComponent;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.platforms.bukkit.platform.BukkitPlatform;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.platform.decorators.SafeScoreboard;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
/**
* Scoreboard abstract implementation using ViaVersion packets,
* focused to unlock new features on older versions, and also
* optimize packet conversion between versions.
*/
public abstract class ViaScoreboard extends SafeScoreboard<BukkitTabPlayer> {
private static final long CHECK_DELAY = 50;
private static final Object DUMMY = new Object();
protected final Class<? extends Protocol> protocol;
protected final PacketType setDisplayObjective;
private final PacketType setObjective;
protected final PacketType setScore;
private final PacketType setPlayerTeam;
/** User connection this scoreboard belongs to */
protected final UserConnection connection;
private transient ScheduledFuture<?> task;
private transient final Queue<PacketWrapper> queuedPackets = new ConcurrentLinkedQueue<>();
/**
* Constructs new instance with given player.
*
* @param player
* Player this scoreboard will belong to
* @param protocol
* Protocol to be sent packets through
* @param setDisplayObjective
* Objective creation packet
* @param setObjective
* Objective update packet
* @param setScore
* Score update packet
* @param setPlayerTeam
* Player team packet
*/
public ViaScoreboard(@NonNull BukkitTabPlayer player, @NonNull Class<? extends Protocol> protocol, @NonNull PacketType setDisplayObjective, @NonNull PacketType setObjective, @NonNull PacketType setScore, @NonNull PacketType setPlayerTeam) {
super(player);
this.protocol = protocol;
this.setDisplayObjective = setDisplayObjective;
this.setObjective = setObjective;
this.setScore = setScore;
this.setPlayerTeam = setPlayerTeam;
this.connection = Via.getManager().getConnectionManager().getConnectedClient(player.getUniqueId());
// Queue packets until connection is available
// Is important to use scheduleWithFixedDelay() to avoid task overlap
task = connection.getChannel().eventLoop().scheduleWithFixedDelay(() -> {
if (connection.getProtocolInfo().getClientState() == State.PLAY) {
PacketWrapper packet;
while ((packet = queuedPackets.poll()) != null) {
packet.send(protocol);
}
task.cancel(true);
}
}, 0L, CHECK_DELAY, TimeUnit.MILLISECONDS);
}
@Override
public void registerObjective(@NonNull Objective objective) {
sendObjectiveUpdate(ObjectiveAction.REGISTER, objective);
}
@Override
public void unregisterObjective(@NonNull Objective objective) {
sendObjectiveUpdate(ObjectiveAction.UNREGISTER, objective);
}
@Override
public void updateObjective(@NonNull Objective objective) {
sendObjectiveUpdate(ObjectiveAction.UPDATE, objective);
}
@Override
public @NonNull Object createTeam(@NonNull String name) {
// This implementation does not use team objects
return DUMMY;
}
@Override
public void registerTeam(@NonNull Team team) {
sendTeamUpdate(TeamAction.CREATE, team);
}
@Override
public void unregisterTeam(@NonNull Team team) {
sendTeamUpdate(TeamAction.REMOVE, team);
}
@Override
public void updateTeam(@NonNull Team team) {
sendTeamUpdate(TeamAction.UPDATE, team);
}
protected void sendObjectiveUpdate(int mode, @NonNull Objective objective) {
final PacketWrapper packet = PacketWrapper.create(setObjective, null, connection);
// Objective name
packet.write(Types.STRING, objective.getName());
// Mode
packet.write(Types.BYTE, (byte) mode);
// Optional
if (mode == ObjectiveAction.REGISTER || mode == ObjectiveAction.UPDATE) {
writeObjectiveDisplay(packet, objective);
}
send(packet);
}
@SuppressWarnings("fallthrough")
protected void sendTeamUpdate(int mode, @NonNull Team team) {
final PacketWrapper packet = PacketWrapper.create(setPlayerTeam, null, connection);
// Team name (max 16 characters on pre 1.20.3)
packet.write(Types.STRING, team.getName());
// Method
packet.write(Types.BYTE, (byte) mode);
switch (mode) {
case TeamAction.REMOVE:
break;
case TeamAction.CREATE:
case TeamAction.UPDATE:
// Team display name
writeComponent(packet, SimpleTextComponent.text(team.getName()));
// Friendly flags
packet.write(Types.BYTE, (byte) team.getOptions());
// Name tag visibility
packet.write(Types.STRING, team.getVisibility().toString());
// Collision rule
packet.write(Types.STRING, team.getCollision().toString());
// Team color
packet.write(Types.VAR_INT, team.getColor().getLegacyColor().ordinal());
// Team prefix
writeComponent(packet, team.getPrefix());
// Team suffix
writeComponent(packet, team.getSuffix());
if (mode == TeamAction.UPDATE) {
break;
}
// Intentional fallthrough for CREATE mode
case TeamAction.ADD_PLAYER:
case TeamAction.REMOVE_PLAYER:
// Entities
packet.write(Types.VAR_INT, team.getPlayers().size());
for (String entity : team.getPlayers()) {
// Entity (player username in this case) (max 40 characters on pre 1.20.3)
packet.write(Types.STRING, entity);
}
break;
default:
throw new IllegalArgumentException("Invalid team update mode: " + mode);
}
send(packet);
}
@Override
public void onPacketSend(@NonNull Object packet) {
((BukkitPlatform) TAB.getInstance().getPlatform()).getServerImplementationProvider().onPacketSend(packet, this);
}
protected abstract void writeComponent(@NonNull PacketWrapper packet, @NonNull TabComponent component);
protected abstract void writeObjectiveDisplay(@NonNull PacketWrapper packet, @NonNull Objective objective);
protected void send(@NonNull PacketWrapper packet) {
if (!task.isCancelled()) {
queuedPackets.add(packet);
} else {
packet.scheduleSend(protocol);
}
}
}
@@ -1,82 +0,0 @@
package me.neznamy.tab.platforms.bukkit.provider.viaversion;
import com.viaversion.viaversion.api.protocol.Protocol;
import com.viaversion.viaversion.api.protocol.packet.PacketType;
import com.viaversion.viaversion.api.protocol.packet.PacketWrapper;
import com.viaversion.viaversion.api.type.Types;
import com.viaversion.viaversion.protocols.v1_12_2to1_13.Protocol1_12_2To1_13;
import com.viaversion.viaversion.protocols.v1_12_2to1_13.packet.ClientboundPackets1_13;
import com.viaversion.viaversion.util.ComponentUtil;
import lombok.NonNull;
import me.neznamy.chat.component.TabComponent;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
/**
* Scoreboard implementation using ViaVersion packets
* to unlock +1.13 player features on pre 1.13 servers.
*/
public class ViaScoreboard113 extends ViaScoreboard {
/**
* Constructs new instance with given player.
*
* @param player Player this scoreboard will belong to
*/
public ViaScoreboard113(@NonNull BukkitTabPlayer player) {
this(player, Protocol1_12_2To1_13.class, ClientboundPackets1_13.SET_DISPLAY_OBJECTIVE, ClientboundPackets1_13.SET_OBJECTIVE, ClientboundPackets1_13.SET_SCORE, ClientboundPackets1_13.SET_PLAYER_TEAM);
}
protected ViaScoreboard113(@NonNull BukkitTabPlayer player, @NonNull Class<? extends Protocol> protocol, @NonNull PacketType setDisplayObjective, @NonNull PacketType setObjective, @NonNull PacketType setScore, @NonNull PacketType setPlayerTeam) {
super(player, protocol, setDisplayObjective, setObjective, setScore, setPlayerTeam);
}
@Override
public void setDisplaySlot(@NonNull Objective objective) {
PacketWrapper packet = PacketWrapper.create(setDisplayObjective, null, connection);
packet.write(Types.BYTE, (byte) objective.getDisplaySlot().ordinal());
packet.write(Types.STRING, objective.getName());
send(packet);
}
@Override
public void setScore(@NonNull Score score) {
sendScoreUpdate(ScoreAction.CHANGE, score);
}
@Override
public void removeScore(@NonNull Score score) {
sendScoreUpdate(ScoreAction.REMOVE, score);
}
protected void sendScoreUpdate(int action, @NonNull Score score) {
final PacketWrapper packet = PacketWrapper.create(setScore, null, connection);
// Entity name (player username in this case)
packet.write(Types.STRING, score.getHolder());
// Action
packet.write(Types.VAR_INT, action);
// Objective name (max 16 characters)
packet.write(Types.STRING, score.getObjective().getName());
if (action == 0) {
// Value
packet.write(Types.VAR_INT, score.getValue());
}
send(packet);
}
@Override
protected void writeComponent(@NonNull PacketWrapper packet, @NonNull TabComponent component) {
packet.write(Types.COMPONENT, ComponentUtil.legacyToJson(component.toLegacyText()));
}
@Override
protected void writeObjectiveDisplay(@NonNull PacketWrapper packet, @NonNull Objective objective) {
// Objective value
writeComponent(packet, objective.getTitle());
// Type
packet.write(Types.VAR_INT, objective.getHealthDisplay() == HealthDisplay.INTEGER ? 0 : 1);
}
}
@@ -1,36 +0,0 @@
package me.neznamy.tab.platforms.bukkit.provider.viaversion;
import com.viaversion.viaversion.api.protocol.Protocol;
import com.viaversion.viaversion.api.protocol.packet.PacketType;
import com.viaversion.viaversion.api.protocol.packet.PacketWrapper;
import com.viaversion.viaversion.api.type.Types;
import com.viaversion.viaversion.protocols.v1_15_2to1_16.Protocol1_15_2To1_16;
import com.viaversion.viaversion.protocols.v1_15_2to1_16.packet.ClientboundPackets1_16;
import lombok.NonNull;
import me.neznamy.chat.component.TabComponent;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
/**
* Scoreboard implementation using ViaVersion packets
* to unlock +1.16 player features on pre 1.16 servers.
*/
public class ViaScoreboard116 extends ViaScoreboard113 {
/**
* Constructs new instance with given player.
*
* @param player Player this scoreboard will belong to
*/
public ViaScoreboard116(@NonNull BukkitTabPlayer player) {
this(player, Protocol1_15_2To1_16.class, ClientboundPackets1_16.SET_DISPLAY_OBJECTIVE, ClientboundPackets1_16.SET_OBJECTIVE, ClientboundPackets1_16.SET_SCORE, ClientboundPackets1_16.SET_PLAYER_TEAM);
}
protected ViaScoreboard116(@NonNull BukkitTabPlayer player, @NonNull Class<? extends Protocol> protocol, @NonNull PacketType setDisplayObjective, @NonNull PacketType setObjective, @NonNull PacketType setScore, @NonNull PacketType setPlayerTeam) {
super(player, protocol, setDisplayObjective, setObjective, setScore, setPlayerTeam);
}
@Override
protected void writeComponent(@NonNull PacketWrapper packet, @NonNull TabComponent component) {
packet.write(Types.COMPONENT, component.toViaVersion());
}
}
@@ -1,101 +0,0 @@
package me.neznamy.tab.platforms.bukkit.provider.viaversion;
import com.viaversion.viaversion.api.protocol.Protocol;
import com.viaversion.viaversion.api.protocol.packet.PacketType;
import com.viaversion.viaversion.api.protocol.packet.PacketWrapper;
import com.viaversion.viaversion.api.type.Types;
import com.viaversion.viaversion.protocols.v1_20_2to1_20_3.Protocol1_20_2To1_20_3;
import com.viaversion.viaversion.protocols.v1_20_2to1_20_3.packet.ClientboundPackets1_20_3;
import lombok.NonNull;
import me.neznamy.chat.component.TabComponent;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import org.jetbrains.annotations.Nullable;
/**
* Scoreboard implementation using ViaVersion packets
* to unlock +1.20.3 player features on pre 1.20.3 servers.
*/
public class ViaScoreboard1203 extends ViaScoreboard116 {
private final PacketType resetScore;
/**
* Constructs new instance with given player.
*
* @param player Player this scoreboard will belong to
*/
public ViaScoreboard1203(@NonNull BukkitTabPlayer player) {
this(player, Protocol1_20_2To1_20_3.class, ClientboundPackets1_20_3.SET_DISPLAY_OBJECTIVE, ClientboundPackets1_20_3.SET_OBJECTIVE, ClientboundPackets1_20_3.SET_SCORE, ClientboundPackets1_20_3.RESET_SCORE, ClientboundPackets1_20_3.SET_PLAYER_TEAM);
}
protected ViaScoreboard1203(@NonNull BukkitTabPlayer player, @NonNull Class<? extends Protocol> protocol, @NonNull PacketType setDisplayObjective, @NonNull PacketType setObjective, @NonNull PacketType setScore, @NonNull PacketType resetScore, @NonNull PacketType setPlayerTeam) {
super(player, protocol, setDisplayObjective, setObjective, setScore, setPlayerTeam);
this.resetScore = resetScore;
}
@Override
public void setDisplaySlot(@NonNull Objective objective) {
PacketWrapper packet = PacketWrapper.create(setDisplayObjective, null, connection);
packet.write(Types.VAR_INT, objective.getDisplaySlot().ordinal());
packet.write(Types.STRING, objective.getName());
send(packet);
}
@Override
public void setScore(@NonNull Score score) {
final PacketWrapper packet = PacketWrapper.create(setScore, null, connection);
// Entity name (player username in this case)
packet.write(Types.STRING, score.getHolder());
// Objective name
packet.write(Types.STRING, score.getObjective().getName());
// Value
packet.write(Types.VAR_INT, score.getValue());
// Display name
packet.write(Types.OPTIONAL_TAG, score.getDisplayName() == null ? null : score.getDisplayName().toViaVersionTag());
// Number format
writeNumberFormat(packet, score.getNumberFormat());
send(packet);
}
@Override
public void removeScore(@NonNull Score score) {
final PacketWrapper packet = PacketWrapper.create(resetScore, null, connection);
// Entity name (player username in this case)
packet.write(Types.STRING, score.getHolder());
// Objective name
packet.write(Types.OPTIONAL_STRING, score.getObjective().getName().isEmpty() ? null : score.getObjective().getName());
send(packet);
}
@Override
protected void writeComponent(@NonNull PacketWrapper packet, @NonNull TabComponent component) {
packet.write(Types.TAG, component.toViaVersionTag());
}
@Override
protected void writeObjectiveDisplay(@NonNull PacketWrapper packet, @NonNull Objective objective) {
super.writeObjectiveDisplay(packet, objective);
writeNumberFormat(packet, objective.getNumberFormat());
}
protected void writeNumberFormat(@NonNull PacketWrapper packet, @Nullable TabComponent content) {
// For now, only fixed format is supported by TAB
if (content == null) {
// Has number format
packet.write(Types.BOOLEAN, false);
} else {
// Has number format
packet.write(Types.BOOLEAN, true);
// Fixed format
packet.write(Types.VAR_INT, 2);
// Content
packet.write(Types.TAG, content.toViaVersionTag());
}
}
}
@@ -1,185 +0,0 @@
package me.neznamy.tab.platforms.bukkit.provider.viaversion;
import com.viaversion.viaversion.api.Via;
import com.viaversion.viaversion.api.connection.UserConnection;
import com.viaversion.viaversion.api.protocol.Protocol;
import com.viaversion.viaversion.api.protocol.packet.PacketType;
import com.viaversion.viaversion.api.protocol.packet.PacketWrapper;
import com.viaversion.viaversion.api.protocol.packet.State;
import com.viaversion.viaversion.api.type.Types;
import lombok.NonNull;
import me.neznamy.chat.component.TabComponent;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.platforms.bukkit.platform.BukkitPlatform;
import me.neznamy.tab.platforms.bukkit.provider.reflection.PacketTabList;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.platform.decorators.TrackedTabList;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.BitSet;
import java.util.Queue;
import java.util.UUID;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
/**
* TabList abstract handler using ViaVersion packets,
* focused to unlock new features on older versions,
* and also optimize packet conversion between versions.
*/
public abstract class ViaTabList extends TrackedTabList<BukkitTabPlayer> {
private static final long CHECK_DELAY = 50;
protected final Class<? extends Protocol> protocol;
private final PacketType playerInfoUpdate;
private final PacketType tabList;
/** User connection this tablist belongs to */
protected final UserConnection connection;
private transient ScheduledFuture<?> task;
private transient final Queue<PacketWrapper> queuedPackets = new ConcurrentLinkedQueue<>();
/**
*
* @param player
* Player this tablist will belong to
* @param protocol
* Protocol to be sent packets through
* @param playerInfoUpdate
* Player information update packet
* @param tabList
* Tab list packet
*/
public ViaTabList(@NonNull BukkitTabPlayer player, @NonNull Class<? extends Protocol> protocol, @NonNull PacketType playerInfoUpdate, @NonNull PacketType tabList) {
super(player);
this.protocol = protocol;
this.playerInfoUpdate = playerInfoUpdate;
this.tabList = tabList;
this.connection = Via.getManager().getConnectionManager().getConnectedClient(player.getUniqueId());
// Queue packets until connection is available
// Is important to use scheduleWithFixedDelay() to avoid task overlap
task = connection.getChannel().eventLoop().scheduleWithFixedDelay(() -> {
if (connection.getProtocolInfo().getClientState() == State.PLAY) {
PacketWrapper packet;
while ((packet = queuedPackets.poll()) != null) {
packet.send(protocol);
}
task.cancel(true);
}
}, 0L, CHECK_DELAY, TimeUnit.MILLISECONDS);
}
@Override
public boolean containsEntry(@NonNull UUID entry) {
return true; // TODO?
}
@Override
public void addEntry0(@NonNull Entry entry) {
sendInfoUpdate(Action.ADD_PLAYER, entry.getUniqueId(), entry);
}
@Override
public void updateDisplayName0(@NonNull UUID entry, @Nullable TabComponent displayName) {
sendInfoUpdate(Action.UPDATE_DISPLAY_NAME, entry, displayName);
}
@Override
public void updateLatency(@NonNull UUID entry, int latency) {
sendInfoUpdate(Action.UPDATE_LATENCY, entry, latency);
}
@Override
public void updateGameMode(@NonNull UUID entry, int gameMode) {
sendInfoUpdate(Action.UPDATE_GAME_MODE, entry, gameMode);
}
@Override
public void setPlayerListHeaderFooter(@NonNull TabComponent header, @NonNull TabComponent footer) {
final PacketWrapper packet = PacketWrapper.create(tabList, null, connection);
writeComponent(packet, header);
writeComponent(packet, footer);
send(packet);
}
@Override
@Nullable
public Skin getSkin() {
return PacketTabList.getSkin(player);
}
protected void sendInfoUpdate(@NonNull Action action, @NonNull UUID uniqueId, Object value) {
final PacketWrapper packet = PacketWrapper.create(playerInfoUpdate, null, connection);
// Action
writeAction(packet, action);
// Players size
packet.write(Types.VAR_INT, 1);
// Players
packet.write(Types.UUID, uniqueId);
switch (action) {
case ADD_PLAYER:
writeEntry(packet, (Entry) value);
break;
case REMOVE_PLAYER:
break;
case UPDATE_GAME_MODE:
case UPDATE_LATENCY:
case UPDATE_LIST_ORDER:
packet.write(Types.VAR_INT, (int) value);
break;
case UPDATE_LISTED:
case UPDATE_HAT:
packet.write(Types.BOOLEAN, (boolean) value);
break;
case UPDATE_DISPLAY_NAME:
writeOptionalComponent(packet, (TabComponent) value);
break;
default:
throw new IllegalArgumentException("Cannot send info update with action " + action.name());
}
send(packet);
}
@Override
public void onPacketSend(@NonNull Object packet) {
((BukkitPlatform) TAB.getInstance().getPlatform()).getServerImplementationProvider().onPacketSend(packet, this);
}
protected abstract void writeComponent(@NonNull PacketWrapper packet, @NonNull TabComponent component);
protected abstract void writeOptionalComponent(@NonNull PacketWrapper packet, @Nullable TabComponent component);
protected abstract void writeAction(@NonNull PacketWrapper packet, @NonNull Action action);
protected abstract void writeEntry(@NonNull PacketWrapper packet, @NonNull Entry entry);
@NotNull
protected static BitSet bitSet(int nbits, int action) {
final BitSet bitSet = new BitSet(nbits);
bitSet.set(action);
return bitSet;
}
@NotNull
protected static BitSet bitSet(int nbits, int fromAction, int toAction) {
final BitSet bit = new BitSet(nbits);
bit.set(fromAction, toAction);
return bit;
}
protected void send(@NonNull PacketWrapper packet) {
if (!task.isCancelled()) {
queuedPackets.add(packet);
} else {
packet.scheduleSend(protocol);
}
}
}
@@ -1,114 +0,0 @@
package me.neznamy.tab.platforms.bukkit.provider.viaversion;
import com.viaversion.viaversion.api.protocol.Protocol;
import com.viaversion.viaversion.api.protocol.packet.PacketType;
import com.viaversion.viaversion.api.protocol.packet.PacketWrapper;
import com.viaversion.viaversion.api.type.Types;
import com.viaversion.viaversion.protocols.v1_15_2to1_16.Protocol1_15_2To1_16;
import com.viaversion.viaversion.protocols.v1_15_2to1_16.packet.ClientboundPackets1_16;
import lombok.NonNull;
import me.neznamy.chat.component.TabComponent;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.UUID;
/**
* TabList handler using ViaVersion packets,
* to unlock +1.16 player features on pre 1.16 servers.
*/
public class ViaTabList116 extends ViaTabList {
private static final int ADD_PLAYER = 0;
private static final int UPDATE_GAME_MODE = 1;
private static final int UPDATE_LATENCY = 2;
private static final int UPDATE_DISPLAY_NAME = 3;
private static final int REMOVE_PLAYER = 4;
/**
* Constructs new instance with given parameters.
*
* @param player
* Player this tablist will belong to
*/
public ViaTabList116(@NotNull BukkitTabPlayer player) {
this(player, Protocol1_15_2To1_16.class, ClientboundPackets1_16.PLAYER_INFO, ClientboundPackets1_16.TAB_LIST);
}
protected ViaTabList116(@NonNull BukkitTabPlayer player, @NonNull Class<? extends Protocol> protocol, @NonNull PacketType playerInfoUpdate, @NonNull PacketType tabList) {
super(player, protocol, playerInfoUpdate, tabList);
}
@Override
public void removeEntry(@NonNull UUID entry) {
sendInfoUpdate(Action.REMOVE_PLAYER, entry, null);
}
@Override
public void updateListed(@NonNull UUID entry, boolean listed) {
// Added on 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
protected void writeComponent(@NonNull PacketWrapper packet, @NonNull TabComponent component) {
packet.write(Types.COMPONENT, component.toViaVersion());
}
@Override
protected void writeOptionalComponent(@NonNull PacketWrapper packet, @Nullable TabComponent component) {
packet.write(Types.OPTIONAL_COMPONENT, component == null ? null : component.toViaVersion());
}
@Override
protected void writeAction(@NonNull PacketWrapper packet, @NonNull Action action) {
switch (action) {
case ADD_PLAYER:
packet.write(Types.VAR_INT, ADD_PLAYER);
break;
case REMOVE_PLAYER:
packet.write(Types.VAR_INT, REMOVE_PLAYER);
break;
case UPDATE_DISPLAY_NAME:
packet.write(Types.VAR_INT, UPDATE_DISPLAY_NAME);
break;
case UPDATE_LATENCY:
packet.write(Types.VAR_INT, UPDATE_LATENCY);
break;
case UPDATE_GAME_MODE:
packet.write(Types.VAR_INT, UPDATE_GAME_MODE);
break;
default:
throw new IllegalStateException("Cannot write " + action.name() + " for 1.16 packet");
}
}
@Override
protected void writeEntry(@NonNull PacketWrapper packet, @NonNull Entry entry) {
packet.write(Types.STRING, entry.getName());
if (entry.getSkin() == null) {
// Properties size
packet.write(Types.VAR_INT, 0);
} else {
// Properties size
packet.write(Types.VAR_INT, 1);
// Properties
packet.write(Types.STRING, entry.getName());
packet.write(Types.STRING, entry.getSkin().getValue());
packet.write(Types.OPTIONAL_STRING, entry.getSkin().getSignature());
}
packet.write(Types.VAR_INT, entry.getGameMode());
packet.write(Types.VAR_INT, entry.getLatency());
writeOptionalComponent(packet, entry.getDisplayName());
}
}
@@ -1,105 +0,0 @@
package me.neznamy.tab.platforms.bukkit.provider.viaversion;
import com.viaversion.viaversion.api.protocol.Protocol;
import com.viaversion.viaversion.api.protocol.packet.PacketType;
import com.viaversion.viaversion.api.protocol.packet.PacketWrapper;
import com.viaversion.viaversion.api.type.Types;
import com.viaversion.viaversion.protocols.v1_19_1to1_19_3.packet.ClientboundPackets1_19_3;
import com.viaversion.viaversion.protocols.v1_19_1to1_19_3.Protocol1_19_1To1_19_3;
import lombok.NonNull;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.BitSet;
import java.util.UUID;
/**
* TabList handler using ViaVersion packets,
* to unlock +1.19.3 player features on pre 1.19.3 servers.
*/
public class ViaTabList1193 extends ViaTabList116 {
private static final BitSet ADD_PLAYER = bitSet(6, 0, 6);
//private static final BitSet INITIALIZE_CHAT = bitSet(6, 1);
private static final BitSet UPDATE_GAME_MODE = bitSet(6, 2);
private static final BitSet UPDATE_LISTED = bitSet(6, 3);
private static final BitSet UPDATE_LATENCY = bitSet(6, 4);
private static final BitSet UPDATE_DISPLAY_NAME = bitSet(6, 5);
private final PacketType playerInfoRemove;
/**
* Constructs new instance with given parameters.
*
* @param player
* Player this tablist will belong to
*/
public ViaTabList1193(@NotNull BukkitTabPlayer player) {
this(player, Protocol1_19_1To1_19_3.class, ClientboundPackets1_19_3.PLAYER_INFO_REMOVE, ClientboundPackets1_19_3.PLAYER_INFO_UPDATE, ClientboundPackets1_19_3.TAB_LIST);
}
protected ViaTabList1193(@NonNull BukkitTabPlayer player, @NonNull Class<? extends Protocol> protocol, @Nullable PacketType playerInfoRemove, @NonNull PacketType playerInfoUpdate, @NonNull PacketType tabList) {
super(player, protocol, playerInfoUpdate, tabList);
this.playerInfoRemove = playerInfoRemove;
}
@Override
public void removeEntry(@NonNull UUID entry) {
final PacketWrapper packet = PacketWrapper.create(playerInfoRemove, null, connection);
// Players
packet.write(Types.UUID_ARRAY, new UUID[] { entry });
send(packet);
}
@Override
public void updateListed(@NonNull UUID entry, boolean listed) {
sendInfoUpdate(Action.UPDATE_LISTED, entry, listed);
}
@Override
protected void writeAction(@NonNull PacketWrapper packet, @NonNull Action action) {
switch (action) {
case ADD_PLAYER:
packet.write(Types.PROFILE_ACTIONS_ENUM1_19_3, ADD_PLAYER);
break;
case UPDATE_DISPLAY_NAME:
packet.write(Types.PROFILE_ACTIONS_ENUM1_19_3, UPDATE_DISPLAY_NAME);
break;
case UPDATE_LATENCY:
packet.write(Types.PROFILE_ACTIONS_ENUM1_19_3, UPDATE_LATENCY);
break;
case UPDATE_GAME_MODE:
packet.write(Types.PROFILE_ACTIONS_ENUM1_19_3, UPDATE_GAME_MODE);
break;
case UPDATE_LISTED:
packet.write(Types.PROFILE_ACTIONS_ENUM1_19_3, UPDATE_LISTED);
break;
default:
throw new IllegalStateException("Cannot write " + action.name() + " for 1.19.3 packet");
}
}
@Override
protected void writeEntry(@NonNull PacketWrapper packet, @NonNull Entry entry) {
packet.write(Types.STRING, entry.getName());
if (entry.getSkin() == null) {
// Properties size
packet.write(Types.VAR_INT, 0);
} else {
// Properties size
packet.write(Types.VAR_INT, 1);
// Properties
packet.write(Types.STRING, entry.getName());
packet.write(Types.STRING, entry.getSkin().getValue());
packet.write(Types.OPTIONAL_STRING, entry.getSkin().getSignature());
}
packet.write(Types.BOOLEAN, false); // No chat session data
packet.write(Types.VAR_INT, entry.getGameMode());
packet.write(Types.BOOLEAN, entry.isListed());
packet.write(Types.VAR_INT, entry.getLatency());
writeOptionalComponent(packet, entry.getDisplayName());
}
}
@@ -1,92 +0,0 @@
package me.neznamy.tab.platforms.bukkit.provider.viaversion;
import com.viaversion.viaversion.api.protocol.Protocol;
import com.viaversion.viaversion.api.protocol.packet.PacketType;
import com.viaversion.viaversion.api.protocol.packet.PacketWrapper;
import com.viaversion.viaversion.api.type.Types;
import com.viaversion.viaversion.protocols.v1_21to1_21_2.packet.ClientboundPackets1_21_2;
import com.viaversion.viaversion.protocols.v1_21to1_21_2.Protocol1_21To1_21_2;
import lombok.NonNull;
import me.neznamy.chat.component.TabComponent;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.BitSet;
import java.util.UUID;
/**
* TabList handler using ViaVersion packets,
* to unlock +1.21.2 player features on pre 1.21.2 servers.
*/
public class ViaTabList1212 extends ViaTabList1193 {
private static final BitSet ADD_PLAYER = bitSet(7, 0, 7);
//private static final BitSet INITIALIZE_CHAT = bitSet(7, 1);
private static final BitSet UPDATE_GAME_MODE = bitSet(7, 2);
private static final BitSet UPDATE_LISTED = bitSet(7, 3);
private static final BitSet UPDATE_LATENCY = bitSet(7, 4);
private static final BitSet UPDATE_DISPLAY_NAME = bitSet(7, 5);
private static final BitSet UPDATE_LIST_ORDER = bitSet(7, 6);
/**
* Constructs new instance with given parameters.
*
* @param player
* Player this tablist will belong to
*/
public ViaTabList1212(@NotNull BukkitTabPlayer player) {
super(player, Protocol1_21To1_21_2.class, ClientboundPackets1_21_2.PLAYER_INFO_REMOVE, ClientboundPackets1_21_2.PLAYER_INFO_UPDATE, ClientboundPackets1_21_2.TAB_LIST);
}
protected ViaTabList1212(@NonNull BukkitTabPlayer player, @NonNull Class<? extends Protocol> protocol, @Nullable PacketType playerInfoRemove, @NonNull PacketType playerInfoUpdate, @NonNull PacketType tabList) {
super(player, protocol, playerInfoRemove, playerInfoUpdate, tabList);
}
@Override
public void updateListOrder(@NonNull UUID entry, int listOrder) {
sendInfoUpdate(Action.UPDATE_LIST_ORDER, entry, listOrder);
}
@Override
protected void writeComponent(@NonNull PacketWrapper packet, @NonNull TabComponent component) {
packet.write(Types.TAG, component.toViaVersionTag());
}
@Override
protected void writeOptionalComponent(@NonNull PacketWrapper packet, @Nullable TabComponent component) {
packet.write(Types.OPTIONAL_TAG, component == null ? null : component.toViaVersionTag());
}
@Override
protected void writeAction(@NonNull PacketWrapper packet, @NonNull Action action) {
switch (action) {
case ADD_PLAYER:
packet.write(Types.PROFILE_ACTIONS_ENUM1_21_2, ADD_PLAYER);
break;
case UPDATE_DISPLAY_NAME:
packet.write(Types.PROFILE_ACTIONS_ENUM1_21_2, UPDATE_DISPLAY_NAME);
break;
case UPDATE_LATENCY:
packet.write(Types.PROFILE_ACTIONS_ENUM1_21_2, UPDATE_LATENCY);
break;
case UPDATE_GAME_MODE:
packet.write(Types.PROFILE_ACTIONS_ENUM1_21_2, UPDATE_GAME_MODE);
break;
case UPDATE_LISTED:
packet.write(Types.PROFILE_ACTIONS_ENUM1_21_2, UPDATE_LISTED);
break;
case UPDATE_LIST_ORDER:
packet.write(Types.PROFILE_ACTIONS_ENUM1_21_2, UPDATE_LIST_ORDER);
break;
default:
throw new IllegalStateException("Cannot write " + action.name() + " for 1.21.2 packet");
}
}
@Override
protected void writeEntry(@NonNull PacketWrapper packet, @NonNull Entry entry) {
super.writeEntry(packet, entry);
packet.write(Types.VAR_INT, entry.getListOrder());
}
}
@@ -1,78 +0,0 @@
package me.neznamy.tab.platforms.bukkit.provider.viaversion;
import com.viaversion.viaversion.api.protocol.packet.PacketWrapper;
import com.viaversion.viaversion.api.type.Types;
import com.viaversion.viaversion.protocols.v1_21_2to1_21_4.Protocol1_21_2To1_21_4;
import com.viaversion.viaversion.protocols.v1_21to1_21_2.packet.ClientboundPackets1_21_2;
import lombok.NonNull;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import org.jetbrains.annotations.NotNull;
import java.util.BitSet;
import java.util.UUID;
/**
* TabList handler using ViaVersion packets,
* to unlock +1.21.4 player features on pre 1.21.4 servers.
*/
public class ViaTabList1214 extends ViaTabList1212 {
private static final BitSet ADD_PLAYER = bitSet(8, 0, 8);
//private static final BitSet INITIALIZE_CHAT = bitSet(8, 1);
private static final BitSet UPDATE_GAME_MODE = bitSet(8, 2);
private static final BitSet UPDATE_LISTED = bitSet(8, 3);
private static final BitSet UPDATE_LATENCY = bitSet(8, 4);
private static final BitSet UPDATE_DISPLAY_NAME = bitSet(8, 5);
private static final BitSet UPDATE_LIST_ORDER = bitSet(8, 6);
private static final BitSet UPDATE_HAT = bitSet(8, 7);
/**
* Constructs new instance with given parameters.
*
* @param player
* Player this tablist will belong to
*/
public ViaTabList1214(@NotNull BukkitTabPlayer player) {
super(player, Protocol1_21_2To1_21_4.class, ClientboundPackets1_21_2.PLAYER_INFO_REMOVE, ClientboundPackets1_21_2.PLAYER_INFO_UPDATE, ClientboundPackets1_21_2.TAB_LIST);
}
@Override
public void updateHat(@NonNull UUID entry, boolean showHat) {
sendInfoUpdate(Action.UPDATE_HAT, entry, showHat);
}
@Override
protected void writeAction(@NonNull PacketWrapper packet, @NonNull Action action) {
switch (action) {
case ADD_PLAYER:
packet.write(Types.PROFILE_ACTIONS_ENUM1_21_4, ADD_PLAYER);
break;
case UPDATE_DISPLAY_NAME:
packet.write(Types.PROFILE_ACTIONS_ENUM1_21_4, UPDATE_DISPLAY_NAME);
break;
case UPDATE_LATENCY:
packet.write(Types.PROFILE_ACTIONS_ENUM1_21_4, UPDATE_LATENCY);
break;
case UPDATE_GAME_MODE:
packet.write(Types.PROFILE_ACTIONS_ENUM1_21_4, UPDATE_GAME_MODE);
break;
case UPDATE_LISTED:
packet.write(Types.PROFILE_ACTIONS_ENUM1_21_4, UPDATE_LISTED);
break;
case UPDATE_LIST_ORDER:
packet.write(Types.PROFILE_ACTIONS_ENUM1_21_4, UPDATE_LIST_ORDER);
break;
case UPDATE_HAT:
packet.write(Types.PROFILE_ACTIONS_ENUM1_21_4, UPDATE_HAT);
break;
default:
throw new IllegalStateException("Cannot write " + action.name() + " for 1.21.4 packet");
}
}
@Override
protected void writeEntry(@NonNull PacketWrapper packet, @NonNull Entry entry) {
super.writeEntry(packet, entry);
packet.write(Types.BOOLEAN, entry.isShowHat());
}
}
@@ -1,65 +0,0 @@
package me.neznamy.tab.platforms.bukkit.provider.viaversion;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.platform.TabList;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.function.Function;
/**
* Implementation for sending new features on old servers using ViaVersion API.
* If no implementation is required for a player, {@code null} is returned.
*/
@RequiredArgsConstructor
public class ViaVersionProvider {
/** Map of tablist implementations by each new version */
private static final Map<ProtocolVersion, Function<BukkitTabPlayer, TabList>> TABLIST_IMPLEMENTATIONS = new LinkedHashMap<>();
/** Map of scoreboard implementations by each new version */
private static final Map<ProtocolVersion, Function<BukkitTabPlayer, Scoreboard>> SCOREBOARD_IMPLEMENTATIONS = new LinkedHashMap<>();
static {
TABLIST_IMPLEMENTATIONS.put(ProtocolVersion.V1_21_4, ViaTabList1214::new);
TABLIST_IMPLEMENTATIONS.put(ProtocolVersion.V1_21_2, ViaTabList1212::new);
TABLIST_IMPLEMENTATIONS.put(ProtocolVersion.V1_19_3, ViaTabList1193::new);
TABLIST_IMPLEMENTATIONS.put(ProtocolVersion.V1_16, ViaTabList116::new);
SCOREBOARD_IMPLEMENTATIONS.put(ProtocolVersion.V1_20_3, ViaScoreboard1203::new);
SCOREBOARD_IMPLEMENTATIONS.put(ProtocolVersion.V1_16, ViaScoreboard116::new);
SCOREBOARD_IMPLEMENTATIONS.put(ProtocolVersion.V1_13, ViaScoreboard113::new);
}
@NotNull
private final ProtocolVersion serverVersion;
@Nullable
public Scoreboard newScoreboard(@NotNull BukkitTabPlayer player) {
int serverVer = serverVersion.getNetworkId();
int playerVer = player.getVersion().getNetworkId();
for (Map.Entry<ProtocolVersion, Function<BukkitTabPlayer, Scoreboard>> entry : SCOREBOARD_IMPLEMENTATIONS.entrySet()) {
if (serverVer < entry.getKey().getNetworkId() && playerVer >= entry.getKey().getNetworkId()) {
return entry.getValue().apply(player);
}
}
return null;
}
@Nullable
public TabList newTabList(@NotNull BukkitTabPlayer player) {
int serverVer = serverVersion.getNetworkId();
int playerVer = player.getVersion().getNetworkId();
for (Map.Entry<ProtocolVersion, Function<BukkitTabPlayer, TabList>> entry : TABLIST_IMPLEMENTATIONS.entrySet()) {
if (serverVer < entry.getKey().getNetworkId() && playerVer >= entry.getKey().getNetworkId()) {
return entry.getValue().apply(player);
}
}
return null;
}
}
@@ -2,12 +2,9 @@ package me.neznamy.tab.platforms.bukkit.v1_12_R1;
import io.netty.channel.Channel;
import lombok.Getter;
import lombok.NonNull;
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.platforms.bukkit.provider.viaversion.ViaScoreboard;
import me.neznamy.tab.platforms.bukkit.provider.viaversion.ViaTabList;
import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.util.function.FunctionWithException;
@@ -29,16 +26,6 @@ public class NMSImplementationProvider implements ImplementationProvider {
return new NMSPacketScoreboard(player);
}
@Override
public void onPacketSend(@NonNull Object packet, @NonNull ViaScoreboard scoreboard) {
NMSPacketScoreboard.onPacketSend(packet, scoreboard);
}
@Override
public void onPacketSend(@NonNull Object packet, @NonNull ViaTabList tabList) {
NMSPacketTabList.onPacketSend(packet, tabList);
}
@Override
@NotNull
public TabList newTabList(@NotNull BukkitTabPlayer player) {
@@ -34,28 +34,6 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
private static final Field DisplayObjective_OBJECTIVE_NAME = ReflectionUtils.getOnlyField(PacketPlayOutScoreboardDisplayObjective.class, String.class);
private static final Field DisplayObjective_POSITION = ReflectionUtils.getOnlyField(PacketPlayOutScoreboardDisplayObjective.class, int.class);
@SneakyThrows
@SuppressWarnings("unchecked")
public static void onPacketSend(@NonNull Object packet, @NonNull SafeScoreboard<BukkitTabPlayer> scoreboard) {
if (scoreboard.isAntiOverrideScoreboard()) {
if (packet instanceof PacketPlayOutScoreboardDisplayObjective) {
TAB.getInstance().getFeatureManager().onDisplayObjective(scoreboard.getPlayer(), DisplayObjective_POSITION.getInt(packet),
(String) DisplayObjective_OBJECTIVE_NAME.get(packet));
}
if (packet instanceof PacketPlayOutScoreboardObjective) {
TAB.getInstance().getFeatureManager().onObjective(scoreboard.getPlayer(),
Objective_METHOD.getInt(packet), (String) Objective_OBJECTIVE_NAME.get(packet));
}
}
if (scoreboard.isAntiOverrideTeams() && packet instanceof PacketPlayOutScoreboardTeam) {
int action = TeamPacket_ACTION.getInt(packet);
if (action == TeamAction.UPDATE) return;
Collection<String> players = (Collection<String>) TeamPacket_PLAYERS.get(packet);
if (players == null) players = Collections.emptyList();
TeamPacket_PLAYERS.set(packet, scoreboard.onTeamPacket(action, (String) TeamPacket_NAME.get(packet), players));
}
}
/**
* Constructs new instance with given player.
*
@@ -69,7 +47,7 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
@Override
public void registerObjective(@NonNull Objective objective) {
ScoreboardObjective obj = new ScoreboardObjective(dummyScoreboard, objective.getName(), IScoreboardCriteria.b);
obj.setDisplayName(cutTo(objective.getTitle().toLegacyText(), Limitations.SCOREBOARD_TITLE_PRE_1_13));
obj.setDisplayName(maybeCut(objective.getTitle().toLegacyText(), Limitations.SCOREBOARD_TITLE_PRE_1_13));
obj.a(IScoreboardCriteria.EnumScoreboardHealthDisplay.valueOf(objective.getHealthDisplay().name()));
objective.setPlatformObjective(obj);
sendPacket(new PacketPlayOutScoreboardObjective(obj, ObjectiveAction.REGISTER));
@@ -88,7 +66,7 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
@Override
public void updateObjective(@NonNull Objective objective) {
ScoreboardObjective obj = (ScoreboardObjective) objective.getPlatformObjective();
obj.setDisplayName(cutTo(objective.getTitle().toLegacyText(), Limitations.SCOREBOARD_TITLE_PRE_1_13));
obj.setDisplayName(maybeCut(objective.getTitle().toLegacyText(), Limitations.SCOREBOARD_TITLE_PRE_1_13));
obj.a(IScoreboardCriteria.EnumScoreboardHealthDisplay.valueOf(objective.getHealthDisplay().name()));
sendPacket(new PacketPlayOutScoreboardObjective(obj, ObjectiveAction.UPDATE));
}
@@ -136,13 +114,29 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
t.setCanSeeFriendlyInvisibles((team.getOptions() & 0x02) != 0);
t.setNameTagVisibility(visibilities[team.getVisibility().ordinal()]);
t.setCollisionRule(collisions[team.getCollision().ordinal()]);
t.setPrefix(cutTo(team.getPrefix().toLegacyText(), Limitations.TEAM_PREFIX_SUFFIX_PRE_1_13));
t.setSuffix(cutTo(team.getSuffix().toLegacyText(), Limitations.TEAM_PREFIX_SUFFIX_PRE_1_13));
t.setPrefix(maybeCut(team.getPrefix().toLegacyText(), Limitations.TEAM_PREFIX_SUFFIX_PRE_1_13));
t.setSuffix(maybeCut(team.getSuffix().toLegacyText(), Limitations.TEAM_PREFIX_SUFFIX_PRE_1_13));
}
@Override
@SneakyThrows
@SuppressWarnings("unchecked")
public void onPacketSend(@NonNull Object packet) {
onPacketSend(packet, this);
if (packet instanceof PacketPlayOutScoreboardDisplayObjective) {
TAB.getInstance().getFeatureManager().onDisplayObjective(player, DisplayObjective_POSITION.getInt(packet),
(String) DisplayObjective_OBJECTIVE_NAME.get(packet));
}
if (packet instanceof PacketPlayOutScoreboardObjective) {
TAB.getInstance().getFeatureManager().onObjective(player,
Objective_METHOD.getInt(packet), (String) Objective_OBJECTIVE_NAME.get(packet));
}
if (packet instanceof PacketPlayOutScoreboardTeam) {
int action = TeamPacket_ACTION.getInt(packet);
if (action == TeamAction.UPDATE) return;
Collection<String> players = (Collection<String>) TeamPacket_PLAYERS.get(packet);
if (players == null) players = Collections.emptyList();
TeamPacket_PLAYERS.set(packet, onTeamPacket(action, (String) TeamPacket_NAME.get(packet), players));
}
}
/**
@@ -154,4 +148,12 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
private void sendPacket(@NotNull Packet<?> packet) {
((CraftPlayer)player.getPlayer()).getHandle().playerConnection.sendPacket(packet);
}
@NotNull
private String maybeCut(@NonNull String string, int length) {
if (player.getVersion().getMinorVersion() < 13 || TAB.getInstance().getConfiguration().getConfig().isPacketEventsCompensation()) {
return cutTo(string, length);
}
return string;
}
}
@@ -42,31 +42,6 @@ public class NMSPacketTabList extends TrackedTabList<BukkitTabPlayer> {
private static final Field HEADER = ReflectionUtils.getFields(PacketPlayOutPlayerListHeaderFooter.class, IChatBaseComponent.class).get(0);
private static final Field FOOTER = ReflectionUtils.getFields(PacketPlayOutPlayerListHeaderFooter.class, IChatBaseComponent.class).get(1);
@SneakyThrows
@SuppressWarnings("unchecked")
public static void onPacketSend(@NonNull Object packet, @NonNull TrackedTabList<BukkitTabPlayer> tabList) {
if (!(packet instanceof PacketPlayOutPlayerInfo)) return;
EnumPlayerInfoAction action = (EnumPlayerInfoAction) ACTION.get(packet);
for (Object nmsData : (List<Object>) PLAYERS.get(packet)) {
GameProfile profile = (GameProfile) PlayerInfoData_Profile.get(nmsData);
UUID id = profile.getId();
if (action == EnumPlayerInfoAction.UPDATE_DISPLAY_NAME || action == EnumPlayerInfoAction.ADD_PLAYER) {
TabComponent expectedName = tabList.getExpectedDisplayNames().get(id);
if (expectedName != null) PlayerInfoData_DisplayName.set(nmsData, expectedName.convert());
}
if (action == EnumPlayerInfoAction.UPDATE_LATENCY || action == EnumPlayerInfoAction.ADD_PLAYER) {
int oldLatency = PlayerInfoData_Latency.getInt(nmsData);
int newLatency = TAB.getInstance().getFeatureManager().onLatencyChange(tabList.getPlayer(), id, oldLatency);
if (oldLatency != newLatency) {
PlayerInfoData_Latency.set(nmsData, newLatency);
}
}
if (action == EnumPlayerInfoAction.ADD_PLAYER) {
TAB.getInstance().getFeatureManager().onEntryAdd(tabList.getPlayer(), id, profile.getName());
}
}
}
/**
* Constructs new instance.
*
@@ -143,8 +118,29 @@ public class NMSPacketTabList extends TrackedTabList<BukkitTabPlayer> {
}
@Override
@SneakyThrows
@SuppressWarnings("unchecked")
public void onPacketSend(@NonNull Object packet) {
onPacketSend(packet, this);
if (!(packet instanceof PacketPlayOutPlayerInfo)) return;
EnumPlayerInfoAction action = (EnumPlayerInfoAction) ACTION.get(packet);
for (Object nmsData : (List<Object>) PLAYERS.get(packet)) {
GameProfile profile = (GameProfile) PlayerInfoData_Profile.get(nmsData);
UUID id = profile.getId();
if (action == EnumPlayerInfoAction.UPDATE_DISPLAY_NAME || action == EnumPlayerInfoAction.ADD_PLAYER) {
TabComponent expectedName = getExpectedDisplayNames().get(id);
if (expectedName != null) PlayerInfoData_DisplayName.set(nmsData, expectedName.convert());
}
if (action == EnumPlayerInfoAction.UPDATE_LATENCY || action == EnumPlayerInfoAction.ADD_PLAYER) {
int oldLatency = PlayerInfoData_Latency.getInt(nmsData);
int newLatency = TAB.getInstance().getFeatureManager().onLatencyChange(player, id, oldLatency);
if (oldLatency != newLatency) {
PlayerInfoData_Latency.set(nmsData, newLatency);
}
}
if (action == EnumPlayerInfoAction.ADD_PLAYER) {
TAB.getInstance().getFeatureManager().onEntryAdd(player, id, profile.getName());
}
}
}
@SneakyThrows
@@ -2,12 +2,9 @@ package me.neznamy.tab.platforms.bukkit.v1_16_R3;
import io.netty.channel.Channel;
import lombok.Getter;
import lombok.NonNull;
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.platforms.bukkit.provider.viaversion.ViaScoreboard;
import me.neznamy.tab.platforms.bukkit.provider.viaversion.ViaTabList;
import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.util.function.FunctionWithException;
@@ -29,16 +26,6 @@ public class NMSImplementationProvider implements ImplementationProvider {
return new NMSPacketScoreboard(player);
}
@Override
public void onPacketSend(@NonNull Object packet, @NonNull ViaScoreboard scoreboard) {
NMSPacketScoreboard.onPacketSend(packet, scoreboard);
}
@Override
public void onPacketSend(@NonNull Object packet, @NonNull ViaTabList tabList) {
NMSPacketTabList.onPacketSend(packet, tabList);
}
@Override
@NotNull
public TabList newTabList(@NotNull BukkitTabPlayer player) {
@@ -33,28 +33,6 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
private static final Field DisplayObjective_OBJECTIVE_NAME = ReflectionUtils.getOnlyField(PacketPlayOutScoreboardDisplayObjective.class, String.class);
private static final Field DisplayObjective_POSITION = ReflectionUtils.getOnlyField(PacketPlayOutScoreboardDisplayObjective.class, int.class);
@SneakyThrows
@SuppressWarnings("unchecked")
public static void onPacketSend(@NonNull Object packet, @NonNull SafeScoreboard<BukkitTabPlayer> scoreboard) {
if (scoreboard.isAntiOverrideScoreboard()) {
if (packet instanceof PacketPlayOutScoreboardDisplayObjective) {
TAB.getInstance().getFeatureManager().onDisplayObjective(scoreboard.getPlayer(), DisplayObjective_POSITION.getInt(packet),
(String) DisplayObjective_OBJECTIVE_NAME.get(packet));
}
if (packet instanceof PacketPlayOutScoreboardObjective) {
TAB.getInstance().getFeatureManager().onObjective(scoreboard.getPlayer(),
Objective_METHOD.getInt(packet), (String) Objective_OBJECTIVE_NAME.get(packet));
}
}
if (scoreboard.isAntiOverrideTeams() && packet instanceof PacketPlayOutScoreboardTeam) {
int action = TeamPacket_ACTION.getInt(packet);
if (action == TeamAction.UPDATE) return;
Collection<String> players = (Collection<String>) TeamPacket_PLAYERS.get(packet);
if (players == null) players = Collections.emptyList();
TeamPacket_PLAYERS.set(packet, scoreboard.onTeamPacket(action, (String) TeamPacket_NAME.get(packet), players));
}
}
/**
* Constructs new instance with given player.
*
@@ -156,8 +134,24 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
}
@Override
@SneakyThrows
@SuppressWarnings("unchecked")
public void onPacketSend(@NonNull Object packet) {
onPacketSend(packet, this);
if (packet instanceof PacketPlayOutScoreboardDisplayObjective) {
TAB.getInstance().getFeatureManager().onDisplayObjective(player, DisplayObjective_POSITION.getInt(packet),
(String) DisplayObjective_OBJECTIVE_NAME.get(packet));
}
if (packet instanceof PacketPlayOutScoreboardObjective) {
TAB.getInstance().getFeatureManager().onObjective(player,
Objective_METHOD.getInt(packet), (String) Objective_OBJECTIVE_NAME.get(packet));
}
if (packet instanceof PacketPlayOutScoreboardTeam) {
int action = TeamPacket_ACTION.getInt(packet);
if (action == TeamAction.UPDATE) return;
Collection<String> players = (Collection<String>) TeamPacket_PLAYERS.get(packet);
if (players == null) players = Collections.emptyList();
TeamPacket_PLAYERS.set(packet, onTeamPacket(action, (String) TeamPacket_NAME.get(packet), players));
}
}
/**
@@ -42,31 +42,6 @@ public class NMSPacketTabList extends TrackedTabList<BukkitTabPlayer> {
private static final Field HEADER = ReflectionUtils.getFields(PacketPlayOutPlayerListHeaderFooter.class, IChatBaseComponent.class).get(0);
private static final Field FOOTER = ReflectionUtils.getFields(PacketPlayOutPlayerListHeaderFooter.class, IChatBaseComponent.class).get(1);
@SneakyThrows
@SuppressWarnings("unchecked")
public static void onPacketSend(@NonNull Object packet, @NonNull TrackedTabList<BukkitTabPlayer> tabList) {
if (!(packet instanceof PacketPlayOutPlayerInfo)) return;
EnumPlayerInfoAction action = (EnumPlayerInfoAction) ACTION.get(packet);
for (Object nmsData : (List<Object>) PLAYERS.get(packet)) {
GameProfile profile = (GameProfile) PlayerInfoData_Profile.get(nmsData);
UUID id = profile.getId();
if (action == EnumPlayerInfoAction.UPDATE_DISPLAY_NAME || action == EnumPlayerInfoAction.ADD_PLAYER) {
TabComponent expectedName = tabList.getExpectedDisplayNames().get(id);
if (expectedName != null) PlayerInfoData_DisplayName.set(nmsData, expectedName.convert());
}
if (action == EnumPlayerInfoAction.UPDATE_LATENCY || action == EnumPlayerInfoAction.ADD_PLAYER) {
int oldLatency = PlayerInfoData_Latency.getInt(nmsData);
int newLatency = TAB.getInstance().getFeatureManager().onLatencyChange(tabList.getPlayer(), id, oldLatency);
if (oldLatency != newLatency) {
PlayerInfoData_Latency.set(nmsData, newLatency);
}
}
if (action == EnumPlayerInfoAction.ADD_PLAYER) {
TAB.getInstance().getFeatureManager().onEntryAdd(tabList.getPlayer(), id, profile.getName());
}
}
}
/**
* Constructs new instance.
*
@@ -143,8 +118,29 @@ public class NMSPacketTabList extends TrackedTabList<BukkitTabPlayer> {
}
@Override
@SneakyThrows
@SuppressWarnings("unchecked")
public void onPacketSend(@NonNull Object packet) {
onPacketSend(packet, this);
if (!(packet instanceof PacketPlayOutPlayerInfo)) return;
EnumPlayerInfoAction action = (EnumPlayerInfoAction) ACTION.get(packet);
for (Object nmsData : (List<Object>) PLAYERS.get(packet)) {
GameProfile profile = (GameProfile) PlayerInfoData_Profile.get(nmsData);
UUID id = profile.getId();
if (action == EnumPlayerInfoAction.UPDATE_DISPLAY_NAME || action == EnumPlayerInfoAction.ADD_PLAYER) {
TabComponent expectedName = getExpectedDisplayNames().get(id);
if (expectedName != null) PlayerInfoData_DisplayName.set(nmsData, expectedName.convert());
}
if (action == EnumPlayerInfoAction.UPDATE_LATENCY || action == EnumPlayerInfoAction.ADD_PLAYER) {
int oldLatency = PlayerInfoData_Latency.getInt(nmsData);
int newLatency = TAB.getInstance().getFeatureManager().onLatencyChange(player, id, oldLatency);
if (oldLatency != newLatency) {
PlayerInfoData_Latency.set(nmsData, newLatency);
}
}
if (action == EnumPlayerInfoAction.ADD_PLAYER) {
TAB.getInstance().getFeatureManager().onEntryAdd(player, id, profile.getName());
}
}
}
@SneakyThrows
@@ -2,12 +2,9 @@ package me.neznamy.tab.platforms.bukkit.v1_17_R1;
import io.netty.channel.Channel;
import lombok.Getter;
import lombok.NonNull;
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.platforms.bukkit.provider.viaversion.ViaScoreboard;
import me.neznamy.tab.platforms.bukkit.provider.viaversion.ViaTabList;
import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.util.function.FunctionWithException;
@@ -29,16 +26,6 @@ public class NMSImplementationProvider implements ImplementationProvider {
return new NMSPacketScoreboard(player);
}
@Override
public void onPacketSend(@NonNull Object packet, @NonNull ViaScoreboard scoreboard) {
NMSPacketScoreboard.onPacketSend(packet, scoreboard);
}
@Override
public void onPacketSend(@NonNull Object packet, @NonNull ViaTabList tabList) {
NMSPacketTabList.onPacketSend(packet, tabList);
}
@Override
@NotNull
public TabList newTabList(@NotNull BukkitTabPlayer player) {
@@ -36,48 +36,6 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
private static final Field TeamPacket_PLAYERS = ReflectionUtils.getOnlyField(PacketPlayOutScoreboardTeam.class, Collection.class);
@SneakyThrows
@SuppressWarnings("unchecked")
public static void onPacketSend(@NonNull Object packet, @NonNull SafeScoreboard<BukkitTabPlayer> scoreboard) {
if (scoreboard.isAntiOverrideScoreboard()) {
if (packet instanceof PacketPlayOutScoreboardDisplayObjective) {
TAB.getInstance().getFeatureManager().onDisplayObjective(
scoreboard.getPlayer(),
((PacketPlayOutScoreboardDisplayObjective)packet).b(),
((PacketPlayOutScoreboardDisplayObjective)packet).c()
);
}
if (packet instanceof PacketPlayOutScoreboardObjective) {
TAB.getInstance().getFeatureManager().onObjective(
scoreboard.getPlayer(),
((PacketPlayOutScoreboardObjective)packet).d(),
((PacketPlayOutScoreboardObjective)packet).b()
);
}
}
if (scoreboard.isAntiOverrideTeams() && packet instanceof PacketPlayOutScoreboardTeam) {
int action = getMethod((PacketPlayOutScoreboardTeam) packet);
if (action == TeamAction.UPDATE) return;
Collection<String> players = (Collection<String>) TeamPacket_PLAYERS.get(packet);
if (players == null) players = Collections.emptyList();
TeamPacket_PLAYERS.set(packet, scoreboard.onTeamPacket(action, ((PacketPlayOutScoreboardTeam)packet).d(), players));
}
}
private static 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;
}
}
/**
* Constructs new instance with given player.
*
@@ -179,8 +137,44 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
}
@Override
@SneakyThrows
@SuppressWarnings("unchecked")
public void onPacketSend(@NonNull Object packet) {
onPacketSend(packet, this);
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) return;
Collection<String> players = (Collection<String>) TeamPacket_PLAYERS.get(packet);
if (players == null) players = Collections.emptyList();
TeamPacket_PLAYERS.set(packet, onTeamPacket(action, ((PacketPlayOutScoreboardTeam)packet).d(), players));
}
}
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;
}
}
/**
@@ -61,6 +61,14 @@ public class NMSPacketTabList extends TrackedTabList<BukkitTabPlayer> {
}
}
@Nullable
public static Skin getSkin(@NonNull BukkitTabPlayer player) {
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());
}
/**
* Constructs new instance.
*
@@ -127,10 +135,7 @@ public class NMSPacketTabList extends TrackedTabList<BukkitTabPlayer> {
@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());
return getSkin(player);
}
@Override
@@ -2,12 +2,9 @@ package me.neznamy.tab.platforms.bukkit.v1_18_R2;
import io.netty.channel.Channel;
import lombok.Getter;
import lombok.NonNull;
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.platforms.bukkit.provider.viaversion.ViaScoreboard;
import me.neznamy.tab.platforms.bukkit.provider.viaversion.ViaTabList;
import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.util.function.FunctionWithException;
@@ -29,16 +26,6 @@ public class NMSImplementationProvider implements ImplementationProvider {
return new NMSPacketScoreboard(player);
}
@Override
public void onPacketSend(@NonNull Object packet, @NonNull ViaScoreboard scoreboard) {
NMSPacketScoreboard.onPacketSend(packet, scoreboard);
}
@Override
public void onPacketSend(@NonNull Object packet, @NonNull ViaTabList tabList) {
NMSPacketTabList.onPacketSend(packet, tabList);
}
@Override
@NotNull
public TabList newTabList(@NotNull BukkitTabPlayer player) {
@@ -37,48 +37,6 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
private static final Field TeamPacket_PLAYERS = ReflectionUtils.getOnlyField(PacketPlayOutScoreboardTeam.class, Collection.class);
@SneakyThrows
@SuppressWarnings("unchecked")
public static void onPacketSend(@NonNull Object packet, @NonNull SafeScoreboard<BukkitTabPlayer> scoreboard) {
if (scoreboard.isAntiOverrideScoreboard()) {
if (packet instanceof PacketPlayOutScoreboardDisplayObjective) {
TAB.getInstance().getFeatureManager().onDisplayObjective(
scoreboard.getPlayer(),
((PacketPlayOutScoreboardDisplayObjective)packet).b(),
((PacketPlayOutScoreboardDisplayObjective)packet).c()
);
}
if (packet instanceof PacketPlayOutScoreboardObjective) {
TAB.getInstance().getFeatureManager().onObjective(
scoreboard.getPlayer(),
((PacketPlayOutScoreboardObjective)packet).d(),
((PacketPlayOutScoreboardObjective)packet).b()
);
}
}
if (scoreboard.isAntiOverrideTeams() && packet instanceof PacketPlayOutScoreboardTeam) {
int action = getMethod((PacketPlayOutScoreboardTeam) packet);
if (action == TeamAction.UPDATE) return;
Collection<String> players = (Collection<String>) TeamPacket_PLAYERS.get(packet);
if (players == null) players = Collections.emptyList();
TeamPacket_PLAYERS.set(packet, scoreboard.onTeamPacket(action, ((PacketPlayOutScoreboardTeam)packet).d(), players));
}
}
private static 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;
}
}
/**
* Constructs new instance with given player.
*
@@ -180,8 +138,44 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
}
@Override
@SneakyThrows
@SuppressWarnings("unchecked")
public void onPacketSend(@NonNull Object packet) {
onPacketSend(packet, this);
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) return;
Collection<String> players = (Collection<String>) TeamPacket_PLAYERS.get(packet);
if (players == null) players = Collections.emptyList();
TeamPacket_PLAYERS.set(packet, onTeamPacket(action, ((PacketPlayOutScoreboardTeam)packet).d(), players));
}
}
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;
}
}
/**
@@ -37,30 +37,6 @@ public class NMSPacketTabList extends TrackedTabList<BukkitTabPlayer> {
private static final Field PlayerInfoData_Latency = ReflectionUtils.getFields(PlayerInfoData.class, int.class).get(0);
private static final Field PlayerInfoData_DisplayName = ReflectionUtils.getOnlyField(PlayerInfoData.class, IChatBaseComponent.class);
@SneakyThrows
public static void onPacketSend(@NonNull Object packet, @NonNull TrackedTabList<BukkitTabPlayer> tabList) {
if (!(packet instanceof PacketPlayOutPlayerInfo)) return;
EnumPlayerInfoAction action = ((PacketPlayOutPlayerInfo)packet).c();
for (PlayerInfoData nmsData : ((PacketPlayOutPlayerInfo)packet).b()) {
GameProfile profile = nmsData.a();
UUID id = profile.getId();
if (action == EnumPlayerInfoAction.d || action == EnumPlayerInfoAction.a) {
TabComponent expectedName = tabList.getExpectedDisplayNames().get(id);
if (expectedName != null) PlayerInfoData_DisplayName.set(nmsData, expectedName.convert());
}
if (action == EnumPlayerInfoAction.c || action == EnumPlayerInfoAction.a) {
int oldLatency = nmsData.b();
int newLatency = TAB.getInstance().getFeatureManager().onLatencyChange(tabList.getPlayer(), id, oldLatency);
if (oldLatency != newLatency) {
PlayerInfoData_Latency.set(nmsData, newLatency);
}
}
if (action == EnumPlayerInfoAction.a) {
TAB.getInstance().getFeatureManager().onEntryAdd(tabList.getPlayer(), id, profile.getName());
}
}
}
/**
* Constructs new instance.
*
@@ -134,8 +110,28 @@ public class NMSPacketTabList extends TrackedTabList<BukkitTabPlayer> {
}
@Override
@SneakyThrows
public void onPacketSend(@NonNull Object packet) {
onPacketSend(packet, this);
if (!(packet instanceof PacketPlayOutPlayerInfo)) return;
EnumPlayerInfoAction action = ((PacketPlayOutPlayerInfo)packet).c();
for (PlayerInfoData nmsData : ((PacketPlayOutPlayerInfo)packet).b()) {
GameProfile profile = nmsData.a();
UUID id = profile.getId();
if (action == EnumPlayerInfoAction.d || action == EnumPlayerInfoAction.a) {
TabComponent expectedName = getExpectedDisplayNames().get(id);
if (expectedName != null) PlayerInfoData_DisplayName.set(nmsData, expectedName.convert());
}
if (action == EnumPlayerInfoAction.c || action == EnumPlayerInfoAction.a) {
int oldLatency = nmsData.b();
int newLatency = TAB.getInstance().getFeatureManager().onLatencyChange(player, id, oldLatency);
if (oldLatency != newLatency) {
PlayerInfoData_Latency.set(nmsData, newLatency);
}
}
if (action == EnumPlayerInfoAction.a) {
TAB.getInstance().getFeatureManager().onEntryAdd(player, id, profile.getName());
}
}
}
@SneakyThrows
@@ -2,12 +2,9 @@ package me.neznamy.tab.platforms.bukkit.v1_19_R1;
import io.netty.channel.Channel;
import lombok.Getter;
import lombok.NonNull;
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.platforms.bukkit.provider.viaversion.ViaScoreboard;
import me.neznamy.tab.platforms.bukkit.provider.viaversion.ViaTabList;
import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.util.function.FunctionWithException;
@@ -15,7 +12,7 @@ import org.bukkit.craftbukkit.v1_19_R1.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
/**
* Implementation provider using direct NMS code for 1.19 - 1.19.2.
* Implementation provider using direct NMS code for 1.19.1 - 1.19.2.
*/
@Getter
public class NMSImplementationProvider implements ImplementationProvider {
@@ -29,16 +26,6 @@ public class NMSImplementationProvider implements ImplementationProvider {
return new NMSPacketScoreboard(player);
}
@Override
public void onPacketSend(@NonNull Object packet, @NonNull ViaScoreboard scoreboard) {
NMSPacketScoreboard.onPacketSend(packet, scoreboard);
}
@Override
public void onPacketSend(@NonNull Object packet, @NonNull ViaTabList tabList) {
NMSPacketTabList.onPacketSend(packet, tabList);
}
@Override
@NotNull
public TabList newTabList(@NotNull BukkitTabPlayer player) {
@@ -37,48 +37,6 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
private static final Field TeamPacket_PLAYERS = ReflectionUtils.getOnlyField(PacketPlayOutScoreboardTeam.class, Collection.class);
@SneakyThrows
@SuppressWarnings("unchecked")
public static void onPacketSend(@NonNull Object packet, @NonNull SafeScoreboard<BukkitTabPlayer> scoreboard) {
if (scoreboard.isAntiOverrideScoreboard()) {
if (packet instanceof PacketPlayOutScoreboardDisplayObjective) {
TAB.getInstance().getFeatureManager().onDisplayObjective(
scoreboard.getPlayer(),
((PacketPlayOutScoreboardDisplayObjective)packet).b(),
((PacketPlayOutScoreboardDisplayObjective)packet).c()
);
}
if (packet instanceof PacketPlayOutScoreboardObjective) {
TAB.getInstance().getFeatureManager().onObjective(
scoreboard.getPlayer(),
((PacketPlayOutScoreboardObjective)packet).d(),
((PacketPlayOutScoreboardObjective)packet).b()
);
}
}
if (scoreboard.isAntiOverrideTeams() && packet instanceof PacketPlayOutScoreboardTeam) {
int action = getMethod((PacketPlayOutScoreboardTeam) packet);
if (action == TeamAction.UPDATE) return;
Collection<String> players = (Collection<String>) TeamPacket_PLAYERS.get(packet);
if (players == null) players = Collections.emptyList();
TeamPacket_PLAYERS.set(packet, scoreboard.onTeamPacket(action, ((PacketPlayOutScoreboardTeam)packet).d(), players));
}
}
private static 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;
}
}
/**
* Constructs new instance with given player.
*
@@ -180,8 +138,44 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
}
@Override
@SneakyThrows
@SuppressWarnings("unchecked")
public void onPacketSend(@NonNull Object packet) {
onPacketSend(packet, this);
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) return;
Collection<String> players = (Collection<String>) TeamPacket_PLAYERS.get(packet);
if (players == null) players = Collections.emptyList();
TeamPacket_PLAYERS.set(packet, onTeamPacket(action, ((PacketPlayOutScoreboardTeam)packet).d(), players));
}
}
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;
}
}
/**
@@ -37,30 +37,6 @@ public class NMSPacketTabList extends TrackedTabList<BukkitTabPlayer> {
private static final Field PlayerInfoData_Latency = ReflectionUtils.getFields(PlayerInfoData.class, int.class).get(0);
private static final Field PlayerInfoData_DisplayName = ReflectionUtils.getOnlyField(PlayerInfoData.class, IChatBaseComponent.class);
@SneakyThrows
public static void onPacketSend(@NonNull Object packet, @NonNull TrackedTabList<BukkitTabPlayer> tabList) {
if (!(packet instanceof PacketPlayOutPlayerInfo)) return;
EnumPlayerInfoAction action = ((PacketPlayOutPlayerInfo)packet).c();
for (PlayerInfoData nmsData : ((PacketPlayOutPlayerInfo)packet).b()) {
GameProfile profile = nmsData.a();
UUID id = profile.getId();
if (action == EnumPlayerInfoAction.d || action == EnumPlayerInfoAction.a) {
TabComponent expectedName = tabList.getExpectedDisplayNames().get(id);
if (expectedName != null) PlayerInfoData_DisplayName.set(nmsData, expectedName.convert());
}
if (action == EnumPlayerInfoAction.c || action == EnumPlayerInfoAction.a) {
int oldLatency = nmsData.b();
int newLatency = TAB.getInstance().getFeatureManager().onLatencyChange(tabList.getPlayer(), id, oldLatency);
if (oldLatency != newLatency) {
PlayerInfoData_Latency.set(nmsData, newLatency);
}
}
if (action == EnumPlayerInfoAction.a) {
TAB.getInstance().getFeatureManager().onEntryAdd(tabList.getPlayer(), id, profile.getName());
}
}
}
/**
* Constructs new instance.
*
@@ -134,8 +110,28 @@ public class NMSPacketTabList extends TrackedTabList<BukkitTabPlayer> {
}
@Override
@SneakyThrows
public void onPacketSend(@NonNull Object packet) {
onPacketSend(packet, this);
if (!(packet instanceof PacketPlayOutPlayerInfo)) return;
EnumPlayerInfoAction action = ((PacketPlayOutPlayerInfo)packet).c();
for (PlayerInfoData nmsData : ((PacketPlayOutPlayerInfo)packet).b()) {
GameProfile profile = nmsData.a();
UUID id = profile.getId();
if (action == EnumPlayerInfoAction.d || action == EnumPlayerInfoAction.a) {
TabComponent expectedName = getExpectedDisplayNames().get(id);
if (expectedName != null) PlayerInfoData_DisplayName.set(nmsData, expectedName.convert());
}
if (action == EnumPlayerInfoAction.c || action == EnumPlayerInfoAction.a) {
int oldLatency = nmsData.b();
int newLatency = TAB.getInstance().getFeatureManager().onLatencyChange(player, id, oldLatency);
if (oldLatency != newLatency) {
PlayerInfoData_Latency.set(nmsData, newLatency);
}
}
if (action == EnumPlayerInfoAction.a) {
TAB.getInstance().getFeatureManager().onEntryAdd(player, id, profile.getName());
}
}
}
@SneakyThrows
@@ -2,12 +2,9 @@ package me.neznamy.tab.platforms.bukkit.v1_8_R3;
import io.netty.channel.Channel;
import lombok.Getter;
import lombok.NonNull;
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.platforms.bukkit.provider.viaversion.ViaScoreboard;
import me.neznamy.tab.platforms.bukkit.provider.viaversion.ViaTabList;
import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.util.function.FunctionWithException;
@@ -29,16 +26,6 @@ public class NMSImplementationProvider implements ImplementationProvider {
return new NMSPacketScoreboard(player);
}
@Override
public void onPacketSend(@NonNull Object packet, @NonNull ViaScoreboard scoreboard) {
NMSPacketScoreboard.onPacketSend(packet, scoreboard);
}
@Override
public void onPacketSend(@NonNull Object packet, @NonNull ViaTabList tabList) {
NMSPacketTabList.onPacketSend(packet, tabList);
}
@Override
@NotNull
public TabList newTabList(@NotNull BukkitTabPlayer player) {
@@ -33,28 +33,6 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
private static final Field DisplayObjective_OBJECTIVE_NAME = ReflectionUtils.getOnlyField(PacketPlayOutScoreboardDisplayObjective.class, String.class);
private static final Field DisplayObjective_POSITION = ReflectionUtils.getOnlyField(PacketPlayOutScoreboardDisplayObjective.class, int.class);
@SneakyThrows
@SuppressWarnings("unchecked")
public static void onPacketSend(@NonNull Object packet, @NonNull SafeScoreboard<BukkitTabPlayer> scoreboard) {
if (scoreboard.isAntiOverrideScoreboard()) {
if (packet instanceof PacketPlayOutScoreboardDisplayObjective) {
TAB.getInstance().getFeatureManager().onDisplayObjective(scoreboard.getPlayer(), DisplayObjective_POSITION.getInt(packet),
(String) DisplayObjective_OBJECTIVE_NAME.get(packet));
}
if (packet instanceof PacketPlayOutScoreboardObjective) {
TAB.getInstance().getFeatureManager().onObjective(scoreboard.getPlayer(),
Objective_METHOD.getInt(packet), (String) Objective_OBJECTIVE_NAME.get(packet));
}
}
if (scoreboard.isAntiOverrideTeams() && packet instanceof PacketPlayOutScoreboardTeam) {
int action = TeamPacket_ACTION.getInt(packet);
if (action == TeamAction.UPDATE) return;
Collection<String> players = (Collection<String>) TeamPacket_PLAYERS.get(packet);
if (players == null) players = Collections.emptyList();
TeamPacket_PLAYERS.set(packet, scoreboard.onTeamPacket(action, (String) TeamPacket_NAME.get(packet), players));
}
}
/**
* Constructs new instance with given player.
*
@@ -68,7 +46,7 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
@Override
public void registerObjective(@NonNull Objective objective) {
ScoreboardObjective obj = new ScoreboardObjective(dummyScoreboard, objective.getName(), IScoreboardCriteria.b);
obj.setDisplayName(cutTo(objective.getTitle().toLegacyText(), Limitations.SCOREBOARD_TITLE_PRE_1_13));
obj.setDisplayName(maybeCut(objective.getTitle().toLegacyText(), Limitations.SCOREBOARD_TITLE_PRE_1_13));
obj.a(IScoreboardCriteria.EnumScoreboardHealthDisplay.valueOf(objective.getHealthDisplay().name()));
objective.setPlatformObjective(obj);
sendPacket(new PacketPlayOutScoreboardObjective(obj, ObjectiveAction.REGISTER));
@@ -87,7 +65,7 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
@Override
public void updateObjective(@NonNull Objective objective) {
ScoreboardObjective obj = (ScoreboardObjective) objective.getPlatformObjective();
obj.setDisplayName(cutTo(objective.getTitle().toLegacyText(), Limitations.SCOREBOARD_TITLE_PRE_1_13));
obj.setDisplayName(maybeCut(objective.getTitle().toLegacyText(), Limitations.SCOREBOARD_TITLE_PRE_1_13));
obj.a(IScoreboardCriteria.EnumScoreboardHealthDisplay.valueOf(objective.getHealthDisplay().name()));
sendPacket(new PacketPlayOutScoreboardObjective(obj, ObjectiveAction.UPDATE));
}
@@ -134,13 +112,29 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
t.setAllowFriendlyFire((team.getOptions() & 0x01) != 0);
t.setCanSeeFriendlyInvisibles((team.getOptions() & 0x02) != 0);
t.setNameTagVisibility(visibilities[team.getVisibility().ordinal()]);
t.setPrefix(cutTo(team.getPrefix().toLegacyText(), Limitations.TEAM_PREFIX_SUFFIX_PRE_1_13));
t.setSuffix(cutTo(team.getSuffix().toLegacyText(), Limitations.TEAM_PREFIX_SUFFIX_PRE_1_13));
t.setPrefix(maybeCut(team.getPrefix().toLegacyText(), Limitations.TEAM_PREFIX_SUFFIX_PRE_1_13));
t.setSuffix(maybeCut(team.getSuffix().toLegacyText(), Limitations.TEAM_PREFIX_SUFFIX_PRE_1_13));
}
@Override
@SneakyThrows
@SuppressWarnings("unchecked")
public void onPacketSend(@NonNull Object packet) {
onPacketSend(packet, this);
if (packet instanceof PacketPlayOutScoreboardDisplayObjective) {
TAB.getInstance().getFeatureManager().onDisplayObjective(player, DisplayObjective_POSITION.getInt(packet),
(String) DisplayObjective_OBJECTIVE_NAME.get(packet));
}
if (packet instanceof PacketPlayOutScoreboardObjective) {
TAB.getInstance().getFeatureManager().onObjective(player,
Objective_METHOD.getInt(packet), (String) Objective_OBJECTIVE_NAME.get(packet));
}
if (packet instanceof PacketPlayOutScoreboardTeam) {
int action = TeamPacket_ACTION.getInt(packet);
if (action == TeamAction.UPDATE) return;
Collection<String> players = (Collection<String>) TeamPacket_PLAYERS.get(packet);
if (players == null) players = Collections.emptyList();
TeamPacket_PLAYERS.set(packet, onTeamPacket(action, (String) TeamPacket_NAME.get(packet), players));
}
}
/**
@@ -152,4 +146,12 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
private void sendPacket(@NotNull Packet<?> packet) {
((CraftPlayer)player.getPlayer()).getHandle().playerConnection.sendPacket(packet);
}
@NotNull
private String maybeCut(@NonNull String string, int length) {
if (player.getVersion().getMinorVersion() < 13 || TAB.getInstance().getConfiguration().getConfig().isPacketEventsCompensation()) {
return cutTo(string, length);
}
return string;
}
}
@@ -36,31 +36,6 @@ public class NMSPacketTabList extends TrackedTabList<BukkitTabPlayer> {
private static final Field FOOTER = ReflectionUtils.getFields(PacketPlayOutPlayerListHeaderFooter.class, IChatBaseComponent.class).get(1);
@SneakyThrows
@SuppressWarnings("unchecked")
public static void onPacketSend(@NonNull Object packet, TrackedTabList<BukkitTabPlayer> tabList) {
if (!(packet instanceof PacketPlayOutPlayerInfo)) return;
EnumPlayerInfoAction action = (EnumPlayerInfoAction) ACTION.get(packet);
for (PlayerInfoData nmsData : (List<PlayerInfoData>) PLAYERS.get(packet)) {
GameProfile profile = nmsData.a();
UUID id = profile.getId();
if (action == EnumPlayerInfoAction.UPDATE_DISPLAY_NAME || action == EnumPlayerInfoAction.ADD_PLAYER) {
TabComponent expectedName = tabList.getExpectedDisplayNames().get(id);
if (expectedName != null) PlayerInfoData_DisplayName.set(nmsData, expectedName.convert());
}
if (action == EnumPlayerInfoAction.UPDATE_LATENCY || action == EnumPlayerInfoAction.ADD_PLAYER) {
int oldLatency = PlayerInfoData_Latency.getInt(nmsData);
int newLatency = TAB.getInstance().getFeatureManager().onLatencyChange(tabList.getPlayer(), id, oldLatency);
if (oldLatency != newLatency) {
PlayerInfoData_Latency.set(nmsData, newLatency);
}
}
if (action == EnumPlayerInfoAction.ADD_PLAYER) {
TAB.getInstance().getFeatureManager().onEntryAdd(tabList.getPlayer(), id, profile.getName());
}
}
}
/**
* Constructs new instance.
*
@@ -136,8 +111,29 @@ public class NMSPacketTabList extends TrackedTabList<BukkitTabPlayer> {
}
@Override
@SneakyThrows
@SuppressWarnings("unchecked")
public void onPacketSend(@NonNull Object packet) {
onPacketSend(packet, this);
if (!(packet instanceof PacketPlayOutPlayerInfo)) return;
EnumPlayerInfoAction action = (EnumPlayerInfoAction) ACTION.get(packet);
for (PlayerInfoData nmsData : (List<PlayerInfoData>) PLAYERS.get(packet)) {
GameProfile profile = nmsData.a();
UUID id = profile.getId();
if (action == EnumPlayerInfoAction.UPDATE_DISPLAY_NAME || action == EnumPlayerInfoAction.ADD_PLAYER) {
TabComponent expectedName = getExpectedDisplayNames().get(id);
if (expectedName != null) PlayerInfoData_DisplayName.set(nmsData, expectedName.convert());
}
if (action == EnumPlayerInfoAction.UPDATE_LATENCY || action == EnumPlayerInfoAction.ADD_PLAYER) {
int oldLatency = PlayerInfoData_Latency.getInt(nmsData);
int newLatency = TAB.getInstance().getFeatureManager().onLatencyChange(player, id, oldLatency);
if (oldLatency != newLatency) {
PlayerInfoData_Latency.set(nmsData, newLatency);
}
}
if (action == EnumPlayerInfoAction.ADD_PLAYER) {
TAB.getInstance().getFeatureManager().onEntryAdd(player, id, profile.getName());
}
}
}
@SneakyThrows
+2 -1
View File
@@ -1,7 +1,8 @@
dependencies {
implementation(projects.shared)
implementation("org.bstats:bstats-bungeecord:3.1.0")
compileOnly("net.md-5:bungeecord-proxy:1.21-R0.1-SNAPSHOT")
compileOnly("net.md-5:bungeecord-api:1.21-R0.4-SNAPSHOT")
compileOnly("net.md-5:bungeecord-proxy:1.21-R0.3-SNAPSHOT") // R0.4 is not available anywhere, combine with new API
compileOnly("com.github.limework.redisbungee:RedisBungee-Bungee:0.11.0")
compileOnly("com.github.LeonMangler:PremiumVanishAPI:2.8.8")
}
@@ -2,19 +2,16 @@ package me.neznamy.tab.platforms.bungeecord;
import com.google.common.collect.Lists;
import lombok.NonNull;
import me.neznamy.chat.component.TabComponent;
import me.neznamy.tab.shared.Limitations;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.TAB;
import me.neznamy.chat.component.SimpleTextComponent;
import me.neznamy.chat.component.TabComponent;
import me.neznamy.tab.shared.platform.decorators.SafeScoreboard;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.protocol.Either;
import net.md_5.bungee.protocol.NumberFormat;
import net.md_5.bungee.protocol.packet.ScoreboardDisplay;
import net.md_5.bungee.protocol.packet.ScoreboardObjective;
import net.md_5.bungee.protocol.packet.ScoreboardScore;
import net.md_5.bungee.protocol.packet.ScoreboardScoreReset;
import net.md_5.bungee.protocol.packet.*;
import net.md_5.bungee.protocol.packet.Team.NameTagVisibility;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -117,30 +114,46 @@ public class BungeeScoreboard extends SafeScoreboard<BungeeTabPlayer> {
player.sendPacket(new net.md_5.bungee.protocol.packet.Team(
team.getName(),
action,
either(SimpleTextComponent.text(team.getName()), Limitations.TEAM_PREFIX_SUFFIX_PRE_1_13),
either(TabComponent.legacyText(team.getName()), Limitations.TEAM_PREFIX_SUFFIX_PRE_1_13),
either(team.getPrefix(), Limitations.TEAM_PREFIX_SUFFIX_PRE_1_13),
either(team.getSuffix(), Limitations.TEAM_PREFIX_SUFFIX_PRE_1_13),
net.md_5.bungee.protocol.packet.Team.NameTagVisibility.valueOf(team.getVisibility().name()),
net.md_5.bungee.protocol.packet.Team.CollisionRule.valueOf(team.getCollision().name()),
convertVisibility(team.getVisibility()),
convertCollision(team.getCollision()),
player.getVersion().getMinorVersion() >= TEAM_REWORK_VERSION ? team.getColor().getLegacyColor().ordinal() : 0,
(byte) team.getOptions(),
team.getPlayers().toArray(new String[0])
));
}
@NotNull
private Either<String, NameTagVisibility> convertVisibility(@NotNull NameVisibility visibility) {
if (player.getVersion().getNetworkId() >= ProtocolVersion.V1_21_5.getNetworkId()) {
return Either.right(NameTagVisibility.valueOf(visibility.name()));
} else {
return Either.left(visibility.toString());
}
}
@NotNull
private Either<String, net.md_5.bungee.protocol.packet.Team.CollisionRule> convertCollision(@NotNull CollisionRule collision) {
if (player.getVersion().getNetworkId() >= ProtocolVersion.V1_21_5.getNetworkId()) {
return Either.right(net.md_5.bungee.protocol.packet.Team.CollisionRule.valueOf(collision.name()));
} else {
return Either.left(collision.toString());
}
}
@Override
public void onPacketSend(@NonNull Object packet) {
if (isAntiOverrideScoreboard()) {
if (packet instanceof ScoreboardDisplay) {
ScoreboardDisplay display = (ScoreboardDisplay) packet;
TAB.getInstance().getFeatureManager().onDisplayObjective(player, display.getPosition(), display.getName());
}
if (packet instanceof ScoreboardObjective) {
ScoreboardObjective objective = (ScoreboardObjective) packet;
TAB.getInstance().getFeatureManager().onObjective(player, objective.getAction(), objective.getName());
}
if (packet instanceof ScoreboardDisplay) {
ScoreboardDisplay display = (ScoreboardDisplay) packet;
TAB.getInstance().getFeatureManager().onDisplayObjective(player, display.getPosition(), display.getName());
}
if (isAntiOverrideTeams() && packet instanceof net.md_5.bungee.protocol.packet.Team) {
if (packet instanceof ScoreboardObjective) {
ScoreboardObjective objective = (ScoreboardObjective) packet;
TAB.getInstance().getFeatureManager().onObjective(player, objective.getAction(), objective.getName());
}
if (packet instanceof net.md_5.bungee.protocol.packet.Team) {
net.md_5.bungee.protocol.packet.Team team = (net.md_5.bungee.protocol.packet.Team) packet;
if (team.getMode() == TeamAction.UPDATE) return;
List<String> players = team.getPlayers() == null ? Collections.emptyList() : Lists.newArrayList(team.getPlayers());
@@ -1,8 +1,9 @@
package me.neznamy.tab.platforms.bungeecord;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.md_5.bungee.api.plugin.Plugin;
import net.md_5.bungee.protocol.Either;
import net.md_5.bungee.protocol.packet.Team;
/**
* Main class for BungeeCord.
@@ -11,17 +12,28 @@ public class BungeeTAB extends Plugin {
@Override
public void onEnable() {
if (ReflectionUtils.classExists("net.md_5.bungee.protocol.packet.Team$NameTagVisibility")) {
if (isCompatible()) {
TAB.create(new BungeePlatform(this));
} else {
getLogger().warning("§c====================================================================================================");
getLogger().warning("§cThe plugin requires BungeeCord build #1899 " +
"(released on February 1st, 2025) and up (or an equivalent fork) to work. If you are using a fork that did not" +
" update to the new BungeeCord version yet, stay on TAB v5.0.5, which supports older builds.");
getLogger().warning("§c====================================================================================================");
logIncompatibleVersionWarning();
}
}
private boolean isCompatible() {
try {
return Team.class.getDeclaredField("nameTagVisibility").getType() == Either.class;
} catch (ReflectiveOperationException e) {
return false;
}
}
private void logIncompatibleVersionWarning() {
getLogger().warning("§c====================================================================================================");
getLogger().warning("§cThe plugin requires BungeeCord build #1980 (released on June 21st, 2025) and up (or an equivalent fork) to work.");
getLogger().warning("§cIf you are using a fork that did not update to the new BungeeCord version yet, stay on TAB v5.2.2, which supports older builds.");
getLogger().warning("§c====================================================================================================");
}
@Override
public void onDisable() {
if (TAB.getInstance() != null) TAB.getInstance().unload();
@@ -1,9 +1,9 @@
package me.neznamy.tab.platforms.bungeecord.hook;
import de.myzelyam.api.vanish.BungeeVanishAPI;
import me.neznamy.chat.component.TabComponent;
import me.neznamy.tab.api.TabPlayer;
import me.neznamy.tab.platforms.bungeecord.BungeeTabPlayer;
import me.neznamy.chat.component.SimpleTextComponent;
import me.neznamy.tab.shared.hook.PremiumVanishHook;
import me.neznamy.tab.shared.platform.Platform;
import me.neznamy.tab.shared.util.ReflectionUtils;
@@ -29,7 +29,7 @@ public class BungeePremiumVanishHook extends PremiumVanishHook {
canSeeEnabled = true;
} else {
canSeeEnabled = false;
platform.logWarn(SimpleTextComponent.text("Detected an outdated version of " +
platform.logWarn(TabComponent.legacyText("Detected an outdated version of " +
"PremiumVanish with limited API. Vanish compatibility " +
"may not work as expected. Update PremiumVanish to version 2.7.11+ for optimal experience."));
}
@@ -28,7 +28,7 @@ public class BungeePipelineInjector extends NettyPipelineInjector {
public BungeePipelineInjector() {
super("inbound-boss");
Config config = TAB.getInstance().getConfiguration().getConfig();
byteBufDeserialization = (config.getTeams() != null && config.getTeams().isAntiOverride()) || config.getScoreboard() != null;
byteBufDeserialization = (config.getTeams() != null) || config.getScoreboard() != null;
}
@Override
@@ -12,7 +12,6 @@ import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.connection.InitialHandler;
import net.md_5.bungee.connection.LoginResult;
import net.md_5.bungee.protocol.Property;
import net.md_5.bungee.protocol.packet.PlayerListHeaderFooter;
import net.md_5.bungee.protocol.packet.PlayerListItem;
import net.md_5.bungee.protocol.packet.PlayerListItem.Item;
import net.md_5.bungee.protocol.packet.PlayerListItemUpdate;
@@ -47,7 +46,7 @@ public abstract class BungeeTabList extends TrackedTabList<BungeeTabPlayer> {
@Override
public void setPlayerListHeaderFooter(@NonNull TabComponent header, @NonNull TabComponent footer) {
player.sendPacket(new PlayerListHeaderFooter(toComponent(header), toComponent(footer)));
player.getPlayer().setTabHeader(toComponent(header), toComponent(footer));
}
/**
-2
View File
@@ -1,5 +1,3 @@
dependencies {
compileOnlyApi("net.kyori:adventure-api:4.18.0")
compileOnlyApi("com.viaversion:viaversion-api:5.2.1")
compileOnlyApi("com.viaversion:viaversion-common:5.2.1")
}
@@ -1,5 +1,6 @@
package me.neznamy.chat.component;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.jetbrains.annotations.NotNull;
@@ -8,7 +9,7 @@ import org.jetbrains.annotations.NotNull;
* A component of "keybind" type that contains bound key.
*/
@Getter
@AllArgsConstructor
@AllArgsConstructor(access = AccessLevel.PACKAGE)
public class KeybindComponent extends TabComponent {
@NotNull
@@ -1,6 +1,5 @@
package me.neznamy.chat.component;
import lombok.NonNull;
import me.neznamy.chat.TextColor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -9,31 +8,15 @@ import org.jetbrains.annotations.Nullable;
* An implementation that only uses the "text" field with legacy colors in it, without using
* any component style or extra.
*/
public class SimpleTextComponent extends TextComponent {
public class LegacyTextComponent extends TextComponent {
/** Empty component to avoid recreating one over and over */
public static final SimpleTextComponent EMPTY = new SimpleTextComponent("");
/**
* Creates a new instance with given text.
*
* @param text
* Component text, including color codes
* @return Component with given text
*/
@NotNull
public static SimpleTextComponent text(@NonNull String text) {
if (text.isEmpty()) return EMPTY;
return new SimpleTextComponent(text);
}
/**
* Constructs new instance with given text.
*
* @param text
* Component text
*/
private SimpleTextComponent(@NotNull String text) {
protected LegacyTextComponent(@NotNull String text) {
super(text);
}
@@ -1,23 +1,22 @@
package me.neznamy.chat.component;
import com.viaversion.nbt.tag.Tag;
import com.viaversion.viaversion.libs.gson.JsonElement;
import com.viaversion.viaversion.util.ComponentUtil;
import lombok.Getter;
import lombok.NonNull;
import lombok.Setter;
import lombok.SneakyThrows;
import me.neznamy.chat.ChatModifier;
import me.neznamy.chat.EnumChatFormat;
import me.neznamy.chat.TextColor;
import me.neznamy.chat.hook.AdventureHook;
import me.neznamy.chat.hook.ViaVersionHook;
import me.neznamy.chat.rgb.RGBUtils;
import me.neznamy.chat.util.TriFunction;
import net.kyori.adventure.text.Component;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -27,6 +26,9 @@ import java.util.regex.Pattern;
*/
public abstract class TabComponent {
/** Empty component to avoid recreating one over and over */
public static final LegacyTextComponent EMPTY_LEGACY_TEXT = new LegacyTextComponent("");
/** Function for converting this class into platform's actual component */
@Nullable
public static Function<TabComponent, Object> CONVERT_FUNCTION;
@@ -106,15 +108,6 @@ public abstract class TabComponent {
@Setter
private Component adventureComponent;
/** ViaVersion component from this component */
@Nullable
@Setter
private JsonElement viaComponent;
/** ViaVersion tag from this component */
@Nullable
@Setter
private Tag viaTag;
@Nullable
private Object fixedFormat;
@@ -185,26 +178,6 @@ public abstract class TabComponent {
return adventureComponent;
}
/**
* Converts this component to an ViaVersion component.
* @return Converted component
*/
@NotNull
public JsonElement toViaVersion() {
if (viaComponent == null) viaComponent = ViaVersionHook.convert(this);
return viaComponent;
}
/**
* Converts this component to an ViaVersion tag.
* @return Converted component
*/
@NotNull
public Tag toViaVersionTag() {
if (viaTag == null) viaTag = ComponentUtil.jsonToTag(toViaVersion());
return viaTag;
}
/**
* Creates a FixedFormat using given platform-specific create function.
* If the value is already initialized, it is returned immediately instead.
@@ -328,7 +301,7 @@ public abstract class TabComponent {
break;
}
}
final TextComponent component = new TextComponent("", components);
TextComponent component = new TextComponent("", components);
// Safe check to avoid rare mojang "bug" that display text as italic by default
// This doesn't affect #toLegacyText() method at all
component.modifier.setItalic(false);
@@ -422,4 +395,48 @@ public abstract class TabComponent {
}
return true;
}
/**
* Returns an empty component of "text" type.
* This is used to avoid creating new empty components over and over.
*
* @return Empty legacy text component
*/
public static TabComponent empty() {
return EMPTY_LEGACY_TEXT;
}
/**
* Creates a new component of "text" type with given legacy text.
*
* @param text
* Text to use in the component
* @return New legacy text component with given text
*/
public static LegacyTextComponent legacyText(@NonNull String text) {
if (text.isEmpty()) return EMPTY_LEGACY_TEXT;
return new LegacyTextComponent(text);
}
/**
* Creates a new component of "translatable" type with given key.
*
* @param key
* Translatable to use in the component
* @return New translatable component with given key
*/
public static TranslatableComponent translatable(@NonNull String key) {
return new TranslatableComponent(key);
}
/**
* Creates a new component of "keybind" type with given key.
*
* @param keybind
* Key to use in the component
* @return New keybind text component with given key
*/
public static KeybindComponent keybind(@NonNull String keybind) {
return new KeybindComponent(keybind);
}
}
@@ -1,5 +1,6 @@
package me.neznamy.chat.component;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.jetbrains.annotations.NotNull;
@@ -8,7 +9,7 @@ import org.jetbrains.annotations.NotNull;
* A component of "translate" type that contains key to translate.
*/
@Getter
@AllArgsConstructor
@AllArgsConstructor(access = AccessLevel.PACKAGE)
public class TranslatableComponent extends TabComponent {
@NotNull
@@ -97,9 +97,9 @@ public class AdventureHook {
if (component instanceof net.kyori.adventure.text.TextComponent) {
tabComponent = new TextComponent(((net.kyori.adventure.text.TextComponent) component).content());
} else if (component instanceof net.kyori.adventure.text.TranslatableComponent) {
tabComponent = new TranslatableComponent(((net.kyori.adventure.text.TranslatableComponent) component).key());
tabComponent = TabComponent.translatable(((net.kyori.adventure.text.TranslatableComponent) component).key());
} else if (component instanceof net.kyori.adventure.text.KeybindComponent) {
tabComponent = new KeybindComponent(((net.kyori.adventure.text.KeybindComponent) component).keybind());
tabComponent = TabComponent.keybind(((net.kyori.adventure.text.KeybindComponent) component).keybind());
} else {
throw new UnsupportedOperationException(component.getClass().getName() + " component type is not supported");
}
@@ -1,81 +0,0 @@
package me.neznamy.chat.hook;
import com.viaversion.viaversion.libs.gson.JsonArray;
import com.viaversion.viaversion.libs.gson.JsonElement;
import com.viaversion.viaversion.libs.gson.JsonObject;
import com.viaversion.viaversion.util.ComponentUtil;
import me.neznamy.chat.ChatModifier;
import me.neznamy.chat.component.KeybindComponent;
import me.neznamy.chat.component.SimpleTextComponent;
import me.neznamy.chat.component.TabComponent;
import me.neznamy.chat.component.TextComponent;
import me.neznamy.chat.component.TranslatableComponent;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
@ApiStatus.Internal
public class ViaVersionHook {
@NotNull
public static JsonElement convert(@NotNull TabComponent component) {
if (component instanceof SimpleTextComponent) {
return ComponentUtil.legacyToJson(component.toLegacyText());
}
final JsonObject object = new JsonObject();
// Root
if (component instanceof TextComponent) {
object.addProperty("type", "text"); // +1.20.3 optimization
object.addProperty("text", ((TextComponent) component).getText());
} else if (component instanceof TranslatableComponent) {
object.addProperty("type", "translatable"); // +1.20.3 optimization
object.addProperty("translate", ((TranslatableComponent) component).getKey());
} else if (component instanceof KeybindComponent) {
object.addProperty("type", "keybind"); // +1.20.3 optimization
object.addProperty("keybind", ((KeybindComponent) component).getKeybind());
} else {
throw new IllegalStateException("Unknown component type: " + component.getClass().getName());
}
// Color
final ChatModifier modifier = component.getModifier();
if (modifier.getColor() != null) {
object.addProperty("color", "#" + modifier.getColor().getHexCode());
}
if (modifier.getShadowColor() != null) {
object.addProperty("shadow_color", modifier.getShadowColor());
}
// Render
if (Boolean.TRUE.equals(modifier.getBold())) {
object.addProperty("bold", true);
}
if (modifier.getItalic() != null) {
object.addProperty("italic", modifier.getItalic());
}
if (Boolean.TRUE.equals(modifier.getObfuscated())) {
object.addProperty("obfuscated", true);
}
if (Boolean.TRUE.equals(modifier.getStrikethrough())) {
object.addProperty("strikethrough", true);
}
if (Boolean.TRUE.equals(modifier.getUnderlined())) {
object.addProperty("underlined", true);
}
if (modifier.getFont() != null) {
object.addProperty("font", modifier.getFont());
}
// Extra
if (!component.getExtra().isEmpty()) {
final JsonArray extra = new JsonArray();
for (TabComponent sub : component.getExtra()) {
extra.add((JsonElement) convert(sub));
}
object.add("extra", extra);
}
return object;
}
}
+2 -2
View File
@@ -11,11 +11,11 @@ repositories {
maven("https://maven.nucleoid.xyz/")
}
val minecraftVersion = "1.21.5"
val minecraftVersion = "1.21.6"
// Fabric API versions for each Minecraft version for easier backporting
val fabricApiVersions = mapOf(
"25w21a" to "0.119.10+1.21.6",
"1.21.6" to "0.125.1+1.21.6",
"1.21.5" to "0.119.9+1.21.5",
"1.21.4" to "0.119.2+1.21.4",
"1.21.3" to "0.114.0+1.21.3",
@@ -106,7 +106,7 @@ public record FabricPlatform(MinecraftServer server) implements BackendPlatform
@Override
@NotNull
public String getServerVersionInfo() {
return "[Fabric] " + SharedConstants.getCurrentVersion().getName();
return "[Fabric] " + SharedConstants.getCurrentVersion().name();
}
@Override
@@ -128,15 +128,13 @@ public class FabricScoreboard extends SafeScoreboard<FabricTabPlayer> {
@Override
@SneakyThrows
public void onPacketSend(@NonNull Object packet) {
if (isAntiOverrideScoreboard()) {
if (packet instanceof ClientboundSetDisplayObjectivePacket display) {
TAB.getInstance().getFeatureManager().onDisplayObjective(player, display.getSlot().ordinal(), display.getObjectiveName());
}
if (packet instanceof ClientboundSetObjectivePacket objective) {
TAB.getInstance().getFeatureManager().onObjective(player, objective.getMethod(), objective.getObjectiveName());
}
if (packet instanceof ClientboundSetDisplayObjectivePacket display) {
TAB.getInstance().getFeatureManager().onDisplayObjective(player, display.getSlot().ordinal(), display.getObjectiveName());
}
if (isAntiOverrideTeams() && packet instanceof ClientboundSetPlayerTeamPacket team) {
if (packet instanceof ClientboundSetObjectivePacket objective) {
TAB.getInstance().getFeatureManager().onObjective(player, objective.getMethod(), objective.getObjectiveName());
}
if (packet instanceof ClientboundSetPlayerTeamPacket team) {
int method = getMethod(team);
if (method == TeamAction.UPDATE) return;
team.getPlayers();
+1 -2
View File
@@ -14,8 +14,7 @@
"fabric-api-base": "*",
"java": ">=17",
"minecraft": [
"1.21.4",
"1.21.5"
"1.21.6"
]
},
"recommends": {
+2 -1
View File
@@ -12,10 +12,11 @@ repositories {
maven("https://maven.neoforged.net/releases")
}
val minecraftVersion = "1.21.5"
val minecraftVersion = "1.21.6"
// Forge API versions for each Minecraft version for easier backporting
val forgeApiVersions = mapOf(
"1.21.6" to "1.21.6-56.0.0",
"1.21.5" to "1.21.5-55.0.3",
"1.21.4" to "1.21.4-54.1.3",
"1.21.3" to "1.21.3-53.1.0",
@@ -4,9 +4,7 @@ import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.platform.EventListener;
import me.neznamy.tab.shared.platform.TabPlayer;
import net.minecraft.server.level.ServerPlayer;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.entity.player.PlayerEvent;
import net.minecraftforge.eventbus.api.IEventBus;
import org.jetbrains.annotations.NotNull;
/**
@@ -18,15 +16,14 @@ public class ForgeEventListener implements EventListener<ServerPlayer> {
* Registers all event listeners.
*/
public void register() {
IEventBus eventBus = MinecraftForge.EVENT_BUS;
eventBus.addListener((PlayerEvent.PlayerLoggedInEvent event) -> join((ServerPlayer) event.getEntity()));
eventBus.addListener((PlayerEvent.PlayerLoggedOutEvent event) -> quit(event.getEntity().getUUID()));
eventBus.addListener((PlayerEvent.PlayerRespawnEvent event) -> {
PlayerEvent.PlayerLoggedInEvent.BUS.addListener(event -> join((ServerPlayer) event.getEntity()));
PlayerEvent.PlayerLoggedOutEvent.BUS.addListener(event -> quit(event.getEntity().getUUID()));
PlayerEvent.PlayerRespawnEvent.BUS.addListener(event -> {
ServerPlayer player = (ServerPlayer) event.getEntity();
replacePlayer(player.getUUID(), player);
worldChange(player.getUUID(), ForgeTAB.getLevelName(player.level()));
});
eventBus.addListener((PlayerEvent.PlayerChangedDimensionEvent event) -> worldChange(event.getEntity().getUUID(), ForgeTAB.getLevelName(event.getEntity().level())));
PlayerEvent.PlayerChangedDimensionEvent.BUS.addListener(event -> worldChange(event.getEntity().getUUID(), ForgeTAB.getLevelName(event.getEntity().level())));
}
@Override
@@ -81,7 +81,7 @@ public record ForgePlatform(MinecraftServer server) implements BackendPlatform {
@Override
@NotNull
public String getServerVersionInfo() {
return "[Forge] " + SharedConstants.getCurrentVersion().getName();
return "[Forge] " + SharedConstants.getCurrentVersion().name();
}
@Override
@@ -128,15 +128,13 @@ public class ForgeScoreboard extends SafeScoreboard<ForgeTabPlayer> {
@Override
@SneakyThrows
public void onPacketSend(@NonNull Object packet) {
if (isAntiOverrideScoreboard()) {
if (packet instanceof ClientboundSetDisplayObjectivePacket display) {
TAB.getInstance().getFeatureManager().onDisplayObjective(player, display.getSlot().ordinal(), display.getObjectiveName());
}
if (packet instanceof ClientboundSetObjectivePacket objective) {
TAB.getInstance().getFeatureManager().onObjective(player, objective.getMethod(), objective.getObjectiveName());
}
if (packet instanceof ClientboundSetDisplayObjectivePacket display) {
TAB.getInstance().getFeatureManager().onDisplayObjective(player, display.getSlot().ordinal(), display.getObjectiveName());
}
if (isAntiOverrideTeams() && packet instanceof ClientboundSetPlayerTeamPacket team) {
if (packet instanceof ClientboundSetObjectivePacket objective) {
TAB.getInstance().getFeatureManager().onObjective(player, objective.getMethod(), objective.getObjectiveName());
}
if (packet instanceof ClientboundSetPlayerTeamPacket team) {
int method = getMethod(team);
if (method == TeamAction.UPDATE) return;
players.set(team, onTeamPacket(method, team.getName(), team.getPlayers()));
@@ -5,23 +5,23 @@ import net.minecraft.world.level.Level;
import net.minecraft.world.level.storage.ServerLevelData;
import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.RegisterCommandsEvent;
import net.minecraftforge.event.server.ServerStartingEvent;
import net.minecraftforge.event.server.ServerStoppingEvent;
import net.minecraftforge.eventbus.api.IEventBus;
import net.minecraftforge.fml.common.Mod;
import org.jetbrains.annotations.NotNull;
/**
* Main class for Forge TAB implementation.
*/
@OnlyIn(Dist.DEDICATED_SERVER)
@Mod(value = "tab")
@Mod("tab")
public class ForgeTAB {
public ForgeTAB() {
IEventBus EVENT_BUS = MinecraftForge.EVENT_BUS;
EVENT_BUS.addListener((RegisterCommandsEvent event) -> new ForgeTabCommand().onRegisterCommands(event.getDispatcher()));
EVENT_BUS.addListener((ServerStartingEvent event) -> TAB.create(new ForgePlatform(event.getServer())));
EVENT_BUS.addListener((ServerStoppingEvent event) -> TAB.getInstance().unload());
RegisterCommandsEvent.BUS.addListener(event -> new ForgeTabCommand().onRegisterCommands(event.getDispatcher()));
ServerStartingEvent.BUS.addListener(event -> TAB.create(new ForgePlatform(event.getServer())));
ServerStoppingEvent.BUS.addListener(event -> TAB.getInstance().unload());
}
@NotNull
+1 -1
View File
@@ -22,6 +22,6 @@ side = "SERVER"
[[dependencies.tab]]
modId = "minecraft"
mandatory = true
versionRange = "[1.21.4],[1.21.5]"
versionRange = "[1.21.6]"
ordering = "NONE"
side = "SERVER"
+1 -1
View File
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
Loaded 100 of 170 files, more files were not shown because too many files have changed in this diff. Show more