Compare commits

..
23 Commits
Author SHA1 Message Date
NEZNAMY 71a19c63d8 5.2.0 2025-03-31 17:45:05 +02:00
NEZNAMY 657327e7ee [Bukkit] Move viaversion presence check to fix class loader error when it's not present 2025-03-31 17:44:50 +02:00
NEZNAMY efeea7cf0f Merge pull request #1460 from JT122406/master
Forge and NeoForge Support
2025-03-31 17:20:48 +02:00
Joseph T. McQuigg 7a4637368d Hard Versions 2025-03-31 11:18:36 -04:00
NEZNAMY 2e6ccd078b 5.2.0-SNAPSHOT 2025-03-31 17:09:19 +02:00
Joseph T. McQuigg c8454ec0c1 Add MC versions to mods.toml 2025-03-31 10:52:34 -04:00
Joseph T. McQuigg 7127a77d92 Remove Versions from mods.toml, Fix register 2025-03-31 10:30:31 -04:00
Joseph T. McQuigg b23912b52f Forge/NeoForge Luckperms Hook 2025-03-30 18:57:32 -04:00
Joseph T. McQuigg 4b21148a03 Adjust Scoreboard classes for setDisplaySlot 2025-03-30 15:06:37 -04:00
Joseph T. McQuigg 2d5ef95e54 Update Forge to 1.21.5 2025-03-30 14:52:00 -04:00
Joseph T. McQuigg b5e2d376dd Update NeoForge to 1.21.5 2025-03-30 14:52:00 -04:00
Joseph T. McQuigg c40b337edc Fix Credits 2025-03-30 14:52:00 -04:00
Joseph T. McQuigg f8583642a8 Correct License 2025-03-30 14:52:00 -04:00
Joseph T. McQuigg 707f28df16 Permission check 2025-03-30 14:52:00 -04:00
Joseph T. McQuigg dd78da089d Use Records for platform 2025-03-30 14:51:59 -04:00
Joseph T. McQuigg c4ea65ed70 Forge Port 2025-03-30 14:51:59 -04:00
Joseph T. McQuigg aabba806c4 Drop Parchment 2025-03-30 14:51:59 -04:00
Joseph T. McQuigg 88167fc42d Default to EmptyTabExpansion 2025-03-30 14:51:59 -04:00
Joseph T. McQuigg e4d7005e46 moddedPlatforms List 2025-03-30 14:51:59 -04:00
Joseph T. McQuigg 5ee27f7ed7 Add Credits 2025-03-30 14:51:59 -04:00
Joseph T. McQuigg 9a4492c401 Add Parchment to Fabric, Use LogUtils instead of AW 2025-03-30 14:51:59 -04:00
Joseph T. McQuigg 39a2aee5a0 NeoForge Port 2025-03-30 14:51:58 -04:00
Joseph T. McQuigg 5184ef170f Update to new com.gradleup.shadow plugin
Gradle 8.13, Fabric Loom 1.10
2025-03-30 14:51:56 -04:00
51 changed files with 1963 additions and 64 deletions

No files matched your search

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