Starsector Nihilism Guidance
首页
GitHub 项目
首页
GitHub 项目
  • 首页
  • 基础及舰船相关
    • MOD 骨架和入口文件
    • 制作一艘基本舰船
    • 制作一艘航母与战机
    • 制作一艘相位舰
    • 制作火炮、光束与导弹
    • 为舰船加入内置武器、船插与战机
    • 补充描述文本
  • 无需入门的代码修改
    • 以船插为例初步熟悉代码
    • 制作完整船插
    • 制作最小战术系统
    • 为武器加入命中与光束特效
  • 势力与生涯星系
    • 注册一个势力
    • 配置势力舰队
    • 制作 MOD 入口类的最基础适配
    • 制作一个生涯星系
    • 补完描述文本
  • 战役与无代码对象
    • 制作测试战役装配
    • 为模拟战与标题画面加入装配
    • 为舰船加入外观风格
    • 制作一艘模块船
    • 制作一种星体类型
    • 制作一个自定义实体
  • 音频资源与播放
    • 准备 OGG 音频资源
    • 注册音频资源
    • 为游戏对象加入音频
  • Java 工程化
    • 确认 Java 与编译依赖
    • 迁移代码文件到 src
    • 配置 Gradle 工程
    • 构建 JAR 并声明产物
  • 全局服务
    • 制作 MOD 入口类
    • 使用 Global 访问游戏服务
    • 读取设置
    • 调用声音播放器
    • 记录和定位 MOD 日志
  • 战斗运行时
    • 读取战斗引擎状态
    • 读取战斗对象
    • 制作每帧战斗插件
    • 修改舰船数值
    • 配置武器脚本
    • 配置战斗监听器
    • 制作分层渲染插件
  • 战斗内容扩展
    • 详解舰船插件
    • 详解战术系统
    • 详解武器动画
  • 生涯世界与基础服务
    • 读取生涯世界状态
    • 构建生涯地点
    • 配置生涯势力
    • 放置生涯实体
  • 生涯舰队与舰队成员
    • 生成生涯舰队
    • 管理舰队中的成员
    • 舰队状态的重算过程
    • 维护舰队成员状态
    • 读取并修改舰船装配
  • 市场与商品
    • 创建生涯市场
    • 读写货舱内容
    • 注册市场商品
    • 实现专用子市场
    • 制作专用产业
    • 注册特殊物品
    • 实现市场条件
  • 人物与技能
    • 配置人物基础
    • 让人物承担角色
    • 制作角色技能
  • 生涯脚本与情报事件
    • 编写生涯逐帧脚本
    • 监听生涯事件
    • 选择生涯插件
    • 管理舰队活动情报
  • 酒吧事件与任务
    • 组织生涯交互对话
    • 实现对话插件
    • 接入酒吧事件
    • 构建阶段任务
    • 接入酒吧任务
  • Rules 系统、命令与内存
    • 组织 Rules 规则表
    • 编排 Rules 对话
    • 接入 Rules 生涯场景
    • 编写 Rules 命令
    • 使用 MemoryAPI 保存状态
  • 文件读取与写入
    • 读取 MOD 内文本
    • 读取 MOD 内 JSON 文件
    • 读取 MOD 内 CSV
    • 读写 common 数据
    • 实现本地化机制
  • 常用依赖库
    • 接入常用依赖库
    • 使用 LazyLib 几何工具
    • 使用 LazyLib 查询战场
    • 使用 LazyLib 保存公共 JSON
    • 注册 GraphicsLib 光照数据
    • 创建 GraphicsLib 动态光源
    • 制作 GraphicsLib 纹理映射
    • 配置 MagicLib 自动尾迹
    • 创建 MagicLib 运行时尾迹
    • 配置 MagicLib 数据赏金
    • 使用 MagicLib 辅助生涯开发
    • 使用 MagicLib 战斗小工具
  • 可选依赖与 Nexerelin
    • 接入可选依赖
    • 登记 Nexerelin 势力与外交配置
    • 配置 Nexerelin 的两种星区开局
    • 配置 Nexerelin 战略 AI 与扩张规模
    • 登记 Nexerelin 地面战单位与产业
    • 为 Nexerelin 特遣队添加命名与事件响应
    • 制作即时生效的 LunaLib 设置面板
  • 着色器与渲染
    • 使用 Java OpenGL
    • 使用 GLSL 进阶
    • 使用 VBO 和 VAO 保存顶点
    • 使用 SSBO 传递参数
    • 使用实例化进行绘制
    • 选择 BoxUtil 渲染方案

