制作大脑
打一个电脑席位的 .oabrain:文件夹、每一条清单键、标签、级别、全图视野、脚本在何处运行、额度,以及它停下时会怎样。
本章概述 docs/brains/oabrain-standard.md、docs/brains/authoring.md 和 docs/playing-with-brains.md。那些文档是规范性文本。
大脑是什么
引擎自己的电脑玩家仍然是默认,并且仍然留在引擎里。大脑打一个电脑席位,而且只打玩家把它设置上去的席位。空的大脑标识就是那个内置电脑玩家。没有大脑的席位、不适配的大脑,以及停下来的大脑,都由同一个玩家来打。Classic 用 Lua 写成,打法与那个玩家相同。它是那个玩家的文档,不是替代。
只有把电脑玩家入座的那台机器运行脚本,所以其他玩家什么都不用安装。他们的机器镜像该席位的单位。录像回放不运行脚本。大脑是 Lua 源码和数据。它没有原生代码,没有文件,没有网络,也没有时钟。全图视野是清单上的一个标志。大脑可以设置它,可以带着它打网络游戏,席位会显示这个标志。
包
大脑是一个 .oabrain 文件,或该文件解压成的文件夹。清单是模组配置所用的严格 YAML:一份文档、一个顶层映射、没有锚点、没有块标量。包的顶层放着 oabrain.yaml。它旁边的文件是下面这些之一。这份列表以外的文件会被拒绝。
my-brain.oabrain玩家拿到的文件├─ oabrain.yaml必需 清单,至多 256 KiB├─ brain.lua入口脚本。默认名字是 brain.lua。entry 另点一个名字时,那个文件必须在包里├─ lua/其余的 Lua 源码。require 只读取这些文件。每一段是小写字母、数字和 _├─ nets/清单点名的网络,nets/ 加上一个以 .oann 结尾的主名├─ data/一张数据表,文件夹只深一层:.csv、.tdf、.json 或 .txt├─ icon.png图片,只有 icon 点名它时才算。没有那个键,这个文件就在范围外├─ README.md顶层的一份说明。游戏不加载它└─ LICENSE顶层的许可证。名字 licence,以及子文件夹里的 readme,都在范围外比较时忽略 ASCII 字母的大小写,标准要求字节必须一致的地方除外。entry 点了别的文件时,brain.lua 就在范围外。一个 Lua 文件至多 1 MiB。图标是正方形 PNG,每边至多 256 像素,至多 256 KiB。OA 把大脑安装到 Brains/ 和清单中的标识。
清单
读取器收集每一条被打破的规则,只要有任何一条被打破,清单就保持不变。标准没有列出的键会被拒绝。配置文件的键属于模组。变异器的键会因为它是变异器的键而被拒绝。版本要加引号,因为裸的 1.0 是数字;星号也要加引号,因为裸的星号是 YAML 别名。
| 键 | 必填 | 作用 |
|---|---|---|
oabrain | 是 | 格式。整数 1。1.0 以及加引号的 1 都会被拒绝。 |
id | 是 | 小写 kebab-case,1 到 64 字节。不能以连字符开头、结尾,也不能有连续连字符。Brains/ 下的文件夹名。 |
name | 是 | 1 到 64 字节,不能有控制字符。玩家看到的名字。 |
version | 是 | 加引号,一到三段,即变异器那样的版本,例如 “2.0”。纯数字会被拒绝。2、2.0 和 2.0.0 比较时相等。 |
author | 是 | 一个映射。name 必填,1 到 128 字节。email 可选。 |
description | 是 | 必填,1 到 2,000 字节。允许换行符。其他任何控制字符都会被拒绝。第一行截到 200 字节,就是摘要。 |
homepage | 否 | 一个 http 或 https 地址。 |
icon | 否 | 只能是 icon.png,包顶层的那张图。其他一律拒绝。 |
tags | 是 | 下面九个标签中的 1 到 4 个,不得重复。 |
works-with | 是 | 1 到 32 个条件,标识不重复。星号表示任意模组和基础游戏,并且这时它是唯一一条。ta-3.1c 表示没有模组的基础游戏。其他标识是模组,可以带版本范围。 |
requires | 否 | 只能有 engine 和 expansions。engine 是一个范围,以大于号开头时要加引号。expansions 是 1 到 16 个 bundle 标识。大脑不能要求一个变异器。 |
levels | 是 | 1 到 6 个级别。每个映射恰好是 id、name 和 difficulty。 |
fullAwareness | 否 | true 或 false。不写则为 false。加引号的 true 会被拒绝。 |
networks | 否 | 1 到 16 个,每个恰好是 id、file 和 sha256。file 是 nets/ 加上一个以 .oann 结尾的主名。sha256 是引号中的 64 个小写十六进制数字。 |
options | 否 | 最多 16 个,沿用变异器的选项规则。开关是 true 或 false,数字是一个数,选择是它的文本。me.options 读取它们。 |
strings | 否 | 最多 16 种语言。一种语言可以包含 name、description、options、labels 和 levels。 |
entry | 否 | 包顶层的一个 Lua 文件,名字由小写字母、数字和 _ 组成。不写这个键时为 brain.lua。 |
九个标签
标签是这九个之一,按字节逐个比较。超过四个,在共享的标签规则已经通过之后,是它自己的问题。不是这九个之一的标签会被拒绝。
| 键 |
|---|
classic |
economy |
rush |
turtle |
air |
naval |
learned |
teaching |
experimental |
级别与难度
levels 是一列 1 到 6 个映射。每个恰好是 id、name 和 difficulty,全部必填。id 是 1 到 32 字节的小写 kebab-case,标识不重复。name 是 1 到 64 字节,不能有控制字符。difficulty 是 easy、medium 或 hard,按字节逐个比较。
这三个名字就是值 0、1 和 2。选定该级别的席位把这个值用作它的收入规则,也就是游戏已经在用的同一难度。
全图视野
fullAwareness 是 true 或 false。不写则为 false。带这个标志的大脑能看见迷雾里的席位看不见的单位,做法与内置电脑玩家相同,而且它的选址检查不需要一个放置玩家。这个标志允许用于网络游戏。资料库显示 能看到地图上的每个单位,与Total Annihilation自带的电脑玩家相同。这个标志是清单自己的。它不由包的标识授予,也不是只给 Classic 的那些东西之一。
大脑在哪里运行
只有把电脑玩家入座的那台机器需要这个包,也只有那台机器运行脚本。席位是这台机器的电脑玩家。人类席位会被拒绝。game.mode 是 skirmish、campaign 或 network。
| 情形 | 运行什么 |
|---|---|
| 遭遇战 | 这台机器所打的每个电脑席位,当玩家在它上面设置了大脑时。空标识是内置电脑玩家。 |
| 战役任务 | 战役席位跟随玩家设置的默认大脑。任务不会忽略那个默认。 |
| 网络游戏 | 拥有该电脑席位的机器运行大脑,全图视野也包括在内。其他机器不运行。 |
| 稍后加载的存档 | 存档点名每个席位。包缺失或版本不同时会怎样,由标准说明。 |
| 录像回放 | 录像存下大脑的文本。回放应用记录下来的数据包。它不让大脑入座,也不调用它的钩子。 |
| 死斗或锦标赛 | 这次启动不把任何电脑玩家入座。 |
席位所打的大脑不是模拟哈希的输入。两场对局若只有入座的大脑不同,对局状态哈希保持相同。模组的 ai.* 规则值是该哈希所覆盖的模组配置的一部分,因此它们仍然到达每一台机器。
运行时的额度
大脑作为 Lua 在一个沙箱里运行。两个大脑不共享模块、不共享全局变量、也不共享额度。脚本没有文件、没有时钟、没有环境、没有进程、也没有网络。每一块都按文本加载。pcall 和 xpcall 不能让一个已经用尽的阶段继续活着。一个回合是一次计入额度的调用,在席位的控制器点上,单位清扫之后。钩子给出的命令在下一个 tick 生效。
| 额度 | 值 |
|---|---|
| load、fits、start、save 和 report 中的指令 | 50,000,000 |
| 一个 tick 增加的指令 | 2,000,000 |
| 额度库可以持有的 tick 数,含当前 tick | 30,因此余额从 60,000,000 开始 |
| Lua 内存 | 64 MiB(67,108,864 字节)。它不会在阶段之间重置。 |
| 网络内存 | 64 MiB,与 Lua 上限分开 |
| 实时,不是回合的阶段,以毫秒计 | 10,000 |
| 实时,一个回合,以毫秒计 | 250 |
| 一条字符串,以字节计 | 65,536 |
大脑失败时
大脑会因钩子或计时器中的错误、用尽的指令额度、实时兜底、Lua 内存上限、超过 65,536 字节的字符串,或席位拒绝的一次加载而停下。失败的回合在返回之前关闭沙箱并清掉钩子。这个失败的回合仍算已经打过。下一个命令 tick 没有钩子,因此由内置电脑玩家接手。被拒绝、启动失败或不适配的大脑从来没有钩子,所以那个玩家从一开始就打。提示使用清单名字和理由,截到 160 字节:
- `{brain} stopped ({reason}); the engine plays this seat on`
- `{brain} could not start ({reason}); the engine plays this seat`
- `{brain} does not fit this game ({reason}); the engine plays this seat`
席位自己没有理由时,理由是 the brain could not be seated。被击败的玩家只是不再被调用。沙箱随对局关闭。