传奇服务端脚本实现全服清零与任务系统

来源: 作者: 点击:
先说清楚一件事:以下写的是 Legend of Mir 2 系列(LEG/GOM/GEE/HERO 类)服务端的脚本级思路,属于你自己搭建的 单机或授权服务端内部脚本开发,不是外挂、不是客户端注入、不是第三方工具。在自己的服务端里改脚本是正常运维;拿去动别人服务器、做破坏用途一律不沾。

一、全服玩家等级清零:不用 M2 操作,纯脚本自动完成

你的需求拆开是五条硬约束:
1. 不通过 M2 手动操作(不用 GM 界面点)
2. 不走“返回角色 / 退出游戏”流程
3. 活动失败 → 自动触发
4. 全服在线玩家直接清零
5. 身上装备先脱下、再放回背包,不能丢、不能消失

1. 触发入口:活动失败的那一刻

活动一般用 Robot(机器人定时器)或 NPC 事件驱动。假设是活动时间到 / 条件不满足导致失败,在失败逻辑里调用一个统一入口:

QuestDiary 写法(LEG/GOM 通用):

[@活动失败处理]
#CALL [\系统\全服清零.txt] @开始清零

失败判定写在机器人或活动 NPC 里,一旦成立就 #CALL 这个文件,全程不碰 M2。

2. 遍历全服在线玩家

服务端要“拿到所有在线角色对象”,不同引擎 API 不同,但思路一致:先取在线人数 → 按索引取 Human 对象 → 逐个处理。

GOM/GEE 类(支持 Human 对象数组):

[@开始清零]
#IF
#ACT
; 1. 先记录全服角色,避免遍历中对象失效
SetInt($G(清零总数)) = <$ONLINECOUNT>
SetInt($G(清零索引)) = 0

[@清零循环]
#IF
CheckInt($G(清零索引)) < CheckInt($G(清零总数))
#ACT
; 取第 N 个在线玩家对象
GetOnlineHuman <$G(清零索引)> $H(当前玩家)
; 执行单角色清零
#CALL [\系统\单角色清零.txt] @执行
; 索引 +1
CalcInt($G(清零索引)) + 1
; 循环
#GOTO [@清零循环]

#ELSEACT
; 全部处理完,发全服公告
SENDMSG 0 活动失败!全服等级已清零,装备已收回背包。


HERO 引擎(lua 插件写法):
function OnActivityFail()
local players = M2.GetOnlinePlayers()
for _, Human in ipairs(players) do
StripAndReset(Human)
end
M2.SendMsg(0, "活动失败!全服等级已清零,装备已收回背包。")
end

M2.GetOnlinePlayers() 和 M2.SendMsg 按你引擎的实际 API 名替换,HERO 里通常叫 GetAllPlayer / GameServer.SendBroadCastMsg。

3. 单角色清零核心:脱装备 → 放背包 → 清等级

关键顺序不能反:先卸装再清零,且卸装前先判背包空格。

单角色清零脚本(GOM 类):

[@执行]
#IF
#ACT
; 1. 把身上 13 个装备位全部卸到背包
; 引擎命令一般为:TakeOffToBag 装备位
; 装备位:0武器 1衣服 2头盔 3项链 4左戒指 5右戒指 6左手镯 7右手镯 8腰带 9鞋子 10宝石 11披风 12面罩
SetInt($H(当前玩家)(卸装位)) = 0

[@卸装循环]
#IF
CheckInt($H(当前玩家)(卸装位)) < 13
#ACT
; 脱下该位装备,放回背包;背包满则自动掉落地面(可配置)
TakeOffToBag <$H(当前玩家)> <$H(当前玩家)(卸装位)>
CalcInt($H(当前玩家)(卸装位)) + 1
#GOTO [@卸装循环]

#ELSEACT
; 2. 卸完再清零
; 清等级(保留 1 级,传奇不允许 0 级)
SetLevel <$H(当前玩家)> 1
; 清经验(经验归零,避免自动升级)
SetExp <$H(当前玩家)> 0
; 清技能等级(按需,成长任务会重做)
ClearSkill <$H(当前玩家)>
; 清任务变量(成长任务/副本任务进度重置)
ClearQuestFlag <$H(当前玩家)>
; 回满血蓝(避免清零后残血死)
FullHp <$H(当前玩家)>
FullMp <$H(当前玩家)>
; 提示该玩家
SendSysMsg <$H(当前玩家)> 活动失败,你的等级已被清零,装备已收回。


