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.
137 lines
4.7 KiB
137 lines
4.7 KiB
|
2 weeks ago
|
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,
|
||
|
|
});
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/** 结束全部(战斗结束) */
|
||
|
|
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);
|
||
|
|
}
|
||
|
|
}
|