162 lines
7.7 KiB
TypeScript
162 lines
7.7 KiB
TypeScript
import { beforeEach, describe, expect, it } from "vitest";
|
|
import { abilityAtLevel, abilityById } from "./abilityCatalog";
|
|
import { resolveStatValue, type ActiveAura } from "./combatAuras";
|
|
import { PLAYER_AURA_ENTITY_ID, useCombatStore } from "./combatStore";
|
|
import { deriveCharacterStats } from "./wotlkStats";
|
|
import { advanceDungeonPartyCombat, resetPartyRuntimeClock } from "./partyRuntime";
|
|
import { createConfiguredPartyMember, usePartyStore } from "./partyStore";
|
|
import { clearPartyRuntimeRegistry, registerPartyRuntimePosition } from "./partyRuntimeRegistry";
|
|
import { EMPTY_ITEM_STATS } from "./itemStats";
|
|
import {
|
|
WOW335_MAGE_FROST_WARDING_ARMOR_ABILITIES,
|
|
WOW335_MAGE_FROST_WARDING_ARMOR_TAG,
|
|
WOW335_MAGE_FROST_WARDING_TALENT_ID,
|
|
} from "./wow335MageFrostWardingRuntime";
|
|
|
|
const TEST_NOW = 1_000;
|
|
|
|
function isArmorOrResistanceAura(aura: ActiveAura): boolean {
|
|
return Boolean(aura.definition.modifiers?.some((modifier) => modifier.kind === "stat"
|
|
&& (modifier.stat === "armor" || modifier.stat.startsWith("resistance:"))));
|
|
}
|
|
|
|
function armorAuras(targetId: string) {
|
|
return useCombatStore.getState().auras.filter((aura) => (
|
|
aura.targetId === targetId
|
|
&& aura.definition.utilityTags?.includes(WOW335_MAGE_FROST_WARDING_ARMOR_TAG)
|
|
));
|
|
}
|
|
|
|
function baseArmor(classId: "mage", level: number, gearStats = EMPTY_ITEM_STATS): number {
|
|
return deriveCharacterStats(classId, level, gearStats).armor;
|
|
}
|
|
|
|
function runPlayerArmorCast(armor: "frost" | "ice", rank: number) {
|
|
useCombatStore.getState().initializeCharacter({ classId: "mage", level: 80, xp: 0 });
|
|
const baseAbility = abilityById(WOW335_MAGE_FROST_WARDING_ARMOR_ABILITIES[armor].abilityId);
|
|
if (!baseAbility) throw new Error(`Missing local Mage ${armor} Armor.`);
|
|
const ability = abilityAtLevel(baseAbility, 80);
|
|
useCombatStore.setState((state) => ({
|
|
talentRanks: { ...state.talentRanks, [WOW335_MAGE_FROST_WARDING_TALENT_ID]: rank },
|
|
}));
|
|
expect(useCombatStore.getState().castAbility(ability.id, undefined, TEST_NOW)).toMatchObject({ ok: true });
|
|
const auras = armorAuras(PLAYER_AURA_ENTITY_ID);
|
|
const activeAuras = useCombatStore.getState().auras.filter((aura) => aura.targetId === PLAYER_AURA_ENTITY_ID);
|
|
const sourceAuraPrefix = `wow335:${ability.dbcSpellId}:effect:`;
|
|
const sourceAuras = activeAuras.filter((aura) => aura.definition.id.startsWith(sourceAuraPrefix)
|
|
&& isArmorOrResistanceAura(aura));
|
|
const ambientAuras = activeAuras.filter((aura) => !sourceAuras.includes(aura));
|
|
const base = baseArmor("mage", 80, useCombatStore.getState().gearStats);
|
|
return {
|
|
auras,
|
|
armorValue: resolveStatValue(base, activeAuras, "armor") - resolveStatValue(base, ambientAuras, "armor"),
|
|
frostResistance: resolveStatValue(0, activeAuras, "resistance:frost")
|
|
- resolveStatValue(0, ambientAuras, "resistance:frost"),
|
|
sourceAuraCount: sourceAuras.length,
|
|
};
|
|
}
|
|
|
|
function runCompanionArmorCast(armor: "frost" | "ice", rank: number) {
|
|
useCombatStore.getState().initializeCharacter({ classId: "mage", level: 80, xp: 0 });
|
|
usePartyStore.getState().initializeParty(`frost-warding-companion-${armor}-${rank}`, 80,
|
|
useCombatStore.getState().health, "damage", 1);
|
|
const talentRanks: Readonly<Record<string, number>> = rank > 0
|
|
? { [WOW335_MAGE_FROST_WARDING_TALENT_ID]: rank } : {};
|
|
const member = createConfiguredPartyMember({
|
|
name: "Frost Warding Mage",
|
|
classId: "mage",
|
|
specialization: "Frost",
|
|
specializationId: "mage:frost",
|
|
talentTreeId: "mage-frost",
|
|
talentRanks,
|
|
role: "damage",
|
|
}, 80, 0, `frost-warding-companion-${armor}-${rank}`);
|
|
const baseAbility = abilityById(WOW335_MAGE_FROST_WARDING_ARMOR_ABILITIES[armor].abilityId);
|
|
if (!baseAbility) throw new Error(`Missing local Mage ${armor} Armor.`);
|
|
const ability = abilityAtLevel(baseAbility, member.level);
|
|
usePartyStore.setState({ members: [{
|
|
...member,
|
|
gearStats: EMPTY_ITEM_STATS,
|
|
talentRanks,
|
|
activeCast: {
|
|
abilityId: ability.id,
|
|
school: "frost",
|
|
mode: "cast",
|
|
startedAt: TEST_NOW,
|
|
completesAt: TEST_NOW,
|
|
originalDurationMs: ability.castTimeMs,
|
|
pushbackHits: 0,
|
|
origin: [0, 0, 0],
|
|
targetId: member.id,
|
|
},
|
|
activeCastTargetId: member.id,
|
|
status: "healing",
|
|
}] });
|
|
registerPartyRuntimePosition(member.id, [0, 0, 0]);
|
|
useCombatStore.getState().synchronizeActors();
|
|
advanceDungeonPartyCombat(TEST_NOW);
|
|
|
|
const auras = armorAuras(member.id);
|
|
const activeAuras = useCombatStore.getState().auras.filter((aura) => aura.targetId === member.id);
|
|
const sourceAuraPrefix = `wow335:${ability.dbcSpellId}:effect:`;
|
|
const sourceAuras = activeAuras.filter((aura) => aura.definition.id.startsWith(sourceAuraPrefix)
|
|
&& isArmorOrResistanceAura(aura));
|
|
const ambientAuras = activeAuras.filter((aura) => !sourceAuras.includes(aura));
|
|
const base = baseArmor("mage", member.level, member.gearStats);
|
|
return {
|
|
auras,
|
|
armorValue: resolveStatValue(base, activeAuras, "armor") - resolveStatValue(base, ambientAuras, "armor"),
|
|
frostResistance: resolveStatValue(0, activeAuras, "resistance:frost")
|
|
- resolveStatValue(0, ambientAuras, "resistance:frost"),
|
|
sourceAuraCount: sourceAuras.length,
|
|
};
|
|
}
|
|
|
|
describe("WoW Mage Frost Warding Frost/Ice Armor integration", () => {
|
|
beforeEach(() => {
|
|
clearPartyRuntimeRegistry();
|
|
resetPartyRuntimeClock();
|
|
useCombatStore.getState().initializeCharacter({ classId: "mage", level: 80, xp: 0 });
|
|
usePartyStore.getState().initializeParty("frost-warding-armor-reset", 80,
|
|
useCombatStore.getState().health, "damage", 1);
|
|
});
|
|
|
|
it.each([
|
|
{ armor: "frost" as const, rank: 0, expectedArmor: 200, expectedResistance: 0 },
|
|
{ armor: "frost" as const, rank: 1, expectedArmor: 250, expectedResistance: 0 },
|
|
{ armor: "frost" as const, rank: 2, expectedArmor: 300, expectedResistance: 0 },
|
|
{ armor: "ice" as const, rank: 0, expectedArmor: 930, expectedResistance: 40 },
|
|
{ armor: "ice" as const, rank: 1, expectedArmor: 1_162.5, expectedResistance: 50 },
|
|
{ armor: "ice" as const, rank: 2, expectedArmor: 1_395, expectedResistance: 60 },
|
|
])("casts player $armor Armor with rank $rank", ({ armor, rank, expectedArmor, expectedResistance }) => {
|
|
const result = runPlayerArmorCast(armor, rank);
|
|
expect(result.armorValue).toBe(expectedArmor);
|
|
expect(result.frostResistance).toBe(expectedResistance);
|
|
expect(result.auras.length).toBe(rank > 0 ? (armor === "ice" ? 2 : 1) : 0);
|
|
expect(result.sourceAuraCount).toBe(armor === "ice" ? 2 : 1);
|
|
});
|
|
|
|
it.each([
|
|
{ armor: "frost" as const, rank: 0, expectedArmor: 200, expectedResistance: 0 },
|
|
{ armor: "frost" as const, rank: 1, expectedArmor: 250, expectedResistance: 0 },
|
|
{ armor: "frost" as const, rank: 2, expectedArmor: 300, expectedResistance: 0 },
|
|
{ armor: "ice" as const, rank: 0, expectedArmor: 930, expectedResistance: 40 },
|
|
{ armor: "ice" as const, rank: 1, expectedArmor: 1_162.5, expectedResistance: 50 },
|
|
{ armor: "ice" as const, rank: 2, expectedArmor: 1_395, expectedResistance: 60 },
|
|
])("casts companion $armor Armor with rank $rank", ({ armor, rank, expectedArmor, expectedResistance }) => {
|
|
const result = runCompanionArmorCast(armor, rank);
|
|
expect(result.armorValue).toBe(expectedArmor);
|
|
expect(result.frostResistance).toBe(expectedResistance);
|
|
expect(result.auras.length).toBe(rank > 0 ? (armor === "ice" ? 2 : 1) : 0);
|
|
expect(result.sourceAuraCount).toBe(armor === "ice" ? 2 : 1);
|
|
});
|
|
|
|
it("does not apply the conditional armor bonus before a Mage Frost/Ice Armor aura exists", () => {
|
|
useCombatStore.getState().initializeCharacter({ classId: "mage", level: 80, xp: 0 });
|
|
useCombatStore.setState((state) => ({
|
|
talentRanks: { ...state.talentRanks, [WOW335_MAGE_FROST_WARDING_TALENT_ID]: 2 },
|
|
}));
|
|
expect(armorAuras(PLAYER_AURA_ENTITY_ID)).toEqual([]);
|
|
});
|
|
});
|