## 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** 显示穿戴/卸下按钮