GameOfMir 脚本语言参考规约
本文目标是把 GameOfMir/GOM 引擎脚本整理成“语言参考”,而不是按功能场景堆命令说明。
来源是原始 CHM 帮助文档解包后的原文。原文没有给出正式 BNF/EBNF,因此本文把官方示例中稳定出现的结构抽象成规约;凡是原文只出现例子、没有明确说明的地方,本文会标注为“实现约定”或“待实测”。
文中标注“来源”的脚本例子都会由 pnpm verify:docs 校验,确保代码块确实出现在抽取后的原文索引中。没有来源标注的 text 代码块只表达语法形态或参数形态,不当作可运行例子。
完整命令签名仍见 命令索引,本文只定义脚本语言的骨架、控制流、变量、文本替换和文件格式。
1. 脚本模型
GOM 脚本是面向事件和标签的行式脚本。
一个脚本文件由若干标签段组成。标签段以 [@标签名] 开始,后面跟条件块、动作块、对话块或普通文本。引擎通过 NPC 点击、登录、死亡、物品触发、定时器、机器人、命令回调等事件进入某个标签段。
典型标签段如下。这个例子来自原始命令说明,不是另造的演示脚本。
来源:召唤指定怪物为宝宝
[@recallmob]
#IF
CHECKSLAVECOUNT < 5
#ACT
RECALLMOB 神兽 7 30
1
#SAY
好好看着自己的宝宝,不要乱咬人。
#ELSESAY
你已经有5个或以上的宝宝,现在不可以再召宝宝。
;==========================================核心执行模型:
[@label]定义入口。#IF下的检测命令全部成功,才执行后续#SAY或#ACT。#OR下的检测命令任意一个成功,就执行后续#SAY或#ACT。#ELSESAY和#ELSEACT是上一组条件失败时执行的分支。- 没有检测命令的
#IF在官方示例中作为无条件块使用。 #ACT是动作命令区。#SAY是 NPC 对话输出区。GOTO @label改变执行位置。BREAK用于中止当前执行流。CLOSE用于关闭对话框。
2. 词法约定
2.1 行
脚本是行式语言。通常一行是一条指令、一个标签、一个对话文本行或一条注释。
script = { line } ;
line = blank | comment | label | directive | command | dialogue ;
blank = "" ;
comment = ";" text ;实现约定:
- 官方示例大量使用
;作为整行注释。 - 官方示例中也出现
//行尾说明,例如NOT CHECKTITLE 巅峰战神 //检测没有有这个称号。是否所有位置都接受//行尾注释需要实测;写规范脚本时优先使用;。 - 命令、指令大小写不敏感。原文混用
#IF/#if/#Act、SENDMSG/sendmsg。
2.2 空白
空格用于分隔命令名和参数。
command = [ target "." ] command-name { whitespace argument } ;
command-name = letter { letter | digit | "_" } ;
argument = token | variable-ref | substitution | label-ref | free-text ;多数命令是按空格分隔参数;少数命令把某个位置之后的内容当作完整文本,例如 SENDMSG 的消息内容、MESSAGEBOX 的提示文本。
中文物品名、地图名、标签名可以直接出现。若文本本身包含空格,是否按单个参数处理取决于具体命令;原文没有统一转义语法。
3. 标签和跳转
3.1 标签定义
label = "[@" label-name "]" ;
label-name = { any-char-except-"]" } ;示例:
[@main]
[@Login]
[@PlayDie]
[@ButtonClick1]
[@FoundryItem_]
[@合成 求婚戒指]标签名可以是英文、数字、下划线、中文,也可以带特定前缀或后缀。很多系统事件要求固定标签名。
常见系统入口:
| 标签 | 所在文件/场景 | 说明 |
|---|---|---|
[@main] | NPC 脚本 | 默认入口 |
[@Login] | QManage.txt | 人物登录触发 |
[@HeroLogin] | QManage.txt | 英雄登录触发 |
[@PlayDie] | QFunction-0.txt | 人物死亡触发 |
[@OnKillMob] | 地图参数 ONKILLMON | 杀怪触发 |
[@StdModeFuncX] | QFunction-0.txt | 自定义物品使用触发 |
[@TakeOnX] / [@TakeOffX] | QFunction-0.txt | 穿戴/取下触发 |
3.2 标签引用
label-ref = "@" label-name ;常见位置:
GOTO @main
DELAYGOTO 3000 @DelayLabel
DelayCall 5000 @Kick
MessageBox 是否确认要删除 @确定 @取消
<领取奖励/@reward>
<请输入充值卡卡号/@@InPutString22>注意:
- 标签定义写作
[@name]。 - 跳转和点击引用写作
@name。 - 自定义输入使用
@@InPutStringN或类似形式,双@@表示打开输入框并回调同名标签。
4. 顶层语法
先把 # 开头的行分层看,会比只按命令名背容易得多:
- 标签层:
[@main]、[@InputString1]这类入口。 - 条件层:
#IF、#OR开始一个条件组。 - 结果块层:
#ACT/#SAY是条件成功时执行的块,#ELSEACT/#ELSESAY是条件失败时执行的块。 - 行内容层:检测命令、动作命令、对话文本、跳转链接都在这些块里面。
所以 #ACT 和 #SAY 不是 #IF 的同类检测条件;它们是 #IF/#OR 后面的结果块。一个条件组后面可以同时出现 #ACT 和 #SAY,表示成功时既执行动作,也输出对话。
常用顶层指令:
| 指令 | 层级 | 用途 |
|---|---|---|
#IF | 条件层 | 开始“全部检测为真才通过”的条件组 |
#OR | 条件层 | 开始“任一检测为真即通过”的条件组 |
NOT | 检测行前缀 | 取反单条检测命令,不是独立块 |
#ACT | 成功结果块 | 条件通过后执行动作命令 |
#SAY | 成功结果块 | 条件通过后输出 NPC 对话 |
#ELSEACT | 失败结果块 | 条件失败后执行动作命令 |
#ELSESAY | 失败结果块 | 条件失败后输出 NPC 对话 |
#INCLUDE | 文件层 | 引入外部配置或脚本片段 |
script = { include | label-section | comment | blank } ;
include = "#INCLUDE" path ;
label-section = label { section-line } ;
section-line = comment
| condition-group
| action-section
| say-section
| dialogue
| command
| blank ;
condition-group = condition-head { condition-command }
{ true-section | false-section } ;
condition-head = "#IF" | "#OR" ;
true-section = action-section | say-section ;
false-section = else-action-section | else-say-section ;
action-section = "#ACT" { action-line } ;
say-section = "#SAY" { say-line } ;
else-action-section = "#ELSEACT" { action-line } ;
else-say-section = "#ELSESAY" { say-line } ;
condition-command = [ "NOT" ] [ target "." ] command-name { argument } ;
action-line = [ target "." ] command-name { argument }
| flow-command
| comment
| blank ;
say-line = dialogue | dialogue-control | comment | blank ;这里把 true-section 和 false-section 写成可重复,是因为原文例子里确实存在 #ACT 后接 #SAY、#ELSESAY 后接 #ELSEACT 的写法。条件组通常到下一个 #IF/#OR 或下一个 [@label] 之前结束。
4.1 #IF、#ACT、#SAY 放在一起看
#IF 先判断检测命令。通过后,#ACT 执行动作,随后 #SAY 输出成功对话;失败时,#ELSESAY 输出失败对话。
来源:召唤指定怪物为宝宝
[@recallmob]
#IF
CHECKSLAVECOUNT < 5
#ACT
RECALLMOB 神兽 7 30
1
#SAY
好好看着自己的宝宝,不要乱咬人。
#ELSESAY
你已经有5个或以上的宝宝,现在不可以再召宝宝。
;==========================================4.2 #ELSEACT
#ELSEACT 是失败动作块。下面的例子里,检测宝宝名成功就发成功消息;失败就进入 #ELSEACT 发失败消息。
来源:检测人物宝宝名字
[@main]
#IF
CHECKSLAVENAME GameOfMir
#ACT
SENDMSG 5 提示:你的宝宝叫GameOfMir
#ELSEACT
SENDMSG 5 提示:你的宝宝不叫GameOfMir
;==========================================4.3 #OR
#OR 也是条件组,但它下面的检测命令只要有一个成功,条件组就通过。下面例子先排除空输入,再用 #OR 检查长度过长或过短,两者任一成立都会进入后面的 #ACT。
来源:人物在线改名系统
[@InputString1]
#IF
Equal S1
#ACT
SENDMSG 6 请输入一个正确的名称
Break
#OR
CheckStringlength S1 > 20
CheckStringlength S1 < 4
#ACT
SENDMSG 6 输入名称长度不正确
Break
#IF
#ACT
QUERYHUMNAMEEXIST S14.4 NOT
NOT 不是一个新区块,它只取反紧随其后的单条检测命令。NOT CHECKLEVELEX > 80 的意思是“等级不大于 80”。
来源:脚本检测命令取反 NOT
[@main_1]
#IF
NOT CHECKLEVELEX > 80 //检测人物等级不大于80级
#ACT
SENDMSG 5 你的等级不大于80级HeroM2 目标前缀也可以与 NOT 一起使用:
来源:脚本检测命令取反 NOT
[@main_3]
#IF
NOT H.CHECKLEVELEX > 80 //检测英雄等级不大于80级
#ACT
SENDMSG 5 你的英雄等级不大于80级4.5 空 #IF
原文里常见空 #IF。这种写法通常用作无条件执行块:没有检测命令,后面的 #ACT/#SAY 直接作为结果块执行。
来源:绝对路径说明
[@Main]
#If
#Act
GETRANDOMLINETEXT ..\QuestDiary\装备列表.txt <$STR(S0)>
#Say
取回的文本是: <$STR(S0)>
;==========================================4.6 分支绑定和阅读顺序
#ELSEACT/#ELSESAY 绑定到前一个 #IF/#OR 条件组。遇到新的 #IF/#OR 时,开始新的条件组;遇到新的 [@label] 时,进入新的标签段。
#IF
CHECKITEM 证明 1
#ACT
TAKE 证明 1
#ELSEACT
SENDMSG 6 缺少证明上面这个结构是规约形态,不是原文例子。实际命令写法应以 命令索引 的来源条目为准。同一标签内可以连续出现多个 #IF/#OR 条件组。它们按顺序执行,除非遇到 BREAK、GOTO、CLOSE 等改变流程的命令。
5. 动作命令
动作命令位于 #ACT 或 #ELSEACT 下,用于改变人物、物品、地图、变量、消息、触发等状态。
action-command = [ target "." ] action-name { argument } ;
flow-command = "GOTO" label-ref
| "BREAK"
| "CLOSE"
| "DELAYGOTO" milliseconds label-ref [ delete-on-map-change ]
| "DelayCall" milliseconds label-ref
| "GOTOLABEL" mode label-ref [ range ]
| "GOTOLABELEX" mode x y range script-scope label-ref ;常见动作命令形态:
GIVE 金币 10000
TAKE 证明 1
MAPMOVE 3 330 330
SENDMSG 6 领取成功
MOV S1 GameOfMir引擎M2
INC N1 1
MOVR G10 10 100
GAMEGOLD + 50000命令参数由命令自己定义。不要试图用统一表达式语法解释所有参数;GOM 更接近“命令 + 位置参数”的 DSL。
6. 检测命令
检测命令位于 #IF、#OR 后,用于返回成功/失败。
condition-command = [ "NOT" ] [ target "." ] check-name { argument } ;
operator = "=" | ">" | "<" ;常见形态:
CHECKLEVELEX > 50
CHECKGAMEGOLD > 9999
CHECKITEM 证明 1
EQUAL P0 5
LARGE N9 50
SMALL P1 10
RANDOM 3#IF 下多个检测命令为全真;#OR 下多个检测命令为任一真。
7. 对话输出语法
#SAY 和 #ELSESAY 输出 NPC 对话文本。标签段中没有 #ACT/#IF 的普通文本也常作为对话文本使用。
dialogue = { text-fragment | variable-substitution | link | image-tag | color-tag } ;
line-break = "\" ;
link = "<" caption "/" label-ref ">" ;
input-link = "<" caption "/" "@@" input-kind input-id [ "(" title ")" ] ">" ;
image-tag = "<Img:" image-args [ "/" label-ref ] ">"
| "<ImgEx:" image-ex-args [ "/" label-ref ] ">"
| "<PlayImg:" play-image-args [ "/" label-ref ] ">" ;
color-tag = "<" caption "/" color-directive ">"
| caption "{" text "/" color-directive "}" ;7.1 普通链接
<领取奖励/@reward>
<返回/@main>7.2 输入链接
<请输入充值卡卡号/@@InPutString22>
<请输入充值卡卡号/@@InPutString22(请输入充值卡卡号:)>实现约定:
@@InPutString22打开字符串输入框。- 输入结果写入
S22,用<$STR(S22)>读取。 @InPutInteger用法类似,但原文只明确说“类似”。- 输入后触发
[@InPutString22]标签。
7.3 图标和动态图片
<Img:N:F:X:Y/@Label>
<ImgEx:F:U:H:D:X:Y/@Label>
<PlayImg:F:N:C:T:X:Y:M:L/@Label>参数含义按原文:
Img:N图片序号,FWIL 文件序号,X/Y微调坐标。ImgEx:FWIL 文件序号,U默认图,H鼠标悬停图,D鼠标按下图,X/Y坐标。PlayImg:FWIL 文件序号,N开始图片,C张数,T速度毫秒,X/Y坐标,M绘制模式,L播放次数。
7.4 颜色和备注
变量字符颜色{<$USERNAME>/FCOLOR=254}\
<字体颜色/FCOLOR=69>\
<自动变色/AUTOCOLOR=254,251,168,191,250,70,245,249,253>\
<可以触发字段颜色{FCOLOR=250}/@跳转1>\NPC 标签备注示例:
<GOM引擎官方网站|253#GOM引擎官方网站:^254#www.gameofmir.com>
<GOM引擎官方网站|249#GOM引擎官方网站:^250#www.gameofmir.com/@打开>8. 变量系统
GOM 有逻辑标志、固定变量、扩展变量、自定义变量和替换变量。
8.1 逻辑标志 [n]
SET [999] 1 属于逻辑标志机制,不是 P/N/S 这类普通变量。原文把 [n] 定义为 1-1024 的逻辑变量,初始值是 0;SET [n] 1 把它打开,Check [n] 1 检测它是否已打开,reset 用来批量清零。
来源:传奇脚本命令详解
[n] ;n为1-1024正整数,是逻辑变量,有0值和1值,初始0值
#IF
条件--执行
#elsesay 或者 #elseact
否定--执行 ;相当于程序中的条件判断
SET [n] 1 ;设置逻辑变量为真按这个定义,SET [999] 1 就是把第 999 号逻辑标志设为真。对应的关闭写法是 SET [999] 0;原文在 reset 展开里给出过 set [100] 0 这类清零行,因此这里把 999 看作同一参数位的替换。
来源:传奇脚本命令详解
reset [XXX] 7 意思是将从XXX开始的7个变量回复到原始值0
比如:reset [100] 7 就是把100 101 102 103 104 105 106 107这7个变量赋值为0。
它等同与:set [100] 0
set [101] 0
set [102] 0
set [103] 0
set [104] 0
set [105] 0
set [106] 0注意:原文文字说 reset [100] 7 覆盖 100...107,但后面的展开只列到 set [106] 0。这里保留原文,不强行修正;精确包含几个编号需要实测。
地图参数里的 NEEDSET_ON(001)、NEEDSET_OFF(001) 也使用同类人物标志语义:满足或未满足指定标志时才允许进入地图。
8.2 固定变量
| 范围 | 类型 | 作用域/生命周期 |
|---|---|---|
P0-P99 | 数字 | 私人变量,打开/关闭对话框重置 |
D0-D99 | 数字 | 私人变量,下线不保存 |
M0-M99 | 数字 | 私人变量,下线不保存,切换地图清空 |
N0-N99 | 数字 | 私人变量,下线不保存,小退归 0 |
S0-S99 | 字符 | 私人变量,下线不保存,小退清空 |
I0-I99 | 数字 | 全局变量,下线不保存,小退归 0 |
G0-G499 | 数字 | 全局变量,可保存,存放于 Mir200/GlobalVal.ini |
A0-A499 | 字符 | 全局变量,可保存,存放于 Mir200/GlobalVal.ini |
U0-U49 | 数字 | 私人变量,可保存,存放于人物数据库 |
T0-T49 | 字符 | 私人变量,可保存,存放于人物数据库 |
自定义变量名不要以 P/D/M/N/S/I/G/A 开头。
8.3 扩展 S$ 和 N$
MOV S$我的人物名称 <$USERNAME>
SENDMSG 0 <$STR(S$我的人物名称)> 255 253
MOV N$我的杀怪总数 100
INC <$STR(N$我的当前杀怪数)> 1S1 和 S$1 是两个不同变量。
8.4 变量操作命令
基础变量操作:
MOV 变量 值
INC 变量 数值
DEC 变量 值
SUM 变量A 变量B
MOVR 变量 最大值
MOVR 变量 最小值 最大值字符删除示例:
MOV S1 GameOfMir引擎M2
DEC S1 GameOfMir引擎
DEC S2 1 3自定义变量示例:
VAR Integer HUMAN QQQQ
CALCVAR HUMAN QQQQ + 5
CHECKVAR HUMAN QQQQ > 5
LOADVAR HUMAN QQQQ VarSave.txt
SAVEVAR HUMAN QQQQ VarSave.txt
<$HUMAN(QQQQ)>8.5 替换变量
substitution = "<$" substitution-body ">" ;常见形态:
<$USERNAME>
<$LEVEL>
<$GAMEGOLD>
<$STR(P0)>
<$STR(S22)>
<$HUMAN(QQQQ)>
<$PARAM(0)><$STR(变量)> 把脚本变量转成可显示文本。
<$PARAM(0)> 到 <$PARAM(6)> 用于读取用户自定义命令输入参数。
8.6 特殊占位符
部分命令和系统回调使用 % 占位符。
SENDMSG 5 %s信息内容%d
SendMsg 5 缺少合成物品<%Item>
CheckFoundryItem %FoundryItem
GiveFoundryItem %FoundryItem原文明确:
%s代表人物名称。%d代表 NPC 名称。%Item、%FoundryItem在合成系统回调中由 M2 自动转换。
9. 多级脚本目标前缀
多级脚本允许“对另一个对象执行检测或动作”。
target = "H" | "O" | "M" | "P" | character-name | variable ;
target-command = target "." command-name { argument } ;固定前缀:
| 前缀 | 含义 |
|---|---|
H. | 英雄 |
O. | 主人 |
M. | 怪物 |
P. | 对面的角色 |
示例:
#IF
H.CheckLevelEx > 1
#SAY
你的英雄大于1级
#IF
P.CheckLevelEx > 1
#SAY
你的对面的角色大于1级
#ACT
M.HumanHP + 100也可以用人物名或变量:
#IF
翎风网络.CheckLevel 51
CheckLevel 51
#ACT
翎风网络.GameGold + 10
GameGold + 10MOVEX 用于多级脚本中的变量数据传递:
MOV S1 神话
S1.MOVEX S2 <$USERNAME>原文说明:如果目标人物不在线,M2 会提示出错,但脚本继续执行并跳过该句。
10. 包含和常量定义
GOM 支持把常量定义放到 Envir\Defines 下,再在脚本中包含。
定义文件形态:
#Define
按钮位置(确定按钮X坐标)
500
#Define
按钮位置(确定按钮Y坐标)
300脚本使用:
[@main]
#INCLUDE 定义的配置.ini
#IF
#ACT
MOV S1 按钮位置(确定按钮X坐标)
MOV S2 按钮位置(确定按钮Y坐标)
SENDMSG 5 按钮X坐标为:<$STR(S1)>,按钮Y坐标为:<$STR(S2)>规约:
define-file = { "#Define" newline define-name newline define-value } ;
include = "#INCLUDE" path ;11. 机器人脚本
机器人脚本使用额外的自动运行声明。
Robot.txt:
;机器人名称 脚本名称
系统控制 AutoRunRobotAutoRunRobot.txt:
#AutoRun NPC SEC 8 @DHB规约:
autorun = "#AutoRun" "NPC" interval-kind interval-value label-ref ;
interval-kind = "SEC" | "MIN" | "HOUR" | "DAY" | "RUNONWEEK" ;示例:
#AutoRun NPC RUNONWEEK 5:15:55 @SendRedMsg12. 外部文件格式
这些不是 NPC 脚本本体,但常和脚本一起使用,应作为参考书附录处理。
12.1 爆率文件
位置:
\Mir200\Envir\MonItems\传统格式:
drop-line = odds item-name [ quantity ] ;
odds = integer "/" integer ;示例:
1/1 万年雪霜
1/1 金币 10000新格式:
child-block = "#CHILD" odds [ "RANDOM" ] newline
"(" newline
{ drop-line | child-block }
")" ;示例:
#CHILD 1/1 RANDOM
(
1/1 圣殿之刃
1/1 圣殿宝甲
)RANDOM 表示从括号内随机抽取一件,括号内爆率设置不再按各自行概率生效。
12.2 地图参数
地图参数在地图配置行中按标志出现。
map-param = param-name [ "(" param-args ")" ] ;
param-args = text ;示例:
CHECKQUEST(Q001)
NEEDSET_ON(001)
MUSIC(Wav\bg.mp3)
EXPRATE(100)
PKWINLEVEL(1)
DECHP(1/10)
FLAME(45:82:50|43:84:50)完整参数表见命令索引中的“地图参数表”。
13. 规范写法建议
为了让脚本可读、可被工具解析,建议统一成下面风格:
[@Label]
#IF
CHECKCOMMAND 参数
#ACT
COMMAND 参数
#ELSEACT
COMMAND 参数规则:
- 标签独占一行。
#IF/#OR/#ACT/#SAY/#ELSEACT/#ELSESAY独占一行。- 每个检测命令独占一行。
- 每个动作命令独占一行。
- 注释使用
;。 - 跳转统一写
GOTO @Label,不要把GOTO和标签拆成两行。 - 需要显示变量时统一使用
<$STR(...)>或对应替换变量。 - 命令名统一大写;变量按原引擎形式保留。
14. 待实测点
以下点原文没有给严格定义,写解析器或校验器时不要贸然定死:
//是否在所有脚本位置都被引擎当作注释。- 标签名允许的完整字符集,以及标签名中空格的精确处理。
- 命令参数跨行是否由引擎支持,还是原 CHM 排版导致的换行。
- 文本参数中空格的保留规则。
#CALL的完整语法;原文只在“配置定义”里提到,没有给系统说明。@InPutInteger的变量写入规则;原文只说和@@InPutString类似。
这些需要用最小脚本在 M2 上跑出来,再把结果回填到本规约。