forked from phenom/healer-man
125 lines
4.3 KiB
TypeScript
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;
|
|
}
|