dnf/v1
开始使用 / dnf/v1

理解插件运行方式

理解通知、规则、权限、Lua 5.1 环境和热重载中的状态。

先认识运行位置

本文档对应 Go 服务端的 dnf/v1 插件宿主,源码核对基线为 cda08ef(2026-10-26)。它与客户端 90CNLua.dll 是两套独立系统;客户端窗口、按键或 DLL API 不能直接搬到这里使用。

一个插件由 plugin.json 和 main.lua 组成,框架文件负责声明哪些插件被加载、获得什么权限和配置。先完成快速上手,再查函数参考、事件目录或 Hook。

Lua 5.1:会写基本脚本即可开始

解释器为 gopher-lua v1.1.1,采用 Lua 5.1 语法。保存脚本为 UTF-8 文本。常用的局部变量、函数、表、if、for、pairs、ipairs、assert、string、table 和 math 均可用于日常插件逻辑。

lua
local dnf = require("dnf")
assert(dnf.api_version == "dnf/v1")

local function is_enabled(config)
    -- Lua 里只有 false 和 nil 为假;0 和空字符串都为真。
    return config.enabled == true
end

dnf.events.on("character.bound", function(ctx, event)
    local id = event.data.id -- 角色 ID 是字符串
    if is_enabled(ctx.config) and id ~= "" then
        -- 在这里处理当前事件,具体可用动作见 API 页。
    end
end)

需要特别注意:

  • 没有 Lua 5.3 的整数类型、// 整除和 &、|、<< 等位运算语法。ARGB 颜色可以直接写 0xFFFFD54F。
  • Lua 数值采用浮点表示。角色、拍卖单、运行等契约明确标为字符串的 ID 应始终按字符串比较,不能 tonumber(id) 后作为身份键。
  • 数组从 1 开始,事件中的 items、members 等数组用 ipairs 遍历;插件配置和返回补丁则使用字符串键表。
  • #text、string.sub 等常见字符串操作按字节处理,不能拿字节长度当作中文字符数。截断中文或表情前应理解 UTF-8 边界;公告长度的最终校验由宿主按 UTF-16 码元完成。
  • 事件中的可选字段可能为 nil。先判空再运算,不能把缺失值或 0 编造为真实业务对象。

可用库与宿主边界

宿主开放受限的 base、table、string、math。require 只接受 "dnf",没有通用模块加载器;需要的辅助函数放在同一个 main.lua 中。

能力 当前行为
文件、网络、数据库、系统命令 没有 io、os、socket、FFI,也没有业务仓储句柄
包系统和反射 没有 package、debug、coroutine
动态加载与环境修改 移除 dofile、loadfile、load、loadstring、getfenv、setfenv、newproxy
输出和垃圾回收控制 移除 print、collectgarbage
日志、计时器、直接发物、修改数据库 当前没有对应插件 API;不能调用 dnf.log、setTimeout 或假想的发奖函数
错误诊断 宿主记录带插件 ID、阶段和错误次数的错误,管理端提供状态计数

受限库和时间预算用于控制日常插件影响,不能当作运行恶意脚本的强隔离环境。安装前应审阅脚本,只运行可信插件。

四个阶段各做什么

阶段 应做的事 可使用的接口
框架初始化 选择插件、授予权限、填写配置、选择通关播报策略 dnf.api_version、dnf.plugins
插件初始化 校验配置、声明事件与 Hook 回调 dnf.config、events、hooks、providers;此时不能发公告
通知回调 读取已发生事件的独立快照,按需暂存公告 ctx.config、ctx.notice.broadcast,或等价的 dnf.notice.broadcast
规则回调 读取输入,返回允许字段组成的计划补丁 ctx.config;此时不能调用广播动作

注册操作只能在 main.lua 顶层初始化时完成。不要在通知或 Hook 内再次 events.on、hooks.before、providers.register 或 hooks.transform。

