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

洱海月 帮会申请城市时报“扣除道具异常”修复

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

马上注册,查看网站隐藏内容!!

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

×

这次记录一个非常典型的服务端逻辑坑:玩家申请帮会城市时,明明身上钱和交子都足够,却在点击申请后直接报错,日志里还能看到一长串 skynet.call failedhuman_item_logic:del_available_item == 扣除道具异常

这类问题特别容易被误判成背包脏数据、物品表配置错误,甚至是 skynet.queue 死锁。但真正把调用链走完以后会发现,问题并不在底层框架,而是在业务判断和实际扣费逻辑没有保持一致。

如果你也碰到了“申请城市时报错、断言、调用失败”这一类问题,这篇教程可以直接照着排查和修。

一、问题现象

线上或本地测试时,申请城市会出现类似下面的报错:

./lualib/human_item_logic.lua:121: human_item_logic:del_available_item == 扣除道具异常
./services/scene/scenecore.lua:11041: in function 'scene.scenecore.cost_human_occupy_city'
./services/msgagent.lua:2203: in local 'f'
./framework/lualib/skynet.lua:720: call failed

从玩家视角看,表现一般是这几种:

  • 点击申请城市后没有正常完成
  • 客户端可能弹错或者没有正确提示
  • 服务端日志出现 call failed
  • 上层看起来像 msgagentskynet 崩了

但这里有一个关键点:

  • 真正的原始异常不是 msgagent
  • 也不是 queue.lua
  • 而是城市申请扣费时,道具扣除分支直接断言了

二、先看申请城市的调用链

这次问题链路非常清晰,核心入口在:

  • /home/ubuntu/Game2/services/msgagent.lua
  • /home/ubuntu/Game2/services/scene/scenecore.lua
  • /home/ubuntu/Game2/lualib/human_item_logic.lua

申请城市时,msgagent.lua 里的逻辑大致如下:

local result = skynet.call(my_scene, "lua", "check_human_occupy_city", my_obj_id)
if not result then
    return
end

result = skynet.call(".Guildmanager", "lua", "city_apply", my_guid, city_apply)
if result then
    skynet.call(my_scene, "lua", "cost_human_occupy_city", my_obj_id)
end

这说明整条链路分成了三步:

  1. 先检查玩家是否满足申请条件
  2. 再执行帮会城市申请
  3. 申请成功后再扣除资源

所以我们要重点核对的是:

  • 检查条件是不是允许通过
  • 实际扣费是不是和检查条件一致

三、根因分析

1. 检查函数允许“令牌或钱”二选一

scenecore.lua 中原始的 check_human_occupy_city 逻辑是这样的:

function scenecore:check_human_occupy_city(who)
    local human = self:get_obj_by_id(who)
    if not human then return end
    local money = human:get_money()
    local jiaozi = human:get_jiaozi()
    local item_index = 30008013
    local item_count = human_item_logic:calc_bag_item_count(human, item_index)
    local result = (item_count > 0) or ((money + jiaozi) >= (1000 * 10000))
    if not result then
        human:notify_tips("创建帮会领地失败,需要1000交子或者建城令牌")
    end
    return result
end

这段代码表达得很明确:

  • 只要玩家有 30008013 建城令牌,就能通过
  • 或者玩家没有令牌,但 money + jiaozi >= 1000 * 10000,一样能通过

也就是说,设计意图本来就是:

  • 有令牌时扣令牌
  • 没令牌时扣钱或交子

2. 实际扣费却写成了“先强行扣令牌”

问题就出在 cost_human_occupy_city

原始代码是:

function scenecore:cost_human_occupy_city(who)
    local human = self:get_obj_by_id(who)
    if not human then return end
    local item_index = 30008013
    local logparam = {}
    local count = 1
    local result = human_item_logic:del_available_item(logparam, human, item_index, count)
    if result then
        return true
    else
        human:cost_money_with_priority(1000 * 10000, "创建帮会领地")
    end
    human:notify_tips("创建帮会领地成功!")
end

表面上它像是在说:

  • 先尝试删令牌
  • 如果删不掉,再扣钱

但实际上,这段逻辑根本不会按作者预期进入 else

3. del_available_item() 删不到时会直接断言

human_item_logic.lua 里的 del_available_item() 有一段关键逻辑:

if curdel < 1 then
    human:notify_tips("扣除道具异常。")
    assert(false,"human_item_logic:del_available_item == 扣除道具异常")
end

也就是说:

  • 如果玩家没有建城令牌
  • 或者虽然检查统计到了,但实际删除时没有删成功
  • 这里不会返回 false
  • 而是直接 assert

一旦断言抛出,cost_human_occupy_city() 后面的 else 就根本不会执行。

所以这次报错的真实原因就是:

  1. 玩家没有建城令牌
  2. 但是钱和交子足够
  3. check_human_occupy_city() 判定通过
  4. cost_human_occupy_city() 还是先去删令牌
  5. del_available_item() 发现删不到,直接断言
  6. 上层 msgagent 收到的就变成了 call failed

这就是一个非常标准的“检查条件和执行条件不一致”问题。

四、为什么日志看起来像框架报错

很多人第一眼看到:

  • skynet.lua:720: call failed
  • skynet/queue.lua:20
  • skynet.dispatch_message

就会开始怀疑:

  • 框架消息队列有问题
  • scene 服务卡死
  • 消息串线

