网络协议

Opis 客户端与服务端之间的全部通信定义。

基本信息

属性 值
通道名 "Opis"(PacketManager.java:87 NetworkRegistry.INSTANCE.newChannel("Opis", codec))
编解码器 私有静态类 PacketManager.Codec,继承 FMLIndexedMessageToMessageCodec<PacketBase>
封包类数量 5
discriminator 分配 0–4,连续
初始化时机 preInit 末尾 PacketManager.init()(modOpis.java:174)
远端版本检查 @NetworkCheckHandler(modOpis.java:121-126)

功能

5 个封包类

discriminator 类 方向 用途
0 PacketReqChunks 客户端 → 服务端 请求区块数据
1 PacketReqData 客户端 → 服务端 请求数据(Message + 可选 ISerializable 参数)
2 NetDataCommand 服务端 → 客户端 无负载指令
3 NetDataList 服务端 → 客户端 列表数据
4 NetDataValue 服务端 → 客户端 单值数据

addDiscriminator 被覆写(PacketManager.java:126-135),会先检查目标类有无无参构造函数,缺失则提前抛错。

Message 枚举分组

mcp.mobius.opis.network.enums.Message 共 69 个成员,按前缀分 10 组:

前缀 成员数 用途 发送方
LIST_ 22 列表类数据请求或下发 双向
COMMAND_ 18 面板操作指令 双向
STATUS_ 10 采集状态广播 服务端 → 客户端
CLIENT_ 7 仅客户端可达的指令 服务端 → 客户端
VALUE_ 6 单值汇总数据 服务端 → 客户端
NEXUS_ 2 Nexus 远程系统登录与实时数据 双向
SWING_ / CHAT_ / PLAYER_ / CONNECTION_ 各 1 面板页签、聊天、玩家状态、连接状态 双向

Message 的每个成员带两个元数据(Message.java:96-118):

  • accessLevel:默认 AccessLevel.NONE,另有 13 个 COMMAND_* 成员在构造时硬编码为 PRIVILEGED
  • tabEnum:默认 EnumSet.of(SelectedTab.ANY),另有 9 个成员绑定到具体页签,用于决定数据是否在该页显示:LIST_PLAYERS、LIST_DIMENSION_DATA、LIST_FORCE_CHUNK_DATA、LIST_PACKETS_OUTBOUND、LIST_PACKETS_INBOUND、LIST_PACKETS_OUTBOUND_250、LIST_PACKETS_INBOUND_250、LIST_THREADS、STATUS_PING

权限校验

Message.canPlayerUseCommand(EntityPlayerMP) 用 PlayerTracker.INSTANCE.getPlayerAccessLevel(player).ordinal() >= this.accessLevel.ordinal() 做序数比较。AccessLevel 的声明顺序即优先级(AccessLevel.java:3-7):NONE < PRIVILEGED < ADMIN。

accessLevel 可在运行期整体改写,见 config 的 ACCESS_RIGHTS:tables 与 ACCESS_RIGHTS:opis。

服务端节流

ServerMessageHandler 对 7 个会遍历区块/探针集合的请求做节流(ServerMessageHandler.java:58-68):LIST_CHUNK_LOADED、LIST_TIMING_CHUNK、LIST_TIMING_CHUNK_DIM、LIST_TIMING_TILEENTS、LIST_TIMING_ENTITIES、LIST_AMOUNT_ENTITIES、LIST_AMOUNT_TILEENTS。同一玩家同一 Message 的最小间隔为 MIN_REQUEST_INTERVAL_MS = 200 毫秒,超出则静默丢弃(注释:Dropped rather than punished: access can be revoked while a client is still polling.)。

命中节流的请求还会被排进 OpisServerTickHandler 的服务端线程队列,且同一玩家同一 Message 只保留最新一个任务(queued.put 返回非 null 即丢弃旧的)。

远端版本兼容

checkRemoteVersion(modOpis.java:121-126)的判定:

  • 远端没有 Opis(remoteVersion == null)→ 放行,Opis 在远端是可选的
  • 版本号与本地 Tags.VERSION 相同 → 放行
  • 版本号 >= 1.4.12-mapless → 放行(这是 Navigator 覆盖层所需的协议版本)
  • 其余 → 拒绝

数值

数值名 值
Message 成员总数 69
封包 discriminator 上限 4
通道名 Opis
服务端请求节流间隔 200 ms
默认硬编码 PRIVILEGED 的 Message 数 13
绑定具体 SelectedTab 的 Message 数 9
被节流的请求 Message 数 7
Navigator 协议版本常量 1.4.12-mapless

已知事实:两个 Message 永不发出

  • VALUE_TIMING_ENTUPDATE:客户端注册了 handler(ProxyClient.java:177)、PanelSummary 也有对应 case(PanelSummary.java:497),但服务端没有任何一处发送。ServerMessageHandler.java:160 的分支体是空的 {},且它不在 PacketManager.sendFullUpdate 的下发列表(PacketManager.java:293-318)里。全仓 4 处引用均为接收侧或空分支。
  • LIST_TIMING_CHUNK_DIM 与 LIST_TIMING_CHUNK 是两条独立消息:前者按维度排名(getTopChunks(100, dim)),后者全局排名(getTopChunks(100))。Message.java:90-91 的注释说明拆分的动机:Dimension-scoped copy of LIST_TIMING_CHUNK, so overlay polling cannot overwrite the Swing table. —— 地图覆盖层的轮询不会覆盖 Swing 表格里的全局榜单。

相关条目

  • extension-api - 接收这些封包的接口与注册器
  • config - accessLevel 的运行期改写
  • swing-ui - 消费这些消息的 23 个面板
  • navigator - 地图覆盖层轮询使用的 LIST_TIMING_CHUNK_DIM