import type { World, EntityId } from 'db://assets/mx/module/ecs'; import type { EffectSpec, ConditionSpec } from '../config/BattleConfigTypes'; import type { AbilityContext } from './AbilityContext'; import type { EffectExecutor } from './EffectExecutor'; import { ConditionRegistry } from '../registry/EffectRegistry'; import { createMembershipSet, membershipAdd, membershipForEach, type MembershipSet } from 'db://assets/mx/tools/MembershipSet'; /** 表现层 deferred 的逻辑 timeline 切片 */ export interface DeferredTimelineSlice { /** 切片序号(多段命中时按序;与 applyNextEffect 顺序一致) */ index?: number; /** 原 timeline 时刻(毫秒,供调试/排序) */ at: number; effects: EffectSpec[]; conditions?: ConditionSpec[]; } interface PendingPresentation { ctx: AbilityContext; slices: DeferredTimelineSlice[]; nextIndex: number; state: 'pending' | 'finished' | 'cancelled'; } /** 施法锁变化:releaseLock 只解冻 OnTick;finish 才触发 interval=0 连发 */ export interface PresentationLockChangeMeta { flushCombo?: boolean; } /** 表现驱动 deferred 管线:hit()/finish() 推进逻辑 timeline */ export class PresentationPipeline { private readonly _byCast = new Map(); private readonly _casterCasts = new Map>(); private _onLockChange: (( caster: EntityId, locked: boolean, meta?: PresentationLockChangeMeta, ) => void) | null = null; /** 表现 finish 后触发 base_para 连发(与施法锁回调分离,避免 releaseLock 竞态丢 flush) */ private _onComboFlush: ((caster: EntityId) => void) | null = null; constructor( private readonly _world: World, private readonly _effects: EffectExecutor, ) {} /** 施法锁回调(AbilitySystem 注册,表现未 finish 前冻结可打断 OnTick) */ setLockHandler(handler: ( caster: EntityId, locked: boolean, meta?: PresentationLockChangeMeta, ) => void): void { this._onLockChange = handler; } /** 表现 finish 后触发 para_6 / atk_before 连发调度 */ setComboFlushHandler(handler: (caster: EntityId) => void): void { this._onComboFlush = handler; } register(castInstanceId: number, ctx: AbilityContext, slices: DeferredTimelineSlice[]): void { this._byCast.set(castInstanceId, { ctx, slices, nextIndex: 0, state: 'pending', }); let set = this._casterCasts.get(ctx.caster); if (!set) { set = createMembershipSet(); this._casterCasts.set(ctx.caster, set); } membershipAdd(set, castInstanceId); this._onLockChange?.(ctx.caster, true); } getPending(castInstanceId: number): Readonly<{ ctx: AbilityContext; slices: readonly DeferredTimelineSlice[]; nextIndex: number; }> | undefined { const p = this._byCast.get(castInstanceId); if (!p || p.state !== 'pending') return undefined; return p; } updateTargets(castInstanceId: number, targets: EntityId[]): void { const p = this._byCast.get(castInstanceId); if (!p || p.state !== 'pending') return; p.ctx.targets = [...targets]; } /** * 结算下一段 deferred 战斗效果(伤害、弹道等)。 * 只有显式 hit 才会造成伤害;可多次调用推进多段。 */ hit(castInstanceId: number): boolean { const p = this._byCast.get(castInstanceId); if (!p || p.state !== 'pending') return false; if (!p.ctx || !this._world.isAlive(p.ctx.caster)) { this.cancel(castInstanceId); return false; } if (p.nextIndex >= p.slices.length) { return false; } const sliceIndex = p.nextIndex; const ok = this._runSlice(p, sliceIndex); return ok; } /** @deprecated 使用 hit */ applyNextEffect(castInstanceId: number): boolean { return this.hit(castInstanceId); } /** * 仅释放施法锁,保留 pending 供后续 hit()。 * 长表现大招(如飞龙)在飞行开始即解锁,带间隔连发由定时链调度,不在此 flush。 */ releaseLock(castInstanceId: number): void { const p = this._byCast.get(castInstanceId); if (!p || p.state !== 'pending') return; if (!p.ctx) return; this._removeCastLock(castInstanceId, p.ctx.caster, false); } /** * 结束表现:丢弃未 hit 的剩余切片并解锁施法,不造成伤害。 * 碰撞类技能可只 finish、不 hit。 */ finish(castInstanceId: number): void { const p = this._byCast.get(castInstanceId); if (!p || p.state !== 'pending') return; this._complete(castInstanceId, p); } /** @deprecated 使用 finish(不再自动刷剩余伤害) */ completePresentation(castInstanceId: number): void { this.finish(castInstanceId); } cancel(castInstanceId: number): void { const p = this._byCast.get(castInstanceId); if (!p || p.state !== 'pending') return; p.state = 'cancelled'; this._removeCast(castInstanceId, p.ctx?.caster ?? 0, false); } cancelByCaster(caster: EntityId): void { const set = this._casterCasts.get(caster); if (!set) return; const ids: number[] = []; membershipForEach(set, id => ids.push(id)); for (const id of ids) this.cancel(id); } clear(): void { this._byCast.clear(); this._casterCasts.clear(); } private _runSlice(p: PendingPresentation, index: number): boolean { if (index < p.nextIndex) return false; const slice = p.slices[index]; if (slice.conditions?.length) { for (const c of slice.conditions) { if (!ConditionRegistry.evaluate(p.ctx.services, p.ctx, c.type, c.params ?? {})) { p.nextIndex = index + 1; return false; } } } this._effects.executeAll(p.ctx, slice.effects); p.nextIndex = index + 1; return true; } private _complete(castInstanceId: number, p: PendingPresentation): void { p.state = 'finished'; this._byCast.delete(castInstanceId); const caster = p.ctx?.caster ?? 0; const set = this._casterCasts.get(caster); set?.delete(castInstanceId); if (!set?.size) { this._casterCasts.delete(caster); } // flush 可能同步 register 下一段连发:须在 flush 之后重算锁, // 否则仍用「flush 前 stillLocked=false」调 _onLockChange 会清掉新施法锁, // OnTick 再叠一层 cast → beginPresentationCast 序号覆盖 → 旧 pending 永不 finish → 永久停攻 this._onComboFlush?.(caster); const after = this._casterCasts.get(caster); const stillLocked = after != null && after.size > 0; this._onLockChange?.(caster, stillLocked, { flushCombo: false }); } private _removeCast(castInstanceId: number, caster: EntityId, flushCombo: boolean): void { this._byCast.delete(castInstanceId); this._removeCastLock(castInstanceId, caster, flushCombo); } private _removeCastLock(castInstanceId: number, caster: EntityId, flushCombo: boolean): void { const set = this._casterCasts.get(caster); set?.delete(castInstanceId); if (!set?.size) { this._casterCasts.delete(caster); this._onLockChange?.(caster, false, { flushCombo }); } } }