Release Healer Man 0.1.6
This commit is contained in:
1 parent
55cfd43d66
commit
b12fb84859
96 files changed
+10874
-609
No files matched your search
@@ -12,6 +12,7 @@ import {
|
||||
EMPTY_CORPSE_DESPAWN_MS,
|
||||
MINIMUM_CORPSE_VISIBLE_MS,
|
||||
MOB_WOUND_ANIMATION_COOLDOWN_MS,
|
||||
PLAYER_AURA_ENTITY_ID,
|
||||
selectedMob,
|
||||
useCombatStore,
|
||||
} from "./combatStore";
|
||||
@@ -90,7 +91,7 @@ describe("combat store", () => {
|
||||
expect(useCombatStore.getState().mobs.ravager.health).toBe(100);
|
||||
useCombatStore.getState().tick(0.1, 2500);
|
||||
expect(useCombatStore.getState().mobs.ravager.health).toBeLessThan(100);
|
||||
expect(useCombatStore.getState().resource).toBe(startingResource - 14);
|
||||
expect(useCombatStore.getState().resource).toBe(startingResource - Math.round(startingResource * 0.14));
|
||||
expect(useCombatStore.getState().activeCast).toBeNull();
|
||||
expect(useCombatStore.getState().mobs.ravager.engaged).toBe(true);
|
||||
});
|
||||
@@ -950,7 +951,7 @@ describe("combat store", () => {
|
||||
expect(useCombatStore.getState().castSlot(0, undefined, 10_000).ok).toBe(true);
|
||||
const boltCompletion = useCombatStore.getState().activeCast!.completesAt;
|
||||
useCombatStore.getState().tick(0.1, boltCompletion);
|
||||
expect(useCombatStore.getState().resource).toBe(startingMana - 9);
|
||||
expect(useCombatStore.getState().resource).toBe(startingMana - Math.round(startingMana * 0.1 * 0.85));
|
||||
});
|
||||
|
||||
it("routes a cast-time direct heal to the selected living party member", () => {
|
||||
@@ -972,6 +973,154 @@ describe("combat store", () => {
|
||||
expect(useCombatStore.getState().selectedTargetId).toBe("hostile-selection");
|
||||
});
|
||||
|
||||
it("fully heals the selected ally with Lay on Hands and starts its cooldown", () => {
|
||||
useCombatStore.getState().initializeCharacter({ classId: "paladin", level: 10 });
|
||||
usePartyStore.getState().initializeParty("lay-on-hands-test", 10, useCombatStore.getState().health);
|
||||
const memberId = preparePartyMember(0, [10, 0, 0], 1, true);
|
||||
const memberBefore = usePartyStore.getState().members.find((member) => member.id === memberId)!;
|
||||
|
||||
expect(useCombatStore.getState().castAbility(
|
||||
"wow335-paladin-lay-on-hands",
|
||||
[0, 0, 0],
|
||||
1_000,
|
||||
)).toMatchObject({
|
||||
ok: true,
|
||||
healing: memberBefore.maxHealth - 1,
|
||||
});
|
||||
expect(usePartyStore.getState().members.find((member) => member.id === memberId)?.health)
|
||||
.toBe(memberBefore.maxHealth);
|
||||
expect(useCombatStore.getState().cooldowns["wow335-paladin-lay-on-hands"])
|
||||
.toBe(1_201_000);
|
||||
expect(useCombatStore.getState().castAbility(
|
||||
"wow335-paladin-lay-on-hands",
|
||||
[0, 0, 0],
|
||||
2_500,
|
||||
)).toMatchObject({ ok: false, reason: "cooldown" });
|
||||
});
|
||||
|
||||
it("makes the selected ally immune to physical attacks with Hand of Protection", () => {
|
||||
useCombatStore.getState().initializeCharacter({ classId: "paladin", level: 10 });
|
||||
usePartyStore.getState().initializeParty("hand-of-protection-test", 10, useCombatStore.getState().health);
|
||||
const memberId = preparePartyMember(0, [0, 0, 0], 1, true);
|
||||
const memberBefore = usePartyStore.getState().members.find((member) => member.id === memberId)!;
|
||||
|
||||
expect(useCombatStore.getState().castAbility(
|
||||
"wow335-paladin-hand-of-protection",
|
||||
[0, 0, 0],
|
||||
1_000,
|
||||
)).toMatchObject({ ok: true });
|
||||
expect(useCombatStore.getState().auras).toContainEqual(expect.objectContaining({
|
||||
targetId: memberId,
|
||||
expiresAt: 7_000,
|
||||
definition: expect.objectContaining({ name: expect.stringContaining("Hand of Protection") }),
|
||||
}));
|
||||
expect(useCombatStore.getState().cooldowns["wow335-paladin-hand-of-protection"])
|
||||
.toBe(301_000);
|
||||
|
||||
useCombatStore.getState().registerMob("party-victim", {
|
||||
name: "Party victim",
|
||||
maxHealth: 200,
|
||||
xpReward: 0,
|
||||
});
|
||||
setMobPosition("party-victim", [2, 0, 0]);
|
||||
useCombatStore.getState().damageMob("party-victim", 5, 1_000, {
|
||||
actorId: memberId,
|
||||
role: "tank",
|
||||
});
|
||||
|
||||
expect(useCombatStore.getState().advanceMobCombat(1_450)).toBe(0);
|
||||
expect(usePartyStore.getState().members.find((member) => member.id === memberId)?.health)
|
||||
.toBe(memberBefore.health);
|
||||
});
|
||||
|
||||
it("clears roots with Hand of Freedom and replaces mutually exclusive seals", () => {
|
||||
useCombatStore.getState().initializeCharacter({ classId: "paladin", level: 80 });
|
||||
usePartyStore.getState().initializeParty("paladin-aura-test", 80, useCombatStore.getState().health);
|
||||
const memberId = preparePartyMember(0, [0, 0, 0], Number.MAX_SAFE_INTEGER, true);
|
||||
usePartyStore.getState().controlMember(memberId, "root", 30_000);
|
||||
|
||||
expect(useCombatStore.getState().castAbility("wow335-paladin-hand-of-freedom", [0, 0, 0], 1_000).ok).toBe(true);
|
||||
expect(usePartyStore.getState().members.find((member) => member.id === memberId)).toMatchObject({
|
||||
controlledUntil: 0,
|
||||
controlMechanic: null,
|
||||
});
|
||||
|
||||
usePartyStore.getState().selectSelf();
|
||||
expect(useCombatStore.getState().castAbility("wow335-paladin-seal-of-righteousness", [0, 0, 0], 3_000).ok).toBe(true);
|
||||
expect(useCombatStore.getState().castAbility("wow335-paladin-seal-of-wisdom", [0, 0, 0], 5_000).ok).toBe(true);
|
||||
const seals = useCombatStore.getState().auras.filter((aura) => (
|
||||
aura.targetId === PLAYER_AURA_ENTITY_ID && aura.definition.exclusiveGroup === "paladin:seal"
|
||||
));
|
||||
expect(seals).toHaveLength(1);
|
||||
expect(seals[0].definition.name).toContain("Seal of Wisdom");
|
||||
});
|
||||
|
||||
it("redirects Hand of Sacrifice damage to the paladin", () => {
|
||||
useCombatStore.getState().initializeCharacter({ classId: "paladin", level: 80 });
|
||||
usePartyStore.getState().initializeParty("hand-of-sacrifice-test", 80, useCombatStore.getState().health);
|
||||
const memberId = preparePartyMember(0, [0, 0, 0], Number.MAX_SAFE_INTEGER, true);
|
||||
const memberBefore = usePartyStore.getState().members.find((member) => member.id === memberId)!;
|
||||
const playerBefore = useCombatStore.getState().health;
|
||||
|
||||
expect(useCombatStore.getState().castAbility("wow335-paladin-hand-of-sacrifice", [0, 0, 0], 1_000).ok).toBe(true);
|
||||
useCombatStore.getState().registerMob("sacrifice-attacker", { name: "Sacrifice attacker", maxHealth: 200, xpReward: 0 });
|
||||
setMobPosition("sacrifice-attacker", [2, 0, 0]);
|
||||
useCombatStore.getState().damageMob("sacrifice-attacker", 5, 1_000, { actorId: memberId, role: "tank" });
|
||||
expect(useCombatStore.getState().advanceMobCombat(1_450)).toBeGreaterThan(0);
|
||||
|
||||
const memberLoss = memberBefore.health - usePartyStore.getState().members.find((member) => member.id === memberId)!.health;
|
||||
const playerLoss = playerBefore - useCombatStore.getState().health;
|
||||
expect(memberLoss).toBeGreaterThan(0);
|
||||
expect(playerLoss).toBeGreaterThan(0);
|
||||
expect(playerLoss).toBeLessThan(memberLoss);
|
||||
});
|
||||
|
||||
it("taunts up to three attackers off an ally with Righteous Defense", () => {
|
||||
useCombatStore.getState().initializeCharacter({ classId: "paladin", level: 80 });
|
||||
usePartyStore.getState().initializeParty("righteous-defense-test", 80, useCombatStore.getState().health);
|
||||
const memberId = preparePartyMember(0, [0, 0, 0], Number.MAX_SAFE_INTEGER, true);
|
||||
for (const id of ["attacker-a", "attacker-b", "attacker-c", "attacker-d"]) {
|
||||
useCombatStore.getState().registerMob(id, { name: id, maxHealth: 100, xpReward: 0 });
|
||||
setMobPosition(id, [2, 0, 0]);
|
||||
}
|
||||
useCombatStore.setState((state) => ({
|
||||
mobs: Object.fromEntries(Object.entries(state.mobs).map(([id, mob]) => [id, {
|
||||
...mob,
|
||||
engaged: true,
|
||||
targetActorId: memberId,
|
||||
threatByActor: { [memberId]: 10 },
|
||||
}])),
|
||||
}));
|
||||
|
||||
expect(useCombatStore.getState().castAbility("wow335-paladin-righteous-defense", [0, 0, 0], 1_000).ok).toBe(true);
|
||||
expect(Object.values(useCombatStore.getState().mobs).filter((mob) => mob.forcedTarget?.actorId === PLAYER_AURA_ENTITY_ID)).toHaveLength(3);
|
||||
});
|
||||
|
||||
it("channels party-wide classic healing without requiring an enemy target", () => {
|
||||
useCombatStore.getState().initializeCharacter({ classId: "priest", level: 80 });
|
||||
usePartyStore.getState().initializeParty("divine-hymn-test", 80, useCombatStore.getState().health);
|
||||
const memberId = preparePartyMember(0, [4, 0, 0], 1);
|
||||
useCombatStore.getState().damagePlayer(500);
|
||||
const playerBefore = useCombatStore.getState().health;
|
||||
|
||||
expect(useCombatStore.getState().castAbility("wow335-priest-divine-hymn", [0, 0, 0], 1_000).ok).toBe(true);
|
||||
const completion = useCombatStore.getState().activeCast?.completesAt;
|
||||
if (completion) useCombatStore.getState().tick(1, completion);
|
||||
expect(useCombatStore.getState().activeCast).toBeNull();
|
||||
expect(useCombatStore.getState().health).toBeGreaterThan(playerBefore);
|
||||
expect(usePartyStore.getState().members.find((member) => member.id === memberId)!.health).toBeGreaterThan(1);
|
||||
});
|
||||
|
||||
it("executes intentional classic utility spells instead of rejecting them", () => {
|
||||
useCombatStore.getState().initializeCharacter({ classId: "mage", level: 80 });
|
||||
useCombatStore.getState().damagePlayer(500);
|
||||
const before = useCombatStore.getState().health;
|
||||
expect(useCombatStore.getState().castAbility("wow335-mage-conjure-food", [0, 0, 0], 1_000).ok).toBe(true);
|
||||
const completion = useCombatStore.getState().activeCast?.completesAt;
|
||||
if (completion) useCombatStore.getState().tick(1, completion);
|
||||
expect(useCombatStore.getState().health).toBeGreaterThan(before);
|
||||
});
|
||||
|
||||
it("falls back to the player and snapshots an explicit null friendly target", () => {
|
||||
registerTarget("enemy", [10, 0, 0], 100, 0);
|
||||
const memberId = preparePartyMember(0, [8, 0, 0], 1);
|
||||
|
||||
Reference in new issue
Block a user