mirror of
https://github.com/NEZNAMY/TAB.git
synced 2026-11-13 12:42:06 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e95a5597fe | ||
|
|
ea1dc94c60 | ||
|
|
2079505a6f | ||
|
|
1a52f3de21 | ||
|
|
87321fe6d9 | ||
|
|
e3afa04f90 | ||
|
|
4a7fb76011 | ||
|
|
4c0b79ef25 | ||
|
|
0d6e71ad9b | ||
|
|
4025eb5426 | ||
|
|
a26ab401b6 | ||
|
|
1846eab87e | ||
|
|
c860fc33fb | ||
|
|
9a3f5b4322 | ||
|
|
fb58ca99ae | ||
|
|
b490d79929 | ||
|
|
fb6044c3c1 | ||
|
|
d518e8c463 | ||
|
|
196ab4d61d | ||
|
|
2e406f3d1c | ||
|
|
8d6c17806c | ||
|
|
d60ffccd2a | ||
|
|
5be3016e90 | ||
|
|
55befc4d0c | ||
|
|
4a4f15ed52 | ||
|
|
0f294a47b0 | ||
|
|
4ee766a6f9 | ||
|
|
a3103ea0c9 |
No files matched your search
@@ -116,7 +116,7 @@ Commands have required permissions associated with them.
|
||||
|
||||
## /tab nametag <showview/hideview/toggleview> \[viewer\] \[options\]
|
||||
* **Permission:** `tab.nametag.view` (`tab.nametag.view.other` for toggling for other players)
|
||||
* **Description:** Shows / hides / toggles nametag VIEW a specified player.
|
||||
* **Description:** Shows / hides / toggles nametag VIEW of a specified player. When hidden, the viewer will not see anyone's nametags.
|
||||
* **Options:**
|
||||
* `-s` for silent toggling (no chat message for affected player)
|
||||
|
||||
|
||||
@@ -11,67 +11,51 @@ TAB does not depend on any other plugins. The experience can however be enhanced
|
||||
|
||||
## Java
|
||||
TAB's shared module is compiled with Java 8. For the platforms themselves, TAB does have a higher requirement, however, it does not require a Java version higher than the server software itself does. Specifically:
|
||||
* Fabric, Forge and NeoForge require Java 21
|
||||
* Fabric and NeoForge require Java 25
|
||||
* Velocity requires Java 17
|
||||
* Bukkit and BungeeCord are ok with Java 8
|
||||
|
||||
# Supported server software and versions
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Software type</th>
|
||||
<th>Software name</th>
|
||||
<th>Supported versions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td rowspan=2>Vanilla</td>
|
||||
<td rowspan=1><a href="https://getbukkit.org/">Bukkit</a> (+forge hybrids)</td>
|
||||
<td>✔ (1.7.10 ~ 26.1.1) (not all versions included)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan=1><a href="https://www.spongepowered.org/">Sponge</a></td>
|
||||
<td>❌</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td rowspan=3>Modded</td>
|
||||
<td rowspan=1><a href="https://fabricmc.net">Fabric</a></td>
|
||||
<td>✔ (1.21.11)<sup>1</sup></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan=1><a href="https://minecraftforge.net">Forge</a></td>
|
||||
<td>✔ (1.21.11)<sup>1</sup></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan=1><a href="https://neoforged.net/">NeoForge</a></td>
|
||||
<td>✔ (1.21.11)<sup>1</sup></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td rowspan=2>Proxies</td>
|
||||
<td rowspan=1><a href="https://ci.md-5.net/job/BungeeCord/">BungeeCord</a></td>
|
||||
<td>✔ (latest only)<sup>2</sup></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan=1><a href="https://www.velocitypowered.com/">Velocity</a></td>
|
||||
<td>✔ (latest only)<sup>2</sup></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
With the introduction of new Minecraft versions, it is becoming harder and harder to support many versions in the same jar, eventually making it impossible since Minecraft 26.1.
|
||||
**As a general rule, if you want the latest TAB version for your server please use [Modrinth](https://modrinth.com/plugin/tab-was-taken)'s download filter where you can select your server software and version and you'll get the latest jar.**
|
||||
|
||||
<sup>1</sup> 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 TAB is not available for your desired Minecraft version or is very outdated, you may [request a backport](https://github.com/NEZNAMY/TAB/issues/new?template=backport.yml).
|
||||
Currently, the latest Minecraft version supported by TAB is 26.1.2. When new versions come out, TAB is always updated soon after to support them.
|
||||
|
||||
<sup>2</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.
|
||||
If your server software and version is reasonably popular and TAB hasn't been backported to that version in a very long time, you may [request a backport](https://github.com/NEZNAMY/TAB/issues/new/choose) (unless backports to that version are no longer offered, see below).
|
||||
Since TAB 6.0.0, Minecraft 1.x started taking hits with each subsequent versions - Bukkit versions started disappearing and **modded platforms no longer offer backports of TAB 6.0.0+ to MC 1.x** (this is because the build scripts had to be massively changed for MC 26+).
|
||||
|
||||
The sections below provide **additional** information for each platform.
|
||||
|
||||
## Bukkit
|
||||
TAB 5.5.0 supports all 1.x versions starting with 1.7. For 1.5.2 - 1.6.4, use TAB v5.2.5.
|
||||
Since TAB 6.0.0, support for unused 1.x versions will continue to disappear with each subsequent Minecraft release.
|
||||
|
||||
## BungeeCord / Velocity
|
||||
For proxies, the plugin is made to work with the latest build.
|
||||
Since breaking changes don't happen too often, it means a wide range of versions is usually supported.
|
||||
When a breaking change occurs, the plugin is updated to support the new version,
|
||||
automatically making old versions incompatible.
|
||||
Since proxies support all client versions, there is never a reason to stay outdated,
|
||||
so you can always safely update to new version/build of your proxy software if the plugin requires it.
|
||||
so you can always safely update to a new build of your proxy software if the plugin requires it.
|
||||
|
||||
## Fabric
|
||||
Every TAB update only supports the latest Fabric version(s) at the time of release.
|
||||
|
||||
Minecraft 1.x - TAB 5.5.0 was backported to 1.20 - 1.21.11 and the jars were uploaded to Modrinth. TAB 5.0.7 supports all Fabric versions from 1.14 to 1.19.4. Backports to 1.x are no longer offered.
|
||||
|
||||
## Forge
|
||||
Every TAB update only supports the latest Forge version(s) at the time of release.
|
||||
**Currently, Forge 26.x is not supported because the modding tool is broken**.
|
||||
|
||||
Minecraft 1.x - TAB 5.5.0 was backported to 1.20 - 1.21.11 and the jars were uploaded to Modrinth, along with a few other random versions people requested. If your version is not available, you may request a backport.
|
||||
|
||||
## NeoForge
|
||||
Every TAB update only supports the latest NeoForge version(s) at the time of release.
|
||||
|
||||
Minecraft 1.x - TAB 5.5.0 was backported to all existing NeoForge versions (1.20.2+) and the jars were uploaded to Modrinth. Backports to 1.x are no longer offered.
|
||||
|
||||
## Sponge
|
||||
This platform is no longer supported as no one is using it and it has breaking changes to the API with each Minecraft version. Sponge is supposed to be combined with Forge or NeoForge, both of which TAB supports. You can use Modrinth's download filter to see what is available.
|
||||
|
||||
# Supported features per platform
|
||||
| Feature <sup>Platform</sup> | Bukkit / Hybrid | Fabric / Forge / NeoForge | BungeeCord | Velocity |
|
||||
@@ -112,7 +96,6 @@ Some are available on all platforms, some only in a few.
|
||||
* **Waterfall**'s `disable_tab_list_rewrite: true` **may** cause tablist to use offline UUIDs while TAB expects online UUIDs, causing various problems (most notably tablist formatting not working). Checking for this option is not an option either, because tablist rewrite might still be enabled despite being disabled (don't ask how, I have no idea). Set the option to `false` if you are experiencing issues.
|
||||
* **ViaVersion on proxy and TAB on backend** acts like a client-sided protocol hack, making it impossible for TAB to know player's real version and causing issues related to it, see [Per-version experience](https://github.com/NEZNAMY/TAB/wiki/Additional-information#per-version-experience) for more info. Avoid this combination. Either install ViaVersion on all backend servers instead or install TAB on the proxy as well.
|
||||
* **Nexo**'s `hide_scoreboard_numbers` config option (may apply to ItemsAdder and Oraxen as well) makes [Belowname](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Belowname)'s and [Playerlist objective](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Playerlist-Objective)'s `value` and `fancy-value` not visible. When using these features, you'll need to keep the option disabled.
|
||||
* **LimboAuth** may prevent TAB from applying tablist formatting in a way that is not detectable using Velocity API, resulting in the feature not working properly on join.
|
||||
* **Custom clients / resource packs** - Unofficially modified Minecraft clients often tend to break things. Just Lunar client has tons of bugs that can be reproduced with TAB. Resource packs may also contain modifications you are not aware of, making things not look the way you want them to. If you are experiencing any visual issue and are using a custom client or resource pack, try it with a clean vanilla client. If it works there, it's an issue with the client / resource pack and TAB cannot do anything about it.
|
||||
For example, here are a few bugs in LunarClient / FeatherClient that you may run into when using TAB:
|
||||
* They add their icon to players in tablist, but don't widen the entries. This results in player names overlapping with latency bar. You can avoid this by configuring some spaces in tabsuffix.
|
||||
|
||||
@@ -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.5.0</version>
|
||||
<version>6.0.0</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
@@ -50,7 +50,7 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly 'com.github.NEZNAMY:TAB-API:5.5.0'
|
||||
compileOnly 'com.github.NEZNAMY:TAB-API:6.0.0'
|
||||
}
|
||||
```
|
||||
|
||||
@@ -61,7 +61,7 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly("com.github.NEZNAMY", "TAB-API", "5.5.0")
|
||||
compileOnly("com.github.NEZNAMY", "TAB-API", "6.0.0")
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
# Content
|
||||
* [About](#about)
|
||||
* [Configuration](#configuration)
|
||||
* ~Commands~
|
||||
* ~Placeholders~
|
||||
* [Limitations](#limitations)
|
||||
* [Compatibility with other plugins](#compatibility-with-other-plugins)
|
||||
* [Additional info](#additional-info)
|
||||
* [Additional note 1 - Copying nametag visibility rule](#additional-note-1---copying-nametag-visibility-rule)
|
||||
* [Additional note 2 - Hidden on sneak on 1.8](#additional-note-2---hidden-on-sneak-on-18)
|
||||
* [Additional note 3 - Visible on NPCs](#additional-note-3---visible-on-npcs)
|
||||
* [[26.1] Visible on all entities](#261-visible-on-all-entities)
|
||||
* [[26.1.x] Visible on all entities](#261x-visible-on-all-entities)
|
||||
* [Additional note 4 - Compatibility with modified clients](#additional-note-4---compatibility-with-modified-clients)
|
||||
* [Troubleshooting](#troubleshooting)
|
||||
* ~API~
|
||||
* [Examples](#examples)
|
||||
* [Example 1 - Per-world values](#example-1---per-world-values)
|
||||
* [Example 2 - Hiding `title` for 1.20.3+ players](#example-2---hiding-title-for-1203-players)
|
||||
@@ -103,19 +107,39 @@ This is how you can achieve it using the following popular NPC plugins:
|
||||
* 3 - Change the NPC's name by editing your hologram's line(s): `/hologram edit hi setline 1 <text>`.
|
||||
You can also add more lines if you want. See [FancyNpcs's wiki](https://docs.fancyinnovations.com/fancyholograms/commands/hologram/#text-hologram-modification) for more commands & info.
|
||||
|
||||
### [26.1] Visible on all entities
|
||||
### [26.1.x] Visible on all entities
|
||||
In Minecraft version 26.1, Mojang "fixed" [MC-99647](https://bugs.mojang.com/browse/MC/issues/MC-99647), which reported that belowname is not visible on non-player entities. As a result, the belowname is now visible on all entities with a custom name (this even includes invisible armor stands, holograms and more). A new bug report was made for this: [MC-307012](https://bugs.mojang.com/browse/MC/issues/MC-307012).
|
||||
|
||||
**This is a client-sided bug. You will experience it on 26.1 regardless of the server version**.
|
||||
If your server is on 26.1 or you support players with this version through ViaVersion, this might be a deal breaker for you. If that's the case, either disable this feature entirely, or set
|
||||
**This is a client-sided bug. You will experience it on 26.1.x regardless of the server version**.
|
||||
If your server is on 26.1.x, or you support players with this version through ViaVersion, this might be a dealbreaker for you. If that's the case, either disable this feature entirely, or set
|
||||
```
|
||||
disable-condition: "%player-version-id%>=775"
|
||||
belowname-objective:
|
||||
disable-condition: "%player-version-id%=775"
|
||||
```
|
||||
which will make 26.1 players not see this feature on anyone (remember: disabling this feature disables it for the viewer, not target players).
|
||||
which will make 26.1.x players not see this feature on anyone (remember: disabling this feature disables it for the viewer, not target players).
|
||||
**This bug was fixed in Minecraft version 26.2.**
|
||||
|
||||
## Additional note 4 - Compatibility with modified clients
|
||||
Sadly, this feature is suffering from bugs introduced by third party clients such as Feather and Lunar. These two completely ignore `fancy-value` as if it was never added into the game, even on 1.20.3+. This is just an example, and it's not limited to these two clients and this one issue. If you experience issues with the feature and believe you configured it correctly, use vanilla client to make sure it's not caused by a broken client.
|
||||
|
||||
# Troubleshooting
|
||||
This is a collection of tips to help you figure out why the feature isn't working as you expect.
|
||||
To get started, run `/tab dump <any player>` (this feature doesn't contain per-player content in dump output) and open the generated link. Scroll down to `features` -> `BelowName` and check the content:
|
||||
* If it says `BelowName: Feature is disabled`, it means you disabled the feature. Enable it by setting
|
||||
```
|
||||
belowname-objective:
|
||||
enabled: true
|
||||
```
|
||||
* Check the `configuration` section and compare it with your config file. If it's different, you either forgot to reload TAB, or uploaded the config to the wrong server (or did not upload it at all).
|
||||
* If a player cannot see belowname on anyone, check the table of players and make sure it's not because `Disabled with condition` is `true`.
|
||||
* Check the table of players for `value`, `fancy-value` and `title` and make sure they match your expectations. Don't forget `value` only supports numbers.
|
||||
* If you want to see `fancy-value` instead of just `value`'s number:
|
||||
* Make sure your client is on 1.20.3+.
|
||||
* Make sure your server is on 1.20.3+ (using TAB on proxy counts as being latest).
|
||||
* Make sure you are not using a 3rd party client that removed this feature from 1.20.3+, such as Lunar or Feather.
|
||||
* Keep in mind `title` parses for viewer and is visible on all other players, it does not parse for each target player. Do not use placeholders with strictly per-player results in `title`.
|
||||
* If you have TAB on Velocity, install [VelocityScoreboardAPI](https://github.com/NEZNAMY/VelocityScoreboardAPI/releases) (and make sure it is not outdated).
|
||||
|
||||
# Examples
|
||||
## Example 1 - Per-world values
|
||||
This feature doesn't directly support per-world values or similar.
|
||||
@@ -202,6 +226,7 @@ You can achieve this on `1.20.3+` using `healthbar` expansion from PlaceholderAP
|
||||
```
|
||||
belowname-objective:
|
||||
fancy-value: "%healthbar_healthbar%"
|
||||
title: ""
|
||||
```
|
||||
<img width="130" height="159" alt="image" src="https://github.com/user-attachments/assets/cef217ec-0a39-41ad-8457-f8f67a1e4b02" />
|
||||
|
||||
|
||||
@@ -3,12 +3,11 @@
|
||||
* [Configuration](#configuration)
|
||||
* [Commands](#commands)
|
||||
* [Placeholders](#placeholders)
|
||||
* [Internal placeholders](#internal-placeholders)
|
||||
* [PlaceholderAPI placeholders](#placeholderapi-placeholders)
|
||||
* [Limitations](#limitations)
|
||||
* [Compatibility with other plugins](#compatibility-with-other-plugins)
|
||||
* [Additional info](#additional-info)
|
||||
* [Additional note 1 - Hiding bar itself](#additional-note-1---hiding-bar-itself)
|
||||
* [Troubleshooting](#troubleshooting)
|
||||
* [API](#api)
|
||||
* [Examples](#examples)
|
||||
* [Example 1 - Changing text](#example-1---changing-text)
|
||||
@@ -81,7 +80,6 @@ Here is an example using all 6 properties:
|
||||
| /tab bossbar announce \<name\> \<duration\> | `tab.announce.bar` | Shows the bossbar with the given `name` to every player on the server for the given `duration`, in seconds. |
|
||||
|
||||
# Placeholders
|
||||
## Internal placeholders
|
||||
Here are TAB's internal placeholders you can use when this feature is enabled:
|
||||
| Placeholder | Description |
|
||||
|-------------|-------------|
|
||||
@@ -90,7 +88,6 @@ Here are TAB's internal placeholders you can use when this feature is enabled:
|
||||
|
||||
You can further use these placeholders in progress, for example, using Math expansion from PlaceholderAPI, such as `%math_{tab_placeholder_bossbar_announce_time_left_<bossbar>}/{tab_placeholder_bossbar_announce_time_total_<bossbar>}*100%` (remember to replace `<bossbar>` with the actual name of the bossbar).
|
||||
|
||||
## PlaceholderAPI placeholders
|
||||
Here are TAB's PlaceholderAPI placeholders you can use when this feature is enabled:
|
||||
| Placeholder | Description |
|
||||
|-------------|-------------|
|
||||
@@ -98,7 +95,8 @@ Here are TAB's PlaceholderAPI placeholders you can use when this feature is enab
|
||||
|
||||
# Limitations
|
||||
* Does not support newlines. If you want to display more lines, you'll need to create multiple bossbars.
|
||||
* The bar itself cannot be removed from the server side. However, it can be removed with a resource pack.
|
||||
* The bar itself cannot be removed from the server side. However, it can be removed with a resource pack (see below for more info).
|
||||
* While there is no explicit limit to how many bossbars you can configure, the client will only display up to 9 depending on window size and GUI scale in client settings.
|
||||
|
||||
# Compatibility with other plugins
|
||||
Bossbar is a feature that can have multiple instances sent and displayed, so there is no possible conflict like in scoreboard for example. Every plugin manages its own bossbars and doesn't care about others.
|
||||
@@ -121,6 +119,20 @@ Here is how to do it:
|
||||
|
||||
Finally, set your Bossbar color in TAB Config to `WHITE`. Now the line will be invisible!
|
||||
|
||||
# Troubleshooting
|
||||
This is a collection of tips to help you figure out why the feature isn't working as you expect.
|
||||
To get started, run `/tab dump <affected player>` and open the generated link. Scroll down to `features` -> `BossBar` and check the content:
|
||||
* If it says `BossBar: Feature is disabled`, it means you disabled the feature. Enable it by setting
|
||||
```
|
||||
bossbar:
|
||||
enabled: true
|
||||
```
|
||||
* Check the `configuration` section and compare it with your config file. If it's different, you either forgot to reload TAB, or uploaded the config to the wrong server (or did not upload it at all).
|
||||
* Check the `bossbar conditions` section and for each bossbar and analyze the `display-condition` options (the value, with placeholders parsed, whether it is met or not) if it matches what you expect. If not, you may have made a mistake in your conditions.
|
||||
* Check `bossbars visible` value to make sure user did not toggle the bossbar(s).
|
||||
* Check `currently displayed bossbars` section and compare it with your expectations, as well as the actual in-game result. If it doesn't match your expectations, you didn't configure something correctly. If the output doesn't match the in-game result, it might be a bug.
|
||||
|
||||
|
||||
# API
|
||||
*To get started with the API, see [Developer API](https://github.com/NEZNAMY/TAB/wiki/Developer-API) page.*
|
||||
|
||||
|
||||
@@ -5,7 +5,14 @@
|
||||
* [Pattern Matching](#pattern-matching)
|
||||
* [Configuring isolated servers](#configuring-isolated-servers)
|
||||
* [Seeing all players on the network from some server](#seeing-all-players-on-the-network-from-some-server)
|
||||
* ~Commands~
|
||||
* [Placeholders](#placeholders)
|
||||
* ~Limitations~
|
||||
* [Compatibility with other plugins](#compatibility-with-other-plugins)
|
||||
* ~Additional Info~
|
||||
* [Troubleshooting](#troubleshooting)
|
||||
* ~API~
|
||||
* ~Examples~
|
||||
|
||||
# About
|
||||
This feature allows you to see players from other servers, instead of only seeing players on the same server.
|
||||
@@ -123,5 +130,33 @@ global-playerlist:
|
||||
```
|
||||
Now, players connected to "spyserver1" or "spyserver2" will see everyone on the network in tablist, but no one else (unless on another spy server / in the same server group) will see them.
|
||||
|
||||
# Placeholders
|
||||
Here are TAB's internal placeholders you can use when this feature is enabled:
|
||||
| Placeholder | Description |
|
||||
|-------------|-------------|
|
||||
| %playerlist-group_\<group\>% | Amount of online players in specified global playerlist group |
|
||||
|
||||
# Compatibility with other plugins
|
||||
For compatibility with vanish plugins, vanish status must be detected correctly. See [Additional information - Vanish detection](https://github.com/NEZNAMY/TAB/wiki/Additional-information#vanish-detection) for more info.
|
||||
|
||||
# Troubleshooting
|
||||
> [!NOTE]
|
||||
> This functionality is coming in the next TAB update (6.0.3 / 6.1.0). Adding it here to prepare the wiki in advance (and for users using dev builds).
|
||||
|
||||
This is a collection of tips to help you figure out why the feature isn't working as you expect.
|
||||
To get started, run `/tab dump <player looking at the tablist>` and open the generated link. Scroll down to `features` -> `GlobalPlayerList` and check the content:
|
||||
* If it says `GlobalPlayerList: Feature is disabled`, it means you did not enable the feature. Enable it by setting
|
||||
```
|
||||
global-playerlist:
|
||||
enabled: true
|
||||
```
|
||||
* Check the `configuration` section and compare it with your config file. If it's different, you either forgot to reload TAB, uploaded the config to the wrong server, or did not upload it at all.
|
||||
* Check the `player info` section and make sure everything is as expected - server name, server group and other servers in that group. If server name says `N/A`, it means you did not configure `server-name` (TODO create section and link it).
|
||||
* Check the `visibility from viewer's perspective` table. Find the target player who the viewer should see but doesn't (or vice versa) and check their server, server group and whether they should be visible or not.
|
||||
<br /> **If "visible" doesn't match the expected result, but matches what is shown in-game:**
|
||||
* See the full message for a hint why the player is or is not visible
|
||||
* Make sure server groups are configured as desired
|
||||
* If the issue involves vanishing, see [Vanish Detection](https://github.com/NEZNAMY/TAB/wiki/Additional-information#vanish-detection). In short, make sure the player has `tab.seevanished` permission to see vanished players and if the vanish plugin is PremiumVanish, its layered vanish system does not block the target player from being seen by the viewer
|
||||
|
||||
**If "visible" matches the expected result, but not in-game result:**
|
||||
* If both players are on the same server, the visibility is managed purely by the server (and plugins there), not by TAB. It means the player was hidden by another plugin or not shown by the server itself, for example when using redis to connect the servers and TAB, but the different servers acting as one do not share tablist information between them.
|
||||
@@ -3,9 +3,13 @@
|
||||
* [Configuration](#configuration)
|
||||
* [Design](#design)
|
||||
* [Chaining designs](#chaining-designs)
|
||||
* ~Commands~
|
||||
* ~Placeholders~
|
||||
* ~Limitations~
|
||||
* [Compatibility with other plugins](#compatibility-with-other-plugins)
|
||||
* [Additional info](#additional-info)
|
||||
* [Additional note 1 - [1.8 - 1.20.1] Not resetting on server switch](#additional-note-1---18---1201-not-resetting-on-server-switch)
|
||||
* [Troubleshooting](#troubleshooting)
|
||||
* [API](#api)
|
||||
* [Examples](#examples)
|
||||
* [Example 1 - Per-version header/footer](#example-1---per-version-headerfooter)
|
||||
@@ -111,7 +115,21 @@ When under a BungeeCord network and having TAB installed on backend server and s
|
||||
* Install TAB on BungeeCord and disable header/footer on the server.
|
||||
* Install a plugin that sends some, or even empty header/footer on join.
|
||||
|
||||
Velocity sends reset packet, so it isn't a problem there.
|
||||
Velocity sends a reset packet on server switch, so it isn't a problem there. It is also not a problem since 1.20.2, where configuration phase was added.
|
||||
|
||||
# Troubleshooting
|
||||
This is a collection of tips to help you figure out why the feature isn't working as you expect.
|
||||
To get started, run `/tab dump <affected player>` and open the generated link. Scroll down to `features` -> `HeaderFooter` and check the content:
|
||||
* If it says `HeaderFooter: Feature is disabled`, it means you disabled the feature. Enable it by setting
|
||||
```
|
||||
header-footer:
|
||||
enabled: true
|
||||
```
|
||||
* Check the `configuration` section and compare it with your config file. If it's different, you either forgot to reload TAB, or uploaded the config to the wrong server (or did not upload it at all).
|
||||
* Check the `chain` section and for each design and analyze the `display-condition` options (the value, with placeholders parsed, whether it is met or not) if it matches what you expect. If not, you may have made a mistake in your conditions. If more display conditions for more than 1 design are met, the first design with display condition met is displayed. See [Chaining designs](#chaining-designs) for more info.
|
||||
* Check `currently displayed design` and compare it with your expectations, as well as the actual in-game result. If it doesn't match your expectations, you didn't configure something correctly.
|
||||
* Make sure you don't have another plugin sending header/footer, such as CMI. While TAB should block other plugins from applying it, this functionality is not available on Velocity installation.
|
||||
|
||||
|
||||
# API
|
||||
*To get started with the API, see [Developer API](https://github.com/NEZNAMY/TAB/wiki/Developer-API) page.*
|
||||
@@ -172,9 +190,8 @@ header-footer:
|
||||
|
||||
## Example 3 - Dynamic line count
|
||||
The entire header is just a single character sequence that allows newline symbol (`\n`) for new lines. It is only being split into multiple lines in config for convenience to make it readable when configuring and editing. Same for footer.
|
||||
If using a placeholder that returns the newline symbol, it will create a new line. You can use this to your advantage and create a [conditional placeholder](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Conditional-placeholders) or an [animation](https://github.com/NEZNAMY/TAB/wiki/Animations) that includes newline in some frames.
|
||||
<details>
|
||||
<summary>Example</summary>
|
||||
If using a placeholder that returns the newline symbol, it will create a new line. You can use this to your advantage and create a [conditional placeholder](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Conditional-placeholders) or an [animation](https://github.com/NEZNAMY/TAB/wiki/Animations) that includes newline in some frames.
|
||||
**Example**:
|
||||
|
||||
**animations.yml**
|
||||
```
|
||||
@@ -191,5 +208,4 @@ header-footer:
|
||||
DesignName:
|
||||
header:
|
||||
- "%animation:MyAnimation%"
|
||||
```
|
||||
</details>
|
||||
```
|
||||
@@ -6,6 +6,9 @@
|
||||
* [Player groups](#player-groups)
|
||||
* [Chaining layouts](#chaining-layouts)
|
||||
* [Skins](#skins)
|
||||
* ~Commands~
|
||||
* ~Placeholders~
|
||||
* ~Limitations~
|
||||
* [Compatibility with other plugins](#compatibility-with-other-plugins)
|
||||
* [Compatibility with other features](#compatibility-with-other-features)
|
||||
* [Playerlist objective incompatibility](#playerlist-objective-incompatibility)
|
||||
@@ -15,12 +18,14 @@
|
||||
* [Additional note 1 - [1.19.3 - 1.21.1] Entries in chat complete](#additional-note-1---1193---1211-entries-in-chat-complete)
|
||||
* [Additional note 2 - [1.8 - 1.21.3] Second layer of skin missing](#additional-note-2---18---1213-second-layer-of-skin-missing)
|
||||
* [Additional note 3 - Entry overlap](#additional-note-3---entry-overlap)
|
||||
* ~Troubleshooting~
|
||||
* ~API~
|
||||
* [Examples](#examples)
|
||||
* [Example 1 - Per-server columns](#example-1---per-server-columns)
|
||||
* [Example 2 - Per-world playerlist](#example-2---per-world-playerlist)
|
||||
|
||||
# About
|
||||
This feature allows you to customize all 80 tablist slots. Displaying less than 4 columns is currently not supported ([here's why](https://gist.github.com/NEZNAMY/3dfcbf7d44283735d3c18266a2851651)). This feature can be enabled and configured in **config.yml** file under **layout** section.
|
||||
This feature allows you to customize all 80 tablist slots (possibly less on 1.19.3+). This feature can be enabled and configured in **config.yml** file under **layout** section.
|
||||
|
||||
This feature is only available for versions **1.8** and up due to massive tablist changes, which would make 1.7- compatibility require a complete rewrite of the functionality and could still cause all kinds of visual issues, including, but not limited to compatibility with other plugins adding/removing players from the tablist.
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
* [Name color](#name-color)
|
||||
* [Commands](#commands)
|
||||
* [Placeholders](#placeholders)
|
||||
* [PlaceholderAPI placeholders](#placeholderapi-placeholders)
|
||||
* [Limitations](#limitations)
|
||||
* [Compatibility with other plugins](#compatibility-with-other-plugins)
|
||||
* [Additional info](#additional-info)
|
||||
@@ -19,6 +18,7 @@
|
||||
* [Additional note 4 - F1 view](#additional-note-4---f1-view)
|
||||
* [Additional note 5 - Transparent players](#additional-note-5---transparent-players)
|
||||
* [Additional note 6 - Vanish hook](#additional-note-6---vanish-hook)
|
||||
* ~Troubleshooting~
|
||||
* [API](#api)
|
||||
* [Changing prefix and suffix](#changing-prefix-and-suffix)
|
||||
* [Collision](#collision)
|
||||
@@ -161,9 +161,6 @@ For that reason, TAB detects the last used color code and uses that for team col
|
||||
| /tab setcollision \<player\> \<true\|false\> | `tab.setcollision` | Forces collision rule for specified player, overriding configuration. |
|
||||
|
||||
# Placeholders
|
||||
This feature does not offer any internal placeholders, only PlaceholderAPI placeholders.
|
||||
|
||||
## PlaceholderAPI placeholders
|
||||
Here are TAB's PlaceholderAPI placeholders you can use when this feature is enabled:
|
||||
| Placeholder | Description |
|
||||
|-------------|-------------|
|
||||
|
||||
@@ -2,6 +2,14 @@
|
||||
* [About](#about)
|
||||
* [Configuration](#configuration)
|
||||
* [Grouping playerlist from multiple worlds](#grouping-playerlist-from-multiple-worlds)
|
||||
* ~Commands~
|
||||
* ~Placeholders~
|
||||
* ~Limitations~
|
||||
* ~Compatibility with other plugins~
|
||||
* ~Additional Info~
|
||||
* ~Troubleshooting~
|
||||
* ~API~
|
||||
* ~Examples~
|
||||
|
||||
# About
|
||||
This feature allows you to limit players you see in tablist to only players in your world, or in a group of worlds.
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
* [Example 1 - Adding brackets to placeholder output if it returns non-empty value](#example-1---adding-brackets-to-placeholder-output-if-it-returns-non-empty-value)
|
||||
* [Example 2 - Replicating PlaceholderAPI's %player_colored_ping%](#example-2---replicating-placeholderapis-player_colored_ping)
|
||||
* [Example 3 - Using both original and replaced output](#example-3---using-both-original-and-replaced-output)
|
||||
* [Example 4 - Replacing identifier with custom text](#example-4---replacing-identifier-with-custom-text)
|
||||
|
||||
# About
|
||||
Most placeholders don't have fully configurable output, making you unable to customize it as you'd like.
|
||||
@@ -193,4 +194,16 @@ placeholder-output-replacements:
|
||||
```
|
||||
Now, you can use `%ping%` for original output and `%animation:ping%` for replaced.
|
||||
|
||||
</details>
|
||||
</details>
|
||||
|
||||
## Example 4 - Replacing identifier with custom text
|
||||
Sometimes a placeholder doesn't parse and instead returns the identifier itself (such as on proxy installation before Bridge responds back with placeholder values), but you want to display some default value to avoid issues (ugly text, warnings in console if used in sorting or conditions).
|
||||
|
||||
This can be achieved in the following way:
|
||||
|
||||
```
|
||||
placeholder-output-replacements:
|
||||
"%my_placeholder%":
|
||||
"%my_placeholder%": "Loading..."
|
||||
```
|
||||
If the placeholder is used in numeric sorting for example, you can make it default to `0` or anything else you need at the time.
|
||||
@@ -1,10 +1,14 @@
|
||||
# Content
|
||||
* [About](#about)
|
||||
* [Configuration](#configuration)
|
||||
* ~Commands~
|
||||
* ~Placeholders~
|
||||
* [Limitations](#limitations)
|
||||
* [Compatibility with other plugins](#compatibility-with-other-plugins)
|
||||
* [Additional info](#additional-info)
|
||||
* [Additional note 1 - Spectator gamemode](#additional-note-1---spectator-gamemode)
|
||||
* [Troubleshooting](#troubleshooting)
|
||||
* ~API~
|
||||
* [Examples](#examples)
|
||||
* [Example 1 - Per-world values](#example-1---per-world-values)
|
||||
* [Example 2 - Displaying hearts](#example-2---displaying-hearts)
|
||||
@@ -60,6 +64,23 @@ Note that if this was automatically generated by a plugin, it will probably be a
|
||||
## Additional note 1 - Spectator gamemode
|
||||
The feature will not be visible on players with spectator gamemode. To resolve this, check out [Spectator fix](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Spectator-fix) feature.
|
||||
|
||||
# Troubleshooting
|
||||
This is a collection of tips to help you figure out why the feature isn't working as you expect.
|
||||
To get started, run `/tab dump <any player>` (this feature doesn't contain per-player content in dump output) and open the generated link. Scroll down to `features` -> `PlayerlistObjective` and check the content:
|
||||
* If it says `PlayerlistObjective: Feature is disabled`, it means you disabled the feature. Enable it by setting
|
||||
```
|
||||
playerlist-objective:
|
||||
enabled: true
|
||||
```
|
||||
* Check the `configuration` section and compare it with your config file. If it's different, you either forgot to reload TAB, or uploaded the config to the wrong server (or did not upload it at all).
|
||||
* If a player cannot see playerlist objective on anyone, check the table of players and make sure it's not because `Disabled with condition` is `true`.
|
||||
* Check the table of players for `value`, `fancy-value` and `title` and make sure they match your expectations. Don't forget `value` only supports numbers.
|
||||
* If you want to see `fancy-value` instead of just `value`'s number:
|
||||
* Make sure your client is on 1.20.3+.
|
||||
* Make sure your server is on 1.20.3+ (using TAB on proxy counts as being latest).
|
||||
* Make sure you are not using a 3rd party client that removed this feature from 1.20.3+, such as Lunar or Feather.
|
||||
* If you have TAB on Velocity, install [VelocityScoreboardAPI](https://github.com/NEZNAMY/VelocityScoreboardAPI/releases) (and make sure it is not outdated).
|
||||
|
||||
# Examples
|
||||
## Example 1 - Per-world values
|
||||
This feature doesn't directly support per-world values or similar.
|
||||
|
||||
@@ -8,12 +8,12 @@
|
||||
* [Chaining scoreboards](#chaining-scoreboards)
|
||||
* [Commands](#commands)
|
||||
* [Placeholders](#placeholders)
|
||||
* [PlaceholderAPI placeholders](#placeholderapi-placeholders)
|
||||
* [Limitations](#limitations)
|
||||
* [Compatibility with other plugins](#compatibility-with-other-plugins)
|
||||
* [Additional info](#additional-info)
|
||||
* [Additional note 1 - Text alignment](#additional-note-1---text-alignment)
|
||||
* [Additional note 2 - Geyser / Bedrock issues](#additional-note-2---geyser--bedrock-issues)
|
||||
* ~Troubleshooting~
|
||||
* [API](#api)
|
||||
* [Creating custom scoreboards](#creating-custom-scoreboards)
|
||||
* [Showing custom scoreboards](#showing-custom-scoreboards)
|
||||
@@ -219,9 +219,6 @@ If a player meets both conditions (has defined permission and is in defined worl
|
||||
| `/tab scoreboard announce <name> <duration>` | `tab.announce.scoreboard` | Shows the scoreboard with the given `name` to every player on the server for the given `duration`, in seconds. |
|
||||
|
||||
# Placeholders
|
||||
This feature does not offer any internal placeholders, only PlaceholderAPI placeholders.
|
||||
|
||||
## PlaceholderAPI placeholders
|
||||
Here are TAB's PlaceholderAPI placeholders you can use when this feature is enabled:
|
||||
| Placeholder | Description |
|
||||
|-------------|-------------|
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
# Content
|
||||
* ~About~
|
||||
* [Enabling](#enabling)
|
||||
* [Methods of sorting](#methods-of-sorting)
|
||||
* [GROUPS](#groups)
|
||||
@@ -11,12 +12,17 @@
|
||||
* [Multiple elements with the same priority](#multiple-elements-with-the-same-priority)
|
||||
* [Combining multiple sorting types](#combining-multiple-sorting-types)
|
||||
* [Additional settings](#additional-settings)
|
||||
* ~Commands~
|
||||
* ~Placeholders~
|
||||
* ~Limitations~
|
||||
* ~Compatibility with other plugins~
|
||||
* [Additional info](#additional-info)
|
||||
* [Additional note 1 - Limitations](#additional-note-1---limitations)
|
||||
* [Additional note 2 - per-world sorting](#additional-note-2---per-world-sorting)
|
||||
* [Additional note 3 - Compatibility issues with other plugins](#additional-note-3---compatibility-issues-with-other-plugins)
|
||||
* [Common mistakes](#common-mistakes)
|
||||
* [Troubleshooting](#troubleshooting)
|
||||
* [API](#api)
|
||||
* ~Examples~
|
||||
|
||||
# Enabling
|
||||
To enable sorting, you must have either [Nametags](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Nametags) or [Layout](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Layout) (or both) enabled.
|
||||
@@ -202,21 +208,24 @@ To identify such an issue, check if /tab reload fixes your sorting issue.
|
||||
If it does, it's a compatibility issue of some sort.
|
||||
If not, it is most likely a misconfiguration issue.
|
||||
|
||||
# Common mistakes
|
||||
Every possible mistake could be called "not reading this wiki page", but that would make this section pointless.
|
||||
For that reason, let's call it list of mistakes made when following this page.
|
||||
# Troubleshooting
|
||||
There are many possible reasons why sorting may not work as desired, let's name them all.
|
||||
To get started, run `/tab dump <some player>` and open the generated link. Scroll down to `features` -> `Sorting`.
|
||||
If it says `Sorting: Feature is disabled`, it means you disabled sorting. See [Enabling](#enabling).
|
||||
Check the table of players in the `Sorting` section. The players are listed in the order they should appear in the tablist. Does it match the in-game result?
|
||||
**Option 1 - Players are listed in the same order they appear in game** - This means you did not configure sorting correctly. Check the table of all values for all players and see what is different than you expected:
|
||||
* Your group is wrong (shown in the table as well as on top of the dump):
|
||||
* You did not configure weights in LuckPerms.
|
||||
* You accidentally enabled `use-bukkit-permission-manager` option when on a proxy without knowing what it does.
|
||||
* You installed TAB on a proxy without having any permission plugin on the proxy.
|
||||
* You enabled `assign-groups-by-permissions` without giving those permissions (or the opposite - giving away OP to users, resulting in the highest group being taken).
|
||||
* You mistook `primary-group-finding-list` for the sorting list and did not configure the actual group sorting list.
|
||||
* You combined multiple sorting types, but listed them in the wrong order. See [Combining multiple sorting types](#combining-multiple-sorting-types) (sorting element priorities go from top to bottom, in the table they appear from left to right).
|
||||
|
||||
The most common mistakes include:
|
||||
* Disabling both teams and layout, not realizing it disables sorting as well.
|
||||
* Mistaking `primary-group-finding-list` for the sorting list, despite that list having nothing to do with sorting and by default even having a comment above it saying it has nothing to do with sorting.
|
||||
* Not configuring primary groups correctly. This can have multiple reasons, such as
|
||||
* Not configuring group weights in LuckPerms.
|
||||
* Accidentally enabling `use-bukkit-permission-manager` option when on a proxy without knowing what it does.
|
||||
* Installing TAB on a proxy without having any permission plugin on the proxy.
|
||||
* Enabling `assign-groups-by-permissions` without giving those permissions (or the opposite - giving away OP to users, resulting in the highest group being taken).
|
||||
|
||||
* Using a plugin that causes TAB to fail to apply teams (such as Tablisknu). This includes installing TAB plugin on both backend and proxy, causing the installations to conflict.
|
||||
* Using TAB on Velocity without installing [VelocityScoreboardAPI](https://github.com/NEZNAMY/VelocityScoreboardAPI/releases)
|
||||
**Option 2 - Players are listed in a different order than in-game** - This means teams were not applied properly. Possible reasons include:
|
||||
* Teams are disabled for the player (`disable-condition` was met or teams were disabled via API).
|
||||
* You have TAB on Velocity and forgot to install [VelocityScoreboardAPI](https://github.com/NEZNAMY/VelocityScoreboardAPI/releases) (or it's outdated and doesn't support your Velocity / game version).
|
||||
* You are using a plugin that causes TAB to fail to apply teams (such as Tablisknu). This includes installing TAB plugin on both backend and proxy, causing the installations to conflict (typically proxy taking over and backend not working).
|
||||
|
||||
# API
|
||||
*To get started with the API, see [Developer API](https://github.com/NEZNAMY/TAB/wiki/Developer-API) page.*
|
||||
|
||||
@@ -7,9 +7,12 @@
|
||||
* [Global settings](#global-settings)
|
||||
* [Per-world / per-server](#per-world--per-server)
|
||||
* [Priorities](#priorities)
|
||||
* ~Commands~
|
||||
* [Placeholders](#placeholders)
|
||||
* [PlaceholderAPI placeholders](#placeholderapi-placeholders)
|
||||
* ~Limitations~
|
||||
* [Compatibility with other plugins](#compatibility-with-other-plugins)
|
||||
* ~Additional info~
|
||||
* ~Troubleshooting~
|
||||
* [API](#api)
|
||||
* [Examples](#examples)
|
||||
* [Example 1 - Taking prefix/suffix from permission plugin](#example-1---taking-prefixsuffix-from-permission-plugin)
|
||||
@@ -137,9 +140,6 @@ You can see the source of a value displayed on player by using `/tab dump <playe
|
||||
part of the value you are looking for (tabprefix / customtabname / tabsuffix).
|
||||
|
||||
# Placeholders
|
||||
This feature does not offer any internal placeholders, only PlaceholderAPI placeholders.
|
||||
|
||||
## PlaceholderAPI placeholders
|
||||
Here are TAB's PlaceholderAPI placeholders you can use when this feature is enabled:
|
||||
| Placeholder | Description |
|
||||
|-------------|-------------|
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* [#4 - Why is player sorting not working?](#4---why-is-player-sorting-not-working)
|
||||
* [#5 - How to add players to %staffonline% placeholder?](#5---how-to-add-players-to-the-staffonline-placeholder)
|
||||
* [#6 - How to make player heads visible in tablist?](#6---how-to-make-player-heads-visible-in-tablist)
|
||||
* [#7 - Where can i find current default config files?](#7---where-can-i-find-current-default-config-files)
|
||||
* [#7 - Where can I find current default config files?](#7---where-can-i-find-current-default-config-files)
|
||||
* [#8 - How can I split players into multiple columns?](#8---how-can-i-split-players-into-multiple-columns)
|
||||
* [#9 - How to use space in prefix/suffix command?](#9---how-to-use-spaces-in-prefixsuffix-command)
|
||||
* [#10 - Can I change/remove the green connection bar in tablist?](#10---can-i-changeremove-the-green-connection-bar-in-tablist)
|
||||
@@ -54,7 +54,7 @@ The same goes for disabling heads - you cannot disable them if you have online m
|
||||
|
||||
Minecraft 1.21.9 has added [object components](https://github.com/NEZNAMY/TAB/wiki/How-to-use-Minecraft-components#object-components-1219), which allow you to display player heads. You can put for example `<head:name:%player%>` at the beginning of tabprefix to simulate this.
|
||||
|
||||
## #7 - Where can i find current default config files?
|
||||
## #7 - Where can I find current default config files?
|
||||
You can do any of the following:
|
||||
* Check the [source code](https://github.com/NEZNAMY/TAB/tree/master/shared/src/main/resources/config).
|
||||
* Delete or rename a file, and the plugin will regenerate it.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# About the wiki
|
||||
The wiki explains the functionality of the plugin as of version **6.0.0**.
|
||||
The wiki explains the functionality of the plugin as of version **6.0.2**.
|
||||
If your config looks different or is missing some features, you are using an old version of the plugin.
|
||||
To get the latest version, check [releases](https://github.com/NEZNAMY/TAB/releases/).
|
||||
Wiki for older plugin versions is not available.
|
||||
|
||||
@@ -172,10 +172,10 @@ See table below for behavior based on your setup.
|
||||
|
||||
# MiniMessage
|
||||
TAB has [MiniMessage](https://docs.advntr.dev/minimessage/format.html) hook, however, it does not include this library on its own. Including it would cause conflicts, so it has to be included in the server software.
|
||||
* It **is** included in **Paper 1.18.2+**, **Velocity** and **Sponge**.
|
||||
* It **is** included in **Paper 1.18.2+** and **Velocity**.
|
||||
* It **is not** included in **Spigot**, any server software for **1.18.1-**, **BungeeCord**, **Fabric**, **Forge** and **NeoForge**.
|
||||
|
||||
When MiniMessage is detected on your server, it is automatically used. All codes are translated to MiniMessage syntax and then parsed by MiniMessage.
|
||||
When MiniMessage is detected on your server, it is automatically used unless support is disabled. All codes are translated to MiniMessage syntax and then parsed by MiniMessage.
|
||||
|
||||
# Config options
|
||||
| Option name | Default value | Description |
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
* [About](#about)
|
||||
* [Availability](#availability)
|
||||
* [Enabling](#enabling)
|
||||
* [Configuration changes](#configuration-changes)
|
||||
* [Data conversion](#data-conversion)
|
||||
@@ -17,18 +16,6 @@ It may be added in the future.
|
||||
|
||||
All tables from TAB are prefixed with `tab_`, currently `tab_groups` for groups and `tab_users` for users.
|
||||
|
||||
# Availability
|
||||
Before attempting to use the function, make sure your server software has an integrated MySQL driver.
|
||||
TAB does not contain it by itself.
|
||||
See the compatibility table below for current information about compatibility:
|
||||
|
||||
| Platform | Available |
|
||||
|------------|-----------|
|
||||
| Bukkit | ✔ |
|
||||
| BungeeCord | ✔ |
|
||||
| Modded | ❌ |
|
||||
| Velocity | ❌ |
|
||||
|
||||
# Enabling
|
||||
Find this section in **config.yml**:
|
||||
```
|
||||
|
||||
@@ -102,21 +102,21 @@ placeholders:
|
||||
in **config.yml**.
|
||||
Full list of placeholders (general + feature specific):
|
||||
|
||||
**General**:
|
||||
|
||||
**General**:
|
||||
|
||||
| Identifier | Description |
|
||||
|-----------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `%tab_replace_<placeholder>%` | Applies [Placeholder output replacements](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Placeholder-output-replacements) to a PlaceholderAPI placeholder (for example `%tab_replace_essentials_vanished%`) |
|
||||
| `%tab_placeholder_<placeholder>%` | returns value of tab's internal placeholder (such as `%tab_placeholder_animation:name%` for `%animation:name%`) (this also applies [Placeholder output replacements](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Placeholder-output-replacements) to them if configured) |
|
||||
|
||||
**BossBar**:
|
||||
|
||||
**BossBar**:
|
||||
|
||||
| Identifier | Description |
|
||||
|-------------------------|-----------------------------------------|
|
||||
| `%tab_bossbar_visible%` | "Enabled" if visible, "Disabled" if not |
|
||||
|
||||
**NameTags**:
|
||||
|
||||
**NameTags**:
|
||||
|
||||
| Identifier | Description |
|
||||
|----------------------------|----------------------------------------------------------------------------------------------|
|
||||
| `%tab_tagprefix%` | Player's current tagprefix with placeholders parsed. |
|
||||
@@ -125,15 +125,15 @@ Full list of placeholders (general + feature specific):
|
||||
| `%tab_tagsuffix_raw%` | Player's current raw tagsuffix with placeholder identifiers. |
|
||||
| `%tab_nametag_visibility%` | "Enabled" if player can see nametags, "Disabled" if disabled using `/tab nametag toggleview` |
|
||||
|
||||
**Scoreboard**:
|
||||
|
||||
**Scoreboard**:
|
||||
|
||||
| Identifier | Description |
|
||||
|----------------------------|-------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `%tab_scoreboard_name%` | Returns name of player's currently displayed scoreboard or empty string if none is displayed due to no display condition being met. |
|
||||
| `%tab_scoreboard_visible%` | "Enabled" if visible, "Disabled" if not (toggled with a command) |
|
||||
|
||||
**Tablist name formatting**:
|
||||
|
||||
**Tablist name formatting**:
|
||||
|
||||
| Identifier | Description |
|
||||
|---------------------------|------------------------------------------------------------------|
|
||||
| `%tab_tabprefix%` | Player's current tabprefix with placeholders parsed. |
|
||||
|
||||
+1
-17
@@ -4,7 +4,7 @@ plugins {
|
||||
|
||||
allprojects {
|
||||
group = "me.neznamy"
|
||||
version = "6.0.2"
|
||||
version = "6.1.0-SNAPSHOT"
|
||||
description = "An all-in-one solution that works"
|
||||
|
||||
ext.set("id", "tab")
|
||||
@@ -20,27 +20,11 @@ val platformPaths = setOf(
|
||||
":bukkit:paper_1_21_4",
|
||||
":bukkit:paper_1_21_9",
|
||||
":bukkit:paper_1_21_11",
|
||||
":bukkit:v1_7_R1",
|
||||
":bukkit:v1_7_R2",
|
||||
":bukkit:v1_7_R3",
|
||||
":bukkit:v1_7_R4",
|
||||
":bukkit:v1_8_R1",
|
||||
":bukkit:v1_8_R2",
|
||||
":bukkit:v1_8_R3",
|
||||
":bukkit:v1_9_R1",
|
||||
":bukkit:v1_9_R2",
|
||||
":bukkit:v1_10_R1",
|
||||
":bukkit:v1_11_R1",
|
||||
":bukkit:v1_12_R1",
|
||||
":bukkit:v1_13_R1",
|
||||
":bukkit:v1_13_R2",
|
||||
":bukkit:v1_14_R1",
|
||||
":bukkit:v1_15_R1",
|
||||
":bukkit:v1_16_R1",
|
||||
":bukkit:v1_16_R2",
|
||||
":bukkit:v1_16_R3",
|
||||
":bukkit:v1_17_R1",
|
||||
":bukkit:v1_18_R1",
|
||||
":bukkit:v1_18_R2",
|
||||
":bukkit:v1_19_R1",
|
||||
":bukkit:v1_19_R2",
|
||||
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
package me.neznamy.tab.platforms.bukkit.paper_1_21_11;
|
||||
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import net.minecraft.network.chat.numbers.FixedFormat;
|
||||
import net.minecraft.network.protocol.game.ClientboundSetObjectivePacket;
|
||||
import net.minecraft.world.scores.criteria.ObjectiveCriteria;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Scoreboard override for Canvas (Folia fork) that implements scoreboards with
|
||||
* new checks in NMS setters since 26.1.2.
|
||||
*/
|
||||
public class CanvasPacketScoreboard extends NMSPacketScoreboard {
|
||||
|
||||
/**
|
||||
* Constructs new instance with given player.
|
||||
*
|
||||
* @param player
|
||||
* Player this scoreboard will belong to
|
||||
*/
|
||||
public CanvasPacketScoreboard(@NotNull BukkitTabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the objective by creating a new one instead of updating the existing one,
|
||||
* which would require sync access on Canvas.
|
||||
*
|
||||
* @param objective
|
||||
* Objective to update
|
||||
*/
|
||||
@Override
|
||||
public void updateObjective(@NonNull Objective objective) {
|
||||
net.minecraft.world.scores.Objective obj = new net.minecraft.world.scores.Objective(
|
||||
dummyScoreboard,
|
||||
objective.getName(),
|
||||
ObjectiveCriteria.DUMMY,
|
||||
objective.getTitle().convert(),
|
||||
ObjectiveCriteria.RenderType.values()[objective.getHealthDisplay().ordinal()],
|
||||
false,
|
||||
objective.getNumberFormat() == null ? null : objective.getNumberFormat().toFixedFormat(FixedFormat::new)
|
||||
);
|
||||
objective.setPlatformObjective(obj);
|
||||
sendPacket(new ClientboundSetObjectivePacket(obj, ObjectiveAction.UPDATE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerTeam(@NonNull Team team) {
|
||||
player.getPlatform().runSyncGlobal(() -> super.registerTeam(team));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterTeam(@NonNull Team team) {
|
||||
// This one too, to avoid register+unregister causing unregister to run first
|
||||
player.getPlatform().runSyncGlobal(() -> super.unregisterTeam(team));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateTeam(@NonNull Team team) {
|
||||
player.getPlatform().runSyncGlobal(() -> super.updateTeam(team));
|
||||
}
|
||||
}
|
||||
+10
-2
@@ -8,23 +8,31 @@ import me.neznamy.tab.platforms.bukkit.provider.ImplementationProvider;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import me.neznamy.tab.shared.platform.TabListEntryTracker;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.bukkit.craftbukkit.entity.CraftPlayer;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Implementation provider using direct Mojang-mapped NMS code for paper 1.21.11.
|
||||
* Implementation provider using direct Mojang-mapped NMS code for Paper 1.21.11 - 26.1.2.
|
||||
*/
|
||||
@Getter
|
||||
public class NMSImplementationProvider implements ImplementationProvider {
|
||||
|
||||
/** Flag tracking if this server is Canvas 26.1.2+ with new scoreboard checks */
|
||||
private final boolean isCanvas = ReflectionUtils.classExists("io.canvasmc.canvas.world.scores.TeamData");
|
||||
|
||||
@NotNull
|
||||
private final ComponentConverter<?> componentConverter = new NMSComponentConverter();
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Scoreboard newScoreboard(@NotNull BukkitTabPlayer player) {
|
||||
return new NMSPacketScoreboard(player);
|
||||
if (isCanvas) {
|
||||
return new CanvasPacketScoreboard(player);
|
||||
} else {
|
||||
return new NMSPacketScoreboard(player);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+2
-2
@@ -30,7 +30,7 @@ public class NMSPacketScoreboard 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();
|
||||
protected static final Scoreboard dummyScoreboard = new Scoreboard();
|
||||
private static final Field players = ReflectionUtils.getOnlyField(ClientboundSetPlayerTeamPacket.class, Collection.class);
|
||||
|
||||
/**
|
||||
@@ -170,7 +170,7 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
|
||||
* @param packet
|
||||
* Packet to send
|
||||
*/
|
||||
private void sendPacket(@NotNull Packet<?> packet) {
|
||||
protected void sendPacket(@NotNull Packet<?> packet) {
|
||||
((CraftPlayer)player.getPlayer()).getHandle().connection.send(packet);
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@ public class BukkitTAB extends JavaPlugin {
|
||||
} catch (IllegalStateException e) {
|
||||
Bukkit.getConsoleSender().sendMessage("§c[TAB] ================================================================================");
|
||||
Bukkit.getConsoleSender().sendMessage("§c[TAB] Your server version (" + Bukkit.getBukkitVersion() + ") is not supported.");
|
||||
Bukkit.getConsoleSender().sendMessage("§c[TAB] This jar only supports 1.7.10, 1.8.8, 1.9.4 - 1.15.x, 1.16.5, 1.17.1, 1.18.2 - 26.1.2");
|
||||
Bukkit.getConsoleSender().sendMessage("§c[TAB] This jar only supports 1.7.10, 1.8.8, 1.12.2, 1.16.5, 1.17.1 and 1.18.2 - 26.1.2");
|
||||
Bukkit.getConsoleSender().sendMessage("§c[TAB] If you just updated to a new Minecraft version, check for TAB updates.");
|
||||
Bukkit.getConsoleSender().sendMessage("§c[TAB] If you are using an unsupported 1.x version, use an older version of TAB (latest TAB 5.x supports all MC 1.x versions).");
|
||||
Bukkit.getConsoleSender().sendMessage("§c[TAB] Thrown error message: " + e.getMessage());
|
||||
|
||||
@@ -12,6 +12,9 @@ import org.jetbrains.annotations.Nullable;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Class for detecting server software and version and finding available NMS implementation(s).
|
||||
*/
|
||||
@Getter
|
||||
public class ServerVersionInfo {
|
||||
|
||||
@@ -23,6 +26,7 @@ public class ServerVersionInfo {
|
||||
@NotNull
|
||||
private final String minecraftVersion;
|
||||
|
||||
/** Name of the server software, either "Paper" or "Spigot" */
|
||||
@NotNull
|
||||
private final String serverName;
|
||||
|
||||
@@ -34,6 +38,10 @@ public class ServerVersionInfo {
|
||||
@Setter
|
||||
private ImplementationProvider implementationProvider;
|
||||
|
||||
/** Name of the implementation package (such as v1_7_R4 or paper_1_20_5) */
|
||||
@Nullable
|
||||
private String implementationPackage;
|
||||
|
||||
/**
|
||||
* Constructs new instance and detects server version info.
|
||||
*/
|
||||
@@ -72,10 +80,11 @@ public class ServerVersionInfo {
|
||||
// Paper <1.20.5 or Spigot 1.x
|
||||
try {
|
||||
// Does not actually support flat 1.19, but whatever, no one is using it anyway
|
||||
implementationPackage = serverPackage;
|
||||
return (ImplementationProvider) Class.forName("me.neznamy.tab.platforms.bukkit." + serverPackage + ".NMSImplementationProvider").getConstructor().newInstance();
|
||||
} catch (ReflectiveOperationException ignored) {
|
||||
throw new IllegalStateException(String.format(
|
||||
"Your server version (%s - %s) is no longer supported.",
|
||||
"Your server version (%s - %s) is no longer supported. Please use an older version of TAB.",
|
||||
minecraftVersion, serverPackage
|
||||
));
|
||||
}
|
||||
@@ -108,18 +117,19 @@ public class ServerVersionInfo {
|
||||
|
||||
if (serverVersion == ProtocolVersion.UNKNOWN) {
|
||||
throw new IllegalStateException(String.format(
|
||||
"Unknown server version (%s), can not find implementation.",
|
||||
"Unknown server version (%s), cannot find implementation.",
|
||||
minecraftVersion
|
||||
));
|
||||
}
|
||||
String implementation = (serverName.equals("Paper") ? paperVersions : spigotVersions).get(serverVersion);
|
||||
if (implementation == null) {
|
||||
throw new IllegalStateException(String.format(
|
||||
"Your server version (%s %s) is no longer supported.",
|
||||
"Your server version (%s %s) is no longer supported. Please use an older version of TAB.",
|
||||
serverName, minecraftVersion
|
||||
));
|
||||
}
|
||||
try {
|
||||
implementationPackage = implementation;
|
||||
return (ImplementationProvider) Class.forName("me.neznamy.tab.platforms.bukkit." + implementation + ".NMSImplementationProvider").getConstructor().newInstance();
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new IllegalStateException(String.format(
|
||||
|
||||
@@ -238,7 +238,9 @@ public class BukkitPlatform implements BackendPlatform {
|
||||
Metrics metrics = new Metrics(plugin, TabConstants.BSTATS_PLUGIN_ID_BUKKIT);
|
||||
metrics.addCustomChart(new SimplePie(TabConstants.MetricsChart.PERMISSION_SYSTEM,
|
||||
() -> TAB.getInstance().getGroupManager().getPermissionPlugin()));
|
||||
metrics.addCustomChart(new SimplePie("tab_6_0_0_servers", serverVersionInfo.getServerVersion()::getFriendlyName));
|
||||
metrics.addCustomChart(new SimplePie("tab_6_1_0_servers",
|
||||
() -> serverVersionInfo.getServerName() + " " + serverVersionInfo.getServerVersion().getFriendlyName()));
|
||||
metrics.addCustomChart(new SimplePie("tab_6_1_0_package", serverVersionInfo::getImplementationPackage));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -369,7 +371,7 @@ public class BukkitPlatform implements BackendPlatform {
|
||||
* Runs task in the main thread for given entity.
|
||||
*
|
||||
* @param entity
|
||||
* Entity's main thread
|
||||
* Entity to run the task for
|
||||
* @param task
|
||||
* Task to run
|
||||
*/
|
||||
@@ -377,6 +379,16 @@ public class BukkitPlatform implements BackendPlatform {
|
||||
Bukkit.getScheduler().runTask(plugin, task);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs task in the global tick thread.
|
||||
*
|
||||
* @param task
|
||||
* Task to run
|
||||
*/
|
||||
public void runSyncGlobal(@NotNull Runnable task) {
|
||||
Bukkit.getScheduler().runTask(plugin, task);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts component to string using bukkit RGB format if supported by the server.
|
||||
* If not, closest legacy color is used instead.
|
||||
|
||||
@@ -18,6 +18,7 @@ import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.DecimalFormatSymbols;
|
||||
import java.util.function.Consumer;
|
||||
@@ -28,14 +29,25 @@ import java.util.function.Function;
|
||||
*/
|
||||
public class FoliaPlatform extends BukkitPlatform {
|
||||
|
||||
/** Global tick thread scheduler */
|
||||
@NotNull
|
||||
private final Object globalScheduler;
|
||||
|
||||
/** FoliaGlobalRegionScheduler#execute(Plugin, Runnable) method */
|
||||
private final Method globalScheduler_execute;
|
||||
|
||||
/**
|
||||
* Constructs new instance with given plugin.
|
||||
*
|
||||
* @param plugin
|
||||
* Plugin
|
||||
*/
|
||||
@SneakyThrows
|
||||
@SuppressWarnings("JavaReflectionMemberAccess")
|
||||
public FoliaPlatform(@NotNull JavaPlugin plugin) {
|
||||
super(plugin);
|
||||
globalScheduler = Bukkit.class.getMethod("getGlobalRegionScheduler").invoke(null);
|
||||
globalScheduler_execute = globalScheduler.getClass().getMethod("execute", Plugin.class, Runnable.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -138,9 +150,25 @@ public class FoliaPlatform extends BukkitPlatform {
|
||||
@SneakyThrows
|
||||
@SuppressWarnings("JavaReflectionMemberAccess")
|
||||
public void runSync(@NotNull Entity entity, @NotNull Runnable task) {
|
||||
if (!getPlugin().isEnabled()) return; // Server shutdown, no one cares anymore, everyone is about to be kicked
|
||||
Object entityScheduler = Entity.class.getMethod("getScheduler").invoke(entity);
|
||||
Consumer<?> consumer = $ -> task.run(); // Reflection and lambdas don't go together
|
||||
entityScheduler.getClass().getMethod("run", Plugin.class, Consumer.class, Runnable.class)
|
||||
.invoke(entityScheduler, getPlugin(), consumer, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs task in global tick thread. It's using reflection, because
|
||||
* Folia uses Java 17 while TAB maintains Java 8 compatibility for compatibility
|
||||
* with MC versions older than their player base.
|
||||
*
|
||||
* @param task
|
||||
* Task to run
|
||||
*/
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void runSyncGlobal(@NotNull Runnable task) {
|
||||
if (!getPlugin().isEnabled()) return; // Server shutdown, no one cares anymore, everyone is about to be kicked
|
||||
globalScheduler_execute.invoke(globalScheduler, getPlugin(), task);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
dependencies {
|
||||
implementation(projects.bukkit)
|
||||
compileOnly("org.bukkit:craftbukkit:1.10.2-R0.1-SNAPSHOT")
|
||||
}
|
||||
-62
@@ -1,62 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.v1_10_R1;
|
||||
|
||||
import me.neznamy.tab.shared.chat.TabStyle;
|
||||
import me.neznamy.tab.shared.chat.component.object.TabAtlasSprite;
|
||||
import me.neznamy.tab.shared.chat.component.object.TabObjectComponent;
|
||||
import me.neznamy.tab.platforms.bukkit.provider.ComponentConverter;
|
||||
import me.neznamy.tab.shared.chat.component.object.TabPlayerSprite;
|
||||
import net.minecraft.server.v1_10_R1.*;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Component converter using direct NMS code.
|
||||
*/
|
||||
public class NMSComponentConverter extends ComponentConverter<IChatBaseComponent> {
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public IChatBaseComponent newTextComponent(@NotNull String text) {
|
||||
return new ChatComponentText(text);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public IChatBaseComponent newTranslatableComponent(@NotNull String key) {
|
||||
return new ChatMessage(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public IChatBaseComponent newKeybindComponent(@NotNull String keybind) {
|
||||
return new ChatComponentText("<Keybind components were added in 1.12>");
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public IChatBaseComponent newObjectComponent(@NotNull TabAtlasSprite sprite) {
|
||||
return new ChatComponentText(TabObjectComponent.ERROR_MESSAGE);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public IChatBaseComponent newObjectComponent(@NotNull TabPlayerSprite sprite) {
|
||||
return new ChatComponentText(TabObjectComponent.ERROR_MESSAGE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyStyle(@NotNull IChatBaseComponent nmsComponent, @NotNull TabStyle modifier) {
|
||||
nmsComponent.setChatModifier(new ChatModifier()
|
||||
.setColor(modifier.getColor() == null ? null : EnumChatFormat.valueOf(modifier.getColor().getLegacyColor().name()))
|
||||
.setBold(modifier.getBold())
|
||||
.setItalic(modifier.getItalic())
|
||||
.setUnderline(modifier.getUnderlined())
|
||||
.setStrikethrough(modifier.getStrikethrough())
|
||||
.setRandom(modifier.getObfuscated())
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addSibling(@NotNull IChatBaseComponent parent, @NotNull IChatBaseComponent child) {
|
||||
parent.addSibling(child);
|
||||
}
|
||||
}
|
||||
-52
@@ -1,52 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.v1_10_R1;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import lombok.Getter;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.provider.ComponentConverter;
|
||||
import me.neznamy.tab.platforms.bukkit.provider.ImplementationProvider;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import me.neznamy.tab.shared.platform.TabListEntryTracker;
|
||||
import org.bukkit.craftbukkit.v1_10_R1.entity.CraftPlayer;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Implementation provider using direct NMS code for 1.10.x.
|
||||
*/
|
||||
@Getter
|
||||
public class NMSImplementationProvider implements ImplementationProvider {
|
||||
|
||||
@NotNull
|
||||
private final ComponentConverter<?> componentConverter = new NMSComponentConverter();
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Scoreboard newScoreboard(@NotNull BukkitTabPlayer player) {
|
||||
return new NMSPacketScoreboard(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public TabList newTabList(@NotNull BukkitTabPlayer player) {
|
||||
return new NMSPacketTabList(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Channel getChannel(@NotNull Player player) {
|
||||
return ((CraftPlayer)player).getHandle().playerConnection.networkManager.channel;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public TabListEntryTracker newTabListEntryTracker(@NotNull Player player) {
|
||||
return new NMSTabListEntryTracker(getChannel(player));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPing(@NotNull BukkitTabPlayer player) {
|
||||
return ((CraftPlayer)player.getPlayer()).getHandle().ping;
|
||||
}
|
||||
}
|
||||
-164
@@ -1,164 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.v1_10_R1;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.shared.Limitations;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.platform.decorators.SafeScoreboard;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import net.minecraft.server.v1_10_R1.*;
|
||||
import org.bukkit.craftbukkit.v1_10_R1.entity.CraftPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* Scoreboard implementation using direct NMS code.
|
||||
*/
|
||||
public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
|
||||
|
||||
private static final ScoreboardTeamBase.EnumNameTagVisibility[] visibilities = ScoreboardTeamBase.EnumNameTagVisibility.values();
|
||||
private static final ScoreboardTeamBase.EnumTeamPush[] collisions = ScoreboardTeamBase.EnumTeamPush.values();
|
||||
private static final Scoreboard dummyScoreboard = new Scoreboard();
|
||||
private static final IScoreboardCriteria INTEGER = new ScoreboardBaseCriteria("dummy");
|
||||
private static final IScoreboardCriteria HEARTS = new ScoreboardHealthCriteria("health");
|
||||
|
||||
private static final Field TeamPacket_NAME = ReflectionUtils.getFields(PacketPlayOutScoreboardTeam.class, String.class).get(0);
|
||||
private static final Field TeamPacket_ACTION = ReflectionUtils.getInstanceFields(PacketPlayOutScoreboardTeam.class, int.class).get(1);
|
||||
private static final Field TeamPacket_PLAYERS = ReflectionUtils.getOnlyField(PacketPlayOutScoreboardTeam.class, Collection.class);
|
||||
|
||||
private static final Field Objective_OBJECTIVE_NAME = ReflectionUtils.getFields(PacketPlayOutScoreboardObjective.class, String.class).get(0);
|
||||
private static final Field Objective_METHOD = ReflectionUtils.getOnlyField(PacketPlayOutScoreboardObjective.class, int.class);
|
||||
|
||||
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);
|
||||
|
||||
/**
|
||||
* Constructs new instance with given player.
|
||||
*
|
||||
* @param player
|
||||
* Player this scoreboard will belong to
|
||||
*/
|
||||
public NMSPacketScoreboard(@NotNull BukkitTabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerObjective(@NonNull Objective objective) {
|
||||
IScoreboardCriteria healthDisplay = objective.getHealthDisplay() == HealthDisplay.INTEGER ? INTEGER : HEARTS;
|
||||
ScoreboardObjective obj = new ScoreboardObjective(dummyScoreboard, objective.getName(), healthDisplay);
|
||||
obj.setDisplayName(maybeCut(objective.getTitle().toLegacyText(), Limitations.SCOREBOARD_TITLE_PRE_1_13));
|
||||
objective.setPlatformObjective(obj);
|
||||
sendPacket(new PacketPlayOutScoreboardObjective(obj, ObjectiveAction.REGISTER));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDisplaySlot(@NonNull Objective objective) {
|
||||
sendPacket(new PacketPlayOutScoreboardDisplayObjective(objective.getDisplaySlot().ordinal(), (ScoreboardObjective) objective.getPlatformObjective()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterObjective(@NonNull Objective objective) {
|
||||
sendPacket(new PacketPlayOutScoreboardObjective((ScoreboardObjective) objective.getPlatformObjective(), ObjectiveAction.UNREGISTER));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateObjective(@NonNull Objective objective) {
|
||||
IScoreboardCriteria healthDisplay = objective.getHealthDisplay() == HealthDisplay.INTEGER ? INTEGER : HEARTS;
|
||||
ScoreboardObjective obj = new ScoreboardObjective(dummyScoreboard, objective.getName(), healthDisplay);
|
||||
obj.setDisplayName(maybeCut(objective.getTitle().toLegacyText(), Limitations.SCOREBOARD_TITLE_PRE_1_13));
|
||||
sendPacket(new PacketPlayOutScoreboardObjective(obj, ObjectiveAction.UPDATE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setScore(@NonNull Score score) {
|
||||
ScoreboardScore s = new ScoreboardScore(dummyScoreboard, (ScoreboardObjective) score.getObjective().getPlatformObjective(), score.getHolder());
|
||||
s.setScore(score.getValue());
|
||||
sendPacket(new PacketPlayOutScoreboardScore(s));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeScore(@NonNull Score score) {
|
||||
sendPacket(new PacketPlayOutScoreboardScore(score.getHolder()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Object createTeam(@NonNull String name) {
|
||||
return new ScoreboardTeam(dummyScoreboard, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerTeam(@NonNull Team team) {
|
||||
updateTeamProperties(team);
|
||||
ScoreboardTeam t = (ScoreboardTeam) team.getPlatformTeam();
|
||||
t.getPlayerNameSet().addAll(team.getPlayers());
|
||||
sendPacket(new PacketPlayOutScoreboardTeam(t, TeamAction.CREATE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterTeam(@NonNull Team team) {
|
||||
sendPacket(new PacketPlayOutScoreboardTeam((ScoreboardTeam) team.getPlatformTeam(), TeamAction.REMOVE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateTeam(@NonNull Team team) {
|
||||
updateTeamProperties(team);
|
||||
sendPacket(new PacketPlayOutScoreboardTeam((ScoreboardTeam) team.getPlatformTeam(), TeamAction.UPDATE));
|
||||
}
|
||||
|
||||
private void updateTeamProperties(@NonNull Team team) {
|
||||
ScoreboardTeam t = (ScoreboardTeam) team.getPlatformTeam();
|
||||
t.setAllowFriendlyFire((team.getOptions() & 0x01) != 0);
|
||||
t.setCanSeeFriendlyInvisibles((team.getOptions() & 0x02) != 0);
|
||||
t.setNameTagVisibility(visibilities[team.getVisibility().ordinal()]);
|
||||
t.setCollisionRule(collisions[team.getCollision().ordinal()]);
|
||||
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")
|
||||
@NotNull
|
||||
public Object onPacketSend(@NonNull Object packet) {
|
||||
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) {
|
||||
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));
|
||||
}
|
||||
}
|
||||
return packet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the packet to the player.
|
||||
*
|
||||
* @param packet
|
||||
* Packet to send
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
-232
@@ -1,232 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.v1_10_R1;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import com.mojang.authlib.properties.Property;
|
||||
import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.shared.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.server.v1_10_R1.*;
|
||||
import net.minecraft.server.v1_10_R1.PacketPlayOutPlayerInfo.EnumPlayerInfoAction;
|
||||
import org.bukkit.craftbukkit.v1_10_R1.entity.CraftPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* TabList implementation using direct NMS code.
|
||||
*/
|
||||
public class NMSPacketTabList extends TrackedTabList<BukkitTabPlayer> {
|
||||
|
||||
// PlayerInfoData subclass is broken, using reflection to get it
|
||||
private static final Class<?> PlayerInfoData = Arrays.stream(PacketPlayOutPlayerInfo.class.getDeclaredClasses()).filter(c -> !c.isEnum() && c.getConstructors().length > 0).findFirst().get();
|
||||
private static final Constructor<?> newPlayerInfoData = ReflectionUtils.getOnlyConstructor(PlayerInfoData);
|
||||
|
||||
private static final Field ACTION = ReflectionUtils.getOnlyField(PacketPlayOutPlayerInfo.class, EnumPlayerInfoAction.class);
|
||||
private static final Field PLAYERS = ReflectionUtils.getOnlyField(PacketPlayOutPlayerInfo.class, List.class);
|
||||
|
||||
private static final Field PlayerInfoData_Profile = ReflectionUtils.getOnlyField(PlayerInfoData, GameProfile.class);
|
||||
private static final Field PlayerInfoData_Latency = ReflectionUtils.getFields(PlayerInfoData, int.class).get(0);
|
||||
private static final Field PlayerInfoData_DisplayName = ReflectionUtils.getOnlyField(PlayerInfoData, IChatBaseComponent.class);
|
||||
private static final Field PlayerInfoData_GameMode = ReflectionUtils.getOnlyField(PlayerInfoData, EnumGamemode.class);
|
||||
|
||||
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);
|
||||
|
||||
/**
|
||||
* Constructs new instance.
|
||||
*
|
||||
* @param player
|
||||
* Player this tablist will belong to
|
||||
*/
|
||||
public NMSPacketTabList(@NotNull BukkitTabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void removeEntry(@NonNull UUID entry) {
|
||||
sendPacket(EnumPlayerInfoAction.REMOVE_PLAYER, entry, "", null, 0, 0, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDisplayName0(@NonNull UUID entry, @Nullable TabComponent displayName) {
|
||||
sendPacket(EnumPlayerInfoAction.UPDATE_DISPLAY_NAME, entry, "", null, 0, 0, displayName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateLatency(@NonNull UUID entry, int latency) {
|
||||
sendPacket(EnumPlayerInfoAction.UPDATE_LATENCY, entry, "", null, latency, 0, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateGameMode(@NonNull UUID entry, int gameMode) {
|
||||
sendPacket(EnumPlayerInfoAction.UPDATE_GAME_MODE, entry, "", null, 0, gameMode, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateListed(@NonNull UUID entry, boolean listed) {
|
||||
// Added in 1.19.3
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateListOrder(@NonNull UUID entry, int listOrder) {
|
||||
// Added in 1.21.2
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateHat(@NonNull UUID entry, boolean showHat) {
|
||||
// Added in 1.21.4
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addEntry0(@NonNull Entry entry) {
|
||||
sendPacket(EnumPlayerInfoAction.ADD_PLAYER, entry.getUniqueId(), entry.getName(), entry.getSkin(), entry.getLatency(),
|
||||
entry.getGameMode(), entry.getDisplayName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPlayerListHeaderFooter0(@NonNull TabComponent header, @NonNull TabComponent footer) {
|
||||
sendPacket(newHeaderFooter(header, footer));
|
||||
}
|
||||
|
||||
@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());
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@SneakyThrows
|
||||
private PacketPlayOutPlayerListHeaderFooter newHeaderFooter(@NotNull TabComponent header, @NonNull TabComponent footer) {
|
||||
PacketPlayOutPlayerListHeaderFooter packet = new PacketPlayOutPlayerListHeaderFooter(header.convert());
|
||||
FOOTER.set(packet, footer.convert());
|
||||
return packet;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
@NotNull
|
||||
@SuppressWarnings("unchecked")
|
||||
public Object onPacketSend(@NonNull Object packet) {
|
||||
if (packet instanceof PacketPlayOutPlayerListHeaderFooter) {
|
||||
PacketPlayOutPlayerListHeaderFooter tablist = (PacketPlayOutPlayerListHeaderFooter) packet;
|
||||
if (header == null || footer == null) return packet;
|
||||
IChatBaseComponent header = (IChatBaseComponent) HEADER.get(tablist);
|
||||
IChatBaseComponent footer = (IChatBaseComponent) FOOTER.get(tablist);
|
||||
if (header != this.header.convert() || footer != this.footer.convert()) {
|
||||
return newHeaderFooter(this.header, this.footer);
|
||||
}
|
||||
}
|
||||
if (!(packet instanceof PacketPlayOutPlayerInfo)) return packet;
|
||||
PacketPlayOutPlayerInfo info = (PacketPlayOutPlayerInfo) packet;
|
||||
EnumPlayerInfoAction action = (EnumPlayerInfoAction) ACTION.get(info);
|
||||
List<Object> updatedList = new ArrayList<>();
|
||||
boolean rewritePacket = false;
|
||||
for (Object nmsData : (List<Object>) PLAYERS.get(info)) {
|
||||
boolean rewriteEntry = false;
|
||||
GameProfile profile = (GameProfile) PlayerInfoData_Profile.get(nmsData);
|
||||
UUID id = profile.getId();
|
||||
IChatBaseComponent displayName = (IChatBaseComponent) PlayerInfoData_DisplayName.get(nmsData);
|
||||
int latency = PlayerInfoData_Latency.getInt(nmsData);
|
||||
int gameMode = ((EnumGamemode)PlayerInfoData_GameMode.get(nmsData)).getId();
|
||||
if (action == EnumPlayerInfoAction.UPDATE_DISPLAY_NAME || action == EnumPlayerInfoAction.ADD_PLAYER) {
|
||||
TabComponent forcedDisplayName = getForcedDisplayNames().get(id);
|
||||
if (forcedDisplayName != null && forcedDisplayName.convert() != displayName) {
|
||||
displayName = forcedDisplayName.convert();
|
||||
rewriteEntry = rewritePacket = true;
|
||||
}
|
||||
}
|
||||
if (action == EnumPlayerInfoAction.UPDATE_GAME_MODE || action == EnumPlayerInfoAction.ADD_PLAYER) {
|
||||
if (getBlockedSpectators().contains(id) && gameMode == 3) {
|
||||
gameMode = 0;
|
||||
rewriteEntry = rewritePacket = true;
|
||||
}
|
||||
}
|
||||
if (action == EnumPlayerInfoAction.UPDATE_LATENCY || action == EnumPlayerInfoAction.ADD_PLAYER) {
|
||||
if (getForcedLatency() != null) {
|
||||
latency = getForcedLatency();
|
||||
rewriteEntry = rewritePacket = true;
|
||||
}
|
||||
}
|
||||
if (action == EnumPlayerInfoAction.ADD_PLAYER) {
|
||||
TAB.getInstance().getFeatureManager().onEntryAdd(player, id, profile.getName());
|
||||
}
|
||||
updatedList.add(rewriteEntry ? newPlayerInfoData(
|
||||
(PacketPlayOutPlayerInfo) packet,
|
||||
profile,
|
||||
latency,
|
||||
EnumGamemode.getById(gameMode),
|
||||
displayName
|
||||
) : nmsData);
|
||||
}
|
||||
if (rewritePacket) {
|
||||
PacketPlayOutPlayerInfo newPacket = new PacketPlayOutPlayerInfo(action, Collections.emptyList());
|
||||
PLAYERS.set(newPacket, updatedList);
|
||||
return newPacket;
|
||||
}
|
||||
return packet;
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private void sendPacket(@NonNull EnumPlayerInfoAction action, @NonNull UUID id, @NonNull String name,
|
||||
@Nullable Skin skin, int latency, int gameMode, @Nullable TabComponent displayName) {
|
||||
PacketPlayOutPlayerInfo packet = new PacketPlayOutPlayerInfo(action);
|
||||
PLAYERS.set(packet, Collections.singletonList(newPlayerInfoData(
|
||||
packet,
|
||||
createProfile(id, name, skin),
|
||||
latency,
|
||||
EnumGamemode.getById(gameMode),
|
||||
displayName == null ? null : displayName.convert())
|
||||
));
|
||||
sendPacket(packet);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@SneakyThrows
|
||||
private Object newPlayerInfoData(@NonNull PacketPlayOutPlayerInfo packet, @NonNull GameProfile profile,
|
||||
int latency, @NonNull EnumGamemode gameMode, @Nullable IChatBaseComponent displayName) {
|
||||
return newPlayerInfoData.newInstance(packet, profile, latency, gameMode, displayName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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().playerConnection.sendPacket(packet);
|
||||
}
|
||||
}
|
||||
-48
@@ -1,48 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.v1_10_R1;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import io.netty.channel.Channel;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.shared.platform.NettyTabListEntryTracker;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import net.minecraft.server.v1_10_R1.PacketPlayOutPlayerInfo;
|
||||
import net.minecraft.server.v1_10_R1.PacketPlayOutPlayerInfo.EnumPlayerInfoAction;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Implementation of TabListEntryTracker.
|
||||
*/
|
||||
public class NMSTabListEntryTracker extends NettyTabListEntryTracker {
|
||||
|
||||
private static final Field ACTION = ReflectionUtils.getOnlyField(PacketPlayOutPlayerInfo.class, EnumPlayerInfoAction.class);
|
||||
private static final Field PLAYERS = ReflectionUtils.getOnlyField(PacketPlayOutPlayerInfo.class, List.class);
|
||||
private static final Class<?> PlayerInfoData = Arrays.stream(PacketPlayOutPlayerInfo.class.getDeclaredClasses()).filter(c -> !c.isEnum() && c.getConstructors().length > 0).findFirst().get();
|
||||
private static final Field PlayerInfoData_Profile = ReflectionUtils.getOnlyField(PlayerInfoData, GameProfile.class);
|
||||
|
||||
public NMSTabListEntryTracker(@NotNull Channel channel) {
|
||||
super(channel);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
@SuppressWarnings("unchecked")
|
||||
public void onPacketSend(@NotNull Object packet) {
|
||||
if (packet instanceof PacketPlayOutPlayerInfo) {
|
||||
PacketPlayOutPlayerInfo info = (PacketPlayOutPlayerInfo) packet;
|
||||
EnumPlayerInfoAction action = (EnumPlayerInfoAction) ACTION.get(info);
|
||||
for (Object nmsData : (List<Object>) PLAYERS.get(info)) {
|
||||
GameProfile profile = (GameProfile) PlayerInfoData_Profile.get(nmsData);
|
||||
if (action == EnumPlayerInfoAction.ADD_PLAYER) {
|
||||
tablistEntries.add(profile.getId());
|
||||
}
|
||||
if (action == EnumPlayerInfoAction.REMOVE_PLAYER) {
|
||||
tablistEntries.remove(profile.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
dependencies {
|
||||
implementation(projects.bukkit)
|
||||
compileOnly("org.bukkit:craftbukkit:1.11.2-R0.1-SNAPSHOT")
|
||||
}
|
||||
-62
@@ -1,62 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.v1_11_R1;
|
||||
|
||||
import me.neznamy.tab.shared.chat.TabStyle;
|
||||
import me.neznamy.tab.shared.chat.component.object.TabAtlasSprite;
|
||||
import me.neznamy.tab.shared.chat.component.object.TabObjectComponent;
|
||||
import me.neznamy.tab.platforms.bukkit.provider.ComponentConverter;
|
||||
import me.neznamy.tab.shared.chat.component.object.TabPlayerSprite;
|
||||
import net.minecraft.server.v1_11_R1.*;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Component converter using direct NMS code.
|
||||
*/
|
||||
public class NMSComponentConverter extends ComponentConverter<IChatBaseComponent> {
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public IChatBaseComponent newTextComponent(@NotNull String text) {
|
||||
return new ChatComponentText(text);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public IChatBaseComponent newTranslatableComponent(@NotNull String key) {
|
||||
return new ChatMessage(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public IChatBaseComponent newKeybindComponent(@NotNull String keybind) {
|
||||
return new ChatComponentText("<Keybind components were added in 1.12>");
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public IChatBaseComponent newObjectComponent(@NotNull TabAtlasSprite sprite) {
|
||||
return new ChatComponentText(TabObjectComponent.ERROR_MESSAGE);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public IChatBaseComponent newObjectComponent(@NotNull TabPlayerSprite sprite) {
|
||||
return new ChatComponentText(TabObjectComponent.ERROR_MESSAGE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyStyle(@NotNull IChatBaseComponent nmsComponent, @NotNull TabStyle modifier) {
|
||||
nmsComponent.setChatModifier(new ChatModifier()
|
||||
.setColor(modifier.getColor() == null ? null : EnumChatFormat.valueOf(modifier.getColor().getLegacyColor().name()))
|
||||
.setBold(modifier.getBold())
|
||||
.setItalic(modifier.getItalic())
|
||||
.setUnderline(modifier.getUnderlined())
|
||||
.setStrikethrough(modifier.getStrikethrough())
|
||||
.setRandom(modifier.getObfuscated())
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addSibling(@NotNull IChatBaseComponent parent, @NotNull IChatBaseComponent child) {
|
||||
parent.addSibling(child);
|
||||
}
|
||||
}
|
||||
-52
@@ -1,52 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.v1_11_R1;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import lombok.Getter;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.provider.ComponentConverter;
|
||||
import me.neznamy.tab.platforms.bukkit.provider.ImplementationProvider;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import me.neznamy.tab.shared.platform.TabListEntryTracker;
|
||||
import org.bukkit.craftbukkit.v1_11_R1.entity.CraftPlayer;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Implementation provider using direct NMS code for 1.11.x.
|
||||
*/
|
||||
@Getter
|
||||
public class NMSImplementationProvider implements ImplementationProvider {
|
||||
|
||||
@NotNull
|
||||
private final ComponentConverter<?> componentConverter = new NMSComponentConverter();
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Scoreboard newScoreboard(@NotNull BukkitTabPlayer player) {
|
||||
return new NMSPacketScoreboard(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public TabList newTabList(@NotNull BukkitTabPlayer player) {
|
||||
return new NMSPacketTabList(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Channel getChannel(@NotNull Player player) {
|
||||
return ((CraftPlayer)player).getHandle().playerConnection.networkManager.channel;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public TabListEntryTracker newTabListEntryTracker(@NotNull Player player) {
|
||||
return new NMSTabListEntryTracker(getChannel(player));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPing(@NotNull BukkitTabPlayer player) {
|
||||
return ((CraftPlayer)player.getPlayer()).getHandle().ping;
|
||||
}
|
||||
}
|
||||
-164
@@ -1,164 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.v1_11_R1;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.shared.Limitations;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.platform.decorators.SafeScoreboard;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import net.minecraft.server.v1_11_R1.*;
|
||||
import org.bukkit.craftbukkit.v1_11_R1.entity.CraftPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* Scoreboard implementation using direct NMS code.
|
||||
*/
|
||||
public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
|
||||
|
||||
private static final ScoreboardTeamBase.EnumNameTagVisibility[] visibilities = ScoreboardTeamBase.EnumNameTagVisibility.values();
|
||||
private static final ScoreboardTeamBase.EnumTeamPush[] collisions = ScoreboardTeamBase.EnumTeamPush.values();
|
||||
private static final Scoreboard dummyScoreboard = new Scoreboard();
|
||||
private static final IScoreboardCriteria INTEGER = new ScoreboardBaseCriteria("dummy");
|
||||
private static final IScoreboardCriteria HEARTS = new ScoreboardHealthCriteria("health");
|
||||
|
||||
private static final Field TeamPacket_NAME = ReflectionUtils.getFields(PacketPlayOutScoreboardTeam.class, String.class).get(0);
|
||||
private static final Field TeamPacket_ACTION = ReflectionUtils.getInstanceFields(PacketPlayOutScoreboardTeam.class, int.class).get(1);
|
||||
private static final Field TeamPacket_PLAYERS = ReflectionUtils.getOnlyField(PacketPlayOutScoreboardTeam.class, Collection.class);
|
||||
|
||||
private static final Field Objective_OBJECTIVE_NAME = ReflectionUtils.getFields(PacketPlayOutScoreboardObjective.class, String.class).get(0);
|
||||
private static final Field Objective_METHOD = ReflectionUtils.getOnlyField(PacketPlayOutScoreboardObjective.class, int.class);
|
||||
|
||||
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);
|
||||
|
||||
/**
|
||||
* Constructs new instance with given player.
|
||||
*
|
||||
* @param player
|
||||
* Player this scoreboard will belong to
|
||||
*/
|
||||
public NMSPacketScoreboard(@NotNull BukkitTabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerObjective(@NonNull Objective objective) {
|
||||
IScoreboardCriteria healthDisplay = objective.getHealthDisplay() == HealthDisplay.INTEGER ? INTEGER : HEARTS;
|
||||
ScoreboardObjective obj = new ScoreboardObjective(dummyScoreboard, objective.getName(), healthDisplay);
|
||||
obj.setDisplayName(maybeCut(objective.getTitle().toLegacyText(), Limitations.SCOREBOARD_TITLE_PRE_1_13));
|
||||
objective.setPlatformObjective(obj);
|
||||
sendPacket(new PacketPlayOutScoreboardObjective(obj, ObjectiveAction.REGISTER));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDisplaySlot(@NonNull Objective objective) {
|
||||
sendPacket(new PacketPlayOutScoreboardDisplayObjective(objective.getDisplaySlot().ordinal(), (ScoreboardObjective) objective.getPlatformObjective()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterObjective(@NonNull Objective objective) {
|
||||
sendPacket(new PacketPlayOutScoreboardObjective((ScoreboardObjective) objective.getPlatformObjective(), ObjectiveAction.UNREGISTER));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateObjective(@NonNull Objective objective) {
|
||||
IScoreboardCriteria healthDisplay = objective.getHealthDisplay() == HealthDisplay.INTEGER ? INTEGER : HEARTS;
|
||||
ScoreboardObjective obj = new ScoreboardObjective(dummyScoreboard, objective.getName(), healthDisplay);
|
||||
obj.setDisplayName(maybeCut(objective.getTitle().toLegacyText(), Limitations.SCOREBOARD_TITLE_PRE_1_13));
|
||||
sendPacket(new PacketPlayOutScoreboardObjective(obj, ObjectiveAction.UPDATE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setScore(@NonNull Score score) {
|
||||
ScoreboardScore s = new ScoreboardScore(dummyScoreboard, (ScoreboardObjective) score.getObjective().getPlatformObjective(), score.getHolder());
|
||||
s.setScore(score.getValue());
|
||||
sendPacket(new PacketPlayOutScoreboardScore(s));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeScore(@NonNull Score score) {
|
||||
sendPacket(new PacketPlayOutScoreboardScore(score.getHolder()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Object createTeam(@NonNull String name) {
|
||||
return new ScoreboardTeam(dummyScoreboard, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerTeam(@NonNull Team team) {
|
||||
updateTeamProperties(team);
|
||||
ScoreboardTeam t = (ScoreboardTeam) team.getPlatformTeam();
|
||||
t.getPlayerNameSet().addAll(team.getPlayers());
|
||||
sendPacket(new PacketPlayOutScoreboardTeam(t, TeamAction.CREATE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterTeam(@NonNull Team team) {
|
||||
sendPacket(new PacketPlayOutScoreboardTeam((ScoreboardTeam) team.getPlatformTeam(), TeamAction.REMOVE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateTeam(@NonNull Team team) {
|
||||
updateTeamProperties(team);
|
||||
sendPacket(new PacketPlayOutScoreboardTeam((ScoreboardTeam) team.getPlatformTeam(), TeamAction.UPDATE));
|
||||
}
|
||||
|
||||
private void updateTeamProperties(@NonNull Team team) {
|
||||
ScoreboardTeam t = (ScoreboardTeam) team.getPlatformTeam();
|
||||
t.setAllowFriendlyFire((team.getOptions() & 0x01) != 0);
|
||||
t.setCanSeeFriendlyInvisibles((team.getOptions() & 0x02) != 0);
|
||||
t.setNameTagVisibility(visibilities[team.getVisibility().ordinal()]);
|
||||
t.setCollisionRule(collisions[team.getCollision().ordinal()]);
|
||||
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")
|
||||
@NotNull
|
||||
public Object onPacketSend(@NonNull Object packet) {
|
||||
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) {
|
||||
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));
|
||||
}
|
||||
}
|
||||
return packet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the packet to the player.
|
||||
*
|
||||
* @param packet
|
||||
* Packet to send
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
-232
@@ -1,232 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.v1_11_R1;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import com.mojang.authlib.properties.Property;
|
||||
import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.shared.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.server.v1_11_R1.*;
|
||||
import net.minecraft.server.v1_11_R1.PacketPlayOutPlayerInfo.EnumPlayerInfoAction;
|
||||
import org.bukkit.craftbukkit.v1_11_R1.entity.CraftPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* TabList implementation using direct NMS code.
|
||||
*/
|
||||
public class NMSPacketTabList extends TrackedTabList<BukkitTabPlayer> {
|
||||
|
||||
// PlayerInfoData subclass is broken, using reflection to get it
|
||||
private static final Class<?> PlayerInfoData = PacketPlayOutPlayerInfo.class.getDeclaredClasses()[0];
|
||||
private static final Constructor<?> newPlayerInfoData = ReflectionUtils.getOnlyConstructor(PlayerInfoData);
|
||||
|
||||
private static final Field ACTION = ReflectionUtils.getOnlyField(PacketPlayOutPlayerInfo.class, EnumPlayerInfoAction.class);
|
||||
private static final Field PLAYERS = ReflectionUtils.getOnlyField(PacketPlayOutPlayerInfo.class, List.class);
|
||||
|
||||
private static final Field PlayerInfoData_Profile = ReflectionUtils.getOnlyField(PlayerInfoData, GameProfile.class);
|
||||
private static final Field PlayerInfoData_Latency = ReflectionUtils.getFields(PlayerInfoData, int.class).get(0);
|
||||
private static final Field PlayerInfoData_DisplayName = ReflectionUtils.getOnlyField(PlayerInfoData, IChatBaseComponent.class);
|
||||
private static final Field PlayerInfoData_GameMode = ReflectionUtils.getOnlyField(PlayerInfoData, EnumGamemode.class);
|
||||
|
||||
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);
|
||||
|
||||
/**
|
||||
* Constructs new instance.
|
||||
*
|
||||
* @param player
|
||||
* Player this tablist will belong to
|
||||
*/
|
||||
public NMSPacketTabList(@NotNull BukkitTabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void removeEntry(@NonNull UUID entry) {
|
||||
sendPacket(EnumPlayerInfoAction.REMOVE_PLAYER, entry, "", null, 0, 0, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDisplayName0(@NonNull UUID entry, @Nullable TabComponent displayName) {
|
||||
sendPacket(EnumPlayerInfoAction.UPDATE_DISPLAY_NAME, entry, "", null, 0, 0, displayName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateLatency(@NonNull UUID entry, int latency) {
|
||||
sendPacket(EnumPlayerInfoAction.UPDATE_LATENCY, entry, "", null, latency, 0, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateGameMode(@NonNull UUID entry, int gameMode) {
|
||||
sendPacket(EnumPlayerInfoAction.UPDATE_GAME_MODE, entry, "", null, 0, gameMode, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateListed(@NonNull UUID entry, boolean listed) {
|
||||
// Added in 1.19.3
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateListOrder(@NonNull UUID entry, int listOrder) {
|
||||
// Added in 1.21.2
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateHat(@NonNull UUID entry, boolean showHat) {
|
||||
// Added in 1.21.4
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addEntry0(@NonNull Entry entry) {
|
||||
sendPacket(EnumPlayerInfoAction.ADD_PLAYER, entry.getUniqueId(), entry.getName(), entry.getSkin(), entry.getLatency(),
|
||||
entry.getGameMode(), entry.getDisplayName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPlayerListHeaderFooter0(@NonNull TabComponent header, @NonNull TabComponent footer) {
|
||||
sendPacket(newHeaderFooter(header, footer));
|
||||
}
|
||||
|
||||
@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());
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@SneakyThrows
|
||||
private PacketPlayOutPlayerListHeaderFooter newHeaderFooter(@NotNull TabComponent header, @NonNull TabComponent footer) {
|
||||
PacketPlayOutPlayerListHeaderFooter packet = new PacketPlayOutPlayerListHeaderFooter(header.convert());
|
||||
FOOTER.set(packet, footer.convert());
|
||||
return packet;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
@NotNull
|
||||
@SuppressWarnings("unchecked")
|
||||
public Object onPacketSend(@NonNull Object packet) {
|
||||
if (packet instanceof PacketPlayOutPlayerListHeaderFooter) {
|
||||
PacketPlayOutPlayerListHeaderFooter tablist = (PacketPlayOutPlayerListHeaderFooter) packet;
|
||||
if (header == null || footer == null) return packet;
|
||||
IChatBaseComponent header = (IChatBaseComponent) HEADER.get(tablist);
|
||||
IChatBaseComponent footer = (IChatBaseComponent) FOOTER.get(tablist);
|
||||
if (header != this.header.convert() || footer != this.footer.convert()) {
|
||||
return newHeaderFooter(this.header, this.footer);
|
||||
}
|
||||
}
|
||||
if (!(packet instanceof PacketPlayOutPlayerInfo)) return packet;
|
||||
PacketPlayOutPlayerInfo info = (PacketPlayOutPlayerInfo) packet;
|
||||
EnumPlayerInfoAction action = (EnumPlayerInfoAction) ACTION.get(info);
|
||||
List<Object> updatedList = new ArrayList<>();
|
||||
boolean rewritePacket = false;
|
||||
for (Object nmsData : (List<Object>) PLAYERS.get(info)) {
|
||||
boolean rewriteEntry = false;
|
||||
GameProfile profile = (GameProfile) PlayerInfoData_Profile.get(nmsData);
|
||||
UUID id = profile.getId();
|
||||
IChatBaseComponent displayName = (IChatBaseComponent) PlayerInfoData_DisplayName.get(nmsData);
|
||||
int latency = PlayerInfoData_Latency.getInt(nmsData);
|
||||
int gameMode = ((EnumGamemode)PlayerInfoData_GameMode.get(nmsData)).getId();
|
||||
if (action == EnumPlayerInfoAction.UPDATE_DISPLAY_NAME || action == EnumPlayerInfoAction.ADD_PLAYER) {
|
||||
TabComponent forcedDisplayName = getForcedDisplayNames().get(id);
|
||||
if (forcedDisplayName != null && forcedDisplayName.convert() != displayName) {
|
||||
displayName = forcedDisplayName.convert();
|
||||
rewriteEntry = rewritePacket = true;
|
||||
}
|
||||
}
|
||||
if (action == EnumPlayerInfoAction.UPDATE_GAME_MODE || action == EnumPlayerInfoAction.ADD_PLAYER) {
|
||||
if (getBlockedSpectators().contains(id) && gameMode == 3) {
|
||||
gameMode = 0;
|
||||
rewriteEntry = rewritePacket = true;
|
||||
}
|
||||
}
|
||||
if (action == EnumPlayerInfoAction.UPDATE_LATENCY || action == EnumPlayerInfoAction.ADD_PLAYER) {
|
||||
if (getForcedLatency() != null) {
|
||||
latency = getForcedLatency();
|
||||
rewriteEntry = rewritePacket = true;
|
||||
}
|
||||
}
|
||||
if (action == EnumPlayerInfoAction.ADD_PLAYER) {
|
||||
TAB.getInstance().getFeatureManager().onEntryAdd(player, id, profile.getName());
|
||||
}
|
||||
updatedList.add(rewriteEntry ? newPlayerInfoData(
|
||||
(PacketPlayOutPlayerInfo) packet,
|
||||
profile,
|
||||
latency,
|
||||
EnumGamemode.getById(gameMode),
|
||||
displayName
|
||||
) : nmsData);
|
||||
}
|
||||
if (rewritePacket) {
|
||||
PacketPlayOutPlayerInfo newPacket = new PacketPlayOutPlayerInfo(action, Collections.emptyList());
|
||||
PLAYERS.set(newPacket, updatedList);
|
||||
return newPacket;
|
||||
}
|
||||
return packet;
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private void sendPacket(@NonNull EnumPlayerInfoAction action, @NonNull UUID id, @NonNull String name,
|
||||
@Nullable Skin skin, int latency, int gameMode, @Nullable TabComponent displayName) {
|
||||
PacketPlayOutPlayerInfo packet = new PacketPlayOutPlayerInfo(action);
|
||||
PLAYERS.set(packet, Collections.singletonList(newPlayerInfoData(
|
||||
packet,
|
||||
createProfile(id, name, skin),
|
||||
latency,
|
||||
EnumGamemode.getById(gameMode),
|
||||
displayName == null ? null : displayName.convert())
|
||||
));
|
||||
sendPacket(packet);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@SneakyThrows
|
||||
private Object newPlayerInfoData(@NonNull PacketPlayOutPlayerInfo packet, @NonNull GameProfile profile,
|
||||
int latency, @NonNull EnumGamemode gameMode, @Nullable IChatBaseComponent displayName) {
|
||||
return newPlayerInfoData.newInstance(packet, profile, latency, gameMode, displayName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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().playerConnection.sendPacket(packet);
|
||||
}
|
||||
}
|
||||
-47
@@ -1,47 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.v1_11_R1;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import io.netty.channel.Channel;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.shared.platform.NettyTabListEntryTracker;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import net.minecraft.server.v1_11_R1.PacketPlayOutPlayerInfo;
|
||||
import net.minecraft.server.v1_11_R1.PacketPlayOutPlayerInfo.EnumPlayerInfoAction;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Implementation of TabListEntryTracker.
|
||||
*/
|
||||
public class NMSTabListEntryTracker extends NettyTabListEntryTracker {
|
||||
|
||||
private static final Field ACTION = ReflectionUtils.getOnlyField(PacketPlayOutPlayerInfo.class, EnumPlayerInfoAction.class);
|
||||
private static final Field PLAYERS = ReflectionUtils.getOnlyField(PacketPlayOutPlayerInfo.class, List.class);
|
||||
private static final Class<?> PlayerInfoData = PacketPlayOutPlayerInfo.class.getDeclaredClasses()[0];
|
||||
private static final Field PlayerInfoData_Profile = ReflectionUtils.getOnlyField(PlayerInfoData, GameProfile.class);
|
||||
|
||||
public NMSTabListEntryTracker(@NotNull Channel channel) {
|
||||
super(channel);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
@SuppressWarnings("unchecked")
|
||||
public void onPacketSend(@NotNull Object packet) {
|
||||
if (packet instanceof PacketPlayOutPlayerInfo) {
|
||||
PacketPlayOutPlayerInfo info = (PacketPlayOutPlayerInfo) packet;
|
||||
EnumPlayerInfoAction action = (EnumPlayerInfoAction) ACTION.get(info);
|
||||
for (Object nmsData : (List<Object>) PLAYERS.get(info)) {
|
||||
GameProfile profile = (GameProfile) PlayerInfoData_Profile.get(nmsData);
|
||||
if (action == EnumPlayerInfoAction.ADD_PLAYER) {
|
||||
tablistEntries.add(profile.getId());
|
||||
}
|
||||
if (action == EnumPlayerInfoAction.REMOVE_PLAYER) {
|
||||
tablistEntries.remove(profile.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
dependencies {
|
||||
implementation(projects.bukkit)
|
||||
compileOnly("org.bukkit:craftbukkit:1.13.2-R0.1-SNAPSHOT")
|
||||
}
|
||||
-62
@@ -1,62 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.v1_13_R2;
|
||||
|
||||
import me.neznamy.tab.shared.chat.TabStyle;
|
||||
import me.neznamy.tab.shared.chat.component.object.TabAtlasSprite;
|
||||
import me.neznamy.tab.shared.chat.component.object.TabObjectComponent;
|
||||
import me.neznamy.tab.platforms.bukkit.provider.ComponentConverter;
|
||||
import me.neznamy.tab.shared.chat.component.object.TabPlayerSprite;
|
||||
import net.minecraft.server.v1_13_R2.*;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Component converter using direct NMS code.
|
||||
*/
|
||||
public class NMSComponentConverter extends ComponentConverter<IChatBaseComponent> {
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public IChatBaseComponent newTextComponent(@NotNull String text) {
|
||||
return new ChatComponentText(text);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public IChatBaseComponent newTranslatableComponent(@NotNull String key) {
|
||||
return new ChatMessage(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public IChatBaseComponent newKeybindComponent(@NotNull String keybind) {
|
||||
return new ChatComponentKeybind(keybind);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public IChatBaseComponent newObjectComponent(@NotNull TabAtlasSprite sprite) {
|
||||
return new ChatComponentText(TabObjectComponent.ERROR_MESSAGE);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public IChatBaseComponent newObjectComponent(@NotNull TabPlayerSprite sprite) {
|
||||
return new ChatComponentText(TabObjectComponent.ERROR_MESSAGE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyStyle(@NotNull IChatBaseComponent nmsComponent, @NotNull TabStyle modifier) {
|
||||
nmsComponent.setChatModifier(new ChatModifier()
|
||||
.setColor(modifier.getColor() == null ? null : EnumChatFormat.valueOf(modifier.getColor().getLegacyColor().name()))
|
||||
.setBold(modifier.getBold())
|
||||
.setItalic(modifier.getItalic())
|
||||
.setUnderline(modifier.getUnderlined())
|
||||
.setStrikethrough(modifier.getStrikethrough())
|
||||
.setRandom(modifier.getObfuscated())
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addSibling(@NotNull IChatBaseComponent parent, @NotNull IChatBaseComponent child) {
|
||||
parent.addSibling(child);
|
||||
}
|
||||
}
|
||||
-52
@@ -1,52 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.v1_13_R2;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import lombok.Getter;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.provider.ComponentConverter;
|
||||
import me.neznamy.tab.platforms.bukkit.provider.ImplementationProvider;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import me.neznamy.tab.shared.platform.TabListEntryTracker;
|
||||
import org.bukkit.craftbukkit.v1_13_R2.entity.CraftPlayer;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Implementation provider using direct NMS code for 1.13.1 - 1.13.2.
|
||||
*/
|
||||
@Getter
|
||||
public class NMSImplementationProvider implements ImplementationProvider {
|
||||
|
||||
@NotNull
|
||||
private final ComponentConverter<?> componentConverter = new NMSComponentConverter();
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Scoreboard newScoreboard(@NotNull BukkitTabPlayer player) {
|
||||
return new NMSPacketScoreboard(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public TabList newTabList(@NotNull BukkitTabPlayer player) {
|
||||
return new NMSPacketTabList(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Channel getChannel(@NotNull Player player) {
|
||||
return ((CraftPlayer)player).getHandle().playerConnection.networkManager.channel;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public TabListEntryTracker newTabListEntryTracker(@NotNull Player player) {
|
||||
return new NMSTabListEntryTracker(getChannel(player));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPing(@NotNull BukkitTabPlayer player) {
|
||||
return ((CraftPlayer)player.getPlayer()).getHandle().ping;
|
||||
}
|
||||
}
|
||||
-169
@@ -1,169 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.v1_13_R2;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.platform.decorators.SafeScoreboard;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import net.minecraft.server.v1_13_R2.*;
|
||||
import org.bukkit.craftbukkit.v1_13_R2.entity.CraftPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* Scoreboard implementation using direct NMS code.
|
||||
*/
|
||||
public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
|
||||
|
||||
private static final ScoreboardTeamBase.EnumNameTagVisibility[] visibilities = ScoreboardTeamBase.EnumNameTagVisibility.values();
|
||||
private static final ScoreboardTeamBase.EnumTeamPush[] collisions = ScoreboardTeamBase.EnumTeamPush.values();
|
||||
private static final Scoreboard dummyScoreboard = new Scoreboard();
|
||||
|
||||
private static final Field TeamPacket_NAME = ReflectionUtils.getFields(PacketPlayOutScoreboardTeam.class, String.class).get(0);
|
||||
private static final Field TeamPacket_ACTION = ReflectionUtils.getInstanceFields(PacketPlayOutScoreboardTeam.class, int.class).get(0);
|
||||
private static final Field TeamPacket_PLAYERS = ReflectionUtils.getOnlyField(PacketPlayOutScoreboardTeam.class, Collection.class);
|
||||
|
||||
private static final Field Objective_OBJECTIVE_NAME = ReflectionUtils.getFields(PacketPlayOutScoreboardObjective.class, String.class).get(0);
|
||||
private static final Field Objective_METHOD = ReflectionUtils.getOnlyField(PacketPlayOutScoreboardObjective.class, int.class);
|
||||
|
||||
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);
|
||||
|
||||
/**
|
||||
* Constructs new instance with given player.
|
||||
*
|
||||
* @param player
|
||||
* Player this scoreboard will belong to
|
||||
*/
|
||||
public NMSPacketScoreboard(@NotNull BukkitTabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerObjective(@NonNull Objective objective) {
|
||||
ScoreboardObjective obj = new ScoreboardObjective(
|
||||
dummyScoreboard,
|
||||
objective.getName(),
|
||||
IScoreboardCriteria.DUMMY,
|
||||
objective.getTitle().convert(),
|
||||
IScoreboardCriteria.EnumScoreboardHealthDisplay.values()[objective.getHealthDisplay().ordinal()]
|
||||
);
|
||||
objective.setPlatformObjective(obj);
|
||||
sendPacket(new PacketPlayOutScoreboardObjective(obj, ObjectiveAction.REGISTER));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDisplaySlot(@NonNull Objective objective) {
|
||||
sendPacket(new PacketPlayOutScoreboardDisplayObjective(
|
||||
objective.getDisplaySlot().ordinal(),
|
||||
(ScoreboardObjective) objective.getPlatformObjective()
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterObjective(@NonNull Objective objective) {
|
||||
sendPacket(new PacketPlayOutScoreboardObjective((ScoreboardObjective) objective.getPlatformObjective(), ObjectiveAction.UNREGISTER));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateObjective(@NonNull Objective objective) {
|
||||
ScoreboardObjective obj = (ScoreboardObjective) objective.getPlatformObjective();
|
||||
obj.setDisplayName(objective.getTitle().convert());
|
||||
obj.setRenderType(IScoreboardCriteria.EnumScoreboardHealthDisplay.valueOf(objective.getHealthDisplay().name()));
|
||||
sendPacket(new PacketPlayOutScoreboardObjective(obj, ObjectiveAction.UPDATE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setScore(@NonNull Score score) {
|
||||
sendPacket(new PacketPlayOutScoreboardScore(
|
||||
ScoreboardServer.Action.CHANGE,
|
||||
score.getObjective().getName(),
|
||||
score.getHolder(),
|
||||
score.getValue()
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeScore(@NonNull Score score) {
|
||||
sendPacket(new PacketPlayOutScoreboardScore(
|
||||
ScoreboardServer.Action.REMOVE,
|
||||
score.getObjective().getName(),
|
||||
score.getHolder(),
|
||||
score.getValue()
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Object createTeam(@NonNull String name) {
|
||||
return new ScoreboardTeam(dummyScoreboard, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerTeam(@NonNull Team team) {
|
||||
updateTeamProperties(team);
|
||||
ScoreboardTeam t = (ScoreboardTeam) team.getPlatformTeam();
|
||||
t.getPlayerNameSet().addAll(team.getPlayers());
|
||||
sendPacket(new PacketPlayOutScoreboardTeam(t, TeamAction.CREATE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterTeam(@NonNull Team team) {
|
||||
sendPacket(new PacketPlayOutScoreboardTeam((ScoreboardTeam) team.getPlatformTeam(), TeamAction.REMOVE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateTeam(@NonNull Team team) {
|
||||
updateTeamProperties(team);
|
||||
sendPacket(new PacketPlayOutScoreboardTeam((ScoreboardTeam) team.getPlatformTeam(), TeamAction.UPDATE));
|
||||
}
|
||||
|
||||
private void updateTeamProperties(@NonNull Team team) {
|
||||
ScoreboardTeam t = (ScoreboardTeam) team.getPlatformTeam();
|
||||
t.setAllowFriendlyFire((team.getOptions() & 0x01) != 0);
|
||||
t.setCanSeeFriendlyInvisibles((team.getOptions() & 0x02) != 0);
|
||||
t.setNameTagVisibility(visibilities[team.getVisibility().ordinal()]);
|
||||
t.setCollisionRule(collisions[team.getCollision().ordinal()]);
|
||||
t.setPrefix(team.getPrefix().convert());
|
||||
t.setSuffix(team.getSuffix().convert());
|
||||
t.setColor(EnumChatFormat.valueOf(team.getColor().name()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
@SuppressWarnings("unchecked")
|
||||
@NotNull
|
||||
public Object onPacketSend(@NonNull Object packet) {
|
||||
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) {
|
||||
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));
|
||||
}
|
||||
}
|
||||
return packet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the packet to the player.
|
||||
*
|
||||
* @param packet
|
||||
* Packet to send
|
||||
*/
|
||||
private void sendPacket(@NotNull Packet<?> packet) {
|
||||
((CraftPlayer)player.getPlayer()).getHandle().playerConnection.sendPacket(packet);
|
||||
}
|
||||
}
|
||||
-227
@@ -1,227 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.v1_13_R2;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import com.mojang.authlib.properties.Property;
|
||||
import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.shared.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.server.v1_13_R2.*;
|
||||
import net.minecraft.server.v1_13_R2.PacketPlayOutPlayerInfo.EnumPlayerInfoAction;
|
||||
import org.bukkit.craftbukkit.v1_13_R2.entity.CraftPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* TabList implementation using direct NMS code.
|
||||
*/
|
||||
public class NMSPacketTabList extends TrackedTabList<BukkitTabPlayer> {
|
||||
|
||||
// PlayerInfoData subclass is broken, using reflection to get it
|
||||
private static final Class<?> PlayerInfoData = PacketPlayOutPlayerInfo.class.getDeclaredClasses()[0];
|
||||
private static final Constructor<?> newPlayerInfoData = ReflectionUtils.getOnlyConstructor(PlayerInfoData);
|
||||
|
||||
private static final Field ACTION = ReflectionUtils.getOnlyField(PacketPlayOutPlayerInfo.class, EnumPlayerInfoAction.class);
|
||||
private static final Field PLAYERS = ReflectionUtils.getOnlyField(PacketPlayOutPlayerInfo.class, List.class);
|
||||
|
||||
private static final Field PlayerInfoData_Profile = ReflectionUtils.getOnlyField(PlayerInfoData, GameProfile.class);
|
||||
private static final Field PlayerInfoData_Latency = ReflectionUtils.getFields(PlayerInfoData, int.class).get(0);
|
||||
private static final Field PlayerInfoData_DisplayName = ReflectionUtils.getOnlyField(PlayerInfoData, IChatBaseComponent.class);
|
||||
private static final Field PlayerInfoData_GameMode = ReflectionUtils.getOnlyField(PlayerInfoData, EnumGamemode.class);
|
||||
|
||||
/**
|
||||
* Constructs new instance.
|
||||
*
|
||||
* @param player
|
||||
* Player this tablist will belong to
|
||||
*/
|
||||
public NMSPacketTabList(@NotNull BukkitTabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void removeEntry(@NonNull UUID entry) {
|
||||
sendPacket(EnumPlayerInfoAction.REMOVE_PLAYER, entry, "", null, 0, 0, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDisplayName0(@NonNull UUID entry, @Nullable TabComponent displayName) {
|
||||
sendPacket(EnumPlayerInfoAction.UPDATE_DISPLAY_NAME, entry, "", null, 0, 0, displayName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateLatency(@NonNull UUID entry, int latency) {
|
||||
sendPacket(EnumPlayerInfoAction.UPDATE_LATENCY, entry, "", null, latency, 0, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateGameMode(@NonNull UUID entry, int gameMode) {
|
||||
sendPacket(EnumPlayerInfoAction.UPDATE_GAME_MODE, entry, "", null, 0, gameMode, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateListed(@NonNull UUID entry, boolean listed) {
|
||||
// Added in 1.19.3
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateListOrder(@NonNull UUID entry, int listOrder) {
|
||||
// Added in 1.21.2
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateHat(@NonNull UUID entry, boolean showHat) {
|
||||
// Added in 1.21.4
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addEntry0(@NonNull Entry entry) {
|
||||
sendPacket(EnumPlayerInfoAction.ADD_PLAYER, entry.getUniqueId(), entry.getName(), entry.getSkin(), entry.getLatency(),
|
||||
entry.getGameMode(), entry.getDisplayName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPlayerListHeaderFooter0(@NonNull TabComponent header, @NonNull TabComponent footer) {
|
||||
sendPacket(newHeaderFooter(header, footer));
|
||||
}
|
||||
|
||||
@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());
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private PacketPlayOutPlayerListHeaderFooter newHeaderFooter(@NotNull TabComponent header, @NonNull TabComponent footer) {
|
||||
PacketPlayOutPlayerListHeaderFooter packet = new PacketPlayOutPlayerListHeaderFooter();
|
||||
packet.header = header.convert();
|
||||
packet.footer = footer.convert();
|
||||
return packet;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
@NotNull
|
||||
@SuppressWarnings("unchecked")
|
||||
public Object onPacketSend(@NonNull Object packet) {
|
||||
if (packet instanceof PacketPlayOutPlayerListHeaderFooter) {
|
||||
PacketPlayOutPlayerListHeaderFooter tablist = (PacketPlayOutPlayerListHeaderFooter) packet;
|
||||
if (header == null || footer == null) return packet;
|
||||
if (tablist.header != header.convert() || tablist.footer != footer.convert()) {
|
||||
return newHeaderFooter(header, footer);
|
||||
}
|
||||
}
|
||||
if (!(packet instanceof PacketPlayOutPlayerInfo)) return packet;
|
||||
PacketPlayOutPlayerInfo info = (PacketPlayOutPlayerInfo) packet;
|
||||
EnumPlayerInfoAction action = (EnumPlayerInfoAction) ACTION.get(info);
|
||||
List<Object> updatedList = new ArrayList<>();
|
||||
boolean rewritePacket = false;
|
||||
for (Object nmsData : (List<Object>) PLAYERS.get(info)) {
|
||||
boolean rewriteEntry = false;
|
||||
GameProfile profile = (GameProfile) PlayerInfoData_Profile.get(nmsData);
|
||||
UUID id = profile.getId();
|
||||
IChatBaseComponent displayName = (IChatBaseComponent) PlayerInfoData_DisplayName.get(nmsData);
|
||||
int latency = PlayerInfoData_Latency.getInt(nmsData);
|
||||
int gameMode = ((EnumGamemode)PlayerInfoData_GameMode.get(nmsData)).getId();
|
||||
if (action == EnumPlayerInfoAction.UPDATE_DISPLAY_NAME || action == EnumPlayerInfoAction.ADD_PLAYER) {
|
||||
TabComponent forcedDisplayName = getForcedDisplayNames().get(id);
|
||||
if (forcedDisplayName != null && forcedDisplayName.convert() != displayName) {
|
||||
displayName = forcedDisplayName.convert();
|
||||
rewriteEntry = rewritePacket = true;
|
||||
}
|
||||
}
|
||||
if (action == EnumPlayerInfoAction.UPDATE_GAME_MODE || action == EnumPlayerInfoAction.ADD_PLAYER) {
|
||||
if (getBlockedSpectators().contains(id) && gameMode == 3) {
|
||||
gameMode = 0;
|
||||
rewriteEntry = rewritePacket = true;
|
||||
}
|
||||
}
|
||||
if (action == EnumPlayerInfoAction.UPDATE_LATENCY || action == EnumPlayerInfoAction.ADD_PLAYER) {
|
||||
if (getForcedLatency() != null) {
|
||||
latency = getForcedLatency();
|
||||
rewriteEntry = rewritePacket = true;
|
||||
}
|
||||
}
|
||||
if (action == EnumPlayerInfoAction.ADD_PLAYER) {
|
||||
TAB.getInstance().getFeatureManager().onEntryAdd(player, id, profile.getName());
|
||||
}
|
||||
updatedList.add(rewriteEntry ? newPlayerInfoData(
|
||||
(PacketPlayOutPlayerInfo) packet,
|
||||
profile,
|
||||
latency,
|
||||
EnumGamemode.getById(gameMode),
|
||||
displayName
|
||||
) : nmsData);
|
||||
}
|
||||
if (rewritePacket) {
|
||||
PacketPlayOutPlayerInfo newPacket = new PacketPlayOutPlayerInfo(action, Collections.emptyList());
|
||||
PLAYERS.set(newPacket, updatedList);
|
||||
return newPacket;
|
||||
}
|
||||
return packet;
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private void sendPacket(@NonNull EnumPlayerInfoAction action, @NonNull UUID id, @NonNull String name,
|
||||
@Nullable Skin skin, int latency, int gameMode, @Nullable TabComponent displayName) {
|
||||
PacketPlayOutPlayerInfo packet = new PacketPlayOutPlayerInfo(action);
|
||||
PLAYERS.set(packet, Collections.singletonList(newPlayerInfoData(
|
||||
packet,
|
||||
createProfile(id, name, skin),
|
||||
latency,
|
||||
EnumGamemode.getById(gameMode),
|
||||
displayName == null ? null : displayName.convert())
|
||||
));
|
||||
sendPacket(packet);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@SneakyThrows
|
||||
private Object newPlayerInfoData(@NonNull PacketPlayOutPlayerInfo packet, @NonNull GameProfile profile,
|
||||
int latency, @NonNull EnumGamemode gameMode, @Nullable IChatBaseComponent displayName) {
|
||||
return newPlayerInfoData.newInstance(packet, profile, latency, gameMode, displayName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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().playerConnection.sendPacket(packet);
|
||||
}
|
||||
}
|
||||
-47
@@ -1,47 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.v1_13_R2;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import io.netty.channel.Channel;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.shared.platform.NettyTabListEntryTracker;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import net.minecraft.server.v1_13_R2.PacketPlayOutPlayerInfo;
|
||||
import net.minecraft.server.v1_13_R2.PacketPlayOutPlayerInfo.EnumPlayerInfoAction;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Implementation of TabListEntryTracker.
|
||||
*/
|
||||
public class NMSTabListEntryTracker extends NettyTabListEntryTracker {
|
||||
|
||||
private static final Field ACTION = ReflectionUtils.getOnlyField(PacketPlayOutPlayerInfo.class, EnumPlayerInfoAction.class);
|
||||
private static final Field PLAYERS = ReflectionUtils.getOnlyField(PacketPlayOutPlayerInfo.class, List.class);
|
||||
private static final Class<?> PlayerInfoData = PacketPlayOutPlayerInfo.class.getDeclaredClasses()[0];
|
||||
private static final Field PlayerInfoData_Profile = ReflectionUtils.getOnlyField(PlayerInfoData, GameProfile.class);
|
||||
|
||||
public NMSTabListEntryTracker(@NotNull Channel channel) {
|
||||
super(channel);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
@SuppressWarnings("unchecked")
|
||||
public void onPacketSend(@NotNull Object packet) {
|
||||
if (packet instanceof PacketPlayOutPlayerInfo) {
|
||||
PacketPlayOutPlayerInfo info = (PacketPlayOutPlayerInfo) packet;
|
||||
EnumPlayerInfoAction action = (EnumPlayerInfoAction) ACTION.get(info);
|
||||
for (Object nmsData : (List<Object>) PLAYERS.get(info)) {
|
||||
GameProfile profile = (GameProfile) PlayerInfoData_Profile.get(nmsData);
|
||||
if (action == EnumPlayerInfoAction.ADD_PLAYER) {
|
||||
tablistEntries.add(profile.getId());
|
||||
}
|
||||
if (action == EnumPlayerInfoAction.REMOVE_PLAYER) {
|
||||
tablistEntries.remove(profile.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
dependencies {
|
||||
implementation(projects.bukkit)
|
||||
compileOnly("org.bukkit:craftbukkit:1.14.4-R0.1-SNAPSHOT")
|
||||
}
|
||||
-62
@@ -1,62 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.v1_14_R1;
|
||||
|
||||
import me.neznamy.tab.shared.chat.TabStyle;
|
||||
import me.neznamy.tab.shared.chat.component.object.TabAtlasSprite;
|
||||
import me.neznamy.tab.shared.chat.component.object.TabObjectComponent;
|
||||
import me.neznamy.tab.platforms.bukkit.provider.ComponentConverter;
|
||||
import me.neznamy.tab.shared.chat.component.object.TabPlayerSprite;
|
||||
import net.minecraft.server.v1_14_R1.*;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Component converter using direct NMS code.
|
||||
*/
|
||||
public class NMSComponentConverter extends ComponentConverter<IChatBaseComponent> {
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public IChatBaseComponent newTextComponent(@NotNull String text) {
|
||||
return new ChatComponentText(text);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public IChatBaseComponent newTranslatableComponent(@NotNull String key) {
|
||||
return new ChatMessage(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public IChatBaseComponent newKeybindComponent(@NotNull String keybind) {
|
||||
return new ChatComponentKeybind(keybind);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public IChatBaseComponent newObjectComponent(@NotNull TabAtlasSprite sprite) {
|
||||
return new ChatComponentText(TabObjectComponent.ERROR_MESSAGE);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public IChatBaseComponent newObjectComponent(@NotNull TabPlayerSprite sprite) {
|
||||
return new ChatComponentText(TabObjectComponent.ERROR_MESSAGE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyStyle(@NotNull IChatBaseComponent nmsComponent, @NotNull TabStyle modifier) {
|
||||
nmsComponent.setChatModifier(new ChatModifier()
|
||||
.setColor(modifier.getColor() == null ? null : EnumChatFormat.valueOf(modifier.getColor().getLegacyColor().name()))
|
||||
.setBold(modifier.getBold())
|
||||
.setItalic(modifier.getItalic())
|
||||
.setUnderline(modifier.getUnderlined())
|
||||
.setStrikethrough(modifier.getStrikethrough())
|
||||
.setRandom(modifier.getObfuscated())
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addSibling(@NotNull IChatBaseComponent parent, @NotNull IChatBaseComponent child) {
|
||||
parent.addSibling(child);
|
||||
}
|
||||
}
|
||||
-52
@@ -1,52 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.v1_14_R1;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import lombok.Getter;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.provider.ComponentConverter;
|
||||
import me.neznamy.tab.platforms.bukkit.provider.ImplementationProvider;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import me.neznamy.tab.shared.platform.TabListEntryTracker;
|
||||
import org.bukkit.craftbukkit.v1_14_R1.entity.CraftPlayer;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Implementation provider using direct NMS code for 1.14.x.
|
||||
*/
|
||||
@Getter
|
||||
public class NMSImplementationProvider implements ImplementationProvider {
|
||||
|
||||
@NotNull
|
||||
private final ComponentConverter<?> componentConverter = new NMSComponentConverter();
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Scoreboard newScoreboard(@NotNull BukkitTabPlayer player) {
|
||||
return new NMSPacketScoreboard(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public TabList newTabList(@NotNull BukkitTabPlayer player) {
|
||||
return new NMSPacketTabList(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Channel getChannel(@NotNull Player player) {
|
||||
return ((CraftPlayer)player).getHandle().playerConnection.networkManager.channel;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public TabListEntryTracker newTabListEntryTracker(@NotNull Player player) {
|
||||
return new NMSTabListEntryTracker(getChannel(player));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPing(@NotNull BukkitTabPlayer player) {
|
||||
return ((CraftPlayer)player.getPlayer()).getHandle().ping;
|
||||
}
|
||||
}
|
||||
-169
@@ -1,169 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.v1_14_R1;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.platform.decorators.SafeScoreboard;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import net.minecraft.server.v1_14_R1.*;
|
||||
import org.bukkit.craftbukkit.v1_14_R1.entity.CraftPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* Scoreboard implementation using direct NMS code.
|
||||
*/
|
||||
public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
|
||||
|
||||
private static final ScoreboardTeamBase.EnumNameTagVisibility[] visibilities = ScoreboardTeamBase.EnumNameTagVisibility.values();
|
||||
private static final ScoreboardTeamBase.EnumTeamPush[] collisions = ScoreboardTeamBase.EnumTeamPush.values();
|
||||
private static final Scoreboard dummyScoreboard = new Scoreboard();
|
||||
|
||||
private static final Field TeamPacket_NAME = ReflectionUtils.getFields(PacketPlayOutScoreboardTeam.class, String.class).get(0);
|
||||
private static final Field TeamPacket_ACTION = ReflectionUtils.getInstanceFields(PacketPlayOutScoreboardTeam.class, int.class).get(0);
|
||||
private static final Field TeamPacket_PLAYERS = ReflectionUtils.getOnlyField(PacketPlayOutScoreboardTeam.class, Collection.class);
|
||||
|
||||
private static final Field Objective_OBJECTIVE_NAME = ReflectionUtils.getFields(PacketPlayOutScoreboardObjective.class, String.class).get(0);
|
||||
private static final Field Objective_METHOD = ReflectionUtils.getOnlyField(PacketPlayOutScoreboardObjective.class, int.class);
|
||||
|
||||
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);
|
||||
|
||||
/**
|
||||
* Constructs new instance with given player.
|
||||
*
|
||||
* @param player
|
||||
* Player this scoreboard will belong to
|
||||
*/
|
||||
public NMSPacketScoreboard(@NotNull BukkitTabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerObjective(@NonNull Objective objective) {
|
||||
ScoreboardObjective obj = new ScoreboardObjective(
|
||||
dummyScoreboard,
|
||||
objective.getName(),
|
||||
IScoreboardCriteria.DUMMY,
|
||||
objective.getTitle().convert(),
|
||||
IScoreboardCriteria.EnumScoreboardHealthDisplay.values()[objective.getHealthDisplay().ordinal()]
|
||||
);
|
||||
objective.setPlatformObjective(obj);
|
||||
sendPacket(new PacketPlayOutScoreboardObjective(obj, ObjectiveAction.REGISTER));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDisplaySlot(@NonNull Objective objective) {
|
||||
sendPacket(new PacketPlayOutScoreboardDisplayObjective(
|
||||
objective.getDisplaySlot().ordinal(),
|
||||
(ScoreboardObjective) objective.getPlatformObjective()
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterObjective(@NonNull Objective objective) {
|
||||
sendPacket(new PacketPlayOutScoreboardObjective((ScoreboardObjective) objective.getPlatformObjective(), ObjectiveAction.UNREGISTER));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateObjective(@NonNull Objective objective) {
|
||||
ScoreboardObjective obj = (ScoreboardObjective) objective.getPlatformObjective();
|
||||
obj.setDisplayName(objective.getTitle().convert());
|
||||
obj.setRenderType(IScoreboardCriteria.EnumScoreboardHealthDisplay.valueOf(objective.getHealthDisplay().name()));
|
||||
sendPacket(new PacketPlayOutScoreboardObjective(obj, ObjectiveAction.UPDATE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setScore(@NonNull Score score) {
|
||||
sendPacket(new PacketPlayOutScoreboardScore(
|
||||
ScoreboardServer.Action.CHANGE,
|
||||
score.getObjective().getName(),
|
||||
score.getHolder(),
|
||||
score.getValue()
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeScore(@NonNull Score score) {
|
||||
sendPacket(new PacketPlayOutScoreboardScore(
|
||||
ScoreboardServer.Action.REMOVE,
|
||||
score.getObjective().getName(),
|
||||
score.getHolder(),
|
||||
score.getValue()
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Object createTeam(@NonNull String name) {
|
||||
return new ScoreboardTeam(dummyScoreboard, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerTeam(@NonNull Team team) {
|
||||
updateTeamProperties(team);
|
||||
ScoreboardTeam t = (ScoreboardTeam) team.getPlatformTeam();
|
||||
t.getPlayerNameSet().addAll(team.getPlayers());
|
||||
sendPacket(new PacketPlayOutScoreboardTeam(t, TeamAction.CREATE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterTeam(@NonNull Team team) {
|
||||
sendPacket(new PacketPlayOutScoreboardTeam((ScoreboardTeam) team.getPlatformTeam(), TeamAction.REMOVE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateTeam(@NonNull Team team) {
|
||||
updateTeamProperties(team);
|
||||
sendPacket(new PacketPlayOutScoreboardTeam((ScoreboardTeam) team.getPlatformTeam(), TeamAction.UPDATE));
|
||||
}
|
||||
|
||||
private void updateTeamProperties(@NonNull Team team) {
|
||||
ScoreboardTeam t = (ScoreboardTeam) team.getPlatformTeam();
|
||||
t.setAllowFriendlyFire((team.getOptions() & 0x01) != 0);
|
||||
t.setCanSeeFriendlyInvisibles((team.getOptions() & 0x02) != 0);
|
||||
t.setNameTagVisibility(visibilities[team.getVisibility().ordinal()]);
|
||||
t.setCollisionRule(collisions[team.getCollision().ordinal()]);
|
||||
t.setPrefix(team.getPrefix().convert());
|
||||
t.setSuffix(team.getSuffix().convert());
|
||||
t.setColor(EnumChatFormat.valueOf(team.getColor().name()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
@SuppressWarnings("unchecked")
|
||||
@NotNull
|
||||
public Object onPacketSend(@NonNull Object packet) {
|
||||
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) {
|
||||
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));
|
||||
}
|
||||
}
|
||||
return packet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the packet to the player.
|
||||
*
|
||||
* @param packet
|
||||
* Packet to send
|
||||
*/
|
||||
private void sendPacket(@NotNull Packet<?> packet) {
|
||||
((CraftPlayer)player.getPlayer()).getHandle().playerConnection.sendPacket(packet);
|
||||
}
|
||||
}
|
||||
-227
@@ -1,227 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.v1_14_R1;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import com.mojang.authlib.properties.Property;
|
||||
import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.shared.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.server.v1_14_R1.*;
|
||||
import net.minecraft.server.v1_14_R1.PacketPlayOutPlayerInfo.EnumPlayerInfoAction;
|
||||
import org.bukkit.craftbukkit.v1_14_R1.entity.CraftPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* TabList implementation using direct NMS code.
|
||||
*/
|
||||
public class NMSPacketTabList extends TrackedTabList<BukkitTabPlayer> {
|
||||
|
||||
// PlayerInfoData subclass is broken, using reflection to get it
|
||||
private static final Class<?> PlayerInfoData = PacketPlayOutPlayerInfo.class.getDeclaredClasses()[0];
|
||||
private static final Constructor<?> newPlayerInfoData = ReflectionUtils.getOnlyConstructor(PlayerInfoData);
|
||||
|
||||
private static final Field ACTION = ReflectionUtils.getOnlyField(PacketPlayOutPlayerInfo.class, EnumPlayerInfoAction.class);
|
||||
private static final Field PLAYERS = ReflectionUtils.getOnlyField(PacketPlayOutPlayerInfo.class, List.class);
|
||||
|
||||
private static final Field PlayerInfoData_Profile = ReflectionUtils.getOnlyField(PlayerInfoData, GameProfile.class);
|
||||
private static final Field PlayerInfoData_Latency = ReflectionUtils.getFields(PlayerInfoData, int.class).get(0);
|
||||
private static final Field PlayerInfoData_DisplayName = ReflectionUtils.getOnlyField(PlayerInfoData, IChatBaseComponent.class);
|
||||
private static final Field PlayerInfoData_GameMode = ReflectionUtils.getOnlyField(PlayerInfoData, EnumGamemode.class);
|
||||
|
||||
/**
|
||||
* Constructs new instance.
|
||||
*
|
||||
* @param player
|
||||
* Player this tablist will belong to
|
||||
*/
|
||||
public NMSPacketTabList(@NotNull BukkitTabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void removeEntry(@NonNull UUID entry) {
|
||||
sendPacket(EnumPlayerInfoAction.REMOVE_PLAYER, entry, "", null, 0, 0, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDisplayName0(@NonNull UUID entry, @Nullable TabComponent displayName) {
|
||||
sendPacket(EnumPlayerInfoAction.UPDATE_DISPLAY_NAME, entry, "", null, 0, 0, displayName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateLatency(@NonNull UUID entry, int latency) {
|
||||
sendPacket(EnumPlayerInfoAction.UPDATE_LATENCY, entry, "", null, latency, 0, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateGameMode(@NonNull UUID entry, int gameMode) {
|
||||
sendPacket(EnumPlayerInfoAction.UPDATE_GAME_MODE, entry, "", null, 0, gameMode, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateListed(@NonNull UUID entry, boolean listed) {
|
||||
// Added in 1.19.3
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateListOrder(@NonNull UUID entry, int listOrder) {
|
||||
// Added in 1.21.2
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateHat(@NonNull UUID entry, boolean showHat) {
|
||||
// Added in 1.21.4
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addEntry0(@NonNull Entry entry) {
|
||||
sendPacket(EnumPlayerInfoAction.ADD_PLAYER, entry.getUniqueId(), entry.getName(), entry.getSkin(), entry.getLatency(),
|
||||
entry.getGameMode(), entry.getDisplayName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPlayerListHeaderFooter0(@NonNull TabComponent header, @NonNull TabComponent footer) {
|
||||
sendPacket(newHeaderFooter(header, footer));
|
||||
}
|
||||
|
||||
@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());
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private PacketPlayOutPlayerListHeaderFooter newHeaderFooter(@NotNull TabComponent header, @NonNull TabComponent footer) {
|
||||
PacketPlayOutPlayerListHeaderFooter packet = new PacketPlayOutPlayerListHeaderFooter();
|
||||
packet.header = header.convert();
|
||||
packet.footer = footer.convert();
|
||||
return packet;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
@NotNull
|
||||
@SuppressWarnings("unchecked")
|
||||
public Object onPacketSend(@NonNull Object packet) {
|
||||
if (packet instanceof PacketPlayOutPlayerListHeaderFooter) {
|
||||
PacketPlayOutPlayerListHeaderFooter tablist = (PacketPlayOutPlayerListHeaderFooter) packet;
|
||||
if (header == null || footer == null) return packet;
|
||||
if (tablist.header != header.convert() || tablist.footer != footer.convert()) {
|
||||
return newHeaderFooter(header, footer);
|
||||
}
|
||||
}
|
||||
if (!(packet instanceof PacketPlayOutPlayerInfo)) return packet;
|
||||
PacketPlayOutPlayerInfo info = (PacketPlayOutPlayerInfo) packet;
|
||||
EnumPlayerInfoAction action = (EnumPlayerInfoAction) ACTION.get(info);
|
||||
List<Object> updatedList = new ArrayList<>();
|
||||
boolean rewritePacket = false;
|
||||
for (Object nmsData : (List<Object>) PLAYERS.get(info)) {
|
||||
boolean rewriteEntry = false;
|
||||
GameProfile profile = (GameProfile) PlayerInfoData_Profile.get(nmsData);
|
||||
UUID id = profile.getId();
|
||||
IChatBaseComponent displayName = (IChatBaseComponent) PlayerInfoData_DisplayName.get(nmsData);
|
||||
int latency = PlayerInfoData_Latency.getInt(nmsData);
|
||||
int gameMode = ((EnumGamemode)PlayerInfoData_GameMode.get(nmsData)).getId();
|
||||
if (action == EnumPlayerInfoAction.UPDATE_DISPLAY_NAME || action == EnumPlayerInfoAction.ADD_PLAYER) {
|
||||
TabComponent forcedDisplayName = getForcedDisplayNames().get(id);
|
||||
if (forcedDisplayName != null && forcedDisplayName.convert() != displayName) {
|
||||
displayName = forcedDisplayName.convert();
|
||||
rewriteEntry = rewritePacket = true;
|
||||
}
|
||||
}
|
||||
if (action == EnumPlayerInfoAction.UPDATE_GAME_MODE || action == EnumPlayerInfoAction.ADD_PLAYER) {
|
||||
if (getBlockedSpectators().contains(id) && gameMode == 3) {
|
||||
gameMode = 0;
|
||||
rewriteEntry = rewritePacket = true;
|
||||
}
|
||||
}
|
||||
if (action == EnumPlayerInfoAction.UPDATE_LATENCY || action == EnumPlayerInfoAction.ADD_PLAYER) {
|
||||
if (getForcedLatency() != null) {
|
||||
latency = getForcedLatency();
|
||||
rewriteEntry = rewritePacket = true;
|
||||
}
|
||||
}
|
||||
if (action == EnumPlayerInfoAction.ADD_PLAYER) {
|
||||
TAB.getInstance().getFeatureManager().onEntryAdd(player, id, profile.getName());
|
||||
}
|
||||
updatedList.add(rewriteEntry ? newPlayerInfoData(
|
||||
(PacketPlayOutPlayerInfo) packet,
|
||||
profile,
|
||||
latency,
|
||||
EnumGamemode.getById(gameMode),
|
||||
displayName
|
||||
) : nmsData);
|
||||
}
|
||||
if (rewritePacket) {
|
||||
PacketPlayOutPlayerInfo newPacket = new PacketPlayOutPlayerInfo(action, Collections.emptyList());
|
||||
PLAYERS.set(newPacket, updatedList);
|
||||
return newPacket;
|
||||
}
|
||||
return packet;
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private void sendPacket(@NonNull EnumPlayerInfoAction action, @NonNull UUID id, @NonNull String name,
|
||||
@Nullable Skin skin, int latency, int gameMode, @Nullable TabComponent displayName) {
|
||||
PacketPlayOutPlayerInfo packet = new PacketPlayOutPlayerInfo(action);
|
||||
PLAYERS.set(packet, Collections.singletonList(newPlayerInfoData(
|
||||
packet,
|
||||
createProfile(id, name, skin),
|
||||
latency,
|
||||
EnumGamemode.getById(gameMode),
|
||||
displayName == null ? null : displayName.convert())
|
||||
));
|
||||
sendPacket(packet);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@SneakyThrows
|
||||
private Object newPlayerInfoData(@NonNull PacketPlayOutPlayerInfo packet, @NonNull GameProfile profile,
|
||||
int latency, @NonNull EnumGamemode gameMode, @Nullable IChatBaseComponent displayName) {
|
||||
return newPlayerInfoData.newInstance(packet, profile, latency, gameMode, displayName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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().playerConnection.sendPacket(packet);
|
||||
}
|
||||
}
|
||||
-47
@@ -1,47 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.v1_14_R1;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import io.netty.channel.Channel;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.shared.platform.NettyTabListEntryTracker;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import net.minecraft.server.v1_14_R1.PacketPlayOutPlayerInfo;
|
||||
import net.minecraft.server.v1_14_R1.PacketPlayOutPlayerInfo.EnumPlayerInfoAction;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Implementation of TabListEntryTracker.
|
||||
*/
|
||||
public class NMSTabListEntryTracker extends NettyTabListEntryTracker {
|
||||
|
||||
private static final Field ACTION = ReflectionUtils.getOnlyField(PacketPlayOutPlayerInfo.class, EnumPlayerInfoAction.class);
|
||||
private static final Field PLAYERS = ReflectionUtils.getOnlyField(PacketPlayOutPlayerInfo.class, List.class);
|
||||
private static final Class<?> PlayerInfoData = PacketPlayOutPlayerInfo.class.getDeclaredClasses()[0];
|
||||
private static final Field PlayerInfoData_Profile = ReflectionUtils.getOnlyField(PlayerInfoData, GameProfile.class);
|
||||
|
||||
public NMSTabListEntryTracker(@NotNull Channel channel) {
|
||||
super(channel);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
@SuppressWarnings("unchecked")
|
||||
public void onPacketSend(@NotNull Object packet) {
|
||||
if (packet instanceof PacketPlayOutPlayerInfo) {
|
||||
PacketPlayOutPlayerInfo info = (PacketPlayOutPlayerInfo) packet;
|
||||
EnumPlayerInfoAction action = (EnumPlayerInfoAction) ACTION.get(info);
|
||||
for (Object nmsData : (List<Object>) PLAYERS.get(info)) {
|
||||
GameProfile profile = (GameProfile) PlayerInfoData_Profile.get(nmsData);
|
||||
if (action == EnumPlayerInfoAction.ADD_PLAYER) {
|
||||
tablistEntries.add(profile.getId());
|
||||
}
|
||||
if (action == EnumPlayerInfoAction.REMOVE_PLAYER) {
|
||||
tablistEntries.remove(profile.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
dependencies {
|
||||
implementation(projects.bukkit)
|
||||
compileOnly("org.bukkit:craftbukkit:1.15.2-R0.1-SNAPSHOT")
|
||||
}
|
||||
-62
@@ -1,62 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.v1_15_R1;
|
||||
|
||||
import me.neznamy.tab.shared.chat.TabStyle;
|
||||
import me.neznamy.tab.shared.chat.component.object.TabAtlasSprite;
|
||||
import me.neznamy.tab.shared.chat.component.object.TabObjectComponent;
|
||||
import me.neznamy.tab.platforms.bukkit.provider.ComponentConverter;
|
||||
import me.neznamy.tab.shared.chat.component.object.TabPlayerSprite;
|
||||
import net.minecraft.server.v1_15_R1.*;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Component converter using direct NMS code.
|
||||
*/
|
||||
public class NMSComponentConverter extends ComponentConverter<IChatBaseComponent> {
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public IChatBaseComponent newTextComponent(@NotNull String text) {
|
||||
return new ChatComponentText(text);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public IChatBaseComponent newTranslatableComponent(@NotNull String key) {
|
||||
return new ChatMessage(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public IChatBaseComponent newKeybindComponent(@NotNull String keybind) {
|
||||
return new ChatComponentKeybind(keybind);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public IChatBaseComponent newObjectComponent(@NotNull TabAtlasSprite sprite) {
|
||||
return new ChatComponentText(TabObjectComponent.ERROR_MESSAGE);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public IChatBaseComponent newObjectComponent(@NotNull TabPlayerSprite sprite) {
|
||||
return new ChatComponentText(TabObjectComponent.ERROR_MESSAGE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyStyle(@NotNull IChatBaseComponent nmsComponent, @NotNull TabStyle modifier) {
|
||||
nmsComponent.setChatModifier(new ChatModifier()
|
||||
.setColor(modifier.getColor() == null ? null : EnumChatFormat.valueOf(modifier.getColor().getLegacyColor().name()))
|
||||
.setBold(modifier.getBold())
|
||||
.setItalic(modifier.getItalic())
|
||||
.setUnderline(modifier.getUnderlined())
|
||||
.setStrikethrough(modifier.getStrikethrough())
|
||||
.setRandom(modifier.getObfuscated())
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addSibling(@NotNull IChatBaseComponent parent, @NotNull IChatBaseComponent child) {
|
||||
parent.addSibling(child);
|
||||
}
|
||||
}
|
||||
-52
@@ -1,52 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.v1_15_R1;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import lombok.Getter;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.provider.ComponentConverter;
|
||||
import me.neznamy.tab.platforms.bukkit.provider.ImplementationProvider;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import me.neznamy.tab.shared.platform.TabListEntryTracker;
|
||||
import org.bukkit.craftbukkit.v1_15_R1.entity.CraftPlayer;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Implementation provider using direct NMS code for 1.15.x.
|
||||
*/
|
||||
@Getter
|
||||
public class NMSImplementationProvider implements ImplementationProvider {
|
||||
|
||||
@NotNull
|
||||
private final ComponentConverter<?> componentConverter = new NMSComponentConverter();
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Scoreboard newScoreboard(@NotNull BukkitTabPlayer player) {
|
||||
return new NMSPacketScoreboard(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public TabList newTabList(@NotNull BukkitTabPlayer player) {
|
||||
return new NMSPacketTabList(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Channel getChannel(@NotNull Player player) {
|
||||
return ((CraftPlayer)player).getHandle().playerConnection.networkManager.channel;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public TabListEntryTracker newTabListEntryTracker(@NotNull Player player) {
|
||||
return new NMSTabListEntryTracker(getChannel(player));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPing(@NotNull BukkitTabPlayer player) {
|
||||
return ((CraftPlayer)player.getPlayer()).getHandle().ping;
|
||||
}
|
||||
}
|
||||
-169
@@ -1,169 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.v1_15_R1;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.platform.decorators.SafeScoreboard;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import net.minecraft.server.v1_15_R1.*;
|
||||
import org.bukkit.craftbukkit.v1_15_R1.entity.CraftPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* Scoreboard implementation using direct NMS code.
|
||||
*/
|
||||
public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
|
||||
|
||||
private static final ScoreboardTeamBase.EnumNameTagVisibility[] visibilities = ScoreboardTeamBase.EnumNameTagVisibility.values();
|
||||
private static final ScoreboardTeamBase.EnumTeamPush[] collisions = ScoreboardTeamBase.EnumTeamPush.values();
|
||||
private static final Scoreboard dummyScoreboard = new Scoreboard();
|
||||
|
||||
private static final Field TeamPacket_NAME = ReflectionUtils.getFields(PacketPlayOutScoreboardTeam.class, String.class).get(0);
|
||||
private static final Field TeamPacket_ACTION = ReflectionUtils.getInstanceFields(PacketPlayOutScoreboardTeam.class, int.class).get(0);
|
||||
private static final Field TeamPacket_PLAYERS = ReflectionUtils.getOnlyField(PacketPlayOutScoreboardTeam.class, Collection.class);
|
||||
|
||||
private static final Field Objective_OBJECTIVE_NAME = ReflectionUtils.getFields(PacketPlayOutScoreboardObjective.class, String.class).get(0);
|
||||
private static final Field Objective_METHOD = ReflectionUtils.getOnlyField(PacketPlayOutScoreboardObjective.class, int.class);
|
||||
|
||||
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);
|
||||
|
||||
/**
|
||||
* Constructs new instance with given player.
|
||||
*
|
||||
* @param player
|
||||
* Player this scoreboard will belong to
|
||||
*/
|
||||
public NMSPacketScoreboard(@NotNull BukkitTabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerObjective(@NonNull Objective objective) {
|
||||
ScoreboardObjective obj = new ScoreboardObjective(
|
||||
dummyScoreboard,
|
||||
objective.getName(),
|
||||
IScoreboardCriteria.DUMMY,
|
||||
objective.getTitle().convert(),
|
||||
IScoreboardCriteria.EnumScoreboardHealthDisplay.values()[objective.getHealthDisplay().ordinal()]
|
||||
);
|
||||
objective.setPlatformObjective(obj);
|
||||
sendPacket(new PacketPlayOutScoreboardObjective(obj, ObjectiveAction.REGISTER));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDisplaySlot(@NonNull Objective objective) {
|
||||
sendPacket(new PacketPlayOutScoreboardDisplayObjective(
|
||||
objective.getDisplaySlot().ordinal(),
|
||||
(ScoreboardObjective) objective.getPlatformObjective()
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterObjective(@NonNull Objective objective) {
|
||||
sendPacket(new PacketPlayOutScoreboardObjective((ScoreboardObjective) objective.getPlatformObjective(), ObjectiveAction.UNREGISTER));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateObjective(@NonNull Objective objective) {
|
||||
ScoreboardObjective obj = (ScoreboardObjective) objective.getPlatformObjective();
|
||||
obj.setDisplayName(objective.getTitle().convert());
|
||||
obj.setRenderType(IScoreboardCriteria.EnumScoreboardHealthDisplay.valueOf(objective.getHealthDisplay().name()));
|
||||
sendPacket(new PacketPlayOutScoreboardObjective(obj, ObjectiveAction.UPDATE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setScore(@NonNull Score score) {
|
||||
sendPacket(new PacketPlayOutScoreboardScore(
|
||||
ScoreboardServer.Action.CHANGE,
|
||||
score.getObjective().getName(),
|
||||
score.getHolder(),
|
||||
score.getValue()
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeScore(@NonNull Score score) {
|
||||
sendPacket(new PacketPlayOutScoreboardScore(
|
||||
ScoreboardServer.Action.REMOVE,
|
||||
score.getObjective().getName(),
|
||||
score.getHolder(),
|
||||
score.getValue()
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Object createTeam(@NonNull String name) {
|
||||
return new ScoreboardTeam(dummyScoreboard, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerTeam(@NonNull Team team) {
|
||||
updateTeamProperties(team);
|
||||
ScoreboardTeam t = (ScoreboardTeam) team.getPlatformTeam();
|
||||
t.getPlayerNameSet().addAll(team.getPlayers());
|
||||
sendPacket(new PacketPlayOutScoreboardTeam(t, TeamAction.CREATE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterTeam(@NonNull Team team) {
|
||||
sendPacket(new PacketPlayOutScoreboardTeam((ScoreboardTeam) team.getPlatformTeam(), TeamAction.REMOVE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateTeam(@NonNull Team team) {
|
||||
updateTeamProperties(team);
|
||||
sendPacket(new PacketPlayOutScoreboardTeam((ScoreboardTeam) team.getPlatformTeam(), TeamAction.UPDATE));
|
||||
}
|
||||
|
||||
private void updateTeamProperties(@NonNull Team team) {
|
||||
ScoreboardTeam t = (ScoreboardTeam) team.getPlatformTeam();
|
||||
t.setAllowFriendlyFire((team.getOptions() & 0x01) != 0);
|
||||
t.setCanSeeFriendlyInvisibles((team.getOptions() & 0x02) != 0);
|
||||
t.setNameTagVisibility(visibilities[team.getVisibility().ordinal()]);
|
||||
t.setCollisionRule(collisions[team.getCollision().ordinal()]);
|
||||
t.setPrefix(team.getPrefix().convert());
|
||||
t.setSuffix(team.getSuffix().convert());
|
||||
t.setColor(EnumChatFormat.valueOf(team.getColor().name()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
@SuppressWarnings("unchecked")
|
||||
@NotNull
|
||||
public Object onPacketSend(@NonNull Object packet) {
|
||||
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) {
|
||||
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));
|
||||
}
|
||||
}
|
||||
return packet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the packet to the player.
|
||||
*
|
||||
* @param packet
|
||||
* Packet to send
|
||||
*/
|
||||
private void sendPacket(@NotNull Packet<?> packet) {
|
||||
((CraftPlayer)player.getPlayer()).getHandle().playerConnection.sendPacket(packet);
|
||||
}
|
||||
}
|
||||
-227
@@ -1,227 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.v1_15_R1;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import com.mojang.authlib.properties.Property;
|
||||
import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.shared.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.server.v1_15_R1.*;
|
||||
import net.minecraft.server.v1_15_R1.PacketPlayOutPlayerInfo.EnumPlayerInfoAction;
|
||||
import org.bukkit.craftbukkit.v1_15_R1.entity.CraftPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* TabList implementation using direct NMS code.
|
||||
*/
|
||||
public class NMSPacketTabList extends TrackedTabList<BukkitTabPlayer> {
|
||||
|
||||
// PlayerInfoData subclass is broken, using reflection to get it
|
||||
private static final Class<?> PlayerInfoData = PacketPlayOutPlayerInfo.class.getDeclaredClasses()[0];
|
||||
private static final Constructor<?> newPlayerInfoData = ReflectionUtils.getOnlyConstructor(PlayerInfoData);
|
||||
|
||||
private static final Field ACTION = ReflectionUtils.getOnlyField(PacketPlayOutPlayerInfo.class, EnumPlayerInfoAction.class);
|
||||
private static final Field PLAYERS = ReflectionUtils.getOnlyField(PacketPlayOutPlayerInfo.class, List.class);
|
||||
|
||||
private static final Field PlayerInfoData_Profile = ReflectionUtils.getOnlyField(PlayerInfoData, GameProfile.class);
|
||||
private static final Field PlayerInfoData_Latency = ReflectionUtils.getFields(PlayerInfoData, int.class).get(0);
|
||||
private static final Field PlayerInfoData_DisplayName = ReflectionUtils.getOnlyField(PlayerInfoData, IChatBaseComponent.class);
|
||||
private static final Field PlayerInfoData_GameMode = ReflectionUtils.getOnlyField(PlayerInfoData, EnumGamemode.class);
|
||||
|
||||
/**
|
||||
* Constructs new instance.
|
||||
*
|
||||
* @param player
|
||||
* Player this tablist will belong to
|
||||
*/
|
||||
public NMSPacketTabList(@NotNull BukkitTabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void removeEntry(@NonNull UUID entry) {
|
||||
sendPacket(EnumPlayerInfoAction.REMOVE_PLAYER, entry, "", null, 0, 0, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDisplayName0(@NonNull UUID entry, @Nullable TabComponent displayName) {
|
||||
sendPacket(EnumPlayerInfoAction.UPDATE_DISPLAY_NAME, entry, "", null, 0, 0, displayName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateLatency(@NonNull UUID entry, int latency) {
|
||||
sendPacket(EnumPlayerInfoAction.UPDATE_LATENCY, entry, "", null, latency, 0, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateGameMode(@NonNull UUID entry, int gameMode) {
|
||||
sendPacket(EnumPlayerInfoAction.UPDATE_GAME_MODE, entry, "", null, 0, gameMode, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateListed(@NonNull UUID entry, boolean listed) {
|
||||
// Added in 1.19.3
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateListOrder(@NonNull UUID entry, int listOrder) {
|
||||
// Added in 1.21.2
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateHat(@NonNull UUID entry, boolean showHat) {
|
||||
// Added in 1.21.4
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addEntry0(@NonNull Entry entry) {
|
||||
sendPacket(EnumPlayerInfoAction.ADD_PLAYER, entry.getUniqueId(), entry.getName(), entry.getSkin(), entry.getLatency(),
|
||||
entry.getGameMode(), entry.getDisplayName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPlayerListHeaderFooter0(@NonNull TabComponent header, @NonNull TabComponent footer) {
|
||||
sendPacket(newHeaderFooter(header, footer));
|
||||
}
|
||||
|
||||
@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());
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private PacketPlayOutPlayerListHeaderFooter newHeaderFooter(@NotNull TabComponent header, @NonNull TabComponent footer) {
|
||||
PacketPlayOutPlayerListHeaderFooter packet = new PacketPlayOutPlayerListHeaderFooter();
|
||||
packet.header = header.convert();
|
||||
packet.footer = footer.convert();
|
||||
return packet;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
@NotNull
|
||||
@SuppressWarnings("unchecked")
|
||||
public Object onPacketSend(@NonNull Object packet) {
|
||||
if (packet instanceof PacketPlayOutPlayerListHeaderFooter) {
|
||||
PacketPlayOutPlayerListHeaderFooter tablist = (PacketPlayOutPlayerListHeaderFooter) packet;
|
||||
if (header == null || footer == null) return packet;
|
||||
if (tablist.header != header.convert() || tablist.footer != footer.convert()) {
|
||||
return newHeaderFooter(header, footer);
|
||||
}
|
||||
}
|
||||
if (!(packet instanceof PacketPlayOutPlayerInfo)) return packet;
|
||||
PacketPlayOutPlayerInfo info = (PacketPlayOutPlayerInfo) packet;
|
||||
EnumPlayerInfoAction action = (EnumPlayerInfoAction) ACTION.get(info);
|
||||
List<Object> updatedList = new ArrayList<>();
|
||||
boolean rewritePacket = false;
|
||||
for (Object nmsData : (List<Object>) PLAYERS.get(info)) {
|
||||
boolean rewriteEntry = false;
|
||||
GameProfile profile = (GameProfile) PlayerInfoData_Profile.get(nmsData);
|
||||
UUID id = profile.getId();
|
||||
IChatBaseComponent displayName = (IChatBaseComponent) PlayerInfoData_DisplayName.get(nmsData);
|
||||
int latency = PlayerInfoData_Latency.getInt(nmsData);
|
||||
int gameMode = ((EnumGamemode)PlayerInfoData_GameMode.get(nmsData)).getId();
|
||||
if (action == EnumPlayerInfoAction.UPDATE_DISPLAY_NAME || action == EnumPlayerInfoAction.ADD_PLAYER) {
|
||||
TabComponent forcedDisplayName = getForcedDisplayNames().get(id);
|
||||
if (forcedDisplayName != null && forcedDisplayName.convert() != displayName) {
|
||||
displayName = forcedDisplayName.convert();
|
||||
rewriteEntry = rewritePacket = true;
|
||||
}
|
||||
}
|
||||
if (action == EnumPlayerInfoAction.UPDATE_GAME_MODE || action == EnumPlayerInfoAction.ADD_PLAYER) {
|
||||
if (getBlockedSpectators().contains(id) && gameMode == 3) {
|
||||
gameMode = 0;
|
||||
rewriteEntry = rewritePacket = true;
|
||||
}
|
||||
}
|
||||
if (action == EnumPlayerInfoAction.UPDATE_LATENCY || action == EnumPlayerInfoAction.ADD_PLAYER) {
|
||||
if (getForcedLatency() != null) {
|
||||
latency = getForcedLatency();
|
||||
rewriteEntry = rewritePacket = true;
|
||||
}
|
||||
}
|
||||
if (action == EnumPlayerInfoAction.ADD_PLAYER) {
|
||||
TAB.getInstance().getFeatureManager().onEntryAdd(player, id, profile.getName());
|
||||
}
|
||||
updatedList.add(rewriteEntry ? newPlayerInfoData(
|
||||
(PacketPlayOutPlayerInfo) packet,
|
||||
profile,
|
||||
latency,
|
||||
EnumGamemode.getById(gameMode),
|
||||
displayName
|
||||
) : nmsData);
|
||||
}
|
||||
if (rewritePacket) {
|
||||
PacketPlayOutPlayerInfo newPacket = new PacketPlayOutPlayerInfo(action, Collections.emptyList());
|
||||
PLAYERS.set(newPacket, updatedList);
|
||||
return newPacket;
|
||||
}
|
||||
return packet;
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private void sendPacket(@NonNull EnumPlayerInfoAction action, @NonNull UUID id, @NonNull String name,
|
||||
@Nullable Skin skin, int latency, int gameMode, @Nullable TabComponent displayName) {
|
||||
PacketPlayOutPlayerInfo packet = new PacketPlayOutPlayerInfo(action);
|
||||
PLAYERS.set(packet, Collections.singletonList(newPlayerInfoData(
|
||||
packet,
|
||||
createProfile(id, name, skin),
|
||||
latency,
|
||||
EnumGamemode.getById(gameMode),
|
||||
displayName == null ? null : displayName.convert())
|
||||
));
|
||||
sendPacket(packet);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@SneakyThrows
|
||||
private Object newPlayerInfoData(@NonNull PacketPlayOutPlayerInfo packet, @NonNull GameProfile profile,
|
||||
int latency, @NonNull EnumGamemode gameMode, @Nullable IChatBaseComponent displayName) {
|
||||
return newPlayerInfoData.newInstance(packet, profile, latency, gameMode, displayName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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().playerConnection.sendPacket(packet);
|
||||
}
|
||||
}
|
||||
-47
@@ -1,47 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.v1_15_R1;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import io.netty.channel.Channel;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.shared.platform.NettyTabListEntryTracker;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import net.minecraft.server.v1_15_R1.PacketPlayOutPlayerInfo;
|
||||
import net.minecraft.server.v1_15_R1.PacketPlayOutPlayerInfo.EnumPlayerInfoAction;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Implementation of TabListEntryTracker.
|
||||
*/
|
||||
public class NMSTabListEntryTracker extends NettyTabListEntryTracker {
|
||||
|
||||
private static final Field ACTION = ReflectionUtils.getOnlyField(PacketPlayOutPlayerInfo.class, EnumPlayerInfoAction.class);
|
||||
private static final Field PLAYERS = ReflectionUtils.getOnlyField(PacketPlayOutPlayerInfo.class, List.class);
|
||||
private static final Class<?> PlayerInfoData = PacketPlayOutPlayerInfo.class.getDeclaredClasses()[0];
|
||||
private static final Field PlayerInfoData_Profile = ReflectionUtils.getOnlyField(PlayerInfoData, GameProfile.class);
|
||||
|
||||
public NMSTabListEntryTracker(@NotNull Channel channel) {
|
||||
super(channel);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
@SuppressWarnings("unchecked")
|
||||
public void onPacketSend(@NotNull Object packet) {
|
||||
if (packet instanceof PacketPlayOutPlayerInfo) {
|
||||
PacketPlayOutPlayerInfo info = (PacketPlayOutPlayerInfo) packet;
|
||||
EnumPlayerInfoAction action = (EnumPlayerInfoAction) ACTION.get(info);
|
||||
for (Object nmsData : (List<Object>) PLAYERS.get(info)) {
|
||||
GameProfile profile = (GameProfile) PlayerInfoData_Profile.get(nmsData);
|
||||
if (action == EnumPlayerInfoAction.ADD_PLAYER) {
|
||||
tablistEntries.add(profile.getId());
|
||||
}
|
||||
if (action == EnumPlayerInfoAction.REMOVE_PLAYER) {
|
||||
tablistEntries.remove(profile.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
dependencies {
|
||||
implementation(projects.bukkit)
|
||||
compileOnly("org.bukkit:craftbukkit:1.9.4-R0.1-SNAPSHOT")
|
||||
}
|
||||
-62
@@ -1,62 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.v1_9_R2;
|
||||
|
||||
import me.neznamy.tab.shared.chat.TabStyle;
|
||||
import me.neznamy.tab.shared.chat.component.object.TabAtlasSprite;
|
||||
import me.neznamy.tab.shared.chat.component.object.TabObjectComponent;
|
||||
import me.neznamy.tab.platforms.bukkit.provider.ComponentConverter;
|
||||
import me.neznamy.tab.shared.chat.component.object.TabPlayerSprite;
|
||||
import net.minecraft.server.v1_9_R2.*;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Component converter using direct NMS code.
|
||||
*/
|
||||
public class NMSComponentConverter extends ComponentConverter<IChatBaseComponent> {
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public IChatBaseComponent newTextComponent(@NotNull String text) {
|
||||
return new ChatComponentText(text);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public IChatBaseComponent newTranslatableComponent(@NotNull String key) {
|
||||
return new ChatMessage(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public IChatBaseComponent newKeybindComponent(@NotNull String keybind) {
|
||||
return new ChatComponentText("<Keybind components were added in 1.12>");
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public IChatBaseComponent newObjectComponent(@NotNull TabAtlasSprite sprite) {
|
||||
return new ChatComponentText(TabObjectComponent.ERROR_MESSAGE);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public IChatBaseComponent newObjectComponent(@NotNull TabPlayerSprite sprite) {
|
||||
return new ChatComponentText(TabObjectComponent.ERROR_MESSAGE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyStyle(@NotNull IChatBaseComponent nmsComponent, @NotNull TabStyle modifier) {
|
||||
nmsComponent.setChatModifier(new ChatModifier()
|
||||
.setColor(modifier.getColor() == null ? null : EnumChatFormat.valueOf(modifier.getColor().getLegacyColor().name()))
|
||||
.setBold(modifier.getBold())
|
||||
.setItalic(modifier.getItalic())
|
||||
.setUnderline(modifier.getUnderlined())
|
||||
.setStrikethrough(modifier.getStrikethrough())
|
||||
.setRandom(modifier.getObfuscated())
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addSibling(@NotNull IChatBaseComponent parent, @NotNull IChatBaseComponent child) {
|
||||
parent.addSibling(child);
|
||||
}
|
||||
}
|
||||
-52
@@ -1,52 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.v1_9_R2;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import lombok.Getter;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.provider.ComponentConverter;
|
||||
import me.neznamy.tab.platforms.bukkit.provider.ImplementationProvider;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import me.neznamy.tab.shared.platform.TabListEntryTracker;
|
||||
import org.bukkit.craftbukkit.v1_9_R2.entity.CraftPlayer;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Implementation provider using direct NMS code for 1.9.4.
|
||||
*/
|
||||
@Getter
|
||||
public class NMSImplementationProvider implements ImplementationProvider {
|
||||
|
||||
@NotNull
|
||||
private final ComponentConverter<?> componentConverter = new NMSComponentConverter();
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Scoreboard newScoreboard(@NotNull BukkitTabPlayer player) {
|
||||
return new NMSPacketScoreboard(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public TabList newTabList(@NotNull BukkitTabPlayer player) {
|
||||
return new NMSPacketTabList(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Channel getChannel(@NotNull Player player) {
|
||||
return ((CraftPlayer)player).getHandle().playerConnection.networkManager.channel;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public TabListEntryTracker newTabListEntryTracker(@NotNull Player player) {
|
||||
return new NMSTabListEntryTracker(getChannel(player));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPing(@NotNull BukkitTabPlayer player) {
|
||||
return ((CraftPlayer)player.getPlayer()).getHandle().ping;
|
||||
}
|
||||
}
|
||||
-164
@@ -1,164 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.v1_9_R2;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.shared.Limitations;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.platform.decorators.SafeScoreboard;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import net.minecraft.server.v1_9_R2.*;
|
||||
import org.bukkit.craftbukkit.v1_9_R2.entity.CraftPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* Scoreboard implementation using direct NMS code.
|
||||
*/
|
||||
public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
|
||||
|
||||
private static final ScoreboardTeamBase.EnumNameTagVisibility[] visibilities = ScoreboardTeamBase.EnumNameTagVisibility.values();
|
||||
private static final ScoreboardTeamBase.EnumTeamPush[] collisions = ScoreboardTeamBase.EnumTeamPush.values();
|
||||
private static final Scoreboard dummyScoreboard = new Scoreboard();
|
||||
private static final IScoreboardCriteria INTEGER = new ScoreboardBaseCriteria("dummy");
|
||||
private static final IScoreboardCriteria HEARTS = new ScoreboardHealthCriteria("health");
|
||||
|
||||
private static final Field TeamPacket_NAME = ReflectionUtils.getFields(PacketPlayOutScoreboardTeam.class, String.class).get(0);
|
||||
private static final Field TeamPacket_ACTION = ReflectionUtils.getInstanceFields(PacketPlayOutScoreboardTeam.class, int.class).get(1);
|
||||
private static final Field TeamPacket_PLAYERS = ReflectionUtils.getOnlyField(PacketPlayOutScoreboardTeam.class, Collection.class);
|
||||
|
||||
private static final Field Objective_OBJECTIVE_NAME = ReflectionUtils.getFields(PacketPlayOutScoreboardObjective.class, String.class).get(0);
|
||||
private static final Field Objective_METHOD = ReflectionUtils.getOnlyField(PacketPlayOutScoreboardObjective.class, int.class);
|
||||
|
||||
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);
|
||||
|
||||
/**
|
||||
* Constructs new instance with given player.
|
||||
*
|
||||
* @param player
|
||||
* Player this scoreboard will belong to
|
||||
*/
|
||||
public NMSPacketScoreboard(@NotNull BukkitTabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerObjective(@NonNull Objective objective) {
|
||||
IScoreboardCriteria healthDisplay = objective.getHealthDisplay() == HealthDisplay.INTEGER ? INTEGER : HEARTS;
|
||||
ScoreboardObjective obj = new ScoreboardObjective(dummyScoreboard, objective.getName(), healthDisplay);
|
||||
obj.setDisplayName(maybeCut(objective.getTitle().toLegacyText(), Limitations.SCOREBOARD_TITLE_PRE_1_13));
|
||||
objective.setPlatformObjective(obj);
|
||||
sendPacket(new PacketPlayOutScoreboardObjective(obj, ObjectiveAction.REGISTER));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDisplaySlot(@NonNull Objective objective) {
|
||||
sendPacket(new PacketPlayOutScoreboardDisplayObjective(objective.getDisplaySlot().ordinal(), (ScoreboardObjective) objective.getPlatformObjective()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterObjective(@NonNull Objective objective) {
|
||||
sendPacket(new PacketPlayOutScoreboardObjective((ScoreboardObjective) objective.getPlatformObjective(), ObjectiveAction.UNREGISTER));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateObjective(@NonNull Objective objective) {
|
||||
IScoreboardCriteria healthDisplay = objective.getHealthDisplay() == HealthDisplay.INTEGER ? INTEGER : HEARTS;
|
||||
ScoreboardObjective obj = new ScoreboardObjective(dummyScoreboard, objective.getName(), healthDisplay);
|
||||
obj.setDisplayName(maybeCut(objective.getTitle().toLegacyText(), Limitations.SCOREBOARD_TITLE_PRE_1_13));
|
||||
sendPacket(new PacketPlayOutScoreboardObjective(obj, ObjectiveAction.UPDATE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setScore(@NonNull Score score) {
|
||||
ScoreboardScore s = new ScoreboardScore(dummyScoreboard, (ScoreboardObjective) score.getObjective().getPlatformObjective(), score.getHolder());
|
||||
s.setScore(score.getValue());
|
||||
sendPacket(new PacketPlayOutScoreboardScore(s));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeScore(@NonNull Score score) {
|
||||
sendPacket(new PacketPlayOutScoreboardScore(score.getHolder()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Object createTeam(@NonNull String name) {
|
||||
return new ScoreboardTeam(dummyScoreboard, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerTeam(@NonNull Team team) {
|
||||
updateTeamProperties(team);
|
||||
ScoreboardTeam t = (ScoreboardTeam) team.getPlatformTeam();
|
||||
t.getPlayerNameSet().addAll(team.getPlayers());
|
||||
sendPacket(new PacketPlayOutScoreboardTeam(t, TeamAction.CREATE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterTeam(@NonNull Team team) {
|
||||
sendPacket(new PacketPlayOutScoreboardTeam((ScoreboardTeam) team.getPlatformTeam(), TeamAction.REMOVE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateTeam(@NonNull Team team) {
|
||||
updateTeamProperties(team);
|
||||
sendPacket(new PacketPlayOutScoreboardTeam((ScoreboardTeam) team.getPlatformTeam(), TeamAction.UPDATE));
|
||||
}
|
||||
|
||||
private void updateTeamProperties(@NonNull Team team) {
|
||||
ScoreboardTeam t = (ScoreboardTeam) team.getPlatformTeam();
|
||||
t.setAllowFriendlyFire((team.getOptions() & 0x01) != 0);
|
||||
t.setCanSeeFriendlyInvisibles((team.getOptions() & 0x02) != 0);
|
||||
t.setNameTagVisibility(visibilities[team.getVisibility().ordinal()]);
|
||||
t.setCollisionRule(collisions[team.getCollision().ordinal()]);
|
||||
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")
|
||||
@NotNull
|
||||
public Object onPacketSend(@NonNull Object packet) {
|
||||
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) {
|
||||
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));
|
||||
}
|
||||
}
|
||||
return packet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the packet to the player.
|
||||
*
|
||||
* @param packet
|
||||
* Packet to send
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
-233
@@ -1,233 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.v1_9_R2;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import com.mojang.authlib.properties.Property;
|
||||
import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.chat.component.TabComponent;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import me.neznamy.tab.shared.platform.decorators.TrackedTabList;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import net.minecraft.server.v1_9_R2.*;
|
||||
import net.minecraft.server.v1_9_R2.PacketPlayOutPlayerInfo.EnumPlayerInfoAction;
|
||||
import net.minecraft.server.v1_9_R2.WorldSettings.EnumGamemode;
|
||||
import org.bukkit.craftbukkit.v1_9_R2.entity.CraftPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* TabList implementation using direct NMS code.
|
||||
*/
|
||||
public class NMSPacketTabList extends TrackedTabList<BukkitTabPlayer> {
|
||||
|
||||
// PlayerInfoData subclass is broken, using reflection to get it
|
||||
private static final Class<?> PlayerInfoData = Arrays.stream(PacketPlayOutPlayerInfo.class.getDeclaredClasses()).filter(c -> !c.isEnum() && c.getConstructors().length > 0).findFirst().get();
|
||||
private static final Constructor<?> newPlayerInfoData = ReflectionUtils.getOnlyConstructor(PlayerInfoData);
|
||||
|
||||
private static final Field ACTION = ReflectionUtils.getOnlyField(PacketPlayOutPlayerInfo.class, EnumPlayerInfoAction.class);
|
||||
private static final Field PLAYERS = ReflectionUtils.getOnlyField(PacketPlayOutPlayerInfo.class, List.class);
|
||||
|
||||
private static final Field PlayerInfoData_Profile = ReflectionUtils.getOnlyField(PlayerInfoData, GameProfile.class);
|
||||
private static final Field PlayerInfoData_Latency = ReflectionUtils.getFields(PlayerInfoData, int.class).get(0);
|
||||
private static final Field PlayerInfoData_DisplayName = ReflectionUtils.getOnlyField(PlayerInfoData, IChatBaseComponent.class);
|
||||
private static final Field PlayerInfoData_GameMode = ReflectionUtils.getOnlyField(PlayerInfoData, EnumGamemode.class);
|
||||
|
||||
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);
|
||||
|
||||
/**
|
||||
* Constructs new instance.
|
||||
*
|
||||
* @param player
|
||||
* Player this tablist will belong to
|
||||
*/
|
||||
public NMSPacketTabList(@NotNull BukkitTabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void removeEntry(@NonNull UUID entry) {
|
||||
sendPacket(EnumPlayerInfoAction.REMOVE_PLAYER, entry, "", null, 0, 0, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDisplayName0(@NonNull UUID entry, @Nullable TabComponent displayName) {
|
||||
sendPacket(EnumPlayerInfoAction.UPDATE_DISPLAY_NAME, entry, "", null, 0, 0, displayName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateLatency(@NonNull UUID entry, int latency) {
|
||||
sendPacket(EnumPlayerInfoAction.UPDATE_LATENCY, entry, "", null, latency, 0, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateGameMode(@NonNull UUID entry, int gameMode) {
|
||||
sendPacket(EnumPlayerInfoAction.UPDATE_GAME_MODE, entry, "", null, 0, gameMode, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateListed(@NonNull UUID entry, boolean listed) {
|
||||
// Added in 1.19.3
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateListOrder(@NonNull UUID entry, int listOrder) {
|
||||
// Added in 1.21.2
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateHat(@NonNull UUID entry, boolean showHat) {
|
||||
// Added in 1.21.4
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addEntry0(@NonNull Entry entry) {
|
||||
sendPacket(EnumPlayerInfoAction.ADD_PLAYER, entry.getUniqueId(), entry.getName(), entry.getSkin(), entry.getLatency(),
|
||||
entry.getGameMode(), entry.getDisplayName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPlayerListHeaderFooter0(@NonNull TabComponent header, @NonNull TabComponent footer) {
|
||||
sendPacket(newHeaderFooter(header, footer));
|
||||
}
|
||||
|
||||
@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());
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@SneakyThrows
|
||||
private PacketPlayOutPlayerListHeaderFooter newHeaderFooter(@NotNull TabComponent header, @NonNull TabComponent footer) {
|
||||
PacketPlayOutPlayerListHeaderFooter packet = new PacketPlayOutPlayerListHeaderFooter(header.convert());
|
||||
FOOTER.set(packet, footer.convert());
|
||||
return packet;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
@NotNull
|
||||
@SuppressWarnings("unchecked")
|
||||
public Object onPacketSend(@NonNull Object packet) {
|
||||
if (packet instanceof PacketPlayOutPlayerListHeaderFooter) {
|
||||
PacketPlayOutPlayerListHeaderFooter tablist = (PacketPlayOutPlayerListHeaderFooter) packet;
|
||||
if (header == null || footer == null) return packet;
|
||||
IChatBaseComponent header = (IChatBaseComponent) HEADER.get(tablist);
|
||||
IChatBaseComponent footer = (IChatBaseComponent) FOOTER.get(tablist);
|
||||
if (header != this.header.convert() || footer != this.footer.convert()) {
|
||||
return newHeaderFooter(this.header, this.footer);
|
||||
}
|
||||
}
|
||||
if (!(packet instanceof PacketPlayOutPlayerInfo)) return packet;
|
||||
PacketPlayOutPlayerInfo info = (PacketPlayOutPlayerInfo) packet;
|
||||
EnumPlayerInfoAction action = (EnumPlayerInfoAction) ACTION.get(info);
|
||||
List<Object> updatedList = new ArrayList<>();
|
||||
boolean rewritePacket = false;
|
||||
for (Object nmsData : (List<Object>) PLAYERS.get(info)) {
|
||||
boolean rewriteEntry = false;
|
||||
GameProfile profile = (GameProfile) PlayerInfoData_Profile.get(nmsData);
|
||||
UUID id = profile.getId();
|
||||
IChatBaseComponent displayName = (IChatBaseComponent) PlayerInfoData_DisplayName.get(nmsData);
|
||||
int latency = PlayerInfoData_Latency.getInt(nmsData);
|
||||
int gameMode = ((EnumGamemode)PlayerInfoData_GameMode.get(nmsData)).getId();
|
||||
if (action == EnumPlayerInfoAction.UPDATE_DISPLAY_NAME || action == EnumPlayerInfoAction.ADD_PLAYER) {
|
||||
TabComponent forcedDisplayName = getForcedDisplayNames().get(id);
|
||||
if (forcedDisplayName != null && forcedDisplayName.convert() != displayName) {
|
||||
displayName = forcedDisplayName.convert();
|
||||
rewriteEntry = rewritePacket = true;
|
||||
}
|
||||
}
|
||||
if (action == EnumPlayerInfoAction.UPDATE_GAME_MODE || action == EnumPlayerInfoAction.ADD_PLAYER) {
|
||||
if (getBlockedSpectators().contains(id) && gameMode == 3) {
|
||||
gameMode = 0;
|
||||
rewriteEntry = rewritePacket = true;
|
||||
}
|
||||
}
|
||||
if (action == EnumPlayerInfoAction.UPDATE_LATENCY || action == EnumPlayerInfoAction.ADD_PLAYER) {
|
||||
if (getForcedLatency() != null) {
|
||||
latency = getForcedLatency();
|
||||
rewriteEntry = rewritePacket = true;
|
||||
}
|
||||
}
|
||||
if (action == EnumPlayerInfoAction.ADD_PLAYER) {
|
||||
TAB.getInstance().getFeatureManager().onEntryAdd(player, id, profile.getName());
|
||||
}
|
||||
updatedList.add(rewriteEntry ? newPlayerInfoData(
|
||||
(PacketPlayOutPlayerInfo) packet,
|
||||
profile,
|
||||
latency,
|
||||
EnumGamemode.getById(gameMode),
|
||||
displayName
|
||||
) : nmsData);
|
||||
}
|
||||
if (rewritePacket) {
|
||||
PacketPlayOutPlayerInfo newPacket = new PacketPlayOutPlayerInfo(action, Collections.emptyList());
|
||||
PLAYERS.set(newPacket, updatedList);
|
||||
return newPacket;
|
||||
}
|
||||
return packet;
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private void sendPacket(@NonNull EnumPlayerInfoAction action, @NonNull UUID id, @NonNull String name,
|
||||
@Nullable Skin skin, int latency, int gameMode, @Nullable TabComponent displayName) {
|
||||
PacketPlayOutPlayerInfo packet = new PacketPlayOutPlayerInfo(action);
|
||||
PLAYERS.set(packet, Collections.singletonList(newPlayerInfoData(
|
||||
packet,
|
||||
createProfile(id, name, skin),
|
||||
latency,
|
||||
EnumGamemode.getById(gameMode),
|
||||
displayName == null ? null : displayName.convert())
|
||||
));
|
||||
sendPacket(packet);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@SneakyThrows
|
||||
private Object newPlayerInfoData(@NonNull PacketPlayOutPlayerInfo packet, @NonNull GameProfile profile,
|
||||
int latency, @NonNull EnumGamemode gameMode, @Nullable IChatBaseComponent displayName) {
|
||||
return newPlayerInfoData.newInstance(packet, profile, latency, gameMode, displayName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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().playerConnection.sendPacket(packet);
|
||||
}
|
||||
}
|
||||
-48
@@ -1,48 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.v1_9_R2;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import io.netty.channel.Channel;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.shared.platform.NettyTabListEntryTracker;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import net.minecraft.server.v1_9_R2.PacketPlayOutPlayerInfo;
|
||||
import net.minecraft.server.v1_9_R2.PacketPlayOutPlayerInfo.EnumPlayerInfoAction;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Implementation of TabListEntryTracker.
|
||||
*/
|
||||
public class NMSTabListEntryTracker extends NettyTabListEntryTracker {
|
||||
|
||||
private static final Field ACTION = ReflectionUtils.getOnlyField(PacketPlayOutPlayerInfo.class, EnumPlayerInfoAction.class);
|
||||
private static final Field PLAYERS = ReflectionUtils.getOnlyField(PacketPlayOutPlayerInfo.class, List.class);
|
||||
private static final Class<?> PlayerInfoData = Arrays.stream(PacketPlayOutPlayerInfo.class.getDeclaredClasses()).filter(c -> !c.isEnum() && c.getConstructors().length > 0).findFirst().get();
|
||||
private static final Field PlayerInfoData_Profile = ReflectionUtils.getOnlyField(PlayerInfoData, GameProfile.class);
|
||||
|
||||
public NMSTabListEntryTracker(@NotNull Channel channel) {
|
||||
super(channel);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
@SuppressWarnings("unchecked")
|
||||
public void onPacketSend(@NotNull Object packet) {
|
||||
if (packet instanceof PacketPlayOutPlayerInfo) {
|
||||
PacketPlayOutPlayerInfo info = (PacketPlayOutPlayerInfo) packet;
|
||||
EnumPlayerInfoAction action = (EnumPlayerInfoAction) ACTION.get(info);
|
||||
for (Object nmsData : (List<Object>) PLAYERS.get(info)) {
|
||||
GameProfile profile = (GameProfile) PlayerInfoData_Profile.get(nmsData);
|
||||
if (action == EnumPlayerInfoAction.ADD_PLAYER) {
|
||||
tablistEntries.add(profile.getId());
|
||||
}
|
||||
if (action == EnumPlayerInfoAction.REMOVE_PLAYER) {
|
||||
tablistEntries.remove(profile.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
dependencies {
|
||||
implementation(projects.shared)
|
||||
implementation("org.bstats:bstats-bungeecord:3.1.0")
|
||||
compileOnly("net.md-5:bungeecord-api:1.21-R0.5-SNAPSHOT")
|
||||
compileOnly("net.md-5:bungeecord-api:26.1-R0.1-SNAPSHOT")
|
||||
compileOnly("net.md-5:bungeecord-proxy:1.21-R0.4-SNAPSHOT")
|
||||
compileOnly("com.github.limework.redisbungee:RedisBungee-Bungee:0.11.0")
|
||||
compileOnly("com.github.LeonMangler:PremiumVanishAPI:2.8.8")
|
||||
|
||||
@@ -31,6 +31,7 @@ import me.neznamy.tab.shared.proxy.ProxyPlatform;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import net.md_5.bungee.api.CommandSender;
|
||||
import net.md_5.bungee.api.ProxyConfig;
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
import net.md_5.bungee.api.chat.*;
|
||||
import net.md_5.bungee.api.chat.TextComponent;
|
||||
@@ -76,8 +77,20 @@ public class BungeePlatform extends ProxyPlatform {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public void loadPlayers() {
|
||||
try {
|
||||
ProxyConfig config = ProxyServer.getInstance().getConfig();
|
||||
if ((boolean) config.getClass().getMethod("isDisableTabListRewrite").invoke(config)) {
|
||||
logWarn(TabComponent.legacyText(
|
||||
"Waterfall's \"disable_tab_list_rewrite: true\" option may cause " +
|
||||
"the plugin to not work correctly. Disable it to avoid issues."
|
||||
));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// Not waterfall
|
||||
}
|
||||
for (ProxiedPlayer p : ProxyServer.getInstance().getPlayers()) {
|
||||
TAB.getInstance().addPlayer(new BungeeTabPlayer(this, p));
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package me.neznamy.tab.platforms.bungeecord;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.shared.chat.EnumChatFormat;
|
||||
import me.neznamy.tab.shared.chat.component.TabComponent;
|
||||
import me.neznamy.tab.shared.Limitations;
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
@@ -18,8 +19,10 @@ import net.md_5.bungee.protocol.util.Either;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Scoreboard handler for BungeeCord. Because it does not offer
|
||||
@@ -28,6 +31,9 @@ import java.util.List;
|
||||
*/
|
||||
public class BungeeScoreboard extends SafeScoreboard<BungeeTabPlayer> {
|
||||
|
||||
private static final Optional<Integer>[] colors = Arrays.stream(EnumChatFormat.values()).map(e -> Optional.of(Math.min(e.ordinal(), 15))).toArray(Optional[]::new);
|
||||
private static final Optional<Integer> optionalZero = Optional.of(0);
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameter
|
||||
*
|
||||
@@ -119,7 +125,7 @@ public class BungeeScoreboard extends SafeScoreboard<BungeeTabPlayer> {
|
||||
either(team.getSuffix(), Limitations.TEAM_PREFIX_SUFFIX_PRE_1_13),
|
||||
convertVisibility(team.getVisibility()),
|
||||
convertCollision(team.getCollision()),
|
||||
player.getVersion().getNetworkId() >= ProtocolVersion.V1_13.getNetworkId() ? team.getColor().ordinal() : 0,
|
||||
player.getVersion().getNetworkId() >= ProtocolVersion.V1_13.getNetworkId() ? colors[team.getColor().ordinal()] : optionalZero,
|
||||
(byte) team.getOptions(),
|
||||
team.getPlayers().toArray(new String[0])
|
||||
));
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
package me.neznamy.tab.platforms.bungeecord;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
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.DefinedPacket;
|
||||
|
||||
/**
|
||||
* Main class for BungeeCord.
|
||||
@@ -19,17 +22,17 @@ public class BungeeTAB extends Plugin {
|
||||
}
|
||||
|
||||
private boolean isCompatible() {
|
||||
return ReflectionUtils.classExists("net.md_5.bungee.api.chat.ObjectComponent");
|
||||
return ReflectionUtils.methodExists(DefinedPacket.class, "readOptional", Function.class, ByteBuf.class);
|
||||
}
|
||||
|
||||
private void logIncompatibleVersionWarning() {
|
||||
int buildNumber = 2000;
|
||||
String releaseDate = "September 30th, 2025";
|
||||
String oldTabVersion = "5.2.5";
|
||||
getLogger().warning("§c====================================================================================================");
|
||||
getLogger().warning(String.format("§cThe plugin requires BungeeCord build #%d (released on %s) and up (or an equivalent fork) to work.", buildNumber, releaseDate));
|
||||
getLogger().warning(String.format("§cIf you are using a fork that did not update to the new BungeeCord version yet, stay on TAB v%s, which supports older builds.", oldTabVersion));
|
||||
getLogger().warning("§c====================================================================================================");
|
||||
int buildNumber = 2068;
|
||||
String releaseDate = "May 9th, 2026";
|
||||
String oldTabVersion = "6.0.2";
|
||||
getLogger().severe("§c====================================================================================================");
|
||||
getLogger().severe(String.format("§cThe plugin requires BungeeCord build #%d (released on %s) and up (or an equivalent fork) to work.", buildNumber, releaseDate));
|
||||
getLogger().severe(String.format("§cIf you are using a fork that did not update to the new BungeeCord version yet, stay on TAB v%s, which supports older builds.", oldTabVersion));
|
||||
getLogger().severe("§c====================================================================================================");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -14,11 +14,12 @@ repositories {
|
||||
maven("https://repo.papermc.io/repository/maven-public/") // Adventure
|
||||
}
|
||||
|
||||
val minecraftVersion = "26.1.2"
|
||||
val minecraftVersion = "26.2-pre-1"
|
||||
|
||||
// Fabric API versions for each Minecraft version for easier backporting
|
||||
// Official website (for updating in the future): https://fabricmc.net/develop/
|
||||
val fabricApiVersions = mapOf(
|
||||
"26.2-pre-1" to "0.149.2+26.2",
|
||||
"26.1.2" to "0.146.1+26.1.2"
|
||||
)
|
||||
|
||||
|
||||
@@ -5,12 +5,10 @@ import com.mojang.authlib.GameProfile;
|
||||
import com.mojang.authlib.properties.Property;
|
||||
import com.mojang.authlib.properties.PropertyMap;
|
||||
import com.mojang.logging.LogUtils;
|
||||
import eu.pb4.placeholders.api.PlaceholderContext;
|
||||
import eu.pb4.placeholders.api.Placeholders;
|
||||
import eu.pb4.placeholders.api.node.TextNode;
|
||||
import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.fabric.hook.FabricTabExpansion;
|
||||
import me.neznamy.tab.platforms.fabric.hook.PlaceholderAPIHook;
|
||||
import me.neznamy.tab.shared.ProjectVariables;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.backend.BackendPlatform;
|
||||
@@ -71,10 +69,7 @@ public record FabricPlatform(MinecraftServer server) implements BackendPlatform
|
||||
|
||||
PlaceholderManagerImpl manager = TAB.getInstance().getPlaceholderManager();
|
||||
manager.registerPlayerPlaceholder(identifier,
|
||||
p -> Placeholders.SERVER_PLACEHOLDER_PARSER.parseComponent(
|
||||
TextNode.convert(Component.literal(identifier)),
|
||||
PlaceholderContext.of((ServerPlayer) p.getPlayer()).asParserContext()
|
||||
).getString()
|
||||
p -> PlaceholderAPIHook.parsePlaceholders(identifier, ((FabricTabPlayer) p).getPlayer())
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,22 +1,19 @@
|
||||
package me.neznamy.tab.platforms.fabric;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.platform.decorators.SafeScoreboard;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import net.minecraft.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.TeamColor;
|
||||
import net.minecraft.world.scores.criteria.ObjectiveCriteria;
|
||||
import net.minecraft.world.scores.criteria.ObjectiveCriteria.RenderType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Collection;
|
||||
import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
@@ -24,11 +21,10 @@ import java.util.Optional;
|
||||
*/
|
||||
public class FabricScoreboard extends SafeScoreboard<FabricTabPlayer> {
|
||||
|
||||
private static final ChatFormatting[] formats = ChatFormatting.values();
|
||||
private static final Optional<TeamColor>[] colors = Arrays.stream(TeamColor.values()).map(Optional::of).toArray(Optional[]::new);
|
||||
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.
|
||||
@@ -118,7 +114,7 @@ public class FabricScoreboard extends SafeScoreboard<FabricTabPlayer> {
|
||||
PlayerTeam t = (PlayerTeam) team.getPlatformTeam();
|
||||
t.setAllowFriendlyFire((team.getOptions() & 0x01) != 0);
|
||||
t.setSeeFriendlyInvisibles((team.getOptions() & 0x02) != 0);
|
||||
t.setColor(formats[team.getColor().ordinal()]);
|
||||
t.setColor(colors[Math.min(team.getColor().ordinal(), colors.length - 1)]);
|
||||
t.setCollisionRule(collisions[team.getCollision().ordinal()]);
|
||||
t.setNameTagVisibility(visibilities[team.getVisibility().ordinal()]);
|
||||
t.setPlayerPrefix(team.getPrefix().convert());
|
||||
@@ -126,7 +122,6 @@ public class FabricScoreboard extends SafeScoreboard<FabricTabPlayer> {
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
@NotNull
|
||||
public Object onPacketSend(@NonNull Object packet) {
|
||||
if (packet instanceof ClientboundSetDisplayObjectivePacket display) {
|
||||
@@ -138,7 +133,7 @@ public class FabricScoreboard extends SafeScoreboard<FabricTabPlayer> {
|
||||
if (packet instanceof ClientboundSetPlayerTeamPacket team) {
|
||||
int method = getMethod(team);
|
||||
if (method != TeamAction.UPDATE) {
|
||||
players.set(team, onTeamPacket(method, team.getName(), team.getPlayers()));
|
||||
team.players = onTeamPacket(method, team.getName(), team.getPlayers());
|
||||
}
|
||||
}
|
||||
return packet;
|
||||
|
||||
@@ -5,12 +5,9 @@ import com.mojang.authlib.GameProfile;
|
||||
import com.mojang.authlib.properties.Property;
|
||||
import com.mojang.authlib.properties.PropertyMap;
|
||||
import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.shared.chat.component.TabComponent;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import me.neznamy.tab.shared.chat.component.TabComponent;
|
||||
import me.neznamy.tab.shared.platform.decorators.TrackedTabList;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket;
|
||||
@@ -20,7 +17,6 @@ import net.minecraft.world.level.GameType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
@@ -35,8 +31,6 @@ public class FabricTabList extends TrackedTabList<FabricTabPlayer> {
|
||||
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateListed = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED);
|
||||
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateListOrder = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LIST_ORDER);
|
||||
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateHat = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_HAT);
|
||||
|
||||
private static final Field entries = ReflectionUtils.getOnlyField(ClientboundPlayerInfoUpdatePacket.class, List.class);
|
||||
|
||||
/**
|
||||
* Constructs new instance.
|
||||
@@ -104,7 +98,6 @@ public class FabricTabList extends TrackedTabList<FabricTabPlayer> {
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
@NotNull
|
||||
public Object onPacketSend(@NonNull Object packet) {
|
||||
if (packet instanceof ClientboundTabListPacket tablist) {
|
||||
@@ -158,18 +151,17 @@ public class FabricTabList extends TrackedTabList<FabricTabPlayer> {
|
||||
}
|
||||
if (rewritePacket) {
|
||||
ClientboundPlayerInfoUpdatePacket newPacket = new ClientboundPlayerInfoUpdatePacket(actions, Collections.emptyList());
|
||||
entries.set(newPacket, updatedList);
|
||||
newPacket.entries = updatedList;
|
||||
return newPacket;
|
||||
}
|
||||
}
|
||||
return packet;
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private void sendPacket(@NonNull EnumSet<ClientboundPlayerInfoUpdatePacket.Action> action, @NonNull UUID id, @NonNull String name, @Nullable Skin skin,
|
||||
boolean listed, int latency, int gameMode, @Nullable TabComponent displayName, int listOrder, boolean showHat) {
|
||||
ClientboundPlayerInfoUpdatePacket packet = new ClientboundPlayerInfoUpdatePacket(action, Collections.emptyList());
|
||||
entries.set(packet, Collections.singletonList(new ClientboundPlayerInfoUpdatePacket.Entry(
|
||||
packet.entries = Collections.singletonList(new ClientboundPlayerInfoUpdatePacket.Entry(
|
||||
id,
|
||||
action.contains(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER) ? createProfile(id, name, skin) : null,
|
||||
listed,
|
||||
@@ -179,7 +171,7 @@ public class FabricTabList extends TrackedTabList<FabricTabPlayer> {
|
||||
showHat,
|
||||
listOrder,
|
||||
null
|
||||
)));
|
||||
));
|
||||
sendPacket(packet);
|
||||
}
|
||||
|
||||
@@ -198,8 +190,8 @@ public class FabricTabList extends TrackedTabList<FabricTabPlayer> {
|
||||
private GameProfile createProfile(@NonNull UUID id, @NonNull String name, @Nullable Skin skin) {
|
||||
ImmutableMultimap.Builder<String, Property> builder = ImmutableMultimap.builder();
|
||||
if (skin != null) {
|
||||
builder.put(TabList.TEXTURES_PROPERTY,
|
||||
new Property(TabList.TEXTURES_PROPERTY, skin.getValue(), skin.getSignature()));
|
||||
builder.put(TEXTURES_PROPERTY,
|
||||
new Property(TEXTURES_PROPERTY, skin.getValue(), skin.getSignature()));
|
||||
}
|
||||
return new GameProfile(id, name, new PropertyMap(builder.build()));
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import me.neznamy.tab.shared.features.PlaceholderManagerImpl;
|
||||
import me.neznamy.tab.shared.placeholders.expansion.TabExpansion;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
@@ -55,11 +56,10 @@ public class FabricTabExpansion implements TabExpansion {
|
||||
do {
|
||||
textBefore = text;
|
||||
for (String placeholder : PlaceholderManagerImpl.detectPlaceholders(text)) {
|
||||
text = text.replace(placeholder, TAB.getInstance().getPlaceholderManager().findReplacement(placeholder,
|
||||
Placeholders.SERVER_PLACEHOLDER_PARSER.parseComponent(
|
||||
placeholder,
|
||||
PlaceholderContext.of(ctx.player()).asParserContext()
|
||||
).getString()));
|
||||
text = text.replace(placeholder, TAB.getInstance().getPlaceholderManager().findReplacement(
|
||||
placeholder,
|
||||
PlaceholderAPIHook.parsePlaceholders(placeholder, (ServerPlayer) ctx.player())
|
||||
));
|
||||
}
|
||||
} while (!textBefore.equals(text));
|
||||
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
package me.neznamy.tab.platforms.fabric.hook;
|
||||
|
||||
import eu.pb4.placeholders.api.PlaceholderContext;
|
||||
import eu.pb4.placeholders.api.Placeholders;
|
||||
import eu.pb4.placeholders.api.node.TextNode;
|
||||
import net.fabricmc.loader.api.FabricLoader;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.network.chat.contents.PlainTextContents;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Class for managing placeholder-api hook.
|
||||
*/
|
||||
public class PlaceholderAPIHook {
|
||||
|
||||
/** Flag tracking whether placeholder-api is installed or not */
|
||||
private static final boolean available = FabricLoader.getInstance().isModLoaded("placeholder-api");
|
||||
|
||||
/**
|
||||
* Parses placeholders in the given text for the specified player.
|
||||
*
|
||||
* @param text
|
||||
* Text to parse
|
||||
* @param player
|
||||
* Player to parse text for
|
||||
* @return Parsed text
|
||||
*/
|
||||
@NotNull
|
||||
public static String parsePlaceholders(@NotNull String text, @NotNull ServerPlayer player) {
|
||||
if (!available) return "<placeholder-api is not installed>";
|
||||
return toTabString(Placeholders.SERVER_PLACEHOLDER_PARSER.parseComponent(
|
||||
TextNode.convert(Component.literal(text)),
|
||||
PlaceholderContext.of(player).asParserContext()
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts component to string format supported by TAB for parsing into a component.
|
||||
*
|
||||
* @param component
|
||||
* Component to convert
|
||||
* @return String representation of the component
|
||||
*/
|
||||
@NotNull
|
||||
private static String toTabString(@NotNull Component component) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (component.getStyle().getColor() != null) {
|
||||
sb.append("#").append(Integer.toHexString(component.getStyle().getColor().getValue()));
|
||||
}
|
||||
if (component.getStyle().isBold()) sb.append("§l");
|
||||
if (component.getStyle().isItalic()) sb.append("§o");
|
||||
if (component.getStyle().isUnderlined()) sb.append("§n");
|
||||
if (component.getStyle().isStrikethrough()) sb.append("§m");
|
||||
if (component.getStyle().isObfuscated()) sb.append("§k");
|
||||
if (component.getContents() instanceof PlainTextContents text) {
|
||||
sb.append(text.text());
|
||||
} else {
|
||||
sb.append(component.getContents()); // Fallback for non-plain text contents
|
||||
}
|
||||
for (Component extra : component.getSiblings()) {
|
||||
sb.append(toTabString(extra));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -14,9 +14,7 @@
|
||||
"fabric-api-base": "*",
|
||||
"java": ">=25",
|
||||
"minecraft": [
|
||||
"26.1.2",
|
||||
"26.1.1",
|
||||
"26.1"
|
||||
"26.2-pre.1"
|
||||
]
|
||||
},
|
||||
"recommends": {
|
||||
|
||||
@@ -3,3 +3,11 @@ accessWidener v2 official
|
||||
# Channel
|
||||
accessible field net/minecraft/server/network/ServerCommonPacketListenerImpl connection Lnet/minecraft/network/Connection;
|
||||
accessible field net/minecraft/network/Connection channel Lio/netty/channel/Channel;
|
||||
|
||||
# Scoreboard team
|
||||
accessible field net/minecraft/network/protocol/game/ClientboundSetPlayerTeamPacket players Ljava/util/Collection;
|
||||
mutable field net/minecraft/network/protocol/game/ClientboundSetPlayerTeamPacket players Ljava/util/Collection;
|
||||
|
||||
# Player info
|
||||
accessible field net/minecraft/network/protocol/game/ClientboundPlayerInfoUpdatePacket entries Ljava/util/List;
|
||||
mutable field net/minecraft/network/protocol/game/ClientboundPlayerInfoUpdatePacket entries Ljava/util/List;
|
||||
@@ -16,19 +16,12 @@ val platformPaths = setOf(
|
||||
":bukkit:paper_1_21_11",
|
||||
":bukkit:v1_7_R4",
|
||||
":bukkit:v1_8_R3",
|
||||
":bukkit:v1_9_R2",
|
||||
":bukkit:v1_10_R1",
|
||||
":bukkit:v1_11_R1",
|
||||
":bukkit:v1_12_R1",
|
||||
":bukkit:v1_13_R2",
|
||||
":bukkit:v1_14_R1",
|
||||
":bukkit:v1_15_R1",
|
||||
":bukkit:v1_16_R3",
|
||||
":bukkit:v1_17_R1",
|
||||
":bukkit:v1_18_R2",
|
||||
":bukkit:v1_19_R1",
|
||||
":bukkit:v1_19_R2",
|
||||
":bukkit:v1_10_R1",
|
||||
":bukkit:v1_19_R3",
|
||||
":bukkit:v1_20_R1",
|
||||
":bukkit:v1_20_R2",
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
package me.neznamy.tab.platforms.neoforge;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.platform.decorators.SafeScoreboard;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.network.chat.numbers.FixedFormat;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
@@ -15,8 +13,6 @@ import net.minecraft.world.scores.criteria.ObjectiveCriteria;
|
||||
import net.minecraft.world.scores.criteria.ObjectiveCriteria.RenderType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Collection;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
@@ -28,7 +24,6 @@ public class NeoForgeScoreboard extends SafeScoreboard<NeoForgeTabPlayer> {
|
||||
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.
|
||||
@@ -126,7 +121,6 @@ public class NeoForgeScoreboard extends SafeScoreboard<NeoForgeTabPlayer> {
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
@NotNull
|
||||
public Object onPacketSend(@NonNull Object packet) {
|
||||
if (packet instanceof ClientboundSetDisplayObjectivePacket display) {
|
||||
@@ -138,7 +132,7 @@ public class NeoForgeScoreboard extends SafeScoreboard<NeoForgeTabPlayer> {
|
||||
if (packet instanceof ClientboundSetPlayerTeamPacket team) {
|
||||
int method = getMethod(team);
|
||||
if (method != TeamAction.UPDATE) {
|
||||
players.set(team, onTeamPacket(method, team.getName(), team.getPlayers()));
|
||||
team.players = onTeamPacket(method, team.getName(), team.getPlayers());
|
||||
}
|
||||
}
|
||||
return packet;
|
||||
|
||||
@@ -5,12 +5,10 @@ import com.mojang.authlib.GameProfile;
|
||||
import com.mojang.authlib.properties.Property;
|
||||
import com.mojang.authlib.properties.PropertyMap;
|
||||
import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.shared.chat.component.TabComponent;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.chat.component.TabComponent;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import me.neznamy.tab.shared.platform.decorators.TrackedTabList;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket;
|
||||
@@ -20,7 +18,6 @@ import net.minecraft.world.level.GameType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
@@ -35,8 +32,6 @@ public class NeoForgeTabList extends TrackedTabList<NeoForgeTabPlayer> {
|
||||
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateListed = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED);
|
||||
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateListOrder = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LIST_ORDER);
|
||||
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateHat = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_HAT);
|
||||
|
||||
private static final Field entries = ReflectionUtils.getOnlyField(ClientboundPlayerInfoUpdatePacket.class, List.class);
|
||||
|
||||
/**
|
||||
* Constructs new instance.
|
||||
@@ -104,7 +99,6 @@ public class NeoForgeTabList extends TrackedTabList<NeoForgeTabPlayer> {
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
@NotNull
|
||||
public Object onPacketSend(@NonNull Object packet) {
|
||||
if (packet instanceof ClientboundTabListPacket tablist) {
|
||||
@@ -158,18 +152,17 @@ public class NeoForgeTabList extends TrackedTabList<NeoForgeTabPlayer> {
|
||||
}
|
||||
if (rewritePacket) {
|
||||
ClientboundPlayerInfoUpdatePacket newPacket = new ClientboundPlayerInfoUpdatePacket(actions, Collections.emptyList());
|
||||
entries.set(newPacket, updatedList);
|
||||
newPacket.entries = updatedList;
|
||||
return newPacket;
|
||||
}
|
||||
}
|
||||
return packet;
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private void sendPacket(@NonNull EnumSet<ClientboundPlayerInfoUpdatePacket.Action> action, @NonNull UUID id, @NonNull String name, @Nullable Skin skin,
|
||||
boolean listed, int latency, int gameMode, @Nullable TabComponent displayName, int listOrder, boolean showHat) {
|
||||
ClientboundPlayerInfoUpdatePacket packet = new ClientboundPlayerInfoUpdatePacket(action, Collections.emptyList());
|
||||
entries.set(packet, Collections.singletonList(new ClientboundPlayerInfoUpdatePacket.Entry(
|
||||
packet.entries = Collections.singletonList(new ClientboundPlayerInfoUpdatePacket.Entry(
|
||||
id,
|
||||
action.contains(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER) ? createProfile(id, name, skin) : null,
|
||||
listed,
|
||||
@@ -179,7 +172,7 @@ public class NeoForgeTabList extends TrackedTabList<NeoForgeTabPlayer> {
|
||||
showHat,
|
||||
listOrder,
|
||||
null
|
||||
)));
|
||||
));
|
||||
sendPacket(packet);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
# Scoreboard team
|
||||
public-f net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket players
|
||||
|
||||
# Player info
|
||||
public-f net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket entries
|
||||
+1
-7
@@ -6,7 +6,7 @@ dependencyResolutionManagement {
|
||||
maven("https://repo.viaversion.com/") // ViaVersion
|
||||
maven("https://repo.william278.net/releases/") // VelocityScoreboardAPI
|
||||
maven("https://repo.codemc.org/repository/nms/") // CraftBukkit + NMS
|
||||
maven("https://repo.papermc.io/repository/maven-public/") // paperweight, Velocity, Adventure
|
||||
maven("https://repo.papermc.io/repository/maven-public/") // paperweight, Velocity, Adventure, BungeeCord-API
|
||||
maven("https://repo.extendedclip.com/content/repositories/placeholderapi/") // PlaceholderAPI
|
||||
maven("https://repo.opencollab.dev/maven-snapshots/") // Floodgate, Bungeecord-proxy
|
||||
maven("https://repo.purpurmc.org/snapshots") // Purpur
|
||||
@@ -39,13 +39,7 @@ include(":bukkit:paper_1_21_9")
|
||||
include(":bukkit:paper_1_21_11")
|
||||
include(":bukkit:v1_7_R4")
|
||||
include(":bukkit:v1_8_R3")
|
||||
include(":bukkit:v1_9_R2")
|
||||
include(":bukkit:v1_10_R1")
|
||||
include(":bukkit:v1_11_R1")
|
||||
include(":bukkit:v1_12_R1")
|
||||
include(":bukkit:v1_13_R2")
|
||||
include(":bukkit:v1_14_R1")
|
||||
include(":bukkit:v1_15_R1")
|
||||
include(":bukkit:v1_16_R3")
|
||||
include(":bukkit:v1_17_R1")
|
||||
include(":bukkit:v1_18_R2")
|
||||
|
||||
@@ -6,7 +6,7 @@ import me.neznamy.tab.shared.chat.TabClickEvent;
|
||||
import me.neznamy.tab.shared.chat.TabTextColor;
|
||||
import me.neznamy.tab.shared.chat.component.TabComponent;
|
||||
import me.neznamy.tab.shared.chat.component.TabTextComponent;
|
||||
import me.neznamy.tab.shared.config.files.Config;
|
||||
import me.neznamy.tab.shared.config.Configs;
|
||||
import me.neznamy.tab.shared.features.types.Dumpable;
|
||||
import me.neznamy.tab.shared.features.types.TabFeature;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
@@ -53,10 +53,10 @@ public class DumpCommand extends SubCommand {
|
||||
Map<String, Object> data = new LinkedHashMap<>();
|
||||
data.put("platform", TAB.getInstance().getPlatform().dump());
|
||||
data.put("player", analyzed.dump());
|
||||
data.put("general-settings", dumpGeneralSettings());
|
||||
data.put("features", dumpFeatures(analyzed));
|
||||
data.put("placeholders", TAB.getInstance().getPlaceholderManager().dump(analyzed));
|
||||
data.put("tablist", analyzed.getTabList().dump());
|
||||
data.put("files", dumpFiles());
|
||||
DumperOptions options = new DumperOptions();
|
||||
options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
|
||||
options.setWidth(Integer.MAX_VALUE);
|
||||
@@ -79,35 +79,12 @@ public class DumpCommand extends SubCommand {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private Object dumpGeneralSettings() {
|
||||
Map<String, Object> settings = new LinkedHashMap<>();
|
||||
Config config = TAB.getInstance().getConfiguration().getConfig();
|
||||
settings.put("assign-groups-by-permissions", config.isGroupsByPermissions());
|
||||
settings.put("primary-group-finding-list", config.getPrimaryGroupFindingList());
|
||||
settings.put("permission-refresh-interval", config.getPermissionRefreshInterval());
|
||||
settings.put("debug", config.isDebugMode());
|
||||
settings.put("mysql.enabled", config.getMysql() != null);
|
||||
if (config.getProxySupport() != null) {
|
||||
settings.put("proxy-support", "type: " + config.getProxySupport().getType());
|
||||
} else {
|
||||
settings.put("proxy-support", "Disabled");
|
||||
}
|
||||
|
||||
settings.put("components", config.getComponents().getSection().getMap());
|
||||
settings.put("config-version", config.getConfig().getInt("config-version", 0));
|
||||
settings.put("compensate-for-packetevents-bug", config.isPacketEventsCompensation());
|
||||
settings.put("use-bukkit-permissions-manager", config.isBukkitPermissions());
|
||||
settings.put("use-online-uuid-in-tablist", config.isOnlineUuidInTabList());
|
||||
settings.put("server-name", config.getServerName());
|
||||
return settings;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private Object dumpFeatures(@NotNull TabPlayer player) {
|
||||
private Map<String, Object> dumpFeatures(@NotNull TabPlayer player) {
|
||||
Map<String, Object> features = new LinkedHashMap<>();
|
||||
List<String> featureList = Arrays.asList(
|
||||
TabConstants.Feature.BELOW_NAME,
|
||||
TabConstants.Feature.BOSS_BAR,
|
||||
TabConstants.Feature.GLOBAL_PLAYER_LIST,
|
||||
TabConstants.Feature.HEADER_FOOTER,
|
||||
TabConstants.Feature.LAYOUT,
|
||||
TabConstants.Feature.NAME_TAGS,
|
||||
@@ -131,6 +108,71 @@ public class DumpCommand extends SubCommand {
|
||||
return features;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private Map<String, Object> dumpFiles() {
|
||||
Map<String, Object> files = new LinkedHashMap<>();
|
||||
Configs configs = TAB.getInstance().getConfiguration();
|
||||
files.put("animations.yml", configs.getAnimations().getAnimationFile().getValues());
|
||||
Map<Object, Object> configClone = deepCopy(configs.getConfig().getConfig().getValues());
|
||||
censorConfig(configClone);
|
||||
files.put("config.yml", configClone);
|
||||
if (configs.getMysql() == null) {
|
||||
files.put("groups.yml", configs.getGroupsFile().getValues());
|
||||
files.put("users.yml", configs.getUsersFile().getValues());
|
||||
} else {
|
||||
files.put("groups.yml", "MySQL connection enabled, groups are in database");
|
||||
files.put("users.yml", "MySQL connection enabled, users are in database");
|
||||
}
|
||||
return files;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void censorConfig(@NotNull Map<Object, Object> config) {
|
||||
Map<String, Object> mysql = (Map<String, Object>) config.get("mysql");
|
||||
mysql.put("host", "*CENSORED*");
|
||||
mysql.put("database", "*CENSORED*");
|
||||
mysql.put("username", "*CENSORED*");
|
||||
mysql.put("password", "*CENSORED*");
|
||||
Map<String, Object> proxySupport = (Map<String, Object>) config.get("proxy-support");
|
||||
Map<String, Object> redis = (Map<String, Object>) proxySupport.get("redis");
|
||||
redis.put("url", "*CENSORED*");
|
||||
Map<String, Object> rabbitmq = (Map<String, Object>) proxySupport.get("rabbitmq");
|
||||
rabbitmq.put("url", "*CENSORED*");
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@SuppressWarnings("unchecked")
|
||||
private Map<Object, Object> deepCopy(@NotNull Map<Object, Object> original) {
|
||||
Map<Object, Object> copy = new LinkedHashMap<>();
|
||||
for (Map.Entry<Object, Object> entry : original.entrySet()) {
|
||||
Object value = entry.getValue();
|
||||
if (value instanceof Map) {
|
||||
copy.put(entry.getKey(), deepCopy((Map<Object, Object>) value));
|
||||
} else if (value instanceof List) {
|
||||
copy.put(entry.getKey(), deepCopy((List<?>) value));
|
||||
} else {
|
||||
copy.put(entry.getKey(), value);
|
||||
}
|
||||
}
|
||||
return copy;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@SuppressWarnings("unchecked")
|
||||
private List<?> deepCopy(@NotNull List<?> original) {
|
||||
List<Object> copy = new ArrayList<>();
|
||||
for (Object item : original) {
|
||||
if (item instanceof Map) {
|
||||
copy.add(deepCopy((Map<Object, Object>) item));
|
||||
} else if (item instanceof List) {
|
||||
copy.add(deepCopy((List<?>) item));
|
||||
} else {
|
||||
copy.add(item);
|
||||
}
|
||||
}
|
||||
return copy;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private String upload(@NotNull String content) throws Exception {
|
||||
URL url = new URL("https://api.pastes.dev/post");
|
||||
|
||||
@@ -36,10 +36,22 @@ public class Configs {
|
||||
/** animations.yml file */
|
||||
private final Animations animations = new Animations();
|
||||
|
||||
//messages.yml file
|
||||
/** groups.yml file */
|
||||
private final YamlPropertyConfigurationFile groupsFile = new YamlPropertyConfigurationFile(
|
||||
getClass().getClassLoader().getResourceAsStream("config/groups.yml"),
|
||||
new File(TAB.getInstance().getDataFolder(), "groups.yml")
|
||||
);
|
||||
|
||||
/** users.yml file */
|
||||
private final YamlPropertyConfigurationFile usersFile = new YamlPropertyConfigurationFile(
|
||||
getClass().getClassLoader().getResourceAsStream("config/users.yml"),
|
||||
new File(TAB.getInstance().getDataFolder(), "users.yml")
|
||||
);
|
||||
|
||||
/** messages.yml file */
|
||||
private final MessageFile messages = new MessageFile();
|
||||
|
||||
//playerdata.yml, used for bossbar & scoreboard toggle saving
|
||||
/** playerdata.yml, used for bossbar & scoreboard toggle saving */
|
||||
private final ConfigurationFile playerData = new YamlConfigurationFile(
|
||||
new ByteArrayInputStream(new byte[0]),
|
||||
new File(TAB.getInstance().getDataFolder(), "playerdata.yml")
|
||||
@@ -90,8 +102,8 @@ public class Configs {
|
||||
TAB.getInstance().getErrorManager().mysqlConnectionFailed(e);
|
||||
}
|
||||
}
|
||||
groups = new YamlPropertyConfigurationFile(getClass().getClassLoader().getResourceAsStream("config/groups.yml"), new File(TAB.getInstance().getDataFolder(), "groups.yml"));
|
||||
users = new YamlPropertyConfigurationFile(getClass().getClassLoader().getResourceAsStream("config/users.yml"), new File(TAB.getInstance().getDataFolder(), "users.yml"));
|
||||
groups = groupsFile;
|
||||
users = usersFile;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -21,9 +21,6 @@ public class DataManager {
|
||||
/** Map of all server groups defined in global playerlist configuration */
|
||||
private final Map<String, ServerGroup> serverGroups = new HashMap<>();
|
||||
|
||||
/** Default server group used for grouping unlisted servers */
|
||||
private final ServerGroup defaultServerGroup = new ServerGroup("DEFAULT", new ArrayList<>());
|
||||
|
||||
/** Map of all worlds, indexed by their name */
|
||||
private final Map<String, World> worlds = new HashMap<>();
|
||||
|
||||
@@ -52,6 +49,16 @@ public class DataManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates global playerlist group of the specified server.
|
||||
* If global playerlist is disabled, returns {@code null}.
|
||||
* If the server is not part of any group, returns either default value
|
||||
* or a new group for isolating unlisted servers, depending on configuration.
|
||||
*
|
||||
* @param server
|
||||
* Server to compute group for
|
||||
* @return Global playerlist group of the server, or {@code null} if global playerlist is disabled
|
||||
*/
|
||||
@Nullable
|
||||
ServerGroup computeServerGroup(@NotNull Server server) {
|
||||
if (globalPlayerListConfiguration == null) return null;
|
||||
@@ -63,9 +70,9 @@ public class DataManager {
|
||||
}
|
||||
}
|
||||
if (globalPlayerListConfiguration.isIsolateUnlistedServers()) {
|
||||
return new ServerGroup("", Collections.emptyList()); // Values are not used, just identity is compared
|
||||
return new ServerGroup("<isolated: " + server.getName() + ">", Collections.emptyList()); // Values are not used, just identity is compared
|
||||
} else {
|
||||
return defaultServerGroup;
|
||||
return ServerGroup.DEFAULT;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ public class Server {
|
||||
/** Flag tracking whether this server is marked as spy-server in global playerlist configuration or not */
|
||||
private boolean isSpyServer;
|
||||
|
||||
/** Server group this server is part of, null if not part of any group */
|
||||
/** Server group this server is part of, null if global playerlist is disabled */
|
||||
@Nullable
|
||||
@Setter
|
||||
private ServerGroup serverGroup;
|
||||
|
||||
@@ -3,7 +3,9 @@ package me.neznamy.tab.shared.data;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -13,6 +15,15 @@ import java.util.List;
|
||||
@Getter
|
||||
public class ServerGroup {
|
||||
|
||||
@NonNull private final String name;
|
||||
@NonNull private final List<String> patterns;
|
||||
/** Default server group for all unlisted servers that should share playerlist */
|
||||
@NotNull
|
||||
public static final ServerGroup DEFAULT = new ServerGroup("<DEFAULT>", new ArrayList<>());
|
||||
|
||||
/** Name of the server group as defined in configuration */
|
||||
@NonNull
|
||||
private final String name;
|
||||
|
||||
/** Server definitions in this group with regex support if prefixed with "regex:" */
|
||||
@NonNull
|
||||
private final List<String> patterns;
|
||||
}
|
||||
@@ -533,16 +533,6 @@ public class PlaceholderManagerImpl extends RefreshableFeature implements Placeh
|
||||
public Object dump(@NotNull TabPlayer player) {
|
||||
Map<String, Object> map = new LinkedHashMap<>();
|
||||
|
||||
// Placeholder-related config sections
|
||||
map.put("configuration", new LinkedHashMap<String, Object>() {{
|
||||
put("placeholders", TAB.getInstance().getConfiguration().getConfig().getPlaceholders().getSection().getMap());
|
||||
put("placeholder-output-replacements", TAB.getInstance().getConfiguration().getConfig().getReplacements().getSection().getMap());
|
||||
put("placeholder-refresh-intervals", TAB.getInstance().getConfiguration().getConfig().getRefresh().getSection().getMap());
|
||||
put("conditions", TAB.getInstance().getConfiguration().getConfig().getConditions().getSection().getMap());
|
||||
put("animations", TAB.getInstance().getConfiguration().getAnimations().getAnimations().getSection().getMap());
|
||||
}});
|
||||
|
||||
// Placeholder values
|
||||
List<List<String>> serverPlaceholders = new ArrayList<>();
|
||||
List<List<String>> playerPlaceholders = new ArrayList<>();
|
||||
Map<TabPlayer, List<List<String>>> relationalPlaceholders = new HashMap<>();
|
||||
|
||||
@@ -2,6 +2,7 @@ package me.neznamy.tab.shared.features.belowname;
|
||||
|
||||
import lombok.Getter;
|
||||
import me.neznamy.tab.shared.Property;
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.cpu.ThreadExecutor;
|
||||
@@ -226,13 +227,17 @@ public class BelowName extends RefreshableFeature implements JoinListener, QuitL
|
||||
if (viewer.belowNameData.disabled.get()) return;
|
||||
if (viewer.server != scoreHolder.server || viewer.world != scoreHolder.world) return; // Viewer definitely cannot see this player in game
|
||||
if (viewer.canSee(scoreHolder)) {
|
||||
viewer.getScoreboard().setScore(
|
||||
OBJECTIVE_NAME,
|
||||
scoreHolder.getNickname(),
|
||||
value,
|
||||
null, // Unused by this objective slot
|
||||
cache.get(fancyValue)
|
||||
);
|
||||
if (fancyValue.isEmpty() && viewer.getVersionId() > ProtocolVersion.V26_1_2.getNetworkId()) { // TODO change this to use V26_2 once added
|
||||
viewer.getScoreboard().removeScore(OBJECTIVE_NAME, scoreHolder.getNickname());
|
||||
} else {
|
||||
viewer.getScoreboard().setScore(
|
||||
OBJECTIVE_NAME,
|
||||
scoreHolder.getNickname(),
|
||||
value,
|
||||
null, // Unused by this objective slot
|
||||
cache.get(fancyValue)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+132
-2
@@ -8,14 +8,17 @@ import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.cpu.ThreadExecutor;
|
||||
import me.neznamy.tab.shared.cpu.TimedCaughtTask;
|
||||
import me.neznamy.tab.shared.data.Server;
|
||||
import me.neznamy.tab.shared.data.ServerGroup;
|
||||
import me.neznamy.tab.shared.features.playerlist.PlayerList;
|
||||
import me.neznamy.tab.shared.features.proxy.ProxyPlayer;
|
||||
import me.neznamy.tab.shared.features.proxy.ProxySupport;
|
||||
import me.neznamy.tab.shared.features.types.*;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import me.neznamy.tab.api.integration.VanishIntegration;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import me.neznamy.tab.shared.util.OnlinePlayers;
|
||||
import me.neznamy.tab.shared.util.PerformanceUtil;
|
||||
import me.neznamy.tab.shared.util.DumpUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -25,7 +28,7 @@ import java.util.*;
|
||||
* Feature handler for global PlayerList feature.
|
||||
*/
|
||||
public class GlobalPlayerList extends RefreshableFeature implements JoinListener, QuitListener, VanishListener, GameModeListener,
|
||||
Loadable, UnLoadable, ServerSwitchListener, TabListClearListener, CustomThreaded, ProxyFeature {
|
||||
Loadable, UnLoadable, ServerSwitchListener, TabListClearListener, CustomThreaded, ProxyFeature, Dumpable {
|
||||
|
||||
@Getter private final ThreadExecutor customThread = new ThreadExecutor("TAB Global PlayerList Thread");
|
||||
@Getter private OnlinePlayers onlinePlayers;
|
||||
@@ -191,7 +194,7 @@ public class GlobalPlayerList extends RefreshableFeature implements JoinListener
|
||||
@Override
|
||||
public void onGameModeChange(@NotNull TabPlayer player) {
|
||||
for (TabPlayer viewer : onlinePlayers.getPlayers()) {
|
||||
if (player.server != viewer.server) {
|
||||
if (player.server != viewer.server && viewer.server.canSee(player.server)) {
|
||||
viewer.getTabList().updateGameMode(player, configuration.isOthersAsSpectators() ? 3 : player.getGamemode());
|
||||
}
|
||||
}
|
||||
@@ -318,4 +321,131 @@ public class GlobalPlayerList extends RefreshableFeature implements JoinListener
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Object dump(@NotNull TabPlayer player) {
|
||||
Map<String, Object> playerInfo = new LinkedHashMap<>();
|
||||
playerInfo.put("server", player.server.getName());
|
||||
playerInfo.put("server is spy server", player.server.isSpyServer());
|
||||
playerInfo.put("server group", player.server.getServerGroup().getName());
|
||||
playerInfo.put("servers in the group", player.server.getServerGroup().getPatterns());
|
||||
|
||||
List<List<String>> rows = new ArrayList<>();
|
||||
List<TabPlayer> targets = new ArrayList<>(Arrays.asList(TAB.getInstance().getOnlinePlayers()));
|
||||
targets.sort(Comparator.comparing(TabPlayer::getName, String.CASE_INSENSITIVE_ORDER));
|
||||
for (TabPlayer t : targets) {
|
||||
String msg = getShouldSeeMessage(t, player);
|
||||
rows.add(Arrays.asList(
|
||||
t.getName(),
|
||||
t.server.getName(),
|
||||
t.server.getServerGroup().getName(),
|
||||
msg
|
||||
));
|
||||
}
|
||||
if (proxy != null) {
|
||||
List<ProxyPlayer> proxyTargets = new ArrayList<>(proxy.getProxyPlayers().values());
|
||||
proxyTargets.sort(Comparator.comparing(ProxyPlayer::getName, String.CASE_INSENSITIVE_ORDER));
|
||||
for (ProxyPlayer t : proxyTargets) {
|
||||
String msg = getShouldSeeMessage(t, player);
|
||||
rows.add(Arrays.asList(
|
||||
"[Proxy] " + t.getName(),
|
||||
t.server.getName(),
|
||||
t.server.getServerGroup().getName(),
|
||||
msg
|
||||
));
|
||||
}
|
||||
}
|
||||
playerInfo.put("visibility from viewer's perspective", DumpUtils.tableToLines(
|
||||
Arrays.asList("Player", "Server", "Server Group", "Visible for " + player.getName()),
|
||||
rows
|
||||
));
|
||||
|
||||
Map<String, Object> map = new LinkedHashMap<>();
|
||||
map.put("configuration", configuration.getSection().getMap());
|
||||
map.put("player info", playerInfo);
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a message about whether viewer can see target player or not and why.
|
||||
*
|
||||
* @param target
|
||||
* Target player to see
|
||||
* @param viewer
|
||||
* Tablist viewer
|
||||
* @return Message about whether viewer can see target player or not and why
|
||||
*/
|
||||
@NotNull
|
||||
private String getShouldSeeMessage(@NotNull TabPlayer target, @NotNull TabPlayer viewer) {
|
||||
if (viewer == target) return "yes - same player";
|
||||
|
||||
if (!viewer.server.canSee(target.server)) {
|
||||
return "no - servers are in different groups (viewer=" + viewer.server.getServerGroup().getName() +
|
||||
", target=" + target.server.getServerGroup().getName() + ") and viewer is not in a spy server";
|
||||
}
|
||||
for (VanishIntegration i : VanishIntegration.getHandlers()) {
|
||||
try {
|
||||
if (!i.canSee(viewer, target)) {
|
||||
return "no - vanish integration '" + i.getPlugin() + "' prevents viewer from seeing target";
|
||||
}
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
}
|
||||
if (target.isVanished() && !viewer.hasPermission(TabConstants.Permission.SEE_VANISHED)) {
|
||||
return "no - target is vanished and viewer lacks permission (" + TabConstants.Permission.SEE_VANISHED + ")";
|
||||
}
|
||||
|
||||
if (viewer.server == target.server) {
|
||||
return "yes - same server (" + viewer.server.getName() + ")";
|
||||
}
|
||||
if (viewer.server.isSpyServer()) {
|
||||
return "yes - viewer is in spy server (" + viewer.server.getName() + ")";
|
||||
}
|
||||
if (viewer.server.getServerGroup() == target.server.getServerGroup()) {
|
||||
if (viewer.server.getServerGroup() == ServerGroup.DEFAULT) {
|
||||
return "yes - both servers are not listed in any group, so they are put in a default shared group";
|
||||
} else {
|
||||
return "yes - same server group (" + viewer.server.getServerGroup().getName() + ")";
|
||||
}
|
||||
}
|
||||
|
||||
return "You should never see this message";
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a message about whether viewer can see target player or not and why.
|
||||
*
|
||||
* @param target
|
||||
* Target player to see
|
||||
* @param viewer
|
||||
* Tablist viewer
|
||||
* @return Message about whether viewer can see target player or not and why
|
||||
*/
|
||||
@NotNull
|
||||
private String getShouldSeeMessage(@NotNull ProxyPlayer target, @NotNull TabPlayer viewer) {
|
||||
if (!viewer.server.canSee(target.server)) {
|
||||
return "no - servers are in different groups (viewer=" + viewer.server.getServerGroup().getName() +
|
||||
", target=" + target.server.getServerGroup().getName() + ") and viewer is not in a spy server";
|
||||
}
|
||||
if (target.isVanished() && !viewer.hasPermission(TabConstants.Permission.SEE_VANISHED)) {
|
||||
return "no - target is vanished and viewer lacks permission (" + TabConstants.Permission.SEE_VANISHED + ")";
|
||||
}
|
||||
|
||||
if (viewer.server == target.server) {
|
||||
return "yes - same server (" + viewer.server.getName() + ")";
|
||||
}
|
||||
if (viewer.server.isSpyServer()) {
|
||||
return "yes - viewer is in spy server (" + viewer.server.getName() + ")";
|
||||
}
|
||||
if (viewer.server.getServerGroup() == target.server.getServerGroup()) {
|
||||
if (viewer.server.getServerGroup() == ServerGroup.DEFAULT) {
|
||||
return "yes - both servers are not listed in any group, so they are put in a default shared group";
|
||||
} else {
|
||||
return "yes - same server group (" + viewer.server.getServerGroup().getName() + ")";
|
||||
}
|
||||
}
|
||||
|
||||
return "You should never see this message";
|
||||
}
|
||||
}
|
||||
+2
@@ -14,6 +14,7 @@ import java.util.*;
|
||||
@RequiredArgsConstructor
|
||||
public class GlobalPlayerListConfiguration {
|
||||
|
||||
@NotNull private final ConfigurationSection section;
|
||||
private final boolean othersAsSpectators;
|
||||
private final boolean vanishedAsSpectators;
|
||||
private final boolean isolateUnlistedServers;
|
||||
@@ -53,6 +54,7 @@ public class GlobalPlayerListConfiguration {
|
||||
}
|
||||
|
||||
return new GlobalPlayerListConfiguration(
|
||||
section,
|
||||
section.getBoolean("display-others-as-spectators", false),
|
||||
section.getBoolean("display-vanished-players-as-spectators", true),
|
||||
section.getBoolean("isolate-unlisted-servers", false),
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
package me.neznamy.tab.shared.hook;
|
||||
|
||||
import me.neznamy.tab.shared.chat.component.TabComponent;
|
||||
import me.neznamy.tab.shared.chat.component.object.TabObjectComponent;
|
||||
import me.neznamy.tab.shared.chat.hook.AdventureHook;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import net.kyori.adventure.text.minimessage.tag.Tag;
|
||||
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -12,6 +17,8 @@ import org.jetbrains.annotations.Nullable;
|
||||
*/
|
||||
public class MiniMessageHook {
|
||||
|
||||
private static final boolean OBJECT_COMPONENTS_AVAILABLE = ReflectionUtils.classExists("net.kyori.adventure.text.object.ObjectContents");
|
||||
|
||||
/** Minimessage deserializer with disabled component post-processing */
|
||||
@Nullable
|
||||
private static final MiniMessage mm = createMiniMessage();
|
||||
@@ -19,12 +26,22 @@ public class MiniMessageHook {
|
||||
@Nullable
|
||||
private static MiniMessage createMiniMessage() {
|
||||
try {
|
||||
return MiniMessage.miniMessage();
|
||||
return MiniMessage.builder()
|
||||
.editTags(builder -> builder.resolver(headTextureTag()))
|
||||
.build();
|
||||
} catch (Throwable ignored) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static TagResolver headTextureTag() {
|
||||
if (OBJECT_COMPONENTS_AVAILABLE) {
|
||||
return MiniMessageObjectHook.headTextureTag();
|
||||
}
|
||||
return MiniMessageSafeAccessHack.fallbackHeadTag();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if MiniMessage is available on the server, {@code false} if not.
|
||||
*
|
||||
@@ -52,4 +69,19 @@ public class MiniMessageHook {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class loader hack to avoid class initializer error when using static methods in interfaces.
|
||||
* No, try/catch does not solve this.
|
||||
*/
|
||||
private static class MiniMessageSafeAccessHack {
|
||||
|
||||
@NotNull
|
||||
private static TagResolver fallbackHeadTag() {
|
||||
return TagResolver.resolver("head_texture", (args, context) -> {
|
||||
args.popOr("Expected texture url"); // Consume the argument to keep the same tag signature
|
||||
return Tag.selfClosingInserting(Component.text(TabObjectComponent.ERROR_MESSAGE));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package me.neznamy.tab.shared.hook;
|
||||
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.minimessage.tag.Tag;
|
||||
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
|
||||
import net.kyori.adventure.text.object.ObjectContents;
|
||||
import net.kyori.adventure.text.object.PlayerHeadObjectContents;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Base64;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Class for hooking into MiniMessage to support object components in a way that is compatible with older versions of the library.
|
||||
*/
|
||||
public class MiniMessageObjectHook {
|
||||
|
||||
/** Constant prefix shared by every Minecraft texture URL */
|
||||
private static final String TEXTURE_URL_PREFIX = "https://textures.minecraft.net/texture/";
|
||||
|
||||
/**
|
||||
* Creates a tag resolver for the "head_texture" tag, which allows inserting player heads with custom textures using a texture hash.
|
||||
* The tag expects a single argument: the Minecraft texture hash (the part after "https://textures.minecraft.net/texture/").
|
||||
*
|
||||
* @return A TagResolver for the "head_texture" tag.
|
||||
*/
|
||||
public static TagResolver headTextureTag() {
|
||||
return TagResolver.resolver("head_texture", (args, context) -> {
|
||||
String hash = args.popOr("Expected texture hash").lowerValue().trim();
|
||||
String textureUrl = TEXTURE_URL_PREFIX + hash;
|
||||
|
||||
String json = String.format("{\"textures\":{\"SKIN\":{\"url\":\"%s\"}}}", textureUrl);
|
||||
String texture = Base64.getEncoder().encodeToString(json.getBytes(StandardCharsets.UTF_8));
|
||||
PlayerHeadObjectContents contents = ObjectContents.playerHead()
|
||||
.profileProperty(
|
||||
PlayerHeadObjectContents.property("textures", texture)
|
||||
)
|
||||
.build();
|
||||
|
||||
return Tag.selfClosingInserting(Component.object(contents));
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -119,16 +119,11 @@ public abstract class TrackedTabList<P extends TabPlayer> implements TabList {
|
||||
|
||||
@Override
|
||||
public void updateGameMode(@NonNull TabPlayer target, int gameMode) {
|
||||
// If entry is not in the tablist, that's okay, because:
|
||||
// - If sent by global playerlist and not present, it's fine
|
||||
// - If sent by spectator fix, gamemode will be overwritten in the packet later anyway
|
||||
if (containsEntry(target.getTablistId())) {
|
||||
updateGameMode(target.getTablistId(), gameMode);
|
||||
} else {
|
||||
// Entry is not in tablist. This could be on join. Delay and try again.
|
||||
TAB.getInstance().getCpu().getTablistEntryCheckThread().executeLater(new TimedCaughtTask(TAB.getInstance().getCpu(), () -> {
|
||||
// If entry was added in the meantime
|
||||
if (containsEntry(target.getTablistId())) {
|
||||
updateGameMode(target.getTablistId(), gameMode);
|
||||
}
|
||||
}, TabConstants.Feature.SPECTATOR_FIX, "Delayed gamemode update"), 500);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,12 +10,15 @@ import me.neznamy.tab.shared.features.PlaceholderManagerImpl;
|
||||
import me.neznamy.tab.shared.features.types.TabFeature;
|
||||
import me.neznamy.tab.shared.hook.LuckPermsHook;
|
||||
import me.neznamy.tab.shared.placeholders.UniversalPlaceholderRegistry;
|
||||
import me.neznamy.tab.shared.placeholders.types.PlayerPlaceholderImpl;
|
||||
import me.neznamy.tab.shared.platform.Platform;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import me.neznamy.tab.shared.proxy.message.outgoing.RegisterPlaceholder;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Abstract class containing common variables and methods
|
||||
* shared between proxies.
|
||||
@@ -44,6 +47,11 @@ public abstract class ProxyPlatform implements Platform {
|
||||
}
|
||||
for (TabPlayer all : TAB.getInstance().getOnlinePlayers()) {
|
||||
((ProxyTabPlayer)all).sendPluginMessage(new RegisterPlaceholder(placeholder.getIdentifier(), refresh));
|
||||
Map<String, String> bridgePlaceholders = ((ProxyTabPlayer)all).getPlaceholders();
|
||||
// TODO do relational placeholders as well
|
||||
if (placeholder instanceof PlayerPlaceholderImpl && bridgePlaceholders.containsKey(identifier)) {
|
||||
((PlayerPlaceholderImpl)placeholder).updateValue(all, bridgePlaceholders.get(identifier));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder;
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.chat.component.TabComponent;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import me.neznamy.tab.shared.platform.decorators.TrackedTabList;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -179,4 +180,10 @@ public class VelocityTabList extends TrackedTabList<VelocityTabPlayer> {
|
||||
public Collection<UUID> getEntries() {
|
||||
return player.getPlayer().getTabList().getEntries().stream().map(e -> e.getProfile().getId()).toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateGameMode(@NonNull TabPlayer target, int gameMode) {
|
||||
// Override of parent function to skip contains check, because setter will check it too, to avoid double check.
|
||||
updateGameMode(target.getTablistId(), gameMode);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user