import type { EntityId } from 'db://assets/mx/module/ecs'; import { SkillPresentationEnv } from '../skill/SkillPresentationEnv'; import type { Skill } from '../skill/Skill'; import type { SkillPresentationContext } from '../skill/SkillPresentationTypes'; import type { ISkillPresenter } from './SkillPresenter'; import { SkillPresenterRouter } from './SkillPresenterRouter'; import { buildShadowDealContext } from './SkillShadowContext'; /** 直伤类型 */ const DMG_TYPE = 'extra_strike'; /** Skill.skill type=11 解析结果 */ export interface ExtraStrikeParams { /** 伤害间隔:从施放起延迟多久结算这次追加伤害(秒) */ delaySec: number; /** 伤害系数(万分比→倍数) */ scale: number; /** 命中效果串(类型,参数...,同 atk_buff1 语法,如 1,5000,3 击退);空=不附加 */ hitEffect: string; } /** * 解析 `11,伤害间隔,伤害万分比,命中效果类型,对应参数`。 * 额外的一次伤害:技能施放时额外安排一次延迟伤害,打本次施放锁定的目标。 * @param raw Skill.skill */ export function parseExtraStrikeParams(raw: string): ExtraStrikeParams | null { const parts = String(raw ?? '').split(',').map((s) => String(s).trim()); if (parts[0] !== '11') return null; const delaySec = Number(parts[1]); const wan = Number(parts[2]); if (!isFinite(delaySec) || !(delaySec >= 0) || !(wan > 0)) { console.log('[ExtraStrike] skill 参数无效(伤害间隔,伤害万分比,命中效果类型,对应参数)', raw); return null; } const hitEffect = parts.slice(3).filter((s) => s.length > 0).join(','); return { delaySec, scale: wan / 10000, hitEffect }; } /** 一次追加伤害 */ interface ExtraStrikePending { caster: EntityId; /** 影子上下文(cast 被 superseded 后仍可结算) */ ctx: SkillPresentationContext; /** 施放锁定的目标快照 */ targets: EntityId[]; scale: number; hitEffect: string; /** 距结算剩余秒 */ wait: number; } let _host: ExtraStrikeHost | null = null; /** 追加伤害运行时 */ export function getExtraStrikeHost(): ExtraStrikeHost { if (!_host) _host = new ExtraStrikeHost(); return _host; } /** Skill.skill type=11:额外增加一次伤害(延迟结算,命中效果同 atk_buff 语法) */ export class ExtraStrikeHost { private readonly _pending: ExtraStrikePending[] = []; /** * 施放时安排:先播本体表现,再按伤害间隔安排一次追加伤害。 * @param ctx 表现上下文 * @param host Skill 宿主 */ start(ctx: SkillPresentationContext, host: Skill): void { const raw = String(ctx.params.extraSkill ?? ctx.params.skillRaw ?? ''); const params = parseExtraStrikeParams(raw); if (!params) { host.drainLogicPresentation(ctx); return; } // 额外效果不抢 cast 锁:本体表现由原派发路径持有 SkillPresenterRouter.instance.dispatchBase(ctx, host); const session = SkillPresentationEnv.getBattleSession(); const targets = (ctx.targets ?? []).filter((id) => !!session && session.world.isAlive(id)); if (targets.length === 0) { console.log('[ExtraStrike] 施放目标为空,跳过追加伤害'); return; } this._pending.push({ caster: ctx.caster, ctx: buildShadowDealContext(ctx, String(ctx.params.buff1 ?? ''), String(ctx.params.buff2 ?? '')), targets, scale: params.scale, hitEffect: params.hitEffect, wait: params.delaySec, }); } /** * 推进延迟结算。 * @param dt 逻辑秒 */ tick(dt: number): void { if (!(dt > 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.ctx.isAlive) continue; for (const id of p.targets) { p.ctx.dealDamage(id, { scale: p.scale, damageType: DMG_TYPE, // 命中效果类型+对应参数(空串走影子上下文默认=技能 atk_buff1/2) skillAtkBuff1: p.hitEffect, skillAtkBuff2: '', }); } } } /** 结束全部(战斗结束) */ abortAll(): void { this._pending.length = 0; } } /** Skill.skill type=11 表现入口 */ export class ExtraStrikePresenter implements ISkillPresenter { /** * 安排额外一次伤害。 * @param ctx 表现上下文 * @param host Skill 宿主 */ present(ctx: SkillPresentationContext, host: Skill): void { if (!ctx.isAlive) { ctx.finish(); return; } getExtraStrikeHost().start(ctx, host); } }