组织 Rules 规则表

本节给独立舰队添加两种通讯问候,非敌对时采用一段,其他情况采用另一段,通讯菜单仍保留原版选项。借这两条规则,我们说明身份、条件、脚本与文字怎样放进 rules.csv,再从加载日志和实际通讯确认它们的作用。

本节预期阅读时间:~25 分钟。

本节预期操作时间:~30 分钟。

学习目标

完成后,您将能够:

  • 说明 rules.csv 七列各自生成什么运行时数据;
  • 在多行单元格中正确使用引号、换行和字段注释;
  • 判断同路径合并、replace 和重复 ID 对加载结果的影响;
  • 从日志区分数据未加载、规则未注册和表达式语法错误;
  • 用两条独立势力 OpenCommLink 规则验证加载结果。

前置知识

  • [C14-S3] 的交互对话框生命周期;
  • [C15-S1] 的 InteractionDialogAPI 与选项分支;
  • [C1-S7] 的 CSV 表头、单元格与引号;
  • [C15-S5] 的 trigger、条件、脚本与选项最小记录。

给独立舰队安排通讯问候

在 MOD 的 data/campaign/rules.csv 追加 GearIn_rules_comm_friendly 与 GearIn_rules_comm_fallback,保留已有规则与表头。前者限定非敌对独立舰队,后者接住其他独立舰队,实际成品在后面的完整记录中保存。下面的局部片段分别展开这些字段。

规则表采用七列。表头把每个单元格对应到字段,字段内部还各有自己的语法:

id,trigger,conditions,script,text,options,notes

每个有效数据行生成规则,列名决定它读入哪份内容。列可以随表头调整顺序,同一文件中的记录继续按该表头排列。当前沿原版顺序组织,便于与已有数据对应。

让 ID 与通讯入口分别对应

id 表示规则身份,加载日志用它定位本条记录。空 ID 不创建规则,以 # 开头则停用整行,局部对照如下:

#GearIn_rules_comm_disabled,OpenCommLink,,,,,暂时停用
GearIn_rules_comm_friendly,OpenCommLink,,,,,独立势力的非敌对问候

第二条使用自己的前缀,日志与后续匹配可以找到同一个 ID。trigger 另决定规则在何时参加候选:独立舰队建立通讯时,原版调用 OpenCommLink,所以这两条规则都挂到这个名字。

GearIn_rules_comm_friendly,OpenCommLink,,,,,独立势力的非敌对问候

规则身份与触发入口分别填写。 id 独立,trigger 与实际调用字符串逐字符一致。自有 trigger 也可以组织规则,但还要由 Java 或另一条 Rules 脚本发起调用。

按势力与敌对关系选择候选

conditions 按物理换行写多条条件。下面的局部片段先限定 independent,再限定非敌对,注释只说明这份条件:

GearIn_rules_comm_friendly,OpenCommLink,"# 只处理独立势力
$faction.id == independent
!$entity.isHostile score:100",,,,独立势力的非敌对问候

读取时跳过空行与 # 子行,其余表达式去掉首尾空白后依次求值。任意一条失败,候选就不成立;score:100 改变第二条条件的分值,[C16-S2] 继续说明计分与选择。

命中后恢复通讯菜单

本节的问候命中后,还要让玩家继续操作。script 先写通讯菜单状态,再用 FireAll PopulateOptions 填入原版选项:

GearIn_rules_comm_friendly,OpenCommLink,,"$menuState = fleetConvMain 0
FireAll PopulateOptions",,,独立势力的非敌对问候

两行按顺序执行,后面的菜单规则能取得前面刚写入的 fleetConvMain。脚本也按换行跳过空行和 # 注释,普通命令从上到下执行。

