背包<->仓库转移接口(BagMgr + StoreMgr,Lua)

前提:已经有 BagMgr(背包)、StoreMgr(仓库)、ConfigMgr、EventMgr、LogMgr、PlayerMgr;物品结构统一,item_uid 唯一。 支持:背包→仓库、仓库→背包;支持单个物品、支持可堆叠物品数量拆分;校验负重、仓库容量、物品锁、限时物品是否允许存仓。

1. Proto 协议(proto3)

ini 复制代码
// 背包仓库互转请求
message C2S_BagStoreTransfer {
    uint64 item_uid = 1;       // 物品唯一uid
    int32 transfer_type = 2;   // 1:背包→仓库  2:仓库→背包
    int32 transfer_num = 3;    // 转移数量,堆叠物品生效;不可堆叠填1
}

// 返回
message S2C_BagStoreTransferRet {
    int32 ret_code = 1;        // 0成功,非0失败码
    string msg = 2;
    uint64 item_uid = 3;
    int32 left_num = 4;        // 原容器剩余数量
}

2. 错误码定义(放到常量文件)

ini 复制代码
-- bag_store_const.lua
local const = {
    RET_OK = 0,
    RET_ITEM_NOT_EXIST = 1001,
    RET_NOT_ENOUGH_NUM = 1002,
    RET_STORE_FULL = 1003,
    RET_BAG_FULL = 1004,
    RET_OVER_WEIGHT = 1005,
    RET_ITEM_LOCK = 1006,
    RET_CANNOT_STORE = 1007, -- 物品配置标记:不可存入仓库
    RET_PARAM_ERR = 1008,
}
return const

3. BagStoreTransfer 核心接口(写在 BagMgr 或者单独 bag_store_api.lua)

ini 复制代码
local bag_store_const = require("bag_store_const")
local BagMgr = require("bag_mgr")
local StoreMgr = require("store_mgr")
local ConfigMgr = require("config_mgr")
local EventMgr = require("event_mgr")
local LogMgr = require("log_mgr")

local M = {}

-- @param player 玩家对象
-- @param item_uid 物品uid
-- @param transfer_type 1 bag->store  2 store->bag
-- @param transfer_num 转移数量
-- @return ret_code, msg, left_num
function M.BagStoreTransfer(player, item_uid, transfer_type, transfer_num)
    local pid = player.pid
    transfer_num = math.max(1, transfer_num or 1)

    -- 参数校验
    if not item_uid or (transfer_type ~= 1 and transfer_type ~= 2) then
        return bag_store_const.RET_PARAM_ERR, "参数错误", 0
    end

    local src_container, dst_container
    local src_name, dst_name
    if transfer_type == 1 then
        -- 背包 → 仓库
        src_container = BagMgr.GetBagContainer(player)
        dst_container = StoreMgr.GetStoreContainer(player)
        src_name = "bag"
        dst_name = "store"
    else
        -- 仓库 → 背包
        src_container = StoreMgr.GetStoreContainer(player)
        dst_container = BagMgr.GetBagContainer(player)
        src_name = "store"
        dst_name = "bag"
    end

    -- 源容器查找物品
    local src_item = src_container:GetItemByUid(item_uid)
    if not src_item then
        return bag_store_const.RET_ITEM_NOT_EXIST, "物品不存在", 0
    end
    local item_cfg = ConfigMgr.GetItemCfg(src_item.item_id)
    if not item_cfg then
        return bag_store_const.RET_ITEM_NOT_EXIST, "物品配置不存在", src_item.num
    end

    -- 规则校验
    -- 1. 物品上锁不能转移
    if src_item.is_lock then
        return bag_store_const.RET_ITEM_LOCK, "物品已锁定,无法转移", src_item.num
    end
    -- 2. 配置:是否允许存入仓库(只在背包->仓库校验)
    if transfer_type == 1 and item_cfg.cannot_store == 1 then
        return bag_store_const.RET_CANNOT_STORE, "该物品不能存入仓库", src_item.num
    end
    -- 3. 数量校验
    if src_item.num < transfer_num then
        return bag_store_const.RET_NOT_ENOUGH_NUM, "物品数量不足", src_item.num
    end

    -- 目标容器校验
    local ok, err = dst_container:CheckCanAddItem(item_cfg, transfer_num)
    if not ok then
        if transfer_type == 1 then
            return bag_store_const.RET_STORE_FULL, "仓库空间不足", src_item.num
        else
            if BagMgr.CheckAddWeight(player, item_cfg.weight * transfer_num) == false then
                return bag_store_const.RET_OVER_WEIGHT, "负重不足", src_item.num
            end
            return bag_store_const.RET_BAG_FULL, "背包空间不足", src_item.num
        end
    end

    -- === 开始扣源、加目标(原子操作,失败回滚)===
    local old_src_num = src_item.num
    local sub_ok, sub_left = src_container:SubItemNum(item_uid, transfer_num)
    if not sub_ok then
        return bag_store_const.RET_NOT_ENOUGH_NUM, "扣数量失败", old_src_num
    end

    local add_ok, new_item_uid = dst_container:AddItem(item_cfg.item_id, transfer_num, src_item)
    if not add_ok then
        -- 回滚:把数量加回去
        src_container:AddItemNum(item_uid, transfer_num)
        return bag_store_const.RET_BAG_FULL, "目标容器添加失败", sub_left
    end

    -- 流水日志
    LogMgr:WriteFlowLog(pid, "bag_store_transfer", {
        item_uid = item_uid,
        item_id = src_item.item_id,
        num = transfer_num,
        transfer_type = transfer_type,
        src = src_name,
        dst = dst_name,
        src_left = sub_left,
        new_dst_uid = new_item_uid,
    })

    -- 事件广播:物品转移事件
    EventMgr:Fire("EVT_BAG_STORE_TRANSFER", player, {
        transfer_type = transfer_type,
        item_uid = item_uid,
        item_id = src_item.item_id,
        transfer_num = transfer_num,
        src_left = sub_left,
    })

    -- 同步容器给客户端
    src_container:SyncToClient(player)
    dst_container:SyncToClient(player)

    return bag_store_const.RET_OK, "转移成功", sub_left
