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 extends String> 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