Compare commits

..
40 Commits
Author SHA1 Message Date
NEZNAMY 71a19c63d8 5.2.0 2025-03-31 17:45:05 +02:00
NEZNAMY 657327e7ee [Bukkit] Move viaversion presence check to fix class loader error when it's not present 2025-03-31 17:44:50 +02:00
NEZNAMY efeea7cf0f Merge pull request #1460 from JT122406/master
Forge and NeoForge Support
2025-03-31 17:20:48 +02:00
Joseph T. McQuigg 7a4637368d Hard Versions 2025-03-31 11:18:36 -04:00
NEZNAMY 2e6ccd078b 5.2.0-SNAPSHOT 2025-03-31 17:09:19 +02:00
NEZNAMY 827197f916 5.1.1 2025-03-31 16:52:47 +02:00
Joseph T. McQuigg c8454ec0c1 Add MC versions to mods.toml 2025-03-31 10:52:34 -04:00
Joseph T. McQuigg 7127a77d92 Remove Versions from mods.toml, Fix register 2025-03-31 10:30:31 -04:00
Joseph T. McQuigg b23912b52f Forge/NeoForge Luckperms Hook 2025-03-30 18:57:32 -04:00
Joseph T. McQuigg 4b21148a03 Adjust Scoreboard classes for setDisplaySlot 2025-03-30 15:06:37 -04:00
Joseph T. McQuigg 2d5ef95e54 Update Forge to 1.21.5 2025-03-30 14:52:00 -04:00
Joseph T. McQuigg b5e2d376dd Update NeoForge to 1.21.5 2025-03-30 14:52:00 -04:00
Joseph T. McQuigg c40b337edc Fix Credits 2025-03-30 14:52:00 -04:00
Joseph T. McQuigg f8583642a8 Correct License 2025-03-30 14:52:00 -04:00
Joseph T. McQuigg 707f28df16 Permission check 2025-03-30 14:52:00 -04:00
Joseph T. McQuigg dd78da089d Use Records for platform 2025-03-30 14:51:59 -04:00
Joseph T. McQuigg c4ea65ed70 Forge Port 2025-03-30 14:51:59 -04:00
Joseph T. McQuigg aabba806c4 Drop Parchment 2025-03-30 14:51:59 -04:00
Joseph T. McQuigg 88167fc42d Default to EmptyTabExpansion 2025-03-30 14:51:59 -04:00
Joseph T. McQuigg e4d7005e46 moddedPlatforms List 2025-03-30 14:51:59 -04:00
Joseph T. McQuigg 5ee27f7ed7 Add Credits 2025-03-30 14:51:59 -04:00
Joseph T. McQuigg 9a4492c401 Add Parchment to Fabric, Use LogUtils instead of AW 2025-03-30 14:51:59 -04:00
Joseph T. McQuigg 39a2aee5a0 NeoForge Port 2025-03-30 14:51:58 -04:00
Joseph T. McQuigg 5184ef170f Update to new com.gradleup.shadow plugin
Gradle 8.13, Fabric Loom 1.10
2025-03-30 14:51:56 -04:00
NEZNAMY 29a35cf1cc [ViaVersion hook] Decrease check interval from 100 to 50 2025-03-30 18:51:39 +02:00
NEZNAMY f26516ecae [Code style] Separate objective register and set display slot functions 2025-03-30 18:47:39 +02:00
NEZNAMY f26a7ad2b1 [Bukkit] Add direct NMS hook for v1_8_R3 and v1_12_R1 2025-03-30 17:46:23 +02:00
NEZNAMY 96e0a29573 Merge pull request #1464 from MineLAT/fix/viaversion-delay
Fix ViaVersion packets delay
2025-03-29 17:12:29 +01:00
NEZNAMY f30d667ca8 [Code style] Add return value annotations and remove unused methods in ConfigurationFile 2025-03-29 17:05:53 +01:00
Rubén 8db7a1d6d9 Fix ViaVersion packets delay 2025-03-29 10:04:46 -06:00
NEZNAMY d285a0512e [Multi-server support] Provide some feedback about connection status (success/fail) (doesn't work for redis) 2025-03-29 16:52:13 +01:00
NEZNAMY 256d0d5c84 Revert "[Placeholders] Parse nested PAPI placeholders using {}"
This reverts commit 6501480980.
2025-03-29 09:57:02 +01:00
NEZNAMY b808e1f9d1 [Bukkit] Refactor scoreboard / tablist implementation finding for players 2025-03-29 09:55:52 +01:00
NEZNAMY 07cd781ec1 [Sponge] Drop Sponge 7 module 2025-03-28 15:43:37 +01:00
NEZNAMY 9115b687f4 [Sponge] Drop support for sponge versions 1.16.5 - 1.20.5 2025-03-27 18:51:28 +01:00
NEZNAMY 7c51377203 [Bukkit] Update paper submodule for 1.21.5 and allow it there 2025-03-27 18:24:02 +01:00
NEZNAMY 193cd02072 [Configuration] Fix startup error when some config options were missing 2025-03-27 16:49:01 +01:00
NEZNAMY dbb6d91f0c [Configuration] Add missing playerlist-objective.render-type option to default config 2025-03-26 19:48:20 +01:00
NEZNAMY a82714e811 [BungeeCord] Fix error (#1462) 2025-03-26 19:00:53 +01:00
NEZNAMY bcd1bbac62 5.1.1-SNAPSHOT 2025-03-26 18:53:58 +01:00
109 changed files with 3455 additions and 1460 deletions

No files matched your search

+3 -3
View File
@@ -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
View File
@@ -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
View File
@@ -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(
+3 -3
View File
@@ -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
@@ -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;
}
@@ -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;
}
}
@@ -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
@@ -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();
}
}
@@ -0,0 +1,175 @@
package me.neznamy.tab.platforms.bukkit.provider;
import com.google.common.collect.Lists;
import io.netty.channel.Channel;
import lombok.Getter;
import lombok.SneakyThrows;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.platforms.bukkit.BukkitUtils;
import me.neznamy.tab.platforms.bukkit.nms.BukkitReflection;
import me.neznamy.tab.platforms.bukkit.nms.converter.ComponentConverter;
import me.neznamy.tab.platforms.bukkit.nms.converter.LegacyComponentConverter;
import me.neznamy.tab.platforms.bukkit.nms.converter.ModerateComponentConverter;
import me.neznamy.tab.platforms.bukkit.nms.converter.ModernComponentConverter;
import me.neznamy.tab.platforms.bukkit.scoreboard.BukkitScoreboard;
import me.neznamy.tab.platforms.bukkit.scoreboard.PaperScoreboard;
import me.neznamy.tab.platforms.bukkit.scoreboard.packet.PacketScoreboard;
import me.neznamy.tab.platforms.bukkit.tablist.BukkitTabList;
import me.neznamy.tab.platforms.bukkit.tablist.PacketTabList1193;
import me.neznamy.tab.platforms.bukkit.tablist.PacketTabList17;
import me.neznamy.tab.platforms.bukkit.tablist.PacketTabList18;
import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.platform.impl.DummyScoreboard;
import me.neznamy.tab.shared.util.ReflectionUtils;
import me.neznamy.tab.shared.util.function.FunctionWithException;
import org.bukkit.Bukkit;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;
/**
* Implementation provider using reflection for NMS / Bukkit implementations as fallback options.
*/
@Getter
public class BukkitImplementationProvider implements ImplementationProvider {
@Nullable
private final FunctionWithException<BukkitTabPlayer, Channel> channelFunction = findChannelFunction();
@Nullable
private final ComponentConverter componentConverter = findComponentConverter();
@NotNull
private final Function<BukkitTabPlayer, Scoreboard> scoreboardProvider = findScoreboardProvider();
@NotNull
private final Function<BukkitTabPlayer, TabList> tablistProvider = findTablistProvider();
@Nullable
private FunctionWithException<BukkitTabPlayer, Channel> findChannelFunction() {
if (BukkitReflection.getMinorVersion() < 8) return null;
try {
Class<?> NetworkManager = BukkitReflection.getClass("network.Connection", "network.NetworkManager", "NetworkManager");
Class<?> PlayerConnection = BukkitReflection.getClass("server.network.ServerGamePacketListenerImpl",
"server.network.PlayerConnection", "PlayerConnection");
Class<?> EntityPlayer = BukkitReflection.getClass("server.level.ServerPlayer", "server.level.EntityPlayer", "EntityPlayer");
Field PLAYER_CONNECTION = ReflectionUtils.getOnlyField(EntityPlayer, PlayerConnection);
Field NETWORK_MANAGER;
if (BukkitReflection.is1_20_2Plus()) {
NETWORK_MANAGER = ReflectionUtils.getOnlyField(PlayerConnection.getSuperclass(), NetworkManager);
} else {
NETWORK_MANAGER = ReflectionUtils.getOnlyField(PlayerConnection, NetworkManager);
}
Field CHANNEL = ReflectionUtils.getOnlyField(NetworkManager, Channel.class);
return player -> (Channel) CHANNEL.get(NETWORK_MANAGER.get(PLAYER_CONNECTION.get(player.getHandle())));
} catch (Exception e) {
BukkitUtils.compatibilityError(e, "network channel injection", null,
"Anti-override for tablist & nametags not working",
"Compatibility with nickname plugins changing player names will not work",
"Scoreboard will not be checking for other plugins");
return null;
}
}
@Nullable
@SneakyThrows
private ComponentConverter findComponentConverter() {
try {
if (BukkitReflection.getMinorVersion() >= 19) {
// 1.19+
return new ModernComponentConverter();
} else if (BukkitReflection.getMinorVersion() >= 16) {
// 1.16 - 1.18.2
return new ModerateComponentConverter();
} else {
// 1.7 - 1.15.2
return new LegacyComponentConverter();
}
} catch (Exception e) {
Bukkit.getConsoleSender().sendMessage("§c[TAB] Failed to initialize converter from TAB components to Minecraft components. " +
"This will negatively impact most features, see below.");
if (BukkitUtils.PRINT_EXCEPTIONS) {
e.printStackTrace();
}
return null;
}
}
@NotNull
private Function<BukkitTabPlayer, Scoreboard> findScoreboardProvider() {
try {
if (BukkitReflection.getMinorVersion() >= 7) Objects.requireNonNull(componentConverter);
PacketScoreboard.load();
return PacketScoreboard::new;
} catch (Exception e) {
if (PaperScoreboard.isAvailable()) {
BukkitUtils.compatibilityError(e, "Scoreboards", "Paper API", "Compatibility with other plugins being reduced");
return PaperScoreboard::new;
} else if (BukkitScoreboard.isAvailable()) {
List<String> missingFeatures = Lists.newArrayList(
"Compatibility with other plugins being reduced",
"Features receiving new artificial character limits"
);
if (BukkitReflection.is1_20_3Plus()) {
missingFeatures.add("1.20.3+ visuals not working due to lack of API"); // soontm?
}
BukkitUtils.compatibilityError(e, "Scoreboards", "Bukkit API", missingFeatures.toArray(new String[0]));
return BukkitScoreboard::new;
} else if (BukkitReflection.getMinorVersion() >= 5) {
BukkitUtils.compatibilityError(e, "Scoreboards", null,
"Scoreboard feature will not work",
"Belowname feature will not work",
"Player objective feature will not work",
"Scoreboard teams feature will not work (nametags & sorting)");
}
}
return DummyScoreboard::new;
}
@NotNull
@SneakyThrows
private Function<BukkitTabPlayer, TabList> findTablistProvider() {
try {
if (ReflectionUtils.classExists("net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket")) {
// 1.19.3+
Objects.requireNonNull(componentConverter);
PacketTabList1193.loadNew();
return PacketTabList1193::new;
} else if (BukkitReflection.getMinorVersion() >= 8) {
// 1.8 - 1.19.2
Objects.requireNonNull(componentConverter);
PacketTabList18.load();
return PacketTabList18::new;
} else {
// 1.7.10 and lower
PacketTabList17.load();
return PacketTabList17::new;
}
} catch (Exception e) {
BukkitUtils.compatibilityError(e, "tablist entry management", "Bukkit API",
"Layout feature will not work",
"Prevent-spectator-effect feature will not work",
"Ping spoof feature will not work",
"Tablist formatting missing anti-override",
"Tablist formatting not supporting relational placeholders");
return BukkitTabList::new;
}
}
@Override
@NotNull
public Scoreboard newScoreboard(@NotNull BukkitTabPlayer player) {
return scoreboardProvider.apply(player);
}
@Override
@NotNull
public TabList newTabList(@NotNull BukkitTabPlayer player) {
return tablistProvider.apply(player);
}
}
@@ -0,0 +1,25 @@
package me.neznamy.tab.platforms.bukkit.provider;
import io.netty.channel.Channel;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.platforms.bukkit.nms.converter.ComponentConverter;
import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.util.function.FunctionWithException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public interface ImplementationProvider {
@NotNull
Scoreboard newScoreboard(@NotNull BukkitTabPlayer player);
@NotNull
TabList newTabList(@NotNull BukkitTabPlayer player);
@Nullable
ComponentConverter getComponentConverter();
@Nullable
FunctionWithException<BukkitTabPlayer, Channel> getChannelFunction();
}
@@ -0,0 +1,72 @@
package me.neznamy.tab.platforms.bukkit.provider;
import lombok.RequiredArgsConstructor;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.platforms.bukkit.scoreboard.viaversion.ViaScoreboard113;
import me.neznamy.tab.platforms.bukkit.scoreboard.viaversion.ViaScoreboard116;
import me.neznamy.tab.platforms.bukkit.scoreboard.viaversion.ViaScoreboard1203;
import me.neznamy.tab.platforms.bukkit.tablist.viaversion.ViaTabList116;
import me.neznamy.tab.platforms.bukkit.tablist.viaversion.ViaTabList1193;
import me.neznamy.tab.platforms.bukkit.tablist.viaversion.ViaTabList1212;
import me.neznamy.tab.platforms.bukkit.tablist.viaversion.ViaTabList1214;
import me.neznamy.tab.shared.ProtocolVersion;
import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.platform.TabList;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.function.Function;
/**
* Implementation for sending new features on old servers using ViaVersion API.
* If no implementation is required for a player, {@code null} is returned.
*/
@RequiredArgsConstructor
public class ViaVersionImplementationProvider {
/** Map of tablist implementations by each new version */
private static final Map<ProtocolVersion, Function<BukkitTabPlayer, TabList>> TABLIST_IMPLEMENTATIONS = new LinkedHashMap<>();
/** Map of scoreboard implementations by each new version */
private static final Map<ProtocolVersion, Function<BukkitTabPlayer, Scoreboard>> SCOREBOARD_IMPLEMENTATIONS = new LinkedHashMap<>();
static {
TABLIST_IMPLEMENTATIONS.put(ProtocolVersion.V1_21_4, ViaTabList1214::new);
TABLIST_IMPLEMENTATIONS.put(ProtocolVersion.V1_21_2, ViaTabList1212::new);
TABLIST_IMPLEMENTATIONS.put(ProtocolVersion.V1_19_3, ViaTabList1193::new);
TABLIST_IMPLEMENTATIONS.put(ProtocolVersion.V1_16, ViaTabList116::new);
SCOREBOARD_IMPLEMENTATIONS.put(ProtocolVersion.V1_20_3, ViaScoreboard1203::new);
SCOREBOARD_IMPLEMENTATIONS.put(ProtocolVersion.V1_16, ViaScoreboard116::new);
SCOREBOARD_IMPLEMENTATIONS.put(ProtocolVersion.V1_13, ViaScoreboard113::new);
}
@NotNull
private final ProtocolVersion serverVersion;
@Nullable
public Scoreboard newScoreboard(@NotNull BukkitTabPlayer player) {
int serverVer = serverVersion.getNetworkId();
int playerVer = player.getVersion().getNetworkId();
for (Map.Entry<ProtocolVersion, Function<BukkitTabPlayer, Scoreboard>> entry : SCOREBOARD_IMPLEMENTATIONS.entrySet()) {
if (serverVer < entry.getKey().getNetworkId() && playerVer >= entry.getKey().getNetworkId()) {
return entry.getValue().apply(player);
}
}
return null;
}
@Nullable
public TabList newTabList(@NotNull BukkitTabPlayer player) {
int serverVer = serverVersion.getNetworkId();
int playerVer = player.getVersion().getNetworkId();
for (Map.Entry<ProtocolVersion, Function<BukkitTabPlayer, TabList>> entry : TABLIST_IMPLEMENTATIONS.entrySet()) {
if (serverVer < entry.getKey().getNetworkId() && playerVer >= entry.getKey().getNetworkId()) {
return entry.getValue().apply(player);
}
}
return null;
}
}
@@ -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();
@@ -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)));
}
@@ -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);
}
@@ -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
@@ -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);
@@ -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);
}
+4
View File
@@ -0,0 +1,4 @@
dependencies {
implementation(projects.bukkit)
compileOnly("org.spigotmc:spigot:1.12-R0.1-SNAPSHOT")
}
@@ -0,0 +1,48 @@
package me.neznamy.tab.platforms.bukkit.v1_12_R1;
import me.neznamy.chat.ChatModifier;
import me.neznamy.tab.platforms.bukkit.nms.converter.ComponentConverter;
import net.minecraft.server.v1_12_R1.*;
import org.jetbrains.annotations.NotNull;
/**
* Component converter using direct NMS code.
*/
public class NMSComponentConverter extends ComponentConverter {
@Override
@NotNull
public Object newTextComponent(@NotNull String text) {
return new ChatComponentText(text);
}
@Override
@NotNull
public Object newTranslatableComponent(@NotNull String key) {
return new ChatMessage(key);
}
@Override
@NotNull
public Object newKeybindComponent(@NotNull String keybind) {
return new ChatComponentKeybind(keybind);
}
@Override
public void applyStyle(@NotNull Object nmsComponent, @NotNull ChatModifier modifier) {
((IChatBaseComponent)nmsComponent).setChatModifier(
new net.minecraft.server.v1_12_R1.ChatModifier()
.setColor(modifier.getColor() == null ? null : EnumChatFormat.valueOf(modifier.getColor().getLegacyColor().name()))
.setBold(modifier.getBold())
.setItalic(modifier.getItalic())
.setUnderline(modifier.getUnderlined())
.setStrikethrough(modifier.getStrikethrough())
.setRandom(modifier.getObfuscated())
);
}
@Override
public void addSibling(@NotNull Object parent, @NotNull Object child) {
((IChatBaseComponent)parent).addSibling((IChatBaseComponent) child);
}
}
@@ -0,0 +1,45 @@
package me.neznamy.tab.platforms.bukkit.v1_12_R1;
import io.netty.channel.Channel;
import me.neznamy.tab.platforms.bukkit.BukkitTabPlayer;
import me.neznamy.tab.platforms.bukkit.nms.converter.ComponentConverter;
import me.neznamy.tab.platforms.bukkit.provider.ImplementationProvider;
import me.neznamy.tab.shared.platform.Scoreboard;
import me.neznamy.tab.shared.platform.TabList;
import me.neznamy.tab.shared.util.function.FunctionWithException;
import org.bukkit.craftbukkit.v1_12_R1.entity.CraftPlayer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Implementation provider using direct NMS code for 1.8.8.
*/
public class NMSImplementationProvider implements ImplementationProvider {
@NotNull
private final ComponentConverter componentConverter = new NMSComponentConverter();
@Override
@NotNull
public Scoreboard newScoreboard(@NotNull BukkitTabPlayer player) {
return new NMSPacketScoreboard(player);
}
@Override
@NotNull
public TabList newTabList(@NotNull BukkitTabPlayer player) {
return new NMSPacketTabList(player);
}
@Override
public @Nullable ComponentConverter getComponentConverter() {
return componentConverter;
}
@Override
@NotNull
public FunctionWithException<BukkitTabPlayer, Channel> getChannelFunction() {
return player -> ((CraftPlayer)player.getPlayer()).getHandle().playerConnection.networkManager.channel;
}
}
@@ -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);
}
}
@@ -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);
}
}
+4
View File
@@ -0,0 +1,4 @@
dependencies {
implementation(projects.bukkit)
compileOnly("org.bukkit:craftbukkit:1.8.8-R0.1-SNAPSHOT")
}
@@ -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);
}
}
@@ -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;
}
}
@@ -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);
}
}
@@ -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);
}
/**
+4 -6
View File
@@ -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;
+29
View File
@@ -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)
}
}
+1
View File
@@ -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"
Binary file not shown.
+1 -1
View File
@@ -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
Vendored
+5 -3
View File
@@ -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
View File
@@ -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
View File
@@ -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)
}
+29
View File
@@ -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)
}
}
+1
View File
@@ -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);
}
}
@@ -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
View File
@@ -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")
+1
View File
@@ -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);
}
/**
@@ -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);
}
/**
@@ -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)
@@ -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)));
}
/**
@@ -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) {
@@ -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
@@ -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();
}
}
@@ -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 {
@@ -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> {
@@ -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
-4
View File
@@ -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