feat: add BossPhaseSystem with phase switch logic

This commit is contained in:
panFD
2026-10-07 15:43:17 +08:00
parent d5158cde52
commit ad50e11052
2 changed files with 126 additions and 0 deletions

View File

@@ -0,0 +1,125 @@
/**
* 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 { }
}

View File

@@ -12,6 +12,7 @@ import { SkillTriggerType, HType } from "../common/config/heroSet";
import { SkillTriggerHelper } from "./SkillTriggerHelper";
import { BossPhaseSet } from "../common/config/BossPhaseSet";
import { BossPhaseComp } from "./BossPhaseComp";
import "./BossPhaseSystem";
import { mLogger } from "../common/Logger";
/** 怪物实体:负责怪物对象池复用、属性初始化、入场动画与回收 */
@ecs.register(`Monster`)