refactor: 重构天赋系统与技能触发逻辑

1.  重构天赋系统架构:将天赋加成与触发修正统一收敛到 HeroAttrsComp.applyTalent
2.  新增天赋触发修正字段:talent_t_num_reduce 与 talent_extra_trigger_chance
3.  重构天赋加点逻辑:统一 1 点/级,新增软前置校验
4.  优化天赋UI组件:改为编辑器静态配置驱动,重构层位展示逻辑
5.  修复技能触发判定:在 SkillTriggerHelper 中应用天赋触发修正
6.  调整天赋配置结构:改为 5 层递进解锁,新增 L4/L5 专属触发修正配置
This commit is contained in:
panFD
2026-09-19 11:20:55 +08:00
parent f53c4293df
commit 707e25e73f
11 changed files with 12833 additions and 7245 deletions

View File

@@ -13,7 +13,6 @@ 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`)
@@ -173,16 +172,6 @@ export class Hero extends ecs.Entity {
model.skills[skill.uuid] = { ...skill, lv: Math.min(FightSet.HERO_SKILL_MAX_LV, Math.max(0, skill.lv + hero_lv - 2)), ccd: 0, overrides: resolveSkillOverrides(skill, hero_lv) };
}
// 按当前英雄等级 resolve 触发技能/光环到运行时缓存,供 SkillTriggerHelper 消费
model.runtime_call = resolveTriggerByLv(model.call, hero_lv);
model.runtime_dead = resolveTriggerByLv(model.dead, hero_lv);
model.runtime_fstart = resolveTriggerByLv(model.fstart, hero_lv);
model.runtime_fend = resolveTriggerByLv(model.fend, hero_lv);
model.runtime_atking = resolveTriggerByLv(model.atking, hero_lv);
model.runtime_atked = resolveTriggerByLv(model.atked, hero_lv);
model.runtime_field = resolveFieldByLv(model.field, hero_lv);
// 复活已纳入 dead 触发组,无需单独解析 revive
// 应用额外属性加成(成长类型每级递增 + bonus 档位累加)
// 成长增量:仅英雄生效(model.fac 已为 HERO),按 grow_type 递增曲线累计
const grow = calcGrowBonus(hero, hero_lv);
@@ -211,12 +200,23 @@ export class Hero extends ecs.Entity {
// lv12 职业专项已移除:单项属性强化统一由 bonus 配置(如 attrs: { ap: 50 })按等级注入
// 天赋加成层:全局天赋(一阶基础/三阶超强全局)全局生效,职业天赋(二阶专精/三阶超强职业)按 role 过滤(仅英雄生效,怪物跳过)
// Why: 放在成长/bonus 之后、光环乘区之后,以基础快照为基数叠加,避免与乘区叠加成乘法膨胀
// 天赋加成层:全局天赋(L1/L2)全局生效,职业天赋(L3-L5)按 role 过滤(仅英雄生效,怪物跳过)
// Why: 放在成长/bonus 之后、runtime_* resolve 之前——触发修正需先写入模型,L4/L5 才能影响阈值判定;
// 属性加成以基础快照为基数叠加,避免与光环乘区叠加成乘法膨胀
if (model.fac === FacSet.HERO) {
applyTalentBonuses(model, smc.getTalentLevels(), model.role);
model.applyTalent();
}
// 按当前英雄等级 resolve 触发技能/光环到运行时缓存,供 SkillTriggerHelper 消费
model.runtime_call = resolveTriggerByLv(model.call, hero_lv);
model.runtime_dead = resolveTriggerByLv(model.dead, hero_lv);
model.runtime_fstart = resolveTriggerByLv(model.fstart, hero_lv);
model.runtime_fend = resolveTriggerByLv(model.fend, hero_lv);
model.runtime_atking = resolveTriggerByLv(model.atking, hero_lv);
model.runtime_atked = resolveTriggerByLv(model.atked, hero_lv);
model.runtime_field = resolveFieldByLv(model.field, hero_lv);
// 复活已纳入 dead 触发组,无需单独解析 revive
// 缓存技能射程等派生数据,减少战斗帧内重复计算
model.updateSkillDistanceCache();

View File

@@ -5,6 +5,7 @@ import { FacSet, FightSet } from "../common/config/GameSet";
import { FieldSkillSet, FieldSkillType, SkillOverrides } from "../common/config/SkillSet";
import { smc } from "../common/SingletonModuleComp";
import { Attrs } from "../common/config/HeroAttrs";
import { applyTalentBonuses, getTalentExtraTriggerChance, getTalentTriggerNumReduce } from "../common/config/TalentSet";
import { FieldSkillHelper } from "./FieldSkillHelper";
import { BuffComp } from "./BuffComp";
import { BuffList, ModOp } from "../common/config/BuffSet";
@@ -101,6 +102,10 @@ export class HeroAttrsComp extends ecs.Comp {
thorns_dmg: number = 0; // 反伤伤害(固定值,受到攻击时对攻击者造成等量伤害)
thorns_rate: number = 0; // 反伤比例(百分点;反伤值 = 实际受到伤害 * thorns_rate / 100 + thorns_dmg)
// ==================== 天赋触发修正(L4/L5 写入,SkillTriggerHelper 消费) ====================
talent_t_num_reduce: number = 0; // L4:触发类技能计数阈值减少量(下限 1 由消费端 clamp)
talent_extra_trigger_chance: number = 0; // L5:触发类技能额外触发 1 次的概率(百分点)
revived_count: number = 0; // 已复活次数(从 dead 触发配置的 revive_count 限制)
death_count: number = 0; // 本回合死亡次数(用于 dead 触发统计)
invincible_time: number = 0;// 无敌时间
@@ -154,6 +159,20 @@ export class HeroAttrsComp extends ecs.Comp {
*/
initAttrs() {
}
/**
* 天赋统一应用入口:写入属性加成(L1-L3)与触发修正(L4/L5)。
* Why: 收敛到属性组件单点调用,Hero.load 与局内升级重算(applyHeroLevel)不再各自 import TalentSet;
* 触发修正必须在 runtime_* 触发缓存 resolve 之前写入,才能影响 SkillTriggerHelper 的阈值判定。
* 调用前请确保触发修正字段已归零(升级重算路径由调用方归零,保证幂等)。
*/
applyTalent(): void {
if (this.fac !== FacSet.HERO) return;
const levels = smc.getTalentLevels();
this.talent_t_num_reduce = getTalentTriggerNumReduce(levels, this.role);
this.talent_extra_trigger_chance = getTalentExtraTriggerChance(levels, this.role);
applyTalentBonuses(this, levels, this.role);
}
/*******************基础属性管理********************/
add_hp(value: number) {
@@ -765,6 +784,8 @@ export class HeroAttrsComp extends ecs.Comp {
this.buff_power = 0;
this.thorns_dmg = 0;
this.thorns_rate = 0;
this.talent_t_num_reduce = 0;
this.talent_extra_trigger_chance = 0;
this.boom = false;
// 重置技能距离缓存

View File

@@ -98,6 +98,7 @@ export class SkillTriggerHelper {
/**
* 处理攻击附加技能
* 判定逻辑:依据配置中的 t_num(触发所需普攻次数),对比当前的普攻累计次数取模判定。
* 天赋修正:L4 减少 t_num 阈值(下限 1);L5 按概率额外触发 1 次。
*/
private static handleAtking(model: HeroAttrsComp, view: HeroViewComp) {
if (!model.runtime_atking || model.runtime_atking.length === 0) return;
@@ -107,10 +108,17 @@ export class SkillTriggerHelper {
triggerCount += FieldSkillHelper.getFieldSkillTotalValue(FieldSkillType.AtkCount);
}
triggerCount = Math.max(1, Math.floor(triggerCount));
// L5 天赋:概率额外触发 1 次(与羁绊次数加成独立乘算)
if (model.talent_extra_trigger_chance > 0 && Math.random() * 100 < model.talent_extra_trigger_chance) {
triggerCount += 1;
}
// L4 天赋:减少触发所需次数(下限 1)
const tNumReduce = model.talent_t_num_reduce;
model.runtime_atking.forEach(atkConfig => {
// atk_count 代表已进行的普攻次数。当其余数刚好整除配置阈值时触发。
if (model.atk_count > 0 && model.atk_count % atkConfig.t_num === 0) {
const tNum = Math.max(1, atkConfig.t_num - tNumReduce);
if (model.atk_count > 0 && model.atk_count % tNum === 0) {
for (let i = 0; i < triggerCount; i++) {
this.dispatchSingle(atkConfig.s_uuid, model, view, SkillTriggerType.Atking, atkConfig.overrides);
}
@@ -121,6 +129,7 @@ export class SkillTriggerHelper {
/**
* 处理受击附加技能
* 判定逻辑:依据配置中的 t_num(触发所需受击次数),对比当前的受击累计次数取模判定。
* 天赋修正:L4 减少 t_num 阈值(下限 1);L5 按概率额外触发 1 次。
*/
private static handleAtked(model: HeroAttrsComp, view: HeroViewComp) {
if (!model.runtime_atked || model.runtime_atked.length === 0) return;
@@ -130,10 +139,17 @@ export class SkillTriggerHelper {
triggerCount += FieldSkillHelper.getFieldSkillTotalValue(FieldSkillType.BeAtkCount);
}
triggerCount = Math.max(1, Math.floor(triggerCount));
// L5 天赋:概率额外触发 1 次(与羁绊次数加成独立乘算)
if (model.talent_extra_trigger_chance > 0 && Math.random() * 100 < model.talent_extra_trigger_chance) {
triggerCount += 1;
}
// L4 天赋:减少触发所需次数(下限 1)
const tNumReduce = model.talent_t_num_reduce;
model.runtime_atked.forEach(atkConfig => {
// atked_count 代表已承受的受击次数。当其余数刚好整除配置阈值时触发。
if (model.atked_count > 0 && model.atked_count % atkConfig.t_num === 0) {
const tNum = Math.max(1, atkConfig.t_num - tNumReduce);
if (model.atked_count > 0 && model.atked_count % tNum === 0) {
for (let i = 0; i < triggerCount; i++) {
this.dispatchSingle(atkConfig.s_uuid, model, view, SkillTriggerType.Atked, atkConfig.overrides);
}