但实际上这些只是上层调用失败后的连带栈。

真正要盯住的是最底层第一条业务断言:

human_item_logic:del_available_item == 扣除道具异常

只要抓到这条,就可以直接把排查范围缩到:

  • 哪个功能在扣道具
  • 它扣的是不是正确的道具
  • 它有没有先判断玩家是否真的有这个道具

五、最终修复思路

这次修复不需要大改,也不需要动客户端,核心原则就两条:

  1. 当前有建城令牌时,才走扣令牌分支
  2. 当前没有建城令牌时,才走扣钱分支

同时再补一层保护:

  • 因为 del_available_item() 内部会 assert
  • 所以调用时最好用 pcall
  • 避免一次扣费失败直接把整个 skynet.call 打崩

六、修复后的推荐写法

修改文件:

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

cost_human_occupy_city() 改成下面这样:

***付费内容***

七、这个修复为什么有效

改完以后,逻辑就顺了:

情况 1:玩家有建城令牌

  • item_count >= 1
  • 进入扣令牌分支
  • 扣除成功后直接提示成功

情况 2:玩家没有建城令牌,但钱和交子足够

  • item_count == 0
  • 不再去碰 del_available_item()
  • 直接走 cost_money_with_priority()
  • 不会再触发“扣除道具异常”

情况 3:玩家既没令牌,钱也不够

  • 这种情况本来就会被 check_human_occupy_city() 拦住
  • 用户直接收到“需要1000交子或者建城令牌”的提示

情况 4:令牌分支内部异常

  • 这时不会把整个 scene 调用直接炸掉
  • 会记录 warning 日志
  • 并给玩家一个明确失败提示

八、实战排查时最容易忽略的点

这次问题里,有三个细节特别值得记住。

1. 不要只看“检查通过了”

很多业务代码里都会先写一个 check_xxx(),然后再写一个 cost_xxx()

如果两边没有共用同一套判定逻辑,就很容易出现:

  • 检查通过
  • 执行时报错
  • 或者检查失败,但实际却能做

所以遇到这类问题时,一定要把“检查”和“执行”两个函数配套看。

2. 不要把 assert 当成普通失败返回

这次原代码最大的问题之一,就是作者把 del_available_item() 当成了“删不到就返回 false”的函数来用。

但实际上它是:

  • 成功返回结果
  • 失败直接断言

这两种函数的调用姿势完全不同。

如果你把“会断言的函数”当成“会返回 false 的函数”来写分支,后面的兜底逻辑基本等于不存在。

3. 上层 call failed 往往只是结果,不是根因

只要日志里还有更底层的业务断言,就先看业务断言。

像这次:

  • msgagent.lua:2203 只是调用 cost_human_occupy_city()
  • 真正出错的是 human_item_logic:del_available_item()

这个顺序一定不要看反。

九、建议顺手检查的一个业务风险

虽然这次修复已经解决了“申请城市时报扣除道具异常”的主问题,但当前链路里还有一个值得注意的点:

  • msgagent.lua 是先调用 .Guildmanager.city_apply
  • 申请成功以后,才回 scene 扣资源

这意味着如果出现下面这种情况:

  • 帮会城市申请已经成功
  • 但后面的扣费又失败了

那就可能出现:

  • 城市已经申请到了
  • 但资源没有真正扣掉

这不影响本次主修复是否生效,但如果你准备做长期稳定版,建议后续再补一个更彻底的方案,例如:

  • 先扣费,再申请
  • 或者申请失败回滚
  • 或者把申请和扣费合并成同一条可控事务链

十、修复后的测试方法

可以按下面三组数据直接验证。

测试一:只有令牌,没有足够钱

  • 背包放 1 个 30008013
  • 金钱和交子故意设得不足
  • 申请城市应成功
  • 令牌应被扣掉

测试二:没有令牌,但钱和交子足够

  • 删除 30008013
  • 保证 money + jiaozi >= 1000 * 10000
  • 申请城市应成功
  • 不应再出现“扣除道具异常”

测试三:既没令牌,钱也不够

  • 删除令牌
  • 金钱和交子都设为不足
  • 应直接提示:
创建帮会领地失败,需要1000交子或者建城令牌

测试四:观察日志

确认申请城市过程中不再出现:

human_item_logic:del_available_item == 扣除道具异常
skynet.lua:720: call failed

十一、结语

这次问题本质上不是“背包扣道具坏了”,也不是“框架不稳定”,而是一个很常见的业务层失配:

  • 检查函数允许 A 或 B
  • 执行函数却只写了 A

这种问题在老项目里非常常见,尤其容易藏在:

  • 道具扣除
  • 货币扣除
  • 次数消耗
  • 活动报名
  • 副本进入条件

所以以后看到类似“明明条件够了,执行时却断言”的报错,优先怀疑:

  • 检查和执行是不是两套条件
  • 下层函数到底是返回 false 还是直接 assert

只要把这两件事对齐,很多看起来像“大问题”的报错,实际上都能很快落地修掉。

付费看帖
剩余 12% 内容需要支付 18.00 金币 后可完整阅读
支持付费阅读,激励作者创作更好的作品。
已有1人购买阅读
  • 1337
您需要登录后才可以回帖 登录 | register

本版积分规则

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

GMT+8, 2026-6-24 05:30 , Processed in 0.070993 second(s), 26 queries .

Powered by Discuz! X5.0

© 2001-2026 Discuz! Team.

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