重构了些结构,并修复些BUG:
如Velocity简体中文翻译缺失
This commit is contained in:
parent
e8f0f81339
commit
b4f87a7b55
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
// 注册生命周期事件
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<ClientboundCustomPayloadPacket>() {
|
||||
@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;
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
{
|
||||
|
||||
"ltd.mod.client.name.trans_server": "LTD跨服传送模组",
|
||||
"ltd.mod.client.key": "LTD跨服传送按键"
|
||||
}
|
||||
|
|
@ -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{
|
||||
|
|
|
|||
|
|
@ -1,2 +1,3 @@
|
|||
plugin_group=com.leisuretimedock.crossplugin
|
||||
plugin_version=1.0.0.0
|
||||
plugin_version=1.0.0.2
|
||||
plugin_name=CrossServerTeleport
|
||||
|
|
@ -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.");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<String> 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<List<String>> suggestAsync(Invocation invocation) {
|
||||
return CompletableFuture.completedFuture(SUGGESTIONS);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<Player> 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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.<xx服务器名>"
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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<Player> 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -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=配置已重新加载。
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user