[MD]
# 【1.2.1 更新】开放对外 API:让第三方游戏模式复用本插件
> WarZBombDefuse 的第三方附加组件合集
> 每个功能都是独立模块,`config.yml` 单独开关,互不影响,无需改动 WBD 本体
---
## ⚠️ 重要变更:移除「防第三人称」,不再需要 ProtocolLib
上个版本(1.0.1)新增的 `antithirdcam`(防第三人称)模块,**本次已连同代码一起删除**。
原因是实测下来它在 Arclight 这类 Forge + Bukkit 混合端上**做不到两全**:
- 遮挡面摆在能挡住第三人称的位置时,第一人称一低头就会扫到它,糊一脸黑块
- 把面挪开避开第一人称,第三人称又挡不住了
根因是这套方案要求在**相机坐标系**里精确摆放一块巨大平面,而这块面能遮住哪里取决于客户端的 FOV、视角距离设置、实体渲染距离 —— **这些服务端全都拿不到**,只能靠一次次试。更根本的一点:面被摆在「相机下方」时,它主要盖住的是视野的**下方**,而隔掩体偷看看的是**正前方**,所以它的实际防护效果本来就有限。
与其留着一个「看起来在跑、其实未必挡得住」的功能,不如直接去掉。
**连带影响:**
| 项目 | 变化 |
| --- | --- |
| **ProtocolLib** | **不再需要**。它当初只是为了让遮挡面「只对本人可见」而引入,现在已无任何模块依赖它,`plugin.yml` 的软依赖与 `pom.xml` 的依赖均已删除 |
| `NOTICE.md` | 已删除(那份「防第三人称现在需要 ProtocolLib」的告知随之作废) |
| `/wbdaddon diag` | **命令已移除**。它就是为诊断该模块的 API 可用性而加的 |
| 旧配置 | `config.yml` 里的 `modules.antithirdcam` 整段可以直接删掉,留着不会有任何效果 |
> 如果你当初是为了这个模块才装的 ProtocolLib,现在可以把它一起移除了。
---
## 🌟 头号新功能:对外 API —— 让第三方游戏模式复用本插件
### 改造前的问题
在此之前,每个模块都用同一句话判断「这个玩家在不在竞技场」:
```java
wbd.getArenaManager().getSession(player) != null
```
这句话把「在竞技场里」和「在 WBD 的爆破模式里」划了等号。于是一个独立实现的模式(比如团队死斗)的玩家不在 WBD 的 `ArenaSession` 里,**所有模块都看不见他们** —— 饥饿不锁、JourneyMap 上队友敌军分不清,而且**不会报任何错**。
### 现在
WBDAddon 把「一个游戏模式」抽象成 `AddonMode` 接口,并发布 `WbdAddonApi` 服务。任何第三方模式都可以注册进来,让上面那些附加功能**自动**对新模式的玩家生效 —— 不必每个模式各写一遍。爆破模式本身也变成了这个接口的一个实现(内置注册,永远排在第一位)。
### 用法
模式侧只需要实现 `AddonMode`:
```java
public final class MyMode implements AddonMode {
@Override public String getModeId() { return "my-mode"; } // 稳定标识
@Override public String getDisplayName() { return "我的模式"; }
@Override public boolean isParticipating(Player p) { return match(p) != null; }
@Override public boolean isPlaying(Player p) { return match(p) != null; }
@Override public boolean isAlive(Player p) { return match(p) != null && match(p).isAlive(p); }
@Override public String getTeamId(Player p) { return match(p).teamId(p); } // "T" / "CT" / null
@Override public String getArenaId(Player p) { return match(p).arenaId(); }
@Override public String getArenaName(Player p) { return match(p).arenaName(); }
}
```
然后在自己的 `onEnable` 里注册:
```java
RegisteredServiceProvider<WbdAddonApi> rsp =
Bukkit.getServicesManager().getRegistration(WbdAddonApi.class);
if (rsp == null) return; // WBDAddon 没装,降级即可
rsp.getProvider().registerMode(new MyMode());
```
> 在 `plugin.yml` 里写 `softdepend: [WBDAddon]`,就能保证自己的 `onEnable` 一定在服务发布之后执行,不必延迟到下一 tick。
> 记得在自己的 `onDisable` 里 `unregisterMode("my-mode")`,否则 WBDAddon 的定时任务会一直踩到一个已经卸载的模式对象。
### `WbdAddonApi` 提供了什么
| 方法 | 作用 |
| --- | --- |
| `getApiVersion()` | API 版本号(当前 `1.2`),可据此判断有没有自己要用的能力 |
| `registerMode(mode)` / `unregisterMode(id)` | 注册 / 注销模式。同一 id 重复注册会覆盖,重载时不必先注销 |
| `getModes()` / `getMode(id)` / `getModeFor(player)` | 查询已接入的模式与玩家归属 |
| `isInAnyMode(player)` | 玩家是否属于任一已注册模式 |
| `resolveWeaponName(player)` | 解析玩家手中武器的人类可读名(TacZ 枪械 `tacz:p320 → P320`) |
| `getCombatStats()` | 取伤害账本,做击杀归属校正或自建伤害统计;模块被禁用时返回 `null` |
| `isHudRelayAvailable()` | 本插件此刻是否真的会代你渲染顶部状态行 |
### 注册后自动获得什么
| 能力 | 由哪个模块提供 | 触发条件 |
| --- | --- | --- |
| 饥饿锁定(跑速恒定、不回血) | `arena-food` | `isParticipating` 为 true |
| JourneyMap 队友可见 / 敌军隐藏 | `journeymap-bridge` | `isAlive` 且有 `getTeamId` |
| 顶部状态行代渲染 | `match-status` | 实现了 `hudLine`,且该模块启用 |
| 伤害账本(击杀归属校正) | `kill-reports` | 通过 `api.getCombatStats()` 取用 |
| 枪械名解析 | 内置 | 通过 `api.resolveWeaponName(player)` 取用 |
### 两个容易踩的坑
1. **`hudLine` 只是「申请」,不保证有人发。** 代渲染取决于 `match-status` 模块是否启用、它的顶栏开关有没有被服主关掉。注册后请调用一次 `api.isHudRelayAvailable()`,返回 `false` 时自己把 HUD 发出去 —— 否则玩家屏幕上会什么都没有,且不报错。
2. **JourneyMap 桥接受「玩家当前使用哪块计分板」影响。** 原版队伍数据跟着玩家当前所用的计分板下发,所以模式如果给玩家 `setScoreboard(自己的独立计分板)`,就会顺带把 `wbd_*` 队伍数据从该玩家那里屏蔽掉,JourneyMap 于是读不到队友关系。想同时要自定义侧边栏和 JourneyMap 桥接,就把侧边栏注册在**主计分板**上。
---
## 🧩 API 1.2 新增:队友名牌前缀 + 隐藏敌人名牌
模式现在可以额外实现两个方法,让队友头顶带前缀、敌人名牌完全隐藏:
| 方法 | 作用 |
| --- | --- |
| `nameTagPrefix(player)` | 队友头顶的名牌前缀,返回 `null` 表示不改动名牌 |
| `hideEnemyNameTags()` | 是否隐藏敌人的头顶名牌 |
实现走的是**原版队伍的 `NAME_TAG_VISIBILITY`**,不需要任何发包插件。两项设计成配对使用:只加前缀而不管可见性的话,敌人头上也会顶着一个 `[队友]`,比不做还糟。
**多模式共存的隔离**:生成的队伍名形如 `wbd_<模式id>_<阵营id>`(例:`wbd_bomb_def_T`)。带上模式 id 是必要的 —— 爆破与死斗都有 T/CT,若只按阵营拼名,两个模式的玩家会挤进同一个原版队伍,在 JourneyMap 上互相看得见,那等于把敌军位置显示给了对手。名字会被裁剪到原版 16 字符上限以内。
> **对爆破玩法没有任何影响**:内置的爆破模式这两个方法都用默认值(不改名牌、不隐藏),因为爆破模式的名牌显示由 WarZBombDefuse 本体自己负责,附加组件擅自设前缀会和主插件抢同一个队伍的 prefix。
---
## ⌨️ 命令变动
| 命令 | 状态 | 作用 |
| --- | --- | --- |
| `/wbdaddon modes` | **新增** | 查看已接入的游戏模式与各自的在线人数 |
| `/wbdaddon diag` | **已移除** | (原为诊断防第三人称的 API 可用性而加) |
| `/wbdaddon reload` | 不变 | 重载配置并重新启停模块 |
| `/wbdaddon modules` | 不变 | 查看各模块启用状态 |
| `/wbdaddon sound <音效名> [音调]` | 不变 | 试听拆弹提示音 |
> 排查「第三方模式接进来了但附加功能没生效」时先看 `/wbdaddon modes`:
> 模式没出现说明注册没成功(多半是加载顺序);模式在、人数为 0 说明它的 `isParticipating` 判定写错了。
---
## 🔧 内部重构
- **伤害账本对外**:`kill-reports` 的伤害累计表抽成 `CombatStats` 接口(`recordDamage` / `getDamageSources` / `reset`),第三方模式可以拿它做击杀归属校正,也可以 `reset()` 在每场比赛开始时清账
- **枪械名解析提到工具类**:原本埋在淘汰消息监听器的私有方法里,现在提取为 `WeaponNames` 并经由 `api.resolveWeaponName(player)` 对外复用。自己实现一遍枪械改名不难,但要把 TacZ 桥接、自定义显示名、lore 回退、颜色码剥离这些边界都处理对就没那么轻松了
- **所有模块改为面向 `AddonMode` 判定玩家**,不再直接认识 WarZBombDefuse 的 `ArenaSession`。这就是第三方模式能被自动覆盖到的前提
---
## 📦 升级步骤
1. 删掉 `plugins/` 里旧的 `WBDAddon-*.jar`(**文件名不认,Bukkit 只认插件名,留着多个会冲突**)
2. 放入新的 `WBDAddon-1.2.1-xxxxxxx-xxxxxxxx-xxxx.jar`
3. **不需要 ProtocolLib** —— 如果你当初是为了本插件装的,可以一并移除
4. 重启服务器,或执行 `/wbdaddon reload`
> 旧版配置文件不会自动补上新配置项,建议对照新版 `config.yml` 手动合并,或删掉重新生成。
> 旧配置里的 `modules.antithirdcam` 整段已无对应模块,删掉即可。
---
## 📋 模块一览
| 模块 | id | 说明 |
| --- | --- | --- |
| 击杀报告 | `kill-reports` | 淘汰提示显示击杀者与武器;回合结束给每人单独发送自己的伤害统计 |
| 比赛状态 | `match-status` | 顶部显示 T/CT 存活人数;炸弹安放后 Bossbar 倒计时 |
| 拆弹声音提示 | `bomb-defuse-sound` | CT 拆弹时向全图播放可定位的提示音与滴答声 |
| 竞技场饥饿锁定 | `arena-food` | 饥饿恒定到安全值:跑速恒定、不自然回血 |
| JourneyMap 桥接 | `journeymap-bridge` | 同步队伍到原版 Team,配合 JourneyMap Teams 队友可见、敌军隐藏 |
| 模式接入 API | — | **新增** — 通过 `WbdAddonApi` 让第三方模式复用上面这些能力 |
| 防第三人称(已移除) | ~~antithirdcam~~ | **本次删除** —— 实测在混合端上做不到两全,连同 ProtocolLib 依赖一并删除 |
---
## 🔗 配合使用:WBDTeamDeathmatch
本 API 的第一个使用方是同一作者的 **WBDTeamDeathmatch**(团队死斗模式,无限复活 / 限时击杀赛)。
它通过注册 `AddonMode` 直接复用本插件的饥饿锁定、JourneyMap 桥接、伤害账本与枪械名解析,
并覆盖了自己的名牌策略(死斗的近身缠斗节奏下,隐藏敌人名牌比显示它更合理)。
---
有问题欢迎回帖或提 Issue,反馈 QQ:2387629002
[/MD]