diff --git a/src/main/java/cc/reconnected/server/RccServer.java b/src/main/java/cc/reconnected/server/RccServer.java index 29b35de..cb30ff4 100644 --- a/src/main/java/cc/reconnected/server/RccServer.java +++ b/src/main/java/cc/reconnected/server/RccServer.java @@ -7,6 +7,7 @@ import cc.reconnected.server.events.PlayerWelcome; import cc.reconnected.server.events.Ready; import cc.reconnected.server.http.ServiceServer; import cc.reconnected.server.struct.ServerPosition; +import cc.reconnected.server.tablist.TabList; import cc.reconnected.server.trackers.AfkTracker; import net.fabricmc.api.ModInitializer; import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback; @@ -14,6 +15,7 @@ import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents; import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents; +import net.kyori.adventure.platform.fabric.FabricServerAudiences; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.minimessage.MiniMessage; import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; @@ -84,6 +86,16 @@ public class RccServer implements ModInitializer { INSTANCE = this; } + private volatile FabricServerAudiences adventure; + + public FabricServerAudiences adventure() { + FabricServerAudiences ret = this.adventure; + if (ret == null) { + throw new IllegalStateException("Tried to access Adventure without a running server!"); + } + return ret; + } + public static final ConcurrentHashMap> teleportRequests = new ConcurrentHashMap<>(); public static final ConcurrentHashMap lastPlayerPositions = new ConcurrentHashMap<>(); @@ -91,7 +103,11 @@ public class RccServer implements ModInitializer { public void onInitialize() { LOGGER.info("Starting rcc-server"); + ServerLifecycleEvents.SERVER_STARTING.register(server -> this.adventure = FabricServerAudiences.of(server)); + ServerLifecycleEvents.SERVER_STOPPED.register(server -> this.adventure = null); + CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> { + RccCommand.register(dispatcher, registryAccess, environment); AfkCommand.register(dispatcher, registryAccess, environment); TellCommand.register(dispatcher, registryAccess, environment); ReplyCommand.register(dispatcher, registryAccess, environment); @@ -104,6 +120,8 @@ public class RccServer implements ModInitializer { GodCommand.register(dispatcher, registryAccess, environment); }); + TabList.register(); + ServerLifecycleEvents.SERVER_STARTED.register(server -> { luckPerms = LuckPermsProvider.get(); afkTracker = new AfkTracker(); diff --git a/src/main/java/cc/reconnected/server/RccServerConfigModel.java b/src/main/java/cc/reconnected/server/RccServerConfigModel.java index 64ab583..eade7b2 100644 --- a/src/main/java/cc/reconnected/server/RccServerConfigModel.java +++ b/src/main/java/cc/reconnected/server/RccServerConfigModel.java @@ -2,6 +2,9 @@ package cc.reconnected.server; import io.wispforest.owo.config.annotation.Config; +import java.util.ArrayList; +import java.util.List; + @Config(name = "rcc-server-config", wrapperName = "RccServerConfig") public class RccServerConfigModel { public boolean enableHttpApi = true; @@ -16,4 +19,13 @@ public class RccServerConfigModel { public String tellMessageSpy = "\uD83D\uDC41 [] "; public int teleportRequestTimeout = 120; + + public boolean enableTabList = true; + public ArrayList tabHeader = new ArrayList<>(List.of( + " " + )); + + public ArrayList tabFooter = new ArrayList<>(List.of( + " " + )); } diff --git a/src/main/java/cc/reconnected/server/commands/RccCommand.java b/src/main/java/cc/reconnected/server/commands/RccCommand.java new file mode 100644 index 0000000..e9c62ca --- /dev/null +++ b/src/main/java/cc/reconnected/server/commands/RccCommand.java @@ -0,0 +1,36 @@ +package cc.reconnected.server.commands; + +import cc.reconnected.server.RccServer; +import com.mojang.brigadier.CommandDispatcher; +import me.lucko.fabric.api.permissions.v0.Permissions; +import net.minecraft.command.CommandRegistryAccess; +import net.minecraft.server.command.CommandManager; +import net.minecraft.server.command.ServerCommandSource; +import net.minecraft.text.Text; + +import static net.minecraft.server.command.CommandManager.literal; + +public class RccCommand { + public static void register(CommandDispatcher dispatcher, CommandRegistryAccess registryAccess, CommandManager.RegistrationEnvironment environment) { + var rootCommand = literal("rcc") + .requires(Permissions.require("rcc.command.afk", 3)) + .then(literal("reload") + .executes(context -> { + context.getSource().sendFeedback(() -> Text.of("Reloading RCC config..."), true); + + try { + RccServer.CONFIG.load(); + } catch(Exception e) { + RccServer.LOGGER.error("Failed to load RCC config", e); + context.getSource().sendFeedback(() -> Text.of("Failed to load RCC config. Check console for more info."), true); + return 1; + } + + context.getSource().sendFeedback(() -> Text.of("Reloaded RCC config"), true); + + return 1; + })); + + dispatcher.register(rootCommand); + } +} diff --git a/src/main/java/cc/reconnected/server/tablist/TabList.java b/src/main/java/cc/reconnected/server/tablist/TabList.java new file mode 100644 index 0000000..25143d3 --- /dev/null +++ b/src/main/java/cc/reconnected/server/tablist/TabList.java @@ -0,0 +1,59 @@ +package cc.reconnected.server.tablist; + +import cc.reconnected.server.RccServer; +import eu.pb4.placeholders.api.PlaceholderContext; +import eu.pb4.placeholders.api.Placeholders; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.minimessage.MiniMessage; +import net.kyori.adventure.text.serializer.json.JSONComponentSerializer; +import net.minecraft.text.Text; + +public class TabList { + public static void register() { + if (!RccServer.CONFIG.enableTabList()) + return; + + ServerTickEvents.END_SERVER_TICK.register(server -> { + var phase = (Math.sin((server.getTicks() * Math.PI) / 20) + 1) / 2d; + var minimessage = MiniMessage.miniMessage(); + + server.getPlayerManager().getPlayerList().forEach(player -> { + var playerContext = PlaceholderContext.of(player); + Component headerComponent = Component.empty(); + for (int i = 0; i < RccServer.CONFIG.tabHeader().size(); i++) { + var line = RccServer.CONFIG.tabHeader().get(i); + line = line.replace("{phase}", String.valueOf(phase)); + if (i > 0) { + headerComponent = headerComponent.appendNewline(); + } + + headerComponent = headerComponent.append(minimessage.deserialize(line)); + } + + Component footerComponent = Component.empty(); + for (int i = 0; i < RccServer.CONFIG.tabFooter().size(); i++) { + var line = RccServer.CONFIG.tabFooter().get(i); + line = line.replace("{phase}", String.valueOf(phase)); + if (i > 0) { + footerComponent = footerComponent.appendNewline(); + } + + footerComponent = footerComponent.append(minimessage.deserialize(line)); + } + + var parsedHeader = Placeholders.parseText(toText(headerComponent), playerContext); + var parsedFooter = Placeholders.parseText(toText(footerComponent), playerContext); + + var audience = RccServer.getInstance().adventure().player(player.getUuid()); + audience.sendPlayerListHeaderAndFooter(parsedHeader, parsedFooter); + }); + }); + + } + + public static Text toText(Component component) { + var json = JSONComponentSerializer.json().serialize(component); + return Text.Serializer.fromJson(json); + } +}