end

return M

4. 消息处理器(注册到网络)

ini 复制代码
local BagStoreApi = require("bag_store_api")
local bag_store_const = require("bag_store_const")

function OnC2S_BagStoreTransfer(player, msg)
    local ret_code, tips, left_num = BagStoreApi.BagStoreTransfer(
        player,
        msg.item_uid,
        msg.transfer_type,
        msg.transfer_num
    )
    player:SendMsg("S2C_BagStoreTransferRet", {
        ret_code = ret_code,
        msg = tips,
        item_uid = msg.item_uid,
        left_num = left_num,
    })
end

-- 注册
RegisterProtoHandler("C2S_BagStoreTransfer", OnC2S_BagStoreTransfer)

5. 配套容器接口约定(你原有 Bag / Store 容器需要具备)

lua 复制代码
-- 容器对象接口(背包、仓库统一抽象)
container:GetItemByUid(uid)        -- 返回item or nil
container:SubItemNum(uid, num)     -- 扣数量,返回 ok,left
container:AddItem(item_id, num, src_item) -- 添加物品,继承限时、属性等
container:AddItemNum(uid, num)     -- 增加数量(回滚用)
container:CheckCanAddItem(cfg, num)-- 检查格子是否放得下
container:SyncToClient(player)     -- 推送容器数据给客户端

6. 关键业务规则 & 坑点说明

  1. 原子性:先扣源,再加目标;加失败立刻回滚源数量,防止丢物品
  2. 继承属性:转移时,限时时间、强化、鉴定、附魔等属性跟随物品;堆叠物品拆分后,新副本继承过期时间
  3. 负重只校验【仓库→背包】 ;存入仓库不计背包负重
  4. 仓库上限只做格子数量限制,一般无负重(传奇常规设计)
  5. 特殊物品:任务物品、绑定物品、活动限时道具,在 cannot_store 配置控制是否可存仓
  6. 上锁物品统一禁止双向转移
  7. 事件 EVT_BAG_STORE_TRANSFER 可被成就、活跃、统计模块订阅

7. 客户端侧流程简要

  1. 玩家选中物品,点【存入仓库/取出仓库】,填数量
  2. 发送 C2S_BagStoreTransfer
  3. 收到返回:成功则刷新背包、仓库UI;失败弹出tips

相关推荐
tntxia1 小时前
设计模式中模板方法的钩子使用
后端
我的div丢了肿么办1 小时前
go语言对路由中的业务逻辑进行抽离
后端·go
leo在掘金1 小时前
纯Rust重写Photoshop,一个月7.2万Star
后端·架构
韩振方1 小时前
Kubernetes 通过资源 requests 决定 Pending Pod 能否找到节点
后端
用户360055579001 小时前
Kestrel_VLLM - 18 · 投机解码位级对拍收口:batch 与 M=1 一致,注意力路径与 CPU 逐字节对齐
后端
Thneonl1 小时前
git reset --hard 之后先别哭:reflog 里存着你刚弄丢的那个分支
运维·后端
弦儿1 小时前
后端 Web 实战:员工管理模块(多表关系 + 多表查询 + 分页查询 + 条件分页)
后端·sql
王中阳Go1 小时前
网页上答对、接口里答错:同一个知识库,差的不是模型是入口
人工智能·后端·架构
郝琳1 小时前
真实 flaky 18.3%,CI 报 0%,让 Agent 修测试,它删掉了被测的变量
后端·ai编程