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

111 lines
4.0 KiB
TypeScript

import type { TalentModifier } from "./talentModifiers";
import { WOW335_TALENT_NODES } from "./wow335Talents.generated";
export const WOW335_DRUID_MASTER_SHAPESHIFTER_TALENT_ID = "talent-1915";
export type Wow335DruidShapeshiftForm = "bear" | "cat" | "moonkin" | "tree-of-life";
const masterShapeshifterTalent = WOW335_TALENT_NODES.find(
(talent) => talent.id === WOW335_DRUID_MASTER_SHAPESHIFTER_TALENT_ID,
) ?? (() => {
throw new Error(`Generated Druid talent ${WOW335_DRUID_MASTER_SHAPESHIFTER_TALENT_ID} is missing`);
})();
function normalizedRank(rank: number): number {
return Number.isFinite(rank)
? Math.max(0, Math.min(masterShapeshifterTalent.rankDescriptions.length, Math.trunc(rank)))
: 0;
}
/**
* The generated rank tooltip has a separate bonus for each shapeshift form.
* Return only the active form's modifier so the clauses cannot leak globally.
*/
export function wow335DruidMasterShapeshifterModifiersForForm(
rank: number,
form: Wow335DruidShapeshiftForm | null,
): readonly TalentModifier[] {
const normalized = normalizedRank(rank);
if (normalized <= 0 || !form) return [];
const description = masterShapeshifterTalent.rankDescriptions[normalized - 1] ?? "";
const clauses = {
bear: {
pattern: /Bear Form\s*-\s*Increases physical damage by\s+(\d+(?:\.\d+)?)%/i,
kind: "damage-percent" as const,
scope: "physical damage",
},
cat: {
pattern: /Cat Form\s*-\s*Increases melee critical strike chance by\s+(\d+(?:\.\d+)?)%/i,
kind: "critical-chance-percent" as const,
scope: "melee critical strike chance",
},
moonkin: {
pattern: /Moonkin Form\s*-\s*Increases Arcane and Nature damage by\s+(\d+(?:\.\d+)?)%/i,
kind: "damage-percent" as const,
scope: "Arcane and Nature damage",
},
"tree-of-life": {
pattern: /Tree of Life Form\s*-\s*Increases healing by\s+(\d+(?:\.\d+)?)%/i,
kind: "healing-percent" as const,
scope: "healing",
},
} as const;
const clause = clauses[form];
const amount = Number(description.match(clause.pattern)?.[1] ?? 0);
if (!Number.isFinite(amount) || amount <= 0) return [];
return [{
kind: clause.kind,
amount,
unit: "percent",
scope: clause.scope,
sourceTalentId: WOW335_DRUID_MASTER_SHAPESHIFTER_TALENT_ID,
sourceTalentName: "Master Shapeshifter",
rank: normalized,
description,
}];
}
/** Resolve the stock WoW shapeshift spell IDs used by the active aura runtime. */
export function wow335DruidShapeshiftFormForSpellId(spellId: number): Wow335DruidShapeshiftForm | null {
switch (spellId) {
case 5487: // Bear Form
case 9634: // Dire Bear Form
return "bear";
case 768: // Cat Form
return "cat";
case 24858: // Moonkin Form
return "moonkin";
case 33891: // Tree of Life
case 65139: // Generated Tree of Life form row
return "tree-of-life";
default:
return null;
}
}
/** Extract a form from the canonical aura IDs written by generated WoW forms. */
export function wow335DruidShapeshiftFormForAuraId(auraId: string): Wow335DruidShapeshiftForm | null {
const spellId = auraId.match(/^wow335:(\d+):/)?.[1];
return spellId === undefined ? null : wow335DruidShapeshiftFormForSpellId(Number(spellId));
}
/** Find an active generated shapeshift aura on the requested actor. */
export function wow335DruidShapeshiftFormForAuras(
auras: readonly {
readonly definition: { readonly id: string };
readonly targetId: string;
readonly expiresAt: number | null;
}[],
actorId: string,
now: number,
): Wow335DruidShapeshiftForm | null {
const matching = auras
.filter((aura) => aura.targetId === actorId && (aura.expiresAt === null || aura.expiresAt > now))
.map((aura) => wow335DruidShapeshiftFormForAuraId(aura.definition.id))
.filter((form): form is Wow335DruidShapeshiftForm => form !== null);
// Normally the aura system removes the previous form. Taking the most recent
// active form also makes stale imported form auras deterministic.
return matching.at(-1) ?? null;
}