主题
ctx 全量 API
生命周期钩子都会收到同一个 ctx 对象。下表是它暴露的全部成员。
生命周期
| 导出函数 | 时机 | 应当做什么 |
|---|---|---|
onLoad(ctx) | 代码载入后 | 恢复设置、初始化数据结构。不要在此注册事件 |
onEnable(ctx) | 被启用时 | 注册事件 / 命令 / 页面,保存返回的取消函数 |
onDisable(ctx) | 被禁用时 | 取消上一阶段注册的一切,停止定时器 |
onUnload(ctx) | 卸载时 | 释放资源(清理函数也会由 ctx.dispose() 兜底执行) |
js
let offEnter;
export function onLoad(ctx) {
ctx.logger.log('loaded');
}
export function onEnable(ctx) {
offEnter = ctx.events.on('game:enterBattle', () => ctx.logger.log('进入战斗'));
ctx.onCleanup(() => ctx.logger.log('清理中'));
}
export function onDisable(ctx) {
offEnter?.();
}基础成员
| 成员 | 类型 | 说明 |
|---|---|---|
ctx.mod | object | 当前 Mod 的 manifest |
ctx.loader | ModLoader | 加载器实例(列表、安装、启用等) |
ctx.logger | ModLogger | 日志,前缀 [Mod:<id>] |
ctx.findorCheat | object | 作弊端根对象(等价于页面里的 findorCheat) |
ctx.game | object | findorCheat.game 快捷引用 |
ctx.TOState | object | findorCheat.TOState 快捷引用 |
ctx.features | object | 你自己的功能容器,同时挂到 findorCheat.modFeatures[<id>] |
ctx.onCleanup(fn) | function | 注册清理函数(ctx.dispose() 时按逆序执行) |
ctx.logger
js
ctx.logger.log('普通日志');
ctx.logger.info('信息');
ctx.logger.warn('警告');
ctx.logger.error('错误', someError);
ctx.logger.debug('调试'); // 默认不输出,需 ctx.logger.debugEnabled = truectx.storage —— 本地存储
按 Mod 隔离(底层 key 为 findor-mod-storage:<id>,值为 JSON)。
js
ctx.storage.set('settings', { enabled: true, delay: 2000 });
const s = ctx.storage.get('settings', {}); // 第二个参数是默认值
ctx.storage.delete('settings');
ctx.storage.keys(); // ['settings']
ctx.storage.clear();ctx.events —— 事件
js
const off = ctx.events.on('game:enterBattle', (payload) => { /* ... */ });
ctx.events.once('announcement:ready', () => { /* 只触发一次 */ });
off(); // 取消订阅
ctx.events.off('game:enterBattle', handler);on/once都返回取消订阅函数- 全局事件由
cheatEventBus桥接而来,完整清单见事件清单 ctx.events.emit(name, ...args)只触发本 Mod 自己的监听;要全局广播请用findorCheat.cheatEventBus.emit(name, ...args)
ctx.commands —— 聊天命令
js
const off = ctx.commands.register('delay', (send, args) => {
const ms = parseInt(args[0], 10);
if (isNaN(ms)) return send('用法: /my-mod:delay <毫秒>');
send(`延迟已设为 ${ms}ms`);
}, { args: 1, usage: '/my-mod:delay <毫秒>' });注册后的完整命令名是 <modId>:<name>,即 /my-mod:delay 100。
| options | 说明 |
|---|---|
args | 'infinite'(默认,任意个数)· 0(不能带参数)· n>0(必须正好 n 个)· n<0(至少 |n| 个) |
usage | 用法提示,默认 /<modId>:<name> |
validate | 自定义校验函数 (providedArgs) => ({ valid, expected, got }),提供后覆盖 args 的默认校验 |
处理器签名:(send, args) => void,其中 send(text) 把消息回显到聊天。
ctx.ui —— 界面
js
// 通知(会加上 [modId] 前缀)
ctx.ui.notify('已完成', 'success'); // type: 'info' | 'success' | 'error'(默认 info)
// 注册一个自定义页面(config 由 Mods UI 消费),返回取消函数
const remove = ctx.ui.addPage({ id: 'my-page', title: '我的页面', render: () => document.createElement('div') });
remove();
// 对话框
ctx.ui.dialog({ title: '提示', content: '确认继续?' });| 方法 | 返回 | 说明 |
|---|---|---|
addPage(config) | 取消函数 | 注册页面并刷新 UI |
removePage(id) | — | 移除页面 |
notify(message, type) | — | 弹通知 |
dialog(options) | — | 弹对话框(title / content 等) |
ctx.models —— 模型访问
js
ctx.models.get('TankModel'); // Model 实例
ctx.models.getObject('TankModel'); // 对应的 gameObject(可能为 null)
ctx.models.listMethods('TankModel'); // 原型上的方法名列表
ctx.models.call('TankModel', 'setState', arg); // 在正确上下文里调用 Model 方法
ctx.models.withContext('TankModel', () => {
// 需要当前 gameObject 上下文的操作放这里
});与 putGameObject 相关
models.call / models.withContext 内部会切换到目标 gameObject 上下文;如果需要返回值的操作,请用 withContext 并在回调里 return。
ctx.protocol —— 协议
js
// 包钩子
const off1 = ctx.protocol.onSerialize('MethodName', (data) => { /* 发送前 */ });
const off2 = ctx.protocol.onDeserialize('MethodName', (result) => { /* 收到后 */ });
// 上行:客户端 → 服务端
ctx.protocol.send(tankModelInstance, 'MethodName', [param1, param2]);
// 下行:模拟服务端 → 客户端(触发本地 Model 处理)
ctx.protocol.invoke(tankModelInstance, 'MethodName', (buffer) => {
// 用 buffer 写入编码后的参数
});
ctx.protocol.listMethods(); // 所有可用方法名
ctx.protocol.describe('MethodName');
ctx.protocol.resolveId('MethodName');ctx.actions —— 拦截游戏 Action
js
const off = ctx.actions.on('SomeAction', (action, { name, edit }) => {
// 返回 false → 阻止该 Action
if (name === 'BlockMe') return false;
// 返回 { modify:true, target } → 改写目标对象(如把目标换成别人)
return { modify: true, target: otherTarget };
});
ctx.actions.onAny((name, action) => { /* 观察所有 Action */ });
off();| 方法 | 说明 |
|---|---|
on(name, handler) | 监听指定 Action |
once(name, handler) | 只监听一次 |
off(name, handler) / offAny(handler) | 取消 |
onAny(handler) | 监听全部 Action,签名 (name, action, { edit }) |
Action 名来自游戏类的 $metadata$.simpleName(Findor 已填充)。
销毁
ctx.dispose() 会:执行全部 onCleanup 回调(逆序)→ 清空事件监听 → 清理协议钩子、UI 页面与命令。卸载时由加载器自动调用;你也可以在 onUnload 里手动调用以确保幂等。
下一步:事件清单 · 示例 hello-world