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

洱海月 锁定物品可以交易的问题

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

问题现象

玩家面对面交易时,服务端在 char_exchange_sync_item_II() 的加物/加宠分支里,直接把背包对象塞进交易盒:

  1. 没有复用已有的“锁定态校验”。
  2. 物品没有校验 can_exchange()
  3. 结果会出现两类问题:
  • 已经被摆摊、商会或其他系统锁住的物品/珍兽,仍然可以加入玩家交易。
  • 配置上禁止交易的物品,仍然可以加入玩家交易。

这个问题和摆摊入口形成了明显不一致。


根因分析

1. 摆摊入口其实已经有基线校验

文件:services/scene/scenecore.lua

摆摊加物时,原本就有两层判断:

if human_item_logic:is_item_can_exchange(obj, item_container, bag_index) then
    local item = item_container:get_item(bag_index)
    if item:is_bind() or item:is_ruler(define.ITEM_RULER_LIST.IRL_PICKBIND) then
        obj:notify_tips("绑定道具不能交易")
        return
    end
    if not item:can_exchange() then
        obj:notify_tips(string.format("%s不能交易", item:get_name()))
        return
    end
    ...
end

摆摊加宠时,也会先校验锁定态:

if human_item_logic:is_pet_can_exchange(obj, pet_container, bag_index) then
    ...
end

2. 玩家交易入口缺少同样的校验

文件:services/scene/scenecore.lua

原始 char_exchange_sync_item_II() 加物逻辑里,只有“是否绑定”和“交易格是否有空位”的判断:

local item = human_item_logic:get_item(obj_me, ei.from_index)
if item then
    for i = 0, define.EXCHANGE_BOX_SIZE - 1 do
        local exchange_item = my_item_container:get_item(i)
        if exchange_item and exchange_item:get_guid() == item:get_guid() then
            return
        end
    end
    if item:is_bind() or item:is_ruler(define.ITEM_RULER_LIST.IRL_PICKBIND) then
        local msg = packet_def.GCExchangeError.new()
        msg.error_code = define.EXCHANGE_ERR.ERR_ILLEGAL
        self:send2client(obj_me, msg)
        return
    end
    local empty_index = my_item_container:get_empty_item_index()
    if empty_index ~= define.INVAILD_ID then
        my_item_container:set_item(empty_index, item)
        if my_item_container:get_item(empty_index) then
            item_operator:lock_item(obj_me:get_prop_bag_container(), ei.from_index)
            item:set_in_exchange(true)
            ...
        end
    end
end

加宠分支也一样,只校验了 from_index 是否存在,没有校验宠物当前是否已经被锁定:

local pet_bag_container = obj_me:get_pet_bag_container()
local from_index = pet_bag_container:get_index_by_pet_guid(ei.pet_guid)
if not from_index then
    ...
    return
end

local empty_index = my_pet_container:get_empty_item_index()
if empty_index ~= define.INVAILD_ID then
    local pet = pet_bag_container:get_item(from_index)
    my_pet_container:set_item(empty_index, pet)
    item_operator:lock_item(pet_bag_container, from_index)
    pet:set_in_exchange(true)
    ...
end

所以玩家交易入口和摆摊入口的约束不一致,最终留下漏洞。


修改目标

本次修复只解决一个问题:

  1. 玩家交易加物时,必须校验“当前是否已被锁定”和“该物品是否允许交易”。
  2. 玩家交易加宠时,必须校验“当前是否已被锁定”。
  3. 交易入口拦截锁定物品时,提示语要准确,不要沿用摆摊提示。

修改文件

本次修复涉及 2 个文件:

  1. lualib/human_item_logic.lua
  2. services/scene/scenecore.lua

第一步:给通用校验函数增加“可选提示文案”

文件:lualib/human_item_logic.lua

原代码:

function human_item_logic:is_item_can_exchange(human, contaner, bag_index)
    local item = contaner and contaner:get_item(bag_index)
    if not item or item:is_empty() then
        return false
    end
    if item:is_lock() then
        human:notify_tips("该道具已在交易或摆摊中")
        return false
    end
    return true
