refactor(hero): 重构英雄数值系统,支持品质系数与12级职业属性加成

1. 调整英雄最大等级为99,实现近似无限升级
2. 新增防御属性加成系统,补充def_bonus相关配置与计算
3. 引入英雄品质系数机制,相邻品质基础属性相差5%
4. 新增12级职业属性专项加成:坦克加防、战士辅助加血、输出加攻
5. 更新英雄设计文档与配置示例,统一数值计算逻辑
6. 重构成长计算函数,支持防御属性成长与加成
This commit is contained in:
panFD
2026-09-01 20:47:36 +08:00
parent d943bc05ce
commit 1dbb3bd90d
6 changed files with 185 additions and 68 deletions

View File

@@ -40,8 +40,8 @@ export enum FightSet {
HEARTPOS = -320,//基地位置 HEARTPOS = -320,//基地位置
/** 场上英雄编制数:由开局 3 次三选一固定,玩家无法扩充 */ /** 场上英雄编制数:由开局 3 次三选一固定,玩家无法扩充 */
HERO_FIELD_NUM = 3, HERO_FIELD_NUM = 3,
/** 英雄可升级到的最高等级(通过升级卡提升) */ /** 英雄可升级到的最高等级(通过升级卡提升),99 近似无限升级 */
HERO_MAX_LV = 6, HERO_MAX_LV = 99,
/** 英雄技能等级上限(无限升级下技能 5 级封顶,超出等级只长属性) */ /** 英雄技能等级上限(无限升级下技能 5 级封顶,超出等级只长属性) */
HERO_SKILL_MAX_LV = 5, HERO_SKILL_MAX_LV = 5,
/** 英雄属性随等级成长的倍率(ap/hp = base * MULTIPLIER^(lv-1)) */ /** 英雄属性随等级成长的倍率(ap/hp = base * MULTIPLIER^(lv-1)) */

View File

@@ -1,4 +1,4 @@
import { v3 } from "cc" import { v3 } from "cc"
import { BoxSet, FacSet, FightSet } from "./GameSet" import { BoxSet, FacSet, FightSet } from "./GameSet"
import { SkillOverrides, TGroup, SkillSet, FieldSkillSet, SkillConfig, SkillKind, FieldSkillConfig, FieldSkillType, mergeSkillParams } from "./SkillSet" import { SkillOverrides, TGroup, SkillSet, FieldSkillSet, SkillConfig, SkillKind, FieldSkillConfig, FieldSkillType, mergeSkillParams } from "./SkillSet"
import { BuffList, BuffConfig, ModOp } from "./BuffSet" import { BuffList, BuffConfig, ModOp } from "./BuffSet"
@@ -107,15 +107,37 @@ export enum HGrowType {
} }
/** 成长类型系数表(每级增量 = 英雄基础属性 × 对应系数) */ /** 成长类型系数表(每级增量 = 英雄基础属性 × 对应系数) */
export const HERO_GROW_COEF: Record<HGrowType, { hp: number; ap: number }> = { export const HERO_GROW_COEF: Record<HGrowType, { hp: number; ap: number; def: number }> = {
[HGrowType.Tank]: { hp: 1.5, ap: 0.6 }, [HGrowType.Tank]: { hp: 1.5, ap: 0.6, def: 1.5 },
[HGrowType.Warrior]: { hp: 1.0, ap: 1.0 }, [HGrowType.Warrior]: { hp: 1.0, ap: 1.0, def: 1.0 },
[HGrowType.Assassin]: { hp: 0.7, ap: 1.4 }, [HGrowType.Assassin]: { hp: 0.7, ap: 1.4, def: 0.5 },
[HGrowType.Mage]: { hp: 0.9, ap: 1.3 }, [HGrowType.Mage]: { hp: 0.9, ap: 1.3, def: 0.5 },
[HGrowType.Support]: { hp: 1.1, ap: 0.7 }, [HGrowType.Support]: { hp: 1.1, ap: 0.7, def: 1.0 },
[HGrowType.Archer]: { hp: 0.8, ap: 1.3 }, [HGrowType.Archer]: { hp: 0.8, ap: 1.3, def: 0.5 },
}; };
/**
* 英雄品质系数步长:相邻品质(card_lv)基础属性相差 5%。
* 系数 = 1 + HERO_QUALITY_STEP × (card_lv - 1),lv1~lv5 → 1.00/1.05/1.10/1.15/1.20。
* 仅作用于 lv1 初始基础属性(AP/HP/DEF),成长与等级 bonus 不随品质缩放。
*/
export const HERO_QUALITY_STEP = 0.05;
/** 计算英雄品质系数(相邻品质基础属性差 5%) */
export function getHeroQualityCoef(cardLv: number): number {
return 1 + HERO_QUALITY_STEP * (Math.max(1, cardLv) - 1);
}
/**
* lv12 职业属性专项:英雄等级达到 12 级时,按职业对单项基础属性 +50%。
* 基数为 lv11 当前等级有效值(基础 + 成长 + lv9 bonus,不含 lv12 自身)。
* Why: 坦克堆防、战士/辅助堆血、输出位堆攻,强化职业特色。
*/
export const HERO_LV12_SPECIAL_LV = 12;
/** lv12 专项提升的档位等级(基数取 LV12 - 1 = 11 级有效值) */
export const HERO_LV12_BASE_LV = HERO_LV12_SPECIAL_LV - 1;
/** /**
* 英雄位置类型(职业定位)。 * 英雄位置类型(职业定位)。
* Why: grow_type 仅驱动数值成长曲线,无法表达"这个英雄在阵容里干什么"; * Why: grow_type 仅驱动数值成长曲线,无法表达"这个英雄在阵容里干什么";
@@ -139,6 +161,16 @@ export const HRoleName: Record<HRole, string> = {
[HRole.Support]: "辅助", [HRole.Support]: "辅助",
}; };
/** lv12 各职业专项属性映射(坦克堆防、战士/辅助堆血、输出位堆攻) */
export const HERO_LV12_SPECIAL_ATTR: Record<HRole, "def" | "hp" | "ap"> = {
[HRole.Tank]: "def",
[HRole.Warrior]: "hp",
[HRole.Assassin]: "ap",
[HRole.Archer]: "ap",
[HRole.Mage]: "ap",
[HRole.Support]: "hp",
};
/** /**
* 职业基础属性模板(仅英雄使用,怪物无 role 直接手写 hp/ap/def)。 * 职业基础属性模板(仅英雄使用,怪物无 role 直接手写 hp/ap/def)。
* 英雄配置处的 hp/ap/def = 本模板值 + 该卡手动增量,模板保证同职业基准一致、增量表达个体差异。 * 英雄配置处的 hp/ap/def = 本模板值 + 该卡手动增量,模板保证同职业基准一致、增量表达个体差异。
@@ -158,16 +190,17 @@ export const HERO_ROLE_BASE: Record<HRole, { hp: number; ap: number; def: number
* 公式:累计 = 基础属性 × 类型系数 × (lv - 1)。 * 公式:累计 = 基础属性 × 类型系数 × (lv - 1)。
* Why: 以英雄自身基础值为基数,自动适配低/高费卡的成长占比,避免全局固定值失衡; * Why: 以英雄自身基础值为基数,自动适配低/高费卡的成长占比,避免全局固定值失衡;
* 线性曲线在无限升级下不会指数爆炸,强度平滑可控。 * 线性曲线在无限升级下不会指数爆炸,强度平滑可控。
* @param hero 英雄配置(取 hp/ap 基础值与 grow_type) * @param hero 英雄配置(取 hp/ap/def 基础值与 grow_type)
* @param targetLv 目标等级(≥1) * @param targetLv 目标等级(≥1)
* @returns 累计 hp/ap 增量(已取整) * @returns 累计 hp/ap/def 增量(已取整)
*/ */
export function calcGrowBonus(hero: Pick<heroInfo, "hp" | "ap" | "grow_type">, targetLv: number): { hp: number; ap: number } { export function calcGrowBonus(hero: Pick<heroInfo, "hp" | "ap" | "def" | "grow_type">, targetLv: number): { hp: number; ap: number; def: number } {
const coef = HERO_GROW_COEF[hero.grow_type ?? HGrowType.Warrior]; const coef = HERO_GROW_COEF[hero.grow_type ?? HGrowType.Warrior];
const steps = Math.max(0, Math.floor(targetLv) - 1); const steps = Math.max(0, Math.floor(targetLv) - 1);
return { return {
hp: Math.floor(hero.hp * coef.hp * steps), hp: Math.floor(hero.hp * coef.hp * steps),
ap: Math.floor(hero.ap * coef.ap * steps), ap: Math.floor(hero.ap * coef.ap * steps),
def: Math.floor(hero.def * coef.def * steps),
}; };
} }
@@ -295,6 +328,7 @@ export interface heroInfo {
skills: Record<number, HSkillInfo>; // 携带技能ID列表 skills: Record<number, HSkillInfo>; // 携带技能ID列表
ap_bonus?: LvBonusEntry[]; // 额外攻击加成(按 lv 升序,运行时累加 ≤英雄等级 的所有档) ap_bonus?: LvBonusEntry[]; // 额外攻击加成(按 lv 升序,运行时累加 ≤英雄等级 的所有档)
hp_bonus?: LvBonusEntry[]; // 额外生命加成(按 lv 升序,运行时累加 ≤英雄等级 的所有档) hp_bonus?: LvBonusEntry[]; // 额外生命加成(按 lv 升序,运行时累加 ≤英雄等级 的所有档)
def_bonus?: LvBonusEntry[]; // 额外防御加成(按 lv 升序,运行时累加 ≤英雄等级 的所有档)
info: string; // 描述文案 info: string; // 描述文案
/** /**
* @deprecated 已废弃:无任何 UI/运行时读取。技能提升说明由 HeroSkillText 依据 * @deprecated 已废弃:无任何 UI/运行时读取。技能提升说明由 HeroSkillText 依据
@@ -422,8 +456,10 @@ export interface LvBonusEntry {
*=============英雄配置列表================ *=============英雄配置列表================
* 几何王国复刻版 — 依据 英雄设计指引.md(A/B 级复刻英雄)配置 * 几何王国复刻版 — 依据 英雄设计指引.md(A/B 级复刻英雄)配置
* *
* 规则:lv3 解锁 atking/atked 触发技能,lv2 解锁 fstart,lv6/12 大招走 lv_entries 分档成长, * 规则:lv1 激活 skills[0]/skills[1],lv6 解锁 atking/atked/fstart 触发技能,
* lv9 card_lv 4/5 配 field 光环,card_lv 3 配 ap_bonus/hp_bonus 基础属性+50% * 大招(skills[1])lv9/lv16 走 lv_entries 分档成长,lv12 按职业对单项基础属性 +50%(基数取 lv11 有效值),
* lv9 card_lv 4/5 配 field 光环,card_lv 3 配 ap_bonus/hp_bonus 基础属性+50%,
* 品质(card_lv)相邻基础属性差 5%(仅 lv1 初始 AP/HP/DEF)
* 编号:本批复刻英雄集中于 5001~5009,card_lv 按配置(不遵循段位规则) * 编号:本批复刻英雄集中于 5001~5009,card_lv 按配置(不遵循段位规则)
*/ */
@@ -443,14 +479,14 @@ export const HeroInfo: Record<number, heroInfo> = {
overrides: { ap: 250, bck: 30, para: 40, summon_count: 1 }, overrides: { ap: 250, bck: 30, para: 40, summon_count: 1 },
desc: "释放能量波,对{target}造成攻击力{ap}%伤害,击退{bck},{para}%概率麻痹", desc: "释放能量波,对{target}造成攻击力{ap}%伤害,击退{bck},{para}%概率麻痹",
lv_entries: [ lv_entries: [
{ lv: 6, s_lv: SkillLv.Lv2, overrides: { ap: 250, bck: 60, para: 40, summon_count: 1 } }, { lv: 9, s_lv: SkillLv.Lv2, overrides: { ap: 250, bck: 60, para: 40, summon_count: 1 } },
{ lv: 12, s_lv: SkillLv.Lv3, overrides: { ap: 300, bck: 90, timed_buff_id: 7005, para: 50, summon_count: 2 }, desc: "释放能量波,对{target}造成攻击力{ap}%伤害,击退{bck},{para}%概率麻痹,额外释放1次,并使目标中毒(每0.5秒受到{buff_value}点毒伤,持续{dur}秒)" }, { lv: 16, s_lv: SkillLv.Lv3, overrides: { ap: 300, bck: 90, timed_buff_id: 7005, para: 50, summon_count: 2 }, desc: "释放能量波,对{target}造成攻击力{ap}%伤害,击退{bck},{para}%概率麻痹,额外释放1次,并使目标中毒(每0.5秒受到{buff_value}点毒伤,持续{dur}秒)" },
], ],
}, },
}, },
atking: { atking: {
6010: [ 6010: [
{ lv: 3, s_lv: SkillLv.Lv1, name: "雷霆万钧", t_num: 3, overrides: { para: 100 }, desc: "释放紫魔球轰击{target},{para}%概率麻痹目标" }, { lv: 6, s_lv: SkillLv.Lv1, name: "雷霆万钧", t_num: 3, overrides: { para: 100 }, desc: "释放紫魔球轰击{target},{para}%概率麻痹目标" },
], ],
}, },
field: [ field: [
@@ -469,14 +505,14 @@ export const HeroInfo: Record<number, heroInfo> = {
overrides: { ap: 150 }, overrides: { ap: 150 },
desc: "斩出光闪,对{target}造成攻击力{ap}%伤害", desc: "斩出光闪,对{target}造成攻击力{ap}%伤害",
lv_entries: [ lv_entries: [
{ lv: 6, s_lv: SkillLv.Lv2, overrides: { ap: 150, timed_buff_id: 7007 }, desc: "斩出光闪,对{target}造成攻击力{ap}%伤害,并使目标出血(每0.5秒受到{buff_value}点伤害,持续{dur}秒)" }, { lv: 9, s_lv: SkillLv.Lv2, overrides: { ap: 150, timed_buff_id: 7007 }, desc: "斩出光闪,对{target}造成攻击力{ap}%伤害,并使目标出血(每0.5秒受到{buff_value}点伤害,持续{dur}秒)" },
{ lv: 12, s_lv: SkillLv.Lv3, overrides: { ap: 180, hit_count: 8, num: 1 }, desc: "斩出光闪,对{target}连击{hit_count}次,每次造成攻击力{ap}%伤害,并双发{num}次" }, { lv: 16, s_lv: SkillLv.Lv3, overrides: { ap: 180, hit_count: 8, num: 1 }, desc: "斩出光闪,对{target}连击{hit_count}次,每次造成攻击力{ap}%伤害,并双发{num}次" },
], ],
}, },
}, },
atking: { atking: {
6004: [ 6004: [
{ lv: 3, s_lv: SkillLv.Lv1, name: "致命节奏", t_num: 3, overrides: { crt: 30 }, desc: "释放斩击,{crt}%概率造成暴击" }, { lv: 6, s_lv: SkillLv.Lv1, name: "致命节奏", t_num: 3, overrides: { crt: 30 }, desc: "释放斩击,{crt}%概率造成暴击" },
], ],
}, },
field: [ field: [
@@ -495,19 +531,19 @@ export const HeroInfo: Record<number, heroInfo> = {
overrides: { ap: 150, para: 40, summon_count: 1 }, overrides: { ap: 150, para: 40, summon_count: 1 },
desc: "召唤落雷斩击{target},造成攻击力{ap}%伤害,{para}%概率麻痹", desc: "召唤落雷斩击{target},造成攻击力{ap}%伤害,{para}%概率麻痹",
lv_entries: [ lv_entries: [
{ lv: 6, s_lv: SkillLv.Lv2, overrides: { ap: 150, timed_buff_id: 7006, para: 40, summon_count: 1 }, desc: "召唤落雷斩击{target},造成攻击力{ap}%伤害,{para}%概率麻痹,并使目标攻击力{buff_value}%(持续{dur}秒)" }, { lv: 9, s_lv: SkillLv.Lv2, overrides: { ap: 150, timed_buff_id: 7006, para: 40, summon_count: 1 }, desc: "召唤落雷斩击{target},造成攻击力{ap}%伤害,{para}%概率麻痹,并使目标攻击力{buff_value}%(持续{dur}秒)" },
{ lv: 12, s_lv: SkillLv.Lv3, overrides: { ap: 180, timed_buff_id: 7006, buff_value: -15, para: 50, summon_count: 2 } }, { lv: 16, s_lv: SkillLv.Lv3, overrides: { ap: 180, timed_buff_id: 7006, buff_value: -15, para: 50, summon_count: 2 } },
], ],
}, },
}, },
atked: { atked: {
6301: [ 6301: [
{ lv: 3, s_lv: SkillLv.Lv1, name: "罗生门", t_num: 4, overrides: { TGroup: TGroup.Self, ap: 2 }, desc: "{target}获得{ap}次格挡护盾" }, { lv: 6, s_lv: SkillLv.Lv1, name: "罗生门", t_num: 4, overrides: { TGroup: TGroup.Self, ap: 2 }, desc: "{target}获得{ap}次格挡护盾" },
], ],
}, },
fstart: { fstart: {
6200: [ 6200: [
{ lv: 2, s_lv: SkillLv.Lv1, name: "三十六烦恼凤", t_num: 1, overrides: { ap: 500 }, desc: "入场释放剑气,对{target}造成攻击力{ap}%伤害" }, { lv: 6, s_lv: SkillLv.Lv1, name: "三十六烦恼凤", t_num: 1, overrides: { ap: 500 }, desc: "入场释放剑气,对{target}造成攻击力{ap}%伤害" },
], ],
}, },
field: [ field: [
@@ -526,14 +562,14 @@ export const HeroInfo: Record<number, heroInfo> = {
overrides: { ap: 150, summon_count: 1 }, overrides: { ap: 150, summon_count: 1 },
desc: "释放能量波,对{target}造成攻击力{ap}%伤害", desc: "释放能量波,对{target}造成攻击力{ap}%伤害",
lv_entries: [ lv_entries: [
{ lv: 6, s_lv: SkillLv.Lv2, overrides: { ap: 150, frz: 15, summon_count: 1 }, desc: "释放能量波,对{target}造成攻击力{ap}%伤害,{frz}%概率冰冻" }, { lv: 9, s_lv: SkillLv.Lv2, overrides: { ap: 150, frz: 15, summon_count: 1 }, desc: "释放能量波,对{target}造成攻击力{ap}%伤害,{frz}%概率冰冻" },
{ lv: 12, s_lv: SkillLv.Lv3, overrides: { ap: 180, frz: 30, hit_count: 8, summon_count: 2 }, desc: "释放能量波,对{target}造成攻击力{ap}%伤害,{frz}%概率冰冻,命中{hit_count}次" }, { lv: 16, s_lv: SkillLv.Lv3, overrides: { ap: 180, frz: 30, hit_count: 8, summon_count: 2 }, desc: "释放能量波,对{target}造成攻击力{ap}%伤害,{frz}%概率冰冻,命中{hit_count}次" },
], ],
}, },
}, },
atking: { atking: {
6009: [ 6009: [
{ lv: 3, s_lv: SkillLv.Lv1, name: "霜寒之触", t_num: 3, overrides: { timed_buff_id: 7003 }, desc: "释放寒气,冰冻{target}{dur}秒" }, { lv: 6, s_lv: SkillLv.Lv1, name: "霜寒之触", t_num: 3, overrides: { timed_buff_id: 7003 }, desc: "释放寒气,冰冻{target}{dur}秒" },
], ],
}, },
field: [ field: [
@@ -552,19 +588,19 @@ export const HeroInfo: Record<number, heroInfo> = {
overrides: { TGroup: TGroup.Self, ap: 3, summon_count: 1 }, overrides: { TGroup: TGroup.Self, ap: 3, summon_count: 1 },
desc: "{target}获得{ap}次格挡护盾", desc: "{target}获得{ap}次格挡护盾",
lv_entries: [ lv_entries: [
{ lv: 6, s_lv: SkillLv.Lv2, overrides: { TGroup: TGroup.Self, ap: 5, summon_count: 1 } }, { lv: 9, s_lv: SkillLv.Lv2, overrides: { TGroup: TGroup.Self, ap: 5, summon_count: 1 } },
{ lv: 12, s_lv: SkillLv.Lv3, overrides: { TGroup: TGroup.Self, ap: 6, summon_count: 2 } }, { lv: 16, s_lv: SkillLv.Lv3, overrides: { TGroup: TGroup.Self, ap: 6, summon_count: 2 } },
], ],
}, },
}, },
atked: { atked: {
6004: [ 6004: [
{ lv: 3, s_lv: SkillLv.Lv1, name: "反击减攻", t_num: 4, overrides: { timed_buff_id: 7006 }, desc: "反击使攻击者攻击力{buff_value}%,持续{dur}秒" }, { lv: 6, s_lv: SkillLv.Lv1, name: "反击减攻", t_num: 4, overrides: { timed_buff_id: 7006 }, desc: "反击使攻击者攻击力{buff_value}%,持续{dur}秒" },
], ],
}, },
fstart: { fstart: {
6412: [ 6412: [
{ lv: 2, s_lv: SkillLv.Lv1, name: "铁壁壁垒", t_num: 1, overrides: { TGroup: TGroup.Team, ap: 50 }, desc: "{target}防御提升{ap}点" }, { lv: 6, s_lv: SkillLv.Lv1, name: "铁壁壁垒", t_num: 1, overrides: { TGroup: TGroup.Team, ap: 50 }, desc: "{target}防御提升{ap}点" },
], ],
}, },
dead: { dead: {
@@ -592,19 +628,19 @@ export const HeroInfo: Record<number, heroInfo> = {
overrides: { TGroup: TGroup.Team, ap: 300, summon_count: 1 }, overrides: { TGroup: TGroup.Team, ap: 300, summon_count: 1 },
desc: "为{target}回复攻击力{ap}%生命", desc: "为{target}回复攻击力{ap}%生命",
lv_entries: [ lv_entries: [
{ lv: 6, s_lv: SkillLv.Lv2, overrides: { TGroup: TGroup.Team, ap: 400, summon_count: 1 } }, { lv: 9, s_lv: SkillLv.Lv2, overrides: { TGroup: TGroup.Team, ap: 400, summon_count: 1 } },
{ lv: 12, s_lv: SkillLv.Lv3, overrides: { TGroup: TGroup.Team, ap: 450, summon_count: 2 } }, { lv: 16, s_lv: SkillLv.Lv3, overrides: { TGroup: TGroup.Team, ap: 450, summon_count: 2 } },
], ],
}, },
}, },
atking: { atking: {
6302: [ 6302: [
{ lv: 3, s_lv: SkillLv.Lv1, name: "圣洁之锤", t_num: 3, overrides: { TGroup: TGroup.Self, ap: 100 }, desc: "释放圣光,为{target}回复攻击力{ap}%生命" }, { lv: 6, s_lv: SkillLv.Lv1, name: "圣洁之锤", t_num: 3, overrides: { TGroup: TGroup.Self, ap: 100 }, desc: "释放圣光,为{target}回复攻击力{ap}%生命" },
], ],
}, },
fstart: { fstart: {
6412: [ 6412: [
{ lv: 2, s_lv: SkillLv.Lv1, name: "圣光庇佑", t_num: 1, overrides: { TGroup: TGroup.Team, ap: 30 }, desc: "{target}防御提升{ap}点" }, { lv: 6, s_lv: SkillLv.Lv1, name: "圣光庇佑", t_num: 1, overrides: { TGroup: TGroup.Team, ap: 30 }, desc: "{target}防御提升{ap}点" },
], ],
}, },
field: [ field: [
@@ -623,14 +659,14 @@ export const HeroInfo: Record<number, heroInfo> = {
overrides: { ap: 150, summon_count: 1 }, overrides: { ap: 150, summon_count: 1 },
desc: "斩出气刃,对{target}造成攻击力{ap}%伤害", desc: "斩出气刃,对{target}造成攻击力{ap}%伤害",
lv_entries: [ lv_entries: [
{ lv: 6, s_lv: SkillLv.Lv2, overrides: { ap: 150, timed_buff_id: 7002, summon_count: 1 }, desc: "斩出气刃,对{target}造成攻击力{ap}%伤害,并使目标易伤(受到伤害加深,持续{dur}秒)" }, { lv: 9, s_lv: SkillLv.Lv2, overrides: { ap: 150, timed_buff_id: 7002, summon_count: 1 }, desc: "斩出气刃,对{target}造成攻击力{ap}%伤害,并使目标易伤(受到伤害加深,持续{dur}秒)" },
{ lv: 12, s_lv: SkillLv.Lv3, overrides: { ap: 180, bck: 60, timed_buff_id: 7002, buff_value: 20, summon_count: 2 }, desc: "斩出气刃,对{target}造成攻击力{ap}%伤害,击退{bck},并使目标易伤(持续{dur}秒)" }, { lv: 16, s_lv: SkillLv.Lv3, overrides: { ap: 180, bck: 60, timed_buff_id: 7002, buff_value: 20, summon_count: 2 }, desc: "斩出气刃,对{target}造成攻击力{ap}%伤害,击退{bck},并使目标易伤(持续{dur}秒)" },
], ],
}, },
}, },
atking: { atking: {
6004: [ 6004: [
{ lv: 3, s_lv: SkillLv.Lv1, name: "雷霆一击", t_num: 3, overrides: { bck: 40 }, desc: "重击{target},击退{bck}" }, { lv: 6, s_lv: SkillLv.Lv1, name: "雷霆一击", t_num: 3, overrides: { bck: 40 }, desc: "重击{target},击退{bck}" },
], ],
}, },
// card_lv 3:lv9 无光环,改为基础属性+50% // card_lv 3:lv9 无光环,改为基础属性+50%
@@ -649,14 +685,14 @@ export const HeroInfo: Record<number, heroInfo> = {
overrides: { ap: 300, summon_count: 1 }, overrides: { ap: 300, summon_count: 1 },
desc: "劈出开山斧,对{target}造成攻击力{ap}%伤害", desc: "劈出开山斧,对{target}造成攻击力{ap}%伤害",
lv_entries: [ lv_entries: [
{ lv: 6, s_lv: SkillLv.Lv2, overrides: { ap: 300, stun: 20, summon_count: 1 }, desc: "劈出开山斧,对{target}造成攻击力{ap}%伤害,{stun}%概率击晕" }, { lv: 9, s_lv: SkillLv.Lv2, overrides: { ap: 300, stun: 20, summon_count: 1 }, desc: "劈出开山斧,对{target}造成攻击力{ap}%伤害,{stun}%概率击晕" },
{ lv: 12, s_lv: SkillLv.Lv3, overrides: { ap: 350, stun: 35, hit_count: 8, summon_count: 2 }, desc: "劈出开山斧,对{target}造成攻击力{ap}%伤害,{stun}%概率击晕,命中{hit_count}次" }, { lv: 16, s_lv: SkillLv.Lv3, overrides: { ap: 350, stun: 35, hit_count: 8, summon_count: 2 }, desc: "劈出开山斧,对{target}造成攻击力{ap}%伤害,{stun}%概率击晕,命中{hit_count}次" },
], ],
}, },
}, },
atking: { atking: {
6004: [ 6004: [
{ lv: 3, s_lv: SkillLv.Lv1, name: "火眼金睛", t_num: 3, overrides: { crt: 25 }, desc: "释放斩击,{crt}%概率造成暴击" }, { lv: 6, s_lv: SkillLv.Lv1, name: "火眼金睛", t_num: 3, overrides: { crt: 25 }, desc: "释放斩击,{crt}%概率造成暴击" },
], ],
}, },
field: [ field: [
@@ -675,20 +711,20 @@ export const HeroInfo: Record<number, heroInfo> = {
overrides: { ap: 150, summon_count: 1 }, overrides: { ap: 150, summon_count: 1 },
desc: "斩出气刃,对{target}造成攻击力{ap}%伤害", desc: "斩出气刃,对{target}造成攻击力{ap}%伤害",
lv_entries: [ lv_entries: [
{ lv: 6, s_lv: SkillLv.Lv2, overrides: { ap: 180, summon_count: 1 } }, { lv: 9, s_lv: SkillLv.Lv2, overrides: { ap: 180, summon_count: 1 } },
{ lv: 12, s_lv: SkillLv.Lv3, overrides: { ap: 200, summon_count: 2 } }, { lv: 16, s_lv: SkillLv.Lv3, overrides: { ap: 200, summon_count: 2 } },
], ],
}, },
}, },
atking: { atking: {
6302: [ 6302: [
{ lv: 3, s_lv: SkillLv.Lv1, name: "嗜血战斧", t_num: 4, overrides: { TGroup: TGroup.Self, ap: 120 }, desc: "释放嗜血斩击,为{target}回复攻击力{ap}%生命" }, { lv: 6, s_lv: SkillLv.Lv1, name: "嗜血战斧", t_num: 4, overrides: { TGroup: TGroup.Self, ap: 120 }, desc: "释放嗜血斩击,为{target}回复攻击力{ap}%生命" },
], ],
}, },
atked: { atked: {
// 坚硬皮肤:借 6513 防御爆发底座注入 7023 防御强化(+50%,可无限叠层) // 坚硬皮肤:借 6513 防御爆发底座注入 7023 防御强化(+50%,可无限叠层)
6513: [ 6513: [
{ lv: 3, s_lv: SkillLv.Lv1, name: "坚硬皮肤", t_num: 2, overrides: { TGroup: TGroup.Self, timed_buff_id: 7023, buff_duration: 10 }, desc: "{target}防御提升{buff_value}%,持续{dur}秒" }, { lv: 6, s_lv: SkillLv.Lv1, name: "坚硬皮肤", t_num: 2, overrides: { TGroup: TGroup.Self, timed_buff_id: 7023, buff_duration: 10 }, desc: "{target}防御提升{buff_value}%,持续{dur}秒" },
], ],
}, },
// card_lv 3:lv9 无光环,改为基础属性+50% // card_lv 3:lv9 无光环,改为基础属性+50%
@@ -964,13 +1000,14 @@ export function resolveReviveByLv(revive: LvReviveEntry[] | undefined, lv: numbe
return pick ? { s_uuid: pick.s_uuid, r_num: pick.r_num, upr: pick.upr } : undefined; return pick ? { s_uuid: pick.s_uuid, r_num: pick.r_num, upr: pick.upr } : undefined;
} }
/** 计算目标等级下额外属性加成(hp_bonus/ap_bonus 一次性奖励 + 成长类型每级递增) */ /** 计算目标等级下额外属性加成(hp/ap/def_bonus 一次性奖励 + 成长类型每级递增) */
function getEvolveBonuses(hero: heroInfo, targetLv: number): { hp: number, ap: number } { function getEvolveBonuses(hero: heroInfo, targetLv: number): { hp: number, ap: number, def: number } {
const lv = Math.max(1, targetLv); const lv = Math.max(1, targetLv);
// 成长类型线性增量(仅英雄生效,怪物无 grow_type 时走 Warrior 基准但不影响平衡,因怪物通常不填) // 成长类型线性增量(仅英雄生效,怪物无 grow_type 时走 Warrior 基准但不影响平衡,因怪物通常不填)
const grow = hero.fac === FacSet.HERO ? calcGrowBonus(hero, lv) : { hp: 0, ap: 0 }; const grow = hero.fac === FacSet.HERO ? calcGrowBonus(hero, lv) : { hp: 0, ap: 0, def: 0 };
let hp = grow.hp; let hp = grow.hp;
let ap = grow.ap; let ap = grow.ap;
let def = grow.def;
if (hero.hp_bonus) { if (hero.hp_bonus) {
for (const e of hero.hp_bonus) { for (const e of hero.hp_bonus) {
if (e.lv <= lv) hp += e.value; if (e.lv <= lv) hp += e.value;
@@ -981,15 +1018,57 @@ function getEvolveBonuses(hero: heroInfo, targetLv: number): { hp: number, ap: n
if (e.lv <= lv) ap += e.value; if (e.lv <= lv) ap += e.value;
} }
} }
return { hp, ap }; if (hero.def_bonus) {
for (const e of hero.def_bonus) {
if (e.lv <= lv) def += e.value;
}
}
return { hp, ap, def };
} }
/** 计算目标等级下的有效 HP/AP(固定基础值 + 成长递增与一次性加成) */ /** 计算目标等级下的有效 HP/AP/DEF(固定基础值 + 成长递增与一次性加成) */
function getEffectiveStats(hero: heroInfo, targetLv: number): { hp: number, ap: number } { export function getEffectiveStats(hero: heroInfo, targetLv: number): { hp: number, ap: number, def: number } {
const bonuses = getEvolveBonuses(hero, targetLv); const bonuses = getEvolveBonuses(hero, targetLv);
return { return {
hp: hero.hp + bonuses.hp, hp: hero.hp + bonuses.hp,
ap: hero.ap + bonuses.ap, ap: hero.ap + bonuses.ap,
def: hero.def + bonuses.def,
};
}
/**
* 计算英雄展示用最终属性(含品质系数与 lv12 职业专项),供 UI 面板与运行时对齐口径。
* 公式:基础值 × 品质系数 + 成长 + bonus,再叠加 lv12 专项(基数取 lv11 有效值)。
* @param hero 英雄配置
* @param targetLv 目标等级(≥1)
* @returns 含品质与专项的最终 hp/ap/def(已取整)
*/
export function getHeroDisplayStats(hero: heroInfo, targetLv: number): { hp: number, ap: number, def: number } {
const lv = Math.max(1, Math.floor(targetLv));
const coef = getHeroQualityCoef(hero.card_lv ?? 1);
const eff = getEffectiveStats(hero, lv);
const stats = {
hp: Math.round(hero.hp * coef) + (eff.hp - hero.hp),
ap: Math.round(hero.ap * coef) + (eff.ap - hero.ap),
def: Math.round(hero.def * coef) + (eff.def - hero.def),
};
// lv12 职业专项:基数为 lv11 展示有效值
if (lv >= HERO_LV12_SPECIAL_LV && hero.role !== undefined) {
const base11 = getHeroDisplayStatsBase(hero, HERO_LV12_BASE_LV);
const attr = HERO_LV12_SPECIAL_ATTR[hero.role];
stats[attr] += Math.round(base11[attr] * 0.5);
}
return stats;
}
/** 内部:lv12 专项基数(不含 lv12 自身)的展示有效值 */
function getHeroDisplayStatsBase(hero: heroInfo, targetLv: number): { hp: number, ap: number, def: number } {
const coef = getHeroQualityCoef(hero.card_lv ?? 1);
const eff = getEffectiveStats(hero, targetLv);
return {
hp: Math.round(hero.hp * coef) + (eff.hp - hero.hp),
ap: Math.round(hero.ap * coef) + (eff.ap - hero.ap),
def: Math.round(hero.def * coef) + (eff.def - hero.def),
}; };
} }

View File

@@ -49,7 +49,8 @@ revive[0]:无
**通用约束**: **通用约束**:
- lv2 走 atking/atked/fstart 提升普攻能力;lv3/5/7 走 `skills[1].lv_entries` 提升大招;lv9 光环或属性补偿 - 等级节奏:lv1 激活 skills[0]/skills[1];lv6 解锁 atking/atked/fstart 触发技能(1 个);大招(skills[1])lv9/lv16 走 `lv_entries` 分档提升;lv12 按职业对单项基础属性 +50%(基数取 lv11 当前等级有效值:坦克→def、战士/辅助→hp、刺客/射手/法师→ap);lv9 光环或属性补偿
- 品质差异:相邻品质(card_lv)lv1 初始基础属性(AP/HP/DEF)相差 5%(系数 1+0.05×(card_lv-1)),其余配置参数完全一致
- 能力只能从能力池(BuffList 7001\~7024 / FieldSkillSet 7001\~7020 / SkillSet 底座)引用 + `overrides` 数值覆盖,**禁止新增同义底座** - 能力只能从能力池(BuffList 7001\~7024 / FieldSkillSet 7001\~7020 / SkillSet 底座)引用 + `overrides` 数值覆盖,**禁止新增同义底座**
**大招召唤数约束**(`summon_count` 覆写规则): **大招召唤数约束**(`summon_count` 覆写规则):
@@ -303,11 +304,13 @@ revive[0]:无
## 配置检查清单 ## 配置检查清单
- [ ] `skills` 引用的 uuid 在 SkillSet 中存在,`overrides.ap` 显式写出 - [ ] `skills` 引用的 uuid 在 SkillSet 中存在,`overrides.ap` 显式写出
- [ ] 大招(`skills[1]`)覆写 `summon_count`:lv1=1、lv3=1、lv5=2、lv7=3,通过 `lv_entries` 逐级覆写,不改动 SkillSet 底座 - [ ] 大招(`skills[1]`)覆写 `summon_count`:lv1=1、lv9=1、lv16=2,通过 `lv_entries` 逐级覆写,不改动 SkillSet 底座
- [ ] `timed_buff_id` 指向 BuffList 已存在条目(能力池 2.1/2.2 内) - [ ] `timed_buff_id` 指向 BuffList 已存在条目(能力池 2.1/2.2 内)
- [ ] debuff/buff 只通过 `overrides` 注入,未改动 SkillSet/BuffList 底座 - [ ] debuff/buff 只通过 `overrides` 注入,未改动 SkillSet/BuffList 底座
- [ ] `atking`/`atked`/`fstart`/`dead` 按 s\_uuid 分组、按 lv 升序 - [ ] `atking`/`atked`/`fstart`/`dead` 按 s\_uuid 分组、按 lv 升序,触发技能统一 lv6 解锁
- [ ] lv9:card\_lv 4/5 配 `field`;card\_lv 1/2/3 配 `ap_bonus`/`hp_bonus`(30/40/50%) - [ ] lv9:card\_lv 4/5 配 `field`;card\_lv 1/2/3 配 `ap_bonus`/`hp_bonus`(30/40/50%)
- [ ] lv12:按职业注入单项属性 +50%(坦克 def、战士/辅助 hp、刺客/射手/法师 ap),基数取 lv11 有效值
- [ ] 品质:card\_lv 相邻基础属性差 5%(运行时品质系数统一乘,配置保持裸值)
- [ ] `t_num` 概率模拟符合预期(1=每次,3≈33%,4≈25%) - [ ] `t_num` 概率模拟符合预期(1=每次,3≈33%,4≈25%)
- [ ] `infos` 分级说明与配置档位一一对应(面板逐条展示) - [ ] `infos` 分级说明与配置档位一一对应(面板逐条展示)
- [ ] 近战/远程形态与 `type`/`dis` 匹配,美术资源 `path`/`icon` 就绪 - [ ] 近战/远程形态与 `type`/`dis` 匹配,美术资源 `path`/`icon` 就绪

View File

@@ -5,7 +5,7 @@ import { smc } from "../common/SingletonModuleComp";
import { HeroAttrsComp } from "./HeroAttrsComp"; import { HeroAttrsComp } from "./HeroAttrsComp";
import { HeroViewComp } from "./HeroViewComp"; import { HeroViewComp } from "./HeroViewComp";
import { BoxSet, FacSet, FightSet, IndexSet } from "../common/config/GameSet"; import { BoxSet, FacSet, FightSet, IndexSet } from "../common/config/GameSet";
import { HeroInfo, resolveFormationTargetX, resolveTriggerByLv, resolveFieldByLv, resolveReviveByLv, resolveSkillOverrides, calcGrowBonus, HeroDisVal } from "../common/config/heroSet"; import { HeroInfo, resolveFormationTargetX, resolveTriggerByLv, resolveFieldByLv, resolveReviveByLv, resolveSkillOverrides, calcGrowBonus, HeroDisVal, getHeroQualityCoef, getEffectiveStats, HERO_LV12_SPECIAL_LV, HERO_LV12_BASE_LV, HERO_LV12_SPECIAL_ATTR } from "../common/config/heroSet";
import { GameEvent } from "../common/config/GameEvent"; import { GameEvent } from "../common/config/GameEvent";
import { SkillTriggerType, HType } from "../common/config/heroSet"; import { SkillTriggerType, HType } from "../common/config/heroSet";
import { SkillTriggerHelper } from "./SkillTriggerHelper"; import { SkillTriggerHelper } from "./SkillTriggerHelper";
@@ -140,15 +140,18 @@ export class Hero extends ecs.Entity {
model.revive = hero.revive; model.revive = hero.revive;
model.ap_bonus = hero.ap_bonus; model.ap_bonus = hero.ap_bonus;
model.hp_bonus = hero.hp_bonus; model.hp_bonus = hero.hp_bonus;
model.def_bonus = hero.def_bonus;
// 基础属性:固定基础值,成长完全由 grow_type 指数递增提供(替代旧 ×2^(lv-1) 翻倍) // 基础属性:固定基础值 × 品质系数(相邻品质差 5%),成长由 grow_type 线性递增提供
// TODO: 局外升级机制落地后,此处基础值将与存档强化累加重算 // TODO: 局外升级机制落地后,此处基础值将与存档强化累加重算
let base_ap = hero.ap; const qualityCoef = getHeroQualityCoef(hero.card_lv ?? 1);
let base_hp = hero.hp; let base_ap = Math.round(hero.ap * qualityCoef);
let base_hp = Math.round(hero.hp * qualityCoef);
let base_def = Math.round(hero.def * qualityCoef);
model.base_ap = base_ap; model.base_ap = base_ap;
model.base_hp = base_hp; model.base_hp = base_hp;
model.defense = hero.def; model.defense = base_def;
// 英雄享受驻场百分比加成,怪物保持基础值 // 英雄享受驻场百分比加成,怪物保持基础值
if (model.fac === FacSet.HERO) { if (model.fac === FacSet.HERO) {
@@ -179,11 +182,12 @@ export class Hero extends ecs.Entity {
model.runtime_field = resolveFieldByLv(model.field, hero_lv); model.runtime_field = resolveFieldByLv(model.field, hero_lv);
model.runtime_revive = resolveReviveByLv(model.revive, hero_lv); model.runtime_revive = resolveReviveByLv(model.revive, hero_lv);
// 应用额外属性加成(成长类型每级递增 + hp_bonus/ap_bonus 一次性奖励累加) // 应用额外属性加成(成长类型每级递增 + hp/ap/def_bonus 一次性奖励累加)
// 成长增量:仅英雄生效(model.fac 已为 HERO),按 grow_type 递增曲线累计 // 成长增量:仅英雄生效(model.fac 已为 HERO),按 grow_type 递增曲线累计
const grow = calcGrowBonus(hero, hero_lv); const grow = calcGrowBonus(hero, hero_lv);
model.hp += grow.hp; model.hp_max += grow.hp; model.hp += grow.hp; model.hp_max += grow.hp;
model.ap += grow.ap; model.ap += grow.ap;
model.defense += grow.def;
if (model.hp_bonus) { if (model.hp_bonus) {
let hpExtra = 0; let hpExtra = 0;
for (const e of model.hp_bonus) { if (e.lv <= hero_lv) hpExtra += e.value; } for (const e of model.hp_bonus) { if (e.lv <= hero_lv) hpExtra += e.value; }
@@ -194,6 +198,21 @@ export class Hero extends ecs.Entity {
for (const e of model.ap_bonus) { if (e.lv <= hero_lv) apExtra += e.value; } for (const e of model.ap_bonus) { if (e.lv <= hero_lv) apExtra += e.value; }
model.ap += apExtra; model.ap += apExtra;
} }
if (model.def_bonus) {
let defExtra = 0;
for (const e of model.def_bonus) { if (e.lv <= hero_lv) defExtra += e.value; }
model.defense += defExtra;
}
// lv12 职业属性专项:达到 12 级时按职业对单项基础属性 +50%
// 基数为 lv11 当前等级有效值(基础 + 成长 + lv9 bonus,不含 lv12 自身),仅英雄生效
if (model.fac === FacSet.HERO && hero_lv >= HERO_LV12_SPECIAL_LV && model.role !== undefined) {
const lv11 = getEffectiveStats(hero, HERO_LV12_BASE_LV);
const attr = HERO_LV12_SPECIAL_ATTR[model.role];
if (attr === "def") model.defense += Math.round(lv11.def * 0.5);
else if (attr === "hp") { const add = Math.round(lv11.hp * 0.5); model.hp += add; model.hp_max += add; }
else if (attr === "ap") model.ap += Math.round(lv11.ap * 0.5);
}
// 天赋加成层:全局天赋(一阶基础/三阶超强全局)全局生效,职业天赋(二阶专精/三阶超强职业)按 role 过滤(仅英雄生效,怪物跳过) // 天赋加成层:全局天赋(一阶基础/三阶超强全局)全局生效,职业天赋(二阶专精/三阶超强职业)按 role 过滤(仅英雄生效,怪物跳过)
// Why: 放在成长/bonus 之后、光环乘区之后,以基础快照为基数叠加,避免与乘区叠加成乘法膨胀 // Why: 放在成长/bonus 之后、光环乘区之后,以基础快照为基数叠加,避免与乘区叠加成乘法膨胀

View File

@@ -54,9 +54,10 @@ export class HeroAttrsComp extends ecs.Comp {
/** 驻场光环(按 lv 解锁,存活期间全局生效) */ /** 驻场光环(按 lv 解锁,存活期间全局生效) */
[SkillTriggerType.Field]?: LvFieldEntry[]; [SkillTriggerType.Field]?: LvFieldEntry[];
[SkillTriggerType.Revive]?: LvReviveEntry[]; [SkillTriggerType.Revive]?: LvReviveEntry[];
/** 额外攻击/生命加成(按 lv 累加) */ /** 额外攻击/生命/防御加成(按 lv 累加) */
ap_bonus?: LvBonusEntry[]; ap_bonus?: LvBonusEntry[];
hp_bonus?: LvBonusEntry[]; hp_bonus?: LvBonusEntry[];
def_bonus?: LvBonusEntry[];
// ==================== 运行时解析缓存(按当前等级 resolve 后的有效列表) ==================== // ==================== 运行时解析缓存(按当前等级 resolve 后的有效列表) ====================
/** 当前等级下生效的攻击触发条目(供 SkillTriggerHelper 消费) */ /** 当前等级下生效的攻击触发条目(供 SkillTriggerHelper 消费) */
@@ -633,6 +634,7 @@ export class HeroAttrsComp extends ecs.Comp {
this.revive = undefined; this.revive = undefined;
this.ap_bonus = undefined; this.ap_bonus = undefined;
this.hp_bonus = undefined; this.hp_bonus = undefined;
this.def_bonus = undefined;
this.runtime_atking = undefined; this.runtime_atking = undefined;
this.runtime_atked = undefined; this.runtime_atked = undefined;
this.runtime_call = undefined; this.runtime_call = undefined;

View File

@@ -54,7 +54,7 @@ import { MissSkillsComp } from "./MissSkillsComp";
import { oops } from "db://oops-framework/core/Oops"; import { oops } from "db://oops-framework/core/Oops";
import { HeroAttrsComp } from "../hero/HeroAttrsComp"; import { HeroAttrsComp } from "../hero/HeroAttrsComp";
import { smc } from "../common/SingletonModuleComp"; import { smc } from "../common/SingletonModuleComp";
import { HeroInfo, HType, resolveTriggerByLv, resolveFieldByLv, resolveReviveByLv, resolveSkillOverrides, calcGrowBonus } from "../common/config/heroSet"; import { HeroInfo, HType, resolveTriggerByLv, resolveFieldByLv, resolveReviveByLv, resolveSkillOverrides, calcGrowBonus, getHeroQualityCoef, getEffectiveStats, HERO_LV12_SPECIAL_LV, HERO_LV12_BASE_LV, HERO_LV12_SPECIAL_ATTR } from "../common/config/heroSet";
import { applyTalentBonuses } from "../common/config/TalentSet"; import { applyTalentBonuses } from "../common/config/TalentSet";
import { HeroViewComp } from "../hero/HeroViewComp"; import { HeroViewComp } from "../hero/HeroViewComp";
import { FacSet, FightSet } from "../common/config/GameSet"; import { FacSet, FightSet } from "../common/config/GameSet";
@@ -2151,9 +2151,10 @@ export class MissionCardComp extends CCComp {
const hpRate = model.hp_max > 0 ? model.hp / model.hp_max : 1; const hpRate = model.hp_max > 0 ? model.hp / model.hp_max : 1;
model.lv = nextLv; model.lv = nextLv;
// 同步 base 快照(与 Hero.load 同公式:固定基础值,成长由 grow_type 指数递增提供) // 同步 base 快照(与 Hero.load 同公式:固定基础值 × 品质系数,成长由 grow_type 线性递增提供)
const base_ap = hero.ap; const qualityCoef = getHeroQualityCoef(hero.card_lv ?? 1);
const base_hp = hero.hp; const base_ap = Math.round(hero.ap * qualityCoef);
const base_hp = Math.round(hero.hp * qualityCoef);
model.base_ap = base_ap; model.base_ap = base_ap;
model.base_hp = base_hp; model.base_hp = base_hp;
@@ -2166,7 +2167,7 @@ export class MissionCardComp extends CCComp {
model.hp_max = base_hp; model.hp_max = base_hp;
} }
// 按 ≤nextLv 累加:成长类型每级递增 + hp_bonus/ap_bonus 一次性奖励(沿用 Hero.load 的加法区规则) // 按 ≤nextLv 累加:成长类型每级递增 + hp/ap/def_bonus 一次性奖励(沿用 Hero.load 的加法区规则)
const grow = calcGrowBonus(hero, nextLv); const grow = calcGrowBonus(hero, nextLv);
model.hp_max += grow.hp; model.hp_max += grow.hp;
model.ap += grow.ap; model.ap += grow.ap;
@@ -2184,7 +2185,20 @@ export class MissionCardComp extends CCComp {
// 天赋加成层(与 Hero.load 对齐):先重置天赋可能修改的字段,避免升级重算时重复叠加 // 天赋加成层(与 Hero.load 对齐):先重置天赋可能修改的字段,避免升级重算时重复叠加
// Why: ap/hp_max 已由上方公式从 base 重算(天然幂等),但 defense 与概率类字段是累加语义,必须先归零再注入 // Why: ap/hp_max 已由上方公式从 base 重算(天然幂等),但 defense 与概率类字段是累加语义,必须先归零再注入
if (model.fac === FacSet.HERO) { if (model.fac === FacSet.HERO) {
model.defense = hero.def; // defense 从品质系数后的基础值重算,叠加 def 成长 + def_bonus + lv12 专项,幂等
let base_def = Math.round(hero.def * qualityCoef) + grow.def;
if (model.def_bonus) {
for (const e of model.def_bonus) { if (e.lv <= nextLv) base_def += e.value; }
}
// lv12 职业属性专项:达到 12 级时按职业对单项基础属性 +50%(基数为 lv11 有效值)
if (nextLv >= HERO_LV12_SPECIAL_LV && model.role !== undefined) {
const lv11 = getEffectiveStats(hero, HERO_LV12_BASE_LV);
const attr = HERO_LV12_SPECIAL_ATTR[model.role];
if (attr === "def") base_def += Math.round(lv11.def * 0.5);
else if (attr === "hp") model.hp_max += Math.round(lv11.hp * 0.5);
else if (attr === "ap") model.ap += Math.round(lv11.ap * 0.5);
}
model.defense = base_def;
model.critical = 0; model.critical = 0;
model.critical_damage = 0; model.critical_damage = 0;
model.stun_chance = 0; model.stun_chance = 0;