功能权限系统(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 颜色码),普通玩家在聊天栏看不到,但服务端可以借此在聊天消息中夹带权限变更。详见 聊天控制码。