添加更多指令和调试内容
This commit is contained in:
parent
e5e46b3ee9
commit
d7f222442c
6
.idea/AndroidProjectSystem.xml
Normal file
6
.idea/AndroidProjectSystem.xml
Normal 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>
|
||||
21
.idea/claudeCodeTabState.xml
Normal file
21
.idea/claudeCodeTabState.xml
Normal 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>
|
||||
|
|
@ -6,7 +6,6 @@
|
|||
<GradleProjectSettings>
|
||||
<option name="distributionType" value="LOCAL" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="gradleHome" value="$PROJECT_DIR$/../../../projEnv/gradle/gradle-8.12" />
|
||||
<option name="modules">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
|
|
@ -14,6 +13,7 @@
|
|||
<option value="$PROJECT_DIR$/velocity-plugin" />
|
||||
</set>
|
||||
</option>
|
||||
<option name="resolveExternalAnnotations" value="true" />
|
||||
</GradleProjectSettings>
|
||||
</option>
|
||||
</component>
|
||||
|
|
|
|||
12
.idea/material_theme_project_new.xml
Normal file
12
.idea/material_theme_project_new.xml
Normal 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
6
SY.md
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
[=== LTD Cross Server Menu ==]
|
||||
/空
|
||||
1. [xxx服务器]
|
||||
/空
|
||||
2. [xxx服务器]
|
||||
...
|
||||
|
|
@ -49,7 +49,7 @@ mod_name=Leisure Time Dock Mod
|
|||
# The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default.
|
||||
mod_license=MIT
|
||||
# The mod version. See https://semver.org/
|
||||
mod_version=1.0.0
|
||||
mod_version=1.1.1
|
||||
# The group ID for the mod. It is only important when publishing as an artifact to a Maven repository.
|
||||
# This should match the base package used for the mod sources.
|
||||
# See https://maven.apache.org/guides/mini/guide-naming-conventions.html
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.leisuretimedock.crossmod;
|
||||
|
||||
import com.leisuretimedock.crossmod.command.CrossModDebugCommand;
|
||||
import com.leisuretimedock.crossmod.command.GotoServerCommand;
|
||||
import com.leisuretimedock.crossmod.config.CrossServerConfig;
|
||||
import com.leisuretimedock.crossmod.config.CrossServerConfigManager;
|
||||
|
|
@ -7,6 +8,7 @@ import com.leisuretimedock.crossmod.command.PingCommand;
|
|||
import com.leisuretimedock.crossmod.network.NetworkHandler;
|
||||
import com.leisuretimedock.crossmod.network.PingRequestManager;
|
||||
import com.leisuretimedock.crossmod.reset.ClientResetManager;
|
||||
import com.leisuretimedock.crossmod.util.ModLogger;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.minecraft.server.MinecraftServer;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
|
|
@ -28,6 +30,7 @@ import net.minecraftforge.fml.event.lifecycle.FMLDedicatedServerSetupEvent;
|
|||
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
|
||||
import net.minecraftforge.fml.loading.FMLEnvironment;
|
||||
import net.minecraftforge.network.NetworkConstants;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Objects;
|
||||
|
|
@ -45,25 +48,32 @@ public class CrossTeleportMod {
|
|||
ModLoadingContext.get().registerConfig(ModConfig.Type.SERVER, CrossServerConfig.SPEC, "cross-server.toml");
|
||||
if(!FMLEnvironment.dist.isDedicatedServer()) modEventBus.addListener(ClientResetManager::init);
|
||||
NetworkHandler.register();
|
||||
// 初始化文件日志
|
||||
ModLogger.initFileLogging();
|
||||
ModLogger.info("CrossMod initialized");
|
||||
ModLogger.bundle("Hello, world!");
|
||||
// 注册关闭钩子
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(ModLogger::closeFileLogging));
|
||||
}
|
||||
@Mod.EventBusSubscriber(modid = MOD_ID, bus = Mod.EventBusSubscriber.Bus.FORGE)
|
||||
public static class CommonEvents {
|
||||
@Nullable
|
||||
public static MinecraftServer server;
|
||||
@SubscribeEvent
|
||||
public static void onRegisterCommands(RegisterCommandsEvent event) {
|
||||
public static void onRegisterCommands(@NotNull RegisterCommandsEvent event) {
|
||||
PingCommand.register(event.getDispatcher());
|
||||
GotoServerCommand.register(event.getDispatcher());
|
||||
CrossModDebugCommand.register(event.getDispatcher());
|
||||
}
|
||||
@SubscribeEvent
|
||||
public static void onPlayerLoggedIn(PlayerEvent.PlayerLoggedInEvent event) {
|
||||
public static void onPlayerLoggedIn(PlayerEvent.@NotNull PlayerLoggedInEvent event) {
|
||||
if (event.getEntity() instanceof ServerPlayer player) {
|
||||
PingRequestManager.monitor(player);
|
||||
}
|
||||
}
|
||||
private static int tickCounter = 0;
|
||||
@SubscribeEvent
|
||||
public static void onServerTick(TickEvent.ServerTickEvent event) {
|
||||
public static void onServerTick(TickEvent.@NotNull ServerTickEvent event) {
|
||||
if (event.phase == TickEvent.Phase.END) {
|
||||
tickCounter++;
|
||||
if (tickCounter % 10 == 0) {
|
||||
|
|
@ -72,13 +82,13 @@ public class CrossTeleportMod {
|
|||
}
|
||||
}
|
||||
@SubscribeEvent
|
||||
public static void onPlayerLoggedOut(PlayerEvent.PlayerLoggedOutEvent event) {
|
||||
public static void onPlayerLoggedOut(PlayerEvent.@NotNull PlayerLoggedOutEvent event) {
|
||||
if (event.getEntity() instanceof ServerPlayer player) {
|
||||
PingRequestManager.unmonitor(player);
|
||||
}
|
||||
}
|
||||
@SubscribeEvent
|
||||
public static void onServerStart(ServerStartedEvent event) {
|
||||
public static void onServerStart(@NotNull ServerStartedEvent event) {
|
||||
server = event.getServer();
|
||||
}
|
||||
@SubscribeEvent
|
||||
|
|
@ -114,7 +124,7 @@ public class CrossTeleportMod {
|
|||
* @param event the event
|
||||
*/
|
||||
@SubscribeEvent
|
||||
public static void onConfigLoaded(ModConfigEvent.Loading event) {
|
||||
public static void onConfigLoaded(ModConfigEvent.@NotNull Loading event) {
|
||||
if (event.getConfig().getSpec() == CrossServerConfig.SPEC) {
|
||||
CrossServerConfigManager.loading(CrossServerConfigManager.INSTANCE);
|
||||
}
|
||||
|
|
@ -126,7 +136,7 @@ public class CrossTeleportMod {
|
|||
* @param event the event
|
||||
*/
|
||||
@SubscribeEvent
|
||||
public static void onConfigReloaded(ModConfigEvent.Reloading event) {
|
||||
public static void onConfigReloaded(ModConfigEvent.@NotNull Reloading event) {
|
||||
if (event.getConfig().getSpec() == CrossServerConfig.SPEC) {
|
||||
CrossServerConfigManager.reloading(CrossServerConfigManager.INSTANCE);
|
||||
}
|
||||
|
|
@ -138,7 +148,7 @@ public class CrossTeleportMod {
|
|||
* @param event the event
|
||||
*/
|
||||
@SubscribeEvent
|
||||
public static void onConfigUnloaded(ModConfigEvent.Unloading event) {
|
||||
public static void onConfigUnloaded(ModConfigEvent.@NotNull Unloading event) {
|
||||
if (event.getConfig().getSpec() == CrossServerConfig.SPEC) {
|
||||
CrossServerConfigManager.unloading(CrossServerConfigManager.INSTANCE);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ public class ClientPingHandler {
|
|||
private static PingRequestManager.PingStats lastStats;
|
||||
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();
|
||||
|
||||
pingResults.forEach((uuid, ping) -> {
|
||||
|
|
@ -29,7 +29,7 @@ public class ClientPingHandler {
|
|||
);
|
||||
}
|
||||
|
||||
public static String getPingDisplayText() {
|
||||
public static @NotNull String getPingDisplayText() {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc.level == null) return "";
|
||||
|
||||
|
|
@ -82,7 +82,7 @@ public class ClientPingHandler {
|
|||
}
|
||||
|
||||
// 获取要显示的统计文本
|
||||
public static String getStatsDisplayText() {
|
||||
public static @NotNull String getStatsDisplayText() {
|
||||
if (lastStats == null || System.currentTimeMillis() - lastStatsUpdateTime > 10000) {
|
||||
return "网络统计: 数据过期";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,20 +9,21 @@ import net.minecraftforge.client.event.RegisterKeyMappingsEvent;
|
|||
import net.minecraftforge.event.TickEvent;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.lwjgl.glfw.GLFW;
|
||||
@Mod.EventBusSubscriber(modid = CrossTeleportMod.MOD_ID, bus = Mod.EventBusSubscriber.Bus.MOD, value = Dist.CLIENT)
|
||||
public class KeyBindingHandler {
|
||||
public static final KeyMapping OPEN_GUI_KEY = new KeyMapping("ltd.mod.client.name.trans_server", GLFW.GLFW_KEY_HOME, "ltd.mod.client.key");
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onRegisterKeyMappingsEvent (RegisterKeyMappingsEvent event) {
|
||||
public static void onRegisterKeyMappingsEvent (@NotNull RegisterKeyMappingsEvent event) {
|
||||
event.register(OPEN_GUI_KEY);
|
||||
}
|
||||
|
||||
@Mod.EventBusSubscriber(modid = CrossTeleportMod.MOD_ID, value = Dist.CLIENT)
|
||||
public static class KeyHandler {
|
||||
@SubscribeEvent
|
||||
public static void onClientTick(TickEvent.ClientTickEvent event) {
|
||||
public static void onClientTick(TickEvent.@NotNull ClientTickEvent event) {
|
||||
if (event.phase == TickEvent.Phase.END && OPEN_GUI_KEY.consumeClick()) {
|
||||
Minecraft.getInstance().setScreen(new CrossServerGui());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.leisuretimedock.crossmod.client;
|
|||
|
||||
import com.leisuretimedock.crossmod.CrossTeleportMod;
|
||||
import com.leisuretimedock.crossmod.client.command.GotoCommand;
|
||||
import com.leisuretimedock.crossmod.client.command.ServerMenuCommand;
|
||||
import com.leisuretimedock.crossmod.client.overlay.CrossServerTipOverLay;
|
||||
import com.leisuretimedock.crossmod.client.overlay.PluginCommand;
|
||||
import com.leisuretimedock.crossmod.network.NetworkHandler;
|
||||
|
|
@ -19,6 +20,7 @@ import net.minecraftforge.client.event.ClientPlayerNetworkEvent;
|
|||
import net.minecraftforge.client.event.RegisterClientCommandsEvent;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Objects;
|
||||
@Slf4j
|
||||
|
|
@ -87,7 +89,7 @@ public class PluginChannelClient {
|
|||
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onLogout(ClientPlayerNetworkEvent.LoggingOut event) {
|
||||
public static void onLogout(ClientPlayerNetworkEvent.@NotNull LoggingOut event) {
|
||||
log.debug("[CrossTeleportMod] 玩家注销事件触发");
|
||||
|
||||
Connection connection = event.getConnection();
|
||||
|
|
@ -110,7 +112,8 @@ public class PluginChannelClient {
|
|||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onRegisterCommand(RegisterClientCommandsEvent event) {
|
||||
public static void onRegisterCommand(@NotNull RegisterClientCommandsEvent event) {
|
||||
GotoCommand.register(event.getDispatcher());
|
||||
ServerMenuCommand.register(event.getDispatcher());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,9 +7,10 @@ import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
|||
import net.minecraft.commands.CommandSourceStack;
|
||||
import net.minecraft.commands.Commands;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class GotoCommand {
|
||||
public static void register(CommandDispatcher<CommandSourceStack> dispatcher) {
|
||||
public static void register(@NotNull CommandDispatcher<CommandSourceStack> dispatcher) {
|
||||
LiteralArgumentBuilder<CommandSourceStack> main = Commands.literal("goto")
|
||||
.then(Commands.argument("server", StringArgumentType.string())
|
||||
.executes(ctx -> {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -163,7 +163,7 @@ public class CrossServerGui extends Screen {
|
|||
}
|
||||
}
|
||||
|
||||
private void renderLogo(GuiGraphics guiGraphics) {
|
||||
private void renderLogo(@NotNull GuiGraphics guiGraphics) {
|
||||
int logoWidth = 64; // 缩小Logo,为列表腾出空间
|
||||
int logoHeight = 64;
|
||||
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ public class GenericIceMessageScreen extends GenericDirtMessageScreen {
|
|||
}
|
||||
|
||||
@SuppressWarnings("UnstableApiUsage")
|
||||
public void renderIceBackground(GuiGraphics guiGraphics, float r, float g, float b, float a) {
|
||||
public void renderIceBackground(@NotNull GuiGraphics guiGraphics, float r, float g, float b, float a) {
|
||||
guiGraphics.setColor(0.65F, 0.65F, 0.65F, 1.0F);
|
||||
guiGraphics.blit(ICE, 0, 0, 0, 0.0F, 0.0F, this.width, this.height, 32, 32);
|
||||
guiGraphics.setColor(r, g, b, a);
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import java.util.Map;
|
|||
public class ServerSelectionList extends ObjectSelectionList<ServerSelectionList.ServerEntry> {
|
||||
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);
|
||||
this.parentScreen = parent;
|
||||
|
||||
|
|
|
|||
|
|
@ -5,12 +5,13 @@ import net.minecraftforge.api.distmarker.Dist;
|
|||
import net.minecraftforge.client.event.RegisterGuiOverlaysEvent;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@Mod.EventBusSubscriber(modid = CrossTeleportMod.MOD_ID, value = Dist.CLIENT, bus = Mod.EventBusSubscriber.Bus.MOD)
|
||||
public class OverlayRenderer {
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onRender(RegisterGuiOverlaysEvent event) {
|
||||
public static void onRender(@NotNull RegisterGuiOverlaysEvent event) {
|
||||
event.registerAboveAll("cross_server_tip", CrossServerTipOverLay.INSTANCE);
|
||||
event.registerAboveAll(
|
||||
"ping_debug",
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import net.minecraft.client.gui.Font;
|
|||
import net.minecraft.client.gui.GuiGraphics;
|
||||
import net.minecraftforge.client.gui.overlay.ForgeGui;
|
||||
import net.minecraftforge.client.gui.overlay.IGuiOverlay;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
|
@ -55,7 +56,7 @@ public class PingOverlayManager implements IGuiOverlay {
|
|||
drawTextLines(guiGraphics, font, allLines, x, y);
|
||||
}
|
||||
|
||||
private List<String> getAllDisplayLines() {
|
||||
private @NotNull List<String> getAllDisplayLines() {
|
||||
List<String> lines = new ArrayList<>();
|
||||
|
||||
// 添加Ping信息
|
||||
|
|
@ -74,7 +75,7 @@ public class PingOverlayManager implements IGuiOverlay {
|
|||
return lines;
|
||||
}
|
||||
|
||||
private int getMaxLineWidth(Font font, List<String> lines) {
|
||||
private int getMaxLineWidth(@NotNull Font font, @NotNull List<String> lines) {
|
||||
return lines.stream()
|
||||
.mapToInt(font::width)
|
||||
.max()
|
||||
|
|
@ -94,14 +95,14 @@ public class PingOverlayManager implements IGuiOverlay {
|
|||
return baseY;
|
||||
}
|
||||
|
||||
private void drawBackground(GuiGraphics guiGraphics, int x, int y, int width, int height, Font font) {
|
||||
private void drawBackground(@NotNull GuiGraphics guiGraphics, int x, int y, int width, int height, Font font) {
|
||||
guiGraphics.fill(
|
||||
x - PADDING, y - PADDING,
|
||||
x + width + PADDING, y + height + PADDING,
|
||||
BACKGROUND_COLOR);
|
||||
}
|
||||
|
||||
private void drawTextLines(GuiGraphics guiGraphics, Font font, List<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++) {
|
||||
String line = lines.get(i);
|
||||
if (!line.isEmpty()) {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
package com.leisuretimedock.crossmod.client.overlay;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
|
||||
|
|
@ -11,7 +13,7 @@ public enum PluginCommand {
|
|||
|
||||
PluginCommand(String id) { this.id = id; }
|
||||
|
||||
public static Optional<PluginCommand> fromId(String id) {
|
||||
public static @NotNull Optional<PluginCommand> fromId(String id) {
|
||||
return Arrays.stream(values()).filter(cmd -> cmd.id.equals(id)).findFirst();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -10,26 +10,28 @@ import net.minecraft.commands.Commands;
|
|||
import net.minecraft.commands.arguments.EntityArgument;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
public class GotoServerCommand {
|
||||
public static void register(CommandDispatcher<CommandSourceStack> dispatcher) {
|
||||
public static void register(@NotNull CommandDispatcher<CommandSourceStack> dispatcher) {
|
||||
LiteralArgumentBuilder<CommandSourceStack> main = Commands.literal("cross")
|
||||
.requires(cs -> cs.hasPermission(2))
|
||||
.then(Commands.argument("players", EntityArgument.players())
|
||||
.then(Commands.literal("goto")
|
||||
.then(Commands.argument("server", StringArgumentType.string())
|
||||
.executes(ctx -> {
|
||||
String server = StringArgumentType.getString(ctx, "server");
|
||||
Collection<ServerPlayer> players = EntityArgument.getPlayers(ctx, "players");
|
||||
players.forEach(p -> NetworkHandler.sendToPlayer(new GotoServerPayload(server), p));
|
||||
ctx.getSource().sendSuccess(
|
||||
() -> Component.translatable("ltd.mod.client.request.goto",server), false);
|
||||
return 1;
|
||||
})
|
||||
.then(
|
||||
Commands.argument("players", EntityArgument.players())
|
||||
.requires(cs -> cs.hasPermission(2))
|
||||
.then(Commands.literal("goto")
|
||||
.then(Commands.argument("server", StringArgumentType.string())
|
||||
.executes(ctx -> {
|
||||
String server = StringArgumentType.getString(ctx, "server");
|
||||
Collection<ServerPlayer> players = EntityArgument.getPlayers(ctx, "players");
|
||||
players.forEach(p -> NetworkHandler.sendToPlayer(new GotoServerPayload(server), p));
|
||||
ctx.getSource().sendSuccess(
|
||||
() -> Component.translatable("ltd.mod.client.request.goto",server), false);
|
||||
return 1;
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
.then(Commands.literal("goto")
|
||||
.then(Commands.argument("server", StringArgumentType.string())
|
||||
|
|
@ -41,6 +43,7 @@ public class GotoServerCommand {
|
|||
NetworkHandler.sendToPlayer(new GotoServerPayload(server), player);
|
||||
source.sendSuccess(
|
||||
() -> Component.translatable("ltd.mod.client.request.goto",server), false);
|
||||
return 0;
|
||||
}
|
||||
source.sendFailure(Component.literal("Request a player"));
|
||||
return 1;
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import net.minecraft.commands.Commands;
|
|||
import net.minecraft.commands.arguments.EntityArgument;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
|
@ -20,7 +21,7 @@ import java.util.Map;
|
|||
import java.util.UUID;
|
||||
|
||||
public class PingCommand {
|
||||
public static void register(CommandDispatcher<CommandSourceStack> dispatcher) {
|
||||
public static void register(@NotNull CommandDispatcher<CommandSourceStack> dispatcher) {
|
||||
LiteralArgumentBuilder<CommandSourceStack> networkping =
|
||||
Commands.literal("netping")
|
||||
.requires(source -> source.hasPermission(2))
|
||||
|
|
@ -90,7 +91,7 @@ public class PingCommand {
|
|||
dispatcher.register(networkping);
|
||||
}
|
||||
|
||||
private static int executePlayerReport(CommandSourceStack source, Collection<ServerPlayer> players) throws CommandSyntaxException {
|
||||
private static int executePlayerReport(CommandSourceStack source, @NotNull Collection<ServerPlayer> players) throws CommandSyntaxException {
|
||||
if (players.isEmpty()) {
|
||||
source.sendSuccess(() -> Component.translatable("ltd.mod.ping.error.no_players"), false);
|
||||
return 0;
|
||||
|
|
@ -110,7 +111,7 @@ public class PingCommand {
|
|||
return Command.SINGLE_SUCCESS;
|
||||
}
|
||||
|
||||
private static int executeFullReport(CommandSourceStack source) throws CommandSyntaxException {
|
||||
private static int executeFullReport(@NotNull CommandSourceStack source) throws CommandSyntaxException {
|
||||
ServerPlayer player = source.getPlayerOrException();
|
||||
Map<UUID, Long> results = PingRequestManager.getAllLatestPings();
|
||||
|
||||
|
|
@ -129,7 +130,7 @@ public class PingCommand {
|
|||
return Command.SINGLE_SUCCESS;
|
||||
}
|
||||
|
||||
private static int executeStatsReport(CommandSourceStack source) throws CommandSyntaxException {
|
||||
private static int executeStatsReport(@NotNull CommandSourceStack source) throws CommandSyntaxException {
|
||||
ServerPlayer player = source.getPlayerOrException();
|
||||
PingRequestManager.PingStats stats = PingRequestManager.getGlobalPingStats();
|
||||
|
||||
|
|
@ -144,7 +145,7 @@ public class PingCommand {
|
|||
return Command.SINGLE_SUCCESS;
|
||||
}
|
||||
|
||||
private static int executeSinglePing(CommandSourceStack source) throws CommandSyntaxException {
|
||||
private static int executeSinglePing(@NotNull CommandSourceStack source) throws CommandSyntaxException {
|
||||
ServerPlayer player = source.getPlayerOrException();
|
||||
if(!PingRequestManager.isMonitored(player.getUUID())) {
|
||||
source.sendFailure(Component.translatable("ltd.mod.ping.error.not_monitored.self"));
|
||||
|
|
@ -155,7 +156,7 @@ public class PingCommand {
|
|||
return Command.SINGLE_SUCCESS;
|
||||
}
|
||||
|
||||
private static int executePingPlayers(CommandSourceStack source, Collection<ServerPlayer> players) throws CommandSyntaxException {
|
||||
private static int executePingPlayers(CommandSourceStack source, @NotNull Collection<ServerPlayer> players) throws CommandSyntaxException {
|
||||
if (players.isEmpty()) {
|
||||
source.sendSuccess(() -> Component.translatable("ltd.mod.ping.error.no_players"), false);
|
||||
return 0;
|
||||
|
|
@ -176,7 +177,7 @@ public class PingCommand {
|
|||
return Command.SINGLE_SUCCESS;
|
||||
}
|
||||
private static int executeMultiPing(CommandSourceStack source,
|
||||
Collection<ServerPlayer> players,
|
||||
@NotNull Collection<ServerPlayer> players,
|
||||
int count,
|
||||
int interval) {
|
||||
if (players.isEmpty()) {
|
||||
|
|
@ -205,7 +206,7 @@ public class PingCommand {
|
|||
|
||||
return Command.SINGLE_SUCCESS;
|
||||
}
|
||||
private static int executeToggleMonitoring(CommandSourceStack source, boolean monitor) throws CommandSyntaxException {
|
||||
private static int executeToggleMonitoring(@NotNull CommandSourceStack source, boolean monitor) throws CommandSyntaxException {
|
||||
ServerPlayer player = source.getPlayerOrException();
|
||||
if (monitor) {
|
||||
PingRequestManager.monitor(player);
|
||||
|
|
@ -217,7 +218,7 @@ public class PingCommand {
|
|||
return Command.SINGLE_SUCCESS;
|
||||
}
|
||||
|
||||
private static int executeToggleMonitoring(CommandSourceStack source, Collection<ServerPlayer> players, boolean monitor) throws CommandSyntaxException {
|
||||
private static int executeToggleMonitoring(CommandSourceStack source, @NotNull Collection<ServerPlayer> players, boolean monitor) throws CommandSyntaxException {
|
||||
if (players.isEmpty()) {
|
||||
source.sendSuccess(() -> Component.translatable("ltd.mod.ping.error.no_players"), false);
|
||||
return 0;
|
||||
|
|
@ -238,7 +239,7 @@ public class PingCommand {
|
|||
return players.size();
|
||||
}
|
||||
|
||||
private static void sendTextReport(ServerPlayer player, Map<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),
|
||||
true);
|
||||
|
||||
|
|
@ -255,7 +256,7 @@ public class PingCommand {
|
|||
});
|
||||
}
|
||||
|
||||
private static void sendStatsTextReport(ServerPlayer player, PingRequestManager.PingStats stats) {
|
||||
private static void sendStatsTextReport(@NotNull ServerPlayer player, PingRequestManager.@NotNull PingStats stats) {
|
||||
player.displayClientMessage(Component.translatable("ltd.mod.ping.title.stats").withStyle(ChatFormatting.GOLD),
|
||||
true);
|
||||
player.displayClientMessage(Component.translatable(
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -10,19 +10,21 @@ public class CrossServerConfig {
|
|||
private static final ForgeConfigSpec.Builder BUILDER = new ForgeConfigSpec.Builder();
|
||||
public static ForgeConfigSpec SPEC;
|
||||
public static final ForgeConfigSpec.ConfigValue<List<? extends String>> SERVER_LIST;
|
||||
public static final ForgeConfigSpec.BooleanValue DISABLED_JOIN_QUIT_MESSAGE;
|
||||
static {
|
||||
BUILDER.comment("Cross Server Config").push("servers");
|
||||
SERVER_LIST = BUILDER
|
||||
.comment("Server list in format: <server_name>: <translate_key>")
|
||||
.defineList("serverList",
|
||||
Arrays.asList(
|
||||
"lobby: ltd.mod.client.menu.button.1",
|
||||
"survival: ltd.mod.client.menu.button.2"
|
||||
"lobby: ltd.mod.client.menu.button.hub",
|
||||
"survival: ltd.mod.client.menu.button.survival"
|
||||
),
|
||||
obj -> obj instanceof String str && checkSyntax(str)
|
||||
);
|
||||
|
||||
BUILDER.pop();
|
||||
DISABLED_JOIN_QUIT_MESSAGE = BUILDER.comment("Disable join or quit message")
|
||||
.define("disabled_join_quit_message", false);
|
||||
SPEC = BUILDER.build();
|
||||
}
|
||||
public static boolean checkSyntax(@NotNull String input) {
|
||||
|
|
|
|||
|
|
@ -32,6 +32,9 @@ public class CrossServerConfigManager {
|
|||
@Getter
|
||||
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) {
|
||||
Map<String, String> serverMap = new TreeMap<>();
|
||||
for (String server : servers) {
|
||||
|
|
@ -53,6 +56,7 @@ public class CrossServerConfigManager {
|
|||
try {
|
||||
clear();
|
||||
servers.putAll(parseServer(CrossServerConfig.SERVER_LIST.get()));
|
||||
disabledJoinQuitMessage = CrossServerConfig.DISABLED_JOIN_QUIT_MESSAGE.get();
|
||||
cacheHash = -1;
|
||||
cacheTag = serializeToNBT();
|
||||
log.debug("Configs reloaded");
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ package com.leisuretimedock.crossmod.mixin;
|
|||
|
||||
import icyllis.modernui.mc.forge.NetworkHandler;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Pseudo;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
|
|
@ -17,7 +18,7 @@ public class MixinMUINetWorkHandler {
|
|||
argsOnly = true,
|
||||
ordinal = 0
|
||||
)
|
||||
private static ResourceLocation modifyNameParameter(ResourceLocation name) {
|
||||
private static ResourceLocation modifyNameParameter(@NotNull ResourceLocation name) {
|
||||
return name.getPath().isEmpty() ? name : new ResourceLocation(name.getNamespace(), "default");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -148,7 +148,7 @@ public class NetworkHandler {
|
|||
* @param subChannel 子通道标识
|
||||
* @param payload 负载数据(字节数组)
|
||||
*/
|
||||
public static void sendPluginMessage(ResourceLocation subChannel, byte[] payload) {
|
||||
public static void sendPluginMessage(ResourceLocation subChannel, byte @NotNull [] payload) {
|
||||
FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer(payload.length));
|
||||
buf.writeBytes(payload);
|
||||
|
||||
|
|
@ -201,7 +201,7 @@ public class NetworkHandler {
|
|||
* @param entity the entity
|
||||
* @param packetDistributor the packet distributor
|
||||
*/
|
||||
public static <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);
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,8 @@ import lombok.Getter;
|
|||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
|
|
@ -210,7 +212,7 @@ public final class PingRequestManager {
|
|||
/**
|
||||
* 获取所有玩家的最新ping值
|
||||
*/
|
||||
public static Map<UUID, Long> getAllLatestPings() {
|
||||
public static @NotNull Map<UUID, Long> getAllLatestPings() {
|
||||
Map<UUID, Long> results = new HashMap<>();
|
||||
|
||||
playerData.forEach((uuid, data) -> {
|
||||
|
|
@ -228,7 +230,7 @@ public final class PingRequestManager {
|
|||
* @param players 要查询的玩家集合
|
||||
* @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<>();
|
||||
|
||||
for (ServerPlayer player : players) {
|
||||
|
|
@ -297,7 +299,7 @@ public final class PingRequestManager {
|
|||
/**
|
||||
* 取消该玩家的所有进行中的Ping请求
|
||||
*/
|
||||
public static void cancelPings(ServerPlayer player) {
|
||||
public static void cancelPings(@NotNull ServerPlayer player) {
|
||||
PlayerPingData data = playerData.get(player.getUUID());
|
||||
if (data != null) {
|
||||
synchronized (data) {
|
||||
|
|
@ -342,7 +344,7 @@ public final class PingRequestManager {
|
|||
PingStats globalStats = getGlobalPingStats();
|
||||
NetworkHandler.sendPingReport(player, latestPings, averages, globalStats);
|
||||
}
|
||||
private static void updatePingHistory(PlayerPingData data, long ping) {
|
||||
private static void updatePingHistory(@NotNull PlayerPingData data, long ping) {
|
||||
data.addPing(ping);
|
||||
}
|
||||
|
||||
|
|
@ -369,7 +371,8 @@ public final class PingRequestManager {
|
|||
return PlayerPingData.calculate(pingHistory);
|
||||
}
|
||||
|
||||
private static double calculatePacketLossRate(PlayerPingData data) {
|
||||
@Contract(pure = true)
|
||||
private static double calculatePacketLossRate(@NotNull PlayerPingData data) {
|
||||
synchronized (PingRequestManager.class) {
|
||||
if (data.totalRequests == 0) return 0;
|
||||
return (1 - (double) data.successfulRequests / data.totalRequests) * 100;
|
||||
|
|
@ -403,7 +406,8 @@ public final class PingRequestManager {
|
|||
return calculate(pings);
|
||||
}
|
||||
|
||||
static double calculate(Collection<Long> pings) {
|
||||
@Contract(pure = true)
|
||||
static double calculate(@NotNull Collection<Long> pings) {
|
||||
if (pings.isEmpty()) return 0;
|
||||
|
||||
double total = 0;
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ import com.leisuretimedock.crossmod.network.NetworkHandler;
|
|||
import com.leisuretimedock.crossmod.network.toServer.SyncCommonConfigRequestPacket;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraftforge.network.NetworkEvent;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
|
|
@ -18,7 +20,7 @@ public record CommonConfigHashInformPacket(int hash) {
|
|||
* @param packet the packet
|
||||
* @param buffer the buffer
|
||||
*/
|
||||
public static void encode(CommonConfigHashInformPacket packet, FriendlyByteBuf buffer) {
|
||||
public static void encode(@NotNull CommonConfigHashInformPacket packet, @NotNull FriendlyByteBuf buffer) {
|
||||
buffer.writeInt(packet.hash());
|
||||
}
|
||||
|
||||
|
|
@ -29,7 +31,8 @@ public record CommonConfigHashInformPacket(int hash) {
|
|||
* @param buffer the buffer
|
||||
* @return the common config hash inform packet
|
||||
*/
|
||||
public static CommonConfigHashInformPacket decode(FriendlyByteBuf buffer) {
|
||||
@Contract("_ -> new")
|
||||
public static @NotNull CommonConfigHashInformPacket decode(@NotNull FriendlyByteBuf buffer) {
|
||||
return new CommonConfigHashInformPacket(buffer.readInt());
|
||||
}
|
||||
|
||||
|
|
@ -39,7 +42,7 @@ public record CommonConfigHashInformPacket(int hash) {
|
|||
* @param packet the packet
|
||||
* @param ctx the ctx
|
||||
*/
|
||||
public static void handle(CommonConfigHashInformPacket packet, Supplier<NetworkEvent.Context> ctx) {
|
||||
public static void handle(CommonConfigHashInformPacket packet, @NotNull Supplier<NetworkEvent.Context> ctx) {
|
||||
NetworkEvent.Context context = ctx.get();
|
||||
context.enqueueWork(() -> {
|
||||
int hash = CrossServerConfigManager.INSTANCE.calculateConfigHash();
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import net.minecraft.network.FriendlyByteBuf;
|
|||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.fml.DistExecutor;
|
||||
import net.minecraftforge.network.NetworkEvent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.function.Supplier;
|
||||
|
|
@ -13,18 +14,18 @@ import static com.leisuretimedock.crossmod.network.NetworkHandler.CHANNEL;
|
|||
|
||||
//Server -> Client
|
||||
public record PingMessagePayload(UUID requestId) {
|
||||
public static void encode(PingMessagePayload payload, FriendlyByteBuf buf) {
|
||||
public static void encode(@NotNull PingMessagePayload payload, @NotNull FriendlyByteBuf buf) {
|
||||
buf.writeLong(payload.requestId().getMostSignificantBits());
|
||||
buf.writeLong(payload.requestId().getLeastSignificantBits());
|
||||
}
|
||||
|
||||
public static PingMessagePayload decode(FriendlyByteBuf buf) {
|
||||
public static @NotNull PingMessagePayload decode(@NotNull FriendlyByteBuf buf) {
|
||||
long mostSignificantBits = buf.readLong();
|
||||
long leastSignificantBits = buf.readLong();
|
||||
return new PingMessagePayload(new UUID(mostSignificantBits, leastSignificantBits));
|
||||
}
|
||||
//客户端处理
|
||||
public static void handle(PingMessagePayload msg, Supplier<NetworkEvent.Context> ctx) {
|
||||
public static void handle(PingMessagePayload msg, @NotNull Supplier<NetworkEvent.Context> ctx) {
|
||||
ctx.get().enqueueWork(() -> DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> () -> {
|
||||
// 客户端收到ping请求,立即返回pong
|
||||
CHANNEL.sendToServer(new PongMessagePayload(msg.requestId));
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ package com.leisuretimedock.crossmod.network.toClient;
|
|||
import com.leisuretimedock.crossmod.client.ClientPingHandler;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraftforge.network.NetworkEvent;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
|
@ -19,7 +21,7 @@ public class PingResultPacket {
|
|||
this.pingResults = new HashMap<>(pingResults);
|
||||
this.averageLatencies = new HashMap<>(averageLatencies);
|
||||
}
|
||||
public PingResultPacket(FriendlyByteBuf buf) {
|
||||
public PingResultPacket(@NotNull FriendlyByteBuf buf) {
|
||||
this.timestamp = System.currentTimeMillis();
|
||||
int size = buf.readVarInt();
|
||||
this.pingResults = new HashMap<>(size);
|
||||
|
|
@ -32,11 +34,12 @@ public class PingResultPacket {
|
|||
averageLatencies.put(uuid, avgLatency);
|
||||
}
|
||||
}
|
||||
public static PingResultPacket decode(FriendlyByteBuf buf) {
|
||||
@Contract("_ -> new")
|
||||
public static @NotNull PingResultPacket decode(FriendlyByteBuf buf) {
|
||||
return new PingResultPacket(buf);
|
||||
}
|
||||
|
||||
public void encode(FriendlyByteBuf buf) {
|
||||
public void encode(@NotNull FriendlyByteBuf buf) {
|
||||
buf.writeVarInt(pingResults.size());
|
||||
pingResults.forEach((uuid, ping) -> {
|
||||
buf.writeUUID(uuid);
|
||||
|
|
@ -45,7 +48,7 @@ public class PingResultPacket {
|
|||
});
|
||||
}
|
||||
|
||||
public void handle(Supplier<NetworkEvent.Context> ctx) {
|
||||
public void handle(@NotNull Supplier<NetworkEvent.Context> ctx) {
|
||||
ctx.get().enqueueWork(() -> {
|
||||
// 检查数据时效性(5秒内有效)
|
||||
if (System.currentTimeMillis() - timestamp < 5000) {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ import com.leisuretimedock.crossmod.client.ClientPingHandler;
|
|||
import com.leisuretimedock.crossmod.network.PingRequestManager;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraftforge.network.NetworkEvent;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
|
|
@ -15,7 +17,8 @@ public class PingStatsPacket {
|
|||
this.stats = stats;
|
||||
}
|
||||
|
||||
public static PingStatsPacket decode(FriendlyByteBuf buf) {
|
||||
@Contract("_ -> new")
|
||||
public static @NotNull PingStatsPacket decode(@NotNull FriendlyByteBuf buf) {
|
||||
return new PingStatsPacket(
|
||||
new PingRequestManager.PingStats(
|
||||
buf.readDouble(),
|
||||
|
|
@ -28,7 +31,7 @@ public class PingStatsPacket {
|
|||
);
|
||||
}
|
||||
|
||||
public void encode(FriendlyByteBuf buf) {
|
||||
public void encode(@NotNull FriendlyByteBuf buf) {
|
||||
buf.writeDouble(stats.average());
|
||||
buf.writeLong(stats.max());
|
||||
buf.writeLong(stats.min());
|
||||
|
|
@ -37,7 +40,7 @@ public class PingStatsPacket {
|
|||
buf.writeDouble(stats.packetLossRate());
|
||||
}
|
||||
|
||||
public void handle(Supplier<NetworkEvent.Context> ctx) {
|
||||
public void handle(@NotNull Supplier<NetworkEvent.Context> ctx) {
|
||||
ctx.get().enqueueWork(() -> {
|
||||
ClientPingHandler.handlePingStats(stats);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ import net.minecraft.network.ConnectionProtocol;
|
|||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraftforge.network.*;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
|
@ -26,7 +28,8 @@ public class ResetPacket extends HandshakeMessages.C2SAcknowledge {
|
|||
public ResetPacket() {
|
||||
super();
|
||||
}
|
||||
public static ResetPacket decode(FriendlyByteBuf ignoredBuf) {
|
||||
@Contract("_ -> new")
|
||||
public static @NotNull ResetPacket decode(FriendlyByteBuf ignoredBuf) {
|
||||
return new ResetPacket();
|
||||
}
|
||||
|
||||
|
|
@ -38,7 +41,7 @@ public class ResetPacket extends HandshakeMessages.C2SAcknowledge {
|
|||
|
||||
}
|
||||
|
||||
public static void handler(Supplier<NetworkEvent.Context> ctxSupplier, Logger log) {
|
||||
public static void handler(@NotNull Supplier<NetworkEvent.Context> ctxSupplier, Logger log) {
|
||||
NetworkEvent.Context ctx = ctxSupplier.get();
|
||||
ClientResetManager.isNegotiating.set(true);
|
||||
Connection conn = ctx.getNetworkManager();
|
||||
|
|
|
|||
|
|
@ -3,24 +3,25 @@ package com.leisuretimedock.crossmod.network.toServer;
|
|||
import com.leisuretimedock.crossmod.network.PingRequestManager;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraftforge.network.NetworkEvent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
//Server
|
||||
public record PongMessagePayload(UUID requestId) {
|
||||
public static void encode(PongMessagePayload payload, FriendlyByteBuf buf) {
|
||||
public static void encode(@NotNull PongMessagePayload payload, @NotNull FriendlyByteBuf buf) {
|
||||
buf.writeLong(payload.requestId().getMostSignificantBits());
|
||||
buf.writeLong(payload.requestId().getLeastSignificantBits());
|
||||
}
|
||||
|
||||
public static PongMessagePayload decode(FriendlyByteBuf buf) {
|
||||
public static @NotNull PongMessagePayload decode(@NotNull FriendlyByteBuf buf) {
|
||||
long mostSignificantBits = buf.readLong();
|
||||
long leastSignificantBits = buf.readLong();
|
||||
return new PongMessagePayload(new UUID(mostSignificantBits, leastSignificantBits));
|
||||
}
|
||||
//服务器处理
|
||||
public static void handle(PongMessagePayload msg, Supplier<NetworkEvent.Context> ctx) {
|
||||
public static void handle(PongMessagePayload msg, @NotNull Supplier<NetworkEvent.Context> ctx) {
|
||||
ctx.get().enqueueWork(() -> {
|
||||
PingRequestManager.complete(ctx.get().getSender(),msg.requestId);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ import com.leisuretimedock.crossmod.network.NetworkHandler;
|
|||
import com.leisuretimedock.crossmod.network.toClient.SyncCommonConfigPacket;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraftforge.network.NetworkEvent;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
|
|
@ -18,7 +20,7 @@ public record SyncCommonConfigRequestPacket(int hash) {
|
|||
* @param msg the msg
|
||||
* @param buf the buf
|
||||
*/
|
||||
public static void encode(SyncCommonConfigRequestPacket msg, FriendlyByteBuf buf) {
|
||||
public static void encode(@NotNull SyncCommonConfigRequestPacket msg, @NotNull FriendlyByteBuf buf) {
|
||||
buf.writeInt(msg.hash);
|
||||
}
|
||||
|
||||
|
|
@ -29,7 +31,8 @@ public record SyncCommonConfigRequestPacket(int hash) {
|
|||
* @param buf the buf
|
||||
* @return the sync common config request packet
|
||||
*/
|
||||
public static SyncCommonConfigRequestPacket decode(FriendlyByteBuf buf) {
|
||||
@Contract("_ -> new")
|
||||
public static @NotNull SyncCommonConfigRequestPacket decode(@NotNull FriendlyByteBuf buf) {
|
||||
return new SyncCommonConfigRequestPacket(buf.readInt());
|
||||
}
|
||||
|
||||
|
|
@ -39,7 +42,7 @@ public record SyncCommonConfigRequestPacket(int hash) {
|
|||
* @param msg the msg
|
||||
* @param ctx the ctx
|
||||
*/
|
||||
public static void handle(SyncCommonConfigRequestPacket msg, Supplier<NetworkEvent.Context> ctx) {
|
||||
public static void handle(SyncCommonConfigRequestPacket msg, @NotNull Supplier<NetworkEvent.Context> ctx) {
|
||||
ctx.get().enqueueWork(() -> {
|
||||
if (msg.hash != CrossServerConfigManager.INSTANCE.cacheHash) {
|
||||
NetworkHandler.sendToPlayer(new SyncCommonConfigPacket(CrossServerConfigManager.INSTANCE.serializeToNBT(), CrossServerConfigManager.INSTANCE.calculateConfigHash()), ctx.get().getSender());
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@ import net.minecraftforge.network.NetworkConstants;
|
|||
import net.minecraftforge.network.NetworkDirection;
|
||||
import net.minecraftforge.network.NetworkEvent;
|
||||
import net.minecraftforge.network.simple.SimpleChannel;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
|
|
@ -25,7 +27,7 @@ public class ClientResetManager {
|
|||
public static AtomicBoolean isNegotiating = new AtomicBoolean(false);
|
||||
public static SimpleChannel handshakeChannel;
|
||||
|
||||
public static void init(FMLCommonSetupEvent event) {
|
||||
public static void init(@NotNull FMLCommonSetupEvent event) {
|
||||
|
||||
event.enqueueWork(() -> {
|
||||
if (handshakeField == null) {
|
||||
|
|
@ -55,7 +57,7 @@ public class ClientResetManager {
|
|||
}
|
||||
});
|
||||
}
|
||||
private static Field fetchHandshakeChannel() {
|
||||
private static @Nullable Field fetchHandshakeChannel() {
|
||||
try {
|
||||
return ObfuscationReflectionHelper.findField(NetworkConstants.class, "handshakeChannel");
|
||||
}
|
||||
|
|
@ -65,7 +67,7 @@ public class ClientResetManager {
|
|||
}
|
||||
}
|
||||
|
||||
private static Constructor<NetworkEvent.Context> fetchNetworkEventContext() {
|
||||
private static @Nullable Constructor<NetworkEvent.Context> fetchNetworkEventContext() {
|
||||
try {
|
||||
return ObfuscationReflectionHelper.findConstructor(NetworkEvent.Context.class, Connection.class, NetworkDirection.class, int.class);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import net.minecraftforge.api.distmarker.Dist;
|
|||
import net.minecraftforge.api.distmarker.OnlyIn;
|
||||
import net.minecraftforge.network.NetworkEvent;
|
||||
import net.minecraftforge.registries.GameData;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Objects;
|
||||
|
|
@ -20,7 +21,7 @@ import static net.minecraft.ChatFormatting.BOLD;
|
|||
@OnlyIn(Dist.CLIENT)
|
||||
public class ResetHelper {
|
||||
@SuppressWarnings("UnstableApiUsage")
|
||||
public static boolean clearClient(NetworkEvent.Context context) {
|
||||
public static boolean clearClient(NetworkEvent.@NotNull Context context) {
|
||||
CompletableFuture<Void> future = context.enqueueWork(() -> {
|
||||
log.debug("Clearing");
|
||||
Minecraft minecraft = Minecraft.getInstance();
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ import lombok.Setter;
|
|||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import net.minecraftforge.network.*;
|
||||
import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
|
|
@ -16,7 +18,8 @@ public class ResetPacket extends HandshakeMessages.C2SAcknowledge {
|
|||
public ResetPacket() {
|
||||
super();
|
||||
}
|
||||
public static ResetPacket decode(FriendlyByteBuf buf) {
|
||||
@Contract("_ -> new")
|
||||
public static @NotNull ResetPacket decode(FriendlyByteBuf buf) {
|
||||
return new ResetPacket();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,57 +1,174 @@
|
|||
package com.leisuretimedock.crossmod.util;
|
||||
|
||||
import com.leisuretimedock.crossmod.config.CrossCommonModConfig;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.io.FileWriter;
|
||||
import java.io.PrintWriter;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.HexFormat;
|
||||
|
||||
public class DebugUtils {
|
||||
public static void debugBuffer(FriendlyByteBuf buf) {
|
||||
|
||||
private static PrintWriter logWriter = null;
|
||||
private static final DateTimeFormatter TIME_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");
|
||||
|
||||
private static PrintWriter getLogWriter() {
|
||||
if (!CrossCommonModConfig.getInstance().isLogToFile()) {
|
||||
return null;
|
||||
}
|
||||
if (logWriter == null) {
|
||||
try {
|
||||
String logPath = CrossCommonModConfig.getInstance().getLogFilePath();
|
||||
logWriter = new PrintWriter(new FileWriter(logPath, true), true);
|
||||
logWriter.println("=== CrossMod Debug Log Started at " + LocalDateTime.now().format(TIME_FORMAT) + " ===");
|
||||
} catch (Exception e) {
|
||||
System.err.println("[CrossMod] Failed to create log file: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
return logWriter;
|
||||
}
|
||||
|
||||
private static void log(String message) {
|
||||
// 控制台输出
|
||||
System.out.println(message);
|
||||
|
||||
// 文件输出
|
||||
PrintWriter writer = getLogWriter();
|
||||
if (writer != null) {
|
||||
writer.println(message);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isDebugEnabled() {
|
||||
return CrossCommonModConfig.getInstance().isDebugPackets();
|
||||
}
|
||||
|
||||
public static void debugBuffer(@NotNull FriendlyByteBuf buf) {
|
||||
debugBuffer(buf, "unknown");
|
||||
}
|
||||
|
||||
public static void debugBuffer(@NotNull FriendlyByteBuf buf, String caller) {
|
||||
if (!isDebugEnabled()) return;
|
||||
|
||||
int readable = buf.readableBytes();
|
||||
System.out.println("[Debug] Readable bytes: " + readable);
|
||||
int readerIdx = buf.readerIndex();
|
||||
int writerIdx = buf.writerIndex();
|
||||
|
||||
log("[Debug][" + caller + "] ====================");
|
||||
log("[Debug][" + caller + "] readerIndex: " + readerIdx);
|
||||
log("[Debug][" + caller + "] writerIndex: " + writerIdx);
|
||||
log("[Debug][" + caller + "] readableBytes: " + readable);
|
||||
|
||||
if (readable <= 0) {
|
||||
System.out.println("[Debug] No extra bytes to inspect.");
|
||||
log("[Debug][" + caller + "] No bytes left to inspect.");
|
||||
return;
|
||||
}
|
||||
|
||||
int maxBytes = CrossCommonModConfig.getInstance().getMaxDebugBytes();
|
||||
int bytesToRead = Math.min(readable, maxBytes);
|
||||
|
||||
// 保存当前位置
|
||||
int index = buf.readerIndex();
|
||||
|
||||
// 读取并打印十六进制
|
||||
byte[] bytes = new byte[readable];
|
||||
byte[] bytes = new byte[bytesToRead];
|
||||
buf.readBytes(bytes);
|
||||
String hex = HexFormat.of().formatHex(bytes);
|
||||
System.out.println("[Debug] Extra bytes (hex): " + hex);
|
||||
log("[Debug][" + caller + "] Bytes (hex): " + hex);
|
||||
|
||||
// 尝试以 UTF-8 解码(仅用于辅助分析)
|
||||
try {
|
||||
String utf8 = new String(bytes, StandardCharsets.UTF_8);
|
||||
System.out.println("[Debug] Interpreted as UTF-8 string:\n" + utf8);
|
||||
} catch (Exception e) {
|
||||
System.out.println("[Debug] Failed to interpret as UTF-8: " + e.getMessage());
|
||||
if (bytesToRead < readable) {
|
||||
log("[Debug][" + caller + "] ... (truncated, " + (readable - bytesToRead) + " more bytes)");
|
||||
}
|
||||
|
||||
// 还原读取位置,避免影响其他逻辑
|
||||
// 尝试以 UTF-8 解码
|
||||
try {
|
||||
String utf8 = new String(bytes, StandardCharsets.UTF_8);
|
||||
String printable = utf8.chars()
|
||||
.mapToObj(c -> (char) c)
|
||||
.filter(c -> c >= 32 && c < 127 || c == '\n' || c == '\r')
|
||||
.collect(StringBuilder::new, StringBuilder::append, StringBuilder::append)
|
||||
.toString();
|
||||
if (!printable.isEmpty()) {
|
||||
log("[Debug][" + caller + "] As UTF-8 (printable): " + printable);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
// 还原读取位置
|
||||
buf.readerIndex(index);
|
||||
}
|
||||
public static void debugFullBuffer(FriendlyByteBuf buf) {
|
||||
ByteBuf internal = buf.copy(); // 复制整个缓冲区(包括所有字节)
|
||||
|
||||
public static void debugFullBuffer(@NotNull FriendlyByteBuf buf) {
|
||||
debugFullBuffer(buf, "unknown");
|
||||
}
|
||||
|
||||
public static void debugFullBuffer(@NotNull FriendlyByteBuf buf, String caller) {
|
||||
if (!isDebugEnabled()) return;
|
||||
|
||||
ByteBuf internal = buf.copy();
|
||||
int size = internal.readableBytes();
|
||||
byte[] data = new byte[size];
|
||||
internal.readBytes(data);
|
||||
|
||||
System.out.println("[Debug] Full buffer size: " + size);
|
||||
System.out.println("[Debug] Hex dump:\n" + HexFormat.of().formatHex(data));
|
||||
log("[Debug][" + caller + "] Full buffer size: " + size);
|
||||
|
||||
try {
|
||||
String utf8 = new String(data, StandardCharsets.UTF_8);
|
||||
System.out.println("[Debug] UTF-8 decoded:\n" + utf8);
|
||||
} catch (Exception e) {
|
||||
System.out.println("[Debug] UTF-8 decode failed: " + e.getMessage());
|
||||
int maxBytes = CrossCommonModConfig.getInstance().getMaxDebugBytes();
|
||||
int hexSize = Math.min(size, maxBytes);
|
||||
byte[] hexData = new byte[hexSize];
|
||||
System.arraycopy(data, 0, hexData, 0, hexSize);
|
||||
log("[Debug][" + caller + "] Hex dump (first " + hexSize + " bytes):\n" + HexFormat.of().formatHex(hexData));
|
||||
|
||||
if (size > hexSize) {
|
||||
log("[Debug][" + caller + "] ... (truncated, " + (size - hexSize) + " more bytes)");
|
||||
}
|
||||
|
||||
internal.release(); // 手动释放 copy() 出来的 ByteBuf,防止泄漏
|
||||
internal.release();
|
||||
}
|
||||
|
||||
// 专门用于检测 BundleDelimiterPacket 的额外字节问题
|
||||
public static void checkBundleDelimiterPacket(@NotNull FriendlyByteBuf buf) {
|
||||
if (!CrossCommonModConfig.getInstance().isDebugBundleDelimiter()) return;
|
||||
|
||||
int remaining = buf.readableBytes();
|
||||
log("[Debug][BundleCheck] Remaining bytes after packet: " + remaining);
|
||||
|
||||
if (remaining > 0) {
|
||||
log("[Debug][BundleCheck] *** EXTRA BYTES FOUND: " + remaining + " bytes ***");
|
||||
|
||||
int savedIdx = buf.readerIndex();
|
||||
|
||||
byte[] extra = new byte[remaining];
|
||||
buf.readBytes(extra);
|
||||
|
||||
if (remaining >= 1) {
|
||||
int possiblePacketId = extra[0] & 0xFF;
|
||||
log("[Debug][BundleCheck] First extra byte (possible next packet ID): 0x" + Integer.toHexString(possiblePacketId));
|
||||
}
|
||||
|
||||
// 打印前几个字节的详细信息
|
||||
int showBytes = Math.min(remaining, 32);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < showBytes; i++) {
|
||||
sb.append(String.format("%02X ", extra[i] & 0xFF));
|
||||
}
|
||||
log("[Debug][BundleCheck] Extra bytes hex: " + sb.toString());
|
||||
|
||||
buf.readerIndex(savedIdx);
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭日志文件(在模组卸载时调用)
|
||||
public static void closeLog() {
|
||||
if (logWriter != null) {
|
||||
logWriter.println("=== CrossMod Debug Log Ended at " + LocalDateTime.now().format(TIME_FORMAT) + " ===");
|
||||
logWriter.close();
|
||||
logWriter = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -2,8 +2,11 @@
|
|||
"ltd.mod.client.name.trans_server": "LTD Cross Server Mod",
|
||||
"ltd.mod.client.key": "Open LTD Cross Server Menu",
|
||||
"ltd.mod.client.menu": "Cross Server Menu",
|
||||
"ltd.mod.client.menu.button.1": "H Hub",
|
||||
"ltd.mod.client.menu.button.2": "S Survival",
|
||||
"ltd.mod.client.menu.button.hub": "H Hub",
|
||||
"ltd.mod.client.menu.button.survival": "S Survival",
|
||||
"ltd.mod.client.menu.button.skyblock": "S SkyBlock",
|
||||
"ltd.mod.client.menu.button.resource": "R Resource",
|
||||
"ltd.mod.client.menu.button.minigame": "M MiniGame",
|
||||
"ltd.mod.client.menu.checkbox.show_trans_tip": "Show cross server tip overlay",
|
||||
"ltd.mod.client.menu.checkbox.show_ping_stat": "Show ping stat overlay",
|
||||
"ltd.mod.client.negotiating": "Negotiating...",
|
||||
|
|
@ -38,6 +41,21 @@
|
|||
"ltd.mod.ping.stats.avg_latency": "Average delay: %.1fms",
|
||||
"ltd.mod.ping.stats.packet_loss": "Packet loss rate: %.1f%%",
|
||||
"ltd.mod.ping.stats.sample_count": "Sample count: %d",
|
||||
"ltd.mod.ping.warn.network_latency": "Network latency is high, so it is recommended to reduce the ping frequency"
|
||||
"ltd.mod.ping.warn.network_latency": "Network latency is high, so it is recommended to reduce the ping frequency",
|
||||
"ltd.mod.client.menu.command.header": "[=== LTD Cross Server Menu ===]",
|
||||
"ltd.mod.client.menu.command.hover": "Click to teleport to %s",
|
||||
"crossmod.command.debug.packets.enabled": "[CrossMod] Packet debugging enabled",
|
||||
"crossmod.command.debug.packets.disabled": "[CrossMod] Packet debugging disabled",
|
||||
"crossmod.command.debug.bundledebug.enabled": "[CrossMod] BundleDelimiter debugging enabled",
|
||||
"crossmod.command.debug.bundledebug.disabled": "[CrossMod] BundleDelimiter debugging disabled",
|
||||
"crossmod.command.debug.status.title": "[CrossMod] Debug status:",
|
||||
"crossmod.command.debug.status.packet": "Packet Debug: ",
|
||||
"crossmod.command.debug.status.bundledebug": "BundleDelimiter Debug: ",
|
||||
"crossmod.command.debug.status.maxbytes": "Max bytes: ",
|
||||
"crossmod.command.debug.status.logfile": "Log to file: ",
|
||||
"crossmod.command.debug.enabled": "Enabled",
|
||||
"crossmod.command.debug.disabled": "Disabled",
|
||||
"crossmod.command.debug.enabled_short": "§aEnabled",
|
||||
"crossmod.command.debug.disabled_short": "§cDisabled"
|
||||
|
||||
}
|
||||
|
|
@ -2,8 +2,11 @@
|
|||
"ltd.mod.client.name.trans_server": "LTD跨服传送模组",
|
||||
"ltd.mod.client.key": "打开LTD跨服传送菜单",
|
||||
"ltd.mod.client.menu": "跨服菜单",
|
||||
"ltd.mod.client.menu.button.1": "H 主城",
|
||||
"ltd.mod.client.menu.button.2": "S 生存服",
|
||||
"ltd.mod.client.menu.button.hub": "H 主城",
|
||||
"ltd.mod.client.menu.button.survival": "S 生存服",
|
||||
"ltd.mod.client.menu.button.skyblock": "S 空岛服",
|
||||
"ltd.mod.client.menu.button.resource": "R 资源服",
|
||||
"ltd.mod.client.menu.button.minigame": "M 小游戏服",
|
||||
"ltd.mod.client.menu.checkbox.show_trans_tip": "显示传送提示",
|
||||
"ltd.mod.client.menu.checkbox.show_ping_stat": "显示Ping状态",
|
||||
"ltd.mod.client.negotiating": "重定向中 ...",
|
||||
|
|
@ -38,5 +41,20 @@
|
|||
"ltd.mod.ping.stats.sample_count": "样本数量: %d",
|
||||
"ltd.mod.ping.warn.network_latency": "网络延迟较高,建议减少Ping频率",
|
||||
"ltd.mod.client.menu.button.no_servers": "没有服务器",
|
||||
"ltd.mod.client.overlay.tip": "按 [%s] 打开跨服传送菜单"
|
||||
"ltd.mod.client.overlay.tip": "按 [%s] 打开跨服传送菜单",
|
||||
"ltd.mod.client.menu.command.header": "[=== LTD 跨服传送菜单 ===]",
|
||||
"ltd.mod.client.menu.command.hover": "点击传送到 %s",
|
||||
"crossmod.command.debug.packets.enabled": "[CrossMod] 数据包调试已开启",
|
||||
"crossmod.command.debug.packets.disabled": "[CrossMod] 数据包调试已关闭",
|
||||
"crossmod.command.debug.bundledebug.enabled": "[CrossMod] 捆绑包调试已开启",
|
||||
"crossmod.command.debug.bundledebug.disabled": "[CrossMod] 捆绑包调试已关闭",
|
||||
"crossmod.command.debug.status.title": "[CrossMod] 调试状态:",
|
||||
"crossmod.command.debug.status.packet": "数据包调试: ",
|
||||
"crossmod.command.debug.status.bundledebug": "捆绑包调试: ",
|
||||
"crossmod.command.debug.status.maxbytes": "最大字节数: ",
|
||||
"crossmod.command.debug.status.logfile": "日志输出到文件: ",
|
||||
"crossmod.command.debug.enabled": "已开启",
|
||||
"crossmod.command.debug.disabled": "已关闭",
|
||||
"crossmod.command.debug.enabled_short": "§a已开启",
|
||||
"crossmod.command.debug.disabled_short": "§c已关闭"
|
||||
}
|
||||
|
|
@ -6,8 +6,12 @@
|
|||
"refmap": "ltdcrossteleport.refmap.json",
|
||||
"mixins": [
|
||||
"AccessorMinecraft",
|
||||
"MixinConnection",
|
||||
"MixinMUINetWorkHandler",
|
||||
"ModListSpoofMixin"
|
||||
"MixinPacketDecoder",
|
||||
"MixinPlayerList",
|
||||
"ModListSpoofMixin",
|
||||
"MixinBundlePacket"
|
||||
],
|
||||
"minVersion": "0.8",
|
||||
"injectors": {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
plugins {
|
||||
id 'java'
|
||||
id 'com.github.johnrengelman.shadow' version '7.1.2'
|
||||
id 'com.github.johnrengelman.shadow' version '8.1.1'
|
||||
id("xyz.jpenilla.run-velocity") version "2.3.1"
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user