添加更多指令和调试内容

This commit is contained in:
叁玖领域 2026-06-06 03:01:10 +08:00
parent e5e46b3ee9
commit d7f222442c
47 changed files with 1303 additions and 121 deletions

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AndroidProjectSystem">
<option name="providerId" value="com.android.tools.idea.GradleProjectSystem" />
</component>
</project>

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ClaudeCodeTabState">
<option name="tabSessions">
<map>
<entry key="0">
<value>
<TabSessionState>
<option name="provider" value="claude" />
<option name="sessionId" value="a3c423d7-ec3d-461e-8608-464d7db16154" />
<option name="cwd" value="$PROJECT_DIR$" />
<option name="model" value="claude-sonnet-4-6[1m]" />
<option name="permissionMode" value="acceptEdits" />
<option name="reasoningEffort" value="high" />
</TabSessionState>
</value>
</entry>
</map>
</option>
</component>
</project>

View File

@ -6,7 +6,6 @@
<GradleProjectSettings> <GradleProjectSettings>
<option name="distributionType" value="LOCAL" /> <option name="distributionType" value="LOCAL" />
<option name="externalProjectPath" value="$PROJECT_DIR$" /> <option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleHome" value="$PROJECT_DIR$/../../../projEnv/gradle/gradle-8.12" />
<option name="modules"> <option name="modules">
<set> <set>
<option value="$PROJECT_DIR$" /> <option value="$PROJECT_DIR$" />
@ -14,6 +13,7 @@
<option value="$PROJECT_DIR$/velocity-plugin" /> <option value="$PROJECT_DIR$/velocity-plugin" />
</set> </set>
</option> </option>
<option name="resolveExternalAnnotations" value="true" />
</GradleProjectSettings> </GradleProjectSettings>
</option> </option>
</component> </component>

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="MaterialThemeProjectNewConfig">
<option name="metadata">
<MTProjectMetadataState>
<option name="migrated" value="true" />
<option name="pristineConfig" value="false" />
<option name="userId" value="-ef169b8:19dcecdb453:-7ffe" />
</MTProjectMetadataState>
</option>
</component>
</project>

6
SY.md Normal file
View File

@ -0,0 +1,6 @@
[=== LTD Cross Server Menu ==]
/空
1. [xxx服务器]
/空
2. [xxx服务器]
...

View File

@ -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. # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default.
mod_license=MIT mod_license=MIT
# The mod version. See https://semver.org/ # 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. # 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. # This should match the base package used for the mod sources.
# See https://maven.apache.org/guides/mini/guide-naming-conventions.html # See https://maven.apache.org/guides/mini/guide-naming-conventions.html

View File

