传奇版本脚本频繁死循环排查:GOTO递归与QFunction触发闭环修复实操

来源: 作者: 点击:
M2控制台弹“脚本死循环”“GOTO超过上限”“ScriptDeadLoop”,伴随CPU占用飙高、点NPC卡住、角色掉线、Mir200\Log\ScriptLog.txt不停追加同一文件名和行号,本质只有一类问题:某段脚本在无终止条件下反复GOTO、#CALL或触发自身,引擎跳出不了执行栈。下面按定位→成因→修法→参数兜底的顺序拆。

一、先锁文件与行号(别瞎翻全部脚本)
1. 看M2Server控制台报错原文,格式通常是:
脚本执行超时:D:\MirServer\Mir200\Envir\QuestDiary\元宝捐献\捐献.txt 第38行 GOTO @Loop
直接按路径用Notepad++打开对应文件,跳到行号。
2. 控制台只写NPC名没写路径:去 Envir\Market_Def 找同名NPC脚本,看里面 #CALL 或 GOTO 引向哪个 QuestDiary 文件,那就是真凶。
3. 连NPC名都没有:死循环九成在你最近新增/改过的文件里,优先翻这四个目录:
• Envir\QuestDiary\ (任务、活动、回收、捐献、转盘)

• Envir\Market_Def\ (NPC对话体)

- Envir\MapQuest_def\ 与 QManage.txt (进图/离线触发)
• Envir\QFunction-0.txt (攻击、获经验、登录、死亡等全局触发)

二、四类高频死循环写法(对照你脚本改)
1. 无条件GOTO跳回自身或互跳
错误示范:
[@Main]
#ACT
SENDMSG 5 欢迎
GOTO @Main
或 [@A]跳[@B]、[@B]又跳[@A],中间没有任何 #IF 阻断。
修法:主菜单别写GOTO,让引擎等玩家点选项;非要循环就加计数器:
#IF LARGE HUMAN LoopCnt 3
#ACT BREAK
#ELSEACT
INC HUMAN LoopCnt 1
GOTO @Main

2. 循环回收/检测不判断退出条件
装备回收脚本常见:
#ACT
TAKE 装备 1
GOTO @Recycle
没先 CHECKITEM 装备 1,背包空了还跳,直接死循环。
修法:循环入口先判定,不满足就 BREAK:
#IF CHECKITEM 装备 1
#ACT TAKE 装备 1
GOTO @Recycle
#ELSEACT BREAK

3. QFunction / 触发事件互锁递归(最隐蔽)
典型:[@GetExp] 里 GOTO @宗派经验,@宗派经验里 AddTextList 写文件被引擎算成“又获得经验”,再触发 [@GetExp],形成“获经验→写文件→获经验”无限递归;或者 [@PlayLevelUp] CALL 回等级事件本身。
修法:
• 严禁在 [@GetExp]、[@PlayLevelUp]、[@OnLogin] 里直接 GOTO 会反触发的段;

• 经验结算改到 RobotManage.txt 定时轮询([@OnTimer30] 每分钟跑一次);

- 或挪到 [@KillMon]、[@OnKillMob] 杀怪触发,避开底层事件;
• 临时止血:在 [@GetExp] 段首加 // 注释或删掉那行 GOTO,重启M2看报错是否消失。

4. 一个 #ACT 下挂多个 GOTO / #CALL 嵌套
GOTO 是绝对跳转,执行第一个后后面全忽略,但若多个 GOTO 分散在不同判定段互相 CALL,容易超引擎默认堆栈(GOM默认30层)。
修法:单段只留一个 GOTO;多段并行用 #IF 分支或 #CALL 独立文件,别链式互CALL;嵌套循环超3层拆文件。

5. 变量不重置导致判定恒真
如 CheckVar Human Flag 0 但后面没 SET Human Flag 1,玩家每次点都进同一段;或 GetRandomText 取值前没 mov 清空,继承上次的空值反复跑。
修法:循环前 mov Var 0 / movS $Temp ,处理完立刻 SET 标记,下次进来走 #ELSEACT BREAK。

三、具体修正片段对照
错误回收段:
[@Recycle]
ACT

TAKE 沃玛装备 1
GIVE 元宝 5
GOTO @Recycle

修正后:
[@Recycle]
IF CHECKITEM 沃玛装备 1

ACT

TAKE 沃玛装备 1
GIVE 元宝 5
INC HUMAN RecCnt 1
GOTO @Recycle
ELSEACT

SENDMSG 6 回收完成,共回收 <$HUMAN(RecCnt)> 件
SET HUMAN RecCnt 0
BREAK

QFunction经验互锁修正:
; 原写法(删掉)
; [@GetExp]
; #ACT
; GOTO @宗派经验
; 新写法:RobotManage.txt
[@OnTimer30]
IF CHECKNAMELIST ..\QuestDiary\宗师系统\宗主名单.txt

ACT

CALCVAR HUMAN ZongMenExp + <$EXP>
BREAK

四、引擎层兜底参数(治标不治本,但先保端不崩)
1. 打开 Mir200\!Setup.txt 或 Setup.txt,找:
ScriptGotoCountLimit=20(或10/300)
改成 5000~50000 之间,保存重启M2,临时抬高跳转阈值不再秒报死循环。
2. M2 → 选项 → 功能设置 → 其它控制 → 脚本循环次数,默认20改50~100测试。
3. 递归深度超限的端(GOM等):M2 → 选项 → 参数设置 → 递归深度 默认50改200。
注意:改参数只是延后爆炸,逻辑闭环不拆照样循环,只是不弹窗而已。

五、标准排查顺序(照做十分钟定位)
1. 备份报错脚本文件到桌面,防止改崩;
2. 看M2/Log\ScriptLog.txt 拿路径行号;
3. 用 // 逐段注释近期改动,重启M2,注释到不报错那段就是源头;
4. 按上面四类成因改:去无条件GOTO、加CHECKITEM/CHECKVAR退出、拆QFunction递归、单#ACT单GOTO;
5. 保存→完全重启M2(别只重启登录器)→进游戏点对应NPC/触发任务验证;
6. 仍报再抬 ScriptGotoCountLimit,但根因必须修脚本。

常见踩坑提醒:Delay 设为0塞进循环、坐标节点误差过大导致来回跑脚本卡死、GlobalVar.ini损坏让检测命令空等、插件(绿盟/945类)与 DelayGoto 冲突,这些表现像死循环,实际是数据或外部依赖异常,查的时候一并看下文件完整性和插件命令兼容。