← 技术文档

剧情 MOD

给原版关卡加事件:什么时候触发、要满足什么、做什么、说哪些新台词。可以让人物加入或留下、改变剧情分支,不占原版的事件位,原版事件一个不动。

更新于 2026-11-03

剧情 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 助手,让它照你的想法写剧情脚本。