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 { useCombatStore, stableCombatUnit } from "./combatStore";
|
||||
import { DEFAULT_ENEMY_ATTACK } from "./enemyAttacks";
|
||||
import { EMPTY_ITEM_STATS } from "./itemStats";
|
||||
import { clearMobRuntimeRegistry, setMobPosition } from "./mobRuntimeRegistry";
|
||||
import { clearPartyRuntimeRegistry, registerPartyRuntimePosition } from "./partyRuntimeRegistry";
|
||||
import { createConfiguredPartyMember, usePartyStore } from "./partyStore";
|
||||
import { advanceDungeonPartyCombat, resetPartyRuntimeClock } from "./partyRuntime";
|
||||
import {
|
||||
WOW335_PALADIN_TOUCHED_BY_THE_LIGHT_TALENT_ID,
|
||||
WOW335_WARRIOR_CRITICAL_BLOCK_TALENT_ID,
|
||||
} from "./wow335WarriorCriticalBlockRuntime";
|
||||
|
||||
const SWING = {
|
||||
...DEFAULT_ENEMY_ATTACK,
|
||||
id: "critical-block-test-swing",
|
||||
name: "Critical Block Test Swing",
|
||||
cooldownMs: 10_000,
|
||||
initialCooldownMs: [0, 0] as const,
|
||||
repeatCooldownMs: [10_000, 10_000] as const,
|
||||
damageMultiplier: 1,
|
||||
};
|
||||
|
||||
function timeForBlock(mobId: string, actorId: string): number {
|
||||
for (let now = 1_000; now < 200_000; now += 1) {
|
||||
const roll = stableCombatUnit(`${mobId}:${SWING.id}:${actorId}:0:${now}:outcome`);
|
||||
if (roll >= 0.2 && roll < 0.9) return now;
|
||||
}
|
||||
throw new Error("Could not find a stable Critical Block test roll.");
|
||||
}
|
||||
|
||||
function registerAttacker(mobId: string, targetActorId: string, now: number): void {
|
||||
useCombatStore.getState().registerMob(mobId, {
|
||||
name: mobId,
|
||||
level: 80,
|
||||
maxHealth: 100_000,
|
||||
xpReward: 0,
|
||||
attacks: [SWING],
|
||||
});
|
||||
setMobPosition(mobId, [2, 0, 0]);
|
||||
useCombatStore.getState().damageMob(mobId, 1, now - 1, {
|
||||
actorId: targetActorId,
|
||||
role: "tank",
|
||||
}, "physical");
|
||||
}
|
||||
|
||||
function incomingEvent(mobId: string, targetActorId: string) {
|
||||
const event = useCombatStore.getState().combatEvents.find((candidate) => (
|
||||
candidate.kind === "damage"
|
||||
&& candidate.sourceActorId === mobId
|
||||
&& candidate.targetActorId === targetActorId
|
||||
));
|
||||
if (!event) throw new Error(`Expected a recorded incoming hit on ${targetActorId}.`);
|
||||
return event;
|
||||
}
|
||||
|
||||
function runPlayerBlock(
|
||||
rank: number,
|
||||
options: { readonly attackerIsPlayer?: boolean; readonly touchedByTheLightRank?: number } = {},
|
||||
) {
|
||||
clearMobRuntimeRegistry();
|
||||
useCombatStore.getState().initializeCharacter({ classId: "warrior", level: 80, role: "tank" });
|
||||
useCombatStore.setState((state) => ({
|
||||
health: 100_000,
|
||||
maxHealth: 100_000,
|
||||
gearStats: { ...EMPTY_ITEM_STATS, blockRating: 1_000_000, blockValue: 100 },
|
||||
talentRanks: {
|
||||
...state.talentRanks,
|
||||
[WOW335_WARRIOR_CRITICAL_BLOCK_TALENT_ID]: rank,
|
||||
[WOW335_PALADIN_TOUCHED_BY_THE_LIGHT_TALENT_ID]: options.touchedByTheLightRank ?? 0,
|
||||
},
|
||||
}));
|
||||
const mobId = "critical-block-player-attacker";
|
||||
const now = timeForBlock(mobId, PLAYER_AGGRO_ID);
|
||||
registerAttacker(mobId, PLAYER_AGGRO_ID, now);
|
||||
if (options.attackerIsPlayer) {
|
||||
useCombatStore.setState((state) => ({
|
||||
mobs: {
|
||||
...state.mobs,
|
||||
[mobId]: { ...state.mobs[mobId]!, characterDefender: { level: 80 } },
|
||||
},
|
||||
}));
|
||||
}
|
||||
useCombatStore.getState().advanceMobCombat(now);
|
||||
return incomingEvent(mobId, PLAYER_AGGRO_ID);
|
||||
}
|
||||
|
||||
function runPartyBlock(rank: number) {
|
||||
clearMobRuntimeRegistry();
|
||||
clearPartyRuntimeRegistry();
|
||||
resetPartyRuntimeClock();
|
||||
useCombatStore.getState().initializeCharacter({ classId: "mage", level: 80 });
|
||||
const partyId = "critical-block-party";
|
||||
usePartyStore.getState().initializeParty(partyId, 80, useCombatStore.getState().health, "damage", 1);
|
||||
const warrior = createConfiguredPartyMember({
|
||||
name: "Critical Block Warrior",
|
||||
classId: "warrior",
|
||||
specialization: "Protection",
|
||||
specializationId: "warrior:protection",
|
||||
talentTreeId: "warrior-protection",
|
||||
talentRanks: { [WOW335_WARRIOR_CRITICAL_BLOCK_TALENT_ID]: rank },
|
||||
role: "tank",
|
||||
}, 80, 0, partyId);
|
||||
usePartyStore.setState({
|
||||
members: [{
|
||||
...warrior,
|
||||
gearStats: { ...EMPTY_ITEM_STATS, blockRating: 1_000_000, blockValue: 100 },
|
||||
}],
|
||||
});
|
||||
registerPartyRuntimePosition(warrior.id, [0, 0, 0]);
|
||||
useCombatStore.getState().synchronizeActors();
|
||||
const mobId = "critical-block-party-attacker";
|
||||
const now = timeForBlock(mobId, warrior.id);
|
||||
registerAttacker(mobId, warrior.id, now);
|
||||
advanceDungeonPartyCombat(now);
|
||||
useCombatStore.getState().advanceMobCombat(now);
|
||||
return incomingEvent(mobId, warrior.id);
|
||||
}
|
||||
|
||||
describe("WoW Warrior Critical Block runtime integration", () => {
|
||||
beforeEach(() => {
|
||||
clearMobRuntimeRegistry();
|
||||
clearPartyRuntimeRegistry();
|
||||
resetPartyRuntimeClock();
|
||||
useCombatStore.getState().initializeCharacter({ classId: "mage", level: 80 });
|
||||
usePartyStore.getState().initializeParty("critical-block-tests", 80, useCombatStore.getState().health, "damage", 1);
|
||||
});
|
||||
|
||||
it("reduces successful player and generated companion blocks by the generated rank-3 25%", () => {
|
||||
const playerRank0 = runPlayerBlock(0);
|
||||
const playerRank3 = runPlayerBlock(3);
|
||||
expect(playerRank0.outcome).toBe("block");
|
||||
expect(playerRank3.outcome).toBe("block");
|
||||
expect(playerRank3.effectiveAmount).toBe(Math.round(playerRank0.effectiveAmount * 0.75));
|
||||
|
||||
const partyRank0 = runPartyBlock(0);
|
||||
const partyRank3 = runPartyBlock(3);
|
||||
expect(partyRank0.outcome).toBe("block");
|
||||
expect(partyRank3.outcome).toBe("block");
|
||||
expect(partyRank3.effectiveAmount).toBe(Math.round(partyRank0.effectiveAmount * 0.75));
|
||||
});
|
||||
|
||||
it("keeps the generated rank-3 block reduction disabled against player attackers and with Touched by the Light", () => {
|
||||
const ordinary = runPlayerBlock(0);
|
||||
expect(runPlayerBlock(3, { attackerIsPlayer: true })).toMatchObject({
|
||||
outcome: "block",
|
||||
effectiveAmount: ordinary.effectiveAmount,
|
||||
});
|
||||
expect(runPlayerBlock(3, { touchedByTheLightRank: 1 })).toMatchObject({
|
||||
outcome: "block",
|
||||
effectiveAmount: ordinary.effectiveAmount,
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user