import { Attrs } from "./HeroAttrs"; import { HRole } from "./heroSet"; /** * 天赋系统配置(局外成长:升级即激活,战斗中对英雄属性常驻加成)。 * * 双层结构: * - 基础天赋:全局生效,对所有英雄叠加; * - 专精天赋:按 HRole 职业过滤,仅对匹配职业的英雄生效,且要求前置天赋达到指定等级。 * * 结算口径(与项目 buff 规范一致): * - percent 模式:按英雄基础值(base_ap/base_hp/hero.def)相对放大; * - flat 模式:概率/抗性类按百分点累加,defense 按固定点数累加。 */ /** 天赋数值模式:percent=按基础值百分比放大;flat=固定值/百分点累加 */ export enum TalentValueMode { Percent = 0, Flat = 1, } /** 单条属性加成词条 */ export interface TalentAttrBonus { /** 目标属性(Attrs 枚举键) */ attr: Attrs; /** 数值模式 */ mode: TalentValueMode; /** 每级数值(percent 模式为百分比;flat 模式为百分点/固定值) */ per_lv: number; } /** 天赋配置 */ export interface TalentConfig { /** 天赋 uuid(段位见 TalentSet 注释) */ uuid: number; /** 天赋名称 */ name: string; /** 描述(UI 展示) */ info: string; /** 属性加成词条列表(每级叠加) */ bonuses: TalentAttrBonus[]; /** 最大等级 */ max_lv: number; /** 专精天赋:仅对该职业英雄生效;基础天赋省略此字段 */ role?: HRole; /** 专精天赋:前置要求(前置天赋 uuid + 所需等级),全部满足才生效 */ requires?: { uuid: number; lv: number }[]; } /** 单条已解析的加成(等级已乘算) */ export interface TalentResolvedBonus { attr: Attrs; mode: TalentValueMode; /** 总值 = per_lv × 天赋等级 */ value: number; } /** * 天赋加成作用目标的最小结构接口(TS 结构化类型,HeroAttrsComp 天然满足)。 * Why: 用结构化接口替代直接 import HeroAttrsComp,避免 config → hero → SingletonModuleComp → config 的循环依赖。 */ export interface ITalentTarget { ap: number; hp: number; hp_max: number; defense: number; /** 基础攻击快照(percent 模式的基数) */ base_ap: number; /** 基础血量快照(percent 模式的基数) */ base_hp: number; /** 防御基础值(percent 模式的基数,等于 hero.def) */ [key: string]: any; } /** * 天赋配置表。 * uuid 段位:9001-9099 基础天赋(全局);9101-9199 专精天赋(按职业过滤)。 */ export const TalentSet: Record = { // ==================== 基础天赋(9001-9099,全局生效) ==================== 9001: { uuid: 9001, name: "攻击强化", info: "所有英雄攻击 +{value}%", bonuses: [{ attr: Attrs.ap, mode: TalentValueMode.Percent, per_lv: 3 }], max_lv: 5, }, 9002: { uuid: 9002, name: "生命强化", info: "所有英雄生命 +{value}%", bonuses: [{ attr: Attrs.hp_max, mode: TalentValueMode.Percent, per_lv: 5 }], max_lv: 5, }, 9003: { uuid: 9003, name: "防御强化", info: "所有英雄防御 +{value} 点", bonuses: [{ attr: Attrs.defense, mode: TalentValueMode.Flat, per_lv: 8 }], max_lv: 5, }, 9004: { uuid: 9004, name: "暴击强化", info: "所有英雄暴击率 +{value}%", bonuses: [{ attr: Attrs.critical, mode: TalentValueMode.Flat, per_lv: 2 }], max_lv: 5, }, 9005: { uuid: 9005, name: "击晕强化", info: "所有英雄击晕率 +{value}%", bonuses: [{ attr: Attrs.stun_chance, mode: TalentValueMode.Flat, per_lv: 2 }], max_lv: 5, }, 9006: { uuid: 9006, name: "冰冻强化", info: "所有英雄冰冻率 +{value}%", bonuses: [{ attr: Attrs.freeze_chance, mode: TalentValueMode.Flat, per_lv: 2 }], max_lv: 5, }, 9007: { uuid: 9007, name: "击退强化", info: "所有英雄击退距离 +{value}", bonuses: [{ attr: Attrs.knockback_distance, mode: TalentValueMode.Flat, per_lv: 10 }], max_lv: 5, }, 9008: { uuid: 9008, name: "风怒强化", info: "所有英雄风怒率 +{value}%", bonuses: [{ attr: Attrs.wfuny, mode: TalentValueMode.Flat, per_lv: 2 }], max_lv: 5, }, // ==================== 专精天赋(9101-9199,按职业过滤 + 前置要求) ==================== // 坦克专精:前置 防御强化 Lv3 → 坦克大量防御 + 生命 9101: { uuid: 9101, name: "坦克专精", info: "坦克防御 +{value}%、生命 +{value2}%", bonuses: [ { attr: Attrs.defense, mode: TalentValueMode.Percent, per_lv: 30 }, { attr: Attrs.hp_max, mode: TalentValueMode.Percent, per_lv: 5 }, ], max_lv: 5, role: HRole.Tank, requires: [{ uuid: 9003, lv: 3 }], }, // 战士专精:前置 生命强化 Lv3 → 战士大量生命 9102: { uuid: 9102, name: "战士专精", info: "战士生命 +{value}%", bonuses: [{ attr: Attrs.hp_max, mode: TalentValueMode.Percent, per_lv: 12 }], max_lv: 5, role: HRole.Warrior, requires: [{ uuid: 9002, lv: 3 }], }, // 刺客专精:前置 暴击强化 Lv3 → 刺客攻击 + 暴击 9103: { uuid: 9103, name: "刺客专精", info: "刺客攻击 +{value}%、暴击率 +{value2}%", bonuses: [ { attr: Attrs.ap, mode: TalentValueMode.Percent, per_lv: 8 }, { attr: Attrs.critical, mode: TalentValueMode.Flat, per_lv: 2 }, ], max_lv: 5, role: HRole.Assassin, requires: [{ uuid: 9004, lv: 3 }], }, // 射手专精:前置 攻击强化 Lv3 → 射手攻击 + 穿透 9104: { uuid: 9104, name: "射手专精", info: "射手攻击 +{value}%、穿透率 +{value2}%", bonuses: [ { attr: Attrs.ap, mode: TalentValueMode.Percent, per_lv: 8 }, { attr: Attrs.puncture_chance, mode: TalentValueMode.Flat, per_lv: 2 }, ], max_lv: 5, role: HRole.Archer, requires: [{ uuid: 9001, lv: 3 }], }, // 法师专精:前置 冰冻强化 Lv3 → 法师攻击 + 冰冻率 9105: { uuid: 9105, name: "法师专精", info: "法师攻击 +{value}%、冰冻率 +{value2}%", bonuses: [ { attr: Attrs.ap, mode: TalentValueMode.Percent, per_lv: 8 }, { attr: Attrs.freeze_chance, mode: TalentValueMode.Flat, per_lv: 2 }, ], max_lv: 5, role: HRole.Mage, requires: [{ uuid: 9006, lv: 3 }], }, // 辅助专精:前置 生命强化 Lv3 → 治疗量提升 + 施加的友方 buff 效果放大 9106: { uuid: 9106, name: "辅助专精", info: "辅助治疗量 +{value}%、施加的增益效果 +{value2}%", bonuses: [ { attr: Attrs.heal_power, mode: TalentValueMode.Flat, per_lv: 10 }, { attr: Attrs.buff_power, mode: TalentValueMode.Flat, per_lv: 8 }, ], max_lv: 5, role: HRole.Support, requires: [{ uuid: 9002, lv: 3 }], }, }; /** 天赋升级消耗:索引为当前等级(0 表示升到 1 级),返回所需天赋点数 */ export const TALENT_UPGRADE_COST: number[] = [1, 1, 2, 2, 3]; /** * 查询升级所需天赋点数 * @param curLv 当前等级 * @returns 升到下一级所需点数;已满级返回 -1 */ export function getTalentUpgradeCost(curLv: number): number { if (curLv < 0 || curLv >= TALENT_UPGRADE_COST.length) return -1; return TALENT_UPGRADE_COST[curLv]; } /** * 聚合指定职业英雄生效的全部天赋加成。 * Why: 基础天赋全局生效,专精天赋按 role 过滤且要求前置天赋达标; * 战斗加载时(Hero.load / applyHeroLevel)调用本函数一次性取出本英雄的天赋加成区。 * @param levels 天赋等级表(key 为天赋 uuid,value 为当前等级) * @param role 英雄职业(专精过滤依据;无 role 时只应用基础天赋) * @returns 已解析的加成列表(等级已乘算进 value) */ export function getTalentBonusesForRole(levels: Record, role?: HRole): TalentResolvedBonus[] { const result: TalentResolvedBonus[] = []; if (!levels) return result; for (const key in levels) { const lv = levels[key]; if (lv <= 0) continue; const cfg = TalentSet[key]; if (!cfg) continue; // 专精天赋:职业不匹配则跳过 if (cfg.role !== undefined && cfg.role !== role) continue; // 前置要求:任一前置天赋等级不足则不生效 if (cfg.requires) { let ok = true; for (const req of cfg.requires) { if ((levels[req.uuid] ?? 0) < req.lv) { ok = false; break; } } if (!ok) continue; } for (const b of cfg.bonuses) { result.push({ attr: b.attr, mode: b.mode, value: b.per_lv * lv }); } } return result; } /** * 将天赋加成写入英雄属性模型(在 Hero.load / MissionCardComp.applyHeroLevel 的属性组装段调用)。 * * 结算规则: * - Percent 模式:以基础快照为基数(ap→base_ap,hp_max→base_hp,defense→def_base),按百分比放大后累加; * - Flat 模式:直接按属性名动态累加(概率/抗性类为百分点,defense 为固定点数)。 * * Why: percent 必须用基础快照而非当前 ap/hp_max 作基数——当前值已含驻场光环与成长增量, * 若以当前值为基数会与光环乘区叠加成乘法,导致数值膨胀失控。 * * @param model 英雄属性模型(满足 ITalentTarget 结构) * @param levels 天赋等级表(smc.getTalentLevels()) * @param role 英雄职业(专精过滤;无 role 的怪物由调用方跳过) */ export function applyTalentBonuses(model: ITalentTarget, levels: Record, role?: HRole): void { const bonuses = getTalentBonusesForRole(levels, role); if (bonuses.length === 0) return; // percent 模式的 defense 基数:取调用方传入的当前 defense(Hero.load/applyHeroLevel 已在调用前重置为 hero.def) const defBase = model.defense; for (const b of bonuses) { if (b.mode === TalentValueMode.Percent) { switch (b.attr) { case Attrs.ap: model.ap += Math.floor(model.base_ap * b.value / 100); break; case Attrs.hp_max: model.hp_max += Math.floor(model.base_hp * b.value / 100); model.hp += Math.floor(model.base_hp * b.value / 100); break; case Attrs.defense: model.defense += Math.floor(defBase * b.value / 100); break; default: model[b.attr] = (typeof model[b.attr] === "number" ? model[b.attr] : 0) + b.value; break; } } else { // flat:概率/抗性类百分点累加,defense 固定点数累加 model[b.attr] = (typeof model[b.attr] === "number" ? model[b.attr] : 0) + b.value; } } }