end

function human_item_logic:is_pet_can_exchange(human, contaner, bag_index)
    local pet = contaner and contaner:get_item(bag_index)
    if not pet then
        return false
    end
    if pet.is_lock and pet:is_lock() then
        human:notify_tips("该珍兽已在交易或摆摊中")
        return false
    end
    return true
end

修改后:

function human_item_logic:is_item_can_exchange(human, contaner, bag_index, locked_tips)
    local item = contaner and contaner:get_item(bag_index)
    if not item or item:is_empty() then
        return false
    end
    if item:is_lock() then
        human:notify_tips(locked_tips or "该道具已在交易或摆摊中")
        return false
    end
    return true
end

function human_item_logic:is_pet_can_exchange(human, contaner, bag_index, locked_tips)
    local pet = contaner and contaner:get_item(bag_index)
    if not pet then
        return false
    end
    if pet.is_lock and pet:is_lock() then
        human:notify_tips(locked_tips or "该珍兽已在交易或摆摊中")
        return false
    end
    return true
end

为什么这么改

这样做有两个好处:

  1. 摆摊、商会等已有调用点不需要修改,默认提示语保持原样。
  2. 玩家交易入口可以传自己的提示语,例如:
"该道具已锁定,无法交易"
"该珍兽已锁定,无法交易"

这样不会把“摆摊文案”错误地显示在“玩家交易”里。


第二步:交易入口先刷新锁状态

文件:services/scene/scenecore.lua

char_exchange_sync_item_II() 入口最前面加入:

obj_me:refresh_locked_item_unlock_state(false)

修改后开头如下:

function scenecore:char_exchange_sync_item_II(who, ei)
    local obj_me = self:get_obj_by_id(who)
    if self:check_item_limit_exchange(who) then
        obj_me:notify_tips("#{HJYK_201223_11}")
        return
    end
    obj_me:refresh_locked_item_unlock_state(false)
    local my_exchange_box = obj_me:get_exchange_box()
    ...
end

为什么要先刷新

因为项目里物品锁定既有“手动锁”,也有“延迟解锁”的状态。

先刷新一次,可以避免:

  1. 客户端已经看到物品解锁了。
  2. 服务端还拿着旧锁状态。
  3. 然后把本来能交易的物品误判成锁定。

第三步:修复加物分支

文件:services/scene/scenecore.lua

3.1 原始加物分支

if ei.opt == packet_def.CGExchangeSynchItemII.OPT.OPT_ADDITEM then
    if ei.from_type == packet_def.CGExchangeSynchItemII.POS.POS_BAG then
        local item = human_item_logic:get_item(obj_me, ei.from_index)
        if item then
            ...
            if item:is_bind() or item:is_ruler(define.ITEM_RULER_LIST.IRL_PICKBIND) then
                ...
                return
            end
            local empty_index = my_item_container:get_empty_item_index()
            if empty_index ~= define.INVAILD_ID then
                my_item_container:set_item(empty_index, item)
                if my_item_container:get_item(empty_index) then
                    item_operator:lock_item(obj_me:get_prop_bag_container(), ei.from_index)
                    item:set_in_exchange(true)
                    ...
                end
            end
        end
    end
end

3.2 修改后的加物分支

