100 lines
7.6 KiB
Markdown
100 lines
7.6 KiB
Markdown
# `plugins/` — 可拔插客户端扩展插件架构
|
||
|
||
`plugins/` 目录承载 **Diablo II: Lord of Destruction (v1.13c) Web Port** 的所有非侵入式客户端辅助扩展插件。所有插件均与核心客户端(`src/client`)严格解耦,仅通过只读插件宿主契约([`src/client/plugin-host/api.ts`](../src/client/plugin-host/api.ts))订阅 `ServerEvent` 与会话状态快照,支持用户在顶部工具栏 **🧩 Plugins(插件)** 弹窗中动态启用/禁用插件、控制工具栏快捷入口显隐,或打开插件独立配套页面。
|
||
|
||
---
|
||
|
||
## 1. 架构边界与工程约束
|
||
|
||
架构边界测试([`tests/arch/boundaries.test.ts`](../tests/arch/boundaries.test.ts))强制执行以下四条核心隔离规则:
|
||
|
||
1. **Rule 7 — 核心客户端零插件耦合**:
|
||
- 除客户端组合根 [`src/client/main.ts`](../src/client/main.ts) 导入静态插件注册表 [`plugins/index.ts`](./index.ts) 外,**`src/client/**` 下的任何文件严禁导入 `plugins/**`**。
|
||
2. **Rule 8 — 服务端与烘焙管线零插件耦合**:
|
||
- `src/common/**`、`src/netproto/**`、`src/server/**` 与 `src/baker/**` 严禁导入 `plugins/**`。
|
||
3. **Rule 9 — 插件仅限访问公共只读宿主契约**:
|
||
- `plugins/**` 仅允许导入:
|
||
- `src/common/**`(只读数据表 `D2DataRegistry`、二进制解析器等)
|
||
- `src/netproto/**`(只读协议事件与常量定义)
|
||
- `src/client/plugin-host/api.ts`(唯一允许导入的 `src/client` 模块)
|
||
- 同一插件目录 `plugins/<self>/**` 内部模块
|
||
- **严禁直接导入 `src/client/world/**`、`src/client/session/**`、`src/client/ui/**`、`src/client/render/**` 等客户端内部实现**。
|
||
4. **Rule 10 — 插件间严格物理隔离**:
|
||
- 每个插件必须自包含在 `plugins/<plugin-id>/` 子目录中,**严禁跨插件导入(`plugins/<a>/**` 不得导入 `plugins/<b>/**`)**。
|
||
- 所有插件均运行在纯浏览器环境(`tsconfig.client.json`),严禁使用任何 Node.js 内置模块。
|
||
|
||
---
|
||
|
||
## 2. 目录结构
|
||
|
||
```text
|
||
plugins/
|
||
├── README.md # 插件系统架构说明与开发指南
|
||
├── index.ts # BUILTIN_PLUGINS 静态注册表(唯一向 src/client/main.ts 暴露的入口)
|
||
└── kill-stats/ # [Built-in #714] 怪物击杀统计插件 (Kill Statistics)
|
||
├── index.ts # killStatsPlugin 描述符、生命周期挂载与工具栏动作
|
||
├── index.html # 独立统计看板页面入口 (/plugins/kill-stats/index.html)
|
||
├── page-main.ts # 独立页面启动引导 (基于 bootPluginPage 统一处理语言与启停状态)
|
||
├── stats-page.ts # 独立统计页控制器 (IndexedDB 实时监听、BroadcastChannel 同步、导入/导出)
|
||
├── kill-tracker.ts # 实时击杀追踪器 (监听 unitDeath S2C 事件并批量落盘 IndexedDB)
|
||
├── kill-stats-model.ts # G-counter CRDT 统计模型与 JSON 序列化/合并校验
|
||
├── kill-stats-store.ts # IndexedDB (d2_kill_stats_v1) 存储实现
|
||
├── kill-stats-view.ts # 独立统计页双语 DOM 渲染器
|
||
├── monster-kill-classes.ts# 基于 MonStats.txt (hcIdx / killable / align) 的可击杀怪物分类器
|
||
└── stats-i18n.ts # 插件专属中英双语词典
|
||
```
|
||
|
||
---
|
||
|
||
## 3. 插件接口契约(`src/client/plugin-host/api.ts`)
|
||
|
||
每个插件通过实现 `D2WebPlugin<TInstance>` 接口并注册到 `plugins/index.ts` 的 `BUILTIN_PLUGINS` 数组中接入宿主:
|
||
|
||
```ts
|
||
export interface D2WebPlugin<TInstance = unknown> {
|
||
readonly descriptor: PluginDescriptor;
|
||
create(ctx: PluginHostContext): PluginLifecycle<TInstance>;
|
||
}
|
||
```
|
||
|
||
- **`PluginDescriptor`**:
|
||
- `id`: 全局唯一 kebab-case 插件标识(如 `'kill-stats'`),必须与目录名 `plugins/<id>/` 一致。
|
||
- `version`: 语义化版本号(如 `'1.0.0'`)。
|
||
- `name` / `description`: `{ zh: string; en: string }` 双语名称与简介(在工具栏 **Plugins** 模态框中展示)。
|
||
- `defaultEnabled`: 默认是否启用。
|
||
- `capabilities`: 声明插件能力标签(`'event-subscriber' | 'standalone-page' | 'toolbar-shortcut' | 'storage-indexeddb'`)。
|
||
- `standalonePagePath?`: 若包含独立页面,声明相对路径 `'plugins/<id>/index.html'`(Vite 构建会自动扫描 `plugins/*/index.html` 并打包为多页入口 `plugin-<id>`)。
|
||
- `toolbarAction?`: 工具栏快捷按钮与插件配置弹窗中的“打开页面”动作元数据。
|
||
- **`PluginHostContext`**:
|
||
- `getLang()` / `setLang(lang)`: 读取或切换全局双语设置(`'zh' | 'en'`)。
|
||
- `getDataRegistry()`: 获取已加载的 `D2DataRegistry`(若尚未加载完成则返回 `null`)。
|
||
- `getSessionView()`: 获取只读会话视图 `PluginSessionView`(当前战网网关、账号名、角色名、角色职业/等级与存活怪物快照 `getMonsterSnapshot(unitId)`)。
|
||
- `onServerEvent(listener)`: 订阅 D2GS `ServerEvent` 流。宿主会自动为每个事件调用包裹 `try/catch` 故障隔离:单个插件抛错不会中断游戏主循环或其他插件。
|
||
- **`bootPluginPage(options)`**:
|
||
- 独立插件页面(`plugins/<id>/page-main.ts`)专用的标准引导辅助函数。自动读取 `PluginConfigStore` 的启用状态与全局语言,并监听跨标签页 `window.addEventListener('storage', ...)` 事件,实时触发 `onEnabledChange` 与 `onLangChange`。
|
||
|
||
---
|
||
|
||
## 4. 如何开发与接入新插件
|
||
|
||
1. **创建插件目录**:在 `plugins/<plugin-id>/` 下创建 `index.ts`(若需要独立 HTML 页面,创建 `plugins/<plugin-id>/index.html` 与 `page-main.ts`)。
|
||
2. **遵循只读与快速失败原则(Fail-Fast)**:
|
||
- 严禁伪造或硬编码游戏规则数据;凡涉及怪物、物品、技能或关卡元数据,必须从 `ctx.getDataRegistry()` 的 1.13c 权威数据表中读取,缺失必需参数时立即抛出明确错误。
|
||
3. **注册到 `plugins/index.ts`**:
|
||
- 将插件实例追加到 `BUILTIN_PLUGINS` 只读数组中。
|
||
- `vite.config.ts` 会自动扫描 `plugins/*/index.html`,无需手工修改 Rollup `input` 列表。
|
||
4. **编写单元测试与边界校验**:
|
||
- 在 `tests/plugins/<plugin-id>/` 下编写单元测试。
|
||
- 运行 `npm run typecheck` 与 `npx vitest run` 验证类型安全及 `tests/arch/boundaries.test.ts` Rules 1–10 零违规。
|
||
|
||
---
|
||
|
||
## 5. 内置与规划插件路线图(Gitea Issues)
|
||
|
||
| 插件目录 / 标识 | 状态 | Gitea Issue | 功能简述 |
|
||
| :--- | :---: | :---: | :--- |
|
||
| [`plugins/kill-stats/`](./kill-stats) | ✅ 已内置 | `#714` | **怪物击杀统计 (Monster Kill Statistics)**:按战网网关/账号/角色隔离追踪全部可击杀怪物(普通/精英/暗金/首领),基于 IndexedDB G-counter CRDT 支持多标签页实时同步与 JSON 导入合并。 |
|
||
| `plugins/loot-filter/` | 📋 规划中 | `#715` | **掉落物品过滤与高价值掉落提醒 (Loot Filter & Drop Notifier)**:订阅地面物品掉落事件,支持自定义过滤规则、底材孔数/无形高亮与高号符文/暗金掉落提示音。 |
|
||
| `plugins/runeword-calc/` | 📋 规划中 | `#716` | **符文之语与赫拉迪克方块配方查询器 (Runeword & Cube Recipe Calculator)**:基于 `Runes.txt` 与 `CubeMain.txt` 结合当前角色背包/储物箱符文快照,实时计算可合成的符文之语与升级打孔配方。 |
|
||
| `plugins/area-guide/` | 📋 规划中 | `#717` | **85 级场景 (TC85) 与怪物抗性/免疫速查面板 (Area Level & Monster Immunity Guide)**:根据当前角色所在关卡 `areaId` 与难度,实时展示来自 `Levels.txt` / `MonStats.txt` 的场景等级、四系免疫分布与 Boss 掉落参考。 |
|