CGPlayerShopFavorite 是玩家商店系统里用于“添加收藏 / 取消收藏”的客户端请求包。当前 Game.exe 与现役 /home/ubuntu/Game2 Lua 都能确认:它是一条固定 13 字节请求,包体结构为 opt:uint8 + PlayerShopGUID(12)。现役服务端收到后不会直接修改商店对象,而是只在角色自身的收藏 GUID 列表里增删目标店铺,并在成功后连续回 GCPlayerShopUpdateFavorite(288) 与 GCPlayerShopAcquireShopList(578)。
包信息
| 项目 |
内容 |
| 封包 ID |
517 |
| 包名 |
CGPlayerShopFavorite |
| 方向 |
CG 客户端 -> 游戏服 |
| 固定包长 |
13 |
| 专用回包 |
GCPlayerShopUpdateFavorite(288) |
| 相关刷新包 |
GCPlayerShopAcquireShopList(578) |
| 失败提示包 |
GCPlayerShopError(207) |
| 当前 Lua 状态 |
现役协议;已接入并实际处理 |
| 作用 |
切换指定玩家商店 GUID 的收藏状态 |
当前 Game.exe 可直接确认:
CGPlayerShopFavorite
GetPacketID() = 0x1402CC9C0 -> 517;
GetPacketSize() = 0x1401CBFF0 -> 13;
Read = 0x1405CB2D0;
Write = 0x1405CB320;
Execute thunk = 0x1405CB2C0 -> 0x140328E50;
包体结构
当前 Game.exe 与现役 Lua 协议定义一致,包体固定为 1 + 12 字节:
| 偏移 |
长度 |
字段 |
类型 |
含义 |
0x00 |
0x01 |
opt |
uint8 |
收藏操作码;当前场景服实际识别 1=添加收藏、2=取消收藏 |
0x01 |
0x02 |
favorite_id.world |
uint16 |
玩家商店 GUID 的 world 字段 |
0x03 |
0x04 |
favorite_id.server |
uint32 |
玩家商店 GUID 的 server 字段 |
0x07 |
0x04 |
favorite_id.scene |
uint32 |
玩家商店 GUID 的 scene 字段 |
0x0B |
0x02 |
favorite_id.pollpos |
uint16 |
玩家商店 GUID 的 pollpos 字段 |
其中 favorite_id 在当前 Lua 中已明确对应 PlayerShopGUID:
world:uint16 + server:uint32 + scene:uint32 + pollpos:uint16
总长度固定为:
13
当前实际行为
- 请求入口在
/home/ubuntu/Game2/services/msgagent.lua 的 request:CGPlayerShopFavorite(),随后直接转发到场景服 char_player_shop_favorite。
- 场景服先把
favorite_id 克隆为普通表,再检查是否为空 GUID;如果为空,当前实现直接 return,不会回任何错误包。
- 当前场景服只识别两个操作码:
opt == 1:添加收藏
opt == 2:取消收藏
opt 取其他值时,当前实现不会执行任何逻辑,也不会回包。
- 添加收藏时,调用的是角色对象的
add_favorite_shop_guid():
- 非空 GUID 才允许进入;
- 如果该店铺已经在收藏列表中,当前实现会直接返回成功;
- 收藏上限固定为
10,达到上限后返回失败;
- 成功后会连续发送
GCPlayerShopUpdateFavorite(is_in_favorite = 1) 和 GCPlayerShopAcquireShopList。
- 取消收藏时,调用的是
del_favorite_shop_guid():
- 空 GUID 会失败,但这条链路前面已经被场景服提前拦掉;
- 即使目标 GUID 原本不在收藏列表里,当前实现也会返回成功;
- 因此现役
ERR_SHOP_DEL_FROM_FAVORITE(31) 在这条主链上基本不会实际触发;
- 当前删除成功后同样会连续发送
GCPlayerShopUpdateFavorite(is_in_favorite = 0) 和 GCPlayerShopAcquireShopList。
- 添加/删除收藏时,场景服不会先校验该店铺 GUID 当前是否真实存在于玩家商店池中。
- 但在随后的
GCPlayerShopAcquireShopList 刷新链里,playershopmanager:get_shop_infos({ human = human }) 会先调用 human:update_favorite_shop_guids(valid_shop_guids),把已经失效或不在已挂牌商店列表中的收藏 GUID 清理掉。
- 这意味着:当前客户端即使对一个不存在或已下架的店铺 GUID 发起收藏,请求链路也可能先收到一次“收藏状态更新成功”,随后在商店列表刷新时又被服务端静默清理。
- 当前
Game.exe 的 Execute() 仅返回 2,没有额外本地执行逻辑。
- 客户端侧还能看到一条字符串为
LUA:PlayerShop::AddFavor[2] param parameter error 的发送入口;结合构包逻辑可推断,当前客户端本地存在一条按店铺索引发起收藏切换的 Lua 导出接口,并会根据本地当前收藏状态自动决定本次 opt 是 1 还是 2。
服务端落点
- 协议定义:
/home/ubuntu/Game2/services/game/packet.lua
- 请求入口:
/home/ubuntu/Game2/services/msgagent.lua
- 场景处理:
/home/ubuntu/Game2/services/scene/scenecore.lua
- 收藏列表维护:
/home/ubuntu/Game2/services/scene/obj/human.lua
- 商店列表刷新与失效收藏清理:
/home/ubuntu/Game2/lualib/playershopmanager.lua
关键代码
以下代码块中,将 Game.exe 的底层流读写函数按语义简化记为 read_bytes / write_bytes,便于对照结构理解。
当前 Lua 协议定义
packet.CGPlayerShopFavorite = {
xy_id = packet.XYID_CG_PLAYER_SHOP_FAVORITE,
new = function()
local o = {}
setmetatable(o, { __index = packet.CGPlayerShopFavorite })
o:ctor()
return o
end,
ctor = function(self)
end,
bis = function(self, buffer)
local stream = bistream.new()
stream:attach(buffer)
self.opt = stream:readuchar()
self.favorite_id = packet.PlayerShopGUID.new()
self.favorite_id:bis(stream)
end
}
当前场景处理逻辑
function scenecore:char_player_shop_favorite(who, psf)
local obj_me = self.objs[who]
if not obj_me then
return
end
local favorite_id = clone_player_shop_guid(psf.favorite_id)
if is_null_player_shop_guid(favorite_id) then
return
end
local opt = math.floor(tonumber(psf.opt) or 0)
if opt == 1 then
if obj_me:add_favorite_shop_guid(favorite_id) then
send_player_shop_update_favorite(self, obj_me, favorite_id, true)
send_player_shop_acquire_shop_list(self, obj_me)
else
send_player_shop_error(self, obj_me, define.PLAYER_SHOP_ERR.ERR_SHOP_ADD_TO_FAVORITE)
end
elseif opt == 2 then
if obj_me:del_favorite_shop_guid(favorite_id) then
send_player_shop_update_favorite(self, obj_me, favorite_id, false)
send_player_shop_acquire_shop_list(self, obj_me)
else
send_player_shop_error(self, obj_me, define.PLAYER_SHOP_ERR.ERR_SHOP_DEL_FROM_FAVORITE)
end
end
end
当前角色收藏列表维护
local MAX_FAVORITE_SHOP_COUNT = 10
function human:add_favorite_shop_guid(sg)
if is_null_shop_guid(sg) then
return false
end
if self:is_in_favorite_shop(sg) then
return true
end
local favorite_shop_guids = self:get_favorite_shop_guids()
if #favorite_shop_guids >= MAX_FAVORITE_SHOP_COUNT then
return false
end
table.insert(favorite_shop_guids, clone_shop_guid_value(sg))
return true
end
function human:del_favorite_shop_guid(sg)
if is_null_shop_guid(sg) then
return false
end
local favorite_shop_guids = self:get_favorite_shop_guids()
for i = #favorite_shop_guids, 1, -1 do
if is_same_shop_guid(favorite_shop_guids[i], sg) then
table.remove(favorite_shop_guids, i)
return true
end
end
return true
end
当前列表刷新时的失效收藏清理
function playershopmanager:get_shop_infos(opts)
opts = opts or {}
local human = opts.human
if human then
local valid_shop_guids = {}
for _, shop in ipairs(self.player_shops) do
if is_listed_player_shop(shop) then
table.insert(valid_shop_guids, shop:get_guid())
end
end
human:update_favorite_shop_guids(valid_shop_guids)
end
...
end
当前 Game.exe 读写与包长
__int64 __fastcall CGPlayerShopFavorite_Read(__int64 packet, __int64 stream)
{
read_bytes(stream, packet + 24, 1); // opt
read_bytes(stream, packet + 28, 12); // favorite_id raw bytes
return 1;
}
__int64 __fastcall CGPlayerShopFavorite_Write(__int64 packet, __int64 stream)
{
write_bytes(stream, packet + 24, 1);
write_bytes(stream, packet + 28, 12);
return 1;
}
__int64 CGPlayerShopFavorite_GetPacketID()
{
return 517;
}
__int64 CGPlayerShopFavorite_GetPacketSize()
{
return 13;
}
__int64 CGPlayerShopFavorite_Execute()
{
return 2;
}
结论
CGPlayerShopFavorite(517) 是玩家商店收藏切换请求包,当前版本结构已经可以明确命名为 opt + favorite_id(PlayerShopGUID),固定长度 13 字节。
- 当前服务端只识别
opt = 1 和 opt = 2 两种操作,分别表示添加收藏和取消收藏。
- 成功路径下,现役服务端会连续发送
GCPlayerShopUpdateFavorite(288) 和 GCPlayerShopAcquireShopList(578);添加失败时才会发送 GCPlayerShopError(207),对应错误码 30。
- 当前实现有两个值得明确记录的现状:重复添加收藏会被当作成功处理;删除分支即使目标本来不在收藏列表里也会返回成功,因此错误码
31 在现役主链中基本不会触发。
- 当前服务端不会在收藏请求入口校验店铺 GUID 是否真实存在,而是依赖后续商店列表刷新时的
update_favorite_shop_guids() 清理失效收藏。