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.
 
 
 
 
AdLike2048/assets/script/battle/presentation/PagodaSmashPresenter.ts

180 lines
5.7 KiB

import { Node, Vec3, sp } from 'cc';
import type { EntityId } from 'db://assets/mx/module/ecs';
import { GBundle, GPath } from '../../game/ConfigRes';
import type { Skill } from '../skill/Skill';
import type { SkillPresentationContext } from '../skill/SkillPresentationTypes';
import { SkillPresentationEnv } from '../skill/SkillPresentationEnv';
import { InstantAtkPresenter } from './AtkTypePresenters';
import { BattleSpinePlayback } from './BattleSpinePlayback';
import type { ISkillPresenter } from './SkillPresenter';
/** battleHero/prefab/skill/宝塔 */
const PREFAB = '宝塔';
/** 预制体皮肤 */
const SKIN = '宝塔';
/** 砸地动画 */
const SMASH_ANIM = '宝塔砸地技能2';
/** 一次砸地特效 */
interface PagodaFx {
/** 宝塔节点 */
node: Node;
/** 回池剩余秒 */
wait: number;
}
let _host: PagodaSmashHost | null = null;
/** 玲珑宝塔砸地特效运行时 */
export function getPagodaSmashHost(): PagodaSmashHost {
if (!_host) _host = new PagodaSmashHost();
return _host;
}
/**
* 玲珑宝塔(Skill.id=20509)落地特效:在主目标处播砸地,动画结束回池。
* 伤害与施法锁仍由 InstantAtkPresenter(atk_type=3)负责。
*/
export class PagodaSmashHost {
private readonly _fx: PagodaFx[] = [];
/**
* 在索敌目标处生成砸地。
* @param target 主目标
* @param skillId 仅用于 warn
*/
spawn(target: EntityId, skillId: number): void {
if (!(target > 0)) {
console.log('[PagodaSmash] 无索敌目标', skillId);
return;
}
const world = SkillPresentationEnv.getTargetWorldPos(target);
if (!world) {
console.log('[PagodaSmash] 目标无世界坐标', target, skillId);
return;
}
const node = this._spawnNode(world);
if (!node) return;
const sk = node.getComponent(sp.Skeleton) ?? node.getComponentInChildren(sp.Skeleton);
if (!sk?.isValid) {
console.log('[PagodaSmash] 缺少 Skeleton', PREFAB);
this._recycle(node);
return;
}
try {
sk.setSkin(SKIN);
} catch {
console.log('[PagodaSmash] setSkin 失败', SKIN);
this._recycle(node);
return;
}
const wait = BattleSpinePlayback.resolveLogicalCompleteDelay(sk, SMASH_ANIM);
if (wait == null) {
console.log('[PagodaSmash] 动画不存在', SMASH_ANIM);
this._recycle(node);
return;
}
if (!BattleSpinePlayback.play(sk, SMASH_ANIM, { loop: false })) {
this._recycle(node);
return;
}
this._fx.push({ node, wait });
}
/**
* 推进回池。
* @param dt 逻辑秒
*/
tick(dt: number): void {
if (!(dt > 0) || this._fx.length <= 0) return;
for (let i = this._fx.length - 1; i >= 0; i--) {
const fx = this._fx[i];
if (!fx.node?.isValid) {
this._fx.splice(i, 1);
continue;
}
fx.wait -= dt;
if (fx.wait <= 0) this._end(i);
}
}
/** 取消全部砸地并回池 */
abortAll(): void {
for (let i = this._fx.length - 1; i >= 0; i--) {
this._end(i);
}
}
/**
* 生成宝塔并放到目标世界坐标。
* @param world 目标世界坐标
*/
private _spawnNode(world: Vec3): Node | null {
const parent = SkillPresentationEnv.flyLayer ?? SkillPresentationEnv.getMap()?.ensureHeroParent();
if (!parent?.isValid) {
console.log('[PagodaSmash] 无父节点');
return null;
}
let node: Node | null = null;
try {
node = gg.res.getNode(GPath.HeroSkillPrefab(), PREFAB, GBundle.BattleHero);
} catch (e) {
console.log('[PagodaSmash] 加载异常', String(e));
return null;
}
if (!node?.isValid) {
console.log('[PagodaSmash] 预制体未加载', PREFAB);
return null;
}
if (!parent.active) parent.active = true;
parent.addChild(node);
node.active = true;
node.layer = parent.layer;
node.setWorldPosition(world.x, world.y, world.z);
return node;
}
/**
* 结束并回池。
* @param index 下标
*/
private _end(index: number): void {
const fx = this._fx[index];
this._fx.splice(index, 1);
this._recycle(fx.node);
}
/**
* 回池宝塔。
* @param node 节点
*/
private _recycle(node: Node | null): void {
if (!node?.isValid) return;
const sk = node.getComponent(sp.Skeleton) ?? node.getComponentInChildren(sp.Skeleton);
BattleSpinePlayback.clearPendingComplete(sk);
BattleSpinePlayback.resetTracks(sk);
gg.res.putNode(node);
const flyLayer = SkillPresentationEnv.flyLayer;
if (flyLayer?.isValid && flyLayer.children.length === 0) flyLayer.active = false;
}
}
/** Skill.id=20509 玲珑宝塔:目标砸地 + 武器通道瞬时施法 */
export class PagodaSmashPresenter implements ISkillPresenter {
/** atk_type=3 命中与施法锁 */
private readonly _instant = new InstantAtkPresenter();
/**
* 在主目标处砸宝塔,并走范围瞬时表现。
* @param ctx 表现上下文
* @param host Skill 宿主
*/
present(ctx: SkillPresentationContext, host: Skill): void {
if (!ctx.isAlive) {
ctx.finish();
return;
}
getPagodaSmashHost().spawn(ctx.targets[0] ?? 0, Number(ctx.params.skillId ?? 0) || 0);
this._instant.present(ctx, host);
}
}