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/FlySwordAtkPresenter.ts

138 lines
4.5 KiB

1 week ago
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);
}
}