命令简单名由 data/config/settings.json 的 ruleCommandPackages 查找,原版基础包为 com.fs.starfarer.api.impl.campaign.rulecmd。命令没有对应类时,在加载阶段报告错误,[C16-S4] 再接入自有命令包。

命令参数需要字面双引号时,CSV 使用两个连续双引号转义。例如 SetShortcut optionId "ESCAPE" 在单元格中写成 SetShortcut optionId ""ESCAPE""。

文字与菜单各自提供结果

text 是命中后显示的正文,规则执行器替换其中的文本 token,再写入当前对话。将条件、脚本与文字放在一起,局部记录如下:

GearIn_rules_comm_friendly,OpenCommLink,"$faction.id == independent
!$entity.isHostile score:100","$menuState = fleetConvMain 0
FireAll PopulateOptions",对方很快回应了通讯请求。,,独立势力的非敌对问候

第五列提供问候,脚本另恢复菜单,notes 留作维护说明。文本中的井号仍是显示字符,例如 # 通讯接通了。 会原样出现在面板中。

用独占一行的 OR 准备候选文本

同一条规则需要随机采用多种措辞时,可以用独占一行的 OR 分隔候选文本,并用一对双引号包住整个多行单元格:

GearIn_rules_text_variants,OpenCommLink,,,"对方很快回应了通讯请求。
OR
# 通讯接通了。",,两种问候文本

加载器按精确的换行序列 \nOR\n 拆分内容,执行规则时再随机选择其中一段。第二个候选开头的井号会正常显示;OR 与其他文字共处一行时属于正文。

给规则添加选项与维护说明

当前问候通过原版 PopulateOptions 取得选项,需要自有选项时,还可以在 options 列逐行写 id:text 或 order:id:text。以下局部片段分别使用默认顺序与顺序 50:

GearIn_rules_options,PopulateOptions,,,,"GearIn_rules_apply_open:检查通讯记录
50:cutCommLink:切断通讯",两个选项

玩家看到两段显示文字,选项数据分别为 GearIn_rules_apply_open 与 cutCommLink。两段格式默认顺序为 0,三段格式把首段解析为数值。读取器按半角冒号拆分,显示文字多出半角冒号就会改变这份结构;ID 以 $ 开头时加载失败。

和条件、脚本一样,options 中以 # 开头的子行是注释。选项 ID 与 $option 的回流关系由 [C16-S2] 负责。

notes 保存用途、数据归属与规则关系,不参与运行。局部记录如下:

GearIn_rules_comm_friendly,OpenCommLink,,,,,GearIn 独立势力通讯问候

这里的维护说明不会出现在玩家面板中,停用仍通过行首 ID 处理。

把多行内容保留在同一条记录中

用双引号包住多行单元格

Rules 先按 [C1-S7] 的 CSV 规则读取单元格,再解释字段内部内容。条件、脚本、文字与选项都可多行,用一对双引号包住整个字段。局部记录如下:

GearIn_rules_comm_friendly,OpenCommLink,"$faction.id == independent
!$entity.isHostile score:100","$menuState = fleetConvMain 0
FireAll PopulateOptions",对方很快回应了通讯请求。,,独立势力的非敌对问候

这条记录占四个物理行,解析后仍是一条七列记录。引号内换行属于当前字段,引号外换行结束记录。加载错误指向后面的行时,前一条是否闭合也参与定位。

转义逗号和双引号

含半角逗号的单元格也要放进双引号。单元格内部的一个字面双引号写成 "";反斜杠不会替代 CSV 的双引号转义。

GearIn_rules_quoted,OpenCommLink,,"AddText ""信号稳定,继续通讯。""",,,含逗号和引号

命令取得的参数为 信号稳定,继续通讯。,字面引号通过 CSV 的 "" 保留到命令层。参数中使用半角逗号时,单元格外层引号也继续保留,逗号才能留在字段内。

区分整行注释与字段注释

井号作用于所在层:ID 中停用记录,条件、脚本与选项中跳过子行,文字中保留显示。以下局部对照把这几种情况放在同一表头下:

