功能权限系统(Feature / Policy)

基本信息

属性 值
核心枚举 journeymap.client.feature.Feature(Feature.java:10)
权限类 journeymap.client.feature.Policy(Policy.java:19)
管理器 journeymap.client.feature.FeatureManager(FeatureManager.java:20)
策略集接口 FeatureManager.PolicySet(FeatureManager.java:222)
受控功能数 5(Feature.java:12-16)
内置策略集 2 个(Unlimited、NoRadar),外加 1 个内联 Fallback(FairPlay)
反射查找的类 journeymap.client.feature.impl.Unlimited、journeymap.client.feature.impl.NoRadar(FeatureManager.java:22-24)

功能

这是 JourneyMap 面向多人的"反作弊式"功能开关系统:服务器可以只允许玩家使用地图的部分能力。它把每个功能拆成"单人允许 / 多人允许"两个独立许可,客户端在渲染前统一查询。

5 个受控功能

Feature 枚举只有 5 个常量(Feature.java:12-16):

功能 说明
RadarPlayers 玩家雷达
RadarAnimals 动物雷达
RadarMobs 敌对生物雷达
RadarVillagers 村民雷达
MapCaves 洞穴地图

两个分组辅助方法(Feature.java:18-26):

  • Feature.radar() → EnumSet.of(RadarPlayers, RadarAnimals, RadarMobs, RadarVillagers)(4 项雷达,不含 MapCaves)
  • Feature.all() → EnumSet.allOf(Feature.class)(5 项全含)

查询接口

方法 签名要点 行为 源码
FeatureManager.isAllowed(Feature) static boolean 取该功能的 Policy,不存在或当前不允许则返回 false :80-84
FeatureManager.getAllowedFeatures() static Map<Feature, Boolean> 一次性返回全部 5 项的许可状态 :91-101
FeatureManager.getPolicySetName() static String 当前策略集名(FairPlay / Unlimited / NoRadar) :106-109
FeatureManager.getPolicyDetails() static String 多行文本,逐项列出每个功能在单人/多人下的许可 :48-65

getPolicyDetails() 的输出格式为 "\n\t<功能名> : singleplayer = <bool> , multiplayer = <bool>"(:62),服务端管理员用它排查权限问题。

策略集的解析顺序

locatePolicySet()(FeatureManager.java:156-169)按类是否存在顺序尝试,首个成功实例化的即为生效策略集:

顺序 类名 存在? 策略集名 效果
1 journeymap.client.feature.impl.Unlimited 是(impl/Unlimited.java) Unlimited 全部 5 项 Policy.bulkCreate(true, true),单人与多人均允许
2 journeymap.client.feature.impl.NoRadar 是(impl/NoRadar.java) NoRadar 4 项雷达 (单人=true, 多人=false);MapCaves 保持 (true, true)
3 都不存在 → createFairPlay() 内联匿名 PolicySet(:190-212) FairPlay 单人全允许,多人全部禁用(Policy.bulkCreate(true, false),:197)

instantiatePolicySet()(:171-188)对每个候选类做三重校验:反射加载 → 必须 implements PolicySet(否则记警告并跳过,:175-178)→ 无参构造实例化。任一步抛异常都返回 null 继续尝试下一个。

构建产物决定生效策略:build.gradle.kts 的 tasks.jar 显式 exclude("journeymap/client/feature/impl/NoRadar.class")(build.gradle.kts,tasks.jar 块),即主 jar 中 NoRadar 被剔除、Unlimited 被保留。因此正常发布的主 jar 总是命中第 1 项 Unlimited;只有当 Unlimited 也缺失时才可能落到 NoRadar,两者都缺失才回退 FairPlay。另有一个 fairPlayJar 任务产出排除 Unlimited.class 的 FairPlay 变体(tasks.fairPlayJar)。

反射目标类核验

按"字符串反射的注册表可能指向不存在的类"这一风险逐个核验(find 确认两个 .java 均存在):

反射类名 对应源文件 存在
journeymap.client.feature.impl.Unlimited src/main/java/journeymap/client/feature/impl/Unlimited.java 是
journeymap.client.feature.impl.NoRadar src/main/java/journeymap/client/feature/impl/NoRadar.java 是

无失效引用。

数值

数值名 值
受控功能总数 5
雷达类功能数 4(Feature.radar())
候选策略集数 2 个具名 + 1 个内联
主 jar 中被排除的策略类 1(NoRadar)
聊天控制码种类 2 类 × 2 种写法(带空格 / 不带空格)= 4 个字符串键

交互

触发 行为
客户端启动 FeatureManager 单例构造,locatePolicySet() 解析策略集并 reset() 建立初始 policyMap(:35-39)
收到聊天中的控制码 handleControlCode(code) 把对应功能的 Policy 改为"单人允许、多人禁止"(:116-128)
下一次 reset() 若曾被控制码改过(controlCodeAltered == true),恢复为策略集默认并记日志(:133-150)
服务器进服 WorldEventHandler 等生命周期事件触发策略重置

聊天控制码

FeatureManager 构造时注册 4 个控制码字符串(:31-34),带空格与不带空格两种写法各一组,映射关系如下(Codes.java:13-14 给出常量定义):

控制码 禁用的功能
\u00a73 \u00a76 \u00a73 \u00a76 \u00a73 \u00a76 \u00a7e Feature.radar()(4 项雷达)
\u00a73\u00a76\u00a73\u00a76\u00a73\u00a76\u00a7e 同上
\u00a73 \u00a76 \u00a73 \u00a76 \u00a73 \u00a76 \u00a7d EnumSet.of(Feature.MapCaves)
\u00a73\u00a76\u00a73\u00a76\u00a73\u00a76\u00a7d 同上

它们是不可见的格式控制码序列(§3/§6 颜色码),普通玩家在聊天栏看不到,但服务端可以借此在聊天消息中夹带权限变更。详见 聊天控制码。

相关条目