194 lines
7.2 KiB
TypeScript
194 lines
7.2 KiB
TypeScript
import { beforeEach, describe, expect, it } from "vitest";
|
|
import { PLAYER_AGGRO_ID } from "./aggro";
|
|
import { applyAura, type AuraDefinition } from "./combatAuras";
|
|
import { useCombatStore } from "./combatStore";
|
|
import { clearMobRuntimeRegistry, setMobPosition } from "./mobRuntimeRegistry";
|
|
import { clearPartyRuntimeRegistry } from "./partyRuntimeRegistry";
|
|
import { usePartyStore } from "./partyStore";
|
|
|
|
function registerTarget(id = "target", distance = 2): void {
|
|
useCombatStore.getState().registerMob(id, { name: id, maxHealth: 1_000, xpReward: 0 });
|
|
setMobPosition(id, [distance, 0, 0]);
|
|
expect(useCombatStore.getState().selectMob(id)).toBe(true);
|
|
}
|
|
|
|
describe("named aura combat runtime", () => {
|
|
beforeEach(() => {
|
|
clearMobRuntimeRegistry();
|
|
clearPartyRuntimeRegistry();
|
|
usePartyStore.getState().initializeParty("aura-runtime", 80, 2_000);
|
|
usePartyStore.getState().selectMember(null);
|
|
useCombatStore.getState().initializeCharacter({ classId: "priest", level: 80 });
|
|
useCombatStore.getState().setPlayerPosition([0, 0, 0]);
|
|
});
|
|
|
|
it("applies and expires an imported named player buff", () => {
|
|
const now = 10_000;
|
|
const result = useCombatStore.getState().castAbility(
|
|
"wow335-priest-power-word-fortitude",
|
|
undefined,
|
|
now,
|
|
);
|
|
|
|
expect(result.ok).toBe(true);
|
|
const fortitude = useCombatStore.getState().auras.find((aura) => (
|
|
aura.definition.name.startsWith("Power Word: Fortitude (DBC")
|
|
));
|
|
expect(fortitude).toMatchObject({
|
|
sourceId: PLAYER_AGGRO_ID,
|
|
targetId: PLAYER_AGGRO_ID,
|
|
expiresAt: now + 1_800_000,
|
|
definition: {
|
|
disposition: "buff",
|
|
dispelCategory: "magic",
|
|
icon: useCombatStore.getState().abilities.find((ability) => (
|
|
ability.id === "wow335-priest-power-word-fortitude"
|
|
))?.icon,
|
|
},
|
|
});
|
|
|
|
useCombatStore.getState().tick(0, now + 1_800_000);
|
|
expect(useCombatStore.getState().auras.some((aura) => aura.definition.id === fortitude?.definition.id)).toBe(false);
|
|
});
|
|
|
|
it("consumes a named absorb before player health", () => {
|
|
const now = Date.now();
|
|
expect(useCombatStore.getState().castAbility(
|
|
"wow335-priest-power-word-shield",
|
|
undefined,
|
|
now,
|
|
).ok).toBe(true);
|
|
const beforeHealth = useCombatStore.getState().health;
|
|
const beforeAbsorb = useCombatStore.getState().auras
|
|
.flatMap((aura) => aura.absorbs)
|
|
.reduce((total, absorb) => total + absorb.remaining, 0);
|
|
expect(beforeAbsorb).toBeGreaterThan(0);
|
|
expect(useCombatStore.getState().auras.find((aura) => aura.absorbs.length)?.definition.icon).toBe(
|
|
useCombatStore.getState().abilities.find((ability) => (
|
|
ability.id === "wow335-priest-power-word-shield"
|
|
))?.icon,
|
|
);
|
|
|
|
const healthDamage = useCombatStore.getState().damagePlayer(20, "shadow", 80);
|
|
const afterAbsorb = useCombatStore.getState().auras
|
|
.flatMap((aura) => aura.absorbs)
|
|
.reduce((total, absorb) => total + absorb.remaining, 0);
|
|
expect(healthDamage).toBe(0);
|
|
expect(useCombatStore.getState().health).toBe(beforeHealth);
|
|
expect(afterAbsorb).toBeLessThan(beforeAbsorb);
|
|
});
|
|
|
|
it("cleanses a matching debuff without removing an unrelated category", () => {
|
|
useCombatStore.getState().initializeCharacter({ classId: "paladin", level: 80 });
|
|
const magic: AuraDefinition = {
|
|
id: "test-magic-debuff",
|
|
name: "Magic Debuff",
|
|
disposition: "debuff",
|
|
durationMs: 30_000,
|
|
dispelCategory: "magic",
|
|
};
|
|
const curse: AuraDefinition = {
|
|
id: "test-curse-debuff",
|
|
name: "Curse Debuff",
|
|
disposition: "debuff",
|
|
durationMs: 30_000,
|
|
dispelCategory: "curse",
|
|
};
|
|
let auras = applyAura([], { definition: magic, sourceId: "enemy", targetId: PLAYER_AGGRO_ID, now: 1_000 }).auras;
|
|
auras = applyAura(auras, { definition: curse, sourceId: "enemy", targetId: PLAYER_AGGRO_ID, now: 1_001 }).auras;
|
|
useCombatStore.setState({ auras });
|
|
|
|
expect(useCombatStore.getState().castAbility("wow335-paladin-cleanse", undefined, 2_000).ok).toBe(true);
|
|
expect(useCombatStore.getState().auras.map((aura) => aura.definition.id)).toEqual(["test-curse-debuff"]);
|
|
});
|
|
|
|
it("interrupts an active enemy cast and applies its school lockout", () => {
|
|
useCombatStore.getState().initializeCharacter({ classId: "rogue", level: 80 });
|
|
registerTarget("caster");
|
|
useCombatStore.setState((state) => ({
|
|
mobs: {
|
|
...state.mobs,
|
|
caster: {
|
|
...state.mobs.caster,
|
|
activeCast: {
|
|
attackId: "fireball",
|
|
name: "Fireball",
|
|
school: "fire",
|
|
interruptible: true,
|
|
targetActorId: PLAYER_AGGRO_ID,
|
|
startedAt: 1_000,
|
|
completesAt: 6_000,
|
|
},
|
|
},
|
|
},
|
|
}));
|
|
|
|
expect(useCombatStore.getState().castAbility("rogue-kick", undefined, 2_000).ok).toBe(true);
|
|
expect(useCombatStore.getState().mobs.caster.activeCast).toBeNull();
|
|
expect(useCombatStore.getState().mobs.caster.schoolLockedUntil.fire).toBeGreaterThanOrEqual(7_000);
|
|
});
|
|
|
|
it("executes a guaranteed aura proc as the real triggered spell", () => {
|
|
useCombatStore.getState().initializeCharacter({ classId: "warlock", level: 80 });
|
|
registerTarget("proc-target");
|
|
const procAura: AuraDefinition = {
|
|
id: "test-triggered-fear",
|
|
name: "Triggered Fear",
|
|
disposition: "buff",
|
|
durationMs: null,
|
|
procs: [{
|
|
id: "test-triggered-fear-proc",
|
|
triggers: "cast",
|
|
chance: 1,
|
|
action: { kind: "custom", id: "wow335-trigger-spell", data: { spellId: 5782 } },
|
|
}],
|
|
};
|
|
useCombatStore.setState({
|
|
auras: applyAura([], {
|
|
definition: procAura,
|
|
sourceId: PLAYER_AGGRO_ID,
|
|
targetId: PLAYER_AGGRO_ID,
|
|
now: 1_000,
|
|
}).auras,
|
|
});
|
|
|
|
expect(useCombatStore.getState().castAbility("warlock-corruption", undefined, 2_000).ok).toBe(true);
|
|
expect(useCombatStore.getState().mobs["proc-target"].statuses).toContainEqual({
|
|
kind: "fear",
|
|
sourceAbilityId: "warlock-fear",
|
|
endsAt: 8_000,
|
|
});
|
|
});
|
|
|
|
it("purges the named and legacy views of a real enemy magic buff", () => {
|
|
useCombatStore.getState().initializeCharacter({ classId: "shaman", level: 80 });
|
|
registerTarget("buffed-enemy");
|
|
const enemyBuff: AuraDefinition = {
|
|
id: "enemy:buffed-enemy:arcane-empowerment",
|
|
name: "Arcane Empowerment",
|
|
disposition: "buff",
|
|
durationMs: 30_000,
|
|
dispelCategory: "magic",
|
|
};
|
|
useCombatStore.setState((state) => ({
|
|
auras: applyAura(state.auras, {
|
|
definition: enemyBuff,
|
|
sourceId: "buffed-enemy",
|
|
targetId: "buffed-enemy",
|
|
now: 1_000,
|
|
}).auras,
|
|
mobs: {
|
|
...state.mobs,
|
|
"buffed-enemy": {
|
|
...state.mobs["buffed-enemy"],
|
|
auras: [{ id: "arcane-empowerment", sourceAbilityId: "enemy-buff", endsAt: 31_000 }],
|
|
},
|
|
},
|
|
}));
|
|
|
|
expect(useCombatStore.getState().castAbility("wow335-shaman-purge", undefined, 2_000).ok).toBe(true);
|
|
expect(useCombatStore.getState().auras.some((aura) => aura.definition.id === enemyBuff.id)).toBe(false);
|
|
expect(useCombatStore.getState().mobs["buffed-enemy"].auras).toEqual([]);
|
|
});
|
|
});
|