import type { EntityId } from 'db://assets/mx/module/ecs'; import { BattleEventFactory, BattleEventType, BattleTeamId, HealthComp, MovementComp, TeamComp, isBajiaoBlowRetreating, isYanSoulClaimed, type BattleEvent, } from 'db://assets/mx/module/battle'; import { BattleBuffCarryStore } from '../../manager/battleBuff/BattleBuffCarryStore'; import { MonsterType } from '../../manager/MonsterDataManager'; import { resolveCarriedBossInterruptConfig, rollCarriedBossInterrupt, type BattleBuffBossInterruptAdjust, } from '../../manager/battleBuff/BattleBuffPassiveSkillUtil'; import { isMonsterAbilityId } from '../config/weapon1/Weapon1BattleIds'; import { isMechanismUnit } from '../mechanism/MechanismServices'; import { schedulePresentation } from '../skill/BattlePresentationClock'; import type { ProjectBattleHandle } from '../runtime/BattleRuntime'; import { getMonsterSkillHost } from './MonsterSkillHost'; import type { BattleGameBossInterruptFxPresenter } from '../../ui/BattleGame/BattleGameBossInterruptFxPresenter'; import { AI_PLAYER_ID } from '../ai/runtime/AiCampArea'; /** 打断 Boss 施法伤害类型 */ const BOSS_INTERRUPT_DAMAGE_TYPE = 'boss_interrupt'; /** 无施法者占位 */ const BOSS_INTERRUPT_CASTER = 0 as EntityId; /** 飘字展示后再击杀(逻辑秒) */ const BOSS_INTERRUPT_KILL_DELAY_SEC = 0.4; /** 单玩家打断配置 */ type PlayerInterruptCfg = { /** 防守方 playerId */ playerId: number; /** 汇总配置 */ config: BattleBuffBossInterruptAdjust; }; /** * 被动 functiontype=21:敌方 Boss 释放技能前按概率打断并造成最大生命比例伤害。 * 本机/AI 各读 CarryStore;每次 Boss 技能 AbilityCast 独立判定。 */ export class BattleBuffBossInterruptHost { private _battle: ProjectBattleHandle | null = null; private _presenter: BattleGameBossInterruptFxPresenter | null = null; private readonly _cfgs: PlayerInterruptCfg[] = []; private _onAbilityCastBound = false; /** 延迟击杀调度世代(unbind 时递增以作废) */ private _killScheduleGen = 0; /** 绑定本局与碎法帝钟表现层 */ bind(battle: ProjectBattleHandle, presenter: BattleGameBossInterruptFxPresenter | null): void { this.unbind(); this._battle = battle; this._presenter = presenter; this._rebuildConfigs(); if (this._cfgs.length <= 0) return; battle.session.events.on(BattleEventType.AbilityCast, this._onAbilityCast); this._onAbilityCastBound = true; } /** AI 挂载后按 CarryStore 重扫(保留表现层) */ refreshCarry(): void { const battle = this._battle; if (!battle) return; const presenter = this._presenter; this.bind(battle, presenter); } /** 解除绑定 */ unbind(): void { const battle = this._battle; if (battle && this._onAbilityCastBound) { battle.session.events.off(BattleEventType.AbilityCast, this._onAbilityCast); } this._onAbilityCastBound = false; this._battle = null; this._presenter = null; this._cfgs.length = 0; this._killScheduleGen++; } /** 重建各方打断配置 */ private _rebuildConfigs(): void { this._cfgs.length = 0; this._tryPush(0); this._tryPush(AI_PLAYER_ID); } /** * 若该玩家携带有效打断则记入。 * @param playerId 槽位 */ private _tryPush(playerId: number): void { const adjust = resolveCarriedBossInterruptConfig(BattleBuffCarryStore.getPassives(playerId)); if (!adjust.active || adjust.probWan <= 0 || adjust.dmgWan <= 0) return; this._cfgs.push({ playerId, config: adjust }); } /** AbilityCast:Boss 怪物技能起手时判定打断 */ private readonly _onAbilityCast = (event: BattleEvent): void => { if (event.type !== BattleEventType.AbilityCast) return; const battle = this._battle; if (!battle || this._cfgs.length <= 0) return; const ctx = event.ctx; if (!ctx) return; const caster = ctx.caster; if (!(caster > 0)) return; if (!isMonsterAbilityId(ctx.abilityId)) return; const ownerId = this._bossTargetPlayerId(battle, caster); if (ownerId < 0) return; let cfg: BattleBuffBossInterruptAdjust | null = null; for (let i = 0; i < this._cfgs.length; i++) { if (this._cfgs[i].playerId === ownerId) { cfg = this._cfgs[i].config; break; } } if (!cfg) return; if (!rollCarriedBossInterrupt(BattleBuffCarryStore.getPassives(ownerId))) return; this._presenter?.playOnBoss(caster); this._interruptCast(battle, caster); this._applyDamage(battle, caster, ownerId, cfg.dmgWan); }; /** * 取消 deferred 表现与 Host 待播,解锁施法锁。 * @param battle 战斗句柄 * @param caster Boss 实体 */ private _interruptCast(battle: ProjectBattleHandle, caster: EntityId): void { battle.session.presentation.cancelByCaster(caster); battle.session.abilities.setPresentationLocked(caster, false); getMonsterSkillHost().releasePassiveReviveHold(caster); } /** * 按最大生命万分比掉血并触发飘字;致死则延迟击杀。 * @param battle 战斗句柄 * @param entityId Boss * @param ownerPlayerId 防守方 * @param dmgWan 伤害万分比 */ private _applyDamage(battle: ProjectBattleHandle, entityId: EntityId, ownerPlayerId: number, dmgWan: number): void { const session = battle.session; const world = session.world; if (!world.isAlive(entityId)) return; if (this._bossTargetPlayerId(battle, entityId) !== ownerPlayerId) return; const hp = world.getComponent(entityId, HealthComp); if (!hp || hp.maxHp <= 0 || !(hp.hp > 0)) return; const move = world.getComponent(entityId, MovementComp); if (move?.spawnProtected || isYanSoulClaimed(move) || isBajiaoBlowRetreating(move)) return; const wan = Math.max(0, Math.floor(dmgWan)); if (wan <= 0) return; const amount = Math.max(1, Math.floor(hp.maxHp * wan / 10000)); const hpBefore = hp.hp; session.damage.apply(BOSS_INTERRUPT_CASTER, entityId, amount, BOSS_INTERRUPT_DAMAGE_TYPE, undefined, false); session.events.flush(); session.events.emit( BattleEventFactory.hitAfter( BOSS_INTERRUPT_CASTER, entityId, amount, false, undefined, BOSS_INTERRUPT_DAMAGE_TYPE, ), true, ); if (hp.hp <= 0) { this._scheduleKill(entityId, hpBefore); } } /** 飘字后再击杀,避免产桃/死亡特效抢在伤害数字前 */ private _scheduleKill(entityId: EntityId, hpBefore: number): void { const battle = this._battle; if (!battle) return; const scheduleGen = this._killScheduleGen; schedulePresentation(BOSS_INTERRUPT_KILL_DELAY_SEC, () => { if (scheduleGen !== this._killScheduleGen || !this._battle) return; const session = this._battle.session; const world = session.world; if (!world.isAlive(entityId)) return; const hpNow = world.getComponent(entityId, HealthComp); if (!hpNow || hpNow.hp > 0) return; session.lifecycle.kill(entityId, BOSS_INTERRUPT_CASTER, hpBefore); }); } /** * Boss 进攻目标玩家;非 Boss 或不可打断返回 -1。 * @param battle 战斗 * @param entityId 实体 */ private _bossTargetPlayerId(battle: ProjectBattleHandle, entityId: EntityId): number { const session = battle.session; const world = session.world; if (!world.isAlive(entityId)) return -1; if (isMechanismUnit(session, entityId)) return -1; const team = world.getComponent(entityId, TeamComp); if (!team || !BattleTeamId.isEnemyTeam(team.team)) return -1; const ownerId = Math.max(0, Math.floor(Number(team.ownerId) || 0)); const row = battle.resolveMonsterRow(entityId); if (!row) return -1; if (Math.floor(Number(row.type)) !== MonsterType.Boss) return -1; return ownerId; } } let _host: BattleBuffBossInterruptHost | null = null; /** 全局打断 Boss 施法被动 Host(单局复用) */ export function getBattleBuffBossInterruptHost(): BattleBuffBossInterruptHost { if (!_host) _host = new BattleBuffBossInterruptHost(); return _host; }