脚本文件从建立那一刻起就要按固定规则命名。QManage.txt只放登录触发,QFunction-0.txt只放功能触发,Robot.txt只放定时器,MonItems目录只放爆率文件。不要把登录脚本、NPC对话、定时器逻辑全部塞进同一个文件,否则后期排查漏洞时连触发入口都找不到。每个文件顶部用分号写清楚用途、修改日期、负责人,改动一行就更新一行记录。
变量声明必须集中管理。在QManage.txt的[@Login]段中把所有需要持久化的HUMAN变量用VAR声明一遍,然后用LOADVAR读取存档,读取失败时用MOV赋默认值。不要等到脚本执行到一半才临时声明变量,引擎不会报错,但变量值可能是0或空字符串,后续判断全部走错分支。变量命名加前缀,N_开头表示数值,S_开头表示字符串,P_开头表示临时变量,H_开头表示英雄变量。看到变量名就知道类型和用途,减少误用。
条件判断必须完整闭合。每个#IF后面至少跟一个#ACT或#SAY,不要出现只有#IF没有执行段的空判断。多个条件用#AND连接时,注意每个条件单独占一行,不要把两个检测命令写在同一行。CHECKLEVELEX和CHECKITEM这类检测命令如果写错参数顺序,判断结果会完全相反。写完一段判断后,用两个不同状态的账号分别测试真分支和假分支,确认两条路径都按预期执行。
GOTO跳转是漏洞高发区。每次写GOTO之前,先确认目标段存在,再确认目标段末尾有Break。如果目标段末尾没有Break,脚本执行到末尾后会自动回到上一段继续执行,形成隐式循环。跨文件跳转更要谨慎,A文件的[@Main]跳到B文件的[@Process],B文件的[@Process]处理完必须Break,不能跳回A文件。跳转关系超过三层的脚本,画一张跳转图,确认没有环。
触发字段要防重入。@Login、@HumDropItem、@Attack、@OnPlayerAttackMon这些触发在特定动作发生时被引擎调用,如果脚本中执行了会再次触发同一字段的操作,就会形成循环调用。典型错误是在@HumDropItem中执行GIVE命令给玩家补发物品,玩家再次死亡时物品再次掉落,再次触发@HumDropItem,再次GIVE。正确做法是在@HumDropItem中只执行StopDropItem阻止掉落,补偿物品放到其他触发段或延时触发中发放。
物品操作先检测后执行。GIVE之前用CHECKBAGSPACE检测背包空格,TAKE之前用CHECKITEM检测物品数量。不要假设玩家背包一定有空格,也不要假设玩家身上一定有足够的物品。TAKE命令在物品绑定、装备中、负重满时可能不生效,如果后续逻辑依赖TAKE成功的结果,必须先检测再执行,执行后再检测一次确认扣除成功。WHILE循环中做TAKE操作时,必须加计数器,循环次数超过设定上限时强制BREAK,防止TAKE不生效导致死循环。
权限检测不能只写在登录脚本里。每个NPC功能、每个命令入口、每个后台操作都要单独做权限判断。ISADMIN只在登录触发中可靠,其他脚本中用CHECKADMIN或CHECKADMINLIST。不要依赖玩家不会发现隐藏NPC,隐藏地图的NPC同样会被玩家用命令探测到。后台管理地图的传送命令后面必须跟权限检测,没有权限的账号执行MAPMOVE时应该被拦截并记录日志。
数据库修改前先备份。StdItems.DB、Monster.DB、MapInfo.txt、String.ini这些文件每次改动前复制一份到备份目录,文件名加日期和改动说明。改完数据库后在M2Server中执行重载操作,不要只保存文件。只保存不重载,游戏内数据不会变化,测试时以为改动无效,反复修改反而引入新错误。重载后进游戏实际验证,不要只看M2控制台的提示。
脚本注释要写清楚为什么这么做。分号后面的注释不要只写“设置变量”,要写“设置转生次数为0,防止新角色读取到旧存档的残留值”。三个月后回头看脚本,没有注释的代码和乱码没有区别。注释中记录修改原因,比如“2024年3月修改,修复攻沙期间装备掉落异常”,出问题时能快速定位改动范围。
测试环境要和正式环境隔离。本地开一个测试服务端,用测试账号跑所有脚本分支。测试账号至少准备三个:普通玩家、低级管理员、高级管理员。每个NPC功能用三种账号分别点一遍,确认权限控制正确。物品操作测试要覆盖背包满、物品绑定、装备中、负重超限四种状态。传送测试要覆盖地图不存在、坐标无效、目标地图禁止传送三种异常。
防刷物品要从脚本层面拦截。所有GIVE命令后面加CHECKBAGSPACE判断,背包满时不给物品并提示。所有兑换功能先TAKE后GIVE,TAKE成功才执行GIVE,TAKE失败直接BREAK。玩家死亡掉落触发中不要用GIVE补物品。NPC回收功能要检测物品是否绑定,绑定物品不允许回收,防止玩家把绑定装备回收成可交易物品。
防复制装备要设置绑定状态。关键装备在获取时就用SetItemState设置禁止扔、禁止交易、禁止存、禁止爆出。四个状态全部设置,只设其中一个仍然有漏洞。禁止交易但允许扔,玩家可以扔到地上让其他号捡;禁止扔但允许存,玩家可以存仓库后换号取。四个状态同时设置,装备才真正锁定在角色身上。
防越权操作要在每个命令入口加检测。玩家执行后台命令时,先判断权限等级,再判断目标玩家是否存在,再判断操作是否超出权限范围。低级管理员不能修改高级管理员的权限,不能给自己发高级装备,不能查看其他管理员的登录记录。权限判断失败时记录日志,日志中写入账号、角色名、时间、尝试执行的操作,方便后续审计。
定时器不要互相触发。Robot.txt中每个定时器的执行条件独立判断,不要用A定时器修改变量、B定时器检测变量变化再修改另一个变量、另一个变量又触发A定时器。多个定时器通过变量互相依赖,执行频率会远高于设定间隔,M2Server负载持续偏高。需要多个定时器协作时,在定时器开头用GETGAMETIME获取当前时间戳,与上次执行时间比较,间隔不足时直接跳过。
脚本长度超过两百行的功能要拆分。一个NPC脚本包含对话、检测、扣除、发放、传送、日志六个环节,全部写在一个[@Main]段中,后期改一个环节容易误伤其他环节。拆成[@Main]只做入口判断,[@Check]做条件检测,[@Do]做执行操作,[@Log]做日志记录,每个段职责单一,跳转关系清晰。
上线前跑一遍检查清单。所有GOTO目标段存在且末尾有Break。所有变量在[@Login]中已初始化。所有物品操作前有检测。所有权限入口有校验。所有WHILE循环有计数器。所有触发字段没有循环调用。所有数据库改动已备份并重载。所有NPC功能用三种权限账号测试通过。所有物品操作覆盖四种异常状态。所有传送命令检测地图和坐标有效性。清单逐项打勾后再开服,漏洞数量会下降一个数量级。
变量声明必须集中管理。在QManage.txt的[@Login]段中把所有需要持久化的HUMAN变量用VAR声明一遍,然后用LOADVAR读取存档,读取失败时用MOV赋默认值。不要等到脚本执行到一半才临时声明变量,引擎不会报错,但变量值可能是0或空字符串,后续判断全部走错分支。变量命名加前缀,N_开头表示数值,S_开头表示字符串,P_开头表示临时变量,H_开头表示英雄变量。看到变量名就知道类型和用途,减少误用。
条件判断必须完整闭合。每个#IF后面至少跟一个#ACT或#SAY,不要出现只有#IF没有执行段的空判断。多个条件用#AND连接时,注意每个条件单独占一行,不要把两个检测命令写在同一行。CHECKLEVELEX和CHECKITEM这类检测命令如果写错参数顺序,判断结果会完全相反。写完一段判断后,用两个不同状态的账号分别测试真分支和假分支,确认两条路径都按预期执行。
GOTO跳转是漏洞高发区。每次写GOTO之前,先确认目标段存在,再确认目标段末尾有Break。如果目标段末尾没有Break,脚本执行到末尾后会自动回到上一段继续执行,形成隐式循环。跨文件跳转更要谨慎,A文件的[@Main]跳到B文件的[@Process],B文件的[@Process]处理完必须Break,不能跳回A文件。跳转关系超过三层的脚本,画一张跳转图,确认没有环。
触发字段要防重入。@Login、@HumDropItem、@Attack、@OnPlayerAttackMon这些触发在特定动作发生时被引擎调用,如果脚本中执行了会再次触发同一字段的操作,就会形成循环调用。典型错误是在@HumDropItem中执行GIVE命令给玩家补发物品,玩家再次死亡时物品再次掉落,再次触发@HumDropItem,再次GIVE。正确做法是在@HumDropItem中只执行StopDropItem阻止掉落,补偿物品放到其他触发段或延时触发中发放。
物品操作先检测后执行。GIVE之前用CHECKBAGSPACE检测背包空格,TAKE之前用CHECKITEM检测物品数量。不要假设玩家背包一定有空格,也不要假设玩家身上一定有足够的物品。TAKE命令在物品绑定、装备中、负重满时可能不生效,如果后续逻辑依赖TAKE成功的结果,必须先检测再执行,执行后再检测一次确认扣除成功。WHILE循环中做TAKE操作时,必须加计数器,循环次数超过设定上限时强制BREAK,防止TAKE不生效导致死循环。
权限检测不能只写在登录脚本里。每个NPC功能、每个命令入口、每个后台操作都要单独做权限判断。ISADMIN只在登录触发中可靠,其他脚本中用CHECKADMIN或CHECKADMINLIST。不要依赖玩家不会发现隐藏NPC,隐藏地图的NPC同样会被玩家用命令探测到。后台管理地图的传送命令后面必须跟权限检测,没有权限的账号执行MAPMOVE时应该被拦截并记录日志。
数据库修改前先备份。StdItems.DB、Monster.DB、MapInfo.txt、String.ini这些文件每次改动前复制一份到备份目录,文件名加日期和改动说明。改完数据库后在M2Server中执行重载操作,不要只保存文件。只保存不重载,游戏内数据不会变化,测试时以为改动无效,反复修改反而引入新错误。重载后进游戏实际验证,不要只看M2控制台的提示。
脚本注释要写清楚为什么这么做。分号后面的注释不要只写“设置变量”,要写“设置转生次数为0,防止新角色读取到旧存档的残留值”。三个月后回头看脚本,没有注释的代码和乱码没有区别。注释中记录修改原因,比如“2024年3月修改,修复攻沙期间装备掉落异常”,出问题时能快速定位改动范围。
测试环境要和正式环境隔离。本地开一个测试服务端,用测试账号跑所有脚本分支。测试账号至少准备三个:普通玩家、低级管理员、高级管理员。每个NPC功能用三种账号分别点一遍,确认权限控制正确。物品操作测试要覆盖背包满、物品绑定、装备中、负重超限四种状态。传送测试要覆盖地图不存在、坐标无效、目标地图禁止传送三种异常。
防刷物品要从脚本层面拦截。所有GIVE命令后面加CHECKBAGSPACE判断,背包满时不给物品并提示。所有兑换功能先TAKE后GIVE,TAKE成功才执行GIVE,TAKE失败直接BREAK。玩家死亡掉落触发中不要用GIVE补物品。NPC回收功能要检测物品是否绑定,绑定物品不允许回收,防止玩家把绑定装备回收成可交易物品。
防复制装备要设置绑定状态。关键装备在获取时就用SetItemState设置禁止扔、禁止交易、禁止存、禁止爆出。四个状态全部设置,只设其中一个仍然有漏洞。禁止交易但允许扔,玩家可以扔到地上让其他号捡;禁止扔但允许存,玩家可以存仓库后换号取。四个状态同时设置,装备才真正锁定在角色身上。
防越权操作要在每个命令入口加检测。玩家执行后台命令时,先判断权限等级,再判断目标玩家是否存在,再判断操作是否超出权限范围。低级管理员不能修改高级管理员的权限,不能给自己发高级装备,不能查看其他管理员的登录记录。权限判断失败时记录日志,日志中写入账号、角色名、时间、尝试执行的操作,方便后续审计。
定时器不要互相触发。Robot.txt中每个定时器的执行条件独立判断,不要用A定时器修改变量、B定时器检测变量变化再修改另一个变量、另一个变量又触发A定时器。多个定时器通过变量互相依赖,执行频率会远高于设定间隔,M2Server负载持续偏高。需要多个定时器协作时,在定时器开头用GETGAMETIME获取当前时间戳,与上次执行时间比较,间隔不足时直接跳过。
脚本长度超过两百行的功能要拆分。一个NPC脚本包含对话、检测、扣除、发放、传送、日志六个环节,全部写在一个[@Main]段中,后期改一个环节容易误伤其他环节。拆成[@Main]只做入口判断,[@Check]做条件检测,[@Do]做执行操作,[@Log]做日志记录,每个段职责单一,跳转关系清晰。
上线前跑一遍检查清单。所有GOTO目标段存在且末尾有Break。所有变量在[@Login]中已初始化。所有物品操作前有检测。所有权限入口有校验。所有WHILE循环有计数器。所有触发字段没有循环调用。所有数据库改动已备份并重载。所有NPC功能用三种权限账号测试通过。所有物品操作覆盖四种异常状态。所有传送命令检测地图和坐标有效性。清单逐项打勾后再开服,漏洞数量会下降一个数量级。

