聊天控制码(服务端功能禁用协议)
基本信息
| 属性 | 值 |
|---|---|
| 服务端发送方 | 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(),然后:
- 先用
replaceAll(EnumChatFormatting.RESET.toString(), "")删掉所有§r(:49)——因为§r插入在控制码中间会打断匹配 - 交给
checkForControlCode():先快速判断文本是否含§(:61),不含则直接返回 - 遍历
featureControlCodes集合做text.contains(code)子串匹配 - 任一命中后,若之前未命中过则清空数据缓存并重置界面(
: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() 的注释对照,说明该文件曾为多版本共用。
相关条目
- 功能权限系统 - 控制码被应用到的
Feature枚举与Policy - World ID 同步 - 同一线程中发送的另一种数据(走封包而非聊天)
- 配置文件 - 服务端
<世界名>.cfg的 6 个权限项 - /jmserver 指令 - 查看 World ID 的诊断指令