189 lines
9.4 KiB
Markdown
189 lines
9.4 KiB
Markdown
## ADDED Requirements
|
||
|
||
### Requirement: 通用 Item 数据模型(全游戏统一)
|
||
|
||
系统 MUST 用统一的 `ItemConfig` 表示**所有**游戏内可获得的资源——包括但不限于金币、钻石、英雄碎片、装备、装备材料、装备图纸、经验书、升星石、觉醒符文、礼包、邮件附件、活动奖励。`ItemConfig` MUST 至少包含字段:id(itemId)、name、icon、itemType(BagItemType 大类)、subType(细分类,如 EquipItemType 或货币子类)、maxStack(堆叠上限,0 表示不限)、useType(使用行为,如 HeroExp/HeroStarExp/Gift/None)、quality(品质 1-5)、desc、sortIndex。
|
||
|
||
#### Scenario: 金币也是 Item
|
||
- **WHEN** 玩家获得 100 金币
|
||
- **THEN** 服务端调用 `BagComponentSystem.AddItem(player, itemId=1 /* 金币的固定 itemId */, 100)`
|
||
- **AND** 前端 `BagComponent` 中存在 itemId=1 的 ItemInfo
|
||
- **AND** UI 通过 `BagItemUICommon.Show(itemId=1, count=100)` 渲染图标和数量
|
||
|
||
#### Scenario: 钻石使用同一套接口
|
||
- **WHEN** 玩家抽卡消耗 10 钻石
|
||
- **THEN** 服务端调用 `BagComponentSystem.RemoveItem(player, itemId=2 /* 钻石的固定 itemId */, 10)`
|
||
- **AND** 不需要任何"特殊货币处理逻辑"
|
||
|
||
#### Scenario: 不允许新增非 Item 资源
|
||
- **WHEN** 业务需要引入新资源类型(如新货币)
|
||
- **THEN** MUST 通过新增 ItemConfig 条目实现,不允许在 Player Entity 上添加新字段
|
||
|
||
### Requirement: 通用奖励/消耗结构
|
||
|
||
系统 MUST 提供统一的奖励/消耗描述结构 `RewardEntry { itemId, count }` 和 `CostEntry { itemId, count }`(结构相同,命名上分语义)。所有"列表型奖励/消耗"业务 MUST 使用 `repeated RewardEntry` 或 `repeated CostEntry`,包括但不限于:抽卡奖励列表、升级消耗、升星消耗、技能升级消耗、礼包道具列表、邮件附件、关卡奖励、商店售卖、活动签到奖励。
|
||
|
||
#### Scenario: 升级消耗用 CostEntry
|
||
- **WHEN** `HeroSkillLevelConfig` 配置技能升级消耗
|
||
- **THEN** 字段类型 MUST 是 `repeated CostEntry`(例:`[{ itemId: 2001, count: 5 }, { itemId: 1, count: 100 }]`)
|
||
- **AND** 服务端通过 `BagComponentSystem.RemoveItems(player, costs)` 批量扣除
|
||
- **AND** 任一项不足则全部失败,不扣任何道具
|
||
|
||
#### Scenario: 抽卡奖励用 RewardEntry
|
||
- **WHEN** 抽卡响应包含奖励列表
|
||
- **THEN** 字段类型 MUST 是 `repeated RewardEntry`
|
||
- **AND** 服务端通过 `BagComponentSystem.AddItems(player, rewards)` 批量发放
|
||
|
||
### Requirement: 通用道具 UI 组件
|
||
|
||
客户端 MUST 提供 `BagItemUICommon` YIUI UICommon,输入 `(itemId, count, optional: showName, showCount, sizePreset)`,自动渲染图标、品质边框、数量、新道具标记。该组件 MUST 被所有道具相关 UI 复用:背包列表、抽卡结果、奖励飘字、邮件附件、商店货品、关卡结算、签到奖励。
|
||
|
||
#### Scenario: 抽卡结果显示
|
||
- **WHEN** GachaResultPanel 渲染 10 个奖励
|
||
- **THEN** 为每个奖励实例化一个 BagItemUICommon
|
||
- **AND** 调用 `Show(reward.itemId, reward.count)`
|
||
|
||
#### Scenario: 邮件附件显示
|
||
- **WHEN** 邮件面板渲染附件列表
|
||
- **THEN** 复用相同的 BagItemUICommon
|
||
- **AND** 不需要重写道具图标渲染逻辑
|
||
|
||
### Requirement: Bag 数据模型
|
||
|
||
系统 MUST 为每个玩家提供一个 `BagComponent` 挂在玩家根 Entity 下。`BagComponent` MUST 维护一个 `ItemInfo` 列表,每个 `ItemInfo` 持有:itemIndex(全局唯一索引)、itemId(对应 `ItemConfig.id`)、count(数量/堆叠)、itemType(大类)、heroId(仅装备类有效,归属英雄)。
|
||
|
||
#### Scenario: 注入背包数据
|
||
- **WHEN** 玩家登录后服务端推送 `Item_ItemInfoList { items }`
|
||
- **THEN** 客户端 `BagComponent` 清空旧 items 列表
|
||
- **AND** 为每个 ItemInfo 创建客户端缓存
|
||
- **AND** 按 `ItemType` 分类放入对应子缓存(resourceItems/speedupItems/...)
|
||
|
||
#### Scenario: 增量更新单个道具
|
||
- **WHEN** 服务端推送 `Item_ItemInfo { item }`
|
||
- **THEN** 客户端按 itemIndex 找到对应缓存
|
||
- **AND** 若 count > 0 更新;若 count == 0 移除
|
||
- **AND** 触发 `OnItemChanged(itemId)` 事件
|
||
|
||
### Requirement: 道具大类与堆叠规则
|
||
|
||
系统 MUST 提供 6 大类道具类型(`BagItemType`):Resource(1) / Speedup(2) / Boost(3) / Equipment(4) / Other(5) / Icon(6)。
|
||
- 非 Equipment 类道具 MUST 按 itemId 堆叠(同 id 累加 count)
|
||
- Equipment 类道具 MUST 每个独立 itemIndex(不堆叠),即使同 itemId
|
||
|
||
#### Scenario: Resource 类堆叠
|
||
- **WHEN** 玩家已有 itemId=1001 数量 50
|
||
- **AND** 服务端发放 100 个 itemId=1001
|
||
- **THEN** 服务端 MUST 更新该道具 count 为 150(不创建新条目)
|
||
|
||
#### Scenario: Equipment 类不堆叠
|
||
- **WHEN** 玩家拥有 1 件 itemId=8001 装备
|
||
- **AND** 服务端发放另 1 件相同 itemId 装备
|
||
- **THEN** 服务端 MUST 创建新 itemIndex 条目,count=1
|
||
- **AND** 两个装备实例独立存在
|
||
|
||
### Requirement: 道具数量查询
|
||
|
||
客户端 MUST 提供以下查询接口:
|
||
- `BagComponentExtensions.GetItemNum(itemId)` 返回某 itemId 的总数(非装备类)
|
||
- `BagComponentExtensions.GetItemIndex(itemId)` 返回单条目的 itemIndex(非装备类)
|
||
- `BagComponentExtensions.GetItemInfoListByType(BagItemType)` 返回某类下所有 ItemInfo
|
||
- `BagComponentExtensions.HasItem(itemId, requiredCount)` 判断是否足够
|
||
|
||
#### Scenario: 查询道具数量
|
||
- **WHEN** 玩家拥有 itemId=2001 数量 30
|
||
- **AND** 调用 `GetItemNum(2001)`
|
||
- **THEN** 返回 30
|
||
|
||
#### Scenario: 查询不存在的道具
|
||
- **WHEN** 玩家未拥有 itemId=9999
|
||
- **AND** 调用 `GetItemNum(9999)`
|
||
- **THEN** 返回 0(不抛异常)
|
||
|
||
### Requirement: 道具使用
|
||
|
||
玩家 MUST 能消耗指定数量道具触发其效果。可使用道具由 `ItemConfig.useType` 标识。服务端 MUST 是真权威,校验所有使用操作。
|
||
|
||
#### Scenario: 使用经验书
|
||
- **WHEN** 玩家发送 `C2G_UseItem { itemId, count }`
|
||
- **AND** `ItemConfig.useType == HeroExp`
|
||
- **AND** 玩家拥有 >= count
|
||
- **THEN** 服务端转发到对应处理器(如 HeroAddExp)
|
||
|
||
#### Scenario: 使用礼包道具
|
||
- **WHEN** 玩家使用 useType=Gift 类道具
|
||
- **THEN** 服务端按 `ItemConfig.giftRewards` 发放奖励
|
||
- **AND** 扣除礼包道具
|
||
- **AND** 推送多个 `Item_ItemInfo` 同步所有变化
|
||
|
||
#### Scenario: 使用数量不足
|
||
- **WHEN** 玩家请求使用 5 个道具,但只有 3 个
|
||
- **THEN** 服务端 MUST 拒绝并返回 `ItemNotEnough`
|
||
|
||
### Requirement: 服务端道具增减接口
|
||
|
||
服务端 MUST 提供以下基础接口给业务调用(如 Hero/Equipment 模块),所有变更 MUST 推送 `Item_ItemInfo`:
|
||
- `BagComponentSystem.AddItem(itemId, count)` 增加道具
|
||
- `BagComponentSystem.RemoveItem(itemId, count)` 减少道具,返回是否成功
|
||
- `BagComponentSystem.RemoveItemByIndex(itemIndex)` 按索引删除(用于唯一装备)
|
||
- `BagComponentSystem.SetHeroIdOnEquip(itemIndex, heroId)` 装备绑定/解绑英雄
|
||
|
||
#### Scenario: 增加道具触发同步
|
||
- **WHEN** 服务端业务调用 `AddItem(itemId=1001, 50)`
|
||
- **THEN** 修改 BagComponent
|
||
- **AND** 标记脏数据待落库
|
||
- **AND** 推送 `Item_ItemInfo { itemId=1001, count=新count }`
|
||
|
||
#### Scenario: 扣除道具失败
|
||
- **WHEN** 业务调用 `RemoveItem(itemId, count)` 但数量不足
|
||
- **THEN** 返回 false
|
||
- **AND** 不修改任何数据
|
||
- **AND** 不推送消息
|
||
|
||
### Requirement: 新道具标记
|
||
|
||
客户端 MUST 维护"新到道具"红点标记,用 ISave 本地存储(避免与服务端通信开销)。当 BagComponent 接收到新 itemId 或 count 增加时,MUST 自动标记 `isNew`。当玩家在 UI 中浏览过该道具,MUST 标记为已读。
|
||
|
||
#### Scenario: 获得新道具标记
|
||
- **WHEN** 服务端推送 `Item_ItemInfo` 增加 itemId=5001(之前为 0)
|
||
- **THEN** 客户端 MUST 在本地 ISave 中标记 `isNew[5001] = true`
|
||
|
||
#### Scenario: 浏览后清除标记
|
||
- **WHEN** 玩家在 BagPanel 中点击查看 itemId=5001
|
||
- **THEN** 客户端 MUST 调用 `SetLocalItemToOld(5001)`
|
||
- **AND** 红点消失
|
||
|
||
### Requirement: 背包红点
|
||
|
||
系统 MUST 提供按类型聚合的红点接口:
|
||
- `GetBagReddotNumByType(BagItemType)` 返回某类新道具数量
|
||
- `GetBagReddotTotal()` 返回所有类型新道具总数
|
||
- `ClearAllReddotRecord()` 清空所有红点
|
||
|
||
#### Scenario: 分类红点
|
||
- **WHEN** 玩家有 3 个新 Resource 道具 + 2 个新 Equipment 道具
|
||
- **AND** 客户端调用 `GetBagReddotNumByType(BagItemType.Resource)`
|
||
- **THEN** 返回 3
|
||
|
||
### Requirement: UI 列表分页与循环加载
|
||
|
||
背包 UI MUST 使用 `cn.etetet.yiuiloopscrollrectasync` 实现循环列表,支持万级道具不卡顿。MUST 按类型分页(Resource/Speedup/Boost/Equipment/Other 各为一页),每页内按 `ItemConfig.sortIndex` 排序。
|
||
|
||
#### Scenario: 切换到装备页
|
||
- **WHEN** 玩家点击 BagPanel 上"装备"分页按钮
|
||
- **THEN** UI 加载 `BagComponent.equipItems` 数据源
|
||
- **AND** 通过 `LoopScrollRect` 异步刷新可见格子
|
||
|
||
### Requirement: 道具详情 Tooltip
|
||
|
||
点击道具 MUST 弹出 `BagItemTooltip` 浮层,显示:图标、名称、品质边框、描述、堆叠数量、使用按钮(若可用)、来源跳转入口。
|
||
|
||
#### Scenario: 显示 Tooltip
|
||
- **WHEN** 玩家点击背包格子
|
||
- **THEN** 在格子位置/右侧弹出 BagItemTooltip
|
||
- **AND** 显示对应 ItemConfig 的全部信息
|
||
|
||
#### Scenario: 装备 Tooltip 显示属性
|
||
- **WHEN** 点击的是 Equipment 类道具
|
||
- **THEN** Tooltip 显示装备属性条目(攻击/防御/血量等)
|
||
- **AND** 显示穿戴/卸下按钮
|