传奇NPC商店脚本相关设置从商品配置到买卖货币回收全流程详细解析说明

来源: 作者: 点击:
商店是传奇版本里玩家接触频率最高的NPC功能,药品、书页、传送卷、杂货全部靠它流通。设置不合理会出现两个极端,要么价格太低被工作室搬空,要么商品与数据库对不上点开就报错。下面把商店脚本的文件位置、登记方式、商品配置、买卖逻辑、限购与库存控制整套讲透,代码按主流引擎通用写法给出,差异点单独标注。

商店脚本放在哪、怎么登记

所有NPC脚本文件统一放在 Mir200\Envir\Market_Def\ 目录下,商店脚本也不例外,文件名建议直接跟着NPC名字走,例如“杂货商人-3.txt”。文件名里那个“-3”是脚本编号后缀,用来区分同名的多个NPC,编号重复会导致其中一个加载失败。

登记NPC打开 Mir200\Envir\MerChant.txt,在文件末尾按行列格式追加一行。以常见的格式为例,依次填写地图编号、坐标X、坐标Y、NPC名称、外观代码、是否显示名字、脚本文件路径。外观代码决定NPC长什么样,普通商人用60,药店用61,武器店用62,具体可用的外观代码以引擎自带的外观列表为准。脚本文件路径不写后缀,引擎会自动去Market_Def目录下找同名txt。登记完成后不要只改文件,必须到M2控制台执行重新加载NPC,否则新NPC不会出现在地图上。

有的版本用的是Merchant.txt而不是MerChant.txt,两者只差大小写,实际以自己服务端目录里存在的那个文件为准,两个都改反而可能引发重复登记。

两条实现路线要先分清楚

第一条是引擎内置商人系统。NPC在登记时被标记为商人类型,商品清单一类数据交给引擎管理,脚本里只需要用跳转命令调起买卖界面,或者在M2控制台的商店管理里直接编辑商品条目,每条包含物品名、货币类型、单价。这条路线省事,界面是引擎自带的,样式统一,改价格不用动脚本。

第二条是自定义脚本商店。不依赖引擎的商人标记,用GIVE、CHECKITEM、CHECKGAMEGOLD这些通用命令自己搭买卖逻辑。好处是限购、等级门槛、活动折扣、动态库存全都能塞进去,坏处是每条商品都要自己写检测和兜底。版本里商店功能需求复杂时走这条。

内置商人系统的配置要点

商品数据不要凭记忆手填,从 Items 数据库里把物品名整段复制过来。物品名对不上是内置商店最常见的故障,表现是商店列表里商品显示正常,玩家点击购买后背包没东西,元宝却照扣,属于典型的赔钱bug。

货币类型这一项要分清。部分引擎一个商品只能绑定一种货币,想同时支持金币和元宝购买,就得把同一件物品配两条记录,分别绑定两种货币。价格字段只填数字,不要带单位,单位由货币类型字段决定。

商品排序按配置顺序显示,把消耗频率最高的药品放在前面,冷门杂货放后面。列表过长时用分页或者分类跳转,不要指望玩家自己滚动找。内置商店一般不支持分类,需要通过多个商人NPC拆分,药品放药店、杂货放杂货店,分工清楚。

调价时改的是商店配置不是脚本。很多人改了脚本里的价格,发现游戏里没变化,就是因为内置商人的价格根本不读脚本,读的是商店配置。改完商店配置同样需要重载才生效。

自定义脚本商店的完整代码

下面这套脚本包含分类菜单、单件购买、批量购买、金币和元宝双货币、出售回收、限购提示,可以直接改物品名和价格使用。

text
;文件位置:Mir200\Envir\Market_Def\杂货商人-3.txt
;保存编码必须是 ANSI,UTF-8 会导致中文物品名乱码

[@MAIN]
#SAY
杂货铺,明码标价,买多买少都行。\ \
<消耗品/@消耗品> <书页/@书页>\
<杂货/@杂货> <出售物品/@出售>\
<离开/@EXIT>

;---------- 消耗品分类 ----------
[@消耗品]
#SAY
消耗品区\ \
<金创药(小量) 10金币/@买金创小 1>\
<金创药(中量) 30金币/@买金创中 1>\
<太阳水 50金币/@买太阳水 1>\
<返回/@MAIN> <离开/@EXIT>

[@买金创小]
#IF
CHECKGOLD > 9
#ACT
GOLD - 10
GIVE 金创药(小量) 1
SENDMSG 6 购买成功,金创药(小量)已经放进背包。
GOTO @消耗品
#ELSEACT
SENDMSG 5 金币不足,无法购买。
GOTO @消耗品

[@买金创中]
#IF
CHECKGOLD > 29
CHECKBAGSIZE > 0
#ACT
GOLD - 30
GIVE 金创药(中量) 1
SENDMSG 6 购买成功,金创药(中量)已经放进背包。
GOTO @消耗品
#ELSEACT
SENDMSG 5 金币不足或者背包已满,无法购买。
GOTO @消耗品