通知与 Hook 的区别

通知是“发生了什么”的异步观察。核心发布时不执行 Lua,插件稍后消费快照。事件可能因队列满、停机、版本切换而丢失,没有持久化、消费确认和自动补发。即使名字带 committed,也只说明该事件描述的事务已经提交,不能推断后续回包或独立奖励事务完成。

Hook 是核心在明确扩展点请求一个规则结果。它有总时间预算、返回值校验和对应业务的失败语义。四项业务限制 Hook 只增加限制,资产变更、归属检查和原生回包仍由服务端核心处理;失败时保留原核心规则。经验和播报的失败语义不同,见 6 项 Hook 详解。

不要用异步通知计数实现“每日限购”“只发一次奖励”这样的持久业务保证。当前 API 没有持久存储和可靠发奖接口。内存计数适合非关键的展示节流,且必须接受重载或重启归零、事件丢失和重复请求。

一个插件可能有两个 VM

每个插件先初始化通知 VM;含 Hook 的插件还会用相同 main.lua 和配置单独初始化规则 VM。两个 VM 的全局变量、闭包、配置表互不共享,也可能并行执行。

lua
local dnf = require("dnf")
local observed = 0

dnf.events.on("character.bound", function(ctx, event)
    observed = observed + 1 -- 只改变通知 VM
end)

-- 如果此文件还声明 Hook,该 Hook 看到的 observed 属于规则 VM,
-- 不会得到上面通知回调累加的值。

同一 VM 内回调串行执行;不同插件的通知互不等待。同一插件中,dnf.config 与 ctx.config 指向该 VM 的同一配置表。修改这张表只影响当前 VM,不写回框架,也不传到另一个 VM。

因此注册应保持确定性:不要依赖随机分支决定是否注册回调,不要让规则依赖通知 VM 更新的变量。配置变更应修改框架,再让整个插件集合成功重载。

资源预算

以下是当前实现的固定限制,设计插件时预留余量。

资源 当前上限
框架声明 / 启用插件 128 次声明 / 16 个启用插件
框架文件 / 每个 main.lua 各 256 KiB,必须是普通文件
每个 plugin.json / 插件配置 JSON 各 16 KiB
Lua 输出值 单字符串 4096 字节、每表 128 项、嵌套深度 16;字符串键、有限数字、布尔、字符串、表、nil
框架初始化 / 单插件每个 VM 初始化 各 100ms 上下文预算
通知回调注册 每插件最多 64 个
通知队列与预算 每插件队列 64;同一插件同一事件的全部匹配回调共享 10ms、最多 4 条公告
规则预算 整条链 20ms;每个阶段回调最多 10ms;每插件规则等待队列 1
公告动作 每插件队列 64,20 次/秒,突发 8 次;通关播报协调队列 64
事件 data 最多 64 KiB
退休版本 最多一个;未退出前拒绝下一次重载

Lua 输出限制用于配置序列化、返回补丁和公告等 Lua 向宿主交付的数据;不能因此假设收到的事件表只有 128 项。输入数组是宿主快照,输出补丁不接受数字键数组。

时间预算依赖上下文协作,不能保证立即中断原生库的长时间计算或提供内存硬隔离。回调应只做有界遍历、简单判断和格式化。四项新增业务限制 Hook 在共享业务锁和写事务外等待;既有经验 Hook 仍在副本结算锁内,详见 Hook 语义。

重载意味着重新开始

成功重载会重建完整插件集合的所有 VM,内存变量与计数归零,不调用卸载函数,不补发 server.ready 或历史事件。只想验证加载成功,应查 generation 与插件清单,再触发一个新的可观察事件。

热重载失败会保留当前版本。实际运维步骤、管理接口和故障定位见部署与排错。

DNF90 · 服务端 Lua 插件核对版本 cda08ef · 2026-10-26

输入关键词,查找指南和全部事件。

点击结果跳转 · Esc 关闭