Update Healer Man server

This commit is contained in:
phenom committed 2026-09-30 09:57:13 -04:00
1 parent 35dfdaed61
commit 4fb7a0b9c8
161 files changed
+24618 -529

No files matched your search

+353 -1
View File
@@ -2,10 +2,11 @@ import { beforeEach, describe, expect, it } from "vitest";
import { PLAYER_AGGRO_ID } from "./aggro";
import { applyAura, resolveStatValue } from "./combatAuras";
import { clearMobRuntimeRegistry, setMobPosition } from "./mobRuntimeRegistry";
import { clearPartyRuntimeRegistry } from "./partyRuntimeRegistry";
import { clearPartyRuntimeRegistry, registerPartyRuntimePosition } from "./partyRuntimeRegistry";
import { preloadAbilityCatalogsForClasses } from "./abilityCatalog";
import { PLAYER_AURA_ENTITY_ID, useCombatStore } from "./combatStore";
import { usePartyStore } from "./partyStore";
import { DEFAULT_ENEMY_ATTACK } from "./enemyAttacks";
const TARGET_ID = "druid-proc-target";
@@ -48,6 +49,50 @@ function castAndFinish(name: string, startedAt: number) {
)).at(-1);
}
function guaranteeDruidMeleeCrits(): void {
useCombatStore.setState((state) => ({
auras: [...applyAura(state.auras, {
definition: {
id: "test-druid-guaranteed-melee-crit",
name: "Test Melee Critical Chance",
disposition: "buff",
durationMs: null,
modifiers: [
{ kind: "stat", stat: "hit", operation: "flat", value: 100 },
{ kind: "stat", stat: "physical-critical-strike", operation: "flat", value: 100 },
{ kind: "stat", stat: "spell-hit", operation: "flat", value: 100 },
{ kind: "stat", stat: "spell-critical-strike", operation: "flat", value: 100 },
],
},
sourceId: PLAYER_AURA_ENTITY_ID,
targetId: PLAYER_AURA_ENTITY_ID,
now: 1_000,
}).auras],
mobs: {
...state.mobs,
[TARGET_ID]: {
...state.mobs[TARGET_ID]!,
characterDefender: {
level: 80,
armor: 0,
dodgePercent: 0,
parryPercent: 0,
blockPercent: 0,
blockValue: 0,
},
},
},
}));
}
function findDruidPrimalFurySeed(procTag: string, chance: number, shouldProc: boolean): string {
for (let index = 0; index < 100_000; index += 1) {
const seed = `druid-primal-fury:${procTag}:${index}`;
if ((unit(`${seed}:wow335-druid-primal-fury-${procTag}`) < chance) === shouldProc) return seed;
}
throw new Error(`No deterministic Primal Fury ${procTag} seed found.`);
}
describe("WoW Druid proc runtime", () => {
beforeEach(async () => {
clearMobRuntimeRegistry();
@@ -246,4 +291,311 @@ describe("WoW Druid proc runtime", () => {
&& aura.definition.utilityTags?.includes("wow335-druid-clearcasting")
))).toBe(true);
});
it("stores 30% of an eligible critical heal and blooms on the target's next direct hit", () => {
const healingTouch = abilityId("Healing Touch");
const startedAt = 1_000;
useCombatStore.setState((state) => ({
health: 1,
talentRanks: { ...state.talentRanks, "talent-1922": 3 },
auras: [...applyAura(state.auras, {
definition: {
id: "test-druid-guaranteed-spell-crit",
name: "Test Critical Chance",
disposition: "buff",
durationMs: null,
modifiers: [{ kind: "stat", stat: "spell-critical-strike", operation: "flat", value: 100 }],
},
sourceId: PLAYER_AURA_ENTITY_ID,
targetId: PLAYER_AURA_ENTITY_ID,
now: startedAt,
}).auras],
}));
expect(useCombatStore.getState().castAbility(healingTouch, { targetActorId: null, now: startedAt }).ok).toBe(true);
const cast = useCombatStore.getState().activeCast;
expect(cast?.abilityId).toBe(healingTouch);
useCombatStore.getState().tick(0.1, cast!.completesAt);
const heal = useCombatStore.getState().combatEvents.find((event) => (
event.kind === "healing" && event.abilityId === healingTouch && event.occurredAt === cast!.completesAt
));
expect(heal).toMatchObject({ critical: true, effectiveAmount: expect.any(Number) });
const storedSeed = useCombatStore.getState().auras.find((aura) => (
aura.targetId === PLAYER_AURA_ENTITY_ID
&& aura.definition.utilityTags?.includes("wow335-druid-living-seed")
));
expect(storedSeed?.expiresAt).toBe(cast!.completesAt + 15_000);
expect(storedSeed?.druidLivingSeedAmount).toBe(Math.round(heal!.effectiveAmount * 0.3));
const healthBeforeHit = useCombatStore.getState().health;
const damage = useCombatStore.getState().damagePlayer(100, "physical", 80, cast!.completesAt + 1);
expect(damage).toBeGreaterThan(0);
expect(useCombatStore.getState().health).toBeGreaterThan(healthBeforeHit - damage);
expect(useCombatStore.getState().auras.some((aura) => (
aura.targetId === PLAYER_AURA_ENTITY_ID
&& aura.definition.utilityTags?.includes("wow335-druid-living-seed")
))).toBe(false);
expect(useCombatStore.getState().combatEvents.some((event) => (
event.kind === "healing" && event.abilityId === "wow335:druid:living-seed" && event.effectiveAmount > 0
))).toBe(true);
});
it("expires an untriggered Living Seed after fifteen seconds", () => {
const healingTouch = abilityId("Healing Touch");
const startedAt = 1_000;
useCombatStore.setState((state) => ({
health: 1,
talentRanks: { ...state.talentRanks, "talent-1922": 3 },
auras: [...applyAura(state.auras, {
definition: {
id: "test-druid-guaranteed-spell-crit",
name: "Test Critical Chance",
disposition: "buff",
durationMs: null,
modifiers: [{ kind: "stat", stat: "spell-critical-strike", operation: "flat", value: 100 }],
},
sourceId: PLAYER_AURA_ENTITY_ID,
targetId: PLAYER_AURA_ENTITY_ID,
now: startedAt,
}).auras],
}));
expect(useCombatStore.getState().castAbility(healingTouch, { targetActorId: null, now: startedAt }).ok).toBe(true);
const cast = useCombatStore.getState().activeCast;
useCombatStore.getState().tick(0.1, cast!.completesAt);
const expiresAt = cast!.completesAt + 15_000;
expect(useCombatStore.getState().auras.some((aura) => (
aura.targetId === PLAYER_AURA_ENTITY_ID
&& aura.definition.utilityTags?.includes("wow335-druid-living-seed")
))).toBe(true);
const healthBeforeHit = useCombatStore.getState().health;
const damage = useCombatStore.getState().damagePlayer(100, "physical", 80, expiresAt);
expect(damage).toBeGreaterThan(0);
expect(useCombatStore.getState().health).toBe(healthBeforeHit - damage);
expect(useCombatStore.getState().auras.some((aura) => (
aura.targetId === PLAYER_AURA_ENTITY_ID
&& aura.definition.utilityTags?.includes("wow335-druid-living-seed")
))).toBe(false);
expect(useCombatStore.getState().combatEvents.some((event) => (
event.kind === "healing" && event.abilityId === "wow335:druid:living-seed"
))).toBe(false);
});
it("blooms on a landed direct enemy hit to a party ally", () => {
const member = usePartyStore.getState().members[0]!;
const memberMaxHealth = 1_000;
usePartyStore.setState((state) => ({
members: state.members.map((candidate) => candidate.id === member.id
? { ...candidate, health: 100, maxHealth: memberMaxHealth }
: candidate),
}));
useCombatStore.setState((state) => ({
actors: {
...state.actors,
[member.id]: { ...state.actors[member.id]!, health: 100, maxHealth: memberMaxHealth, alive: true },
},
talentRanks: { ...state.talentRanks, "talent-1922": 3 },
auras: [...applyAura(state.auras, {
definition: {
id: "test-druid-guaranteed-spell-crit",
name: "Test Critical Chance",
disposition: "buff",
durationMs: null,
modifiers: [{ kind: "stat", stat: "spell-critical-strike", operation: "flat", value: 100 }],
},
sourceId: PLAYER_AURA_ENTITY_ID,
targetId: PLAYER_AURA_ENTITY_ID,
now: 1_000,
}).auras,
...applyAura(state.auras, {
definition: {
id: "test-druid-guaranteed-ally-hit",
name: "Test Incoming Hit Chance",
disposition: "buff",
durationMs: null,
modifiers: [{ kind: "stat", stat: "incoming-melee-hit", operation: "flat", value: 100 }],
},
sourceId: PLAYER_AURA_ENTITY_ID,
targetId: member.id,
now: 1_000,
}).auras],
}));
registerPartyRuntimePosition(member.id, [2, 0, 0]);
const healingTouch = abilityId("Healing Touch");
useCombatStore.getState().synchronizeActors();
useCombatStore.getState().selectFriendlyActor(member.id);
const cast = useCombatStore.getState().castAbility(healingTouch, { targetActorId: member.id, now: 1_000 });
expect(cast.ok, JSON.stringify(cast)).toBe(true);
const activeCast = useCombatStore.getState().activeCast;
useCombatStore.getState().tick(0.1, activeCast!.completesAt);
const storedSeed = useCombatStore.getState().auras.find((aura) => (
aura.targetId === member.id
&& aura.definition.utilityTags?.includes("wow335-druid-living-seed")
));
expect(storedSeed?.druidLivingSeedAmount).toBeGreaterThan(0);
const mobId = "druid-living-seed-party-hit";
const attackId = "druid-living-seed-party-swing";
useCombatStore.getState().registerMob(mobId, {
name: "Party attacker",
level: 1,
maxHealth: 1_000,
xpReward: 0,
attacks: [{
...DEFAULT_ENEMY_ATTACK,
id: attackId,
initialCooldownMs: [0, 0],
repeatCooldownMs: [1_000, 1_000],
}],
});
setMobPosition(mobId, [4, 0, 0]);
useCombatStore.setState((state) => ({
mobs: {
...state.mobs,
[mobId]: {
...state.mobs[mobId]!,
engaged: true,
combatPhase: "attacking",
targetActorId: member.id,
threatByActor: { [member.id]: 100 },
},
},
}));
useCombatStore.getState().advanceMobCombat(activeCast!.completesAt + 500);
const enemyDamage = useCombatStore.getState().combatEvents.find((event) => (
event.kind === "damage" && event.sourceActorId === mobId && event.targetActorId === member.id
));
expect(enemyDamage).toMatchObject({ outcome: "hit" });
const remainingSeed = useCombatStore.getState().auras.find((aura) => (
aura.targetId === member.id
&& aura.definition.utilityTags?.includes("wow335-druid-living-seed")
));
expect(remainingSeed).toBeUndefined();
expect(useCombatStore.getState().combatEvents.some((event) => (
event.kind === "healing" && event.abilityId === "wow335:druid:living-seed"
&& event.targetActorId === member.id && event.effectiveAmount > 0
))).toBe(true);
});
it("grants five Rage on Bear and Dire Bear Form critical auto-attacks at Primal Fury's 50% rank", () => {
useCombatStore.setState((state) => ({
talentRanks: { ...state.talentRanks, "talent-801": 1 },
resourcePools: { ...state.resourcePools, rage: 0 },
maxResourcePools: { ...state.maxResourcePools, rage: 100 },
}));
guaranteeDruidMeleeCrits();
const form = useCombatStore.getState().castAbility(abilityId("Bear Form"), {
targetActorId: null,
now: 1_000,
});
expect(form.ok, JSON.stringify(form)).toBe(true);
const procSeed = findDruidPrimalFurySeed("rage", 0.5, true);
const proc = useCombatStore.getState().damageMob(TARGET_ID, 100, 2_000, undefined, "physical", {
attackKind: "melee",
seed: procSeed,
rules: { isAutoAttack: true, canMiss: false, canDodge: false, canParry: false, canBlock: false },
});
expect(proc.combatResult?.critical).toBe(true);
expect(useCombatStore.getState().resourcePools.rage).toBe(5);
expect(useCombatStore.getState().combatEvents.at(-1)).toMatchObject({
kind: "resource",
sourceActorId: PLAYER_AGGRO_ID,
targetActorId: PLAYER_AURA_ENTITY_ID,
abilityId: "wow335-druid-primal-fury",
rawAmount: 5,
effectiveAmount: 5,
});
const noProcSeed = findDruidPrimalFurySeed("rage", 0.5, false);
const noProc = useCombatStore.getState().damageMob(TARGET_ID, 100, 3_000, undefined, "physical", {
attackKind: "melee",
seed: noProcSeed,
rules: { isAutoAttack: true, canMiss: false, canDodge: false, canParry: false, canBlock: false },
});
expect(noProc.combatResult?.critical).toBe(true);
expect(useCombatStore.getState().resourcePools.rage).toBe(5);
const direBearForm = useCombatStore.getState().castAbility(abilityId("Dire Bear Form"), {
targetActorId: null,
now: 4_000,
});
expect(direBearForm.ok, JSON.stringify(direBearForm)).toBe(true);
const direBearProc = useCombatStore.getState().damageMob(TARGET_ID, 100, 5_000, undefined, "physical", {
attackKind: "melee",
seed: findDruidPrimalFurySeed("rage", 0.5, true),
rules: { isAutoAttack: true, canMiss: false, canDodge: false, canParry: false, canBlock: false },
});
expect(direBearProc.combatResult?.critical).toBe(true);
expect(useCombatStore.getState().resourcePools.rage).toBe(10);
});
it("adds one capped combo point on a Cat Form combo-builder critical and ignores other hits", () => {
useCombatStore.setState((state) => ({
talentRanks: { ...state.talentRanks, "talent-801": 2 },
resourcePools: { ...state.resourcePools, rage: 0 },
maxResourcePools: { ...state.maxResourcePools, rage: 100 },
}));
guaranteeDruidMeleeCrits();
const form = useCombatStore.getState().castAbility(abilityId("Cat Form"), {
targetActorId: null,
now: 1_000,
});
expect(form.ok, JSON.stringify(form)).toBe(true);
const claw = castAndFinish("Claw", 3_000);
expect(claw).toMatchObject({ critical: true, sourceActorId: PLAYER_AGGRO_ID, targetActorId: TARGET_ID });
expect(useCombatStore.getState().comboPoints).toBe(2);
expect(useCombatStore.getState().comboTargetId).toBe(TARGET_ID);
expect(useCombatStore.getState().resourcePools.rage).toBe(0);
useCombatStore.setState((state) => ({
comboPoints: 4,
comboTargetId: TARGET_ID,
resourcePools: {
...state.resourcePools,
energy: state.maxResourcePools.energy,
},
}));
const cappedClaw = castAndFinish("Claw", 5_000);
expect(cappedClaw?.critical).toBe(true);
expect(useCombatStore.getState().comboPoints).toBe(5);
expect(useCombatStore.getState().comboTargetId).toBe(TARGET_ID);
useCombatStore.setState((state) => ({ comboPoints: 0, comboTargetId: null }));
const clawId = abilityId("Claw");
useCombatStore.getState().damageMob(TARGET_ID, 100, 7_000, undefined, "physical", {
attackKind: "melee",
seed: "druid-primal-fury-cat-auto-attack",
abilityId: clawId,
rules: { isAutoAttack: true, canMiss: false, canDodge: false, canParry: false, canBlock: false },
});
expect(useCombatStore.getState().comboPoints).toBe(0);
expect(useCombatStore.getState().comboTargetId).toBeNull();
useCombatStore.setState((state) => ({
comboPoints: 0,
comboTargetId: null,
resourcePools: { ...state.resourcePools, energy: state.maxResourcePools.energy },
}));
const rake = castAndFinish("Rake", 8_000);
expect(rake).toMatchObject({ critical: true, sourceActorId: PLAYER_AGGRO_ID, targetActorId: TARGET_ID });
expect(useCombatStore.getState().comboPoints).toBe(2);
useCombatStore.getState().tick(9, 17_000);
expect(useCombatStore.getState().comboPoints).toBe(2);
expect(useCombatStore.getState().comboTargetId).toBe(TARGET_ID);
useCombatStore.setState((state) => ({ comboPoints: 0, comboTargetId: null }));
useCombatStore.getState().damageMob(TARGET_ID, 100, 18_000, undefined, "arcane", {
attackKind: "spell",
seed: "druid-primal-fury-cat-non-combo",
abilityId: abilityId("Moonfire"),
rules: { canMiss: false, canDodge: false, canParry: false, canBlock: false },
});
expect(useCombatStore.getState().comboPoints).toBe(0);
expect(useCombatStore.getState().comboTargetId).toBeNull();
});
});