Update Healer Man server
This commit is contained in:
1 parent
35dfdaed61
commit
4fb7a0b9c8
161 files changed
+24618
-529
No files matched your search
@@ -0,0 +1,189 @@
|
||||
import { beforeEach, describe, expect, it } from "vitest";
|
||||
import { abilitiesForCharacter, abilityById } from "./abilityCatalog";
|
||||
import { PLAYER_AGGRO_ID } from "./aggro";
|
||||
import { useCombatStore } from "./combatStore";
|
||||
import { clearMobRuntimeRegistry, setMobPosition } from "./mobRuntimeRegistry";
|
||||
import {
|
||||
advanceDungeonPartyCombat,
|
||||
partyCastCanMoveDuringMeditationPath,
|
||||
resetPartyRuntimeClock,
|
||||
} from "./partyRuntime";
|
||||
import {
|
||||
clearPartyRuntimeRegistry,
|
||||
registerPartyRuntimePosition,
|
||||
updatePartyRuntimePosition,
|
||||
updatePartyRuntimeTargetVisibility,
|
||||
} from "./partyRuntimeRegistry";
|
||||
import { createConfiguredPartyMember, usePartyStore } from "./partyStore";
|
||||
import {
|
||||
ROM_MAGE_WARRIOR_MEDITATION_PATH_ACTIVE_TAG,
|
||||
ROM_MAGE_WARRIOR_MEDITATION_PATH_LOCKOUT_TAG,
|
||||
} from "./romMageWarriorRuntime";
|
||||
|
||||
const TARGET_ID = "meditation-path-target";
|
||||
|
||||
function initializeMageWarrior(): void {
|
||||
useCombatStore.getState().initializeCharacter({
|
||||
classId: "rom-mage",
|
||||
secondaryClassId: "rom-warrior",
|
||||
raceId: "rom-human",
|
||||
level: 80,
|
||||
});
|
||||
useCombatStore.setState((state) => ({
|
||||
resource: 1_000,
|
||||
maxResource: 1_000,
|
||||
resourcePools: { ...state.resourcePools, mana: 1_000 },
|
||||
maxResourcePools: { ...state.maxResourcePools, mana: 1_000 },
|
||||
}));
|
||||
useCombatStore.getState().setPlayerPosition([0, 0, 0]);
|
||||
}
|
||||
|
||||
function registerTarget(): void {
|
||||
useCombatStore.getState().registerMob(TARGET_ID, { name: TARGET_ID, maxHealth: 1_000_000, xpReward: 0 });
|
||||
setMobPosition(TARGET_ID, [3, 0, 0]);
|
||||
useCombatStore.getState().selectMob(TARGET_ID);
|
||||
}
|
||||
|
||||
function auraWithTag(tag: string, now: number) {
|
||||
return useCombatStore.getState().auras.find((aura) => aura.targetId === PLAYER_AGGRO_ID
|
||||
&& (aura.expiresAt === null || aura.expiresAt > now)
|
||||
&& aura.definition.utilityTags?.includes(tag));
|
||||
}
|
||||
|
||||
describe("RoM Mage/Warrior Meditation Path", () => {
|
||||
beforeEach(() => {
|
||||
clearMobRuntimeRegistry();
|
||||
clearPartyRuntimeRegistry();
|
||||
resetPartyRuntimeClock();
|
||||
initializeMageWarrior();
|
||||
});
|
||||
|
||||
it.each([
|
||||
["rom-mage-general-0", 490204, "Fireball"],
|
||||
["rom-mage-general-1", 490212, "Lightning"],
|
||||
["rom-mage-primary-4", 490242, "Discharge"],
|
||||
["rom-mage-primary-6", 490239, "Phoenix"],
|
||||
] as const)("grants 7 seconds of mobile casting after %s (%i, %s)", (abilityId, spellId, _name) => {
|
||||
registerTarget();
|
||||
const state = useCombatStore.getState();
|
||||
expect(state.abilities.some((ability) => ability.dbcSpellId === 499588 && ability.passive)).toBe(true);
|
||||
expect(abilityById(abilityId)?.dbcSpellId).toBe(spellId);
|
||||
|
||||
expect(state.castAbility(abilityId, undefined, 1_000)).toMatchObject({ ok: true });
|
||||
expect(auraWithTag(ROM_MAGE_WARRIOR_MEDITATION_PATH_ACTIVE_TAG, 1_000)?.expiresAt).toBe(8_000);
|
||||
expect(auraWithTag(ROM_MAGE_WARRIOR_MEDITATION_PATH_LOCKOUT_TAG, 1_000)?.expiresAt).toBe(16_000);
|
||||
});
|
||||
|
||||
it("does not proc on Flame and does not retrigger during the 15-second lockout", () => {
|
||||
registerTarget();
|
||||
const flame = abilityById("rom-mage-primary-1");
|
||||
const fireball = abilityById("rom-mage-general-0");
|
||||
if (!flame || !fireball) throw new Error("Missing local Mage ability rows");
|
||||
|
||||
expect(useCombatStore.getState().castAbility(flame.id, undefined, 1_000)).toMatchObject({ ok: true });
|
||||
useCombatStore.getState().tick(0.1, 4_100);
|
||||
expect(auraWithTag(ROM_MAGE_WARRIOR_MEDITATION_PATH_ACTIVE_TAG, 4_100)).toBeUndefined();
|
||||
expect(auraWithTag(ROM_MAGE_WARRIOR_MEDITATION_PATH_LOCKOUT_TAG, 4_100)).toBeUndefined();
|
||||
|
||||
expect(useCombatStore.getState().castAbility(fireball.id, undefined, 6_000)).toMatchObject({ ok: true });
|
||||
expect(auraWithTag(ROM_MAGE_WARRIOR_MEDITATION_PATH_ACTIVE_TAG, 6_000)?.expiresAt).toBe(13_000);
|
||||
expect(useCombatStore.getState().castAbility(fireball.id, undefined, 14_000)).toMatchObject({ ok: true });
|
||||
expect(auraWithTag(ROM_MAGE_WARRIOR_MEDITATION_PATH_ACTIVE_TAG, 14_000)).toBeUndefined();
|
||||
expect(auraWithTag(ROM_MAGE_WARRIOR_MEDITATION_PATH_LOCKOUT_TAG, 14_000)?.expiresAt).toBe(21_000);
|
||||
|
||||
expect(useCombatStore.getState().castAbility(fireball.id, undefined, 21_000)).toMatchObject({ ok: true });
|
||||
expect(auraWithTag(ROM_MAGE_WARRIOR_MEDITATION_PATH_ACTIVE_TAG, 21_000)?.expiresAt).toBe(28_000);
|
||||
});
|
||||
|
||||
it("lets a cast started during Meditation Path continue after movement", () => {
|
||||
registerTarget();
|
||||
const fireball = abilityById("rom-mage-general-0");
|
||||
const flame = abilityById("rom-mage-primary-1");
|
||||
if (!fireball || !flame) throw new Error("Missing local Mage ability rows");
|
||||
|
||||
expect(useCombatStore.getState().castAbility(fireball.id, undefined, 1_000)).toMatchObject({ ok: true });
|
||||
expect(auraWithTag(ROM_MAGE_WARRIOR_MEDITATION_PATH_ACTIVE_TAG, 1_000)).toBeDefined();
|
||||
expect(useCombatStore.getState().castAbility(flame.id, undefined, 2_600)).toMatchObject({ ok: true });
|
||||
expect(useCombatStore.getState().activeCast).toMatchObject({ abilityId: flame.id, canCastWhileMoving: true });
|
||||
|
||||
useCombatStore.getState().setPlayerPosition([1, 0, 0]);
|
||||
expect(useCombatStore.getState().activeCast).toMatchObject({ abilityId: flame.id, canCastWhileMoving: true });
|
||||
useCombatStore.getState().tick(0.1, 5_700);
|
||||
expect(useCombatStore.getState().activeCast).toBeNull();
|
||||
expect(useCombatStore.getState().combatEvents.some((event) => event.kind === "cast"
|
||||
&& event.abilityId === flame.id && event.outcome === "completed")).toBe(true);
|
||||
expect(auraWithTag(ROM_MAGE_WARRIOR_MEDITATION_PATH_ACTIVE_TAG, 5_700)?.expiresAt).toBe(8_000);
|
||||
});
|
||||
|
||||
it("grants the companion aura after a supported cast and preserves movement casting", () => {
|
||||
const seed = "meditation-path-party";
|
||||
const partyMember = createConfiguredPartyMember({
|
||||
name: "Meditation Mage",
|
||||
classId: "rom-mage",
|
||||
secondaryClassId: "rom-warrior",
|
||||
specialization: "Primary",
|
||||
role: "damage",
|
||||
}, 80, 0, seed);
|
||||
const fireball = abilityById("rom-mage-general-0");
|
||||
const flame = abilityById("rom-mage-primary-1");
|
||||
if (!fireball || !flame) throw new Error("Missing local Mage ability rows");
|
||||
|
||||
usePartyStore.getState().initializeParty(seed, 80, useCombatStore.getState().health, "damage", 2, "rom");
|
||||
usePartyStore.getState().setCommand("attack");
|
||||
usePartyStore.setState({ members: [{
|
||||
...partyMember,
|
||||
resource: 10_000,
|
||||
maxResource: 10_000,
|
||||
resourcePools: { ...partyMember.resourcePools, mana: 10_000 },
|
||||
activeCast: {
|
||||
abilityId: fireball.id,
|
||||
school: "fire",
|
||||
mode: "cast",
|
||||
startedAt: 500,
|
||||
completesAt: 1_000,
|
||||
originalDurationMs: 500,
|
||||
pushbackHits: 0,
|
||||
origin: [0, 0, 0],
|
||||
targetId: TARGET_ID,
|
||||
},
|
||||
activeCastTargetId: TARGET_ID,
|
||||
status: "attacking",
|
||||
}] });
|
||||
registerPartyRuntimePosition(partyMember.id, [0, 0, 0]);
|
||||
registerTarget();
|
||||
useCombatStore.getState().damageMob(TARGET_ID, 1, 900);
|
||||
updatePartyRuntimeTargetVisibility(partyMember.id, TARGET_ID, true);
|
||||
useCombatStore.getState().synchronizeActors();
|
||||
|
||||
advanceDungeonPartyCombat(1_000);
|
||||
const partyAuras = useCombatStore.getState().auras;
|
||||
expect(partyAuras.find((aura) => aura.targetId === partyMember.id
|
||||
&& aura.definition.utilityTags?.includes(ROM_MAGE_WARRIOR_MEDITATION_PATH_ACTIVE_TAG))?.expiresAt).toBe(8_000);
|
||||
expect(partyAuras.find((aura) => aura.targetId === partyMember.id
|
||||
&& aura.definition.utilityTags?.includes(ROM_MAGE_WARRIOR_MEDITATION_PATH_LOCKOUT_TAG))?.expiresAt).toBe(16_000);
|
||||
|
||||
const canMoveDuringCast = partyCastCanMoveDuringMeditationPath(partyMember, flame, 7_000);
|
||||
expect(canMoveDuringCast).toBe(true);
|
||||
usePartyStore.setState({ members: usePartyStore.getState().members.map((member) => member.id === partyMember.id
|
||||
? { ...member, activeCast: {
|
||||
abilityId: flame.id,
|
||||
school: "fire",
|
||||
mode: "cast",
|
||||
startedAt: 7_000,
|
||||
completesAt: 10_000,
|
||||
originalDurationMs: 3_000,
|
||||
pushbackHits: 0,
|
||||
canCastWhileMoving: canMoveDuringCast,
|
||||
origin: [0, 0, 0],
|
||||
targetId: TARGET_ID,
|
||||
}, activeCastTargetId: TARGET_ID, status: "attacking" }
|
||||
: member) });
|
||||
useCombatStore.getState().synchronizeActors();
|
||||
advanceDungeonPartyCombat(7_500);
|
||||
updatePartyRuntimePosition(partyMember.id, 1, 0, 0);
|
||||
advanceDungeonPartyCombat(10_000);
|
||||
expect(usePartyStore.getState().members.find((member) => member.id === partyMember.id)?.activeCast).toBeNull();
|
||||
expect(useCombatStore.getState().combatEvents.some((event) => event.sourceActorId === partyMember.id
|
||||
&& event.abilityId === flame.id && event.outcome === "hit")).toBe(true);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user