112 lines
4.7 KiB
TypeScript
112 lines
4.7 KiB
TypeScript
import { WOW335_TALENT_NODES } from "./wow335Talents.generated";
|
|
|
|
export const WOW335_SHAMAN_MAELSTROM_WEAPON_TALENT_ID = "talent-2057";
|
|
export const WOW335_SHAMAN_MAELSTROM_WEAPON_AURA_TAG = "wow335-shaman-maelstrom-weapon";
|
|
export const WOW335_SHAMAN_MAELSTROM_WEAPON_DURATION_MS = 30_000;
|
|
export const WOW335_SHAMAN_MAELSTROM_WEAPON_MAX_STACKS = 5;
|
|
export const WOW335_SHAMAN_MAELSTROM_WEAPON_DEFAULT_SPEED_MS = 2_400;
|
|
|
|
export const WOW335_SHAMAN_MAELSTROM_WEAPON_ELIGIBLE_SPELLS: ReadonlySet<string> = new Set([
|
|
"Lightning Bolt",
|
|
"Chain Lightning",
|
|
"Lava Burst",
|
|
"Lesser Healing Wave",
|
|
"Fireball",
|
|
"Frostbolt",
|
|
]);
|
|
|
|
const WOW335_SHAMAN_MAELSTROM_WEAPON_RANK_SPELL_IDS = [51528, 51529, 51530] as const;
|
|
|
|
// The generated rank rows omit proc rates (procChance is 0 for each rank).
|
|
// WotLKDB spells 51528, 51529, and 51530 supply 4/8/12 procs per minute.
|
|
const WOW335_SHAMAN_MAELSTROM_WEAPON_PROCS_PER_MINUTE = [4, 8, 12] as const;
|
|
|
|
const WOW335_SHAMAN_MAELSTROM_WEAPON_SOURCE = WOW335_TALENT_NODES.find((node) => (
|
|
node.id === WOW335_SHAMAN_MAELSTROM_WEAPON_TALENT_ID
|
|
));
|
|
|
|
export interface Wow335ShamanMaelstromWeaponProfile {
|
|
readonly rank: 1 | 2 | 3;
|
|
readonly spellId: number;
|
|
readonly procsPerMinute: number;
|
|
readonly procChancePerWeaponHit: number;
|
|
readonly durationMs: number;
|
|
readonly maxStacks: number;
|
|
readonly castTimeReductionPerStackPercent: number;
|
|
readonly pvpDamageReductionPerStackPercent: number;
|
|
}
|
|
|
|
/**
|
|
* Resolves the generated Maelstrom Weapon rank and the missing 3.3.5a PPM
|
|
* value. WotLKDB child spell 53817 supplies the missing 30-second aura
|
|
* duration; the generated rank tooltip remains authoritative for 20% per
|
|
* stack and the five-stack cap.
|
|
*/
|
|
export function wow335ShamanMaelstromWeaponProfile(
|
|
rank: number,
|
|
weaponSpeedMs = WOW335_SHAMAN_MAELSTROM_WEAPON_DEFAULT_SPEED_MS,
|
|
): Wow335ShamanMaelstromWeaponProfile | null {
|
|
const safeRank = Math.min(3, Math.max(0, Math.trunc(Number.isFinite(rank) ? rank : 0)));
|
|
if (safeRank === 0 || !Number.isFinite(weaponSpeedMs) || weaponSpeedMs <= 0) return null;
|
|
|
|
const rankDescription = WOW335_SHAMAN_MAELSTROM_WEAPON_SOURCE?.rankDescriptions[safeRank - 1];
|
|
const rankEffect = WOW335_SHAMAN_MAELSTROM_WEAPON_SOURCE?.rankEffects[safeRank - 1];
|
|
const expectedSpellId = WOW335_SHAMAN_MAELSTROM_WEAPON_RANK_SPELL_IDS[safeRank - 1];
|
|
if (!rankDescription || !rankEffect || rankEffect.spellId !== expectedSpellId) return null;
|
|
|
|
const castTimeReductionPerStackPercent = Number(rankDescription.match(/reduce the cast time .*? by (\d+)%/i)?.[1]);
|
|
const maxStacks = Number(rankDescription.match(/Stacks up to (\d+)/i)?.[1]);
|
|
const pvpDamageReductionPerStackPercent = Number(
|
|
rankDescription.match(/deal (\d+)% reduced damage in PvP combat per stack/i)?.[1],
|
|
);
|
|
if (!Number.isFinite(castTimeReductionPerStackPercent) || castTimeReductionPerStackPercent <= 0
|
|
|| !Number.isFinite(maxStacks) || maxStacks <= 0
|
|
|| !Number.isFinite(pvpDamageReductionPerStackPercent) || pvpDamageReductionPerStackPercent < 0) return null;
|
|
|
|
const procsPerMinute = WOW335_SHAMAN_MAELSTROM_WEAPON_PROCS_PER_MINUTE[safeRank - 1];
|
|
return {
|
|
rank: safeRank as 1 | 2 | 3,
|
|
spellId: expectedSpellId,
|
|
procsPerMinute,
|
|
// The source supplies a PPM rate, so chance scales with the attacking
|
|
// weapon's speed. This simulator models at most one proc per hit.
|
|
procChancePerWeaponHit: Math.min(1, procsPerMinute * weaponSpeedMs / 60_000),
|
|
durationMs: WOW335_SHAMAN_MAELSTROM_WEAPON_DURATION_MS,
|
|
maxStacks,
|
|
castTimeReductionPerStackPercent,
|
|
pvpDamageReductionPerStackPercent,
|
|
};
|
|
}
|
|
|
|
export function wow335ShamanMaelstromWeaponCastTimeReductionPercent(
|
|
profile: Pick<Wow335ShamanMaelstromWeaponProfile, "maxStacks" | "castTimeReductionPerStackPercent">,
|
|
stacks: number,
|
|
): number {
|
|
const safeStacks = Math.min(
|
|
profile.maxStacks,
|
|
Math.max(0, Math.trunc(Number.isFinite(stacks) ? stacks : 0)),
|
|
);
|
|
return Math.min(100, safeStacks * profile.castTimeReductionPerStackPercent);
|
|
}
|
|
|
|
/**
|
|
* Resolves the generated Maelstrom Weapon PvP damage penalty for an eligible
|
|
* damage spell. Lesser Healing Wave is eligible for the cast-time proc but is
|
|
* a heal, so this damage-only rider does not apply to it.
|
|
*/
|
|
export function wow335ShamanMaelstromWeaponPvpDamageReductionPercent(
|
|
rank: number,
|
|
consumedStacks: number,
|
|
spellName: string,
|
|
): number {
|
|
if (!WOW335_SHAMAN_MAELSTROM_WEAPON_ELIGIBLE_SPELLS.has(spellName)
|
|
|| spellName === "Lesser Healing Wave") return 0;
|
|
const profile = wow335ShamanMaelstromWeaponProfile(rank);
|
|
if (!profile) return 0;
|
|
const safeStacks = Math.min(
|
|
profile.maxStacks,
|
|
Math.max(0, Math.trunc(Number.isFinite(consumedStacks) ? consumedStacks : 0)),
|
|
);
|
|
return safeStacks * profile.pvpDamageReductionPerStackPercent;
|
|
}
|