GCTeamResult 是当前旧版项目队伍主链里的结果广播包。它由世界服构造,先交给场景服更新本地 team_info,再按场景服返回值决定是否继续转发给客户端。因此它既是服务端跨服通知对象,也是实际会发到客户端的队伍事件同步包。
当前 Game.exe 可以明确确认这条包的基础头是“固定 24 字节 + 名字变长”,并且仅在 return_type == 0 或 8 时额外附带一段 44 字节 TeamMemberInfo 尾段。结合当前 Lua 主链,现役发送类型主要是入队、离队、踢人、任命新队长、解散队伍、开始切场景和进入新场景。
包信息
| 项目 |
内容 |
| 封包 ID |
793 |
| 包名 |
GCTeamResult |
| 方向 |
GC 服务端 -> 客户端 |
| 包长模式 |
动态长度 |
| 最小包长 |
24 |
| 最大包长 |
98 |
| 当前 Lua 状态 |
现役协议;世界服构造后,经 msgagent -> scenecore -> human:update_team_info() 处理,再视返回值转发客户端 |
| 主要作用 |
同步队伍事件结果,驱动客户端队伍状态刷新与成员场景状态变更 |
当前 Game.exe 的虚函数结论为:
GetPacketID() = 793;
GetPacketSize() = name_size + 24 + ((return_type == 0 || return_type == 8) ? 44 : 0);
Execute() = 2;
包体结构
基础头结构如下:
| 偏移 |
长度 |
字段 |
类型 |
含义 |
0x00 |
0x01 |
return_type |
uint8 |
队伍结果类型 |
0x01 |
0x04 |
guid |
int32 |
事件主体 GUID;不同 return_type 下表示入队者、离队者、切场景者等当前事件主体 |
0x05 |
0x02 |
team_id |
uint16 |
队伍 ID |
0x07 |
0x04 |
guid_ex |
int32 |
附加 ID;当前按结果类型复用,入队同步时还可能在场景服转发前被改写为成员 obj_id |
0x0B |
0x02 |
sceneid |
int16 |
相关场景 ID |
0x0D |
0x02 |
client_res |
int16 |
场景客户端资源 ID |
0x0F |
0x01 |
name_size |
uint8 |
名字长度,当前客户端允许 0..30 |
0x10 |
char[n] |
name |
char[n] |
队员名字 |
0x10 + n |
0x04 |
portrait_id |
uint32 |
头像 ID |
0x14 + n |
0x02 |
model |
uint16 |
当前 Lua 实际写入 uinfo.model;旧实现里的 m_uDataID 可作为参考命名 |
0x16 + n |
0x02 |
reserved_1 |
int16 |
当前 Game.exe 会继续传给界面层;Lua 默认写 -1,未见现役发送点赋予明确语义 |
当 return_type == 0 或 8 时,还会在基础头后继续附带一段固定 44 字节的 TeamMemberInfo:
| 偏移 |
长度 |
字段 |
类型 |
含义 |
0x18 + n + 0x00 |
0x04 |
member_info.model |
uint32 |
队员模型 ID |
0x18 + n + 0x04 |
0x04 |
member_info.menpai |
uint32 |
门派 ID |
0x18 + n + 0x08 |
0x04 |
member_info.level |
uint32 |
等级 |
0x18 + n + 0x0C |
0x04 |
member_info.reserved_1 |
uint32 |
当前 Lua 默认 0 |
0x18 + n + 0x10 |
0x04 |
member_info.reserved_2 |
uint32 |
当前 Lua 默认 259 |
0x18 + n + 0x14 |
0x04 |
member_info.reserved_3 |
uint32 |
当前 Lua 默认 0 |
0x18 + n + 0x18 |
0x04 |
member_info.fixed_zero |
int32 |
当前 Lua 固定写 0 |
0x18 + n + 0x1C |
0x04 |
member_info.reserved_4 |
uint32 |
当前 Lua 默认 8 |
0x18 + n + 0x20 |
0x04 |
member_info.fixed_neg1_a |
int32 |
当前 Lua 固定写 -1 |
0x18 + n + 0x24 |
0x04 |
member_info.fixed_neg1_b |
int32 |
当前 Lua 固定写 -1 |
0x18 + n + 0x28 |
0x04 |
member_info.fixed_neg1_c |
int32 |
当前 Lua 固定写 -1 |
长度公式可写成:
普通结果包: size = 24 + name_size
入队/进场景: size = 68 + name_size
老 Server 可作为参考:旧实现里基础字段名依次是 m_Return / m_GUID / m_TeamID / m_GUIDEx / m_SceneID / m_SceneResID / m_Name / m_nPortrait / m_uDataID。但旧实现没有当前 Game.exe 里的额外 reserved_1 和 44 字节 TeamMemberInfo 尾段,不能直接视为当前版本最终结构。
当前实际行为
当前现役发送类型与用途如下:
return_type |
常量 |
当前用途 |
是否附带 TeamMemberInfo |
0 |
TEAM_RESULT_MEMBERENTERTEAM |
新成员入队通知 |
是 |
1 |
TEAM_RESULT_MEMBERLEAVETEAM |
普通成员退队 |
否 |
2 |
TEAM_RESULT_LEADERLEAVETEAM |
队长离队并转让队长 |
否 |
3 |
TEAM_RESULT_TEAMDISMISS |
队伍解散 |
否 |
4 |
TEAM_RESULT_TEAMKICK |
踢出队员 |
否 |
5 |
TEAM_RESULT_TEAMAPPOINT |
任命新队长 |
否 |
7 |
TEAM_RESULT_STARTCHANGESCENE |
队员开始切换场景 |
否 |
8 |
TEAM_RESULT_ENTERSCENE |
队员进入新场景 |
是 |
当前可见代码里没有找到这些类型的世界服现役发送点:
return_type |
常量 |
当前状态 |
6 |
TEAM_RESULT_TEAMREFRESH |
仅保留在枚举和场景更新逻辑中,未见当前世界服发送 |
9 |
TEAM_RESULT_REFRESHSERVERINFO |
仅保留在枚举中,未见当前发送链 |
10 |
TEAM_RESULT_MEMBEROFFLINE |
场景更新逻辑保留分支,但未见当前世界服发送 |
这条包的当前服务端行为还有几个关键点:
- 世界服先构造
GCTeamResult,再交给 msgagent。
msgagent 不会直接发送,而是先调用场景服 on_notify_team_result()。
- 场景服里的
human:update_team_info() 会先更新本地队伍缓存,再决定是否返回 true 允许继续转发客户端。
leader_guid 和 uinfo 是服务端内部辅助字段,不在 packet.GCTeamResult.bos() 的网络序列化里。
TEAM_RESULT_MEMBERENTERTEAM(0) 分支中,场景服会在转发前把 guid_ex 改写为队员当前 obj_id 或 INVAILD_ID。
- 对事件主体本人,
TEAM_RESULT_MEMBERENTERTEAM(0)、TEAM_RESULT_STARTCHANGESCENE(7)、TEAM_RESULT_ENTERSCENE(8) 当前可能只更新服务端 team_info 而不转发客户端,因为 human:update_team_info() 会返回 false。
服务端落点
- 协议定义与序列化:
/home/ubuntu/Game2/services/game/packet.lua
- 世界服构造与广播:
/home/ubuntu/Game2/services/world/worldcore.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/team.lua
关键代码
当前 Lua 的网络序列化
stream:writeuchar(self.return_type)
stream:writeint(self.guid)
stream:writeushort(self.team_id)
stream:writeint(self.guid_ex)
stream:writeshort(self.sceneid)
stream:writeshort(self.client_res)
stream:writeuchar(self.name_size)
if self.name_size > 0 and self.name_size <= 0x1E then
stream:write(self.name, self.name_size)
end
stream:writeuint(self.portrait_id)
stream:writeushort(self.model or self.sex or 0)
stream:writeshort(self.unknow_7)
if self.return_type == 8 or self.return_type == 0 then
packet.TeamMemberInfo.bos(self.uinfo, stream)
end
当前 msgagent 的转发门槛
elseif xy_id == packet_def.GCTeamResult.xy_id then
ret = on_notify_team_result(packet)
end
if ret then
Net:send2client(xy_id, packet, my_obj_id)
end
场景服在入队同步时会改写 guid_ex
if msg.return_type == define.TEAM_RESULT.TEAM_RESULT_MEMBERENTERTEAM
or msg.return_type == define.TEAM_RESULT.TEAM_RESULT_TEAMREFRESH then
local mb = {}
mb.guid = msg.guid
mb.sceneid = msg.sceneid
if mb.guid == self:get_guid() then
mb.m_objID = self:get_obj_id()
elseif mb.sceneid == self.scene:get_id() then
local obj = self.scene:get_obj_by_id(msg.guid_ex)
if obj then
mb.m_objID = obj:get_obj_id()
end
bNotifyTeamInfoFlag = true
else
mb.m_objID = define.INVAILD_ID
bNotifyTeamInfoFlag = true
end
msg.guid_ex = mb.m_objID
end
当前 Game.exe 的长度判定
UINT GetPacketSize() {
return name_size + 24 + ((return_type == 0 || return_type == 8) ? 44 : 0);
}
结论
GCTeamResult(793) 是当前旧版项目里仍在实际使用的队伍结果广播包,不是单纯的客户端遗留协议。
- 当前
Game.exe 明确确认它的网络结构是“基础头 24 + name_size”,并且只有 return_type == 0 或 8 时才追加 44 字节 TeamMemberInfo 尾段。
- 这条包先在场景服更新本地
team_info,再按返回值决定是否转发客户端,因此同一个 GCTeamResult 既承担服务端内部同步,也承担实际客户端通知。
guid_ex 不是固定语义字段,而是按结果类型复用;其中入队同步时,场景服还会在转发前把它改写为成员 obj_id。
- 老 Server 只适合提供命名参考;当前版本最终应以当前 Lua 主链和当前
Game.exe 的 Read / Write / GetPacketSize / Execute 结论为准。
|