Compare commits

...
102 Commits
Author SHA1 Message Date
NEZNAMY 484d46252b 4.1.2 2023-12-08 09:47:09 +01:00
NEZNAMY a2e3c67298 Fix MiniMessage parsing causing issues to different syntax rules (#1166) 2023-12-08 09:46:36 +01:00
NEZNAMY b2ea05d8dd Merge 1.14.4 and 1.15.2 fabric modules for faster compilation 2023-12-07 20:26:13 +01:00
NEZNAMY 1477f47962 Merge 1.17 and 1.17.1 fabric modules for faster compilation 2023-12-07 19:18:25 +01:00
NEZNAMY 273ce49c14 4.1.2-SNAPSHOT 2023-12-07 19:16:56 +01:00
NEZNAMY 96b6790646 4.1.1 2023-12-07 18:50:30 +01:00
NEZNAMY 38227ec878 Update Fabric to 1.20.4 2023-12-07 18:48:08 +01:00
NEZNAMY 11da632972 Add 1.20.4 to version list 2023-12-07 17:46:51 +01:00
NEZNAMY 3721b9fe22 Fix MiniMessage not working in scoreboard 2023-12-07 14:12:13 +01:00
NEZNAMY ecb697e5fb Update default config to match recent scoreboard change (using ||) 2023-12-07 13:31:51 +01:00
NEZNAMY c754772280 Remove warn if ViaVersion returns -1 version (finally got to reproduce it & make tests) 2023-12-06 18:21:58 +01:00
NEZNAMY 5f2b4ba0b3 Fix disconnect on BungeeCord on 1.20.3 2023-12-06 13:46:21 +01:00
NEZNAMY 0ab60becfc Change scoreboard line number format split sequence to || instead of | to prevent false trigger when trying to actually display the character. 2023-12-05 23:01:54 +01:00
NEZNAMY 1dcfb7d6dc 4.1.1-SNAPSHOT 2023-12-05 23:01:11 +01:00
NEZNAMY 3c0b44b365 4.1.0 2023-12-05 20:45:58 +01:00
NEZNAMY ec4aec50ef Mark 1.20.3 as latest known version 2023-12-05 18:55:42 +01:00
NEZNAMY 9c86cfe056 Add Vault to softdepend to avoid console warn 2023-12-05 18:53:49 +01:00
NEZNAMY 6bb2337276 Print warn if RedisSupport received unknown message instead of throwing a NPE 2023-12-05 18:49:53 +01:00
NEZNAMY 4702ca255b Update Fabric to 1.20.3 2023-12-05 18:48:54 +01:00
NEZNAMY ab47aa36cc Confirm network id of 1.20.3 2023-12-05 18:40:55 +01:00
NEZNAMY 4bb14c4a4e Rename new config options 2023-12-05 17:27:24 +01:00
NEZNAMY 1dd75162f9 Add a version to list 2023-12-04 21:09:37 +01:00
NEZNAMY a2242e3c27 Move server version check 2023-12-04 20:52:29 +01:00
NEZNAMY 26ecf494a9 Little fix 2023-12-04 20:45:51 +01:00
NEZNAMY 659af5d0b3 Completely get rid of NMSStorage class 2023-12-04 20:33:23 +01:00
NEZNAMY 382a5ee7fc Move packet sending method to a separate class and make loading optional 2023-12-04 19:17:51 +01:00
NEZNAMY 7364496844 Move ping retrieving method to a separate class 2023-12-04 17:52:32 +01:00
NEZNAMY d342316b87 Reduce dependence on the main NMS class on Bukkit 2023-12-04 17:29:25 +01:00
NEZNAMY b38141da88 Add &f at the beginning of scoreboard lines for 1.13+ players to prevent color field from coloring text if no color is used 2023-12-04 16:40:06 +01:00
NEZNAMY 98f80f7dae Remove unused methods 2023-12-03 21:36:22 +01:00
NEZNAMY c134ec6bfe Simplify class loading 2023-12-03 21:32:00 +01:00
NEZNAMY 4e3f759243 Use NMS prefix only once instead of every usage 2023-12-03 21:03:08 +01:00
NEZNAMY a5f2e0157b Make player game profile getting optional instead of throwing a compatibility error 2023-12-03 20:52:29 +01:00
NEZNAMY fd21032e51 Make pipeline injection compatibility optional and print a warning if a compatibility error is found 2023-12-03 19:22:33 +01:00
NEZNAMY c4f5f9b7d3 Update comment 2023-12-03 18:11:40 +01:00
NEZNAMY 6baf5a7068 Fix NPE 2023-12-03 17:28:41 +01:00
NEZNAMY d3460174de Enable fallback solutions for Scoreboard implementation on Bukkit 2023-12-03 16:07:05 +01:00
NEZNAMY f9f19628ca Fix BukkitScoreboard class issues 2023-12-03 15:38:22 +01:00
NEZNAMY 04e48fea5d Clean up method 2023-12-01 20:22:41 +01:00
NEZNAMY 3e99e22fce Simplify 2023-12-01 19:18:58 +01:00
NEZNAMY 0b244d5ff7 Refactor PacketScoreboard class and draft 1.20.3 support 2023-12-01 19:15:56 +01:00
NEZNAMY 703f2ff316 Fix potentially wrong uuid write 2023-12-01 09:35:47 +01:00
NEZNAMY 34a2bbdbae Simplify 2023-12-01 09:34:14 +01:00
NEZNAMY cd8697c1a4 Add exclusions for some dependencies 2023-11-30 19:15:20 +01:00
NEZNAMY ed2dc2dd51 Avoid rare error on Velocity 2023-11-30 18:50:53 +01:00
NEZNAMY 2dfdc1b993 Update Fabric to 1.20.3-rc1 2023-11-30 18:27:49 +01:00
NEZNAMY 6f2d7f1194 Fix 1.20.3 client crash on BungeeCord 2023-11-30 17:52:01 +01:00
NEZNAMY 30009ef127 Attempt to fix error (#1146) 2023-11-29 17:19:11 +01:00
NEZNAMY 051a422331 Update Fabric to 1.20.3-pre4 2023-11-29 16:56:35 +01:00
NEZNAMY fab6489e9c Fix compilation 2023-11-28 16:12:27 +01:00
NEZNAMY a448614e4a Slightly better organize method 2023-11-27 18:54:28 +01:00
NEZNAMY 84ad874b2a Split complex method into 2 2023-11-27 18:43:43 +01:00
NEZNAMY 19ba978291 Simplify 2023-11-27 18:16:48 +01:00
NEZNAMY e304469e55 Remove star import 2023-11-27 18:10:28 +01:00
NEZNAMY 7ce9e2592a Update README.md 2023-11-27 18:07:35 +01:00
NEZNAMY 681b812c7b Rename feature in /tab cpu output 2023-11-26 13:59:29 +01:00
NEZNAMY ee1ab6785d Rename objective 2023-11-26 12:36:12 +01:00
NEZNAMY 57704b6622 Move a few classes around 2023-11-26 11:15:23 +01:00
NEZNAMY 7fe804e0fc Fix disconnect on bungeecord on 1.20.3 due to null text in component 2023-11-25 22:24:02 +01:00
NEZNAMY a71529b641 Simplify 2023-11-25 18:07:47 +01:00
NEZNAMY 4055173bdb Replace .length() == 0 with .isEmpty() 2023-11-25 17:09:20 +01:00
NEZNAMY 3f8841a276 Update to latest BungeeCord (for the 3rd time in a row) 2023-11-25 17:04:34 +01:00
NEZNAMY 15c38851bc Add misconfiguration check and prevent startup error 2023-11-25 16:56:11 +01:00
NEZNAMY 05030ae817 Update outdated message 2023-11-25 13:05:57 +01:00
NEZNAMY 255fa11371 Fix NPE 2023-11-24 18:41:08 +01:00
NEZNAMY a074c0bf74 Simplify 2023-11-24 10:49:12 +01:00
NEZNAMY 6ccd649fc4 Remove unused imports 2023-11-24 10:46:59 +01:00
NEZNAMY 21dbea07dd Add support for nested relational placeholders (#1153) 2023-11-23 20:28:21 +01:00
NEZNAMY 4c21d9d127 Remove unused import 2023-11-23 20:27:16 +01:00
NEZNAMY 766002b079 Delete aligned suffix feature, since 1.20.3 now has it naturally using playerlist objective (rip 1.20.2-) 2023-11-23 19:39:47 +01:00
NEZNAMY 2ce430ec41 Add support for all Fabric versions in a single jar 2023-11-23 19:18:31 +01:00
NEZNAMY cf2b151045 Bump Velocity version and use new method 2023-11-19 16:57:22 +01:00
NEZNAMY 92216179df Replace try/catch with SneakyThrows annotation 2023-11-19 16:27:06 +01:00
NEZNAMY a2a95b8591 Replace scoreboard enums with fields 2023-11-19 10:50:54 +01:00
NEZNAMY 4a22dc111a Fix attempt to send header/footer to 1.7 players on bungeecord, causing disconnect 2023-11-19 10:16:27 +01:00
NEZNAMY 5df7ecc245 Fix compatibility error message 2023-11-18 20:49:24 +01:00
NEZNAMY d4ef520f26 Print required bridge version in debug command if not connected 2023-11-18 20:39:09 +01:00
NEZNAMY e0f8737865 Use direct code for bungeecord instead of reflection and drop support for old versions 2023-11-18 20:37:37 +01:00
NEZNAMY 37b13a54a7 Create classes for plugin message types instead of writing all content on each usage + bump bridge version to 5 due to braking changes 2023-11-18 14:44:22 +01:00
NEZNAMY d9d28d31d0 Prepare for new 1.20.3 scoreboard features 2023-11-17 20:21:17 +01:00
NEZNAMY 66bf5da3f5 Delete CustomLine class due to not being viable 2023-11-17 11:41:11 +01:00
NEZNAMY 840c721afc Simplify, the performance gain was pretty much zero 2023-11-17 10:59:28 +01:00
NEZNAMY 859e705a48 Drop multi-version code for fabric (hard to maintain + a lot of it didn't actually work) 2023-11-16 22:10:45 +01:00
NEZNAMY 7375070b21 oops (#1144) 2023-11-15 16:53:42 +01:00
NEZNAMY 3e2eda6a71 Add ClickEvent to components 2023-11-11 20:17:22 +01:00
NEZNAMY 798d327dcb Fix plugin not removing redundant character width overrides from config 2023-11-11 18:11:37 +01:00
NEZNAMY f5ca9139e6 Fix double bossbar on Velocity on server switch (#1139) 2023-11-11 14:02:07 +01:00
NEZNAMY f35e8cfa51 Improve vanish tracking for nametag feature 2023-11-10 21:31:21 +01:00
NEZNAMY 0f6953711b Update build.yml 2023-11-10 15:57:34 +01:00
NEZNAMY e2c5e6f9ac Allow empty text for fixed slots in layout 2023-11-10 15:46:26 +01:00
NEZNAMY 5ef5d6ddba Update README.md 2023-11-10 12:51:41 +01:00
NEZNAMY 5b19a5d798 Only unregister team for other players when player is vanishing 2023-11-10 11:02:35 +01:00
NEZNAMY bf804e8923 Merge pull request #1137 from NEZNAMY/vanish-patch
Alternate implementation of #1136
2023-11-09 18:53:45 +01:00
NEZNAMY f47b8b5528 Alternate implementation of #1136 2023-11-09 18:30:56 +01:00
NEZNAMY c353f6787a Fix endsWith condition (#1134) 2023-11-07 17:37:52 +01:00
NEZNAMY 1b7d5c1a5c Add support for latest BungeeCord (yes, again), as well as some previous versions and remove compatibility check. 2023-11-06 17:12:26 +01:00
NEZNAMY 956456f157 4.1.0-SNAPSHOT 2023-11-06 13:41:51 +01:00
NEZNAMY 49077bd798 Create enum constants for packet actions for better readability and understanding of code 2023-11-05 21:16:07 +01:00
NEZNAMY 019a0287b2 4.0.9 2023-11-05 14:27:55 +01:00
NEZNAMY a71dac28c0 Hotfix after adding support for latest bungeecord (#1132) 2023-11-05 14:21:25 +01:00
NEZNAMY 155886e000 Fix typo 2023-11-04 15:39:45 +01:00
NEZNAMY ed4c303d2e 4.0.8 2023-11-04 14:55:32 +01:00
184 changed files with 6589 additions and 68684 deletions

No files matched your search

+1 -4
View File
@@ -3,9 +3,6 @@ on:
push:
branches:
- master
- v4
pull_request:
types: [opened, synchronize, reopened]
jobs:
build:
name: Build
@@ -41,5 +38,5 @@ jobs:
- name: Upload a Build Artifact
uses: actions/upload-artifact@v3.1.2 #https://github.com/actions/upload-artifact/releases
with:
name: Jar
name: Click here to download
path: jar/build/libs/TAB-**.jar
+34
View File
@@ -3,6 +3,17 @@ TAB aims to be a superior all-in-one minecraft plugin for displaying information
similar plugins in terms of features, performance and compatibility.
More information can be found at [Why TAB?](https://github.com/NEZNAMY/TAB/wiki/Why-TAB%3F) wiki page.
# Download
### Releases
You can download full releases from [GitHub releases](https://github.com/NEZNAMY/TAB/releases),
[SpigotMC](https://www.spigotmc.org/resources/57806/) or [Modrinth](https://modrinth.com/plugin/tab-was-taken).
### Dev builds
Plugin's development is continuous and contains lots of small changes/fixes not worthy of being called a full
update, therefore development builds are being created and eventually a full update is released when enough changes are made.
If you want to benefit from a recent change in the code before official release, you can download development builds
from [GitHub actions](https://github.com/NEZNAMY/TAB/actions).
# Compiling
Compilation requires JDK 17 and up.
To compile the plugin, run `./gradlew build` from the terminal.
@@ -21,3 +32,26 @@ is in quality of the report. Bug reports where steps to reproduce are "idk" have
# Contributing
See [CONTRIBUTING.md](https://github.com/NEZNAMY/TAB/blob/master/CONTRIBUTING.md)
# Provided services
The plugin is being used by a large amount of servers, which also means the demand for my attention is high. Unfortunately,
I am no longer able to keep up with the demand since I don't want to dedicate my whole existence to a free plugin.
In order to provide the best quality of all provided services, the amount of services provided may vary over time.
Here is the full list of potential services and their status:
| Service type | Availability |
|-------------------------------|--------------|
| Updates for new MC versions | ✔ |
| Bug fixes | ✔ |
| Wiki updates<sup>1</sup> | ✔ |
| Feature requests | ❌ |
| Customer support<sup>2</sup> | ✔ |
| Free user support<sup>3</sup> | ❌ |
| Optimization<sup>4</sup> | ✔ |
<sup>1</sup> - Adding new examples, guides and other kind of useful information people ask about.
<sup>2</sup> - Assistance with the plugin on discord, including answering any questions and helping with
configuration to those, who bought a copy of the plugin when it was purchasable.
<sup>3</sup> - Like <sup>2</sup>, but for anyone in the world who asks for it.
<sup>4</sup> - Improving plugin's performance while not losing out on any features. This was done
proactively a lot in the past, so there isn't much to do anymore, only if someone actually finds and reports something.
+11 -2
View File
@@ -4,7 +4,7 @@ plugins {
allprojects {
group = "me.neznamy"
version = "4.0.8-SNAPSHOT"
version = "4.1.2"
description = "An all-in-one solution that works"
ext.set("id", "tab")
@@ -18,7 +18,16 @@ val platforms = setOf(
projects.velocity,
projects.sponge7,
projects.sponge8,
projects.fabric
projects.fabric,
projects.fabric.v1152,
projects.fabric.v1165,
projects.fabric.v1171,
projects.fabric.v1182,
projects.fabric.v1192,
projects.fabric.v1193,
projects.fabric.v1201,
projects.fabric.v1202,
projects.fabric.v1204
).map { it.dependencyProject }
val special = setOf(
+5
View File
@@ -9,5 +9,10 @@ dependencies {
}
compileOnly("LibsDisguises:LibsDisguises:10.0.21") {
exclude("org.spigotmc", "spigot")
exclude("org.spigotmc", "spigot-api")
exclude("com.github.dmulloy2", "ProtocolLib")
exclude("org.ow2.asm", "asm")
exclude("net.md-5", "bungeecord-chat")
}
compileOnly("com.github.LeonMangler:PremiumVanishAPI:2.8.8")
}
@@ -2,21 +2,26 @@ package me.neznamy.tab.platforms.bukkit;
import com.mojang.authlib.GameProfile;
import io.netty.channel.Channel;
import lombok.Getter;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
import me.neznamy.tab.platforms.bukkit.scoreboard.PacketScoreboard;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.features.nametags.NameTag;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.platforms.bukkit.nms.NMSStorage;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.features.injection.NettyPipelineInjector;
import me.neznamy.tab.shared.features.sorting.Sorting;
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.Field;
import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;
@@ -25,6 +30,14 @@ import java.util.stream.Collectors;
*/
public class BukkitPipelineInjector extends NettyPipelineInjector {
private static Method getHandle;
private static Field PLAYER_CONNECTION;
private static Field NETWORK_MANAGER;
private static Field CHANNEL;
@Getter
private static boolean available;
/**
* Constructs new instance
*/
@@ -32,35 +45,55 @@ public class BukkitPipelineInjector extends NettyPipelineInjector {
super("packet_handler");
}
public static void tryLoad() {
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");
getHandle = Class.forName("org.bukkit.craftbukkit." + BukkitReflection.getServerPackage() + ".entity.CraftPlayer").getMethod("getHandle");
PLAYER_CONNECTION = ReflectionUtils.getOnlyField(EntityPlayer, PlayerConnection);
if (BukkitReflection.is1_20_2Plus()) {
NETWORK_MANAGER = ReflectionUtils.getOnlyField(PlayerConnection.getSuperclass(), NetworkManager);
} else {
NETWORK_MANAGER = ReflectionUtils.getOnlyField(PlayerConnection, NetworkManager);
}
CHANNEL = ReflectionUtils.getOnlyField(NetworkManager, Channel.class);
available = true;
} catch (Exception e) {
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.RED.getFormat() + "[TAB] Failed to initialize NMS fields for " +
"network channel injection due to a compatibility error. This will make the following features not work: " +
"Unlimited nametag mode, anti-override for tablist formatting & nametags, detecting nickname change for compatibility " +
"with nick plugins and compatibility with other scoreboard plugins. " +
"Please update the plugin a to version with native support for your server version to unlock the features.");
}
}
@Override
@Nullable
@SneakyThrows
protected Channel getChannel(@NotNull TabPlayer player) {
BukkitTabPlayer bukkit = (BukkitTabPlayer) player;
NMSStorage nms = NMSStorage.getInstance();
try {
if (nms.CHANNEL != null) return (Channel) nms.CHANNEL.get(nms.NETWORK_MANAGER.get(bukkit.getPlayerConnection()));
} catch (IllegalAccessException exception) {
TAB.getInstance().getErrorManager().printError("Failed to get channel of " + bukkit.getName(), exception);
}
return null;
return (Channel) CHANNEL.get(NETWORK_MANAGER.get(PLAYER_CONNECTION.get(getHandle.invoke(player.getPlayer()))));
}
@Override
@SneakyThrows
public void onDisplayObjective(@NotNull TabPlayer player, @NotNull Object packet) {
if (!PacketScoreboard.isAvailable()) return;
int position;
if (BukkitReflection.is1_20_2Plus()) {
position = ((Enum<?>)PacketScoreboard.DisplayObjective_POSITION.get(packet)).ordinal();
position = ((Enum<?>)PacketScoreboard.displayPacketData.DisplayObjective_POSITION.get(packet)).ordinal();
} else {
position = PacketScoreboard.DisplayObjective_POSITION.getInt(packet);
position = PacketScoreboard.displayPacketData.DisplayObjective_POSITION.getInt(packet);
}
TAB.getInstance().getFeatureManager().onDisplayObjective(player, position,
(String) PacketScoreboard.DisplayObjective_OBJECTIVE_NAME.get(packet));
(String) PacketScoreboard.displayPacketData.DisplayObjective_OBJECTIVE_NAME.get(packet));
}
@Override
@SneakyThrows
public void onObjective(@NotNull TabPlayer player, @NotNull Object packet) {
if (!PacketScoreboard.isAvailable()) return;
TAB.getInstance().getFeatureManager().onObjective(player,
PacketScoreboard.Objective_METHOD.getInt(packet),
(String) PacketScoreboard.Objective_OBJECTIVE_NAME.get(packet));
@@ -68,17 +101,20 @@ public class BukkitPipelineInjector extends NettyPipelineInjector {
@Override
public boolean isDisplayObjective(@NotNull Object packet) {
return PacketScoreboard.DisplayObjectiveClass.isInstance(packet);
if (!PacketScoreboard.isAvailable()) return false;
return PacketScoreboard.displayPacketData.DisplayObjectiveClass.isInstance(packet);
}
@Override
public boolean isObjective(@NotNull Object packet) {
if (!PacketScoreboard.isAvailable()) return false;
return PacketScoreboard.ObjectivePacketClass.isInstance(packet);
}
@Override
public boolean isTeam(@NotNull Object packet) {
return PacketScoreboard.TeamPacketClass.isInstance(packet);
if (!PacketScoreboard.isAvailable()) return false;
return PacketScoreboard.teamPacketData.TeamPacketClass.isInstance(packet);
}
@Override
@@ -87,55 +123,67 @@ public class BukkitPipelineInjector extends NettyPipelineInjector {
}
@Override
@SneakyThrows
public void onPlayerInfo(@NotNull TabPlayer receiver, @NotNull Object packet) {
List<String> actions;
if (BukkitTabList.ClientboundPlayerInfoRemovePacket != null) {
//1.19.3+
actions = ((EnumSet<?>)BukkitTabList.ACTION.get(packet)).stream().map(Enum::name).collect(Collectors.toList());
if (BukkitReflection.is1_19_3Plus()) {
onPlayerInfo1_19_3(receiver, packet);
} else {
//1.19.2-
actions = Collections.singletonList(BukkitTabList.ACTION.get(packet).toString());
onPlayerInfo1_19_2(receiver, packet);
}
}
@SneakyThrows
private void onPlayerInfo1_19_3(@NotNull TabPlayer receiver, @NotNull Object packet) {
List<String> actions = ((EnumSet<?>)BukkitTabList.ACTION.get(packet)).stream().map(Enum::name).collect(Collectors.toList());
List<Object> updatedList = new ArrayList<>();
for (Object nmsData : (List<?>) BukkitTabList.PLAYERS.get(packet)) {
GameProfile profile = (GameProfile) BukkitTabList.PlayerInfoData_Profile.get(nmsData);
UUID id;
if (BukkitReflection.is1_19_3Plus()) {
id = (UUID) BukkitTabList.PlayerInfoData_UUID.get(nmsData);
} else {
id = profile.getId();
}
id = (UUID) BukkitTabList.PlayerInfoData_UUID.get(nmsData);
Object displayName = null;
int latency = 0;
if (actions.contains(TabList.Action.UPDATE_DISPLAY_NAME.name()) || actions.contains(TabList.Action.ADD_PLAYER.name())) {
if (actions.contains(TabList.Action.UPDATE_DISPLAY_NAME.name())) {
displayName = BukkitTabList.PlayerInfoData_DisplayName.get(nmsData);
IChatBaseComponent newDisplayName = TAB.getInstance().getFeatureManager().onDisplayNameChange(receiver, id);
if (newDisplayName != null) displayName = ((BukkitTabPlayer)receiver).getPlatform().toComponent(newDisplayName, receiver.getVersion());
if (!BukkitReflection.is1_19_3Plus()) BukkitTabList.PlayerInfoData_DisplayName.set(nmsData, displayName);
if (newDisplayName != null) displayName = ((BukkitTabPlayer)receiver).getTabList().toComponent(newDisplayName);
}
if (actions.contains(TabList.Action.UPDATE_LATENCY.name()) || actions.contains(TabList.Action.ADD_PLAYER.name())) {
latency = BukkitTabList.PlayerInfoData_Latency.getInt(nmsData);
latency = TAB.getInstance().getFeatureManager().onLatencyChange(receiver, id, latency);
if (!BukkitReflection.is1_19_3Plus()) BukkitTabList.PlayerInfoData_Latency.set(nmsData, latency);
if (actions.contains(TabList.Action.UPDATE_LATENCY.name())) {
latency = TAB.getInstance().getFeatureManager().onLatencyChange(receiver, id, BukkitTabList.PlayerInfoData_Latency.getInt(nmsData));
}
if (actions.contains(TabList.Action.ADD_PLAYER.name())) {
TAB.getInstance().getFeatureManager().onEntryAdd(receiver, id, profile.getName());
}
if (BukkitReflection.is1_19_3Plus()) {
// 1.19.3 is using records, which do not allow changing final fields, need to rewrite the list entirely
updatedList.add(BukkitTabList.newPlayerInfoData.newInstance(
id,
profile,
BukkitTabList.PlayerInfoData_Listed.getBoolean(nmsData),
latency,
BukkitTabList.PlayerInfoData_GameMode.get(nmsData),
displayName,
BukkitTabList.PlayerInfoData_RemoteChatSession.get(nmsData)));
}
// 1.19.3 is using records, which do not allow changing final fields, need to rewrite the list entirely
updatedList.add(BukkitTabList.newPlayerInfoData.newInstance(
id,
profile,
BukkitTabList.PlayerInfoData_Listed.getBoolean(nmsData),
latency,
BukkitTabList.PlayerInfoData_GameMode.get(nmsData),
displayName,
BukkitTabList.PlayerInfoData_RemoteChatSession.get(nmsData)));
}
if (BukkitReflection.is1_19_3Plus()) {
BukkitTabList.PLAYERS.set(packet, updatedList);
BukkitTabList.PLAYERS.set(packet, updatedList);
}
@SneakyThrows
private void onPlayerInfo1_19_2(@NotNull TabPlayer receiver, @NotNull Object packet) {
String action = BukkitTabList.ACTION.get(packet).toString();
for (Object nmsData : (List<?>) BukkitTabList.PLAYERS.get(packet)) {
GameProfile profile = (GameProfile) BukkitTabList.PlayerInfoData_Profile.get(nmsData);
UUID id = profile.getId();
if (action.equals(TabList.Action.UPDATE_DISPLAY_NAME.name()) || action.equals(TabList.Action.ADD_PLAYER.name())) {
Object displayName = BukkitTabList.PlayerInfoData_DisplayName.get(nmsData);
IChatBaseComponent newDisplayName = TAB.getInstance().getFeatureManager().onDisplayNameChange(receiver, id);
if (newDisplayName != null) displayName = ((BukkitTabPlayer)receiver).getTabList().toComponent(newDisplayName);
BukkitTabList.PlayerInfoData_DisplayName.set(nmsData, displayName);
}
if (action.equals(TabList.Action.UPDATE_LATENCY.name()) || action.equals(TabList.Action.ADD_PLAYER.name())) {
int latency = TAB.getInstance().getFeatureManager().onLatencyChange(receiver, id, BukkitTabList.PlayerInfoData_Latency.getInt(nmsData));
BukkitTabList.PlayerInfoData_Latency.set(nmsData, latency);
}
if (action.equals(TabList.Action.ADD_PLAYER.name())) {
TAB.getInstance().getFeatureManager().onEntryAdd(receiver, id, profile.getName());
}
}
}
@@ -148,11 +196,12 @@ public class BukkitPipelineInjector extends NettyPipelineInjector {
@Override
@SneakyThrows
public void modifyPlayers(@NotNull Object packetPlayOutScoreboardTeam) {
if (!PacketScoreboard.isAvailable()) return;
if (TAB.getInstance().getNameTagManager() == null) return;
int action = PacketScoreboard.TeamPacket_ACTION.getInt(packetPlayOutScoreboardTeam);
int action = PacketScoreboard.teamPacketData.TeamPacket_ACTION.getInt(packetPlayOutScoreboardTeam);
if (action == 1 || action == 2 || action == 4) return;
Collection<String> players = (Collection<String>) PacketScoreboard.TeamPacket_PLAYERS.get(packetPlayOutScoreboardTeam);
String teamName = (String) PacketScoreboard.TeamPacket_NAME.get(packetPlayOutScoreboardTeam);
Collection<String> players = (Collection<String>) PacketScoreboard.teamPacketData.TeamPacket_PLAYERS.get(packetPlayOutScoreboardTeam);
String teamName = (String) PacketScoreboard.teamPacketData.TeamPacket_NAME.get(packetPlayOutScoreboardTeam);
if (players == null) return;
//creating a new list to prevent NoSuchFieldException in minecraft packet encoder when a player is removed
Collection<String> newList = new ArrayList<>();
@@ -175,6 +224,6 @@ public class BukkitPipelineInjector extends NettyPipelineInjector {
newList.add(entry);
}
}
PacketScoreboard.TeamPacket_PLAYERS.set(packetPlayOutScoreboardTeam, newList);
PacketScoreboard.teamPacketData.TeamPacket_PLAYERS.set(packetPlayOutScoreboardTeam, newList);
}
}
@@ -1,6 +1,6 @@
package me.neznamy.tab.platforms.bukkit;
import me.neznamy.tab.platforms.bukkit.nms.NMSStorage;
import me.neznamy.tab.platforms.bukkit.nms.*;
import me.neznamy.tab.platforms.bukkit.platform.BukkitPlatform;
import me.neznamy.tab.platforms.bukkit.platform.FoliaPlatform;
import me.neznamy.tab.shared.ProtocolVersion;
@@ -40,13 +40,15 @@ public class BukkitTAB extends JavaPlugin {
*/
private boolean isVersionSupported() {
try {
NMSStorage.setInstance(new NMSStorage());
DataWatcher.load();
PacketEntityView.load();
BukkitTabList.load();
return true;
} catch (Exception ex) {
if (ProtocolVersion.fromFriendlyName(Bukkit.getBukkitVersion().split("-")[0]) == ProtocolVersion.UNKNOWN_SERVER_VERSION) {
Bukkit.getConsoleSender().sendMessage(String.format(
"%s[TAB] Your server version is not compatible. This plugin version was made for %s - %s. Disabling.",
EnumChatFormat.RED.getFormat(), ProtocolVersion.V1_5, ProtocolVersion.LATEST_KNOWN_VERSION
EnumChatFormat.RED.getFormat(), ProtocolVersion.V1_5.getFriendlyName(), ProtocolVersion.LATEST_KNOWN_VERSION.getFriendlyName()
));
} else {
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.RED.getFormat() + "[TAB] A compatibility issue " +
@@ -2,21 +2,24 @@ package me.neznamy.tab.platforms.bukkit;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.header.HeaderFooter;
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.TabList.Entry.Builder;
import me.neznamy.tab.shared.util.ComponentCache;
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.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
/**
@@ -33,15 +36,12 @@ import java.util.*;
@SuppressWarnings({"unchecked", "rawtypes"})
public class BukkitTabList implements TabList {
// NMS Fields
public static Class<?> PlayerInfoClass;
private static Constructor<?> newPlayerInfo;
public static Field ACTION;
public static Field PLAYERS;
private static Class<Enum> ActionClass;
public static Class<?> ClientboundPlayerInfoRemovePacket;
private static Class<?> RemoteChatSession$Data;
private static Constructor<?> newClientboundPlayerInfoRemovePacket;
private static Class<?> EntityPlayer;
@@ -56,57 +56,45 @@ public class BukkitTabList implements TabList {
private static Object[] gameModes;
@Getter
private static boolean available;
@Nullable
private static SkinData skinData;
private static Method ChatSerializer_DESERIALIZE;
private static final ComponentCache<IChatBaseComponent, Object> componentCache = new ComponentCache<>(1000,
(component, clientVersion) -> ChatSerializer_DESERIALIZE.invoke(null, component.toString(clientVersion)));
/** Player this TabList belongs to */
private final BukkitTabPlayer player;
public static void load() throws NoSuchMethodException, ClassNotFoundException {
if (BukkitReflection.getMinorVersion() < 8) return; // Not supported (yet?)
if (BukkitReflection.getMinorVersion() < 8) return; // Not supported
// Classes
Class<?> playerInfoDataClass;
Class<?> IChatBaseComponent;
Class<Enum> EnumGamemodeClass;
if (BukkitReflection.isMojangMapped()) {
IChatBaseComponent = Class.forName("net.minecraft.network.chat.Component");
EntityPlayer = Class.forName("net.minecraft.server.level.ServerPlayer");
EnumGamemodeClass = (Class<Enum>) Class.forName("net.minecraft.world.level.GameType");
if (BukkitReflection.is1_19_3Plus()) {
ClientboundPlayerInfoRemovePacket = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket");
PlayerInfoClass = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket");
ActionClass = (Class<Enum>) Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket$Action");
playerInfoDataClass = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket$Entry");
RemoteChatSession$Data = Class.forName("net.minecraft.network.chat.RemoteChatSession$Data");
} else {
PlayerInfoClass = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket");
ActionClass = (Class<Enum>) Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket$Action");
playerInfoDataClass = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket$PlayerUpdate");
}
} else if (BukkitReflection.getMinorVersion() >= 17) {
IChatBaseComponent = Class.forName("net.minecraft.network.chat.IChatBaseComponent");
EntityPlayer = Class.forName("net.minecraft.server.level.EntityPlayer");
EnumGamemodeClass = (Class<Enum>) Class.forName("net.minecraft.world.level.EnumGamemode");
if (BukkitReflection.is1_19_3Plus()) {
ClientboundPlayerInfoRemovePacket = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket");
PlayerInfoClass = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket");
ActionClass = (Class<Enum>) Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket$a");
playerInfoDataClass = Class.forName("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket$b");
RemoteChatSession$Data = Class.forName("net.minecraft.network.chat.RemoteChatSession$a");
} else {
PlayerInfoClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutPlayerInfo");
ActionClass = (Class<Enum>) Class.forName("net.minecraft.network.protocol.game.PacketPlayOutPlayerInfo$EnumPlayerInfoAction");
playerInfoDataClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutPlayerInfo$PlayerInfoData");
}
} else {
IChatBaseComponent = BukkitReflection.getLegacyClass("IChatBaseComponent");
EntityPlayer = BukkitReflection.getLegacyClass("EntityPlayer");
PlayerInfoClass = BukkitReflection.getLegacyClass("PacketPlayOutPlayerInfo");
ActionClass = (Class<Enum>) BukkitReflection.getLegacyClass("PacketPlayOutPlayerInfo$EnumPlayerInfoAction", "EnumPlayerInfoAction");
playerInfoDataClass = BukkitReflection.getLegacyClass("PacketPlayOutPlayerInfo$PlayerInfoData", "PlayerInfoData");
EnumGamemodeClass = (Class<Enum>) BukkitReflection.getLegacyClass("EnumGamemode", "WorldSettings$EnumGamemode");
}
Class<?> ChatSerializer = BukkitReflection.getClass("network.chat.Component$Serializer",
"network.chat.IChatBaseComponent$ChatSerializer", "IChatBaseComponent$ChatSerializer", "ChatSerializer");
ChatSerializer_DESERIALIZE = ReflectionUtils.getMethods(ChatSerializer, Object.class, String.class).get(0);
Class<?> IChatBaseComponent = BukkitReflection.getClass("network.chat.Component", "network.chat.IChatBaseComponent", "IChatBaseComponent");
Class<Enum> EnumGamemodeClass = (Class<Enum>) BukkitReflection.getClass("world.level.GameType",
"world.level.EnumGamemode", "EnumGamemode", "WorldSettings$EnumGamemode");
EntityPlayer = BukkitReflection.getClass("server.level.ServerPlayer", "server.level.EntityPlayer", "EntityPlayer");
ActionClass = (Class<Enum>) BukkitReflection.getClass(
"network.protocol.game.ClientboundPlayerInfoUpdatePacket$Action", // Mojang 1.19.3+
"network.protocol.game.ClientboundPlayerInfoPacket$Action", // Mojang 1.17 - 1.19.2
"network.protocol.game.ClientboundPlayerInfoUpdatePacket$a", // Bukkit 1.19.3+
"network.protocol.game.PacketPlayOutPlayerInfo$EnumPlayerInfoAction", // Bukkit 1.17 - 1.19.2
"PacketPlayOutPlayerInfo$EnumPlayerInfoAction", // Bukkit 1.8.1 - 1.16.5
"EnumPlayerInfoAction" // Bukkit 1.8.0
);
PlayerInfoClass = BukkitReflection.getClass("network.protocol.game.ClientboundPlayerInfoUpdatePacket",
"network.protocol.game.ClientboundPlayerInfoPacket",
"network.protocol.game.PacketPlayOutPlayerInfo", "PacketPlayOutPlayerInfo");
Class<?> playerInfoDataClass = BukkitReflection.getClass(
"network.protocol.game.ClientboundPlayerInfoUpdatePacket$Entry", // Mojang 1.19.3
"network.protocol.game.ClientboundPlayerInfoPacket$PlayerUpdate", // Mojang 1.17 - 1.19.2
"network.protocol.game.ClientboundPlayerInfoUpdatePacket$b", // Bukkit 1.19.3+
"network.protocol.game.PacketPlayOutPlayerInfo$PlayerInfoData", // Bukkit 1.17 - 1.19.2
"PacketPlayOutPlayerInfo$PlayerInfoData", // Bukkit 1.8.1 - 1.16.5
"PlayerInfoData" // Bukkit 1.8.0
);
PLAYERS = ReflectionUtils.getOnlyField(PlayerInfoClass, List.class);
newPlayerInfoData = playerInfoDataClass.getConstructors()[0]; // #1105, a specific 1.8.8 fork has 2 constructors
@@ -115,6 +103,8 @@ public class BukkitTabList implements TabList {
PlayerInfoData_GameMode = ReflectionUtils.getOnlyField(playerInfoDataClass, EnumGamemodeClass);
PlayerInfoData_DisplayName = ReflectionUtils.getOnlyField(playerInfoDataClass, IChatBaseComponent);
if (BukkitReflection.is1_19_3Plus()) {
ClientboundPlayerInfoRemovePacket = BukkitReflection.getClass("network.protocol.game.ClientboundPlayerInfoRemovePacket");
Class<?> RemoteChatSession$Data = BukkitReflection.getClass("network.chat.RemoteChatSession$Data", "network.chat.RemoteChatSession$a");
newClientboundPlayerInfoRemovePacket = ClientboundPlayerInfoRemovePacket.getConstructor(List.class);
newPlayerInfo = PlayerInfoClass.getConstructor(EnumSet.class, Collection.class);
ACTION = ReflectionUtils.getOnlyField(PlayerInfoClass, EnumSet.class);
@@ -131,13 +121,21 @@ public class BukkitTabList implements TabList {
Enum.valueOf(EnumGamemodeClass, "ADVENTURE"),
Enum.valueOf(EnumGamemodeClass, "SPECTATOR")
};
available = true;
try {
skinData = new SkinData();
} catch (Exception e) {
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.RED.getFormat() + "[TAB] Failed to initialize NMS fields for " +
"getting player's game profile due to a compatibility error. This will " +
"result in player skins not properly working in layout feature. " +
"Please update the plugin a to version with native support for your server version for optimal experience.");
}
}
@Override
@SneakyThrows
public void removeEntry(@NotNull UUID entry) {
if (!available) return;
if (PlayerInfoClass == null) return;
if (ClientboundPlayerInfoRemovePacket != null) {
//1.19.3+
player.sendPacket(newClientboundPlayerInfoRemovePacket.newInstance(Collections.singletonList(entry)));
@@ -149,25 +147,25 @@ public class BukkitTabList implements TabList {
@Override
public void updateDisplayName(@NotNull UUID entry, @Nullable IChatBaseComponent displayName) {
if (!available) return;
if (PlayerInfoClass == null) return;
player.sendPacket(createPacket(Action.UPDATE_DISPLAY_NAME, new Builder(entry).displayName(displayName).build()));
}
@Override
public void updateLatency(@NotNull UUID entry, int latency) {
if (!available) return;
if (PlayerInfoClass == null) return;
player.sendPacket(createPacket(Action.UPDATE_LATENCY, new Builder(entry).latency(latency).build()));
}
@Override
public void updateGameMode(@NotNull UUID entry, int gameMode) {
if (!available) return;
if (PlayerInfoClass == null) return;
player.sendPacket(createPacket(Action.UPDATE_GAME_MODE, new Builder(entry).gameMode(gameMode).build()));
}
@Override
public void addEntry(@NotNull Entry entry) {
if (!available) return;
if (PlayerInfoClass == null) return;
player.sendPacket(createPacket(Action.ADD_PLAYER, entry));
}
@@ -216,8 +214,8 @@ public class BukkitTabList implements TabList {
return packet;
}
private Object toComponent(IChatBaseComponent component) {
return player.getPlatform().toComponent(component, player.getVersion());
public Object toComponent(IChatBaseComponent component) {
return componentCache.get(component, player.getVersion());
}
@NotNull
@@ -229,4 +227,43 @@ public class BukkitTabList implements TabList {
}
return profile;
}
@Nullable
public Skin getSkin() {
if (skinData == null) return null;
return skinData.getSkin(player);
}
private static class SkinData {
private final Method getHandle;
private final Method getProfile;
@SneakyThrows
public SkinData() {
Class<?> EntityHuman = BukkitReflection.getClass("world.entity.player.Player", "world.entity.player.EntityHuman", "EntityHuman");
getHandle = Class.forName("org.bukkit.craftbukkit." + BukkitReflection.getServerPackage() + ".entity.CraftPlayer").getMethod("getHandle");
// There is only supposed to be one, however there are exceptions:
// #1 - CatServer adds another method
// #2 - Random mods may perform deep hack into the server and add another one (see #1089)
// Get first and hope for the best, alternatively players may not have correct skins in layout, but who cares
getProfile = ReflectionUtils.getMethods(EntityHuman, GameProfile.class).get(0);
}
@Nullable
@SneakyThrows
public Skin getSkin(@NotNull BukkitTabPlayer player) {
Collection<Property> col = ((GameProfile) getProfile.invoke(getHandle.invoke(player.getPlayer()))).getProperties().get(TEXTURES_PROPERTY);
if (col.isEmpty()) return null; //offline mode
Property property = col.iterator().next();
if (BukkitReflection.is1_20_2Plus()) {
return new Skin(
(String) property.getClass().getMethod("value").invoke(property),
(String) property.getClass().getMethod("signature").invoke(property)
);
} else {
return new Skin(property.getValue(), property.getSignature());
}
}
}
}
@@ -1,33 +1,31 @@
package me.neznamy.tab.platforms.bukkit;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
import lombok.Getter;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.hook.LibsDisguisesHook;
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
import me.neznamy.tab.platforms.bukkit.nms.PacketEntityView;
import me.neznamy.tab.platforms.bukkit.nms.PacketSender;
import me.neznamy.tab.platforms.bukkit.nms.PingRetriever;
import me.neznamy.tab.platforms.bukkit.platform.BukkitPlatform;
import me.neznamy.tab.platforms.bukkit.scoreboard.PacketScoreboard;
import me.neznamy.tab.platforms.bukkit.scoreboard.ScoreboardLoader;
import me.neznamy.tab.shared.backend.entityview.EntityView;
import me.neznamy.tab.shared.platform.bossbar.BossBar;
import me.neznamy.tab.shared.platform.BossBar;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.platforms.bukkit.bossbar.EntityBossBar;
import me.neznamy.tab.platforms.bukkit.bossbar.BukkitBossBar;
import me.neznamy.tab.platforms.bukkit.bossbar.ViaBossBar;
import me.neznamy.tab.platforms.bukkit.nms.NMSStorage;
import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.backend.BackendTabPlayer;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.md_5.bungee.chat.ComponentSerializer;
import org.bukkit.entity.Player;
import org.bukkit.metadata.MetadataValue;
import org.bukkit.potion.PotionEffectType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
/**
* TabPlayer implementation for Bukkit platform
*/
@@ -35,23 +33,17 @@ import java.util.Collection;
@Getter
public class BukkitTabPlayer extends BackendTabPlayer {
/** Player's NMS handle (EntityPlayer), preloading for speed */
@NotNull
private final Object handle;
/** Player's connection for sending packets, preloading for speed */
@NotNull
private final Object playerConnection;
private static final boolean spigot = ReflectionUtils.classExists("org.bukkit.entity.Player$Spigot");
@NotNull
private final Scoreboard<BukkitTabPlayer> scoreboard = new PacketScoreboard(this);
private final Scoreboard<BukkitTabPlayer> scoreboard = ScoreboardLoader.getInstance().apply(this);
@NotNull
private final TabList tabList = new BukkitTabList(this);
private final BukkitTabList tabList = new BukkitTabList(this);
@NotNull
private final BossBar bossBar = TAB.getInstance().getServerVersion().getMinorVersion() >= 9 ?
new BukkitBossBar(this) : getVersion().getMinorVersion() >= 9 ? new ViaBossBar(this) : new EntityBossBar(this);
private final BossBar bossBar = BukkitReflection.getMinorVersion() >= 9 ? new BukkitBossBar(this) :
getVersion().getMinorVersion() >= 9 ? new ViaBossBar(this) : new EntityBossBar(this);
@NotNull
private final EntityView entityView = new PacketEntityView(this);
@@ -64,11 +56,8 @@ public class BukkitTabPlayer extends BackendTabPlayer {
* @param p
* bukkit player
*/
@SneakyThrows
public BukkitTabPlayer(@NotNull BukkitPlatform platform, @NotNull Player p) {
super(platform, p, p.getUniqueId(), p.getName(), p.getWorld().getName());
handle = NMSStorage.getInstance().getHandle.invoke(player);
playerConnection = NMSStorage.getInstance().PLAYER_CONNECTION.get(handle);
}
@Override
@@ -79,21 +68,21 @@ public class BukkitTabPlayer extends BackendTabPlayer {
@Override
@SneakyThrows
public int getPing() {
if (BukkitReflection.getMinorVersion() >= 17) {
return getPlayer().getPing();
}
return NMSStorage.getInstance().PING.getInt(handle);
return PingRetriever.getPing(getPlayer());
}
@SneakyThrows
public void sendPacket(@NotNull Object nmsPacket) {
if (!getPlayer().isOnline()) return;
NMSStorage.getInstance().sendPacket.invoke(playerConnection, nmsPacket);
PacketSender.sendPacket(getPlayer(), nmsPacket);
}
@Override
public void sendMessage(@NotNull IChatBaseComponent message) {
getPlayer().sendMessage(BukkitUtils.toBukkitFormat(message, getVersion().getMinorVersion() >= 16));
if (spigot) {
getPlayer().spigot().sendMessage(ComponentSerializer.parse(message.toString(getVersion())));
} else {
getPlayer().sendMessage(BukkitUtils.toBukkitFormat(message, getVersion().getMinorVersion() >= 16));
}
}
@Override
@@ -107,20 +96,9 @@ public class BukkitTabPlayer extends BackendTabPlayer {
}
@Override
@SneakyThrows
@Nullable
public TabList.Skin getSkin() {
Collection<Property> col = ((GameProfile)NMSStorage.getInstance().getProfile.invoke(handle)).getProperties().get(TabList.TEXTURES_PROPERTY);
if (col.isEmpty()) return null; //offline mode
Property property = col.iterator().next();
if (BukkitReflection.is1_20_2Plus()) {
return new TabList.Skin(
(String) property.getClass().getMethod("value").invoke(property),
(String) property.getClass().getMethod("signature").invoke(property)
);
} else {
return new TabList.Skin(property.getValue(), property.getSignature());
}
return tabList.getSkin();
}
@Override
@@ -5,7 +5,7 @@ import me.neznamy.tab.api.bossbar.BarColor;
import me.neznamy.tab.api.bossbar.BarStyle;
import me.neznamy.tab.platforms.bukkit.BukkitUtils;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.bossbar.BossBar;
import me.neznamy.tab.shared.platform.BossBar;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import org.bukkit.Bukkit;
import org.jetbrains.annotations.NotNull;
@@ -21,25 +21,8 @@ import java.util.UUID;
@RequiredArgsConstructor
public class BukkitBossBar implements BossBar {
/** Color array for fast access */
private static final org.bukkit.boss.BarColor[] colors = new org.bukkit.boss.BarColor[] {
org.bukkit.boss.BarColor.PINK,
org.bukkit.boss.BarColor.BLUE,
org.bukkit.boss.BarColor.RED,
org.bukkit.boss.BarColor.GREEN,
org.bukkit.boss.BarColor.YELLOW,
org.bukkit.boss.BarColor.PURPLE,
org.bukkit.boss.BarColor.WHITE
};
/** Style array for fast access */
private static final org.bukkit.boss.BarStyle[] styles = new org.bukkit.boss.BarStyle[] {
org.bukkit.boss.BarStyle.SOLID,
org.bukkit.boss.BarStyle.SEGMENTED_6,
org.bukkit.boss.BarStyle.SEGMENTED_10,
org.bukkit.boss.BarStyle.SEGMENTED_12,
org.bukkit.boss.BarStyle.SEGMENTED_20
};
private static final org.bukkit.boss.BarStyle[] styles = org.bukkit.boss.BarStyle.values();
/** Player this handler belongs to */
@NotNull
@@ -54,7 +37,7 @@ public class BukkitBossBar implements BossBar {
if (bossBars.containsKey(id)) return;
org.bukkit.boss.BossBar bar = Bukkit.createBossBar(
BukkitUtils.toBukkitFormat(IChatBaseComponent.optimizedComponent(title), player.getVersion().getMinorVersion() >= 16),
colors[color.ordinal()],
org.bukkit.boss.BarColor.valueOf(color.name()),
styles[style.ordinal()]
);
bar.setProgress(progress);
@@ -79,7 +62,7 @@ public class BukkitBossBar implements BossBar {
@Override
public void update(@NotNull UUID id, @NotNull BarColor color) {
bossBars.get(id).setColor(colors[color.ordinal()]);
bossBars.get(id).setColor(org.bukkit.boss.BarColor.valueOf(color.name()));
}
@Override
@@ -1,7 +1,7 @@
package me.neznamy.tab.platforms.bukkit.bossbar;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.shared.platform.bossbar.BossBar;
import me.neznamy.tab.shared.platform.BossBar;
import me.neznamy.tab.api.bossbar.BarColor;
import me.neznamy.tab.api.bossbar.BarStyle;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
@@ -5,7 +5,7 @@ import com.viaversion.viaversion.api.legacy.bossbar.BossColor;
import com.viaversion.viaversion.api.legacy.bossbar.BossStyle;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.bossbar.BossBar;
import me.neznamy.tab.shared.platform.BossBar;
import me.neznamy.tab.api.bossbar.BarColor;
import me.neznamy.tab.api.bossbar.BarStyle;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
@@ -21,25 +21,8 @@ import java.util.UUID;
@RequiredArgsConstructor
public class ViaBossBar implements BossBar {
/** Color array for fast access */
private static final BossColor[] colors = new BossColor[] {
BossColor.PINK,
BossColor.BLUE,
BossColor.RED,
BossColor.GREEN,
BossColor.YELLOW,
BossColor.PURPLE,
BossColor.WHITE
};
/** Style array for fast access */
private static final BossStyle[] styles = new BossStyle[] {
BossStyle.SOLID,
BossStyle.SEGMENTED_6,
BossStyle.SEGMENTED_10,
BossStyle.SEGMENTED_12,
BossStyle.SEGMENTED_20
};
private static final BossStyle[] styles = BossStyle.values();
/** Player this handler belongs to */
@NotNull
@@ -55,7 +38,7 @@ public class ViaBossBar implements BossBar {
com.viaversion.viaversion.api.legacy.bossbar.BossBar bar = Via.getAPI().legacyAPI().createLegacyBossBar(
IChatBaseComponent.optimizedComponent(title).toString(player.getVersion()),
progress,
colors[color.ordinal()],
BossColor.valueOf(color.name()),
styles[style.ordinal()]
);
viaBossBars.put(id, bar);
@@ -79,7 +62,7 @@ public class ViaBossBar implements BossBar {
@Override
public void update(@NotNull UUID id, @NotNull BarColor color) {
viaBossBars.get(id).setColor(colors[color.ordinal()]);
viaBossBars.get(id).setColor(BossColor.valueOf(color.name()));
}
@Override
@@ -1,6 +1,7 @@
package me.neznamy.tab.platforms.bukkit.features;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
import me.neznamy.tab.platforms.bukkit.nms.DataWatcher;
import me.neznamy.tab.platforms.bukkit.platform.BukkitPlatform;
import me.neznamy.tab.shared.TAB;
@@ -73,7 +74,7 @@ public class BukkitNameTagX extends BackendNameTagX implements Listener {
@NotNull
public List<Integer> getPassengers(@NotNull Object entity) {
Entity vehicle = (Entity) entity;
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 11) {
if (BukkitReflection.getMinorVersion() >= 11) {
return vehicle.getPassengers().stream().map(Entity::getEntityId).collect(Collectors.toList());
} else {
if (vehicle.getPassenger() != null) {
@@ -109,13 +110,13 @@ public class BukkitNameTagX extends BackendNameTagX implements Listener {
@Override
public boolean isSwimming(@NotNull TabPlayer player) {
Player p = (Player) player.getPlayer();
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 14 && p.getPose() == Pose.SWIMMING) return true;
return TAB.getInstance().getServerVersion().getMinorVersion() == 13 && p.isSwimming();
if (BukkitReflection.getMinorVersion() >= 14 && p.getPose() == Pose.SWIMMING) return true;
return BukkitReflection.getMinorVersion() == 13 && p.isSwimming();
}
@Override
public boolean isGliding(@NotNull TabPlayer player) {
return TAB.getInstance().getServerVersion().getMinorVersion() >= 9 && ((Player)player.getPlayer()).isGliding();
return BukkitReflection.getMinorVersion() >= 9 && ((Player)player.getPlayer()).isGliding();
}
@Override
@@ -5,6 +5,7 @@ import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import lombok.Getter;
import me.neznamy.tab.platforms.bukkit.BukkitUtils;
import me.neznamy.tab.shared.features.types.Loadable;
import me.neznamy.tab.shared.features.types.UnLoadable;
@@ -28,6 +29,8 @@ import org.jetbrains.annotations.NotNull;
@SuppressWarnings("deprecation")
public class PerWorldPlayerList extends TabFeature implements Listener, Loadable, UnLoadable {
@Getter private final String featureName = "Per world PlayerList";
/** Config options */
private final boolean allowBypass = TAB.getInstance().getConfig().getBoolean("per-world-playerlist.allow-bypass-permission", false);
private final List<String> ignoredWorlds = TAB.getInstance().getConfig().getStringList("per-world-playerlist.ignore-effect-in-worlds", Arrays.asList("ignored_world", "build"));
@@ -113,10 +116,4 @@ public class PerWorldPlayerList extends TabFeature implements Listener, Loadable
}
return viewerWorldGroup.equals(targetWorldGroup);
}
@Override
@NotNull
public String getFeatureName() {
return "Per world PlayerList";
}
}
@@ -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.platforms.bukkit.nms.BukkitReflection;
import me.neznamy.tab.platforms.bukkit.nms.PacketSender;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import org.bukkit.Bukkit;
@@ -25,19 +25,19 @@ public abstract class HeaderFooter {
public static void findInstance() {
if (PaperHeaderFooter.isAvailable()) {
instance = new PaperHeaderFooter();
} else if (PacketHeaderFooter.isAvailable()) {
} else if (PacketHeaderFooter.isAvailable() && PacketSender.isAvailable()) {
instance = new PacketHeaderFooter();
} else if (BukkitHeaderFooter.isAvailable()) {
instance = new BukkitHeaderFooter();
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.RED.getFormat() + "[TAB] Failed to initialize NMS fields for " +
"sending Header/Footer due to a compatibility error. Using fallback solution using Bukkit API. " +
"This will drastically drop performance, as well as miss support for fonts (added in 1.16). " +
"Please update the plugin with to version proper support for your server version.");
} else if (BukkitReflection.getMinorVersion() >= 8) {
"Please update the plugin to version with proper support for your server version.");
} else {
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.RED.getFormat() + "[TAB] Failed to initialize NMS fields for " +
"sending Header/Footer due to a compatibility error. No fallback solution was found either. This will " +
"result in the feature not working. " +
"Please update the plugin with to version proper support for your server version.");
"Please update the plugin to version with proper support for your server version.");
}
}
@@ -5,11 +5,14 @@ import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.util.ComponentCache;
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;
/**
* Header/footer sender that uses NMS to send packets. Available
@@ -20,6 +23,9 @@ public class PacketHeaderFooter extends HeaderFooter {
private static Constructor<?> newHeaderFooter;
private static Field HEADER;
private static Field FOOTER;
private static Method ChatSerializer_DESERIALIZE;
private static final ComponentCache<IChatBaseComponent, Object> componentCache = new ComponentCache<>(1000,
(component, clientVersion) -> ChatSerializer_DESERIALIZE.invoke(null, component.toString(clientVersion)));
@Getter
private static boolean available;
@@ -27,18 +33,13 @@ public class PacketHeaderFooter extends HeaderFooter {
static {
try {
if (BukkitReflection.getMinorVersion() >= 8) {
Class<?> IChatBaseComponent;
Class<?> HeaderFooterClass;
if (BukkitReflection.isMojangMapped()) {
IChatBaseComponent = Class.forName("net.minecraft.network.chat.Component");
HeaderFooterClass = Class.forName("net.minecraft.network.protocol.game.ClientboundTabListPacket");
} else if (BukkitReflection.getMinorVersion() >= 17) {
IChatBaseComponent = Class.forName("net.minecraft.network.chat.IChatBaseComponent");
HeaderFooterClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutPlayerListHeaderFooter");
} else {
IChatBaseComponent = BukkitReflection.getLegacyClass("IChatBaseComponent");
HeaderFooterClass = BukkitReflection.getLegacyClass("PacketPlayOutPlayerListHeaderFooter");
}
Class<?> IChatBaseComponent = BukkitReflection.getClass("network.chat.Component",
"network.chat.IChatBaseComponent", "IChatBaseComponent");
Class<?> HeaderFooterClass = BukkitReflection.getClass("network.protocol.game.ClientboundTabListPacket",
"network.protocol.game.PacketPlayOutPlayerListHeaderFooter", "PacketPlayOutPlayerListHeaderFooter");
Class<?> ChatSerializer = BukkitReflection.getClass("network.chat.Component$Serializer",
"network.chat.IChatBaseComponent$ChatSerializer", "IChatBaseComponent$ChatSerializer", "ChatSerializer");
ChatSerializer_DESERIALIZE = ReflectionUtils.getMethods(ChatSerializer, Object.class, String.class).get(0);
if (BukkitReflection.getMinorVersion() >= 17) {
newHeaderFooter = HeaderFooterClass.getConstructor(IChatBaseComponent, IChatBaseComponent);
} else {
@@ -56,15 +57,17 @@ public class PacketHeaderFooter extends HeaderFooter {
@SneakyThrows
@Override
public void set(@NotNull BukkitTabPlayer player, @NotNull IChatBaseComponent header, @NotNull IChatBaseComponent footer) {
Object headerComponent = player.getPlatform().toComponent(header, player.getVersion());
Object footerComponent = player.getPlatform().toComponent(footer, player.getVersion());
if (BukkitReflection.getMinorVersion() >= 17) {
player.sendPacket(newHeaderFooter.newInstance(headerComponent, footerComponent));
player.sendPacket(newHeaderFooter.newInstance(convert(header, player), convert(footer, player)));
} else {
Object packet = newHeaderFooter.newInstance();
HEADER.set(packet, headerComponent);
FOOTER.set(packet, footerComponent);
HEADER.set(packet, convert(header, player));
FOOTER.set(packet, convert(footer, player));
player.sendPacket(packet);
}
}
private Object convert(IChatBaseComponent component, TabPlayer player) {
return componentCache.get(component, player.getVersion());
}
}
@@ -11,10 +11,9 @@ import org.jetbrains.annotations.NotNull;
/**
* Header/footer sender using Paper's API added in 1.16.5.
* Due to lack of component cache for adventure components in TAB,
* there is a performance loss compared to using NMS. However, we do not
* need to serialize/deserialize anything ourselves thanks to direct component
* conversion, so in the end it is roughly the same.
* Thanks to both cache and direct component conversion, this
* implementation is even faster than using NMS components
* and is therefore preferred if available.
*/
public class PaperHeaderFooter extends HeaderFooter {
@@ -17,10 +17,6 @@ public class BukkitReflection {
@Getter
private static final int minorVersion = Integer.parseInt(serverPackage.split("_")[1]);
/** Flag determining whether this server jar is mojang mapped or not */
@Getter
private static final boolean mojangMapped = checkMojangMapped();
/** Flag determining whether the server version is at least 1.19.3 or not */
@Getter
private static final boolean is1_19_3Plus = ReflectionUtils.classExists("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket");
@@ -31,20 +27,15 @@ public class BukkitReflection {
/** Flag determining whether the server version is at least 1.20.2 or not */
@Getter
private static final boolean is1_20_2Plus = minorVersion >= 20 && !serverPackage.equals("v1_20_R1");
private static final boolean is1_20_2Plus = ReflectionUtils.classExists("net.minecraft.world.scores.DisplaySlot");
private static boolean checkMojangMapped() {
try {
Class.forName("net.minecraft.network.syncher.SynchedEntityData").getMethod("define",
Class.forName("net.minecraft.network.syncher.EntityDataAccessor"), Object.class);
return true;
} catch (ReflectiveOperationException | NullPointerException ex) {
return false;
}
}
/** Flag determining whether the server version is at least 1.20.3 or not */
@Getter
private static final boolean is1_20_3Plus = ReflectionUtils.classExists("net.minecraft.network.protocol.game.ClientboundResetScorePacket");
/**
* Returns class with given potential names in same order
* Returns class with given potential names in same order. For 1.17+ it takes packaged class names
* without "net.minecraft." prefix, for <1.17 it takes class name only.
*
* @param names
* possible class names
@@ -52,32 +43,19 @@ public class BukkitReflection {
* @throws ClassNotFoundException
* if class does not exist
*/
public static Class<?> getLegacyClass(@NotNull String... names) throws ClassNotFoundException {
public static Class<?> getClass(@NotNull String... names) throws ClassNotFoundException {
ClassLoader loader = BukkitReflection.class.getClassLoader();
for (String name : names) {
try {
return getLegacyClass(name);
} catch (ClassNotFoundException e) {
//not the first class name in array
if (minorVersion >= 17) {
return Class.forName("net.minecraft." + name);
} else {
return loader.loadClass("net.minecraft.server." + serverPackage + "." + name);
}
} catch (ClassNotFoundException | NullPointerException ignored) {
// not the first class name in array
}
}
throw new ClassNotFoundException("No class found with possible names " + Arrays.toString(names));
}
/**
* Returns class from given name. Supports modded servers, such as Thermos.
*
* @param name
* class name
* @return class from given name
* @throws ClassNotFoundException
* if class was not found
*/
public static Class<?> getLegacyClass(@NotNull String name) throws ClassNotFoundException {
try {
return BukkitReflection.class.getClassLoader().loadClass("net.minecraft.server." + serverPackage + "." + name);
} catch (NullPointerException e) {
// nested class not found
throw new ClassNotFoundException(name);
}
}
}
@@ -1,11 +1,10 @@
package me.neznamy.tab.platforms.bukkit.nms;
import lombok.*;
import me.neznamy.tab.platforms.bukkit.platform.BukkitPlatform;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.backend.EntityData;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.util.ComponentCache;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -32,8 +31,6 @@ public class DataWatcher implements EntityData {
private static Class<?> DataWatcherObject;
private static Constructor<?> newDataWatcherObject;
private static Class<?> DataWatcherRegistry;
private static Class<?> DataWatcherSerializer;
private static Object DataWatcherSerializer_BYTE;
private static Object DataWatcherSerializer_FLOAT;
private static Object DataWatcherSerializer_STRING;
@@ -41,7 +38,11 @@ public class DataWatcher implements EntityData {
private static Object DataWatcherSerializer_BOOLEAN;
private static final int armorStandFlagsPosition = getArmorStandFlagsPosition();
private static Method ChatSerializer_DESERIALIZE;
private static final ComponentCache<IChatBaseComponent, Object> componentCache = new ComponentCache<>(1000,
(component, clientVersion) -> ChatSerializer_DESERIALIZE.invoke(null, component.toString(clientVersion)));
/** Watched data */
private final Map<Integer, Item> dataValues = new HashMap<>();
@@ -57,7 +58,7 @@ public class DataWatcher implements EntityData {
} else if (BukkitReflection.getMinorVersion() >= 15) {
//1.15.x, 1.16.x
return 14;
} else if (BukkitReflection.getMinorVersion() >= 14) {
} else if (BukkitReflection.getMinorVersion() == 14) {
//1.14.x
return 13;
} else if (BukkitReflection.getMinorVersion() >= 10) {
@@ -74,70 +75,60 @@ public class DataWatcher implements EntityData {
*/
public static void load() throws ReflectiveOperationException {
int minorVersion = BukkitReflection.getMinorVersion();
Class<?> Entity;
if (BukkitReflection.isMojangMapped()) {
Entity = Class.forName("net.minecraft.world.entity.Entity");
DataWatcher = Class.forName("net.minecraft.network.syncher.SynchedEntityData");
DataWatcherObject = Class.forName("net.minecraft.network.syncher.EntityDataAccessor");
DataWatcherRegistry = Class.forName("net.minecraft.network.syncher.EntityDataSerializers");
DataWatcherSerializer = Class.forName("net.minecraft.network.syncher.EntityDataSerializer");
} else if (minorVersion >= 17) {
Entity = Class.forName("net.minecraft.world.entity.Entity");
DataWatcher = Class.forName("net.minecraft.network.syncher.DataWatcher");
DataWatcherObject = Class.forName("net.minecraft.network.syncher.DataWatcherObject");
DataWatcherRegistry = Class.forName("net.minecraft.network.syncher.DataWatcherRegistry");
DataWatcherSerializer = Class.forName("net.minecraft.network.syncher.DataWatcherSerializer");
} else {
Entity = BukkitReflection.getLegacyClass("Entity");
DataWatcher = BukkitReflection.getLegacyClass("DataWatcher");
if (minorVersion >= 9) {
DataWatcherObject = BukkitReflection.getLegacyClass("DataWatcherObject");
DataWatcherRegistry = BukkitReflection.getLegacyClass("DataWatcherRegistry");
DataWatcherSerializer = BukkitReflection.getLegacyClass("DataWatcherSerializer");
}
}
DataWatcher = BukkitReflection.getClass("network.syncher.SynchedEntityData", "network.syncher.DataWatcher", "DataWatcher");
if (minorVersion >= 7) {
newDataWatcher = DataWatcher.getConstructor(Entity);
Class<?> ChatSerializer = BukkitReflection.getClass("network.chat.Component$Serializer",
"network.chat.IChatBaseComponent$ChatSerializer", "IChatBaseComponent$ChatSerializer", "ChatSerializer");
ChatSerializer_DESERIALIZE = ReflectionUtils.getMethods(ChatSerializer, Object.class, String.class).get(0);
newDataWatcher = DataWatcher.getConstructor(BukkitReflection.getClass("world.entity.Entity", "Entity"));
} else {
newDataWatcher = DataWatcher.getConstructor();
}
if (minorVersion >= 9) {
DataWatcherObject = BukkitReflection.getClass("network.syncher.EntityDataAccessor",
"network.syncher.DataWatcherObject", "DataWatcherObject");
Class<?> dataWatcherRegistry = BukkitReflection.getClass("network.syncher.EntityDataSerializers",
"network.syncher.DataWatcherRegistry", "DataWatcherRegistry");
Class<?> dataWatcherSerializer = BukkitReflection.getClass("network.syncher.EntityDataSerializer",
"network.syncher.DataWatcherSerializer", "DataWatcherSerializer");
DataWatcher_register = ReflectionUtils.getMethod(
DataWatcher,
new String[]{"define", "register", "a", "m_135372_"}, // {Bukkit 1.20.2+, Bukkit, Bukkit 1.18+, Mohist 1.18.2}
new String[]{"define", "register", "a", "m_135372_"}, // {Mojang, Bukkit, Bukkit 1.18+, Mohist 1.18.2}
DataWatcherObject, Object.class
);
} else {
DataWatcher_register = ReflectionUtils.getMethod(DataWatcher, new String[]{"func_75682_a", "a"}, int.class, Object.class); // {Thermos 1.7.10, Bukkit}
}
if (minorVersion >= 19) {
DataWatcher_packDirty = ReflectionUtils.getMethod(DataWatcher, new String[] {"packDirty", "b"}); // {Mojang | 1.20.2+, 1.20.2-}
}
if (BukkitReflection.is1_19_3Plus()) {
DataWatcher_markDirty = ReflectionUtils.getMethods(DataWatcher, void.class, DataWatcherObject).get(0);
}
if (minorVersion >= 9) {
newDataWatcherObject = DataWatcherObject.getConstructor(int.class, DataWatcherSerializer);
DataWatcherSerializer_BYTE = ReflectionUtils.getField(DataWatcherRegistry, "BYTE", "a", "f_135027_").get(null); // Mohist 1.18.2
DataWatcherSerializer_FLOAT = ReflectionUtils.getField(DataWatcherRegistry, "FLOAT", "c", "f_135029_").get(null); // Mohist 1.18.2
DataWatcherSerializer_STRING = ReflectionUtils.getField(DataWatcherRegistry, "STRING", "d", "f_135030_").get(null); // Mohist 1.18.2
newDataWatcherObject = DataWatcherObject.getConstructor(int.class, dataWatcherSerializer);
DataWatcherSerializer_BYTE = ReflectionUtils.getField(dataWatcherRegistry, "BYTE", "a", "f_135027_").get(null); // Mohist 1.18.2
DataWatcherSerializer_FLOAT = ReflectionUtils.getField(dataWatcherRegistry, "FLOAT", "c", "f_135029_").get(null); // Mohist 1.18.2
DataWatcherSerializer_STRING = ReflectionUtils.getField(dataWatcherRegistry, "STRING", "d", "f_135030_").get(null); // Mohist 1.18.2
if (BukkitReflection.is1_19_3Plus()) {
DataWatcherSerializer_OPTIONAL_COMPONENT = ReflectionUtils.getField(DataWatcherRegistry, "OPTIONAL_COMPONENT", "g").get(null);
DataWatcherSerializer_OPTIONAL_COMPONENT = ReflectionUtils.getField(dataWatcherRegistry, "OPTIONAL_COMPONENT", "g").get(null);
if (BukkitReflection.is1_19_4Plus()) {
DataWatcherSerializer_BOOLEAN = ReflectionUtils.getField(DataWatcherRegistry, "BOOLEAN", "k").get(null);
DataWatcherSerializer_BOOLEAN = ReflectionUtils.getField(dataWatcherRegistry, "BOOLEAN", "k").get(null);
} else {
DataWatcherSerializer_BOOLEAN = ReflectionUtils.getField(DataWatcherRegistry, "BOOLEAN", "j").get(null);
DataWatcherSerializer_BOOLEAN = ReflectionUtils.getField(dataWatcherRegistry, "BOOLEAN", "j").get(null);
}
} else {
if (minorVersion >= 13) {
DataWatcherSerializer_OPTIONAL_COMPONENT = ReflectionUtils.getField(DataWatcherRegistry,
DataWatcherSerializer_OPTIONAL_COMPONENT = ReflectionUtils.getField(dataWatcherRegistry,
"OPTIONAL_COMPONENT", "f", "f_135032_").get(null); // Mohist 1.18.2
DataWatcherSerializer_BOOLEAN = ReflectionUtils.getField(DataWatcherRegistry,
DataWatcherSerializer_BOOLEAN = ReflectionUtils.getField(dataWatcherRegistry,
"BOOLEAN", "i", "f_135035_").get(null); // Mohist 1.18.2
} else {
DataWatcherSerializer_BOOLEAN = DataWatcherRegistry.getDeclaredField("h").get(null);
DataWatcherSerializer_BOOLEAN = dataWatcherRegistry.getDeclaredField("h").get(null);
}
}
} else {
DataWatcher_register = ReflectionUtils.getMethod(
DataWatcher,
new String[]{"func_75682_a", "a"}, int.class, // {Thermos 1.7.10, Bukkit}
Object.class
);
}
if (minorVersion >= 19) {
DataWatcher_packDirty = ReflectionUtils.getMethod(DataWatcher, new String[] {"packDirty", "b"}); // {Mojang, Bukkit}
}
if (BukkitReflection.is1_19_3Plus()) {
DataWatcher_markDirty = ReflectionUtils.getMethods(DataWatcher, void.class, DataWatcherObject).get(0);
}
}
@@ -176,7 +167,7 @@ public class DataWatcher implements EntityData {
public void setCustomName(@NotNull String customName, @NotNull ProtocolVersion clientVersion) {
if (BukkitReflection.getMinorVersion() >= 13) {
setValue(2, DataWatcherSerializer_OPTIONAL_COMPONENT,
Optional.of(((BukkitPlatform)TAB.getInstance().getPlatform()).toComponent(IChatBaseComponent.optimizedComponent(customName), clientVersion)));
Optional.of(componentCache.get(IChatBaseComponent.optimizedComponent(customName), clientVersion)));
} else if (BukkitReflection.getMinorVersion() >= 8) {
setValue(2, DataWatcherSerializer_STRING, customName);
} else {
@@ -1,123 +0,0 @@
package me.neznamy.tab.platforms.bukkit.nms;
import com.mojang.authlib.GameProfile;
import io.netty.channel.Channel;
import lombok.Getter;
import lombok.Setter;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.BukkitTabList;
import me.neznamy.tab.platforms.bukkit.header.HeaderFooter;
import me.neznamy.tab.platforms.bukkit.scoreboard.PacketScoreboard;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.util.ComponentCache;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.bukkit.Bukkit;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* Class holding all NMS classes, methods, fields and constructors used by TAB.
*/
public class NMSStorage {
/** Instance of this class */
@Getter @Setter private static NMSStorage instance;
/** Server's NMS/CraftBukkit package */
private final String serverPackage = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3];
/** Server's minor version */
@Getter private final int minorVersion = Integer.parseInt(serverPackage.split("_")[1]);
/** Basic universal values */
protected Class<?> Packet;
protected Class<?> NetworkManager;
public Class<?> EntityPlayer;
protected Class<?> EntityHuman;
protected Class<?> PlayerConnection;
// Ping field for 1.5.2 - 1.16.5, 1.17+ has Player#getPing()
public Field PING;
public Field PLAYER_CONNECTION;
public Field NETWORK_MANAGER;
public Field CHANNEL;
public Method getHandle;
public Method sendPacket;
public Method getProfile;
/** Chat components */
protected Class<?> ChatSerializer;
public Method ChatSerializer_DESERIALIZE;
public final ComponentCache<IChatBaseComponent, Object> componentCache = new ComponentCache<>(1000,
(component, clientVersion) -> ChatSerializer_DESERIALIZE.invoke(null, component.toString(clientVersion)));
/**
* Constructs new instance and attempts to load all fields, methods and constructors.
*/
@SneakyThrows
public NMSStorage() {
ProtocolVersion.UNKNOWN_SERVER_VERSION.setMinorVersion(minorVersion); //fixing compatibility with forks that set version field value to "Unknown"
loadClasses();
if (minorVersion >= 7) {
if (BukkitReflection.is1_20_2Plus()) {
NETWORK_MANAGER = ReflectionUtils.getOnlyField(PlayerConnection.getSuperclass(), NetworkManager);
} else {
NETWORK_MANAGER = ReflectionUtils.getOnlyField(PlayerConnection, NetworkManager);
}
sendPacket = ReflectionUtils.getMethods(PlayerConnection, void.class, Packet).get(0);
} else {
sendPacket = ReflectionUtils.getMethod(PlayerConnection, new String[]{"sendPacket"}, Packet);
}
if (minorVersion >= 8) {
ChatSerializer_DESERIALIZE = ReflectionUtils.getMethods(ChatSerializer, Object.class, String.class).get(0);
CHANNEL = ReflectionUtils.getOnlyField(NetworkManager, Channel.class);
// There is only supposed to be one, however there are exceptions:
// #1 - CatServer adds another method
// #2 - Random mods may perform deep hack into the server and add another one (see #1089)
// Get first and hope for the best, alternatively players may not have correct skins in layout, but who cares
getProfile = ReflectionUtils.getMethods(EntityHuman, GameProfile.class).get(0);
}
PLAYER_CONNECTION = ReflectionUtils.getOnlyField(EntityPlayer, PlayerConnection);
getHandle = Class.forName("org.bukkit.craftbukkit." + serverPackage + ".entity.CraftPlayer").getMethod("getHandle");
BukkitTabList.load();
DataWatcher.load();
PacketEntityView.load();
PacketScoreboard.load();
HeaderFooter.findInstance();
if (minorVersion < 17) {
PING = ReflectionUtils.getField(EntityPlayer, "ping", "field_71138_i"); // 1.5.2 - 1.16.5, 1.7.10 Thermos
}
}
private void loadClasses() throws ClassNotFoundException {
if (minorVersion >= 17 && ReflectionUtils.classExists("net.minecraft.ChatFormatting")) {
ChatSerializer = Class.forName("net.minecraft.network.chat.Component$Serializer");
EntityHuman = Class.forName("net.minecraft.world.entity.player.Player");
NetworkManager = Class.forName("net.minecraft.network.Connection");
Packet = Class.forName("net.minecraft.network.protocol.Packet");
EntityPlayer = Class.forName("net.minecraft.server.level.ServerPlayer");
PlayerConnection = Class.forName("net.minecraft.server.network.ServerGamePacketListenerImpl");
} else if (minorVersion >= 17) {
ChatSerializer = Class.forName("net.minecraft.network.chat.IChatBaseComponent$ChatSerializer");
EntityHuman = Class.forName("net.minecraft.world.entity.player.EntityHuman");
NetworkManager = Class.forName("net.minecraft.network.NetworkManager");
Packet = Class.forName("net.minecraft.network.protocol.Packet");
EntityPlayer = Class.forName("net.minecraft.server.level.EntityPlayer");
PlayerConnection = Class.forName("net.minecraft.server.network.PlayerConnection");
} else {
EntityHuman = BukkitReflection.getLegacyClass("EntityHuman");
Packet = BukkitReflection.getLegacyClass("Packet");
EntityPlayer = BukkitReflection.getLegacyClass("EntityPlayer");
PlayerConnection = BukkitReflection.getLegacyClass("PlayerConnection");
NetworkManager = BukkitReflection.getLegacyClass("NetworkManager");
if (minorVersion >= 7) {
ChatSerializer = BukkitReflection.getLegacyClass("IChatBaseComponent$ChatSerializer", "ChatSerializer");
}
}
}
}
@@ -4,7 +4,6 @@ import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.backend.EntityData;
import me.neznamy.tab.shared.backend.Location;
import me.neznamy.tab.shared.backend.entityview.EntityView;
@@ -255,7 +254,7 @@ public class PacketEntityView implements EntityView {
SpawnEntity_EntityType.set(nmsPacket, entityIds.get((EntityType) entityType));
player.sendPacket(nmsPacket);
}
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 15) {
if (BukkitReflection.getMinorVersion() >= 15) {
updateEntityMetadata(entityId, data);
}
}
@@ -0,0 +1,47 @@
package me.neznamy.tab.platforms.bukkit.nms;
import lombok.Getter;
import lombok.SneakyThrows;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class PacketSender {
private static Method getHandle;
private static Field PLAYER_CONNECTION;
private static Method sendPacket;
@Getter private static boolean available;
public static void tryLoad() {
try {
Class<?> Packet = BukkitReflection.getClass("network.protocol.Packet", "Packet");
Class<?> EntityPlayer = BukkitReflection.getClass("server.level.ServerPlayer", "server.level.EntityPlayer", "EntityPlayer");
Class<?> PlayerConnection = BukkitReflection.getClass("server.network.ServerGamePacketListenerImpl",
"server.network.PlayerConnection", "PlayerConnection");
getHandle = Class.forName("org.bukkit.craftbukkit." + BukkitReflection.getServerPackage() + ".entity.CraftPlayer").getMethod("getHandle");
PLAYER_CONNECTION = ReflectionUtils.getOnlyField(EntityPlayer, PlayerConnection);
if (BukkitReflection.getMinorVersion() >= 7) {
sendPacket = ReflectionUtils.getMethods(PlayerConnection, void.class, Packet).get(0);
} else {
sendPacket = ReflectionUtils.getMethod(PlayerConnection, new String[]{"sendPacket"}, Packet);
}
available = true;
} catch (Exception e) {
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.RED.getFormat() + "[TAB] Failed to initialize NMS fields for " +
"sending packets due to a compatibility error. Majority of the features will not work / be very limited. " +
"Please update the plugin to a version with proper support for your server version.");
}
}
@SneakyThrows
public static void sendPacket(@NotNull Player player, @NotNull Object packet) {
if (!player.isOnline() || sendPacket == null) return;
sendPacket.invoke(PLAYER_CONNECTION.get(getHandle.invoke(player)), packet);
}
}
@@ -0,0 +1,41 @@
package me.neznamy.tab.platforms.bukkit.nms;
import lombok.SneakyThrows;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class PingRetriever {
private static Method getHandle;
private static Field PING;
public static void tryLoad() {
try {
if (BukkitReflection.getMinorVersion() < 17) {
getHandle = Class.forName("org.bukkit.craftbukkit." + BukkitReflection.getServerPackage() + ".entity.CraftPlayer").getMethod("getHandle");
Class<?> EntityPlayer = BukkitReflection.getClass("server.level.ServerPlayer", "server.level.EntityPlayer", "EntityPlayer");
PING = ReflectionUtils.getField(EntityPlayer, "ping", "field_71138_i"); // 1.5.2 - 1.16.5, 1.7.10 Thermos
}
} catch (Exception e) {
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.RED.getFormat() + "[TAB] Failed to initialize NMS fields for " +
"getting player's ping due to a compatibility error. This will " +
"result in ping showing \"-1\". " +
"Please update the plugin a to version with native support for your server version to properly show ping.");
}
}
@SneakyThrows
public static int getPing(@NotNull Player player) {
if (BukkitReflection.getMinorVersion() >= 17) {
return player.getPing();
}
if (PING == null) return -1;
return PING.getInt(getHandle.invoke(player));
}
}
@@ -1,9 +1,15 @@
package me.neznamy.tab.platforms.bukkit.platform;
import de.myzelyam.api.vanish.VanishAPI;
import lombok.Getter;
import lombok.SneakyThrows;
import me.clip.placeholderapi.PlaceholderAPI;
import me.neznamy.tab.platforms.bukkit.*;
import me.neznamy.tab.platforms.bukkit.header.HeaderFooter;
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
import me.neznamy.tab.platforms.bukkit.nms.PacketSender;
import me.neznamy.tab.platforms.bukkit.nms.PingRetriever;
import me.neznamy.tab.platforms.bukkit.scoreboard.ScoreboardLoader;
import me.neznamy.tab.shared.GroupManager;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.TabConstants;
@@ -15,7 +21,6 @@ import me.neznamy.tab.platforms.bukkit.features.BukkitTabExpansion;
import me.neznamy.tab.platforms.bukkit.features.PerWorldPlayerList;
import me.neznamy.tab.platforms.bukkit.features.WitherBossBar;
import me.neznamy.tab.platforms.bukkit.features.BukkitNameTagX;
import me.neznamy.tab.platforms.bukkit.nms.NMSStorage;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.backend.BackendPlatform;
import me.neznamy.tab.shared.features.PlaceholderManagerImpl;
@@ -49,23 +54,40 @@ import java.lang.reflect.Field;
@Getter
public class BukkitPlatform implements BackendPlatform {
/** Plugin instance for registering tasks and events */
/** Flag tracking plugin presence */
private final boolean premiumVanish = Bukkit.getPluginManager().isPluginEnabled("PremiumVanish");
/**
* Plugin instance for registering tasks and events
*/
@NotNull
private final JavaPlugin plugin;
/** Variables checking presence of other plugins to hook into */
/**
* Variables checking presence of other plugins to hook into
*/
private final boolean placeholderAPI = ReflectionUtils.classExists("me.clip.placeholderapi.PlaceholderAPI");
/** NMS server to get TPS from on spigot */
@Nullable private Object server;
/**
* NMS server to get TPS from on spigot
*/
@Nullable
private Object server;
/** TPS field */
@Nullable private Field spigotTps;
/**
* TPS field
*/
@Nullable
private Field spigotTps;
/** Detection for presence of Paper's TPS getter */
/**
* Detection for presence of Paper's TPS getter
*/
private final boolean paperTps = ReflectionUtils.methodExists(Bukkit.class, "getTPS");
/** Detection for presence of Paper's MSPT getter */
/**
* Detection for presence of Paper's MSPT getter
*/
private final boolean paperMspt = ReflectionUtils.methodExists(Bukkit.class, "getAverageTickTime");
public BukkitPlatform(@NotNull JavaPlugin plugin) {
@@ -76,6 +98,13 @@ public class BukkitPlatform implements BackendPlatform {
} catch (ReflectiveOperationException e) {
//not spigot
}
PingRetriever.tryLoad();
PacketSender.tryLoad();
ScoreboardLoader.findInstance();
if (BukkitReflection.getMinorVersion() >= 8) {
BukkitPipelineInjector.tryLoad();
HeaderFooter.findInstance();
}
}
@Override
@@ -106,20 +135,20 @@ public class BukkitPlatform implements BackendPlatform {
}
// 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 -> ((TabPlayer)p).getPing());
p -> ((TabPlayer) p).getPing());
BackendPlatform.super.registerPlaceholders();
}
@Override
@Nullable
public PipelineInjector createPipelineInjector() {
return NMSStorage.getInstance().getMinorVersion() >= 8 ? new BukkitPipelineInjector() : null;
return BukkitReflection.getMinorVersion() >= 8 && BukkitPipelineInjector.isAvailable() ? new BukkitPipelineInjector() : null;
}
@Override
@NotNull
public NameTag getUnlimitedNameTags() {
return new BukkitNameTagX(plugin);
return BukkitReflection.getMinorVersion() >= 8 ? new BukkitNameTagX(plugin) : new NameTag();
}
@Override
@@ -169,7 +198,7 @@ public class BukkitPlatform implements BackendPlatform {
Bukkit.getScheduler().runTask(plugin, () -> {
long time = System.nanoTime();
ppl[0].updateValue(p, placeholderAPI ? PlaceholderAPI.setPlaceholders((Player) p.getPlayer(), syncedPlaceholder) : identifier);
TAB.getInstance().getCPUManager().addPlaceholderTime(identifier, System.nanoTime()-time);
TAB.getInstance().getCPUManager().addPlaceholderTime(identifier, System.nanoTime() - time);
});
return null;
});
@@ -218,7 +247,7 @@ public class BukkitPlatform implements BackendPlatform {
metrics.addCustomChart(new SimplePie(TabConstants.MetricsChart.PERMISSION_SYSTEM,
() -> TAB.getInstance().getGroupManager().getPermissionPlugin()));
metrics.addCustomChart(new SimplePie(TabConstants.MetricsChart.SERVER_VERSION,
() -> "1." + TAB.getInstance().getServerVersion().getMinorVersion() + ".x"));
() -> "1." + BukkitReflection.getMinorVersion() + ".x"));
}
@Override
@@ -233,19 +262,6 @@ public class BukkitPlatform implements BackendPlatform {
return plugin.getDataFolder();
}
/**
* Converts internal component class to platform's component class
*
* @param component
* Component to convert
* @param version
* Game version to convert component for
* @return Converted component
*/
public Object toComponent(@NotNull IChatBaseComponent component, @NotNull ProtocolVersion version) {
return NMSStorage.getInstance().componentCache.get(component, version);
}
@Override
@NotNull
public GroupManager detectPermissionPlugin() {
@@ -275,11 +291,26 @@ public class BukkitPlatform implements BackendPlatform {
@Override
public double getMSPT() {
if (paperMspt) return Bukkit.getAverageTickTime();
return -1;
if (paperMspt) return Bukkit.getAverageTickTime();
return -1;
}
public void runSync(@NotNull Entity entity, @NotNull Runnable task) {
Bukkit.getScheduler().runTask(plugin, task);
}
public void runEntityTask(@NotNull Entity entity, @NotNull Runnable task) {
task.run();
}
@Override
public boolean canSee(@NotNull TabPlayer viewer, @NotNull TabPlayer target) {
Player bViewer = ((BukkitTabPlayer)viewer).getPlayer();
Player bTarget = ((BukkitTabPlayer)target).getPlayer();
//noinspection ConstantConditions
if (premiumVanish && VanishAPI.canSee(bViewer, bTarget)) return true;
if (bViewer.canSee(bTarget)) return true;
return BackendPlatform.super.canSee(viewer, target);
}
}
@@ -74,9 +74,10 @@ public class FoliaPlatform extends BukkitPlatform {
* @param task
* Task to run
*/
@Override
@SneakyThrows
@SuppressWarnings("JavaReflectionMemberAccess")
private void runSync(@NotNull Entity entity, @NotNull Runnable task) {
public void runSync(@NotNull Entity entity, @NotNull Runnable task) {
Object entityScheduler = Entity.class.getMethod("getScheduler").invoke(entity);
Consumer<?> consumer = $ -> task.run(); // Reflection and lambdas don't go together
entityScheduler.getClass().getMethod("run", Plugin.class, Consumer.class, Runnable.class)
@@ -1,6 +1,7 @@
package me.neznamy.tab.platforms.bukkit.scoreboard;
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;
@@ -13,13 +14,12 @@ import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.scoreboard.NameTagVisibility;
import org.bukkit.scoreboard.Objective;
import org.bukkit.scoreboard.RenderType;
import org.bukkit.scoreboard.Team;
import org.bukkit.scoreboard.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.concurrent.CountDownLatch;
/**
* Scoreboard implementation using Bukkit API (1.5.2+). It has several issues:
@@ -47,49 +47,68 @@ public class BukkitScoreboard extends Scoreboard<BukkitTabPlayer> {
/** Flag tracking whether this implementation is available for use */
@Getter
private static final boolean available = ReflectionUtils.methodExists(Bukkit.class, "getScoreboardManager");
private static final boolean available;
/** Array of display slots, because the Bukkit order does not match network ordinals */
private static org.bukkit.scoreboard.DisplaySlot[] slots;
/** Server's minor version */
private final int serverMinorVersion = BukkitReflection.getMinorVersion();
@NotNull
protected final org.bukkit.scoreboard.Scoreboard scoreboard = Bukkit.getScoreboardManager().getNewScoreboard();
protected org.bukkit.scoreboard.Scoreboard scoreboard;
static {
available = ReflectionUtils.methodExists(Bukkit.class, "getScoreboardManager");
if (available) {
slots = new org.bukkit.scoreboard.DisplaySlot[]{
org.bukkit.scoreboard.DisplaySlot.PLAYER_LIST,
org.bukkit.scoreboard.DisplaySlot.SIDEBAR,
org.bukkit.scoreboard.DisplaySlot.BELOW_NAME
};
}
}
@SneakyThrows
public BukkitScoreboard(@NotNull BukkitTabPlayer player) {
super(player);
// Put player into a different scoreboard for per-player view
player.getPlayer().setScoreboard(scoreboard);
runSync(() -> {
scoreboard = Bukkit.getScoreboardManager().getNewScoreboard();
player.getPlayer().setScoreboard(scoreboard);
});
}
@Override
public void setDisplaySlot(@NotNull DisplaySlot slot, @NotNull String objective) {
public void setDisplaySlot0(int slot, @NotNull String objective) {
checkPlayerScoreboard();
scoreboard.getObjective(objective).setDisplaySlot(org.bukkit.scoreboard.DisplaySlot.valueOf(slot.name()));
scoreboard.getObjective(objective).setDisplaySlot(slots[slot]);
}
@Override
public void setScore0(@NotNull String objective, @NotNull String playerName, int score) {
public void setScore0(@NotNull String objective, @NotNull String scoreHolder, int score,
@Nullable IChatBaseComponent displayName, @Nullable IChatBaseComponent numberFormat) {
checkPlayerScoreboard();
if (serverMinorVersion >= 7 && TAB.getInstance().getServerVersion().getNetworkId() >= ProtocolVersion.V1_7_8.getNetworkId()) {
scoreboard.getObjective(objective).getScore(playerName).setScore(score);
scoreboard.getObjective(objective).getScore(scoreHolder).setScore(score);
} else {
scoreboard.getObjective(objective).getScore(Bukkit.getOfflinePlayer(playerName)).setScore(score);
scoreboard.getObjective(objective).getScore(Bukkit.getOfflinePlayer(scoreHolder)).setScore(score);
}
}
@Override
public void removeScore0(@NotNull String objective, @NotNull String playerName) {
public void removeScore0(@NotNull String objective, @NotNull String scoreHolder) {
checkPlayerScoreboard();
if (serverMinorVersion >= 7 && TAB.getInstance().getServerVersion().getNetworkId() >= ProtocolVersion.V1_7_8.getNetworkId()) {
scoreboard.resetScores(playerName);
scoreboard.resetScores(scoreHolder);
} else {
scoreboard.resetScores(Bukkit.getOfflinePlayer(playerName));
scoreboard.resetScores(Bukkit.getOfflinePlayer(scoreHolder));
}
}
@Override
public void registerObjective0(@NotNull String objectiveName, @NotNull String title, @NotNull HealthDisplay display) {
public void registerObjective0(@NotNull String objectiveName, @NotNull String title, int display,
@Nullable IChatBaseComponent numberFormat) {
checkPlayerScoreboard();
newObjective(objectiveName, "dummy", title, display);
}
@@ -101,11 +120,12 @@ public class BukkitScoreboard extends Scoreboard<BukkitTabPlayer> {
}
@Override
public void updateObjective0(@NotNull String objectiveName, @NotNull String title, @NotNull HealthDisplay display) {
public void updateObjective0(@NotNull String objectiveName, @NotNull String title, int display,
@Nullable IChatBaseComponent numberFormat) {
checkPlayerScoreboard();
Objective obj = scoreboard.getObjective(objectiveName);
setDisplayName(obj, title);
if (serverMinorVersion >= 14) obj.setRenderType(RenderType.valueOf(display.name()));
if (serverMinorVersion >= 14) obj.setRenderType(RenderType.values()[display]);
}
@Override
@@ -154,9 +174,14 @@ public class BukkitScoreboard extends Scoreboard<BukkitTabPlayer> {
team.setCanSeeFriendlyInvisibles((options & 0x02) != 0);
}
public void newObjective(String objectiveName, String criteria, String title, @NotNull HealthDisplay display) {
public void newObjective(String objectiveName, String criteria, String title, int display) {
if (serverMinorVersion >= 14) {
scoreboard.registerNewObjective(objectiveName, criteria, transform(title, 128, Limitations.SCOREBOARD_TITLE_PRE_1_13), RenderType.valueOf(display.name()));
scoreboard.registerNewObjective(
objectiveName,
criteria,
transform(title, 128, Limitations.SCOREBOARD_TITLE_PRE_1_13),
RenderType.values()[display]
);
} else {
setDisplayName(scoreboard.registerNewObjective(objectiveName, display == HealthDisplay.HEARTS ? "health" : "dummy"), title);
}
@@ -210,11 +235,34 @@ public class BukkitScoreboard extends Scoreboard<BukkitTabPlayer> {
* Sadly there is no efficient solution to this, which md_5 fails
* to understand and keeps saying you don't need packets for scoreboards.
*/
@SneakyThrows
private void checkPlayerScoreboard() {
if (player.getPlayer().getScoreboard() != scoreboard) {
player.getPlayer().setScoreboard(scoreboard);
TAB.getInstance().getErrorManager().printError("Player " + player.getName() + " was in a different scoreboard " +
"than expected. This means another plugin changed player's scoreboard.");
runSync(() -> player.getPlayer().setScoreboard(scoreboard));
}
}
/**
* Runs the task if in main thread. If not, submits the task and waits for the result.
*
* @param task
* Task to run
*/
@SneakyThrows
private void runSync(@NotNull Runnable task) {
if (Bukkit.isPrimaryThread()) {
// Server thread (plugin reload)
task.run();
} else {
// Plugin thread (player join)
CountDownLatch c = new CountDownLatch(1);
player.getPlatform().runSync(player.getPlayer(), () -> {
task.run();
c.countDown();
});
c.await();
}
}
}
@@ -0,0 +1,67 @@
package me.neznamy.tab.platforms.bukkit.scoreboard;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.Scoreboard;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
public class NullScoreboard extends Scoreboard<BukkitTabPlayer> {
public NullScoreboard(@NotNull BukkitTabPlayer player) {
super(player);
}
@Override
protected void setDisplaySlot0(int slot, @NotNull String objective) {
// Do nothing
}
@Override
protected void setScore0(@NotNull String objective, @NotNull String scoreHolder, int score,
@Nullable IChatBaseComponent displayName, @Nullable IChatBaseComponent numberFormat) {
// Do nothing
}
@Override
protected void removeScore0(@NotNull String objective, @NotNull String scoreHolder) {
// Do nothing
}
@Override
protected void registerObjective0(@NotNull String objectiveName, @NotNull String title, int display,
@Nullable IChatBaseComponent numberFormat) {
// Do nothing
}
@Override
protected void unregisterObjective0(@NotNull String objectiveName) {
// Do nothing
}
@Override
protected void updateObjective0(@NotNull String objectiveName, @NotNull String title, int display,
@Nullable IChatBaseComponent numberFormat) {
// Do nothing
}
@Override
protected void registerTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix,
@NotNull NameVisibility visibility, @NotNull CollisionRule collision,
@NotNull Collection<String> players, int options) {
// Do nothing
}
@Override
protected void unregisterTeam0(@NotNull String name) {
// Do nothing
}
@Override
protected void updateTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix,
@NotNull NameVisibility visibility, @NotNull CollisionRule collision, int options) {
// Do nothing
}
}
@@ -7,8 +7,10 @@ import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.util.ComponentCache;
import me.neznamy.tab.shared.util.ReflectionUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
@@ -16,249 +18,98 @@ import java.lang.reflect.Method;
import java.util.Collection;
import java.util.List;
import static me.neznamy.tab.platforms.bukkit.nms.BukkitReflection.getLegacyClass;
/**
* Scoreboard implementation for Bukkit, which uses packets
* to send scoreboards to use the full potential on all versions
* and server software without any artificial limits.
*/
@SuppressWarnings({"unchecked", "rawtypes"})
@SuppressWarnings("unchecked")
public class PacketScoreboard extends Scoreboard<BukkitTabPlayer> {
@Getter
private static boolean available;
private static Class<?> Component;
private static Class<?> Scoreboard;
private static Class<?> ScoreboardObjective;
private static Object emptyScoreboard;
private static Field IScoreboardCriteria_self;
private static Class<?> NumberFormat;
private static Constructor<?> newFixedFormat;
// PacketPlayOutScoreboardDisplayObjective
public static Class<?> DisplayObjectiveClass;
private static Constructor<?> newDisplayObjective;
public static Field DisplayObjective_POSITION;
public static Field DisplayObjective_OBJECTIVE_NAME;
// PacketPlayOutScoreboardScore
private static Class<Enum> EnumScoreboardAction;
private static Constructor<?> newScorePacket_1_13;
private static Constructor<?> newScorePacket_String;
private static Constructor<?> newScorePacket;
private static Constructor<?> newScoreboardScore;
private static Method ScoreboardScore_setScore;
// PacketPlayOutScoreboardObjective
// Objective packet
public static Class<?> ObjectivePacketClass;
private static Constructor<?> newObjectivePacket;
public static Field Objective_OBJECTIVE_NAME;
public static Field Objective_METHOD;
private static Field Objective_RENDER_TYPE;
private static Field Objective_DISPLAY_NAME;
private static Class<Enum> EnumScoreboardHealthDisplay;
private static Constructor<?> newScoreboardObjective;
private static Method ScoreboardObjective_setDisplayName;
private static Enum<?>[] healthDisplays;
private static Object IScoreboardCriteria_dummy;
// PacketPlayOutScoreboardTeamStorage
public static Class<?> TeamPacketClass;
private static Constructor<?> newTeamPacket;
private static Method TeamPacketConstructor_of;
private static Method TeamPacketConstructor_ofBoolean;
public static Field TeamPacket_NAME;
public static Field TeamPacket_ACTION;
public static Field TeamPacket_PLAYERS;
private static Class<Enum> EnumNameTagVisibility;
private static Class<Enum> EnumTeamPush;
private static Constructor<?> newScoreboardTeam;
private static Method ScoreboardTeam_getPlayerNameSet;
private static Method ScoreboardTeam_setNameTagVisibility;
private static Method ScoreboardTeam_setCollisionRule;
private static Method ScoreboardTeam_setPrefix;
private static Method ScoreboardTeam_setSuffix;
private static Method ScoreboardTeam_setColor;
private static Method ScoreboardTeam_setAllowFriendlyFire;
private static Method ScoreboardTeam_setCanSeeFriendlyInvisibles;
private static ScorePacketData scorePacketData;
public static TeamPacketData teamPacketData;
public static DisplayPacketData displayPacketData;
private static Enum[] scoreboardActions;
private static Enum[] displaySlots;
private static Enum[] nameVisibilities;
private static Enum[] collisionRules;
private static Enum[] healthDisplays;
private static Enum[] chatFormats;
private static Method ChatSerializer_DESERIALIZE;
private static final ComponentCache<IChatBaseComponent, Object> componentCache = new ComponentCache<>(1000,
(component, clientVersion) -> ChatSerializer_DESERIALIZE.invoke(null, component.toString(clientVersion)));
public static void load() throws ReflectiveOperationException {
int minorVersion = BukkitReflection.getMinorVersion();
Class<?> scoreboardTeam;
Class<?> scoreboardObjective;
Class<?> scoreboardScoreClass;
Class<?> IScoreboardCriteria;
Class<?> scoreboard;
Class<?> scorePacketClass;
Class<?> IChatBaseComponent = null;
Class<Enum> enumChatFormatClass;
if (BukkitReflection.isMojangMapped()) {
IChatBaseComponent = Class.forName("net.minecraft.network.chat.Component");
enumChatFormatClass = (Class<Enum>) Class.forName("net.minecraft.ChatFormatting");
DisplayObjectiveClass = Class.forName("net.minecraft.network.protocol.game.ClientboundSetDisplayObjectivePacket");
ObjectivePacketClass = Class.forName("net.minecraft.network.protocol.game.ClientboundSetObjectivePacket");
scoreboard = Class.forName("net.minecraft.world.scores.Scoreboard");
scorePacketClass = Class.forName("net.minecraft.network.protocol.game.ClientboundSetScorePacket");
scoreboardObjective = Class.forName("net.minecraft.world.scores.Objective");
scoreboardScoreClass = Class.forName("net.minecraft.world.scores.Score");
IScoreboardCriteria = Class.forName("net.minecraft.world.scores.criteria.ObjectiveCriteria");
EnumScoreboardHealthDisplay = (Class<Enum>) Class.forName("net.minecraft.world.scores.criteria.ObjectiveCriteria$RenderType");
EnumScoreboardAction = (Class<Enum>) Class.forName("net.minecraft.server.ServerScoreboard$Method");
TeamPacketClass = Class.forName("net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket");
scoreboardTeam = Class.forName("net.minecraft.world.scores.PlayerTeam");
EnumNameTagVisibility = (Class<Enum>) Class.forName("net.minecraft.world.scores.Team$Visibility");
EnumTeamPush = (Class<Enum>) Class.forName("net.minecraft.world.scores.Team$CollisionRule");
} else if (minorVersion >= 17) {
IChatBaseComponent = Class.forName("net.minecraft.network.chat.IChatBaseComponent");
enumChatFormatClass = (Class<Enum>) Class.forName("net.minecraft.EnumChatFormat");
DisplayObjectiveClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutScoreboardDisplayObjective");
ObjectivePacketClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutScoreboardObjective");
scoreboard = Class.forName("net.minecraft.world.scores.Scoreboard");
scorePacketClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutScoreboardScore");
scoreboardObjective = Class.forName("net.minecraft.world.scores.ScoreboardObjective");
scoreboardScoreClass = Class.forName("net.minecraft.world.scores.ScoreboardScore");
IScoreboardCriteria = Class.forName("net.minecraft.world.scores.criteria.IScoreboardCriteria");
EnumScoreboardHealthDisplay = (Class<Enum>) Class.forName("net.minecraft.world.scores.criteria.IScoreboardCriteria$EnumScoreboardHealthDisplay");
EnumScoreboardAction = (Class<Enum>) Class.forName("net.minecraft.server.ScoreboardServer$Action");
TeamPacketClass = Class.forName("net.minecraft.network.protocol.game.PacketPlayOutScoreboardTeam");
scoreboardTeam = Class.forName("net.minecraft.world.scores.ScoreboardTeam");
EnumNameTagVisibility = (Class<Enum>) Class.forName("net.minecraft.world.scores.ScoreboardTeamBase$EnumNameTagVisibility");
EnumTeamPush = (Class<Enum>) Class.forName("net.minecraft.world.scores.ScoreboardTeamBase$EnumTeamPush");
} else {
enumChatFormatClass = (Class<Enum>) getLegacyClass("EnumChatFormat");
DisplayObjectiveClass = getLegacyClass("PacketPlayOutScoreboardDisplayObjective", "Packet208SetScoreboardDisplayObjective");
ObjectivePacketClass = getLegacyClass("PacketPlayOutScoreboardObjective", "Packet206SetScoreboardObjective");
TeamPacketClass = getLegacyClass("PacketPlayOutScoreboardTeam", "Packet209SetScoreboardTeam");
scorePacketClass = getLegacyClass("PacketPlayOutScoreboardScore", "Packet207SetScoreboardScore");
scoreboard = getLegacyClass("Scoreboard");
scoreboardObjective = getLegacyClass("ScoreboardObjective");
scoreboardScoreClass = getLegacyClass("ScoreboardScore");
IScoreboardCriteria = getLegacyClass("IScoreboardCriteria", "IObjective"); // 1.5.1+, 1.5
scoreboardTeam = getLegacyClass("ScoreboardTeam");
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) {
IChatBaseComponent = getLegacyClass("IChatBaseComponent");
Component = BukkitReflection.getClass("network.chat.Component", "network.chat.IChatBaseComponent", "IChatBaseComponent");
Class<?> ChatSerializer = BukkitReflection.getClass("network.chat.Component$Serializer",
"network.chat.IChatBaseComponent$ChatSerializer", "IChatBaseComponent$ChatSerializer", "ChatSerializer");
ChatSerializer_DESERIALIZE = ReflectionUtils.getMethods(ChatSerializer, Object.class, String.class).get(0);
}
if (minorVersion >= 8) {
EnumScoreboardHealthDisplay = (Class<Enum>) getLegacyClass("IScoreboardCriteria$EnumScoreboardHealthDisplay", "EnumScoreboardHealthDisplay");
EnumScoreboardAction = (Class<Enum>) getLegacyClass("ScoreboardServer$Action", "PacketPlayOutScoreboardScore$EnumScoreboardAction", "EnumScoreboardAction");
EnumNameTagVisibility = (Class<Enum>) getLegacyClass("ScoreboardTeamBase$EnumNameTagVisibility", "EnumNameTagVisibility");
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 >= 9) {
EnumTeamPush = (Class<Enum>) getLegacyClass("ScoreboardTeamBase$EnumTeamPush");
if (minorVersion < 13) {
ScoreboardObjective_setDisplayName = ReflectionUtils.getOnlyMethod(ScoreboardObjective, void.class, String.class);
}
}
emptyScoreboard = scoreboard.getConstructor().newInstance();
IScoreboardCriteria_self = ReflectionUtils.getFields(IScoreboardCriteria, IScoreboardCriteria).get(0);
if (BukkitReflection.is1_20_2Plus()) {
Class<?> DisplaySlot = Class.forName("net.minecraft.world.scores.DisplaySlot");
DisplayObjective_POSITION = ReflectionUtils.getOnlyField(DisplayObjectiveClass, DisplaySlot);
newDisplayObjective = DisplayObjectiveClass.getConstructor(DisplaySlot, scoreboardObjective);
displaySlots = (Enum[]) DisplaySlot.getDeclaredMethod("values").invoke(null);
} else {
DisplayObjective_POSITION = ReflectionUtils.getOnlyField(DisplayObjectiveClass, int.class);
newDisplayObjective = DisplayObjectiveClass.getConstructor(int.class, scoreboardObjective);
}
DisplayObjective_OBJECTIVE_NAME = ReflectionUtils.getOnlyField(DisplayObjectiveClass, String.class);
newScoreboardObjective = ReflectionUtils.getOnlyConstructor(scoreboardObjective);
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);
newScoreboardScore = scoreboardScoreClass.getConstructor(scoreboard, scoreboardObjective, String.class);
newScoreboardTeam = scoreboardTeam.getConstructor(scoreboard, String.class);
TeamPacket_NAME = ReflectionUtils.getFields(TeamPacketClass, String.class).get(0);
TeamPacket_ACTION = ReflectionUtils.getInstanceFields(TeamPacketClass, int.class).get(0);
TeamPacket_PLAYERS = ReflectionUtils.getOnlyField(TeamPacketClass, Collection.class);
ScoreboardTeam_getPlayerNameSet = ReflectionUtils.getOnlyMethod(scoreboardTeam, Collection.class);
if (minorVersion >= 13) {
newScorePacket_1_13 = scorePacketClass.getConstructor(EnumScoreboardAction, String.class, String.class, int.class);
newObjectivePacket = ObjectivePacketClass.getConstructor(scoreboardObjective, int.class);
Objective_DISPLAY_NAME = ReflectionUtils.getOnlyField(ObjectivePacketClass, IChatBaseComponent);
ScoreboardTeam_setColor = ReflectionUtils.getOnlyMethod(scoreboardTeam, void.class, enumChatFormatClass);
scoreboardActions = new Enum[] {
Enum.valueOf(EnumScoreboardAction, "CHANGE"),
Enum.valueOf(EnumScoreboardAction, "REMOVE")
};
chatFormats = (Enum[]) enumChatFormatClass.getMethod("values").invoke(null);
} else {
newScorePacket_String = scorePacketClass.getConstructor(String.class);
newObjectivePacket = ObjectivePacketClass.getConstructor();
Objective_DISPLAY_NAME = ReflectionUtils.getFields(ObjectivePacketClass, String.class).get(1);
if (minorVersion >= 8) {
newScorePacket = scorePacketClass.getConstructor(scoreboardScoreClass);
Objective_RENDER_TYPE = ReflectionUtils.getOnlyField(ObjectivePacketClass, EnumScoreboardHealthDisplay);
} else {
newScorePacket = scorePacketClass.getConstructor(scoreboardScoreClass, int.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();
available = true;
} catch (Exception ignored) {
// Print exception to find out what went wrong
}
if (minorVersion >= 8) {
nameVisibilities = new Enum[] {
Enum.valueOf(EnumNameTagVisibility, "ALWAYS"),
Enum.valueOf(EnumNameTagVisibility, "NEVER"),
Enum.valueOf(EnumNameTagVisibility, "HIDE_FOR_OTHER_TEAMS"),
Enum.valueOf(EnumNameTagVisibility, "HIDE_FOR_OWN_TEAM")
};
healthDisplays = new Enum[] {
Enum.valueOf(EnumScoreboardHealthDisplay, "INTEGER"),
Enum.valueOf(EnumScoreboardHealthDisplay, "HEARTS")
};
}
if (minorVersion >= 9) {
ScoreboardTeam_setCollisionRule = ReflectionUtils.getOnlyMethod(scoreboardTeam, void.class, EnumTeamPush);
collisionRules = (Enum[]) EnumTeamPush.getMethod("values").invoke(null); // <1.13 has wrong enum constant names
}
if (minorVersion >= 17) {
TeamPacketConstructor_of = ReflectionUtils.getOnlyMethod(TeamPacketClass, TeamPacketClass, scoreboardTeam);
TeamPacketConstructor_ofBoolean = ReflectionUtils.getOnlyMethod(TeamPacketClass, TeamPacketClass, scoreboardTeam, boolean.class);
} else {
newTeamPacket = TeamPacketClass.getConstructor(scoreboardTeam, int.class);
}
ScoreboardScore_setScore = ReflectionUtils.getMethod(
scoreboardScoreClass,
new String[] {"func_96647_c", "setScore", "b", "c", "m_83402_"}, // {Thermos, 1.5.1 - 1.17.1 & 1.20.2+, 1.18 - 1.20.1, 1.5, Mohist 1.18.2}
int.class
);
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 >= 13) {
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+}
IChatBaseComponent
);
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+}
IChatBaseComponent
);
} 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 (minorVersion >= 8) {
ScoreboardTeam_setNameTagVisibility = ReflectionUtils.getMethod(
scoreboardTeam,
new String[] {"setNameTagVisibility", "a", "m_83346_"}, // {1.8.1+, 1.8 & 1.18+, Mohist 1.18.2}
EnumNameTagVisibility
);
}
available = true;
}
public PacketScoreboard(@NotNull BukkitTabPlayer player) {
@@ -267,54 +118,36 @@ public class PacketScoreboard extends Scoreboard<BukkitTabPlayer> {
@Override
@SneakyThrows
public void setDisplaySlot(@NotNull DisplaySlot slot, @NotNull String objective) {
Object displaySlot;
if (BukkitReflection.is1_20_2Plus()) {
displaySlot = displaySlots[slot.ordinal()];
} else {
displaySlot = slot.ordinal();
}
player.sendPacket(newDisplayObjective.newInstance(displaySlot, newScoreboardObjective(objective)));
public void setDisplaySlot0(int slot, @NotNull String objective) {
player.sendPacket(displayPacketData.setDisplaySlot(slot, newObjective(objective, "", 0, null)));
}
@Override
@SneakyThrows
public void registerObjective0(@NotNull String objectiveName, @NotNull String title, @NotNull HealthDisplay display) {
player.sendPacket(buildObjective(0, objectiveName, title, display));
public void registerObjective0(@NotNull String objectiveName, @NotNull String title, int display,
@Nullable IChatBaseComponent numberFormat) {
player.sendPacket(newObjectivePacket(ObjectiveAction.REGISTER, objectiveName, title, display, numberFormat));
}
@Override
public void unregisterObjective0(@NotNull String objectiveName) {
player.sendPacket(buildObjective(1, objectiveName, "", HealthDisplay.INTEGER));
player.sendPacket(newObjectivePacket(ObjectiveAction.UNREGISTER, objectiveName, "", 0, null));
}
@Override
public void updateObjective0(@NotNull String objectiveName, @NotNull String title, @NotNull HealthDisplay display) {
player.sendPacket(buildObjective(2, objectiveName, title, display));
public void updateObjective0(@NotNull String objectiveName, @NotNull String title, int display,
@Nullable IChatBaseComponent numberFormat) {
player.sendPacket(newObjectivePacket(ObjectiveAction.UPDATE, objectiveName, title, display, numberFormat));
}
@SneakyThrows
private Object buildObjective(int action, String objectiveName, String title, @NotNull HealthDisplay display) {
if (BukkitReflection.getMinorVersion() >= 13) {
return newObjectivePacket.newInstance(
newScoreboardObjective.newInstance(
null,
objectiveName,
null,
toComponent(title),
healthDisplays[display.ordinal()]
),
action
);
private Object newObjectivePacket(int action, @NotNull String objectiveName, @NotNull String title, int display,
@Nullable IChatBaseComponent numberFormat) {
Object packet = newObjectivePacket.newInstance(newObjective(objectiveName, title, display, numberFormat), action);
if (BukkitReflection.getMinorVersion() >= 8 && BukkitReflection.getMinorVersion() < 13) {
Objective_RENDER_TYPE.set(packet, healthDisplays[display]);
}
Object nmsPacket = newObjectivePacket.newInstance();
Objective_OBJECTIVE_NAME.set(nmsPacket, objectiveName);
Objective_DISPLAY_NAME.set(nmsPacket, title);
if (BukkitReflection.getMinorVersion() >= 8) {
Objective_RENDER_TYPE.set(nmsPacket, healthDisplays[display.ordinal()]);
}
Objective_METHOD.set(nmsPacket, action);
return nmsPacket;
return packet;
}
@Override
@@ -322,111 +155,363 @@ public class PacketScoreboard extends Scoreboard<BukkitTabPlayer> {
public void registerTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix,
@NotNull NameVisibility visibility, @NotNull CollisionRule collision,
@NotNull Collection<String> players, int options) {
Object team = createTeam(name, prefix, suffix, visibility, collision, options);
((Collection<String>)ScoreboardTeam_getPlayerNameSet.invoke(team)).addAll(players);
if (BukkitReflection.getMinorVersion() >= 17) {
player.sendPacket(TeamPacketConstructor_ofBoolean.invoke(null, team, true));
} else {
player.sendPacket(newTeamPacket.newInstance(team, 0));
}
player.sendPacket(teamPacketData.registerTeam(name, prefix, toComponent(prefix), suffix,
toComponent(suffix), visibility, collision, players, options));
}
@Override
@SneakyThrows
public void unregisterTeam0(@NotNull String name) {
Object team = newScoreboardTeam.newInstance(emptyScoreboard, name);
if (BukkitReflection.getMinorVersion() >= 17) {
player.sendPacket(TeamPacketConstructor_of.invoke(null, team));
} else {
player.sendPacket(newTeamPacket.newInstance(team, 1));
}
player.sendPacket(teamPacketData.unregisterTeam(name));
}
@Override
@SneakyThrows
public void updateTeam0(@NotNull String name, @NotNull String prefix, @NotNull String suffix,
@NotNull NameVisibility visibility, @NotNull CollisionRule collision, int options) {
Object team = createTeam(name, prefix, suffix, visibility, collision, options);
if (BukkitReflection.getMinorVersion() >= 17) {
player.sendPacket(TeamPacketConstructor_ofBoolean.invoke(null, team, false));
} else {
player.sendPacket(newTeamPacket.newInstance(team, 2));
}
}
@SneakyThrows
private Object createTeam(@NotNull String teamName, @NotNull String prefix, @NotNull String suffix,
@NotNull NameVisibility visibility, @NotNull CollisionRule collision, int options) {
Object team = newScoreboardTeam.newInstance(emptyScoreboard, teamName);
ScoreboardTeam_setAllowFriendlyFire.invoke(team, (options & 0x1) > 0);
ScoreboardTeam_setCanSeeFriendlyInvisibles.invoke(team, (options & 0x2) > 0);
if (BukkitReflection.getMinorVersion() >= 13) {
ScoreboardTeam_setPrefix.invoke(team, toComponent(prefix));
ScoreboardTeam_setSuffix.invoke(team, toComponent(suffix));
ScoreboardTeam_setColor.invoke(team, chatFormats[EnumChatFormat.lastColorsOf(prefix).ordinal()]);
} else {
ScoreboardTeam_setPrefix.invoke(team, prefix);
ScoreboardTeam_setSuffix.invoke(team, suffix);
}
if (BukkitReflection.getMinorVersion() >= 8)
ScoreboardTeam_setNameTagVisibility.invoke(team, nameVisibilities[visibility.ordinal()]);
if (BukkitReflection.getMinorVersion() >= 9)
ScoreboardTeam_setCollisionRule.invoke(team, collisionRules[collision.ordinal()]);
return team;
player.sendPacket(teamPacketData.updateTeam(name, prefix, toComponent(prefix), suffix,
toComponent(suffix), visibility, collision, options));
}
@Override
public void setScore0(@NotNull String objective, @NotNull String scoreHolder, int score,
@Nullable IChatBaseComponent displayName, @Nullable IChatBaseComponent numberFormat) {
player.sendPacket(scorePacketData.setScore(objective, scoreHolder, score, toComponent(displayName), toFixedFormat(numberFormat)));
}
@Override
public void removeScore0(@NotNull String objective, @NotNull String scoreHolder) {
player.sendPacket(scorePacketData.removeScore(objective, scoreHolder));
}
/**
* Creates a new Scoreboard Objective with given parameters.
*
* @param objectiveName
* Objective name
* @param title
* Objective title
* @param renderType
* Render type
* @param numberFormat
* Default number format (1.20.3+)
* @return Created objective
*/
@SneakyThrows
public void setScore0(@NotNull String objective, @NotNull String playerName, int score) {
public Object newObjective(@NotNull String objectiveName, @NotNull String title, int renderType,
@Nullable IChatBaseComponent numberFormat) {
if (BukkitReflection.is1_20_3Plus()) {
// 1.20.3+
return newScoreboardObjective.newInstance(
emptyScoreboard,
objectiveName,
null, // Criteria
toComponent(title),
healthDisplays[renderType],
false, // Auto update
toFixedFormat(numberFormat)
);
}
if (BukkitReflection.getMinorVersion() >= 13) {
player.sendPacket(newScorePacket_1_13.newInstance(scoreboardActions[0], objective, playerName, score));
} else {
Object scoreboardScore = newScoreboardScore.newInstance(emptyScoreboard, newScoreboardObjective(objective), playerName);
ScoreboardScore_setScore.invoke(scoreboardScore, score);
if (BukkitReflection.getMinorVersion() >= 8) {
player.sendPacket(newScorePacket.newInstance(scoreboardScore));
// 1.13 - 1.20.2
return newScoreboardObjective.newInstance(
emptyScoreboard,
objectiveName,
null, // Criteria
toComponent(title),
healthDisplays[renderType]
);
}
// 1.5 - 1.12.2
Object objective = newScoreboardObjective.newInstance(emptyScoreboard, objectiveName, IScoreboardCriteria_dummy);
ScoreboardObjective_setDisplayName.invoke(objective, title);
return objective;
}
@Nullable
private Object toComponent(@NotNull String text) {
return toComponent(IChatBaseComponent.optimizedComponent(text));
}
@Nullable
private Object toComponent(@Nullable IChatBaseComponent component) {
if (component == null || BukkitReflection.getMinorVersion() < 8) return null;
return componentCache.get(component, player.getVersion());
}
@Nullable
@SneakyThrows
private Object toFixedFormat(@Nullable IChatBaseComponent numberFormat) {
if (numberFormat == null || newFixedFormat == null) return null;
return newFixedFormat.newInstance(toComponent(numberFormat));
}
private static class ScorePacketData {
private final Constructor<?> newSetScorePacket;
private Constructor<?> newResetScorePacket; // 1.20.3+
private Constructor<?> newSetScorePacket_String; // 1.12-
private Constructor<?> newScoreboardScore; // 1.12-
private Field SetScorePacket_SCORE; // 1.12-
private Enum<?>[] scoreboardActions; // 1.20.2-
@SneakyThrows
public ScorePacketData() {
Class<?> SetScorePacket = BukkitReflection.getClass(
"network.protocol.game.ClientboundSetScorePacket", // Mojang mapped
"network.protocol.game.PacketPlayOutScoreboardScore", // Bukkit 1.17+
"PacketPlayOutScoreboardScore", // 1.7 - 1.16.5
"Packet207SetScoreboardScore" // 1.5 - 1.6.4
);
if (BukkitReflection.is1_20_3Plus()) {
newResetScorePacket = BukkitReflection.getClass("network.protocol.game.ClientboundResetScorePacket").getConstructor(String.class, String.class);
newSetScorePacket = SetScorePacket.getConstructor(String.class, String.class, int.class, Component, NumberFormat);
} else if (BukkitReflection.getMinorVersion() >= 13) {
Class<?> EnumScoreboardAction = BukkitReflection.getClass("server.ServerScoreboard$Method",
"server.ScoreboardServer$Action", "ScoreboardServer$Action");
newSetScorePacket = SetScorePacket.getConstructor(EnumScoreboardAction, String.class, String.class, int.class);
scoreboardActions = (Enum<?>[]) EnumScoreboardAction.getMethod("values").invoke(null);
} else {
player.sendPacket(newScorePacket.newInstance(scoreboardScore, 0));
Class<?> ScoreboardScore = BukkitReflection.getClass("ScoreboardScore");
newSetScorePacket_String = SetScorePacket.getConstructor(String.class);
SetScorePacket_SCORE = ReflectionUtils.getFields(SetScorePacket, int.class).get(0);
newScoreboardScore = ScoreboardScore.getConstructor(Scoreboard, ScoreboardObjective, String.class);
if (BukkitReflection.getMinorVersion() >= 8) {
newSetScorePacket = SetScorePacket.getConstructor(ScoreboardScore);
} else {
newSetScorePacket = SetScorePacket.getConstructor(ScoreboardScore, int.class);
}
}
}
@SneakyThrows
public Object setScore(@NotNull String objective, @NotNull String scoreHolder, int score,
@Nullable Object displayName, @Nullable Object numberFormat) {
if (BukkitReflection.is1_20_3Plus()) {
return newSetScorePacket.newInstance(scoreHolder, objective, score, displayName, numberFormat);
} else if (BukkitReflection.getMinorVersion() >= 13) {
return newSetScorePacket.newInstance(scoreboardActions[0], objective, scoreHolder, score);
} else {
Object scoreboardScore = newScoreboardScore.newInstance(
emptyScoreboard,
newScoreboardObjective.newInstance(emptyScoreboard, objective, IScoreboardCriteria_dummy),
scoreHolder
);
Object packet;
if (BukkitReflection.getMinorVersion() >= 8) {
packet = newSetScorePacket.newInstance(scoreboardScore);
} else {
packet = newSetScorePacket.newInstance(scoreboardScore, ScoreAction.CHANGE);
}
SetScorePacket_SCORE.set(packet, score);
return packet;
}
}
@SneakyThrows
public Object removeScore(@NotNull String objective, @NotNull String scoreHolder) {
if (BukkitReflection.is1_20_3Plus()) {
return newResetScorePacket.newInstance(scoreHolder, objective);
} else if (BukkitReflection.getMinorVersion() >= 13) {
return newSetScorePacket.newInstance(scoreboardActions[1], objective, scoreHolder, 0);
} else {
return newSetScorePacket_String.newInstance(scoreHolder);
}
}
}
@Override
@SneakyThrows
public void removeScore0(@NotNull String objective, @NotNull String playerName) {
if (BukkitReflection.getMinorVersion() >= 13) {
player.sendPacket(newScorePacket_1_13.newInstance(scoreboardActions[1], objective, playerName, 0));
} else {
player.sendPacket(newScorePacket_String.newInstance(playerName));
}
}
public static class TeamPacketData {
/**
* Creates a new Scoreboard Objective with given name.
*
* @param objectiveName
* Objective name
* @return NMS Objective
*/
@SneakyThrows
public Object newScoreboardObjective(@NotNull String objectiveName) {
if (BukkitReflection.getMinorVersion() >= 13) {
return newScoreboardObjective.newInstance(
null,
objectiveName,
null,
toComponent(""),
null
public Class<?> TeamPacketClass;
private Constructor<?> newTeamPacket;
private final Constructor<?> newScoreboardTeam;
private Method TeamPacketConstructor_of;
private Method TeamPacketConstructor_ofBoolean;
public Field TeamPacket_NAME;
public Field TeamPacket_ACTION;
public Field TeamPacket_PLAYERS;
private final Method ScoreboardTeam_getPlayerNameSet;
private Method ScoreboardTeam_setNameTagVisibility;
private Method ScoreboardTeam_setCollisionRule;
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 Enum<?>[] nameVisibilities;
private Enum<?>[] collisionRules;
@SneakyThrows
public TeamPacketData() {
int minorVersion = BukkitReflection.getMinorVersion();
Class<?> scoreboardTeam = BukkitReflection.getClass("world.scores.PlayerTeam", "world.scores.ScoreboardTeam", "ScoreboardTeam");
Class<?> enumChatFormatClass = BukkitReflection.getClass("ChatFormatting", "EnumChatFormat", "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
);
newScoreboardTeam = scoreboardTeam.getConstructor(Scoreboard, String.class);
TeamPacket_NAME = ReflectionUtils.getFields(TeamPacketClass, String.class).get(0);
TeamPacket_ACTION = ReflectionUtils.getInstanceFields(TeamPacketClass, int.class).get(0);
TeamPacket_PLAYERS = ReflectionUtils.getOnlyField(TeamPacketClass, Collection.class);
ScoreboardTeam_getPlayerNameSet = ReflectionUtils.getOnlyMethod(scoreboardTeam, 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) {
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
);
nameVisibilities = (Enum<?>[]) enumNameTagVisibility.getMethod("values").invoke(null);
ScoreboardTeam_setNameTagVisibility = ReflectionUtils.getMethod(
scoreboardTeam,
new String[] {"setNameTagVisibility", "a", "m_83346_"}, // {1.8.1+, 1.8 & 1.18+, Mohist 1.18.2}
enumNameTagVisibility
);
}
if (minorVersion >= 9) {
Class<?> enumTeamPush = BukkitReflection.getClass("world.scores.Team$CollisionRule",
"world.scores.ScoreboardTeamBase$EnumTeamPush", "ScoreboardTeamBase$EnumTeamPush");
ScoreboardTeam_setCollisionRule = ReflectionUtils.getOnlyMethod(scoreboardTeam, void.class, enumTeamPush);
collisionRules = (Enum<?>[]) enumTeamPush.getMethod("values").invoke(null);
}
if (minorVersion >= 13) {
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
);
} 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 (minorVersion >= 17) {
TeamPacketConstructor_of = ReflectionUtils.getOnlyMethod(TeamPacketClass, TeamPacketClass, scoreboardTeam);
TeamPacketConstructor_ofBoolean = ReflectionUtils.getOnlyMethod(TeamPacketClass, TeamPacketClass, scoreboardTeam, boolean.class);
} else {
newTeamPacket = TeamPacketClass.getConstructor(scoreboardTeam, int.class);
}
}
@SneakyThrows
public Object registerTeam(@NotNull String name, @NotNull String prefix, @Nullable Object prefixComponent,
@NotNull String suffix, @Nullable Object suffixComponent,
@NotNull NameVisibility visibility, @NotNull CollisionRule collision,
@NotNull Collection<String> players, int options) {
Object team = createTeam(name, prefix, prefixComponent, suffix, suffixComponent, visibility, collision, options);
((Collection<String>)ScoreboardTeam_getPlayerNameSet.invoke(team)).addAll(players);
if (BukkitReflection.getMinorVersion() >= 17) {
return TeamPacketConstructor_ofBoolean.invoke(null, team, true);
} else {
return newTeamPacket.newInstance(team, TeamAction.CREATE);
}
}
@SneakyThrows
public Object unregisterTeam(@NotNull String name) {
Object team = newScoreboardTeam.newInstance(emptyScoreboard, name);
if (BukkitReflection.getMinorVersion() >= 17) {
return TeamPacketConstructor_of.invoke(null, team);
} else {
return newTeamPacket.newInstance(team, TeamAction.REMOVE);
}
}
@SneakyThrows
public Object updateTeam(@NotNull String name, @NotNull String prefix, @Nullable Object prefixComponent,
@NotNull String suffix, @Nullable Object suffixComponent,
@NotNull NameVisibility visibility, @NotNull CollisionRule collision, int options) {
Object team = createTeam(name, prefix, prefixComponent, suffix, suffixComponent, visibility, collision, options);
if (BukkitReflection.getMinorVersion() >= 17) {
return TeamPacketConstructor_ofBoolean.invoke(null, team, false);
} else {
return newTeamPacket.newInstance(team, TeamAction.UPDATE);
}
}
@SneakyThrows
private Object createTeam(@NotNull String teamName, @NotNull String prefix, @Nullable Object prefixComponent,
@NotNull String suffix, @Nullable Object suffixComponent,
@NotNull NameVisibility visibility, @NotNull CollisionRule collision, int options) {
Object team = newScoreboardTeam.newInstance(emptyScoreboard, teamName);
ScoreboardTeam_setAllowFriendlyFire.invoke(team, (options & 0x1) > 0);
ScoreboardTeam_setCanSeeFriendlyInvisibles.invoke(team, (options & 0x2) > 0);
if (BukkitReflection.getMinorVersion() >= 13) {
ScoreboardTeam_setPrefix.invoke(team, prefixComponent);
ScoreboardTeam_setSuffix.invoke(team, suffixComponent);
ScoreboardTeam_setColor.invoke(team, chatFormats[EnumChatFormat.lastColorsOf(prefix).ordinal()]);
} else {
ScoreboardTeam_setPrefix.invoke(team, prefix);
ScoreboardTeam_setSuffix.invoke(team, suffix);
}
if (BukkitReflection.getMinorVersion() >= 8)
ScoreboardTeam_setNameTagVisibility.invoke(team, nameVisibilities[visibility.ordinal()]);
if (BukkitReflection.getMinorVersion() >= 9)
ScoreboardTeam_setCollisionRule.invoke(team, collisionRules[collision.ordinal()]);
return team;
}
return newScoreboardObjective.newInstance(
null,
objectiveName,
IScoreboardCriteria_self.get(null)
);
}
private Object toComponent(String text) {
return player.getPlatform().toComponent(IChatBaseComponent.optimizedComponent(text), player.getVersion());
public static class DisplayPacketData {
public final Class<?> DisplayObjectiveClass;
private final Constructor<?> newDisplayObjective;
public final Field DisplayObjective_POSITION;
public final Field DisplayObjective_OBJECTIVE_NAME;
private final Object[] displaySlots;
@SneakyThrows
public DisplayPacketData() {
DisplayObjectiveClass = BukkitReflection.getClass(
"network.protocol.game.ClientboundSetDisplayObjectivePacket", // Mojang mapped
"network.protocol.game.PacketPlayOutScoreboardDisplayObjective", // Bukkit 1.17+
"PacketPlayOutScoreboardDisplayObjective", // Bukkit 1.7 - 1.16.5
"Packet208SetScoreboardDisplayObjective" // Bukkit 1.5 - 1.6.4
);
DisplayObjective_OBJECTIVE_NAME = ReflectionUtils.getOnlyField(DisplayObjectiveClass, String.class);
if (BukkitReflection.is1_20_2Plus()) {
Class<?> DisplaySlot = BukkitReflection.getClass("world.scores.DisplaySlot");
displaySlots = (Object[]) DisplaySlot.getDeclaredMethod("values").invoke(null);
DisplayObjective_POSITION = ReflectionUtils.getOnlyField(DisplayObjectiveClass, DisplaySlot);
newDisplayObjective = DisplayObjectiveClass.getConstructor(DisplaySlot, ScoreboardObjective);
} else {
displaySlots = new Object[]{0, 1, 2};
DisplayObjective_POSITION = ReflectionUtils.getOnlyField(DisplayObjectiveClass, int.class);
newDisplayObjective = DisplayObjectiveClass.getConstructor(int.class, ScoreboardObjective);
}
}
@SneakyThrows
public Object setDisplaySlot(int slot, @NotNull Object objective) {
return newDisplayObjective.newInstance(displaySlots[slot], objective);
}
}
}
@@ -32,8 +32,8 @@ public class PaperScoreboard extends BukkitScoreboard {
}
@Override
public void newObjective(String objectiveName, String criteria, String title, @NotNull HealthDisplay display) {
scoreboard.registerNewObjective(objectiveName, criteria, toAdventure(title), RenderType.valueOf(display.name()));
public void newObjective(String objectiveName, String criteria, String title, int display) {
scoreboard.registerNewObjective(objectiveName, criteria, toAdventure(title), RenderType.values()[display]);
}
@Override
@@ -0,0 +1,45 @@
package me.neznamy.tab.platforms.bukkit.scoreboard;
import lombok.Getter;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.platforms.bukkit.nms.PacketSender;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.platform.Scoreboard;
import org.bukkit.Bukkit;
import java.util.function.Function;
public class ScoreboardLoader {
/** Instance function */
@Getter
private static Function<BukkitTabPlayer, Scoreboard<BukkitTabPlayer>> instance;
/**
* Finds the best available instance for current server software.
*/
public static void findInstance() {
if (PacketScoreboard.isAvailable() && PacketSender.isAvailable()) {
instance = PacketScoreboard::new;
} else if (PaperScoreboard.isAvailable()) {
instance = PaperScoreboard::new;
sendMessage("Paper");
} else if (BukkitScoreboard.isAvailable()) {
instance = BukkitScoreboard::new;
sendMessage("Bukkit");
} else {
instance = NullScoreboard::new;
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.RED.getFormat() + "[TAB] Failed to initialize NMS fields for " +
"Scoreboards due to a compatibility error. No fallback solution was found either. This will " +
"result in the features not working. " +
"Please update the plugin a to version with native support for your server version to unlock the features.");
}
}
private static void sendMessage(String implementationName) {
Bukkit.getConsoleSender().sendMessage(EnumChatFormat.RED.getFormat() + "[TAB] Failed to initialize NMS fields for " +
"Scoreboards due to a compatibility error. Using fallback solution using " + implementationName + " API. " +
"This may result in features not working properly, mainly compatibility with other plugins. " +
"Please update the plugin to a version with native support for your server version for optimal experience.");
}
}
+1 -4
View File
@@ -7,7 +7,7 @@ load: POSTWORLD
author: @author@
website: "@website@"
depend: []
softdepend: [PlaceholderAPI, LuckPerms, LibsDisguises, ViaVersion, ViaRewind, floodgate]
softdepend: [PlaceholderAPI, LuckPerms, LibsDisguises, ViaVersion, ViaRewind, floodgate, Vault]
loadbefore: []
prefix: TAB
libraries: []
@@ -49,9 +49,6 @@ permissions:
tab.change.*:
description: Allows player to change property using command
default: op
tab.width:
description: Allows player to run /tab width
default: op
tab.announce.bar:
description: Allows player to run /tab announce bar
default: op
+1 -1
View File
@@ -1,7 +1,7 @@
dependencies {
implementation(projects.shared)
implementation("org.bstats:bstats-bungeecord:3.0.1")
compileOnly("net.md-5:bungeecord-proxy:1.20-R0.1-SNAPSHOT")
compileOnly("net.md-5:bungeecord-proxy:1.20-R0.2-SNAPSHOT")
compileOnly("com.github.limework.redisbungee:RedisBungee-Bungee:0.11.0")
compileOnly("com.github.LeonMangler:PremiumVanishAPI:2.8.8")
}
@@ -1,7 +1,8 @@
package me.neznamy.tab.platforms.bungeecord;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.shared.platform.bossbar.BossBar;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.BossBar;
import me.neznamy.tab.api.bossbar.BarColor;
import me.neznamy.tab.api.bossbar.BarStyle;
import org.jetbrains.annotations.NotNull;
@@ -34,7 +35,7 @@ public class BungeeBossBar implements BossBar {
net.md_5.bungee.protocol.packet.BossBar packet = new net.md_5.bungee.protocol.packet.BossBar(id, 0);
packet.setHealth(progress);
BungeeMultiVersion.setTitle(packet, title, player.getVersion());
packet.setTitle(player.getPlatform().toComponent(IChatBaseComponent.optimizedComponent(title), player.getVersion()));
packet.setColor(color.ordinal());
packet.setDivision(style.ordinal());
player.sendPacket(packet);
@@ -48,7 +49,7 @@ public class BungeeBossBar implements BossBar {
if (player.getVersion().getMinorVersion() < 9) return;
net.md_5.bungee.protocol.packet.BossBar packet = new net.md_5.bungee.protocol.packet.BossBar(id, 3);
BungeeMultiVersion.setTitle(packet, title, player.getVersion());
packet.setTitle(player.getPlatform().toComponent(IChatBaseComponent.optimizedComponent(title), player.getVersion()));
player.sendPacket(packet);
}
@@ -28,13 +28,20 @@ public class BungeeEventListener extends EventListener<ProxiedPlayer> implements
public void onSwitch(ServerSwitchEvent e) {
TAB tab = TAB.getInstance();
if (tab.isPluginDisabled()) return;
// Avoid 1.20.3 client crash on scoreboard packets, do it sync to prevent packet being sent after event, but before processing
TabPlayer p = tab.getPlayer(e.getPlayer().getUniqueId());
if (p != null && p.getVersion().getNetworkId() >= ProtocolVersion.V1_20_3.getNetworkId()) {
p.getScoreboard().freeze();
}
tab.getCPUManager().runTask(() -> {
TabPlayer player = tab.getPlayer(e.getPlayer().getUniqueId());
if (player == null) {
player = createPlayer(e.getPlayer());
// I don't think this is actually needed, but someone said it fixed geyser warn in console
// Doing this won't hurt, so whatever
// Things will get cleared immediately after, so no point in sending it, also someone said it fixed some warn from Geyser
// Sending these packets before login packet will also crash the client on 1.20.3
if (player.getVersion().getNetworkId() >= ProtocolVersion.V1_20_2.getNetworkId()) {
player.getScoreboard().freeze();
}
@@ -1,200 +0,0 @@
package me.neznamy.tab.platforms.bungeecord;
import lombok.SneakyThrows;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.chat.ChatModifier;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.md_5.bungee.UserConnection;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.protocol.DefinedPacket;
import net.md_5.bungee.protocol.packet.*;
import net.md_5.bungee.protocol.packet.PlayerListItem.Item;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.InvocationTargetException;
import java.util.stream.Collectors;
public class BungeeMultiVersion {
/**
* Flag tracking if the current BungeeCord version contains changes made in build 1732.
* This build changes "ScoreboardDisplay" field "position" from "byte" to "int".
*/
private static final boolean b1732Plus = ReflectionUtils.getFields(ScoreboardDisplay.class, int.class).size() > 0;
/**
* Flag tracking if the current BungeeCord version contains changes made in build 1738.
* This build adds "sendPacketQueued" method to UserConnection class.
*/
private static final boolean b1738Plus = ReflectionUtils.methodExists(UserConnection.class, "sendPacketQueued", DefinedPacket.class);
/**
* Flag tracking if the current BungeeCord version contains changes made in build 1760.
* This build changes all packet fields for displaying information from String to BaseComponent.
*/
private static final boolean b1760Plus = ReflectionUtils.methodExists(BossBar.class, "setTitle", BaseComponent.class);
@SneakyThrows
@NotNull
public static ScoreboardDisplay newScoreboardDisplay(int position, @NotNull String name) {
if (b1732Plus) {
return ScoreboardDisplay.class.getConstructor(int.class, String.class).newInstance(position, name);
} else {
return new ScoreboardDisplay((byte) position, name);
}
}
@SneakyThrows
public static void sendPacket(@NotNull BungeeTabPlayer player, @NotNull DefinedPacket packet) {
if (b1738Plus) {
try {
UserConnection.class.getDeclaredMethod("sendPacketQueued", DefinedPacket.class).invoke(player.getPlayer(), packet);
} catch (InvocationTargetException BungeeCordBug) { // Since we use reflection we must catch this one
// java.lang.NullPointerException: Cannot invoke "net.md_5.bungee.protocol.MinecraftEncoder.getProtocol()" because the return value of "io.netty.channel.ChannelPipeline.get(java.lang.Class)" is null
// at net.md_5.bungee.netty.ChannelWrapper.getEncodeProtocol(ChannelWrapper.java:51)
// at net.md_5.bungee.UserConnection.sendPacketQueued(UserConnection.java:194)
if (TAB.getInstance().getConfiguration().isDebugMode()) {
TAB.getInstance().getErrorManager().printError("Failed to deliver packet to player " + player.getName() +
" (online = " + player.getPlayer().isConnected() + "): " + packet, BungeeCordBug);
}
}
} else {
player.getPlayer().unsafe().sendPacket(packet);
}
}
@SneakyThrows
public static void setDisplayName(@NotNull Item item, @NotNull IChatBaseComponent displayName, @NotNull ProtocolVersion version) {
if (b1760Plus) {
Item.class.getMethod("setDisplayName", BaseComponent.class).invoke(item, toComponent(displayName, version));
} else {
item.setDisplayName(displayName.toString(version));
}
}
@SneakyThrows
@NotNull
public static PlayerListHeaderFooter newPlayerListHeaderFooter(@NotNull IChatBaseComponent header,
@NotNull IChatBaseComponent footer, ProtocolVersion version) {
if (b1760Plus) {
return PlayerListHeaderFooter.class.getConstructor(BaseComponent.class, BaseComponent.class).newInstance(
toComponent(header, version),
toComponent(footer, version)
);
} else {
return new PlayerListHeaderFooter(
header.toString(version),
footer.toString(version)
);
}
}
@SneakyThrows
public static void setTitle(@NotNull BossBar bossBar, @NotNull String title, @NotNull ProtocolVersion version) {
if (b1760Plus) {
BossBar.class.getMethod("setTitle", BaseComponent.class).invoke(bossBar, toComponent(IChatBaseComponent.optimizedComponent(title), version));
} else {
bossBar.setTitle(IChatBaseComponent.optimizedComponent(title).toString(version));
}
}
@NotNull
@SneakyThrows
public static ScoreboardObjective newScoreboardObjective(@NotNull String objective, @NotNull IChatBaseComponent title,
@Nullable ScoreboardObjective.HealthDisplay healthDisplay, byte action,
@NotNull ProtocolVersion version) {
if (b1760Plus) {
return ScoreboardObjective.class
.getConstructor(String.class, BaseComponent.class, ScoreboardObjective.HealthDisplay.class, byte.class)
.newInstance(objective, toComponent(title, version), healthDisplay, action);
} else {
return new ScoreboardObjective(objective, title.toString(version), healthDisplay, action);
}
}
@NotNull
@SneakyThrows
public static Team newTeam(@NotNull String name, byte mode, @NotNull String prefix, @NotNull String suffix,
@NotNull String nameTagVisibility, @NotNull String collisionRule, int color,
byte options, @Nullable String[] players, @NotNull ProtocolVersion version) {
if (b1760Plus) {
return Team.class.getConstructor(String.class, byte.class, BaseComponent.class, BaseComponent.class,
BaseComponent.class, String.class, String.class, int.class, byte.class, String[].class)
.newInstance(
name,
mode,
toComponent(IChatBaseComponent.optimizedComponent(name), version),
toComponent(IChatBaseComponent.optimizedComponent(prefix), version),
toComponent(IChatBaseComponent.optimizedComponent(suffix), version),
nameTagVisibility,
collisionRule,
color,
options,
players
);
} else {
return new Team(
name,
mode,
jsonOrRaw(name, version),
jsonOrRaw(prefix, version),
jsonOrRaw(suffix, version),
nameTagVisibility,
collisionRule,
color,
options,
players
);
}
}
/**
* If player's version is 1.13+, creates a component from given text and returns
* it as a serialized component, which BungeeCord uses.
* <p>
* If player's version is 1.12-, the text is returned
*
* @param text
* Text to convert
* @param version
* Protocol version to create json string for
* @return serialized component for 1.13+ clients, cut string for 1.12-
*/
@NotNull
private static String jsonOrRaw(@NotNull String text, @NotNull ProtocolVersion version) {
if (version.getMinorVersion() >= 13) {
return IChatBaseComponent.optimizedComponent(text).toString(version);
} else {
return text;
}
}
/**
* Converts internal component class to platform's component class
*
* @param component
* Component to convert
* @param version
* Game version to convert component for
* @return Converted component
*/
private static BaseComponent toComponent(@NotNull IChatBaseComponent component, @NotNull ProtocolVersion version) {
TextComponent textComponent = new TextComponent(component.getText());
ChatModifier modifier = component.getModifier();
if (modifier.getColor() != null) textComponent.setColor(ChatColor.of(
modifier.getColor().toString(version.getMinorVersion() >= 16)));
if (modifier.isBold()) textComponent.setBold(true);
if (modifier.isItalic()) textComponent.setItalic(true);
if (modifier.isObfuscated()) textComponent.setObfuscated(true);
if (modifier.isStrikethrough()) textComponent.setStrikethrough(true);
if (modifier.isUnderlined()) textComponent.setUnderlined(true);
if (!component.getExtra().isEmpty()) textComponent.setExtra(
component.getExtra().stream().map(c -> toComponent(c, version)).collect(Collectors.toList()));
return textComponent;
}
}
@@ -56,7 +56,7 @@ public class BungeePipelineInjector extends NettyPipelineInjector {
try {
(wrapperField = InitialHandler.class.getDeclaredField("ch")).setAccessible(true);
directionData = ReflectionUtils.setAccessible(Protocol.class.getDeclaredField("TO_CLIENT")).get(Protocol.GAME);
getId = ReflectionUtils.setAccessible(directionData.getClass().getDeclaredMethod("getId", Class.class, int.class));
getId = ReflectionUtils.setAccessible(Protocol.DirectionData.class.getDeclaredMethod("getId", Class.class, int.class));
} catch (ReflectiveOperationException exception) {
TAB.getInstance().getErrorManager().criticalError("Failed to initialize bungee internal fields", exception);
}
@@ -77,25 +77,16 @@ public class BungeePipelineInjector extends NettyPipelineInjector {
@Override
@Nullable
@SneakyThrows
protected Channel getChannel(@NotNull TabPlayer player) {
if (wrapperField == null) return null;
final BungeeTabPlayer bungee = (BungeeTabPlayer) player;
try {
return ((ChannelWrapper) wrapperField.get(bungee.getPlayer().getPendingConnection())).getHandle();
} catch (IllegalAccessException exception) {
TAB.getInstance().getErrorManager().criticalError("Failed to get channel of " + bungee.getPlayer().getName(), exception);
}
return null;
return ((ChannelWrapper) wrapperField.get(((BungeeTabPlayer) player).getPlayer().getPendingConnection())).getHandle();
}
@Override
@SneakyThrows
public void onDisplayObjective(@NotNull TabPlayer player, @NotNull Object packet) {
TAB.getInstance().getFeatureManager().onDisplayObjective(
player,
((Number) ScoreboardDisplay.class.getMethod("getPosition").invoke(packet)).intValue(),
((ScoreboardDisplay) packet).getName()
);
TAB.getInstance().getFeatureManager().onDisplayObjective(player,
((ScoreboardDisplay)packet).getPosition(), ((ScoreboardDisplay) packet).getName());
}
@Override
@@ -158,26 +149,12 @@ public class BungeePipelineInjector extends NettyPipelineInjector {
@Override
public void onPlayerInfo(@NotNull TabPlayer receiver, @NotNull Object packet) {
if (packet instanceof PlayerListItemUpdate) {
PlayerListItemUpdate update = (PlayerListItemUpdate) packet;
for (PlayerListItem.Item item : update.getItems()) {
if (update.getActions().contains(PlayerListItemUpdate.Action.UPDATE_DISPLAY_NAME)) {
IChatBaseComponent newDisplayName = TAB.getInstance().getFeatureManager().onDisplayNameChange(receiver, item.getUuid());
if (newDisplayName != null) BungeeMultiVersion.setDisplayName(item, newDisplayName, receiver.getVersion());
}
if (update.getActions().contains(PlayerListItemUpdate.Action.UPDATE_LATENCY)) {
item.setPing(TAB.getInstance().getFeatureManager().onLatencyChange(receiver, item.getUuid(), item.getPing()));
}
if (update.getActions().contains(PlayerListItemUpdate.Action.ADD_PLAYER)) {
TAB.getInstance().getFeatureManager().onEntryAdd(receiver, item.getUuid(), item.getUsername());
}
}
} else {
if (packet instanceof PlayerListItem) {
PlayerListItem listItem = (PlayerListItem) packet;
for (PlayerListItem.Item item : listItem.getItems()) {
if (listItem.getAction() == PlayerListItem.Action.UPDATE_DISPLAY_NAME || listItem.getAction() == PlayerListItem.Action.ADD_PLAYER) {
IChatBaseComponent newDisplayName = TAB.getInstance().getFeatureManager().onDisplayNameChange(receiver, item.getUuid());
if (newDisplayName != null) BungeeMultiVersion.setDisplayName(item, newDisplayName, receiver.getVersion());
if (newDisplayName != null) item.setDisplayName(((BungeeTabPlayer)receiver).getPlatform().toComponent(newDisplayName, receiver.getVersion()));
}
if (listItem.getAction() == PlayerListItem.Action.UPDATE_LATENCY || listItem.getAction() == PlayerListItem.Action.ADD_PLAYER) {
item.setPing(TAB.getInstance().getFeatureManager().onLatencyChange(receiver, item.getUuid(), item.getPing()));
@@ -186,6 +163,20 @@ public class BungeePipelineInjector extends NettyPipelineInjector {
TAB.getInstance().getFeatureManager().onEntryAdd(receiver, item.getUuid(), item.getUsername());
}
}
} else {
PlayerListItemUpdate update = (PlayerListItemUpdate) packet;
for (PlayerListItem.Item item : update.getItems()) {
if (update.getActions().contains(PlayerListItemUpdate.Action.UPDATE_DISPLAY_NAME)) {
IChatBaseComponent newDisplayName = TAB.getInstance().getFeatureManager().onDisplayNameChange(receiver, item.getUuid());
if (newDisplayName != null) item.setDisplayName(((BungeeTabPlayer)receiver).getPlatform().toComponent(newDisplayName, receiver.getVersion()));
}
if (update.getActions().contains(PlayerListItemUpdate.Action.UPDATE_LATENCY)) {
item.setPing(TAB.getInstance().getFeatureManager().onLatencyChange(receiver, item.getUuid(), item.getPing()));
}
if (update.getActions().contains(PlayerListItemUpdate.Action.ADD_PLAYER)) {
TAB.getInstance().getFeatureManager().onEntryAdd(receiver, item.getUuid(), item.getUsername());
}
}
}
}
@@ -236,10 +227,15 @@ public class BungeePipelineInjector extends NettyPipelineInjector {
*/
@NotNull
private Object deserialize(@NotNull ByteBuf buf) {
if (wrapperField == null) return buf;
int marker = buf.readerIndex();
try {
int packetId = buf.readByte();
for (int i=0; i<extraPacketClasses.length; i++) {
ChannelWrapper ch = (ChannelWrapper) wrapperField.get(((BungeeTabPlayer) player).getPlayer().getPendingConnection());
if (!ch.getEncodeProtocol().TO_CLIENT.hasPacket(extraPacketClasses[i], ((ProxiedPlayer)player.getPlayer()).getPendingConnection().getVersion())) {
continue;
}
if (packetId == getPacketId(((BungeeTabPlayer)player).getPlayer().getPendingConnection().getVersion(), extraPacketClasses[i])) {
DefinedPacket packet = extraPacketSuppliers[i].get();
packet.read(buf, null, ((ProxiedPlayer)player.getPlayer()).getPendingConnection().getVersion());
@@ -263,15 +259,10 @@ public class BungeePipelineInjector extends NettyPipelineInjector {
* packet class
* @return packet ID
*/
@SneakyThrows
private int getPacketId(int protocolVersion, @NotNull Class<? extends DefinedPacket> clazz) {
if (getId == null) return -1;
try {
return (int) getId.invoke(directionData, clazz, protocolVersion);
} catch (ReflectiveOperationException e) {
TAB.getInstance().getErrorManager().printError("Failed to get packet id for packet " + clazz +
" with client version " + protocolVersion, e);
return -1;
}
return (int) getId.invoke(directionData, clazz, protocolVersion);
}
}
}
@@ -1,18 +1,28 @@
package me.neznamy.tab.platforms.bungeecord;
import com.imaginarycode.minecraft.redisbungee.RedisBungeeAPI;
import de.myzelyam.api.vanish.BungeeVanishAPI;
import lombok.AllArgsConstructor;
import lombok.Getter;
import me.neznamy.tab.platforms.bungeecord.features.BungeeRedisSupport;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.chat.ChatModifier;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.features.injection.PipelineInjector;
import me.neznamy.tab.shared.features.redis.RedisSupport;
import me.neznamy.tab.shared.hook.ViaVersionHook;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.proxy.ProxyPlatform;
import me.neznamy.tab.shared.util.ComponentCache;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.ClickEvent;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import org.bstats.bungeecord.Metrics;
import org.bstats.charts.SimplePie;
@@ -20,6 +30,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.util.stream.Collectors;
/**
* BungeeCord implementation of Platform
@@ -27,6 +38,13 @@ import java.io.File;
@AllArgsConstructor
public class BungeePlatform extends ProxyPlatform {
/** Flag tracking plugin presence */
@Getter
private final boolean premiumVanish = ProxyServer.getInstance().getPluginManager().getPlugin("PremiumVanish") != null;
/** Component cache for better performance */
private final ComponentCache<IChatBaseComponent, BaseComponent> cache = new ComponentCache<>(1000, this::toComponent0);
@NotNull
private final BungeeTAB plugin;
@@ -96,4 +114,62 @@ public class BungeePlatform extends ProxyPlatform {
public void registerChannel() {
ProxyServer.getInstance().registerChannel(TabConstants.PLUGIN_MESSAGE_CHANNEL_NAME);
}
@Override
public boolean canSee(@NotNull TabPlayer viewer, @NotNull TabPlayer target) {
//noinspection ConstantConditions
if (premiumVanish && BungeeVanishAPI.canSee(
((BungeeTabPlayer)viewer).getPlayer(),
((BungeeTabPlayer)target).getPlayer())) return true;
return super.canSee(viewer, target);
}
/**
* Converts internal component class to platform's component class. If the component is
* present in the cache, it is taken from it.
*
* @param component
* Component to convert
* @param version
* Game version to convert component for
* @return Converted component
*/
public BaseComponent toComponent(@NotNull IChatBaseComponent component, @NotNull ProtocolVersion version) {
return cache.get(component, version);
}
/**
* Converts internal component class to platform's component class
*
* @param component
* Component to convert
* @param version
* Game version to convert component for
* @return Converted component
*/
private BaseComponent toComponent0(@NotNull IChatBaseComponent component, @NotNull ProtocolVersion version) {
TextComponent textComponent = new TextComponent(component.getText());
ChatModifier modifier = component.getModifier();
if (modifier.getColor() != null) textComponent.setColor(ChatColor.of(
modifier.getColor().toString(version.getMinorVersion() >= 16)));
if (modifier.isBold()) textComponent.setBold(true);
if (modifier.isItalic()) textComponent.setItalic(true);
if (modifier.isObfuscated()) textComponent.setObfuscated(true);
if (modifier.isStrikethrough()) textComponent.setStrikethrough(true);
if (modifier.isUnderlined()) textComponent.setUnderlined(true);
textComponent.setFont(modifier.getFont());
if (modifier.getClickEvent() != null) {
textComponent.setClickEvent(new ClickEvent(
ClickEvent.Action.valueOf(modifier.getClickEvent().getAction().name()),
modifier.getClickEvent().getValue()
));
}
if (!component.getExtra().isEmpty()) textComponent.setExtra(
component.getExtra().stream().map(c -> toComponent(c, version)).collect(Collectors.toList()));
return textComponent;
}
}
@@ -1,13 +1,15 @@
package me.neznamy.tab.platforms.bungeecord;
import lombok.SneakyThrows;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.Scoreboard;
import net.md_5.bungee.protocol.packet.ScoreboardObjective;
import net.md_5.bungee.protocol.packet.ScoreboardScore;
import net.md_5.bungee.protocol.packet.Team;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.protocol.Either;
import net.md_5.bungee.protocol.NumberFormat;
import net.md_5.bungee.protocol.packet.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
@@ -23,41 +25,44 @@ public class BungeeScoreboard extends Scoreboard<BungeeTabPlayer> {
}
@Override
@SneakyThrows
public void setDisplaySlot(@NotNull DisplaySlot slot, @NotNull String objective) {
player.sendPacket(BungeeMultiVersion.newScoreboardDisplay(slot.ordinal(), objective));
public void setDisplaySlot0(int slot, @NotNull String objective) {
player.sendPacket(new ScoreboardDisplay(slot, objective));
}
@Override
public void registerObjective0(@NotNull String objectiveName, @NotNull String title, @NotNull HealthDisplay display) {
player.sendPacket(BungeeMultiVersion.newScoreboardObjective(
public void registerObjective0(@NotNull String objectiveName, @NotNull String title, int display,
@Nullable IChatBaseComponent numberFormat) {
player.sendPacket(new ScoreboardObjective(
objectiveName,
IChatBaseComponent.optimizedComponent(title),
ScoreboardObjective.HealthDisplay.valueOf(display.name()),
(byte) 0,
player.getVersion()
either(title),
ScoreboardObjective.HealthDisplay.values()[display],
(byte) ObjectiveAction.REGISTER,
numberFormat == null ? null : new NumberFormat(NumberFormat.Type.FIXED,
player.getPlatform().toComponent(numberFormat, player.getVersion()))
));
}
@Override
public void unregisterObjective0(@NotNull String objectiveName) {
player.sendPacket(BungeeMultiVersion.newScoreboardObjective(
player.sendPacket(new ScoreboardObjective(
objectiveName,
new IChatBaseComponent(), // Empty value instead of null to prevent NPE kick on 1.7
either(""), // Empty value instead of null to prevent NPE kick on 1.7
null,
(byte) 1,
player.getVersion()
(byte) ObjectiveAction.UNREGISTER,
null
));
}
@Override
public void updateObjective0(@NotNull String objectiveName, @NotNull String title, @NotNull HealthDisplay display) {
player.sendPacket(BungeeMultiVersion.newScoreboardObjective(
public void updateObjective0(@NotNull String objectiveName, @NotNull String title, int display,
@Nullable IChatBaseComponent numberFormat) {
player.sendPacket(new ScoreboardObjective(
objectiveName,
IChatBaseComponent.optimizedComponent(title),
ScoreboardObjective.HealthDisplay.valueOf(display.name()),
(byte) 2,
player.getVersion()
either(title),
ScoreboardObjective.HealthDisplay.values()[display],
(byte) ObjectiveAction.UPDATE,
numberFormat == null ? null : new NumberFormat(NumberFormat.Type.FIXED,
player.getPlatform().toComponent(numberFormat, player.getVersion()))
));
}
@@ -69,17 +74,17 @@ public class BungeeScoreboard extends Scoreboard<BungeeTabPlayer> {
if (player.getVersion().getMinorVersion() >= 13) {
color = EnumChatFormat.lastColorsOf(prefix).ordinal();
}
player.sendPacket(BungeeMultiVersion.newTeam(
player.sendPacket(new Team(
name,
(byte) 0,
prefix,
suffix,
(byte) TeamAction.CREATE,
either(name),
either(prefix),
either(suffix),
visibility.toString(),
collision.toString(),
color,
(byte)options,
players.toArray(new String[0]),
player.getVersion()
players.toArray(new String[0])
));
}
@@ -95,27 +100,48 @@ public class BungeeScoreboard extends Scoreboard<BungeeTabPlayer> {
if (player.getVersion().getMinorVersion() >= 13) {
color = EnumChatFormat.lastColorsOf(prefix).ordinal();
}
player.sendPacket(BungeeMultiVersion.newTeam(
player.sendPacket(new Team(
name,
(byte) 2,
prefix,
suffix,
(byte) TeamAction.UPDATE,
either(name),
either(prefix),
either(suffix),
visibility.toString(),
collision.toString(),
color,
(byte)options,
null,
player.getVersion()
null
));
}
@Override
public void setScore0(@NotNull String objective, @NotNull String playerName, int score) {
player.sendPacket(new ScoreboardScore(playerName, (byte) 0, objective, score));
public void setScore0(@NotNull String objective, @NotNull String scoreHolder, int score,
@Nullable IChatBaseComponent displayName, @Nullable IChatBaseComponent numberFormat) {
player.sendPacket(new ScoreboardScore(
scoreHolder,
(byte) ScoreAction.CHANGE,
objective,
score,
displayName == null ? null : player.getPlatform().toComponent(displayName, player.getVersion()),
numberFormat == null ? null : new NumberFormat(NumberFormat.Type.FIXED,
player.getPlatform().toComponent(numberFormat, player.getVersion()))
));
}
@Override
public void removeScore0(@NotNull String objective, @NotNull String playerName) {
player.sendPacket(new ScoreboardScore(playerName, (byte) 1, objective, 0));
public void removeScore0(@NotNull String objective, @NotNull String scoreHolder) {
if (player.getVersion().getNetworkId() >= ProtocolVersion.V1_20_3.getNetworkId()) {
player.sendPacket(new ScoreboardScoreReset(scoreHolder, objective));
} else {
player.sendPacket(new ScoreboardScore(scoreHolder, (byte) ScoreAction.REMOVE, objective, 0, null, null));
}
}
private Either<String, BaseComponent> either(@NotNull String text) {
if (player.getVersion().getMinorVersion() >= 13) {
return Either.right(player.getPlatform().toComponent(IChatBaseComponent.optimizedComponent(text), player.getVersion()));
} else {
return Either.left(text);
}
}
}
@@ -12,9 +12,9 @@ public class BungeeTAB extends Plugin {
@Override
public void onEnable() {
if (!ReflectionUtils.classExists("net.md_5.bungee.protocol.packet.PlayerListItemUpdate")) {
getLogger().info(EnumChatFormat.color("&cThe plugin requires BungeeCord build #1671 " +
"(released on December 7th, 2022) and up (or an equivalent fork) to work."));
if (!ReflectionUtils.classExists("net.md_5.bungee.protocol.packet.ScoreboardScoreReset")) {
getLogger().warning(EnumChatFormat.color("&cThe plugin requires BungeeCord build #1774 " +
"(released on November 25th, 2023) and up (or an equivalent fork) to work."));
return;
}
TAB.create(new BungeePlatform(this));
@@ -2,7 +2,6 @@ package me.neznamy.tab.platforms.bungeecord;
import de.myzelyam.api.vanish.BungeeVanishAPI;
import lombok.Getter;
import lombok.SneakyThrows;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.platforms.bungeecord.tablist.BungeeTabList1193;
import me.neznamy.tab.platforms.bungeecord.tablist.BungeeTabList17;
@@ -12,11 +11,10 @@ import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.bossbar.BossBar;
import me.neznamy.tab.shared.platform.BossBar;
import me.neznamy.tab.shared.proxy.ProxyTabPlayer;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.UserConnection;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.chat.ComponentSerializer;
import net.md_5.bungee.connection.InitialHandler;
import net.md_5.bungee.connection.LoginResult;
import net.md_5.bungee.protocol.DefinedPacket;
@@ -30,9 +28,6 @@ import org.jetbrains.annotations.Nullable;
@Getter
public class BungeeTabPlayer extends ProxyTabPlayer {
/** Flag tracking plugin presence */
private static final boolean premiumVanish = ProxyServer.getInstance().getPluginManager().getPlugin("PremiumVanish") != null;
/** Player's scoreboard */
@NotNull
private final Scoreboard<BungeeTabPlayer> scoreboard = new BungeeScoreboard(this);
@@ -75,7 +70,7 @@ public class BungeeTabPlayer extends ProxyTabPlayer {
@Override
public void sendMessage(@NotNull IChatBaseComponent message) {
getPlayer().sendMessage(ComponentSerializer.parse(message.toString(getVersion())));
getPlayer().sendMessage(getPlatform().toComponent(message, getVersion()));
}
@Override
@@ -138,7 +133,7 @@ public class BungeeTabPlayer extends ProxyTabPlayer {
public boolean isVanished() {
try {
//noinspection ConstantConditions
if (premiumVanish && BungeeVanishAPI.isInvisible(getPlayer())) return true;
if (getPlatform().isPremiumVanish() && BungeeVanishAPI.isInvisible(getPlayer())) return true;
} catch (IllegalStateException ignored) {
// PV Bug: PremiumVanish must be enabled to use its API
}
@@ -161,9 +156,18 @@ public class BungeeTabPlayer extends ProxyTabPlayer {
* @param packet
* Packet to send
*/
@SneakyThrows
public void sendPacket(@NotNull DefinedPacket packet) {
if (!getPlayer().isConnected()) return;
BungeeMultiVersion.sendPacket(this, packet);
try {
((UserConnection)getPlayer()).sendPacketQueued(packet);
} catch (NullPointerException BungeeCordBug) {
// java.lang.NullPointerException: Cannot invoke "net.md_5.bungee.protocol.MinecraftEncoder.getProtocol()" because the return value of "io.netty.channel.ChannelPipeline.get(java.lang.Class)" is null
// at net.md_5.bungee.netty.ChannelWrapper.getEncodeProtocol(ChannelWrapper.java:51)
// at net.md_5.bungee.UserConnection.sendPacketQueued(UserConnection.java:194)
if (TAB.getInstance().getConfiguration().isDebugMode()) {
TAB.getInstance().getErrorManager().printError("Failed to deliver packet to player " + getName() +
" (online = " + getPlayer().isConnected() + "): " + packet, BungeeCordBug);
}
}
}
}
@@ -1,13 +1,13 @@
package me.neznamy.tab.platforms.bungeecord.tablist;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bungeecord.BungeeMultiVersion;
import me.neznamy.tab.platforms.bungeecord.BungeeTabPlayer;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.md_5.bungee.UserConnection;
import net.md_5.bungee.protocol.Property;
import net.md_5.bungee.protocol.packet.PlayerListHeaderFooter;
import net.md_5.bungee.protocol.packet.PlayerListItem.Item;
import net.md_5.bungee.tab.ServerUnique;
import org.jetbrains.annotations.NotNull;
@@ -33,7 +33,10 @@ public abstract class BungeeTabList implements TabList {
@Override
public void setPlayerListHeaderFooter(@NotNull IChatBaseComponent header, @NotNull IChatBaseComponent footer) {
player.sendPacket(BungeeMultiVersion.newPlayerListHeaderFooter(header, footer, player.getVersion()));
player.sendPacket(new PlayerListHeaderFooter(
player.getPlatform().toComponent(header, player.getVersion()),
player.getPlatform().toComponent(footer, player.getVersion())
));
}
@NotNull
@@ -47,7 +50,7 @@ public abstract class BungeeTabList implements TabList {
public Item entryToItem(Entry entry) {
Item item = item(entry.getUniqueId());
if (entry.getDisplayName() != null) {
BungeeMultiVersion.setDisplayName(item, entry.getDisplayName(), player.getVersion());
item.setDisplayName(player.getPlatform().toComponent(entry.getDisplayName(), player.getVersion()));
}
item.setGamemode(entry.getGameMode());
item.setListed(true);
@@ -1,6 +1,5 @@
package me.neznamy.tab.platforms.bungeecord.tablist;
import me.neznamy.tab.platforms.bungeecord.BungeeMultiVersion;
import me.neznamy.tab.platforms.bungeecord.BungeeTabPlayer;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import net.md_5.bungee.protocol.packet.PlayerListItem.Item;
@@ -33,7 +32,7 @@ public class BungeeTabList1193 extends BungeeTabList {
@Override
public void updateDisplayName(@NotNull UUID entry, @Nullable IChatBaseComponent displayName) {
Item item = item(entry);
if (displayName != null) BungeeMultiVersion.setDisplayName(item, displayName, player.getVersion());
if (displayName != null) item.setDisplayName(player.getPlatform().toComponent(displayName, player.getVersion()));
sendPacket(EnumSet.of(PlayerListItemUpdate.Action.UPDATE_DISPLAY_NAME), item);
}
@@ -1,6 +1,5 @@
package me.neznamy.tab.platforms.bungeecord.tablist;
import me.neznamy.tab.platforms.bungeecord.BungeeMultiVersion;
import me.neznamy.tab.platforms.bungeecord.BungeeTabPlayer;
import me.neznamy.tab.shared.Limitations;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
@@ -74,6 +73,11 @@ public class BungeeTabList17 extends BungeeTabList {
displayNames.put(entry.getUniqueId(), displayNameString);
}
@Override
public void setPlayerListHeaderFooter(@NotNull IChatBaseComponent header, @NotNull IChatBaseComponent footer) {
// Not available on 1.7
}
private void update(@NotNull PlayerListItem.Action action, @NotNull PlayerListItem.Item item) {
PlayerListItem packet = new PlayerListItem();
packet.setAction(action);
@@ -84,8 +88,8 @@ public class BungeeTabList17 extends BungeeTabList {
private PlayerListItem.Item createItem(@Nullable String username, @NotNull String displayName, int latency) {
PlayerListItem.Item item = new PlayerListItem.Item();
item.setUsername(username);
BungeeMultiVersion.setDisplayName(item, IChatBaseComponent.optimizedComponent(displayName), player.getVersion());
item.setPing(latency);
item.setDisplayName(player.getPlatform().toComponent(IChatBaseComponent.optimizedComponent(displayName), player.getVersion()));
return item;
}
}
@@ -1,6 +1,5 @@
package me.neznamy.tab.platforms.bungeecord.tablist;
import me.neznamy.tab.platforms.bungeecord.BungeeMultiVersion;
import me.neznamy.tab.platforms.bungeecord.BungeeTabPlayer;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import net.md_5.bungee.protocol.packet.PlayerListItem;
@@ -29,7 +28,7 @@ public class BungeeTabList18 extends BungeeTabList {
@Override
public void updateDisplayName(@NotNull UUID entry, @Nullable IChatBaseComponent displayName) {
Item item = item(entry);
if (displayName != null) BungeeMultiVersion.setDisplayName(item, displayName, player.getVersion());
if (displayName != null) item.setDisplayName(player.getPlatform().toComponent(displayName, player.getVersion()));
sendPacket(PlayerListItem.Action.UPDATE_DISPLAY_NAME, item);
}
+10 -60
View File
@@ -1,34 +1,3 @@
val minecraftVersion = "1.20.2"
val v115 = "0.28.5+1.15"
val v116 = "0.42.0+1.16"
val fabricApiVersions = mapOf(
"1.20.2" to "0.88.5+1.20.2",
"1.20.1" to "0.83.0+1.20.1",
"1.20" to "0.83.0+1.20",
"1.19.4" to "0.77.0+1.19.4",
"1.19.3" to "0.76.0+1.19.3",
"1.19.2" to "0.76.0+1.19.2",
"1.19.1" to "0.58.5+1.19.1",
"1.19" to "0.58.0+1.19",
"1.18.2" to "0.76.0+1.18.2",
"1.18.1" to "0.46.6+1.18",
"1.18" to "0.46.6+1.18",
"1.17.1" to "0.46.1+1.17",
"1.17" to "0.46.1+1.17",
"1.16.5" to v116,
"1.16.4" to v116,
"1.16.3" to v116,
"1.16.2" to v116,
"1.16.1" to v116,
"1.16" to v116,
"1.15.2" to v115,
"1.15.1" to v115,
"1.15" to v115,
"1.14.4" to "0.28.5+1.14",
)
plugins {
id("fabric-loom")
}
@@ -42,33 +11,14 @@ repositories {
}
dependencies {
implementation(projects.shared)
minecraft("com.mojang:minecraft:$minecraftVersion")
api(projects.shared)
minecraft("com.mojang:minecraft:1.20.4")
mappings(loom.officialMojangMappings())
modImplementation("net.fabricmc", "fabric-loader", "0.14.17")
modImplementation("me.lucko", "fabric-permissions-api", "0.2-SNAPSHOT")
modImplementation(fabricApi.module("fabric-api-base", fabricApiVersions[minecraftVersion]))
modImplementation(fabricApi.module("fabric-lifecycle-events-v1", fabricApiVersions[minecraftVersion]))
modImplementation(fabricApi.module("fabric-networking-api-v1", fabricApiVersions[minecraftVersion]))
modImplementation(fabricApi.module("fabric-command-api-v${if (minecraftVersion.split(".")[1].toInt() >= 19) "2" else "1"}", fabricApiVersions[minecraftVersion]))
if (minecraftVersion.split(".")[1].toInt() >= 16) {
modImplementation(fabricApi.module("fabric-entity-events-v1", fabricApiVersions[minecraftVersion]))
}
}
loom {
accessWidenerPath.set(file("src/main/resources/tab.accesswidener"))
}
tasks {
compileJava {
options.release.set(17)
}
validateAccessWidener {
// We don't want to validate the access wideners, as we have wideners for multiple versions, and the validation task will
// fail, as the classes for the old wideners don't exist in the new versions and vice versa.
enabled = false
}
}
modImplementation("me.lucko:fabric-permissions-api:0.2-SNAPSHOT")
modImplementation("net.fabricmc:fabric-loader:0.14.17")
val version = "0.91.1+1.20.4"
modImplementation(fabricApi.module("fabric-api-base", version))
modImplementation(fabricApi.module("fabric-lifecycle-events-v1", version))
modImplementation(fabricApi.module("fabric-networking-api-v1", version))
modImplementation(fabricApi.module("fabric-entity-events-v1", version))
}
@@ -1,41 +1,22 @@
package me.neznamy.tab.platforms.fabric;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.api.bossbar.BarColor;
import me.neznamy.tab.api.bossbar.BarStyle;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.bossbar.BossBar;
import me.neznamy.tab.shared.platform.BossBar;
import net.minecraft.server.level.ServerBossEvent;
import net.minecraft.world.BossEvent.BossBarColor;
import net.minecraft.world.BossEvent.BossBarOverlay;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
@RequiredArgsConstructor
public class FabricBossBar implements BossBar {
/** Color array for fast access */
private static final BossBarColor[] colors = new BossBarColor[] {
BossBarColor.PINK,
BossBarColor.BLUE,
BossBarColor.RED,
BossBarColor.GREEN,
BossBarColor.YELLOW,
BossBarColor.PURPLE,
BossBarColor.WHITE
};
/** Overlay array for fast access */
private static final BossBarOverlay[] overlays = new BossBarOverlay[] {
BossBarOverlay.PROGRESS,
BossBarOverlay.NOTCHED_6,
BossBarOverlay.NOTCHED_10,
BossBarOverlay.NOTCHED_12,
BossBarOverlay.NOTCHED_20
};
@NotNull
private final FabricTabPlayer player;
@@ -46,10 +27,10 @@ public class FabricBossBar implements BossBar {
public void create(@NotNull UUID id, @NotNull String title, float progress, @NotNull BarColor color, @NotNull BarStyle style) {
ServerBossEvent bar = new ServerBossEvent(
player.getPlatform().toComponent(IChatBaseComponent.optimizedComponent(title), player.getVersion()),
colors[color.ordinal()],
overlays[style.ordinal()]
BossBarColor.valueOf(color.name()),
BossBarOverlay.valueOf(style.name())
);
FabricMultiVersion.setProgress(bar, progress);
bar.setProgress(progress); // Somehow the compiled method name is same despite method being renamed in 1.17
bars.put(id, bar);
bar.addPlayer(player.getPlayer());
}
@@ -61,17 +42,17 @@ public class FabricBossBar implements BossBar {
@Override
public void update(@NotNull UUID id, float progress) {
FabricMultiVersion.setProgress(bars.get(id), progress);
bars.get(id).setProgress(progress); // Somehow the compiled method name is same despite method being renamed in 1.17
}
@Override
public void update(@NotNull UUID id, @NotNull BarStyle style) {
bars.get(id).setOverlay(overlays[style.ordinal()]);
bars.get(id).setOverlay(BossBarOverlay.valueOf(style.name()));
}
@Override
public void update(@NotNull UUID id, @NotNull BarColor color) {
bars.get(id).setColor(colors[color.ordinal()]);
bars.get(id).setColor(BossBarColor.valueOf(color.name()));
}
@Override
@@ -1,31 +1,37 @@
package me.neznamy.tab.platforms.fabric;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import me.neznamy.tab.shared.backend.EntityData;
import me.neznamy.tab.shared.backend.Location;
import me.neznamy.tab.shared.backend.entityview.EntityView;
import net.minecraft.network.protocol.game.*;
import net.minecraft.world.entity.Entity;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.minecraft.network.protocol.game.ClientboundMoveEntityPacket;
import net.minecraft.network.protocol.game.ClientboundRemoveEntitiesPacket;
import net.minecraft.network.protocol.game.ClientboundTeleportEntityPacket;
import net.minecraft.world.entity.decoration.ArmorStand;
import org.jetbrains.annotations.NotNull;
import java.util.UUID;
public record FabricEntityView(FabricTabPlayer player) implements EntityView {
@RequiredArgsConstructor
public class FabricEntityView implements EntityView {
private final FabricTabPlayer player;
@NotNull
private static final Entity dummyEntity = new ArmorStand(null, 0, 0, 0);
private static final ArmorStand dummyEntity = new ArmorStand(null, 0, 0, 0);
@Override
public void spawnEntity(int entityId, @NotNull UUID id, @NotNull Object entityType, @NotNull Location location,
@NotNull EntityData data) {
player.sendPacket(FabricMultiVersion.addEntity(entityId, id, entityType, location, data));
player.sendPacket(FabricTAB.getVersion().spawnEntity(entityId, id, entityType, location));
updateEntityMetadata(entityId, data);
}
@Override
public void updateEntityMetadata(int entityId, @NotNull EntityData data) {
player.sendPacket(FabricMultiVersion.newEntityMetadata(entityId, data));
player.sendPacket(FabricTAB.getVersion().newEntityMetadata(entityId, data));
}
@Override
@@ -37,12 +43,12 @@ public record FabricEntityView(FabricTabPlayer player) implements EntityView {
@Override
public void destroyEntities(int... entities) {
FabricMultiVersion.destroyEntities(player, entities);
FabricTAB.getVersion().destroyEntities(player, entities);
}
@Override
public boolean isDestroyPacket(@NotNull Object packet) {
return FabricMultiVersion.isEntityDestroyPacket(packet);
return packet instanceof ClientboundRemoveEntitiesPacket;
}
@Override
@@ -52,7 +58,7 @@ public record FabricEntityView(FabricTabPlayer player) implements EntityView {
@Override
public boolean isNamedEntitySpawnPacket(@NotNull Object packet) {
return packet instanceof ClientboundAddEntityPacket;
return FabricTAB.getVersion().isSpawnPlayerPacket(packet);
}
@Override
@@ -66,33 +72,35 @@ public record FabricEntityView(FabricTabPlayer player) implements EntityView {
}
@Override
@SneakyThrows
public int getTeleportEntityId(@NotNull Object teleportPacket) {
return ((ClientboundTeleportEntityPacket) teleportPacket).getId();
// Reflection because on 1.16.5 there is no getter and per-version code would only add unnecessary code
return ReflectionUtils.getFields(ClientboundTeleportEntityPacket.class, int.class).get(0).getInt(teleportPacket);
}
@Override
@SneakyThrows
public int getMoveEntityId(@NotNull Object movePacket) {
return ((ClientboundMoveEntityPacket) movePacket).entityId;
return (int) ReflectionUtils.getFields(ClientboundMoveEntityPacket.class, int.class).get(0).get(movePacket);
}
@Override
public int getSpawnedPlayer(@NotNull Object playerSpawnPacket) {
return ((ClientboundAddEntityPacket) playerSpawnPacket).getId();
return FabricTAB.getVersion().getSpawnedPlayer(playerSpawnPacket);
}
@Override
public int[] getDestroyedEntities(@NotNull Object destroyPacket) {
return FabricMultiVersion.getDestroyedEntities(destroyPacket);
return FabricTAB.getVersion().getDestroyedEntities(destroyPacket);
}
@Override
public boolean isBundlePacket(@NotNull Object packet) {
return FabricMultiVersion.isBundlePacket(packet);
return FabricTAB.getVersion().isBundlePacket(packet);
}
@SuppressWarnings("unchecked")
@Override
public Iterable<Object> getPackets(@NotNull Object bundlePacket) {
return (Iterable<Object>) (Object) FabricMultiVersion.getPackets(bundlePacket);
return FabricTAB.getVersion().getPackets(bundlePacket);
}
}
@@ -1,8 +1,11 @@
package me.neznamy.tab.platforms.fabric;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.platform.EventListener;
import me.neznamy.tab.shared.platform.TabPlayer;
import net.fabricmc.fabric.api.entity.event.v1.ServerEntityWorldChangeEvents;
import net.fabricmc.fabric.api.entity.event.v1.ServerPlayerEvents;
import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents;
import net.minecraft.server.level.ServerPlayer;
import org.jetbrains.annotations.NotNull;
@@ -12,8 +15,18 @@ public class FabricEventListener extends EventListener<ServerPlayer> {
public void register() {
ServerPlayConnectionEvents.DISCONNECT.register((connection, $) -> quit(connection.player.getUUID()));
ServerPlayConnectionEvents.JOIN.register((connection, $, $$) -> join(connection.player));
FabricMultiVersion.registerEntityEvents(this::replacePlayer, this::worldChange);
//TODO command preprocess
if (ProtocolVersion.fromFriendlyName(FabricTAB.getVersion().getServerVersion()).getMinorVersion() >= 16) {
// Added in 1.16
ServerPlayerEvents.AFTER_RESPAWN.register(
(oldPlayer, newPlayer, alive) -> {
replacePlayer(newPlayer.getUUID(), newPlayer);
// respawning from death & taking end portal in the end do not call world change event
worldChange(newPlayer.getUUID(), FabricTAB.getVersion().getName(FabricTAB.getVersion().getLevel(newPlayer)));
});
ServerEntityWorldChangeEvents.AFTER_PLAYER_CHANGE_WORLD.register(
(player, origin, destination) -> worldChange(player.getUUID(), FabricTAB.getVersion().getName(destination)));
} // TODO else
}
@Override
@@ -1,324 +0,0 @@
package me.neznamy.tab.platforms.fabric;
import com.mojang.authlib.properties.Property;
import me.neznamy.tab.platforms.fabric.features.FabricNameTagX;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.backend.EntityData;
import me.neznamy.tab.shared.backend.Location;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.TabPlayer;
import net.minecraft.SharedConstants;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.*;
import net.minecraft.network.syncher.EntityDataSerializers;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.server.level.ServerBossEvent;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.level.GameType;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.storage.ServerLevelData;
import net.minecraft.world.phys.Vec3;
import net.minecraft.world.scores.Objective;
import net.minecraft.world.scores.PlayerTeam;
import org.jetbrains.annotations.NotNull;
import java.util.*;
import java.util.function.BiConsumer;
/**
* Class that contains (almost) all code that needs to be changed
* to set compatibility with any MC version that fabric supports (1.14.4+).
*/
public class FabricMultiVersion {
private static final int armorStandFlagsPosition = getArmorStandFlagsPosition();
private static int getArmorStandFlagsPosition() {
int minorVersion = ProtocolVersion.fromFriendlyName(SharedConstants.getCurrentVersion().getName()).getMinorVersion();
if (minorVersion >= 17) {
//1.17.x, 1.18.x, 1.19.x, 1.20.x
return 15;
} else if (minorVersion >= 15) {
//1.15.x, 1.16.x
return 14;
} else {
//1.14.x
return 13;
}
}
public static String getWorldName(Level level) {
// 1.15.2-
// return level.getLevelData().getLevelName() + level.dimension.getType().getFileSuffix();
// 1.16+
String path = level.dimension().location().getPath();
String dimensionSuffix = switch (path) {
case "overworld" -> ""; // No suffix for overworld
case "the_nether" -> "_nether";
default -> "_" + path; // End + default behavior for other dimensions created by mods
};
return ((ServerLevelData)level.getLevelData()).getLevelName() + dimensionSuffix;
}
public static void sendSystemMessage(CommandSourceStack source, Component message) {
// 1.19.4-
// source.sendSuccess(message, false);
// 1.19.1+
source.sendSystemMessage(message);
}
public static boolean isSneaking(ServerPlayer player) {
// 1.14.4-
// return player.isSneaking();
// 1.15+
return player.isCrouching();
}
public static void registerNameTagXEvents(FabricNameTagX nameTagX) {
// 1.16+
net.fabricmc.fabric.api.entity.event.v1.ServerPlayerEvents.AFTER_RESPAWN.register((oldPlayer, newPlayer, alive) -> {
if (nameTagX.enabled) nameTagX.respawn(oldPlayer.getUUID());
});
// TODO sneaking
}
public static void registerEntityEvents(BiConsumer<UUID, ServerPlayer> respawnAction, BiConsumer<UUID, String> worldChangeAction) {
// 1.16+
net.fabricmc.fabric.api.entity.event.v1.ServerPlayerEvents.AFTER_RESPAWN.register(
(oldPlayer, newPlayer, alive) -> {
respawnAction.accept(newPlayer.getUUID(), newPlayer);
// respawning from death & taking end portal in the end do not call world change event
worldChangeAction.accept(newPlayer.getUUID(), getWorldName(newPlayer.level));
});
net.fabricmc.fabric.api.entity.event.v1.ServerEntityWorldChangeEvents.AFTER_PLAYER_CHANGE_WORLD.register(
(player, origin, destination) -> worldChangeAction.accept(player.getUUID(), getWorldName(destination)));
}
public static ClientboundTabListPacket setHeaderFooter(Component header, Component footer) {
// 1.16.5-
/*ClientboundTabListPacket packet = new ClientboundTabListPacket();
packet.header = header;
packet.footer = footer;
return packet;*/
// 1.17+
return new ClientboundTabListPacket(header, footer);
}
public static ClientboundSetPlayerTeamPacket createTeam(PlayerTeam team) {
// 1.16.5-
// return new ClientboundSetPlayerTeamPacket(team, 0);
// 1.17+
return ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, true);
}
public static ClientboundSetPlayerTeamPacket removeTeam(PlayerTeam team) {
// 1.16.5-
// return new ClientboundSetPlayerTeamPacket(team, 1);
// 1.17+
return ClientboundSetPlayerTeamPacket.createRemovePacket(team);
}
public static ClientboundSetPlayerTeamPacket updateTeam(PlayerTeam team) {
// 1.16.5-
// return new ClientboundSetPlayerTeamPacket(team, 2);
// 1.17+
return ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, false);
}
public static void setProgress(ServerBossEvent bar, float progress) {
// 1.16.5-
// bar.setPercent(progress);
// 1.17+
bar.setProgress(progress);
}
public static int[] getDestroyedEntities(Object destroyPacket) {
// 1.16.5-
// return ((ClientboundRemoveEntitiesPacket) destroyPacket).getEntityIds();
// 1.17
// return new int[]{((ClientboundRemoveEntityPacket)destroyPacket).getEntityId()};
// 1.17.1+
return ((ClientboundRemoveEntitiesPacket) destroyPacket).getEntityIds().toIntArray();
}
public static void destroyEntities(FabricTabPlayer player, int... entities) {
// 1.17
/*for (int i : entities) {
player.sendPacket(new ClientboundRemoveEntityPacket(i));
}*/
// every other version
player.sendPacket(new ClientboundRemoveEntitiesPacket(entities));
}
public static boolean isEntityDestroyPacket(Object packet) {
// 1.17
// return packet instanceof ClientboundRemoveEntityPacket;
// every other version
return packet instanceof ClientboundRemoveEntitiesPacket;
}
public static void registerCommand() {
// 1.18.2-
// net.fabricmc.fabric.api.command.v1.CommandRegistrationCallback.EVENT.register((dispatcher, $) -> new FabricTabCommand().onRegisterCommands(dispatcher));
// 1.19+
net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback.EVENT.register((dispatcher, $, $$) -> new FabricTabCommand().onRegisterCommands(dispatcher));
}
public static void sendMessage(FabricTabPlayer player, Component message) {
// 1.15.2-
// player.getPlayer().sendMessage(message);
// 1.16 - 1.18.2
// player.getPlayer().sendMessage(message, new UUID(0, 0));
// 1.19+
player.getPlayer().sendSystemMessage(message);
}
public static ClientboundAddEntityPacket addEntity(int entityId, @NotNull UUID id, @NotNull Object entityType,
@NotNull Location location, @NotNull EntityData data) {
// 1.18.2-
// return new ClientboundAddEntityPacket(entityId, id, location.getX(), location.getY(), location.getZ(), 0, 0, (EntityType<?>) entityType, 0, Vec3.ZERO);
// 1.19+
return new ClientboundAddEntityPacket(entityId, id, location.getX(), location.getY(), location.getZ(), 0, 0, (EntityType<?>) entityType, 0, Vec3.ZERO, 0);
}
public static boolean isPlayerInfo(Object packet) {
// 1.19.2-
// return packet instanceof ClientboundPlayerInfoPacket;
// 1.19.3+
return packet instanceof ClientboundPlayerInfoUpdatePacket;
}
public static Packet<?> build(FabricTabList.Action action, FabricTabList.Builder entry) {
// 1.16.5-
/*ClientboundPlayerInfoPacket packet = new ClientboundPlayerInfoPacket(ClientboundPlayerInfoPacket.Action.valueOf(action.name()), Collections.emptyList());
packet.entries = Collections.singletonList(new ClientboundPlayerInfoPacket().new PlayerUpdate(
entry.createProfile(), entry.getLatency(), GameType.byId(entry.getGameMode()), entry.getDisplayName()
));
return packet;*/
// 1.17 - 1.18.2
/*ClientboundPlayerInfoPacket packet = new ClientboundPlayerInfoPacket(ClientboundPlayerInfoPacket.Action.valueOf(action.name()), Collections.emptyList());
packet.entries = Collections.singletonList(new ClientboundPlayerInfoPacket.PlayerUpdate(
entry.createProfile(), entry.getLatency(), GameType.byId(entry.getGameMode()), entry.getDisplayName()
));
return packet;*/
// 1.19 - 1.19.2
/*ClientboundPlayerInfoPacket packet = new ClientboundPlayerInfoPacket(ClientboundPlayerInfoPacket.Action.valueOf(action.name()), Collections.emptyList());
packet.entries = Collections.singletonList(new ClientboundPlayerInfoPacket.PlayerUpdate(
entry.createProfile(), entry.getLatency(), GameType.byId(entry.getGameMode()), entry.getDisplayName(), null
));
return packet;*/
// 1.19.3+
if (action == FabricTabList.Action.REMOVE_PLAYER) {
return new ClientboundPlayerInfoRemovePacket(Collections.singletonList(entry.getId()));
}
EnumSet<ClientboundPlayerInfoUpdatePacket.Action> actions = action == FabricTabList.Action.ADD_PLAYER ?
EnumSet.allOf(ClientboundPlayerInfoUpdatePacket.Action.class) :
EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.valueOf(action.name()));
ClientboundPlayerInfoUpdatePacket packet = new ClientboundPlayerInfoUpdatePacket(actions, Collections.emptyList());
packet.entries = Collections.singletonList(new ClientboundPlayerInfoUpdatePacket.Entry(
entry.getId(),
entry.createProfile(),
true,
entry.getLatency(),
GameType.byId(entry.getGameMode()),
entry.getDisplayName(),
null
));
return packet;
}
public static EntityData createDataWatcher(@NotNull TabPlayer viewer, byte flags, @NotNull String displayName, boolean nameVisible) {
// 1.19.2-
/*SynchedEntityData data = new SynchedEntityData(null);
data.define(new EntityDataAccessor<>(0, EntityDataSerializers.BYTE), flags);
data.define(new EntityDataAccessor<>(2, EntityDataSerializers.OPTIONAL_COMPONENT),
Optional.of(((FabricTabPlayer)viewer).getPlatform().toComponent(IChatBaseComponent.optimizedComponent(displayName), viewer.getVersion())));
data.define(new EntityDataAccessor<>(3, EntityDataSerializers.BOOLEAN), nameVisible);
data.define(new EntityDataAccessor<>(15, EntityDataSerializers.BYTE), (byte)16);
return () -> data;*/
// 1.19.3+
return () -> java.util.Arrays.asList(
new SynchedEntityData.DataValue<>(0, EntityDataSerializers.BYTE, flags),
new SynchedEntityData.DataValue<>(2, EntityDataSerializers.OPTIONAL_COMPONENT,
Optional.of(((FabricTabPlayer)viewer).getPlatform().toComponent(IChatBaseComponent.optimizedComponent(displayName), viewer.getVersion()))),
new SynchedEntityData.DataValue<>(3, EntityDataSerializers.BOOLEAN, nameVisible),
new SynchedEntityData.DataValue<>(armorStandFlagsPosition, EntityDataSerializers.BYTE, (byte)16)
);
}
@SuppressWarnings("unchecked")
public static ClientboundSetEntityDataPacket newEntityMetadata(int entityId, EntityData data) {
// 1.19.2-
// return new ClientboundSetEntityDataPacket(entityId, (SynchedEntityData) data.build(), true);
// 1.19.3+
return new ClientboundSetEntityDataPacket(entityId, (List<SynchedEntityData.DataValue<?>>) data.build());
}
public static boolean isBundlePacket(Object packet) {
// 1.19.4+
return packet instanceof ClientboundBundlePacket;
}
public static Iterable<Packet<ClientGamePacketListener>> getPackets(Object bundlePacket) {
// 1.19.4+
return ((ClientboundBundlePacket)bundlePacket).subPackets();
}
public static ClientboundSetDisplayObjectivePacket newDisplayObjective(int slot, Objective objective) {
// 1.20.1-
// return new ClientboundSetDisplayObjectivePacket(slot, objective);
// 1.20.2+
return new ClientboundSetDisplayObjectivePacket(net.minecraft.world.scores.DisplaySlot.values()[slot], objective);
}
public static int getSlot(ClientboundSetDisplayObjectivePacket packet) {
// 1.20.1-
// return packet.getSlot();
// 1.20.2+
return packet.getSlot().ordinal();
}
public static int getPing(FabricTabPlayer player) {
// 1.20.1-
// return player.getPlayer().latency;
// 1.20.2+
return player.getPlayer().connection.latency();
}
public static TabList.Skin propertyToSkin(Property property) {
// 1.20.1-
// return new TabList.Skin(property.getValue(), property.getSignature());
// 1.20.2+
return new TabList.Skin(property.value(), property.signature());
}
}
@@ -1,5 +1,9 @@
package me.neznamy.tab.platforms.fabric;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import me.lucko.fabric.api.permissions.v0.Permissions;
import me.neznamy.tab.platforms.fabric.features.FabricNameTagX;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.TAB;
@@ -11,9 +15,10 @@ import me.neznamy.tab.shared.features.nametags.NameTag;
import me.neznamy.tab.shared.features.types.TabFeature;
import me.neznamy.tab.shared.placeholders.expansion.EmptyTabExpansion;
import me.neznamy.tab.shared.placeholders.expansion.TabExpansion;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.fabricmc.fabric.api.networking.v1.PlayerLookup;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.SharedConstants;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.network.chat.Component;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerPlayer;
@@ -25,7 +30,13 @@ import java.io.File;
/**
* Platform implementation for Fabric
*/
public record FabricPlatform(MinecraftServer server) implements BackendPlatform {
@RequiredArgsConstructor
@Getter
public class FabricPlatform implements BackendPlatform {
private static final boolean fabricPermissionsApi = FabricLoader.getInstance().isModLoaded("fabric-permissions-api-v0");
private final MinecraftServer server;
@Override
public void registerUnknownPlaceholder(@NotNull String identifier) {
@@ -42,7 +53,7 @@ public record FabricPlatform(MinecraftServer server) implements BackendPlatform
@Override
@NotNull
public PipelineInjector createPipelineInjector() {
return new FabricPipelineInjector();
return FabricTAB.getVersion().createPipelineInjector();
}
@Override
@@ -64,19 +75,34 @@ public record FabricPlatform(MinecraftServer server) implements BackendPlatform
}
@Override
@SneakyThrows
public void logInfo(@NotNull IChatBaseComponent message) {
MinecraftServer.LOGGER.info("[TAB] " + message.toRawText());
Object logger = getLogger();
logger.getClass().getMethod("info", String.class).invoke(logger, message.toRawText());
}
@Override
@SneakyThrows
public void logWarn(@NotNull IChatBaseComponent message) {
MinecraftServer.LOGGER.warn("[TAB] " + message.toRawText()); // Fabric console does not support colors
Object logger = getLogger();
logger.getClass().getMethod("warn", String.class).invoke(logger, message.toRawText());
}
@SneakyThrows
private Object getLogger() {
Class<?> loggerClass;
if (getServerVersion().getNetworkId() >= ProtocolVersion.V1_18_2.getNetworkId()) {
loggerClass = Class.forName("org.slf4j.Logger");
} else {
loggerClass = Class.forName("org.apache.logging.log4j.Logger");
}
return ReflectionUtils.getFields(MinecraftServer.class, loggerClass).get(0).get(null);
}
@Override
@NotNull
public String getServerVersionInfo() {
return "[Fabric] " + SharedConstants.getCurrentVersion().getName();
return "[Fabric] " + FabricTAB.getVersion().getServerVersion();
}
@Override
@@ -97,7 +123,7 @@ public record FabricPlatform(MinecraftServer server) implements BackendPlatform
@Override
@NotNull
public ProtocolVersion getServerVersion() {
return ProtocolVersion.fromFriendlyName(SharedConstants.getCurrentVersion().getName());
return ProtocolVersion.fromFriendlyName(FabricTAB.getVersion().getServerVersion());
}
@Override
@@ -116,7 +142,7 @@ public record FabricPlatform(MinecraftServer server) implements BackendPlatform
* @return Converted component
*/
public Component toComponent(@NotNull IChatBaseComponent component, @NotNull ProtocolVersion version) {
return Component.Serializer.fromJson(component.toString(version));
return FabricTAB.getVersion().deserialize(component.toString(version));
}
@Override
@@ -126,6 +152,11 @@ public record FabricPlatform(MinecraftServer server) implements BackendPlatform
@Override
public double getMSPT() {
return server.getAverageTickTime();
return FabricTAB.getVersion().getMSPT();
}
public boolean hasPermission(@NotNull CommandSourceStack source, @NotNull String permission) {
if (source.hasPermission(4)) return true;
return fabricPermissionsApi && Permissions.check(source, permission);
}
}
@@ -1,96 +1,63 @@
package me.neznamy.tab.platforms.fabric;
import java.util.Collection;
import java.util.Objects;
import me.neznamy.tab.shared.chat.EnumChatFormat;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.Scoreboard;
import net.minecraft.ChatFormatting;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.game.ClientboundSetObjectivePacket;
import net.minecraft.network.protocol.game.ClientboundSetScorePacket;
import net.minecraft.server.ServerScoreboard;
import net.minecraft.world.scores.Objective;
import net.minecraft.world.scores.PlayerTeam;
import net.minecraft.world.scores.Team;
import net.minecraft.world.scores.criteria.ObjectiveCriteria;
import net.minecraft.world.scores.criteria.ObjectiveCriteria.RenderType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
public class FabricScoreboard extends Scoreboard<FabricTabPlayer> {
@NotNull
private static final net.minecraft.world.scores.Scoreboard dummyScoreboard = new net.minecraft.world.scores.Scoreboard();
@NotNull
private static final Component EMPTY_COMPONENT = Objects.requireNonNull(Component.Serializer.fromJson(IChatBaseComponent.EMPTY_COMPONENT));
private final Map<String, Objective> objectives = new HashMap<>();
public FabricScoreboard(FabricTabPlayer player) {
super(player);
}
@Override
public void setDisplaySlot(@NotNull DisplaySlot slot, @NotNull String objective) {
player.sendPacket(
FabricMultiVersion.newDisplayObjective(
slot.ordinal(),
new Objective(
dummyScoreboard,
objective,
ObjectiveCriteria.DUMMY,
EMPTY_COMPONENT,
ObjectiveCriteria.RenderType.INTEGER
)
)
);
public void setDisplaySlot0(int slot, @NotNull String objective) {
player.sendPacket(FabricTAB.getVersion().setDisplaySlot(slot, objectives.get(objective)));
}
@Override
public void registerObjective0(@NotNull String objectiveName, @NotNull String title, @NotNull HealthDisplay display) {
player.sendPacket(
new ClientboundSetObjectivePacket(
new Objective(
dummyScoreboard,
objectiveName,
ObjectiveCriteria.DUMMY,
player.getPlatform().toComponent(IChatBaseComponent.optimizedComponent(title), player.getVersion()),
ObjectiveCriteria.RenderType.valueOf(display.name())
),
0
)
public void registerObjective0(@NotNull String objectiveName, @NotNull String title, int display,
@Nullable IChatBaseComponent numberFormat) {
Objective obj = FabricTAB.getVersion().newObjective(
objectiveName,
toComponent(title),
RenderType.values()[display],
numberFormat == null ? null : toComponent(numberFormat)
);
objectives.put(objectiveName, obj);
player.sendPacket(new ClientboundSetObjectivePacket(obj, ObjectiveAction.REGISTER));
}
@Override
public void unregisterObjective0(@NotNull String objectiveName) {
player.sendPacket(
new ClientboundSetObjectivePacket(
new Objective(
dummyScoreboard,
objectiveName,
ObjectiveCriteria.DUMMY,
EMPTY_COMPONENT,
ObjectiveCriteria.RenderType.INTEGER
),
1
)
);
player.sendPacket(new ClientboundSetObjectivePacket(objectives.remove(objectiveName), ObjectiveAction.UNREGISTER));
}
@Override
public void updateObjective0(@NotNull String objectiveName, @NotNull String title, @NotNull HealthDisplay display) {
player.sendPacket(
new ClientboundSetObjectivePacket(
new Objective(
dummyScoreboard,
objectiveName,
ObjectiveCriteria.DUMMY,
player.getPlatform().toComponent(IChatBaseComponent.optimizedComponent(title), player.getVersion()),
ObjectiveCriteria.RenderType.valueOf(display.name())
),
2
)
);
public void updateObjective0(@NotNull String objectiveName, @NotNull String title, int display,
@Nullable IChatBaseComponent numberFormat) {
Objective obj = objectives.get(objectiveName);
obj.setDisplayName(toComponent(title));
obj.setRenderType(RenderType.values()[display]);
player.sendPacket(new ClientboundSetObjectivePacket(obj, ObjectiveAction.UPDATE));
}
@Override
@@ -103,15 +70,15 @@ public class FabricScoreboard extends Scoreboard<FabricTabPlayer> {
team.setColor(ChatFormatting.valueOf(EnumChatFormat.lastColorsOf(prefix).name()));
team.setCollisionRule(Team.CollisionRule.valueOf(collision.name()));
team.setNameTagVisibility(Team.Visibility.valueOf(visibility.name()));
team.setPlayerPrefix(player.getPlatform().toComponent(IChatBaseComponent.optimizedComponent(prefix), player.getVersion()));
team.setPlayerSuffix(player.getPlatform().toComponent(IChatBaseComponent.optimizedComponent(suffix), player.getVersion()));
team.setPlayerPrefix(toComponent(prefix));
team.setPlayerSuffix(toComponent(suffix));
team.getPlayers().addAll(players);
player.sendPacket(FabricMultiVersion.createTeam(team));
player.sendPacket(FabricTAB.getVersion().registerTeam(team));
}
@Override
public void unregisterTeam0(@NotNull String name) {
player.sendPacket(FabricMultiVersion.removeTeam(new PlayerTeam(dummyScoreboard, name)));
player.sendPacket(FabricTAB.getVersion().unregisterTeam(new PlayerTeam(dummyScoreboard, name)));
}
@Override
@@ -123,18 +90,35 @@ public class FabricScoreboard extends Scoreboard<FabricTabPlayer> {
team.setColor(ChatFormatting.valueOf(EnumChatFormat.lastColorsOf(prefix).name()));
team.setCollisionRule(Team.CollisionRule.valueOf(collision.name()));
team.setNameTagVisibility(Team.Visibility.valueOf(visibility.name()));
team.setPlayerPrefix(player.getPlatform().toComponent(IChatBaseComponent.optimizedComponent(prefix), player.getVersion()));
team.setPlayerSuffix(player.getPlatform().toComponent(IChatBaseComponent.optimizedComponent(suffix), player.getVersion()));
player.sendPacket(FabricMultiVersion.updateTeam(team));
team.setPlayerPrefix(toComponent(prefix));
team.setPlayerSuffix(toComponent(suffix));
player.sendPacket(FabricTAB.getVersion().updateTeam(team));
}
@Override
public void setScore0(@NotNull String objective, @NotNull String playerName, int score) {
player.sendPacket(new ClientboundSetScorePacket(ServerScoreboard.Method.CHANGE, objective, playerName, score));
public void setScore0(@NotNull String objective, @NotNull String scoreHolder, int score,
@Nullable IChatBaseComponent displayName, @Nullable IChatBaseComponent numberFormat) {
player.sendPacket(FabricTAB.getVersion().setScore(
objective,
scoreHolder,
score,
displayName == null ? null : toComponent(displayName),
numberFormat == null ? null : toComponent(numberFormat)
));
}
@Override
public void removeScore0(@NotNull String objective, @NotNull String playerName) {
player.sendPacket(new ClientboundSetScorePacket(ServerScoreboard.Method.REMOVE, objective, playerName, 0));
public void removeScore0(@NotNull String objective, @NotNull String scoreHolder) {
player.sendPacket(FabricTAB.getVersion().removeScore(objective, scoreHolder));
}
@NotNull
private Component toComponent(@NotNull String string) {
return toComponent(IChatBaseComponent.optimizedComponent(string));
}
@NotNull
private Component toComponent(@NotNull IChatBaseComponent component) {
return player.getPlatform().toComponent(component, player.getVersion());
}
}
@@ -1,26 +1,38 @@
package me.neznamy.tab.platforms.fabric;
import me.lucko.fabric.api.permissions.v0.Permissions;
import lombok.Getter;
import me.neznamy.tab.shared.TAB;
import net.fabricmc.api.DedicatedServerModInitializer;
import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.commands.CommandSourceStack;
import org.jetbrains.annotations.NotNull;
public class FabricTAB implements DedicatedServerModInitializer {
private static final boolean fabricPermissionsApi = FabricLoader.getInstance().isModLoaded("fabric-permissions-api-v0");
@Getter
private static VersionLoader version;
@Override
public void onInitializeServer() {
FabricMultiVersion.registerCommand();
ServerLifecycleEvents.SERVER_STARTING.register(server -> TAB.create(new FabricPlatform(server)));
ServerLifecycleEvents.SERVER_STOPPING.register($ -> TAB.getInstance().unload());
}
public static boolean hasPermission(@NotNull CommandSourceStack source, @NotNull String permission) {
if (source.hasPermission(4)) return true;
return fabricPermissionsApi && Permissions.check(source, permission);
String[] modules = {
"v1_15_2", // 1.14.4, 1.15.2
"v1_16_5",
"v1_17_1", // 1.17, 1.17.1
"v1_18_2", // 1.18, 1.18.1, 1.18.2
"v1_19_2", // 1.19, 1.19.1, 1.19.2
"v1_19_3",
"v1_20_1", // 1.19.4, 1.20, 1.20.1
"v1_20_2",
"v1_20_4" // 1.20.3, 1.20.4
};
for (String module : modules) {
try {
version = (VersionLoader) Class.forName("me.neznamy.tab.platforms.fabric." + module + ".FabricTAB").getConstructor().newInstance();
if (!version.getSupportedVersions().contains(version.getServerVersion())) continue;
version.registerCommandCallback();
ServerLifecycleEvents.SERVER_STARTING.register(server -> TAB.create(new FabricPlatform(server)));
ServerLifecycleEvents.SERVER_STOPPING.register($ -> TAB.getInstance().unload());
return;
} catch (Throwable ignored) {}
}
throw new IllegalStateException("Your server version is marked as compatible, but a compatibility issue was found.");
}
}
@@ -48,10 +48,10 @@ public class FabricTabCommand {
private int executeCommand(@NotNull CommandSourceStack source, @NotNull String[] args) {
if (TAB.getInstance().isPluginDisabled()) {
boolean hasReloadPermission = FabricTAB.hasPermission(source, TabConstants.Permission.COMMAND_RELOAD);
boolean hasAdminPermission = FabricTAB.hasPermission(source, TabConstants.Permission.COMMAND_ALL);
boolean hasReloadPermission = ((FabricPlatform)TAB.getInstance().getPlatform()).hasPermission(source, TabConstants.Permission.COMMAND_RELOAD);
boolean hasAdminPermission = ((FabricPlatform)TAB.getInstance().getPlatform()).hasPermission(source, TabConstants.Permission.COMMAND_ALL);
for (String message : TAB.getInstance().getDisabledCommand().execute(args, hasReloadPermission, hasAdminPermission)) {
FabricMultiVersion.sendSystemMessage(source, ((FabricPlatform)TAB.getInstance().getPlatform()).toComponent(
FabricTAB.getVersion().sendMessage(source, ((FabricPlatform)TAB.getInstance().getPlatform()).toComponent(
IChatBaseComponent.optimizedComponent(message), TAB.getInstance().getServerVersion()));
}
} else {
@@ -4,41 +4,49 @@ import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.TabList;
import net.minecraft.network.chat.Component;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.UUID;
public record FabricTabList(@NotNull FabricTabPlayer player) implements TabList {
@RequiredArgsConstructor
public class FabricTabList implements TabList {
@NotNull
private final FabricTabPlayer player;
@Override
public void removeEntry(@NotNull UUID entry) {
player.sendPacket(FabricMultiVersion.build(Action.REMOVE_PLAYER, new Builder(entry)));
player.sendPacket(FabricTAB.getVersion().build(Action.UPDATE_DISPLAY_NAME, new Builder(entry)));
}
@Override
public void updateDisplayName(@NotNull UUID entry, @Nullable IChatBaseComponent displayName) {
player.sendPacket(FabricMultiVersion.build(Action.UPDATE_DISPLAY_NAME,
player.sendPacket(FabricTAB.getVersion().build(Action.UPDATE_DISPLAY_NAME,
new Builder(entry).setDisplayName(displayName == null ? null : player.getPlatform().toComponent(displayName, player.getVersion()))
));
}
@Override
public void updateLatency(@NotNull UUID entry, int latency) {
player.sendPacket(FabricMultiVersion.build(Action.UPDATE_LATENCY, new Builder(entry).setLatency(latency)));
player.sendPacket(FabricTAB.getVersion().build(Action.UPDATE_LATENCY,
new Builder(entry).setLatency(latency)));
}
@Override
public void updateGameMode(@NotNull UUID entry, int gameMode) {
player.sendPacket(FabricMultiVersion.build(Action.UPDATE_GAME_MODE, new Builder(entry).setGameMode(gameMode)));
player.sendPacket(FabricTAB.getVersion().build(Action.UPDATE_GAME_MODE,
new Builder(entry).setGameMode(gameMode)));
}
@Override
public void addEntry(@NotNull Entry entry) {
player.sendPacket(FabricMultiVersion.build(Action.ADD_PLAYER, new Builder(entry.getUniqueId())
player.sendPacket(FabricTAB.getVersion().build(Action.ADD_PLAYER,
new Builder(entry.getUniqueId())
.setName(entry.getName())
.setSkin(entry.getSkin())
.setGameMode(entry.getGameMode())
@@ -48,11 +56,12 @@ public record FabricTabList(@NotNull FabricTabPlayer player) implements TabList
}
@Override
@SneakyThrows
public void setPlayerListHeaderFooter(@NotNull IChatBaseComponent header, @NotNull IChatBaseComponent footer) {
player.getPlayer().connection.send(FabricMultiVersion.setHeaderFooter(
player.sendPacket(FabricTAB.getVersion().newHeaderFooter(
player.getPlatform().toComponent(header, player.getVersion()),
player.getPlatform().toComponent(footer, player.getVersion()))
);
player.getPlatform().toComponent(footer, player.getVersion())
));
}
@RequiredArgsConstructor
@@ -7,7 +7,7 @@ import me.neznamy.tab.shared.backend.entityview.EntityView;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.bossbar.BossBar;
import me.neznamy.tab.shared.platform.BossBar;
import net.minecraft.network.protocol.Packet;
import net.minecraft.server.level.ServerPlayer;
import org.jetbrains.annotations.NotNull;
@@ -31,22 +31,23 @@ public class FabricTabPlayer extends BackendTabPlayer {
private final EntityView entityView = new FabricEntityView(this);
public FabricTabPlayer(@NotNull FabricPlatform platform, @NotNull ServerPlayer player) {
super(platform, player, player.getUUID(), player.getGameProfile().getName(), FabricMultiVersion.getWorldName(player.level));
super(platform, player, player.getUUID(), player.getGameProfile().getName(),
FabricTAB.getVersion().getName(FabricTAB.getVersion().getLevel(player)));
}
@Override
public boolean hasPermission(@NotNull String permission) {
return FabricTAB.hasPermission(getPlayer().createCommandSourceStack(), permission);
return getPlatform().hasPermission(getPlayer().createCommandSourceStack(), permission);
}
@Override
public int getPing() {
return FabricMultiVersion.getPing(this);
return FabricTAB.getVersion().getPing(getPlayer());
}
@Override
public void sendMessage(@NotNull IChatBaseComponent message) {
FabricMultiVersion.sendMessage(this, getPlatform().toComponent(message, getVersion()));
FabricTAB.getVersion().sendMessage(getPlayer(), getPlatform().toComponent(message, getVersion()));
}
@Override
@@ -64,8 +65,7 @@ public class FabricTabPlayer extends BackendTabPlayer {
public TabList.Skin getSkin() {
Collection<Property> properties = getPlayer().getGameProfile().getProperties().get(TabList.TEXTURES_PROPERTY);
if (properties.isEmpty()) return null; // Offline mode
Property skinProperty = properties.iterator().next();
return FabricMultiVersion.propertyToSkin(skinProperty);
return FabricTAB.getVersion().propertyToSkin(properties.iterator().next());
}
@Override
@@ -0,0 +1,90 @@
package me.neznamy.tab.platforms.fabric;
import com.mojang.authlib.properties.Property;
import me.neznamy.tab.shared.backend.EntityData;
import me.neznamy.tab.shared.backend.Location;
import me.neznamy.tab.shared.features.injection.PipelineInjector;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.TabPlayer;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.Packet;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.decoration.ArmorStand;
import net.minecraft.world.level.Level;
import net.minecraft.world.scores.Objective;
import net.minecraft.world.scores.PlayerTeam;
import net.minecraft.world.scores.Scoreboard;
import net.minecraft.world.scores.criteria.ObjectiveCriteria.RenderType;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.UUID;
public interface VersionLoader {
Scoreboard dummyScoreboard = new Scoreboard();
ArmorStand dummyEntity = new ArmorStand(null, 0, 0, 0);
String getName(Level level);
int getPing(ServerPlayer player);
void sendMessage(ServerPlayer player, Component message);
TabList.Skin propertyToSkin(Property property);
void sendMessage(CommandSourceStack sender, Component message);
String getServerVersion();
double getMSPT();
Component deserialize(String string);
Packet<?> build(TabList.Action action, FabricTabList.Builder entry);
Level getLevel(ServerPlayer player);
boolean isSneaking(ServerPlayer player);
EntityData createDataWatcher(TabPlayer viewer, byte flags, String displayName, boolean nameVisible);
Packet<?> spawnEntity(int entityId, UUID id, Object entityType, Location location);
Packet<?> newEntityMetadata(int entityId, EntityData data);
boolean isSpawnPlayerPacket(Object packet);
int getSpawnedPlayer(Object spawnPacket);
int[] getDestroyedEntities(Object entityDestroyPacket);
boolean isBundlePacket(Object packet);
Iterable<Object> getPackets(Object bundlePacket);
PipelineInjector createPipelineInjector();
void destroyEntities(FabricTabPlayer player, int... entities);
void registerCommandCallback();
List<String> getSupportedVersions();
Packet<?> newHeaderFooter(Component header, Component footer);
Packet<?> setDisplaySlot(int slot, Objective objective);
Objective newObjective(String name, Component displayName, RenderType renderType, @Nullable Component numberFormat);
Packet<?> registerTeam(PlayerTeam team);
Packet<?> unregisterTeam(PlayerTeam team);
Packet<?> updateTeam(PlayerTeam team);
Packet<?> setScore(String objective, String scoreHolder, int score, Component displayName, @Nullable Component numberFormat);
Packet<?> removeScore(String objective, String scoreHolder);
}
@@ -1,10 +1,12 @@
package me.neznamy.tab.platforms.fabric.features;
import me.neznamy.tab.platforms.fabric.FabricMultiVersion;
import me.neznamy.tab.platforms.fabric.FabricTAB;
import me.neznamy.tab.platforms.fabric.FabricTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.backend.EntityData;
import me.neznamy.tab.shared.backend.features.unlimitedtags.BackendNameTagX;
import me.neznamy.tab.shared.platform.TabPlayer;
import net.fabricmc.fabric.api.entity.event.v1.ServerPlayerEvents;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.phys.Vec3;
@@ -19,7 +21,13 @@ public class FabricNameTagX extends BackendNameTagX {
public boolean enabled = true;
public FabricNameTagX() {
FabricMultiVersion.registerNameTagXEvents(this);
if (TAB.getInstance().getServerVersion().getMinorVersion() >= 16) {
// Added in 1.16
ServerPlayerEvents.AFTER_RESPAWN.register((oldPlayer, newPlayer, alive) -> {
if (enabled) respawn(oldPlayer.getUUID());
});
} // TODO else
// TODO sneaking
}
@Override
@@ -31,7 +39,8 @@ public class FabricNameTagX extends BackendNameTagX {
@Override
public boolean areInSameWorld(@NotNull TabPlayer player1, @NotNull TabPlayer player2) {
return ((FabricTabPlayer)player1).getPlayer().level == ((FabricTabPlayer)player2).getPlayer().level;
return FabricTAB.getVersion().getLevel(((FabricTabPlayer)player1).getPlayer()) ==
FabricTAB.getVersion().getLevel(((FabricTabPlayer)player2).getPlayer());
}
@Override
@@ -69,7 +78,7 @@ public class FabricNameTagX extends BackendNameTagX {
@Override
public boolean isSneaking(@NotNull TabPlayer player) {
return FabricMultiVersion.isSneaking(((FabricTabPlayer)player).getPlayer());
return FabricTAB.getVersion().isSneaking(((FabricTabPlayer)player).getPlayer());
}
@Override
@@ -111,7 +120,7 @@ public class FabricNameTagX extends BackendNameTagX {
@Override
@NotNull
public EntityData createDataWatcher(@NotNull TabPlayer viewer, byte flags, @NotNull String displayName, boolean nameVisible) {
return FabricMultiVersion.createDataWatcher(viewer, flags, displayName, nameVisible);
return FabricTAB.getVersion().createDataWatcher(viewer, flags, displayName, nameVisible);
}
@Override
+10 -4
View File
@@ -10,12 +10,18 @@
"server": ["me.neznamy.tab.platforms.fabric.FabricTAB"]
},
"depends": {
"fabricloader": ">=0.9.0",
"fabric-api-base": "*",
"minecraft": "1.20.2"
"minecraft": [
"1.14.4",
"1.15.2",
"1.16.5",
"1.17", "1.17.1",
"1.18", "1.18.1", "1.18.2",
"1.19", "1.19.1", "1.19.2", "1.19.3", "1.19.4",
"1.20", "1.20.1", "1.20.2", "1.20.3", "1.20.4"
]
},
"recommends": {
"fabric-permissions-api-v0": "*"
},
"accessWidener": "tab.accesswidener"
}
}
@@ -1,32 +0,0 @@
accessWidener v1 named
accessible field net/minecraft/network/protocol/game/ClientboundMoveEntityPacket entityId I
accessible field net/minecraft/server/network/ServerGamePacketListenerImpl connection Lnet/minecraft/network/Connection;
accessible field net/minecraft/network/Connection channel Lio/netty/channel/Channel;
accessible field net/minecraft/network/protocol/game/ClientboundSetPlayerTeamPacket method I
accessible field net/minecraft/network/protocol/game/ClientboundSetPlayerTeamPacket players Ljava/util/Collection;
mutable field net/minecraft/network/protocol/game/ClientboundSetPlayerTeamPacket players Ljava/util/Collection;
accessible field net/minecraft/server/network/ServerCommonPacketListenerImpl connection Lnet/minecraft/network/Connection;
# 1.20+
accessible field net/minecraft/world/entity/Entity level Lnet/minecraft/world/level/Level;
# new
accessible field net/minecraft/server/MinecraftServer LOGGER Lorg/slf4j/Logger;
mutable field net/minecraft/server/MinecraftServer LOGGER Lorg/slf4j/Logger;
# old (~1.16.5-?)
accessible field net/minecraft/server/MinecraftServer LOGGER Lorg/apache/logging/log4j/Logger;
mutable field net/minecraft/server/MinecraftServer LOGGER Lorg/apache/logging/log4j/Logger;
# 1.19.3+
accessible field net/minecraft/network/protocol/game/ClientboundPlayerInfoUpdatePacket entries Ljava/util/List;
mutable field net/minecraft/network/protocol/game/ClientboundPlayerInfoUpdatePacket entries Ljava/util/List;
# 1.19.2-
accessible field net/minecraft/network/protocol/game/ClientboundPlayerInfoPacket entries Ljava/util/List;
mutable field net/minecraft/network/protocol/game/ClientboundPlayerInfoPacket entries Ljava/util/List;
# 1.16.5-
accessible field net/minecraft/network/protocol/game/ClientboundTabListPacket header Lnet/minecraft/network/chat/Component;
accessible field net/minecraft/network/protocol/game/ClientboundTabListPacket footer Lnet/minecraft/network/chat/Component;
+22
View File
@@ -0,0 +1,22 @@
plugins {
id("fabric-loom")
}
repositories {
// Gradle doesn't support combining settings and project repositories, so we have to re-declare all the settings repos we need
maven("https://jitpack.io") // YamlAssist
maven("https://repo.opencollab.dev/maven-snapshots/")
maven("https://repo.viaversion.com/")
maven("https://oss.sonatype.org/content/repositories/snapshots")
}
dependencies {
implementation(projects.fabric)
minecraft("com.mojang:minecraft:1.15.2")
mappings(loom.officialMojangMappings())
modImplementation("net.fabricmc:fabric-loader:0.14.17")
val fabricApiVersion = "0.28.5+1.15"
modImplementation(fabricApi.module("fabric-api-base", fabricApiVersion))
modImplementation(fabricApi.module("fabric-lifecycle-events-v1", fabricApiVersion))
modImplementation(fabricApi.module("fabric-command-api-v1", fabricApiVersion))
}
@@ -0,0 +1,149 @@
package me.neznamy.tab.platforms.fabric.v1_15_2;
import com.mojang.authlib.GameProfile;
import io.netty.channel.Channel;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.fabric.FabricTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.features.injection.NettyPipelineInjector;
import me.neznamy.tab.shared.features.nametags.NameTag;
import me.neznamy.tab.shared.features.sorting.Sorting;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.minecraft.network.Connection;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket.Action;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket.PlayerUpdate;
import net.minecraft.network.protocol.game.ClientboundSetDisplayObjectivePacket;
import net.minecraft.network.protocol.game.ClientboundSetObjectivePacket;
import net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket;
import net.minecraft.server.network.ServerGamePacketListenerImpl;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public class FabricPipelineInjector extends NettyPipelineInjector {
public FabricPipelineInjector() {
super("packet_handler");
}
@Override
@Nullable
@SneakyThrows
protected Channel getChannel(@NotNull TabPlayer player) {
Connection c = (Connection) ReflectionUtils.getFields(ServerGamePacketListenerImpl.class, Connection.class)
.get(0).get(((FabricTabPlayer)player).getPlayer().connection);
return (Channel) ReflectionUtils.getFields(Connection.class, Channel.class).get(0).get(c);
}
@Override
@SneakyThrows
public void onDisplayObjective(@NotNull TabPlayer player, @NotNull Object packet) {
// Getters are client only
int slot = ReflectionUtils.getFields(ClientboundSetDisplayObjectivePacket.class, int.class).get(0).getInt(packet);
String objective = (String) ReflectionUtils.getFields(ClientboundSetDisplayObjectivePacket.class, String.class).get(0).get(packet);
TAB.getInstance().getFeatureManager().onDisplayObjective(player, slot, objective);
}
@Override
@SneakyThrows
public void onObjective(@NotNull TabPlayer player, @NotNull Object packet) {
// Getters are client only
int action = ReflectionUtils.getFields(ClientboundSetObjectivePacket.class, int.class).get(0).getInt(packet);
String objective = (String) ReflectionUtils.getFields(ClientboundSetObjectivePacket.class, String.class).get(0).get(packet);
TAB.getInstance().getFeatureManager().onObjective(player, action, objective);
}
@Override
public boolean isDisplayObjective(@NotNull Object packet) {
return packet instanceof ClientboundSetDisplayObjectivePacket;
}
@Override
public boolean isObjective(@NotNull Object packet) {
return packet instanceof ClientboundSetObjectivePacket;
}
@Override
public boolean isTeam(@NotNull Object packet) {
return packet instanceof ClientboundSetPlayerTeamPacket;
}
@Override
public boolean isPlayerInfo(@NotNull Object packet) {
return packet instanceof ClientboundPlayerInfoPacket;
}
@Override
@SneakyThrows
@SuppressWarnings("unchecked")
public void onPlayerInfo(@NotNull TabPlayer receiver, @NotNull Object packet) {
// Getters are client only
Action action = (Action) ReflectionUtils.getFields(ClientboundPlayerInfoPacket.class, Action.class).get(0).get(packet);
List<PlayerUpdate> players = (List<PlayerUpdate>) ReflectionUtils.getFields(ClientboundPlayerInfoPacket.class, List.class).get(0).get(packet);
for (PlayerUpdate nmsData : players) {
GameProfile profile = nmsData.getProfile();
Field displayNameField = ReflectionUtils.getFields(PlayerUpdate.class, Component.class).get(0);
Field latencyField = ReflectionUtils.getFields(PlayerUpdate.class, int.class).get(0);
if (action == Action.UPDATE_DISPLAY_NAME) {
IChatBaseComponent newDisplayName = TAB.getInstance().getFeatureManager().onDisplayNameChange(receiver, profile.getId());
if (newDisplayName != null) displayNameField.set(nmsData, ((FabricTabPlayer)receiver).getPlatform().toComponent(newDisplayName, receiver.getVersion()));
}
if (action == Action.UPDATE_LATENCY) {
latencyField.set(nmsData, TAB.getInstance().getFeatureManager().onLatencyChange(receiver, profile.getId(), latencyField.getInt(nmsData)));
}
if (action == Action.ADD_PLAYER) {
TAB.getInstance().getFeatureManager().onEntryAdd(receiver, profile.getId(), profile.getName());
}
}
}
@Override
public boolean isLogin(@NotNull Object packet) {
return false;
}
@Override
@SneakyThrows
@SuppressWarnings("unchecked")
public void modifyPlayers(@NotNull Object teamPacket) {
if (TAB.getInstance().getNameTagManager() == null) return;
ClientboundSetPlayerTeamPacket packet = (ClientboundSetPlayerTeamPacket) teamPacket;
int action = ReflectionUtils.getInstanceFields(ClientboundSetPlayerTeamPacket.class, int.class).get(0).getInt(packet);
if (action == 1 || action == 2 || action == 4) return;
Field playersField = ReflectionUtils.getFields(ClientboundSetPlayerTeamPacket.class, Collection.class).get(0);
Collection<String> players = (Collection<String>) playersField.get(packet);
String teamName = String.valueOf(ReflectionUtils.getFields(ClientboundSetPlayerTeamPacket.class, String.class).get(0).get(packet));
if (players == null) return;
//creating a new list to prevent NoSuchFieldException in minecraft packet encoder when a player is removed
Collection<String> newList = new ArrayList<>();
for (String entry : players) {
TabPlayer p = getPlayer(entry);
if (p == null) {
newList.add(entry);
continue;
}
Sorting sorting = TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.SORTING);
String expectedTeam = sorting.getShortTeamName(p);
if (expectedTeam == null) {
newList.add(entry);
continue;
}
if (!((NameTag)TAB.getInstance().getNameTagManager()).getDisableChecker().isDisabledPlayer(p) &&
!TAB.getInstance().getNameTagManager().hasTeamHandlingPaused(p) && !teamName.equals(expectedTeam)) {
logTeamOverride(teamName, p.getName(), expectedTeam);
} else {
newList.add(entry);
}
}
playersField.set(packet, newList);
}
}
@@ -0,0 +1,227 @@
package me.neznamy.tab.platforms.fabric.v1_15_2;
import com.mojang.authlib.properties.Property;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.fabric.*;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.backend.EntityData;
import me.neznamy.tab.shared.backend.Location;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.features.injection.PipelineInjector;
import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.fabricmc.fabric.api.command.v1.CommandRegistrationCallback;
import net.minecraft.SharedConstants;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.*;
import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.network.syncher.EntityDataSerializers;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.server.ServerScoreboard;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.level.GameType;
import net.minecraft.world.level.Level;
import net.minecraft.world.scores.Objective;
import net.minecraft.world.scores.PlayerTeam;
import net.minecraft.world.scores.criteria.ObjectiveCriteria;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Field;
import java.util.*;
public class FabricTAB implements VersionLoader {
@Override
public List<String> getSupportedVersions() {
return Arrays.asList("1.14.4", "1.15.2");
}
@Override
public String getName(Level level) {
return level.getLevelData().getLevelName() + level.dimension.getType().getFileSuffix();
}
@Override
public int getPing(ServerPlayer player) {
return player.latency;
}
@Override
public void sendMessage(ServerPlayer player, Component message) {
player.sendMessage(message);
}
@Override
public TabList.Skin propertyToSkin(Property property) {
return new TabList.Skin(property.getValue(), property.getSignature());
}
@Override
public void sendMessage(CommandSourceStack sender, Component message) {
sender.sendSuccess(message, false);
}
@Override
public String getServerVersion() {
return SharedConstants.getCurrentVersion().getName();
}
@Override
public double getMSPT() {
return ((FabricPlatform)TAB.getInstance().getPlatform()).getServer().getAverageTickTime();
}
@Override
public Component deserialize(String string) {
return Component.Serializer.fromJson(string);
}
@Override
@SneakyThrows
public Packet<?> build(TabList.Action action, FabricTabList.Builder entry) {
ClientboundPlayerInfoPacket packet = new ClientboundPlayerInfoPacket(ClientboundPlayerInfoPacket.Action.valueOf(action.name()), Collections.emptyList());
ReflectionUtils.getFields(ClientboundPlayerInfoPacket.class, List.class).get(0).set(packet,
Collections.singletonList(new ClientboundPlayerInfoPacket().new PlayerUpdate(
entry.createProfile(),
entry.getLatency(),
GameType.byId(entry.getGameMode()),
entry.getDisplayName()
)));
return packet;
}
@Override
public Level getLevel(ServerPlayer player) {
return player.level;
}
@Override
@SneakyThrows
public boolean isSneaking(ServerPlayer player) {
if (getServerVersion().equals("1.14.4")) {
return (boolean) player.getClass().getMethod("method_5715").invoke(player);
}
// 1.15.2
return player.isCrouching();
}
@Override
public EntityData createDataWatcher(TabPlayer viewer, byte flags, String displayName, boolean nameVisible) {
SynchedEntityData data = new SynchedEntityData(null);
data.define(new EntityDataAccessor<>(0, EntityDataSerializers.BYTE), flags);
data.define(new EntityDataAccessor<>(2, EntityDataSerializers.OPTIONAL_COMPONENT),
Optional.of(((FabricTabPlayer)viewer).getPlatform().toComponent(IChatBaseComponent.optimizedComponent(displayName), viewer.getVersion())));
data.define(new EntityDataAccessor<>(3, EntityDataSerializers.BOOLEAN), nameVisible);
int markerPosition = viewer.getPlatform().getServerVersion().getMinorVersion() >= 15 ? 14 : 13;
data.define(new EntityDataAccessor<>(markerPosition, EntityDataSerializers.BYTE), (byte)16);
return () -> data;
}
@Override
public Packet<?> spawnEntity(int entityId, UUID id, Object entityType, Location location) {
dummyEntity.setId(entityId);
dummyEntity.setUUID(id);
dummyEntity.setPos(location.getX(), location.getY(), location.getZ());
return new ClientboundAddMobPacket(dummyEntity);
}
@Override
public Packet<?> newEntityMetadata(int entityId, EntityData data) {
return new ClientboundSetEntityDataPacket(entityId, (SynchedEntityData) data.build(), true);
}
@Override
public boolean isSpawnPlayerPacket(Object packet) {
return packet instanceof ClientboundAddPlayerPacket;
}
@Override
@SneakyThrows
public int getSpawnedPlayer(Object spawnPacket) {
// Getter is client only
return ReflectionUtils.getFields(ClientboundAddPlayerPacket.class, int.class).get(0).getInt(spawnPacket);
}
@Override
@SneakyThrows
public int[] getDestroyedEntities(Object entityDestroyPacket) {
// Getter is client only
return (int[]) ReflectionUtils.getFields(ClientboundRemoveEntitiesPacket.class, int[].class).get(0).get(entityDestroyPacket);
}
@Override
public boolean isBundlePacket(Object packet) {
return false;
}
@Override
public Iterable<Object> getPackets(Object bundlePacket) {
return Collections.emptyList();
}
@Override
public PipelineInjector createPipelineInjector() {
return new FabricPipelineInjector();
}
@Override
public void destroyEntities(FabricTabPlayer player, int... entities) {
player.sendPacket(new ClientboundRemoveEntitiesPacket(entities));
}
@Override
public void registerCommandCallback() {
CommandRegistrationCallback.EVENT.register((dispatcher, $) -> new FabricTabCommand().onRegisterCommands(dispatcher));
}
@Override
@SneakyThrows
public Packet<?> newHeaderFooter(Component header, Component footer) {
ClientboundTabListPacket packet = new ClientboundTabListPacket();
List<Field> fields = ReflectionUtils.getFields(ClientboundTabListPacket.class, Component.class);
fields.get(0).set(packet, header);
fields.get(1).set(packet, footer);
return packet;
}
@Override
public Packet<?> setDisplaySlot(int slot, Objective objective) {
return new ClientboundSetDisplayObjectivePacket(slot, objective);
}
@Override
public Objective newObjective(String name, Component displayName, ObjectiveCriteria.RenderType renderType,
@Nullable Component numberFormat) {
return new Objective(dummyScoreboard, name, ObjectiveCriteria.DUMMY, displayName, renderType);
}
@Override
public Packet<?> registerTeam(PlayerTeam team) {
return new ClientboundSetPlayerTeamPacket(team, Scoreboard.TeamAction.CREATE);
}
@Override
public Packet<?> unregisterTeam(PlayerTeam team) {
return new ClientboundSetPlayerTeamPacket(team, Scoreboard.TeamAction.REMOVE);
}
@Override
public Packet<?> updateTeam(PlayerTeam team) {
return new ClientboundSetPlayerTeamPacket(team, Scoreboard.TeamAction.UPDATE);
}
@Override
public Packet<?> setScore(String objective, String scoreHolder, int score, Component displayName, @Nullable Component numberFormat) {
return new ClientboundSetScorePacket(ServerScoreboard.Method.CHANGE, objective, scoreHolder, score);
}
@Override
public Packet<?> removeScore(String objective, String scoreHolder) {
return new ClientboundSetScorePacket(ServerScoreboard.Method.REMOVE, objective, scoreHolder, 0);
}
}
+22
View File
@@ -0,0 +1,22 @@
plugins {
id("fabric-loom")
}
repositories {
// Gradle doesn't support combining settings and project repositories, so we have to re-declare all the settings repos we need
maven("https://jitpack.io") // YamlAssist
maven("https://repo.opencollab.dev/maven-snapshots/")
maven("https://repo.viaversion.com/")
maven("https://oss.sonatype.org/content/repositories/snapshots")
}
dependencies {
implementation(projects.fabric)
minecraft("com.mojang:minecraft:1.16.5")
mappings(loom.officialMojangMappings())
modImplementation("net.fabricmc:fabric-loader:0.14.17")
val fabricApiVersion = "0.42.0+1.16"
modImplementation(fabricApi.module("fabric-api-base", fabricApiVersion))
modImplementation(fabricApi.module("fabric-lifecycle-events-v1", fabricApiVersion))
modImplementation(fabricApi.module("fabric-command-api-v1", fabricApiVersion))
}
@@ -0,0 +1,149 @@
package me.neznamy.tab.platforms.fabric.v1_16_5;
import com.mojang.authlib.GameProfile;
import io.netty.channel.Channel;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.fabric.FabricTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.features.injection.NettyPipelineInjector;
import me.neznamy.tab.shared.features.nametags.NameTag;
import me.neznamy.tab.shared.features.sorting.Sorting;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.minecraft.network.Connection;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket.Action;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket.PlayerUpdate;
import net.minecraft.network.protocol.game.ClientboundSetDisplayObjectivePacket;
import net.minecraft.network.protocol.game.ClientboundSetObjectivePacket;
import net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket;
import net.minecraft.server.network.ServerGamePacketListenerImpl;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public class FabricPipelineInjector extends NettyPipelineInjector {
public FabricPipelineInjector() {
super("packet_handler");
}
@Override
@Nullable
@SneakyThrows
protected Channel getChannel(@NotNull TabPlayer player) {
Connection c = (Connection) ReflectionUtils.getFields(ServerGamePacketListenerImpl.class, Connection.class)
.get(0).get(((FabricTabPlayer)player).getPlayer().connection);
return (Channel) ReflectionUtils.getFields(Connection.class, Channel.class).get(0).get(c);
}
@Override
@SneakyThrows
public void onDisplayObjective(@NotNull TabPlayer player, @NotNull Object packet) {
// Getters are client only
int slot = ReflectionUtils.getFields(ClientboundSetDisplayObjectivePacket.class, int.class).get(0).getInt(packet);
String objective = (String) ReflectionUtils.getFields(ClientboundSetDisplayObjectivePacket.class, String.class).get(0).get(packet);
TAB.getInstance().getFeatureManager().onDisplayObjective(player, slot, objective);
}
@Override
@SneakyThrows
public void onObjective(@NotNull TabPlayer player, @NotNull Object packet) {
// Getters are client only
int action = ReflectionUtils.getFields(ClientboundSetObjectivePacket.class, int.class).get(0).getInt(packet);
String objective = (String) ReflectionUtils.getFields(ClientboundSetObjectivePacket.class, String.class).get(0).get(packet);
TAB.getInstance().getFeatureManager().onObjective(player, action, objective);
}
@Override
public boolean isDisplayObjective(@NotNull Object packet) {
return packet instanceof ClientboundSetDisplayObjectivePacket;
}
@Override
public boolean isObjective(@NotNull Object packet) {
return packet instanceof ClientboundSetObjectivePacket;
}
@Override
public boolean isTeam(@NotNull Object packet) {
return packet instanceof ClientboundSetPlayerTeamPacket;
}
@Override
public boolean isPlayerInfo(@NotNull Object packet) {
return packet instanceof ClientboundPlayerInfoPacket;
}
@Override
@SneakyThrows
@SuppressWarnings("unchecked")
public void onPlayerInfo(@NotNull TabPlayer receiver, @NotNull Object packet) {
// Getters are client only
Action action = (Action) ReflectionUtils.getFields(ClientboundPlayerInfoPacket.class, Action.class).get(0).get(packet);
List<PlayerUpdate> players = (List<PlayerUpdate>) ReflectionUtils.getFields(ClientboundPlayerInfoPacket.class, List.class).get(0).get(packet);
for (PlayerUpdate nmsData : players) {
GameProfile profile = nmsData.getProfile();
Field displayNameField = ReflectionUtils.getFields(PlayerUpdate.class, Component.class).get(0);
Field latencyField = ReflectionUtils.getFields(PlayerUpdate.class, int.class).get(0);
if (action == Action.UPDATE_DISPLAY_NAME) {
IChatBaseComponent newDisplayName = TAB.getInstance().getFeatureManager().onDisplayNameChange(receiver, profile.getId());
if (newDisplayName != null) displayNameField.set(nmsData, ((FabricTabPlayer)receiver).getPlatform().toComponent(newDisplayName, receiver.getVersion()));
}
if (action == Action.UPDATE_LATENCY) {
latencyField.set(nmsData, TAB.getInstance().getFeatureManager().onLatencyChange(receiver, profile.getId(), latencyField.getInt(nmsData)));
}
if (action == Action.ADD_PLAYER) {
TAB.getInstance().getFeatureManager().onEntryAdd(receiver, profile.getId(), profile.getName());
}
}
}
@Override
public boolean isLogin(@NotNull Object packet) {
return false;
}
@Override
@SneakyThrows
@SuppressWarnings("unchecked")
public void modifyPlayers(@NotNull Object teamPacket) {
if (TAB.getInstance().getNameTagManager() == null) return;
ClientboundSetPlayerTeamPacket packet = (ClientboundSetPlayerTeamPacket) teamPacket;
int action = ReflectionUtils.getInstanceFields(ClientboundSetPlayerTeamPacket.class, int.class).get(0).getInt(packet);
if (action == 1 || action == 2 || action == 4) return;
Field playersField = ReflectionUtils.getFields(ClientboundSetPlayerTeamPacket.class, Collection.class).get(0);
Collection<String> players = (Collection<String>) playersField.get(packet);
String teamName = String.valueOf(ReflectionUtils.getFields(ClientboundSetPlayerTeamPacket.class, String.class).get(0).get(packet));
if (players == null) return;
//creating a new list to prevent NoSuchFieldException in minecraft packet encoder when a player is removed
Collection<String> newList = new ArrayList<>();
for (String entry : players) {
TabPlayer p = getPlayer(entry);
if (p == null) {
newList.add(entry);
continue;
}
Sorting sorting = TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.SORTING);
String expectedTeam = sorting.getShortTeamName(p);
if (expectedTeam == null) {
newList.add(entry);
continue;
}
if (!((NameTag)TAB.getInstance().getNameTagManager()).getDisableChecker().isDisabledPlayer(p) &&
!TAB.getInstance().getNameTagManager().hasTeamHandlingPaused(p) && !teamName.equals(expectedTeam)) {
logTeamOverride(teamName, p.getName(), expectedTeam);
} else {
newList.add(entry);
}
}
playersField.set(packet, newList);
}
}
@@ -0,0 +1,234 @@
package me.neznamy.tab.platforms.fabric.v1_16_5;
import com.mojang.authlib.properties.Property;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.fabric.*;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.backend.EntityData;
import me.neznamy.tab.shared.backend.Location;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.features.injection.PipelineInjector;
import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.fabricmc.fabric.api.command.v1.CommandRegistrationCallback;
import net.minecraft.SharedConstants;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.*;
import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.network.syncher.EntityDataSerializers;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.server.ServerScoreboard;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.level.GameType;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.storage.ServerLevelData;
import net.minecraft.world.scores.Objective;
import net.minecraft.world.scores.PlayerTeam;
import net.minecraft.world.scores.criteria.ObjectiveCriteria;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Field;
import java.util.*;
public class FabricTAB implements VersionLoader {
@Override
public List<String> getSupportedVersions() {
return Collections.singletonList("1.16.5");
}
@Override
public String getName(Level level) {
String path = level.dimension().location().getPath();
String dimensionSuffix;
switch (path) {
case "overworld":
dimensionSuffix = ""; // No suffix for overworld
break;
case "the_nether":
dimensionSuffix = "_nether";
break;
default:
dimensionSuffix = "_" + path; // End + default behavior for other dimensions created by mods
break;
}
return ((ServerLevelData)level.getLevelData()).getLevelName() + dimensionSuffix;
}
@Override
public int getPing(ServerPlayer player) {
return player.latency;
}
@Override
public void sendMessage(ServerPlayer player, Component message) {
player.sendMessage(message, new UUID(0, 0));
}
@Override
public TabList.Skin propertyToSkin(Property property) {
return new TabList.Skin(property.getValue(), property.getSignature());
}
@Override
public void sendMessage(CommandSourceStack sender, Component message) {
sender.sendSuccess(message, false);
}
@Override
public String getServerVersion() {
return SharedConstants.getCurrentVersion().getName();
}
@Override
public double getMSPT() {
return ((FabricPlatform)TAB.getInstance().getPlatform()).getServer().getAverageTickTime();
}
@Override
public Component deserialize(String string) {
return Component.Serializer.fromJson(string);
}
@Override
@SneakyThrows
public Packet<?> build(TabList.Action action, FabricTabList.Builder entry) {
ClientboundPlayerInfoPacket packet = new ClientboundPlayerInfoPacket(ClientboundPlayerInfoPacket.Action.valueOf(action.name()), Collections.emptyList());
ReflectionUtils.getFields(ClientboundPlayerInfoPacket.class, List.class).get(0).set(packet,
Collections.singletonList(new ClientboundPlayerInfoPacket().new PlayerUpdate(
entry.createProfile(),
entry.getLatency(),
GameType.byId(entry.getGameMode()),
entry.getDisplayName()
)));
return packet;
}
@Override
public Level getLevel(ServerPlayer player) {
return player.level;
}
@Override
public boolean isSneaking(ServerPlayer player) {
return player.isCrouching();
}
@Override
public EntityData createDataWatcher(TabPlayer viewer, byte flags, String displayName, boolean nameVisible) {
SynchedEntityData data = new SynchedEntityData(null);
data.define(new EntityDataAccessor<>(0, EntityDataSerializers.BYTE), flags);
data.define(new EntityDataAccessor<>(2, EntityDataSerializers.OPTIONAL_COMPONENT),
Optional.of(((FabricTabPlayer)viewer).getPlatform().toComponent(IChatBaseComponent.optimizedComponent(displayName), viewer.getVersion())));
data.define(new EntityDataAccessor<>(3, EntityDataSerializers.BOOLEAN), nameVisible);
data.define(new EntityDataAccessor<>(14, EntityDataSerializers.BYTE), (byte)16);
return () -> data;
}
@Override
public Packet<?> spawnEntity(int entityId, UUID id, Object entityType, Location location) {
dummyEntity.setId(entityId);
dummyEntity.setUUID(id);
dummyEntity.setPos(location.getX(), location.getY(), location.getZ());
return new ClientboundAddMobPacket(dummyEntity);
}
@Override
public Packet<?> newEntityMetadata(int entityId, EntityData data) {
return new ClientboundSetEntityDataPacket(entityId, (SynchedEntityData) data.build(), true);
}
@Override
public boolean isSpawnPlayerPacket(Object packet) {
return packet instanceof ClientboundAddPlayerPacket;
}
@Override
@SneakyThrows
public int getSpawnedPlayer(Object spawnPacket) {
// Getter is client only
return ReflectionUtils.getFields(ClientboundAddPlayerPacket.class, int.class).get(0).getInt(spawnPacket);
}
@Override
@SneakyThrows
public int[] getDestroyedEntities(Object entityDestroyPacket) {
// Getter is client only
return (int[]) ReflectionUtils.getFields(ClientboundRemoveEntitiesPacket.class, int[].class).get(0).get(entityDestroyPacket);
}
@Override
public boolean isBundlePacket(Object packet) {
return false;
}
@Override
public Iterable<Object> getPackets(Object bundlePacket) {
return Collections.emptyList();
}
@Override
public PipelineInjector createPipelineInjector() {
return new FabricPipelineInjector();
}
@Override
public void destroyEntities(FabricTabPlayer player, int... entities) {
player.sendPacket(new ClientboundRemoveEntitiesPacket(entities));
}
@Override
public void registerCommandCallback() {
CommandRegistrationCallback.EVENT.register((dispatcher, $) -> new FabricTabCommand().onRegisterCommands(dispatcher));
}
@Override
@SneakyThrows
public Packet<?> newHeaderFooter(Component header, Component footer) {
ClientboundTabListPacket packet = new ClientboundTabListPacket();
List<Field> fields = ReflectionUtils.getFields(ClientboundTabListPacket.class, Component.class);
fields.get(0).set(packet, header);
fields.get(1).set(packet, footer);
return packet;
}
@Override
public Packet<?> setDisplaySlot(int slot, Objective objective) {
return new ClientboundSetDisplayObjectivePacket(slot, objective);
}
@Override
public Objective newObjective(String name, Component displayName, ObjectiveCriteria.RenderType renderType,
@Nullable Component numberFormat) {
return new Objective(dummyScoreboard, name, ObjectiveCriteria.DUMMY, displayName, renderType);
}
@Override
public Packet<?> registerTeam(PlayerTeam team) {
return new ClientboundSetPlayerTeamPacket(team, Scoreboard.TeamAction.CREATE);
}
@Override
public Packet<?> unregisterTeam(PlayerTeam team) {
return new ClientboundSetPlayerTeamPacket(team, Scoreboard.TeamAction.REMOVE);
}
@Override
public Packet<?> updateTeam(PlayerTeam team) {
return new ClientboundSetPlayerTeamPacket(team, Scoreboard.TeamAction.UPDATE);
}
@Override
public Packet<?> setScore(String objective, String scoreHolder, int score, Component displayName, @Nullable Component numberFormat) {
return new ClientboundSetScorePacket(ServerScoreboard.Method.CHANGE, objective, scoreHolder, score);
}
@Override
public Packet<?> removeScore(String objective, String scoreHolder) {
return new ClientboundSetScorePacket(ServerScoreboard.Method.REMOVE, objective, scoreHolder, 0);
}
}
+28
View File
@@ -0,0 +1,28 @@
plugins {
id("fabric-loom")
}
repositories {
// Gradle doesn't support combining settings and project repositories, so we have to re-declare all the settings repos we need
maven("https://jitpack.io") // YamlAssist
maven("https://repo.opencollab.dev/maven-snapshots/")
maven("https://repo.viaversion.com/")
maven("https://oss.sonatype.org/content/repositories/snapshots")
}
dependencies {
implementation(projects.fabric)
minecraft("com.mojang:minecraft:1.17.1")
mappings(loom.officialMojangMappings())
modImplementation("net.fabricmc:fabric-loader:0.14.17")
val fabricApiVersion = "0.46.1+1.17"
modImplementation(fabricApi.module("fabric-api-base", fabricApiVersion))
modImplementation(fabricApi.module("fabric-lifecycle-events-v1", fabricApiVersion))
modImplementation(fabricApi.module("fabric-command-api-v1", fabricApiVersion))
}
tasks {
compileJava {
options.release.set(17)
}
}
@@ -0,0 +1,137 @@
package me.neznamy.tab.platforms.fabric.v1_17_1;
import com.mojang.authlib.GameProfile;
import io.netty.channel.Channel;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.fabric.FabricTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.features.injection.NettyPipelineInjector;
import me.neznamy.tab.shared.features.nametags.NameTag;
import me.neznamy.tab.shared.features.sorting.Sorting;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.minecraft.network.Connection;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket;
import net.minecraft.network.protocol.game.ClientboundSetDisplayObjectivePacket;
import net.minecraft.network.protocol.game.ClientboundSetObjectivePacket;
import net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket;
import net.minecraft.server.network.ServerGamePacketListenerImpl;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
public class FabricPipelineInjector extends NettyPipelineInjector {
public FabricPipelineInjector() {
super("packet_handler");
}
@Override
@Nullable
@SneakyThrows
protected Channel getChannel(@NotNull TabPlayer player) {
Connection c = (Connection) ReflectionUtils.getFields(ServerGamePacketListenerImpl.class, Connection.class)
.get(0).get(((FabricTabPlayer)player).getPlayer().connection);
return (Channel) ReflectionUtils.getFields(Connection.class, Channel.class).get(0).get(c);
}
@Override
public void onDisplayObjective(@NotNull TabPlayer player, @NotNull Object packet) {
TAB.getInstance().getFeatureManager().onDisplayObjective(player,
((ClientboundSetDisplayObjectivePacket) packet).getSlot(),
String.valueOf(((ClientboundSetDisplayObjectivePacket)packet).getObjectiveName()));
}
@Override
public void onObjective(@NotNull TabPlayer player, @NotNull Object packet) {
TAB.getInstance().getFeatureManager().onObjective(player,
((ClientboundSetObjectivePacket)packet).getMethod(),
((ClientboundSetObjectivePacket)packet).getObjectiveName());
}
@Override
public boolean isDisplayObjective(@NotNull Object packet) {
return packet instanceof ClientboundSetDisplayObjectivePacket;
}
@Override
public boolean isObjective(@NotNull Object packet) {
return packet instanceof ClientboundSetObjectivePacket;
}
@Override
public boolean isTeam(@NotNull Object packet) {
return packet instanceof ClientboundSetPlayerTeamPacket;
}
@Override
public boolean isPlayerInfo(@NotNull Object packet) {
return packet instanceof ClientboundPlayerInfoPacket;
}
@Override
@SneakyThrows
public void onPlayerInfo(@NotNull TabPlayer receiver, @NotNull Object packet0) {
ClientboundPlayerInfoPacket packet = (ClientboundPlayerInfoPacket) packet0;
for (ClientboundPlayerInfoPacket.PlayerUpdate nmsData : packet.getEntries()) {
GameProfile profile = nmsData.getProfile();
Field displayNameField = ReflectionUtils.getFields(ClientboundPlayerInfoPacket.PlayerUpdate.class, Component.class).get(0);
Field latencyField = ReflectionUtils.getFields(ClientboundPlayerInfoPacket.PlayerUpdate.class, int.class).get(0);
if (packet.getAction() == ClientboundPlayerInfoPacket.Action.UPDATE_DISPLAY_NAME) {
IChatBaseComponent newDisplayName = TAB.getInstance().getFeatureManager().onDisplayNameChange(receiver, profile.getId());
if (newDisplayName != null) displayNameField.set(nmsData, ((FabricTabPlayer)receiver).getPlatform().toComponent(newDisplayName, receiver.getVersion()));
}
if (packet.getAction() == ClientboundPlayerInfoPacket.Action.UPDATE_LATENCY) {
latencyField.set(nmsData, TAB.getInstance().getFeatureManager().onLatencyChange(receiver, profile.getId(), latencyField.getInt(nmsData)));
}
if (packet.getAction() == ClientboundPlayerInfoPacket.Action.ADD_PLAYER) {
TAB.getInstance().getFeatureManager().onEntryAdd(receiver, profile.getId(), profile.getName());
}
}
}
@Override
public boolean isLogin(@NotNull Object packet) {
return false;
}
@Override
@SneakyThrows
public void modifyPlayers(@NotNull Object teamPacket) {
if (TAB.getInstance().getNameTagManager() == null) return;
ClientboundSetPlayerTeamPacket packet = (ClientboundSetPlayerTeamPacket) teamPacket;
int action = ReflectionUtils.getInstanceFields(ClientboundSetPlayerTeamPacket.class, int.class).get(0).getInt(packet);
if (action == 1 || action == 2 || action == 4) return;
Collection<String> players = packet.getPlayers();
String teamName = packet.getName();
if (players == null) return;
//creating a new list to prevent NoSuchFieldException in minecraft packet encoder when a player is removed
Collection<String> newList = new ArrayList<>();
for (String entry : players) {
TabPlayer p = getPlayer(entry);
if (p == null) {
newList.add(entry);
continue;
}
Sorting sorting = TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.SORTING);
String expectedTeam = sorting.getShortTeamName(p);
if (expectedTeam == null) {
newList.add(entry);
continue;
}
if (!((NameTag)TAB.getInstance().getNameTagManager()).getDisableChecker().isDisabledPlayer(p) &&
!TAB.getInstance().getNameTagManager().hasTeamHandlingPaused(p) && !teamName.equals(expectedTeam)) {
logTeamOverride(teamName, p.getName(), expectedTeam);
} else {
newList.add(entry);
}
}
ReflectionUtils.getFields(ClientboundSetPlayerTeamPacket.class, Collection.class).get(0).set(packet, newList);
}
}
@@ -0,0 +1,235 @@
package me.neznamy.tab.platforms.fabric.v1_17_1;
import com.mojang.authlib.properties.Property;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.fabric.*;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.backend.EntityData;
import me.neznamy.tab.shared.backend.Location;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.features.injection.PipelineInjector;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.fabricmc.fabric.api.command.v1.CommandRegistrationCallback;
import net.minecraft.SharedConstants;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.*;
import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.network.syncher.EntityDataSerializers;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.server.ServerScoreboard;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.level.GameType;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.storage.ServerLevelData;
import net.minecraft.world.phys.Vec3;
import net.minecraft.world.scores.Objective;
import net.minecraft.world.scores.PlayerTeam;
import net.minecraft.world.scores.criteria.ObjectiveCriteria;
import org.jetbrains.annotations.Nullable;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
public class FabricTAB implements VersionLoader {
@Override
public List<String> getSupportedVersions() {
return List.of("1.17", "1.17.1");
}
@Override
public String getName(Level level) {
String path = level.dimension().location().getPath();
String dimensionSuffix = switch (path) {
case "overworld" -> ""; // No suffix for overworld
case "the_nether" -> "_nether";
default -> "_" + path; // End + default behavior for other dimensions created by mods
};
return ((ServerLevelData)level.getLevelData()).getLevelName() + dimensionSuffix;
}
@Override
public int getPing(ServerPlayer player) {
return player.latency;
}
@Override
public void sendMessage(ServerPlayer player, Component message) {
player.sendMessage(message, new UUID(0, 0));
}
@Override
public TabList.Skin propertyToSkin(Property property) {
return new TabList.Skin(property.getValue(), property.getSignature());
}
@Override
public void sendMessage(CommandSourceStack sender, Component message) {
sender.sendSuccess(message, false);
}
@Override
public String getServerVersion() {
return SharedConstants.getCurrentVersion().getName();
}
@Override
public double getMSPT() {
return ((FabricPlatform)TAB.getInstance().getPlatform()).getServer().getAverageTickTime();
}
@Override
public Component deserialize(String string) {
return Component.Serializer.fromJson(string);
}
@Override
@SneakyThrows
public Packet<?> build(TabList.Action action, FabricTabList.Builder entry) {
ClientboundPlayerInfoPacket packet = new ClientboundPlayerInfoPacket(ClientboundPlayerInfoPacket.Action.valueOf(action.name()), Collections.emptyList());
ReflectionUtils.getFields(ClientboundPlayerInfoPacket.class, List.class).get(0).set(packet,
Collections.singletonList(new ClientboundPlayerInfoPacket.PlayerUpdate(
entry.createProfile(),
entry.getLatency(),
GameType.byId(entry.getGameMode()),
entry.getDisplayName()
)));
return packet;
}
@Override
public Level getLevel(ServerPlayer player) {
return player.level;
}
@Override
public boolean isSneaking(ServerPlayer player) {
return player.isCrouching();
}
@Override
public EntityData createDataWatcher(TabPlayer viewer, byte flags, String displayName, boolean nameVisible) {
SynchedEntityData data = new SynchedEntityData(null);
data.define(new EntityDataAccessor<>(0, EntityDataSerializers.BYTE), flags);
data.define(new EntityDataAccessor<>(2, EntityDataSerializers.OPTIONAL_COMPONENT),
Optional.of(((FabricTabPlayer)viewer).getPlatform().toComponent(IChatBaseComponent.optimizedComponent(displayName), viewer.getVersion())));
data.define(new EntityDataAccessor<>(3, EntityDataSerializers.BOOLEAN), nameVisible);
data.define(new EntityDataAccessor<>(15, EntityDataSerializers.BYTE), (byte)16);
return () -> data;
}
@Override
public Packet<?> spawnEntity(int entityId, UUID id, Object entityType, Location location) {
return new ClientboundAddEntityPacket(entityId, id, location.getX(), location.getY(), location.getZ(),
0, 0, (EntityType<?>) entityType, 0, Vec3.ZERO);
}
@Override
public Packet<?> newEntityMetadata(int entityId, EntityData data) {
return new ClientboundSetEntityDataPacket(entityId, (SynchedEntityData) data.build(), true);
}
@Override
public boolean isSpawnPlayerPacket(Object packet) {
return packet instanceof ClientboundAddPlayerPacket;
}
@Override
public int getSpawnedPlayer(Object spawnPacket) {
return ((ClientboundAddPlayerPacket) spawnPacket).getEntityId();
}
@Override
@SneakyThrows
public int[] getDestroyedEntities(Object packet) {
if (getServerVersion().equals("1.17")) {
return new int[]{(int) ReflectionUtils.getOnlyMethod(packet.getClass(), int.class).invoke(packet)};
}
// 1.17.1
return ((ClientboundRemoveEntitiesPacket) packet).getEntityIds().toIntArray();
}
@Override
public boolean isBundlePacket(Object packet) {
return false;
}
@Override
public Iterable<Object> getPackets(Object bundlePacket) {
return Collections.emptyList();
}
@Override
public PipelineInjector createPipelineInjector() {
return new FabricPipelineInjector();
}
@Override
@SneakyThrows
public void destroyEntities(FabricTabPlayer player, int... entities) {
if (getServerVersion().equals("1.17")) {
for (int entity : entities) {
// While the actual packet name is different, fabric-mapped name is the same
player.sendPacket(ClientboundRemoveEntitiesPacket.class.getConstructor(int.class).newInstance(entity));
}
return;
}
// 1.17.1
player.sendPacket(new ClientboundRemoveEntitiesPacket(entities));
}
@Override
public void registerCommandCallback() {
CommandRegistrationCallback.EVENT.register((dispatcher, $) -> new FabricTabCommand().onRegisterCommands(dispatcher));
}
@Override
public Packet<?> newHeaderFooter(Component header, Component footer) {
return new ClientboundTabListPacket(header, footer);
}
@Override
public Packet<?> setDisplaySlot(int slot, Objective objective) {
return new ClientboundSetDisplayObjectivePacket(slot, objective);
}
@Override
public Objective newObjective(String name, Component displayName, ObjectiveCriteria.RenderType renderType,
@Nullable Component numberFormat) {
return new Objective(dummyScoreboard, name, ObjectiveCriteria.DUMMY, displayName, renderType);
}
@Override
public Packet<?> registerTeam(PlayerTeam team) {
return ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, true);
}
@Override
public Packet<?> unregisterTeam(PlayerTeam team) {
return ClientboundSetPlayerTeamPacket.createRemovePacket(team);
}
@Override
public Packet<?> updateTeam(PlayerTeam team) {
return ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, false);
}
@Override
public Packet<?> setScore(String objective, String scoreHolder, int score, Component displayName, @Nullable Component numberFormat) {
return new ClientboundSetScorePacket(ServerScoreboard.Method.CHANGE, objective, scoreHolder, score);
}
@Override
public Packet<?> removeScore(String objective, String scoreHolder) {
return new ClientboundSetScorePacket(ServerScoreboard.Method.REMOVE, objective, scoreHolder, 0);
}
}
+28
View File
@@ -0,0 +1,28 @@
plugins {
id("fabric-loom")
}
repositories {
// Gradle doesn't support combining settings and project repositories, so we have to re-declare all the settings repos we need
maven("https://jitpack.io") // YamlAssist
maven("https://repo.opencollab.dev/maven-snapshots/")
maven("https://repo.viaversion.com/")
maven("https://oss.sonatype.org/content/repositories/snapshots")
}
dependencies {
implementation(projects.fabric)
minecraft("com.mojang:minecraft:1.18.2")
mappings(loom.officialMojangMappings())
modImplementation("net.fabricmc:fabric-loader:0.14.17")
val fabricApiVersion = "0.76.0+1.18.2"
modImplementation(fabricApi.module("fabric-api-base", fabricApiVersion))
modImplementation(fabricApi.module("fabric-lifecycle-events-v1", fabricApiVersion))
modImplementation(fabricApi.module("fabric-command-api-v1", fabricApiVersion))
}
tasks {
compileJava {
options.release.set(17)
}
}
@@ -0,0 +1,137 @@
package me.neznamy.tab.platforms.fabric.v1_18_2;
import com.mojang.authlib.GameProfile;
import io.netty.channel.Channel;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.fabric.FabricTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.features.injection.NettyPipelineInjector;
import me.neznamy.tab.shared.features.nametags.NameTag;
import me.neznamy.tab.shared.features.sorting.Sorting;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.minecraft.network.Connection;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket;
import net.minecraft.network.protocol.game.ClientboundSetDisplayObjectivePacket;
import net.minecraft.network.protocol.game.ClientboundSetObjectivePacket;
import net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket;
import net.minecraft.server.network.ServerGamePacketListenerImpl;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
public class FabricPipelineInjector extends NettyPipelineInjector {
public FabricPipelineInjector() {
super("packet_handler");
}
@Override
@Nullable
@SneakyThrows
protected Channel getChannel(@NotNull TabPlayer player) {
Connection c = (Connection) ReflectionUtils.getFields(ServerGamePacketListenerImpl.class, Connection.class)
.get(0).get(((FabricTabPlayer)player).getPlayer().connection);
return (Channel) ReflectionUtils.getFields(Connection.class, Channel.class).get(0).get(c);
}
@Override
public void onDisplayObjective(@NotNull TabPlayer player, @NotNull Object packet) {
TAB.getInstance().getFeatureManager().onDisplayObjective(player,
((ClientboundSetDisplayObjectivePacket) packet).getSlot(),
String.valueOf(((ClientboundSetDisplayObjectivePacket)packet).getObjectiveName()));
}
@Override
public void onObjective(@NotNull TabPlayer player, @NotNull Object packet) {
TAB.getInstance().getFeatureManager().onObjective(player,
((ClientboundSetObjectivePacket)packet).getMethod(),
((ClientboundSetObjectivePacket)packet).getObjectiveName());
}
@Override
public boolean isDisplayObjective(@NotNull Object packet) {
return packet instanceof ClientboundSetDisplayObjectivePacket;
}
@Override
public boolean isObjective(@NotNull Object packet) {
return packet instanceof ClientboundSetObjectivePacket;
}
@Override
public boolean isTeam(@NotNull Object packet) {
return packet instanceof ClientboundSetPlayerTeamPacket;
}
@Override
public boolean isPlayerInfo(@NotNull Object packet) {
return packet instanceof ClientboundPlayerInfoPacket;
}
@Override
@SneakyThrows
public void onPlayerInfo(@NotNull TabPlayer receiver, @NotNull Object packet0) {
ClientboundPlayerInfoPacket packet = (ClientboundPlayerInfoPacket) packet0;
for (ClientboundPlayerInfoPacket.PlayerUpdate nmsData : packet.getEntries()) {
GameProfile profile = nmsData.getProfile();
Field displayNameField = ReflectionUtils.getFields(ClientboundPlayerInfoPacket.PlayerUpdate.class, Component.class).get(0);
Field latencyField = ReflectionUtils.getFields(ClientboundPlayerInfoPacket.PlayerUpdate.class, int.class).get(0);
if (packet.getAction() == ClientboundPlayerInfoPacket.Action.UPDATE_DISPLAY_NAME) {
IChatBaseComponent newDisplayName = TAB.getInstance().getFeatureManager().onDisplayNameChange(receiver, profile.getId());
if (newDisplayName != null) displayNameField.set(nmsData, ((FabricTabPlayer)receiver).getPlatform().toComponent(newDisplayName, receiver.getVersion()));
}
if (packet.getAction() == ClientboundPlayerInfoPacket.Action.UPDATE_LATENCY) {
latencyField.set(nmsData, TAB.getInstance().getFeatureManager().onLatencyChange(receiver, profile.getId(), latencyField.getInt(nmsData)));
}
if (packet.getAction() == ClientboundPlayerInfoPacket.Action.ADD_PLAYER) {
TAB.getInstance().getFeatureManager().onEntryAdd(receiver, profile.getId(), profile.getName());
}
}
}
@Override
public boolean isLogin(@NotNull Object packet) {
return false;
}
@Override
@SneakyThrows
public void modifyPlayers(@NotNull Object teamPacket) {
if (TAB.getInstance().getNameTagManager() == null) return;
ClientboundSetPlayerTeamPacket packet = (ClientboundSetPlayerTeamPacket) teamPacket;
int action = ReflectionUtils.getInstanceFields(ClientboundSetPlayerTeamPacket.class, int.class).get(0).getInt(packet);
if (action == 1 || action == 2 || action == 4) return;
Collection<String> players = packet.getPlayers();
String teamName = packet.getName();
if (players == null) return;
//creating a new list to prevent NoSuchFieldException in minecraft packet encoder when a player is removed
Collection<String> newList = new ArrayList<>();
for (String entry : players) {
TabPlayer p = getPlayer(entry);
if (p == null) {
newList.add(entry);
continue;
}
Sorting sorting = TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.SORTING);
String expectedTeam = sorting.getShortTeamName(p);
if (expectedTeam == null) {
newList.add(entry);
continue;
}
if (!((NameTag)TAB.getInstance().getNameTagManager()).getDisableChecker().isDisabledPlayer(p) &&
!TAB.getInstance().getNameTagManager().hasTeamHandlingPaused(p) && !teamName.equals(expectedTeam)) {
logTeamOverride(teamName, p.getName(), expectedTeam);
} else {
newList.add(entry);
}
}
ReflectionUtils.getFields(ClientboundSetPlayerTeamPacket.class, Collection.class).get(0).set(packet, newList);
}
}
@@ -0,0 +1,219 @@
package me.neznamy.tab.platforms.fabric.v1_18_2;
import com.mojang.authlib.properties.Property;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.fabric.*;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.backend.EntityData;
import me.neznamy.tab.shared.backend.Location;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.features.injection.PipelineInjector;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.fabricmc.fabric.api.command.v1.CommandRegistrationCallback;
import net.minecraft.SharedConstants;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.*;
import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.network.syncher.EntityDataSerializers;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.server.ServerScoreboard;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.level.GameType;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.storage.ServerLevelData;
import net.minecraft.world.phys.Vec3;
import net.minecraft.world.scores.Objective;
import net.minecraft.world.scores.PlayerTeam;
import net.minecraft.world.scores.criteria.ObjectiveCriteria;
import org.jetbrains.annotations.Nullable;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
public class FabricTAB implements VersionLoader {
@Override
public List<String> getSupportedVersions() {
return List.of("1.18", "1.18.1", "1.18.2");
}
@Override
public String getName(Level level) {
String path = level.dimension().location().getPath();
String dimensionSuffix = switch (path) {
case "overworld" -> ""; // No suffix for overworld
case "the_nether" -> "_nether";
default -> "_" + path; // End + default behavior for other dimensions created by mods
};
return ((ServerLevelData)level.getLevelData()).getLevelName() + dimensionSuffix;
}
@Override
public int getPing(ServerPlayer player) {
return player.latency;
}
@Override
public void sendMessage(ServerPlayer player, Component message) {
player.sendMessage(message, new UUID(0, 0));
}
@Override
public TabList.Skin propertyToSkin(Property property) {
return new TabList.Skin(property.getValue(), property.getSignature());
}
@Override
public void sendMessage(CommandSourceStack sender, Component message) {
sender.sendSuccess(message, false);
}
@Override
public String getServerVersion() {
return SharedConstants.getCurrentVersion().getName();
}
@Override
public double getMSPT() {
return ((FabricPlatform)TAB.getInstance().getPlatform()).getServer().getAverageTickTime();
}
@Override
public Component deserialize(String string) {
return Component.Serializer.fromJson(string);
}
@Override
@SneakyThrows
public Packet<?> build(TabList.Action action, FabricTabList.Builder entry) {
ClientboundPlayerInfoPacket packet = new ClientboundPlayerInfoPacket(ClientboundPlayerInfoPacket.Action.valueOf(action.name()), Collections.emptyList());
ReflectionUtils.getFields(ClientboundPlayerInfoPacket.class, List.class).get(0).set(packet,
Collections.singletonList(new ClientboundPlayerInfoPacket.PlayerUpdate(
entry.createProfile(),
entry.getLatency(),
GameType.byId(entry.getGameMode()),
entry.getDisplayName()
)));
return packet;
}
@Override
public Level getLevel(ServerPlayer player) {
return player.level;
}
@Override
public boolean isSneaking(ServerPlayer player) {
return player.isCrouching();
}
@Override
public EntityData createDataWatcher(TabPlayer viewer, byte flags, String displayName, boolean nameVisible) {
SynchedEntityData data = new SynchedEntityData(null);
data.define(new EntityDataAccessor<>(0, EntityDataSerializers.BYTE), flags);
data.define(new EntityDataAccessor<>(2, EntityDataSerializers.OPTIONAL_COMPONENT),
Optional.of(((FabricTabPlayer)viewer).getPlatform().toComponent(IChatBaseComponent.optimizedComponent(displayName), viewer.getVersion())));
data.define(new EntityDataAccessor<>(3, EntityDataSerializers.BOOLEAN), nameVisible);
data.define(new EntityDataAccessor<>(15, EntityDataSerializers.BYTE), (byte)16);
return () -> data;
}
@Override
public Packet<?> spawnEntity(int entityId, UUID id, Object entityType, Location location) {
return new ClientboundAddEntityPacket(entityId, id, location.getX(), location.getY(), location.getZ(),
0, 0, (EntityType<?>) entityType, 0, Vec3.ZERO);
}
@Override
public Packet<?> newEntityMetadata(int entityId, EntityData data) {
return new ClientboundSetEntityDataPacket(entityId, (SynchedEntityData) data.build(), true);
}
@Override
public boolean isSpawnPlayerPacket(Object packet) {
return packet instanceof ClientboundAddPlayerPacket;
}
@Override
public int getSpawnedPlayer(Object spawnPacket) {
return ((ClientboundAddPlayerPacket) spawnPacket).getEntityId();
}
@Override
public int[] getDestroyedEntities(Object entityDestroyPacket) {
return ((ClientboundRemoveEntitiesPacket) entityDestroyPacket).getEntityIds().toIntArray();
}
@Override
public boolean isBundlePacket(Object packet) {
return false;
}
@Override
public Iterable<Object> getPackets(Object bundlePacket) {
return Collections.emptyList();
}
@Override
public PipelineInjector createPipelineInjector() {
return new FabricPipelineInjector();
}
@Override
public void destroyEntities(FabricTabPlayer player, int... entities) {
player.sendPacket(new ClientboundRemoveEntitiesPacket(entities));
}
@Override
public void registerCommandCallback() {
CommandRegistrationCallback.EVENT.register((dispatcher, $) -> new FabricTabCommand().onRegisterCommands(dispatcher));
}
@Override
public Packet<?> newHeaderFooter(Component header, Component footer) {
return new ClientboundTabListPacket(header, footer);
}
@Override
public Packet<?> setDisplaySlot(int slot, Objective objective) {
return new ClientboundSetDisplayObjectivePacket(slot, objective);
}
@Override
public Objective newObjective(String name, Component displayName, ObjectiveCriteria.RenderType renderType,
@Nullable Component numberFormat) {
return new Objective(dummyScoreboard, name, ObjectiveCriteria.DUMMY, displayName, renderType);
}
@Override
public Packet<?> registerTeam(PlayerTeam team) {
return ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, true);
}
@Override
public Packet<?> unregisterTeam(PlayerTeam team) {
return ClientboundSetPlayerTeamPacket.createRemovePacket(team);
}
@Override
public Packet<?> updateTeam(PlayerTeam team) {
return ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, false);
}
@Override
public Packet<?> setScore(String objective, String scoreHolder, int score, Component displayName, @Nullable Component numberFormat) {
return new ClientboundSetScorePacket(ServerScoreboard.Method.CHANGE, objective, scoreHolder, score);
}
@Override
public Packet<?> removeScore(String objective, String scoreHolder) {
return new ClientboundSetScorePacket(ServerScoreboard.Method.REMOVE, objective, scoreHolder, 0);
}
}
+28
View File
@@ -0,0 +1,28 @@
plugins {
id("fabric-loom")
}
repositories {
// Gradle doesn't support combining settings and project repositories, so we have to re-declare all the settings repos we need
maven("https://jitpack.io") // YamlAssist
maven("https://repo.opencollab.dev/maven-snapshots/")
maven("https://repo.viaversion.com/")
maven("https://oss.sonatype.org/content/repositories/snapshots")
}
dependencies {
implementation(projects.fabric)
minecraft("com.mojang:minecraft:1.19.2")
mappings(loom.officialMojangMappings())
modImplementation("net.fabricmc:fabric-loader:0.14.17")
val fabricApiVersion = "0.76.0+1.19.2"
modImplementation(fabricApi.module("fabric-api-base", fabricApiVersion))
modImplementation(fabricApi.module("fabric-lifecycle-events-v1", fabricApiVersion))
modImplementation(fabricApi.module("fabric-command-api-v2", fabricApiVersion))
}
tasks {
compileJava {
options.release.set(17)
}
}
@@ -0,0 +1,137 @@
package me.neznamy.tab.platforms.fabric.v1_19_2;
import com.mojang.authlib.GameProfile;
import io.netty.channel.Channel;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.fabric.FabricTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.features.injection.NettyPipelineInjector;
import me.neznamy.tab.shared.features.nametags.NameTag;
import me.neznamy.tab.shared.features.sorting.Sorting;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.minecraft.network.Connection;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoPacket;
import net.minecraft.network.protocol.game.ClientboundSetDisplayObjectivePacket;
import net.minecraft.network.protocol.game.ClientboundSetObjectivePacket;
import net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket;
import net.minecraft.server.network.ServerGamePacketListenerImpl;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
public class FabricPipelineInjector extends NettyPipelineInjector {
public FabricPipelineInjector() {
super("packet_handler");
}
@Override
@Nullable
@SneakyThrows
protected Channel getChannel(@NotNull TabPlayer player) {
Connection c = (Connection) ReflectionUtils.getFields(ServerGamePacketListenerImpl.class, Connection.class)
.get(0).get(((FabricTabPlayer)player).getPlayer().connection);
return (Channel) ReflectionUtils.getFields(Connection.class, Channel.class).get(0).get(c);
}
@Override
public void onDisplayObjective(@NotNull TabPlayer player, @NotNull Object packet) {
TAB.getInstance().getFeatureManager().onDisplayObjective(player,
((ClientboundSetDisplayObjectivePacket) packet).getSlot(),
String.valueOf(((ClientboundSetDisplayObjectivePacket)packet).getObjectiveName()));
}
@Override
public void onObjective(@NotNull TabPlayer player, @NotNull Object packet) {
TAB.getInstance().getFeatureManager().onObjective(player,
((ClientboundSetObjectivePacket)packet).getMethod(),
((ClientboundSetObjectivePacket)packet).getObjectiveName());
}
@Override
public boolean isDisplayObjective(@NotNull Object packet) {
return packet instanceof ClientboundSetDisplayObjectivePacket;
}
@Override
public boolean isObjective(@NotNull Object packet) {
return packet instanceof ClientboundSetObjectivePacket;
}
@Override
public boolean isTeam(@NotNull Object packet) {
return packet instanceof ClientboundSetPlayerTeamPacket;
}
@Override
public boolean isPlayerInfo(@NotNull Object packet) {
return packet instanceof ClientboundPlayerInfoPacket;
}
@Override
@SneakyThrows
public void onPlayerInfo(@NotNull TabPlayer receiver, @NotNull Object packet0) {
ClientboundPlayerInfoPacket packet = (ClientboundPlayerInfoPacket) packet0;
for (ClientboundPlayerInfoPacket.PlayerUpdate nmsData : packet.getEntries()) {
GameProfile profile = nmsData.getProfile();
Field displayNameField = ReflectionUtils.getFields(ClientboundPlayerInfoPacket.PlayerUpdate.class, Component.class).get(0);
Field latencyField = ReflectionUtils.getFields(ClientboundPlayerInfoPacket.PlayerUpdate.class, int.class).get(0);
if (packet.getAction() == ClientboundPlayerInfoPacket.Action.UPDATE_DISPLAY_NAME) {
IChatBaseComponent newDisplayName = TAB.getInstance().getFeatureManager().onDisplayNameChange(receiver, profile.getId());
if (newDisplayName != null) displayNameField.set(nmsData, ((FabricTabPlayer)receiver).getPlatform().toComponent(newDisplayName, receiver.getVersion()));
}
if (packet.getAction() == ClientboundPlayerInfoPacket.Action.UPDATE_LATENCY) {
latencyField.set(nmsData, TAB.getInstance().getFeatureManager().onLatencyChange(receiver, profile.getId(), latencyField.getInt(nmsData)));
}
if (packet.getAction() == ClientboundPlayerInfoPacket.Action.ADD_PLAYER) {
TAB.getInstance().getFeatureManager().onEntryAdd(receiver, profile.getId(), profile.getName());
}
}
}
@Override
public boolean isLogin(@NotNull Object packet) {
return false;
}
@Override
@SneakyThrows
public void modifyPlayers(@NotNull Object teamPacket) {
if (TAB.getInstance().getNameTagManager() == null) return;
ClientboundSetPlayerTeamPacket packet = (ClientboundSetPlayerTeamPacket) teamPacket;
int action = ReflectionUtils.getInstanceFields(ClientboundSetPlayerTeamPacket.class, int.class).get(0).getInt(packet);
if (action == 1 || action == 2 || action == 4) return;
Collection<String> players = packet.getPlayers();
String teamName = packet.getName();
if (players == null) return;
//creating a new list to prevent NoSuchFieldException in minecraft packet encoder when a player is removed
Collection<String> newList = new ArrayList<>();
for (String entry : players) {
TabPlayer p = getPlayer(entry);
if (p == null) {
newList.add(entry);
continue;
}
Sorting sorting = TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.SORTING);
String expectedTeam = sorting.getShortTeamName(p);
if (expectedTeam == null) {
newList.add(entry);
continue;
}
if (!((NameTag)TAB.getInstance().getNameTagManager()).getDisableChecker().isDisabledPlayer(p) &&
!TAB.getInstance().getNameTagManager().hasTeamHandlingPaused(p) && !teamName.equals(expectedTeam)) {
logTeamOverride(teamName, p.getName(), expectedTeam);
} else {
newList.add(entry);
}
}
ReflectionUtils.getFields(ClientboundSetPlayerTeamPacket.class, Collection.class).get(0).set(packet, newList);
}
}
@@ -0,0 +1,220 @@
package me.neznamy.tab.platforms.fabric.v1_19_2;
import com.mojang.authlib.properties.Property;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.fabric.*;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.backend.EntityData;
import me.neznamy.tab.shared.backend.Location;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.features.injection.PipelineInjector;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
import net.minecraft.SharedConstants;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.*;
import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.network.syncher.EntityDataSerializers;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.server.ServerScoreboard;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.level.GameType;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.storage.ServerLevelData;
import net.minecraft.world.phys.Vec3;
import net.minecraft.world.scores.Objective;
import net.minecraft.world.scores.PlayerTeam;
import net.minecraft.world.scores.criteria.ObjectiveCriteria;
import org.jetbrains.annotations.Nullable;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
public class FabricTAB implements VersionLoader {
@Override
public List<String> getSupportedVersions() {
return List.of("1.19", "1.19.1", "1.19.2");
}
@Override
public String getName(Level level) {
String path = level.dimension().location().getPath();
String dimensionSuffix = switch (path) {
case "overworld" -> ""; // No suffix for overworld
case "the_nether" -> "_nether";
default -> "_" + path; // End + default behavior for other dimensions created by mods
};
return ((ServerLevelData)level.getLevelData()).getLevelName() + dimensionSuffix;
}
@Override
public int getPing(ServerPlayer player) {
return player.latency;
}
@Override
public void sendMessage(ServerPlayer player, Component message) {
player.sendSystemMessage(message);
}
@Override
public TabList.Skin propertyToSkin(Property property) {
return new TabList.Skin(property.getValue(), property.getSignature());
}
@Override
public void sendMessage(CommandSourceStack sender, Component message) {
sender.sendSystemMessage(message);
}
@Override
public String getServerVersion() {
return SharedConstants.getCurrentVersion().getName();
}
@Override
public double getMSPT() {
return ((FabricPlatform)TAB.getInstance().getPlatform()).getServer().getAverageTickTime();
}
@Override
public Component deserialize(String string) {
return Component.Serializer.fromJson(string);
}
@Override
@SneakyThrows
public Packet<?> build(TabList.Action action, FabricTabList.Builder entry) {
ClientboundPlayerInfoPacket packet = new ClientboundPlayerInfoPacket(ClientboundPlayerInfoPacket.Action.valueOf(action.name()), Collections.emptyList());
ReflectionUtils.getFields(ClientboundPlayerInfoPacket.class, List.class).get(0).set(packet,
Collections.singletonList(new ClientboundPlayerInfoPacket.PlayerUpdate(
entry.createProfile(),
entry.getLatency(),
GameType.byId(entry.getGameMode()),
entry.getDisplayName(),
null
)));
return packet;
}
@Override
public Level getLevel(ServerPlayer player) {
return player.level;
}
@Override
public boolean isSneaking(ServerPlayer player) {
return player.isCrouching();
}
@Override
public EntityData createDataWatcher(TabPlayer viewer, byte flags, String displayName, boolean nameVisible) {
SynchedEntityData data = new SynchedEntityData(null);
data.define(new EntityDataAccessor<>(0, EntityDataSerializers.BYTE), flags);
data.define(new EntityDataAccessor<>(2, EntityDataSerializers.OPTIONAL_COMPONENT),
Optional.of(((FabricTabPlayer)viewer).getPlatform().toComponent(IChatBaseComponent.optimizedComponent(displayName), viewer.getVersion())));
data.define(new EntityDataAccessor<>(3, EntityDataSerializers.BOOLEAN), nameVisible);
data.define(new EntityDataAccessor<>(15, EntityDataSerializers.BYTE), (byte)16);
return () -> data;
}
@Override
public Packet<?> spawnEntity(int entityId, UUID id, Object entityType, Location location) {
return new ClientboundAddEntityPacket(entityId, id, location.getX(), location.getY(), location.getZ(),
0, 0, (EntityType<?>) entityType, 0, Vec3.ZERO, 0);
}
@Override
public Packet<?> newEntityMetadata(int entityId, EntityData data) {
return new ClientboundSetEntityDataPacket(entityId, (SynchedEntityData) data.build(), true);
}
@Override
public boolean isSpawnPlayerPacket(Object packet) {
return packet instanceof ClientboundAddPlayerPacket;
}
@Override
public int getSpawnedPlayer(Object spawnPacket) {
return ((ClientboundAddPlayerPacket) spawnPacket).getEntityId();
}
@Override
public int[] getDestroyedEntities(Object entityDestroyPacket) {
return ((ClientboundRemoveEntitiesPacket) entityDestroyPacket).getEntityIds().toIntArray();
}
@Override
public boolean isBundlePacket(Object packet) {
return false;
}
@Override
public Iterable<Object> getPackets(Object bundlePacket) {
return Collections.emptyList();
}
@Override
public PipelineInjector createPipelineInjector() {
return new FabricPipelineInjector();
}
@Override
public void destroyEntities(FabricTabPlayer player, int... entities) {
player.sendPacket(new ClientboundRemoveEntitiesPacket(entities));
}
@Override
public void registerCommandCallback() {
CommandRegistrationCallback.EVENT.register((dispatcher, $, $$) -> new FabricTabCommand().onRegisterCommands(dispatcher));
}
@Override
public Packet<?> newHeaderFooter(Component header, Component footer) {
return new ClientboundTabListPacket(header, footer);
}
@Override
public Packet<?> setDisplaySlot(int slot, Objective objective) {
return new ClientboundSetDisplayObjectivePacket(slot, objective);
}
@Override
public Objective newObjective(String name, Component displayName, ObjectiveCriteria.RenderType renderType,
@Nullable Component numberFormat) {
return new Objective(dummyScoreboard, name, ObjectiveCriteria.DUMMY, displayName, renderType);
}
@Override
public Packet<?> registerTeam(PlayerTeam team) {
return ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, true);
}
@Override
public Packet<?> unregisterTeam(PlayerTeam team) {
return ClientboundSetPlayerTeamPacket.createRemovePacket(team);
}
@Override
public Packet<?> updateTeam(PlayerTeam team) {
return ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, false);
}
@Override
public Packet<?> setScore(String objective, String scoreHolder, int score, Component displayName, @Nullable Component numberFormat) {
return new ClientboundSetScorePacket(ServerScoreboard.Method.CHANGE, objective, scoreHolder, score);
}
@Override
public Packet<?> removeScore(String objective, String scoreHolder) {
return new ClientboundSetScorePacket(ServerScoreboard.Method.REMOVE, objective, scoreHolder, 0);
}
}
+28
View File
@@ -0,0 +1,28 @@
plugins {
id("fabric-loom")
}
repositories {
// Gradle doesn't support combining settings and project repositories, so we have to re-declare all the settings repos we need
maven("https://jitpack.io") // YamlAssist
maven("https://repo.opencollab.dev/maven-snapshots/")
maven("https://repo.viaversion.com/")
maven("https://oss.sonatype.org/content/repositories/snapshots")
}
dependencies {
implementation(projects.fabric)
minecraft("com.mojang:minecraft:1.19.3")
mappings(loom.officialMojangMappings())
modImplementation("net.fabricmc:fabric-loader:0.14.17")
val fabricApiVersion = "0.76.0+1.19.3"
modImplementation(fabricApi.module("fabric-api-base", fabricApiVersion))
modImplementation(fabricApi.module("fabric-lifecycle-events-v1", fabricApiVersion))
modImplementation(fabricApi.module("fabric-command-api-v2", fabricApiVersion))
}
tasks {
compileJava {
options.release.set(17)
}
}
@@ -1,7 +1,9 @@
package me.neznamy.tab.platforms.fabric;
package me.neznamy.tab.platforms.fabric.v1_19_3;
import com.mojang.authlib.GameProfile;
import io.netty.channel.Channel;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.fabric.FabricTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
@@ -9,11 +11,14 @@ import me.neznamy.tab.shared.features.injection.NettyPipelineInjector;
import me.neznamy.tab.shared.features.nametags.NameTag;
import me.neznamy.tab.shared.features.sorting.Sorting;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.minecraft.network.Connection;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket;
import net.minecraft.network.protocol.game.ClientboundSetDisplayObjectivePacket;
import net.minecraft.network.protocol.game.ClientboundSetObjectivePacket;
import net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket;
import net.minecraft.server.network.ServerGamePacketListenerImpl;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -30,14 +35,17 @@ public class FabricPipelineInjector extends NettyPipelineInjector {
@Override
@Nullable
@SneakyThrows
protected Channel getChannel(@NotNull TabPlayer player) {
return ((FabricTabPlayer)player).getPlayer().connection.connection.channel;
Connection c = (Connection) ReflectionUtils.getFields(ServerGamePacketListenerImpl.class, Connection.class)
.get(0).get(((FabricTabPlayer)player).getPlayer().connection);
return (Channel) ReflectionUtils.getFields(Connection.class, Channel.class).get(0).get(c);
}
@Override
public void onDisplayObjective(@NotNull TabPlayer player, @NotNull Object packet) {
TAB.getInstance().getFeatureManager().onDisplayObjective(player,
FabricMultiVersion.getSlot((ClientboundSetDisplayObjectivePacket) packet),
((ClientboundSetDisplayObjectivePacket) packet).getSlot(),
String.valueOf(((ClientboundSetDisplayObjectivePacket)packet).getObjectiveName()));
}
@@ -65,14 +73,12 @@ public class FabricPipelineInjector extends NettyPipelineInjector {
@Override
public boolean isPlayerInfo(@NotNull Object packet) {
return FabricMultiVersion.isPlayerInfo(packet);
return packet instanceof ClientboundPlayerInfoUpdatePacket;
}
@Override
@SneakyThrows
public void onPlayerInfo(@NotNull TabPlayer receiver, @NotNull Object packet0) {
// Comment this entire method out when compiling with 1.19.2-, adding compatibility would be tough and not worth,
// as nobody needs these features on Fabric anyway
ClientboundPlayerInfoUpdatePacket packet = (ClientboundPlayerInfoUpdatePacket) packet0;
EnumSet<ClientboundPlayerInfoUpdatePacket.Action> actions = packet.actions();
List<ClientboundPlayerInfoUpdatePacket.Entry> updatedList = new ArrayList<>();
@@ -92,7 +98,7 @@ public class FabricPipelineInjector extends NettyPipelineInjector {
}
updatedList.add(new ClientboundPlayerInfoUpdatePacket.Entry(nmsData.profileId(), profile, nmsData.listed(), latency, nmsData.gameMode(), displayName, nmsData.chatSession()));
}
packet.entries = updatedList;
ReflectionUtils.getFields(ClientboundPlayerInfoUpdatePacket.class, List.class).get(0).set(packet, updatedList);
}
@Override
@@ -101,10 +107,11 @@ public class FabricPipelineInjector extends NettyPipelineInjector {
}
@Override
@SneakyThrows
public void modifyPlayers(@NotNull Object teamPacket) {
if (TAB.getInstance().getNameTagManager() == null) return;
ClientboundSetPlayerTeamPacket packet = (ClientboundSetPlayerTeamPacket) teamPacket;
int action = packet.method;
int action = ReflectionUtils.getInstanceFields(ClientboundSetPlayerTeamPacket.class, int.class).get(0).getInt(packet);
if (action == 1 || action == 2 || action == 4) return;
Collection<String> players = packet.getPlayers();
String teamName = packet.getName();
@@ -130,6 +137,6 @@ public class FabricPipelineInjector extends NettyPipelineInjector {
newList.add(entry);
}
}
packet.players = newList;
ReflectionUtils.getFields(ClientboundSetPlayerTeamPacket.class, Collection.class).get(0).set(packet, newList);
}
}
@@ -0,0 +1,225 @@
package me.neznamy.tab.platforms.fabric.v1_19_3;
import com.mojang.authlib.properties.Property;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.fabric.*;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.backend.EntityData;
import me.neznamy.tab.shared.backend.Location;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.features.injection.PipelineInjector;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
import net.minecraft.SharedConstants;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.*;
import net.minecraft.network.syncher.EntityDataSerializers;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.server.ServerScoreboard;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.level.GameType;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.storage.ServerLevelData;
import net.minecraft.world.phys.Vec3;
import net.minecraft.world.scores.Objective;
import net.minecraft.world.scores.PlayerTeam;
import net.minecraft.world.scores.criteria.ObjectiveCriteria;
import org.jetbrains.annotations.Nullable;
import java.util.*;
public class FabricTAB implements VersionLoader {
@Override
public List<String> getSupportedVersions() {
return List.of("1.19.3");
}
@Override
public String getName(Level level) {
String path = level.dimension().location().getPath();
String dimensionSuffix = switch (path) {
case "overworld" -> ""; // No suffix for overworld
case "the_nether" -> "_nether";
default -> "_" + path; // End + default behavior for other dimensions created by mods
};
return ((ServerLevelData)level.getLevelData()).getLevelName() + dimensionSuffix;
}
@Override
public int getPing(ServerPlayer player) {
return player.latency;
}
@Override
public void sendMessage(ServerPlayer player, Component message) {
player.sendSystemMessage(message);
}
@Override
public TabList.Skin propertyToSkin(Property property) {
return new TabList.Skin(property.getValue(), property.getSignature());
}
@Override
public void sendMessage(CommandSourceStack sender, Component message) {
sender.sendSystemMessage(message);
}
@Override
public String getServerVersion() {
return SharedConstants.getCurrentVersion().getName();
}
@Override
public double getMSPT() {
return ((FabricPlatform)TAB.getInstance().getPlatform()).getServer().getAverageTickTime();
}
@Override
public Component deserialize(String string) {
return Component.Serializer.fromJson(string);
}
@Override
@SneakyThrows
public Packet<?> build(TabList.Action action, FabricTabList.Builder entry) {
if (action == FabricTabList.Action.REMOVE_PLAYER) {
return new ClientboundPlayerInfoRemovePacket(Collections.singletonList(entry.getId()));
}
EnumSet<ClientboundPlayerInfoUpdatePacket.Action> actions = action == FabricTabList.Action.ADD_PLAYER ?
EnumSet.allOf(ClientboundPlayerInfoUpdatePacket.Action.class) :
EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.valueOf(action.name()));
ClientboundPlayerInfoUpdatePacket packet = new ClientboundPlayerInfoUpdatePacket(actions, Collections.emptyList());
ReflectionUtils.getFields(ClientboundPlayerInfoUpdatePacket.class, List.class).get(0).set(packet,
Collections.singletonList(new ClientboundPlayerInfoUpdatePacket.Entry(
entry.getId(),
entry.createProfile(),
true,
entry.getLatency(),
GameType.byId(entry.getGameMode()),
entry.getDisplayName(),
null
)));
return packet;
}
@Override
public Level getLevel(ServerPlayer player) {
return player.level;
}
@Override
public boolean isSneaking(ServerPlayer player) {
return player.isCrouching();
}
@Override
public EntityData createDataWatcher(TabPlayer viewer, byte flags, String displayName, boolean nameVisible) {
return () -> java.util.Arrays.asList(
new SynchedEntityData.DataValue<>(0, EntityDataSerializers.BYTE, flags),
new SynchedEntityData.DataValue<>(2, EntityDataSerializers.OPTIONAL_COMPONENT,
Optional.of(((FabricTabPlayer)viewer).getPlatform().toComponent(IChatBaseComponent.optimizedComponent(displayName), viewer.getVersion()))),
new SynchedEntityData.DataValue<>(3, EntityDataSerializers.BOOLEAN, nameVisible),
new SynchedEntityData.DataValue<>(15, EntityDataSerializers.BYTE, (byte)16)
);
}
@Override
public Packet<?> spawnEntity(int entityId, UUID id, Object entityType, Location location) {
return new ClientboundAddEntityPacket(entityId, id, location.getX(), location.getY(), location.getZ(),
0, 0, (EntityType<?>) entityType, 0, Vec3.ZERO, 0);
}
@Override
@SuppressWarnings("unchecked")
public Packet<?> newEntityMetadata(int entityId, EntityData data) {
return new ClientboundSetEntityDataPacket(entityId, (List<SynchedEntityData.DataValue<?>>) data.build());
}
@Override
public boolean isSpawnPlayerPacket(Object packet) {
return packet instanceof ClientboundAddPlayerPacket;
}
@Override
public int getSpawnedPlayer(Object spawnPacket) {
return ((ClientboundAddPlayerPacket) spawnPacket).getEntityId();
}
@Override
public int[] getDestroyedEntities(Object entityDestroyPacket) {
return ((ClientboundRemoveEntitiesPacket) entityDestroyPacket).getEntityIds().toIntArray();
}
@Override
public boolean isBundlePacket(Object packet) {
return false;
}
@Override
public Iterable<Object> getPackets(Object bundlePacket) {
return Collections.emptyList();
}
@Override
public PipelineInjector createPipelineInjector() {
return new FabricPipelineInjector();
}
@Override
public void destroyEntities(FabricTabPlayer player, int... entities) {
player.sendPacket(new ClientboundRemoveEntitiesPacket(entities));
}
@Override
public void registerCommandCallback() {
CommandRegistrationCallback.EVENT.register((dispatcher, $, $$) -> new FabricTabCommand().onRegisterCommands(dispatcher));
}
@Override
public Packet<?> newHeaderFooter(Component header, Component footer) {
return new ClientboundTabListPacket(header, footer);
}
@Override
public Packet<?> setDisplaySlot(int slot, Objective objective) {
return new ClientboundSetDisplayObjectivePacket(slot, objective);
}
@Override
public Objective newObjective(String name, Component displayName, ObjectiveCriteria.RenderType renderType,
@Nullable Component numberFormat) {
return new Objective(dummyScoreboard, name, ObjectiveCriteria.DUMMY, displayName, renderType);
}
@Override
public Packet<?> registerTeam(PlayerTeam team) {
return ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, true);
}
@Override
public Packet<?> unregisterTeam(PlayerTeam team) {
return ClientboundSetPlayerTeamPacket.createRemovePacket(team);
}
@Override
public Packet<?> updateTeam(PlayerTeam team) {
return ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, false);
}
@Override
public Packet<?> setScore(String objective, String scoreHolder, int score, Component displayName, @Nullable Component numberFormat) {
return new ClientboundSetScorePacket(ServerScoreboard.Method.CHANGE, objective, scoreHolder, score);
}
@Override
public Packet<?> removeScore(String objective, String scoreHolder) {
return new ClientboundSetScorePacket(ServerScoreboard.Method.REMOVE, objective, scoreHolder, 0);
}
}
+28
View File
@@ -0,0 +1,28 @@
plugins {
id("fabric-loom")
}
repositories {
// Gradle doesn't support combining settings and project repositories, so we have to re-declare all the settings repos we need
maven("https://jitpack.io") // YamlAssist
maven("https://repo.opencollab.dev/maven-snapshots/")
maven("https://repo.viaversion.com/")
maven("https://oss.sonatype.org/content/repositories/snapshots")
}
dependencies {
implementation(projects.fabric)
minecraft("com.mojang:minecraft:1.20.1")
mappings(loom.officialMojangMappings())
modImplementation("net.fabricmc:fabric-loader:0.14.17")
val fabricApiVersion = "0.83.0+1.20.1"
modImplementation(fabricApi.module("fabric-api-base", fabricApiVersion))
modImplementation(fabricApi.module("fabric-lifecycle-events-v1", fabricApiVersion))
modImplementation(fabricApi.module("fabric-command-api-v2", fabricApiVersion))
}
tasks {
compileJava {
options.release.set(17)
}
}
@@ -0,0 +1,142 @@
package me.neznamy.tab.platforms.fabric.v1_20_1;
import com.mojang.authlib.GameProfile;
import io.netty.channel.Channel;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.fabric.FabricTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.features.injection.NettyPipelineInjector;
import me.neznamy.tab.shared.features.nametags.NameTag;
import me.neznamy.tab.shared.features.sorting.Sorting;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.minecraft.network.Connection;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket;
import net.minecraft.network.protocol.game.ClientboundSetDisplayObjectivePacket;
import net.minecraft.network.protocol.game.ClientboundSetObjectivePacket;
import net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket;
import net.minecraft.server.network.ServerGamePacketListenerImpl;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumSet;
import java.util.List;
public class FabricPipelineInjector extends NettyPipelineInjector {
public FabricPipelineInjector() {
super("packet_handler");
}
@Override
@Nullable
@SneakyThrows
protected Channel getChannel(@NotNull TabPlayer player) {
Connection c = (Connection) ReflectionUtils.getFields(ServerGamePacketListenerImpl.class, Connection.class)
.get(0).get(((FabricTabPlayer)player).getPlayer().connection);
return (Channel) ReflectionUtils.getFields(Connection.class, Channel.class).get(0).get(c);
}
@Override
public void onDisplayObjective(@NotNull TabPlayer player, @NotNull Object packet) {
TAB.getInstance().getFeatureManager().onDisplayObjective(player,
((ClientboundSetDisplayObjectivePacket) packet).getSlot(),
String.valueOf(((ClientboundSetDisplayObjectivePacket)packet).getObjectiveName()));
}
@Override
public void onObjective(@NotNull TabPlayer player, @NotNull Object packet) {
TAB.getInstance().getFeatureManager().onObjective(player,
((ClientboundSetObjectivePacket)packet).getMethod(),
((ClientboundSetObjectivePacket)packet).getObjectiveName());
}
@Override
public boolean isDisplayObjective(@NotNull Object packet) {
return packet instanceof ClientboundSetDisplayObjectivePacket;
}
@Override
public boolean isObjective(@NotNull Object packet) {
return packet instanceof ClientboundSetObjectivePacket;
}
@Override
public boolean isTeam(@NotNull Object packet) {
return packet instanceof ClientboundSetPlayerTeamPacket;
}
@Override
public boolean isPlayerInfo(@NotNull Object packet) {
return packet instanceof ClientboundPlayerInfoUpdatePacket;
}
@Override
@SneakyThrows
public void onPlayerInfo(@NotNull TabPlayer receiver, @NotNull Object packet0) {
ClientboundPlayerInfoUpdatePacket packet = (ClientboundPlayerInfoUpdatePacket) packet0;
EnumSet<ClientboundPlayerInfoUpdatePacket.Action> actions = packet.actions();
List<ClientboundPlayerInfoUpdatePacket.Entry> updatedList = new ArrayList<>();
for (ClientboundPlayerInfoUpdatePacket.Entry nmsData : packet.entries()) {
GameProfile profile = nmsData.profile();
Component displayName = nmsData.displayName();
int latency = nmsData.latency();
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME)) {
IChatBaseComponent newDisplayName = TAB.getInstance().getFeatureManager().onDisplayNameChange(receiver, nmsData.profileId());
if (newDisplayName != null) displayName = ((FabricTabPlayer)receiver).getPlatform().toComponent(newDisplayName, receiver.getVersion());
}
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY)) {
latency = TAB.getInstance().getFeatureManager().onLatencyChange(receiver, nmsData.profileId(), latency);
}
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER)) {
TAB.getInstance().getFeatureManager().onEntryAdd(receiver, nmsData.profileId(), profile.getName());
}
updatedList.add(new ClientboundPlayerInfoUpdatePacket.Entry(nmsData.profileId(), profile, nmsData.listed(), latency, nmsData.gameMode(), displayName, nmsData.chatSession()));
}
ReflectionUtils.getFields(ClientboundPlayerInfoUpdatePacket.class, List.class).get(0).set(packet, updatedList);
}
@Override
public boolean isLogin(@NotNull Object packet) {
return false;
}
@Override
@SneakyThrows
public void modifyPlayers(@NotNull Object teamPacket) {
if (TAB.getInstance().getNameTagManager() == null) return;
ClientboundSetPlayerTeamPacket packet = (ClientboundSetPlayerTeamPacket) teamPacket;
int action = ReflectionUtils.getInstanceFields(ClientboundSetPlayerTeamPacket.class, int.class).get(0).getInt(packet);
if (action == 1 || action == 2 || action == 4) return;
Collection<String> players = packet.getPlayers();
String teamName = packet.getName();
if (players == null) return;
//creating a new list to prevent NoSuchFieldException in minecraft packet encoder when a player is removed
Collection<String> newList = new ArrayList<>();
for (String entry : players) {
TabPlayer p = getPlayer(entry);
if (p == null) {
newList.add(entry);
continue;
}
Sorting sorting = TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.SORTING);
String expectedTeam = sorting.getShortTeamName(p);
if (expectedTeam == null) {
newList.add(entry);
continue;
}
if (!((NameTag)TAB.getInstance().getNameTagManager()).getDisableChecker().isDisabledPlayer(p) &&
!TAB.getInstance().getNameTagManager().hasTeamHandlingPaused(p) && !teamName.equals(expectedTeam)) {
logTeamOverride(teamName, p.getName(), expectedTeam);
} else {
newList.add(entry);
}
}
ReflectionUtils.getFields(ClientboundSetPlayerTeamPacket.class, Collection.class).get(0).set(packet, newList);
}
}
@@ -0,0 +1,226 @@
package me.neznamy.tab.platforms.fabric.v1_20_1;
import com.mojang.authlib.properties.Property;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.fabric.*;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.backend.EntityData;
import me.neznamy.tab.shared.backend.Location;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.features.injection.PipelineInjector;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
import net.minecraft.SharedConstants;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.*;
import net.minecraft.network.syncher.EntityDataSerializers;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.server.ServerScoreboard;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.level.GameType;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.storage.ServerLevelData;
import net.minecraft.world.phys.Vec3;
import net.minecraft.world.scores.Objective;
import net.minecraft.world.scores.PlayerTeam;
import net.minecraft.world.scores.criteria.ObjectiveCriteria;
import org.jetbrains.annotations.Nullable;
import java.util.*;
public class FabricTAB implements VersionLoader {
@Override
public List<String> getSupportedVersions() {
return List.of("1.19.4", "1.20", "1.20.1");
}
@Override
public String getName(Level level) {
String path = level.dimension().location().getPath();
String dimensionSuffix = switch (path) {
case "overworld" -> ""; // No suffix for overworld
case "the_nether" -> "_nether";
default -> "_" + path; // End + default behavior for other dimensions created by mods
};
return ((ServerLevelData)level.getLevelData()).getLevelName() + dimensionSuffix;
}
@Override
public int getPing(ServerPlayer player) {
return player.latency;
}
@Override
public void sendMessage(ServerPlayer player, Component message) {
player.sendSystemMessage(message);
}
@Override
public TabList.Skin propertyToSkin(Property property) {
return new TabList.Skin(property.getValue(), property.getSignature());
}
@Override
public void sendMessage(CommandSourceStack sender, Component message) {
sender.sendSystemMessage(message);
}
@Override
public String getServerVersion() {
return SharedConstants.getCurrentVersion().getName();
}
@Override
public double getMSPT() {
return ((FabricPlatform)TAB.getInstance().getPlatform()).getServer().getAverageTickTime();
}
@Override
public Component deserialize(String string) {
return Component.Serializer.fromJson(string);
}
@Override
@SneakyThrows
public Packet<?> build(TabList.Action action, FabricTabList.Builder entry) {
if (action == FabricTabList.Action.REMOVE_PLAYER) {
return new ClientboundPlayerInfoRemovePacket(Collections.singletonList(entry.getId()));
}
EnumSet<ClientboundPlayerInfoUpdatePacket.Action> actions = action == FabricTabList.Action.ADD_PLAYER ?
EnumSet.allOf(ClientboundPlayerInfoUpdatePacket.Action.class) :
EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.valueOf(action.name()));
ClientboundPlayerInfoUpdatePacket packet = new ClientboundPlayerInfoUpdatePacket(actions, Collections.emptyList());
ReflectionUtils.getFields(ClientboundPlayerInfoUpdatePacket.class, List.class).get(0).set(packet,
Collections.singletonList(new ClientboundPlayerInfoUpdatePacket.Entry(
entry.getId(),
entry.createProfile(),
true,
entry.getLatency(),
GameType.byId(entry.getGameMode()),
entry.getDisplayName(),
null
)));
return packet;
}
@Override
public Level getLevel(ServerPlayer player) {
return player.level();
}
@Override
public boolean isSneaking(ServerPlayer player) {
return player.isCrouching();
}
@Override
public EntityData createDataWatcher(TabPlayer viewer, byte flags, String displayName, boolean nameVisible) {
return () -> java.util.Arrays.asList(
new SynchedEntityData.DataValue<>(0, EntityDataSerializers.BYTE, flags),
new SynchedEntityData.DataValue<>(2, EntityDataSerializers.OPTIONAL_COMPONENT,
Optional.of(((FabricTabPlayer)viewer).getPlatform().toComponent(IChatBaseComponent.optimizedComponent(displayName), viewer.getVersion()))),
new SynchedEntityData.DataValue<>(3, EntityDataSerializers.BOOLEAN, nameVisible),
new SynchedEntityData.DataValue<>(15, EntityDataSerializers.BYTE, (byte)16)
);
}
@Override
public Packet<?> spawnEntity(int entityId, UUID id, Object entityType, Location location) {
return new ClientboundAddEntityPacket(entityId, id, location.getX(), location.getY(), location.getZ(),
0, 0, (EntityType<?>) entityType, 0, Vec3.ZERO, 0);
}
@Override
@SuppressWarnings("unchecked")
public Packet<?> newEntityMetadata(int entityId, EntityData data) {
return new ClientboundSetEntityDataPacket(entityId, (List<SynchedEntityData.DataValue<?>>) data.build());
}
@Override
public boolean isSpawnPlayerPacket(Object packet) {
return packet instanceof ClientboundAddPlayerPacket;
}
@Override
public int getSpawnedPlayer(Object spawnPacket) {
return ((ClientboundAddPlayerPacket) spawnPacket).getEntityId();
}
@Override
public int[] getDestroyedEntities(Object entityDestroyPacket) {
return ((ClientboundRemoveEntitiesPacket) entityDestroyPacket).getEntityIds().toIntArray();
}
@Override
public boolean isBundlePacket(Object packet) {
return packet instanceof ClientboundBundlePacket;
}
@Override
@SuppressWarnings("unchecked")
public Iterable<Object> getPackets(Object bundlePacket) {
return (Iterable<Object>) (Object) ((ClientboundBundlePacket)bundlePacket).subPackets();
}
@Override
public PipelineInjector createPipelineInjector() {
return new FabricPipelineInjector();
}
@Override
public void destroyEntities(FabricTabPlayer player, int... entities) {
player.sendPacket(new ClientboundRemoveEntitiesPacket(entities));
}
@Override
public void registerCommandCallback() {
CommandRegistrationCallback.EVENT.register((dispatcher, $, $$) -> new FabricTabCommand().onRegisterCommands(dispatcher));
}
@Override
public Packet<?> newHeaderFooter(Component header, Component footer) {
return new ClientboundTabListPacket(header, footer);
}
@Override
public Packet<?> setDisplaySlot(int slot, Objective objective) {
return new ClientboundSetDisplayObjectivePacket(slot, objective);
}
@Override
public Objective newObjective(String name, Component displayName, ObjectiveCriteria.RenderType renderType,
@Nullable Component numberFormat) {
return new Objective(dummyScoreboard, name, ObjectiveCriteria.DUMMY, displayName, renderType);
}
@Override
public Packet<?> registerTeam(PlayerTeam team) {
return ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, true);
}
@Override
public Packet<?> unregisterTeam(PlayerTeam team) {
return ClientboundSetPlayerTeamPacket.createRemovePacket(team);
}
@Override
public Packet<?> updateTeam(PlayerTeam team) {
return ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, false);
}
@Override
public Packet<?> setScore(String objective, String scoreHolder, int score, Component displayName, @Nullable Component numberFormat) {
return new ClientboundSetScorePacket(ServerScoreboard.Method.CHANGE, objective, scoreHolder, score);
}
@Override
public Packet<?> removeScore(String objective, String scoreHolder) {
return new ClientboundSetScorePacket(ServerScoreboard.Method.REMOVE, objective, scoreHolder, 0);
}
}
+28
View File
@@ -0,0 +1,28 @@
plugins {
id("fabric-loom")
}
repositories {
// Gradle doesn't support combining settings and project repositories, so we have to re-declare all the settings repos we need
maven("https://jitpack.io") // YamlAssist
maven("https://repo.opencollab.dev/maven-snapshots/")
maven("https://repo.viaversion.com/")
maven("https://oss.sonatype.org/content/repositories/snapshots")
}
dependencies {
implementation(projects.fabric)
minecraft("com.mojang:minecraft:1.20.2")
mappings(loom.officialMojangMappings())
modImplementation("net.fabricmc:fabric-loader:0.14.17")
val fabricApiVersion = "0.88.5+1.20.2"
modImplementation(fabricApi.module("fabric-api-base", fabricApiVersion))
modImplementation(fabricApi.module("fabric-lifecycle-events-v1", fabricApiVersion))
modImplementation(fabricApi.module("fabric-command-api-v2", fabricApiVersion))
}
tasks {
compileJava {
options.release.set(17)
}
}
@@ -0,0 +1,142 @@
package me.neznamy.tab.platforms.fabric.v1_20_2;
import com.mojang.authlib.GameProfile;
import io.netty.channel.Channel;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.fabric.FabricTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.features.injection.NettyPipelineInjector;
import me.neznamy.tab.shared.features.nametags.NameTag;
import me.neznamy.tab.shared.features.sorting.Sorting;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.minecraft.network.Connection;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket;
import net.minecraft.network.protocol.game.ClientboundSetDisplayObjectivePacket;
import net.minecraft.network.protocol.game.ClientboundSetObjectivePacket;
import net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket;
import net.minecraft.server.network.ServerCommonPacketListenerImpl;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumSet;
import java.util.List;
public class FabricPipelineInjector extends NettyPipelineInjector {
public FabricPipelineInjector() {
super("packet_handler");
}
@Override
@Nullable
@SneakyThrows
protected Channel getChannel(@NotNull TabPlayer player) {
Connection c = (Connection) ReflectionUtils.getFields(ServerCommonPacketListenerImpl.class, Connection.class)
.get(0).get(((FabricTabPlayer)player).getPlayer().connection);
return (Channel) ReflectionUtils.getFields(Connection.class, Channel.class).get(0).get(c);
}
@Override
public void onDisplayObjective(@NotNull TabPlayer player, @NotNull Object packet) {
TAB.getInstance().getFeatureManager().onDisplayObjective(player,
((ClientboundSetDisplayObjectivePacket) packet).getSlot().ordinal(),
String.valueOf(((ClientboundSetDisplayObjectivePacket)packet).getObjectiveName()));
}
@Override
public void onObjective(@NotNull TabPlayer player, @NotNull Object packet) {
TAB.getInstance().getFeatureManager().onObjective(player,
((ClientboundSetObjectivePacket)packet).getMethod(),
((ClientboundSetObjectivePacket)packet).getObjectiveName());
}
@Override
public boolean isDisplayObjective(@NotNull Object packet) {
return packet instanceof ClientboundSetDisplayObjectivePacket;
}
@Override
public boolean isObjective(@NotNull Object packet) {
return packet instanceof ClientboundSetObjectivePacket;
}
@Override
public boolean isTeam(@NotNull Object packet) {
return packet instanceof ClientboundSetPlayerTeamPacket;
}
@Override
public boolean isPlayerInfo(@NotNull Object packet) {
return packet instanceof ClientboundPlayerInfoUpdatePacket;
}
@Override
@SneakyThrows
public void onPlayerInfo(@NotNull TabPlayer receiver, @NotNull Object packet0) {
ClientboundPlayerInfoUpdatePacket packet = (ClientboundPlayerInfoUpdatePacket) packet0;
EnumSet<ClientboundPlayerInfoUpdatePacket.Action> actions = packet.actions();
List<ClientboundPlayerInfoUpdatePacket.Entry> updatedList = new ArrayList<>();
for (ClientboundPlayerInfoUpdatePacket.Entry nmsData : packet.entries()) {
GameProfile profile = nmsData.profile();
Component displayName = nmsData.displayName();
int latency = nmsData.latency();
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME)) {
IChatBaseComponent newDisplayName = TAB.getInstance().getFeatureManager().onDisplayNameChange(receiver, nmsData.profileId());
if (newDisplayName != null) displayName = ((FabricTabPlayer)receiver).getPlatform().toComponent(newDisplayName, receiver.getVersion());
}
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY)) {
latency = TAB.getInstance().getFeatureManager().onLatencyChange(receiver, nmsData.profileId(), latency);
}
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER)) {
TAB.getInstance().getFeatureManager().onEntryAdd(receiver, nmsData.profileId(), profile.getName());
}
updatedList.add(new ClientboundPlayerInfoUpdatePacket.Entry(nmsData.profileId(), profile, nmsData.listed(), latency, nmsData.gameMode(), displayName, nmsData.chatSession()));
}
ReflectionUtils.getFields(ClientboundPlayerInfoUpdatePacket.class, List.class).get(0).set(packet, updatedList);
}
@Override
public boolean isLogin(@NotNull Object packet) {
return false;
}
@Override
@SneakyThrows
public void modifyPlayers(@NotNull Object teamPacket) {
if (TAB.getInstance().getNameTagManager() == null) return;
ClientboundSetPlayerTeamPacket packet = (ClientboundSetPlayerTeamPacket) teamPacket;
int action = ReflectionUtils.getInstanceFields(ClientboundSetPlayerTeamPacket.class, int.class).get(0).getInt(packet);
if (action == 1 || action == 2 || action == 4) return;
Collection<String> players = packet.getPlayers();
String teamName = packet.getName();
if (players == null) return;
//creating a new list to prevent NoSuchFieldException in minecraft packet encoder when a player is removed
Collection<String> newList = new ArrayList<>();
for (String entry : players) {
TabPlayer p = getPlayer(entry);
if (p == null) {
newList.add(entry);
continue;
}
Sorting sorting = TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.SORTING);
String expectedTeam = sorting.getShortTeamName(p);
if (expectedTeam == null) {
newList.add(entry);
continue;
}
if (!((NameTag)TAB.getInstance().getNameTagManager()).getDisableChecker().isDisabledPlayer(p) &&
!TAB.getInstance().getNameTagManager().hasTeamHandlingPaused(p) && !teamName.equals(expectedTeam)) {
logTeamOverride(teamName, p.getName(), expectedTeam);
} else {
newList.add(entry);
}
}
ReflectionUtils.getFields(ClientboundSetPlayerTeamPacket.class, Collection.class).get(0).set(packet, newList);
}
}
@@ -0,0 +1,227 @@
package me.neznamy.tab.platforms.fabric.v1_20_2;
import com.mojang.authlib.properties.Property;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.fabric.*;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.backend.EntityData;
import me.neznamy.tab.shared.backend.Location;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.features.injection.PipelineInjector;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
import net.minecraft.SharedConstants;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.*;
import net.minecraft.network.syncher.EntityDataSerializers;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.server.ServerScoreboard;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.level.GameType;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.storage.ServerLevelData;
import net.minecraft.world.phys.Vec3;
import net.minecraft.world.scores.DisplaySlot;
import net.minecraft.world.scores.Objective;
import net.minecraft.world.scores.PlayerTeam;
import net.minecraft.world.scores.criteria.ObjectiveCriteria;
import org.jetbrains.annotations.Nullable;
import java.util.*;
public class FabricTAB implements VersionLoader {
@Override
public List<String> getSupportedVersions() {
return List.of("1.20.2");
}
@Override
public String getName(Level level) {
String path = level.dimension().location().getPath();
String dimensionSuffix = switch (path) {
case "overworld" -> ""; // No suffix for overworld
case "the_nether" -> "_nether";
default -> "_" + path; // End + default behavior for other dimensions created by mods
};
return ((ServerLevelData)level.getLevelData()).getLevelName() + dimensionSuffix;
}
@Override
public int getPing(ServerPlayer player) {
return player.connection.latency();
}
@Override
public void sendMessage(ServerPlayer player, Component message) {
player.sendSystemMessage(message);
}
@Override
public TabList.Skin propertyToSkin(Property property) {
return new TabList.Skin(property.value(), property.signature());
}
@Override
public void sendMessage(CommandSourceStack sender, Component message) {
sender.sendSystemMessage(message);
}
@Override
public String getServerVersion() {
return SharedConstants.getCurrentVersion().getName();
}
@Override
public double getMSPT() {
return ((FabricPlatform)TAB.getInstance().getPlatform()).getServer().getAverageTickTime();
}
@Override
public Component deserialize(String string) {
return Component.Serializer.fromJson(string);
}
@Override
@SneakyThrows
public Packet<?> build(TabList.Action action, FabricTabList.Builder entry) {
if (action == FabricTabList.Action.REMOVE_PLAYER) {
return new ClientboundPlayerInfoRemovePacket(Collections.singletonList(entry.getId()));
}
EnumSet<ClientboundPlayerInfoUpdatePacket.Action> actions = action == FabricTabList.Action.ADD_PLAYER ?
EnumSet.allOf(ClientboundPlayerInfoUpdatePacket.Action.class) :
EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.valueOf(action.name()));
ClientboundPlayerInfoUpdatePacket packet = new ClientboundPlayerInfoUpdatePacket(actions, Collections.emptyList());
ReflectionUtils.getFields(ClientboundPlayerInfoUpdatePacket.class, List.class).get(0).set(packet,
Collections.singletonList(new ClientboundPlayerInfoUpdatePacket.Entry(
entry.getId(),
entry.createProfile(),
true,
entry.getLatency(),
GameType.byId(entry.getGameMode()),
entry.getDisplayName(),
null
)));
return packet;
}
@Override
public Level getLevel(ServerPlayer player) {
return player.level();
}
@Override
public boolean isSneaking(ServerPlayer player) {
return player.isCrouching();
}
@Override
public EntityData createDataWatcher(TabPlayer viewer, byte flags, String displayName, boolean nameVisible) {
return () -> java.util.Arrays.asList(
new SynchedEntityData.DataValue<>(0, EntityDataSerializers.BYTE, flags),
new SynchedEntityData.DataValue<>(2, EntityDataSerializers.OPTIONAL_COMPONENT,
Optional.of(((FabricTabPlayer)viewer).getPlatform().toComponent(IChatBaseComponent.optimizedComponent(displayName), viewer.getVersion()))),
new SynchedEntityData.DataValue<>(3, EntityDataSerializers.BOOLEAN, nameVisible),
new SynchedEntityData.DataValue<>(15, EntityDataSerializers.BYTE, (byte)16)
);
}
@Override
public Packet<?> spawnEntity(int entityId, UUID id, Object entityType, Location location) {
return new ClientboundAddEntityPacket(entityId, id, location.getX(), location.getY(), location.getZ(),
0, 0, (EntityType<?>) entityType, 0, Vec3.ZERO, 0);
}
@Override
@SuppressWarnings("unchecked")
public Packet<?> newEntityMetadata(int entityId, EntityData data) {
return new ClientboundSetEntityDataPacket(entityId, (List<SynchedEntityData.DataValue<?>>) data.build());
}
@Override
public boolean isSpawnPlayerPacket(Object packet) {
return packet instanceof ClientboundAddEntityPacket;
}
@Override
public int getSpawnedPlayer(Object spawnPacket) {
return ((ClientboundAddEntityPacket) spawnPacket).getId();
}
@Override
public int[] getDestroyedEntities(Object entityDestroyPacket) {
return ((ClientboundRemoveEntitiesPacket) entityDestroyPacket).getEntityIds().toIntArray();
}
@Override
public boolean isBundlePacket(Object packet) {
return packet instanceof ClientboundBundlePacket;
}
@Override
@SuppressWarnings("unchecked")
public Iterable<Object> getPackets(Object bundlePacket) {
return (Iterable<Object>) (Object) ((ClientboundBundlePacket)bundlePacket).subPackets();
}
@Override
public PipelineInjector createPipelineInjector() {
return new FabricPipelineInjector();
}
@Override
public void destroyEntities(FabricTabPlayer player, int... entities) {
player.sendPacket(new ClientboundRemoveEntitiesPacket(entities));
}
@Override
public void registerCommandCallback() {
CommandRegistrationCallback.EVENT.register((dispatcher, $, $$) -> new FabricTabCommand().onRegisterCommands(dispatcher));
}
@Override
public Packet<?> newHeaderFooter(Component header, Component footer) {
return new ClientboundTabListPacket(header, footer);
}
@Override
public Packet<?> setDisplaySlot(int slot, Objective objective) {
return new ClientboundSetDisplayObjectivePacket(DisplaySlot.values()[slot], objective);
}
@Override
public Objective newObjective(String name, Component displayName, ObjectiveCriteria.RenderType renderType,
@Nullable Component numberFormat) {
return new Objective(dummyScoreboard, name, ObjectiveCriteria.DUMMY, displayName, renderType);
}
@Override
public Packet<?> registerTeam(PlayerTeam team) {
return ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, true);
}
@Override
public Packet<?> unregisterTeam(PlayerTeam team) {
return ClientboundSetPlayerTeamPacket.createRemovePacket(team);
}
@Override
public Packet<?> updateTeam(PlayerTeam team) {
return ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, false);
}
@Override
public Packet<?> setScore(String objective, String scoreHolder, int score, Component displayName, @Nullable Component numberFormat) {
return new ClientboundSetScorePacket(ServerScoreboard.Method.CHANGE, objective, scoreHolder, score);
}
@Override
public Packet<?> removeScore(String objective, String scoreHolder) {
return new ClientboundSetScorePacket(ServerScoreboard.Method.REMOVE, objective, scoreHolder, 0);
}
}
+27
View File
@@ -0,0 +1,27 @@
plugins {
id("fabric-loom")
}
repositories {
// Gradle doesn't support combining settings and project repositories, so we have to re-declare all the settings repos we need
maven("https://jitpack.io") // YamlAssist
maven("https://repo.opencollab.dev/maven-snapshots/")
maven("https://repo.viaversion.com/")
maven("https://oss.sonatype.org/content/repositories/snapshots")
}
dependencies {
implementation(projects.fabric)
minecraft("com.mojang:minecraft:1.20.4")
mappings(loom.officialMojangMappings())
modImplementation("net.fabricmc:fabric-loader:0.14.17")
val fabricApiVersion = "0.91.1+1.20.4"
modImplementation(fabricApi.module("fabric-api-base", fabricApiVersion))
modImplementation(fabricApi.module("fabric-command-api-v2", fabricApiVersion))
}
tasks {
compileJava {
options.release.set(17)
}
}
@@ -0,0 +1,142 @@
package me.neznamy.tab.platforms.fabric.v1_20_4;
import com.mojang.authlib.GameProfile;
import io.netty.channel.Channel;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.fabric.FabricTabPlayer;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.TabConstants;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.features.injection.NettyPipelineInjector;
import me.neznamy.tab.shared.features.nametags.NameTag;
import me.neznamy.tab.shared.features.sorting.Sorting;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.minecraft.network.Connection;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket;
import net.minecraft.network.protocol.game.ClientboundSetDisplayObjectivePacket;
import net.minecraft.network.protocol.game.ClientboundSetObjectivePacket;
import net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket;
import net.minecraft.server.network.ServerCommonPacketListenerImpl;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumSet;
import java.util.List;
public class FabricPipelineInjector extends NettyPipelineInjector {
public FabricPipelineInjector() {
super("packet_handler");
}
@Override
@Nullable
@SneakyThrows
protected Channel getChannel(@NotNull TabPlayer player) {
Connection c = (Connection) ReflectionUtils.getFields(ServerCommonPacketListenerImpl.class, Connection.class)
.get(0).get(((FabricTabPlayer)player).getPlayer().connection);
return (Channel) ReflectionUtils.getFields(Connection.class, Channel.class).get(0).get(c);
}
@Override
public void onDisplayObjective(@NotNull TabPlayer player, @NotNull Object packet) {
TAB.getInstance().getFeatureManager().onDisplayObjective(player,
((ClientboundSetDisplayObjectivePacket) packet).getSlot().ordinal(),
String.valueOf(((ClientboundSetDisplayObjectivePacket)packet).getObjectiveName()));
}
@Override
public void onObjective(@NotNull TabPlayer player, @NotNull Object packet) {
TAB.getInstance().getFeatureManager().onObjective(player,
((ClientboundSetObjectivePacket)packet).getMethod(),
((ClientboundSetObjectivePacket)packet).getObjectiveName());
}
@Override
public boolean isDisplayObjective(@NotNull Object packet) {
return packet instanceof ClientboundSetDisplayObjectivePacket;
}
@Override
public boolean isObjective(@NotNull Object packet) {
return packet instanceof ClientboundSetObjectivePacket;
}
@Override
public boolean isTeam(@NotNull Object packet) {
return packet instanceof ClientboundSetPlayerTeamPacket;
}
@Override
public boolean isPlayerInfo(@NotNull Object packet) {
return packet instanceof ClientboundPlayerInfoUpdatePacket;
}
@Override
@SneakyThrows
public void onPlayerInfo(@NotNull TabPlayer receiver, @NotNull Object packet0) {
ClientboundPlayerInfoUpdatePacket packet = (ClientboundPlayerInfoUpdatePacket) packet0;
EnumSet<ClientboundPlayerInfoUpdatePacket.Action> actions = packet.actions();
List<ClientboundPlayerInfoUpdatePacket.Entry> updatedList = new ArrayList<>();
for (ClientboundPlayerInfoUpdatePacket.Entry nmsData : packet.entries()) {
GameProfile profile = nmsData.profile();
Component displayName = nmsData.displayName();
int latency = nmsData.latency();
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME)) {
IChatBaseComponent newDisplayName = TAB.getInstance().getFeatureManager().onDisplayNameChange(receiver, nmsData.profileId());
if (newDisplayName != null) displayName = ((FabricTabPlayer)receiver).getPlatform().toComponent(newDisplayName, receiver.getVersion());
}
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY)) {
latency = TAB.getInstance().getFeatureManager().onLatencyChange(receiver, nmsData.profileId(), latency);
}
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER)) {
TAB.getInstance().getFeatureManager().onEntryAdd(receiver, nmsData.profileId(), profile.getName());
}
updatedList.add(new ClientboundPlayerInfoUpdatePacket.Entry(nmsData.profileId(), profile, nmsData.listed(), latency, nmsData.gameMode(), displayName, nmsData.chatSession()));
}
ReflectionUtils.getFields(ClientboundPlayerInfoUpdatePacket.class, List.class).get(0).set(packet, updatedList);
}
@Override
public boolean isLogin(@NotNull Object packet) {
return false;
}
@Override
@SneakyThrows
public void modifyPlayers(@NotNull Object teamPacket) {
if (TAB.getInstance().getNameTagManager() == null) return;
ClientboundSetPlayerTeamPacket packet = (ClientboundSetPlayerTeamPacket) teamPacket;
int action = ReflectionUtils.getInstanceFields(ClientboundSetPlayerTeamPacket.class, int.class).get(0).getInt(packet);
if (action == 1 || action == 2 || action == 4) return;
Collection<String> players = packet.getPlayers();
String teamName = packet.getName();
if (players == null) return;
//creating a new list to prevent NoSuchFieldException in minecraft packet encoder when a player is removed
Collection<String> newList = new ArrayList<>();
for (String entry : players) {
TabPlayer p = getPlayer(entry);
if (p == null) {
newList.add(entry);
continue;
}
Sorting sorting = TAB.getInstance().getFeatureManager().getFeature(TabConstants.Feature.SORTING);
String expectedTeam = sorting.getShortTeamName(p);
if (expectedTeam == null) {
newList.add(entry);
continue;
}
if (!((NameTag)TAB.getInstance().getNameTagManager()).getDisableChecker().isDisabledPlayer(p) &&
!TAB.getInstance().getNameTagManager().hasTeamHandlingPaused(p) && !teamName.equals(expectedTeam)) {
logTeamOverride(teamName, p.getName(), expectedTeam);
} else {
newList.add(entry);
}
}
ReflectionUtils.getFields(ClientboundSetPlayerTeamPacket.class, Collection.class).get(0).set(packet, newList);
}
}
@@ -0,0 +1,233 @@
package me.neznamy.tab.platforms.fabric.v1_20_4;
import com.mojang.authlib.properties.Property;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.fabric.*;
import me.neznamy.tab.shared.TAB;
import me.neznamy.tab.shared.backend.EntityData;
import me.neznamy.tab.shared.backend.Location;
import me.neznamy.tab.shared.chat.IChatBaseComponent;
import me.neznamy.tab.shared.features.injection.PipelineInjector;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.util.ReflectionUtils;
import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback;
import net.minecraft.SharedConstants;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.numbers.FixedFormat;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.*;
import net.minecraft.network.syncher.EntityDataSerializers;
import net.minecraft.network.syncher.SynchedEntityData;
import net.minecraft.server.level.ServerPlayer;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.level.GameType;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.storage.ServerLevelData;
import net.minecraft.world.phys.Vec3;
import net.minecraft.world.scores.*;
import net.minecraft.world.scores.criteria.ObjectiveCriteria;
import org.jetbrains.annotations.Nullable;
import java.util.*;
public class FabricTAB implements VersionLoader {
@Override
public List<String> getSupportedVersions() {
return Arrays.asList("1.20.3", "1.20.4");
}
@Override
public String getName(Level level) {
String path = level.dimension().location().getPath();
String dimensionSuffix = switch (path) {
case "overworld" -> ""; // No suffix for overworld
case "the_nether" -> "_nether";
default -> "_" + path; // End + default behavior for other dimensions created by mods
};
return ((ServerLevelData)level.getLevelData()).getLevelName() + dimensionSuffix;
}
@Override
public int getPing(ServerPlayer player) {
return player.connection.latency();
}
@Override
public void sendMessage(ServerPlayer player, Component message) {
player.sendSystemMessage(message);
}
@Override
public TabList.Skin propertyToSkin(Property property) {
return new TabList.Skin(property.value(), property.signature());
}
@Override
public void sendMessage(CommandSourceStack sender, Component message) {
sender.sendSystemMessage(message);
}
@Override
public String getServerVersion() {
return SharedConstants.getCurrentVersion().getName();
}
@Override
public double getMSPT() {
return (float) ((FabricPlatform)TAB.getInstance().getPlatform()).getServer().getAverageTickTimeNanos() / 1000000;
}
@Override
public Component deserialize(String string) {
return Component.Serializer.fromJson(string);
}
@Override
@SneakyThrows
public Packet<?> build(TabList.Action action, FabricTabList.Builder entry) {
if (action == FabricTabList.Action.REMOVE_PLAYER) {
return new ClientboundPlayerInfoRemovePacket(Collections.singletonList(entry.getId()));
}
EnumSet<ClientboundPlayerInfoUpdatePacket.Action> actions = action == FabricTabList.Action.ADD_PLAYER ?
EnumSet.allOf(ClientboundPlayerInfoUpdatePacket.Action.class) :
EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.valueOf(action.name()));
ClientboundPlayerInfoUpdatePacket packet = new ClientboundPlayerInfoUpdatePacket(actions, Collections.emptyList());
ReflectionUtils.getFields(ClientboundPlayerInfoUpdatePacket.class, List.class).get(0).set(packet,
Collections.singletonList(new ClientboundPlayerInfoUpdatePacket.Entry(
entry.getId(),
entry.createProfile(),
true,
entry.getLatency(),
GameType.byId(entry.getGameMode()),
entry.getDisplayName(),
null
)));
return packet;
}
@Override
public Level getLevel(ServerPlayer player) {
return player.level();
}
@Override
public boolean isSneaking(ServerPlayer player) {
return player.isCrouching();
}
@Override
public EntityData createDataWatcher(TabPlayer viewer, byte flags, String displayName, boolean nameVisible) {
return () -> java.util.Arrays.asList(
new SynchedEntityData.DataValue<>(0, EntityDataSerializers.BYTE, flags),
new SynchedEntityData.DataValue<>(2, EntityDataSerializers.OPTIONAL_COMPONENT,
Optional.of(((FabricTabPlayer)viewer).getPlatform().toComponent(IChatBaseComponent.optimizedComponent(displayName), viewer.getVersion()))),
new SynchedEntityData.DataValue<>(3, EntityDataSerializers.BOOLEAN, nameVisible),
new SynchedEntityData.DataValue<>(15, EntityDataSerializers.BYTE, (byte)16)
);
}
@Override
public Packet<?> spawnEntity(int entityId, UUID id, Object entityType, Location location) {
return new ClientboundAddEntityPacket(entityId, id, location.getX(), location.getY(), location.getZ(),
0, 0, (EntityType<?>) entityType, 0, Vec3.ZERO, 0);
}
@Override
@SuppressWarnings("unchecked")
public Packet<?> newEntityMetadata(int entityId, EntityData data) {
return new ClientboundSetEntityDataPacket(entityId, (List<SynchedEntityData.DataValue<?>>) data.build());
}
@Override
public boolean isSpawnPlayerPacket(Object packet) {
return packet instanceof ClientboundAddEntityPacket;
}
@Override
public int getSpawnedPlayer(Object spawnPacket) {
return ((ClientboundAddEntityPacket) spawnPacket).getId();
}
@Override
public int[] getDestroyedEntities(Object entityDestroyPacket) {
return ((ClientboundRemoveEntitiesPacket) entityDestroyPacket).getEntityIds().toIntArray();
}
@Override
public boolean isBundlePacket(Object packet) {
return packet instanceof ClientboundBundlePacket;
}
@Override
@SuppressWarnings("unchecked")
public Iterable<Object> getPackets(Object bundlePacket) {
return (Iterable<Object>) (Object) ((ClientboundBundlePacket)bundlePacket).subPackets();
}
@Override
public PipelineInjector createPipelineInjector() {
return new FabricPipelineInjector();
}
@Override
public void destroyEntities(FabricTabPlayer player, int... entities) {
player.sendPacket(new ClientboundRemoveEntitiesPacket(entities));
}
@Override
public void registerCommandCallback() {
CommandRegistrationCallback.EVENT.register((dispatcher, $, $$) -> new FabricTabCommand().onRegisterCommands(dispatcher));
}
@Override
public Packet<?> newHeaderFooter(Component header, Component footer) {
return new ClientboundTabListPacket(header, footer);
}
@Override
public Packet<?> setDisplaySlot(int slot, Objective objective) {
return new ClientboundSetDisplayObjectivePacket(DisplaySlot.values()[slot], objective);
}
@Override
public Objective newObjective(String name, Component displayName, ObjectiveCriteria.RenderType renderType,
@Nullable Component numberFormat) {
return new Objective(dummyScoreboard, name, ObjectiveCriteria.DUMMY, displayName, renderType, false,
numberFormat == null ? null : new FixedFormat(numberFormat));
}
@Override
public Packet<?> registerTeam(PlayerTeam team) {
return ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, true);
}
@Override
public Packet<?> unregisterTeam(PlayerTeam team) {
return ClientboundSetPlayerTeamPacket.createRemovePacket(team);
}
@Override
public Packet<?> updateTeam(PlayerTeam team) {
return ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(team, false);
}
@Override
public Packet<?> setScore(String objective, String scoreHolder, int score, Component displayName,
@Nullable Component numberFormat) {
return new ClientboundSetScorePacket(
scoreHolder,
objective,
score,
displayName,
numberFormat == null ? null : new FixedFormat(numberFormat)
);
}
@Override
public Packet<?> removeScore(String objective, String scoreHolder) {
return new ClientboundResetScorePacket(scoreHolder, objective);
}
}
+1 -1
View File
@@ -2,7 +2,7 @@
org.gradle.daemon=true
org.gradle.configureondemand=true
org.gradle.parallel=true
org.gradle.jvmargs=-Xms512M -Xmx4G
org.gradle.jvmargs=-Xms512M -Xmx8G
# Kotlin properties
kotlin.code.style=official
+17 -4
View File
@@ -9,7 +9,20 @@ val platforms = setOf(
rootProject.projects.bungeecord,
rootProject.projects.velocity,
rootProject.projects.sponge7,
rootProject.projects.sponge8,
rootProject.projects.sponge8
).map { it.dependencyProject }
val fabrics = setOf(
rootProject.projects.fabric,
rootProject.projects.fabric.v1152,
rootProject.projects.fabric.v1165,
rootProject.projects.fabric.v1171,
rootProject.projects.fabric.v1182,
rootProject.projects.fabric.v1192,
rootProject.projects.fabric.v1193,
rootProject.projects.fabric.v1201,
rootProject.projects.fabric.v1202,
rootProject.projects.fabric.v1204
).map { it.dependencyProject }
tasks {
@@ -27,9 +40,9 @@ tasks {
registerPlatform(it, it.tasks.named<ShadowJar>("shadowJar").get())
}
// Fabric is different
val fabricProject = rootProject.projects.fabric.dependencyProject
registerPlatform(fabricProject, fabricProject.tasks.named<org.gradle.jvm.tasks.Jar>("remapJar").get())
fabrics.forEach {
registerPlatform(it, it.tasks.named<org.gradle.jvm.tasks.Jar>("remapJar").get())
}
}
build {
dependsOn(shadowJar)
+11 -2
View File
@@ -5,12 +5,12 @@ dependencyResolutionManagement {
mavenCentral() // Netty, SnakeYaml, json-simple, Guava, Kyori event, bStats, AuthLib, LuckPerms
maven("https://repo.extendedclip.com/content/repositories/placeholderapi/") // PlaceholderAPI
maven("https://repo.viaversion.com/") // ViaVersion
maven("https://repo.codemc.org/repository/maven-public/") // RedisBungee, BungeeCord-proxy
maven("https://repo.opencollab.dev/maven-snapshots/") // Floodgate
maven("https://repo.purpurmc.org/snapshots") // Purpur
maven("https://repo.spongepowered.org/repository/maven-public/") // Sponge
maven("https://jitpack.io") // PremiumVanish, Vault, YamlAssist
maven("https://jitpack.io") // PremiumVanish, Vault, YamlAssist, RedisBungee
maven("https://repo.md-5.net/content/groups/public/") // LibsDisguises
maven("https://nexus.codecrafter47.dyndns.eu/content/repositories/public/") // BungeeCord-proxy // I feel bad for doing this
}
}
@@ -33,4 +33,13 @@ include(":bungeecord")
include(":sponge7")
include(":sponge8")
include(":fabric")
include(":fabric:v1_15_2")
include(":fabric:v1_16_5")
include(":fabric:v1_17_1")
include(":fabric:v1_18_2")
include(":fabric:v1_19_2")
include(":fabric:v1_19_3")
include(":fabric:v1_20_1")
include(":fabric:v1_20_2")
include(":fabric:v1_20_4")
include(":jar")
@@ -11,7 +11,6 @@ import me.neznamy.tab.api.placeholder.PlayerPlaceholder;
import me.neznamy.tab.shared.config.Configs;
import me.neznamy.tab.shared.config.mysql.MySQLUserConfiguration;
import me.neznamy.tab.shared.features.*;
import me.neznamy.tab.shared.features.alignedplayerlist.AlignedPlayerList;
import me.neznamy.tab.shared.features.bossbar.BossBarManagerImpl;
import me.neznamy.tab.shared.features.globalplayerlist.GlobalPlayerList;
import me.neznamy.tab.shared.features.injection.PipelineInjector;
@@ -25,6 +24,7 @@ import me.neznamy.tab.shared.features.types.*;
import me.neznamy.tab.shared.platform.TabPlayer;
import me.neznamy.tab.shared.proxy.ProxyPlatform;
import me.neznamy.tab.shared.proxy.ProxyTabPlayer;
import me.neznamy.tab.shared.proxy.message.outgoing.Unload;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -72,7 +72,7 @@ public class FeatureManager {
TAB.getInstance().getPlaceholderManager().getTabExpansion().unregisterExpansion();
if (TAB.getInstance().getPlatform() instanceof ProxyPlatform) {
for (TabPlayer player : TAB.getInstance().getOnlinePlayers()) {
((ProxyTabPlayer)player).sendPluginMessage("Unload");
((ProxyTabPlayer)player).sendPluginMessage(new Unload());
}
}
}
@@ -329,13 +329,12 @@ public class FeatureManager {
boolean scoreboard = configuration.getConfig().getBoolean("scoreboard.enabled", false);
boolean perWorldPlayerList = configuration.getConfig().getBoolean("per-world-playerlist.enabled", false);
boolean layout = configuration.getConfig().getBoolean("layout.enabled", false);
boolean yellowNumber = configuration.getConfig().getBoolean("yellow-number-in-tablist.enabled", true);
boolean yellowNumber = configuration.getConfig().getBoolean("playerlist-objective.enabled", true);
boolean belowName = configuration.getConfig().getBoolean("belowname-objective.enabled", false);
boolean teams = configuration.getConfig().getBoolean("scoreboard-teams.enabled", true);
boolean unlimitedTags = configuration.getConfig().getBoolean("scoreboard-teams.unlimited-nametag-mode.enabled", false);
boolean globalPlayerList = configuration.getConfig().getBoolean("global-playerlist.enabled", false);
boolean tablistFormatting = configuration.getConfig().getBoolean("tablist-name-formatting.enabled", true);
boolean alignedSuffix = configuration.getConfig().getBoolean("tablist-name-formatting.align-tabsuffix-on-the-right", false);
if (perWorldPlayerList && layout) TAB.getInstance().getMisconfigurationHelper().bothPerWorldPlayerListAndLayoutEnabled();
if (yellowNumber && layout) TAB.getInstance().getMisconfigurationHelper().layoutBreaksYellowNumber();
@@ -365,7 +364,7 @@ public class FeatureManager {
// Must be loaded after: Sorting
if (teams) {
if (unlimitedTags && minorVersion >= 8) {
if (unlimitedTags) {
NameTag unlimited = TAB.getInstance().getPlatform().getUnlimitedNameTags();
if (unlimited instanceof NameTagX) {
featureManager.registerFeature(TabConstants.Feature.UNLIMITED_NAME_TAGS, unlimited);
@@ -381,13 +380,7 @@ public class FeatureManager {
if (layout) featureManager.registerFeature(TabConstants.Feature.LAYOUT, new LayoutManagerImpl());
// Must be loaded after: Layout
if (tablistFormatting) {
if (alignedSuffix) {
featureManager.registerFeature(TabConstants.Feature.PLAYER_LIST, new AlignedPlayerList());
} else {
featureManager.registerFeature(TabConstants.Feature.PLAYER_LIST, new PlayerList());
}
}
if (tablistFormatting) featureManager.registerFeature(TabConstants.Feature.PLAYER_LIST, new PlayerList());
// Must be loaded after: PlayerList
if (globalPlayerList && TAB.getInstance().getServerVersion() == ProtocolVersion.PROXY) {
@@ -330,11 +330,15 @@ public class MisconfigurationHelper {
}
public void layoutBreaksYellowNumber() {
startupWarn("Layout feature breaks yellow-number feature, because it replaces real player with fake slots " +
"with different usernames for more reliable functionality. Disable yellow-number feature, as it will only look bad " +
startupWarn("Layout feature breaks playerlist-objective feature, because it replaces real player with fake slots " +
"with different usernames for more reliable functionality. Disable playerlist-objective feature, as it will only look bad " +
"and consume resources.");
}
public void invalidScoreboardSection(@NotNull String name) {
startupWarn("Invalid scoreboard section \"" + name + "\" with no value.");
}
/**
* Sends a startup warn message into console
*
@@ -20,6 +20,8 @@ import org.jetbrains.annotations.Nullable;
*/
public class Property {
private static long counter = 0;
/** Internal identifier for this text for PlaceholderAPI expansion, null if it should not be exposed */
@Nullable private final String name;
@@ -105,7 +107,7 @@ public class Property {
for (String placeholder : placeholders0) {
rawFormattedValue0 = replaceFirst(rawFormattedValue0, placeholder, "%s");
}
if (placeholders0.size() > 0 && rawFormattedValue0.contains("%")) {
if (!placeholders0.isEmpty() && rawFormattedValue0.contains("%")) {
int index = rawFormattedValue0.lastIndexOf('%');
if (rawFormattedValue0.length() == index+1 || rawFormattedValue0.charAt(index+1) != 's') {
StringBuilder sb = new StringBuilder(rawFormattedValue0);
@@ -245,12 +247,24 @@ public class Property {
* the viewer
* @return format for the viewer
*/
public @NotNull String getFormat(@Nullable TabPlayer viewer) {
public @NotNull String getFormat(@NotNull TabPlayer viewer) {
String format = lastReplacedValue;
// Direct placeholders
for (String identifier : relPlaceholders) {
RelationalPlaceholderImpl pl = (RelationalPlaceholderImpl) TAB.getInstance().getPlaceholderManager().getPlaceholder(identifier);
format = format.replace(pl.getIdentifier(), viewer == null ? "" : pl.getLastValue(viewer, owner));
format = format.replace(pl.getIdentifier(), pl.getLastValue(viewer, owner));
}
// Nested placeholders
for (String identifier : TAB.getInstance().getPlaceholderManager().detectPlaceholders(format)) {
if (!identifier.startsWith("%rel_")) continue;
RelationalPlaceholderImpl pl = (RelationalPlaceholderImpl) TAB.getInstance().getPlaceholderManager().getPlaceholder(identifier);
format = format.replace(pl.getIdentifier(), pl.getLastValue(viewer, owner));
}
return EnumChatFormat.color(format);
}
public static String randomName() {
return String.valueOf(counter++);
}
}
@@ -2,7 +2,6 @@ package me.neznamy.tab.shared;
import lombok.Getter;
import lombok.NonNull;
import lombok.Setter;
import org.jetbrains.annotations.NotNull;
/**
@@ -14,6 +13,8 @@ public enum ProtocolVersion {
UNKNOWN_SERVER_VERSION ("Unknown"),
UNKNOWN_CLIENT_VERSION ("Unknown"),
PROXY ("Proxy"),
V1_20_4 (765),
V1_20_3 (765),
V1_20_2 (764),
V1_20_1 (763),
V1_20 (763),
@@ -72,19 +73,21 @@ public enum ProtocolVersion {
V1_6_1 (73),
V1_5_2 (61),
V1_5_1 (60),
V1_5 (60);
V1_5 (60),
V1_4_7 (51),
V1_4_6 (51);
/** Newest MC version this plugin jar knows */
public static final ProtocolVersion LATEST_KNOWN_VERSION = ProtocolVersion.V1_20_2;
public static final ProtocolVersion LATEST_KNOWN_VERSION = ProtocolVersion.V1_20_3;
/** Version's network id found at https://wiki.vg/Protocol_version_numbers */
/** Version's network id found at <a href="https://wiki.vg/Protocol_version_numbers">wiki.vg</a> */
private final int networkId;
/** Version's minor version, such as 16 for 1.16.x. Allowing override to
* set minor version of UNKNOWN_SERVER_VERSION value to value from package to fix compatibility
* with server forks that set bukkit version field value to "Unknown".
*/
@Setter private int minorVersion;
private final int minorVersion;
/** Version's friendly name displayed in %player-version% placeholder */
@NotNull private final String friendlyName;
@@ -109,7 +112,7 @@ public enum ProtocolVersion {
*/
ProtocolVersion(@NotNull String friendlyName) {
this.networkId = 999;
this.minorVersion = 18;
this.minorVersion = 20;
this.friendlyName = friendlyName;
}
@@ -19,7 +19,7 @@ public class TabConstants {
public static final String NO_GROUP = "NONE";
public static final String DEFAULT_GROUP = "_DEFAULT_";
public static final String PLUGIN_MESSAGE_CHANNEL_NAME = "tab:bridge-4";
public static final String PLUGIN_MESSAGE_CHANNEL_NAME = "tab:bridge-5";
public static final String REDIS_CHANNEL_NAME = PLUGIN_NAME;
public static final String PIPELINE_HANDLER_NAME = PLUGIN_NAME;
@@ -80,7 +80,6 @@ public class TabConstants {
public static final String COMMAND_AUTOCOMPLETE = "tab.tabcomplete";
public static final String COMMAND_DATA_REMOVE = "tab.remove";
public static final String COMMAND_PROPERTY_CHANGE_PREFIX = "tab.change.";
public static final String COMMAND_WIDTH = "tab.width";
public static final String COMMAND_BOSSBAR_ANNOUNCE = "tab.announce.bar";
public static final String COMMAND_BOSSBAR_TOGGLE = "tab.bossbar.toggle";
@@ -130,13 +129,6 @@ public class TabConstants {
public static final String NAMETAG = "nametag";
public static final String BELOWNAME = "belowname";
public static final String SCOREBOARD_TITLE = "scoreboard-title";
public static final String BELOWNAME_NUMBER = "belowname-number";
public static final String BELOWNAME_TEXT = "belowname-text";
public static final String YELLOW_NUMBER = "yellow-number";
public static String bossbarTitle(String name) {
return "bossbar-title-" + name;
}
@@ -153,17 +145,9 @@ public class TabConstants {
return "bossbar-style-" + name;
}
public static String scoreboardPrefix(String scoreboard, int lineNumber) {
return scoreboard + "-" + lineNumber + "-prefix";
}
public static String scoreboardName(String scoreboard, int lineNumber) {
return scoreboard + "-" + lineNumber + "-name";
}
public static String scoreboardSuffix(String scoreboard, int lineNumber) {
return scoreboard + "-" + lineNumber + "-suffix";
}
}
/**
@@ -213,6 +197,10 @@ public class TabConstants {
}
public static String scoreboardLine(String scoreboard, int index) {
return "scoreboard-line-" + scoreboard + "-" + index;
}
public static String scoreboardScore(String scoreboard, int index) {
return "scoreboard-score-" + scoreboard + "-" + index;
}
@@ -123,7 +123,7 @@ public class ArmorStand {
public boolean calculateVisibility() {
if (manager.isArmorStandsAlwaysVisible()) return true;
if (owner.isDisguised() || manager.isOnBoat(owner)) return false;
return owner.getGamemode() != 3 && !manager.hasHiddenNameTag(owner) && property.get().length() > 0 &&
return owner.getGamemode() != 3 && !manager.hasHiddenNameTag(owner) && !property.get().isEmpty() &&
!manager.getUnlimitedDisableChecker().isDisabledPlayer(owner);
}
@@ -136,10 +136,10 @@ public class ArmorStand {
* @return {@code true} if it's empty, {@code false} if not
*/
protected boolean isNameVisiblyEmpty(@NotNull String displayName) {
if (displayName.length() == 0) return true;
if (displayName.isEmpty()) return true;
String rawText = displayName.contains(" ") ? displayName.replace(" ", "") : displayName;
if (!rawText.startsWith(EnumChatFormat.COLOR_STRING) && !rawText.startsWith("&") && !rawText.startsWith("#")) return false;
return IChatBaseComponent.fromColoredText(rawText).toRawText().length() == 0;
return IChatBaseComponent.fromColoredText(rawText).toRawText().isEmpty();
}
/**
@@ -144,7 +144,7 @@ public class BackendArmorStandManager implements ArmorStandManager {
double currentY = -0.26;
for (ArmorStand as : armorStandArray) {
if (as.isStaticOffset()) continue;
if (as.getProperty().get().length() != 0) {
if (!as.getProperty().get().isEmpty()) {
currentY += 0.26;
as.setOffset(currentY);
}
@@ -9,23 +9,23 @@ import org.json.simple.JSONObject;
@Data @NoArgsConstructor
public class ChatModifier {
@Nullable
private TextColor color;
@Nullable private TextColor color;
private boolean bold;
private boolean italic;
private boolean underlined;
private boolean strikethrough;
private boolean obfuscated;
@Nullable
private String font;
@Nullable private ClickEvent clickEvent;
@Nullable private String font;
public ChatModifier(@NotNull ChatModifier modifier) {
this.color = modifier.color == null ? null : new TextColor(modifier.color);
if (modifier.color != null) color = new TextColor(modifier.color);
this.bold = modifier.bold;
this.italic = modifier.italic;
this.underlined = modifier.underlined;
this.strikethrough = modifier.strikethrough;
this.obfuscated = modifier.obfuscated;
if (modifier.clickEvent != null) this.clickEvent = new ClickEvent(modifier.clickEvent.getAction(), modifier.clickEvent.getValue());
this.font = modifier.font;
}
@@ -39,6 +39,12 @@ public class ChatModifier {
if (underlined) json.put("underlined", true);
if (strikethrough) json.put("strikethrough", true);
if (obfuscated) json.put("obfuscated", true);
if (clickEvent != null) {
JSONObject click = new JSONObject();
click.put("action", clickEvent.getAction().name().toLowerCase());
click.put("value", clickEvent.getValue());
json.put("clickEvent", click);
}
if (font != null) json.put("font", font);
return json;
}
@@ -0,0 +1,23 @@
package me.neznamy.tab.shared.chat;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.jetbrains.annotations.NotNull;
@Data
@AllArgsConstructor
public class ClickEvent {
@NotNull private Action action;
@NotNull private String value;
public enum Action {
OPEN_URL,
OPEN_FILE,
RUN_COMMAND,
SUGGEST_COMMAND,
CHANGE_PAGE, //since 1.8
COPY_TO_CLIPBOARD //since 1.15
}
}
@@ -8,6 +8,7 @@ import org.jetbrains.annotations.Nullable;
/**
* An enum containing all possible legacy color codes and magic codes. Also contains handy color-related methods.
*/
@Getter
public enum EnumChatFormat {
BLACK('0', 0x000000),
@@ -43,22 +44,22 @@ public enum EnumChatFormat {
public static final String COLOR_STRING = String.valueOf(COLOR_CHAR);
/** Character representing the color or magic code */
@Getter private final char character;
private final char character;
/** Red value of this constant, 0 for magic codes */
@Getter private final short red;
private final short red;
/** Green value of this constant, 0 for magic codes */
@Getter private final short green;
private final short green;
/** Blue value of this constant, 0 for magic codes */
@Getter private final short blue;
private final short blue;
/** Color as a hex code, 0 for magic codes */
@Getter private final int hexCode;
private final int hexCode;
/** Color symbol followed by constant's character */
@Getter private final String format;
private final String format;
/**
* Constructs new color instance with given character and hex code
@@ -115,10 +116,10 @@ public enum EnumChatFormat {
* @return last used color code in given string or WHITE if nothing is found
*/
public static @NotNull EnumChatFormat lastColorsOf(@NotNull String string) {
if (string.length() == 0) return EnumChatFormat.WHITE;
if (string.isEmpty()) return EnumChatFormat.WHITE;
String legacyText = RGBUtils.getInstance().convertRGBtoLegacy(string);
String last = getLastColors(legacyText);
if (last.length() > 0) {
if (!last.isEmpty()) {
char c = last.toCharArray()[1];
for (EnumChatFormat e : VALUES) {
if (e.character == c) return e;
@@ -124,7 +124,7 @@ public class IChatBaseComponent {
* @return serialized string
*/
public @NotNull String toString(@NotNull ProtocolVersion clientVersion) {
if (extra == null && (text == null || text.length() == 0)) return EMPTY_COMPONENT;
if (extra == null && (text == null || text.isEmpty())) return EMPTY_COMPONENT;
targetVersion = clientVersion;
for (IChatBaseComponent child : getExtra()) {
child.targetVersion = clientVersion;
@@ -337,4 +337,9 @@ public class IChatBaseComponent {
public static @NotNull IChatBaseComponent optimizedComponent(@NotNull String text) {
return stringCache.get(text, null);
}
public static @Nullable IChatBaseComponent emptyToNullOptimizedComponent(@NotNull String text) {
if (text.isEmpty()) return null;
return optimizedComponent(text);
}
}
@@ -11,12 +11,15 @@ import org.jetbrains.annotations.NotNull;
public class MiniMessageFormat implements RGBFormatter {
@Override
public @NotNull String reformat(@NotNull String text) {
if (text.contains("<") && !text.contains(EnumChatFormat.COLOR_STRING)) {
try {
return LegacyComponentSerializer.legacySection().serialize(MiniMessage.miniMessage().deserialize(text));
} catch (Throwable ignored) {}
@NotNull
public String reformat(@NotNull String text) {
if (!text.contains("<")) return text; // User did not even attempt to use MiniMessage
String modified = text.replace(EnumChatFormat.WHITE.getFormat(), "<white>"); // Forced &f in scoreboard
if (modified.contains(EnumChatFormat.COLOR_STRING)) return text;
try {
return LegacyComponentSerializer.legacySection().serialize(MiniMessage.miniMessage().deserialize(modified));
} catch (Throwable ignored) {
return text;
}
return text;
}
}
Loaded 100 of 184 files, more files were not shown because too many files have changed in this diff. Show more