Update Healer Man server
This commit is contained in:
1 parent
4c768fbec6
commit
b16821e92e
330 files changed
+35406
-1760
No files matched your search
@@ -0,0 +1,194 @@
|
||||
import { beforeEach, describe, expect, it } from "vitest";
|
||||
import { PLAYER_AGGRO_ID } from "./aggro";
|
||||
import { clearPartyRuntimeRegistry, registerPartyRuntimePosition } from "./partyRuntimeRegistry";
|
||||
import { advanceDungeonPartyCombat, resetPartyRuntimeClock } from "./partyRuntime";
|
||||
import { createConfiguredPartyMember, usePartyStore, type PartyMember } from "./partyStore";
|
||||
import { useCombatStore } from "./combatStore";
|
||||
import { useDungeonSimulationStore } from "./dungeonSimulationStore";
|
||||
import { ROM_ALL_ABILITIES } from "./romAbilityCatalog";
|
||||
import { ROM_DRUID_ROGUE_SINISTER_REQUEST_RESOURCES } from "./romDruidRogueCurrentCoa";
|
||||
import { abilityAtLevel, type ResourceType } from "./abilityCatalog";
|
||||
|
||||
const RESOURCE_TYPES = ROM_DRUID_ROGUE_SINISTER_REQUEST_RESOURCES as readonly ResourceType[];
|
||||
const STARTING_VALUES: Readonly<Record<ResourceType, number>> = {
|
||||
mana: 20,
|
||||
rage: 10,
|
||||
focus: 10,
|
||||
energy: 10,
|
||||
psi: 1,
|
||||
"nature-power": 1,
|
||||
health: 0,
|
||||
"runic-power": 0,
|
||||
"holy-power": 0,
|
||||
chi: 0,
|
||||
"soul-power": 0,
|
||||
};
|
||||
|
||||
function memberWithPools(seed: string, resources = RESOURCE_TYPES, index = 0): PartyMember {
|
||||
const member = createConfiguredPartyMember({
|
||||
name: "Druid/Rogue",
|
||||
classId: "rom-druid",
|
||||
secondaryClassId: "rom-rogue",
|
||||
raceId: "rom-elf",
|
||||
specialization: "Druid/Rogue",
|
||||
specializationId: "rom-druid:rom-rogue",
|
||||
talentTreeId: "rom-druid-rom-rogue",
|
||||
talentRanks: {},
|
||||
role: "damage",
|
||||
}, 80, index, seed);
|
||||
const resourcePools = Object.fromEntries(
|
||||
resources.map((resource) => [resource, STARTING_VALUES[resource]]),
|
||||
) as Partial<Record<ResourceType, number>>;
|
||||
return {
|
||||
...member,
|
||||
resource: resourcePools[member.resourceType] ?? member.resource,
|
||||
resourcePools,
|
||||
};
|
||||
}
|
||||
|
||||
function sinisterRequestId(): string {
|
||||
const request = ROM_ALL_ABILITIES.find((ability) => ability.dbcSpellId === 494023
|
||||
&& ability.classId === "rom-druid" && ability.secondaryClassId === "rom-rogue");
|
||||
if (!request) throw new Error("Druid/Rogue Sinister Request catalog row is missing.");
|
||||
return request.id;
|
||||
}
|
||||
|
||||
function assertPoolsAreFull(actorId: string, expectedResources: readonly ResourceType[]): void {
|
||||
const resources = useCombatStore.getState().actors[actorId]?.resources;
|
||||
expect(resources).toBeDefined();
|
||||
for (const resource of expectedResources) {
|
||||
expect(resources![resource]?.current).toBe(resources![resource]?.maximum);
|
||||
}
|
||||
}
|
||||
|
||||
describe("current CoA Druid/Rogue Sinister Request runtime", () => {
|
||||
beforeEach(() => {
|
||||
clearPartyRuntimeRegistry();
|
||||
resetPartyRuntimeClock();
|
||||
useDungeonSimulationStore.setState({ sessionMode: "play" });
|
||||
useCombatStore.getState().initializeCharacter({
|
||||
classId: "rom-druid",
|
||||
secondaryClassId: "rom-rogue",
|
||||
level: 80,
|
||||
xp: 0,
|
||||
});
|
||||
useCombatStore.getState().setPlayerPosition([0, 0, 0]);
|
||||
usePartyStore.getState().initializeParty("sinister-request-test", 80,
|
||||
useCombatStore.getState().health, "damage", 3, "rom");
|
||||
});
|
||||
|
||||
it("fills the player's and nearby companions' existing pools, includes radius edge, and leaves outside/absent pools alone", () => {
|
||||
const nearby = memberWithPools("sinister-player-nearby", RESOURCE_TYPES, 0);
|
||||
const edge = memberWithPools("sinister-player-edge", ["mana", "energy"], 1);
|
||||
const outside = memberWithPools("sinister-player-outside", RESOURCE_TYPES, 2);
|
||||
usePartyStore.setState({ members: [nearby, edge, outside] });
|
||||
registerPartyRuntimePosition(nearby.id, [5, 0, 0]);
|
||||
registerPartyRuntimePosition(edge.id, [15, 0, 0]);
|
||||
registerPartyRuntimePosition(outside.id, [15.01, 0, 0]);
|
||||
|
||||
useCombatStore.setState((state) => {
|
||||
const maxResourcePools = {
|
||||
...state.maxResourcePools,
|
||||
mana: 240,
|
||||
rage: 100,
|
||||
focus: 100,
|
||||
energy: 100,
|
||||
psi: 6,
|
||||
"nature-power": 15,
|
||||
};
|
||||
const resourcePools = {
|
||||
...state.resourcePools,
|
||||
mana: 1,
|
||||
rage: 1,
|
||||
focus: 1,
|
||||
energy: 1,
|
||||
psi: 1,
|
||||
"nature-power": 1,
|
||||
};
|
||||
return {
|
||||
maxResourcePools,
|
||||
resourcePools,
|
||||
maxResource: 240,
|
||||
resource: 1,
|
||||
};
|
||||
});
|
||||
useCombatStore.getState().synchronizeActors();
|
||||
|
||||
const beforeOutside = { ...useCombatStore.getState().actors[outside.id].resources };
|
||||
const abilityId = sinisterRequestId();
|
||||
const catalogRow = ROM_ALL_ABILITIES.find((ability) => ability.id === abilityId)!;
|
||||
expect(abilityAtLevel(catalogRow, 80).effects).toHaveLength(RESOURCE_TYPES.length);
|
||||
expect(useCombatStore.getState()).toMatchObject({ classId: "rom-druid", secondaryClassId: "rom-rogue" });
|
||||
const castResult = useCombatStore.getState().castAbility(abilityId, { targetActorId: null, now: 1_000 });
|
||||
expect(castResult).toMatchObject({ ok: true });
|
||||
|
||||
assertPoolsAreFull(PLAYER_AGGRO_ID, RESOURCE_TYPES);
|
||||
assertPoolsAreFull(nearby.id, RESOURCE_TYPES);
|
||||
assertPoolsAreFull(edge.id, ["mana", "energy"]);
|
||||
expect(useCombatStore.getState().actors[edge.id].resources.focus).toBeUndefined();
|
||||
for (const resource of RESOURCE_TYPES) {
|
||||
expect(useCombatStore.getState().actors[outside.id].resources[resource]).toEqual(beforeOutside[resource]);
|
||||
}
|
||||
|
||||
const events = useCombatStore.getState().combatEvents.filter((event) => (
|
||||
event.kind === "resource" && event.abilityId === abilityId
|
||||
));
|
||||
expect(events.filter((event) => event.targetActorId === PLAYER_AGGRO_ID)).toHaveLength(RESOURCE_TYPES.length);
|
||||
expect(events.filter((event) => event.targetActorId === nearby.id)).toHaveLength(RESOURCE_TYPES.length);
|
||||
expect(events.filter((event) => event.targetActorId === edge.id)).toHaveLength(2);
|
||||
expect(events.some((event) => event.targetActorId === outside.id)).toBe(false);
|
||||
expect(events.every((event) => event.sourceActorId === PLAYER_AGGRO_ID)).toBe(true);
|
||||
});
|
||||
|
||||
it("applies a companion cast to the in-range party, caps each existing pool, and attributes resource events to the caster", () => {
|
||||
const abilityId = sinisterRequestId();
|
||||
const casterBase = memberWithPools("sinister-companion-caster", RESOURCE_TYPES, 0);
|
||||
const caster = {
|
||||
...casterBase,
|
||||
activeCast: {
|
||||
abilityId,
|
||||
school: "nature" as const,
|
||||
mode: "cast" as const,
|
||||
startedAt: 0,
|
||||
completesAt: 0,
|
||||
originalDurationMs: 0,
|
||||
pushbackHits: 0,
|
||||
origin: [0, 0, 0] as const,
|
||||
targetId: casterBase.id,
|
||||
},
|
||||
activeCastTargetId: casterBase.id,
|
||||
status: "healing" as const,
|
||||
};
|
||||
const nearby = memberWithPools("sinister-companion-nearby", RESOURCE_TYPES.filter((resource) => resource !== "psi"), 1);
|
||||
const edge = memberWithPools("sinister-companion-edge", ["mana", "rage"], 2);
|
||||
const outside = memberWithPools("sinister-companion-outside", RESOURCE_TYPES, 3);
|
||||
usePartyStore.setState({ members: [caster, nearby, edge, outside] });
|
||||
registerPartyRuntimePosition(caster.id, [0, 0, 0]);
|
||||
registerPartyRuntimePosition(nearby.id, [5, 0, 0]);
|
||||
registerPartyRuntimePosition(edge.id, [15, 0, 0]);
|
||||
registerPartyRuntimePosition(outside.id, [15.01, 0, 0]);
|
||||
useCombatStore.getState().synchronizeActors();
|
||||
const beforeOutside = { ...useCombatStore.getState().actors[outside.id].resources };
|
||||
|
||||
advanceDungeonPartyCombat(1_000);
|
||||
|
||||
assertPoolsAreFull(PLAYER_AGGRO_ID, ["mana"]);
|
||||
assertPoolsAreFull(caster.id, RESOURCE_TYPES);
|
||||
assertPoolsAreFull(nearby.id, RESOURCE_TYPES.filter((resource) => resource !== "psi"));
|
||||
expect(useCombatStore.getState().actors[nearby.id].resources.psi).toBeUndefined();
|
||||
assertPoolsAreFull(edge.id, ["mana", "rage"]);
|
||||
for (const resource of RESOURCE_TYPES) {
|
||||
expect(useCombatStore.getState().actors[outside.id].resources[resource]).toEqual(beforeOutside[resource]);
|
||||
}
|
||||
|
||||
const events = useCombatStore.getState().combatEvents.filter((event) => (
|
||||
event.kind === "resource" && event.abilityId === abilityId
|
||||
));
|
||||
expect(events.filter((event) => event.targetActorId === PLAYER_AGGRO_ID)).toHaveLength(1);
|
||||
expect(events.filter((event) => event.targetActorId === caster.id)).toHaveLength(RESOURCE_TYPES.length);
|
||||
expect(events.filter((event) => event.targetActorId === nearby.id)).toHaveLength(RESOURCE_TYPES.length - 1);
|
||||
expect(events.filter((event) => event.targetActorId === edge.id)).toHaveLength(2);
|
||||
expect(events.some((event) => event.targetActorId === outside.id)).toBe(false);
|
||||
expect(events.every((event) => event.sourceActorId === caster.id)).toBe(true);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user