HERO lua 对应:
function StripAndReset(Human)
-- 卸全部装备到背包
for idx = 0, 12 do
Human:TakeOffToBag(idx)
end
Human:SetLevel(1)
Human:SetExp(0)
Human:ClearSkill()
Human:ClearQuestFlag()
Human:FullHp()
Human:FullMp()
Human:SysMsg("活动失败,等级已清零,装备已收回。")
end


4. 几个必须处理的坑

① 背包满导致装备消失
清零前先判背包空格数,不足就:
• 清掉背包里的垃圾白装(按重量/价值过滤)

• 或临时扩展背包(部分引擎支持 SetBagSize)

• 或把装备直接存进仓库(DepotSave),活动后再发

推荐做法:先 GetBagFree(Human) 拿到空格,空格 < 13 就先清垃圾再卸装。

② 马匹 / 英雄 / 宝宝装备
如果有英雄系统,英雄身上也要脱:

TakeOffHeroAll <$H(当前玩家)>

宝宝(道士神兽)直接消失即可,召唤物不算装备。

③ 元宝 / 金币 / 仓库要不要清
等级清零通常只清等级+经验+技能+任务进度,不动金币元宝仓库。你可按需加:

; 清金币(保留 1000 做路费)
SetGold <$H(当前玩家)> 1000

别清成 0,否则玩家连回城卷都买不起,直接卡死。

④ 玩家正在交易 / 在商店 / 在副本
遍历处理时若玩家处于“不可操作”状态,TakeOffToBag 会失败。做法:
• 先踢出交易/商店界面(CancelTrade / CloseShop)

• 副本中的玩家标记“待清零”,等他回主城再执行(用变量记录,回城事件里补处理)

• 或强制召回:MoveTo <$H(当前玩家)> 3 330 330(回土城再处理)

⑤ 跨线 / 微端
单线遍历只能清本线。多线要在中心服/网关层广播“清零指令”,各线收到后各自遍历本线在线角色。脚本里加一层:

; 线1 处理完向线2发消息
SendCrossMsg 2 CLEAR_ALL_LEVEL

各线 Robot 监听该消息并触发自己的清零流程。

5. 完整触发链路


活动失败判定
→ Robot定时器 / NPC事件
→ #CALL 全服清零.txt @开始清零
→ 遍历在线玩家
→ 每个玩家:判背包空格 → 清垃圾 → 卸13件装备到背包 → 清等级/经验/技能/任务 → 回血蓝 → 发提示
→ 遍历完发全服公告
→ (跨线)广播各线重复上述


全程不打开 M2,不踢线不返回角色,玩家原地直接被清零。

二、任务系统:任务栏闪烁 + 任务窗口分类

传奇原版没有“任务栏闪烁”和“成长/副本分类”这种客户端 UI,要实现得靠两部分配合:
• 服务端:任务数据、接/交逻辑、进度存储

• 客户端 / 插件:任务栏图标闪烁、分类窗口(需要支持自定义 UI 的引擎,如 GEE、HERO、翎风)

下面按“脚本能控制的程度”分层给方案。

1. 服务端:任务数据结构与分类

QuestDiary 里按文件夹分类(这是“任务分类”的服务端根基):

\Envir\QuestDiary\
成长任务\
每日签到.txt
升级任务.txt
转生任务.txt
副本任务\
祖玛副本.txt
赤月副本.txt
火龙副本.txt
活动任务\
攻城预热.txt
节日收集.txt


每个任务文件统一模板:

; ===== 成长任务:升级到 35 级 =====
[@main]
#IF
ISACCEPT 成长_升级35
#ACT
#SAY
任务进度:<$\QUEST(成长_升级35)> / 35
<继续升级/@close>
#ELSEACT
#SAY
<接受任务:升级到35级/@accept>

[@accept]
#IF
#ACT
ACCEPTQUEST 成长_升级35
SetInt(<$HUMAN(成长_升级35)>) = 0
SENDMSG 0 已接受成长任务【升级到35级】!

; ===== 进度更新(在升级事件里调用)=====
[@升级回调]
#IF
ISACCEPT 成长_升级35
CheckLevelEx >= 35
#ACT
COMPLETEQUEST 成长_升级35
GiveItem 祝福油 1
SENDMSG 0 成长任务【升级到35级】已完成,奖励已发放!


任务分类标识用前缀:成长_ 副本_ 活动_,后面客户端按前缀自动归类。

2. 任务进度存储

• 玩家变量:SetInt(<$HUMAN(成长_升级35)>) —— 存进度,重启不丢

