找回密码
 register
搜索
查看: 34|回复: 0

洱海月 帮贡牌兑换功能无法正常兑换 只改服务端:从永远提示帮贡不足到二次确认无反应的完整排障

[复制链接]
  • 打卡等级:本地老炮
  • 打卡总天数:533
  • 打卡月天数:22
  • 打卡总奖励:530
  • 最近打卡:2026-06-24 01:45:59
Waylee 发表于 2026-4-8 17:50 | 显示全部楼层 |阅读模式 | Google Chrome | Windows 10

一、先说结论

这次我要修的是一个很典型的“界面能打开,但真正提交链路断掉”的老问题。

问题现象有两个阶段:

  • 第一阶段:帮贡牌兑换 界面能正常打开,但输入合法帮贡后,服务端永远提示“您输入的帮贡数量大于您拥有的帮贡数量”。
  • 第二阶段:前面的提示修掉之后,二次确认界面点“确定”又变成完全没反应。

这类问题最容易误判成“客户端没发包”或者“服务端缺整套功能”。
但这次结合当前 Game.exe、现网 Lua 服务端,以及老 Server 的 IDA 结果看下来,结论非常明确:

  • 不是客户端坏了。
  • 也不是 pc_guilddirector.lua 自己的兑换逻辑没写。
  • 真正断掉的是“当前客户端实际发包路径”和“Lua 服务端实际接包路径”之间的兼容层。

并且这个问题只能改服务端,不需要也不应该改客户端。

二、为什么我说不要改客户端

先看当前客户端真实代码。

文件:

  • \Interface\BanggongExchange\BanggongExchange.lua
function BanggongExchange_OK_Clicked()  
    local str = BanggongExchange_Moral_Value:GetText()
    local strNumber = 0

    if str == nil or str == "" then
        return
    end

    strNumber = tonumber(str)

    if strNumber > Guild:GetGuildContri() then
        PushDebugMessage("#{BGCH_8829_03}")
        return
    end

    --帮贡牌兑换最大不能超过200点
    if strNumber > g_ExchangeMaxBangGong then
        PushDebugMessage("#{BGCH_8922_25}")
        return
    end

    --帮贡牌兑换最小不能低于10点
    if strNumber < g_ExchangeMinBangGong then
        PushDebugMessage("#{BGCH_8922_26}")
        return
    end

--  Clear_XSCRIPT();
--      Set_XSCRIPT_Function_Name("BanggongExchange");
--      Set_XSCRIPT_ScriptID(805009);
--      Set_XSCRIPT_Parameter(0,strNumber);
--      Set_XSCRIPT_ParamCount(1);
--  Send_XSCRIPT();

    Guild:ExchangeBangGong(strNumber,g_clientNpcId)

    BanggongExchange_Close()
end

这里最关键的一点就是最后这句:

Guild:ExchangeBangGong(strNumber,g_clientNpcId)

也就是说,当前客户端已经不再直接走注释掉的旧 XSCRIPT 代码了。

帮会捐钱也是同样的写法。

文件:

  • \Interface\ConfraternityJuanxian\ConfraternityJuanxian.lua
--  Clear_XSCRIPT();
--      Set_XSCRIPT_Function_Name("PutGuildMoney");
--      Set_XSCRIPT_ScriptID(805012);
--      Set_XSCRIPT_Parameter(0,strNumber);
--      Set_XSCRIPT_ParamCount(1);
--  Send_XSCRIPT();

    Guild:PutGuildMoney(strNumber,g_clientNpcId)

所以这次修复的思路一定要立住:

  1. 一切以当前 Game.exe 和当前客户端脚本为准。
  2. 不要去改客户端 Lua。
  3. 只把服务端缺失的兼容层补完整。

三、问题真正卡在了哪三层

这次实际卡了三层。

1. CGEventRequest 兼容字段没补

帮会相关 UI 在某些流程里还是会经过 CGEventRequest 风格包装。
而现有 Lua 版 packet.lua 只解析了老字段名,没有补业务层常用别名,导致场景层拿参数时很容易拿错。

