CGDiscardItem 是客户端向游戏服提交“丢弃指定格子道具”的固定长度请求包。
当前版本里,这条包的结构很简单,只带两个字节:目标格索引和来源类型。真正的可丢弃判断、锁定判断、交易冲突处理以及最终删除动作,都由服务端在收到请求后完成,并通过 GCDiscardItemResult 返回结果。
包信息
| 项目 |
内容 |
| 封包 ID |
291 |
| 包名 |
CGDiscardItem |
| 方向 |
CG 客户端 -> 游戏服 |
| 包长 |
2 |
| 当前 Lua 协议定义 |
/home/ubuntu/Game2/services/game/packet.lua |
| 当前服务端入口 |
/home/ubuntu/Game2/services/msgagent.lua |
| 当前场景处理 |
/home/ubuntu/Game2/services/scene/scenecore.lua 的 char_discard_item() |
| 主要作用 |
请求丢弃指定来源中的一件道具 |
当前 Game.exe 可直接确认:
GetPacketID() = 291;
GetPacketSize() = 2;
Read() = sub_1405C54F0;
Write() = sub_1405C5540;
Execute() = sub_1403281A0;
包体结构
| 偏移 |
长度 |
字段 |
类型 |
含义 |
0x00 |
0x01 |
bag_index |
uint8 |
要丢弃的目标格索引 |
0x01 |
0x01 |
opt |
uint8 |
来源类型,当前 Lua 定义中 0=FromBag、1=FromBank |
当前 Lua 中的来源枚举为:
OPT = {
FromBag = 0,
FromBank = 1,
}
当前实际行为 / 模式说明
这条包虽然定义了 FromBag 和 FromBank 两种来源,但当前场景层只实际处理:
也就是“从角色道具背包丢弃”。
当前 msgagent 收到请求后,会先检查:
如果未解锁,会直接回操作错误,不进入场景层。
进入 scenecore:char_discard_item() 后,当前实际流程如下:
- 仅当
discard.opt == packet_def.CGDiscardItem.OPT.FromBag 时继续处理
- 刷新锁定道具状态
- 从角色道具背包
prop_bag_container 取出 discard.bag_index 对应物品
- 如果物品正参与交易,会先取消交易,再重新取物品
- 如果物品已锁定,则直接提示“该道具已锁定,无法丢弃”
- 如果物品规则不允许丢弃,则提示“xxx不能丢弃”
- 只有在物品满足
IRL_DISCARD 规则时,才调用 erase_item_by_bag_index() 真正删除
- 最后通过
GCDiscardItemResult 回传处理结果
当前版本里的结果码定义为:
define.DISCARDITEM_RESULT = {
DISCARDITEM_SUCCESS = 0,
DISCARDITEM_FAIL = 1,
}
因此,这条包的重点不是包体复杂,而是服务端会对“是否真的允许丢弃”做完整校验。
服务端落点
- 协议定义:
/home/ubuntu/Game2/services/game/packet.lua
- 请求入口:
/home/ubuntu/Game2/services/msgagent.lua
- 场景处理:
/home/ubuntu/Game2/services/scene/scenecore.lua
- 相关结果回包:
/home/ubuntu/Game2/services/game/packet.lua 中的 GCDiscardItemResult
当前 msgagent 入口逻辑如下:
function request:CGDiscardItem()
if not ma_func:is_minor_password_is_unlock() then
ma_func:send_operate_result_msg(define.OPERATE_RESULT.OR_NEED_UNLOCKMINORPASSWORD)
return
end
skynet.call(my_scene, "lua", "char_discard_item", my_obj_id, self)
end
当前场景层核心处理如下:
function scenecore:char_discard_item(who, discard)
local obj = self.objs[who]
if not obj then
return
end
if discard.opt == packet_def.CGDiscardItem.OPT.FromBag then
obj:refresh_locked_item_unlock_state(false)
local container = obj:get_prop_bag_container()
local item = container:get_item(discard.bag_index)
if item then
-- 交易冲突、锁定、规则校验
-- 满足 IRL_DISCARD 才真正删除
-- 最终回发 GCDiscardItemResult
end
end
end
关键代码
当前 Lua 协议定义
packet.CGDiscardItem = {
xy_id = packet.XYID_CG_DISCARD_ITEM,
OPT = {
FromBag = 0,
FromBank = 1,
},
bis = function(self, buffer)
local stream = bistream.new()
stream:attach(buffer)
self.bag_index = stream:readuchar()
self.opt = stream:readuchar()
end
}
当前结果回包
当前 GCDiscardItemResult 结构为:
| 偏移 |
长度 |
字段 |
类型 |
含义 |
0x00 |
0x01 |
result |
uint8 |
丢弃结果码,0=成功、1=失败 |
0x01 |
0x01 |
bag_index |
uint8 |
原背包格索引 |
0x02 |
0x01 |
opt |
uint8 |
来源类型 |
0x03 |
0x04 |
item_index |
int32 |
被处理物品的物品表索引 |
这说明客户端最终展示结果时,不只是看成功失败,还会同步知道“哪一格、哪一种来源、哪一个物品索引”被处理。
当前 Game.exe 关键结论
当前客户端包类的读写顺序与 Lua 完全一致:
Read(stream) {
read_u8(bag_index);
read_u8(opt);
return 1;
}
Write(stream) {
write_u8(bag_index);
write_u8(opt);
return 1;
}
GetPacketSize() {
return 2;
}
Execute() {
return 2;
}
其中 Read() 在当前 Game.exe 里对应两次 1 字节读取,顺序就是:
read(stream, this + 0x19, 1);
read(stream, this + 0x18, 1);
结合当前 Lua 定义,可以明确还原成:
bag_index:uint8;
opt:uint8;
结论
CGDiscardItem 在当前 Temp_Release_v0-07-0300 里的最终结论可以直接收敛为:
- 这是一条固定长度
2 字节的客户端请求包。
- 包体字段只有:
- 当前协议枚举里虽然保留了
FromBag 与 FromBank,但当前场景层只真实处理 FromBag。
- 服务端会在真正删除前执行交易冲突、锁定状态和道具规则校验,不满足条件时不会删除。
- 最终处理结果通过
GCDiscardItemResult 回包返回。
- 当前
Game.exe 与当前 Lua 对这条包的结构判断完全一致,不需要借助旧 Server 才能确定包体。