World ID 同步

基本信息

属性 值
封包通道名 world_info(WorldIDPacket.CHANNEL_NAME,WorldIDPacket.java:26)
封包类 journeymap.common.network.WorldIDPacket(实现 IMessage,:23)
公共发送端 journeymap.common.network.PacketHandler(PacketHandler.java:19)
客户端接收端 journeymap.client.network.WorldInfoHandler(WorldInfoHandler.java:40)
封包判别符 PACKET_WORLDID = 0(WorldInfoHandler.java:43)
请求节流 1000 ms(MIN_DELAY_MS,WorldInfoHandler.java:46)
方向 服务端 → 客户端(单向推送 + 客户端主动请求)
载荷 单个 UTF-8 字符串(World ID)

功能

“World ID” 是 JourneyMap 给每个存档分配的一个稳定标识(默认随机 UUID,存放在服务端 <世界名>.cfg)。客户端用它作为地图数据的命名空间键——同一台服务器上多个存档、或换 IP 后重连,都靠它把地图数据对上号。

封包

WorldIDPacket 实现 IMessage,只有一个 String worldID 字段(:28):

方法 实现 源码
fromBytes(ByteBuf) ByteBufUtils.readUTF8String(buf),异常记 error :47-57
toBytes(ByteBuf) 仅当 worldID != null 才写 :60-70

WorldIDPacket() 无参构造(:30-33)用于客户端"空请求"——发一个空封包问服务端要 ID。

内部类 WorldIdListener implements IMessageHandler<WorldIDPacket, IMessage>(:73)是客户端的实际处理者。

服务端发送

PacketHandler(PacketHandler.java:19)持有通道单例,提供两个发送方法:

| 方法 | 目标 | 实现 | 源码 | |------|------|------|------|----------| | sendAllPlayersWorldID(String) | 全体在线玩家 | WORLD_INFO_CHANNEL.sendToAll(new WorldIDPacket(worldID)) | :23-26 | | sendPlayerWorldID(String, EntityPlayerMP) | 单个玩家 | WORLD_INFO_CHANNEL.sendTo(...),RuntimeException 与 Exception 分别记 error | :28-52 |

sendPlayerWorldID() 的 instanceof EntityPlayerMP 检查写在 player != null 之前(:32)——顺序反了,若 player 为 null 会在 instanceof 上安全返回 false,功能上无 bug,但属于可疑写法。

PacketHandler 中另有一个私有的 toInt(boolean) 工具方法(:54-),在本条同步路径中未被调用。

客户端接收

WorldInfoHandler 构造时注册通道与消息(:61-77):

channel = NetworkRegistry.INSTANCE.newSimpleChannel(WorldIDPacket.CHANNEL_NAME);
channel.registerMessage(WorldIdListener.class, WorldIDPacket.class, PACKET_WORLDID, Side.CLIENT);
MinecraftForge.EVENT_BUS.register(this);

注册成功后打印 Registered channel: world_info(:69);抛 Throwable 则记 error(:73-75)。

触发点有 2 个 @SubscribeEvent:

事件 行为 源码
EntityJoinWorldEvent 仅当自己进世界时才请求——比较 event.entity 与 mc.thePlayer 的实体名是否相同(:112),且要求非单人游戏(!mc.isSingleplayer())且玩家未死亡 :107-116
FMLNetworkEvent.ClientConnectedToServerEvent 空实现,方法体只有一行注释 // TODO: Check the timing on this?(:98-100) :96-101

请求节流

requestWorldID()(:84-95)在发请求前检查两个时间戳:

if (lastRequest + MIN_DELAY_MS < now && lastResponse + MIN_DELAY_MS < now)

即距上次请求和距上次响应都必须超过 1000 ms 才发下一次,避免刷屏与死循环。成功发送后更新 lastRequest。

数值

数值名 值
通道名 world_info
封包判别符 0
注册方向 Side.CLIENT(服务端发,客户端收)
请求最小间隔 1000 ms
载荷大小 1 个 UTF-8 字符串
WorldIDPacket 的构造器 2(无参空请求 / 带 worldID)
客户端事件触发点 2(EntityJoinWorldEvent 有效,ClientConnectedToServerEvent 为空壳)
服务端默认 World ID UUID.randomUUID().toString()(ConfigHandler.java:77)

交互

触发 行为
玩家加入服务端世界(服务端侧) ForgeEvents.UserJoinWorldThread 在 isUsingWorldID() 为真时调 PacketManager.instance.sendPlayerWorldID()(ForgeEvents.java:86-90)
管理员执行 /jmserver worldid resync PacketHandler.sendAllPlayersWorldID() 向全体重发(CommandJourneyMapServer.java:76-81)
管理员执行 /jmserver worldid set/setrandom 先落盘再 PacketHandler.sendAllPlayersWorldID() 广播(:93-100)
客户端自己进世界 触发 EntityJoinWorldEvent → requestWorldID()(受 1000 ms 节流)
客户端连上服务器 ClientConnectedToServerEvent 触发但什么都不做

服务端走的是另一套实现:JourneymapServer.initialize() 中 PacketManager.init(new ForgePacketHandler())(JourneymapServer.java:80),ForgeEvents.java:89 调的 PacketManager.instance.sendPlayerWorldID(worldID, playerName) 按玩家名分发(PacketManager.java:32-35),最终交给 IPacketHandler 实现。common/network/PacketHandler 那套按 EntityPlayerMP 的代码属于公共/客户端侧。同一初始化方法还注册了 new ForgeEvents() 到 MinecraftForge.EVENT_BUS(JourneymapServer.java:78),这正是控制码链路的起点。

死代码

  • FMLNetworkEvent.ClientConnectedToServerEvent 处理器为空实现(WorldInfoHandler.java:96-101),仅含 // TODO: Check the timing on this?
  • PacketHandler.toInt(boolean)(PacketHandler.java:54-)无调用方
  • PacketHandler 的 JM_PERMS 通道常量被注释(:22),配套的 PermissionsPacket 整类不可用(详见 聊天控制码)

相关条目