CGAskLeanAbility 是当前版本里用于提交生活技能升级请求的客户端封包。虽然名字里仍保留了 LeanAbility 这种旧写法,但现役 /home/ubuntu/Game2 Lua 主链的实际行为不是“首次学习生活技能”,而是“对已学会的生活技能申请升到下一等级”。
包信息
| 项目 |
内容 |
| 封包 ID |
469 |
| 包名 |
CGAskLeanAbility |
| 方向 |
CG 客户端 -> 游戏服 |
| 固定包长 |
18 |
| 常见后续封包 |
GCAbilityLevel / GCAbilityTeacherInfo |
| 后续封包 ID |
760 / 731 |
| 当前 Lua 状态 |
现役协议;由场景服 ask_lean_ability 处理 |
| 作用 |
提交某个生活技能的升级申请,并附带客户端界面上缓存的消耗参数 |
当前 Game.exe 可直接确认:
GetPacketID() = 469;
GetPacketSize() = 18;
Read() = read_u16 + read_4x_u32;
Write() = write_u16 + write_4x_u32;
Execute() = 2;
包体结构
当前 Game.exe 与现役 Lua packet.lua 一致确认,这条包是固定 18 字节结构:
| 偏移 |
长度 |
字段 |
类型 |
含义 |
0x00 |
0x02 |
m_nAbilityID |
uint16 |
目标生活技能 ID |
0x02 |
0x04 |
m_nNeedMoney |
uint32 |
客户端界面缓存的升级所需金钱 |
0x06 |
0x04 |
m_nNeedExp |
uint32 |
客户端界面缓存的升级所需角色经验 |
0x0A |
0x04 |
m_nNeedSkillExp |
uint32 |
客户端界面缓存的升级所需技能熟练度 |
0x0E |
0x04 |
m_iNpcID |
uint32 |
当前交互导师 NPC 的场景对象 ID |
总长度固定为:
18
当前实际行为
- 客户端发包后,
msgagent 会把角色对象 ID、NPC 对象 ID、技能 ID 和三项消耗参数一起转交给场景服的 ask_lean_ability。
- 场景服随后调用人物对象的
human:ask_lean_ability() 执行真正的升级逻辑。
- 当前现役 Lua 首先要求该生活技能已经存在;如果
self:get_ability(ability_id) 取不到,就直接提示“你还没有学会该生活技能”并返回。
- 如果
m_iNpcID 非 0,当前 Lua 还会校验该 NPC 对象当前仍在场景里存在;不存在则提示“目标不存在”。
- 现役 Lua 会按配置表重新计算下一等级真正需要的:
- 因此
m_nNeedMoney / m_nNeedExp / m_nNeedSkillExp 当前更像“客户端界面缓存的回显值”,不会直接作为服务端最终判定依据。
- 真正的校验顺序是:
- 角色等级是否达到要求
- 金钱是否足够
- 角色经验是否足够
- 技能熟练度是否足够
- 通过校验后,服务端会扣钱、扣经验、提升技能等级、刷新角色属性,并补发两类后续封包:
GCAbilityLevel(760):同步该生活技能的新等级
GCAbilityTeacherInfo(731):刷新导师界面下一次升级所需数据
- 当前 Lua 仍会把客户端传来的三项消耗值带进
cost_money_with_priority(..., "CGAskLeanAbility", {...}) 的上下文里,但实际扣费金额仍使用服务端重新计算的 need_money。
- 当前
Game.exe 的 Execute() 只是空壳返回 2,没有额外本地业务逻辑。
这也说明一件容易误判的事:CGAskLeanAbility 并不是“单包即可决定升级结果”的封包,当前版本里的真正结果表现是由提示文本、GCAbilityLevel 与 GCAbilityTeacherInfo 组合完成的。
服务端落点
- 协议定义:
/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
关键代码
当前 Lua 协议定义
packet.CGAskLeanAbility = {
xy_id = packet.XYID_CG_ASK_LEAN_ABILITY,
ctor = function(self)
self.m_nAbilityID = 0
self.m_nNeedMoney = 0
self.m_nNeedExp = 0
self.m_nNeedSkillExp = 0
self.m_iNpcID = 0
end,
bis = function(self, buffer)
local stream = bistream.new()
stream:attach(buffer)
self.m_nAbilityID = stream:readushort()
self.m_nNeedMoney = stream:readuint()
self.m_nNeedExp = stream:readuint()
self.m_nNeedSkillExp = stream:readuint()
self.m_iNpcID = stream:readuint()
end
}
当前 Lua 请求入口
function request:CGAskLeanAbility()
skynet.call(
my_scene,
"lua",
"ask_lean_ability",
my_obj_id,
self.m_iNpcID,
self.m_nAbilityID,
self.m_nNeedMoney,
self.m_nNeedExp,
self.m_nNeedSkillExp
)
end
function scenecore:ask_lean_ability(whos, npc_id, ability_id, need_money, need_exp, need_skill_exp)
local human = self:get_obj_by_id(whos)
if not human then
return
end
human:ask_lean_ability(npc_id, ability_id, need_money, need_exp, need_skill_exp)
end
当前 Lua 的现役升级逻辑
function human:ask_lean_ability(npc_id, ability_id, client_need_money, client_need_exp, client_need_skill_exp)
local ability = self:get_ability(ability_id)
if not ability then
self:notify_tips("你还没有学会该生活技能。")
return false
end
if npc_id and npc_id ~= 0 and not self:get_scene():get_obj_by_id(npc_id) then
self:notify_tips("目标不存在。")
return false
end
local next_level = ability.level + 1
local level_config = self:get_ability_level_up_config(ability_id, next_level)
if not level_config then
self:notify_tips("当前技能已无法继续升级。")
return false
end
local need_money = tonumber(level_config["需求金钱"]) or 0
local need_exp = tonumber(level_config["需求经验"]) or 0
local need_skill_exp = tonumber(level_config["技能熟练度限制"]) or 0
local need_level = tonumber(level_config["等级限制"]) or 0
if self:get_attrib("level") < need_level then
self:notify_tips("你的等级不够。")
return false
end
if not self:check_money_with_priority(need_money) then
self:notify_tips("#{no_money}")
return false
end
if self:get_exp() < need_exp then
self:notify_tips("经验不足。")
return false
end
if self:get_ability_exp(ability_id) < need_skill_exp then
self:notify_tips("技能熟练度不足。")
return false
end
local ok = self:cost_money_with_priority(need_money, "CGAskLeanAbility", {
ability_id = ability_id,
npc_id = npc_id,
client_need_money = client_need_money,
client_need_exp = client_need_exp,
client_need_skill_exp = client_need_skill_exp,
level = next_level,
})
if not ok then
self:notify_tips("#{no_money}")
return false
end
self:set_exp(self:get_exp() - need_exp)
self:study_ability(ability_id, ability.exp or 0, next_level)
self:send_refresh_attrib()
self:send_ability_teacher_info(npc_id, ability_id)
return true
end
当前 Game.exe 的读写与执行结论
GetPacketID() = 469;
GetPacketSize() = 18;
Read(stream) {
read_u16(m_nAbilityID);
read_u32(m_nNeedMoney);
read_u32(m_nNeedExp);
read_u32(m_nNeedSkillExp);
read_u32(m_iNpcID);
}
Write(stream) {
write_u16(m_nAbilityID);
write_u32(m_nNeedMoney);
write_u32(m_nNeedExp);
write_u32(m_nNeedSkillExp);
write_u32(m_iNpcID);
}
Execute() {
return 2;
}
结论
CGAskLeanAbility(469) 是固定 18 字节的生活技能升级请求包,字段结构为 uint16 + 4 * uint32。
- 当前现役 Lua 并不把它当成“首次学习生活技能”入口,而是要求目标技能已经存在,随后执行“升到下一等级”的校验与扣费。
- 包里的
m_nNeedMoney / m_nNeedExp / m_nNeedSkillExp 当前不会直接决定服务端结果,服务端会重新按配置表计算真实需求。
m_iNpcID 当前主要用于校验导师 NPC 是否存在,以及升级成功后刷新 GCAbilityTeacherInfo(731)。
- 升级成功后的关键后续封包是
GCAbilityLevel(760) 与 GCAbilityTeacherInfo(731);失败时则主要通过提示文本反馈,而不是专门的失败回包。