聊天控制码(服务端功能禁用协议)

基本信息

属性 值
服务端发送方 journeymap.server.oldservercode.events.ForgeEvents.UserJoinWorldThread(ForgeEvents.java:42)
服务端判定器 journeymap.server.oldservercode.mapcontrol.MappingOptionsHandler(MappingOptionsHandler.java:16)
控制码常量 journeymap.server.oldservercode.reference.Codes(Codes.java:13-14)
客户端接收方 journeymap.client.forge.event.ChatEventHandler(ChatEventHandler.java:27)
权限应用方 journeymap.client.feature.FeatureManager.handleControlCode()(FeatureManager.java:116)
传输载体 ClientChatReceivedEvent(一条普通聊天消息)
控制码种类 2 类 × 2 种写法 = 4 个字符串键

功能

这是 JourneyMap 服务端限制玩家地图功能的唯一生效通道。它不新增协议或封包,而是把不可见的 Minecraft 格式控制码(§ 颜色码序列)塞进一条发给玩家的聊天消息里;客户端在渲染聊天前扫描文本,命中即改写本地功能权限。

完整链路

从服务端玩家进世界到客户端权限改写,共 6 步:

服务端玩家进世界
  → ForgeEvents.on(EntityJoinWorldEvent)                    ForgeEvents.java:29-39
  → 起 UserJoinWorldThread,先 sleep(500ms)                  :61
  → options.disableRadar(playerName)                         :74
  → options.disableCaveMapping(playerName)                   :80
  → 命中则 player.addChatMessage(new ChatComponentTranslation(Codes.RADAR_CODE / CAVE_MAPPING_CODE))  :77、:83
        ↓ (网络传输一条聊天消息)
  → 客户端 ClientChatReceivedEvent                          ChatEventHandler.java:40
  → 去掉 §r 后 checkForControlCode(text)                     :49
  → 遍历 featureControlCodes,命中则 FeatureManager.handleControlCode(code)  :64-71
  → 重建缓存与界面:DataCache.purge() + UIManager.reset()    :72-76

两个控制码

Codes.java:13-14 定义两个常量,均为 §3 §6 §3 §6 §3 §6 § 加上一个结尾符:

常量 结尾符 解码后颜色 客户端映射到的功能
RADAR_CODE \u00a7e 黄色(§e) Feature.radar() —— 4 项雷达全部禁用
CAVE_MAPPING_CODE \u00a7d 红色(§d) EnumSet.of(Feature.MapCaves) —— 禁用洞穴地图

结尾符 e(黄)与 d(红)在渲染后不可见,玩家只会看到一条空白的颜色切换——这是"不可见控制码"的关键。

客户端 FeatureManager 构造时额外注册了 4 个键(FeatureManager.java:31-34),在上述两个常量之外各多一个不带空格的变体(\u00a73\u00a76\u00a73\u00a76\u00a73\u00a76\u00a7e),以兼容不同插件生成的聊天文本。服务端常量只使用带空格的形式,两个无空格键是为第三方插件准备的兼容项。

客户端匹配前的规范化

ChatEventHandler.invoke()(:40-57)取 event.message.getFormattedText(),然后:

  1. 先用 replaceAll(EnumChatFormatting.RESET.toString(), "") 删掉所有 §r(:49)——因为 §r 插入在控制码中间会打断匹配
  2. 交给 checkForControlCode():先快速判断文本是否含 §(:61),不含则直接返回
  3. 遍历 featureControlCodes 集合做 text.contains(code) 子串匹配
  4. 任一命中后,若之前未命中过则清空数据缓存并重置界面(:72-76)

整个过程包在 try/catch (Exception) 中,异常只记 warn(:52-55),不影响聊天显示。

数值

数值名 值
控制码类数 2(雷达 / 洞穴地图)
客户端注册的匹配键总数 4(2 类 × 带空格 / 不带空格)
服务端实际发出的形式 仅带空格形式(Codes.RADAR_CODE / CAVE_MAPPING_CODE)
权限改写方式 new Policy(feature, true, false) —— 单人允许、多人禁止(FeatureManager.java:126)
匹配前规范化 移除全部 §r
命中后副作用 DataCache.instance().purge() + UIManager.getInstance().reset()
服务端进服延迟 sleep(500L) 后再发送(ForgeEvents.java:61)

MappingOptionsHandler 的判定顺序

disableRadar(player)(MappingOptionsHandler.java:26-44)按顺序短路,任一命中即不禁用:

顺序 条件 结果
1 config.getRadar().isPlayerRadar() 为 true 不禁用(全体放行)
2 玩家是 op 且 isOpRadar() 为 true 不禁用(op 豁免)
3 白名单(getWhiteListRadar() 非 null)含该玩家 不禁用
4 以上都不满足 禁用

disableCaveMapping(player)(:46-64)结构完全相同,只是换成 CaveMapping 组的三个配置项。

白名单解析 isUserInWhiteList()(:66-86)先去掉所有空格,含 , 则按逗号切分做忽略大小写的整串比对(:75),否则整串比对(:81)。

服务端默认配置(ConfigHandler.java:78-83)把这 6 项全部设为放行(true / true / ""),即开箱即用状态下不会禁用任何功能;必须管理员手动改 <世界名>.cfg 才会生效。

交互

触发 行为
玩家进入服务端世界 EntityJoinWorldEvent → 后台线程等待 500ms → 按配置决定是否发控制码
管理员把 Radar.PlayerRadar 设为 false 普通玩家进服收到 RADAR_CODE,本地 4 项雷达被禁用并清空缓存
管理员把 Radar.WhiteListRadar 填入玩家名(逗号分隔) 白名单内玩家进服不受影响
客户端收到含控制码的聊天 先剥离 §r,再匹配;命中则重建缓存与界面
FeatureManager.reset() 再次执行 因 controlCodeAltered 为 true,恢复策略集默认权限(FeatureManager.java:145-148)

死代码

journeymap.server.oldservercode.network.PermissionsPacket(PermissionsPacket.java:11)是一个已被弃用的封包方案,当前完全不生效:

  • 类声明的 implements IMessage 被注释掉:public class PermissionsPacket /*implements IMessage*/(:11)
  • 在 common/network/PacketHandler.java 中的注册与发送代码整段被注释(:68-70、:82)
  • 通道常量 JM_PERMS 亦被注释(PacketHandler.java:22)

该类保留了一个接收 5 个 int 的构造器(radarAnimals, radarPlayers, radarMobs, radarVillagers, mapCaves,PermissionsPacket.java:20)与 PermissionsListener 内部类(:66-68),但没有任何代码路径能实例化并发送它。功能权限完全走聊天控制码,不走封包。

另 ForgeEvents.java:68-72 保留了 1.7.10 / 1.8 两版 getCommandSenderName() / getName() 的注释对照,说明该文件曾为多版本共用。

相关条目