if ei.opt == packet_def.CGExchangeSynchItemII.OPT.OPT_ADDITEM then
    if ei.from_type == packet_def.CGExchangeSynchItemII.POS.POS_BAG then
        local item = human_item_logic:get_item(obj_me, ei.from_index)
        if item then
            local prop_bag_container = obj_me:get_prop_bag_container()
            if not human_item_logic:is_item_can_exchange(obj_me, prop_bag_container, ei.from_index, "该道具已锁定,无法交易") then
                local msg = packet_def.GCExchangeError.new()
                msg.error_code = define.EXCHANGE_ERR.ERR_ILLEGAL
                self:send2client(obj_me, msg)
                return
            end
            for i = 0, define.EXCHANGE_BOX_SIZE - 1 do
                local exchange_item = my_item_container:get_item(i)
                if exchange_item and exchange_item:get_guid() == item:get_guid() then
                    return
                end
            end
            if item:is_bind() or item:is_ruler(define.ITEM_RULER_LIST.IRL_PICKBIND) then
                local msg = packet_def.GCExchangeError.new()
                msg.error_code = define.EXCHANGE_ERR.ERR_ILLEGAL
                self:send2client(obj_me, msg)
                return
            end
            if not item:can_exchange() then
                obj_me:notify_tips(string.format("%s不能交易", item:get_name()))
                local msg = packet_def.GCExchangeError.new()
                msg.error_code = define.EXCHANGE_ERR.ERR_ILLEGAL
                self:send2client(obj_me, msg)
                return
            end
            local empty_index = my_item_container:get_empty_item_index()
            if empty_index ~= define.INVAILD_ID then
                my_item_container:set_item(empty_index, item)
                if my_item_container:get_item(empty_index) then
                    item_operator:lock_item(prop_bag_container, ei.from_index)
                    item:set_in_exchange(true)
                    ...
                end
            end
        end
    end
end

3.3 这里实际补了什么

补了两层防线:

第一层:锁定态校验

if not human_item_logic:is_item_can_exchange(obj_me, prop_bag_container, ei.from_index, "该道具已锁定,无法交易") then
    local msg = packet_def.GCExchangeError.new()
    msg.error_code = define.EXCHANGE_ERR.ERR_ILLEGAL
    self:send2client(obj_me, msg)
    return
end

这样就能拦住:

  1. 已被摆摊锁住的物品。
  2. 已被其他系统临时锁住的物品。
  3. 其他已经处于不可操作状态的物品。

第二层:物品交易规则校验

if not item:can_exchange() then
    obj_me:notify_tips(string.format("%s不能交易", item:get_name()))
    local msg = packet_def.GCExchangeError.new()
    msg.error_code = define.EXCHANGE_ERR.ERR_ILLEGAL
    self:send2client(obj_me, msg)
    return
end

这一步专门拦:

  1. 配表上禁止交易的物品。
  2. 规则层不允许进入玩家交易的物品。

第四步:修复加宠分支

文件:services/scene/scenecore.lua

4.1 原始加宠分支

elseif ei.opt == packet_def.CGExchangeSynchItemII.OPT.OPT_ADDPET then
    local pet_bag_container = obj_me:get_pet_bag_container()
    local from_index = pet_bag_container:get_index_by_pet_guid(ei.pet_guid)
    if not from_index then
        local msg = packet_def.GCExchangeError.new()
        msg.error_code = define.EXCHANGE_ERR.ERR_NOT_ENOUGHT_EXROOM
        self:send2client(obj_me, msg)
        return
    end
    ...
    local empty_index = my_pet_container:get_empty_item_index()
    if empty_index ~= define.INVAILD_ID then
        local pet = pet_bag_container:get_item(from_index)
        my_pet_container:set_item(empty_index, pet)
        item_operator:lock_item(pet_bag_container, from_index)
        pet:set_in_exchange(true)
        ...
    end
end

4.2 修改后的加宠分支

elseif ei.opt == packet_def.CGExchangeSynchItemII.OPT.OPT_ADDPET then
    local pet_bag_container = obj_me:get_pet_bag_container()
    local from_index = pet_bag_container:get_index_by_pet_guid(ei.pet_guid)
    if not from_index then
        local msg = packet_def.GCExchangeError.new()
        msg.error_code = define.EXCHANGE_ERR.ERR_NOT_ENOUGHT_EXROOM
        self:send2client(obj_me, msg)
        return
    end
    for i = 0, define.EXCHANGE_BOX_SIZE - 1 do
        local pet = my_pet_container:get_item(i)
        if pet and pet:get_guid() == ei.pet_guid then
            return
        end
    end
    if not human_item_logic:is_pet_can_exchange(obj_me, pet_bag_container, from_index, "该珍兽已锁定,无法交易") then
        local msg = packet_def.GCExchangeError.new()
        msg.error_code = define.EXCHANGE_ERR.ERR_ILLEGAL
        self:send2client(obj_me, msg)
        return
    end
    local empty_index = my_pet_container:get_empty_item_index()
    if empty_index ~= define.INVAILD_ID then
        local pet = pet_bag_container:get_item(from_index)
        my_pet_container:set_item(empty_index, pet)
        item_operator:lock_item(pet_bag_container, from_index)
        pet:set_in_exchange(true)
        ...
    end
