forked from phenom/healer-man
Release Healer Man 0.1.55
This commit is contained in:
1 parent
2dbb0acfc1
commit
38b3d4d160
14 files changed
+279
-41
No files matched your search
@@ -22,7 +22,9 @@ import {
|
||||
import { createConfiguredPartyMember, usePartyStore } from "./partyStore";
|
||||
import { EMPTY_ITEM_STATS } from "./itemStats";
|
||||
import { subscribeCombatPresentation, type CombatPresentationEvent } from "./combatPresentation";
|
||||
import { subscribeCombatEvents, type StructuredCombatEvent } from "./combatEvents";
|
||||
import { abilitiesForClass } from "./abilityCatalog";
|
||||
import { runtimePartyTalentRanksForLevel } from "./partyTalentRuntime";
|
||||
|
||||
function registerEngagedMob(id = "pack:a", maxHealth = 200): void {
|
||||
useCombatStore.getState().registerMob(id, { name: id, maxHealth, xpReward: 0 });
|
||||
@@ -138,6 +140,101 @@ describe("party combat runtime", () => {
|
||||
expect(useCombatStore.getState().summons.filter((summon) => (
|
||||
summon.ownerActorId === shaman.id && summon.sourceAbilityId === feralSpirit.id
|
||||
))).toHaveLength(2);
|
||||
const beforeSummonAttack = useCombatStore.getState().mobs["feral-spirit-boss"].health;
|
||||
useCombatStore.getState().tick(0.1, 10_000);
|
||||
expect(useCombatStore.getState().mobs["feral-spirit-boss"].health).toBeLessThan(beforeSummonAttack);
|
||||
});
|
||||
|
||||
it("gives companion Hunter and Warlock pets a hostile target when summoned from self", () => {
|
||||
useCombatStore.getState().initializeCharacter({ classId: "mage", role: "damage", level: 80 });
|
||||
const hunter = createConfiguredPartyMember({
|
||||
name: "Beast Mastery",
|
||||
classId: "hunter",
|
||||
specialization: "Beast Mastery",
|
||||
specializationId: "hunter:beast-mastery",
|
||||
talentTreeId: "hunter-beast-mastery",
|
||||
role: "damage",
|
||||
}, 80, 0, "pet-target-runtime");
|
||||
const warlock = createConfiguredPartyMember({
|
||||
name: "Demonology",
|
||||
classId: "warlock",
|
||||
specialization: "Demonology",
|
||||
specializationId: "warlock:demonology",
|
||||
talentTreeId: "warlock-demonology",
|
||||
talentRanks: runtimePartyTalentRanksForLevel("warlock", "Demonology", 80),
|
||||
role: "damage",
|
||||
}, 80, 1, "pet-target-runtime");
|
||||
usePartyStore.getState().initializeParty("pet-target-runtime", 80, useCombatStore.getState().health, "damage");
|
||||
usePartyStore.setState({ members: [hunter, warlock] });
|
||||
for (const member of usePartyStore.getState().members) registerPartyRuntimePosition(member.id, [-14.5, 0, 0]);
|
||||
useCombatStore.getState().synchronizeActors();
|
||||
registerEngagedMob("pet-runtime-boss", 100_000);
|
||||
const events: StructuredCombatEvent[] = [];
|
||||
const stop = subscribeCombatEvents((event) => events.push(event));
|
||||
for (let now = 1_000; now <= 30_000; now += 100) {
|
||||
useCombatStore.getState().tick(0.1, now);
|
||||
advanceDungeonPartyCombat(now);
|
||||
}
|
||||
stop();
|
||||
|
||||
const pets = useCombatStore.getState().summons.filter((summon) => (
|
||||
summon.ownerActorId === hunter.id || summon.ownerActorId === warlock.id
|
||||
));
|
||||
expect(new Set(pets.map((summon) => summon.ownerActorId))).toEqual(new Set([hunter.id, warlock.id]));
|
||||
expect(pets.every((summon) => summon.targetId === "pet-runtime-boss")).toBe(true);
|
||||
expect(events.filter((event) => event.kind === "damage"
|
||||
&& [hunter.id, warlock.id].includes(event.sourceActorId))).toEqual(expect.arrayContaining([
|
||||
expect.objectContaining({ abilityId: "wow335-hunter-call-pet" }),
|
||||
expect.objectContaining({ abilityId: "wow335-warlock-summon-felguard" }),
|
||||
]));
|
||||
});
|
||||
|
||||
it("casts Power Word: Shield on an injured ally and grants absorb capacity", () => {
|
||||
useCombatStore.getState().initializeCharacter({ classId: "mage", role: "damage", level: 80 });
|
||||
const tank = createConfiguredPartyMember({
|
||||
name: "Tank",
|
||||
classId: "warrior",
|
||||
specialization: "Protection",
|
||||
specializationId: "warrior:protection",
|
||||
talentTreeId: "warrior-protection",
|
||||
role: "tank",
|
||||
}, 80, 0, "discipline-shield-runtime");
|
||||
const healer = createConfiguredPartyMember({
|
||||
name: "Discipline",
|
||||
classId: "priest",
|
||||
specialization: "Discipline",
|
||||
specializationId: "priest:discipline",
|
||||
talentTreeId: "priest-discipline",
|
||||
role: "healer",
|
||||
}, 80, 1, "discipline-shield-runtime");
|
||||
const priestAbilities = abilitiesForClass("priest");
|
||||
const shield = priestAbilities.find((ability) => ability.name === "Power Word: Shield")!;
|
||||
usePartyStore.getState().initializeParty("discipline-shield-runtime", 80, useCombatStore.getState().health, "damage");
|
||||
usePartyStore.setState({
|
||||
members: [
|
||||
{ ...tank, health: Math.round(tank.maxHealth * 0.5) },
|
||||
{
|
||||
...healer,
|
||||
resource: 100_000,
|
||||
maxResource: 100_000,
|
||||
resourcePools: { mana: 100_000 },
|
||||
cooldowns: Object.fromEntries(priestAbilities.filter((ability) => ability.id !== shield.id)
|
||||
.map((ability) => [ability.id, 60_000])),
|
||||
},
|
||||
],
|
||||
});
|
||||
for (const member of usePartyStore.getState().members) registerPartyRuntimePosition(member.id, [0, 0, 0]);
|
||||
useCombatStore.getState().synchronizeActors();
|
||||
registerEngagedMob("discipline-shield-boss", 100_000);
|
||||
|
||||
advanceDungeonPartyCombat(1_000);
|
||||
|
||||
const applied = useCombatStore.getState().auras.find((aura) => (
|
||||
aura.sourceId === healer.id
|
||||
&& aura.targetId === tank.id
|
||||
&& aura.definition.name.includes("Power Word: Shield")
|
||||
));
|
||||
expect(applied?.absorbs.reduce((total, absorb) => total + absorb.remaining, 0)).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("lets a resurrected tank rejoin independently instead of taunting or casting from its checkpoint", () => {
|
||||
|
||||
Reference in new issue
Block a user