# DNF90 dnf/v1 可运行插件示例

这些文件用于 **Go 服务端** `dnf/v1`（Lua 5.1），不能放进客户端 `90CNLua.dll` 目录。需使用包含六项 Hook 的配套服务端。插件是进程内可信代码，先在独立测试实例验证。

## 安装全部示例

1. 将本包中的 `framework.lua` 和七个插件目录复制到发行包顶层 `runtime/plugins/`。保留每个插件目录中的 `plugin.json` 与 `main.lua`。`README.md` 无需复制，`welcome-framework.lua` 是仅安装欢迎插件的可选框架。不要把外层 `examples/` 目录一同嵌套进去。
2. 若已有自定义框架，先备份，再将所需 `dnf.plugins.load(...)` 合并进去；不要直接覆盖已有插件声明。各插件清单 ID 必须唯一，同一扩展点只能有一个 provider。
3. 将下列 `plugins` 字段合并到已有 `runtime/config/instance.json`，保留其他字段。不要修改 `runtime/configs/` 生成文件。若曾使用旧 `entries`，先将其迁移到框架；不要同时配置 `framework` 与非空 `entries`。

```json
{
  "plugins": {
    "enabled": true,
    "framework": "plugins/framework.lua"
  }
}
```

4. 通过实例原有控制入口完整重启。总开关和框架路径改变需要重启。随后框架、已启用脚本、清单修改可热重载。
5. 在本机管理接口 `GET /local/plugins` 确认 `enabled/running` 为 `true`，并列出七个 `docs-*` 插件。使用实际服务端管理地址、`platform` 管理权限和 `X-Admin-Token`，勿使用游戏端口或把凭据写进文件。
6. 重新选择角色验证欢迎公告；完成新的普通副本验证通关播报。重载不会补发过去事件。

目录应为：

```text
runtime/
  config/instance.json
  plugins/
    framework.lua
    welcome/plugin.json
    welcome/main.lua
    clear-announcement/plugin.json
    clear-announcement/main.lua
    clear-experience/plugin.json
    clear-experience/main.lua
    item-policy/plugin.json
    item-policy/main.lua
    shop-policy/plugin.json
    shop-policy/main.lua
    admission-policy/plugin.json
    admission-policy/main.lua
    chat-policy/plugin.json
    chat-policy/main.lua
```

## 默认行为与改法

| 目录 / 清单 ID | 默认行为 | 在框架中调整 |
| --- | --- | --- |
| welcome / docs-welcome | 角色绑定时向当前频道发欢迎公告；含重连 | `suffix`（UTF-8 最多 128 字节） |
| clear-announcement / docs-clear-announcement | 作为唯一 provider 接管频道通关播报 | `scope="channel"` 或 `"instance"`、`color`（uint32 ARGB） |
| clear-experience / docs-clear-experience | 倍率 1，保留基础通关经验 | `multiplier`：1..10，可为小数，向下取整后钳制 uint32 |
| item-policy / docs-item-policy | 空集合，不增加道具限制 | `blocked_item_ids` |
| shop-policy / docs-shop-policy | 空集合，不增加购买限制 | `blocked_item_ids`、`blocked_commodity_ids` |
| admission-policy / docs-admission-policy | 空集合，不增加入场限制 | `blocked_dungeon_ids` |
| chat-policy / docs-chat-policy | 空集合，不增加聊天限制 | `blocked_phrases` |

资源编号必须从本实例当前 PVF/商店定义核实。编号集合采用十进制**字符串键**与 boolean 值，例如 Lua 写法 `blocked_item_ids = { ["从PVF取得的真实编号"] = true }`。这里的中文是说明占位，必须替换为真实十进制编号，原样使用会被初始化校验拒绝。不要改成数组 `{123,456}`，也不要给配置表使用数字键。可直接保持空集合 `{}`，此时不增加限制。聊天集合可写成 `blocked_phrases = { ["测试屏蔽词"] = true }`，按区分大小写的原文子串匹配。

