GCTeamAskApply 是当前队伍申请链里发给目标队长的提示包。申请者执行 CGTeamApply 后,世界服不会直接让其入队,而是先把申请者的基础信息和角色预览发给队长,由队长客户端弹出审批提示,再通过 CGTeamRetApply 决定是否同意。
当前 Temp_Release_v0-07-0300 里,这条协议既保留在 Game.exe,也确实被 /home/ubuntu/Game2/services/world/worldcore.lua 发送。它的主体结构是“双 GUID + 双名字 + 单人预览信息 + 尾部 token”,其中单人预览块与 GCTeamAskInvite 使用的是同一套外观预览序列化思路。
包信息
| 项目 |
内容 |
| 封包 ID |
644 |
| 包名 |
GCTeamAskApply |
| 方向 |
GC 服务端 -> 队长客户端 |
| 前置来源 |
CGTeamApply |
| 后续回复 |
CGTeamRetApply |
| 当前 Lua 协议定义 |
已定义 |
| 当前 Lua 发送点 |
/home/ubuntu/Game2/services/world/worldcore.lua |
| 主要作用 |
把“某玩家申请加入当前队伍”的审批提示与申请者预览信息发给队长 |
当前 Game.exe 的虚函数结论为:
GCTeamAskApply
GetPacketID() = 644;
GetPacketSize() = 33 + source_name_size + dest_name_size + (detail_flag ? 92 : 0);
Read = 0x140609830;
Write = 0x140609BC0;
Execute thunk = 0x140609820 -> sub_14033F2B0;
包体结构
这条包是“固定头 + 双变长名字 + 单人预览块 + 固定尾”的变长结构。
当前 Game.exe 的 GetPacketSize() 公式为:
33 + source_name_size + dest_name_size + (detail_flag ? 92 : 0)
包体主结构如下:
| 偏移 |
长度 |
字段 |
类型 |
含义 |
0x00 |
0x04 |
source_guid |
int32 |
申请入队者 GUID |
0x04 |
0x04 |
dest_guid |
int32 |
当前队长 GUID |
0x08 |
0x01 |
source_name_size |
uint8 |
申请者名字长度 |
0x09 |
0x01 |
dest_name_size |
uint8 |
队长名字长度 |
0x0A |
source_name_size |
source_name |
char[n] |
申请者名字 |
0x0A+n |
dest_name_size |
dest_name |
char[m] |
队长名字 |
0x0A+n+m |
0x04 |
menpai |
int32 |
申请者门派 |
... |
0x02 |
sceneid |
int16 |
申请者当前场景 ID |
... |
0x04 |
level |
int32 |
申请者等级 |
... |
0x01 |
detail_flag |
uint8 |
是否附带完整外观预览 |
... |
0x02 |
model |
int16 |
申请者模型 ID |
... |
0x04 |
preview_param_1 |
int32 |
角色预览附加参数;当前 Lua 默认 2 |
... |
0x02 |
preview_param_2 |
int16 |
角色预览附加参数;当前 Lua 默认 -1 |
... |
0x5C |
appearance_preview |
byte[92] |
仅当 detail_flag != 0 时写入的完整外观预览 |
... |
0x04 |
apply_token |
uint32 |
申请上下文 token;当前 Lua 默认写 39344113 |
appearance_preview 结构
当 detail_flag != 0 时,后面会继续写入 92 字节外观数据:
| 顺序 |
长度 |
字段 |
类型 |
含义 |
1 |
0x0A |
weapon |
int32 + int32 + uint16 |
武器外观三元组 |
2 |
0x0A |
cap |
int32 + int32 + uint16 |
帽子外观三元组 |
3 |
0x0A |
armour |
int32 + int32 + uint16 |
衣服外观三元组 |
4 |
0x0A |
cuff |
int32 + int32 + uint16 |
护腕外观三元组 |
5 |
0x0A |
foot |
int32 + int32 + uint16 |
鞋子外观三元组 |
6 |
0x04 |
face_style |
int32 |
脸型 |
7 |
0x04 |
hair_style |
int32 |
发型 |
8 |
0x04 |
hair_color |
int32 |
发色 |
9 |
0x12 |
fashion |
int32 * 4 + uint16 |
时装外观组合 |
10 |
0x02 |
exterior_back_id |
uint16 |
背饰 ID |
11 |
0x02 |
exterior_head_id |
uint16 |
头饰 ID |
12 |
0x04 |
exterior_back_pos |
int32 |
背饰位置参数 |
13 |
0x04 |
exterior_head_pos |
int32 |
头饰位置参数 |
当前 Game.exe 读取名字时接受 1..30 字节长度;当前 Lua 发送时使用 < 0x1E 限制,因此实际项目里名字通常应控制在 29 字节以内。
当前实际行为
当前实际业务链如下:
- 玩家发起
CGTeamApply。
- 世界服在
/home/ubuntu/Game2/services/world/worldcore.lua 里检查申请者、目标玩家和目标队伍状态。
- 校验通过后,世界服找到目标队长,构造
GCTeamAskApply 发给队长。
- 队长客户端收到后,生成一条本地待审批申请,并显示
GCTeamAskApplyhandler_Info_Someone_Apply_Team 提示。
- 队长后续再通过
CGTeamRetApply 决定是否同意。
当前 Lua 的发送字段也很直接:
source_guid = 申请者 GUID
dest_guid = 队长 GUID
source_name = 申请者名字
dest_name = 队长名字
uinfo = 申请者 uinfo 的副本
fill_team_preview_detail() 会固定把 detail_flag 置为 1,并补齐武器、帽子、衣服、护腕、鞋子、时装、发型、背饰、头饰等预览字段
当前客户端 Execute() 的关键结论有三点:
- 它会把
source_guid、source_name、申请者预览块和 apply_token 组装成本地申请项。
- 它会显示
GCTeamAskApplyhandler_Info_Someone_Apply_Team,并额外触发一次本地后续流程 sub_140378380(2)。
- 当前观察到的执行逻辑里并没有实际使用
dest_guid 和 dest_name 参与后续分支。
因此,这条包的真正核心是“把申请者是谁、长什么样、能不能展示预览”交给队长客户端,而不是直接完成入队。
服务端落点
当前项目里与这条协议直接相关的落点如下:
- 协议定义:
/home/ubuntu/Game2/services/game/packet.lua
- 申请入口转发:
/home/ubuntu/Game2/services/msgagent.lua
- 世界服发送审批提示:
/home/ubuntu/Game2/services/world/worldcore.lua
- 队长后续回应处理:
/home/ubuntu/Game2/services/world/worldcore.lua
关键代码
当前 Lua 协议定义
packet.GCTeamAskApply = {
xy_id = packet.XYID_GC_TEAM_ASK_APPLY,
bos = function(self)
local stream = bostream.new()
stream:writeint(self.source_guid)
stream:writeint(self.dest_guid)
...
stream:writeuint(uinfo.menpai)
stream:writeushort(uinfo.sceneid)
stream:writeuint(uinfo.level)
stream:writeuchar(uinfo.detail_flag or 0)
stream:writeushort(uinfo.model)
stream:writeint(uinfo.unknow_1 or 2)
stream:writeshort(uinfo.unknow_2 or -1)
if uinfo.detail_flag ~= 0 then
stream:writeint(uinfo.weapon[define.WG_KEY_A])
...
stream:writeint(uinfo.exterior_head_pos or 0)
end
stream:writeuint(uinfo.unknow_15 or 39344113)
return stream:get()
end
}
当前 Lua 发送位置
local msg = packet_def.GCTeamAskApply.new()
msg.source_guid = guid
msg.dest_guid = leader_guid
msg.source_name = me.uinfo.name
msg.dest_name = leader.uinfo.name
msg.uinfo = table.clone(me.uinfo)
fill_team_preview_detail(msg.uinfo, me.uinfo)
self:send2client(leader, msg)
当前 Game.exe 的读写结论
GetPacketID() = 644;
GetPacketSize() = 33 + source_name_size + dest_name_size + (detail_flag ? 92 : 0);
Read(stream) {
read_i32(source_guid);
read_i32(dest_guid);
read_u8(source_name_size);
read_u8(dest_name_size);
read_char_n(source_name, source_name_size);
read_char_n(dest_name, dest_name_size);
read_i32(menpai);
read_i16(sceneid);
read_i32(level);
read_u8(detail_flag);
read_i16(model);
read_i32(preview_param_1);
read_i16(preview_param_2);
if (detail_flag)
read_bytes(92);
read_u32(apply_token);
}
当前 Game.exe 的执行结论
Execute() {
if (client_ctx_ready) {
cache_pending_apply(source_guid, source_name, preview_info, apply_token);
show_text("GCTeamAskApplyhandler_Info_Someone_Apply_Team");
sub_140378380(2);
}
return 0;
}
结论
GCTeamAskApply 在当前 Temp_Release_v0-07-0300 项目里的结论可以直接收敛为:
- 这条包的 ID 是
644。
- 它是发给队长的入队申请提示包,不是最终入队结果包。
- 当前
Game.exe 与 Lua 都确认它是变长结构,长度公式为 33 + source_name_size + dest_name_size + (detail_flag ? 92 : 0)。
- 包里真正关键的是申请者 GUID、申请者名字、申请者预览信息,以及尾部
apply_token。
- 当前 Lua 发送时会固定补全
detail_flag = 1 的完整 92 字节外观预览块。
- 当前客户端执行逻辑会缓存申请项并弹出“有人申请入队”的提示,后续真正是否入队仍取决于
CGTeamRetApply。