diff --git a/.idea/AndroidProjectSystem.xml b/.idea/AndroidProjectSystem.xml new file mode 100644 index 0000000..4a53bee --- /dev/null +++ b/.idea/AndroidProjectSystem.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/claudeCodeTabState.xml b/.idea/claudeCodeTabState.xml new file mode 100644 index 0000000..dc88554 --- /dev/null +++ b/.idea/claudeCodeTabState.xml @@ -0,0 +1,21 @@ + + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml index f24b863..c0ec0d8 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -6,7 +6,6 @@ + diff --git a/.idea/material_theme_project_new.xml b/.idea/material_theme_project_new.xml new file mode 100644 index 0000000..601c128 --- /dev/null +++ b/.idea/material_theme_project_new.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/SY.md b/SY.md new file mode 100644 index 0000000..7f8cd17 --- /dev/null +++ b/SY.md @@ -0,0 +1,6 @@ +[=== LTD Cross Server Menu ==] +/空 +1. [xxx服务器] +/空 +2. [xxx服务器] +... \ No newline at end of file diff --git a/forge-mod/gradle.properties b/forge-mod/gradle.properties index ebbdea3..87d6051 100644 --- a/forge-mod/gradle.properties +++ b/forge-mod/gradle.properties @@ -49,7 +49,7 @@ mod_name=Leisure Time Dock Mod # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=MIT # The mod version. See https://semver.org/ -mod_version=1.0.0 +mod_version=1.1.1 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html diff --git a/forge-mod/src/main/java/com/leisuretimedock/crossmod/CrossTeleportMod.java b/forge-mod/src/main/java/com/leisuretimedock/crossmod/CrossTeleportMod.java index 99a2014..5074f3d 100644 --- a/forge-mod/src/main/java/com/leisuretimedock/crossmod/CrossTeleportMod.java +++ b/forge-mod/src/main/java/com/leisuretimedock/crossmod/CrossTeleportMod.java @@ -1,5 +1,6 @@ package com.leisuretimedock.crossmod; +import com.leisuretimedock.crossmod.command.CrossModDebugCommand; import com.leisuretimedock.crossmod.command.GotoServerCommand; import com.leisuretimedock.crossmod.config.CrossServerConfig; import com.leisuretimedock.crossmod.config.CrossServerConfigManager; @@ -7,6 +8,7 @@ import com.leisuretimedock.crossmod.command.PingCommand; import com.leisuretimedock.crossmod.network.NetworkHandler; import com.leisuretimedock.crossmod.network.PingRequestManager; import com.leisuretimedock.crossmod.reset.ClientResetManager; +import com.leisuretimedock.crossmod.util.ModLogger; import lombok.extern.slf4j.Slf4j; import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerPlayer; @@ -28,6 +30,7 @@ import net.minecraftforge.fml.event.lifecycle.FMLDedicatedServerSetupEvent; import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; import net.minecraftforge.fml.loading.FMLEnvironment; import net.minecraftforge.network.NetworkConstants; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Objects; @@ -45,25 +48,32 @@ public class CrossTeleportMod { ModLoadingContext.get().registerConfig(ModConfig.Type.SERVER, CrossServerConfig.SPEC, "cross-server.toml"); if(!FMLEnvironment.dist.isDedicatedServer()) modEventBus.addListener(ClientResetManager::init); NetworkHandler.register(); + // 初始化文件日志 + ModLogger.initFileLogging(); + ModLogger.info("CrossMod initialized"); + ModLogger.bundle("Hello, world!"); + // 注册关闭钩子 + Runtime.getRuntime().addShutdownHook(new Thread(ModLogger::closeFileLogging)); } @Mod.EventBusSubscriber(modid = MOD_ID, bus = Mod.EventBusSubscriber.Bus.FORGE) public static class CommonEvents { @Nullable public static MinecraftServer server; @SubscribeEvent - public static void onRegisterCommands(RegisterCommandsEvent event) { + public static void onRegisterCommands(@NotNull RegisterCommandsEvent event) { PingCommand.register(event.getDispatcher()); GotoServerCommand.register(event.getDispatcher()); + CrossModDebugCommand.register(event.getDispatcher()); } @SubscribeEvent - public static void onPlayerLoggedIn(PlayerEvent.PlayerLoggedInEvent event) { + public static void onPlayerLoggedIn(PlayerEvent.@NotNull PlayerLoggedInEvent event) { if (event.getEntity() instanceof ServerPlayer player) { PingRequestManager.monitor(player); } } private static int tickCounter = 0; @SubscribeEvent - public static void onServerTick(TickEvent.ServerTickEvent event) { + public static void onServerTick(TickEvent.@NotNull ServerTickEvent event) { if (event.phase == TickEvent.Phase.END) { tickCounter++; if (tickCounter % 10 == 0) { @@ -72,13 +82,13 @@ public class CrossTeleportMod { } } @SubscribeEvent - public static void onPlayerLoggedOut(PlayerEvent.PlayerLoggedOutEvent event) { + public static void onPlayerLoggedOut(PlayerEvent.@NotNull PlayerLoggedOutEvent event) { if (event.getEntity() instanceof ServerPlayer player) { PingRequestManager.unmonitor(player); } } @SubscribeEvent - public static void onServerStart(ServerStartedEvent event) { + public static void onServerStart(@NotNull ServerStartedEvent event) { server = event.getServer(); } @SubscribeEvent @@ -114,7 +124,7 @@ public class CrossTeleportMod { * @param event the event */ @SubscribeEvent - public static void onConfigLoaded(ModConfigEvent.Loading event) { + public static void onConfigLoaded(ModConfigEvent.@NotNull Loading event) { if (event.getConfig().getSpec() == CrossServerConfig.SPEC) { CrossServerConfigManager.loading(CrossServerConfigManager.INSTANCE); } @@ -126,7 +136,7 @@ public class CrossTeleportMod { * @param event the event */ @SubscribeEvent - public static void onConfigReloaded(ModConfigEvent.Reloading event) { + public static void onConfigReloaded(ModConfigEvent.@NotNull Reloading event) { if (event.getConfig().getSpec() == CrossServerConfig.SPEC) { CrossServerConfigManager.reloading(CrossServerConfigManager.INSTANCE); } @@ -138,7 +148,7 @@ public class CrossTeleportMod { * @param event the event */ @SubscribeEvent - public static void onConfigUnloaded(ModConfigEvent.Unloading event) { + public static void onConfigUnloaded(ModConfigEvent.@NotNull Unloading event) { if (event.getConfig().getSpec() == CrossServerConfig.SPEC) { CrossServerConfigManager.unloading(CrossServerConfigManager.INSTANCE); } diff --git a/forge-mod/src/main/java/com/leisuretimedock/crossmod/client/ClientPingHandler.java b/forge-mod/src/main/java/com/leisuretimedock/crossmod/client/ClientPingHandler.java index 5671ac4..982c67b 100644 --- a/forge-mod/src/main/java/com/leisuretimedock/crossmod/client/ClientPingHandler.java +++ b/forge-mod/src/main/java/com/leisuretimedock/crossmod/client/ClientPingHandler.java @@ -14,7 +14,7 @@ public class ClientPingHandler { private static PingRequestManager.PingStats lastStats; private static long lastStatsUpdateTime; - public static void handlePingResults(Map pingResults, Map averages) { + public static void handlePingResults(@NotNull Map pingResults, Map averages) { long now = System.currentTimeMillis(); pingResults.forEach((uuid, ping) -> { @@ -29,7 +29,7 @@ public class ClientPingHandler { ); } - public static String getPingDisplayText() { + public static @NotNull String getPingDisplayText() { Minecraft mc = Minecraft.getInstance(); if (mc.level == null) return ""; @@ -82,7 +82,7 @@ public class ClientPingHandler { } // 获取要显示的统计文本 - public static String getStatsDisplayText() { + public static @NotNull String getStatsDisplayText() { if (lastStats == null || System.currentTimeMillis() - lastStatsUpdateTime > 10000) { return "网络统计: 数据过期"; } diff --git a/forge-mod/src/main/java/com/leisuretimedock/crossmod/client/KeyBindingHandler.java b/forge-mod/src/main/java/com/leisuretimedock/crossmod/client/KeyBindingHandler.java index abbb18d..9c57a22 100644 --- a/forge-mod/src/main/java/com/leisuretimedock/crossmod/client/KeyBindingHandler.java +++ b/forge-mod/src/main/java/com/leisuretimedock/crossmod/client/KeyBindingHandler.java @@ -9,20 +9,21 @@ import net.minecraftforge.client.event.RegisterKeyMappingsEvent; import net.minecraftforge.event.TickEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.common.Mod; +import org.jetbrains.annotations.NotNull; import org.lwjgl.glfw.GLFW; @Mod.EventBusSubscriber(modid = CrossTeleportMod.MOD_ID, bus = Mod.EventBusSubscriber.Bus.MOD, value = Dist.CLIENT) public class KeyBindingHandler { public static final KeyMapping OPEN_GUI_KEY = new KeyMapping("ltd.mod.client.name.trans_server", GLFW.GLFW_KEY_HOME, "ltd.mod.client.key"); @SubscribeEvent - public static void onRegisterKeyMappingsEvent (RegisterKeyMappingsEvent event) { + public static void onRegisterKeyMappingsEvent (@NotNull RegisterKeyMappingsEvent event) { event.register(OPEN_GUI_KEY); } @Mod.EventBusSubscriber(modid = CrossTeleportMod.MOD_ID, value = Dist.CLIENT) public static class KeyHandler { @SubscribeEvent - public static void onClientTick(TickEvent.ClientTickEvent event) { + public static void onClientTick(TickEvent.@NotNull ClientTickEvent event) { if (event.phase == TickEvent.Phase.END && OPEN_GUI_KEY.consumeClick()) { Minecraft.getInstance().setScreen(new CrossServerGui()); } diff --git a/forge-mod/src/main/java/com/leisuretimedock/crossmod/client/PluginChannelClient.java b/forge-mod/src/main/java/com/leisuretimedock/crossmod/client/PluginChannelClient.java index 3d617fd..b4b8a43 100644 --- a/forge-mod/src/main/java/com/leisuretimedock/crossmod/client/PluginChannelClient.java +++ b/forge-mod/src/main/java/com/leisuretimedock/crossmod/client/PluginChannelClient.java @@ -2,6 +2,7 @@ package com.leisuretimedock.crossmod.client; import com.leisuretimedock.crossmod.CrossTeleportMod; import com.leisuretimedock.crossmod.client.command.GotoCommand; +import com.leisuretimedock.crossmod.client.command.ServerMenuCommand; import com.leisuretimedock.crossmod.client.overlay.CrossServerTipOverLay; import com.leisuretimedock.crossmod.client.overlay.PluginCommand; import com.leisuretimedock.crossmod.network.NetworkHandler; @@ -19,6 +20,7 @@ import net.minecraftforge.client.event.ClientPlayerNetworkEvent; import net.minecraftforge.client.event.RegisterClientCommandsEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.common.Mod; +import org.jetbrains.annotations.NotNull; import java.util.Objects; @Slf4j @@ -87,7 +89,7 @@ public class PluginChannelClient { @SubscribeEvent - public static void onLogout(ClientPlayerNetworkEvent.LoggingOut event) { + public static void onLogout(ClientPlayerNetworkEvent.@NotNull LoggingOut event) { log.debug("[CrossTeleportMod] 玩家注销事件触发"); Connection connection = event.getConnection(); @@ -110,7 +112,8 @@ public class PluginChannelClient { } @SubscribeEvent - public static void onRegisterCommand(RegisterClientCommandsEvent event) { + public static void onRegisterCommand(@NotNull RegisterClientCommandsEvent event) { GotoCommand.register(event.getDispatcher()); + ServerMenuCommand.register(event.getDispatcher()); } } diff --git a/forge-mod/src/main/java/com/leisuretimedock/crossmod/client/command/GotoCommand.java b/forge-mod/src/main/java/com/leisuretimedock/crossmod/client/command/GotoCommand.java index 30e1de3..b995ccc 100644 --- a/forge-mod/src/main/java/com/leisuretimedock/crossmod/client/command/GotoCommand.java +++ b/forge-mod/src/main/java/com/leisuretimedock/crossmod/client/command/GotoCommand.java @@ -7,9 +7,10 @@ import com.mojang.brigadier.builder.LiteralArgumentBuilder; import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.Commands; import net.minecraft.network.chat.Component; +import org.jetbrains.annotations.NotNull; public class GotoCommand { - public static void register(CommandDispatcher dispatcher) { + public static void register(@NotNull CommandDispatcher dispatcher) { LiteralArgumentBuilder main = Commands.literal("goto") .then(Commands.argument("server", StringArgumentType.string()) .executes(ctx -> { diff --git a/forge-mod/src/main/java/com/leisuretimedock/crossmod/client/command/ServerMenuCommand.java b/forge-mod/src/main/java/com/leisuretimedock/crossmod/client/command/ServerMenuCommand.java new file mode 100644 index 0000000..8ca9982 --- /dev/null +++ b/forge-mod/src/main/java/com/leisuretimedock/crossmod/client/command/ServerMenuCommand.java @@ -0,0 +1,81 @@ +package com.leisuretimedock.crossmod.client.command; + +import com.leisuretimedock.crossmod.client.gui.CrossServerGui; +import com.leisuretimedock.crossmod.config.CrossServerConfigManager; +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import net.minecraft.ChatFormatting; +import net.minecraft.client.Minecraft; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.network.chat.ClickEvent; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.HoverEvent; +import net.minecraft.network.chat.Style; +import org.jetbrains.annotations.NotNull; + +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +public class ServerMenuCommand { + public static void register(@NotNull CommandDispatcher dispatcher) { + LiteralArgumentBuilder main = Commands.literal("ltdmenu") + .executes(ServerMenuCommand::msgMenu) + .then( + Commands.literal("gui") + .executes(ServerMenuCommand::guiMenu) + ) + .then( + Commands.literal("msg") + .executes(ServerMenuCommand::msgMenu) + ); + dispatcher.register(main); + } + + public static int guiMenu(CommandContext ctx) { + Minecraft instance = Minecraft.getInstance(); + instance.setScreen(new CrossServerGui()); + return 0; + } + + public static int msgMenu(@NotNull CommandContext ctx) { + Map servers = CrossServerConfigManager.INSTANCE.getServers(); + + ctx.getSource().sendSuccess( + () -> Component.translatable("ltd.mod.client.menu.command.header") + .withStyle(ChatFormatting.GOLD, ChatFormatting.BOLD), + false); + + ctx.getSource().sendSuccess(() -> Component.literal(" "), false); + + if (servers.isEmpty()) { + ctx.getSource().sendSuccess( + () -> Component.translatable("ltd.mod.client.menu.button.no_servers") + .withStyle(ChatFormatting.GRAY), + false); + } else { + AtomicInteger index = new AtomicInteger(1); + servers.forEach((serverId, translateKey) -> { + Component entry = Component.literal(index.getAndIncrement() + ". [") + .withStyle(ChatFormatting.WHITE) + .append(Component.translatable(translateKey) + .withStyle(ChatFormatting.GREEN)) + .append(Component.literal("]") + .withStyle(ChatFormatting.WHITE)) + .withStyle(Style.EMPTY + .withClickEvent(new ClickEvent( + ClickEvent.Action.RUN_COMMAND, "/cross goto " + serverId)) + .withHoverEvent(new HoverEvent( + HoverEvent.Action.SHOW_TEXT, + Component.translatable("ltd.mod.client.menu.command.hover", + Component.translatable(translateKey))))); + + ctx.getSource().sendSuccess(() -> entry, false); + ctx.getSource().sendSuccess(() -> Component.literal(" "), false); + }); + } + return 1; + } +} diff --git a/forge-mod/src/main/java/com/leisuretimedock/crossmod/client/gui/CrossServerGui.java b/forge-mod/src/main/java/com/leisuretimedock/crossmod/client/gui/CrossServerGui.java index ae7b201..13fb415 100644 --- a/forge-mod/src/main/java/com/leisuretimedock/crossmod/client/gui/CrossServerGui.java +++ b/forge-mod/src/main/java/com/leisuretimedock/crossmod/client/gui/CrossServerGui.java @@ -163,7 +163,7 @@ public class CrossServerGui extends Screen { } } - private void renderLogo(GuiGraphics guiGraphics) { + private void renderLogo(@NotNull GuiGraphics guiGraphics) { int logoWidth = 64; // 缩小Logo,为列表腾出空间 int logoHeight = 64; diff --git a/forge-mod/src/main/java/com/leisuretimedock/crossmod/client/gui/GenericIceMessageScreen.java b/forge-mod/src/main/java/com/leisuretimedock/crossmod/client/gui/GenericIceMessageScreen.java index fb8f9be..4eaf831 100644 --- a/forge-mod/src/main/java/com/leisuretimedock/crossmod/client/gui/GenericIceMessageScreen.java +++ b/forge-mod/src/main/java/com/leisuretimedock/crossmod/client/gui/GenericIceMessageScreen.java @@ -37,7 +37,7 @@ public class GenericIceMessageScreen extends GenericDirtMessageScreen { } @SuppressWarnings("UnstableApiUsage") - public void renderIceBackground(GuiGraphics guiGraphics, float r, float g, float b, float a) { + public void renderIceBackground(@NotNull GuiGraphics guiGraphics, float r, float g, float b, float a) { guiGraphics.setColor(0.65F, 0.65F, 0.65F, 1.0F); guiGraphics.blit(ICE, 0, 0, 0, 0.0F, 0.0F, this.width, this.height, 32, 32); guiGraphics.setColor(r, g, b, a); diff --git a/forge-mod/src/main/java/com/leisuretimedock/crossmod/client/gui/ServerSelectionList.java b/forge-mod/src/main/java/com/leisuretimedock/crossmod/client/gui/ServerSelectionList.java index cfafb79..f3e87f0 100644 --- a/forge-mod/src/main/java/com/leisuretimedock/crossmod/client/gui/ServerSelectionList.java +++ b/forge-mod/src/main/java/com/leisuretimedock/crossmod/client/gui/ServerSelectionList.java @@ -12,7 +12,7 @@ import java.util.Map; public class ServerSelectionList extends ObjectSelectionList { private final CrossServerGui parentScreen; - public ServerSelectionList(CrossServerGui parent, Minecraft mc, int width, int height, int y0, int y1, int itemHeight, Map servers) { + public ServerSelectionList(CrossServerGui parent, Minecraft mc, int width, int height, int y0, int y1, int itemHeight, @NotNull Map servers) { super(mc, width, height, y0, y1, itemHeight); this.parentScreen = parent; diff --git a/forge-mod/src/main/java/com/leisuretimedock/crossmod/client/overlay/OverlayRenderer.java b/forge-mod/src/main/java/com/leisuretimedock/crossmod/client/overlay/OverlayRenderer.java index a00f3db..c7e61df 100644 --- a/forge-mod/src/main/java/com/leisuretimedock/crossmod/client/overlay/OverlayRenderer.java +++ b/forge-mod/src/main/java/com/leisuretimedock/crossmod/client/overlay/OverlayRenderer.java @@ -5,12 +5,13 @@ import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.client.event.RegisterGuiOverlaysEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.common.Mod; +import org.jetbrains.annotations.NotNull; @Mod.EventBusSubscriber(modid = CrossTeleportMod.MOD_ID, value = Dist.CLIENT, bus = Mod.EventBusSubscriber.Bus.MOD) public class OverlayRenderer { @SubscribeEvent - public static void onRender(RegisterGuiOverlaysEvent event) { + public static void onRender(@NotNull RegisterGuiOverlaysEvent event) { event.registerAboveAll("cross_server_tip", CrossServerTipOverLay.INSTANCE); event.registerAboveAll( "ping_debug", diff --git a/forge-mod/src/main/java/com/leisuretimedock/crossmod/client/overlay/PingOverlayManager.java b/forge-mod/src/main/java/com/leisuretimedock/crossmod/client/overlay/PingOverlayManager.java index 635b1e9..73f0dc0 100644 --- a/forge-mod/src/main/java/com/leisuretimedock/crossmod/client/overlay/PingOverlayManager.java +++ b/forge-mod/src/main/java/com/leisuretimedock/crossmod/client/overlay/PingOverlayManager.java @@ -6,6 +6,7 @@ import net.minecraft.client.gui.Font; import net.minecraft.client.gui.GuiGraphics; import net.minecraftforge.client.gui.overlay.ForgeGui; import net.minecraftforge.client.gui.overlay.IGuiOverlay; +import org.jetbrains.annotations.NotNull; import java.util.ArrayList; import java.util.List; @@ -55,7 +56,7 @@ public class PingOverlayManager implements IGuiOverlay { drawTextLines(guiGraphics, font, allLines, x, y); } - private List getAllDisplayLines() { + private @NotNull List getAllDisplayLines() { List lines = new ArrayList<>(); // 添加Ping信息 @@ -74,7 +75,7 @@ public class PingOverlayManager implements IGuiOverlay { return lines; } - private int getMaxLineWidth(Font font, List lines) { + private int getMaxLineWidth(@NotNull Font font, @NotNull List lines) { return lines.stream() .mapToInt(font::width) .max() @@ -94,14 +95,14 @@ public class PingOverlayManager implements IGuiOverlay { return baseY; } - private void drawBackground(GuiGraphics guiGraphics, int x, int y, int width, int height, Font font) { + private void drawBackground(@NotNull GuiGraphics guiGraphics, int x, int y, int width, int height, Font font) { guiGraphics.fill( x - PADDING, y - PADDING, x + width + PADDING, y + height + PADDING, BACKGROUND_COLOR); } - private void drawTextLines(GuiGraphics guiGraphics, Font font, List lines, int x, int y) { + private void drawTextLines(GuiGraphics guiGraphics, Font font, @NotNull List lines, int x, int y) { for (int i = 0; i < lines.size(); i++) { String line = lines.get(i); if (!line.isEmpty()) { diff --git a/forge-mod/src/main/java/com/leisuretimedock/crossmod/client/overlay/PluginCommand.java b/forge-mod/src/main/java/com/leisuretimedock/crossmod/client/overlay/PluginCommand.java index 7596da7..7e748dc 100644 --- a/forge-mod/src/main/java/com/leisuretimedock/crossmod/client/overlay/PluginCommand.java +++ b/forge-mod/src/main/java/com/leisuretimedock/crossmod/client/overlay/PluginCommand.java @@ -1,5 +1,7 @@ package com.leisuretimedock.crossmod.client.overlay; +import org.jetbrains.annotations.NotNull; + import java.util.Arrays; import java.util.Optional; @@ -11,7 +13,7 @@ public enum PluginCommand { PluginCommand(String id) { this.id = id; } - public static Optional fromId(String id) { + public static @NotNull Optional fromId(String id) { return Arrays.stream(values()).filter(cmd -> cmd.id.equals(id)).findFirst(); } } diff --git a/forge-mod/src/main/java/com/leisuretimedock/crossmod/command/CrossModDebugCommand.java b/forge-mod/src/main/java/com/leisuretimedock/crossmod/command/CrossModDebugCommand.java new file mode 100644 index 0000000..59d8d59 --- /dev/null +++ b/forge-mod/src/main/java/com/leisuretimedock/crossmod/command/CrossModDebugCommand.java @@ -0,0 +1,88 @@ +package com.leisuretimedock.crossmod.command; + +import com.leisuretimedock.crossmod.config.CrossCommonModConfig; +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.arguments.BoolArgumentType; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; +import org.jetbrains.annotations.NotNull; + +public class CrossModDebugCommand { + + public static void register(@NotNull CommandDispatcher dispatcher) { + dispatcher.register(Commands.literal("crossmod") + .then(Commands.literal("debug") + .then(Commands.literal("packets") + .then(Commands.argument("enabled", BoolArgumentType.bool()) + .executes(ctx -> { + boolean enabled = BoolArgumentType.getBool(ctx, "enabled"); + CrossCommonModConfig.getInstance().setDebugPackets(enabled); + + MutableComponent message; + if (enabled) { + message = Component.translatable("crossmod.command.debug.packets.enabled"); + } else { + message = Component.translatable("crossmod.command.debug.packets.disabled"); + } + + ctx.getSource().sendSuccess(() -> message, true); + return 1; + }) + ) + ) + .then(Commands.literal("bundledebug") + .then(Commands.argument("enabled", BoolArgumentType.bool()) + .executes(ctx -> { + boolean enabled = BoolArgumentType.getBool(ctx, "enabled"); + CrossCommonModConfig.getInstance().setDebugBundleDelimiter(enabled); + + MutableComponent message; + if (enabled) { + message = Component.translatable("crossmod.command.debug.bundledebug.enabled"); + } else { + message = Component.translatable("crossmod.command.debug.bundledebug.disabled"); + } + + ctx.getSource().sendSuccess(() -> message, true); + return 1; + }) + ) + ) + .then(Commands.literal("status") + .executes(ctx -> { + CrossCommonModConfig config = CrossCommonModConfig.getInstance(); + + MutableComponent status = Component.literal("") + .append(Component.translatable("crossmod.command.debug.status.title")) + .append("\n §6") + .append(Component.translatable("crossmod.command.debug.status.packet")) + .append(getStatusText(config.isDebugPackets())) + .append("\n §6") + .append(Component.translatable("crossmod.command.debug.status.bundledebug")) + .append(getStatusText(config.isDebugBundleDelimiter())) + .append("\n §6") + .append(Component.translatable("crossmod.command.debug.status.maxbytes")) + .append("§f") + .append(String.valueOf(config.getMaxDebugBytes())) + .append("\n §6") + .append(Component.translatable("crossmod.command.debug.status.logfile")) + .append(getStatusText(config.isLogToFile())); + + ctx.getSource().sendSuccess(() -> status, false); + return 1; + }) + ) + ) + ); + } + + private static @NotNull MutableComponent getStatusText(boolean enabled) { + if (enabled) { + return Component.translatable("crossmod.command.debug.enabled_short"); + } else { + return Component.translatable("crossmod.command.debug.disabled_short"); + } + } +} \ No newline at end of file diff --git a/forge-mod/src/main/java/com/leisuretimedock/crossmod/command/GotoServerCommand.java b/forge-mod/src/main/java/com/leisuretimedock/crossmod/command/GotoServerCommand.java index 6439cbe..171c5df 100644 --- a/forge-mod/src/main/java/com/leisuretimedock/crossmod/command/GotoServerCommand.java +++ b/forge-mod/src/main/java/com/leisuretimedock/crossmod/command/GotoServerCommand.java @@ -10,26 +10,28 @@ import net.minecraft.commands.Commands; import net.minecraft.commands.arguments.EntityArgument; import net.minecraft.network.chat.Component; import net.minecraft.server.level.ServerPlayer; +import org.jetbrains.annotations.NotNull; import java.util.Collection; public class GotoServerCommand { - public static void register(CommandDispatcher dispatcher) { + public static void register(@NotNull CommandDispatcher dispatcher) { LiteralArgumentBuilder main = Commands.literal("cross") - .requires(cs -> cs.hasPermission(2)) - .then(Commands.argument("players", EntityArgument.players()) - .then(Commands.literal("goto") - .then(Commands.argument("server", StringArgumentType.string()) - .executes(ctx -> { - String server = StringArgumentType.getString(ctx, "server"); - Collection players = EntityArgument.getPlayers(ctx, "players"); - players.forEach(p -> NetworkHandler.sendToPlayer(new GotoServerPayload(server), p)); - ctx.getSource().sendSuccess( - () -> Component.translatable("ltd.mod.client.request.goto",server), false); - return 1; - }) + .then( + Commands.argument("players", EntityArgument.players()) + .requires(cs -> cs.hasPermission(2)) + .then(Commands.literal("goto") + .then(Commands.argument("server", StringArgumentType.string()) + .executes(ctx -> { + String server = StringArgumentType.getString(ctx, "server"); + Collection players = EntityArgument.getPlayers(ctx, "players"); + players.forEach(p -> NetworkHandler.sendToPlayer(new GotoServerPayload(server), p)); + ctx.getSource().sendSuccess( + () -> Component.translatable("ltd.mod.client.request.goto",server), false); + return 1; + }) + ) ) - ) ) .then(Commands.literal("goto") .then(Commands.argument("server", StringArgumentType.string()) @@ -41,6 +43,7 @@ public class GotoServerCommand { NetworkHandler.sendToPlayer(new GotoServerPayload(server), player); source.sendSuccess( () -> Component.translatable("ltd.mod.client.request.goto",server), false); + return 0; } source.sendFailure(Component.literal("Request a player")); return 1; diff --git a/forge-mod/src/main/java/com/leisuretimedock/crossmod/command/PingCommand.java b/forge-mod/src/main/java/com/leisuretimedock/crossmod/command/PingCommand.java index 3b03be4..45aab10 100644 --- a/forge-mod/src/main/java/com/leisuretimedock/crossmod/command/PingCommand.java +++ b/forge-mod/src/main/java/com/leisuretimedock/crossmod/command/PingCommand.java @@ -13,6 +13,7 @@ import net.minecraft.commands.Commands; import net.minecraft.commands.arguments.EntityArgument; import net.minecraft.network.chat.Component; import net.minecraft.server.level.ServerPlayer; +import org.jetbrains.annotations.NotNull; import java.util.Collection; import java.util.Collections; @@ -20,7 +21,7 @@ import java.util.Map; import java.util.UUID; public class PingCommand { - public static void register(CommandDispatcher dispatcher) { + public static void register(@NotNull CommandDispatcher dispatcher) { LiteralArgumentBuilder networkping = Commands.literal("netping") .requires(source -> source.hasPermission(2)) @@ -90,7 +91,7 @@ public class PingCommand { dispatcher.register(networkping); } - private static int executePlayerReport(CommandSourceStack source, Collection players) throws CommandSyntaxException { + private static int executePlayerReport(CommandSourceStack source, @NotNull Collection players) throws CommandSyntaxException { if (players.isEmpty()) { source.sendSuccess(() -> Component.translatable("ltd.mod.ping.error.no_players"), false); return 0; @@ -110,7 +111,7 @@ public class PingCommand { return Command.SINGLE_SUCCESS; } - private static int executeFullReport(CommandSourceStack source) throws CommandSyntaxException { + private static int executeFullReport(@NotNull CommandSourceStack source) throws CommandSyntaxException { ServerPlayer player = source.getPlayerOrException(); Map results = PingRequestManager.getAllLatestPings(); @@ -129,7 +130,7 @@ public class PingCommand { return Command.SINGLE_SUCCESS; } - private static int executeStatsReport(CommandSourceStack source) throws CommandSyntaxException { + private static int executeStatsReport(@NotNull CommandSourceStack source) throws CommandSyntaxException { ServerPlayer player = source.getPlayerOrException(); PingRequestManager.PingStats stats = PingRequestManager.getGlobalPingStats(); @@ -144,7 +145,7 @@ public class PingCommand { return Command.SINGLE_SUCCESS; } - private static int executeSinglePing(CommandSourceStack source) throws CommandSyntaxException { + private static int executeSinglePing(@NotNull CommandSourceStack source) throws CommandSyntaxException { ServerPlayer player = source.getPlayerOrException(); if(!PingRequestManager.isMonitored(player.getUUID())) { source.sendFailure(Component.translatable("ltd.mod.ping.error.not_monitored.self")); @@ -155,7 +156,7 @@ public class PingCommand { return Command.SINGLE_SUCCESS; } - private static int executePingPlayers(CommandSourceStack source, Collection players) throws CommandSyntaxException { + private static int executePingPlayers(CommandSourceStack source, @NotNull Collection players) throws CommandSyntaxException { if (players.isEmpty()) { source.sendSuccess(() -> Component.translatable("ltd.mod.ping.error.no_players"), false); return 0; @@ -176,7 +177,7 @@ public class PingCommand { return Command.SINGLE_SUCCESS; } private static int executeMultiPing(CommandSourceStack source, - Collection players, + @NotNull Collection players, int count, int interval) { if (players.isEmpty()) { @@ -205,7 +206,7 @@ public class PingCommand { return Command.SINGLE_SUCCESS; } - private static int executeToggleMonitoring(CommandSourceStack source, boolean monitor) throws CommandSyntaxException { + private static int executeToggleMonitoring(@NotNull CommandSourceStack source, boolean monitor) throws CommandSyntaxException { ServerPlayer player = source.getPlayerOrException(); if (monitor) { PingRequestManager.monitor(player); @@ -217,7 +218,7 @@ public class PingCommand { return Command.SINGLE_SUCCESS; } - private static int executeToggleMonitoring(CommandSourceStack source, Collection players, boolean monitor) throws CommandSyntaxException { + private static int executeToggleMonitoring(CommandSourceStack source, @NotNull Collection players, boolean monitor) throws CommandSyntaxException { if (players.isEmpty()) { source.sendSuccess(() -> Component.translatable("ltd.mod.ping.error.no_players"), false); return 0; @@ -238,7 +239,7 @@ public class PingCommand { return players.size(); } - private static void sendTextReport(ServerPlayer player, Map results) { + private static void sendTextReport(@NotNull ServerPlayer player, @NotNull Map results) { player.displayClientMessage(Component.translatable("ltd.mod.ping.title.report").withStyle(ChatFormatting.GOLD), true); @@ -255,7 +256,7 @@ public class PingCommand { }); } - private static void sendStatsTextReport(ServerPlayer player, PingRequestManager.PingStats stats) { + private static void sendStatsTextReport(@NotNull ServerPlayer player, PingRequestManager.@NotNull PingStats stats) { player.displayClientMessage(Component.translatable("ltd.mod.ping.title.stats").withStyle(ChatFormatting.GOLD), true); player.displayClientMessage(Component.translatable( diff --git a/forge-mod/src/main/java/com/leisuretimedock/crossmod/config/CrossCommonModConfig.java b/forge-mod/src/main/java/com/leisuretimedock/crossmod/config/CrossCommonModConfig.java new file mode 100644 index 0000000..30007fc --- /dev/null +++ b/forge-mod/src/main/java/com/leisuretimedock/crossmod/config/CrossCommonModConfig.java @@ -0,0 +1,69 @@ +package com.leisuretimedock.crossmod.config; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import net.minecraftforge.fml.loading.FMLPaths; + +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; + +public class CrossCommonModConfig { + private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create(); + private static final Path CONFIG_PATH = FMLPaths.CONFIGDIR.get().resolve("crossmod-common.json"); + + private static CrossCommonModConfig INSTANCE; + + // 配置项 + private boolean debugPackets = false; + private boolean debugBundleDelimiter = false; + private int maxDebugBytes = 256; + private boolean logToFile = false; + private String logFilePath = "crossmod-debug.log"; + + // 单例模式 + public static CrossCommonModConfig getInstance() { + if (INSTANCE == null) { + INSTANCE = loadConfig(); + } + return INSTANCE; + } + + private static CrossCommonModConfig loadConfig() { + if (Files.exists(CONFIG_PATH)) { + try (Reader reader = new FileReader(CONFIG_PATH.toFile())) { + return GSON.fromJson(reader, CrossCommonModConfig.class); + } catch (IOException e) { + System.err.println("[CrossMod] Failed to load config, using defaults: " + e.getMessage()); + } + } + CrossCommonModConfig config = new CrossCommonModConfig(); + config.saveConfig(); + return config; + } + + private void saveConfig() { + try (Writer writer = new FileWriter(CONFIG_PATH.toFile())) { + GSON.toJson(this, writer); + } catch (IOException e) { + System.err.println("[CrossMod] Failed to save config: " + e.getMessage()); + } + } + + // Getters + public boolean isDebugPackets() { return debugPackets; } + public boolean isDebugBundleDelimiter() { return debugBundleDelimiter; } + public int getMaxDebugBytes() { return maxDebugBytes; } + public boolean isLogToFile() { return logToFile; } + public String getLogFilePath() { return logFilePath; } + + // Setters(用于热重载) + public void setDebugPackets(boolean debugPackets) { + this.debugPackets = debugPackets; + saveConfig(); + } + public void setDebugBundleDelimiter(boolean debugBundleDelimiter) { + this.debugBundleDelimiter = debugBundleDelimiter; + saveConfig(); + } +} \ No newline at end of file diff --git a/forge-mod/src/main/java/com/leisuretimedock/crossmod/config/CrossServerConfig.java b/forge-mod/src/main/java/com/leisuretimedock/crossmod/config/CrossServerConfig.java index a33941e..a6741a8 100644 --- a/forge-mod/src/main/java/com/leisuretimedock/crossmod/config/CrossServerConfig.java +++ b/forge-mod/src/main/java/com/leisuretimedock/crossmod/config/CrossServerConfig.java @@ -10,19 +10,21 @@ public class CrossServerConfig { private static final ForgeConfigSpec.Builder BUILDER = new ForgeConfigSpec.Builder(); public static ForgeConfigSpec SPEC; public static final ForgeConfigSpec.ConfigValue> SERVER_LIST; + public static final ForgeConfigSpec.BooleanValue DISABLED_JOIN_QUIT_MESSAGE; static { BUILDER.comment("Cross Server Config").push("servers"); SERVER_LIST = BUILDER .comment("Server list in format: : ") .defineList("serverList", Arrays.asList( - "lobby: ltd.mod.client.menu.button.1", - "survival: ltd.mod.client.menu.button.2" + "lobby: ltd.mod.client.menu.button.hub", + "survival: ltd.mod.client.menu.button.survival" ), obj -> obj instanceof String str && checkSyntax(str) ); - BUILDER.pop(); + DISABLED_JOIN_QUIT_MESSAGE = BUILDER.comment("Disable join or quit message") + .define("disabled_join_quit_message", false); SPEC = BUILDER.build(); } public static boolean checkSyntax(@NotNull String input) { diff --git a/forge-mod/src/main/java/com/leisuretimedock/crossmod/config/CrossServerConfigManager.java b/forge-mod/src/main/java/com/leisuretimedock/crossmod/config/CrossServerConfigManager.java index 42d30e9..aed98cf 100644 --- a/forge-mod/src/main/java/com/leisuretimedock/crossmod/config/CrossServerConfigManager.java +++ b/forge-mod/src/main/java/com/leisuretimedock/crossmod/config/CrossServerConfigManager.java @@ -32,6 +32,9 @@ public class CrossServerConfigManager { @Getter private final Map servers = new TreeMap<>(); + @Getter + private boolean disabledJoinQuitMessage = false; + private @NotNull @Unmodifiable Map parseServer(@NotNull List servers) { Map serverMap = new TreeMap<>(); for (String server : servers) { @@ -53,6 +56,7 @@ public class CrossServerConfigManager { try { clear(); servers.putAll(parseServer(CrossServerConfig.SERVER_LIST.get())); + disabledJoinQuitMessage = CrossServerConfig.DISABLED_JOIN_QUIT_MESSAGE.get(); cacheHash = -1; cacheTag = serializeToNBT(); log.debug("Configs reloaded"); diff --git a/forge-mod/src/main/java/com/leisuretimedock/crossmod/mixin/MixinBundlePacket.java b/forge-mod/src/main/java/com/leisuretimedock/crossmod/mixin/MixinBundlePacket.java new file mode 100644 index 0000000..2346038 --- /dev/null +++ b/forge-mod/src/main/java/com/leisuretimedock/crossmod/mixin/MixinBundlePacket.java @@ -0,0 +1,153 @@ +package com.leisuretimedock.crossmod.mixin; + +import com.leisuretimedock.crossmod.config.CrossCommonModConfig; +import com.leisuretimedock.crossmod.util.ModLogger; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.PacketListener; +import net.minecraft.network.protocol.BundlePacket; +import net.minecraft.network.protocol.Packet; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(BundlePacket.class) +public abstract class MixinBundlePacket implements Packet { + + @Shadow + public abstract Iterable> subPackets(); + + @Unique + private static int encodeCallCount = 0; + + @Unique + private int savedWriterIndex = -1; + + @Unique + private int savedReaderIndex = -1; + + /** + * 监控 write 方法开始 - 记录初始状态 + */ + @Inject(method = "write", at = @At("HEAD"), cancellable = false) + private void onWriteHead(FriendlyByteBuf buffer, CallbackInfo ci) { + if (!CrossCommonModConfig.getInstance().isDebugBundleDelimiter()) { + return; + } + + encodeCallCount++; + savedWriterIndex = buffer.writerIndex(); + savedReaderIndex = buffer.readerIndex(); + + ModLogger.bundle(""); + ModLogger.bundle("========== BundlePacket.write #{} ==========", encodeCallCount); + ModLogger.bundle("BundlePacket type: {}", this.getClass().getSimpleName()); + ModLogger.bundle("Buffer state BEFORE write:"); + ModLogger.bundle(" readerIndex: {}", savedReaderIndex); + ModLogger.bundle(" writerIndex: {}", savedWriterIndex); + ModLogger.bundle(" readableBytes: {}", buffer.readableBytes()); + ModLogger.bundle(" writableBytes: {}", buffer.writableBytes()); + + // 记录子包信息 + int packetCount = 0; + ModLogger.bundle("Sub-packets in this bundle:"); + for (Packet packet : subPackets()) { + packetCount++; + ModLogger.bundle(" #{}. {}", packetCount, packet.getClass().getSimpleName()); + } + ModLogger.bundle("Total sub-packets: {}", packetCount); + } + + /** + * 监控 write 方法返回后 - 检查是否有异常写入 + */ + @Inject(method = "write", at = @At("RETURN")) + private void onWriteReturn(FriendlyByteBuf buffer, CallbackInfo ci) { + if (!CrossCommonModConfig.getInstance().isDebugBundleDelimiter()) { + return; + } + + int afterWriterIndex = buffer.writerIndex(); + int afterReaderIndex = buffer.readerIndex(); + int bytesWritten = afterWriterIndex - savedWriterIndex; + int readerMoved = afterReaderIndex - savedReaderIndex; + + ModLogger.bundle("Buffer state AFTER write:"); + ModLogger.bundle(" readerIndex: {} (changed by {})", afterReaderIndex, readerMoved); + ModLogger.bundle(" writerIndex: {} (changed by {})", afterWriterIndex, bytesWritten); + ModLogger.bundle(" readableBytes: {}", buffer.readableBytes()); + + // 关键检查:BundlePacket.write() 应该是空实现 + // 正常情况下不应该有任何写入操作 + if (bytesWritten != 0) { + ModLogger.error("!!! CRITICAL: BundlePacket.write wrote {} bytes to buffer !!!", bytesWritten); + ModLogger.error("BundlePacket.write() should be EMPTY and write NOTHING!"); + ModLogger.error("This indicates a bug in: {}", this.getClass().getSimpleName()); + + // 如果写入了数据,尝试分析写入的内容 + if (bytesWritten > 0 && bytesWritten <= buffer.writableBytes()) { + int savedIdx = buffer.readerIndex(); + int readPos = savedWriterIndex; + buffer.readerIndex(readPos); + + int showBytes = Math.min(bytesWritten, 64); + byte[] writtenData = new byte[showBytes]; + buffer.readBytes(writtenData); + + StringBuilder hex = new StringBuilder(); + StringBuilder ascii = new StringBuilder(); + for (int i = 0; i < showBytes; i++) { + hex.append(String.format("%02X ", writtenData[i] & 0xFF)); + char c = (char) (writtenData[i] & 0xFF); + ascii.append(c >= 32 && c < 127 ? c : '.'); + } + + ModLogger.bundle("Written data hex (first {} of {}):", showBytes, bytesWritten); + ModLogger.bundle(" {}", hex.toString()); + ModLogger.bundle("Written data ASCII: {}", ascii.toString()); + buffer.readerIndex(savedIdx); + } + + // 打印堆栈,找出是谁调用了写入 + ModLogger.bundle("Stack trace of the write operation:"); + StackTraceElement[] stack = Thread.currentThread().getStackTrace(); + for (int i = 2; i < Math.min(stack.length, 12); i++) { + ModLogger.bundle(" at {}", stack[i].toString()); + } + } else { + ModLogger.bundle("✓ BundlePacket.write wrote 0 bytes (correct behavior)"); + } + + // 检查读取位置是否被意外移动 + if (readerMoved != 0) { + ModLogger.warn("WARNING: BundlePacket.write changed readerIndex by {} bytes!", readerMoved); + ModLogger.warn("This should not happen in write operation!"); + } + + ModLogger.bundle("========================================"); + + // 重置保存的索引 + savedWriterIndex = -1; + savedReaderIndex = -1; + } + + /** + * 可选:监控子包的写入过程(更细粒度的调试) + * 需要在配置中开启更详细的调试 + */ + @Unique + protected void debugSubPacketWrite(Packet packet, FriendlyByteBuf buffer, int beforeWrite) { + if (!CrossCommonModConfig.getInstance().isDebugBundleDelimiter()) { + return; + } + + if (CrossCommonModConfig.getInstance().isDebugPackets()) { + int afterWrite = buffer.writerIndex(); + int subPacketBytes = afterWrite - beforeWrite; + ModLogger.debug(" Sub-packet {} wrote {} bytes", + packet.getClass().getSimpleName(), subPacketBytes); + } + } +} \ No newline at end of file diff --git a/forge-mod/src/main/java/com/leisuretimedock/crossmod/mixin/MixinConnection.java b/forge-mod/src/main/java/com/leisuretimedock/crossmod/mixin/MixinConnection.java new file mode 100644 index 0000000..93205e1 --- /dev/null +++ b/forge-mod/src/main/java/com/leisuretimedock/crossmod/mixin/MixinConnection.java @@ -0,0 +1,48 @@ +package com.leisuretimedock.crossmod.mixin; + +import com.leisuretimedock.crossmod.config.CrossCommonModConfig; +import com.leisuretimedock.crossmod.util.ModLogger; +import io.netty.channel.Channel; +import net.minecraft.network.Connection; +import net.minecraft.network.PacketSendListener; +import net.minecraft.network.protocol.BundleDelimiterPacket; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.game.ClientboundBundlePacket; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(Connection.class) +public class MixinConnection { + @Shadow + private Channel channel; + + private static int sendCount = 0; + + @Inject(method = "send(Lnet/minecraft/network/protocol/Packet;Lnet/minecraft/network/PacketSendListener;)V", + at = @At("HEAD")) + private void onSendPacket(Packet packet, PacketSendListener sendListener, CallbackInfo ci) { + if (!CrossCommonModConfig.getInstance().isDebugBundleDelimiter()) { + return; + } + + if (packet instanceof BundleDelimiterPacket) { + sendCount++; + ModLogger.bundle(""); + ModLogger.bundle(">>> Sending BundleDelimiterPacket #{}", sendCount); + ModLogger.bundle("Channel: {}", channel); + logStackTrace(); + } else if (packet instanceof ClientboundBundlePacket) { + ModLogger.bundle(">>> Sending ClientboundBundlePacket"); + } + } + + private void logStackTrace() { + StackTraceElement[] stack = Thread.currentThread().getStackTrace(); + for (int i = 2; i < Math.min(stack.length, 8); i++) { + ModLogger.bundle(" at {}", stack[i].toString()); + } + } +} diff --git a/forge-mod/src/main/java/com/leisuretimedock/crossmod/mixin/MixinMUINetWorkHandler.java b/forge-mod/src/main/java/com/leisuretimedock/crossmod/mixin/MixinMUINetWorkHandler.java index 2a9c0bd..1b5c511 100644 --- a/forge-mod/src/main/java/com/leisuretimedock/crossmod/mixin/MixinMUINetWorkHandler.java +++ b/forge-mod/src/main/java/com/leisuretimedock/crossmod/mixin/MixinMUINetWorkHandler.java @@ -2,6 +2,7 @@ package com.leisuretimedock.crossmod.mixin; import icyllis.modernui.mc.forge.NetworkHandler; import net.minecraft.resources.ResourceLocation; +import org.jetbrains.annotations.NotNull; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Pseudo; import org.spongepowered.asm.mixin.injection.At; @@ -17,7 +18,7 @@ public class MixinMUINetWorkHandler { argsOnly = true, ordinal = 0 ) - private static ResourceLocation modifyNameParameter(ResourceLocation name) { + private static ResourceLocation modifyNameParameter(@NotNull ResourceLocation name) { return name.getPath().isEmpty() ? name : new ResourceLocation(name.getNamespace(), "default"); } diff --git a/forge-mod/src/main/java/com/leisuretimedock/crossmod/mixin/MixinPacketDecoder.java b/forge-mod/src/main/java/com/leisuretimedock/crossmod/mixin/MixinPacketDecoder.java new file mode 100644 index 0000000..76bd73b --- /dev/null +++ b/forge-mod/src/main/java/com/leisuretimedock/crossmod/mixin/MixinPacketDecoder.java @@ -0,0 +1,127 @@ +package com.leisuretimedock.crossmod.mixin; + +import com.leisuretimedock.crossmod.config.CrossCommonModConfig; +import com.leisuretimedock.crossmod.util.ModLogger; +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import net.minecraft.network.PacketDecoder; +import net.minecraft.network.protocol.BundleDelimiterPacket; +import net.minecraft.network.protocol.game.ClientboundBundlePacket; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.util.List; + +@Mixin(PacketDecoder.class) +public class MixinPacketDecoder { + + private static int decodeCount = 0; + private static int bundleDelimiterCount = 0; + private static int clientboundBundleCount = 0; + + @Inject(method = "decode", at = @At("HEAD")) + private void onDecodeHead(ChannelHandlerContext ctx, ByteBuf in, List out, CallbackInfo ci) { + if (!CrossCommonModConfig.getInstance().isDebugBundleDelimiter()) { + return; + } + + if (in.readableBytes() < 1) return; + + decodeCount++; + int savedIdx = in.readerIndex(); + int packetId = in.getByte(savedIdx) & 0xFF; + + ModLogger.bundle(""); + ModLogger.bundle("========== PacketDecoder.decode #{} ==========", decodeCount); + ModLogger.bundle("Packet ID: 0x{} ({})", Integer.toHexString(packetId), packetId); + ModLogger.bundle("Available bytes before decode: {}", in.readableBytes()); + ModLogger.bundle("Reader index: {}, Writer index: {}", in.readerIndex(), in.writerIndex()); + } + + @Inject(method = "decode", at = @At("RETURN")) + private void onDecodeReturn(ChannelHandlerContext ctx, ByteBuf in, List out, CallbackInfo ci) { + if (!CrossCommonModConfig.getInstance().isDebugBundleDelimiter()) { + return; + } + + // 检查解码出的包类型 + boolean isBundleDelimiter = false; + boolean isClientboundBundle = false; + + for (Object packet : out) { + if (packet instanceof BundleDelimiterPacket) { + isBundleDelimiter = true; + bundleDelimiterCount++; + } else if (packet instanceof ClientboundBundlePacket) { + isClientboundBundle = true; + clientboundBundleCount++; + } + } + + int remaining = in.readableBytes(); + + if (isBundleDelimiter) { + ModLogger.bundle(""); + ModLogger.bundle(">>> Decoded BundleDelimiterPacket #{} <<<", bundleDelimiterCount); + ModLogger.bundle("Remaining bytes after decode: {}", remaining); + + if (remaining > 0) { + ModLogger.error("!!! CRITICAL: {} bytes remain unread after BundleDelimiterPacket !!!", remaining); + logRemainingBytes(in, remaining); + ModLogger.bundle("Stack trace:"); + logStackTrace(); + } else { + ModLogger.bundle("✓ BundleDelimiterPacket decoded cleanly"); + } + } + + if (isClientboundBundle) { + ModLogger.bundle(""); + ModLogger.bundle(">>> Decoded ClientboundBundlePacket #{} <<<", clientboundBundleCount); + ModLogger.bundle("Remaining bytes after decode: {}", remaining); + + if (remaining > 0) { + ModLogger.error("!!! CRITICAL: {} bytes remain unread after ClientboundBundlePacket !!!", remaining); + logRemainingBytes(in, remaining); + } else { + ModLogger.bundle("✓ ClientboundBundlePacket decoded cleanly"); + } + } + } + + private void logRemainingBytes(ByteBuf in, int remaining) { + int savedIdx = in.readerIndex(); + int showBytes = Math.min(remaining, 64); + byte[] leftover = new byte[showBytes]; + in.readBytes(leftover); + + StringBuilder hex = new StringBuilder(); + StringBuilder ascii = new StringBuilder(); + for (int i = 0; i < showBytes; i++) { + hex.append(String.format("%02X ", leftover[i] & 0xFF)); + char c = (char) (leftover[i] & 0xFF); + ascii.append(c >= 32 && c < 127 ? c : '.'); + } + + ModLogger.bundle("Leftover data hex (first {} of {}): {}", showBytes, remaining, hex.toString()); + ModLogger.bundle("Leftover data ASCII: {}", ascii.toString()); + + // 尝试识别可能的包类型 + if (showBytes >= 1) { + int nextPacketId = leftover[0] & 0xFF; + ModLogger.bundle("Next packet ID would be: 0x{} ({})", + Integer.toHexString(nextPacketId), nextPacketId); + } + + in.readerIndex(savedIdx); + } + + private void logStackTrace() { + StackTraceElement[] stack = Thread.currentThread().getStackTrace(); + for (int i = 2; i < Math.min(stack.length, 10); i++) { + ModLogger.bundle(" at {}", stack[i].toString()); + } + } +} \ No newline at end of file diff --git a/forge-mod/src/main/java/com/leisuretimedock/crossmod/mixin/MixinPlayerList.java b/forge-mod/src/main/java/com/leisuretimedock/crossmod/mixin/MixinPlayerList.java new file mode 100644 index 0000000..f16e7fa --- /dev/null +++ b/forge-mod/src/main/java/com/leisuretimedock/crossmod/mixin/MixinPlayerList.java @@ -0,0 +1,33 @@ +package com.leisuretimedock.crossmod.mixin; + +import com.leisuretimedock.crossmod.config.CrossServerConfigManager; +import net.minecraft.network.chat.Component; +import net.minecraft.server.players.PlayerList; +import org.slf4j.Logger; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(PlayerList.class) +public class MixinPlayerList { + @Shadow + @Final + private static Logger LOGGER; + + @Inject( + method = "broadcastSystemMessage(Lnet/minecraft/network/chat/Component;Z)V", + at = @At("HEAD"), cancellable = true + ) + private void send(Component message, boolean bypassHiddenChat, CallbackInfo ci) { + try { + if (CrossServerConfigManager.INSTANCE.isDisabledJoinQuitMessage() && message.toString().contains("multiplayer.player.joined") || message.toString().contains("multiplayer.player.left")) { + ci.cancel(); + } + } catch (Exception e) { + LOGGER.warn("Exception while sending system message to client", e); + } + } +} diff --git a/forge-mod/src/main/java/com/leisuretimedock/crossmod/network/NetworkHandler.java b/forge-mod/src/main/java/com/leisuretimedock/crossmod/network/NetworkHandler.java index 63964ac..2b8d7ea 100644 --- a/forge-mod/src/main/java/com/leisuretimedock/crossmod/network/NetworkHandler.java +++ b/forge-mod/src/main/java/com/leisuretimedock/crossmod/network/NetworkHandler.java @@ -148,7 +148,7 @@ public class NetworkHandler { * @param subChannel 子通道标识 * @param payload 负载数据(字节数组) */ - public static void sendPluginMessage(ResourceLocation subChannel, byte[] payload) { + public static void sendPluginMessage(ResourceLocation subChannel, byte @NotNull [] payload) { FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer(payload.length)); buf.writeBytes(payload); @@ -201,7 +201,7 @@ public class NetworkHandler { * @param entity the entity * @param packetDistributor the packet distributor */ - public static void sendToPlayer(MSG message, T entity, PacketDistributor packetDistributor){ + public static void sendToPlayer(MSG message, T entity, @NotNull PacketDistributor packetDistributor){ CHANNEL.send(packetDistributor.with(() -> entity), message); } } \ No newline at end of file diff --git a/forge-mod/src/main/java/com/leisuretimedock/crossmod/network/PingRequestManager.java b/forge-mod/src/main/java/com/leisuretimedock/crossmod/network/PingRequestManager.java index 4659589..04ade23 100644 --- a/forge-mod/src/main/java/com/leisuretimedock/crossmod/network/PingRequestManager.java +++ b/forge-mod/src/main/java/com/leisuretimedock/crossmod/network/PingRequestManager.java @@ -5,6 +5,8 @@ import lombok.Getter; import lombok.extern.slf4j.Slf4j; import net.minecraft.network.chat.Component; import net.minecraft.server.level.ServerPlayer; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; import java.util.*; import java.util.concurrent.*; @@ -210,7 +212,7 @@ public final class PingRequestManager { /** * 获取所有玩家的最新ping值 */ - public static Map getAllLatestPings() { + public static @NotNull Map getAllLatestPings() { Map results = new HashMap<>(); playerData.forEach((uuid, data) -> { @@ -228,7 +230,7 @@ public final class PingRequestManager { * @param players 要查询的玩家集合 * @return 包含玩家UUID和最新ping时间的Map */ - public static Map getLatestPingsForPlayers(Collection players) { + public static @NotNull Map getLatestPingsForPlayers(@NotNull Collection players) { Map results = new HashMap<>(); for (ServerPlayer player : players) { @@ -297,7 +299,7 @@ public final class PingRequestManager { /** * 取消该玩家的所有进行中的Ping请求 */ - public static void cancelPings(ServerPlayer player) { + public static void cancelPings(@NotNull ServerPlayer player) { PlayerPingData data = playerData.get(player.getUUID()); if (data != null) { synchronized (data) { @@ -342,7 +344,7 @@ public final class PingRequestManager { PingStats globalStats = getGlobalPingStats(); NetworkHandler.sendPingReport(player, latestPings, averages, globalStats); } - private static void updatePingHistory(PlayerPingData data, long ping) { + private static void updatePingHistory(@NotNull PlayerPingData data, long ping) { data.addPing(ping); } @@ -369,7 +371,8 @@ public final class PingRequestManager { return PlayerPingData.calculate(pingHistory); } - private static double calculatePacketLossRate(PlayerPingData data) { + @Contract(pure = true) + private static double calculatePacketLossRate(@NotNull PlayerPingData data) { synchronized (PingRequestManager.class) { if (data.totalRequests == 0) return 0; return (1 - (double) data.successfulRequests / data.totalRequests) * 100; @@ -403,7 +406,8 @@ public final class PingRequestManager { return calculate(pings); } - static double calculate(Collection pings) { + @Contract(pure = true) + static double calculate(@NotNull Collection pings) { if (pings.isEmpty()) return 0; double total = 0; diff --git a/forge-mod/src/main/java/com/leisuretimedock/crossmod/network/toClient/CommonConfigHashInformPacket.java b/forge-mod/src/main/java/com/leisuretimedock/crossmod/network/toClient/CommonConfigHashInformPacket.java index 701c039..3523595 100644 --- a/forge-mod/src/main/java/com/leisuretimedock/crossmod/network/toClient/CommonConfigHashInformPacket.java +++ b/forge-mod/src/main/java/com/leisuretimedock/crossmod/network/toClient/CommonConfigHashInformPacket.java @@ -5,6 +5,8 @@ import com.leisuretimedock.crossmod.network.NetworkHandler; import com.leisuretimedock.crossmod.network.toServer.SyncCommonConfigRequestPacket; import net.minecraft.network.FriendlyByteBuf; import net.minecraftforge.network.NetworkEvent; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; import java.util.function.Supplier; @@ -18,7 +20,7 @@ public record CommonConfigHashInformPacket(int hash) { * @param packet the packet * @param buffer the buffer */ - public static void encode(CommonConfigHashInformPacket packet, FriendlyByteBuf buffer) { + public static void encode(@NotNull CommonConfigHashInformPacket packet, @NotNull FriendlyByteBuf buffer) { buffer.writeInt(packet.hash()); } @@ -29,7 +31,8 @@ public record CommonConfigHashInformPacket(int hash) { * @param buffer the buffer * @return the common config hash inform packet */ - public static CommonConfigHashInformPacket decode(FriendlyByteBuf buffer) { + @Contract("_ -> new") + public static @NotNull CommonConfigHashInformPacket decode(@NotNull FriendlyByteBuf buffer) { return new CommonConfigHashInformPacket(buffer.readInt()); } @@ -39,7 +42,7 @@ public record CommonConfigHashInformPacket(int hash) { * @param packet the packet * @param ctx the ctx */ - public static void handle(CommonConfigHashInformPacket packet, Supplier ctx) { + public static void handle(CommonConfigHashInformPacket packet, @NotNull Supplier ctx) { NetworkEvent.Context context = ctx.get(); context.enqueueWork(() -> { int hash = CrossServerConfigManager.INSTANCE.calculateConfigHash(); diff --git a/forge-mod/src/main/java/com/leisuretimedock/crossmod/network/toClient/PingMessagePayload.java b/forge-mod/src/main/java/com/leisuretimedock/crossmod/network/toClient/PingMessagePayload.java index 3b07c50..9ff7c64 100644 --- a/forge-mod/src/main/java/com/leisuretimedock/crossmod/network/toClient/PingMessagePayload.java +++ b/forge-mod/src/main/java/com/leisuretimedock/crossmod/network/toClient/PingMessagePayload.java @@ -5,6 +5,7 @@ import net.minecraft.network.FriendlyByteBuf; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.fml.DistExecutor; import net.minecraftforge.network.NetworkEvent; +import org.jetbrains.annotations.NotNull; import java.util.UUID; import java.util.function.Supplier; @@ -13,18 +14,18 @@ import static com.leisuretimedock.crossmod.network.NetworkHandler.CHANNEL; //Server -> Client public record PingMessagePayload(UUID requestId) { - public static void encode(PingMessagePayload payload, FriendlyByteBuf buf) { + public static void encode(@NotNull PingMessagePayload payload, @NotNull FriendlyByteBuf buf) { buf.writeLong(payload.requestId().getMostSignificantBits()); buf.writeLong(payload.requestId().getLeastSignificantBits()); } - public static PingMessagePayload decode(FriendlyByteBuf buf) { + public static @NotNull PingMessagePayload decode(@NotNull FriendlyByteBuf buf) { long mostSignificantBits = buf.readLong(); long leastSignificantBits = buf.readLong(); return new PingMessagePayload(new UUID(mostSignificantBits, leastSignificantBits)); } //客户端处理 - public static void handle(PingMessagePayload msg, Supplier ctx) { + public static void handle(PingMessagePayload msg, @NotNull Supplier ctx) { ctx.get().enqueueWork(() -> DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> () -> { // 客户端收到ping请求,立即返回pong CHANNEL.sendToServer(new PongMessagePayload(msg.requestId)); diff --git a/forge-mod/src/main/java/com/leisuretimedock/crossmod/network/toClient/PingResultPacket.java b/forge-mod/src/main/java/com/leisuretimedock/crossmod/network/toClient/PingResultPacket.java index b2c3137..d9a95d3 100644 --- a/forge-mod/src/main/java/com/leisuretimedock/crossmod/network/toClient/PingResultPacket.java +++ b/forge-mod/src/main/java/com/leisuretimedock/crossmod/network/toClient/PingResultPacket.java @@ -3,6 +3,8 @@ package com.leisuretimedock.crossmod.network.toClient; import com.leisuretimedock.crossmod.client.ClientPingHandler; import net.minecraft.network.FriendlyByteBuf; import net.minecraftforge.network.NetworkEvent; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; import java.util.HashMap; import java.util.Map; @@ -19,7 +21,7 @@ public class PingResultPacket { this.pingResults = new HashMap<>(pingResults); this.averageLatencies = new HashMap<>(averageLatencies); } - public PingResultPacket(FriendlyByteBuf buf) { + public PingResultPacket(@NotNull FriendlyByteBuf buf) { this.timestamp = System.currentTimeMillis(); int size = buf.readVarInt(); this.pingResults = new HashMap<>(size); @@ -32,11 +34,12 @@ public class PingResultPacket { averageLatencies.put(uuid, avgLatency); } } - public static PingResultPacket decode(FriendlyByteBuf buf) { + @Contract("_ -> new") + public static @NotNull PingResultPacket decode(FriendlyByteBuf buf) { return new PingResultPacket(buf); } - public void encode(FriendlyByteBuf buf) { + public void encode(@NotNull FriendlyByteBuf buf) { buf.writeVarInt(pingResults.size()); pingResults.forEach((uuid, ping) -> { buf.writeUUID(uuid); @@ -45,7 +48,7 @@ public class PingResultPacket { }); } - public void handle(Supplier ctx) { + public void handle(@NotNull Supplier ctx) { ctx.get().enqueueWork(() -> { // 检查数据时效性(5秒内有效) if (System.currentTimeMillis() - timestamp < 5000) { diff --git a/forge-mod/src/main/java/com/leisuretimedock/crossmod/network/toClient/PingStatsPacket.java b/forge-mod/src/main/java/com/leisuretimedock/crossmod/network/toClient/PingStatsPacket.java index 14f012a..6cb5268 100644 --- a/forge-mod/src/main/java/com/leisuretimedock/crossmod/network/toClient/PingStatsPacket.java +++ b/forge-mod/src/main/java/com/leisuretimedock/crossmod/network/toClient/PingStatsPacket.java @@ -4,6 +4,8 @@ import com.leisuretimedock.crossmod.client.ClientPingHandler; import com.leisuretimedock.crossmod.network.PingRequestManager; import net.minecraft.network.FriendlyByteBuf; import net.minecraftforge.network.NetworkEvent; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; import java.util.function.Supplier; @@ -15,7 +17,8 @@ public class PingStatsPacket { this.stats = stats; } - public static PingStatsPacket decode(FriendlyByteBuf buf) { + @Contract("_ -> new") + public static @NotNull PingStatsPacket decode(@NotNull FriendlyByteBuf buf) { return new PingStatsPacket( new PingRequestManager.PingStats( buf.readDouble(), @@ -28,7 +31,7 @@ public class PingStatsPacket { ); } - public void encode(FriendlyByteBuf buf) { + public void encode(@NotNull FriendlyByteBuf buf) { buf.writeDouble(stats.average()); buf.writeLong(stats.max()); buf.writeLong(stats.min()); @@ -37,7 +40,7 @@ public class PingStatsPacket { buf.writeDouble(stats.packetLossRate()); } - public void handle(Supplier ctx) { + public void handle(@NotNull Supplier ctx) { ctx.get().enqueueWork(() -> { ClientPingHandler.handlePingStats(stats); }); diff --git a/forge-mod/src/main/java/com/leisuretimedock/crossmod/network/toClient/ResetPacket.java b/forge-mod/src/main/java/com/leisuretimedock/crossmod/network/toClient/ResetPacket.java index 707e4a6..19de585 100644 --- a/forge-mod/src/main/java/com/leisuretimedock/crossmod/network/toClient/ResetPacket.java +++ b/forge-mod/src/main/java/com/leisuretimedock/crossmod/network/toClient/ResetPacket.java @@ -14,6 +14,8 @@ import net.minecraft.network.ConnectionProtocol; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.chat.Component; import net.minecraftforge.network.*; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import java.util.function.Supplier; @@ -26,7 +28,8 @@ public class ResetPacket extends HandshakeMessages.C2SAcknowledge { public ResetPacket() { super(); } - public static ResetPacket decode(FriendlyByteBuf ignoredBuf) { + @Contract("_ -> new") + public static @NotNull ResetPacket decode(FriendlyByteBuf ignoredBuf) { return new ResetPacket(); } @@ -38,7 +41,7 @@ public class ResetPacket extends HandshakeMessages.C2SAcknowledge { } - public static void handler(Supplier ctxSupplier, Logger log) { + public static void handler(@NotNull Supplier ctxSupplier, Logger log) { NetworkEvent.Context ctx = ctxSupplier.get(); ClientResetManager.isNegotiating.set(true); Connection conn = ctx.getNetworkManager(); diff --git a/forge-mod/src/main/java/com/leisuretimedock/crossmod/network/toServer/PongMessagePayload.java b/forge-mod/src/main/java/com/leisuretimedock/crossmod/network/toServer/PongMessagePayload.java index 025f3ec..2e612bf 100644 --- a/forge-mod/src/main/java/com/leisuretimedock/crossmod/network/toServer/PongMessagePayload.java +++ b/forge-mod/src/main/java/com/leisuretimedock/crossmod/network/toServer/PongMessagePayload.java @@ -3,24 +3,25 @@ package com.leisuretimedock.crossmod.network.toServer; import com.leisuretimedock.crossmod.network.PingRequestManager; import net.minecraft.network.FriendlyByteBuf; import net.minecraftforge.network.NetworkEvent; +import org.jetbrains.annotations.NotNull; import java.util.UUID; import java.util.function.Supplier; //Server public record PongMessagePayload(UUID requestId) { - public static void encode(PongMessagePayload payload, FriendlyByteBuf buf) { + public static void encode(@NotNull PongMessagePayload payload, @NotNull FriendlyByteBuf buf) { buf.writeLong(payload.requestId().getMostSignificantBits()); buf.writeLong(payload.requestId().getLeastSignificantBits()); } - public static PongMessagePayload decode(FriendlyByteBuf buf) { + public static @NotNull PongMessagePayload decode(@NotNull FriendlyByteBuf buf) { long mostSignificantBits = buf.readLong(); long leastSignificantBits = buf.readLong(); return new PongMessagePayload(new UUID(mostSignificantBits, leastSignificantBits)); } //服务器处理 - public static void handle(PongMessagePayload msg, Supplier ctx) { + public static void handle(PongMessagePayload msg, @NotNull Supplier ctx) { ctx.get().enqueueWork(() -> { PingRequestManager.complete(ctx.get().getSender(),msg.requestId); }); diff --git a/forge-mod/src/main/java/com/leisuretimedock/crossmod/network/toServer/SyncCommonConfigRequestPacket.java b/forge-mod/src/main/java/com/leisuretimedock/crossmod/network/toServer/SyncCommonConfigRequestPacket.java index a90deda..c27cbd0 100644 --- a/forge-mod/src/main/java/com/leisuretimedock/crossmod/network/toServer/SyncCommonConfigRequestPacket.java +++ b/forge-mod/src/main/java/com/leisuretimedock/crossmod/network/toServer/SyncCommonConfigRequestPacket.java @@ -5,6 +5,8 @@ import com.leisuretimedock.crossmod.network.NetworkHandler; import com.leisuretimedock.crossmod.network.toClient.SyncCommonConfigPacket; import net.minecraft.network.FriendlyByteBuf; import net.minecraftforge.network.NetworkEvent; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; import java.util.function.Supplier; @@ -18,7 +20,7 @@ public record SyncCommonConfigRequestPacket(int hash) { * @param msg the msg * @param buf the buf */ - public static void encode(SyncCommonConfigRequestPacket msg, FriendlyByteBuf buf) { + public static void encode(@NotNull SyncCommonConfigRequestPacket msg, @NotNull FriendlyByteBuf buf) { buf.writeInt(msg.hash); } @@ -29,7 +31,8 @@ public record SyncCommonConfigRequestPacket(int hash) { * @param buf the buf * @return the sync common config request packet */ - public static SyncCommonConfigRequestPacket decode(FriendlyByteBuf buf) { + @Contract("_ -> new") + public static @NotNull SyncCommonConfigRequestPacket decode(@NotNull FriendlyByteBuf buf) { return new SyncCommonConfigRequestPacket(buf.readInt()); } @@ -39,7 +42,7 @@ public record SyncCommonConfigRequestPacket(int hash) { * @param msg the msg * @param ctx the ctx */ - public static void handle(SyncCommonConfigRequestPacket msg, Supplier ctx) { + public static void handle(SyncCommonConfigRequestPacket msg, @NotNull Supplier ctx) { ctx.get().enqueueWork(() -> { if (msg.hash != CrossServerConfigManager.INSTANCE.cacheHash) { NetworkHandler.sendToPlayer(new SyncCommonConfigPacket(CrossServerConfigManager.INSTANCE.serializeToNBT(), CrossServerConfigManager.INSTANCE.calculateConfigHash()), ctx.get().getSender()); diff --git a/forge-mod/src/main/java/com/leisuretimedock/crossmod/reset/ClientResetManager.java b/forge-mod/src/main/java/com/leisuretimedock/crossmod/reset/ClientResetManager.java index 6919a29..04d0bfa 100644 --- a/forge-mod/src/main/java/com/leisuretimedock/crossmod/reset/ClientResetManager.java +++ b/forge-mod/src/main/java/com/leisuretimedock/crossmod/reset/ClientResetManager.java @@ -12,6 +12,8 @@ import net.minecraftforge.network.NetworkConstants; import net.minecraftforge.network.NetworkDirection; import net.minecraftforge.network.NetworkEvent; import net.minecraftforge.network.simple.SimpleChannel; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.lang.reflect.Constructor; import java.lang.reflect.Field; @@ -25,7 +27,7 @@ public class ClientResetManager { public static AtomicBoolean isNegotiating = new AtomicBoolean(false); public static SimpleChannel handshakeChannel; - public static void init(FMLCommonSetupEvent event) { + public static void init(@NotNull FMLCommonSetupEvent event) { event.enqueueWork(() -> { if (handshakeField == null) { @@ -55,7 +57,7 @@ public class ClientResetManager { } }); } - private static Field fetchHandshakeChannel() { + private static @Nullable Field fetchHandshakeChannel() { try { return ObfuscationReflectionHelper.findField(NetworkConstants.class, "handshakeChannel"); } @@ -65,7 +67,7 @@ public class ClientResetManager { } } - private static Constructor fetchNetworkEventContext() { + private static @Nullable Constructor fetchNetworkEventContext() { try { return ObfuscationReflectionHelper.findConstructor(NetworkEvent.Context.class, Connection.class, NetworkDirection.class, int.class); } diff --git a/forge-mod/src/main/java/com/leisuretimedock/crossmod/reset/ResetHelper.java b/forge-mod/src/main/java/com/leisuretimedock/crossmod/reset/ResetHelper.java index 7f49c75..af07d5c 100644 --- a/forge-mod/src/main/java/com/leisuretimedock/crossmod/reset/ResetHelper.java +++ b/forge-mod/src/main/java/com/leisuretimedock/crossmod/reset/ResetHelper.java @@ -9,6 +9,7 @@ import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.OnlyIn; import net.minecraftforge.network.NetworkEvent; import net.minecraftforge.registries.GameData; +import org.jetbrains.annotations.NotNull; import java.util.NoSuchElementException; import java.util.Objects; @@ -20,7 +21,7 @@ import static net.minecraft.ChatFormatting.BOLD; @OnlyIn(Dist.CLIENT) public class ResetHelper { @SuppressWarnings("UnstableApiUsage") - public static boolean clearClient(NetworkEvent.Context context) { + public static boolean clearClient(NetworkEvent.@NotNull Context context) { CompletableFuture future = context.enqueueWork(() -> { log.debug("Clearing"); Minecraft minecraft = Minecraft.getInstance(); diff --git a/forge-mod/src/main/java/com/leisuretimedock/crossmod/reset/ResetPacket.java b/forge-mod/src/main/java/com/leisuretimedock/crossmod/reset/ResetPacket.java index 96ca8e3..fc75fb2 100644 --- a/forge-mod/src/main/java/com/leisuretimedock/crossmod/reset/ResetPacket.java +++ b/forge-mod/src/main/java/com/leisuretimedock/crossmod/reset/ResetPacket.java @@ -5,6 +5,8 @@ import lombok.Setter; import lombok.extern.slf4j.Slf4j; import net.minecraft.network.FriendlyByteBuf; import net.minecraftforge.network.*; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.NotNull; import java.util.function.Supplier; @@ -16,7 +18,8 @@ public class ResetPacket extends HandshakeMessages.C2SAcknowledge { public ResetPacket() { super(); } - public static ResetPacket decode(FriendlyByteBuf buf) { + @Contract("_ -> new") + public static @NotNull ResetPacket decode(FriendlyByteBuf buf) { return new ResetPacket(); } diff --git a/forge-mod/src/main/java/com/leisuretimedock/crossmod/util/DebugUtils.java b/forge-mod/src/main/java/com/leisuretimedock/crossmod/util/DebugUtils.java index 4c37c52..2e3be8d 100644 --- a/forge-mod/src/main/java/com/leisuretimedock/crossmod/util/DebugUtils.java +++ b/forge-mod/src/main/java/com/leisuretimedock/crossmod/util/DebugUtils.java @@ -1,57 +1,174 @@ package com.leisuretimedock.crossmod.util; +import com.leisuretimedock.crossmod.config.CrossCommonModConfig; import io.netty.buffer.ByteBuf; import net.minecraft.network.FriendlyByteBuf; +import org.jetbrains.annotations.NotNull; +import java.io.FileWriter; +import java.io.PrintWriter; import java.nio.charset.StandardCharsets; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; import java.util.HexFormat; public class DebugUtils { - public static void debugBuffer(FriendlyByteBuf buf) { + + private static PrintWriter logWriter = null; + private static final DateTimeFormatter TIME_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"); + + private static PrintWriter getLogWriter() { + if (!CrossCommonModConfig.getInstance().isLogToFile()) { + return null; + } + if (logWriter == null) { + try { + String logPath = CrossCommonModConfig.getInstance().getLogFilePath(); + logWriter = new PrintWriter(new FileWriter(logPath, true), true); + logWriter.println("=== CrossMod Debug Log Started at " + LocalDateTime.now().format(TIME_FORMAT) + " ==="); + } catch (Exception e) { + System.err.println("[CrossMod] Failed to create log file: " + e.getMessage()); + } + } + return logWriter; + } + + private static void log(String message) { + // 控制台输出 + System.out.println(message); + + // 文件输出 + PrintWriter writer = getLogWriter(); + if (writer != null) { + writer.println(message); + } + } + + private static boolean isDebugEnabled() { + return CrossCommonModConfig.getInstance().isDebugPackets(); + } + + public static void debugBuffer(@NotNull FriendlyByteBuf buf) { + debugBuffer(buf, "unknown"); + } + + public static void debugBuffer(@NotNull FriendlyByteBuf buf, String caller) { + if (!isDebugEnabled()) return; + int readable = buf.readableBytes(); - System.out.println("[Debug] Readable bytes: " + readable); + int readerIdx = buf.readerIndex(); + int writerIdx = buf.writerIndex(); + + log("[Debug][" + caller + "] ===================="); + log("[Debug][" + caller + "] readerIndex: " + readerIdx); + log("[Debug][" + caller + "] writerIndex: " + writerIdx); + log("[Debug][" + caller + "] readableBytes: " + readable); if (readable <= 0) { - System.out.println("[Debug] No extra bytes to inspect."); + log("[Debug][" + caller + "] No bytes left to inspect."); return; } + int maxBytes = CrossCommonModConfig.getInstance().getMaxDebugBytes(); + int bytesToRead = Math.min(readable, maxBytes); + // 保存当前位置 int index = buf.readerIndex(); // 读取并打印十六进制 - byte[] bytes = new byte[readable]; + byte[] bytes = new byte[bytesToRead]; buf.readBytes(bytes); String hex = HexFormat.of().formatHex(bytes); - System.out.println("[Debug] Extra bytes (hex): " + hex); + log("[Debug][" + caller + "] Bytes (hex): " + hex); - // 尝试以 UTF-8 解码(仅用于辅助分析) - try { - String utf8 = new String(bytes, StandardCharsets.UTF_8); - System.out.println("[Debug] Interpreted as UTF-8 string:\n" + utf8); - } catch (Exception e) { - System.out.println("[Debug] Failed to interpret as UTF-8: " + e.getMessage()); + if (bytesToRead < readable) { + log("[Debug][" + caller + "] ... (truncated, " + (readable - bytesToRead) + " more bytes)"); } - // 还原读取位置,避免影响其他逻辑 + // 尝试以 UTF-8 解码 + try { + String utf8 = new String(bytes, StandardCharsets.UTF_8); + String printable = utf8.chars() + .mapToObj(c -> (char) c) + .filter(c -> c >= 32 && c < 127 || c == '\n' || c == '\r') + .collect(StringBuilder::new, StringBuilder::append, StringBuilder::append) + .toString(); + if (!printable.isEmpty()) { + log("[Debug][" + caller + "] As UTF-8 (printable): " + printable); + } + } catch (Exception e) { + // ignore + } + + // 还原读取位置 buf.readerIndex(index); } - public static void debugFullBuffer(FriendlyByteBuf buf) { - ByteBuf internal = buf.copy(); // 复制整个缓冲区(包括所有字节) + + public static void debugFullBuffer(@NotNull FriendlyByteBuf buf) { + debugFullBuffer(buf, "unknown"); + } + + public static void debugFullBuffer(@NotNull FriendlyByteBuf buf, String caller) { + if (!isDebugEnabled()) return; + + ByteBuf internal = buf.copy(); int size = internal.readableBytes(); byte[] data = new byte[size]; internal.readBytes(data); - System.out.println("[Debug] Full buffer size: " + size); - System.out.println("[Debug] Hex dump:\n" + HexFormat.of().formatHex(data)); + log("[Debug][" + caller + "] Full buffer size: " + size); - try { - String utf8 = new String(data, StandardCharsets.UTF_8); - System.out.println("[Debug] UTF-8 decoded:\n" + utf8); - } catch (Exception e) { - System.out.println("[Debug] UTF-8 decode failed: " + e.getMessage()); + int maxBytes = CrossCommonModConfig.getInstance().getMaxDebugBytes(); + int hexSize = Math.min(size, maxBytes); + byte[] hexData = new byte[hexSize]; + System.arraycopy(data, 0, hexData, 0, hexSize); + log("[Debug][" + caller + "] Hex dump (first " + hexSize + " bytes):\n" + HexFormat.of().formatHex(hexData)); + + if (size > hexSize) { + log("[Debug][" + caller + "] ... (truncated, " + (size - hexSize) + " more bytes)"); } - internal.release(); // 手动释放 copy() 出来的 ByteBuf,防止泄漏 + internal.release(); } -} + + // 专门用于检测 BundleDelimiterPacket 的额外字节问题 + public static void checkBundleDelimiterPacket(@NotNull FriendlyByteBuf buf) { + if (!CrossCommonModConfig.getInstance().isDebugBundleDelimiter()) return; + + int remaining = buf.readableBytes(); + log("[Debug][BundleCheck] Remaining bytes after packet: " + remaining); + + if (remaining > 0) { + log("[Debug][BundleCheck] *** EXTRA BYTES FOUND: " + remaining + " bytes ***"); + + int savedIdx = buf.readerIndex(); + + byte[] extra = new byte[remaining]; + buf.readBytes(extra); + + if (remaining >= 1) { + int possiblePacketId = extra[0] & 0xFF; + log("[Debug][BundleCheck] First extra byte (possible next packet ID): 0x" + Integer.toHexString(possiblePacketId)); + } + + // 打印前几个字节的详细信息 + int showBytes = Math.min(remaining, 32); + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < showBytes; i++) { + sb.append(String.format("%02X ", extra[i] & 0xFF)); + } + log("[Debug][BundleCheck] Extra bytes hex: " + sb.toString()); + + buf.readerIndex(savedIdx); + } + } + + // 关闭日志文件(在模组卸载时调用) + public static void closeLog() { + if (logWriter != null) { + logWriter.println("=== CrossMod Debug Log Ended at " + LocalDateTime.now().format(TIME_FORMAT) + " ==="); + logWriter.close(); + logWriter = null; + } + } +} \ No newline at end of file diff --git a/forge-mod/src/main/java/com/leisuretimedock/crossmod/util/ModLogger.java b/forge-mod/src/main/java/com/leisuretimedock/crossmod/util/ModLogger.java new file mode 100644 index 0000000..3ba64ac --- /dev/null +++ b/forge-mod/src/main/java/com/leisuretimedock/crossmod/util/ModLogger.java @@ -0,0 +1,324 @@ +package com.leisuretimedock.crossmod.util; + +import com.leisuretimedock.crossmod.config.CrossCommonModConfig; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.File; +import java.io.FileWriter; +import java.io.PrintWriter; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +/** + * 跨模组调试日志工具 + * 支持控制台输出和文件输出,可通过配置文件控制开关 + */ +public class ModLogger { + + private static final String PREFIX = "[CrossMod]"; + private static final String BUNDLE_PREFIX = "[CrossMod][Bundle]"; + private static final DateTimeFormatter TIME_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"); + + private static final Logger LOGGER = LogManager.getLogger("CrossMod"); + private static PrintWriter fileWriter = null; + private static boolean fileLoggingEnabled = false; + private static boolean initAttempted = false; + + /** + * 初始化文件日志输出 + */ + public static void initFileLogging() { + if (initAttempted) { + return; + } + initAttempted = true; + + CrossCommonModConfig config = CrossCommonModConfig.getInstance(); + + // 输出配置状态 + LOGGER.info(PREFIX + " Config - debugPackets: {}, debugBundleDelimiter: {}, logToFile: {}", + config.isDebugPackets(), config.isDebugBundleDelimiter(), config.isLogToFile()); + + if (!config.isLogToFile()) { + LOGGER.info(PREFIX + " File logging is disabled in config"); + return; + } + + String logPath = config.getLogFilePath(); + if (logPath == null || logPath.isEmpty()) { + logPath = "logs/crossmod-debug.log"; + } + + try { + File logFile = new File(logPath); + File parentDir = logFile.getParentFile(); + if (parentDir != null && !parentDir.exists()) { + boolean created = parentDir.mkdirs(); + LOGGER.info(PREFIX + " Created log directory: {} - {}", parentDir.getAbsolutePath(), created); + } + + // 测试文件是否可写 + boolean canWrite = logFile.exists() ? logFile.canWrite() : true; + LOGGER.info(PREFIX + " Log file path: {}, can write: {}", logFile.getAbsolutePath(), canWrite); + + // 追加模式打开文件 + fileWriter = new PrintWriter(new FileWriter(logFile, true), true); + fileLoggingEnabled = true; + + writeToFileOnly("========================================"); + writeToFileOnly("CrossMod Debug Log Started"); + writeToFileOnly("Time: " + LocalDateTime.now().format(TIME_FORMAT)); + writeToFileOnly("========================================"); + writeToFileOnly("Config: debugPackets=" + config.isDebugPackets() + + ", debugBundleDelimiter=" + config.isDebugBundleDelimiter()); + writeToFileOnly(""); + + LOGGER.info(PREFIX + " File logging enabled: " + logFile.getAbsolutePath()); + + } catch (Exception e) { + LOGGER.error(PREFIX + " Failed to enable file logging: " + e.getMessage(), e); + fileLoggingEnabled = false; + } + } + + /** + * 关闭文件日志 + */ + public static void closeFileLogging() { + if (fileWriter != null) { + writeToFileOnly("========================================"); + writeToFileOnly("CrossMod Debug Log Ended"); + writeToFileOnly("Time: " + LocalDateTime.now().format(TIME_FORMAT)); + writeToFileOnly("========================================"); + + fileWriter.close(); + fileWriter = null; + } + fileLoggingEnabled = false; + } + + /** + * 写入文件(不输出到控制台) + */ + private static void writeToFileOnly(String message) { + if (fileWriter != null) { + fileWriter.println(message); + } + } + + /** + * 格式化消息(将 {} 替换为参数) + * Log4j 风格:使用 {} 作为占位符 + */ + private static String formatMessage(String format, Object... args) { + if (args == null || args.length == 0) { + return format; + } + + String result = format; + for (Object arg : args) { + String argStr = arg != null ? arg.toString() : "null"; + result = result.replaceFirst("\\{\\}", argStr); + } + return result; + } + + /** + * 通用信息日志 + */ + public static void info(String message) { + LOGGER.info(PREFIX + " " + message); + if (fileWriter != null) { + fileWriter.println(PREFIX + " " + message); + } + } + + public static void info(String format, Object... args) { + String message = formatMessage(format, args); + info(message); + } + + /** + * 警告日志 + */ + public static void warn(String message) { + LOGGER.warn(PREFIX + " " + message); + if (fileWriter != null) { + fileWriter.println(PREFIX + " [WARN] " + message); + } + } + + public static void warn(String format, Object... args) { + String message = formatMessage(format, args); + warn(message); + } + + /** + * 错误日志(总是输出) + */ + public static void error(String message) { + LOGGER.error(PREFIX + " [ERROR] " + message); + if (fileWriter != null) { + fileWriter.println(PREFIX + " [ERROR] " + message); + } + } + + public static void error(String format, Object... args) { + String message = formatMessage(format, args); + error(message); + } + + /** + * 调试日志(受 debugPackets 配置控制) + */ + public static void debug(String message) { + if (!CrossCommonModConfig.getInstance().isDebugPackets()) { + return; + } + LOGGER.debug(PREFIX + " [DEBUG] " + message); + if (fileWriter != null) { + fileWriter.println(PREFIX + " [DEBUG] " + message); + } + } + + public static void debug(String format, Object... args) { + if (!CrossCommonModConfig.getInstance().isDebugPackets()) { + return; + } + String message = formatMessage(format, args); + debug(message); + } + + /** + * Bundle 专用调试日志(受 debugBundleDelimiter 配置控制) + */ + public static void bundle(String message) { + if (!CrossCommonModConfig.getInstance().isDebugBundleDelimiter()) { + return; + } + String formatted = BUNDLE_PREFIX + " " + message; + LOGGER.debug(formatted); + if (fileWriter != null) { + fileWriter.println(formatted); + fileWriter.flush(); // 确保立即写入 + } + } + + public static void bundle(String format, Object... args) { + if (!CrossCommonModConfig.getInstance().isDebugBundleDelimiter()) { + return; + } + String message = formatMessage(format, args); + bundle(message); + } + + /** + * 打印十六进制数据(Bundle 调试用) + */ + public static void bundleHex(String title, byte[] data, int offset, int length) { + if (!CrossCommonModConfig.getInstance().isDebugBundleDelimiter()) { + return; + } + + int len = Math.min(length, data.length - offset); + if (len <= 0) { + bundle("{}: (empty)", title); + return; + } + + StringBuilder hex = new StringBuilder(); + StringBuilder ascii = new StringBuilder(); + + for (int i = 0; i < len; i++) { + byte b = data[offset + i]; + hex.append(String.format("%02X ", b & 0xFF)); + char c = (char) (b & 0xFF); + ascii.append((c >= 32 && c < 127) ? c : '.'); + + // 每16字节换行 + if ((i + 1) % 16 == 0 && i + 1 < len) { + bundle("{}: {}", title, hex.toString()); + bundle("{} {}", title, ascii.toString()); + hex.setLength(0); + ascii.setLength(0); + } + } + + if (hex.length() > 0) { + bundle("{}: {}", title, hex.toString()); + bundle("{} {}", title, ascii.toString()); + } + } + + /** + * 打印堆栈跟踪(Bundle 调试用) + */ + public static void bundleStackTrace() { + if (!CrossCommonModConfig.getInstance().isDebugBundleDelimiter()) { + return; + } + + bundle("Stack trace:"); + StackTraceElement[] stack = Thread.currentThread().getStackTrace(); + for (int i = 2; i < Math.min(stack.length, 12); i++) { + bundle(" at {}", stack[i].toString()); + } + } + + /** + * 打印分隔线(Bundle 调试用) + */ + public static void bundleSeparator() { + bundle("========================================"); + } + + /** + * 打印带标题的分隔线 + */ + public static void bundleSection(String title) { + bundle(""); + bundle("========== " + title + " =========="); + } + + public static void bundleVerbose(String message) { + if (!CrossCommonModConfig.getInstance().isDebugBundleDelimiter()) { + return; + } + if (CrossCommonModConfig.getInstance().isDebugPackets()) { + String formatted = BUNDLE_PREFIX + " [VERBOSE] " + message; + LOGGER.debug(formatted); + if (fileWriter != null) { + fileWriter.println(formatted); + } + } + } + + public static void bundleVerbose(String format, Object... args) { + String message = formatMessage(format, args); + bundleVerbose(message); + } + + /** + * 强制写入日志(忽略配置开关,用于关键错误) + */ + public static void force(String message) { + LOGGER.error(PREFIX + " [FORCE] " + message); + if (fileWriter != null) { + fileWriter.println(PREFIX + " [FORCE] " + message); + fileWriter.flush(); + } + } + + public static void force(String format, Object... args) { + String message = formatMessage(format, args); + force(message); + } + + /** + * 检查文件日志是否可用(用于调试) + */ + public static boolean isFileLoggingEnabled() { + return fileLoggingEnabled; + } +} \ No newline at end of file diff --git a/forge-mod/src/main/resources/assets/ltdcrossteleport/lang/en_us.json b/forge-mod/src/main/resources/assets/ltdcrossteleport/lang/en_us.json index 88208ce..d59e8cd 100644 --- a/forge-mod/src/main/resources/assets/ltdcrossteleport/lang/en_us.json +++ b/forge-mod/src/main/resources/assets/ltdcrossteleport/lang/en_us.json @@ -2,8 +2,11 @@ "ltd.mod.client.name.trans_server": "LTD Cross Server Mod", "ltd.mod.client.key": "Open LTD Cross Server Menu", "ltd.mod.client.menu": "Cross Server Menu", - "ltd.mod.client.menu.button.1": "H Hub", - "ltd.mod.client.menu.button.2": "S Survival", + "ltd.mod.client.menu.button.hub": "H Hub", + "ltd.mod.client.menu.button.survival": "S Survival", + "ltd.mod.client.menu.button.skyblock": "S SkyBlock", + "ltd.mod.client.menu.button.resource": "R Resource", + "ltd.mod.client.menu.button.minigame": "M MiniGame", "ltd.mod.client.menu.checkbox.show_trans_tip": "Show cross server tip overlay", "ltd.mod.client.menu.checkbox.show_ping_stat": "Show ping stat overlay", "ltd.mod.client.negotiating": "Negotiating...", @@ -38,6 +41,21 @@ "ltd.mod.ping.stats.avg_latency": "Average delay: %.1fms", "ltd.mod.ping.stats.packet_loss": "Packet loss rate: %.1f%%", "ltd.mod.ping.stats.sample_count": "Sample count: %d", - "ltd.mod.ping.warn.network_latency": "Network latency is high, so it is recommended to reduce the ping frequency" + "ltd.mod.ping.warn.network_latency": "Network latency is high, so it is recommended to reduce the ping frequency", + "ltd.mod.client.menu.command.header": "[=== LTD Cross Server Menu ===]", + "ltd.mod.client.menu.command.hover": "Click to teleport to %s", + "crossmod.command.debug.packets.enabled": "[CrossMod] Packet debugging enabled", + "crossmod.command.debug.packets.disabled": "[CrossMod] Packet debugging disabled", + "crossmod.command.debug.bundledebug.enabled": "[CrossMod] BundleDelimiter debugging enabled", + "crossmod.command.debug.bundledebug.disabled": "[CrossMod] BundleDelimiter debugging disabled", + "crossmod.command.debug.status.title": "[CrossMod] Debug status:", + "crossmod.command.debug.status.packet": "Packet Debug: ", + "crossmod.command.debug.status.bundledebug": "BundleDelimiter Debug: ", + "crossmod.command.debug.status.maxbytes": "Max bytes: ", + "crossmod.command.debug.status.logfile": "Log to file: ", + "crossmod.command.debug.enabled": "Enabled", + "crossmod.command.debug.disabled": "Disabled", + "crossmod.command.debug.enabled_short": "§aEnabled", + "crossmod.command.debug.disabled_short": "§cDisabled" } \ No newline at end of file diff --git a/forge-mod/src/main/resources/assets/ltdcrossteleport/lang/zh_cn.json b/forge-mod/src/main/resources/assets/ltdcrossteleport/lang/zh_cn.json index 7b6e98e..e531892 100644 --- a/forge-mod/src/main/resources/assets/ltdcrossteleport/lang/zh_cn.json +++ b/forge-mod/src/main/resources/assets/ltdcrossteleport/lang/zh_cn.json @@ -2,8 +2,11 @@ "ltd.mod.client.name.trans_server": "LTD跨服传送模组", "ltd.mod.client.key": "打开LTD跨服传送菜单", "ltd.mod.client.menu": "跨服菜单", - "ltd.mod.client.menu.button.1": "H 主城", - "ltd.mod.client.menu.button.2": "S 生存服", + "ltd.mod.client.menu.button.hub": "H 主城", + "ltd.mod.client.menu.button.survival": "S 生存服", + "ltd.mod.client.menu.button.skyblock": "S 空岛服", + "ltd.mod.client.menu.button.resource": "R 资源服", + "ltd.mod.client.menu.button.minigame": "M 小游戏服", "ltd.mod.client.menu.checkbox.show_trans_tip": "显示传送提示", "ltd.mod.client.menu.checkbox.show_ping_stat": "显示Ping状态", "ltd.mod.client.negotiating": "重定向中 ...", @@ -38,5 +41,20 @@ "ltd.mod.ping.stats.sample_count": "样本数量: %d", "ltd.mod.ping.warn.network_latency": "网络延迟较高,建议减少Ping频率", "ltd.mod.client.menu.button.no_servers": "没有服务器", - "ltd.mod.client.overlay.tip": "按 [%s] 打开跨服传送菜单" + "ltd.mod.client.overlay.tip": "按 [%s] 打开跨服传送菜单", + "ltd.mod.client.menu.command.header": "[=== LTD 跨服传送菜单 ===]", + "ltd.mod.client.menu.command.hover": "点击传送到 %s", + "crossmod.command.debug.packets.enabled": "[CrossMod] 数据包调试已开启", + "crossmod.command.debug.packets.disabled": "[CrossMod] 数据包调试已关闭", + "crossmod.command.debug.bundledebug.enabled": "[CrossMod] 捆绑包调试已开启", + "crossmod.command.debug.bundledebug.disabled": "[CrossMod] 捆绑包调试已关闭", + "crossmod.command.debug.status.title": "[CrossMod] 调试状态:", + "crossmod.command.debug.status.packet": "数据包调试: ", + "crossmod.command.debug.status.bundledebug": "捆绑包调试: ", + "crossmod.command.debug.status.maxbytes": "最大字节数: ", + "crossmod.command.debug.status.logfile": "日志输出到文件: ", + "crossmod.command.debug.enabled": "已开启", + "crossmod.command.debug.disabled": "已关闭", + "crossmod.command.debug.enabled_short": "§a已开启", + "crossmod.command.debug.disabled_short": "§c已关闭" } \ No newline at end of file diff --git a/forge-mod/src/main/resources/ltdcrossteleport.mixins.json b/forge-mod/src/main/resources/ltdcrossteleport.mixins.json index 8f8131a..62475bb 100644 --- a/forge-mod/src/main/resources/ltdcrossteleport.mixins.json +++ b/forge-mod/src/main/resources/ltdcrossteleport.mixins.json @@ -6,8 +6,12 @@ "refmap": "ltdcrossteleport.refmap.json", "mixins": [ "AccessorMinecraft", + "MixinConnection", "MixinMUINetWorkHandler", - "ModListSpoofMixin" + "MixinPacketDecoder", + "MixinPlayerList", + "ModListSpoofMixin", + "MixinBundlePacket" ], "minVersion": "0.8", "injectors": { diff --git a/velocity-plugin/build.gradle b/velocity-plugin/build.gradle index 7eeb6a3..2c7e419 100644 --- a/velocity-plugin/build.gradle +++ b/velocity-plugin/build.gradle @@ -1,6 +1,6 @@ plugins { id 'java' - id 'com.github.johnrengelman.shadow' version '7.1.2' + id 'com.github.johnrengelman.shadow' version '8.1.1' id("xyz.jpenilla.run-velocity") version "2.3.1" }