`item-policy` 仅覆盖合法真实源的 op27 UseStackable，不含 op160、op515 或任意礼包使用。`shop-policy` 仅 NPC/秘密/Cera 商店，不含拍卖/摆摊；Cera 按商品 `commodity_id`，不是最终发放物品编号。`admission-policy` 覆盖首次选图/训练直入，全队决策一次，不含 op72 再次挑战。`chat-policy` 在转发与喇叭消耗前拒绝，不修改正文。

后四项只增加限制，核心仍校验资源、余额、门禁、背包、归属并负责事务与原生回包。脚本出错、超时或繁忙时回退原核心规则，因此不能把示例当作绝对安全门禁或持久账本。

## 单独安装欢迎插件

只复制 `welcome/`，将 `welcome-framework.lua` 另存为 `runtime/plugins/framework.lua`，其完整内容如下：

```lua
local dnf = require("dnf")
dnf.plugins.clear_announcement("auto")
dnf.plugins.load("plugins/welcome", {
    enabled = true,
    permissions = { "notice.broadcast" },
    config = { suffix = "祝冒险愉快！" }
})
```

仍需合并实例配置并首次重启。目录路径、清单权限与框架授权必须保持一致。

## 热重载、检查与停用

保存框架/已启用脚本后通常在下个 2 秒检查周期尝试原子重载。版本字段不用递增；保留同样大小与修改时间的替换须手动重载。任何插件加载失败都保留整个旧集合，首次启动失败会阻止游戏监听。成功重载重置所有 Lua 内存状态与计数，不补发事件。

下列环境变量由操作者在命令会话提前设置，属于示例输入，不是服务端新增配置。不要将真实凭据提交或写进插件。

```powershell
$pluginAdminBase = $env:DNF90_ADMIN_URL
$pluginAdminHeaders = @{ 'X-Admin-Token' = $env:DNF90_ADMIN_TOKEN }
Invoke-RestMethod -Method Get -Uri "$pluginAdminBase/local/plugins" -Headers $pluginAdminHeaders
Invoke-RestMethod -Method Get -Uri "$pluginAdminBase/local/plugins/catalog" -Headers $pluginAdminHeaders
Invoke-RestMethod -Method Post -Uri "$pluginAdminBase/local/plugins/reload" -Headers $pluginAdminHeaders
```

手动重载成功返回 200，候选加载失败返回 409；检查返回文本与状态的 `last_reload_error`。成功时 `generation` 增加，计数从零开始。`accepted` 是通知入队，`actions` 是宿主动作调用成功，均不等于客户端送达。规则插件可以没有通知计数增长。

把某声明改为 `enabled=false` 并成功重载即可停用。`auto` 下停用本包唯一通关 provider 后恢复内置播报；若仍有其他 provider，按当前全集合重新决定。回滚应恢复上一份框架/源码并确认成功加载；过去公告和已固定的经验决策不会撤销。

## 开发边界

- 事件回调得到信封 `event.data`；通关播报 Hook 也是信封；其他五项 Hook 是裸上下文。
- `before` 返回 boolean；provider 返回字段补丁表；transform 返回补丁表或 nil。直接修改 `plan`、`event` 不生效。
- 同步规则不能调用公告；只有有 `notice.broadcast` 权限的通知回调可广播。不要在初始化广播。
- 通知与规则独立 VM，不能用通知中的变量给规则传状态。热重载不迁移内存状态。
- 没有 `dnf.log`、数据库/文件/网络/定时器/任意发道具/协议包 API。`require` 只允许 `dnf`。
- 通知尽力交付，不能用来可靠发奖。经验只改变普通副本基础通关值，首次计算固定，既有加成、等级/SP/TP 与事务继续由核心执行。

文件与网站对应 API 的核对日期：2026-10-26。应再通过当前宿主 `catalog` 核对版本能力。
