47 KiB
ROK 背包/道具系统业务规则(1:1 迁移依据)
来源:
E:\Game\gmd\ROK服务端 Lua(ItemLogic.lua/Item.luaproxy)+ 客户端 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.luaproxy 的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 dictGet(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 / HeroIDEquipItemInfo : 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):
- 查
s_Item:Get(itemId),不存在 →LOG_ERROR返回 nil - 特殊分支:若
sitemInfo.type == HEAD,调RoleLogic:unlockRoleHead(rid, itemId)解锁头像框后直接返回{},不进背包 - 是否堆叠:
- 装备类(
isEquipItem(subType)= subType 在 ARMS/HELMET/BREASTPLATE/GLOVES/PANTS/ACCESSORIES/SHOES 中)→ 跳过查找,必新增 - 其他:遍历
getItem(rid)查同itemId的现有 itemIndex
- 装备类(
- 堆叠分支:找到现有同 itemId →
overlay = oldOverlay + itemNum,Set(rid, itemIndex, overlay, newNum) - 新建分支:
- 非装备:
getFreeItemIndex(rid)取最小空 index,新建一条{itemId, overlay=itemNum, rid, itemIndex, exclusive, heroId=0},Add(rid, itemIndex, itemInfo) - 装备:循环 itemNum 次,每次
getFreeItemIndex+overlay=1单独建条目(装备永远不堆叠)
- 非装备:
- 同步:
syncItem(rid, nil, syncItemInfo, true)推Item_ItemInfo - 日志:若
eventType非空,调LogLogic:itemChange({rid, iggid, itemId, changeNum, oldNum, newNum, logType, logType2}) - 资源阈值告警:若
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):
getItem(rid, itemIndex)取道具实体- 数量判定:
overlay <= itemNum→ 整条删除:MSM.d_item[rid].req.Delete(rid, itemIndex),newNum = 0overlay > itemNum→ 扣减:Set(rid, itemIndex, overlay, overlay - itemNum)
- 同步推送
{[Enum.Item.overlay] = newNum} - 日志:
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:
checkItemEnough(rid, costItemId, costNum)→ false 返回ITEM_NOT_ENOUGHdelItemById(rid, costItemId, costNum)扣除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 实现:
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):
- 参数校验:
itemIndex/itemNum必填 → 否则ITEM_ARG_ERROR - 道具存在:
getItem(rid, itemIndex)→ 空则ITEM_NOT_EXIST - 可用性:
sitem.itemFunction == NOT_USE(0)→ITEM_NOT_USE - 批量限制:
itemNum > 1 && sitem.batchUse == NO(0)→ITEM_NOT_BATCH_USE - 业务前置:
- LEAGUE_POINTS:未入盟 →
ITEM_NOT_JOIN_GUILD
- LEAGUE_POINTS:未入盟 →
- 数量足够:
itemInfo.overlay < itemNum→ITEM_NOT_ENOUGH - 召唤怪物分支:SUMMON_MONSTER 时先
MonsterSummonMgr.req.summonMonster校验成功,失败 →ITEM_SOMMON_MONSTER_FAILED - 选包校验:CHOOSE_ITEMPACKAGE 时
s_ItemRewardChoice[data2][id]不存在 →ITEM_PACKAGEID_NOT_EXIST - 扣除道具:
delItem(rid, itemIndex, itemNum, nil, USE_BAG_ITEM_COST_ITEM) - 任务进度:
TaskLogic:updateItemUseTaskSchedule(rid, nil, itemNum, sitem) - 按 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 推送
- OPEN_ITEMPACKAGE / RECYCLE →
- 最终发奖:若
rewardId > 0→getItemPackage(rid, rewardId, openNum=itemNum)发放 - 响应:
{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)
- 首次登录(
m_isFirstGetItemInfo == true):全部不算新道具(避免登录时所有道具都亮红点),并强制SetLocalItemToOld把所有 itemIndex 标为 -1 - 非首次:服务端推来一条 ItemInfo,且
ItemDefine.redDotPrompt == 1,且:- 新增
itemIndex→ 红点+=overlay - 同
itemIndexoverlay 增加 → 红点+=增量 - 同
itemIndexoverlay 减少 → 移除红点记录
- 新增
- 装备已穿戴(
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 数据列表上:
- 按
ItemDefine.rank升序 - 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)
- 错误响应 →
ErrorCodeHelper.ShowErrorCodeTip - 成功:
Tip.CreateTip("使用了 {道具名} x{数量}") - 按
itemFunction分发后续 UI:- 10/13 → 弹
s_itemCollection显示奖励 - 12 → 相机飞向召唤点 + 召唤特效
- 其他 → 若
HasRewardInfo弹s_rewardGetWin奖励展示
- 10/13 → 弹
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 设计陷阱(迁移时易踩)
- 不要直接搬"双轨制":Survivors 设计目标是统一 Item。金币/钻石如果也作为 Item,则没有
Item_ItemChangeResource这个 RPC,相关ItemType.RESOURCE子类型大量道具变成"普通礼包道具" exclusive字段:ROK 是装备专属英雄 ID,Survivors 设计中可能不需要装备绑定,可去除uniqueIndex字段:ROK 中预留未用,Survivors 直接删掉maxStack缺失:必须新增(防溢出 int 攻击)itemFunction拆分:ROK 的 35 个值分散且历史包袱重,Survivors 应只实现 P1-P3 核心 5-8 个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)