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,140 @@
|
||||
import { beforeEach, describe, expect, it } from "vitest";
|
||||
import { PLAYER_AGGRO_ID } from "./aggro";
|
||||
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_DEATH_KNIGHT_SPELL_DEFLECTION_TALENT_ID } from "./wow335DeathKnightSpellDeflectionRuntime";
|
||||
|
||||
const ATTACK = {
|
||||
...DEFAULT_ENEMY_ATTACK,
|
||||
id: "spell-deflection-incoming-test",
|
||||
name: "Spell Deflection 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,
|
||||
};
|
||||
|
||||
const MAX_PARRY_GEAR = { ...EMPTY_ITEM_STATS, parryRating: 50_000 };
|
||||
|
||||
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`);
|
||||
if (resultRoll >= 0.2 && resultRoll < 0.9) return now;
|
||||
}
|
||||
throw new Error("Could not find a landed incoming spell hit seed.");
|
||||
}
|
||||
|
||||
function setupAttacker(mobId: string, targetId: string, now: number, school: EnemyDamageSchool): void {
|
||||
useCombatStore.getState().registerMob(mobId, {
|
||||
name: "Spell Deflection 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 resetForHit(playerClass: "death-knight" | "warrior", runId: string): void {
|
||||
clearMobRuntimeRegistry();
|
||||
clearPartyRuntimeRegistry();
|
||||
resetPartyRuntimeClock();
|
||||
useCombatStore.getState().initializeCharacter({ classId: playerClass, level: 80, xp: 0 });
|
||||
usePartyStore.getState().initializeParty(
|
||||
`wow335-spell-deflection-${runId}`,
|
||||
80,
|
||||
useCombatStore.getState().health,
|
||||
"tank",
|
||||
1,
|
||||
);
|
||||
}
|
||||
|
||||
function runPlayerHit(rank: number) {
|
||||
resetForHit("death-knight", `player-${rank}`);
|
||||
useCombatStore.setState((state) => ({
|
||||
gearStats: MAX_PARRY_GEAR,
|
||||
talentRanks: { ...state.talentRanks, [WOW335_DEATH_KNIGHT_SPELL_DEFLECTION_TALENT_ID]: rank },
|
||||
}));
|
||||
const mobId = "spell-deflection-player-attacker";
|
||||
const now = hitTime(mobId, PLAYER_AGGRO_ID);
|
||||
setupAttacker(mobId, PLAYER_AGGRO_ID, now, "shadow");
|
||||
useCombatStore.getState().advanceMobCombat(now);
|
||||
return incomingEvent(mobId, PLAYER_AURA_ENTITY_ID);
|
||||
}
|
||||
|
||||
function runCompanionHit(rank: number) {
|
||||
resetForHit("warrior", `companion-${rank}`);
|
||||
const talentRanks = { [WOW335_DEATH_KNIGHT_SPELL_DEFLECTION_TALENT_ID]: rank };
|
||||
const member = createConfiguredPartyMember({
|
||||
name: "Spell Deflection DK",
|
||||
classId: "death-knight",
|
||||
specialization: "Blood",
|
||||
specializationId: "death-knight:blood",
|
||||
talentTreeId: "death-knight-blood",
|
||||
role: "tank",
|
||||
talentRanks,
|
||||
}, 80, 0, `wow335-spell-deflection-companion-${rank}`);
|
||||
usePartyStore.setState({
|
||||
members: [{
|
||||
...member,
|
||||
health: 100_000,
|
||||
maxHealth: 100_000,
|
||||
gearStats: MAX_PARRY_GEAR,
|
||||
talentRanks,
|
||||
globalCooldownEndsAt: 61_000,
|
||||
nextActionAt: 61_000,
|
||||
}],
|
||||
});
|
||||
registerPartyRuntimePosition(member.id, [0, 0, 0]);
|
||||
useCombatStore.getState().synchronizeActors();
|
||||
|
||||
const mobId = "spell-deflection-companion-attacker";
|
||||
const now = hitTime(mobId, member.id);
|
||||
setupAttacker(mobId, member.id, now, "shadow");
|
||||
advanceDungeonPartyCombat(now);
|
||||
useCombatStore.getState().advanceMobCombat(now, [100, 0, 0]);
|
||||
return incomingEvent(mobId, member.id);
|
||||
}
|
||||
|
||||
describe("WoW Death Knight Spell Deflection combat integration", () => {
|
||||
beforeEach(() => {
|
||||
clearMobRuntimeRegistry();
|
||||
clearPartyRuntimeRegistry();
|
||||
resetPartyRuntimeClock();
|
||||
});
|
||||
|
||||
it("absorbs the rank percentage of a direct shadow hit for player and generated companion", () => {
|
||||
const playerBaseline = runPlayerHit(0);
|
||||
const playerDeflected = runPlayerHit(3);
|
||||
const companionBaseline = runCompanionHit(0);
|
||||
const companionDeflected = runCompanionHit(3);
|
||||
|
||||
expect(playerBaseline.effectiveAmount).toBeGreaterThan(0);
|
||||
expect(playerBaseline.absorbed).toBe(0);
|
||||
expect(playerDeflected.absorbed).toBe(Math.floor(playerBaseline.effectiveAmount * 0.45));
|
||||
expect(playerDeflected.effectiveAmount).toBe(playerBaseline.effectiveAmount - playerDeflected.absorbed);
|
||||
|
||||
expect(companionBaseline.effectiveAmount).toBeGreaterThan(0);
|
||||
expect(companionBaseline.absorbed).toBe(0);
|
||||
expect(companionDeflected.absorbed).toBe(Math.floor(companionBaseline.effectiveAmount * 0.45));
|
||||
expect(companionDeflected.effectiveAmount).toBe(companionBaseline.effectiveAmount - companionDeflected.absorbed);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user