Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7e7d0025fa | ||
|
|
8252b6486e | ||
|
|
378041cb16 | ||
|
|
ee5ff07565 | ||
|
|
80c63dd414 | ||
|
|
e182366566 | ||
|
|
acee89cce3 | ||
|
|
3ef3d14c10 | ||
|
|
74365780a7 | ||
|
|
f6ad80370f | ||
|
|
87eb544936 | ||
|
|
cde0389380 | ||
|
|
9e46ffc4e1 | ||
|
|
f3c35de780 | ||
|
|
75f5378c8c | ||
|
|
19d32127e8 | ||
|
|
2f00aa7750 | ||
|
|
07f5dc68b5 | ||
|
|
96a39d7037 | ||
|
|
409f1c4230 | ||
|
|
c3d2f9aeea | ||
|
|
380b3dd1f6 | ||
|
|
c15a6009a2 | ||
|
|
e4e7c364fd | ||
|
|
438361eed2 | ||
|
|
684b35f2df | ||
|
|
1d603ae76f | ||
|
|
a342b23890 | ||
|
|
c0dde2eaa7 | ||
|
|
6245fe9553 | ||
|
|
fb5be9e2aa | ||
|
|
918620a7dc | ||
|
|
d2855449f0 | ||
|
|
f59326c0ae | ||
|
|
033b9ed035 | ||
|
|
5c299767ec | ||
|
|
818701cd07 | ||
|
|
9b5e63aa25 | ||
|
|
1ad285bd79 | ||
|
|
4507bb179a | ||
|
|
2dd79295a9 | ||
|
|
b21d840e60 | ||
|
|
f3fa40bb0a | ||
|
|
d8e14de0cf | ||
|
|
f7b96eceeb | ||
|
|
6f760939f3 | ||
|
|
5273ad0c5f | ||
|
|
ac12abfb8b | ||
|
|
e5146e7f53 | ||
|
|
62d1210872 | ||
|
|
0d0177cd40 | ||
|
|
227ecf9c9d | ||
|
|
562cd50762 | ||
|
|
ec56d530a6 | ||
|
|
6350714508 | ||
|
|
1b251dad6a | ||
|
|
7765b51a55 | ||
|
|
ff6ba6a8e9 | ||
|
|
ce3485c823 | ||
|
|
90a99d3a79 | ||
|
|
7248dc4fef | ||
|
|
d704db55fa | ||
|
|
0f581e47b1 |
No files matched your search
@@ -0,0 +1,12 @@
|
||||
# About
|
||||
This folder contains the plugin's description as advertised on various marketplaces which use various syntax's (bbcode, markdown).
|
||||
|
||||
While this is not accurate folder to have this stored in (".github"), it is the closest if we don't want to create a folder for this in the root.
|
||||
|
||||
The file structure of this folder is as following:
|
||||
|
||||
| File name | Description |
|
||||
|-------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| images | Folder containing all images used in the descriptions and wiki for advertising. Used not only as a backup, but for hosting them as well, so we do not need to rely on third party websites. |
|
||||
| description_markdown.md | Plugin description written in **markdown** syntax. Used by **Modrinth**. |
|
||||
| description_bbcode.txt | Plugin description written in **bbcode** syntax. Used by **SpigotMC** and **BuiltByBit**. |
|
||||
@@ -0,0 +1,184 @@
|
||||
[IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Section_About.png[/IMG]
|
||||
TAB is an all-in-one plugin for displaying information in various places, which aims to outperform all similar plugins in terms of features, compatibility and performance.
|
||||
The compact configuration allows you to get the plugin to work the way you want regardless of how simple or complex you want it to be or how experienced you are. Default configuration already contains useful values and examples to give you a better understanding and get you started quickly with instant results. The extensive [URL='https://github.com/NEZNAMY/TAB/wiki']wiki[/URL] answers any and all of your questions.
|
||||
|
||||
[IMG]https://img.shields.io/badge/Release-5.0.1-blue.svg[/IMG]
|
||||
[IMG]https://img.shields.io/badge/Minecraft-1.5%20--%201.21.3-blue.svg[/IMG]
|
||||
[IMG]https://img.shields.io/badge/Java-8+-blue.svg[/IMG]
|
||||
[URL='https://github.com/NEZNAMY/TAB'][IMG]https://img.shields.io/badge/GitHub-Source%20code-yellow.svg[/IMG][/URL]
|
||||
[URL='https://github.com/NEZNAMY/TAB/issues'][IMG]https://img.shields.io/github/issues/neznamy/tab?label=Issues[/IMG][/URL]
|
||||
[URL='https://github.com/NEZNAMY/TAB/wiki'][IMG]https://img.shields.io/badge/Documentation-Wiki-yellow.svg[/IMG][/URL]
|
||||
[CENTER][B][SIZE=7]Discord?[/SIZE][/B][/CENTER]
|
||||
Official support by me is no longer provided to free users due to limited time and extreme inefficiency (see [URL='https://github.com/NEZNAMY/TAB?tab=readme-ov-file#provided-services']provided services[/URL] for more info).
|
||||
However, you can join this [URL='https://discord.gg/YPqXt63YQj']fresh new community discord[/URL] made by a nice person who promises to help everyone with all plugins, including TAB.
|
||||
|
||||
Before you consider requiring assistance, check out the [URL='https://github.com/NEZNAMY/TAB/wiki']wiki[/URL] first. It will answer any questions you may have and is also constantly getting improved.
|
||||
|
||||
[CENTER][IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Section_Features.png[/IMG]
|
||||
[IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Section_HeaderFooter.png[/IMG]
|
||||
[IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Example_HeaderFooter.png[/IMG][/CENTER]
|
||||
[LIST]
|
||||
[*]Per-world / per-server support
|
||||
[*]Per-group / per-player support
|
||||
[*]Disable if a specified condition is met
|
||||
[/LIST]
|
||||
[CENTER][IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Section_NameTags.png[/IMG]
|
||||
[IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Example_NameTags.png[/IMG][/CENTER]
|
||||
[LIST]
|
||||
[*]Configurable prefix and suffix
|
||||
[*]Per-world / per-server support
|
||||
[*]Per-group / per-player support
|
||||
[*]Disable if a specified condition is met
|
||||
[*]Make nametags completely invisible
|
||||
[*]Control collision rule
|
||||
[*]Automatic compensation for 1.8.x client sided bug making nametags of invisible players still visible
|
||||
[*]Anti-override to prevent other plugins from overriding this one
|
||||
[/LIST]
|
||||
[IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Section_Sorting.png[/IMG]
|
||||
[CENTER][IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Example_Sorting.png[/IMG][/CENTER]
|
||||
[LIST]
|
||||
[*]Sorting by primary permission group
|
||||
[*]Sorting by permission nodes
|
||||
[*]Sorting by a numeric placeholder from lowest to highest or highest to lowest
|
||||
[*]Alphabetically by a placeholder (A-Z or Z-A)
|
||||
[*]Sorting by pre-defined placeholder values
|
||||
[*]Combination of multiple sorting types
|
||||
[*]Enable / disable case-sensitive sorting
|
||||
[/LIST]
|
||||
[CENTER][IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Section_TablistFormatting.png[/IMG]
|
||||
[IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Example_TablistFormatting.png[/IMG][/CENTER]
|
||||
[LIST]
|
||||
[*]Configurable prefix, name and suffix
|
||||
[*]Per-world / per-server support
|
||||
[*]Per-group / per-player support
|
||||
[*]Disable if a specified condition is met
|
||||
[*]Anti-override to prevent other plugins from overriding this one
|
||||
[/LIST]
|
||||
[CENTER][IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Section_PlayerlistObjective.png[/IMG]
|
||||
[IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Example_PlayerlistObjectiveNumber.png[/IMG]
|
||||
[IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Example_PlayerlistObjectiveHealth.png[/IMG]
|
||||
[IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Example_PlayerlistObjectiveFancy.png[/IMG] [/CENTER]
|
||||
[LIST]
|
||||
[*]Display value of any numeric placeholder or hearts
|
||||
[*]Display any text on 1.20.3+
|
||||
[*]Disable if a specified condition is met
|
||||
[/LIST]
|
||||
[CENTER][IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Section_Belowname.png[/IMG]
|
||||
[IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Example_BelownameComparison.png[/IMG][/CENTER]
|
||||
[LIST]
|
||||
[*]Display value of any numeric placeholder (<1.20.3) or any text (1.20.3+)
|
||||
[*]Configurable text
|
||||
[*]Disable if a specified condition is met
|
||||
[/LIST]
|
||||
[CENTER][IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Section_Bossbar.png[/IMG]
|
||||
[IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Example_BossBar.png[/IMG][/CENTER]
|
||||
[LIST]
|
||||
[*]Configurable text, progress, color and style
|
||||
[*]Define display condition to see a bossbar
|
||||
[*]Announce defined bossbar for specified amount of time
|
||||
[*]Configurable toggle command and message
|
||||
[*]Option to remember toggle choice between logins
|
||||
[/LIST]
|
||||
[CENTER][IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Section_GlobalPlayerlist.png[/IMG][/CENTER]
|
||||
[LIST]
|
||||
[*]Display players from all servers on a proxy network in tablist
|
||||
[*]Configurable server groups to share playerlist instead of with all servers
|
||||
[*]Ability to create isolated servers
|
||||
[*]Servers where people will see everyone
|
||||
[*]Compatible with vanish plugins
|
||||
[*]Option to display players from other servers as spectators
|
||||
[/LIST]
|
||||
[CENTER][IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Section_Layout.png[/IMG]
|
||||
[IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Example_Layout.png[/IMG][/CENTER]
|
||||
[LIST]
|
||||
[*]Customize all 80 tablist slots
|
||||
[*]Create static slots with any text and skin
|
||||
[*]Create player groups based on conditions
|
||||
[*]Multiple layouts based on conditions
|
||||
[/LIST]
|
||||
[CENTER][IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Section_Scoreboard.png[/IMG]
|
||||
[IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Example_ScoreboardLegacy.png[/IMG][/CENTER]
|
||||
[LIST]
|
||||
[*]Display up to 15 lines
|
||||
[*]Option to show 0 (or any other static number) in every line instead of 1-15
|
||||
[*]Fully configurable text on right side of lines on 1.20.3+
|
||||
[*]Displaying different scoreboards based on conditions (output of placeholder, permissions)
|
||||
[*]Up to 64 characters per line for <1.13, unlimited for 1.13+ clients
|
||||
[*]Customizable toggle command and message
|
||||
[*]Option to remember toggle choice between logins
|
||||
[*]Automatic detection of scoreboards from other plugins to hide own and show it again once other plugin hides its scoreboard
|
||||
[/LIST]
|
||||
[CENTER][IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Section_PerWorldPlayerlist.png[/IMG][/CENTER]
|
||||
[LIST]
|
||||
[*]Only display players from current world in tablist
|
||||
[*]Group worlds which should share players
|
||||
[*]Worlds where players see everyone
|
||||
[*]Option to see all players in all worlds for players with permission
|
||||
[/LIST]
|
||||
[CENTER][IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Section_API.png[/IMG][/CENTER]
|
||||
[LIST]
|
||||
[*]Change player prefix/name/suffix in tablist
|
||||
[*]Change header/footer
|
||||
[*]Change nametag prefix / suffix
|
||||
[*]Change player collision rule and nametag visibility
|
||||
[*]Change sorting priority
|
||||
[*]Create custom scoreboards, show different scoreboard, toggle scoreboard
|
||||
[*]Create custom placeholders
|
||||
[*]Create custom boss bars, toggle bossbar visibility, announce bossbar
|
||||
[*]... and more!
|
||||
[/LIST]
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
[LIST]
|
||||
[*]Placeholder support
|
||||
[LIST]
|
||||
[*]Internal placeholders with customizable output
|
||||
[*]Create and use custom animations
|
||||
[*]All placeholders and animations are supported in all features
|
||||
[*]Full PlaceholderAPI support including relational placeholders
|
||||
[*]PlaceholderAPI support on BungeeCord
|
||||
[/LIST]
|
||||
[*]Placeholder output replacements
|
||||
[LIST]
|
||||
[*]Modify output of any placeholder, including PlaceholderAPI placeholders
|
||||
[*]Replace exact text with another text
|
||||
[*]Change output if it's in a number interval
|
||||
[*]Set value if none of the above is met
|
||||
[*]Use original placeholder's output in new one
|
||||
[*]Support for nested placeholders
|
||||
[*]Can be used in other plugins as well
|
||||
[/LIST]
|
||||
[*]RGB Support
|
||||
[LIST]
|
||||
[*]5 supported RGB patterns
|
||||
[*]4 supported gradient patterns
|
||||
[*]Automatically display closest color to legacy clients
|
||||
[*]Configurable color for legacy clients
|
||||
[*]Supported in all features [B]where possible[/B]
|
||||
[/LIST]
|
||||
[*]Conditional placeholders
|
||||
[LIST]
|
||||
[*]Conditions based on placeholder output (comparing numeric value, exact output)
|
||||
[*]Conditions based on permission node
|
||||
[*]Combine conditions with AND or OR
|
||||
[*]Configurable output of both cases when condition is and isn't met
|
||||
[*]Placeholder support in condition output
|
||||
[/LIST]
|
||||
[*]High performance
|
||||
[LIST]
|
||||
[*]Full control of CPU usage by optimizing configuration and disabling unwanted features
|
||||
[*]Fully asynchronous - no impact on TPS
|
||||
[*]Check plugin's CPU usage using /tab cpu
|
||||
[/LIST]
|
||||
[*]RedisBungee support
|
||||
[*]MySQL support
|
||||
[*]Fully client-sided, server-side stays unaffected (scoreboards with their teams and objectives, bossbar entities, armor stands)
|
||||
[*]Disable vanilla feature making players in spectator gamemode appear on the bottom of tablist
|
||||
[*]Possible errors moved into a separate file to not spam your console
|
||||
[*]Yaml error assistant for more user-friendly messages and fix suggestions
|
||||
[*]Many misconfiguration checks to help solve issues much faster
|
||||
[/LIST]
|
||||
@@ -0,0 +1,166 @@
|
||||

|
||||
TAB is an all-in-one plugin for displaying information in various places, which aims to outperform all similar plugins in terms of features, compatibility and performance.
|
||||
The compact configuration allows you to get the plugin to work the way you want regardless of how simple or complex you want it to be or how experienced you are.
|
||||
Default configuration already contains useful values and examples to give you a better understanding and get you started quickly with instant results.
|
||||
The extensive [wiki](https://github.com/NEZNAMY/TAB/wiki) answers any and all of your questions.
|
||||
|
||||

|
||||

|
||||

|
||||
[](https://github.com/NEZNAMY/TAB)
|
||||
[](https://github.com/NEZNAMY/TAB/issues)
|
||||
[](https://github.com/NEZNAMY/TAB/wiki)
|
||||
|
||||
# Discord?
|
||||
Official support by me is no longer provided to free users due to limited time and extreme inefficiency (see [provided services]([https://github.com/NEZNAMY/TAB?tab=readme-ov-file#provided-services](https://github.com/NEZNAMY/TAB?tab=readme-ov-file#provided-services)) for more info).
|
||||
However, you can join this [fresh new community discord]([https://discord.gg/YPqXt63YQj](https://discord.gg/YPqXt63YQj)) made by a nice person who promises to help everyone with all plugins, including TAB.
|
||||
|
||||
Before you consider requiring assistance, check out the [wiki](https://github.com/NEZNAMY/TAB/wiki) first. It will answer any questions you may have and is also constantly getting improved.
|
||||
|
||||

|
||||

|
||||

|
||||
* Per-world / per-server support
|
||||
* Per-group / per-player support
|
||||
* Disable if a specified condition is met
|
||||
|
||||

|
||||

|
||||
* Configurable prefix and suffix
|
||||
* Per-world / per-server support
|
||||
* Per-group / per-player support
|
||||
* Disable if a specified condition is met
|
||||
* Make nametags completely invisible
|
||||
* Control collision rule
|
||||
* Automatic compensation for 1.8.x client sided bug making nametags of invisible players still visible
|
||||
* Anti-override to prevent other plugins from overriding this one
|
||||
|
||||

|
||||

|
||||
* Sorting by primary permission group
|
||||
* Sorting by permission nodes
|
||||
* Sorting by a numeric placeholder from lowest to highest or highest to lowest
|
||||
* Alphabetically by a placeholder (A-Z or Z-A)
|
||||
* Sorting by pre-defined placeholder values
|
||||
* Combination of multiple sorting types
|
||||
* Enable / disable case-sensitive sorting
|
||||
|
||||

|
||||

|
||||
* Configurable prefix, name and suffix
|
||||
* Per-world / per-server support
|
||||
* Per-group / per-player support
|
||||
* Disable if a specified condition is met
|
||||
* Anti-override to prevent other plugins from overriding this one
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
* Display value of any numeric placeholder or hearts
|
||||
* Display any text on 1.20.3+
|
||||
* Disable if a specified condition is met
|
||||
|
||||

|
||||

|
||||
* Display value of any numeric placeholder (<1.20.3) or any text (1.20.3+)
|
||||
* Configurable text
|
||||
* Disable if a specified condition is met
|
||||
|
||||

|
||||

|
||||
* Configurable text, progress, color and style
|
||||
* Define display condition to see a bossbar
|
||||
* Announce defined bossbar for specified amount of time
|
||||
* Configurable toggle command and message
|
||||
* Option to remember toggle choice between logins
|
||||
|
||||

|
||||
* Display players from all servers on a proxy network in tablist
|
||||
* Configurable server groups to share playerlist instead of with all servers
|
||||
* Ability to create isolated servers
|
||||
* Servers where people will see everyone
|
||||
* Compatible with vanish plugins
|
||||
* Option to display players from other servers as spectators
|
||||
|
||||

|
||||

|
||||
* Customize all 80 tablist slots
|
||||
* Create static slots with any text and skin
|
||||
* Create player groups based on conditions
|
||||
* Multiple layouts based on conditions
|
||||
|
||||

|
||||

|
||||
* Display up to 15 lines
|
||||
* Option to show 0 (or any other static number) in every line instead of 1-15
|
||||
* Fully configurable text on right side of lines on 1.20.3+
|
||||
* Displaying different scoreboards based on conditions (output of placeholder, permissions)
|
||||
* Up to 64 characters per line for <1.13, unlimited for 1.13+ clients
|
||||
* Customizable toggle command and message
|
||||
* Option to remember toggle choice between logins
|
||||
* Automatic detection of scoreboards from other plugins to hide own and show it again once other plugin hides its scoreboard
|
||||
|
||||

|
||||
* Only display players from current world in tablist
|
||||
* Group worlds which should share players
|
||||
* Worlds where players see everyone
|
||||
* Option to see all players in all worlds for players with permission
|
||||
|
||||

|
||||
* Change player prefix/name/suffix in tablist
|
||||
* Change header/footer
|
||||
* Change nametag prefix / suffix
|
||||
* Change player collision rule and nametag visibility
|
||||
* Change sorting priority
|
||||
* Create custom scoreboards, show different scoreboard, toggle scoreboard
|
||||
* Create custom placeholders
|
||||
* Create custom boss bars, toggle bossbar visibility, announce bossbar
|
||||
* ... and more!
|
||||
|
||||
.
|
||||
.
|
||||
.
|
||||
.
|
||||
.
|
||||
Placeholder support
|
||||
* Internal placeholders with customizable output
|
||||
* Create and use custom animations
|
||||
* All placeholders and animations are supported in all features
|
||||
* Full [PlaceholderAPI]('https://www.spigotmc.org/resources/placeholderapi.6245/') support including [relational placeholders]('https://www.spigotmc.org/resources/placeholderapi.6245/update?update=167267')
|
||||
* PlaceholderAPI support on BungeeCord / Velocity
|
||||
|
||||
Placeholder output replacements
|
||||
* Modify output of any placeholder, including PlaceholderAPI placeholders
|
||||
* Replace exact text with another text
|
||||
* Change output if it's in a number interval
|
||||
* Set value if none of the above is met
|
||||
* Use original placeholder's output in new one
|
||||
* Support for nested placeholders
|
||||
* Can be used in other plugins as well
|
||||
|
||||
RGB Support
|
||||
* 5 supported RGB patterns
|
||||
* 3 supported gradient patterns
|
||||
* Automatically display the closest color to legacy clients, can also be configured manually
|
||||
* Supported in all features **where possible**
|
||||
|
||||
Conditional placeholders
|
||||
* Conditions based on placeholder output (comparing numeric value, exact output)
|
||||
* Conditions based on permission node
|
||||
* Combine conditions with AND or OR
|
||||
* Configurable output of both cases when condition is and isn't met
|
||||
* Placeholder support in conditions
|
||||
|
||||
High performance
|
||||
* Full control of CPU usage by optimizing configuration and disabling unwanted features
|
||||
* Fully asynchronous - no impact on TPS
|
||||
* Check plugin's CPU usage using /tab cpu
|
||||
|
||||
RedisBungee support
|
||||
MySQL support
|
||||
Fully client-sided, server-side stays unaffected
|
||||
Disable vanilla feature making players in spectator gamemode appear on the bottom of tablist
|
||||
Possible errors moved into a separate file to not spam your console
|
||||
Yaml error assistant for more user-friendly messages and fix suggestions
|
||||
Many misconfiguration checks to help solve issues much faster
|
||||
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 2.8 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 63 KiB |
|
After Width: | Height: | Size: 7.4 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 236 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 76 KiB |
|
After Width: | Height: | Size: 75 KiB |
|
After Width: | Height: | Size: 89 KiB |
|
After Width: | Height: | Size: 86 KiB |
|
After Width: | Height: | Size: 85 KiB |
|
After Width: | Height: | Size: 93 KiB |
|
After Width: | Height: | Size: 92 KiB |
|
After Width: | Height: | Size: 90 KiB |
|
After Width: | Height: | Size: 81 KiB |
|
After Width: | Height: | Size: 85 KiB |
|
After Width: | Height: | Size: 96 KiB |
|
After Width: | Height: | Size: 95 KiB |
|
After Width: | Height: | Size: 89 KiB |
|
After Width: | Height: | Size: 83 KiB |
|
After Width: | Height: | Size: 94 KiB |
@@ -28,18 +28,18 @@ It is compiled with Java 8 and therefore supports any version of 8 and above.
|
||||
<tr>
|
||||
<td rowspan=2>Vanilla</td>
|
||||
<td rowspan=1><a href="https://getbukkit.org/">Bukkit</a> (+forge hybrids)</td>
|
||||
<td>✔ (1.5 - 1.21.1)</td>
|
||||
<td>✔ (1.5 - 1.21.3)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan=1><a href="https://www.spongepowered.org/">Sponge</a></td>
|
||||
<td>✔ (1.9<sup>1</sup> - 1.20.4)</td>
|
||||
<td>✔ (1.9<sup>1</sup> - 1.21.3)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td rowspan=2>Modded</td>
|
||||
<td rowspan=1><a href="https://fabricmc.net">Fabric</a></td>
|
||||
<td>✔ (1.14.4 - 1.21.1)</td>
|
||||
<td>✔ (1.14 - 1.21.3)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan=1><a href="https://minecraftforge.net">Forge</a></td>
|
||||
@@ -68,15 +68,15 @@ It is compiled with Java 8 and therefore supports any version of 8 and above.
|
||||
# Supported features per platform
|
||||
| Feature \ Platform | Bukkit / Hybrid | Sponge | Fabric | BungeeCord | Velocity |
|
||||
| ------------- | ------------- | ------------- | ------------- | ------------- | ------------- |
|
||||
| [Belowname](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Belowname) | ✔ | ✔ | ✔ | ✔ | ✔ (via [Bridge](https://github.com/NEZNAMY/TAB/wiki/TAB-Bridge)) |
|
||||
| [Belowname](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Belowname) | ✔ | ✔ | ✔ | ✔ | ✔ (via [VSAPI](https://github.com/NEZNAMY/VelocityScoreboardAPI/releases)) |
|
||||
| [BossBar](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Bossbar) | ✔ | ✔ | ✔ | ✔ (1.9+) | ✔ (1.9+) |
|
||||
| [Global Playerlist](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Global-playerlist) | ❌ | ❌ | ❌ | ✔ | ✔ |
|
||||
| [Header/Footer](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Header-&-Footer) | ✔ | ✔ | ✔ | ✔ | ✔ |
|
||||
| [Layout](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Layout) | ✔ | ✔ | ✔ | ✔ | ✔ |
|
||||
| [Nametags](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Nametags) & [Sorting](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Sorting-players-in-tablist) | ✔ | ❗ | ✔ | ✔ | ✔ (via [Bridge](https://github.com/NEZNAMY/TAB/wiki/TAB-Bridge)) |
|
||||
| [Nametags](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Nametags) & [Sorting](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Sorting-players-in-tablist) | ✔ | ❗ | ✔ | ✔ | ✔ (via [VSAPI](https://github.com/NEZNAMY/VelocityScoreboardAPI/releases)) |
|
||||
| [Per world playerlist](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Per-world-playerlist) | ✔ | ❌ | ❌ | ❌ | ❌ |
|
||||
| [Playerlist objective](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Playerlist-Objective) | ✔ | ✔ | ✔ | ✔ | ✔ (via [Bridge](https://github.com/NEZNAMY/TAB/wiki/TAB-Bridge)) |
|
||||
| [Scoreboard](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Scoreboard) | ✔ | ❗ | ✔ | ✔ | ❗ (via [Bridge](https://github.com/NEZNAMY/TAB/wiki/TAB-Bridge)) |
|
||||
| [Playerlist objective](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Playerlist-Objective) | ✔ | ✔ | ✔ | ✔ | ✔ (via [VSAPI](https://github.com/NEZNAMY/VelocityScoreboardAPI/releases)) |
|
||||
| [Scoreboard](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Scoreboard) | ✔ | ❗ | ✔ | ✔ | ✔ (via [VSAPI](https://github.com/NEZNAMY/VelocityScoreboardAPI/releases)) |
|
||||
| [Spectator fix](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Spectator-fix) | ✔ | ✔ | ✔ | ✔ | ✔ |
|
||||
| [Tablist names](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Tablist-name-formatting) | ✔ | ✔ | ✔ | ✔ | ✔ |
|
||||
|
||||
@@ -109,7 +109,8 @@ In order to enhance user experience, TAB hooks into other plugins for better exp
|
||||
|
||||
## Velocity
|
||||
[**PremiumVanish**](https://www.spigotmc.org/resources/14404) - Supporting PV's vanish levels instead of using a basic vanish compatibility system.
|
||||
[**RedisBungee**](https://github.com/ProxioDev/RedisBungee) - Communicating with other proxies to properly display visuals on players on another proxy.
|
||||
[**RedisBungee**](https://github.com/ProxioDev/RedisBungee) - Communicating with other proxies to properly display visuals on players on another proxy.
|
||||
[**VelocityScoreboardAPI**](https://github.com/NEZNAMY/VelocityScoreboardAPI) - Sending scoreboard packets (scoreboard-teams, belowname-objective, playerlist-objective, scoreboard)
|
||||
|
||||
# Compatibility issues
|
||||
* **Glow plugins** will fail to apply glow color correctly. Check [How to make the plugin compatible with glow plugins](https://github.com/NEZNAMY/TAB/wiki/How-to-make-TAB-compatible-with-glow-plugins) for more information.
|
||||
|
||||
@@ -27,7 +27,7 @@ For Maven users, you can add the API dependency like this:
|
||||
<dependency>
|
||||
<groupId>com.github.NEZNAMY</groupId>
|
||||
<artifactId>TAB-API</artifactId>
|
||||
<version>4.1.8</version>
|
||||
<version>5.0.0</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
```
|
||||
@@ -41,7 +41,7 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly 'com.github.NEZNAMY:TAB-API:4.1.8'
|
||||
compileOnly 'com.github.NEZNAMY:TAB-API:5.0.0'
|
||||
}
|
||||
```
|
||||
|
||||
@@ -52,7 +52,7 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly("com.github.NEZNAMY", "TAB-API", "4.1.8")
|
||||
compileOnly("com.github.NEZNAMY", "TAB-API", "5.0.0")
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ TAB logs various events in up to 3 files to keep the console clean. All of these
|
||||
|
||||
Files are only created when something is about to get logged. This means if you have none of these files in your folder, everything is good. Also, it is safe to delete the logs any time while server is running without needing to reload the plugin or anything.
|
||||
|
||||
All files have their sizes limited to 1MB to avoid giant files consisting of the same error repeating over and over. Don't forget these files are **logs**, therefore, after solving the problems you must delete them. These files will not delete themselves.
|
||||
All files have their sizes limited to 16 MB to avoid giant files consisting of the same error repeating over and over. Don't forget these files are **logs**, therefore, after solving the problems you must delete them. These files will not delete themselves.
|
||||
After deleting a file, there is no need to perform any other action. Every time an error is about to be logged, file is created if it doesn't exist.
|
||||
|
||||
Java errors usually contain stack traces. If you got an error spamming with exception message but no stack trace, the most common cause is java not including it because too many errors were thrown already. To bypass this check, add `-XX:-OmitStackTraceInFastThrow` to your startup parameters.
|
||||
|
||||
@@ -4,10 +4,14 @@
|
||||
* [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)
|
||||
* [Tips & Tricks](#tips--tricks)
|
||||
* [Tip 1 - Heart symbol](#tip-1---heart-symbol)
|
||||
* [Tip 2 - Displaying health as 0-10 or in %](#tip-2---displaying-health-as-0-10-or-in-)
|
||||
* [Limitations](#limitations)
|
||||
* [Examples](#examples)
|
||||
* [Example 1 - Per-world values](#example-1---per-world-values)
|
||||
* [Example 2 - Hiding `text` for 1.20.3+ players](#example-2---hiding-text-for-1203-players)
|
||||
|
||||
# About
|
||||
The scoreboard objective with BELOW_NAME display slot that adds another line of text below nametag of players. It is only visible on players within 8 block range. This value is client sided and cannot be changed by the plugin.
|
||||
@@ -38,6 +42,17 @@ The minecraft feature is programmed to be affected by nametag visibility rule. T
|
||||
## Additional note 2 - Hidden on sneak on 1.8
|
||||
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
|
||||
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-display-default` (1.20.3+). This includes player NPCs.
|
||||
|
||||
There is currently only 1 way to make them not visible on NPCs. From [Additional note 1 - Copying nametag visibility rule](#additional-note-1---copying-nametag-visibility-rule) we know that belowname is not visible if player's nametag is invisible. NPC plugin can take advantage of this by using teams to hide the original name and display a hologram instead.
|
||||
|
||||
This is how you can achieve it using the following popular NPC plugins:
|
||||
* **Citizens**:
|
||||
* 1 - Select the NPC (`/npc select <ID>` or `/npc select` to select the nearest NPC)
|
||||
* 2 - Hide its original nametag (which also hides belowname) using `/npc name`
|
||||
* 3 - Display your desired text using `/npc hologram add <text>` ([more info](https://wiki.citizensnpcs.co/Commands#:~:text=the%20NPC%20hitbox-,/npc%20hologram,-add%20%5Btext%5D%20%7C%20set))
|
||||
|
||||
# Tips & Tricks
|
||||
## Tip 1 - Heart symbol
|
||||
If you want to show health of players and display a heart symbol as text, you can use this one: `❤`. Make sure you [save config in UTF-8 encoding](https://github.com/NEZNAMY/TAB/wiki/How-to-save-the-config-in-UTF8-encoding) so it's loaded correctly.
|
||||
@@ -55,4 +70,49 @@ If you want health to display health as 0-10 instead of 0-20, you can achieve it
|
||||
* The format is a **[score] + space + shared text**, where [score] is either a white number (1.20.2-) or any text (1.20.3+).
|
||||
* The text is same on all players, therefore cannot be personalized (such as player's faction). Only the [score] can be per-player.
|
||||
* It appears on all entites of player type. **This includes player NPCs.**
|
||||
* Text length is limited to 32 characters on <1.13
|
||||
* Text length is limited to 32 characters on <1.13
|
||||
|
||||
# Examples
|
||||
## Example 1 - Per-world values
|
||||
This feature doesn't directly support per-world values or similar. However, this can be achieved with conditions. Let's make `fancy-display-players` conditional based on world. Let's make an example with 3 worlds. Check if player is in world `world1`, then show one text. If not, check if player is in world `world2`, then show another text. If not, show final text. This can be achieved by chaining 2 conditions:
|
||||
```
|
||||
conditions:
|
||||
belowname1:
|
||||
conditions:
|
||||
- "%world%=world1"
|
||||
true: "Text to display in world world1" # Player is in world world1, display the text
|
||||
false: "%condition:belowname2%" # Player is not in world world1, check another condition
|
||||
belowname2:
|
||||
conditions:
|
||||
- "%world%=world2"
|
||||
true: "Text to display in world world2" # Player is in world world2, display the text
|
||||
false: "Text to display in other worlds" # Player is not in any of the 2 worlds
|
||||
```
|
||||
**WARNING: DO NOT JUST RANDOMLY PASTE THIS ENTIRE "CONDITIONS" SECTION INTO YOUR CONFIG! INSTEAD, EDIT YOUR EXISTING CONDITIONS SECTION TO PREVENT HAVING THE SECTION TWICE, HAVING SECOND ONE COMPLETELY OVERRIDE THE FIRST ONE!!!**
|
||||
|
||||
Finally, use this condition as value in `fancy-display-players`:
|
||||
```
|
||||
belowname-objective:
|
||||
fancy-display-players: "%condition:belowname1%"
|
||||
```
|
||||
If you are under 1.20.3 and want to make `number` and `text` conditional, create 2 condition chains, one for each value and use them.
|
||||
|
||||
*Note: This is just an example, the plugin is not limited to displaying different values only per world. If you want per server values on BungeeCord, use %server% with server names. If you want worldguard regions, use %worldguard_region_name% with region names. This works for any placeholder offered by the plugin or by PlaceholderAPI.*
|
||||
|
||||
## Example 2 - Hiding `text` for 1.20.3+ players
|
||||
1.20.3 has replaced `number` field, which is limited to a white number with value configurable as `fancy-display-players`, which has no limits. However, `text` field is still visible on 1.20.3+. If you support both version ranges and want to make the best out of the new functionality, you might want to not show `text` to 1.20.3+ players anymore, since you can fully customize the text you want to display in `fancy-display-players`.
|
||||
|
||||
First, let's remind outselves what `text` *actually* is. It is actually called scoreboard title. Yes, just like the one on top of sidebar, just displayed elsewhere. This is why it displays on all players and placeholders are parsed for the viewing player. With this knowledge, all we need to do is check for player's version and only show something for <1.20.3. For this, we will use `%player-version-id%` placeholder, which returns network protocol version of player's game version, which can then be compared to.
|
||||
```
|
||||
conditions:
|
||||
version:
|
||||
- "%player-version-id%>=765" # 1.20.3 is 765
|
||||
true: "" # Show empty value for 1.20.3+
|
||||
false: "Some text to display"
|
||||
```
|
||||
**WARNING: DO NOT JUST RANDOMLY PASTE THIS ENTIRE "CONDITIONS" SECTION INTO YOUR CONFIG! INSTEAD, EDIT YOUR EXISTING CONDITIONS SECTION TO PREVENT HAVING THE SECTION TWICE, HAVING SECOND ONE COMPLETELY OVERRIDE THE FIRST ONE!!!**
|
||||
```
|
||||
belowname-objective:
|
||||
text: "%condition:version%"
|
||||
```
|
||||
Unfortunately, due to the limitations of this feature, there is a forced space between `number`/`fancy-display-players` and `text`, even if the `text` is empty. You'll either need to live with it, or put some shared text there to avoid having a space at the end.
|
||||
@@ -11,22 +11,17 @@
|
||||
* [Announce command](#announce-command)
|
||||
* [Additional settings](#additional-settings)
|
||||
* [Limitations](#limitations)
|
||||
* [on <1.9](#on--19)
|
||||
* [Additional info](#additional-info)
|
||||
* [Additional note 1 - Hiding bar itself](#additional-note-1---hiding-bar-itself)
|
||||
* [Additional note 2 - Not hiding on server switch](#additional-note-2---not-hiding-on-server-switch)
|
||||
* [API](#api)
|
||||
* [1.8 vs 1.9 bossbar](#18-vs-19-bossbar)
|
||||
* [Background](#background)
|
||||
* [Execution by plugins](#execution-by-plugins)
|
||||
* [Execution by TAB](#execution-by-tab)
|
||||
* [Tips & Tricks](#tips--tricks)
|
||||
* [Tip 1 - Animated bossbar color](#tip-1---animated-bossbar-color)
|
||||
* [Tip 2 - Switching between bossbars with condition](#tip-2---switching-between-bossbars-with-condition)
|
||||
|
||||
# About
|
||||

|
||||
Bars with text on top of the screen originally designed to display health of ender dragon & wither, but plugins found another use for it. In 1.9 mojang added a packet dedicated to displaying text without requiring an entity and allowing customizable color and style as well.
|
||||
Bars with text on top of the screen originally designed to display health of ender dragon & wither, but plugins found another use for it. In 1.9 mojang added a packet dedicated to displaying text without requiring an entity and allowing customizable color and style as well. TAB only supports this new functionality and the feature does not support 1.8 and lower.
|
||||
|
||||
This feature can be configured in **config.yml** under **bossbar** section.
|
||||
|
||||
@@ -44,14 +39,14 @@ bossbar:
|
||||
Every bossbar line has 4 properties:
|
||||
|
||||
### style
|
||||
Type of bossbar division. Only visible on 1.9+ clients.
|
||||
Type of bossbar division.
|
||||
You can choose from 5 styles: **PROGRESS**, **NOTCHED_6**, **NOTCHED_10**, **NOTCHED_12** and **NOTCHED_20**
|
||||

|
||||
These were defined by mojang, no other options are available. Other plugins may be using different names, but it's the same result in the end.
|
||||
|
||||
|
||||
### color
|
||||
Color of bossbar. Only visible on 1.9+ clients. Old clients will see it purple.
|
||||
Color of bossbar.
|
||||
You can choose from 7 colors: **BLUE**, **GREEN**, **PINK**, **PURPLE**, **RED**, **WHITE** and **YELLOW**
|
||||

|
||||
These were defined by mojang, no other options are available.
|
||||
@@ -114,13 +109,6 @@ When using a bossbar announcement, you have a new placeholder available: `%count
|
||||
# Limitations
|
||||
* Does not support newlines. If you want to display more lines, you'll need to create multiple bossbars.
|
||||
* The bar itself cannot be removed from the server side. However, it can be removed with a resource pack.
|
||||
## on < 1.9
|
||||
* **Will not display at all if TAB is installed on bungeecord** (bungeecord is missing entity packets needed to make it work)
|
||||
* Doesn't offer color and style features.
|
||||
* Only one line can be displayed at a time.
|
||||
* You can see the fog if you don't disable particles in options.
|
||||
* Text length is limited to 64 characters on <1.7
|
||||
* May not be visible when looking at blocks
|
||||
|
||||
# Additional info
|
||||
## Additional note 1 - Hiding bar itself
|
||||
@@ -172,18 +160,6 @@ Methods in `BossBar`:
|
||||
* `BossBar#containsPlayer(TabPlayer)` - Returns `true` if player can see this bossbar, `false` if not.
|
||||
* `BossBar#isAnnouncementBar()` - Returns `true` if this bossbar is defined as announcement bar in config, `false` if visible permanently.
|
||||
|
||||
# 1.8 vs 1.9 bossbar
|
||||
The way of displaying information in bossbar is completely different on 1.8 and 1.9+. Here you can find out why.
|
||||
|
||||
## Background
|
||||
Server owners have always been looking for ways to display information. Another way they discovered is by spawning a fake ender dragon / wither, making it invisible, giving it custom name and teleporting it near player. This way, however, has several flaws as you can see above. As of 1.9, mojang decided to add a packet purely for displaying text without requiring an entity. They also added options to set color and style of the bossbar.
|
||||
|
||||
## Execution by plugins
|
||||
With introduction of 1.9 bossbar which is much easier to use, plugins have switched over to that method. However, this feature cannot be used for <1.9 clients as the feature did not exist back then. For this reason you can see some bossbar plugins saying "supports 1.9 and higher", as they don't want to mess with entities.
|
||||
|
||||
## Execution by TAB
|
||||
TAB will attempt to display 1.9 bossbar whenever possible, entity otherwise. 1.9+ clients will always see 1.9 bossbar (even on a 1.8 server thanks to viaversion api). <1.9 clients will see an invisible entity. On bungeecord, however, <1.9 clients won't see anything as bungeecord did not implement entity packets required to make it work.
|
||||
|
||||
# Tips & Tricks
|
||||
## Tip 1 - Animated bossbar color
|
||||
You can animate color of a bossbar using [animations](https://github.com/NEZNAMY/TAB/wiki/Animations).
|
||||
|
||||
@@ -19,6 +19,7 @@ To use this feature, you need [TAB installed on bungeecord](https://github.com/N
|
||||
| display-others-as-spectators | false | When enabled, players on different servers will appear as having spectator gamemode in tablist. |
|
||||
| display-vanished-players-as-spectators | true | When enabled, vanished players will show as in spectator gamemode for those, who have permission to see vanished players (tab.seevanished) (others will obviously not see them at all). Vanished players will still see themselves in the gamemode they are actually in, since changing that would cause problems (client would think it's in spectator gamemode while it isn't). |
|
||||
| isolate-unlisted-servers | false | When enabled, servers not listed in any group will not share playerlist with any other server, instead of sharing it with other unlisted servers. |
|
||||
| update-latency | false | When **enabled**, plugin will send ping update to players on other servers, since backend servers only send ping updates of players on the same server. This is displayed as the green bar in tablist, unless altered by a mod / resource pack (?). <br /> <br />When **disabled**, ping of players on other servers will be set to 0 (5 bars). <br /> <br />Disabled by default, because it's not only not very useful, but also has a very high CPU usage to due spamming a large amount of packets (especially on large networks). |
|
||||
|
||||
## Grouping players only from certain servers
|
||||
In case you want multiple servers to share playerlist, but not with every server, simply create a server group and put servers there. The default example
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* [Additional note 2 - Global playerlist incompatibility](#additional-note-2---global-playerlist-incompatibility)
|
||||
* [Additional note 3 - Entries in chat complete [1.19.3 - 1.21.1]](#additional-note-3---entries-in-chat-complete-1193---1211)
|
||||
* [Additional note 4 - Per world playerlist incompatibility](#additional-note-4---per-world-playerlist-incompatibility)
|
||||
* [Additional note 5 - Second layer of skin missing](#additional-note-5---second-layer-of-skin-missing)
|
||||
* [Examples](#examples)
|
||||
* [Example 1 - Per-server columns](#example-1---per-server-columns)
|
||||
|
||||
@@ -137,7 +138,7 @@ Yellow: 1307755006
|
||||
| direction | COLUMNS | Defines direction of slots. Options are COLUMNS (top to bottom, left to right) and ROWS (left to right, top to bottom). This does not only change the slot numbers in configuration, but will also affect the way players are being filled into player groups. |
|
||||
| enable-remaining-players-text | true | When enabled, the last slot of player group will show how many more players there are, instead of using the last slot for one more player. |
|
||||
| remaining-players-text | "... and %s more" | Text to show if option above is enabled. |
|
||||
| default-skin | "mineskin:1753261242" | Default skin to display for fixed slots that do not define a skin, empty slots and fixed slots with invalid skin. |
|
||||
| default-skin | "mineskin:383747683" | Default skin to display for fixed slots that do not define a skin, empty slots and fixed slots with invalid skin. |
|
||||
| empty-slot-ping-value | 1000 | Ping value to use for fixed slots and empty slots. The ping intervals for bars are client sided and are as following: <br />- Negative value: ✖ <br />- 0 - 149: 5 bars <br />- 150 - 299: 4 bars <br />- 300 - 599: 3 bars <br />- 600 - 999: 2 bars <br />- 1000+: 1 bar |
|
||||
|
||||

|
||||
@@ -156,8 +157,7 @@ Layout feature is capable of working with all online players connected to the se
|
||||
|
||||
## Additional note 3 - Entries in chat complete [1.19.3 - 1.21.1]
|
||||
Since 1.19.3 until 1.21.1 (inclusive), entries will also appear in chat complete. The mechanic used to hide them on <1.19.3 can no longer be used since 1.19.3. 1.21.2 has added a new way of sorting players, which is being taken advantage of to restore empty chat complete.
|
||||
**Note**: In order to hide entries from chat complete on 1.21.2+, the server also has to be 1.21.2+ so it can send new content to the players (having TAB installed on proxy counts as using latest version).
|
||||
**Note 2**: The 1.21.2+ improvement was added in [dev builds](https://github.com/NEZNAMY/TAB/actions) (v5) and will not be added into v4.
|
||||
**Note**: In order to hide entries from chat complete on 1.21.2+, the server also has to be 1.21.2+ so it can send new content to the players (having TAB installed on proxy counts as using latest version).
|
||||
|
||||
## Additional note 4 - Per world playerlist incompatibility
|
||||
Layout works by adding 80 fake players into the tablist, pushing real player entries out of view. Because of this, when using [per world playerlist](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Per-world-playerlist), the feature will only hide the real players, which are outside of tablist and not visible anyway and not touch the layout entries. However, you can replicate the same effect using layout feature itself, using conditions.
|
||||
@@ -210,13 +210,16 @@ You can also replicate "ignore-effect-in-worlds" by creating a layout with condi
|
||||
```
|
||||
</details>
|
||||
|
||||
## Additional note 5 - Second layer of skin missing
|
||||
Layout works by creating 80 fake entries, pushing real players out of the tablist. Unfortunately, players must be spawned around the player viewing the tablist in order to see second layer of their skin. Since those fake entries you see are not the actual players and aren't spawned, as a result, you won't see second layer of skin of players. This can only be fixed by actually letting the real entries display in the tablist, which would require a rework of the feature, which is not planned.
|
||||
|
||||
# Examples
|
||||
## Example 1 - Per-server columns
|
||||
```
|
||||
layout:
|
||||
enabled: true
|
||||
direction: COLUMNS
|
||||
default-skin: mineskin:1753261242
|
||||
default-skin: mineskin:383747683
|
||||
enable-remaining-players-text: true
|
||||
remaining-players-text: '... and %s more'
|
||||
empty-slot-ping-value: 1000
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
* [Additional note 2 - Prefix/suffix on pets](#additional-note-2---prefixsuffix-on-pets)
|
||||
* [Additional note 3 - Changing name itself](#additional-note-3---changing-name-itself)
|
||||
* [Additional note 4 - F1 view](#additional-note-4---f1-view)
|
||||
* [Additional note 5 - Transparent players](#additional-note-5---transparent-players)
|
||||
* [API](#api)
|
||||
* [Changing prefix and suffix](#changing-prefix-and-suffix)
|
||||
* [Collision](#collision)
|
||||
@@ -150,6 +151,16 @@ Teams do not allow to change the nametag name itself. Changing name is a complic
|
||||
## Additional note 4 - F1 view
|
||||
Using teams causes player nametags to remain visible when using F1 view. This cannot be avoided by the plugin in any way. The only possible solution would be to modify the client.
|
||||
|
||||
## Additional note 5 - Transparent players
|
||||
Minecraft teams have an option to show players in the same team transparent (partially invisible). This requires both players (viewer and target) to be in the same team. As such, this effect is impossible to achieve with TAB, because it puts every player into a different team to properly sort players and give them different prefix/suffix in nametag (and more, such as collision and nametag visibility).
|
||||
|
||||
If you want this effect, the only way is to use another plugin that offers this and disable TAB's teams entirely by setting
|
||||
```
|
||||
scoreboard-teams:
|
||||
enabled: false
|
||||
```
|
||||
in config.yml. Keep in mind you'll not be able to use any team features if you do so.
|
||||
|
||||
# API
|
||||
*To get started with the API, see [Developer API](https://github.com/NEZNAMY/TAB/wiki/Developer-API) page.*
|
||||
|
||||
|
||||
@@ -138,4 +138,4 @@ placeholder-output-replacements:
|
||||
"51-100": "&e%ping%"
|
||||
"else": "&c%ping%"
|
||||
```
|
||||
Keep in mind that now the placeholder will no longer return just a number, which breaks features like [Yellow Number](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Yellow-number), which require a numeric input. If you want that feature and ping to be displayed there, replacements won't work for you and you'll need to use [Conditional placeholders](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Conditional-placeholders) instead to achieve colored ping.
|
||||
Keep in mind that now the placeholder will no longer return just a number, which can break features like [Playerlist Objective](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Playerlist-Objective), which require a numeric input (for versions prior to 1.20.3). If you want that feature and ping to be displayed there, replacements won't work for you and you'll need to use [Conditional placeholders](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Conditional-placeholders) instead to achieve colored ping.
|
||||
@@ -20,6 +20,7 @@
|
||||
* [Examples](#examples)
|
||||
* [Example 1 - per-world scoreboards](#example-1---per-world-scoreboards)
|
||||
* [Example 2 - Periodical scoreboard switching](#example-2---periodical-scoreboard-switching)
|
||||
* [Example 3 - Per-version scoreboards](#example-3---per-version-scoreboards)
|
||||
|
||||
# About
|
||||
Scoreboard objective with SIDEBAR display slot.
|
||||
@@ -47,7 +48,7 @@ Since 1.20.3, you can also configure the value on the right side, which no longe
|
||||
Will give you the following:
|
||||

|
||||
|
||||
The right side text will not be visible on 1.20.2 and lower and numbers will be displayed instead. In order to properly see the right side not only the client has to be 1.20.3+, but the server as well, since plugin cannot send a packet that does not exist (on older versions). BungeeCord always includes new content and therefore counts as latest version. This can be used to send new content to new players while having outdated server version.
|
||||
The right side text will not be visible on 1.20.2 and lower and numbers will be displayed instead. **In order to properly see the right side not only the client has to be 1.20.3+, but the server as well, since plugin cannot send a packet that does not exist (on older versions). BungeeCord always includes new content and therefore counts as latest version.** This can be used to send new content to new players while having outdated server version.
|
||||
|
||||
### Alignment
|
||||
Left side of the text is automatically aligned to the left, while the right side is automatically aligned to the right. This is done by the client and is out of plugin's control. If you want to center a line, you'll need to do it manually. If dynamic placeholder output length is preventing your from doing it, you can try to bypass it by adding spaces before and after it to artificially increase and force the scoreboard width, such as `- " Centered text "` (or more spaces if needed).
|
||||
@@ -96,7 +97,6 @@ time - amount of seconds to display the scoreboard for
|
||||
| use-numbers | false | If enabled, numbers 1-15 will be used in the scoreboard. If disabled, `static-number` is shown everywhere. |
|
||||
| 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. |
|
||||
| respect-other-plugins | true | When enabled, TAB will hide its scoreboard if another plugin sent one and send it back once the other plugin's scoreboard is hidden. |
|
||||
|
||||
# Limitations
|
||||
* Title is limited to 32 characters (including color codes) on <1.13.
|
||||
@@ -224,4 +224,36 @@ Then, we will use output of this animation as a display condition. If animation
|
||||
lines:
|
||||
- 'Text'
|
||||
```
|
||||
We don't need to define display condition for second scoreboard, since if animation is not on the first frame, it has to be on the second one. If using more than 2, you will need to define display condition for all of them (except the last one).
|
||||
We don't need to define display condition for second scoreboard, since if animation is not on the first frame, it has to be on the second one. If using more than 2, you will need to define display condition for all of them (except the last one).
|
||||
|
||||
# Example 3 - Per-version scoreboards
|
||||
1.20.3 has added new features to scoreboards, which are not visible to older clients. If you want to use new features for new players while still displaying things properly for older players, you'll need to create 2 different scoreboards.
|
||||
You'll find 2 placeholders useful for this:
|
||||
* `%player-version-id%` - Returns network ID of player's protocol version. Unlike with version names, you can perform numerical comparisons with these. You can find all version IDs [here](https://github.com/NEZNAMY/TAB/blob/master/shared/src/main/java/me/neznamy/tab/shared/ProtocolVersion.java). For example, 1.20.3 is 765.
|
||||
* `%bedrock%` - Returns `true` if player is using bedrock edition, which does not support new functions either.
|
||||
|
||||
We only want the first scoreboard for players with version 765 or greater and not bedrock players. This can be achieved with the following condition: `display-condition: "%player-version-id%>=765;%bedrock%=false"`.
|
||||
|
||||
Let's go ahead and set it up (with proper spacing):
|
||||
```
|
||||
scoreboards:
|
||||
scoreboard-1.20.3+:
|
||||
title: "Scoreboard for 1.20.3+ and not bedrock"
|
||||
display-condition: "%player-version-id%>=765;%bedrock%=false" # Only display it to players using 1.20.3+ AND NOT bedrock edition
|
||||
lines:
|
||||
- "* &eOnline&7:||%online%"
|
||||
- "* &eCurrent World&7:||%worldonline%"
|
||||
- "* &eStaff&7:||%staffonline%"
|
||||
- "* &bRank&7:||%group%"
|
||||
- "* &bPing&7:||%ping%&8ms"
|
||||
- "* &bWorld&7:||%world%"
|
||||
scoreboard:
|
||||
title: "Scoreboard for <1.20.3 and bedrock"
|
||||
lines:
|
||||
- "* &eOnline&7: &f%online%"
|
||||
- "* &eCurrent World&7: &f%worldonline%"
|
||||
- "* &eStaff&7: &f%staffonline%"
|
||||
- "* &bRank&7: &f%group%"
|
||||
- "* &bPing&7: &f%ping%&8ms"
|
||||
- "* &bWorld&7: &f%world%"
|
||||
```
|
||||
@@ -26,6 +26,8 @@ The display name is split into 3 parts for convenience:
|
||||
These values are then merged together and displayed in the tablist.
|
||||

|
||||
|
||||
While you are free to use the 3 properties in any way you want since they are only split for convenience, it is still a good idea to use them for their designed purpose to not get lost a poor configuration. This means that the text which should appear before player's name should be put into `tabprefix`, any placeholder that is supposed to display player's name (such as from a nickname plugin) should go into `customtabname` and any text that should appear after player's name should be put into `tabsuffix`.
|
||||
|
||||
## Groups and users
|
||||
Properties can be applied in 2 ways: groups and users. Users can be defined by both username and their UUID. Values applied to users take priority over groups.
|
||||
**groups.yml**
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
# Content
|
||||
* [#1 - Why are NPCs showing up in the tablist?](#1---why-are-npcs-showing-up-in-the-tablist)
|
||||
* [#2 - How to make TAB work with LuckPerms?](#2---how-to-make-tab-work-with-luckperms)
|
||||
* [#3 - How can i make belowname not affect NPCs but keep it for players?](#3---how-can-i-make-belowname-not-affect-npcs-but-keep-it-for-players)
|
||||
* [#4 - I enabled MySQL and my prefix is now gone!](#4---i-enabled-mysql-and-my-prefix-is-now-gone)
|
||||
* [#5 - Why is player sorting not working?](#5---why-is-player-sorting-not-working)
|
||||
* [#6 - How to add players to %staffonline% placeholder?](#6---how-to-add-players-to-the-staffonline-placeholder)
|
||||
* [#7 - How to make player heads visible in tablist?](#7---how-to-make-player-heads-visible-in-tablist)
|
||||
* [#8 - Where can i find current default config files?](https://github.com/NEZNAMY/TAB/tree/master/shared/src/main/resources)
|
||||
* [#9 - How can I split players into multiple columns?](#9---how-can-i-split-players-into-multiple-columns)
|
||||
* [#10 - How to use space in prefix/suffix command?](#10---how-to-use-spaces-in-prefixsuffix-command)
|
||||
* [#11 - Can I change/remove the green connection bar in tablist?](#11---can-i-changeremove-the-green-connection-bar-in-tablist)
|
||||
* [#12 - Placeholder is not working](#12---placeholder-is-not-working)
|
||||
* [#13 - How can I display ItemsAdder images?](#13---how-can-i-display-itemsadder-images)
|
||||
* [#14 - How can I use UTF characters in configuration?](#14---how-can-i-use-utf-characters-in-configuration)
|
||||
* [#15 - Is there a way to remove all players from tablist?](#15---is-there-a-way-to-remove-all-players-from-tablist)
|
||||
* [#16 - How to add images to tablist?](#16---how-to-add-images-to-tablist)
|
||||
* [#17 - Is MiniMessage supported?](#17---is-minimessage-supported)
|
||||
* [#18 - Is it possible to show amount of players in a specific group, similar to staffonline placeholder?](#18---is-it-possible-to-show-amount-of-players-in-a-specific-group-similar-to-staffonline-placeholder)
|
||||
* [#3 - I enabled MySQL and my prefix is now gone!](#3---i-enabled-mysql-and-my-prefix-is-now-gone)
|
||||
* [#4 - Why is player sorting not working?](#4---why-is-player-sorting-not-working)
|
||||
* [#5 - How to add players to %staffonline% placeholder?](#5---how-to-add-players-to-the-staffonline-placeholder)
|
||||
* [#6 - How to make player heads visible in tablist?](#6---how-to-make-player-heads-visible-in-tablist)
|
||||
* [#7 - Where can i find current default config files?](#7---where-can-i-find-current-default-config-files)
|
||||
* [#8 - How can I split players into multiple columns?](#8---how-can-i-split-players-into-multiple-columns)
|
||||
* [#9 - How to use space in prefix/suffix command?](#9---how-to-use-spaces-in-prefixsuffix-command)
|
||||
* [#10 - Can I change/remove the green connection bar in tablist?](#10---can-i-changeremove-the-green-connection-bar-in-tablist)
|
||||
* [#11 - Placeholder is not working](#11---placeholder-is-not-working)
|
||||
* [#12 - How can I display ItemsAdder images?](#12---how-can-i-display-itemsadder-images)
|
||||
* [#13 - How can I use UTF characters in configuration?](#13---how-can-i-use-utf-characters-in-configuration)
|
||||
* [#14 - Is there a way to remove all players from tablist?](#14---is-there-a-way-to-remove-all-players-from-tablist)
|
||||
* [#15 - How to add images to tablist?](#15---how-to-add-images-to-tablist)
|
||||
* [#16 - Is MiniMessage supported?](#16---is-minimessage-supported)
|
||||
* [#17 - Is it possible to show amount of players in a specific group, similar to staffonline placeholder?](#17---is-it-possible-to-show-amount-of-players-in-a-specific-group-similar-to-staffonline-placeholder)
|
||||
* [#18 - Why is the plugin flagged as a virus by Windows defender?](#18---why-is-the-plugin-flagged-as-a-virus-by-windows-defender)
|
||||
|
||||
## #1 - Why are NPCs showing up in the tablist?
|
||||
See [Citizens FAQ](https://wiki.citizensnpcs.co/Frequently_Asked_Questions#Why_are_NPCs_showing_up_in_the_tablist.3F).
|
||||
@@ -28,38 +28,38 @@ See [Citizens FAQ](https://wiki.citizensnpcs.co/Frequently_Asked_Questions#Why_a
|
||||
* Configure [sorting by groups](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Sorting-players-in-tablist#groups) (recommended).
|
||||
* Sorting by weights directly: `PLACEHOLDER_HIGH_TO_LOW:%luckperms_highest_group_weight%`.
|
||||
|
||||
## #3 - How can I make belowname not affect NPCs but keep it for players?
|
||||
Make NPC plugin hide their original names (using teams) and place holograms for displaying text.
|
||||
|
||||
## #4 - I enabled MySQL and my prefix is now gone!
|
||||
## #3 - I enabled MySQL and my prefix is now gone!
|
||||
When enabling [MySQL](https://github.com/NEZNAMY/TAB/wiki/MySQL), it will be used as a data storage and groups.yml / users.yml files will no longer be used.
|
||||
If you want to upload your configuration to MySQL (or download it back to files), use [MySQL conversion commands](https://github.com/NEZNAMY/TAB/wiki/MySQL#data-conversion) - [`/tab mysql upload`](https://github.com/NEZNAMY/TAB/wiki/MySQL#uploading-from-files-to-mysql) and [`/tab mysql download`](https://github.com/NEZNAMY/TAB/wiki/MySQL#downloading-from-mysql-to-files), respectively.
|
||||
After you are done, run `/tab reload` for the changes to take effect.
|
||||
|
||||
## #5 - Why is player sorting not working?
|
||||
## #4 - Why is player sorting not working?
|
||||
See [Common mistakes section on sorting page](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Sorting-players-in-tablist#common-mistakes).
|
||||
|
||||
## #6 - How to add players to the %staffonline% placeholder?
|
||||
## #5 - How to add players to the %staffonline% placeholder?
|
||||
Give them the `tab.staff` permission.
|
||||
|
||||
## #7 - How to make player heads visible in tablist?
|
||||
## #6 - How to make player heads visible in tablist?
|
||||
Players who did not buy the game will never be able to see heads. Player heads are displayed by the client when connection to the server is an online connection authenticated through Mojang. It cannot be directly controlled by plugins. The intended way to reach this is by setting `online-mode=true` in `server.properties`.
|
||||
If you want to allow pirates on your server, you can still display heads for players who bought the game by changing their connections to online connections. Most commonly used plugins for this are [FastLogin](https://www.spigotmc.org/resources/14153/) and [JPremium](https://www.spigotmc.org/resources/27766/), which change connections of premium players who enabled it to online connections.
|
||||
Same goes for disabling heads - you cannot disable them if you have online mode enabled.
|
||||
|
||||
## #8 - Where can i find current default config files?
|
||||
You can find all default files in the [source code](https://github.com/NEZNAMY/TAB/tree/master/shared/src/main/resources). You can also delete or rename a file and the plugin will regenerate it.
|
||||
## #7 - Where can i find current default config files?
|
||||
You can do any of the following:
|
||||
* Check the [source code](https://github.com/NEZNAMY/TAB/tree/master/shared/src/main/resources/config).
|
||||
* Delete or rename a file and the plugin will regenerate it.
|
||||
* Open the plugin jar as a zip file and take files from there.
|
||||
|
||||
## #9 - How can I split players into multiple columns?
|
||||
## #8 - How can I split players into multiple columns?
|
||||
Players are put into columns by the client, it is not managed by the server. The only way is to get more players. They automatically split into more columns at 21, 41 and 61 players (due to limit of 20 entries per column). If you don't mind getting all 80 slots filled with fake player slots, check out the [Layout](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Layout) feature.
|
||||
|
||||
## #10 - How to use spaces in prefix/suffix command?
|
||||
## #9 - How to use spaces in prefix/suffix command?
|
||||
Use "", for example `/tab group owner tabprefix "&2&lOwner&r "`
|
||||
|
||||
## #11 - Can I change/remove the green connection bar in tablist?
|
||||
## #10 - Can I change/remove the green connection bar in tablist?
|
||||
Connection bar is client sided and cannot be retextured or removed by TAB. You will need a custom minecraft client or a resource pack.
|
||||
|
||||
## #12 - Placeholder is not working
|
||||
## #11 - Placeholder is not working
|
||||
Most common reasons for a placeholder to not work include:
|
||||
* Using % symbol by itself, which breaks placeholder starts and ends and therefore breaks all placeholders after it (use %% to display the symbol)
|
||||
* Trying to use a PlaceholderAPI placeholder without downloading its expansion or not having PlaceholderAPI installed at all
|
||||
@@ -68,10 +68,13 @@ Most common reasons for a placeholder to not work include:
|
||||
|
||||
Full list of reasons can be found on the [Placeholders](https://github.com/NEZNAMY/TAB/wiki/Placeholders#placeholder-is-not-working) page.
|
||||
|
||||
## #13 - How can I display ItemsAdder images?
|
||||
ItemsAdder offers placeholders, which return the respective symbol to which your desired image is bound to. You can get them using ItemsAdder's PlaceholderAPI expansion using their `%img_<something>%` placeholder. ItemsAdder's internal placeholders (`:something:`) will not work in TAB.
|
||||
## #12 - How can I display ItemsAdder images?
|
||||
ItemsAdder offers placeholders, which return the respective symbol to which your desired image is bound to. You can get them using ItemsAdder's PlaceholderAPI expansion using their `%img_<name>%` placeholder. ItemsAdder's internal placeholders (`:something:`) will not work in TAB.
|
||||
|
||||
## #14 - How can I use UTF characters in configuration?
|
||||
If you are unable to change the references (for example because they are defined in permission plugin and used elsewhere as well), you can use [PlaceholderAPI](https://github.com/NEZNAMY/TAB/wiki/Quick-PlaceholderAPI-startup-guide)'s `imgfix` expansion that replaces format of given placeholders into the correct format.
|
||||
Example: `%luckperms_prefix%` -> `%imgfix_{luckperms_prefix}%`
|
||||
|
||||
## #13 - How can I use UTF characters in configuration?
|
||||
**Option 1**: [Save the file in UTF-8 encoding](https://github.com/NEZNAMY/TAB/wiki/How-to-save-the-config-in-UTF8-encoding) and use your desired symbol directly.
|
||||
|
||||
**Option 2**: Find hex code of your symbol (4 hexadecimal numbers) and use `\uxxxx` format, where `xxxx` is hexcode of the symbol.
|
||||
@@ -79,13 +82,13 @@ Make sure to also wrap the text into `""` for the text to properly take `\` as a
|
||||
|
||||
**Note:** Minecraft does not support every single UTF symbol and displays unsupported symbols as a box. You can try sending your symbol into chat and see if it works or not. If not, it's not supported by MC.
|
||||
|
||||
## #15 - Is there a way to remove all players from tablist?
|
||||
## #14 - Is there a way to remove all players from tablist?
|
||||
For a player to be visible in game, they must be in the tablist as well. Remember the NPC in tablist issue? If you don't mind players not appearing in game, get a plugin that hides all players. If you want to see them in game however, your only choice is to put a lot of empty lines into header, which will push all players out of the screen.
|
||||
|
||||
## #16 - How to add images to tablist?
|
||||
## #15 - How to add images to tablist?
|
||||
You can check out [this reddit post](https://www.reddit.com/r/admincraft/comments/llrgty/comment/gnswdcz/?utm_source=share&utm_medium=web2x&context=3). When using the symbol in configuration using \u format, don't forget to use `""` in config instead of `''`.
|
||||
|
||||
## #17 - Is MiniMessage supported?
|
||||
## #16 - Is MiniMessage supported?
|
||||
Kind of. MiniMessage support **is** included, however, this doesn't mean it is guaranteed to work. Here are a few reasons why it may not work for you:
|
||||
* MiniMessage library must be included in your server software. TAB does not include this library.
|
||||
* It **is** included in Paper 1.16.5 and higher, Velocity and Sponge 8.
|
||||
@@ -95,5 +98,12 @@ Kind of. MiniMessage support **is** included, however, this doesn't mean it is g
|
||||
|
||||
This list is not final. There might still be other reasons why MiniMessage won't work for you which are not known yet. If you believe MiniMessage should be supported on your server but doesn't work, open a bug report.
|
||||
|
||||
## #18 - Is it possible to show amount of players in a specific group, similar to staffonline placeholder?
|
||||
The plugin's internal placeholders are limited and this kind of functionality is not included. However, you can achieve this using PlaceholderAPI's `PlayerList` expansion and following [this example](https://github.com/Tanguygab/PlayerList-PlaceholderAPI-Expansion/wiki#list-of-players-in-group).
|
||||
## #17 - Is it possible to show amount of players in a specific group, similar to staffonline placeholder?
|
||||
The plugin's internal placeholders are limited and this kind of functionality is not included. However, you can achieve this using PlaceholderAPI's `PlayerList` expansion and following [this example](https://github.com/Tanguygab/PlayerList-PlaceholderAPI-Expansion/wiki#list-of-players-in-group).
|
||||
|
||||
## #18 - Why is the plugin flagged as a virus by Windows defender?
|
||||
First of all, this is a false positive. [Here](https://www.spigotmc.org/threads/windows-defender-false-positives.639507/)'s a spigot thread confirming it.
|
||||
|
||||
After experimenting with VirusTotal it turns out the anti-virus software used there flags [bStats](https://github.com/Bastian/bStats) library as malicious. Its purpose is to collect anonymous data about servers and submitting it to [bStats.org](https://bstats.org/), where plugin authors can see how many servers use their plugins (if they didn't disable it). It also checks for CPU and OS types, which could be the reason for the false positive. [Here](https://bstats.org/plugin/bukkit/TAB%20Reborn/5304) is one of TAB's pages as an example.
|
||||
|
||||
Windows defender could be flagging it for the same reason.
|
||||
@@ -5,7 +5,6 @@ Combine the knowledge gathered by reading the wiki to get interesting results, m
|
||||
* [Showing player version in tablist](#showing-player-version-in-tablist)
|
||||
* [Replicating Purpur's TPSBar](#replicating-purpurs-tpsbar)
|
||||
* [BedWars1058 compatibility](#bedwars1058-compatibility)
|
||||
* [Moving nametag when player wears a hat](#moving-nametag-when-player-wears-a-hat)
|
||||
* [Displaying AFK status in tablist](#displaying-afk-status-in-tablist)
|
||||
* [Showing combined online player count from multiple servers](#showing-combined-online-player-count-from-multiple-servers)
|
||||
* [Option 1 - Using global playerlist](#option-1---using-global-playerlist)
|
||||
@@ -131,98 +130,6 @@ conditions:
|
||||
```
|
||||
And then do `/tab group _DEFAULT_ tabprefix %condition:rankOrTeam%` and `/tab group _DEFAULT_ tagprefix %condition:rankOrTeam%` so it shows in your nametag and in your tablist name.
|
||||
|
||||
# Moving nametag when player wears a hat
|
||||
Unfortunately, there is no simple solution to this problem. We are going to use conditions with [unlimited nametag mode](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Unlimited-nametag-mode) to reach our results.
|
||||
|
||||
First, enable [unlimited nametag mode](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Unlimited-nametag-mode) by setting
|
||||
```
|
||||
scoreboard-teams:
|
||||
enabled: true
|
||||
unlimited-nametag-mode:
|
||||
enabled: true
|
||||
```
|
||||
in config.yml.
|
||||
|
||||
Second, define static lines, which will be used in case player has a hat. Let's make them 1 block higher than normal (hint: default space between lines is 0.26 blocks).
|
||||
```
|
||||
unlimited-nametag-mode:
|
||||
enabled: true
|
||||
dynamic-lines:
|
||||
- abovenameNormal
|
||||
- nametagNormal
|
||||
- belownameNormal
|
||||
static-lines:
|
||||
abovenameHat: 1.52
|
||||
nametagHat: 1.26
|
||||
belownameHat: 1
|
||||
```
|
||||
|
||||
Now, we are going to assume your configuration is [taking prefixes/suffixes from permission plugin](#taking-prefixessuffixes-from-permission-plugin), because otherwise we would end up with a TON of conditions, which would take too much brain power create and maintain. Let's create 2 conditions: 1 for having a hat, 1 for not having a hat. In the example, we will use `%magicosmetics_equipped_HAT%` placeholder. It return `true` if player has a hat, `false` if not.
|
||||
```
|
||||
conditions:
|
||||
nametagNormal:
|
||||
conditions:
|
||||
- '%magicosmetics_equipped_HAT%=false'
|
||||
true: '%vault-prefix%%player%%vault-suffix%'
|
||||
false: ""
|
||||
nametagHat:
|
||||
conditions:
|
||||
- '%magicosmetics_equipped_HAT%=true'
|
||||
true: '%vault-prefix%%player%%vault-suffix%'
|
||||
false: ""
|
||||
```
|
||||
Let's use it in groups.yml
|
||||
```
|
||||
_DEFAULT_:
|
||||
nametagNormal: "%condition:nametagNormal%"
|
||||
nametagHat: "%condition:nametagHat%"
|
||||
```
|
||||
Obviously don't forget to add your tabprefix and stuff. If you are using abovename and/or belowname as well, do the same for those lines as well. We already created static lines for them, let's create conditions for them.
|
||||
```
|
||||
conditions:
|
||||
nametagNormal:
|
||||
conditions:
|
||||
- '%magicosmetics_equipped_HAT%=false'
|
||||
true: '%vault-prefix%%player%%vault-suffix%'
|
||||
false: ""
|
||||
nametagHat:
|
||||
conditions:
|
||||
- '%magicosmetics_equipped_HAT%=true'
|
||||
true: '%vault-prefix%%player%%vault-suffix%'
|
||||
false: ""
|
||||
abovenameNormal:
|
||||
conditions:
|
||||
- '%magicosmetics_equipped_HAT%=false'
|
||||
true: 'abovename text'
|
||||
false: ""
|
||||
abovenameHat:
|
||||
conditions:
|
||||
- '%magicosmetics_equipped_HAT%=true'
|
||||
true: 'abovename text'
|
||||
false: ""
|
||||
belownameNormal:
|
||||
conditions:
|
||||
- '%magicosmetics_equipped_HAT%=false'
|
||||
true: 'belownametext'
|
||||
false: ""
|
||||
belownameHat:
|
||||
conditions:
|
||||
- '%magicosmetics_equipped_HAT%=true'
|
||||
true: 'belowname text'
|
||||
false: ""
|
||||
```
|
||||
and use in groups.yml
|
||||
```
|
||||
_DEFAULT_:
|
||||
nametagNormal: "%condition:nametagNormal%"
|
||||
nametagHat: "%condition:nametagHat%"
|
||||
abovenameNormal: "%condition:abovenameNormal%"
|
||||
abovenameHat: "%condition:abovenameHat%"
|
||||
belownameNormal: "%condition:belownameNormal%"
|
||||
belownameHat: "%condition:belownameHat%"
|
||||
```
|
||||
If you are not using all 3 lines, you can also remove them from static lines. You may also need to adjust static height, since you may want different height of the prefix+name+suffix line based on if you have belowname or not, to avoid the line being too high.
|
||||
|
||||
# Displaying AFK status in tablist
|
||||
First, you'll need to get an AFK plugin. TAB does not track player AFK status. Then, find a PlaceholderAPI placeholder for your AFK plugin in their documentation, such as
|
||||
* `%essentials_afk%` for Essentials
|
||||
|
||||
@@ -16,7 +16,7 @@ playerdata.yml file (for toggling scoreboard/bossbar and remembering decision) i
|
||||
All tables from TAB are prefixed with `tab_`, currently `tab_groups` for groups and `tab_users` for users.
|
||||
|
||||
# Availability
|
||||
Before attempting to use the function, make sure your server software has an integrated MySQL driver. TAB does not contain it by itself. See compatibility matrix below for current information about compatibility:
|
||||
Before attempting to use the function, make sure your server software has an integrated MySQL driver. TAB does not contain it by itself. See compatibility table below for current information about compatibility:
|
||||
|
||||
| Platform | Available |
|
||||
| ------------- | ------------- |
|
||||
|
||||
@@ -9,8 +9,9 @@ The usage can be checked at any time using [**/tab cpu**](https://github.com/NEZ
|
||||
* [#5 - [All platforms] Disable tablist name formatting](#5---all-platforms-disable-tablist-name-formatting)
|
||||
* [#6 - [All platforms] Increase permission refresh interval](#6---all-platforms-increase-permission-refresh-interval)
|
||||
* [#7 - [BungeeCord / Velocity] Disable TAB expansion](#7---bungeecord--velocity-disable-tab-expansion)
|
||||
* [#8 - [BungeeCord] Disable ByteBuf deserialization](#8---bungeecord-disable-bytebuf-deserialization)
|
||||
* [#9 - [Velocity] Disable header/footer translation](#9---velocity-disable-headerfooter-translation)
|
||||
* [#8 - [BungeeCord / Velocity] Disable update-latency option in global playerlist](#8---bungeecord--velocity-disable-update-latency-option-in-global-playerlist)
|
||||
* [#9 - [BungeeCord] Disable ByteBuf deserialization](#9---bungeecord-disable-bytebuf-deserialization)
|
||||
* [#10 - [Velocity] Disable header/footer translation](#10---velocity-disable-headerfooter-translation)
|
||||
|
||||
# #1 - [All platforms] Static text instead of placeholders
|
||||
Static text doesn't need to be refreshed periorically, 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 permission plugin.
|
||||
@@ -81,8 +82,10 @@ Disabling TAB's PlaceholderAPI expansion if you don't use it improves performanc
|
||||
placeholders:
|
||||
register-tab-expansion: false
|
||||
```
|
||||
# #8 - [BungeeCord / Velocity] Disable update-latency option in global playerlist
|
||||
When [Global playerlist](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Global-playerlist) is enabled, you can enable / disable [update-latency](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Global-playerlist#:~:text=other%20unlisted%20servers.-,update%2Dlatency,-false) option (open the link for detailed description of the option). Disabling this option massively improves performance.
|
||||
|
||||
# #8 - [BungeeCord] Disable ByteBuf deserialization
|
||||
# #9 - [BungeeCord] Disable ByteBuf deserialization
|
||||
In order for anti-override to work properly, some packets must be manually deserialized due to BungeeCord not doing it. If you don't need it, you can disable those functions, which will also disable the deserialization completely. To do so, you must disable 2 functions (only having 1 disabled is not enough):
|
||||
#1 - Anti-override for teams
|
||||
```
|
||||
@@ -92,13 +95,9 @@ scoreboard-teams:
|
||||
Whether you need anti-override or not depends on configuration of your plugins. The easiest way is to have it enabled and see if you get `anti-override.log` file in TAB folder. If you don't, you don't need it. If you do, you'll need to do some configuring. [Here](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Sorting-players-in-tablist#additional-note-3---compatibility-issues-with-other-plugins) are a few examples of software and their team names, which you may find helpful.
|
||||
|
||||
#2 - Scoreboard detection of other plugins
|
||||
```
|
||||
scoreboard:
|
||||
respect-other-plugins: false
|
||||
```
|
||||
TAB checks for packets sent by other plugins and if another plugin sends a scoreboard, TAB hides its own and resends it after the other plugin removes it. If you don't use scoreboard feature, this option is naturally disabled. If you do, but don't need compatibility with other plugins, you can disable this option.
|
||||
TAB checks for packets sent by other plugins and if another plugin sends a scoreboard, TAB hides its own and resends it after the other plugin removes it. This check cannot be disabled if scoreboard is enabled.
|
||||
|
||||
# #9 - [Velocity] Disable header/footer translation
|
||||
# #10 - [Velocity] Disable header/footer translation
|
||||
Velocity offers header/footer translation of translatable components, however, TAB does not use these. Despite that, attempts to "translate" it are still there, causing the whole feature to use up to 3x more CPU than it would without it. There is no way to disable this inside Velocit itself, however, there is a third party [fork](https://github.com/GemstoneGG/Velocity-CTD/) that offers it.
|
||||
**Use at your own risk**.
|
||||
After installing, default velocity.toml file will contain `translate-header-footer = true` option. If just upgrading, you will need to add it there yourself. Setting it to `false` will disable this function.
|
||||
@@ -92,17 +92,11 @@ Full list of placeholders:
|
||||
| %tab_tagprefix% | Current tagprefix with replaced placeholders |
|
||||
| %tab_tagsuffix% | Current tagsuffix with replaced placeholders |
|
||||
| %tab_customtabname% | Current customtabname with replaced placeholders |
|
||||
| %tab_customtagname% | Current customtagname with replaced placeholders |
|
||||
| %tab_belowname% | Current belowname with replaced placeholders |
|
||||
| %tab_abovename% | Current abovename with replaced placeholders |
|
||||
| %tab_tabprefix_raw% | Raw tabprefix containing unparsed placeholders |
|
||||
| %tab_tabsuffix_raw% | Raw tabsuffix containing unparsed placeholders |
|
||||
| %tab_tagprefix_raw% | Raw tagprefix containing unparsed placeholders |
|
||||
| %tab_tagsuffix_raw% | Raw tagsuffix containing unparsed placeholders |
|
||||
| %tab_customtabname_raw% | Raw customtabname containing unparsed placeholders |
|
||||
| %tab_customtagname_raw% | Raw customtagname containing unparsed placeholders |
|
||||
| %tab_belowname_raw% | Raw belowname containing unparsed placeholders |
|
||||
| %tab_abovename_raw% | Raw abovename containing unparsed placeholders |
|
||||
| %tab_scoreboard_name% | Returns name of player's currently displayed scoreboard or empty string if none is displayed due to no display condition being met |
|
||||
| %tab_scoreboard_visible% | "Enabled" if visible, "Disabled" if not |
|
||||
| %tab_bossbar_visible% | "Enabled" if visible, "Disabled" if not |
|
||||
|
||||
@@ -6,20 +6,11 @@ PlaceholderAPI is a standalone bukkit plugin which can be installed just like an
|
||||
|
||||
# Expansions
|
||||
In order to effectively scale and allow the level of modification it does, PlaceholderAPI is using placeholder packages called expansions. They are loaded into the plugin on startup. Each expansion can be created and modified independently of the main plugin's update frequency. Each expansion has a name, which is used to identify its placeholders.
|
||||
The syntax is `%<expansion name>_<placeholder>%`, where `<expansion name>` is name of the expansion, for example `player` and `<placeholder>` is placeholder of the expansion, for example `health`, which returns player's health. The full placeholder syntax will then be `%player_health%`.
|
||||
You can therefore guess name of the expansion which a placeholder you are trying to use comes from. You have 2 ways of getting expansions. Which one it is for your specific placeholder expansion should be included in documentation.
|
||||
The syntax is `%<expansion>_<placeholder>%`, where `<expansion>` is name of the expansion, for example `player` and `<placeholder>` is placeholder of the expansion, for example `health`, which returns player's health. The full placeholder syntax will then be `%player_health%`.
|
||||
|
||||
## External expansions
|
||||
First way is using external jars, which are stored on [PlaceholderAPI website](https://api.extendedclip.com/all/). You can get them by either manually downloading them into `plugins/PlaceholderAPI/expansions/` folder, or by running `/papi ecloud download <expansion name>`.
|
||||
To load new expansions, run `/papi reload`.
|
||||
Some plugins offer their own expansions, which is either integrated into the plugin itself or external on PlaceholderAPI cloud. Integrated expansions are not downloaded, instead, plugins register them automatically.
|
||||
|
||||
## Expansions in plugins
|
||||
PlaceholderAPI expansion is in the end just a java class (or a set of classes), which can also be included in the plugin they are hooking into. If wiki of the plugin says something like "no download command" or "expansion is included in the plugin", it's this case. You can also identify this case by trying the previous method and download command saying expansion was not found.
|
||||
If no further installation steps are provided, these expansions are automatically enabled when their respective plugin is installed.
|
||||
|
||||
# Managing installed expansions
|
||||
To see what expansions are currently enabled, run `/papi list`. It will show list of all currently enabled expansions. If you downloaded an expansion but it's not in the list, it may have failed to enable due to an error. Check console if you got any errors on startup.
|
||||
Updating expansions can be done in the same way as installing them. For external expansions, run the download command or download it manually and reload. For expansions in plugins, they always contain "latest" (the only) version of the expansion, so if a new placeholder was added, you need to update the plugin.
|
||||
You can download / update external expansions using `/papi ecloud download <expansion>`. After downloading it, run `/papi reload` to enable it.
|
||||
|
||||
# Checking if placeholder is working
|
||||
To check if a placeholder is working, run `/papi parse <player> <placeholder>`. You can also use `me` as player, which parses placeholder for the player who ran the command. If the command outputs the identifier you entered and not the actual output, it means you either entered and invalid placeholder or you are missing an expansion (or you have an outdated version of the expansion), see above.
|
||||
@@ -13,7 +13,6 @@ Installation is as simple as it can be. Put it into plugins folder of your backe
|
||||
* Forwarding vanish status for compatibility with global playerlist / layout features
|
||||
* Forward player's world for %world% and per-world settings
|
||||
* Allow TAB's PlaceholderAPI expansion with TAB on BungeeCord
|
||||
* Encodes scoreboard packets (if TAB is installed on Velocity, which doesn't offer them)
|
||||
* Forward player's gamemode for spectator fix feature to work
|
||||
|
||||
# Compatibility
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
package me.neznamy.tab.api.integration;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
public abstract class VanishIntegration {
|
||||
|
||||
/** List of all registered VanishIntegration handlers */
|
||||
private static final List<VanishIntegration> HANDLERS = new ArrayList<>();
|
||||
|
||||
/** Name of the plugin this integration is associated with */
|
||||
@NotNull
|
||||
private final String plugin;
|
||||
|
||||
/**
|
||||
* Determines if a viewer can see a target player.
|
||||
*
|
||||
* @param viewer
|
||||
* The player attempting to view another player
|
||||
* @param target
|
||||
* The player being viewed
|
||||
* @return {@code true} if the viewer can see the target, {@code false} otherwise
|
||||
*/
|
||||
public abstract boolean canSee(@NotNull TabPlayer viewer, @NotNull TabPlayer target);
|
||||
|
||||
/**
|
||||
* Checks if the specified player is in a vanished state.
|
||||
*
|
||||
* @param player
|
||||
* The player to check
|
||||
* @return {@code true} if the player is vanished, {@code false} otherwise
|
||||
*/
|
||||
public abstract boolean isVanished(@NotNull TabPlayer player);
|
||||
|
||||
/**
|
||||
* Registers this integration handler to the global list of handlers.
|
||||
*/
|
||||
public void register() {
|
||||
registerHandler(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters this integration handler from the global list of handlers.
|
||||
*/
|
||||
public void unregister() {
|
||||
unregisterHandler(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a VanishIntegration handler to the global list of handlers.
|
||||
*
|
||||
* @param handler
|
||||
* The handler to register
|
||||
*/
|
||||
public static void registerHandler(@NotNull VanishIntegration handler) {
|
||||
HANDLERS.add(handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a VanishIntegration handler from the global list of handlers.
|
||||
*
|
||||
* @param handler
|
||||
* The handler to unregister
|
||||
*/
|
||||
public static void unregisterHandler(@NotNull VanishIntegration handler) {
|
||||
HANDLERS.remove(handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the list of all registered VanishIntegration handlers.
|
||||
*
|
||||
* @return A list of registered VanishIntegration handlers
|
||||
*/
|
||||
@NotNull
|
||||
public static List<VanishIntegration> getHandlers() {
|
||||
return HANDLERS;
|
||||
}
|
||||
}
|
||||
@@ -5,5 +5,5 @@ plugins {
|
||||
dependencies {
|
||||
implementation("com.github.johnrengelman:shadow:8.1.1")
|
||||
implementation("io.freefair.gradle:lombok-plugin:8.6")
|
||||
implementation("fabric-loom:fabric-loom.gradle.plugin:1.6-SNAPSHOT")
|
||||
implementation("fabric-loom:fabric-loom.gradle.plugin:1.8-SNAPSHOT")
|
||||
}
|
||||
@@ -4,7 +4,7 @@ plugins {
|
||||
|
||||
allprojects {
|
||||
group = "me.neznamy"
|
||||
version = "5.0.0"
|
||||
version = "5.0.2"
|
||||
description = "An all-in-one solution that works"
|
||||
|
||||
ext.set("id", "tab")
|
||||
@@ -22,7 +22,8 @@ val platforms = setOf(
|
||||
projects.fabric,
|
||||
projects.fabric.v1144,
|
||||
projects.fabric.v1182,
|
||||
projects.fabric.v1203
|
||||
projects.fabric.v1203,
|
||||
projects.fabric.v1213
|
||||
).map { it.dependencyProject }
|
||||
|
||||
val special = setOf(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
dependencies {
|
||||
implementation(projects.shared)
|
||||
implementation("org.bstats:bstats-bukkit:3.0.1")
|
||||
api(projects.shared)
|
||||
implementation("org.bstats:bstats-bukkit:3.1.0")
|
||||
compileOnly("org.purpurmc.purpur:purpur-api:1.16.5-R0.1-SNAPSHOT")
|
||||
compileOnly("me.clip:placeholderapi:2.11.6")
|
||||
compileOnly("com.mojang:authlib:1.5.25")
|
||||
|
||||
@@ -9,11 +9,10 @@ repositories {
|
||||
maven("https://oss.sonatype.org/content/repositories/snapshots")
|
||||
}
|
||||
|
||||
val version = "1.21.1-R0.1-SNAPSHOT"
|
||||
val version = "1.21.3-R0.1-SNAPSHOT"
|
||||
|
||||
dependencies {
|
||||
implementation(projects.bukkit)
|
||||
implementation(projects.shared) // No idea why is this needed when Bukkit already contains it
|
||||
paperweight.paperDevBundle(version)
|
||||
compileOnly("io.papermc.paper:paper-api:${version}")
|
||||
}
|
||||
|
||||
@@ -12,6 +12,6 @@ import org.bukkit.craftbukkit.entity.CraftPlayer;
|
||||
public class PaperLoader {
|
||||
|
||||
/** Channel getter using direct NMS code */
|
||||
public static FunctionWithException<BukkitTabPlayer, Channel> getChannel =
|
||||
public static final FunctionWithException<BukkitTabPlayer, Channel> getChannel =
|
||||
player -> ((CraftPlayer)player.getPlayer()).getHandle().connection.connection.channel;
|
||||
}
|
||||
@@ -24,16 +24,17 @@ import java.lang.reflect.Field;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* TabList implementation using direct mojang-mapped code for versions 1.20.5+.
|
||||
* TabList implementation using direct mojang-mapped code for versions 1.21.2+.
|
||||
*/
|
||||
@SuppressWarnings("unused") // Used via reflection
|
||||
public class PaperPacketTabList extends TabListBase<Component> {
|
||||
|
||||
private final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateDisplayName = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME);
|
||||
private final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateLatency = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY);
|
||||
private final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateGameMode = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE);
|
||||
private final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateListed = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED);
|
||||
private 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> addPlayer = EnumSet.allOf(ClientboundPlayerInfoUpdatePacket.Action.class);
|
||||
|
||||
private static final Field entries;
|
||||
|
||||
@@ -63,43 +64,50 @@ public class PaperPacketTabList extends TabListBase<Component> {
|
||||
@Override
|
||||
public void updateDisplayName(@NonNull UUID entry, @Nullable Component displayName) {
|
||||
sendPacket(new ClientboundPlayerInfoUpdatePacket(updateDisplayName, new ClientboundPlayerInfoUpdatePacket.Entry(
|
||||
entry, null, false, 0, null, displayName, null
|
||||
entry, null, false, 0, null, displayName, 0, null
|
||||
)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateLatency(@NonNull UUID entry, int latency) {
|
||||
sendPacket(new ClientboundPlayerInfoUpdatePacket(updateLatency, new ClientboundPlayerInfoUpdatePacket.Entry(
|
||||
entry, null, false, latency, null, null, null
|
||||
entry, null, false, latency, null, null, 0, null
|
||||
)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateGameMode(@NonNull UUID entry, int gameMode) {
|
||||
sendPacket(new ClientboundPlayerInfoUpdatePacket(updateGameMode, new ClientboundPlayerInfoUpdatePacket.Entry(
|
||||
entry, null, false, 0, GameType.byId(gameMode), null, null
|
||||
entry, null, false, 0, GameType.byId(gameMode), null, 0, null
|
||||
)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateListed(@NonNull UUID entry, boolean listed) {
|
||||
sendPacket(new ClientboundPlayerInfoUpdatePacket(updateListed, new ClientboundPlayerInfoUpdatePacket.Entry(
|
||||
entry, null, listed, 0, null, null, null
|
||||
entry, null, listed, 0, null, null, 0, null
|
||||
)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateListOrder(@NonNull UUID entry, int listOrder) {
|
||||
// TODO update module to 1.21.2 when it comes out
|
||||
sendPacket(new ClientboundPlayerInfoUpdatePacket(updateListOrder, new ClientboundPlayerInfoUpdatePacket.Entry(
|
||||
entry, null, false, 0, null, null, listOrder, null
|
||||
)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateHat(@NonNull UUID entry, boolean showHat) {
|
||||
// TODO update module to 1.21.4 when it comes out
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addEntry(@NonNull UUID id, @NonNull String name, @Nullable Skin skin, boolean listed, int latency,
|
||||
int gameMode, @Nullable Component displayName, int listOrder) {
|
||||
int gameMode, @Nullable Component displayName, int listOrder, boolean showHat) {
|
||||
sendPacket(new ClientboundPlayerInfoUpdatePacket(addPlayer, new ClientboundPlayerInfoUpdatePacket.Entry(
|
||||
id, createProfile(id, name, skin), listed, latency, GameType.byId(gameMode), displayName, null
|
||||
id, createProfile(id, name, skin), listed, latency, GameType.byId(gameMode), displayName, listOrder, null
|
||||
)));
|
||||
// TODO update module to 1.21.2 when it comes out
|
||||
// TODO update module to 1.21.4 when it comes out
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -136,7 +144,7 @@ public class PaperPacketTabList extends TabListBase<Component> {
|
||||
TAB.getInstance().getFeatureManager().onEntryAdd(player, nmsData.profileId(), nmsData.profile().getName());
|
||||
}
|
||||
updatedList.add(rewriteEntry ? new ClientboundPlayerInfoUpdatePacket.Entry(
|
||||
nmsData.profileId(), nmsData.profile(), nmsData.listed(), latency, nmsData.gameMode(), displayName, nmsData.chatSession()
|
||||
nmsData.profileId(), nmsData.profile(), nmsData.listed(), latency, nmsData.gameMode(), displayName, nmsData.listOrder(), nmsData.chatSession()
|
||||
) : nmsData);
|
||||
}
|
||||
if (rewritePacket) entries.set(info, updatedList);
|
||||
|
||||
@@ -19,6 +19,16 @@ import java.util.EnumSet;
|
||||
*/
|
||||
public class BukkitPipelineInjector extends NettyPipelineInjector {
|
||||
|
||||
/** Versions supported by paper module that uses direct mojang-mapped NMS for latest MC version */
|
||||
private static final EnumSet<ProtocolVersion> paperNativeVersions = EnumSet.of(
|
||||
ProtocolVersion.V1_20_5,
|
||||
ProtocolVersion.V1_20_6,
|
||||
ProtocolVersion.V1_21,
|
||||
ProtocolVersion.V1_21_1,
|
||||
ProtocolVersion.V1_21_2,
|
||||
ProtocolVersion.V1_21_3
|
||||
);
|
||||
|
||||
/** Function for getting player's channel */
|
||||
@Setter
|
||||
private static FunctionWithException<BukkitTabPlayer, Channel> getChannel;
|
||||
@@ -40,13 +50,7 @@ public class BukkitPipelineInjector extends NettyPipelineInjector {
|
||||
@SuppressWarnings("unchecked")
|
||||
public static void tryLoad(@NotNull ProtocolVersion serverVersion) {
|
||||
try {
|
||||
boolean versionCheck = EnumSet.of(
|
||||
ProtocolVersion.V1_20_5,
|
||||
ProtocolVersion.V1_20_6,
|
||||
ProtocolVersion.V1_21,
|
||||
ProtocolVersion.V1_21_1
|
||||
).contains(serverVersion);
|
||||
if (ReflectionUtils.classExists("org.bukkit.craftbukkit.CraftServer") && versionCheck) {
|
||||
if (ReflectionUtils.classExists("org.bukkit.craftbukkit.CraftServer") && paperNativeVersions.contains(serverVersion)) {
|
||||
getChannel = (FunctionWithException<BukkitTabPlayer, Channel>) Class.forName("me.neznamy.tab.platforms.paper.PaperLoader").getDeclaredField("getChannel").get(null);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package me.neznamy.tab.platforms.bukkit.features;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitUtils;
|
||||
import me.neznamy.tab.shared.config.files.config.PerWorldPlayerListConfiguration;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.features.PerWorldPlayerListConfiguration;
|
||||
import me.neznamy.tab.shared.features.types.Loadable;
|
||||
import me.neznamy.tab.shared.features.types.TabFeature;
|
||||
import me.neznamy.tab.shared.features.types.UnLoadable;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
@@ -15,12 +15,11 @@ import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerChangedWorldEvent;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import me.neznamy.tab.shared.features.types.TabFeature;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
/**
|
||||
* Per-world-PlayerList feature handler
|
||||
*/
|
||||
@@ -115,10 +114,10 @@ public class PerWorldPlayerList extends TabFeature implements Listener, Loadable
|
||||
*/
|
||||
private boolean shouldSee(@NotNull Player viewer, @NotNull Player target) {
|
||||
if (target == viewer) return true;
|
||||
if ((configuration.allowBypassPermission && viewer.hasPermission(TabConstants.Permission.PER_WORLD_PLAYERLIST_BYPASS)) || configuration.ignoredWorlds.contains(viewer.getWorld().getName())) return true;
|
||||
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";
|
||||
for (Entry<String, List<String>> group : configuration.sharedWorlds.entrySet()) {
|
||||
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();
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package me.neznamy.tab.platforms.bukkit.hook;
|
||||
|
||||
import de.myzelyam.api.vanish.VanishAPI;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.shared.hook.PremiumVanishHook;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
@@ -12,7 +12,7 @@ import org.jetbrains.annotations.NotNull;
|
||||
public class BukkitPremiumVanishHook extends PremiumVanishHook {
|
||||
|
||||
@Override
|
||||
public synchronized boolean canSee(@NotNull TabPlayer viewer, @NotNull TabPlayer target) {
|
||||
public boolean canSee(@NotNull TabPlayer viewer, @NotNull TabPlayer target) {
|
||||
return VanishAPI.canSee(((BukkitTabPlayer)viewer).getPlayer(), ((BukkitTabPlayer)target).getPlayer());
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,28 @@
|
||||
package me.neznamy.tab.platforms.bukkit.nms.converter;
|
||||
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.shared.chat.TabComponent;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.EnumSet;
|
||||
|
||||
/**
|
||||
* Interface for converting TAB component into NMS components (1.7+).
|
||||
*/
|
||||
public abstract class ComponentConverter {
|
||||
|
||||
/** Versions supported by paper module that uses direct mojang-mapped NMS for latest MC version */
|
||||
private static final EnumSet<ProtocolVersion> paperNativeVersions = EnumSet.of(
|
||||
ProtocolVersion.V1_20_5,
|
||||
ProtocolVersion.V1_20_6,
|
||||
ProtocolVersion.V1_21,
|
||||
ProtocolVersion.V1_21_1,
|
||||
ProtocolVersion.V1_21_2,
|
||||
ProtocolVersion.V1_21_3
|
||||
);
|
||||
|
||||
/** Instance of this class */
|
||||
@Nullable
|
||||
public static ComponentConverter INSTANCE;
|
||||
@@ -24,4 +38,21 @@ public abstract class ComponentConverter {
|
||||
*/
|
||||
@NotNull
|
||||
public abstract Object convert(@NotNull TabComponent component, boolean modern);
|
||||
|
||||
/**
|
||||
* Attempts to load component converter.
|
||||
*
|
||||
* @param serverVersion
|
||||
* Server version
|
||||
*/
|
||||
public static void tryLoad(@NotNull ProtocolVersion serverVersion) {
|
||||
try {
|
||||
if (ReflectionUtils.classExists("org.bukkit.craftbukkit.CraftServer") && paperNativeVersions.contains(serverVersion)) {
|
||||
INSTANCE = (ComponentConverter) Class.forName("me.neznamy.tab.platforms.paper.PaperComponentConverter").getConstructor().newInstance();
|
||||
} else {
|
||||
INSTANCE = new ReflectionComponentConverter();
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@ package me.neznamy.tab.platforms.bukkit.nms.converter;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.shared.chat.ChatModifier;
|
||||
import me.neznamy.tab.shared.chat.SimpleComponent;
|
||||
import me.neznamy.tab.shared.chat.StructuredComponent;
|
||||
@@ -14,7 +13,6 @@ import org.jetbrains.annotations.NotNull;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
@@ -47,7 +45,7 @@ public class ReflectionComponentConverter extends ComponentConverter {
|
||||
* @throws ReflectiveOperationException
|
||||
* If something failed
|
||||
*/
|
||||
private ReflectionComponentConverter() throws ReflectiveOperationException {
|
||||
public ReflectionComponentConverter() throws ReflectiveOperationException {
|
||||
Class<?> IChatBaseComponent = BukkitReflection.getClass("network.chat.Component", "network.chat.IChatBaseComponent", "IChatBaseComponent");
|
||||
if (BukkitReflection.getMinorVersion() >= 19) {
|
||||
Method IChatBaseComponent_b = ReflectionUtils.getMethod(IChatBaseComponent, new String[] {"b", "literal"}, String.class);
|
||||
@@ -132,27 +130,4 @@ public class ReflectionComponentConverter extends ComponentConverter {
|
||||
if (modifier.isObfuscated()) magicCodes.get(4).set(nmsModifier, true);
|
||||
return nmsModifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to load component converter.
|
||||
*
|
||||
* @param serverVersion
|
||||
* Server version
|
||||
*/
|
||||
public static void tryLoad(@NotNull ProtocolVersion serverVersion) {
|
||||
try {
|
||||
boolean versionCheck = EnumSet.of(
|
||||
ProtocolVersion.V1_20_5,
|
||||
ProtocolVersion.V1_20_6,
|
||||
ProtocolVersion.V1_21,
|
||||
ProtocolVersion.V1_21_1
|
||||
).contains(serverVersion);
|
||||
if (ReflectionUtils.classExists("org.bukkit.craftbukkit.CraftServer") && versionCheck) {
|
||||
INSTANCE = (ComponentConverter) Class.forName("me.neznamy.tab.platforms.paper.PaperComponentConverter").getConstructor().newInstance();
|
||||
} else {
|
||||
INSTANCE = new ReflectionComponentConverter();
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,34 +6,32 @@ import me.clip.placeholderapi.PlaceholderAPI;
|
||||
import me.neznamy.tab.platforms.bukkit.*;
|
||||
import me.neznamy.tab.platforms.bukkit.bossbar.BukkitBossBar;
|
||||
import me.neznamy.tab.platforms.bukkit.bossbar.ViaBossBar;
|
||||
import me.neznamy.tab.platforms.bukkit.features.BukkitTabExpansion;
|
||||
import me.neznamy.tab.platforms.bukkit.features.PerWorldPlayerList;
|
||||
import me.neznamy.tab.platforms.bukkit.header.HeaderFooter;
|
||||
import me.neznamy.tab.platforms.bukkit.hook.BukkitPremiumVanishHook;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.converter.ComponentConverter;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.PingRetriever;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.converter.ReflectionComponentConverter;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.converter.ComponentConverter;
|
||||
import me.neznamy.tab.platforms.bukkit.scoreboard.ScoreboardLoader;
|
||||
import me.neznamy.tab.platforms.bukkit.tablist.TabListBase;
|
||||
import me.neznamy.tab.shared.GroupManager;
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.backend.BackendPlatform;
|
||||
import me.neznamy.tab.shared.chat.EnumChatFormat;
|
||||
import me.neznamy.tab.shared.chat.StructuredComponent;
|
||||
import me.neznamy.tab.shared.chat.SimpleComponent;
|
||||
import me.neznamy.tab.shared.chat.StructuredComponent;
|
||||
import me.neznamy.tab.shared.chat.TabComponent;
|
||||
import me.neznamy.tab.shared.config.files.config.PerWorldPlayerListConfiguration;
|
||||
import me.neznamy.tab.shared.features.PerWorldPlayerListConfiguration;
|
||||
import me.neznamy.tab.shared.features.PlaceholderManagerImpl;
|
||||
import me.neznamy.tab.shared.features.injection.PipelineInjector;
|
||||
import me.neznamy.tab.shared.features.types.TabFeature;
|
||||
import me.neznamy.tab.platforms.bukkit.features.BukkitTabExpansion;
|
||||
import me.neznamy.tab.platforms.bukkit.features.PerWorldPlayerList;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.backend.BackendPlatform;
|
||||
import me.neznamy.tab.shared.features.PlaceholderManagerImpl;
|
||||
import me.neznamy.tab.shared.hook.LuckPermsHook;
|
||||
import me.neznamy.tab.shared.hook.PremiumVanishHook;
|
||||
import me.neznamy.tab.shared.placeholders.types.PlayerPlaceholderImpl;
|
||||
import me.neznamy.tab.shared.placeholders.expansion.EmptyTabExpansion;
|
||||
import me.neznamy.tab.shared.placeholders.expansion.TabExpansion;
|
||||
import me.neznamy.tab.shared.placeholders.types.PlayerPlaceholderImpl;
|
||||
import me.neznamy.tab.shared.platform.BossBar;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
@@ -99,10 +97,10 @@ public class BukkitPlatform implements BackendPlatform {
|
||||
//not spigot
|
||||
}
|
||||
if (Bukkit.getPluginManager().isPluginEnabled("PremiumVanish")) {
|
||||
PremiumVanishHook.setInstance(new BukkitPremiumVanishHook());
|
||||
new BukkitPremiumVanishHook().register();
|
||||
}
|
||||
PingRetriever.tryLoad();
|
||||
ReflectionComponentConverter.tryLoad(serverVersion);
|
||||
ComponentConverter.tryLoad(serverVersion);
|
||||
ScoreboardLoader.findInstance(serverVersion);
|
||||
TabListBase.findInstance(serverVersion);
|
||||
if (BukkitReflection.getMinorVersion() >= 8) {
|
||||
@@ -294,6 +292,16 @@ public class BukkitPlatform implements BackendPlatform {
|
||||
return TabListBase.getInstance().apply((BukkitTabPlayer) player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsNumberFormat() {
|
||||
return serverVersion.getNetworkId() >= ProtocolVersion.V1_20_3.getNetworkId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsListOrder() {
|
||||
return serverVersion.getNetworkId() >= ProtocolVersion.V1_21_2.getNetworkId();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public GroupManager detectPermissionPlugin() {
|
||||
@@ -338,11 +346,16 @@ public class BukkitPlatform implements BackendPlatform {
|
||||
Bukkit.getScheduler().runTask(plugin, task);
|
||||
}
|
||||
|
||||
// Disabling this check, because vanish plugins might process hiding with a delay on join, resulting
|
||||
// in wrong result when called by TAB, causing vanished players to appear in layout for players who
|
||||
// just joined, in the worst case scenario not hiding shortly after if vanish plugin takes too long.
|
||||
/*
|
||||
@Override
|
||||
public boolean canSee(@NotNull TabPlayer viewer, @NotNull TabPlayer target) {
|
||||
if (BackendPlatform.super.canSee(viewer, target)) return true;
|
||||
return ((BukkitTabPlayer)viewer).getPlayer().canSee(((BukkitTabPlayer)target).getPlayer());
|
||||
}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Converts component to legacy string using bukkit RGB format if supported by both server and client.
|
||||
|
||||
@@ -24,6 +24,16 @@ import java.util.List;
|
||||
*/
|
||||
public class ScoreboardLoader {
|
||||
|
||||
/** Versions supported by paper module that uses direct mojang-mapped NMS for latest MC version */
|
||||
private static final EnumSet<ProtocolVersion> paperNativeVersions = EnumSet.of(
|
||||
ProtocolVersion.V1_20_5,
|
||||
ProtocolVersion.V1_20_6,
|
||||
ProtocolVersion.V1_21,
|
||||
ProtocolVersion.V1_21_1,
|
||||
ProtocolVersion.V1_21_2,
|
||||
ProtocolVersion.V1_21_3
|
||||
);
|
||||
|
||||
/** Instance function */
|
||||
@Getter
|
||||
@Setter
|
||||
@@ -38,13 +48,7 @@ public class ScoreboardLoader {
|
||||
*/
|
||||
@SneakyThrows
|
||||
public static void findInstance(@NotNull ProtocolVersion serverVersion) {
|
||||
boolean versionCheck = EnumSet.of(
|
||||
ProtocolVersion.V1_20_5,
|
||||
ProtocolVersion.V1_20_6,
|
||||
ProtocolVersion.V1_21,
|
||||
ProtocolVersion.V1_21_1
|
||||
).contains(serverVersion);
|
||||
if (ReflectionUtils.classExists("org.bukkit.craftbukkit.CraftServer") && versionCheck) {
|
||||
if (ReflectionUtils.classExists("org.bukkit.craftbukkit.CraftServer") && paperNativeVersions.contains(serverVersion)) {
|
||||
Constructor<?> constructor = Class.forName("me.neznamy.tab.platforms.paper.PaperPacketScoreboard").getConstructor(BukkitTabPlayer.class);
|
||||
instance = player -> (Scoreboard) constructor.newInstance(player);
|
||||
} else if (PacketScoreboard.isAvailable()) {
|
||||
|
||||
@@ -57,9 +57,14 @@ public class BukkitTabList extends TabListBase<String> {
|
||||
// Shrug
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateHat(@NonNull UUID entry, boolean showHat) {
|
||||
// Shrug
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addEntry(@NonNull UUID id, @NonNull String name, @Nullable Skin skin, boolean listed, int latency,
|
||||
int gameMode, @Nullable String displayName, int listOrder) {
|
||||
int gameMode, @Nullable String displayName, int listOrder, boolean showHat) {
|
||||
// Shrug
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ public class PacketTabList1193 extends PacketTabList18 {
|
||||
private static final Map<Action, EnumSet<?>> actionToEnumSet = new EnumMap<>(Action.class);
|
||||
|
||||
private static boolean v1_21_2Plus;
|
||||
private static boolean v1_21_4Plus;
|
||||
|
||||
private static Enum actionAddPlayer;
|
||||
private static Enum actionUpdateDisplayName;
|
||||
@@ -35,6 +36,7 @@ public class PacketTabList1193 extends PacketTabList18 {
|
||||
private static Field PlayerInfoData_UUID;
|
||||
private static Field PlayerInfoData_GameMode;
|
||||
private static Field PlayerInfoData_Listed;
|
||||
private static Field PlayerInfoData_ShowHat;
|
||||
private static Field PlayerInfoData_ListOrder;
|
||||
private static Field PlayerInfoData_RemoteChatSession;
|
||||
|
||||
@@ -74,7 +76,7 @@ public class PacketTabList1193 extends PacketTabList18 {
|
||||
|
||||
loadSharedContent(playerInfoDataClass, EnumGamemodeClass);
|
||||
|
||||
PlayerInfoData_Listed = ReflectionUtils.getOnlyField(playerInfoDataClass, boolean.class);
|
||||
PlayerInfoData_Listed = ReflectionUtils.getFields(playerInfoDataClass, boolean.class).get(0);
|
||||
PlayerInfoData_GameMode = ReflectionUtils.getOnlyField(playerInfoDataClass, EnumGamemodeClass);
|
||||
PlayerInfoData_RemoteChatSession = ReflectionUtils.getOnlyField(playerInfoDataClass, RemoteChatSession$Data);
|
||||
PlayerInfoData_UUID = ReflectionUtils.getOnlyField(playerInfoDataClass, UUID.class);
|
||||
@@ -91,10 +93,20 @@ public class PacketTabList1193 extends PacketTabList18 {
|
||||
actionToEnumSet.put(Action.UPDATE_LISTED, EnumSet.of(Enum.valueOf(ActionClass, Action.UPDATE_LISTED.name())));
|
||||
try {
|
||||
actionToEnumSet.put(Action.UPDATE_LIST_ORDER, EnumSet.of(Enum.valueOf(ActionClass, Action.UPDATE_LIST_ORDER.name())));
|
||||
newPlayerInfoData = playerInfoDataClass.getConstructor(UUID.class, GameProfile.class, boolean.class, int.class,
|
||||
EnumGamemodeClass, IChatBaseComponent, int.class, RemoteChatSession$Data);
|
||||
PlayerInfoData_ListOrder = ReflectionUtils.getFields(playerInfoDataClass, int.class).get(1);
|
||||
v1_21_2Plus = true;
|
||||
try {
|
||||
// 1.21.4+
|
||||
actionToEnumSet.put(Action.UPDATE_HAT, EnumSet.of(Enum.valueOf(ActionClass, Action.UPDATE_HAT.name())));
|
||||
PlayerInfoData_ShowHat = ReflectionUtils.getFields(playerInfoDataClass, boolean.class).get(1);
|
||||
newPlayerInfoData = playerInfoDataClass.getConstructor(UUID.class, GameProfile.class, boolean.class, int.class,
|
||||
EnumGamemodeClass, IChatBaseComponent, boolean.class, int.class, RemoteChatSession$Data);
|
||||
v1_21_4Plus = true;
|
||||
} catch (Exception ignored) {
|
||||
// 1.21.2 - 1.21.3
|
||||
newPlayerInfoData = playerInfoDataClass.getConstructor(UUID.class, GameProfile.class, boolean.class, int.class,
|
||||
EnumGamemodeClass, IChatBaseComponent, int.class, RemoteChatSession$Data);
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
// 1.21.1-, should have a better check
|
||||
newPlayerInfoData = playerInfoDataClass.getConstructor(UUID.class, GameProfile.class, boolean.class, int.class,
|
||||
@@ -111,14 +123,22 @@ public class PacketTabList1193 extends PacketTabList18 {
|
||||
@Override
|
||||
public void updateListed(@NonNull UUID entry, boolean listed) {
|
||||
packetSender.sendPacket(player,
|
||||
createPacket(Action.UPDATE_LISTED, entry, "", null, listed, 0, 0, null, 0));
|
||||
createPacket(Action.UPDATE_LISTED, entry, "", null, listed, 0, 0, null, 0, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateListOrder(@NonNull UUID entry, int listOrder) {
|
||||
if (player.getPlatform().getServerVersion().getNetworkId() >= ProtocolVersion.V1_21_2.getNetworkId()) {
|
||||
packetSender.sendPacket(player,
|
||||
createPacket(Action.UPDATE_LIST_ORDER, entry, "", null, false, 0, 0, null, listOrder));
|
||||
createPacket(Action.UPDATE_LIST_ORDER, entry, "", null, false, 0, 0, null, listOrder, false));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateHat(@NonNull UUID entry, boolean showHat) {
|
||||
if (player.getPlatform().getServerVersion().getNetworkId() >= ProtocolVersion.V1_21_4.getNetworkId()) {
|
||||
packetSender.sendPacket(player,
|
||||
createPacket(Action.UPDATE_HAT, entry, "", null, false, 0, 0, null, 0, showHat));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,7 +146,7 @@ public class PacketTabList1193 extends PacketTabList18 {
|
||||
@NotNull
|
||||
@Override
|
||||
public Object createPacket(@NonNull Action action, @NonNull UUID id, @NonNull String name, @Nullable Skin skin,
|
||||
boolean listed, int latency, int gameMode, @Nullable Object displayName, int listOrder) {
|
||||
boolean listed, int latency, int gameMode, @Nullable Object displayName, int listOrder, boolean showHat) {
|
||||
Object packet = newPlayerInfo.newInstance(actionToEnumSet.get(action), Collections.emptyList());
|
||||
PLAYERS.set(packet, Collections.singletonList(newPlayerInfoData(
|
||||
id,
|
||||
@@ -135,6 +155,7 @@ public class PacketTabList1193 extends PacketTabList18 {
|
||||
latency,
|
||||
gameModes[gameMode],
|
||||
displayName,
|
||||
showHat,
|
||||
listOrder,
|
||||
null
|
||||
)));
|
||||
@@ -155,6 +176,7 @@ public class PacketTabList1193 extends PacketTabList18 {
|
||||
Object displayName = PlayerInfoData_DisplayName.get(nmsData);
|
||||
int latency = PlayerInfoData_Latency.getInt(nmsData);
|
||||
int listOrder = v1_21_2Plus ? PlayerInfoData_ListOrder.getInt(nmsData) : 0;
|
||||
boolean showHat = v1_21_4Plus && PlayerInfoData_ShowHat.getBoolean(nmsData);
|
||||
if (actions.contains(actionUpdateDisplayName)) {
|
||||
Object expectedName = getExpectedDisplayNames().get(id);
|
||||
if (expectedName != null && expectedName != displayName) {
|
||||
@@ -180,6 +202,7 @@ public class PacketTabList1193 extends PacketTabList18 {
|
||||
latency,
|
||||
PlayerInfoData_GameMode.get(nmsData),
|
||||
displayName,
|
||||
showHat,
|
||||
listOrder,
|
||||
PlayerInfoData_RemoteChatSession.get(nmsData)) : nmsData);
|
||||
}
|
||||
@@ -189,11 +212,13 @@ public class PacketTabList1193 extends PacketTabList18 {
|
||||
@NotNull
|
||||
@SneakyThrows
|
||||
private static Object newPlayerInfoData(@NotNull UUID id, @Nullable GameProfile profile, boolean listed, int latency,
|
||||
@Nullable Object gameMode, @Nullable Object displayName, int listOrder, @Nullable Object chatSession) {
|
||||
if (v1_21_2Plus) {
|
||||
return newPlayerInfoData.newInstance(id, profile, listed, latency, gameMode, displayName, listOrder, chatSession);
|
||||
@Nullable Object gameMode, @Nullable Object displayName, boolean showHat, int listOrder, @Nullable Object chatSession) {
|
||||
if (v1_21_4Plus) {
|
||||
return newPlayerInfoData.newInstance(id, profile, listed, latency, gameMode, displayName, showHat, listOrder, chatSession);
|
||||
} else if (v1_21_2Plus) {
|
||||
return newPlayerInfoData.newInstance(id, profile, listed, latency, gameMode, displayName, listOrder, chatSession);
|
||||
} else {
|
||||
return newPlayerInfoData.newInstance(id, profile, listed, latency, gameMode, displayName, chatSession);
|
||||
return newPlayerInfoData.newInstance(id, profile, listed, latency, gameMode, displayName, chatSession);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -86,7 +86,7 @@ public class PacketTabList17 extends TabListBase<String> {
|
||||
public void updateDisplayName(@NonNull UUID entry, @Nullable String displayName) {
|
||||
if (!displayNames.containsKey(entry)) return; // Entry not tracked by TAB
|
||||
packetSender.sendPacket(player, newPacket.apply(displayNames.get(entry), false, 0));
|
||||
addEntry(entry, userNames.get(entry), null, false, 0, 0, displayName, 0);
|
||||
addEntry(entry, userNames.get(entry), null, false, 0, 0, displayName, 0, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -111,10 +111,15 @@ public class PacketTabList17 extends TabListBase<String> {
|
||||
// Added in 1.21.2
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateHat(@NonNull UUID entry, boolean showHat) {
|
||||
// Added in 1.21.4
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void addEntry(@NonNull UUID id, @NonNull String name, @Nullable Skin skin, boolean listed, int latency,
|
||||
int gameMode, @Nullable String displayName, int listOrder) {
|
||||
int gameMode, @Nullable String displayName, int listOrder, boolean showHat) {
|
||||
String display = displayName == null ? name : displayName;
|
||||
packetSender.sendPacket(player, newPacket.apply(display, true, latency));
|
||||
userNames.put(id, name);
|
||||
|
||||
@@ -112,25 +112,25 @@ public class PacketTabList18 extends TabListBase<Object> {
|
||||
@Override
|
||||
public void removeEntry(@NonNull UUID entry) {
|
||||
packetSender.sendPacket(player,
|
||||
createPacket(Action.REMOVE_PLAYER, entry, "", null, false, 0, 0, null, 0));
|
||||
createPacket(Action.REMOVE_PLAYER, entry, "", null, false, 0, 0, null, 0, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDisplayName(@NonNull UUID entry, @Nullable Object displayName) {
|
||||
packetSender.sendPacket(player,
|
||||
createPacket(Action.UPDATE_DISPLAY_NAME, entry, "", null, false, 0, 0, displayName, 0));
|
||||
createPacket(Action.UPDATE_DISPLAY_NAME, entry, "", null, false, 0, 0, displayName, 0, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateLatency(@NonNull UUID entry, int latency) {
|
||||
packetSender.sendPacket(player,
|
||||
createPacket(Action.UPDATE_LATENCY, entry, "", null, false, latency, 0, null, 0));
|
||||
createPacket(Action.UPDATE_LATENCY, entry, "", null, false, latency, 0, null, 0, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateGameMode(@NonNull UUID entry, int gameMode) {
|
||||
packetSender.sendPacket(player,
|
||||
createPacket(Action.UPDATE_GAME_MODE, entry, "", null, false, 0, gameMode, null, 0));
|
||||
createPacket(Action.UPDATE_GAME_MODE, entry, "", null, false, 0, gameMode, null, 0, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -143,11 +143,16 @@ public class PacketTabList18 extends TabListBase<Object> {
|
||||
// Added in 1.21.2
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateHat(@NonNull UUID entry, boolean showHat) {
|
||||
// Added in 1.21.4
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addEntry(@NonNull UUID id, @NonNull String name, @Nullable Skin skin, boolean listed, int latency,
|
||||
int gameMode, @Nullable Object displayName, int listOrder) {
|
||||
int gameMode, @Nullable Object displayName, int listOrder, boolean showHat) {
|
||||
packetSender.sendPacket(player,
|
||||
createPacket(Action.ADD_PLAYER, id, name, skin, listed, latency, gameMode, displayName, listOrder));
|
||||
createPacket(Action.ADD_PLAYER, id, name, skin, listed, latency, gameMode, displayName, listOrder, showHat));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -171,12 +176,14 @@ public class PacketTabList18 extends TabListBase<Object> {
|
||||
* Entry display name
|
||||
* @param listOrder
|
||||
* Entry list order
|
||||
* @param showHat
|
||||
* Show hat flag
|
||||
* @return Packet from given parameters
|
||||
*/
|
||||
@SneakyThrows
|
||||
@NotNull
|
||||
public Object createPacket(@NonNull Action action, @NonNull UUID id, @NonNull String name, @Nullable Skin skin,
|
||||
boolean listed, int latency, int gameMode, @Nullable Object displayName, int listOrder) {
|
||||
boolean listed, int latency, int gameMode, @Nullable Object displayName, int listOrder, boolean showHat) {
|
||||
Object packet = newPlayerInfo.newInstance(Enum.valueOf(ActionClass, action.name()), Collections.emptyList());
|
||||
List<Object> parameters = new ArrayList<>();
|
||||
if (newPlayerInfoData.getParameterTypes()[0] == PlayerInfoClass) {
|
||||
|
||||
@@ -27,6 +27,12 @@ import java.util.UUID;
|
||||
*/
|
||||
public abstract class TabListBase<C> extends TrackedTabList<BukkitTabPlayer, C> {
|
||||
|
||||
/** Versions supported by paper module that uses direct mojang-mapped NMS for latest MC version */
|
||||
private static final EnumSet<ProtocolVersion> paperNativeVersions = EnumSet.of(
|
||||
ProtocolVersion.V1_21_2,
|
||||
ProtocolVersion.V1_21_3
|
||||
);
|
||||
|
||||
/** Instance function */
|
||||
@Getter
|
||||
@Setter
|
||||
@@ -53,13 +59,7 @@ public abstract class TabListBase<C> extends TrackedTabList<BukkitTabPlayer, C>
|
||||
*/
|
||||
public static void findInstance(@NotNull ProtocolVersion serverVersion) {
|
||||
try {
|
||||
boolean versionCheck = EnumSet.of(
|
||||
ProtocolVersion.V1_20_5,
|
||||
ProtocolVersion.V1_20_6,
|
||||
ProtocolVersion.V1_21,
|
||||
ProtocolVersion.V1_21_1
|
||||
).contains(serverVersion);
|
||||
if (ReflectionUtils.classExists("org.bukkit.craftbukkit.CraftServer") && versionCheck) {
|
||||
if (ReflectionUtils.classExists("org.bukkit.craftbukkit.CraftServer") && paperNativeVersions.contains(serverVersion)) {
|
||||
Constructor<?> constructor = Class.forName("me.neznamy.tab.platforms.paper.PaperPacketTabList").getConstructor(BukkitTabPlayer.class);
|
||||
instance = player -> (TabListBase<?>) constructor.newInstance(player);
|
||||
} else if (ReflectionUtils.classExists("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket")) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
dependencies {
|
||||
implementation(projects.shared)
|
||||
implementation("org.bstats:bstats-bungeecord:3.0.1")
|
||||
implementation("org.bstats:bstats-bungeecord:3.1.0")
|
||||
compileOnly("net.md-5:bungeecord-proxy:1.21-R0.1-SNAPSHOT")
|
||||
compileOnly("com.github.limework.redisbungee:RedisBungee-Bungee:0.11.0")
|
||||
compileOnly("com.github.LeonMangler:PremiumVanishAPI:2.8.8")
|
||||
|
||||
@@ -53,7 +53,7 @@ public class BungeePlatform extends ProxyPlatform {
|
||||
public BungeePlatform(@NotNull BungeeTAB plugin) {
|
||||
this.plugin = plugin;
|
||||
if (ProxyServer.getInstance().getPluginManager().getPlugin("PremiumVanish") != null) {
|
||||
PremiumVanishHook.setInstance(new BungeePremiumVanishHook(this));
|
||||
new BungeePremiumVanishHook(this).register();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,8 +196,12 @@ public class BungeePlatform extends ProxyPlatform {
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public ProtocolVersion getServerVersion() {
|
||||
return ProtocolVersion.V1_21_2;
|
||||
public boolean supportsNumberFormat() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsListOrder() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,6 @@ package me.neznamy.tab.platforms.bungeecord;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.chat.TabComponent;
|
||||
import me.neznamy.tab.shared.hook.PremiumVanishHook;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import me.neznamy.tab.shared.proxy.ProxyTabPlayer;
|
||||
import net.md_5.bungee.UserConnection;
|
||||
@@ -48,6 +47,7 @@ public class BungeeTabPlayer extends ProxyTabPlayer {
|
||||
try {
|
||||
getPlayer().sendMessage((BaseComponent) message.convert(getVersion()));
|
||||
} catch (NullPointerException BungeeCordBug) {
|
||||
// TODO remove this at some point in the near future as it got fixed in build #1882 (when 1.21.4 is out and required bungee build gets bumped)
|
||||
// java.lang.NullPointerException: Cannot invoke "net.md_5.bungee.protocol.MinecraftEncoder.getProtocol()" because the return value of "io.netty.channel.ChannelPipeline.get(java.lang.Class)" is null
|
||||
// at net.md_5.bungee.netty.ChannelWrapper.getEncodeProtocol(ChannelWrapper.java:51)
|
||||
// at net.md_5.bungee.UserConnection.sendPacketQueued(UserConnection.java:198)
|
||||
@@ -93,6 +93,7 @@ public class BungeeTabPlayer extends ProxyTabPlayer {
|
||||
try {
|
||||
getPlayer().getServer().sendData(TabConstants.PLUGIN_MESSAGE_CHANNEL_NAME, message);
|
||||
} catch (NullPointerException BungeeCordBug) {
|
||||
// TODO remove this at some point in the near future as it got fixed in build #1882 (when 1.21.4 is out and required bungee build gets bumped)
|
||||
// java.lang.NullPointerException: Cannot invoke "net.md_5.bungee.protocol.MinecraftEncoder.getProtocol()" because the return value of "io.netty.channel.ChannelPipeline.get(java.lang.Class)" is null
|
||||
// at net.md_5.bungee.netty.ChannelWrapper.getEncodeProtocol(ChannelWrapper.java:51)
|
||||
// at net.md_5.bungee.ServerConnection.sendPacketQueued(ServerConnection.java:48)
|
||||
@@ -104,12 +105,6 @@ public class BungeeTabPlayer extends ProxyTabPlayer {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVanished() {
|
||||
if (PremiumVanishHook.getInstance() != null && PremiumVanishHook.getInstance().isVanished(this)) return true;
|
||||
return super.isVanished();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends packet to the player. If BungeeCord supports 1.20.2+, new packet queue method is used
|
||||
* to avoid error when sending packet in configuration phase.
|
||||
@@ -122,6 +117,7 @@ public class BungeeTabPlayer extends ProxyTabPlayer {
|
||||
try {
|
||||
((UserConnection)getPlayer()).sendPacketQueued(packet);
|
||||
} catch (NullPointerException BungeeCordBug) {
|
||||
// TODO remove this at some point in the near future as it got fixed in build #1882 (when 1.21.4 is out and required bungee build gets bumped)
|
||||
// java.lang.NullPointerException: Cannot invoke "net.md_5.bungee.protocol.MinecraftEncoder.getProtocol()" because the return value of "io.netty.channel.ChannelPipeline.get(java.lang.Class)" is null
|
||||
// at net.md_5.bungee.netty.ChannelWrapper.getEncodeProtocol(ChannelWrapper.java:51)
|
||||
// at net.md_5.bungee.UserConnection.sendPacketQueued(UserConnection.java:194)
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package me.neznamy.tab.platforms.bungeecord.hook;
|
||||
|
||||
import de.myzelyam.api.vanish.BungeeVanishAPI;
|
||||
import me.neznamy.tab.api.TabPlayer;
|
||||
import me.neznamy.tab.platforms.bungeecord.BungeeTabPlayer;
|
||||
import me.neznamy.tab.shared.chat.TabComponent;
|
||||
import me.neznamy.tab.shared.hook.PremiumVanishHook;
|
||||
import me.neznamy.tab.shared.platform.Platform;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -36,7 +36,7 @@ public class BungeePremiumVanishHook extends PremiumVanishHook {
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized boolean canSee(@NotNull TabPlayer viewer, @NotNull TabPlayer target) {
|
||||
public boolean canSee(@NotNull TabPlayer viewer, @NotNull TabPlayer target) {
|
||||
//noinspection ConstantValue
|
||||
return canSeeEnabled && BungeeVanishAPI.canSee(((BungeeTabPlayer)viewer).getPlayer(), ((BungeeTabPlayer)target).getPlayer());
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelDuplexHandler;
|
||||
import me.neznamy.tab.platforms.bungeecord.BungeeTabPlayer;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.config.files.config.Config;
|
||||
import me.neznamy.tab.shared.config.files.Config;
|
||||
import me.neznamy.tab.shared.features.injection.NettyPipelineInjector;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import net.md_5.bungee.UserConnection;
|
||||
@@ -28,7 +28,7 @@ public class BungeePipelineInjector extends NettyPipelineInjector {
|
||||
public BungeePipelineInjector() {
|
||||
super("inbound-boss");
|
||||
Config config = TAB.getInstance().getConfiguration().getConfig();
|
||||
byteBufDeserialization = (config.getTeams() != null && config.getTeams().antiOverride) || config.getScoreboard() != null;
|
||||
byteBufDeserialization = (config.getTeams() != null && config.getTeams().isAntiOverride()) || config.getScoreboard() != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -81,11 +81,13 @@ public abstract class BungeeTabList extends TrackedTabList<BungeeTabPlayer, Base
|
||||
* Entry display name
|
||||
* @param listOrder
|
||||
* Entry list order
|
||||
* @param showHat
|
||||
* Show hat flag
|
||||
* @return Converted item from parameters
|
||||
*/
|
||||
@NotNull
|
||||
public Item entryToItem(@NonNull UUID id, @NonNull String name, @Nullable Skin skin, boolean listed, int latency,
|
||||
int gameMode, @Nullable BaseComponent displayName, int listOrder) {
|
||||
int gameMode, @Nullable BaseComponent displayName, int listOrder, boolean showHat) {
|
||||
Item item = item(id);
|
||||
item.setUsername(name);
|
||||
item.setDisplayName(displayName);
|
||||
@@ -98,6 +100,7 @@ public abstract class BungeeTabList extends TrackedTabList<BungeeTabPlayer, Base
|
||||
item.setProperties(new Property[0]);
|
||||
}
|
||||
item.setListOrder(listOrder);
|
||||
// TODO showHat once BungeeCord adds it
|
||||
return item;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,25 +17,30 @@ import java.util.*;
|
||||
*/
|
||||
public class BungeeTabList1193 extends BungeeTabList {
|
||||
|
||||
/** Map of actions to prevent creating new EnumSet on each packet send */
|
||||
private static final Map<Action, EnumSet<PlayerListItemUpdate.Action>> actions = new EnumMap<>(Action.class);
|
||||
private static final EnumSet<PlayerListItemUpdate.Action> updateDisplayName = EnumSet.of(PlayerListItemUpdate.Action.UPDATE_DISPLAY_NAME);
|
||||
private static final EnumSet<PlayerListItemUpdate.Action> updateLatency = EnumSet.of(PlayerListItemUpdate.Action.UPDATE_LATENCY);
|
||||
private static final EnumSet<PlayerListItemUpdate.Action> updateGameMode = EnumSet.of(PlayerListItemUpdate.Action.UPDATE_GAMEMODE);
|
||||
private static final EnumSet<PlayerListItemUpdate.Action> updateListed = EnumSet.of(PlayerListItemUpdate.Action.UPDATE_LISTED);
|
||||
private static final EnumSet<PlayerListItemUpdate.Action> updateListOrder = EnumSet.of(PlayerListItemUpdate.Action.UPDATE_LIST_ORDER);
|
||||
|
||||
private static final EnumSet<PlayerListItemUpdate.Action> allActions = EnumSet.allOf(PlayerListItemUpdate.Action.class);
|
||||
// All actions for 1.19.3 - 1.21.1
|
||||
private static final EnumSet<PlayerListItemUpdate.Action> addPlayer_1_21_1 = EnumSet.of(
|
||||
PlayerListItemUpdate.Action.ADD_PLAYER,
|
||||
PlayerListItemUpdate.Action.UPDATE_GAMEMODE,
|
||||
PlayerListItemUpdate.Action.UPDATE_LISTED,
|
||||
PlayerListItemUpdate.Action.UPDATE_LATENCY,
|
||||
PlayerListItemUpdate.Action.UPDATE_DISPLAY_NAME
|
||||
);
|
||||
|
||||
static {
|
||||
// Do not use allOf because <1.21.2 does not support UPDATE_LIST_ORDER
|
||||
actions.put(Action.ADD_PLAYER, EnumSet.of(
|
||||
PlayerListItemUpdate.Action.ADD_PLAYER,
|
||||
PlayerListItemUpdate.Action.UPDATE_GAMEMODE,
|
||||
PlayerListItemUpdate.Action.UPDATE_LISTED,
|
||||
PlayerListItemUpdate.Action.UPDATE_LATENCY,
|
||||
PlayerListItemUpdate.Action.UPDATE_DISPLAY_NAME
|
||||
));
|
||||
actions.put(Action.UPDATE_GAME_MODE, EnumSet.of(PlayerListItemUpdate.Action.UPDATE_GAMEMODE));
|
||||
actions.put(Action.UPDATE_DISPLAY_NAME, EnumSet.of(PlayerListItemUpdate.Action.UPDATE_DISPLAY_NAME));
|
||||
actions.put(Action.UPDATE_LATENCY, EnumSet.of(PlayerListItemUpdate.Action.UPDATE_LATENCY));
|
||||
actions.put(Action.UPDATE_LISTED, EnumSet.of(PlayerListItemUpdate.Action.UPDATE_LISTED));
|
||||
}
|
||||
// All actions for 1.21.2 - 1.21.3
|
||||
private static final EnumSet<PlayerListItemUpdate.Action> addPlayer_1_21_2 = EnumSet.of(
|
||||
PlayerListItemUpdate.Action.ADD_PLAYER,
|
||||
PlayerListItemUpdate.Action.UPDATE_GAMEMODE,
|
||||
PlayerListItemUpdate.Action.UPDATE_LISTED,
|
||||
PlayerListItemUpdate.Action.UPDATE_LATENCY,
|
||||
PlayerListItemUpdate.Action.UPDATE_DISPLAY_NAME,
|
||||
PlayerListItemUpdate.Action.UPDATE_LIST_ORDER
|
||||
);
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameter.
|
||||
@@ -59,28 +64,28 @@ public class BungeeTabList1193 extends BungeeTabList {
|
||||
public void updateDisplayName(@NonNull UUID entry, @Nullable BaseComponent displayName) {
|
||||
Item item = item(entry);
|
||||
item.setDisplayName(displayName);
|
||||
sendPacket(Action.UPDATE_DISPLAY_NAME, item);
|
||||
sendPacket(updateDisplayName, item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateLatency(@NonNull UUID entry, int latency) {
|
||||
Item item = item(entry);
|
||||
item.setPing(latency);
|
||||
sendPacket(Action.UPDATE_LATENCY, item);
|
||||
sendPacket(updateLatency, item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateGameMode(@NonNull UUID entry, int gameMode) {
|
||||
Item item = item(entry);
|
||||
item.setGamemode(gameMode);
|
||||
sendPacket(Action.UPDATE_GAME_MODE, item);
|
||||
sendPacket(updateGameMode, item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateListed(@NonNull UUID entry, boolean listed) {
|
||||
Item item = item(entry);
|
||||
item.setListed(listed);
|
||||
sendPacket(Action.UPDATE_LISTED, item);
|
||||
sendPacket(updateListed, item);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -88,23 +93,25 @@ public class BungeeTabList1193 extends BungeeTabList {
|
||||
if (player.getVersion().getNetworkId() < ProtocolVersion.V1_21_2.getNetworkId()) return;
|
||||
Item item = item(entry);
|
||||
item.setListOrder(listOrder);
|
||||
sendPacket(Action.UPDATE_LIST_ORDER, item);
|
||||
sendPacket(updateListOrder, item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateHat(@NonNull UUID entry, boolean showHat) {
|
||||
//TODO once BungeeCord adds it
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addEntry(@NonNull UUID id, @NonNull String name, @Nullable Skin skin, boolean listed, int latency,
|
||||
int gameMode, @Nullable BaseComponent displayName, int listOrder) {
|
||||
int gameMode, @Nullable BaseComponent displayName, int listOrder, boolean showHat) {
|
||||
addUuid(id);
|
||||
sendPacket(Action.ADD_PLAYER, entryToItem(id, name, skin, listed, latency, gameMode, displayName, listOrder));
|
||||
sendPacket(player.getVersion().getNetworkId() >= ProtocolVersion.V1_21_2.getNetworkId() ? addPlayer_1_21_2 : addPlayer_1_21_1,
|
||||
entryToItem(id, name, skin, listed, latency, gameMode, displayName, listOrder, showHat));
|
||||
}
|
||||
|
||||
private void sendPacket(@NonNull Action action, @NonNull Item item) {
|
||||
private void sendPacket(@NonNull EnumSet<PlayerListItemUpdate.Action> actions, @NonNull Item item) {
|
||||
PlayerListItemUpdate packet = new PlayerListItemUpdate();
|
||||
if (player.getVersion().getNetworkId() >= ProtocolVersion.V1_21_2.getNetworkId() && action == Action.ADD_PLAYER) {
|
||||
packet.setActions(allActions);
|
||||
} else {
|
||||
packet.setActions(actions.get(action));
|
||||
}
|
||||
packet.setActions(actions);
|
||||
packet.setItems(new Item[]{item});
|
||||
player.sendPacket(packet);
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ public class BungeeTabList17 extends BungeeTabList {
|
||||
public void updateDisplayName(@NonNull UUID entry, @Nullable BaseComponent displayName) {
|
||||
if (!displayNames.containsKey(entry)) return; // Entry not tracked by TAB
|
||||
update(PlayerListItem.Action.REMOVE_PLAYER, createItem(null, displayNames.get(entry), 0));
|
||||
addEntry(entry, userNames.get(entry), null, false, 0, 0, displayName, 0);
|
||||
addEntry(entry, userNames.get(entry), null, false, 0, 0, displayName, 0, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -81,9 +81,14 @@ public class BungeeTabList17 extends BungeeTabList {
|
||||
// Added in 1.21.2
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateHat(@NonNull UUID entry, boolean showHat) {
|
||||
// Added in 1.21.4
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addEntry(@NonNull UUID id, @NonNull String name, @Nullable Skin skin, boolean listed, int latency,
|
||||
int gameMode, @Nullable BaseComponent displayName, int listOrder) {
|
||||
int gameMode, @Nullable BaseComponent displayName, int listOrder, boolean showHat) {
|
||||
addUuid(id);
|
||||
update(PlayerListItem.Action.ADD_PLAYER, createItem(name, displayName == null ? new TextComponent(name) : displayName, latency));
|
||||
|
||||
|
||||
@@ -62,11 +62,16 @@ public class BungeeTabList18 extends BungeeTabList {
|
||||
// Added in 1.21.2
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateHat(@NonNull UUID entry, boolean showHat) {
|
||||
// Added in 1.21.4
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addEntry(@NonNull UUID id, @NonNull String name, @Nullable Skin skin, boolean listed, int latency,
|
||||
int gameMode, @Nullable BaseComponent displayName, int listOrder) {
|
||||
int gameMode, @Nullable BaseComponent displayName, int listOrder, boolean showHat) {
|
||||
addUuid(id);
|
||||
sendPacket(PlayerListItem.Action.ADD_PLAYER, entryToItem(id, name, skin, listed, latency, gameMode, displayName, listOrder));
|
||||
sendPacket(PlayerListItem.Action.ADD_PLAYER, entryToItem(id, name, skin, listed, latency, gameMode, displayName, listOrder, showHat));
|
||||
}
|
||||
|
||||
private void sendPacket(@NonNull PlayerListItem.Action action, @NonNull Item item) {
|
||||
|
||||
@@ -8,13 +8,15 @@ repositories {
|
||||
maven("https://repo.opencollab.dev/maven-snapshots/")
|
||||
maven("https://repo.viaversion.com/")
|
||||
maven("https://oss.sonatype.org/content/repositories/snapshots")
|
||||
maven("https://maven.nucleoid.xyz/")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api(projects.shared)
|
||||
minecraft("com.mojang:minecraft:1.21.2")
|
||||
minecraft("com.mojang:minecraft:1.21.4-pre2")
|
||||
mappings(loom.officialMojangMappings())
|
||||
modImplementation("me.lucko:fabric-permissions-api:0.2-SNAPSHOT")
|
||||
modImplementation("eu.pb4:placeholder-api:2.5.0+1.21.2")
|
||||
modImplementation("net.fabricmc:fabric-loader:0.15.9")
|
||||
val version = "0.100.1+1.21"
|
||||
modImplementation(fabricApi.module("fabric-lifecycle-events-v1", version))
|
||||
|
||||
@@ -35,6 +35,9 @@ public class FabricMultiVersion {
|
||||
/** Method loader using latest supported MC version */
|
||||
private static final Loader loaderLatest = new Loader_Latest();
|
||||
|
||||
/** Method loader using 1.21.2 - 1.21.3 */
|
||||
private static final Loader loader1_21_3 = createLoader("1_21_3");
|
||||
|
||||
/** Method loader using 1.20.3 - 1.21.1 */
|
||||
private static final Loader loader1_20_3 = createLoader("1_20_3");
|
||||
|
||||
@@ -256,7 +259,8 @@ public class FabricMultiVersion {
|
||||
* Received packet
|
||||
*/
|
||||
public static void onPlayerInfo(@NotNull TabPlayer receiver, @NotNull Object packet) {
|
||||
if (serverVersion.getNetworkId() >= ProtocolVersion.V1_21_2.getNetworkId()) loaderLatest.onPlayerInfo(receiver, packet);
|
||||
if (serverVersion.getNetworkId() >= ProtocolVersion.V1_21_4.getNetworkId()) loaderLatest.onPlayerInfo(receiver, packet);
|
||||
else if (serverVersion.getNetworkId() >= ProtocolVersion.V1_21_2.getNetworkId()) loader1_21_3.onPlayerInfo(receiver, packet);
|
||||
else if (serverVersion.getNetworkId() >= ProtocolVersion.V1_19_3.getNetworkId()) loader1_20_3.onPlayerInfo(receiver, packet);
|
||||
else if (serverVersion.getMinorVersion() >= 17) loader1_18_2.onPlayerInfo(receiver, packet);
|
||||
else loader1_14_4.onPlayerInfo(receiver, packet);
|
||||
@@ -273,7 +277,8 @@ public class FabricMultiVersion {
|
||||
*/
|
||||
@NotNull
|
||||
public static Packet<?> buildTabListPacket(@NotNull TabList.Action action, @NotNull FabricTabList.Builder builder) {
|
||||
if (serverVersion.getNetworkId() >= ProtocolVersion.V1_21_2.getNetworkId()) return loaderLatest.buildTabListPacket(action, builder);
|
||||
if (serverVersion.getNetworkId() >= ProtocolVersion.V1_21_4.getNetworkId()) return loaderLatest.buildTabListPacket(action, builder);
|
||||
if (serverVersion.getNetworkId() >= ProtocolVersion.V1_21_2.getNetworkId()) return loader1_21_3.buildTabListPacket(action, builder);
|
||||
if (serverVersion.getNetworkId() >= ProtocolVersion.V1_19_3.getNetworkId()) return loader1_20_3.buildTabListPacket(action, builder);
|
||||
if (serverVersion.getMinorVersion() >= 17) return loader1_18_2.buildTabListPacket(action, builder);
|
||||
return loader1_14_4.buildTabListPacket(action, builder);
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
package me.neznamy.tab.platforms.fabric;
|
||||
|
||||
import eu.pb4.placeholders.api.PlaceholderContext;
|
||||
import eu.pb4.placeholders.api.Placeholders;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import me.neznamy.tab.platforms.fabric.hook.FabricTabExpansion;
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
@@ -9,7 +12,8 @@ import me.neznamy.tab.shared.backend.BackendPlatform;
|
||||
import me.neznamy.tab.shared.chat.SimpleComponent;
|
||||
import me.neznamy.tab.shared.chat.StructuredComponent;
|
||||
import me.neznamy.tab.shared.chat.TabComponent;
|
||||
import me.neznamy.tab.shared.config.files.config.PerWorldPlayerListConfiguration;
|
||||
import me.neznamy.tab.shared.features.PerWorldPlayerListConfiguration;
|
||||
import me.neznamy.tab.shared.features.PlaceholderManagerImpl;
|
||||
import me.neznamy.tab.shared.features.injection.PipelineInjector;
|
||||
import me.neznamy.tab.shared.features.types.TabFeature;
|
||||
import me.neznamy.tab.shared.placeholders.expansion.EmptyTabExpansion;
|
||||
@@ -44,7 +48,19 @@ public class FabricPlatform implements BackendPlatform {
|
||||
|
||||
@Override
|
||||
public void registerUnknownPlaceholder(@NotNull String identifier) {
|
||||
registerDummyPlaceholder(identifier);
|
||||
if (!FabricLoader.getInstance().isModLoaded("placeholder-api")) {
|
||||
registerDummyPlaceholder(identifier);
|
||||
return;
|
||||
}
|
||||
|
||||
PlaceholderManagerImpl manager = TAB.getInstance().getPlaceholderManager();
|
||||
int refresh = manager.getRefreshInterval(identifier);
|
||||
manager.registerPlayerPlaceholder(identifier, refresh,
|
||||
p -> Placeholders.parseText(
|
||||
FabricMultiVersion.newTextComponent(identifier),
|
||||
PlaceholderContext.of((ServerPlayer) p.getPlayer())
|
||||
).getString()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -68,6 +84,8 @@ public class FabricPlatform implements BackendPlatform {
|
||||
@Override
|
||||
@NotNull
|
||||
public TabExpansion createTabExpansion() {
|
||||
if (FabricLoader.getInstance().isModLoaded("placeholder-api"))
|
||||
return new FabricTabExpansion();
|
||||
return new EmptyTabExpansion();
|
||||
}
|
||||
|
||||
@@ -147,6 +165,16 @@ public class FabricPlatform implements BackendPlatform {
|
||||
return new FabricTabList((FabricTabPlayer) player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsNumberFormat() {
|
||||
return serverVersion.getNetworkId() >= ProtocolVersion.V1_20_3.getNetworkId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsListOrder() {
|
||||
return serverVersion.getNetworkId() >= ProtocolVersion.V1_21_2.getNetworkId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getTPS() {
|
||||
return -1; // Not available
|
||||
|
||||
@@ -32,32 +32,32 @@ public class FabricTabList extends TrackedTabList<FabricTabPlayer, Component> {
|
||||
@Override
|
||||
public void removeEntry(@NonNull UUID entry) {
|
||||
player.sendPacket(FabricMultiVersion.buildTabListPacket(Action.REMOVE_PLAYER,
|
||||
new Builder(entry, "", null, false, 0, 0, null, 0)));
|
||||
new Builder(entry, "", null, false, 0, 0, null, 0, false)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDisplayName(@NonNull UUID entry, @Nullable Component displayName) {
|
||||
player.sendPacket(FabricMultiVersion.buildTabListPacket(Action.UPDATE_DISPLAY_NAME,
|
||||
new Builder(entry, "", null, false, 0, 0, displayName, 0)));
|
||||
new Builder(entry, "", null, false, 0, 0, displayName, 0, false)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateLatency(@NonNull UUID entry, int latency) {
|
||||
player.sendPacket(FabricMultiVersion.buildTabListPacket(Action.UPDATE_LATENCY,
|
||||
new Builder(entry, "", null, false, latency, 0, null, 0)));
|
||||
new Builder(entry, "", null, false, latency, 0, null, 0, false)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateGameMode(@NonNull UUID entry, int gameMode) {
|
||||
player.sendPacket(FabricMultiVersion.buildTabListPacket(Action.UPDATE_GAME_MODE,
|
||||
new Builder(entry, "", null, false, 0, gameMode, null, 0)));
|
||||
new Builder(entry, "", null, false, 0, gameMode, null, 0, false)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateListed(@NonNull UUID entry, boolean listed) {
|
||||
if (player.getPlatform().getServerVersion().getNetworkId() >= ProtocolVersion.V1_19_3.getNetworkId()) {
|
||||
player.sendPacket(FabricMultiVersion.buildTabListPacket(Action.UPDATE_LISTED,
|
||||
new Builder(entry, "", null, listed, 0, 0, null, 0)));
|
||||
new Builder(entry, "", null, listed, 0, 0, null, 0, false)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,15 +65,23 @@ public class FabricTabList extends TrackedTabList<FabricTabPlayer, Component> {
|
||||
public void updateListOrder(@NonNull UUID entry, int listOrder) {
|
||||
if (player.getPlatform().getServerVersion().getNetworkId() >= ProtocolVersion.V1_21_2.getNetworkId()) {
|
||||
player.sendPacket(FabricMultiVersion.buildTabListPacket(Action.UPDATE_LIST_ORDER,
|
||||
new Builder(entry, "", null, false, 0, 0, null, listOrder)));
|
||||
new Builder(entry, "", null, false, 0, 0, null, listOrder, false)));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateHat(@NonNull UUID entry, boolean showHat) {
|
||||
if (player.getPlatform().getServerVersion().getNetworkId() >= ProtocolVersion.V1_21_4.getNetworkId()) {
|
||||
player.sendPacket(FabricMultiVersion.buildTabListPacket(Action.UPDATE_HAT,
|
||||
new Builder(entry, "", null, false, 0, 0, null, 0, showHat)));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addEntry(@NonNull UUID id, @NonNull String name, @Nullable Skin skin, boolean listed, int latency,
|
||||
int gameMode, @Nullable Component displayName, int listOrder) {
|
||||
int gameMode, @Nullable Component displayName, int listOrder, boolean showHat) {
|
||||
player.sendPacket(FabricMultiVersion.buildTabListPacket(Action.ADD_PLAYER,
|
||||
new Builder(id, name, skin, listed, latency, gameMode, displayName, listOrder)));
|
||||
new Builder(id, name, skin, listed, latency, gameMode, displayName, listOrder, showHat)));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -108,6 +116,7 @@ public class FabricTabList extends TrackedTabList<FabricTabPlayer, Component> {
|
||||
private int gameMode;
|
||||
@Nullable private Component displayName;
|
||||
private int listOrder;
|
||||
private boolean showHat;
|
||||
|
||||
/**
|
||||
* Creates profile of this entry.
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
package me.neznamy.tab.platforms.fabric.hook;
|
||||
|
||||
import eu.pb4.placeholders.api.PlaceholderContext;
|
||||
import eu.pb4.placeholders.api.PlaceholderHandler;
|
||||
import eu.pb4.placeholders.api.PlaceholderResult;
|
||||
import eu.pb4.placeholders.api.Placeholders;
|
||||
import lombok.Getter;
|
||||
import me.neznamy.tab.platforms.fabric.FabricMultiVersion;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.features.PlaceholderManagerImpl;
|
||||
import me.neznamy.tab.shared.placeholders.expansion.TabExpansion;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* TAB's expansion for Text PlaceholderAPI
|
||||
*/
|
||||
@Getter
|
||||
public class FabricTabExpansion implements TabExpansion {
|
||||
|
||||
public FabricTabExpansion() {
|
||||
List<String> placeholders = Arrays.asList(
|
||||
"tabprefix",
|
||||
"tabsuffix",
|
||||
"tagprefix",
|
||||
"tagsuffix",
|
||||
"customtabname",
|
||||
"tabprefix_raw",
|
||||
"tabsuffix_raw",
|
||||
"tagprefix_raw",
|
||||
"tagsuffix_raw",
|
||||
"customtabname_raw",
|
||||
"scoreboard_name",
|
||||
"scoreboard_visible",
|
||||
"bossbar_visible",
|
||||
"nametag_visibility"
|
||||
);
|
||||
for (String placeholder : placeholders) {
|
||||
registerPlaceholder(placeholder, (ctx, arg) -> {
|
||||
if (!ctx.hasPlayer()) return PlaceholderResult.invalid("No player!");
|
||||
TabPlayer player = TAB.getInstance().getPlayer(ctx.player().getUUID());
|
||||
return PlaceholderResult.value(player.expansionValues.get(placeholder));
|
||||
});
|
||||
}
|
||||
|
||||
registerPlaceholder("replace", (ctx, arg) -> {
|
||||
if (!ctx.hasPlayer()) return PlaceholderResult.invalid("No player!");
|
||||
if (arg == null) return PlaceholderResult.invalid("No placeholder!");
|
||||
|
||||
String text = "%" + arg + "%";
|
||||
String textBefore;
|
||||
do {
|
||||
textBefore = text;
|
||||
for (String placeholder : PlaceholderManagerImpl.detectPlaceholders(text)) {
|
||||
text = text.replace(placeholder, TAB.getInstance().getPlaceholderManager().findReplacement(placeholder,
|
||||
Placeholders.parseText(
|
||||
FabricMultiVersion.newTextComponent(placeholder),
|
||||
PlaceholderContext.of(ctx.player())
|
||||
).getString()));
|
||||
}
|
||||
} while (!textBefore.equals(text));
|
||||
|
||||
return PlaceholderResult.value(text);
|
||||
});
|
||||
|
||||
registerPlaceholder("placeholder", (ctx, arg) -> {
|
||||
if (arg == null) return PlaceholderResult.invalid("No placeholder!");
|
||||
|
||||
TabPlayer player = ctx.hasPlayer() ? TAB.getInstance().getPlayer(ctx.player().getUUID()) : null;
|
||||
|
||||
String placeholder = "%"+arg+"%";
|
||||
PlaceholderManagerImpl manager = TAB.getInstance().getPlaceholderManager();
|
||||
manager.addUsedPlaceholder(placeholder, manager);
|
||||
return PlaceholderResult.value(manager.getPlaceholder(placeholder).getLastValue(player));
|
||||
});
|
||||
}
|
||||
|
||||
private void registerPlaceholder(String identifier, PlaceholderHandler handler) {
|
||||
Placeholders.register(ResourceLocation.tryParse(TabConstants.PLUGIN_ID+":"+identifier), handler);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void setValue(@NotNull TabPlayer player, @NotNull String key, @NotNull String value) {
|
||||
player.expansionValues.put(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterExpansion() {}
|
||||
|
||||
|
||||
}
|
||||
@@ -73,19 +73,14 @@ public class Loader_Latest implements Loader {
|
||||
color = TextColor.fromRgb(modifier.getColor().getLegacyColor().getRgb());
|
||||
}
|
||||
}
|
||||
|
||||
return new Style(
|
||||
color,
|
||||
modifier.isBold(),
|
||||
modifier.isItalic(),
|
||||
modifier.isUnderlined(),
|
||||
modifier.isStrikethrough(),
|
||||
modifier.isObfuscated(),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
modifier.getFont() == null ? null : ResourceLocation.tryParse(modifier.getFont())
|
||||
);
|
||||
return Style.EMPTY
|
||||
.withColor(color)
|
||||
.withBold(modifier.isBold())
|
||||
.withItalic(modifier.isItalic())
|
||||
.withUnderlined(modifier.isUnderlined())
|
||||
.withStrikethrough(modifier.isStrikethrough())
|
||||
.withObfuscated(modifier.isObfuscated())
|
||||
.withFont(modifier.getFont() == null ? null : ResourceLocation.tryParse(modifier.getFont()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -160,7 +155,7 @@ public class Loader_Latest implements Loader {
|
||||
TAB.getInstance().getFeatureManager().onEntryAdd(receiver, nmsData.profileId(), profile.getName());
|
||||
}
|
||||
updatedList.add(new ClientboundPlayerInfoUpdatePacket.Entry(nmsData.profileId(), profile, nmsData.listed(),
|
||||
latency, nmsData.gameMode(), displayName, nmsData.listOrder(), nmsData.chatSession()));
|
||||
latency, nmsData.gameMode(), displayName, nmsData.showHat(), nmsData.listOrder(), nmsData.chatSession()));
|
||||
}
|
||||
packet.entries = updatedList;
|
||||
}
|
||||
@@ -179,6 +174,7 @@ public class Loader_Latest implements Loader {
|
||||
entry.getLatency(),
|
||||
GameType.byId(entry.getGameMode()),
|
||||
entry.getDisplayName(),
|
||||
entry.isShowHat(),
|
||||
entry.getListOrder(),
|
||||
null
|
||||
));
|
||||
@@ -295,6 +291,7 @@ public class Loader_Latest implements Loader {
|
||||
actions.put(TabList.Action.UPDATE_LATENCY, EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY));
|
||||
actions.put(TabList.Action.UPDATE_LISTED, EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED));
|
||||
actions.put(TabList.Action.UPDATE_LIST_ORDER, EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LIST_ORDER));
|
||||
actions.put(TabList.Action.UPDATE_HAT, EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_HAT));
|
||||
return actions;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
"1.18", "1.18.1", "1.18.2",
|
||||
"1.19", "1.19.1", "1.19.2", "1.19.3", "1.19.4",
|
||||
"1.20", "1.20.1", "1.20.2", "1.20.3", "1.20.4", "1.20.5", "1.20.6",
|
||||
"1.21", "1.21.1", "1.21.2"
|
||||
"1.21", "1.21.1", "1.21.2", "1.21.3", "1.21.4-alpha.24.44.a", "1.21.4-alpha.24.45.a", "1.21.4-alpha.24.46.a", "1.21.4-beta.1", "1.21.4-beta.2"
|
||||
]
|
||||
},
|
||||
"recommends": {
|
||||
|
||||
@@ -5,9 +5,6 @@ accessible field net/minecraft/network/protocol/game/ClientboundSetObjectivePack
|
||||
accessible field net/minecraft/network/protocol/game/ClientboundSetObjectivePacket method I
|
||||
accessible field net/minecraft/network/protocol/game/ClientboundSetDisplayObjectivePacket objectiveName Ljava/lang/String;
|
||||
|
||||
# Style constructor (1.16+)
|
||||
accessible method net/minecraft/network/chat/Style <init> (Lnet/minecraft/network/chat/TextColor;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Lnet/minecraft/network/chat/ClickEvent;Lnet/minecraft/network/chat/HoverEvent;Ljava/lang/String;Lnet/minecraft/resources/ResourceLocation;)V
|
||||
|
||||
# Team packet (1.17+)
|
||||
accessible field net/minecraft/network/protocol/game/ClientboundSetPlayerTeamPacket method I
|
||||
accessible field net/minecraft/network/protocol/game/ClientboundSetPlayerTeamPacket players Ljava/util/Collection;
|
||||
|
||||
@@ -8,6 +8,7 @@ repositories {
|
||||
maven("https://repo.opencollab.dev/maven-snapshots/")
|
||||
maven("https://repo.viaversion.com/")
|
||||
maven("https://oss.sonatype.org/content/repositories/snapshots")
|
||||
maven("https://maven.nucleoid.xyz/")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
@@ -8,6 +8,7 @@ repositories {
|
||||
maven("https://repo.opencollab.dev/maven-snapshots/")
|
||||
maven("https://repo.viaversion.com/")
|
||||
maven("https://oss.sonatype.org/content/repositories/snapshots")
|
||||
maven("https://maven.nucleoid.xyz/")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
@@ -8,6 +8,7 @@ repositories {
|
||||
maven("https://repo.opencollab.dev/maven-snapshots/")
|
||||
maven("https://repo.viaversion.com/")
|
||||
maven("https://oss.sonatype.org/content/repositories/snapshots")
|
||||
maven("https://maven.nucleoid.xyz/")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
plugins {
|
||||
id("fabric-loom")
|
||||
}
|
||||
|
||||
repositories {
|
||||
// Gradle doesn't support combining settings and project repositories, so we have to re-declare all the settings repos we need
|
||||
maven("https://jitpack.io") // YamlAssist
|
||||
maven("https://repo.opencollab.dev/maven-snapshots/")
|
||||
maven("https://repo.viaversion.com/")
|
||||
maven("https://oss.sonatype.org/content/repositories/snapshots")
|
||||
maven("https://maven.nucleoid.xyz/")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(projects.fabric)
|
||||
minecraft("com.mojang:minecraft:1.21.3")
|
||||
mappings(loom.officialMojangMappings())
|
||||
modImplementation("net.fabricmc:fabric-loader:0.15.9") // Not required, but causes warn if not present
|
||||
}
|
||||
|
||||
loom {
|
||||
accessWidenerPath.set(file("../src/main/resources/resources/tab.accesswidener"))
|
||||
}
|
||||
|
||||
tasks {
|
||||
compileJava {
|
||||
options.release.set(17)
|
||||
}
|
||||
|
||||
validateAccessWidener {
|
||||
enabled = false // Disable validation because the file is made for latest version, so some fields will throw error (the one we need has not changed)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package me.neznamy.tab.platforms.fabric.loader;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import me.neznamy.tab.platforms.fabric.FabricTabList;
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
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.world.level.GameType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Implementation containing some methods that have changed multiple times
|
||||
* throughout the versions and need a separate module. This module implements
|
||||
* a few methods in the state of Minecraft 1.21.2 - 1.21.3.
|
||||
*/
|
||||
@SuppressWarnings("unused") // Actually used, just via reflection
|
||||
@RequiredArgsConstructor
|
||||
public class Loader_1_21_3 implements Loader {
|
||||
|
||||
private final ProtocolVersion serverVersion;
|
||||
|
||||
@Override
|
||||
public void onPlayerInfo(@NotNull TabPlayer receiver, @NotNull Object packet0) {
|
||||
ClientboundPlayerInfoUpdatePacket packet = (ClientboundPlayerInfoUpdatePacket) packet0;
|
||||
EnumSet<ClientboundPlayerInfoUpdatePacket.Action> actions = packet.actions();
|
||||
List<ClientboundPlayerInfoUpdatePacket.Entry> updatedList = new ArrayList<>();
|
||||
for (ClientboundPlayerInfoUpdatePacket.Entry nmsData : packet.entries()) {
|
||||
GameProfile profile = nmsData.profile();
|
||||
Component displayName = nmsData.displayName();
|
||||
int latency = nmsData.latency();
|
||||
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME)) {
|
||||
Component expectedDisplayName = ((FabricTabList)receiver.getTabList()).getExpectedDisplayNames().get(nmsData.profileId());
|
||||
if (expectedDisplayName != null) displayName = expectedDisplayName;
|
||||
}
|
||||
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY)) {
|
||||
latency = TAB.getInstance().getFeatureManager().onLatencyChange(receiver, nmsData.profileId(), latency);
|
||||
}
|
||||
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER)) {
|
||||
TAB.getInstance().getFeatureManager().onEntryAdd(receiver, nmsData.profileId(), profile.getName());
|
||||
}
|
||||
updatedList.add(new ClientboundPlayerInfoUpdatePacket.Entry(nmsData.profileId(), profile, nmsData.listed(),
|
||||
latency, nmsData.gameMode(), displayName, nmsData.listOrder(), nmsData.chatSession()));
|
||||
}
|
||||
packet.entries = updatedList;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Packet<?> buildTabListPacket(@NotNull TabList.Action action, @NotNull FabricTabList.Builder entry) {
|
||||
if (action == TabList.Action.REMOVE_PLAYER) {
|
||||
return new ClientboundPlayerInfoRemovePacket(Collections.singletonList(entry.getId()));
|
||||
}
|
||||
ClientboundPlayerInfoUpdatePacket packet = new ClientboundPlayerInfoUpdatePacket(Register1_19_3.actionMap.get(action), Collections.emptyList());
|
||||
packet.entries = Collections.singletonList(new ClientboundPlayerInfoUpdatePacket.Entry(
|
||||
entry.getId(),
|
||||
action == TabList.Action.ADD_PLAYER ? entry.createProfile() : null,
|
||||
entry.isListed(),
|
||||
entry.getLatency(),
|
||||
GameType.byId(entry.getGameMode()),
|
||||
entry.getDisplayName(),
|
||||
entry.getListOrder(),
|
||||
null
|
||||
));
|
||||
return packet;
|
||||
}
|
||||
|
||||
private static class Register1_19_3 {
|
||||
|
||||
static final Map<TabList.Action, EnumSet<ClientboundPlayerInfoUpdatePacket.Action>> actionMap = createActionMap();
|
||||
|
||||
private static Map<TabList.Action, EnumSet<ClientboundPlayerInfoUpdatePacket.Action>> createActionMap() {
|
||||
Map<TabList.Action, EnumSet<ClientboundPlayerInfoUpdatePacket.Action>> actions = new EnumMap<>(TabList.Action.class);
|
||||
actions.put(TabList.Action.ADD_PLAYER, EnumSet.allOf(ClientboundPlayerInfoUpdatePacket.Action.class));
|
||||
actions.put(TabList.Action.UPDATE_GAME_MODE, EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE));
|
||||
actions.put(TabList.Action.UPDATE_DISPLAY_NAME, EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME));
|
||||
actions.put(TabList.Action.UPDATE_LATENCY, EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY));
|
||||
actions.put(TabList.Action.UPDATE_LISTED, EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED));
|
||||
actions.put(TabList.Action.UPDATE_LIST_ORDER, EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LIST_ORDER));
|
||||
return actions;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
@@ -17,7 +17,8 @@ val fabrics = setOf(
|
||||
rootProject.projects.fabric,
|
||||
rootProject.projects.fabric.v1144,
|
||||
rootProject.projects.fabric.v1182,
|
||||
rootProject.projects.fabric.v1203
|
||||
rootProject.projects.fabric.v1203,
|
||||
rootProject.projects.fabric.v1213
|
||||
).map { it.dependencyProject }
|
||||
|
||||
tasks {
|
||||
|
||||
@@ -38,4 +38,5 @@ include(":fabric")
|
||||
include(":fabric:v1_14_4")
|
||||
include(":fabric:v1_18_2")
|
||||
include(":fabric:v1_20_3")
|
||||
include(":fabric:v1_21_3")
|
||||
include(":jar")
|
||||
@@ -2,15 +2,20 @@ package me.neznamy.tab.shared;
|
||||
|
||||
import me.neznamy.tab.api.placeholder.PlayerPlaceholder;
|
||||
import me.neznamy.tab.shared.TabConstants.CpuUsageCategory;
|
||||
import me.neznamy.tab.shared.config.files.config.Config;
|
||||
import me.neznamy.tab.shared.config.files.Config;
|
||||
import me.neznamy.tab.shared.config.mysql.MySQLUserConfiguration;
|
||||
import me.neznamy.tab.shared.cpu.TimedCaughtTask;
|
||||
import me.neznamy.tab.shared.features.*;
|
||||
import me.neznamy.tab.shared.features.belowname.BelowName;
|
||||
import me.neznamy.tab.shared.features.bossbar.BossBarManagerImpl;
|
||||
import me.neznamy.tab.shared.features.globalplayerlist.GlobalPlayerList;
|
||||
import me.neznamy.tab.shared.features.header.HeaderFooter;
|
||||
import me.neznamy.tab.shared.features.injection.PipelineInjector;
|
||||
import me.neznamy.tab.shared.features.layout.LayoutManagerImpl;
|
||||
import me.neznamy.tab.shared.features.nametags.NameTag;
|
||||
import me.neznamy.tab.shared.features.pingspoof.PingSpoof;
|
||||
import me.neznamy.tab.shared.features.playerlist.PlayerList;
|
||||
import me.neznamy.tab.shared.features.playerlistobjective.YellowNumber;
|
||||
import me.neznamy.tab.shared.features.redis.RedisPlayer;
|
||||
import me.neznamy.tab.shared.features.redis.RedisSupport;
|
||||
import me.neznamy.tab.shared.features.scoreboard.ScoreboardManagerImpl;
|
||||
|
||||
@@ -11,6 +11,8 @@ import org.jetbrains.annotations.NotNull;
|
||||
public enum ProtocolVersion {
|
||||
|
||||
UNKNOWN,
|
||||
V1_21_4 (769), // Predicted PVN
|
||||
V1_21_3 (768),
|
||||
V1_21_2 (768),
|
||||
V1_21_1 (767),
|
||||
V1_21 (767),
|
||||
@@ -84,7 +86,7 @@ public enum ProtocolVersion {
|
||||
private static final ProtocolVersion[] VALUES = values();
|
||||
|
||||
/** Newest MC version this plugin jar knows */
|
||||
public static final ProtocolVersion LATEST_KNOWN_VERSION = VALUES[1];
|
||||
public static final ProtocolVersion LATEST_KNOWN_VERSION = V1_21_3;
|
||||
|
||||
/** Version's network id found at <a href="https://wiki.vg/Protocol_version_numbers">wiki.vg</a> */
|
||||
private final int networkId;
|
||||
|
||||
@@ -53,12 +53,12 @@ public class RGBUtils {
|
||||
//{#RRGGBB>}text{#RRGGBB<}
|
||||
new CMIGradient(),
|
||||
//<#RRGGBB>Text</#RRGGBB>
|
||||
new CommonGradient(Pattern.compile("<#[0-9a-fA-F]{6}>[^<]*</#[0-9a-fA-F]{6}>"),
|
||||
Pattern.compile("<#[0-9a-fA-F]{6}\\|.>[^<]*</#[0-9a-fA-F]{6}>"),
|
||||
new CommonGradient(Pattern.compile("<#[0-9a-fA-F]{6}>.*?</#[0-9a-fA-F]{6}>"),
|
||||
Pattern.compile("<#[0-9a-fA-F]{6}\\|.>.*?</#[0-9a-fA-F]{6}>"),
|
||||
"<#", 9, 2, 9, 7),
|
||||
//<$#RRGGBB>Text<$#RRGGBB>
|
||||
new CommonGradient(Pattern.compile("<\\$#[0-9a-fA-F]{6}>[^<]*<\\$#[0-9a-fA-F]{6}>"),
|
||||
Pattern.compile("<\\$#[0-9a-fA-F]{6}\\|.>[^<]*<\\$#[0-9a-fA-F]{6}>"),
|
||||
new CommonGradient(Pattern.compile("<\\$#[0-9a-fA-F]{6}>.*?<\\$#[0-9a-fA-F]{6}>"),
|
||||
Pattern.compile("<\\$#[0-9a-fA-F]{6}\\|.>.*?<\\$#[0-9a-fA-F]{6}>"),
|
||||
"<$", 10, 3, 10, 7),
|
||||
new NexEngineGradient()
|
||||
};
|
||||
|
||||
@@ -60,7 +60,7 @@ public class MySQLCommand extends SubCommand {
|
||||
sendMessage(sender, getMessages().getMySQLFailNotEnabled());
|
||||
return;
|
||||
}
|
||||
TAB.getInstance().getCPUManager().runTask(() -> {
|
||||
TAB.getInstance().getCPUManager().getMysqlThread().execute(() -> {
|
||||
try {
|
||||
YamlPropertyConfigurationFile groupFile = new YamlPropertyConfigurationFile(Configs.class.getClassLoader().getResourceAsStream("config/groups.yml"), new File(TAB.getInstance().getDataFolder(), "groups.yml"));
|
||||
YamlPropertyConfigurationFile userFile = new YamlPropertyConfigurationFile(Configs.class.getClassLoader().getResourceAsStream("config/users.yml"), new File(TAB.getInstance().getDataFolder(), "users.yml"));
|
||||
@@ -88,7 +88,7 @@ public class MySQLCommand extends SubCommand {
|
||||
sendMessage(sender, getMessages().getMySQLFailNotEnabled());
|
||||
return;
|
||||
}
|
||||
TAB.getInstance().getCPUManager().runTask(() -> {
|
||||
TAB.getInstance().getCPUManager().getMysqlThread().execute(() -> {
|
||||
try {
|
||||
YamlPropertyConfigurationFile groupFile = new YamlPropertyConfigurationFile(Configs.class.getClassLoader().getResourceAsStream("config/groups.yml"), new File(TAB.getInstance().getDataFolder(), "config/groups.yml"));
|
||||
YamlPropertyConfigurationFile userFile = new YamlPropertyConfigurationFile(Configs.class.getClassLoader().getResourceAsStream("config/users.yml"), new File(TAB.getInstance().getDataFolder(), "config/users.yml"));
|
||||
|
||||
@@ -1,19 +1,17 @@
|
||||
package me.neznamy.tab.shared.command;
|
||||
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.config.PropertyConfiguration;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public abstract class PropertyCommand extends SubCommand {
|
||||
|
||||
/** All properties assignable with a command */
|
||||
private static final List<String> allProperties = Arrays.asList("tabprefix", "customtabname", "tabsuffix", "tagprefix", "tagsuffix");
|
||||
|
||||
protected PropertyCommand(String name) {
|
||||
super(name, null);
|
||||
}
|
||||
@@ -21,7 +19,7 @@ public abstract class PropertyCommand extends SubCommand {
|
||||
@Override
|
||||
public @NotNull List<String> complete(@Nullable TabPlayer sender, @NotNull String[] arguments) {
|
||||
if (arguments.length != 2) return Collections.emptyList();
|
||||
return getStartingArgument(allProperties, arguments[1]);
|
||||
return getStartingArgument(PropertyConfiguration.VALID_PROPERTIES, arguments[1]);
|
||||
}
|
||||
|
||||
protected void help(@Nullable TabPlayer sender) {
|
||||
@@ -47,7 +45,7 @@ public abstract class PropertyCommand extends SubCommand {
|
||||
value = value.substring(1, value.length()-1);
|
||||
}
|
||||
String property = args[1].toLowerCase();
|
||||
if (allProperties.contains(property)) {
|
||||
if (PropertyConfiguration.VALID_PROPERTIES.contains(property)) {
|
||||
if (hasPermission(sender, TabConstants.Permission.COMMAND_PROPERTY_CHANGE_PREFIX + property)) {
|
||||
saveEntity(sender, args[0], property, value, server, world);
|
||||
} else {
|
||||
|
||||
@@ -7,8 +7,8 @@ import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.config.file.ConfigurationFile;
|
||||
import me.neznamy.tab.shared.config.file.YamlConfigurationFile;
|
||||
import me.neznamy.tab.shared.config.file.YamlPropertyConfigurationFile;
|
||||
import me.neznamy.tab.shared.config.files.animations.Animations;
|
||||
import me.neznamy.tab.shared.config.files.config.Config;
|
||||
import me.neznamy.tab.shared.config.files.Animations;
|
||||
import me.neznamy.tab.shared.config.files.Config;
|
||||
import me.neznamy.tab.shared.config.mysql.MySQL;
|
||||
import me.neznamy.tab.shared.config.mysql.MySQLGroupConfiguration;
|
||||
import me.neznamy.tab.shared.config.mysql.MySQLUserConfiguration;
|
||||
|
||||
@@ -33,7 +33,7 @@ public class Converter {
|
||||
public void convert2810to290(@NotNull ConfigurationFile animations) {
|
||||
if (animations.getValues().size() == 1 && animations.getValues().containsKey("animations")) {
|
||||
TAB.getInstance().getPlatform().logInfo(TabComponent.fromColoredText(EnumChatFormat.YELLOW + "Performing configuration conversion from 2.8.10 to 2.9.0"));
|
||||
animations.setValues(animations.getConfigurationSection("animations"));
|
||||
animations.setValues(animations.getMap("animations"));
|
||||
animations.save();
|
||||
}
|
||||
}
|
||||
@@ -166,7 +166,7 @@ public class Converter {
|
||||
}
|
||||
|
||||
private void convertBelowName(@NotNull ConfigurationFile oldConfig, @NotNull ConfigurationFile newConfig) {
|
||||
newConfig.set("belowname-objective", oldConfig.getConfigurationSection("classic-vanilla-belowname"));
|
||||
newConfig.set("belowname-objective", oldConfig.getMap("classic-vanilla-belowname"));
|
||||
newConfig.set("belowname-objective.disable-in-worlds", oldConfig.getStringList("disable-features-in-worlds.belowname", Collections.singletonList("disabledworld")));
|
||||
if (TAB.getInstance().getPlatform().isProxy())
|
||||
newConfig.set("belowname-objective.disable-in-servers", oldConfig.getStringList("disable-features-in-servers.belowname", Collections.singletonList("disabledserver")));
|
||||
@@ -177,8 +177,8 @@ public class Converter {
|
||||
newConfig.set("bossbar.toggle-command", bossBar.getString("bossbar-toggle-command", "/bossbar"));
|
||||
newConfig.set("bossbar.remember-toggle-choice", bossBar.getBoolean("remember-toggle-choice", false));
|
||||
newConfig.set("bossbar.hidden-by-default", bossBar.getBoolean("hidden-by-default", false));
|
||||
Map<Object, Map<String, Object>> bars = bossBar.getConfigurationSection("bars");
|
||||
Map<String, List<Object>> perWorldBossBars = bossBar.getConfigurationSection("per-world");
|
||||
Map<Object, Map<String, Object>> bars = bossBar.getMap("bars");
|
||||
Map<String, List<Object>> perWorldBossBars = bossBar.getMap("per-world");
|
||||
List<Object> activeBossBars = new ArrayList<>(bossBar.getStringList("default-bars", new ArrayList<>()));
|
||||
String separator = TAB.getInstance().getPlatform().isProxy() ? "server" : "world";
|
||||
for (Map.Entry<String, List<Object>> entry : perWorldBossBars.entrySet()) {
|
||||
@@ -204,8 +204,8 @@ public class Converter {
|
||||
String separator = TAB.getInstance().getPlatform().isProxy() ? "server" : "world";
|
||||
newConfig.set("scoreboard", premiumConfig.getObject("scoreboard"));
|
||||
newConfig.set("scoreboard.permission-required-to-toggle", null);
|
||||
Map<String, Map<String,Object>> scoreboards = premiumConfig.getConfigurationSection("scoreboards");
|
||||
Map<String, String> perWorldScoreboards = premiumConfig.getConfigurationSection("scoreboard.per-world");
|
||||
Map<String, Map<String,Object>> scoreboards = premiumConfig.getMap("scoreboards");
|
||||
Map<String, String> perWorldScoreboards = premiumConfig.getMap("scoreboard.per-world");
|
||||
newConfig.set("scoreboard.default-scoreboard", null);
|
||||
newConfig.set("scoreboard.per-world", null);
|
||||
for (Map.Entry<String, String> entry : perWorldScoreboards.entrySet()) {
|
||||
@@ -256,7 +256,7 @@ public class Converter {
|
||||
newConfig.set("header-footer.disable-in-servers", oldConfig.getStringList("disable-features-in-servers.header-footer", Collections.singletonList("disabledserver")));
|
||||
|
||||
String separator = TAB.getInstance().getPlatform().isProxy() ? "server" : "world";
|
||||
Map<String, Map<String, Object>> perWorldSettings = oldConfig.getConfigurationSection("per-" + separator + "-settings");
|
||||
Map<String, Map<String, Object>> perWorldSettings = oldConfig.getMap("per-" + separator + "-settings");
|
||||
Map<String, Object> headerFooterMap = new LinkedHashMap<>();
|
||||
for (Map.Entry<String, Map<String, Object>> worldEntry : new LinkedHashMap<>(perWorldSettings).entrySet()) {
|
||||
Map<String, Object> headerFooter = new LinkedHashMap<>();
|
||||
@@ -272,10 +272,10 @@ public class Converter {
|
||||
private void convertOtherOptions(@NotNull ConfigurationFile oldConfig, @NotNull ConfigurationFile newConfig, @Nullable ConfigurationFile premiumConfig) {
|
||||
newConfig.set("prevent-spectator-effect.enabled", oldConfig.getBoolean("do-not-move-spectators",false));
|
||||
|
||||
Map<String,Object> placeholders = oldConfig.getConfigurationSection("placeholders");
|
||||
Map<String,Object> placeholders = oldConfig.getMap("placeholders");
|
||||
if (premiumConfig != null) {
|
||||
newConfig.set("placeholder-output-replacements", premiumConfig.getConfigurationSection("placeholder-output-replacements"));
|
||||
newConfig.set("conditions", premiumConfig.getConfigurationSection("conditions"));
|
||||
newConfig.set("placeholder-output-replacements", premiumConfig.getMap("placeholder-output-replacements"));
|
||||
newConfig.set("conditions", premiumConfig.getMap("conditions"));
|
||||
} else {
|
||||
newConfig.set("placeholder-output-replacements.%essentials_vanished%.yes", "&7| Vanished");
|
||||
newConfig.set("placeholder-output-replacements.%essentials_vanished%.no", "");
|
||||
@@ -285,7 +285,7 @@ public class Converter {
|
||||
}
|
||||
|
||||
newConfig.set("placeholders", placeholders);
|
||||
newConfig.set("placeholderapi-refresh-intervals", oldConfig.getConfigurationSection("placeholderapi-refresh-intervals"));
|
||||
newConfig.set("placeholderapi-refresh-intervals", oldConfig.getMap("placeholderapi-refresh-intervals"));
|
||||
newConfig.set("assign-groups-by-permissions", oldConfig.getBoolean("assign-groups-by-permissions", false));
|
||||
newConfig.set("primary-group-finding-list", oldConfig.getStringList("primary-group-finding-list", Arrays.asList("Owner","Admin","Mod","Helper","default")));
|
||||
|
||||
@@ -299,20 +299,20 @@ public class Converter {
|
||||
newConfig.set("mysql.password", "password");
|
||||
|
||||
if (TAB.getInstance().getPlatform().isProxy()) {
|
||||
newConfig.set("global-playerlist", oldConfig.getConfigurationSection("global-playerlist"));
|
||||
newConfig.set("global-playerlist", oldConfig.getMap("global-playerlist"));
|
||||
newConfig.set("global-playerlist.update-latency", false);
|
||||
newConfig.set("use-bukkit-permissions-manager", false);
|
||||
} else {
|
||||
newConfig.set("per-world-playerlist", oldConfig.getConfigurationSection("per-world-playerlist"));
|
||||
newConfig.set("per-world-playerlist", oldConfig.getMap("per-world-playerlist"));
|
||||
}
|
||||
}
|
||||
|
||||
private void convertGroupsAndUsers(@NotNull ConfigurationFile oldConfig, @NotNull ConfigurationFile groups, @NotNull ConfigurationFile users) {
|
||||
groups.setValues(oldConfig.getConfigurationSection("Groups"));
|
||||
users.setValues(oldConfig.getConfigurationSection("Users"));
|
||||
groups.setValues(oldConfig.getMap("Groups"));
|
||||
users.setValues(oldConfig.getMap("Users"));
|
||||
|
||||
String separator = TAB.getInstance().getPlatform().isProxy() ? "server" : "world";
|
||||
Map<String,Map<String,Object>> perWorldSettings = oldConfig.getConfigurationSection("per-" + separator + "-settings");
|
||||
Map<String,Map<String,Object>> perWorldSettings = oldConfig.getMap("per-" + separator + "-settings");
|
||||
Map<String,Object> groupMap = new LinkedHashMap<>();
|
||||
Map<String,Object> userMap = new LinkedHashMap<>();
|
||||
Map<String,Map<String,Object>> worldMap = new LinkedHashMap<>(perWorldSettings);
|
||||
@@ -325,10 +325,10 @@ public class Converter {
|
||||
}
|
||||
}
|
||||
groups.set("per-" + separator, groupMap);
|
||||
groups.set(TabConstants.DEFAULT_GROUP, groups.getConfigurationSection("_OTHER_"));
|
||||
groups.set(TabConstants.DEFAULT_GROUP, groups.getMap("_OTHER_"));
|
||||
groups.set("_OTHER_", null);
|
||||
users.set("per-" + separator, userMap);
|
||||
for (Object world : groups.getConfigurationSection("per-" + separator).keySet()) {
|
||||
for (Object world : groups.getMap("per-" + separator).keySet()) {
|
||||
String gPath = "per-" + separator + "." + world;
|
||||
if (!groups.hasConfigOption(gPath + "._OTHER_")) continue;
|
||||
groups.set(gPath + "." + TabConstants.DEFAULT_GROUP, groups.getObject(gPath + "._OTHER_"));
|
||||
@@ -386,7 +386,7 @@ public class Converter {
|
||||
}
|
||||
|
||||
// Merged refresh intervals
|
||||
Map<Object, Object> intervals = config.getConfigurationSection("placeholderapi-refresh-intervals");
|
||||
Map<Object, Object> intervals = config.getMap("placeholderapi-refresh-intervals");
|
||||
boolean updated = false;
|
||||
for (Map.Entry<?, ?> entry : new ArrayList<>(intervals.entrySet())) {
|
||||
Object value = entry.getValue();
|
||||
@@ -427,11 +427,11 @@ public class Converter {
|
||||
config.save();
|
||||
}
|
||||
});
|
||||
disabledConditionConverter.accept(config.getConfigurationSection("header-footer"));
|
||||
disabledConditionConverter.accept(config.getConfigurationSection("tablist-name-formatting"));
|
||||
disabledConditionConverter.accept(config.getConfigurationSection("scoreboard-teams"));
|
||||
disabledConditionConverter.accept(config.getConfigurationSection("yellow-number-in-tablist"));
|
||||
disabledConditionConverter.accept(config.getConfigurationSection("belowname-objective"));
|
||||
disabledConditionConverter.accept(config.getMap("header-footer"));
|
||||
disabledConditionConverter.accept(config.getMap("tablist-name-formatting"));
|
||||
disabledConditionConverter.accept(config.getMap("scoreboard-teams"));
|
||||
disabledConditionConverter.accept(config.getMap("yellow-number-in-tablist"));
|
||||
disabledConditionConverter.accept(config.getMap("belowname-objective"));
|
||||
|
||||
// Removed config option
|
||||
config.removeOption("layout.hide-vanished-players");
|
||||
@@ -451,32 +451,47 @@ public class Converter {
|
||||
public void convert409to410(@NotNull ConfigurationFile config) {
|
||||
if (config.hasConfigOption("yellow-number-in-tablist")) {
|
||||
TAB.getInstance().getPlatform().logInfo(TabComponent.fromColoredText(EnumChatFormat.YELLOW + "Performing configuration conversion from 4.0.9 to 4.1.0"));
|
||||
Map<Object, Object> section = config.getConfigurationSection("yellow-number-in-tablist");
|
||||
Map<Object, Object> section = config.getMap("yellow-number-in-tablist");
|
||||
section.put("fancy-value", "&7Ping: %ping%");
|
||||
config.set("yellow-number-in-tablist", null);
|
||||
config.set("playerlist-objective", section);
|
||||
}
|
||||
config.setIfMissing("belowname-objective.fancy-display-default", "NPC");
|
||||
config.setIfMissing("belowname-objective.fancy-display-players", "&c" + TabConstants.Placeholder.HEALTH);
|
||||
config.setIfMissing("belowname-objective.fancy-value-default", "NPC");
|
||||
config.setIfMissing("belowname-objective.fancy-value", "&c" + TabConstants.Placeholder.HEALTH);
|
||||
config.removeOption("tablist-name-formatting.align-tabsuffix-on-the-right");
|
||||
config.removeOption("tablist-name-formatting.character-width-overrides");
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts config from 4.1.9 to 5.0.0.
|
||||
* This removes unlimited nametag mode option from config, which got removed.
|
||||
* This removes unlimited nametag mode option from config, which got removed and adds update-latency option to global playerlist.
|
||||
*
|
||||
* @param config
|
||||
* Config file
|
||||
*/
|
||||
public void convert419to500(@NotNull ConfigurationFile config) {
|
||||
if (config.hasConfigOption("scoreboard-teams.unlimited-nametag-mode")) {
|
||||
if (config.removeOption("scoreboard-teams.unlimited-nametag-mode")) {
|
||||
TAB.getInstance().getPlatform().logInfo(TabComponent.fromColoredText(EnumChatFormat.YELLOW + "Performing configuration conversion from 4.1.9 to 5.0.0"));
|
||||
config.removeOption("scoreboard-teams.unlimited-nametag-mode");
|
||||
config.removeOption("scoreboard.respect-other-plugins");
|
||||
}
|
||||
if (!config.hasConfigOption("global-playerlist.update-latency")) {
|
||||
config.set("global-playerlist.update-latency", false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts config from 5.0.1 to 5.0.2.
|
||||
* This renames options under belowname-objective to make them more clear.
|
||||
*
|
||||
* @param config
|
||||
* Config file
|
||||
*/
|
||||
public void convert501to502(@NotNull ConfigurationFile config) {
|
||||
if (config.rename("belowname-objective.number", "belowname-objective.value")) {
|
||||
TAB.getInstance().getPlatform().logInfo(TabComponent.fromColoredText(EnumChatFormat.YELLOW + "Performing configuration conversion from 5.0.1 to 5.0.2"));
|
||||
}
|
||||
config.rename("belowname-objective.text", "belowname-objective.title");
|
||||
config.rename("belowname-objective.fancy-display-players", "belowname-objective.fancy-value");
|
||||
config.rename("belowname-objective.fancy-display-default", "belowname-objective.fancy-value-default");
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
package me.neznamy.tab.shared.config;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -12,6 +14,10 @@ import java.util.stream.Collectors;
|
||||
*/
|
||||
public interface PropertyConfiguration {
|
||||
|
||||
/** List of all valid properties for groups and users */
|
||||
@NotNull
|
||||
List<String> VALID_PROPERTIES = Lists.newArrayList("tagprefix", "tagsuffix", "tabprefix", "customtabname", "tabsuffix", "header", "footer");
|
||||
|
||||
/**
|
||||
* Sets property value of group or user to specified value. If {@code world} or
|
||||
* {@code server} is not {@code null}, value will be inserted as a per-world or
|
||||
@@ -147,4 +153,36 @@ public interface PropertyConfiguration {
|
||||
}
|
||||
return string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if configured property name is valid. If not, prints a warning.
|
||||
*
|
||||
* @param source
|
||||
* Source of the configuration (specific file or MySQL)
|
||||
* @param type
|
||||
* Entity type (group / player)
|
||||
* @param name
|
||||
* Name of the group or player
|
||||
* @param property
|
||||
* Configured property name
|
||||
* @param server
|
||||
* Server if this property is only in specified server
|
||||
* @param world
|
||||
* World if this property is only in specified world
|
||||
* @param startupWarn
|
||||
* Whether the message should be printed as startup warn and counted or not
|
||||
*/
|
||||
default void checkProperty(@NotNull String source, @NotNull String type, @NotNull String name, @NotNull String property,
|
||||
@Nullable String server, @Nullable String world, boolean startupWarn) {
|
||||
if (VALID_PROPERTIES.contains(property)) return;
|
||||
StringBuilder msg = new StringBuilder(String.format("[%s] Unknown property \"%s\" defined for %s \"%s\"", source, property, type, name));
|
||||
if (world != null) msg.append(" in world \"").append(world).append("\"");
|
||||
if (server != null) msg.append(" in server \"").append(server).append("\"");
|
||||
msg.append(". Valid properties: ").append(VALID_PROPERTIES);
|
||||
if (startupWarn) {
|
||||
TAB.getInstance().getConfigHelper().startup().startupWarn(msg.toString());
|
||||
} else {
|
||||
TAB.getInstance().getConfigHelper().runtime().error(msg.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||