From 3d2bba0cac05d3713f2e01c9285d725680499259 Mon Sep 17 00:00:00 2001 From: panFD Date: Fri, 21 Aug 2026 23:41:21 +0800 Subject: [PATCH] =?UTF-8?q?docs(spec):=20=E6=9B=B4=E6=96=B0=E8=8B=B1?= =?UTF-8?q?=E9=9B=84=E4=B8=93=E5=B1=9E=E6=AD=A6=E5=99=A8=E7=B3=BB=E7=BB=9F?= =?UTF-8?q?=E8=AE=BE=E8=AE=A1=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 将原英雄个人装备系统设计文档重命名为英雄专属武器系统设计文档,更新了背景目标、数据结构、锻造重铸规则、局内集成、存档结构、UI交互等内容,将装备相关逻辑替换为专属武器逻辑,并完成现有光环卡改名适配。 --- .../specs/2026-08-21-hero-equip-design.md | 249 +++++++++--------- 1 file changed, 119 insertions(+), 130 deletions(-) diff --git a/docs/superpowers/specs/2026-08-21-hero-equip-design.md b/docs/superpowers/specs/2026-08-21-hero-equip-design.md index ad3b59fd..0a34b430 100644 --- a/docs/superpowers/specs/2026-08-21-hero-equip-design.md +++ b/docs/superpowers/specs/2026-08-21-hero-equip-design.md @@ -1,33 +1,39 @@ -# 英雄个人装备系统设计 +# 英雄专属武器系统设计 - 日期:2026-08-21 - 状态:已评审(brainstorming 阶段用户逐节确认) -- 涉及文件:`HeroEquipSet.ts`(新增)、`heroSet.ts`、`HeroAttrs.ts`、`HeroAttrsComp.ts`、`SingletonModuleComp.ts`、`GameEvent.ts`、`EquipSet.ts`(改名 TalentSet) +- 涉及文件:`HeroWeaponSet.ts`(新增)、`heroSet.ts`、`HeroAttrs.ts`、`HeroAttrsComp.ts`、`SingletonModuleComp.ts`、`GameEvent.ts`、`GameSet.ts`、`EquipSet.ts`(改名 TalentSet) ## 1. 背景与目标 现有"装备"(`EquipSet.ts`,8701~8820 段)是**全体驻场光环卡**:购买后全队加属性,经 `EquipBoxComp → FieldSkillHelper` 全局聚合。 -本设计新增**英雄个人装备**:每个英雄可装备**一件**装备,词条只作用于该英雄本身;与现有光环卡**并存**,光环卡在表述上改名为**战队天赋**。 +本设计新增**英雄专属武器**:每个英雄天生绑定一件专属武器,不可卸下、不可更换;词条只作用于该英雄本身;与现有光环卡**并存**,光环卡在表述上改名为**战队天赋**。 核心特性: -- 装备在**玩家获得时**(开箱子 / 抽卡)随机生成词条并固定(暗黑式:从词条池随机抽 N 条、数值在区间内随机)。 -- **词条数量与数值高低决定装备品质**。 -- 装备是**局外资产**,随云存档持久化;局内只有战斗,不产生、不修改装备,开局快照生效。 -## 2. 数据结构(HeroEquipSet.ts,新增) +- 武器通过**锻造**成长:每次锻造 = 等级 +1,并从词条池加权随机获得 1 条词条(暗黑式:数值在区间内随机)。 +- 抽中**已有词条则数值累加**,新词条则新增一条。 +- 等级上限 **16 级**;**特定里程碑等级必得该英雄专属词条**(数值随机,合并到同 attr)。 +- 0 级白板武器自带 1 条**初始专属词条**。 +- **品质按等级阶梯跃升**,与词条数脱钩。 +- **重铸**:看广告将武器归零(0 级 0 随机词条),已投入材料全部返还,初始专属词条重新 roll。 +- 武器是**局外资产**,随云存档持久化;局内只有战斗,不产生、不修改武器,开局快照生效。 -位置:`assets/script/game/common/config/HeroEquipSet.ts`,与 `EquipSet.ts`(将改名 `TalentSet.ts`)平级。 +## 2. 数据结构(HeroWeaponSet.ts,新增) + +位置:`assets/script/game/common/config/HeroWeaponSet.ts`,与 `EquipSet.ts`(将改名 `TalentSet.ts`)平级。 ```typescript import { Attrs } from "./HeroAttrs"; -/** 装备品质(由词条数推导:1=普通 2=稀有 3=史诗 4=传说) */ -export enum EquipQuality { - Normal = 1, - Rare = 2, - Epic = 3, - Legend = 4, +/** 武器品质(按等级阶梯推导,不存储) */ +export enum WeaponQuality { + Fine = 1, // 优秀(绿)0~3 级 + Superior = 2, // 精良(蓝)4~7 级 + Epic = 3, // 史诗(紫)8~11 级 + Legend = 4, // 传说(橙)12~15 级 + Unique = 5, // 唯一(红)16 级满级 } /** 词条池条目:attr 指向 Attrs 枚举,roll 时 value 在 [min, max] 内随机 */ @@ -38,125 +44,129 @@ export interface AffixPoolEntry { weight: number; // 抽取权重 } -/** 装备底座:只定义随机规则,不含固定词条(段位 9001-9099) */ -export interface HeroEquipBase { - base_id: number; - name: string; - icon: string; - affix_count: [number, number]; // 词条数区间,如 [1,2] - affix_pool: AffixPoolEntry[]; +/** 专属词条里程碑:到达 level 必得,value 在 [min, max] 内随机后合并到同 attr */ +export interface ExclusiveAffix { + level: number; // 0=初始自带;1~16=锻造到达解锁 + attr: Attrs; + min: number; + max: number; } -/** 装备实例(roll 生成后固定;不冗余存 name/icon,查询走底座表) */ -export interface HeroEquipInstance { - inst_id: number; // 实例唯一 id(equip_next_id 自增) - base_id: number; - affixes: { attr: Attrs; value: number }[]; // quality = affixes.length,不单独存储 -} - -/** 装备箱子配置(段位 8901-8999,2 档) */ -export interface EquipBoxConfig { - uuid: number; +/** 武器底座:key=英雄模板 uuid(段位 9001-9099),只定义规则不含实例 */ +export interface HeroWeaponBase { + hero_uuid: number; name: string; icon: string; - pool: { base_id: number; weight: number }[]; // 底座加权池 + affix_pool: AffixPoolEntry[]; // 随机词条池(attr 白名单见第 4.4 节) + exclusive: ExclusiveAffix[]; // 专属词条里程碑,按 level 升序,必含 1 条 level=0 + forge_cost: { item_id: number; count: number }[]; // 单次锻造材料(各级同价) } + +/** 武器状态:挂在英雄存档上,局外持久化;同 attr 词条已合并 */ +export interface HeroWeaponState { + level: number; // = 已锻造次数,0=未锻造白板,上限 16 + affixes: { attr: Attrs; value: number }[]; // 随机词条+专属词条,同 attr 已累加合并 + invested: { item_id: number; count: number }[]; // 累计已投入材料(重铸全量返还用) +} +``` + +### 品质阶梯 + +| 等级 | 品质 | 颜色 | +|------|------|------| +| 0~3 | 优秀(Fine) | 绿 | +| 4~7 | 精良(Superior) | 蓝 | +| 8~11 | 史诗(Epic) | 紫 | +| 12~15 | 传说(Legend) | 橙 | +| 16 | 唯一(Unique) | 红 | + +```typescript +/** 按武器等级推导品质(不存储,实时推导) */ +export function getWeaponQuality(level: number): WeaponQuality ``` ### 词条口径(与现有百分比口径一致) - **数值型**(仅 `ap` / `hp_max` / `defense`):Flat 数值,roll 取**整数**。 - **概率型**(其余全部):百分点,roll 取 **1 位小数**。 -- 词条池仅允许出现第 4.4 节白名单中的 attr,配置时校验。 +- 词条池与专属词条仅允许出现第 4.4 节白名单中的 attr,配置加载时校验。 -### 品质规则 +## 3. 锻造与重铸 -- `quality = 词条数`(1~4 对应普通~传说),初版从简;数值高低不参与品质,后续可扩展档位评分。 - -## 3. Roll 生成与获取途径 - -### 3.1 核心纯函数(HeroEquipSet.ts) +### 3.1 核心纯函数(HeroWeaponSet.ts) ```typescript /** - * 获得装备时调用:按底座规则 roll 出一件词条固定的装备实例 - * @param base_id 装备底座 id - * @param instId 实例 id(由 SingletonModuleComp 分配) + * 锻造一次:等级+1 → 从 affix_pool 加权抽 1 条词条合并(已有 attr 数值累加,新 attr 新增) + * → 若新等级命中 exclusive 里程碑,roll 专属词条数值并合并到同 attr + * @param base 武器底座 + * @param state 当前武器状态(level < 16) + * @returns 新状态;base 非法或已满级返回 null 并 mLogger.warn */ -export function rollHeroEquip(base_id: number, instId: number): HeroEquipInstance | null -``` +export function forgeWeapon(base: HeroWeaponBase, state: HeroWeaponState): HeroWeaponState | null -Roll 流程: -1. `affix_count: [min, max]` 内均匀随机词条数 N。 -2. 从 `affix_pool` 按 weight 加权**不放回**抽 N 条不同 attr(同装备不重复属性)。 -3. 每条词条在 `[min, max]` 内随机(数值型取整,概率型 1 位小数)。 -4. 品质 = N。 - -```typescript /** - * 按底座池加权抽取并 roll 出 count 件装备(箱子/抽卡统一入口) - * @param pool 底座加权池 - * @param count 抽取件数 - * @param allocInstId 实例 id 分配器(每次调用返回新 id) - * @param guaranteeMinQuality N 连保底品质(0=不保底) + * 初始化武器:0 级白板 + roll level=0 的初始专属词条(获得英雄/懒初始化时调用) */ -export function rollEquipPool( - pool: { base_id: number; weight: number }[], - count: number, - allocInstId: () => number, - guaranteeMinQuality: EquipQuality = 0, -): HeroEquipInstance[] +export function initWeapon(base: HeroWeaponBase): HeroWeaponState + +/** + * 重铸:等级与随机词条清零,invested 全量返还,初始专属词条(level=0)重新 roll + * @returns 新状态 + 应返还材料列表(= 原 invested 全量) + */ +export function reforgeWeapon(base: HeroWeaponBase, state: HeroWeaponState): { + state: HeroWeaponState; + refund: { item_id: number; count: number }[]; +} ``` -保底逻辑:当 `count >= GACHA_COUNT` 且全部 < `guaranteeMinQuality` 时,最后一件重 roll 至 ≥ 保底品质。 +锻造流程: + +1. `level+1`。 +2. 从 `affix_pool` 按 weight 加权抽 1 条 attr;在 `[min, max]` 内随机(数值型取整,概率型 1 位小数)。 +3. 该 attr 已存在于 `affixes` → 数值累加;否则新增一条。 +4. 若 `exclusive` 中存在 `level == 新等级` 的条目 → 每条在 `[min, max]` 内随机并累加到同 attr(不存在则新增)。 +5. `invested` 累加本次 `forge_cost`。 ### 3.2 可调参数(GameSet.ts) ```typescript -GACHA_COUNT = 10, // N 连连抽数(后期可调 20/30) -GACHA_MAX_DRAW = 10, // 单次最多一起抽的张数 -GACHA_GUARANTEE_QUALITY = 2, // N 连保底品质(稀有) +MAX_WEAPON_LEVEL = 16, // 武器锻造等级上限 ``` -### 3.3 双获取通道 +### 3.3 材料与货币 -| 途径 | 流程 | 产出 | -|------|------|------| -| **装备箱子**(2 档:普通箱/高级箱) | 商店购买/奖励 → `equip_boxes` 库存 → 背包界面点击开箱 | 固定 1 件,走 `rollEquipPool(box.pool, 1)` | -| **抽卡**(消耗装备抽卡券) | 抽卡界面选 1 抽 / N 连(默认 10,≤ GACHA_MAX_DRAW)→ 消耗 `equip_tickets` | N 件,走 `rollEquipPool(gachaPool, N, 保底)` | - -- 高级箱与普通箱的差异仅为 `pool` 中底座权重(高词条数底座权重更高)。 -- 单局结算奖励可发放:箱子(入 `equip_boxes`)和/或装备抽卡券(入 `equip_tickets`)。 +- 锻造材料为通用道具(如"精铁"),走现有道具库存;`forge_cost` 各级同价(递增为后续扩展,见 YAGNI)。 +- 重铸不消耗材料,改为**看激励视频广告**:广告播放完成回调成功后才执行 `reforgeWeapon`;`invested` 中材料全量返还道具库存。 ## 4. 局内集成(HeroAttrsComp) -**原则:局内快照,只读生效。** 开战创建英雄实体时快照装备词条,局内不参与装备增删改。 +**原则:局内快照,只读生效。** 开战创建英雄实体时快照武器词条,局内不参与武器增删改。 ### 4.1 快照字段 ```typescript // HeroAttrsComp 新增 -/** 装备词条加成缓存(局外穿戴装备在开局时快照;数值型=flat,概率型=百分点) */ +/** 武器词条加成缓存(局外武器在开局时快照;数值型=flat,概率型=百分点) */ equip_mods: Partial> = {}; ``` -- 不存 `HeroEquipInstance`,只存"每条属性加了多少"的聚合结果(同 attr 多词条快照时已累加)。 +- 不存 `HeroWeaponState`,只存"每条属性加了多少"的聚合结果(`affixes` 已同 attr 合并,直接遍历即可)。 - `reset()` 中清空。 -- 局内 UI 若要显示装备名,从 `hero_equips` 反查,不影响战斗数据。 +- 局内 UI 若要显示武器名,从 `hero_weapons` 反查,不影响战斗数据。 ### 4.2 快照时机 英雄实体初始化(读 `HeroInfo` 填充属性的同一入口处)追加: ```typescript -const instId = smc.data.hero_equips[hero_uuid]; -const inst = instId ? smc.data.equips[instId] : null; -attrsComp.equip_mods = aggregateAffixes(inst); // HeroEquipSet.ts 纯函数,UI 属性预览可复用 +const weapon = smc.getHeroWeapon(hero_uuid); +attrsComp.equip_mods = aggregateWeaponAffixes(weapon); // HeroWeaponSet.ts 纯函数,UI 属性预览可复用 ``` ### 4.3 结算接入(最小侵入,每个 getFinal 加一项) -**数值型**:装备 flat **先进基础值,再吃驻场光环百分比**(乘算放大,装备更值钱): +**数值型**:武器 flat **先进基础值,再吃驻场光环百分比**(乘算放大,武器更值钱): ```typescript public getFinalAp(): number { @@ -167,9 +177,9 @@ public getFinalAp(): number { } ``` -顺序:装备 flat → 驻场光环 % → 计时 buff。同理 `getFinalHpMax`、`getFinalDefense`。 +顺序:武器 flat → 驻场光环 % → 计时 buff。同理 `getFinalHpMax`、`getFinalDefense`。 -**概率型**:百分点直接累加(`base + equip + timed`): +**概率型**:百分点直接累加(`base + weapon + timed`): ```typescript public getFinalCritical(): number { @@ -197,66 +207,44 @@ public getFinalCritical(): number { ```typescript data: any = { // ...现有字段 - /** 玩家装备库:key=inst_id;局外资产随云存档持久化 */ - equips: {} as Record, - /** 实例 id 自增计数器(云端同步,防 id 冲突) */ - equip_next_id: 1, - /** 英雄穿戴表:key=英雄模板 uuid,value=inst_id(0=未穿戴) */ - hero_equips: {} as Record, - /** 装备箱子库存:key=箱子 uuid,value=数量 */ - equip_boxes: {} as Record, - /** 装备抽卡券数量 */ - equip_tickets: 0, + /** 英雄武器状态:key=英雄模板 uuid;局外资产随云存档持久化,懒初始化 */ + hero_weapons: {} as Record, } ``` `GameDate` 接口同步补充上述可选字段。 -**数据量评估**:每件装备实例 JSON 约 60~80 字节(只存 `inst_id/base_id/affixes`,不冗余存 name/icon);500 件 ≈ 40KB,远低于微信云存储 1MB 单记录上限。`quality` 不存储(= `affixes.length`)。 +**数据量评估**:每件武器 JSON 约 40~60 字节(只存 `level/affixes/invested`);50 英雄 ≈ 3KB,远低于微信云存储 1MB 单记录上限。品质不存储(= `getWeaponQuality(level)` 实时推导)。 ### 数据操作方法(封装在 SingletonModuleComp,UI 不直改数据) ```typescript -addEquipInstances(insts: HeroEquipInstance[]): void // 入库 + markDataDirty + Equip_Update -wearEquip(heroUuid: number, instId: number): boolean // 穿戴/替换,旧装自动回库 -unwearEquip(heroUuid: number): void -consumeEquipBox(boxUuid: number): HeroEquipInstance | null // 开箱:扣库存→roll→入库→返回 -drawEquips(count: number): HeroEquipInstance[] | null // 抽卡:扣券→roll(保底)→入库 -addEquipTickets(n: number): void -addEquipBox(boxUuid: number, n: number): void +getHeroWeapon(heroUuid: number): HeroWeaponState // 懒初始化:无记录时按底座 initWeapon 生成 0 级白板 +forgeHeroWeapon(heroUuid: number): boolean // 校验满级/材料 → 扣材料 → forgeWeapon → 累加 invested → markDataDirty + HeroWeapon_Update +reforgeHeroWeapon(heroUuid: number): boolean // 广告成功回调内调用 → 返还 invested 全量 → reforgeWeapon → markDataDirty + HeroWeapon_Update ``` -每个方法内部 `gameDataSync.markDataDirty()` + `oops.message.dispatchEvent` 对应事件。 +每个方法内部 `gameDataSync.markDataDirty()` + `oops.message.dispatchEvent(GameEvent.HeroWeapon_Update)`。 ### 新增事件(GameEvent.ts) ```typescript -static Equip_Update = "Equip_Update"; // 装备库/穿戴表变更(背包/英雄界面刷新) -static EquipBox_Update = "EquipBox_Update"; // 箱子数量变更 -static EquipTicket_Update = "EquipTicket_Update"; // 抽卡券数量变更 +static HeroWeapon_Update = "HeroWeapon_Update"; // 武器锻造/重铸变更(英雄界面刷新) ``` 监听方必须在 `onDestroy`/`onDisable` 注销(oops 规范)。 ## 6. UI 与交互 -### 6.1 英雄界面(唯一穿戴入口) +### 6.1 英雄界面 · 武器页签(唯一操作入口) -- 英雄详情面板新增**装备槽**(属性区附近):空槽显示 `+`,已穿戴显示装备图标+品质边框。 -- **穿戴/换装**:点击装备槽 → 弹出装备选择层,仅列**未被任何英雄穿戴**的装备(按品质排序)→ 点击即穿戴/替换(旧装回库)。 -- **卸下**:已穿戴时选择层提供【卸下】按钮。 -- 已被其他英雄穿戴的装备在选择层中**不显示**。 +- 英雄详情面板新增**武器页签**:武器图标 + 品质边框(按等级变色)+ 等级(x/16)+ 当前词条列表(attr 名 + 数值)。 +- 【锻造】按钮:显示本次材料消耗;材料不足或满级(16 级)置灰;点击 → 扣材料锻造 → 弹出结果浮层,高亮本次新增/累加的词条变化与品质跃升提示。 +- 【重铸】按钮:弹确认框(说明"观看广告重置武器,全部锻造材料返还")→ 调激励视频广告 → 播放完成回调成功后执行重铸并展示返还材料。 -### 6.2 装备背包界面(资产总览 + 开箱) +### 6.2 不做独立武器背包 -- 入口:主界面"装备"按钮(复用 `tip_done.equip` 引导标记)。 -- 布局:装备网格列表(按品质:传说→史诗→稀有→普通;已穿戴的标记穿戴英雄头像角标)+ 点击看词条详情;顶部**箱子区**(普通箱/高级箱图标+数量),点击开箱 → 展示战利品 → 入库。 - -### 6.3 抽卡界面 - -- 入口:商店界面新增"装备抽卡"页签。 -- 【抽 1 次】= 1 张券;【抽 N 次】= N 张券(N 显示当前 `GACHA_COUNT`);券不足按钮置灰。 -- 结果浮层:网格展示 N 件装备(图标+品质色),点击看词条;保底装备高亮"保底"标记。 +武器与英雄强绑定,无装备网格列表、无穿戴选择层;武器信息只在英雄界面展示。 ## 7. 现有光环卡改名"战队天赋" @@ -266,16 +254,17 @@ static EquipTicket_Update = "EquipTicket_Update"; // 抽卡券数量变更 ## 8. 错误处理与边界 -- `rollHeroEquip` 对未知 `base_id` 返回 `null` 并 `mLogger.warn`;`rollEquipPool` 空池返回空数组。 -- 穿戴时 `instId` 不存在(云端数据异常)→ `wearEquip` 返回 false,不崩溃。 -- 英雄被穿戴的装备在库中不存在 → 快照时按无装备处理(`equip_mods = {}`)。 -- 词条池 attr 不在白名单 → 配置加载时 `mLogger.warn` 并跳过该词条。 -- 旧存档无 `equips` 等新字段 → `Object.assign` 合并后由访问方兜底初始化(参照 `getHeroGrowth` 的懒初始化模式)。 +- `forgeWeapon` 对未知 `hero_uuid` 底座或已满级(16 级)返回 `null` 并 `mLogger.warn`;`forgeHeroWeapon` 材料不足返回 false,不扣材料。 +- 存档中 `hero_weapons[hero_uuid]` 存在但底座表无对应配置(云端数据异常)→ 快照时按 0 级白板处理(`equip_mods = {}`)。 +- 词条池 / 专属词条 attr 不在白名单 → 配置加载时 `mLogger.warn` 并跳过该词条。 +- 旧存档无 `hero_weapons` 字段 → `Object.assign` 合并后由 `getHeroWeapon` 懒初始化兜底(参照 `getHeroGrowth` 的懒初始化模式)。 +- 重铸广告播放失败/中途关闭 → 不执行重铸,数据不变。 +- `invested` 与 `level × forge_cost` 不一致(异常数据)→ 重铸以 `invested` 记录为准。 ## 9. 不做的事(YAGNI) -- 跨局账号级装备强化/分解/合成系统。 -- 装备部位(武器/盔甲等多槽位)——当前每英雄仅 1 槽。 -- 数值档位评分影响品质(初版品质=词条数)。 -- 局内掉落/换装。 -- 词条重 roll(洗练)。 +- 武器卸下/更换/交易;多武器槽位。 +- 词条锁定(洗单词条保留其他词条)。 +- 锻造材料随等级递增(初版各级同价)。 +- 词条重 roll(洗练)——重铸全洗已覆盖该需求。 +- 局内掉落/换武器。