mirror of
https://github.com/NEZNAMY/TAB.git
synced 2026-11-12 20:22:01 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bef190ddd7 | ||
|
|
cf320b3fa2 | ||
|
|
34c8745fa9 | ||
|
|
8096ff7f98 | ||
|
|
dad3006f84 | ||
|
|
0e35f15a86 | ||
|
|
d423740273 | ||
|
|
49f489c4dc | ||
|
|
ac71bf2111 | ||
|
|
6d40dd12fa | ||
|
|
a732abcdd6 | ||
|
|
c2f90f9daf | ||
|
|
898f56c32a | ||
|
|
2ebc4384f5 | ||
|
|
687b8d5e29 | ||
|
|
d09d95e069 | ||
|
|
4dffc1ffe9 | ||
|
|
f3b2cfbb87 | ||
|
|
4af57308d3 | ||
|
|
28ecf8666c | ||
|
|
1f3d1beb8f | ||
|
|
96d21bc544 | ||
|
|
f5cb2b5a1d | ||
|
|
d1d6e73bbc | ||
|
|
2321ae1c0c | ||
|
|
982ee5b6bf | ||
|
|
dbe699843b | ||
|
|
fda19760bd | ||
|
|
97d42152cd | ||
|
|
5981607783 | ||
|
|
7b7927f328 | ||
|
|
a8e2730f2f | ||
|
|
7ae18187bc | ||
|
|
02634c531f | ||
|
|
5e8ff89c43 | ||
|
|
2f307c492f | ||
|
|
e4b3e17c14 | ||
|
|
66d14a0754 | ||
|
|
cb47dcbe12 | ||
|
|
ea15b40e05 | ||
|
|
93cfd74fc0 | ||
|
|
79eb6a9078 | ||
|
|
2e9e5042e1 | ||
|
|
bb520edd53 | ||
|
|
2beb9563bb | ||
|
|
87c15f7611 | ||
|
|
655bc176fa | ||
|
|
66cb30d235 | ||
|
|
cbe0b25b5f | ||
|
|
a8ccf4e1c9 | ||
|
|
cce29c51e3 | ||
|
|
242015b0cc | ||
|
|
f209e1b0f9 | ||
|
|
8a1ab06585 | ||
|
|
50e817a86d | ||
|
|
dbd596f079 | ||
|
|
f8e3a07e6f | ||
|
|
ccd4ac3271 | ||
|
|
fce1925448 | ||
|
|
335eb5ef4d | ||
|
|
654c40b892 | ||
|
|
a937243cdf | ||
|
|
665d6baf1c | ||
|
|
b4d326b52c | ||
|
|
3aacec826b | ||
|
|
3261856385 | ||
|
|
8cf479bf3b | ||
|
|
3c3ef636eb | ||
|
|
3bd0b13740 | ||
|
|
92349227a2 | ||
|
|
4dfc2a638a | ||
|
|
d7c73a8072 | ||
|
|
297acc0322 | ||
|
|
1126b3b471 | ||
|
|
ac59973283 | ||
|
|
4b130ed71d | ||
|
|
ce90a33ce9 | ||
|
|
50fee32218 | ||
|
|
39334a4cd9 | ||
|
|
fec2131ba8 | ||
|
|
7c63436da4 | ||
|
|
de8fe63bd0 | ||
|
|
1c22f2387b | ||
|
|
e1b277582a | ||
|
|
94acc4f356 | ||
|
|
9afb62a6a4 | ||
|
|
95ed957d54 | ||
|
|
58814bc2da | ||
|
|
9e7556c3c1 | ||
|
|
abaf723fb7 | ||
|
|
f52e99c86e | ||
|
|
d6e70c35d8 | ||
|
|
4c91e73f0e | ||
|
|
d083fc6e43 | ||
|
|
ff2df25e3b | ||
|
|
86c2342097 | ||
|
|
6dbd176683 | ||
|
|
141aab1c22 | ||
|
|
126ea4f456 | ||
|
|
a0e5a97594 | ||
|
|
0420c11937 | ||
|
|
27472fd387 | ||
|
|
104bdc646f | ||
|
|
cbc4c275d1 | ||
|
|
372d51f873 | ||
|
|
21b7393274 | ||
|
|
dcd5f5a3e6 | ||
|
|
bf313b3ba1 | ||
|
|
5b4e3c479c | ||
|
|
6dedb05c29 | ||
|
|
5f78e01342 | ||
|
|
881506a3d5 | ||
|
|
e26fc661b2 | ||
|
|
f921758ec0 | ||
|
|
01d65ba780 | ||
|
|
d77e2d6f45 | ||
|
|
7c30f55f31 | ||
|
|
c2d63865d5 | ||
|
|
78f8c99f11 |
No files matched your search
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="156" height="20" role="img" aria-label="Minecraft: 1.7.x - 1.21.11"><title>Minecraft: 1.7.x - 1.21.11</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="156" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="61" height="20" fill="#555"/><rect x="61" width="95" height="20" fill="#007ec6"/><rect width="156" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="315" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="510">Minecraft</text><text x="315" y="140" transform="scale(.1)" fill="#fff" textLength="510">Minecraft</text><text aria-hidden="true" x="1075" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="850">1.7.x - 1.21.11</text><text x="1075" y="140" transform="scale(.1)" fill="#fff" textLength="850">1.7.x - 1.21.11</text></g></svg>
|
||||
|
Before Width: | Height: | Size: 1.1 KiB |
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="92" height="20" role="img" aria-label="Release: 5.4.0"><title>Release: 5.4.0</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="92" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="53" height="20" fill="#555"/><rect x="53" width="39" height="20" fill="#007ec6"/><rect width="92" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="275" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="430">Release</text><text x="275" y="140" transform="scale(.1)" fill="#fff" textLength="430">Release</text><text aria-hidden="true" x="715" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="290">5.4.0</text><text x="715" y="140" transform="scale(.1)" fill="#fff" textLength="290">5.4.0</text></g></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="92" height="20" role="img" aria-label="Release: 6.0.2"><title>Release: 6.0.2</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="92" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="53" height="20" fill="#555"/><rect x="53" width="39" height="20" fill="#007ec6"/><rect width="92" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="275" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="430">Release</text><text x="275" y="140" transform="scale(.1)" fill="#fff" textLength="430">Release</text><text aria-hidden="true" x="715" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="290">6.0.2</text><text x="715" y="140" transform="scale(.1)" fill="#fff" textLength="290">6.0.2</text></g></svg>
|
||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
@@ -274,12 +274,9 @@ if __name__ == "__main__":
|
||||
]
|
||||
)
|
||||
|
||||
release = "5.4.0"
|
||||
release = "6.0.2"
|
||||
download_image(f"https://img.shields.io/badge/Release-{release}-blue.svg", "Badge_Release.svg")
|
||||
|
||||
minecraft = "1.7.x - 1.21.11".replace(" ", "%20").replace("-", "--")
|
||||
download_image(f"https://img.shields.io/badge/Minecraft-{minecraft}-blue.svg", "Badge_Minecraft.svg")
|
||||
|
||||
java = "8+"
|
||||
download_image(f"https://img.shields.io/badge/Java-{java}-blue.svg", "Badge_Java.svg")
|
||||
|
||||
|
||||
@@ -26,12 +26,12 @@
|
||||
|
||||
## Functions
|
||||
* [Animations](https://github.com/NEZNAMY/TAB/wiki/Animations)
|
||||
* [Component usage (RGB, fonts, sprites)](https://github.com/NEZNAMY/TAB/wiki/How-to-use-Minecraft-components)
|
||||
* [Conditional placeholders](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Conditional-placeholders)
|
||||
* [Error logging](https://github.com/NEZNAMY/TAB/wiki/Error-logging)
|
||||
* [MySQL](https://github.com/NEZNAMY/TAB/wiki/MySQL)
|
||||
* [Placeholder output replacements](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Placeholder-output-replacements)
|
||||
* [Placeholders](https://github.com/NEZNAMY/TAB/wiki/Placeholders)
|
||||
* [RGB/font usage](https://github.com/NEZNAMY/TAB/wiki/How-to-use-RGB-color-codes)
|
||||
|
||||
## Other
|
||||
* [Additional information](https://github.com/NEZNAMY/TAB/wiki/Additional-information)
|
||||
|
||||
@@ -44,6 +44,7 @@ That includes header/footer, prefix/suffix, belowname, playerlist objective, bos
|
||||
# Tips & Tricks
|
||||
* Animations don't need to be fast. You can use them as a changing text in general.
|
||||
* Animations can also be used as a short alias to a long text. You can do this by creating an animation with one frame.
|
||||
* Adding to the previous point, animations can be used as a way to wrap a placeholder, having two different identifiers return the same value, allowing to use both original and replaced output from [Placeholder output replacements](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Placeholder-output-replacements).
|
||||
|
||||
# External tools
|
||||
If you want an animation too complicated to be done manually, you can check out these websites:
|
||||
|
||||
@@ -13,7 +13,7 @@ Commands have required permissions associated with them.
|
||||
* [/tab \<player/group/playeruuid\> \<name\> \<property\> \[value\] \[options\]](#tab-playergroupplayeruuid-name-property-value-options)
|
||||
* [/tab \<player/group/playeruuid\> \<name\> remove](#tab-playergroupplayeruuid-name-remove)
|
||||
* [/tab reload](#tab-reload)
|
||||
* [/tab debug \[player\]](#tab-debug-player)
|
||||
* [/tab dump \[player\]](#tab-dump-player)
|
||||
* [/tab group \<group\>](#tab-group-group)
|
||||
* [/tab groups](#tab-groups)
|
||||
* [Bossbar](#bossbar)
|
||||
@@ -65,13 +65,9 @@ Commands have required permissions associated with them.
|
||||
* **Permission:** `tab.reload`
|
||||
* **Description:** Unloads the plugin, loads configuration files including changes and loads the plugin.
|
||||
|
||||
## /tab debug [player]
|
||||
* **Permission:** `tab.debug`
|
||||
* **Description:** Shows the server version, plugin version, permission group choice logic, and sorting type. If player argument is filled, shows info about that player:
|
||||
* On proxy, shows whether player is connected to the backend server with [Bridge](https://github.com/NEZNAMY/TAB/wiki/TAB-Bridge) plugin or not.
|
||||
* Sorting value & explanation, useful to see what went wrong if players are not sorted correctly.
|
||||
* Primary group set using [How to assign players into groups](https://github.com/NEZNAMY/TAB/wiki/How-to-assign-players-into-groups)
|
||||
* List of all configured properties, their values and source.
|
||||
## /tab dump \<player\>
|
||||
* **Permission:** `tab.dump`
|
||||
* **Description:** Creates a very detailed dump about the specified player to help with troubleshooting, uploads it to a paste site and shows the generated URL.
|
||||
|
||||
## /tab group \<group\>
|
||||
* **Permission:** `tab.groupinfo`
|
||||
|
||||
@@ -3,13 +3,7 @@
|
||||
* [Java](#java)
|
||||
* [Supported server software and versions](#supported-server-software-and-versions)
|
||||
* [Supported features per platform](#supported-features-per-platform)
|
||||
* [Plugin hooks](#plugin-hooks)
|
||||
* [All platforms](#all-platforms)
|
||||
* [Bukkit](#bukkit)
|
||||
* [BungeeCord](#bungeecord)
|
||||
* [Fabric](#fabric)
|
||||
* [Forge / NeoForge](#forge--neoforge)
|
||||
* [Velocity](#velocity)
|
||||
* [Plugin / mod hooks](#plugin--mod-hooks)
|
||||
* [Compatibility issues](#compatibility-issues)
|
||||
|
||||
# Requirements
|
||||
@@ -34,7 +28,7 @@ TAB's shared module is compiled with Java 8. For the platforms themselves, TAB d
|
||||
<tr>
|
||||
<td rowspan=2>Vanilla</td>
|
||||
<td rowspan=1><a href="https://getbukkit.org/">Bukkit</a> (+forge hybrids)</td>
|
||||
<td>✔ (1.7.x - 1.21.11)</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>
|
||||
@@ -49,7 +43,7 @@ TAB's shared module is compiled with Java 8. For the platforms themselves, TAB d
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan=1><a href="https://minecraftforge.net">Forge</a></td>
|
||||
<td>✔ (1.21.9 - 1.21.10)<sup>1</sup></td>
|
||||
<td>✔ (1.21.11)<sup>1</sup></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan=1><a href="https://neoforged.net/">NeoForge</a></td>
|
||||
@@ -97,41 +91,20 @@ so you can always safely update to new version/build of your proxy software if t
|
||||
✔ = Fully functional
|
||||
❌ = Completely missing
|
||||
|
||||
# Plugin hooks
|
||||
To enhance user experience, TAB hooks into other plugins for better experience.
|
||||
This set of plugins is different for each platform based on their availability.
|
||||
# Plugin / mod hooks
|
||||
To enhance user experience, TAB hooks into other plugins / mods for better experience.
|
||||
This set of plugins / mods is different for each platform based on their availability.
|
||||
Some are available on all platforms, some only in a few.
|
||||
## All platforms
|
||||
[**Floodgate**](https://github.com/GeyserMC/Floodgate) - For properly detecting bedrock players to adapt features for the best possible user experience.
|
||||
[**LuckPerms**](https://github.com/LuckPerms/LuckPerms) - Detecting permission groups of players for per-group settings.
|
||||
[**ViaVersion**](https://github.com/ViaVersion/ViaVersion) - For properly detecting player's version to adapt features for the best possible user experience.
|
||||
|
||||
## Bukkit
|
||||
[**LibsDisguises**](https://github.com/libraryaddict/LibsDisguises) - Detecting disguised players to disable collision to avoid endless push by colliding with own copy created by LibsDisguises.
|
||||
[**PlaceholderAPI**](https://github.com/PlaceholderAPI/PlaceholderAPI) - Allows users to use its placeholders inside TAB.
|
||||
[**PremiumVanish**](https://www.spigotmc.org/resources/14404) - Supporting PremiumVanish's vanishing levels instead of using a basic compatibility system.
|
||||
[**Vault**](https://github.com/milkbowl/Vault) - Detecting permission groups of players for per-group settings.
|
||||
|
||||
## BungeeCord
|
||||
[**PremiumVanish**](https://www.spigotmc.org/resources/14404) -
|
||||
Supporting PremiumVanish's vanishing levels instead of using a basic compatibility system.
|
||||
[**RedisBungee**](https://github.com/ProxioDev/RedisBungee) -
|
||||
Communicating with other proxies to properly display visuals on players on another proxy.
|
||||
|
||||
## Fabric
|
||||
[**fabric-permissions-api**](https://github.com/lucko/fabric-permissions-api) - Supporting permission nodes instead of OP levels.
|
||||
[**placeholder-api**](https://modrinth.com/mod/placeholder-api) - Displaying placeholders from that mod and offering TAB's placeholders into it.
|
||||
|
||||
## Forge / NeoForge
|
||||
[**LuckPerms**](https://github.com/LuckPerms/LuckPerms) - Permission checks with a string instead of using vanilla OP levels.
|
||||
|
||||
## Velocity
|
||||
[**PremiumVanish**](https://www.spigotmc.org/resources/14404) -
|
||||
Supporting PremiumVanish's vanishing levels instead of using a basic compatibility system.
|
||||
[**RedisBungee**](https://github.com/ProxioDev/RedisBungee) -
|
||||
Communicating with other proxies to properly display visuals on players on another proxy.
|
||||
[**VelocityScoreboardAPI**](https://github.com/NEZNAMY/VelocityScoreboardAPI) -
|
||||
Sending scoreboard packets (scoreboard-teams, belowname-objective, playerlist-objective, scoreboard)
|
||||
| Platform | Plugins / mods |
|
||||
|---------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| All platforms | [**Floodgate**](https://github.com/GeyserMC/Floodgate) - For properly detecting bedrock players to adapt features for the best possible user experience. <br /> [**LuckPerms**](https://github.com/LuckPerms/LuckPerms) - Detecting permission groups of players for per-group settings. <br /> [**ViaVersion**](https://github.com/ViaVersion/ViaVersion) - For properly detecting player's version to adapt features for the best possible user experience. |
|
||||
| Bukkit | [**LibsDisguises**](https://github.com/libraryaddict/LibsDisguises) - Detecting disguised players to disable collision to avoid endless push by colliding with own copy created by LibsDisguises. <br />[**PlaceholderAPI**](https://github.com/PlaceholderAPI/PlaceholderAPI) - Allows users to use its placeholders inside TAB. <br />[**PremiumVanish**](https://www.spigotmc.org/resources/14404) - Supporting PremiumVanish's vanishing levels instead of using a basic compatibility system. <br />[**Vault**](https://github.com/milkbowl/Vault) - Detecting permission groups of players for per-group settings. |
|
||||
| BungeeCord | [**PremiumVanish**](https://www.spigotmc.org/resources/14404) - Supporting PremiumVanish's vanishing levels instead of using a basic compatibility system. <br />[**RedisBungee**](https://github.com/ProxioDev/RedisBungee) - Communicating with other proxies to properly display visuals on players on another proxy. |
|
||||
| Fabric | [**fabric-permissions-api**](https://github.com/lucko/fabric-permissions-api) - Supporting permission nodes instead of OP levels. <br />[**placeholder-api**](https://modrinth.com/mod/placeholder-api) - Displaying placeholders from that mod and offering TAB's placeholders into it. |
|
||||
| Forge | [**LuckPerms**](https://github.com/LuckPerms/LuckPerms) - Permission checks with a string instead of using vanilla OP levels. <br /> [**ForgePlaceholderAPI**](https://github.com/EnvyWare/ForgePlaceholderAPI) - Allows users to use its placeholders inside TAB. <br /> [**Vanishmod**](https://modrinth.com/mod/vanishmod) - Getting player's vanish status. |
|
||||
| NeoForge | [**LuckPerms**](https://github.com/LuckPerms/LuckPerms) - Permission checks with a string instead of using vanilla OP levels. |
|
||||
| Velocity | [**PremiumVanish**](https://www.spigotmc.org/resources/14404) - Supporting PremiumVanish's vanishing levels instead of using a basic compatibility system. <br />[**RedisBungee**](https://github.com/ProxioDev/RedisBungee) - Communicating with other proxies to properly display visuals on players on another proxy. <br />[**VelocityScoreboardAPI**](https://github.com/NEZNAMY/VelocityScoreboardAPI) - Sending scoreboard packets (scoreboard-teams, belowname-objective, playerlist-objective, scoreboard) |
|
||||
|
||||
# Compatibility issues
|
||||
* **Glow plugins** will fail to apply glow color correctly. Check [How to make the plugin compatible with glow plugins](https://github.com/NEZNAMY/TAB/wiki/How-to-make-TAB-compatible-with-glow-plugins) for more information.
|
||||
|
||||
@@ -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.4.0</version>
|
||||
<version>5.5.0</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
@@ -50,7 +50,7 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly 'com.github.NEZNAMY:TAB-API:5.4.0'
|
||||
compileOnly 'com.github.NEZNAMY:TAB-API:5.5.0'
|
||||
}
|
||||
```
|
||||
|
||||
@@ -61,7 +61,7 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly("com.github.NEZNAMY", "TAB-API", "5.4.0")
|
||||
compileOnly("com.github.NEZNAMY", "TAB-API", "5.5.0")
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
* [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)
|
||||
* [Additional note 4 - Compatibility with modified clients](#additional-note-4---compatibility-with-modified-clients)
|
||||
* [Examples](#examples)
|
||||
* [Example 1 - Per-world values](#example-1---per-world-values)
|
||||
@@ -50,7 +51,7 @@ All the options are explained in the following table.
|
||||
| enabled | true | Enables / Disables the feature |
|
||||
| value | %health% | [1.20.2-] An integer from -2147483648 to 2147483647, doesn't support decimal values. The number is always white. Supports placeholders with player-specific output, such as player health. Only visible on 1.20.2 and lower. <br/> **Note**: Even if you only support 1.20.3+, you still need to configure this value to properly evaluate to a number, because the value is still sent to the client (just not displayed). You can set it to `0` for simplicity. |
|
||||
| fancy-value | &c%health% | [1.20.3+] Any text, supports placeholders with per-player output. Only visible on 1.20.3+, where it completely replaces `value`. |
|
||||
| fancy-value-default | NPC | [1.20.3+] Default display for all player entities. Plugin uses the value above for every real player, therefore this default value will only appear on player entities which are not actual players, a.k.a. NPCs. Only visible on 1.20.3+. |
|
||||
| fancy-value-default | NPC | [1.20.3+] Default number format (`fancy-value`) for all player entities. `fancy-value` is displayed on every real player, therefore this default value will only appear on player entities which are not actual players, a.k.a. NPCs. Only visible on 1.20.3+. |
|
||||
| title | Health | Shared label shown after the score for every player entity. Player sees the same text on everyone (= placeholders are parsed for the viewer). Use the `value`/`fancy-value` fields for per‑player data; use `title` only for static labels like `Health` or placeholders which are supposed to be parsed for the viewing player. |
|
||||
| disable-condition | %world%=disabledworld | A [condition](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Conditional-placeholders) (either name of a condition or a conditional expression) that must be met for disabling the feature for players. Set to empty for not disabling the feature ever. <br/> **Note**: Disabling the feature for a player means sending objective unregister packet to them, which results in player not seeing belowname on anyone anymore. It doesn't work the other way around - you cannot disable this feature on target players, only for viewers. |
|
||||
|
||||
@@ -102,7 +103,17 @@ 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.
|
||||
|
||||
# Additional note 4 - Compatibility with modified clients
|
||||
### [26.1] 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
|
||||
```
|
||||
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).
|
||||
|
||||
## 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.
|
||||
|
||||
# Examples
|
||||
|
||||
@@ -3,18 +3,18 @@
|
||||
* [Configuration](#configuration)
|
||||
* [Commands](#commands)
|
||||
* [Placeholders](#placeholders)
|
||||
* [Internal placeholders](#internal-placeholders)
|
||||
* [PlaceholderAPI placeholders](#placeholderapi-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)
|
||||
* [Additional note 1 - Hiding bar itself](#additional-note-1---hiding-bar-itself)
|
||||
* [API](#api)
|
||||
* [Examples](#examples)
|
||||
* [Example 1 - Changing text](#example-1---changing-text)
|
||||
* [Example 2 - Switching between bossbars with condition](#example-2---switching-between-bossbars-with-condition)
|
||||
* [Example 3 - Periodical appearing and disappearing](#example-3---periodical-appearing-and-disappearing)
|
||||
* [Example 4 - Dynamic progress tracking a relative placeholder value](#example-4---dynamic-progress-tracking-a-relative-placeholder-value)
|
||||
* [Example 1 - Changing text](#example-1---changing-text)
|
||||
* [Example 2 - Switching between bossbars with condition](#example-2---switching-between-bossbars-with-condition)
|
||||
* [Example 3 - Periodical appearing and disappearing](#example-3---periodical-appearing-and-disappearing)
|
||||
* [Example 4 - Dynamic progress tracking a relative placeholder value](#example-4---dynamic-progress-tracking-a-relative-placeholder-value)
|
||||
|
||||
# About
|
||||
This feature allows you to send modern bossbars to players, which appear on top of the screen.
|
||||
|
||||
@@ -4,13 +4,11 @@
|
||||
* [Number comparisons](#number-comparisons)
|
||||
* [Text operations](#text-operations)
|
||||
* [Permission](#permission)
|
||||
* [Multiple condition requirements](#multiple-condition-requirements)
|
||||
* [Condition output](#condition-output)
|
||||
* [Configuration](#configuration)
|
||||
* [Usage](#usage)
|
||||
* [Displaying text](#displaying-text)
|
||||
* [Display condition of a feature](#display-condition-of-a-feature)
|
||||
* [Short format](#short-format)
|
||||
* [Conditional text](#conditional-text)
|
||||
* [Using in a field where condition is expected](#using-in-a-field-where-condition-is-expected)
|
||||
* [Using in a field where condition is expected (short format)](#using-in-a-field-where-condition-is-expected-short-format)
|
||||
* [Relational conditions](#relational-conditions)
|
||||
* [Refresh interval](#refresh-interval)
|
||||
* [Examples](#examples)
|
||||
* [Example 1 - Chaining conditional placeholders](#example-1---chaining-conditional-placeholders)
|
||||
@@ -48,7 +46,7 @@ They have 2 main uses in the plugin:
|
||||
> For `=` and `!=` you can check for empty output of a placeholder using `%my_placeholder%=` and `%my_placeholder%!=`.
|
||||
|
||||
> [!NOTE]
|
||||
> For string operations, the text must match placeholder's output exactly, including color codes.
|
||||
> For string operations, the text must match placeholder's output exactly, including color codes, as well as lower/UPPER case letters.
|
||||
If you are using [Placeholder output replacements](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Placeholder-output-replacements),
|
||||
condition must contain the altered output.
|
||||
To see the exact output of a placeholder including color codes, use `/tab parse <player> <placeholder>`.
|
||||
@@ -59,82 +57,65 @@ To see the exact output of a placeholder including color codes, use `/tab parse
|
||||
| `permission:<value>` | Permission requirement | `permission:my.permission` will pass if player has `my.permission` permission |
|
||||
| `!permission:<value>` | Negative permission requirement | `!permission:my.permission` will pass if player **does not have** `my.permission` permission |
|
||||
|
||||
# Multiple condition requirements
|
||||
Each condition has a `conditions` parameter, which a list of conditions. If you define more than 1 condition, you must specify the condition type.
|
||||
This value can be found under `type` field. Types are:
|
||||
* `AND` - all sub-conditions must be met for the final condition to pass
|
||||
* `OR` - at least one sub-condition must be met for the final condition to pass
|
||||
|
||||
If you only defined one subcondition, you don't need to define the type at all, since it's not used for anything.
|
||||
|
||||
# Condition output
|
||||
If using condition as a placeholder, you can specify output in both cases using `true` and `false` values. `true` is used when condition passes, `false` if not. If using condition only as a view requirement, you can leave these values empty / not specify them at all.
|
||||
|
||||
# Configuration
|
||||
Open **config.yml** and find this section:
|
||||
```
|
||||
conditions:
|
||||
health:
|
||||
conditions:
|
||||
- '%health%<21'
|
||||
- '%health%>15'
|
||||
type: AND
|
||||
true: Healthy!
|
||||
false: Damaged!
|
||||
```
|
||||
`health` is name of our condition in this case.
|
||||
`conditions` is a list of subconditions that must be met for this condition to pass.
|
||||
`type` defines whether all subconditions must be met or at least one.
|
||||
`true` & `false` define output in both cases.
|
||||
|
||||
# Usage
|
||||
You have 2 ways to use conditions.
|
||||
There are multiple different ways you can use conditions, all of which are described below.
|
||||
|
||||
## Displaying text
|
||||
The first way is to use conditions to display text.
|
||||
Configure outputs in `true` and `false` values and then use `%condition:<name>%`,
|
||||
which will output text defined in `true` or `false` depending on if condition is met or not.
|
||||
<details>
|
||||
<summary>Example</summary>
|
||||
## Conditional text
|
||||
This way allows you to create conditional text that depends on other placeholders.
|
||||
|
||||
First, find the `conditions` section in your config and create a new condition like this one:
|
||||
```
|
||||
conditions:
|
||||
serverName:
|
||||
lobby:
|
||||
conditions:
|
||||
- "%server%=lobby"
|
||||
true: "You are in the lobby"
|
||||
- '%server%=lobby'
|
||||
true: "You are in lobby"
|
||||
false: "You are not in lobby"
|
||||
```
|
||||
This condition checks if player is in server `lobby` and if yes, displays `"You are in lobby"`, otherwise displays `"Your are not in lobby"`.
|
||||
To check for more than just 1 condition, add more to the list. Additionally, you will now need to specify `type`, which is either `AND` or `OR`.
|
||||
|
||||
Use with `%condition:serverName%`
|
||||
</details>
|
||||
|
||||
## Display condition of a feature
|
||||
The second way is to use condition's name in places where a condition is accepted.
|
||||
This includes display conditions for bossbar, scoreboard and layout.
|
||||
In these cases, true/false texts are unused; therefore, they do not need to be defined.
|
||||
<details>
|
||||
<summary>Example</summary>
|
||||
|
||||
For example,
|
||||
```
|
||||
conditions:
|
||||
MyCondition:
|
||||
lobby:
|
||||
conditions:
|
||||
- "permission:tab.admin"
|
||||
- '%server%=lobby'
|
||||
- '%server%=lobby2'
|
||||
type: OR
|
||||
true: "You are in lobby"
|
||||
false: "You are not in lobby"
|
||||
```
|
||||
will return the lobby text if player is in server `lobby` **OR** in server `lobby2`. If you want both of those conditions to be required to display text, use `type: AND`.
|
||||
It is not possible to combine both `AND` and `OR` in a single condition. To do that, create multiple conditions and use one inside another (see Example 2 below).
|
||||
|
||||
Now that the condition is created, we can use it using `%condition:lobby%`. This placeholder will return the texts configured in `true` and `false` values based on whether the condition was met or not.
|
||||
|
||||
## Using in a field where condition is expected
|
||||
A lot of the features support either `display-condition` or `disable-condition`. The value configured is expected to be a condition. Let's say we want to use it as a display condition somewhere, for example when player is in server `lobby`. First, create the condition:
|
||||
```
|
||||
scoreboards:
|
||||
MyScoreboard:
|
||||
display-condition: MyCondition
|
||||
conditions:
|
||||
lobby:
|
||||
conditions:
|
||||
- '%server%=lobby'
|
||||
```
|
||||
As you can see, `true` and `false` were not configured. This is because we are not trying to display any text, we are passing the condition as an object to evaluate.
|
||||
Then, use `display-condition: lobby` in the feature. This will evaluate the condition named `lobby` and either display or not display the visual based on player's server. To check for 2 servers, use
|
||||
```
|
||||
conditions:
|
||||
lobby:
|
||||
conditions:
|
||||
- '%server%=lobby'
|
||||
- '%server%=lobby2'
|
||||
type: OR
|
||||
```
|
||||
Now, the condition `lobby` will pass if player is in server `lobby` **OR** in server `lobby2`. To require both of the conditions to be met instead of at least 1, use `type: AND`.
|
||||
It is not possible to combine both `AND` and `OR` in a single condition. To do that, create multiple conditions and use one inside another (see Example 2 below).
|
||||
|
||||
In this example, scoreboard will only be displayed to players with `tab.admin` permission.
|
||||
</details>
|
||||
## Using in a field where condition is expected (short format)
|
||||
When using condition as a `display-condition` or `disable-condition`, you don't need to specify `true` / `false` texts. In fact, you only need the list of conditions and separator (AND / OR).
|
||||
For this purpose, short format was created. Instead of having to create a condition and using it, you can specify the expression directly.
|
||||
|
||||
### Short format
|
||||
If trying to use a condition on place where it's available (bossbar display condition, scoreboard display condition) where you don't need the true/false values, you can use a short format instead.
|
||||
|
||||
This can be used by simply creating all subconditions and separating them with `;` for `AND` condition type. For `OR` type, use `|`.
|
||||
**Single condition example**:
|
||||
```
|
||||
display-condition: "%server%=lobby"
|
||||
@@ -149,6 +130,24 @@ display-condition: "%server%=lobby;%world%=world"
|
||||
```
|
||||
display-condition: "%server%=lobby|%server%=lobby2"
|
||||
```
|
||||
It is not possible to combine both `AND` (`;`) and `OR` (`|`) in a single condition. To do that, create multiple conditions and use one inside another (see Example 2 below).
|
||||
|
||||
## Relational conditions
|
||||
> [!NOTE]
|
||||
> This section only describes differences from standard conditions. For more information about those, see sections above.
|
||||
|
||||
Relational conditions allow you to check placeholder outputs for the player who is looking instead of only the target player.
|
||||
|
||||
To make placeholder check for viewer instead of the target player, use `%viewer:<placeholder>%` instead of just `%placeholder%`.
|
||||
You can still check for target player using `%placeholder%` or `%target:<placeholder>%` (if you want to make it more clear for the reader).
|
||||
Example: `%viewer:server%=%server%` will pass if viewer is in the same server as the target player.
|
||||
|
||||
There are 2 ways to use it:
|
||||
* display-condition of layout player groups (which is currently the only feature supporting it as it would not make sense elsewhere)
|
||||
* A placeholder using `%rel_condition:<condition>%` syntax
|
||||
|
||||
If trying to use relational condition in `%condition:name%` syntax, it will return a warning text instead of evaluating the condition.
|
||||
Same the other way around, if trying to use standard condition in `%rel_condition:name%` syntax, it will return a warning text.
|
||||
|
||||
# Refresh interval
|
||||
Conditions are just placeholders after all, and, as such, they must be refreshed periodically.
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
* [About](#about)
|
||||
* [Configuration](#configuration)
|
||||
* [Grouping players only from certain servers](#grouping-players-only-from-certain-servers)
|
||||
* [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)
|
||||
* [Compatibility with other plugins](#compatibility-with-other-plugins)
|
||||
@@ -82,8 +83,8 @@ server-groups:
|
||||
```yaml
|
||||
server-groups:
|
||||
lobbies:
|
||||
- Lobby-* # Matches: Lobby-1, Lobby-2, Lobby-Hub, etc.
|
||||
- *-lobby # Matches: eu-lobby, us-lobby, etc.
|
||||
- "Lobby-*" # Matches: Lobby-1, Lobby-2, Lobby-Hub, etc.
|
||||
- "*-lobby" # Matches: eu-lobby, us-lobby, etc.
|
||||
```
|
||||
|
||||
**Regex Patterns** - Use `regex:` prefix for advanced pattern matching:
|
||||
@@ -123,4 +124,4 @@ 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.
|
||||
|
||||
# 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.
|
||||
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.
|
||||
@@ -101,8 +101,8 @@ Design `per-world` is checked first. If condition is not met, `default` is displ
|
||||
If a chaining 3 or more and player meets 2 or more conditions for 2 (or more) different designs, the first one defined in the config will be displayed. You can swap order of designs based on your priority needs.
|
||||
|
||||
# Compatibility with other plugins
|
||||
Header/Footer is a feature that cannot be handled by multiple plugins at once.
|
||||
To make sure no other plugin sends their own Header/Footer when not disabled in the plugin's config properly, TAB will block header/footer coming from all other plugins if this feature is enabled (and not disabled for a player with a condition).
|
||||
Header/Footer is a feature that cannot be handled by multiple plugins at once.
|
||||
To make sure no other plugin sends their own Header/Footer when not disabled in the plugin's config properly, TAB will block header/footer coming from all other plugins if this feature is enabled (and not disabled for a player with a condition).
|
||||
An exception to this is Velocity installation, where this "anti-override" is not available.
|
||||
|
||||
# Additional info
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
* [Additional note 3 - Entry overlap](#additional-note-3---entry-overlap)
|
||||
* [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.
|
||||
@@ -57,7 +58,7 @@ layout:
|
||||
- '43|&8&m '
|
||||
groups:
|
||||
staff:
|
||||
condition: permission:tab.staff
|
||||
display-condition: permission:tab.staff
|
||||
slots:
|
||||
- 24-40
|
||||
players:
|
||||
@@ -80,9 +81,9 @@ All the options are explained in the following table.
|
||||
A layout consists of the following options:
|
||||
| Option name | Description |
|
||||
|-------------|------------------|
|
||||
| `condition` (optional) | [Condition](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Conditional-placeholders) (either condition name or a conditional expression) that must be met for the layout to be displayed. If not defined, layout will be displayed without any required condition. If player does not meet condition, another layout may be displayed based on chaining (see below for more info). |
|
||||
| `display-condition` (optional) | [Condition](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Conditional-placeholders) (either condition name or a conditional expression) that must be met for the layout to be displayed. If not defined, layout will be displayed without any required condition. If player does not meet condition, another layout may be displayed based on chaining (see below for more info). |
|
||||
| `default-skin` (optional) | Overrides global `default-skin` setting for this layout. |
|
||||
| `slot-count` (optional) | Amount of slots to show in this layout. If not specified, defaults to `80`. **This option is coming in TAB v5.5.0**. |
|
||||
| `slot-count` (optional) | Amount of slots to show in this layout. If not specified, defaults to `80`. **Only works on 1.19.3+**. |
|
||||
| `fixed-slots` | List of fixed slots, see below for more info. |
|
||||
| `player-groups` | Map of player groups, see below for more info. |
|
||||
|
||||
@@ -112,7 +113,7 @@ Example:
|
||||
### Player groups
|
||||
These are groups of players that meet the specified condition. They consist of 2 parts - condition and slots.
|
||||
|
||||
**1. Condition**
|
||||
**1. Display Condition**
|
||||
The condition players must meet to be displayed in the group.
|
||||
If a player doesn't meet condition for a group, the next group's condition is checked
|
||||
(groups are checked in the order they are defined in config).
|
||||
@@ -127,7 +128,7 @@ Example:
|
||||
myLayout:
|
||||
groups:
|
||||
staff:
|
||||
condition: permission:tab.staff
|
||||
display-condition: permission:tab.staff
|
||||
slots:
|
||||
- '24-40'
|
||||
players:
|
||||
@@ -146,7 +147,7 @@ Example:
|
||||
```
|
||||
layouts:
|
||||
staff:
|
||||
condition: "permission:tab.staff"
|
||||
display-condition: "permission:tab.staff"
|
||||
fixed-slots:
|
||||
...
|
||||
groups:
|
||||
@@ -168,7 +169,7 @@ Currently, TAB supports these skin formats:
|
||||
|--------|---------|-------------|
|
||||
| `mineskin:<UUID>` | `mineskin:37e93c8e12cd426cb28fce31969e0674` | Takes UUID from [MineSkin](https://mineskin.org). |
|
||||
| `player:<name>` | `player:Notch` | Displays skin of specified player. |
|
||||
| `texture:<texture>` | `texture:469b3bac406b51ba0e76c2c218aa4d45fde9ea7c101c85fbd8106c92c4aa36dd` | Uses skin texture from `textures.minecraft.net`. Skin browsing websites show this value. |
|
||||
| `texture:<texture>` | `texture:469b3bac406b51ba0e76c2c218aa4d45fde9ea7c101c85fbd8106c92c4aa36dd` | Uses skin texture from `textures.minecraft.net`. Skin browsing websites often show this value (for example `Texture URL` on mineskin.org). |
|
||||
|
||||
# Compatibility with other plugins
|
||||
This feature does not have any compatibility issues with other plugins.
|
||||
@@ -202,19 +203,19 @@ Each layout only displays players in that world.
|
||||
```
|
||||
layouts:
|
||||
layout1:
|
||||
condition: "%world%=world1"
|
||||
display-condition: "%world%=world1"
|
||||
fixed-slots: []
|
||||
groups:
|
||||
players:
|
||||
condition: "%world%=world1"
|
||||
display-condition: "%world%=world1"
|
||||
slots:
|
||||
- 1-80
|
||||
layout2:
|
||||
condition: "%world%=world2"
|
||||
display-condition: "%world%=world2"
|
||||
fixed-slots: []
|
||||
groups:
|
||||
players:
|
||||
condition: "%world%=world2"
|
||||
display-condition: "%world%=world2"
|
||||
slots:
|
||||
- 1-80
|
||||
```
|
||||
@@ -222,11 +223,11 @@ You can also merge multiple worlds into a group.
|
||||
```
|
||||
layouts:
|
||||
layout1:
|
||||
condition: "%world%=world1|%world%=world2"
|
||||
display-condition: "%world%=world1|%world%=world2"
|
||||
fixed-slots: []
|
||||
groups:
|
||||
players:
|
||||
condition: "%world%=world1|%world%=world2"
|
||||
display-condition: "%world%=world1|%world%=world2"
|
||||
slots:
|
||||
- 1-80
|
||||
```
|
||||
@@ -234,7 +235,7 @@ You can also replicate "ignore-effect-in-worlds" by creating a layout with condi
|
||||
```
|
||||
layouts:
|
||||
globalWorld:
|
||||
condition: "%world%=globalWorld"
|
||||
display-condition: "%world%=globalWorld"
|
||||
fixed-slots: []
|
||||
groups:
|
||||
players: # No condition here to show all players
|
||||
@@ -297,21 +298,65 @@ layout:
|
||||
- "61|&2&lSkyblock"
|
||||
groups:
|
||||
lobby:
|
||||
condition: "%server%=lobby"
|
||||
display-condition: "%server%=lobby"
|
||||
slots:
|
||||
- 2-20
|
||||
survival:
|
||||
condition: "%server%=survival"
|
||||
display-condition: "%server%=survival"
|
||||
slots:
|
||||
- 22-40
|
||||
creative:
|
||||
condition: "%server%=creative"
|
||||
display-condition: "%server%=creative"
|
||||
slots:
|
||||
- 42-60
|
||||
skyblock:
|
||||
condition: "%server%=skyblock"
|
||||
display-condition: "%server%=skyblock"
|
||||
slots:
|
||||
- 62-80
|
||||
```
|
||||
You can also remove the last condition (in this case `condition: "%server=skyblock%"`) and the columns will be used for all other servers, instead of just a specified one.
|
||||
You can also remove the last condition (in this case `display-condition: "%server=skyblock%"`) and the columns will be used for all other servers, instead of just a specified one.
|
||||

|
||||
|
||||
|
||||
## Example 2 - Per-world playerlist
|
||||
If you want to reproduce the per-world-playerlist feature but with layouts, you can use relational conditions to check the viewer's world (`%viewer:world%`) with the target player's world (`%world%`) like so:
|
||||
```yml
|
||||
layout:
|
||||
enabled: true
|
||||
direction: COLUMNS
|
||||
default-skin: mineskin:383747683
|
||||
enable-remaining-players-text: true
|
||||
remaining-players-text: '... and %s more'
|
||||
empty-slot-ping-value: 1000
|
||||
layouts:
|
||||
default:
|
||||
slot-count: 80
|
||||
fixed-slots:
|
||||
- '1|&3Website&f:'
|
||||
- '2|&bmyserver.net'
|
||||
- '3|&8&m '
|
||||
- '4|&3Name&f:'
|
||||
- '5|&b%player%'
|
||||
- '7|&3Rank&f:'
|
||||
- '8|Rank: %group%'
|
||||
- '10|&3World&f:'
|
||||
- '11|&b%world%'
|
||||
- '13|&3Time&f:'
|
||||
- '14|&b%time%'
|
||||
- '21|&3Teamspeak&f:'
|
||||
- '22|&bts.myserver.net'
|
||||
- '23|&8&m '
|
||||
- '41|&3Store&f:'
|
||||
- '42|&bshop.myserver.net'
|
||||
- '43|&8&m '
|
||||
groups:
|
||||
players:
|
||||
display-condition: "%viewer:world%=%world%"
|
||||
slots:
|
||||
- 24-40
|
||||
- 44-80
|
||||
```
|
||||
> [!NOTE]
|
||||
> This is just an example, relational conditions are not limited to displaying only players in your world.
|
||||
> If you want players in your server on proxy, use `%viewer:server%=%server%` in `display-condition`.
|
||||
> This works for any placeholder offered by TAB or by PlaceholderAPI.
|
||||
@@ -3,6 +3,9 @@
|
||||
* [Configuration](#configuration)
|
||||
* [config.yml](#configyml)
|
||||
* [groups.yml and users.yml](#groupsyml-and-usersyml)
|
||||
* [Global settings](#global-settings)
|
||||
* [Per-world / per-server](#per-world--per-server)
|
||||
* [Priorities](#priorities)
|
||||
* [Name color](#name-color)
|
||||
* [Commands](#commands)
|
||||
* [Placeholders](#placeholders)
|
||||
@@ -63,6 +66,7 @@ All of the options are explained in the following table.
|
||||
## groups.yml and users.yml
|
||||
This part allows you to configure prefix and suffix. They are called `tagprefix` and `tagsuffix`, where "tag" refers to name tag, to allow separation from tablist settings.
|
||||
|
||||
### Global settings
|
||||
Properties can be applied in 2 ways: groups and users. Users can be defined by both username and their UUID. Values applied to users take priority over groups.
|
||||
|
||||
**groups.yml**
|
||||
@@ -94,6 +98,7 @@ _DEFAULT_:
|
||||
tagprefix: "&7" # This will be displayed on everyone except admin
|
||||
```
|
||||
|
||||
### Per-world / per-server
|
||||
Values can also be applied per-world (and per-server on a proxy), where they can be defined per group/user. These values take priority over global settings. Example:
|
||||
**groups.yml**
|
||||
```
|
||||
@@ -120,6 +125,7 @@ per-world:
|
||||
tagsuffix: "Suffix in all worlds starting with lobby-"
|
||||
```
|
||||
|
||||
### Priorities
|
||||
The full list of priorities to choose correct prefix/suffix for a player looks like this:
|
||||
1. value set using the [API](#api)
|
||||
2. per-world / per-server applied to username
|
||||
@@ -136,13 +142,13 @@ This list is browsed through until the first match is found. If no match is foun
|
||||
Values are taken independently of each other.
|
||||
This means you can set per-world tagprefix, but only keep one global tagsuffix, for example.
|
||||
|
||||
You can see the source of a value displayed on player by using `/tab debug <player>` and checking "source"
|
||||
part of the value you are looking for.
|
||||
You can see the source of a value displayed on player by using `/tab dump <player>`, opening the link and checking "source"
|
||||
part of the value you are looking for (tagprefix / tagsuffix).
|
||||
|
||||
## Name color
|
||||
On versions 1.12.2 and below, name color follows the last color of prefix. This also includes magic codes. On 1.13+, this is no longer the case. Instead, a new field called "team color" was created. This is an enum constant, where we need to pick from 22 options - 16 colors, 5 magic codes and reset. **As you can see, it doesn't allow for RGB colors**. This also means name can no longer have color and magic codes simultaneously.
|
||||
|
||||
TAB does not have an option for configuring team color to avoid complexity, such as per-version configuration, or having to configure an additional field.
|
||||
TAB does not have an option for configuring team color to avoid complexity, such as per-version configuration, or having to configure an additional field.
|
||||
For that reason, TAB detects the last used color code and uses that for team color.
|
||||
> [!CAUTION]
|
||||
> An exception is when using MiniMessage syntax / using RGB codes on server which has MiniMessage library. In this scenario, it is not possible to detect reset (`&r`) as last used "color", because in Minecraft components it is not a color, rather, it resets formatting by setting all magic codes to false / color to null. If you use `&r` (`<reset>`) and notice it not working, replace it with `&f` (`<white>`) (or add it after the reset to be safe). There seems to be no difference between setting color to `WHITE` versus `RESET`.
|
||||
@@ -168,7 +174,7 @@ Here are TAB's PlaceholderAPI placeholders you can use when this feature is enab
|
||||
| `%tab_nametag_visibility%` | "Enabled" if player can see nametags, "Disabled" if disabled using `/tab nametag toggleview` |
|
||||
|
||||
# Limitations
|
||||
* [<1.12.2] Prefix/suffix length is limited to 16 characters (including color codes). Any characters beyond that will be cut to prevent players getting disconnected.
|
||||
* [1.12.2-] Prefix/suffix length is limited to 16 characters (including color codes). Any characters beyond that will be cut to prevent players getting disconnected.
|
||||
* [1.13+] Name color is controlled by a chat format enum constant. This means:
|
||||
* Name can only have one code. That is either color or magic code (such as &4 or &l), but not both.
|
||||
* Name does not support RGB codes. Any used RGB colors will be rounded to the nearest legacy code.
|
||||
@@ -182,7 +188,7 @@ If you want to avoid these at all costs, the only way is to create entities (arm
|
||||
* [CustomNameplates](https://polymart.org/product/2543/customnameplates)
|
||||
|
||||
# Compatibility with other plugins
|
||||
Scoreboard teams is a feature that cannot be handled by multiple plugins at once.
|
||||
Scoreboard teams is a feature that cannot be handled by multiple plugins at once.
|
||||
To make sure no other plugin sends their own teams when not disabled in the plugin's config properly, TAB will block players being added into teams coming from all other plugins (if this feature is enabled and not disabled for a player with a condition).
|
||||
|
||||
# Additional info
|
||||
@@ -222,7 +228,7 @@ In config.yml. Keep in mind, you'll not be able to use any team features if you
|
||||
## Additional note 6 - Vanish hook
|
||||
When a player is [vanished](https://github.com/NEZNAMY/TAB/wiki/Additional-information#vanish-detection), their team is unregistered for all other players who do **not** have `tab.seevanished` permission.
|
||||
|
||||
The purpose of this is to avoid players figuring out a staff member is still online but just vanished by "checking" registered teams and their members.
|
||||
The purpose of this is to avoid players figuring out a staff member is still online but just vanished by "checking" registered teams and their members.
|
||||
An example of "exploiting" this is to have a 3rd party client spawn a player entity with name of a chosen player (staff member) and seeing if their name is formatted (= team is registered = player is online) or not (offline).
|
||||
|
||||
For this reason, make sure you give all of your staff members who can see vanished players the `tab.seevanished` permission, otherwise the vanished player will appear on top of the tablist (players without team are above players with team) and their nametag will not be formatted.
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
* [Additional note 1 - Spectator gamemode](#additional-note-1---spectator-gamemode)
|
||||
* [Examples](#examples)
|
||||
* [Example 1 - Per-world values](#example-1---per-world-values)
|
||||
* [Example 2 - Displaying hearts](#example-2---displaying-hearts)
|
||||
|
||||
# About
|
||||
This features gives you control over Minecraft's scoreboard objective feature with PLAYER_LIST slot.
|
||||
@@ -35,10 +36,10 @@ All of the options are explained in the following table.
|
||||
| Option name | Default value | Description |
|
||||
|-------------------|-----------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| enabled | true | Enables / Disables the feature |
|
||||
| value | "%ping%" | [1.20.2-] An integer from -2147483648 to 2147483647, doesn't support decimal values. The number is always yellow. Supports placeholders with player-specific output, such as player health. Only visible on 1.20.2 and lower. <br/> **Note**: Even if you only support 1.20.3+, you still need to configure this value to properly evaluate to a number, because the value is still sent to the client (just not displayed). You can set it to `0` for simplicity. |
|
||||
| fancy-value | "&7Ping: %ping%" | [1.20.3+] Any text, supports placeholders with per-player output. Only visible on 1.20.3+, where it completely replaces `value`. |
|
||||
| value | "%ping%" | [1.20.2-] An integer from -2147483648 to 2147483647, doesn't support decimal values. The number is always yellow. Supports placeholders with player-specific output, such as player health. Only visible on 1.20.2 and lower (unless you set `render-type` to `HEARTS`). <br/> **Note**: Even if you only support 1.20.3+, you still need to configure this value to properly evaluate to a number, because the value is still sent to the client (just not displayed). You can set it to `0` for simplicity. |
|
||||
| fancy-value | "&7Ping: %ping%" | [1.20.3+] Any text, supports placeholders with per-player output. Only visible on 1.20.3+, where it completely replaces `value`, or if `render-type` is set to `HEARTS`. |
|
||||
| title | "TAB" | Title to send. Only visible on Bedrock Edition. |
|
||||
| render-type | INTEGER | Render type of the value. Supported values are `INTEGER` and `HEARTS`. |
|
||||
| render-type | INTEGER | Render type of the value. Supported values are `INTEGER` and `HEARTS`. <br />When set to `HEARTS`, the `value` is used displayed even on 1.20.3+, where `fancy-value` is ignored. |
|
||||
| disable-condition | %world%=disabledworld | A [condition](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Conditional-placeholders) (either name of a condition or a conditional expression) that must be met for disabling the feature for players. Set to empty for not disabling the feature ever. <br/> **Note**: Disabling the feature for a player means sending objective unregister packet to them, which results in player not seeing the feature on anyone anymore. It doesn't work the other way around - you cannot disable this feature on target players, only for viewers. |
|
||||
|
||||
# Limitations
|
||||
@@ -97,3 +98,16 @@ playerlist-objective:
|
||||
> This is just an example, the plugin is not limited to displaying different values only per world.
|
||||
> If you want per server values on proxy, use %server% with server names.
|
||||
> This works for any placeholder offered by TAB or by PlaceholderAPI.
|
||||
|
||||
## Example 2 - Displaying hearts
|
||||
In order to display the value as hearts, set `render-type` to `HEARTS`.
|
||||
Then, set `value` to a placeholder that returns player's health. If you have TAB on a backend server, use `%health%`. If you have TAB on a proxy, use PlaceholderAPI's `%player_health_rounded%`.
|
||||
Example configuration:
|
||||
|
||||
```
|
||||
value: "%health%"
|
||||
render-type: HEARTS
|
||||
```
|
||||
When using `HEARTS` render type, `fancy-value` is not displayed by the client, so it doesn't matter what it is set to.
|
||||
Final result:
|
||||

|
||||
@@ -108,7 +108,21 @@ A scoreboard consists of the following options:
|
||||
### Line syntax
|
||||
The lines are specified as a list of strings.
|
||||
|
||||
Since 1.20.3, you can also configure the value on the right side, which no longer has to be a red number. To set it, use `||` sequence and put right-side text after it. Example:
|
||||
Since 1.20.3, you can also configure the value on the right side, which no longer has to be a red number.
|
||||
On <1.20.3, you can configure red numbers with either a static number, or a placeholder that evaluates to a number.
|
||||
|
||||
The final syntax is `<text>||<number format>||<score>`, where:
|
||||
* `<text>` is the main text displayed on the left side
|
||||
* `<number format>` is the new 1.20.3+ feature that replaces `score` with any text, not just a number
|
||||
* `<score>` is the value (red number), only visible on <1.20.3 and must be a number (or a placeholder evaluating to one)
|
||||
|
||||
The second and third part are optional. Example syntaxes that configure a line are:
|
||||
* `<text>`
|
||||
* `<text>||<number format>`
|
||||
* `<text>||<number format>||<score>`
|
||||
* `<text>||||<score>`
|
||||
|
||||
For example:
|
||||
```
|
||||
lines:
|
||||
- "Left 1---"
|
||||
@@ -140,8 +154,7 @@ Additionally, the following configuration:
|
||||
Will give you
|
||||
<img width="583" height="131" alt="image" src="https://github.com/user-attachments/assets/15e809c6-4ecd-4234-a30e-744c2d413335" />
|
||||
|
||||
|
||||
The right side text will not be visible at 1.20.2 and lower, and numbers will be displayed instead.
|
||||
The `number format` will not be visible at 1.20.2 and lower, and `score` will be displayed instead.
|
||||
|
||||
### Hiding a line
|
||||
When a line consists only of a placeholder that returned empty output, the line will be hidden completely. This is intentional to allow dynamic scoreboard size based on placeholder output. This is not the case if empty line is configured (`""`), to allow empty lines in configuration.
|
||||
|
||||
@@ -21,9 +21,6 @@
|
||||
# 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.
|
||||
|
||||
> [!TIP]
|
||||
> To verify you enabled sorting, run `/tab debug`. It will say `Sorting type:` followed by anything except `DISABLED`.
|
||||
|
||||
# Methods of sorting
|
||||
Below are all the different methods that you can use to sort players.
|
||||
|
||||
@@ -33,10 +30,6 @@ This is the default and recommended method. Players will be sorted by their prim
|
||||
First, put your players into groups in your permission plugin.
|
||||
All the ways to do it can be found
|
||||
at [How to assign players into groups](https://github.com/NEZNAMY/TAB/wiki/How-to-assign-players-into-groups).
|
||||
> [!TIP]
|
||||
> Verify player's group using `/tab debug <player>`.
|
||||
It should say `Primary permission group:` followed by group you configured.
|
||||
If not, you did not assign players into groups correctly.
|
||||
|
||||
Second, place all of your groups in to a comma separated list **in order of priority** into `sorting-types`. Example:
|
||||
```yaml
|
||||
@@ -44,15 +37,6 @@ scoreboard-teams:
|
||||
sorting-types:
|
||||
- "GROUPS:owner,admin,mod,default"
|
||||
```
|
||||
> [!TIP]
|
||||
> Verify configured sorting priority of players using `/tab debug <player>`.
|
||||
|
||||
It should show a message like this:
|
||||

|
||||
|
||||
If a group is not in the list, you will get the following:
|
||||

|
||||
If that's the case, you forgot to add that group into the list.
|
||||
|
||||
## PERMISSIONS
|
||||
> [!CAUTION]
|
||||
@@ -71,8 +55,6 @@ scoreboard-teams:
|
||||
sorting-types:
|
||||
- "PERMISSIONS:my.permission.1,my.permission.2,my.permission.3"
|
||||
```
|
||||
> [!TIP]
|
||||
> Verify sorting permissions of a player using `/tab debug <player>`
|
||||
|
||||
## PLACEHOLDER
|
||||
This method sorts players using the output of a placeholder and comparing that to pre-defined values.
|
||||
@@ -233,7 +215,6 @@ The most common mistakes include:
|
||||
* 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).
|
||||
|
||||
[Debug command](https://github.com/NEZNAMY/TAB/wiki/Commands-&-Permissions#tab-debug-player) will help you identify if this is your case.
|
||||
* 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)
|
||||
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
# Content
|
||||
* [About](#about)
|
||||
* [Configuration](#configuration)
|
||||
* [Introduction](#introduction)
|
||||
* [Groups and users](#groups-and-users)
|
||||
* [Per-world / per-server](#per-world--per-server)
|
||||
* [Priority system](#priority-system)
|
||||
* [Placeholder support](#placeholder-support)
|
||||
* [Additional settings](#additional-settings)
|
||||
* [Tips & Tricks](#tips--tricks)
|
||||
* [config.yml](#configyml)
|
||||
* [groups.yml and users.yml](#groupsyml-and-usersyml)
|
||||
* [Format structure](#format-structure)
|
||||
* [Global settings](#global-settings)
|
||||
* [Per-world / per-server](#per-world--per-server)
|
||||
* [Priorities](#priorities)
|
||||
* [Placeholders](#placeholders)
|
||||
* [PlaceholderAPI placeholders](#placeholderapi-placeholders)
|
||||
* [Compatibility with other plugins](#compatibility-with-other-plugins)
|
||||
* [API](#api)
|
||||
* [Examples](#examples)
|
||||
* [Example 1 - Taking prefix/suffix from permission plugin](#example-1---taking-prefixsuffix-from-permission-plugin)
|
||||
|
||||
# About
|
||||
This feature allows you to configure player name formats in the tablist.
|
||||
@@ -20,10 +24,25 @@ take advantage of [this client-sided mechanic](https://github.com/NEZNAMY/TAB/wi
|
||||
which will appear in the tablist.
|
||||

|
||||
|
||||
Settings of this feature can be configured in **config.yml** under **tablist-name-formatting** section.
|
||||
|
||||
# Configuration
|
||||
## Introduction
|
||||
This feature is configured in 2 parts based on what you are configuring. Some settings are in the **config.yml**, but prefix/suffix is configured in **groups.yml** and **users.yml**.
|
||||
## config.yml
|
||||
The first part can be configured in **config.yml** under **tablist-name-formatting** section. It controls everything except prefix and suffix.
|
||||
|
||||
This is how the default configuration looks:
|
||||
```
|
||||
tablist-name-formatting:
|
||||
enabled: true
|
||||
disable-condition: '%world%=disabledworld'
|
||||
```
|
||||
All of the options are explained in the following table.
|
||||
| Option name | Default value | Description |
|
||||
|-----------------------------|-----------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| enabled | true | Enables / Disables the feature |
|
||||
| disable-condition | %world%=disabledworld | A [condition](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Conditional-placeholders) that must be met for disabling the feature for players. Set to empty for not disabling the feature ever. |
|
||||
|
||||
## groups.yml and users.yml
|
||||
### Format structure
|
||||
The display name is split into 3 parts for convenience:
|
||||
`tabprefix` - first part of the text, this is where prefix is usually configured
|
||||
`customtabname` - second part of the text, it will be used as player name. If customtabname is not defined at all (not just setting empty value!), player's original name will be used instead
|
||||
@@ -33,22 +52,20 @@ These values are then merged and displayed in the tablist.
|
||||

|
||||
|
||||
You are free to use the 3 properties in any way you want since they are only split for convenience.
|
||||
However, it is still a good idea to use them for their designed purpose to not get lost a poor configuration.
|
||||
However, it is still a good idea to use them for their designed purpose to not get lost in a poor configuration.
|
||||
This means that the text which should appear before player's name should be put into `tabprefix`,
|
||||
any placeholder that is supposed to display player's name (such as from a nickname plugin)
|
||||
should go into `customtabname` and any text that should appear after player's name should be put into `tabsuffix`.
|
||||
|
||||
## Groups and users
|
||||
Properties can be applied in 2 ways: groups and users.
|
||||
Users can be defined by both username and their UUID.
|
||||
Values applied to users take priority over groups.
|
||||
Example:
|
||||
### Global settings
|
||||
Properties can be applied in 2 ways: groups and users. Users can be defined by both username and their UUID. Values applied to users take priority over groups.
|
||||
|
||||
**groups.yml**
|
||||
```
|
||||
admin:
|
||||
tabprefix: "&4&lAdmin &r"
|
||||
```
|
||||
This can also be achieved with commands, in this case `/tab group admin tabprefix "&4&lAdmin &r"`.
|
||||
Or with an in-game command `/tab group admin tabprefix "&4&lAdmin &r"`.
|
||||
|
||||
**users.yml**
|
||||
```
|
||||
@@ -59,11 +76,11 @@ _NEZNAMY_:
|
||||
237d8b55-3f97-4749-aa60-e9fe97b45062:
|
||||
tabprefix: "&6&lTAB &r"
|
||||
```
|
||||
This can also be achieved with commands, in this case `/tab player _NEZNAMY_ tabprefix "&6&lTAB &r"` or `/tab playeruuid _NEZNAMY_ tabprefix "&6&lTAB &r"`.
|
||||
Or with in-game commands `/tab player _NEZNAMY_ tabprefix "&6&lTAB &r"` and `/tab playeruuid _NEZNAMY_ tabprefix "&6&lTAB &r"` (the player must be online for uuid option to work).
|
||||
|
||||
Properties can also be set as "default" for everyone who does not have them defined.
|
||||
For that purpose, a group keyword `_DEFAULT_` was made.
|
||||
Example:
|
||||
For that purpose, a group keyword `_DEFAULT_` was made.
|
||||
Example:
|
||||
**groups.yml**
|
||||
```
|
||||
admin:
|
||||
@@ -72,7 +89,7 @@ _DEFAULT_:
|
||||
tabprefix: "&7" # This will be displayed on everyone except admin
|
||||
```
|
||||
|
||||
## Per-world / per-server
|
||||
### Per-world / per-server
|
||||
Values can also be applied per-world (and per-server on a proxy), where they can be defined per group/user. These values take priority over global settings. Example:
|
||||
**groups.yml**
|
||||
```
|
||||
@@ -85,21 +102,22 @@ per-server:
|
||||
_DEFAULT_:
|
||||
tabprefix: "&a" # Everyone in server "server1" will have this prefix
|
||||
```
|
||||
For multiple worlds/servers to share the same settings, separate them with `;`.
|
||||
For worlds/servers starting with a specified text, use `*` after shared part. For ending with a shared part, use `*` at the beginning.
|
||||
|
||||
For multiple worlds/servers to share the same settings, separate them with a`;`.
|
||||
For worlds/server starting with a specified text, use `*` after shared part. For ending with a shared part, use `*` at the beginning.
|
||||
Example:
|
||||
```
|
||||
per-world:
|
||||
world1;world2:
|
||||
_DEFAULT_:
|
||||
tabsuffix: "Shared tabsuffix in worlds world1 and world2"
|
||||
tabsuffix: "Shared tagsuffix in worlds world1 and world2"
|
||||
lobby-*:
|
||||
_DEFAULT_:
|
||||
tabsuffix: "Suffix in all worlds starting with lobby-"
|
||||
```
|
||||
|
||||
## Priority system
|
||||
The full list of priorities looks like this:
|
||||
### Priorities
|
||||
The full list of priorities to choose correct prefix/name/suffix for a player looks like this:
|
||||
1. value set using the [API](#api)
|
||||
2. per-world / per-server applied to username
|
||||
3. value applied to username
|
||||
@@ -110,32 +128,31 @@ The full list of priorities looks like this:
|
||||
8. value applied to player's group
|
||||
9. value applied to group `_DEFAULT_`
|
||||
|
||||
This list is browsed through until the first match is found. If no match is found, empty value is used (except `customtabname`, which defaults to player's name).
|
||||
This list is browsed through until the first match is found. If no match is found, empty value is used.
|
||||
|
||||
Values are taken independently of each other.
|
||||
This means you can set per-world tabprefix, but only keep one global tabsuffix, for example.
|
||||
|
||||
You can see the source of a value displayed on player by using `/tab debug <player>` and checking "source"
|
||||
part of the value you are looking for.
|
||||
You can see the source of a value displayed on player by using `/tab dump <player>`, opening the link and checking "source"
|
||||
part of the value you are looking for (tabprefix / customtabname / tabsuffix).
|
||||
|
||||
## Placeholder support
|
||||
All values fully support [TAB's internal placeholders](https://github.com/NEZNAMY/TAB/wiki/Placeholders#internal-placeholders) and [PlaceholderAPI placeholders](https://github.com/PlaceholderAPI/PlaceholderAPI/wiki/Placeholders) including relational placeholders.
|
||||
The number of placeholders is not limited, and they can be used in combination with static text as well.
|
||||
# Placeholders
|
||||
This feature does not offer any internal placeholders, only PlaceholderAPI placeholders.
|
||||
|
||||
## Additional settings
|
||||
| Option name | Default value | Description |
|
||||
|-------------------|-----------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| enabled | true | Enables / Disables the feature |
|
||||
| disable-condition | %world%=disabledworld | A [condition](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Conditional-placeholders) that must be met for disabling the feature for players. Set to empty for not disabling the feature ever. |
|
||||
## PlaceholderAPI placeholders
|
||||
Here are TAB's PlaceholderAPI placeholders you can use when this feature is enabled:
|
||||
| Placeholder | Description |
|
||||
|-------------|-------------|
|
||||
| `%tab_tabprefix%` | Player's current tabprefix with placeholders parsed. |
|
||||
| `%tab_customtabname%` | Player's current customtabname with placeholders parsed. |
|
||||
| `%tab_tabsuffix%` | Player's current tabprefix with placeholders parsed. |
|
||||
| `%tab_tabprefix_raw%` | Player's current raw tabprefix with placeholder identifiers. |
|
||||
| `%tab_customtabname_raw%` | Player's current raw customtabname with placeholder identifiers. |
|
||||
| `%tab_tabsuffix_raw%` | Player's current raw tabsuffix with placeholder identifiers. |
|
||||
|
||||
# Tips & Tricks
|
||||
If you want TAB to only take prefixes/suffixes from your permission plugin,
|
||||
delete all groups from **groups.yml** and only keep this:
|
||||
```
|
||||
_DEFAULT_:
|
||||
tabprefix: "%luckperms-prefix%"
|
||||
tabsuffix: "%luckperms-suffix%"
|
||||
```
|
||||
# Compatibility with other plugins
|
||||
Tablist formatting is a feature that cannot be effectively handled by multiple plugins at once.
|
||||
To make sure no other plugin sets tablist display names when not disabled in the plugin's config properly, TAB will block attempts to modify tablist display names coming from all other plugins (if this feature is enabled and not disabled for a player with a condition).
|
||||
|
||||
# API
|
||||
*To get started with the API, see [Developer API](https://github.com/NEZNAMY/TAB/wiki/Developer-API) page.*
|
||||
@@ -162,4 +179,18 @@ To get the original value set by the plugin based on configuration:
|
||||
* `TabListFormatManager#getOriginalReplacedName(TabPlayer)` - Name with all placeholders parsed
|
||||
* `TabListFormatManager#getOriginalReplacedSuffix(TabPlayer)` - Suffix with all placeholders parsed
|
||||
|
||||
**Note**: These values are only temporary, meaning they won't get saved anywhere and will get reset on player quit or plugin reload. If you wish to save these values into file, use [commands](https://github.com/NEZNAMY/TAB/wiki/Commands-&-Permissions#tab-playergroupplayeruuid-name-property-value-options).
|
||||
**Note**: These values are only temporary, meaning they won't get saved anywhere and will get reset on player quit or plugin reload. If you wish to save these values into file, use [commands](https://github.com/NEZNAMY/TAB/wiki/Commands-&-Permissions#tab-playergroupplayeruuid-name-property-value-options).
|
||||
|
||||
# Examples
|
||||
## Example 1 - Taking prefix/suffix from permission plugin
|
||||
If you want TAB to only take prefixes/suffixes from the permission plugin,
|
||||
delete all groups from **groups.yml** and only keep this:
|
||||
```
|
||||
_DEFAULT_:
|
||||
tabprefix: "%luckperms-prefix%"
|
||||
tabsuffix: "%luckperms-suffix%"
|
||||
```
|
||||
Or a PlaceholderAPI placeholder from your permission plugin if you use a different one.
|
||||
|
||||
> [!NOTE]
|
||||
> When doing this, make sure you don't delete something you didn't mean to delete, for example ta**g**prefix and ta**g**suffix from nametag feature.
|
||||
@@ -1,5 +1,5 @@
|
||||
# About the wiki
|
||||
The wiki explains the functionality of the plugin as of version **5.4.0**.
|
||||
The wiki explains the functionality of the plugin as of version **6.0.0**.
|
||||
If your config looks different or is missing some features, you are using an old version of the plugin.
|
||||
To get the latest version, check [releases](https://github.com/NEZNAMY/TAB/releases/).
|
||||
Wiki for older plugin versions is not available.
|
||||
|
||||
@@ -22,7 +22,7 @@ There are 2 simple steps to this:
|
||||
> (for luckperms /lp group \<group\> setweight \<number\>).
|
||||
|
||||
> [!TIP]
|
||||
> Verify player's group using `/tab debug <player>`. It should say `Primary permission group: <your group>`.
|
||||
> Verify player's group using `/tab dump <player>`, opening the link and check "permissionGroup" in player info.
|
||||
|
||||
## Option 2: Permission nodes
|
||||
Instead of taking whole groups, TAB will check for permission nodes and assign groups based on the highest permission.
|
||||
|
||||
@@ -52,14 +52,12 @@ this has lots of side effects.
|
||||
Most notably, it breaks all name-bound features,
|
||||
especially [nametags](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Nametags) and [sorting](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Sorting-players-in-tablist).
|
||||
For that reason, TAB must detect name change.
|
||||
This is done automatically on Bukkit, BungeeCord and modded platforms.
|
||||
**This detection is not available on Velocity**.
|
||||
|
||||
> [!WARNING]
|
||||
> If you have TAB installed on backend server and a proxy plugin changes nickname, TAB is not able to detect this change, since the modified packets never actually go through the backend server where TAB is installed.
|
||||
|
||||
> [!TIP]
|
||||
> If automatic detection is not working for you (either you use TAB on Velocity, or use proxy plugin for changing names while TAB is on backend), you can use [this API method](https://github.com/NEZNAMY/TAB/blob/master/api/src/main/java/me/neznamy/tab/api/TabPlayer.java#L88) to let TAB know player's name changed.
|
||||
> If automatic detection is not working for you (you use proxy plugin for changing names while TAB is on backend), you can use [this API method](https://github.com/NEZNAMY/TAB/blob/master/api/src/main/java/me/neznamy/tab/api/TabPlayer.java#L88) to let TAB know player's name changed.
|
||||
|
||||
In order for TAB to properly detect name change, player UUID must remain the same,
|
||||
otherwise it's a random entry with a random uuid and random name, which TAB isn't able to match with actual players.
|
||||
@@ -84,7 +82,7 @@ conditions:
|
||||
```
|
||||
Then, use `%condition:nick%` in your `customtabname` instead of the placeholder directly.
|
||||
|
||||
# Tip 2 - Sorting nicked players with the lowest priority
|
||||
## Tip 2 - Sorting nicked players with the lowest priority
|
||||
Let's say your sorting list looks like this:
|
||||
```
|
||||
sorting-types:
|
||||
|
||||
@@ -104,12 +104,20 @@ where `ATLAS` is the atlas and `SPRITE` is the sprite. Note that atlas can conta
|
||||
Example:
|
||||
```
|
||||
header:
|
||||
- '&bDiamond helmet: <sprite:"minecraft:blocks":"minecraft:item/diamond_helmet">'
|
||||
- 'Diamond chestplate: <sprite:"minecraft:blocks":"minecraft:item/diamond_chestplate">'
|
||||
- 'Diamond leggings: <sprite:"minecraft:blocks":"minecraft:item/diamond_leggings">'
|
||||
- 'Diamond boots: <sprite:"minecraft:blocks":"minecraft:item/diamond_boots">'
|
||||
- '&bDiamond helmet: <sprite:"minecraft:items":"minecraft:item/diamond_helmet">'
|
||||
- 'Diamond chestplate: <sprite:"minecraft:items":"minecraft:item/diamond_chestplate">'
|
||||
- 'Diamond leggings: <sprite:"minecraft:items":"minecraft:item/diamond_leggings">'
|
||||
- 'Diamond boots: <sprite:"minecraft:items":"minecraft:item/diamond_boots">'
|
||||
```
|
||||
<img width="386" height="101" alt="image" src="https://github.com/user-attachments/assets/e9a66305-2ae4-44f4-9d8e-a227fb7591e4" />
|
||||
<img width="386" height="101" alt="image" src="https://github.com/user-attachments/assets/e9a66305-2ae4-44f4-9d8e-a227fb7591e4" />
|
||||
|
||||
|
||||
> [!IMPORTANT]
|
||||
> These values change over MC versions (for example items were in `minecraft:blocks` atlas in 1.21.9 - 1.21.10, but got moved to `minecraft:items` in 1.21.11), so make sure you are using the correct values for your MC version. For proxy installations, you may need to use conditional placeholders and check for player's version. On backend, ViaVersion may or may not automatically translate these.
|
||||
|
||||
> [!TIP]
|
||||
> For full list of available sprites you can check out https://www.gamergeeks.net/apps/minecraft/list-of-atlas-sprites. However, it doesn't contain any information regarding different Minecraft versions.
|
||||
|
||||
|
||||
|
||||
## Player sprite
|
||||
|
||||
@@ -35,6 +35,7 @@ Advantages of proxy installation compared to installing on all backend servers i
|
||||
* [Layout](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Layout) can work with all players connected to the proxy
|
||||
* Avoid [header/footer](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Header-&-Footer#additional-note-1---not-resetting-on-server-switch) and [bossbar](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Bossbar#additional-note-2---not-hiding-on-server-switch) not disappearing on server switch
|
||||
* Configuration files in a single place for easier editing / no need to use MySQL for syncing data
|
||||
* Can use new features for new players even if backend is using an older version (RGB, 1.20.3+ NumberFormat and more).
|
||||
|
||||
|
||||
Disadvantages compared to installing on all backend servers instead:
|
||||
@@ -44,11 +45,12 @@ Disadvantages compared to installing on all backend servers instead:
|
||||
Just like when not running a network, install the plugin on all servers where you want the plugin. However, this setup is not as good (see advantages of proxy installation).
|
||||
|
||||
## Mixed installation
|
||||
Mixed installation is absolutely not recommended and should be avoided.
|
||||
It requires you to disable features to make only 1 instance handle a single feature at a time,
|
||||
which most people fail miserably to do,
|
||||
resulting in issues such as header/footer flashing between proxy and backend configuration.
|
||||
With bridge, you are able to achieve almost anything with proxy installation.
|
||||
Mixed installation is absolutely not supported and should be avoided.
|
||||
|
||||
There is no sort of communication between backend and proxy TAB installations (this is only done by TAB-Bridge, which doesn't do anything on its own). Having TAB on both proxy and backend is effectively installing 2 plugins to do the same thing, which will naturally cause issues (although TAB's anti-override for key features should help with this).
|
||||
|
||||
If you really wish to proceed (for example having feature X handled by proxy and feature Y on backend), make sure to properly disable features (`enabled: false`) to avoid problems. However, the only case where this makes any sense is per world playerlist feature, which is not available on proxies. Everything else can be done from proxy side.
|
||||
|
||||
|
||||
# Updating the plugin
|
||||
Updating the plugin is always as simple as replacing old jar with the new one.
|
||||
@@ -59,5 +61,5 @@ TAB will automatically convert your existing configuration files to the latest f
|
||||
(most notably 2.9.2 → 3.0.0, but any other changes as well).
|
||||
|
||||
Downgrading is completely unsupported, as an old plugin version cannot know what the future config format is / will be.
|
||||
Just like Minecraft itself.
|
||||
There is no reason for downgrading anyway, as every version is superior to older versions released before it.
|
||||
Just like Minecraft itself, although TAB won't fail with an error message like Minecraft does, but you will get warnings and / or features not working correctly.
|
||||
There is no reason for downgrading anyway, since in general, every version is typically superior to older versions released before it.
|
||||
@@ -9,6 +9,7 @@ Combine the knowledge gathered by reading the wiki to get interesting results, m
|
||||
* [Showing combined online player count from multiple servers](#showing-combined-online-player-count-from-multiple-servers)
|
||||
* [Option 1 - Using global playerlist](#option-1---using-global-playerlist)
|
||||
* [Option 2 - Using PlaceholderAPI](#option-2---using-placeholderapi)
|
||||
* [Showing head components only to 1.21.9+ players](#showing-head-components-only-to-1219-players)
|
||||
|
||||
# Displaying kill / death counter in tablist
|
||||
This example uses PlaceholderAPI placeholders.
|
||||
@@ -203,4 +204,31 @@ Unlike the first option, this one offers much more freedom, but also requires an
|
||||
First, you need placeholders for online count on specific servers. Bungee expansion does exactly that. The format is `%bungee_<server>%`. To merge the online counts, use math expansion. The syntax will be `%math_0_<expression>%`, where `expression` uses online counts from specific servers from bungee expansion. Finally, you'll end up with a placeholder like `%math_0_{bungee_lobby1}+{bungee_lobby2}%`. This placeholder will merge online counts from `lobby1` and `lobby2`.
|
||||
Please note that by default, bungee expansion updates values only every 30 seconds. This can be changed in PlaceholderAPI's config. If the numbers still don't show correct value, check if the placeholder works correctly using `/papi parse me <placeholder>`. If not, this is not a TAB issue.
|
||||
|
||||
Advantage of this is no requirement for a specific TAB feature to be enabled, which also includes no need to have TAB installed on the proxy. The downside is relying on 3rd party software. If TAB is on proxy, you'll also need [bridge plugin](https://github.com/NEZNAMY/TAB/wiki/TAB-Bridge).
|
||||
Advantage of this is no requirement for a specific TAB feature to be enabled, which also includes no need to have TAB installed on the proxy. The downside is relying on 3rd party software. If TAB is on proxy, you'll also need [bridge plugin](https://github.com/NEZNAMY/TAB/wiki/TAB-Bridge).
|
||||
|
||||
# Showing head components only to 1.21.9+ players
|
||||
Object components were added in 1.21.9, which means they cannot be shown to 1.21.8- players. [If you try to do so, you'll either get an ugly text (BungeeCord / backend), or disconnect with a packet decode error (on Velocity)](https://github.com/NEZNAMY/TAB/wiki/How-to-use-Minecraft-components#compatibility-with--1219).
|
||||
|
||||
To achieve this goal, we will use [relational conditions](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Conditional-placeholders#relational-conditions) and check if viewer is 1.21.9+:
|
||||
```
|
||||
conditions:
|
||||
head:
|
||||
conditions:
|
||||
- "%viewer:player-version-id%>=773"
|
||||
true: "<head:name:%player%> "
|
||||
false: "" # Show nothing
|
||||
```
|
||||
The `773` is the protocol version of 1.21.9, so we can use a simple math operation instead of comparing strings. You can check [TAB's source code](https://github.com/NEZNAMY/TAB/blob/master/shared/src/main/java/me/neznamy/tab/shared/ProtocolVersion.java) for the full list. This condition will return head component of the **target** player if the **viewer** is 1.21.9+, empty value otherwise.
|
||||
[If you want to use this head syntax, you'll need to disable MiniMessage support](https://github.com/NEZNAMY/TAB/wiki/How-to-use-Minecraft-components#player-sprite), or use a syntax supported by MiniMessage (which is currently extremely limited).
|
||||
Use this condition with `%rel_condition:head%`. The rel is important, so TAB knows the placeholder is a relational placeholder. If you forget to, the condition will return a warning message instead of configured value.
|
||||
To showcase it, I have this in **groups.yml** (to show player versions as well):
|
||||
```
|
||||
_DEFAULT_:
|
||||
tabprefix: "%rel_condition:head%"
|
||||
tabsuffix: " %player-version% (%player-version-id%)"
|
||||
```
|
||||
1.21.11 player's view:
|
||||
<img width="634" height="103" alt="image" src="https://github.com/user-attachments/assets/4c762fae-3166-4247-865f-c87d1916afd7" />
|
||||
|
||||
1.21.8 player's view:
|
||||
<img width="581" height="106" alt="image" src="https://github.com/user-attachments/assets/cb7ab93d-0db5-4968-bcfe-60004125ddf1" />
|
||||
@@ -65,12 +65,13 @@ These placeholders are available on all platforms.
|
||||
| %bedrock% | -1 | Returns `true`/`false` based on whether player is using Bedrock edition or not (Java) |
|
||||
| %bossbar_announce_time_total_\<bossbar\>% | -1 | Total time (inputted value) of an announced bossbar. |
|
||||
| %bossbar_announce_time_left_\<bossbar\>% | 100 | Remaining time of an announced bossbar. |
|
||||
| %online_\<server name\>% | 1000 | Amount of online players on specified server (excluding vanished players) |
|
||||
| %% | -1 | Returns the `%` symbol. Useful to display `%` symbol without breaking all placeholders after it |
|
||||
|
||||
## Backend only
|
||||
These placeholders only work when TAB is installed on a backend server, they don't work when TAB is on a proxy. If you have TAB installed on proxy and wish to use these, use their [PlaceholderAPI](https://github.com/NEZNAMY/TAB/wiki/Quick-PlaceholderAPI-startup-guide) alternative with [Bridge plugin](https://github.com/NEZNAMY/TAB/wiki/TAB-Bridge) installed for PlaceholderAPI support on proxy.
|
||||
| Identifier | Refresh interval | Description | PlaceholderAPI alternative |
|
||||
| ------------- | ------------- | ------------- | ------------- |
|
||||
|---------------|---------------| ------------- | ------------- |
|
||||
| %health% | 100 | Player's health, rounded up to match Minecraft's heart display. | %player_health_rounded% |
|
||||
| %displayname% | 500 | Player's display name value set by permission plugin. | %player_displayname% |
|
||||
| %tps% | 1000 | TPS of server from the last minute. Measured by the server. Not directly available on modded platforms - primitive approximation using MSPT (TPS = 1000/MSPT, max 20) is used there. | %server_tps_1% |
|
||||
@@ -82,7 +83,6 @@ These placeholders only work when TAB is installed on a backend server, they don
|
||||
|------------------------------|------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| %server% | -1 | Name of server where player is, defined in proxy's config. |
|
||||
| %serveronline% | 1000 | Amount of online players on server where the player is (excluding vanished players) |
|
||||
| %online_\<server name\>% | 1000 | Amount of online players on specified server (excluding vanished players) |
|
||||
| %playerlist-group_\<group\>% | 1000 | Amount of online players in specified [global playerlist](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Global-playerlist) group (therefore the feature must be enabled) |
|
||||
|
||||
# PlaceholderAPI
|
||||
@@ -102,41 +102,46 @@ placeholders:
|
||||
in **config.yml**.
|
||||
Full list of placeholders (general + feature specific):
|
||||
|
||||
**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%`) |
|
||||
**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**:
|
||||
| Identifier | Description |
|
||||
| ------------- | ------------- |
|
||||
**BossBar**:
|
||||
|
||||
| Identifier | Description |
|
||||
|-------------------------|-----------------------------------------|
|
||||
| `%tab_bossbar_visible%` | "Enabled" if visible, "Disabled" if not |
|
||||
|
||||
**NameTags**:
|
||||
| Identifier | Description |
|
||||
| ------------- | ------------- |
|
||||
| `%tab_tagprefix%` | Player's current tagprefix with placeholders parsed. |
|
||||
| `%tab_tagsuffix%` | Player's current tagprefix with placeholders parsed. |
|
||||
| `%tab_tagprefix_raw%` | Player's current raw tagprefix with placeholder identifiers. |
|
||||
| `%tab_tagsuffix_raw%` | Player's current raw tagsuffix with placeholder identifiers. |
|
||||
**NameTags**:
|
||||
|
||||
| Identifier | Description |
|
||||
|----------------------------|----------------------------------------------------------------------------------------------|
|
||||
| `%tab_tagprefix%` | Player's current tagprefix with placeholders parsed. |
|
||||
| `%tab_tagsuffix%` | Player's current tagprefix with placeholders parsed. |
|
||||
| `%tab_tagprefix_raw%` | Player's current raw tagprefix with placeholder identifiers. |
|
||||
| `%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**:
|
||||
| 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) |
|
||||
|
||||
| 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**:
|
||||
| Identifier | Description |
|
||||
| ------------- | ------------- |
|
||||
| `%tab_tabprefix%` | Player's current tabprefix with placeholders parsed. |
|
||||
| `%tab_customtabname%` | Player's current customtabname with placeholders parsed. |
|
||||
| `%tab_tabsuffix%` | Player's current tabprefix with placeholders parsed. |
|
||||
| `%tab_tabprefix_raw%` | Player's current raw tabprefix with placeholder identifiers. |
|
||||
**Tablist name formatting**:
|
||||
|
||||
| Identifier | Description |
|
||||
|---------------------------|------------------------------------------------------------------|
|
||||
| `%tab_tabprefix%` | Player's current tabprefix with placeholders parsed. |
|
||||
| `%tab_customtabname%` | Player's current customtabname with placeholders parsed. |
|
||||
| `%tab_tabsuffix%` | Player's current tabprefix with placeholders parsed. |
|
||||
| `%tab_tabprefix_raw%` | Player's current raw tabprefix with placeholder identifiers. |
|
||||
| `%tab_customtabname_raw%` | Player's current raw customtabname with placeholder identifiers. |
|
||||
| `%tab_tabsuffix_raw%` | Player's current raw tabsuffix with placeholder identifiers. |
|
||||
| `%tab_tabsuffix_raw%` | Player's current raw tabsuffix with placeholder identifiers. |
|
||||
|
||||
# Relational placeholders
|
||||
## About
|
||||
|
||||
@@ -25,9 +25,4 @@ older software will not be compatible with the new one.
|
||||
You should always be running the latest versions of both TAB and Bridge.
|
||||
|
||||
# Confirming it works
|
||||
You have many ways to verify TAB and Bridge are properly connected. Besides checking if the features above work, the easiest one is to run `/btab debug <player>`.
|
||||
If it's properly connected, you'll get a message saying this:
|
||||

|
||||
If it's not properly connected, you'll get this message:
|
||||

|
||||
If this is the case, it means Bridge is either not installed or versions are not compatible. Update both to the latest version.
|
||||
You have many ways to verify TAB and Bridge are properly connected. Besides checking if the features above work, the easiest one is to run `/btab dump <player>` and check if bridge connection says connected. If not, it means Bridge is either not installed or versions are not compatible. Update both to the latest version.
|
||||
@@ -8,14 +8,14 @@ jobs:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4 # https://github.com/actions/checkout/releases
|
||||
- uses: actions/checkout@v6 # https://github.com/actions/checkout/releases
|
||||
with:
|
||||
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
|
||||
- name: Set up JDK 21
|
||||
uses: actions/setup-java@v4 # https://github.com/actions/setup-java/releases
|
||||
- name: Set up JDK 25
|
||||
uses: actions/setup-java@v5 # https://github.com/actions/setup-java/releases
|
||||
with:
|
||||
distribution: 'temurin'
|
||||
java-version: 21
|
||||
java-version: 25
|
||||
- name: Cache Gradle
|
||||
uses: actions/cache@v4.2.0 # https://github.com/actions/cache/releases
|
||||
with:
|
||||
@@ -31,11 +31,15 @@ jobs:
|
||||
run: ./gradlew clean build
|
||||
- name: Rename JAR with Build Number
|
||||
run: |
|
||||
original_jar=$(find jar/build/libs -type f -name "TAB-*.jar")
|
||||
new_name=$(echo "$original_jar" | sed "s/TAB-\(.*\)\.jar/TAB-\1+${{ github.run_number }}.jar/")
|
||||
mv "$original_jar" "$new_name"
|
||||
find jar/build/libs -type f -name "TAB v*.jar" -print0 | while IFS= read -r -d '' original; do
|
||||
dir=$(dirname "$original")
|
||||
base=$(basename "$original")
|
||||
new_base="${base%.jar}+${{ github.run_number }}.jar"
|
||||
new_path="$dir/$new_base"
|
||||
mv "$original" "$new_path"
|
||||
done
|
||||
- name: Upload a Build Artifact
|
||||
uses: actions/upload-artifact@v4.3.3 # https://github.com/actions/upload-artifact/releases
|
||||
with:
|
||||
name: Click here to download
|
||||
path: jar/build/libs/TAB-*+${{ github.run_number }}.jar
|
||||
path: "jar/build/libs/TAB v*+${{ github.run_number }}.jar"
|
||||
@@ -15,7 +15,7 @@ If you want to benefit from a recent change in the code before official release,
|
||||
from [GitHub actions](https://github.com/NEZNAMY/TAB/actions) (you must be logged into GitHub to be able to download it).
|
||||
|
||||
# Compiling
|
||||
Compilation requires JDK 21 and up.
|
||||
Compilation requires JDK 25 and up.
|
||||
To compile the plugin, run `./gradlew build` from the terminal.
|
||||
Once the plugin compiles, grab the jar from `/jar/build/libs/` folder.
|
||||
The universal jar contains all modules for all supported platforms.
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
package me.neznamy.tab.api.event.plugin;
|
||||
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import me.neznamy.tab.api.event.TabEvent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* Called when an unknown placeholder is about to be registered.
|
||||
* It defaults to a PlaceholderAPI implementation, but can be overridden.
|
||||
*/
|
||||
public interface PlaceholderRegisterEvent extends TabEvent {
|
||||
|
||||
/**
|
||||
* Returns placeholder identifier starting and ending with {@code %}
|
||||
*
|
||||
* @return placeholder identifier starting and ending with {@code %}
|
||||
*/
|
||||
@NotNull String getIdentifier();
|
||||
|
||||
/**
|
||||
* Sets placeholder to specified server placeholder implementation.
|
||||
*
|
||||
* @param supplier
|
||||
* Placeholder replacer
|
||||
*/
|
||||
void setServerPlaceholder(@NonNull Supplier<String> supplier);
|
||||
|
||||
/**
|
||||
* Sets placeholder to specified player placeholder implementation.
|
||||
*
|
||||
* @param function
|
||||
* Placeholder replacer
|
||||
*/
|
||||
void setPlayerPlaceholder(@NonNull Function<TabPlayer, String> function);
|
||||
|
||||
/**
|
||||
* Sets placeholder to specified relational placeholder implementation.
|
||||
*
|
||||
* @param function
|
||||
* Placeholder replacer
|
||||
*/
|
||||
void setRelationalPlaceholder(@NonNull BiFunction<TabPlayer, TabPlayer, String> function);
|
||||
}
|
||||
@@ -8,6 +8,8 @@ import org.jetbrains.annotations.NotNull;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* An interface allowing placeholder registration. Instance can be
|
||||
@@ -65,6 +67,14 @@ public interface PlaceholderManager {
|
||||
*/
|
||||
@NotNull RelationalPlaceholder registerRelationalPlaceholder(@NonNull String identifier, int refresh, @NonNull BiFunction<TabPlayer, TabPlayer, String> function);
|
||||
|
||||
void registerServerPlaceholder(@NonNull Pattern identifier, int refresh, @NonNull Function<Matcher, Supplier<String>> function);
|
||||
|
||||
void registerPlayerPlaceholder(@NonNull Pattern identifier, int refresh,
|
||||
@NonNull Function<Matcher, Function<me.neznamy.tab.api.TabPlayer, String>> function);
|
||||
|
||||
void registerRelationalPlaceholder(@NonNull Pattern identifier, int refresh,
|
||||
@NonNull Function<Matcher, BiFunction<me.neznamy.tab.api.TabPlayer, me.neznamy.tab.api.TabPlayer, String>> function);
|
||||
|
||||
/**
|
||||
* Returns placeholder from specified identifier. If it does not exist, it is registered
|
||||
* as a PlaceholderAPI placeholder and returned.
|
||||
|
||||
@@ -84,8 +84,24 @@ public interface Scoreboard {
|
||||
*/
|
||||
void removeLine(int index);
|
||||
|
||||
/**
|
||||
* Replaces all current lines with the provided list.
|
||||
* Supports placeholders, which will automatically be refreshed.
|
||||
* Client can only see up to 15 lines at a time.
|
||||
* If the new list is identical to the current one, nothing happens.
|
||||
*
|
||||
* @param lines
|
||||
* New list of lines to display
|
||||
* @throws NullPointerException
|
||||
* if {@code lines} is {@code null}
|
||||
* @see #getLines()
|
||||
* @see #addLine(String)
|
||||
* @see #removeLine(int)
|
||||
*/
|
||||
void setLines(@NonNull List<String> lines);
|
||||
|
||||
/**
|
||||
* Unregisters this scoreboard from all players who can see it.
|
||||
*/
|
||||
void unregister();
|
||||
}
|
||||
}
|
||||
@@ -65,6 +65,20 @@ public interface Layout {
|
||||
*/
|
||||
void addFixedSlot(int slot, @NonNull String text, @NonNull String skin, int ping);
|
||||
|
||||
/**
|
||||
* Adds fixed slot with specified parameters
|
||||
*
|
||||
* @param slot
|
||||
* Slot (1-80)
|
||||
* @param text
|
||||
* Text to display
|
||||
* @param skin
|
||||
* Skin definition like in config
|
||||
* @param ping
|
||||
* Slot's ping, accepts placeholders that evaluate to a number
|
||||
*/
|
||||
void addFixedSlot(int slot, @NonNull String text, @NonNull String skin, @NonNull String ping);
|
||||
|
||||
/**
|
||||
* Adds a player group.
|
||||
*
|
||||
|
||||
@@ -24,6 +24,18 @@ public interface LayoutManager {
|
||||
@NotNull
|
||||
Layout createNewLayout(@NonNull String name);
|
||||
|
||||
/**
|
||||
* Creates new layout with given name and slot count (1.19.3+ only).
|
||||
*
|
||||
* @param name
|
||||
* Unique layout name
|
||||
* @param slotCount
|
||||
* Amount of slots to use for 1.19.3+ players
|
||||
* @return Created layout
|
||||
*/
|
||||
@NotNull
|
||||
Layout createNewLayout(@NonNull String name, int slotCount);
|
||||
|
||||
/**
|
||||
* Returns defined layout from config by name. If no such layout is defined in config,
|
||||
* {@code null} is returned.
|
||||
|
||||
@@ -3,7 +3,7 @@ plugins {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("com.gradleup.shadow:shadow-gradle-plugin:9.2.2")
|
||||
implementation("io.freefair.gradle:lombok-plugin:9.0.0")
|
||||
implementation("dev.architectury.loom:dev.architectury.loom.gradle.plugin:1.13-SNAPSHOT")
|
||||
implementation("com.gradleup.shadow:shadow-gradle-plugin:9.4.1")
|
||||
implementation("io.freefair.gradle:lombok-plugin:9.2.0")
|
||||
implementation("dev.architectury.loom-no-remap:dev.architectury.loom-no-remap.gradle.plugin:1.14-SNAPSHOT")
|
||||
}
|
||||
@@ -13,7 +13,8 @@ tasks {
|
||||
"version" to project.version,
|
||||
"description" to project.description,
|
||||
"website" to rootProject.ext.get("website")!!.toString(),
|
||||
"author" to rootProject.ext.get("author")!!.toString()
|
||||
"author" to rootProject.ext.get("author")!!.toString(),
|
||||
"credits" to rootProject.ext.get("credits")!!.toString()
|
||||
))
|
||||
}
|
||||
javadoc {
|
||||
@@ -31,6 +32,6 @@ tasks {
|
||||
|
||||
java {
|
||||
toolchain {
|
||||
languageVersion.set(JavaLanguageVersion.of(21))
|
||||
languageVersion.set(JavaLanguageVersion.of(25))
|
||||
}
|
||||
}
|
||||
+4
-3
@@ -4,7 +4,7 @@ plugins {
|
||||
|
||||
allprojects {
|
||||
group = "me.neznamy"
|
||||
version = "5.5.0"
|
||||
version = "6.0.2"
|
||||
description = "An all-in-one solution that works"
|
||||
|
||||
ext.set("id", "tab")
|
||||
@@ -56,11 +56,12 @@ val platformPaths = setOf(
|
||||
":bukkit:v1_21_R5",
|
||||
":bukkit:v1_21_R6",
|
||||
":bukkit:v1_21_R7",
|
||||
":bukkit:v26_1",
|
||||
":bungeecord",
|
||||
":velocity",
|
||||
":fabric",
|
||||
":neoforge",
|
||||
":forge"
|
||||
":neoforge"
|
||||
// ":forge"
|
||||
)
|
||||
|
||||
val specialPaths = setOf(
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
package me.neznamy.tab.platforms.paper_1_20_5;
|
||||
package me.neznamy.tab.platforms.bukkit.paper_1_20_5;
|
||||
|
||||
import me.neznamy.tab.shared.chat.TabStyle;
|
||||
import me.neznamy.tab.shared.chat.component.object.TabAtlasSprite;
|
||||
@@ -12,7 +12,7 @@ import org.jetbrains.annotations.NotNull;
|
||||
/**
|
||||
* Component converter using direct mojang-mapped code.
|
||||
*/
|
||||
public class PaperComponentConverter extends ComponentConverter<Component> {
|
||||
public class NMSComponentConverter extends ComponentConverter<Component> {
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
+8
-9
@@ -1,4 +1,4 @@
|
||||
package me.neznamy.tab.platforms.paper_1_21_11;
|
||||
package me.neznamy.tab.platforms.bukkit.paper_1_20_5;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import lombok.Getter;
|
||||
@@ -11,27 +11,26 @@ import me.neznamy.tab.shared.platform.TabListEntryTracker;
|
||||
import org.bukkit.craftbukkit.entity.CraftPlayer;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Implementation provider using direct Mojang-mapped NMS code for version 1.21.11.
|
||||
* Implementation provider using direct Mojang-mapped NMS code for paper 1.20.5 - 1.21.1.
|
||||
*/
|
||||
@Getter
|
||||
public class PaperImplementationProvider implements ImplementationProvider {
|
||||
public class NMSImplementationProvider implements ImplementationProvider {
|
||||
|
||||
@NotNull
|
||||
private final ComponentConverter<?> componentConverter = new PaperComponentConverter();
|
||||
private final ComponentConverter<?> componentConverter = new NMSComponentConverter();
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Scoreboard newScoreboard(@NotNull BukkitTabPlayer player) {
|
||||
return new PaperPacketScoreboard(player);
|
||||
return new NMSPacketScoreboard(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public TabList newTabList(@NotNull BukkitTabPlayer player) {
|
||||
return new PaperPacketTabList(player);
|
||||
return new NMSPacketTabList(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -42,8 +41,8 @@ public class PaperImplementationProvider implements ImplementationProvider {
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public TabListEntryTracker newTabListEntryTracker() {
|
||||
return new PaperTabListEntryTracker();
|
||||
public TabListEntryTracker newTabListEntryTracker(@NotNull Player player) {
|
||||
return new NMSTabListEntryTracker(getChannel(player));
|
||||
}
|
||||
|
||||
@Override
|
||||
+3
-3
@@ -1,4 +1,4 @@
|
||||
package me.neznamy.tab.platforms.paper_1_20_5;
|
||||
package me.neznamy.tab.platforms.bukkit.paper_1_20_5;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
||||
@@ -25,7 +25,7 @@ import java.util.Optional;
|
||||
/**
|
||||
* Scoreboard implementation using direct mojang-mapped code.
|
||||
*/
|
||||
public class PaperPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
|
||||
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();
|
||||
@@ -39,7 +39,7 @@ public class PaperPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
|
||||
* @param player
|
||||
* Player this scoreboard will belong to
|
||||
*/
|
||||
public PaperPacketScoreboard(@NotNull BukkitTabPlayer player) {
|
||||
public NMSPacketScoreboard(@NotNull BukkitTabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
+3
-3
@@ -1,4 +1,4 @@
|
||||
package me.neznamy.tab.platforms.paper_1_20_5;
|
||||
package me.neznamy.tab.platforms.bukkit.paper_1_20_5;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import com.mojang.authlib.properties.Property;
|
||||
@@ -23,7 +23,7 @@ import java.util.*;
|
||||
/**
|
||||
* TabList implementation using direct mojang-mapped code.
|
||||
*/
|
||||
public class PaperPacketTabList extends TrackedTabList<BukkitTabPlayer> {
|
||||
public class NMSPacketTabList extends TrackedTabList<BukkitTabPlayer> {
|
||||
|
||||
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> addPlayer = EnumSet.allOf(ClientboundPlayerInfoUpdatePacket.Action.class);
|
||||
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateDisplayName = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME);
|
||||
@@ -37,7 +37,7 @@ public class PaperPacketTabList extends TrackedTabList<BukkitTabPlayer> {
|
||||
* @param player
|
||||
* Player this tablist will belong to
|
||||
*/
|
||||
public PaperPacketTabList(@NotNull BukkitTabPlayer player) {
|
||||
public NMSPacketTabList(@NotNull BukkitTabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
+8
-3
@@ -1,6 +1,7 @@
|
||||
package me.neznamy.tab.platforms.paper_1_21_11;
|
||||
package me.neznamy.tab.platforms.bukkit.paper_1_20_5;
|
||||
|
||||
import me.neznamy.tab.shared.platform.TabListEntryTracker;
|
||||
import io.netty.channel.Channel;
|
||||
import me.neznamy.tab.shared.platform.NettyTabListEntryTracker;
|
||||
import net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket;
|
||||
import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -10,7 +11,11 @@ import java.util.UUID;
|
||||
/**
|
||||
* Implementation of TabListEntryTracker.
|
||||
*/
|
||||
public class PaperTabListEntryTracker extends TabListEntryTracker {
|
||||
public class NMSTabListEntryTracker extends NettyTabListEntryTracker {
|
||||
|
||||
public NMSTabListEntryTracker(@NotNull Channel channel) {
|
||||
super(channel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketSend(@NotNull Object packet) {
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
package me.neznamy.tab.platforms.bukkit.paper_1_21_11;
|
||||
|
||||
import com.google.common.collect.ImmutableMultimap;
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import com.mojang.authlib.properties.Property;
|
||||
import com.mojang.authlib.properties.PropertyMap;
|
||||
import me.neznamy.tab.platforms.bukkit.provider.ComponentConverter;
|
||||
import me.neznamy.tab.shared.chat.TabStyle;
|
||||
import me.neznamy.tab.shared.chat.component.object.TabAtlasSprite;
|
||||
import me.neznamy.tab.shared.chat.component.object.TabPlayerSprite;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import net.minecraft.network.chat.*;
|
||||
import net.minecraft.network.chat.contents.objects.AtlasSprite;
|
||||
import net.minecraft.network.chat.contents.objects.PlayerSprite;
|
||||
import net.minecraft.resources.Identifier;
|
||||
import net.minecraft.world.item.component.ResolvableProfile;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Component converter using direct mojang-mapped code.
|
||||
*/
|
||||
public class NMSComponentConverter extends ComponentConverter<Component> {
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Component newTextComponent(@NotNull String text) {
|
||||
return Component.literal(text);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Component newTranslatableComponent(@NotNull String key) {
|
||||
return Component.translatable(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Component newKeybindComponent(@NotNull String keybind) {
|
||||
return Component.keybind(keybind);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Component newObjectComponent(@NotNull TabAtlasSprite sprite) {
|
||||
return Component.object(new AtlasSprite(Identifier.parse(sprite.getAtlas()), Identifier.parse(sprite.getSprite())));
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Component newObjectComponent(@NotNull TabPlayerSprite sprite) {
|
||||
ResolvableProfile profile;
|
||||
if (sprite.getId() != null) {
|
||||
profile = ResolvableProfile.createUnresolved(sprite.getId());
|
||||
} else if (sprite.getName() != null) {
|
||||
profile = ResolvableProfile.createUnresolved(sprite.getName());
|
||||
} else if (sprite.getSkin() != null) {
|
||||
ImmutableMultimap.Builder<String, Property> builder = ImmutableMultimap.builder();
|
||||
builder.put(TabList.TEXTURES_PROPERTY, new Property(TabList.TEXTURES_PROPERTY, sprite.getSkin().getValue(), sprite.getSkin().getSignature()));
|
||||
profile = ResolvableProfile.createResolved(new GameProfile(NIL_UUID, "", new PropertyMap(builder.build())));
|
||||
} else {
|
||||
throw new IllegalStateException("Player head component does not have id, name or skin set");
|
||||
}
|
||||
return Component.object(new PlayerSprite(profile, sprite.isShowHat()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyStyle(@NotNull Component nmsComponent, @NotNull TabStyle modifier) {
|
||||
Style style = Style.EMPTY
|
||||
.withColor(modifier.getColor() == null ? null : TextColor.fromRgb(modifier.getColor().getRgb()))
|
||||
.withBold(modifier.getBold())
|
||||
.withItalic(modifier.getItalic())
|
||||
.withUnderlined(modifier.getUnderlined())
|
||||
.withStrikethrough(modifier.getStrikethrough())
|
||||
.withObfuscated(modifier.getObfuscated())
|
||||
.withFont(modifier.getFont() == null ? null : new FontDescription.Resource(Identifier.parse(modifier.getFont())));
|
||||
if (modifier.getShadowColor() != null) style = style.withShadowColor(modifier.getShadowColor()); // withShadowColor takes int instead of Integer, bug?
|
||||
((MutableComponent)nmsComponent).setStyle(style);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addSibling(@NotNull Component parent, @NotNull Component child) {
|
||||
((MutableComponent)parent).append(child);
|
||||
}
|
||||
}
|
||||
+6
-6
@@ -1,4 +1,4 @@
|
||||
package me.neznamy.tab.platforms.bukkit.v1_18_R1;
|
||||
package me.neznamy.tab.platforms.bukkit.paper_1_21_11;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import lombok.Getter;
|
||||
@@ -8,12 +8,12 @@ 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_18_R1.entity.CraftPlayer;
|
||||
import org.bukkit.craftbukkit.entity.CraftPlayer;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Implementation provider using direct NMS code for 1.18 - 1.18.1.
|
||||
* Implementation provider using direct Mojang-mapped NMS code for paper 1.21.11.
|
||||
*/
|
||||
@Getter
|
||||
public class NMSImplementationProvider implements ImplementationProvider {
|
||||
@@ -36,13 +36,13 @@ public class NMSImplementationProvider implements ImplementationProvider {
|
||||
@Override
|
||||
@NotNull
|
||||
public Channel getChannel(@NotNull Player player) {
|
||||
return ((CraftPlayer)player).getHandle().b.a.k;
|
||||
return ((CraftPlayer)player).getHandle().connection.connection.channel;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public TabListEntryTracker newTabListEntryTracker() {
|
||||
return new NMSTabListEntryTracker();
|
||||
public TabListEntryTracker newTabListEntryTracker(@NotNull Player player) {
|
||||
return new NMSTabListEntryTracker(getChannel(player));
|
||||
}
|
||||
|
||||
@Override
|
||||
+176
@@ -0,0 +1,176 @@
|
||||
package me.neznamy.tab.platforms.bukkit.paper_1_21_11;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.platform.decorators.SafeScoreboard;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.network.chat.numbers.FixedFormat;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.network.protocol.game.*;
|
||||
import net.minecraft.world.scores.PlayerTeam;
|
||||
import net.minecraft.world.scores.Scoreboard;
|
||||
import net.minecraft.world.scores.criteria.ObjectiveCriteria;
|
||||
import net.minecraft.world.scores.criteria.ObjectiveCriteria.RenderType;
|
||||
import org.bukkit.craftbukkit.entity.CraftPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Scoreboard implementation using direct mojang-mapped code.
|
||||
*/
|
||||
public class 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();
|
||||
private static final Field players = ReflectionUtils.getOnlyField(ClientboundSetPlayerTeamPacket.class, Collection.class);
|
||||
|
||||
/**
|
||||
* Constructs new instance with given player.
|
||||
*
|
||||
* @param player
|
||||
* Player this scoreboard will belong to
|
||||
*/
|
||||
public NMSPacketScoreboard(@NotNull BukkitTabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerObjective(@NonNull Objective objective) {
|
||||
net.minecraft.world.scores.Objective obj = new net.minecraft.world.scores.Objective(
|
||||
dummyScoreboard,
|
||||
objective.getName(),
|
||||
ObjectiveCriteria.DUMMY,
|
||||
objective.getTitle().convert(),
|
||||
RenderType.values()[objective.getHealthDisplay().ordinal()],
|
||||
false,
|
||||
objective.getNumberFormat() == null ? null : objective.getNumberFormat().toFixedFormat(FixedFormat::new)
|
||||
);
|
||||
objective.setPlatformObjective(obj);
|
||||
sendPacket(new ClientboundSetObjectivePacket(obj, ObjectiveAction.REGISTER));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDisplaySlot(@NonNull Objective objective) {
|
||||
sendPacket(new ClientboundSetDisplayObjectivePacket(
|
||||
net.minecraft.world.scores.DisplaySlot.values()[objective.getDisplaySlot().ordinal()],
|
||||
(net.minecraft.world.scores.Objective) objective.getPlatformObjective()
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterObjective(@NonNull Objective objective) {
|
||||
sendPacket(new ClientboundSetObjectivePacket((net.minecraft.world.scores.Objective) objective.getPlatformObjective(), ObjectiveAction.UNREGISTER));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateObjective(@NonNull Objective objective) {
|
||||
net.minecraft.world.scores.Objective obj = (net.minecraft.world.scores.Objective) objective.getPlatformObjective();
|
||||
obj.setDisplayName(objective.getTitle().convert());
|
||||
obj.setRenderType(RenderType.values()[objective.getHealthDisplay().ordinal()]);
|
||||
sendPacket(new ClientboundSetObjectivePacket(obj, ObjectiveAction.UPDATE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setScore(@NonNull Score score) {
|
||||
sendPacket(new ClientboundSetScorePacket(
|
||||
score.getHolder(),
|
||||
score.getObjective().getName(),
|
||||
score.getValue(),
|
||||
Optional.ofNullable(score.getDisplayName() == null ? null : score.getDisplayName().convert()),
|
||||
Optional.ofNullable(score.getNumberFormat() == null ? null : score.getNumberFormat().toFixedFormat(FixedFormat::new))
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeScore(@NonNull Score score) {
|
||||
sendPacket(new ClientboundResetScorePacket(score.getHolder(), score.getObjective().getName()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Object createTeam(@NonNull String name) {
|
||||
return new PlayerTeam(dummyScoreboard, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerTeam(@NonNull Team team) {
|
||||
updateTeamProperties(team);
|
||||
PlayerTeam t = (PlayerTeam) team.getPlatformTeam();
|
||||
t.getPlayers().addAll(team.getPlayers());
|
||||
sendPacket(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(t, true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterTeam(@NonNull Team team) {
|
||||
sendPacket(ClientboundSetPlayerTeamPacket.createRemovePacket((PlayerTeam) team.getPlatformTeam()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateTeam(@NonNull Team team) {
|
||||
updateTeamProperties(team);
|
||||
sendPacket(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket((PlayerTeam) team.getPlatformTeam(), false));
|
||||
}
|
||||
|
||||
private void updateTeamProperties(@NonNull Team team) {
|
||||
PlayerTeam t = (PlayerTeam) team.getPlatformTeam();
|
||||
t.setAllowFriendlyFire((team.getOptions() & 0x01) != 0);
|
||||
t.setSeeFriendlyInvisibles((team.getOptions() & 0x02) != 0);
|
||||
t.setColor(formats[team.getColor().ordinal()]);
|
||||
t.setCollisionRule(collisions[team.getCollision().ordinal()]);
|
||||
t.setNameTagVisibility(visibilities[team.getVisibility().ordinal()]);
|
||||
t.setPlayerPrefix(team.getPrefix().convert());
|
||||
t.setPlayerSuffix(team.getSuffix().convert());
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
@NotNull
|
||||
public Object onPacketSend(@NonNull Object packet) {
|
||||
if (packet instanceof ClientboundSetDisplayObjectivePacket display) {
|
||||
TAB.getInstance().getFeatureManager().onDisplayObjective(player, display.getSlot().ordinal(), display.getObjectiveName());
|
||||
}
|
||||
if (packet instanceof ClientboundSetObjectivePacket objective) {
|
||||
TAB.getInstance().getFeatureManager().onObjective(player, objective.getMethod(), objective.getObjectiveName());
|
||||
}
|
||||
if (packet instanceof ClientboundSetPlayerTeamPacket team) {
|
||||
int action = getMethod(team);
|
||||
if (action != TeamAction.UPDATE) {
|
||||
players.set(team, onTeamPacket(action, team.getName(), team.getPlayers() == null ? Collections.emptyList() : team.getPlayers()));
|
||||
}
|
||||
}
|
||||
return packet;
|
||||
}
|
||||
|
||||
private int getMethod(@NonNull ClientboundSetPlayerTeamPacket team) {
|
||||
if (team.getTeamAction() == ClientboundSetPlayerTeamPacket.Action.ADD) {
|
||||
return 0;
|
||||
} else if (team.getTeamAction() == ClientboundSetPlayerTeamPacket.Action.REMOVE) {
|
||||
return 1;
|
||||
} else if (team.getPlayerAction() == ClientboundSetPlayerTeamPacket.Action.ADD) {
|
||||
return 3;
|
||||
} else if (team.getPlayerAction() == ClientboundSetPlayerTeamPacket.Action.REMOVE) {
|
||||
return 4;
|
||||
} else {
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the packet to the player.
|
||||
*
|
||||
* @param packet
|
||||
* Packet to send
|
||||
*/
|
||||
private void sendPacket(@NotNull Packet<?> packet) {
|
||||
((CraftPlayer)player.getPlayer()).getHandle().connection.send(packet);
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -1,4 +1,4 @@
|
||||
package me.neznamy.tab.platforms.paper_1_21_11;
|
||||
package me.neznamy.tab.platforms.bukkit.paper_1_21_11;
|
||||
|
||||
import com.google.common.collect.ImmutableMultimap;
|
||||
import com.mojang.authlib.GameProfile;
|
||||
@@ -24,7 +24,7 @@ import java.util.*;
|
||||
/**
|
||||
* TabList implementation using direct mojang-mapped code.
|
||||
*/
|
||||
public class PaperPacketTabList extends TrackedTabList<BukkitTabPlayer> {
|
||||
public class NMSPacketTabList extends TrackedTabList<BukkitTabPlayer> {
|
||||
|
||||
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> addPlayer = EnumSet.allOf(ClientboundPlayerInfoUpdatePacket.Action.class);
|
||||
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateDisplayName = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME);
|
||||
@@ -40,7 +40,7 @@ public class PaperPacketTabList extends TrackedTabList<BukkitTabPlayer> {
|
||||
* @param player
|
||||
* Player this tablist will belong to
|
||||
*/
|
||||
public PaperPacketTabList(@NotNull BukkitTabPlayer player) {
|
||||
public NMSPacketTabList(@NotNull BukkitTabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
package me.neznamy.tab.platforms.bukkit.paper_1_21_11;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import me.neznamy.tab.shared.platform.NettyTabListEntryTracker;
|
||||
import net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket;
|
||||
import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Implementation of TabListEntryTracker.
|
||||
*/
|
||||
public class NMSTabListEntryTracker extends NettyTabListEntryTracker {
|
||||
|
||||
public NMSTabListEntryTracker(@NotNull Channel channel) {
|
||||
super(channel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketSend(@NotNull Object packet) {
|
||||
if (packet instanceof ClientboundPlayerInfoRemovePacket remove) {
|
||||
for (UUID id : remove.profileIds()) {
|
||||
tablistEntries.remove(id);
|
||||
}
|
||||
}
|
||||
if (packet instanceof ClientboundPlayerInfoUpdatePacket update) {
|
||||
if (update.actions().contains(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER)) {
|
||||
for (ClientboundPlayerInfoUpdatePacket.Entry nmsData : update.entries()) {
|
||||
tablistEntries.add(nmsData.profileId());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
package me.neznamy.tab.platforms.paper_1_21_2;
|
||||
package me.neznamy.tab.platforms.bukkit.paper_1_21_2;
|
||||
|
||||
import me.neznamy.tab.shared.chat.TabStyle;
|
||||
import me.neznamy.tab.shared.chat.component.object.TabAtlasSprite;
|
||||
@@ -15,7 +15,7 @@ import org.jetbrains.annotations.NotNull;
|
||||
/**
|
||||
* Component converter using direct mojang-mapped code.
|
||||
*/
|
||||
public class PaperComponentConverter extends ComponentConverter<Component> {
|
||||
public class NMSComponentConverter extends ComponentConverter<Component> {
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
+8
-9
@@ -1,4 +1,4 @@
|
||||
package me.neznamy.tab.platforms.paper_1_21_4;
|
||||
package me.neznamy.tab.platforms.bukkit.paper_1_21_2;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import lombok.Getter;
|
||||
@@ -11,27 +11,26 @@ import me.neznamy.tab.shared.platform.TabListEntryTracker;
|
||||
import org.bukkit.craftbukkit.entity.CraftPlayer;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Implementation provider using direct Mojang-mapped NMS code for versions 1.21.4 - 1.21.8.
|
||||
* Implementation provider using direct Mojang-mapped NMS code for paper 1.21.2 - 1.21.3.
|
||||
*/
|
||||
@Getter
|
||||
public class PaperImplementationProvider implements ImplementationProvider {
|
||||
public class NMSImplementationProvider implements ImplementationProvider {
|
||||
|
||||
@NotNull
|
||||
private final ComponentConverter<?> componentConverter = new PaperComponentConverter();
|
||||
private final ComponentConverter<?> componentConverter = new NMSComponentConverter();
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Scoreboard newScoreboard(@NotNull BukkitTabPlayer player) {
|
||||
return new PaperPacketScoreboard(player);
|
||||
return new NMSPacketScoreboard(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public TabList newTabList(@NotNull BukkitTabPlayer player) {
|
||||
return new PaperPacketTabList(player);
|
||||
return new NMSPacketTabList(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -42,8 +41,8 @@ public class PaperImplementationProvider implements ImplementationProvider {
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public TabListEntryTracker newTabListEntryTracker() {
|
||||
return new PaperTabListEntryTracker();
|
||||
public TabListEntryTracker newTabListEntryTracker(@NotNull Player player) {
|
||||
return new NMSTabListEntryTracker(getChannel(player));
|
||||
}
|
||||
|
||||
@Override
|
||||
+3
-3
@@ -1,4 +1,4 @@
|
||||
package me.neznamy.tab.platforms.paper_1_21_2;
|
||||
package me.neznamy.tab.platforms.bukkit.paper_1_21_2;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
||||
@@ -25,7 +25,7 @@ import java.util.Optional;
|
||||
/**
|
||||
* Scoreboard implementation using direct mojang-mapped code.
|
||||
*/
|
||||
public class PaperPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
|
||||
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();
|
||||
@@ -39,7 +39,7 @@ public class PaperPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
|
||||
* @param player
|
||||
* Player this scoreboard will belong to
|
||||
*/
|
||||
public PaperPacketScoreboard(@NotNull BukkitTabPlayer player) {
|
||||
public NMSPacketScoreboard(@NotNull BukkitTabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
+3
-3
@@ -1,4 +1,4 @@
|
||||
package me.neznamy.tab.platforms.paper_1_21_2;
|
||||
package me.neznamy.tab.platforms.bukkit.paper_1_21_2;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import com.mojang.authlib.properties.Property;
|
||||
@@ -23,7 +23,7 @@ import java.util.*;
|
||||
/**
|
||||
* TabList implementation using direct mojang-mapped code.
|
||||
*/
|
||||
public class PaperPacketTabList extends TrackedTabList<BukkitTabPlayer> {
|
||||
public class NMSPacketTabList extends TrackedTabList<BukkitTabPlayer> {
|
||||
|
||||
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> addPlayer = EnumSet.allOf(ClientboundPlayerInfoUpdatePacket.Action.class);
|
||||
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateDisplayName = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME);
|
||||
@@ -38,7 +38,7 @@ public class PaperPacketTabList extends TrackedTabList<BukkitTabPlayer> {
|
||||
* @param player
|
||||
* Player this tablist will belong to
|
||||
*/
|
||||
public PaperPacketTabList(@NotNull BukkitTabPlayer player) {
|
||||
public NMSPacketTabList(@NotNull BukkitTabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
+8
-3
@@ -1,6 +1,7 @@
|
||||
package me.neznamy.tab.platforms.paper_1_21_4;
|
||||
package me.neznamy.tab.platforms.bukkit.paper_1_21_2;
|
||||
|
||||
import me.neznamy.tab.shared.platform.TabListEntryTracker;
|
||||
import io.netty.channel.Channel;
|
||||
import me.neznamy.tab.shared.platform.NettyTabListEntryTracker;
|
||||
import net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket;
|
||||
import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -10,7 +11,11 @@ import java.util.UUID;
|
||||
/**
|
||||
* Implementation of TabListEntryTracker.
|
||||
*/
|
||||
public class PaperTabListEntryTracker extends TabListEntryTracker {
|
||||
public class NMSTabListEntryTracker extends NettyTabListEntryTracker {
|
||||
|
||||
public NMSTabListEntryTracker(@NotNull Channel channel) {
|
||||
super(channel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketSend(@NotNull Object packet) {
|
||||
-53
@@ -1,53 +0,0 @@
|
||||
package me.neznamy.tab.platforms.paper_1_21_2;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import lombok.Getter;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.provider.ComponentConverter;
|
||||
import me.neznamy.tab.platforms.bukkit.provider.ImplementationProvider;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import me.neznamy.tab.shared.platform.TabListEntryTracker;
|
||||
import org.bukkit.craftbukkit.entity.CraftPlayer;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Implementation provider using direct Mojang-mapped NMS code for versions 1.21.2 - 1.21.3.
|
||||
*/
|
||||
@Getter
|
||||
public class PaperImplementationProvider implements ImplementationProvider {
|
||||
|
||||
@NotNull
|
||||
private final ComponentConverter<?> componentConverter = new PaperComponentConverter();
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Scoreboard newScoreboard(@NotNull BukkitTabPlayer player) {
|
||||
return new PaperPacketScoreboard(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public TabList newTabList(@NotNull BukkitTabPlayer player) {
|
||||
return new PaperPacketTabList(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Channel getChannel(@NotNull Player player) {
|
||||
return ((CraftPlayer)player).getHandle().connection.connection.channel;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public TabListEntryTracker newTabListEntryTracker() {
|
||||
return new PaperTabListEntryTracker();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPing(@NotNull BukkitTabPlayer player) {
|
||||
return player.getPlayer().getPing();
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
package me.neznamy.tab.platforms.paper_1_21_4;
|
||||
package me.neznamy.tab.platforms.bukkit.paper_1_21_4;
|
||||
|
||||
import me.neznamy.tab.shared.chat.TabStyle;
|
||||
import me.neznamy.tab.shared.chat.component.object.TabAtlasSprite;
|
||||
@@ -12,7 +12,7 @@ import org.jetbrains.annotations.NotNull;
|
||||
/**
|
||||
* Component converter using direct mojang-mapped code.
|
||||
*/
|
||||
public class PaperComponentConverter extends ComponentConverter<Component> {
|
||||
public class NMSComponentConverter extends ComponentConverter<Component> {
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
+8
-9
@@ -1,4 +1,4 @@
|
||||
package me.neznamy.tab.platforms.paper_1_20_5;
|
||||
package me.neznamy.tab.platforms.bukkit.paper_1_21_4;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import lombok.Getter;
|
||||
@@ -11,27 +11,26 @@ import me.neznamy.tab.shared.platform.TabListEntryTracker;
|
||||
import org.bukkit.craftbukkit.entity.CraftPlayer;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Implementation provider using direct Mojang-mapped NMS code for versions 1.20.5 - 1.21.1.
|
||||
* Implementation provider using direct Mojang-mapped NMS code for paper 1.21.4 - 1.21.8.
|
||||
*/
|
||||
@Getter
|
||||
public class PaperImplementationProvider implements ImplementationProvider {
|
||||
public class NMSImplementationProvider implements ImplementationProvider {
|
||||
|
||||
@NotNull
|
||||
private final ComponentConverter<?> componentConverter = new PaperComponentConverter();
|
||||
private final ComponentConverter<?> componentConverter = new NMSComponentConverter();
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Scoreboard newScoreboard(@NotNull BukkitTabPlayer player) {
|
||||
return new PaperPacketScoreboard(player);
|
||||
return new NMSPacketScoreboard(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public TabList newTabList(@NotNull BukkitTabPlayer player) {
|
||||
return new PaperPacketTabList(player);
|
||||
return new NMSPacketTabList(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -42,8 +41,8 @@ public class PaperImplementationProvider implements ImplementationProvider {
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public TabListEntryTracker newTabListEntryTracker() {
|
||||
return new PaperTabListEntryTracker();
|
||||
public TabListEntryTracker newTabListEntryTracker(@NotNull Player player) {
|
||||
return new NMSTabListEntryTracker(getChannel(player));
|
||||
}
|
||||
|
||||
@Override
|
||||
+3
-3
@@ -1,4 +1,4 @@
|
||||
package me.neznamy.tab.platforms.paper_1_21_11;
|
||||
package me.neznamy.tab.platforms.bukkit.paper_1_21_4;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
||||
@@ -25,7 +25,7 @@ import java.util.Optional;
|
||||
/**
|
||||
* Scoreboard implementation using direct mojang-mapped code.
|
||||
*/
|
||||
public class PaperPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
|
||||
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();
|
||||
@@ -39,7 +39,7 @@ public class PaperPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
|
||||
* @param player
|
||||
* Player this scoreboard will belong to
|
||||
*/
|
||||
public PaperPacketScoreboard(@NotNull BukkitTabPlayer player) {
|
||||
public NMSPacketScoreboard(@NotNull BukkitTabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
+3
-3
@@ -1,4 +1,4 @@
|
||||
package me.neznamy.tab.platforms.paper_1_21_4;
|
||||
package me.neznamy.tab.platforms.bukkit.paper_1_21_4;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import com.mojang.authlib.properties.Property;
|
||||
@@ -23,7 +23,7 @@ import java.util.*;
|
||||
/**
|
||||
* TabList implementation using direct mojang-mapped code.
|
||||
*/
|
||||
public class PaperPacketTabList extends TrackedTabList<BukkitTabPlayer> {
|
||||
public class NMSPacketTabList extends TrackedTabList<BukkitTabPlayer> {
|
||||
|
||||
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> addPlayer = EnumSet.allOf(ClientboundPlayerInfoUpdatePacket.Action.class);
|
||||
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateDisplayName = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME);
|
||||
@@ -39,7 +39,7 @@ public class PaperPacketTabList extends TrackedTabList<BukkitTabPlayer> {
|
||||
* @param player
|
||||
* Player this tablist will belong to
|
||||
*/
|
||||
public PaperPacketTabList(@NotNull BukkitTabPlayer player) {
|
||||
public NMSPacketTabList(@NotNull BukkitTabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
+8
-3
@@ -1,6 +1,7 @@
|
||||
package me.neznamy.tab.platforms.paper_1_21_2;
|
||||
package me.neznamy.tab.platforms.bukkit.paper_1_21_4;
|
||||
|
||||
import me.neznamy.tab.shared.platform.TabListEntryTracker;
|
||||
import io.netty.channel.Channel;
|
||||
import me.neznamy.tab.shared.platform.NettyTabListEntryTracker;
|
||||
import net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket;
|
||||
import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -10,7 +11,11 @@ import java.util.UUID;
|
||||
/**
|
||||
* Implementation of TabListEntryTracker.
|
||||
*/
|
||||
public class PaperTabListEntryTracker extends TabListEntryTracker {
|
||||
public class NMSTabListEntryTracker extends NettyTabListEntryTracker {
|
||||
|
||||
public NMSTabListEntryTracker(@NotNull Channel channel) {
|
||||
super(channel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketSend(@NotNull Object packet) {
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
package me.neznamy.tab.platforms.paper_1_21_9;
|
||||
package me.neznamy.tab.platforms.bukkit.paper_1_21_9;
|
||||
|
||||
import com.google.common.collect.ImmutableMultimap;
|
||||
import com.mojang.authlib.GameProfile;
|
||||
@@ -19,7 +19,7 @@ import org.jetbrains.annotations.NotNull;
|
||||
/**
|
||||
* Component converter using direct mojang-mapped code.
|
||||
*/
|
||||
public class PaperComponentConverter extends ComponentConverter<Component> {
|
||||
public class NMSComponentConverter extends ComponentConverter<Component> {
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
package me.neznamy.tab.platforms.bukkit.paper_1_21_9;
|
||||
|
||||
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.entity.CraftPlayer;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Implementation provider using direct Mojang-mapped NMS code for paper 1.21.9 - 1.21.10.
|
||||
*/
|
||||
@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().connection.connection.channel;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public TabListEntryTracker newTabListEntryTracker(@NotNull Player player) {
|
||||
return new NMSTabListEntryTracker(getChannel(player));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPing(@NotNull BukkitTabPlayer player) {
|
||||
return player.getPlayer().getPing();
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -1,4 +1,4 @@
|
||||
package me.neznamy.tab.platforms.paper_1_21_9;
|
||||
package me.neznamy.tab.platforms.bukkit.paper_1_21_9;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
||||
@@ -25,7 +25,7 @@ import java.util.Optional;
|
||||
/**
|
||||
* Scoreboard implementation using direct mojang-mapped code.
|
||||
*/
|
||||
public class PaperPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
|
||||
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();
|
||||
@@ -39,7 +39,7 @@ public class PaperPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
|
||||
* @param player
|
||||
* Player this scoreboard will belong to
|
||||
*/
|
||||
public PaperPacketScoreboard(@NotNull BukkitTabPlayer player) {
|
||||
public NMSPacketScoreboard(@NotNull BukkitTabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
+3
-3
@@ -1,4 +1,4 @@
|
||||
package me.neznamy.tab.platforms.paper_1_21_9;
|
||||
package me.neznamy.tab.platforms.bukkit.paper_1_21_9;
|
||||
|
||||
import com.google.common.collect.ImmutableMultimap;
|
||||
import com.mojang.authlib.GameProfile;
|
||||
@@ -25,7 +25,7 @@ import java.util.*;
|
||||
/**
|
||||
* TabList implementation using direct mojang-mapped code.
|
||||
*/
|
||||
public class PaperPacketTabList extends TrackedTabList<BukkitTabPlayer> {
|
||||
public class NMSPacketTabList extends TrackedTabList<BukkitTabPlayer> {
|
||||
|
||||
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> addPlayer = EnumSet.allOf(ClientboundPlayerInfoUpdatePacket.Action.class);
|
||||
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateDisplayName = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME);
|
||||
@@ -41,7 +41,7 @@ public class PaperPacketTabList extends TrackedTabList<BukkitTabPlayer> {
|
||||
* @param player
|
||||
* Player this tablist will belong to
|
||||
*/
|
||||
public PaperPacketTabList(@NotNull BukkitTabPlayer player) {
|
||||
public NMSPacketTabList(@NotNull BukkitTabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
package me.neznamy.tab.platforms.bukkit.paper_1_21_9;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import me.neznamy.tab.shared.platform.NettyTabListEntryTracker;
|
||||
import net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket;
|
||||
import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Implementation of TabListEntryTracker.
|
||||
*/
|
||||
public class NMSTabListEntryTracker extends NettyTabListEntryTracker {
|
||||
|
||||
public NMSTabListEntryTracker(@NotNull Channel channel) {
|
||||
super(channel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketSend(@NotNull Object packet) {
|
||||
if (packet instanceof ClientboundPlayerInfoRemovePacket remove) {
|
||||
for (UUID id : remove.profileIds()) {
|
||||
tablistEntries.remove(id);
|
||||
}
|
||||
}
|
||||
if (packet instanceof ClientboundPlayerInfoUpdatePacket update) {
|
||||
if (update.actions().contains(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER)) {
|
||||
for (ClientboundPlayerInfoUpdatePacket.Entry nmsData : update.entries()) {
|
||||
tablistEntries.add(nmsData.profileId());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-53
@@ -1,53 +0,0 @@
|
||||
package me.neznamy.tab.platforms.paper_1_21_9;
|
||||
|
||||
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.entity.CraftPlayer;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Implementation provider using direct Mojang-mapped NMS code for versions 1.21.9 - 1.21.10.
|
||||
*/
|
||||
@Getter
|
||||
public class PaperImplementationProvider implements ImplementationProvider {
|
||||
|
||||
@NotNull
|
||||
private final ComponentConverter<?> componentConverter = new PaperComponentConverter();
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Scoreboard newScoreboard(@NotNull BukkitTabPlayer player) {
|
||||
return new PaperPacketScoreboard(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public TabList newTabList(@NotNull BukkitTabPlayer player) {
|
||||
return new PaperPacketTabList(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Channel getChannel(@NotNull Player player) {
|
||||
return ((CraftPlayer)player).getHandle().connection.connection.channel;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public TabListEntryTracker newTabListEntryTracker() {
|
||||
return new PaperTabListEntryTracker();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPing(@NotNull BukkitTabPlayer player) {
|
||||
return player.getPlayer().getPing();
|
||||
}
|
||||
}
|
||||
-30
@@ -1,30 +0,0 @@
|
||||
package me.neznamy.tab.platforms.paper_1_21_9;
|
||||
|
||||
import me.neznamy.tab.shared.platform.TabListEntryTracker;
|
||||
import net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket;
|
||||
import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Implementation of TabListEntryTracker.
|
||||
*/
|
||||
public class PaperTabListEntryTracker extends TabListEntryTracker {
|
||||
|
||||
@Override
|
||||
public void onPacketSend(@NotNull Object packet) {
|
||||
if (packet instanceof ClientboundPlayerInfoRemovePacket remove) {
|
||||
for (UUID id : remove.profileIds()) {
|
||||
tablistEntries.remove(id);
|
||||
}
|
||||
}
|
||||
if (packet instanceof ClientboundPlayerInfoUpdatePacket update) {
|
||||
if (update.actions().contains(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER)) {
|
||||
for (ClientboundPlayerInfoUpdatePacket.Entry nmsData : update.entries()) {
|
||||
tablistEntries.add(nmsData.profileId());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -44,8 +44,7 @@ public class BukkitEventListener implements EventListener<Player>, Listener {
|
||||
public void onJoin(PlayerJoinEvent e) {
|
||||
TAB.getInstance().addTablistTracker(
|
||||
e.getPlayer().getUniqueId(),
|
||||
platform.getImplementationProvider().getChannel(e.getPlayer()),
|
||||
platform.getImplementationProvider().newTabListEntryTracker()
|
||||
platform.getServerVersionInfo().getImplementationProvider().newTabListEntryTracker(e.getPlayer())
|
||||
);
|
||||
join(e.getPlayer());
|
||||
}
|
||||
|
||||
@@ -21,6 +21,6 @@ public class BukkitPipelineInjector extends NettyPipelineInjector {
|
||||
@Override
|
||||
@NotNull
|
||||
protected Channel getChannel(@NotNull TabPlayer player) {
|
||||
return ((BukkitPlatform)player.getPlatform()).getImplementationProvider().getChannel(((BukkitTabPlayer) player).getPlayer());
|
||||
return ((BukkitPlatform)player.getPlatform()).getServerVersionInfo().getImplementationProvider().getChannel(((BukkitTabPlayer) player).getPlayer());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,10 +17,13 @@ public class BukkitTAB extends JavaPlugin {
|
||||
boolean folia = ReflectionUtils.classExists("io.papermc.paper.threadedregions.RegionizedServer");
|
||||
try {
|
||||
TAB.create(folia ? new FoliaPlatform(this) : new BukkitPlatform(this));
|
||||
} catch (UnsupportedOperationException e) {
|
||||
} catch (IllegalStateException e) {
|
||||
Bukkit.getConsoleSender().sendMessage("§c[TAB] ================================================================================");
|
||||
Bukkit.getConsoleSender().sendMessage("§c[TAB] Your server version (" + Bukkit.getBukkitVersion().split("-")[0] + ") is not supported. " +
|
||||
"This jar only supports 1.7 - 1.21.11. If you just updated to a new Minecraft version, check for TAB updates.");
|
||||
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] If you just updated to a new Minecraft version, check for TAB updates.");
|
||||
Bukkit.getConsoleSender().sendMessage("§c[TAB] If you are using an unsupported 1.x version, use an older version of TAB (latest TAB 5.x supports all MC 1.x versions).");
|
||||
Bukkit.getConsoleSender().sendMessage("§c[TAB] Thrown error message: " + e.getMessage());
|
||||
Bukkit.getConsoleSender().sendMessage("§c[TAB] ================================================================================");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ public class BukkitTabPlayer extends BackendTabPlayer {
|
||||
* bukkit player
|
||||
*/
|
||||
public BukkitTabPlayer(@NotNull BukkitPlatform platform, @NotNull Player p) {
|
||||
super(platform, p, p.getUniqueId(), p.getName(), p.getWorld().getName(), platform.getServerVersion().getNetworkId());
|
||||
super(platform, p, p.getUniqueId(), p.getName(), p.getWorld().getName(), platform.getServerVersionInfo().getServerVersion().getNetworkId());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -35,7 +35,7 @@ public class BukkitTabPlayer extends BackendTabPlayer {
|
||||
|
||||
@Override
|
||||
public int getPing() {
|
||||
return getPlatform().getImplementationProvider().getPing(this);
|
||||
return getPlatform().getServerVersionInfo().getImplementationProvider().getPing(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
package me.neznamy.tab.platforms.bukkit;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import me.neznamy.tab.platforms.bukkit.provider.ImplementationProvider;
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@Getter
|
||||
public class ServerVersionInfo {
|
||||
|
||||
/** Package name of the server implementation, null on Paper 1.20.5+ / Spigot 26+ */
|
||||
@Nullable
|
||||
private final String serverPackage;
|
||||
|
||||
/** Minecraft version as returned by the platform */
|
||||
@NotNull
|
||||
private final String minecraftVersion;
|
||||
|
||||
@NotNull
|
||||
private final String serverName;
|
||||
|
||||
/** Server version */
|
||||
private final ProtocolVersion serverVersion;
|
||||
|
||||
/** Implementation for creating new instances using content available on the server */
|
||||
@NotNull
|
||||
@Setter
|
||||
private ImplementationProvider implementationProvider;
|
||||
|
||||
/**
|
||||
* Constructs new instance and detects server version info.
|
||||
*/
|
||||
public ServerVersionInfo() {
|
||||
// Server package
|
||||
String CRAFTBUKKIT_PACKAGE = Bukkit.getServer().getClass().getPackage().getName();
|
||||
String[] array = CRAFTBUKKIT_PACKAGE.split("\\.");
|
||||
serverPackage = array.length > 3 ? array[3] : null;
|
||||
|
||||
// Minecraft version
|
||||
if (ReflectionUtils.methodExists(Bukkit.class, "getMinecraftVersion")) {
|
||||
serverName = "Paper";
|
||||
minecraftVersion = Bukkit.getMinecraftVersion();
|
||||
} else {
|
||||
serverName = "Spigot";
|
||||
minecraftVersion = Bukkit.getBukkitVersion().split("-")[0];
|
||||
}
|
||||
|
||||
// Minecraft version to server version
|
||||
serverVersion = ProtocolVersion.fromFriendlyName(minecraftVersion);
|
||||
|
||||
// Find implementation
|
||||
implementationProvider = findImplementationProvider();
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds implementation provider for current server software and version.
|
||||
*
|
||||
* @return Implementation provider for current server
|
||||
* @throws IllegalStateException
|
||||
* If no implementation was found
|
||||
*/
|
||||
@NotNull
|
||||
private ImplementationProvider findImplementationProvider() {
|
||||
if (serverPackage != null) {
|
||||
// Paper <1.20.5 or Spigot 1.x
|
||||
try {
|
||||
// Does not actually support flat 1.19, but whatever, no one is using it anyway
|
||||
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.",
|
||||
minecraftVersion, serverPackage
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// Paper 1.20.5+ or Spigot 26+
|
||||
Map<ProtocolVersion, String> spigotVersions = new LinkedHashMap<>();
|
||||
spigotVersions.put(ProtocolVersion.V26_1, "v26_1");
|
||||
spigotVersions.put(ProtocolVersion.V26_1_1, "v26_1");
|
||||
spigotVersions.put(ProtocolVersion.V26_1_2, "v26_1");
|
||||
|
||||
Map<ProtocolVersion, String> paperVersions = new LinkedHashMap<>();
|
||||
paperVersions.put(ProtocolVersion.V1_20_5, "paper_1_20_5");
|
||||
paperVersions.put(ProtocolVersion.V1_20_6, "paper_1_20_5");
|
||||
paperVersions.put(ProtocolVersion.V1_21, "paper_1_20_5");
|
||||
paperVersions.put(ProtocolVersion.V1_21_1, "paper_1_20_5");
|
||||
paperVersions.put(ProtocolVersion.V1_21_2, "paper_1_21_2");
|
||||
paperVersions.put(ProtocolVersion.V1_21_3, "paper_1_21_2");
|
||||
paperVersions.put(ProtocolVersion.V1_21_4, "paper_1_21_4");
|
||||
paperVersions.put(ProtocolVersion.V1_21_5, "paper_1_21_4");
|
||||
paperVersions.put(ProtocolVersion.V1_21_6, "paper_1_21_4");
|
||||
paperVersions.put(ProtocolVersion.V1_21_7, "paper_1_21_4");
|
||||
paperVersions.put(ProtocolVersion.V1_21_8, "paper_1_21_4");
|
||||
paperVersions.put(ProtocolVersion.V1_21_9, "paper_1_21_9");
|
||||
paperVersions.put(ProtocolVersion.V1_21_10, "paper_1_21_9");
|
||||
paperVersions.put(ProtocolVersion.V1_21_11, "paper_1_21_11");
|
||||
paperVersions.put(ProtocolVersion.V26_1, "paper_1_21_11"); // v26_1 works too
|
||||
paperVersions.put(ProtocolVersion.V26_1_1, "paper_1_21_11"); // v26_1 works too
|
||||
paperVersions.put(ProtocolVersion.V26_1_2, "paper_1_21_11"); // v26_1 works too
|
||||
|
||||
if (serverVersion == ProtocolVersion.UNKNOWN) {
|
||||
throw new IllegalStateException(String.format(
|
||||
"Unknown server version (%s), can not 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.",
|
||||
serverName, minecraftVersion
|
||||
));
|
||||
}
|
||||
try {
|
||||
return (ImplementationProvider) Class.forName("me.neznamy.tab.platforms.bukkit." + implementation + ".NMSImplementationProvider").getConstructor().newInstance();
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new IllegalStateException(String.format(
|
||||
"Your server version (%s %s) is marked as compatible, but the implementation does not exist. This is probably a bug.",
|
||||
serverName, minecraftVersion
|
||||
), e);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
throw new IllegalStateException(String.format(
|
||||
"Failed to initialize implementation for %s %s. This is probably a bug.",
|
||||
serverName, minecraftVersion
|
||||
), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,8 @@ import org.bukkit.Bukkit;
|
||||
import org.bukkit.boss.BossBar;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* BossBar for 1.9+ servers where Bukkit API is used. If ViaVersion is used
|
||||
* to allow 1.8 players, it will handle the entity and teleporting by itself.
|
||||
@@ -32,7 +34,7 @@ public class BukkitBossBar extends SafeBossBar<BossBar> {
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public BossBar constructBossBar(@NotNull TabComponent title, float progress, @NotNull BarColor color, @NotNull BarStyle style) {
|
||||
public BossBar constructBossBar(@NotNull UUID id, @NotNull TabComponent title, float progress, @NotNull BarColor color, @NotNull BarStyle style) {
|
||||
BossBar bar = Bukkit.createBossBar(
|
||||
player.getPlatform().toBukkitFormat(title),
|
||||
org.bukkit.boss.BarColor.valueOf(color.name()),
|
||||
@@ -43,7 +45,7 @@ public class BukkitBossBar extends SafeBossBar<BossBar> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void create(SafeBossBar<BossBar>.@NotNull BossBarInfo bar) {
|
||||
public void show(SafeBossBar<BossBar>.@NotNull BossBarInfo bar) {
|
||||
bar.getBossBar().addPlayer(player.getPlayer());
|
||||
}
|
||||
|
||||
@@ -68,7 +70,7 @@ public class BukkitBossBar extends SafeBossBar<BossBar> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(SafeBossBar<BossBar>.@NotNull BossBarInfo bar) {
|
||||
public void hide(SafeBossBar<BossBar>.@NotNull BossBarInfo bar) {
|
||||
bar.getBossBar().removePlayer(player.getPlayer());
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,8 @@ import me.neznamy.tab.shared.chat.component.TabComponent;
|
||||
import me.neznamy.tab.shared.platform.decorators.SafeBossBar;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Handler for 1.9+ players on 1.8 server using ViaVersion API.
|
||||
*/
|
||||
@@ -27,7 +29,7 @@ public class ViaBossBar extends SafeBossBar<BossBar> {
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public BossBar constructBossBar(@NotNull TabComponent title, float progress, @NotNull BarColor color, @NotNull BarStyle style) {
|
||||
public BossBar constructBossBar(@NotNull UUID id, @NotNull TabComponent title, float progress, @NotNull BarColor color, @NotNull BarStyle style) {
|
||||
return Via.getAPI().legacyAPI().createLegacyBossBar(
|
||||
title.toLegacyText(),
|
||||
progress,
|
||||
@@ -37,7 +39,7 @@ public class ViaBossBar extends SafeBossBar<BossBar> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void create(@NotNull BossBarInfo bar) {
|
||||
public void show(@NotNull BossBarInfo bar) {
|
||||
bar.getBossBar().addPlayer(player.getPlayer().getUniqueId());
|
||||
}
|
||||
|
||||
@@ -62,7 +64,7 @@ public class ViaBossBar extends SafeBossBar<BossBar> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(@NotNull BossBarInfo bar) {
|
||||
public void hide(@NotNull BossBarInfo bar) {
|
||||
bar.getBossBar().removePlayer(player.getPlayer().getUniqueId());
|
||||
}
|
||||
}
|
||||
+3
-13
@@ -98,9 +98,9 @@ public class BukkitTabExpansion extends PlaceholderExpansion implements TabExpan
|
||||
String requestedPlaceholder = "%" + identifier.substring("placeholder_".length()) + "%";
|
||||
PlaceholderManagerImpl pm = TAB.getInstance().getPlaceholderManager();
|
||||
pm.addUsedPlaceholder(requestedPlaceholder, pm);
|
||||
return pm.getPlaceholder(requestedPlaceholder).set(requestedPlaceholder, p);
|
||||
return pm.getPlaceholder(requestedPlaceholder).parse(p);
|
||||
}
|
||||
return p.expansionValues.get(identifier);
|
||||
return p.expansionData.getValue(identifier);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -132,17 +132,7 @@ public class BukkitTabExpansion extends PlaceholderExpansion implements TabExpan
|
||||
return "<Not a relational placeholder: " + requestedPlaceholder + ">";
|
||||
}
|
||||
}
|
||||
return "<Unknown identifier: " + identifier + ">";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPlaceholderValue(@NotNull TabPlayer player, @NotNull String placeholder, @NotNull String value) {
|
||||
// Do not unnecessarily do all placeholders, just retrieve them on request to save resources by not using string builder all the time
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setValue(@NotNull TabPlayer player, @NotNull String key, @NotNull String value) {
|
||||
player.expansionValues.put(key, value);
|
||||
return "<Unknown identifier: \"" + identifier + "\">";
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+44
-6
@@ -7,6 +7,9 @@ import me.neznamy.tab.shared.features.PerWorldPlayerListConfiguration;
|
||||
import me.neznamy.tab.shared.features.types.Loadable;
|
||||
import me.neznamy.tab.shared.features.types.TabFeature;
|
||||
import me.neznamy.tab.shared.features.types.UnLoadable;
|
||||
import me.neznamy.tab.shared.features.types.VanishListener;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
@@ -14,6 +17,8 @@ import org.bukkit.event.HandlerList;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerChangedWorldEvent;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.metadata.MetadataValue;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@@ -24,7 +29,7 @@ import java.util.Map.Entry;
|
||||
* Per-world-PlayerList feature handler
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
public class PerWorldPlayerList extends TabFeature implements Listener, Loadable, UnLoadable {
|
||||
public class PerWorldPlayerList extends TabFeature implements Listener, Loadable, UnLoadable, VanishListener {
|
||||
|
||||
/** Reference to platform */
|
||||
@NotNull
|
||||
@@ -34,6 +39,9 @@ public class PerWorldPlayerList extends TabFeature implements Listener, Loadable
|
||||
@NotNull
|
||||
private final PerWorldPlayerListConfiguration configuration;
|
||||
|
||||
/** Check for presence of modern (1.12.2+) methods that take plugin as argument to avoid conflict */
|
||||
private final boolean modernMethodsAvailable = ReflectionUtils.methodExists(Player.class, "hidePlayer", Plugin.class, Player.class);
|
||||
|
||||
/**
|
||||
* Constructs new instance and registers events.
|
||||
*
|
||||
@@ -61,7 +69,7 @@ public class PerWorldPlayerList extends TabFeature implements Listener, Loadable
|
||||
public void unload() {
|
||||
for (Player p : platform.getOnlinePlayers()) {
|
||||
for (Player pl : platform.getOnlinePlayers()) {
|
||||
p.showPlayer(pl);
|
||||
showPlayer(p, pl);
|
||||
}
|
||||
}
|
||||
HandlerList.unregisterAll(this);
|
||||
@@ -104,10 +112,32 @@ public class PerWorldPlayerList extends TabFeature implements Listener, Loadable
|
||||
private void checkPlayer(@NotNull Player p) {
|
||||
for (Player all : platform.getOnlinePlayers()) {
|
||||
if (all == p) continue;
|
||||
if (!shouldSee(p, all) && p.canSee(all)) p.hidePlayer(all);
|
||||
if (shouldSee(p, all) && !p.canSee(all)) p.showPlayer(all);
|
||||
if (!shouldSee(all, p) && all.canSee(p)) all.hidePlayer(p);
|
||||
if (shouldSee(all, p) && !all.canSee(p)) all.showPlayer(p);
|
||||
if (shouldSee(p, all)) {
|
||||
showPlayer(p, all);
|
||||
} else {
|
||||
hidePlayer(p, all);
|
||||
}
|
||||
if (shouldSee(all, p)) {
|
||||
showPlayer(all, p);
|
||||
} else {
|
||||
hidePlayer(all, p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void hidePlayer(@NotNull Player viewer, @NotNull Player target) {
|
||||
if (modernMethodsAvailable) {
|
||||
viewer.hidePlayer(platform.getPlugin(), target);
|
||||
} else {
|
||||
viewer.hidePlayer(target);
|
||||
}
|
||||
}
|
||||
|
||||
private void showPlayer(@NotNull Player viewer, @NotNull Player target) {
|
||||
if (modernMethodsAvailable) {
|
||||
viewer.showPlayer(platform.getPlugin(), target);
|
||||
} else {
|
||||
viewer.showPlayer(target);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,6 +151,9 @@ public class PerWorldPlayerList extends TabFeature implements Listener, Loadable
|
||||
*/
|
||||
private boolean shouldSee(@NotNull Player viewer, @NotNull Player target) {
|
||||
if (target == viewer) return true;
|
||||
for (MetadataValue v : target.getMetadata("vanished")) {
|
||||
if (v.asBoolean() && !viewer.hasPermission(TabConstants.Permission.SEE_VANISHED)) return false;
|
||||
}
|
||||
if ((configuration.isAllowBypassPermission() && viewer.hasPermission(TabConstants.Permission.PER_WORLD_PLAYERLIST_BYPASS)) || configuration.getIgnoredWorlds().contains(viewer.getWorld().getName())) return true;
|
||||
String viewerWorldGroup = viewer.getWorld().getName() + "-default"; //preventing unwanted behavior when some group is called exactly like a world
|
||||
String targetWorldGroup = target.getWorld().getName() + "-default";
|
||||
@@ -138,4 +171,9 @@ public class PerWorldPlayerList extends TabFeature implements Listener, Loadable
|
||||
public String getFeatureName() {
|
||||
return "Per world PlayerList";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVanishStatusChange(@NotNull TabPlayer player) {
|
||||
platform.runSync((Player) player.getPlayer(), () -> checkPlayer((Player) player.getPlayer()));
|
||||
}
|
||||
}
|
||||
+55
-105
@@ -1,23 +1,17 @@
|
||||
package me.neznamy.tab.platforms.bukkit.platform;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.SneakyThrows;
|
||||
import me.clip.placeholderapi.PlaceholderAPI;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitEventListener;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitPipelineInjector;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabCommand;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.clip.placeholderapi.PlaceholderAPIPlugin;
|
||||
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
|
||||
import me.neznamy.tab.platforms.bukkit.*;
|
||||
import me.neznamy.tab.platforms.bukkit.bossbar.BukkitBossBar;
|
||||
import me.neznamy.tab.platforms.bukkit.bossbar.ViaBossBar;
|
||||
import me.neznamy.tab.platforms.bukkit.features.BukkitTabExpansion;
|
||||
import me.neznamy.tab.platforms.bukkit.features.PerWorldPlayerList;
|
||||
import me.neznamy.tab.platforms.bukkit.hook.BukkitPremiumVanishHook;
|
||||
import me.neznamy.tab.platforms.bukkit.provider.ImplementationProvider;
|
||||
import me.neznamy.tab.shared.GroupManager;
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.*;
|
||||
import me.neznamy.tab.shared.backend.BackendPlatform;
|
||||
import me.neznamy.tab.shared.chat.TabTextColor;
|
||||
import me.neznamy.tab.shared.chat.component.TabComponent;
|
||||
@@ -53,6 +47,7 @@ import org.bukkit.command.SimpleCommandMap;
|
||||
import org.bukkit.command.defaults.BukkitCommand;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.plugin.RegisteredServiceProvider;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -72,8 +67,9 @@ public class BukkitPlatform implements BackendPlatform {
|
||||
@NotNull
|
||||
private final JavaPlugin plugin;
|
||||
|
||||
/** Server version */
|
||||
private final ProtocolVersion serverVersion = ProtocolVersion.fromFriendlyName(Bukkit.getBukkitVersion().split("-")[0]);
|
||||
/** Information about server version */
|
||||
@NotNull
|
||||
private final ServerVersionInfo serverVersionInfo = new ServerVersionInfo();
|
||||
|
||||
/** Variables checking presence of other plugins to hook into */
|
||||
private final boolean placeholderAPI = ReflectionUtils.classExists("me.clip.placeholderapi.PlaceholderAPI");
|
||||
@@ -87,15 +83,6 @@ public class BukkitPlatform implements BackendPlatform {
|
||||
/** Detection for presence of Paper's MSPT getter */
|
||||
private final boolean paperMspt = ReflectionUtils.methodExists(Bukkit.class, "getAverageTickTime");
|
||||
|
||||
/** Package name of the server implementation, null on Paper 1.20.5+ */
|
||||
@Nullable
|
||||
private final String serverPackage;
|
||||
|
||||
/** Implementation for creating new instances using content available on the server */
|
||||
@NotNull
|
||||
@Setter
|
||||
private ImplementationProvider implementationProvider;
|
||||
|
||||
private final boolean modernOnlinePlayers;
|
||||
|
||||
/** Command map for dynamic command registering */
|
||||
@@ -116,10 +103,7 @@ public class BukkitPlatform implements BackendPlatform {
|
||||
public BukkitPlatform(@NotNull JavaPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
modernOnlinePlayers = Bukkit.class.getMethod("getOnlinePlayers").getReturnType() == Collection.class;
|
||||
String CRAFTBUKKIT_PACKAGE = Bukkit.getServer().getClass().getPackage().getName();
|
||||
String[] array = CRAFTBUKKIT_PACKAGE.split("\\.");
|
||||
serverPackage = array.length > 3 ? array[3] : null;
|
||||
implementationProvider = findImplementationProvider();
|
||||
logInfo(new TabTextComponent("Found NMS implementation: " + serverVersionInfo.getImplementationProvider().getClass().getName(), TabTextColor.GRAY));
|
||||
try {
|
||||
Object server = Bukkit.getServer().getClass().getMethod("getServer").invoke(Bukkit.getServer());
|
||||
recentTps = ((double[]) server.getClass().getField("recentTps").get(server));
|
||||
@@ -133,63 +117,6 @@ public class BukkitPlatform implements BackendPlatform {
|
||||
knownCommands = (Map<String, Command>) ReflectionUtils.getField(SimpleCommandMap.class, "knownCommands").get(commandMap);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@SneakyThrows
|
||||
private ImplementationProvider findImplementationProvider() {
|
||||
if (serverPackage == null) {
|
||||
// Paper 1.20.5+, check for available module
|
||||
String paperModule = getPaperModule();
|
||||
if (paperModule != null) {
|
||||
return (ImplementationProvider) Class.forName("me.neznamy.tab.platforms.paper_" + paperModule + ".PaperImplementationProvider").getConstructor().newInstance();
|
||||
} else {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
} else {
|
||||
// Paper <1.20.5 or Spigot
|
||||
try {
|
||||
// Does not actually support flat 1.19, but whatever, no one is using it anyway
|
||||
return (ImplementationProvider) Class.forName("me.neznamy.tab.platforms.bukkit." + serverPackage + ".NMSImplementationProvider").getConstructor().newInstance();
|
||||
} catch (ClassNotFoundException ignored) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns name of the paper module that can be used on this server.
|
||||
* If this server is not using paper or no module is available for any other reason,
|
||||
* {@code null} is returned.
|
||||
*
|
||||
* @return Name of the available paper module or {@code null} if not available
|
||||
*/
|
||||
@Nullable
|
||||
private String getPaperModule() {
|
||||
if (!ReflectionUtils.classExists("org.bukkit.craftbukkit.CraftServer")) return null;
|
||||
switch (serverVersion) {
|
||||
case V1_20_5:
|
||||
case V1_20_6:
|
||||
case V1_21:
|
||||
case V1_21_1:
|
||||
return "1_20_5";
|
||||
case V1_21_2:
|
||||
case V1_21_3:
|
||||
return "1_21_2";
|
||||
case V1_21_4:
|
||||
case V1_21_5:
|
||||
case V1_21_6:
|
||||
case V1_21_7:
|
||||
case V1_21_8:
|
||||
return "1_21_4";
|
||||
case V1_21_9:
|
||||
case V1_21_10:
|
||||
return "1_21_9";
|
||||
case V1_21_11:
|
||||
return "1_21_11";
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadPlayers() {
|
||||
for (Player p : getOnlinePlayers()) {
|
||||
@@ -200,14 +127,14 @@ public class BukkitPlatform implements BackendPlatform {
|
||||
@Override
|
||||
public void registerPlaceholders() {
|
||||
PlaceholderManagerImpl manager = TAB.getInstance().getPlaceholderManager();
|
||||
manager.registerInternalServerPlaceholder("%vault-prefix%", -1, () -> "");
|
||||
manager.registerInternalServerPlaceholder("%vault-suffix%", -1, () -> "");
|
||||
manager.registerServerPlaceholder("%vault-prefix%", -1, () -> "");
|
||||
manager.registerServerPlaceholder("%vault-suffix%", -1, () -> "");
|
||||
if (Bukkit.getPluginManager().isPluginEnabled("Vault")) {
|
||||
RegisteredServiceProvider<Chat> rspChat = Bukkit.getServicesManager().getRegistration(Chat.class);
|
||||
if (rspChat != null) {
|
||||
Chat chat = rspChat.getProvider();
|
||||
manager.registerInternalPlayerPlaceholder("%vault-prefix%", 1000, p -> chat.getPlayerPrefix((Player) p.getPlayer()));
|
||||
manager.registerInternalPlayerPlaceholder("%vault-suffix%", 1000, p -> chat.getPlayerSuffix((Player) p.getPlayer()));
|
||||
manager.registerPlayerPlaceholder("%vault-prefix%", p -> chat.getPlayerPrefix((Player) p.getPlayer()));
|
||||
manager.registerPlayerPlaceholder("%vault-suffix%", p -> chat.getPlayerSuffix((Player) p.getPlayer()));
|
||||
}
|
||||
}
|
||||
BackendPlatform.super.registerPlaceholders();
|
||||
@@ -216,7 +143,8 @@ public class BukkitPlatform implements BackendPlatform {
|
||||
@Override
|
||||
@Nullable
|
||||
public PipelineInjector createPipelineInjector() {
|
||||
return serverVersion.getMinorVersion() >= 8 ? new BukkitPipelineInjector() : null;
|
||||
return serverVersionInfo.getServerVersion().getNetworkId() >= ProtocolVersion.V1_8.getNetworkId()
|
||||
? new BukkitPipelineInjector() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -288,12 +216,6 @@ public class BukkitPlatform implements BackendPlatform {
|
||||
Bukkit.getConsoleSender().sendMessage("§c[TAB] [WARN] " + toBukkitFormat(message));
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String getServerVersionInfo() {
|
||||
return "[Bukkit] " + Bukkit.getName() + " - " + Bukkit.getBukkitVersion().split("-")[0] + " (" + serverPackage + ")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerListener() {
|
||||
Bukkit.getPluginManager().registerEvents(new BukkitEventListener(this), plugin);
|
||||
@@ -316,9 +238,7 @@ 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()));
|
||||
String version = serverVersion == ProtocolVersion.UNKNOWN ? "Unknown" : "1." + serverVersion.getMinorVersion() + ".x";
|
||||
metrics.addCustomChart(new SimplePie(TabConstants.MetricsChart.SERVER_VERSION, () -> version));
|
||||
metrics.addCustomChart(new SimplePie("tab_5_5_0_servers", serverVersion::getFriendlyName));
|
||||
metrics.addCustomChart(new SimplePie("tab_6_0_0_servers", serverVersionInfo.getServerVersion()::getFriendlyName));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -330,13 +250,13 @@ public class BukkitPlatform implements BackendPlatform {
|
||||
@Override
|
||||
@NotNull
|
||||
public Object convertComponent(@NotNull TabComponent component) {
|
||||
return implementationProvider.getComponentConverter().convert(component);
|
||||
return serverVersionInfo.getImplementationProvider().getComponentConverter().convert(component);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Scoreboard createScoreboard(@NotNull TabPlayer player) {
|
||||
return implementationProvider.newScoreboard((BukkitTabPlayer) player);
|
||||
return serverVersionInfo.getImplementationProvider().newScoreboard((BukkitTabPlayer) player);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -349,7 +269,7 @@ public class BukkitPlatform implements BackendPlatform {
|
||||
if (BukkitBossBar.isAvailable()) return new BukkitBossBar((BukkitTabPlayer) player);
|
||||
|
||||
// 1.9+ player on 1.8 server, handle using ViaVersion API
|
||||
if (player.getVersion().getMinorVersion() >= 9) return new ViaBossBar((BukkitTabPlayer) player);
|
||||
if (player.getVersion().getNetworkId() >= ProtocolVersion.V1_9.getNetworkId()) return new ViaBossBar((BukkitTabPlayer) player);
|
||||
|
||||
// 1.8- server and player, no implementation
|
||||
return new DummyBossBar();
|
||||
@@ -358,7 +278,7 @@ public class BukkitPlatform implements BackendPlatform {
|
||||
@Override
|
||||
@NotNull
|
||||
public TabList createTabList(@NotNull TabPlayer player) {
|
||||
return implementationProvider.newTabList((BukkitTabPlayer) player);
|
||||
return serverVersionInfo.getImplementationProvider().newTabList((BukkitTabPlayer) player);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -368,17 +288,17 @@ public class BukkitPlatform implements BackendPlatform {
|
||||
|
||||
@Override
|
||||
public boolean supportsListed() {
|
||||
return serverVersion.getNetworkId() >= ProtocolVersion.V1_19_3.getNetworkId();
|
||||
return serverVersionInfo.getServerVersion().getNetworkId() >= ProtocolVersion.V1_19_3.getNetworkId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsListOrder() {
|
||||
return serverVersion.getNetworkId() >= ProtocolVersion.V1_21_2.getNetworkId();
|
||||
return serverVersionInfo.getServerVersion().getNetworkId() >= ProtocolVersion.V1_21_2.getNetworkId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSafeFromPacketEventsBug() {
|
||||
return serverVersion.getMinorVersion() >= 13;
|
||||
return serverVersionInfo.getServerVersion().getMinorVersion() >= 13;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -410,6 +330,7 @@ public class BukkitPlatform implements BackendPlatform {
|
||||
knownCommands.remove(command.getName() + ":" + command.getName());
|
||||
command.unregister(commandMap);
|
||||
}
|
||||
customCommands.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -468,7 +389,7 @@ public class BukkitPlatform implements BackendPlatform {
|
||||
public String toBukkitFormat(@NotNull TabComponent component) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (component.getModifier().getColor() != null) {
|
||||
if (serverVersion.supportsRGB()) {
|
||||
if (serverVersionInfo.getServerVersion().getNetworkId() >= ProtocolVersion.V1_16.getNetworkId()) {
|
||||
String hexCode = component.getModifier().getColor().getHexCode();
|
||||
sb.append('§').append("x").append('§').append(hexCode.charAt(0)).append('§').append(hexCode.charAt(1))
|
||||
.append('§').append(hexCode.charAt(2)).append('§').append(hexCode.charAt(3))
|
||||
@@ -510,4 +431,33 @@ public class BukkitPlatform implements BackendPlatform {
|
||||
}
|
||||
return Arrays.asList((Player[]) Bukkit.class.getMethod("getOnlinePlayers").invoke(null));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Object dump() {
|
||||
Map<String, Object> map = new LinkedHashMap<>();
|
||||
map.put("server-type", "Bukkit");
|
||||
map.put("server-name", Bukkit.getName());
|
||||
map.put("server-version", serverVersionInfo.getMinecraftVersion());
|
||||
map.put("craftbukkit-package", serverVersionInfo.getServerPackage());
|
||||
map.put("nms-implementation", serverVersionInfo.getImplementationProvider().getClass().getName());
|
||||
map.put("tab-version", ProjectVariables.PLUGIN_VERSION);
|
||||
Map<String, Object> plugins = new LinkedHashMap<>();
|
||||
Plugin[] pluginArray = Bukkit.getPluginManager().getPlugins();
|
||||
Arrays.sort(pluginArray, Comparator.comparing(p -> p.getDescription().getName(), String.CASE_INSENSITIVE_ORDER));
|
||||
for (Plugin p : pluginArray) {
|
||||
plugins.put(p.getDescription().getName(), p.getDescription().getVersion());
|
||||
}
|
||||
map.put("plugins", plugins);
|
||||
if (placeholderAPI) {
|
||||
Map<String, String> expansions = new LinkedHashMap<>();
|
||||
PlaceholderExpansion[] expansionArray = PlaceholderAPIPlugin.getInstance().getLocalExpansionManager().getExpansions().toArray(new PlaceholderExpansion[0]);
|
||||
Arrays.sort(expansionArray, Comparator.comparing(PlaceholderExpansion::getIdentifier, String.CASE_INSENSITIVE_ORDER));
|
||||
for (PlaceholderExpansion p : expansionArray) {
|
||||
expansions.put(p.getIdentifier(), p.getVersion());
|
||||
}
|
||||
map.put("placeholderapi-expansions", expansions);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
}
|
||||
@@ -64,8 +64,8 @@ public class FoliaPlatform extends BukkitPlatform {
|
||||
DecimalFormatSymbols symbols = new DecimalFormatSymbols();
|
||||
symbols.setDecimalSeparator('.');
|
||||
decimal2 = new DecimalFormat("#.##", symbols);
|
||||
registerInternalSyncPlaceholder(TabConstants.Placeholder.MSPT, 1000, p -> decimal2.format(Bukkit.getAverageTickTime()));
|
||||
registerInternalSyncPlaceholder(TabConstants.Placeholder.TPS, 1000, p -> decimal2.format(Math.min(20, Bukkit.getTPS()[0])));
|
||||
registerInternalSyncPlaceholder(TabConstants.Placeholder.MSPT, p -> decimal2.format(Bukkit.getAverageTickTime()));
|
||||
registerInternalSyncPlaceholder(TabConstants.Placeholder.TPS, p -> decimal2.format(Math.min(20, Bukkit.getTPS()[0])));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -85,9 +85,9 @@ public class FoliaPlatform extends BukkitPlatform {
|
||||
});
|
||||
}
|
||||
|
||||
private void registerInternalSyncPlaceholder(@NonNull String identifier, int refresh, @NonNull Function<TabPlayer, String> function) {
|
||||
private void registerInternalSyncPlaceholder(@NonNull String identifier, @NonNull Function<TabPlayer, String> function) {
|
||||
PlayerPlaceholderImpl[] ppl = new PlayerPlaceholderImpl[1];
|
||||
ppl[0] = TAB.getInstance().getPlaceholderManager().registerInternalPlayerPlaceholder(identifier, refresh, p -> {
|
||||
ppl[0] = TAB.getInstance().getPlaceholderManager().registerPlayerPlaceholder(identifier, p -> {
|
||||
runSync((Entity) p.getPlayer(), () -> {
|
||||
long time = System.nanoTime();
|
||||
String output = function.apply((TabPlayer) p);
|
||||
@@ -112,6 +112,18 @@ public class FoliaPlatform extends BukkitPlatform {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overriding the method to fix initial error caused by ServerPlaceholder implementation trying to
|
||||
* retrieve the value in constructor, but folia does not support that. Overriding this function to avoid the
|
||||
* TPS function being called in the wrong thread.
|
||||
*
|
||||
* @return -1
|
||||
*/
|
||||
@Override
|
||||
public double getTPS() {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs task using player's entity scheduler. It's using reflection, because
|
||||
* Folia uses Java 17 while TAB maintains Java 8 compatibility for compatibility
|
||||
|
||||
+3
-1
@@ -55,10 +55,12 @@ public interface ImplementationProvider {
|
||||
/**
|
||||
* Creates a new instance of tab list entry tracker.
|
||||
*
|
||||
* @param player
|
||||
* Player to create tab list entry tracker for
|
||||
* @return Newly created tab list entry tracker
|
||||
*/
|
||||
@NotNull
|
||||
TabListEntryTracker newTabListEntryTracker();
|
||||
TabListEntryTracker newTabListEntryTracker(@NotNull Player player);
|
||||
|
||||
/**
|
||||
* Returns ping of the given player.
|
||||
|
||||
@@ -25,8 +25,8 @@ permissions:
|
||||
tab.cpu:
|
||||
description: Allows player to run /tab cpu
|
||||
default: op
|
||||
tab.debug:
|
||||
description: Allows player to run /tab debug [player]
|
||||
tab.dump:
|
||||
description: Allows player to run /tab dump <player>
|
||||
default: op
|
||||
tab.groupinfo:
|
||||
description: Allows player to run /tab group <group>
|
||||
|
||||
+2
-2
@@ -41,8 +41,8 @@ public class NMSImplementationProvider implements ImplementationProvider {
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public TabListEntryTracker newTabListEntryTracker() {
|
||||
return new NMSTabListEntryTracker();
|
||||
public TabListEntryTracker newTabListEntryTracker(@NotNull Player player) {
|
||||
return new NMSTabListEntryTracker(getChannel(player));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+7
-2
@@ -1,8 +1,9 @@
|
||||
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.TabListEntryTracker;
|
||||
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;
|
||||
@@ -15,13 +16,17 @@ import java.util.List;
|
||||
/**
|
||||
* Implementation of TabListEntryTracker.
|
||||
*/
|
||||
public class NMSTabListEntryTracker extends 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")
|
||||
|
||||
+2
-2
@@ -41,8 +41,8 @@ public class NMSImplementationProvider implements ImplementationProvider {
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public TabListEntryTracker newTabListEntryTracker() {
|
||||
return new NMSTabListEntryTracker();
|
||||
public TabListEntryTracker newTabListEntryTracker(@NotNull Player player) {
|
||||
return new NMSTabListEntryTracker(getChannel(player));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+7
-2
@@ -1,8 +1,9 @@
|
||||
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.TabListEntryTracker;
|
||||
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;
|
||||
@@ -14,13 +15,17 @@ import java.util.List;
|
||||
/**
|
||||
* Implementation of TabListEntryTracker.
|
||||
*/
|
||||
public class NMSTabListEntryTracker extends 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")
|
||||
|
||||
+2
-2
@@ -41,8 +41,8 @@ public class NMSImplementationProvider implements ImplementationProvider {
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public TabListEntryTracker newTabListEntryTracker() {
|
||||
return new NMSTabListEntryTracker();
|
||||
public TabListEntryTracker newTabListEntryTracker(@NotNull Player player) {
|
||||
return new NMSTabListEntryTracker(getChannel(player));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+7
-2
@@ -1,8 +1,9 @@
|
||||
package me.neznamy.tab.platforms.bukkit.v1_12_R1;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import io.netty.channel.Channel;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.shared.platform.TabListEntryTracker;
|
||||
import me.neznamy.tab.shared.platform.NettyTabListEntryTracker;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import net.minecraft.server.v1_12_R1.PacketPlayOutPlayerInfo;
|
||||
import net.minecraft.server.v1_12_R1.PacketPlayOutPlayerInfo.EnumPlayerInfoAction;
|
||||
@@ -14,13 +15,17 @@ import java.util.List;
|
||||
/**
|
||||
* Implementation of TabListEntryTracker.
|
||||
*/
|
||||
public class NMSTabListEntryTracker extends 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")
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
dependencies {
|
||||
implementation(projects.bukkit)
|
||||
compileOnly("org.bukkit:craftbukkit:1.13-R0.1-SNAPSHOT")
|
||||
}
|
||||
-62
@@ -1,62 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.v1_13_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_13_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_13_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_13_R1.entity.CraftPlayer;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Implementation provider using direct NMS code for 1.13.
|
||||
*/
|
||||
@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() {
|
||||
return new NMSTabListEntryTracker();
|
||||
}
|
||||
|
||||
@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_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_13_R1.*;
|
||||
import org.bukkit.craftbukkit.v1_13_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.b,
|
||||
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.a(IScoreboardCriteria.EnumScoreboardHealthDisplay.valueOf(objective.getHealthDisplay().name()));
|
||||
sendPacket(new PacketPlayOutScoreboardObjective(obj, ObjectiveAction.UPDATE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setScore(@NonNull Score score) {
|
||||
sendPacket(new PacketPlayOutScoreboardScore(
|
||||
ScoreboardServer.Action.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_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_13_R1.*;
|
||||
import net.minecraft.server.v1_13_R1.PacketPlayOutPlayerInfo.EnumPlayerInfoAction;
|
||||
import org.bukkit.craftbukkit.v1_13_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.a = header.convert();
|
||||
packet.b = 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.a != header.convert() || tablist.b != 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);
|
||||
}
|
||||
}
|
||||
-42
@@ -1,42 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.v1_13_R1;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.shared.platform.TabListEntryTracker;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import net.minecraft.server.v1_13_R1.PacketPlayOutPlayerInfo;
|
||||
import net.minecraft.server.v1_13_R1.PacketPlayOutPlayerInfo.EnumPlayerInfoAction;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Implementation of TabListEntryTracker.
|
||||
*/
|
||||
public class NMSTabListEntryTracker extends TabListEntryTracker {
|
||||
|
||||
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);
|
||||
|
||||
@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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -41,8 +41,8 @@ public class NMSImplementationProvider implements ImplementationProvider {
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public TabListEntryTracker newTabListEntryTracker() {
|
||||
return new NMSTabListEntryTracker();
|
||||
public TabListEntryTracker newTabListEntryTracker(@NotNull Player player) {
|
||||
return new NMSTabListEntryTracker(getChannel(player));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+7
-2
@@ -1,8 +1,9 @@
|
||||
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.TabListEntryTracker;
|
||||
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;
|
||||
@@ -14,13 +15,17 @@ import java.util.List;
|
||||
/**
|
||||
* Implementation of TabListEntryTracker.
|
||||
*/
|
||||
public class NMSTabListEntryTracker extends 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")
|
||||
|
||||
+2
-2
@@ -41,8 +41,8 @@ public class NMSImplementationProvider implements ImplementationProvider {
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public TabListEntryTracker newTabListEntryTracker() {
|
||||
return new NMSTabListEntryTracker();
|
||||
public TabListEntryTracker newTabListEntryTracker(@NotNull Player player) {
|
||||
return new NMSTabListEntryTracker(getChannel(player));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+7
-2
@@ -1,8 +1,9 @@
|
||||
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.TabListEntryTracker;
|
||||
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;
|
||||
@@ -14,13 +15,17 @@ import java.util.List;
|
||||
/**
|
||||
* Implementation of TabListEntryTracker.
|
||||
*/
|
||||
public class NMSTabListEntryTracker extends 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")
|
||||
|
||||
+2
-2
@@ -41,8 +41,8 @@ public class NMSImplementationProvider implements ImplementationProvider {
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public TabListEntryTracker newTabListEntryTracker() {
|
||||
return new NMSTabListEntryTracker();
|
||||
public TabListEntryTracker newTabListEntryTracker(@NotNull Player player) {
|
||||
return new NMSTabListEntryTracker(getChannel(player));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+7
-2
@@ -1,8 +1,9 @@
|
||||
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.TabListEntryTracker;
|
||||
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;
|
||||
@@ -14,13 +15,17 @@ import java.util.List;
|
||||
/**
|
||||
* Implementation of TabListEntryTracker.
|
||||
*/
|
||||
public class NMSTabListEntryTracker extends 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")
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
dependencies {
|
||||
implementation(projects.bukkit)
|
||||
compileOnly("org.bukkit:craftbukkit:1.16.1-R0.1-SNAPSHOT")
|
||||
}
|
||||
-62
@@ -1,62 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.v1_16_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_16_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) {
|
||||
((IChatMutableComponent)nmsComponent).setChatModifier(ChatModifier.b
|
||||
.setColor(modifier.getColor() == null ? null : ChatHexColor.a(modifier.getColor().getRgb()))
|
||||
.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) {
|
||||
((IChatMutableComponent)parent).addSibling(child);
|
||||
}
|
||||
}
|
||||
-52
@@ -1,52 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.v1_16_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_16_R1.entity.CraftPlayer;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Implementation provider using direct NMS code for 1.16 - 1.16.1.
|
||||
*/
|
||||
@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() {
|
||||
return new NMSTabListEntryTracker();
|
||||
}
|
||||
|
||||
@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_16_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_16_R1.*;
|
||||
import org.bukkit.craftbukkit.v1_16_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);
|
||||
}
|
||||
}
|
||||
Loaded 100 of 349 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user