#GearIn_rules_comm_disabled,OpenCommLink,,,,,整行停用
GearIn_rules_comm_note,OpenCommLink,"# 条件注释
$faction.id == independent","# 脚本注释
$menuState = fleetConvMain 0","# 这里的井号会显示",,字段内注释

第二条规则仍会注册。它只有一个有效条件和一条有效脚本,而文本面板会完整显示 "# 这里的井号会显示"。

保存为 UTF-8 无 BOM

规则表使用 UTF-8 无 BOM。BOM 位于文件首字节时可能进入首个表头或键的解析结果;智能引号替换只处理弯引号等字符,不负责移除错误编码标记。

先让当前记录解析出七列,字段内部的 Rules 表达式才会交给正确读取器。缺失引号或裸逗号改变列边界时,条件与脚本的语法判断也会随之落到错误内容上。

从加载结果定位规则

读取并合并同路径资源

启动时读取原版与 MOD 的同路径 CSV,按 ID 合并,并处理智能引号。当前记录参与合并,保留独立 ID。 规则从启用的数据重建,存档中的运行状态另由 Memory 等对象保存。

日志先出现 Loading CSV data from [...],再输出 Loading rule: <id>。后者表示开始解析这条规则,随后还可能发生表达式错误;确认启动加载成功后,再用同一个 ID 找到实际通讯候选。文件路径、规则解析和运行命中各有对应结果。

重复 ID 有两种结果

同一文件中的重复 ID 会抛出 RuntimeException("Duplicate key [...] while loading [...]") 并停止加载;跨文件重复时先处理的规则保留,后处理的同 ID 行静默跳过。

mod_info.json 中的 replace 若包含这条路径,读取器改为采用替换文件。当前文件承接整张结果,原版与其他同路径来源也就不再参与合并。

replace 会切断原版 Rules 数据

当前扩展参与同路径合并。选择 replace 后,当前 MOD 文件承担整张表的结果,原版通讯、市场、任务等规则也需要完整提供。

加载器还会统计只出现一次的变量,并向标准输出打印 WARNING: variable [...] only used once; rule id: [...]。$tag: 和 $market.mc: 不参与这项统计;WARNING 不会改变规则匹配。

表达式错误发生在哪一列

条件和脚本使用同一套表达式解析。状态赋值写在脚本,比较放在条件;加载明确拒绝条件中的 = 和脚本中的 ==。!=、>、>=、<、<= 同样用于比较,脚本中的计算返回值也不承担候选筛选。

条件列出现赋值、脚本列出现 ==、未知命令、选项 ID 以 $ 开头、三段选项的 order 不是数值,以及同一文件出现重复 ID,都会阻止对应数据正常进入运行时。堆栈中的规则 ID 和 CSV 路径是修复入口。

同文件重复键会在 CSV 读取时失败,跨文件重复则保留先处理项。定位文件已读取、目标 ID 却没有进入规则解析时,按这些合并关系核对来源与唯一性。

七列速查表

字段读取结果内部拆分主要错误
id规则稳定身份不拆分空值、# 跳过、重复 ID
trigger候选集合名称不拆分拼写或大小写与调用方不一致
conditions候选匹配条件按换行拆分,跳过空行和 # 子行写入赋值或错误比较
script命中后的赋值与命令按换行顺序拆分,跳过空行和 # 子行未知命令、比较进入脚本
text文本面板内容整个单元格作为文本内容读取把 # 误认为字段注释
options选项 ID、文本与顺序按换行拆分,再按冒号分成 2 或 3 段裸冒号、非法 order、$ 开头 ID
notes维护说明不参与运行时解析把 notes 当作停用开关

用 OpenCommLink 验证规则表

OpenCommLink 从原版舰队通讯进入规则匹配。两个成品都限定 independent,friendly 另限定非敌对并取得较高分数,脚本恢复 fleetConvMain 与原版选项。目标关系变化时采用对应问候,通讯操作继续保留。

完整最小例

把下列完整表头与两条数据写入前述文件。已有表头时追加两条记录,保留前文规则。