[@买太阳水]
#IF
CHECKGOLD > 49
CHECKBAGSIZE > 0
#ACT
GOLD - 50
GIVE 太阳水 1
SENDMSG 6 购买成功,太阳水已经放进背包。
GOTO @消耗品
#ELSEACT
SENDMSG 5 金币不足或者背包已满,无法购买。
GOTO @消耗品

;---------- 书页区,元宝计价 ----------
[@书页]
#SAY
技能书页专区,元宝结算\ \
<武学书页 100元宝/@买书页 1>\
<高级书页 500元宝/@买高级书页 1>\
<返回/@MAIN> <离开/@EXIT>

[@买书页]
#IF
CHECKGAMEGOLD > 99
CHECKBAGSIZE > 0
#ACT
GAMEGOLD - 100
GIVE 武学书页 1
SENDMSG 6 购买成功,武学书页已经放进背包。
GOTO @书页
#ELSEACT
SENDMSG 5 元宝不足或者背包已满,无法购买。
GOTO @书页

[@买高级书页]
#IF
CHECKGAMEGOLD > 499
CHECKBAGSIZE > 0
#ACT
GAMEGOLD - 500
GIVE 高级书页 1
SENDMSG 6 购买成功,高级书页已经放进背包。
GOTO @书页
#ELSEACT
SENDMSG 5 元宝不足或者背包已满,无法购买。
GOTO @书页

;---------- 杂货区 ----------
[@杂货]
#SAY
杂货区\ \
<随机传送卷 20金币/@买随机卷 1>\
<回城卷 30金币/@买回城卷 1>\
<返回/@MAIN> <离开/@EXIT>

[@买随机卷]
#IF
CHECKGOLD > 19
CHECKBAGSIZE > 0
#ACT
GOLD - 20
GIVE 随机传送卷 1
SENDMSG 6 购买成功,随机传送卷已经放进背包。
GOTO @杂货
#ELSEACT
SENDMSG 5 金币不足或者背包已满,无法购买。
GOTO @杂货

[@买回城卷]
#IF
CHECKGOLD > 29
CHECKBAGSIZE > 0
#ACT
GOLD - 30
GIVE 回城卷 1
SENDMSG 6 购买成功,回城卷已经放进背包。
GOTO @杂货
#ELSEACT
SENDMSG 5 金币不足或者背包已满,无法购买。
GOTO @杂货

;---------- 出售回收 ----------
[@出售]
#SAY
本店回收以下物品,价格童叟无欺。\ \
<出售金创药(小量) 每瓶5金币/@卖金创小>\
<出售太阳水 每瓶25金币/@卖太阳水>\
<返回/@MAIN> <离开/@EXIT>

[@卖金创小]
#IF
CHECKITEM 金创药(小量) 1
#ACT
TAKE 金创药(小量) 1
GOLD + 5
SENDMSG 6 出售成功,你获得5金币。
GOTO @出售
#ELSEACT
SENDMSG 5 背包里没有金创药(小量)。
GOTO @出售

[@卖太阳水]
#IF
CHECKITEM 太阳水 1
#ACT
TAKE 太阳水 1
GOLD + 25
SENDMSG 6 出售成功,你获得25金币。
GOTO @出售
#ELSEACT
SENDMSG 5 背包里没有太阳水。
GOTO @出售


几个容易踩的坑先说清楚。CHECKGOLD > 9 这种写法对应的是“持有10金币及以上”,用大于而非大于等于,是因为部分引擎在数值刚好相等时判定会出偏差。所有给物品的分支都要带 CHECKBAGSIZE > 0,漏了这条会出现货币扣了东西塞不进背包的情况。GOTO 的跳转目标必须是脚本里真实存在的段名,拼错一个字就是点击无反应。物品名里的括号、空格、特殊符号要和数据库完全一致,写完从数据库复制一遍核对。

批量购买与输入数量

固定档位满足不了消耗需求时,用输入框让玩家自己填数量。用 GETSTRING 拉取玩家输入的数字存进变量,再用 CALC 算出总消耗和总数量,最后一次性扣除和发放。

text
[@买金创小输入]
#SAY
请输入要购买的数量,最多50瓶。\ \
<确认购买/@确认买金创小>\
<返回/@消耗品>

[@确认买金创小]
#ACT
GETSTRING 1 请输入购买数量 1 50
CALC N$购买量 = $STR(S0)
CALC N$总价 = N$购买量 * 10
#IF
CHECKGOLD > N$总价
CHECKBAGSIZE > N$购买量
#ACT
GOLD - N$总价
GIVE 金创药(小量) N$购买量
SENDMSG 6 购买成功,你获得了N$购买量瓶金创药(小量)。
GOTO @消耗品
#ELSEACT
SENDMSG 5 金币不足或者背包空位不够,无法购买。
GOTO @消耗品


用输入框时一定要设上下限。上限卡死在50到100之间,防止有人输入一个超大数字触发算术溢出,或者瞬间买满背包。下限设1,避免输入0或负数造成反向刷货币。

