import { Node, sp, Tween } from 'cc'; import { BattlePhase } from 'db://assets/mx/module/battle'; import { GBundle } from '../../game/ConfigRes'; import { BATTLE_HERO_BUFF_PATH } from '../../battle/res/BattleHeroResCollector'; import { BattleSpinePlayback } from '../../battle/presentation/BattleSpinePlayback'; import type { CocosBagBoardView } from '../../battle/view/CocosBagBoardView'; import type { ProjectBattleHandle } from '../../battle/runtime/BattleRuntime'; import { BattleGameExtraBossSkill } from './BattleGameExtraBossSkill'; /** 万神幡预制体(battleHero/prefab/buff/万神幡) */ const EXTRA_BOSS_FX_PREFAB_NAME = '万神幡'; /** 万神幡 Spine 动画名 */ const EXTRA_BOSS_FX_ANIM_NAME = 'animaction'; /** * 万神幡(functiontype=13):拖到我方出怪口播万神幡,播完再额外召唤 Boss。 */ export class BattleGameExtraBossFxPresenter { private _battle: ProjectBattleHandle | null = null; private _bagView: CocosBagBoardView | null = null; private _playing = false; private _activeFxNode: Node | null = null; /** 是否正在播放万神幡 */ get isPlaying(): boolean { return this._playing; } /** * 绑定战斗与背包视图。 * @param battle 战斗句柄 * @param bagView 背包视图(读出怪口节点) */ bind(battle: ProjectBattleHandle | null, bagView: CocosBagBoardView | null): void { this.unbind(); this._battle = battle; this._bagView = bagView; } /** 注销并中断进行中的表现 */ unbind(): void { this._abortFx(); this._battle = null; this._bagView = null; } /** * 在出怪口播万神幡,播完召唤 Boss。 * @param spawnPort 出怪口编号 * @param onApplied 召唤成功并进入 CD */ tryPlay(spawnPort: number, onApplied: () => void): boolean { if (this._playing) return false; const battle = this._battle; const bagView = this._bagView; if (!battle || !bagView) return false; if (battle.session.phase !== BattlePhase.COMBAT) { console.log('[ExtraBossFx] 仅战斗阶段可召唤 Boss'); return false; } const ownPort = bagView.resolveOwnSpawnPort(); if (spawnPort !== ownPort) { console.log('[ExtraBossFx] 仅可施放到己方出怪口', spawnPort, ownPort); return false; } const spawnNode = bagView.getSpawnPointNode(spawnPort); if (!spawnNode?.isValid) { console.log('[ExtraBossFx] 出怪口节点缺失', spawnPort); return false; } const fxNode = this._spawnFxOnSpawnPoint(spawnNode); if (!fxNode) return false; const skel = fxNode.getComponent(sp.Skeleton) ?? fxNode.getComponentInChildren(sp.Skeleton); if (!skel?.isValid) { console.log('[ExtraBossFx] 万神幡缺少 sp.Skeleton'); this._releaseFx(fxNode); return false; } this._playing = true; const started = BattleSpinePlayback.play(skel, EXTRA_BOSS_FX_ANIM_NAME, { loop: false, onComplete: this._onAnimComplete.bind(this, fxNode, skel, onApplied), }); if (!started) { this._releaseFx(fxNode); this._playing = false; return false; } return true; } /** 动画播完:回收表现 → 召唤 Boss → CD */ private _onAnimComplete(fxNode: Node, skel: sp.Skeleton, onApplied: () => void): void { if (skel?.isValid) BattleSpinePlayback.clearPendingComplete(skel); this._releaseFx(fxNode); this._playing = false; const battle = this._battle; if (!battle) return; if (!BattleGameExtraBossSkill.trySummon(battle)) return; onApplied(); } /** 加载并挂到出怪口 */ private _spawnFxOnSpawnPoint(spawnNode: Node): Node | null { if (!spawnNode?.isValid) return null; let node: Node | null = null; try { node = gg.res.getNode(BATTLE_HERO_BUFF_PATH, EXTRA_BOSS_FX_PREFAB_NAME, GBundle.BattleHero); } catch (e) { console.log('[ExtraBossFx] 万神幡预制体加载异常', String(e)); return null; } if (!node?.isValid) { console.log('[ExtraBossFx] 万神幡预制体缺失', EXTRA_BOSS_FX_PREFAB_NAME); return null; } node.parent = spawnNode; node.setPosition(0, 0, 0); node.active = true; this._activeFxNode = node; return node; } /** 回收特效节点 */ private _releaseFx(node: Node | null): void { if (!node?.isValid) return; if (this._activeFxNode === node) this._activeFxNode = null; Tween.stopAllByTarget(node); if (node['resKey']) gg.res.putNode(node); else node.destroy(); } /** 中断并清理表现 */ private _abortFx(): void { const node = this._activeFxNode; if (node?.isValid) { const skel = node.getComponent(sp.Skeleton) ?? node.getComponentInChildren(sp.Skeleton); if (skel?.isValid) BattleSpinePlayback.clearPendingComplete(skel); this._releaseFx(node); } this._playing = false; } }