1. 规则
当前代码的行为如下:
- 随身珍兽可以锁定、发起解锁、显示解锁中。
- 珍兽银行里的珍兽只显示锁状态,不直接发起锁/解锁。
- 锁/解锁请求只处理随身珍兽,银行里的珍兽要先取回随身再解锁。
- 锁定珍兽可以存入仓库,也可以从仓库取出。
- 珍兽进仓、仓库互换只拦交易中的珍兽,不拦锁定状态。
- 锁定珍兽仍然不能交易、不能摆摊、不能上架。
2. 先看客户端入口
服务端规则按客户端现有入口来定。
2.1 锁/解锁入口只在随身珍兽列表
文件:客户端/Interface/ItemCoffer/ItemCoffer.lua
function ItemCoffer_Lock_Clicked()
if( g_nTheTabIndex == 2 ) then
local nPetIndex = ItemCoffer_PetList:GetFirstSelectItem();
if(nPetIndex == -1) then
PlayerPackage:Lock("lock", "pet", -1);
else
PlayerPackage:Lock("lock", "pet", g_PetIndex[nPetIndex]);
end
end
end
function ItemCoffer_Unlock_Clicked()
if( g_nTheTabIndex == 2 ) then
local nPetIndex = ItemCoffer_PetList:GetFirstSelectItem();
if(nPetIndex == -1) then
PlayerPackage:Lock("unlock", "pet", -1);
else
PlayerPackage:Lock("unlock", "pet", g_PetIndex[nPetIndex]);
end
end
end
这里传的是随身珍兽索引,不是银行索引。
2.2 银行界面只有显示,没有锁/解锁入口
文件:客户端/Interface/PetBankMain/PetBankMain.lua
if (PlayerPackage:IsPetLock(i-1,1) == 1) then
local nUnlockElapsedTime = PlayerPackage:GetPUnlockElapsedTime_Pet(i-1 ,1);
end
function PetBankMain_Refuse_Click()
Pet:SavePetIntoBank_PetBank(PETBANK_CURRENT_SELECT,0); -- 银行到背包
end
这里就能把服务端规则定下来:
- 银行界面会显示锁状态。
- 银行界面没有
OpenLockFrame / Lock("unlock","pet", ...)。
- 所以服务端不能把“银行里的锁定珍兽禁止取出”做成硬规则,否则会卡死。
3. 第一步:补 CGAskLockObj.PET
文件:/home/ubuntu/Game2/services/scene/scenecore.lua
原来的问题是 char_ask_lock_obj() 只处理 ITEM,PET 请求直接失败。
主要改法如下:
***付费内容***
这里保持两个限制:
- 只允许“随身珍兽”进锁/解锁请求。
- 不要在这里支持银行珍兽,否则会和客户端真实入口冲突。
4. 第二步:在 human.lua 建一套珍兽解锁状态管理
文件:/home/ubuntu/Game2/services/scene/obj/human.lua
4.1 新增常量
***付费内容***
4.2 新增心跳刷新
if self:has_pending_pet_unlock() then
if self.pet_unlock_refresh_tick > delta_time then
self.pet_unlock_refresh_tick = self.pet_unlock_refresh_tick - delta_time
else
self.pet_unlock_refresh_tick = PET_LOCK_REFRESH_INTERVAL_MS
self:refresh_locked_pet_unlock_state(true)
end
else
self.pet_unlock_refresh_tick = 0
end
4.3 新增解锁时间表
function human:get_pet_unlock_time_map()
self.game_flag = self.game_flag or {}
local unlock_map = self.game_flag[PET_LOCK_UNLOCK_TIME_KEY]
if type(unlock_map) ~= "table" then
unlock_map = {}
self.game_flag[PET_LOCK_UNLOCK_TIME_KEY] = unlock_map
end
return unlock_map
end
function human:is_pet_unlocking_by_guid(guid)
local unlock_time = self:get_pet_unlock_time_by_guid(guid)
return unlock_time > os.time()
end
function human:set_pet_unlock_time_by_guid(guid, unlock_time)
local unlock_map = self:get_pet_unlock_time_map()
unlock_map[make_pet_lock_guid_key(guid)] = math.max(0, math.floor(tonumber(unlock_time) or 0))
end
function human:clear_pet_unlock_time_by_guid(guid)
local unlock_map = self:get_pet_unlock_time_map()
unlock_map[make_pet_lock_guid_key(guid)] = nil
end
5. 第三步:统一封装珍兽锁字段发包
Game.exe 用到的就是珍兽详情包里的这两个字段:
unknow_5:锁状态位
unknow_10:解锁中的时间编码
这两个字段统一放进 human:sync_pet_lock_packet_fields()。
文件:/home/ubuntu/Game2/services/scene/obj/human.lua
***付费内容***
这里不要再回到旧写法:
unknow_5 用 2/3,不要再写死成 2。
unknow_10 不是固定 0,解锁中时要编码。
- 编码公式在
get_pet_unlock_packet_value_by_guid() 里统一处理。
6. 第四步:所有珍兽详情包都必须走统一锁字段
6.1 pet_detail.lua
文件:/home/ubuntu/Game2/lualib/pet_detail.lua
function pet_detail:calculate_pet_detail_attrib(msg)
...
msg:set_unknow_2(-1)
msg:set_titles(self:get_titles())
msg:set_current_title(self:get_current_title())
local owner = self:get_owner()
if owner and owner.sync_pet_lock_packet_fields then
owner:sync_pet_lock_packet_fields(self, msg)
else
msg:set_unknow_5(self:is_lock() and 3 or 2)
msg:set_unknow_10(0)
end
msg:set_unknow_11(5399)
msg:set_unknow_13(0)
end
6.2 pet_attrib.lua
文件:/home/ubuntu/Game2/lualib/db/pet_attrib.lua
if creator and creator.sync_pet_lock_packet_fields then
creator:sync_pet_lock_packet_fields(pet, refresh_detail_attrib)
else
refresh_detail_attrib:set_unknow_5(pet.is_lock and pet:is_lock() and 3 or 2)
refresh_detail_attrib:set_unknow_10(0)
end
这里保持一个做法:
- 不允许再写死
set_unknow_5(2)。
- 不允许再写死
set_unknow_10(0)。
7. 第五步:补珍兽银行刷新包
银行界面不是单个 GCDetailAttrib_Pet,而是整包 GCPetBankListUPData。
文件:/home/ubuntu/Game2/services/scene/obj/human.lua
function human:send_pet_bank_list_update()
local scene = self:get_scene()
local pet_bank_container = self:get_pet_bank_container()
if not scene or not pet_bank_container then
return
end
local pet_list = {}
for i = 0, pet_bank_container:get_size() - 1 do
local pet = pet_bank_container:get_item(i)
if pet then
local msg_detail = packet_def.GCDetailAttrib_Pet.new()
pet:calculate_pet_detail_attrib(msg_detail)
msg_detail.unknow_6 = 0
local pet_buffer = msg_detail:bos()
pet_list[i + 1] = pet_buffer
end
end
local msg = packet_def.GCPetBankListUPData.new()
msg.pet_list = pet_list
scene:send2client(self, msg)
end
然后在仓库增删换后统一调用:
文件:/home/ubuntu/Game2/services/scene/scenecore.lua
human:send_pet_bank_list_update()
8. 第六步:珍兽银行的业务规则按客户端入口来
文件:/home/ubuntu/Game2/services/scene/scenecore.lua
char_pet_bank_add_pet() 改成下面这套逻辑:
function scenecore:char_pet_bank_add_pet(who, bap)
local human = self.objs[who]
if not human then return end
human:refresh_locked_item_unlock_state(false)
human:refresh_locked_pet_unlock_state(false)
local pet_bag_container = human:get_pet_bag_container()
local pet_bank_container = human:get_pet_bank_container()
local function check_pet_not_in_exchange(pet, tips)
if not pet then
return false
end
if self:is_pet_in_exchange(pet) then
human:notify_tips(tips or "交易中的珍兽无法进行此操作")
return false
end
return true
end
if bap.type == 0 then
-- 银行 -> 背包:允许取出锁定珍兽
elseif bap.type == 1 then
-- 背包 -> 银行:允许锁定珍兽存入,只拦交易状态
local pet = pet_bag_container:get_pet_by_guid(bap.pet_guid_from)
if not check_pet_not_in_exchange(pet, "交易中的珍兽无法存入仓库") then
return
end
elseif bap.type == 2 then
-- 背包 <-> 银行:只拦背包侧交易状态,不拦锁定状态
local pet_from = pet_bag_container:get_item(from)
if not check_pet_not_in_exchange(pet_from, "交易中的珍兽无法与仓库互换") then
return
end
end
human:send_pet_bank_list_update()
end
这里容易踩坑:
- 银行里的锁定珍兽必须允许取出。
- 锁定珍兽必须允许存仓,否则锁状态跨容器会断在半路。
- 背包 -> 银行、背包 <-> 银行只拦交易中的珍兽,不拦锁定状态。
- 客户端没有“直接解锁银行珍兽”的入口,解锁还是回到随身界面做。
9. 第七步:存档恢复,不然重登丢锁状态
文件:/home/ubuntu/Game2/lualib/pet_detail.lua
9.1 加载时恢复
***付费内容***
9.2 保存时写回
function pet_detail:copy_raw_data()
return {
skills = self.skill_list,
titles = self.titles,
lv1_attrib = self.db:get_lv1_attrib(),
attrib = self.db:get_db_save_attrib(),
equips = self:get_equip_container():get_save_data(),
locked = self.locked == true,
cool_down_times = self.cool_down_times
}
end
10. 第八步:交易/摆摊等入口补刷新
文件:/home/ubuntu/Game2/lualib/human_item_logic.lua
function human_item_logic:is_pet_can_exchange(human, contaner, bag_index, locked_tips)
human:refresh_locked_pet_unlock_state(false)
local pet = contaner and contaner:get_item(bag_index)
if not pet then
return false
end
if pet.is_lock and pet:is_lock() then
human:notify_tips(locked_tips or "该珍兽已在交易或摆摊中")
return false
end
return true
end
原因:
- 有些入口不走
check_pet_operable()。
- 必须先刷新解锁状态,再判断
is_lock()。
11. 第九步:银行容器也要绑定 owner
文件:/home/ubuntu/Game2/services/scene/obj/human.lua
self.pet_bank_container:init(pet_bank_bag_size, data.pet_bank_list,define.CONTAINER_INDEX.HUMAN_PET_BANK,self)
不传 self 的话,银行里的 pet_detail:get_owner() 拿不到 human,发包时就拿不到统一锁状态。
12. 收尾检查
代码改完后,至少把下面几项过一遍:
char_ask_lock_obj() 已支持 OBJ_TYPE.PET。
- 锁/解锁请求只处理随身珍兽,不处理银行珍兽。
human.lua 里有 pet_lock_unlock_time 时间表和心跳刷新。
pet_detail.lua / pet_attrib.lua 不再写死 unknow_5=2、unknow_10=0。
GCPetBankListUPData 走 calculate_pet_detail_attrib(),而不是旧的固定字段。
- 银行锁定珍兽允许取出到随身。
- 锁定珍兽允许存仓,仓库互换不拦锁定状态。
- 交易、摆摊、上架仍会拦锁定珍兽。
pet_detail:copy_raw_data() 已保存 locked。
13. 检查记录
先跑语法检查:
luac -p /home/ubuntu/Game2/services/scene/obj/human.lua
luac -p /home/ubuntu/Game2/services/scene/scenecore.lua
luac -p /home/ubuntu/Game2/lualib/db/pet_attrib.lua
luac -p /home/ubuntu/Game2/lualib/pet_detail.lua
luac -p /home/ubuntu/Game2/lualib/human_item_logic.lua
联机把下面几项点过:
- 珍兽锁定正常。
- 珍兽解锁正常。
- 银行显示锁状态正常。
- 锁定珍兽可存入银行。
- 银行锁定珍兽可取出。
- 锁定珍兽不能交易、摆摊、上架。