• 任务接受标记:ISACCEPT 任务名 —— 引擎内置命令

• 任务完成标记:COMPLETEQUEST 任务名 —— 控制能否重复领取

不要用全局变量存进度,否则全服共用一个进度,A 完成 B 也领奖。

3. 任务栏闪烁(客户端层)

传奇客户端右下角“任务”图标闪烁,本质是:服务端推一个状态标志 → 客户端检测到有新任务/可交任务 → 图标开始闪。

方案 A:引擎自带任务系统(GEE/HERO 推荐)
这些引擎内部已有任务栏,脚本只要正确调用 ACCEPTQUEST / COMPLETEQUEST,客户端自动:
• 新任务可接 → 图标亮

• 任务可交 → 图标闪

• 任务栏里按“当前 / 已完成 / 可接”分组

你要做的只是在接/交任务时保证命令成对调用,别漏 COMPLETEQUEST。

方案 B:自定义闪烁(通用,支持插件引擎)
用客户端插件监听一个自定义封包,服务端在任务状态变化时发送:

服务端(lua 插件):
function NotifyQuestFlash(Human, questType)
-- questType: 1成长 2副本 3活动
Human:SendCustomPacket(0xAB, questType)
end

客户端(支持 lua UI 的引擎):
RegisterPacket(0xAB, function(type)
TaskWindow:SetFlash(true)
TaskWindow:SetType(type) -- 1/2/3 对应三个分类页
end)

客户端收到 0xAB 就让任务栏图标 SetFlash(true) 开始闪烁,并切到对应分类页。

方案 C:无客户端修改,纯服务端替代(LEG 老引擎)
老引擎改不了客户端 UI,就用 NPC 头顶感叹号 + 屏幕提示 模拟:
• 任务可接:NPC 头顶显示 !(服务端 SetNpcTitle)

• 任务可交:NPC 头顶显示 ?

• 玩家打开任务 NPC 时,#SAY 里列出分类清单(见下)

4. 任务窗口:分类显示

纯脚本的分类清单(NPC 对话框,LEG/GOM 通用):

[@任务列表]
#ACT
#SAY
\【成长任务】\ \
<升级到35级:<$\QUEST(成长_升级35)>/35/@成长_升级>\ \
<转生任务:<$\QUEST(成长_转生)>/1/@成长_转生>\ \
\【副本任务】\ \
<祖玛副本:<$\QUEST(副本_祖玛)>/10/@副本_祖玛>\ \
<赤月副本:<$\QUEST(副本_赤月)>/5/@副本_赤月>\ \
\【活动任务】\ \
<攻城预热/@活动_攻城>\ \
<返回/@main>


带客户端 UI 的分类(GEE/HERO):在插件里维护一个任务表,按类型分组渲染:
local QuestDB = {
[1] = { -- 成长
{name="升级到35级", cur=Human:GetVar("成长_升级35"), max=35, state=...},
{name="转生任务", cur=..., max=1},
},
[2] = { -- 副本
{name="祖玛副本", cur=..., max=10},
},
[3] = { -- 活动
{name="攻城预热", cur=..., max=1},
},
}

客户端按 1/2/3 渲染成三个 Tab,任务栏图标按 state(可接/进行中/可交)决定闪不闪、什么颜色。

5. 闪烁触发时机汇总

要让玩家“及时看到闪”,在这几个节点调用通知:
• 接任务成功 → 闪(新任务)

• 任务进度满(可交) → 闪(提示交任务)

• 交任务领奖 → 闪(切到下一个)

• 活动开启 / 新日常刷新 → 闪(可接任务出现)

• 副本通关结算 → 更新进度,满则闪

统一封装一个函数最省事:
function UpdateQuest(Human, questName)
local def = QuestDef[questName]
local cur = Human:GetVar(questName)
if cur >= def.max and not Human:IsComplete(questName) then
Human:SetComplete(questName, true)
NotifyQuestFlash(Human, def.type) -- 可交,闪
elseif not Human:IsAccept(questName) and def.autoAccept then
Human:Accept(questName)
NotifyQuestFlash(Human, def.type) -- 新任务,闪
end
end


6. 任务系统完整链路


服务端 QuestDiary 按 成长/副本/活动 分文件夹
→ 接任务:ACCEPTQUEST + SetInt(HUMAN(进度))
→ 进度更新:升级/杀怪/副本结算事件 → SetInt + UpdateQuest
→ 满进度:COMPLETEQUEST + NotifyQuestFlash(类型)
→ 交任务:GiveItem + 开下一环
→ 客户端收到 0xAB → 任务栏闪 + 切分类 Tab
→ 玩家打开任务窗口 → 按 1/2/3 渲染列表