end

4.3 这里为什么只补锁定态

当前珍兽侧并没有和物品 item:can_exchange() 对应的统一规则函数。

所以本次第一阶段修复先把最危险的漏洞堵上:

  1. 已被锁定的珍兽,不能加入玩家交易。
  2. 提示语准确显示为“该珍兽已锁定,无法交易”。

如果后续项目里还有“珍兽交易规则表”,再在这里补一层显式规则校验即可。


第五步:为什么错误时还要发 GCExchangeError

锁定态拦截时,除了 notify_tips(),还额外回了:

local msg = packet_def.GCExchangeError.new()
msg.error_code = define.EXCHANGE_ERR.ERR_ILLEGAL
self:send2client(obj_me, msg)

这么做是为了保持交易协议层行为一致:

  1. 玩家能看到明确提示。
  2. 客户端交易界面能收到“本次操作非法/失败”的协议反馈。

只弹提示,不回协议,容易出现客户端界面状态停留在“已尝试加入”的半同步状态。


第六步:最终代码落点

lualib/human_item_logic.lua

新增可选提示参数:

function human_item_logic:is_item_can_exchange(human, contaner, bag_index, locked_tips)
    ...
end

function human_item_logic:is_pet_can_exchange(human, contaner, bag_index, locked_tips)
    ...
end

services/scene/scenecore.lua

交易入口最前面刷新锁状态:

obj_me:refresh_locked_item_unlock_state(false)

交易加物补锁定态和 can_exchange() 校验:

if not human_item_logic:is_item_can_exchange(obj_me, prop_bag_container, ei.from_index, "该道具已锁定,无法交易") then
    ...
    return
end

if not item:can_exchange() then
    ...
    return
end

交易加宠补锁定态校验:

if not human_item_logic:is_pet_can_exchange(obj_me, pet_bag_container, from_index, "该珍兽已锁定,无法交易") then
    ...
    return
end

第七步:建议回归测试

7.1 锁定物品加入玩家交易

步骤:

  1. 准备一个已被其他系统锁住的物品。
  2. 发起玩家交易。
  3. 尝试把该物品加入交易。

预期:

  1. 服务端拒绝。
  2. 提示:该道具已锁定,无法交易
  3. 客户端收到交易错误包。

7.2 禁交易物品加入玩家交易

步骤:

  1. 准备一个 can_exchange() == false 的物品。
  2. 发起玩家交易。
  3. 尝试加入交易。

预期:

  1. 服务端拒绝。
  2. 提示:xxx不能交易
  3. 不会进入交易盒。

7.3 锁定珍兽加入玩家交易

步骤:

  1. 准备一个已被锁住的珍兽。
  2. 发起玩家交易。
  3. 尝试加入交易。

预期:

  1. 服务端拒绝。
  2. 提示:该珍兽已锁定,无法交易

7.4 普通可交易物品加入交易

步骤:

  1. 准备普通未绑定、未锁定、允许交易的物品。
  2. 发起玩家交易。
  3. 正常加入交易盒。

预期:

  1. 服务端允许加入。
  2. 背包物品被锁定。
  3. item:set_in_exchange(true) 生效。

7.5 摆摊原提示不变

步骤:

  1. 在摆摊入口放入锁定物品/珍兽。

预期:

  1. 仍然沿用原来的默认提示:
    该道具已在交易或摆摊中
    该珍兽已在交易或摆摊中

这说明“可选提示参数”没有误伤旧逻辑。


本帖子中包含更多资源

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

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

本版积分规则

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

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

Powered by Discuz! X5.0

© 2001-2026 Discuz! Team.

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