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整类不可用(详见 聊天控制码)
相关条目
- /jmserver 指令 - 查看与修改 World ID
- 聊天控制码 - 与本条走同一线程、但用聊天而非封包传输
- 配置文件 - World ID 存在服务端
<世界名>.cfg - 入口与代理类 -
checkModLists()握手