id,trigger,conditions,script,text,options,notes
GearIn_rules_comm_friendly,OpenCommLink,"$faction.id == independent
!$entity.isHostile score:100","$menuState = fleetConvMain 0
FireAll PopulateOptions",对方很快回应了通讯请求。,,独立势力的非敌对问候
GearIn_rules_comm_fallback,OpenCommLink,$faction.id == independent score:50,"$menuState = fleetConvMain 0
FireAll PopulateOptions",独立舰队接通了通讯。,,独立势力的回退问候

验证方案

前置数据:已启用包含本页 data/campaign/rules.csv 的 MOD。观察位置包括启动日志、启动器标准输出和独立舰队通讯界面。

  1. 启动游戏,确认日志出现 Loading CSV data from [...],并找到 GearIn_rules_comm_friendly 与 GearIn_rules_comm_fallback 的 Loading rule: 行。
  2. 找到非敌对独立舰队,选择建立通讯,查看 friendly 文本与原版选项。
  3. 让同一势力舰队进入敌对状态后再次通讯,确认 fallback 文本接替 friendly 文本。
  4. 在启动器标准输出中核对单次使用变量 WARNING,并把变量名与规则 ID 对应起来。
  5. 分别制造同文件重复 ID 和跨文件重复 ID,确认前者中止加载、后者只注册先处理的规则;恢复两个唯一 ID。
  6. 在 conditions 中写赋值、在 script 中写比较、把命令名写错,逐项确认加载期错误后恢复正确表达式。
  7. 在 options 文本中加入半角冒号,确认选项解析错误;改用全角冒号或改写句子。
  8. 给 MOD 声明同路径 replace,确认原版规则未参与合并;移除该声明后确认原版和 MOD 规则共同加载。

预期结果:

  • 日志同时出现规则文件路径和两个稳定 ID;数据结构错误在进入生涯前暴露。
  • 非敌对独立舰队使用 friendly 文本,敌对独立舰队使用 fallback 文本。
  • 两条脚本都恢复 fleetConvMain 并追加原版 PopulateOptions 结果。

故障分析

  • 规则加载时启动中止:conditions 中出现赋值、script 中出现 ==、选项 ID 以 $ 开头、命令类缺失或同文件 ID 重复。

    • 解决方案:按堆栈定位 CSV 行并把表达式放回正确列,重启。
  • 规则没有触发且日志无异常:本规则 ID 与其他文件重复,后加载项被静默跳过。

    • 解决方案:改用带 MOD 前缀的唯一 ID。
  • 选项文本在冒号后被截断:加载器把冒号解释为选项字段分隔。

    • 解决方案:改写文本或使用全角冒号,重启。
  • 选项加载时报 NumberFormatException:三段选项的首段不是可解析的排序数值。

    • 解决方案:删除多余冒号或提供合法 order,重启。
  • 整份原版规则消失:MOD 对 data/campaign/rules.csv 声明了 replace。

    • 解决方案:移除 replace,让同路径 CSV 参与合并。

总结

两条通讯记录用规则 ID 保存身份,trigger 连接通讯请求,条件按目标关系选择,脚本恢复菜单,文字提供问候。CSV 保留多行字段,Rules 再解析其中的表达式与显示内容。

加载成功后,规则还要进入实际调用的候选并满足条件。日志与通讯界面共同确认这些结果,下一节再把选项回流与结果选择连接成完整分支。

建议参考项

  • data/campaign/rules.csv:本节 GearIn_rules_comm_* 数据与原版规则表的存放路径。
  • data/config/settings.json:ruleCommandPackages 命令包配置。
  • com.fs.starfarer.api.campaign.rules.RuleAPI:加载后单条规则的字段对象。
  • com.fs.starfarer.api.campaign.rules.Option:对话选项的 id / text / order。
  • com.fs.starfarer.api.campaign.rules.ExpressionAPI:条件和脚本表达式的加载结果。
  • com.fs.starfarer.api.impl.campaign.rulecmd.FireAll:示例脚本调用的原版菜单填充命令。
在 GitHub 上编辑此页
上次更新: 2026/10/14 12:25
贡献者: 1847905557
下一页
编排 Rules 对话