深深与额外动作拓展(Coopanion 扩展)
cortico-world-extra-actions · MIT · 作者:Daniele贤狼赫萝 & 奶糖_Net(丹尼尔 / Net-ang)
发布流程(打 tag、npm publish)写在仓库根目录的 PUBLISH.md 里,不随包分发。
把《深深 · 动作库》里的 105 段动作做成一张勾选表:勾中的动作写进形象包的
额外动作拓展/ 文件夹,桌宠照着做 —— 待机会轮播、点她会触发、重启后 bot 也能点名。
素材全是 640×360 高清:动作库一段一个 anim.webp(整段动图,加载快、不逐帧换图),
插件自带的示例形象包(4 段身体状态 + 3 段示例动作)也是同一套高清,新装的人第一眼就不糊。
- 插件形态:Cortico World(
kind: world,api: 6),面板在 Coopanion 的扩展页里;显示名 深深与额外动作拓展 - 数据落点:
<形象包>/额外动作拓展/(桌宠读的东西)+worlds.extra-actions.libraryDir(走WorldContext.persist存进 config.json) - 不改播放器、不改形象包的其它文件;
figure.json只增删它自己管过的那几个词,第一次动之前留备份
零、协议与安全(先看这段)
这个包是 Cortico World,不是往主程序里塞代码。契约按安装目录里的源码
(resources/app/build/cortico/src/)核对过:
| 契约 | 要求 | 本包 |
|---|---|---|
package.json → cortico.kind | world / provider / bot | world ✓ |
cortico.api | 必须正好是 EXTENSION_API_VERSIONS.world = 6 | 6 ✓ |
cortico.webUiClient | 包内相对路径、不含 \、不以 / 开头、无 ..、.js/.mjs | dist/webui.js ✓ |
keywords | 要有 cortico-world | ✓ |
| 默认导出 | 有 id / label / defaults() / create() | ✓(isWorldDefinition 就查这四项) |
| 实例 | start(host) / stop() / envPromptVars() / tools() | ✓(tools() 返回空数组:不给 bot 任何可调用的写入口) |
| 面板 | webUi(language) 返回 { label, panels, invoke },客户端 export default { panels: { picker } } | ✓ |
| 配置 | WorldContext.cfg 是活引用,改动走 ctx.persist(patch) | 动作库路径就是这么存的 |
| 目录 | WorldContext.botDir 是部署目录,桌宠数据在它下面 data/figures | 优先用它定位,不再瞎猜 |
没有引入 cortico/* 任何模块,所以不走宿主的 tsx 解析链,纯 ESM 直接被 import() 加载;
整个包里没有 eval / new Function / 子进程 / 本地 HTTP 服务。
注入面(写入是唯一碰磁盘的地方,src/writer.js 全部收紧):
- 只写
<pack>/额外动作拓展/,写之前校验路径确实在那个包里(isInside,挡..、盘符、越界); - 拷帧只认清单里那 frames 张
f_NNNN.webp,符号链接、非常规文件、>4 MB 的帧、>64 MB 的模块一律拒绝; 不做整目录递归拷贝 —— 动作库里多塞一个payload.js也不会进包; action.json不复制,按校验过的字段重写;- 生成的
额外动作拓展/index.js是数据:所有字符串走JSON.stringify,id/分类/路径先过^[a-z0-9][a-z0-9-]{0,31}$白名单,名字去掉控制字符与行分隔符; figure.json只动vocab一个字段;- 面板侧对拿到的清单同样再筛一遍(id/分类/帧数/时长/缩略图前缀),不信任 invoker 传来的任何东西;
invoke的参数当不可信输入:包 id 必须匹配 slug 正则,一次最多 300 个动作。
一、装
方式 A:npm 装(推荐)
cd "<Coo 安装目录>\data\extensions"
npm install cortico-world-extra-actions
Coopanion 启动时会自己扫 data\extensions\node_modules,装完重启 Coopanion 就行,
不需要额外脚本(install-extension.mjs 只服务于下面这种方式)。
包里 dependencies 只有 cortico-world-extra-actions 这一条,不会动别的扩展。
方式 B:拿 zip / 源码目录(离线、要改代码时用)
node tools/install-extension.mjs --extensions "<Coo 安装目录>\data\extensions"
:: 或先设环境变量 COOPANION_EXTENSIONS
它会:把本目录拷到 <extensions>\vendor\cortico-world-extra-actions\,
在 data\extensions\package.json 里加一条 link: 依赖(先备份 package.json.bak-extra-actions),
再建一个 node_modules 联接。然后重启 Coopanion。
手动做同样三步:
- 把整个文件夹拷到
...\data\extensions\vendor\cortico-world-extra-actions\ data\extensions\package.json的dependencies里加一行:"cortico-world-extra-actions": "link:vendor/cortico-world-extra-actions"- 建联接(管理员不需要,junction 就行):
mklink /J "...\data\extensions\node_modules\cortico-world-extra-actions" "...\data\extensions\vendor\cortico-world-extra-actions"
重启后,Coopanion 的扩展页里会出现「额外动作拓展」。
tools/只随 npm 包和 zip 一起发,仓库里也有;node tools/extra-actions-cli.mjs那套命令行 两种装法都能用。
二、用
面板里:
- 上方选一个可写的角色包(只有会读
额外动作拓展/的播放器才列出来 —— 目前是 《深深 · 动作库》shenshen-actions,以及打了对应补丁的深深)。 - 左下的分类芯片 + 搜索框筛动作;点一行就勾上/取消。
- 右下「保存随机列表」。
列表永远是完整的一套固定目录(105 条),跟库里有没有素材无关:
- 库里有的那几条正常显示、可以勾
- 库里还没有的:缩略图发灰、标签写「库里没有 · 缺帧」、复选框点不动
- 每一条鼠标悬停都会显示它的固定目录(
actions/<分类>/<id>),方便你照着往库里补 - 底部汇总:
清单 105 条:库里 N · 缺 M
四个库/包相关的按钮:
| 按钮 | 作用 |
|---|---|
| 选择动作库位置… | 手动指一个已有的动作库目录(路径会被记住) |
| 用自带示例建库… | 在你会选的目录下建 <你选的目录>\深深-动作库(3 段示例)并设为当前库 |
| 自动寻找动作库… | 把本机像动作库的目录扫出来列成候选,点一行就切过去 |
| 建立空的角色包(选位置) | 建一个只有 4 段身体状态、词表空、没有额外动作的空角色包,放哪由你选 |
保存时它做的事:
- 勾中的模块:从动作库把整段动画(高清单文件
anim.webp,或帧序列f_0001.webp…)+preview.webp+action.json拷进<包>/额外动作拓展/<分类>/<id>/(已经拷过的不重复拷) - 取消勾选的:删掉对应目录
- 重写
额外动作拓展/index.js(桌宠 import 的那份清单)、index.json、README.md - 把
figure.json的动作词裁成你勾的这些:勾中的加进去,没勾的动作词撤掉(表情、非动作条目一个字不动)。 第一次改之前备份figure.json.bak-额外动作拓展,原始词表记在.managed.json里 - 不碰桌宠:插件只读桌宠的 /api/state、/api/figures。要让新动作立刻生效,去 Coopanion 的装扮页重新点一下这个形象
所以语义是 勾选即全部:勾了什么,桌宠就只做这些 —— 待机轮播、点击回应、bot 能说的动作词, 都收在这次勾选里。没勾的动作不会自己冒出来。
名词生效时机:figure.json 是 Coopanion 启动时扫的,所以 bot 要重启后才会说
【吃饺子】;在那之前动作已经在待机轮播和点击里了。
命令行(不用面板也能干同样的事):
node tools\extra-actions-cli.mjs list
node tools\extra-actions-cli.mjs state
node tools\extra-actions-cli.mjs save --pack shenshen-actions --ids eat-dumplings,fireworks,cheer-wave
node tools\extra-actions-cli.mjs clear --pack shenshen-actions
三、额外动作拓展/ 长什么样
<形象包>/额外动作拓展/
├─ index.js ← 桌宠读的就是这个
├─ index.json 同一份内容,给工具和人看
├─ README.md 这一份是谁写的、什么时候、勾了什么
├─ .managed.json 插件管过哪些动作、加过哪些词(取消勾选和恢复时用)
└─ eat/eat-dumplings/
├─ anim.webp 640×360,24 fps,整段一个文件(推荐,高清)
│ (或者 f_0001.webp … f_0241.webp 帧序列,老格式同样认)
├─ preview.webp
└─ action.json
index.js:
export default [
{ word: "eat-dumplings", clip: "额外动作拓展/eat/eat-dumplings", frames: 241, fps: 24,
seconds: 10.042, name: "吃饺子", category: "eat", anim: true,
noMirror: false, reaction: false, random: true },
];
anim: true 告诉播放器「这个目录是一个整段动图」,它就直接挂一个 <image> 让浏览器自己循环,
不再逐帧换 src。没有这个字段也认:播放器会先探一下 anim.webp 在不在。
播放器把这一份并到 actions/index.js 后面(同名词以这份为准),word 不为空的登记进词表。
所以只要播放器认这个格式,换一个形象包也能直接复用这个文件夹。
四、限制与边界
- 只支持帧序列播放器:入口
figure.js里得出现额外动作拓展或actions/index.js(单文件anim.webp和帧序列两种模块都认)。 《深深 · 动作库》和本仓库配套的那个播放器可以;早先只认ext/clips.js的补丁、以及 horo / clawd 这些别的角色都不行(面板里不会列出来)。 - 帧画的是深深:这是整段画好的 640×360 立绘,不是分层部件。加进别的角色会穿帮, 也没法叠在别人的立绘上 —— 面板会提示。
- 状态类动作不勾:
state-idle / state-walk / state-drag / look-around是身体的待机、走路、 被拎起和转向,勾了没用(它们本来就在包里)。 - 只写可写的包:Coo / 鲸鱼这些内置包不会被列出来。
- 可逆:
figure.json只动插件自己加过的词;一键「清空随机动作列表」会删掉整个额外动作拓展/并把词撤干净。改动前的原文在figure.json.bak-额外动作拓展。
动作库放哪(固定,不用另外下载)
动作库就是一个固定的文件夹,结构固定:
<动作库>/
└─ actions/
├─ index.js 清单(可由 action.json 重建)
└─ <分类>/<id>/ 分类与 id 都在白名单内
├─ action.json
├─ f_0001.webp …
└─ preview.webp(可选)
- 首次启用时插件把自带的示例库铺到
<dataDir>\shenshen-library,路径记在config.json的worlds.extra-actions.libraryDir,面板上直接显示 - 想换位置或换一个库:点「自动寻找动作库…」让它自己扫出来,或用「选择动作库…」手动指
- 不需要去任何地方下载动作库;动作不够就点「添加更多动作…」自己收进来
五、添加更多动作
面板最下面有一个折叠的 「添加更多动作…」:选一个文件夹,里面是一整套
f_0001.webp、f_0002.webp…(同尺寸),或者一个装着若干这种文件夹的父目录。
它会:
- 把帧收进
<动作库>/actions/<分类>/<id>/(默认分类extra,同名 id 自动加-2); - 按「帧数 ÷ fps」算时长,写一个
action.json; - 往
actions/index.js追加一行 —— 桌宠的播放器就认它。
收进来之后,这些动作和自带的 105 段一样出现在上面的列表里,可以勾选、保存随机列表。
可选在同目录放 action.json 指定 {"id":"eat-burger","zh":"吃汉堡","category":"extra","fps":24};
不给就按目录名生成、24 fps。
安全上和写入层同一套:只拷贝 f_NNNN.webp(源目录里别的东西不会进库)、拒绝符号链接、
单帧 ≤4 MB / 单模块 ≤128 MB、id 与分类过白名单、index.js 里所有字符串走 JSON.stringify。
命令行同样能加:node tools\extra-actions-cli.mjs add --dir <文件夹>。
六、制作
面板最下面还有一个折叠的 「制作」,放着两位制作人的头像与分工(头像以 data URL 嵌在面板里, 不读本地文件、不发网络请求,默认折叠,不影响日常勾选):
- Daniele贤狼赫萝(丹尼尔) —— 形象包机制与插件制作 ·
[email protected] - 奶糖_Net(Net-ang) —— 制作 · 素材与实现 ·
[email protected]
形象包的 figure.json 里也记了这两条 credits。
七、文件
| 路径 | 作用 |
|---|---|
src/index.js | World 定义(id / label / defaults / create / describe) |
src/world.js | 面板方法、形象包发现、保存/清空、扫库清单、参数校验 |
src/writer.js | 纯落盘逻辑 + 注入面防护(面板和命令行共用) |
src/importer.js | 「添加更多动作」:校验一套帧、收进动作库、重建清单 |
src/starter.js | 自带素材落地 + 「建立空的角色包」 |
src/pet.js | 本地桌宠服务的只读客户端(/api/state、/api/figures) |
dist/webui.js | 面板(无 import 的纯 ESM,export default { panels: { picker } }) |
assets/catalog.json | 105 段动作的清单 + 缩略图 |
assets/starter/ | 自带的形象包与示例动作库(首次启动落地) |
assets/icon.webp | 扩展封面(cortico.icon) |
tools/install-extension.mjs | 装到 Coopanion(拷包 + link 依赖 + junction),需要 --extensions |
tools/extra-actions-cli.mjs | 命令行:list / state / save / remove / clear / add / prune / reindex |
tools/check-registered.mjs | 自检:宿主能不能按 name 解析到这个包、config 里有没有它 |
tools/verify_installed.mjs | 对真机:装好的包是不是只做勾选的那些 |
tools/test_writer.mjs | 保存 / 删除 / 清空 / 瘦身的落盘语义 |
tools/test_security.mjs | 注入面:路径、白名单、符号链接 |
tools/test_importer.mjs | 「添加更多动作」 |
tools/test_catalog.mjs | 清单以磁盘为准、index 可重建 |
tools/test_panel_guard.mjs | 面板模板缺节点会当场报错 |
tools/test_starter.mjs | 自带素材的首次落地 |
tools/test_restore.mjs | 「建立空的角色包」与查重 |