Files
pixelheros/assets/script/game/common/config/TalentSet.ts
pan df519fdc4e feat(talent): 重构天赋系统为三阶结构并实现全新UI
1.  重构天赋系统为一阶基础/二阶专精/三阶超强三阶结构,移除前置天赋要求改为阶位累计点数解锁
2.  新增阶位解锁校验逻辑与天赋点投入统计
3.  重做天赋UI为三阶Tab页签+迷你升级卡样式,支持状态展示与一键升级
4.  补充新的天赋配置与属性加成,更新天赋系统文档
5.  修复属性计算问题,新增暴击伤害与穿刺伤害加成支持
2026-08-25 16:29:11 +08:00

434 lines
19 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { Attrs } from "./HeroAttrs";
import { HRole } from "./heroSet";
/**
* 天赋系统配置(局外成长:升级即激活,战斗中对英雄属性常驻加成)。
*
* 三阶结构(阶位逐级解锁,门槛见 TALENT_TIER_UNLOCK_THRESHOLD):
* - 一阶·基础天赋(9001-9099):全局生效,对所有英雄叠加;
* - 二阶·专精天赋(9101-9199):按 HRole 职业过滤,仅对匹配职业的英雄生效;
* - 三阶·超强天赋(9201-9219):全局(9201-9209)+ 职业(9211-9219)混合,数值幅度约为前两阶的 2 倍。
*
* 解锁规则:一阶恒可升级;二阶需一阶累计投入 ≥10 点;三阶需二阶累计投入 ≥10 点。
* 专精/超强职业天赋不再有独立前置天赋,仅受阶位解锁约束。
*
* 结算口径(与项目 buff 规范一致):
* - percent 模式:按英雄基础值(base_ap/base_hp/hero.def)相对放大;
* - flat 模式:概率/抗性类按百分点累加,defense 按固定点数累加。
*/
/** 天赋数值模式:percent=按基础值百分比放大;flat=固定值/百分点累加 */
export enum TalentValueMode {
Percent = 0,
Flat = 1,
}
/** 天赋阶位:一阶基础 / 二阶专精 / 三阶超强 */
export enum TalentTier {
/** 一阶:基础天赋,全局生效 */
Basic = 1,
/** 二阶:专精天赋,按职业过滤 */
Specialized = 2,
/** 三阶:超强天赋,全局 + 职业混合 */
Super = 3,
}
/** 单条属性加成词条 */
export interface TalentAttrBonus {
/** 目标属性(Attrs 枚举键) */
attr: Attrs;
/** 数值模式 */
mode: TalentValueMode;
/** 每级数值(percent 模式为百分比;flat 模式为百分点/固定值) */
per_lv: number;
}
/** 天赋配置 */
export interface TalentConfig {
/** 天赋 uuid(段位见 TalentSet 注释) */
uuid: number;
/** 所属阶位(升级校验:升下一阶天赋需前一阶累计投入点数达标) */
tier: TalentTier;
/** 天赋名称 */
name: string;
/** 描述(UI 展示,{value}/{value2} 为按当前等级计算后的数值占位) */
info: string;
/** 图标(uiconsAtlas 帧名,缺省时 UI 端按资源目录回退) */
icon?: string;
/** 属性加成词条列表(每级叠加) */
bonuses: TalentAttrBonus[];
/** 最大等级 */
max_lv: number;
/** 专精/超强职业天赋:仅对该职业英雄生效;全局天赋省略此字段 */
role?: HRole;
}
/** 单条已解析的加成(等级已乘算) */
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 二阶专精(按职业);
* 9201-9209 三阶超强·全局;9211-9219 三阶超强·职业。
* icon 复用 SkillSet/uiconsAtlas 既有帧名,后续可在编辑器侧替换为专属图标。
*/
export const TalentSet: Record<number, TalentConfig> = {
// ==================== 一阶·基础天赋(9001-9099,全局生效) ====================
9001: {
uuid: 9001, tier: TalentTier.Basic, name: "攻击强化", info: "所有英雄攻击 +{value}%", icon: "Stat_Attack_03",
bonuses: [{ attr: Attrs.ap, mode: TalentValueMode.Percent, per_lv: 3 }],
max_lv: 5,
},
9002: {
uuid: 9002, tier: TalentTier.Basic, name: "生命强化", info: "所有英雄生命 +{value}%", icon: "Stat_Hp_02",
bonuses: [{ attr: Attrs.hp_max, mode: TalentValueMode.Percent, per_lv: 5 }],
max_lv: 5,
},
9003: {
uuid: 9003, tier: TalentTier.Basic, name: "防御强化", info: "所有英雄防御 +{value} 点", icon: "Stat_Defense",
bonuses: [{ attr: Attrs.defense, mode: TalentValueMode.Flat, per_lv: 8 }],
max_lv: 5,
},
9004: {
uuid: 9004, tier: TalentTier.Basic, name: "暴击强化", info: "所有英雄暴击率 +{value}%", icon: "Stat_CriticalChance_02",
bonuses: [{ attr: Attrs.critical, mode: TalentValueMode.Flat, per_lv: 2 }],
max_lv: 5,
},
9005: {
uuid: 9005, tier: TalentTier.Basic, name: "击晕强化", info: "所有英雄击晕率 +{value}%", icon: "Stat_Stun_01",
bonuses: [{ attr: Attrs.stun_chance, mode: TalentValueMode.Flat, per_lv: 2 }],
max_lv: 5,
},
9006: {
uuid: 9006, tier: TalentTier.Basic, name: "冰冻强化", info: "所有英雄冰冻率 +{value}%", icon: "Stat_Freeze",
bonuses: [{ attr: Attrs.freeze_chance, mode: TalentValueMode.Flat, per_lv: 2 }],
max_lv: 5,
},
9007: {
uuid: 9007, tier: TalentTier.Basic, name: "击退强化", info: "所有英雄击退距离 +{value}", icon: "Stat_Stun_01",
bonuses: [{ attr: Attrs.knockback_distance, mode: TalentValueMode.Flat, per_lv: 10 }],
max_lv: 5,
},
// 风怒机制已停用: 9008 风怒强化天赋已移除
/*
9008: {
uuid: 9008, tier: TalentTier.Basic, name: "风怒强化", info: "所有英雄风怒率 +{value}%", icon: "Stat_CriticalComboChance",
bonuses: [{ attr: Attrs.wfuny, mode: TalentValueMode.Flat, per_lv: 2 }],
max_lv: 5,
},
*/
// ==================== 二阶·专精天赋(9101-9199,按职业过滤;一阶投入 10 点解锁) ====================
9101: {
uuid: 9101, tier: TalentTier.Specialized, name: "坦克专精", info: "坦克防御 +{value}%、生命 +{value2}%", icon: "Stat_Armor_01",
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,
},
9102: {
uuid: 9102, tier: TalentTier.Specialized, name: "战士专精", info: "战士生命 +{value}%", icon: "Sword-FA_WP_Main_Sword_003_PurpleSilver",
bonuses: [{ attr: Attrs.hp_max, mode: TalentValueMode.Percent, per_lv: 12 }],
max_lv: 5, role: HRole.Warrior,
},
9103: {
uuid: 9103, tier: TalentTier.Specialized, name: "刺客专精", info: "刺客攻击 +{value}%、暴击率 +{value2}%", icon: "Sword-FA_WP_Main_Sword_019_WoodSilver",
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,
},
9104: {
uuid: 9104, tier: TalentTier.Specialized, name: "射手专精", info: "射手攻击 +{value}%、穿透率 +{value2}%", icon: "Stat_RangedAttack",
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,
},
9105: {
uuid: 9105, tier: TalentTier.Specialized, name: "法师专精", info: "法师攻击 +{value}%、冰冻率 +{value2}%", icon: "Stat_Mana",
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,
},
9106: {
uuid: 9106, tier: TalentTier.Specialized, name: "辅助专精", info: "辅助治疗量 +{value}%、施加的增益效果 +{value2}%", icon: "Stat_PotionBoost",
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,
},
// ==================== 三阶·超强天赋(9201-9219,二阶投入 10 点解锁;幅度约为前两阶 2 倍) ====================
// ---- 全局超强(9201-9209) ----
9201: {
uuid: 9201, tier: TalentTier.Super, name: "神威降临", info: "所有英雄攻击 +{value}%、生命 +{value2}%", icon: "Stat_HolyDamage",
bonuses: [
{ attr: Attrs.ap, mode: TalentValueMode.Percent, per_lv: 8 },
{ attr: Attrs.hp_max, mode: TalentValueMode.Percent, per_lv: 8 },
],
max_lv: 5,
},
9202: {
uuid: 9202, tier: TalentTier.Super, name: "致命终结", info: "所有英雄暴击率 +{value}%、暴击伤害 +{value2}%", icon: "Stat_Critical_01",
bonuses: [
{ attr: Attrs.critical, mode: TalentValueMode.Flat, per_lv: 3 },
{ attr: Attrs.critical_damage, mode: TalentValueMode.Flat, per_lv: 15 },
],
max_lv: 5,
},
9203: {
uuid: 9203, tier: TalentTier.Super, name: "破甲重创", info: "所有英雄穿透率 +{value}%、穿透伤害 +{value2}%", icon: "Stat_Tripleshot",
bonuses: [
{ attr: Attrs.puncture_chance, mode: TalentValueMode.Flat, per_lv: 3 },
{ attr: Attrs.puncture_dmg_bonus, mode: TalentValueMode.Flat, per_lv: 10 },
],
max_lv: 5,
},
9204: {
uuid: 9204, tier: TalentTier.Super, name: "钢铁之躯", info: "所有英雄防御 +{value}%、生命 +{value2}%", icon: "Stat_Defense",
bonuses: [
{ attr: Attrs.defense, mode: TalentValueMode.Percent, per_lv: 20 },
{ attr: Attrs.hp_max, mode: TalentValueMode.Percent, per_lv: 6 },
],
max_lv: 5,
},
// ---- 职业超强(9211-9219,按 HRole 过滤) ----
9211: {
uuid: 9211, tier: TalentTier.Super, name: "不朽壁垒", info: "坦克防御 +{value}%、生命 +{value2}%", icon: "Stat_Armor_01",
bonuses: [
{ attr: Attrs.defense, mode: TalentValueMode.Percent, per_lv: 40 },
{ attr: Attrs.hp_max, mode: TalentValueMode.Percent, per_lv: 8 },
],
max_lv: 5, role: HRole.Tank,
},
9212: {
uuid: 9212, tier: TalentTier.Super, name: "狂血战魂", info: "战士攻击 +{value}%、生命 +{value2}%", icon: "Stat_Attack_03",
bonuses: [
{ attr: Attrs.ap, mode: TalentValueMode.Percent, per_lv: 10 },
{ attr: Attrs.hp_max, mode: TalentValueMode.Percent, per_lv: 8 },
],
max_lv: 5, role: HRole.Warrior,
},
9213: {
uuid: 9213, tier: TalentTier.Super, name: "影刃绝杀", info: "刺客攻击 +{value}%、暴击伤害 +{value2}%", icon: "Sword-FA_WP_Main_Sword_019_WoodSilver",
bonuses: [
{ attr: Attrs.ap, mode: TalentValueMode.Percent, per_lv: 12 },
{ attr: Attrs.critical_damage, mode: TalentValueMode.Flat, per_lv: 12 },
],
max_lv: 5, role: HRole.Assassin,
},
9214: {
uuid: 9214, tier: TalentTier.Super, name: "穿云贯日", info: "射手攻击 +{value}%、穿透伤害 +{value2}%", icon: "Stat_RangedAttack",
bonuses: [
{ attr: Attrs.ap, mode: TalentValueMode.Percent, per_lv: 12 },
{ attr: Attrs.puncture_dmg_bonus, mode: TalentValueMode.Flat, per_lv: 8 },
],
max_lv: 5, role: HRole.Archer,
},
9215: {
uuid: 9215, tier: TalentTier.Super, name: "元素主宰", info: "法师攻击 +{value}%、冰冻率 +{value2}%", icon: "Sub_Item-FA_WP_Sub_MagicBall_001_Blue",
bonuses: [
{ attr: Attrs.ap, mode: TalentValueMode.Percent, per_lv: 12 },
{ attr: Attrs.freeze_chance, mode: TalentValueMode.Flat, per_lv: 3 },
],
max_lv: 5, role: HRole.Mage,
},
9216: {
uuid: 9216, tier: TalentTier.Super, name: "圣光普照", info: "辅助治疗量 +{value}%、施加的增益效果 +{value2}%", icon: "Stat_HolyDamage",
bonuses: [
{ attr: Attrs.heal_power, mode: TalentValueMode.Flat, per_lv: 15 },
{ attr: Attrs.buff_power, mode: TalentValueMode.Flat, per_lv: 12 },
],
max_lv: 5, role: HRole.Support,
},
};
/** 天赋升级消耗:索引为当前等级(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];
}
/** 阶位解锁门槛:前一阶累计投入 ≥ 该点数才能升级下一阶天赋 */
export const TALENT_TIER_UNLOCK_THRESHOLD = 10;
/**
* 计算指定阶位已投入的天赋点数(按升级消耗表逐级累加)。
* Why: 阶位解锁按"实际消耗点数"而非"等级总和"口径——与玩家直觉(投入了多少点)
* 及重置退款口径(resetTalents 同表对账)保持一致。
* @param levels 天赋等级表(key 为天赋 uuid,value 为当前等级)
* @param tier 目标阶位
* @returns 该阶位全部天赋已投入的点数总和
*/
export function getTalentTierInvestedPoints(levels: Record<number, number>, tier: TalentTier): number {
if (!levels) return 0;
let invested = 0;
for (const key in levels) {
const cfg = TalentSet[key];
if (!cfg || cfg.tier !== tier) continue;
for (let i = 0; i < levels[key]; i++) {
const c = getTalentUpgradeCost(i);
if (c > 0) invested += c;
}
}
return invested;
}
/**
* 判断指定阶位是否已解锁(加点入口校验用;战斗结算无需关心阶位——已投入的等级恒生效)。
* 规则:一阶恒解锁;二阶要求一阶累计投入 ≥ TALENT_TIER_UNLOCK_THRESHOLD;三阶要求二阶累计投入达标。
* @param levels 天赋等级表
* @param tier 目标阶位
*/
export function isTalentTierUnlocked(levels: Record<number, number>, tier: TalentTier): boolean {
if (tier === TalentTier.Basic) return true;
const prevTier = (tier - 1) as TalentTier;
return getTalentTierInvestedPoints(levels, prevTier) >= TALENT_TIER_UNLOCK_THRESHOLD;
}
/** 天赋属性的卡面显示名(迷你卡空间有限,用短标签) */
const TALENT_ATTR_LABELS: Partial<Record<Attrs, string>> = {
[Attrs.ap]: "攻击",
[Attrs.hp_max]: "生命",
[Attrs.defense]: "防御",
[Attrs.critical]: "暴击",
[Attrs.critical_damage]: "暴伤",
[Attrs.stun_chance]: "击晕",
[Attrs.freeze_chance]: "冰冻",
[Attrs.knockback_distance]: "击退",
[Attrs.puncture_chance]: "穿透",
[Attrs.puncture_dmg_bonus]: "穿透伤",
[Attrs.heal_power]: "治疗",
[Attrs.buff_power]: "增益",
};
/** 天赋属性的单位后缀:概率/治疗/增益类为百分点(%),防御为"点",击退距离为纯数值 */
const TALENT_ATTR_SUFFIX: Partial<Record<Attrs, string>> = {
[Attrs.defense]: "点",
[Attrs.knockback_distance]: "",
};
/**
* 生成天赋迷你卡的卡面文案(自包含:不显示满级效果)。
* 规则:已加点(lv>0)显示当前已生效数值,如「攻击 +9%」;
* 未加点(lv=0)显示每级数值引导,如「攻击 +3%/级」。
* Why: 迷你点选卡没有二级详情入口,文案必须一眼看懂当前收益。
* @param cfg 天赋配置
* @param lv 当前等级
*/
export function getTalentDisplayText(cfg: TalentConfig, lv: number): string {
const mult = lv > 0 ? lv : 1;
const parts: string[] = [];
for (const b of cfg.bonuses) {
const label = TALENT_ATTR_LABELS[b.attr] ?? String(b.attr);
const suffix = TALENT_ATTR_SUFFIX[b.attr] ?? "%";
parts.push(`${label} +${b.per_lv * mult}${suffix}`);
}
const text = parts.join("、");
return lv > 0 ? text : `${text}/级`;
}
/**
* 聚合指定职业英雄生效的全部天赋加成。
* Why: 基础/超强全局天赋全局生效,专精与超强职业天赋按 role 过滤;
* 阶位解锁只约束加点阶段,战斗加载时(Hero.load / applyHeroLevel)按当前等级直接取加成。
* @param levels 天赋等级表(key 为天赋 uuid,value 为当前等级)
* @param role 英雄职业(职业过滤依据;无 role 时只应用全局天赋)
* @returns 已解析的加成列表(等级已乘算进 value)
*/
export function getTalentBonusesForRole(levels: Record<number, number>, 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;
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<number, number>, 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;
}
}
}