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