功能目标
在玄武岛增加 NPC“沙洲冷”,玩家与其对话后可以打开珍兽提交框,选择一只珍兽出售。服务端根据珍兽等级发放金币,并删除玩家提交的那只珍兽。
参考配置:
[monster246]
guid=15896887
type=158
name=沙洲冷
title=珍兽收购商
pos_x=84.9041
pos_z=37.9212
dir=14
script_id=112002
参考脚本映射:
112002=\obj\petisland\opetisland_shazhouleng.lua
250000=\event\luoyang\eluoyang_50000.lua
文件清单
需要补齐或确认以下服务端文件:
configs/scene/petisland_monster.ini
services/scripts/scripts.lua
services/scripts/obj/petisland/opetisland_shazhouleng.lua
services/scripts/event/luoyang/eluoyang_50000.lua
lualib/script_base/pet_repair_drop_misc.lua
services/game/packet_parts/social_trade/team_exchange.lua
services/scene/scenecore/mission_challenge.lua
其中:
opetisland_shazhouleng.lua 是 NPC 菜单壳。
eluoyang_50000.lua 是珍兽回收核心逻辑。
pet_repair_drop_misc.lua 提供按珍兽 GUID 查询和删除的脚本接口。
team_exchange.lua 和 mission_challenge.lua 负责把客户端提交框传来的珍兽 GUID 继续传到脚本层。
注册脚本
先在脚本注册表中加入 NPC 脚本和事件脚本:
[250000] = "scripts.event.luoyang.eluoyang_50000",
[112002] = "scripts.obj.petisland.opetisland_shazhouleng",
注册位置按项目原有结构放入对应的事件脚本区和 NPC 脚本区即可。
NPC 菜单壳
services/scripts/obj/petisland/opetisland_shazhouleng.lua 主要负责:
- 显示 NPC 对话。
- 枚举“我要出售珍兽”按钮。
- 显示“出售的珍兽怎么抓”的帮助说明。
- 将提交框回调转发给事件脚本
250000。
关键点是 g_eventList 只挂沙洲冷需要的事件:
opetisland_shazhouleng.script_id = 112002
opetisland_shazhouleng.g_eventList = { 250000 }
NPC 默认对话里调用事件脚本的 OnEnumerate:
for _, eventId in pairs(self.g_eventList) do
self:CallScriptFunction(eventId, "OnEnumerate", self, selfId, targetId)
end
当玩家点击“我要出售珍兽”时,NPC 壳调用事件脚本:
self:CallScriptFunction(arg, "OnDefaultEvent", selfId, targetId)
当玩家在提交框中点确定后,NPC 壳要继续转发 OnMissionCheck,并保留珍兽 GUID 参数:
self:CallScriptFunction(
scriptId,
"OnMissionCheck",
selfId,
targetId,
scriptId,
index1,
index2,
index3,
petIndex,
missionIndex,
petGuidH,
petGuidL
)
这里不要只转发 petIndex,否则容易出现客户端选了 B 珍兽、服务端却删除 A 珍兽的问题。
打开提交珍兽框
services/scripts/event/luoyang/eluoyang_50000.lua 负责打开提交框:
function eluoyang_50000:OnDefaultEvent(selfId, targetId)
self:BeginEvent(self.script_id)
self:AddText(self.g_MissionName)
self:AddText(self.g_MissionInfo)
self:EndEvent()
self:DispatchMissionDemandInfo(selfId, targetId, self.script_id, self.g_MissionId, 2)
end
本端 Lua5 脚本环境里,DispatchMissionDemandInfo 会读取当前脚本对象的 self.event。因此调用前必须先 BeginEvent、AddText、EndEvent,不能像部分 Lua4 参考脚本那样直接发送。
done=2 用于打开可提交界面。出售成功后再发一次 done=1,通知客户端提交流程已经完成:
self:DispatchMissionDemandInfo(selfId, targetId, self.script_id, self.g_MissionId, 1)
否则可能出现服务端已经删宠发钱,但客户端仍显示默认失败提示的问题。
出售金额
金额按珍兽等级分段:
function eluoyang_50000:PetValue(petLevel)
if petLevel == nil or petLevel <= 0 then
return 0
end
if petLevel <= 5 then
return 225
end
if petLevel <= 15 then
return 595
end
if petLevel <= 25 then
return 1191
end
if petLevel <= 35 then
return 1779
end
if petLevel <= 45 then
return 2450
end
if petLevel <= 55 then
return 3205
end
if petLevel <= 65 then
return 4042
end
if petLevel <= 75 then
return 4964
end
if petLevel <= 85 then
return 5968
end
return 7056
end
这套逻辑不限制珍兽来源。只要玩家通过提交框放入珍兽,就按等级结算。
按 GUID 删除珍兽
提交框协议里通常会带:
pet_index
pet_guid_high
pet_guid_low
实现时不要按 pet_index 删除。pet_index 可能受客户端显示顺序、珍兽栏空洞、索引基准影响,导致删错珍兽。更稳的做法是按客户端提交的 GUID 精确查询并删除。
在脚本基础库中补充:
function script_base:LuaFnGetPet_DataIDByGUID(selfId, petGUID_H, petGUID_L)
local human = self:get_scene():get_obj_by_id(selfId)
local pet_bag_container = human:get_pet_bag_container()
local guid = pet_guid_cls.new()
guid:set(petGUID_H, petGUID_L)
local pet_detail = pet_bag_container:get_pet_by_guid(guid)
if pet_detail then
return pet_detail:get_data_index()
end
end
function script_base:LuaFnGetPet_LevelByGUID(selfId, petGUID_H, petGUID_L)
local human = self:get_scene():get_obj_by_id(selfId)
local pet_bag_container = human:get_pet_bag_container()
local guid = pet_guid_cls.new()
guid:set(petGUID_H, petGUID_L)
local pet_detail = pet_bag_container:get_pet_by_guid(guid)
if pet_detail then
return pet_detail:get_level()
end
end
function script_base:LuaFnDeletePetByGUID(selfId, petGUID_H, petGUID_L)
local human = self:get_scene():get_obj_by_id(selfId)
local pet_bag_container = human:get_pet_bag_container()
local guid = pet_guid_cls.new()
guid:set(petGUID_H, petGUID_L)
local index = pet_bag_container:get_index_by_pet_guid(guid)
if index then
pet_bag_container:erase_item(index)
local msg = packet_def.GCRemovePet.new()
msg.guid = guid
self:get_scene():send2client(human, msg)
return true
end
return false
end
函数名可按项目已有容器 API 调整,核心原则是:查询、取等级、删除都使用同一个 GUID。
处理提交逻辑
事件脚本 OnMissionCheck 推荐流程:
- 没放珍兽时提示玩家。
- 没有 GUID 时拒绝处理。
- 按 GUID 读取等级和 DataID。
- 删除同一个 GUID 对应的珍兽。
- 按等级发钱。
- 发任务提示,并通知客户端提交完成。
示例:
function eluoyang_50000:OnMissionCheck(
selfId,
targetId,
scriptId,
index1,
index2,
index3,
indexpet,
missionIndex,
petGuidH,
petGuidL
)
if indexpet == 255 then
self:NotifyTip(selfId, "请先放入你要出售的珍兽!")
return
end
if petGuidH == nil or petGuidL == nil or petGuidH == 0 or petGuidL == 0 then
self:NotifyTip(selfId, "请先放入你要出售的珍兽!")
return
end
local petLevel = self:LuaFnGetPet_LevelByGUID(selfId, petGuidH, petGuidL)
if petLevel == nil then
self:NotifyTip(selfId, "请先放入你要出售的珍兽!")
return
end
local dataId = self:LuaFnGetPet_DataIDByGUID(selfId, petGuidH, petGuidL)
local petName = self:GetSellPetName(dataId)
local deleted = self:LuaFnDeletePetByGUID(selfId, petGuidH, petGuidL)
if not deleted then
self:NotifyTip(selfId, "出售珍兽失败,请重新选择。")
return
end
local moneyNum = self:PetValue(petLevel)
self:AddMoney(selfId, moneyNum)
self:NotifyTip(selfId, "成功出售了" .. petName .. ",获得了#{_MONEY" .. tostring(moneyNum) .. "}")
self:BeginEvent(self.script_id)
self:AddText(self.g_MissionComplete)
self:EndEvent()
self:DispatchMissionDemandInfo(selfId, targetId, self.script_id, self.g_MissionId, 1)
end
如果项目里的删除接口已经稳定返回布尔值,建议按上面这样判断删除结果。若旧接口删除成功但没有返回值,就不要直接用 ret > 0 判断,否则会出现“珍兽已删但提示失败”。
名字兜底
部分服务端的珍兽配置表可能不完整,直接调用 GetPetName(dataId) 可能因为缺配置报错。建议给出售提示单独做兜底:
function eluoyang_50000:GetSellPetName(dataId)
if dataId == nil or dataId <= 0 then
return "该珍兽"
end
local ok, petName = pcall(self.GetPetName, self, dataId)
if ok and petName and petName ~= "" then
return petName
end
return "该珍兽"
end
这样即使某个珍兽 DataID 没有名字配置,出售流程也不会中断。
协议层注意点
如果服务端协议层已经解析了 pet_guid_high、pet_guid_low,需要确认它们以无符号整数读取,并继续传到场景脚本:
packet.pet_guid_high = stream:readuint()
packet.pet_guid_low = stream:readuint()
场景层转发 OnMissionCheck 时,要注意不要破坏原有参数位置。可以保留 missionIndex 位置,再追加 GUID 参数:
script:OnMissionCheck(
selfId,
targetId,
scriptId,
index1,
index2,
index3,
petIndex,
missionIndex,
petGuidH,
petGuidL
)
很多 NPC 壳和任务脚本依赖第 8 个参数是 missionIndex,不要为了沙洲冷直接把 GUID 插到中间。