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,168 @@
|
||||
import { beforeEach, describe, expect, it } from "vitest";
|
||||
import { targetingRulesForAbility } from "./abilityRules";
|
||||
import { clearMobRuntimeRegistry, setMobPosition } from "./mobRuntimeRegistry";
|
||||
import {
|
||||
clearPartyRuntimeRegistry,
|
||||
registerPartyRuntimePosition,
|
||||
updatePartyRuntimeTargetVisibility,
|
||||
} from "./partyRuntimeRegistry";
|
||||
import { useCombatStore } from "./combatStore";
|
||||
import { advanceDungeonPartyCombat, resetPartyRuntimeClock } from "./partyRuntime";
|
||||
import { commitPartyAbilityUse } from "./partyAbilityAi";
|
||||
import { abilitiesForCharacter } from "./abilityCatalog";
|
||||
import { ROM_ALL_ABILITIES } from "./romAbilityCatalog";
|
||||
import { createConfiguredPartyMember, usePartyStore } from "./partyStore";
|
||||
|
||||
const HEART_RUNE_ENERGY_ID = 498612;
|
||||
const BLESSING_OF_HUMILITY_ID = 490301;
|
||||
const TARGET_DISTANCES = [2, 15, 30, 45, 65, 80] as const;
|
||||
const OUTSIDE_DISTANCE = 80.1;
|
||||
const TEST_NOW = 1_000;
|
||||
const heartRuneEnergy = ROM_ALL_ABILITIES.find((ability) => ability.dbcSpellId === HEART_RUNE_ENERGY_ID
|
||||
&& ability.classId === "rom-priest" && ability.secondaryClassId === "rom-champion")!;
|
||||
|
||||
function registerTarget(id: string, distance: number): void {
|
||||
useCombatStore.getState().registerMob(id, {
|
||||
name: id,
|
||||
maxHealth: 1_000_000,
|
||||
level: 1,
|
||||
xpReward: 0,
|
||||
});
|
||||
setMobPosition(id, [distance, 0, 0]);
|
||||
useCombatStore.getState().damageMob(id, 1, 100);
|
||||
}
|
||||
|
||||
function registerHeartRuneTargets(prefix: string): { insideIds: string[]; outsideId: string } {
|
||||
const insideIds = TARGET_DISTANCES.map((distance, index) => {
|
||||
const id = `${prefix}-inside-${index}`;
|
||||
registerTarget(id, distance);
|
||||
return id;
|
||||
});
|
||||
const outsideId = `${prefix}-outside`;
|
||||
registerTarget(outsideId, OUTSIDE_DISTANCE);
|
||||
return { insideIds, outsideId };
|
||||
}
|
||||
|
||||
function abilityDamageTargets(abilityId: string, sourceActorId?: string): string[] {
|
||||
return useCombatStore.getState().combatEvents
|
||||
.filter((event) => event.kind === "damage"
|
||||
&& event.abilityId === abilityId
|
||||
&& (!sourceActorId || event.sourceActorId === sourceActorId)
|
||||
&& event.school === "holy"
|
||||
&& event.effectiveAmount > 0)
|
||||
.map((event) => event.targetActorId)
|
||||
.sort();
|
||||
}
|
||||
|
||||
describe("RoM Priest/Champion Heart Rune Energy runtime", () => {
|
||||
beforeEach(() => {
|
||||
clearMobRuntimeRegistry();
|
||||
clearPartyRuntimeRegistry();
|
||||
resetPartyRuntimeClock();
|
||||
useCombatStore.getState().initializeCharacter({
|
||||
classId: "rom-priest",
|
||||
secondaryClassId: "rom-champion",
|
||||
raceId: "rom-human",
|
||||
level: 80,
|
||||
role: "damage",
|
||||
});
|
||||
usePartyStore.getState().initializeParty("rom-priest-heart-rune-energy", 80,
|
||||
useCombatStore.getState().health, "damage", 2, "rom");
|
||||
});
|
||||
|
||||
it("executes the caster-centered 80-radius Light hit for every catalog-eligible target and reduces Blessing cooldown", () => {
|
||||
const { insideIds, outsideId } = registerHeartRuneTargets("heart-rune-player");
|
||||
const blessing = useCombatStore.getState().abilities.find((ability) => (
|
||||
ability.dbcSpellId === BLESSING_OF_HUMILITY_ID
|
||||
))!;
|
||||
useCombatStore.setState((state) => ({
|
||||
resourcePools: { ...state.resourcePools, rage: 50 },
|
||||
cooldowns: { ...state.cooldowns, [blessing.id]: TEST_NOW + 60_000 },
|
||||
}));
|
||||
useCombatStore.getState().selectMob(insideIds[0]!);
|
||||
|
||||
const result = useCombatStore.getState().castAbility(heartRuneEnergy.id, undefined, TEST_NOW);
|
||||
|
||||
expect(result).toMatchObject({ ok: true });
|
||||
expect(useCombatStore.getState().resourcePools.rage).toBe(35);
|
||||
expect(useCombatStore.getState().cooldowns[heartRuneEnergy.id]).toBe(TEST_NOW + 12_000);
|
||||
expect(useCombatStore.getState().cooldowns[blessing.id]).toBe(TEST_NOW + 45_000);
|
||||
expect(abilityDamageTargets(heartRuneEnergy.id)).toEqual(insideIds.sort());
|
||||
expect(abilityDamageTargets(heartRuneEnergy.id)).not.toContain(outsideId);
|
||||
});
|
||||
|
||||
it("executes for generated Priest/Champion companions with the same radius, no configured target cap, and cooldown rider", () => {
|
||||
const { insideIds, outsideId } = registerHeartRuneTargets("heart-rune-companion");
|
||||
const seed = "rom-priest-heart-rune-energy-companion";
|
||||
const blessing = abilitiesForCharacter("rom-priest", "rom-champion")
|
||||
.find((ability) => ability.dbcSpellId === BLESSING_OF_HUMILITY_ID)!;
|
||||
const member = createConfiguredPartyMember({
|
||||
name: "Priest Champion",
|
||||
classId: "rom-priest",
|
||||
secondaryClassId: "rom-champion",
|
||||
specialization: "Primary",
|
||||
role: "damage",
|
||||
}, 80, 0, seed);
|
||||
const fundedMember = {
|
||||
...member,
|
||||
resourcePools: { ...member.resourcePools, rage: 50 },
|
||||
cooldowns: { ...member.cooldowns, [blessing.id]: TEST_NOW + 60_000 },
|
||||
};
|
||||
const committedMember = commitPartyAbilityUse(fundedMember, heartRuneEnergy, TEST_NOW);
|
||||
expect(committedMember).not.toBeNull();
|
||||
usePartyStore.getState().initializeParty(seed, 80, useCombatStore.getState().health, "damage", 2, "rom");
|
||||
usePartyStore.getState().setCommand("attack");
|
||||
usePartyStore.setState({ members: [{
|
||||
...committedMember!,
|
||||
activeCast: {
|
||||
abilityId: heartRuneEnergy.id,
|
||||
school: "holy",
|
||||
mode: "cast",
|
||||
startedAt: TEST_NOW,
|
||||
completesAt: TEST_NOW,
|
||||
originalDurationMs: 0,
|
||||
pushbackHits: 0,
|
||||
origin: [0, 0, 0],
|
||||
targetId: insideIds[0]!,
|
||||
},
|
||||
activeCastTargetId: insideIds[0]!,
|
||||
status: "attacking",
|
||||
}] });
|
||||
registerPartyRuntimePosition(member.id, [0, 0, 0]);
|
||||
for (const id of [...insideIds, outsideId]) updatePartyRuntimeTargetVisibility(member.id, id, true);
|
||||
useCombatStore.getState().synchronizeActors();
|
||||
|
||||
advanceDungeonPartyCombat(TEST_NOW);
|
||||
|
||||
const updatedMember = usePartyStore.getState().members.find((candidate) => candidate.id === member.id)!;
|
||||
expect(updatedMember.resourcePools.rage).toBe(35);
|
||||
expect(updatedMember.cooldowns[heartRuneEnergy.id]).toBe(TEST_NOW + 12_000);
|
||||
expect(updatedMember.cooldowns[blessing.id]).toBe(TEST_NOW + 45_000);
|
||||
expect(abilityDamageTargets(heartRuneEnergy.id, member.id)).toEqual(insideIds.sort());
|
||||
expect(abilityDamageTargets(heartRuneEnergy.id, member.id)).not.toContain(outsideId);
|
||||
});
|
||||
|
||||
it("keeps the current source radius and leaves target count uncapped because CoA provides no cap", () => {
|
||||
const targeting = targetingRulesForAbility(heartRuneEnergy);
|
||||
expect(heartRuneEnergy).toMatchObject({
|
||||
target: "self",
|
||||
range: { max: 150 },
|
||||
radius: 80,
|
||||
castMode: "instant",
|
||||
castTimeMs: 0,
|
||||
cooldownMs: 12_000,
|
||||
cost: { resource: "rage", amount: 15 },
|
||||
});
|
||||
expect(targeting).toMatchObject({ shape: "circle", radius: 80 });
|
||||
expect(targeting.maximumTargets).toBeUndefined();
|
||||
expect(heartRuneEnergy.effects).toEqual(expect.arrayContaining([
|
||||
expect.objectContaining({ kind: "damage", coefficient: 9.751, school: "holy" }),
|
||||
expect.objectContaining({
|
||||
kind: "cooldown-reset",
|
||||
mode: "reduce",
|
||||
abilityName: "Blessing of Humility",
|
||||
amountMs: 15_000,
|
||||
}),
|
||||
]));
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user