三、两个功能的联动

你这个场景里,两个需求其实是同一个活动系统的两头:

活动失败 → 全服清零 → 同时重置所有任务进度

[@活动失败处理]
#CALL [\系统\全服清零.txt] @开始清零
#CALL [\系统\任务重置.txt] @重置全部任务


任务重置脚本:

[@重置全部任务]
#IF
#ACT
SetInt($G(任务索引)) = 0

[@任务循环]
#IF
CheckInt($G(任务索引)) < CheckInt($G(任务总数))
#ACT
GetOnlineHuman <$G(任务索引)> $H(当前玩家)
; 清掉三类任务进度变量(按你实际任务名遍历)
ResetQuest <$H(当前玩家)> 成长_升级35
ResetQuest <$H(当前玩家)> 成长_转生
ResetQuest <$H(当前玩家)> 副本_祖玛
ResetQuest <$H(当前玩家)> 副本_赤月
ResetQuest <$H(当前玩家)> 活动_攻城
; 重新开放“成长任务·第一章”作为新一轮起点
ACCEPTQUEST <$H(当前玩家)> 成长_第一章
NotifyQuestFlash <$H(当前玩家)> 1
CalcInt($G(任务索引)) + 1
#GOTO [@任务循环]


这样一次活动失败:等级清零 + 装备回收 + 任务进度归零 + 任务栏自动闪(新的一轮成长任务可接),玩家不用下线不用返回角色,原地直接重新开始。

四、调试与排错清单

清零脚本常见翻车点:
• TakeOffToBag 后装备消失 → 背包满,先清空格

• 清零后玩家卡死/掉线 → 没清交易状态、没回血蓝

• 跨线只清了一半 → 没做中心服广播

• 英雄装备没脱 → 加 TakeOffHeroAll

• 技能没清导致等级1但技能满级 → ClearSkill 必须调用

• 金币元宝一起清了 → 玩家无法买药,留基础路费

任务系统常见翻车点:
• 图标不闪 → 客户端没注册 0xAB 封包 / 引擎自带任务开关没开

• 分类错乱 → 任务名前缀不统一,脚本按前缀分组时匹配失败

• 进度不更新 → 升级/杀怪事件里没调 UpdateQuest

• 重复领奖 → 没判 ISACCEPT / COMPLETEQUEST,玩家点两次发两份

• 任务栏显示旧数据 → 客户端没在 Notify 时刷新列表,要主动调 RefreshTaskWindow

调试方法:
• 先拿 1 个测试号跑通,再放开全服

• 清零前 SENDMSG 打印“即将清零人数 = $G(清零总数)”

• 单角色处理完打印“已处理:H(当前玩家) 等级 LEVEL”

• 任务系统先写 1 个成长任务跑通接受→进度→完成→闪烁→领奖,再复制到其他任务

• 所有变量名建一张表统一管理,避免 成长_升级35 和 成长_升35 这种拼写差

五、命令对照速查(按引擎)

功能 LEG/GOM HERO lua

取在线人数 <$ONLINECOUNT> M2:GetOnlineCount()

取在线玩家 GetOnlineHuman N $H(x) M2:GetOnlinePlayers()

设等级 SetLevel Human 1 Human:SetLevel(1)

清经验 SetExp Human 0 Human:SetExp(0)

卸装备到背包 TakeOffToBag Human 位 Human:TakeOffToBag(idx)

清技能 ClearSkill Human Human:ClearSkill()

背包空格 GetBagFree Human Human:GetBagFree()

接任务 ACCEPTQUEST 任务名 Human:Accept(name)

交任务 COMPLETEQUEST 任务名 Human:Complete(name)

发公告 SENDMSG 0 文本 M2:SendMsg(0, text)

引擎不同命令名一定对一遍文档,GameGold / AddGameGold / Human:AddGameGold 这类在三个引擎里写法完全不同的命令,照抄必崩。

把“全服清零”理解成 遍历 + 卸装 + 清属性 三步,把“任务系统”理解成 分类存储 + 进度回调 + 客户端闪烁 四块,剩下的就是按你引擎的实际命令名填空。先单角色跑通清零,再接活动失败事件;先做一个成长任务跑通全流程,再加副本/活动分类——两部分都稳定后,用一次 #CALL 把清零和任务重置串起来,就是你要的“活动失败 → 全服原地清零 + 任务重新开始 + 任务栏闪烁提示”。