diff --git a/.idea/intellij-javadocs-4.0.1.xml b/.idea/intellij-javadocs-4.0.1.xml new file mode 100644 index 0000000..3ed9781 --- /dev/null +++ b/.idea/intellij-javadocs-4.0.1.xml @@ -0,0 +1,204 @@ + + + + + UPDATE + false + true + + METHOD + TYPE + FIELD + + + DEFAULT + PUBLIC + PROTECTED + + + + + + ^.*(public|protected|private)*.+interface\s+\w+.* + /**\n + * The interface ${name}.\n +<#if element.typeParameters?has_content> * \n +</#if> +<#list element.typeParameters as typeParameter> + * @param <${typeParameter.name}> the type parameter\n +</#list> + */ + + + ^.*(public|protected|private)*.+enum\s+\w+.* + /**\n + * The enum ${name}.\n + */ + + + ^.*(public|protected|private)*.+class\s+\w+.* + /**\n + * The type ${name}.\n +<#if element.typeParameters?has_content> * \n +</#if> +<#list element.typeParameters as typeParameter> + * @param <${typeParameter.name}> the type parameter\n +</#list> + */ + + + .+ + /**\n + * The type ${name}.\n + */ + + + + + .+ + /**\n + * Instantiates a new ${name}.\n +<#if element.parameterList.parameters?has_content> + *\n +</#if> +<#list element.parameterList.parameters as parameter> + * @param ${parameter.name} the ${paramNames[parameter.name]}\n +</#list> +<#if element.throwsList.referenceElements?has_content> + *\n +</#if> +<#list element.throwsList.referenceElements as exception> + * @throws ${exception.referenceName} the ${exceptionNames[exception.referenceName]}\n +</#list> + */ + + + + + ^.*(public|protected|private)*\s*.*(\w(\s*<.+>)*)+\s+get\w+\s*\(.*\).+ + /**\n + * Gets ${partName}.\n +<#if element.typeParameters?has_content> * \n +</#if> +<#list element.typeParameters as typeParameter> + * @param <${typeParameter.name}> the type parameter\n +</#list> +<#if element.parameterList.parameters?has_content> + *\n +</#if> +<#list element.parameterList.parameters as parameter> + * @param ${parameter.name} the ${paramNames[parameter.name]}\n +</#list> +<#if isNotVoid> + *\n + * @return the ${partName}\n +</#if> +<#if element.throwsList.referenceElements?has_content> + *\n +</#if> +<#list element.throwsList.referenceElements as exception> + * @throws ${exception.referenceName} the ${exceptionNames[exception.referenceName]}\n +</#list> + */ + + + ^.*(public|protected|private)*\s*.*(void|\w(\s*<.+>)*)+\s+set\w+\s*\(.*\).+ + /**\n + * Sets ${partName}.\n +<#if element.typeParameters?has_content> * \n +</#if> +<#list element.typeParameters as typeParameter> + * @param <${typeParameter.name}> the type parameter\n +</#list> +<#if element.parameterList.parameters?has_content> + *\n +</#if> +<#list element.parameterList.parameters as parameter> + * @param ${parameter.name} the ${paramNames[parameter.name]}\n +</#list> +<#if isNotVoid> + *\n + * @return the ${partName}\n +</#if> +<#if element.throwsList.referenceElements?has_content> + *\n +</#if> +<#list element.throwsList.referenceElements as exception> + * @throws ${exception.referenceName} the ${exceptionNames[exception.referenceName]}\n +</#list> + */ + + + ^.*((public\s+static)|(static\s+public))\s+void\s+main\s*\(\s*String\s*(\[\s*\]|\.\.\.)\s+\w+\s*\).+ + /**\n + * The entry point of application.\n + + <#if element.parameterList.parameters?has_content> + *\n +</#if> + * @param ${element.parameterList.parameters[0].name} the input arguments\n +<#if element.throwsList.referenceElements?has_content> + *\n +</#if> +<#list element.throwsList.referenceElements as exception> + * @throws ${exception.referenceName} the ${exceptionNames[exception.referenceName]}\n +</#list> + */ + + + .+ + /**\n + * ${name}<#if isNotVoid> ${return}</#if>.\n +<#if element.typeParameters?has_content> * \n +</#if> +<#list element.typeParameters as typeParameter> + * @param <${typeParameter.name}> the type parameter\n +</#list> +<#if element.parameterList.parameters?has_content> + *\n +</#if> +<#list element.parameterList.parameters as parameter> + * @param ${parameter.name} the ${paramNames[parameter.name]}\n +</#list> +<#if isNotVoid> + *\n + * @return the ${return}\n +</#if> +<#if element.throwsList.referenceElements?has_content> + *\n +</#if> +<#list element.throwsList.referenceElements as exception> + * @throws ${exception.referenceName} the ${exceptionNames[exception.referenceName]}\n +</#list> + */ + + + + + ^.*(public|protected|private)*.+static.*(\w\s\w)+.+ + /**\n + * The constant ${element.getName()}.\n + */ + + + ^.*(public|protected|private)*.*(\w\s\w)+.+ + /**\n + <#if element.parent.isInterface()> + * The constant ${element.getName()}.\n +<#else> + * The ${name}.\n +</#if> */ + + + .+ + /**\n + <#if element.parent.isEnum()> + *${name} ${typeName}.\n +<#else> + * The ${name}.\n +</#if>*/ + + + + + \ No newline at end of file diff --git a/forge-mod/build.gradle b/forge-mod/build.gradle index d288b51..21fd4f4 100644 --- a/forge-mod/build.gradle +++ b/forge-mod/build.gradle @@ -59,12 +59,17 @@ legacyForge { } client { client() + systemProperty 'forge.enabledGameTestNamespaces', mod_id } data { data() + programArguments.addAll '--mod', mod_id, '--all', + '--output', file('src/generated/resources/').absolutePath, + '--existing', file('src/main/resources/').absolutePath } server { server() + systemProperty 'forge.enabledGameTestNamespaces', mod_id } } mods { @@ -97,11 +102,7 @@ dependencies { annotationProcessor 'org.projectlombok:lombok:1.18.24' annotationProcessor 'org.spongepowered:mixin:0.8.5:processor' - modImplementation "curse.maven:easy-villagers-400514:3887794" - modImplementation "curse.maven:xaeros-world-map-317780:6538320" - modImplementation "curse.maven:immersive-aircraft-666014:4679496" - modCompileOnly "curse.maven:modern-ui-352491:5229350" - modImplementation "curse.maven:iceberg-520110:4035917" + modCompileOnly "curse.maven:modern-ui-352491:6199942" } // 编译任务优化 diff --git a/forge-mod/gradle.properties b/forge-mod/gradle.properties index d51ac2d..1e20597 100644 --- a/forge-mod/gradle.properties +++ b/forge-mod/gradle.properties @@ -2,23 +2,23 @@ # This is required to provide enough memory for the Minecraft decompilation process. org.gradle.jvmargs=-Xmx3G org.gradle.daemon=false -neoForge.parchment.minecraftVersion=1.18.2 -neoForge.parchment.mappingsVersion=2022.11.06 +neoForge.parchment.minecraftVersion=1.20.1 +neoForge.parchment.mappingsVersion=2023.09.03 ## Environment Properties # The Minecraft version must agree with the Forge version to get a valid artifact -minecraft_version=1.18.2 +minecraft_version=1.20.1 # The Minecraft version range can use any release version of Minecraft as bounds. # Snapshots, pre-releases, and release candidates are not guaranteed to sort properly # as they do not follow standard versioning conventions. -minecraft_version_range=[1.18.2,1.19) +minecraft_version_range=[1.20.1,1.21) # The Forge version must agree with the Minecraft version to get a valid artifact -forge_version=40.3.0 +forge_version=47.3.4 # The Forge version range can use any version of Forge as bounds or match the loader version range -forge_version_range=[40,) +forge_version_range=[47,) # The loader version range can only use the major version of Forge/FML as bounds -loader_version_range=[40,) +loader_version_range=[47,) # The mapping channel to use for mappings. # The default set of supported mapping channels are ["official", "snapshot", "snapshot_nodoc", "stable", "stable_nodoc"]. # Additional mapping channels can be registered through the "channelProviders" extension in a Gradle plugin. @@ -36,8 +36,8 @@ loader_version_range=[40,) mapping_channel=parchment # The mapping version to query from the mapping channel. # This must match the format required by the mapping channel. -mapping_version=2022.11.06-1.18.2 -mapping_lasting_version=2022.11.06 +mapping_version=2023.09.03-1.20.1 +mapping_lasting_version=2023.09.03 # imgui_version=1.89.0 ## Mod Properties diff --git a/forge-mod/src/main/java/com/leisuretimedock/crossmod/CrossTeleportMod.java b/forge-mod/src/main/java/com/leisuretimedock/crossmod/CrossTeleportMod.java index 30cbe8c..2413bd1 100644 --- a/forge-mod/src/main/java/com/leisuretimedock/crossmod/CrossTeleportMod.java +++ b/forge-mod/src/main/java/com/leisuretimedock/crossmod/CrossTeleportMod.java @@ -1,13 +1,19 @@ package com.leisuretimedock.crossmod; +import com.leisuretimedock.crossmod.command.GotoServerCommand; +import com.leisuretimedock.crossmod.config.CrossServerConfig; +import com.leisuretimedock.crossmod.config.CrossServerConfigManager; import com.leisuretimedock.crossmod.command.PingCommand; import com.leisuretimedock.crossmod.network.NetworkHandler; import com.leisuretimedock.crossmod.network.PingRequestManager; +import com.leisuretimedock.crossmod.network.toClient.GotoServerPayload; import com.leisuretimedock.crossmod.reset.ClientResetManager; +import lombok.extern.slf4j.Slf4j; import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerPlayer; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.event.RegisterCommandsEvent; +import net.minecraftforge.event.TickEvent; import net.minecraftforge.event.entity.player.PlayerEvent; import net.minecraftforge.event.server.ServerStartedEvent; import net.minecraftforge.event.server.ServerStoppedEvent; @@ -16,6 +22,8 @@ import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.IExtensionPoint; import net.minecraftforge.fml.ModLoadingContext; import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.config.ModConfig; +import net.minecraftforge.fml.event.config.ModConfigEvent; import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent; import net.minecraftforge.fml.event.lifecycle.FMLDedicatedServerSetupEvent; import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; @@ -26,16 +34,16 @@ import org.jetbrains.annotations.Nullable; import java.util.Objects; import java.util.UUID; +@Slf4j @Mod(CrossTeleportMod.MOD_ID) public class CrossTeleportMod { public static final String MOD_ID ="ltdcrossteleport"; - - public CrossTeleportMod() { // 注册生命周期事件 ModLoadingContext.get().registerExtensionPoint(IExtensionPoint.DisplayTest.class, () -> new IExtensionPoint.DisplayTest(() -> NetworkConstants.IGNORESERVERONLY, (a, b) -> true)); IEventBus modEventBus = FMLJavaModLoadingContext.get().getModEventBus(); + ModLoadingContext.get().registerConfig(ModConfig.Type.SERVER, CrossServerConfig.SPEC, "cross-server.toml"); if(!FMLEnvironment.dist.isDedicatedServer()) modEventBus.addListener(ClientResetManager::init); NetworkHandler.register(); } @@ -46,6 +54,7 @@ public class CrossTeleportMod { @SubscribeEvent public static void onRegisterCommands(RegisterCommandsEvent event) { PingCommand.register(event.getDispatcher()); + GotoServerCommand.register(event.getDispatcher()); } @SubscribeEvent public static void onPlayerLoggedIn(PlayerEvent.PlayerLoggedInEvent event) { @@ -53,6 +62,16 @@ public class CrossTeleportMod { PingRequestManager.monitor(player); } } + private static int tickCounter = 0; + @SubscribeEvent + public static void onServerTick(TickEvent.ServerTickEvent event) { + if (event.phase == TickEvent.Phase.END) { + tickCounter++; + if (tickCounter % 10 == 0) { + CrossServerConfigManager.INSTANCE.broadHashPacket(); + } + } + } @SubscribeEvent public static void onPlayerLoggedOut(PlayerEvent.PlayerLoggedOutEvent event) { if (event.getEntity() instanceof ServerPlayer player) { @@ -87,4 +106,43 @@ public class CrossTeleportMod { } } + @Mod.EventBusSubscriber(modid = MOD_ID, value = Dist.DEDICATED_SERVER, bus = Mod.EventBusSubscriber.Bus.MOD) + public static class ServerModEvents { + /** + * On config loaded. + * + * @param event the event + */ + @SubscribeEvent + public static void onConfigLoaded(ModConfigEvent.Loading event) { + if (event.getConfig().getSpec() == CrossServerConfig.SPEC) { + CrossServerConfigManager.loading(CrossServerConfigManager.INSTANCE); + } + } + + /** + * On config reloaded. + * + * @param event the event + */ + @SubscribeEvent + public static void onConfigReloaded(ModConfigEvent.Reloading event) { + if (event.getConfig().getSpec() == CrossServerConfig.SPEC) { + CrossServerConfigManager.reloading(CrossServerConfigManager.INSTANCE); + } + } + + /** + * On config unloaded. + * + * @param event the event + */ + @SubscribeEvent + public static void onConfigUnloaded(ModConfigEvent.Unloading event) { + if (event.getConfig().getSpec() == CrossServerConfig.SPEC) { + CrossServerConfigManager.unloading(CrossServerConfigManager.INSTANCE); + } + } + } + } diff --git a/forge-mod/src/main/java/com/leisuretimedock/crossmod/client/KeyBindingHandler.java b/forge-mod/src/main/java/com/leisuretimedock/crossmod/client/KeyBindingHandler.java index 8b424b1..abbb18d 100644 --- a/forge-mod/src/main/java/com/leisuretimedock/crossmod/client/KeyBindingHandler.java +++ b/forge-mod/src/main/java/com/leisuretimedock/crossmod/client/KeyBindingHandler.java @@ -2,25 +2,21 @@ package com.leisuretimedock.crossmod.client; import com.leisuretimedock.crossmod.CrossTeleportMod; import com.leisuretimedock.crossmod.client.gui.CrossServerGui; -import com.leisuretimedock.crossmod.client.gui.GenericIceMessageScreen; -import net.minecraft.ChatFormatting; import net.minecraft.client.KeyMapping; import net.minecraft.client.Minecraft; -import net.minecraft.network.chat.TranslatableComponent; import net.minecraftforge.api.distmarker.Dist; -import net.minecraftforge.client.ClientRegistry; +import net.minecraftforge.client.event.RegisterKeyMappingsEvent; import net.minecraftforge.event.TickEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.common.Mod; -import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent; 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 onRegisterKey(FMLClientSetupEvent event) { - event.enqueueWork(() -> ClientRegistry.registerKeyBinding(OPEN_GUI_KEY)); + public static void onRegisterKeyMappingsEvent (RegisterKeyMappingsEvent event) { + event.register(OPEN_GUI_KEY); } @Mod.EventBusSubscriber(modid = CrossTeleportMod.MOD_ID, value = Dist.CLIENT) diff --git a/forge-mod/src/main/java/com/leisuretimedock/crossmod/client/PluginChannelClient.java b/forge-mod/src/main/java/com/leisuretimedock/crossmod/client/PluginChannelClient.java index 20afbe9..3d617fd 100644 --- a/forge-mod/src/main/java/com/leisuretimedock/crossmod/client/PluginChannelClient.java +++ b/forge-mod/src/main/java/com/leisuretimedock/crossmod/client/PluginChannelClient.java @@ -27,7 +27,7 @@ public class PluginChannelClient { private static final String HANDLER_NAME = CrossTeleportMod.MOD_ID + ":channel"; @SubscribeEvent - public static void onLogin(ClientPlayerNetworkEvent.LoggedInEvent event) { + public static void onLogin(ClientPlayerNetworkEvent.LoggingIn event) { log.debug("[CrossTeleportMod] 玩家登录事件触发"); if (ClientResetManager.isNegotiating.get()) ClientResetManager.isNegotiating.set(false); @@ -87,7 +87,7 @@ public class PluginChannelClient { @SubscribeEvent - public static void onLogout(ClientPlayerNetworkEvent.LoggedOutEvent event) { + public static void onLogout(ClientPlayerNetworkEvent.LoggingOut event) { log.debug("[CrossTeleportMod] 玩家注销事件触发"); Connection connection = event.getConnection(); diff --git a/forge-mod/src/main/java/com/leisuretimedock/crossmod/client/command/GotoCommand.java b/forge-mod/src/main/java/com/leisuretimedock/crossmod/client/command/GotoCommand.java index 7cd675f..30e1de3 100644 --- a/forge-mod/src/main/java/com/leisuretimedock/crossmod/client/command/GotoCommand.java +++ b/forge-mod/src/main/java/com/leisuretimedock/crossmod/client/command/GotoCommand.java @@ -6,7 +6,7 @@ import com.mojang.brigadier.arguments.StringArgumentType; import com.mojang.brigadier.builder.LiteralArgumentBuilder; import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.Commands; -import net.minecraft.network.chat.TranslatableComponent; +import net.minecraft.network.chat.Component; public class GotoCommand { public static void register(CommandDispatcher dispatcher) { @@ -16,7 +16,7 @@ public class GotoCommand { String server = StringArgumentType.getString(ctx, "server"); NetworkHandler.sendTeleportRequest(server); ctx.getSource().sendSuccess( - new TranslatableComponent("ltd.mod.client.request.goto",server), false); + () -> Component.translatable("ltd.mod.client.request.goto",server), false); return 1; })); dispatcher.register(main); diff --git a/forge-mod/src/main/java/com/leisuretimedock/crossmod/client/gui/CrossServerGui.java b/forge-mod/src/main/java/com/leisuretimedock/crossmod/client/gui/CrossServerGui.java index 5f74037..ae7b201 100644 --- a/forge-mod/src/main/java/com/leisuretimedock/crossmod/client/gui/CrossServerGui.java +++ b/forge-mod/src/main/java/com/leisuretimedock/crossmod/client/gui/CrossServerGui.java @@ -1,56 +1,58 @@ package com.leisuretimedock.crossmod.client.gui; import com.leisuretimedock.crossmod.CrossTeleportMod; +import com.leisuretimedock.crossmod.config.CrossServerConfigManager; import com.leisuretimedock.crossmod.client.overlay.CrossServerTipOverLay; import com.leisuretimedock.crossmod.client.overlay.PingOverlayManager; -import com.mojang.blaze3d.systems.RenderSystem; -import com.mojang.blaze3d.vertex.PoseStack; import io.netty.buffer.Unpooled; import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.components.Button; import net.minecraft.client.gui.components.Checkbox; import net.minecraft.client.gui.screens.Screen; -import net.minecraft.client.renderer.GameRenderer; import net.minecraft.network.FriendlyByteBuf; -import net.minecraft.network.chat.TranslatableComponent; +import net.minecraft.network.chat.Component; import net.minecraft.network.protocol.game.ServerboundCustomPayloadPacket; import net.minecraft.resources.ResourceLocation; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.OnlyIn; import org.jetbrains.annotations.NotNull; +import java.util.Map; + @OnlyIn(Dist.CLIENT) public class CrossServerGui extends Screen { - + public final static Component TITLE = Component.translatable("ltd.mod.client.menu"); private static final ResourceLocation CHANNEL_ID = new ResourceLocation(CrossTeleportMod.MOD_ID, "teleport"); private static final ResourceLocation LOGO_TEXTURE = new ResourceLocation(CrossTeleportMod.MOD_ID, "textures/ltd_logo.png"); + // 存储组件引用,以便在渲染时控制顺序 + private Checkbox enableCrCheckBox; + private Checkbox enablePiCheckBox; + private Button closeButton; + private ServerSelectionList serverList; + public CrossServerGui() { - super(new TranslatableComponent("ltd.mod.client.menu")); + super(TITLE); } @Override protected void init() { + // 先添加按钮和复选框(它们应该渲染在列表上方) + initButtons(); + + // 再添加列表(它应该渲染在下方) + initServerList(); + } + + private void initButtons() { int centerX = width / 2; - int centerY = height / 2; - int buttonWidth = 150; - int buttonHeight = 20; - int spacing = 5; + int bottomY = height - 60; // 从底部向上60像素 - addRenderableWidget(new Button(centerX - buttonWidth / 2, centerY - buttonHeight - spacing, - buttonWidth, buttonHeight, new TranslatableComponent("ltd.mod.client.menu.button.1"), btn -> { - sendCustomPayload("connect:lobby"); - onClose(); - })); - - addRenderableWidget(new Button(centerX - buttonWidth / 2, centerY, - buttonWidth, buttonHeight, new TranslatableComponent("ltd.mod.client.menu.button.2"), btn -> { - sendCustomPayload("connect:survival"); - onClose(); - })); - // 添加 Checkbox 控件 - Checkbox enableCrCheckBox = new Checkbox(centerX - buttonWidth / 2, centerY + buttonHeight + spacing + 5, - 150, 20, new TranslatableComponent("ltd.mod.client.menu.checkbox.show_trans_tip"), !CrossServerTipOverLay.isShowOverlay()) { + // 添加 Checkbox 控件 - 显示传送提示 + enableCrCheckBox = new Checkbox(centerX - 150, bottomY, + 140, 20, Component.translatable("ltd.mod.client.menu.checkbox.show_trans_tip"), + !CrossServerTipOverLay.isShowOverlay()) { @Override public void onPress() { super.onPress(); @@ -58,9 +60,11 @@ public class CrossServerGui extends Screen { } }; addRenderableWidget(enableCrCheckBox); - // 添加 Checkbox 控件 - Checkbox enablePiCheckBox = new Checkbox(centerX - buttonWidth / 2, centerY + buttonHeight + spacing + 25, - 150, 20, new TranslatableComponent("ltd.mod.client.menu.checkbox.show_ping_stat"), !PingOverlayManager.isShowOverlay()) { + + // 添加 Checkbox 控件 - 显示ping统计 + enablePiCheckBox = new Checkbox(centerX + 10, bottomY, + 140, 20, Component.translatable("ltd.mod.client.menu.checkbox.show_ping_stat"), + !PingOverlayManager.isShowOverlay()) { @Override public void onPress() { super.onPress(); @@ -68,9 +72,89 @@ public class CrossServerGui extends Screen { } }; addRenderableWidget(enablePiCheckBox); + + // 添加关闭按钮 + closeButton = Button.builder( + Component.translatable("gui.done"), + button -> this.onClose() + ) + .bounds(centerX - 50, bottomY + 30, 100, 20) + .build(); + addRenderableWidget(closeButton); } - private void sendCustomPayload(String message) { + private void initServerList() { + int screenWidth = this.width; + int screenHeight = this.height; + + + // 创建服务器列表,但尺寸要避开按钮区域 + serverList = new ServerSelectionList( + this, + Minecraft.getInstance(), + screenWidth, + screenHeight, + 48, // X位置 + height - 64, // Y位置 + 36, // 条目高度 + CrossServerConfigManager.INSTANCE.getServers() + ); + + // 设置列表属性 + serverList.setRenderBackground(true); + serverList.setRenderTopAndBottom(true); + + + addRenderableWidget(serverList); + } + + @Override + public void render(@NotNull GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTicks) { + + // 渲染标题 + guiGraphics.drawString(this.font, this.title.getString(), this.width / 2 - font.width(this.title.getString()) / 2, 20, 0xFFFFFF); + + // 重要:先渲染列表(在底层) + if (serverList != null) { + serverList.render(guiGraphics, mouseX, mouseY, partialTicks); + } + + // 然后渲染按钮和复选框(在上层) + // 手动调用按钮的render方法,确保它们在最上面 + if (enableCrCheckBox != null) { + enableCrCheckBox.render(guiGraphics, mouseX, mouseY, partialTicks); + } + if (enablePiCheckBox != null) { + enablePiCheckBox.render(guiGraphics, mouseX, mouseY, partialTicks); + } + if (closeButton != null) { + closeButton.render(guiGraphics, mouseX, mouseY, partialTicks); + } + renderLogo(guiGraphics); + } + + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + // 先检查按钮点击 + if (enableCrCheckBox != null && enableCrCheckBox.mouseClicked(mouseX, mouseY, button)) { + return true; + } + if (enablePiCheckBox != null && enablePiCheckBox.mouseClicked(mouseX, mouseY, button)) { + return true; + } + if (closeButton != null && closeButton.mouseClicked(mouseX, mouseY, button)) { + return true; + } + + // 再检查列表点击 + if (serverList != null && serverList.mouseClicked(mouseX, mouseY, button)) { + return true; + } + + return super.mouseClicked(mouseX, mouseY, button); + } + + void sendCustomPayload(String message) { Minecraft mc = Minecraft.getInstance(); if (mc.getConnection() != null) { FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer(256)); @@ -79,38 +163,17 @@ public class CrossServerGui extends Screen { } } - @Override - public void render(@NotNull PoseStack poseStack, int mouseX, int mouseY, float partialTicks) { - // 背景 - this.renderBackground(poseStack); + private void renderLogo(GuiGraphics guiGraphics) { + int logoWidth = 64; // 缩小Logo,为列表腾出空间 + int logoHeight = 64; - // Logo 渲染(缩放绘制) - renderLogo(poseStack); - - // 渲染标题文字 - drawCenteredString(poseStack, this.font, this.title.getString(), this.width / 2 + 5, 10, 0xFFFFFF); - - // 渲染按钮等组件 - super.render(poseStack, mouseX, mouseY, partialTicks); - } - - private void renderLogo(PoseStack poseStack) { - RenderSystem.setShader(GameRenderer::getPositionTexShader); - RenderSystem.setShaderTexture(0, LOGO_TEXTURE); - RenderSystem.enableDepthTest(); - - - int logoWidth = 100; // 你可以改成 150、200 等 - int logoHeight = 100; // 保持比例缩放 - - int x = (this.width - logoWidth) / 2; - int y = 15; - - blit(poseStack, x, y, 0, 0, logoWidth, logoHeight, logoWidth, logoHeight); + int x = (this.width - logoWidth - font.width(this.title.getString()) * 2) / 2; + int y = -5; // 更靠近顶部 + guiGraphics.blit(LOGO_TEXTURE, x, y, 0, 0, logoWidth, logoHeight, logoWidth, logoHeight); } @Override public boolean isPauseScreen() { return false; } -} +} \ No newline at end of file diff --git a/forge-mod/src/main/java/com/leisuretimedock/crossmod/client/gui/GenericIceMessageScreen.java b/forge-mod/src/main/java/com/leisuretimedock/crossmod/client/gui/GenericIceMessageScreen.java index 6061254..fb8f9be 100644 --- a/forge-mod/src/main/java/com/leisuretimedock/crossmod/client/gui/GenericIceMessageScreen.java +++ b/forge-mod/src/main/java/com/leisuretimedock/crossmod/client/gui/GenericIceMessageScreen.java @@ -1,10 +1,8 @@ package com.leisuretimedock.crossmod.client.gui; -import com.mojang.blaze3d.systems.RenderSystem; -import com.mojang.blaze3d.vertex.*; +import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.screens.GenericDirtMessageScreen; import net.minecraft.client.gui.screens.inventory.InventoryScreen; -import net.minecraft.client.renderer.GameRenderer; import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; import net.minecraftforge.client.event.ScreenEvent; @@ -26,32 +24,24 @@ public class GenericIceMessageScreen extends GenericDirtMessageScreen { } @Override - public void render(@NotNull PoseStack poseStack, int mouseX, int mouseY, float partialTick) { - super.render(poseStack, mouseX, mouseY, partialTick); + public void render(@NotNull GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTick) { + super.render(guiGraphics, mouseX, mouseY, partialTick); if (minecraft != null && minecraft.player != null){ - InventoryScreen.renderEntityInInventory(width / 2, height / 2, 30, (float) width / 2 - mouseX, (float) height / 2 - mouseY, minecraft.player); + InventoryScreen.renderEntityInInventoryFollowsMouse(guiGraphics, width / 2, height / 2, 30, (float) width / 2 - mouseX, (float) height / 2 - mouseY, minecraft.player); } } @Override - public void renderDirtBackground(int vOffset) { - renderIceBackground(vOffset,200, 200, 200, 255); + public void renderDirtBackground(@NotNull GuiGraphics guiGraphics) { + renderIceBackground(guiGraphics,1.0F, 1.0F, 1.0F, 1.0F); } - public void renderIceBackground(int vOffset, int r, int g, int b, int a) { - Tesselator tesselator = Tesselator.getInstance(); - BufferBuilder bufferbuilder = tesselator.getBuilder(); - RenderSystem.setShader(GameRenderer::getPositionTexColorShader); - RenderSystem.setShaderTexture(0, ICE); - RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); - float f = 32.0F; - bufferbuilder.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.POSITION_TEX_COLOR); - bufferbuilder.vertex(0.0, this.height, 0.0).uv(0.0F, (float)this.height / f + (float)vOffset).color(r, g, b, a).endVertex(); - bufferbuilder.vertex(this.width, this.height, 0.0).uv((float)this.width / f, (float)this.height / f + (float)vOffset).color(r, g, b, a).endVertex(); - bufferbuilder.vertex(this.width, 0.0, 0.0).uv((float)this.width / f, (float)vOffset).color(r, g, b, a).endVertex(); - bufferbuilder.vertex(0.0, 0.0, 0.0).uv(0.0F, (float)vOffset).color(r, g, b, a).endVertex(); - tesselator.end(); - MinecraftForge.EVENT_BUS.post(new ScreenEvent.BackgroundDrawnEvent(this, new PoseStack())); + @SuppressWarnings("UnstableApiUsage") + public void renderIceBackground(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); + MinecraftForge.EVENT_BUS.post(new ScreenEvent.BackgroundRendered(this, guiGraphics)); } } diff --git a/forge-mod/src/main/java/com/leisuretimedock/crossmod/client/gui/ServerSelectionList.java b/forge-mod/src/main/java/com/leisuretimedock/crossmod/client/gui/ServerSelectionList.java new file mode 100644 index 0000000..cfafb79 --- /dev/null +++ b/forge-mod/src/main/java/com/leisuretimedock/crossmod/client/gui/ServerSelectionList.java @@ -0,0 +1,92 @@ +package com.leisuretimedock.crossmod.client.gui; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.components.ObjectSelectionList; +import net.minecraft.network.chat.Component; +import org.jetbrains.annotations.NotNull; + +import java.util.Map; + +public class ServerSelectionList extends ObjectSelectionList { + private final CrossServerGui parentScreen; + + public ServerSelectionList(CrossServerGui parent, Minecraft mc, int width, int height, int y0, int y1, int itemHeight, Map servers) { + super(mc, width, height, y0, y1, itemHeight); + this.parentScreen = parent; + + // 添加服务器条目 + if (servers.isEmpty()) { + this.addEntry(new ServerEntry(Component.translatable("ltd.mod.client.menu.button.no_servers"), null, parentScreen)); + } else { + servers.forEach((server_name, translate_key) -> { + this.addEntry(new ServerEntry(Component.translatable(translate_key), server_name, parentScreen)); + }); + } + } + + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + if (button == 0) { + ServerEntry entry = this.getEntryAtPosition(mouseX, mouseY); + if (entry != null && entry.serverId != null) { + parentScreen.sendCustomPayload("connect:" + entry.serverId); + parentScreen.onClose(); + return true; + } + } + return super.mouseClicked(mouseX, mouseY, button); + } + public static class ServerEntry extends ObjectSelectionList.Entry { + private final Component displayName; + private final String serverId; + private Button serverButton; + + public ServerEntry(Component displayName, String serverId, CrossServerGui parent) { + this.displayName = displayName; + this.serverId = serverId; + + if (serverId != null) { + this.serverButton = Button.builder(displayName, button -> { + parent.sendCustomPayload("connect:" + serverId); + parent.onClose(); + }).build(); + } + } + + @Override + public void render(@NotNull GuiGraphics guiGraphics, int index, int top, int left, int width, int height, + int mouseX, int mouseY, boolean isMouseOver, float partialTick) { + + if (serverButton != null) { + // 更新按钮位置和大小 + serverButton.setX(left + 5); + serverButton.setY(top + 2); + serverButton.setWidth(width - 10); + serverButton.setHeight(height - 4); + + // 渲染按钮 + serverButton.render(guiGraphics, mouseX, mouseY, partialTick); + } else { + // "无服务器"条目 + int textX = left + (width - Minecraft.getInstance().font.width(displayName)) / 2; + int textY = top + (height - Minecraft.getInstance().font.lineHeight) / 2; + guiGraphics.drawString(Minecraft.getInstance().font, displayName, textX, textY, 0xFFAAAAAA); + } + } + + @Override + public boolean mouseClicked(double mouseX, double mouseY, int button) { + if (serverButton != null) { + return serverButton.mouseClicked(mouseX, mouseY, button); + } + return false; + } + + @Override + public @NotNull Component getNarration() { + return displayName; + } + } +} diff --git a/forge-mod/src/main/java/com/leisuretimedock/crossmod/client/overlay/CrossServerTipOverLay.java b/forge-mod/src/main/java/com/leisuretimedock/crossmod/client/overlay/CrossServerTipOverLay.java index 741341d..fd0c08e 100644 --- a/forge-mod/src/main/java/com/leisuretimedock/crossmod/client/overlay/CrossServerTipOverLay.java +++ b/forge-mod/src/main/java/com/leisuretimedock/crossmod/client/overlay/CrossServerTipOverLay.java @@ -4,14 +4,18 @@ import com.leisuretimedock.crossmod.client.KeyBindingHandler; import com.mojang.blaze3d.vertex.PoseStack; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.Font; -import net.minecraft.client.gui.GuiComponent; +import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.renderer.entity.ItemRenderer; +import net.minecraft.client.resources.language.I18n; +import net.minecraft.network.chat.Component; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.Items; -import net.minecraftforge.client.gui.ForgeIngameGui; -import net.minecraftforge.client.gui.IIngameOverlay; +import net.minecraftforge.client.gui.overlay.ForgeGui; +import net.minecraftforge.client.gui.overlay.IGuiOverlay; -public class CrossServerTipOverLay implements IIngameOverlay { +import java.awt.*; + +public class CrossServerTipOverLay implements IGuiOverlay { public static final CrossServerTipOverLay INSTANCE = new CrossServerTipOverLay(); private static boolean showOverlay = false; private static final Minecraft mc = Minecraft.getInstance(); @@ -22,7 +26,7 @@ public class CrossServerTipOverLay implements IIngameOverlay { showOverlay = show; } @Override - public void render(ForgeIngameGui forgeIngameGui, PoseStack poseStack, float v, int i, int i1) { + public void render(ForgeGui forgeGui, GuiGraphics guiGraphics, float v, int i, int i1) { if ( !showOverlay || mc.player == null || mc.level == null) return; int x = 10; int y = 10; @@ -31,15 +35,14 @@ public class CrossServerTipOverLay implements IIngameOverlay { // 1. 原版钟物品 ItemStack clockStack = new ItemStack(Items.CLOCK); - - // 2. 渲染钟图标(含动画帧) - itemRenderer.renderAndDecorateItem(clockStack, x, y); - itemRenderer.renderGuiItemDecorations(mc.font, clockStack, x, y); - + PoseStack poseStack = new PoseStack(); + poseStack.translate(10, 10, 10); + // 2. 渲染钟图标 + guiGraphics.renderItem(clockStack, x, y); + guiGraphics.renderItemDecorations(font,clockStack, x, y); // 3. 绘制提示文字 - String keyText = KeyBindingHandler.OPEN_GUI_KEY.getTranslatedKeyMessage().getString(); // 可动态从 KeyMapping 获取 - String text = "按 [" + keyText.toUpperCase() + "] 打开跨服传送菜单"; - GuiComponent.drawString(poseStack, font, text, x + 20, y + 6, 0xFFFFFF); + String text = Component.translatable("ltd.mod.client.overlay.tip", KeyBindingHandler.OPEN_GUI_KEY.getTranslatedKeyMessage()).getString(); + guiGraphics.drawString(font, text, x + 20, y + 6, 0xFFFFFF); } } diff --git a/forge-mod/src/main/java/com/leisuretimedock/crossmod/client/overlay/OverlayRenderer.java b/forge-mod/src/main/java/com/leisuretimedock/crossmod/client/overlay/OverlayRenderer.java index adaf733..a00f3db 100644 --- a/forge-mod/src/main/java/com/leisuretimedock/crossmod/client/overlay/OverlayRenderer.java +++ b/forge-mod/src/main/java/com/leisuretimedock/crossmod/client/overlay/OverlayRenderer.java @@ -2,26 +2,20 @@ package com.leisuretimedock.crossmod.client.overlay; import com.leisuretimedock.crossmod.CrossTeleportMod; import net.minecraftforge.api.distmarker.Dist; -import net.minecraftforge.client.gui.OverlayRegistry; +import net.minecraftforge.client.event.RegisterGuiOverlaysEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.common.Mod; -import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent; @Mod.EventBusSubscriber(modid = CrossTeleportMod.MOD_ID, value = Dist.CLIENT, bus = Mod.EventBusSubscriber.Bus.MOD) public class OverlayRenderer { @SubscribeEvent - public static void onRender(FMLClientSetupEvent event) { - event.enqueueWork(() -> { - OverlayRegistry.registerOverlayTop( - "cross_server_tip", - CrossServerTipOverLay.INSTANCE - ); - OverlayRegistry.registerOverlayTop( - "ping_debug", - PingOverlayManager.INSTANCE - ); - }); + public static void onRender(RegisterGuiOverlaysEvent event) { + event.registerAboveAll("cross_server_tip", CrossServerTipOverLay.INSTANCE); + event.registerAboveAll( + "ping_debug", + PingOverlayManager.INSTANCE + ); } } diff --git a/forge-mod/src/main/java/com/leisuretimedock/crossmod/client/overlay/PingOverlayManager.java b/forge-mod/src/main/java/com/leisuretimedock/crossmod/client/overlay/PingOverlayManager.java index 5de2c06..635b1e9 100644 --- a/forge-mod/src/main/java/com/leisuretimedock/crossmod/client/overlay/PingOverlayManager.java +++ b/forge-mod/src/main/java/com/leisuretimedock/crossmod/client/overlay/PingOverlayManager.java @@ -1,19 +1,18 @@ package com.leisuretimedock.crossmod.client.overlay; import com.leisuretimedock.crossmod.client.ClientPingHandler; -import com.mojang.blaze3d.vertex.PoseStack; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.Font; -import net.minecraft.client.gui.GuiComponent; -import net.minecraftforge.client.gui.ForgeIngameGui; -import net.minecraftforge.client.gui.IIngameOverlay; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraftforge.client.gui.overlay.ForgeGui; +import net.minecraftforge.client.gui.overlay.IGuiOverlay; import java.util.ArrayList; import java.util.List; -public class PingOverlayManager implements IIngameOverlay { +public class PingOverlayManager implements IGuiOverlay { public static final PingOverlayManager INSTANCE = new PingOverlayManager(); - private static boolean showOverlay = true; + private static boolean showOverlay = false; private static final Minecraft mc = Minecraft.getInstance(); public static boolean isShowOverlay() { return !showOverlay || mc.player == null || mc.level == null; @@ -29,7 +28,7 @@ public class PingOverlayManager implements IIngameOverlay { } @Override - public void render(ForgeIngameGui gui, PoseStack poseStack, float partialTick, int width, int height) { + public void render(ForgeGui gui, GuiGraphics guiGraphics, float partialTick, int width, int height) { if (!showOverlay || mc.player == null || mc.level == null) { return; } @@ -50,10 +49,10 @@ public class PingOverlayManager implements IIngameOverlay { int y = findSuitableYPosition(height, totalHeight); // 绘制背景 - drawBackground(poseStack, x, y, maxWidth, totalHeight, font); + drawBackground(guiGraphics, x, y, maxWidth, totalHeight, font); // 绘制文本 - drawTextLines(gui, poseStack, font, allLines, x, y); + drawTextLines(guiGraphics, font, allLines, x, y); } private List getAllDisplayLines() { @@ -95,18 +94,18 @@ public class PingOverlayManager implements IIngameOverlay { return baseY; } - private void drawBackground(PoseStack poseStack, int x, int y, int width, int height, Font font) { - GuiComponent.fill(poseStack, + private void drawBackground(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(ForgeIngameGui gui, PoseStack poseStack, Font font, List lines, int x, int y) { + private void drawTextLines(GuiGraphics guiGraphics, Font font, List lines, int x, int y) { for (int i = 0; i < lines.size(); i++) { String line = lines.get(i); if (!line.isEmpty()) { - gui.getFont().draw(poseStack, line, + guiGraphics.drawString(font, line, x, y + i * font.lineHeight, TEXT_COLOR); diff --git a/forge-mod/src/main/java/com/leisuretimedock/crossmod/command/GotoServerCommand.java b/forge-mod/src/main/java/com/leisuretimedock/crossmod/command/GotoServerCommand.java new file mode 100644 index 0000000..02ee27b --- /dev/null +++ b/forge-mod/src/main/java/com/leisuretimedock/crossmod/command/GotoServerCommand.java @@ -0,0 +1,53 @@ +package com.leisuretimedock.crossmod.command; + +import com.leisuretimedock.crossmod.network.NetworkHandler; +import com.leisuretimedock.crossmod.network.toClient.GotoServerPayload; +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.commands.arguments.EntityArgument; +import net.minecraft.network.chat.Component; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.player.Player; + +import java.util.Collection; + +public class GotoServerCommand { + public static void register(CommandDispatcher dispatcher) { + LiteralArgumentBuilder main = Commands.literal("server") + .requires(cs -> cs.hasPermission(2)) + .then(Commands.argument("players", EntityArgument.players()) + .then(Commands.literal("goto") + .then(Commands.argument("server", StringArgumentType.string()) + .executes(ctx -> { + String server = StringArgumentType.getString(ctx, "server"); + Collection players = EntityArgument.getPlayers(ctx, "players"); + players.forEach(p -> NetworkHandler.sendToPlayer(new GotoServerPayload(server), p)); + ctx.getSource().sendSuccess( + () -> Component.translatable("ltd.mod.client.request.goto",server), false); + return 1; + }) + ) + ) + ) + .then(Commands.literal("goto") + .then(Commands.argument("server", StringArgumentType.string()) + .executes(ctx -> { + CommandSourceStack source = ctx.getSource(); + ServerPlayer player = source.getPlayer(); + if (player != null) { + String server = StringArgumentType.getString(ctx, "server"); + NetworkHandler.sendToPlayer(new GotoServerPayload(server), player); + source.sendSuccess( + () -> Component.translatable("ltd.mod.client.request.goto",server), false); + } + source.sendFailure(Component.literal("Request a player")); + return 1; + }) + ) + ); + dispatcher.register(main); + } +} diff --git a/forge-mod/src/main/java/com/leisuretimedock/crossmod/command/PingCommand.java b/forge-mod/src/main/java/com/leisuretimedock/crossmod/command/PingCommand.java index 156f2af..3b03be4 100644 --- a/forge-mod/src/main/java/com/leisuretimedock/crossmod/command/PingCommand.java +++ b/forge-mod/src/main/java/com/leisuretimedock/crossmod/command/PingCommand.java @@ -11,7 +11,7 @@ import net.minecraft.ChatFormatting; import net.minecraft.commands.CommandSourceStack; import net.minecraft.commands.Commands; import net.minecraft.commands.arguments.EntityArgument; -import net.minecraft.network.chat.TranslatableComponent; +import net.minecraft.network.chat.Component; import net.minecraft.server.level.ServerPlayer; import java.util.Collection; @@ -92,7 +92,7 @@ public class PingCommand { private static int executePlayerReport(CommandSourceStack source, Collection players) throws CommandSyntaxException { if (players.isEmpty()) { - source.sendSuccess(new TranslatableComponent("ltd.mod.ping.error.no_players"), false); + source.sendSuccess(() -> Component.translatable("ltd.mod.ping.error.no_players"), false); return 0; } @@ -100,7 +100,7 @@ public class PingCommand { Map results = PingRequestManager.getLatestPingsForPlayers(players); if (results.isEmpty()) { - source.sendSuccess(new TranslatableComponent("ltd.mod.ping.info.no_data"), false); + source.sendSuccess(() -> Component.translatable("ltd.mod.ping.info.no_data"), false); return Command.SINGLE_SUCCESS; } @@ -115,7 +115,7 @@ public class PingCommand { Map results = PingRequestManager.getAllLatestPings(); if (results.isEmpty()) { - source.sendSuccess(new TranslatableComponent("ltd.mod.ping.info.no_data"), false); + source.sendSuccess(() -> Component.translatable("ltd.mod.ping.info.no_data"), false); return Command.SINGLE_SUCCESS; } @@ -134,7 +134,7 @@ public class PingCommand { PingRequestManager.PingStats stats = PingRequestManager.getGlobalPingStats(); if (stats.sampleCount() == 0) { - source.sendSuccess(new TranslatableComponent("ltd.mod.ping.info.no_data"), false); + source.sendSuccess(() -> Component.translatable("ltd.mod.ping.info.no_data"), false); return Command.SINGLE_SUCCESS; } @@ -147,28 +147,28 @@ public class PingCommand { private static int executeSinglePing(CommandSourceStack source) throws CommandSyntaxException { ServerPlayer player = source.getPlayerOrException(); if(!PingRequestManager.isMonitored(player.getUUID())) { - source.sendFailure(new TranslatableComponent("ltd.mod.ping.error.not_monitored.self")); + source.sendFailure(Component.translatable("ltd.mod.ping.error.not_monitored.self")); return -1; } PingRequestManager.ping(player); - source.sendSuccess(new TranslatableComponent("ltd.mod.ping.success.ping_self"), false); + source.sendSuccess(() -> Component.translatable("ltd.mod.ping.success.ping_self"), false); return Command.SINGLE_SUCCESS; } private static int executePingPlayers(CommandSourceStack source, Collection players) throws CommandSyntaxException { if (players.isEmpty()) { - source.sendSuccess(new TranslatableComponent("ltd.mod.ping.error.no_players"), false); + source.sendSuccess(() -> Component.translatable("ltd.mod.ping.error.no_players"), false); return 0; } players.forEach(player -> { if(!PingRequestManager.isMonitored(player.getUUID())) { - source.sendFailure(new TranslatableComponent("ltd.mod.ping.error.not_monitored.other", + source.sendFailure(Component.translatable("ltd.mod.ping.error.not_monitored.other", player.getScoreboardName())); } else { PingRequestManager.ping(player); - source.sendSuccess(new TranslatableComponent("ltd.mod.ping.success.ping_other", + source.sendSuccess(() -> Component.translatable("ltd.mod.ping.success.ping_other", player.getScoreboardName()), false); } }); @@ -180,7 +180,7 @@ public class PingCommand { int count, int interval) { if (players.isEmpty()) { - source.sendSuccess(new TranslatableComponent("ltd.mod.ping.error.no_players"), false); + source.sendSuccess(() -> Component.translatable("ltd.mod.ping.error.no_players"), false); return 0; } @@ -188,12 +188,12 @@ public class PingCommand { if (PingRequestManager.sendMultiplePings(player, count, interval)) { source.sendSuccess( player.getScoreboardName().equals(source.getTextName()) ? - new TranslatableComponent("ltd.mod.ping.success.multiping.start.self", count, interval) : - new TranslatableComponent("ltd.mod.ping.success.multiping.start.other", player.getScoreboardName(), count, interval), + () -> Component.translatable("ltd.mod.ping.success.multiping.start.self", count, interval) : + () -> Component.translatable("ltd.mod.ping.success.multiping.start.other", player.getScoreboardName(), count, interval), false); } else { source.sendFailure( - new TranslatableComponent( + Component.translatable( player.getScoreboardName().equals(source.getTextName()) ? "ltd.mod.ping.error.multiping.fail.self" : "ltd.mod.ping.error.multiping.fail.other", @@ -209,28 +209,28 @@ public class PingCommand { ServerPlayer player = source.getPlayerOrException(); if (monitor) { PingRequestManager.monitor(player); - source.sendSuccess(new TranslatableComponent("ltd.mod.ping.success.monitor.self"), false); + source.sendSuccess(() -> Component.translatable("ltd.mod.ping.success.monitor.self"), false); } else { PingRequestManager.unmonitor(player); - source.sendSuccess(new TranslatableComponent("ltd.mod.ping.success.unmonitor.self"), false); + source.sendSuccess(() -> Component.translatable("ltd.mod.ping.success.unmonitor.self"), false); } return Command.SINGLE_SUCCESS; } private static int executeToggleMonitoring(CommandSourceStack source, Collection players, boolean monitor) throws CommandSyntaxException { if (players.isEmpty()) { - source.sendSuccess(new TranslatableComponent("ltd.mod.ping.error.no_players"), false); + source.sendSuccess(() -> Component.translatable("ltd.mod.ping.error.no_players"), false); return 0; } players.forEach(player -> { if (monitor) { PingRequestManager.monitor(player); - source.sendFailure(new TranslatableComponent("ltd.mod.ping.error.not_monitored.other", + source.sendFailure(Component.translatable("ltd.mod.ping.error.not_monitored.other", player.getScoreboardName())); } else { PingRequestManager.unmonitor(player); - source.sendSuccess(new TranslatableComponent("ltd.mod.ping.success.ping_other", + source.sendSuccess(() -> Component.translatable("ltd.mod.ping.success.ping_other", player.getScoreboardName()), false); } }); @@ -239,37 +239,37 @@ public class PingCommand { } private static void sendTextReport(ServerPlayer player, Map results) { - player.sendMessage(new TranslatableComponent("ltd.mod.ping.title.report").withStyle(ChatFormatting.GOLD), - player.getUUID()); + player.displayClientMessage(Component.translatable("ltd.mod.ping.title.report").withStyle(ChatFormatting.GOLD), + true); results.forEach((uuid, ping) -> { - player.sendMessage( - new TranslatableComponent( + player.displayClientMessage( + Component.translatable( "ltd.mod.ping.report.entry", uuid.toString().substring(0, 8), ping, PingRequestManager.getAverageLatency(uuid), PingRequestManager.getPacketLossRate(uuid)), - player.getUUID() + true ); }); } private static void sendStatsTextReport(ServerPlayer player, PingRequestManager.PingStats stats) { - player.sendMessage(new TranslatableComponent("ltd.mod.ping.title.stats").withStyle(ChatFormatting.GOLD), - player.getUUID()); - player.sendMessage(new TranslatableComponent( - "ltd.mod.ping.stats.average", stats.average()), player.getUUID()); - player.sendMessage(new TranslatableComponent( - "ltd.mod.ping.stats.max", stats.max()), player.getUUID()); - player.sendMessage(new TranslatableComponent( - "ltd.mod.ping.stats.min", stats.max()), player.getUUID()); - player.sendMessage(new TranslatableComponent( - "ltd.mod.ping.stats.avg_latency", stats.averageLatency()), player.getUUID()); - player.sendMessage(new TranslatableComponent( - "ltd.mod.ping.stats.packet_loss", stats.packetLossRate()), player.getUUID()); - player.sendMessage(new TranslatableComponent( - "ltd.mod.ping.stats.sample_count", stats.sampleCount()), player.getUUID()); + player.displayClientMessage(Component.translatable("ltd.mod.ping.title.stats").withStyle(ChatFormatting.GOLD), + true); + player.displayClientMessage(Component.translatable( + "ltd.mod.ping.stats.average", stats.average()), true); + player.displayClientMessage(Component.translatable( + "ltd.mod.ping.stats.max", stats.max()), true); + player.displayClientMessage(Component.translatable( + "ltd.mod.ping.stats.min", stats.max()), true); + player.displayClientMessage(Component.translatable( + "ltd.mod.ping.stats.avg_latency", stats.averageLatency()), true); + player.displayClientMessage(Component.translatable( + "ltd.mod.ping.stats.packet_loss", stats.packetLossRate()), true); + player.displayClientMessage(Component.translatable( + "ltd.mod.ping.stats.sample_count", stats.sampleCount()), true); } } \ No newline at end of file diff --git a/forge-mod/src/main/java/com/leisuretimedock/crossmod/config/CrossServerConfig.java b/forge-mod/src/main/java/com/leisuretimedock/crossmod/config/CrossServerConfig.java new file mode 100644 index 0000000..a33941e --- /dev/null +++ b/forge-mod/src/main/java/com/leisuretimedock/crossmod/config/CrossServerConfig.java @@ -0,0 +1,31 @@ +package com.leisuretimedock.crossmod.config; + +import net.minecraftforge.common.ForgeConfigSpec; +import org.jetbrains.annotations.NotNull; + +import java.util.Arrays; +import java.util.List; + +public class CrossServerConfig { + private static final ForgeConfigSpec.Builder BUILDER = new ForgeConfigSpec.Builder(); + public static ForgeConfigSpec SPEC; + public static final ForgeConfigSpec.ConfigValue> SERVER_LIST; + static { + BUILDER.comment("Cross Server Config").push("servers"); + SERVER_LIST = BUILDER + .comment("Server list in format: : ") + .defineList("serverList", + Arrays.asList( + "lobby: ltd.mod.client.menu.button.1", + "survival: ltd.mod.client.menu.button.2" + ), + obj -> obj instanceof String str && checkSyntax(str) + ); + + BUILDER.pop(); + SPEC = BUILDER.build(); + } + public static boolean checkSyntax(@NotNull String input) { + return CrossServerConfigManager.SYNTAX.matcher(input).matches(); + } +} diff --git a/forge-mod/src/main/java/com/leisuretimedock/crossmod/config/CrossServerConfigManager.java b/forge-mod/src/main/java/com/leisuretimedock/crossmod/config/CrossServerConfigManager.java new file mode 100644 index 0000000..351780f --- /dev/null +++ b/forge-mod/src/main/java/com/leisuretimedock/crossmod/config/CrossServerConfigManager.java @@ -0,0 +1,166 @@ +package com.leisuretimedock.crossmod.config; + +import com.leisuretimedock.crossmod.network.NetworkHandler; +import com.leisuretimedock.crossmod.network.toClient.CommonConfigHashInformPacket; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import net.minecraft.nbt.CompoundTag; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Unmodifiable; + +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static java.util.regex.Pattern.compile; + +@Slf4j +public class CrossServerConfigManager { + public static CrossServerConfigManager INSTANCE = new CrossServerConfigManager(); + public static final Pattern SYNTAX = + compile("([a-zA-Z]\\w+):\\s+([_.\\w]+)"); + /** + * The constant cacheTag. + */ + // ========= 缓存 ======== + public volatile CompoundTag cacheTag = null; + /** + * The constant cacheHash. + */ + public volatile int cacheHash = -1; + + @Getter + private final Map servers = new TreeMap<>(); + + private @NotNull @Unmodifiable Map parseServer(@NotNull List servers) { + Map serverMap = new TreeMap<>(); + for (String server : servers) { + Matcher matcher = SYNTAX.matcher(server); + if (matcher.matches()) { + String key = matcher.group(1); // 第一部分:[a-zA-Z]\w+ + String value = matcher.group(2); // 第二部分:[_.\w]+ + if(!serverMap.containsKey(key)) { + serverMap.put(key, value); + } else { + log.warn("Duplicate server name '{}' found in config, skip it", key); + } + } + } + return Collections.unmodifiableMap(serverMap); + } + + public void reloadAll() { + try { + clear(); + servers.putAll(parseServer(CrossServerConfig.SERVER_LIST.get())); + cacheTag = serializeToNBT(); + } catch (Exception e) { + log.error("Failed to reload configs", e); + cacheHash = -1; + cacheTag = null; + } + + } + + /** + * Clear. + */ + public void clear() { + servers.clear(); + } + + public synchronized CompoundTag serializeToNBT() { + if (cacheHash == calculateConfigHash() && cacheTag != null) return cacheTag; + CompoundTag tag = new CompoundTag(); + serializeMap(tag, "servers", this.servers); + cacheHash = calculateConfigHash(); + cacheTag = tag; + return tag; + } + + private void serializeMap(CompoundTag parent, String key, @NotNull Map map) { + CompoundTag mapTag = new CompoundTag(); + for (Map.Entry entry : map.entrySet()) { + mapTag.putString(entry.getKey(), entry.getValue()); + } + parent.put(key, mapTag); + } + + + /** + * 从NBT反序列化配置管理器状态 + * + * @param tag the tag + */ + public void deserializeFromNBT(@NotNull CompoundTag tag) { + clear(); + deserializeMap(tag, "servers", servers); + } + + private void deserializeMap(@NotNull CompoundTag parent, String key, Map map) { + if (parent.contains(key)) { + CompoundTag mapTag = parent.getCompound(key); + for (String key_ : mapTag.getAllKeys()) { + map.put(key_, mapTag.getString(key_)); + } + } + } + + /** + * Loading. + * + * @param manager the manager + */ + public static void loading(@NotNull CrossServerConfigManager manager) { + manager.reloadAll(); + } + + /** + * Reloading. + * + * @param manager the manager + */ + public static void reloading(@NotNull CrossServerConfigManager manager) { + manager.reloadAll(); + } + + /** + * Unloading. + * + * @param manager the manager + */ + public static void unloading(CrossServerConfigManager manager) { + if(manager != null) manager.clear(); + } + + public int calculateConfigHash() { + // 使用FNV-1a哈希算法 + int hash = 0x811c9dc5; // FNV偏移基础值 + hash = fnv1aHashMap(hash, servers); + return hash; + } + + private int fnv1aHashString(int hash, @NotNull String str) { + for (int i = 0; i < str.length(); i++) { + hash ^= str.charAt(i); + hash *= 0x01000193; + } + return hash; + } + private int fnv1aHashMap(int hash, @NotNull Map map) { + for (Map.Entry entry : map.entrySet()) { + hash = fnv1aHashString(hash, entry.getKey()); + hash = fnv1aHashString(hash, entry.getValue()); + + } + return hash; + } + /** + * Broad hash packet. + */ + public void broadHashPacket() { + if (cacheHash != -1){ + NetworkHandler.sendToAllPlayer(new CommonConfigHashInformPacket(cacheHash)); + } + } +} diff --git a/forge-mod/src/main/java/com/leisuretimedock/crossmod/mixin/MixinMUINetWorkHandler.java b/forge-mod/src/main/java/com/leisuretimedock/crossmod/mixin/MixinMUINetWorkHandler.java index d33733d..2a9c0bd 100644 --- a/forge-mod/src/main/java/com/leisuretimedock/crossmod/mixin/MixinMUINetWorkHandler.java +++ b/forge-mod/src/main/java/com/leisuretimedock/crossmod/mixin/MixinMUINetWorkHandler.java @@ -1,26 +1,24 @@ package com.leisuretimedock.crossmod.mixin; import icyllis.modernui.mc.forge.NetworkHandler; +import net.minecraft.resources.ResourceLocation; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Pseudo; import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.ModifyArg; +import org.spongepowered.asm.mixin.injection.ModifyVariable; @Pseudo @Mixin(value = NetworkHandler.class, remap = false) public class MixinMUINetWorkHandler { - /** - * 修补构造 ResourceLocation("modernui", id) 时,若 id 是空字符串,则替换为 "default" - */ - @ModifyArg( + + @ModifyVariable( method = "", - at = @At( - value = "INVOKE", - target = "Lnet/minecraft/resources/ResourceLocation;(Ljava/lang/String;Ljava/lang/String;)V" - ), - index = 1 // 修改 id 参数 + at = @At("HEAD"), + argsOnly = true, + ordinal = 0 ) - private String fixEmptyId(String id) { - return id == null || id.isEmpty() ? "default" : id; + private static ResourceLocation modifyNameParameter(ResourceLocation name) { + return name.getPath().isEmpty() ? name : new ResourceLocation(name.getNamespace(), "default"); } + } diff --git a/forge-mod/src/main/java/com/leisuretimedock/crossmod/network/NetworkHandler.java b/forge-mod/src/main/java/com/leisuretimedock/crossmod/network/NetworkHandler.java index db9fcdc..63964ac 100644 --- a/forge-mod/src/main/java/com/leisuretimedock/crossmod/network/NetworkHandler.java +++ b/forge-mod/src/main/java/com/leisuretimedock/crossmod/network/NetworkHandler.java @@ -1,11 +1,9 @@ -// 客户端网络处理类(CrossMod 端) package com.leisuretimedock.crossmod.network; import com.leisuretimedock.crossmod.CrossTeleportMod; -import com.leisuretimedock.crossmod.network.toClient.PingMessagePayload; -import com.leisuretimedock.crossmod.network.toClient.PingResultPacket; -import com.leisuretimedock.crossmod.network.toClient.PingStatsPacket; +import com.leisuretimedock.crossmod.network.toClient.*; import com.leisuretimedock.crossmod.network.toServer.PongMessagePayload; +import com.leisuretimedock.crossmod.network.toServer.SyncCommonConfigRequestPacket; import io.netty.buffer.Unpooled; import lombok.extern.slf4j.Slf4j; import net.minecraft.client.Minecraft; @@ -17,6 +15,7 @@ import net.minecraftforge.network.NetworkDirection; import net.minecraftforge.network.NetworkRegistry; import net.minecraftforge.network.PacketDistributor; import net.minecraftforge.network.simple.SimpleChannel; +import org.jetbrains.annotations.NotNull; import java.util.*; @@ -74,6 +73,26 @@ public class NetworkHandler { PingStatsPacket::decode, PingStatsPacket::handle ); + CHANNEL.messageBuilder(SyncCommonConfigPacket.class, messageId++, NetworkDirection.PLAY_TO_CLIENT) + .decoder(SyncCommonConfigPacket::decode) + .encoder(SyncCommonConfigPacket::encode) + .consumerNetworkThread(SyncCommonConfigPacket::handle) + .add(); + CHANNEL.messageBuilder(SyncCommonConfigRequestPacket.class, messageId++, NetworkDirection.PLAY_TO_SERVER) + .decoder(SyncCommonConfigRequestPacket::decode) + .encoder(SyncCommonConfigRequestPacket::encode) + .consumerNetworkThread(SyncCommonConfigRequestPacket::handle) + .add(); + CHANNEL.messageBuilder(CommonConfigHashInformPacket.class, messageId++, NetworkDirection.PLAY_TO_CLIENT) + .decoder(CommonConfigHashInformPacket::decode) + .encoder(CommonConfigHashInformPacket::encode) + .consumerNetworkThread(CommonConfigHashInformPacket::handle) + .add(); + CHANNEL.messageBuilder(GotoServerPayload.class, messageId++, NetworkDirection.PLAY_TO_CLIENT) + .decoder(GotoServerPayload::decode) + .encoder(GotoServerPayload::encode) + .consumerMainThread(GotoServerPayload::handle) + .add(); } // 新增发送报告方法 public static void sendPingReport(ServerPlayer player, @@ -88,7 +107,7 @@ public class NetworkHandler { } - public static void sendPingResults(ServerPlayer player, Map results) { + public static void sendPingResults(ServerPlayer player, @NotNull Map results) { // 创建平均时延映射 Map averageLatencies = new HashMap<>(); @@ -116,7 +135,7 @@ public class NetworkHandler { public static void sendPingRequest(ServerPlayer player, UUID requestId) { try { CHANNEL.sendTo(new PingMessagePayload(requestId), - player.connection.getConnection(), + player.connection.connection, NetworkDirection.PLAY_TO_CLIENT); } catch (Exception e) { log.error("发送ping请求失败", e); @@ -152,4 +171,37 @@ public class NetworkHandler { public static void sendTeleportRequest(String serverName) { PluginMessageListener.sendTeleport(serverName); } + /** + * Send to player. + * + * @param the type parameter + * @param message the message + * @param player the player + */ + public static void sendToPlayer(MSG message, ServerPlayer player){ + CHANNEL.send(PacketDistributor.PLAYER.with(() -> player), message); + } + + /** + * Send to all player. + * + * @param the type parameter + * @param message the message + */ + public static void sendToAllPlayer(MSG message){ + CHANNEL.send(PacketDistributor.ALL.noArg(), message); + } + + /** + * Send to player. + * + * @param the type parameter + * @param the type parameter + * @param message the message + * @param entity the entity + * @param packetDistributor the packet distributor + */ + public static void sendToPlayer(MSG message, T entity, PacketDistributor packetDistributor){ + CHANNEL.send(packetDistributor.with(() -> entity), message); + } } \ No newline at end of file diff --git a/forge-mod/src/main/java/com/leisuretimedock/crossmod/network/PingRequestManager.java b/forge-mod/src/main/java/com/leisuretimedock/crossmod/network/PingRequestManager.java index 1565cfc..d39cea6 100644 --- a/forge-mod/src/main/java/com/leisuretimedock/crossmod/network/PingRequestManager.java +++ b/forge-mod/src/main/java/com/leisuretimedock/crossmod/network/PingRequestManager.java @@ -3,11 +3,13 @@ package com.leisuretimedock.crossmod.network; import com.leisuretimedock.crossmod.CrossTeleportMod; import lombok.Getter; import lombok.extern.slf4j.Slf4j; -import net.minecraft.network.chat.TranslatableComponent; +import net.minecraft.network.chat.Component; import net.minecraft.server.level.ServerPlayer; import java.util.*; import java.util.concurrent.*; + + @Slf4j public final class PingRequestManager { // 配置常量 @@ -104,8 +106,8 @@ public final class PingRequestManager { // 网络拥塞检测 if (ping > DEFAULT_TIMEOUT_MS * 0.8) { - player.sendMessage(new TranslatableComponent("ltd.mod.ping.warn.network_latency"), - player.getUUID()); + player.displayClientMessage(Component.translatable("ltd.mod.ping.warn.network_latency", player.getUUID()), + true); } updatePingHistory(data, ping); @@ -175,7 +177,7 @@ public final class PingRequestManager { int successfulRequests = 0; for (PlayerPingData data : playerData.values()) { - synchronized (data) { + synchronized (playerData) { allPings.addAll(data.pingHistory); totalRequests += data.totalRequests; successfulRequests += data.successfulRequests; @@ -212,7 +214,7 @@ public final class PingRequestManager { Map results = new HashMap<>(); playerData.forEach((uuid, data) -> { - synchronized (data) { + synchronized (PingRequestManager.class) { if (!data.pingHistory.isEmpty()) { results.put(uuid, data.pingHistory.getLast()); } @@ -281,12 +283,12 @@ public final class PingRequestManager { if (attempt == count) { data.batchInProgress = 0; - player.sendMessage( - new TranslatableComponent("ltd.mod.ping.success.multiping.complete", count), - player.getUUID()); + player.displayClientMessage( + Component.translatable("ltd.mod.ping.success.multiping.complete", count), + true); } } - }, i * Math.max(intervalMs, MIN_PING_INTERVAL), TimeUnit.MILLISECONDS); + }, i * intervalMs, TimeUnit.MILLISECONDS); } return true; @@ -328,7 +330,7 @@ public final class PingRequestManager { Map averages = new HashMap<>(); playerData.forEach((uuid, data) -> { - synchronized (data) { + synchronized (PingRequestManager.class) { if (!data.pingHistory.isEmpty()) { latestPings.put(uuid, PingRequestManager.getLatestPing(uuid).orElse(-1L)); averages.put(uuid, PingRequestManager.calculateAverageLatency(data.pingHistory)); @@ -348,7 +350,7 @@ public final class PingRequestManager { long currentTime = System.currentTimeMillis(); playerData.forEach((playerId, data) -> { - synchronized (data) { + synchronized (PingRequestManager.class) { data.activeRequests.entrySet().removeIf(entry -> currentTime - entry.getValue() > DEFAULT_TIMEOUT_MS ); @@ -364,24 +366,11 @@ public final class PingRequestManager { private static double calculateAverageLatency(Collection pingHistory) { - if (pingHistory.isEmpty()) return 0; - - double total = 0; - double weightSum = 0; - int i = 1; - - for (Long ping : pingHistory) { - double weight = 1.0 / i; - total += ping * weight; - weightSum += weight; - i++; - } - - return total / weightSum; + return PlayerPingData.calculate(pingHistory); } private static double calculatePacketLossRate(PlayerPingData data) { - synchronized (data) { + synchronized (PingRequestManager.class) { if (data.totalRequests == 0) return 0; return (1 - (double) data.successfulRequests / data.totalRequests) * 100; } @@ -406,6 +395,10 @@ public final class PingRequestManager { } private double calculateAverageLatency(Collection pings) { + return calculate(pings); + } + + static double calculate(Collection pings) { if (pings.isEmpty()) return 0; double total = 0; diff --git a/forge-mod/src/main/java/com/leisuretimedock/crossmod/network/toClient/CommonConfigHashInformPacket.java b/forge-mod/src/main/java/com/leisuretimedock/crossmod/network/toClient/CommonConfigHashInformPacket.java new file mode 100644 index 0000000..701c039 --- /dev/null +++ b/forge-mod/src/main/java/com/leisuretimedock/crossmod/network/toClient/CommonConfigHashInformPacket.java @@ -0,0 +1,54 @@ +package com.leisuretimedock.crossmod.network.toClient; + +import com.leisuretimedock.crossmod.config.CrossServerConfigManager; +import com.leisuretimedock.crossmod.network.NetworkHandler; +import com.leisuretimedock.crossmod.network.toServer.SyncCommonConfigRequestPacket; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraftforge.network.NetworkEvent; + +import java.util.function.Supplier; + +/** + * The type Common config hash inform packet. + */ +public record CommonConfigHashInformPacket(int hash) { + /** + * Encode. + * + * @param packet the packet + * @param buffer the buffer + */ + public static void encode(CommonConfigHashInformPacket packet, FriendlyByteBuf buffer) { + buffer.writeInt(packet.hash()); + } + + + /** + * Decode common config hash inform packet. + * + * @param buffer the buffer + * @return the common config hash inform packet + */ + public static CommonConfigHashInformPacket decode(FriendlyByteBuf buffer) { + return new CommonConfigHashInformPacket(buffer.readInt()); + } + + /** + * Handle. + * + * @param packet the packet + * @param ctx the ctx + */ + public static void handle(CommonConfigHashInformPacket packet, Supplier ctx) { + NetworkEvent.Context context = ctx.get(); + context.enqueueWork(() -> { + int hash = CrossServerConfigManager.INSTANCE.calculateConfigHash(); + if (hash != packet.hash()) { + NetworkHandler.CHANNEL.sendToServer(new SyncCommonConfigRequestPacket(hash)); + } + } + ); + context.setPacketHandled(true); + } + +} diff --git a/forge-mod/src/main/java/com/leisuretimedock/crossmod/network/toClient/GotoServerPayload.java b/forge-mod/src/main/java/com/leisuretimedock/crossmod/network/toClient/GotoServerPayload.java new file mode 100644 index 0000000..8464d01 --- /dev/null +++ b/forge-mod/src/main/java/com/leisuretimedock/crossmod/network/toClient/GotoServerPayload.java @@ -0,0 +1,24 @@ +package com.leisuretimedock.crossmod.network.toClient; + +import com.leisuretimedock.crossmod.network.NetworkHandler; +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; + +public record GotoServerPayload(String serverName) { + public static void encode(@NotNull GotoServerPayload payload, @NotNull FriendlyByteBuf buf) { + buf.writeUtf(payload.serverName); + } + @Contract("_ -> new") + public static @NotNull GotoServerPayload decode(@NotNull FriendlyByteBuf buf) { + return new GotoServerPayload(buf.readUtf()); + } + public static void handle(@NotNull GotoServerPayload msg, @NotNull Supplier ctx) { + NetworkEvent.Context context = ctx.get(); + context.enqueueWork(() -> NetworkHandler.sendTeleportRequest(msg.serverName)); + context.setPacketHandled(true); + } +} diff --git a/forge-mod/src/main/java/com/leisuretimedock/crossmod/network/toClient/ResetPacket.java b/forge-mod/src/main/java/com/leisuretimedock/crossmod/network/toClient/ResetPacket.java index 11937a5..707e4a6 100644 --- a/forge-mod/src/main/java/com/leisuretimedock/crossmod/network/toClient/ResetPacket.java +++ b/forge-mod/src/main/java/com/leisuretimedock/crossmod/network/toClient/ResetPacket.java @@ -8,11 +8,13 @@ import lombok.Setter; import lombok.extern.slf4j.Slf4j; import net.minecraft.client.Minecraft; import net.minecraft.client.multiplayer.ClientHandshakePacketListenerImpl; +import net.minecraft.client.multiplayer.ServerData; import net.minecraft.network.Connection; import net.minecraft.network.ConnectionProtocol; import net.minecraft.network.FriendlyByteBuf; -import net.minecraft.network.chat.TranslatableComponent; +import net.minecraft.network.chat.Component; import net.minecraftforge.network.*; +import org.slf4j.Logger; import java.util.function.Supplier; @@ -32,18 +34,24 @@ public class ResetPacket extends HandshakeMessages.C2SAcknowledge { } public static void handler(HandshakeHandler ignoredHandler, ResetPacket ignoredMsg, Supplier ctxSupplier) { + handler(ctxSupplier, log); + + } + + public static void handler(Supplier ctxSupplier, Logger log) { NetworkEvent.Context ctx = ctxSupplier.get(); ClientResetManager.isNegotiating.set(true); Connection conn = ctx.getNetworkManager(); if (ctx.getDirection() != NetworkDirection.LOGIN_TO_CLIENT && ctx.getDirection() != NetworkDirection.PLAY_TO_CLIENT) { - conn.disconnect(new TranslatableComponent("ltd.mod.client.invalid_packet")); + conn.disconnect(Component.translatable("ltd.mod.client.invalid_packet")); return; } if (ResetHelper.clearClient(ctx)) { + ServerData serverData = Minecraft.getInstance().getCurrentServer(); NetworkHooks.registerClientLoginChannel(conn); conn.setProtocol(ConnectionProtocol.LOGIN); conn.setListener(new ClientHandshakePacketListenerImpl( - conn, Minecraft.getInstance(), null, s -> {} + conn, Minecraft.getInstance(), serverData ,null, true, null, s -> {} )); ((AccessorMinecraft) Minecraft.getInstance()).setPendingConnection(conn); @@ -55,11 +63,10 @@ public class ResetPacket extends HandshakeMessages.C2SAcknowledge { ); } catch (Exception e) { log.error("Failed to send acknowledgment", e); - conn.disconnect(new TranslatableComponent("ltd.mod.client.error.handshake")); + conn.disconnect(Component.translatable("ltd.mod.client.error.handshake")); } } - ctx.setPacketHandled(true); - + ctx.setPacketHandled(true); } } diff --git a/forge-mod/src/main/java/com/leisuretimedock/crossmod/network/toClient/SyncCommonConfigPacket.java b/forge-mod/src/main/java/com/leisuretimedock/crossmod/network/toClient/SyncCommonConfigPacket.java new file mode 100644 index 0000000..84c392c --- /dev/null +++ b/forge-mod/src/main/java/com/leisuretimedock/crossmod/network/toClient/SyncCommonConfigPacket.java @@ -0,0 +1,55 @@ +package com.leisuretimedock.crossmod.network.toClient; + +import com.leisuretimedock.crossmod.config.CrossServerConfigManager; +import lombok.extern.slf4j.Slf4j; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraftforge.network.NetworkEvent; + +import java.util.function.Supplier; + +/** + * The type Sync common config packet. + */ +@Slf4j +public record SyncCommonConfigPacket(CompoundTag config, int hash) { + /** + * Encode. + * + * @param msg the msg + * @param buf the buf + */ + public static void encode(SyncCommonConfigPacket msg, FriendlyByteBuf buf) { + buf.writeNbt(msg.config); + buf.writeInt(msg.hash); + } + + /** + * Decode packet eternal potato remove packet. + * + * @param buf the buf + * @return the packet eternal potato remove packet + */ + public static SyncCommonConfigPacket decode(FriendlyByteBuf buf) { + return new SyncCommonConfigPacket(buf.readNbt(), buf.readInt()); + } + + /** + * Handle. + * + * @param msg the msg + * @param ctx the ctx + */ + public static void handle(SyncCommonConfigPacket msg, Supplier ctx) { + ctx.get().enqueueWork(() -> { + CompoundTag old = CrossServerConfigManager.INSTANCE.serializeToNBT(); + CrossServerConfigManager.INSTANCE.deserializeFromNBT(msg.config); + int newHashCode = CrossServerConfigManager.INSTANCE.calculateConfigHash(); + if (newHashCode != msg.hash) { //BACK + log.error("Hash mismatch! Except:{}, Actual:{}", msg.hash, newHashCode); + CrossServerConfigManager.INSTANCE.deserializeFromNBT(old); + } + }); + ctx.get().setPacketHandled(true); + } +} diff --git a/forge-mod/src/main/java/com/leisuretimedock/crossmod/network/toServer/SyncCommonConfigRequestPacket.java b/forge-mod/src/main/java/com/leisuretimedock/crossmod/network/toServer/SyncCommonConfigRequestPacket.java new file mode 100644 index 0000000..a90deda --- /dev/null +++ b/forge-mod/src/main/java/com/leisuretimedock/crossmod/network/toServer/SyncCommonConfigRequestPacket.java @@ -0,0 +1,50 @@ +package com.leisuretimedock.crossmod.network.toServer; + +import com.leisuretimedock.crossmod.config.CrossServerConfigManager; +import com.leisuretimedock.crossmod.network.NetworkHandler; +import com.leisuretimedock.crossmod.network.toClient.SyncCommonConfigPacket; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraftforge.network.NetworkEvent; + +import java.util.function.Supplier; + +/** + * The type Sync common config request packet. + */ +public record SyncCommonConfigRequestPacket(int hash) { + /** + * Encode. + * + * @param msg the msg + * @param buf the buf + */ + public static void encode(SyncCommonConfigRequestPacket msg, FriendlyByteBuf buf) { + buf.writeInt(msg.hash); + } + + + /** + * Decode sync common config request packet. + * + * @param buf the buf + * @return the sync common config request packet + */ + public static SyncCommonConfigRequestPacket decode(FriendlyByteBuf buf) { + return new SyncCommonConfigRequestPacket(buf.readInt()); + } + + /** + * Handle. + * + * @param msg the msg + * @param ctx the ctx + */ + public static void handle(SyncCommonConfigRequestPacket msg, Supplier ctx) { + ctx.get().enqueueWork(() -> { + if (msg.hash != CrossServerConfigManager.INSTANCE.cacheHash) { + NetworkHandler.sendToPlayer(new SyncCommonConfigPacket(CrossServerConfigManager.INSTANCE.serializeToNBT(), CrossServerConfigManager.INSTANCE.calculateConfigHash()), ctx.get().getSender()); + } + }); + ctx.get().setPacketHandled(true); + } +} diff --git a/forge-mod/src/main/java/com/leisuretimedock/crossmod/reset/ClientResetManager.java b/forge-mod/src/main/java/com/leisuretimedock/crossmod/reset/ClientResetManager.java index 6abb62e..6919a29 100644 --- a/forge-mod/src/main/java/com/leisuretimedock/crossmod/reset/ClientResetManager.java +++ b/forge-mod/src/main/java/com/leisuretimedock/crossmod/reset/ClientResetManager.java @@ -45,7 +45,7 @@ public class ClientResetManager { .loginIndex(ResetPacket::getLoginIndex, ResetPacket::setLoginIndex) .decoder(ResetPacket::decode) .encoder(ResetPacket::encode) - .consumer(HandshakeHandler.biConsumerFor(ResetPacket::handler)) + .consumerMainThread(HandshakeHandler.biConsumerFor(ResetPacket::handler)) .add(); log.info( "Registered forge reset packet successfully."); } diff --git a/forge-mod/src/main/java/com/leisuretimedock/crossmod/reset/ResetHelper.java b/forge-mod/src/main/java/com/leisuretimedock/crossmod/reset/ResetHelper.java index d643c68..7f49c75 100644 --- a/forge-mod/src/main/java/com/leisuretimedock/crossmod/reset/ResetHelper.java +++ b/forge-mod/src/main/java/com/leisuretimedock/crossmod/reset/ResetHelper.java @@ -3,13 +3,14 @@ package com.leisuretimedock.crossmod.reset; import com.leisuretimedock.crossmod.client.gui.GenericIceMessageScreen; import lombok.extern.slf4j.Slf4j; import net.minecraft.client.Minecraft; -import net.minecraft.client.multiplayer.ServerData; -import net.minecraft.network.chat.TranslatableComponent; +import net.minecraft.network.chat.Component; +import net.minecraft.server.packs.repository.Pack; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.OnlyIn; import net.minecraftforge.network.NetworkEvent; import net.minecraftforge.registries.GameData; +import java.util.NoSuchElementException; import java.util.Objects; import java.util.concurrent.CompletableFuture; @@ -18,17 +19,26 @@ import static net.minecraft.ChatFormatting.BOLD; @Slf4j @OnlyIn(Dist.CLIENT) public class ResetHelper { + @SuppressWarnings("UnstableApiUsage") public static boolean clearClient(NetworkEvent.Context context) { CompletableFuture future = context.enqueueWork(() -> { log.debug("Clearing"); Minecraft minecraft = Minecraft.getInstance(); - ServerData serverData = minecraft.getCurrentServer(); + Pack serverPack = Minecraft.getInstance().getDownloadedPackSource().serverPack; if (minecraft.level == null) { GameData.revertToFrozen(); } - - minecraft.clearLevel(new GenericIceMessageScreen(new TranslatableComponent("ltd.mod.client.negotiating").withStyle(BOLD))); - minecraft.setCurrentServer(serverData); + Minecraft.getInstance().getDownloadedPackSource().serverPack = null; + minecraft.clearLevel(new GenericIceMessageScreen(Component.translatable("ltd.mod.client.negotiating").withStyle(BOLD))); + try { + context.getNetworkManager().channel().pipeline().remove("forge:forge_fixes"); + } catch (NoSuchElementException ignored) { + } + try { + context.getNetworkManager().channel().pipeline().remove("forge:vanilla_filter"); + } catch (NoSuchElementException ignored) { + } + Minecraft.getInstance().getDownloadedPackSource().serverPack = serverPack; }); log.debug("Waiting for Clear to complete"); try { @@ -37,7 +47,7 @@ public class ResetHelper { return true; } catch (Exception e) { log.error("Failed to clear client connection", e); - Objects.requireNonNull(Minecraft.getInstance().getConnection()).onDisconnect(new TranslatableComponent("ltd.mod.client.failed.reset_connection")); + Objects.requireNonNull(Minecraft.getInstance().getConnection()).onDisconnect(Component.translatable("ltd.mod.client.failed.reset_connection")); return false; } } diff --git a/forge-mod/src/main/java/com/leisuretimedock/crossmod/reset/ResetPacket.java b/forge-mod/src/main/java/com/leisuretimedock/crossmod/reset/ResetPacket.java index dd785ca..96ca8e3 100644 --- a/forge-mod/src/main/java/com/leisuretimedock/crossmod/reset/ResetPacket.java +++ b/forge-mod/src/main/java/com/leisuretimedock/crossmod/reset/ResetPacket.java @@ -1,15 +1,9 @@ package com.leisuretimedock.crossmod.reset; -import com.leisuretimedock.crossmod.mixin.AccessorMinecraft; import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; -import net.minecraft.client.Minecraft; -import net.minecraft.client.multiplayer.ClientHandshakePacketListenerImpl; -import net.minecraft.network.Connection; -import net.minecraft.network.ConnectionProtocol; import net.minecraft.network.FriendlyByteBuf; -import net.minecraft.network.chat.TranslatableComponent; import net.minecraftforge.network.*; import java.util.function.Supplier; @@ -30,33 +24,7 @@ public class ResetPacket extends HandshakeMessages.C2SAcknowledge { } public static void handler(HandshakeHandler handler , ResetPacket msg, Supplier ctxSupplier) { - NetworkEvent.Context ctx = ctxSupplier.get(); - ClientResetManager.isNegotiating.set(true); - Connection conn = ctx.getNetworkManager(); - if (ctx.getDirection() != NetworkDirection.LOGIN_TO_CLIENT && ctx.getDirection() != NetworkDirection.PLAY_TO_CLIENT) { - conn.disconnect(new TranslatableComponent("ltd.mod.client.invalid_packet")); - return; - } - if (ResetHelper.clearClient(ctx)) { - NetworkHooks.registerClientLoginChannel(conn); - conn.setProtocol(ConnectionProtocol.LOGIN); - conn.setListener(new ClientHandshakePacketListenerImpl( - conn, Minecraft.getInstance(), null, s -> {} - )); - - ((AccessorMinecraft) Minecraft.getInstance()).setPendingConnection(conn); - - try { - ClientResetManager.handshakeChannel.reply( - new HandshakeMessages.C2SAcknowledge(), - ClientResetManager.contextConstructor.newInstance(conn, NetworkDirection.LOGIN_TO_CLIENT, 98) - ); - } catch (Exception e) { - log.error("Failed to send acknowledgment", e); - conn.disconnect(new TranslatableComponent("ltd.mod.client.error.handshake")); - } - } - ctx.setPacketHandled(true); + com.leisuretimedock.crossmod.network.toClient.ResetPacket.handler(ctxSupplier, log); } diff --git a/forge-mod/src/main/resources/META-INF/accesstransformer.cfg b/forge-mod/src/main/resources/META-INF/accesstransformer.cfg index 076d79b..26de0d2 100644 --- a/forge-mod/src/main/resources/META-INF/accesstransformer.cfg +++ b/forge-mod/src/main/resources/META-INF/accesstransformer.cfg @@ -1 +1,2 @@ -public net.minecraft.client.Minecraft pendingConnection #pendingConnection +public net.minecraft.client.resources.DownloadedPackSource f_244082_ # serverPack +public net.minecraft.client.Minecraft f_91009_ # pendingConnection diff --git a/forge-mod/src/main/resources/assets/ltdcrossteleport/lang/en_us.json b/forge-mod/src/main/resources/assets/ltdcrossteleport/lang/en_us.json index 51a5ab4..88208ce 100644 --- a/forge-mod/src/main/resources/assets/ltdcrossteleport/lang/en_us.json +++ b/forge-mod/src/main/resources/assets/ltdcrossteleport/lang/en_us.json @@ -10,6 +10,8 @@ "ltd.mod.client.failed.reset_connection": "Failed to reset connection.", "ltd.mod.client.error.handshake": "Handshake error", "ltd.mod.client.invalid_reset_packet": "Invalid reset packet", + "ltd.mod.client.menu.button.no_servers": "No servers", + "ltd.mod.client.overlay.tip": "use [%s] to open cross server menu", "ltd.mod.client.invalid_packet": "Invalid packet", "ltd.mod.client.request.goto": "Request goto %s", "ltd.mod.ping.error.no_players": "No valid players specified", diff --git a/forge-mod/src/main/resources/assets/ltdcrossteleport/lang/zh_cn.json b/forge-mod/src/main/resources/assets/ltdcrossteleport/lang/zh_cn.json index b50665b..7b6e98e 100644 --- a/forge-mod/src/main/resources/assets/ltdcrossteleport/lang/zh_cn.json +++ b/forge-mod/src/main/resources/assets/ltdcrossteleport/lang/zh_cn.json @@ -36,5 +36,7 @@ "ltd.mod.ping.stats.avg_latency": "平均时延: %.1fms", "ltd.mod.ping.stats.packet_loss": "丢包率: %.1f%%", "ltd.mod.ping.stats.sample_count": "样本数量: %d", - "ltd.mod.ping.warn.network_latency": "网络延迟较高,建议减少Ping频率" + "ltd.mod.ping.warn.network_latency": "网络延迟较高,建议减少Ping频率", + "ltd.mod.client.menu.button.no_servers": "没有服务器", + "ltd.mod.client.overlay.tip": "按 [%s] 打开跨服传送菜单" } \ No newline at end of file