mirror of
https://github.com/NEZNAMY/TAB.git
synced 2026-11-12 20:22:01 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
71a19c63d8 | ||
|
|
657327e7ee | ||
|
|
efeea7cf0f | ||
|
|
7a4637368d | ||
|
|
2e6ccd078b | ||
|
|
827197f916 | ||
|
|
c8454ec0c1 | ||
|
|
7127a77d92 | ||
|
|
b23912b52f | ||
|
|
4b21148a03 | ||
|
|
2d5ef95e54 | ||
|
|
b5e2d376dd | ||
|
|
c40b337edc | ||
|
|
f8583642a8 | ||
|
|
707f28df16 | ||
|
|
dd78da089d | ||
|
|
c4ea65ed70 | ||
|
|
aabba806c4 | ||
|
|
88167fc42d | ||
|
|
e4d7005e46 | ||
|
|
5ee27f7ed7 | ||
|
|
9a4492c401 | ||
|
|
39a2aee5a0 | ||
|
|
5184ef170f | ||
|
|
29a35cf1cc | ||
|
|
f26516ecae | ||
|
|
f26a7ad2b1 | ||
|
|
96e0a29573 | ||
|
|
f30d667ca8 | ||
|
|
8db7a1d6d9 | ||
|
|
d285a0512e | ||
|
|
256d0d5c84 | ||
|
|
b808e1f9d1 | ||
|
|
07cd781ec1 | ||
|
|
9115b687f4 | ||
|
|
7c51377203 | ||
|
|
193cd02072 | ||
|
|
dbb6d91f0c | ||
|
|
a82714e811 | ||
|
|
bcd1bbac62 |
No files matched your search
@@ -3,7 +3,7 @@ plugins {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("com.github.johnrengelman:shadow:8.1.1")
|
||||
implementation("io.freefair.gradle:lombok-plugin:8.6")
|
||||
implementation("fabric-loom:fabric-loom.gradle.plugin:1.8-SNAPSHOT")
|
||||
implementation("com.gradleup.shadow:shadow-gradle-plugin:8.3.6")
|
||||
implementation("io.freefair.gradle:lombok-plugin:8.13")
|
||||
implementation("dev.architectury.loom:dev.architectury.loom.gradle.plugin:1.10-SNAPSHOT")
|
||||
}
|
||||
@@ -3,6 +3,9 @@ rootProject.name = "build-logic"
|
||||
dependencyResolutionManagement {
|
||||
repositories {
|
||||
maven("https://maven.fabricmc.net/")
|
||||
maven("https://maven.architectury.dev/")
|
||||
maven("https://maven.minecraftforge.net/")
|
||||
maven("https://maven.neoforged.net/releases/")
|
||||
gradlePluginPortal()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
plugins {
|
||||
id("tab.standard-conventions")
|
||||
id("com.github.johnrengelman.shadow")
|
||||
id("com.gradleup.shadow")
|
||||
}
|
||||
|
||||
tasks.withType<Zip>().configureEach {
|
||||
|
||||
+8
-4
@@ -4,23 +4,27 @@ plugins {
|
||||
|
||||
allprojects {
|
||||
group = "me.neznamy"
|
||||
version = "5.1.0"
|
||||
version = "5.2.0"
|
||||
description = "An all-in-one solution that works"
|
||||
|
||||
ext.set("id", "tab")
|
||||
ext.set("website", "https://github.com/NEZNAMY/TAB")
|
||||
ext.set("author", "NEZNAMY")
|
||||
ext.set("credits", "Joseph T. McQuigg (JT122406)")
|
||||
}
|
||||
|
||||
val platforms = setOf(
|
||||
projects.bukkit,
|
||||
projects.bukkit.paper,
|
||||
projects.bukkit.v18R3,
|
||||
projects.bukkit.v112R1,
|
||||
projects.bungeecord,
|
||||
projects.component,
|
||||
projects.velocity,
|
||||
projects.sponge7,
|
||||
projects.sponge8,
|
||||
projects.fabric
|
||||
projects.sponge,
|
||||
projects.fabric,
|
||||
projects.neoforge,
|
||||
projects.forge
|
||||
).map { it.dependencyProject }
|
||||
|
||||
val special = setOf(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
plugins {
|
||||
id("io.papermc.paperweight.userdev") version "2.0.0-beta.8"
|
||||
id("io.papermc.paperweight.userdev") version "2.0.0-beta.16"
|
||||
}
|
||||
|
||||
repositories {
|
||||
@@ -9,11 +9,11 @@ repositories {
|
||||
maven("https://oss.sonatype.org/content/repositories/snapshots")
|
||||
}
|
||||
|
||||
val version = "1.21.4-R0.1-SNAPSHOT"
|
||||
val version = "1.21.5-R0.1-SNAPSHOT"
|
||||
|
||||
dependencies {
|
||||
implementation(projects.bukkit)
|
||||
paperweight.paperDevBundle(version)
|
||||
paperweight.paperDevBundle("1.21.5-no-moonrise-SNAPSHOT") // Just temporary
|
||||
compileOnly("io.papermc.paper:paper-api:${version}")
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ import org.jetbrains.annotations.NotNull;
|
||||
/**
|
||||
* Component converter using direct mojang-mapped code.
|
||||
*/
|
||||
@SuppressWarnings("unused") // Used via reflection
|
||||
public class PaperComponentConverter extends ComponentConverter {
|
||||
|
||||
@Override
|
||||
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
package me.neznamy.tab.platforms.paper;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import lombok.Getter;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.converter.ComponentConverter;
|
||||
import me.neznamy.tab.platforms.bukkit.provider.ImplementationProvider;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import me.neznamy.tab.shared.util.function.FunctionWithException;
|
||||
import org.bukkit.craftbukkit.entity.CraftPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Implementation provider using direct Mojang-mapped NMS code (1.20.5+).
|
||||
*/
|
||||
@Getter
|
||||
public class PaperImplementationProvider implements ImplementationProvider {
|
||||
|
||||
@NotNull
|
||||
private final ComponentConverter componentConverter = new PaperComponentConverter();
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Scoreboard newScoreboard(@NotNull BukkitTabPlayer player) {
|
||||
return new PaperPacketScoreboard(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public TabList newTabList(@NotNull BukkitTabPlayer player) {
|
||||
return new PaperPacketTabList(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public FunctionWithException<BukkitTabPlayer, Channel> getChannelFunction() {
|
||||
return player -> ((CraftPlayer)player.getPlayer()).getHandle().connection.connection.channel;
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
package me.neznamy.tab.platforms.paper;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.shared.util.function.FunctionWithException;
|
||||
import org.bukkit.craftbukkit.entity.CraftPlayer;
|
||||
|
||||
/**
|
||||
* Loader for Paper to speed up functions by using direct NMS code instead of reflection.
|
||||
*/
|
||||
@SuppressWarnings("unused") // Via reflection
|
||||
public class PaperLoader {
|
||||
|
||||
/** Channel getter using direct NMS code */
|
||||
public static final FunctionWithException<BukkitTabPlayer, Channel> getChannel =
|
||||
player -> ((CraftPlayer)player.getPlayer()).getHandle().connection.connection.channel;
|
||||
}
|
||||
+11
-13
@@ -18,30 +18,21 @@ import org.bukkit.craftbukkit.entity.CraftPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Scoreboard implementation using direct mojang-mapped code.
|
||||
*/
|
||||
@SuppressWarnings("unused") // Used via reflection
|
||||
public class PaperPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
|
||||
|
||||
private static final ChatFormatting[] formats = ChatFormatting.values();
|
||||
private static final net.minecraft.world.scores.Team.CollisionRule[] collisions = net.minecraft.world.scores.Team.CollisionRule.values();
|
||||
private static final net.minecraft.world.scores.Team.Visibility[] visibilities = net.minecraft.world.scores.Team.Visibility.values();
|
||||
private static final Scoreboard dummyScoreboard = new Scoreboard();
|
||||
private static final Field method;
|
||||
private static final Field players;
|
||||
|
||||
static {
|
||||
try {
|
||||
method = ReflectionUtils.setAccessible(ClientboundSetPlayerTeamPacket.class.getDeclaredField("method"));
|
||||
players = ReflectionUtils.setAccessible(ClientboundSetPlayerTeamPacket.class.getDeclaredField("players"));
|
||||
} catch (NoSuchFieldException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
private static final Field method = ReflectionUtils.getInstanceFields(ClientboundSetPlayerTeamPacket.class, int.class).getFirst();
|
||||
private static final Field players = ReflectionUtils.getOnlyField(ClientboundSetPlayerTeamPacket.class, Collection.class);
|
||||
|
||||
/**
|
||||
* Constructs new instance with given player.
|
||||
@@ -66,7 +57,14 @@ public class PaperPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
|
||||
);
|
||||
objective.setPlatformObjective(obj);
|
||||
sendPacket(new ClientboundSetObjectivePacket(obj, ObjectiveAction.REGISTER));
|
||||
sendPacket(new ClientboundSetDisplayObjectivePacket(net.minecraft.world.scores.DisplaySlot.values()[objective.getDisplaySlot().ordinal()], obj));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDisplaySlot(@NonNull Objective objective) {
|
||||
sendPacket(new ClientboundSetDisplayObjectivePacket(
|
||||
net.minecraft.world.scores.DisplaySlot.values()[objective.getDisplaySlot().ordinal()],
|
||||
(net.minecraft.world.scores.Objective) objective.getPlatformObjective()
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -26,7 +26,6 @@ import java.util.*;
|
||||
/**
|
||||
* TabList implementation using direct mojang-mapped code.
|
||||
*/
|
||||
@SuppressWarnings("unused") // Used via reflection
|
||||
public class PaperPacketTabList extends TrackedTabList<BukkitTabPlayer> {
|
||||
|
||||
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> addPlayer = EnumSet.allOf(ClientboundPlayerInfoUpdatePacket.Action.class);
|
||||
|
||||
@@ -3,15 +3,11 @@ package me.neznamy.tab.platforms.bukkit;
|
||||
import io.netty.channel.Channel;
|
||||
import lombok.Setter;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
|
||||
import me.neznamy.tab.shared.features.injection.NettyPipelineInjector;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import me.neznamy.tab.shared.util.function.FunctionWithException;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
/**
|
||||
* Pipeline injection for Bukkit 1.8+.
|
||||
*/
|
||||
@@ -28,33 +24,6 @@ public class BukkitPipelineInjector extends NettyPipelineInjector {
|
||||
super("packet_handler");
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to load required classes, fields and methods and marks class as available.
|
||||
* If something fails, an error message is printed and class is not marked as available.
|
||||
*/
|
||||
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");
|
||||
Field PLAYER_CONNECTION = ReflectionUtils.getOnlyField(EntityPlayer, PlayerConnection);
|
||||
Field NETWORK_MANAGER;
|
||||
if (BukkitReflection.is1_20_2Plus()) {
|
||||
NETWORK_MANAGER = ReflectionUtils.getOnlyField(PlayerConnection.getSuperclass(), NetworkManager);
|
||||
} else {
|
||||
NETWORK_MANAGER = ReflectionUtils.getOnlyField(PlayerConnection, NetworkManager);
|
||||
}
|
||||
Field CHANNEL = ReflectionUtils.getOnlyField(NetworkManager, Channel.class);
|
||||
getChannel = player -> (Channel) CHANNEL.get(NETWORK_MANAGER.get(PLAYER_CONNECTION.get(player.getHandle())));
|
||||
} catch (Exception e) {
|
||||
BukkitUtils.compatibilityError(e, "network channel injection", null,
|
||||
"Anti-override for tablist & nametags not working",
|
||||
"Compatibility with nickname plugins changing player names will not work",
|
||||
"Scoreboard will not be checking for other plugins");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if pipeline injection is available, {@code false} if not.
|
||||
*
|
||||
|
||||
@@ -7,6 +7,7 @@ import me.neznamy.tab.shared.util.function.FunctionWithException;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
@@ -23,6 +24,7 @@ public class BukkitReflection {
|
||||
public static final Method CraftPlayer_getHandle = getHandle();
|
||||
|
||||
/** Server version data */
|
||||
@Getter
|
||||
private static final ServerVersion serverVersion = detectServerVersion();
|
||||
|
||||
/** Flag determining whether the server version is at least 1.20.2 or not */
|
||||
@@ -59,11 +61,12 @@ public class BukkitReflection {
|
||||
ClassLoader loader = BukkitReflection.class.getClassLoader();
|
||||
classFunction = name -> loader.loadClass("net.minecraft.server." + serverPackage + "." + name);
|
||||
}
|
||||
return new ServerVersion(classFunction, minorVersion, serverPackage);
|
||||
} else {
|
||||
// Paper without CB relocation
|
||||
minorVersion = Integer.parseInt(Bukkit.getBukkitVersion().split("-")[0].split("\\.")[1]);
|
||||
return new ServerVersion(classFunction, minorVersion, null);
|
||||
}
|
||||
return new ServerVersion(classFunction, minorVersion);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -102,9 +105,12 @@ public class BukkitReflection {
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@Getter
|
||||
private static class ServerVersion {
|
||||
public static class ServerVersion {
|
||||
|
||||
private final FunctionWithException<String, Class<?>> getClass;
|
||||
private final int minorVersion;
|
||||
|
||||
@Nullable
|
||||
private final String serverPackage;
|
||||
}
|
||||
}
|
||||
+47
-165
@@ -1,7 +1,5 @@
|
||||
package me.neznamy.tab.platforms.bukkit.platform;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import io.netty.channel.Channel;
|
||||
import lombok.Getter;
|
||||
import lombok.SneakyThrows;
|
||||
import me.clip.placeholderapi.PlaceholderAPI;
|
||||
@@ -15,14 +13,9 @@ import me.neznamy.tab.platforms.bukkit.header.*;
|
||||
import me.neznamy.tab.platforms.bukkit.hook.BukkitPremiumVanishHook;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.PingRetriever;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.converter.ComponentConverter;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.converter.LegacyComponentConverter;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.converter.ModerateComponentConverter;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.converter.ModernComponentConverter;
|
||||
import me.neznamy.tab.platforms.bukkit.scoreboard.BukkitScoreboard;
|
||||
import me.neznamy.tab.platforms.bukkit.scoreboard.PaperScoreboard;
|
||||
import me.neznamy.tab.platforms.bukkit.scoreboard.packet.PacketScoreboard;
|
||||
import me.neznamy.tab.platforms.bukkit.tablist.*;
|
||||
import me.neznamy.tab.platforms.bukkit.provider.BukkitImplementationProvider;
|
||||
import me.neznamy.tab.platforms.bukkit.provider.ImplementationProvider;
|
||||
import me.neznamy.tab.platforms.bukkit.provider.ViaVersionImplementationProvider;
|
||||
import me.neznamy.tab.shared.GroupManager;
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
@@ -33,7 +26,6 @@ import me.neznamy.tab.shared.features.PlaceholderManagerImpl;
|
||||
import me.neznamy.tab.shared.features.injection.PipelineInjector;
|
||||
import me.neznamy.tab.shared.features.types.TabFeature;
|
||||
import me.neznamy.tab.shared.hook.LuckPermsHook;
|
||||
import me.neznamy.tab.shared.hook.ViaVersionHook;
|
||||
import me.neznamy.tab.shared.placeholders.expansion.EmptyTabExpansion;
|
||||
import me.neznamy.tab.shared.placeholders.expansion.TabExpansion;
|
||||
import me.neznamy.tab.shared.placeholders.types.PlayerPlaceholderImpl;
|
||||
@@ -43,10 +35,8 @@ import me.neznamy.tab.shared.platform.TabList;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import me.neznamy.tab.shared.platform.impl.AdventureBossBar;
|
||||
import me.neznamy.tab.shared.platform.impl.DummyBossBar;
|
||||
import me.neznamy.tab.shared.platform.impl.DummyScoreboard;
|
||||
import me.neznamy.tab.shared.util.PerformanceUtil;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import me.neznamy.tab.shared.util.function.FunctionWithException;
|
||||
import net.kyori.adventure.audience.Audience;
|
||||
import net.milkbowl.vault.chat.Chat;
|
||||
import net.milkbowl.vault.permission.Permission;
|
||||
@@ -62,10 +52,8 @@ import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
@@ -94,25 +82,24 @@ public class BukkitPlatform implements BackendPlatform {
|
||||
/** Detection for presence of Paper's MSPT getter */
|
||||
private final boolean paperMspt = ReflectionUtils.methodExists(Bukkit.class, "getAverageTickTime");
|
||||
|
||||
/** Component converter from TAB to minecraft components */
|
||||
@Nullable
|
||||
private final ComponentConverter componentConverter = findComponentConverter();
|
||||
|
||||
/** Provider for scoreboard implementation */
|
||||
@NotNull
|
||||
private final FunctionWithException<BukkitTabPlayer, Scoreboard> scoreboardProvider = findScoreboardProvider();
|
||||
|
||||
/** Provider for tablist implementation */
|
||||
@NotNull
|
||||
private final FunctionWithException<BukkitTabPlayer, TabList> tablistProvider = findTablistProvider();
|
||||
|
||||
/** Header/footer implementation */
|
||||
@Getter
|
||||
@NotNull
|
||||
private final HeaderFooter headerFooter = findHeaderFooter();
|
||||
private final HeaderFooter headerFooter;
|
||||
|
||||
/** Flag tracking if direct mojang-mapped code can be used or not */
|
||||
private final boolean canUseDirectNMS = BukkitReflection.isMojangMapped() && serverVersion == ProtocolVersion.V1_21_4;
|
||||
private final boolean canUseDirectNMS = BukkitReflection.isMojangMapped() &&
|
||||
(serverVersion == ProtocolVersion.V1_21_4 || serverVersion == ProtocolVersion.V1_21_5);
|
||||
|
||||
/** Implementation for creating new instances using content available on the server */
|
||||
@NotNull
|
||||
private final ImplementationProvider serverImplementationProvider;
|
||||
|
||||
/** Implementation for sending new content to new players on old servers */
|
||||
@Nullable
|
||||
private final ViaVersionImplementationProvider viaVersionImplementationProvider =
|
||||
ReflectionUtils.classExists("com.viaversion.viaversion.protocols.v1_20_2to1_20_3.Protocol1_20_2To1_20_3") ?
|
||||
new ViaVersionImplementationProvider(serverVersion) : null;
|
||||
|
||||
/**
|
||||
* Constructs new instance with given plugin.
|
||||
@@ -121,7 +108,6 @@ public class BukkitPlatform implements BackendPlatform {
|
||||
* Plugin
|
||||
*/
|
||||
@SneakyThrows
|
||||
@SuppressWarnings("unchecked")
|
||||
public BukkitPlatform(@NotNull JavaPlugin plugin) {
|
||||
this.plugin = plugin;
|
||||
long time = System.currentTimeMillis();
|
||||
@@ -134,105 +120,27 @@ public class BukkitPlatform implements BackendPlatform {
|
||||
if (Bukkit.getPluginManager().isPluginEnabled("PremiumVanish")) {
|
||||
new BukkitPremiumVanishHook().register();
|
||||
}
|
||||
PingRetriever.tryLoad();
|
||||
if (BukkitReflection.getMinorVersion() >= 8) {
|
||||
if (canUseDirectNMS) {
|
||||
BukkitPipelineInjector.setGetChannel((FunctionWithException<BukkitTabPlayer, Channel>) Class.forName("me.neznamy.tab.platforms.paper.PaperLoader").getDeclaredField("getChannel").get(null));
|
||||
} else {
|
||||
BukkitPipelineInjector.tryLoad();
|
||||
}
|
||||
if (canUseDirectNMS) {
|
||||
serverImplementationProvider = (ImplementationProvider) Class.forName("me.neznamy.tab.platforms.paper.PaperImplementationProvider").getConstructor().newInstance();
|
||||
} else if ("v1_12_R1".equals(BukkitReflection.getServerVersion().getServerPackage())) {
|
||||
serverImplementationProvider = (ImplementationProvider) Class.forName("me.neznamy.tab.platforms.bukkit.v1_12_R1.NMSImplementationProvider").getConstructor().newInstance();
|
||||
} else if ("v1_8_R3".equals(BukkitReflection.getServerVersion().getServerPackage())) {
|
||||
serverImplementationProvider = (ImplementationProvider) Class.forName("me.neznamy.tab.platforms.bukkit.v1_8_R3.NMSImplementationProvider").getConstructor().newInstance();
|
||||
} else {
|
||||
serverImplementationProvider = new BukkitImplementationProvider();
|
||||
}
|
||||
headerFooter = findHeaderFooter();
|
||||
PingRetriever.tryLoad();
|
||||
BukkitPipelineInjector.setGetChannel(serverImplementationProvider.getChannelFunction());
|
||||
BukkitUtils.sendCompatibilityMessage();
|
||||
Bukkit.getConsoleSender().sendMessage("[TAB] §7Loaded NMS hook in " + (System.currentTimeMillis()-time) + "ms");
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@SneakyThrows
|
||||
private FunctionWithException<BukkitTabPlayer, Scoreboard> findScoreboardProvider() {
|
||||
final FunctionWithException<BukkitTabPlayer, Scoreboard> base;
|
||||
if (ViaVersionHook.getInstance().isInstalled()) {
|
||||
base = ViaVersionPlatform.findScoreboardProvider(serverVersion);
|
||||
} else {
|
||||
base = FunctionWithException.empty();
|
||||
}
|
||||
if (canUseDirectNMS) {
|
||||
Constructor<?> constructor = Class.forName("me.neznamy.tab.platforms.paper.PaperPacketScoreboard").getConstructor(BukkitTabPlayer.class);
|
||||
return base.fallback(player -> (Scoreboard) constructor.newInstance(player));
|
||||
}
|
||||
try {
|
||||
if (BukkitReflection.getMinorVersion() >= 7) Objects.requireNonNull(componentConverter);
|
||||
PacketScoreboard.load();
|
||||
return base.fallback(PacketScoreboard::new);
|
||||
} catch (Exception e) {
|
||||
if (PaperScoreboard.isAvailable()) {
|
||||
BukkitUtils.compatibilityError(e, "Scoreboards", "Paper API", "Compatibility with other plugins being reduced");
|
||||
return base.fallback(PaperScoreboard::new);
|
||||
} else if (BukkitScoreboard.isAvailable()) {
|
||||
List<String> missingFeatures = Lists.newArrayList(
|
||||
"Compatibility with other plugins being reduced",
|
||||
"Features receiving new artificial character limits"
|
||||
);
|
||||
if (BukkitReflection.is1_20_3Plus()) {
|
||||
missingFeatures.add("1.20.3+ visuals not working due to lack of API"); // soontm?
|
||||
}
|
||||
BukkitUtils.compatibilityError(e, "Scoreboards", "Bukkit API", missingFeatures.toArray(new String[0]));
|
||||
return base.fallback(BukkitScoreboard::new);
|
||||
} else if (BukkitReflection.getMinorVersion() >= 5) {
|
||||
BukkitUtils.compatibilityError(e, "Scoreboards", null,
|
||||
"Scoreboard feature will not work",
|
||||
"Belowname feature will not work",
|
||||
"Player objective feature will not work",
|
||||
"Scoreboard teams feature will not work (nametags & sorting)");
|
||||
}
|
||||
}
|
||||
return base.fallback(DummyScoreboard::new);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@SneakyThrows
|
||||
private FunctionWithException<BukkitTabPlayer, TabList> findTablistProvider() {
|
||||
final FunctionWithException<BukkitTabPlayer, TabList> base;
|
||||
if (ViaVersionHook.getInstance().isInstalled()) {
|
||||
base = ViaVersionPlatform.findTablistProvider(serverVersion);
|
||||
} else {
|
||||
base = FunctionWithException.empty();
|
||||
}
|
||||
if (canUseDirectNMS) {
|
||||
Constructor<?> constructor = Class.forName("me.neznamy.tab.platforms.paper.PaperPacketTabList").getConstructor(BukkitTabPlayer.class);
|
||||
return base.fallback(player -> (TabList) constructor.newInstance(player));
|
||||
}
|
||||
try {
|
||||
if (ReflectionUtils.classExists("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket")) {
|
||||
// 1.19.3+
|
||||
Objects.requireNonNull(componentConverter);
|
||||
PacketTabList1193.loadNew();
|
||||
return base.fallback(PacketTabList1193::new);
|
||||
} else if (BukkitReflection.getMinorVersion() >= 8) {
|
||||
// 1.8 - 1.19.2
|
||||
Objects.requireNonNull(componentConverter);
|
||||
PacketTabList18.load();
|
||||
return base.fallback(PacketTabList18::new);
|
||||
} else {
|
||||
// 1.7.10 and lower
|
||||
PacketTabList17.load();
|
||||
return base.fallback(PacketTabList17::new);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
BukkitUtils.compatibilityError(e, "tablist entry management", "Bukkit API",
|
||||
"Layout feature will not work",
|
||||
"Prevent-spectator-effect feature will not work",
|
||||
"Ping spoof feature will not work",
|
||||
"Tablist formatting missing anti-override",
|
||||
"Tablist formatting not supporting relational placeholders");
|
||||
return base.fallback(BukkitTabList::new);
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private HeaderFooter findHeaderFooter() {
|
||||
if (BukkitReflection.getMinorVersion() >= 8) {
|
||||
try {
|
||||
Objects.requireNonNull(componentConverter);
|
||||
Objects.requireNonNull(serverImplementationProvider.getComponentConverter());
|
||||
return new PacketHeaderFooter();
|
||||
} catch (Exception e) {
|
||||
if (PaperHeaderFooter.isAvailable()) return new PaperHeaderFooter();
|
||||
@@ -250,38 +158,6 @@ public class BukkitPlatform implements BackendPlatform {
|
||||
return new DummyHeaderFooter();
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to load component converter.
|
||||
*
|
||||
* @return Instance or {@code null} if not available
|
||||
*/
|
||||
@Nullable
|
||||
@SneakyThrows
|
||||
public ComponentConverter findComponentConverter() {
|
||||
if (canUseDirectNMS) {
|
||||
return (ComponentConverter) Class.forName("me.neznamy.tab.platforms.paper.PaperComponentConverter").getConstructor().newInstance();
|
||||
}
|
||||
try {
|
||||
if (BukkitReflection.getMinorVersion() >= 19) {
|
||||
// 1.19+
|
||||
return new ModernComponentConverter();
|
||||
} else if (BukkitReflection.getMinorVersion() >= 16) {
|
||||
// 1.16 - 1.18.2
|
||||
return new ModerateComponentConverter();
|
||||
} else {
|
||||
// 1.7 - 1.15.2
|
||||
return new LegacyComponentConverter();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Bukkit.getConsoleSender().sendMessage("§c[TAB] Failed to initialize converter from TAB components to Minecraft components. " +
|
||||
"This will negatively impact most features, see below.");
|
||||
if (BukkitUtils.PRINT_EXCEPTIONS) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadPlayers() {
|
||||
for (Player p : getOnlinePlayers()) {
|
||||
@@ -338,19 +214,17 @@ public class BukkitPlatform implements BackendPlatform {
|
||||
}
|
||||
PlaceholderManagerImpl pl = TAB.getInstance().getPlaceholderManager();
|
||||
if (identifier.startsWith("%rel_")) {
|
||||
// relational placeholder
|
||||
pl.registerRelationalPlaceholder(identifier, (viewer, target) ->
|
||||
//relational placeholder
|
||||
TAB.getInstance().getPlaceholderManager().registerRelationalPlaceholder(identifier, (viewer, target) ->
|
||||
PlaceholderAPI.setRelationalPlaceholders((Player) viewer.getPlayer(), (Player) target.getPlayer(), identifier));
|
||||
} else if (identifier.startsWith("%sync:")) {
|
||||
registerSyncPlaceholder(identifier);
|
||||
} else if (identifier.contains("{") && identifier.contains("}")) {
|
||||
// has nested bracket placeholders
|
||||
pl.registerPlayerPlaceholder(identifier, p -> PlaceholderAPI.setPlaceholders((Player) p.getPlayer(), PlaceholderAPI.setBracketPlaceholders((Player) p.getPlayer(), identifier)));
|
||||
} else if (identifier.startsWith("%server_")) {
|
||||
// placeholder with the same output for all players, register as server for better performance
|
||||
pl.registerServerPlaceholder(identifier, () -> PlaceholderAPI.setPlaceholders(null, identifier));
|
||||
TAB.getInstance().getPlaceholderManager().registerServerPlaceholder(identifier,
|
||||
() -> PlaceholderAPI.setPlaceholders(null, identifier));
|
||||
} else {
|
||||
pl.registerPlayerPlaceholder(identifier, p -> PlaceholderAPI.setPlaceholders((Player) p.getPlayer(), identifier));
|
||||
TAB.getInstance().getPlaceholderManager().registerPlayerPlaceholder(identifier,
|
||||
p -> PlaceholderAPI.setPlaceholders((Player) p.getPlayer(), identifier));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -366,7 +240,7 @@ public class BukkitPlatform implements BackendPlatform {
|
||||
ppl[0] = TAB.getInstance().getPlaceholderManager().registerPlayerPlaceholder(identifier, p -> {
|
||||
Bukkit.getScheduler().runTask(plugin, () -> {
|
||||
long time = System.nanoTime();
|
||||
ppl[0].updateValue(p, placeholderAPI ? PlaceholderAPI.setPlaceholders((Player) p.getPlayer(), PlaceholderAPI.setBracketPlaceholders((Player) p.getPlayer(), syncedPlaceholder)) : identifier);
|
||||
ppl[0].updateValue(p, placeholderAPI ? PlaceholderAPI.setPlaceholders((Player) p.getPlayer(), syncedPlaceholder) : identifier);
|
||||
TAB.getInstance().getCPUManager().addPlaceholderTime(identifier, System.nanoTime() - time);
|
||||
});
|
||||
return null;
|
||||
@@ -424,8 +298,8 @@ public class BukkitPlatform implements BackendPlatform {
|
||||
@Override
|
||||
@NotNull
|
||||
public Object convertComponent(@NotNull TabComponent component) {
|
||||
if (componentConverter != null) {
|
||||
return componentConverter.convert(component);
|
||||
if (serverImplementationProvider.getComponentConverter() != null) {
|
||||
return serverImplementationProvider.getComponentConverter().convert(component);
|
||||
} else {
|
||||
return component;
|
||||
}
|
||||
@@ -435,7 +309,11 @@ public class BukkitPlatform implements BackendPlatform {
|
||||
@NotNull
|
||||
@SneakyThrows
|
||||
public Scoreboard createScoreboard(@NotNull TabPlayer player) {
|
||||
return scoreboardProvider.apply((BukkitTabPlayer) player);
|
||||
if (viaVersionImplementationProvider != null) {
|
||||
Scoreboard scoreboard = viaVersionImplementationProvider.newScoreboard((BukkitTabPlayer) player);
|
||||
if (scoreboard != null) return scoreboard;
|
||||
}
|
||||
return serverImplementationProvider.newScoreboard((BukkitTabPlayer) player);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -458,7 +336,11 @@ public class BukkitPlatform implements BackendPlatform {
|
||||
@NotNull
|
||||
@SneakyThrows
|
||||
public TabList createTabList(@NotNull TabPlayer player) {
|
||||
return tablistProvider.apply((BukkitTabPlayer) player);
|
||||
if (viaVersionImplementationProvider != null) {
|
||||
TabList tabList = viaVersionImplementationProvider.newTabList((BukkitTabPlayer) player);
|
||||
if (tabList != null) return tabList;
|
||||
}
|
||||
return serverImplementationProvider.newTabList((BukkitTabPlayer) player);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
-118
@@ -1,118 +0,0 @@
|
||||
package me.neznamy.tab.platforms.bukkit.platform;
|
||||
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.scoreboard.viaversion.ViaScoreboard1203;
|
||||
import me.neznamy.tab.platforms.bukkit.scoreboard.viaversion.ViaScoreboard113;
|
||||
import me.neznamy.tab.platforms.bukkit.scoreboard.viaversion.ViaScoreboard116;
|
||||
import me.neznamy.tab.platforms.bukkit.tablist.viaversion.ViaTabList1193;
|
||||
import me.neznamy.tab.platforms.bukkit.tablist.viaversion.ViaTabList1212;
|
||||
import me.neznamy.tab.platforms.bukkit.tablist.viaversion.ViaTabList1214;
|
||||
import me.neznamy.tab.platforms.bukkit.tablist.viaversion.ViaTabList116;
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import me.neznamy.tab.shared.util.function.FunctionWithException;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class ViaVersionPlatform {
|
||||
|
||||
@NotNull
|
||||
public static FunctionWithException<BukkitTabPlayer, Scoreboard> findScoreboardProvider(@NotNull ProtocolVersion serverVersion) {
|
||||
if (!ReflectionUtils.classExists("com.viaversion.viaversion.protocols.v1_20_2to1_20_3.Protocol1_20_2To1_20_3")) {
|
||||
return FunctionWithException.empty();
|
||||
}
|
||||
if (serverVersion.getNetworkId() < ProtocolVersion.V1_13.getNetworkId()) {
|
||||
return player -> {
|
||||
if (player.getVersion().getNetworkId() >= ProtocolVersion.V1_20_3.getNetworkId()) {
|
||||
return new ViaScoreboard1203(player);
|
||||
}
|
||||
if (player.getVersion().getNetworkId() >= ProtocolVersion.V1_16.getNetworkId()) {
|
||||
return new ViaScoreboard116(player);
|
||||
}
|
||||
if (player.getVersion().getNetworkId() >= ProtocolVersion.V1_13.getNetworkId()) {
|
||||
return new ViaScoreboard113(player);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
}
|
||||
if (serverVersion.getNetworkId() < ProtocolVersion.V1_16.getNetworkId()) {
|
||||
return player -> {
|
||||
if (player.getVersion().getNetworkId() >= ProtocolVersion.V1_20_3.getNetworkId()) {
|
||||
return new ViaScoreboard1203(player);
|
||||
}
|
||||
if (player.getVersion().getNetworkId() >= ProtocolVersion.V1_16.getNetworkId()) {
|
||||
return new ViaScoreboard116(player);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
}
|
||||
if (serverVersion.getNetworkId() < ProtocolVersion.V1_20_3.getNetworkId()) {
|
||||
return player -> {
|
||||
if (player.getVersion().getNetworkId() >= ProtocolVersion.V1_20_3.getNetworkId()) {
|
||||
return new ViaScoreboard1203(player);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
}
|
||||
return FunctionWithException.empty();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static FunctionWithException<BukkitTabPlayer, TabList> findTablistProvider(@NotNull ProtocolVersion serverVersion) {
|
||||
if (!ReflectionUtils.classExists("com.viaversion.viaversion.protocols.v1_21_2to1_21_4.Protocol1_21_2To1_21_4")) {
|
||||
return FunctionWithException.empty();
|
||||
}
|
||||
if (serverVersion.getNetworkId() < ProtocolVersion.V1_16.getNetworkId()) {
|
||||
return player -> {
|
||||
if (player.getVersion().getNetworkId() >= ProtocolVersion.V1_21_4.getNetworkId()) {
|
||||
return new ViaTabList1214(player);
|
||||
}
|
||||
if (player.getVersion().getNetworkId() >= ProtocolVersion.V1_21_2.getNetworkId()) {
|
||||
return new ViaTabList1212(player);
|
||||
}
|
||||
if (player.getVersion().getNetworkId() >= ProtocolVersion.V1_19_3.getNetworkId()) {
|
||||
return new ViaTabList1193(player);
|
||||
}
|
||||
if (player.getVersion().getNetworkId() >= ProtocolVersion.V1_16.getNetworkId()) {
|
||||
return new ViaTabList116(player);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
}
|
||||
if (serverVersion.getNetworkId() < ProtocolVersion.V1_19_3.getNetworkId()) {
|
||||
return player -> {
|
||||
if (player.getVersion().getNetworkId() >= ProtocolVersion.V1_21_4.getNetworkId()) {
|
||||
return new ViaTabList1214(player);
|
||||
}
|
||||
if (player.getVersion().getNetworkId() >= ProtocolVersion.V1_21_2.getNetworkId()) {
|
||||
return new ViaTabList1212(player);
|
||||
}
|
||||
if (player.getVersion().getNetworkId() >= ProtocolVersion.V1_19_3.getNetworkId()) {
|
||||
return new ViaTabList1193(player);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
}
|
||||
if (serverVersion.getNetworkId() < ProtocolVersion.V1_21_2.getNetworkId()) {
|
||||
return player -> {
|
||||
if (player.getVersion().getNetworkId() >= ProtocolVersion.V1_21_4.getNetworkId()) {
|
||||
return new ViaTabList1214(player);
|
||||
}
|
||||
if (player.getVersion().getNetworkId() >= ProtocolVersion.V1_21_2.getNetworkId()) {
|
||||
return new ViaTabList1212(player);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
}
|
||||
if (serverVersion.getNetworkId() < ProtocolVersion.V1_21_4.getNetworkId()) {
|
||||
return player -> {
|
||||
if (player.getVersion().getNetworkId() >= ProtocolVersion.V1_21_4.getNetworkId()) {
|
||||
return new ViaTabList1214(player);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
}
|
||||
return FunctionWithException.empty();
|
||||
}
|
||||
}
|
||||
+175
@@ -0,0 +1,175 @@
|
||||
package me.neznamy.tab.platforms.bukkit.provider;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import io.netty.channel.Channel;
|
||||
import lombok.Getter;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitUtils;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.converter.ComponentConverter;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.converter.LegacyComponentConverter;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.converter.ModerateComponentConverter;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.converter.ModernComponentConverter;
|
||||
import me.neznamy.tab.platforms.bukkit.scoreboard.BukkitScoreboard;
|
||||
import me.neznamy.tab.platforms.bukkit.scoreboard.PaperScoreboard;
|
||||
import me.neznamy.tab.platforms.bukkit.scoreboard.packet.PacketScoreboard;
|
||||
import me.neznamy.tab.platforms.bukkit.tablist.BukkitTabList;
|
||||
import me.neznamy.tab.platforms.bukkit.tablist.PacketTabList1193;
|
||||
import me.neznamy.tab.platforms.bukkit.tablist.PacketTabList17;
|
||||
import me.neznamy.tab.platforms.bukkit.tablist.PacketTabList18;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import me.neznamy.tab.shared.platform.impl.DummyScoreboard;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import me.neznamy.tab.shared.util.function.FunctionWithException;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Implementation provider using reflection for NMS / Bukkit implementations as fallback options.
|
||||
*/
|
||||
@Getter
|
||||
public class BukkitImplementationProvider implements ImplementationProvider {
|
||||
|
||||
@Nullable
|
||||
private final FunctionWithException<BukkitTabPlayer, Channel> channelFunction = findChannelFunction();
|
||||
|
||||
@Nullable
|
||||
private final ComponentConverter componentConverter = findComponentConverter();
|
||||
|
||||
@NotNull
|
||||
private final Function<BukkitTabPlayer, Scoreboard> scoreboardProvider = findScoreboardProvider();
|
||||
|
||||
@NotNull
|
||||
private final Function<BukkitTabPlayer, TabList> tablistProvider = findTablistProvider();
|
||||
|
||||
@Nullable
|
||||
private FunctionWithException<BukkitTabPlayer, Channel> findChannelFunction() {
|
||||
if (BukkitReflection.getMinorVersion() < 8) return null;
|
||||
try {
|
||||
Class<?> NetworkManager = BukkitReflection.getClass("network.Connection", "network.NetworkManager", "NetworkManager");
|
||||
Class<?> PlayerConnection = BukkitReflection.getClass("server.network.ServerGamePacketListenerImpl",
|
||||
"server.network.PlayerConnection", "PlayerConnection");
|
||||
Class<?> EntityPlayer = BukkitReflection.getClass("server.level.ServerPlayer", "server.level.EntityPlayer", "EntityPlayer");
|
||||
Field PLAYER_CONNECTION = ReflectionUtils.getOnlyField(EntityPlayer, PlayerConnection);
|
||||
Field NETWORK_MANAGER;
|
||||
if (BukkitReflection.is1_20_2Plus()) {
|
||||
NETWORK_MANAGER = ReflectionUtils.getOnlyField(PlayerConnection.getSuperclass(), NetworkManager);
|
||||
} else {
|
||||
NETWORK_MANAGER = ReflectionUtils.getOnlyField(PlayerConnection, NetworkManager);
|
||||
}
|
||||
Field CHANNEL = ReflectionUtils.getOnlyField(NetworkManager, Channel.class);
|
||||
return player -> (Channel) CHANNEL.get(NETWORK_MANAGER.get(PLAYER_CONNECTION.get(player.getHandle())));
|
||||
} catch (Exception e) {
|
||||
BukkitUtils.compatibilityError(e, "network channel injection", null,
|
||||
"Anti-override for tablist & nametags not working",
|
||||
"Compatibility with nickname plugins changing player names will not work",
|
||||
"Scoreboard will not be checking for other plugins");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@SneakyThrows
|
||||
private ComponentConverter findComponentConverter() {
|
||||
try {
|
||||
if (BukkitReflection.getMinorVersion() >= 19) {
|
||||
// 1.19+
|
||||
return new ModernComponentConverter();
|
||||
} else if (BukkitReflection.getMinorVersion() >= 16) {
|
||||
// 1.16 - 1.18.2
|
||||
return new ModerateComponentConverter();
|
||||
} else {
|
||||
// 1.7 - 1.15.2
|
||||
return new LegacyComponentConverter();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Bukkit.getConsoleSender().sendMessage("§c[TAB] Failed to initialize converter from TAB components to Minecraft components. " +
|
||||
"This will negatively impact most features, see below.");
|
||||
if (BukkitUtils.PRINT_EXCEPTIONS) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private Function<BukkitTabPlayer, Scoreboard> findScoreboardProvider() {
|
||||
try {
|
||||
if (BukkitReflection.getMinorVersion() >= 7) Objects.requireNonNull(componentConverter);
|
||||
PacketScoreboard.load();
|
||||
return PacketScoreboard::new;
|
||||
} catch (Exception e) {
|
||||
if (PaperScoreboard.isAvailable()) {
|
||||
BukkitUtils.compatibilityError(e, "Scoreboards", "Paper API", "Compatibility with other plugins being reduced");
|
||||
return PaperScoreboard::new;
|
||||
} else if (BukkitScoreboard.isAvailable()) {
|
||||
List<String> missingFeatures = Lists.newArrayList(
|
||||
"Compatibility with other plugins being reduced",
|
||||
"Features receiving new artificial character limits"
|
||||
);
|
||||
if (BukkitReflection.is1_20_3Plus()) {
|
||||
missingFeatures.add("1.20.3+ visuals not working due to lack of API"); // soontm?
|
||||
}
|
||||
BukkitUtils.compatibilityError(e, "Scoreboards", "Bukkit API", missingFeatures.toArray(new String[0]));
|
||||
return BukkitScoreboard::new;
|
||||
} else if (BukkitReflection.getMinorVersion() >= 5) {
|
||||
BukkitUtils.compatibilityError(e, "Scoreboards", null,
|
||||
"Scoreboard feature will not work",
|
||||
"Belowname feature will not work",
|
||||
"Player objective feature will not work",
|
||||
"Scoreboard teams feature will not work (nametags & sorting)");
|
||||
}
|
||||
}
|
||||
return DummyScoreboard::new;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@SneakyThrows
|
||||
private Function<BukkitTabPlayer, TabList> findTablistProvider() {
|
||||
try {
|
||||
if (ReflectionUtils.classExists("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket")) {
|
||||
// 1.19.3+
|
||||
Objects.requireNonNull(componentConverter);
|
||||
PacketTabList1193.loadNew();
|
||||
return PacketTabList1193::new;
|
||||
} else if (BukkitReflection.getMinorVersion() >= 8) {
|
||||
// 1.8 - 1.19.2
|
||||
Objects.requireNonNull(componentConverter);
|
||||
PacketTabList18.load();
|
||||
return PacketTabList18::new;
|
||||
} else {
|
||||
// 1.7.10 and lower
|
||||
PacketTabList17.load();
|
||||
return PacketTabList17::new;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
BukkitUtils.compatibilityError(e, "tablist entry management", "Bukkit API",
|
||||
"Layout feature will not work",
|
||||
"Prevent-spectator-effect feature will not work",
|
||||
"Ping spoof feature will not work",
|
||||
"Tablist formatting missing anti-override",
|
||||
"Tablist formatting not supporting relational placeholders");
|
||||
return BukkitTabList::new;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Scoreboard newScoreboard(@NotNull BukkitTabPlayer player) {
|
||||
return scoreboardProvider.apply(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public TabList newTabList(@NotNull BukkitTabPlayer player) {
|
||||
return tablistProvider.apply(player);
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package me.neznamy.tab.platforms.bukkit.provider;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.converter.ComponentConverter;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import me.neznamy.tab.shared.util.function.FunctionWithException;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public interface ImplementationProvider {
|
||||
|
||||
@NotNull
|
||||
Scoreboard newScoreboard(@NotNull BukkitTabPlayer player);
|
||||
|
||||
@NotNull
|
||||
TabList newTabList(@NotNull BukkitTabPlayer player);
|
||||
|
||||
@Nullable
|
||||
ComponentConverter getComponentConverter();
|
||||
|
||||
@Nullable
|
||||
FunctionWithException<BukkitTabPlayer, Channel> getChannelFunction();
|
||||
}
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
package me.neznamy.tab.platforms.bukkit.provider;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.scoreboard.viaversion.ViaScoreboard113;
|
||||
import me.neznamy.tab.platforms.bukkit.scoreboard.viaversion.ViaScoreboard116;
|
||||
import me.neznamy.tab.platforms.bukkit.scoreboard.viaversion.ViaScoreboard1203;
|
||||
import me.neznamy.tab.platforms.bukkit.tablist.viaversion.ViaTabList116;
|
||||
import me.neznamy.tab.platforms.bukkit.tablist.viaversion.ViaTabList1193;
|
||||
import me.neznamy.tab.platforms.bukkit.tablist.viaversion.ViaTabList1212;
|
||||
import me.neznamy.tab.platforms.bukkit.tablist.viaversion.ViaTabList1214;
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Implementation for sending new features on old servers using ViaVersion API.
|
||||
* If no implementation is required for a player, {@code null} is returned.
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class ViaVersionImplementationProvider {
|
||||
|
||||
/** Map of tablist implementations by each new version */
|
||||
private static final Map<ProtocolVersion, Function<BukkitTabPlayer, TabList>> TABLIST_IMPLEMENTATIONS = new LinkedHashMap<>();
|
||||
|
||||
/** Map of scoreboard implementations by each new version */
|
||||
private static final Map<ProtocolVersion, Function<BukkitTabPlayer, Scoreboard>> SCOREBOARD_IMPLEMENTATIONS = new LinkedHashMap<>();
|
||||
|
||||
static {
|
||||
TABLIST_IMPLEMENTATIONS.put(ProtocolVersion.V1_21_4, ViaTabList1214::new);
|
||||
TABLIST_IMPLEMENTATIONS.put(ProtocolVersion.V1_21_2, ViaTabList1212::new);
|
||||
TABLIST_IMPLEMENTATIONS.put(ProtocolVersion.V1_19_3, ViaTabList1193::new);
|
||||
TABLIST_IMPLEMENTATIONS.put(ProtocolVersion.V1_16, ViaTabList116::new);
|
||||
|
||||
SCOREBOARD_IMPLEMENTATIONS.put(ProtocolVersion.V1_20_3, ViaScoreboard1203::new);
|
||||
SCOREBOARD_IMPLEMENTATIONS.put(ProtocolVersion.V1_16, ViaScoreboard116::new);
|
||||
SCOREBOARD_IMPLEMENTATIONS.put(ProtocolVersion.V1_13, ViaScoreboard113::new);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private final ProtocolVersion serverVersion;
|
||||
|
||||
@Nullable
|
||||
public Scoreboard newScoreboard(@NotNull BukkitTabPlayer player) {
|
||||
int serverVer = serverVersion.getNetworkId();
|
||||
int playerVer = player.getVersion().getNetworkId();
|
||||
for (Map.Entry<ProtocolVersion, Function<BukkitTabPlayer, Scoreboard>> entry : SCOREBOARD_IMPLEMENTATIONS.entrySet()) {
|
||||
if (serverVer < entry.getKey().getNetworkId() && playerVer >= entry.getKey().getNetworkId()) {
|
||||
return entry.getValue().apply(player);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public TabList newTabList(@NotNull BukkitTabPlayer player) {
|
||||
int serverVer = serverVersion.getNetworkId();
|
||||
int playerVer = player.getVersion().getNetworkId();
|
||||
for (Map.Entry<ProtocolVersion, Function<BukkitTabPlayer, TabList>> entry : TABLIST_IMPLEMENTATIONS.entrySet()) {
|
||||
if (serverVer < entry.getKey().getNetworkId() && playerVer >= entry.getKey().getNetworkId()) {
|
||||
return entry.getValue().apply(player);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+8
-2
@@ -41,7 +41,7 @@ import java.util.concurrent.CountDownLatch;
|
||||
* making per-player view of teams, especially sidebar not working.
|
||||
*/
|
||||
// Throw the NPE if something is not as expected, parent class should ensure it anyway
|
||||
@SuppressWarnings({"deprecation", "DataFlowIssue"})
|
||||
@SuppressWarnings("deprecation")
|
||||
public class BukkitScoreboard extends SafeScoreboard<BukkitTabPlayer> {
|
||||
|
||||
/** Version in which team colors were added */
|
||||
@@ -100,10 +100,16 @@ public class BukkitScoreboard extends SafeScoreboard<BukkitTabPlayer> {
|
||||
checkPlayerScoreboard();
|
||||
org.bukkit.scoreboard.Objective obj = newObjective(objective.getName(), "dummy", objective.getTitle(), objective.getHealthDisplay());
|
||||
setObjectiveNumberFormat(obj, objective.getNumberFormat());
|
||||
obj.setDisplaySlot(slots[objective.getDisplaySlot().ordinal()]);
|
||||
objective.setPlatformObjective(obj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDisplaySlot(@NonNull Objective objective) {
|
||||
checkPlayerScoreboard();
|
||||
org.bukkit.scoreboard.Objective obj = (org.bukkit.scoreboard.Objective) objective.getPlatformObjective();
|
||||
obj.setDisplaySlot(slots[objective.getDisplaySlot().ordinal()]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterObjective(@NonNull Objective objective) {
|
||||
checkPlayerScoreboard();
|
||||
|
||||
+4
@@ -113,6 +113,10 @@ public class PacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
|
||||
@Override
|
||||
public void registerObjective(@NonNull Objective objective) {
|
||||
packetSender.sendPacket(player, newObjectivePacket(ObjectiveAction.REGISTER, objective));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDisplaySlot(@NonNull Objective objective) {
|
||||
packetSender.sendPacket(player, displayPacketData.setDisplaySlot(objective.getDisplaySlot().ordinal(), newObjective(objective)));
|
||||
}
|
||||
|
||||
|
||||
+19
-22
@@ -5,6 +5,7 @@ import com.viaversion.viaversion.api.connection.UserConnection;
|
||||
import com.viaversion.viaversion.api.protocol.Protocol;
|
||||
import com.viaversion.viaversion.api.protocol.packet.PacketType;
|
||||
import com.viaversion.viaversion.api.protocol.packet.PacketWrapper;
|
||||
import com.viaversion.viaversion.api.protocol.packet.State;
|
||||
import com.viaversion.viaversion.api.type.Types;
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.chat.component.SimpleTextComponent;
|
||||
@@ -14,6 +15,7 @@ import me.neznamy.tab.shared.platform.decorators.SafeScoreboard;
|
||||
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
@@ -23,19 +25,19 @@ import java.util.concurrent.TimeUnit;
|
||||
*/
|
||||
public abstract class ViaScoreboard extends SafeScoreboard<BukkitTabPlayer> {
|
||||
|
||||
private static final long DELAY = 500;
|
||||
private static final long CHECK_DELAY = 50;
|
||||
private static final Object DUMMY = new Object();
|
||||
|
||||
protected final Class<? extends Protocol> protocol;
|
||||
private final PacketType setDisplayObjective;
|
||||
protected final PacketType setDisplayObjective;
|
||||
private final PacketType setObjective;
|
||||
protected final PacketType setScore;
|
||||
private final PacketType setPlayerTeam;
|
||||
/** User connection this tablist belongs to */
|
||||
/** User connection this scoreboard belongs to */
|
||||
protected final UserConnection connection;
|
||||
|
||||
private transient final Queue<Runnable> queue = new ConcurrentLinkedQueue<>();
|
||||
private transient boolean delayed = true;
|
||||
private transient ScheduledFuture<?> task;
|
||||
private transient final Queue<PacketWrapper> queuedPackets = new ConcurrentLinkedQueue<>();
|
||||
|
||||
/**
|
||||
* Constructs new instance with given player.
|
||||
@@ -62,25 +64,22 @@ public abstract class ViaScoreboard extends SafeScoreboard<BukkitTabPlayer> {
|
||||
this.setPlayerTeam = setPlayerTeam;
|
||||
this.connection = Via.getManager().getConnectionManager().getConnectedClient(player.getUniqueId());
|
||||
|
||||
// First-time delay
|
||||
connection.getChannel().eventLoop().schedule(() -> {
|
||||
Runnable task;
|
||||
while ((task = queue.poll()) != null) {
|
||||
task.run();
|
||||
// Queue packets until connection is available
|
||||
// Is important to use scheduleWithFixedDelay() to avoid task overlap
|
||||
task = connection.getChannel().eventLoop().scheduleWithFixedDelay(() -> {
|
||||
if (connection.getProtocolInfo().getClientState() == State.PLAY) {
|
||||
PacketWrapper packet;
|
||||
while ((packet = queuedPackets.poll()) != null) {
|
||||
packet.send(protocol);
|
||||
}
|
||||
task.cancel(true);
|
||||
}
|
||||
delayed = false;
|
||||
}, DELAY, TimeUnit.MILLISECONDS);
|
||||
}, 0L, CHECK_DELAY, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerObjective(@NonNull Objective objective) {
|
||||
sendObjectiveUpdate(ObjectiveAction.REGISTER, objective);
|
||||
|
||||
final PacketWrapper packet = PacketWrapper.create(setDisplayObjective, null, connection);
|
||||
|
||||
writeObjective(packet, objective);
|
||||
|
||||
send(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -178,13 +177,11 @@ public abstract class ViaScoreboard extends SafeScoreboard<BukkitTabPlayer> {
|
||||
|
||||
protected abstract void writeComponent(@NonNull PacketWrapper packet, @NonNull TabComponent component);
|
||||
|
||||
protected abstract void writeObjective(@NonNull PacketWrapper packet, @NonNull Objective objective);
|
||||
|
||||
protected abstract void writeObjectiveDisplay(@NonNull PacketWrapper packet, @NonNull Objective objective);
|
||||
|
||||
protected void send(@NonNull PacketWrapper packet) {
|
||||
if (delayed) {
|
||||
queue.add(() -> packet.send(protocol));
|
||||
if (!task.isCancelled()) {
|
||||
queuedPackets.add(packet);
|
||||
} else {
|
||||
packet.scheduleSend(protocol);
|
||||
}
|
||||
|
||||
+10
-6
@@ -30,6 +30,16 @@ public class ViaScoreboard113 extends ViaScoreboard {
|
||||
super(player, protocol, setDisplayObjective, setObjective, setScore, setPlayerTeam);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDisplaySlot(@NonNull Objective objective) {
|
||||
PacketWrapper packet = PacketWrapper.create(setDisplayObjective, null, connection);
|
||||
|
||||
packet.write(Types.BYTE, (byte) objective.getDisplaySlot().ordinal());
|
||||
packet.write(Types.STRING, objective.getName());
|
||||
|
||||
send(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setScore(@NonNull Score score) {
|
||||
sendScoreUpdate(ScoreAction.CHANGE, score);
|
||||
@@ -62,12 +72,6 @@ public class ViaScoreboard113 extends ViaScoreboard {
|
||||
packet.write(Types.COMPONENT, ComponentUtil.legacyToJson(component.toLegacyText()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeObjective(@NonNull PacketWrapper packet, @NonNull Objective objective) {
|
||||
packet.write(Types.BYTE, (byte) objective.getDisplaySlot().ordinal());
|
||||
packet.write(Types.STRING, objective.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeObjectiveDisplay(@NonNull PacketWrapper packet, @NonNull Objective objective) {
|
||||
// Objective value
|
||||
|
||||
+10
-6
@@ -33,6 +33,16 @@ public class ViaScoreboard1203 extends ViaScoreboard116 {
|
||||
this.resetScore = resetScore;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDisplaySlot(@NonNull Objective objective) {
|
||||
PacketWrapper packet = PacketWrapper.create(setDisplayObjective, null, connection);
|
||||
|
||||
packet.write(Types.VAR_INT, objective.getDisplaySlot().ordinal());
|
||||
packet.write(Types.STRING, objective.getName());
|
||||
|
||||
send(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setScore(@NonNull Score score) {
|
||||
final PacketWrapper packet = PacketWrapper.create(setScore, null, connection);
|
||||
@@ -68,12 +78,6 @@ public class ViaScoreboard1203 extends ViaScoreboard116 {
|
||||
packet.write(Types.TAG, component.toViaVersionTag());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeObjective(@NonNull PacketWrapper packet, @NonNull Objective objective) {
|
||||
packet.write(Types.VAR_INT, objective.getDisplaySlot().ordinal());
|
||||
packet.write(Types.STRING, objective.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void writeObjectiveDisplay(@NonNull PacketWrapper packet, @NonNull Objective objective) {
|
||||
super.writeObjectiveDisplay(packet, objective);
|
||||
|
||||
+17
-12
@@ -5,6 +5,7 @@ import com.viaversion.viaversion.api.connection.UserConnection;
|
||||
import com.viaversion.viaversion.api.protocol.Protocol;
|
||||
import com.viaversion.viaversion.api.protocol.packet.PacketType;
|
||||
import com.viaversion.viaversion.api.protocol.packet.PacketWrapper;
|
||||
import com.viaversion.viaversion.api.protocol.packet.State;
|
||||
import com.viaversion.viaversion.api.type.Types;
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
@@ -18,6 +19,7 @@ import java.util.BitSet;
|
||||
import java.util.Queue;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
@@ -27,7 +29,7 @@ import java.util.concurrent.TimeUnit;
|
||||
*/
|
||||
public abstract class ViaTabList extends TrackedTabList<BukkitTabPlayer> {
|
||||
|
||||
private static final long DELAY = 500;
|
||||
private static final long CHECK_DELAY = 50;
|
||||
|
||||
protected final Class<? extends Protocol> protocol;
|
||||
private final PacketType playerInfoUpdate;
|
||||
@@ -35,8 +37,8 @@ public abstract class ViaTabList extends TrackedTabList<BukkitTabPlayer> {
|
||||
/** User connection this tablist belongs to */
|
||||
protected final UserConnection connection;
|
||||
|
||||
private transient final Queue<Runnable> queue = new ConcurrentLinkedQueue<>();
|
||||
private transient boolean delayed = true;
|
||||
private transient ScheduledFuture<?> task;
|
||||
private transient final Queue<PacketWrapper> queuedPackets = new ConcurrentLinkedQueue<>();
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -56,14 +58,17 @@ public abstract class ViaTabList extends TrackedTabList<BukkitTabPlayer> {
|
||||
this.tabList = tabList;
|
||||
this.connection = Via.getManager().getConnectionManager().getConnectedClient(player.getUniqueId());
|
||||
|
||||
// First-time delay
|
||||
connection.getChannel().eventLoop().schedule(() -> {
|
||||
Runnable task;
|
||||
while ((task = queue.poll()) != null) {
|
||||
task.run();
|
||||
// Queue packets until connection is available
|
||||
// Is important to use scheduleWithFixedDelay() to avoid task overlap
|
||||
task = connection.getChannel().eventLoop().scheduleWithFixedDelay(() -> {
|
||||
if (connection.getProtocolInfo().getClientState() == State.PLAY) {
|
||||
PacketWrapper packet;
|
||||
while ((packet = queuedPackets.poll()) != null) {
|
||||
packet.send(protocol);
|
||||
}
|
||||
task.cancel(true);
|
||||
}
|
||||
delayed = false;
|
||||
}, DELAY, TimeUnit.MILLISECONDS);
|
||||
}, 0L, CHECK_DELAY, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -164,8 +169,8 @@ public abstract class ViaTabList extends TrackedTabList<BukkitTabPlayer> {
|
||||
}
|
||||
|
||||
protected void send(@NonNull PacketWrapper packet) {
|
||||
if (delayed) {
|
||||
queue.add(() -> packet.send(protocol));
|
||||
if (!task.isCancelled()) {
|
||||
queuedPackets.add(packet);
|
||||
} else {
|
||||
packet.scheduleSend(protocol);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
dependencies {
|
||||
implementation(projects.bukkit)
|
||||
compileOnly("org.spigotmc:spigot:1.12-R0.1-SNAPSHOT")
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
package me.neznamy.tab.platforms.bukkit.v1_12_R1;
|
||||
|
||||
import me.neznamy.chat.ChatModifier;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.converter.ComponentConverter;
|
||||
import net.minecraft.server.v1_12_R1.*;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Component converter using direct NMS code.
|
||||
*/
|
||||
public class NMSComponentConverter extends ComponentConverter {
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Object newTextComponent(@NotNull String text) {
|
||||
return new ChatComponentText(text);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Object newTranslatableComponent(@NotNull String key) {
|
||||
return new ChatMessage(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Object newKeybindComponent(@NotNull String keybind) {
|
||||
return new ChatComponentKeybind(keybind);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyStyle(@NotNull Object nmsComponent, @NotNull ChatModifier modifier) {
|
||||
((IChatBaseComponent)nmsComponent).setChatModifier(
|
||||
new net.minecraft.server.v1_12_R1.ChatModifier()
|
||||
.setColor(modifier.getColor() == null ? null : EnumChatFormat.valueOf(modifier.getColor().getLegacyColor().name()))
|
||||
.setBold(modifier.getBold())
|
||||
.setItalic(modifier.getItalic())
|
||||
.setUnderline(modifier.getUnderlined())
|
||||
.setStrikethrough(modifier.getStrikethrough())
|
||||
.setRandom(modifier.getObfuscated())
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addSibling(@NotNull Object parent, @NotNull Object child) {
|
||||
((IChatBaseComponent)parent).addSibling((IChatBaseComponent) child);
|
||||
}
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
package me.neznamy.tab.platforms.bukkit.v1_12_R1;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.converter.ComponentConverter;
|
||||
import me.neznamy.tab.platforms.bukkit.provider.ImplementationProvider;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import me.neznamy.tab.shared.util.function.FunctionWithException;
|
||||
import org.bukkit.craftbukkit.v1_12_R1.entity.CraftPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Implementation provider using direct NMS code for 1.8.8.
|
||||
*/
|
||||
|
||||
public class NMSImplementationProvider implements ImplementationProvider {
|
||||
|
||||
@NotNull
|
||||
private final ComponentConverter componentConverter = new NMSComponentConverter();
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Scoreboard newScoreboard(@NotNull BukkitTabPlayer player) {
|
||||
return new NMSPacketScoreboard(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public TabList newTabList(@NotNull BukkitTabPlayer player) {
|
||||
return new NMSPacketTabList(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ComponentConverter getComponentConverter() {
|
||||
return componentConverter;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public FunctionWithException<BukkitTabPlayer, Channel> getChannelFunction() {
|
||||
return player -> ((CraftPlayer)player.getPlayer()).getHandle().playerConnection.networkManager.channel;
|
||||
}
|
||||
}
|
||||
+159
@@ -0,0 +1,159 @@
|
||||
package me.neznamy.tab.platforms.bukkit.v1_12_R1;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.shared.Limitations;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.platform.decorators.SafeScoreboard;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import net.minecraft.server.v1_12_R1.*;
|
||||
import org.bukkit.craftbukkit.v1_12_R1.entity.CraftPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* Scoreboard implementation using direct NMS code.
|
||||
*/
|
||||
public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
|
||||
|
||||
private static final ScoreboardTeamBase.EnumNameTagVisibility[] visibilities = ScoreboardTeamBase.EnumNameTagVisibility.values();
|
||||
private static final ScoreboardTeamBase.EnumTeamPush[] collisions = ScoreboardTeamBase.EnumTeamPush.values();
|
||||
private static final Scoreboard dummyScoreboard = new Scoreboard();
|
||||
|
||||
private static final Field TeamPacket_NAME = ReflectionUtils.getFields(PacketPlayOutScoreboardTeam.class, String.class).get(0);
|
||||
private static final Field TeamPacket_ACTION = ReflectionUtils.getInstanceFields(PacketPlayOutScoreboardTeam.class, int.class).get(1);
|
||||
private static final Field TeamPacket_PLAYERS = ReflectionUtils.getOnlyField(PacketPlayOutScoreboardTeam.class, Collection.class);
|
||||
|
||||
private static final Field Objective_OBJECTIVE_NAME = ReflectionUtils.getFields(PacketPlayOutScoreboardObjective.class, String.class).get(0);
|
||||
private static final Field Objective_METHOD = ReflectionUtils.getOnlyField(PacketPlayOutScoreboardObjective.class, int.class);
|
||||
|
||||
private static final Field DisplayObjective_OBJECTIVE_NAME = ReflectionUtils.getOnlyField(PacketPlayOutScoreboardDisplayObjective.class, String.class);
|
||||
private static final Field DisplayObjective_POSITION = ReflectionUtils.getOnlyField(PacketPlayOutScoreboardDisplayObjective.class, int.class);
|
||||
|
||||
|
||||
/**
|
||||
* Constructs new instance with given player.
|
||||
*
|
||||
* @param player
|
||||
* Player this scoreboard will belong to
|
||||
*/
|
||||
public NMSPacketScoreboard(@NotNull BukkitTabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerObjective(@NonNull Objective objective) {
|
||||
ScoreboardObjective obj = new ScoreboardObjective(dummyScoreboard, objective.getName(), IScoreboardCriteria.b);
|
||||
obj.setDisplayName(cutTo(objective.getTitle().toLegacyText(), Limitations.SCOREBOARD_TITLE_PRE_1_13));
|
||||
obj.a(IScoreboardCriteria.EnumScoreboardHealthDisplay.valueOf(objective.getHealthDisplay().name()));
|
||||
objective.setPlatformObjective(obj);
|
||||
sendPacket(new PacketPlayOutScoreboardObjective(obj, ObjectiveAction.REGISTER));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDisplaySlot(@NonNull Objective objective) {
|
||||
sendPacket(new PacketPlayOutScoreboardDisplayObjective(objective.getDisplaySlot().ordinal(), (ScoreboardObjective) objective.getPlatformObjective()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterObjective(@NonNull Objective objective) {
|
||||
sendPacket(new PacketPlayOutScoreboardObjective((ScoreboardObjective) objective.getPlatformObjective(), ObjectiveAction.UNREGISTER));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateObjective(@NonNull Objective objective) {
|
||||
ScoreboardObjective obj = (ScoreboardObjective) objective.getPlatformObjective();
|
||||
String title = objective.getTitle().toLegacyText();
|
||||
if (player.getVersion().getMinorVersion() < 13 || TAB.getInstance().getConfiguration().getConfig().isPacketEventsCompensation()) {
|
||||
title = cutTo(title, Limitations.SCOREBOARD_TITLE_PRE_1_13);
|
||||
}
|
||||
obj.setDisplayName(title);
|
||||
obj.a(IScoreboardCriteria.EnumScoreboardHealthDisplay.valueOf(objective.getHealthDisplay().name()));
|
||||
sendPacket(new PacketPlayOutScoreboardObjective(obj, ObjectiveAction.UPDATE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setScore(@NonNull Score score) {
|
||||
ScoreboardScore s = new ScoreboardScore(dummyScoreboard, (ScoreboardObjective) score.getObjective().getPlatformObjective(), score.getHolder());
|
||||
s.setScore(score.getValue());
|
||||
sendPacket(new PacketPlayOutScoreboardScore(s));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeScore(@NonNull Score score) {
|
||||
sendPacket(new PacketPlayOutScoreboardScore(score.getHolder()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Object createTeam(@NonNull String name) {
|
||||
return new ScoreboardTeam(dummyScoreboard, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerTeam(@NonNull Team team) {
|
||||
updateTeamProperties(team);
|
||||
ScoreboardTeam t = (ScoreboardTeam) team.getPlatformTeam();
|
||||
t.getPlayerNameSet().addAll(team.getPlayers());
|
||||
sendPacket(new PacketPlayOutScoreboardTeam(t, TeamAction.CREATE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterTeam(@NonNull Team team) {
|
||||
sendPacket(new PacketPlayOutScoreboardTeam((ScoreboardTeam) team.getPlatformTeam(), TeamAction.REMOVE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateTeam(@NonNull Team team) {
|
||||
updateTeamProperties(team);
|
||||
sendPacket(new PacketPlayOutScoreboardTeam((ScoreboardTeam) team.getPlatformTeam(), TeamAction.UPDATE));
|
||||
}
|
||||
|
||||
private void updateTeamProperties(@NonNull Team team) {
|
||||
ScoreboardTeam t = (ScoreboardTeam) team.getPlatformTeam();
|
||||
t.setAllowFriendlyFire((team.getOptions() & 0x01) != 0);
|
||||
t.setCanSeeFriendlyInvisibles((team.getOptions() & 0x02) != 0);
|
||||
t.setNameTagVisibility(visibilities[team.getVisibility().ordinal()]);
|
||||
t.setCollisionRule(collisions[team.getCollision().ordinal()]);
|
||||
t.setPrefix(cutTo(team.getPrefix().toLegacyText(), Limitations.TEAM_PREFIX_SUFFIX_PRE_1_13));
|
||||
t.setSuffix(cutTo(team.getSuffix().toLegacyText(), Limitations.TEAM_PREFIX_SUFFIX_PRE_1_13));
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
@SuppressWarnings("unchecked")
|
||||
public void onPacketSend(@NonNull Object packet) {
|
||||
if (isAntiOverrideScoreboard()) {
|
||||
if (packet instanceof PacketPlayOutScoreboardDisplayObjective) {
|
||||
TAB.getInstance().getFeatureManager().onDisplayObjective(player, DisplayObjective_POSITION.getInt(packet),
|
||||
(String) DisplayObjective_OBJECTIVE_NAME.get(packet));
|
||||
}
|
||||
if (packet instanceof PacketPlayOutScoreboardObjective) {
|
||||
TAB.getInstance().getFeatureManager().onObjective(player,
|
||||
Objective_METHOD.getInt(packet), (String) Objective_OBJECTIVE_NAME.get(packet));
|
||||
}
|
||||
}
|
||||
if (isAntiOverrideTeams() && packet instanceof PacketPlayOutScoreboardTeam) {
|
||||
int action = TeamPacket_ACTION.getInt(packet);
|
||||
if (action == TeamAction.UPDATE) return;
|
||||
Collection<String> players = (Collection<String>) TeamPacket_PLAYERS.get(packet);
|
||||
if (players == null) players = Collections.emptyList();
|
||||
TeamPacket_PLAYERS.set(packet, ((SafeScoreboard<?>)player.getScoreboard()).onTeamPacket(
|
||||
action, (String) TeamPacket_NAME.get(packet), players));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the packet to the player.
|
||||
*
|
||||
* @param packet
|
||||
* Packet to send
|
||||
*/
|
||||
private void sendPacket(@NotNull Packet<?> packet) {
|
||||
((CraftPlayer)player.getPlayer()).getHandle().playerConnection.sendPacket(packet);
|
||||
}
|
||||
}
|
||||
+189
@@ -0,0 +1,189 @@
|
||||
package me.neznamy.tab.platforms.bukkit.v1_12_R1;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import com.mojang.authlib.properties.Property;
|
||||
import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import me.neznamy.tab.shared.platform.decorators.TrackedTabList;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import net.minecraft.server.v1_12_R1.*;
|
||||
import org.bukkit.craftbukkit.v1_12_R1.entity.CraftPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* TabList implementation using direct NMS code.
|
||||
*/
|
||||
public class NMSPacketTabList extends TrackedTabList<BukkitTabPlayer> {
|
||||
|
||||
// PlayerInfoData subclass is broken, using reflection to get it
|
||||
private static final Class<?> PlayerInfoData = PacketPlayOutPlayerInfo.class.getDeclaredClasses()[0];
|
||||
private static final Constructor<?> newPlayerInfoData = ReflectionUtils.getOnlyConstructor(PlayerInfoData);
|
||||
|
||||
private static final Field ACTION = ReflectionUtils.getOnlyField(PacketPlayOutPlayerInfo.class, PacketPlayOutPlayerInfo.EnumPlayerInfoAction.class);
|
||||
private static final Field PLAYERS = ReflectionUtils.getOnlyField(PacketPlayOutPlayerInfo.class, List.class);
|
||||
|
||||
private static final Field PlayerInfoData_Profile = ReflectionUtils.getOnlyField(PlayerInfoData, GameProfile.class);
|
||||
private static final Field PlayerInfoData_Latency = ReflectionUtils.getFields(PlayerInfoData, int.class).get(0);
|
||||
private static final Field PlayerInfoData_DisplayName = ReflectionUtils.getOnlyField(PlayerInfoData, IChatBaseComponent.class);
|
||||
|
||||
private static final Field HEADER = ReflectionUtils.getFields(PacketPlayOutPlayerListHeaderFooter.class, IChatBaseComponent.class).get(0);
|
||||
private static final Field FOOTER = ReflectionUtils.getFields(PacketPlayOutPlayerListHeaderFooter.class, IChatBaseComponent.class).get(1);
|
||||
|
||||
/**
|
||||
* Constructs new instance.
|
||||
*
|
||||
* @param player
|
||||
* Player this tablist will belong to
|
||||
*/
|
||||
public NMSPacketTabList(@NotNull BukkitTabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void removeEntry(@NonNull UUID entry) {
|
||||
sendPacket(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.REMOVE_PLAYER, entry, "", null, 0, 0, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDisplayName0(@NonNull UUID entry, @Nullable TabComponent displayName) {
|
||||
sendPacket(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.UPDATE_DISPLAY_NAME, entry, "", null, 0, 0, displayName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateLatency(@NonNull UUID entry, int latency) {
|
||||
sendPacket(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.UPDATE_LATENCY, entry, "", null, latency, 0, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateGameMode(@NonNull UUID entry, int gameMode) {
|
||||
sendPacket(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.UPDATE_GAME_MODE, entry, "", null, 0, gameMode, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateListed(@NonNull UUID entry, boolean listed) {
|
||||
// Added in 1.19.3
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateListOrder(@NonNull UUID entry, int listOrder) {
|
||||
// Added in 1.20.2
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateHat(@NonNull UUID entry, boolean showHat) {
|
||||
// Added in 1.20.4
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addEntry0(@NonNull Entry entry) {
|
||||
sendPacket(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.ADD_PLAYER, entry.getUniqueId(), entry.getName(), entry.getSkin(), entry.getLatency(),
|
||||
entry.getGameMode(), entry.getDisplayName());
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void setPlayerListHeaderFooter(@NonNull TabComponent header, @NonNull TabComponent footer) {
|
||||
PacketPlayOutPlayerListHeaderFooter packet = new PacketPlayOutPlayerListHeaderFooter();
|
||||
HEADER.set(packet, header.convert());
|
||||
FOOTER.set(packet, footer.convert());
|
||||
sendPacket(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsEntry(@NonNull UUID entry) {
|
||||
return true; // TODO?
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Skin getSkin() {
|
||||
Collection<Property> properties = ((CraftPlayer)player.getPlayer()).getProfile().getProperties().get(TEXTURES_PROPERTY);
|
||||
if (properties.isEmpty()) return null; // Offline mode
|
||||
Property property = properties.iterator().next();
|
||||
return new Skin(property.getValue(), property.getSignature());
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
@SuppressWarnings("unchecked")
|
||||
public void onPacketSend(@NonNull Object packet) {
|
||||
if (!(packet instanceof PacketPlayOutPlayerInfo)) return;
|
||||
String action = ACTION.get(packet).toString();
|
||||
for (Object nmsData : (List<Object>) PLAYERS.get(packet)) {
|
||||
GameProfile profile = (GameProfile) PlayerInfoData_Profile.get(nmsData);
|
||||
UUID id = profile.getId();
|
||||
if (action.equals(Action.UPDATE_DISPLAY_NAME.name()) || action.equals(Action.ADD_PLAYER.name())) {
|
||||
TabComponent expectedName = getExpectedDisplayNames().get(id);
|
||||
if (expectedName != null) PlayerInfoData_DisplayName.set(nmsData, expectedName.convert());
|
||||
}
|
||||
if (action.equals(Action.UPDATE_LATENCY.name()) || action.equals(Action.ADD_PLAYER.name())) {
|
||||
int oldLatency = PlayerInfoData_Latency.getInt(nmsData);
|
||||
int newLatency = TAB.getInstance().getFeatureManager().onLatencyChange(player, id, oldLatency);
|
||||
if (oldLatency != newLatency) {
|
||||
PlayerInfoData_Latency.set(nmsData, newLatency);
|
||||
}
|
||||
}
|
||||
if (action.equals(Action.ADD_PLAYER.name())) {
|
||||
TAB.getInstance().getFeatureManager().onEntryAdd(player, id, profile.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private void sendPacket(@NonNull PacketPlayOutPlayerInfo.EnumPlayerInfoAction action, @NonNull UUID id, @NonNull String name,
|
||||
@Nullable Skin skin, int latency, int gameMode, @Nullable TabComponent displayName) {
|
||||
PacketPlayOutPlayerInfo packet = new PacketPlayOutPlayerInfo(action);
|
||||
PLAYERS.set(packet, Collections.singletonList(newPlayerInfoData.newInstance(
|
||||
packet,
|
||||
createProfile(id, name, skin),
|
||||
latency,
|
||||
EnumGamemode.values()[gameMode],
|
||||
displayName == null ? null : displayName.convert())
|
||||
));
|
||||
sendPacket(packet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates GameProfile from given parameters.
|
||||
*
|
||||
* @param id
|
||||
* Profile ID
|
||||
* @param name
|
||||
* Profile name
|
||||
* @param skin
|
||||
* Player skin
|
||||
* @return GameProfile from given parameters
|
||||
*/
|
||||
@NotNull
|
||||
private GameProfile createProfile(@NonNull UUID id, @NonNull String name, @Nullable Skin skin) {
|
||||
GameProfile profile = new GameProfile(id, name);
|
||||
if (skin != null) {
|
||||
profile.getProperties().put(TabList.TEXTURES_PROPERTY,
|
||||
new Property(TabList.TEXTURES_PROPERTY, skin.getValue(), skin.getSignature()));
|
||||
}
|
||||
return profile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the packet to the player.
|
||||
*
|
||||
* @param packet
|
||||
* Packet to send
|
||||
*/
|
||||
private void sendPacket(@NotNull Packet<?> packet) {
|
||||
((CraftPlayer)player.getPlayer()).getHandle().playerConnection.sendPacket(packet);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
dependencies {
|
||||
implementation(projects.bukkit)
|
||||
compileOnly("org.bukkit:craftbukkit:1.8.8-R0.1-SNAPSHOT")
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
package me.neznamy.tab.platforms.bukkit.v1_8_R3;
|
||||
|
||||
import me.neznamy.chat.ChatModifier;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.converter.ComponentConverter;
|
||||
import net.minecraft.server.v1_8_R3.ChatComponentText;
|
||||
import net.minecraft.server.v1_8_R3.ChatMessage;
|
||||
import net.minecraft.server.v1_8_R3.EnumChatFormat;
|
||||
import net.minecraft.server.v1_8_R3.IChatBaseComponent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Component converter using direct NMS code.
|
||||
*/
|
||||
public class NMSComponentConverter extends ComponentConverter {
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Object newTextComponent(@NotNull String text) {
|
||||
return new ChatComponentText(text);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Object newTranslatableComponent(@NotNull String key) {
|
||||
return new ChatMessage(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Object newKeybindComponent(@NotNull String keybind) {
|
||||
throw new UnsupportedOperationException("Keybind components were added in 1.12");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyStyle(@NotNull Object nmsComponent, @NotNull ChatModifier modifier) {
|
||||
((IChatBaseComponent)nmsComponent).setChatModifier(
|
||||
new net.minecraft.server.v1_8_R3.ChatModifier()
|
||||
.setColor(modifier.getColor() == null ? null : EnumChatFormat.valueOf(modifier.getColor().getLegacyColor().name()))
|
||||
.setBold(modifier.getBold())
|
||||
.setItalic(modifier.getItalic())
|
||||
.setUnderline(modifier.getUnderlined())
|
||||
.setStrikethrough(modifier.getStrikethrough())
|
||||
.setRandom(modifier.getObfuscated())
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addSibling(@NotNull Object parent, @NotNull Object child) {
|
||||
((IChatBaseComponent)parent).addSibling((IChatBaseComponent) child);
|
||||
}
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
package me.neznamy.tab.platforms.bukkit.v1_8_R3;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import lombok.Getter;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.platforms.bukkit.nms.converter.ComponentConverter;
|
||||
import me.neznamy.tab.platforms.bukkit.provider.ImplementationProvider;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import me.neznamy.tab.shared.util.function.FunctionWithException;
|
||||
import org.bukkit.craftbukkit.v1_8_R3.entity.CraftPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Implementation provider using direct NMS code for 1.8.8.
|
||||
*/
|
||||
@Getter
|
||||
public class NMSImplementationProvider implements ImplementationProvider {
|
||||
|
||||
@NotNull
|
||||
private final ComponentConverter componentConverter = new NMSComponentConverter();
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Scoreboard newScoreboard(@NotNull BukkitTabPlayer player) {
|
||||
return new NMSPacketScoreboard(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public TabList newTabList(@NotNull BukkitTabPlayer player) {
|
||||
return new NMSPacketTabList(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public FunctionWithException<BukkitTabPlayer, Channel> getChannelFunction() {
|
||||
return player -> ((CraftPlayer)player.getPlayer()).getHandle().playerConnection.networkManager.channel;
|
||||
}
|
||||
}
|
||||
+157
@@ -0,0 +1,157 @@
|
||||
package me.neznamy.tab.platforms.bukkit.v1_8_R3;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.shared.Limitations;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.platform.decorators.SafeScoreboard;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import net.minecraft.server.v1_8_R3.*;
|
||||
import org.bukkit.craftbukkit.v1_8_R3.entity.CraftPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* Scoreboard implementation using direct NMS code.
|
||||
*/
|
||||
public class NMSPacketScoreboard extends SafeScoreboard<BukkitTabPlayer> {
|
||||
|
||||
private static final ScoreboardTeamBase.EnumNameTagVisibility[] visibilities = ScoreboardTeamBase.EnumNameTagVisibility.values();
|
||||
private static final Scoreboard dummyScoreboard = new Scoreboard();
|
||||
|
||||
private static final Field TeamPacket_NAME = ReflectionUtils.getFields(PacketPlayOutScoreboardTeam.class, String.class).get(0);
|
||||
private static final Field TeamPacket_ACTION = ReflectionUtils.getInstanceFields(PacketPlayOutScoreboardTeam.class, int.class).get(1);
|
||||
private static final Field TeamPacket_PLAYERS = ReflectionUtils.getOnlyField(PacketPlayOutScoreboardTeam.class, Collection.class);
|
||||
|
||||
private static final Field Objective_OBJECTIVE_NAME = ReflectionUtils.getFields(PacketPlayOutScoreboardObjective.class, String.class).get(0);
|
||||
private static final Field Objective_METHOD = ReflectionUtils.getOnlyField(PacketPlayOutScoreboardObjective.class, int.class);
|
||||
|
||||
private static final Field DisplayObjective_OBJECTIVE_NAME = ReflectionUtils.getOnlyField(PacketPlayOutScoreboardDisplayObjective.class, String.class);
|
||||
private static final Field DisplayObjective_POSITION = ReflectionUtils.getOnlyField(PacketPlayOutScoreboardDisplayObjective.class, int.class);
|
||||
|
||||
|
||||
/**
|
||||
* Constructs new instance with given player.
|
||||
*
|
||||
* @param player
|
||||
* Player this scoreboard will belong to
|
||||
*/
|
||||
public NMSPacketScoreboard(@NotNull BukkitTabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerObjective(@NonNull Objective objective) {
|
||||
ScoreboardObjective obj = new ScoreboardObjective(dummyScoreboard, objective.getName(), IScoreboardCriteria.b);
|
||||
obj.setDisplayName(cutTo(objective.getTitle().toLegacyText(), Limitations.SCOREBOARD_TITLE_PRE_1_13));
|
||||
obj.a(IScoreboardCriteria.EnumScoreboardHealthDisplay.valueOf(objective.getHealthDisplay().name()));
|
||||
objective.setPlatformObjective(obj);
|
||||
sendPacket(new PacketPlayOutScoreboardObjective(obj, ObjectiveAction.REGISTER));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDisplaySlot(@NonNull Objective objective) {
|
||||
sendPacket(new PacketPlayOutScoreboardDisplayObjective(objective.getDisplaySlot().ordinal(), (ScoreboardObjective) objective.getPlatformObjective()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterObjective(@NonNull Objective objective) {
|
||||
sendPacket(new PacketPlayOutScoreboardObjective((ScoreboardObjective) objective.getPlatformObjective(), ObjectiveAction.UNREGISTER));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateObjective(@NonNull Objective objective) {
|
||||
ScoreboardObjective obj = (ScoreboardObjective) objective.getPlatformObjective();
|
||||
String title = objective.getTitle().toLegacyText();
|
||||
if (player.getVersion().getMinorVersion() < 13 || TAB.getInstance().getConfiguration().getConfig().isPacketEventsCompensation()) {
|
||||
title = cutTo(title, Limitations.SCOREBOARD_TITLE_PRE_1_13);
|
||||
}
|
||||
obj.setDisplayName(title);
|
||||
obj.a(IScoreboardCriteria.EnumScoreboardHealthDisplay.valueOf(objective.getHealthDisplay().name()));
|
||||
sendPacket(new PacketPlayOutScoreboardObjective(obj, ObjectiveAction.UPDATE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setScore(@NonNull Score score) {
|
||||
ScoreboardScore s = new ScoreboardScore(dummyScoreboard, (ScoreboardObjective) score.getObjective().getPlatformObjective(), score.getHolder());
|
||||
s.setScore(score.getValue());
|
||||
sendPacket(new PacketPlayOutScoreboardScore(s));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeScore(@NonNull Score score) {
|
||||
sendPacket(new PacketPlayOutScoreboardScore(score.getHolder()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Object createTeam(@NonNull String name) {
|
||||
return new ScoreboardTeam(dummyScoreboard, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerTeam(@NonNull Team team) {
|
||||
updateTeamProperties(team);
|
||||
ScoreboardTeam t = (ScoreboardTeam) team.getPlatformTeam();
|
||||
t.getPlayerNameSet().addAll(team.getPlayers());
|
||||
sendPacket(new PacketPlayOutScoreboardTeam(t, TeamAction.CREATE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterTeam(@NonNull Team team) {
|
||||
sendPacket(new PacketPlayOutScoreboardTeam((ScoreboardTeam) team.getPlatformTeam(), TeamAction.REMOVE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateTeam(@NonNull Team team) {
|
||||
updateTeamProperties(team);
|
||||
sendPacket(new PacketPlayOutScoreboardTeam((ScoreboardTeam) team.getPlatformTeam(), TeamAction.UPDATE));
|
||||
}
|
||||
|
||||
private void updateTeamProperties(@NonNull Team team) {
|
||||
ScoreboardTeam t = (ScoreboardTeam) team.getPlatformTeam();
|
||||
t.setAllowFriendlyFire((team.getOptions() & 0x01) != 0);
|
||||
t.setCanSeeFriendlyInvisibles((team.getOptions() & 0x02) != 0);
|
||||
t.setNameTagVisibility(visibilities[team.getVisibility().ordinal()]);
|
||||
t.setPrefix(cutTo(team.getPrefix().toLegacyText(), Limitations.TEAM_PREFIX_SUFFIX_PRE_1_13));
|
||||
t.setSuffix(cutTo(team.getSuffix().toLegacyText(), Limitations.TEAM_PREFIX_SUFFIX_PRE_1_13));
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
@SuppressWarnings("unchecked")
|
||||
public void onPacketSend(@NonNull Object packet) {
|
||||
if (isAntiOverrideScoreboard()) {
|
||||
if (packet instanceof PacketPlayOutScoreboardDisplayObjective) {
|
||||
TAB.getInstance().getFeatureManager().onDisplayObjective(player, DisplayObjective_POSITION.getInt(packet),
|
||||
(String) DisplayObjective_OBJECTIVE_NAME.get(packet));
|
||||
}
|
||||
if (packet instanceof PacketPlayOutScoreboardObjective) {
|
||||
TAB.getInstance().getFeatureManager().onObjective(player,
|
||||
Objective_METHOD.getInt(packet), (String) Objective_OBJECTIVE_NAME.get(packet));
|
||||
}
|
||||
}
|
||||
if (isAntiOverrideTeams() && packet instanceof PacketPlayOutScoreboardTeam) {
|
||||
int action = TeamPacket_ACTION.getInt(packet);
|
||||
if (action == TeamAction.UPDATE) return;
|
||||
Collection<String> players = (Collection<String>) TeamPacket_PLAYERS.get(packet);
|
||||
if (players == null) players = Collections.emptyList();
|
||||
TeamPacket_PLAYERS.set(packet, ((SafeScoreboard<?>)player.getScoreboard()).onTeamPacket(
|
||||
action, (String) TeamPacket_NAME.get(packet), players));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the packet to the player.
|
||||
*
|
||||
* @param packet
|
||||
* Packet to send
|
||||
*/
|
||||
private void sendPacket(@NotNull Packet<?> packet) {
|
||||
((CraftPlayer)player.getPlayer()).getHandle().playerConnection.sendPacket(packet);
|
||||
}
|
||||
}
|
||||
+178
@@ -0,0 +1,178 @@
|
||||
package me.neznamy.tab.platforms.bukkit.v1_8_R3;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import com.mojang.authlib.properties.Property;
|
||||
import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import me.neznamy.tab.shared.platform.decorators.TrackedTabList;
|
||||
import me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import net.minecraft.server.v1_8_R3.*;
|
||||
import net.minecraft.server.v1_8_R3.PacketPlayOutPlayerInfo.PlayerInfoData;
|
||||
import org.bukkit.craftbukkit.v1_8_R3.entity.CraftPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* TabList implementation using direct NMS code.
|
||||
*/
|
||||
public class NMSPacketTabList extends TrackedTabList<BukkitTabPlayer> {
|
||||
|
||||
private static final Field ACTION = ReflectionUtils.getOnlyField(PacketPlayOutPlayerInfo.class, PacketPlayOutPlayerInfo.EnumPlayerInfoAction.class);
|
||||
private static final Field PLAYERS = ReflectionUtils.getOnlyField(PacketPlayOutPlayerInfo.class, List.class);
|
||||
|
||||
private static final Field PlayerInfoData_Latency = ReflectionUtils.getFields(PlayerInfoData.class, int.class).get(0);
|
||||
private static final Field PlayerInfoData_DisplayName = ReflectionUtils.getOnlyField(PlayerInfoData.class, IChatBaseComponent.class);
|
||||
|
||||
private static final Field FOOTER = ReflectionUtils.getFields(PacketPlayOutPlayerListHeaderFooter.class, IChatBaseComponent.class).get(1);
|
||||
|
||||
/**
|
||||
* Constructs new instance.
|
||||
*
|
||||
* @param player
|
||||
* Player this tablist will belong to
|
||||
*/
|
||||
public NMSPacketTabList(@NotNull BukkitTabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void removeEntry(@NonNull UUID entry) {
|
||||
sendPacket(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.REMOVE_PLAYER, entry, "", null, 0, 0, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDisplayName0(@NonNull UUID entry, @Nullable TabComponent displayName) {
|
||||
sendPacket(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.UPDATE_DISPLAY_NAME, entry, "", null, 0, 0, displayName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateLatency(@NonNull UUID entry, int latency) {
|
||||
sendPacket(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.UPDATE_LATENCY, entry, "", null, latency, 0, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateGameMode(@NonNull UUID entry, int gameMode) {
|
||||
sendPacket(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.UPDATE_GAME_MODE, entry, "", null, 0, gameMode, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateListed(@NonNull UUID entry, boolean listed) {
|
||||
// Added in 1.19.3
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateListOrder(@NonNull UUID entry, int listOrder) {
|
||||
// Added in 1.20.2
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateHat(@NonNull UUID entry, boolean showHat) {
|
||||
// Added in 1.20.4
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addEntry0(@NonNull Entry entry) {
|
||||
sendPacket(PacketPlayOutPlayerInfo.EnumPlayerInfoAction.ADD_PLAYER, entry.getUniqueId(), entry.getName(), entry.getSkin(), entry.getLatency(),
|
||||
entry.getGameMode(), entry.getDisplayName());
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public void setPlayerListHeaderFooter(@NonNull TabComponent header, @NonNull TabComponent footer) {
|
||||
PacketPlayOutPlayerListHeaderFooter packet = new PacketPlayOutPlayerListHeaderFooter(header.convert());
|
||||
FOOTER.set(packet, footer.convert());
|
||||
sendPacket(packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsEntry(@NonNull UUID entry) {
|
||||
return true; // TODO?
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Skin getSkin() {
|
||||
Collection<Property> properties = ((CraftPlayer)player.getPlayer()).getProfile().getProperties().get(TEXTURES_PROPERTY);
|
||||
if (properties.isEmpty()) return null; // Offline mode
|
||||
Property property = properties.iterator().next();
|
||||
return new Skin(property.getValue(), property.getSignature());
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
@SuppressWarnings("unchecked")
|
||||
public void onPacketSend(@NonNull Object packet) {
|
||||
if (!(packet instanceof PacketPlayOutPlayerInfo)) return;
|
||||
String action = ACTION.get(packet).toString();
|
||||
for (PlayerInfoData nmsData : (List<PlayerInfoData>) PLAYERS.get(packet)) {
|
||||
GameProfile profile = nmsData.a();
|
||||
UUID id = profile.getId();
|
||||
if (action.equals(Action.UPDATE_DISPLAY_NAME.name()) || action.equals(Action.ADD_PLAYER.name())) {
|
||||
TabComponent expectedName = getExpectedDisplayNames().get(id);
|
||||
if (expectedName != null) PlayerInfoData_DisplayName.set(nmsData, expectedName.convert());
|
||||
}
|
||||
if (action.equals(Action.UPDATE_LATENCY.name()) || action.equals(Action.ADD_PLAYER.name())) {
|
||||
int oldLatency = PlayerInfoData_Latency.getInt(nmsData);
|
||||
int newLatency = TAB.getInstance().getFeatureManager().onLatencyChange(player, id, oldLatency);
|
||||
if (oldLatency != newLatency) {
|
||||
PlayerInfoData_Latency.set(nmsData, newLatency);
|
||||
}
|
||||
}
|
||||
if (action.equals(Action.ADD_PLAYER.name())) {
|
||||
TAB.getInstance().getFeatureManager().onEntryAdd(player, id, profile.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private void sendPacket(@NonNull PacketPlayOutPlayerInfo.EnumPlayerInfoAction action, @NonNull UUID id, @NonNull String name,
|
||||
@Nullable Skin skin, int latency, int gameMode, @Nullable TabComponent displayName) {
|
||||
PacketPlayOutPlayerInfo packet = new PacketPlayOutPlayerInfo(action);
|
||||
PLAYERS.set(packet, Collections.singletonList(packet.new PlayerInfoData(
|
||||
createProfile(id, name, skin),
|
||||
latency,
|
||||
WorldSettings.EnumGamemode.values()[gameMode],
|
||||
displayName == null ? null : displayName.convert())
|
||||
));
|
||||
sendPacket(packet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates GameProfile from given parameters.
|
||||
*
|
||||
* @param id
|
||||
* Profile ID
|
||||
* @param name
|
||||
* Profile name
|
||||
* @param skin
|
||||
* Player skin
|
||||
* @return GameProfile from given parameters
|
||||
*/
|
||||
@NotNull
|
||||
private GameProfile createProfile(@NonNull UUID id, @NonNull String name, @Nullable Skin skin) {
|
||||
GameProfile profile = new GameProfile(id, name);
|
||||
if (skin != null) {
|
||||
profile.getProperties().put(TabList.TEXTURES_PROPERTY,
|
||||
new Property(TabList.TEXTURES_PROPERTY, skin.getValue(), skin.getSignature()));
|
||||
}
|
||||
return profile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the packet to the player.
|
||||
*
|
||||
* @param packet
|
||||
* Packet to send
|
||||
*/
|
||||
private void sendPacket(@NotNull Packet<?> packet) {
|
||||
((CraftPlayer)player.getPlayer()).getHandle().playerConnection.sendPacket(packet);
|
||||
}
|
||||
}
|
||||
@@ -44,6 +44,10 @@ public class BungeeScoreboard extends SafeScoreboard<BungeeTabPlayer> {
|
||||
@Override
|
||||
public void registerObjective(@NonNull Objective objective) {
|
||||
sendObjectivePacket(objective, (byte) ObjectiveAction.REGISTER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDisplaySlot(@NonNull Objective objective) {
|
||||
player.sendPacket(new ScoreboardDisplay(objective.getDisplaySlot().ordinal(), objective.getName()));
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.proxy.ProxyTabPlayer;
|
||||
import net.md_5.bungee.UserConnection;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.api.connection.Server;
|
||||
import net.md_5.bungee.protocol.DefinedPacket;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@@ -53,7 +54,8 @@ public class BungeeTabPlayer extends ProxyTabPlayer {
|
||||
|
||||
@Override
|
||||
public void sendPluginMessage(byte[] message) {
|
||||
getPlayer().getServer().sendData(TabConstants.PLUGIN_MESSAGE_CHANNEL_NAME, message);
|
||||
Server server = getPlayer().getServer();
|
||||
if (server != null) server.sendData(TabConstants.PLUGIN_MESSAGE_CHANNEL_NAME, message);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
plugins {
|
||||
id("fabric-loom")
|
||||
id("dev.architectury.loom")
|
||||
}
|
||||
|
||||
repositories {
|
||||
@@ -12,12 +12,12 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api(projects.shared)
|
||||
minecraft("com.mojang:minecraft:1.21.5")
|
||||
mappings(loom.officialMojangMappings())
|
||||
api(projects.shared)
|
||||
modImplementation("me.lucko:fabric-permissions-api:0.2-SNAPSHOT")
|
||||
modImplementation("eu.pb4:placeholder-api:2.5.0+1.21.2")
|
||||
modImplementation("net.fabricmc:fabric-loader:0.15.9")
|
||||
modImplementation("net.fabricmc:fabric-loader:0.15.10")
|
||||
val version = "0.100.1+1.21"
|
||||
modImplementation(fabricApi.module("fabric-lifecycle-events-v1", version))
|
||||
modImplementation(fabricApi.module("fabric-networking-api-v1", version))
|
||||
@@ -25,9 +25,7 @@ dependencies {
|
||||
modImplementation(fabricApi.module("fabric-command-api-v2", version))
|
||||
}
|
||||
|
||||
loom {
|
||||
accessWidenerPath.set(file("src/main/resources/resources/tab.accesswidener"))
|
||||
}
|
||||
loom.accessWidenerPath.set(file("src/main/resources/resources/tab.accesswidener"))
|
||||
|
||||
tasks {
|
||||
compileJava {
|
||||
|
||||
@@ -18,8 +18,8 @@ public class FabricEventListener implements EventListener<ServerPlayer> {
|
||||
* Registers all event listeners.
|
||||
*/
|
||||
public void register() {
|
||||
ServerPlayConnectionEvents.DISCONNECT.register((connection, $) -> quit(connection.player.getUUID()));
|
||||
ServerPlayConnectionEvents.JOIN.register((connection, $, $$) -> join(connection.player));
|
||||
ServerPlayConnectionEvents.DISCONNECT.register((connection, server) -> quit(connection.player.getUUID()));
|
||||
ServerPlayConnectionEvents.JOIN.register((connection, sender, server) -> join(connection.player));
|
||||
//TODO command preprocess
|
||||
ServerPlayerEvents.AFTER_RESPAWN.register(
|
||||
(oldPlayer, newPlayer, alive) -> {
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
package me.neznamy.tab.platforms.fabric;
|
||||
|
||||
import com.mojang.logging.LogUtils;
|
||||
import eu.pb4.placeholders.api.PlaceholderContext;
|
||||
import eu.pb4.placeholders.api.Placeholders;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import me.neznamy.chat.component.KeybindComponent;
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import me.neznamy.chat.component.TextComponent;
|
||||
@@ -40,13 +39,10 @@ import java.util.Collections;
|
||||
|
||||
/**
|
||||
* Platform implementation for Fabric
|
||||
*
|
||||
* @param server Minecraft server reference
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@Getter
|
||||
public class FabricPlatform implements BackendPlatform {
|
||||
|
||||
/** Minecraft server reference */
|
||||
private final MinecraftServer server;
|
||||
public record FabricPlatform(MinecraftServer server) implements BackendPlatform {
|
||||
|
||||
@Override
|
||||
public void registerUnknownPlaceholder(@NotNull String identifier) {
|
||||
@@ -98,12 +94,12 @@ public class FabricPlatform implements BackendPlatform {
|
||||
|
||||
@Override
|
||||
public void logInfo(@NotNull TabComponent message) {
|
||||
MinecraftServer.LOGGER.info("[TAB] " + message.toRawText());
|
||||
LogUtils.getLogger().info("[TAB] {}", message.toRawText());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logWarn(@NotNull TabComponent message) {
|
||||
MinecraftServer.LOGGER.warn("[TAB] " + message.toRawText());
|
||||
LogUtils.getLogger().warn("[TAB] {}", message.toRawText());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -48,7 +48,11 @@ public class FabricScoreboard extends SafeScoreboard<FabricTabPlayer> {
|
||||
);
|
||||
objective.setPlatformObjective(obj);
|
||||
sendPacket(new ClientboundSetObjectivePacket(obj, ObjectiveAction.REGISTER));
|
||||
sendPacket(new ClientboundSetDisplayObjectivePacket(net.minecraft.world.scores.DisplaySlot.values()[objective.getDisplaySlot().ordinal()], obj));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDisplaySlot(@NonNull Objective objective) {
|
||||
sendPacket(new ClientboundSetDisplayObjectivePacket(net.minecraft.world.scores.DisplaySlot.values()[objective.getDisplaySlot().ordinal()], (net.minecraft.world.scores.Objective) objective.getPlatformObjective()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -128,7 +132,7 @@ public class FabricScoreboard extends SafeScoreboard<FabricTabPlayer> {
|
||||
}
|
||||
if (isAntiOverrideTeams() && packet instanceof ClientboundSetPlayerTeamPacket team) {
|
||||
if (team.method == TeamAction.UPDATE) return;
|
||||
team.players = onTeamPacket(team.method, team.getName(), team.players);
|
||||
team.players = onTeamPacket(team.method, team.getName(), team.getPlayers());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,9 +15,9 @@ public class FabricTAB implements DedicatedServerModInitializer {
|
||||
|
||||
@Override
|
||||
public void onInitializeServer() {
|
||||
CommandRegistrationCallback.EVENT.register((dispatcher, $, $$) -> new FabricTabCommand().onRegisterCommands(dispatcher));
|
||||
CommandRegistrationCallback.EVENT.register((dispatcher, commandBuildContext, commandSelection) -> new FabricTabCommand().onRegisterCommands(dispatcher));
|
||||
ServerLifecycleEvents.SERVER_STARTING.register(server -> TAB.create(new FabricPlatform(server)));
|
||||
ServerLifecycleEvents.SERVER_STOPPING.register($ -> TAB.getInstance().unload());
|
||||
ServerLifecycleEvents.SERVER_STOPPING.register(server -> TAB.getInstance().unload());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
"name": "@name@",
|
||||
"description": "@description@",
|
||||
"authors": ["@author@"],
|
||||
"contributors": ["credits"],
|
||||
"environment": "server",
|
||||
"entrypoints": {
|
||||
"server": ["me.neznamy.tab.platforms.fabric.FabricTAB"]
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
accessWidener v1 named
|
||||
accessWidener v2 named
|
||||
|
||||
# Scoreboard packets
|
||||
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;
|
||||
|
||||
# Logger
|
||||
accessible field net/minecraft/server/MinecraftServer LOGGER Lorg/slf4j/Logger;
|
||||
|
||||
# Tablist packet entries
|
||||
accessible field net/minecraft/network/protocol/game/ClientboundPlayerInfoUpdatePacket entries Ljava/util/List;
|
||||
mutable field net/minecraft/network/protocol/game/ClientboundPlayerInfoUpdatePacket entries Ljava/util/List;
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
plugins {
|
||||
id("dev.architectury.loom")
|
||||
}
|
||||
|
||||
repositories {
|
||||
// Gradle doesn't support combining settings and project repositories, so we have to re-declare all the settings repos we need
|
||||
maven("https://jitpack.io") // YamlAssist
|
||||
maven("https://repo.opencollab.dev/maven-snapshots/")
|
||||
maven("https://repo.viaversion.com/")
|
||||
maven("https://oss.sonatype.org/content/repositories/snapshots")
|
||||
maven("https://maven.nucleoid.xyz/")
|
||||
maven("https://maven.neoforged.net/releases")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
minecraft("com.mojang:minecraft:1.21.5")
|
||||
mappings(loom.officialMojangMappings())
|
||||
forge("net.minecraftforge:forge:1.21.5-55.0.3")
|
||||
api(projects.shared)
|
||||
compileOnly("net.luckperms:api:5.4")
|
||||
}
|
||||
|
||||
loom.forge.accessTransformer(file("src/main/resources/META-INF/accesstransformer.cfg"))
|
||||
|
||||
tasks {
|
||||
compileJava {
|
||||
options.release.set(21)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
loom.platform=forge
|
||||
@@ -0,0 +1,64 @@
|
||||
package me.neznamy.tab.platforms.forge;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import me.neznamy.tab.api.bossbar.BarColor;
|
||||
import me.neznamy.tab.api.bossbar.BarStyle;
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import me.neznamy.tab.shared.platform.decorators.SafeBossBar;
|
||||
import net.minecraft.server.level.ServerBossEvent;
|
||||
import net.minecraft.world.BossEvent.BossBarColor;
|
||||
import net.minecraft.world.BossEvent.BossBarOverlay;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* BossBar implementation for Forge using packets.
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class ForgeBossBar extends SafeBossBar<ServerBossEvent> {
|
||||
|
||||
/** Player this BossBar belongs to */
|
||||
@NotNull
|
||||
private final ForgeTabPlayer player;
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public ServerBossEvent constructBossBar(@NotNull TabComponent title, float progress, @NotNull BarColor color, @NotNull BarStyle style) {
|
||||
ServerBossEvent bar = new ServerBossEvent(
|
||||
title.convert(),
|
||||
BossBarColor.valueOf(color.name()),
|
||||
BossBarOverlay.valueOf(style.name())
|
||||
);
|
||||
bar.setProgress(progress);
|
||||
return bar;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void create(@NotNull BossBarInfo bar) {
|
||||
bar.getBossBar().addPlayer(player.getPlayer());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateTitle(@NotNull BossBarInfo bar) {
|
||||
bar.getBossBar().setName(bar.getTitle().convert());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateProgress(@NotNull BossBarInfo bar) {
|
||||
bar.getBossBar().setProgress(bar.getProgress());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateStyle(@NotNull BossBarInfo bar) {
|
||||
bar.getBossBar().setOverlay(BossBarOverlay.valueOf(bar.getStyle().name()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateColor(@NotNull BossBarInfo bar) {
|
||||
bar.getBossBar().setColor(BossBarColor.valueOf(bar.getColor().name()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(@NotNull BossBarInfo bar) {
|
||||
bar.getBossBar().removePlayer(player.getPlayer());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package me.neznamy.tab.platforms.forge;
|
||||
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.platform.EventListener;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraftforge.common.MinecraftForge;
|
||||
import net.minecraftforge.event.entity.player.PlayerEvent;
|
||||
import net.minecraftforge.eventbus.api.IEventBus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Event listener for Forge.
|
||||
*/
|
||||
public class ForgeEventListener implements EventListener<ServerPlayer> {
|
||||
|
||||
/**
|
||||
* Registers all event listeners.
|
||||
*/
|
||||
public void register() {
|
||||
IEventBus eventBus = MinecraftForge.EVENT_BUS;
|
||||
eventBus.addListener((PlayerEvent.PlayerLoggedInEvent event) -> join((ServerPlayer) event.getEntity()));
|
||||
eventBus.addListener((PlayerEvent.PlayerLoggedOutEvent event) -> quit(event.getEntity().getUUID()));
|
||||
eventBus.addListener((PlayerEvent.PlayerRespawnEvent event) -> {
|
||||
ServerPlayer player = (ServerPlayer) event.getEntity();
|
||||
replacePlayer(player.getUUID(), player);
|
||||
worldChange(player.getUUID(), ForgeTAB.getLevelName(player.level()));
|
||||
});
|
||||
eventBus.addListener((PlayerEvent.PlayerChangedDimensionEvent event) -> worldChange(event.getEntity().getUUID(), ForgeTAB.getLevelName(event.getEntity().level())));
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public TabPlayer createPlayer(@NotNull ServerPlayer player) {
|
||||
return new ForgeTabPlayer((ForgePlatform) TAB.getInstance().getPlatform(), player);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package me.neznamy.tab.platforms.forge;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import me.neznamy.tab.shared.features.injection.NettyPipelineInjector;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Pipeline injector for Forge.
|
||||
*/
|
||||
public class ForgePipelineInjector extends NettyPipelineInjector {
|
||||
|
||||
/**
|
||||
* Constructs new instance.
|
||||
*/
|
||||
public ForgePipelineInjector() {
|
||||
super("packet_handler");
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
protected Channel getChannel(@NotNull TabPlayer player) {
|
||||
return ((ForgeTabPlayer)player).getPlayer().connection.getConnection().channel();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
package me.neznamy.tab.platforms.forge;
|
||||
|
||||
import com.mojang.logging.LogUtils;
|
||||
import me.neznamy.chat.component.KeybindComponent;
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import me.neznamy.chat.component.TextComponent;
|
||||
import me.neznamy.chat.component.TranslatableComponent;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.backend.BackendPlatform;
|
||||
import me.neznamy.tab.shared.features.PerWorldPlayerListConfiguration;
|
||||
import me.neznamy.tab.shared.features.injection.PipelineInjector;
|
||||
import me.neznamy.tab.shared.features.types.TabFeature;
|
||||
import me.neznamy.tab.shared.platform.BossBar;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import net.minecraft.SharedConstants;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.network.chat.MutableComponent;
|
||||
import net.minecraft.network.chat.Style;
|
||||
import net.minecraft.network.chat.TextColor;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraftforge.fml.loading.FMLPaths;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* Platform implementation for NeoForge
|
||||
*
|
||||
* @param server Minecraft server reference
|
||||
*/
|
||||
public record ForgePlatform(MinecraftServer server) implements BackendPlatform {
|
||||
|
||||
@Override
|
||||
public void registerUnknownPlaceholder(@NotNull String identifier) {
|
||||
registerDummyPlaceholder(identifier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadPlayers() {
|
||||
for (ServerPlayer player : getOnlinePlayers()) {
|
||||
TAB.getInstance().addPlayer(new ForgeTabPlayer(this, player));
|
||||
}
|
||||
}
|
||||
|
||||
private Collection<ServerPlayer> getOnlinePlayers() {
|
||||
// It's nullable on startup
|
||||
return server.getPlayerList() == null ? Collections.emptyList() : server.getPlayerList().getPlayers();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public PipelineInjector createPipelineInjector() {
|
||||
return new ForgePipelineInjector();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public TabFeature getPerWorldPlayerList(@NotNull PerWorldPlayerListConfiguration configuration) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logInfo(@NotNull TabComponent message) {
|
||||
LogUtils.getLogger().info("[TAB] {}", message.toRawText());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logWarn(@NotNull TabComponent message) {
|
||||
LogUtils.getLogger().warn("[TAB] {}", message.toRawText());
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String getServerVersionInfo() {
|
||||
return "[Forge] " + SharedConstants.getCurrentVersion().getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerListener() {
|
||||
new ForgeEventListener().register();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerCommand() {
|
||||
// Event listener must be registered in main class
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startMetrics() {
|
||||
// Not available
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public File getDataFolder() {
|
||||
return FMLPaths.CONFIGDIR.get().resolve(TabConstants.PLUGIN_ID).toFile();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Component convertComponent(@NotNull TabComponent component) {
|
||||
// Component type
|
||||
MutableComponent nmsComponent;
|
||||
if (component instanceof TextComponent) {
|
||||
nmsComponent = Component.literal(((TextComponent) component).getText());
|
||||
} else if (component instanceof TranslatableComponent) {
|
||||
nmsComponent = Component.translatable(((TranslatableComponent) component).getKey());
|
||||
} else if (component instanceof KeybindComponent) {
|
||||
nmsComponent = Component.keybind(((KeybindComponent) component).getKeybind());
|
||||
} else {
|
||||
throw new IllegalStateException("Unexpected component type: " + component.getClass().getName());
|
||||
}
|
||||
|
||||
// Component style
|
||||
nmsComponent.setStyle(new Style(
|
||||
component.getModifier().getColor() == null ? null : TextColor.fromRgb(component.getModifier().getColor().getRgb()),
|
||||
component.getModifier().getShadowColor(),
|
||||
component.getModifier().getBold(),
|
||||
component.getModifier().getItalic(),
|
||||
component.getModifier().getUnderlined(),
|
||||
component.getModifier().getStrikethrough(),
|
||||
component.getModifier().getObfuscated(),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
component.getModifier().getFont() == null ? null : ResourceLocation.tryParse(component.getModifier().getFont())
|
||||
));
|
||||
|
||||
// Extra
|
||||
for (TabComponent extra : component.getExtra()) {
|
||||
nmsComponent.getSiblings().add(convertComponent(extra));
|
||||
}
|
||||
|
||||
return nmsComponent;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Scoreboard createScoreboard(@NotNull TabPlayer player) {
|
||||
return new ForgeScoreboard((ForgeTabPlayer) player);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public BossBar createBossBar(@NotNull TabPlayer player) {
|
||||
return new ForgeBossBar((ForgeTabPlayer) player);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public TabList createTabList(@NotNull TabPlayer player) {
|
||||
return new ForgeTabList((ForgeTabPlayer) player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsScoreboards() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getTPS() {
|
||||
double mspt = getMSPT();
|
||||
if (mspt < 50) return 20;
|
||||
return Math.round(1000 / mspt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getMSPT() {
|
||||
return (float) server.getAverageTickTimeNanos() / 1000000;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
package me.neznamy.tab.platforms.forge;
|
||||
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.platform.decorators.SafeScoreboard;
|
||||
import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.network.chat.numbers.FixedFormat;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.network.protocol.game.*;
|
||||
import net.minecraft.world.scores.PlayerTeam;
|
||||
import net.minecraft.world.scores.Scoreboard;
|
||||
import net.minecraft.world.scores.criteria.ObjectiveCriteria;
|
||||
import net.minecraft.world.scores.criteria.ObjectiveCriteria.RenderType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Scoreboard implementation for Forge using packets.
|
||||
*/
|
||||
public class ForgeScoreboard extends SafeScoreboard<ForgeTabPlayer> {
|
||||
|
||||
private static final ChatFormatting[] formats = ChatFormatting.values();
|
||||
private static final net.minecraft.world.scores.Team.CollisionRule[] collisions = net.minecraft.world.scores.Team.CollisionRule.values();
|
||||
private static final net.minecraft.world.scores.Team.Visibility[] visibilities = net.minecraft.world.scores.Team.Visibility.values();
|
||||
private static final Scoreboard dummyScoreboard = new Scoreboard();
|
||||
|
||||
/**
|
||||
* Constructs new instance with given player.
|
||||
*
|
||||
* @param player
|
||||
* Player this scoreboard will belong to
|
||||
*/
|
||||
public ForgeScoreboard(ForgeTabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerObjective(@NonNull Objective objective) {
|
||||
net.minecraft.world.scores.Objective obj = new net.minecraft.world.scores.Objective(
|
||||
dummyScoreboard,
|
||||
objective.getName(),
|
||||
ObjectiveCriteria.DUMMY,
|
||||
objective.getTitle().convert(),
|
||||
RenderType.values()[objective.getHealthDisplay().ordinal()],
|
||||
false,
|
||||
objective.getNumberFormat() == null ? null : objective.getNumberFormat().toFixedFormat(FixedFormat::new)
|
||||
);
|
||||
objective.setPlatformObjective(obj);
|
||||
sendPacket(new ClientboundSetObjectivePacket(obj, ObjectiveAction.REGISTER));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDisplaySlot(@NonNull Objective objective) {
|
||||
sendPacket(new ClientboundSetDisplayObjectivePacket(net.minecraft.world.scores.DisplaySlot.values()[objective.getDisplaySlot().ordinal()], (net.minecraft.world.scores.Objective) objective.getPlatformObjective()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterObjective(@NonNull Objective objective) {
|
||||
sendPacket(new ClientboundSetObjectivePacket((net.minecraft.world.scores.Objective) objective.getPlatformObjective(), ObjectiveAction.UNREGISTER));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateObjective(@NonNull Objective objective) {
|
||||
net.minecraft.world.scores.Objective obj = (net.minecraft.world.scores.Objective) objective.getPlatformObjective();
|
||||
obj.setDisplayName(objective.getTitle().convert());
|
||||
obj.setRenderType(RenderType.values()[objective.getHealthDisplay().ordinal()]);
|
||||
sendPacket(new ClientboundSetObjectivePacket(obj, ObjectiveAction.UPDATE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setScore(@NonNull Score score) {
|
||||
sendPacket(new ClientboundSetScorePacket(
|
||||
score.getHolder(),
|
||||
score.getObjective().getName(),
|
||||
score.getValue(),
|
||||
Optional.ofNullable(score.getDisplayName() == null ? null : score.getDisplayName().convert()),
|
||||
Optional.ofNullable(score.getNumberFormat() == null ? null : score.getNumberFormat().toFixedFormat(FixedFormat::new))
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeScore(@NonNull Score score) {
|
||||
sendPacket(new ClientboundResetScorePacket(score.getHolder(), score.getObjective().getName()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Object createTeam(@NonNull String name) {
|
||||
return new PlayerTeam(dummyScoreboard, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerTeam(@NonNull Team team) {
|
||||
updateTeamProperties(team);
|
||||
PlayerTeam t = (PlayerTeam) team.getPlatformTeam();
|
||||
t.getPlayers().addAll(team.getPlayers());
|
||||
sendPacket(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(t, true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterTeam(@NonNull Team team) {
|
||||
sendPacket(ClientboundSetPlayerTeamPacket.createRemovePacket((PlayerTeam) team.getPlatformTeam()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateTeam(@NonNull Team team) {
|
||||
updateTeamProperties(team);
|
||||
sendPacket(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket((PlayerTeam) team.getPlatformTeam(), false));
|
||||
}
|
||||
|
||||
private void updateTeamProperties(@NonNull Team team) {
|
||||
PlayerTeam t = (PlayerTeam) team.getPlatformTeam();
|
||||
t.setAllowFriendlyFire((team.getOptions() & 0x01) != 0);
|
||||
t.setSeeFriendlyInvisibles((team.getOptions() & 0x02) != 0);
|
||||
t.setColor(formats[team.getColor().getLegacyColor().ordinal()]);
|
||||
t.setCollisionRule(collisions[team.getCollision().ordinal()]);
|
||||
t.setNameTagVisibility(visibilities[team.getVisibility().ordinal()]);
|
||||
t.setPlayerPrefix(team.getPrefix().convert());
|
||||
t.setPlayerSuffix(team.getSuffix().convert());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketSend(@NonNull Object packet) {
|
||||
if (isAntiOverrideScoreboard()) {
|
||||
if (packet instanceof ClientboundSetDisplayObjectivePacket display) {
|
||||
TAB.getInstance().getFeatureManager().onDisplayObjective(player, display.getSlot().ordinal(), display.getObjectiveName());
|
||||
}
|
||||
if (packet instanceof ClientboundSetObjectivePacket objective) {
|
||||
TAB.getInstance().getFeatureManager().onObjective(player, objective.getMethod(), objective.getObjectiveName());
|
||||
}
|
||||
}
|
||||
if (isAntiOverrideTeams() && packet instanceof ClientboundSetPlayerTeamPacket team) {
|
||||
if (team.method == TeamAction.UPDATE) return;
|
||||
team.players = onTeamPacket(team.method, team.getName(), team.getPlayers());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the packet to the player.
|
||||
*
|
||||
* @param packet
|
||||
* Packet to send
|
||||
*/
|
||||
private void sendPacket(@NotNull Packet<?> packet) {
|
||||
player.getPlayer().connection.send(packet);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package me.neznamy.tab.platforms.forge;
|
||||
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.storage.ServerLevelData;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
import net.minecraftforge.common.MinecraftForge;
|
||||
import net.minecraftforge.event.RegisterCommandsEvent;
|
||||
import net.minecraftforge.event.server.ServerStartingEvent;
|
||||
import net.minecraftforge.event.server.ServerStoppingEvent;
|
||||
import net.minecraftforge.eventbus.api.IEventBus;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@OnlyIn(Dist.DEDICATED_SERVER)
|
||||
@Mod(value = "tab")
|
||||
public class ForgeTAB {
|
||||
|
||||
public ForgeTAB() {
|
||||
IEventBus EVENT_BUS = MinecraftForge.EVENT_BUS;
|
||||
EVENT_BUS.addListener((RegisterCommandsEvent event) -> new ForgeTabCommand().onRegisterCommands(event.getDispatcher()));
|
||||
EVENT_BUS.addListener((ServerStartingEvent event) -> TAB.create(new ForgePlatform(event.getServer())));
|
||||
EVENT_BUS.addListener((ServerStoppingEvent event) -> TAB.getInstance().unload());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static String getLevelName(@NotNull Level level) {
|
||||
String path = level.dimension().location().getPath();
|
||||
return ((ServerLevelData)level.getLevelData()).getLevelName() + switch (path) {
|
||||
case "overworld" -> ""; // No suffix for overworld
|
||||
case "the_nether" -> "_nether";
|
||||
default -> "_" + path; // End + default behavior for other dimensions created by mods
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package me.neznamy.tab.platforms.forge;
|
||||
|
||||
import com.mojang.brigadier.CommandDispatcher;
|
||||
import com.mojang.brigadier.arguments.StringArgumentType;
|
||||
import com.mojang.brigadier.context.CommandContext;
|
||||
import com.mojang.brigadier.suggestion.Suggestions;
|
||||
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
|
||||
import com.mojang.brigadier.tree.ArgumentCommandNode;
|
||||
import com.mojang.brigadier.tree.LiteralCommandNode;
|
||||
import me.neznamy.tab.platforms.forge.hook.LuckPermsAPIHook;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
import net.minecraft.commands.Commands;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
/**
|
||||
* Command handler for plugin's command for Forge.
|
||||
*/
|
||||
public class ForgeTabCommand {
|
||||
|
||||
/**
|
||||
* Handles command register request from server and registers own command.
|
||||
*
|
||||
* @param dispatcher
|
||||
* Dispatcher to register command to
|
||||
*/
|
||||
public void onRegisterCommands(@NotNull CommandDispatcher<CommandSourceStack> dispatcher) {
|
||||
LiteralCommandNode<CommandSourceStack> command = Commands.literal("tab") // TODO extract it from Platform somehow
|
||||
.executes(context -> executeCommand(context.getSource(), new String[0]))
|
||||
.build();
|
||||
ArgumentCommandNode<CommandSourceStack, String> args = Commands.argument("args", StringArgumentType.greedyString())
|
||||
.suggests((context, builder) -> getSuggestions(context.getSource(), getArguments(context), builder))
|
||||
.executes(context -> executeCommand(context.getSource(), getArguments(context)))
|
||||
.build();
|
||||
command.addChild(args);
|
||||
dispatcher.getRoot().addChild(command);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private String[] getArguments(@NotNull CommandContext<CommandSourceStack> context) {
|
||||
String input = context.getInput();
|
||||
int firstSpace = input.indexOf(' ');
|
||||
if (firstSpace == -1) return new String[0];
|
||||
String rawArgs = input.substring(firstSpace + 1);
|
||||
String[] args = rawArgs.split(" ");
|
||||
if (rawArgs.endsWith(" ")) {
|
||||
args = Arrays.copyOf(args, args.length+1);
|
||||
args[args.length-1] = "";
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
@SuppressWarnings("SameReturnValue") // Unused by plugin
|
||||
private int executeCommand(@NotNull CommandSourceStack source, @NotNull String[] args) {
|
||||
if (TAB.getInstance().isPluginDisabled()) {
|
||||
boolean hasReloadPermission = LuckPermsAPIHook.hasPermission(source, TabConstants.Permission.COMMAND_RELOAD);
|
||||
boolean hasAdminPermission = LuckPermsAPIHook.hasPermission(source, TabConstants.Permission.COMMAND_ALL);
|
||||
for (String message : TAB.getInstance().getDisabledCommand().execute(args, hasReloadPermission, hasAdminPermission)) {
|
||||
source.sendSystemMessage(TabComponent.fromColoredText(message).convert());
|
||||
}
|
||||
} else {
|
||||
if (source.getEntity() == null) {
|
||||
TAB.getInstance().getCommand().execute(null, args);
|
||||
} else {
|
||||
TabPlayer player = TAB.getInstance().getPlayer(source.getEntity().getUUID());
|
||||
if (player != null) TAB.getInstance().getCommand().execute(player, args);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private CompletableFuture<Suggestions> getSuggestions(@NotNull CommandSourceStack source, @NotNull String[] args,
|
||||
@NotNull SuggestionsBuilder builder) {
|
||||
TabPlayer player = null;
|
||||
if (source.getEntity() != null) {
|
||||
player = TAB.getInstance().getPlayer(source.getEntity().getUUID());
|
||||
if (player == null) return Suggestions.empty();
|
||||
}
|
||||
|
||||
SuggestionsBuilder newBuilder = builder;
|
||||
int lastSpace = newBuilder.getRemaining().lastIndexOf(' ');
|
||||
if (lastSpace != -1) {
|
||||
newBuilder = newBuilder.createOffset(lastSpace + 1 + newBuilder.getStart());
|
||||
}
|
||||
for (String suggestion : TAB.getInstance().getCommand().complete(player, args)) {
|
||||
newBuilder.suggest(suggestion);
|
||||
}
|
||||
return newBuilder.buildFuture();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
package me.neznamy.tab.platforms.forge;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import com.mojang.authlib.properties.Property;
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import me.neznamy.tab.shared.platform.decorators.TrackedTabList;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket;
|
||||
import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket;
|
||||
import net.minecraft.network.protocol.game.ClientboundTabListPacket;
|
||||
import net.minecraft.world.level.GameType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* TabList implementation for Forge using packets.
|
||||
*/
|
||||
public class ForgeTabList extends TrackedTabList<ForgeTabPlayer> {
|
||||
|
||||
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> addPlayer = EnumSet.allOf(ClientboundPlayerInfoUpdatePacket.Action.class);
|
||||
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateDisplayName = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME);
|
||||
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateLatency = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY);
|
||||
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateGameMode = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE);
|
||||
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateListed = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED);
|
||||
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateListOrder = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LIST_ORDER);
|
||||
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateHat = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_HAT);
|
||||
|
||||
/**
|
||||
* Constructs new instance.
|
||||
*
|
||||
* @param player
|
||||
* Player this tablist will belong to
|
||||
*/
|
||||
public ForgeTabList(@NotNull ForgeTabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeEntry(@NonNull UUID entry) {
|
||||
sendPacket(new ClientboundPlayerInfoRemovePacket(Collections.singletonList(entry)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDisplayName0(@NonNull UUID entry, @Nullable TabComponent displayName) {
|
||||
sendPacket(updateDisplayName, entry, "", null, false, 0, 0, displayName, 0, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateLatency(@NonNull UUID entry, int latency) {
|
||||
sendPacket(updateLatency, entry, "", null, false, latency, 0, null, 0, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateGameMode(@NonNull UUID entry, int gameMode) {
|
||||
sendPacket(updateGameMode, entry, "", null, false, 0, gameMode, null, 0, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateListed(@NonNull UUID entry, boolean listed) {
|
||||
sendPacket(updateListed, entry, "", null, listed, 0, 0, null, 0, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateListOrder(@NonNull UUID entry, int listOrder) {
|
||||
sendPacket(updateListOrder, entry, "", null, false, 0, 0, null, listOrder, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateHat(@NonNull UUID entry, boolean showHat) {
|
||||
sendPacket(updateHat, entry, "", null, false, 0, 0, null, 0, showHat);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addEntry0(@NonNull Entry entry) {
|
||||
sendPacket(addPlayer, entry.getUniqueId(), entry.getName(), entry.getSkin(), entry.isListed(), entry.getLatency(),
|
||||
entry.getGameMode(), entry.getDisplayName(), entry.getListOrder(), entry.isShowHat());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPlayerListHeaderFooter(@NonNull TabComponent header, @NonNull TabComponent footer) {
|
||||
sendPacket(new ClientboundTabListPacket(header.convert(), footer.convert()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsEntry(@NonNull UUID entry) {
|
||||
return true; // TODO?
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Skin getSkin() {
|
||||
Collection<Property> properties = player.getPlayer().getGameProfile().getProperties().get(TEXTURES_PROPERTY);
|
||||
if (properties.isEmpty()) return null; // Offline mode
|
||||
Property property = properties.iterator().next();
|
||||
return new Skin(property.value(), property.signature());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketSend(@NonNull Object packet) {
|
||||
if (packet instanceof ClientboundPlayerInfoUpdatePacket info) {
|
||||
EnumSet<ClientboundPlayerInfoUpdatePacket.Action> actions = info.actions();
|
||||
List<ClientboundPlayerInfoUpdatePacket.Entry> updatedList = new ArrayList<>();
|
||||
boolean rewritePacket = false;
|
||||
for (ClientboundPlayerInfoUpdatePacket.Entry nmsData : info.entries()) {
|
||||
boolean rewriteEntry = false;
|
||||
Component displayName = nmsData.displayName();
|
||||
int latency = nmsData.latency();
|
||||
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME)) {
|
||||
TabComponent expectedDisplayName = getExpectedDisplayNames().get(nmsData.profileId());
|
||||
if (expectedDisplayName != null && expectedDisplayName.convert() != displayName) {
|
||||
displayName = expectedDisplayName.convert();
|
||||
rewriteEntry = rewritePacket = true;
|
||||
}
|
||||
}
|
||||
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY)) {
|
||||
int newLatency = TAB.getInstance().getFeatureManager().onLatencyChange(player, nmsData.profileId(), latency);
|
||||
if (newLatency != latency) {
|
||||
latency = newLatency;
|
||||
rewriteEntry = rewritePacket = true;
|
||||
}
|
||||
}
|
||||
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER)) {
|
||||
TAB.getInstance().getFeatureManager().onEntryAdd(player, nmsData.profileId(), nmsData.profile().getName());
|
||||
}
|
||||
updatedList.add(rewriteEntry ? new ClientboundPlayerInfoUpdatePacket.Entry(
|
||||
nmsData.profileId(), nmsData.profile(), nmsData.listed(), latency, nmsData.gameMode(), displayName,
|
||||
nmsData.showHat(), nmsData.listOrder(), nmsData.chatSession()
|
||||
) : nmsData);
|
||||
}
|
||||
if (rewritePacket) info.entries = updatedList;
|
||||
}
|
||||
}
|
||||
|
||||
private void sendPacket(@NonNull EnumSet<ClientboundPlayerInfoUpdatePacket.Action> action, @NonNull UUID id, @NonNull String name, @Nullable Skin skin,
|
||||
boolean listed, int latency, int gameMode, @Nullable TabComponent displayName, int listOrder, boolean showHat) {
|
||||
ClientboundPlayerInfoUpdatePacket packet = new ClientboundPlayerInfoUpdatePacket(action, Collections.emptyList());
|
||||
packet.entries = Collections.singletonList(new ClientboundPlayerInfoUpdatePacket.Entry(
|
||||
id,
|
||||
action.contains(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER) ? createProfile(id, name, skin) : null,
|
||||
listed,
|
||||
latency,
|
||||
GameType.byId(gameMode),
|
||||
displayName == null ? null : displayName.convert(),
|
||||
showHat,
|
||||
listOrder,
|
||||
null
|
||||
));
|
||||
sendPacket(packet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates GameProfile from given parameters.
|
||||
*
|
||||
* @param id
|
||||
* Profile ID
|
||||
* @param name
|
||||
* Profile name
|
||||
* @param skin
|
||||
* Player skin
|
||||
* @return GameProfile from given parameters
|
||||
*/
|
||||
@NotNull
|
||||
private GameProfile createProfile(@NonNull UUID id, @NonNull String name, @Nullable Skin skin) {
|
||||
GameProfile profile = new GameProfile(id, name);
|
||||
if (skin != null) {
|
||||
profile.getProperties().put(TabList.TEXTURES_PROPERTY,
|
||||
new Property(TabList.TEXTURES_PROPERTY, skin.getValue(), skin.getSignature()));
|
||||
}
|
||||
return profile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the packet to the player.
|
||||
*
|
||||
* @param packet
|
||||
* Packet to send
|
||||
*/
|
||||
private void sendPacket(@NotNull Packet<?> packet) {
|
||||
player.getPlayer().connection.send(packet);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package me.neznamy.tab.platforms.forge;
|
||||
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import me.neznamy.tab.platforms.forge.hook.LuckPermsAPIHook;
|
||||
import me.neznamy.tab.shared.backend.BackendTabPlayer;
|
||||
import net.minecraft.SharedConstants;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* TabPlayer implementation for Forge.
|
||||
*/
|
||||
public class ForgeTabPlayer extends BackendTabPlayer {
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters.
|
||||
*
|
||||
* @param platform
|
||||
* Server platform
|
||||
* @param player
|
||||
* Platform's player object
|
||||
*/
|
||||
public ForgeTabPlayer(@NotNull ForgePlatform platform, @NotNull ServerPlayer player) {
|
||||
super(platform, player, player.getUUID(), player.getGameProfile().getName(),
|
||||
ForgeTAB.getLevelName(player.level()), SharedConstants.getProtocolVersion());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPermission(@NotNull String permission) {
|
||||
return LuckPermsAPIHook.hasPermission(getPlayer().createCommandSourceStack(), permission);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPing() {
|
||||
return getPlayer().connection.latency();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(@NotNull TabComponent message) {
|
||||
getPlayer().sendSystemMessage(message.convert());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasInvisibilityPotion() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDisguised() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public ServerPlayer getPlayer() {
|
||||
return (ServerPlayer) player;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ForgePlatform getPlatform() {
|
||||
return (ForgePlatform) platform;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVanished0() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getGamemode() {
|
||||
return getPlayer().gameMode.getGameModeForPlayer().getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getHealth() {
|
||||
return getPlayer().getHealth();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String getDisplayName() {
|
||||
return getPlayer().getGameProfile().getName(); // Will make it work properly if someone asks
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package me.neznamy.tab.platforms.forge.hook;
|
||||
|
||||
import net.luckperms.api.LuckPermsProvider;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
import net.minecraftforge.fml.ModList;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Class hooking into LuckPerms mod for permission nodes instead
|
||||
* of only using the integrated OP level.
|
||||
*/
|
||||
public class LuckPermsAPIHook {
|
||||
|
||||
/** Flag tracking presence of LuckPerms API */
|
||||
private static final boolean luckPerms = ModList.get().isLoaded("luckperms");
|
||||
|
||||
/**
|
||||
* Checks for permission and returns the result.
|
||||
*
|
||||
* @param source
|
||||
* Source to check permission of
|
||||
* @param permission
|
||||
* Permission node to check
|
||||
* @return {@code true} if has permission, {@code false} if not
|
||||
*/
|
||||
public static boolean hasPermission(@NotNull CommandSourceStack source, @NotNull String permission) {
|
||||
if (source.hasPermission(4)) return true;
|
||||
return luckPerms && LuckPermsProvider.get().getUserManager().getUser(source.getPlayer().getUUID()).getCachedData().getPermissionData().checkPermission(permission).asBoolean();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
public net.minecraft.network.chat.Style <init>(Lnet/minecraft/network/chat/TextColor;Ljava/lang/Integer;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Lnet/minecraft/network/chat/ClickEvent;Lnet/minecraft/network/chat/HoverEvent;Ljava/lang/String;Lnet/minecraft/resources/ResourceLocation;)V # constructor
|
||||
public-f net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket entries # entries
|
||||
public-f net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket method # method
|
||||
public-f net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket players # players
|
||||
@@ -0,0 +1,27 @@
|
||||
modLoader = "javafml"
|
||||
loaderVersion = "[52,)"
|
||||
issueTrackerURL = "https://github.com/NEZNAMY/TAB/issues"
|
||||
license = "Apache License Version 2.0"
|
||||
|
||||
[[mods]]
|
||||
modId = "tab"
|
||||
version = "@version@"
|
||||
displayName = "@name@"
|
||||
authors = "@author@"
|
||||
credits = "@credits@"
|
||||
description = '''@description@'''
|
||||
displayURL = "@website@"
|
||||
|
||||
[[dependencies.tab]]
|
||||
modId = "forge"
|
||||
mandatory = true
|
||||
versionRange = "*"
|
||||
ordering = "NONE"
|
||||
side = "SERVER"
|
||||
|
||||
[[dependencies.tab]]
|
||||
modId = "minecraft"
|
||||
mandatory = true
|
||||
versionRange = "[1.21.4,1.21.5]"
|
||||
ordering = "NONE"
|
||||
side = "SERVER"
|
||||
Vendored
BIN
Binary file not shown.
+1
-1
@@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
@@ -55,7 +57,7 @@
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
@@ -84,7 +86,7 @@ done
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
|
||||
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
@@ -203,7 +205,7 @@ fi
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command:
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# and any embedded shellness will be escaped.
|
||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||
# treated as '${Hostname}' itself on the command line.
|
||||
|
||||
Vendored
+2
@@ -13,6 +13,8 @@
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
@rem SPDX-License-Identifier: Apache-2.0
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
|
||||
+14
-8
@@ -1,16 +1,23 @@
|
||||
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
|
||||
|
||||
plugins {
|
||||
id("com.github.johnrengelman.shadow")
|
||||
id("com.gradleup.shadow")
|
||||
}
|
||||
|
||||
val platforms = setOf(
|
||||
rootProject.projects.bukkit,
|
||||
rootProject.projects.bukkit.paper,
|
||||
rootProject.projects.bukkit.v18R3,
|
||||
rootProject.projects.bukkit.v112R1,
|
||||
rootProject.projects.bungeecord,
|
||||
rootProject.projects.velocity,
|
||||
rootProject.projects.sponge7,
|
||||
rootProject.projects.sponge8
|
||||
rootProject.projects.sponge
|
||||
).map { it.dependencyProject }
|
||||
|
||||
val moddedPlatforms = setOf(
|
||||
rootProject.projects.fabric,
|
||||
rootProject.projects.neoforge,
|
||||
rootProject.projects.forge
|
||||
).map { it.dependencyProject }
|
||||
|
||||
tasks {
|
||||
@@ -28,10 +35,9 @@ tasks {
|
||||
registerPlatform(it, it.tasks.named<ShadowJar>("shadowJar").get())
|
||||
}
|
||||
|
||||
registerPlatform(rootProject.projects.fabric.dependencyProject, rootProject.projects.fabric.dependencyProject.tasks.named<org.gradle.jvm.tasks.Jar>("remapJar").get())
|
||||
|
||||
}
|
||||
build {
|
||||
dependsOn(shadowJar)
|
||||
moddedPlatforms.forEach {
|
||||
registerPlatform(it, it.tasks.named<org.gradle.jvm.tasks.Jar>("remapJar").get())
|
||||
}
|
||||
}
|
||||
build.get().dependsOn(shadowJar)
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
plugins {
|
||||
id("dev.architectury.loom")
|
||||
}
|
||||
|
||||
repositories {
|
||||
// Gradle doesn't support combining settings and project repositories, so we have to re-declare all the settings repos we need
|
||||
maven("https://jitpack.io") // YamlAssist
|
||||
maven("https://repo.opencollab.dev/maven-snapshots/")
|
||||
maven("https://repo.viaversion.com/")
|
||||
maven("https://oss.sonatype.org/content/repositories/snapshots")
|
||||
maven("https://maven.nucleoid.xyz/")
|
||||
maven("https://maven.neoforged.net/releases")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
minecraft("com.mojang:minecraft:1.21.5")
|
||||
mappings(loom.officialMojangMappings())
|
||||
neoForge("net.neoforged:neoforge:21.5.2-beta")
|
||||
api(projects.shared)
|
||||
compileOnly("net.luckperms:api:5.4")
|
||||
}
|
||||
|
||||
loom.neoForge.accessTransformer(file("src/main/resources/META-INF/accesstransformer.cfg"))
|
||||
|
||||
tasks {
|
||||
compileJava {
|
||||
options.release.set(21)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
loom.platform=neoforge
|
||||
@@ -0,0 +1,64 @@
|
||||
package me.neznamy.tab.platforms.neoforge;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import me.neznamy.tab.api.bossbar.BarColor;
|
||||
import me.neznamy.tab.api.bossbar.BarStyle;
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import me.neznamy.tab.shared.platform.decorators.SafeBossBar;
|
||||
import net.minecraft.server.level.ServerBossEvent;
|
||||
import net.minecraft.world.BossEvent.BossBarColor;
|
||||
import net.minecraft.world.BossEvent.BossBarOverlay;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* BossBar implementation for NeoForge using packets.
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class NeoForgeBossBar extends SafeBossBar<ServerBossEvent> {
|
||||
|
||||
/** Player this BossBar belongs to */
|
||||
@NotNull
|
||||
private final NeoForgeTabPlayer player;
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public ServerBossEvent constructBossBar(@NotNull TabComponent title, float progress, @NotNull BarColor color, @NotNull BarStyle style) {
|
||||
ServerBossEvent bar = new ServerBossEvent(
|
||||
title.convert(),
|
||||
BossBarColor.valueOf(color.name()),
|
||||
BossBarOverlay.valueOf(style.name())
|
||||
);
|
||||
bar.setProgress(progress);
|
||||
return bar;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void create(@NotNull BossBarInfo bar) {
|
||||
bar.getBossBar().addPlayer(player.getPlayer());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateTitle(@NotNull BossBarInfo bar) {
|
||||
bar.getBossBar().setName(bar.getTitle().convert());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateProgress(@NotNull BossBarInfo bar) {
|
||||
bar.getBossBar().setProgress(bar.getProgress());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateStyle(@NotNull BossBarInfo bar) {
|
||||
bar.getBossBar().setOverlay(BossBarOverlay.valueOf(bar.getStyle().name()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateColor(@NotNull BossBarInfo bar) {
|
||||
bar.getBossBar().setColor(BossBarColor.valueOf(bar.getColor().name()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(@NotNull BossBarInfo bar) {
|
||||
bar.getBossBar().removePlayer(player.getPlayer());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package me.neznamy.tab.platforms.neoforge;
|
||||
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.platform.EventListener;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.neoforged.bus.api.IEventBus;
|
||||
import net.neoforged.neoforge.common.NeoForge;
|
||||
import net.neoforged.neoforge.event.entity.player.PlayerEvent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Event listener for NeoForge.
|
||||
*/
|
||||
public class NeoForgeEventListener implements EventListener<ServerPlayer> {
|
||||
|
||||
/**
|
||||
* Registers all event listeners.
|
||||
*/
|
||||
public void register() {
|
||||
IEventBus eventBus = NeoForge.EVENT_BUS;
|
||||
eventBus.addListener((PlayerEvent.PlayerLoggedInEvent event) -> join((ServerPlayer) event.getEntity()));
|
||||
eventBus.addListener((PlayerEvent.PlayerLoggedOutEvent event) -> quit(event.getEntity().getUUID()));
|
||||
eventBus.addListener((PlayerEvent.PlayerRespawnEvent event) -> {
|
||||
ServerPlayer player = (ServerPlayer) event.getEntity();
|
||||
replacePlayer(player.getUUID(), player);
|
||||
worldChange(player.getUUID(), NeoForgeTAB.getLevelName(player.level()));
|
||||
});
|
||||
eventBus.addListener((PlayerEvent.PlayerChangedDimensionEvent event) -> worldChange(event.getEntity().getUUID(), NeoForgeTAB.getLevelName(event.getEntity().level())));
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public TabPlayer createPlayer(@NotNull ServerPlayer player) {
|
||||
return new NeoForgeTabPlayer((NeoForgePlatform) TAB.getInstance().getPlatform(), player);
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package me.neznamy.tab.platforms.neoforge;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import me.neznamy.tab.shared.features.injection.NettyPipelineInjector;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Pipeline injector for NeoForge.
|
||||
*/
|
||||
public class NeoForgePipelineInjector extends NettyPipelineInjector {
|
||||
|
||||
/**
|
||||
* Constructs new instance.
|
||||
*/
|
||||
public NeoForgePipelineInjector() {
|
||||
super("packet_handler");
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
protected Channel getChannel(@NotNull TabPlayer player) {
|
||||
return ((NeoForgeTabPlayer)player).getPlayer().connection.getConnection().channel();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
package me.neznamy.tab.platforms.neoforge;
|
||||
|
||||
import com.mojang.logging.LogUtils;
|
||||
import me.neznamy.chat.component.KeybindComponent;
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import me.neznamy.chat.component.TextComponent;
|
||||
import me.neznamy.chat.component.TranslatableComponent;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.backend.BackendPlatform;
|
||||
import me.neznamy.tab.shared.features.PerWorldPlayerListConfiguration;
|
||||
import me.neznamy.tab.shared.features.injection.PipelineInjector;
|
||||
import me.neznamy.tab.shared.features.types.TabFeature;
|
||||
import me.neznamy.tab.shared.platform.BossBar;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import net.minecraft.SharedConstants;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.network.chat.MutableComponent;
|
||||
import net.minecraft.network.chat.Style;
|
||||
import net.minecraft.network.chat.TextColor;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.neoforged.fml.loading.FMLPaths;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* Platform implementation for NeoForge
|
||||
*
|
||||
* @param server Minecraft server reference
|
||||
*/
|
||||
public record NeoForgePlatform(MinecraftServer server) implements BackendPlatform {
|
||||
|
||||
@Override
|
||||
public void registerUnknownPlaceholder(@NotNull String identifier) {
|
||||
registerDummyPlaceholder(identifier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadPlayers() {
|
||||
for (ServerPlayer player : getOnlinePlayers()) {
|
||||
TAB.getInstance().addPlayer(new NeoForgeTabPlayer(this, player));
|
||||
}
|
||||
}
|
||||
|
||||
private Collection<ServerPlayer> getOnlinePlayers() {
|
||||
// It's nullable on startup
|
||||
return server.getPlayerList() == null ? Collections.emptyList() : server.getPlayerList().getPlayers();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public PipelineInjector createPipelineInjector() {
|
||||
return new NeoForgePipelineInjector();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public TabFeature getPerWorldPlayerList(@NotNull PerWorldPlayerListConfiguration configuration) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logInfo(@NotNull TabComponent message) {
|
||||
LogUtils.getLogger().info("[TAB] {}", message.toRawText());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logWarn(@NotNull TabComponent message) {
|
||||
LogUtils.getLogger().warn("[TAB] {}", message.toRawText());
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String getServerVersionInfo() {
|
||||
return "[NeoForge] " + SharedConstants.getCurrentVersion().getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerListener() {
|
||||
new NeoForgeEventListener().register();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerCommand() {
|
||||
// Event listener must be registered in main class
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startMetrics() {
|
||||
// Not available
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public File getDataFolder() {
|
||||
return FMLPaths.CONFIGDIR.get().resolve(TabConstants.PLUGIN_ID).toFile();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Component convertComponent(@NotNull TabComponent component) {
|
||||
// Component type
|
||||
MutableComponent nmsComponent;
|
||||
if (component instanceof TextComponent) {
|
||||
nmsComponent = Component.literal(((TextComponent) component).getText());
|
||||
} else if (component instanceof TranslatableComponent) {
|
||||
nmsComponent = Component.translatable(((TranslatableComponent) component).getKey());
|
||||
} else if (component instanceof KeybindComponent) {
|
||||
nmsComponent = Component.keybind(((KeybindComponent) component).getKeybind());
|
||||
} else {
|
||||
throw new IllegalStateException("Unexpected component type: " + component.getClass().getName());
|
||||
}
|
||||
|
||||
// Component style
|
||||
nmsComponent.setStyle(new Style(
|
||||
component.getModifier().getColor() == null ? null : TextColor.fromRgb(component.getModifier().getColor().getRgb()),
|
||||
component.getModifier().getShadowColor(),
|
||||
component.getModifier().getBold(),
|
||||
component.getModifier().getItalic(),
|
||||
component.getModifier().getUnderlined(),
|
||||
component.getModifier().getStrikethrough(),
|
||||
component.getModifier().getObfuscated(),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
component.getModifier().getFont() == null ? null : ResourceLocation.tryParse(component.getModifier().getFont())
|
||||
));
|
||||
|
||||
// Extra
|
||||
for (TabComponent extra : component.getExtra()) {
|
||||
nmsComponent.getSiblings().add(convertComponent(extra));
|
||||
}
|
||||
|
||||
return nmsComponent;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Scoreboard createScoreboard(@NotNull TabPlayer player) {
|
||||
return new NeoForgeScoreboard((NeoForgeTabPlayer) player);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public BossBar createBossBar(@NotNull TabPlayer player) {
|
||||
return new NeoForgeBossBar((NeoForgeTabPlayer) player);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public TabList createTabList(@NotNull TabPlayer player) {
|
||||
return new NeoForgeTabList((NeoForgeTabPlayer) player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsScoreboards() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getTPS() {
|
||||
double mspt = getMSPT();
|
||||
if (mspt < 50) return 20;
|
||||
return Math.round(1000 / mspt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getMSPT() {
|
||||
return (float) server.getAverageTickTimeNanos() / 1000000;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
package me.neznamy.tab.platforms.neoforge;
|
||||
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.platform.decorators.SafeScoreboard;
|
||||
import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.network.chat.numbers.FixedFormat;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.network.protocol.game.*;
|
||||
import net.minecraft.world.scores.PlayerTeam;
|
||||
import net.minecraft.world.scores.Scoreboard;
|
||||
import net.minecraft.world.scores.criteria.ObjectiveCriteria;
|
||||
import net.minecraft.world.scores.criteria.ObjectiveCriteria.RenderType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Scoreboard implementation for NeoForge using packets.
|
||||
*/
|
||||
public class NeoForgeScoreboard extends SafeScoreboard<NeoForgeTabPlayer> {
|
||||
|
||||
private static final ChatFormatting[] formats = ChatFormatting.values();
|
||||
private static final net.minecraft.world.scores.Team.CollisionRule[] collisions = net.minecraft.world.scores.Team.CollisionRule.values();
|
||||
private static final net.minecraft.world.scores.Team.Visibility[] visibilities = net.minecraft.world.scores.Team.Visibility.values();
|
||||
private static final Scoreboard dummyScoreboard = new Scoreboard();
|
||||
|
||||
/**
|
||||
* Constructs new instance with given player.
|
||||
*
|
||||
* @param player
|
||||
* Player this scoreboard will belong to
|
||||
*/
|
||||
public NeoForgeScoreboard(NeoForgeTabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerObjective(@NonNull Objective objective) {
|
||||
net.minecraft.world.scores.Objective obj = new net.minecraft.world.scores.Objective(
|
||||
dummyScoreboard,
|
||||
objective.getName(),
|
||||
ObjectiveCriteria.DUMMY,
|
||||
objective.getTitle().convert(),
|
||||
RenderType.values()[objective.getHealthDisplay().ordinal()],
|
||||
false,
|
||||
objective.getNumberFormat() == null ? null : objective.getNumberFormat().toFixedFormat(FixedFormat::new)
|
||||
);
|
||||
objective.setPlatformObjective(obj);
|
||||
sendPacket(new ClientboundSetObjectivePacket(obj, ObjectiveAction.REGISTER));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDisplaySlot(@NonNull Objective objective) {
|
||||
sendPacket(new ClientboundSetDisplayObjectivePacket(net.minecraft.world.scores.DisplaySlot.values()[objective.getDisplaySlot().ordinal()], (net.minecraft.world.scores.Objective) objective.getPlatformObjective()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterObjective(@NonNull Objective objective) {
|
||||
sendPacket(new ClientboundSetObjectivePacket((net.minecraft.world.scores.Objective) objective.getPlatformObjective(), ObjectiveAction.UNREGISTER));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateObjective(@NonNull Objective objective) {
|
||||
net.minecraft.world.scores.Objective obj = (net.minecraft.world.scores.Objective) objective.getPlatformObjective();
|
||||
obj.setDisplayName(objective.getTitle().convert());
|
||||
obj.setRenderType(RenderType.values()[objective.getHealthDisplay().ordinal()]);
|
||||
sendPacket(new ClientboundSetObjectivePacket(obj, ObjectiveAction.UPDATE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setScore(@NonNull Score score) {
|
||||
sendPacket(new ClientboundSetScorePacket(
|
||||
score.getHolder(),
|
||||
score.getObjective().getName(),
|
||||
score.getValue(),
|
||||
Optional.ofNullable(score.getDisplayName() == null ? null : score.getDisplayName().convert()),
|
||||
Optional.ofNullable(score.getNumberFormat() == null ? null : score.getNumberFormat().toFixedFormat(FixedFormat::new))
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeScore(@NonNull Score score) {
|
||||
sendPacket(new ClientboundResetScorePacket(score.getHolder(), score.getObjective().getName()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Object createTeam(@NonNull String name) {
|
||||
return new PlayerTeam(dummyScoreboard, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerTeam(@NonNull Team team) {
|
||||
updateTeamProperties(team);
|
||||
PlayerTeam t = (PlayerTeam) team.getPlatformTeam();
|
||||
t.getPlayers().addAll(team.getPlayers());
|
||||
sendPacket(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(t, true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterTeam(@NonNull Team team) {
|
||||
sendPacket(ClientboundSetPlayerTeamPacket.createRemovePacket((PlayerTeam) team.getPlatformTeam()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateTeam(@NonNull Team team) {
|
||||
updateTeamProperties(team);
|
||||
sendPacket(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket((PlayerTeam) team.getPlatformTeam(), false));
|
||||
}
|
||||
|
||||
private void updateTeamProperties(@NonNull Team team) {
|
||||
PlayerTeam t = (PlayerTeam) team.getPlatformTeam();
|
||||
t.setAllowFriendlyFire((team.getOptions() & 0x01) != 0);
|
||||
t.setSeeFriendlyInvisibles((team.getOptions() & 0x02) != 0);
|
||||
t.setColor(formats[team.getColor().getLegacyColor().ordinal()]);
|
||||
t.setCollisionRule(collisions[team.getCollision().ordinal()]);
|
||||
t.setNameTagVisibility(visibilities[team.getVisibility().ordinal()]);
|
||||
t.setPlayerPrefix(team.getPrefix().convert());
|
||||
t.setPlayerSuffix(team.getSuffix().convert());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketSend(@NonNull Object packet) {
|
||||
if (isAntiOverrideScoreboard()) {
|
||||
if (packet instanceof ClientboundSetDisplayObjectivePacket display) {
|
||||
TAB.getInstance().getFeatureManager().onDisplayObjective(player, display.getSlot().ordinal(), display.getObjectiveName());
|
||||
}
|
||||
if (packet instanceof ClientboundSetObjectivePacket objective) {
|
||||
TAB.getInstance().getFeatureManager().onObjective(player, objective.getMethod(), objective.getObjectiveName());
|
||||
}
|
||||
}
|
||||
if (isAntiOverrideTeams() && packet instanceof ClientboundSetPlayerTeamPacket team) {
|
||||
if (team.method == TeamAction.UPDATE) return;
|
||||
team.players = onTeamPacket(team.method, team.getName(), team.getPlayers());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the packet to the player.
|
||||
*
|
||||
* @param packet
|
||||
* Packet to send
|
||||
*/
|
||||
private void sendPacket(@NotNull Packet<?> packet) {
|
||||
player.getPlayer().connection.send(packet);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package me.neznamy.tab.platforms.neoforge;
|
||||
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.storage.ServerLevelData;
|
||||
import net.neoforged.api.distmarker.Dist;
|
||||
import net.neoforged.bus.api.IEventBus;
|
||||
import net.neoforged.fml.common.Mod;
|
||||
import net.neoforged.neoforge.common.NeoForge;
|
||||
import net.neoforged.neoforge.event.RegisterCommandsEvent;
|
||||
import net.neoforged.neoforge.event.server.ServerStartingEvent;
|
||||
import net.neoforged.neoforge.event.server.ServerStoppingEvent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@Mod(value = "tab", dist = Dist.DEDICATED_SERVER)
|
||||
public class NeoForgeTAB {
|
||||
|
||||
public NeoForgeTAB() {
|
||||
IEventBus EVENT_BUS = NeoForge.EVENT_BUS;
|
||||
EVENT_BUS.addListener((RegisterCommandsEvent event) -> new NeoForgeTabCommand().onRegisterCommands(event.getDispatcher()));
|
||||
EVENT_BUS.addListener((ServerStartingEvent event) -> TAB.create(new NeoForgePlatform(event.getServer())));
|
||||
EVENT_BUS.addListener((ServerStoppingEvent event) -> TAB.getInstance().unload());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static String getLevelName(@NotNull Level level) {
|
||||
String path = level.dimension().location().getPath();
|
||||
return ((ServerLevelData)level.getLevelData()).getLevelName() + switch (path) {
|
||||
case "overworld" -> ""; // No suffix for overworld
|
||||
case "the_nether" -> "_nether";
|
||||
default -> "_" + path; // End + default behavior for other dimensions created by mods
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package me.neznamy.tab.platforms.neoforge;
|
||||
|
||||
import com.mojang.brigadier.CommandDispatcher;
|
||||
import com.mojang.brigadier.arguments.StringArgumentType;
|
||||
import com.mojang.brigadier.context.CommandContext;
|
||||
import com.mojang.brigadier.suggestion.Suggestions;
|
||||
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
|
||||
import com.mojang.brigadier.tree.ArgumentCommandNode;
|
||||
import com.mojang.brigadier.tree.LiteralCommandNode;
|
||||
import me.neznamy.tab.platforms.neoforge.hook.LuckPermsAPIHook;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
import net.minecraft.commands.Commands;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
/**
|
||||
* Command handler for plugin's command for NeoForge.
|
||||
*/
|
||||
public class NeoForgeTabCommand {
|
||||
|
||||
/**
|
||||
* Handles command register request from server and registers own command.
|
||||
*
|
||||
* @param dispatcher
|
||||
* Dispatcher to register command to
|
||||
*/
|
||||
public void onRegisterCommands(@NotNull CommandDispatcher<CommandSourceStack> dispatcher) {
|
||||
LiteralCommandNode<CommandSourceStack> command = Commands.literal("tab") // TODO extract it from Platform somehow
|
||||
.executes(context -> executeCommand(context.getSource(), new String[0]))
|
||||
.build();
|
||||
ArgumentCommandNode<CommandSourceStack, String> args = Commands.argument("args", StringArgumentType.greedyString())
|
||||
.suggests((context, builder) -> getSuggestions(context.getSource(), getArguments(context), builder))
|
||||
.executes(context -> executeCommand(context.getSource(), getArguments(context)))
|
||||
.build();
|
||||
command.addChild(args);
|
||||
dispatcher.getRoot().addChild(command);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private String[] getArguments(@NotNull CommandContext<CommandSourceStack> context) {
|
||||
String input = context.getInput();
|
||||
int firstSpace = input.indexOf(' ');
|
||||
if (firstSpace == -1) return new String[0];
|
||||
String rawArgs = input.substring(firstSpace + 1);
|
||||
String[] args = rawArgs.split(" ");
|
||||
if (rawArgs.endsWith(" ")) {
|
||||
args = Arrays.copyOf(args, args.length+1);
|
||||
args[args.length-1] = "";
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
@SuppressWarnings("SameReturnValue") // Unused by plugin
|
||||
private int executeCommand(@NotNull CommandSourceStack source, @NotNull String[] args) {
|
||||
if (TAB.getInstance().isPluginDisabled()) {
|
||||
boolean hasReloadPermission = LuckPermsAPIHook.hasPermission(source, TabConstants.Permission.COMMAND_RELOAD);
|
||||
boolean hasAdminPermission = LuckPermsAPIHook.hasPermission(source, TabConstants.Permission.COMMAND_ALL);
|
||||
for (String message : TAB.getInstance().getDisabledCommand().execute(args, hasReloadPermission, hasAdminPermission)) {
|
||||
source.sendSystemMessage(TabComponent.fromColoredText(message).convert());
|
||||
}
|
||||
} else {
|
||||
if (source.getEntity() == null) {
|
||||
TAB.getInstance().getCommand().execute(null, args);
|
||||
} else {
|
||||
TabPlayer player = TAB.getInstance().getPlayer(source.getEntity().getUUID());
|
||||
if (player != null) TAB.getInstance().getCommand().execute(player, args);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private CompletableFuture<Suggestions> getSuggestions(@NotNull CommandSourceStack source, @NotNull String[] args,
|
||||
@NotNull SuggestionsBuilder builder) {
|
||||
TabPlayer player = null;
|
||||
if (source.getEntity() != null) {
|
||||
player = TAB.getInstance().getPlayer(source.getEntity().getUUID());
|
||||
if (player == null) return Suggestions.empty();
|
||||
}
|
||||
|
||||
SuggestionsBuilder newBuilder = builder;
|
||||
int lastSpace = newBuilder.getRemaining().lastIndexOf(' ');
|
||||
if (lastSpace != -1) {
|
||||
newBuilder = newBuilder.createOffset(lastSpace + 1 + newBuilder.getStart());
|
||||
}
|
||||
for (String suggestion : TAB.getInstance().getCommand().complete(player, args)) {
|
||||
newBuilder.suggest(suggestion);
|
||||
}
|
||||
return newBuilder.buildFuture();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
package me.neznamy.tab.platforms.neoforge;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import com.mojang.authlib.properties.Property;
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import me.neznamy.tab.shared.platform.decorators.TrackedTabList;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket;
|
||||
import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket;
|
||||
import net.minecraft.network.protocol.game.ClientboundTabListPacket;
|
||||
import net.minecraft.world.level.GameType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* TabList implementation for NeoForge using packets.
|
||||
*/
|
||||
public class NeoForgeTabList extends TrackedTabList<NeoForgeTabPlayer> {
|
||||
|
||||
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> addPlayer = EnumSet.allOf(ClientboundPlayerInfoUpdatePacket.Action.class);
|
||||
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateDisplayName = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME);
|
||||
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateLatency = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY);
|
||||
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateGameMode = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE);
|
||||
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateListed = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED);
|
||||
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateListOrder = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LIST_ORDER);
|
||||
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateHat = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_HAT);
|
||||
|
||||
/**
|
||||
* Constructs new instance.
|
||||
*
|
||||
* @param player
|
||||
* Player this tablist will belong to
|
||||
*/
|
||||
public NeoForgeTabList(@NotNull NeoForgeTabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeEntry(@NonNull UUID entry) {
|
||||
sendPacket(new ClientboundPlayerInfoRemovePacket(Collections.singletonList(entry)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDisplayName0(@NonNull UUID entry, @Nullable TabComponent displayName) {
|
||||
sendPacket(updateDisplayName, entry, "", null, false, 0, 0, displayName, 0, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateLatency(@NonNull UUID entry, int latency) {
|
||||
sendPacket(updateLatency, entry, "", null, false, latency, 0, null, 0, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateGameMode(@NonNull UUID entry, int gameMode) {
|
||||
sendPacket(updateGameMode, entry, "", null, false, 0, gameMode, null, 0, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateListed(@NonNull UUID entry, boolean listed) {
|
||||
sendPacket(updateListed, entry, "", null, listed, 0, 0, null, 0, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateListOrder(@NonNull UUID entry, int listOrder) {
|
||||
sendPacket(updateListOrder, entry, "", null, false, 0, 0, null, listOrder, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateHat(@NonNull UUID entry, boolean showHat) {
|
||||
sendPacket(updateHat, entry, "", null, false, 0, 0, null, 0, showHat);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addEntry0(@NonNull Entry entry) {
|
||||
sendPacket(addPlayer, entry.getUniqueId(), entry.getName(), entry.getSkin(), entry.isListed(), entry.getLatency(),
|
||||
entry.getGameMode(), entry.getDisplayName(), entry.getListOrder(), entry.isShowHat());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPlayerListHeaderFooter(@NonNull TabComponent header, @NonNull TabComponent footer) {
|
||||
sendPacket(new ClientboundTabListPacket(header.convert(), footer.convert()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsEntry(@NonNull UUID entry) {
|
||||
return true; // TODO?
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Skin getSkin() {
|
||||
Collection<Property> properties = player.getPlayer().getGameProfile().getProperties().get(TEXTURES_PROPERTY);
|
||||
if (properties.isEmpty()) return null; // Offline mode
|
||||
Property property = properties.iterator().next();
|
||||
return new Skin(property.value(), property.signature());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketSend(@NonNull Object packet) {
|
||||
if (packet instanceof ClientboundPlayerInfoUpdatePacket info) {
|
||||
EnumSet<ClientboundPlayerInfoUpdatePacket.Action> actions = info.actions();
|
||||
List<ClientboundPlayerInfoUpdatePacket.Entry> updatedList = new ArrayList<>();
|
||||
boolean rewritePacket = false;
|
||||
for (ClientboundPlayerInfoUpdatePacket.Entry nmsData : info.entries()) {
|
||||
boolean rewriteEntry = false;
|
||||
Component displayName = nmsData.displayName();
|
||||
int latency = nmsData.latency();
|
||||
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME)) {
|
||||
TabComponent expectedDisplayName = getExpectedDisplayNames().get(nmsData.profileId());
|
||||
if (expectedDisplayName != null && expectedDisplayName.convert() != displayName) {
|
||||
displayName = expectedDisplayName.convert();
|
||||
rewriteEntry = rewritePacket = true;
|
||||
}
|
||||
}
|
||||
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY)) {
|
||||
int newLatency = TAB.getInstance().getFeatureManager().onLatencyChange(player, nmsData.profileId(), latency);
|
||||
if (newLatency != latency) {
|
||||
latency = newLatency;
|
||||
rewriteEntry = rewritePacket = true;
|
||||
}
|
||||
}
|
||||
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER)) {
|
||||
TAB.getInstance().getFeatureManager().onEntryAdd(player, nmsData.profileId(), nmsData.profile().getName());
|
||||
}
|
||||
updatedList.add(rewriteEntry ? new ClientboundPlayerInfoUpdatePacket.Entry(
|
||||
nmsData.profileId(), nmsData.profile(), nmsData.listed(), latency, nmsData.gameMode(), displayName,
|
||||
nmsData.showHat(), nmsData.listOrder(), nmsData.chatSession()
|
||||
) : nmsData);
|
||||
}
|
||||
if (rewritePacket) info.entries = updatedList;
|
||||
}
|
||||
}
|
||||
|
||||
private void sendPacket(@NonNull EnumSet<ClientboundPlayerInfoUpdatePacket.Action> action, @NonNull UUID id, @NonNull String name, @Nullable Skin skin,
|
||||
boolean listed, int latency, int gameMode, @Nullable TabComponent displayName, int listOrder, boolean showHat) {
|
||||
ClientboundPlayerInfoUpdatePacket packet = new ClientboundPlayerInfoUpdatePacket(action, Collections.emptyList());
|
||||
packet.entries = Collections.singletonList(new ClientboundPlayerInfoUpdatePacket.Entry(
|
||||
id,
|
||||
action.contains(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER) ? createProfile(id, name, skin) : null,
|
||||
listed,
|
||||
latency,
|
||||
GameType.byId(gameMode),
|
||||
displayName == null ? null : displayName.convert(),
|
||||
showHat,
|
||||
listOrder,
|
||||
null
|
||||
));
|
||||
sendPacket(packet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates GameProfile from given parameters.
|
||||
*
|
||||
* @param id
|
||||
* Profile ID
|
||||
* @param name
|
||||
* Profile name
|
||||
* @param skin
|
||||
* Player skin
|
||||
* @return GameProfile from given parameters
|
||||
*/
|
||||
@NotNull
|
||||
private GameProfile createProfile(@NonNull UUID id, @NonNull String name, @Nullable Skin skin) {
|
||||
GameProfile profile = new GameProfile(id, name);
|
||||
if (skin != null) {
|
||||
profile.getProperties().put(TabList.TEXTURES_PROPERTY,
|
||||
new Property(TabList.TEXTURES_PROPERTY, skin.getValue(), skin.getSignature()));
|
||||
}
|
||||
return profile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the packet to the player.
|
||||
*
|
||||
* @param packet
|
||||
* Packet to send
|
||||
*/
|
||||
private void sendPacket(@NotNull Packet<?> packet) {
|
||||
player.getPlayer().connection.send(packet);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package me.neznamy.tab.platforms.neoforge;
|
||||
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import me.neznamy.tab.platforms.neoforge.hook.LuckPermsAPIHook;
|
||||
import me.neznamy.tab.shared.backend.BackendTabPlayer;
|
||||
import net.minecraft.SharedConstants;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* TabPlayer implementation for NeoForge.
|
||||
*/
|
||||
public class NeoForgeTabPlayer extends BackendTabPlayer {
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters.
|
||||
*
|
||||
* @param platform
|
||||
* Server platform
|
||||
* @param player
|
||||
* Platform's player object
|
||||
*/
|
||||
public NeoForgeTabPlayer(@NotNull NeoForgePlatform platform, @NotNull ServerPlayer player) {
|
||||
super(platform, player, player.getUUID(), player.getGameProfile().getName(),
|
||||
NeoForgeTAB.getLevelName(player.level()), SharedConstants.getProtocolVersion());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPermission(@NotNull String permission) {
|
||||
return LuckPermsAPIHook.hasPermission(getPlayer().createCommandSourceStack(), permission);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPing() {
|
||||
return getPlayer().connection.latency();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(@NotNull TabComponent message) {
|
||||
getPlayer().sendSystemMessage(message.convert());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasInvisibilityPotion() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDisguised() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public ServerPlayer getPlayer() {
|
||||
return (ServerPlayer) player;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NeoForgePlatform getPlatform() {
|
||||
return (NeoForgePlatform) platform;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVanished0() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getGamemode() {
|
||||
return getPlayer().gameMode.getGameModeForPlayer().getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getHealth() {
|
||||
return getPlayer().getHealth();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String getDisplayName() {
|
||||
return getPlayer().getGameProfile().getName(); // Will make it work properly if someone asks
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package me.neznamy.tab.platforms.neoforge.hook;
|
||||
|
||||
import net.luckperms.api.LuckPermsProvider;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
import net.neoforged.fml.ModList;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Class hooking into LuckPerms mod for permission nodes instead
|
||||
* of only using the integrated OP level.
|
||||
*/
|
||||
public class LuckPermsAPIHook {
|
||||
|
||||
/** Flag tracking presence of LuckPerms API */
|
||||
private static final boolean luckPerms = ModList.get().isLoaded("luckperms");
|
||||
|
||||
/**
|
||||
* Checks for permission and returns the result.
|
||||
*
|
||||
* @param source
|
||||
* Source to check permission of
|
||||
* @param permission
|
||||
* Permission node to check
|
||||
* @return {@code true} if has permission, {@code false} if not
|
||||
*/
|
||||
public static boolean hasPermission(@NotNull CommandSourceStack source, @NotNull String permission) {
|
||||
if (source.hasPermission(4)) return true;
|
||||
return luckPerms && LuckPermsProvider.get().getUserManager().getUser(source.getPlayer().getUUID()).getCachedData().getPermissionData().checkPermission(permission).asBoolean();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
public net.minecraft.network.chat.Style <init>(Lnet/minecraft/network/chat/TextColor;Ljava/lang/Integer;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Lnet/minecraft/network/chat/ClickEvent;Lnet/minecraft/network/chat/HoverEvent;Ljava/lang/String;Lnet/minecraft/resources/ResourceLocation;)V # constructor
|
||||
public-f net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket entries # entries
|
||||
public-f net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket method # method
|
||||
public-f net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket players # players
|
||||
@@ -0,0 +1,26 @@
|
||||
modLoader = "javafml"
|
||||
loaderVersion = "[4,)"
|
||||
issueTrackerURL = "https://github.com/NEZNAMY/TAB/issues"
|
||||
license = "Apache License Version 2.0"
|
||||
displayURL = "@website@"
|
||||
|
||||
[[mods]]
|
||||
modId = "tab"
|
||||
version = "@version@"
|
||||
displayName = "@name@"
|
||||
authors = "@author@"
|
||||
credits = "@credits@"
|
||||
description = '''@description@'''
|
||||
|
||||
[[dependencies.tab]]
|
||||
modId = "neoforge"
|
||||
type = "required"
|
||||
ordering = "NONE"
|
||||
side = "SERVER"
|
||||
|
||||
[[dependencies.tab]]
|
||||
modId = "minecraft"
|
||||
type = "required"
|
||||
versionRange = "[1.21.4,1.21.5]"
|
||||
ordering = "NONE"
|
||||
side = "SERVER"
|
||||
+7
-2
@@ -12,6 +12,7 @@ dependencyResolutionManagement {
|
||||
maven("https://repo.spongepowered.org/repository/maven-public/") // Sponge
|
||||
maven("https://jitpack.io") // PremiumVanish, Vault, YamlAssist, RedisBungee
|
||||
maven("https://repo.md-5.net/content/groups/public/") // LibsDisguises
|
||||
maven("https://repo.codemc.org/repository/nms/") // CraftBukkit + NMS
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +20,7 @@ pluginManagement {
|
||||
includeBuild("build-logic")
|
||||
repositories {
|
||||
maven("https://repo.spongepowered.org/repository/maven-public/")
|
||||
maven("https://maven.architectury.dev/")
|
||||
gradlePluginPortal()
|
||||
mavenCentral()
|
||||
}
|
||||
@@ -31,9 +33,12 @@ include(":shared")
|
||||
include(":velocity")
|
||||
include(":bukkit")
|
||||
include(":bukkit:paper")
|
||||
include(":bukkit:v1_8_R3")
|
||||
include(":bukkit:v1_12_R1")
|
||||
include(":bungeecord")
|
||||
include(":component")
|
||||
include(":sponge7")
|
||||
include(":sponge8")
|
||||
include(":sponge")
|
||||
include(":fabric")
|
||||
include(":neoforge")
|
||||
include(":forge")
|
||||
include(":jar")
|
||||
@@ -39,5 +39,6 @@ blossom {
|
||||
replaceToken("@description@", project.description)
|
||||
replaceToken("@website@", rootProject.ext.get("website")!!.toString())
|
||||
replaceToken("@author@", rootProject.ext.get("author")!!.toString())
|
||||
replaceToken("@credits@", rootProject.ext.get("credits")!!.toString())
|
||||
replaceTokenIn("src/main/java/me/neznamy/tab/shared/TabConstants.java")
|
||||
}
|
||||
@@ -569,20 +569,18 @@ public class FeatureManager {
|
||||
|
||||
// Load the feature first, because it will be processed in main thread (to make it run before feature threads)
|
||||
if (config.isEnableProxySupport()) {
|
||||
String type = config.getConfig().getString("proxy-support.type");
|
||||
ProxySupport proxy;
|
||||
String type = config.getConfig().getString("proxy-support.type", "PLUGIN");
|
||||
ProxySupport proxy = null;
|
||||
if (type.equalsIgnoreCase("PLUGIN")) {
|
||||
String plugin = config.getConfig().getString("proxy-support.plugin.name", "RedisBungee");
|
||||
proxy = TAB.getInstance().getPlatform().getProxySupport(plugin);
|
||||
} else if (type.equalsIgnoreCase("REDIS")) {
|
||||
String url = config.getConfig().getString("proxy-support.redis.url");
|
||||
proxy = new ProxyMessengerSupport(() -> RedisBroker.of(url));
|
||||
String url = config.getConfig().getString("proxy-support.redis.url", "redis://:password@localhost:6379/0");
|
||||
proxy = new ProxyMessengerSupport("Redis", () -> RedisBroker.of(url));
|
||||
} else if (type.equalsIgnoreCase("RABBITMQ")) {
|
||||
String exchange = config.getConfig().getString("proxy-support.rabbitmq.exchange");
|
||||
String url = config.getConfig().getString("proxy-support.rabbitmq.url");
|
||||
proxy = new ProxyMessengerSupport(() -> RabbitMQBroker.of(url, exchange));
|
||||
} else {
|
||||
proxy = null;
|
||||
String exchange = config.getConfig().getString("proxy-support.rabbitmq.exchange", "plugin");
|
||||
String url = config.getConfig().getString("proxy-support.rabbitmq.url", "amqp://guest:guest@localhost:5672/%2F");
|
||||
proxy = new ProxyMessengerSupport("RabbitMQ", () -> RabbitMQBroker.of(url, exchange));
|
||||
}
|
||||
if (proxy != null) TAB.getInstance().getFeatureManager().registerFeature(TabConstants.Feature.PROXY_SUPPORT, proxy);
|
||||
}
|
||||
|
||||
@@ -249,8 +249,8 @@ public class Converter {
|
||||
|
||||
private void convertHeaderFooter(@NotNull ConfigurationFile oldConfig, @NotNull ConfigurationFile newConfig) {
|
||||
newConfig.set("header-footer.enabled", oldConfig.getBoolean("enable-header-footer", true));
|
||||
newConfig.set("header-footer.header", oldConfig.getStringList("header"));
|
||||
newConfig.set("header-footer.footer", oldConfig.getStringList("footer"));
|
||||
newConfig.set("header-footer.header", oldConfig.getStringList("header", Collections.emptyList()));
|
||||
newConfig.set("header-footer.footer", oldConfig.getStringList("footer", Collections.emptyList()));
|
||||
newConfig.set("header-footer.disable-in-worlds", oldConfig.getStringList("disable-features-in-worlds.header-footer", Collections.singletonList("disabledworld")));
|
||||
if (TAB.getInstance().getPlatform().isProxy())
|
||||
newConfig.set("header-footer.disable-in-servers", oldConfig.getStringList("disable-features-in-servers.header-footer", Collections.singletonList("disabledserver")));
|
||||
@@ -337,7 +337,7 @@ public class Converter {
|
||||
}
|
||||
|
||||
private String translateWorldGroup(@NotNull ConfigurationFile oldConfig, @NotNull String group) {
|
||||
String oldSeparator = oldConfig.hasConfigOption("multi-world-separator") ? oldConfig.getString("multi-world-separator") : "-";
|
||||
String oldSeparator = oldConfig.getString("multi-world-separator", "-");
|
||||
return group.replace(oldSeparator, ";");
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.Setter;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -17,17 +18,17 @@ import java.util.Map.Entry;
|
||||
/**
|
||||
* Abstract class for configuration file
|
||||
*/
|
||||
@Getter
|
||||
@SuppressWarnings("unchecked")
|
||||
public abstract class ConfigurationFile {
|
||||
|
||||
/** Comments on top of the file */
|
||||
protected List<String> header;
|
||||
|
||||
/** Configuration file content */
|
||||
@Getter @Setter protected Map<Object, Object> values;
|
||||
@Setter
|
||||
protected Map<Object, Object> values;
|
||||
|
||||
/** File to use */
|
||||
@Getter protected final File file;
|
||||
@NotNull
|
||||
protected final File file;
|
||||
|
||||
/**
|
||||
* Constructs new instance and attempts to load specified configuration file.
|
||||
@@ -69,6 +70,7 @@ public abstract class ConfigurationFile {
|
||||
* Value to be inserted and returned if option is not present
|
||||
* @return value from configuration file
|
||||
*/
|
||||
@Nullable
|
||||
public Object getObject(@NonNull String path, @Nullable Object defaultValue) {
|
||||
if (path.isEmpty()) return values;
|
||||
Object value = values;
|
||||
@@ -108,10 +110,10 @@ public abstract class ConfigurationFile {
|
||||
* @return Value at specified path
|
||||
*/
|
||||
@Nullable
|
||||
public Object getObject(String[] path) {
|
||||
public Object getObject(@NonNull String[] path) {
|
||||
Object value = values;
|
||||
for (String section : path) {
|
||||
if (section == null || !(value instanceof Map)) {
|
||||
if (!(value instanceof Map)) {
|
||||
return null;
|
||||
}
|
||||
value = getIgnoreCase((Map<Object, Object>) value, section);
|
||||
@@ -129,6 +131,7 @@ public abstract class ConfigurationFile {
|
||||
* case-insensitive key name
|
||||
* @return map value from case-insensitive key
|
||||
*/
|
||||
@Nullable
|
||||
private Object getIgnoreCase(@NonNull Map<Object, Object> map, @NonNull String key) {
|
||||
try {
|
||||
for (Entry<Object, Object> entry : map.entrySet()) {
|
||||
@@ -141,18 +144,6 @@ public abstract class ConfigurationFile {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns config option with specified path as {@code String}. Returns {@code null} if
|
||||
* option is not present.
|
||||
*
|
||||
* @param path
|
||||
* Path to the option with sections separated with "{@code .}"
|
||||
* @return value from file or null if not present
|
||||
*/
|
||||
public String getString(@NonNull String path) {
|
||||
return getString(path, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns config option with specified path as {@code String}. If the option is not present
|
||||
* and {@code defaultValue} is not {@code null}, value is inserted, {@link #save()} called
|
||||
@@ -164,25 +155,13 @@ public abstract class ConfigurationFile {
|
||||
* Value to be inserted and returned if option is not present
|
||||
* @return value from configuration file as {@code String}
|
||||
*/
|
||||
@Contract("_, !null -> !null")
|
||||
public String getString(@NonNull String path, @Nullable String defaultValue) {
|
||||
Object value = getObject(path, defaultValue);
|
||||
if (value == null) return defaultValue;
|
||||
return String.valueOf(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns config option with specified path as {@code List<String>}. Returns {@code null} if
|
||||
* option is not present.
|
||||
*
|
||||
* @param path
|
||||
* Path to the option with sections separated with "{@code .}"
|
||||
* @return value from file or null if not present
|
||||
*/
|
||||
@Nullable
|
||||
public List<String> getStringList(@NonNull String path) {
|
||||
return getStringList(path, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns config option with specified path as {@code List<String>}. If the option is not present
|
||||
* and {@code defaultValue} is not {@code null}, value is inserted, {@link #save()} called
|
||||
@@ -194,6 +173,7 @@ public abstract class ConfigurationFile {
|
||||
* Value to be inserted and returned if option is not present
|
||||
* @return value from configuration file as {@code List<String>}
|
||||
*/
|
||||
@Contract("_, !null -> !null")
|
||||
public List<String> getStringList(@NonNull String path, @Nullable List<String> defaultValue) {
|
||||
Object value = getObject(path, defaultValue);
|
||||
if (value == null) return defaultValue;
|
||||
@@ -207,18 +187,6 @@ public abstract class ConfigurationFile {
|
||||
return fixedList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns config option with specified path as {@code Integer}. Returns {@code null} if
|
||||
* option is not present.
|
||||
*
|
||||
* @param path
|
||||
* Path to the option with sections separated with "{@code .}"
|
||||
* @return value from file or null if not present
|
||||
*/
|
||||
public Integer getInt(@NonNull String path) {
|
||||
return getInt(path, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns config option with specified path as {@code Integer}. If the option is not present
|
||||
* and {@code defaultValue} is not {@code null}, value is inserted, {@link #save()} called
|
||||
@@ -230,6 +198,7 @@ public abstract class ConfigurationFile {
|
||||
* Value to be inserted and returned if option is not present
|
||||
* @return value from configuration file as {@code Integer}
|
||||
*/
|
||||
@Contract("_, !null -> !null")
|
||||
public Integer getInt(@NonNull String path, @Nullable Integer defaultValue) {
|
||||
Object value = getObject(path, defaultValue);
|
||||
if (value == null) return defaultValue;
|
||||
@@ -257,27 +226,6 @@ public abstract class ConfigurationFile {
|
||||
return Boolean.parseBoolean(value.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns config option with specified path as {@code Double}. If the option is not present
|
||||
* and {@code defaultValue} is not {@code null}, value is inserted, {@link #save()} called
|
||||
* and {@code defaultValue} returned.
|
||||
*
|
||||
* @param path
|
||||
* Path to the option with sections separated with "{@code .}"
|
||||
* @param defaultValue
|
||||
* Value to be inserted and returned if option is not present
|
||||
* @return value from configuration file as {@code Double}
|
||||
*/
|
||||
public Double getDouble(@NonNull String path, double defaultValue) {
|
||||
Object value = getObject(path, defaultValue);
|
||||
if (value == null) return defaultValue;
|
||||
try {
|
||||
return Double.parseDouble(value.toString());
|
||||
} catch (NumberFormatException e) {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns config option with specified path as {@code Map<K, V>}. If the option
|
||||
* is not present or value is not a {@code Map}, new empty {@code Map} is returned.
|
||||
@@ -337,6 +285,7 @@ public abstract class ConfigurationFile {
|
||||
* Value to save
|
||||
* @return the first argument to allow chaining
|
||||
*/
|
||||
@NotNull
|
||||
private Map<Object, Object> set(@NonNull Map<Object, Object> map, @NonNull String path, @Nullable Object value) {
|
||||
if (path.contains(".")) {
|
||||
String keyWord = getRealKey(map, path.split("\\.")[0]);
|
||||
@@ -365,6 +314,7 @@ public abstract class ConfigurationFile {
|
||||
* Key to find
|
||||
* @return The real key name
|
||||
*/
|
||||
@NotNull
|
||||
private String getRealKey(@NonNull Map<?, ?> map, @NonNull String key) {
|
||||
for (Object mapKey : map.keySet()) {
|
||||
if (mapKey.toString().equalsIgnoreCase(key)) return mapKey.toString();
|
||||
|
||||
@@ -208,12 +208,12 @@ public class BelowName extends RefreshableFeature implements JoinListener, QuitL
|
||||
|
||||
private void register(@NotNull TabPlayer player) {
|
||||
player.getScoreboard().registerObjective(
|
||||
Scoreboard.DisplaySlot.BELOW_NAME,
|
||||
OBJECTIVE_NAME,
|
||||
cache.get(player.belowNameData.text.updateAndGet()),
|
||||
Scoreboard.HealthDisplay.INTEGER,
|
||||
cache.get(player.belowNameData.defaultNumberFormat.updateAndGet())
|
||||
);
|
||||
player.getScoreboard().setDisplaySlot(OBJECTIVE_NAME, Scoreboard.DisplaySlot.BELOW_NAME);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+1
-1
@@ -208,12 +208,12 @@ public class YellowNumber extends RefreshableFeature implements JoinListener, Qu
|
||||
|
||||
private void register(@NotNull TabPlayer player) {
|
||||
player.getScoreboard().registerObjective(
|
||||
Scoreboard.DisplaySlot.PLAYER_LIST,
|
||||
OBJECTIVE_NAME,
|
||||
cache.get(player.playerlistObjectiveData.title.updateAndGet()),
|
||||
configuration.getHealthDisplay(),
|
||||
SimpleTextComponent.EMPTY
|
||||
);
|
||||
player.getScoreboard().setDisplaySlot(OBJECTIVE_NAME, Scoreboard.DisplaySlot.PLAYER_LIST);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+28
-10
@@ -3,38 +3,56 @@ package me.neznamy.tab.shared.features.proxy;
|
||||
import com.saicone.delivery4j.AbstractMessenger;
|
||||
import com.saicone.delivery4j.Broker;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import me.neznamy.chat.TextColor;
|
||||
import me.neznamy.chat.component.TextComponent;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class ProxyMessengerSupport extends ProxySupport {
|
||||
|
||||
@NotNull
|
||||
private final String messengerName;
|
||||
|
||||
@NotNull
|
||||
private final Supplier<Broker> brokerSupplier;
|
||||
|
||||
@Nullable
|
||||
private AbstractMessenger messenger;
|
||||
|
||||
@Override
|
||||
public void sendMessage(@NotNull String message) {
|
||||
if (messenger == null) return;
|
||||
messenger.send(TabConstants.PROXY_CHANNEL_NAME, message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void register() {
|
||||
messenger = new AbstractMessenger() {
|
||||
@Override
|
||||
protected @NotNull Broker loadBroker() {
|
||||
return brokerSupplier.get();
|
||||
}
|
||||
};
|
||||
messenger.subscribe(TabConstants.PROXY_CHANNEL_NAME).consume((channel, lines) -> {
|
||||
processMessage(lines[0]);
|
||||
}).cache(true);
|
||||
messenger.start();
|
||||
try {
|
||||
Broker broker = brokerSupplier.get();
|
||||
messenger = new AbstractMessenger() {
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
protected Broker loadBroker() {
|
||||
return broker;
|
||||
}
|
||||
};
|
||||
messenger.subscribe(TabConstants.PROXY_CHANNEL_NAME).consume((channel, lines) -> processMessage(lines[0])).cache(true);
|
||||
messenger.start();
|
||||
TAB.getInstance().getPlatform().logInfo(new TextComponent("Successfully connected to " + messengerName, TextColor.GREEN));
|
||||
} catch (Exception e) {
|
||||
TAB.getInstance().getErrorManager().criticalError("Failed to connect to " + messengerName + ": " + e.getClass().getName() + ": " + e.getMessage(), null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregister() {
|
||||
if (messenger == null) return;
|
||||
messenger.close();
|
||||
messenger.clear();
|
||||
}
|
||||
|
||||
@@ -150,12 +150,12 @@ public class ScoreboardImpl extends RefreshableFeature implements me.neznamy.tab
|
||||
if (p.scoreboardData.activeScoreboard == this) return; // already registered
|
||||
p.scoreboardData.titleProperty = new Property(this, p, title);
|
||||
p.getScoreboard().registerObjective(
|
||||
Scoreboard.DisplaySlot.SIDEBAR,
|
||||
ScoreboardManagerImpl.OBJECTIVE_NAME,
|
||||
manager.getCache().get(p.scoreboardData.titleProperty.get()),
|
||||
Scoreboard.HealthDisplay.INTEGER,
|
||||
SimpleTextComponent.EMPTY
|
||||
);
|
||||
p.getScoreboard().setDisplaySlot(ScoreboardManagerImpl.OBJECTIVE_NAME, Scoreboard.DisplaySlot.SIDEBAR);
|
||||
for (Line s : lines) {
|
||||
((ScoreboardLine)s).register(p);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import me.neznamy.tab.shared.features.PerWorldPlayerListConfiguration;
|
||||
import me.neznamy.tab.shared.features.injection.PipelineInjector;
|
||||
import me.neznamy.tab.shared.features.proxy.ProxySupport;
|
||||
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 org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -56,7 +57,9 @@ public interface Platform {
|
||||
*
|
||||
* @return Created expansion
|
||||
*/
|
||||
@NotNull TabExpansion createTabExpansion();
|
||||
default @NotNull TabExpansion createTabExpansion() {
|
||||
return new EmptyTabExpansion();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates ProxySupport feature, registers listeners and returns it
|
||||
|
||||
@@ -20,8 +20,6 @@ public interface Scoreboard {
|
||||
/**
|
||||
* Registers new scoreboard objective.
|
||||
*
|
||||
* @param displaySlot
|
||||
* Display slot
|
||||
* @param objectiveName
|
||||
* Objective name
|
||||
* @param title
|
||||
@@ -31,8 +29,16 @@ public interface Scoreboard {
|
||||
* @param numberFormat
|
||||
* Default number format for all scores in this objective (1.20.3+)
|
||||
*/
|
||||
void registerObjective(@NonNull DisplaySlot displaySlot, @NonNull String objectiveName, @NonNull TabComponent title,
|
||||
@NonNull HealthDisplay display, @Nullable TabComponent numberFormat);
|
||||
void registerObjective(@NonNull String objectiveName, @NonNull TabComponent title, @NonNull HealthDisplay display, @Nullable TabComponent numberFormat);
|
||||
|
||||
/**
|
||||
* Sets display slot of an objective.
|
||||
* @param objectiveName
|
||||
* Objective name
|
||||
* @param displaySlot
|
||||
* Display slot
|
||||
*/
|
||||
void setDisplaySlot(@NonNull String objectiveName, @NonNull DisplaySlot displaySlot);
|
||||
|
||||
/**
|
||||
* Unregisters scoreboard objective.
|
||||
|
||||
@@ -57,9 +57,9 @@ public abstract class SafeScoreboard<T extends TabPlayer> implements Scoreboard
|
||||
private boolean antiOverrideScoreboard;
|
||||
|
||||
@Override
|
||||
public synchronized void registerObjective(@NonNull DisplaySlot displaySlot, @NonNull String objectiveName, @NonNull TabComponent title,
|
||||
public synchronized void registerObjective(@NonNull String objectiveName, @NonNull TabComponent title,
|
||||
@NonNull HealthDisplay display, @Nullable TabComponent numberFormat) {
|
||||
Objective objective = new Objective(displaySlot, objectiveName, title, display, numberFormat, null);
|
||||
Objective objective = new Objective(objectiveName, title, display, numberFormat, null);
|
||||
if (objectives.put(objectiveName, objective) != null) {
|
||||
error("Tried to register duplicated objective %s to player ", objectiveName);
|
||||
return;
|
||||
@@ -68,6 +68,18 @@ public abstract class SafeScoreboard<T extends TabPlayer> implements Scoreboard
|
||||
registerObjective(objective);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void setDisplaySlot(@NonNull String objectiveName, @NonNull DisplaySlot displaySlot) {
|
||||
Objective objective = objectives.get(objectiveName);
|
||||
if (objective == null) {
|
||||
error("Tried to set display slot for non-existing objective %s to player ", objectiveName);
|
||||
return;
|
||||
}
|
||||
objective.setDisplaySlot(displaySlot);
|
||||
if (frozen) return;
|
||||
setDisplaySlot(objective);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void unregisterObjective(@NonNull String objectiveName) {
|
||||
Objective objective = objectives.remove(objectiveName);
|
||||
@@ -202,6 +214,9 @@ public abstract class SafeScoreboard<T extends TabPlayer> implements Scoreboard
|
||||
public synchronized void resend() {
|
||||
for (Objective objective : objectives.values()) {
|
||||
registerObjective(objective);
|
||||
if (objective.getDisplaySlot() != null) {
|
||||
setDisplaySlot(objective);
|
||||
}
|
||||
for (Score score : objective.getScores().values()) {
|
||||
setScore(score);
|
||||
}
|
||||
@@ -370,6 +385,14 @@ public abstract class SafeScoreboard<T extends TabPlayer> implements Scoreboard
|
||||
*/
|
||||
public abstract void registerObjective(@NonNull Objective objective);
|
||||
|
||||
/**
|
||||
* Sets display slot for an objective
|
||||
*
|
||||
* @param objective
|
||||
* Objective to set display slot for
|
||||
*/
|
||||
public abstract void setDisplaySlot(@NonNull Objective objective);
|
||||
|
||||
/**
|
||||
* Unregisters an objective
|
||||
*
|
||||
@@ -439,12 +462,11 @@ public abstract class SafeScoreboard<T extends TabPlayer> implements Scoreboard
|
||||
/**
|
||||
* Structure holding objective data.
|
||||
*/
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
@Setter
|
||||
public static class Objective {
|
||||
|
||||
@NonNull private final DisplaySlot displaySlot;
|
||||
@Nullable private DisplaySlot displaySlot;
|
||||
@NonNull private final String name;
|
||||
@NonNull private TabComponent title;
|
||||
@NonNull private HealthDisplay healthDisplay;
|
||||
@@ -454,6 +476,15 @@ public abstract class SafeScoreboard<T extends TabPlayer> implements Scoreboard
|
||||
/** Platform's objective object (if it has one) for fast access */
|
||||
@Nullable private Object platformObjective;
|
||||
|
||||
private Objective(@NonNull String name, @NonNull TabComponent title,
|
||||
@NonNull HealthDisplay healthDisplay, @Nullable TabComponent numberFormat, @Nullable Object platformObjective) {
|
||||
this.name = name;
|
||||
this.title = title;
|
||||
this.healthDisplay = healthDisplay;
|
||||
this.numberFormat = numberFormat;
|
||||
this.platformObjective = platformObjective;
|
||||
}
|
||||
|
||||
private void update(@NonNull TabComponent title, @NonNull HealthDisplay healthDisplay, @Nullable TabComponent numberFormat) {
|
||||
this.title = title;
|
||||
this.healthDisplay = healthDisplay;
|
||||
|
||||
@@ -51,7 +51,7 @@ public abstract class TrackedTabList<P extends TabPlayer> implements TabList {
|
||||
* Not needed for platforms which support pipeline injection.
|
||||
*/
|
||||
public void checkDisplayNames() {
|
||||
// Empty by default, overridden by Sponge7, Sponge8 and Velocity
|
||||
// Empty by default, overridden Sponge and Velocity
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -25,6 +25,11 @@ public class DummyScoreboard extends SafeScoreboard<TabPlayer> {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDisplaySlot(@NonNull Objective objective) {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterObjective(@NonNull Objective objective) {
|
||||
// Do nothing
|
||||
|
||||
@@ -57,6 +57,7 @@ playerlist-objective:
|
||||
value: "%ping%"
|
||||
fancy-value: "&7Ping: %ping%"
|
||||
title: "Java Edition is better" # Only visible on Bedrock Edition
|
||||
render-type: INTEGER
|
||||
disable-condition: '%world%=disabledworld'
|
||||
|
||||
# https://github.com/NEZNAMY/TAB/wiki/Feature-guide:-Belowname
|
||||
|
||||
@@ -17,8 +17,12 @@ dependencies {
|
||||
implementation("org.bstats:bstats-sponge:3.1.0")
|
||||
}
|
||||
|
||||
tasks.compileJava {
|
||||
options.release.set(21)
|
||||
}
|
||||
|
||||
sponge {
|
||||
apiVersion("8.0.0")
|
||||
apiVersion("12.0.0")
|
||||
loader {
|
||||
name(PluginLoaders.JAVA_PLAIN)
|
||||
version("1.0")
|
||||
@@ -28,7 +32,7 @@ sponge {
|
||||
displayName(rootProject.name)
|
||||
version(project.version.toString())
|
||||
description(project.description)
|
||||
entrypoint("me.neznamy.tab.platforms.sponge8.Sponge8TAB")
|
||||
entrypoint("me.neznamy.tab.platforms.sponge.SpongeTAB")
|
||||
links {
|
||||
val website = rootProject.ext.get("website")!!.toString()
|
||||
homepage(website)
|
||||
+6
-3
@@ -1,4 +1,4 @@
|
||||
package me.neznamy.tab.platforms.sponge8;
|
||||
package me.neznamy.tab.platforms.sponge;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
@@ -14,9 +14,12 @@ import org.spongepowered.api.event.entity.ChangeEntityWorldEvent;
|
||||
import org.spongepowered.api.event.entity.living.player.RespawnPlayerEvent;
|
||||
import org.spongepowered.api.event.filter.cause.First;
|
||||
import org.spongepowered.api.event.network.ServerSideConnectionEvent;
|
||||
import org.spongepowered.api.profile.GameProfile;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Event listener for Sponge 8 to forward events to enabled features.
|
||||
* Event listener for Sponge to forward events to enabled features.
|
||||
*/
|
||||
public class SpongeEventListener implements EventListener<ServerPlayer> {
|
||||
|
||||
@@ -29,7 +32,7 @@ public class SpongeEventListener implements EventListener<ServerPlayer> {
|
||||
@Listener
|
||||
@SneakyThrows
|
||||
public void onQuit(ServerSideConnectionEvent.Disconnect event) {
|
||||
quit(SpongeMultiVersion.getUniqueId.apply(event));
|
||||
quit(event.profile().map(GameProfile::uuid).orElse(new UUID(0, 0)));
|
||||
}
|
||||
|
||||
/**
|
||||
+3
-11
@@ -1,4 +1,4 @@
|
||||
package me.neznamy.tab.platforms.sponge8;
|
||||
package me.neznamy.tab.platforms.sponge;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
@@ -10,8 +10,6 @@ import me.neznamy.chat.component.TabComponent;
|
||||
import me.neznamy.tab.shared.features.PerWorldPlayerListConfiguration;
|
||||
import me.neznamy.tab.shared.features.injection.PipelineInjector;
|
||||
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.platform.BossBar;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
@@ -28,14 +26,14 @@ import org.spongepowered.api.entity.living.player.server.ServerPlayer;
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* Platform implementation for Sponge 8 and up.
|
||||
* Platform implementation for Sponge.
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class SpongePlatform implements BackendPlatform {
|
||||
|
||||
/** Main class reference */
|
||||
@NotNull
|
||||
private final Sponge8TAB plugin;
|
||||
private final SpongeTAB plugin;
|
||||
|
||||
/** Server version */
|
||||
@Getter
|
||||
@@ -59,12 +57,6 @@ public class SpongePlatform implements BackendPlatform {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public TabExpansion createTabExpansion() {
|
||||
return new EmptyTabExpansion();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public TabFeature getPerWorldPlayerList(@NotNull PerWorldPlayerListConfiguration configuration) {
|
||||
+9
-5
@@ -1,4 +1,4 @@
|
||||
package me.neznamy.tab.platforms.sponge8;
|
||||
package me.neznamy.tab.platforms.sponge;
|
||||
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.shared.platform.decorators.SafeScoreboard;
|
||||
@@ -14,7 +14,7 @@ import org.spongepowered.api.scoreboard.objective.displaymode.ObjectiveDisplayMo
|
||||
import org.spongepowered.api.scoreboard.objective.displaymode.ObjectiveDisplayModes;
|
||||
|
||||
/**
|
||||
* Scoreboard implementation for Sponge 8 using its API.
|
||||
* Scoreboard implementation for Sponge using its API.
|
||||
*/
|
||||
public class SpongeScoreboard extends SafeScoreboard<SpongeTabPlayer> {
|
||||
|
||||
@@ -72,10 +72,14 @@ public class SpongeScoreboard extends SafeScoreboard<SpongeTabPlayer> {
|
||||
.criterion(Criteria.DUMMY)
|
||||
.build();
|
||||
sb.addObjective(obj);
|
||||
sb.updateDisplaySlot(obj, displaySlots[objective.getDisplaySlot().ordinal()]);
|
||||
objective.setPlatformObjective(obj);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDisplaySlot(@NonNull Objective objective) {
|
||||
sb.updateDisplaySlot((org.spongepowered.api.scoreboard.objective.Objective) objective.getPlatformObjective(), displaySlots[objective.getDisplaySlot().ordinal()]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterObjective(@NonNull Objective objective) {
|
||||
sb.removeObjective((org.spongepowered.api.scoreboard.objective.Objective) objective.getPlatformObjective());
|
||||
@@ -91,13 +95,13 @@ public class SpongeScoreboard extends SafeScoreboard<SpongeTabPlayer> {
|
||||
@Override
|
||||
public void setScore(@NonNull Score score) {
|
||||
org.spongepowered.api.scoreboard.objective.Objective obj = (org.spongepowered.api.scoreboard.objective.Objective) score.getObjective().getPlatformObjective();
|
||||
SpongeMultiVersion.findOrCreateScore(obj, score.getHolder()).setScore(score.getValue());
|
||||
obj.findOrCreateScore(score.getHolder()).setScore(score.getValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeScore(@NonNull Score score) {
|
||||
org.spongepowered.api.scoreboard.objective.Objective obj = (org.spongepowered.api.scoreboard.objective.Objective) score.getObjective().getPlatformObjective();
|
||||
obj.removeScore(SpongeMultiVersion.findOrCreateScore(obj, score.getHolder()));
|
||||
obj.removeScore(score.getHolder());
|
||||
}
|
||||
|
||||
@Override
|
||||
+15
-4
@@ -1,13 +1,16 @@
|
||||
package me.neznamy.tab.platforms.sponge8;
|
||||
package me.neznamy.tab.platforms.sponge;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import lombok.Getter;
|
||||
import me.neznamy.tab.shared.ProtocolVersion;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.bstats.sponge.Metrics;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.spongepowered.api.Server;
|
||||
import org.spongepowered.api.Sponge;
|
||||
import org.spongepowered.api.command.Command;
|
||||
import org.spongepowered.api.config.ConfigDir;
|
||||
import org.spongepowered.api.event.Listener;
|
||||
@@ -20,15 +23,16 @@ import org.spongepowered.plugin.builtin.jvm.Plugin;
|
||||
import java.nio.file.Path;
|
||||
|
||||
/**
|
||||
* Main class for Sponge 8.
|
||||
* Main class for Sponge.
|
||||
*/
|
||||
@Getter
|
||||
@Plugin(TabConstants.PLUGIN_ID)
|
||||
public class Sponge8TAB {
|
||||
public class SpongeTAB {
|
||||
|
||||
@Inject @ConfigDir(sharedRoot = false) private Path configDir;
|
||||
@Inject private PluginContainer container;
|
||||
@Inject private Metrics.Factory metricsFactory;
|
||||
@Inject private Logger logger;
|
||||
|
||||
/**
|
||||
* Enables the plugin.
|
||||
@@ -38,6 +42,13 @@ public class Sponge8TAB {
|
||||
*/
|
||||
@Listener
|
||||
public void onServerStart(@Nullable StartingEngineEvent<Server> event) {
|
||||
ProtocolVersion serverVersion = ProtocolVersion.fromNetworkId(Sponge.platform().minecraftVersion().protocolVersion());
|
||||
if (serverVersion.getNetworkId() < ProtocolVersion.V1_20_6.getNetworkId()) {
|
||||
logger.warn("====================================================================================================");
|
||||
logger.warn("This plugin version was made for Minecraft version 1.20.6 and above.");
|
||||
logger.warn("====================================================================================================");
|
||||
return;
|
||||
}
|
||||
TAB.create(new SpongePlatform(this));
|
||||
}
|
||||
|
||||
@@ -60,6 +71,6 @@ public class Sponge8TAB {
|
||||
*/
|
||||
@Listener
|
||||
public void onServerStop(@Nullable StoppingEngineEvent<Server> event) {
|
||||
TAB.getInstance().unload();
|
||||
if (TAB.getInstance() != null) TAB.getInstance().unload();
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
package me.neznamy.tab.platforms.sponge8;
|
||||
package me.neznamy.tab.platforms.sponge;
|
||||
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
@@ -22,7 +22,7 @@ import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Command processor for plugin's command for Sponge 8.
|
||||
* Command processor for plugin's command for Sponge.
|
||||
*/
|
||||
public class SpongeTabCommand implements Command.Raw {
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
package me.neznamy.tab.platforms.sponge8;
|
||||
package me.neznamy.tab.platforms.sponge;
|
||||
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
@@ -15,7 +15,7 @@ import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* TabList implementation for Sponge 8 and up
|
||||
* TabList implementation for Sponge.
|
||||
*/
|
||||
public class SpongeTabList extends TrackedTabList<SpongeTabPlayer> {
|
||||
|
||||
+4
-3
@@ -1,4 +1,4 @@
|
||||
package me.neznamy.tab.platforms.sponge8;
|
||||
package me.neznamy.tab.platforms.sponge;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
@@ -10,11 +10,12 @@ import org.spongepowered.api.effect.potion.PotionEffect;
|
||||
import org.spongepowered.api.effect.potion.PotionEffectTypes;
|
||||
import org.spongepowered.api.entity.living.player.gamemode.GameModes;
|
||||
import org.spongepowered.api.entity.living.player.server.ServerPlayer;
|
||||
import org.spongepowered.api.network.ServerConnectionState;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* TabPlayer implementation for Sponge 8+.
|
||||
* TabPlayer implementation for Sponge.
|
||||
*/
|
||||
public class SpongeTabPlayer extends BackendTabPlayer {
|
||||
|
||||
@@ -38,7 +39,7 @@ public class SpongeTabPlayer extends BackendTabPlayer {
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public int getPing() {
|
||||
return SpongeMultiVersion.getPing.apply(getPlayer());
|
||||
return getPlayer().connection().state().map(c -> ((ServerConnectionState.Game)c).latency()).orElse(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1,4 +0,0 @@
|
||||
dependencies {
|
||||
implementation(projects.shared)
|
||||
compileOnly("org.spongepowered:spongeapi:5.0.0")
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
package me.neznamy.tab.platforms.sponge7;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import lombok.Getter;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.spongepowered.api.config.ConfigDir;
|
||||
import org.spongepowered.api.event.Listener;
|
||||
import org.spongepowered.api.event.game.state.GameStartedServerEvent;
|
||||
import org.spongepowered.api.event.game.state.GameStoppedServerEvent;
|
||||
import org.spongepowered.api.plugin.Plugin;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* Main class for Sponge 7.
|
||||
*/
|
||||
@Plugin(
|
||||
id = TabConstants.PLUGIN_ID,
|
||||
name = TabConstants.PLUGIN_NAME,
|
||||
version = TabConstants.PLUGIN_VERSION,
|
||||
description = TabConstants.PLUGIN_DESCRIPTION,
|
||||
url = TabConstants.PLUGIN_WEBSITE,
|
||||
authors = TabConstants.PLUGIN_AUTHOR
|
||||
)
|
||||
@Getter
|
||||
public class Sponge7TAB {
|
||||
|
||||
@Inject @ConfigDir(sharedRoot = false) private File configDir;
|
||||
@Inject private Logger logger;
|
||||
|
||||
/**
|
||||
* Enables the plugin.
|
||||
*
|
||||
* @param event
|
||||
* Server start event
|
||||
*/
|
||||
@Listener
|
||||
public void onServerStart(@Nullable GameStartedServerEvent event) {
|
||||
TAB.create(new SpongePlatform(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables the plugin.
|
||||
*
|
||||
* @param event
|
||||
* Server stop event
|
||||
*/
|
||||
@Listener
|
||||
public void onServerStop(@Nullable GameStoppedServerEvent event) {
|
||||
TAB.getInstance().unload();
|
||||
}
|
||||
}
|
||||
Loaded 100 of 109 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user