You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
152 lines
5.2 KiB
152 lines
5.2 KiB
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;
|
|
}
|
|
}
|
|
|