mirror of
https://github.com/NEZNAMY/TAB.git
synced 2026-11-12 20:22:01 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
484d46252b | ||
|
|
a2e3c67298 | ||
|
|
b2ea05d8dd | ||
|
|
1477f47962 | ||
|
|
273ce49c14 | ||
|
|
96b6790646 | ||
|
|
38227ec878 | ||
|
|
11da632972 | ||
|
|
3721b9fe22 | ||
|
|
ecb697e5fb | ||
|
|
c754772280 | ||
|
|
5f2b4ba0b3 | ||
|
|
0ab60becfc | ||
|
|
1dcfb7d6dc | ||
|
|
3c0b44b365 | ||
|
|
ec4aec50ef | ||
|
|
9c86cfe056 | ||
|
|
6bb2337276 | ||
|
|
4702ca255b | ||
|
|
ab47aa36cc | ||
|
|
4bb14c4a4e | ||
|
|
1dd75162f9 | ||
|
|
a2242e3c27 | ||
|
|
26ecf494a9 | ||
|
|
659af5d0b3 | ||
|
|
382a5ee7fc | ||
|
|
7364496844 | ||
|
|
d342316b87 | ||
|
|
b38141da88 | ||
|
|
98f80f7dae | ||
|
|
c134ec6bfe | ||
|
|
4e3f759243 | ||
|
|
a5f2e0157b | ||
|
|
fd21032e51 | ||
|
|
c4f5f9b7d3 | ||
|
|
6baf5a7068 | ||
|
|
d3460174de | ||
|
|
f9f19628ca | ||
|
|
04e48fea5d | ||
|
|
3e99e22fce | ||
|
|
0b244d5ff7 | ||
|
|
703f2ff316 | ||
|
|
34a2bbdbae | ||
|
|
cd8697c1a4 | ||
|
|
ed2dc2dd51 | ||
|
|
2dfdc1b993 | ||
|
|
6f2d7f1194 | ||
|
|
30009ef127 | ||
|
|
051a422331 | ||
|
|
fab6489e9c | ||
|
|
a448614e4a | ||
|
|
84ad874b2a | ||
|
|
19ba978291 | ||
|
|
e304469e55 | ||
|
|
7ce9e2592a | ||
|
|
681b812c7b | ||
|
|
ee1ab6785d | ||
|
|
57704b6622 | ||
|
|
7fe804e0fc | ||
|
|
a71529b641 | ||
|
|
4055173bdb | ||
|
|
3f8841a276 | ||
|
|
15c38851bc | ||
|
|
05030ae817 | ||
|
|
255fa11371 | ||
|
|
a074c0bf74 | ||
|
|
6ccd649fc4 | ||
|
|
21dbea07dd | ||
|
|
4c21d9d127 | ||
|
|
766002b079 | ||
|
|
2ce430ec41 | ||
|
|
cf2b151045 | ||
|
|
92216179df | ||
|
|
a2a95b8591 | ||
|
|
4a22dc111a | ||
|
|
5df7ecc245 | ||
|
|
d4ef520f26 | ||
|
|
e0f8737865 | ||
|
|
37b13a54a7 | ||
|
|
d9d28d31d0 | ||
|
|
66bf5da3f5 | ||
|
|
840c721afc | ||
|
|
859e705a48 | ||
|
|
7375070b21 | ||
|
|
3e2eda6a71 | ||
|
|
798d327dcb | ||
|
|
f5ca9139e6 | ||
|
|
f35e8cfa51 | ||
|
|
0f6953711b | ||
|
|
e2c5e6f9ac | ||
|
|
5ef5d6ddba | ||
|
|
5b19a5d798 | ||
|
|
bf804e8923 | ||
|
|
f47b8b5528 | ||
|
|
c353f6787a | ||
|
|
1b7d5c1a5c | ||
|
|
956456f157 | ||
|
|
49077bd798 |
No files matched your search
@@ -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
|
||||
@@ -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
@@ -4,7 +4,7 @@ plugins {
|
||||
|
||||
allprojects {
|
||||
group = "me.neznamy"
|
||||
version = "4.0.9"
|
||||
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(
|
||||
|
||||
@@ -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
|
||||
|
||||
+3
-6
@@ -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,7 +25,7 @@ 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();
|
||||
@@ -33,7 +33,7 @@ public abstract class HeaderFooter {
|
||||
"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 to version with proper support for your server version.");
|
||||
} else if (BukkitReflection.getMinorVersion() >= 8) {
|
||||
} 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. " +
|
||||
|
||||
+20
-17
@@ -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)
|
||||
|
||||
+70
-22
@@ -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
|
||||
}
|
||||
}
|
||||
+413
-328
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -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.");
|
||||
}
|
||||
}
|
||||
@@ -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,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);
|
||||
}
|
||||
|
||||
|
||||
+9
-2
@@ -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 String 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(IChatBaseComponent.optimizedComponent(title), version), healthDisplay, action);
|
||||
} else {
|
||||
return new ScoreboardObjective(objective, jsonOrRaw(title, 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;
|
||||
}
|
||||
}
|
||||
+28
-37
@@ -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;
|
||||
}
|
||||
}
|
||||
+66
-39
@@ -1,12 +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;
|
||||
|
||||
@@ -22,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,
|
||||
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,
|
||||
"", // 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,
|
||||
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()))
|
||||
));
|
||||
}
|
||||
|
||||
@@ -68,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])
|
||||
));
|
||||
}
|
||||
|
||||
@@ -94,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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+6
-3
@@ -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
-2
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
+6
-2
@@ -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
-2
@@ -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
@@ -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,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;
|
||||
@@ -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))
|
||||
}
|
||||
+149
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
+149
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
+137
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
+137
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
+137
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
+17
-10
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
+142
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
+142
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
+142
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+1
-1
@@ -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
Reference in new issue
Block a user