2. 二次确认实际走的是 CGExecuteScript

这一步是最容易踩坑的地方。

前面“能弹二次确认框”并不代表真正的提交也走同一条链。
当前客户端的二次确认,实际是 CGExecuteScript,而不是 CGEventRequest

Lua 服务端如果还沿用旧的 char_excute_Script 生硬直转,或者函数名里还带 \0 结尾,最后就会出现:

  • 白名单过不去
  • 函数名匹配不上
  • 或者脚本参数进错位置

表面现象就是“点了确定没反应”。

3. 二级密码解锁状态没有同步到场景对象

这个是最后的真拦路虎。

pc_guilddirector.luacity0_building5.lua 在执行兑换、捐献时,都会过:

self:IsPilferLockFlag(selfId)

但原来的 Lua 版服务端,场景里的 human 对象并没有真正维护“当前是否已解锁二级密码”这个状态。
结果就是玩家明明在网关侧已经解锁了,脚本层依然会把他当成未解锁,直接提前返回。

于是就出现了最迷惑的现象:

  • 客户端点确定了
  • 服务端也收到请求了
  • 但脚本里提前 return
  • 玩家看到的结果就是“没有任何反应”

四、修复目标

我这次修复遵循 4 个原则:

  • 不修改客户端。
  • 一切以当前 Game.exe 的行为为准。
  • 只补服务端兼容层,不重做整套帮会系统。
  • 顺手把“静默失败”改成“有提示失败”,方便后续排错。

五、涉及到的文件

本次真正和 帮贡牌兑换 修复直接相关的文件如下:

  • \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\script_base.lua

为了避免修完二级密码后影响其他旧功能,我还顺手修了三处历史反向判断:

  • \home\ubuntu\Game2\services\scripts\event\equip\wuhun.lua
  • \home\ubuntu\Game2\services\scripts\event\loulangucheng\eloulan_darkup.lua
  • \home\ubuntu\Game2\services\scripts\obj\luoyang\oluoyang_zhouran.lua

六、第一步:补 packet.lua 的协议兼容

文件:

  • \home\ubuntu\Game2\services\game\packet.lua

1. 修改 packet.CGExecuteScript

bis 改成下面这样,重点是把结尾的 \0 去掉:

***付费内容***

如果这一步不做,很容易出现:

  • 白名单明明配了函数名,但匹配不上
  • BanggongExchange\0BanggongExchange 被当成两个名字

2. 修改 packet.CGEventRequest

bis 改成下面这样,重点是补四个别名字段:

***付费内容***

这一步的意义很简单:

  • 老代码习惯用 m_objID/index/arg/unknow
  • 新兼容逻辑更适合统一用 script_id/ex_index/npc_obj_id/issue_script_id

别名补上之后,后面的场景层就可以不用反复猜字段。

七、第二步:在场景层补帮会 UI 的包装桥

文件:

  • \home\ubuntu\Game2\services\scene\scenecore.lua

这一段是本次修复里最关键的第一刀。
我建议直接把下面这整段加进去。

***付费内容***

然后把 char_event_request 改成下面这样:

***付费内容***

这一步修完之后,前面“永远提示帮贡不足”的问题通常就能消掉。
因为服务端终于能从包装过的事件参数里,把真正的兑换数量提出来了。

八、第三步:把 CGExecuteScript 路由改对

文件:

  • \home\ubuntu\Game2\services\msgagent.lua

1. 先加辅助函数

把下面这段加进去:

local function check_cg_execute_script_allow(script_id, func_name)
    local configs = configenginer:get_config("allowable_script_func")
    if not configs then
        return false, "config_not_loaded"
    end

    local script_funcs = configs[script_id]
    if not script_funcs then
        return false, "script_id_not_allowed"
    end

    if script_funcs[func_name] ~= true then
        return false, "func_name_not_allowed"
    end

    return true
