126 lines
5.3 KiB
TypeScript
126 lines
5.3 KiB
TypeScript
/**
|
||
* BossPhaseSystem — Boss 多阶段战斗状态机。
|
||
*
|
||
* 检测混合触发条件(血量阈值 OR 在场时长),命中后执行阶段切换:
|
||
* removes 移除旧技能 → adds 注入新技能(cd 兜底 + 资源预热)→ attr_mod 属性修正
|
||
* → 空钩子 onPhaseExit/onPhaseEnter(演出后续迭代)→ 广播 GameEvent.BossPhaseChange
|
||
*
|
||
* Why: 独立系统符合 M/V/S 分层;filter 按 BossPhaseComp 过滤,每局 Boss 唯一,开销可忽略。
|
||
* 计时在组件 elapsed(随 reset 归零),避免系统级计数在 per-entity update 中重复累加。
|
||
*/
|
||
|
||
import { Prefab } from "cc";
|
||
import { ecs } from "../../../../extensions/oops-plugin-framework/assets/libs/ecs/ECS";
|
||
import { oops } from "../../../../extensions/oops-plugin-framework/assets/core/Oops";
|
||
import { smc } from "../common/SingletonModuleComp";
|
||
import { GameEvent } from "../common/config/GameEvent";
|
||
import { BossPhaseSet, PhaseSwitchDef } from "../common/config/BossPhaseSet";
|
||
import { SkillSet } from "../common/config/SkillSet";
|
||
import { mLogger } from "../common/Logger";
|
||
import { HeroAttrsComp } from "./HeroAttrsComp";
|
||
import { BossPhaseComp } from "./BossPhaseComp";
|
||
|
||
/** 阶段模块调试日志开关 */
|
||
const DEBUG = true;
|
||
|
||
/** 新注入技能缺省 CD(秒),与 Mon.ts 大招注入兜底一致 */
|
||
const DEFAULT_ADD_CD = 8;
|
||
|
||
@ecs.register('BossPhaseSystem')
|
||
export class BossPhaseSystem extends ecs.ComblockSystem implements ecs.ISystemUpdate {
|
||
|
||
filter(): ecs.IMatcher {
|
||
return ecs.allOf(BossPhaseComp, HeroAttrsComp);
|
||
}
|
||
|
||
update(e: ecs.Entity): void {
|
||
const phase = e.get(BossPhaseComp);
|
||
const model = e.get(HeroAttrsComp);
|
||
if (!phase || !model) return;
|
||
|
||
// 战斗状态守卫(与 MoveComp/SCastSystem 同款模式):暂停/未开战不计时
|
||
if (!smc.mission.play || smc.mission.pause) return;
|
||
// 死亡同帧守卫:防死尸换阶段
|
||
if (model.is_dead) return;
|
||
// P3 后不再检测(固定 3 阶段)
|
||
if (phase.index >= 2) return;
|
||
|
||
const plan = BossPhaseSet[phase.plan_id];
|
||
if (!plan) return;
|
||
|
||
phase.elapsed += this.dt;
|
||
const def = plan.switches[phase.index];
|
||
|
||
// 混合触发:任一条件满足即切换(血量实时计算,禁止缓存绝对值——hp_max 在 load 后被外部覆盖)
|
||
const hpPct = model.hp / model.hp_max * 100;
|
||
const hpHit = def.trigger.hp_pct !== undefined && hpPct <= def.trigger.hp_pct;
|
||
const timeHit = def.trigger.time !== undefined && phase.elapsed >= def.trigger.time;
|
||
if (hpHit || timeHit) {
|
||
this.executePhaseSwitch(e, def);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 执行阶段切换:技能增量改写 + 属性修正 + 钩子与事件广播
|
||
* Why 改 skills 而非施法系统过滤:与 _testSkills/RogueConfig.ults 动态注入同模式,
|
||
* SCastSystem 零改动;施法闭包捕获 SkillSet 静态配置,进行中施法不受影响
|
||
*/
|
||
private executePhaseSwitch(e: ecs.Entity, def: PhaseSwitchDef): void {
|
||
const phase = e.get(BossPhaseComp)!;
|
||
const model = e.get(HeroAttrsComp)!;
|
||
const from = phase.index;
|
||
|
||
this.onPhaseExit(e);
|
||
|
||
// 1. 移除旧技能(仅大招键;普攻首键受键序契约保护,见 BossPhaseSet 文件头注释)
|
||
if (def.removes) {
|
||
for (const s_uuid of def.removes) {
|
||
delete model.skills[s_uuid];
|
||
}
|
||
}
|
||
|
||
// 2. 注入新技能(cd 强制兜底,防 undefined → Math.min 结果 NaN 导致大招永不释放)
|
||
if (def.adds) {
|
||
for (const add of def.adds) {
|
||
if (!add || !add.s_uuid || !SkillSet[add.s_uuid]) continue;
|
||
model.skills[add.s_uuid] = {
|
||
uuid: add.s_uuid,
|
||
lv: 1,
|
||
cd: add.cd ?? DEFAULT_ADD_CD,
|
||
ccd: 0,
|
||
overrides: add.overrides,
|
||
};
|
||
// 资源预热:新技能 prefab 首次异步加载会丢一发,切换时预载(路径与 Skill.load 对齐)
|
||
oops.res.load(`game/skill/atk/${SkillSet[add.s_uuid].sp_name}`, Prefab);
|
||
}
|
||
}
|
||
|
||
// 3. 属性修正:基于当前值链式乘算(P3 基于 P2 修正后的值,配置按此口径)
|
||
const mod = def.attr_mod;
|
||
if (mod) {
|
||
if (mod.ap_pct) model.ap = Math.round(model.ap * (1 + mod.ap_pct / 100));
|
||
if (mod.defense_pct) model.defense = Math.round(model.defense * (1 + mod.defense_pct / 100));
|
||
}
|
||
|
||
phase.index++;
|
||
|
||
this.onPhaseEnter(e);
|
||
|
||
mLogger.log(DEBUG, "BossPhase", `${model.hero_name} 阶段切换 P${from + 1}→P${phase.index + 1}(方案 ${phase.plan_id})`);
|
||
|
||
// 广播阶段切换(payload 用 eid 定位实体,防监听方持有已回收实体裸引用)
|
||
oops.message.dispatchEvent(GameEvent.BossPhaseChange, {
|
||
eid: e.eid,
|
||
planId: phase.plan_id,
|
||
fromPhase: from,
|
||
toPhase: phase.index,
|
||
});
|
||
}
|
||
|
||
/** TODO: 阶段退场表现(震屏/变暗等),后续迭代实现 */
|
||
private onPhaseExit(e: ecs.Entity): void { }
|
||
|
||
/** TODO: 阶段入场表现(怒吼/无敌帧/技能横幅等),后续迭代实现 */
|
||
private onPhaseEnter(e: ecs.Entity): void { }
|
||
}
|