定价思路

商店定价直接决定经济系统的走向。药品、传送卷这类消耗品要便宜,让玩家随手能买,价格压到打几只怪就能回本的水平。技能书、高级材料这类成长性物资要有明显门槛,用元宝计价,逼出充值需求。回收价永远低于出售价,中间的差价就是回收系统的利润缓冲。差价比通常留百分之二十到四十,太低会被反复低买高卖套利,太高玩家不愿意卖东西给NPC,仓库里堆满垃圾。

同一件物品不要在多处NPC设置不同价格,否则玩家会挑最便宜的那个,贵的那个形同虚设。要搞活动折扣,就在同一个NPC脚本里用时间判断开关价格档位,不要另起一个低价NPC。

限购、限时与库存控制

开放限量商品时用个人变量记录购买次数。在 QManage.txt 的登录触发段里先声明变量并加载存档,脚本里购买前用 CHECK 判断次数是否超限,购买后用 CALC 累加次数,同时把变量写回存档。

text
;登录触发段
[@Login]
#ACT
VAR Integer HUMAN 今日购买书页
LOADVAR HUMAN 今日购买书页 ..\QuestDiary\数据文件\商店限购.txt


购买段里加一条 CHECK HUMAN 今日购买书页 < 5,执行购买时加 CALC HUMAN 今日购买书页 + 1,最后用 SAVEVAR 把变量存回文件。每日零点由 Robot.txt 的定时任务统一清零,不写这段清零逻辑,限购会变成永久限购,玩家第二天也买不了。

限时商品分两种做法,一种是按整点时段开关,用引擎的时间判断命令在不同时段跳转到不同脚本段,白天正常价、晚间活动价。另一种是倒计时上架,活动开启时给商品加标记,活动结束由定时任务撤销商品条目,操作复杂,脚本量大的版本才值得做。

动态库存适合拍卖性质的稀有商品,库存数量存进全局变量,每售出一件减一,归零后脚本跳转到“已售罄”提示段。库存变量要加锁防止并发,多个玩家同时购买时先检测再扣减,中间不要插入其他检测命令,否则会出现超卖。

各引擎的差异

GOM和GEE对上面这套命令的兼容性最好,CHECKGOLD、CHECKGAMEGOLD、GIVE、TAKE、CHECKBAGSIZE全部通用,脚本可以照搬。

翎风引擎的物品检测命令参数顺序有细微差别,导入前先用单条命令测试。带通配符的批量回收能按品质或等级筛选,批量出售场景可以少写很多行。

HGE引擎的元宝显示变量和GOM不同,涉及元宝显示的提示文字要替换对应变量,否则界面上元宝数会显示为空白。

996三端的个人变量前缀是 N$ 格式,登录时直接赋初始值,不需要预先声明 VAR,变量加载命令也简化了。它支持 txt 和 lua 双写,逻辑简单的商店用 txt 就够,涉及数组、循环、随机权重的复杂商店建议用 lua,txt 里实现起来很别扭。

Blue引擎的商店调用命令和GOM不同,脚本入口的跳转段要按该引擎的命令表改写。

上线前的测试清单

物品名逐条和数据库核对,中英文括号、空格、符号一个都不能差。把商店里所有商品名复制到 Items 表里搜一遍,搜不到的立刻改。

每个按钮的跳转段名都在脚本里存在。把所有 [@xxx] 段名列一遍,再对照所有跳转目标核对,缺哪个补哪个。

异常场景逐个点。背包塞满时买药品、金币不足时买书页、元宝不足时买高级材料、背包里没有对应物品时点出售,四种情况都要给出明确提示,且不能出现扣钱不给东西。

批量购买输入框测试边界值,输入0、输入负数、输入超过上限的数字,看脚本是否都能正确拦截。

限购功能测试跨日重置,改系统时间到第二天零点,确认限购次数归零。改完脚本重启M2或者重载NPC,只改文件不重载等于没改。

故障排查顺序

点NPC没反应,先查MerChant.txt里的脚本路径和实际文件名是否一致,再查脚本文件是否放在正确的目录,最后重载NPC。

购买后物品没进背包但钱扣了,检查是否漏了 CHECKBAGSIZE 检测,以及物品名是否和数据库一致。

出售点不动,检查 CHECKITEM 后面跟的物品名和数量,数量写0或者不写都会判定失败。

中文全是乱码,脚本编码不是ANSI,用记事本另存为ANSI编码重新保存。

限购不生效,检查变量有没有正确 LOADVAR 和 SAVEVAR,存储文件夹是否真实存在,路径不存在时加载和保存都会静默失败。

零点了限购没清零,检查 Robot.txt 里的定时任务时间和清空脚本路径,脚本路径写错不会报错,但清空动作不会执行。

按这套配置走下来,商店脚本基本能稳定运行。真正决定商店好不好用的不是代码写得多花哨,而是价格体系合不合理、物品名有没有核对干净、异常场景有没有全部覆盖。这三件事做到位,商店不会出大问题。