From b4f87a7b558029aaabf51952f9b6c5d99d87136e Mon Sep 17 00:00:00 2001 From: 3944Realms Date: Tue, 22 Jul 2025 17:06:33 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=8D=E6=9E=84=E4=BA=86=E4=BA=9B=E7=BB=93?= =?UTF-8?q?=E6=9E=84=EF=BC=8C=E5=B9=B6=E4=BF=AE=E5=A4=8D=E4=BA=9BBUG?= =?UTF-8?q?=EF=BC=9A=20=E5=A6=82Velocity=E7=AE=80=E4=BD=93=E4=B8=AD?= =?UTF-8?q?=E6=96=87=E7=BF=BB=E8=AF=91=E7=BC=BA=E5=A4=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- forge-mod/gradle.properties | 2 +- .../crossmod/CrossTeleportMod.java | 4 +- .../crossmod/NetworkHandler.java | 78 ++++--- .../crossmod/PluginMessageListener.java | 55 +++++ .../crossmod/client/PluginChannelClient.java | 28 ++- .../src/main/resources/META-INF/mods.toml | 2 +- .../assets/ltdcrossteleport/lang/en_us.json | 4 +- .../assets/ltdcrossteleport/lang/zh_cn.json | 3 +- velocity-plugin/build.gradle | 5 +- velocity-plugin/gradle.properties | 3 +- .../crossplugin/CrossPlugin.java | 16 +- .../command/ReloadConfigCommand.java | 18 +- .../handler/PluginChannelHandler.java | 95 --------- .../handler/PluginMessageHandler.java | 85 -------- .../listener/PluginMessageListener.java | 194 ++++++++++++++++++ .../crossplugin/manager/OverlayManager.java | 6 +- .../crossserver/Bundle/zh_CN.properties | 6 +- 17 files changed, 336 insertions(+), 268 deletions(-) create mode 100644 forge-mod/src/main/java/com/leisuretimedock/crossmod/PluginMessageListener.java delete mode 100644 velocity-plugin/src/main/java/com/leisuretimedock/crossplugin/handler/PluginChannelHandler.java delete mode 100644 velocity-plugin/src/main/java/com/leisuretimedock/crossplugin/handler/PluginMessageHandler.java create mode 100644 velocity-plugin/src/main/java/com/leisuretimedock/crossplugin/listener/PluginMessageListener.java diff --git a/forge-mod/gradle.properties b/forge-mod/gradle.properties index fbfeb62..8ed5f68 100644 --- a/forge-mod/gradle.properties +++ b/forge-mod/gradle.properties @@ -49,7 +49,7 @@ mod_name=Leisure Time Dock Mod # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=MIT # The mod version. See https://semver.org/ -mod_version=0.0.0.1 +mod_version=0.0.0.2 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html diff --git a/forge-mod/src/main/java/com/leisuretimedock/crossmod/CrossTeleportMod.java b/forge-mod/src/main/java/com/leisuretimedock/crossmod/CrossTeleportMod.java index b5af5d2..8b738ba 100644 --- a/forge-mod/src/main/java/com/leisuretimedock/crossmod/CrossTeleportMod.java +++ b/forge-mod/src/main/java/com/leisuretimedock/crossmod/CrossTeleportMod.java @@ -1,8 +1,6 @@ package com.leisuretimedock.crossmod; -import net.minecraft.resources.ResourceLocation; import net.minecraftforge.api.distmarker.Dist; -import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.IExtensionPoint; import net.minecraftforge.fml.ModLoadingContext; @@ -12,7 +10,7 @@ import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent; @Mod(CrossTeleportMod.MOD_ID) public class CrossTeleportMod { public static final String MOD_ID ="ltdcrossteleport"; - public static final ResourceLocation CHANNEL = new ResourceLocation(MOD_ID, "teleport"); + public CrossTeleportMod() { // 注册生命周期事件 diff --git a/forge-mod/src/main/java/com/leisuretimedock/crossmod/NetworkHandler.java b/forge-mod/src/main/java/com/leisuretimedock/crossmod/NetworkHandler.java index cda0799..8551797 100644 --- a/forge-mod/src/main/java/com/leisuretimedock/crossmod/NetworkHandler.java +++ b/forge-mod/src/main/java/com/leisuretimedock/crossmod/NetworkHandler.java @@ -1,64 +1,56 @@ +// 客户端网络处理类(CrossMod 端) package com.leisuretimedock.crossmod; import io.netty.buffer.Unpooled; import net.minecraft.client.Minecraft; import net.minecraft.network.FriendlyByteBuf; -import net.minecraft.network.protocol.game.ClientboundCustomPayloadPacket; import net.minecraft.network.protocol.game.ServerboundCustomPayloadPacket; import net.minecraft.resources.ResourceLocation; -import net.minecraftforge.network.NetworkRegistry; -import net.minecraftforge.network.simple.SimpleChannel; -import java.io.ByteArrayOutputStream; -import java.io.DataOutputStream; -import java.io.IOException; import java.util.Objects; -import static com.leisuretimedock.crossmod.client.PluginChannelClient.CHANNEL_ID; - +/** + * NetworkHandler 用于客户端向服务端发送插件消息。 + * 目前只用 plugin message 方式进行通信。 + */ public class NetworkHandler { - private static final String PROTOCOL_VERSION = "1"; - private static SimpleChannel CHANNEL; + + // 自定义插件消息通道标识 + public static final ResourceLocation TELEPORT_ID = new ResourceLocation(CrossTeleportMod.MOD_ID, "teleport"); + public static final ResourceLocation CHANNEL_ID = new ResourceLocation(CrossTeleportMod.MOD_ID, "channel"); public static void register() { - //TODO: 以后会做出双端版本,以让游戏服务器端可以允运行代理命令简化些流程 - // 不需要注册普通 packet,因为我们只用 plugin message - CHANNEL = NetworkRegistry.newSimpleChannel( - new ResourceLocation(CrossTeleportMod.MOD_ID, "teleport"), - () -> PROTOCOL_VERSION, - PROTOCOL_VERSION::equals, - PROTOCOL_VERSION::equals - ); + // TODO: 未来支持双端注册,以便服务器端也能处理相关命令 + // 当前仅客户端发送 PluginMessage,无需额外注册 } - public static void sendTeleportMessage(String serverName) { - // 构建 raw plugin message + /** + * 发送自定义插件消息 + * @param subChannel 子通道标识 + * @param payload 负载数据(字节数组) + */ + public static void sendPluginMessage(ResourceLocation subChannel, byte[] payload) { FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer()); - buf.writeUtf(serverName); +// buf.writeUtf(subChannel.getPath()); // 写入子通道字符串 + buf.writeBytes(payload); // 写入负载字节 - Objects.requireNonNull(Minecraft.getInstance().getConnection()).send( - new ServerboundCustomPayloadPacket( - CrossTeleportMod.CHANNEL, buf - ) - ); - } - public static void sendClientReady() { - if (Minecraft.getInstance().player == null) return; - - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - DataOutputStream dos = new DataOutputStream(baos); - try { - dos.writeUTF("client_ready"); - dos.flush(); - } catch (IOException e) { - throw new RuntimeException(e); - } - byte[] bytes = baos.toByteArray(); + // 获取当前连接并发送自定义负载包 Objects.requireNonNull(Minecraft.getInstance().getConnection()) - .send(new ServerboundCustomPayloadPacket(CHANNEL_ID, new FriendlyByteBuf(Unpooled.wrappedBuffer(bytes)))); - - + .send(new ServerboundCustomPayloadPacket(subChannel, buf)); } -} + /** + * 发送客户端已准备好消息(示例方法,调用具体实现) + */ + public static void sendClientReady() { + PluginMessageListener.sendClientReady(); + } + /** + * 发送传送请求到代理服务器 + * @param serverName 目标服务器名 + */ + public static void sendTeleportRequest(String serverName) { + PluginMessageListener.sendTeleport(serverName); + } +} diff --git a/forge-mod/src/main/java/com/leisuretimedock/crossmod/PluginMessageListener.java b/forge-mod/src/main/java/com/leisuretimedock/crossmod/PluginMessageListener.java new file mode 100644 index 0000000..335e931 --- /dev/null +++ b/forge-mod/src/main/java/com/leisuretimedock/crossmod/PluginMessageListener.java @@ -0,0 +1,55 @@ +package com.leisuretimedock.crossmod; + +import lombok.extern.slf4j.Slf4j; +import net.minecraft.client.Minecraft; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +/** + * 客户端插件消息发送工具类,负责向服务器发送自定义插件消息。 + */ +@Slf4j +public class PluginMessageListener { + + /** + * 发送客户端已准备好消息给服务器(CHANNEL_ID通道) + */ + public static void sendClientReady() { + if (Minecraft.getInstance().player == null) return; + + try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); + DataOutputStream dos = new DataOutputStream(baos)) { + + dos.writeUTF("client_ready"); // 命令字符串 + dos.flush(); + byte[] payload = baos.toByteArray(); + NetworkHandler.sendPluginMessage(NetworkHandler.CHANNEL_ID, payload); + log.debug("Sent client_ready message with payload length: {}", payload.length); + } catch (IOException e) { + log.error("Failed to send client ready", e); + } + } + + /** + * 发送传送请求给服务器(TELEPORT_ID通道) + * @param serverName 目标服务器名 + */ + public static void sendTeleport(String serverName) { + if (Minecraft.getInstance().player == null) return; + + try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); + DataOutputStream dos = new DataOutputStream(baos)) { + + // 旧协议:写一个UTF字符串 “teleport:目标服务器名” + // 代理端代码是识别 "teleport:" 开头的字符串的 + dos.writeUTF("teleport:" + serverName); + dos.flush(); + + NetworkHandler.sendPluginMessage(NetworkHandler.CHANNEL_ID, baos.toByteArray()); + } catch (IOException e) { + log.error("Failed to send teleport", e); + } + } +} 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 684a4e6..3269370 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 @@ -13,7 +13,6 @@ import net.minecraft.network.Connection; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.chat.TextComponent; import net.minecraft.network.protocol.game.ClientboundCustomPayloadPacket; -import net.minecraft.resources.ResourceLocation; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.client.event.ClientPlayerNetworkEvent; import net.minecraftforge.client.event.RegisterClientCommandsEvent; @@ -24,25 +23,24 @@ import java.util.Objects; @Slf4j @Mod.EventBusSubscriber(modid = CrossTeleportMod.MOD_ID, value = Dist.CLIENT) public class PluginChannelClient { - public static final ResourceLocation CHANNEL_ID = new ResourceLocation(CrossTeleportMod.MOD_ID, "channel"); - private static final String HANDLER_NAME = CrossTeleportMod.MOD_ID+":channel"; + private static final String HANDLER_NAME = CrossTeleportMod.MOD_ID + ":channel"; @SubscribeEvent public static void onLogin(ClientPlayerNetworkEvent.LoggedInEvent event) { - log.info("[CrossTeleportMod] 玩家登录事件触发"); + log.debug("[CrossTeleportMod] 玩家登录事件触发"); Connection connection = Objects.requireNonNull(Minecraft.getInstance().getConnection()).getConnection(); ChannelPipeline pipeline = connection.channel().pipeline(); - log.info("[CrossTeleportMod] 当前管线内容: {}", pipeline.names()); + log.debug("[CrossTeleportMod] 当前管线内容: {}", pipeline.names()); if (pipeline.get(HANDLER_NAME) == null) { pipeline.addBefore("packet_handler", HANDLER_NAME, new SimpleChannelInboundHandler() { @Override protected void channelRead0(ChannelHandlerContext ctx, ClientboundCustomPayloadPacket packet) { - log.info("[CrossTeleportMod] 收到插件消息包: {}", packet.getIdentifier()); + log.debug("[CrossTeleportMod] 收到插件消息包: {}", packet.getIdentifier()); - if (!packet.getIdentifier().equals(CHANNEL_ID)) { + if (!packet.getIdentifier().equals(NetworkHandler.CHANNEL_ID)) { log.warn("[CrossTeleportMod] 未识别插件消息频道: {}", packet.getIdentifier()); return; } @@ -55,17 +53,17 @@ public class PluginChannelClient { // 再读 String command = buf.readUtf(); - log.info("[CrossTeleportMod] 收到指令: {}", command); + log.debug("[CrossTeleportMod] 收到指令: {}", command); Minecraft.getInstance().execute(() -> { PluginCommand.fromId(command).ifPresentOrElse(cmd -> { switch (cmd) { case OVERLAY_SHOW -> { - log.info("[CrossTeleportMod] 执行 OVERLAY_SHOW"); + log.debug("[CrossTeleportMod] 执行 OVERLAY_SHOW"); OverlayRenderer.setShow(true); } case OVERLAY_HIDE -> { - log.info("[CrossTeleportMod] 执行 OVERLAY_HIDE"); + log.debug("[CrossTeleportMod] 执行 OVERLAY_HIDE"); OverlayRenderer.setShow(false); } } @@ -78,7 +76,7 @@ public class PluginChannelClient { } }); - log.info("[CrossTeleportMod] 已添加插件消息处理器: {}", HANDLER_NAME); + log.debug("[CrossTeleportMod] 已添加插件消息处理器: {}", HANDLER_NAME); NetworkHandler.sendClientReady(); } else { @@ -90,17 +88,17 @@ public class PluginChannelClient { @SubscribeEvent public static void onLogout(ClientPlayerNetworkEvent.LoggedOutEvent event) { - log.info("[CrossTeleportMod] 玩家注销事件触发"); + log.debug("[CrossTeleportMod] 玩家注销事件触发"); Connection connection = event.getConnection(); if (connection != null) { ChannelPipeline pipeline = connection.channel().pipeline(); - log.info("[CrossTeleportMod] 当前管线内容: {}", pipeline.names()); + log.debug("[CrossTeleportMod] 当前管线内容: {}", pipeline.names()); if (pipeline.get(HANDLER_NAME) != null) { pipeline.remove(HANDLER_NAME); - log.info("[CrossTeleportMod] 成功移除插件消息处理器: {}", HANDLER_NAME); + log.debug("[CrossTeleportMod] 成功移除插件消息处理器: {}", HANDLER_NAME); } else { log.warn("[CrossTeleportMod] 未找到插件消息处理器: {}", HANDLER_NAME); } @@ -116,7 +114,7 @@ public class PluginChannelClient { .then(Commands.argument("server", StringArgumentType.string()) .executes(ctx -> { String server = StringArgumentType.getString(ctx, "server"); - NetworkHandler.sendTeleportMessage(server); + NetworkHandler.sendTeleportRequest(server); ctx.getSource().sendSuccess( new TextComponent("请求传送到 " + server), false); return 1; diff --git a/forge-mod/src/main/resources/META-INF/mods.toml b/forge-mod/src/main/resources/META-INF/mods.toml index 046fe22..b2c2630 100644 --- a/forge-mod/src/main/resources/META-INF/mods.toml +++ b/forge-mod/src/main/resources/META-INF/mods.toml @@ -53,7 +53,7 @@ versionRange="${forge_version_range}" #mandatory # AFTER - This mod is loaded AFTER the dependency ordering="NONE" # Side this dependency is applied on - BOTH, CLIENT, or SERVER -side="BOTH" +side="CLIENT" # Here's another dependency [[dependencies.${mod_id}]] modId="minecraft" 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 14463b4..ce66496 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 @@ -1,4 +1,4 @@ { - "ltd.mod.client.name.trans_server": "LTD跨服传送模组", - "ltd.mod.client.key": "LTD跨服传送按键" + "ltd.mod.client.name.trans_server": "LTD Cross Server Mod", + "ltd.mod.client.key": "LTD Key" } \ No newline at end of file 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 0e0dcd2..14463b4 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 @@ -1,3 +1,4 @@ { - + "ltd.mod.client.name.trans_server": "LTD跨服传送模组", + "ltd.mod.client.key": "LTD跨服传送按键" } \ No newline at end of file diff --git a/velocity-plugin/build.gradle b/velocity-plugin/build.gradle index 58f437c..145b4d5 100644 --- a/velocity-plugin/build.gradle +++ b/velocity-plugin/build.gradle @@ -11,6 +11,9 @@ repositories { maven { url 'https://repo.velocitypowered.com/releases/' } maven { url 'https://repo.lucko.me/' } // LuckPerms } +base { + archivesName = plugin_name +} dependencies { compileOnly 'org.projectlombok:lombok:1.18.24' @@ -27,7 +30,7 @@ shadowJar { jar { manifest { - attributes 'Main-Class': 'com.yourname.CrossServerVelocityPlugin' + attributes 'Main-Class': 'com.leisuretimedock.crossplugin.CrossPlugin' } } processResources{ diff --git a/velocity-plugin/gradle.properties b/velocity-plugin/gradle.properties index ed690cb..2ad4846 100644 --- a/velocity-plugin/gradle.properties +++ b/velocity-plugin/gradle.properties @@ -1,2 +1,3 @@ plugin_group=com.leisuretimedock.crossplugin -plugin_version=1.0.0.0 \ No newline at end of file +plugin_version=1.0.0.2 +plugin_name=CrossServerTeleport \ No newline at end of file diff --git a/velocity-plugin/src/main/java/com/leisuretimedock/crossplugin/CrossPlugin.java b/velocity-plugin/src/main/java/com/leisuretimedock/crossplugin/CrossPlugin.java index 8b3a1ce..0607afc 100644 --- a/velocity-plugin/src/main/java/com/leisuretimedock/crossplugin/CrossPlugin.java +++ b/velocity-plugin/src/main/java/com/leisuretimedock/crossplugin/CrossPlugin.java @@ -2,16 +2,13 @@ package com.leisuretimedock.crossplugin; import com.google.inject.Inject; import com.leisuretimedock.crossplugin.command.ReloadConfigCommand; -import com.leisuretimedock.crossplugin.handler.PluginChannelHandler; -import com.leisuretimedock.crossplugin.handler.PluginMessageHandler; +import com.leisuretimedock.crossplugin.listener.PluginMessageListener; import com.leisuretimedock.crossplugin.manager.ConfigManager; import com.leisuretimedock.crossplugin.messages.I18n; -import com.mojang.brigadier.tree.CommandNode; import com.velocitypowered.api.event.Subscribe; import com.velocitypowered.api.event.proxy.ProxyInitializeEvent; import com.velocitypowered.api.plugin.Plugin; import com.velocitypowered.api.plugin.PluginContainer; -import com.velocitypowered.api.plugin.PluginManager; import com.velocitypowered.api.plugin.annotation.DataDirectory; import com.velocitypowered.api.proxy.ProxyServer; import org.slf4j.Logger; @@ -30,8 +27,7 @@ public class CrossPlugin { private final ProxyServer server; public final Logger logger; - public final PluginMessageHandler pluginMessageHandler; - public final PluginChannelHandler pluginChannelHandler; + public final PluginMessageListener listener; public static boolean isLuckPermsEnabled; public final PluginContainer pluginContainer; @Inject @@ -42,8 +38,7 @@ public class CrossPlugin { I18n.addBundle(Locale.US); I18n.addBundle(Locale.SIMPLIFIED_CHINESE); I18n.init(); - pluginChannelHandler = new PluginChannelHandler(server, logger, config); - pluginMessageHandler = new PluginMessageHandler(server, logger, config); + this.listener = new PluginMessageListener(server, logger, config); this.pluginContainer = pluginContainer; server.getCommandManager().register( server.getCommandManager() @@ -58,9 +53,8 @@ public class CrossPlugin { @Subscribe public void onProxyInit(ProxyInitializeEvent event) { - server.getChannelRegistrar().register(PluginMessageHandler.CHANNEL_ID, PluginChannelHandler.CHANNEL_ID); - server.getEventManager().register(this, pluginChannelHandler); - server.getEventManager().register(this, pluginMessageHandler); + server.getChannelRegistrar().register(PluginMessageListener.CHANNEL_ID, PluginMessageListener.TELEPORT_ID); + server.getEventManager().register(this, listener); isLuckPermsEnabled = server.getPluginManager().getPlugin("luckperms").isPresent(); logger.info("[INIT] Plugin initialized, channel registered."); } diff --git a/velocity-plugin/src/main/java/com/leisuretimedock/crossplugin/command/ReloadConfigCommand.java b/velocity-plugin/src/main/java/com/leisuretimedock/crossplugin/command/ReloadConfigCommand.java index 8b84d55..a33e250 100644 --- a/velocity-plugin/src/main/java/com/leisuretimedock/crossplugin/command/ReloadConfigCommand.java +++ b/velocity-plugin/src/main/java/com/leisuretimedock/crossplugin/command/ReloadConfigCommand.java @@ -10,6 +10,9 @@ import lombok.extern.slf4j.Slf4j; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; +import java.util.List; +import java.util.concurrent.CompletableFuture; + @Slf4j public class ReloadConfigCommand implements SimpleCommand { @@ -19,21 +22,24 @@ public class ReloadConfigCommand implements SimpleCommand { public ReloadConfigCommand(ConfigManager configManager) { this.configManager = configManager; } + public static List SUGGESTIONS = List.of("reload", "help"); @Override public void execute(SimpleCommand.Invocation invocation) { CommandSource source = invocation.source(); String[] args = invocation.arguments(); - // /ltdcrossserver + // ltdcrossserver if (args.length == 0) { - source.sendMessage(I18n.translatable(PERMISSION_HELP, NamedTextColor.YELLOW)); + source.sendMessage(I18n.translatable(I18nKeyEnum.COMMAND_HELP, NamedTextColor.YELLOW)); return; } + String subCommand = args[0].toLowerCase(); switch (subCommand) { case "reload" -> handleReload(source); - default -> source.sendMessage(I18n.translatable(I18nKeyEnum.UNKNOWN_COMMAND, NamedTextColor.YELLOW)); + case "help" -> source.sendMessage(I18n.translatable(I18nKeyEnum.COMMAND_HELP, NamedTextColor.YELLOW)); + default -> source.sendMessage(I18n.translatable(I18nKeyEnum.UNKNOWN_COMMAND, NamedTextColor.YELLOW, Component.text(subCommand))); } @@ -54,5 +60,11 @@ public class ReloadConfigCommand implements SimpleCommand { log.error("Failed to reload config", e); } } + + @Override + public CompletableFuture> suggestAsync(Invocation invocation) { + return CompletableFuture.completedFuture(SUGGESTIONS); + } + } diff --git a/velocity-plugin/src/main/java/com/leisuretimedock/crossplugin/handler/PluginChannelHandler.java b/velocity-plugin/src/main/java/com/leisuretimedock/crossplugin/handler/PluginChannelHandler.java deleted file mode 100644 index ade61fe..0000000 --- a/velocity-plugin/src/main/java/com/leisuretimedock/crossplugin/handler/PluginChannelHandler.java +++ /dev/null @@ -1,95 +0,0 @@ -package com.leisuretimedock.crossplugin.handler; - -import com.leisuretimedock.crossplugin.Static; -import com.leisuretimedock.crossplugin.manager.ConfigManager; -import com.leisuretimedock.crossplugin.manager.OverlayManager; -import com.leisuretimedock.crossplugin.manager.ServerManager; -import com.leisuretimedock.crossplugin.messages.I18n; -import com.leisuretimedock.crossplugin.messages.I18nKeyEnum; -import com.velocitypowered.api.event.Subscribe; -import com.velocitypowered.api.event.connection.PluginMessageEvent; -import com.velocitypowered.api.event.player.ServerConnectedEvent; -import com.velocitypowered.api.proxy.Player; -import com.velocitypowered.api.proxy.ProxyServer; -import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.format.NamedTextColor; -import org.slf4j.Logger; - -import java.io.ByteArrayInputStream; -import java.io.DataInputStream; -import java.io.IOException; -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; - -public class PluginChannelHandler { - - public static final MinecraftChannelIdentifier CHANNEL_ID = - MinecraftChannelIdentifier.create(Static.MOD_ID, "channel"); - - private final ProxyServer proxy; - private final Logger logger; - private final ConfigManager configManager; - private final ServerManager serverManager; - private final Set waitingForReady = Collections.synchronizedSet(new HashSet<>()); - public PluginChannelHandler(ProxyServer proxy, Logger logger, ConfigManager configManager) { - this.proxy = proxy; - this.logger = logger; - this.configManager = configManager; - this.serverManager = new ServerManager(proxy); - } - - @Subscribe - public void onPluginMessage(PluginMessageEvent event) { - if (!event.getIdentifier().equals(CHANNEL_ID)) return; - if (!(event.getSource() instanceof Player player)) return; - - try (DataInputStream in = new DataInputStream(new ByteArrayInputStream(event.getData()))) { - String command = in.readUTF(); - logger.debug("Received plugin message from {}: {}", player.getUsername(), command); - - if (command.startsWith("teleport:")) { - String targetServer = command.substring("teleport:".length()); - proxy.getServer(targetServer).ifPresentOrElse(server -> { - player.createConnectionRequest(server).fireAndForget(); - logger.debug("Teleporting {} to {}", player.getUsername(), targetServer); - }, () -> { - player.sendMessage(I18n.translatable(I18nKeyEnum.SERVER_NOT_FOUND, NamedTextColor.RED, Component.text(targetServer))); - }); - } else if ("client_ready".equals(command)) { - // 收到客户端准备消息 - if (waitingForReady.remove(player)) { - logger.debug("[CrossTeleportMod] {} is ready, sending overlay and server list", player.getUsername()); - OverlayManager.showOverlay(player); - //TODO:未来计划使对应客户端mod可加载来自插件的自定义服务器列表 -// OverlayManager.sendServerList(player, serverManager.getAvailableServers()); - } else { - logger.debug("[CrossTeleportMod] Received client_ready from {}, but was not waiting", player.getUsername()); - } - } else { - logger.warn("[CrossTeleportMod] Unknown plugin command from {}: {}", player.getUsername(), command); - } - } catch (IOException e) { - logger.error("[CrossTeleportMod] Error parsing plugin message", e); - } - } - - @Subscribe - public void onPlayerJoin(ServerConnectedEvent event) { - Player player = event.getPlayer(); - String currentServer = event.getServer().getServerInfo().getName(); - - logger.debug("[CrossTeleportMod] Player {} joined server {}", player.getUsername(), currentServer); - - if (configManager.getOverlayServers().contains(currentServer)) { - // 标记此玩家等待客户端准备确认 - waitingForReady.add(player); - logger.debug("[CrossTeleportMod] Added {} to waitingForReady set", player.getUsername()); - } else { - // 不是 lobby,隐藏 overlay - OverlayManager.hideOverlay(player); - logger.debug("[CrossTeleportMod] Hide overlay for player {}", player.getUsername()); - } - } -} diff --git a/velocity-plugin/src/main/java/com/leisuretimedock/crossplugin/handler/PluginMessageHandler.java b/velocity-plugin/src/main/java/com/leisuretimedock/crossplugin/handler/PluginMessageHandler.java deleted file mode 100644 index 0c613f3..0000000 --- a/velocity-plugin/src/main/java/com/leisuretimedock/crossplugin/handler/PluginMessageHandler.java +++ /dev/null @@ -1,85 +0,0 @@ -package com.leisuretimedock.crossplugin.handler; - -import com.leisuretimedock.crossplugin.CrossPlugin; -import com.leisuretimedock.crossplugin.Static; -import com.leisuretimedock.crossplugin.manager.ConfigManager; -import com.leisuretimedock.crossplugin.messages.I18n; -import com.leisuretimedock.crossplugin.messages.I18nKeyEnum; -import com.velocitypowered.api.event.Subscribe; -import com.velocitypowered.api.event.connection.PluginMessageEvent; -import com.velocitypowered.api.proxy.Player; -import com.velocitypowered.api.proxy.ProxyServer; -import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.format.NamedTextColor; -import org.slf4j.Logger; - -import java.nio.charset.StandardCharsets; - -public class PluginMessageHandler { - public static final MinecraftChannelIdentifier CHANNEL_ID = - MinecraftChannelIdentifier.create(Static.MOD_ID, "teleport"); - private static final String PERMISSION_HEAD = Static.MOD_ID + ".goto."; - - private final ProxyServer server; - private final Logger logger; - private final ConfigManager config; - - public PluginMessageHandler(ProxyServer server, Logger logger, ConfigManager config) { - this.server = server; - this.logger = logger; - this.config = config; - } - - @Subscribe - public void onPluginMessage(PluginMessageEvent event) { - if (!(event.getSource() instanceof Player player)) return; - if (!event.getIdentifier().equals(CHANNEL_ID)) return; - - byte[] data = event.getData(); - String raw = new String(data, 1, data.length - 1, StandardCharsets.UTF_8); - - logger.info("Received plugin message from {}: {}", player.getUsername(), raw); - - // 处理 connect:key 模式 - if (raw.startsWith("connect:")) { - String key = raw.substring("connect:".length()); - String targetServerName = config.resolveServerName(key); - - if (isAlreadyOnServer(player, targetServerName)) { - player.sendMessage(I18n.translatable(I18nKeyEnum.ALREADY_ON_SERVER, NamedTextColor.RED)); - return; - } - - server.getServer(targetServerName).ifPresentOrElse( - srv -> player.createConnectionRequest(srv).fireAndForget(), - () -> player.sendMessage(I18n.translatable(I18nKeyEnum.SERVER_NOT_FOUND, NamedTextColor.RED, Component.text(targetServerName))) - ); - return; - } - - // 普通 serverName 模式 - String permissionNode = PERMISSION_HEAD + raw; - //这个权限是 "ltdcrossteleport.goto." - if (CrossPlugin.isLuckPermsEnabled && !player.hasPermission(permissionNode)) { - player.sendMessage(I18n.translatable(I18nKeyEnum.NO_PERMISSION_TO_TRANS_THIS_SERVER, NamedTextColor.RED, Component.text(raw))); - return; - } - - if (isAlreadyOnServer(player, raw)) { - player.sendMessage(I18n.translatable(I18nKeyEnum.ALREADY_ON_SERVER, NamedTextColor.RED)); - return; - } - - server.getServer(raw).ifPresentOrElse( - srv -> player.createConnectionRequest(srv).fireAndForget(), - () -> player.sendMessage(I18n.translatable(I18nKeyEnum.SERVER_NOT_FOUND, NamedTextColor.RED, Component.text(raw))) - ); - } - - private boolean isAlreadyOnServer(Player player, String serverName) { - return player.getCurrentServer() - .map(current -> current.getServerInfo().getName().equalsIgnoreCase(serverName)) - .orElse(false); - } -} diff --git a/velocity-plugin/src/main/java/com/leisuretimedock/crossplugin/listener/PluginMessageListener.java b/velocity-plugin/src/main/java/com/leisuretimedock/crossplugin/listener/PluginMessageListener.java new file mode 100644 index 0000000..7bbd9d0 --- /dev/null +++ b/velocity-plugin/src/main/java/com/leisuretimedock/crossplugin/listener/PluginMessageListener.java @@ -0,0 +1,194 @@ +// 代理端插件消息监听器(Velocity Proxy 端) +package com.leisuretimedock.crossplugin.listener; + +import com.leisuretimedock.crossplugin.Static; +import com.leisuretimedock.crossplugin.manager.ConfigManager; +import com.leisuretimedock.crossplugin.manager.OverlayManager; +import com.leisuretimedock.crossplugin.manager.ServerManager; +import com.leisuretimedock.crossplugin.messages.I18n; +import com.leisuretimedock.crossplugin.messages.I18nKeyEnum; + +import com.velocitypowered.api.event.Subscribe; +import com.velocitypowered.api.event.connection.PluginMessageEvent; +import com.velocitypowered.api.event.player.ServerConnectedEvent; +import com.velocitypowered.api.proxy.Player; +import com.velocitypowered.api.proxy.ProxyServer; +import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; + +import org.slf4j.Logger; + +import java.io.ByteArrayInputStream; +import java.io.DataInputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +/** + * 插件消息监听器,负责接收客户端发来的插件消息并处理跨服传送、Overlay显示等逻辑。 + */ +public class PluginMessageListener { + + // 插件消息通道标识(与客户端保持一致) + public static final MinecraftChannelIdentifier TELEPORT_ID = + MinecraftChannelIdentifier.create(Static.MOD_ID, "teleport"); + public static final MinecraftChannelIdentifier CHANNEL_ID = + MinecraftChannelIdentifier.create(Static.MOD_ID, "channel"); + + private static final String PERMISSION_HEAD = Static.MOD_ID + ".goto."; + + private final ProxyServer proxy; + private final Logger logger; + private final ConfigManager configManager; + @SuppressWarnings("ALL") + private final ServerManager serverManager; + + /** + * 维护等待客户端发送 "client_ready" 的玩家集合。 + */ + private final Set waitingForReady = Collections.synchronizedSet(new HashSet<>()); + + public PluginMessageListener(ProxyServer proxy, Logger logger, ConfigManager configManager) { + this.proxy = proxy; + this.logger = logger; + this.configManager = configManager; + this.serverManager = new ServerManager(proxy); + } + + /** + * 监听插件消息事件 + */ + @Subscribe + public void onPluginMessage(PluginMessageEvent event) { + if (!(event.getSource() instanceof Player player)) return; + + MinecraftChannelIdentifier id = (MinecraftChannelIdentifier) event.getIdentifier(); + + if (id.equals(TELEPORT_ID)) { + handleTeleportChannel(player, event.getData()); + } else if (id.equals(CHANNEL_ID)) { + handlePluginChannel(player, event.getData()); + } + } + + /** + * 处理“teleport”子通道,旧协议兼容纯字符串形式 + * @param player 玩家对象 + * @param data 消息字节数组 + */ + private void handleTeleportChannel(Player player, byte[] data) { + // 跳过第一个 byte(长度信息),后面是 UTF-8 字符串 + String raw = new String(data, 1, data.length - 1, StandardCharsets.UTF_8); + logger.debug("[CrossTeleportMod] Received teleport msg from {}: {}", player.getUsername(), raw); + + if (raw.startsWith("connect:")) { + // 兼容旧的 connect: 方式,映射别名到真实服务器名 + String key = raw.substring("connect:".length()); + String targetServerName = configManager.resolveServerName(key); + tryTeleport(player, targetServerName, false); + } else { + tryTeleport(player, raw, true); + } + } + + /** + * 处理“channel”子通道,支持多命令格式 + * @param player 玩家对象 + * @param data 消息字节数组 + */ + private void handlePluginChannel(Player player, byte[] data) { + // 简单日志,打印字节长度和十六进制,便于调试 + System.out.println("Received plugin message on channel 'channel' from player " + player.getUsername()); + System.out.println("Data length: " + data.length); + StringBuilder sb = new StringBuilder(); + for (byte b : data) { + sb.append(String.format("%02X ", b)); + } + System.out.println("Data hex: " + sb); + try (DataInputStream in = new DataInputStream(new ByteArrayInputStream(data))) { + String command = in.readUTF(); + logger.debug("[CrossTeleportMod] Received plugin command from {}: {}", player.getUsername(), command); + + if ("client_ready".equals(command)) { + if (waitingForReady.remove(player)) { + logger.debug("[CrossTeleportMod] {} is ready, sending overlay", player.getUsername()); + OverlayManager.showOverlay(player); + // TODO: 支持发送自定义服务器列表 + } else { + logger.debug("[CrossTeleportMod] Received client_ready from {}, but not in waiting set", player.getUsername()); + } + } else if (command.startsWith("teleport:")) { + String server = command.substring("teleport:".length()); + tryTeleport(player, server, true); + } else { + logger.warn("[CrossTeleportMod] Unknown command: {}", command); + } + + } catch (IOException e) { + logger.error("[CrossTeleportMod] Failed to parse plugin message from {}", player.getUsername(), e); + } + } + + /** + * 尝试传送玩家到目标服务器,包含权限与当前所在服务器判断 + * @param player 玩家对象 + * @param targetServer 目标服务器名 + * @param checkPermission 是否检查权限 + */ + private void tryTeleport(Player player, String targetServer, boolean checkPermission) { + if (checkPermission && !player.hasPermission(PERMISSION_HEAD + targetServer)) { + player.sendMessage(I18n.translatable(I18nKeyEnum.NO_PERMISSION_TO_TRANS_THIS_SERVER, + NamedTextColor.RED, Component.text(targetServer))); + return; + } + + if (isAlreadyOnServer(player, targetServer)) { + player.sendMessage(I18n.translatable(I18nKeyEnum.ALREADY_ON_SERVER, NamedTextColor.RED)); + return; + } + + proxy.getServer(targetServer).ifPresentOrElse(server -> { + player.createConnectionRequest(server).fireAndForget(); + logger.info("[CrossTeleportMod] Sent {} to {}", player.getUsername(), targetServer); + }, () -> { + player.sendMessage(I18n.translatable(I18nKeyEnum.SERVER_NOT_FOUND, + NamedTextColor.RED, Component.text(targetServer))); + }); + } + + /** + * 监听玩家服务器连接事件,维护是否显示 Overlay 状态 + */ + @Subscribe + public void onPlayerJoin(ServerConnectedEvent event) { + Player player = event.getPlayer(); + String currentServer = event.getServer().getServerInfo().getName(); + + logger.debug("[CrossTeleportMod] Player {} joined server {}", player.getUsername(), currentServer); + + if (configManager.getOverlayServers().contains(currentServer)) { + waitingForReady.add(player); + logger.debug("[CrossTeleportMod] Added {} to waitingForReady set", player.getUsername()); + } else { + OverlayManager.hideOverlay(player); + logger.debug("[CrossTeleportMod] Hiding overlay for {}", player.getUsername()); + } + } + + /** + * 判断玩家是否已经在目标服务器 + * @param player 玩家对象 + * @param serverName 目标服务器名 + * @return 是否已在该服务器 + */ + private boolean isAlreadyOnServer(Player player, String serverName) { + return player.getCurrentServer() + .map(s -> s.getServerInfo().getName().equalsIgnoreCase(serverName)) + .orElse(false); + } +} diff --git a/velocity-plugin/src/main/java/com/leisuretimedock/crossplugin/manager/OverlayManager.java b/velocity-plugin/src/main/java/com/leisuretimedock/crossplugin/manager/OverlayManager.java index cd672a0..d1d31b2 100644 --- a/velocity-plugin/src/main/java/com/leisuretimedock/crossplugin/manager/OverlayManager.java +++ b/velocity-plugin/src/main/java/com/leisuretimedock/crossplugin/manager/OverlayManager.java @@ -1,6 +1,6 @@ package com.leisuretimedock.crossplugin.manager; -import com.leisuretimedock.crossplugin.handler.PluginChannelHandler; +import com.leisuretimedock.crossplugin.listener.PluginMessageListener; import com.leisuretimedock.crossplugin.messages.I18n; import com.leisuretimedock.crossplugin.messages.I18nKeyEnum; import com.velocitypowered.api.proxy.Player; @@ -29,7 +29,7 @@ public class OverlayManager { data.flush(); player.sendPluginMessage( - PluginChannelHandler.CHANNEL_ID, + PluginMessageListener.CHANNEL_ID, out.toByteArray() ); } catch (Exception e) { @@ -56,7 +56,7 @@ public class OverlayManager { } player.sendPluginMessage( - PluginChannelHandler.CHANNEL_ID, + PluginMessageListener.CHANNEL_ID, out.toByteArray() ); diff --git a/velocity-plugin/src/main/resources/crossserver/Bundle/zh_CN.properties b/velocity-plugin/src/main/resources/crossserver/Bundle/zh_CN.properties index 982b604..d2dda9b 100644 --- a/velocity-plugin/src/main/resources/crossserver/Bundle/zh_CN.properties +++ b/velocity-plugin/src/main/resources/crossserver/Bundle/zh_CN.properties @@ -1,6 +1,6 @@ -ltd.plugin.trans.no_permission=你没有权限传送到该服务器!({0}) -ltd.plugin.trans.server_not_found=目标服务器不存在!({0}) -ltd.plugin.trans.already_on_server=你已经在该服务器上了。 +ltd.plugin.trans.failed.no_permission=你没有权限传送到该服务器!({0}) +ltd.plugin.trans.failed.server_not_found=目标服务器不存在!({0}) +ltd.plugin.trans.failed.already_on_server=你已经在该服务器上了。 ltd.plugin.send_server_list.failed=发送服务器列表失败。 ltd.plugin.command.no_permission=你没有权限重载去执行该指令,需要权限节点:{0}! ltd.plugin.reload.successful=配置已重新加载。