end

local function log_cg_execute_script_reject(script_id, func_name, reason)
    local my_data = ma_func:get_my_data()
    local guid = my_data and my_data.attrib and my_data.attrib.guid or "nil"
    local name = my_data and my_data.attrib and my_data.attrib.name or "nil"
    skynet.loge("CGExecuteScript blocked:",
        "guid =", guid,
        ";name =", name,
        ";script_id =", script_id,
        ";func_name =", tostring(func_name),
        ";reason =", reason)
end

local function notify_cg_execute_script_reject(reason)
    if reason == "invalid_packet" then
        return
    end
    ma_func:notify_tips("该功能当前暂未开放,请联系管理员处理")
end

local function normalize_cg_execute_script_func_name(func_name)
    if type(func_name) ~= "string" then
        return func_name
    end
    local zero_pos = string.find(func_name, "\0", 1, true)
    if zero_pos then
        func_name = string.sub(func_name, 1, zero_pos - 1)
    end
    return func_name
end

2. 用新的 request:CGExecuteScript() 覆盖旧逻辑

把原来的 request:CGExecuteScript() 换成下面这段:

function request:CGExecuteScript()
    local script_id = self.m_ScriptID
    local func_name = normalize_cg_execute_script_func_name(self.m_szFunName)
    self.m_szFunName = func_name
    if not script_id or not func_name or func_name == "" then
        log_cg_execute_script_reject(script_id, func_name, "invalid_packet")
        notify_cg_execute_script_reject("invalid_packet")
        return
    end

    local allow, reason = check_cg_execute_script_allow(script_id, func_name)
    if not allow then
        log_cg_execute_script_reject(script_id, func_name, reason)
        notify_cg_execute_script_reject(reason)
        return
    end

    skynet.call(my_scene, "lua", "execute_client_script", my_obj_id, script_id, func_name, table.unpack(self.m_aParam))
end

这里我特意不再走老的:

char_excute_Script

原因很简单:

  • 这次我们要在场景层对 BanggongExchangePutGuildMoney 做参数纠正。
  • 还要对缺脚本、缺函数做兜底提示。
  • 所以必须单独走一个新的 execute_client_script 入口。

九、第四步:给场景层增加 execute_client_script

文件:

  • \home\ubuntu\Game2\services\scene\scenecore.lua

把下面这两个函数加进去:

***付费内容***

这段代码我建议原样抄。

它解决了三件事:

  1. 二次确认真正进到了正确脚本。
  2. whoamount 的参数顺序被重新拉正。
  3. 后续如果客户端发到了一个服务端未开放脚本,也不会再表现成“没反应”,而是明确提示玩家。

十、第五步:把二级密码解锁状态同步到场景对象

到这一步为止,很多人会以为已经修好了。
但实际上,帮贡牌兑换 真正提交时还要经过物品锁校验。

如果你不补这一层,就会出现:

  • 日志里已经能看到 execute_client_script
  • 玩家也点了确定
  • 但脚本就是不往下走

1. 给 human.lua 增加场景态字段

文件:

  • \home\ubuntu\Game2\services\scene\obj\human.lua

在初始化位置补一个字段:

self.minor_password_is_unlock = false

然后新增 getter / setter:

function human:set_minor_password_is_unlock(is_unlock)
    self.minor_password_is_unlock = is_unlock == true
end

function human:is_minor_password_is_unlock()
    return self.minor_password_is_unlock == true
end

2. 改写 script_base:IsPilferLockFlag

文件:

  • \home\ubuntu\Game2\lualib\script_base.lua

直接改成下面这样:

function script_base:IsPilferLockFlag(selfId)
    local human = self.scene:get_obj_by_id(selfId)
    if not human or human:get_obj_type() ~= "human" then
        return false
    end
    if human:is_minor_password_is_unlock() then
        return true
    end
    human:send_operate_result_msg(define.OPERATE_RESULT.OR_NEED_UNLOCKMINORPASSWORD)
    return false
