Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
827197f916 | ||
|
|
29a35cf1cc | ||
|
|
f26516ecae | ||
|
|
f26a7ad2b1 | ||
|
|
96e0a29573 | ||
|
|
f30d667ca8 | ||
|
|
8db7a1d6d9 | ||
|
|
d285a0512e | ||
|
|
256d0d5c84 | ||
|
|
b808e1f9d1 | ||
|
|
07cd781ec1 | ||
|
|
9115b687f4 | ||
|
|
7c51377203 | ||
|
|
193cd02072 | ||
|
|
dbb6d91f0c | ||
|
|
a82714e811 | ||
|
|
bcd1bbac62 | ||
|
|
2fde0e68ca | ||
|
|
e6e9b1c5ae | ||
|
|
48b789c51d | ||
|
|
433b39209d | ||
|
|
b6df7a9f33 | ||
|
|
ad1d55537f | ||
|
|
9982d2c6ef | ||
|
|
1e3f622a98 | ||
|
|
ce05d5f6a6 | ||
|
|
19378e2095 | ||
|
|
0d992f1e50 | ||
|
|
de7cabfd54 | ||
|
|
cfe73a00ee | ||
|
|
35a7acbbd9 | ||
|
|
20eebca395 | ||
|
|
7c2a4deb1a | ||
|
|
e227e6a0f4 | ||
|
|
e015c29a3d | ||
|
|
a2859937e2 | ||
|
|
cd26252444 | ||
|
|
47894a6e74 | ||
|
|
bcf94180d5 | ||
|
|
6d2eeda28c | ||
|
|
049c768013 | ||
|
|
ffae488d1e | ||
|
|
3ea0ae6e5b | ||
|
|
530c25655c | ||
|
|
ca830fd64c | ||
|
|
4272476230 | ||
|
|
b7e44246ce | ||
|
|
33b9578fd1 | ||
|
|
df0a96843d | ||
|
|
5b5f7dfc12 | ||
|
|
8b094adcca | ||
|
|
de9d9ab844 | ||
|
|
2fd6d2f50b | ||
|
|
349c9eca4b | ||
|
|
1f119233d7 | ||
|
|
17c2d63892 | ||
|
|
79c76e913d | ||
|
|
6501480980 | ||
|
|
5ad9bbba37 | ||
|
|
f012448758 | ||
|
|
99b6d635d7 | ||
|
|
5f742627e9 | ||
|
|
a9e725002a | ||
|
|
49150f9b1e | ||
|
|
89aa1ba94c | ||
|
|
6aca313bd8 | ||
|
|
f76d5a958f | ||
|
|
b833a10de4 | ||
|
|
f3ce5632fb | ||
|
|
f5bf4c3831 | ||
|
|
08a3acc5e0 | ||
|
|
5b63034607 | ||
|
|
d4543032d9 | ||
|
|
254a91c3bd | ||
|
|
4c01ddaaef | ||
|
|
2993d1450a | ||
|
|
582c3a4c1a | ||
|
|
dd6b964405 | ||
|
|
16827bfbac | ||
|
|
3c4d72bb71 | ||
|
|
004036e019 | ||
|
|
ba06f80baa | ||
|
|
ee47428a80 | ||
|
|
1e40cae27b | ||
|
|
f5b7bb5549 | ||
|
|
115883f8d0 | ||
|
|
a73446a1c5 | ||
|
|
7501c4f76b | ||
|
|
9f038fae63 | ||
|
|
a61a520570 | ||
|
|
885783cd9a | ||
|
|
181f850c58 | ||
|
|
fe8742b580 | ||
|
|
daeba7caa9 | ||
|
|
1340e6dc1b | ||
|
|
cba7655f0d | ||
|
|
ac7e2a9aef | ||
|
|
fcc0bfbf32 | ||
|
|
a7e86bdb64 | ||
|
|
c3eecd8d14 | ||
|
|
25e6da3ac5 | ||
|
|
a7e5786fee | ||
|
|
a6bccc3dd3 | ||
|
|
d8cd39dc3e | ||
|
|
6eacf833f5 | ||
|
|
dbdd417e0d | ||
|
|
4c8dd52f28 | ||
|
|
31a2d8a02b | ||
|
|
f246d57a7e | ||
|
|
4ae856630f | ||
|
|
8652baf2ae | ||
|
|
4a18853e8c | ||
|
|
a60be9d8c1 | ||
|
|
e16b47409c | ||
|
|
6f14cbe746 | ||
|
|
ea79c9c5dd | ||
|
|
650d13ed71 | ||
|
|
169b8b8e94 | ||
|
|
8cef4a5bcb | ||
|
|
70b40802cc | ||
|
|
416e707cda | ||
|
|
12578293bc | ||
|
|
042ab6850b | ||
|
|
09fc042630 | ||
|
|
c73d351bf1 | ||
|
|
4d4e0a9c2a | ||
|
|
2fd29b2347 | ||
|
|
f332068318 | ||
|
|
186c79f93c | ||
|
|
3770066485 | ||
|
|
17b0ffe52b | ||
|
|
d259b5bb78 | ||
|
|
f6c8d24177 | ||
|
|
ab3bd61d3c | ||
|
|
6a5cee8f0e | ||
|
|
549732fc8b | ||
|
|
09d400c6fb | ||
|
|
7e1cdafa78 | ||
|
|
434c0216d5 | ||
|
|
d5a0ae8e71 | ||
|
|
79e56c6a30 | ||
|
|
11cc1c3f25 | ||
|
|
8b343bcfb6 | ||
|
|
ecd83beb7d | ||
|
|
438f21fb11 | ||
|
|
dd1524cf31 | ||
|
|
0f85aa5384 | ||
|
|
4644a0ae96 | ||
|
|
84b3efe0fc | ||
|
|
511acbd5ff | ||
|
|
8749b3b354 | ||
|
|
b66e63d242 | ||
|
|
ba951238c3 | ||
|
|
a4eb76f1cf | ||
|
|
c486899dcf | ||
|
|
b212f0d5be | ||
|
|
caa534ff7f | ||
|
|
9c2fac6749 | ||
|
|
2a4f918a10 | ||
|
|
b45e2afe4c | ||
|
|
2c7237e5d9 | ||
|
|
26477e3150 |
No files matched your search
@@ -1,7 +1,9 @@
|
||||
# About
|
||||
This folder contains the plugin's description as advertised on various marketplaces which use various syntax's (bbcode, markdown).
|
||||
This folder contains the plugin's description as advertised on various marketplaces which use various syntaxes
|
||||
(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.
|
||||
While this is not an 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:
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
[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.
|
||||
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. The 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 of your questions.
|
||||
|
||||
[IMG]https://img.shields.io/badge/Release-5.0.3-blue.svg[/IMG]
|
||||
[IMG]https://img.shields.io/badge/Release-5.0.7-blue.svg[/IMG]
|
||||
[IMG]https://img.shields.io/badge/Minecraft-1.5%20--%201.21.4-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]
|
||||
@@ -57,7 +57,7 @@ Before you consider requiring assistance, check out the [URL='https://github.com
|
||||
[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]
|
||||
[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+
|
||||
@@ -97,7 +97,7 @@ Before you consider requiring assistance, check out the [URL='https://github.com
|
||||
[*]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]
|
||||
[IMG]https://raw.githubusercontent.com/NEZNAMY/TAB/master/.github/description/images/Example_ScoreboardComparison.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
|
||||
@@ -127,12 +127,6 @@ Before you consider requiring assistance, check out the [URL='https://github.com
|
||||
[*]Create custom boss bars, toggle bossbar visibility, announce bossbar
|
||||
[*]... and more!
|
||||
[/LIST]
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
[LIST]
|
||||
[*]Placeholder support
|
||||
[LIST]
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||

|
||||
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.
|
||||
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.
|
||||
The 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 of your questions.
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
[](https://github.com/NEZNAMY/TAB)
|
||||
@@ -13,8 +13,8 @@ The extensive [wiki](https://github.com/NEZNAMY/TAB/wiki) answers any and all of
|
||||
|
||||
# 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.
|
||||
|
||||
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.
|
||||
|
||||

|
||||
@@ -56,7 +56,7 @@ Before you consider requiring assistance, check out the [wiki](https://github.co
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
* Display value of any numeric placeholder or hearts
|
||||
* Display any text on 1.20.3+
|
||||
* Disable if a specified condition is met
|
||||
@@ -91,18 +91,18 @@ Before you consider requiring assistance, check out the [wiki](https://github.co
|
||||
* 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+
|
||||
* Fully configurable text on the 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
|
||||
* Automatic detection of scoreboards from other plugins to hide own and show it again once the other plugin hides its scoreboard
|
||||
|
||||

|
||||
* Only display players from current world in tablist
|
||||
* Only display players from the same 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
|
||||
@@ -117,7 +117,7 @@ Before you consider requiring assistance, check out the [wiki](https://github.co
|
||||
* Create custom placeholders
|
||||
* Create custom boss bars, toggle bossbar visibility, announce bossbar
|
||||
* ... and more!
|
||||
|
||||
|
||||
.
|
||||
.
|
||||
.
|
||||
@@ -142,25 +142,24 @@ Placeholder output replacements
|
||||
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
|
||||
* 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
|
||||
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
|
||||
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: 47 KiB |
|
After Width: | Height: | Size: 39 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 41 KiB |
|
After Width: | Height: | Size: 43 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 72 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 54 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 39 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 72 KiB |
|
After Width: | Height: | Size: 239 KiB |
|
After Width: | Height: | Size: 36 KiB |
@@ -0,0 +1,262 @@
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
|
||||
line_spacing = 10
|
||||
padding = 10
|
||||
|
||||
class Text:
|
||||
|
||||
def __init__(self, text, color="black", font=ImageFont.truetype("arial.ttf", 20)):
|
||||
self.text = text
|
||||
self.color = color
|
||||
self.font = font
|
||||
|
||||
class ImageGenerator:
|
||||
|
||||
def __init__(self, overlay_text, second_images, output_path, texts):
|
||||
self.overlay_text = overlay_text
|
||||
self.second_images = second_images
|
||||
self.output_path = output_path
|
||||
self.texts = texts
|
||||
|
||||
self.header_template = Image.open("Template_Small.png")
|
||||
self.example_images = [Image.open(path) for path in second_images]
|
||||
|
||||
def calculate_total_height(self):
|
||||
total_text_height = 0
|
||||
for text in self.texts:
|
||||
text_width, text_height = ImageDraw.Draw(self.header_template).textbbox((0, 0), text.text, font=text.font)[2:]
|
||||
total_text_height += text_height + line_spacing
|
||||
|
||||
total_second_images_height = sum([img.height+padding for img in self.example_images])
|
||||
return self.header_template.height + total_second_images_height + total_text_height + (3 * padding)
|
||||
|
||||
def draw_rounded_border(self, final_img, draw, radius, color, width):
|
||||
draw.rounded_rectangle([(0, 0), (final_img.width - 1, final_img.height - 1)], radius=radius, outline=color, width=width)
|
||||
|
||||
def paste_top_image(self, final_img, font):
|
||||
header_with_text = self.header_template.copy()
|
||||
draw = ImageDraw.Draw(header_with_text)
|
||||
|
||||
text_width, text_height = draw.textbbox((0, 0), self.overlay_text, font=font)[2:]
|
||||
text_x = (header_with_text.width - text_width) // 2 # Center text horizontally
|
||||
text_y = 20 # Padding from the top
|
||||
|
||||
draw.text((text_x, text_y), self.overlay_text, fill="white", font=font)
|
||||
|
||||
final_img.paste(header_with_text, (padding, padding))
|
||||
|
||||
def paste_examples(self, final_img):
|
||||
y_offset = self.header_template.height + 2 * padding
|
||||
for img2 in self.example_images:
|
||||
img2_x = (final_img.width - img2.width) // 2 # Center horizontally
|
||||
final_img.paste(img2, (img2_x, y_offset))
|
||||
y_offset += img2.height + padding
|
||||
|
||||
return y_offset
|
||||
|
||||
def paste_text(self, final_draw, y_offset):
|
||||
y_offset += padding # Extra padding before text
|
||||
for text in self.texts:
|
||||
fancy_line = f"• {text.text}" # Add bullet point
|
||||
final_draw.text((padding, y_offset), fancy_line, fill=text.color, font=text.font)
|
||||
y_offset += ImageDraw.Draw(self.header_template).textbbox((0, 0), fancy_line, font=text.font)[3] + line_spacing # Add space between lines
|
||||
|
||||
def save(self, final_img):
|
||||
final_img.save(self.output_path)
|
||||
|
||||
|
||||
def create_image(overlay_text, second_images, output_path, texts):
|
||||
generator = ImageGenerator(overlay_text, second_images, output_path, texts)
|
||||
final_img = Image.new("RGB", (generator.header_template.width + 2 * padding, generator.calculate_total_height()), "white")
|
||||
final_draw = ImageDraw.Draw(final_img)
|
||||
|
||||
# Rounded border
|
||||
generator.draw_rounded_border(final_img, final_draw, 15, (39, 0, 114), 5)
|
||||
|
||||
# Template with text
|
||||
generator.paste_top_image(final_img, ImageFont.truetype("arial.ttf", 60))
|
||||
|
||||
# Examples
|
||||
y_offset = generator.paste_examples(final_img)
|
||||
|
||||
# Text
|
||||
generator.paste_text(final_draw, y_offset)
|
||||
|
||||
# Save
|
||||
generator.save(final_img)
|
||||
|
||||
if __name__ == "__main__":
|
||||
create_image("Header & Footer", ["Example_HeaderFooter.png"], "Generated_HeaderFooter.png",
|
||||
[
|
||||
Text("Per-world / per-server support"),
|
||||
Text("Per-group / per-player support"),
|
||||
Text("Disable if a specified condition is met")
|
||||
]
|
||||
)
|
||||
create_image("NameTags", ["Example_NameTags.png"], "Generated_NameTags.png",
|
||||
[
|
||||
Text("Configurable prefix and suffix"),
|
||||
Text("Per-world / per-server support"),
|
||||
Text("Per-group / per-player support"),
|
||||
Text("Disable if a specified condition is met"),
|
||||
Text("Make nametags completely invisible"),
|
||||
Text("Control collision rule"),
|
||||
Text("Automatic compensation for 1.8.x client sided bug making nametags of invisible players still visible"),
|
||||
Text("Anti-override to prevent other plugins from overriding this one")
|
||||
]
|
||||
)
|
||||
create_image("Sorting", ["Example_Sorting.png"], "Generated_Sorting.png",
|
||||
[
|
||||
Text("Sorting by primary permission group"),
|
||||
Text("Sorting by permission nodes"),
|
||||
Text("Sorting by a numeric placeholder from lowest to highest or highest to lowest"),
|
||||
Text("Alphabetically by a placeholder (A-Z or Z-A)"),
|
||||
Text("Sorting by pre-defined placeholder values"),
|
||||
Text("Combination of multiple sorting types"),
|
||||
Text("Enable / disable case-sensitive sorting")
|
||||
]
|
||||
)
|
||||
create_image("Tablist Formatting", ["Example_TablistFormatting.png"], "Generated_TablistFormatting.png",
|
||||
[
|
||||
Text("Configurable prefix, name and suffix"),
|
||||
Text("Per-world / per-server support"),
|
||||
Text("Per-group / per-player support"),
|
||||
Text("Disable if a specified condition is met"),
|
||||
Text("Anti-override to prevent other plugins from overriding this one")
|
||||
]
|
||||
)
|
||||
create_image("Playerlist Objective",
|
||||
[
|
||||
"Example_PlayerlistObjectiveNumber.png",
|
||||
"Example_PlayerlistObjectiveHealth.png",
|
||||
"Example_PlayerlistObjectiveFancy.png"
|
||||
],
|
||||
"Generated_PlayerlistObjective.png",
|
||||
[
|
||||
Text("Display value of any numeric placeholder or hearts"),
|
||||
Text("Display any text on 1.20.3+"),
|
||||
Text("Disable if a specified condition is met")
|
||||
]
|
||||
)
|
||||
create_image("Belowname", ["Example_BelownameComparison.png"], "Generated_Belowname.png",
|
||||
[
|
||||
Text("Display value of any numeric placeholder (<1.20.3) or any text (1.20.3+)"),
|
||||
Text("Configurable text"),
|
||||
Text("Disable if a specified condition is met")
|
||||
]
|
||||
)
|
||||
create_image("Bossbar", ["Example_BossBar.png"], "Generated_Bossbar.png",
|
||||
[
|
||||
Text("Configurable text, progress, color and style"),
|
||||
Text("Define display condition to see a bossbar"),
|
||||
Text("Announce defined bossbar for specified amount of time"),
|
||||
Text("Configurable toggle command and message"),
|
||||
Text("Option to remember toggle choice between logins")
|
||||
]
|
||||
)
|
||||
create_image("Global Playerlist", [], "Generated_GlobalPlayerlist.png",
|
||||
[
|
||||
Text("Display players from all servers on a proxy network in tablist"),
|
||||
Text("Configurable server groups to share playerlist instead of with all servers"),
|
||||
Text("Ability to create isolated servers"),
|
||||
Text("Servers where people will see everyone"),
|
||||
Text("Compatible with vanish plugins"),
|
||||
Text("Option to display players from other servers as spectators")
|
||||
]
|
||||
)
|
||||
create_image("Layout", ["Example_Layout.png"], "Generated_Layout.png",
|
||||
[
|
||||
Text("Customize all 80 tablist slots"),
|
||||
Text("Create static slots with any text and skin"),
|
||||
Text("Create player groups based on conditions"),
|
||||
Text("Multiple layouts based on conditions")
|
||||
]
|
||||
)
|
||||
create_image("Scoreboard", ["Example_ScoreboardComparison.png"], "Generated_Scoreboard.png",
|
||||
[
|
||||
Text("Display up to 15 lines"),
|
||||
Text("Option to show 0 (or any other static number) in every line instead of 1-15"),
|
||||
Text("Fully configurable text on right side of lines on 1.20.3+"),
|
||||
Text("Displaying different scoreboards based on conditions (output of placeholder, permissions)"),
|
||||
Text("Up to 64 characters per line for <1.13, unlimited for 1.13+ clients"),
|
||||
Text("Customizable toggle command and message"),
|
||||
Text("Option to remember toggle choice between logins"),
|
||||
Text("Automatic detection of scoreboards from other plugins to hide own and show it again once other plugin hides its scoreboard")
|
||||
]
|
||||
)
|
||||
create_image("Per world Playerlist", [], "Generated_PerWorldPlayerlist.png",
|
||||
[
|
||||
Text("Only display players from current world in tablist"),
|
||||
Text("Group worlds which should share players"),
|
||||
Text("Worlds where players see everyone"),
|
||||
Text("Option to see all players in all worlds for players with permission")
|
||||
]
|
||||
)
|
||||
create_image("API", [], "Generated_API.png",
|
||||
[
|
||||
Text("Tablist formatting - Change player prefix/name/suffix"),
|
||||
Text("Header / Footer - Change values"),
|
||||
Text("Teams - Change nametag prefix / suffix, change visibility, collision rule"),
|
||||
Text("Sorting - Change sorting priority"),
|
||||
Text("Scoreboard - Create custom scoreboards, show different scoreboard, toggle scoreboard, announce scoreboard"),
|
||||
Text("Bossbar - Create custom boss bars, toggle bossbar visibility, announce/send bossbar"),
|
||||
Text("Layout - Create custom layouts, send existing layouts"),
|
||||
Text("Placeholders - Create custom placeholders")
|
||||
]
|
||||
)
|
||||
create_image("Placeholders", [], "Generated_Placeholders.png",
|
||||
[
|
||||
Text("Internal placeholders with customizable output"),
|
||||
Text("Create and use custom animations"),
|
||||
Text("All placeholders and animations are supported in all features"),
|
||||
Text("Full PlaceholderAPI support including relational placeholders"),
|
||||
Text("PlaceholderAPI support on BungeeCord / Velocity"),
|
||||
Text("Conditional placeholders")
|
||||
]
|
||||
)
|
||||
create_image("Conditional placeholders", [], "Generated_ConditionalPlaceholders.png",
|
||||
[
|
||||
Text("Conditions based on placeholder output (comparing numeric value, exact output)"),
|
||||
Text("Conditions based on permission node"),
|
||||
Text("Combine conditions with AND or OR"),
|
||||
Text("Configurable output of both cases when condition is and isn't met"),
|
||||
Text("Placeholder support in condition output")
|
||||
]
|
||||
)
|
||||
create_image("Placeholder replacements", [], "Generated_PlaceholderOutputReplacements.png",
|
||||
[
|
||||
Text("Modify output of any placeholder, including PlaceholderAPI placeholders"),
|
||||
Text("Replace exact text with another text"),
|
||||
Text("Change output if it's in a number interval"),
|
||||
Text("Set value if none of the above is met"),
|
||||
Text("Use original placeholder's output in new one"),
|
||||
Text("Support for nested placeholders"),
|
||||
Text("Can be used in other plugins as well")
|
||||
]
|
||||
)
|
||||
create_image("RGB Support", [], "Generated_RGB.png",
|
||||
[
|
||||
Text("5 supported RGB patterns"),
|
||||
Text("3 supported gradient patterns"),
|
||||
Text("Automatically display closest color to legacy clients"),
|
||||
Text("Supported in all features where possible")
|
||||
]
|
||||
)
|
||||
create_image("High Performance", [], "Generated_HighPerformance.png",
|
||||
[
|
||||
Text("Full control of CPU usage by optimizing configuration and disabling unwanted features"),
|
||||
Text("Fully asynchronous - no impact on TPS"),
|
||||
Text("Check plugin's CPU usage using /tab cpu"),
|
||||
Text("Constant improvements to reduce usage"),
|
||||
Text("Handle thousands of players")
|
||||
]
|
||||
)
|
||||
create_image("And more!", [], "Generated_More.png",
|
||||
[
|
||||
Text("RedisBungee support for multiple proxies"),
|
||||
Text("MySQL support to store groups/users"),
|
||||
Text("Potential errors logged into a file instead of spamming the console"),
|
||||
Text("Yaml error assistant for more user-friendly messages and fix suggestions"),
|
||||
Text("Many misconfiguration checks to help solve issues much faster")
|
||||
]
|
||||
)
|
||||
|
Before Width: | Height: | Size: 75 KiB |
|
Before Width: | Height: | Size: 86 KiB |
|
Before Width: | Height: | Size: 85 KiB |
|
Before Width: | Height: | Size: 92 KiB |
|
Before Width: | Height: | Size: 90 KiB |
|
Before Width: | Height: | Size: 81 KiB |
|
Before Width: | Height: | Size: 85 KiB |
|
Before Width: | Height: | Size: 96 KiB |
|
Before Width: | Height: | Size: 95 KiB |
|
Before Width: | Height: | Size: 89 KiB |
|
Before Width: | Height: | Size: 83 KiB |
|
Before Width: | Height: | Size: 94 KiB |
|
After Width: | Height: | Size: 9.6 KiB |
@@ -34,13 +34,13 @@
|
||||
* [RGB/font usage](https://github.com/NEZNAMY/TAB/wiki/How-to-use-RGB-color-codes)
|
||||
|
||||
## Other
|
||||
* [Client-sided mechanics](https://github.com/NEZNAMY/TAB/wiki/Client%E2%80%90sided-mechanics)
|
||||
* [Quick PlaceholderAPI startup guide](https://github.com/NEZNAMY/TAB/wiki/Quick-PlaceholderAPI-startup-guide)
|
||||
* [How to set up compatibility with glow plugins](https://github.com/NEZNAMY/TAB/wiki/How-to-make-TAB-compatible-with-glow-plugins)
|
||||
* [How to display name from nickname plugins](https://github.com/NEZNAMY/TAB/wiki/How-to-display-name-from-nickname-plugins)
|
||||
* [How to configure weights in LuckPerms](https://github.com/NEZNAMY/TAB/wiki/How-to-setup-weights-priorities)
|
||||
* [How to save config in UTF-8 encoding](https://github.com/NEZNAMY/TAB/wiki/How-to-save-the-config-in-UTF8-encoding)
|
||||
* [Mini guides collection](https://github.com/NEZNAMY/TAB/wiki/Mini-guides-collection)
|
||||
* [Additional information](https://github.com/NEZNAMY/TAB/wiki/Additional-information)
|
||||
* [Bedrock compatibility](https://github.com/NEZNAMY/TAB/wiki/Bedrock-compatibility)
|
||||
* [Client-sided mechanics](https://github.com/NEZNAMY/TAB/wiki/Client%E2%80%90sided-mechanics)
|
||||
* [Developer API](https://github.com/NEZNAMY/TAB/wiki/Developer-API)
|
||||
* [How to display name from nickname plugins](https://github.com/NEZNAMY/TAB/wiki/How-to-display-name-from-nickname-plugins)
|
||||
* [How to save config in UTF-8 encoding](https://github.com/NEZNAMY/TAB/wiki/How-to-save-the-config-in-UTF8-encoding)
|
||||
* [How to set up compatibility with glow plugins](https://github.com/NEZNAMY/TAB/wiki/How-to-make-TAB-compatible-with-glow-plugins)
|
||||
* [Mini guides collection](https://github.com/NEZNAMY/TAB/wiki/Mini-guides-collection)
|
||||
* [Optimizing plugin's CPU usage](https://github.com/NEZNAMY/TAB/wiki/Optimizing-the-plugin)
|
||||
* [Developer API](https://github.com/NEZNAMY/TAB/wiki/Developer-API)
|
||||
* [Quick PlaceholderAPI startup guide](https://github.com/NEZNAMY/TAB/wiki/Quick-PlaceholderAPI-startup-guide)
|
||||
@@ -2,52 +2,16 @@
|
||||
This is a collection of random information about the plugin not necessarily limited to a single feature or function.
|
||||
|
||||
# Content
|
||||
* [Bedrock compatibility](#bedrock-compatibility)
|
||||
* [Detection](#detection)
|
||||
* [Differences](#differences)
|
||||
* [Issues with `.` in name](#issues-with--in-name)
|
||||
* [Vanish detection](#vanish-detection)
|
||||
* [Async player loading](#async-player-loading)
|
||||
* [Scoreboard objective and team naming](#scoreboard-objective-and-team-naming)
|
||||
* [Per-version experience](#per-version-experience)
|
||||
* [Detection](#detection-1)
|
||||
* [Differences](#differences-1)
|
||||
* [Detection](#detection)
|
||||
* [Differences](#differences)
|
||||
* [Debug config option](#debug-config-option)
|
||||
|
||||
# Bedrock compatibility
|
||||
## Detection
|
||||
For TAB to properly detect player as a bedrock player, floodgate and TAB must be installed on the same server instance.
|
||||
If TAB is installed on bukkit and floodgate is on both bungeecord and bukkit,
|
||||
make sure [they are connected](https://geysermc.org/wiki/floodgate/setup/?platform=proxy-servers).
|
||||
|
||||
To make sure floodgate is configured correctly, you have two options:
|
||||
* Set `debug: true` in TAB config and check console output on join/reload. It should say `Floodgate returned bedrock status TRUE for player <player>`.
|
||||
* Run `/tab parse <player> %bedrock%`. It should return `true`.
|
||||
|
||||
If any of them say `false` for an actual bedrock player, you didn't connect floodgate correctly.
|
||||
|
||||
## Differences
|
||||
It is a job of plugin allowing bedrock clients to connect (probably Geyser) to correctly translate all packets.
|
||||
If you are experiencing an issue on bedrock but not java, it is most likely not an issue in TAB code.
|
||||
|
||||
Currently, TAB does check for bedrock clients for:
|
||||
* Displaying scoreboard numbers as 1–15 instead of all 0 even if it's set to all 0, because lines would not be ordered correctly.
|
||||
* Disabling [Layout](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Layout) for bedrock players.
|
||||
* Disabling [Playerlist objective](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Playerlist-Objective) for bedrock players due to many issues with the feature on bedrock.
|
||||
|
||||
These are the only 3 known changes that are required on TAB side.
|
||||
|
||||
## Issues with `.` in name
|
||||
If you configured Geyser to prefix bedrock player names with `.`, it will mess up config loading,
|
||||
because `.` is used to split a section path.
|
||||
This means that using `/tab player <playername containing .> ...` will not work.
|
||||
There are three solutions available:
|
||||
* Use player UUID if commands are executed via another plugin (`/tab player <uuid> ...`).
|
||||
* Use `playeruuid` type, which accepts player name and saves the online player's UUID (`/tab playeruuid <player name> ...`).
|
||||
* Use a different username prefix than `.`.
|
||||
|
||||
# Vanish detection
|
||||
Although TAB does not manage player vanishing in any way,
|
||||
Although TAB does not manage vanishing players in any way,
|
||||
it checks vanish status for features to be properly compatible.
|
||||
At the moment, vanishing detection is used for:
|
||||
* `%vanished%` placeholder (used for internal functionality, but can be used to display vanish status if one wants to)
|
||||
@@ -61,14 +25,19 @@ Permission `tab.seevanished` allows players to:
|
||||
* See vanished players in [Layout](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Layout).
|
||||
* Not have teams of vanished players unregistered, which would make them not appear sorted properly and have no nametag prefix/suffix.
|
||||
|
||||
For a player to be detected, player has to have `vanished` metadata flag set to `true`. Most, if not all vanish plugins do this, including, but not limited to CMI, Essentials, SuperVanish, PremiumVanish and probably more. If TAB is installed on BungeeCord, [Bridge](https://github.com/NEZNAMY/TAB/wiki/TAB-Bridge) must be installed on backend servers to forward vanish status to the proxy.
|
||||
For a player to be detected, player has to have `vanished` metadata flag set to `true`.
|
||||
Most, (if not all) vanish plugins do this, including, but not limited to CMI,
|
||||
Essentials, SuperVanish, PremiumVanish and probably more.
|
||||
If TAB is installed on proxy,
|
||||
[Bridge](https://github.com/NEZNAMY/TAB/wiki/TAB-Bridge) must be installed on backend servers
|
||||
to forward vanish status to the proxy.
|
||||
|
||||
Additionally, if PremiumVanish is installed and vanish status of a player changes,
|
||||
it is asked if the vanished player can be seen by each player.
|
||||
Players who are marked as being able to see that vanished player by PV do not require `tab.seevaninished` permission.
|
||||
|
||||
# Async player loading
|
||||
In order to not slow down the server, TAB loads players asynchronously on join.
|
||||
To not slow down the server, TAB loads players asynchronously on join.
|
||||
As a casual user, this does not impact you, but there are some cases where you'll feel it:
|
||||
* Players will not be accessible in the [API](https://github.com/NEZNAMY/TAB/wiki/Developer-API) during PlayerJoinEvent
|
||||
* [TAB's PlaceholderAPI expansion](https://github.com/NEZNAMY/TAB/wiki/Placeholders#placeholderapi) will not be accessible during the join process, such as when trying to use it in plugins editing join messages (the exception is `replace_` placeholder, which doesn't directly use any TAB code).
|
||||
@@ -84,7 +53,7 @@ TAB's objective and team naming:
|
||||
* `TAB-Scoreboard` for [Scoreboard](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Scoreboard)
|
||||
* `TAB-PlayerList` for [Playerlist objective](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Playerlist-Objective)
|
||||
* Teams ("Team ... already exists in this scoreboard")
|
||||
* For sidebar scoreboard: `TAB-SB-TM-x` where `x` is a number from 1 to 15+ (for each line)
|
||||
* For sidebar scoreboard: `TAB-Sidebar-x` where `x` is a number from 1 to 15+ (for each line)
|
||||
* For players: This is more complicated to use the 16-character limit to the max and heavily depends on configuration. If you are sorting by player names, part of player name will be there. It also often contains random non-alphanumeric symbols. The most reliable way is the team name (almost) always ending with `A` (or `B`, `C` etc. if you tried to sort several players into the same slot).
|
||||
|
||||
# Per-version experience
|
||||
@@ -102,42 +71,35 @@ You can check what version the plugin thinks player has by using any of the foll
|
||||
* Set `debug: true` in config and check console messages on player join, it will send a message containing game version ViaVersion API has returned
|
||||
|
||||
Player game version detection may fail. Here are a few possible scenarios why:
|
||||
* ViaVersion is installed on BungeeCord
|
||||
* And TAB on BungeeCord as well - ViaVersion makes BungeeCord API return version of the backend server player is connected to instead of player's actual version
|
||||
* And TAB is on backend servers - There is no ViaVersion to hook into, therefore, there is no suspicion of player not using server's version and not having a way to verify it
|
||||
* ViaVersion API returns `-1` - There is currently no known cause when or why this happens. However, when this happens, TAB prints a console warn notifying this has happened. Plugin will assume player is using the same version as the server in such cases.
|
||||
* ViaVersion is installed on proxy and TAB is on backend servers - There is no ViaVersion to hook into, therefore, there is no suspicion of player not using server's version and not having a way to verify it.
|
||||
* ViaVersion API returns `-1` - This was an issue in the past and hasn't been reported in years now.
|
||||
|
||||
When a player's game version is not detected correctly, player's experience will be very limited, see below.
|
||||
|
||||
## Differences
|
||||
To ensure the best possible experience, TAB is currently checking for player's version to:
|
||||
* [Layout](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Layout):
|
||||
* Disabled for 1.7 and lower due to massive tablist differences and plugin not being adapted to those
|
||||
* Use alternate method of sorting the entries for 1.19.3+ using entry names, as the previous, better option was removed
|
||||
* Use RGB color codes for 1.16+ and use closest (or forced) legacy color for 1.15-
|
||||
* Use 1.9+ bossbar on 1.8 servers for 1.9+ players using ViaVersion API
|
||||
* Disable [Tablist name formatting](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Tablist-name-formatting) for 1.7 and lower due to a 16-character limit total to take advantage of the mechanic where nametags are displayed in tablist, which extends the limit to up to 48 characters (16 prefix, 16 name, 16 suffix)
|
||||
* Play around <1.13 limitations of [Scoreboard](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Scoreboard)
|
||||
* Cutting nametag prefix/suffix to 16 characters for <1.13, but keeping it full for 1.13+
|
||||
To ensure the best possible experience, TAB is currently checking for player's version to alter its logic in some way.
|
||||
If a player's version is not detected correctly, a side effect might occur instead, making the situation worse.
|
||||
You will not experience all of these, only those that affect the version difference between the server and client.
|
||||
|
||||
When version detection fails, plugin's workarounds will be wrong and cause problems, such as:
|
||||
* Nametag prefix/suffix being cut to 16 characters, even for 1.13+ clients
|
||||
* Scoreboard lines limited to ~26 characters, even for 1.13+ clients
|
||||
* Scoreboard lines limited to 14 characters for < 1.13 clients
|
||||
* Scoreboard lines might be out of order
|
||||
* RGB colors displayed as legacy colors for 1.16+ clients
|
||||
* Layout being out of order for 1.19.3+ clients
|
||||
| Change of logic | Negative side effect if version is not detected correctly |
|
||||
|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| Disabling [Layout](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Layout) for 1.7 players | [Layout](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Layout) entries being sent into the tablist of the player, breaking it as the layout is not designed support 1.7 |
|
||||
| Use alternate way of sorting [Layout](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Layout) entries for 1.19.3 - 1.21.1 as the optimal sorting way cannot be used | [Layout](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Layout) entries being out of order for 1.19.3 - 1.21.1 <br />Layout entries appearing in chat complete for <1.19.3 even if they don't have to |
|
||||
| Show 1.9+ bossbar on 1.8 servers for 1.9+ players using ViaVersion API | No 1.9+ bossbar for 1.9+ players on 1.8 server |
|
||||
| Disabling [Tablist name formatting](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Tablist-name-formatting) for 1.7 and lower due to a 16-character limit total to take advantage of the mechanic where nametags are displayed in tablist, which extends the limit to up to 48 characters (16 prefix, 16 name, 16 suffix) | Trying to fit the entire formatting into 16 characters, resulting in massive cutting and nametag feature not working |
|
||||
| Play around <1.13 limitations of [Scoreboard](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Scoreboard) | Scoreboard lines limited to ~26 characters, even for 1.13+ clients <br />Scoreboard lines limited to 14 characters for < 1.13 clients |
|
||||
| Cutting nametag prefix/suffix to 16 characters for <1.13, but keeping it full for 1.13+ | Nametag prefix/suffix being cut to 16 characters, even for 1.13+ clients |
|
||||
|
||||
# Debug config option
|
||||
You may have noticed `debug: false` in your config.
|
||||
Enabling it will unlock extra console messages beyond the usual misconfiguration / errors.
|
||||
They give deeper information about what's going on in the plugin in case a more complex issue comes up.
|
||||
Currently, it offers the following:
|
||||
* Prints thrown errors into console as well, instead of just errors.log file. They are printed into the console even if the error log reaches the 16MB limit.
|
||||
* Sends a message about how long each feature + total it took to process player join/quit or plugin reload. High values may suggest something is not optimized correctly. Originally added for myself when optimizing, but adding/removing it each time would be too much of a hassle, so it remains as a debug message.
|
||||
* Makes plugin print thrown errors into console as well, instead of just errors.log file. They are printed into the console even if the error log reaches the 16MB limit.
|
||||
* Sends a message saying how long each feature + total it took to process player join/quit or plugin reload. High values may suggest something is not optimized correctly. Originally added for myself when optimizing, but adding/removing it each time would be too much of a hassle, so it remains as a debug message.
|
||||
* Sends a message when plugin detects player name change by a nick plugin. Since lots of things are bound to player names, having players change their name and plugin not detecting it would result in plugin not working correctly them. Therefore, every time a player changes their nickname (or resets it), a message should pop up.
|
||||
* Sends a message whenever another plugin sends a scoreboard (such as a minigame plugin) and TAB hides its own. Then, when that other plugin removes its scoreboard, TAB sends its own back (and sends a console message).
|
||||
* Sends a message regarding bedrock client status of players who join in using Floodgate API. This includes a message if floodgate is not enabled correctly (API instance is null), as well as true/false bedrock status of players.
|
||||
* Shows network id of protocol version of players who join when ViaVersion is installed. It should show the version players are actually using. If not, plugin may not work correctly for those players. This is usually caused by having ViaVersion on proxy and backend.
|
||||
* Sends a message whenever another plugin sends a scoreboard (such as a mini-game plugin) and TAB hides its own. Then, when that other plugin removes its scoreboard, TAB sends its own back (and sends a console message).
|
||||
* Sends a message regarding the bedrock client status of players who join in using Floodgate API. This includes a message if floodgate is not enabled correctly (API instance is null), as well as true/false bedrock status of players.
|
||||
* Shows network id of protocol version of players who join when ViaVersion is installed. It should show the version players are actually using. If not, the plugin may not work correctly for those players. This is usually caused by having ViaVersion on proxy and backend.
|
||||
* Sends a message whenever a placeholder takes more than 50 milliseconds to retrieve value. If it only happens once, it may be a random hiccup and can be ignored. In case of consistent problems, this may cause plugin overload.
|
||||
* On proxy installation, shows how long did Bridge take to answer player join plugin message. This should be below 100ms. If it's continuously significantly higher, it means bridge is overloaded with the most likely cause being an inefficient placeholder.
|
||||
* On proxy installation, shows how long did Bridge take to answer player join plugin message. This should be below 100ms. If it's continuously significantly higher, it suggests something bad is going on, either bridge being overloaded or the server itself.
|
||||
@@ -56,5 +56,5 @@ If you want an animation too complicated to be done manually, you can check out
|
||||
All animations start at the same time, which is time of plugin being (re)loaded.
|
||||
Thanks to this, all animations with the same number of frames and the same change interval will be synchronized.
|
||||
If you want to synchronize multiple animations with a different change interval and different number of frames,
|
||||
you need to make sure `frame count`*`change interval` is same for both animations,
|
||||
you need to make sure `(frame count)*(change interval)` is same for both animations,
|
||||
for example one animation with 10 frames and 1000ms and second animation with 2 frames and 5000ms.
|
||||
@@ -0,0 +1,33 @@
|
||||
# Content
|
||||
* [Detection](#detection)
|
||||
* [Differences](#differences)
|
||||
* [Issues with `.` in name](#issues-with--in-name)
|
||||
|
||||
# Detection
|
||||
For TAB to properly detect player as a bedrock player, floodgate and TAB must be installed on the same server instance.
|
||||
If TAB is installed on bukkit and floodgate is on both bungeecord and bukkit,
|
||||
make sure [they are connected](https://geysermc.org/wiki/floodgate/setup/?platform=proxy-servers).
|
||||
|
||||
To make sure floodgate is configured correctly, you have two options:
|
||||
* Set `debug: true` in TAB config and check console output on join/reload. It should say `Floodgate returned bedrock status TRUE for player <player>`.
|
||||
* Run `/tab parse <player> %bedrock%`. It should return `true`.
|
||||
|
||||
If any of them say `false` for an actual bedrock player, you didn't connect floodgate correctly.
|
||||
|
||||
# Differences
|
||||
It is a job of plugin allowing bedrock clients to connect (probably Geyser) to correctly translate all packets.
|
||||
If you are experiencing an issue on bedrock but not java, it is most likely not an issue in TAB code.
|
||||
|
||||
Currently, TAB does check for bedrock clients for:
|
||||
* Disabling [Layout](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Layout) for bedrock players.
|
||||
|
||||
This is the only known change required on the TAB's side.
|
||||
|
||||
# Issues with `.` in name
|
||||
If you configured Geyser to prefix bedrock player names with `.`, it will mess up config loading,
|
||||
because `.` is used to split a section path.
|
||||
This means that using `/tab player <playername containing .> ...` will not work.
|
||||
There are three solutions available:
|
||||
* Use player UUID if commands are executed via another plugin (`/tab player <uuid> ...`).
|
||||
* Use `playeruuid` type, which accepts player name and saves the online player's UUID (`/tab playeruuid <player name> ...`).
|
||||
* Use a different username prefix than `.`.
|
||||
@@ -49,7 +49,7 @@ Commands have required permissions associated with them.
|
||||
#### Names for each command type
|
||||
* player: Supports both player name and raw uuid. Player doesn't need to be online.
|
||||
* playeruuid: Requires name of an online player. Equal to using `player` with player's uuid.
|
||||
* group: Uses group names from permission plugin. `_DEFAULT_` is used for default settings for all groups.
|
||||
* group: Using group names from permission plugin. `_DEFAULT_` is used for default settings for all groups.
|
||||
|
||||
## /tab \<player/group/playeruuid\> \<name\> remove
|
||||
* **Permission:** `tab.remove`
|
||||
@@ -133,7 +133,7 @@ Commands have required permissions associated with them.
|
||||
|
||||
## /tab parse \<player\> \<text\>
|
||||
* **Permission:** `tab.parse`
|
||||
* **Description:** Replaces all placeholders (both the plugin's internal ones, and those from PlaceholderAPI if it is installed) in the given `text`. It can be used to test if a placeholder works as expected.
|
||||
* **Description:** Replaces all placeholders (both the plugin's internal ones, and those from PlaceholderAPI if it is installed) in the given `text`. It can be used to verify if a placeholder works as expected.
|
||||
|
||||
# Additional permissions
|
||||
* `tab.admin` - Allows the player to execute all commands.
|
||||
|
||||
@@ -32,7 +32,7 @@ It is compiled with Java 8 and therefore supports any version of 8 and above.
|
||||
</tr>
|
||||
<tr>
|
||||
<td rowspan=1><a href="https://www.spongepowered.org/">Sponge</a></td>
|
||||
<td>✔ (1.9<sup>1</sup> - 1.21.3)</td>
|
||||
<td>✔ (1.9<sup>1</sup> - 1.21.4)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tbody>
|
||||
@@ -91,7 +91,9 @@ so you can always safely update to new version/build of your proxy software if t
|
||||
❌ = Completely missing
|
||||
|
||||
# Plugin hooks
|
||||
In order to enhance user experience, TAB hooks into other plugins for better experience. This set of plugins is different for each platform based on their availability. Some are available on all platforms, some only in a few.
|
||||
To enhance user experience, TAB hooks into other plugins for better experience.
|
||||
This set of plugins is different for each platform based on their availability.
|
||||
Some are available on all platforms, some only in a few.
|
||||
## All platforms
|
||||
[**Floodgate**](https://github.com/GeyserMC/Floodgate) - For properly detecting bedrock players to adapt features for the best possible user experience.
|
||||
[**LuckPerms**](https://github.com/LuckPerms/LuckPerms) - Detecting permission groups of players for per-group settings.
|
||||
@@ -100,12 +102,14 @@ In order to enhance user experience, TAB hooks into other plugins for better exp
|
||||
## Bukkit
|
||||
[**LibsDisguises**](https://github.com/libraryaddict/LibsDisguises) - Detecting disguised players to disable collision to avoid endless push by colliding with own copy created by LibsDisguises.
|
||||
[**PlaceholderAPI**](https://github.com/PlaceholderAPI/PlaceholderAPI) - Allows users to use its placeholders inside TAB.
|
||||
[**PremiumVanish**](https://www.spigotmc.org/resources/14404) - Supporting PremiumVanish's vanish levels instead of using a basic vanish compatibility system.
|
||||
[**PremiumVanish**](https://www.spigotmc.org/resources/14404) - Supporting PremiumVanish's vanishing levels instead of using a basic compatibility system.
|
||||
[**Vault**](https://github.com/milkbowl/Vault) - Detecting permission groups of players for per-group settings.
|
||||
|
||||
## BungeeCord
|
||||
[**PremiumVanish**](https://www.spigotmc.org/resources/14404) - Supporting PremiumVanish's 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.
|
||||
[**PremiumVanish**](https://www.spigotmc.org/resources/14404) -
|
||||
Supporting PremiumVanish's vanishing levels instead of using a basic compatibility system.
|
||||
[**RedisBungee**](https://github.com/ProxioDev/RedisBungee) -
|
||||
Communicating with other proxies to properly display visuals on players on another proxy.
|
||||
|
||||
## Fabric
|
||||
[**fabric-permissions-api**](https://github.com/lucko/fabric-permissions-api) - Supporting permission nodes instead of OP levels.
|
||||
@@ -114,16 +118,20 @@ In order to enhance user experience, TAB hooks into other plugins for better exp
|
||||
*None*
|
||||
|
||||
## 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.
|
||||
[**VelocityScoreboardAPI**](https://github.com/NEZNAMY/VelocityScoreboardAPI) - Sending scoreboard packets (scoreboard-teams, belowname-objective, playerlist-objective, scoreboard)
|
||||
[**PremiumVanish**](https://www.spigotmc.org/resources/14404) -
|
||||
Supporting PremiumVanish's vanishing levels instead of using a basic compatibility system.
|
||||
[**RedisBungee**](https://github.com/ProxioDev/RedisBungee) -
|
||||
Communicating with other proxies to properly display visuals on players on another proxy.
|
||||
[**VelocityScoreboardAPI**](https://github.com/NEZNAMY/VelocityScoreboardAPI) -
|
||||
Sending scoreboard packets (scoreboard-teams, belowname-objective, playerlist-objective, scoreboard)
|
||||
|
||||
# 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.
|
||||
* **[Tablisknu](https://forums.skunity.com/resources/tablisknu.727/)** (skript addon) prevents TAB from assigning teams (sorting & nametags).
|
||||
* **SkBee** (skript addon) sends empty scoreboard, causing TAB's to not show sometimes.
|
||||
* **Waterfall**'s `disable_tab_list_rewrite: true` **may** cause tablist to use offline UUIDs while TAB expects online uuids, causing various problems (most notably tablist formatting not working). Checking for this option is not an option either, because tablist rewrite might still be enabled despite being disabled (don't ask how, I have no idea). Set the option to `false` if you are experiencing issues.
|
||||
* **ViaVersion on BungeeCord and TAB on backend** acts like a client-sided protocol hack, making it impossible for TAB to know player's real version and causing issues related to it, see [Per-version experience](https://github.com/NEZNAMY/TAB/wiki/Additional-information#per-version-experience) for more info. Avoid this combination. Either install ViaVersion on all backend servers instead or install TAB on BungeeCord instead.
|
||||
* **ViaVersion on proxy and TAB on backend** acts like a client-sided protocol hack, making it impossible for TAB to know player's real version and causing issues related to it, see [Per-version experience](https://github.com/NEZNAMY/TAB/wiki/Additional-information#per-version-experience) for more info. Avoid this combination. Either install ViaVersion on all backend servers instead or install TAB on the proxy as well.
|
||||
* **ViaVersion**'s `hide-scoreboard-numbers` and **Nexo**'s `hide_scoreboard_numbers` config options (may apply to ItemsAdder and Oraxen as well) make [Belowname](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Belowname)'s `value` and [Playerlist objective](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Playerlist-Objective)'s `value` not visible for 1.20.3+ clients on <1.20.3 server when enabled. When using this setup, you'll need to keep the option disabled (sadly scoreboard numbers will not be hidden, solving this would require an implementation on ViaVersion's side).
|
||||
* **Custom clients / resource packs** - Unofficially modified minecraft clients often tend to break things. Just Lunar client has tons of bugs that can be reproduced with TAB. Resource packs may also contain modifications you are not aware of, making things not look the way you want them to. If you are experiencing any visual issue and are using a custom client or resource pack, try it with a clean vanilla client. If it works there, it's an issue with the client / resource pack and TAB cannot do anything about it.
|
||||
For example, here are a few bugs in LunarClient / FeatherClient that you may run into when using TAB:
|
||||
* They add their icon to players in tablist, but don't widen the entries. This results in player names overlapping with latency bar. You can avoid this by configuring some spaces in tabsuffix.
|
||||
@@ -133,4 +141,4 @@ In order to enhance user experience, TAB hooks into other plugins for better exp
|
||||
* They don't respect nametag visibility rule, showing their own nametag using F5 even if set to invisible by the plugin.
|
||||
* When the scoreboard is set to use all 0s, lines are rendered in opposite order on 1.20.3+.
|
||||
* They don't display 1.20.3+ NumberFormat feature in scoreboard objectives.
|
||||
* **Random Spigot/BungeeCord forks** - All safe patches for improving security & performance are present in well-known public opensource projects, such as Paper, Purpur or Waterfall. Using a random overpriced closed-source (and probably obfuscated) fork from BuiltByBit may not be safe, since they likely include unsafe patches that may break compatibility with plugins in an attempt to fix things that are not broken. Before spending your entire budget on such fork, reconsider it first. Paper (and its forks) is a performance-oriented fork used by 2/3 of all MC servers worldwide, while the rest is still stuck on Spigot. It is highly unlikely your needs are so specific you need every single "improvement" anyone can come up with. If you need a feature, Purpur is a feature-oriented fork. Together with plugins you should achieve what you are looking for.
|
||||
* **Random Spigot/BungeeCord forks** - All safe patches for improving security & performance are present in well-known public opensource projects, such as Paper, Purpur or Waterfall. Using a random overpriced closed-source (and probably obfuscated) fork from BuiltByBit may not be safe, since they likely include unsafe patches that may break compatibility with plugins in an attempt to fix things that are not broken. Before spending your entire budget on such a fork, reconsider it first. Paper (and its forks) is a performance-oriented fork used by 2/3 of all MC servers worldwide, while the rest is still stuck on Spigot. It is highly unlikely your needs are so specific you need every single "improvement" anyone can come up with. If you need a feature, Purpur is a feature-oriented fork. Together with plugins you should achieve what you are looking for.
|
||||
@@ -26,7 +26,7 @@ For Maven users, you can add the API dependency like this:
|
||||
<dependency>
|
||||
<groupId>com.github.NEZNAMY</groupId>
|
||||
<artifactId>TAB-API</artifactId>
|
||||
<version>5.0.3</version>
|
||||
<version>5.0.7</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
```
|
||||
@@ -40,7 +40,7 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly 'com.github.NEZNAMY:TAB-API:5.0.3'
|
||||
compileOnly 'com.github.NEZNAMY:TAB-API:5.0.7'
|
||||
}
|
||||
```
|
||||
|
||||
@@ -51,7 +51,7 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly("com.github.NEZNAMY", "TAB-API", "5.0.3")
|
||||
compileOnly("com.github.NEZNAMY", "TAB-API", "5.0.7")
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -28,14 +28,14 @@ The feature's properties are displayed as `[score]` + space + `title` (no, the s
|
||||
* `fancy-value` for 1.20.3+ (will show `fancy-value-default` on NPCs)
|
||||
|
||||
# Configuration
|
||||
| Option name | Default value | Description |
|
||||
| ------------- | ------------- | ------------- |
|
||||
| enabled | true | Enables / Disables the feature |
|
||||
| value | %health% | [1.20.2-] An integer from -2147483648 to 2147483647, doesn't support decimal values. The number is always white. Supports placeholders with player-specific output, such as player health. Only visible on 1.20.2 and lower. <br/> **Note**: Even if you only support 1.20.3+, you still need to configure this value to properly evaluate to a number, because the value is still sent to the client (just not displayed). You can set it to `0` for simplicity. |
|
||||
| fancy-value | &c%health% | [1.20.3+] Any text, supports placeholders with per-player output. Only visible on 1.20.3+. |
|
||||
| fancy-value-default | NPC | [1.20.3+] Default display for all player entities. Plugin uses the value above for every real player, therefore this default value will only appear on player entities which are not actual players, a.k.a. NPCs. Only visible on 1.20.3+. |
|
||||
| title | Health | Text displayed after `value`. It is formally called scoreboard title, which is displayed on all player entities and value is shared on all players and therefore not bound to specific players, which makes it unable to display the value per-player. It should only contain static text or placeholders which return same value for all players. All inserted placeholders are parsed for the player viewing the text. This is intentional to allow advanced configuration with conditions for per-world values for example. |
|
||||
| disable-condition | %world%=disabledworld | A [condition](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Conditional-placeholders) that must be met for disabling the feature for players. Set to empty for not disabling the feature ever. |
|
||||
| Option name | Default value | Description |
|
||||
|---------------------|-----------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| enabled | true | Enables / Disables the feature |
|
||||
| value | %health% | [1.20.2-] An integer from -2147483648 to 2147483647, doesn't support decimal values. The number is always white. Supports placeholders with player-specific output, such as player health. Only visible on 1.20.2 and lower. <br/> **Note**: Even if you only support 1.20.3+, you still need to configure this value to properly evaluate to a number, because the value is still sent to the client (just not displayed). You can set it to `0` for simplicity. |
|
||||
| fancy-value | &c%health% | [1.20.3+] Any text, supports placeholders with per-player output. Only visible on 1.20.3+. |
|
||||
| fancy-value-default | NPC | [1.20.3+] Default display for all player entities. Plugin uses the value above for every real player, therefore this default value will only appear on player entities which are not actual players, a.k.a. NPCs. Only visible on 1.20.3+. |
|
||||
| title | Health | Text displayed after `value`. It is formally called scoreboard title, which is displayed on all player entities and value is shared on all players and therefore not bound to specific players, which makes it unable to display the value per-player. It should only contain static text or placeholders which return same value for all players. All inserted placeholders are parsed for the player viewing the text. This is intentional to allow advanced configuration with conditions for per-world values for example. |
|
||||
| disable-condition | %world%=disabledworld | A [condition](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Conditional-placeholders) that must be met for disabling the feature for players. Set to empty for not disabling the feature ever. |
|
||||
|
||||
# Additional Info
|
||||
## Additional note 1 - Copying nametag visibility rule
|
||||
@@ -64,12 +64,12 @@ This is how you can achieve it using the following popular NPC plugins:
|
||||
# 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.
|
||||
```
|
||||
belowname-objective:
|
||||
title: "&4❤"
|
||||
```
|
||||
> [!IMPORTANT]
|
||||
> Make sure your config is saved in UTF-8 encoding to properly read the heart symbol.
|
||||
|
||||
## Tip 2 - Displaying health as 0-10 or in %
|
||||
If you want health to display health as 0-10 instead of 0-20, you can achieve it with PlaceholderAPI:
|
||||
@@ -77,10 +77,11 @@ If you want health to display health as 0-10 instead of 0-20, you can achieve it
|
||||
* `%math_0_{player_health}*5%` for 0-100
|
||||
|
||||
# Limitations
|
||||
* The format is a **[score] + space + shared title**, where [score] is either a white number (1.20.2-) or any text (1.20.3+). No, the space cannot be removed.
|
||||
* The title is the same on all players, therefore, it cannot be personalized (such as player's faction). Only the [score] can be per-player.
|
||||
* [1.5 - 1.12.2] Title length is limited to 32 characters (including color codes).
|
||||
* [1.5 - 1.20.2] `value` is limited to a white number.
|
||||
* The format is a **[score] + space + shared title**. No, the space cannot be removed.
|
||||
* The title is the same on all players, therefore, it cannot be personalized (such as player's faction). Only [score] can be per-player.
|
||||
* It appears on all entities of player type. **This includes player NPCs.**
|
||||
* Title length is limited to 32 characters on <1.13
|
||||
|
||||
# Examples
|
||||
## Example 1 - Per-world values
|
||||
@@ -105,7 +106,10 @@ conditions:
|
||||
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!!!**
|
||||
> [!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-value`:
|
||||
```
|
||||
@@ -115,7 +119,11 @@ belowname-objective:
|
||||
If you are under 1.20.3 and want to make `value` and `title` conditional,
|
||||
create two 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.*
|
||||
> [!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 `title` for 1.20.3+ players
|
||||
1.20.3 has replaced `value` field,
|
||||
@@ -139,7 +147,10 @@ conditions:
|
||||
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!!!**
|
||||
> [!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:
|
||||
title: "%condition:version%"
|
||||
|
||||
@@ -61,8 +61,8 @@ A number from 0 to 100. Accepts decimals.
|
||||

|
||||
If you want to show the progress of a placeholder's value relative to a maximum one, you can use the [Math Expansion](https://github.com/PlaceholderAPI/PlaceholderAPI/wiki/Placeholders#math) from PlaceholderAPI and use a placeholder like this one: `%math_{placeholder_current_value}/{placeholder_max_value}*100%` or `%math_{placeholder_current_value}/<max_value>*100%` if your max value is fixed.
|
||||
|
||||
Notes:
|
||||
Replace the text in between the `{}` with the actual placeholder you want to use and `<max_value>` with a valid number
|
||||
> [!NOTE]
|
||||
> Replace the text in between the `{}` with the actual placeholder you want to use and `<max_value>` with a valid number
|
||||
(i.e., 20).
|
||||
You cannot use the `%placeholder%` format inside the math placeholder.
|
||||
|
||||
@@ -107,12 +107,12 @@ You can further use this placeholder in progress, for example,
|
||||
using Math expansion from PlaceholderAPI, such as `%math_{tab_placeholder_countdown}/<total time>*100%`.
|
||||
|
||||
## Additional settings
|
||||
| Option name | Default value | Description |
|
||||
| ------------- | ------------- | ------------- |
|
||||
| enabled | false | Enables / Disables the feature |
|
||||
| toggle-command | /bossbar | A command to toggle bossbar visibility for player running the command. This command requires `tab.bossbar.toggle` permission. <br />**Note:** This command will not appear in command auto-complete, because it's not a real registered command. Registered commands must be defined in plugin jar directly and cannot be dynamic. If you want to solve this, you can try getting a dummy private plugin made which registers that command. |
|
||||
| remember-toggle-choice | false | When enabled, disabling bossbar using `toggle-command` is remembered between reloads and restarts. |
|
||||
| hidden-by-default | false | When enabled, bossbar will automatically be hidden until toggle command is used. |
|
||||
| Option name | Default value | Description |
|
||||
|------------------------|---------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| enabled | false | Enables / Disables the feature |
|
||||
| toggle-command | /bossbar | A command to toggle bossbar visibility for player running the command. This command requires `tab.bossbar.toggle` permission. <br />**Note:** This command will not appear in command auto-complete, because it's not a real registered command. Registered commands must be defined in plugin jar directly and cannot be dynamic. If you want to solve this, you can try getting a dummy private plugin made which registers that command. |
|
||||
| remember-toggle-choice | false | When enabled, disabling bossbar using `toggle-command` is remembered between reloads and restarts. |
|
||||
| hidden-by-default | false | When enabled, bossbar will automatically be hidden until toggle command is used. |
|
||||
|
||||
# Limitations
|
||||
* Does not support newlines. If you want to display more lines, you'll need to create multiple bossbars.
|
||||
@@ -121,11 +121,14 @@ using Math expansion from PlaceholderAPI, such as `%math_{tab_placeholder_countd
|
||||
# Additional info
|
||||
## Additional note 1 - Hiding bar itself
|
||||
If you want to hide the bossbar itself and only show text, this is possible, but not from the plugin's side.
|
||||
You'll need to create a custom resource pack where the bar doesn't show up and only the text does.
|
||||
You'll need to create a custom resource pack where the bar is invisible.
|
||||
Then, you need to force your players to get the resource pack.
|
||||
|
||||
## Additional note 2 - Not hiding on server switch
|
||||
When under a BungeeCord network and having TAB installed on backend server and switching to another server, the bossbar will not hide. This is because BungeeCord makes it look like a world switch to the client. To avoid this, the only way is to install TAB on BungeeCord and disable bossbar on that server.
|
||||
When under a proxy network and having TAB installed on backend server and switching to another server,
|
||||
the bossbar will not hide.
|
||||
This is because BungeeCord makes it look like a world switch to the client.
|
||||
To avoid this, the only way is to install TAB on BungeeCord and disable bossbar on that server.
|
||||
|
||||
# API
|
||||
*To get started with the API, see [Developer API](https://github.com/NEZNAMY/TAB/wiki/Developer-API) page.*
|
||||
|
||||
@@ -25,44 +25,35 @@ They have 2 main uses in the plugin:
|
||||
|
||||
# Condition types
|
||||
## Number comparations
|
||||
`>=`: greater than or equal to
|
||||
Example: `%ping%>=100` will pass if the player's ping is greater than or equal to `100`.
|
||||
|
||||
`>`: greater than
|
||||
Example: `%ping%>100` will pass if the player's ping is greater than `100`.
|
||||
|
||||
`<=`: less than or equal to
|
||||
Example: `%ping%<=100` will pass if the player's ping is less than or equal to `100`.
|
||||
|
||||
`<`: less than
|
||||
Example: `%ping%<100` will pass if the player's ping is less than `100`.
|
||||
| Operation | Description | Example |
|
||||
|-------------------|---------------------|---------|
|
||||
| `>=` | Greater than or equal to | `%ping%>=100` will pass if the player's ping is greater than or equal to `100` |
|
||||
| `>` | Greater than | `%ping%>100` will pass if the player's ping is greater than `100` |
|
||||
| `<=` | Less than or equal to | `%ping%<=100` will pass if the player's ping is less than or equal to `100` |
|
||||
| `<` | Less than | `%ping%<100` will pass if the player's ping is less than `100` |
|
||||
|
||||
## Text operations
|
||||
`=`: equal to (must match exactly)
|
||||
Example: `%world%=world` will pass if player is in world `world`.
|
||||
| Operation | Description | Example |
|
||||
|-------------------|---------------------|---------|
|
||||
| `=` | Equal to | `%world%=world` will pass if player is in world `world` |
|
||||
| `!=` | Not equal to | `%world%!=world` will pass if player is in any world except `world` |
|
||||
| `<-` | Contains (left side for full text, right side text to contain) | `%world%<-lobby-` will pass if player is in any world that contains `lobby-` (such as `lobby-1` etc.) |
|
||||
| `\|-` | Starts with (left side for full text, right side text to start with) | `%world%\|-lobby-` will pass if player is in any world that starts with `lobby-` (such as `lobby-1` etc.) |
|
||||
| `-\|` | Ends with (left side for full text, right side text to end with) | `%world%-\|nether` will pass if player is in any world that ends with `nether` (such as `world_nether` etc.) |
|
||||
|
||||
`!=`: not equal to (opposite of the above)
|
||||
Example: `%world%!=world` will pass if player is in any world except `world`.
|
||||
> [!NOTE]
|
||||
> For `=` and `!=` you can check for empty output of a placeholder using `%my_placeholder%=` and `%my_placeholder%!=`.
|
||||
|
||||
**Note 1**: For `=` and `!=` you can check for empty output of a placeholder using `%my_placeholder%=` and `%my_placeholder%!=`.
|
||||
|
||||
`<-`: contains (left side for full text, right side text to contain)
|
||||
Example: `%world%<-lobby-` will pass if player is in any world that contains `lobby-` (such as `lobby-1` etc.).
|
||||
|
||||
`|-`: starts with (left side for full text, right side text to start with)
|
||||
Example: `%world%|-lobby-` will pass if player is in any world that starts with `lobby-` (such as `lobby-1` etc.).
|
||||
|
||||
`-|`: ends with (left side for full text, right side text to end with)
|
||||
Example: `%world%-|nether` will pass if player is in any world that ends with `nether` (such as `world_nether` etc.).
|
||||
|
||||
**Note 2**: For string operators, the text must match placeholder's output exactly, including color codes.
|
||||
> [!NOTE]
|
||||
> For string operations, the text must match placeholder's output exactly, including color codes.
|
||||
If you are using [Placeholder output replacements](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Placeholder-output-replacements),
|
||||
condition must contain the altered output.
|
||||
To see the exact output of a placeholder including color codes, use `/tab parse <player> <placeholder>`.
|
||||
|
||||
## Permission
|
||||
`permission:<value>`: permission requirement
|
||||
Example: `permission:my.permission` will pass if player has `my.permission` permission.
|
||||
| Operation | Description | Example |
|
||||
|-------------------|---------------------|---------|
|
||||
| `permission:<value>` | Permission requirement | `permission:my.permission` will pass if player has `my.permission` permission |
|
||||
|
||||
# Multiple condition requirements
|
||||
Each condition has a `conditions` parameter, which a list of conditions. If you define more than 1 condition, you must specify the condition type.
|
||||
@@ -166,7 +157,7 @@ Refresh intervals of conditions are not directly configurable.
|
||||
They are based on placeholders used inside (subconditions, yes/no values).
|
||||
Permission checks count as 1000ms.
|
||||
To configure refresh intervals of placeholders,
|
||||
check out the [Optimization guide](https://github.com/NEZNAMY/TAB/wiki/Optimizing-the-plugin#2---placeholder-refresh-intervals).
|
||||
check out the [Optimization guide](https://github.com/NEZNAMY/TAB/wiki/Optimizing-the-plugin#2---all-platforms-placeholder-refresh-intervals).
|
||||
|
||||
# Examples
|
||||
## Example 1 - Chaining conditional placeholders
|
||||
|
||||
@@ -11,15 +11,15 @@ This feature allows you to see players from other servers under a bungeecord net
|
||||
To use this feature, you need [TAB installed on bungeecord](https://github.com/NEZNAMY/TAB/wiki/Installation#bungeecord). This option is not available on bukkit and will not be added.
|
||||
|
||||
# Configuration
|
||||
| Option name | Default value | Description |
|
||||
| ------------- | ------------- | ------------- |
|
||||
| enabled | false | Enables / Disables the feature |
|
||||
| server-groups | *Map* | See [Grouping players only from certain servers](#grouping-players-only-from-certain-servers) |
|
||||
| spy-servers | *List* | See [Seeing all players on the network from some server](#seeing-all-players-on-the-network-from-some-server) |
|
||||
| display-others-as-spectators | false | When enabled, players on different servers will appear as having spectator gamemode in tablist. |
|
||||
| display-vanished-players-as-spectators | true | When enabled, vanished players will show as in spectator gamemode for those, who have permission to see vanished players (tab.seevanished) (others will obviously not see them at all). Vanished players will still see themselves in the gamemode they are actually in, since changing that would cause problems (client would think it's in spectator gamemode while it isn't). |
|
||||
| isolate-unlisted-servers | false | When enabled, servers not listed in any group will not share playerlist with any other server, instead of sharing it with other unlisted servers. |
|
||||
| update-latency | false | When **enabled**, plugin will send ping update to players on other servers, since backend servers only send ping updates of players on the same server. This is displayed as the green bar in tablist, unless altered by a mod / resource pack (?). <br /> <br />When **disabled**, ping of players on other servers will be set to 0 (5 bars). <br /> <br />Disabled by default, because it's not only not very useful, but also has a very high CPU usage to due spamming a large amount of packets (especially on large networks). |
|
||||
| Option name | Default value | Description |
|
||||
|----------------------------------------|---------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| enabled | false | Enables / Disables the feature |
|
||||
| server-groups | *Map* | See [Grouping players only from certain servers](#grouping-players-only-from-certain-servers) |
|
||||
| spy-servers | *List* | See [Seeing all players on the network from some server](#seeing-all-players-on-the-network-from-some-server) |
|
||||
| display-others-as-spectators | false | When enabled, players on different servers will appear as having spectator gamemode in tablist. |
|
||||
| display-vanished-players-as-spectators | true | When enabled, vanished players will show as in spectator gamemode for those, who have permission to see vanished players (tab.seevanished) (others will obviously not see them at all). Vanished players will still see themselves in the gamemode they are actually in, since changing that would cause problems (client would think it's in spectator gamemode while it isn't). |
|
||||
| isolate-unlisted-servers | false | When enabled, servers not listed in any group will not share playerlist with any other server, instead of sharing it with other unlisted servers. |
|
||||
| update-latency | false | When **enabled**, plugin will send ping update to players on other servers, since backend servers only send ping updates of players on the same server. This is displayed as the green bar in tablist, unless altered by a mod / resource pack (?). <br /> <br />When **disabled**, ping of players on other servers will be set to 0 (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,
|
||||
|
||||
@@ -16,9 +16,9 @@ Minecraft feature **introduced in 1.8** showing text above and below playerlist.
|
||||

|
||||
|
||||
# Configuration
|
||||
| Option name | Default value | Description |
|
||||
| ------------- | ------------- | ------------- |
|
||||
| enabled | true | Enables / Disables the feature |
|
||||
| Option name | Default value | Description |
|
||||
|-------------------|-----------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| enabled | true | Enables / Disables the feature |
|
||||
| disable-condition | %world%=disabledworld | A [condition](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Conditional-placeholders) that must be met for disabling the feature for players. Set to empty for not disabling the feature ever. |
|
||||
|
||||
## Global settings
|
||||
@@ -65,7 +65,9 @@ header-footer:
|
||||
- "Header in all worlds starting with lobby-"
|
||||
```
|
||||
|
||||
**Note**: To make per-world work on BungeeCord, install the [TAB-Bridge](https://www.mc-market.org/resources/21641) plugin on your backend servers.
|
||||
> [!NOTE]
|
||||
> To make per-world work on proxy installation,
|
||||
> install the [TAB-Bridge](https://github.com/NEZNAMY/TAB/wiki/TAB-Bridge) plugin on your backend servers.
|
||||
|
||||
## Per-group / per-player
|
||||
**groups.yml**
|
||||
|
||||
@@ -11,9 +11,9 @@
|
||||
* [Additional info](#additional-info)
|
||||
* [Additional note 1 - Playerlist objective incompatibility](#additional-note-1---playerlist-objective-incompatibility)
|
||||
* [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 3 - [1.19.3 - 1.21.1] Entries in chat complete](#additional-note-3---1193---1211-entries-in-chat-complete)
|
||||
* [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)
|
||||
* [Additional note 5 - [1.8 - 1.21.3] Second layer of skin missing](#additional-note-5---18---1213-second-layer-of-skin-missing)
|
||||
* [Examples](#examples)
|
||||
* [Example 1 - Per-server columns](#example-1---per-server-columns)
|
||||
|
||||
@@ -144,13 +144,13 @@ Yellow: 1307755006
|
||||
```
|
||||
|
||||
# Additional settings
|
||||
| Option name | Default value | Description |
|
||||
| ------------- | ------------- | ------------- |
|
||||
| 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: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 |
|
||||
| Option name | Default value | Description |
|
||||
|-------------------------------|----------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| 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: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 |
|
||||
|
||||

|
||||

|
||||
@@ -166,11 +166,12 @@ The way this feature works is by pushing all real players out of the tablist and
|
||||
As a result, enabling [Global playerlist](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Global-playerlist) feature won't make any difference, since real players aren't visible in the tablist.
|
||||
Layout feature is capable of working with all online players connected to the server where TAB is installed, meaning it can show players from all servers when installed on BungeeCord. Because of this, if you use layout, you can disable global playerlist to reduce resource usage.
|
||||
|
||||
## Additional note 3 - Entries in chat complete [1.19.3 - 1.21.1]
|
||||
## Additional note 3 - [1.19.3 - 1.21.1] Entries in chat complete
|
||||
From 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**: To hide entries from chat complete on 1.21.2+,
|
||||
1.21.2 has added a new way of sorting players, which is being taken advantage of to restore empty chat complete.
|
||||
> [!NOTE]
|
||||
> To hide entries from chat complete on 1.21.2+,
|
||||
the server also has to be 1.21.2+ so TAB can send the new content to the players
|
||||
(having TAB installed on proxy counts as using the latest version).
|
||||
|
||||
@@ -232,13 +233,15 @@ You can also replicate "ignore-effect-in-worlds" by creating a layout with condi
|
||||
```
|
||||
</details>
|
||||
|
||||
## Additional note 5 - Second layer of skin missing
|
||||
## Additional note 5 - [1.8 - 1.21.3] 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 to see the 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 the second layers of skins.
|
||||
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.
|
||||
which would require a rework of the feature, which is not planned.
|
||||
**1.21.4 has added a new tablist option called "show hats", which layout sets to true.
|
||||
They will be visible for 1.21.4+ players on 1.21.4+ servers (using a proxy counts as being latest).**
|
||||
|
||||
# Examples
|
||||
## Example 1 - Per-server columns
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
* [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)
|
||||
* [Additional note 6 - Vanish hook](#additional-note-6---vanish-hook)
|
||||
* [API](#api)
|
||||
* [Changing prefix and suffix](#changing-prefix-and-suffix)
|
||||
* [Collision](#collision)
|
||||
@@ -119,14 +120,14 @@ All values fully support [TAB's internal placeholders](https://github.com/NEZNAM
|
||||
The number of placeholders is not limited, and they can be used in combination with static text as well.
|
||||
|
||||
## Additional settings
|
||||
| Option name | Default value | Description |
|
||||
| ------------- | ------------- | ------------- |
|
||||
| enabled | true | Enables / Disables the feature |
|
||||
| enable-collision | true | Controls collision rule. Available values are: <br />- `true` - Collision will be enabled permanently. <br />- `false` - Collision will be disabled permanently. <br /> - *Conditional expression* - Collision will be enabled if player meets the condition (for example `%world%=world` will result in collision only being enabled for players in world `world`). <br /> - *Condition name* - Uses name of a [defined condition](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Conditional-placeholders) that must be met for a player to have collision enabled. |
|
||||
| invisible-nametags | false| When enabled, everyone will have invisible nametag all the time. Option to hide nametags was added in minecraft **1.8**, therefore it will not work on 1.7 and lower. |
|
||||
| anti-override | true | While enabled, prevents other plugins from assigning online players into teams and making TAB not work correctly. They should be configured to not use teams, however many users fail to disable features in other plugins that they don't want, making this option required. Some plugins don't even say they use teams to achieve their goals. Did you know even Paper uses teams? |
|
||||
| can-see-friendly-invisibles | false | Controlling value of the team flag. It allows you to see invisible players in the same team as transparent instead of completely invisible. Since TAB places each player into an individual team, this option will only take effect in combination with plugins that spawn a dummy clone of the player (sit or disguise plugins). |
|
||||
| disable-condition | %world%=disabledworld | A [condition](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Conditional-placeholders) that must be met for disabling the feature for players. Set to empty for not disabling the feature ever. |
|
||||
| Option name | Default value | Description |
|
||||
|-----------------------------|-----------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| enabled | true | Enables / Disables the feature |
|
||||
| enable-collision | true | Controls collision rule. Available values are: <br />- `true` - Collision will be enabled permanently. <br />- `false` - Collision will be disabled permanently. <br /> - *Conditional expression* - Collision will be enabled if player meets the condition (for example `%world%=world` will result in collision only being enabled for players in world `world`). <br /> - *Condition name* - Uses name of a [defined condition](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Conditional-placeholders) that must be met for a player to have collision enabled. |
|
||||
| invisible-nametags | false | When enabled, everyone will have invisible nametag all the time. Option to hide nametags was added in minecraft **1.8**, therefore it will not work on 1.7 and lower. |
|
||||
| anti-override | true | While enabled, prevents other plugins from assigning online players into teams and making TAB not work correctly. They should be configured to not use teams, however many users fail to disable features in other plugins that they don't want, making this option required. Some plugins don't even say they use teams to achieve their goals. Did you know even Paper uses teams? |
|
||||
| can-see-friendly-invisibles | false | Controlling value of the team flag. It allows you to see invisible players in the same team as transparent instead of completely invisible. Since TAB places each player into an individual team, this option will only take effect in combination with plugins that spawn a dummy clone of the player (sit or disguise plugins). |
|
||||
| disable-condition | %world%=disabledworld | A [condition](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Conditional-placeholders) that must be met for disabling the feature for players. Set to empty for not disabling the feature ever. |
|
||||
|
||||
# Tips & Tricks
|
||||
If you want TAB to only take prefixes/suffixes from the permission plugin,
|
||||
@@ -138,9 +139,9 @@ _DEFAULT_:
|
||||
```
|
||||
|
||||
# Limitations
|
||||
* Prefix/suffix length is limited to 16 characters (including color codes) on <1.13. There is no reachable limit on 1.13+.
|
||||
* [1.5 - 1.12.2] Prefix/suffix length is limited to 16 characters (including color codes). Any characters beyond that will be cut to prevent players getting disconnected.
|
||||
* [1.13+] The name can only have one code. That is either color or magic code (such as &4 or &l), but not both.
|
||||
* The name cannot be effectively changed and the plugin doesn't offer it.
|
||||
* Since 1.13, the name can only have one code. That is either color or magic code (such as &4 or &l), but not both.
|
||||
* Name does not support RGB codes. Any used RGB colors will be rounded to the nearest legacy code.
|
||||
* The same value manages name color and glow color, which means they cannot be different.
|
||||
|
||||
@@ -183,6 +184,13 @@ scoreboard-teams:
|
||||
```
|
||||
In config.yml. Keep in mind, you'll not be able to use any team features if you do so.
|
||||
|
||||
# Additional note 6 - Vanish hook
|
||||
When a player is [vanished](https://github.com/NEZNAMY/TAB/wiki/Additional-information#vanish-detection), their team is unregistered for all other players who do **not** have `tab.seevanished` permission.
|
||||
|
||||
The purpose of this is to avoid players figuring out a staff member is still online but just vanished by "checking" registered teams and their members. The easiest way of "exploiting" this is to have a 3rd party client spawn a player entity with name of a chosen player (staff member) and seeing if their name is formatted (= team is registered = player is online) or not (offline).
|
||||
|
||||
For this reason, make sure you give all of your staff members who can see vanished players the `tab.seevanished` permission, otherwise the vanished player will appear on top of the tablist (players without team are above players with team) and their nametag will not be formatted.
|
||||
|
||||
# API
|
||||
*To get started with the API, see [Developer API](https://github.com/NEZNAMY/TAB/wiki/Developer-API) page.*
|
||||
|
||||
@@ -192,7 +200,8 @@ To access this feature, you'll need to obtain `NameTagManager` instance. Get it
|
||||
To set the values for the respective formatting, use the following:
|
||||
* `NameTagManager#setPrefix(TabPlayer, String)`
|
||||
* `NameTagManager#setSuffix(TabPlayer, String)`
|
||||
To reset them, set values to `null`.
|
||||
|
||||
To reset them, set values to `null`.
|
||||
|
||||
To get custom values previously set using the API (they will return `null` if no custom value is set):
|
||||
* `NameTagManager#getCustomPrefix(TabPlayer)`
|
||||
@@ -202,11 +211,15 @@ To get the original value set by the plugin based on configuration:
|
||||
* `NameTagManager#getOriginalPrefix(TabPlayer)`
|
||||
* `NameTagManager#getOriginalSuffix(TabPlayer)`
|
||||
|
||||
**Note**: These values are only temporary, meaning they won't get saved anywhere and will get reset on player quit or plugin reload. If you wish to save these values into file, use [commands](https://github.com/NEZNAMY/TAB/wiki/Commands-&-Permissions#tab-playergroupplayeruuid-name-property-value-options).
|
||||
> [!NOTE]
|
||||
> These values are only temporary,
|
||||
> meaning they won't get saved anywhere and will get reset on player quit or plugin reload.
|
||||
> If you wish to save these values into file,
|
||||
> use [commands](https://github.com/NEZNAMY/TAB/wiki/Commands-&-Permissions#tab-playergroupplayeruuid-name-property-value-options).
|
||||
|
||||
## Collision
|
||||
* `NameTagManager#getCollisionRule(TabPlayer)` - Returns forced collision rule using the API, `null` if no value was forced and player follows the configuration.
|
||||
* `NameTagManager#setCollisionRule(TabPlayer, Boolean)` - Forces collision rule to the player. Use `null` to reset value and make it follow configuration again.
|
||||
* `NameTagManager#getCollisionRule(TabPlayer)` - Returns forced collision rule using the API, `null` if no value was forced and player follows the configuration.
|
||||
|
||||
## Manipulating visibility
|
||||
* `NameTagManager#hideNametag(TabPlayer)` - Hides player's nametag from all players
|
||||
|
||||
@@ -9,12 +9,12 @@ This feature can be configured in **config.yml** under **per-world-playerlist**
|
||||
To use it, you need TAB installed on bukkit server. This feature is not available on BungeeCord.
|
||||
|
||||
# Configuration
|
||||
| Option name | Default value | Description |
|
||||
| ------------- | ------------- | ------------- |
|
||||
| enabled | false | Enables / Disables the feature. |
|
||||
| allow-bypass-permission | false | If enabled, players with `tab.bypass` permission will see everyone in tablist despite this feature enabled. |
|
||||
| ignore-effect-in-worlds | *List* | List of worlds where players will see everyone in tablist, not just players in the same world / world group |
|
||||
| shared-playerlist-world-groups | *Map* | See [Grouping playerlist from multiple worlds](#grouping-playerlist-from-multiple-worlds) |
|
||||
| Option name | Default value | Description |
|
||||
|--------------------------------|---------------|-------------------------------------------------------------------------------------------------------------|
|
||||
| enabled | false | Enables / Disables the feature. |
|
||||
| allow-bypass-permission | false | If enabled, players with `tab.bypass` permission will see everyone in tablist despite this feature enabled. |
|
||||
| ignore-effect-in-worlds | *List* | List of worlds where players will see everyone in tablist, not just players in the same world / world group |
|
||||
| shared-playerlist-world-groups | *Map* | See [Grouping playerlist from multiple worlds](#grouping-playerlist-from-multiple-worlds) |
|
||||
|
||||
## Grouping playerlist from multiple worlds
|
||||
In case you want multiple worlds to share playerlist despite this option enabled, create a group and put world names there. The default example
|
||||
|
||||
@@ -45,8 +45,14 @@ placeholder-output-replacements:
|
||||
|
||||
</details>
|
||||
|
||||
**Note 1:** If you want to replace empty output, use `""` or `''` on the left side.
|
||||
**Note 2:** The text must match exactly, including color codes. To check color codes a placeholder returns use [`/tab parse <player> <placeholder>`](https://github.com/NEZNAMY/TAB/wiki/Commands-&-Permissions#tab-parse-text) and check "Without colors" part.
|
||||
> [!NOTE]
|
||||
> If you want to replace empty output, use `""` or `''` on the left side.
|
||||
|
||||
> [!NOTE]
|
||||
> The text must match exactly, including color codes.
|
||||
> To check the color codes a placeholder returns use
|
||||
> [`/tab parse <player> <placeholder>`](https://github.com/NEZNAMY/TAB/wiki/Commands-&-Permissions#tab-parse-text)
|
||||
> and check "Without colors" part.
|
||||
|
||||
### Pattern 2: Number intervals
|
||||
Output will be replaced if it's numeric and belongs in a configured interval.
|
||||
@@ -62,7 +68,9 @@ placeholder-output-replacements:
|
||||
```
|
||||
</details>
|
||||
|
||||
**Note:** If a number is included in more than 1 interval (just like 5 and 15 from the example above), the one higher is used. In this case, 15 would give `Solid` and 5 would give `Bad`.
|
||||
> [!NOTE]
|
||||
> If a number is included in more than 1 interval (just like 5 and 15 from the example above), the one higher is used.
|
||||
> In this case, 15 would give `Solid` and 5 would give `Bad`.
|
||||
|
||||
Intervals can be defined using both `-` and `~`.
|
||||
If you need support for negative values, use `~` for an interval instead of `-`.
|
||||
@@ -140,7 +148,9 @@ placeholder-output-replacements:
|
||||
```
|
||||
**Don't forget that parse command applies replacements, so when trying to see what placeholder returns, you should disable any previously configured replacements for that placeholder to see the correct raw output**.
|
||||
## Example 2 - Replicating PlaceholderAPI's %player_colored_ping%
|
||||
*Please note that this is just an example to demonstrate the plugin's functionality. It is not supposed to discourage anyone from using PlaceholderAPI or any of its placeholders.*
|
||||
> [!NOTE]
|
||||
> Please note that this is just an example to demonstrate the plugin's functionality.
|
||||
> It is not supposed to discourage anyone from using PlaceholderAPI or any of its placeholders.*
|
||||
|
||||
The placeholder allows you to configure intervals in config.yml. Default values are <50 for good, <100 for medium and 100+ for high. We can achieve this using [Pattern 2: Number intervals](#pattern-2-number-intervals) and [Pattern 3: else](#pattern-3-else). When put together, it looks as following:
|
||||
```
|
||||
@@ -162,7 +172,7 @@ to achieve colored ping.
|
||||
Once configured, replacements are applied as the placeholder output everywhere. It is not directly possible to use original output in one place and replaced in another.
|
||||
|
||||
If you need this, you should consider [conditional placeholders](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Conditional-placeholders) instead.
|
||||
If conditional placeholders would be too complicated to use, there are alternate ways. The main point of the solution is to create different placeholder that eventually calls the same function and returns the same result. This can be done by "wrapping" the placeholder into another one. The easiest way using existing TAB's functions is using animations. Create an animation with a single frame that contains the placeholder and configure replacements for that placeholder instead.
|
||||
If conditional placeholders are too complicated to use, there are alternate ways. The main point of the solution is to create different placeholder that eventually calls the same function and returns the same result. This can be done by "wrapping" the placeholder into another one. The easiest way using existing TAB's functions is using animations. Create an animation with a single frame that contains the placeholder and configure replacements for that placeholder instead.
|
||||
<details>
|
||||
<summary>Example for %ping%</summary>
|
||||
|
||||
|
||||
@@ -18,15 +18,16 @@ Since 1.20.3, it can display any text.
|
||||
This feature can be configured in **config.yml** under **playerlist-objective** section.
|
||||
|
||||
# Configuration
|
||||
| Option name | Default value | Description |
|
||||
| ------------- | ------------- | ------------- |
|
||||
| enabled | true | Enables / Disables the feature |
|
||||
| value | "%ping%" | [1.20.2-] Defines value displayed. You can set this to any placeholder that outputs a number. Setting value to `%health%`, `%player_health%` or `%player_health_rounded%` will result in the display type being changed to hearts instead of number. If you want to display health but as a number and not hearts, set value to `0%health%` for example. Only visible on 1.20.2 and lower. <br/> **Note**: Even if you only support 1.20.3+, you still need to configure this value to properly evaluate to a number, because the value is still sent to the client (just not displayed). You can set it to `0` for simplicity. |
|
||||
| fancy-value | "&7Ping: %ping%"| [1.20.3+] Value to display for 1.20.3+ clients. Supports any text. |
|
||||
| disable-condition | %world%=disabledworld | A [condition](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Conditional-placeholders) that must be met for disabling the feature for players. Set to empty for not disabling the feature ever. |
|
||||
| Option name | Default value | Description |
|
||||
|-------------------|-----------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| enabled | true | Enables / Disables the feature |
|
||||
| value | "%ping%" | [1.20.2-] Defines value displayed. You can set this to any placeholder that outputs a number. Setting value to `%health%`, `%player_health%` or `%player_health_rounded%` will result in the display type being changed to hearts instead of number. If you want to display health but as a number and not hearts, set value to `0%health%` for example. Only visible on 1.20.2 and lower. <br/> **Note**: Even if you only support 1.20.3+, you still need to configure this value to properly evaluate to a number, because the value is still sent to the client (just not displayed). You can set it to `0` for simplicity. |
|
||||
| fancy-value | "&7Ping: %ping%" | [1.20.3+] Value to display for 1.20.3+ clients. Supports any text. |
|
||||
| disable-condition | %world%=disabledworld | A [condition](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Conditional-placeholders) that must be met for disabling the feature for players. Set to empty for not disabling the feature ever. |
|
||||
|
||||
# Limitations
|
||||
At 1.20.2 and lower, only 2 display types are supported - number and hearts. The number's color is yellow and cannot be changed.
|
||||
* [1.5 - 1.20.2] Only 2 display types are supported - yellow number and hearts.
|
||||
* [1.5 - 1.20.2] A yellow 0 will appear on all tablist entries that aren't actual players (such as NPCs and [layout](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Layout) entries) and it cannot be removed.
|
||||
|
||||
# Additional info
|
||||
## Additional note 1 - Spectator gamemode
|
||||
|
||||
@@ -107,21 +107,21 @@ name - name of scoreboard to be displayed
|
||||
time - number of seconds to display the scoreboard for
|
||||
|
||||
# Additional settings
|
||||
| Option name | Default value | Description |
|
||||
| ------------- | ------------- | ------------- |
|
||||
| enabled | false | Enables / Disables the feature |
|
||||
| toggle-command | /sb | Command that can be used to toggle scoreboard for the player running the command. Players need `tab.scoreboard.toggle` permission to use it. <br />**Note:** This command will not appear in command auto-complete, because it's not a real registered command. Registered commands must be defined in plugin jar directly and cannot be dynamic. If you want to solve this, you can try getting a dummy private plugin made which registers that command. |
|
||||
| remember-toggle-choice | false | When enabled, toggling decision is saved into a file to remember it even after reloads/restarts/reconnects |
|
||||
| hidden-by-default | false | If enabled, scoreboard will automatically be hidden on join until toggle command is used to show it. |
|
||||
| use-numbers | false | If enabled, numbers 1-15 will be used in the scoreboard. If disabled, `static-number` is shown everywhere. |
|
||||
| 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. |
|
||||
| Option name | Default value | Description |
|
||||
|----------------------------|---------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| enabled | false | Enables / Disables the feature |
|
||||
| toggle-command | /sb | Command that can be used to toggle scoreboard for the player running the command. Players need `tab.scoreboard.toggle` permission to use it. <br />**Note:** This command will not appear in command auto-complete, because it's not a real registered command. Registered commands must be defined in plugin jar directly and cannot be dynamic. If you want to solve this, you can try getting a dummy private plugin made which registers that command. |
|
||||
| remember-toggle-choice | false | When enabled, toggling decision is saved into a file to remember it even after reloads/restarts/reconnects |
|
||||
| hidden-by-default | false | If enabled, scoreboard will automatically be hidden on join until toggle command is used to show it. |
|
||||
| use-numbers | false | If enabled, numbers 1-15 will be used in the scoreboard. If disabled, `static-number` is shown everywhere. |
|
||||
| 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. |
|
||||
|
||||
# Limitations
|
||||
* The title is limited to 32 characters (including color codes) on <1.13.
|
||||
* Line length is limited to 28 characters (including color codes) on <1.13 (68 on lines with static text / [Longer lines](#longer-lines)).
|
||||
* Only displays up to 15 lines.
|
||||
* The red numbers on the right really cannot be removed from the plugin side (a client modification / resource pack is necessary) at 1.20.2 and lower. They no longer appear on 1.20.3+, and instead any text can be configured (see above).
|
||||
* [1.5 - 1.12.2] The title is limited to 32 characters (including color codes).
|
||||
* [1.5 - 1.12.2] Line length is limited to 28 characters (including color codes) (68 on lines with static text / [Longer lines](#longer-lines)).
|
||||
* [1.5 - 1.20.2] The red numbers on the right really cannot be removed from the plugin side (a client modification / resource pack is necessary).
|
||||
* The client only displays up to 15 lines. If a plugin sends more, only the top 15 scores will be displayed. Changing this would require a client modification.
|
||||
|
||||
# Longer lines
|
||||
To make sure the scoreboard never flickers, it's only using prefix/suffix components to display text.
|
||||
@@ -246,7 +246,11 @@ If none of the conditions are met, display the default scoreboard.
|
||||
lines:
|
||||
- 'You are not in any of the worlds listed above'
|
||||
```
|
||||
*Note: This is just an example, the plugin is not limited to displaying scoreboard only per world. If you want per server scoreboards 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.*
|
||||
> [!NOTE]
|
||||
> This is just an example, the plugin is not limited to displaying scoreboard only per world.
|
||||
> If you want per server scoreboards on proxy, 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 - Periodical scoreboard switching
|
||||
We can use [animations](https://github.com/NEZNAMY/TAB/wiki/Animations) to make the plugin switch between scoreboards using display condition. Let's say we want to switch between 2 scoreboards. First, we create an animation:
|
||||
|
||||
@@ -21,18 +21,20 @@
|
||||
# Enabling
|
||||
To enable sorting, you must have either [Nametags](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Nametags) or [Layout](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Layout) (or both) enabled.
|
||||
|
||||
To **verify** you enabled sorting, run `/tab debug`. It will say `Sorting type:` followed by anything except `DISABLED`.
|
||||
> [!TIP]
|
||||
> To verify you enabled sorting, run `/tab debug`. It will say `Sorting type:` followed by anything except `DISABLED`.
|
||||
|
||||
# Methods of sorting
|
||||
Below are all the different methods that you can use to sort players in TAB.
|
||||
Below are all the different methods that you can use to sort players.
|
||||
|
||||
## GROUPS
|
||||
This is the default and recommended method. Players will be sorted by their primary permission group, according to the configured group list.
|
||||
|
||||
First, put your players into groups in your permission plugin.
|
||||
All the ways to do it can be found
|
||||
at [How to assign players into groups](https://github.com/NEZNAMY/TAB/wiki/How-to-assign-players-into-groups).
|
||||
**Verify** player's group using `/tab debug <player>`.
|
||||
at [How to assign players into groups](https://github.com/NEZNAMY/TAB/wiki/How-to-assign-players-into-groups).
|
||||
> [!TIP]
|
||||
> Verify player's group using `/tab debug <player>`.
|
||||
It should say `Primary permission group:` followed by group you configured.
|
||||
If not, you did not assign players into groups correctly.
|
||||
|
||||
@@ -42,7 +44,8 @@ scoreboard-teams:
|
||||
sorting-types:
|
||||
- "GROUPS:owner,admin,mod,default"
|
||||
```
|
||||
**Verify** configured sorting priority of players using `/tab debug <player>`.
|
||||
> [!TIP]
|
||||
> Verify configured sorting priority of players using `/tab debug <player>`.
|
||||
|
||||
It should show a message like this:
|
||||

|
||||
@@ -52,8 +55,12 @@ If a group is not in the list, you will get the following:
|
||||
If that's the case, you forgot to add that group into the list.
|
||||
|
||||
## PERMISSIONS
|
||||
**This method is not recommended, as many low quality servers give OP to every staff member and don't know how to negate permissions, making this option not function as intended. If you think you can pull it off, and sorting by primary group is not an option, you can give this a try.
|
||||
This method is, however, handy when trying to sort specific players **without** assigning them into a group.**
|
||||
> [!CAUTION]
|
||||
> This method is not recommended,
|
||||
> as many low quality servers give OP to every staff member and don't know how to negate permissions,
|
||||
> making this option not function as intended.
|
||||
> If you think you can pull it off, and sorting by primary group is not an option, you can give this a try.
|
||||
This method is, however, handy when trying to sort specific players without assigning them into a group.
|
||||
|
||||
This method will sort players based on permission nodes they have.
|
||||
Place all of your permissions in to a comma separated list **in order of priority**.
|
||||
@@ -64,6 +71,8 @@ scoreboard-teams:
|
||||
sorting-types:
|
||||
- "PERMISSIONS:my.permission.1,my.permission.2,my.permission.3"
|
||||
```
|
||||
> [!TIP]
|
||||
> Verify sorting permissions of a player using `/tab debug <player>`
|
||||
|
||||
## PLACEHOLDER
|
||||
This method sorts players using the output of a placeholder and comparing that to pre-defined values.
|
||||
@@ -164,9 +173,9 @@ you will need to use outputs respectively when defining sorting as well.
|
||||
Although you can theoretically use as many sorting types as you want, there are still strict limits set by mojang. See more about these limits and how to avoid them as much as possible at [Additional note 1 - Limitations](#additional-note-1---limitations).
|
||||
|
||||
# Additional settings
|
||||
| Option name | Default value | Description |
|
||||
| ------------- | ------------- | ------------- |
|
||||
| case-sensitive-sorting | true | When enabled, players will be sorted as A-Z a-z. When disabled, Aa-Zz. |
|
||||
| Option name | Default value | Description |
|
||||
|------------------------|---------------|------------------------------------------------------------------------|
|
||||
| case-sensitive-sorting | true | When enabled, players will be sorted as A-Z a-z. When disabled, Aa-Zz. |
|
||||
|
||||
# Additional info
|
||||
## Additional note 1 - Limitations
|
||||
|
||||
@@ -18,9 +18,9 @@ prevent-spectator-effect:
|
||||
```
|
||||
|
||||
# Permissions
|
||||
| Permission | Description |
|
||||
| ------------- | ------------- |
|
||||
| `tab.spectatorbypass` | Players with this permission will see spectators the usual way despite this feature being enabled. If you don't want players (or even staff members) to bypass this feature, don't give them that permission. |
|
||||
| Permission | Description |
|
||||
|-----------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `tab.spectatorbypass` | Players with this permission will see spectators the usual way despite this feature being enabled. If you don't want players (or even staff members) to bypass this feature, don't give them that permission. |
|
||||
|
||||
# Additional info
|
||||
## Additional note 1 - Seeing yourself still as spectator
|
||||
|
||||
@@ -123,11 +123,11 @@ All values fully support [TAB's internal placeholders](https://github.com/NEZNAM
|
||||
The number of placeholders is not limited, and they can be used in combination with static text as well.
|
||||
|
||||
## Additional settings
|
||||
| Option name | Default value | Description |
|
||||
| ------------- | ------------- | ------------- |
|
||||
| enabled | true | Enables / Disables the feature |
|
||||
| anti-override | true | When enabled, prevents other plugins from overriding TAB. This results in slightly higher CPU usage, but will make sure the function works as expected. <br />Unlike with teams, attempts to override TAB are not logged into anti-override.log file. This is due to high amount of false positives from TAB's own features, as well as bungeecord server switch, higher CPU usage and more. Whether you need this or not depends on how properly your plugins are configured. Some of them attempt to format tablist even if you'd not expect it from such plugin. Keep this enabled to stay safe. |
|
||||
| disable-condition | %world%=disabledworld | A [condition](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Conditional-placeholders) that must be met for disabling the feature for players. Set to empty for not disabling the feature ever. |
|
||||
| Option name | Default value | Description |
|
||||
|-------------------|-----------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| enabled | true | Enables / Disables the feature |
|
||||
| anti-override | true | When enabled, prevents other plugins from overriding TAB. This results in slightly higher CPU usage, but will make sure the function works as expected. <br />Unlike with teams, attempts to override TAB are not logged into anti-override.log file. This is due to high amount of false positives from TAB's own features, as well as bungeecord server switch, higher CPU usage and more. Whether you need this or not depends on how properly your plugins are configured. Some of them attempt to format tablist even if you'd not expect it from such plugin. Keep this enabled to stay safe. |
|
||||
| disable-condition | %world%=disabledworld | A [condition](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Conditional-placeholders) that must be met for disabling the feature for players. Set to empty for not disabling the feature ever. |
|
||||
|
||||
# Tips & Tricks
|
||||
If you want TAB to only take prefixes/suffixes from your permission plugin,
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
# About
|
||||
A collection of things people have requested that I might look into implementing when I get extremely bored sometime next decade.
|
||||
* Less than 4 columns in Layout
|
||||
* `invisible-nametags` supporting conditions like `enable-collision` does
|
||||
* Using ViaVersion API to send 1.20.3+ scoreboard content on <1.20.3 server.
|
||||
* Relational placeholder support in sorting
|
||||
* Conditional header/footer
|
||||
* Relational conditions for layout player groups
|
||||
* Improve bossbar announce placeholder(s)
|
||||
@@ -75,7 +75,7 @@ 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
|
||||
* Trying to use [TAB's internal bukkit-only placeholders](https://github.com/NEZNAMY/TAB/wiki/Placeholders#bukkit-only) on BungeeCord
|
||||
* Trying to use [PlaceholderAPI placeholders on BungeeCord](https://github.com/NEZNAMY/TAB/wiki/How-to-set-up-PlaceholderAPI-support-on-bungeecord) without installing [bridge plugin](https://www.mc-market.org/resources/20631/)
|
||||
* Trying to use [PlaceholderAPI placeholders on BungeeCord](https://github.com/NEZNAMY/TAB/wiki/How-to-set-up-PlaceholderAPI-support-on-bungeecord) without installing [bridge plugin](https://github.com/NEZNAMY/TAB/wiki/TAB-Bridge)
|
||||
|
||||
The full list of reasons can be found on the [Placeholders](https://github.com/NEZNAMY/TAB/wiki/Placeholders#placeholder-is-not-working) page.
|
||||
|
||||
@@ -112,15 +112,11 @@ When using the symbol in configuration using \u format, remember to use `""` in
|
||||
## #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.
|
||||
* It **is not** included in Spigot, any server software for 1.16.4 and lower, BungeeCord (and any of its forks), Sponge 7 and Fabric.
|
||||
* You may not use any legacy color codes (&) in the text where you want MiniMessage support. If you use them, MiniMessage parser will throw an error. Therefore, if trying to use both legacy colors and MiniMessage syntax, MiniMessage syntax will not work.
|
||||
* A plugin/mod might be shading an outdated/incomplete version of Adventure without relocating it, and java class loader might make TAB use the wrong source of the duplicated library.
|
||||
MiniMessage library must be included in your server software.
|
||||
TAB does not include this library.
|
||||
* It **is** included in **Paper 1.18.2** and higher, **Velocity** and **Sponge 8**.
|
||||
* It **is not** included in **Spigot**, any server software for 1.18.1 and lower, **BungeeCord**, **Sponge 7** and **Fabric**.
|
||||
|
||||
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.
|
||||
|
||||
## #17 - Is it possible to show the number of players in a specific group, similar to staffonline placeholder?
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# About the wiki
|
||||
The wiki explains the functionality of the plugin as of version **5.0.3**.
|
||||
The wiki explains the functionality of the plugin as of version **5.0.7**.
|
||||
If your config looks different or is missing some features, you are using an old version of the plugin.
|
||||
To get the latest version, check [releases](https://github.com/NEZNAMY/TAB/releases/).
|
||||
Wiki for older plugin versions is not available.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* [About](#about)
|
||||
* [Option 1: Primary group from permission plugin](#option-1-primary-group-from-permission-plugin)
|
||||
* [Option 2: Permission nodes](#option-2-permission-nodes)
|
||||
* [Taking groups from backend servers when TAB is on BungeeCord](#taking-groups-from-backend-servers-when-tab-is-on-bungeecord)
|
||||
* [Taking groups from backend servers when TAB is on proxy](#taking-groups-from-backend-servers-when-tab-is-on-proxy)
|
||||
|
||||
## About
|
||||
TAB uses groups to assign properties (such as tabprefix) and sort players in tablist (if sorting by groups). You have 2 ways of configuring primary groups of players in TAB.
|
||||
@@ -12,13 +12,17 @@ First option hooks into your permission plugin and takes groups from it. Therefo
|
||||
If your permission plugin is not supported, you can use [Option 2](#option-2-permission-nodes).
|
||||
|
||||
## Option 1: Primary group from permission plugin
|
||||
This is the default method and recommended. TAB will ask your permission plugin for player's primary group.
|
||||
To use it, keep `assign-groups-by-permissions: false` in your config.
|
||||
This is the default method and recommended.
|
||||
TAB will ask your permission plugin for player's primary group.
|
||||
There are 2 simple steps to this:
|
||||
* Keep `assign-groups-by-permissions: false` in your config
|
||||
* Put players into groups in your permission plugin
|
||||
> [!CAUTION]
|
||||
> If assigning players into multiple groups, make sure their weights are correct
|
||||
> (for luckperms /lp group \<group\> setweight \<number\>).
|
||||
|
||||
Put players into groups in your permission plugin
|
||||
(remember to configure weights - for luckperms /lp group \<group\> setweight \<number\>).
|
||||
|
||||
**Verify** group using `/tab debug <player>`. It should say `Primary permission group: <your group>`.
|
||||
> [!TIP]
|
||||
> Verify player's group using `/tab debug <player>`. It should say `Primary permission group: <your group>`.
|
||||
|
||||
## Option 2: Permission nodes
|
||||
Instead of taking whole groups, TAB will check for permission nodes and assign groups based on the highest permission.
|
||||
@@ -31,9 +35,14 @@ Instead of taking whole groups, TAB will check for permission nodes and assign g
|
||||
#2 - if a player has permission for more than one group, the list will be used to pick the correct one
|
||||
(the one higher in the list).
|
||||
|
||||
**Remember that OP = all permissions!** That's the whole point of OP function. To grant all permissions. In that case, you need to negate permissions in your permission plugin for other groups which are higher than player's wanted group (depending on your permission plugin it can be achieved using `-tab.group.groupname` or setting the permission value to false).
|
||||
> [!WARNING]
|
||||
> Remember that OP = all permissions!
|
||||
> That's the whole point of OP function.
|
||||
> To grant all permissions.
|
||||
> In that case,
|
||||
> you need to negate permissions in your permission plugin for other groups which are higher than player's wanted group.
|
||||
|
||||
# Taking groups from backend servers when TAB is on BungeeCord
|
||||
# Taking groups from backend servers when TAB is on proxy
|
||||
By default, TAB takes groups from the permission plugin installed on the same server instance.
|
||||
In the case of proxy installation, groups are by default taken from the proxy.
|
||||
To see how is your proxy permission plugin configured, use its respective command
|
||||
|
||||
@@ -22,7 +22,10 @@ or using in-game command `/tab group _DEFAULT_ customtabname %essentials_nicknam
|
||||
|
||||
This example uses `%essentials_nickname%` placeholder from Essentials. Giving it to `_DEFAULT_` group keyword makes it a default value for all groups that do not override this property.
|
||||
|
||||
**WARNING: Do NOT put nickname placeholder into tabprefix! You will end up with double names!**
|
||||
> [!CAUTION]
|
||||
> Do NOT put nickname placeholder into tabprefix!
|
||||
> You will end up with double names!
|
||||
> Put the nickname placeholder into customtabname instead.
|
||||
|
||||
# Nametag
|
||||
Nametags are more complicated because they consist of player's username, team prefix and team suffix.
|
||||
|
||||
@@ -11,11 +11,10 @@ Glow color is managed by scoreboard teams, which also handle nametag formatting
|
||||
|
||||
## Solution
|
||||
The solution to get around this issue is to use the glow plugin's placeholder (if it has any).
|
||||
This placeholder has to be at the end of the player's `tagprefix` as it has to be the last color in the prefix.
|
||||
**NOTE:
|
||||
For Minecraft version 1.9 -
|
||||
1.12 the prefix can't be longer than 14 characters due to [16-character limit](https://github.com/NEZNAMY/TAB/wiki/Limitations#name-tags) total
|
||||
(glow placeholder adds 2 characters - `&` and the color code)**
|
||||
This placeholder has to be at the end of the player's `tagprefix` as it has to be the last color in the prefix.
|
||||
> [!WARNING]
|
||||
> For Minecraft versions below 1.13 the prefix can't be longer than 14 characters due to [16-character limit](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Nametags#limitations) total
|
||||
(glow placeholder adds 2 characters - `&` and the color code)
|
||||
|
||||
**Example:**
|
||||
This example uses the placeholder of the [eGlow](https://www.spigotmc.org/resources/63295/) plugin.
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
* [Magic codes](#magic-codes)
|
||||
* [Invalid code handling](#invalid-code-handling)
|
||||
* [Compatibility with <1.16](#compatibility-with-116)
|
||||
* [Manually defining legacy color](#manually-defining-legacy-color)
|
||||
* [Fonts](#fonts)
|
||||
* [Usage](#usage-1)
|
||||
* [Compatibility with RGB and gradients](#compatibility-with-rgb-and-gradients)
|
||||
@@ -29,8 +28,8 @@ Gradients:
|
||||
|
||||
TAB uses the first by default, but is able to accept the other types as well without leaving any characters (such as `{}`) behind.
|
||||
**Gradients only support 2 colors, using more will result in an invalid syntax and won't work.**
|
||||
**Remember to replace `RRGGBB` with actual hexadecimal numbers, for example `FFAA00`** (yes, some people straight up copy "RRGGBB" and ask why it is not working):
|
||||
This color code has 256 red, 170 green and 0 blue. It is equal to the legacy color `6` (gold/orange).
|
||||
> [!CAUTION]
|
||||
> Remember to replace `RRGGBB` with actual hexadecimal numbers (0-9, a-f)!
|
||||
|
||||
You can use a tool like [this one](https://htmlcolorcodes.com/) to easily find suitable colors.
|
||||
|
||||
@@ -43,31 +42,15 @@ For gradients, put the magic codes at the beginning of text. For example `<#00FF
|
||||
In case you accidentally use invalid hex color code, the plugin will simply ignore it and keep it in raw format.
|
||||
|
||||
# Compatibility with <1.16
|
||||
Since <1.16 clients do not understand these color codes, something else must be displayed instead. The plugin will find the closest legacy color to the desired RGB combination and will use that color instead.
|
||||
|
||||
## Manually defining legacy color
|
||||
If you want to specify a legacy color to use instead of plugin finding the closest one, you can define it by adding `|L` after RGB code, where `L` is a legacy color to use (0-9 a-f).
|
||||
Usage for classic codes:
|
||||
* `#RRGGBB|L`
|
||||
* `&#RRGGBB|L`
|
||||
* `{#RRGGBB|L}`
|
||||
* `#<RRGGBB|L>`
|
||||
* `<#RRGGBB|L>`
|
||||
* `&x&R&R&G&G&B&B|L`
|
||||
|
||||
For gradients,
|
||||
defining color will result in the whole text
|
||||
having the same color instead of each character converting to the closest colors,
|
||||
resulting in color groups.
|
||||
To define it, put `|L` after the beginning color of gradient.
|
||||
Usage:
|
||||
* `<#RRGGBB|L>Text</#RRGGBB>`
|
||||
* `{#RRGGBB|L>}Text{#RRGGBB<}`
|
||||
* `<$#RRGGBB|L>Text<$#RRGGBB>`
|
||||
Since <1.16 clients do not understand these color codes, something else must be displayed instead.
|
||||
TAB, server software or viaversion (depending on your setup)
|
||||
will find the closest legacy color to the desired RGB combination and will use that color instead.
|
||||
|
||||
# Fonts
|
||||
The plugin offers usage of the font feature introduced in Minecraft 1.16.
|
||||
**Warning!** This does NOT refer to different character sets! This refers to different fonts defined in resource packs or MC itself.
|
||||
The plugin offers usage of the font feature introduced in Minecraft 1.16.
|
||||
> [!IMPORTANT]
|
||||
> This does NOT refer to different character sets!
|
||||
> This refers to different fonts defined in resource packs or MC itself.
|
||||
|
||||
## Usage
|
||||
The syntax is `<font:FONT_NAME>Text</font>` where `FONT_NAME` is name of your desired font.
|
||||
|
||||
@@ -16,7 +16,7 @@ However, plugins hooking into TAB may break when you use such ways to (re)load i
|
||||
When running a network, you have 3 options:
|
||||
* Installing TAB only on the proxy (recommended)
|
||||
* Installing TAB on all backend servers instead
|
||||
* Installing TAB everywhere and disabling features so they don't conflict (not recommended)
|
||||
* Installing TAB everywhere and disabling features, so they don't conflict (not recommended)
|
||||
|
||||
See below for more information regarding each way.
|
||||
|
||||
@@ -24,11 +24,13 @@ See below for more information regarding each way.
|
||||
This is the recommended setup.
|
||||
Put the plugin into the plugins folder of the proxy server and restart it.
|
||||
**Recommended**:
|
||||
Install [TAB-Bridge](https://github.com/NEZNAMY/TAB/wiki/TAB-Bridge) on your backend servers for PlaceholderAPI support and more.
|
||||
Install [TAB-Bridge](https://github.com/NEZNAMY/TAB/wiki/TAB-Bridge) on your **backend servers** for PlaceholderAPI support and more.
|
||||
**Recommended**:
|
||||
If you are using **Velocity**, install [VelocityScoreboardAPI](https://github.com/NEZNAMY/VelocityScoreboardAPI/) plugin for scoreboard-related features to work.
|
||||
If you are using **Velocity**,
|
||||
install [VelocityScoreboardAPI](https://github.com/NEZNAMY/VelocityScoreboardAPI/) plugin **on the proxy** for scoreboard-related features to work.
|
||||
|
||||
**Note**: With proxy installation use **/btab** command instead of **/tab**.
|
||||
> [!NOTE]
|
||||
> With proxy installation, use **/btab** command instead of **/tab**.
|
||||
|
||||
Advantages of proxy installation compared to installing on all backend servers instead:
|
||||
* Can use the [Global player list](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Global-playerlist) and [Layout](https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Layout) can work with all players connected to the proxy
|
||||
|
||||
@@ -4,7 +4,7 @@ This is collection if minecraft issues/bugs you may experience when using TAB, w
|
||||
# 1.5.x - 1.12.x - Unwanted space
|
||||
You may see unwanted space at the end of a line of text even if you didn't configure any.
|
||||
This is a client sided bug.
|
||||
To avoid it, add &r after ending bold segment (for example, prefix &4&lAdmin&r&6).
|
||||
To avoid it, add `&r` after ending bold segment (for example, prefix `&4&lAdmin&r&6`).
|
||||
|
||||
# 1.15.x - Strikethrough and underline
|
||||
Using &m (strikethrough) and &n (underline) does not work in header,
|
||||
@@ -12,8 +12,4 @@ last line of the scoreboard and possibly other places
|
||||
([MC-180110](https://bugs.mojang.com/browse/MC-180110)).
|
||||
|
||||
# 1.20 - 1.20.4 - Chat over tablist
|
||||
Visual bug rendering chat over the tablist ([MC-263256](https://bugs.mojang.com/browse/MC-263256)).
|
||||
|
||||
# 1.20.3+ - Text not bold
|
||||
Since 1.20.3 text configured to be bold (using &l) may not appear bold. Steps to reproduce are currently unknown, if you have any information that may help, let us know.
|
||||
Clue #1 - It might be a BungeeCord bug breaking bold text. It may or may not have been fixed.
|
||||
Visual bug rendering chat over the tablist ([MC-263256](https://bugs.mojang.com/browse/MC-263256)).
|
||||
@@ -102,11 +102,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_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_tabprefix_raw% | Raw tabprefix containing raw placeholders |
|
||||
| %tab_tabsuffix_raw% | Raw tabsuffix containing raw placeholders |
|
||||
| %tab_tagprefix_raw% | Raw tagprefix containing raw placeholders |
|
||||
| %tab_tagsuffix_raw% | Raw tagsuffix containing raw placeholders |
|
||||
| %tab_customtabname_raw% | Raw customtabname containing raw 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 |
|
||||
@@ -156,19 +156,28 @@ Configured refresh intervals and CPU usage of placeholders can be checked using
|
||||
# Placeholder is not working
|
||||
When using a placeholder, you should know if it's a [TAB's internal placeholder](#internal-placeholders) or a [PlaceholderAPI placeholder](https://github.com/PlaceholderAPI/PlaceholderAPI/wiki/Placeholders). If you don't, that's the problem.
|
||||
|
||||
**Note:** Remember that using `%` symbol will mess up placeholder starts and ends, breaking all placeholders after it.
|
||||
To make the symbol display correctly without messing up placeholders, use `%%` to display the symbol.
|
||||
**Example:** `Sale 100% OFF! %some_placeholder%` -> `Sale 100%% OFF! %some_placeholder%`.
|
||||
> [!CAUTION]
|
||||
> Remember that using `%` symbol will mess up placeholder starts and ends, breaking all placeholders after it.
|
||||
> To make the symbol display correctly without messing up placeholders, use `%%` to display the symbol.
|
||||
> **Example:** `Sale 100% OFF! %some_placeholder%` -> `Sale 100%% OFF! %some_placeholder%`.
|
||||
|
||||
## Internal placeholder is not working
|
||||
All TAB's internal placeholders are listed above. Some of them are bukkit only (meaning they won't work on bungeecord) and some are bungee only. **Trying to use a bukkit-only placeholder on bungeecord will not work.**
|
||||
If you want to use those, [set up PlaceholderAPI support on bungeecord](https://github.com/NEZNAMY/TAB/wiki/TAB-Bridge) and find their PlaceholderAPI equivalent. A large portion of those are included on [PlaceholderAPI's wiki](https://github.com/PlaceholderAPI/PlaceholderAPI/wiki/Placeholders).
|
||||
For example, you can use %vault_prefix% instead of %vault-prefix%, %player_health% instead of %health% and so on.
|
||||
All TAB's internal placeholders are listed above.
|
||||
Some of them are bukkit only (meaning they won't work on bungeecord) and some are bungee only.
|
||||
> [!IMPORTANT]
|
||||
> Trying to use a bukkit-only placeholder on bungeecord will not work.
|
||||
> If you want to use those,
|
||||
> [set up PlaceholderAPI support on bungeecord](https://github.com/NEZNAMY/TAB/wiki/TAB-Bridge)
|
||||
> and find their PlaceholderAPI equivalent.
|
||||
> For example, you can use %vault_prefix% instead of %vault-prefix%,
|
||||
> %player_health_rounded% instead of %health% and so on.
|
||||
|
||||
## PlaceholderAPI placeholder is not working
|
||||
If you have TAB installed on bungeecord, make sure you [set up PlaceholderAPI support on bungeecord](https://github.com/NEZNAMY/TAB/wiki/TAB-Bridge) as there is no other way to retrieve data from technically a different server.
|
||||
|
||||
If you have TAB on bukkit or the previous step did not work, make sure it works when using `/papi parse me <placeholder>`. If it does not work, the issue is not on TAB's end.
|
||||
> [!TIP]
|
||||
> Use `/papi parse me <placeholder>` to check if the placeholder is working.
|
||||
> If it does not work, the issue is not on TAB's end.
|
||||
|
||||
# API
|
||||
*To get started with the API, see [Developer API](https://github.com/NEZNAMY/TAB/wiki/Developer-API) page.*
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# About
|
||||
When TAB is installed on BungeeCord, the plugin has very limited access to information. It is unable to retrieve internal server data, as well as hook into backend plugins, because it's a different server.
|
||||
Bridge is a backend plugin that sends this data to the proxy, allowing TAB to work better when installed on BungeeCord.
|
||||
You can download it from [GitHub](https://github.com/NEZNAMY/TAB-Bridge/releases) or [Spigot](https://www.spigotmc.org/resources/83966).
|
||||
You can download it from [GitHub](https://github.com/NEZNAMY/TAB-Bridge/releases) or [Spigot](https://www.spigotmc.org/resources/83966).
|
||||
|
||||
# Installation
|
||||
Installation is as simple as it can be.
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
# ---------------------------------------
|
||||
# THIS PAGE IS WORK IN PROGRESS
|
||||
# THIS PAGE IS A WORK IN PROGRESS
|
||||
# ---------------------------------------
|
||||
|
||||
# About
|
||||
This page compares TAB to other plugins offering similar features based on various parameters.
|
||||
|
||||
### Open issues
|
||||
Amount of open issues on GitHub. Keep in mind this number shouldn't be taken 100% seriously, because:
|
||||
Number of open issues on GitHub. Keep in mind this number shouldn't be taken 100% seriously, because:
|
||||
* People often open issues to ask for help instead of reporting an issue.
|
||||
* If nobody is using a plugin, nobody can find bugs. Bug not found doesn't mean it's not there.
|
||||
* Some of the issues may be feature requests, which may not be present elsewhere as those are not accepted.
|
||||
* Some issues may be feature requests, which may not be present elsewhere as those are not accepted.
|
||||
|
||||
### Price
|
||||
The price to obtain the jar. Most plugins are free, some are paid.
|
||||
The price to get the jar. Most plugins are free, some are paid.
|
||||
|
||||
### Minecraft
|
||||
Supported Minecraft versions by the plugin.
|
||||
@@ -30,118 +30,118 @@ Supported platforms by the plugin. Full tracked list includes:
|
||||
Whether the plugin runs in server's main thread (sync) or in a different thread (async). If a plugin is running sync, it may slow down the server's TPS. All of these features can be done async.
|
||||
|
||||
# Belowname
|
||||
| Attribute | [TAB](https://github.com/NEZNAMY/TAB) |
|
||||
| ------------- | ------------- |
|
||||
| **Open issues** |  |
|
||||
| **Price** |  |
|
||||
| **Minecraft** | 1.5+ |
|
||||
| **Platforms** |      |
|
||||
| **Async** | ✔ |
|
||||
| PlaceholderAPI support | ✔ |
|
||||
| Configurable number | ✔ |
|
||||
| Configurable text | ✔ |
|
||||
| 1.20.3+ NumberFormat support | ✔ |
|
||||
| Conditionally disable the feature | If any specified condition is met |
|
||||
| Attribute | [TAB](https://github.com/NEZNAMY/TAB) |
|
||||
|-----------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| **Open issues** |  |
|
||||
| **Price** |  |
|
||||
| **Minecraft** | 1.5+ |
|
||||
| **Platforms** |      |
|
||||
| **Async** | ✔ |
|
||||
| PlaceholderAPI support | ✔ |
|
||||
| Configurable number | ✔ |
|
||||
| Configurable text | ✔ |
|
||||
| 1.20.3+ NumberFormat support | ✔ |
|
||||
| Conditionally disable the feature | If any specified condition is met |
|
||||
|
||||
# Bossbar
|
||||
| Attribute | [TAB](https://github.com/NEZNAMY/TAB)| [UltraBar](https://github.com/ryandw11/UltraBar) | [AnimaBossBar](https://www.spigotmc.org/resources/bossbar-spigot-free-bossbar-plugin-1-9-1-18-1.59135/) |
|
||||
| ------------- | ------------- | ------------- | ------------- |
|
||||
| **Open issues** |  |  | - |
|
||||
| **Price** |  |  |  |
|
||||
| **Minecraft** | 1.5+ | 1.12+ | 1.9 - 1.18.1 |
|
||||
| **Platforms** |      |  |  |
|
||||
| **Async** | ✔ | ❌ | ❌ |
|
||||
| Announcements | ✔ (defined in config) | ✔ (defined at runtime) | ✔ (defined at runtime) |
|
||||
| Toggle command | ✔ (customizable) | ❌ | ❌ |
|
||||
| Display condition | Anything that has a placeholder | On join / death / command / in worldguard region | ❌ |
|
||||
| Attribute | [TAB](https://github.com/NEZNAMY/TAB) | [UltraBar](https://github.com/ryandw11/UltraBar) | [AnimaBossBar](https://www.spigotmc.org/resources/bossbar-spigot-free-bossbar-plugin-1-9-1-18-1.59135/) |
|
||||
|-------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------|
|
||||
| **Open issues** |  |  | - |
|
||||
| **Price** |  |  |  |
|
||||
| **Minecraft** | 1.5+ | 1.12+ | 1.9 - 1.18.1 |
|
||||
| **Platforms** |      |  |  |
|
||||
| **Async** | ✔ | ❌ | ❌ |
|
||||
| Announcements | ✔ (defined in config) | ✔ (defined at runtime) | ✔ (defined at runtime) |
|
||||
| Toggle command | ✔ (customizable) | ❌ | ❌ |
|
||||
| Display condition | Anything that has a placeholder | On join / death / command / in worldguard region | ❌ |
|
||||
|
||||
# Global Playerlist
|
||||
| Attribute | [TAB](https://github.com/NEZNAMY/TAB)| [BungeeTabListPlus](https://github.com/CodeCrafter47/BungeeTabListPlus) | [Velocitab](https://github.com/WiIIiam278/Velocitab) |
|
||||
| ------------- | ------------- | ------------- | ------------- |
|
||||
| **Open issues** |  |  |  |
|
||||
| **Price** |  |  |  |
|
||||
| **Platforms** |   |  |  |
|
||||
| Attribute | [TAB](https://github.com/NEZNAMY/TAB) | [BungeeTabListPlus](https://github.com/CodeCrafter47/BungeeTabListPlus) | [Velocitab](https://github.com/WiIIiam278/Velocitab) |
|
||||
|-----------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------|
|
||||
| **Open issues** |  |  |  |
|
||||
| **Price** |  |  |  |
|
||||
| **Platforms** |   |  |  |
|
||||
|
||||
# Header/Footer
|
||||
| Attribute | [TAB](https://github.com/NEZNAMY/TAB)| [TabList](https://github.com/montlikadani/TabList) |
|
||||
| ------------- | ------------- | ------------- |
|
||||
| **Open issues** |  |  |
|
||||
| **Price** |  |  |
|
||||
| **Platforms** |      |    |
|
||||
| Attribute | [TAB](https://github.com/NEZNAMY/TAB) | [TabList](https://github.com/montlikadani/TabList) |
|
||||
|-----------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| **Open issues** |  |  |
|
||||
| **Price** |  |  |
|
||||
| **Platforms** |      |    |
|
||||
|
||||
# Layout
|
||||
| Attribute | [TAB](https://github.com/NEZNAMY/TAB)| [BungeeTabListPlus](https://github.com/CodeCrafter47/BungeeTabListPlus) |
|
||||
| ------------- | ------------- | ------------- |
|
||||
| **Open issues** |  |  |
|
||||
| **Price** |  |  |
|
||||
| **Platforms** |      |   |
|
||||
| Attribute | [TAB](https://github.com/NEZNAMY/TAB) | [BungeeTabListPlus](https://github.com/CodeCrafter47/BungeeTabListPlus) |
|
||||
|-----------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| **Open issues** |  |  |
|
||||
| **Price** |  |  |
|
||||
| **Platforms** |      |   |
|
||||
|
||||
# Nametags
|
||||
| Attribute | [TAB](https://github.com/NEZNAMY/TAB)| [NametagEdit](https://github.com/sgtcaze/NametagEdit) |
|
||||
| ------------- | ------------- | ------------- |
|
||||
| **Open issues** |  |  |
|
||||
| **Price** |  |  |
|
||||
| **Minecraft** | 1.5+ | 1.7+ |
|
||||
| **Platforms** |      |  |
|
||||
| **Async** | ✔ | ❌ |
|
||||
| Customization | Per-group<br />Per-player<br />Per-world<br />Per-server | Per-group<br />Per-player |
|
||||
| PlaceholderAPI support | ✔ | ✔ |
|
||||
| Configurable collision | ✔ | ✔ |
|
||||
| Configurable nametag visibility | ✔ | ❌ |
|
||||
| Conditionally disable the feature | If any specified condition is met | ❌ |
|
||||
| Bonus | - Option to use armor stands to bypass<br /> all limits and support multiple lines<br />- Anti-override to avoid random plugins breaking it | - |
|
||||
| Attribute | [TAB](https://github.com/NEZNAMY/TAB) | [NametagEdit](https://github.com/sgtcaze/NametagEdit) |
|
||||
|-----------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------|
|
||||
| **Open issues** |  |  |
|
||||
| **Price** |  |  |
|
||||
| **Minecraft** | 1.5+ | 1.7+ |
|
||||
| **Platforms** |      |  |
|
||||
| **Async** | ✔ | ❌ |
|
||||
| Customization | Per-group<br />Per-player<br />Per-world<br />Per-server | Per-group<br />Per-player |
|
||||
| PlaceholderAPI support | ✔ | ✔ |
|
||||
| Configurable collision | ✔ | ✔ |
|
||||
| Configurable nametag visibility | ✔ | ❌ |
|
||||
| Conditionally disable the feature | If any specified condition is met | ❌ |
|
||||
| Bonus | - Option to use armor stands to bypass<br /> all limits and support multiple lines<br />- Anti-override to avoid random plugins breaking it | - |
|
||||
|
||||
# Per world playerlist
|
||||
|
||||
# Playerlist objective
|
||||
| Attribute | [TAB](https://github.com/NEZNAMY/TAB)| [TabList](https://github.com/montlikadani/TabList) |
|
||||
| ------------- | ------------- | ------------- |
|
||||
| **Open issues** |  |  |
|
||||
| **Price** |  |  |
|
||||
| **Platforms** |      |   |
|
||||
| Display any number | ✔ | ✔ |
|
||||
| Supports hearts display type | ✔ | ✔ |
|
||||
| 1.20.3+ NumberFormat support | ✔ | ❌ |
|
||||
| Conditionally disable the feature | When any specified condition is met | Disable in worlds |
|
||||
| Attribute | [TAB](https://github.com/NEZNAMY/TAB) | [TabList](https://github.com/montlikadani/TabList) |
|
||||
|-----------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| **Open issues** |  |  |
|
||||
| **Price** |  |  |
|
||||
| **Platforms** |      |   |
|
||||
| Display any number | ✔ | ✔ |
|
||||
| Supports hearts display type | ✔ | ✔ |
|
||||
| 1.20.3+ NumberFormat support | ✔ | ❌ |
|
||||
| Conditionally disable the feature | When any specified condition is met | Disable in worlds |
|
||||
|
||||
# Scoreboard
|
||||
| Attribute | [TAB](https://github.com/NEZNAMY/TAB)| [RealScoreboard](https://github.com/joserodpt/RealScoreboard) | [AnimatedScoreboard](https://www.spigotmc.org/resources/20848/) | [SternalBoard](https://github.com/ShieldCommunity/SternalBoard) | [KiteBoard](https://github.com/Niall7459/KiteBoard-Documentation) |
|
||||
| ------------- | ------------- | ------------- | ------------- | ------------- | ------------- |
|
||||
| **Open issues** |  |  | - |  |  |
|
||||
| **Price** |  |  |  |  |  |
|
||||
| **Minecraft** | 1.5+ | 1.13+ | 1.8+ | 1.7+ | 1.8 - 1.20.1 |
|
||||
| **Platforms** |      |  |  |  |  |
|
||||
| **Async** | ✔ | ✔ | ✔ | ✔ | ✔ |
|
||||
| Customizable toggle command | ✔ | ❌ | ❌ | ❌ | ❌ |
|
||||
| 1.20.3+ NumberFormat support | ✔ | ❌ | ❌ | ❌ | ❌ |
|
||||
| Max line length limit (<1.13 / 1.13+) |  /  | - /  |  /  |  /  |  /  |
|
||||
| Attribute | [TAB](https://github.com/NEZNAMY/TAB) | [RealScoreboard](https://github.com/joserodpt/RealScoreboard) | [AnimatedScoreboard](https://www.spigotmc.org/resources/20848/) | [SternalBoard](https://github.com/ShieldCommunity/SternalBoard) | [KiteBoard](https://github.com/Niall7459/KiteBoard-Documentation) |
|
||||
|---------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| **Open issues** |  |  | - |  |  |
|
||||
| **Price** |  |  |  |  |  |
|
||||
| **Minecraft** | 1.5+ | 1.13+ | 1.8+ | 1.7+ | 1.8 - 1.20.1 |
|
||||
| **Platforms** |      |  |  |  |  |
|
||||
| **Async** | ✔ | ✔ | ✔ | ✔ | ✔ |
|
||||
| Customizable toggle command | ✔ | ❌ | ❌ | ❌ | ❌ |
|
||||
| 1.20.3+ NumberFormat support | ✔ | ❌ | ❌ | ❌ | ❌ |
|
||||
| Max line length limit (<1.13 / 1.13+) |  /  | - /  |  /  |  /  |  /  |
|
||||
|
||||
| Attribute | [FeatherBoard](https://www.spigotmc.org/resources/featherboard.2691/) | [UltraScoreboards](https://www.spigotmc.org/resources/ultra-scoreboards.93726/) | [QuickBoard](https://github.com/tadeas-drab/QuickBoard) | [SimpleScore](https://github.com/r4g3baby/SimpleScore) | [InfiniteScoreboard](https://www.spigotmc.org/resources/infinite-scoreboard.87425/) |
|
||||
| ------------- | ------------- | ------------- | ------------- | ------------- | ------------- |
|
||||
| **Open issues** | - | - |  |  | - |
|
||||
| **Price** |  |  |  |  |  |
|
||||
| **Minecraft** | 1.7+ | 1.8+ | 1.8 - 1.16.1 | 1.8+ | 1.8 - 1.19 |
|
||||
| **Platforms** |  |   |  |  |  |
|
||||
| **Async** | ✔ | ❌ | ❌ | ❌ | ✔ |
|
||||
| Customizable toggle command | ❌ | ❌ | ❌ | ❌ | ❌ |
|
||||
| 1.20.3+ NumberFormat support | ❌ | ❌ | ❌ | ❌ | ❌ |
|
||||
| Attribute | [FeatherBoard](https://www.spigotmc.org/resources/featherboard.2691/) | [UltraScoreboards](https://www.spigotmc.org/resources/ultra-scoreboards.93726/) | [QuickBoard](https://github.com/tadeas-drab/QuickBoard) | [SimpleScore](https://github.com/r4g3baby/SimpleScore) | [InfiniteScoreboard](https://www.spigotmc.org/resources/infinite-scoreboard.87425/) |
|
||||
|---------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| **Open issues** | - | - |  |  | - |
|
||||
| **Price** |  |  |  |  |  |
|
||||
| **Minecraft** | 1.7+ | 1.8+ | 1.8 - 1.16.1 | 1.8+ | 1.8 - 1.19 |
|
||||
| **Platforms** |  |   |  |  |  |
|
||||
| **Async** | ✔ | ❌ | ❌ | ❌ | ✔ |
|
||||
| Customizable toggle command | ❌ | ❌ | ❌ | ❌ | ❌ |
|
||||
| 1.20.3+ NumberFormat support | ❌ | ❌ | ❌ | ❌ | ❌ |
|
||||
| Max line length limit (<1.13 / 1.13+) | - /  |  /  |  /  |  /  |  /  |
|
||||
|
||||
# Sorting
|
||||
| Attribute | [TAB](https://github.com/NEZNAMY/TAB)| [NametagEdit](https://github.com/sgtcaze/NametagEdit) | [TabList](https://github.com/montlikadani/TabList) | [Velocitab](https://github.com/WiIIiam278/Velocitab) |
|
||||
| ------------- | ------------- | ------------- | ------------- | ------------- |
|
||||
| **Open issues** |  |  |  |  |
|
||||
| **Price** |  |  |  |  |
|
||||
| **Minecraft** | 1.8+ | 1.8+ | 1.8+ | 1.8+ |
|
||||
| **Platforms** |      |  |   |  |
|
||||
| **Async** | ✔ | ❌ | ✔ | ✔ |
|
||||
| Sorting options | Groups<br />Permissions<br />Placeholder alphabetically<br />Placeholder numerically<br />Pre-defined placeholder values | Groups | Groups | Placeholder alphabetically<br />Placeholder numerically |
|
||||
| Attribute | [TAB](https://github.com/NEZNAMY/TAB) | [NametagEdit](https://github.com/sgtcaze/NametagEdit) | [TabList](https://github.com/montlikadani/TabList) | [Velocitab](https://github.com/WiIIiam278/Velocitab) |
|
||||
|-----------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------|
|
||||
| **Open issues** |  |  |  |  |
|
||||
| **Price** |  |  |  |  |
|
||||
| **Minecraft** | 1.8+ | 1.8+ | 1.8+ | 1.8+ |
|
||||
| **Platforms** |      |  |   |  |
|
||||
| **Async** | ✔ | ❌ | ✔ | ✔ |
|
||||
| Sorting options | Groups<br />Permissions<br />Placeholder alphabetically<br />Placeholder numerically<br />Pre-defined placeholder values | Groups | Groups | Placeholder alphabetically<br />Placeholder numerically |
|
||||
|
||||
# Tablist name formatting
|
||||
| Attribute | [TAB](https://github.com/NEZNAMY/TAB)| [TabList](https://github.com/montlikadani/TabList) | [BungeeTabListPlus](https://github.com/CodeCrafter47/BungeeTabListPlus) | [SimpleTabList](https://github.com/TheYIMIR/SimpleTabList) | [Velocitab](https://github.com/WiIIiam278/Velocitab) |
|
||||
| ------------- | ------------- | ------------- | ------------- | ------------- | ------------- |
|
||||
| **Open issues** |  |  |  |  |  |
|
||||
| **Price** |  |  |  |  |  |
|
||||
| **Minecraft** | 1.8+ | 1.8+ | 1.8+ | 1.16+ | 1.8+ |
|
||||
| **Platforms** |      |    |   |  |  |
|
||||
| **Async** | ✔ | ✔ | ✔ | ❌ | ✔ |
|
||||
| Attribute | [TAB](https://github.com/NEZNAMY/TAB) | [TabList](https://github.com/montlikadani/TabList) | [BungeeTabListPlus](https://github.com/CodeCrafter47/BungeeTabListPlus) | [SimpleTabList](https://github.com/TheYIMIR/SimpleTabList) | [Velocitab](https://github.com/WiIIiam278/Velocitab) |
|
||||
|-----------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------|
|
||||
| **Open issues** |  |  |  |  |  |
|
||||
| **Price** |  |  |  |  |  |
|
||||
| **Minecraft** | 1.8+ | 1.8+ | 1.8+ | 1.16+ | 1.8+ |
|
||||
| **Platforms** |      |    |   |  |  |
|
||||
| **Async** | ✔ | ✔ | ✔ | ❌ | ✔ |
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
distribution: 'temurin'
|
||||
java-version: 21
|
||||
- name: Cache Gradle
|
||||
uses: actions/cache@v4.0.2 # https://github.com/actions/cache/releases
|
||||
uses: actions/cache@v4.2.0 # https://github.com/actions/cache/releases
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
|
||||
@@ -201,15 +201,37 @@ public interface NameTagManager {
|
||||
/**
|
||||
* Toggles name tag visibility view on all players for specified player.
|
||||
* On first call, name tags of all players will become invisible for specified
|
||||
* player. On second call, they will become visible again.
|
||||
* player. On the second call, they will become visible again.
|
||||
*
|
||||
* @param player
|
||||
* player to toggle name tag visibility view for
|
||||
* @param sendToggleMessage
|
||||
* {@code true} if configured toggle message should be sent, {@code false} if not
|
||||
* {@code true} if the configured toggle message should be sent, {@code false} if not
|
||||
*/
|
||||
void toggleNameTagVisibilityView(@NonNull TabPlayer player, boolean sendToggleMessage);
|
||||
|
||||
/**
|
||||
* Shows back name tag visibility view on all players for specified player.
|
||||
* If the view is not hidden, nothing happens.
|
||||
*
|
||||
* @param player
|
||||
* player to show name tag visibility view for
|
||||
* @param sendToggleMessage
|
||||
* {@code true} if the configured toggle message should be sent, {@code false} if not
|
||||
*/
|
||||
void showNameTagVisibilityView(@NonNull TabPlayer player, boolean sendToggleMessage);
|
||||
|
||||
/**
|
||||
* Hides name tag visibility view on all players for specified player.
|
||||
* If the view is already hidden, nothing happens.
|
||||
*
|
||||
* @param player
|
||||
* player to show name tag visibility view for
|
||||
* @param sendToggleMessage
|
||||
* {@code true} if the configured toggle message should be sent, {@code false} if not
|
||||
*/
|
||||
void hideNameTagVisibilityView(@NonNull TabPlayer player, boolean sendToggleMessage);
|
||||
|
||||
/**
|
||||
* Returns {@code true} if player has hidden name tags by either calling
|
||||
* {@link #toggleNameTagVisibilityView(TabPlayer, boolean)} or using a command,
|
||||
|
||||
@@ -3,6 +3,10 @@ plugins {
|
||||
id("com.github.johnrengelman.shadow")
|
||||
}
|
||||
|
||||
tasks.withType<Zip>().configureEach {
|
||||
isZip64 = true
|
||||
}
|
||||
|
||||
tasks {
|
||||
shadowJar {
|
||||
archiveFileName.set("TAB-${project.name}-${project.version}.jar")
|
||||
@@ -11,5 +15,10 @@ tasks {
|
||||
relocate("net.kyori.event", "me.neznamy.tab.libs.net.kyori.event")
|
||||
relocate("me.neznamy.yamlassist", "me.neznamy.tab.libs.me.neznamy.yamlassist")
|
||||
relocate("org.yaml.snakeyaml", "me.neznamy.tab.libs.org.yaml.snakeyaml")
|
||||
relocate("redis.clients.jedis", "me.neznamy.tab.libs.redis.clients.jedis")
|
||||
relocate("org.apache.commons.pool2", "me.neznamy.tab.libs.org.apache.commons.pool2")
|
||||
relocate("org.json", "me.neznamy.tab.libs.org.json")
|
||||
relocate("com.rabbitmq", "me.neznamy.tab.libs.com.rabbitmq")
|
||||
relocate("com.saicone.delivery4j", "me.neznamy.tab.libs.com.saicone.delivery4j")
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ plugins {
|
||||
|
||||
allprojects {
|
||||
group = "me.neznamy"
|
||||
version = "5.0.4"
|
||||
version = "5.1.1"
|
||||
description = "An all-in-one solution that works"
|
||||
|
||||
ext.set("id", "tab")
|
||||
@@ -15,15 +15,13 @@ allprojects {
|
||||
val platforms = setOf(
|
||||
projects.bukkit,
|
||||
projects.bukkit.paper,
|
||||
projects.bukkit.v18R3,
|
||||
projects.bukkit.v112R1,
|
||||
projects.bungeecord,
|
||||
projects.component,
|
||||
projects.velocity,
|
||||
projects.sponge7,
|
||||
projects.sponge8,
|
||||
projects.fabric,
|
||||
projects.fabric.v1144,
|
||||
projects.fabric.v1182,
|
||||
projects.fabric.v1203,
|
||||
projects.fabric.v1213
|
||||
projects.sponge,
|
||||
projects.fabric
|
||||
).map { it.dependencyProject }
|
||||
|
||||
val special = setOf(
|
||||
|
||||
@@ -15,4 +15,6 @@ dependencies {
|
||||
exclude("net.md-5", "bungeecord-chat")
|
||||
}
|
||||
compileOnly("com.github.LeonMangler:PremiumVanishAPI:2.8.8")
|
||||
compileOnlyApi("com.viaversion:viaversion-api:5.2.1")
|
||||
compileOnly("com.viaversion:viaversion-common:5.2.1")
|
||||
}
|
||||
@@ -9,11 +9,11 @@ repositories {
|
||||
maven("https://oss.sonatype.org/content/repositories/snapshots")
|
||||
}
|
||||
|
||||
val version = "1.21.4-R0.1-SNAPSHOT"
|
||||
val version = "1.21.5-R0.1-SNAPSHOT"
|
||||
|
||||
dependencies {
|
||||
implementation(projects.bukkit)
|
||||
paperweight.paperDevBundle(version)
|
||||
paperweight.paperDevBundle("1.21.5-no-moonrise-SNAPSHOT") // Just temporary
|
||||
compileOnly("io.papermc.paper:paper-api:${version}")
|
||||
}
|
||||
|
||||
|
||||
@@ -1,106 +1,50 @@
|
||||
package me.neznamy.tab.platforms.paper;
|
||||
|
||||
import me.neznamy.chat.ChatModifier;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.converter.ComponentConverter;
|
||||
import me.neznamy.tab.shared.chat.*;
|
||||
import net.kyori.adventure.key.Key;
|
||||
import net.kyori.adventure.text.KeybindComponent;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import net.kyori.adventure.text.TranslatableComponent;
|
||||
import net.kyori.adventure.text.format.TextDecoration;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.network.chat.MutableComponent;
|
||||
import net.minecraft.network.chat.Style;
|
||||
import net.minecraft.network.chat.TextColor;
|
||||
import net.minecraft.network.chat.*;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Component converter using direct mojang-mapped code for versions 1.20.5+.
|
||||
* Component converter using direct mojang-mapped code.
|
||||
*/
|
||||
@SuppressWarnings("unused") // Used via reflection
|
||||
public class PaperComponentConverter extends ComponentConverter {
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Component convert(@NotNull TabComponent component) {
|
||||
switch (component) {
|
||||
case SimpleComponent simpleComponent -> {
|
||||
return Component.literal(simpleComponent.getText());
|
||||
}
|
||||
case StructuredComponent component1 -> {
|
||||
MutableComponent nmsComponent = Component.literal(component1.getText());
|
||||
ChatModifier modifier = component1.getModifier();
|
||||
TextColor color = null;
|
||||
if (modifier.getColor() != null) {
|
||||
color = TextColor.fromRgb(modifier.getColor().getRgb());
|
||||
}
|
||||
nmsComponent.setStyle(newStyle(
|
||||
color,
|
||||
modifier.getBold(),
|
||||
modifier.getItalic(),
|
||||
modifier.getUnderlined(),
|
||||
modifier.getStrikethrough(),
|
||||
modifier.getObfuscated(),
|
||||
modifier.getFont()
|
||||
));
|
||||
for (StructuredComponent extra : component1.getExtra()) {
|
||||
nmsComponent.append(convert(extra));
|
||||
}
|
||||
return nmsComponent;
|
||||
}
|
||||
case AdventureComponent component1 -> {
|
||||
return fromAdventure(component1.getComponent());
|
||||
}
|
||||
default -> throw new IllegalStateException("Unexpected component type: " + component.getClass().getName());
|
||||
}
|
||||
public Object newTextComponent(@NotNull String text) {
|
||||
return Component.literal(text);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
private Component fromAdventure(@NotNull net.kyori.adventure.text.Component component) {
|
||||
MutableComponent nmsComponent = switch (component) {
|
||||
case TextComponent text -> Component.literal(text.content());
|
||||
case TranslatableComponent translate -> Component.translatable(translate.key());
|
||||
case KeybindComponent keyBind -> Component.keybind(keyBind.keybind());
|
||||
default -> throw new IllegalStateException("Cannot convert " + component.getClass().getName());
|
||||
};
|
||||
|
||||
net.kyori.adventure.text.format.TextColor color = component.color();
|
||||
Key font = component.style().font();
|
||||
Map<TextDecoration, TextDecoration.State> decorations = component.style().decorations();
|
||||
nmsComponent.setStyle(newStyle(
|
||||
color == null ? null : TextColor.fromRgb(color.value()),
|
||||
getDecoration(decorations.get(TextDecoration.BOLD)),
|
||||
getDecoration(decorations.get(TextDecoration.ITALIC)),
|
||||
getDecoration(decorations.get(TextDecoration.UNDERLINED)),
|
||||
getDecoration(decorations.get(TextDecoration.STRIKETHROUGH)),
|
||||
getDecoration(decorations.get(TextDecoration.OBFUSCATED)),
|
||||
font == null ? null : font.asString()
|
||||
));
|
||||
for (net.kyori.adventure.text.Component extra : component.children()) {
|
||||
nmsComponent.append(fromAdventure(extra));
|
||||
}
|
||||
return nmsComponent;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Boolean getDecoration(@Nullable TextDecoration.State state) {
|
||||
if (state == null || state == TextDecoration.State.NOT_SET) return null;
|
||||
return state == TextDecoration.State.TRUE;
|
||||
public Object newTranslatableComponent(@NotNull String key) {
|
||||
return Component.translatable(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
private Style newStyle(@Nullable TextColor color, @Nullable Boolean bold, @Nullable Boolean italic, @Nullable Boolean underlined,
|
||||
@Nullable Boolean strikethrough, @Nullable Boolean obfuscated, @Nullable String font) {
|
||||
return Style.EMPTY
|
||||
.withColor(color)
|
||||
.withBold(bold)
|
||||
.withItalic(italic)
|
||||
.withUnderlined(underlined)
|
||||
.withStrikethrough(strikethrough)
|
||||
.withObfuscated(obfuscated)
|
||||
.withFont(font == null ? null : ResourceLocation.tryParse(font));
|
||||
public Object newKeybindComponent(@NotNull String keybind) {
|
||||
return Component.keybind(keybind);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyStyle(@NotNull Object nmsComponent, @NotNull ChatModifier modifier) {
|
||||
Style style = Style.EMPTY
|
||||
.withColor(modifier.getColor() == null ? null : TextColor.fromRgb(modifier.getColor().getRgb()))
|
||||
.withBold(modifier.getBold())
|
||||
.withItalic(modifier.getItalic())
|
||||
.withUnderlined(modifier.getUnderlined())
|
||||
.withStrikethrough(modifier.getStrikethrough())
|
||||
.withObfuscated(modifier.getObfuscated())
|
||||
.withFont(modifier.getFont() == null ? null : ResourceLocation.tryParse(modifier.getFont()));
|
||||
if (modifier.getShadowColor() != null) style = style.withShadowColor(modifier.getShadowColor()); // withShadowColor takes int instead of Integer, bug?
|
||||
((MutableComponent)nmsComponent).setStyle(style);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addSibling(@NotNull Object parent, @NotNull Object child) {
|
||||
((MutableComponent)parent).append((Component) child);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package me.neznamy.tab.platforms.paper;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import lombok.Getter;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.converter.ComponentConverter;
|
||||
import me.neznamy.tab.platforms.bukkit.provider.ImplementationProvider;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import me.neznamy.tab.shared.util.function.FunctionWithException;
|
||||
import org.bukkit.craftbukkit.entity.CraftPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Implementation provider using direct Mojang-mapped NMS code (1.20.5+).
|
||||
*/
|
||||
@Getter
|
||||
public class PaperImplementationProvider implements ImplementationProvider {
|
||||
|
||||
@NotNull
|
||||
private final ComponentConverter componentConverter = new PaperComponentConverter();
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Scoreboard newScoreboard(@NotNull BukkitTabPlayer player) {
|
||||
return new PaperPacketScoreboard(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public TabList newTabList(@NotNull BukkitTabPlayer player) {
|
||||
return new PaperPacketTabList(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public FunctionWithException<BukkitTabPlayer, Channel> getChannelFunction() {
|
||||
return player -> ((CraftPlayer)player.getPlayer()).getHandle().connection.connection.channel;
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package me.neznamy.tab.platforms.paper;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.shared.util.FunctionWithException;
|
||||
import org.bukkit.craftbukkit.entity.CraftPlayer;
|
||||
|
||||
/**
|
||||
* Loader for 1.20.5+ Paper to speed up functions by using direct NMS code instead of reflection.
|
||||
*/
|
||||
@SuppressWarnings("unused") // Via reflection
|
||||
public class PaperLoader {
|
||||
|
||||
/** Channel getter using direct NMS code */
|
||||
public static final FunctionWithException<BukkitTabPlayer, Channel> getChannel =
|
||||
player -> ((CraftPlayer)player.getPlayer()).getHandle().connection.connection.channel;
|
||||
}
|
||||
@@ -13,34 +13,26 @@ import net.minecraft.network.protocol.game.*;
|
||||
import net.minecraft.world.scores.PlayerTeam;
|
||||
import net.minecraft.world.scores.Scoreboard;
|
||||
import net.minecraft.world.scores.criteria.ObjectiveCriteria;
|
||||
import net.minecraft.world.scores.criteria.ObjectiveCriteria.RenderType;
|
||||
import org.bukkit.craftbukkit.entity.CraftPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Scoreboard implementation using direct mojang-mapped code for versions 1.20.5+.
|
||||
* Scoreboard implementation using direct mojang-mapped code.
|
||||
*/
|
||||
@SuppressWarnings("unused") // Used via reflection
|
||||
public class PaperPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
|
||||
|
||||
private static final ChatFormatting[] formats = ChatFormatting.values();
|
||||
private static final net.minecraft.world.scores.Team.CollisionRule[] collisions = net.minecraft.world.scores.Team.CollisionRule.values();
|
||||
private static final net.minecraft.world.scores.Team.Visibility[] visibilities = net.minecraft.world.scores.Team.Visibility.values();
|
||||
private static final Scoreboard dummyScoreboard = new Scoreboard();
|
||||
private static final Field method;
|
||||
private static final Field players;
|
||||
|
||||
static {
|
||||
try {
|
||||
method = ReflectionUtils.setAccessible(ClientboundSetPlayerTeamPacket.class.getDeclaredField("method"));
|
||||
players = ReflectionUtils.setAccessible(ClientboundSetPlayerTeamPacket.class.getDeclaredField("players"));
|
||||
} catch (NoSuchFieldException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
private static final Field method = ReflectionUtils.getInstanceFields(ClientboundSetPlayerTeamPacket.class, int.class).getFirst();
|
||||
private static final Field players = ReflectionUtils.getOnlyField(ClientboundSetPlayerTeamPacket.class, Collection.class);
|
||||
|
||||
/**
|
||||
* Constructs new instance with given player.
|
||||
@@ -59,13 +51,20 @@ public class PaperPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
|
||||
objective.getName(),
|
||||
ObjectiveCriteria.DUMMY,
|
||||
objective.getTitle().convert(),
|
||||
ObjectiveCriteria.RenderType.values()[objective.getHealthDisplay().ordinal()],
|
||||
RenderType.values()[objective.getHealthDisplay().ordinal()],
|
||||
false,
|
||||
objective.getNumberFormat() == null ? null : objective.getNumberFormat().toFixedFormat(FixedFormat::new)
|
||||
);
|
||||
objective.setPlatformObjective(obj);
|
||||
sendPacket(new ClientboundSetObjectivePacket(obj, ObjectiveAction.REGISTER));
|
||||
sendPacket(new ClientboundSetDisplayObjectivePacket(net.minecraft.world.scores.DisplaySlot.values()[objective.getDisplaySlot().ordinal()], obj));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDisplaySlot(@NonNull Objective objective) {
|
||||
sendPacket(new ClientboundSetDisplayObjectivePacket(
|
||||
net.minecraft.world.scores.DisplaySlot.values()[objective.getDisplaySlot().ordinal()],
|
||||
(net.minecraft.world.scores.Objective) objective.getPlatformObjective()
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -77,21 +76,19 @@ public class PaperPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
|
||||
public void updateObjective(@NonNull Objective objective) {
|
||||
net.minecraft.world.scores.Objective obj = (net.minecraft.world.scores.Objective) objective.getPlatformObjective();
|
||||
obj.setDisplayName(objective.getTitle().convert());
|
||||
obj.setRenderType(ObjectiveCriteria.RenderType.values()[objective.getHealthDisplay().ordinal()]);
|
||||
obj.setRenderType(RenderType.values()[objective.getHealthDisplay().ordinal()]);
|
||||
sendPacket(new ClientboundSetObjectivePacket(obj, ObjectiveAction.UPDATE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setScore(@NonNull Score score) {
|
||||
sendPacket(
|
||||
new ClientboundSetScorePacket(
|
||||
score.getHolder(),
|
||||
score.getObjective().getName(),
|
||||
score.getValue(),
|
||||
Optional.ofNullable(score.getDisplayName() == null ? null : score.getDisplayName().convert()),
|
||||
Optional.ofNullable(score.getNumberFormat() == null ? null : score.getNumberFormat().toFixedFormat(FixedFormat::new))
|
||||
)
|
||||
);
|
||||
sendPacket(new ClientboundSetScorePacket(
|
||||
score.getHolder(),
|
||||
score.getObjective().getName(),
|
||||
score.getValue(),
|
||||
Optional.ofNullable(score.getDisplayName() == null ? null : score.getDisplayName().convert()),
|
||||
Optional.ofNullable(score.getNumberFormat() == null ? null : score.getNumberFormat().toFixedFormat(FixedFormat::new))
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -152,7 +149,13 @@ public class PaperPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
|
||||
players.set(team, onTeamPacket(action, team.getName(), team.getPlayers() == null ? Collections.emptyList() : team.getPlayers()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sends the packet to the player.
|
||||
*
|
||||
* @param packet
|
||||
* Packet to send
|
||||
*/
|
||||
private void sendPacket(@NotNull Packet<?> packet) {
|
||||
((CraftPlayer)player.getPlayer()).getHandle().connection.sendPacket(packet);
|
||||
}
|
||||
|
||||
@@ -4,11 +4,11 @@ import com.mojang.authlib.GameProfile;
|
||||
import com.mojang.authlib.properties.Property;
|
||||
import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.tablist.TabListBase;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.chat.TabComponent;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import me.neznamy.tab.shared.platform.decorators.TrackedTabList;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
@@ -24,18 +24,17 @@ import java.lang.reflect.Field;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* TabList implementation using direct mojang-mapped code for versions 1.21.4+.
|
||||
* TabList implementation using direct mojang-mapped code.
|
||||
*/
|
||||
@SuppressWarnings("unused") // Used via reflection
|
||||
public class PaperPacketTabList extends TabListBase<Component> {
|
||||
public class PaperPacketTabList extends TrackedTabList<BukkitTabPlayer> {
|
||||
|
||||
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> addPlayer = EnumSet.allOf(ClientboundPlayerInfoUpdatePacket.Action.class);
|
||||
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateDisplayName = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME);
|
||||
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateLatency = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY);
|
||||
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateGameMode = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE);
|
||||
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateListed = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED);
|
||||
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateListOrder = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LIST_ORDER);
|
||||
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateHat = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_HAT);
|
||||
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> addPlayer = EnumSet.allOf(ClientboundPlayerInfoUpdatePacket.Action.class);
|
||||
|
||||
private static final Field entries;
|
||||
|
||||
@@ -63,53 +62,39 @@ 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, false, 0, null
|
||||
)));
|
||||
public void updateDisplayName0(@NonNull UUID entry, @Nullable TabComponent displayName) {
|
||||
sendPacket(updateDisplayName, entry, "", null, false, 0, 0, displayName, 0, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateLatency(@NonNull UUID entry, int latency) {
|
||||
sendPacket(new ClientboundPlayerInfoUpdatePacket(updateLatency, new ClientboundPlayerInfoUpdatePacket.Entry(
|
||||
entry, null, false, latency, null, null, false, 0, null
|
||||
)));
|
||||
sendPacket(updateLatency, entry, "", null, false, latency, 0, null, 0, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateGameMode(@NonNull UUID entry, int gameMode) {
|
||||
sendPacket(new ClientboundPlayerInfoUpdatePacket(updateGameMode, new ClientboundPlayerInfoUpdatePacket.Entry(
|
||||
entry, null, false, 0, GameType.byId(gameMode), null, false, 0, null
|
||||
)));
|
||||
sendPacket(updateGameMode, entry, "", null, false, 0, gameMode, null, 0, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateListed(@NonNull UUID entry, boolean listed) {
|
||||
sendPacket(new ClientboundPlayerInfoUpdatePacket(updateListed, new ClientboundPlayerInfoUpdatePacket.Entry(
|
||||
entry, null, listed, 0, null, null, false, 0, null
|
||||
)));
|
||||
sendPacket(updateListed, entry, "", null, listed, 0, 0, null, 0, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateListOrder(@NonNull UUID entry, int listOrder) {
|
||||
sendPacket(new ClientboundPlayerInfoUpdatePacket(updateListOrder, new ClientboundPlayerInfoUpdatePacket.Entry(
|
||||
entry, null, false, 0, null, null, false, listOrder, null
|
||||
)));
|
||||
sendPacket(updateListOrder, entry, "", null, false, 0, 0, null, listOrder, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateHat(@NonNull UUID entry, boolean showHat) {
|
||||
sendPacket(new ClientboundPlayerInfoUpdatePacket(updateHat, new ClientboundPlayerInfoUpdatePacket.Entry(
|
||||
entry, null, false, 0, null, null, showHat, 0, null
|
||||
)));
|
||||
sendPacket(updateHat, 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, boolean showHat) {
|
||||
sendPacket(new ClientboundPlayerInfoUpdatePacket(addPlayer, new ClientboundPlayerInfoUpdatePacket.Entry(
|
||||
id, createProfile(id, name, skin), listed, latency, GameType.byId(gameMode), displayName, showHat, listOrder, null
|
||||
)));
|
||||
public void addEntry0(@NonNull Entry entry) {
|
||||
sendPacket(addPlayer, entry.getUniqueId(), entry.getName(), entry.getSkin(), entry.isListed(), entry.getLatency(),
|
||||
entry.getGameMode(), entry.getDisplayName(), entry.getListOrder(), entry.isShowHat());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -117,6 +102,20 @@ public class PaperPacketTabList extends TabListBase<Component> {
|
||||
sendPacket(new ClientboundTabListPacket(header.convert(), footer.convert()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsEntry(@NonNull UUID entry) {
|
||||
return true; // TODO?
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Skin getSkin() {
|
||||
Collection<Property> properties = ((CraftPlayer)player.getPlayer()).getProfile().getProperties().get(TEXTURES_PROPERTY);
|
||||
if (properties.isEmpty()) return null; // Offline mode
|
||||
Property property = properties.iterator().next();
|
||||
return new Skin(property.value(), property.signature());
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void onPacketSend(@NonNull Object packet) {
|
||||
@@ -129,9 +128,9 @@ public class PaperPacketTabList extends TabListBase<Component> {
|
||||
Component displayName = nmsData.displayName();
|
||||
int latency = nmsData.latency();
|
||||
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME)) {
|
||||
Component expectedDisplayName = getExpectedDisplayNames().get(nmsData.profileId());
|
||||
if (expectedDisplayName != null) {
|
||||
displayName = expectedDisplayName;
|
||||
TabComponent expectedDisplayName = getExpectedDisplayNames().get(nmsData.profileId());
|
||||
if (expectedDisplayName != null && expectedDisplayName.convert() != displayName) {
|
||||
displayName = expectedDisplayName.convert();
|
||||
rewriteEntry = rewritePacket = true;
|
||||
}
|
||||
}
|
||||
@@ -154,17 +153,35 @@ public class PaperPacketTabList extends TabListBase<Component> {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Skin getSkin() {
|
||||
Collection<Property> col = ((CraftPlayer)player.getPlayer()).getProfile().getProperties().get(TEXTURES_PROPERTY);
|
||||
if (col.isEmpty()) return null; //offline mode
|
||||
Property property = col.iterator().next();
|
||||
return new TabList.Skin(property.value(), property.signature());
|
||||
private void sendPacket(@NonNull EnumSet<ClientboundPlayerInfoUpdatePacket.Action> action, @NonNull UUID id, @NonNull String name, @Nullable Skin skin,
|
||||
boolean listed, int latency, int gameMode, @Nullable TabComponent displayName, int listOrder, boolean showHat) {
|
||||
ClientboundPlayerInfoUpdatePacket packet = new ClientboundPlayerInfoUpdatePacket(action, new ClientboundPlayerInfoUpdatePacket.Entry(
|
||||
id,
|
||||
action.contains(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER) ? createProfile(id, name, skin) : null,
|
||||
listed,
|
||||
latency,
|
||||
GameType.byId(gameMode),
|
||||
displayName == null ? null : displayName.convert(),
|
||||
showHat,
|
||||
listOrder,
|
||||
null
|
||||
));
|
||||
sendPacket(packet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates GameProfile from given parameters.
|
||||
*
|
||||
* @param id
|
||||
* Profile ID
|
||||
* @param name
|
||||
* Profile name
|
||||
* @param skin
|
||||
* Player skin
|
||||
* @return GameProfile from given parameters
|
||||
*/
|
||||
@NotNull
|
||||
private GameProfile createProfile(@NotNull UUID id, @NotNull String name, @Nullable Skin skin) {
|
||||
private GameProfile createProfile(@NonNull UUID id, @NonNull String name, @Nullable Skin skin) {
|
||||
GameProfile profile = new GameProfile(id, name);
|
||||
if (skin != null) {
|
||||
profile.getProperties().put(TabList.TEXTURES_PROPERTY,
|
||||
@@ -173,7 +190,13 @@ public class PaperPacketTabList extends TabListBase<Component> {
|
||||
return profile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the packet to the player.
|
||||
*
|
||||
* @param packet
|
||||
* Packet to send
|
||||
*/
|
||||
private void sendPacket(@NotNull Packet<?> packet) {
|
||||
((CraftPlayer)player.getPlayer()).getHandle().connection.sendPacket(packet);
|
||||
((CraftPlayer)player.getPlayer()).getHandle().connection.send(packet);
|
||||
}
|
||||
}
|
||||
@@ -3,33 +3,16 @@ package me.neznamy.tab.platforms.bukkit;
|
||||
import io.netty.channel.Channel;
|
||||
import lombok.Setter;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.shared.features.injection.NettyPipelineInjector;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import me.neznamy.tab.shared.util.FunctionWithException;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import me.neznamy.tab.shared.util.function.FunctionWithException;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.EnumSet;
|
||||
|
||||
/**
|
||||
* Pipeline injection for Bukkit 1.8+.
|
||||
*/
|
||||
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,
|
||||
ProtocolVersion.V1_21_4
|
||||
);
|
||||
|
||||
/** Function for getting player's channel */
|
||||
@Setter
|
||||
private static FunctionWithException<BukkitTabPlayer, Channel> getChannel;
|
||||
@@ -41,41 +24,6 @@ public class BukkitPipelineInjector extends NettyPipelineInjector {
|
||||
super("packet_handler");
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to load required classes, fields and methods and marks class as available.
|
||||
* If something fails, error message is printed and class is not marked as available.
|
||||
*
|
||||
* @param serverVersion
|
||||
* Server version
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static void tryLoad(@NotNull ProtocolVersion serverVersion) {
|
||||
try {
|
||||
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;
|
||||
}
|
||||
Class<?> NetworkManager = BukkitReflection.getClass("network.Connection", "network.NetworkManager", "NetworkManager");
|
||||
Class<?> PlayerConnection = BukkitReflection.getClass("server.network.ServerGamePacketListenerImpl",
|
||||
"server.network.PlayerConnection", "PlayerConnection");
|
||||
Class<?> EntityPlayer = BukkitReflection.getClass("server.level.ServerPlayer", "server.level.EntityPlayer", "EntityPlayer");
|
||||
Field PLAYER_CONNECTION = ReflectionUtils.getOnlyField(EntityPlayer, PlayerConnection);
|
||||
Field NETWORK_MANAGER;
|
||||
if (BukkitReflection.is1_20_2Plus()) {
|
||||
NETWORK_MANAGER = ReflectionUtils.getOnlyField(PlayerConnection.getSuperclass(), NetworkManager);
|
||||
} else {
|
||||
NETWORK_MANAGER = ReflectionUtils.getOnlyField(PlayerConnection, NetworkManager);
|
||||
}
|
||||
Field CHANNEL = ReflectionUtils.getOnlyField(NetworkManager, Channel.class);
|
||||
getChannel = player -> (Channel) CHANNEL.get(NETWORK_MANAGER.get(PLAYER_CONNECTION.get(player.getHandle())));
|
||||
} catch (Exception e) {
|
||||
BukkitUtils.compatibilityError(e, "network channel injection", null,
|
||||
"Anti-override for tablist & nametags not working",
|
||||
"Compatibility with nickname plugins changing player names will not work",
|
||||
"Scoreboard will not be checking for other plugins");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if pipeline injection is available, {@code false} if not.
|
||||
*
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
package me.neznamy.tab.platforms.bukkit;
|
||||
|
||||
import me.neznamy.tab.platforms.bukkit.platform.BukkitPlatform;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.chat.EnumChatFormat;
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
@@ -23,7 +24,7 @@ public class BukkitTabCommand implements CommandExecutor, TabCompleter {
|
||||
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
|
||||
if (TAB.getInstance().isPluginDisabled()) {
|
||||
for (String message : TAB.getInstance().getDisabledCommand().execute(args, sender.hasPermission(TabConstants.Permission.COMMAND_RELOAD), sender.hasPermission(TabConstants.Permission.COMMAND_ALL))) {
|
||||
sender.sendMessage(EnumChatFormat.color(message));
|
||||
sender.sendMessage(((BukkitPlatform)TAB.getInstance().getPlatform()).toBukkitFormat(TabComponent.fromColoredText(message)));
|
||||
}
|
||||
} else {
|
||||
TabPlayer p = null;
|
||||
|
||||
@@ -2,14 +2,12 @@ package me.neznamy.tab.platforms.bukkit;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import me.neznamy.tab.platforms.bukkit.hook.LibsDisguisesHook;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.PingRetriever;
|
||||
import me.neznamy.tab.platforms.bukkit.platform.BukkitPlatform;
|
||||
import me.neznamy.tab.platforms.bukkit.tablist.TabListBase;
|
||||
import me.neznamy.tab.shared.backend.BackendTabPlayer;
|
||||
import me.neznamy.tab.shared.chat.TabComponent;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.metadata.MetadataValue;
|
||||
import org.bukkit.potion.PotionEffectType;
|
||||
@@ -70,12 +68,6 @@ public class BukkitTabPlayer extends BackendTabPlayer {
|
||||
return LibsDisguisesHook.isDisguised(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public TabList.Skin getSkin() {
|
||||
return ((TabListBase<?>)getTabList()).getSkin();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Player getPlayer() {
|
||||
|
||||
@@ -2,15 +2,11 @@ package me.neznamy.tab.platforms.bukkit;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* Class containing static util methods used on Bukkit.
|
||||
*/
|
||||
@@ -22,26 +18,6 @@ public class BukkitUtils {
|
||||
|
||||
private static boolean compatibilityIssue;
|
||||
|
||||
/**
|
||||
* Returns online players from Bukkit API. This requires reflection, as return type changed in 1.8,
|
||||
* and we want to avoid errors.
|
||||
*
|
||||
* @return Online players from Bukkit API.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@SneakyThrows
|
||||
@NotNull
|
||||
public static Player[] getOnlinePlayers() {
|
||||
Object players = Bukkit.class.getMethod("getOnlinePlayers").invoke(null);
|
||||
if (players instanceof Player[]) {
|
||||
//1.7-
|
||||
return (Player[]) players;
|
||||
} else {
|
||||
//1.8+
|
||||
return ((Collection<Player>)players).toArray(new Player[0]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints a console warn that some compatibility issue was found.
|
||||
*
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package me.neznamy.tab.platforms.bukkit.bossbar;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import me.neznamy.tab.api.bossbar.BarColor;
|
||||
import me.neznamy.tab.api.bossbar.BarStyle;
|
||||
import me.neznamy.tab.shared.chat.TabComponent;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.shared.platform.decorators.SafeBossBar;
|
||||
import org.bukkit.Bukkit;
|
||||
|
||||
@@ -8,7 +8,7 @@ import lombok.RequiredArgsConstructor;
|
||||
import me.neznamy.tab.api.bossbar.BarColor;
|
||||
import me.neznamy.tab.api.bossbar.BarStyle;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.shared.chat.TabComponent;
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import me.neznamy.tab.shared.platform.decorators.SafeBossBar;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package me.neznamy.tab.platforms.bukkit.features;
|
||||
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitUtils;
|
||||
import me.neznamy.tab.platforms.bukkit.platform.BukkitPlatform;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.features.PerWorldPlayerListConfiguration;
|
||||
@@ -26,6 +26,10 @@ import java.util.Map.Entry;
|
||||
@SuppressWarnings("deprecation")
|
||||
public class PerWorldPlayerList extends TabFeature implements Listener, Loadable, UnLoadable {
|
||||
|
||||
/** Reference to platform */
|
||||
@NotNull
|
||||
private final BukkitPlatform platform;
|
||||
|
||||
/** Config options */
|
||||
@NotNull
|
||||
private final PerWorldPlayerListConfiguration configuration;
|
||||
@@ -35,25 +39,28 @@ public class PerWorldPlayerList extends TabFeature implements Listener, Loadable
|
||||
*
|
||||
* @param plugin
|
||||
* Plugin instance to register events
|
||||
* @param platform
|
||||
* Platform reference
|
||||
* @param configuration
|
||||
* Feature configuration
|
||||
*/
|
||||
public PerWorldPlayerList(@NotNull JavaPlugin plugin, @NotNull PerWorldPlayerListConfiguration configuration) {
|
||||
public PerWorldPlayerList(@NotNull JavaPlugin plugin, @NotNull BukkitPlatform platform, @NotNull PerWorldPlayerListConfiguration configuration) {
|
||||
this.configuration = configuration;
|
||||
this.platform = platform;
|
||||
Bukkit.getPluginManager().registerEvents(this, plugin);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load() {
|
||||
for (Player p : BukkitUtils.getOnlinePlayers()) {
|
||||
for (Player p : platform.getOnlinePlayers()) {
|
||||
checkPlayer(p);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unload() {
|
||||
for (Player p : BukkitUtils.getOnlinePlayers()) {
|
||||
for (Player pl : BukkitUtils.getOnlinePlayers()) {
|
||||
for (Player p : platform.getOnlinePlayers()) {
|
||||
for (Player pl : platform.getOnlinePlayers()) {
|
||||
p.showPlayer(pl);
|
||||
}
|
||||
}
|
||||
@@ -95,7 +102,7 @@ public class PerWorldPlayerList extends TabFeature implements Listener, Loadable
|
||||
* Player to update
|
||||
*/
|
||||
private void checkPlayer(@NotNull Player p) {
|
||||
for (Player all : BukkitUtils.getOnlinePlayers()) {
|
||||
for (Player all : platform.getOnlinePlayers()) {
|
||||
if (all == p) continue;
|
||||
if (!shouldSee(p, all) && p.canSee(all)) p.hidePlayer(all);
|
||||
if (shouldSee(p, all) && !p.canSee(all)) p.showPlayer(all);
|
||||
|
||||
@@ -2,7 +2,7 @@ package me.neznamy.tab.platforms.bukkit.header;
|
||||
|
||||
import lombok.Getter;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.shared.chat.TabComponent;
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package me.neznamy.tab.platforms.bukkit.header;
|
||||
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* An empty implementation that does not do anything.
|
||||
*/
|
||||
public class DummyHeaderFooter extends HeaderFooter {
|
||||
|
||||
@Override
|
||||
public void set(@NotNull BukkitTabPlayer player, @NotNull TabComponent header, @NotNull TabComponent footer) {
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
@@ -1,51 +1,14 @@
|
||||
package me.neznamy.tab.platforms.bukkit.header;
|
||||
|
||||
import lombok.Getter;
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitUtils;
|
||||
import me.neznamy.tab.shared.chat.TabComponent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* A class responsible for sending header/footer to players on request.
|
||||
*/
|
||||
public abstract class HeaderFooter {
|
||||
|
||||
/** Instance of the class, null if no implementation is available */
|
||||
@Getter
|
||||
@Nullable
|
||||
private static HeaderFooter instance;
|
||||
|
||||
/**
|
||||
* Finds the best available instance for current server software.
|
||||
*/
|
||||
public static void findInstance() {
|
||||
instance = findInstance0();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static HeaderFooter findInstance0() {
|
||||
try {
|
||||
return new PacketHeaderFooter();
|
||||
} catch (Exception e) {
|
||||
if (PaperHeaderFooter.isAvailable()) {
|
||||
BukkitUtils.compatibilityError(e, "sending Header/Footer", "Paper API");
|
||||
return new PaperHeaderFooter();
|
||||
}
|
||||
if (BukkitHeaderFooter.isAvailable()) {
|
||||
BukkitUtils.compatibilityError(e, "sending Header/Footer", "Bukkit API",
|
||||
"Header/Footer having drastically increased CPU usage",
|
||||
"Header/Footer not supporting fonts (1.16+)");
|
||||
return new BukkitHeaderFooter();
|
||||
} else {
|
||||
BukkitUtils.compatibilityError(e, "sending Header/Footer", null,
|
||||
"Header/Footer feature not working");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends header/footer to player.
|
||||
*
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
package me.neznamy.tab.platforms.bukkit.header;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.converter.ComponentConverter;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.PacketSender;
|
||||
import me.neznamy.tab.shared.chat.TabComponent;
|
||||
import me.neznamy.tab.shared.util.BiFunctionWithException;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import me.neznamy.tab.shared.util.function.BiFunctionWithException;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
@@ -32,7 +31,6 @@ public class PacketHeaderFooter extends HeaderFooter {
|
||||
Class<?> Component = BukkitReflection.getClass("network.chat.Component", "network.chat.IChatBaseComponent", "IChatBaseComponent");
|
||||
Class<?> HeaderFooterClass = BukkitReflection.getClass("network.protocol.game.ClientboundTabListPacket",
|
||||
"network.protocol.game.PacketPlayOutPlayerListHeaderFooter", "PacketPlayOutPlayerListHeaderFooter");
|
||||
if (ComponentConverter.INSTANCE == null) throw new IllegalStateException("Component converter is not available");
|
||||
if (BukkitReflection.getMinorVersion() >= 17) {
|
||||
Constructor<?> newHeaderFooter = HeaderFooterClass.getConstructor(Component, Component);
|
||||
createPacket = newHeaderFooter::newInstance;
|
||||
|
||||
@@ -2,7 +2,7 @@ package me.neznamy.tab.platforms.bukkit.header;
|
||||
|
||||
import lombok.Getter;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.shared.chat.TabComponent;
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
@@ -3,10 +3,11 @@ package me.neznamy.tab.platforms.bukkit.nms;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.shared.util.FunctionWithException;
|
||||
import me.neznamy.tab.shared.util.function.FunctionWithException;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
@@ -23,6 +24,7 @@ public class BukkitReflection {
|
||||
public static final Method CraftPlayer_getHandle = getHandle();
|
||||
|
||||
/** Server version data */
|
||||
@Getter
|
||||
private static final ServerVersion serverVersion = detectServerVersion();
|
||||
|
||||
/** Flag determining whether the server version is at least 1.20.2 or not */
|
||||
@@ -38,6 +40,9 @@ public class BukkitReflection {
|
||||
private static final boolean is1_21_4Plus = serverVersion.minorVersion >= 21 &&
|
||||
ReflectionUtils.getFields(getClass("network.chat.Style", "network.chat.ChatModifier"), Integer.class).size() == 1;
|
||||
|
||||
@Getter
|
||||
private static final boolean mojangMapped = ReflectionUtils.classExists("org.bukkit.craftbukkit.CraftServer");
|
||||
|
||||
@NotNull
|
||||
@SneakyThrows
|
||||
private static Method getHandle() {
|
||||
@@ -56,11 +61,12 @@ public class BukkitReflection {
|
||||
ClassLoader loader = BukkitReflection.class.getClassLoader();
|
||||
classFunction = name -> loader.loadClass("net.minecraft.server." + serverPackage + "." + name);
|
||||
}
|
||||
return new ServerVersion(classFunction, minorVersion, serverPackage);
|
||||
} else {
|
||||
// Paper without CB relocation
|
||||
minorVersion = Integer.parseInt(Bukkit.getBukkitVersion().split("-")[0].split("\\.")[1]);
|
||||
return new ServerVersion(classFunction, minorVersion, null);
|
||||
}
|
||||
return new ServerVersion(classFunction, minorVersion);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -99,9 +105,12 @@ public class BukkitReflection {
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@Getter
|
||||
private static class ServerVersion {
|
||||
public static class ServerVersion {
|
||||
|
||||
private final FunctionWithException<String, Class<?>> getClass;
|
||||
private final int minorVersion;
|
||||
|
||||
@Nullable
|
||||
private final String serverPackage;
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ package me.neznamy.tab.platforms.bukkit.nms;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.shared.util.BiConsumerWithException;
|
||||
import me.neznamy.tab.shared.util.function.BiConsumerWithException;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ package me.neznamy.tab.platforms.bukkit.nms;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitUtils;
|
||||
import me.neznamy.tab.shared.util.ToIntFunction;
|
||||
import me.neznamy.tab.shared.util.function.ToIntFunction;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@@ -1,34 +1,17 @@
|
||||
package me.neznamy.tab.platforms.bukkit.nms.converter;
|
||||
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitUtils;
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.shared.chat.TabComponent;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import me.neznamy.chat.ChatModifier;
|
||||
import me.neznamy.chat.component.KeybindComponent;
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import me.neznamy.chat.component.TextComponent;
|
||||
import me.neznamy.chat.component.TranslatableComponent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.EnumSet;
|
||||
|
||||
/**
|
||||
* Interface for converting TAB component into NMS components (1.7+).
|
||||
* Interface for converting TAB components 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,
|
||||
ProtocolVersion.V1_21_4
|
||||
);
|
||||
|
||||
/** Instance of this class */
|
||||
@Nullable
|
||||
public static ComponentConverter INSTANCE;
|
||||
|
||||
/**
|
||||
* Converts TAB component to NMS component.
|
||||
*
|
||||
@@ -37,25 +20,77 @@ public abstract class ComponentConverter {
|
||||
* @return Converted component
|
||||
*/
|
||||
@NotNull
|
||||
public abstract Object convert(@NotNull TabComponent component);
|
||||
public Object convert(@NotNull TabComponent component) {
|
||||
// Component type
|
||||
Object nmsComponent;
|
||||
if (component instanceof TextComponent) {
|
||||
nmsComponent = newTextComponent(((TextComponent) component).getText());
|
||||
} else if (component instanceof TranslatableComponent) {
|
||||
nmsComponent = newTranslatableComponent(((TranslatableComponent) component).getKey());
|
||||
} else if (component instanceof KeybindComponent) {
|
||||
nmsComponent = newKeybindComponent(((KeybindComponent)component).getKeybind());
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unexpected component type: " + component.getClass().getName());
|
||||
}
|
||||
|
||||
// Component style
|
||||
applyStyle(nmsComponent, component.getModifier());
|
||||
|
||||
// Extra
|
||||
for (TabComponent extra : component.getExtra()) {
|
||||
addSibling(nmsComponent, convert(extra));
|
||||
}
|
||||
|
||||
return nmsComponent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to load component converter.
|
||||
* Creates a new text component with given text.
|
||||
*
|
||||
* @param serverVersion
|
||||
* Server version
|
||||
* @param text
|
||||
* Text to display
|
||||
* @return Text component with given text
|
||||
*/
|
||||
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 e) {
|
||||
if (BukkitUtils.PRINT_EXCEPTIONS) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
@NotNull
|
||||
public abstract Object newTextComponent(@NotNull String text);
|
||||
|
||||
/**
|
||||
* Creates a new translatable component with the given key.
|
||||
*
|
||||
* @param key
|
||||
* Key to translate
|
||||
* @return Translatable component with the given key
|
||||
*/
|
||||
@NotNull
|
||||
public abstract Object newTranslatableComponent(@NotNull String key);
|
||||
|
||||
/**
|
||||
* Creates a new keybind component with given keybind.
|
||||
*
|
||||
* @param keybind
|
||||
* Keybind to show
|
||||
* @return Keybind component with given keybind
|
||||
*/
|
||||
@NotNull
|
||||
public abstract Object newKeybindComponent(@NotNull String keybind);
|
||||
|
||||
/**
|
||||
* Converts given chat modifier to minecraft style and applies it to the component.
|
||||
*
|
||||
* @param nmsComponent
|
||||
* Component to apply style to
|
||||
* @param style
|
||||
* Style to convert and apply
|
||||
*/
|
||||
public abstract void applyStyle(@NotNull Object nmsComponent, @NotNull ChatModifier style);
|
||||
|
||||
/**
|
||||
* Appends child to the given parent component.
|
||||
*
|
||||
* @param parent
|
||||
* Parent to append the child to
|
||||
* @param child
|
||||
* Child component to append
|
||||
*/
|
||||
public abstract void addSibling(@NotNull Object parent, @NotNull Object child);
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package me.neznamy.tab.platforms.bukkit.nms.converter;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.chat.ChatModifier;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Class for converting TAB components into NMS components for versions 1.7 - 1.15.2.
|
||||
*/
|
||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
public class LegacyComponentConverter extends ComponentConverter {
|
||||
|
||||
private final Class<?> IChatBaseComponent = BukkitReflection.getClass("IChatBaseComponent");
|
||||
|
||||
private final Constructor<?> newTextComponent = BukkitReflection.getClass("ChatComponentText").getConstructor(String.class);
|
||||
private final Constructor<?> newTranslatableComponent = BukkitReflection.getClass("ChatMessage").getConstructor(String.class, Object[].class);
|
||||
private Constructor<?> newKeybindComponent;
|
||||
|
||||
private final Class<?> ChatModifierClass = BukkitReflection.getClass("ChatModifier");
|
||||
private final Constructor<?> newChatModifier = ChatModifierClass.getConstructor();
|
||||
private final Class<Enum> EnumChatFormat = (Class<Enum>) BukkitReflection.getClass("ChatFormatting", "EnumChatFormat");
|
||||
private final Class<?> ChatBaseComponent = BukkitReflection.getClass("ChatBaseComponent");
|
||||
private final Method ChatBaseComponent_addSibling = ReflectionUtils.getOnlyMethod(ChatBaseComponent, IChatBaseComponent, IChatBaseComponent);
|
||||
private final Field Component_modifier = ReflectionUtils.getOnlyField(ChatBaseComponent, ChatModifierClass);
|
||||
private final List<Field> magicCodes = ReflectionUtils.getFields(ChatModifierClass, Boolean.class);
|
||||
private final Method ChatModifier_setColor = ReflectionUtils.getOnlyMethod(ChatModifierClass, ChatModifierClass, EnumChatFormat);
|
||||
|
||||
/**
|
||||
* Constructs new instance and loads all NMS classes, constructors, and methods.
|
||||
*
|
||||
* @throws ReflectiveOperationException
|
||||
* If something failed
|
||||
*/
|
||||
public LegacyComponentConverter() throws ReflectiveOperationException {
|
||||
if (BukkitReflection.getMinorVersion() >= 12) {
|
||||
newKeybindComponent = BukkitReflection.getClass("ChatComponentKeybind").getConstructor(String.class);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
@SneakyThrows
|
||||
public Object newTextComponent(@NotNull String text) {
|
||||
return newTextComponent.newInstance(text);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
@SneakyThrows
|
||||
public Object newTranslatableComponent(@NotNull String key) {
|
||||
return newTranslatableComponent.newInstance(key, new Object[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
@SneakyThrows
|
||||
public Object newKeybindComponent(@NotNull String keybind) {
|
||||
if (BukkitReflection.getMinorVersion() >= 12) {
|
||||
return newKeybindComponent.newInstance(keybind);
|
||||
}
|
||||
throw new UnsupportedOperationException("Keybind components were added in 1.12");
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void applyStyle(@NotNull Object nmsComponent, @NotNull ChatModifier style) {
|
||||
Object nmsModifier = newChatModifier.newInstance();
|
||||
if (style.getColor() != null) {
|
||||
ChatModifier_setColor.invoke(nmsModifier, Enum.valueOf(EnumChatFormat, style.getColor().getLegacyColor().name()));
|
||||
}
|
||||
magicCodes.get(0).set(nmsModifier, style.getBold());
|
||||
magicCodes.get(1).set(nmsModifier, style.getItalic());
|
||||
magicCodes.get(2).set(nmsModifier, style.getStrikethrough());
|
||||
magicCodes.get(3).set(nmsModifier, style.getUnderlined());
|
||||
magicCodes.get(4).set(nmsModifier, style.getObfuscated());
|
||||
Component_modifier.set(nmsComponent, nmsModifier);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void addSibling(@NotNull Object parent, @NotNull Object child) {
|
||||
ChatBaseComponent_addSibling.invoke(parent, child);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package me.neznamy.tab.platforms.bukkit.nms.converter;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.chat.ChatModifier;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* Class for converting TAB components into NMS components for versions 1.16 - 1.18.2.
|
||||
*/
|
||||
public class ModerateComponentConverter extends ComponentConverter {
|
||||
|
||||
private final Class<?> IChatBaseComponent = BukkitReflection.getClass("network.chat.Component", "network.chat.IChatBaseComponent", "IChatBaseComponent");
|
||||
private final Class<?> ChatBaseComponent = BukkitReflection.getClass("network.chat.BaseComponent", "network.chat.ChatBaseComponent", "ChatBaseComponent");
|
||||
private final Class<?> TextColor = BukkitReflection.getClass("network.chat.TextColor", "network.chat.ChatHexColor", "ChatHexColor");
|
||||
private final Class<?> ResourceLocation = BukkitReflection.getClass("resources.ResourceLocation", "resources.MinecraftKey", "MinecraftKey");
|
||||
|
||||
private final Constructor<?> newTextComponent = BukkitReflection.getClass("network.chat.TextComponent", "network.chat.ChatComponentText", "ChatComponentText").getConstructor(String.class);
|
||||
private final Constructor<?> newTranslatableComponent = BukkitReflection.getClass("network.chat.TranslatableComponent", "network.chat.ChatMessage", "ChatMessage").getConstructor(String.class, Object[].class);
|
||||
private final Constructor<?> newKeybindComponent = BukkitReflection.getClass("network.chat.KeybindComponent", "network.chat.ChatComponentKeybind", "ChatComponentKeybind").getConstructor(String.class);
|
||||
|
||||
private final Class<?> ChatModifierClass = BukkitReflection.getClass("network.chat.Style", "network.chat.ChatModifier", "ChatModifier");
|
||||
private final Constructor<?> newChatModifier = ReflectionUtils.setAccessible(ChatModifierClass.getDeclaredConstructor(
|
||||
TextColor,
|
||||
Boolean.class, Boolean.class, Boolean.class, Boolean.class, Boolean.class,
|
||||
BukkitReflection.getClass("network.chat.ClickEvent", "network.chat.ChatClickable", "ChatClickable"),
|
||||
BukkitReflection.getClass("network.chat.HoverEvent", "network.chat.ChatHoverable", "ChatHoverable"),
|
||||
String.class,
|
||||
ResourceLocation
|
||||
));
|
||||
private final Method ChatBaseComponent_addSibling = ReflectionUtils.getOnlyMethod(ChatBaseComponent, IChatBaseComponent, IChatBaseComponent);
|
||||
private final Field Component_modifier = ReflectionUtils.getOnlyField(ChatBaseComponent, ChatModifierClass);
|
||||
|
||||
private final Method ChatHexColor_fromRGB = ReflectionUtils.getMethods(TextColor, TextColor, int.class).get(0); // There should only be 1, but some mods add more
|
||||
private final Method ResourceLocation_tryParse = ReflectionUtils.getMethod(ResourceLocation, new String[]{"tryParse", "m_135820_", "a"}, String.class);
|
||||
|
||||
/**
|
||||
* Constructs new instance and loads all NMS classes, constructors, and methods.
|
||||
*
|
||||
* @throws ReflectiveOperationException
|
||||
* If something failed
|
||||
*/
|
||||
public ModerateComponentConverter() throws ReflectiveOperationException {
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
@SneakyThrows
|
||||
public Object newTextComponent(@NotNull String text) {
|
||||
return newTextComponent.newInstance(text);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
@SneakyThrows
|
||||
public Object newTranslatableComponent(@NotNull String key) {
|
||||
return newTranslatableComponent.newInstance(key, new Object[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
@SneakyThrows
|
||||
public Object newKeybindComponent(@NotNull String keybind) {
|
||||
return newKeybindComponent.newInstance(keybind);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void applyStyle(@NotNull Object nmsComponent, @NotNull ChatModifier style) {
|
||||
Component_modifier.set(nmsComponent, newChatModifier.newInstance(
|
||||
style.getColor() == null ? null : ChatHexColor_fromRGB.invoke(null, style.getColor().getRgb()),
|
||||
style.getBold(),
|
||||
style.getItalic(),
|
||||
style.getUnderlined(),
|
||||
style.getStrikethrough(),
|
||||
style.getObfuscated(),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
style.getFont() == null ? null : ResourceLocation_tryParse.invoke(null, style.getFont())
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void addSibling(@NotNull Object parent, @NotNull Object child) {
|
||||
ChatBaseComponent_addSibling.invoke(parent, child);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
package me.neznamy.tab.platforms.bukkit.nms.converter;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.chat.ChatModifier;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
/**
|
||||
* Class for converting TAB components into NMS components for versions 1.19 and up.
|
||||
*/
|
||||
public class ModernComponentConverter extends ComponentConverter {
|
||||
|
||||
private final Class<?> IChatBaseComponent = BukkitReflection.getClass("network.chat.Component", "network.chat.IChatBaseComponent");
|
||||
private final Class<?> IChatMutableComponent = BukkitReflection.getClass("network.chat.MutableComponent", "network.chat.IChatMutableComponent");
|
||||
private final Class<?> TextColor = BukkitReflection.getClass("network.chat.TextColor", "network.chat.ChatHexColor");
|
||||
private final Class<?> ResourceLocation = BukkitReflection.getClass("resources.ResourceLocation", "resources.MinecraftKey");
|
||||
|
||||
private final Method newTextComponent = ReflectionUtils.getMethod(IChatBaseComponent, new String[] {"b", "literal"}, String.class);
|
||||
private final Method newTranslatableComponent = ReflectionUtils.getMethod(IChatBaseComponent, new String[] {"c", "translatable"}, String.class);
|
||||
private final Method newKeybindComponent = ReflectionUtils.getMethod(IChatBaseComponent, new String[] {"d", "keybind"}, String.class);
|
||||
|
||||
private final Class<?> ChatModifierClass = BukkitReflection.getClass("network.chat.Style", "network.chat.ChatModifier");
|
||||
private final Constructor<?> newChatModifier;
|
||||
private final Method ChatBaseComponent_addSibling = ReflectionUtils.getOnlyMethod(IChatMutableComponent, IChatMutableComponent, IChatBaseComponent);
|
||||
private final Field Component_modifier = ReflectionUtils.getOnlyField(IChatMutableComponent, ChatModifierClass);
|
||||
|
||||
private final Method ChatHexColor_fromRGB = ReflectionUtils.getMethods(TextColor, TextColor, int.class).get(0); // There should only be 1, but some mods add more
|
||||
private final Method ResourceLocation_tryParse = ReflectionUtils.getMethod(ResourceLocation, new String[]{"tryParse", "m_135820_", "a"}, String.class);
|
||||
|
||||
/**
|
||||
* Constructs new instance and loads all NMS classes, constructors, and methods.
|
||||
*
|
||||
* @throws ReflectiveOperationException
|
||||
* If something failed
|
||||
*/
|
||||
public ModernComponentConverter() throws ReflectiveOperationException {
|
||||
Class<?> chatClickable = BukkitReflection.getClass("network.chat.ClickEvent", "network.chat.ChatClickable");
|
||||
Class<?> chatHoverable = BukkitReflection.getClass("network.chat.HoverEvent", "network.chat.ChatHoverable");
|
||||
if (BukkitReflection.is1_21_4Plus()) {
|
||||
// 1.21.4+
|
||||
newChatModifier = ReflectionUtils.setAccessible(ChatModifierClass.getDeclaredConstructor(TextColor, Integer.class, Boolean.class, Boolean.class,
|
||||
Boolean.class, Boolean.class, Boolean.class, chatClickable, chatHoverable, String.class, ResourceLocation));
|
||||
} else {
|
||||
// 1.21.3-
|
||||
newChatModifier = ReflectionUtils.setAccessible(ChatModifierClass.getDeclaredConstructor(TextColor, Boolean.class, Boolean.class, Boolean.class,
|
||||
Boolean.class, Boolean.class, chatClickable, chatHoverable, String.class, ResourceLocation));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
@SneakyThrows
|
||||
public Object newTextComponent(@NotNull String text) {
|
||||
return newTextComponent.invoke(null, text);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
@SneakyThrows
|
||||
public Object newTranslatableComponent(@NotNull String key) {
|
||||
return newTranslatableComponent.invoke(null, key);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
@SneakyThrows
|
||||
public Object newKeybindComponent(@NotNull String keybind) {
|
||||
return newKeybindComponent.invoke(null, keybind);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void applyStyle(@NotNull Object nmsComponent, @NotNull ChatModifier style) {
|
||||
if (BukkitReflection.is1_21_4Plus()) {
|
||||
Component_modifier.set(nmsComponent, newChatModifier.newInstance(
|
||||
style.getColor() == null ? null : ChatHexColor_fromRGB.invoke(null, style.getColor().getRgb()),
|
||||
style.getShadowColor(),
|
||||
style.getBold(),
|
||||
style.getItalic(),
|
||||
style.getUnderlined(),
|
||||
style.getStrikethrough(),
|
||||
style.getObfuscated(),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
style.getFont() == null ? null : ResourceLocation_tryParse.invoke(null, style.getFont())
|
||||
));
|
||||
} else {
|
||||
Component_modifier.set(nmsComponent, newChatModifier.newInstance(
|
||||
style.getColor() == null ? null : ChatHexColor_fromRGB.invoke(null, style.getColor().getRgb()),
|
||||
style.getBold(),
|
||||
style.getItalic(),
|
||||
style.getUnderlined(),
|
||||
style.getStrikethrough(),
|
||||
style.getObfuscated(),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
style.getFont() == null ? null : ResourceLocation_tryParse.invoke(null, style.getFont())
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void addSibling(@NotNull Object parent, @NotNull Object child) {
|
||||
ChatBaseComponent_addSibling.invoke(parent, child);
|
||||
}
|
||||
}
|
||||
@@ -1,228 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.nms.converter;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
|
||||
import me.neznamy.tab.shared.chat.*;
|
||||
import me.neznamy.tab.shared.util.FunctionWithException;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import net.kyori.adventure.key.Key;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.KeybindComponent;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import net.kyori.adventure.text.TranslatableComponent;
|
||||
import net.kyori.adventure.text.format.TextDecoration;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Class for converting TAB component into NMS components (1.7+).
|
||||
*/
|
||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||
public class ReflectionComponentConverter extends ComponentConverter {
|
||||
|
||||
private final FunctionWithException<String, Object> newTextComponent;
|
||||
private final FunctionWithException<String, Object> newTranslatableComponent;
|
||||
private final FunctionWithException<String, Object> newKeybindComponent;
|
||||
private final Function<ChatModifier, Object> convertModifier;
|
||||
|
||||
private final Class<?> ChatModifier = BukkitReflection.getClass("network.chat.Style", "network.chat.ChatModifier", "ChatModifier");
|
||||
private final Class<Enum> EnumChatFormat = (Class<Enum>) BukkitReflection.getClass("ChatFormatting", "EnumChatFormat");
|
||||
private final Constructor<?> newChatModifier;
|
||||
private final Method ChatBaseComponent_addSibling;
|
||||
private final Field Component_modifier;
|
||||
private final List<Field> magicCodes = ReflectionUtils.getFields(ChatModifier, Boolean.class);
|
||||
|
||||
// 1.15-
|
||||
private Method ChatModifier_setColor;
|
||||
|
||||
// 1.16+
|
||||
private Method ChatHexColor_fromRGB;
|
||||
private Method ResourceLocation_tryParse;
|
||||
|
||||
/**
|
||||
* Constructs new instance and loads all NMS classes, constructors and methods.
|
||||
*
|
||||
* @throws ReflectiveOperationException
|
||||
* If something failed
|
||||
*/
|
||||
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);
|
||||
newTextComponent = text -> IChatBaseComponent_b.invoke(null, text);
|
||||
|
||||
Method IChatBaseComponent_c = ReflectionUtils.getMethod(IChatBaseComponent, new String[] {"c", "translatable"}, String.class);
|
||||
newTranslatableComponent = text -> IChatBaseComponent_c.invoke(null, text);
|
||||
|
||||
Method IChatBaseComponent_d = ReflectionUtils.getMethod(IChatBaseComponent, new String[] {"d", "keybind"}, String.class);
|
||||
newKeybindComponent = text -> IChatBaseComponent_d.invoke(null, text);
|
||||
|
||||
Class<?> IChatMutableComponent = BukkitReflection.getClass("network.chat.MutableComponent", "network.chat.IChatMutableComponent", "IChatMutableComponent");
|
||||
Component_modifier = ReflectionUtils.getOnlyField(IChatMutableComponent, ChatModifier);
|
||||
ChatBaseComponent_addSibling = ReflectionUtils.getOnlyMethod(IChatMutableComponent, IChatMutableComponent, IChatBaseComponent);
|
||||
} else {
|
||||
Class<?> ChatComponentText = BukkitReflection.getClass("network.chat.TextComponent", "network.chat.ChatComponentText", "ChatComponentText");
|
||||
Constructor<?> newChatComponentText = ChatComponentText.getConstructor(String.class);
|
||||
newTextComponent = newChatComponentText::newInstance;
|
||||
|
||||
Class<?> ChatMessage = BukkitReflection.getClass("network.chat.TranslatableComponent", "network.chat.ChatMessage", "ChatMessage");
|
||||
Constructor<?> newChatMessage = ChatMessage.getConstructor(String.class, Object[].class);
|
||||
newTranslatableComponent = text -> newChatMessage.newInstance(text, new Object[0]);
|
||||
|
||||
newKeybindComponent = text -> {
|
||||
throw new UnsupportedOperationException("Keybind component conversion is not implemented");
|
||||
};
|
||||
|
||||
Class<?> ChatBaseComponent = BukkitReflection.getClass("network.chat.BaseComponent", "network.chat.ChatBaseComponent", "ChatBaseComponent");
|
||||
Component_modifier = ReflectionUtils.getOnlyField(ChatBaseComponent, ChatModifier);
|
||||
ChatBaseComponent_addSibling = ReflectionUtils.getOnlyMethod(ChatBaseComponent, IChatBaseComponent, IChatBaseComponent);
|
||||
}
|
||||
if (BukkitReflection.getMinorVersion() >= 16) {
|
||||
Class<?> chatHexColor = BukkitReflection.getClass("network.chat.TextColor", "network.chat.ChatHexColor", "ChatHexColor");
|
||||
Class<?> ResourceLocation = BukkitReflection.getClass("resources.ResourceLocation", "resources.MinecraftKey", "MinecraftKey");
|
||||
Class<?> chatClickable = BukkitReflection.getClass("network.chat.ClickEvent", "network.chat.ChatClickable", "ChatClickable");
|
||||
Class<?> chatHoverable = BukkitReflection.getClass("network.chat.HoverEvent", "network.chat.ChatHoverable", "ChatHoverable");
|
||||
ResourceLocation_tryParse = ReflectionUtils.getMethod(ResourceLocation, new String[]{"tryParse", "m_135820_", "a"}, String.class);
|
||||
ChatHexColor_fromRGB = ReflectionUtils.getMethods(chatHexColor, chatHexColor, int.class).get(0); // There should only be 1, but some mods add more
|
||||
if (BukkitReflection.is1_21_4Plus()) {
|
||||
// 1.21.4+
|
||||
newChatModifier = ReflectionUtils.setAccessible(ChatModifier.getDeclaredConstructor(chatHexColor, Integer.class, Boolean.class, Boolean.class,
|
||||
Boolean.class, Boolean.class, Boolean.class, chatClickable, chatHoverable, String.class, ResourceLocation));
|
||||
} else {
|
||||
// 1.21.3-
|
||||
newChatModifier = ReflectionUtils.setAccessible(ChatModifier.getDeclaredConstructor(chatHexColor, Boolean.class, Boolean.class, Boolean.class,
|
||||
Boolean.class, Boolean.class, chatClickable, chatHoverable, String.class, ResourceLocation));
|
||||
}
|
||||
convertModifier = this::createModifierModern;
|
||||
} else {
|
||||
newChatModifier = ChatModifier.getConstructor();
|
||||
ChatModifier_setColor = ReflectionUtils.getOnlyMethod(ChatModifier, ChatModifier, EnumChatFormat);
|
||||
convertModifier = this::createModifierLegacy;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
@NotNull
|
||||
public Object convert(@NotNull TabComponent component) {
|
||||
if (component instanceof SimpleComponent) {
|
||||
return newTextComponent.apply(((SimpleComponent) component).getText());
|
||||
} else if (component instanceof StructuredComponent) {
|
||||
StructuredComponent component1 = (StructuredComponent) component;
|
||||
Object nmsComponent = newTextComponent.apply(component1.getText());
|
||||
Component_modifier.set(nmsComponent, convertModifier.apply(component1.getModifier()));
|
||||
for (StructuredComponent extra : component1.getExtra()) {
|
||||
ChatBaseComponent_addSibling.invoke(nmsComponent, convert(extra));
|
||||
}
|
||||
return nmsComponent;
|
||||
} else {
|
||||
return fromAdventure(((AdventureComponent)component).getComponent());
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
@NotNull
|
||||
private Object fromAdventure(@NotNull Component component) {
|
||||
Object nmsComponent;
|
||||
if (component instanceof TextComponent) {
|
||||
nmsComponent = newTextComponent.apply(((TextComponent) component).content());
|
||||
} else if (component instanceof TranslatableComponent) {
|
||||
nmsComponent = newTranslatableComponent.apply(((TranslatableComponent)component).key());
|
||||
} else if (component instanceof KeybindComponent) {
|
||||
nmsComponent = newKeybindComponent.apply(((KeybindComponent)component).keybind());
|
||||
} else {
|
||||
throw new IllegalStateException("Cannot convert " + component.getClass().getName());
|
||||
}
|
||||
|
||||
net.kyori.adventure.text.format.TextColor color = component.color();
|
||||
Key font = component.style().font();
|
||||
Map<TextDecoration, TextDecoration.State> decorations = component.style().decorations();
|
||||
Component_modifier.set(nmsComponent, newStyleModern(
|
||||
color == null ? null : ChatHexColor_fromRGB.invoke(null, color.value()),
|
||||
getDecoration(decorations.get(TextDecoration.BOLD)),
|
||||
getDecoration(decorations.get(TextDecoration.ITALIC)),
|
||||
getDecoration(decorations.get(TextDecoration.UNDERLINED)),
|
||||
getDecoration(decorations.get(TextDecoration.STRIKETHROUGH)),
|
||||
getDecoration(decorations.get(TextDecoration.OBFUSCATED)),
|
||||
font == null ? null : font.asString()
|
||||
));
|
||||
for (Component extra : component.children()) {
|
||||
ChatBaseComponent_addSibling.invoke(nmsComponent, fromAdventure(extra));
|
||||
}
|
||||
return nmsComponent;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Boolean getDecoration(@Nullable TextDecoration.State state) {
|
||||
if (state == null || state == TextDecoration.State.NOT_SET) return null;
|
||||
return state == TextDecoration.State.TRUE;
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private Object createModifierModern(@NotNull ChatModifier modifier) {
|
||||
return newStyleModern(
|
||||
modifier.getColor() == null ? null : ChatHexColor_fromRGB.invoke(null, modifier.getColor().getRgb()),
|
||||
modifier.getBold(),
|
||||
modifier.getItalic(),
|
||||
modifier.getUnderlined(),
|
||||
modifier.getStrikethrough(),
|
||||
modifier.getObfuscated(),
|
||||
modifier.getFont()
|
||||
);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private Object createModifierLegacy(@NotNull ChatModifier modifier) {
|
||||
Object nmsModifier = newChatModifier.newInstance();
|
||||
if (modifier.getColor() != null) {
|
||||
ChatModifier_setColor.invoke(nmsModifier, Enum.valueOf(EnumChatFormat, modifier.getColor().getLegacyColor().name()));
|
||||
}
|
||||
magicCodes.get(0).set(nmsModifier, modifier.getBold());
|
||||
magicCodes.get(1).set(nmsModifier, modifier.getItalic());
|
||||
magicCodes.get(2).set(nmsModifier, modifier.getStrikethrough());
|
||||
magicCodes.get(3).set(nmsModifier, modifier.getUnderlined());
|
||||
magicCodes.get(4).set(nmsModifier, modifier.getObfuscated());
|
||||
return nmsModifier;
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
@NotNull
|
||||
private Object newStyleModern(@Nullable Object color, @Nullable Boolean bold, @Nullable Boolean italic, @Nullable Boolean underlined,
|
||||
@Nullable Boolean strikethrough, @Nullable Boolean obfuscated, @Nullable String font) {
|
||||
if (BukkitReflection.is1_21_4Plus()) {
|
||||
return newChatModifier.newInstance(
|
||||
color,
|
||||
null,
|
||||
bold,
|
||||
italic,
|
||||
underlined,
|
||||
strikethrough,
|
||||
obfuscated,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
font == null ? null : ResourceLocation_tryParse.invoke(null, font)
|
||||
);
|
||||
} else {
|
||||
return newChatModifier.newInstance(
|
||||
color,
|
||||
bold,
|
||||
italic,
|
||||
underlined,
|
||||
strikethrough,
|
||||
obfuscated,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
font == null ? null : ResourceLocation_tryParse.invoke(null, font)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,24 +3,24 @@ package me.neznamy.tab.platforms.bukkit.platform;
|
||||
import lombok.Getter;
|
||||
import lombok.SneakyThrows;
|
||||
import me.clip.placeholderapi.PlaceholderAPI;
|
||||
import me.neznamy.chat.component.*;
|
||||
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.header.*;
|
||||
import me.neznamy.tab.platforms.bukkit.hook.BukkitPremiumVanishHook;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.PingRetriever;
|
||||
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.platforms.bukkit.provider.BukkitImplementationProvider;
|
||||
import me.neznamy.tab.platforms.bukkit.provider.ImplementationProvider;
|
||||
import me.neznamy.tab.platforms.bukkit.provider.ViaVersionImplementationProvider;
|
||||
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.*;
|
||||
import me.neznamy.tab.shared.features.PerWorldPlayerListConfiguration;
|
||||
import me.neznamy.tab.shared.features.PlaceholderManagerImpl;
|
||||
import me.neznamy.tab.shared.features.injection.PipelineInjector;
|
||||
@@ -38,7 +38,6 @@ import me.neznamy.tab.shared.platform.impl.DummyBossBar;
|
||||
import me.neznamy.tab.shared.util.PerformanceUtil;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import net.kyori.adventure.audience.Audience;
|
||||
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
|
||||
import net.milkbowl.vault.chat.Chat;
|
||||
import net.milkbowl.vault.permission.Permission;
|
||||
import org.bstats.bukkit.Metrics;
|
||||
@@ -53,6 +52,9 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Implementation of Platform interface for Bukkit platform
|
||||
@@ -80,12 +82,30 @@ public class BukkitPlatform implements BackendPlatform {
|
||||
/** Detection for presence of Paper's MSPT getter */
|
||||
private final boolean paperMspt = ReflectionUtils.methodExists(Bukkit.class, "getAverageTickTime");
|
||||
|
||||
/** Header/footer implementation */
|
||||
@Getter
|
||||
@NotNull
|
||||
private final HeaderFooter headerFooter;
|
||||
|
||||
/** Flag tracking if direct mojang-mapped code can be used or not */
|
||||
private final boolean canUseDirectNMS = BukkitReflection.isMojangMapped() &&
|
||||
(serverVersion == ProtocolVersion.V1_21_4 || serverVersion == ProtocolVersion.V1_21_5);
|
||||
|
||||
/** Implementation for creating new instances using content available on the server */
|
||||
@NotNull
|
||||
private final ImplementationProvider serverImplementationProvider;
|
||||
|
||||
/** Implementation for sending new content to new players on old servers */
|
||||
@NotNull
|
||||
private final ViaVersionImplementationProvider viaVersionImplementationProvider = new ViaVersionImplementationProvider(serverVersion);
|
||||
|
||||
/**
|
||||
* Constructs new instance with given plugin.
|
||||
*
|
||||
* @param plugin
|
||||
* Plugin
|
||||
*/
|
||||
@SneakyThrows
|
||||
public BukkitPlatform(@NotNull JavaPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
long time = System.currentTimeMillis();
|
||||
@@ -98,21 +118,47 @@ public class BukkitPlatform implements BackendPlatform {
|
||||
if (Bukkit.getPluginManager().isPluginEnabled("PremiumVanish")) {
|
||||
new BukkitPremiumVanishHook().register();
|
||||
}
|
||||
PingRetriever.tryLoad();
|
||||
ComponentConverter.tryLoad(serverVersion);
|
||||
ScoreboardLoader.findInstance(serverVersion);
|
||||
TabListBase.findInstance(serverVersion);
|
||||
if (BukkitReflection.getMinorVersion() >= 8) {
|
||||
HeaderFooter.findInstance();
|
||||
BukkitPipelineInjector.tryLoad(serverVersion);
|
||||
if (canUseDirectNMS) {
|
||||
serverImplementationProvider = (ImplementationProvider) Class.forName("me.neznamy.tab.platforms.paper.PaperImplementationProvider").getConstructor().newInstance();
|
||||
} else if ("v1_12_R1".equals(BukkitReflection.getServerVersion().getServerPackage())) {
|
||||
serverImplementationProvider = (ImplementationProvider) Class.forName("me.neznamy.tab.platforms.bukkit.v1_12_R1.NMSImplementationProvider").getConstructor().newInstance();
|
||||
} else if ("v1_8_R3".equals(BukkitReflection.getServerVersion().getServerPackage())) {
|
||||
serverImplementationProvider = (ImplementationProvider) Class.forName("me.neznamy.tab.platforms.bukkit.v1_8_R3.NMSImplementationProvider").getConstructor().newInstance();
|
||||
} else {
|
||||
serverImplementationProvider = new BukkitImplementationProvider();
|
||||
}
|
||||
headerFooter = findHeaderFooter();
|
||||
PingRetriever.tryLoad();
|
||||
BukkitPipelineInjector.setGetChannel(serverImplementationProvider.getChannelFunction());
|
||||
BukkitUtils.sendCompatibilityMessage();
|
||||
Bukkit.getConsoleSender().sendMessage("[TAB] §7Loaded NMS hook in " + (System.currentTimeMillis()-time) + "ms");
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private HeaderFooter findHeaderFooter() {
|
||||
if (BukkitReflection.getMinorVersion() >= 8) {
|
||||
try {
|
||||
Objects.requireNonNull(serverImplementationProvider.getComponentConverter());
|
||||
return new PacketHeaderFooter();
|
||||
} catch (Exception e) {
|
||||
if (PaperHeaderFooter.isAvailable()) return new PaperHeaderFooter();
|
||||
if (BukkitHeaderFooter.isAvailable()) {
|
||||
BukkitUtils.compatibilityError(e, "sending Header/Footer", "Bukkit API",
|
||||
"Header/Footer having drastically increased CPU usage",
|
||||
"Header/Footer not supporting fonts (1.16+)");
|
||||
return new BukkitHeaderFooter();
|
||||
} else {
|
||||
BukkitUtils.compatibilityError(e, "sending Header/Footer", null,
|
||||
"Header/Footer feature not working");
|
||||
}
|
||||
}
|
||||
}
|
||||
return new DummyHeaderFooter();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadPlayers() {
|
||||
for (Player p : BukkitUtils.getOnlinePlayers()) {
|
||||
for (Player p : getOnlinePlayers()) {
|
||||
TAB.getInstance().addPlayer(new BukkitTabPlayer(this, p));
|
||||
}
|
||||
}
|
||||
@@ -120,20 +166,18 @@ public class BukkitPlatform implements BackendPlatform {
|
||||
@Override
|
||||
public void registerPlaceholders() {
|
||||
PlaceholderManagerImpl manager = TAB.getInstance().getPlaceholderManager();
|
||||
manager.registerServerPlaceholder("%vault-prefix%", -1, () -> "");
|
||||
manager.registerServerPlaceholder("%vault-suffix%", -1, () -> "");
|
||||
manager.registerInternalServerPlaceholder("%vault-prefix%", -1, () -> "");
|
||||
manager.registerInternalServerPlaceholder("%vault-suffix%", -1, () -> "");
|
||||
if (Bukkit.getPluginManager().isPluginEnabled("Vault")) {
|
||||
RegisteredServiceProvider<Chat> rspChat = Bukkit.getServicesManager().getRegistration(Chat.class);
|
||||
if (rspChat != null) {
|
||||
Chat chat = rspChat.getProvider();
|
||||
int refresh = TAB.getInstance().getConfiguration().getConfig().getPermissionRefreshInterval();
|
||||
manager.registerPlayerPlaceholder("%vault-prefix%", refresh, p -> chat.getPlayerPrefix((Player) p.getPlayer()));
|
||||
manager.registerPlayerPlaceholder("%vault-suffix%", refresh, p -> chat.getPlayerSuffix((Player) p.getPlayer()));
|
||||
manager.registerInternalPlayerPlaceholder("%vault-prefix%", 1000, p -> chat.getPlayerPrefix((Player) p.getPlayer()));
|
||||
manager.registerInternalPlayerPlaceholder("%vault-suffix%", 1000, p -> chat.getPlayerSuffix((Player) p.getPlayer()));
|
||||
}
|
||||
}
|
||||
// Override for the PAPI placeholder to prevent console errors on unsupported server versions when ping field changes
|
||||
manager.registerPlayerPlaceholder("%player_ping%", manager.getRefreshInterval("%player_ping%"),
|
||||
p -> PerformanceUtil.toString(((TabPlayer) p).getPing()));
|
||||
manager.registerPlayerPlaceholder("%player_ping%", p -> PerformanceUtil.toString(((TabPlayer) p).getPing()));
|
||||
BackendPlatform.super.registerPlaceholders();
|
||||
}
|
||||
|
||||
@@ -157,7 +201,7 @@ public class BukkitPlatform implements BackendPlatform {
|
||||
@Override
|
||||
@Nullable
|
||||
public TabFeature getPerWorldPlayerList(@NotNull PerWorldPlayerListConfiguration configuration) {
|
||||
return new PerWorldPlayerList(plugin, configuration);
|
||||
return new PerWorldPlayerList(plugin, this, configuration);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -167,34 +211,31 @@ public class BukkitPlatform implements BackendPlatform {
|
||||
return;
|
||||
}
|
||||
PlaceholderManagerImpl pl = TAB.getInstance().getPlaceholderManager();
|
||||
int refresh = pl.getRefreshInterval(identifier);
|
||||
if (identifier.startsWith("%rel_")) {
|
||||
//relational placeholder
|
||||
TAB.getInstance().getPlaceholderManager().registerRelationalPlaceholder(identifier, pl.getRefreshInterval(identifier), (viewer, target) ->
|
||||
TAB.getInstance().getPlaceholderManager().registerRelationalPlaceholder(identifier, (viewer, target) ->
|
||||
PlaceholderAPI.setRelationalPlaceholders((Player) viewer.getPlayer(), (Player) target.getPlayer(), identifier));
|
||||
} else if (identifier.startsWith("%sync:")) {
|
||||
registerSyncPlaceholder(identifier, refresh);
|
||||
registerSyncPlaceholder(identifier);
|
||||
} else if (identifier.startsWith("%server_")) {
|
||||
TAB.getInstance().getPlaceholderManager().registerServerPlaceholder(identifier, refresh,
|
||||
TAB.getInstance().getPlaceholderManager().registerServerPlaceholder(identifier,
|
||||
() -> PlaceholderAPI.setPlaceholders(null, identifier));
|
||||
} else {
|
||||
TAB.getInstance().getPlaceholderManager().registerPlayerPlaceholder(identifier, refresh,
|
||||
TAB.getInstance().getPlaceholderManager().registerPlayerPlaceholder(identifier,
|
||||
p -> PlaceholderAPI.setPlaceholders((Player) p.getPlayer(), identifier));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a sync placeholder with given identifier and refresh.
|
||||
* Registers a sync placeholder with given identifier and automatically decided refresh.
|
||||
*
|
||||
* @param identifier
|
||||
* Placeholder identifier
|
||||
* @param refresh
|
||||
* Placeholder refresh
|
||||
*/
|
||||
public void registerSyncPlaceholder(@NotNull String identifier, int refresh) {
|
||||
public void registerSyncPlaceholder(@NotNull String identifier) {
|
||||
String syncedPlaceholder = "%" + identifier.substring(6);
|
||||
PlayerPlaceholderImpl[] ppl = new PlayerPlaceholderImpl[1];
|
||||
ppl[0] = TAB.getInstance().getPlaceholderManager().registerPlayerPlaceholder(identifier, refresh, p -> {
|
||||
ppl[0] = TAB.getInstance().getPlaceholderManager().registerPlayerPlaceholder(identifier, p -> {
|
||||
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||
long time = System.nanoTime();
|
||||
ppl[0].updateValue(p, placeholderAPI ? PlaceholderAPI.setPlaceholders((Player) p.getPlayer(), syncedPlaceholder) : identifier);
|
||||
@@ -233,7 +274,7 @@ public class BukkitPlatform implements BackendPlatform {
|
||||
command.setExecutor(cmd);
|
||||
command.setTabCompleter(cmd);
|
||||
} else {
|
||||
logWarn(TabComponent.fromColoredText("Failed to register command, is it defined in plugin.yml?"));
|
||||
logWarn(SimpleTextComponent.text("Failed to register command, is it defined in plugin.yml?"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -254,9 +295,9 @@ public class BukkitPlatform implements BackendPlatform {
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Object convertComponent(@NotNull TabComponent component, boolean modern) {
|
||||
if (ComponentConverter.INSTANCE != null) {
|
||||
return ComponentConverter.INSTANCE.convert(component);
|
||||
public Object convertComponent(@NotNull TabComponent component) {
|
||||
if (serverImplementationProvider.getComponentConverter() != null) {
|
||||
return serverImplementationProvider.getComponentConverter().convert(component);
|
||||
} else {
|
||||
return component;
|
||||
}
|
||||
@@ -266,7 +307,9 @@ public class BukkitPlatform implements BackendPlatform {
|
||||
@NotNull
|
||||
@SneakyThrows
|
||||
public Scoreboard createScoreboard(@NotNull TabPlayer player) {
|
||||
return ScoreboardLoader.getInstance().apply((BukkitTabPlayer) player);
|
||||
Scoreboard scoreboard = viaVersionImplementationProvider.newScoreboard((BukkitTabPlayer) player);
|
||||
if (scoreboard != null) return scoreboard;
|
||||
return serverImplementationProvider.newScoreboard((BukkitTabPlayer) player);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -289,17 +332,9 @@ public class BukkitPlatform implements BackendPlatform {
|
||||
@NotNull
|
||||
@SneakyThrows
|
||||
public TabList createTabList(@NotNull TabPlayer player) {
|
||||
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();
|
||||
TabList tabList = viaVersionImplementationProvider.newTabList((BukkitTabPlayer) player);
|
||||
if (tabList != null) return tabList;
|
||||
return serverImplementationProvider.newTabList((BukkitTabPlayer) player);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -307,6 +342,11 @@ public class BukkitPlatform implements BackendPlatform {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSafeFromPacketEventsBug() {
|
||||
return serverVersion.getMinorVersion() >= 13;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public GroupManager detectPermissionPlugin() {
|
||||
@@ -361,29 +401,46 @@ public class BukkitPlatform implements BackendPlatform {
|
||||
*/
|
||||
@NotNull
|
||||
public String toBukkitFormat(@NotNull TabComponent component) {
|
||||
if (component instanceof SimpleComponent) {
|
||||
return ((SimpleComponent) component).getText();
|
||||
}
|
||||
if (component instanceof StructuredComponent) {
|
||||
StructuredComponent iComponent = (StructuredComponent) component;
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (iComponent.getModifier().getColor() != null) {
|
||||
if (serverVersion.supportsRGB()) {
|
||||
String hexCode = iComponent.getModifier().getColor().getHexCode();
|
||||
sb.append('§').append("x").append('§').append(hexCode.charAt(0)).append('§').append(hexCode.charAt(1))
|
||||
.append('§').append(hexCode.charAt(2)).append('§').append(hexCode.charAt(3))
|
||||
.append('§').append(hexCode.charAt(4)).append('§').append(hexCode.charAt(5));
|
||||
} else {
|
||||
sb.append(iComponent.getModifier().getColor().getLegacyColor().getFormat());
|
||||
}
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (component.getModifier().getColor() != null) {
|
||||
if (serverVersion.supportsRGB()) {
|
||||
String hexCode = component.getModifier().getColor().getHexCode();
|
||||
sb.append('§').append("x").append('§').append(hexCode.charAt(0)).append('§').append(hexCode.charAt(1))
|
||||
.append('§').append(hexCode.charAt(2)).append('§').append(hexCode.charAt(3))
|
||||
.append('§').append(hexCode.charAt(4)).append('§').append(hexCode.charAt(5));
|
||||
} else {
|
||||
sb.append('§').append(component.getModifier().getColor().getLegacyColor().getCharacter());
|
||||
}
|
||||
sb.append(iComponent.getModifier().getMagicCodes());
|
||||
sb.append(iComponent.getText());
|
||||
for (StructuredComponent extra : iComponent.getExtra()) {
|
||||
sb.append(toBukkitFormat(extra));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
return LegacyComponentSerializer.builder().hexColors().useUnusualXRepeatedCharacterHexFormat().build().serialize(((AdventureComponent)component).getComponent());
|
||||
sb.append(component.getModifier().getMagicCodes());
|
||||
if (component instanceof TextComponent) {
|
||||
sb.append(((TextComponent) component).getText());
|
||||
} else if (component instanceof TranslatableComponent) {
|
||||
sb.append(((TranslatableComponent) component).getKey());
|
||||
} else if (component instanceof KeybindComponent) {
|
||||
sb.append(((KeybindComponent) component).getKeybind());
|
||||
} else {
|
||||
throw new IllegalStateException("Unexpected component type: " + component.getClass().getName());
|
||||
}
|
||||
for (TabComponent extra : component.getExtra()) {
|
||||
sb.append(toBukkitFormat(extra));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns online players from Bukkit API.
|
||||
* This method may use reflections, because the return type changed in 1.8,
|
||||
* and we want to avoid errors.
|
||||
*
|
||||
* @return Online players from Bukkit API.
|
||||
*/
|
||||
@SneakyThrows
|
||||
@NotNull
|
||||
public Collection<? extends Player> getOnlinePlayers() {
|
||||
if (serverVersion.getMinorVersion() >= 8) {
|
||||
return Bukkit.getOnlinePlayers();
|
||||
}
|
||||
return Arrays.asList((Player[]) Bukkit.class.getMethod("getOnlinePlayers").invoke(null));
|
||||
}
|
||||
}
|
||||