mirror of
https://github.com/NEZNAMY/TAB.git
synced 2026-11-12 20:22:01 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
71a19c63d8 | ||
|
|
657327e7ee | ||
|
|
efeea7cf0f | ||
|
|
7a4637368d | ||
|
|
2e6ccd078b | ||
|
|
c8454ec0c1 | ||
|
|
7127a77d92 | ||
|
|
b23912b52f | ||
|
|
4b21148a03 | ||
|
|
2d5ef95e54 | ||
|
|
b5e2d376dd | ||
|
|
c40b337edc | ||
|
|
f8583642a8 | ||
|
|
707f28df16 | ||
|
|
dd78da089d | ||
|
|
c4ea65ed70 | ||
|
|
aabba806c4 | ||
|
|
88167fc42d | ||
|
|
e4d7005e46 | ||
|
|
5ee27f7ed7 | ||
|
|
9a4492c401 | ||
|
|
39a2aee5a0 | ||
|
|
5184ef170f |
No files matched your search
@@ -3,7 +3,7 @@ plugins {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("com.github.johnrengelman:shadow:8.1.1")
|
||||
implementation("io.freefair.gradle:lombok-plugin:8.6")
|
||||
implementation("fabric-loom:fabric-loom.gradle.plugin:1.8-SNAPSHOT")
|
||||
implementation("com.gradleup.shadow:shadow-gradle-plugin:8.3.6")
|
||||
implementation("io.freefair.gradle:lombok-plugin:8.13")
|
||||
implementation("dev.architectury.loom:dev.architectury.loom.gradle.plugin:1.10-SNAPSHOT")
|
||||
}
|
||||
@@ -3,6 +3,9 @@ rootProject.name = "build-logic"
|
||||
dependencyResolutionManagement {
|
||||
repositories {
|
||||
maven("https://maven.fabricmc.net/")
|
||||
maven("https://maven.architectury.dev/")
|
||||
maven("https://maven.minecraftforge.net/")
|
||||
maven("https://maven.neoforged.net/releases/")
|
||||
gradlePluginPortal()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
plugins {
|
||||
id("tab.standard-conventions")
|
||||
id("com.github.johnrengelman.shadow")
|
||||
id("com.gradleup.shadow")
|
||||
}
|
||||
|
||||
tasks.withType<Zip>().configureEach {
|
||||
|
||||
+5
-2
@@ -4,12 +4,13 @@ plugins {
|
||||
|
||||
allprojects {
|
||||
group = "me.neznamy"
|
||||
version = "5.1.1"
|
||||
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(
|
||||
@@ -21,7 +22,9 @@ val platforms = setOf(
|
||||
projects.component,
|
||||
projects.velocity,
|
||||
projects.sponge,
|
||||
projects.fabric
|
||||
projects.fabric,
|
||||
projects.neoforge,
|
||||
projects.forge
|
||||
).map { it.dependencyProject }
|
||||
|
||||
val special = setOf(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
plugins {
|
||||
id("io.papermc.paperweight.userdev") version "2.0.0-beta.8"
|
||||
id("io.papermc.paperweight.userdev") version "2.0.0-beta.16"
|
||||
}
|
||||
|
||||
repositories {
|
||||
|
||||
@@ -96,8 +96,10 @@ public class BukkitPlatform implements BackendPlatform {
|
||||
private final ImplementationProvider serverImplementationProvider;
|
||||
|
||||
/** Implementation for sending new content to new players on old servers */
|
||||
@NotNull
|
||||
private final ViaVersionImplementationProvider viaVersionImplementationProvider = new ViaVersionImplementationProvider(serverVersion);
|
||||
@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.
|
||||
@@ -307,8 +309,10 @@ public class BukkitPlatform implements BackendPlatform {
|
||||
@NotNull
|
||||
@SneakyThrows
|
||||
public Scoreboard createScoreboard(@NotNull TabPlayer player) {
|
||||
Scoreboard scoreboard = viaVersionImplementationProvider.newScoreboard((BukkitTabPlayer) player);
|
||||
if (scoreboard != null) return scoreboard;
|
||||
if (viaVersionImplementationProvider != null) {
|
||||
Scoreboard scoreboard = viaVersionImplementationProvider.newScoreboard((BukkitTabPlayer) player);
|
||||
if (scoreboard != null) return scoreboard;
|
||||
}
|
||||
return serverImplementationProvider.newScoreboard((BukkitTabPlayer) player);
|
||||
}
|
||||
|
||||
@@ -332,8 +336,10 @@ public class BukkitPlatform implements BackendPlatform {
|
||||
@NotNull
|
||||
@SneakyThrows
|
||||
public TabList createTabList(@NotNull TabPlayer player) {
|
||||
TabList tabList = viaVersionImplementationProvider.newTabList((BukkitTabPlayer) player);
|
||||
if (tabList != null) return tabList;
|
||||
if (viaVersionImplementationProvider != null) {
|
||||
TabList tabList = viaVersionImplementationProvider.newTabList((BukkitTabPlayer) player);
|
||||
if (tabList != null) return tabList;
|
||||
}
|
||||
return serverImplementationProvider.newTabList((BukkitTabPlayer) player);
|
||||
}
|
||||
|
||||
|
||||
+1
-7
@@ -12,7 +12,6 @@ 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 me.neznamy.tab.shared.util.ReflectionUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -44,13 +43,11 @@ public class ViaVersionImplementationProvider {
|
||||
SCOREBOARD_IMPLEMENTATIONS.put(ProtocolVersion.V1_13, ViaScoreboard113::new);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private final ProtocolVersion serverVersion;
|
||||
|
||||
@Nullable
|
||||
public Scoreboard newScoreboard(@NotNull BukkitTabPlayer player) {
|
||||
if (!ReflectionUtils.classExists("com.viaversion.viaversion.protocols.v1_20_2to1_20_3.Protocol1_20_2To1_20_3")) {
|
||||
return null;
|
||||
}
|
||||
int serverVer = serverVersion.getNetworkId();
|
||||
int playerVer = player.getVersion().getNetworkId();
|
||||
for (Map.Entry<ProtocolVersion, Function<BukkitTabPlayer, Scoreboard>> entry : SCOREBOARD_IMPLEMENTATIONS.entrySet()) {
|
||||
@@ -63,9 +60,6 @@ public class ViaVersionImplementationProvider {
|
||||
|
||||
@Nullable
|
||||
public TabList newTabList(@NotNull BukkitTabPlayer player) {
|
||||
if (!ReflectionUtils.classExists("com.viaversion.viaversion.protocols.v1_21_2to1_21_4.Protocol1_21_2To1_21_4")) {
|
||||
return null;
|
||||
}
|
||||
int serverVer = serverVersion.getNetworkId();
|
||||
int playerVer = player.getVersion().getNetworkId();
|
||||
for (Map.Entry<ProtocolVersion, Function<BukkitTabPlayer, TabList>> entry : TABLIST_IMPLEMENTATIONS.entrySet()) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -132,7 +132,7 @@ public class FabricScoreboard extends SafeScoreboard<FabricTabPlayer> {
|
||||
}
|
||||
if (isAntiOverrideTeams() && packet instanceof ClientboundSetPlayerTeamPacket team) {
|
||||
if (team.method == TeamAction.UPDATE) return;
|
||||
team.players = onTeamPacket(team.method, team.getName(), team.players);
|
||||
team.players = onTeamPacket(team.method, team.getName(), team.getPlayers());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,9 +15,9 @@ public class FabricTAB implements DedicatedServerModInitializer {
|
||||
|
||||
@Override
|
||||
public void onInitializeServer() {
|
||||
CommandRegistrationCallback.EVENT.register((dispatcher, $, $$) -> new FabricTabCommand().onRegisterCommands(dispatcher));
|
||||
CommandRegistrationCallback.EVENT.register((dispatcher, commandBuildContext, commandSelection) -> new FabricTabCommand().onRegisterCommands(dispatcher));
|
||||
ServerLifecycleEvents.SERVER_STARTING.register(server -> TAB.create(new FabricPlatform(server)));
|
||||
ServerLifecycleEvents.SERVER_STOPPING.register($ -> TAB.getInstance().unload());
|
||||
ServerLifecycleEvents.SERVER_STOPPING.register(server -> TAB.getInstance().unload());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
"name": "@name@",
|
||||
"description": "@description@",
|
||||
"authors": ["@author@"],
|
||||
"contributors": ["credits"],
|
||||
"environment": "server",
|
||||
"entrypoints": {
|
||||
"server": ["me.neznamy.tab.platforms.fabric.FabricTAB"]
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
accessWidener v1 named
|
||||
accessWidener v2 named
|
||||
|
||||
# Scoreboard packets
|
||||
accessible field net/minecraft/network/protocol/game/ClientboundSetPlayerTeamPacket method I
|
||||
accessible field net/minecraft/network/protocol/game/ClientboundSetPlayerTeamPacket players Ljava/util/Collection;
|
||||
mutable field net/minecraft/network/protocol/game/ClientboundSetPlayerTeamPacket players Ljava/util/Collection;
|
||||
|
||||
# Logger
|
||||
accessible field net/minecraft/server/MinecraftServer LOGGER Lorg/slf4j/Logger;
|
||||
|
||||
# Tablist packet entries
|
||||
accessible field net/minecraft/network/protocol/game/ClientboundPlayerInfoUpdatePacket entries Ljava/util/List;
|
||||
mutable field net/minecraft/network/protocol/game/ClientboundPlayerInfoUpdatePacket entries Ljava/util/List;
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
plugins {
|
||||
id("dev.architectury.loom")
|
||||
}
|
||||
|
||||
repositories {
|
||||
// Gradle doesn't support combining settings and project repositories, so we have to re-declare all the settings repos we need
|
||||
maven("https://jitpack.io") // YamlAssist
|
||||
maven("https://repo.opencollab.dev/maven-snapshots/")
|
||||
maven("https://repo.viaversion.com/")
|
||||
maven("https://oss.sonatype.org/content/repositories/snapshots")
|
||||
maven("https://maven.nucleoid.xyz/")
|
||||
maven("https://maven.neoforged.net/releases")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
minecraft("com.mojang:minecraft:1.21.5")
|
||||
mappings(loom.officialMojangMappings())
|
||||
forge("net.minecraftforge:forge:1.21.5-55.0.3")
|
||||
api(projects.shared)
|
||||
compileOnly("net.luckperms:api:5.4")
|
||||
}
|
||||
|
||||
loom.forge.accessTransformer(file("src/main/resources/META-INF/accesstransformer.cfg"))
|
||||
|
||||
tasks {
|
||||
compileJava {
|
||||
options.release.set(21)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
loom.platform=forge
|
||||
@@ -0,0 +1,64 @@
|
||||
package me.neznamy.tab.platforms.forge;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import me.neznamy.tab.api.bossbar.BarColor;
|
||||
import me.neznamy.tab.api.bossbar.BarStyle;
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import me.neznamy.tab.shared.platform.decorators.SafeBossBar;
|
||||
import net.minecraft.server.level.ServerBossEvent;
|
||||
import net.minecraft.world.BossEvent.BossBarColor;
|
||||
import net.minecraft.world.BossEvent.BossBarOverlay;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* BossBar implementation for Forge using packets.
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class ForgeBossBar extends SafeBossBar<ServerBossEvent> {
|
||||
|
||||
/** Player this BossBar belongs to */
|
||||
@NotNull
|
||||
private final ForgeTabPlayer player;
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public ServerBossEvent constructBossBar(@NotNull TabComponent title, float progress, @NotNull BarColor color, @NotNull BarStyle style) {
|
||||
ServerBossEvent bar = new ServerBossEvent(
|
||||
title.convert(),
|
||||
BossBarColor.valueOf(color.name()),
|
||||
BossBarOverlay.valueOf(style.name())
|
||||
);
|
||||
bar.setProgress(progress);
|
||||
return bar;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void create(@NotNull BossBarInfo bar) {
|
||||
bar.getBossBar().addPlayer(player.getPlayer());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateTitle(@NotNull BossBarInfo bar) {
|
||||
bar.getBossBar().setName(bar.getTitle().convert());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateProgress(@NotNull BossBarInfo bar) {
|
||||
bar.getBossBar().setProgress(bar.getProgress());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateStyle(@NotNull BossBarInfo bar) {
|
||||
bar.getBossBar().setOverlay(BossBarOverlay.valueOf(bar.getStyle().name()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateColor(@NotNull BossBarInfo bar) {
|
||||
bar.getBossBar().setColor(BossBarColor.valueOf(bar.getColor().name()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(@NotNull BossBarInfo bar) {
|
||||
bar.getBossBar().removePlayer(player.getPlayer());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package me.neznamy.tab.platforms.forge;
|
||||
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.platform.EventListener;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraftforge.common.MinecraftForge;
|
||||
import net.minecraftforge.event.entity.player.PlayerEvent;
|
||||
import net.minecraftforge.eventbus.api.IEventBus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Event listener for Forge.
|
||||
*/
|
||||
public class ForgeEventListener implements EventListener<ServerPlayer> {
|
||||
|
||||
/**
|
||||
* Registers all event listeners.
|
||||
*/
|
||||
public void register() {
|
||||
IEventBus eventBus = MinecraftForge.EVENT_BUS;
|
||||
eventBus.addListener((PlayerEvent.PlayerLoggedInEvent event) -> join((ServerPlayer) event.getEntity()));
|
||||
eventBus.addListener((PlayerEvent.PlayerLoggedOutEvent event) -> quit(event.getEntity().getUUID()));
|
||||
eventBus.addListener((PlayerEvent.PlayerRespawnEvent event) -> {
|
||||
ServerPlayer player = (ServerPlayer) event.getEntity();
|
||||
replacePlayer(player.getUUID(), player);
|
||||
worldChange(player.getUUID(), ForgeTAB.getLevelName(player.level()));
|
||||
});
|
||||
eventBus.addListener((PlayerEvent.PlayerChangedDimensionEvent event) -> worldChange(event.getEntity().getUUID(), ForgeTAB.getLevelName(event.getEntity().level())));
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public TabPlayer createPlayer(@NotNull ServerPlayer player) {
|
||||
return new ForgeTabPlayer((ForgePlatform) TAB.getInstance().getPlatform(), player);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package me.neznamy.tab.platforms.forge;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import me.neznamy.tab.shared.features.injection.NettyPipelineInjector;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Pipeline injector for Forge.
|
||||
*/
|
||||
public class ForgePipelineInjector extends NettyPipelineInjector {
|
||||
|
||||
/**
|
||||
* Constructs new instance.
|
||||
*/
|
||||
public ForgePipelineInjector() {
|
||||
super("packet_handler");
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
protected Channel getChannel(@NotNull TabPlayer player) {
|
||||
return ((ForgeTabPlayer)player).getPlayer().connection.getConnection().channel();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
package me.neznamy.tab.platforms.forge;
|
||||
|
||||
import com.mojang.logging.LogUtils;
|
||||
import me.neznamy.chat.component.KeybindComponent;
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import me.neznamy.chat.component.TextComponent;
|
||||
import me.neznamy.chat.component.TranslatableComponent;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.backend.BackendPlatform;
|
||||
import me.neznamy.tab.shared.features.PerWorldPlayerListConfiguration;
|
||||
import me.neznamy.tab.shared.features.injection.PipelineInjector;
|
||||
import me.neznamy.tab.shared.features.types.TabFeature;
|
||||
import me.neznamy.tab.shared.platform.BossBar;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import net.minecraft.SharedConstants;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.network.chat.MutableComponent;
|
||||
import net.minecraft.network.chat.Style;
|
||||
import net.minecraft.network.chat.TextColor;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraftforge.fml.loading.FMLPaths;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* Platform implementation for NeoForge
|
||||
*
|
||||
* @param server Minecraft server reference
|
||||
*/
|
||||
public record ForgePlatform(MinecraftServer server) implements BackendPlatform {
|
||||
|
||||
@Override
|
||||
public void registerUnknownPlaceholder(@NotNull String identifier) {
|
||||
registerDummyPlaceholder(identifier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadPlayers() {
|
||||
for (ServerPlayer player : getOnlinePlayers()) {
|
||||
TAB.getInstance().addPlayer(new ForgeTabPlayer(this, player));
|
||||
}
|
||||
}
|
||||
|
||||
private Collection<ServerPlayer> getOnlinePlayers() {
|
||||
// It's nullable on startup
|
||||
return server.getPlayerList() == null ? Collections.emptyList() : server.getPlayerList().getPlayers();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public PipelineInjector createPipelineInjector() {
|
||||
return new ForgePipelineInjector();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public TabFeature getPerWorldPlayerList(@NotNull PerWorldPlayerListConfiguration configuration) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logInfo(@NotNull TabComponent message) {
|
||||
LogUtils.getLogger().info("[TAB] {}", message.toRawText());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logWarn(@NotNull TabComponent message) {
|
||||
LogUtils.getLogger().warn("[TAB] {}", message.toRawText());
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String getServerVersionInfo() {
|
||||
return "[Forge] " + SharedConstants.getCurrentVersion().getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerListener() {
|
||||
new ForgeEventListener().register();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerCommand() {
|
||||
// Event listener must be registered in main class
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startMetrics() {
|
||||
// Not available
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public File getDataFolder() {
|
||||
return FMLPaths.CONFIGDIR.get().resolve(TabConstants.PLUGIN_ID).toFile();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Component convertComponent(@NotNull TabComponent component) {
|
||||
// Component type
|
||||
MutableComponent nmsComponent;
|
||||
if (component instanceof TextComponent) {
|
||||
nmsComponent = Component.literal(((TextComponent) component).getText());
|
||||
} else if (component instanceof TranslatableComponent) {
|
||||
nmsComponent = Component.translatable(((TranslatableComponent) component).getKey());
|
||||
} else if (component instanceof KeybindComponent) {
|
||||
nmsComponent = Component.keybind(((KeybindComponent) component).getKeybind());
|
||||
} else {
|
||||
throw new IllegalStateException("Unexpected component type: " + component.getClass().getName());
|
||||
}
|
||||
|
||||
// Component style
|
||||
nmsComponent.setStyle(new Style(
|
||||
component.getModifier().getColor() == null ? null : TextColor.fromRgb(component.getModifier().getColor().getRgb()),
|
||||
component.getModifier().getShadowColor(),
|
||||
component.getModifier().getBold(),
|
||||
component.getModifier().getItalic(),
|
||||
component.getModifier().getUnderlined(),
|
||||
component.getModifier().getStrikethrough(),
|
||||
component.getModifier().getObfuscated(),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
component.getModifier().getFont() == null ? null : ResourceLocation.tryParse(component.getModifier().getFont())
|
||||
));
|
||||
|
||||
// Extra
|
||||
for (TabComponent extra : component.getExtra()) {
|
||||
nmsComponent.getSiblings().add(convertComponent(extra));
|
||||
}
|
||||
|
||||
return nmsComponent;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Scoreboard createScoreboard(@NotNull TabPlayer player) {
|
||||
return new ForgeScoreboard((ForgeTabPlayer) player);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public BossBar createBossBar(@NotNull TabPlayer player) {
|
||||
return new ForgeBossBar((ForgeTabPlayer) player);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public TabList createTabList(@NotNull TabPlayer player) {
|
||||
return new ForgeTabList((ForgeTabPlayer) player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsScoreboards() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getTPS() {
|
||||
double mspt = getMSPT();
|
||||
if (mspt < 50) return 20;
|
||||
return Math.round(1000 / mspt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getMSPT() {
|
||||
return (float) server.getAverageTickTimeNanos() / 1000000;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
package me.neznamy.tab.platforms.forge;
|
||||
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.platform.decorators.SafeScoreboard;
|
||||
import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.network.chat.numbers.FixedFormat;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.network.protocol.game.*;
|
||||
import net.minecraft.world.scores.PlayerTeam;
|
||||
import net.minecraft.world.scores.Scoreboard;
|
||||
import net.minecraft.world.scores.criteria.ObjectiveCriteria;
|
||||
import net.minecraft.world.scores.criteria.ObjectiveCriteria.RenderType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Scoreboard implementation for Forge using packets.
|
||||
*/
|
||||
public class ForgeScoreboard extends SafeScoreboard<ForgeTabPlayer> {
|
||||
|
||||
private static final ChatFormatting[] formats = ChatFormatting.values();
|
||||
private static final net.minecraft.world.scores.Team.CollisionRule[] collisions = net.minecraft.world.scores.Team.CollisionRule.values();
|
||||
private static final net.minecraft.world.scores.Team.Visibility[] visibilities = net.minecraft.world.scores.Team.Visibility.values();
|
||||
private static final Scoreboard dummyScoreboard = new Scoreboard();
|
||||
|
||||
/**
|
||||
* Constructs new instance with given player.
|
||||
*
|
||||
* @param player
|
||||
* Player this scoreboard will belong to
|
||||
*/
|
||||
public ForgeScoreboard(ForgeTabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerObjective(@NonNull Objective objective) {
|
||||
net.minecraft.world.scores.Objective obj = new net.minecraft.world.scores.Objective(
|
||||
dummyScoreboard,
|
||||
objective.getName(),
|
||||
ObjectiveCriteria.DUMMY,
|
||||
objective.getTitle().convert(),
|
||||
RenderType.values()[objective.getHealthDisplay().ordinal()],
|
||||
false,
|
||||
objective.getNumberFormat() == null ? null : objective.getNumberFormat().toFixedFormat(FixedFormat::new)
|
||||
);
|
||||
objective.setPlatformObjective(obj);
|
||||
sendPacket(new ClientboundSetObjectivePacket(obj, ObjectiveAction.REGISTER));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDisplaySlot(@NonNull Objective objective) {
|
||||
sendPacket(new ClientboundSetDisplayObjectivePacket(net.minecraft.world.scores.DisplaySlot.values()[objective.getDisplaySlot().ordinal()], (net.minecraft.world.scores.Objective) objective.getPlatformObjective()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterObjective(@NonNull Objective objective) {
|
||||
sendPacket(new ClientboundSetObjectivePacket((net.minecraft.world.scores.Objective) objective.getPlatformObjective(), ObjectiveAction.UNREGISTER));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateObjective(@NonNull Objective objective) {
|
||||
net.minecraft.world.scores.Objective obj = (net.minecraft.world.scores.Objective) objective.getPlatformObjective();
|
||||
obj.setDisplayName(objective.getTitle().convert());
|
||||
obj.setRenderType(RenderType.values()[objective.getHealthDisplay().ordinal()]);
|
||||
sendPacket(new ClientboundSetObjectivePacket(obj, ObjectiveAction.UPDATE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setScore(@NonNull Score score) {
|
||||
sendPacket(new ClientboundSetScorePacket(
|
||||
score.getHolder(),
|
||||
score.getObjective().getName(),
|
||||
score.getValue(),
|
||||
Optional.ofNullable(score.getDisplayName() == null ? null : score.getDisplayName().convert()),
|
||||
Optional.ofNullable(score.getNumberFormat() == null ? null : score.getNumberFormat().toFixedFormat(FixedFormat::new))
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeScore(@NonNull Score score) {
|
||||
sendPacket(new ClientboundResetScorePacket(score.getHolder(), score.getObjective().getName()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Object createTeam(@NonNull String name) {
|
||||
return new PlayerTeam(dummyScoreboard, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerTeam(@NonNull Team team) {
|
||||
updateTeamProperties(team);
|
||||
PlayerTeam t = (PlayerTeam) team.getPlatformTeam();
|
||||
t.getPlayers().addAll(team.getPlayers());
|
||||
sendPacket(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(t, true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterTeam(@NonNull Team team) {
|
||||
sendPacket(ClientboundSetPlayerTeamPacket.createRemovePacket((PlayerTeam) team.getPlatformTeam()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateTeam(@NonNull Team team) {
|
||||
updateTeamProperties(team);
|
||||
sendPacket(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket((PlayerTeam) team.getPlatformTeam(), false));
|
||||
}
|
||||
|
||||
private void updateTeamProperties(@NonNull Team team) {
|
||||
PlayerTeam t = (PlayerTeam) team.getPlatformTeam();
|
||||
t.setAllowFriendlyFire((team.getOptions() & 0x01) != 0);
|
||||
t.setSeeFriendlyInvisibles((team.getOptions() & 0x02) != 0);
|
||||
t.setColor(formats[team.getColor().getLegacyColor().ordinal()]);
|
||||
t.setCollisionRule(collisions[team.getCollision().ordinal()]);
|
||||
t.setNameTagVisibility(visibilities[team.getVisibility().ordinal()]);
|
||||
t.setPlayerPrefix(team.getPrefix().convert());
|
||||
t.setPlayerSuffix(team.getSuffix().convert());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketSend(@NonNull Object packet) {
|
||||
if (isAntiOverrideScoreboard()) {
|
||||
if (packet instanceof ClientboundSetDisplayObjectivePacket display) {
|
||||
TAB.getInstance().getFeatureManager().onDisplayObjective(player, display.getSlot().ordinal(), display.getObjectiveName());
|
||||
}
|
||||
if (packet instanceof ClientboundSetObjectivePacket objective) {
|
||||
TAB.getInstance().getFeatureManager().onObjective(player, objective.getMethod(), objective.getObjectiveName());
|
||||
}
|
||||
}
|
||||
if (isAntiOverrideTeams() && packet instanceof ClientboundSetPlayerTeamPacket team) {
|
||||
if (team.method == TeamAction.UPDATE) return;
|
||||
team.players = onTeamPacket(team.method, team.getName(), team.getPlayers());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the packet to the player.
|
||||
*
|
||||
* @param packet
|
||||
* Packet to send
|
||||
*/
|
||||
private void sendPacket(@NotNull Packet<?> packet) {
|
||||
player.getPlayer().connection.send(packet);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package me.neznamy.tab.platforms.forge;
|
||||
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.storage.ServerLevelData;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
import net.minecraftforge.common.MinecraftForge;
|
||||
import net.minecraftforge.event.RegisterCommandsEvent;
|
||||
import net.minecraftforge.event.server.ServerStartingEvent;
|
||||
import net.minecraftforge.event.server.ServerStoppingEvent;
|
||||
import net.minecraftforge.eventbus.api.IEventBus;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@OnlyIn(Dist.DEDICATED_SERVER)
|
||||
@Mod(value = "tab")
|
||||
public class ForgeTAB {
|
||||
|
||||
public ForgeTAB() {
|
||||
IEventBus EVENT_BUS = MinecraftForge.EVENT_BUS;
|
||||
EVENT_BUS.addListener((RegisterCommandsEvent event) -> new ForgeTabCommand().onRegisterCommands(event.getDispatcher()));
|
||||
EVENT_BUS.addListener((ServerStartingEvent event) -> TAB.create(new ForgePlatform(event.getServer())));
|
||||
EVENT_BUS.addListener((ServerStoppingEvent event) -> TAB.getInstance().unload());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static String getLevelName(@NotNull Level level) {
|
||||
String path = level.dimension().location().getPath();
|
||||
return ((ServerLevelData)level.getLevelData()).getLevelName() + switch (path) {
|
||||
case "overworld" -> ""; // No suffix for overworld
|
||||
case "the_nether" -> "_nether";
|
||||
default -> "_" + path; // End + default behavior for other dimensions created by mods
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package me.neznamy.tab.platforms.forge;
|
||||
|
||||
import com.mojang.brigadier.CommandDispatcher;
|
||||
import com.mojang.brigadier.arguments.StringArgumentType;
|
||||
import com.mojang.brigadier.context.CommandContext;
|
||||
import com.mojang.brigadier.suggestion.Suggestions;
|
||||
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
|
||||
import com.mojang.brigadier.tree.ArgumentCommandNode;
|
||||
import com.mojang.brigadier.tree.LiteralCommandNode;
|
||||
import me.neznamy.tab.platforms.forge.hook.LuckPermsAPIHook;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
import net.minecraft.commands.Commands;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
/**
|
||||
* Command handler for plugin's command for Forge.
|
||||
*/
|
||||
public class ForgeTabCommand {
|
||||
|
||||
/**
|
||||
* Handles command register request from server and registers own command.
|
||||
*
|
||||
* @param dispatcher
|
||||
* Dispatcher to register command to
|
||||
*/
|
||||
public void onRegisterCommands(@NotNull CommandDispatcher<CommandSourceStack> dispatcher) {
|
||||
LiteralCommandNode<CommandSourceStack> command = Commands.literal("tab") // TODO extract it from Platform somehow
|
||||
.executes(context -> executeCommand(context.getSource(), new String[0]))
|
||||
.build();
|
||||
ArgumentCommandNode<CommandSourceStack, String> args = Commands.argument("args", StringArgumentType.greedyString())
|
||||
.suggests((context, builder) -> getSuggestions(context.getSource(), getArguments(context), builder))
|
||||
.executes(context -> executeCommand(context.getSource(), getArguments(context)))
|
||||
.build();
|
||||
command.addChild(args);
|
||||
dispatcher.getRoot().addChild(command);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private String[] getArguments(@NotNull CommandContext<CommandSourceStack> context) {
|
||||
String input = context.getInput();
|
||||
int firstSpace = input.indexOf(' ');
|
||||
if (firstSpace == -1) return new String[0];
|
||||
String rawArgs = input.substring(firstSpace + 1);
|
||||
String[] args = rawArgs.split(" ");
|
||||
if (rawArgs.endsWith(" ")) {
|
||||
args = Arrays.copyOf(args, args.length+1);
|
||||
args[args.length-1] = "";
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
@SuppressWarnings("SameReturnValue") // Unused by plugin
|
||||
private int executeCommand(@NotNull CommandSourceStack source, @NotNull String[] args) {
|
||||
if (TAB.getInstance().isPluginDisabled()) {
|
||||
boolean hasReloadPermission = LuckPermsAPIHook.hasPermission(source, TabConstants.Permission.COMMAND_RELOAD);
|
||||
boolean hasAdminPermission = LuckPermsAPIHook.hasPermission(source, TabConstants.Permission.COMMAND_ALL);
|
||||
for (String message : TAB.getInstance().getDisabledCommand().execute(args, hasReloadPermission, hasAdminPermission)) {
|
||||
source.sendSystemMessage(TabComponent.fromColoredText(message).convert());
|
||||
}
|
||||
} else {
|
||||
if (source.getEntity() == null) {
|
||||
TAB.getInstance().getCommand().execute(null, args);
|
||||
} else {
|
||||
TabPlayer player = TAB.getInstance().getPlayer(source.getEntity().getUUID());
|
||||
if (player != null) TAB.getInstance().getCommand().execute(player, args);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private CompletableFuture<Suggestions> getSuggestions(@NotNull CommandSourceStack source, @NotNull String[] args,
|
||||
@NotNull SuggestionsBuilder builder) {
|
||||
TabPlayer player = null;
|
||||
if (source.getEntity() != null) {
|
||||
player = TAB.getInstance().getPlayer(source.getEntity().getUUID());
|
||||
if (player == null) return Suggestions.empty();
|
||||
}
|
||||
|
||||
SuggestionsBuilder newBuilder = builder;
|
||||
int lastSpace = newBuilder.getRemaining().lastIndexOf(' ');
|
||||
if (lastSpace != -1) {
|
||||
newBuilder = newBuilder.createOffset(lastSpace + 1 + newBuilder.getStart());
|
||||
}
|
||||
for (String suggestion : TAB.getInstance().getCommand().complete(player, args)) {
|
||||
newBuilder.suggest(suggestion);
|
||||
}
|
||||
return newBuilder.buildFuture();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
package me.neznamy.tab.platforms.forge;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import com.mojang.authlib.properties.Property;
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import me.neznamy.tab.shared.platform.decorators.TrackedTabList;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket;
|
||||
import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket;
|
||||
import net.minecraft.network.protocol.game.ClientboundTabListPacket;
|
||||
import net.minecraft.world.level.GameType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* TabList implementation for Forge using packets.
|
||||
*/
|
||||
public class ForgeTabList extends TrackedTabList<ForgeTabPlayer> {
|
||||
|
||||
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> addPlayer = EnumSet.allOf(ClientboundPlayerInfoUpdatePacket.Action.class);
|
||||
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateDisplayName = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME);
|
||||
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateLatency = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY);
|
||||
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateGameMode = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE);
|
||||
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateListed = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED);
|
||||
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateListOrder = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LIST_ORDER);
|
||||
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateHat = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_HAT);
|
||||
|
||||
/**
|
||||
* Constructs new instance.
|
||||
*
|
||||
* @param player
|
||||
* Player this tablist will belong to
|
||||
*/
|
||||
public ForgeTabList(@NotNull ForgeTabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeEntry(@NonNull UUID entry) {
|
||||
sendPacket(new ClientboundPlayerInfoRemovePacket(Collections.singletonList(entry)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDisplayName0(@NonNull UUID entry, @Nullable TabComponent displayName) {
|
||||
sendPacket(updateDisplayName, entry, "", null, false, 0, 0, displayName, 0, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateLatency(@NonNull UUID entry, int latency) {
|
||||
sendPacket(updateLatency, entry, "", null, false, latency, 0, null, 0, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateGameMode(@NonNull UUID entry, int gameMode) {
|
||||
sendPacket(updateGameMode, entry, "", null, false, 0, gameMode, null, 0, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateListed(@NonNull UUID entry, boolean listed) {
|
||||
sendPacket(updateListed, entry, "", null, listed, 0, 0, null, 0, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateListOrder(@NonNull UUID entry, int listOrder) {
|
||||
sendPacket(updateListOrder, entry, "", null, false, 0, 0, null, listOrder, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateHat(@NonNull UUID entry, boolean showHat) {
|
||||
sendPacket(updateHat, entry, "", null, false, 0, 0, null, 0, showHat);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addEntry0(@NonNull Entry entry) {
|
||||
sendPacket(addPlayer, entry.getUniqueId(), entry.getName(), entry.getSkin(), entry.isListed(), entry.getLatency(),
|
||||
entry.getGameMode(), entry.getDisplayName(), entry.getListOrder(), entry.isShowHat());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPlayerListHeaderFooter(@NonNull TabComponent header, @NonNull TabComponent footer) {
|
||||
sendPacket(new ClientboundTabListPacket(header.convert(), footer.convert()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsEntry(@NonNull UUID entry) {
|
||||
return true; // TODO?
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Skin getSkin() {
|
||||
Collection<Property> properties = player.getPlayer().getGameProfile().getProperties().get(TEXTURES_PROPERTY);
|
||||
if (properties.isEmpty()) return null; // Offline mode
|
||||
Property property = properties.iterator().next();
|
||||
return new Skin(property.value(), property.signature());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketSend(@NonNull Object packet) {
|
||||
if (packet instanceof ClientboundPlayerInfoUpdatePacket info) {
|
||||
EnumSet<ClientboundPlayerInfoUpdatePacket.Action> actions = info.actions();
|
||||
List<ClientboundPlayerInfoUpdatePacket.Entry> updatedList = new ArrayList<>();
|
||||
boolean rewritePacket = false;
|
||||
for (ClientboundPlayerInfoUpdatePacket.Entry nmsData : info.entries()) {
|
||||
boolean rewriteEntry = false;
|
||||
Component displayName = nmsData.displayName();
|
||||
int latency = nmsData.latency();
|
||||
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME)) {
|
||||
TabComponent expectedDisplayName = getExpectedDisplayNames().get(nmsData.profileId());
|
||||
if (expectedDisplayName != null && expectedDisplayName.convert() != displayName) {
|
||||
displayName = expectedDisplayName.convert();
|
||||
rewriteEntry = rewritePacket = true;
|
||||
}
|
||||
}
|
||||
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY)) {
|
||||
int newLatency = TAB.getInstance().getFeatureManager().onLatencyChange(player, nmsData.profileId(), latency);
|
||||
if (newLatency != latency) {
|
||||
latency = newLatency;
|
||||
rewriteEntry = rewritePacket = true;
|
||||
}
|
||||
}
|
||||
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER)) {
|
||||
TAB.getInstance().getFeatureManager().onEntryAdd(player, nmsData.profileId(), nmsData.profile().getName());
|
||||
}
|
||||
updatedList.add(rewriteEntry ? new ClientboundPlayerInfoUpdatePacket.Entry(
|
||||
nmsData.profileId(), nmsData.profile(), nmsData.listed(), latency, nmsData.gameMode(), displayName,
|
||||
nmsData.showHat(), nmsData.listOrder(), nmsData.chatSession()
|
||||
) : nmsData);
|
||||
}
|
||||
if (rewritePacket) info.entries = updatedList;
|
||||
}
|
||||
}
|
||||
|
||||
private void sendPacket(@NonNull EnumSet<ClientboundPlayerInfoUpdatePacket.Action> action, @NonNull UUID id, @NonNull String name, @Nullable Skin skin,
|
||||
boolean listed, int latency, int gameMode, @Nullable TabComponent displayName, int listOrder, boolean showHat) {
|
||||
ClientboundPlayerInfoUpdatePacket packet = new ClientboundPlayerInfoUpdatePacket(action, Collections.emptyList());
|
||||
packet.entries = Collections.singletonList(new ClientboundPlayerInfoUpdatePacket.Entry(
|
||||
id,
|
||||
action.contains(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER) ? createProfile(id, name, skin) : null,
|
||||
listed,
|
||||
latency,
|
||||
GameType.byId(gameMode),
|
||||
displayName == null ? null : displayName.convert(),
|
||||
showHat,
|
||||
listOrder,
|
||||
null
|
||||
));
|
||||
sendPacket(packet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates GameProfile from given parameters.
|
||||
*
|
||||
* @param id
|
||||
* Profile ID
|
||||
* @param name
|
||||
* Profile name
|
||||
* @param skin
|
||||
* Player skin
|
||||
* @return GameProfile from given parameters
|
||||
*/
|
||||
@NotNull
|
||||
private GameProfile createProfile(@NonNull UUID id, @NonNull String name, @Nullable Skin skin) {
|
||||
GameProfile profile = new GameProfile(id, name);
|
||||
if (skin != null) {
|
||||
profile.getProperties().put(TabList.TEXTURES_PROPERTY,
|
||||
new Property(TabList.TEXTURES_PROPERTY, skin.getValue(), skin.getSignature()));
|
||||
}
|
||||
return profile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the packet to the player.
|
||||
*
|
||||
* @param packet
|
||||
* Packet to send
|
||||
*/
|
||||
private void sendPacket(@NotNull Packet<?> packet) {
|
||||
player.getPlayer().connection.send(packet);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package me.neznamy.tab.platforms.forge;
|
||||
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import me.neznamy.tab.platforms.forge.hook.LuckPermsAPIHook;
|
||||
import me.neznamy.tab.shared.backend.BackendTabPlayer;
|
||||
import net.minecraft.SharedConstants;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* TabPlayer implementation for Forge.
|
||||
*/
|
||||
public class ForgeTabPlayer extends BackendTabPlayer {
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters.
|
||||
*
|
||||
* @param platform
|
||||
* Server platform
|
||||
* @param player
|
||||
* Platform's player object
|
||||
*/
|
||||
public ForgeTabPlayer(@NotNull ForgePlatform platform, @NotNull ServerPlayer player) {
|
||||
super(platform, player, player.getUUID(), player.getGameProfile().getName(),
|
||||
ForgeTAB.getLevelName(player.level()), SharedConstants.getProtocolVersion());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPermission(@NotNull String permission) {
|
||||
return LuckPermsAPIHook.hasPermission(getPlayer().createCommandSourceStack(), permission);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPing() {
|
||||
return getPlayer().connection.latency();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(@NotNull TabComponent message) {
|
||||
getPlayer().sendSystemMessage(message.convert());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasInvisibilityPotion() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDisguised() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public ServerPlayer getPlayer() {
|
||||
return (ServerPlayer) player;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ForgePlatform getPlatform() {
|
||||
return (ForgePlatform) platform;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVanished0() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getGamemode() {
|
||||
return getPlayer().gameMode.getGameModeForPlayer().getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getHealth() {
|
||||
return getPlayer().getHealth();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String getDisplayName() {
|
||||
return getPlayer().getGameProfile().getName(); // Will make it work properly if someone asks
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package me.neznamy.tab.platforms.forge.hook;
|
||||
|
||||
import net.luckperms.api.LuckPermsProvider;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
import net.minecraftforge.fml.ModList;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Class hooking into LuckPerms mod for permission nodes instead
|
||||
* of only using the integrated OP level.
|
||||
*/
|
||||
public class LuckPermsAPIHook {
|
||||
|
||||
/** Flag tracking presence of LuckPerms API */
|
||||
private static final boolean luckPerms = ModList.get().isLoaded("luckperms");
|
||||
|
||||
/**
|
||||
* Checks for permission and returns the result.
|
||||
*
|
||||
* @param source
|
||||
* Source to check permission of
|
||||
* @param permission
|
||||
* Permission node to check
|
||||
* @return {@code true} if has permission, {@code false} if not
|
||||
*/
|
||||
public static boolean hasPermission(@NotNull CommandSourceStack source, @NotNull String permission) {
|
||||
if (source.hasPermission(4)) return true;
|
||||
return luckPerms && LuckPermsProvider.get().getUserManager().getUser(source.getPlayer().getUUID()).getCachedData().getPermissionData().checkPermission(permission).asBoolean();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
public net.minecraft.network.chat.Style <init>(Lnet/minecraft/network/chat/TextColor;Ljava/lang/Integer;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Lnet/minecraft/network/chat/ClickEvent;Lnet/minecraft/network/chat/HoverEvent;Ljava/lang/String;Lnet/minecraft/resources/ResourceLocation;)V # constructor
|
||||
public-f net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket entries # entries
|
||||
public-f net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket method # method
|
||||
public-f net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket players # players
|
||||
@@ -0,0 +1,27 @@
|
||||
modLoader = "javafml"
|
||||
loaderVersion = "[52,)"
|
||||
issueTrackerURL = "https://github.com/NEZNAMY/TAB/issues"
|
||||
license = "Apache License Version 2.0"
|
||||
|
||||
[[mods]]
|
||||
modId = "tab"
|
||||
version = "@version@"
|
||||
displayName = "@name@"
|
||||
authors = "@author@"
|
||||
credits = "@credits@"
|
||||
description = '''@description@'''
|
||||
displayURL = "@website@"
|
||||
|
||||
[[dependencies.tab]]
|
||||
modId = "forge"
|
||||
mandatory = true
|
||||
versionRange = "*"
|
||||
ordering = "NONE"
|
||||
side = "SERVER"
|
||||
|
||||
[[dependencies.tab]]
|
||||
modId = "minecraft"
|
||||
mandatory = true
|
||||
versionRange = "[1.21.4,1.21.5]"
|
||||
ordering = "NONE"
|
||||
side = "SERVER"
|
||||
Vendored
BIN
Binary file not shown.
+1
-1
@@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
@@ -55,7 +57,7 @@
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
@@ -84,7 +86,7 @@ done
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
|
||||
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
@@ -203,7 +205,7 @@ fi
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command:
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# and any embedded shellness will be escaped.
|
||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||
# treated as '${Hostname}' itself on the command line.
|
||||
|
||||
Vendored
+2
@@ -13,6 +13,8 @@
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
@rem SPDX-License-Identifier: Apache-2.0
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
|
||||
+11
-6
@@ -1,7 +1,7 @@
|
||||
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
|
||||
|
||||
plugins {
|
||||
id("com.github.johnrengelman.shadow")
|
||||
id("com.gradleup.shadow")
|
||||
}
|
||||
|
||||
val platforms = setOf(
|
||||
@@ -14,6 +14,12 @@ val platforms = setOf(
|
||||
rootProject.projects.sponge
|
||||
).map { it.dependencyProject }
|
||||
|
||||
val moddedPlatforms = setOf(
|
||||
rootProject.projects.fabric,
|
||||
rootProject.projects.neoforge,
|
||||
rootProject.projects.forge
|
||||
).map { it.dependencyProject }
|
||||
|
||||
tasks {
|
||||
shadowJar {
|
||||
archiveFileName.set("TAB-${project.version}.jar")
|
||||
@@ -29,10 +35,9 @@ tasks {
|
||||
registerPlatform(it, it.tasks.named<ShadowJar>("shadowJar").get())
|
||||
}
|
||||
|
||||
registerPlatform(rootProject.projects.fabric.dependencyProject, rootProject.projects.fabric.dependencyProject.tasks.named<org.gradle.jvm.tasks.Jar>("remapJar").get())
|
||||
|
||||
}
|
||||
build {
|
||||
dependsOn(shadowJar)
|
||||
moddedPlatforms.forEach {
|
||||
registerPlatform(it, it.tasks.named<org.gradle.jvm.tasks.Jar>("remapJar").get())
|
||||
}
|
||||
}
|
||||
build.get().dependsOn(shadowJar)
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
plugins {
|
||||
id("dev.architectury.loom")
|
||||
}
|
||||
|
||||
repositories {
|
||||
// Gradle doesn't support combining settings and project repositories, so we have to re-declare all the settings repos we need
|
||||
maven("https://jitpack.io") // YamlAssist
|
||||
maven("https://repo.opencollab.dev/maven-snapshots/")
|
||||
maven("https://repo.viaversion.com/")
|
||||
maven("https://oss.sonatype.org/content/repositories/snapshots")
|
||||
maven("https://maven.nucleoid.xyz/")
|
||||
maven("https://maven.neoforged.net/releases")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
minecraft("com.mojang:minecraft:1.21.5")
|
||||
mappings(loom.officialMojangMappings())
|
||||
neoForge("net.neoforged:neoforge:21.5.2-beta")
|
||||
api(projects.shared)
|
||||
compileOnly("net.luckperms:api:5.4")
|
||||
}
|
||||
|
||||
loom.neoForge.accessTransformer(file("src/main/resources/META-INF/accesstransformer.cfg"))
|
||||
|
||||
tasks {
|
||||
compileJava {
|
||||
options.release.set(21)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
loom.platform=neoforge
|
||||
@@ -0,0 +1,64 @@
|
||||
package me.neznamy.tab.platforms.neoforge;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import me.neznamy.tab.api.bossbar.BarColor;
|
||||
import me.neznamy.tab.api.bossbar.BarStyle;
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import me.neznamy.tab.shared.platform.decorators.SafeBossBar;
|
||||
import net.minecraft.server.level.ServerBossEvent;
|
||||
import net.minecraft.world.BossEvent.BossBarColor;
|
||||
import net.minecraft.world.BossEvent.BossBarOverlay;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* BossBar implementation for NeoForge using packets.
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class NeoForgeBossBar extends SafeBossBar<ServerBossEvent> {
|
||||
|
||||
/** Player this BossBar belongs to */
|
||||
@NotNull
|
||||
private final NeoForgeTabPlayer player;
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public ServerBossEvent constructBossBar(@NotNull TabComponent title, float progress, @NotNull BarColor color, @NotNull BarStyle style) {
|
||||
ServerBossEvent bar = new ServerBossEvent(
|
||||
title.convert(),
|
||||
BossBarColor.valueOf(color.name()),
|
||||
BossBarOverlay.valueOf(style.name())
|
||||
);
|
||||
bar.setProgress(progress);
|
||||
return bar;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void create(@NotNull BossBarInfo bar) {
|
||||
bar.getBossBar().addPlayer(player.getPlayer());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateTitle(@NotNull BossBarInfo bar) {
|
||||
bar.getBossBar().setName(bar.getTitle().convert());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateProgress(@NotNull BossBarInfo bar) {
|
||||
bar.getBossBar().setProgress(bar.getProgress());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateStyle(@NotNull BossBarInfo bar) {
|
||||
bar.getBossBar().setOverlay(BossBarOverlay.valueOf(bar.getStyle().name()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateColor(@NotNull BossBarInfo bar) {
|
||||
bar.getBossBar().setColor(BossBarColor.valueOf(bar.getColor().name()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(@NotNull BossBarInfo bar) {
|
||||
bar.getBossBar().removePlayer(player.getPlayer());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package me.neznamy.tab.platforms.neoforge;
|
||||
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.platform.EventListener;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.neoforged.bus.api.IEventBus;
|
||||
import net.neoforged.neoforge.common.NeoForge;
|
||||
import net.neoforged.neoforge.event.entity.player.PlayerEvent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Event listener for NeoForge.
|
||||
*/
|
||||
public class NeoForgeEventListener implements EventListener<ServerPlayer> {
|
||||
|
||||
/**
|
||||
* Registers all event listeners.
|
||||
*/
|
||||
public void register() {
|
||||
IEventBus eventBus = NeoForge.EVENT_BUS;
|
||||
eventBus.addListener((PlayerEvent.PlayerLoggedInEvent event) -> join((ServerPlayer) event.getEntity()));
|
||||
eventBus.addListener((PlayerEvent.PlayerLoggedOutEvent event) -> quit(event.getEntity().getUUID()));
|
||||
eventBus.addListener((PlayerEvent.PlayerRespawnEvent event) -> {
|
||||
ServerPlayer player = (ServerPlayer) event.getEntity();
|
||||
replacePlayer(player.getUUID(), player);
|
||||
worldChange(player.getUUID(), NeoForgeTAB.getLevelName(player.level()));
|
||||
});
|
||||
eventBus.addListener((PlayerEvent.PlayerChangedDimensionEvent event) -> worldChange(event.getEntity().getUUID(), NeoForgeTAB.getLevelName(event.getEntity().level())));
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public TabPlayer createPlayer(@NotNull ServerPlayer player) {
|
||||
return new NeoForgeTabPlayer((NeoForgePlatform) TAB.getInstance().getPlatform(), player);
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package me.neznamy.tab.platforms.neoforge;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import me.neznamy.tab.shared.features.injection.NettyPipelineInjector;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Pipeline injector for NeoForge.
|
||||
*/
|
||||
public class NeoForgePipelineInjector extends NettyPipelineInjector {
|
||||
|
||||
/**
|
||||
* Constructs new instance.
|
||||
*/
|
||||
public NeoForgePipelineInjector() {
|
||||
super("packet_handler");
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
protected Channel getChannel(@NotNull TabPlayer player) {
|
||||
return ((NeoForgeTabPlayer)player).getPlayer().connection.getConnection().channel();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
package me.neznamy.tab.platforms.neoforge;
|
||||
|
||||
import com.mojang.logging.LogUtils;
|
||||
import me.neznamy.chat.component.KeybindComponent;
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import me.neznamy.chat.component.TextComponent;
|
||||
import me.neznamy.chat.component.TranslatableComponent;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.tab.shared.backend.BackendPlatform;
|
||||
import me.neznamy.tab.shared.features.PerWorldPlayerListConfiguration;
|
||||
import me.neznamy.tab.shared.features.injection.PipelineInjector;
|
||||
import me.neznamy.tab.shared.features.types.TabFeature;
|
||||
import me.neznamy.tab.shared.platform.BossBar;
|
||||
import me.neznamy.tab.shared.platform.Scoreboard;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import net.minecraft.SharedConstants;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.network.chat.MutableComponent;
|
||||
import net.minecraft.network.chat.Style;
|
||||
import net.minecraft.network.chat.TextColor;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.neoforged.fml.loading.FMLPaths;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* Platform implementation for NeoForge
|
||||
*
|
||||
* @param server Minecraft server reference
|
||||
*/
|
||||
public record NeoForgePlatform(MinecraftServer server) implements BackendPlatform {
|
||||
|
||||
@Override
|
||||
public void registerUnknownPlaceholder(@NotNull String identifier) {
|
||||
registerDummyPlaceholder(identifier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadPlayers() {
|
||||
for (ServerPlayer player : getOnlinePlayers()) {
|
||||
TAB.getInstance().addPlayer(new NeoForgeTabPlayer(this, player));
|
||||
}
|
||||
}
|
||||
|
||||
private Collection<ServerPlayer> getOnlinePlayers() {
|
||||
// It's nullable on startup
|
||||
return server.getPlayerList() == null ? Collections.emptyList() : server.getPlayerList().getPlayers();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public PipelineInjector createPipelineInjector() {
|
||||
return new NeoForgePipelineInjector();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public TabFeature getPerWorldPlayerList(@NotNull PerWorldPlayerListConfiguration configuration) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logInfo(@NotNull TabComponent message) {
|
||||
LogUtils.getLogger().info("[TAB] {}", message.toRawText());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logWarn(@NotNull TabComponent message) {
|
||||
LogUtils.getLogger().warn("[TAB] {}", message.toRawText());
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String getServerVersionInfo() {
|
||||
return "[NeoForge] " + SharedConstants.getCurrentVersion().getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerListener() {
|
||||
new NeoForgeEventListener().register();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerCommand() {
|
||||
// Event listener must be registered in main class
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startMetrics() {
|
||||
// Not available
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public File getDataFolder() {
|
||||
return FMLPaths.CONFIGDIR.get().resolve(TabConstants.PLUGIN_ID).toFile();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Component convertComponent(@NotNull TabComponent component) {
|
||||
// Component type
|
||||
MutableComponent nmsComponent;
|
||||
if (component instanceof TextComponent) {
|
||||
nmsComponent = Component.literal(((TextComponent) component).getText());
|
||||
} else if (component instanceof TranslatableComponent) {
|
||||
nmsComponent = Component.translatable(((TranslatableComponent) component).getKey());
|
||||
} else if (component instanceof KeybindComponent) {
|
||||
nmsComponent = Component.keybind(((KeybindComponent) component).getKeybind());
|
||||
} else {
|
||||
throw new IllegalStateException("Unexpected component type: " + component.getClass().getName());
|
||||
}
|
||||
|
||||
// Component style
|
||||
nmsComponent.setStyle(new Style(
|
||||
component.getModifier().getColor() == null ? null : TextColor.fromRgb(component.getModifier().getColor().getRgb()),
|
||||
component.getModifier().getShadowColor(),
|
||||
component.getModifier().getBold(),
|
||||
component.getModifier().getItalic(),
|
||||
component.getModifier().getUnderlined(),
|
||||
component.getModifier().getStrikethrough(),
|
||||
component.getModifier().getObfuscated(),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
component.getModifier().getFont() == null ? null : ResourceLocation.tryParse(component.getModifier().getFont())
|
||||
));
|
||||
|
||||
// Extra
|
||||
for (TabComponent extra : component.getExtra()) {
|
||||
nmsComponent.getSiblings().add(convertComponent(extra));
|
||||
}
|
||||
|
||||
return nmsComponent;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Scoreboard createScoreboard(@NotNull TabPlayer player) {
|
||||
return new NeoForgeScoreboard((NeoForgeTabPlayer) player);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public BossBar createBossBar(@NotNull TabPlayer player) {
|
||||
return new NeoForgeBossBar((NeoForgeTabPlayer) player);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public TabList createTabList(@NotNull TabPlayer player) {
|
||||
return new NeoForgeTabList((NeoForgeTabPlayer) player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsScoreboards() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getTPS() {
|
||||
double mspt = getMSPT();
|
||||
if (mspt < 50) return 20;
|
||||
return Math.round(1000 / mspt);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getMSPT() {
|
||||
return (float) server.getAverageTickTimeNanos() / 1000000;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
package me.neznamy.tab.platforms.neoforge;
|
||||
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.platform.decorators.SafeScoreboard;
|
||||
import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.network.chat.numbers.FixedFormat;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.network.protocol.game.*;
|
||||
import net.minecraft.world.scores.PlayerTeam;
|
||||
import net.minecraft.world.scores.Scoreboard;
|
||||
import net.minecraft.world.scores.criteria.ObjectiveCriteria;
|
||||
import net.minecraft.world.scores.criteria.ObjectiveCriteria.RenderType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Scoreboard implementation for NeoForge using packets.
|
||||
*/
|
||||
public class NeoForgeScoreboard extends SafeScoreboard<NeoForgeTabPlayer> {
|
||||
|
||||
private static final ChatFormatting[] formats = ChatFormatting.values();
|
||||
private static final net.minecraft.world.scores.Team.CollisionRule[] collisions = net.minecraft.world.scores.Team.CollisionRule.values();
|
||||
private static final net.minecraft.world.scores.Team.Visibility[] visibilities = net.minecraft.world.scores.Team.Visibility.values();
|
||||
private static final Scoreboard dummyScoreboard = new Scoreboard();
|
||||
|
||||
/**
|
||||
* Constructs new instance with given player.
|
||||
*
|
||||
* @param player
|
||||
* Player this scoreboard will belong to
|
||||
*/
|
||||
public NeoForgeScoreboard(NeoForgeTabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerObjective(@NonNull Objective objective) {
|
||||
net.minecraft.world.scores.Objective obj = new net.minecraft.world.scores.Objective(
|
||||
dummyScoreboard,
|
||||
objective.getName(),
|
||||
ObjectiveCriteria.DUMMY,
|
||||
objective.getTitle().convert(),
|
||||
RenderType.values()[objective.getHealthDisplay().ordinal()],
|
||||
false,
|
||||
objective.getNumberFormat() == null ? null : objective.getNumberFormat().toFixedFormat(FixedFormat::new)
|
||||
);
|
||||
objective.setPlatformObjective(obj);
|
||||
sendPacket(new ClientboundSetObjectivePacket(obj, ObjectiveAction.REGISTER));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDisplaySlot(@NonNull Objective objective) {
|
||||
sendPacket(new ClientboundSetDisplayObjectivePacket(net.minecraft.world.scores.DisplaySlot.values()[objective.getDisplaySlot().ordinal()], (net.minecraft.world.scores.Objective) objective.getPlatformObjective()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterObjective(@NonNull Objective objective) {
|
||||
sendPacket(new ClientboundSetObjectivePacket((net.minecraft.world.scores.Objective) objective.getPlatformObjective(), ObjectiveAction.UNREGISTER));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateObjective(@NonNull Objective objective) {
|
||||
net.minecraft.world.scores.Objective obj = (net.minecraft.world.scores.Objective) objective.getPlatformObjective();
|
||||
obj.setDisplayName(objective.getTitle().convert());
|
||||
obj.setRenderType(RenderType.values()[objective.getHealthDisplay().ordinal()]);
|
||||
sendPacket(new ClientboundSetObjectivePacket(obj, ObjectiveAction.UPDATE));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setScore(@NonNull Score score) {
|
||||
sendPacket(new ClientboundSetScorePacket(
|
||||
score.getHolder(),
|
||||
score.getObjective().getName(),
|
||||
score.getValue(),
|
||||
Optional.ofNullable(score.getDisplayName() == null ? null : score.getDisplayName().convert()),
|
||||
Optional.ofNullable(score.getNumberFormat() == null ? null : score.getNumberFormat().toFixedFormat(FixedFormat::new))
|
||||
));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeScore(@NonNull Score score) {
|
||||
sendPacket(new ClientboundResetScorePacket(score.getHolder(), score.getObjective().getName()));
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Object createTeam(@NonNull String name) {
|
||||
return new PlayerTeam(dummyScoreboard, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerTeam(@NonNull Team team) {
|
||||
updateTeamProperties(team);
|
||||
PlayerTeam t = (PlayerTeam) team.getPlatformTeam();
|
||||
t.getPlayers().addAll(team.getPlayers());
|
||||
sendPacket(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket(t, true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregisterTeam(@NonNull Team team) {
|
||||
sendPacket(ClientboundSetPlayerTeamPacket.createRemovePacket((PlayerTeam) team.getPlatformTeam()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateTeam(@NonNull Team team) {
|
||||
updateTeamProperties(team);
|
||||
sendPacket(ClientboundSetPlayerTeamPacket.createAddOrModifyPacket((PlayerTeam) team.getPlatformTeam(), false));
|
||||
}
|
||||
|
||||
private void updateTeamProperties(@NonNull Team team) {
|
||||
PlayerTeam t = (PlayerTeam) team.getPlatformTeam();
|
||||
t.setAllowFriendlyFire((team.getOptions() & 0x01) != 0);
|
||||
t.setSeeFriendlyInvisibles((team.getOptions() & 0x02) != 0);
|
||||
t.setColor(formats[team.getColor().getLegacyColor().ordinal()]);
|
||||
t.setCollisionRule(collisions[team.getCollision().ordinal()]);
|
||||
t.setNameTagVisibility(visibilities[team.getVisibility().ordinal()]);
|
||||
t.setPlayerPrefix(team.getPrefix().convert());
|
||||
t.setPlayerSuffix(team.getSuffix().convert());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketSend(@NonNull Object packet) {
|
||||
if (isAntiOverrideScoreboard()) {
|
||||
if (packet instanceof ClientboundSetDisplayObjectivePacket display) {
|
||||
TAB.getInstance().getFeatureManager().onDisplayObjective(player, display.getSlot().ordinal(), display.getObjectiveName());
|
||||
}
|
||||
if (packet instanceof ClientboundSetObjectivePacket objective) {
|
||||
TAB.getInstance().getFeatureManager().onObjective(player, objective.getMethod(), objective.getObjectiveName());
|
||||
}
|
||||
}
|
||||
if (isAntiOverrideTeams() && packet instanceof ClientboundSetPlayerTeamPacket team) {
|
||||
if (team.method == TeamAction.UPDATE) return;
|
||||
team.players = onTeamPacket(team.method, team.getName(), team.getPlayers());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the packet to the player.
|
||||
*
|
||||
* @param packet
|
||||
* Packet to send
|
||||
*/
|
||||
private void sendPacket(@NotNull Packet<?> packet) {
|
||||
player.getPlayer().connection.send(packet);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package me.neznamy.tab.platforms.neoforge;
|
||||
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.storage.ServerLevelData;
|
||||
import net.neoforged.api.distmarker.Dist;
|
||||
import net.neoforged.bus.api.IEventBus;
|
||||
import net.neoforged.fml.common.Mod;
|
||||
import net.neoforged.neoforge.common.NeoForge;
|
||||
import net.neoforged.neoforge.event.RegisterCommandsEvent;
|
||||
import net.neoforged.neoforge.event.server.ServerStartingEvent;
|
||||
import net.neoforged.neoforge.event.server.ServerStoppingEvent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@Mod(value = "tab", dist = Dist.DEDICATED_SERVER)
|
||||
public class NeoForgeTAB {
|
||||
|
||||
public NeoForgeTAB() {
|
||||
IEventBus EVENT_BUS = NeoForge.EVENT_BUS;
|
||||
EVENT_BUS.addListener((RegisterCommandsEvent event) -> new NeoForgeTabCommand().onRegisterCommands(event.getDispatcher()));
|
||||
EVENT_BUS.addListener((ServerStartingEvent event) -> TAB.create(new NeoForgePlatform(event.getServer())));
|
||||
EVENT_BUS.addListener((ServerStoppingEvent event) -> TAB.getInstance().unload());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static String getLevelName(@NotNull Level level) {
|
||||
String path = level.dimension().location().getPath();
|
||||
return ((ServerLevelData)level.getLevelData()).getLevelName() + switch (path) {
|
||||
case "overworld" -> ""; // No suffix for overworld
|
||||
case "the_nether" -> "_nether";
|
||||
default -> "_" + path; // End + default behavior for other dimensions created by mods
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package me.neznamy.tab.platforms.neoforge;
|
||||
|
||||
import com.mojang.brigadier.CommandDispatcher;
|
||||
import com.mojang.brigadier.arguments.StringArgumentType;
|
||||
import com.mojang.brigadier.context.CommandContext;
|
||||
import com.mojang.brigadier.suggestion.Suggestions;
|
||||
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
|
||||
import com.mojang.brigadier.tree.ArgumentCommandNode;
|
||||
import com.mojang.brigadier.tree.LiteralCommandNode;
|
||||
import me.neznamy.tab.platforms.neoforge.hook.LuckPermsAPIHook;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.TabConstants;
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import me.neznamy.tab.shared.platform.TabPlayer;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
import net.minecraft.commands.Commands;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
/**
|
||||
* Command handler for plugin's command for NeoForge.
|
||||
*/
|
||||
public class NeoForgeTabCommand {
|
||||
|
||||
/**
|
||||
* Handles command register request from server and registers own command.
|
||||
*
|
||||
* @param dispatcher
|
||||
* Dispatcher to register command to
|
||||
*/
|
||||
public void onRegisterCommands(@NotNull CommandDispatcher<CommandSourceStack> dispatcher) {
|
||||
LiteralCommandNode<CommandSourceStack> command = Commands.literal("tab") // TODO extract it from Platform somehow
|
||||
.executes(context -> executeCommand(context.getSource(), new String[0]))
|
||||
.build();
|
||||
ArgumentCommandNode<CommandSourceStack, String> args = Commands.argument("args", StringArgumentType.greedyString())
|
||||
.suggests((context, builder) -> getSuggestions(context.getSource(), getArguments(context), builder))
|
||||
.executes(context -> executeCommand(context.getSource(), getArguments(context)))
|
||||
.build();
|
||||
command.addChild(args);
|
||||
dispatcher.getRoot().addChild(command);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private String[] getArguments(@NotNull CommandContext<CommandSourceStack> context) {
|
||||
String input = context.getInput();
|
||||
int firstSpace = input.indexOf(' ');
|
||||
if (firstSpace == -1) return new String[0];
|
||||
String rawArgs = input.substring(firstSpace + 1);
|
||||
String[] args = rawArgs.split(" ");
|
||||
if (rawArgs.endsWith(" ")) {
|
||||
args = Arrays.copyOf(args, args.length+1);
|
||||
args[args.length-1] = "";
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
@SuppressWarnings("SameReturnValue") // Unused by plugin
|
||||
private int executeCommand(@NotNull CommandSourceStack source, @NotNull String[] args) {
|
||||
if (TAB.getInstance().isPluginDisabled()) {
|
||||
boolean hasReloadPermission = LuckPermsAPIHook.hasPermission(source, TabConstants.Permission.COMMAND_RELOAD);
|
||||
boolean hasAdminPermission = LuckPermsAPIHook.hasPermission(source, TabConstants.Permission.COMMAND_ALL);
|
||||
for (String message : TAB.getInstance().getDisabledCommand().execute(args, hasReloadPermission, hasAdminPermission)) {
|
||||
source.sendSystemMessage(TabComponent.fromColoredText(message).convert());
|
||||
}
|
||||
} else {
|
||||
if (source.getEntity() == null) {
|
||||
TAB.getInstance().getCommand().execute(null, args);
|
||||
} else {
|
||||
TabPlayer player = TAB.getInstance().getPlayer(source.getEntity().getUUID());
|
||||
if (player != null) TAB.getInstance().getCommand().execute(player, args);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private CompletableFuture<Suggestions> getSuggestions(@NotNull CommandSourceStack source, @NotNull String[] args,
|
||||
@NotNull SuggestionsBuilder builder) {
|
||||
TabPlayer player = null;
|
||||
if (source.getEntity() != null) {
|
||||
player = TAB.getInstance().getPlayer(source.getEntity().getUUID());
|
||||
if (player == null) return Suggestions.empty();
|
||||
}
|
||||
|
||||
SuggestionsBuilder newBuilder = builder;
|
||||
int lastSpace = newBuilder.getRemaining().lastIndexOf(' ');
|
||||
if (lastSpace != -1) {
|
||||
newBuilder = newBuilder.createOffset(lastSpace + 1 + newBuilder.getStart());
|
||||
}
|
||||
for (String suggestion : TAB.getInstance().getCommand().complete(player, args)) {
|
||||
newBuilder.suggest(suggestion);
|
||||
}
|
||||
return newBuilder.buildFuture();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
package me.neznamy.tab.platforms.neoforge;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import com.mojang.authlib.properties.Property;
|
||||
import lombok.NonNull;
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import me.neznamy.tab.shared.TAB;
|
||||
import me.neznamy.tab.shared.platform.TabList;
|
||||
import me.neznamy.tab.shared.platform.decorators.TrackedTabList;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.network.protocol.game.ClientboundPlayerInfoRemovePacket;
|
||||
import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket;
|
||||
import net.minecraft.network.protocol.game.ClientboundTabListPacket;
|
||||
import net.minecraft.world.level.GameType;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* TabList implementation for NeoForge using packets.
|
||||
*/
|
||||
public class NeoForgeTabList extends TrackedTabList<NeoForgeTabPlayer> {
|
||||
|
||||
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> addPlayer = EnumSet.allOf(ClientboundPlayerInfoUpdatePacket.Action.class);
|
||||
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateDisplayName = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME);
|
||||
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateLatency = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY);
|
||||
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateGameMode = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE);
|
||||
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateListed = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LISTED);
|
||||
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateListOrder = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LIST_ORDER);
|
||||
private static final EnumSet<ClientboundPlayerInfoUpdatePacket.Action> updateHat = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_HAT);
|
||||
|
||||
/**
|
||||
* Constructs new instance.
|
||||
*
|
||||
* @param player
|
||||
* Player this tablist will belong to
|
||||
*/
|
||||
public NeoForgeTabList(@NotNull NeoForgeTabPlayer player) {
|
||||
super(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeEntry(@NonNull UUID entry) {
|
||||
sendPacket(new ClientboundPlayerInfoRemovePacket(Collections.singletonList(entry)));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDisplayName0(@NonNull UUID entry, @Nullable TabComponent displayName) {
|
||||
sendPacket(updateDisplayName, entry, "", null, false, 0, 0, displayName, 0, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateLatency(@NonNull UUID entry, int latency) {
|
||||
sendPacket(updateLatency, entry, "", null, false, latency, 0, null, 0, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateGameMode(@NonNull UUID entry, int gameMode) {
|
||||
sendPacket(updateGameMode, entry, "", null, false, 0, gameMode, null, 0, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateListed(@NonNull UUID entry, boolean listed) {
|
||||
sendPacket(updateListed, entry, "", null, listed, 0, 0, null, 0, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateListOrder(@NonNull UUID entry, int listOrder) {
|
||||
sendPacket(updateListOrder, entry, "", null, false, 0, 0, null, listOrder, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateHat(@NonNull UUID entry, boolean showHat) {
|
||||
sendPacket(updateHat, entry, "", null, false, 0, 0, null, 0, showHat);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addEntry0(@NonNull Entry entry) {
|
||||
sendPacket(addPlayer, entry.getUniqueId(), entry.getName(), entry.getSkin(), entry.isListed(), entry.getLatency(),
|
||||
entry.getGameMode(), entry.getDisplayName(), entry.getListOrder(), entry.isShowHat());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setPlayerListHeaderFooter(@NonNull TabComponent header, @NonNull TabComponent footer) {
|
||||
sendPacket(new ClientboundTabListPacket(header.convert(), footer.convert()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsEntry(@NonNull UUID entry) {
|
||||
return true; // TODO?
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Skin getSkin() {
|
||||
Collection<Property> properties = player.getPlayer().getGameProfile().getProperties().get(TEXTURES_PROPERTY);
|
||||
if (properties.isEmpty()) return null; // Offline mode
|
||||
Property property = properties.iterator().next();
|
||||
return new Skin(property.value(), property.signature());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPacketSend(@NonNull Object packet) {
|
||||
if (packet instanceof ClientboundPlayerInfoUpdatePacket info) {
|
||||
EnumSet<ClientboundPlayerInfoUpdatePacket.Action> actions = info.actions();
|
||||
List<ClientboundPlayerInfoUpdatePacket.Entry> updatedList = new ArrayList<>();
|
||||
boolean rewritePacket = false;
|
||||
for (ClientboundPlayerInfoUpdatePacket.Entry nmsData : info.entries()) {
|
||||
boolean rewriteEntry = false;
|
||||
Component displayName = nmsData.displayName();
|
||||
int latency = nmsData.latency();
|
||||
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME)) {
|
||||
TabComponent expectedDisplayName = getExpectedDisplayNames().get(nmsData.profileId());
|
||||
if (expectedDisplayName != null && expectedDisplayName.convert() != displayName) {
|
||||
displayName = expectedDisplayName.convert();
|
||||
rewriteEntry = rewritePacket = true;
|
||||
}
|
||||
}
|
||||
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_LATENCY)) {
|
||||
int newLatency = TAB.getInstance().getFeatureManager().onLatencyChange(player, nmsData.profileId(), latency);
|
||||
if (newLatency != latency) {
|
||||
latency = newLatency;
|
||||
rewriteEntry = rewritePacket = true;
|
||||
}
|
||||
}
|
||||
if (actions.contains(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER)) {
|
||||
TAB.getInstance().getFeatureManager().onEntryAdd(player, nmsData.profileId(), nmsData.profile().getName());
|
||||
}
|
||||
updatedList.add(rewriteEntry ? new ClientboundPlayerInfoUpdatePacket.Entry(
|
||||
nmsData.profileId(), nmsData.profile(), nmsData.listed(), latency, nmsData.gameMode(), displayName,
|
||||
nmsData.showHat(), nmsData.listOrder(), nmsData.chatSession()
|
||||
) : nmsData);
|
||||
}
|
||||
if (rewritePacket) info.entries = updatedList;
|
||||
}
|
||||
}
|
||||
|
||||
private void sendPacket(@NonNull EnumSet<ClientboundPlayerInfoUpdatePacket.Action> action, @NonNull UUID id, @NonNull String name, @Nullable Skin skin,
|
||||
boolean listed, int latency, int gameMode, @Nullable TabComponent displayName, int listOrder, boolean showHat) {
|
||||
ClientboundPlayerInfoUpdatePacket packet = new ClientboundPlayerInfoUpdatePacket(action, Collections.emptyList());
|
||||
packet.entries = Collections.singletonList(new ClientboundPlayerInfoUpdatePacket.Entry(
|
||||
id,
|
||||
action.contains(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER) ? createProfile(id, name, skin) : null,
|
||||
listed,
|
||||
latency,
|
||||
GameType.byId(gameMode),
|
||||
displayName == null ? null : displayName.convert(),
|
||||
showHat,
|
||||
listOrder,
|
||||
null
|
||||
));
|
||||
sendPacket(packet);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates GameProfile from given parameters.
|
||||
*
|
||||
* @param id
|
||||
* Profile ID
|
||||
* @param name
|
||||
* Profile name
|
||||
* @param skin
|
||||
* Player skin
|
||||
* @return GameProfile from given parameters
|
||||
*/
|
||||
@NotNull
|
||||
private GameProfile createProfile(@NonNull UUID id, @NonNull String name, @Nullable Skin skin) {
|
||||
GameProfile profile = new GameProfile(id, name);
|
||||
if (skin != null) {
|
||||
profile.getProperties().put(TabList.TEXTURES_PROPERTY,
|
||||
new Property(TabList.TEXTURES_PROPERTY, skin.getValue(), skin.getSignature()));
|
||||
}
|
||||
return profile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the packet to the player.
|
||||
*
|
||||
* @param packet
|
||||
* Packet to send
|
||||
*/
|
||||
private void sendPacket(@NotNull Packet<?> packet) {
|
||||
player.getPlayer().connection.send(packet);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package me.neznamy.tab.platforms.neoforge;
|
||||
|
||||
import me.neznamy.chat.component.TabComponent;
|
||||
import me.neznamy.tab.platforms.neoforge.hook.LuckPermsAPIHook;
|
||||
import me.neznamy.tab.shared.backend.BackendTabPlayer;
|
||||
import net.minecraft.SharedConstants;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* TabPlayer implementation for NeoForge.
|
||||
*/
|
||||
public class NeoForgeTabPlayer extends BackendTabPlayer {
|
||||
|
||||
/**
|
||||
* Constructs new instance with given parameters.
|
||||
*
|
||||
* @param platform
|
||||
* Server platform
|
||||
* @param player
|
||||
* Platform's player object
|
||||
*/
|
||||
public NeoForgeTabPlayer(@NotNull NeoForgePlatform platform, @NotNull ServerPlayer player) {
|
||||
super(platform, player, player.getUUID(), player.getGameProfile().getName(),
|
||||
NeoForgeTAB.getLevelName(player.level()), SharedConstants.getProtocolVersion());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPermission(@NotNull String permission) {
|
||||
return LuckPermsAPIHook.hasPermission(getPlayer().createCommandSourceStack(), permission);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPing() {
|
||||
return getPlayer().connection.latency();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(@NotNull TabComponent message) {
|
||||
getPlayer().sendSystemMessage(message.convert());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasInvisibilityPotion() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDisguised() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public ServerPlayer getPlayer() {
|
||||
return (ServerPlayer) player;
|
||||
}
|
||||
|
||||
@Override
|
||||
public NeoForgePlatform getPlatform() {
|
||||
return (NeoForgePlatform) platform;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVanished0() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getGamemode() {
|
||||
return getPlayer().gameMode.getGameModeForPlayer().getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getHealth() {
|
||||
return getPlayer().getHealth();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public String getDisplayName() {
|
||||
return getPlayer().getGameProfile().getName(); // Will make it work properly if someone asks
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package me.neznamy.tab.platforms.neoforge.hook;
|
||||
|
||||
import net.luckperms.api.LuckPermsProvider;
|
||||
import net.minecraft.commands.CommandSourceStack;
|
||||
import net.neoforged.fml.ModList;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Class hooking into LuckPerms mod for permission nodes instead
|
||||
* of only using the integrated OP level.
|
||||
*/
|
||||
public class LuckPermsAPIHook {
|
||||
|
||||
/** Flag tracking presence of LuckPerms API */
|
||||
private static final boolean luckPerms = ModList.get().isLoaded("luckperms");
|
||||
|
||||
/**
|
||||
* Checks for permission and returns the result.
|
||||
*
|
||||
* @param source
|
||||
* Source to check permission of
|
||||
* @param permission
|
||||
* Permission node to check
|
||||
* @return {@code true} if has permission, {@code false} if not
|
||||
*/
|
||||
public static boolean hasPermission(@NotNull CommandSourceStack source, @NotNull String permission) {
|
||||
if (source.hasPermission(4)) return true;
|
||||
return luckPerms && LuckPermsProvider.get().getUserManager().getUser(source.getPlayer().getUUID()).getCachedData().getPermissionData().checkPermission(permission).asBoolean();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
public net.minecraft.network.chat.Style <init>(Lnet/minecraft/network/chat/TextColor;Ljava/lang/Integer;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Lnet/minecraft/network/chat/ClickEvent;Lnet/minecraft/network/chat/HoverEvent;Ljava/lang/String;Lnet/minecraft/resources/ResourceLocation;)V # constructor
|
||||
public-f net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket entries # entries
|
||||
public-f net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket method # method
|
||||
public-f net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket players # players
|
||||
@@ -0,0 +1,26 @@
|
||||
modLoader = "javafml"
|
||||
loaderVersion = "[4,)"
|
||||
issueTrackerURL = "https://github.com/NEZNAMY/TAB/issues"
|
||||
license = "Apache License Version 2.0"
|
||||
displayURL = "@website@"
|
||||
|
||||
[[mods]]
|
||||
modId = "tab"
|
||||
version = "@version@"
|
||||
displayName = "@name@"
|
||||
authors = "@author@"
|
||||
credits = "@credits@"
|
||||
description = '''@description@'''
|
||||
|
||||
[[dependencies.tab]]
|
||||
modId = "neoforge"
|
||||
type = "required"
|
||||
ordering = "NONE"
|
||||
side = "SERVER"
|
||||
|
||||
[[dependencies.tab]]
|
||||
modId = "minecraft"
|
||||
type = "required"
|
||||
versionRange = "[1.21.4,1.21.5]"
|
||||
ordering = "NONE"
|
||||
side = "SERVER"
|
||||
@@ -20,6 +20,7 @@ pluginManagement {
|
||||
includeBuild("build-logic")
|
||||
repositories {
|
||||
maven("https://repo.spongepowered.org/repository/maven-public/")
|
||||
maven("https://maven.architectury.dev/")
|
||||
gradlePluginPortal()
|
||||
mavenCentral()
|
||||
}
|
||||
@@ -38,4 +39,6 @@ include(":bungeecord")
|
||||
include(":component")
|
||||
include(":sponge")
|
||||
include(":fabric")
|
||||
include(":neoforge")
|
||||
include(":forge")
|
||||
include(":jar")
|
||||
@@ -39,5 +39,6 @@ blossom {
|
||||
replaceToken("@description@", project.description)
|
||||
replaceToken("@website@", rootProject.ext.get("website")!!.toString())
|
||||
replaceToken("@author@", rootProject.ext.get("author")!!.toString())
|
||||
replaceToken("@credits@", rootProject.ext.get("credits")!!.toString())
|
||||
replaceTokenIn("src/main/java/me/neznamy/tab/shared/TabConstants.java")
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
@@ -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) {
|
||||
|
||||
Reference in new issue
Block a user