Files
healer-man/src/game/wow335DeathKnightMarkOfBloodRuntime.ts
T
2026-09-30 09:57:13 -04:00

95 lines
3.8 KiB
TypeScript

import { ActiveAura, AuraDefinition } from "./combatAuras";
import { WOW335_TALENT_NODES } from "./wow335Talents.generated";
export const WOW335_DEATH_KNIGHT_MARK_OF_BLOOD_TAG = "wow335-death-knight-mark-of-blood";
export const WOW335_DEATH_KNIGHT_MARK_OF_BLOOD_SPELL_ID = 49005;
export const WOW335_DEATH_KNIGHT_MARK_OF_BLOOD_ABILITY_ID = "wow335-death-knight-mark-of-blood";
export interface Wow335DeathKnightMarkOfBloodProfile {
readonly healingFraction: number;
readonly durationMs: number;
readonly hitCap: number;
}
export interface Wow335DeathKnightMarkOfBloodDamageResult {
readonly auras: readonly ActiveAura[];
readonly triggered: boolean;
readonly healingAmount: number;
readonly sourceActorId?: string;
readonly abilityId?: string;
readonly remainingHits?: number;
}
const MARK_OF_BLOOD_TALENT = WOW335_TALENT_NODES.find((node) => node.id === "talent-1949");
/** Read Mark of Blood's heal, duration, and charge cap from the local generated rank. */
export function wow335DeathKnightMarkOfBloodProfile(rank = 1): Wow335DeathKnightMarkOfBloodProfile | null {
const normalizedRank = Math.trunc(rank);
const description = MARK_OF_BLOOD_TALENT?.rankDescriptions[normalizedRank - 1];
const rankEffect = MARK_OF_BLOOD_TALENT?.rankEffects[normalizedRank - 1];
if (!description || !rankEffect) return null;
const healPercent = /healed for\s+(\d+(?:\.\d+)?)% of its maximum health/i.exec(description);
const durationSeconds = /Lasts for\s+(\d+(?:\.\d+)?) seconds/i.exec(description);
const healingFraction = healPercent ? Number(healPercent[1]) / 100 : 0;
const durationMs = durationSeconds ? Math.round(Number(durationSeconds[1]) * 1_000) : 0;
const hitCap = Math.floor(rankEffect.procCharges ?? 0);
if (!(healingFraction > 0) || !(durationMs > 0) || !(hitCap > 0)) return null;
return { healingFraction, durationMs, hitCap };
}
/** Attach the generated Mark of Blood hit count and trigger tag to its DBC aura. */
export function wow335DeathKnightMarkOfBloodAuraDefinition(
definition: AuraDefinition,
): AuraDefinition {
const profile = wow335DeathKnightMarkOfBloodProfile();
if (!profile) return definition;
return {
...definition,
durationMs: profile.durationMs,
maxStacks: profile.hitCap,
initialStacks: profile.hitCap,
stackBehavior: "replace",
utilityTags: [...new Set([...(definition.utilityTags ?? []), WOW335_DEATH_KNIGHT_MARK_OF_BLOOD_TAG])],
};
}
/** Consume one Mark charge for landed damage; only surviving targets can receive the heal. */
export function wow335DeathKnightMarkOfBloodOnDamage(
auras: readonly ActiveAura[],
markedEnemyId: string,
targetHealthAfterDamage: number,
targetMaxHealth: number,
now: number,
landedDamage: boolean,
): Wow335DeathKnightMarkOfBloodDamageResult {
if (!landedDamage || !(targetMaxHealth > 0)) {
return { auras, triggered: false, healingAmount: 0 };
}
const mark = auras.find((aura) => aura.targetId === markedEnemyId
&& (aura.expiresAt === null || aura.expiresAt > now)
&& aura.definition.utilityTags?.includes(WOW335_DEATH_KNIGHT_MARK_OF_BLOOD_TAG));
const profile = wow335DeathKnightMarkOfBloodProfile();
if (!mark || !profile || mark.stacks <= 0) {
return { auras, triggered: false, healingAmount: 0 };
}
const remainingHits = mark.stacks - 1;
const nextAuras = remainingHits > 0
? auras.map((aura) => aura.instanceId === mark.instanceId
? { ...aura, stacks: remainingHits }
: aura)
: auras.filter((aura) => aura.instanceId !== mark.instanceId);
return {
auras: nextAuras,
triggered: true,
healingAmount: targetHealthAfterDamage > 0
? Math.round(targetMaxHealth * profile.healingFraction)
: 0,
sourceActorId: mark.sourceId,
abilityId: WOW335_DEATH_KNIGHT_MARK_OF_BLOOD_ABILITY_ID,
remainingHits,
};
}