XtGameKit/Doc/ROK-Equip-Spec.md

510 lines
21 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 | 当前穿戴该装备的英雄 ID0 = 未穿 |
| `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 | 套装 ID0 = 非套装),指向 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=trueUI 突出显示
- 玩家确认 → 调 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` ↔ EquipDefinefield 列表见 §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 subTypeaccessories 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 段 |