GOM 脚本语言参考
语言指南

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/#ActSENDMSG/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. 顶层语法

先把 # 开头的行分层看,会比只按命令名背容易得多:

  1. 标签层:[@main][@InputString1] 这类入口。
  2. 条件层:#IF#OR 开始一个条件组。
  3. 结果块层:#ACT/#SAY 是条件成功时执行的块,#ELSEACT/#ELSESAY 是条件失败时执行的块。
  4. 行内容层:检测命令、动作命令、对话文本、跳转链接都在这些块里面。

所以 #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-sectionfalse-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 S1

4.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 条件组。它们按顺序执行,除非遇到 BREAKGOTOCLOSE 等改变流程的命令。

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 图片序号,F WIL 文件序号,X/Y 微调坐标。
  • ImgEx: F WIL 文件序号,U 默认图,H 鼠标悬停图,D 鼠标按下图,X/Y 坐标。
  • PlayImg: F WIL 文件序号,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 的逻辑变量,初始值是 0SET [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$我的当前杀怪数)> 1

S1S$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 + 10

MOVEX 用于多级脚本中的变量数据传递:

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

;机器人名称 脚本名称
系统控制 AutoRunRobot

AutoRunRobot.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 @SendRedMsg

12. 外部文件格式

这些不是 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 上跑出来,再把结果回填到本规约。

On this page