import type { BattleSession } from 'db://assets/mx/module/battle'; import type { AbilityContext } from 'db://assets/mx/module/battle/combat/AbilityContext'; import { setDamagePreCalcHook, type DamagePreCalcState, } from 'db://assets/mx/module/battle/combat/DamagePipeline'; 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; } /** * 局内数字 buff(含 107/113 技能倍率)+ 被动 condition7/8 result21 伤害预调。 */ 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, state: DamagePreCalcState): void { const session = ctx.services as unknown as BattleSession; const target = ctx.targets[0] ?? 0; if (target > 0) { getPassiveSkillHost().applyHitDamageMods(session, ctx.caster, target, state); } 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; } } if (!(target > 0)) return; const mode = flags.condCrit; if (!mode) return; if (mode === 'always_convert') { state.forceCrit = true; state.critRateToCritDmg = true; return; } const host = getPassiveSkillHost(); if (mode === 'poison') { if (host.hasPoison(target)) state.forceCrit = true; return; } if (mode === 'slow') { if (host.isTargetSlowed(session, target)) state.forceCrit = true; } } }