Files
healer-man/src/game/wow335ShamanFlurryRuntime.ts
T
2026-10-02 10:38:12 -04:00

125 lines
4.3 KiB
TypeScript

import { WOW335_TALENT_NODES } from "./wow335Talents.generated";
import { applyAura, type ActiveAura, type AuraDefinition } from "./combatAuras";
import type { WotlkAttackKind } from "./wotlkStats";
export const WOW335_SHAMAN_FLURRY_TALENT_ID = "talent-602";
export const WOW335_SHAMAN_FLURRY_AURA_TAG = "wow335-shaman-flurry";
/** Local child Spell.dbc rows 16257/16277/16278 map duration 8 to 15 seconds. */
export const WOW335_SHAMAN_FLURRY_DURATION_MS = 15_000;
const FLURRY_SOURCE = WOW335_TALENT_NODES.find((node) => (
node.id === WOW335_SHAMAN_FLURRY_TALENT_ID
));
export interface Wow335ShamanFlurryProfile {
readonly rank: number;
readonly meleeHastePercent: number;
readonly swingCharges: number;
}
export interface Wow335ShamanFlurryTriggerEvent {
readonly attackKind: WotlkAttackKind;
readonly landed: boolean;
readonly critical: boolean;
}
/** Reads Flurry's haste and swing count from the selected generated DBC tooltip rank. */
export function wow335ShamanFlurryProfileForRank(rank: number): Wow335ShamanFlurryProfile | null {
const maxRank = FLURRY_SOURCE?.rankDescriptions.length ?? 0;
const safeRank = Number.isFinite(rank) ? Math.max(0, Math.min(maxRank, Math.trunc(rank))) : 0;
if (safeRank <= 0) return null;
const description = FLURRY_SOURCE?.rankDescriptions[safeRank - 1];
const match = description?.match(
/increases your melee attack speed by (\d+(?:\.\d+)?)% for your next (\d+) swings after dealing a melee critical strike/i,
);
if (!match) return null;
const meleeHastePercent = Number(match[1]);
const swingCharges = Number(match[2]);
return Number.isFinite(meleeHastePercent) && meleeHastePercent > 0
&& Number.isSafeInteger(swingCharges) && swingCharges > 0
? { rank: safeRank, meleeHastePercent, swingCharges }
: null;
}
/** The generated tooltip triggers Flurry after a landed melee critical strike. */
export function wow335ShamanFlurryShouldProc(
rank: number,
event: Wow335ShamanFlurryTriggerEvent,
): boolean {
return wow335ShamanFlurryProfileForRank(rank) !== null
&& event.attackKind === "melee"
&& event.landed
&& event.critical;
}
export function wow335ShamanFlurryAuraDefinition(
profile: Wow335ShamanFlurryProfile,
): AuraDefinition {
return {
id: WOW335_SHAMAN_FLURRY_AURA_TAG,
name: "Flurry",
description: `Melee attack speed increased by ${profile.meleeHastePercent}% for the next ${profile.swingCharges} swings.`,
disposition: "buff",
durationMs: WOW335_SHAMAN_FLURRY_DURATION_MS,
maxStacks: profile.swingCharges,
stackBehavior: "replace",
utilityTags: [WOW335_SHAMAN_FLURRY_AURA_TAG],
modifiers: [{
kind: "stat",
stat: "melee-haste",
operation: "flat",
value: profile.meleeHastePercent,
perStack: false,
}],
};
}
/** Applies/refreshes the three swing charges after an eligible generated rank proc. */
export function wow335ShamanFlurryApplyProc(
auras: readonly ActiveAura[],
actorId: string,
rank: number,
now: number,
): readonly ActiveAura[] {
const profile = wow335ShamanFlurryProfileForRank(rank);
if (!profile) return auras;
return applyAura(auras, {
definition: wow335ShamanFlurryAuraDefinition(profile),
sourceId: actorId,
targetId: actorId,
stacks: profile.swingCharges,
now,
}).auras;
}
export function wow335ShamanFlurryIsActive(
auras: readonly ActiveAura[],
actorId: string,
now: number,
): boolean {
return auras.some((aura) => aura.sourceId === actorId && aura.targetId === actorId
&& aura.definition.utilityTags?.includes(WOW335_SHAMAN_FLURRY_AURA_TAG)
&& (aura.expiresAt === null || aura.expiresAt > now));
}
/** Consumes one active charge for a completed owner autoattack swing attempt. */
export function wow335ShamanFlurryConsumeSwingCharge(
auras: readonly ActiveAura[],
actorId: string,
now: number,
): readonly ActiveAura[] {
return auras.flatMap((aura) => {
if (aura.sourceId !== actorId || aura.targetId !== actorId
|| !aura.definition.utilityTags?.includes(WOW335_SHAMAN_FLURRY_AURA_TAG)
|| (aura.expiresAt !== null && aura.expiresAt <= now)) return [aura];
return aura.stacks > 1 ? [{ ...aura, stacks: aura.stacks - 1 }] : [];
});
}
/** Exposes the generated rank row for provenance checks and focused tests. */
export function wow335ShamanFlurryGeneratedSource() {
return FLURRY_SOURCE ?? null;
}