849 lines
47 KiB
Markdown
849 lines
47 KiB
Markdown
# ROK 背包/道具系统业务规则(1:1 迁移依据)
|
||
|
||
> 来源:`E:\Game\gmd\ROK` 服务端 Lua(`ItemLogic.lua` / `Item.lua` proxy)+ 客户端 C#(`BagProxy.cs` / `BagMediator.cs` / `PlayerCmd.cs` / `ItemConfig.cs` / `ItemEnum.lua` / `Common.sproto` / `ErrorCode.cs`)
|
||
> 用途:作为 Survivors 工程 `cn.etetet.bag` 包实现的业务依据,**不直接复制源代码**,只摊开业务规则与数据结构
|
||
> 调研时间:2026-05-27
|
||
> 关键约定:表名加 `s_` 前缀(如 `s_Item`)的为服务端 ConfigEntity;客户端对应 `ItemDefine`、`ItemPackageDefine` 等
|
||
|
||
---
|
||
|
||
## 1. 整体业务范围
|
||
|
||
| # | 子模块 | 一句话定位 |
|
||
|---|---|---|
|
||
| 1 | 通用道具增/删 | `ItemLogic:addItem / delItem / delItemById / checkItemEnough`,所有业务发奖/扣道具的唯一入口 |
|
||
| 2 | 道具分类 | 服务端 `Enum.ItemType`(6 类,HEAD 不入背包);客户端 `BagItemType`(5 个 Tab) |
|
||
| 3 | 堆叠规则 | 非装备同 `itemId` 合并到同一 `itemIndex`;装备每件独立 `itemIndex`、`overlay=1` |
|
||
| 4 | 道具使用 | `Item.lua:response.ItemUse`,按 `s_Item.itemFunction` 分支 if/elseif 处理(**无独立脚本表**) |
|
||
| 5 | 资源类道具兑换 | `Item.lua:response.ItemChangeResource`,把"资源类道具"换成等额玩家货币(金币/木料/粮食/VIP/行动力) |
|
||
| 6 | 礼包/选包奖励 | `ItemLogic:getGroupPackage / giveReward / getItemPackage` + `s_ItemPackage` + `s_ItemRewardChoice` |
|
||
| 7 | 新道具/红点标记 | **服务端不存**,全部用 `PlayerPrefs` 在客户端持久化(按 `rid + itemIndex`) |
|
||
| 8 | 头像框解锁 | `Enum.ItemType.HEAD` 类型道具**不进背包**,直接走 `RoleLogic:unlockRoleHead` |
|
||
| 9 | 资源货币 | 粮/木/石/金/宝石/行动力/VIP/远征币等**作为 Role 字段**存储,不进 Item 表(详见 §5.7) |
|
||
| 10 | 同步推送 | 唯一同步协议 `Item_ItemInfo`(30101)增量推 |
|
||
| 11 | 创角初始道具 | `s_Config.initialItemType + initialItemNum` 硬编码 8 个道具 |
|
||
| 12 | 资源阈值告警 | `s_GameWarning.num` 单道具数量超阈值时 `Common.sendResourceAlarm` 给运营 |
|
||
| 13 | 日志审计 | 每次增删都 `LogLogic:itemChange(rid, iggid, itemId, changeNum, oldNum, newNum, logType, logType2)` |
|
||
|
||
> ⚠️ ROK 工程中**没有 `ItemScript.lua` 实质实现**——同名文件只剩 3 个空函数。所有"道具使用脚本"逻辑都在 `Item.lua` proxy 的 `ItemUse` 函数里用 `if/elseif (sitem.itemFunction)` 巨型分支硬编码。
|
||
|
||
---
|
||
|
||
## 2. 道具分类 BagItemType
|
||
|
||
### 2.1 服务端 `Enum.ItemType`(`ItemEnum.lua`)
|
||
|
||
| 名称 | 值 | 含义 | 是否入背包 | 备注 |
|
||
|---|---|---|---|---|
|
||
| `RESOURCE` | 1 | 资源(金/木/石/粮/宝石/VIP/行动力类道具袋) | ✅ | 通过 `Item_ItemChangeResource` 兑换玩家货币字段 |
|
||
| `SPEED` | 2 | 加速(建筑/训练/研究/治疗/通用 5 类) | ✅ | 在各功能界面用,背包 Tab2 |
|
||
| `GAIN` | 3 | 增益(采集加成/护盾/反侦察/部队扩编等) | ✅ | 大多对应 City Buff,Tab3 |
|
||
| `EQUIP` | 4 | 装备(武器/头盔/胸甲/手套/裤/鞋/饰品 + 装备材料 + 图纸) | ✅ | 每件独立 itemIndex |
|
||
| `OTHER` | 5 | 其他(改名卡、迁城卡、经验书、英雄雕像、钥匙等) | ✅ | 杂项 Tab |
|
||
| `HEAD` | 6 | 头像框 | ❌ | 进 `addItem` 后**直接走 `RoleLogic:unlockRoleHead`** 返回空 syncInfo |
|
||
|
||
**itemId 编码约定**:客户端 `BagProxy.GetItemTypeById(itemId) = (int)itemId / 100000000`,即 itemId 最高位决定 type。
|
||
- 1xxxxxxxx → RESOURCE(如 `101010001`)
|
||
- 2xxxxxxxx → SPEED (如 `201010010`)
|
||
- 3xxxxxxxx → GAIN
|
||
- 4xxxxxxxx → EQUIP (如 `401010001` 材料、`401020001` 装备)
|
||
- 5xxxxxxxx → OTHER (如 `502030004` 改名卡、`502070001` 经验书)
|
||
|
||
### 2.2 客户端 `BagItemType`(`BagProxy.cs`)
|
||
|
||
| 名称 | 值 | UI Tab | 与服务端关系 |
|
||
|---|---|---|---|
|
||
| `Resource` | 1 | 资源 | == ItemType.RESOURCE |
|
||
| `Speedup` | 2 | 加速 | == ItemType.SPEED |
|
||
| `Boost` | 3 | 增益 | == ItemType.GAIN |
|
||
| `Equipment` | 4 | 装备 | == ItemType.EQUIP |
|
||
| `Other` | 5 | 其他 | == ItemType.OTHER |
|
||
| `Icon` | 6 | 不显示 | == ItemType.HEAD(实际客户端 BagPanel 只 5 个 Tab) |
|
||
|
||
> `BagMediator` 实测**只显示 5 个 Tab**(1-5),HEAD 类型在 `for i = 1; i < 6` 循环里被略过。
|
||
|
||
### 2.3 `Enum.ItemSubType`(46 个细分子类型,关键值)
|
||
|
||
`ItemEnum.lua` 中所有 subType 都是 5 位数:
|
||
|
||
| 段位 | 含义 | 示例 |
|
||
|---|---|---|
|
||
| `101xx` | RESOURCE 子类 | 10101=VIP, 10102=GOLD, 10103=STONE, 10104=WOOD, 10105=GRAIN |
|
||
| `201xx` | SPEED 子类 | 20101=全加速, 20102=建筑, 20103=训练, 20104=研究, 20105=治疗 |
|
||
| `301xx` | GAIN 子类 | 30101-30111 各种产出/Buff |
|
||
| `401xx` | 装备材料 | 40101 羽毛 / 40102 皮革 / 40103 铁石矿 / 40104 兽骨 / 40105 水晶 / 40106 丝绸 / 40107 乌木 |
|
||
| `402xx` | 装备本体 | 40201=武器/40202=头盔/40203=胸甲/40204=手套/40205=裤/40206=鞋/40207=饰品 |
|
||
| `403xx` | 装备图纸 | 40301-40307 对应 7 个部位 |
|
||
| `501xx-509xx` | OTHER 杂项 | 50101=经验书 / 50201=改名卡 / 50202=工人招募 / 50203=迁城 / 50204=钥匙 / 50205=文明更换 / 50206=天赋重置 / 50207=建筑升级 / 50208=行动力 / 50301-50304=升星材料 / 50305=技能材料 / 50901=英雄雕像 |
|
||
|
||
### 2.4 每类道具的堆叠/唯一性特征
|
||
|
||
| 类 | 堆叠 | maxStack | 唯一字段 |
|
||
|---|---|---|---|
|
||
| 装备 (`isEquipItem(subType) == true`,即 40201/40202/40203/40204/40205/40206/40207) | ❌ 每件独立 | 1(强制) | `itemIndex` + `exclusive`(专属英雄)+ `heroId`(穿戴者) |
|
||
| 其他全部 | ✅ 无限叠 | **代码中无 maxStack 限制**(`overlay` int 累加) | 只按 itemId 合并到同一 itemIndex |
|
||
|
||
> ⚠️ **没有 maxStack 限制**:`addItem` 只在装备分支才循环创建多条;其他都是 `overlay = oldOverlay + itemNum` 直接累加。配置表 `ItemDefine` 也**没有 maxStack 字段**。
|
||
|
||
---
|
||
|
||
## 3. Item 数据模型
|
||
|
||
### 3.1 `.ItemInfo` sproto(`Common.sproto` L308)
|
||
|
||
| 字段 | 类型 | 含义 |
|
||
|---|---|---|
|
||
| `itemIndex` | integer | **背包槽位**,自增正整数,玩家维度唯一(`getFreeItemIndex` 找最小空位) |
|
||
| `uniqueIndex` | integer | 唯一索引(实测 Lua 中未使用,可能为后续扩展预留) |
|
||
| `itemId` | integer | 道具配置 ID,关联 `s_Item.ID` |
|
||
| `overlay` | integer | 叠加数量(装备恒为 1) |
|
||
| `exclusive` | integer | 装备专属标志,`0` 不专属;非 0 时表示绑定英雄 ID(影响装备分解和穿戴) |
|
||
| `heroId` | integer | 当前穿戴此装备的英雄 ID(0 = 未穿戴) |
|
||
|
||
### 3.2 服务端存储(`MSM.d_item[rid]` snax service)
|
||
|
||
`MSM.d_item[rid].req` 提供的操作:
|
||
- `Get(rid)` → 全部 items dict
|
||
- `Get(rid, itemIndex)` → 单条
|
||
- `Get(rid, itemIndex, fields)` → 部分字段
|
||
- `Set(rid, itemIndex, field, value)` → 改单字段
|
||
- `Add(rid, itemIndex, itemInfo)` → 新增
|
||
- `Delete(rid, itemIndex)` → 删除
|
||
|
||
`Enum.Item` 字段名常量:`itemIndex / itemId / overlay / exclusive / heroId`(即 ItemInfo 5 字段)
|
||
|
||
### 3.3 客户端缓存(`BagProxy.cs`)
|
||
|
||
```
|
||
public Dictionary<Int64, ItemInfoEntity> Items; // itemIndex → 道具实体
|
||
private List<ItemInfoEntity> m_itemList; // 顺序列表
|
||
private Dictionary<Int64, int> m_itemNumMap; // itemId → 累计数量(懒计算,-1 表示需重算)
|
||
private Dictionary<long, ItemInfo> m_materialItemInfos; // 装备材料分组
|
||
private Dictionary<long, ItemInfo> m_equipItemInfos; // 装备本体分组
|
||
private Dictionary<int, Dictionary<long, long>> m_reddotRecord; // type → (itemIndex → 新增数量)
|
||
private Dictionary<int, long> m_reddotTotalDic; // type → 红点总数缓存
|
||
```
|
||
|
||
派生类:
|
||
- `ItemInfo` 基础:`ItemIndex / ItemID / ItemNum / ItemType / HeroID`
|
||
- `EquipItemInfo : ItemInfo` 额外:`Exclusive / Order / Group`(来自 `EquipDefine`)
|
||
- `MaterialItem : ItemInfo` 额外:`MaterialDefine` + `MaterialType`(Material / Equip / Drawing / DrawingMaterial)
|
||
|
||
### 3.4 新道具标记存储(**纯客户端 PlayerPrefs**)
|
||
|
||
Key 格式:`{rid}/itemIndex:{itemIndex}` → int
|
||
|
||
| 值 | 含义 |
|
||
|---|---|
|
||
| 不存在 | 还没在客户端被看到 |
|
||
| `1` | 新道具(红点亮) |
|
||
| `-1` | 旧道具(已被标记/查看过) |
|
||
| `0` | 已清除 |
|
||
|
||
特殊情况:装备 `heroId != 0`(已穿戴)→ 强制 `SetLocalItemToOld`,不亮红点。
|
||
|
||
---
|
||
|
||
## 4. 配置表清单
|
||
|
||
### 4.1 `ItemDefine`(`ItemConfig.cs`,对应服务端 `s_Item`)
|
||
|
||
| 字段 | 类型 | 含义 |
|
||
|---|---|---|
|
||
| `ID` | int | 主键,道具 ID(同 itemId) |
|
||
| `l_nameID` | int | 道具名称语言包 ID |
|
||
| `l_tipsID` | int | tips 道具名称语言包 ID |
|
||
| `subType` | int | 子分组标签(`Enum.ItemSubType`,5 位数) |
|
||
| `type` | int | 功能分组(`Enum.ItemType`,1-6) |
|
||
| `l_typeDes` | int | 功能分组语言包 |
|
||
| `typeGroup` | int | 类型组(装备页用,1=Material 2=Equip 3=Drawing 4=DrawingMaterial) |
|
||
| `itemIcon` | string | 道具图标资源路径 |
|
||
| `lv` | int | 使用等级要求(实测大部分==0) |
|
||
| `quality` | int | 品质 1-5(白/绿/蓝/紫/橙,对应 `Enum.ItemQualityType`) |
|
||
| `batchUse` | int | 是否可批量使用(`Enum.ItemBatchUse`,0/1) |
|
||
| `l_buttonDes` | int | 按钮文字语言包(`<1` 表示无按钮) |
|
||
| `itemFunction` | int | **道具功能类型**,对应 `Enum.ItemFunctionType`(见 §4.2) |
|
||
| `data1` | int | 功能参数 1(数值,如 VIP 加多少点/兑换比例) |
|
||
| `data2` | int | 功能参数 2(关联 ID,如 `s_ItemPackage` group ID / `s_ItemRewardChoice` ID / cityBuff ID) |
|
||
| `l_desID` | int | 道具描述语言包(`string.format(text, desData1, desData2)`) |
|
||
| `desData1` | int | 描述参数 1 |
|
||
| `desData2` | int | 描述参数 2 |
|
||
| `l_topID` | int | 图标顶部信息语言包(`<1` 不显示) |
|
||
| `topData` | int | 顶部信息参数 |
|
||
| `get` | List\<int\> | 获得途径列表,关联 `s_ItemGet` |
|
||
| `shortcutPrice` | int | 快捷使用价格(钻石购买后直接使用,用于行动力/VIP 道具) |
|
||
| `shopPrice` | int | 商城道具价格 |
|
||
| `redDotPrompt` | int | 是否要红点提示(0/1) |
|
||
| `rank` | int | 显示排序(背包内同 type 按 rank 升序,rank 相同按 itemId 升序) |
|
||
|
||
> ⚠️ **没有 `maxStack` / `bind` / `tradeable` 字段**(与同名 IGG 老项目 `item.json` 形成对比,那个有 `MaxNum / CanTrade / IsDecompose`,但不是 ROK 用的)。
|
||
|
||
### 4.2 `Enum.ItemFunctionType` 枚举(**核心扩展点**)
|
||
|
||
| 值 | 名称 | 服务端处理(`Item.lua:ItemUse`) | 客户端处理(`BagMediator.BtnOperate` + `PlayerCmd`) |
|
||
|---|---|---|---|
|
||
| 0 | NOT_USE | 直接返回 `ITEM_NOT_USE` 错误 | 无按钮 |
|
||
| 1 | OPEN_ITEMPACKAGE | 调用 `getItemPackage(data2)` 发奖(`data2` = s_ItemPackage group) | 直接发请求 |
|
||
| 2 | CHOOSE_ITEMPACKAGE | 校验 `s_ItemRewardChoice[data2][id]`,按选择 ID 发对应 reward | 弹 `BagGiftOpenView`(ShowType=1)让玩家选 |
|
||
| 3 | RECYCLE | 同 OPEN_ITEMPACKAGE(用 data2 兑换包) | 弹 `BagGiftOpenView`(ShowType=2)确认兑换 |
|
||
| 4 | CITY_BUFF | `RoleLogic:addCityBuff(data2)` 加 buff | `m_cityBuffProxy.SendUseItem(data2, 1, cb)` |
|
||
| 5 | VIP | `RoleLogic:addVip(data1 * itemNum)` | 走 VIP 满级校验后直接发 |
|
||
| 6 | ACTION_FORCE | `RoleLogic:addActionForce(data1 * itemNum)` | 直接发 |
|
||
| 10 | KINGDOM_MAP | 调 `DenseFogLogic:openNearDenseFog(pos)`,开雾失败则 `getItemPackage(data2)` 补偿 | 检测迷雾,否则弹 `OpenFogShow` 让玩家选位置;返回奖励则弹 `ItemCollection` |
|
||
| 12 | SUMMON_MONSTER | `MSM.MonsterSummonMgr.req.summonMonster(data2)` 召唤野怪 | 关闭背包,相机飞向召唤点,播放召唤特效 |
|
||
| 13 | SECONDE_QUEUE | 工人小屋:满建造队列则发 `data2` 补偿;否则 `BuildingLogic:unlockQueue(workQueueTime)` | 根据 `result.status` 分别提示成功/延长/补偿 |
|
||
| 14 | TRAIN_NUM | 预备部队增容:`itemAddTroopsCapacity = data1`,`itemAddTroopsCapacityCount += itemNum` | 若已有不同档位 `data1` 则二次确认 |
|
||
| 35 | LEAGUE_POINTS | 校验入盟后 `GuildLogic:addGuildCurrency(leaguePoints, data1*num)`,返回 `rewardInfo.leaguePoints` | 直接发 |
|
||
| 7 | (客户端独有)跳转界面 | — | `SystemOpen.IsCanOpenByUiId(data1)` + `OpenUI2(data1, data2)` |
|
||
| 17 | (客户端独有)暂未实现 | — | Tip 提示"暂未开放" |
|
||
| 30 | (客户端独有)通用迁城 | — | 弹 `MoveCity` 界面 |
|
||
| 31 | (客户端独有)随机迁城 | — | 二次确认后发 `Map_MoveCity{type=4}` |
|
||
|
||
> 关键约束:服务端枚举值与客户端的 `BtnOperate` 分支**必须一一对应**,新增 itemFunction 时两边都要改。
|
||
|
||
### 4.3 其他 Item 相关 Define
|
||
|
||
| 客户端类 | 服务端表 | 用途 |
|
||
|---|---|---|
|
||
| `ItemPackageDefine` | `s_ItemPackage` | 奖励组(按 randomGroup 分组随机,含 type/typeData/odds/number/civilization_limit/numberStep_lv/numberStep_increment/numberFloat_min/numberFloat_max) |
|
||
| `ItemRewardChoiceDefine` | `s_ItemRewardChoice` | 选包奖励配置(按 data2 关联,每条 `{id, reward}` 表示一个选项对应的 itemPackage group) |
|
||
| `ItemHeroDefine` | `s_Hero` 关联 | 英雄相关道具映射(英雄碎片 → 英雄 ID) |
|
||
| `ItemGetDefine` | `s_ItemGet` | 获得途径配置(关联 `ItemDefine.get[]`) |
|
||
| `ItemPackageShowDefine` | `s_ItemPackageShow` | 礼包展示配置 |
|
||
| `ItemPlayerHeadDefine` | `s_ItemPlayerHead` | 头像框配置 |
|
||
| `EquipDefine` | `s_Equip` | 装备本体配置(含 makeMaterial / makeMaterialNum / order / group) |
|
||
| `EquipMaterialDefine` | `s_EquipMaterial` | 装备材料配置(含 mix / split / mixCostNum 用于合成分解) |
|
||
|
||
### 4.4 配置条目数
|
||
|
||
ROK 客户端使用 SQLite/Bin 序列化(`Assets/StreamingAssets/Config/Bin/Item.bin` ≈ 87KB),**没有可读的 item.json**。`gmd/Unity/Assets/Bundles/Config/item.json` 是另一个老项目残留(字段为 `MaxNum / CanTrade / FunctionID` 等,与 ROK 完全不同结构),**不属于本次调研范围**。
|
||
|
||
实际条目数无法从源代码直接计数,但根据 itemId 编码(5 个一级类 × 数百子分类)+ initialItemType 已出现 `502070003`、`201010013` 等量级,**估算总条目数在 200-500 范围**。
|
||
|
||
### 4.5 `ItemPackageType` 奖励类型枚举(`s_ItemPackage.type`)
|
||
|
||
| 值 | 名称 | typeData 含义 |
|
||
|---|---|---|
|
||
| 0 | NONE | 空 |
|
||
| 100 | CURRENCY | `Enum.CurrencyType`(food/wood/stone/gold/denar/actionForce/vip/expeditionCoin/individualPoints/leaguePoints/activityActivePoint) |
|
||
| 200 | ITEM | `s_Item.ID`(普通道具) |
|
||
| 300 | SOLDIER | `s_Arms.ID` |
|
||
| 400 | HERO | `s_Hero.ID` |
|
||
| 500 | SUB_ITEM_TYPE | `Enum.ZeroEmptyType.SUB_ITEM_TYPE` 关联表 ID(再从子表 Random 出具体 itemId) |
|
||
| 600 | GUILD_GIFT | 联盟礼物类型 |
|
||
|
||
### 4.6 `CurrencyType`(`OtherEnum.lua` L101,**不属于 Item,是 Role 字段**)
|
||
|
||
| 值 | 名称 | 含义 |
|
||
|---|---|---|
|
||
| 100 | food | 粮食 |
|
||
| 101 | wood | 木材 |
|
||
| 102 | stone | 石料 |
|
||
| 103 | gold | 金币 |
|
||
| 104 | denar | 宝石(钻石) |
|
||
| 105 | actionForce | 行动力 |
|
||
| 106 | individualPoints | 联盟个人积分 |
|
||
| 107 | leaguePoints | 联盟积分 |
|
||
| 108-111 | allianceFood/Wood/Stone/Gold | 联盟资源 |
|
||
| 112+ | vip / expeditionCoin / activityActivePoint | VIP 点 / 远征币 / 活动积分 |
|
||
|
||
---
|
||
|
||
## 5. 业务规则详细
|
||
|
||
### 5.1 添加道具 `ItemLogic:addItem(args)`
|
||
|
||
**入参** `args` 表:`{rid, itemId, itemNum=1, exclusive=nil, noSync=nil, eventType=nil, eventArg=nil}`
|
||
|
||
**流程**(`ItemLogic.lua` L69-164):
|
||
1. 查 `s_Item:Get(itemId)`,不存在 → `LOG_ERROR` 返回 nil
|
||
2. **特殊分支**:若 `sitemInfo.type == HEAD`,调 `RoleLogic:unlockRoleHead(rid, itemId)` 解锁头像框后**直接返回 `{}`**,不进背包
|
||
3. **是否堆叠**:
|
||
- **装备类**(`isEquipItem(subType)` = subType 在 ARMS/HELMET/BREASTPLATE/GLOVES/PANTS/ACCESSORIES/SHOES 中)→ 跳过查找,必新增
|
||
- **其他**:遍历 `getItem(rid)` 查同 `itemId` 的现有 itemIndex
|
||
4. **堆叠分支**:找到现有同 itemId → `overlay = oldOverlay + itemNum`,`Set(rid, itemIndex, overlay, newNum)`
|
||
5. **新建分支**:
|
||
- 非装备:`getFreeItemIndex(rid)` 取最小空 index,新建一条 `{itemId, overlay=itemNum, rid, itemIndex, exclusive, heroId=0}`,`Add(rid, itemIndex, itemInfo)`
|
||
- 装备:**循环 itemNum 次**,每次 `getFreeItemIndex` + `overlay=1` 单独建条目(**装备永远不堆叠**)
|
||
6. 同步:`syncItem(rid, nil, syncItemInfo, true)` 推 `Item_ItemInfo`
|
||
7. 日志:若 `eventType` 非空,调 `LogLogic:itemChange({rid, iggid, itemId, changeNum, oldNum, newNum, logType, logType2})`
|
||
8. 资源阈值告警:若 `s_GameWarning:Get(itemId, "num")` 存在且 `newNum > 阈值` → `Common.sendResourceAlarm(rid, itemId, newNum)`
|
||
|
||
**返回值**:`syncItemInfo` dict(itemIndex → 新建/更新后的 ItemInfo)
|
||
|
||
**触发新道具标记**:服务端**不主动标**,客户端 `BagProxy.UpdateItemInfo` 收到推送时根据 `m_isFirstGetItemInfo == false`(即非首次登录) + `redDotPrompt == 1` + isNewItem 或 overlay 增加,则在 `m_reddotRecord[type][itemIndex]` 写入新增数量。
|
||
|
||
**协议**:`Item_ItemInfo` (30101) 增量推送
|
||
|
||
**错误码**:无(addItem 自身只 LOG_ERROR,不返回错误码)
|
||
|
||
---
|
||
|
||
### 5.2 移除道具
|
||
|
||
#### 5.2.1 按 itemIndex 移除:`ItemLogic:delItem(rid, itemIndex, itemNum, noSync, logType, logExtraType)`
|
||
|
||
**流程**(`ItemLogic.lua` L167-198):
|
||
1. `getItem(rid, itemIndex)` 取道具实体
|
||
2. **数量判定**:
|
||
- `overlay <= itemNum` → 整条删除:`MSM.d_item[rid].req.Delete(rid, itemIndex)`,`newNum = 0`
|
||
- `overlay > itemNum` → 扣减:`Set(rid, itemIndex, overlay, overlay - itemNum)`
|
||
3. 同步推送 `{[Enum.Item.overlay] = newNum}`
|
||
4. 日志:`LogLogic:itemChange({rid, iggid, logType, logType2, itemId, changeNum, oldNum, newNum})`
|
||
|
||
**返回值**:`syncItems` dict
|
||
|
||
> ⚠️ `delItem` **不校验** `itemNum > overlay`,传过量会直接清零(业务方需自己先 `checkItemEnough`)。
|
||
|
||
#### 5.2.2 按 itemId 移除:`ItemLogic:delItemById(rid, itemId, itemNum, noSync, logType, logExtraType)`
|
||
|
||
**流程**(`ItemLogic.lua` L241-250):遍历所有 items 找第一个匹配 itemId,转发到 `delItem`。
|
||
|
||
> ⚠️ 业务侧约束:因为非装备同 itemId 必合并在同一 itemIndex,所以只有一条匹配;**装备类不应该用 delItemById**(每个 itemIndex 独立)。
|
||
|
||
#### 5.2.3 数量检查:`ItemLogic:checkItemEnough(rid, itemId, itemNum)`
|
||
|
||
**返回**:`(boolean, overlay)`。
|
||
**特点**:因为非装备无限叠加 + 必合并,**只检查第一条匹配的 itemId.overlay 是否 >= itemNum**。
|
||
|
||
**装备移除前是否校验穿戴**:`delItem` 本身**不校验** `heroId != 0`;具体校验由调用方在装备分解逻辑里处理(`Build_DecompositionEquipment` proxy handler 校验,本次未深入展开)。
|
||
|
||
**RPC**:无独立移除 RPC(移除都从其他业务里调用,如使用消耗、装备分解、资源兑换扣道具等)
|
||
|
||
**错误码**:无(delItem 不报错);上层有 `ITEM_NOT_ENOUGH(8000)` `ITEM_NOT_EXIST(8002)`
|
||
|
||
---
|
||
|
||
### 5.3 通用奖励/消耗结构
|
||
|
||
#### 5.3.1 数据结构
|
||
|
||
| 名称 | 结构 |
|
||
|---|---|
|
||
| 奖励项(sproto) | `.RewardItem { itemId, itemNum }` |
|
||
| 完整奖励信息 | `.RewardInfo { food, wood, stone, gold, denar, items:*RewardItem, soldiers:*SoldierInfo, groupId, actionForce, guildGifts, heros:*Heros, expeditionCoin, guildPoint, vip, leaguePoints, activityActivePoint }` |
|
||
| 消耗项(sproto) | `.Items { itemId, itemNum }`(用于商店购买/合成消耗) |
|
||
|
||
> 注意:ROK **没有统一的 `CostInfo`**,消耗信息分散在各 RPC 的 request 里(如 `Shop_BuyShopItem.itemId/itemNum`)。
|
||
|
||
#### 5.3.2 批量接口
|
||
|
||
| 函数 | 用途 |
|
||
|---|---|
|
||
| `ItemLogic:giveReward(rid, rewards, groupId, noSync, noHeroShow, block, isBuyGift, packageNameId)` | **核心发奖入口**,遍历 RewardInfo 各字段调对应 `RoleLogic:add*` / `addItem` / `ArmyTrainLogic:addSoldiers` / `HeroLogic:addHero` |
|
||
| `ItemLogic:getItemPackage(rid, groupId, noAdd, noSync, noHeroShow, noMerge, block, isBuyGift, openNum, mergeHero, packageNameId)` | **打开礼包**:调 `getGroupPackage` 计算实际奖励,再调 `giveReward` 发放 |
|
||
| `ItemLogic:getGroupPackage(rid, groupId, noMerge, openNum, mergeHero)` | **奖励计算**:按 randomGroup 加权随机 + 等级增量 + 浮动比例,输出合并后的 reward 结构 |
|
||
| `ItemLogic:mergeReward(rawReward, addReward)` | **奖励合并**:把两个 RewardInfo 累加(货币求和,items/soldiers/heros 按 ID 合并) |
|
||
| `ItemLogic:checkItemEnough(rid, itemId, itemNum)` | 单道具数量校验 |
|
||
|
||
#### 5.3.3 事务性
|
||
|
||
**ROK 没有事务**。`giveReward` 遍历各字段顺序发放,任一中途失败:
|
||
- 货币 `add*` 失败:仅 LOG_ERROR,已发的不会回滚
|
||
- `addItem` 失败:同上
|
||
- **业务方需保证 reward 结构合法**(数量 > 0 / itemId 存在),失败属于配置错误
|
||
|
||
业务实现"先检查再扣除"模式:典型如 `Shop_BuyShopItem`:
|
||
1. `checkItemEnough(rid, costItemId, costNum)` → false 返回 `ITEM_NOT_ENOUGH`
|
||
2. `delItemById(rid, costItemId, costNum)` 扣除
|
||
3. `addItem(rid, productItemId, productNum)` 发放
|
||
|
||
**约束**:检查/扣除/发放之间**没有锁**(单线程 Lua 协程模型,actor 内串行执行天然安全;跨 actor 调用需 RPC 等待)
|
||
|
||
---
|
||
|
||
### 5.4 道具使用 `Item.lua:response.ItemUse(msg)`
|
||
|
||
#### 5.4.1 触发流程(客户端)
|
||
|
||
```
|
||
玩家点击道具 → BagMediator.RefreshItemDetail 显示按钮
|
||
↓ 玩家点"使用"
|
||
BagMediator.BtnOperate 按 itemFunction 分支:
|
||
├─ itemFunction == 2 → 弹 BagGiftOpenView(选包),用户选完调 Send(itemIndex, num, selectId)
|
||
├─ itemFunction == 3 → 弹 BagGiftOpenView(兑换确认),用户确认后 Send(itemIndex, num, 0)
|
||
├─ itemFunction == 4 → cityBuffProxy.SendUseItem(data2) → Send(itemIndex, 1, 0)
|
||
├─ itemFunction == 10 → 检测迷雾,否则弹 OpenFogShow 选坐标后发请求(含 pos)
|
||
├─ itemFunction == 14 → 若已有不同档位预备部队 buff,二次确认
|
||
├─ itemFunction == 31 → 二次确认后发 Map_MoveCity(非 Item_ItemUse)
|
||
└─ 其他 → Send(itemIndex, num, 0) → 发 Item_ItemUse.request
|
||
```
|
||
|
||
`Send` 实现:
|
||
|
||
```csharp
|
||
var sp = new Item_ItemUse.request {
|
||
itemIndex = itemIndex,
|
||
itemNum = itemNum,
|
||
id = id, // >0 时才赋值,给 CHOOSE_ITEMPACKAGE 选包用
|
||
};
|
||
AppFacade.GetInstance().SendSproto(sp);
|
||
```
|
||
|
||
#### 5.4.2 服务端校验流程
|
||
|
||
`Item.lua:response.ItemUse` 完整流程(L82-221):
|
||
|
||
1. **参数校验**:`itemIndex` / `itemNum` 必填 → 否则 `ITEM_ARG_ERROR`
|
||
2. **道具存在**:`getItem(rid, itemIndex)` → 空则 `ITEM_NOT_EXIST`
|
||
3. **可用性**:`sitem.itemFunction == NOT_USE(0)` → `ITEM_NOT_USE`
|
||
4. **批量限制**:`itemNum > 1 && sitem.batchUse == NO(0)` → `ITEM_NOT_BATCH_USE`
|
||
5. **业务前置**:
|
||
- LEAGUE_POINTS:未入盟 → `ITEM_NOT_JOIN_GUILD`
|
||
6. **数量足够**:`itemInfo.overlay < itemNum` → `ITEM_NOT_ENOUGH`
|
||
7. **召唤怪物分支**:SUMMON_MONSTER 时先 `MonsterSummonMgr.req.summonMonster` 校验成功,失败 → `ITEM_SOMMON_MONSTER_FAILED`
|
||
8. **选包校验**:CHOOSE_ITEMPACKAGE 时 `s_ItemRewardChoice[data2][id]` 不存在 → `ITEM_PACKAGEID_NOT_EXIST`
|
||
9. **扣除道具**:`delItem(rid, itemIndex, itemNum, nil, USE_BAG_ITEM_COST_ITEM)`
|
||
10. **任务进度**:`TaskLogic:updateItemUseTaskSchedule(rid, nil, itemNum, sitem)`
|
||
11. **按 itemFunction 分发**:
|
||
- OPEN_ITEMPACKAGE / RECYCLE → `rewardId = data2`
|
||
- CHOOSE_ITEMPACKAGE → `rewardId = s_ItemRewardChoice[data2][id].reward`
|
||
- CITY_BUFF → `addCityBuff(data2)` 直接返回
|
||
- VIP / ACTION_FORCE / LEAGUE_POINTS → 调 `add*` 直接返回
|
||
- KINGDOM_MAP → `openNearDenseFog(pos)`,开雾失败则 `getItemPackage(data2)` 补偿,含 rewardInfo 返回
|
||
- SECONDE_QUEUE → 满队列返补偿包,否则 `unlockQueue`
|
||
- TRAIN_NUM → 修改 `itemAddTroopsCapacity` + `Count`,syncSelf 推送
|
||
12. **最终发奖**:若 `rewardId > 0` → `getItemPackage(rid, rewardId, openNum=itemNum)` 发放
|
||
13. **响应**:`{itemId, itemNum, rewardInfo, objectIndex(召怪用), pos(召怪位置), status(队列状态)}`
|
||
|
||
#### 5.4.3 选择数量使用 / 选择目标使用
|
||
|
||
| 模式 | 实现 |
|
||
|---|---|
|
||
| **批量数量** | request.itemNum 字段;服务端 `batchUse == YES` 时支持;`getItemPackage` 内部 `openNum = itemNum` 表示开 N 次抽奖 |
|
||
| **选包 ID** | request.id 字段;只在 CHOOSE_ITEMPACKAGE 时传,对应 `s_ItemRewardChoice[data2][id].reward` 之 itemPackage group |
|
||
| **选择坐标** | request.pos 字段;只用于 KINGDOM_MAP(开迷雾选位置) |
|
||
| **选择英雄** | **不支持**——经验书等给英雄加经验的道具走的是 `Hero_ExchangeHeroItem`(602)等独立 RPC,**不走 ItemUse** |
|
||
|
||
#### 5.4.4 涉及 RPC
|
||
|
||
| RPC | ID | request | response |
|
||
|---|---|---|---|
|
||
| `Item_ItemUse` | 852 | itemIndex, itemNum, id(选包), pos(王国地图) | itemId, itemNum, rewardInfo, status, objectIndex, pos |
|
||
|
||
#### 5.4.5 错误码
|
||
|
||
| 错误码 | 值 | 含义 |
|
||
|---|---|---|
|
||
| `ITEM_NOT_ENOUGH` | 8000 | 道具不足 |
|
||
| `ITEM_ARG_ERROR` | 8001 | 参数错误 |
|
||
| `ITEM_NOT_EXIST` | 8002 | 道具不存在 |
|
||
| `ITEM_NOT_BATCH_USE` | 8004 | 不支持批量使用 |
|
||
| `ITEM_NOT_USE` | 8005 | 不可使用 |
|
||
| `ITEM_PACKAGEID_NOT_EXIST` | 8006 | 所选礼包组不存在 |
|
||
| `ITEM_KINGDOM_MAP_NO_POS` | 8007 | 王国地图未传坐标(实测代码中未使用,预留) |
|
||
| `ITEM_NOT_JOIN_GUILD` | 8008 | 未加入联盟 |
|
||
| `ITEM_SOMMON_MONSTER_FAILED` | 8009 | 召怪失败(周围乱军太多) |
|
||
|
||
---
|
||
|
||
### 5.5 道具"脚本"机制(**实际上没有独立脚本表**)
|
||
|
||
#### 5.5.1 真实实现
|
||
|
||
`ItemScript.lua` 仅有 3 个空函数(`requireAllScript / useItem / dropItem`),**没有任何注册机制**。所有"脚本"逻辑都是 `Item.lua:ItemUse` 函数内的 `if/elseif (sitem.itemFunction)` 巨型分支。
|
||
|
||
**没有的东西**:
|
||
- ❌ 没有 itemId → Lua 函数的映射表
|
||
- ❌ 没有动态加载脚本
|
||
- ❌ 没有 `s_ItemScript` 配置表
|
||
|
||
**有的东西**:
|
||
- ✅ `ItemDefine.itemFunction`(int 枚举)一一对应硬编码 if 分支
|
||
- ✅ `ItemDefine.data1 / data2` 作为该分支的参数
|
||
|
||
#### 5.5.2 "脚本类型"清单(即 §4.2 ItemFunctionType 表)
|
||
|
||
按业务能力归类:
|
||
| 业务能力 | itemFunction | 实现位置 |
|
||
|---|---|---|
|
||
| 直接发奖励组 | 1 / 3 | `getItemPackage(data2)` |
|
||
| 选包奖励 | 2 | `s_ItemRewardChoice[data2][id].reward` → `getItemPackage` |
|
||
| 时效 Buff | 4 (CITY_BUFF) | `RoleLogic:addCityBuff(data2)` |
|
||
| 加 VIP / 行动力 | 5 / 6 | `RoleLogic:addVip / addActionForce` |
|
||
| 解锁迷雾 | 10 | `DenseFogLogic:openNearDenseFog(pos)` |
|
||
| 召唤野怪 | 12 | `MonsterSummonMgr.req.summonMonster(data2)` |
|
||
| 工人小屋扩展 | 13 | `BuildingLogic:unlockQueue` |
|
||
| 预备部队增容 | 14 | 修改 Role 字段 |
|
||
| 联盟积分 | 35 | `GuildLogic:addGuildCurrency(leaguePoints)` |
|
||
|
||
#### 5.5.3 选包奖励的客户端选择流程
|
||
|
||
```
|
||
点击使用 → BagMediator.BtnOperate 检测 itemFunction == 2
|
||
→ 弹 BagGiftOpenView (ShowType=1)
|
||
→ m_rewardGroupProxy.GetChoiceRewardDataByGroup(define.data2)
|
||
// 查 s_ItemRewardChoice[data2] 列表,每条 {id, reward(=itemPackage group)}
|
||
→ 用户点击某选项 → m_selectId = data.id
|
||
→ 点确定 → Item_ItemUse.request { itemIndex, itemNum, id = m_selectId }
|
||
→ 服务端按 id 查到具体 reward group → getItemPackage 发放
|
||
```
|
||
|
||
---
|
||
|
||
### 5.6 新道具标记 / 红点
|
||
|
||
#### 5.6.1 服务端/客户端分工
|
||
|
||
- **服务端**:**完全不维护新道具状态**,只负责增量推 `Item_ItemInfo`
|
||
- **客户端**:完全在 `BagProxy` 中维护:
|
||
- 持久化:`PlayerPrefs` 用 `{rid}/itemIndex:{itemIndex}` 存"新/旧/已清"三态
|
||
- 内存:`m_reddotRecord[type][itemIndex] = 新增数量` + `m_reddotTotalDic[type] = 红点总数`
|
||
|
||
#### 5.6.2 触发时机(`BagProxy.UpdateItemInfo` + `ReddotRecord`)
|
||
|
||
1. **首次登录**(`m_isFirstGetItemInfo == true`):**全部不算新道具**(避免登录时所有道具都亮红点),并强制 `SetLocalItemToOld` 把所有 itemIndex 标为 -1
|
||
2. **非首次**:服务端推来一条 ItemInfo,且 `ItemDefine.redDotPrompt == 1`,且:
|
||
- 新增 `itemIndex` → 红点+=overlay
|
||
- 同 `itemIndex` overlay 增加 → 红点+=增量
|
||
- 同 `itemIndex` overlay 减少 → 移除红点记录
|
||
3. **装备已穿戴**(`heroId != 0`):直接 `SetLocalItemToOld`,不亮
|
||
|
||
#### 5.6.3 清除时机
|
||
|
||
| 函数 | 时机 |
|
||
|---|---|
|
||
| `ClearReddotRecordByIndex(index, itemId)` | 用户在 BagPanel 选中该道具看详情时(`RefreshItemDetail` 内调) |
|
||
| `ClearReddotRecordByType(type)` | 用户切换 Bag Tab 时(`SwitchMenu` 内对原 type 调) |
|
||
| `ClearAllReddotRecord()` | BagPanel 关闭时(`BagMediator.OnRemove`) |
|
||
|
||
> ⚠️ 设计后果:**只要打开过背包并选了道具,红点就消**。不需要服务端记账。
|
||
|
||
#### 5.6.4 红点数量统计
|
||
|
||
`GetBagReddotTotal()` = sum over type `GetBagReddotNumByType(type)` = sum over reddotRecord[type][index]
|
||
|
||
---
|
||
|
||
### 5.7 资源类道具(金币 / 钻石 / 等)
|
||
|
||
#### 5.7.1 双轨制设计(**关键决策点**)
|
||
|
||
ROK 采取**双轨制**:
|
||
|
||
| 维度 | 玩家货币(Role 字段) | 资源类道具(Bag Item) |
|
||
|---|---|---|
|
||
| 存储位置 | `Role.food / wood / stone / gold / denar / vip / actionForce / expeditionCoin / individualPoints / leaguePoints / activityActivePoint` | `s_Item` 配置 + 背包 Item 条目 |
|
||
| 操作接口 | `RoleLogic:addGold / addStone / addFood / addVip / addActionForce …` | `ItemLogic:addItem / delItem` |
|
||
| 同步协议 | `Role_RoleInfo` 推送 | `Item_ItemInfo` 推送 |
|
||
| 上限 | 单字段 int64,配置表内有 individualPointsLimit/alliancePointsLimit 等业务上限 | `s_GameWarning.num` 仅做告警,**无硬上限** |
|
||
| 红点 | 无 | 有(按 type 统计) |
|
||
| 例子 | 玩家身上的"500 金币"是 Role.gold 字段 | 背包里"金币袋 x3(每袋可换 1000 金币)"是 itemId 在 RESOURCE 类下的 Item |
|
||
|
||
#### 5.7.2 资源类道具如何转成货币
|
||
|
||
`Item_ItemChangeResource`(851):
|
||
```
|
||
request: { itemIndex, itemNum }
|
||
→ getItem(rid, itemIndex) 取 itemInfo
|
||
→ 校验 s_Item.type == RESOURCE 或 subType == ACTION_FORCE → 否则 ITEM_NOT_RESOURCE_ITEM
|
||
→ checkOverlay >= itemNum → 否则 ITEM_NOT_ENOUGH
|
||
→ delItem 扣除
|
||
→ 按 subType 分支调对应 RoleLogic:add*:
|
||
- GOLD → addGold(data1 * itemNum)
|
||
- STONE → addStone
|
||
- WOOD → addWood
|
||
- GRAIN → addFood
|
||
- VIP → addVip
|
||
- ACTION_FORCE → addActionForce
|
||
→ updateItemUseTaskSchedule
|
||
response: { result, itemId, itemNum }
|
||
```
|
||
|
||
> ⚠️ **没有 DENAR(钻石)/ DIAMOND 这条分支**——钻石不能通过道具兑换得来(设计上钻石是付费货币)。
|
||
|
||
#### 5.7.3 资源大额变动审计
|
||
|
||
- 全部走 `LogLogic:itemChange` / `LogLogic:roleChange`(每个 RoleLogic:add* 内部都会调)
|
||
- 阈值告警:`s_GameWarning.num` 配置单 itemId 阈值 → `Common.sendResourceAlarm(rid, itemId, newNum)` 发给监控
|
||
|
||
---
|
||
|
||
### 5.8 初始资源发放
|
||
|
||
`ItemLogic:createRoleGiveItems(rid)`(L49-66):
|
||
|
||
```
|
||
读取 s_Config:Get("initialItemType") 和 ("initialItemNum")
|
||
两个 list 长度必须一致(不一致以小的为准)
|
||
循环调 addItem(rid, itemId, itemNum, eventType=GUILD_CREATE_ROLE_GAIN_ITEM)
|
||
```
|
||
|
||
**实际配置值**(`Server/common/config/gen/Configs.data` L13376 `s_Config[0]`):
|
||
|
||
```
|
||
initialItemType = { 502030004, 502010001, 502020001, 502040002, 201010010, 201010013, 502070001, 502070003 }
|
||
initialItemNum = { 8, 1, 1, 1, 30, 8, 10, 10 }
|
||
|
||
initialFood = 100000
|
||
initialWood = 100000
|
||
initialStone = 0
|
||
initialGold = 0
|
||
initialDiamond = 300 // 钻石(denar)
|
||
initialArmsType = { 10101 } // 士兵类型
|
||
initialArmsNum = { 1000 } // 士兵数
|
||
initialBuff = 30105 // 初始 City Buff(治疗加速)
|
||
```
|
||
|
||
- 道具初始有 8 种 × 各若干(共 69 个道具实例)
|
||
- 货币初始:粮 10W、木 10W、钻 300,金/石/行动力默认 0
|
||
- 士兵初始:10101 类型 1000 个
|
||
|
||
> 资源发放走 `RoleLogic:createRole`(或 `setRole` 直接赋值字段),不走 `addItem`。
|
||
|
||
---
|
||
|
||
## 6. 协议清单
|
||
|
||
### 6.1 RPC 协议(请求-响应)
|
||
|
||
| 协议名 | ID | 方向 | request | response | 用途 |
|
||
|---|---|---|---|---|---|
|
||
| `Item_ItemChangeResource` | 851 | C→S | `itemIndex, itemNum` | `result, itemId, itemNum` | 使用资源类道具兑换玩家货币(金/木/石/粮/VIP/行动力) |
|
||
| `Item_ItemUse` | 852 | C→S | `itemIndex, itemNum, id, pos` | `itemId, itemNum, rewardInfo, status, objectIndex, pos` | 使用任意可用道具(按 itemFunction 分发) |
|
||
|
||
### 6.2 推送协议(服务端 → 客户端)
|
||
|
||
| 协议名 | ID | 方向 | request | 用途 |
|
||
|---|---|---|---|---|
|
||
| `Item_ItemInfo` | 30101 | S→C | `itemInfo : *ItemInfo(itemIndex)` | 增量推送道具变更(新增/数量变更/删除),客户端按 `itemIndex` 合并到本地 `Items` dict |
|
||
|
||
### 6.3 间接相关协议(涉及道具但不属于 Item 模块)
|
||
|
||
| 协议 | 用途 |
|
||
|---|---|
|
||
| `Shop_BuyShopItem (901)` | 普通商店购买,含 itemId/itemNum |
|
||
| `Shop_BuyPostItem (902)` | 驿站道具 |
|
||
| `Shop_BuyVipStore (904)` | VIP 商店购买 |
|
||
| `Shop_BuyExpeditionStore (905)` | 远征商店购买 |
|
||
| `Build_ProduceMaterial / Build_MaterialDecomposition / Build_MaterialSynthesis / Build_MakeEquipment / Build_DecompositionEquipment / Build_CheckMakeEquip` | 装备/材料生产/合成/分解 |
|
||
| `Hero_ExchangeHeroItem (605)` | 英雄碎片兑换 |
|
||
| `Role_RoleInfo (30251)` | 含 RoleInfo(货币字段 food/wood/stone/gold/denar 等) |
|
||
|
||
---
|
||
|
||
## 7. 错误码清单(`ITEM_` 前缀)
|
||
|
||
| 错误码 | 值 | 含义 |
|
||
|---|---|---|
|
||
| `ITEM_NOT_ENOUGH` | 8000 | 道具不足 |
|
||
| `ITEM_ARG_ERROR` | 8001 | 道具模块参数错误 |
|
||
| `ITEM_NOT_EXIST` | 8002 | 道具不存在 |
|
||
| `ITEM_NOT_RESOURCE_ITEM` | 8003 | 不是资源类型道具(ChangeResource 用) |
|
||
| `ITEM_NOT_BATCH_USE` | 8004 | 道具不支持批量使用 |
|
||
| `ITEM_NOT_USE` | 8005 | 道具无法使用(itemFunction=0 或前置不满足) |
|
||
| `ITEM_PACKAGEID_NOT_EXIST` | 8006 | 所选道具礼包组不存在 |
|
||
| `ITEM_KINGDOM_MAP_NO_POS` | 8007 | 使用王国地图未上传坐标(代码中预留,未实际触发) |
|
||
| `ITEM_NOT_JOIN_GUILD` | 8008 | 未加入任何联盟(用 LEAGUE_POINTS 道具时) |
|
||
| `ITEM_SOMMON_MONSTER_FAILED` | 8009 | 召唤怪物失败 |
|
||
|
||
### 7.1 关联模块道具相关错误码(散落在其他模块前缀)
|
||
|
||
| 错误码 | 值 | 含义 |
|
||
|---|---|---|
|
||
| `ROLE_NAME_ITEM_DENAR_NOT_ENOUGH` | 1046 | 改名道具+钻石都不足 |
|
||
| `ROLE_BUFF_ITEM_NOT_ENOUGH` | 1048 | buff 道具不足 |
|
||
| `ROLE_SPEED_ITEM_NOT_ENOUGH` | 1066 | 加速道具不足 |
|
||
| `MAP_MOVE_CITY_ITEM_NOT_ENOUGH` | 2030 | 迁城道具不足 |
|
||
| `HERO_SUMMON_ITEM_NOT_ENOUGH` | 3001 | 召唤统帅道具不足 |
|
||
| `HERO_LEVEL_ITEM_NOT_ENOUGH` | 3005 | 升级技能道具不足 |
|
||
| `HERO_EXCHANGE_ITEM_NOT_ENOUGH` | 3010 | 通用雕像不足 |
|
||
| `HERO_ITEM_ERROR` | 3015 | 升星材料类型错误 |
|
||
| `HERO_ITEM_TO_MUCH` | 3016 | 道具不足无法升星 |
|
||
| `BUILDING_ITEM_ERROR` | 6021 | 解锁建筑道具类型错误 |
|
||
| `BUILDING_SMITHY_ITEM_ERROR` | 6024 | 无法生产该道具 |
|
||
| `BUILDING_SMITHY_ITEM_NOT_ENOUGH` | 6027 | 该材料不足 |
|
||
| `BUILDING_EQUIP_ITEM_NO_ENOUGH` | 6030 | 装备道具不足 |
|
||
| `SHOP_ITEM_NOT_SELL` | 10000 | 商品没在商店售卖 |
|
||
| `SHOP_POST_ITEM_NOT_EXIST` | 10001 | 驿站道具不存在 |
|
||
| `SHOP_POST_ITEM_HAVE_BUY` | 10002 | 驿站道具已购买 |
|
||
| `SHOP_EXPEDITION_ITEM_COUNT_MAX` | 10007 | 远征商店购买超上限 |
|
||
| `GUILD_SHOP_ITEM_NOT_EXIST` | 12073 | 联盟商店商品不存在 |
|
||
| `GUILD_SHOP_ITEM_NOT_ENOUGH` | 12074 | 联盟商店商品不足 |
|
||
|
||
> ⚠️ **`BAG_` 前缀错误码不存在**——背包功能复用 `ITEM_` 前缀。
|
||
|
||
---
|
||
|
||
## 8. UI 流程(`BagMediator` / `BagGiftOpenMediator` / `UI_Win_UseItemMediator`)
|
||
|
||
### 8.1 BagPanel 分页
|
||
|
||
- **5 个 Tab**:资源(1) / 加速(2) / 增益(3) / 装备(4) / 其他(5)
|
||
- Tab 切换:`OnMenuRes/Speed/Gain/Equip/Other` → `SwitchMenu(type)` → 切 Tab 时**自动清除当前 Tab 的红点记录**
|
||
- 每个 Tab 显示:4 列 × N 行(`m_itemCol = 4`,`m_itemLineCount = ceil(count/4)`)
|
||
- 数据源:`m_bagProxy.Items` 全部过滤 `overlay > 0`,按 `type = itemId / 100000000` 分组
|
||
- Tab 角标红点数:`m_pageReddotImgList[i]` + `GetBagReddotNumByType(i+1)`
|
||
|
||
### 8.2 排序规则
|
||
|
||
`DataSort` 在每个 Tab 数据列表上:
|
||
1. 按 `ItemDefine.rank` 升序
|
||
2. rank 相同则按 itemId 升序
|
||
|
||
### 8.3 道具详情 Tooltip 显示内容
|
||
|
||
`RefreshItemDetail` 渲染:
|
||
- 品质背景图(`m_qualityDic[quality]`,5 档)
|
||
- 道具图标(`itemDefine.itemIcon`)
|
||
- 顶部小卡描述(仅 `l_topID >= 1` 时显示,格式 `format(text, topData)`)
|
||
- 名称(`l_nameID`)
|
||
- **装备**(`typeGroup == 2`)→ 显示 `UI_Item_EquipAtt` 属性卡片
|
||
- **非装备** → 显示 `l_desID` 格式化文本 `format(text, desData1, desData2)`
|
||
- **可批量使用**(`batchUse == 1`)→ 显示 `+/-` 数量调整 + 输入框 + Max 按钮
|
||
- **使用按钮**:仅 `l_buttonDes >= 1` 时显示(文字来自语言包)
|
||
|
||
### 8.4 使用流程的二次确认
|
||
|
||
| 情况 | 是否二次确认 |
|
||
|---|---|
|
||
| 普通礼包(itemFunction=1) | 无确认,直接发请求 |
|
||
| 选包奖励(itemFunction=2) | 弹 `BagGiftOpenView` 必选,本身就是确认界面 |
|
||
| 兑换确认(itemFunction=3) | 弹 `BagGiftOpenView` 显示前后对比 |
|
||
| 王国地图(itemFunction=10) | 选坐标本身就是确认 |
|
||
| 预备部队(itemFunction=14) | 若已有不同档位 buff 弹 `Alert` 二次确认 |
|
||
| VIP 道具(UseItemView) | 计算 overflow,超出时弹 `Alert` 二次确认 |
|
||
| 随机迁城(itemFunction=31) | 强制弹 `Alert` 二次确认 |
|
||
| 其他 | 无 |
|
||
|
||
### 8.5 ItemUse 响应处理(`PlayerCmd.cs:Item_ItemUse.TagName`)
|
||
|
||
1. 错误响应 → `ErrorCodeHelper.ShowErrorCodeTip`
|
||
2. 成功:`Tip.CreateTip("使用了 {道具名} x{数量}")`
|
||
3. 按 `itemFunction` 分发后续 UI:
|
||
- 10/13 → 弹 `s_itemCollection` 显示奖励
|
||
- 12 → 相机飞向召唤点 + 召唤特效
|
||
- 其他 → 若 `HasRewardInfo` 弹 `s_rewardGetWin` 奖励展示
|
||
|
||
---
|
||
|
||
## 9. 与 Survivors 实现的对照点
|
||
|
||
### 9.1 模块对应表
|
||
|
||
| ROK 模块 | Survivors 对应(计划) | 说明 |
|
||
|---|---|---|
|
||
| `BagProxy.cs` + 客户端缓存 | `BagComponent`(挂玩家 Entity) + `BagComponentSystem` | ET 风格的 Component/System,业务逻辑放 System |
|
||
| `ItemLogic.lua:addItem/delItem` | `BagComponentSystem.AddItems / RemoveItems` | 统一入口,但 **Survivors 设计是"所有奖励都走 Item"**,比 ROK 更激进 |
|
||
| `Item.lua:ItemUse` 巨型 if/elseif | C# `Dictionary<int, IItemUseHandler>` 注册机制 | 比 Lua 硬编码更可扩展,便于单测 |
|
||
| `s_Item` + `ItemDefine` | Luban 配置 `ItemConfig` | 字段保留 quality/type/subType/itemFunction/data1/data2,删掉 ROK 残留(`exclusive` 改名 `bindType` 等) |
|
||
| `Item_ItemInfo` 增量推送 | `M2C_ItemInfoSync`(protobuf) | 用 MemoryPack 序列化 |
|
||
| `Item_ItemUse` (852) | `C2M_UseItem` / `M2C_UseItemResponse` | RPC + 推送分离 |
|
||
| `BagPanel.prefab`(5 Tab) | `BagPanel.prefab`(YIUI,4 Tab:资源/装备/道具/材料) | Survivors 设计简化成 4 Tab |
|
||
| `BagGiftOpenView`(选包) | `GiftOpenPanel` | 同流程 |
|
||
| 红点 PlayerPrefs | ET `RedDotComponent` + 持久化到 Mongo | 服务端记账,多端同步 |
|
||
| 创角初始道具 `initialItemType/Num` | `InitialResourceConfig`(Luban) | 列表配置,启动时遍历 AddItems |
|
||
| 错误码 8000-8009 | `ErrorCode.cs` 同段位 | 直接复用 8000-8009 |
|
||
|
||
### 9.2 关键差异决策
|
||
|
||
| 维度 | ROK | Survivors 设计 |
|
||
|---|---|---|
|
||
| **货币/道具是否合并** | **分离**(双轨制) | **合并**(所有可获得资源 = Item,金币也是 itemId) |
|
||
| **使用脚本机制** | 硬编码 if/elseif(itemFunction 枚举) | C# `IItemUseHandler` 注册(每个 itemFunction 一个 handler 类) |
|
||
| **新道具红点** | 客户端 PlayerPrefs | 服务端 Mongo + 客户端缓存 |
|
||
| **配置表** | SQLite + 自研 Define | Luban 生成 |
|
||
| **协议** | sproto | protobuf3 + MemoryPack |
|
||
| **持久化** | 服务端 Redis + 落 SQL | Mongo(IDBComponent) |
|
||
| **装备穿戴** | item.heroId 字段 | 同 |
|
||
| **奖励合并** | `mergeReward` Lua | C# 通用 RewardEntry 列表 |
|
||
|
||
### 9.3 设计陷阱(迁移时易踩)
|
||
|
||
1. **不要直接搬"双轨制"**:Survivors 设计目标是统一 Item。金币/钻石如果也作为 Item,则没有 `Item_ItemChangeResource` 这个 RPC,相关 `ItemType.RESOURCE` 子类型大量道具变成"普通礼包道具"
|
||
2. **`exclusive` 字段**:ROK 是装备专属英雄 ID,Survivors 设计中可能不需要装备绑定,可去除
|
||
3. **`uniqueIndex` 字段**:ROK 中预留未用,Survivors 直接删掉
|
||
4. **`maxStack` 缺失**:必须新增(防溢出 int 攻击)
|
||
5. **`itemFunction` 拆分**:ROK 的 35 个值分散且历史包袱重,Survivors 应只实现 P1-P3 核心 5-8 个
|
||
6. **`itemId` 编码约定**:ROK 用 `itemId / 100000000` 划分 type,Survivors 应改为 ItemConfig 显式 type 字段(itemId 不再有语义)
|
||
|
||
---
|
||
|
||
## 10. 实现优先级建议
|
||
|
||
### P1 必须(核心基础设施,无 UI)
|
||
| # | 任务 | 对应 ROK | 备注 |
|
||
|---|---|---|---|
|
||
| 1 | `ItemConfig` Luban 配置定义(含 type/subType/quality/itemFunction/data1/data2/icon/name/desc/maxStack) | `s_Item` + `ItemDefine` | 删 exclusive/uniqueIndex,加 maxStack |
|
||
| 2 | `BagComponent`(玩家身上挂) + `ItemUnit`(子 Entity,itemIndex 主键) | `MSM.d_item[rid]` | Mongo 持久化 |
|
||
| 3 | `BagComponentSystem.AddItems / RemoveItems / HasItems / GetItemNum` | `addItem / delItem / checkItemEnough` | **统一发奖/扣资源入口**,所有业务都调它 |
|
||
| 4 | `M2C_ItemInfoSync` 增量推送 | `Item_ItemInfo` (30101) | 客户端 `BagComponentClient` 合并 |
|
||
| 5 | `RewardEntry { itemId, count }` 通用结构 | `.RewardItem` | 一切奖励/消耗都用它 |
|
||
| 6 | `InitialResourceConfig` + 创角发放 | `s_Config.initialItemType/Num` | Login 时调 |
|
||
| 7 | 错误码 8000-8009 | ErrorCode.cs | 直接复用 |
|
||
|
||
### P2 必须(道具使用 + 核心 itemFunction)
|
||
| # | 任务 | 对应 ROK | 备注 |
|
||
|---|---|---|---|
|
||
| 1 | `C2M_UseItem` Handler | `Item.lua:ItemUse` | 校验/扣道具/调 handler/返回结果 |
|
||
| 2 | `IItemUseHandler` 注册机制 + `ItemUseHandlerComponent` | 巨型 if/elseif | C# 字典查找替代硬编码 |
|
||
| 3 | `ItemUseHandler_OpenPackage`(itemFunction=1) | OPEN_ITEMPACKAGE | 调 `ItemPackageSystem.GetReward` |
|
||
| 4 | `ItemUseHandler_ChoosePackage`(itemFunction=2) | CHOOSE_ITEMPACKAGE | 含 request.id 参数 |
|
||
| 5 | `ItemPackageConfig` + `ItemPackageSystem.GetReward(groupId, openNum)` | `s_ItemPackage` + `getItemPackage` | 加权随机 |
|
||
| 6 | `ItemRewardChoiceConfig` | `s_ItemRewardChoice` | 选包配置 |
|
||
| 7 | 操作日志 `LogLogic:itemChange` 等效 | `LogLogic` | jsonl 落盘 |
|
||
|
||
### P3 可推迟(业务扩展类 itemFunction)
|
||
| # | 任务 | 备注 |
|
||
|---|---|---|
|
||
| 1 | itemFunction=5/6 VIP/行动力(**如果 Survivors 把它们作为 Item**,则这俩 handler 不需要,直接发 itemId) | |
|
||
| 2 | itemFunction=4 CityBuff(如果 Survivors 有 buff 系统才做) | |
|
||
| 3 | itemFunction=10 解锁迷雾(Survivors 无大地图,**跳过**) | |
|
||
| 4 | itemFunction=12 召唤怪物(**跳过**) | |
|
||
| 5 | itemFunction=13/14 工人/部队(SLG 专有,**跳过**) | |
|
||
| 6 | itemFunction=30/31 迁城(**跳过**) | |
|
||
| 7 | itemFunction=35 联盟积分(无联盟,**跳过**) | |
|
||
|
||
### P4 UI 集中实现
|
||
| # | 任务 | 对应 ROK |
|
||
|---|---|---|
|
||
| 1 | `BagPanel`(4 Tab:资源/装备/道具/材料) | `BagView` |
|
||
| 2 | `BagItemUICommon` 通用道具显示组件 | `UI_Item_Bag` + `UI_Model_Item` |
|
||
| 3 | `BagItemTooltip` 道具详情浮层 | `RefreshItemDetail` 内的详情面板 |
|
||
| 4 | `GiftOpenPanel` 选包/兑换 UI | `BagGiftOpenView` |
|
||
| 5 | `RewardGetPanel` 奖励展示弹窗 | `s_rewardGetWin` |
|
||
| 6 | Tab 红点 | `m_pageReddotImgList` |
|
||
|
||
### P5 后续完善
|
||
| # | 任务 |
|
||
|---|---|
|
||
| 1 | 资源阈值告警(`GameWarning` 配置 + Alarm) |
|
||
| 2 | 大额变动审计日志 |
|
||
| 3 | 装备分解/合成(如果做装备系统) |
|
||
| 4 | 装备图纸(如果做装备系统) |
|
||
|
||
---
|
||
|
||
## 附录 A:ROK 关键源代码位置索引
|
||
|
||
| 内容 | 路径 |
|
||
|---|---|
|
||
| 服务端 ItemLogic(addItem/delItem/giveReward/getItemPackage) | `Server/server/game_server/logic/lualib/ItemLogic.lua` |
|
||
| 服务端 Item proxy(ItemUse/ItemChangeResource RPC handler) | `Server/server/game_server/logic/service/proxy/Item.lua` |
|
||
| 服务端 Item 数据 service | `Server/server/game_server/logic/service/data/d_item.lua`(snax 服务,本次未深入) |
|
||
| 道具枚举(ItemType/SubType/FunctionType/PackageType/BatchUse) | `Server/common/lualib/enum/ItemEnum.lua` |
|
||
| CurrencyType 枚举(货币不是 Item) | `Server/common/lualib/enum/OtherEnum.lua` L101 |
|
||
| ItemScript(**空壳**,无实质内容) | `Server/server/game_server/logic/lualib/itemscript/ItemScript.lua` |
|
||
| Common.sproto(ItemInfo / RewardInfo / RewardItem) | `Server/common/protocol/Common.sproto` L308 / L381 / L362 |
|
||
| Protocol.sproto(Item_ItemUse 852 / Item_ItemChangeResource 851 / Item_ItemInfo 30101) | `Server/common/protocol/Protocol.sproto` |
|
||
| 服务端 s_Config 初始道具配置(initialItemType/Num) | `Server/common/config/gen/Configs.data` L13376(id=0 行) |
|
||
| 客户端 BagProxy(增量同步/红点/装备/材料) | `Client/Assets/Scripts/Hotfix/MVC/Proxy/BagProxy.cs` |
|
||
| 客户端 BagMediator(5 Tab + 使用按钮分支) | `Client/Assets/Scripts/Hotfix/MVC/View_Mediator/Bag/BagMediator.cs` |
|
||
| 客户端 BagGiftOpenMediator(选包/兑换 UI) | `Client/Assets/Scripts/Hotfix/MVC/View_Mediator/Bag/BagGiftOpenMediator.cs` |
|
||
| 客户端 UI_Win_UseItemMediator(行动力/VIP 快捷使用) | `Client/Assets/Scripts/Hotfix/MVC/View_Mediator/UseItem/UI_Win_UseItemMediator.cs` |
|
||
| 客户端 PlayerCmd.cs(Item_ItemUse 响应处理) | `Client/Assets/Scripts/Hotfix/MVC/CMD/PlayerCmd.cs` L38-128 |
|
||
| 客户端 ItemDefine(25 字段) | `Client/Assets/Scripts/Hotfix/Config/ItemConfig.cs` |
|
||
| 客户端 ItemPackageConfig / ItemRewardChoiceConfig 等 | `Client/Assets/Scripts/Hotfix/Config/Item*Config.cs`(7 个) |
|
||
| 错误码 ITEM_ 前缀(8000-8009) | `Client/Assets/Scripts/Hotfix/MVC/CMD/ErrorCode.cs` L255-264 |
|
||
| 客户端配置 Bin 文件 | `Client/Assets/StreamingAssets/Config/Bin/Item.bin`(87 KB) |
|
||
|
||
## 附录 B:未深入调研的边角
|
||
|
||
- `s_Item.lua` 的 ConfigEntity 加载机制(只看了 stub,没看 ConfigEntity 父类)
|
||
- `MSM.d_item[rid]` snax 服务实现细节
|
||
- `Build_*` 装备制造/合成 RPC 详细参数(涉及 EquipDefine + EquipMaterialDefine 联动)
|
||
- `Hero_ExchangeHeroItem` 等英雄相关道具用法(属于 Hero 模块)
|
||
- 钻石(denar)的付费充值入口(RechargeLogic,属于充值模块)
|
||
- 联盟礼物 `guildGifts` 在奖励组中的具体发放机制(属于 Guild 模块)
|
||
- `LogLogic:itemChange` 的具体日志格式(输出到 log_server)
|