import type { EntityId } from 'db://assets/mx/module/ecs'; import type { Skill } from '../skill/Skill'; import type { SkillPresentationContext } from '../skill/SkillPresentationTypes'; import type { SkillVisualChannel } from './SkillVisualChannel'; import type { ISkillPresenter } from './SkillPresenter'; /** 待发射飞剑 */ interface FlySwordPending { /** 施法者 */ caster: EntityId; /** Ability id */ abilityId: string; /** 目标 */ target: EntityId; /** 伤害倍率 */ scale: number; /** atk_buff1 */ buff1: string; /** atk_buff2 */ buff2: string; /** 距发射(秒) */ wait: number; /** 表现通道 */ channel: SkillVisualChannel; } let _host: FlySwordHost | null = null; /** 飞剑交错发射宿主 */ export function getFlySwordHost(): FlySwordHost { if (!_host) _host = new FlySwordHost(); return _host; } /** * atk_type=10 飞剑线:按间隔依次发射,落地伤由 BowAtkHost 结算。 */ export class FlySwordHost { private readonly _pending: FlySwordPending[] = []; /** * 排队发射。 * @param channel 表现通道 * @param ctx 表现上下文 * @param scale 伤害倍率 * @param intervalSec 邻剑间隔 * @param maxCount 最多发射数 */ begin(channel: SkillVisualChannel, ctx: SkillPresentationContext, scale: number, intervalSec: number, maxCount: number): void { const buff1 = String(ctx.params.buff1 ?? ''); const buff2 = String(ctx.params.buff2 ?? ''); const targets = ctx.targets; let launched = 0; for (let i = 0; i < targets.length && launched < maxCount; i++) { const tid = targets[i]; if (!(tid > 0)) continue; this._pending.push({ caster: ctx.caster, abilityId: ctx.abilityId, target: tid, scale, buff1, buff2, wait: launched * Math.max(0, intervalSec), channel, }); launched += 1; } if (launched <= 0) { console.log('[FlySword] 无有效目标', ctx.params.skillId); return; } this.tick(0); } /** * 推进排队发射。 * @param dt 逻辑秒 */ tick(dt: number): void { if (this._pending.length <= 0) return; for (let i = this._pending.length - 1; i >= 0; i--) { const p = this._pending[i]; p.wait -= dt; if (p.wait > 0) continue; this._pending.splice(i, 1); if (!p.channel.launchFlySword(p.caster, p.abilityId, p.target, p.scale, p.buff1, p.buff2)) { console.log('[FlySword] 发射失败', p.target); } } } /** * 卸下施法者排队。 * @param caster 塔实体 */ cancelCaster(caster: EntityId): void { for (let i = this._pending.length - 1; i >= 0; i--) { if (this._pending[i].caster === caster) this._pending.splice(i, 1); } } /** 清空全部排队 */ abortAll(): void { this._pending.length = 0; } } /** * 飞剑武器主动:瞬发,实例化飞剑.prefab 飞向目标,落地结算。 */ export class FlySwordAtkPresenter implements ISkillPresenter { present(ctx: SkillPresentationContext, host: Skill): void { if (!ctx.isAlive) { ctx.finish(); return; } const raw = String(ctx.params.atkPara ?? '').split(','); const count = Math.floor(Number(String(raw[0] ?? '').trim()) || 0); const intervalSec = Number(String(raw[1] ?? '').trim()); const wan = Number(String(raw[2] ?? '').trim()); if (!(count > 0) || !Number.isFinite(intervalSec) || intervalSec < 0 || !Number.isFinite(wan) || !(wan > 0)) { console.log('[FlySwordAtkPresenter] atk_para 无效', ctx.params.skillId, ctx.params.atkPara); ctx.finish(); return; } const skillKind = String(ctx.params.skillKind ?? ''); const followWeapon = skillKind === 'weapon' || host.visualChannel.useWeapon; if (followWeapon) host.visualChannel.ensureWeapon(); const seq = host.beginPresentationCast(ctx); if (!(seq > 0)) return; host.disarmAnimComplete(); getFlySwordHost().begin(host.visualChannel, ctx, wan / 10000, intervalSec, count); ctx.releasePresentationLock(); host.finishPresentationCast(seq, false); } }