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.
227 lines
8.5 KiB
227 lines
8.5 KiB
|
3 weeks ago
|
import { Node, sp, Tween } from 'cc';
|
||
|
|
import { GBundle } from '../../game/ConfigRes';
|
||
|
|
import { BATTLE_HERO_BUFF_PATH } from '../../battle/res/BattleHeroResCollector';
|
||
|
|
import { BattleSpinePlayback } from '../../battle/presentation/BattleSpinePlayback';
|
||
|
|
import { schedulePresentation } from '../../battle/skill/BattlePresentationClock';
|
||
|
|
import type { BattleBuffSkillDropTarget, CocosBagBoardView } from '../../battle/view/CocosBagBoardView';
|
||
|
|
import type { TextGrid } from '../../battle/view/TextGrid';
|
||
|
|
|
||
|
|
/** 授仙箓上层预制体(字块上方) */
|
||
|
|
const RANDOM_LEVEL_UPPER_PREFAB_NAME = '授仙箓上层';
|
||
|
|
/** 授仙箓预制体(字块下方) */
|
||
|
|
const RANDOM_LEVEL_LOWER_PREFAB_NAME = '授仙箓';
|
||
|
|
/** 上层 Spine 动画 */
|
||
|
|
const RANDOM_LEVEL_UPPER_ANIM_NAME = '授仙箓';
|
||
|
|
/** 下层升级 Spine 动画 */
|
||
|
|
const RANDOM_LEVEL_UP_ANIM_NAME = '升级效果';
|
||
|
|
/** 下层降级 Spine 动画 */
|
||
|
|
const RANDOM_LEVEL_DOWN_ANIM_NAME = '降级效果';
|
||
|
|
/** 下层相对上层延迟(逻辑秒) */
|
||
|
|
const LOWER_FX_DELAY_SEC = 1;
|
||
|
|
/** 授仙箓特效相对锚点 Y 偏移 */
|
||
|
|
const RANDOM_LEVEL_FX_OFFSET_Y = 20;
|
||
|
|
|
||
|
|
import type { TextGridBattleBuffFxLayer } from '../../battle/view/TextGrid';
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 授仙箓(functiontype=7):上层播授仙箓,播完随机升/降级(合成武将升级补剩余经验);下层延迟 1s 起播升/降级效果。
|
||
|
|
*/
|
||
|
|
export class BattleGameRandomLevelFxPresenter {
|
||
|
|
private _bagView: CocosBagBoardView | null = null;
|
||
|
|
private readonly _activeFxNodes: Node[] = [];
|
||
|
|
private _playing = false;
|
||
|
|
/** 延迟下层特效世代(unbind 时递增以作废待触发回调) */
|
||
|
|
private _scheduleGen = 0;
|
||
|
|
private _lowerDelayPassed = false;
|
||
|
|
private _upperDone = false;
|
||
|
|
private _pendingLevelDelta: number | null = null;
|
||
|
|
private _pendingLowerGrid: TextGrid | null = null;
|
||
|
|
|
||
|
|
/** 绑定背包视图 */
|
||
|
|
bind(bagView: CocosBagBoardView | null, _flyLayer: Node | null | undefined): void {
|
||
|
|
this.unbind();
|
||
|
|
this._bagView = bagView;
|
||
|
|
}
|
||
|
|
|
||
|
|
/** 注销并中断全部进行中的表现 */
|
||
|
|
unbind(): void {
|
||
|
|
this._scheduleGen++;
|
||
|
|
this._abortAllFx();
|
||
|
|
this._bagView = null;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 上层播完后随机升/降级;下层在距上层开始满 1s 且已有结果后播升/降级效果。
|
||
|
|
* @param target 地图格 / 背包槽
|
||
|
|
* @param levelUpProbsWan 授仙箓概率配置
|
||
|
|
* @param onApplied 升/降级成功并进入 CD
|
||
|
|
*/
|
||
|
|
tryPlay(target: BattleBuffSkillDropTarget, levelUpProbsWan: readonly number[], onApplied: () => void): boolean {
|
||
|
|
if (this._playing) return false;
|
||
|
|
const bagView = this._bagView;
|
||
|
|
if (!bagView || levelUpProbsWan.length < 4) return false;
|
||
|
|
if (!bagView.canApplyBattleBuffRandomLevelTarget(target)) return false;
|
||
|
|
|
||
|
|
const grid = bagView.getTextGridForDropTarget(target);
|
||
|
|
if (!grid?.node?.isValid) {
|
||
|
|
console.log(`[RandomLevelFx] 目标字块视图缺失 ${target.kind}`);
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
const upperFx = this._spawnFxOnText(grid, RANDOM_LEVEL_UPPER_PREFAB_NAME, 'over');
|
||
|
|
if (!upperFx) {
|
||
|
|
console.log('[RandomLevelFx] 授仙箓上层预制体加载失败');
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
const skel = upperFx.getComponent(sp.Skeleton) ?? upperFx.getComponentInChildren(sp.Skeleton);
|
||
|
|
if (!skel?.isValid) {
|
||
|
|
console.log('[RandomLevelFx] 授仙箓上层缺少 sp.Skeleton');
|
||
|
|
this._releaseFx(upperFx);
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
this._playing = true;
|
||
|
|
this._lowerDelayPassed = false;
|
||
|
|
this._upperDone = false;
|
||
|
|
this._pendingLevelDelta = null;
|
||
|
|
this._pendingLowerGrid = grid;
|
||
|
|
|
||
|
|
const scheduleGen = ++this._scheduleGen;
|
||
|
|
schedulePresentation(LOWER_FX_DELAY_SEC, () => {
|
||
|
|
if (scheduleGen !== this._scheduleGen) return;
|
||
|
|
this._lowerDelayPassed = true;
|
||
|
|
this._tryPlayLowerFx();
|
||
|
|
});
|
||
|
|
|
||
|
|
BattleSpinePlayback.play(skel, RANDOM_LEVEL_UPPER_ANIM_NAME, {
|
||
|
|
loop: false,
|
||
|
|
onComplete: this._onUpperAnimComplete.bind(this, upperFx, skel, target, levelUpProbsWan, onApplied),
|
||
|
|
});
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
/** 上层播完:回收上层 → 随机升/降级 */
|
||
|
|
private _onUpperAnimComplete(
|
||
|
|
upperFx: Node,
|
||
|
|
skel: sp.Skeleton,
|
||
|
|
target: BattleBuffSkillDropTarget,
|
||
|
|
levelUpProbsWan: readonly number[],
|
||
|
|
onApplied: () => void,
|
||
|
|
): void {
|
||
|
|
if (skel?.isValid) BattleSpinePlayback.clearPendingComplete(skel);
|
||
|
|
this._releaseFx(upperFx);
|
||
|
|
|
||
|
|
const bagView = this._bagView;
|
||
|
|
if (!bagView) {
|
||
|
|
this._resetPlayState();
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
if (!bagView.canApplyBattleBuffRandomLevelTarget(target)) {
|
||
|
|
console.log(`[RandomLevelFx] 上层动画结束时目标已不可施放 ${target.kind}`);
|
||
|
|
this._resetPlayState();
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
const levelDelta = bagView.applyBattleBuffRandomLevelTarget(target, levelUpProbsWan);
|
||
|
|
if (levelDelta == null) {
|
||
|
|
console.log(`[RandomLevelFx] 随机升/降级失败 ${target.kind}`);
|
||
|
|
this._resetPlayState();
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
onApplied();
|
||
|
|
this._pendingLevelDelta = levelDelta;
|
||
|
|
this._upperDone = true;
|
||
|
|
this._tryPlayLowerFx();
|
||
|
|
}
|
||
|
|
|
||
|
|
/** 下层:距上层开始满 1s 且升/降级结果已出时播放 */
|
||
|
|
private _tryPlayLowerFx(): void {
|
||
|
|
if (!this._upperDone || !this._lowerDelayPassed) return;
|
||
|
|
|
||
|
|
const levelDelta = this._pendingLevelDelta;
|
||
|
|
const grid = this._pendingLowerGrid;
|
||
|
|
this._resetPlayState();
|
||
|
|
|
||
|
|
if (levelDelta == null || levelDelta === 0 || !grid?.node?.isValid) return;
|
||
|
|
|
||
|
|
const animName = levelDelta > 0 ? RANDOM_LEVEL_UP_ANIM_NAME : RANDOM_LEVEL_DOWN_ANIM_NAME;
|
||
|
|
const lowerFx = this._spawnFxOnText(grid, RANDOM_LEVEL_LOWER_PREFAB_NAME, 'under');
|
||
|
|
if (!lowerFx) {
|
||
|
|
console.log('[RandomLevelFx] 授仙箓预制体加载失败');
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
const skel = lowerFx.getComponent(sp.Skeleton) ?? lowerFx.getComponentInChildren(sp.Skeleton);
|
||
|
|
if (!skel?.isValid) {
|
||
|
|
console.log('[RandomLevelFx] 授仙箓缺少 sp.Skeleton');
|
||
|
|
this._releaseFx(lowerFx);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
BattleSpinePlayback.play(skel, animName, {
|
||
|
|
loop: false,
|
||
|
|
onComplete: this._onLowerAnimComplete.bind(this, lowerFx, skel),
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
/** 下层播完:回收 */
|
||
|
|
private _onLowerAnimComplete(fx: Node, skel: sp.Skeleton): void {
|
||
|
|
if (skel?.isValid) BattleSpinePlayback.clearPendingComplete(skel);
|
||
|
|
this._releaseFx(fx);
|
||
|
|
}
|
||
|
|
|
||
|
|
/** 挂到字块根节点并按层级插入 */
|
||
|
|
private _spawnFxOnText(grid: TextGrid, prefabName: string, layer: TextGridBattleBuffFxLayer): Node | null {
|
||
|
|
if (!grid?.node?.isValid) return null;
|
||
|
|
|
||
|
|
let node: Node | null = null;
|
||
|
|
try {
|
||
|
|
node = gg.res.getNode(BATTLE_HERO_BUFF_PATH, prefabName, GBundle.BattleHero);
|
||
|
|
} catch (e) {
|
||
|
|
console.log('[RandomLevelFx] 预制体加载异常', prefabName, String(e));
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
if (!node?.isValid) return null;
|
||
|
|
|
||
|
|
grid.attachBattleBuffFx(node, layer);
|
||
|
|
const pos = node.position;
|
||
|
|
node.setPosition(pos.x, pos.y + RANDOM_LEVEL_FX_OFFSET_Y, pos.z);
|
||
|
|
this._activeFxNodes.push(node);
|
||
|
|
return node;
|
||
|
|
}
|
||
|
|
|
||
|
|
/** 回收特效节点 */
|
||
|
|
private _releaseFx(node: Node | null): void {
|
||
|
|
if (!node?.isValid) return;
|
||
|
|
const idx = this._activeFxNodes.indexOf(node);
|
||
|
|
if (idx >= 0) this._activeFxNodes.splice(idx, 1);
|
||
|
|
Tween.stopAllByTarget(node);
|
||
|
|
if (node['resKey']) gg.res.putNode(node);
|
||
|
|
else node.destroy();
|
||
|
|
}
|
||
|
|
|
||
|
|
/** 重置单次施法中间态 */
|
||
|
|
private _resetPlayState(): void {
|
||
|
|
this._playing = false;
|
||
|
|
this._lowerDelayPassed = false;
|
||
|
|
this._upperDone = false;
|
||
|
|
this._pendingLevelDelta = null;
|
||
|
|
this._pendingLowerGrid = null;
|
||
|
|
}
|
||
|
|
|
||
|
|
/** 中断并清理全部表现节点 */
|
||
|
|
private _abortAllFx(): void {
|
||
|
|
const pending = this._activeFxNodes.slice();
|
||
|
|
this._activeFxNodes.length = 0;
|
||
|
|
for (let i = 0; i < pending.length; i++) {
|
||
|
|
const node = pending[i];
|
||
|
|
const skel = node.getComponent(sp.Skeleton) ?? node.getComponentInChildren(sp.Skeleton);
|
||
|
|
if (skel?.isValid) BattleSpinePlayback.clearPendingComplete(skel);
|
||
|
|
this._releaseFx(node);
|
||
|
|
}
|
||
|
|
this._resetPlayState();
|
||
|
|
}
|
||
|
|
}
|