在解决多模组环境下服务器跳转导致的客户端数据包解析异常问题时(如ClientAddEntityPacket数据读取错误),我最初发现问题源于Netty管道传递机制的处理疏漏。然而进一步排查发现,另一个关联插件的工作机制才是关键因素。经深入分析其交互逻辑后,我决定将功能不完善的ForgeClientResetPacket模组整合到当前项目中。经过多次版本迭代和持续调试,最终成功实现了稳定的跨服实体同步功能。(
This commit is contained in:
parent
b4f87a7b55
commit
f5591e7df3
|
|
@ -19,6 +19,13 @@ allprojects {
|
|||
maven { url = "https://maven.izzel.io/releases/" }
|
||||
maven { url = "https://maven.bawnorton.com/releases" }
|
||||
maven { url 'https://repo.lucko.me/' } // LuckPerms
|
||||
maven {
|
||||
name = "Modrinth"
|
||||
url = "https://api.modrinth.com/maven"
|
||||
}
|
||||
maven {
|
||||
url "https://cursemaven.com"
|
||||
}
|
||||
}
|
||||
processResources{
|
||||
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||
|
|
|
|||
|
|
@ -32,11 +32,11 @@ println "Java: ${System.getProperty 'java.version'}, JVM: ${System.getProperty '
|
|||
|
||||
|
||||
|
||||
//// 配置 Mixin
|
||||
//mixin {
|
||||
// add sourceSets.main, "${mod_id}.refmap.json"
|
||||
// config "${mod_id}.mixins.json"
|
||||
//}
|
||||
// 配置 Mixin
|
||||
mixin {
|
||||
add sourceSets.main, "${mod_id}.refmap.json"
|
||||
config "${mod_id}.mixins.json"
|
||||
}
|
||||
|
||||
// 配置 LegacyForge 运行环境
|
||||
legacyForge {
|
||||
|
|
@ -93,6 +93,12 @@ configurations {
|
|||
dependencies {
|
||||
compileOnly 'org.projectlombok:lombok:1.18.24'
|
||||
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"
|
||||
modImplementation "curse.maven:modern-ui-352491:5229350"
|
||||
modImplementation "curse.maven:iceberg-520110:4035917"
|
||||
}
|
||||
|
||||
// 编译任务优化
|
||||
|
|
@ -114,7 +120,7 @@ jar {
|
|||
'Implementation-Version' : archiveVersion,
|
||||
'Implementation-Vendor' : mod_authors,
|
||||
'Implementation-Timestamp': new Date().format("yyyy-MM-dd'T'HH:mm:ssZ"),
|
||||
// 'MixinConfigs' : "${mod_id}.mixin.json"
|
||||
'MixinConfigs' : "${mod_id}.mixins.json"
|
||||
])
|
||||
}
|
||||
finalizedBy 'reobfJar'
|
||||
|
|
|
|||
|
|
@ -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.2
|
||||
mod_version=0.0.1.3
|
||||
# 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,11 +1,17 @@
|
|||
package com.leisuretimedock.crossmod;
|
||||
|
||||
import com.leisuretimedock.crossmod.reset.ClientResetManager;
|
||||
import net.minecraftforge.api.distmarker.Dist;
|
||||
import net.minecraftforge.eventbus.api.IEventBus;
|
||||
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.event.lifecycle.FMLClientSetupEvent;
|
||||
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
|
||||
import net.minecraftforge.network.NetworkConstants;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
@Mod(CrossTeleportMod.MOD_ID)
|
||||
public class CrossTeleportMod {
|
||||
|
|
@ -15,7 +21,9 @@ public class CrossTeleportMod {
|
|||
public CrossTeleportMod() {
|
||||
// 注册生命周期事件
|
||||
ModLoadingContext.get().registerExtensionPoint(IExtensionPoint.DisplayTest.class,
|
||||
() -> new IExtensionPoint.DisplayTest(() -> "ANY", (a, b) -> true));
|
||||
() -> new IExtensionPoint.DisplayTest(() -> NetworkConstants.IGNORESERVERONLY, (a, b) -> true));
|
||||
IEventBus modEventBus = FMLJavaModLoadingContext.get().getModEventBus();
|
||||
modEventBus.addListener(ClientResetManager::init);
|
||||
|
||||
}
|
||||
@Mod.EventBusSubscriber(modid = MOD_ID, value = Dist.CLIENT, bus = Mod.EventBusSubscriber.Bus.FORGE)
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ import java.util.Objects;
|
|||
public class NetworkHandler {
|
||||
|
||||
// 自定义插件消息通道标识
|
||||
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() {
|
||||
|
|
@ -30,9 +29,8 @@ public class NetworkHandler {
|
|||
* @param payload 负载数据(字节数组)
|
||||
*/
|
||||
public static void sendPluginMessage(ResourceLocation subChannel, byte[] payload) {
|
||||
FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer());
|
||||
// buf.writeUtf(subChannel.getPath()); // 写入子通道字符串
|
||||
buf.writeBytes(payload); // 写入负载字节
|
||||
FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer(payload.length));
|
||||
buf.writeBytes(payload);
|
||||
|
||||
// 获取当前连接并发送自定义负载包
|
||||
Objects.requireNonNull(Minecraft.getInstance().getConnection())
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ public class CrossServerGui extends Screen {
|
|||
private void sendCustomPayload(String message) {
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc.getConnection() != null) {
|
||||
FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer());
|
||||
FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer(256));
|
||||
buf.writeUtf(message);
|
||||
mc.getConnection().send(new ServerboundCustomPayloadPacket(CHANNEL_ID, buf));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.leisuretimedock.crossmod.client;
|
|||
|
||||
import com.leisuretimedock.crossmod.CrossTeleportMod;
|
||||
import com.leisuretimedock.crossmod.NetworkHandler;
|
||||
import com.leisuretimedock.crossmod.reset.ClientResetManager;
|
||||
import com.mojang.brigadier.arguments.StringArgumentType;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
|
|
@ -28,7 +29,8 @@ public class PluginChannelClient {
|
|||
@SubscribeEvent
|
||||
public static void onLogin(ClientPlayerNetworkEvent.LoggedInEvent event) {
|
||||
log.debug("[CrossTeleportMod] 玩家登录事件触发");
|
||||
|
||||
if (ClientResetManager.isNegotiating.get())
|
||||
ClientResetManager.isNegotiating.set(false);
|
||||
Connection connection = Objects.requireNonNull(Minecraft.getInstance().getConnection()).getConnection();
|
||||
ChannelPipeline pipeline = connection.channel().pipeline();
|
||||
|
||||
|
|
@ -38,13 +40,11 @@ public class PluginChannelClient {
|
|||
pipeline.addBefore("packet_handler", HANDLER_NAME, new SimpleChannelInboundHandler<ClientboundCustomPayloadPacket>() {
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx, ClientboundCustomPayloadPacket packet) {
|
||||
log.debug("[CrossTeleportMod] 收到插件消息包: {}", packet.getIdentifier());
|
||||
|
||||
if (!packet.getIdentifier().equals(NetworkHandler.CHANNEL_ID)) {
|
||||
log.warn("[CrossTeleportMod] 未识别插件消息频道: {}", packet.getIdentifier());
|
||||
ctx.fireChannelRead(packet);
|
||||
return;
|
||||
}
|
||||
|
||||
log.debug("[CrossTeleportMod] 收到插件消息包: {}", packet.getIdentifier());
|
||||
FriendlyByteBuf buf = packet.getData();
|
||||
try {
|
||||
// 先读一个字符串但不使用它,出现空消息
|
||||
|
|
@ -96,9 +96,11 @@ public class PluginChannelClient {
|
|||
|
||||
log.debug("[CrossTeleportMod] 当前管线内容: {}", pipeline.names());
|
||||
|
||||
if (pipeline.get(HANDLER_NAME) != null) {
|
||||
pipeline.remove(HANDLER_NAME);
|
||||
log.debug("[CrossTeleportMod] 成功移除插件消息处理器: {}", HANDLER_NAME);
|
||||
if (pipeline.get(HANDLER_NAME) != null ) {
|
||||
if (!ClientResetManager.isNegotiating.get()) {
|
||||
pipeline.remove(HANDLER_NAME);
|
||||
log.debug("[CrossTeleportMod] 成功移除插件消息处理器: {}", HANDLER_NAME);
|
||||
} else log.debug("[CrossTeleport] 跳转中,不移除消息处理器: {}", HANDLER_NAME);
|
||||
} else {
|
||||
log.warn("[CrossTeleportMod] 未找到插件消息处理器: {}", HANDLER_NAME);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
package com.leisuretimedock.crossmod.mixin;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.network.Connection;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.gen.Accessor;
|
||||
|
||||
@Mixin(Minecraft.class)
|
||||
public interface AccessorMinecraft {
|
||||
@Accessor("pendingConnection")
|
||||
void setPendingConnection(Connection connection);
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package com.leisuretimedock.crossmod.mixin;
|
||||
|
||||
import icyllis.modernui.mc.forge.NetworkHandler;
|
||||
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;
|
||||
|
||||
@Pseudo
|
||||
@Mixin(value = NetworkHandler.class, remap = false)
|
||||
public class MixinMUINetWorkHandler {
|
||||
/**
|
||||
* 修补构造 ResourceLocation("modernui", id) 时,若 id 是空字符串,则替换为 "default"
|
||||
*/
|
||||
@ModifyArg(
|
||||
method = "<init>",
|
||||
at = @At(
|
||||
value = "INVOKE",
|
||||
target = "Lnet/minecraft/resources/ResourceLocation;<init>(Ljava/lang/String;Ljava/lang/String;)V"
|
||||
),
|
||||
index = 1 // 修改 id 参数
|
||||
)
|
||||
private String fixEmptyId(String id) {
|
||||
return id == null || id.isEmpty() ? "default" : id;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package com.leisuretimedock.crossmod.mixin;
|
||||
|
||||
import net.minecraftforge.network.HandshakeMessages;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.injection.At;
|
||||
import org.spongepowered.asm.mixin.injection.Inject;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mixin(value = HandshakeMessages.C2SModListReply.class, remap = false)
|
||||
public class ModListSpoofMixin {
|
||||
@Inject(method = "<init>*", at = @At("RETURN"))
|
||||
private void injectFakeModList(CallbackInfo ci) {
|
||||
HandshakeMessages.C2SModListReply self = HandshakeMessages.C2SModListReply.class.cast(this);
|
||||
List<String> mods = self.getModList();
|
||||
if (!mods.contains("clientresetpacket")) {
|
||||
// "[Mixin] 模拟添加 clientresetpacket 模组到 modlist" ,以启用跳转功能
|
||||
mods.add("clientresetpacket");
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
package com.leisuretimedock.crossmod.reset;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.minecraft.network.Connection;
|
||||
import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent;
|
||||
import net.minecraftforge.fml.util.ObfuscationReflectionHelper;
|
||||
import net.minecraftforge.network.*;
|
||||
import net.minecraftforge.network.simple.SimpleChannel;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
@Slf4j
|
||||
public class ClientResetManager {
|
||||
public static final Field handshakeField;
|
||||
public static final Constructor<NetworkEvent.Context> contextConstructor;
|
||||
public static AtomicBoolean isNegotiating = new AtomicBoolean(false);
|
||||
public static SimpleChannel handshakeChannel;
|
||||
|
||||
public static void init(FMLCommonSetupEvent event) {
|
||||
|
||||
event.enqueueWork(() -> {
|
||||
if (handshakeField == null) {
|
||||
log.error( "Failed to find FML's handshake channel. Disabling mod.");
|
||||
return;
|
||||
}
|
||||
if (contextConstructor == null) {
|
||||
log.error("Failed to find FML's network event context constructor. Disabling mod.");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
Object handshake = handshakeField.get(null);
|
||||
if (handshake instanceof SimpleChannel) {
|
||||
handshakeChannel = (SimpleChannel)handshake;
|
||||
log.info("Registering forge reset packet.");
|
||||
handshakeChannel.messageBuilder(ResetPacket.class, 98)
|
||||
.loginIndex(ResetPacket::getLoginIndex, ResetPacket::setLoginIndex)
|
||||
.decoder(ResetPacket::decode)
|
||||
.encoder(ResetPacket::encode)
|
||||
.consumer(HandshakeHandler.biConsumerFor(ResetPacket::handler))
|
||||
.add();
|
||||
log.info( "Registered forge reset packet successfully.");
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
log.error("Caught exception when attempting to utilize FML's handshake. Disabling mod. Exception: {}", e.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
private static Field fetchHandshakeChannel() {
|
||||
try {
|
||||
return ObfuscationReflectionHelper.findField(NetworkConstants.class, "handshakeChannel");
|
||||
}
|
||||
catch (Exception e) {
|
||||
log.error("Exception occurred while accessing handshakeChannel: {}", e.getMessage(), e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static Constructor<NetworkEvent.Context> fetchNetworkEventContext() {
|
||||
try {
|
||||
return ObfuscationReflectionHelper.findConstructor(NetworkEvent.Context.class, Connection.class, NetworkDirection.class, int.class);
|
||||
}
|
||||
catch (Exception e) {
|
||||
log.error("Exception occurred while accessing getLoginIndex: {}", e.getMessage(), e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static {
|
||||
handshakeField = fetchHandshakeChannel();
|
||||
contextConstructor = fetchNetworkEventContext();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
package com.leisuretimedock.crossmod.reset;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.screens.ConnectScreen;
|
||||
import net.minecraft.client.gui.screens.GenericDirtMessageScreen;
|
||||
import net.minecraft.client.multiplayer.ServerData;
|
||||
import net.minecraft.client.multiplayer.resolver.ServerAddress;
|
||||
import net.minecraft.network.chat.TextComponent;
|
||||
import net.minecraft.network.chat.TranslatableComponent;
|
||||
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.Objects;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import static net.minecraft.ChatFormatting.BOLD;
|
||||
|
||||
@Slf4j
|
||||
@OnlyIn(Dist.CLIENT)
|
||||
public class ResetHelper {
|
||||
public static boolean clearClient(NetworkEvent.Context context) {
|
||||
CompletableFuture<Void> future = context.enqueueWork(() -> {
|
||||
log.debug("Clearing");
|
||||
Minecraft minecraft = Minecraft.getInstance();
|
||||
ServerData serverData = minecraft.getCurrentServer();
|
||||
if (minecraft.level == null) {
|
||||
GameData.revertToFrozen();
|
||||
}
|
||||
|
||||
minecraft.clearLevel(new GenericDirtMessageScreen(new TranslatableComponent("ltd.mod.client.negotiating").withStyle(BOLD)));
|
||||
minecraft.setCurrentServer(serverData);
|
||||
});
|
||||
log.debug("Waiting for Clear to complete");
|
||||
try {
|
||||
future.get();
|
||||
log.debug("Clear complete, continuing reset");
|
||||
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"));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
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;
|
||||
|
||||
@Slf4j
|
||||
@Setter
|
||||
@Getter
|
||||
public class ResetPacket extends HandshakeMessages.C2SAcknowledge {
|
||||
private int loginIndex;
|
||||
public ResetPacket() {
|
||||
super();
|
||||
}
|
||||
public static ResetPacket decode(FriendlyByteBuf buf) {
|
||||
return new ResetPacket();
|
||||
}
|
||||
|
||||
public void encode(FriendlyByteBuf buf) {
|
||||
}
|
||||
|
||||
public static void handler(HandshakeHandler handler , ResetPacket msg, Supplier<NetworkEvent.Context> 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);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
package com.leisuretimedock.crossmod.util;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import net.minecraft.network.FriendlyByteBuf;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HexFormat;
|
||||
|
||||
public class DebugUtils {
|
||||
public static void debugBuffer(FriendlyByteBuf buf) {
|
||||
int readable = buf.readableBytes();
|
||||
System.out.println("[Debug] Readable bytes: " + readable);
|
||||
|
||||
if (readable <= 0) {
|
||||
System.out.println("[Debug] No extra bytes to inspect.");
|
||||
return;
|
||||
}
|
||||
|
||||
// 保存当前位置
|
||||
int index = buf.readerIndex();
|
||||
|
||||
// 读取并打印十六进制
|
||||
byte[] bytes = new byte[readable];
|
||||
buf.readBytes(bytes);
|
||||
String hex = HexFormat.of().formatHex(bytes);
|
||||
System.out.println("[Debug] Extra bytes (hex): " + hex);
|
||||
|
||||
// 尝试以 UTF-8 解码(仅用于辅助分析)
|
||||
try {
|
||||
String utf8 = new String(bytes, StandardCharsets.UTF_8);
|
||||
System.out.println("[Debug] Interpreted as UTF-8 string:\n" + utf8);
|
||||
} catch (Exception e) {
|
||||
System.out.println("[Debug] Failed to interpret as UTF-8: " + e.getMessage());
|
||||
}
|
||||
|
||||
// 还原读取位置,避免影响其他逻辑
|
||||
buf.readerIndex(index);
|
||||
}
|
||||
public static void debugFullBuffer(FriendlyByteBuf buf) {
|
||||
ByteBuf internal = buf.copy(); // 复制整个缓冲区(包括所有字节)
|
||||
int size = internal.readableBytes();
|
||||
byte[] data = new byte[size];
|
||||
internal.readBytes(data);
|
||||
|
||||
System.out.println("[Debug] Full buffer size: " + size);
|
||||
System.out.println("[Debug] Hex dump:\n" + HexFormat.of().formatHex(data));
|
||||
|
||||
try {
|
||||
String utf8 = new String(data, StandardCharsets.UTF_8);
|
||||
System.out.println("[Debug] UTF-8 decoded:\n" + utf8);
|
||||
} catch (Exception e) {
|
||||
System.out.println("[Debug] UTF-8 decode failed: " + e.getMessage());
|
||||
}
|
||||
|
||||
internal.release(); // 手动释放 copy() 出来的 ByteBuf,防止泄漏
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
public net.minecraft.client.Minecraft pendingConnection #pendingConnection
|
||||
|
|
@ -1,4 +1,9 @@
|
|||
{
|
||||
"ltd.mod.client.name.trans_server": "LTD Cross Server Mod",
|
||||
"ltd.mod.client.key": "LTD Key"
|
||||
"ltd.mod.client.key": "Open LTD Cross Server Menu",
|
||||
"ltd.mod.client.negotiating": "Negotiating...",
|
||||
"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.invalid_packet": "Invalid packet"
|
||||
}
|
||||
|
|
@ -1,4 +1,10 @@
|
|||
{
|
||||
"ltd.mod.client.name.trans_server": "LTD跨服传送模组",
|
||||
"ltd.mod.client.key": "LTD跨服传送按键"
|
||||
"ltd.mod.client.key": "打开LTD跨服传送菜单",
|
||||
"ltd.mod.client.negotiating": "重定向中 ...",
|
||||
"ltd.mod.client.failed.reset_connection": "重置链接失败。",
|
||||
"ltd.mod.client.error.handshake": "握手出错",
|
||||
"ltd.mod.client.invalid_reset_packet": "无效的重置链接包",
|
||||
"ltd.mod.client.invalid_packet": "无效的包"
|
||||
|
||||
}
|
||||
11
forge-mod/src/main/resources/ltdcrossteleport.mixins.json
Normal file
11
forge-mod/src/main/resources/ltdcrossteleport.mixins.json
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"required": true,
|
||||
"package": "com.leisuretimedock.crossmod.mixin",
|
||||
"compatibilityLevel": "JAVA_17",
|
||||
"mixins": [
|
||||
"AccessorMinecraft",
|
||||
"MixinMUINetWorkHandler",
|
||||
"ModListSpoofMixin"
|
||||
],
|
||||
"minVersion": "0.8"
|
||||
}
|
||||
|
|
@ -1,3 +1,3 @@
|
|||
plugin_group=com.leisuretimedock.crossplugin
|
||||
plugin_version=1.0.0.2
|
||||
plugin_version=1.0.0.5
|
||||
plugin_name=CrossServerTeleport
|
||||
|
|
@ -18,7 +18,7 @@ public class ReloadConfigCommand implements SimpleCommand {
|
|||
|
||||
private final ConfigManager configManager;
|
||||
public static final String PERMISSION_RELOAD = "ltdcrossserver.reload";
|
||||
public static final String PERMISSION_HELP = "ltdcrossserver.help";
|
||||
|
||||
public ReloadConfigCommand(ConfigManager configManager) {
|
||||
this.configManager = configManager;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import com.velocitypowered.api.proxy.Player;
|
|||
import com.velocitypowered.api.proxy.ProxyServer;
|
||||
import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
|
||||
|
|
@ -29,9 +30,11 @@ import java.util.Collections;
|
|||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
|
||||
/**
|
||||
* 插件消息监听器,负责接收客户端发来的插件消息并处理跨服传送、Overlay显示等逻辑。
|
||||
*/
|
||||
@Slf4j
|
||||
public class PluginMessageListener {
|
||||
|
||||
// 插件消息通道标识(与客户端保持一致)
|
||||
|
|
@ -103,13 +106,13 @@ public class PluginMessageListener {
|
|||
*/
|
||||
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);
|
||||
log.trace("Received plugin message on channel 'channel' from player {}", player.getUsername());
|
||||
log.trace("Data length: {}", data.length);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (byte b : data) {
|
||||
sb.append(String.format("%02X ", b));
|
||||
}
|
||||
System.out.println("Data hex: " + sb);
|
||||
log.trace("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);
|
||||
|
|
@ -117,7 +120,14 @@ public class PluginMessageListener {
|
|||
if ("client_ready".equals(command)) {
|
||||
if (waitingForReady.remove(player)) {
|
||||
logger.debug("[CrossTeleportMod] {} is ready, sending overlay", player.getUsername());
|
||||
OverlayManager.showOverlay(player);
|
||||
player.getCurrentServer().ifPresent(i -> {
|
||||
String name = i.getServerInfo().getName();
|
||||
boolean contains = configManager.getOverlayServers().contains(name);
|
||||
if (contains) {
|
||||
OverlayManager.showOverlay(player);
|
||||
}
|
||||
else OverlayManager.hideOverlay(player);
|
||||
});
|
||||
// TODO: 支持发送自定义服务器列表
|
||||
} else {
|
||||
logger.debug("[CrossTeleportMod] Received client_ready from {}, but not in waiting set", player.getUsername());
|
||||
|
|
@ -171,13 +181,7 @@ public class PluginMessageListener {
|
|||
|
||||
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());
|
||||
}
|
||||
waitingForReady.add(player);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user