forked from phenom/healer-man
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,155 @@
|
||||
import { beforeEach, describe, expect, it } from "vitest";
|
||||
import { PLAYER_AGGRO_ID } from "./aggro";
|
||||
import { abilityAtLevel, abilityById } from "./abilityCatalog";
|
||||
import { useCombatStore, stableCombatUnit, PLAYER_AURA_ENTITY_ID } from "./combatStore";
|
||||
import { DEFAULT_ENEMY_ATTACK, type EnemyDamageSchool } from "./enemyAttacks";
|
||||
import { clearMobRuntimeRegistry, setMobPosition } from "./mobRuntimeRegistry";
|
||||
import { clearPartyRuntimeRegistry, registerPartyRuntimePosition } from "./partyRuntimeRegistry";
|
||||
import { advanceDungeonPartyCombat, resetPartyRuntimeClock } from "./partyRuntime";
|
||||
import { createConfiguredPartyMember, usePartyStore } from "./partyStore";
|
||||
import { EMPTY_ITEM_STATS } from "./itemStats";
|
||||
import {
|
||||
WOW335_MAGE_FROST_WARDING_TALENT_ID,
|
||||
WOW335_MAGE_FROST_WARDING_WARD_ABILITIES,
|
||||
} from "./wow335MageFrostWardingRuntime";
|
||||
|
||||
const ATTACK = {
|
||||
...DEFAULT_ENEMY_ATTACK,
|
||||
id: "frost-warding-incoming-test",
|
||||
name: "Frost Warding incoming test",
|
||||
delivery: "projectile" as const,
|
||||
animation: "cast" as const,
|
||||
castTimeMs: 0,
|
||||
range: 20,
|
||||
cooldownMs: 10_000,
|
||||
initialCooldownMs: [0, 0] as const,
|
||||
repeatCooldownMs: [10_000, 10_000] as const,
|
||||
damageMultiplier: 1,
|
||||
};
|
||||
|
||||
function incomingEvent(mobId: string, targetId: string) {
|
||||
const event = useCombatStore.getState().combatEvents.find((candidate) => (
|
||||
candidate.kind === "damage" && candidate.sourceActorId === mobId && candidate.targetActorId === targetId
|
||||
));
|
||||
if (!event) throw new Error(`Expected an incoming damage event on ${targetId}.`);
|
||||
return event;
|
||||
}
|
||||
|
||||
function hitTime(mobId: string, targetId: string): number {
|
||||
for (let now = 1_000; now < 200_000; now += 1) {
|
||||
const resultRoll = stableCombatUnit(`${mobId}:${ATTACK.id}:${targetId}:0:${now}:outcome`);
|
||||
const wardRoll = stableCombatUnit(`${targetId}:${mobId}:${ATTACK.id}:0:wow335-frost-warding`);
|
||||
if (resultRoll >= 0.2 && resultRoll < 0.9 && wardRoll < 0.5) return now;
|
||||
}
|
||||
throw new Error("Could not find a landed hit that passes rank 2 Frost Warding's 50% roll.");
|
||||
}
|
||||
|
||||
function mobIdForWardProc(prefix: string, targetId: string): string {
|
||||
for (let index = 0; index < 1_000; index += 1) {
|
||||
const mobId = `${prefix}-${index}`;
|
||||
if (stableCombatUnit(`${targetId}:${mobId}:${ATTACK.id}:0:wow335-frost-warding`) < 0.5) return mobId;
|
||||
}
|
||||
throw new Error("Could not find a stable Frost Warding success seed.");
|
||||
}
|
||||
|
||||
function setupAttacker(mobId: string, targetId: string, now: number, school: EnemyDamageSchool): void {
|
||||
useCombatStore.getState().registerMob(mobId, {
|
||||
name: "Frost Warding test attacker", level: 80, maxHealth: 100_000, xpReward: 0,
|
||||
attacks: [{ ...ATTACK, school }],
|
||||
});
|
||||
setMobPosition(mobId, [2, 0, 0]);
|
||||
useCombatStore.setState((state) => ({
|
||||
mobs: { ...state.mobs, [mobId]: { ...state.mobs[mobId]!, attackDamage: 4_000 } },
|
||||
}));
|
||||
useCombatStore.getState().damageMob(mobId, 1, now - 1, { actorId: targetId, role: "tank" }, "physical");
|
||||
}
|
||||
|
||||
function runPlayerHit(rank: number, ward: "fire" | "frost") {
|
||||
clearMobRuntimeRegistry();
|
||||
clearPartyRuntimeRegistry();
|
||||
useCombatStore.getState().initializeCharacter({ classId: "mage", level: 80, xp: 0 });
|
||||
usePartyStore.getState().initializeParty(`frost-warding-player-${ward}`, 80, useCombatStore.getState().health, "damage", 1);
|
||||
const spell = abilityById(WOW335_MAGE_FROST_WARDING_WARD_ABILITIES[ward].abilityId);
|
||||
if (!spell) throw new Error(`Missing local Mage ${ward} ward.`);
|
||||
useCombatStore.setState((state) => ({
|
||||
talentRanks: { ...state.talentRanks, [WOW335_MAGE_FROST_WARDING_TALENT_ID]: rank },
|
||||
resource: 10_000,
|
||||
resourcePools: { ...state.resourcePools, mana: 10_000 },
|
||||
}));
|
||||
expect(useCombatStore.getState().castAbility(spell.id, undefined, 1_000)).toMatchObject({ ok: true });
|
||||
useCombatStore.setState((state) => ({
|
||||
resource: 1_000,
|
||||
resourcePools: { ...state.resourcePools, mana: 1_000 },
|
||||
}));
|
||||
const mobId = mobIdForWardProc(`frost-warding-player-${ward}`, PLAYER_AGGRO_ID);
|
||||
const now = hitTime(mobId, PLAYER_AGGRO_ID);
|
||||
setupAttacker(mobId, PLAYER_AGGRO_ID, now, ward);
|
||||
useCombatStore.getState().advanceMobCombat(now);
|
||||
return { event: incomingEvent(mobId, PLAYER_AGGRO_ID), mana: useCombatStore.getState().resourcePools.mana ?? 0 };
|
||||
}
|
||||
|
||||
function runCompanionHit(rank: number, ward: "fire" | "frost") {
|
||||
clearMobRuntimeRegistry();
|
||||
clearPartyRuntimeRegistry();
|
||||
useCombatStore.getState().initializeCharacter({ classId: "mage", level: 80, xp: 0 });
|
||||
usePartyStore.getState().initializeParty(`frost-warding-companion-${ward}`, 80, useCombatStore.getState().health, "damage", 1);
|
||||
const talentRanks = { [WOW335_MAGE_FROST_WARDING_TALENT_ID]: rank };
|
||||
const member = createConfiguredPartyMember({
|
||||
name: "Frost Warding Mage", classId: "mage", specialization: "Frost",
|
||||
specializationId: "mage:frost", talentTreeId: "mage-frost", talentRanks, role: "tank",
|
||||
}, 80, 0, `frost-warding-companion-${ward}-${rank}`);
|
||||
const wardAbility = abilityById(WOW335_MAGE_FROST_WARDING_WARD_ABILITIES[ward].abilityId);
|
||||
if (!wardAbility) throw new Error("Missing local Mage Ward ability.");
|
||||
const resolvedWard = abilityAtLevel(wardAbility, 80);
|
||||
const auraEffect = resolvedWard.effects.find((effect) => effect.kind === "apply-aura");
|
||||
if (!auraEffect || auraEffect.kind !== "apply-aura") throw new Error("Mage Ward has no generated aura effect.");
|
||||
usePartyStore.setState({ members: [{
|
||||
...member,
|
||||
health: 100_000,
|
||||
maxHealth: 100_000,
|
||||
gearStats: EMPTY_ITEM_STATS,
|
||||
talentRanks,
|
||||
resource: 1_000,
|
||||
maxResource: 20_000,
|
||||
resourcePools: { ...member.resourcePools, mana: 1_000 },
|
||||
}] });
|
||||
registerPartyRuntimePosition(member.id, [0, 0, 0]);
|
||||
useCombatStore.getState().synchronizeActors();
|
||||
expect(useCombatStore.getState().applyActorAura(member.id, member.id, resolvedWard, auraEffect, 1_000)).toBe(true);
|
||||
const mobId = mobIdForWardProc(`frost-warding-companion-${ward}`, member.id);
|
||||
const now = hitTime(mobId, member.id);
|
||||
setupAttacker(mobId, member.id, now, ward);
|
||||
advanceDungeonPartyCombat(now);
|
||||
useCombatStore.getState().advanceMobCombat(now);
|
||||
const actor = useCombatStore.getState().actors[member.id];
|
||||
return { event: incomingEvent(mobId, member.id), mana: actor?.resources.mana?.current ?? 0 };
|
||||
}
|
||||
|
||||
describe("WoW Mage Frost Warding combat integration", () => {
|
||||
beforeEach(() => {
|
||||
clearMobRuntimeRegistry();
|
||||
clearPartyRuntimeRegistry();
|
||||
resetPartyRuntimeClock();
|
||||
});
|
||||
|
||||
it.each(["fire", "frost"] as const)(
|
||||
"reduces matching %s spell damage before its Ward absorb and refunds the same Mana to player and companion",
|
||||
(ward) => {
|
||||
const baselinePlayer = runPlayerHit(0, ward);
|
||||
const wardedPlayer = runPlayerHit(2, ward);
|
||||
const baselineCompanion = runCompanionHit(0, ward);
|
||||
const wardedCompanion = runCompanionHit(2, ward);
|
||||
const baselineAfterAbsorb = baselinePlayer.event.effectiveAmount + baselinePlayer.event.absorbed;
|
||||
const wardedAfterAbsorb = wardedPlayer.event.effectiveAmount + wardedPlayer.event.absorbed;
|
||||
const companionBaselineAfterAbsorb = baselineCompanion.event.effectiveAmount + baselineCompanion.event.absorbed;
|
||||
const companionWardedAfterAbsorb = wardedCompanion.event.effectiveAmount + wardedCompanion.event.absorbed;
|
||||
|
||||
expect(baselineAfterAbsorb).toBeGreaterThan(0);
|
||||
expect(wardedAfterAbsorb).toBe(Math.round(baselineAfterAbsorb * 0.5));
|
||||
expect(companionBaselineAfterAbsorb).toBeGreaterThan(0);
|
||||
expect(companionWardedAfterAbsorb).toBe(Math.round(companionBaselineAfterAbsorb * 0.5));
|
||||
expect(wardedPlayer.mana).toBeGreaterThan(1_000);
|
||||
expect(wardedCompanion.mana).toBeGreaterThan(1_000);
|
||||
},
|
||||
);
|
||||
});
|
||||
Reference in new issue
Block a user