510 lines
21 KiB
Markdown
510 lines
21 KiB
Markdown
# ROK 装备系统业务规则(1:1 迁移依据)
|
||
|
||
> 来源:`E:\Game\gmd\ROK` 服务端 Lua(`HeroLogic.lua` / `BuildingLogic.lua` / `ItemLogic.lua` / proxy `Hero.lua` / proxy `Build.lua`)+ 客户端 Mediator/View + sproto 协议 + Config DTO
|
||
> 用途:作为 Survivors 工程装备子系统实现的业务依据
|
||
> 调研日期:2026-05-27
|
||
|
||
---
|
||
|
||
## 0. 核心结论(先看这里)
|
||
|
||
- **装备就是 Item 的一种**(带 `heroId` 字段、不堆叠、每件独立 `itemIndex`);不需要建独立 EquipEntity,靠 ItemUnit 的 `heroId>0` 区分
|
||
- **Hero 持有装备的方式**:Hero Entity 上有 8 个整型字段(`head`/`breastPlate`/`weapon`/`gloves`/`pants`/`accessories1`/`accessories2`/`shoes`),值是 `itemIndex` 引用回背包
|
||
- **8 个槽位 ↔ 6 种 subType 的多对一映射**:accessories1 与 accessories2 都接 `ACCESSORIES` 子类型(共享一种装备池)
|
||
- **锻造分两段 RPC**:先 `CheckMakeEquip` 跑 50% 概率给"专属"标记 → 再 `MakeEquipment` 真正消耗材料 + 金币产出装备;专属属性写在装备 Item 的 `exclusive` 字段
|
||
- **分解、合成(材料 mix/split)都是确定性配置查询**,不带随机
|
||
- **错误码段**:装备穿戴用 Hero 段(3024-3027),锻造用 Building 段(6029-6031)
|
||
- **建议**:在 Survivors 不建独立 `cn.etetet.equip` 包,**作为 `cn.etetet.bag` 的子系统实现**(与 Item 共享数据底座),锻造逻辑放 `cn.etetet.bag/Equip/Forge.cs` 子目录
|
||
|
||
---
|
||
|
||
## 1. 整体业务范围
|
||
|
||
| 功能模块 | 实现位置(ROK 原版) | 是否 P1 必需 |
|
||
|---|---|---|
|
||
| 装备穿戴(WearEquip) | `proxy/Hero.lua:414` + `HeroLogic.lua` | P2(与英雄养成同期) |
|
||
| 装备卸下(TakeOffEquip) | `proxy/Hero.lua:486` | P2 |
|
||
| 装备战力计算(套装加成) | `HeroLogic.lua:792 getHeroEquipAttr` | P2 |
|
||
| 装备锻造(MakeEquipment) | `proxy/Build.lua:742` + `BuildingLogic` | P3 |
|
||
| 锻造预检(CheckMakeEquip) | `proxy/Build.lua:713` | P3 |
|
||
| 装备分解(DecompositionEquipment) | `proxy/Build.lua:792` | P3 |
|
||
| 材料合成(mix) | 配置 + ItemScript 模式 | P3+ |
|
||
| 材料分解(split) | 配置 + ItemScript 模式 | P3+ |
|
||
| 套装效果(2/4/6/8 件套) | `HeroLogic.lua:832` + `EquipComposeDefine` | P2 |
|
||
| 专属属性(exclusive) | `RoleLogic.exclusive` 角色标记 + 装备 Item `exclusive` 字段 | P3+(可推迟) |
|
||
|
||
---
|
||
|
||
## 2. 数据模型
|
||
|
||
### 2.1 Hero Entity 上的装备字段
|
||
|
||
Hero 实体直接挂 8 个整型字段(值为 `itemIndex`,0 表示空槽):
|
||
|
||
| 字段名 | 槽位 | 接受的 `ItemSubType` |
|
||
|---|---|---|
|
||
| `head` | 1 | `HELMET` |
|
||
| `breastPlate` | 2 | `BREASTPLATE` |
|
||
| `weapon` | 3 | `ARMS` |
|
||
| `gloves` | 4 | `GLOVES` |
|
||
| `pants` | 5 | `PANTS` |
|
||
| `accessories1` | 6 | `ACCESSORIES` |
|
||
| `accessories2` | 7 | `ACCESSORIES` |
|
||
| `shoes` | 8 | `SHOES` |
|
||
|
||
> **注意两个细节**:
|
||
> 1. 客户端"装备索引" `equipIndex` 用 **1-8**(不是 0-7),代码里到处写 `equips[1]`...`equips[8]`
|
||
> 2. accessories 有两槽且接同一种 subType(一个英雄能同时戴两件饰品,但要分别选 6 号槽还是 7 号槽)
|
||
|
||
### 2.2 装备 Item 字段(在 BagItem 之上扩展)
|
||
|
||
装备 = BagItem 但多了几个语义字段:
|
||
|
||
| 字段 | 类型 | 含义 |
|
||
|---|---|---|
|
||
| `itemIndex` | int | 背包唯一索引(与普通 Item 一致) |
|
||
| `itemId` | int | 装备配置 ID(指向 EquipDefine) |
|
||
| `count` | int | 永远是 1(装备不堆叠) |
|
||
| `heroId` | int | 当前穿戴该装备的英雄 ID,0 = 未穿 |
|
||
| `exclusive` | int / bool | 是否带"专属"加成(锻造时 50% 触发) |
|
||
|
||
> 设计推论:**装备就是 `count=1` + `heroId≥0` 的特殊 Item**,不需要独立 Entity 树。
|
||
|
||
---
|
||
|
||
## 3. 配置表清单
|
||
|
||
### 3.1 EquipDefine(装备基础表,主键 `itemID`)
|
||
|
||
| 字段 | 类型 | 含义 |
|
||
|---|---|---|
|
||
| `itemID` | int (PK) | 装备道具 ID(也是 ItemDefine 的 itemId) |
|
||
| `group` | int | 装备组(同一组可能是不同品质或不同套装的同部位) |
|
||
| `useLevel` | int | 英雄达到此等级才能穿戴 |
|
||
| `makeMaterial` | List\<int\> | 锻造所需材料 itemId 列表 |
|
||
| `makeMaterialNum` | List\<int\> | 对应的数量(与 makeMaterial 等长) |
|
||
| `costGold` | int | 锻造消耗金币 |
|
||
| `decomposeMaterial` | List\<int\> | 分解返还材料 itemId 列表 |
|
||
| `decomposeMaterialNum` | List\<int\> | 对应数量 |
|
||
| `att` | List\<int\> | 装备属性 ID 列表(指向 EquipAttDefine.ID) |
|
||
| `attAddEx` | List\<int\> | 服务端属性数值(**千分比**整数) |
|
||
| `attAdd` | List\<int\> | 客户端展示用属性数值 |
|
||
| `compose` | int | 套装 ID(0 = 非套装),指向 EquipComposeDefine.ID |
|
||
| `order` | int | UI 排序 |
|
||
|
||
### 3.2 EquipAttDefine(装备属性映射表)
|
||
|
||
| 字段 | 类型 | 含义 |
|
||
|---|---|---|
|
||
| `ID` | int (PK) | 属性 ID(被 EquipDefine.att 引用) |
|
||
| `attNew` | enum attrType | 客户端使用的属性枚举 |
|
||
| `att` | string | 服务端属性 key 字符串(用于数值表内查名) |
|
||
| `l_nameID` | int | 语言包 ID |
|
||
| `icon` | string | 图标资源名 |
|
||
| `color` | string | 文字颜色 |
|
||
|
||
### 3.3 EquipComposeDefine(套装效果表,主键 `ID` = EquipDefine.compose)
|
||
|
||
四段套装加成(满 2 件 / 4 件 / 6 件 / 8 件分别叠加):
|
||
|
||
| 字段 | 类型 | 含义 |
|
||
|---|---|---|
|
||
| `ID` | int (PK) | 套装 ID |
|
||
| `l_nameID` | int | 套装名语言包 ID |
|
||
| `compose2` | List\<int\> | 满 2 件触发的属性 ID 列表 |
|
||
| `compose2Add` | List\<int\> | 客户端展示数值 |
|
||
| `compose2AddEx` | List\<int\> | 服务端千分比数值 |
|
||
| `compose4` / `compose4Add` / `compose4AddEx` | 同上 | 满 4 件 |
|
||
| `compose6` / `compose6Add` / `compose6AddEx` | 同上 | 满 6 件 |
|
||
| `compose8` / `compose8Add` / `compose8AddEx` | 同上 | 满 8 件(全套)|
|
||
|
||
### 3.4 EquipMaterialDefine(材料表,主键 `itemID`)
|
||
|
||
| 字段 | 类型 | 含义 |
|
||
|---|---|---|
|
||
| `itemID` | int (PK) | 材料道具 ID |
|
||
| `order` | int | UI 排序 |
|
||
| `group` | int | 材料组(同组不同品质用于显示分类) |
|
||
| `rare` | int | 品质 |
|
||
| `add` | int | 各品质对应的数值参数 |
|
||
| `mixCostNum` | int | 合成上一级时所需本级数量(如 3 个低 → 1 个高) |
|
||
| `mix` | int | 合成后的材料 itemId(下一级) |
|
||
| `split` | int | 分解后的材料 itemId(下一级反向?或者材料碎片) |
|
||
| `splitCostCur` | int | 分解时消耗的货币 itemId |
|
||
| `splitCostCurNum` | int | 分解消耗货币数量 |
|
||
| `splitGetNum` | int | 分解一次产出 split 道具的数量 |
|
||
|
||
### 3.5 EquipMaterialGroupDefine(材料组表)
|
||
|
||
| 字段 | 类型 | 含义 |
|
||
|---|---|---|
|
||
| `ID` | int (PK) | 组 ID |
|
||
| `group` | int | 与 EquipMaterialDefine.group 关联 |
|
||
| `l_nameID` | int | 组名语言包 ID |
|
||
| `icon` | string | 组图标 |
|
||
|
||
---
|
||
|
||
## 4. 业务规则详细
|
||
|
||
### 4.1 装备穿戴 `Hero_HeroWearEquip 611`
|
||
|
||
**请求**:`{ heroId: int, itemIndex: int, equipIndex: int }`(equipIndex = 1..8)
|
||
|
||
**服务端流程**(`proxy/Hero.lua:414`):
|
||
|
||
```
|
||
1. 取 ItemLogic.getItem(rid, itemIndex) → itemInfo
|
||
- 不存在 → ERR ITEM_NOT_EXIST
|
||
2. 取 EquipDefine[itemInfo.itemId] → sEquip
|
||
3. 校验 ItemSubType 与 equipIndex 槽位匹配(按 §2.1 映射表)
|
||
- 不匹配 → ERR HERO_EQUIP_SUBTYPE_ERROR (3025)
|
||
4. 校验 hero.level >= sEquip.useLevel
|
||
- 不够 → ERR HERO_EQUIP_LV_NO_ENOUGH (3024)
|
||
5. 校验目标英雄 idle(不在出征中)
|
||
- HeroLogic.checkHeroIdle(rid, heroId) → false → ERR HERO_EQUIP_NOT_IN_CITY (3026)
|
||
6. 处理"装备已被其他英雄穿戴":
|
||
if itemInfo.heroId > 0 then
|
||
beforeHero = HeroLogic.getHero(rid, itemInfo.heroId)
|
||
attr = equips[equipIndex].attr # 该槽位对应的 hero 字段
|
||
if beforeHero[attr] > 0 then
|
||
beforeHero[attr] = 0 # 旧英雄槽位清空
|
||
HeroLogic.setHero + syncHero(推送给客户端)
|
||
end
|
||
end
|
||
7. 处理"目标槽位已有装备":
|
||
attr = equips[equipIndex].attr
|
||
if hero[attr] > 0 then
|
||
oldEquip = ItemLogic.getItem(rid, hero[attr])
|
||
oldEquip.heroId = 0 # 旧装备解绑
|
||
ItemLogic.setItem + syncItem
|
||
end
|
||
8. 建立新关系:
|
||
hero[attr] = itemIndex
|
||
itemInfo.heroId = heroId
|
||
HeroLogic.setHero + syncHero
|
||
(Item 端 syncItem 已在 §6/§7 中被触发)
|
||
```
|
||
|
||
**关键设计点**:
|
||
- 槽位映射在 Lua 里硬编码(不在配置表):`equips[1]={HELMET, head}`...`equips[8]={SHOES, shoes}`
|
||
- ROK 这里**没用客户端先发卸下、再发穿戴**的两步流程,**服务端一个 RPC 完成"卸旧+穿新"**
|
||
- 跨英雄抢装备时,原英雄的同槽位会**自动腾出**,但原英雄是否还有别的装备保持不变
|
||
|
||
### 4.2 装备卸下 `Hero_TakeOffEquip 612`
|
||
|
||
**请求**:`{ heroId: int, equipIndex: int }`
|
||
|
||
**服务端流程**(`proxy/Hero.lua:486`):
|
||
|
||
```
|
||
1. 校验英雄 idle
|
||
- false → ERR HERO_EQUIP_NOT_IN_CITY
|
||
2. attr = equips[equipIndex].attr
|
||
if hero[attr] > 0 then
|
||
itemInfo = ItemLogic.getItem(rid, hero[attr])
|
||
itemInfo.heroId = 0
|
||
ItemLogic.setItem + syncItem
|
||
hero[attr] = 0
|
||
HeroLogic.setHero + syncHero
|
||
end
|
||
3. response { result = true }
|
||
```
|
||
|
||
**关键点**:卸下后装备**回到背包未绑定状态**,不会消失。
|
||
|
||
### 4.3 装备战力计算 `HeroLogic.getHeroEquipAttr`
|
||
|
||
**位置**:`HeroLogic.lua:792`
|
||
|
||
**输入**:`(rid, heroId)`
|
||
|
||
**输出**:`{ attrName1: value1, attrName2: value2, ... }`(属性叠加表)
|
||
|
||
**算法**:
|
||
|
||
```
|
||
equips = [head, breastPlate, weapon, gloves, pants, accessories1, accessories2, shoes]
|
||
equipCompose = {} # 套装计数 { composeId: pieceCount }
|
||
|
||
# 第一步:累加每件装备的基础属性
|
||
for pos in equips:
|
||
if hero[pos] > 0:
|
||
item = ItemLogic.getItem(hero[pos])
|
||
sEquip = EquipDefine[item.itemId]
|
||
if sEquip.compose > 0:
|
||
equipCompose[sEquip.compose] = (equipCompose[sEquip.compose] or 0) + 1
|
||
for i = 1..len(sEquip.att):
|
||
attrName = EquipAttDefine[sEquip.att[i]].att
|
||
num = sEquip.attAddEx[i] # 千分比
|
||
# 如果天赋有"装备属性提升"加成
|
||
if heroHas TalentBonus(equipTalentPromote):
|
||
num = ceil(equipTalentPromote * num * 2) / 2
|
||
result[attrName] += num
|
||
|
||
# 第二步:套装加成(compose2 / compose4 / compose6 / compose8)
|
||
for composeId, count in equipCompose:
|
||
sCompose = EquipComposeDefine[composeId]
|
||
if count >= 2: 累加 sCompose.compose2 / compose2AddEx
|
||
if count >= 4: 累加 sCompose.compose4 / compose4AddEx
|
||
if count >= 6: 累加 sCompose.compose6 / compose6AddEx
|
||
if count >= 8: 累加 sCompose.compose8 / compose8AddEx
|
||
|
||
return result
|
||
```
|
||
|
||
**注意**:所有数值都是**千分比整数**,在客户端展示时除以 10 显示为百分比。
|
||
|
||
### 4.4 锻造预检 `Build_CheckMakeEquip 417`
|
||
|
||
**请求**:`{ itemId: int }`(要锻造的装备 ID)
|
||
|
||
**响应**:`{ exclusive: bool }`
|
||
|
||
**作用**:客户端按下"锻造"按钮时先调这个 RPC,决定本次是否能锻造出"专属"装备。
|
||
|
||
**服务端流程**(`proxy/Build.lua:713`):
|
||
|
||
```
|
||
1. sEquip = EquipDefine[itemId]
|
||
2. 校验金币 >= sEquip.costGold
|
||
- 否 → ERR ROLE_GOLD_NOT_ENOUGH
|
||
3. 校验材料:调用 BuildingLogic.cancleMaterial(rid, itemId) 做存量检查(不真扣)
|
||
- 否 → ERR BUILDING_EQUIP_ITEM_NO_ENOUGH (6030)
|
||
4. 随机:num = Random(1, 100)
|
||
if num > 50:
|
||
exclusive = true
|
||
RoleLogic.setRole(rid, { exclusive = true }) # 把 50% 概率结果存到玩家身上
|
||
5. return { exclusive }
|
||
```
|
||
|
||
**关键点**:**专属概率 50% 写在硬编码里**(`num > 50`),不在配置表。
|
||
|
||
### 4.5 装备锻造 `Build_MakeEquipment 418`
|
||
|
||
**请求**:`{ itemId: int, exclusive: int }`(exclusive 由 CheckMakeEquip 返回,客户端透传回来)
|
||
|
||
**服务端流程**(`proxy/Build.lua:742`):
|
||
|
||
```
|
||
1. 二次校验 exclusive: 客户端说要带专属 → 但 RoleLogic.exclusive 必须为 true(否则作弊)
|
||
- 不一致 → ERR BUILDING_EQUIP_EXCLUSIVE_ERROR (6031)
|
||
2. 二次校验金币 / 材料(同 4.4 第 2-3 步)
|
||
3. flag, itemInfo = BuildingLogic.cancleMaterial(rid, itemId)
|
||
itemInfo 是 dict { itemId: needNum }
|
||
4. 逐项 ItemLogic.checkItemEnough → 任一不足 → ERR
|
||
5. 逐项 ItemLogic.delItemById(LogType=MAKE_EQUIP_COST_ITEM)
|
||
6. RoleLogic.addGold(-price, LogType=MAKE_EQUIP_COST_CURRENCY)
|
||
7. ItemLogic.addItem({ itemId, itemNum=1, exclusive=exclusive, LogType=MAKE_EQUIP_GAIN_ITEM })
|
||
8. 任务统计:TaskLogic.addTaskStatisticsSum(EQUIP_QUALITY, ItemDefine.quality, +1)
|
||
9. response { itemIndex, itemInfo }(实际响应字段以 sproto 为准)
|
||
```
|
||
|
||
**关键设计点**:
|
||
- **没有锻造概率**,材料够 + 金币够 = 必定产出(专属属性是另一回事)
|
||
- 专属 50% 概率在**预检 RPC** 里就决定了,玩家"看脸"决定要不要消耗材料锻造
|
||
- 装备本体是 `count=1` 的 BagItem,带 `exclusive` 字段
|
||
|
||
### 4.6 装备分解 `Build_DecompositionEquipment 419`
|
||
|
||
**请求**:`{ itemIndex: int }`
|
||
|
||
**服务端流程**(`proxy/Build.lua:792`):
|
||
|
||
```
|
||
1. itemInfo = ItemLogic.getItem(rid, itemIndex)
|
||
- 不存在 → ERR BUILDING_EQUIP_NO_EXIST (6029)
|
||
2. 如已穿戴:校验英雄 idle
|
||
- 否 → ERR HERO_EQUIP_NOT_IN_CITY
|
||
3. sEquip = EquipDefine[itemInfo.itemId]
|
||
4. ItemLogic.delItem(rid, itemIndex, 1, LogType=DECOMPOSITION_EQUIP_COST_ITEM)
|
||
5. 按 sEquip.decomposeMaterial[] / decomposeMaterialNum[] 数组返还材料:
|
||
for i = 1..N:
|
||
ItemLogic.addItem({ itemId=sEquip.decomposeMaterial[i], itemNum=sEquip.decomposeMaterialNum[i], LogType=DECOMPOSITION_EQUIP_GAIN_ITEM })
|
||
6. 如已穿戴,同步对应英雄槽位清零(伪代码省略,与穿戴的反向)
|
||
```
|
||
|
||
**关键点**:
|
||
- 已穿戴装备**允许直接分解**(前提英雄在城内),不要求先卸下
|
||
- 返还固定(按配置表),**无随机**
|
||
|
||
### 4.7 材料合成 / 分解 `mix` / `split`
|
||
|
||
**没有专属 RPC**——这两个操作通过 **道具使用 `Use_Item` 协议** 实现:
|
||
|
||
- 客户端 UI(`UI_Win_MaterialView`)让玩家选材料 + 数量
|
||
- 服务端按 `EquipMaterialDefine.mix` / `mixCostNum` 配置查表执行
|
||
- 合成:消耗 N 个低级材料 → 产出 1 个高级材料
|
||
- 分解:消耗 1 个高级材料 + `splitCostCur` 货币 → 产出 `splitGetNum` 个低级
|
||
|
||
**关键设计点**:复用现有 UseItem 链路,无需新协议。
|
||
|
||
---
|
||
|
||
## 5. 客户端 UI 流程
|
||
|
||
### 5.1 装备主面板 `UI_Win_EquipView`(武将装备视图)
|
||
|
||
- 显示英雄的 8 个装备槽(每槽点击 → 选择装备列表)
|
||
- 显示当前套装件数 + 已激活的套装效果
|
||
- 显示装备战力小计
|
||
- "卸下"按钮 / "替换"流程
|
||
|
||
### 5.2 装备快速操作 `UI_Win_EquipQuickView`
|
||
|
||
- 快速一键穿戴该英雄部位的"最强可用装备"
|
||
|
||
### 5.3 锻造面板(在 Build 体系下,不在 Equip View_Mediator)
|
||
|
||
- 选装备 ID(按 group 分类)→ 显示所需材料 + 金币
|
||
- 按下"锻造" → 调 CheckMakeEquip → 若返回 exclusive=true,UI 突出显示
|
||
- 玩家确认 → 调 MakeEquipment
|
||
- 成功 → `UI_IF_EquipSuccessView` 展示飘屏
|
||
|
||
### 5.4 材料合成 `UI_Win_MaterialView`
|
||
|
||
- 选材料组 → 按 rare 排序展示 → 选合成 / 分解 → 走 UseItem
|
||
|
||
### 5.5 天赋装备选择 `UI_IF_EquipTalentChooseView`
|
||
|
||
- 在英雄升级到关键节点时弹出,让玩家选装备方向(特殊功能,可推迟)
|
||
|
||
---
|
||
|
||
## 6. 协议清单
|
||
|
||
| 协议号 | 名称 | 方向 | 用途 |
|
||
|---|---|---|---|
|
||
| 417 | `Build_CheckMakeEquip` | C2G + Resp | 锻造前 50% 概率预检(决定本次能否带专属) |
|
||
| 418 | `Build_MakeEquipment` | C2G + Resp | 真正锻造 |
|
||
| 419 | `Build_DecompositionEquipment` | C2G + Resp | 装备分解 |
|
||
| 611 | `Hero_HeroWearEquip` | C2G + Resp | 英雄穿戴装备(含自动卸旧) |
|
||
| 612 | `Hero_TakeOffEquip` | C2G + Resp | 英雄卸下指定槽位 |
|
||
| (复用) | `Use_Item` | C2G + Resp | 材料合成 / 分解(无专属协议) |
|
||
| (推送) | `Hero_HeroInfo` | G2C | 英雄数据变更(含 8 槽位 itemIndex) |
|
||
| (推送) | `Item_ItemInfo` | G2C | 装备 Item 的 heroId 变更 |
|
||
|
||
---
|
||
|
||
## 7. 错误码清单
|
||
|
||
| 错误码 | 名称 | 含义 |
|
||
|---|---|---|
|
||
| 3024 | `HERO_EQUIP_LV_NO_ENOUGH` | 英雄等级不足 useLevel |
|
||
| 3025 | `HERO_EQUIP_SUBTYPE_ERROR` | 装备类型与槽位不匹配 |
|
||
| 3026 | `HERO_EQUIP_NOT_IN_CITY` | 英雄不在城内(出征中无法换装) |
|
||
| 3027 | `HERO_EQUIP_ALREADY_WEAR` | 该装备已被穿戴(预留,实际流程会自动处理) |
|
||
| 6029 | `BUILDING_EQUIP_NO_EXIST` | 装备 Item 不存在(分解时) |
|
||
| 6030 | `BUILDING_EQUIP_ITEM_NO_ENOUGH` | 锻造材料 / 道具不足 |
|
||
| 6031 | `BUILDING_EQUIP_EXCLUSIVE_ERROR` | 锻造专属校验失败(CheckMakeEquip 与 MakeEquipment 不一致) |
|
||
| (复用) | `ROLE_GOLD_NOT_ENOUGH` | 金币不足 |
|
||
| (复用) | `ITEM_NOT_EXIST` | 道具不存在 |
|
||
|
||
---
|
||
|
||
## 8. 与 Survivors 实现的对照点
|
||
|
||
### 8.1 包归属
|
||
|
||
**建议不建独立 `cn.etetet.equip` 包**:
|
||
|
||
| 理由 | 说明 |
|
||
|---|---|
|
||
| 装备就是 Item 的子集 | `count=1` + `heroId≥0`,没必要建独立 Entity 树 |
|
||
| 穿戴逻辑天然依赖 Hero + Bag 两个包 | 放独立包要双向引用,反而割裂 |
|
||
| ROK 原版的"穿戴"在 HeroLogic,"锻造"在 BuildingLogic | 没有独立 EquipLogic.lua |
|
||
|
||
**实现安排**:
|
||
|
||
```
|
||
cn.etetet.bag/ # 装备数据底座
|
||
├── Scripts/Model/Server/
|
||
│ ├── BagComponent.cs # ItemUnit 上加 heroId/exclusive 字段
|
||
│ └── ItemUnit.cs # heroId>0 = 装备已穿戴
|
||
└── Scripts/Hotfix/Server/
|
||
├── BagComponentSystem.cs # AddItem 时识别装备类型
|
||
└── Forge/ # 锻造子目录
|
||
├── ForgeHelper.cs # CheckMakeEquip + MakeEquipment + Decompose
|
||
└── MaterialMixHelper.cs
|
||
|
||
cn.etetet.hero/ # 穿戴关系挂在 Hero 上
|
||
├── Scripts/Model/Server/
|
||
│ └── HeroEquipSlots.cs # Hero 上加 head/.../shoes 8 字段
|
||
└── Scripts/Hotfix/Server/
|
||
└── HeroWearEquipHandler.cs # 转发到 BagComponentSystem + HeroComponentSystem
|
||
```
|
||
|
||
### 8.2 8 槽位与 Survivors 协议
|
||
|
||
```protobuf
|
||
message HeroEquipSlots {
|
||
int64 head = 1;
|
||
int64 breast_plate = 2;
|
||
int64 weapon = 3;
|
||
int64 gloves = 4;
|
||
int64 pants = 5;
|
||
int64 accessories1 = 6;
|
||
int64 accessories2 = 7;
|
||
int64 shoes = 8;
|
||
}
|
||
|
||
enum EquipSubType {
|
||
EQUIP_HELMET = 0;
|
||
EQUIP_BREASTPLATE = 1;
|
||
EQUIP_ARMS = 2;
|
||
EQUIP_GLOVES = 3;
|
||
EQUIP_PANTS = 4;
|
||
EQUIP_ACCESSORIES = 5; // accessories1 + accessories2 共用
|
||
EQUIP_SHOES = 6;
|
||
}
|
||
|
||
// 协议号建议 3100-3199 段(与本项目 Bag 段相邻)
|
||
message C2G_HeroWearEquip { int64 hero_id = 1; int64 item_index = 2; int32 equip_index = 3; }
|
||
message R2C_HeroWearEquip { int32 error = 1; }
|
||
message C2G_HeroTakeOffEquip { int64 hero_id = 1; int32 equip_index = 1; }
|
||
message C2G_CheckMakeEquip { int32 item_id = 1; }
|
||
message R2C_CheckMakeEquip { int32 error = 1; bool exclusive = 2; }
|
||
message C2G_MakeEquipment { int32 item_id = 1; bool exclusive = 2; }
|
||
message C2G_Decompose { int64 item_index = 1; }
|
||
```
|
||
|
||
### 8.3 Luban 配置(替代 ROK 的 SqlCipher)
|
||
|
||
5 张表照搬:
|
||
- `EquipConfig.xlsx` ↔ EquipDefine(field 列表见 §3.1)
|
||
- `EquipAttConfig.xlsx` ↔ EquipAttDefine
|
||
- `EquipComposeConfig.xlsx` ↔ EquipComposeDefine
|
||
- `EquipMaterialConfig.xlsx` ↔ EquipMaterialDefine
|
||
- `EquipMaterialGroupConfig.xlsx` ↔ EquipMaterialGroupDefine
|
||
|
||
注意 `List<int>` 字段在 Luban 用 `list,int` 类型表达;`attAddEx` 千分比整数保留 ROK 写法(不改成浮点)。
|
||
|
||
### 8.4 数据迁移
|
||
|
||
走 `tools/rok-migrate/export.py` 扩展支持 5 张 Equip 表,从 `Configs.data` 导出 JSON → 转 Luban xlsx。**禁止手输**。
|
||
|
||
---
|
||
|
||
## 9. 实现优先级
|
||
|
||
| 阶段 | 任务 | 备注 |
|
||
|---|---|---|
|
||
| **P2** | Hero 8 槽位字段 + 穿戴 / 卸下 / 战力计算(含套装) | 与英雄养成同期,撑起 Hero 详情面 |
|
||
| **P3** | 锻造(含 exclusive 双段 RPC)+ 分解 | 撑起背包→装备生产闭环 |
|
||
| **P3+** | 材料合成 / 分解(复用 UseItem) | 优先级低,先把装备能玩通再说 |
|
||
| **P4+** | "天赋装备选择" `UI_IF_EquipTalentChoose` | 特殊节点弹窗,非主路径 |
|
||
|
||
---
|
||
|
||
## 10. 与原 openspec 的差异提醒
|
||
|
||
之前 `openspec/changes/port-rok-hero-bag-system/specs/equipment-system/spec.md` 与 `tasks.md` 里的几个表述需要根据本调研修正:
|
||
|
||
| openspec 原写法 | ROK 真实情况 | 应改为 |
|
||
|---|---|---|
|
||
| "EquipItemType 4 个" | 实际 6 个 subType + 8 个槽位 | 改为 "8 槽位 / 6 subType(accessories 2 槽共享)" |
|
||
| "锻造概率(必中 / 随机)" | 锻造必中,**专属** 50% 概率(在预检 RPC 里) | 区分"装备产出"必中 vs "专属属性"50% |
|
||
| "MakeEquipment 单 RPC" | 实际是 **CheckMakeEquip + MakeEquipment 两个 RPC** | tasks 3.4 加 `C2G_CheckMakeEquip` |
|
||
| "EquipDefine 含 List<AttrEntry>" | 实际是 `att[] + attAddEx[]` 两个 List 平行 | bean schema 按平行 List 设计 |
|
||
| "套装效果 = 4 件套" | 实际是 2/4/6/8 件**逐级叠加** | bean schema 拆 4 段 |
|