@ -1,5 +1,6 @@
package com.leisuretimedock.crossmod; package com.leisuretimedock.crossmod;
import com.leisuretimedock.crossmod.command.CrossModDebugCommand;
import com.leisuretimedock.crossmod.command.GotoServerCommand; import com.leisuretimedock.crossmod.command.GotoServerCommand;
import com.leisuretimedock.crossmod.config.CrossServerConfig; import com.leisuretimedock.crossmod.config.CrossServerConfig;
import com.leisuretimedock.crossmod.config.CrossServerConfigManager; 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.NetworkHandler;
import com.leisuretimedock.crossmod.network.PingRequestManager; import com.leisuretimedock.crossmod.network.PingRequestManager;
import com.leisuretimedock.crossmod.reset.ClientResetManager; import com.leisuretimedock.crossmod.reset.ClientResetManager;
import com.leisuretimedock.crossmod.util.ModLogger;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import net.minecraft.server.MinecraftServer; import net.minecraft.server.MinecraftServer;
import net.minecraft.server.level.ServerPlayer; 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.javafmlmod.FMLJavaModLoadingContext;
import net.minecraftforge.fml.loading.FMLEnvironment; import net.minecraftforge.fml.loading.FMLEnvironment;
import net.minecraftforge.network.NetworkConstants; import net.minecraftforge.network.NetworkConstants;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.Objects; import java.util.Objects;
@ -45,25 +48,32 @@ public class CrossTeleportMod {
ModLoadingContext.get().registerConfig(ModConfig.Type.SERVER, CrossServerConfig.SPEC, "cross-server.toml"); ModLoadingContext.get().registerConfig(ModConfig.Type.SERVER, CrossServerConfig.SPEC, "cross-server.toml");
if(!FMLEnvironment.dist.isDedicatedServer()) modEventBus.addListener(ClientResetManager::init); if(!FMLEnvironment.dist.isDedicatedServer()) modEventBus.addListener(ClientResetManager::init);
NetworkHandler.register(); 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) @Mod.EventBusSubscriber(modid = MOD_ID, bus = Mod.EventBusSubscriber.Bus.FORGE)
public static class CommonEvents { public static class CommonEvents {
@Nullable @Nullable
public static MinecraftServer server; public static MinecraftServer server;
@SubscribeEvent @SubscribeEvent
public static void onRegisterCommands(RegisterCommandsEvent event) { public static void onRegisterCommands(@NotNull RegisterCommandsEvent event) {
PingCommand.register(event.getDispatcher()); PingCommand.register(event.getDispatcher());
GotoServerCommand.register(event.getDispatcher()); GotoServerCommand.register(event.getDispatcher());
CrossModDebugCommand.register(event.getDispatcher());
} }
@SubscribeEvent @SubscribeEvent
public static void onPlayerLoggedIn(PlayerEvent.PlayerLoggedInEvent event) { public static void onPlayerLoggedIn(PlayerEvent.@NotNull PlayerLoggedInEvent event) {
if (event.getEntity() instanceof ServerPlayer player) { if (event.getEntity() instanceof ServerPlayer player) {
PingRequestManager.monitor(player); PingRequestManager.monitor(player);
} }
} }
private static int tickCounter = 0; private static int tickCounter = 0;
@SubscribeEvent @SubscribeEvent
public static void onServerTick(TickEvent.ServerTickEvent event) { public static void onServerTick(TickEvent.@NotNull ServerTickEvent event) {
if (event.phase == TickEvent.Phase.END) { if (event.phase == TickEvent.Phase.END) {
tickCounter++; tickCounter++;
if (tickCounter % 10 == 0) { if (tickCounter % 10 == 0) {
@ -72,13 +82,13 @@ public class CrossTeleportMod {
} }
} }
@SubscribeEvent @SubscribeEvent
public static void onPlayerLoggedOut(PlayerEvent.PlayerLoggedOutEvent event) { public static void onPlayerLoggedOut(PlayerEvent.@NotNull PlayerLoggedOutEvent event) {
if (event.getEntity() instanceof ServerPlayer player) { if (event.getEntity() instanceof ServerPlayer player) {
PingRequestManager.unmonitor(player); PingRequestManager.unmonitor(player);
} }
} }
@SubscribeEvent @SubscribeEvent
public static void onServerStart(ServerStartedEvent event) { public static void onServerStart(@NotNull ServerStartedEvent event) {
server = event.getServer(); server = event.getServer();
} }
@SubscribeEvent @SubscribeEvent
@ -114,7 +124,7 @@ public class CrossTeleportMod {
* @param event the event * @param event the event
*/ */
@SubscribeEvent @SubscribeEvent
public static void onConfigLoaded(ModConfigEvent.Loading event) { public static void onConfigLoaded(ModConfigEvent.@NotNull Loading event) {
if (event.getConfig().getSpec() == CrossServerConfig.SPEC) { if (event.getConfig().getSpec() == CrossServerConfig.SPEC) {
CrossServerConfigManager.loading(CrossServerConfigManager.INSTANCE); CrossServerConfigManager.loading(CrossServerConfigManager.INSTANCE);
} }
@ -126,7 +136,7 @@ public class CrossTeleportMod {
* @param event the event * @param event the event
*/ */
@SubscribeEvent @SubscribeEvent
public static void onConfigReloaded(ModConfigEvent.Reloading event) { public static void onConfigReloaded(ModConfigEvent.@NotNull Reloading event) {
if (event.getConfig().getSpec() == CrossServerConfig.SPEC) { if (event.getConfig().getSpec() == CrossServerConfig.SPEC) {
CrossServerConfigManager.reloading(CrossServerConfigManager.INSTANCE); CrossServerConfigManager.reloading(CrossServerConfigManager.INSTANCE);
} }
@ -138,7 +148,7 @@ public class CrossTeleportMod {
* @param event the event * @param event the event
*/ */
@SubscribeEvent @SubscribeEvent
public static void onConfigUnloaded(ModConfigEvent.Unloading event) { public static void onConfigUnloaded(ModConfigEvent.@NotNull Unloading event) {
if (event.getConfig().getSpec() == CrossServerConfig.SPEC) { if (event.getConfig().getSpec() == CrossServerConfig.SPEC) {
CrossServerConfigManager.unloading(CrossServerConfigManager.INSTANCE); CrossServerConfigManager.unloading(CrossServerConfigManager.INSTANCE);
} }

View File

@ -14,7 +14,7 @@ public class ClientPingHandler {
private static PingRequestManager.PingStats lastStats; private static PingRequestManager.PingStats lastStats;
private static long lastStatsUpdateTime; private static long lastStatsUpdateTime;
public static void handlePingResults(Map<UUID, Long> pingResults, Map<UUID, Double> averages) { public static void handlePingResults(@NotNull Map<UUID, Long> pingResults, Map<UUID, Double> averages) {
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();
pingResults.forEach((uuid, ping) -> { pingResults.forEach((uuid, ping) -> {
@ -29,7 +29,7 @@ public class ClientPingHandler {
); );
} }
public static String getPingDisplayText() { public static @NotNull String getPingDisplayText() {
Minecraft mc = Minecraft.getInstance(); Minecraft mc = Minecraft.getInstance();
if (mc.level == null) return ""; 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) { if (lastStats == null || System.currentTimeMillis() - lastStatsUpdateTime > 10000) {
return "网络统计: 数据过期"; return "网络统计: 数据过期";
} }

View File

@ -9,20 +9,21 @@ import net.minecraftforge.client.event.RegisterKeyMappingsEvent;
import net.minecraftforge.event.TickEvent; import net.minecraftforge.event.TickEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.Mod;
import org.jetbrains.annotations.NotNull;
import org.lwjgl.glfw.GLFW; import org.lwjgl.glfw.GLFW;
@Mod.EventBusSubscriber(modid = CrossTeleportMod.MOD_ID, bus = Mod.EventBusSubscriber.Bus.MOD, value = Dist.CLIENT) @Mod.EventBusSubscriber(modid = CrossTeleportMod.MOD_ID, bus = Mod.EventBusSubscriber.Bus.MOD, value = Dist.CLIENT)
public class KeyBindingHandler { 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"); public static final KeyMapping OPEN_GUI_KEY = new KeyMapping("ltd.mod.client.name.trans_server", GLFW.GLFW_KEY_HOME, "ltd.mod.client.key");
@SubscribeEvent @SubscribeEvent
public static void onRegisterKeyMappingsEvent (RegisterKeyMappingsEvent event) { public static void onRegisterKeyMappingsEvent (@NotNull RegisterKeyMappingsEvent event) {
event.register(OPEN_GUI_KEY); event.register(OPEN_GUI_KEY);
} }
@Mod.EventBusSubscriber(modid = CrossTeleportMod.MOD_ID, value = Dist.CLIENT) @Mod.EventBusSubscriber(modid = CrossTeleportMod.MOD_ID, value = Dist.CLIENT)
public static class KeyHandler { public static class KeyHandler {
@SubscribeEvent @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()) { if (event.phase == TickEvent.Phase.END && OPEN_GUI_KEY.consumeClick()) {
Minecraft.getInstance().setScreen(new CrossServerGui()); Minecraft.getInstance().setScreen(new CrossServerGui());
} }

View File

@ -2,6 +2,7 @@ package com.leisuretimedock.crossmod.client;
import com.leisuretimedock.crossmod.CrossTeleportMod; import com.leisuretimedock.crossmod.CrossTeleportMod;
import com.leisuretimedock.crossmod.client.command.GotoCommand; 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.CrossServerTipOverLay;
import com.leisuretimedock.crossmod.client.overlay.PluginCommand; import com.leisuretimedock.crossmod.client.overlay.PluginCommand;
import com.leisuretimedock.crossmod.network.NetworkHandler; 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.client.event.RegisterClientCommandsEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.Mod;
import org.jetbrains.annotations.NotNull;
import java.util.Objects; import java.util.Objects;
@Slf4j @Slf4j
@ -87,7 +89,7 @@ public class PluginChannelClient {
@SubscribeEvent @SubscribeEvent
public static void onLogout(ClientPlayerNetworkEvent.LoggingOut event) { public static void onLogout(ClientPlayerNetworkEvent.@NotNull LoggingOut event) {
log.debug("[CrossTeleportMod] 玩家注销事件触发"); log.debug("[CrossTeleportMod] 玩家注销事件触发");
Connection connection = event.getConnection(); Connection connection = event.getConnection();
@ -110,7 +112,8 @@ public class PluginChannelClient {
} }
@SubscribeEvent @SubscribeEvent
public static void onRegisterCommand(RegisterClientCommandsEvent event) { public static void onRegisterCommand(@NotNull RegisterClientCommandsEvent event) {
GotoCommand.register(event.getDispatcher()); GotoCommand.register(event.getDispatcher());
ServerMenuCommand.register(event.getDispatcher());
} }
} }

View File

@ -7,9 +7,10 @@ import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.CommandSourceStack;
import net.minecraft.commands.Commands; import net.minecraft.commands.Commands;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import org.jetbrains.annotations.NotNull;
public class GotoCommand { public class GotoCommand {
public static void register(CommandDispatcher<CommandSourceStack> dispatcher) { public static void register(@NotNull CommandDispatcher<CommandSourceStack> dispatcher) {
LiteralArgumentBuilder<CommandSourceStack> main = Commands.literal("goto") LiteralArgumentBuilder<CommandSourceStack> main = Commands.literal("goto")
.then(Commands.argument("server", StringArgumentType.string()) .then(Commands.argument("server", StringArgumentType.string())
.executes(ctx -> { .executes(ctx -> {

View File

@ -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<CommandSourceStack> dispatcher) {
LiteralArgumentBuilder<CommandSourceStack> 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<CommandSourceStack> ctx) {
Minecraft instance = Minecraft.getInstance();
instance.setScreen(new CrossServerGui());
return 0;
}
public static int msgMenu(@NotNull CommandContext<CommandSourceStack> ctx) {
Map<String, String> 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;
}
}

View File

@ -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 logoWidth = 64; // 缩小Logo为列表腾出空间
int logoHeight = 64; int logoHeight = 64;

View File

@ -37,7 +37,7 @@ public class GenericIceMessageScreen extends GenericDirtMessageScreen {
} }
@SuppressWarnings("UnstableApiUsage") @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.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.blit(ICE, 0, 0, 0, 0.0F, 0.0F, this.width, this.height, 32, 32);
guiGraphics.setColor(r, g, b, a); guiGraphics.setColor(r, g, b, a);

View File

@ -12,7 +12,7 @@ import java.util.Map;
public class ServerSelectionList extends ObjectSelectionList<ServerSelectionList.ServerEntry> { public class ServerSelectionList extends ObjectSelectionList<ServerSelectionList.ServerEntry> {
private final CrossServerGui parentScreen; private final CrossServerGui parentScreen;
public ServerSelectionList(CrossServerGui parent, Minecraft mc, int width, int height, int y0, int y1, int itemHeight, Map<String, String> servers) { public ServerSelectionList(CrossServerGui parent, Minecraft mc, int width, int height, int y0, int y1, int itemHeight, @NotNull Map<String, String> servers) {
super(mc, width, height, y0, y1, itemHeight); super(mc, width, height, y0, y1, itemHeight);
this.parentScreen = parent; this.parentScreen = parent;

View File

@ -5,12 +5,13 @@ import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.client.event.RegisterGuiOverlaysEvent; import net.minecraftforge.client.event.RegisterGuiOverlaysEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.fml.common.Mod; 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) @Mod.EventBusSubscriber(modid = CrossTeleportMod.MOD_ID, value = Dist.CLIENT, bus = Mod.EventBusSubscriber.Bus.MOD)
public class OverlayRenderer { public class OverlayRenderer {
@SubscribeEvent @SubscribeEvent
public static void onRender(RegisterGuiOverlaysEvent event) { public static void onRender(@NotNull RegisterGuiOverlaysEvent event) {
event.registerAboveAll("cross_server_tip", CrossServerTipOverLay.INSTANCE); event.registerAboveAll("cross_server_tip", CrossServerTipOverLay.INSTANCE);
event.registerAboveAll( event.registerAboveAll(
"ping_debug", "ping_debug",

View File

@ -6,6 +6,7 @@ import net.minecraft.client.gui.Font;
import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.GuiGraphics;
import net.minecraftforge.client.gui.overlay.ForgeGui; import net.minecraftforge.client.gui.overlay.ForgeGui;
import net.minecraftforge.client.gui.overlay.IGuiOverlay; import net.minecraftforge.client.gui.overlay.IGuiOverlay;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -55,7 +56,7 @@ public class PingOverlayManager implements IGuiOverlay {
drawTextLines(guiGraphics, font, allLines, x, y); drawTextLines(guiGraphics, font, allLines, x, y);
} }
private List<String> getAllDisplayLines() { private @NotNull List<String> getAllDisplayLines() {
List<String> lines = new ArrayList<>(); List<String> lines = new ArrayList<>();
// 添加Ping信息 // 添加Ping信息
@ -74,7 +75,7 @@ public class PingOverlayManager implements IGuiOverlay {
return lines; return lines;
} }
private int getMaxLineWidth(Font font, List<String> lines) { private int getMaxLineWidth(@NotNull Font font, @NotNull List<String> lines) {
return lines.stream() return lines.stream()
.mapToInt(font::width) .mapToInt(font::width)
.max() .max()
@ -94,14 +95,14 @@ public class PingOverlayManager implements IGuiOverlay {
return baseY; 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( guiGraphics.fill(
x - PADDING, y - PADDING, x - PADDING, y - PADDING,
x + width + PADDING, y + height + PADDING, x + width + PADDING, y + height + PADDING,
BACKGROUND_COLOR); BACKGROUND_COLOR);
} }
private void drawTextLines(GuiGraphics guiGraphics, Font font, List<String> lines, int x, int y) { private void drawTextLines(GuiGraphics guiGraphics, Font font, @NotNull List<String> lines, int x, int y) {
for (int i = 0; i < lines.size(); i++) { for (int i = 0; i < lines.size(); i++) {
String line = lines.get(i); String line = lines.get(i);
if (!line.isEmpty()) { if (!line.isEmpty()) {

View File

@ -1,5 +1,7 @@
package com.leisuretimedock.crossmod.client.overlay; package com.leisuretimedock.crossmod.client.overlay;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays; import java.util.Arrays;
import java.util.Optional; import java.util.Optional;
@ -11,7 +13,7 @@ public enum PluginCommand {
PluginCommand(String id) { this.id = id; } PluginCommand(String id) { this.id = id; }
public static Optional<PluginCommand> fromId(String id) { public static @NotNull Optional<PluginCommand> fromId(String id) {
return Arrays.stream(values()).filter(cmd -> cmd.id.equals(id)).findFirst(); return Arrays.stream(values()).filter(cmd -> cmd.id.equals(id)).findFirst();
} }
} }

View File

@ -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<CommandSourceStack> 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");
}
}
}

View File

@ -10,14 +10,16 @@ import net.minecraft.commands.Commands;
import net.minecraft.commands.arguments.EntityArgument; import net.minecraft.commands.arguments.EntityArgument;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.level.ServerPlayer;
import org.jetbrains.annotations.NotNull;
import java.util.Collection; import java.util.Collection;
public class GotoServerCommand { public class GotoServerCommand {
public static void register(CommandDispatcher<CommandSourceStack> dispatcher) { public static void register(@NotNull CommandDispatcher<CommandSourceStack> dispatcher) {
LiteralArgumentBuilder<CommandSourceStack> main = Commands.literal("cross") LiteralArgumentBuilder<CommandSourceStack> main = Commands.literal("cross")
.then(
Commands.argument("players", EntityArgument.players())
.requires(cs -> cs.hasPermission(2)) .requires(cs -> cs.hasPermission(2))
.then(Commands.argument("players", EntityArgument.players())
.then(Commands.literal("goto") .then(Commands.literal("goto")
.then(Commands.argument("server", StringArgumentType.string()) .then(Commands.argument("server", StringArgumentType.string())
.executes(ctx -> { .executes(ctx -> {
@ -41,6 +43,7 @@ public class GotoServerCommand {
NetworkHandler.sendToPlayer(new GotoServerPayload(server), player); NetworkHandler.sendToPlayer(new GotoServerPayload(server), player);
source.sendSuccess( source.sendSuccess(
() -> Component.translatable("ltd.mod.client.request.goto",server), false); () -> Component.translatable("ltd.mod.client.request.goto",server), false);
return 0;
} }
source.sendFailure(Component.literal("Request a player")); source.sendFailure(Component.literal("Request a player"));
return 1; return 1;

View File

@ -13,6 +13,7 @@ import net.minecraft.commands.Commands;
import net.minecraft.commands.arguments.EntityArgument; import net.minecraft.commands.arguments.EntityArgument;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.level.ServerPlayer;
import org.jetbrains.annotations.NotNull;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
@ -20,7 +21,7 @@ import java.util.Map;
import java.util.UUID; import java.util.UUID;
public class PingCommand { public class PingCommand {
public static void register(CommandDispatcher<CommandSourceStack> dispatcher) { public static void register(@NotNull CommandDispatcher<CommandSourceStack> dispatcher) {
LiteralArgumentBuilder<CommandSourceStack> networkping = LiteralArgumentBuilder<CommandSourceStack> networkping =
Commands.literal("netping") Commands.literal("netping")
.requires(source -> source.hasPermission(2)) .requires(source -> source.hasPermission(2))
@ -90,7 +91,7 @@ public class PingCommand {
dispatcher.register(networkping); dispatcher.register(networkping);
} }
private static int executePlayerReport(CommandSourceStack source, Collection<ServerPlayer> players) throws CommandSyntaxException { private static int executePlayerReport(CommandSourceStack source, @NotNull Collection<ServerPlayer> players) throws CommandSyntaxException {
if (players.isEmpty()) { if (players.isEmpty()) {
source.sendSuccess(() -> Component.translatable("ltd.mod.ping.error.no_players"), false); source.sendSuccess(() -> Component.translatable("ltd.mod.ping.error.no_players"), false);
return 0; return 0;
@ -110,7 +111,7 @@ public class PingCommand {
return Command.SINGLE_SUCCESS; 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(); ServerPlayer player = source.getPlayerOrException();
Map<UUID, Long> results = PingRequestManager.getAllLatestPings(); Map<UUID, Long> results = PingRequestManager.getAllLatestPings();
@ -129,7 +130,7 @@ public class PingCommand {
return Command.SINGLE_SUCCESS; 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(); ServerPlayer player = source.getPlayerOrException();
PingRequestManager.PingStats stats = PingRequestManager.getGlobalPingStats(); PingRequestManager.PingStats stats = PingRequestManager.getGlobalPingStats();
@ -144,7 +145,7 @@ public class PingCommand {
return Command.SINGLE_SUCCESS; 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(); ServerPlayer player = source.getPlayerOrException();
if(!PingRequestManager.isMonitored(player.getUUID())) { if(!PingRequestManager.isMonitored(player.getUUID())) {
source.sendFailure(Component.translatable("ltd.mod.ping.error.not_monitored.self")); source.sendFailure(Component.translatable("ltd.mod.ping.error.not_monitored.self"));
@ -155,7 +156,7 @@ public class PingCommand {
return Command.SINGLE_SUCCESS; return Command.SINGLE_SUCCESS;
} }
private static int executePingPlayers(CommandSourceStack source, Collection<ServerPlayer> players) throws CommandSyntaxException { private static int executePingPlayers(CommandSourceStack source, @NotNull Collection<ServerPlayer> players) throws CommandSyntaxException {
if (players.isEmpty()) { if (players.isEmpty()) {
source.sendSuccess(() -> Component.translatable("ltd.mod.ping.error.no_players"), false); source.sendSuccess(() -> Component.translatable("ltd.mod.ping.error.no_players"), false);
return 0; return 0;
@ -176,7 +177,7 @@ public class PingCommand {
return Command.SINGLE_SUCCESS; return Command.SINGLE_SUCCESS;
} }
private static int executeMultiPing(CommandSourceStack source, private static int executeMultiPing(CommandSourceStack source,
Collection<ServerPlayer> players, @NotNull Collection<ServerPlayer> players,
int count, int count,
int interval) { int interval) {
if (players.isEmpty()) { if (players.isEmpty()) {
@ -205,7 +206,7 @@ public class PingCommand {
return Command.SINGLE_SUCCESS; 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(); ServerPlayer player = source.getPlayerOrException();
if (monitor) { if (monitor) {
PingRequestManager.monitor(player); PingRequestManager.monitor(player);
@ -217,7 +218,7 @@ public class PingCommand {
return Command.SINGLE_SUCCESS; return Command.SINGLE_SUCCESS;
} }
private static int executeToggleMonitoring(CommandSourceStack source, Collection<ServerPlayer> players, boolean monitor) throws CommandSyntaxException { private static int executeToggleMonitoring(CommandSourceStack source, @NotNull Collection<ServerPlayer> players, boolean monitor) throws CommandSyntaxException {
if (players.isEmpty()) { if (players.isEmpty()) {
source.sendSuccess(() -> Component.translatable("ltd.mod.ping.error.no_players"), false); source.sendSuccess(() -> Component.translatable("ltd.mod.ping.error.no_players"), false);
return 0; return 0;
@ -238,7 +239,7 @@ public class PingCommand {
return players.size(); return players.size();
} }
private static void sendTextReport(ServerPlayer player, Map<UUID, Long> results) { private static void sendTextReport(@NotNull ServerPlayer player, @NotNull Map<UUID, Long> results) {
player.displayClientMessage(Component.translatable("ltd.mod.ping.title.report").withStyle(ChatFormatting.GOLD), player.displayClientMessage(Component.translatable("ltd.mod.ping.title.report").withStyle(ChatFormatting.GOLD),
true); 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), player.displayClientMessage(Component.translatable("ltd.mod.ping.title.stats").withStyle(ChatFormatting.GOLD),
true); true);
player.displayClientMessage(Component.translatable( player.displayClientMessage(Component.translatable(

View File

@ -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();
}
}

View File

@ -10,19 +10,21 @@ public class CrossServerConfig {
private static final ForgeConfigSpec.Builder BUILDER = new ForgeConfigSpec.Builder(); private static final ForgeConfigSpec.Builder BUILDER = new ForgeConfigSpec.Builder();
public static ForgeConfigSpec SPEC; public static ForgeConfigSpec SPEC;
public static final ForgeConfigSpec.ConfigValue<List<? extends String>> SERVER_LIST; public static final ForgeConfigSpec.ConfigValue<List<? extends String>> SERVER_LIST;
public static final ForgeConfigSpec.BooleanValue DISABLED_JOIN_QUIT_MESSAGE;
static { static {
BUILDER.comment("Cross Server Config").push("servers"); BUILDER.comment("Cross Server Config").push("servers");
SERVER_LIST = BUILDER SERVER_LIST = BUILDER
.comment("Server list in format: <server_name>: <translate_key>") .comment("Server list in format: <server_name>: <translate_key>")
.defineList("serverList", .defineList("serverList",
Arrays.asList( Arrays.asList(
"lobby: ltd.mod.client.menu.button.1", "lobby: ltd.mod.client.menu.button.hub",
"survival: ltd.mod.client.menu.button.2" "survival: ltd.mod.client.menu.button.survival"
), ),
obj -> obj instanceof String str && checkSyntax(str) obj -> obj instanceof String str && checkSyntax(str)
); );
BUILDER.pop(); BUILDER.pop();
DISABLED_JOIN_QUIT_MESSAGE = BUILDER.comment("Disable join or quit message")
.define("disabled_join_quit_message", false);
SPEC = BUILDER.build(); SPEC = BUILDER.build();
} }
public static boolean checkSyntax(@NotNull String input) { public static boolean checkSyntax(@NotNull String input) {

View File

@ -32,6 +32,9 @@ public class CrossServerConfigManager {
@Getter @Getter
private final Map<String, String> servers = new TreeMap<>(); private final Map<String, String> servers = new TreeMap<>();
@Getter
private boolean disabledJoinQuitMessage = false;
private @NotNull @Unmodifiable Map<String, String> parseServer(@NotNull List<? extends String> servers) { private @NotNull @Unmodifiable Map<String, String> parseServer(@NotNull List<? extends String> servers) {
Map<String, String> serverMap = new TreeMap<>(); Map<String, String> serverMap = new TreeMap<>();
for (String server : servers) { for (String server : servers) {
@ -53,6 +56,7 @@ public class CrossServerConfigManager {
try { try {
clear(); clear();
servers.putAll(parseServer(CrossServerConfig.SERVER_LIST.get())); servers.putAll(parseServer(CrossServerConfig.SERVER_LIST.get()));
disabledJoinQuitMessage = CrossServerConfig.DISABLED_JOIN_QUIT_MESSAGE.get();
cacheHash = -1; cacheHash = -1;
cacheTag = serializeToNBT(); cacheTag = serializeToNBT();
log.debug("Configs reloaded"); log.debug("Configs reloaded");

View File

@ -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<T extends PacketListener> implements Packet<T> {
@Shadow
public abstract Iterable<Packet<T>> 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<T> 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<T> 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);
}
}
}

View File

@ -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());
}
}
}

View File

@ -2,6 +2,7 @@ package com.leisuretimedock.crossmod.mixin;
import icyllis.modernui.mc.forge.NetworkHandler; import icyllis.modernui.mc.forge.NetworkHandler;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import org.jetbrains.annotations.NotNull;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Pseudo; import org.spongepowered.asm.mixin.Pseudo;
import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.At;
@ -17,7 +18,7 @@ public class MixinMUINetWorkHandler {
argsOnly = true, argsOnly = true,
ordinal = 0 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"); return name.getPath().isEmpty() ? name : new ResourceLocation(name.getNamespace(), "default");
} }

View File

@ -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<Object> 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<Object> 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());
}
}
}

View File

@ -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);
}
}
}

View File

@ -148,7 +148,7 @@ public class NetworkHandler {
* @param subChannel 子通道标识 * @param subChannel 子通道标识
* @param payload 负载数据字节数组 * @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)); FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer(payload.length));
buf.writeBytes(payload); buf.writeBytes(payload);
@ -201,7 +201,7 @@ public class NetworkHandler {
* @param entity the entity * @param entity the entity
* @param packetDistributor the packet distributor * @param packetDistributor the packet distributor
*/ */
public static <MSG, T> void sendToPlayer(MSG message, T entity, PacketDistributor<T> packetDistributor){ public static <MSG, T> void sendToPlayer(MSG message, T entity, @NotNull PacketDistributor<T> packetDistributor){
CHANNEL.send(packetDistributor.with(() -> entity), message); CHANNEL.send(packetDistributor.with(() -> entity), message);
} }
} }

View File

@ -5,6 +5,8 @@ import lombok.Getter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.level.ServerPlayer;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import java.util.*; import java.util.*;
import java.util.concurrent.*; import java.util.concurrent.*;
@ -210,7 +212,7 @@ public final class PingRequestManager {
/** /**
* 获取所有玩家的最新ping值 * 获取所有玩家的最新ping值
*/ */
public static Map<UUID, Long> getAllLatestPings() { public static @NotNull Map<UUID, Long> getAllLatestPings() {
Map<UUID, Long> results = new HashMap<>(); Map<UUID, Long> results = new HashMap<>();
playerData.forEach((uuid, data) -> { playerData.forEach((uuid, data) -> {
@ -228,7 +230,7 @@ public final class PingRequestManager {
* @param players 要查询的玩家集合 * @param players 要查询的玩家集合
* @return 包含玩家UUID和最新ping时间的Map * @return 包含玩家UUID和最新ping时间的Map
*/ */
public static Map<UUID, Long> getLatestPingsForPlayers(Collection<ServerPlayer> players) { public static @NotNull Map<UUID, Long> getLatestPingsForPlayers(@NotNull Collection<ServerPlayer> players) {
Map<UUID, Long> results = new HashMap<>(); Map<UUID, Long> results = new HashMap<>();
for (ServerPlayer player : players) { for (ServerPlayer player : players) {
@ -297,7 +299,7 @@ public final class PingRequestManager {
/** /**
* 取消该玩家的所有进行中的Ping请求 * 取消该玩家的所有进行中的Ping请求
*/ */
public static void cancelPings(ServerPlayer player) { public static void cancelPings(@NotNull ServerPlayer player) {
PlayerPingData data = playerData.get(player.getUUID()); PlayerPingData data = playerData.get(player.getUUID());
if (data != null) { if (data != null) {
synchronized (data) { synchronized (data) {
@ -342,7 +344,7 @@ public final class PingRequestManager {
PingStats globalStats = getGlobalPingStats(); PingStats globalStats = getGlobalPingStats();
NetworkHandler.sendPingReport(player, latestPings, averages, globalStats); 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); data.addPing(ping);
} }
@ -369,7 +371,8 @@ public final class PingRequestManager {
return PlayerPingData.calculate(pingHistory); return PlayerPingData.calculate(pingHistory);
} }
private static double calculatePacketLossRate(PlayerPingData data) { @Contract(pure = true)
private static double calculatePacketLossRate(@NotNull PlayerPingData data) {
synchronized (PingRequestManager.class) { synchronized (PingRequestManager.class) {
if (data.totalRequests == 0) return 0; if (data.totalRequests == 0) return 0;
return (1 - (double) data.successfulRequests / data.totalRequests) * 100; return (1 - (double) data.successfulRequests / data.totalRequests) * 100;
@ -403,7 +406,8 @@ public final class PingRequestManager {
return calculate(pings); return calculate(pings);
} }
static double calculate(Collection<Long> pings) { @Contract(pure = true)
static double calculate(@NotNull Collection<Long> pings) {
if (pings.isEmpty()) return 0; if (pings.isEmpty()) return 0;
double total = 0; double total = 0;

View File

@ -5,6 +5,8 @@ import com.leisuretimedock.crossmod.network.NetworkHandler;
import com.leisuretimedock.crossmod.network.toServer.SyncCommonConfigRequestPacket; import com.leisuretimedock.crossmod.network.toServer.SyncCommonConfigRequestPacket;
import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.FriendlyByteBuf;
import net.minecraftforge.network.NetworkEvent; import net.minecraftforge.network.NetworkEvent;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import java.util.function.Supplier; import java.util.function.Supplier;
@ -18,7 +20,7 @@ public record CommonConfigHashInformPacket(int hash) {
* @param packet the packet * @param packet the packet
* @param buffer the buffer * @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()); buffer.writeInt(packet.hash());
} }
@ -29,7 +31,8 @@ public record CommonConfigHashInformPacket(int hash) {
* @param buffer the buffer * @param buffer the buffer
* @return the common config hash inform packet * @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()); return new CommonConfigHashInformPacket(buffer.readInt());
} }
@ -39,7 +42,7 @@ public record CommonConfigHashInformPacket(int hash) {
* @param packet the packet * @param packet the packet
* @param ctx the ctx * @param ctx the ctx
*/ */
public static void handle(CommonConfigHashInformPacket packet, Supplier<NetworkEvent.Context> ctx) { public static void handle(CommonConfigHashInformPacket packet, @NotNull Supplier<NetworkEvent.Context> ctx) {
NetworkEvent.Context context = ctx.get(); NetworkEvent.Context context = ctx.get();
context.enqueueWork(() -> { context.enqueueWork(() -> {
int hash = CrossServerConfigManager.INSTANCE.calculateConfigHash(); int hash = CrossServerConfigManager.INSTANCE.calculateConfigHash();

View File

@ -5,6 +5,7 @@ import net.minecraft.network.FriendlyByteBuf;
import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.fml.DistExecutor; import net.minecraftforge.fml.DistExecutor;
import net.minecraftforge.network.NetworkEvent; import net.minecraftforge.network.NetworkEvent;
import org.jetbrains.annotations.NotNull;
import java.util.UUID; import java.util.UUID;
import java.util.function.Supplier; import java.util.function.Supplier;
@ -13,18 +14,18 @@ import static com.leisuretimedock.crossmod.network.NetworkHandler.CHANNEL;
//Server -> Client //Server -> Client
public record PingMessagePayload(UUID requestId) { 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().getMostSignificantBits());
buf.writeLong(payload.requestId().getLeastSignificantBits()); buf.writeLong(payload.requestId().getLeastSignificantBits());
} }
public static PingMessagePayload decode(FriendlyByteBuf buf) { public static @NotNull PingMessagePayload decode(@NotNull FriendlyByteBuf buf) {
long mostSignificantBits = buf.readLong(); long mostSignificantBits = buf.readLong();
long leastSignificantBits = buf.readLong(); long leastSignificantBits = buf.readLong();
return new PingMessagePayload(new UUID(mostSignificantBits, leastSignificantBits)); return new PingMessagePayload(new UUID(mostSignificantBits, leastSignificantBits));
} }
//客户端处理 //客户端处理
public static void handle(PingMessagePayload msg, Supplier<NetworkEvent.Context> ctx) { public static void handle(PingMessagePayload msg, @NotNull Supplier<NetworkEvent.Context> ctx) {
ctx.get().enqueueWork(() -> DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> () -> { ctx.get().enqueueWork(() -> DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> () -> {
// 客户端收到ping请求立即返回pong // 客户端收到ping请求立即返回pong
CHANNEL.sendToServer(new PongMessagePayload(msg.requestId)); CHANNEL.sendToServer(new PongMessagePayload(msg.requestId));

View File

@ -3,6 +3,8 @@ package com.leisuretimedock.crossmod.network.toClient;
import com.leisuretimedock.crossmod.client.ClientPingHandler; import com.leisuretimedock.crossmod.client.ClientPingHandler;
import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.FriendlyByteBuf;
import net.minecraftforge.network.NetworkEvent; import net.minecraftforge.network.NetworkEvent;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@ -19,7 +21,7 @@ public class PingResultPacket {
this.pingResults = new HashMap<>(pingResults); this.pingResults = new HashMap<>(pingResults);
this.averageLatencies = new HashMap<>(averageLatencies); this.averageLatencies = new HashMap<>(averageLatencies);
} }
public PingResultPacket(FriendlyByteBuf buf) { public PingResultPacket(@NotNull FriendlyByteBuf buf) {
this.timestamp = System.currentTimeMillis(); this.timestamp = System.currentTimeMillis();
int size = buf.readVarInt(); int size = buf.readVarInt();
this.pingResults = new HashMap<>(size); this.pingResults = new HashMap<>(size);
@ -32,11 +34,12 @@ public class PingResultPacket {
averageLatencies.put(uuid, avgLatency); averageLatencies.put(uuid, avgLatency);
} }
} }
public static PingResultPacket decode(FriendlyByteBuf buf) { @Contract("_ -> new")
public static @NotNull PingResultPacket decode(FriendlyByteBuf buf) {
return new PingResultPacket(buf); return new PingResultPacket(buf);
} }
public void encode(FriendlyByteBuf buf) { public void encode(@NotNull FriendlyByteBuf buf) {
buf.writeVarInt(pingResults.size()); buf.writeVarInt(pingResults.size());
pingResults.forEach((uuid, ping) -> { pingResults.forEach((uuid, ping) -> {
buf.writeUUID(uuid); buf.writeUUID(uuid);
@ -45,7 +48,7 @@ public class PingResultPacket {
}); });
} }
public void handle(Supplier<NetworkEvent.Context> ctx) { public void handle(@NotNull Supplier<NetworkEvent.Context> ctx) {
ctx.get().enqueueWork(() -> { ctx.get().enqueueWork(() -> {
// 检查数据时效性(5秒内有效) // 检查数据时效性(5秒内有效)
if (System.currentTimeMillis() - timestamp < 5000) { if (System.currentTimeMillis() - timestamp < 5000) {

View File

@ -4,6 +4,8 @@ import com.leisuretimedock.crossmod.client.ClientPingHandler;
import com.leisuretimedock.crossmod.network.PingRequestManager; import com.leisuretimedock.crossmod.network.PingRequestManager;
import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.FriendlyByteBuf;
import net.minecraftforge.network.NetworkEvent; import net.minecraftforge.network.NetworkEvent;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import java.util.function.Supplier; import java.util.function.Supplier;
@ -15,7 +17,8 @@ public class PingStatsPacket {
this.stats = stats; this.stats = stats;
} }
public static PingStatsPacket decode(FriendlyByteBuf buf) { @Contract("_ -> new")
public static @NotNull PingStatsPacket decode(@NotNull FriendlyByteBuf buf) {
return new PingStatsPacket( return new PingStatsPacket(
new PingRequestManager.PingStats( new PingRequestManager.PingStats(
buf.readDouble(), 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.writeDouble(stats.average());
buf.writeLong(stats.max()); buf.writeLong(stats.max());
buf.writeLong(stats.min()); buf.writeLong(stats.min());
@ -37,7 +40,7 @@ public class PingStatsPacket {
buf.writeDouble(stats.packetLossRate()); buf.writeDouble(stats.packetLossRate());
} }
public void handle(Supplier<NetworkEvent.Context> ctx) { public void handle(@NotNull Supplier<NetworkEvent.Context> ctx) {
ctx.get().enqueueWork(() -> { ctx.get().enqueueWork(() -> {
ClientPingHandler.handlePingStats(stats); ClientPingHandler.handlePingStats(stats);
}); });

View File

@ -14,6 +14,8 @@ import net.minecraft.network.ConnectionProtocol;
import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.chat.Component; import net.minecraft.network.chat.Component;
import net.minecraftforge.network.*; import net.minecraftforge.network.*;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger; import org.slf4j.Logger;
import java.util.function.Supplier; import java.util.function.Supplier;
@ -26,7 +28,8 @@ public class ResetPacket extends HandshakeMessages.C2SAcknowledge {
public ResetPacket() { public ResetPacket() {
super(); super();
} }
public static ResetPacket decode(FriendlyByteBuf ignoredBuf) { @Contract("_ -> new")
public static @NotNull ResetPacket decode(FriendlyByteBuf ignoredBuf) {
return new ResetPacket(); return new ResetPacket();
} }
@ -38,7 +41,7 @@ public class ResetPacket extends HandshakeMessages.C2SAcknowledge {
} }
public static void handler(Supplier<NetworkEvent.Context> ctxSupplier, Logger log) { public static void handler(@NotNull Supplier<NetworkEvent.Context> ctxSupplier, Logger log) {
NetworkEvent.Context ctx = ctxSupplier.get(); NetworkEvent.Context ctx = ctxSupplier.get();
ClientResetManager.isNegotiating.set(true); ClientResetManager.isNegotiating.set(true);
Connection conn = ctx.getNetworkManager(); Connection conn = ctx.getNetworkManager();

View File

@ -3,24 +3,25 @@ package com.leisuretimedock.crossmod.network.toServer;
import com.leisuretimedock.crossmod.network.PingRequestManager; import com.leisuretimedock.crossmod.network.PingRequestManager;
import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.FriendlyByteBuf;
import net.minecraftforge.network.NetworkEvent; import net.minecraftforge.network.NetworkEvent;
import org.jetbrains.annotations.NotNull;
import java.util.UUID; import java.util.UUID;
import java.util.function.Supplier; import java.util.function.Supplier;
//Server //Server
public record PongMessagePayload(UUID requestId) { 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().getMostSignificantBits());
buf.writeLong(payload.requestId().getLeastSignificantBits()); buf.writeLong(payload.requestId().getLeastSignificantBits());
} }
public static PongMessagePayload decode(FriendlyByteBuf buf) { public static @NotNull PongMessagePayload decode(@NotNull FriendlyByteBuf buf) {
long mostSignificantBits = buf.readLong(); long mostSignificantBits = buf.readLong();
long leastSignificantBits = buf.readLong(); long leastSignificantBits = buf.readLong();
return new PongMessagePayload(new UUID(mostSignificantBits, leastSignificantBits)); return new PongMessagePayload(new UUID(mostSignificantBits, leastSignificantBits));
} }
//服务器处理 //服务器处理
public static void handle(PongMessagePayload msg, Supplier<NetworkEvent.Context> ctx) { public static void handle(PongMessagePayload msg, @NotNull Supplier<NetworkEvent.Context> ctx) {
ctx.get().enqueueWork(() -> { ctx.get().enqueueWork(() -> {
PingRequestManager.complete(ctx.get().getSender(),msg.requestId); PingRequestManager.complete(ctx.get().getSender(),msg.requestId);
}); });

View File

@ -5,6 +5,8 @@ import com.leisuretimedock.crossmod.network.NetworkHandler;
import com.leisuretimedock.crossmod.network.toClient.SyncCommonConfigPacket; import com.leisuretimedock.crossmod.network.toClient.SyncCommonConfigPacket;
import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.FriendlyByteBuf;
import net.minecraftforge.network.NetworkEvent; import net.minecraftforge.network.NetworkEvent;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import java.util.function.Supplier; import java.util.function.Supplier;
@ -18,7 +20,7 @@ public record SyncCommonConfigRequestPacket(int hash) {
* @param msg the msg * @param msg the msg
* @param buf the buf * @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); buf.writeInt(msg.hash);
} }
@ -29,7 +31,8 @@ public record SyncCommonConfigRequestPacket(int hash) {
* @param buf the buf * @param buf the buf
* @return the sync common config request packet * @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()); return new SyncCommonConfigRequestPacket(buf.readInt());
} }
@ -39,7 +42,7 @@ public record SyncCommonConfigRequestPacket(int hash) {
* @param msg the msg * @param msg the msg
* @param ctx the ctx * @param ctx the ctx
*/ */
public static void handle(SyncCommonConfigRequestPacket msg, Supplier<NetworkEvent.Context> ctx) { public static void handle(SyncCommonConfigRequestPacket msg, @NotNull Supplier<NetworkEvent.Context> ctx) {
ctx.get().enqueueWork(() -> { ctx.get().enqueueWork(() -> {
if (msg.hash != CrossServerConfigManager.INSTANCE.cacheHash) { if (msg.hash != CrossServerConfigManager.INSTANCE.cacheHash) {
NetworkHandler.sendToPlayer(new SyncCommonConfigPacket(CrossServerConfigManager.INSTANCE.serializeToNBT(), CrossServerConfigManager.INSTANCE.calculateConfigHash()), ctx.get().getSender()); NetworkHandler.sendToPlayer(new SyncCommonConfigPacket(CrossServerConfigManager.INSTANCE.serializeToNBT(), CrossServerConfigManager.INSTANCE.calculateConfigHash()), ctx.get().getSender());

View File

@ -12,6 +12,8 @@ import net.minecraftforge.network.NetworkConstants;
import net.minecraftforge.network.NetworkDirection; import net.minecraftforge.network.NetworkDirection;
import net.minecraftforge.network.NetworkEvent; import net.minecraftforge.network.NetworkEvent;
import net.minecraftforge.network.simple.SimpleChannel; 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.Constructor;
import java.lang.reflect.Field; import java.lang.reflect.Field;
@ -25,7 +27,7 @@ public class ClientResetManager {
public static AtomicBoolean isNegotiating = new AtomicBoolean(false); public static AtomicBoolean isNegotiating = new AtomicBoolean(false);
public static SimpleChannel handshakeChannel; public static SimpleChannel handshakeChannel;
public static void init(FMLCommonSetupEvent event) { public static void init(@NotNull FMLCommonSetupEvent event) {
event.enqueueWork(() -> { event.enqueueWork(() -> {
if (handshakeField == null) { if (handshakeField == null) {
@ -55,7 +57,7 @@ public class ClientResetManager {
} }
}); });
} }
private static Field fetchHandshakeChannel() { private static @Nullable Field fetchHandshakeChannel() {
try { try {
return ObfuscationReflectionHelper.findField(NetworkConstants.class, "handshakeChannel"); return ObfuscationReflectionHelper.findField(NetworkConstants.class, "handshakeChannel");
} }
@ -65,7 +67,7 @@ public class ClientResetManager {
} }
} }
private static Constructor<NetworkEvent.Context> fetchNetworkEventContext() { private static @Nullable Constructor<NetworkEvent.Context> fetchNetworkEventContext() {
try { try {
return ObfuscationReflectionHelper.findConstructor(NetworkEvent.Context.class, Connection.class, NetworkDirection.class, int.class); return ObfuscationReflectionHelper.findConstructor(NetworkEvent.Context.class, Connection.class, NetworkDirection.class, int.class);
} }

View File

@ -9,6 +9,7 @@ import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.api.distmarker.OnlyIn; import net.minecraftforge.api.distmarker.OnlyIn;
import net.minecraftforge.network.NetworkEvent; import net.minecraftforge.network.NetworkEvent;
import net.minecraftforge.registries.GameData; import net.minecraftforge.registries.GameData;
import org.jetbrains.annotations.NotNull;
import java.util.NoSuchElementException; import java.util.NoSuchElementException;
import java.util.Objects; import java.util.Objects;
@ -20,7 +21,7 @@ import static net.minecraft.ChatFormatting.BOLD;
@OnlyIn(Dist.CLIENT) @OnlyIn(Dist.CLIENT)
public class ResetHelper { public class ResetHelper {
@SuppressWarnings("UnstableApiUsage") @SuppressWarnings("UnstableApiUsage")
public static boolean clearClient(NetworkEvent.Context context) { public static boolean clearClient(NetworkEvent.@NotNull Context context) {
CompletableFuture<Void> future = context.enqueueWork(() -> { CompletableFuture<Void> future = context.enqueueWork(() -> {
log.debug("Clearing"); log.debug("Clearing");
Minecraft minecraft = Minecraft.getInstance(); Minecraft minecraft = Minecraft.getInstance();

View File

@ -5,6 +5,8 @@ import lombok.Setter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.FriendlyByteBuf;
import net.minecraftforge.network.*; import net.minecraftforge.network.*;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import java.util.function.Supplier; import java.util.function.Supplier;
@ -16,7 +18,8 @@ public class ResetPacket extends HandshakeMessages.C2SAcknowledge {
public ResetPacket() { public ResetPacket() {
super(); super();
} }
public static ResetPacket decode(FriendlyByteBuf buf) { @Contract("_ -> new")
public static @NotNull ResetPacket decode(FriendlyByteBuf buf) {
return new ResetPacket(); return new ResetPacket();
} }

View File

@ -1,57 +1,174 @@
package com.leisuretimedock.crossmod.util; package com.leisuretimedock.crossmod.util;
import com.leisuretimedock.crossmod.config.CrossCommonModConfig;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import net.minecraft.network.FriendlyByteBuf; 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.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HexFormat; import java.util.HexFormat;
public class DebugUtils { 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(); 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) { if (readable <= 0) {
System.out.println("[Debug] No extra bytes to inspect."); log("[Debug][" + caller + "] No bytes left to inspect.");
return; return;
} }
int maxBytes = CrossCommonModConfig.getInstance().getMaxDebugBytes();
int bytesToRead = Math.min(readable, maxBytes);
// 保存当前位置 // 保存当前位置
int index = buf.readerIndex(); int index = buf.readerIndex();
// 读取并打印十六进制 // 读取并打印十六进制
byte[] bytes = new byte[readable]; byte[] bytes = new byte[bytesToRead];
buf.readBytes(bytes); buf.readBytes(bytes);
String hex = HexFormat.of().formatHex(bytes); String hex = HexFormat.of().formatHex(bytes);
System.out.println("[Debug] Extra bytes (hex): " + hex); log("[Debug][" + caller + "] Bytes (hex): " + hex);
// 尝试以 UTF-8 解码仅用于辅助分析 if (bytesToRead < readable) {
log("[Debug][" + caller + "] ... (truncated, " + (readable - bytesToRead) + " more bytes)");
}
// 尝试以 UTF-8 解码
try { try {
String utf8 = new String(bytes, StandardCharsets.UTF_8); String utf8 = new String(bytes, StandardCharsets.UTF_8);
System.out.println("[Debug] Interpreted as UTF-8 string:\n" + utf8); 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) { } catch (Exception e) {
System.out.println("[Debug] Failed to interpret as UTF-8: " + e.getMessage()); // ignore
} }
// 还原读取位置避免影响其他逻辑 // 还原读取位置
buf.readerIndex(index); 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(); int size = internal.readableBytes();
byte[] data = new byte[size]; byte[] data = new byte[size];
internal.readBytes(data); internal.readBytes(data);
System.out.println("[Debug] Full buffer size: " + size); log("[Debug][" + caller + "] Full buffer size: " + size);
System.out.println("[Debug] Hex dump:\n" + HexFormat.of().formatHex(data));
try { int maxBytes = CrossCommonModConfig.getInstance().getMaxDebugBytes();
String utf8 = new String(data, StandardCharsets.UTF_8); int hexSize = Math.min(size, maxBytes);
System.out.println("[Debug] UTF-8 decoded:\n" + utf8); byte[] hexData = new byte[hexSize];
} catch (Exception e) { System.arraycopy(data, 0, hexData, 0, hexSize);
System.out.println("[Debug] UTF-8 decode failed: " + e.getMessage()); 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;
}
} }
} }

View File

@ -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;
}
}

View File

@ -2,8 +2,11 @@
"ltd.mod.client.name.trans_server": "LTD Cross Server Mod", "ltd.mod.client.name.trans_server": "LTD Cross Server Mod",
"ltd.mod.client.key": "Open LTD Cross Server Menu", "ltd.mod.client.key": "Open LTD Cross Server Menu",
"ltd.mod.client.menu": "Cross Server Menu", "ltd.mod.client.menu": "Cross Server Menu",
"ltd.mod.client.menu.button.1": "H Hub", "ltd.mod.client.menu.button.hub": "H Hub",
"ltd.mod.client.menu.button.2": "S Survival", "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_trans_tip": "Show cross server tip overlay",
"ltd.mod.client.menu.checkbox.show_ping_stat": "Show ping stat overlay", "ltd.mod.client.menu.checkbox.show_ping_stat": "Show ping stat overlay",
"ltd.mod.client.negotiating": "Negotiating...", "ltd.mod.client.negotiating": "Negotiating...",
@ -38,6 +41,21 @@
"ltd.mod.ping.stats.avg_latency": "Average delay: %.1fms", "ltd.mod.ping.stats.avg_latency": "Average delay: %.1fms",
"ltd.mod.ping.stats.packet_loss": "Packet loss rate: %.1f%%", "ltd.mod.ping.stats.packet_loss": "Packet loss rate: %.1f%%",
"ltd.mod.ping.stats.sample_count": "Sample count: %d", "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"
} }

View File

@ -2,8 +2,11 @@
"ltd.mod.client.name.trans_server": "LTD跨服传送模组", "ltd.mod.client.name.trans_server": "LTD跨服传送模组",
"ltd.mod.client.key": "打开LTD跨服传送菜单", "ltd.mod.client.key": "打开LTD跨服传送菜单",
"ltd.mod.client.menu": "跨服菜单", "ltd.mod.client.menu": "跨服菜单",
"ltd.mod.client.menu.button.1": "H 主城", "ltd.mod.client.menu.button.hub": "H 主城",
"ltd.mod.client.menu.button.2": "S 生存服", "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_trans_tip": "显示传送提示",
"ltd.mod.client.menu.checkbox.show_ping_stat": "显示Ping状态", "ltd.mod.client.menu.checkbox.show_ping_stat": "显示Ping状态",
"ltd.mod.client.negotiating": "重定向中 ...", "ltd.mod.client.negotiating": "重定向中 ...",
@ -38,5 +41,20 @@
"ltd.mod.ping.stats.sample_count": "样本数量: %d", "ltd.mod.ping.stats.sample_count": "样本数量: %d",
"ltd.mod.ping.warn.network_latency": "网络延迟较高建议减少Ping频率", "ltd.mod.ping.warn.network_latency": "网络延迟较高建议减少Ping频率",
"ltd.mod.client.menu.button.no_servers": "没有服务器", "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已关闭"
} }

View File

@ -6,8 +6,12 @@
"refmap": "ltdcrossteleport.refmap.json", "refmap": "ltdcrossteleport.refmap.json",
"mixins": [ "mixins": [
"AccessorMinecraft", "AccessorMinecraft",
"MixinConnection",
"MixinMUINetWorkHandler", "MixinMUINetWorkHandler",
"ModListSpoofMixin" "MixinPacketDecoder",
"MixinPlayerList",
"ModListSpoofMixin",
"MixinBundlePacket"
], ],
"minVersion": "0.8", "minVersion": "0.8",
"injectors": { "injectors": {

View File

@ -1,6 +1,6 @@
plugins { plugins {
id 'java' 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" id("xyz.jpenilla.run-velocity") version "2.3.1"
} }