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

139 lines
4.9 KiB

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);
}
}