剧情 MOD 给原版的关卡加事件:第几回合、谁被击破、谁和谁交战、选了「说服」……的时候,播你写的台词,让人物加入、离场,或者改剧情变量让后面的原版剧情换一条路走。触发判断由游戏来做,你只写条件和要做的事,不用懂原版的事件表。原版的事件一个都不会被改动或挤掉。
包的安装和管理见 MOD 包:安装与制作。需要 0.5.3 或更新的版本。
完整的示例是「东方不败存活」,源文件在 config/recomp/mods/master-asia:独立军线「死斗!大屿山大决战(下)」和 OZ 线「绝望与悲伤之拳(下)」里,多蒙在战斗中和东方不败交谈过之后可以说服他,击坠数 50 以上时东方不败撤退,过关后加入。
包的结构
yourname.story/
mod.json
story/
<任意名>.json 剧情脚本,可以有几份
data/
pilots.json 需要时顺便改数值(见数值 MOD)
打包时(tools/mods/pack.py)story/ 里的剧情脚本会被编译成游戏读的格式,写错了会报出是哪个事件的哪一项。
写法
{
"schema": "srw64.story.v1",
"events": [
{
"id": "persuade",
"scene": 129,
"when": { "persuade": { "by": 4, "target": 18 } },
"if": { "vars": { "131": 0 }, "kills": { "4": 50 } },
"do": [
{ "say": 4, "text": { "ja": "「師匠!」", "zh-Hans": "“师父!”", "en": "“Master!”" } },
{ "say": 18, "mode": 2, "text": { "ja": "「……」", "zh-Hans": "“……”", "en": "“...”" } },
{ "leave": 18 },
{ "set": { "131": 3 } }
]
},
{
"id": "joins",
"scene": 129,
"when": { "after": "end" },
"if": { "fired": ["persuade"] },
"do": [ { "join": { "pilot": 18, "unit": 27 } } ]
}
]
}
每个事件:
| 字段 | 说明 |
|---|---|
id |
事件名,包里唯一,别的事件用它引用 |
scene |
哪一关,写场景号(0–255)。各关的场景号见场景号对照表 |
when |
什么时候触发,见下 |
if |
可选,还要满足什么 |
do |
做什么,按顺序执行 |
repeat |
可选,写 true 可以反复触发 |
set_flags |
可选,事件跑完时设上的标记,给后面的关用 |
note |
可选,随便写说明,游戏不读 |
每个事件每关默认只触发一次,游戏自己记,跟着中断存档走,不用像原版那样自己写变量防止重复播放。
人物号、机体号见数值 MOD 的人物表和机体表。
when:什么时候
| 写法 | 意思 |
|---|---|
{"turn": {"turn": 3, "side": "player"}} |
第 3 回合起、该阵营的回合开始(player、enemy、third) |
{"defeated": 18} |
某人物的单位被击破(离场不算) |
{"hp_below": {"who": 18, "percent": 30}} |
某人物的 HP 降到 30% 以下 |
{"battle": {"between": [4, 18], "moment": "after"}} |
两人交战之后(before 是交战之前;人物号写 0 表示任何人) |
{"enemies_gone": true} |
敌方全灭;写 {"by_turn": 5} 限第 5 回合以内 |
{"remaining": {"side": "enemy", "at_most": 3}} |
敌方(或 third 第三方)只剩 3 台以下 |
{"persuade": {"by": 4, "target": 18}} |
4 号站在 18 号旁边时,指令菜单出现「说服」,选了就触发 |
{"after": "opening"}、"setup"、"end" |
接在这关原版的开场、初期配置、结束事件之后 |
同一时刻原版也有事件要触发时,先跑原版的,你的事件在下一次检查时再看。开场、配置、结束之后的事件不能写 repeat。
if:还要满足什么
| 写法 | 意思 |
|---|---|
"vars": {"131": 0} |
剧情变量(0–199)等于这个值(0–3)。原版用剧情变量记分支,例如场景 129 的 131 号是「多蒙在战斗中和东方不败说过话」(说过是 0) |
"kills": {"4": 50} |
我方这个人物的击坠数至少这么多 |
"kills_below": {"4": 50} |
击坠数不到这么多 |
"fired": ["persuade"] |
本包的这些事件本关已经触发过 |
"not_fired": [...] |
本关还没触发过 |
"roster": {"pilots": [18], "units": [27]} |
这些人物、机体在我方名册里(不管出没出击) |
"not_roster": {...} |
都不在名册里 |
"flags": ["persuaded"] |
本包的这些标记已经设过 |
"not_flags": [...] |
都没设过 |
人物不在我方名册里时,kills 和 kills_below 都不成立。
成功和失败两套台词:同一个时机写两个事件,让条件互斥,游戏会选符合的那个。示例里击坠数够就成功,不够就播被拒的台词:
{ "id": "persuade", "when": { "persuade": { "by": 4, "target": 18 } },
"if": { "vars": { "131": 0 }, "kills": { "4": 50 }, "not_fired": ["refused"] }, "do": [] },
{ "id": "refused", "when": { "persuade": { "by": 4, "target": 18 } },
"if": { "vars": { "131": 0 }, "kills_below": { "4": 50 } }, "do": [] }
被拒的事件只触发一次,成功的又要求 not_fired: refused,所以被拒之后本关两个都不成立,菜单里不再出现「说服」,剧情照原版走。想让玩家能再试,就给被拒的写 "repeat": true,并去掉成功那个的 not_fired。
跨关:标记
fired 只管本关,到下一关就清空。要让后面的关知道前面发生过什么,在事件上写 "set_flags": ["标记名"](小写字母、数字、_、-,最多 32 个字),事件跑完时设上。标记只属于你的包,和原版剧情变量无关,跟着中断存档和场间存档走,开新游戏才清空。
{ "id": "persuade", "scene": 129, "set_flags": ["persuaded"], "when": { "persuade": { "by": 4, "target": 18 } }, "do": [] },
{ "id": "comes-along", "scene": 81, "when": { "after": "opening" },
"if": { "roster": { "pilots": [18] }, "flags": ["persuaded"] },
"do": [ { "say": 18, "text": { "ja": "「ドモン、遅れを取るでないぞ!」", "zh-Hans": "“多蒙,别落在后面!”" } } ] }
这里同时看名册,是因为他之后可能又被原版剧情或别的事件从名册删掉;只看标记的话,人不在也会说话。
do:做什么
| 动作 | 意思 |
|---|---|
{"say": 18, "text": {...}, "mode": 0} |
一句台词,见下 |
{"join": {"pilot": 18, "unit": 27}} |
人物和机体加入我方 |
{"remove": {"pilot": 18, "unit": 27}} |
从名册删掉(单位要先离场) |
{"leave": 18} |
人物的单位离场(不算击破) |
{"leave_group": 11}、{"deploy": 11} |
原版这关的出击组离场、登场 |
{"set": {"131": 3}} |
设剧情变量 |
{"if": {"var": 128, "is": 2}, "then": [...], "else": [...]} |
按剧情变量分支 |
{"focus": 18} |
镜头移到这个人物 |
{"bgm": 11}、{"sound": 124} |
换音乐、放音效 |
{"fade": "out"}、{"fade": "in"}、{"wait": 30} |
淡出、淡入、等待(帧) |
{"close": "window"} |
关掉对白窗口 |
{"op": "3D35", "args": [2335, 1]} |
原版脚本指令照写,见关卡脚本指令参考。可以直接用原版的台词,例如 {"op": "3D40", "args": [43215]} |
台词:text 必须有 ja,其他语言没写的显示日文(繁体没写时用简体)。一句最多 200 字,阅读器会自己分页,不要写 <STOP>。引号要自己写,和原版台词一样:日文、繁体用「」,简体、英文用 “”。mode 是原版对白的显示方式,地图上一般我方写 0、敌方写 2;世界地图上 0 和 1 上下交替。
加入的等级:照原版规则取这关的基准等级(由我方前 15 人的平均等级算出)。"level": {"offset": 3} 在基准上加减,"level": {"like": 4} 和 4 号人物同级(按加入那一刻算)。SP、能力、技能、精神由原版按等级算好;基础值要改就用 data/pilots.json(敌方人物的 SP、精神常常是空的,示例就补了 SP)。
要注意的
- 原版事件只看原版变量。想让原版的某段事件不播或者换一段,就要改它判断的变量。示例里说服之后写
131 = 3,原版结束事件只在 131 为 1 或 2 时播东方不败的死别。 - 单位离场(
leave)不会触发原版的击破事件。原版击破后要做的事(示例里是王登场、打开过关条件),要在你的事件里自己做一遍。 - 同一段剧情搬到另一关前,先看那关的原版事件:交谈用哪个变量、某人被击破后原版接着做什么、结束事件按哪些变量播哪些段。示例的 OZ 线事件就是照场景 137 的原版事件改的。
- 标记存在存档旁边(存档文件夹
saves/里的story-state/),不在存档文件本身里。把存档拿到别的模拟器,或者删掉这个文件夹,读回来就没有标记,跨关的事件不会触发;本关之内的事件不受影响。 - 追加剧本(
campaign/)里的关卡不跑剧情 MOD。
检查和打包
python3 tools/mods/story.py compile story/master.json -o out.json
python3 tools/mods/pack.py yourname.story -o build/mods
第一条只编译一份剧情脚本,看每个事件编译出的指令。第二条打包并用和游戏相同的规则检查。游戏读不了的包会在日志里报 SRW64_STORY_MOD_PROBLEM。
仓库的 docs/guide/story-mods/ 文件夹里有完整的格式说明和场景号对照表,可以整个交给 AI 助手,让它照你的想法写剧情脚本。