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.
106 lines
3.5 KiB
106 lines
3.5 KiB
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 + 被动 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<string, unknown>, 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;
|
|
}
|
|
}
|
|
}
|
|
|