GCPlayerShopApply 是当前版本里用于向客户端下发“玩家商店申请信息”的结果包。现役 /home/ubuntu/Game2 主链中,这条包不是由 CGPlayerShopApply 请求链驱动,而是由 NPC 脚本直接调用 ApplyPlayerShop() 构造发送,用来告诉客户端当前开店 NPC、佣金系数、申请费用,以及玩家当前还能申请的店铺类型。
包信息
| 项目 |
内容 |
| 封包 ID |
128 |
| 包名 |
GCPlayerShopApply |
| 方向 |
Server -> Client |
| 对应请求 |
无专用请求;现役由脚本直接下发 |
| 固定包长 |
14 |
| 作用 |
下发玩家商店申请面板所需的 NPC、佣金、费用与可申请店铺类型 |
包体结构
当前 Game.exe 与现役 Lua 协议定义都能确认,这条包固定长度为 14 字节。
| 偏移 |
长度 |
字段 |
类型 |
含义 |
0x00 |
0x04 |
npc_id |
int32 |
当前开店 NPC ID |
0x04 |
0x04 |
com_factor |
float32 |
当前佣金系数 |
0x08 |
0x04 |
cost |
uint32 |
当前申请费用 |
0x0C |
0x01 |
shop_type |
uint8 |
可申请店铺类型;当前 1=ITEM、2=PET、3=BOTH |
0x0D |
0x01 |
reserved_flag |
uint8 |
保留字节;现役 Lua 字段名仍为 unknow,当前主链默认发送 0 |
当前 Game.exe 的 GetPacketSize() 返回固定值:
14
当前实际行为
- 当前
Game.exe 中,GCPlayerShopApply::GetPacketID() 返回 128,GetPacketSize() 返回 14。
- 当前
Game.exe 的 Read() / Write() 顺序可直接确认:
npc_id
com_factor
cost
shop_type
reserved_flag
- 当前
Game.exe 的 Execute() 会先按 npc_id 查询目标 NPC,再把包内的申请参数写入客户端玩家商店上下文,并触发内部事件 209。
- 现役 Lua 发送链不走
request:CGPlayerShopApply();真正的发送入口是 script_base:ApplyPlayerShop(selfId, targetId)。
- 当前脚本发送前会先做两类校验:
- 角色等级必须达到
30
- 角色不能同时已经拥有道具店和珍兽店
- 校验失败时,当前主链发送的是
GCPlayerShopError,不会发送 GCPlayerShopApply。
- 校验通过时,当前脚本会计算:
com_factor = playershopmanager:get_com_factor()
cost = 300000 * com_factor * 2 * 1.03
- 当前
shop_type 的发送规则为:
- 尚未拥有任意店铺时发送
TYPE_BOTH(3)
- 只缺道具店时发送
TYPE_ITEM(1)
- 只缺珍兽店时发送
TYPE_PET(2)
- 当前代码库里检出的实际触发脚本只有两处,均为洛阳 NPC:
oluoyang_qiaofusheng.lua
oluoyang_suzhe.lua
- 当前主链没有检出其他地方对
reserved_flag 的赋值,因此该字节在现役版本里应视为保留位。
服务端落点
- 协议定义:
/home/ubuntu/Game2/services/game/packet.lua
- 发送逻辑:
/home/ubuntu/Game2/lualib/script_base.lua
- 当前触发脚本:
/home/ubuntu/Game2/services/scripts/obj/luoyang/oluoyang_qiaofusheng.lua
/home/ubuntu/Game2/services/scripts/obj/luoyang/oluoyang_suzhe.lua
关键代码
当前 Lua 协议定义
packet.GCPlayerShopApply = {
xy_id = packet.XYID_GC_PLAYER_SHOP_APPLY,
TYPE = {
TYPE_ERR = 0,
TYPE_ITEM = 1,
TYPE_PET = 2,
TYPE_BOTH = 3,
},
new = function()
local o = {}
setmetatable(o, { __index = packet.GCPlayerShopApply })
o:ctor()
return o
end,
ctor = function(self)
self.unknow = 0
end,
bis = function(self, buffer)
local stream = bistream.new()
stream:attach(buffer)
self.npc_id = stream:readint()
self.com_factor = stream:readfloat()
self.cost = stream:readuint()
self.type = stream:readuchar()
self.unknow = stream:readuchar()
end,
bos = function(self)
local stream = bostream.new()
stream:writeint(self.npc_id)
stream:writefloat(self.com_factor)
stream:writeint(self.cost)
stream:writeuchar(self.type)
stream:writeuchar(self.unknow)
return stream:get()
end
}
当前 Lua 发送链
function script_base:ApplyPlayerShop(selfId, targetId)
local human = self.scene:get_obj_by_id(selfId)
local com_factor = playershopmanager:get_com_factor()
local cost = 300000 * com_factor * 2 * 1.03
local msg = packet_def.GCPlayerShopError.new()
if human:get_level() < 30 then
msg.error_code = define.PLAYER_SHOP_ERR.ERR_NOT_ENOUGH_LEVEL
human:get_scene():send2client(human, msg)
return
end
local shop_guids = human:get_shop_guids()
local has_item = false
local has_pet = false
for _, guid in ipairs(shop_guids) do
local shop = playershopmanager:get_player_shop_by_guid(guid)
if shop then
if shop:get_owner_guid() == human:get_guid() and shop:get_type() == shop.TYPE.ITEM then
has_item = true
end
if shop:get_owner_guid() == human:get_guid() and shop:get_type() == shop.TYPE.PET then
has_pet = true
end
end
end
if has_item and has_pet then
msg.error_code = define.PLAYER_SHOP_ERR.ERR_ALREADY_HAVE_ENOUGH_SHOP
human:get_scene():send2client(human, msg)
return
end
msg = packet_def.GCPlayerShopApply.new()
msg.com_factor = com_factor
msg.cost = cost
msg.npc_id = targetId
if not has_item and not has_pet then
msg.type = msg.TYPE.TYPE_BOTH
elseif not has_item then
msg.type = msg.TYPE.TYPE_ITEM
elseif not has_pet then
msg.type = msg.TYPE.TYPE_PET
end
human:get_scene():send2client(human, msg)
end
当前 Game.exe 结论
Packets::GCPlayerShopApply::vftable
GetPacketID() -> 128
GetPacketSize() -> 14
Read() -> read dword, dword, dword, byte, byte
Write() -> write dword, dword, dword, byte, byte
Execute() -> cache packet fields and dispatch client event 209
当前 Game.exe 的读写顺序
0x00 npc_id
0x04 com_factor
0x08 cost
0x0C shop_type
0x0D reserved_flag
结论
GCPlayerShopApply(128) 是当前版本里的固定 14 字节结果包,用于下发玩家商店申请界面的参数。
- 现役主链中它不是由
CGPlayerShopApply(117) 驱动,而是由 NPC 脚本直接调用 ApplyPlayerShop() 发送。
- 这条包的核心字段只有五个:
npc_id、com_factor、cost、shop_type、reserved_flag。
shop_type 已可明确写成店铺类型位:1=道具店、2=珍兽店、3=两者都可申请。
- 最后一个字节当前没有足够证据给出更强语义,现阶段应按
reserved_flag 处理,不建议继续沿用文档层面的 unknow 命名。