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.
120 lines
4.0 KiB
120 lines
4.0 KiB
import type { AbilityContext } from 'db://assets/mx/module/battle/combat/AbilityContext';
|
|
import {
|
|
setDamagePreCalcHook,
|
|
type DamagePreCalcState,
|
|
} from 'db://assets/mx/module/battle/combat/DamagePipeline';
|
|
import { AttributeComp } from 'db://assets/mx/module/battle/ecs/Components';
|
|
import type { EntityId } from 'db://assets/mx/module/ecs';
|
|
import { getBattleBuffDigitStatHost } from './BattleBuffDigitStatHost';
|
|
import { getPassiveSkillHost } from './PassiveSkillRuntime';
|
|
|
|
/**
|
|
* 从 Ability.id 解析 Skill.id。
|
|
* @param abilityId Ability id
|
|
*/
|
|
export function parseSkillIdFromAbilityId(abilityId: string): number {
|
|
const raw = String(abilityId ?? '');
|
|
const m = /(?:ability_(?:norm|hero|weapon|monster|buffskill)_|passive_)(\d+)/.exec(raw);
|
|
if (!m) return 0;
|
|
return Math.floor(Number(m[1]) || 0);
|
|
}
|
|
|
|
/**
|
|
* 列表是否含 skillId。
|
|
* @param ids id 列表
|
|
* @param skillId Skill.id
|
|
*/
|
|
function listHasSkill(ids: readonly number[], skillId: number): boolean {
|
|
for (let i = 0; i < ids.length; i++) {
|
|
if (ids[i] === skillId) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* 目标是否处于减速(move_speed_mul<1 或 debuff_3 / slow 标签)。
|
|
* @param ctx 伤害上下文
|
|
* @param target 目标
|
|
*/
|
|
function targetIsSlowed(ctx: AbilityContext, target: EntityId): boolean {
|
|
const world = ctx.services.world;
|
|
const attr = world.getComponent(target, AttributeComp);
|
|
if (attr) {
|
|
for (let i = 0; i < attr.modifiers.length; i++) {
|
|
const m = attr.modifiers[i];
|
|
if (m.attr === 'move_speed_mul' && m.op === 'Mul' && m.value < 1) return true;
|
|
}
|
|
}
|
|
if (ctx.services.buffs.hasTag(target, 'debuff_3')) return true;
|
|
if (ctx.services.buffs.hasTag(target, 'slow')) return true;
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* 局内数字 buff 伤害预调:107/110/111/112。
|
|
*/
|
|
export class BattleBuffDamageHook {
|
|
/** 注册到 DamagePipeline */
|
|
static install(): void {
|
|
setDamagePreCalcHook((ctx, params, state) => {
|
|
BattleBuffDamageHook.apply(ctx, params, state);
|
|
});
|
|
}
|
|
|
|
/** 清除注册 */
|
|
static uninstall(): void {
|
|
setDamagePreCalcHook(null);
|
|
}
|
|
|
|
/**
|
|
* @param ctx 伤害上下文
|
|
* @param params deal 参数
|
|
* @param state 可调状态
|
|
*/
|
|
static apply(ctx: AbilityContext, params: Record<string, unknown>, state: DamagePreCalcState): void {
|
|
const flags = getBattleBuffDigitStatHost().getFlags(ctx.caster);
|
|
if (!flags) return;
|
|
const skillId = parseSkillIdFromAbilityId(ctx.abilityId);
|
|
const tableSkillId = Math.floor(Number(params.skillId) || 0);
|
|
const sid = skillId > 0 ? skillId : tableSkillId;
|
|
|
|
for (let i = 0; i < flags.skillMuls.length; i++) {
|
|
const row = flags.skillMuls[i];
|
|
if (sid > 0 && listHasSkill(row.skillIds, sid)) {
|
|
state.atk *= row.mul;
|
|
}
|
|
}
|
|
|
|
for (let i = 0; i < flags.forceCritSkills.length; i++) {
|
|
const row = flags.forceCritSkills[i];
|
|
if (sid > 0 && listHasSkill(row.skillIds, sid)) {
|
|
state.forceCrit = true;
|
|
if (row.convert) state.critRateToCritDmg = true;
|
|
}
|
|
}
|
|
|
|
if (flags.chanceMul && flags.chanceMul.wan > 0 && flags.chanceMul.mul > 1) {
|
|
const roll = ctx.rng.next() * 10000;
|
|
if (roll < flags.chanceMul.wan) {
|
|
state.atk *= flags.chanceMul.mul;
|
|
}
|
|
}
|
|
|
|
const target = ctx.targets[0];
|
|
if (!(target > 0)) return;
|
|
const mode = flags.condCrit;
|
|
if (!mode) return;
|
|
if (mode === 'always_convert') {
|
|
state.forceCrit = true;
|
|
state.critRateToCritDmg = true;
|
|
return;
|
|
}
|
|
if (mode === 'poison') {
|
|
if (getPassiveSkillHost().hasPoison(target)) state.forceCrit = true;
|
|
return;
|
|
}
|
|
if (mode === 'slow') {
|
|
if (targetIsSlowed(ctx, target)) state.forceCrit = true;
|
|
}
|
|
}
|
|
}
|
|
|