end

这一步就是整次修复里最关键的最后一刀。

因为 pc_guilddirector.lua 本身就是这样写的:

function pc_guilddirector:BanggongExchange(selfId, nvalue)
    local haveBangGong = self:CityGetAttr(selfId, ScriptGlobal.GUILD_CONTRIB_POINT)
    if not self:IsPilferLockFlag(selfId) then
        return
    end
    if nvalue > haveBangGong then
        self:NotifyFailTips(selfId, "#{BGCH_8829_03}")
        return
    end

city0_building5.lua 也是同样道理:

function city0_building5:PutGuildMoney(selfId, money)
    ...
    if not self:IsPilferLockFlag(selfId) then return end
    ...
end

也就是说,只要 IsPilferLockFlag() 判断错了,兑换和捐献都会在最前面被拦死。

3. 在 msgagent.lua 里补状态同步

文件:

  • \home\ubuntu\Game2\services\msgagent.lua

先加一个同步函数:

local function sync_minor_password_unlock_state_to_scene()
    if my_scene == nil or my_obj_id == nil then
        return
    end
    local ok, err = pcall(
        skynet.call,
        my_scene,
        "lua",
        "set_minor_password_unlock_state",
        my_obj_id,
        ma_func:is_minor_password_is_unlock()
    )
    if not ok then
        skynet.loge(
            "sync_minor_password_unlock_state_to_scene failed:",
            "scene =",
            my_scene,
            ";obj_id =",
            my_obj_id,
            ";err =",
            err
        )
    end
end

然后在以下三个时机调用它:

1. 玩家进入场景后

ma_func:on_enter_scene(my_scene, my_obj_id, sceneid)
sync_minor_password_unlock_state_to_scene()

2. 设置二级密码成功后

minor_password = { password = self.password }
ma_func:set_minor_password(minor_password)
sync_minor_password_unlock_state_to_scene()
msg.m_Type = define.MINORPASSWD_RETURN_TYPE.MRETT_SETPASSWDSUCC

3. 解锁密码成功或失败时

if self.password ~= minor_password.password then
    ma_func:clear_minor_password_is_unlock()
    sync_minor_password_unlock_state_to_scene()
    msg.m_Type = define.MINORPASSWD_RETURN_TYPE.MRETT_ERR_UNLOCKPASSWDFAIL
else
    ma_func:set_minor_password_is_unlock()
    sync_minor_password_unlock_state_to_scene()
    msg.m_Type = define.MINORPASSWD_RETURN_TYPE.MRETT_UNLOCKPASSWDSUCC
end

这样一来:

  • 网关侧知道玩家是否已解锁
  • 场景里的 human 也知道
  • 脚本层 IsPilferLockFlag() 才能判断正确

十一、第六步:修一下这三个历史反向判断

这一步不是专门为 帮贡牌兑换 写的,但既然我们把 IsPilferLockFlag() 语义修正了,就必须顺手把几个历史错误调用改过来。

正确写法应该是:

if not self:IsPilferLockFlag(selfId) then return end

我这次顺手改了这三处:

1. services/scripts/event/equip/wuhun.lua

function wuhun:KfsCompoud(selfId, nKfsMain, nKfsCom)
    if not self:IsPilferLockFlag(selfId) then return end
    ...
end

2. services/scripts/event/loulangucheng/eloulan_darkup.lua

function eloulan_darkup:Anqi2Shenzhen(selfId, nPos, nMaterial)
    if not self:IsPilferLockFlag(selfId) then return end
    ...
end

3. services/scripts/obj/luoyang/oluoyang_zhouran.lua

function oluoyang_zhouran:CheckBuyLuoyang(selfId, targetId)
    if not self:IsPilferLockFlag(selfId) then return 0 end
    ...
end

如果你只修 帮贡牌兑换,这三处不是强依赖。
但如果你已经把 IsPilferLockFlag() 改正确了,这三个地方不改,后面早晚会炸出新的“解锁后反而不能操作”的老坑。

