Compare commits

...
68 Commits
Author SHA1 Message Date
NEZNAMY 6e6848e007 6.1.2 2026-08-09 07:58:23 +02:00
NEZNAMY fc83f0fec6 [Bukkit] Catch everything to fix error on 1.8.8 and lower (#1706) 2026-08-09 07:47:31 +02:00
NEZNAMY 30593c1bfd [Bukkit] Attempt to reduce false bug reports by 75% (#1701 #1702 #1704) 2026-08-08 09:42:39 +02:00
NEZNAMY 89e69591e0 6.1.2-SNAPSHOT 2026-08-08 09:20:26 +02:00
NEZNAMY 147279a8ca [Description] Update badges 2026-08-04 17:48:22 +02:00
NEZNAMY 517ac4403f 6.1.1 2026-08-04 17:27:08 +02:00
NEZNAMY b4e88e570f [Layout] Fix conflict with Bedrock UUID pattern (#1688) 2026-08-03 21:04:32 +02:00
NEZNAMY 9b5385ed61 [Dump command] Add scoreboard (objectives and teams) information to dump output 2026-08-02 13:13:52 +02:00
NEZNAMY 284e3e91f3 [Belowname] Implement view-distance (26.1+) (not working on proxy yet) (#1672) 2026-08-01 16:21:26 +02:00
NEZNAMY 0fb6519617 [Wiki] Monthly wiki backup for changes made in July 2026 2026-08-01 09:46:36 +02:00
NEZNAMY e2452f2780 Merge pull request #1698 from SenseiJu/fix/nametag-registeredteams-leak
[Nametags] Use weak keys for registered team maps to fix disconnected player leak
2026-07-29 20:30:47 +02:00
SenseiJu f1c763861b [Scoreboards] Prevent scoreboard updates for disconnected players 2026-07-29 19:29:45 +01:00
SenseiJu ea99736142 [Nametags] Clear registered teams on player disconnect to prevent memory leaks 2026-07-29 03:58:39 +01:00
NEZNAMY 21d661bd22 [Components] Append dummy char to MiniPlaceholders and then remove it to avoid dropping trailing colors (#1696) 2026-07-23 19:31:27 +02:00
NEZNAMY fc0666fe46 [Commands] Change parse command to use string to component converter (with MM support) instead of just TAB's own converter (#1696) 2026-07-23 17:16:09 +02:00
SenseiJuandClaude Opus 4.8 869c6191b6 [Nametags] Use weak keys for registered team maps to fix disconnected player leak
registeredTeams and registeredProxyTeams held strong references to their
team owners, so a disconnected player that was not explicitly unregistered
(NameTag#unregisterTeam only iterates online viewers) or that got
re-registered by a refresh firing after quit stayed as a map key forever.
Every viewer keeps such a map keyed by every target, so a single missed
removal compounds to O(players^2) retained objects.

Switch both maps to WeakHashMap, matching the sibling per-player maps in
this class (opaqueNameTagViewers, nameTagInvisibilityReasonsRelational).
Online owners stay strongly reachable via TAB#data so their entries are
retained as before; once an owner disconnects and is dereferenced
elsewhere, the weak key lets it be collected and the stale entry is dropped
automatically. Values are Strings that never reference the key, and the maps
are only used via put/remove/containsKey on the feature's single thread.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-22 21:21:18 +01:00
NEZNAMY a53f41336d [Commands] Disable placeholder check in parse command as the command does more than that (<> placeholders, fonts, heads, MM, ...) (#1696) 2026-07-22 18:17:29 +02:00
NEZNAMY ff4e82d2cc [Placeholders] Fix placeholder detection after MP pull request to preserve order and duplicates (#1696) 2026-07-22 18:12:58 +02:00
NEZNAMY 2de7e3bd7e Merge pull request #1695 from MC20018/codex/align-fand-dependency-packaging
[Fand] Align dependency packaging with universal jar
2026-07-19 19:01:23 +02:00
MC20018 c72e0c5108 [Fand] Align dependency packaging with universal jar
Remove the Fand-only dependency exclusions from the shared platform shadow configuration so the Fand intermediate jar embeds and relocates Redis, Delivery4j, RabbitMQ, Commons Pool, and JSON consistently with the other platform modules.

Delete the Fand libraries.json manifest because the distributed universal jar already includes these dependencies through its merged platform shadow jars. This avoids redundant runtime downloads and keeps Fand on the same packaged dependency path as the artifact users install.
2026-07-20 00:55:08 +08:00
NEZNAMY 11814e0fd2 Merge pull request #1693 from MC20018/codex/update-fand-api-0.8.3
[Fand] Update API dependency to 0.8.3
2026-07-19 07:22:16 +02:00
MC20018 d41d2e74bd [Fand] Update API dependency to 0.8.3
Compile the Fand platform against the latest published API release to keep the integration validated against current server contracts.
2026-07-19 01:50:17 +08:00
NEZNAMY 122b54caee [Components] Update head syntax to match MiniMessage (including TAB's own tag resolvers) 2026-07-12 15:00:59 +02:00
NEZNAMY 3a67785be6 Merge pull request #1690 from MC20018/master
[Fand] Add platform support
2026-07-12 13:26:49 +02:00
NEZNAMY 01e2457611 [Nametags] Create new messages for opaque view after the PR 2026-07-12 11:49:43 +02:00
MC20018 dd13d244c0 Fix Fand platform edge cases 2026-07-12 15:06:45 +08:00
MC20018 fba8983f46 Complete Fand platform integration 2026-07-12 14:12:22 +08:00
MC20018 4a2af1f37a [Fand] Add platform support 2026-07-11 21:25:37 +08:00
NEZNAMY 2b82c45176 Merge pull request #1686 from Nattapat2871/feature/nametag-opaque-view
Add opaque nametag visibility mode
2026-07-10 17:21:39 +02:00
NEZNAMY 3203c64287 [Bukkit] Add update check for Canvas fork due to their refactoring 2026-07-08 21:03:58 +02:00
NEZNAMY 1683e1faf6 [Placeholders] Add coloring after placeholders are parsed, allowing "&" in one element and color code in another 2026-07-08 19:52:41 +02:00
nattapat2871 eb9de8cc3f Add opaque nametag visibility mode 2026-07-07 11:56:36 +07:00
NEZNAMY df6a8281a6 [Wiki] Monthly wiki backup for changes made in June 2026 2026-07-01 18:13:29 +02:00
NEZNAMY 05568296d9 Merge pull request #1677 from Crafting-tunbol/master
Add MiniPlaceholders support for Velocity
2026-06-30 21:04:04 +02:00
Crafting_tunbol c024b28a45 Removed unused methods 2026-06-28 17:13:26 +02:00
Crafting_tunbol 0b0c457cdc Move placeholder identifier handling to PlaceholderConfiguration 2026-06-28 16:30:52 +02:00
Crafting_tunbol 001e26cbb0 Remove unnecessary null check 2026-06-28 16:07:06 +02:00
Crafting_tunbol 3d75520863 Remove missleading minimessage-support config usage for MiniPlaceHolder parsing issues 2026-06-27 17:23:05 +02:00
Crafting_tunbol 63bc73fd98 Fix typo for TAB Bridge 2026-06-27 16:15:30 +02:00
Crafting_tunbol b47e7d2d3b Fix wiki docs for MiniPlaceholders usage on Velocity 2026-06-27 16:11:26 +02:00
Crafting_tunbol cf7b0eed28 Add wiki docs and warning for MiniPlaceholders usage on Velocity 2026-06-27 14:36:39 +02:00
Crafting_tunbol b3b0652222 [Velocity] Use MiniPlaceholders <syntax> for MP placeholders and preserve RGB colors
fix: detect MiniPlaceholders <tag> syntax in placeholder scanning

fix(velocity): support MiniPlaceholders <syntax> alongside %bridge% placeholders

fix: use existing color support for MiniPlaceholder
2026-06-26 18:39:14 +02:00
NEZNAMY 8e78e4e9ce [Fabric] Fix RGB syntax when reading placeholders 2026-06-25 20:01:05 +02:00
Crafting_tunbol f9d60a224a [velocity] Add MiniPlaceholders API support
add missing changes

wip: clean minor issues
2026-06-24 22:21:11 +02:00
NEZNAMY 396678d996 Merge pull request #1675 from JT122406/master
Readd Forge Support
2026-06-24 16:14:51 +02:00
Joseph T. McQuigg fae2d7a532 Readd Forge Support 2026-06-23 22:53:43 -04:00
NEZNAMY 73ab71d7c0 [Dump command] Include MiniMessage information (#1674) 2026-06-23 18:21:13 +02:00
NEZNAMY ec6eb51515 [MiniMessage hook] Add another custom tag for <mineskin:UUID> 2026-06-21 13:00:34 +02:00
NEZNAMY 02a9f25148 6.1.1-SNAPSHOT 2026-06-21 13:00:24 +02:00
NEZNAMY ea4c16a162 Remove unused import 2026-06-21 12:17:40 +02:00
NEZNAMY c7e8c891e2 [Description] Update badges 2026-06-18 18:13:58 +02:00
NEZNAMY 651580078c 6.1.0 2026-06-18 17:50:55 +02:00
NEZNAMY 2c3a7a2256 Fix jar name 2026-06-18 17:50:41 +02:00
NEZNAMY 47b3579fde [Components] Fix last color detection not working for magic codes after using &r 2026-06-18 17:17:08 +02:00
NEZNAMY 8147fd4edf [BungeeCord] Fix magic codes not being sent to <26.2 2026-06-17 20:06:16 +02:00
NEZNAMY 4c6fd3282b [Build] Change how jar for Paper 1.20.5 - 1.21.4 is made and named 2026-06-17 19:09:12 +02:00
NEZNAMY 363f5c7e5e [Bukkit] Add 26.2 support 2026-06-17 18:35:06 +02:00
NEZNAMY 229f7d665b [Fabric / NeoForge] Update to 26.2 2026-06-17 17:52:16 +02:00
NEZNAMY ff2dd5d1d5 [Bukkit] Update error message 2026-06-17 16:58:17 +02:00
NEZNAMY 37f1763d9c [ProxySupport] Implement disable-condition forwarding (#1635) 2026-06-14 17:43:43 +02:00
NEZNAMY 962adc683d [BossBar / Scoreboard] Pass arguments of toggle command to the main command to allow silent toggling and more 2026-06-07 11:05:15 +02:00
NEZNAMY 6f4f6b71ff [Fabric] Update to 26.2-pre4 2026-06-06 14:51:05 +02:00
NEZNAMY 6576c53235 [Bukkit] Avoid modifying final fields in player info packet on 1.17 - 1.19.2 (#1648) 2026-06-06 11:32:20 +02:00
NEZNAMY 3d939c2a39 [Bukkit] Avoid modifying final fields in team packet on 1.17+ (#1648) 2026-06-06 11:07:14 +02:00
NEZNAMY 9564e2cdf8 [Bukkit] Avoid modifying final fields in player info packet on 1.8 - 1.16.5 (#1648) 2026-06-06 09:51:35 +02:00
NEZNAMY 2ed7cb794e [Bukkit] Avoid modifying final fields in team packet on 1.8 - 1.16.5 (#1648) 2026-06-06 09:38:06 +02:00
NEZNAMY f2d4a6bee7 [Per world playerlist] Support "*" wildcard and regex in world names just like in global playerlist 2026-06-06 08:09:39 +02:00
NEZNAMY 63be8824f6 [GitHub] Update badges 2026-06-02 18:07:24 +02:00
192 changed files with 5118 additions and 534 deletions

No files matched your search

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="126" height="20" role="img" aria-label="Documentation: Wiki"><title>Documentation: Wiki</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="126" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="93" height="20" fill="#555"/><rect x="93" width="33" height="20" fill="#dfb317"/><rect width="126" 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="475" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="830">Documentation</text><text x="475" y="140" transform="scale(.1)" fill="#fff" textLength="830">Documentation</text><text aria-hidden="true" x="1085" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="230">Wiki</text><text x="1085" y="140" transform="scale(.1)" fill="#fff" textLength="230">Wiki</text></g></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="126" height="20" role="img" aria-label="Documentation: Wiki"><title>Documentation: Wiki</title><filter id="blur"><feGaussianBlur stdDeviation="16"/></filter><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="126" height="20" rx="3"/></clipPath><g clip-path="url(#r)"><rect width="93" height="20" fill="#555"/><rect x="93" width="33" height="20" fill="#d8b800"/><rect width="126" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><g transform="scale(.1)"><g aria-hidden="true" fill="#010101"><text x="475" y="150" fill-opacity=".8" filter="url(#blur)" textLength="830">Documentation</text><text x="475" y="150" fill-opacity=".3" textLength="830">Documentation</text></g><text x="475" y="140" textLength="830">Documentation</text></g><g transform="scale(.1)"><g aria-hidden="true" fill="#010101"><text x="1085" y="150" fill-opacity=".8" filter="url(#blur)" textLength="230">Wiki</text><text x="1085" y="150" fill-opacity=".3" textLength="230">Wiki</text></g><text x="1085" y="140" textLength="230">Wiki</text></g></g></svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

+1 -1
View File
@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="60" height="20" role="img" aria-label="Java: 8+"><title>Java: 8+</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="60" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="35" height="20" fill="#555"/><rect x="35" width="25" height="20" fill="#007ec6"/><rect width="60" 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="185" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="250">Java</text><text x="185" y="140" transform="scale(.1)" fill="#fff" textLength="250">Java</text><text aria-hidden="true" x="465" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="150">8+</text><text x="465" y="140" transform="scale(.1)" fill="#fff" textLength="150">8+</text></g></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="60" height="20" role="img" aria-label="Java: 8+"><title>Java: 8+</title><filter id="blur"><feGaussianBlur stdDeviation="16"/></filter><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="60" height="20" rx="3"/></clipPath><g clip-path="url(#r)"><rect width="35" height="20" fill="#555"/><rect x="35" width="25" height="20" fill="#007ec6"/><rect width="60" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><g transform="scale(.1)"><g aria-hidden="true" fill="#010101"><text x="185" y="150" fill-opacity=".8" filter="url(#blur)" textLength="250">Java</text><text x="185" y="150" fill-opacity=".3" textLength="250">Java</text></g><text x="185" y="140" textLength="250">Java</text></g><g transform="scale(.1)"><g aria-hidden="true" fill="#010101"><text x="465" y="150" fill-opacity=".8" filter="url(#blur)" textLength="150">8+</text><text x="465" y="150" fill-opacity=".3" textLength="150">8+</text></g><text x="465" y="140" textLength="150">8+</text></g></g></svg>

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

+1 -1
View File
@@ -1 +1 @@
<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>
<svg xmlns="http://www.w3.org/2000/svg" width="92" height="20" role="img" aria-label="Release: 6.1.1"><title>Release: 6.1.1</title><filter id="blur"><feGaussianBlur stdDeviation="16"/></filter><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="92" height="20" rx="3"/></clipPath><g clip-path="url(#r)"><rect width="53" height="20" fill="#555"/><rect x="53" width="39" height="20" fill="#007ec6"/><rect width="92" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><g transform="scale(.1)"><g aria-hidden="true" fill="#010101"><text x="275" y="150" fill-opacity=".8" filter="url(#blur)" textLength="430">Release</text><text x="275" y="150" fill-opacity=".3" textLength="430">Release</text></g><text x="275" y="140" textLength="430">Release</text></g><g transform="scale(.1)"><g aria-hidden="true" fill="#010101"><text x="715" y="150" fill-opacity=".8" filter="url(#blur)" textLength="290">6.1.1</text><text x="715" y="150" fill-opacity=".3" textLength="290">6.1.1</text></g><text x="715" y="140" textLength="290">6.1.1</text></g></g></svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="124" height="20" role="img" aria-label="GitHub: Source code"><title>GitHub: Source code</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="124" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="47" height="20" fill="#555"/><rect x="47" width="77" height="20" fill="#dfb317"/><rect width="124" 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="245" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="370">GitHub</text><text x="245" y="140" transform="scale(.1)" fill="#fff" textLength="370">GitHub</text><text aria-hidden="true" x="845" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="670">Source code</text><text x="845" y="140" transform="scale(.1)" fill="#fff" textLength="670">Source code</text></g></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="124" height="20" role="img" aria-label="GitHub: Source code"><title>GitHub: Source code</title><filter id="blur"><feGaussianBlur stdDeviation="16"/></filter><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="124" height="20" rx="3"/></clipPath><g clip-path="url(#r)"><rect width="47" height="20" fill="#555"/><rect x="47" width="77" height="20" fill="#d8b800"/><rect width="124" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><g transform="scale(.1)"><g aria-hidden="true" fill="#010101"><text x="245" y="150" fill-opacity=".8" filter="url(#blur)" textLength="370">GitHub</text><text x="245" y="150" fill-opacity=".3" textLength="370">GitHub</text></g><text x="245" y="140" textLength="370">GitHub</text></g><g transform="scale(.1)"><g aria-hidden="true" fill="#010101"><text x="845" y="150" fill-opacity=".8" filter="url(#blur)" textLength="670">Source code</text><text x="845" y="150" fill-opacity=".3" textLength="670">Source code</text></g><text x="845" y="140" textLength="670">Source code</text></g></g></svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

+1 -1
View File
@@ -274,7 +274,7 @@ if __name__ == "__main__":
]
)
release = "6.0.2"
release = "6.1.1"
download_image(f"https://img.shields.io/badge/Release-{release}-blue.svg", "Badge_Release.svg")
java = "8+"
+2 -8
View File
@@ -7,19 +7,13 @@
* [Compatibility issues](#compatibility-issues)
# Requirements
TAB does not depend on any other plugins. The experience can however be enhanced by installing other plugins, see below.
## Java
TAB's shared module is compiled with Java 8. For the platforms themselves, TAB does have a higher requirement, however, it does not require a Java version higher than the server software itself does. Specifically:
* Fabric and NeoForge require Java 25
* Velocity requires Java 17
* Bukkit and BungeeCord are ok with Java 8
TAB does not depend on any other plugins. The experience can however be enhanced by installing other plugins, see below. There is also no additional Java version requirement. If your server runs on your Java version, TAB will too.
# Supported server software and versions
With the introduction of new Minecraft versions, it is becoming harder and harder to support many versions in the same jar, eventually making it impossible since Minecraft 26.1.
**As a general rule, if you want the latest TAB version for your server please use [Modrinth](https://modrinth.com/plugin/tab-was-taken)'s download filter where you can select your server software and version and you'll get the latest jar.**
Currently, the latest Minecraft version supported by TAB is 26.1.2. When new versions come out, TAB is always updated soon after to support them.
Currently, the latest Minecraft version supported by TAB is **26.2**. When new versions come out, TAB is always updated soon after to support them.
If your server software and version is reasonably popular and TAB hasn't been backported to that version in a very long time, you may [request a backport](https://github.com/NEZNAMY/TAB/issues/new/choose) (unless backports to that version are no longer offered, see below).
Since TAB 6.0.0, Minecraft 1.x started taking hits with each subsequent versions - Bukkit versions started disappearing and **modded platforms no longer offer backports of TAB 6.0.0+ to MC 1.x** (this is because the build scripts had to be massively changed for MC 26+).
+31 -10
View File
@@ -7,8 +7,8 @@
* [Compatibility with other plugins](#compatibility-with-other-plugins)
* [Additional info](#additional-info)
* [Additional note 1 - Copying nametag visibility rule](#additional-note-1---copying-nametag-visibility-rule)
* [Additional note 2 - Hidden on sneak on 1.8](#additional-note-2---hidden-on-sneak-on-18)
* [Additional note 3 - Visible on NPCs](#additional-note-3---visible-on-npcs)
* [Additional note 2 - [1.8.x] Hidden on sneak](#additional-note-2---18x-hidden-on-sneak)
* [Additional note 3 - [26.1.2-] Visible on NPCs](#additional-note-3---2612--visible-on-npcs)
* [[26.1.x] Visible on all entities](#261x-visible-on-all-entities)
* [Additional note 4 - Compatibility with modified clients](#additional-note-4---compatibility-with-modified-clients)
* [Troubleshooting](#troubleshooting)
@@ -18,13 +18,14 @@
* [Example 2 - Hiding `title` for 1.20.3+ players](#example-2---hiding-title-for-1203-players)
* [Example 3 - Displaying health as 0-10 or in %](#example-3---displaying-health-as-0-10-or-in-)
* [Example 4 - Health bar using hearts](#example-4---health-bar-using-hearts)
* [Example 5 - Conditionally hiding belowname for 26.2+ viewers](#example-5---conditionally-hiding-belowname-for-262-viewers)
* [Tips & Tricks](#tips--tricks)
* [Tip 1 - Heart symbol](#tip-1---heart-symbol)
# About
This features gives you control over Minecraft's scoreboard objective feature with BELOW_NAME slot.
This line is displayed below the nametags of all player entities in game. It is not possible to explicitly disable this feature for NPCs; however, this goal can be achieved (see below for more info).
It is only visible when within an 8-block range of the player (the range is hardcoded in the client and cannot be changed with a plugin).
This features gives you control over Minecraft's scoreboard objective feature with BELOW_NAME slot.
On Minecraft 1.21.11 and below, it is visible on all player entities (including NPCs). On 26.1.x, it is visible on all player entities and all named entities (such as holograms), making this feature unusable. On 26.2 and above, it only appears on entities with a score assigned (the ideal implementation).
It is only visible when the target player is within a 10-block range of the viewer (the value is configurable since Minecraft 26.1).
Example visual effect:
![](https://images-ext-1.discordapp.net/external/YlGPCRDJVeZZI0TPWmVBKyHszxSkjatmclyqUThvTz8/https/image.prntscr.com/image/jcETUzVQQYqectQ2aI4iqQ.png)
@@ -54,8 +55,8 @@ 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 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+. |
| fancy-value | &c%health% | [1.20.3+] Any text, supports placeholders with per-player output. Only visible on 1.20.3+, where it completely replaces `value`. When evaluates to an empty string, the entire belowname is hidden for viewers using MC 26.2+ (when it was made possible). |
| fancy-value-default | NPC | [1.20.3 - 26.1.2] Default number format (`fancy-value`) for all player entities. `fancy-value` is displayed on every real player, therefore this default value will only appear on player entities which are not actual players, a.k.a. NPCs. Only visible on since 1.20.3 (the function did not exist prior) until 26.1.2 (player entities without scores no longer display any belowname since 26.2). |
| title | Health | Shared label shown after the score for every player entity. Player sees the same text on everyone (= placeholders are parsed for the viewer). Use the `value`/`fancy-value` fields for per‑player data; use `title` only for static labels like `Health` or placeholders which are supposed to be parsed for the viewing player. |
| disable-condition | %world%=disabledworld | A [condition](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Conditional-placeholders) (either name of a condition or a conditional expression) that must be met for disabling the feature for players. Set to empty for not disabling the feature ever. <br/> **Note**: Disabling the feature for a player means sending objective unregister packet to them, which results in player not seeing belowname on anyone anymore. It doesn't work the other way around - you cannot disable this feature on target players, only for viewers. |
@@ -82,10 +83,10 @@ Note that if this was automatically generated by a plugin, it will probably be a
The Minecraft feature is programmed to be affected by nametag visibility rule.
This means that when the nametag is set to invisible, belowname will be invisible as well.
## Additional note 2 - Hidden on sneak on 1.8
## Additional note 2 - [1.8.x] Hidden on sneak
Belowname is not visible on 1.8.x clients when player is sneaking. This is client-sided behavior and cannot be changed by the server.
## Additional note 3 - Visible on NPCs
## Additional note 3 - [26.1.2-] Visible on NPCs
Belowname objective is automatically attached to all entities of player type with the default value of
`0` (<1.20.3) or value configured as `fancy-value-default` (1.20.3+).
This includes player NPCs.
@@ -240,4 +241,24 @@ belowname-objective:
title: "&4❤"
```
> [!IMPORTANT]
> Make sure your config is [saved in UTF-8 encoding](https://github.com/NEZNAMY/TAB/wiki/How-to-save-the-config-in-UTF8-encoding) to properly read the heart symbol.
> Make sure your config is [saved in UTF-8 encoding](https://github.com/NEZNAMY/TAB/wiki/How-to-save-the-config-in-UTF8-encoding) to properly read the heart symbol.
## Example 5 - Conditionally hiding belowname for 26.2+ viewers
Since Minecraft 26.2, belowname only appears on entities with a score. This can be used to disable the belowname on players with a condition. We are trying to achieve a condition like this: "If something, hide belowname for 26.2+ viewers".
TAB is made to reset score (= hide belowname) of target players for 26.2+ viewers (<26.2 viewers are not affected, resetting scores for them would result in `fancy-value-default` to be shown, which is undesirable) if `fancy-value` evaluated to an empty string. Therefore, we will need to make a [relational condition](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Conditional-placeholders#relational-conditions) that checks the viewer's version and returns empty string if some condition is met.
Here is an example of hiding it if target player has less than 10 health (5 hearts):
```
conditions:
belowname-health:
conditions:
- "%viewer:player-version-id%>=776" # 26.2 uses 776, check for viewer's version, not target player's
- "%health%<10" # This should check target player's health
type: AND
true: "" # Viewer is on 26.2+ and target has less than 10 health, return empty string to hide it
false: "%health%" # Display health as normal
```
Then, use this condition as a relational condition (because it checks both viewer and target players):
```
belowname-objective:
fancy-value: "%rel_condition:belowname-health%"
```
@@ -10,6 +10,8 @@
* [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)
* [Additional info](#additional-info)
* [Additional note 1 - Choosing the correct AND / OR type](#additional-note-1---choosing-the-correct-and--or-type)
* [Examples](#examples)
* [Example 1 - Chaining conditional placeholders](#example-1---chaining-conditional-placeholders)
* [Example 2 - Combining AND and OR](#example-2---combining-and-and-or)
@@ -52,6 +54,9 @@ condition must contain the altered output.
To see the exact output of a placeholder including color codes, use `/tab parse <player> <placeholder>`.
## Permission
> [!WARNING]
> Giving someone OP status will automatically give them all permissions, which will make all the permission checks in conditions pass.
| Operation | Description | Example |
|-----------------------|---------------------------------|----------------------------------------------------------------------------------------------|
| `permission:<value>` | Permission requirement | `permission:my.permission` will pass if player has `my.permission` permission |
@@ -146,8 +151,9 @@ 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.
Using `%viewer:...%` will upgrade the condition to a relational condition and standard version will no longer be available.
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.
@@ -157,6 +163,13 @@ Permission checks count as 1000ms.
To configure refresh intervals of placeholders,
check out the [Optimization guide](https://github.com/NEZNAMY/TAB/wiki/Optimizing-the-plugin#2---all-platforms-placeholder-refresh-intervals).
# Additional Info
## Additional note 1 - Choosing the correct AND / OR type
Sometimes you want to combine multiple expressions using `AND` or `OR`. When doing this, you must pick the correct one based on your needs.
A common mistake is trying to turn spoken language directly into config, which can be wrong.
For example, you say you want to display scoreboard in servers `server1` **and** `server2`. As such, you may accidentally use `type: AND` or `;` in short format (`%server%=server1;%server%=server2`). This is, however, wrong, because that would mean your server must be `server1` **and** `server2` at the same time, which is obviously impossible. The correct type is `OR` here (`|`). So instead of thinking "I want to display scoreboard in `server1` **and** `server2`", you should think "I want to display scoreboard if player is in `server1` **or** `server2`.
# Examples
## Example 1 - Chaining conditional placeholders
Let's create colored ping by chaining conditions using multiple intervals for ping quality. This specific case would be easier done using [replacements](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Placeholder-output-replacements), but this is just an example. Let's say we want <50 for good, <100 for medium and 100+ for bad. Let's create the first condition:
@@ -140,9 +140,6 @@ Here are TAB's internal placeholders you can use when this feature is enabled:
For compatibility with vanish plugins, vanish status must be detected correctly. See [Additional information - Vanish detection](https://github.com/NEZNAMY/TAB/wiki/Additional-information#vanish-detection) for more info.
# Troubleshooting
> [!NOTE]
> This functionality is coming in the next TAB update (6.0.3 / 6.1.0). Adding it here to prepare the wiki in advance (and for users using dev builds).
This is a collection of tips to help you figure out why the feature isn't working as you expect.
To get started, run `/tab dump <player looking at the tablist>` and open the generated link. Scroll down to `features` -> `GlobalPlayerList` and check the content:
* If it says `GlobalPlayerList: Feature is disabled`, it means you did not enable the feature. Enable it by setting
@@ -2,6 +2,7 @@
* [About](#about)
* [Configuration](#configuration)
* [Grouping playerlist from multiple worlds](#grouping-playerlist-from-multiple-worlds)
* [Pattern Matching](#pattern-matching)
* ~Commands~
* ~Placeholders~
* ~Limitations~
@@ -40,4 +41,34 @@ Contains 2 groups, each of them consisting of 2 worlds.
World group name can be anything (in our case, they're called "lobby" and "minigames").
Under group name, list the actual world names.
With this setup, "lobby1" and "lobby2" will share playerlist, as well as "paintball" with "bedwars". All other worlds will have playerlist not shared with any other worlds. Creating a group for 1 world only is completely useless.
With this setup, "lobby1" and "lobby2" will share playerlist, as well as "paintball" with "bedwars". All other worlds will have playerlist not shared with any other worlds. Creating a group for 1 world only is completely useless.
### Pattern Matching
World names in groups support multiple pattern types for flexible configuration:
**Exact Match** - Matches server name exactly:
```yaml
shared-playerlist-world-groups:
worlds:
- world1
- world2
```
**Wildcard Patterns** - Use `*` for prefix or suffix matching:
```yaml
shared-playerlist-world-groups:
worlds:
- "World-*" # Matches: World-1, World-2, World-Hub, etc.
- "*-world" # Matches: eu-world, us-world, etc.
```
**Regex Patterns** - Use `regex:` prefix for advanced pattern matching:
```yaml
shared-playerlist-world-groups:
worlds:
- regex:World-[0-9]+ # Matches: World-1, World-23, but not World-Hub
smp:
- regex:SMP-(EU|US)-[0-9]+ # Matches: SMP-EU-1, SMP-US-2, etc.
```
Note: Wildcard patterns are case-insensitive, while regex patterns are case-sensitive by default (use `(?i)` flag for case-insensitive regex).
+1 -4
View File
@@ -78,11 +78,8 @@ All of the options are explained in the following table.
| toggle-command | /sb | Command that can be used to toggle scoreboard for the player running the command. Players need `tab.scoreboard.toggle` permission to use it. <br />**Note:** Commands are not supposed to be registered / unregistered dynamically at runtime. As such, you may run into small issues when changing the toggle command and reloading. After you modify the command to your liking, reconnect to properly see it in tabcomplete. If you don't use TAB on a proxy, consider restarting the server as well for the old command to actually be unregistered. |
| remember-toggle-choice | false | When enabled, toggling decision is saved into a file to remember it even after reloads/restarts/reconnects |
| hidden-by-default | false | If enabled, scoreboard will automatically be hidden on join until toggle command is used to show it. |
| use-numbers | false | If enabled, numbers 1-15 will be used in the scoreboard. If disabled, `static-number` is shown everywhere. Will not be visible for 1.20.3+ players, instead, you can configure any text to show using `\|\|text` in the lines (scroll up for more info). |
| static-number | 0 | If `use-numbers` is disabled, this is number to be in all lines. |
| delay-on-join-milliseconds | 0 | Delay in milliseconds to send scoreboard after joining. |
| scoreboards | *Map* | Scoreboards to display based on conditions (see below for more info).
|
| scoreboards | *Map* | Scoreboards to display based on conditions (see below for more info). |
## Scoreboard
A scoreboard consists of the following options:
-1
View File
@@ -2,4 +2,3 @@
A collection of things people have requested that I might look into implementing when I get extremely bored sometime next decade.
* Relational placeholder support in sorting
* 1.21.2+ sorting support
* disable-condition support in multi-server-support
+1 -1
View File
@@ -1,5 +1,5 @@
# About the wiki
The wiki explains the functionality of the plugin as of version **6.0.2**.
The wiki explains the functionality of the plugin as of version **6.1.0**.
If your config looks different or is missing some features, you are using an old version of the plugin.
To get the latest version, check [releases](https://github.com/NEZNAMY/TAB/releases/).
Wiki for older plugin versions is not available.
@@ -173,10 +173,13 @@ See table below for behavior based on your setup.
# MiniMessage
TAB has [MiniMessage](https://docs.advntr.dev/minimessage/format.html) hook, however, it does not include this library on its own. Including it would cause conflicts, so it has to be included in the server software.
* It **is** included in **Paper 1.18.2+** and **Velocity**.
* It **is not** included in **Spigot**, any server software for **1.18.1-**, **BungeeCord**, **Fabric**, **Forge** and **NeoForge**.
* It **is not** included in **Spigot**, any server software for **1.18.1-**, **BungeeCord**, **Fabric**, **Forge** and **NeoForge**. However, you can install [adventure-platform-mod](https://modrinth.com/mod/adventure-platform-mod) on Fabric / NeoForge.
When MiniMessage is detected on your server, it is automatically used unless support is disabled. All codes are translated to MiniMessage syntax and then parsed by MiniMessage.
> [!WARNING]
> TAB's gradient syntax (`<#RRGGBB>Text</#RRGGBB>`) is a compatibility layer that TAB rewrites before parsing, it is more fragile than native [MiniMessage](https://docs.advntr.dev/minimessage/format.html) gradients (`<gradient:#RRGGBB:#RRGGBB>Text</gradient>`). On **Velocity**, this is especially problematic because [MiniPlaceholders](https://github.com/MiniPlaceholders/MiniPlaceholders) (see [TAB integration PR #1677](https://github.com/NEZNAMY/TAB/pull/1677)) parses MiniMessage *before* TAB can rewrite its gradient syntax. For example, placeholder output like `<#E0B11E>MyServer</#FF0000>` is interpreted by MiniPlaceholders as a hex color tag (`<#E0B11E>`), but `</#FF0000>` is not recognized as a gradient closing tag, so the formatting breaks. Prefer native MiniMessage syntax in placeholder output (e.g. `<gradient:#E0B11E:#FF0000>MyServer</gradient>`). See [Placeholders - MiniPlaceholders](https://github.com/NEZNAMY/TAB/wiki/Placeholders#miniplaceholders) for more information.
# Config options
| Option name | Default value | Description |
|--------------------------|---------------|--------------------------------------------------------------------------------------------------------------------------------------------|
+1 -1
View File
@@ -27,7 +27,7 @@ Install [TAB-Bridge](https://github.com/NEZNAMY/TAB/wiki/TAB-Bridge) on your **b
If you are using **Velocity**,
install [VelocityScoreboardAPI](https://github.com/NEZNAMY/VelocityScoreboardAPI/) plugin **on the proxy** for scoreboard-related features to work.
> [!NOTE]
> [!NOTE]
> With proxy installation, use **/btab** command instead of **/tab**.
Advantages of proxy installation compared to installing on all backend servers instead:
+80 -19
View File
@@ -1,24 +1,33 @@
# Content
* [About](#about)
* [Ways of optimizing](#ways-of-optimizing)
* [#1 - [All platforms] Static text instead of placeholders](#1---all-platforms-static-text-instead-of-placeholders)
* [#2 - [All platforms] Placeholder refresh intervals](#2---all-platforms-placeholder-refresh-intervals)
* [#3 - [All platforms] Don't use RGB](#3---all-platforms-dont-use-rgb)
* [#4 - [All platforms] Don't use animations](#4---all-platforms-dont-use-animations)
* [#5 - [All platforms] Disable tablist name formatting](#5---all-platforms-disable-tablist-name-formatting)
* [#6 - [All platforms] Increase permission refresh interval](#6---all-platforms-increase-permission-refresh-interval)
* [#7 - [Proxies] Disable TAB expansion](#7---proxies-disable-tab-expansion)
* [#8 - [Proxies] Disable update-latency option in global playerlist](#8---proxies-disable-update-latency-option-in-global-playerlist)
* [Netty false positive in spark reports](#netty-false-positive-in-spark-reports)
* [Why there is a false positive](#why-there-is-a-false-positive)
* [How to read the real value](#how-to-read-the-real-value)
* [Packet listening usage](#packet-listening-usage)
* [Sending packets](#sending-packets)
# About
This guide will explain all primary ways you can reduce plugin's CPU usage besides the obvious things like "don't spam animations" or "disable features you don't want".
The usage can be checked at any time using [**/tab cpu**](https://github.com/NEZNAMY/TAB/wiki/Commands-&-Permissions#tab-cpu) command. If the numbers appear to be higher than they should be, check this guide for various ways to optimize the plugin. Most of these apply for all platforms, but some of them are only specific for some platforms.
# Content
* [#1 - [All platforms] Static text instead of placeholders](#1---all-platforms-static-text-instead-of-placeholders)
* [#2 - [All platforms] Placeholder refresh intervals](#2---all-platforms-placeholder-refresh-intervals)
* [#3 - [All platforms] Don't use RGB](#3---all-platforms-dont-use-rgb)
* [#4 - [All platforms] Don't use animations](#4---all-platforms-dont-use-animations)
* [#5 - [All platforms] Disable tablist name formatting](#5---all-platforms-disable-tablist-name-formatting)
* [#6 - [All platforms] Increase permission refresh interval](#6---all-platforms-increase-permission-refresh-interval)
* [#7 - [Proxies] Disable TAB expansion](#7---proxies-disable-tab-expansion)
* [#8 - [Proxies] Disable update-latency option in global playerlist](#8---proxies-disable-update-latency-option-in-global-playerlist)
# #1 - [All platforms] Static text instead of placeholders
# Ways of optimizing
## #1 - [All platforms] Static text instead of placeholders
Static text doesn't need to be refreshed periodically, resulting in better performance.
Some placeholders can be replaced with static text, such as placeholder for max player counts,
since that value doesn't change at runtime.
Another option is to configure prefixes per-group in config
instead of using placeholders to take those values from your permission plugin.
# #2 - [All platforms] Placeholder refresh intervals
## #2 - [All platforms] Placeholder refresh intervals
Some placeholders take a long time to process and result in high cpu usage. However, this can be optimized by configuring refresh interval of placeholders.
Internal placeholders use a value hardcoded into the plugin by default. PlaceholderAPI placeholders use `default-refresh-interval`.
@@ -45,16 +54,16 @@ making it look like the CPU is perfect, while on the next refresh it may be high
For this reason, if you want to cleanly track usage, keep the interval at 10000 at most.
Using a higher interval will help if needed, but may lead to misleading info in cpu output.
# #3 - [All platforms] Don't use RGB
## #3 - [All platforms] Don't use RGB
Though RGB colors look good, they require the text to be split into several components with `color` field used instead of the whole text pasted into the `text` field. This is a complicated process that takes quite some time. Sticking to legacy colors results in better performance.
# #4 - [All platforms] Don't use animations
## #4 - [All platforms] Don't use animations
While animations look cool, they also have a high resource usage.
This is because they work by spamming updates every time an animation switches to a new frame.
When animations are not used, no updates need to be sent (unless another placeholder changes value),
not causing extra CPU usage.
# #5 - [All platforms] Disable tablist name formatting
## #5 - [All platforms] Disable tablist name formatting
As we already know from [Client-sided mechanics](https://github.com/NEZNAMY/TAB/wiki/Client%E2%80%90sided-mechanics#nametag-format-in-tablist) page,
the nametag format appears in tablist if tablist name is not defined.
You can take advantage of this to effectively disable one entire feature and its cpu usage,
@@ -65,7 +74,7 @@ However, you can only do this if:
Whether you have another plugin attempting to handle them or not may not be easy to identify with a lot of plugins and not enough time spent configuring them. In that case, your best choice is to try it and see. If tablist names break, it means you have another plugin and cannot take advantage of this optimization option.
# #6 - [All platforms] Increase permission refresh interval
## #6 - [All platforms] Increase permission refresh interval
Plugin needs to constantly check permissions for:
* Permission checks in conditions
* Permission checks if sorting by permissions
@@ -79,7 +88,7 @@ Refreshes them every second.
If you don't need any permission/group changes to take effect within a second,
you can increase this value for better performance.
# #7 - [Proxies] Disable TAB expansion
## #7 - [Proxies] Disable TAB expansion
To maximize the performance of TAB's response to PlaceholderAPI request,
values are tracked in advance and then quickly returned.
This process takes resources and is especially heavy on proxy installation,
@@ -90,5 +99,57 @@ Disabling TAB's PlaceholderAPI expansion if you don't use it improves performanc
placeholders:
register-tab-expansion: false
```
# #8 - [Proxies] Disable update-latency option in global playerlist
When [Global playerlist](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Global-playerlist) is enabled, you can enable / disable [update-latency](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Global-playerlist#:~:text=other%20unlisted%20servers.-,update%2Dlatency,-false) option (open the link for detailed description of the option). Disabling this option massively improves performance.
## #8 - [Proxies] Disable update-latency option in global playerlist
When [Global playerlist](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Global-playerlist) is enabled, you can enable / disable [update-latency](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Global-playerlist#:~:text=other%20unlisted%20servers.-,update%2Dlatency,-false) option (open the link for detailed description of the option). Disabling this option massively improves performance.
# Netty false positive in spark reports
When taking a spark report with all threads monitored instead of just the server thread using `--thread *` argument (or on a proxy where this is enabled by default since there is no main thread to monitor by default), you may notice high usage (both CPU and RAM) in netty thread when grouping threads by plugin. **Vast majority of that usage is false positive.** Let's go over why this is the case and how to read the real value.
## Why there is a false positive
When listening to an event, the server fires the events in a loop, where each event is the end point. As such, measuring every event handler individually is accurate. Here is how the hierarchy looks with example numbers:
```
Event processing in loop (6ms total)
↳ Plugin 1 (1ms)
↳ Plugin 2 (2ms)
↳ Plugin 3 (3ms)
End of loop
```
As such, the measured values are accurate and spark is able to separate each event handler based on plugin. Command executions and repeating tasks are even simpler.
The issue is with netty injection. The purpose is to listen to outgoing packets, react to them and possibly modify them. Unlike with events, these are not processed in parallel, but every handler forwards the packet to the next one (or doesn't if it should be cancelled). As such, the hierarchy looks like this (simplified version):
```
Beginning (12ms total)
↳ Plugin 1 (1ms self, 12ms total)
↳ Plugin 2 (1ms self, 11ms total)
↳ Plugin 3 (1ms self, 10ms total)
↳ Minecraft encoder (10ms self)
```
As such, spark will show 12ms total for Plugin 1, even though its own usage is only 1ms. This is due to a limitation of the design of Spark in combination with how netty handlers work. This is only the case for plugins with their own netty injection, it does not apply to plugins that use ProtocolLib, because that one offers event-like functionality explained above. In this case, the usage shown in spark report is 100% accurate.
## How to read the real value
An actual usage of the netty threads can be split into 2 parts:
* Listening to outgoing packets
* Sending packets, which causes it to go though the pipeline and then encode
### Packet listening usage
This one is simple and accurate. Take the shown number, expand the call stack and subtract `io.netty.channel.ChannelDuplexHandler.write()` usage, which is forwarding the packet to the next handler and would be there even if that plugin wasn't injected.
Let's take a look at a practical example:
<img width="1091" height="266" alt="image" src="https://github.com/user-attachments/assets/6d9e8c25-8375-413b-ab9b-31b52ff9262c" />
Here, spark reports `31.71%` usage by TAB. However, `30.87%` of it is forwarding it to the next handler (`io.netty.channel.ChannelDuplexHandler.write()`), which forwards it to the next one, until eventually the server encodes the packet. Therefore, TAB's real usage by listening to outgoing packets in this report is `31.71%` - `30.87%` = `0.84%`. Much less than what you originally see.
### Sending packets
Sadly, this one is impossible to calculate directly. When a packet is scheduled for sending, it is only added to a queue, then consumed by a netty thread, not the caller thread. At that point we are losing information about where a packet comes from.
There is some good news though.
First, vast majority of plugins barely send any packets. For example PlaceholderAPI is just an interface between plugins, chat plugins only modify chat message packets that would be sent anyway, ViaVersion same thing, and so on...
Second, plugins that send a lot of packets usually send the kind that the server either doesn't send at all, or not much. Expanding the data from the previously shown report gives us this:
<img width="1083" height="496" alt="image" src="https://github.com/user-attachments/assets/c6157e78-ef6a-4d9c-b4b7-7c7b32dd14f8" />
Here we can see that encoding team packets took 0.99%. Since TAB is the only team plugin you need for managing players, it is safe to assume most, if not all of it comes from TAB.
Same here - 0.13% from header and footer.
<img width="1085" height="93" alt="image" src="https://github.com/user-attachments/assets/559dce56-83d3-49b2-9f4b-29b6124f944c" />
Sadly there are other operations needed than just encoding packets, such as allocating buffers and other, so this part is not that simple anymore.
+11 -2
View File
@@ -5,6 +5,7 @@
* [Backend only](#backend-only)
* [Proxy only](#proxy-only)
* [PlaceholderAPI](#placeholderapi)
* [MiniPlaceholders](#miniplaceholders)
* [Relational placeholders](#relational-placeholders)
* [About](#about-1)
* [Usage](#usage)
@@ -107,7 +108,9 @@ Full list of placeholders (general + feature specific):
| 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) |
| `%tab_placeholder_<placeholder>%` | Returns value of tab's internal placeholder (this also applies [Placeholder output replacements](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Placeholder-output-replacements) to them if configured). <br />Example: `%tab_placeholder_animation:name%` for `%animation:name%` |
| `%rel_tab_replace_<rel_placeholder>%` | Applies [Placeholder output replacements](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Placeholder-output-replacements) to a **relationnal** PlaceholderAPI placeholder |
| `%rel_tab_placeholder_<rel_placeholder>%` | Returns value of tab's internal **relational** placeholder (this also applies [Placeholder output replacements](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Placeholder-output-replacements) to them if configured). It may look strange that the `rel` is twice in there, but there's no other way. The first one tells PlaceholderAPI that this is a relational placeholder, the second one is part of the TAB's identifier, which also uses it to mark the placeholder as relational. <br />Example: `%rel_tab_placeholder_rel_condition:name%` for `%rel_condition:name%` |
**BossBar**:
@@ -143,6 +146,12 @@ Full list of placeholders (general + feature specific):
| `%tab_customtabname_raw%` | Player's current raw customtabname with placeholder identifiers. |
| `%tab_tabsuffix_raw%` | Player's current raw tabsuffix with placeholder identifiers. |
# MiniPlaceholders
On **Velocity**, TAB integrates with [MiniPlaceholders](https://github.com/MiniPlaceholders/MiniPlaceholders) when the plugin is installed. TAB automatically detects and resolves MiniPlaceholders placeholders in config text, including global, per-player, and relational ones. Placeholders use angle brackets, with the expansion and name separated by an underscore (for example `<luckperms_prefix>`), and arguments added after a colon (for example `<luckperms_meta:key>`). See the [MiniPlaceholders placeholder list](https://miniplaceholders.github.io/docs/user-guide/Placeholders) for all available placeholders. TAB also registers its own expansion, offering placeholders such as `<tab_tabprefix>` and `<tab_placeholder:name>`. The `%placeholder%` syntax remains reserved for [PlaceholderAPI](https://github.com/PlaceholderAPI/PlaceholderAPI) (via TAB Bridge on Velocity).
> [!WARNING]
> TAB's gradient syntax (`<#RRGGBB>Text</#RRGGBB>`) is a compatibility layer that TAB rewrites before parsing, it is more fragile than native [MiniMessage](https://docs.advntr.dev/minimessage/format.html) gradients (`<gradient:#RRGGBB:#RRGGBB>Text</gradient>`). On **Velocity**, this is especially problematic because [MiniPlaceholders](https://github.com/MiniPlaceholders/MiniPlaceholders) parses MiniMessage *before* TAB can rewrite its gradient syntax. For example, placeholder output like `<#E0B11E>MyServer</#FF0000>` is interpreted by MiniPlaceholders as a hex color tag (`<#E0B11E>`), but `</#FF0000>` is not recognized as a gradient closing tag, so the formatting breaks. Prefer native MiniMessage syntax in placeholder output (e.g. `<gradient:#E0B11E:#FF0000>MyServer</gradient>`). See [How to use Minecraft components - MiniMessage](https://github.com/NEZNAMY/TAB/wiki/How-to-use-Minecraft-components#minimessage) for more information.
# Relational placeholders
## About
Unlike classic placeholders which take either 0
@@ -168,7 +177,7 @@ To properly distinguish viewer/target (if needed), your relational placeholder e
# Refreshing
## Refresh intervals
TAB is refreshing placeholders in intervals. For internal placeholders it's using values I found the most optimal and set directly in the plugin. Refresh intervals for PlaceholderAPI placeholders can be [configured](https://github.com/NEZNAMY/TAB/wiki/Optimizing-the-plugin#2---placeholder-refresh-intervals).
TAB is refreshing placeholders in intervals. For internal placeholders it's using values I found the most optimal and set directly in the plugin. Refresh intervals for PlaceholderAPI placeholders can be [configured](https://github.com/NEZNAMY/TAB/wiki/Optimizing-the-plugin#2---all-platforms-placeholder-refresh-intervals).
## Sync refreshing
By default, all placeholders are refreshed asynchronously,
+2 -2
View File
@@ -17,7 +17,7 @@ jobs:
distribution: 'temurin'
java-version: 25
- name: Cache Gradle
uses: actions/cache@v4.2.0 # https://github.com/actions/cache/releases
uses: actions/cache@v6 # https://github.com/actions/cache/releases
with:
path: |
~/.gradle/caches
@@ -39,7 +39,7 @@ jobs:
mv "$original" "$new_path"
done
- name: Upload a Build Artifact
uses: actions/upload-artifact@v4.3.3 # https://github.com/actions/upload-artifact/releases
uses: actions/upload-artifact@v7 # https://github.com/actions/upload-artifact/releases
with:
name: Click here to download
path: "jar/build/libs/TAB v*+${{ github.run_number }}.jar"
+3 -3
View File
@@ -3,7 +3,7 @@ plugins {
}
dependencies {
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")
implementation("com.gradleup.shadow:shadow-gradle-plugin:9.4.2")
implementation("io.freefair.gradle:lombok-plugin:9.5.0")
implementation("dev.architectury.loom-no-remap:dev.architectury.loom-no-remap.gradle.plugin:1.17-SNAPSHOT")
}
+4 -1
View File
@@ -4,7 +4,7 @@ plugins {
allprojects {
group = "me.neznamy"
version = "6.1.0-SNAPSHOT"
version = "6.1.2"
description = "An all-in-one solution that works"
ext.set("id", "tab")
@@ -20,6 +20,7 @@ val platformPaths = setOf(
":bukkit:paper_1_21_4",
":bukkit:paper_1_21_9",
":bukkit:paper_1_21_11",
":bukkit:paper_26_2",
":bukkit:v1_7_R4",
":bukkit:v1_8_R3",
":bukkit:v1_12_R1",
@@ -41,9 +42,11 @@ val platformPaths = setOf(
":bukkit:v1_21_R6",
":bukkit:v1_21_R7",
":bukkit:v26_1",
":bukkit:v26_2",
":bungeecord",
":velocity",
":fabric",
":fand",
":neoforge"
// ":forge"
)
+1 -1
View File
@@ -1,5 +1,5 @@
plugins {
id("io.papermc.paperweight.userdev") version "2.0.0-beta.19"
id("io.papermc.paperweight.userdev") version "2.0.0-beta.21"
}
repositories {
@@ -5,7 +5,6 @@ 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;
@@ -17,9 +16,8 @@ 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.lang.reflect.Constructor;
import java.util.Collection;
import java.util.Collections;
import java.util.Optional;
/**
@@ -31,8 +29,16 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
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);
private static final Constructor<ClientboundSetPlayerTeamPacket> teamConstructor;
static {
try {
teamConstructor = ClientboundSetPlayerTeamPacket.class.getDeclaredConstructor(String.class, int.class, Optional.class, Collection.class);
teamConstructor.setAccessible(true);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
/**
* Constructs new instance with given player.
*
@@ -144,7 +150,15 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
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()));
Collection<String> players = team.getPlayers();
if (players != null) {
return teamConstructor.newInstance(
team.getName(),
action,
team.getParameters(),
onTeamPacket(action, team.getName(), players)
);
}
}
}
return packet;
@@ -6,6 +6,7 @@ import lombok.NonNull;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.chat.component.TabComponent;
import me.neznamy.tab.shared.features.layout.LayoutManagerImpl;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.decorators.TrackedTabList;
import net.minecraft.network.chat.Component;
@@ -135,7 +136,7 @@ public class NMSPacketTabList extends TrackedTabList<BukkitTabPlayer> {
}
}
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED)) {
if (allPlayersHidden && nmsData.profileId().getMostSignificantBits() != 0) { // Filter out layout entries
if (allPlayersHidden && !LayoutManagerImpl.UUIDS_SET.contains(nmsData.profileId())) { // Filter out layout entries
listed = false;
rewriteEntry = rewritePacket = true;
}
+1 -1
View File
@@ -1,5 +1,5 @@
plugins {
id("io.papermc.paperweight.userdev") version "2.0.0-beta.19"
id("io.papermc.paperweight.userdev") version "2.0.0-beta.21"
}
repositories {
@@ -5,7 +5,6 @@ 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;
@@ -17,9 +16,8 @@ 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.lang.reflect.Constructor;
import java.util.Collection;
import java.util.Collections;
import java.util.Optional;
/**
@@ -31,8 +29,16 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
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();
protected static final Scoreboard dummyScoreboard = new Scoreboard();
private static final Field players = ReflectionUtils.getOnlyField(ClientboundSetPlayerTeamPacket.class, Collection.class);
private static final Constructor<ClientboundSetPlayerTeamPacket> teamConstructor;
static {
try {
teamConstructor = ClientboundSetPlayerTeamPacket.class.getDeclaredConstructor(String.class, int.class, Optional.class, Collection.class);
teamConstructor.setAccessible(true);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
/**
* Constructs new instance with given player.
*
@@ -144,7 +150,15 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
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()));
Collection<String> players = team.getPlayers();
if (players != null) {
return teamConstructor.newInstance(
team.getName(),
action,
team.getParameters(),
onTeamPacket(action, team.getName(), players)
);
}
}
}
return packet;
@@ -8,6 +8,7 @@ import lombok.NonNull;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.chat.component.TabComponent;
import me.neznamy.tab.shared.features.layout.LayoutManagerImpl;
import me.neznamy.tab.shared.platform.decorators.TrackedTabList;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.Packet;
@@ -138,7 +139,7 @@ public class NMSPacketTabList extends TrackedTabList<BukkitTabPlayer> {
}
}
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED)) {
if (allPlayersHidden && nmsData.profileId().getMostSignificantBits() != 0) { // Filter out layout entries
if (allPlayersHidden && !LayoutManagerImpl.UUIDS_SET.contains(nmsData.profileId())) { // Filter out layout entries
listed = false;
rewriteEntry = rewritePacket = true;
}
+1 -1
View File
@@ -1,5 +1,5 @@
plugins {
id("io.papermc.paperweight.userdev") version "2.0.0-beta.19"
id("io.papermc.paperweight.userdev") version "2.0.0-beta.21"
}
repositories {
@@ -5,7 +5,6 @@ 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;
@@ -17,9 +16,8 @@ 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.lang.reflect.Constructor;
import java.util.Collection;
import java.util.Collections;
import java.util.Optional;
/**
@@ -31,8 +29,16 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
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);
private static final Constructor<ClientboundSetPlayerTeamPacket> teamConstructor;
static {
try {
teamConstructor = ClientboundSetPlayerTeamPacket.class.getDeclaredConstructor(String.class, int.class, Optional.class, Collection.class);
teamConstructor.setAccessible(true);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
/**
* Constructs new instance with given player.
*
@@ -144,7 +150,15 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
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()));
Collection<String> players = team.getPlayers();
if (players != null) {
return teamConstructor.newInstance(
team.getName(),
action,
team.getParameters(),
onTeamPacket(action, team.getName(), players)
);
}
}
}
return packet;
@@ -6,6 +6,7 @@ import lombok.NonNull;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.chat.component.TabComponent;
import me.neznamy.tab.shared.features.layout.LayoutManagerImpl;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.decorators.TrackedTabList;
import net.minecraft.network.chat.Component;
@@ -136,7 +137,7 @@ public class NMSPacketTabList extends TrackedTabList<BukkitTabPlayer> {
}
}
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED)) {
if (allPlayersHidden && nmsData.profileId().getMostSignificantBits() != 0) { // Filter out layout entries
if (allPlayersHidden && !LayoutManagerImpl.UUIDS_SET.contains(nmsData.profileId())) { // Filter out layout entries
listed = false;
rewriteEntry = rewritePacket = true;
}
+1 -1
View File
@@ -1,5 +1,5 @@
plugins {
id("io.papermc.paperweight.userdev") version "2.0.0-beta.19"
id("io.papermc.paperweight.userdev") version "2.0.0-beta.21"
}
repositories {
@@ -5,7 +5,6 @@ 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;
@@ -17,9 +16,8 @@ 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.lang.reflect.Constructor;
import java.util.Collection;
import java.util.Collections;
import java.util.Optional;
/**
@@ -31,8 +29,16 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
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);
private static final Constructor<ClientboundSetPlayerTeamPacket> teamConstructor;
static {
try {
teamConstructor = ClientboundSetPlayerTeamPacket.class.getDeclaredConstructor(String.class, int.class, Optional.class, Collection.class);
teamConstructor.setAccessible(true);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
/**
* Constructs new instance with given player.
*
@@ -144,7 +150,15 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
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()));
Collection<String> players = team.getPlayers();
if (players != null) {
return teamConstructor.newInstance(
team.getName(),
action,
team.getParameters(),
onTeamPacket(action, team.getName(), players)
);
}
}
}
return packet;
@@ -6,6 +6,7 @@ import lombok.NonNull;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.chat.component.TabComponent;
import me.neznamy.tab.shared.features.layout.LayoutManagerImpl;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.decorators.TrackedTabList;
import net.minecraft.network.chat.Component;
@@ -137,7 +138,7 @@ public class NMSPacketTabList extends TrackedTabList<BukkitTabPlayer> {
}
}
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED)) {
if (allPlayersHidden && nmsData.profileId().getMostSignificantBits() != 0) { // Filter out layout entries
if (allPlayersHidden && !LayoutManagerImpl.UUIDS_SET.contains(nmsData.profileId())) { // Filter out layout entries
listed = false;
rewriteEntry = rewritePacket = true;
}
+1 -1
View File
@@ -1,5 +1,5 @@
plugins {
id("io.papermc.paperweight.userdev") version "2.0.0-beta.19"
id("io.papermc.paperweight.userdev") version "2.0.0-beta.21"
}
repositories {
@@ -5,7 +5,6 @@ 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;
@@ -17,9 +16,8 @@ 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.lang.reflect.Constructor;
import java.util.Collection;
import java.util.Collections;
import java.util.Optional;
/**
@@ -31,8 +29,16 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
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);
private static final Constructor<ClientboundSetPlayerTeamPacket> teamConstructor;
static {
try {
teamConstructor = ClientboundSetPlayerTeamPacket.class.getDeclaredConstructor(String.class, int.class, Optional.class, Collection.class);
teamConstructor.setAccessible(true);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
/**
* Constructs new instance with given player.
*
@@ -144,7 +150,15 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
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()));
Collection<String> players = team.getPlayers();
if (players != null) {
return teamConstructor.newInstance(
team.getName(),
action,
team.getParameters(),
onTeamPacket(action, team.getName(), players)
);
}
}
}
return packet;
@@ -8,6 +8,7 @@ import lombok.NonNull;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.chat.component.TabComponent;
import me.neznamy.tab.shared.features.layout.LayoutManagerImpl;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.decorators.TrackedTabList;
import net.minecraft.network.chat.Component;
@@ -139,7 +140,7 @@ public class NMSPacketTabList extends TrackedTabList<BukkitTabPlayer> {
}
}
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED)) {
if (allPlayersHidden && nmsData.profileId().getMostSignificantBits() != 0) { // Filter out layout entries
if (allPlayersHidden && !LayoutManagerImpl.UUIDS_SET.contains(nmsData.profileId())) { // Filter out layout entries
listed = false;
rewriteEntry = rewritePacket = true;
}
+28
View File
@@ -0,0 +1,28 @@
plugins {
id("io.papermc.paperweight.userdev") version "2.0.0-beta.21"
}
repositories {
maven("https://jitpack.io") // YamlAssist
maven("https://repo.opencollab.dev/maven-snapshots/")
maven("https://repo.viaversion.com/")
maven {
name = "luck-repo"
url = uri("https://repo.lucko.me/")
content {
includeModule("me.lucko", "spark-api")
}
}
}
val version = "26.2.build.+"
dependencies {
implementation(projects.bukkit)
paperweight.paperDevBundle(version)
compileOnly("io.papermc.paper:paper-api:${version}")
}
tasks.compileJava {
options.release.set(25)
}
@@ -0,0 +1,63 @@
package me.neznamy.tab.platforms.bukkit.paper_26_2;
import lombok.NonNull;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import net.minecraft.network.chat.numbers.FixedFormat;
import net.minecraft.network.protocol.game.ClientboundSetObjectivePacket;
import net.minecraft.world.scores.criteria.ObjectiveCriteria;
import org.jetbrains.annotations.NotNull;
/**
* Scoreboard override for Canvas (Folia fork) that implements scoreboards with
* new checks in NMS setters since 26.1.2.
*/
public class CanvasPacketScoreboard extends NMSPacketScoreboard {
/**
* Constructs new instance with given player.
*
* @param player
* Player this scoreboard will belong to
*/
public CanvasPacketScoreboard(@NotNull BukkitTabPlayer player) {
super(player);
}
/**
* Updates the objective by creating a new one instead of updating the existing one,
* which would require sync access on Canvas.
*
* @param objective
* Objective to update
*/
@Override
public void updateObjective(@NonNull Objective objective) {
net.minecraft.world.scores.Objective obj = new net.minecraft.world.scores.Objective(
dummyScoreboard,
objective.getName(),
ObjectiveCriteria.DUMMY,
objective.getTitle().convert(),
ObjectiveCriteria.RenderType.values()[objective.getHealthDisplay().ordinal()],
false,
objective.getNumberFormat() == null ? null : objective.getNumberFormat().toFixedFormat(FixedFormat::new)
);
objective.setPlatformObjective(obj);
sendPacket(new ClientboundSetObjectivePacket(obj, ObjectiveAction.UPDATE));
}
@Override
public void registerTeam(@NonNull Team team) {
player.getPlatform().runSyncGlobal(() -> super.registerTeam(team));
}
@Override
public void unregisterTeam(@NonNull Team team) {
// This one too, to avoid register+unregister causing unregister to run first
player.getPlatform().runSyncGlobal(() -> super.unregisterTeam(team));
}
@Override
public void updateTeam(@NonNull Team team) {
player.getPlatform().runSyncGlobal(() -> super.updateTeam(team));
}
}
@@ -0,0 +1,84 @@
package me.neznamy.tab.platforms.bukkit.paper_26_2;
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);
}
}
@@ -0,0 +1,61 @@
package me.neznamy.tab.platforms.bukkit.paper_26_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 me.neznamy.tab.shared.util.ReflectionUtils;
import org.bukkit.craftbukkit.entity.CraftPlayer;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
/**
* Implementation provider using direct Mojang-mapped NMS code for Paper 26.2.
*/
@Getter
public class NMSImplementationProvider implements ImplementationProvider {
/** Flag tracking if this server is Canvas 26.1.2+ with new scoreboard checks */
private final boolean isCanvas = ReflectionUtils.classExists("io.canvasmc.canvas.world.scores.TeamData") ||
ReflectionUtils.classExists("io.canvasmc.canvas.threadedregions.scores.TeamData");
@NotNull
private final ComponentConverter<?> componentConverter = new NMSComponentConverter();
@Override
@NotNull
public Scoreboard newScoreboard(@NotNull BukkitTabPlayer player) {
if (isCanvas) {
return new CanvasPacketScoreboard(player);
} else {
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();
}
}
@@ -0,0 +1,191 @@
package me.neznamy.tab.platforms.bukkit.paper_26_2;
import lombok.NonNull;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.platform.decorators.SafeScoreboard;
import net.minecraft.network.chat.numbers.FixedFormat;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.*;
import net.minecraft.world.scores.PlayerTeam;
import net.minecraft.world.scores.Scoreboard;
import net.minecraft.world.scores.TeamColor;
import net.minecraft.world.scores.criteria.ObjectiveCriteria;
import net.minecraft.world.scores.criteria.ObjectiveCriteria.RenderType;
import org.bukkit.craftbukkit.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Constructor;
import java.util.Arrays;
import java.util.Collection;
import java.util.Optional;
/**
* Scoreboard implementation using direct mojang-mapped code.
*/
public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
private static final Optional<TeamColor>[] colors = Arrays.stream(TeamColor.values()).map(Optional::of).toArray(Optional[]::new);
private static final net.minecraft.world.scores.Team.CollisionRule[] collisions = net.minecraft.world.scores.Team.CollisionRule.values();
private static final net.minecraft.world.scores.Team.Visibility[] visibilities = net.minecraft.world.scores.Team.Visibility.values();
protected static final Scoreboard dummyScoreboard = new Scoreboard();
private static final Constructor<ClientboundSetPlayerTeamPacket> teamConstructor;
static {
try {
teamConstructor = ClientboundSetPlayerTeamPacket.class.getDeclaredConstructor(String.class, int.class, Optional.class, Collection.class);
teamConstructor.setAccessible(true);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
/**
* Constructs new instance with given player.
*
* @param player
* Player this scoreboard will belong to
*/
public NMSPacketScoreboard(@NotNull BukkitTabPlayer player) {
super(player);
}
@Override
public void registerObjective(@NonNull Objective objective) {
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(colors[Math.min(team.getColor().ordinal(), colors.length - 1)]);
t.setCollisionRule(collisions[team.getCollision().ordinal()]);
t.setNameTagVisibility(visibilities[team.getVisibility().ordinal()]);
t.setPlayerPrefix(team.getPrefix().convert());
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) {
Collection<String> players = team.getPlayers();
if (players != null) {
return teamConstructor.newInstance(
team.getName(),
action,
team.getParameters(),
onTeamPacket(action, team.getName(), players)
);
}
}
}
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
*/
protected void sendPacket(@NotNull Packet<?> packet) {
((CraftPlayer)player.getPlayer()).getHandle().connection.send(packet);
}
}
@@ -0,0 +1,205 @@
package me.neznamy.tab.platforms.bukkit.paper_26_2;
import com.google.common.collect.ImmutableMultimap;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
import com.mojang.authlib.properties.PropertyMap;
import lombok.NonNull;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.chat.component.TabComponent;
import me.neznamy.tab.shared.features.layout.LayoutManagerImpl;
import me.neznamy.tab.shared.platform.decorators.TrackedTabList;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket;
import net.minecraft.network.protocol.game.ClientboundTabListPacket;
import net.minecraft.world.level.GameType;
import org.bukkit.craftbukkit.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
/**
* TabList implementation using direct mojang-mapped code.
*/
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);
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateLatency = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY);
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateGameMode = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE);
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateListed = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED);
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateListOrder = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LIST_ORDER);
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateHat = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_HAT);
/**
* Constructs new instance.
*
* @param player
* Player this tablist will belong to
*/
public NMSPacketTabList(@NotNull BukkitTabPlayer player) {
super(player);
}
@Override
public void removeEntry(@NonNull UUID entry) {
sendPacket(new ClientboundPlayerInfoRemovePacket(Collections.singletonList(entry)));
}
@Override
public void updateDisplayName0(@NonNull UUID entry, @Nullable TabComponent displayName) {
sendPacket(updateDisplayName, entry, "", null, false, 0, 0, displayName, 0, false);
}
@Override
public void updateLatency(@NonNull UUID entry, int latency) {
sendPacket(updateLatency, entry, "", null, false, latency, 0, null, 0, false);
}
@Override
public void updateGameMode(@NonNull UUID entry, int gameMode) {
sendPacket(updateGameMode, entry, "", null, false, 0, gameMode, null, 0, false);
}
@Override
public void updateListed(@NonNull UUID entry, boolean listed) {
sendPacket(updateListed, entry, "", null, listed, 0, 0, null, 0, false);
}
@Override
public void updateListOrder(@NonNull UUID entry, int listOrder) {
sendPacket(updateListOrder, entry, "", null, false, 0, 0, null, listOrder, false);
}
@Override
public void updateHat(@NonNull UUID entry, boolean showHat) {
sendPacket(updateHat, entry, "", null, false, 0, 0, null, 0, showHat);
}
@Override
public void addEntry0(@NonNull Entry entry) {
sendPacket(addPlayer, entry.getUniqueId(), entry.getName(), entry.getSkin(), entry.isListed(), entry.getLatency(),
entry.getGameMode(), entry.getDisplayName(), entry.getListOrder(), entry.isShowHat());
}
@Override
public void setPlayerListHeaderFooter0(@NonNull TabComponent header, @NonNull TabComponent footer) {
sendPacket(new ClientboundTabListPacket(header.convert(), footer.convert()));
}
@Override
@Nullable
public Skin getSkin() {
Collection<Property> properties = ((CraftPlayer)player.getPlayer()).getProfile().properties().get(TEXTURES_PROPERTY);
if (properties.isEmpty()) return null; // Offline mode
Property property = properties.iterator().next();
return new Skin(property.value(), property.signature());
}
@Override
@NotNull
public Object onPacketSend(@NonNull Object packet) {
if (packet instanceof ClientboundTabListPacket tablist) {
if (header == null || footer == null) return packet;
if (tablist.header() != header.convert() || tablist.footer() != footer.convert()) {
return new ClientboundTabListPacket(header.convert(), footer.convert());
}
}
if (packet instanceof ClientboundPlayerInfoUpdatePacket info) {
EnumSet<ClientboundPlayerInfoUpdatePacket.Action> actions = info.actions();
List<ClientboundPlayerInfoUpdatePacket.Entry> updatedList = new ArrayList<>();
boolean rewritePacket = false;
for (ClientboundPlayerInfoUpdatePacket.Entry nmsData : info.entries()) {
boolean rewriteEntry = false;
Component displayName = nmsData.displayName();
int latency = nmsData.latency();
int gameMode = nmsData.gameMode().getId();
boolean listed = nmsData.listed();
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME)) {
TabComponent forcedDisplayName = getForcedDisplayNames().get(nmsData.profileId());
if (forcedDisplayName != null && forcedDisplayName.convert() != displayName) {
displayName = forcedDisplayName.convert();
rewriteEntry = rewritePacket = true;
}
}
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE)) {
if (getBlockedSpectators().contains(nmsData.profileId()) && gameMode == 3) {
gameMode = 0;
rewriteEntry = rewritePacket = true;
}
}
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY)) {
if (getForcedLatency() != null) {
latency = getForcedLatency();
rewriteEntry = rewritePacket = true;
}
}
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED)) {
if (allPlayersHidden && !LayoutManagerImpl.UUIDS_SET.contains(nmsData.profileId())) { // Filter out layout entries
listed = false;
rewriteEntry = rewritePacket = true;
}
}
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER)) {
TAB.getInstance().getFeatureManager().onEntryAdd(player, nmsData.profileId(), nmsData.profile().name());
}
updatedList.add(rewriteEntry ? new ClientboundPlayerInfoUpdatePacket.Entry(
nmsData.profileId(), nmsData.profile(), listed, latency, GameType.byId(gameMode), displayName,
nmsData.showHat(), nmsData.listOrder(), nmsData.chatSession()
) : nmsData);
}
if (rewritePacket) return new ClientboundPlayerInfoUpdatePacket(actions, updatedList);
}
return packet;
}
private void sendPacket(@NonNull EnumSet<ClientboundPlayerInfoUpdatePacket.Action> action, @NonNull UUID id, @NonNull String name, @Nullable Skin skin,
boolean listed, int latency, int gameMode, @Nullable TabComponent displayName, int listOrder, boolean showHat) {
ClientboundPlayerInfoUpdatePacket packet = new ClientboundPlayerInfoUpdatePacket(action, new ClientboundPlayerInfoUpdatePacket.Entry(
id,
action.contains(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER) ? createProfile(id, name, skin) : null,
listed,
latency,
GameType.byId(gameMode),
displayName == null ? null : displayName.convert(),
showHat,
listOrder,
null
));
sendPacket(packet);
}
/**
* Creates GameProfile from given parameters.
*
* @param id
* Profile ID
* @param name
* Profile name
* @param skin
* Player skin
* @return GameProfile from given parameters
*/
@NotNull
private GameProfile createProfile(@NonNull UUID id, @NonNull String name, @Nullable Skin skin) {
ImmutableMultimap.Builder<String, Property> builder = ImmutableMultimap.builder();
if (skin != null) {
builder.put(TEXTURES_PROPERTY, new Property(TEXTURES_PROPERTY, skin.getValue(), skin.getSignature()));
}
return new GameProfile(id, name, new PropertyMap(builder.build()));
}
/**
* Sends the packet to the player.
*
* @param packet
* Packet to send
*/
private void sendPacket(@NotNull Packet<?> packet) {
((CraftPlayer)player.getPlayer()).getHandle().connection.send(packet);
}
}
@@ -0,0 +1,35 @@
package me.neznamy.tab.platforms.bukkit.paper_26_2;
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,10 +2,12 @@ package me.neznamy.tab.platforms.bukkit;
import me.neznamy.tab.platforms.bukkit.platform.BukkitPlatform;
import me.neznamy.tab.platforms.bukkit.platform.FoliaPlatform;
import me.neznamy.tab.shared.ProjectVariables;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.bukkit.Bukkit;
import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.NotNull;
/**
* Main class for Bukkit.
@@ -19,15 +21,32 @@ public class BukkitTAB extends JavaPlugin {
TAB.create(folia ? new FoliaPlatform(this) : new BukkitPlatform(this));
} catch (IllegalStateException e) {
Bukkit.getConsoleSender().sendMessage("§c[TAB] ================================================================================");
Bukkit.getConsoleSender().sendMessage("§c[TAB] Your server version (" + Bukkit.getBukkitVersion() + ") is not supported.");
Bukkit.getConsoleSender().sendMessage("§c[TAB] This jar only supports 1.7.10, 1.8.8, 1.12.2, 1.16.5, 1.17.1 and 1.18.2 - 26.1.2");
Bukkit.getConsoleSender().sendMessage("§c[TAB] If you just updated to a new Minecraft version, check for TAB updates.");
Bukkit.getConsoleSender().sendMessage("§c[TAB] If you are using an unsupported 1.x version, use an older version of TAB (latest TAB 5.x supports all MC 1.x versions).");
Bukkit.getConsoleSender().sendMessage("§c[TAB] Thrown error message: " + e.getMessage());
if (isSpecialJar()) {
Bukkit.getConsoleSender().sendMessage("§c[TAB] The " + ProjectVariables.PLUGIN_VERSION + " release consists of 2 jars due to Java version problems.");
Bukkit.getConsoleSender().sendMessage("§c[TAB] One jar is for Paper 1.20.5 - 1.21.4, the other jar is for everything else.");
Bukkit.getConsoleSender().sendMessage("§c[TAB] You have installed the jar that explicitly says \"Paper 1.20.5 - 1.21.4\", which does not support your server version (" + Bukkit.getBukkitVersion() + ").");
Bukkit.getConsoleSender().sendMessage("§c[TAB] Use the other jar in the release instead.");
} else {
Bukkit.getConsoleSender().sendMessage("§c[TAB] Your server version (" + Bukkit.getBukkitVersion() + ") is not supported.");
Bukkit.getConsoleSender().sendMessage("§c[TAB] This jar only supports 1.7.10, 1.8.8, 1.12.2, 1.16.5, 1.17.1 and 1.18.2 - 26.2");
Bukkit.getConsoleSender().sendMessage("§c[TAB] If you just updated to a new Minecraft version, check for TAB updates.");
Bukkit.getConsoleSender().sendMessage("§c[TAB] If you are using an unsupported 1.x version, use an older version of TAB (latest TAB 5.x supports all MC 1.x versions).");
Bukkit.getConsoleSender().sendMessage("§c[TAB] Thrown error message: " + e.getMessage());
}
Bukkit.getConsoleSender().sendMessage("§c[TAB] ================================================================================");
}
}
private boolean isSpecialJar() {
return !moduleExists("v1_8_R3") &&
moduleExists("paper_1_20_5") && moduleExists("paper_1_21_2") && moduleExists("paper_1_21_4") &&
!moduleExists("paper_1_21_9");
}
private boolean moduleExists(@NotNull String module) {
return ReflectionUtils.classExists("me.neznamy.tab.platforms.bukkit." + module + ".NMSImplementationProvider");
}
@Override
public void onDisable() {
if (TAB.getInstance() == null) return;
@@ -5,10 +5,12 @@ import me.neznamy.tab.platforms.bukkit.platform.BukkitPlatform;
import me.neznamy.tab.shared.backend.BackendTabPlayer;
import me.neznamy.tab.shared.chat.component.TabComponent;
import org.bukkit.Statistic;
import org.bukkit.attribute.Attribute;
import org.bukkit.entity.Player;
import org.bukkit.metadata.MetadataValue;
import org.bukkit.potion.PotionEffectType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* TabPlayer implementation for Bukkit platform
@@ -16,6 +18,18 @@ import org.jetbrains.annotations.NotNull;
@SuppressWarnings("deprecation")
public class BukkitTabPlayer extends BackendTabPlayer {
/** Below name distance attribute (26.1+) */
@Nullable
private static Attribute BELOW_NAME_DISTANCE;
static {
try {
BELOW_NAME_DISTANCE = (Attribute) Attribute.class.getDeclaredField("BELOW_NAME_DISTANCE").get(null);
} catch (Throwable e) {
// 1.21.11 and lower
}
}
/**
* Constructs new instance with given bukkit player
*
@@ -64,6 +78,13 @@ public class BukkitTabPlayer extends BackendTabPlayer {
return (BukkitPlatform) platform;
}
@Override
public void setBelowNameDistance(double distance) {
if (BELOW_NAME_DISTANCE != null) {
getPlayer().getAttribute(BELOW_NAME_DISTANCE).setBaseValue(distance);
}
}
@Override
public boolean isVanished0() {
for (MetadataValue v : getPlayer().getMetadata("vanished")) {
@@ -95,6 +95,7 @@ public class ServerVersionInfo {
spigotVersions.put(ProtocolVersion.V26_1, "v26_1");
spigotVersions.put(ProtocolVersion.V26_1_1, "v26_1");
spigotVersions.put(ProtocolVersion.V26_1_2, "v26_1");
spigotVersions.put(ProtocolVersion.V26_2, "v26_2");
Map<ProtocolVersion, String> paperVersions = new LinkedHashMap<>();
paperVersions.put(ProtocolVersion.V1_20_5, "paper_1_20_5");
@@ -114,11 +115,12 @@ public class ServerVersionInfo {
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
paperVersions.put(ProtocolVersion.V26_2, "paper_26_2"); // v26_2 works too
if (serverVersion == ProtocolVersion.UNKNOWN) {
throw new IllegalStateException(String.format(
"Unknown server version (%s), cannot find implementation.",
minecraftVersion
"Unknown server version (%s %s), cannot find implementation.",
serverName, minecraftVersion
));
}
String implementation = (serverName.equals("Paper") ? paperVersions : spigotVersions).get(serverVersion);
@@ -155,15 +155,21 @@ public class PerWorldPlayerList extends TabFeature implements Listener, Loadable
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";
return computeWorldGroup(viewer).equals(computeWorldGroup(target));
}
@NotNull
private String computeWorldGroup(@NotNull Player p) {
for (Entry<String, List<String>> group : configuration.getSharedWorlds().entrySet()) {
if (group.getValue() != null) {
if (group.getValue().contains(viewer.getWorld().getName())) viewerWorldGroup = group.getKey();
if (group.getValue().contains(target.getWorld().getName())) targetWorldGroup = group.getKey();
List<String> worlds = group.getValue();
if (worlds == null) continue;
for (String world : worlds) {
if (TAB.getInstance().getDataManager().matchesPattern(p.getWorld().getName(), world)) {
return group.getKey();
}
}
}
return viewerWorldGroup.equals(targetWorldGroup);
return p.getWorld().getName() + "-default"; //preventing unwanted behavior when some group is called exactly like a world
}
@NotNull
@@ -72,7 +72,7 @@ public class BukkitPlatform implements BackendPlatform {
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");
private final boolean placeholderAPI = Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI");
/** Spigot field for tracking TPS, the array is final and only being modified instead of re-instantiated */
private double[] recentTps;
@@ -389,6 +389,11 @@ public class BukkitPlatform implements BackendPlatform {
Bukkit.getScheduler().runTask(plugin, task);
}
@Override
public boolean hasLineOfSight(@NotNull TabPlayer viewer, @NotNull TabPlayer target) {
return ((Player) viewer.getPlayer()).hasLineOfSight((Player) target.getPlayer());
}
/**
* Converts component to string using bukkit RGB format if supported by the server.
* If not, closest legacy color is used instead.
@@ -171,4 +171,9 @@ public class FoliaPlatform extends BukkitPlatform {
if (!getPlugin().isEnabled()) return; // Server shutdown, no one cares anymore, everyone is about to be kicked
globalScheduler_execute.invoke(globalScheduler, getPlugin(), task);
}
@Override
public boolean hasLineOfSight(@NotNull TabPlayer viewer, @NotNull TabPlayer target) {
return true; // Cross-entity line-of-sight checks are not safe from Folia's global scheduler.
}
}
@@ -13,7 +13,6 @@ import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.Collections;
/**
* Scoreboard implementation using direct NMS code.
@@ -137,8 +136,11 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
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));
if (players != null) {
Collection<String> modified = onTeamPacket(action, (String) TeamPacket_NAME.get(packet), players);
players.clear();
players.addAll(modified);
}
}
}
return packet;
@@ -173,23 +173,27 @@ public class NMSPacketTabList extends TrackedTabList<BukkitTabPlayer> {
}
if (rewritePacket) {
PacketPlayOutPlayerInfo newPacket = new PacketPlayOutPlayerInfo(action, Collections.emptyList());
PLAYERS.set(newPacket, updatedList);
List<Object> entries = (List<Object>) PLAYERS.get(newPacket);
entries.clear();
entries.addAll(updatedList);
return newPacket;
}
return packet;
}
@SneakyThrows
@SuppressWarnings("unchecked")
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(
List<Object> entries = (List<Object>) PLAYERS.get(packet);
entries.add(newPlayerInfoData(
packet,
createProfile(id, name, skin),
latency,
EnumGamemode.getById(gameMode),
displayName == null ? null : displayName.convert())
));
);
sendPacket(packet);
}
@@ -12,7 +12,6 @@ import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.Collections;
/**
* Scoreboard implementation using direct NMS code.
@@ -150,8 +149,11 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
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));
if (players != null) {
Collection<String> modified = onTeamPacket(action, (String) TeamPacket_NAME.get(packet), players);
players.clear();
players.addAll(modified);
}
}
}
return packet;
@@ -167,23 +167,27 @@ public class NMSPacketTabList extends TrackedTabList<BukkitTabPlayer> {
}
if (rewritePacket) {
PacketPlayOutPlayerInfo newPacket = new PacketPlayOutPlayerInfo(action, Collections.emptyList());
PLAYERS.set(newPacket, updatedList);
List<Object> entries = (List<Object>) PLAYERS.get(newPacket);
entries.clear();
entries.addAll(updatedList);
return newPacket;
}
return packet;
}
@SneakyThrows
@SuppressWarnings("unchecked")
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(
List<Object> entries = (List<Object>) PLAYERS.get(packet);
entries.add(newPlayerInfoData(
packet,
createProfile(id, name, skin),
latency,
EnumGamemode.getById(gameMode),
displayName == null ? null : displayName.convert())
));
);
sendPacket(packet);
}
@@ -5,7 +5,6 @@ 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.EnumChatFormat;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.PacketPlayOutScoreboardDisplayObjective;
@@ -21,9 +20,9 @@ import net.minecraft.world.scores.criteria.IScoreboardCriteria;
import org.bukkit.craftbukkit.v1_17_R1.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
import java.lang.reflect.Constructor;
import java.util.Collection;
import java.util.Collections;
import java.util.Optional;
/**
* Scoreboard implementation using direct NMS code.
@@ -34,7 +33,16 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
private static final ScoreboardTeamBase.EnumTeamPush[] collisions = ScoreboardTeamBase.EnumTeamPush.values();
private static final Scoreboard dummyScoreboard = new Scoreboard();
private static final Field TeamPacket_PLAYERS = ReflectionUtils.getOnlyField(PacketPlayOutScoreboardTeam.class, Collection.class);
private static final Constructor<PacketPlayOutScoreboardTeam> teamConstructor;
static {
try {
teamConstructor = PacketPlayOutScoreboardTeam.class.getDeclaredConstructor(String.class, int.class, Optional.class, Collection.class);
teamConstructor.setAccessible(true);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
/**
* Constructs new instance with given player.
@@ -158,8 +166,15 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
int action = getMethod((PacketPlayOutScoreboardTeam) packet);
if (action != TeamAction.UPDATE) {
Collection<String> players = ((PacketPlayOutScoreboardTeam) packet).e();
if (players == null) players = Collections.emptyList();
TeamPacket_PLAYERS.set(packet, onTeamPacket(action, ((PacketPlayOutScoreboardTeam)packet).d(), players));
if (players != null) {
String name = ((PacketPlayOutScoreboardTeam) packet).d();
return teamConstructor.newInstance(
name,
action,
((PacketPlayOutScoreboardTeam) packet).f(),
onTeamPacket(action, name, players)
);
}
}
}
return packet;
@@ -4,12 +4,11 @@ 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.chat.component.TabComponent;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.decorators.TrackedTabList;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.minecraft.network.chat.IChatBaseComponent;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.PacketPlayOutPlayerInfo;
@@ -21,7 +20,6 @@ import org.bukkit.craftbukkit.v1_17_R1.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Field;
import java.util.*;
/**
@@ -29,8 +27,6 @@ import java.util.*;
*/
public class NMSPacketTabList extends TrackedTabList<BukkitTabPlayer> {
private static final Field PLAYERS = ReflectionUtils.getOnlyField(PacketPlayOutPlayerInfo.class, List.class);
private static final EnumPlayerInfoAction ADD_PLAYER = EnumPlayerInfoAction.a;
private static final EnumPlayerInfoAction UPDATE_GAME_MODE = EnumPlayerInfoAction.b;
private static final EnumPlayerInfoAction UPDATE_LATENCY = EnumPlayerInfoAction.c;
@@ -158,21 +154,21 @@ public class NMSPacketTabList extends TrackedTabList<BukkitTabPlayer> {
}
if (rewritePacket) {
PacketPlayOutPlayerInfo newPacket = new PacketPlayOutPlayerInfo(action, Collections.emptyList());
PLAYERS.set(newPacket, updatedList);
newPacket.b().clear();
newPacket.b().addAll(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(new PlayerInfoData(
packet.b().add(new PlayerInfoData(
createProfile(id, name, skin),
latency,
EnumGamemode.getById(gameMode),
displayName == null ? null : displayName.convert())
displayName == null ? null : displayName.convert()
));
sendPacket(packet);
}
@@ -5,7 +5,6 @@ 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.EnumChatFormat;
import net.minecraft.network.chat.IChatBaseComponent;
import net.minecraft.network.protocol.Packet;
@@ -22,9 +21,9 @@ import net.minecraft.world.scores.criteria.IScoreboardCriteria;
import org.bukkit.craftbukkit.v1_18_R2.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
import java.lang.reflect.Constructor;
import java.util.Collection;
import java.util.Collections;
import java.util.Optional;
/**
* Scoreboard implementation using direct NMS code.
@@ -35,7 +34,16 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
private static final ScoreboardTeamBase.EnumTeamPush[] collisions = ScoreboardTeamBase.EnumTeamPush.values();
private static final Scoreboard dummyScoreboard = new Scoreboard();
private static final Field TeamPacket_PLAYERS = ReflectionUtils.getOnlyField(PacketPlayOutScoreboardTeam.class, Collection.class);
private static final Constructor<PacketPlayOutScoreboardTeam> teamConstructor;
static {
try {
teamConstructor = PacketPlayOutScoreboardTeam.class.getDeclaredConstructor(String.class, int.class, Optional.class, Collection.class);
teamConstructor.setAccessible(true);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
/**
* Constructs new instance with given player.
@@ -159,8 +167,15 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
int action = getMethod((PacketPlayOutScoreboardTeam) packet);
if (action != TeamAction.UPDATE) {
Collection<String> players = ((PacketPlayOutScoreboardTeam) packet).e();
if (players == null) players = Collections.emptyList();
TeamPacket_PLAYERS.set(packet, onTeamPacket(action, ((PacketPlayOutScoreboardTeam)packet).d(), players));
if (players != null) {
String name = ((PacketPlayOutScoreboardTeam) packet).d();
return teamConstructor.newInstance(
name,
action,
((PacketPlayOutScoreboardTeam) packet).f(),
onTeamPacket(action, name, players)
);
}
}
}
return packet;
@@ -4,12 +4,11 @@ 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.chat.component.TabComponent;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.decorators.TrackedTabList;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.minecraft.network.chat.IChatBaseComponent;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.PacketPlayOutPlayerInfo;
@@ -21,7 +20,6 @@ import org.bukkit.craftbukkit.v1_18_R2.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Field;
import java.util.*;
/**
@@ -29,8 +27,6 @@ import java.util.*;
*/
public class NMSPacketTabList extends TrackedTabList<BukkitTabPlayer> {
private static final Field PLAYERS = ReflectionUtils.getOnlyField(PacketPlayOutPlayerInfo.class, List.class);
private static final EnumPlayerInfoAction ADD_PLAYER = EnumPlayerInfoAction.a;
private static final EnumPlayerInfoAction UPDATE_GAME_MODE = EnumPlayerInfoAction.b;
private static final EnumPlayerInfoAction UPDATE_LATENCY = EnumPlayerInfoAction.c;
@@ -158,21 +154,21 @@ public class NMSPacketTabList extends TrackedTabList<BukkitTabPlayer> {
}
if (rewritePacket) {
PacketPlayOutPlayerInfo newPacket = new PacketPlayOutPlayerInfo(action, Collections.emptyList());
PLAYERS.set(newPacket, updatedList);
newPacket.b().clear();
newPacket.b().addAll(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(new PlayerInfoData(
packet.b().add(new PlayerInfoData(
createProfile(id, name, skin),
latency,
EnumGamemode.a(gameMode),
displayName == null ? null : displayName.convert())
displayName == null ? null : displayName.convert()
));
sendPacket(packet);
}
@@ -5,7 +5,6 @@ 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.EnumChatFormat;
import net.minecraft.network.chat.IChatBaseComponent;
import net.minecraft.network.protocol.Packet;
@@ -22,9 +21,9 @@ import net.minecraft.world.scores.criteria.IScoreboardCriteria;
import org.bukkit.craftbukkit.v1_19_R1.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
import java.lang.reflect.Constructor;
import java.util.Collection;
import java.util.Collections;
import java.util.Optional;
/**
* Scoreboard implementation using direct NMS code.
@@ -35,7 +34,16 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
private static final ScoreboardTeamBase.EnumTeamPush[] collisions = ScoreboardTeamBase.EnumTeamPush.values();
private static final Scoreboard dummyScoreboard = new Scoreboard();
private static final Field TeamPacket_PLAYERS = ReflectionUtils.getOnlyField(PacketPlayOutScoreboardTeam.class, Collection.class);
private static final Constructor<PacketPlayOutScoreboardTeam> teamConstructor;
static {
try {
teamConstructor = PacketPlayOutScoreboardTeam.class.getDeclaredConstructor(String.class, int.class, Optional.class, Collection.class);
teamConstructor.setAccessible(true);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
/**
* Constructs new instance with given player.
@@ -159,8 +167,15 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
int action = getMethod((PacketPlayOutScoreboardTeam) packet);
if (action != TeamAction.UPDATE) {
Collection<String> players = ((PacketPlayOutScoreboardTeam) packet).e();
if (players == null) players = Collections.emptyList();
TeamPacket_PLAYERS.set(packet, onTeamPacket(action, ((PacketPlayOutScoreboardTeam)packet).d(), players));
if (players != null) {
String name = ((PacketPlayOutScoreboardTeam) packet).d();
return teamConstructor.newInstance(
name,
action,
((PacketPlayOutScoreboardTeam) packet).f(),
onTeamPacket(action, name, players)
);
}
}
}
return packet;
@@ -4,12 +4,11 @@ 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.chat.component.TabComponent;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.decorators.TrackedTabList;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.minecraft.network.chat.IChatBaseComponent;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.PacketPlayOutPlayerInfo;
@@ -22,7 +21,6 @@ import org.bukkit.craftbukkit.v1_19_R1.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Field;
import java.util.*;
/**
@@ -30,8 +28,6 @@ import java.util.*;
*/
public class NMSPacketTabList extends TrackedTabList<BukkitTabPlayer> {
private static final Field PLAYERS = ReflectionUtils.getOnlyField(PacketPlayOutPlayerInfo.class, List.class);
private static final EnumPlayerInfoAction ADD_PLAYER = EnumPlayerInfoAction.a;
private static final EnumPlayerInfoAction UPDATE_GAME_MODE = EnumPlayerInfoAction.b;
private static final EnumPlayerInfoAction UPDATE_LATENCY = EnumPlayerInfoAction.c;
@@ -161,23 +157,23 @@ public class NMSPacketTabList extends TrackedTabList<BukkitTabPlayer> {
}
if (rewritePacket) {
PacketPlayOutPlayerInfo newPacket = new PacketPlayOutPlayerInfo(action, Collections.emptyList());
PLAYERS.set(newPacket, updatedList);
newPacket.b().clear();
newPacket.b().addAll(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(new PlayerInfoData(
packet.b().add(new PlayerInfoData(
createProfile(id, name, skin),
latency,
EnumGamemode.a(gameMode),
displayName == null ? null : displayName.convert(),
null
)));
));
sendPacket(packet);
}
@@ -5,7 +5,6 @@ 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.EnumChatFormat;
import net.minecraft.network.chat.IChatBaseComponent;
import net.minecraft.network.protocol.Packet;
@@ -22,9 +21,9 @@ import net.minecraft.world.scores.criteria.IScoreboardCriteria;
import org.bukkit.craftbukkit.v1_19_R2.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
import java.lang.reflect.Constructor;
import java.util.Collection;
import java.util.Collections;
import java.util.Optional;
/**
* Scoreboard implementation using direct NMS code.
@@ -35,8 +34,16 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
private static final ScoreboardTeamBase.EnumTeamPush[] collisions = ScoreboardTeamBase.EnumTeamPush.values();
private static final Scoreboard dummyScoreboard = new Scoreboard();
private static final Field TeamPacket_PLAYERS = ReflectionUtils.getOnlyField(PacketPlayOutScoreboardTeam.class, Collection.class);
private static final Constructor<PacketPlayOutScoreboardTeam> teamConstructor;
static {
try {
teamConstructor = PacketPlayOutScoreboardTeam.class.getDeclaredConstructor(String.class, int.class, Optional.class, Collection.class);
teamConstructor.setAccessible(true);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
/**
* Constructs new instance with given player.
*
@@ -159,8 +166,15 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
int action = getMethod((PacketPlayOutScoreboardTeam) packet);
if (action != TeamAction.UPDATE) {
Collection<String> players = ((PacketPlayOutScoreboardTeam) packet).e();
if (players == null) players = Collections.emptyList();
TeamPacket_PLAYERS.set(packet, onTeamPacket(action, ((PacketPlayOutScoreboardTeam)packet).d(), players));
if (players != null) {
String name = ((PacketPlayOutScoreboardTeam) packet).d();
return teamConstructor.newInstance(
name,
action,
((PacketPlayOutScoreboardTeam) packet).f(),
onTeamPacket(action, name, players)
);
}
}
}
return packet;
@@ -4,9 +4,10 @@ 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.chat.component.TabComponent;
import me.neznamy.tab.shared.features.layout.LayoutManagerImpl;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.decorators.TrackedTabList;
import me.neznamy.tab.shared.util.ReflectionUtils;
@@ -152,7 +153,7 @@ public class NMSPacketTabList extends TrackedTabList<BukkitTabPlayer> {
}
}
if (actions.contains(UPDATE_LISTED)) {
if (allPlayersHidden && nmsData.a().getMostSignificantBits() != 0) { // Filter out layout entries
if (allPlayersHidden && !LayoutManagerImpl.UUIDS_SET.contains(nmsData.a())) { // Filter out layout entries
listed = false;
rewriteEntry = rewritePacket = true;
}
@@ -5,7 +5,6 @@ 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.EnumChatFormat;
import net.minecraft.network.chat.IChatBaseComponent;
import net.minecraft.network.protocol.Packet;
@@ -22,9 +21,9 @@ import net.minecraft.world.scores.criteria.IScoreboardCriteria;
import org.bukkit.craftbukkit.v1_19_R3.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
import java.lang.reflect.Constructor;
import java.util.Collection;
import java.util.Collections;
import java.util.Optional;
/**
* Scoreboard implementation using direct NMS code.
@@ -35,7 +34,16 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
private static final ScoreboardTeamBase.EnumTeamPush[] collisions = ScoreboardTeamBase.EnumTeamPush.values();
private static final Scoreboard dummyScoreboard = new Scoreboard();
private static final Field TeamPacket_PLAYERS = ReflectionUtils.getOnlyField(PacketPlayOutScoreboardTeam.class, Collection.class);
private static final Constructor<PacketPlayOutScoreboardTeam> teamConstructor;
static {
try {
teamConstructor = PacketPlayOutScoreboardTeam.class.getDeclaredConstructor(String.class, int.class, Optional.class, Collection.class);
teamConstructor.setAccessible(true);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
/**
* Constructs new instance with given player.
@@ -159,8 +167,15 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
int action = getMethod((PacketPlayOutScoreboardTeam) packet);
if (action != TeamAction.UPDATE) {
Collection<String> players = ((PacketPlayOutScoreboardTeam) packet).e();
if (players == null) players = Collections.emptyList();
TeamPacket_PLAYERS.set(packet, onTeamPacket(action, ((PacketPlayOutScoreboardTeam)packet).d(), players));
if (players != null) {
String name = ((PacketPlayOutScoreboardTeam) packet).d();
return teamConstructor.newInstance(
name,
action,
((PacketPlayOutScoreboardTeam) packet).f(),
onTeamPacket(action, name, players)
);
}
}
}
return packet;
@@ -4,9 +4,10 @@ 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.chat.component.TabComponent;
import me.neznamy.tab.shared.features.layout.LayoutManagerImpl;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.decorators.TrackedTabList;
import me.neznamy.tab.shared.util.ReflectionUtils;
@@ -152,7 +153,7 @@ public class NMSPacketTabList extends TrackedTabList<BukkitTabPlayer> {
}
}
if (actions.contains(UPDATE_LISTED)) {
if (allPlayersHidden && nmsData.a().getMostSignificantBits() != 0) { // Filter out layout entries
if (allPlayersHidden && !LayoutManagerImpl.UUIDS_SET.contains(nmsData.a())) { // Filter out layout entries
listed = false;
rewriteEntry = rewritePacket = true;
}
@@ -5,7 +5,6 @@ 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.EnumChatFormat;
import net.minecraft.network.chat.IChatBaseComponent;
import net.minecraft.network.protocol.Packet;
@@ -22,9 +21,9 @@ import net.minecraft.world.scores.criteria.IScoreboardCriteria;
import org.bukkit.craftbukkit.v1_20_R1.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
import java.lang.reflect.Constructor;
import java.util.Collection;
import java.util.Collections;
import java.util.Optional;
/**
* Scoreboard implementation using direct NMS code.
@@ -35,7 +34,16 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
private static final ScoreboardTeamBase.EnumTeamPush[] collisions = ScoreboardTeamBase.EnumTeamPush.values();
private static final Scoreboard dummyScoreboard = new Scoreboard();
private static final Field TeamPacket_PLAYERS = ReflectionUtils.getOnlyField(PacketPlayOutScoreboardTeam.class, Collection.class);
private static final Constructor<PacketPlayOutScoreboardTeam> teamConstructor;
static {
try {
teamConstructor = PacketPlayOutScoreboardTeam.class.getDeclaredConstructor(String.class, int.class, Optional.class, Collection.class);
teamConstructor.setAccessible(true);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
/**
* Constructs new instance with given player.
@@ -159,8 +167,15 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
int action = getMethod((PacketPlayOutScoreboardTeam) packet);
if (action != TeamAction.UPDATE) {
Collection<String> players = ((PacketPlayOutScoreboardTeam) packet).e();
if (players == null) players = Collections.emptyList();
TeamPacket_PLAYERS.set(packet, onTeamPacket(action, ((PacketPlayOutScoreboardTeam)packet).d(), players));
if (players != null) {
String name = ((PacketPlayOutScoreboardTeam) packet).d();
return teamConstructor.newInstance(
name,
action,
((PacketPlayOutScoreboardTeam) packet).f(),
onTeamPacket(action, name, players)
);
}
}
}
return packet;
@@ -4,9 +4,10 @@ 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.chat.component.TabComponent;
import me.neznamy.tab.shared.features.layout.LayoutManagerImpl;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.decorators.TrackedTabList;
import me.neznamy.tab.shared.util.ReflectionUtils;
@@ -152,7 +153,7 @@ public class NMSPacketTabList extends TrackedTabList<BukkitTabPlayer> {
}
}
if (actions.contains(UPDATE_LISTED)) {
if (allPlayersHidden && nmsData.a().getMostSignificantBits() != 0) { // Filter out layout entries
if (allPlayersHidden && !LayoutManagerImpl.UUIDS_SET.contains(nmsData.a())) { // Filter out layout entries
listed = false;
rewriteEntry = rewritePacket = true;
}
@@ -5,7 +5,6 @@ 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.EnumChatFormat;
import net.minecraft.network.chat.IChatBaseComponent;
import net.minecraft.network.protocol.Packet;
@@ -14,14 +13,17 @@ import net.minecraft.network.protocol.game.PacketPlayOutScoreboardObjective;
import net.minecraft.network.protocol.game.PacketPlayOutScoreboardScore;
import net.minecraft.network.protocol.game.PacketPlayOutScoreboardTeam;
import net.minecraft.server.ScoreboardServer;
import net.minecraft.world.scores.*;
import net.minecraft.world.scores.Scoreboard;
import net.minecraft.world.scores.ScoreboardObjective;
import net.minecraft.world.scores.ScoreboardTeam;
import net.minecraft.world.scores.ScoreboardTeamBase;
import net.minecraft.world.scores.criteria.IScoreboardCriteria;
import org.bukkit.craftbukkit.v1_20_R2.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
import java.lang.reflect.Constructor;
import java.util.Collection;
import java.util.Collections;
import java.util.Optional;
/**
* Scoreboard implementation using direct NMS code.
@@ -32,7 +34,16 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
private static final ScoreboardTeamBase.EnumTeamPush[] collisions = ScoreboardTeamBase.EnumTeamPush.values();
private static final Scoreboard dummyScoreboard = new Scoreboard();
private static final Field TeamPacket_PLAYERS = ReflectionUtils.getOnlyField(PacketPlayOutScoreboardTeam.class, Collection.class);
private static final Constructor<PacketPlayOutScoreboardTeam> teamConstructor;
static {
try {
teamConstructor = PacketPlayOutScoreboardTeam.class.getDeclaredConstructor(String.class, int.class, Optional.class, Collection.class);
teamConstructor.setAccessible(true);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
/**
* Constructs new instance with given player.
@@ -147,7 +158,16 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
if (packet instanceof PacketPlayOutScoreboardTeam team) {
int action = getMethod(team);
if (action != TeamAction.UPDATE) {
TeamPacket_PLAYERS.set(packet, onTeamPacket(action, team.e(), team.f() == null ? Collections.emptyList() : team.f()));
Collection<String> players = team.f();
if (players != null) {
String name = team.e();
return teamConstructor.newInstance(
name,
action,
team.g(),
onTeamPacket(action, name, players)
);
}
}
}
return packet;
@@ -4,9 +4,10 @@ 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.chat.component.TabComponent;
import me.neznamy.tab.shared.features.layout.LayoutManagerImpl;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.decorators.TrackedTabList;
import me.neznamy.tab.shared.util.ReflectionUtils;
@@ -150,7 +151,7 @@ public class NMSPacketTabList extends TrackedTabList<BukkitTabPlayer> {
}
}
if (actions.contains(UPDATE_LISTED)) {
if (allPlayersHidden && nmsData.a().getMostSignificantBits() != 0) { // Filter out layout entries
if (allPlayersHidden && !LayoutManagerImpl.UUIDS_SET.contains(nmsData.a())) { // Filter out layout entries
listed = false;
rewriteEntry = rewritePacket = true;
}
@@ -5,7 +5,6 @@ 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.EnumChatFormat;
import net.minecraft.network.chat.IChatBaseComponent;
import net.minecraft.network.chat.numbers.FixedFormat;
@@ -19,9 +18,9 @@ import net.minecraft.world.scores.criteria.IScoreboardCriteria;
import org.bukkit.craftbukkit.v1_20_R3.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
import java.lang.reflect.Constructor;
import java.util.Collection;
import java.util.Collections;
import java.util.Optional;
/**
* Scoreboard implementation using direct NMS code.
@@ -32,7 +31,16 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
private static final ScoreboardTeamBase.EnumTeamPush[] collisions = ScoreboardTeamBase.EnumTeamPush.values();
private static final Scoreboard dummyScoreboard = new Scoreboard();
private static final Field TeamPacket_PLAYERS = ReflectionUtils.getOnlyField(PacketPlayOutScoreboardTeam.class, Collection.class);
private static final Constructor<PacketPlayOutScoreboardTeam> teamConstructor;
static {
try {
teamConstructor = PacketPlayOutScoreboardTeam.class.getDeclaredConstructor(String.class, int.class, Optional.class, Collection.class);
teamConstructor.setAccessible(true);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
/**
* Constructs new instance with given player.
@@ -145,7 +153,16 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
if (packet instanceof PacketPlayOutScoreboardTeam team) {
int action = getMethod(team);
if (action != TeamAction.UPDATE) {
TeamPacket_PLAYERS.set(packet, onTeamPacket(action, team.e(), team.f() == null ? Collections.emptyList() : team.f()));
Collection<String> players = team.f();
if (players != null) {
String name = team.e();
return teamConstructor.newInstance(
name,
action,
team.g(),
onTeamPacket(action, name, players)
);
}
}
}
return packet;
@@ -4,9 +4,10 @@ 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.chat.component.TabComponent;
import me.neznamy.tab.shared.features.layout.LayoutManagerImpl;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.decorators.TrackedTabList;
import me.neznamy.tab.shared.util.ReflectionUtils;
@@ -150,7 +151,7 @@ public class NMSPacketTabList extends TrackedTabList<BukkitTabPlayer> {
}
}
if (actions.contains(UPDATE_LISTED)) {
if (allPlayersHidden && nmsData.a().getMostSignificantBits() != 0) { // Filter out layout entries
if (allPlayersHidden && !LayoutManagerImpl.UUIDS_SET.contains(nmsData.a())) { // Filter out layout entries
listed = false;
rewriteEntry = rewritePacket = true;
}
@@ -5,7 +5,6 @@ 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.EnumChatFormat;
import net.minecraft.network.chat.IChatBaseComponent;
import net.minecraft.network.chat.numbers.FixedFormat;
@@ -20,9 +19,8 @@ import net.minecraft.world.scores.criteria.IScoreboardCriteria;
import org.bukkit.craftbukkit.v1_20_R4.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
import java.lang.reflect.Constructor;
import java.util.Collection;
import java.util.Collections;
import java.util.Optional;
/**
@@ -34,7 +32,16 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
private static final ScoreboardTeamBase.EnumTeamPush[] collisions = ScoreboardTeamBase.EnumTeamPush.values();
private static final Scoreboard dummyScoreboard = new Scoreboard();
private static final Field TeamPacket_PLAYERS = ReflectionUtils.getOnlyField(PacketPlayOutScoreboardTeam.class, Collection.class);
private static final Constructor<PacketPlayOutScoreboardTeam> teamConstructor;
static {
try {
teamConstructor = PacketPlayOutScoreboardTeam.class.getDeclaredConstructor(String.class, int.class, Optional.class, Collection.class);
teamConstructor.setAccessible(true);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
/**
* Constructs new instance with given player.
@@ -147,7 +154,16 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
if (packet instanceof PacketPlayOutScoreboardTeam team) {
int action = getMethod(team);
if (action != TeamAction.UPDATE) {
TeamPacket_PLAYERS.set(packet, onTeamPacket(action, team.f(), team.g() == null ? Collections.emptyList() : team.g()));
Collection<String> players = team.g();
if (players != null) {
String name = team.f();
return teamConstructor.newInstance(
name,
action,
team.h(),
onTeamPacket(action, name, players)
);
}
}
}
return packet;
@@ -4,9 +4,10 @@ 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.chat.component.TabComponent;
import me.neznamy.tab.shared.features.layout.LayoutManagerImpl;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.decorators.TrackedTabList;
import me.neznamy.tab.shared.util.ReflectionUtils;
@@ -151,7 +152,7 @@ public class NMSPacketTabList extends TrackedTabList<BukkitTabPlayer> {
}
}
if (actions.contains(UPDATE_LISTED)) {
if (allPlayersHidden && nmsData.a().getMostSignificantBits() != 0) { // Filter out layout entries
if (allPlayersHidden && !LayoutManagerImpl.UUIDS_SET.contains(nmsData.a())) { // Filter out layout entries
listed = false;
rewriteEntry = rewritePacket = true;
}
@@ -5,7 +5,6 @@ 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.EnumChatFormat;
import net.minecraft.network.chat.IChatBaseComponent;
import net.minecraft.network.chat.numbers.FixedFormat;
@@ -20,9 +19,8 @@ import net.minecraft.world.scores.criteria.IScoreboardCriteria;
import org.bukkit.craftbukkit.v1_21_R1.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
import java.lang.reflect.Constructor;
import java.util.Collection;
import java.util.Collections;
import java.util.Optional;
/**
@@ -34,7 +32,16 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
private static final ScoreboardTeamBase.EnumTeamPush[] collisions = ScoreboardTeamBase.EnumTeamPush.values();
private static final Scoreboard dummyScoreboard = new Scoreboard();
private static final Field TeamPacket_PLAYERS = ReflectionUtils.getOnlyField(PacketPlayOutScoreboardTeam.class, Collection.class);
private static final Constructor<PacketPlayOutScoreboardTeam> teamConstructor;
static {
try {
teamConstructor = PacketPlayOutScoreboardTeam.class.getDeclaredConstructor(String.class, int.class, Optional.class, Collection.class);
teamConstructor.setAccessible(true);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
/**
* Constructs new instance with given player.
@@ -147,7 +154,16 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
if (packet instanceof PacketPlayOutScoreboardTeam team) {
int action = getMethod(team);
if (action != TeamAction.UPDATE) {
TeamPacket_PLAYERS.set(packet, onTeamPacket(action, team.f(), team.g() == null ? Collections.emptyList() : team.g()));
Collection<String> players = team.g();
if (players != null) {
String name = team.f();
return teamConstructor.newInstance(
name,
action,
team.h(),
onTeamPacket(action, name, players)
);
}
}
}
return packet;
@@ -4,9 +4,10 @@ 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.chat.component.TabComponent;
import me.neznamy.tab.shared.features.layout.LayoutManagerImpl;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.decorators.TrackedTabList;
import me.neznamy.tab.shared.util.ReflectionUtils;
@@ -151,7 +152,7 @@ public class NMSPacketTabList extends TrackedTabList<BukkitTabPlayer> {
}
}
if (actions.contains(UPDATE_LISTED)) {
if (allPlayersHidden && nmsData.a().getMostSignificantBits() != 0) { // Filter out layout entries
if (allPlayersHidden && !LayoutManagerImpl.UUIDS_SET.contains(nmsData.a())) { // Filter out layout entries
listed = false;
rewriteEntry = rewritePacket = true;
}
@@ -5,7 +5,6 @@ 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.EnumChatFormat;
import net.minecraft.network.chat.IChatBaseComponent;
import net.minecraft.network.chat.numbers.FixedFormat;
@@ -20,9 +19,8 @@ import net.minecraft.world.scores.criteria.IScoreboardCriteria;
import org.bukkit.craftbukkit.v1_21_R2.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
import java.lang.reflect.Constructor;
import java.util.Collection;
import java.util.Collections;
import java.util.Optional;
/**
@@ -34,7 +32,16 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
private static final ScoreboardTeamBase.EnumTeamPush[] collisions = ScoreboardTeamBase.EnumTeamPush.values();
private static final Scoreboard dummyScoreboard = new Scoreboard();
private static final Field TeamPacket_PLAYERS = ReflectionUtils.getOnlyField(PacketPlayOutScoreboardTeam.class, Collection.class);
private static final Constructor<PacketPlayOutScoreboardTeam> teamConstructor;
static {
try {
teamConstructor = PacketPlayOutScoreboardTeam.class.getDeclaredConstructor(String.class, int.class, Optional.class, Collection.class);
teamConstructor.setAccessible(true);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
/**
* Constructs new instance with given player.
@@ -147,7 +154,16 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
if (packet instanceof PacketPlayOutScoreboardTeam team) {
int action = getMethod(team);
if (action != TeamAction.UPDATE) {
TeamPacket_PLAYERS.set(packet, onTeamPacket(action, team.f(), team.g() == null ? Collections.emptyList() : team.g()));
Collection<String> players = team.g();
if (players != null) {
String name = team.f();
return teamConstructor.newInstance(
name,
action,
team.h(),
onTeamPacket(action, name, players)
);
}
}
}
return packet;
@@ -4,9 +4,10 @@ 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.chat.component.TabComponent;
import me.neznamy.tab.shared.features.layout.LayoutManagerImpl;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.decorators.TrackedTabList;
import me.neznamy.tab.shared.util.ReflectionUtils;
@@ -153,7 +154,7 @@ public class NMSPacketTabList extends TrackedTabList<BukkitTabPlayer> {
}
}
if (actions.contains(UPDATE_LISTED)) {
if (allPlayersHidden && nmsData.a().getMostSignificantBits() != 0) { // Filter out layout entries
if (allPlayersHidden && !LayoutManagerImpl.UUIDS_SET.contains(nmsData.a())) { // Filter out layout entries
listed = false;
rewriteEntry = rewritePacket = true;
}
@@ -5,7 +5,6 @@ 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.EnumChatFormat;
import net.minecraft.network.chat.IChatBaseComponent;
import net.minecraft.network.chat.numbers.FixedFormat;
@@ -20,9 +19,8 @@ import net.minecraft.world.scores.criteria.IScoreboardCriteria;
import org.bukkit.craftbukkit.v1_21_R3.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
import java.lang.reflect.Constructor;
import java.util.Collection;
import java.util.Collections;
import java.util.Optional;
/**
@@ -34,7 +32,16 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
private static final ScoreboardTeamBase.EnumTeamPush[] collisions = ScoreboardTeamBase.EnumTeamPush.values();
private static final Scoreboard dummyScoreboard = new Scoreboard();
private static final Field TeamPacket_PLAYERS = ReflectionUtils.getOnlyField(PacketPlayOutScoreboardTeam.class, Collection.class);
private static final Constructor<PacketPlayOutScoreboardTeam> teamConstructor;
static {
try {
teamConstructor = PacketPlayOutScoreboardTeam.class.getDeclaredConstructor(String.class, int.class, Optional.class, Collection.class);
teamConstructor.setAccessible(true);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
/**
* Constructs new instance with given player.
@@ -147,7 +154,16 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
if (packet instanceof PacketPlayOutScoreboardTeam team) {
int action = getMethod(team);
if (action != TeamAction.UPDATE) {
TeamPacket_PLAYERS.set(packet, onTeamPacket(action, team.f(), team.g() == null ? Collections.emptyList() : team.g()));
Collection<String> players = team.g();
if (players != null) {
String name = team.f();
return teamConstructor.newInstance(
name,
action,
team.h(),
onTeamPacket(action, name, players)
);
}
}
}
return packet;
@@ -4,9 +4,10 @@ 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.chat.component.TabComponent;
import me.neznamy.tab.shared.features.layout.LayoutManagerImpl;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.decorators.TrackedTabList;
import me.neznamy.tab.shared.util.ReflectionUtils;
@@ -155,7 +156,7 @@ public class NMSPacketTabList extends TrackedTabList<BukkitTabPlayer> {
}
}
if (actions.contains(UPDATE_LISTED)) {
if (allPlayersHidden && nmsData.a().getMostSignificantBits() != 0) { // Filter out layout entries
if (allPlayersHidden && !LayoutManagerImpl.UUIDS_SET.contains(nmsData.a())) { // Filter out layout entries
listed = false;
rewriteEntry = rewritePacket = true;
}
@@ -5,7 +5,6 @@ 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.EnumChatFormat;
import net.minecraft.network.chat.IChatBaseComponent;
import net.minecraft.network.chat.numbers.FixedFormat;
@@ -20,9 +19,8 @@ import net.minecraft.world.scores.criteria.IScoreboardCriteria;
import org.bukkit.craftbukkit.v1_21_R4.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
import java.lang.reflect.Constructor;
import java.util.Collection;
import java.util.Collections;
import java.util.Optional;
/**
@@ -34,7 +32,16 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
private static final ScoreboardTeamBase.EnumTeamPush[] collisions = ScoreboardTeamBase.EnumTeamPush.values();
private static final Scoreboard dummyScoreboard = new Scoreboard();
private static final Field TeamPacket_PLAYERS = ReflectionUtils.getOnlyField(PacketPlayOutScoreboardTeam.class, Collection.class);
private static final Constructor<PacketPlayOutScoreboardTeam> teamConstructor;
static {
try {
teamConstructor = PacketPlayOutScoreboardTeam.class.getDeclaredConstructor(String.class, int.class, Optional.class, Collection.class);
teamConstructor.setAccessible(true);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
/**
* Constructs new instance with given player.
@@ -147,7 +154,16 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
if (packet instanceof PacketPlayOutScoreboardTeam team) {
int action = getMethod(team);
if (action != TeamAction.UPDATE) {
TeamPacket_PLAYERS.set(packet, onTeamPacket(action, team.f(), team.g() == null ? Collections.emptyList() : team.g()));
Collection<String> players = team.g();
if (players != null) {
String name = team.f();
return teamConstructor.newInstance(
name,
action,
team.h(),
onTeamPacket(action, name, players)
);
}
}
}
return packet;
@@ -4,9 +4,10 @@ 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.chat.component.TabComponent;
import me.neznamy.tab.shared.features.layout.LayoutManagerImpl;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.decorators.TrackedTabList;
import me.neznamy.tab.shared.util.ReflectionUtils;
@@ -155,7 +156,7 @@ public class NMSPacketTabList extends TrackedTabList<BukkitTabPlayer> {
}
}
if (actions.contains(UPDATE_LISTED)) {
if (allPlayersHidden && nmsData.a().getMostSignificantBits() != 0) { // Filter out layout entries
if (allPlayersHidden && !LayoutManagerImpl.UUIDS_SET.contains(nmsData.a())) { // Filter out layout entries
listed = false;
rewriteEntry = rewritePacket = true;
}
@@ -5,7 +5,6 @@ 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.EnumChatFormat;
import net.minecraft.network.chat.IChatBaseComponent;
import net.minecraft.network.chat.numbers.FixedFormat;
@@ -20,9 +19,8 @@ import net.minecraft.world.scores.criteria.IScoreboardCriteria;
import org.bukkit.craftbukkit.v1_21_R5.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
import java.lang.reflect.Constructor;
import java.util.Collection;
import java.util.Collections;
import java.util.Optional;
/**
@@ -34,7 +32,16 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
private static final ScoreboardTeamBase.EnumTeamPush[] collisions = ScoreboardTeamBase.EnumTeamPush.values();
private static final Scoreboard dummyScoreboard = new Scoreboard();
private static final Field TeamPacket_PLAYERS = ReflectionUtils.getOnlyField(PacketPlayOutScoreboardTeam.class, Collection.class);
private static final Constructor<PacketPlayOutScoreboardTeam> teamConstructor;
static {
try {
teamConstructor = PacketPlayOutScoreboardTeam.class.getDeclaredConstructor(String.class, int.class, Optional.class, Collection.class);
teamConstructor.setAccessible(true);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
/**
* Constructs new instance with given player.
@@ -147,7 +154,16 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
if (packet instanceof PacketPlayOutScoreboardTeam team) {
int action = getMethod(team);
if (action != TeamAction.UPDATE) {
TeamPacket_PLAYERS.set(packet, onTeamPacket(action, team.f(), team.g() == null ? Collections.emptyList() : team.g()));
Collection<String> players = team.g();
if (players != null) {
String name = team.f();
return teamConstructor.newInstance(
name,
action,
team.h(),
onTeamPacket(action, name, players)
);
}
}
}
return packet;
@@ -4,9 +4,10 @@ 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.chat.component.TabComponent;
import me.neznamy.tab.shared.features.layout.LayoutManagerImpl;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.decorators.TrackedTabList;
import me.neznamy.tab.shared.util.ReflectionUtils;
@@ -155,7 +156,7 @@ public class NMSPacketTabList extends TrackedTabList<BukkitTabPlayer> {
}
}
if (actions.contains(UPDATE_LISTED)) {
if (allPlayersHidden && nmsData.a().getMostSignificantBits() != 0) { // Filter out layout entries
if (allPlayersHidden && !LayoutManagerImpl.UUIDS_SET.contains(nmsData.a())) { // Filter out layout entries
listed = false;
rewriteEntry = rewritePacket = true;
}
@@ -5,7 +5,6 @@ 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.EnumChatFormat;
import net.minecraft.network.chat.IChatBaseComponent;
import net.minecraft.network.chat.numbers.FixedFormat;
@@ -20,9 +19,8 @@ import net.minecraft.world.scores.criteria.IScoreboardCriteria;
import org.bukkit.craftbukkit.v1_21_R6.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
import java.lang.reflect.Constructor;
import java.util.Collection;
import java.util.Collections;
import java.util.Optional;
/**
@@ -34,7 +32,16 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
private static final ScoreboardTeamBase.EnumTeamPush[] collisions = ScoreboardTeamBase.EnumTeamPush.values();
private static final Scoreboard dummyScoreboard = new Scoreboard();
private static final Field TeamPacket_PLAYERS = ReflectionUtils.getOnlyField(PacketPlayOutScoreboardTeam.class, Collection.class);
private static final Constructor<PacketPlayOutScoreboardTeam> teamConstructor;
static {
try {
teamConstructor = PacketPlayOutScoreboardTeam.class.getDeclaredConstructor(String.class, int.class, Optional.class, Collection.class);
teamConstructor.setAccessible(true);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
/**
* Constructs new instance with given player.
@@ -147,7 +154,16 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
if (packet instanceof PacketPlayOutScoreboardTeam team) {
int action = getMethod(team);
if (action != TeamAction.UPDATE) {
TeamPacket_PLAYERS.set(packet, onTeamPacket(action, team.f(), team.g() == null ? Collections.emptyList() : team.g()));
Collection<String> players = team.g();
if (players != null) {
String name = team.f();
return teamConstructor.newInstance(
name,
action,
team.h(),
onTeamPacket(action, name, players)
);
}
}
}
return packet;
@@ -6,9 +6,10 @@ import com.mojang.authlib.properties.Property;
import com.mojang.authlib.properties.PropertyMap;
import lombok.NonNull;
import lombok.SneakyThrows;
import me.neznamy.tab.shared.chat.component.TabComponent;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.chat.component.TabComponent;
import me.neznamy.tab.shared.features.layout.LayoutManagerImpl;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.decorators.TrackedTabList;
import me.neznamy.tab.shared.util.ReflectionUtils;
@@ -157,7 +158,7 @@ public class NMSPacketTabList extends TrackedTabList<BukkitTabPlayer> {
}
}
if (actions.contains(UPDATE_LISTED)) {
if (allPlayersHidden && nmsData.a().getMostSignificantBits() != 0) { // Filter out layout entries
if (allPlayersHidden && !LayoutManagerImpl.UUIDS_SET.contains(nmsData.a())) { // Filter out layout entries
listed = false;
rewriteEntry = rewritePacket = true;
}
@@ -5,7 +5,6 @@ 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.EnumChatFormat;
import net.minecraft.network.chat.IChatBaseComponent;
import net.minecraft.network.chat.numbers.FixedFormat;
@@ -20,9 +19,8 @@ import net.minecraft.world.scores.criteria.IScoreboardCriteria;
import org.bukkit.craftbukkit.v1_21_R7.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
import java.lang.reflect.Constructor;
import java.util.Collection;
import java.util.Collections;
import java.util.Optional;
/**
@@ -34,7 +32,16 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
private static final ScoreboardTeamBase.EnumTeamPush[] collisions = ScoreboardTeamBase.EnumTeamPush.values();
private static final Scoreboard dummyScoreboard = new Scoreboard();
private static final Field TeamPacket_PLAYERS = ReflectionUtils.getOnlyField(PacketPlayOutScoreboardTeam.class, Collection.class);
private static final Constructor<PacketPlayOutScoreboardTeam> teamConstructor;
static {
try {
teamConstructor = PacketPlayOutScoreboardTeam.class.getDeclaredConstructor(String.class, int.class, Optional.class, Collection.class);
teamConstructor.setAccessible(true);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
/**
* Constructs new instance with given player.
@@ -147,7 +154,16 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
if (packet instanceof PacketPlayOutScoreboardTeam team) {
int action = getMethod(team);
if (action != TeamAction.UPDATE) {
TeamPacket_PLAYERS.set(packet, onTeamPacket(action, team.f(), team.g() == null ? Collections.emptyList() : team.g()));
Collection<String> players = team.g();
if (players != null) {
String name = team.f();
return teamConstructor.newInstance(
name,
action,
team.h(),
onTeamPacket(action, name, players)
);
}
}
}
return packet;
@@ -6,9 +6,10 @@ import com.mojang.authlib.properties.Property;
import com.mojang.authlib.properties.PropertyMap;
import lombok.NonNull;
import lombok.SneakyThrows;
import me.neznamy.tab.shared.chat.component.TabComponent;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.chat.component.TabComponent;
import me.neznamy.tab.shared.features.layout.LayoutManagerImpl;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.decorators.TrackedTabList;
import me.neznamy.tab.shared.util.ReflectionUtils;
@@ -157,7 +158,7 @@ public class NMSPacketTabList extends TrackedTabList<BukkitTabPlayer> {
}
}
if (actions.contains(UPDATE_LISTED)) {
if (allPlayersHidden && nmsData.a().getMostSignificantBits() != 0) { // Filter out layout entries
if (allPlayersHidden && !LayoutManagerImpl.UUIDS_SET.contains(nmsData.a())) { // Filter out layout entries
listed = false;
rewriteEntry = rewritePacket = true;
}
@@ -13,7 +13,6 @@ import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.Collections;
/**
* Scoreboard implementation using direct NMS code.
@@ -135,8 +134,11 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
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));
if (players != null) {
Collection<String> modified = onTeamPacket(action, (String) TeamPacket_NAME.get(packet), players);
players.clear();
players.addAll(modified);
}
}
}
return packet;
@@ -162,22 +162,26 @@ public class NMSPacketTabList extends TrackedTabList<BukkitTabPlayer> {
}
if (rewritePacket) {
PacketPlayOutPlayerInfo newPacket = new PacketPlayOutPlayerInfo(action, Collections.emptyList());
PLAYERS.set(newPacket, updatedList);
List<PlayerInfoData> entries = (List<PlayerInfoData>) PLAYERS.get(newPacket);
entries.clear();
entries.addAll(updatedList);
return newPacket;
}
return packet;
}
@SneakyThrows
@SuppressWarnings("unchecked")
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(packet.new PlayerInfoData(
List<PlayerInfoData> entries = (List<PlayerInfoData>) PLAYERS.get(packet);
entries.add(packet.new PlayerInfoData(
createProfile(id, name, skin),
latency,
EnumGamemode.getById(gameMode),
displayName == null ? null : displayName.convert())
));
);
sendPacket(packet);
}
@@ -19,7 +19,7 @@ import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
/**
* Implementation provider using direct NMS code for 26.1.
* Implementation provider using direct NMS code for 26.1.x.
*/
@Getter
public class NMSImplementationProvider implements ImplementationProvider {
@@ -5,7 +5,6 @@ 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;
@@ -17,9 +16,8 @@ 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.lang.reflect.Constructor;
import java.util.Collection;
import java.util.Collections;
import java.util.Optional;
/**
@@ -31,7 +29,16 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
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);
private static final Constructor<ClientboundSetPlayerTeamPacket> teamConstructor;
static {
try {
teamConstructor = ClientboundSetPlayerTeamPacket.class.getDeclaredConstructor(String.class, int.class, Optional.class, Collection.class);
teamConstructor.setAccessible(true);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
/**
* Constructs new instance with given player.
@@ -144,7 +151,15 @@ public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
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()));
Collection<String> players = team.getPlayers();
if (players != null) {
return teamConstructor.newInstance(
team.getName(),
action,
team.getParameters(),
onTeamPacket(action, team.getName(), players)
);
}
}
}
return packet;
@@ -9,6 +9,7 @@ import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.chat.component.TabComponent;
import me.neznamy.tab.shared.features.layout.LayoutManagerImpl;
import me.neznamy.tab.shared.platform.decorators.TrackedTabList;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.minecraft.network.chat.Component;
@@ -144,7 +145,7 @@ public class NMSPacketTabList extends TrackedTabList<BukkitTabPlayer> {
}
}
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED)) {
if (allPlayersHidden && nmsData.profileId().getMostSignificantBits() != 0) { // Filter out layout entries
if (allPlayersHidden && !LayoutManagerImpl.UUIDS_SET.contains(nmsData.profileId())) { // Filter out layout entries
listed = false;
rewriteEntry = rewritePacket = true;
}
+8
View File
@@ -0,0 +1,8 @@
dependencies {
implementation(projects.bukkit)
compileOnly("org.spigotmc:spigot:26.2-R0.1-SNAPSHOT")
}
tasks.compileJava {
options.release.set(21) // Included library "jtracy" requires Java 21
}
@@ -0,0 +1,84 @@
package me.neznamy.tab.platforms.bukkit.v26_2;
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);
}
}
@@ -0,0 +1,62 @@
package me.neznamy.tab.platforms.bukkit.v26_2;
import io.netty.channel.Channel;
import lombok.Getter;
import lombok.SneakyThrows;
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 me.neznamy.tab.shared.util.ReflectionUtils;
import net.minecraft.network.Connection;
import net.minecraft.server.network.ServerCommonPacketListenerImpl;
import org.bukkit.craftbukkit.entity.CraftPlayer;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
/**
* Implementation provider using direct NMS code for 26.2.
*/
@Getter
public class NMSImplementationProvider implements ImplementationProvider {
/** Field is somehow private */
private static final Field networkManager = ReflectionUtils.getOnlyField(ServerCommonPacketListenerImpl.class, Connection.class);
@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
@SneakyThrows
public Channel getChannel(@NotNull Player player) {
return ((Connection)networkManager.get(((CraftPlayer)player).getHandle().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();
}
}
@@ -0,0 +1,192 @@
package me.neznamy.tab.platforms.bukkit.v26_2;
import lombok.NonNull;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.platform.decorators.SafeScoreboard;
import net.minecraft.network.chat.numbers.FixedFormat;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.*;
import net.minecraft.world.scores.PlayerTeam;
import net.minecraft.world.scores.Scoreboard;
import net.minecraft.world.scores.TeamColor;
import net.minecraft.world.scores.criteria.ObjectiveCriteria;
import net.minecraft.world.scores.criteria.ObjectiveCriteria.RenderType;
import org.bukkit.craftbukkit.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Constructor;
import java.util.Arrays;
import java.util.Collection;
import java.util.Optional;
/**
* Scoreboard implementation using direct mojang-mapped code.
*/
public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
private static final Optional<TeamColor>[] colors = Arrays.stream(TeamColor.values()).map(Optional::of).toArray(Optional[]::new);
private static final net.minecraft.world.scores.Team.CollisionRule[] collisions = net.minecraft.world.scores.Team.CollisionRule.values();
private static final net.minecraft.world.scores.Team.Visibility[] visibilities = net.minecraft.world.scores.Team.Visibility.values();
private static final Scoreboard dummyScoreboard = new Scoreboard();
private static final Constructor<ClientboundSetPlayerTeamPacket> teamConstructor;
static {
try {
teamConstructor = ClientboundSetPlayerTeamPacket.class.getDeclaredConstructor(String.class, int.class, Optional.class, Collection.class);
teamConstructor.setAccessible(true);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
/**
* Constructs new instance with given player.
*
* @param player
* Player this scoreboard will belong to
*/
public NMSPacketScoreboard(@NotNull BukkitTabPlayer player) {
super(player);
}
@Override
public void registerObjective(@NonNull Objective objective) {
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(colors[Math.min(team.getColor().ordinal(), colors.length - 1)]);
t.setCollisionRule(collisions[team.getCollision().ordinal()]);
t.setNameTagVisibility(visibilities[team.getVisibility().ordinal()]);
t.setPlayerPrefix(team.getPrefix().convert());
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) {
Collection<String> players = team.getPlayers();
if (players != null) {
return teamConstructor.newInstance(
team.getName(),
action,
team.getParameters(),
onTeamPacket(action, team.getName(), players)
);
}
}
}
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);
}
}
@@ -0,0 +1,217 @@
package me.neznamy.tab.platforms.bukkit.v26_2;
import com.google.common.collect.ImmutableMultimap;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
import com.mojang.authlib.properties.PropertyMap;
import lombok.NonNull;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.chat.component.TabComponent;
import me.neznamy.tab.shared.features.layout.LayoutManagerImpl;
import me.neznamy.tab.shared.platform.decorators.TrackedTabList;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket;
import net.minecraft.network.protocol.game.ClientboundTabListPacket;
import net.minecraft.world.level.GameType;
import org.bukkit.craftbukkit.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Field;
import java.util.*;
/**
* TabList implementation using direct mojang-mapped code.
*/
public class NMSPacketTabList extends TrackedTabList<BukkitTabPlayer> {
private static final Field PLAYERS = ReflectionUtils.getOnlyField(ClientboundPlayerInfoUpdatePacket.class, List.class);
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> addPlayer = EnumSet.allOf(ClientboundPlayerInfoUpdatePacket.Action.class);
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateDisplayName = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME);
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateLatency = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY);
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateGameMode = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE);
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateListed = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED);
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateListOrder = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LIST_ORDER);
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateHat = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_HAT);
/**
* Constructs new instance.
*
* @param player
* Player this tablist will belong to
*/
public NMSPacketTabList(@NotNull BukkitTabPlayer player) {
super(player);
}
@Override
public void removeEntry(@NonNull UUID entry) {
sendPacket(new ClientboundPlayerInfoRemovePacket(Collections.singletonList(entry)));
}
@Override
public void updateDisplayName0(@NonNull UUID entry, @Nullable TabComponent displayName) {
sendPacket(updateDisplayName, entry, "", null, false, 0, 0, displayName, 0, false);
}
@Override
public void updateLatency(@NonNull UUID entry, int latency) {
sendPacket(updateLatency, entry, "", null, false, latency, 0, null, 0, false);
}
@Override
public void updateGameMode(@NonNull UUID entry, int gameMode) {
sendPacket(updateGameMode, entry, "", null, false, 0, gameMode, null, 0, false);
}
@Override
public void updateListed(@NonNull UUID entry, boolean listed) {
sendPacket(updateListed, entry, "", null, listed, 0, 0, null, 0, false);
}
@Override
public void updateListOrder(@NonNull UUID entry, int listOrder) {
sendPacket(updateListOrder, entry, "", null, false, 0, 0, null, listOrder, false);
}
@Override
public void updateHat(@NonNull UUID entry, boolean showHat) {
sendPacket(updateHat, entry, "", null, false, 0, 0, null, 0, showHat);
}
@Override
public void addEntry0(@NonNull Entry entry) {
sendPacket(addPlayer, entry.getUniqueId(), entry.getName(), entry.getSkin(), entry.isListed(), entry.getLatency(),
entry.getGameMode(), entry.getDisplayName(), entry.getListOrder(), entry.isShowHat());
}
@Override
public void setPlayerListHeaderFooter0(@NonNull TabComponent header, @NonNull TabComponent footer) {
sendPacket(new ClientboundTabListPacket(header.convert(), footer.convert()));
}
@Override
@Nullable
public Skin getSkin() {
Collection<Property> properties = ((CraftPlayer)player.getPlayer()).getProfile().properties().get(TEXTURES_PROPERTY);
if (properties.isEmpty()) return null; // Offline mode
Property property = properties.iterator().next();
return new Skin(property.value(), property.signature());
}
@Override
@NotNull
@SneakyThrows
public Object onPacketSend(@NonNull Object packet) {
if (packet instanceof ClientboundTabListPacket tablist) {
if (header == null || footer == null) return packet;
if (tablist.header() != header.convert() || tablist.footer() != footer.convert()) {
return new ClientboundTabListPacket(header.convert(), footer.convert());
}
}
if (packet instanceof ClientboundPlayerInfoUpdatePacket info) {
EnumSet<ClientboundPlayerInfoUpdatePacket.Action> actions = info.actions();
List<ClientboundPlayerInfoUpdatePacket.Entry> updatedList = new ArrayList<>();
boolean rewritePacket = false;
for (ClientboundPlayerInfoUpdatePacket.Entry nmsData : info.entries()) {
boolean rewriteEntry = false;
Component displayName = nmsData.displayName();
int latency = nmsData.latency();
int gameMode = nmsData.gameMode().getId();
boolean listed = nmsData.listed();
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME)) {
TabComponent forcedDisplayName = getForcedDisplayNames().get(nmsData.profileId());
if (forcedDisplayName != null && forcedDisplayName.convert() != displayName) {
displayName = forcedDisplayName.convert();
rewriteEntry = rewritePacket = true;
}
}
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE)) {
if (getBlockedSpectators().contains(nmsData.profileId()) && gameMode == 3) {
gameMode = 0;
rewriteEntry = rewritePacket = true;
}
}
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY)) {
if (getForcedLatency() != null) {
latency = getForcedLatency();
rewriteEntry = rewritePacket = true;
}
}
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED)) {
if (allPlayersHidden && !LayoutManagerImpl.UUIDS_SET.contains(nmsData.profileId())) { // Filter out layout entries
listed = false;
rewriteEntry = rewritePacket = true;
}
}
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER)) {
TAB.getInstance().getFeatureManager().onEntryAdd(player, nmsData.profileId(), nmsData.profile().name());
}
updatedList.add(rewriteEntry ? new ClientboundPlayerInfoUpdatePacket.Entry(
nmsData.profileId(), nmsData.profile(), listed, latency, GameType.byId(gameMode), displayName,
nmsData.showHat(), nmsData.listOrder(), nmsData.chatSession()
) : nmsData);
}
if (rewritePacket) {
ClientboundPlayerInfoUpdatePacket newPacket = new ClientboundPlayerInfoUpdatePacket(actions, Collections.emptyList());
PLAYERS.set(newPacket, updatedList);
return newPacket;
}
}
return packet;
}
@SneakyThrows
private void sendPacket(@NonNull EnumSet<ClientboundPlayerInfoUpdatePacket.Action> action, @NonNull UUID id, @NonNull String name, @Nullable Skin skin,
boolean listed, int latency, int gameMode, @Nullable TabComponent displayName, int listOrder, boolean showHat) {
ClientboundPlayerInfoUpdatePacket packet = new ClientboundPlayerInfoUpdatePacket(action, Collections.emptyList());
PLAYERS.set(packet, Collections.singletonList(new ClientboundPlayerInfoUpdatePacket.Entry(
id,
action.contains(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER) ? createProfile(id, name, skin) : null,
listed,
latency,
GameType.byId(gameMode),
displayName == null ? null : displayName.convert(),
showHat,
listOrder,
null
)));
sendPacket(packet);
}
/**
* Creates GameProfile from given parameters.
*
* @param id
* Profile ID
* @param name
* Profile name
* @param skin
* Player skin
* @return GameProfile from given parameters
*/
@NotNull
private GameProfile createProfile(@NonNull UUID id, @NonNull String name, @Nullable Skin skin) {
ImmutableMultimap.Builder<String, Property> builder = ImmutableMultimap.builder();
if (skin != null) {
builder.put(TEXTURES_PROPERTY, new Property(TEXTURES_PROPERTY, skin.getValue(), skin.getSignature()));
}
return new GameProfile(id, name, new PropertyMap(builder.build()));
}
/**
* Sends the packet to the player.
*
* @param packet
* Packet to send
*/
private void sendPacket(@NotNull Packet<?> packet) {
((CraftPlayer)player.getPlayer()).getHandle().connection.send(packet);
}
}
@@ -0,0 +1,35 @@
package me.neznamy.tab.platforms.bukkit.v26_2;
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,11 +2,10 @@ package me.neznamy.tab.platforms.bungeecord;
import com.google.common.collect.Lists;
import lombok.NonNull;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.chat.component.TabComponent;
import me.neznamy.tab.shared.Limitations;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.chat.component.TabComponent;
import me.neznamy.tab.shared.platform.decorators.SafeScoreboard;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.protocol.data.NumberFormat;
@@ -19,7 +18,6 @@ import net.md_5.bungee.protocol.util.Either;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
@@ -31,9 +29,6 @@ import java.util.Optional;
*/
public class BungeeScoreboard extends SafeScoreboard<BungeeTabPlayer> {
private static final Optional<Integer>[] colors = Arrays.stream(EnumChatFormat.values()).map(e -> Optional.of(Math.min(e.ordinal(), 15))).toArray(Optional[]::new);
private static final Optional<Integer> optionalZero = Optional.of(0);
/**
* Constructs new instance with given parameter
*
@@ -108,7 +103,7 @@ public class BungeeScoreboard extends SafeScoreboard<BungeeTabPlayer> {
@Override
public void unregisterTeam(@NonNull Team team) {
sendTeamPacket(team, (byte) TeamAction.REMOVE);
player.sendPacket(new net.md_5.bungee.protocol.packet.Team(team.getName()));
}
@Override
@@ -117,6 +112,14 @@ public class BungeeScoreboard extends SafeScoreboard<BungeeTabPlayer> {
}
private void sendTeamPacket(@NonNull Team team, byte action) {
Optional<Integer> color;
if (player.getVersion().getNetworkId() >= ProtocolVersion.V26_2.getNetworkId()) {
color = Optional.of(Math.min(team.getColor().ordinal(), 15));
} else if (player.getVersion().getNetworkId() >= ProtocolVersion.V1_13.getNetworkId()) {
color = Optional.of(team.getColor().ordinal());
} else {
color = Optional.of(0);
}
player.sendPacket(new net.md_5.bungee.protocol.packet.Team(
team.getName(),
action,
@@ -125,7 +128,7 @@ public class BungeeScoreboard extends SafeScoreboard<BungeeTabPlayer> {
either(team.getSuffix(), Limitations.TEAM_PREFIX_SUFFIX_PRE_1_13),
convertVisibility(team.getVisibility()),
convertCollision(team.getCollision()),
player.getVersion().getNetworkId() >= ProtocolVersion.V1_13.getNetworkId() ? colors[team.getColor().ordinal()] : optionalZero,
color,
(byte) team.getOptions(),
team.getPlayers().toArray(new String[0])
));
@@ -5,6 +5,7 @@ import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bungeecord.BungeeTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.chat.component.TabComponent;
import me.neznamy.tab.shared.features.layout.LayoutManagerImpl;
import me.neznamy.tab.shared.platform.decorators.TrackedTabList;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.md_5.bungee.UserConnection;
@@ -162,7 +163,7 @@ public abstract class BungeeTabList extends TrackedTabList<BungeeTabPlayer> {
}
}
if (update.getActions().contains(PlayerListItemUpdate.Action.UPDATE_LISTED)) {
if (allPlayersHidden && item.getUuid().getMostSignificantBits() != 0) { // Filter out layout entries
if (allPlayersHidden && !LayoutManagerImpl.UUIDS_SET.contains(item.getUuid())) { // Filter out layout entries
item.setListed(false);
}
}
+3 -3
View File
@@ -14,13 +14,13 @@ repositories {
maven("https://repo.papermc.io/repository/maven-public/") // Adventure
}
val minecraftVersion = "26.2-pre-1"
val minecraftVersion = "26.2"
// Fabric API versions for each Minecraft version for easier backporting
// Official website (for updating in the future): https://fabricmc.net/develop/
val fabricApiVersions = mapOf(
"26.2-pre-1" to "0.149.2+26.2",
"26.1.2" to "0.146.1+26.1.2"
"26.2" to "0.150.3+26.2",
"26.1.2" to "0.150.0+26.1.2"
)
dependencies {
@@ -273,6 +273,16 @@ public record FabricPlatform(MinecraftServer server) implements BackendPlatform
return (float) server.getAverageTickTimeNanos() / 1000000;
}
@Override
public void runSyncGlobal(@NotNull Runnable task) {
server.execute(task);
}
@Override
public boolean hasLineOfSight(@NotNull TabPlayer viewer, @NotNull TabPlayer target) {
return ((FabricTabPlayer) viewer).getPlayer().hasLineOfSight(((FabricTabPlayer) target).getPlayer());
}
@Override
@NotNull
public Object dump() {
@@ -7,6 +7,7 @@ import com.mojang.authlib.properties.PropertyMap;
import lombok.NonNull;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.chat.component.TabComponent;
import me.neznamy.tab.shared.features.layout.LayoutManagerImpl;
import me.neznamy.tab.shared.platform.decorators.TrackedTabList;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.Packet;
@@ -136,7 +137,7 @@ public class FabricTabList extends TrackedTabList<FabricTabPlayer> {
}
}
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED)) {
if (allPlayersHidden && nmsData.profileId().getMostSignificantBits() != 0) { // Filter out layout entries
if (allPlayersHidden && !LayoutManagerImpl.UUIDS_SET.contains(nmsData.profileId())) { // Filter out layout entries
listed = false;
rewriteEntry = rewritePacket = true;
}
Loaded 100 of 192 files, more files were not shown because too many files have changed in this diff. Show more