Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
71a19c63d8 | ||
|
|
657327e7ee | ||
|
|
efeea7cf0f | ||
|
|
7a4637368d | ||
|
|
2e6ccd078b | ||
|
|
827197f916 | ||
|
|
c8454ec0c1 | ||
|
|
7127a77d92 | ||
|
|
b23912b52f | ||
|
|
4b21148a03 | ||
|
|
2d5ef95e54 | ||
|
|
b5e2d376dd | ||
|
|
c40b337edc | ||
|
|
f8583642a8 | ||
|
|
707f28df16 | ||
|
|
dd78da089d | ||
|
|
c4ea65ed70 | ||
|
|
aabba806c4 | ||
|
|
88167fc42d | ||
|
|
e4d7005e46 | ||
|
|
5ee27f7ed7 | ||
|
|
9a4492c401 | ||
|
|
39a2aee5a0 | ||
|
|
5184ef170f | ||
|
|
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 |
No files matched your search
@@ -2,7 +2,7 @@
|
||||
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. 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.5-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]
|
||||
|
||||
@@ -4,7 +4,7 @@ The compact configuration allows you to get the plugin to work the way you want
|
||||
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)
|
||||
|
||||
|
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 |
@@ -28,7 +28,7 @@ Permission `tab.seevanished` allows players to:
|
||||
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,
|
||||
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.
|
||||
|
||||
@@ -71,10 +71,8 @@ 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.
|
||||
|
||||
@@ -86,7 +84,7 @@ You will not experience all of these, only those that affect the version differe
|
||||
| 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 |
|
||||
| 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 |
|
||||
|
||||
@@ -130,8 +130,8 @@ Sending scoreboard packets (scoreboard-teams, belowname-objective, playerlist-ob
|
||||
* **[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**'s `hide-scoreboard-numbers` config option makes [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).
|
||||
* **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.
|
||||
|
||||
@@ -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.5</version>
|
||||
<version>5.0.7</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
```
|
||||
@@ -40,7 +40,7 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly 'com.github.NEZNAMY:TAB-API:5.0.5'
|
||||
compileOnly 'com.github.NEZNAMY:TAB-API:5.0.7'
|
||||
}
|
||||
```
|
||||
|
||||
@@ -51,7 +51,7 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly("com.github.NEZNAMY", "TAB-API", "5.0.5")
|
||||
compileOnly("com.github.NEZNAMY", "TAB-API", "5.0.7")
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -67,7 +67,7 @@ header-footer:
|
||||
|
||||
> [!NOTE]
|
||||
> To make per-world work on proxy installation,
|
||||
> install the [TAB-Bridge](https://www.mc-market.org/resources/21641) plugin on your backend servers.
|
||||
> install the [TAB-Bridge](https://github.com/NEZNAMY/TAB/wiki/TAB-Bridge) plugin on your backend servers.
|
||||
|
||||
## Per-group / per-player
|
||||
**groups.yml**
|
||||
|
||||
@@ -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)
|
||||
@@ -138,7 +139,7 @@ _DEFAULT_:
|
||||
```
|
||||
|
||||
# Limitations
|
||||
* [1.5 - 1.12.2] Prefix/suffix length is limited to 16 characters (including color codes).
|
||||
* [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.
|
||||
* Name does not support RGB codes. Any used RGB colors will be rounded to the nearest legacy code.
|
||||
@@ -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)`
|
||||
@@ -209,8 +218,8 @@ To get the original value set by the plugin based on configuration:
|
||||
> 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
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# About the wiki
|
||||
The wiki explains the functionality of the plugin as of version **5.0.5**.
|
||||
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.
|
||||
|
||||
@@ -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,7 +3,7 @@ plugins {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("com.github.johnrengelman:shadow:8.1.1")
|
||||
implementation("io.freefair.gradle:lombok-plugin:8.6")
|
||||
implementation("fabric-loom:fabric-loom.gradle.plugin:1.8-SNAPSHOT")
|
||||
implementation("com.gradleup.shadow:shadow-gradle-plugin:8.3.6")
|
||||
implementation("io.freefair.gradle:lombok-plugin:8.13")
|
||||
implementation("dev.architectury.loom:dev.architectury.loom.gradle.plugin:1.10-SNAPSHOT")
|
||||
}
|
||||
@@ -3,6 +3,9 @@ rootProject.name = "build-logic"
|
||||
dependencyResolutionManagement {
|
||||
repositories {
|
||||
maven("https://maven.fabricmc.net/")
|
||||
maven("https://maven.architectury.dev/")
|
||||
maven("https://maven.minecraftforge.net/")
|
||||
maven("https://maven.neoforged.net/releases/")
|
||||
gradlePluginPortal()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
plugins {
|
||||
id("tab.standard-conventions")
|
||||
id("com.github.johnrengelman.shadow")
|
||||
id("com.gradleup.shadow")
|
||||
}
|
||||
|
||||
tasks.withType<Zip>().configureEach {
|
||||
isZip64 = true
|
||||
}
|
||||
|
||||
tasks {
|
||||
@@ -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,26 +4,27 @@ plugins {
|
||||
|
||||
allprojects {
|
||||
group = "me.neznamy"
|
||||
version = "5.0.6"
|
||||
version = "5.2.0"
|
||||
description = "An all-in-one solution that works"
|
||||
|
||||
ext.set("id", "tab")
|
||||
ext.set("website", "https://github.com/NEZNAMY/TAB")
|
||||
ext.set("author", "NEZNAMY")
|
||||
ext.set("credits", "Joseph T. McQuigg (JT122406)")
|
||||
}
|
||||
|
||||
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.sponge,
|
||||
projects.fabric,
|
||||
projects.fabric.v1144,
|
||||
projects.fabric.v1182,
|
||||
projects.fabric.v1203,
|
||||
projects.fabric.v1213
|
||||
projects.neoforge,
|
||||
projects.forge
|
||||
).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")
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
plugins {
|
||||
id("io.papermc.paperweight.userdev") version "2.0.0-beta.8"
|
||||
id("io.papermc.paperweight.userdev") version "2.0.0-beta.16"
|
||||
}
|
||||
|
||||
repositories {
|
||||
@@ -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,37 +1,36 @@
|
||||
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.ChatModifier;
|
||||
import me.neznamy.tab.shared.chat.component.KeybindComponent;
|
||||
import me.neznamy.tab.shared.chat.component.TabComponent;
|
||||
import me.neznamy.tab.shared.chat.component.TextComponent;
|
||||
import me.neznamy.tab.shared.chat.component.TranslatableComponent;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Component converter using direct mojang-mapped code for versions 1.21.4+.
|
||||
* 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) {
|
||||
// Component type
|
||||
MutableComponent nmsComponent = switch (component) {
|
||||
case TextComponent text -> Component.literal(text.getText());
|
||||
case TranslatableComponent translatable -> Component.translatable(translatable.getKey());
|
||||
case KeybindComponent keybind -> Component.keybind(keybind.getKeybind());
|
||||
default -> throw new IllegalStateException("Unexpected component type: " + component.getClass().getName());
|
||||
};
|
||||
public Object newTextComponent(@NotNull String text) {
|
||||
return Component.literal(text);
|
||||
}
|
||||
|
||||
// Component style
|
||||
ChatModifier modifier = component.getModifier();
|
||||
@Override
|
||||
@NotNull
|
||||
public Object newTranslatableComponent(@NotNull String key) {
|
||||
return Component.translatable(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
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())
|
||||
@@ -41,12 +40,11 @@ public class PaperComponentConverter extends ComponentConverter {
|
||||
.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?
|
||||
nmsComponent.setStyle(style);
|
||||
((MutableComponent)nmsComponent).setStyle(style);
|
||||
}
|
||||
|
||||
// Extra
|
||||
for (TabComponent extra : component.getExtra()) {
|
||||
nmsComponent.append(convert(extra));
|
||||
}
|
||||
return nmsComponent;
|
||||
@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.function.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.component.TabComponent;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import me.neznamy.tab.shared.platform.decorators.TrackedTabList;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
@@ -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.function.FunctionWithException;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
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.
|
||||
*
|
||||
|
||||
@@ -3,7 +3,7 @@ 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.component.TabComponent;
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
|
||||
@@ -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.component.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.component.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.component.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.component.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.component.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.component.TabComponent;
|
||||
import me.neznamy.tab.shared.util.function.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.component.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;
|
||||
|
||||
@@ -7,6 +7,7 @@ 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;
|
||||
}
|
||||
}
|
||||
@@ -1,28 +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.component.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 components into NMS components (1.7+).
|
||||
*/
|
||||
public abstract class ComponentConverter {
|
||||
|
||||
/** Versions supported by paper module that uses direct mojang-mapped NMS */
|
||||
private static final EnumSet<ProtocolVersion> paperNativeVersions = EnumSet.of(
|
||||
ProtocolVersion.V1_21_4
|
||||
);
|
||||
|
||||
/** Instance of this class */
|
||||
@Nullable
|
||||
public static ComponentConverter INSTANCE;
|
||||
|
||||
/**
|
||||
* Converts TAB component to NMS component.
|
||||
*
|
||||
@@ -31,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,171 +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.ChatModifier;
|
||||
import me.neznamy.tab.shared.chat.component.KeybindComponent;
|
||||
import me.neznamy.tab.shared.chat.component.TabComponent;
|
||||
import me.neznamy.tab.shared.chat.component.TextComponent;
|
||||
import me.neznamy.tab.shared.chat.component.TranslatableComponent;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import me.neznamy.tab.shared.util.function.FunctionWithException;
|
||||
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 (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 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 on < 1.19");
|
||||
};
|
||||
|
||||
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));
|
||||
}
|
||||
} else {
|
||||
newChatModifier = ChatModifier.getConstructor();
|
||||
ChatModifier_setColor = ReflectionUtils.getOnlyMethod(ChatModifier, ChatModifier, EnumChatFormat);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
@NotNull
|
||||
public Object convert(@NotNull TabComponent component) {
|
||||
// Component type
|
||||
Object nmsComponent;
|
||||
if (component instanceof TextComponent) {
|
||||
nmsComponent = newTextComponent.apply(((TextComponent) component).getText());
|
||||
} else if (component instanceof TranslatableComponent) {
|
||||
nmsComponent = newTranslatableComponent.apply(((TranslatableComponent) component).getKey());
|
||||
} else if (component instanceof KeybindComponent) {
|
||||
nmsComponent = newKeybindComponent.apply(((KeybindComponent) component).getKeybind());
|
||||
} else {
|
||||
throw new IllegalStateException("Unexpected component type: " + component.getClass().getName());
|
||||
}
|
||||
|
||||
// Component style
|
||||
Component_modifier.set(nmsComponent, convertStyle(component.getModifier()));
|
||||
|
||||
// Extra
|
||||
for (TabComponent extra : component.getExtra()) {
|
||||
ChatBaseComponent_addSibling.invoke(nmsComponent, convert(extra));
|
||||
}
|
||||
return nmsComponent;
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private Object convertStyle(@NotNull ChatModifier modifier) {
|
||||
if (BukkitReflection.is1_21_4Plus()) {
|
||||
return newChatModifier.newInstance(
|
||||
modifier.getColor() == null ? null : ChatHexColor_fromRGB.invoke(null, modifier.getColor().getRgb()),
|
||||
modifier.getShadowColor(),
|
||||
modifier.getBold(),
|
||||
modifier.getItalic(),
|
||||
modifier.getItalic(),
|
||||
modifier.getUnderlined(),
|
||||
modifier.getObfuscated(),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
modifier.getFont() == null ? null : ResourceLocation_tryParse.invoke(null, modifier.getFont())
|
||||
);
|
||||
} else if (BukkitReflection.getMinorVersion() >= 16) {
|
||||
return newChatModifier.newInstance(
|
||||
modifier.getColor() == null ? null : ChatHexColor_fromRGB.invoke(null, modifier.getColor().getRgb()),
|
||||
modifier.getBold(),
|
||||
modifier.getItalic(),
|
||||
modifier.getItalic(),
|
||||
modifier.getUnderlined(),
|
||||
modifier.getObfuscated(),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
modifier.getFont() == null ? null : ResourceLocation_tryParse.invoke(null, modifier.getFont())
|
||||
);
|
||||
} else {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.component.*;
|
||||
import me.neznamy.tab.shared.features.PerWorldPlayerListConfiguration;
|
||||
import me.neznamy.tab.shared.features.PlaceholderManagerImpl;
|
||||
import me.neznamy.tab.shared.features.injection.PipelineInjector;
|
||||
@@ -52,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
|
||||
@@ -79,12 +82,32 @@ 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 */
|
||||
@Nullable
|
||||
private final ViaVersionImplementationProvider viaVersionImplementationProvider =
|
||||
ReflectionUtils.classExists("com.viaversion.viaversion.protocols.v1_20_2to1_20_3.Protocol1_20_2To1_20_3") ?
|
||||
new ViaVersionImplementationProvider(serverVersion) : null;
|
||||
|
||||
/**
|
||||
* Constructs new instance with given plugin.
|
||||
*
|
||||
* @param plugin
|
||||
* Plugin
|
||||
*/
|
||||
@SneakyThrows
|
||||
public BukkitPlatform(@NotNull JavaPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
long time = System.currentTimeMillis();
|
||||
@@ -97,21 +120,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));
|
||||
}
|
||||
}
|
||||
@@ -119,20 +168,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();
|
||||
}
|
||||
|
||||
@@ -156,7 +203,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
|
||||
@@ -166,34 +213,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);
|
||||
@@ -232,7 +276,7 @@ public class BukkitPlatform implements BackendPlatform {
|
||||
command.setExecutor(cmd);
|
||||
command.setTabCompleter(cmd);
|
||||
} else {
|
||||
logWarn(new SimpleTextComponent("Failed to register command, is it defined in plugin.yml?"));
|
||||
logWarn(SimpleTextComponent.text("Failed to register command, is it defined in plugin.yml?"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -254,8 +298,8 @@ public class BukkitPlatform implements BackendPlatform {
|
||||
@Override
|
||||
@NotNull
|
||||
public Object convertComponent(@NotNull TabComponent component) {
|
||||
if (ComponentConverter.INSTANCE != null) {
|
||||
return ComponentConverter.INSTANCE.convert(component);
|
||||
if (serverImplementationProvider.getComponentConverter() != null) {
|
||||
return serverImplementationProvider.getComponentConverter().convert(component);
|
||||
} else {
|
||||
return component;
|
||||
}
|
||||
@@ -265,7 +309,11 @@ public class BukkitPlatform implements BackendPlatform {
|
||||
@NotNull
|
||||
@SneakyThrows
|
||||
public Scoreboard createScoreboard(@NotNull TabPlayer player) {
|
||||
return ScoreboardLoader.getInstance().apply((BukkitTabPlayer) player);
|
||||
if (viaVersionImplementationProvider != null) {
|
||||
Scoreboard scoreboard = viaVersionImplementationProvider.newScoreboard((BukkitTabPlayer) player);
|
||||
if (scoreboard != null) return scoreboard;
|
||||
}
|
||||
return serverImplementationProvider.newScoreboard((BukkitTabPlayer) player);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -288,17 +336,11 @@ 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();
|
||||
if (viaVersionImplementationProvider != null) {
|
||||
TabList tabList = viaVersionImplementationProvider.newTabList((BukkitTabPlayer) player);
|
||||
if (tabList != null) return tabList;
|
||||
}
|
||||
return serverImplementationProvider.newTabList((BukkitTabPlayer) player);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -391,4 +433,20 @@ public class BukkitPlatform implements BackendPlatform {
|
||||
}
|
||||
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));
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import me.clip.placeholderapi.PlaceholderAPI;
|
||||
import me.neznamy.tab.platforms.bukkit.features.PerWorldPlayerList;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.chat.component.SimpleTextComponent;
|
||||
import me.neznamy.chat.component.SimpleTextComponent;
|
||||
import me.neznamy.tab.shared.cpu.TimedCaughtTask;
|
||||
import me.neznamy.tab.shared.placeholders.types.PlayerPlaceholderImpl;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
@@ -38,7 +38,7 @@ public class FoliaPlatform extends BukkitPlatform {
|
||||
super.loadPlayers();
|
||||
|
||||
// Values are never updated in the API, warn users
|
||||
logWarn(new SimpleTextComponent("Folia never updates MSPT and TPS values in the API, making " +
|
||||
logWarn(SimpleTextComponent.text("Folia never updates MSPT and TPS values in the API, making " +
|
||||
"%mspt% and %tps% return the default values (0 and 20)."));
|
||||
|
||||
// Folia never calls PlayerChangedWorldEvent, this is a workaround
|
||||
@@ -57,10 +57,10 @@ public class FoliaPlatform extends BukkitPlatform {
|
||||
}
|
||||
|
||||
@Override
|
||||
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 -> {
|
||||
runSync((Entity) p.getPlayer(), () -> {
|
||||
long time = System.nanoTime();
|
||||
String output = isPlaceholderAPI() ? PlaceholderAPI.setPlaceholders((Player) p.getPlayer(), syncedPlaceholder) : identifier;
|
||||
|
||||
@@ -0,0 +1,175 @@
|
||||
package me.neznamy.tab.platforms.bukkit.provider;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import io.netty.channel.Channel;
|
||||
import lombok.Getter;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitUtils;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.converter.ComponentConverter;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.converter.LegacyComponentConverter;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.converter.ModerateComponentConverter;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.converter.ModernComponentConverter;
|
||||
import me.neznamy.tab.platforms.bukkit.scoreboard.BukkitScoreboard;
|
||||
import me.neznamy.tab.platforms.bukkit.scoreboard.PaperScoreboard;
|
||||
import me.neznamy.tab.platforms.bukkit.scoreboard.packet.PacketScoreboard;
|
||||
import me.neznamy.tab.platforms.bukkit.tablist.BukkitTabList;
|
||||
import me.neznamy.tab.platforms.bukkit.tablist.PacketTabList1193;
|
||||
import me.neznamy.tab.platforms.bukkit.tablist.PacketTabList17;
|
||||
import me.neznamy.tab.platforms.bukkit.tablist.PacketTabList18;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import me.neznamy.tab.shared.platform.impl.DummyScoreboard;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import me.neznamy.tab.shared.util.function.FunctionWithException;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Implementation provider using reflection for NMS / Bukkit implementations as fallback options.
|
||||
*/
|
||||
@Getter
|
||||
public class BukkitImplementationProvider implements ImplementationProvider {
|
||||
|
||||
@Nullable
|
||||
private final FunctionWithException<BukkitTabPlayer, Channel> channelFunction = findChannelFunction();
|
||||
|
||||
@Nullable
|
||||
private final ComponentConverter componentConverter = findComponentConverter();
|
||||
|
||||
@NotNull
|
||||
private final Function<BukkitTabPlayer, Scoreboard> scoreboardProvider = findScoreboardProvider();
|
||||
|
||||
@NotNull
|
||||
private final Function<BukkitTabPlayer, TabList> tablistProvider = findTablistProvider();
|
||||
|
||||
@Nullable
|
||||
private FunctionWithException<BukkitTabPlayer, Channel> findChannelFunction() {
|
||||
if (BukkitReflection.getMinorVersion() < 8) return null;
|
||||
try {
|
||||
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);
|
||||
return 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");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@SneakyThrows
|
||||
private ComponentConverter findComponentConverter() {
|
||||
try {
|
||||
if (BukkitReflection.getMinorVersion() >= 19) {
|
||||
// 1.19+
|
||||
return new ModernComponentConverter();
|
||||
} else if (BukkitReflection.getMinorVersion() >= 16) {
|
||||
// 1.16 - 1.18.2
|
||||
return new ModerateComponentConverter();
|
||||
} else {
|
||||
// 1.7 - 1.15.2
|
||||
return new LegacyComponentConverter();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Bukkit.getConsoleSender().sendMessage("§c[TAB] Failed to initialize converter from TAB components to Minecraft components. " +
|
||||
"This will negatively impact most features, see below.");
|
||||
if (BukkitUtils.PRINT_EXCEPTIONS) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private Function<BukkitTabPlayer, Scoreboard> findScoreboardProvider() {
|
||||
try {
|
||||
if (BukkitReflection.getMinorVersion() >= 7) Objects.requireNonNull(componentConverter);
|
||||
PacketScoreboard.load();
|
||||
return PacketScoreboard::new;
|
||||
} catch (Exception e) {
|
||||
if (PaperScoreboard.isAvailable()) {
|
||||
BukkitUtils.compatibilityError(e, "Scoreboards", "Paper API", "Compatibility with other plugins being reduced");
|
||||
return PaperScoreboard::new;
|
||||
} else if (BukkitScoreboard.isAvailable()) {
|
||||
List<String> missingFeatures = Lists.newArrayList(
|
||||
"Compatibility with other plugins being reduced",
|
||||
"Features receiving new artificial character limits"
|
||||
);
|
||||
if (BukkitReflection.is1_20_3Plus()) {
|
||||
missingFeatures.add("1.20.3+ visuals not working due to lack of API"); // soontm?
|
||||
}
|
||||
BukkitUtils.compatibilityError(e, "Scoreboards", "Bukkit API", missingFeatures.toArray(new String[0]));
|
||||
return BukkitScoreboard::new;
|
||||
} else if (BukkitReflection.getMinorVersion() >= 5) {
|
||||
BukkitUtils.compatibilityError(e, "Scoreboards", null,
|
||||
"Scoreboard feature will not work",
|
||||
"Belowname feature will not work",
|
||||
"Player objective feature will not work",
|
||||
"Scoreboard teams feature will not work (nametags & sorting)");
|
||||
}
|
||||
}
|
||||
return DummyScoreboard::new;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@SneakyThrows
|
||||
private Function<BukkitTabPlayer, TabList> findTablistProvider() {
|
||||
try {
|
||||
if (ReflectionUtils.classExists("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket")) {
|
||||
// 1.19.3+
|
||||
Objects.requireNonNull(componentConverter);
|
||||
PacketTabList1193.loadNew();
|
||||
return PacketTabList1193::new;
|
||||
} else if (BukkitReflection.getMinorVersion() >= 8) {
|
||||
// 1.8 - 1.19.2
|
||||
Objects.requireNonNull(componentConverter);
|
||||
PacketTabList18.load();
|
||||
return PacketTabList18::new;
|
||||
} else {
|
||||
// 1.7.10 and lower
|
||||
PacketTabList17.load();
|
||||
return PacketTabList17::new;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
BukkitUtils.compatibilityError(e, "tablist entry management", "Bukkit API",
|
||||
"Layout feature will not work",
|
||||
"Prevent-spectator-effect feature will not work",
|
||||
"Ping spoof feature will not work",
|
||||
"Tablist formatting missing anti-override",
|
||||
"Tablist formatting not supporting relational placeholders");
|
||||
return BukkitTabList::new;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Scoreboard newScoreboard(@NotNull BukkitTabPlayer player) {
|
||||
return scoreboardProvider.apply(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public TabList newTabList(@NotNull BukkitTabPlayer player) {
|
||||
return tablistProvider.apply(player);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package me.neznamy.tab.platforms.bukkit.provider;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.converter.ComponentConverter;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import me.neznamy.tab.shared.util.function.FunctionWithException;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public interface ImplementationProvider {
|
||||
|
||||
@NotNull
|
||||
Scoreboard newScoreboard(@NotNull BukkitTabPlayer player);
|
||||
|
||||
@NotNull
|
||||
TabList newTabList(@NotNull BukkitTabPlayer player);
|
||||
|
||||
@Nullable
|
||||
ComponentConverter getComponentConverter();
|
||||
|
||||
@Nullable
|
||||
FunctionWithException<BukkitTabPlayer, Channel> getChannelFunction();
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package me.neznamy.tab.platforms.bukkit.provider;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.scoreboard.viaversion.ViaScoreboard113;
|
||||
import me.neznamy.tab.platforms.bukkit.scoreboard.viaversion.ViaScoreboard116;
|
||||
import me.neznamy.tab.platforms.bukkit.scoreboard.viaversion.ViaScoreboard1203;
|
||||
import me.neznamy.tab.platforms.bukkit.tablist.viaversion.ViaTabList116;
|
||||
import me.neznamy.tab.platforms.bukkit.tablist.viaversion.ViaTabList1193;
|
||||
import me.neznamy.tab.platforms.bukkit.tablist.viaversion.ViaTabList1212;
|
||||
import me.neznamy.tab.platforms.bukkit.tablist.viaversion.ViaTabList1214;
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Implementation for sending new features on old servers using ViaVersion API.
|
||||
* If no implementation is required for a player, {@code null} is returned.
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class ViaVersionImplementationProvider {
|
||||
|
||||
/** Map of tablist implementations by each new version */
|
||||
private static final Map<ProtocolVersion, Function<BukkitTabPlayer, TabList>> TABLIST_IMPLEMENTATIONS = new LinkedHashMap<>();
|
||||
|
||||
/** Map of scoreboard implementations by each new version */
|
||||
private static final Map<ProtocolVersion, Function<BukkitTabPlayer, Scoreboard>> SCOREBOARD_IMPLEMENTATIONS = new LinkedHashMap<>();
|
||||
|
||||
static {
|
||||
TABLIST_IMPLEMENTATIONS.put(ProtocolVersion.V1_21_4, ViaTabList1214::new);
|
||||
TABLIST_IMPLEMENTATIONS.put(ProtocolVersion.V1_21_2, ViaTabList1212::new);
|
||||
TABLIST_IMPLEMENTATIONS.put(ProtocolVersion.V1_19_3, ViaTabList1193::new);
|
||||
TABLIST_IMPLEMENTATIONS.put(ProtocolVersion.V1_16, ViaTabList116::new);
|
||||
|
||||
SCOREBOARD_IMPLEMENTATIONS.put(ProtocolVersion.V1_20_3, ViaScoreboard1203::new);
|
||||
SCOREBOARD_IMPLEMENTATIONS.put(ProtocolVersion.V1_16, ViaScoreboard116::new);
|
||||
SCOREBOARD_IMPLEMENTATIONS.put(ProtocolVersion.V1_13, ViaScoreboard113::new);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private final ProtocolVersion serverVersion;
|
||||
|
||||
@Nullable
|
||||
public Scoreboard newScoreboard(@NotNull BukkitTabPlayer player) {
|
||||
int serverVer = serverVersion.getNetworkId();
|
||||
int playerVer = player.getVersion().getNetworkId();
|
||||
for (Map.Entry<ProtocolVersion, Function<BukkitTabPlayer, Scoreboard>> entry : SCOREBOARD_IMPLEMENTATIONS.entrySet()) {
|
||||
if (serverVer < entry.getKey().getNetworkId() && playerVer >= entry.getKey().getNetworkId()) {
|
||||
return entry.getValue().apply(player);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public TabList newTabList(@NotNull BukkitTabPlayer player) {
|
||||
int serverVer = serverVersion.getNetworkId();
|
||||
int playerVer = player.getVersion().getNetworkId();
|
||||
for (Map.Entry<ProtocolVersion, Function<BukkitTabPlayer, TabList>> entry : TABLIST_IMPLEMENTATIONS.entrySet()) {
|
||||
if (serverVer < entry.getKey().getNetworkId() && playerVer >= entry.getKey().getNetworkId()) {
|
||||
return entry.getValue().apply(player);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
|
||||
import me.neznamy.tab.shared.Limitations;
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.shared.chat.component.TabComponent;
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import me.neznamy.tab.shared.platform.decorators.SafeScoreboard;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.bukkit.Bukkit;
|
||||
@@ -41,7 +41,7 @@ import java.util.concurrent.CountDownLatch;
|
||||
* making per-player view of teams, especially sidebar not working.
|
||||
*/
|
||||
// Throw the NPE if something is not as expected, parent class should ensure it anyway
|
||||
@SuppressWarnings({"deprecation", "DataFlowIssue"})
|
||||
@SuppressWarnings("deprecation")
|
||||
public class BukkitScoreboard extends SafeScoreboard<BukkitTabPlayer> {
|
||||
|
||||
/** Version in which team colors were added */
|
||||
@@ -100,10 +100,16 @@ public class BukkitScoreboard extends SafeScoreboard<BukkitTabPlayer> {
|
||||
checkPlayerScoreboard();
|
||||
org.bukkit.scoreboard.Objective obj = newObjective(objective.getName(), "dummy", objective.getTitle(), objective.getHealthDisplay());
|
||||
setObjectiveNumberFormat(obj, objective.getNumberFormat());
|
||||
obj.setDisplaySlot(slots[objective.getDisplaySlot().ordinal()]);
|
||||
objective.setPlatformObjective(obj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDisplaySlot(@NonNull Objective objective) {
|
||||
checkPlayerScoreboard();
|
||||
org.bukkit.scoreboard.Objective obj = (org.bukkit.scoreboard.Objective) objective.getPlatformObjective();
|
||||
obj.setDisplaySlot(slots[objective.getDisplaySlot().ordinal()]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterObjective(@NonNull Objective objective) {
|
||||
checkPlayerScoreboard();
|
||||
|
||||
@@ -4,7 +4,7 @@ import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.shared.chat.component.TabComponent;
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.ComponentLike;
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.scoreboard;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitUtils;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
|
||||
import me.neznamy.tab.platforms.bukkit.scoreboard.packet.PacketScoreboard;
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard;
|
||||
import me.neznamy.tab.shared.platform.impl.DummyScoreboard;
|
||||
import me.neznamy.tab.shared.util.function.FunctionWithException;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Loader finding the best available Scoreboard implementation.
|
||||
*/
|
||||
public class ScoreboardLoader {
|
||||
|
||||
/** Versions supported by paper module that uses direct mojang-mapped NMS for latest MC version */
|
||||
private static final EnumSet<ProtocolVersion> paperNativeVersions = EnumSet.of(
|
||||
ProtocolVersion.V1_20_5,
|
||||
ProtocolVersion.V1_20_6,
|
||||
ProtocolVersion.V1_21,
|
||||
ProtocolVersion.V1_21_1,
|
||||
ProtocolVersion.V1_21_2,
|
||||
ProtocolVersion.V1_21_3,
|
||||
ProtocolVersion.V1_21_4
|
||||
);
|
||||
|
||||
/** Instance function */
|
||||
@Getter
|
||||
@Setter
|
||||
@NotNull
|
||||
private static FunctionWithException<BukkitTabPlayer, Scoreboard> instance = DummyScoreboard::new;
|
||||
|
||||
/**
|
||||
* Finds the best available instance for current server software.
|
||||
*
|
||||
* @param serverVersion
|
||||
* Server version
|
||||
*/
|
||||
@SneakyThrows
|
||||
public static void findInstance(@NotNull ProtocolVersion serverVersion) {
|
||||
if (ReflectionUtils.classExists("org.bukkit.craftbukkit.CraftServer") && paperNativeVersions.contains(serverVersion)) {
|
||||
Constructor<?> constructor = Class.forName("me.neznamy.tab.platforms.paper.PaperPacketScoreboard").getConstructor(BukkitTabPlayer.class);
|
||||
instance = player -> (Scoreboard) constructor.newInstance(player);
|
||||
} else if (PacketScoreboard.isAvailable()) {
|
||||
instance = PacketScoreboard::new;
|
||||
} else if (PaperScoreboard.isAvailable()) {
|
||||
instance = PaperScoreboard::new;
|
||||
BukkitUtils.compatibilityError(PacketScoreboard.getException(), "Scoreboards", "Paper API", "Compatibility with other plugins being reduced");
|
||||
} else if (BukkitScoreboard.isAvailable()) {
|
||||
instance = BukkitScoreboard::new;
|
||||
List<String> missingFeatures = Lists.newArrayList(
|
||||
"Compatibility with other plugins being reduced",
|
||||
"Features receiving new artificial character limits"
|
||||
);
|
||||
if (BukkitReflection.is1_20_3Plus()) {
|
||||
missingFeatures.add("1.20.3+ visuals not working due to lack of API"); // soontm?
|
||||
}
|
||||
BukkitUtils.compatibilityError(PacketScoreboard.getException(), "Scoreboards", "Bukkit API", missingFeatures.toArray(new String[0]));
|
||||
} else {
|
||||
BukkitUtils.compatibilityError(PacketScoreboard.getException(), "Scoreboards", null,
|
||||
"Scoreboard feature will not work",
|
||||
"Belowname feature will not work",
|
||||
"Player objective feature will not work",
|
||||
"Scoreboard teams feature will not work (nametags & sorting)");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,13 +3,12 @@ package me.neznamy.tab.platforms.bukkit.scoreboard.packet;
|
||||
import lombok.Getter;
|
||||
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.nms.BukkitReflection;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.converter.ComponentConverter;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.PacketSender;
|
||||
import me.neznamy.tab.shared.Limitations;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.chat.component.TabComponent;
|
||||
import me.neznamy.tab.shared.platform.decorators.SafeScoreboard;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -18,6 +17,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@@ -27,12 +27,6 @@ import java.util.List;
|
||||
*/
|
||||
public class PacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
|
||||
|
||||
@Getter
|
||||
private static boolean available;
|
||||
|
||||
@Getter
|
||||
private static Exception exception;
|
||||
|
||||
static Class<?> Component;
|
||||
static Class<?> Scoreboard;
|
||||
static Class<?> ScoreboardObjective;
|
||||
@@ -56,60 +50,54 @@ public class PacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
|
||||
@Getter private static DisplayPacketData displayPacketData;
|
||||
private static PacketSender packetSender;
|
||||
|
||||
static {
|
||||
try {
|
||||
int minorVersion = BukkitReflection.getMinorVersion();
|
||||
Scoreboard = BukkitReflection.getClass("world.scores.Scoreboard", "Scoreboard");
|
||||
ScoreboardObjective = BukkitReflection.getClass("world.scores.Objective", "world.scores.ScoreboardObjective", "ScoreboardObjective");
|
||||
Class<?> IScoreboardCriteria = BukkitReflection.getClass(
|
||||
"world.scores.criteria.ObjectiveCriteria", // Mojang mapped
|
||||
"world.scores.criteria.IScoreboardCriteria", // Bukkit 1.17.+
|
||||
"IScoreboardCriteria", // 1.5.1 - 1.16.5
|
||||
"IObjective" // 1.5.0
|
||||
);
|
||||
ObjectivePacketClass = BukkitReflection.getClass(
|
||||
"network.protocol.game.ClientboundSetObjectivePacket", // Mojang mapped
|
||||
"network.protocol.game.PacketPlayOutScoreboardObjective", // Bukkit 1.17+
|
||||
"PacketPlayOutScoreboardObjective", // 1.7 - 1.16.5
|
||||
"Packet206SetScoreboardObjective" // 1.5 - 1.6.4
|
||||
);
|
||||
emptyScoreboard = Scoreboard.getConstructor().newInstance();
|
||||
Objective_OBJECTIVE_NAME = ReflectionUtils.getFields(ObjectivePacketClass, String.class).get(0);
|
||||
List<Field> list = ReflectionUtils.getFields(ObjectivePacketClass, int.class);
|
||||
Objective_METHOD = list.get(list.size()-1);
|
||||
newObjectivePacket = ObjectivePacketClass.getConstructor(ScoreboardObjective, int.class);
|
||||
IScoreboardCriteria_dummy = ReflectionUtils.getFields(IScoreboardCriteria, IScoreboardCriteria).get(0).get(null);
|
||||
newScoreboardObjective = ReflectionUtils.getOnlyConstructor(ScoreboardObjective);
|
||||
if (minorVersion >= 7) {
|
||||
Component = BukkitReflection.getClass("network.chat.Component", "network.chat.IChatBaseComponent", "IChatBaseComponent");
|
||||
if (ComponentConverter.INSTANCE == null) throw new IllegalStateException("Component converter is not available");
|
||||
}
|
||||
if (minorVersion >= 8) {
|
||||
Class<?> EnumScoreboardHealthDisplay = BukkitReflection.getClass(
|
||||
"world.scores.criteria.ObjectiveCriteria$RenderType",
|
||||
"world.scores.criteria.IScoreboardCriteria$EnumScoreboardHealthDisplay",
|
||||
"IScoreboardCriteria$EnumScoreboardHealthDisplay",
|
||||
"EnumScoreboardHealthDisplay");
|
||||
healthDisplays = (Enum<?>[]) EnumScoreboardHealthDisplay.getMethod("values").invoke(null);
|
||||
if (minorVersion < 13) {
|
||||
Objective_RENDER_TYPE = ReflectionUtils.getOnlyField(ObjectivePacketClass, EnumScoreboardHealthDisplay);
|
||||
}
|
||||
}
|
||||
if (minorVersion < 13) {
|
||||
ScoreboardObjective_setDisplayName = ReflectionUtils.getOnlyMethod(ScoreboardObjective, void.class, String.class);
|
||||
}
|
||||
if (BukkitReflection.is1_20_3Plus()) {
|
||||
NumberFormat = BukkitReflection.getClass("network.chat.numbers.NumberFormat");
|
||||
newFixedFormat = BukkitReflection.getClass("network.chat.numbers.FixedFormat").getConstructor(Component);
|
||||
}
|
||||
scorePacketData = new ScorePacketData();
|
||||
teamPacketData = new TeamPacketData();
|
||||
displayPacketData = new DisplayPacketData();
|
||||
packetSender = new PacketSender();
|
||||
available = true;
|
||||
} catch (Exception e) {
|
||||
exception = e;
|
||||
public static void load() throws ReflectiveOperationException {
|
||||
int minorVersion = BukkitReflection.getMinorVersion();
|
||||
Scoreboard = BukkitReflection.getClass("world.scores.Scoreboard", "Scoreboard");
|
||||
ScoreboardObjective = BukkitReflection.getClass("world.scores.Objective", "world.scores.ScoreboardObjective", "ScoreboardObjective");
|
||||
Class<?> IScoreboardCriteria = BukkitReflection.getClass(
|
||||
"world.scores.criteria.ObjectiveCriteria", // Mojang mapped
|
||||
"world.scores.criteria.IScoreboardCriteria", // Bukkit 1.17.+
|
||||
"IScoreboardCriteria", // 1.5.1 - 1.16.5
|
||||
"IObjective" // 1.5.0
|
||||
);
|
||||
ObjectivePacketClass = BukkitReflection.getClass(
|
||||
"network.protocol.game.ClientboundSetObjectivePacket", // Mojang mapped
|
||||
"network.protocol.game.PacketPlayOutScoreboardObjective", // Bukkit 1.17+
|
||||
"PacketPlayOutScoreboardObjective", // 1.7 - 1.16.5
|
||||
"Packet206SetScoreboardObjective" // 1.5 - 1.6.4
|
||||
);
|
||||
emptyScoreboard = Scoreboard.getConstructor().newInstance();
|
||||
Objective_OBJECTIVE_NAME = ReflectionUtils.getFields(ObjectivePacketClass, String.class).get(0);
|
||||
List<Field> list = ReflectionUtils.getFields(ObjectivePacketClass, int.class);
|
||||
Objective_METHOD = list.get(list.size()-1);
|
||||
newObjectivePacket = ObjectivePacketClass.getConstructor(ScoreboardObjective, int.class);
|
||||
IScoreboardCriteria_dummy = ReflectionUtils.getFields(IScoreboardCriteria, IScoreboardCriteria).get(0).get(null);
|
||||
newScoreboardObjective = ReflectionUtils.getOnlyConstructor(ScoreboardObjective);
|
||||
if (minorVersion >= 7) {
|
||||
Component = BukkitReflection.getClass("network.chat.Component", "network.chat.IChatBaseComponent", "IChatBaseComponent");
|
||||
}
|
||||
if (minorVersion >= 8) {
|
||||
Class<?> EnumScoreboardHealthDisplay = BukkitReflection.getClass(
|
||||
"world.scores.criteria.ObjectiveCriteria$RenderType",
|
||||
"world.scores.criteria.IScoreboardCriteria$EnumScoreboardHealthDisplay",
|
||||
"IScoreboardCriteria$EnumScoreboardHealthDisplay",
|
||||
"EnumScoreboardHealthDisplay");
|
||||
healthDisplays = (Enum<?>[]) EnumScoreboardHealthDisplay.getMethod("values").invoke(null);
|
||||
if (minorVersion < 13) {
|
||||
Objective_RENDER_TYPE = ReflectionUtils.getOnlyField(ObjectivePacketClass, EnumScoreboardHealthDisplay);
|
||||
}
|
||||
}
|
||||
if (minorVersion < 13) {
|
||||
ScoreboardObjective_setDisplayName = ReflectionUtils.getOnlyMethod(ScoreboardObjective, void.class, String.class);
|
||||
}
|
||||
if (BukkitReflection.is1_20_3Plus()) {
|
||||
NumberFormat = BukkitReflection.getClass("network.chat.numbers.NumberFormat");
|
||||
newFixedFormat = BukkitReflection.getClass("network.chat.numbers.FixedFormat").getConstructor(Component);
|
||||
}
|
||||
scorePacketData = new ScorePacketData();
|
||||
teamPacketData = new TeamPacketData();
|
||||
displayPacketData = new DisplayPacketData();
|
||||
packetSender = new PacketSender();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -125,6 +113,10 @@ public class PacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
|
||||
@Override
|
||||
public void registerObjective(@NonNull Objective objective) {
|
||||
packetSender.sendPacket(player, newObjectivePacket(ObjectiveAction.REGISTER, objective));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDisplaySlot(@NonNull Objective objective) {
|
||||
packetSender.sendPacket(player, displayPacketData.setDisplaySlot(objective.getDisplaySlot().ordinal(), newObjective(objective)));
|
||||
}
|
||||
|
||||
@@ -152,23 +144,30 @@ public class PacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
@SneakyThrows
|
||||
public Object createTeam(@NonNull String name) {
|
||||
return teamPacketData.createTeam(name);
|
||||
return teamPacketData.createTeam.apply(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void registerTeam(@NonNull Team team) {
|
||||
packetSender.sendPacket(player, teamPacketData.registerTeam(team, player.getVersion()));
|
||||
teamPacketData.ScoreboardTeam_players.set(team.getPlatformTeam(), new HashSet<>(team.getPlayers()));
|
||||
teamPacketData.updateTeamData(team, player.getVersion());
|
||||
packetSender.sendPacket(player, teamPacketData.newRegisterTeamPacket.apply(team, player.getVersion()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void unregisterTeam(@NonNull Team team) {
|
||||
packetSender.sendPacket(player, teamPacketData.unregisterTeam(team));
|
||||
packetSender.sendPacket(player, teamPacketData.newUnregisterTeamPacket.apply(team));
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void updateTeam(@NonNull Team team) {
|
||||
packetSender.sendPacket(player, teamPacketData.updateTeam(team, player.getVersion()));
|
||||
teamPacketData.updateTeamData(team, player.getVersion());
|
||||
packetSender.sendPacket(player, teamPacketData.newUpdateTeamPacket.apply(team, player.getVersion()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -232,6 +231,12 @@ public class PacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
|
||||
@SneakyThrows
|
||||
private static Object toFixedFormat(@NonNull TabComponent component) {
|
||||
if (newFixedFormat == null) return null;
|
||||
return component.toFixedFormat(nmsComponent -> newFixedFormat.newInstance(nmsComponent));
|
||||
return component.toFixedFormat(PacketScoreboard::convertFixedFormat);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@SneakyThrows
|
||||
private static Object convertFixedFormat(@NotNull Object nmsComponent) {
|
||||
return newFixedFormat.newInstance(nmsComponent);
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,20 @@
|
||||
package me.neznamy.tab.platforms.bukkit.scoreboard.packet;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
|
||||
import me.neznamy.tab.shared.Limitations;
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.platform.decorators.SafeScoreboard;
|
||||
import me.neznamy.tab.shared.platform.decorators.SafeScoreboard.Team;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard.TeamAction;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import me.neznamy.tab.shared.util.function.BiConsumerWithException;
|
||||
import me.neznamy.tab.shared.platform.decorators.SafeScoreboard;
|
||||
import me.neznamy.tab.shared.platform.decorators.SafeScoreboard.Team;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import me.neznamy.tab.shared.util.function.BiConsumerWithException;
|
||||
import me.neznamy.tab.shared.util.function.BiFunctionWithException;
|
||||
import me.neznamy.tab.shared.util.function.ConsumerWithException;
|
||||
import me.neznamy.tab.shared.util.function.FunctionWithException;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
@@ -22,6 +23,7 @@ import java.lang.reflect.Method;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Class storing all team related fields and methods.
|
||||
@@ -29,33 +31,45 @@ import java.util.List;
|
||||
@SuppressWarnings("unchecked")
|
||||
public class TeamPacketData {
|
||||
|
||||
/** Flag tracking if server uses modern team data using components */
|
||||
private final boolean MODERN_TEAM_DATA_VERSION = BukkitReflection.getMinorVersion() >= 13;
|
||||
// Classes
|
||||
private final Class<?> ScoreboardClass = BukkitReflection.getClass("world.scores.Scoreboard", "Scoreboard");
|
||||
private final Class<?> ScoreboardTeamClass = BukkitReflection.getClass("world.scores.PlayerTeam", "world.scores.ScoreboardTeam", "ScoreboardTeam");
|
||||
private final Class<?> TeamPacketClass = BukkitReflection.getClass(
|
||||
"network.protocol.game.ClientboundSetPlayerTeamPacket", // Mojang mapped
|
||||
"network.protocol.game.PacketPlayOutScoreboardTeam", // Bukkit 1.17+
|
||||
"PacketPlayOutScoreboardTeam", // Bukkit 1.7 - 1.16.5
|
||||
"Packet209SetScoreboardTeam" // 1.5 - 1.6.4
|
||||
);
|
||||
|
||||
/** Flag tracking if server uses static constructor-like methods */
|
||||
private final boolean STATIC_CONSTRUCTOR_VERSION = BukkitReflection.getMinorVersion() >= 17;
|
||||
// Constructors
|
||||
private final Constructor<?> newScoreboardTeam = ScoreboardTeamClass.getConstructor(ScoreboardClass, String.class);
|
||||
|
||||
private final Object emptyScoreboard;
|
||||
@Getter private final Class<?> TeamPacketClass;
|
||||
private Constructor<?> newTeamPacket;
|
||||
private final Constructor<?> newScoreboardTeam;
|
||||
private Method TeamPacketConstructor_of;
|
||||
private Method TeamPacketConstructor_ofBoolean;
|
||||
private final Field TeamPacket_NAME;
|
||||
// Fields
|
||||
public final Field ScoreboardTeam_players = ReflectionUtils.getFields(ScoreboardTeamClass, Set.class).get(0);
|
||||
private final Field ScoreboardTeam_allowFriendlyFire = ReflectionUtils.getFields(ScoreboardTeamClass, boolean.class).get(0);
|
||||
private final Field ScoreboardTeam_seeFriendlyInvisibles = ReflectionUtils.getFields(ScoreboardTeamClass, boolean.class).get(1);
|
||||
private final Field TeamPacket_NAME = ReflectionUtils.getFields(TeamPacketClass, String.class).get(0);
|
||||
private final Field TeamPacket_ACTION;
|
||||
private final Field TeamPacket_PLAYERS;
|
||||
private final Method ScoreboardTeam_setPrefix;
|
||||
private final Method ScoreboardTeam_setSuffix;
|
||||
private Method ScoreboardTeam_setColor;
|
||||
private final Method ScoreboardTeam_setAllowFriendlyFire;
|
||||
private final Method ScoreboardTeam_setCanSeeFriendlyInvisibles;
|
||||
private final Enum<?>[] chatFormats;
|
||||
private final Field TeamPacket_PLAYERS = ReflectionUtils.getOnlyField(TeamPacketClass, Collection.class);
|
||||
|
||||
private BiConsumerWithException<Object, Scoreboard.NameVisibility> setVisibility = (team, visibility) -> {};
|
||||
private BiConsumerWithException<Object, Scoreboard.CollisionRule> setCollision = (team, collision) -> {};
|
||||
// Objects
|
||||
private final Object emptyScoreboard = ScoreboardClass.getConstructor().newInstance();
|
||||
|
||||
// Team / packet creation
|
||||
public final FunctionWithException<String, Object> createTeam = name -> newScoreboardTeam.newInstance(emptyScoreboard, name);
|
||||
public final BiFunctionWithException<Team, ProtocolVersion, Object> newRegisterTeamPacket;
|
||||
public final FunctionWithException<Team, Object> newUnregisterTeamPacket;
|
||||
public final BiFunctionWithException<Team, ProtocolVersion, Object> newUpdateTeamPacket;
|
||||
|
||||
// Team property setters
|
||||
private final BiConsumerWithException<Team, ProtocolVersion> updatePlatformTeamPrefix = loadPrefix();
|
||||
private final BiConsumerWithException<Team, ProtocolVersion> updatePlatformTeamSuffix = loadSuffix();
|
||||
private final ConsumerWithException<Team> updatePlatformTeamColor = loadColor();
|
||||
private final ConsumerWithException<Team> updatePlatformTeamVisibility = loadVisibility();
|
||||
private final ConsumerWithException<Team> updatePlatformTeamCollision = loadCollision();
|
||||
|
||||
/**
|
||||
* Constructs new instance and loads all required NMS classes, fields and methods.
|
||||
* Constructs new instance and loads all required NMS classes, fields, and methods.
|
||||
* If anything fails, exception is thrown.
|
||||
*
|
||||
* @throws ReflectiveOperationException
|
||||
@@ -63,151 +77,108 @@ public class TeamPacketData {
|
||||
*/
|
||||
public TeamPacketData() throws ReflectiveOperationException {
|
||||
int minorVersion = BukkitReflection.getMinorVersion();
|
||||
Class<?> Scoreboard = BukkitReflection.getClass("world.scores.Scoreboard", "Scoreboard");
|
||||
Class<?> scoreboardTeam = BukkitReflection.getClass("world.scores.PlayerTeam", "world.scores.ScoreboardTeam", "ScoreboardTeam");
|
||||
Class<?> enumChatFormatClass = BukkitReflection.getClass("ChatFormatting", "EnumChatFormat");
|
||||
TeamPacketClass = BukkitReflection.getClass(
|
||||
"network.protocol.game.ClientboundSetPlayerTeamPacket", // Mojang mapped
|
||||
"network.protocol.game.PacketPlayOutScoreboardTeam", // Bukkit 1.17+
|
||||
"PacketPlayOutScoreboardTeam", // Bukkit 1.7 - 1.16.5
|
||||
"Packet209SetScoreboardTeam" // 1.5 - 1.6.4
|
||||
);
|
||||
emptyScoreboard = Scoreboard.getConstructor().newInstance();
|
||||
newScoreboardTeam = scoreboardTeam.getConstructor(Scoreboard, String.class);
|
||||
TeamPacket_NAME = ReflectionUtils.getFields(TeamPacketClass, String.class).get(0);
|
||||
List<Field> intFields = ReflectionUtils.getInstanceFields(TeamPacketClass, int.class);
|
||||
if (minorVersion >= 8 && minorVersion <= 12) {
|
||||
TeamPacket_ACTION = intFields.get(1);
|
||||
} else {
|
||||
TeamPacket_ACTION = intFields.get(0);
|
||||
}
|
||||
TeamPacket_PLAYERS = ReflectionUtils.getOnlyField(TeamPacketClass, Collection.class);
|
||||
chatFormats = (Enum<?>[]) enumChatFormatClass.getMethod("values").invoke(null);
|
||||
ScoreboardTeam_setAllowFriendlyFire = ReflectionUtils.getMethod(
|
||||
scoreboardTeam,
|
||||
new String[]{"func_96660_a", "setAllowFriendlyFire", "a", "m_83355_"}, // {Thermos, 1.5.1+, 1.5 & 1.18+, Mohist 1.18.2}
|
||||
boolean.class
|
||||
);
|
||||
ScoreboardTeam_setCanSeeFriendlyInvisibles = ReflectionUtils.getMethod(
|
||||
scoreboardTeam,
|
||||
new String[]{"func_98300_b", "setCanSeeFriendlyInvisibles", "b", "m_83362_", "setSeeFriendlyInvisibles"}, // {Thermos, 1.5.1+, 1.5 & 1.18+, Mohist 1.18.2, 1.20.2+}
|
||||
boolean.class
|
||||
);
|
||||
if (minorVersion >= 8) loadVisibility(scoreboardTeam);
|
||||
if (minorVersion >= 9) loadCollision(scoreboardTeam);
|
||||
if (MODERN_TEAM_DATA_VERSION) {
|
||||
|
||||
// Packet constructors
|
||||
if (BukkitReflection.getMinorVersion() >= 17) {
|
||||
Method TeamPacketConstructor_of = ReflectionUtils.getOnlyMethod(TeamPacketClass, TeamPacketClass, ScoreboardTeamClass);
|
||||
Method TeamPacketConstructor_ofBoolean = ReflectionUtils.getOnlyMethod(TeamPacketClass, TeamPacketClass, ScoreboardTeamClass, boolean.class);
|
||||
newRegisterTeamPacket = (team, version) -> TeamPacketConstructor_ofBoolean.invoke(null, team.getPlatformTeam(), true);
|
||||
newUnregisterTeamPacket = team -> TeamPacketConstructor_of.invoke(null, team.getPlatformTeam());
|
||||
newUpdateTeamPacket = (team, version) -> TeamPacketConstructor_ofBoolean.invoke(null, team.getPlatformTeam(), false);
|
||||
} else {
|
||||
Constructor<?> newTeamPacket = TeamPacketClass.getConstructor(ScoreboardTeamClass, int.class);
|
||||
newRegisterTeamPacket = (team, version) -> newTeamPacket.newInstance(team.getPlatformTeam(), TeamAction.CREATE);
|
||||
newUnregisterTeamPacket = team -> newTeamPacket.newInstance(team.getPlatformTeam(), TeamAction.REMOVE);
|
||||
newUpdateTeamPacket = (team, version) -> newTeamPacket.newInstance(team.getPlatformTeam(), TeamAction.UPDATE);
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
@NotNull
|
||||
private BiConsumerWithException<Team, ProtocolVersion> loadPrefix() {
|
||||
if (BukkitReflection.getMinorVersion() >= 13) {
|
||||
Class<?> Component = BukkitReflection.getClass("network.chat.Component", "network.chat.IChatBaseComponent", "IChatBaseComponent");
|
||||
ScoreboardTeam_setColor = ReflectionUtils.getOnlyMethod(scoreboardTeam, void.class, enumChatFormatClass);
|
||||
ScoreboardTeam_setPrefix = ReflectionUtils.getMethod(
|
||||
scoreboardTeam,
|
||||
new String[]{"setPrefix", "b", "m_83360_", "setPlayerPrefix"}, // {1.17.1-, 1.18 - 1.20.1, Mohist 1.18.2, 1.20.2+}
|
||||
Component
|
||||
);
|
||||
ScoreboardTeam_setSuffix = ReflectionUtils.getMethod(
|
||||
scoreboardTeam,
|
||||
new String[]{"setSuffix", "c", "m_83365_", "setPlayerSuffix"}, // {1.17.1-, 1.18 - 1.20.1, Mohist 1.18.2, 1.20.2+}
|
||||
Component
|
||||
);
|
||||
Field prefix = ReflectionUtils.getFields(ScoreboardTeamClass, Component).get(1);
|
||||
return (team, version) -> prefix.set(team.getPlatformTeam(), team.getPrefix().convert());
|
||||
} else {
|
||||
ScoreboardTeam_setPrefix = ReflectionUtils.getMethod(
|
||||
scoreboardTeam,
|
||||
new String[]{"func_96666_b", "setPrefix", "b"}, // {Thermos, 1.5.1+, 1.5}
|
||||
String.class
|
||||
);
|
||||
ScoreboardTeam_setSuffix = ReflectionUtils.getMethod(
|
||||
scoreboardTeam,
|
||||
new String[]{"func_96662_c", "setSuffix", "c"}, // {Thermos, 1.5.1+, 1.5}
|
||||
String.class
|
||||
);
|
||||
}
|
||||
if (STATIC_CONSTRUCTOR_VERSION) {
|
||||
TeamPacketConstructor_of = ReflectionUtils.getOnlyMethod(TeamPacketClass, TeamPacketClass, scoreboardTeam);
|
||||
TeamPacketConstructor_ofBoolean = ReflectionUtils.getOnlyMethod(TeamPacketClass, TeamPacketClass, scoreboardTeam, boolean.class);
|
||||
} else {
|
||||
newTeamPacket = TeamPacketClass.getConstructor(scoreboardTeam, int.class);
|
||||
Field prefix = ReflectionUtils.getFields(ScoreboardTeamClass, String.class).get(2);
|
||||
return (team, version) -> {
|
||||
String legacy = team.getPrefix().toLegacyText();
|
||||
if (version.getMinorVersion() < 13 || TAB.getInstance().getConfiguration().getConfig().isPacketEventsCompensation()) {
|
||||
legacy = SafeScoreboard.cutTo(legacy, Limitations.TEAM_PREFIX_SUFFIX_PRE_1_13);
|
||||
}
|
||||
prefix.set(team.getPlatformTeam(), legacy);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private void loadVisibility(@NonNull Class<?> scoreboardTeam) {
|
||||
Class<?> enumNameTagVisibility = BukkitReflection.getClass(
|
||||
"world.scores.Team$Visibility", // Mojang mapped
|
||||
"world.scores.ScoreboardTeamBase$EnumNameTagVisibility", // Bukkit 1.17+
|
||||
"ScoreboardTeamBase$EnumNameTagVisibility", // Bukkit 1.8.1 - 1.16.5
|
||||
"EnumNameTagVisibility" // Bukkit 1.8.0
|
||||
);
|
||||
Enum<?>[] nameVisibilities = (Enum<?>[]) enumNameTagVisibility.getMethod("values").invoke(null);
|
||||
Method setNameTagVisibility = ReflectionUtils.getMethod(
|
||||
scoreboardTeam,
|
||||
new String[]{"setNameTagVisibility", "a", "m_83346_"}, // {1.8.1+, 1.8 & 1.18+, Mohist 1.18.2}
|
||||
enumNameTagVisibility
|
||||
);
|
||||
setVisibility = (team, visibility) -> setNameTagVisibility.invoke(team, nameVisibilities[visibility.ordinal()]);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private void loadCollision(@NonNull Class<?> scoreboardTeam) {
|
||||
Class<?> enumTeamPush = BukkitReflection.getClass("world.scores.Team$CollisionRule",
|
||||
"world.scores.ScoreboardTeamBase$EnumTeamPush", "ScoreboardTeamBase$EnumTeamPush");
|
||||
Enum<?>[] collisionRules = (Enum<?>[]) enumTeamPush.getMethod("values").invoke(null);
|
||||
Method setCollisionRule = ReflectionUtils.getOnlyMethod(scoreboardTeam, void.class, enumTeamPush);
|
||||
setCollision = (team, collision) -> setCollisionRule.invoke(team, collisionRules[collision.ordinal()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates team register packet with specified parameters.
|
||||
*
|
||||
* @param team
|
||||
* Team to register
|
||||
* @param clientVersion
|
||||
* Version to create the values for
|
||||
* @return Register team packet with specified parameters
|
||||
*/
|
||||
@SneakyThrows
|
||||
public Object registerTeam(@NonNull Team team, @NotNull ProtocolVersion clientVersion) {
|
||||
updateTeamData(team, clientVersion);
|
||||
Object packet;
|
||||
if (STATIC_CONSTRUCTOR_VERSION) {
|
||||
packet = TeamPacketConstructor_ofBoolean.invoke(null, team.getPlatformTeam(), true);
|
||||
@NotNull
|
||||
private BiConsumerWithException<Team, ProtocolVersion> loadSuffix() {
|
||||
if (BukkitReflection.getMinorVersion() >= 13) {
|
||||
Class<?> Component = BukkitReflection.getClass("network.chat.Component", "network.chat.IChatBaseComponent", "IChatBaseComponent");
|
||||
Field suffix = ReflectionUtils.getFields(ScoreboardTeamClass, Component).get(2);
|
||||
return (team, version) -> suffix.set(team.getPlatformTeam(), team.getSuffix().convert());
|
||||
} else {
|
||||
packet = newTeamPacket.newInstance(team.getPlatformTeam(), TeamAction.CREATE);
|
||||
}
|
||||
TeamPacket_PLAYERS.set(packet, team.getPlayers());
|
||||
return packet;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates unregister team packet with given team.
|
||||
*
|
||||
* @param team
|
||||
* Team to unregister
|
||||
* @return Packet for unregistering team
|
||||
*/
|
||||
@SneakyThrows
|
||||
public Object unregisterTeam(@NonNull Team team) {
|
||||
if (STATIC_CONSTRUCTOR_VERSION) {
|
||||
return TeamPacketConstructor_of.invoke(null, team.getPlatformTeam());
|
||||
} else {
|
||||
return newTeamPacket.newInstance(team.getPlatformTeam(), TeamAction.REMOVE);
|
||||
Field suffix = ReflectionUtils.getFields(ScoreboardTeamClass, String.class).get(3);
|
||||
return (team, version) -> {
|
||||
String legacy = team.getSuffix().toLegacyText();
|
||||
if (version.getMinorVersion() < 13 || TAB.getInstance().getConfiguration().getConfig().isPacketEventsCompensation()) {
|
||||
legacy = SafeScoreboard.cutTo(legacy, Limitations.TEAM_PREFIX_SUFFIX_PRE_1_13);
|
||||
}
|
||||
suffix.set(team.getPlatformTeam(), legacy);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates team update packet with specified parameters.
|
||||
*
|
||||
* @param team
|
||||
* Team to update
|
||||
* @param clientVersion
|
||||
* Version to create the values for
|
||||
* @return Update team packet with specified parameters
|
||||
*/
|
||||
@SneakyThrows
|
||||
public Object updateTeam(@NonNull Team team, @NotNull ProtocolVersion clientVersion) {
|
||||
updateTeamData(team, clientVersion);
|
||||
if (STATIC_CONSTRUCTOR_VERSION) {
|
||||
return TeamPacketConstructor_ofBoolean.invoke(null, team.getPlatformTeam(), false);
|
||||
@NotNull
|
||||
private ConsumerWithException<Team> loadColor() {
|
||||
if (BukkitReflection.getMinorVersion() >= 13) {
|
||||
Class<?> EnumChatFormatClass = BukkitReflection.getClass("ChatFormatting", "EnumChatFormat");
|
||||
Enum<?>[] chatFormats = (Enum<?>[]) EnumChatFormatClass.getMethod("values").invoke(null);
|
||||
Method setColor = ReflectionUtils.getOnlyMethod(ScoreboardTeamClass, void.class, EnumChatFormatClass);
|
||||
return team -> setColor.invoke(team.getPlatformTeam(), chatFormats[team.getColor().getLegacyColor().ordinal()]);
|
||||
} else {
|
||||
return newTeamPacket.newInstance(team.getPlatformTeam(), TeamAction.UPDATE);
|
||||
return team -> {};
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
@NotNull
|
||||
private ConsumerWithException<Team> loadVisibility() {
|
||||
if (BukkitReflection.getMinorVersion() >= 8) {
|
||||
Class<?> enumNameTagVisibility = BukkitReflection.getClass(
|
||||
"world.scores.Team$Visibility", // Mojang mapped
|
||||
"world.scores.ScoreboardTeamBase$EnumNameTagVisibility", // Bukkit 1.17+
|
||||
"ScoreboardTeamBase$EnumNameTagVisibility", // Bukkit 1.8.1 - 1.16.5
|
||||
"EnumNameTagVisibility" // Bukkit 1.8.0
|
||||
);
|
||||
Enum<?>[] nameVisibilities = (Enum<?>[]) enumNameTagVisibility.getMethod("values").invoke(null);
|
||||
Field ScoreboardTeam_nameTagVisibility = ReflectionUtils.getFields(ScoreboardTeamClass, enumNameTagVisibility).get(0);
|
||||
return team -> ScoreboardTeam_nameTagVisibility.set(team.getPlatformTeam(), nameVisibilities[team.getVisibility().ordinal()]);
|
||||
} else {
|
||||
return team -> {};
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
@NotNull
|
||||
private ConsumerWithException<Team> loadCollision() {
|
||||
if (BukkitReflection.getMinorVersion() >= 9) {
|
||||
Class<?> enumTeamPush = BukkitReflection.getClass("world.scores.Team$CollisionRule",
|
||||
"world.scores.ScoreboardTeamBase$EnumTeamPush", "ScoreboardTeamBase$EnumTeamPush");
|
||||
Enum<?>[] collisionRules = (Enum<?>[]) enumTeamPush.getMethod("values").invoke(null);
|
||||
Method setCollisionRule = ReflectionUtils.getOnlyMethod(ScoreboardTeamClass, void.class, enumTeamPush);
|
||||
return team -> setCollisionRule.invoke(team.getPlatformTeam(), collisionRules[team.getCollision().ordinal()]);
|
||||
} else {
|
||||
return team -> {};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -220,49 +191,25 @@ public class TeamPacketData {
|
||||
* Version to create the values for
|
||||
*/
|
||||
@SneakyThrows
|
||||
private void updateTeamData(@NonNull Team team, @NotNull ProtocolVersion clientVersion) {
|
||||
public void updateTeamData(@NonNull Team team, @NotNull ProtocolVersion clientVersion) {
|
||||
Object nmsTeam = team.getPlatformTeam();
|
||||
ScoreboardTeam_setAllowFriendlyFire.invoke(nmsTeam, (team.getOptions() & 0x1) > 0);
|
||||
ScoreboardTeam_setCanSeeFriendlyInvisibles.invoke(nmsTeam, (team.getOptions() & 0x2) > 0);
|
||||
if (MODERN_TEAM_DATA_VERSION) {
|
||||
ScoreboardTeam_setPrefix.invoke(nmsTeam, (Object) team.getPrefix().convert());
|
||||
ScoreboardTeam_setSuffix.invoke(nmsTeam, (Object) team.getSuffix().convert());
|
||||
ScoreboardTeam_setColor.invoke(nmsTeam, chatFormats[team.getColor().getLegacyColor().ordinal()]);
|
||||
} else {
|
||||
String prefix = team.getPrefix().toLegacyText();
|
||||
String suffix = team.getSuffix().toLegacyText();
|
||||
if (clientVersion.getMinorVersion() < 13 || TAB.getInstance().getConfiguration().getConfig().isPacketEventsCompensation()) {
|
||||
prefix = SafeScoreboard.cutTo(prefix, Limitations.TEAM_PREFIX_SUFFIX_PRE_1_13);
|
||||
suffix = SafeScoreboard.cutTo(suffix, Limitations.TEAM_PREFIX_SUFFIX_PRE_1_13);
|
||||
}
|
||||
ScoreboardTeam_setPrefix.invoke(nmsTeam, prefix);
|
||||
ScoreboardTeam_setSuffix.invoke(nmsTeam, suffix);
|
||||
}
|
||||
setVisibility.accept(nmsTeam, team.getVisibility());
|
||||
setCollision.accept(nmsTeam, team.getCollision());
|
||||
ScoreboardTeam_allowFriendlyFire.set(nmsTeam, (team.getOptions() & 0x1) > 0);
|
||||
ScoreboardTeam_seeFriendlyInvisibles.set(nmsTeam, (team.getOptions() & 0x2) > 0);
|
||||
updatePlatformTeamPrefix.accept(team, clientVersion);
|
||||
updatePlatformTeamSuffix.accept(team, clientVersion);
|
||||
updatePlatformTeamColor.accept(team);
|
||||
updatePlatformTeamVisibility.accept(team);
|
||||
updatePlatformTeamCollision.accept(team);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a team with given name.
|
||||
*
|
||||
* @param name
|
||||
* Team name
|
||||
* @return Team with specified name
|
||||
*/
|
||||
@SneakyThrows
|
||||
@NotNull
|
||||
public Object createTeam(@NonNull String name) {
|
||||
return newScoreboardTeam.newInstance(emptyScoreboard, name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if packet is team packet and removes all real players from team
|
||||
* Checks if the packet is a team packet and removes all real players from the team
|
||||
* if sent by other plugins and anti-override is fully active on a player.
|
||||
*
|
||||
* @param player
|
||||
* Player who received the packet
|
||||
* @param packet
|
||||
* Received packet
|
||||
* The received packet
|
||||
*/
|
||||
@SneakyThrows
|
||||
public void onPacketSend(@NonNull TabPlayer player, @NonNull Object packet) {
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
package me.neznamy.tab.platforms.bukkit.scoreboard.viaversion;
|
||||
|
||||
import com.viaversion.viaversion.api.Via;
|
||||
import com.viaversion.viaversion.api.connection.UserConnection;
|
||||
import com.viaversion.viaversion.api.protocol.Protocol;
|
||||
import com.viaversion.viaversion.api.protocol.packet.PacketType;
|
||||
import com.viaversion.viaversion.api.protocol.packet.PacketWrapper;
|
||||
import com.viaversion.viaversion.api.protocol.packet.State;
|
||||
import com.viaversion.viaversion.api.type.Types;
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.chat.component.SimpleTextComponent;
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.shared.platform.decorators.SafeScoreboard;
|
||||
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Scoreboard abstract implementation using ViaVersion packets,
|
||||
* focused to unlock new features on older versions, and also
|
||||
* optimize packet conversion between versions.
|
||||
*/
|
||||
public abstract class ViaScoreboard extends SafeScoreboard<BukkitTabPlayer> {
|
||||
|
||||
private static final long CHECK_DELAY = 50;
|
||||
private static final Object DUMMY = new Object();
|
||||
|
||||
protected final Class<? extends Protocol> protocol;
|
||||
protected final PacketType setDisplayObjective;
|
||||
private final PacketType setObjective;
|
||||
protected final PacketType setScore;
|
||||
private final PacketType setPlayerTeam;
|
||||
/** User connection this scoreboard belongs to */
|
||||
protected final UserConnection connection;
|
||||
|
||||
private transient ScheduledFuture<?> task;
|
||||
private transient final Queue<PacketWrapper> queuedPackets = new ConcurrentLinkedQueue<>();
|
||||
|
||||
/**
|
||||
* Constructs new instance with given player.
|
||||
*
|
||||
* @param player
|
||||
* Player this scoreboard will belong to
|
||||
* @param protocol
|
||||
* Protocol to be sent packets through
|
||||
* @param setDisplayObjective
|
||||
* Objective creation packet
|
||||
* @param setObjective
|
||||
* Objective update packet
|
||||
* @param setScore
|
||||
* Score update packet
|
||||
* @param setPlayerTeam
|
||||
* Player team packet
|
||||
*/
|
||||
public ViaScoreboard(@NonNull BukkitTabPlayer player, @NonNull Class<? extends Protocol> protocol, @NonNull PacketType setDisplayObjective, @NonNull PacketType setObjective, @NonNull PacketType setScore, @NonNull PacketType setPlayerTeam) {
|
||||
super(player);
|
||||
this.protocol = protocol;
|
||||
this.setDisplayObjective = setDisplayObjective;
|
||||
this.setObjective = setObjective;
|
||||
this.setScore = setScore;
|
||||
this.setPlayerTeam = setPlayerTeam;
|
||||
this.connection = Via.getManager().getConnectionManager().getConnectedClient(player.getUniqueId());
|
||||
|
||||
// Queue packets until connection is available
|
||||
// Is important to use scheduleWithFixedDelay() to avoid task overlap
|
||||
task = connection.getChannel().eventLoop().scheduleWithFixedDelay(() -> {
|
||||
if (connection.getProtocolInfo().getClientState() == State.PLAY) {
|
||||
PacketWrapper packet;
|
||||
while ((packet = queuedPackets.poll()) != null) {
|
||||
packet.send(protocol);
|
||||
}
|
||||
task.cancel(true);
|
||||
}
|
||||
}, 0L, CHECK_DELAY, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerObjective(@NonNull Objective objective) {
|
||||
sendObjectiveUpdate(ObjectiveAction.REGISTER, objective);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterObjective(@NonNull Objective objective) {
|
||||
sendObjectiveUpdate(ObjectiveAction.UNREGISTER, objective);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateObjective(@NonNull Objective objective) {
|
||||
sendObjectiveUpdate(ObjectiveAction.UPDATE, objective);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull Object createTeam(@NonNull String name) {
|
||||
// This implementation does not use team objects
|
||||
return DUMMY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerTeam(@NonNull Team team) {
|
||||
sendTeamUpdate(TeamAction.CREATE, team);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterTeam(@NonNull Team team) {
|
||||
sendTeamUpdate(TeamAction.REMOVE, team);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateTeam(@NonNull Team team) {
|
||||
sendTeamUpdate(TeamAction.UPDATE, team);
|
||||
}
|
||||
|
||||
protected void sendObjectiveUpdate(int mode, @NonNull Objective objective) {
|
||||
final PacketWrapper packet = PacketWrapper.create(setObjective, null, connection);
|
||||
|
||||
// Objective name
|
||||
packet.write(Types.STRING, objective.getName());
|
||||
// Mode
|
||||
packet.write(Types.BYTE, (byte) mode);
|
||||
// Optional
|
||||
if (mode == ObjectiveAction.REGISTER || mode == ObjectiveAction.UPDATE) {
|
||||
writeObjectiveDisplay(packet, objective);
|
||||
}
|
||||
|
||||
send(packet);
|
||||
}
|
||||
|
||||
@SuppressWarnings("fallthrough")
|
||||
protected void sendTeamUpdate(int mode, @NonNull Team team) {
|
||||
final PacketWrapper packet = PacketWrapper.create(setPlayerTeam, null, connection);
|
||||
|
||||
// Team name (max 16 characters on pre 1.20.3)
|
||||
packet.write(Types.STRING, team.getName());
|
||||
// Method
|
||||
packet.write(Types.BYTE, (byte) mode);
|
||||
switch (mode) {
|
||||
case TeamAction.REMOVE:
|
||||
break;
|
||||
case TeamAction.CREATE:
|
||||
case TeamAction.UPDATE:
|
||||
// Team display name
|
||||
writeComponent(packet, SimpleTextComponent.text(team.getName()));
|
||||
// Friendly flags
|
||||
packet.write(Types.BYTE, (byte) team.getOptions());
|
||||
// Name tag visibility
|
||||
packet.write(Types.STRING, team.getVisibility().toString());
|
||||
// Collision rule
|
||||
packet.write(Types.STRING, team.getCollision().toString());
|
||||
// Team color
|
||||
packet.write(Types.VAR_INT, team.getColor().getLegacyColor().ordinal());
|
||||
// Team prefix
|
||||
writeComponent(packet, team.getPrefix());
|
||||
// Team suffix
|
||||
writeComponent(packet, team.getSuffix());
|
||||
if (mode == TeamAction.UPDATE) {
|
||||
break;
|
||||
}
|
||||
// Intentional fallthrough for CREATE mode
|
||||
case TeamAction.ADD_PLAYER:
|
||||
case TeamAction.REMOVE_PLAYER:
|
||||
// Entities
|
||||
packet.write(Types.VAR_INT, team.getPlayers().size());
|
||||
for (String entity : team.getPlayers()) {
|
||||
// Entity (player username in this case) (max 40 characters on pre 1.20.3)
|
||||
packet.write(Types.STRING, entity);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Invalid team update mode: " + mode);
|
||||
}
|
||||
|
||||
send(packet);
|
||||
}
|
||||
|
||||
protected abstract void writeComponent(@NonNull PacketWrapper packet, @NonNull TabComponent component);
|
||||
|
||||
protected abstract void writeObjectiveDisplay(@NonNull PacketWrapper packet, @NonNull Objective objective);
|
||||
|
||||
protected void send(@NonNull PacketWrapper packet) {
|
||||
if (!task.isCancelled()) {
|
||||
queuedPackets.add(packet);
|
||||
} else {
|
||||
packet.scheduleSend(protocol);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package me.neznamy.tab.platforms.bukkit.scoreboard.viaversion;
|
||||
|
||||
import com.viaversion.viaversion.api.protocol.Protocol;
|
||||
import com.viaversion.viaversion.api.protocol.packet.PacketType;
|
||||
import com.viaversion.viaversion.api.protocol.packet.PacketWrapper;
|
||||
import com.viaversion.viaversion.api.type.Types;
|
||||
import com.viaversion.viaversion.protocols.v1_12_2to1_13.Protocol1_12_2To1_13;
|
||||
import com.viaversion.viaversion.protocols.v1_12_2to1_13.packet.ClientboundPackets1_13;
|
||||
import com.viaversion.viaversion.util.ComponentUtil;
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
|
||||
/**
|
||||
* Scoreboard implementation using ViaVersion packets
|
||||
* to unlock +1.13 player features on pre 1.13 servers.
|
||||
*/
|
||||
public class ViaScoreboard113 extends ViaScoreboard {
|
||||
|
||||
/**
|
||||
* Constructs new instance with given player.
|
||||
*
|
||||
* @param player Player this scoreboard will belong to
|
||||
*/
|
||||
public ViaScoreboard113(@NonNull BukkitTabPlayer player) {
|
||||
this(player, Protocol1_12_2To1_13.class, ClientboundPackets1_13.SET_DISPLAY_OBJECTIVE, ClientboundPackets1_13.SET_OBJECTIVE, ClientboundPackets1_13.SET_SCORE, ClientboundPackets1_13.SET_PLAYER_TEAM);
|
||||
}
|
||||
|
||||
protected ViaScoreboard113(@NonNull BukkitTabPlayer player, @NonNull Class<? extends Protocol> protocol, @NonNull PacketType setDisplayObjective, @NonNull PacketType setObjective, @NonNull PacketType setScore, @NonNull PacketType setPlayerTeam) {
|
||||
super(player, protocol, setDisplayObjective, setObjective, setScore, setPlayerTeam);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDisplaySlot(@NonNull Objective objective) {
|
||||
PacketWrapper packet = PacketWrapper.create(setDisplayObjective, null, connection);
|
||||
|
||||
packet.write(Types.BYTE, (byte) objective.getDisplaySlot().ordinal());
|
||||
packet.write(Types.STRING, objective.getName());
|
||||
|
||||
send(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setScore(@NonNull Score score) {
|
||||
sendScoreUpdate(ScoreAction.CHANGE, score);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeScore(@NonNull Score score) {
|
||||
sendScoreUpdate(ScoreAction.REMOVE, score);
|
||||
}
|
||||
|
||||
protected void sendScoreUpdate(int action, @NonNull Score score) {
|
||||
final PacketWrapper packet = PacketWrapper.create(setScore, null, connection);
|
||||
|
||||
// Entity name (player username in this case)
|
||||
packet.write(Types.STRING, score.getHolder());
|
||||
// Action
|
||||
packet.write(Types.VAR_INT, action);
|
||||
// Objective name (max 16 characters)
|
||||
packet.write(Types.STRING, score.getObjective().getName());
|
||||
if (action == 0) {
|
||||
// Value
|
||||
packet.write(Types.VAR_INT, score.getValue());
|
||||
}
|
||||
|
||||
send(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeComponent(@NonNull PacketWrapper packet, @NonNull TabComponent component) {
|
||||
packet.write(Types.COMPONENT, ComponentUtil.legacyToJson(component.toLegacyText()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeObjectiveDisplay(@NonNull PacketWrapper packet, @NonNull Objective objective) {
|
||||
// Objective value
|
||||
writeComponent(packet, objective.getTitle());
|
||||
// Type
|
||||
packet.write(Types.VAR_INT, objective.getHealthDisplay() == HealthDisplay.INTEGER ? 0 : 1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package me.neznamy.tab.platforms.bukkit.scoreboard.viaversion;
|
||||
|
||||
import com.viaversion.viaversion.api.protocol.Protocol;
|
||||
import com.viaversion.viaversion.api.protocol.packet.PacketType;
|
||||
import com.viaversion.viaversion.api.protocol.packet.PacketWrapper;
|
||||
import com.viaversion.viaversion.api.type.Types;
|
||||
import com.viaversion.viaversion.protocols.v1_15_2to1_16.Protocol1_15_2To1_16;
|
||||
import com.viaversion.viaversion.protocols.v1_15_2to1_16.packet.ClientboundPackets1_16;
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
|
||||
/**
|
||||
* Scoreboard implementation using ViaVersion packets
|
||||
* to unlock +1.16 player features on pre 1.16 servers.
|
||||
*/
|
||||
public class ViaScoreboard116 extends ViaScoreboard113 {
|
||||
|
||||
/**
|
||||
* Constructs new instance with given player.
|
||||
*
|
||||
* @param player Player this scoreboard will belong to
|
||||
*/
|
||||
public ViaScoreboard116(@NonNull BukkitTabPlayer player) {
|
||||
this(player, Protocol1_15_2To1_16.class, ClientboundPackets1_16.SET_DISPLAY_OBJECTIVE, ClientboundPackets1_16.SET_OBJECTIVE, ClientboundPackets1_16.SET_SCORE, ClientboundPackets1_16.SET_PLAYER_TEAM);
|
||||
}
|
||||
|
||||
protected ViaScoreboard116(@NonNull BukkitTabPlayer player, @NonNull Class<? extends Protocol> protocol, @NonNull PacketType setDisplayObjective, @NonNull PacketType setObjective, @NonNull PacketType setScore, @NonNull PacketType setPlayerTeam) {
|
||||
super(player, protocol, setDisplayObjective, setObjective, setScore, setPlayerTeam);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeComponent(@NonNull PacketWrapper packet, @NonNull TabComponent component) {
|
||||
packet.write(Types.COMPONENT, component.toViaVersion());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package me.neznamy.tab.platforms.bukkit.scoreboard.viaversion;
|
||||
|
||||
import com.viaversion.viaversion.api.protocol.Protocol;
|
||||
import com.viaversion.viaversion.api.protocol.packet.PacketType;
|
||||
import com.viaversion.viaversion.api.protocol.packet.PacketWrapper;
|
||||
import com.viaversion.viaversion.api.type.Types;
|
||||
import com.viaversion.viaversion.protocols.v1_20_2to1_20_3.Protocol1_20_2To1_20_3;
|
||||
import com.viaversion.viaversion.protocols.v1_20_2to1_20_3.packet.ClientboundPackets1_20_3;
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Scoreboard implementation using ViaVersion packets
|
||||
* to unlock +1.20.3 player features on pre 1.20.3 servers.
|
||||
*/
|
||||
public class ViaScoreboard1203 extends ViaScoreboard116 {
|
||||
|
||||
private final PacketType resetScore;
|
||||
|
||||
/**
|
||||
* Constructs new instance with given player.
|
||||
*
|
||||
* @param player Player this scoreboard will belong to
|
||||
*/
|
||||
public ViaScoreboard1203(@NonNull BukkitTabPlayer player) {
|
||||
this(player, Protocol1_20_2To1_20_3.class, ClientboundPackets1_20_3.SET_DISPLAY_OBJECTIVE, ClientboundPackets1_20_3.SET_OBJECTIVE, ClientboundPackets1_20_3.SET_SCORE, ClientboundPackets1_20_3.RESET_SCORE, ClientboundPackets1_20_3.SET_PLAYER_TEAM);
|
||||
}
|
||||
|
||||
protected ViaScoreboard1203(@NonNull BukkitTabPlayer player, @NonNull Class<? extends Protocol> protocol, @NonNull PacketType setDisplayObjective, @NonNull PacketType setObjective, @NonNull PacketType setScore, @NonNull PacketType resetScore, @NonNull PacketType setPlayerTeam) {
|
||||
super(player, protocol, setDisplayObjective, setObjective, setScore, setPlayerTeam);
|
||||
this.resetScore = resetScore;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDisplaySlot(@NonNull Objective objective) {
|
||||
PacketWrapper packet = PacketWrapper.create(setDisplayObjective, null, connection);
|
||||
|
||||
packet.write(Types.VAR_INT, objective.getDisplaySlot().ordinal());
|
||||
packet.write(Types.STRING, objective.getName());
|
||||
|
||||
send(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setScore(@NonNull Score score) {
|
||||
final PacketWrapper packet = PacketWrapper.create(setScore, null, connection);
|
||||
|
||||
// Entity name (player username in this case)
|
||||
packet.write(Types.STRING, score.getHolder());
|
||||
// Objective name
|
||||
packet.write(Types.STRING, score.getObjective().getName());
|
||||
// Value
|
||||
packet.write(Types.VAR_INT, score.getValue());
|
||||
// Display name
|
||||
packet.write(Types.OPTIONAL_TAG, score.getDisplayName() == null ? null : score.getDisplayName().toViaVersionTag());
|
||||
// Number format
|
||||
writeNumberFormat(packet, score.getNumberFormat());
|
||||
|
||||
send(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeScore(@NonNull Score score) {
|
||||
final PacketWrapper packet = PacketWrapper.create(resetScore, null, connection);
|
||||
|
||||
// Entity name (player username in this case)
|
||||
packet.write(Types.STRING, score.getHolder());
|
||||
// Objective name
|
||||
packet.write(Types.OPTIONAL_STRING, score.getObjective().getName().isEmpty() ? null : score.getObjective().getName());
|
||||
|
||||
send(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeComponent(@NonNull PacketWrapper packet, @NonNull TabComponent component) {
|
||||
packet.write(Types.TAG, component.toViaVersionTag());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeObjectiveDisplay(@NonNull PacketWrapper packet, @NonNull Objective objective) {
|
||||
super.writeObjectiveDisplay(packet, objective);
|
||||
writeNumberFormat(packet, objective.getNumberFormat());
|
||||
}
|
||||
|
||||
protected void writeNumberFormat(@NonNull PacketWrapper packet, @Nullable TabComponent content) {
|
||||
// For now, only fixed format is supported by TAB
|
||||
if (content == null) {
|
||||
// Has number format
|
||||
packet.write(Types.BOOLEAN, false);
|
||||
} else {
|
||||
// Has number format
|
||||
packet.write(Types.BOOLEAN, true);
|
||||
// Fixed format
|
||||
packet.write(Types.VAR_INT, 2);
|
||||
// Content
|
||||
packet.write(Types.TAG, content.toViaVersionTag());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,8 @@ package me.neznamy.tab.platforms.bukkit.tablist;
|
||||
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.shared.chat.component.TabComponent;
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import me.neznamy.tab.shared.platform.decorators.TrackedTabList;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -12,7 +13,7 @@ import java.util.UUID;
|
||||
/**
|
||||
* TabList handler using the almighty Bukkit API.
|
||||
*/
|
||||
public class BukkitTabList extends TabListBase<String> {
|
||||
public class BukkitTabList extends TrackedTabList<BukkitTabPlayer> {
|
||||
|
||||
/**
|
||||
* Constructs new instance with given player.
|
||||
@@ -31,10 +32,10 @@ public class BukkitTabList extends TabListBase<String> {
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
public void updateDisplayName(@NonNull UUID entry, @Nullable String displayName) {
|
||||
public void updateDisplayName0(@NonNull UUID entry, @Nullable TabComponent displayName) {
|
||||
Player p = Bukkit.getPlayer(entry);
|
||||
if (p == null) return;
|
||||
p.setPlayerListName(displayName);
|
||||
p.setPlayerListName(displayName == null ? null : displayName.toLegacyText());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -63,13 +64,23 @@ public class BukkitTabList extends TabListBase<String> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addEntry(@NonNull UUID id, @NonNull String name, @Nullable Skin skin, boolean listed, int latency,
|
||||
int gameMode, @Nullable String displayName, int listOrder, boolean showHat) {
|
||||
public void addEntry0(@NonNull Entry entry) {
|
||||
// Shrug
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toComponent(@NonNull TabComponent component) {
|
||||
return player.getPlatform().toBukkitFormat(component);
|
||||
public boolean containsEntry(@NonNull UUID entry) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPlayerListHeaderFooter(@NonNull TabComponent header, @NonNull TabComponent footer) {
|
||||
player.getPlatform().getHeaderFooter().set(player, header, footer);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Skin getSkin() {
|
||||
return null; // Shrug
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package me.neznamy.tab.platforms.bukkit.tablist;
|
||||
import com.mojang.authlib.GameProfile;
|
||||
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.nms.BukkitReflection;
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
@@ -144,7 +145,7 @@ public class PacketTabList1193 extends PacketTabList18 {
|
||||
@NotNull
|
||||
@Override
|
||||
public Object createPacket(@NonNull Action action, @NonNull UUID id, @NonNull String name, @Nullable Skin skin,
|
||||
boolean listed, int latency, int gameMode, @Nullable Object displayName, int listOrder, boolean showHat) {
|
||||
boolean listed, int latency, int gameMode, @Nullable TabComponent displayName, int listOrder, boolean showHat) {
|
||||
Object packet = newPlayerInfo.newInstance(actionToEnumSet.get(action), Collections.emptyList());
|
||||
PLAYERS.set(packet, Collections.singletonList(newPlayerInfoData(
|
||||
id,
|
||||
@@ -152,7 +153,7 @@ public class PacketTabList1193 extends PacketTabList18 {
|
||||
listed,
|
||||
latency,
|
||||
gameModes[gameMode],
|
||||
displayName,
|
||||
displayName == null ? null : displayName.convert(),
|
||||
showHat,
|
||||
listOrder,
|
||||
null
|
||||
@@ -176,9 +177,9 @@ public class PacketTabList1193 extends PacketTabList18 {
|
||||
int listOrder = v1_21_2Plus ? PlayerInfoData_ListOrder.getInt(nmsData) : 0;
|
||||
boolean showHat = BukkitReflection.is1_21_4Plus() && PlayerInfoData_ShowHat.getBoolean(nmsData);
|
||||
if (actions.contains(actionUpdateDisplayName)) {
|
||||
Object expectedName = getExpectedDisplayNames().get(id);
|
||||
if (expectedName != null && expectedName != displayName) {
|
||||
displayName = expectedName;
|
||||
TabComponent expectedName = getExpectedDisplayNames().get(id);
|
||||
if (expectedName != null && expectedName.convert() != displayName) {
|
||||
displayName = expectedName.convert();
|
||||
rewriteEntry = rewritePacket = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,8 @@ import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.PacketSender;
|
||||
import me.neznamy.tab.shared.Limitations;
|
||||
import me.neznamy.tab.shared.chat.component.TabComponent;
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import me.neznamy.tab.shared.platform.decorators.TrackedTabList;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import me.neznamy.tab.shared.util.function.TriFunctionWithException;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -21,7 +22,7 @@ import java.util.UUID;
|
||||
/**
|
||||
* TabList handler for 1.7- servers using packets.
|
||||
*/
|
||||
public class PacketTabList17 extends TabListBase<String> {
|
||||
public class PacketTabList17 extends TrackedTabList<BukkitTabPlayer> {
|
||||
|
||||
private static TriFunctionWithException<String, Boolean, Integer, Object> newPacket;
|
||||
private static PacketSender packetSender;
|
||||
@@ -83,10 +84,10 @@ public class PacketTabList17 extends TabListBase<String> {
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void updateDisplayName(@NonNull UUID entry, @Nullable String displayName) {
|
||||
public void updateDisplayName0(@NonNull UUID entry, @Nullable TabComponent displayName) {
|
||||
if (!displayNames.containsKey(entry)) return; // Entry not tracked by TAB
|
||||
packetSender.sendPacket(player, newPacket.apply(displayNames.get(entry), false, 0));
|
||||
addEntry(entry, userNames.get(entry), null, false, 0, 0, displayName, 0, false);
|
||||
addEntry0(new Entry(entry, userNames.get(entry), null, false, 0, 0, displayName, 0, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -117,19 +118,33 @@ public class PacketTabList17 extends TabListBase<String> {
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void addEntry(@NonNull UUID id, @NonNull String name, @Nullable Skin skin, boolean listed, int latency,
|
||||
int gameMode, @Nullable String displayName, int listOrder, boolean showHat) {
|
||||
String display = displayName == null ? name : displayName;
|
||||
packetSender.sendPacket(player, newPacket.apply(display, true, latency));
|
||||
userNames.put(id, name);
|
||||
displayNames.put(id, display);
|
||||
public boolean containsEntry(@NonNull UUID entry) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toComponent(@NonNull TabComponent component) {
|
||||
String name = component.toLegacyText();
|
||||
if (name.length() > Limitations.MAX_DISPLAY_NAME_LENGTH_1_7) name = name.substring(0, Limitations.MAX_DISPLAY_NAME_LENGTH_1_7);
|
||||
return name;
|
||||
public void setPlayerListHeaderFooter(@NonNull TabComponent header, @NonNull TabComponent footer) {
|
||||
// Added in 1.8
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void addEntry0(@NonNull Entry entry) {
|
||||
String displayName;
|
||||
if (entry.getDisplayName() != null) {
|
||||
displayName = entry.getDisplayName().toLegacyText();
|
||||
if (displayName.length() > Limitations.MAX_DISPLAY_NAME_LENGTH_1_7) displayName = displayName.substring(0, Limitations.MAX_DISPLAY_NAME_LENGTH_1_7);
|
||||
} else {
|
||||
displayName = entry.getName();
|
||||
}
|
||||
packetSender.sendPacket(player, newPacket.apply(displayName, true, entry.getLatency()));
|
||||
userNames.put(entry.getUniqueId(), entry.getName());
|
||||
displayNames.put(entry.getUniqueId(), displayName);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Skin getSkin() {
|
||||
return null; // Added in 1.8
|
||||
}
|
||||
}
|
||||
@@ -5,13 +5,14 @@ import com.mojang.authlib.properties.Property;
|
||||
import lombok.NonNull;
|
||||
import lombok.Setter;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitUtils;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.converter.ComponentConverter;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.PacketSender;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import me.neznamy.tab.shared.platform.decorators.TrackedTabList;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -25,7 +26,10 @@ import java.util.*;
|
||||
*/
|
||||
@Setter
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
public class PacketTabList18 extends TabListBase<Object> {
|
||||
public class PacketTabList18 extends TrackedTabList<BukkitTabPlayer> {
|
||||
|
||||
@Nullable
|
||||
public static SkinData skinData;
|
||||
|
||||
protected static Class<?> PlayerInfoClass;
|
||||
protected static Constructor<?> newPlayerInfo;
|
||||
@@ -100,7 +104,6 @@ public class PacketTabList18 extends TabListBase<Object> {
|
||||
Enum.valueOf(gameMode, "SPECTATOR")
|
||||
};
|
||||
packetSender = new PacketSender();
|
||||
if (ComponentConverter.INSTANCE == null) throw new IllegalStateException("Component converter is not available");
|
||||
try {
|
||||
skinData = new SkinData();
|
||||
} catch (Exception e) {
|
||||
@@ -116,7 +119,7 @@ public class PacketTabList18 extends TabListBase<Object> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDisplayName(@NonNull UUID entry, @Nullable Object displayName) {
|
||||
public void updateDisplayName0(@NonNull UUID entry, @Nullable TabComponent displayName) {
|
||||
packetSender.sendPacket(player,
|
||||
createPacket(Action.UPDATE_DISPLAY_NAME, entry, "", null, false, 0, 0, displayName, 0, false));
|
||||
}
|
||||
@@ -149,10 +152,15 @@ public class PacketTabList18 extends TabListBase<Object> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addEntry(@NonNull UUID id, @NonNull String name, @Nullable Skin skin, boolean listed, int latency,
|
||||
int gameMode, @Nullable Object displayName, int listOrder, boolean showHat) {
|
||||
public void addEntry0(@NonNull Entry entry) {
|
||||
packetSender.sendPacket(player,
|
||||
createPacket(Action.ADD_PLAYER, id, name, skin, listed, latency, gameMode, displayName, listOrder, showHat));
|
||||
createPacket(Action.ADD_PLAYER, entry.getUniqueId(), entry.getName(), entry.getSkin(), entry.isListed(),
|
||||
entry.getLatency(), entry.getGameMode(), entry.getDisplayName(), entry.getListOrder(), entry.isShowHat()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPlayerListHeaderFooter(@NonNull TabComponent header, @NonNull TabComponent footer) {
|
||||
player.getPlatform().getHeaderFooter().set(player, header, footer);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -183,7 +191,7 @@ public class PacketTabList18 extends TabListBase<Object> {
|
||||
@SneakyThrows
|
||||
@NotNull
|
||||
public Object createPacket(@NonNull Action action, @NonNull UUID id, @NonNull String name, @Nullable Skin skin,
|
||||
boolean listed, int latency, int gameMode, @Nullable Object displayName, int listOrder, boolean showHat) {
|
||||
boolean listed, int latency, int gameMode, @Nullable TabComponent displayName, int listOrder, boolean showHat) {
|
||||
Object packet = newPlayerInfo.newInstance(Enum.valueOf(ActionClass, action.name()), Collections.emptyList());
|
||||
List<Object> parameters = new ArrayList<>();
|
||||
if (newPlayerInfoData.getParameterTypes()[0] == PlayerInfoClass) {
|
||||
@@ -192,7 +200,7 @@ public class PacketTabList18 extends TabListBase<Object> {
|
||||
parameters.add(createProfile(id, name, skin));
|
||||
parameters.add(latency);
|
||||
parameters.add(gameModes[gameMode]);
|
||||
parameters.add(displayName);
|
||||
parameters.add(displayName == null ? null : displayName.convert());
|
||||
if (BukkitReflection.getMinorVersion() >= 19) parameters.add(null);
|
||||
PLAYERS.set(packet, Collections.singletonList(newPlayerInfoData.newInstance(parameters.toArray())));
|
||||
return packet;
|
||||
@@ -228,8 +236,8 @@ public class PacketTabList18 extends TabListBase<Object> {
|
||||
GameProfile profile = (GameProfile) PlayerInfoData_Profile.get(nmsData);
|
||||
UUID id = profile.getId();
|
||||
if (action.equals(Action.UPDATE_DISPLAY_NAME.name()) || action.equals(Action.ADD_PLAYER.name())) {
|
||||
Object expectedName = getExpectedDisplayNames().get(id);
|
||||
if (expectedName != null) PlayerInfoData_DisplayName.set(nmsData, expectedName);
|
||||
TabComponent expectedName = getExpectedDisplayNames().get(id);
|
||||
if (expectedName != null) PlayerInfoData_DisplayName.set(nmsData, expectedName.convert());
|
||||
}
|
||||
if (action.equals(Action.UPDATE_LATENCY.name()) || action.equals(Action.ADD_PLAYER.name())) {
|
||||
int oldLatency = PlayerInfoData_Latency.getInt(nmsData);
|
||||
@@ -243,4 +251,16 @@ public class PacketTabList18 extends TabListBase<Object> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsEntry(@NonNull UUID entry) {
|
||||
return true; // TODO?
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Skin getSkin() {
|
||||
if (skinData == null) return null;
|
||||
return skinData.getSkin(player);
|
||||
}
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.tablist;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.Setter;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitUtils;
|
||||
import me.neznamy.tab.platforms.bukkit.header.HeaderFooter;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.shared.chat.component.TabComponent;
|
||||
import me.neznamy.tab.shared.platform.decorators.TrackedTabList;
|
||||
import me.neznamy.tab.shared.util.function.FunctionWithException;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.EnumSet;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Base TabList class for all implementations.
|
||||
*
|
||||
* @param <C>
|
||||
* Component class
|
||||
*/
|
||||
public abstract class TabListBase<C> extends TrackedTabList<BukkitTabPlayer, C> {
|
||||
|
||||
/** Versions supported by paper module that uses direct mojang-mapped NMS for latest MC version */
|
||||
private static final EnumSet<ProtocolVersion> paperNativeVersions = EnumSet.of(
|
||||
ProtocolVersion.V1_21_4
|
||||
);
|
||||
|
||||
/** Instance function */
|
||||
@Getter
|
||||
@Setter
|
||||
private static FunctionWithException<BukkitTabPlayer, TabListBase<?>> instance;
|
||||
|
||||
@Nullable
|
||||
protected static SkinData skinData;
|
||||
|
||||
/**
|
||||
* Constructs new instance.
|
||||
*
|
||||
* @param player
|
||||
* Player this tablist will belong to
|
||||
*/
|
||||
protected TabListBase(@NotNull BukkitTabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the best available instance for current server software.
|
||||
*
|
||||
* @param serverVersion
|
||||
* Server version
|
||||
*/
|
||||
public static void findInstance(@NotNull ProtocolVersion serverVersion) {
|
||||
try {
|
||||
if (ReflectionUtils.classExists("org.bukkit.craftbukkit.CraftServer") && paperNativeVersions.contains(serverVersion)) {
|
||||
Constructor<?> constructor = Class.forName("me.neznamy.tab.platforms.paper.PaperPacketTabList").getConstructor(BukkitTabPlayer.class);
|
||||
instance = player -> (TabListBase<?>) constructor.newInstance(player);
|
||||
} else if (ReflectionUtils.classExists("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket")) {
|
||||
PacketTabList1193.loadNew();
|
||||
instance = PacketTabList1193::new;
|
||||
} else if (BukkitReflection.getMinorVersion() >= 8) {
|
||||
PacketTabList18.load();
|
||||
instance = PacketTabList18::new;
|
||||
} else {
|
||||
PacketTabList17.load();
|
||||
instance = PacketTabList17::new;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
BukkitUtils.compatibilityError(e, "tablist entry management", "Bukkit API",
|
||||
"Layout feature will not work",
|
||||
"Prevent-spectator-effect feature will not work",
|
||||
"Ping spoof feature will not work",
|
||||
"Tablist formatting missing anti-override",
|
||||
"Tablist formatting not supporting relational placeholders");
|
||||
instance = BukkitTabList::new;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPlayerListHeaderFooter(@NonNull TabComponent header, @NonNull TabComponent footer) {
|
||||
if (HeaderFooter.getInstance() != null) HeaderFooter.getInstance().set(player, header, footer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsEntry(@NonNull UUID entry) {
|
||||
return true; // TODO?
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns player's skin. If NMS fields did not load or server is in
|
||||
* offline mode, returns {@code null}.
|
||||
*
|
||||
* @return Player's skin or {@code null}.
|
||||
*/
|
||||
@Nullable
|
||||
public Skin getSkin() {
|
||||
if (skinData == null) return null;
|
||||
return skinData.getSkin(player);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
package me.neznamy.tab.platforms.bukkit.tablist.viaversion;
|
||||
|
||||
import com.viaversion.viaversion.api.Via;
|
||||
import com.viaversion.viaversion.api.connection.UserConnection;
|
||||
import com.viaversion.viaversion.api.protocol.Protocol;
|
||||
import com.viaversion.viaversion.api.protocol.packet.PacketType;
|
||||
import com.viaversion.viaversion.api.protocol.packet.PacketWrapper;
|
||||
import com.viaversion.viaversion.api.protocol.packet.State;
|
||||
import com.viaversion.viaversion.api.type.Types;
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.tablist.PacketTabList18;
|
||||
import me.neznamy.tab.shared.platform.decorators.TrackedTabList;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.BitSet;
|
||||
import java.util.Queue;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* TabList abstract handler using ViaVersion packets,
|
||||
* focused to unlock new features on older versions,
|
||||
* and also optimize packet conversion between versions.
|
||||
*/
|
||||
public abstract class ViaTabList extends TrackedTabList<BukkitTabPlayer> {
|
||||
|
||||
private static final long CHECK_DELAY = 50;
|
||||
|
||||
protected final Class<? extends Protocol> protocol;
|
||||
private final PacketType playerInfoUpdate;
|
||||
private final PacketType tabList;
|
||||
/** User connection this tablist belongs to */
|
||||
protected final UserConnection connection;
|
||||
|
||||
private transient ScheduledFuture<?> task;
|
||||
private transient final Queue<PacketWrapper> queuedPackets = new ConcurrentLinkedQueue<>();
|
||||
|
||||
/**
|
||||
*
|
||||
* @param player
|
||||
* Player this tablist will belong to
|
||||
* @param protocol
|
||||
* Protocol to be sent packets through
|
||||
* @param playerInfoUpdate
|
||||
* Player information update packet
|
||||
* @param tabList
|
||||
* Tab list packet
|
||||
*/
|
||||
public ViaTabList(@NonNull BukkitTabPlayer player, @NonNull Class<? extends Protocol> protocol, @NonNull PacketType playerInfoUpdate, @NonNull PacketType tabList) {
|
||||
super(player);
|
||||
this.protocol = protocol;
|
||||
this.playerInfoUpdate = playerInfoUpdate;
|
||||
this.tabList = tabList;
|
||||
this.connection = Via.getManager().getConnectionManager().getConnectedClient(player.getUniqueId());
|
||||
|
||||
// Queue packets until connection is available
|
||||
// Is important to use scheduleWithFixedDelay() to avoid task overlap
|
||||
task = connection.getChannel().eventLoop().scheduleWithFixedDelay(() -> {
|
||||
if (connection.getProtocolInfo().getClientState() == State.PLAY) {
|
||||
PacketWrapper packet;
|
||||
while ((packet = queuedPackets.poll()) != null) {
|
||||
packet.send(protocol);
|
||||
}
|
||||
task.cancel(true);
|
||||
}
|
||||
}, 0L, CHECK_DELAY, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsEntry(@NonNull UUID entry) {
|
||||
return true; // TODO?
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addEntry0(@NonNull Entry entry) {
|
||||
sendInfoUpdate(Action.ADD_PLAYER, entry.getUniqueId(), entry);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDisplayName0(@NonNull UUID entry, @Nullable TabComponent displayName) {
|
||||
sendInfoUpdate(Action.UPDATE_DISPLAY_NAME, entry, displayName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateLatency(@NonNull UUID entry, int latency) {
|
||||
sendInfoUpdate(Action.UPDATE_LATENCY, entry, latency);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateGameMode(@NonNull UUID entry, int gameMode) {
|
||||
sendInfoUpdate(Action.UPDATE_GAME_MODE, entry, gameMode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPlayerListHeaderFooter(@NonNull TabComponent header, @NonNull TabComponent footer) {
|
||||
final PacketWrapper packet = PacketWrapper.create(tabList, null, connection);
|
||||
|
||||
writeComponent(packet, header);
|
||||
writeComponent(packet, footer);
|
||||
|
||||
send(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Skin getSkin() {
|
||||
if (PacketTabList18.skinData == null) return null;
|
||||
return PacketTabList18.skinData.getSkin(player);
|
||||
}
|
||||
|
||||
protected void sendInfoUpdate(@NonNull Action action, @NonNull UUID uniqueId, Object value) {
|
||||
final PacketWrapper packet = PacketWrapper.create(playerInfoUpdate, null, connection);
|
||||
|
||||
// Action
|
||||
writeAction(packet, action);
|
||||
// Players size
|
||||
packet.write(Types.VAR_INT, 1);
|
||||
// Players
|
||||
packet.write(Types.UUID, uniqueId);
|
||||
switch (action) {
|
||||
case ADD_PLAYER:
|
||||
writeEntry(packet, (Entry) value);
|
||||
break;
|
||||
case REMOVE_PLAYER:
|
||||
break;
|
||||
case UPDATE_GAME_MODE:
|
||||
case UPDATE_LATENCY:
|
||||
case UPDATE_LIST_ORDER:
|
||||
packet.write(Types.VAR_INT, (int) value);
|
||||
break;
|
||||
case UPDATE_LISTED:
|
||||
case UPDATE_HAT:
|
||||
packet.write(Types.BOOLEAN, (boolean) value);
|
||||
break;
|
||||
case UPDATE_DISPLAY_NAME:
|
||||
writeOptionalComponent(packet, (TabComponent) value);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Cannot send info update with action " + action.name());
|
||||
}
|
||||
|
||||
send(packet);
|
||||
}
|
||||
|
||||
protected abstract void writeComponent(@NonNull PacketWrapper packet, @NonNull TabComponent component);
|
||||
|
||||
protected abstract void writeOptionalComponent(@NonNull PacketWrapper packet, @Nullable TabComponent component);
|
||||
|
||||
protected abstract void writeAction(@NonNull PacketWrapper packet, @NonNull Action action);
|
||||
|
||||
protected abstract void writeEntry(@NonNull PacketWrapper packet, @NonNull Entry entry);
|
||||
|
||||
@NotNull
|
||||
protected static BitSet bitSet(int nbits, int action) {
|
||||
final BitSet bitSet = new BitSet(nbits);
|
||||
bitSet.set(action);
|
||||
return bitSet;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
protected static BitSet bitSet(int nbits, int fromAction, int toAction) {
|
||||
final BitSet bit = new BitSet(nbits);
|
||||
bit.set(fromAction, toAction);
|
||||
return bit;
|
||||
}
|
||||
|
||||
protected void send(@NonNull PacketWrapper packet) {
|
||||
if (!task.isCancelled()) {
|
||||
queuedPackets.add(packet);
|
||||
} else {
|
||||
packet.scheduleSend(protocol);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
package me.neznamy.tab.platforms.bukkit.tablist.viaversion;
|
||||
|
||||
import com.viaversion.viaversion.api.protocol.Protocol;
|
||||
import com.viaversion.viaversion.api.protocol.packet.PacketType;
|
||||
import com.viaversion.viaversion.api.protocol.packet.PacketWrapper;
|
||||
import com.viaversion.viaversion.api.type.Types;
|
||||
import com.viaversion.viaversion.protocols.v1_15_2to1_16.Protocol1_15_2To1_16;
|
||||
import com.viaversion.viaversion.protocols.v1_15_2to1_16.packet.ClientboundPackets1_16;
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* TabList handler using ViaVersion packets,
|
||||
* to unlock +1.16 player features on pre 1.16 servers.
|
||||
*/
|
||||
public class ViaTabList116 extends ViaTabList {
|
||||
|
||||
private static final int ADD_PLAYER = 0;
|
||||
private static final int UPDATE_GAME_MODE = 1;
|
||||
private static final int UPDATE_LATENCY = 2;
|
||||
private static final int UPDATE_DISPLAY_NAME = 3;
|
||||
private static final int REMOVE_PLAYER = 4;
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters.
|
||||
*
|
||||
* @param player
|
||||
* Player this tablist will belong to
|
||||
*/
|
||||
public ViaTabList116(@NotNull BukkitTabPlayer player) {
|
||||
this(player, Protocol1_15_2To1_16.class, ClientboundPackets1_16.PLAYER_INFO, ClientboundPackets1_16.TAB_LIST);
|
||||
}
|
||||
|
||||
protected ViaTabList116(@NonNull BukkitTabPlayer player, @NonNull Class<? extends Protocol> protocol, @NonNull PacketType playerInfoUpdate, @NonNull PacketType tabList) {
|
||||
super(player, protocol, playerInfoUpdate, tabList);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeEntry(@NonNull UUID entry) {
|
||||
sendInfoUpdate(Action.REMOVE_PLAYER, entry, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateListed(@NonNull UUID entry, boolean listed) {
|
||||
// Added on 1.19.3
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateListOrder(@NonNull UUID entry, int listOrder) {
|
||||
// Added in 1.21.2
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateHat(@NonNull UUID entry, boolean showHat) {
|
||||
// Added in 1.21.4
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeComponent(@NonNull PacketWrapper packet, @NonNull TabComponent component) {
|
||||
packet.write(Types.COMPONENT, component.toViaVersion());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeOptionalComponent(@NonNull PacketWrapper packet, @Nullable TabComponent component) {
|
||||
packet.write(Types.OPTIONAL_COMPONENT, component == null ? null : component.toViaVersion());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeAction(@NonNull PacketWrapper packet, @NonNull Action action) {
|
||||
switch (action) {
|
||||
case ADD_PLAYER:
|
||||
packet.write(Types.VAR_INT, ADD_PLAYER);
|
||||
break;
|
||||
case REMOVE_PLAYER:
|
||||
packet.write(Types.VAR_INT, REMOVE_PLAYER);
|
||||
break;
|
||||
case UPDATE_DISPLAY_NAME:
|
||||
packet.write(Types.VAR_INT, UPDATE_DISPLAY_NAME);
|
||||
break;
|
||||
case UPDATE_LATENCY:
|
||||
packet.write(Types.VAR_INT, UPDATE_LATENCY);
|
||||
break;
|
||||
case UPDATE_GAME_MODE:
|
||||
packet.write(Types.VAR_INT, UPDATE_GAME_MODE);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Cannot write " + action.name() + " for 1.16 packet");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeEntry(@NonNull PacketWrapper packet, @NonNull Entry entry) {
|
||||
packet.write(Types.STRING, entry.getName());
|
||||
if (entry.getSkin() == null) {
|
||||
// Properties size
|
||||
packet.write(Types.VAR_INT, 0);
|
||||
} else {
|
||||
// Properties size
|
||||
packet.write(Types.VAR_INT, 1);
|
||||
// Properties
|
||||
packet.write(Types.STRING, entry.getName());
|
||||
packet.write(Types.STRING, entry.getSkin().getValue());
|
||||
packet.write(Types.OPTIONAL_STRING, entry.getSkin().getSignature());
|
||||
}
|
||||
packet.write(Types.VAR_INT, entry.getGameMode());
|
||||
packet.write(Types.VAR_INT, entry.getLatency());
|
||||
writeOptionalComponent(packet, entry.getDisplayName());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
package me.neznamy.tab.platforms.bukkit.tablist.viaversion;
|
||||
|
||||
import com.viaversion.viaversion.api.protocol.Protocol;
|
||||
import com.viaversion.viaversion.api.protocol.packet.PacketType;
|
||||
import com.viaversion.viaversion.api.protocol.packet.PacketWrapper;
|
||||
import com.viaversion.viaversion.api.type.Types;
|
||||
import com.viaversion.viaversion.protocols.v1_19_1to1_19_3.packet.ClientboundPackets1_19_3;
|
||||
import com.viaversion.viaversion.protocols.v1_19_1to1_19_3.Protocol1_19_1To1_19_3;
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.BitSet;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* TabList handler using ViaVersion packets,
|
||||
* to unlock +1.19.3 player features on pre 1.19.3 servers.
|
||||
*/
|
||||
public class ViaTabList1193 extends ViaTabList116 {
|
||||
|
||||
private static final BitSet ADD_PLAYER = bitSet(6, 0, 6);
|
||||
//private static final BitSet INITIALIZE_CHAT = bitSet(6, 1);
|
||||
private static final BitSet UPDATE_GAME_MODE = bitSet(6, 2);
|
||||
private static final BitSet UPDATE_LISTED = bitSet(6, 3);
|
||||
private static final BitSet UPDATE_LATENCY = bitSet(6, 4);
|
||||
private static final BitSet UPDATE_DISPLAY_NAME = bitSet(6, 5);
|
||||
|
||||
private final PacketType playerInfoRemove;
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters.
|
||||
*
|
||||
* @param player
|
||||
* Player this tablist will belong to
|
||||
*/
|
||||
public ViaTabList1193(@NotNull BukkitTabPlayer player) {
|
||||
this(player, Protocol1_19_1To1_19_3.class, ClientboundPackets1_19_3.PLAYER_INFO_REMOVE, ClientboundPackets1_19_3.PLAYER_INFO_UPDATE, ClientboundPackets1_19_3.TAB_LIST);
|
||||
}
|
||||
|
||||
protected ViaTabList1193(@NonNull BukkitTabPlayer player, @NonNull Class<? extends Protocol> protocol, @Nullable PacketType playerInfoRemove, @NonNull PacketType playerInfoUpdate, @NonNull PacketType tabList) {
|
||||
super(player, protocol, playerInfoUpdate, tabList);
|
||||
this.playerInfoRemove = playerInfoRemove;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeEntry(@NonNull UUID entry) {
|
||||
final PacketWrapper packet = PacketWrapper.create(playerInfoRemove, null, connection);
|
||||
|
||||
// Players
|
||||
packet.write(Types.UUID_ARRAY, new UUID[] { entry });
|
||||
|
||||
send(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateListed(@NonNull UUID entry, boolean listed) {
|
||||
sendInfoUpdate(Action.UPDATE_LISTED, entry, listed);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeAction(@NonNull PacketWrapper packet, @NonNull Action action) {
|
||||
switch (action) {
|
||||
case ADD_PLAYER:
|
||||
packet.write(Types.PROFILE_ACTIONS_ENUM1_19_3, ADD_PLAYER);
|
||||
break;
|
||||
case UPDATE_DISPLAY_NAME:
|
||||
packet.write(Types.PROFILE_ACTIONS_ENUM1_19_3, UPDATE_DISPLAY_NAME);
|
||||
break;
|
||||
case UPDATE_LATENCY:
|
||||
packet.write(Types.PROFILE_ACTIONS_ENUM1_19_3, UPDATE_LATENCY);
|
||||
break;
|
||||
case UPDATE_GAME_MODE:
|
||||
packet.write(Types.PROFILE_ACTIONS_ENUM1_19_3, UPDATE_GAME_MODE);
|
||||
break;
|
||||
case UPDATE_LISTED:
|
||||
packet.write(Types.PROFILE_ACTIONS_ENUM1_19_3, UPDATE_LISTED);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Cannot write " + action.name() + " for 1.19.3 packet");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeEntry(@NonNull PacketWrapper packet, @NonNull Entry entry) {
|
||||
packet.write(Types.STRING, entry.getName());
|
||||
if (entry.getSkin() == null) {
|
||||
// Properties size
|
||||
packet.write(Types.VAR_INT, 0);
|
||||
} else {
|
||||
// Properties size
|
||||
packet.write(Types.VAR_INT, 1);
|
||||
// Properties
|
||||
packet.write(Types.STRING, entry.getName());
|
||||
packet.write(Types.STRING, entry.getSkin().getValue());
|
||||
packet.write(Types.OPTIONAL_STRING, entry.getSkin().getSignature());
|
||||
}
|
||||
packet.write(Types.BOOLEAN, false); // No chat session data
|
||||
packet.write(Types.VAR_INT, entry.getGameMode());
|
||||
packet.write(Types.BOOLEAN, entry.isListed());
|
||||
packet.write(Types.VAR_INT, entry.getLatency());
|
||||
writeOptionalComponent(packet, entry.getDisplayName());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package me.neznamy.tab.platforms.bukkit.tablist.viaversion;
|
||||
|
||||
import com.viaversion.viaversion.api.protocol.Protocol;
|
||||
import com.viaversion.viaversion.api.protocol.packet.PacketType;
|
||||
import com.viaversion.viaversion.api.protocol.packet.PacketWrapper;
|
||||
import com.viaversion.viaversion.api.type.Types;
|
||||
import com.viaversion.viaversion.protocols.v1_21to1_21_2.packet.ClientboundPackets1_21_2;
|
||||
import com.viaversion.viaversion.protocols.v1_21to1_21_2.Protocol1_21To1_21_2;
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.BitSet;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* TabList handler using ViaVersion packets,
|
||||
* to unlock +1.21.2 player features on pre 1.21.2 servers.
|
||||
*/
|
||||
public class ViaTabList1212 extends ViaTabList1193 {
|
||||
|
||||
private static final BitSet ADD_PLAYER = bitSet(7, 0, 7);
|
||||
//private static final BitSet INITIALIZE_CHAT = bitSet(7, 1);
|
||||
private static final BitSet UPDATE_GAME_MODE = bitSet(7, 2);
|
||||
private static final BitSet UPDATE_LISTED = bitSet(7, 3);
|
||||
private static final BitSet UPDATE_LATENCY = bitSet(7, 4);
|
||||
private static final BitSet UPDATE_DISPLAY_NAME = bitSet(7, 5);
|
||||
private static final BitSet UPDATE_LIST_ORDER = bitSet(7, 6);
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters.
|
||||
*
|
||||
* @param player
|
||||
* Player this tablist will belong to
|
||||
*/
|
||||
public ViaTabList1212(@NotNull BukkitTabPlayer player) {
|
||||
super(player, Protocol1_21To1_21_2.class, ClientboundPackets1_21_2.PLAYER_INFO_REMOVE, ClientboundPackets1_21_2.PLAYER_INFO_UPDATE, ClientboundPackets1_21_2.TAB_LIST);
|
||||
}
|
||||
|
||||
protected ViaTabList1212(@NonNull BukkitTabPlayer player, @NonNull Class<? extends Protocol> protocol, @Nullable PacketType playerInfoRemove, @NonNull PacketType playerInfoUpdate, @NonNull PacketType tabList) {
|
||||
super(player, protocol, playerInfoRemove, playerInfoUpdate, tabList);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateListOrder(@NonNull UUID entry, int listOrder) {
|
||||
sendInfoUpdate(Action.UPDATE_LIST_ORDER, entry, listOrder);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeComponent(@NonNull PacketWrapper packet, @NonNull TabComponent component) {
|
||||
packet.write(Types.TAG, component.toViaVersionTag());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeOptionalComponent(@NonNull PacketWrapper packet, @Nullable TabComponent component) {
|
||||
packet.write(Types.OPTIONAL_TAG, component == null ? null : component.toViaVersionTag());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeAction(@NonNull PacketWrapper packet, @NonNull Action action) {
|
||||
switch (action) {
|
||||
case ADD_PLAYER:
|
||||
packet.write(Types.PROFILE_ACTIONS_ENUM1_21_2, ADD_PLAYER);
|
||||
break;
|
||||
case UPDATE_DISPLAY_NAME:
|
||||
packet.write(Types.PROFILE_ACTIONS_ENUM1_21_2, UPDATE_DISPLAY_NAME);
|
||||
break;
|
||||
case UPDATE_LATENCY:
|
||||
packet.write(Types.PROFILE_ACTIONS_ENUM1_21_2, UPDATE_LATENCY);
|
||||
break;
|
||||
case UPDATE_GAME_MODE:
|
||||
packet.write(Types.PROFILE_ACTIONS_ENUM1_21_2, UPDATE_GAME_MODE);
|
||||
break;
|
||||
case UPDATE_LISTED:
|
||||
packet.write(Types.PROFILE_ACTIONS_ENUM1_21_2, UPDATE_LISTED);
|
||||
break;
|
||||
case UPDATE_LIST_ORDER:
|
||||
packet.write(Types.PROFILE_ACTIONS_ENUM1_21_2, UPDATE_LIST_ORDER);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Cannot write " + action.name() + " for 1.21.2 packet");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeEntry(@NonNull PacketWrapper packet, @NonNull Entry entry) {
|
||||
super.writeEntry(packet, entry);
|
||||
packet.write(Types.VAR_INT, entry.getListOrder());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package me.neznamy.tab.platforms.bukkit.tablist.viaversion;
|
||||
|
||||
import com.viaversion.viaversion.api.protocol.packet.PacketWrapper;
|
||||
import com.viaversion.viaversion.api.type.Types;
|
||||
import com.viaversion.viaversion.protocols.v1_21_2to1_21_4.Protocol1_21_2To1_21_4;
|
||||
import com.viaversion.viaversion.protocols.v1_21to1_21_2.packet.ClientboundPackets1_21_2;
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.BitSet;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* TabList handler using ViaVersion packets,
|
||||
* to unlock +1.21.4 player features on pre 1.21.4 servers.
|
||||
*/
|
||||
public class ViaTabList1214 extends ViaTabList1212 {
|
||||
|
||||
private static final BitSet ADD_PLAYER = bitSet(8, 0, 8);
|
||||
//private static final BitSet INITIALIZE_CHAT = bitSet(8, 1);
|
||||
private static final BitSet UPDATE_GAME_MODE = bitSet(8, 2);
|
||||
private static final BitSet UPDATE_LISTED = bitSet(8, 3);
|
||||
private static final BitSet UPDATE_LATENCY = bitSet(8, 4);
|
||||
private static final BitSet UPDATE_DISPLAY_NAME = bitSet(8, 5);
|
||||
private static final BitSet UPDATE_LIST_ORDER = bitSet(8, 6);
|
||||
private static final BitSet UPDATE_HAT = bitSet(8, 7);
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters.
|
||||
*
|
||||
* @param player
|
||||
* Player this tablist will belong to
|
||||
*/
|
||||
public ViaTabList1214(@NotNull BukkitTabPlayer player) {
|
||||
super(player, Protocol1_21_2To1_21_4.class, ClientboundPackets1_21_2.PLAYER_INFO_REMOVE, ClientboundPackets1_21_2.PLAYER_INFO_UPDATE, ClientboundPackets1_21_2.TAB_LIST);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateHat(@NonNull UUID entry, boolean showHat) {
|
||||
sendInfoUpdate(Action.UPDATE_HAT, entry, showHat);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeAction(@NonNull PacketWrapper packet, @NonNull Action action) {
|
||||
switch (action) {
|
||||
case ADD_PLAYER:
|
||||
packet.write(Types.PROFILE_ACTIONS_ENUM1_21_4, ADD_PLAYER);
|
||||
break;
|
||||
case UPDATE_DISPLAY_NAME:
|
||||
packet.write(Types.PROFILE_ACTIONS_ENUM1_21_4, UPDATE_DISPLAY_NAME);
|
||||
break;
|
||||
case UPDATE_LATENCY:
|
||||
packet.write(Types.PROFILE_ACTIONS_ENUM1_21_4, UPDATE_LATENCY);
|
||||
break;
|
||||
case UPDATE_GAME_MODE:
|
||||
packet.write(Types.PROFILE_ACTIONS_ENUM1_21_4, UPDATE_GAME_MODE);
|
||||
break;
|
||||
case UPDATE_LISTED:
|
||||
packet.write(Types.PROFILE_ACTIONS_ENUM1_21_4, UPDATE_LISTED);
|
||||
break;
|
||||
case UPDATE_LIST_ORDER:
|
||||
packet.write(Types.PROFILE_ACTIONS_ENUM1_21_4, UPDATE_LIST_ORDER);
|
||||
break;
|
||||
case UPDATE_HAT:
|
||||
packet.write(Types.PROFILE_ACTIONS_ENUM1_21_4, UPDATE_HAT);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Cannot write " + action.name() + " for 1.21.4 packet");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeEntry(@NonNull PacketWrapper packet, @NonNull Entry entry) {
|
||||
super.writeEntry(packet, entry);
|
||||
packet.write(Types.BOOLEAN, entry.isShowHat());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
dependencies {
|
||||
implementation(projects.bukkit)
|
||||
compileOnly("org.spigotmc:spigot:1.12-R0.1-SNAPSHOT")
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package me.neznamy.tab.platforms.bukkit.v1_12_R1;
|
||||
|
||||
import me.neznamy.chat.ChatModifier;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.converter.ComponentConverter;
|
||||
import net.minecraft.server.v1_12_R1.*;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Component converter using direct NMS code.
|
||||
*/
|
||||
public class NMSComponentConverter extends ComponentConverter {
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Object newTextComponent(@NotNull String text) {
|
||||
return new ChatComponentText(text);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Object newTranslatableComponent(@NotNull String key) {
|
||||
return new ChatMessage(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Object newKeybindComponent(@NotNull String keybind) {
|
||||
return new ChatComponentKeybind(keybind);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyStyle(@NotNull Object nmsComponent, @NotNull ChatModifier modifier) {
|
||||
((IChatBaseComponent)nmsComponent).setChatModifier(
|
||||
new net.minecraft.server.v1_12_R1.ChatModifier()
|
||||
.setColor(modifier.getColor() == null ? null : EnumChatFormat.valueOf(modifier.getColor().getLegacyColor().name()))
|
||||
.setBold(modifier.getBold())
|
||||
.setItalic(modifier.getItalic())
|
||||
.setUnderline(modifier.getUnderlined())
|
||||
.setStrikethrough(modifier.getStrikethrough())
|
||||
.setRandom(modifier.getObfuscated())
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addSibling(@NotNull Object parent, @NotNull Object child) {
|
||||
((IChatBaseComponent)parent).addSibling((IChatBaseComponent) child);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package me.neznamy.tab.platforms.bukkit.v1_12_R1;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
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.v1_12_R1.entity.CraftPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Implementation provider using direct NMS code for 1.8.8.
|
||||
*/
|
||||
|
||||
public class NMSImplementationProvider implements ImplementationProvider {
|
||||
|
||||
@NotNull
|
||||
private final ComponentConverter componentConverter = new NMSComponentConverter();
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Scoreboard newScoreboard(@NotNull BukkitTabPlayer player) {
|
||||
return new NMSPacketScoreboard(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public TabList newTabList(@NotNull BukkitTabPlayer player) {
|
||||
return new NMSPacketTabList(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ComponentConverter getComponentConverter() {
|
||||
return componentConverter;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public FunctionWithException<BukkitTabPlayer, Channel> getChannelFunction() {
|
||||
return player -> ((CraftPlayer)player.getPlayer()).getHandle().playerConnection.networkManager.channel;
|
||||
}
|
||||
}
|
||||