十二、为什么老 Server 只能参考思路,不能直接照搬

我这次还用 IDA 看了老 Server

老 C++ 版里,CGExecuteScriptHandler::Execute 的核心思路是:

  • 先过 AllowableScriptFunc
  • 再按参数个数进 LuaInterface::ExeScript_*

这只能说明一件事:

  • 当前客户端二次确认确实是脚本调用链
  • 方向没有错

但它不能直接拿来抄,因为我们现在跑的是 Lua 化后的 skynet 服务端,接包点、场景对象、脚本引擎接口都已经变了。

所以我的做法是:

  • 用老 Server 确认“行为方向”
  • 真正落地代码时,只改现网 Lua 服务端

这才是最稳的修法。

十三、修完后的验证方法

我建议按下面顺序验证:

1. 帮贡牌兑换

  1. 登录一个已经加入帮会的角色。
  2. 找到帮会总管,打开 帮贡牌兑换
  3. 输入一个合法数值,例如 10
  4. 点确认。
  5. 如果角色有二级密码,先完成解锁。
  6. 再次点击确认。

正常结果:

  • 不再提示“您输入的帮贡数量大于您拥有的帮贡数量”
  • 二次确认后真正执行兑换

2. 看日志

如果修好了,场景日志里通常能看到类似:

scenecore:execute_client_script banggong exchange who = 16 amount = 10

如果这里能打出来,说明:

  • CGExecuteScript 路由已经通了
  • 参数位置也对了

3. 顺手测一下帮会捐钱

因为 PutGuildMoneyBanggongExchange 这次走的是同一套兼容思路,所以最好顺手也测一下。

十四、如果你只想最快抄代码,最少要改哪些地方

如果你只想最小闭环,我建议至少改下面这些:

必改

  • services/game/packet.lua
    • CGExecuteScript.bis
    • CGEventRequest.bis
  • services/scene/scenecore.lua
    • 帮会 UI wrapper 兼容桥
    • execute_client_script
    • set_minor_password_unlock_state
  • services/msgagent.lua
    • request:CGExecuteScript
    • sync_minor_password_unlock_state_to_scene
    • 登录进场景后同步
    • 设置/解锁二级密码后同步
  • services/scene/obj/human.lua
    • minor_password_is_unlock 字段
    • getter / setter
  • lualib/script_base.lua
    • IsPilferLockFlag

建议顺手改

  • services/scripts/event/equip/wuhun.lua
  • services/scripts/event/loulangucheng/eloulan_darkup.lua
  • services/scripts/obj/luoyang/oluoyang_zhouran.lua

十五、最后总结

这次 帮贡牌兑换 的问题,本质上不是一个“单点脚本报错”,而是一个典型的多层兼容链断裂:

  1. 客户端 UI 已经换成了新的调用方式。
  2. Lua 服务端还在按旧入口理解它。
  3. 真正提交时又被二级密码场景态拦住了。

所以如果只补其中一层,就会出现那种很烦人的半修状态:

  • 不是永远报错
  • 就是弹确认后没反应
  • 看起来像修好了,其实还差最后一脚

我这次这套改法的核心优点有三个:

  • 不改客户端。
  • 一切以当前 Game.exe 行为为准。
  • 修完之后不仅 帮贡牌兑换 能用,后续同类 CGExecuteScript 问题也更容易排查。
付费看帖
剩余 46% 内容需要支付 222.00 金币 后可完整阅读
支持付费阅读,激励作者创作更好的作品。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?register

×
您需要登录后才可以回帖 登录 | register

本版积分规则

QQ|雪舞知识库 ( 浙ICP备15015590号-1 | 萌ICP备20232229号|浙公网安备33048102000118号 )|天天打卡

GMT+8, 2026-6-24 05:32 , Processed in 0.102105 second(s), 32 queries .

Powered by Discuz! X5.0

© 2001-2026 Discuz! Team.

快速回复 返回顶部 返回列表