diff --git a/assets/script/game/common/SingletonModuleComp.ts b/assets/script/game/common/SingletonModuleComp.ts index 06cc5af0..c374fe5b 100644 --- a/assets/script/game/common/SingletonModuleComp.ts +++ b/assets/script/game/common/SingletonModuleComp.ts @@ -9,6 +9,7 @@ import { GameScoreStats } from "./config/HeroAttrs"; import { mLogger } from "./Logger"; import { gameDataSync } from "./GameDataSync"; import { HERO_UPGRADE_NEED_FRAGMENTS } from "./config/GameSet"; +import { TalentSet, getTalentUpgradeCost } from "./config/TalentSet"; /** * 用远程数据覆盖本地数据(统一方法) @@ -35,6 +36,10 @@ export interface GameDate { timestamp?: number, // 用于比对本地与云端数据的最新状态 /** 英雄局外成长数据:key 为英雄 uuid,value 为成长记录 */ hero_growth?: Record, + /** 天赋等级表:key 为天赋 uuid,value 为当前等级(未记录视为 0 级) */ + talents?: Record, + /** 当前可用天赋点数(升级天赋消耗,经验/等级系统接入后由其产出) */ + talent_points?: number, } export interface CloudData { openid: string; @@ -121,6 +126,10 @@ export class SingletonModuleComp extends ecs.Comp { // 已满级:验证满级后 upgradeHero 返回 false 5004: { uuid: 5004, lv: 6, fragments: 999 }, } as Record, + /** 天赋等级表:key 为天赋 uuid,value 为当前等级 */ + talents: {} as Record, + /** 可用天赋点数 */ + talent_points: 0, } vmdata: any = { @@ -376,6 +385,87 @@ export class SingletonModuleComp extends ecs.Comp { return true; } + // ==================== 天赋系统 ==================== + + /** + * 获取天赋当前等级(未升级返回 0) + * @param uuid 天赋 uuid(TalentSet key) + */ + getTalentLv(uuid: number): number { + return this.data.talents?.[uuid] ?? 0; + } + + /** + * 获取当前可用天赋点数 + */ + getTalentPoints(): number { + return this.data.talent_points ?? 0; + } + + /** + * 增加天赋点(经验/等级系统接入后由其调用) + * @param count 增加的点数(正数) + */ + addTalentPoints(count: number): void { + if (!this.data.talents) this.data.talents = {}; + this.data.talent_points = Math.max(0, (this.data.talent_points ?? 0) + count); + gameDataSync.markDataDirty(); + oops.message.dispatchEvent(GameEvent.TALENT_UPDATE); + } + + /** + * 消耗天赋点升级天赋(升级即激活,下一局生效) + * @param uuid 天赋 uuid + * @returns 是否升级成功(天赋不存在/已满级/前置未满足/点数不足时返回 false) + */ + upgradeTalent(uuid: number): boolean { + const cfg = TalentSet[uuid]; + if (!cfg) return false; + if (!this.data.talents) this.data.talents = {}; + const curLv = this.getTalentLv(uuid); + if (curLv >= cfg.max_lv) return false; + // 专精天赋的前置要求:前置天赋等级不足则禁止升级 + if (cfg.requires) { + for (const req of cfg.requires) { + if (this.getTalentLv(req.uuid) < req.lv) return false; + } + } + const cost = getTalentUpgradeCost(curLv); + if (cost < 0 || this.getTalentPoints() < cost) return false; + this.data.talent_points -= cost; + this.data.talents[uuid] = curLv + 1; + gameDataSync.markDataDirty(); + oops.message.dispatchEvent(GameEvent.TALENT_UPDATE); + return true; + } + + /** + * 重置所有天赋等级并退还全部已消耗天赋点(看广告入口) + * Why: 退还点数按"升到当前等级的累计消耗"计算,与升级消耗表严格对账,避免点数膨胀/缩水。 + */ + resetTalents(): void { + if (!this.data.talents) this.data.talents = {}; + let refund = 0; + for (const key in this.data.talents) { + const lv = this.data.talents[key]; + for (let i = 0; i < lv; i++) { + const c = getTalentUpgradeCost(i); + if (c > 0) refund += c; + } + } + this.data.talents = {}; + this.data.talent_points = (this.data.talent_points ?? 0) + refund; + gameDataSync.markDataDirty(); + oops.message.dispatchEvent(GameEvent.TALENT_UPDATE); + } + + /** + * 获取天赋等级表(战斗加载时供 Hero.load / applyHeroLevel 聚合加成) + */ + getTalentLevels(): Record { + return this.data.talents ?? {}; + } + /** * 在游戏载入早期调用,预加载常用图集 */ diff --git a/assets/script/game/common/config/GameEvent.ts b/assets/script/game/common/config/GameEvent.ts index f820d93e..5108ac69 100644 --- a/assets/script/game/common/config/GameEvent.ts +++ b/assets/script/game/common/config/GameEvent.ts @@ -98,5 +98,7 @@ export enum GameEvent { ComboReach = "ComboReach", /** 英雄局外成长数据变更(碎片/等级更新,payload 为空,UI 重新读取 smc.data.hero_growth) */ HERO_GROWTH_UPDATE = "HERO_GROWTH_UPDATE", + /** 天赋数据变更(等级/点数更新,payload 为空,UI 重新读取 smc.data.talents / talent_points) */ + TALENT_UPDATE = "TALENT_UPDATE", } diff --git a/assets/script/game/common/config/HeroAttrs.ts b/assets/script/game/common/config/HeroAttrs.ts index 38688675..e5b0bd66 100644 --- a/assets/script/game/common/config/HeroAttrs.ts +++ b/assets/script/game/common/config/HeroAttrs.ts @@ -43,6 +43,8 @@ export enum Attrs { wfuny = "wfuny", // 风怒 vulnerable_res = "vulnerable_res", // 易伤抗性(百分比减免易伤附加伤害) paralyze_power = "paralyze_power", // 麻痹强化(百分点;提升麻痹对技能 CD 的减速量,累计达 100 则 CD 停止增长) + heal_power = "heal_power", // 治疗强化(百分点;放大该英雄施放技能的治疗量,辅助专精天赋写入) + buff_power = "buff_power", // buff 强化(百分点;放大该英雄施加的友方 buff 数值,辅助专精天赋写入) } /** diff --git a/assets/script/game/common/config/TalentSet.ts b/assets/script/game/common/config/TalentSet.ts new file mode 100644 index 00000000..b0608b15 --- /dev/null +++ b/assets/script/game/common/config/TalentSet.ts @@ -0,0 +1,269 @@ +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; + } + } +} diff --git a/assets/script/game/common/config/TalentSet.ts.meta b/assets/script/game/common/config/TalentSet.ts.meta new file mode 100644 index 00000000..fa6756b7 --- /dev/null +++ b/assets/script/game/common/config/TalentSet.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.24", + "importer": "typescript", + "imported": true, + "uuid": "0f2652aa-4d80-43ee-a5d4-35c3334520cc", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/assets/script/game/hero/Hero.ts b/assets/script/game/hero/Hero.ts index 491f4738..39b42ab6 100644 --- a/assets/script/game/hero/Hero.ts +++ b/assets/script/game/hero/Hero.ts @@ -13,6 +13,7 @@ import { Attrs } from "../common/config/HeroAttrs"; import { MoveComp } from "./MoveComp"; import { mLogger } from "../common/Logger"; import { FieldSkillType } from "../common/config/SkillSet"; +import { applyTalentBonuses } from "../common/config/TalentSet"; /** 英雄实体:负责英雄节点创建、属性初始化、入场动画与销毁流程 */ @ecs.register(`Hero`) @@ -194,6 +195,12 @@ export class Hero extends ecs.Entity { model.ap += apExtra; } + // 天赋加成层:基础天赋全局生效,专精天赋按职业过滤(仅英雄生效,怪物跳过) + // Why: 放在成长/bonus 之后、光环乘区之后,以基础快照为基数叠加,避免与乘区叠加成乘法膨胀 + if (model.fac === FacSet.HERO) { + applyTalentBonuses(model, smc.getTalentLevels(), model.role); + } + // 缓存技能射程等派生数据,减少战斗帧内重复计算 model.updateSkillDistanceCache(); diff --git a/assets/script/game/hero/HeroAttrsComp.ts b/assets/script/game/hero/HeroAttrsComp.ts index 34b89e77..700be54e 100644 --- a/assets/script/game/hero/HeroAttrsComp.ts +++ b/assets/script/game/hero/HeroAttrsComp.ts @@ -89,7 +89,9 @@ export class HeroAttrsComp extends ecs.Comp { puncture_chance: number = 0; // 穿透概率 puncture_dmg_bonus: number = 0; // 穿透后伤害增量(百分点,乘算加成) wfuny: number = 0; // 风怒 - vulnerable_res: number = 0; // 易伤抗性(百分比减免易伤附加伤害) + vulnerable_res: number = 0; // 易伤抗性(百分比减免易伤附加伤害) + heal_power: number = 0; // 治疗强化(百分点;放大该英雄施放技能的治疗量,辅助专精天赋写入) + buff_power: number = 0; // buff 强化(百分点;放大该英雄施加的友方 buff 数值,辅助专精天赋写入) revived_count: number = 0; // 已复活次数 invincible_time: number = 0;// 无敌时间 @@ -660,6 +662,8 @@ export class HeroAttrsComp extends ecs.Comp { this.puncture_dmg_bonus = 0; this.wfuny = 0; this.vulnerable_res = 0; + this.heal_power = 0; + this.buff_power = 0; this.boom = false; // 重置技能距离缓存 diff --git a/assets/script/game/hero/SCastSystem.ts b/assets/script/game/hero/SCastSystem.ts index 5c91e964..d7e06416 100644 --- a/assets/script/game/hero/SCastSystem.ts +++ b/assets/script/game/hero/SCastSystem.ts @@ -16,6 +16,7 @@ import { SkillTriggerHelper } from "./SkillTriggerHelper"; import { MissionEconomy } from "../map/MissionEconomy"; import { mLogger } from "../common/Logger"; import { buffManager } from "./BuffManager"; +import { BuffList } from "../common/config/BuffSet"; /** * ==================== 自动施法系统 ==================== @@ -527,7 +528,8 @@ export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate } if (kind === SkillKind.Heal && sAp !== 0) { - const addHp = Math.floor(sAp * _cAttrsComp.ap / 100); + // 治疗强化(heal_power,百分点):辅助专精天赋写入,放大施法者的治疗量 + const addHp = Math.floor(sAp * _cAttrsComp.ap / 100 * (1 + _cAttrsComp.heal_power / 100)); model.add_hp(addHp); target.health(addHp); if (_cAttrsComp.fac === FacSet.HERO) { @@ -547,6 +549,17 @@ export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate const buffOverrides: { value?: number; duration?: number } = {}; if (config.buff_value !== undefined) buffOverrides.value = config.buff_value; if (config.buff_duration !== undefined) buffOverrides.duration = config.buff_duration; + // buff 强化(buff_power,百分点):辅助专精天赋写入,按施法者放大 buff 数值。 + // 基准值优先取技能 buff_value,未配置时反查 BuffList(modifiers[0].value 优先,其次 tick.damage_or_heal) + if (_cAttrsComp.buff_power > 0) { + const buffCfg = BuffList[config.timed_buff_id]; + const baseValue = config.buff_value + ?? buffCfg?.modifiers?.[0]?.value + ?? buffCfg?.tick?.damage_or_heal; + if (baseValue !== undefined) { + buffOverrides.value = Math.floor(baseValue * (1 + _cAttrsComp.buff_power / 100)); + } + } buffManager.applyBuff( target.ent, config.timed_buff_id, @@ -558,7 +571,8 @@ export class SCastSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate } // 永久 buff 统一按百分比结算:攻击/生命按基础值相对放大,其余按百分点累加 - const totalBuffValue = config.ap; + // buff 强化(buff_power,百分点):辅助专精天赋写入,按施法者放大永久 buff 数值 + const totalBuffValue = config.ap * (1 + _cAttrsComp.buff_power / 100); switch (config.buff_type) { case Attrs.ap: { diff --git a/assets/script/game/map/MissionCardComp.ts b/assets/script/game/map/MissionCardComp.ts index 28168256..5d34c80c 100644 --- a/assets/script/game/map/MissionCardComp.ts +++ b/assets/script/game/map/MissionCardComp.ts @@ -58,6 +58,7 @@ import { oops } from "db://oops-framework/core/Oops"; import { HeroAttrsComp } from "../hero/HeroAttrsComp"; import { smc } from "../common/SingletonModuleComp"; import { HeroInfo, HType, resolveTriggerByLv, resolveFieldByLv, resolveReviveByLv, resolveSkillOverrides, calcGrowBonus } from "../common/config/heroSet"; +import { applyTalentBonuses } from "../common/config/TalentSet"; import { HeroViewComp } from "../hero/HeroViewComp"; import { FacSet, FightSet } from "../common/config/GameSet"; import { MissionEconomy } from "./MissionEconomy"; @@ -2183,6 +2184,21 @@ export class MissionCardComp extends CCComp { model.ap += apExtra; } + // 天赋加成层(与 Hero.load 对齐):先重置天赋可能修改的字段,避免升级重算时重复叠加 + // Why: ap/hp_max 已由上方公式从 base 重算(天然幂等),但 defense 与概率类字段是累加语义,必须先归零再注入 + if (model.fac === FacSet.HERO) { + model.defense = hero.def; + model.critical = 0; + model.stun_chance = 0; + model.freeze_chance = 0; + model.wfuny = 0; + model.puncture_chance = 0; + model.knockback_distance = 0; + model.heal_power = 0; + model.buff_power = 0; + applyTalentBonuses(model, smc.getTalentLevels(), model.role); + } + // 血量按当前血线比例映射到新上限,保留升级前战斗状态 model.hp = Math.max(1, Math.floor(model.hp_max * Math.max(0, Math.min(1, hpRate))));