Update Healer Man server
This commit is contained in:
1 parent
35dfdaed61
commit
4fb7a0b9c8
161 files changed
+24618
-529
No files matched your search
@@ -0,0 +1,204 @@
|
||||
import { beforeEach, describe, expect, it } from "vitest";
|
||||
import { PLAYER_AGGRO_ID } from "./aggro";
|
||||
import { clearMobRuntimeRegistry, setMobPosition } from "./mobRuntimeRegistry";
|
||||
import { clearPartyRuntimeRegistry, registerPartyRuntimePosition } from "./partyRuntimeRegistry";
|
||||
import { usePartyStore } from "./partyStore";
|
||||
import { useCombatStore } from "./combatStore";
|
||||
import { DEFAULT_ENEMY_ATTACK } from "./enemyAttacks";
|
||||
import { deriveCharacterStats } from "./wotlkStats";
|
||||
import {
|
||||
WOW335_ROGUE_UNFAIR_ADVANTAGE_DAMAGE_ID,
|
||||
wow335RogueUnfairAdvantageDamageMultiplierForRank,
|
||||
} from "./wow335RogueTalentRuntime";
|
||||
|
||||
function stableUnit(seed: string): number {
|
||||
let hash = 2_166_136_261;
|
||||
for (let index = 0; index < seed.length; index += 1) {
|
||||
hash ^= seed.charCodeAt(index);
|
||||
hash = Math.imul(hash, 16_777_619);
|
||||
}
|
||||
return (hash >>> 0) / 0x1_0000_0000;
|
||||
}
|
||||
|
||||
function timeForDodge(
|
||||
mobId: string,
|
||||
attackId: string,
|
||||
attackRevision: number,
|
||||
earliest: number,
|
||||
actorId = "__player__",
|
||||
): number {
|
||||
for (let now = earliest; now < earliest + 30_000; now += 1) {
|
||||
const roll = stableUnit(`${mobId}:${attackId}:${actorId}:${attackRevision}:${now}:outcome`);
|
||||
if (roll >= 0.1 && roll < 0.99) return now;
|
||||
}
|
||||
throw new Error(`Could not find a deterministic dodge roll for ${mobId}:${attackRevision}`);
|
||||
}
|
||||
|
||||
describe("WoW Rogue Unfair Advantage", () => {
|
||||
beforeEach(() => {
|
||||
clearMobRuntimeRegistry();
|
||||
clearPartyRuntimeRegistry();
|
||||
useCombatStore.getState().initializeCharacter({ classId: "rogue", level: 80 });
|
||||
useCombatStore.getState().setPlayerPosition([0, 0, 0]);
|
||||
});
|
||||
|
||||
const setUpDodgeTarget = (id: string, rank: number, repeatCooldownMs = 3_000) => {
|
||||
const attackId = `${id}-swing`;
|
||||
useCombatStore.setState((state) => ({
|
||||
gearStats: {
|
||||
...state.gearStats,
|
||||
dodgeRating: 1_000_000,
|
||||
meleeWeapon: {
|
||||
damageMin: 100,
|
||||
damageMax: 100,
|
||||
damageSchool: "physical",
|
||||
speedMs: 2_000,
|
||||
},
|
||||
},
|
||||
talentRanks: { ...state.talentRanks, "talent-2073": rank },
|
||||
}));
|
||||
useCombatStore.getState().registerMob(id, {
|
||||
name: id,
|
||||
level: 80,
|
||||
maxHealth: 100_000,
|
||||
xpReward: 0,
|
||||
attacks: [{
|
||||
...DEFAULT_ENEMY_ATTACK,
|
||||
id: attackId,
|
||||
initialCooldownMs: [0, 0],
|
||||
repeatCooldownMs: [repeatCooldownMs, repeatCooldownMs],
|
||||
}],
|
||||
});
|
||||
setMobPosition(id, [2, 0, 0]);
|
||||
useCombatStore.getState().damageMob(id, 1, 1_100);
|
||||
return attackId;
|
||||
};
|
||||
|
||||
it.each([[1, 0.5], [2, 1]] as const)(
|
||||
"returns the generated rank %i main-hand damage fraction after a dodge",
|
||||
(rank, multiplier) => {
|
||||
const targetId = `unfair-advantage-rank-${rank}`;
|
||||
const attackId = setUpDodgeTarget(targetId, rank);
|
||||
const state = useCombatStore.getState();
|
||||
const weapon = state.gearStats.meleeWeapon!;
|
||||
const stats = deriveCharacterStats(
|
||||
state.classId,
|
||||
state.level,
|
||||
state.gearStats,
|
||||
state.raceId,
|
||||
{},
|
||||
state.secondaryClassId,
|
||||
);
|
||||
const expectedRawDamage = Math.round((
|
||||
(weapon.damageMin + weapon.damageMax) / 2
|
||||
+ stats.attackPower / 14 * weapon.speedMs / 1_000
|
||||
) * multiplier);
|
||||
|
||||
const dodgeAt = timeForDodge(targetId, attackId, 0, 1_450);
|
||||
useCombatStore.getState().advanceMobCombat(dodgeAt);
|
||||
|
||||
const retaliation = useCombatStore.getState().combatEvents.find((event) => (
|
||||
event.kind === "damage"
|
||||
&& event.sourceActorId === PLAYER_AGGRO_ID
|
||||
&& event.targetActorId === targetId
|
||||
&& event.abilityId === WOW335_ROGUE_UNFAIR_ADVANTAGE_DAMAGE_ID
|
||||
));
|
||||
expect(useCombatStore.getState().combatEvents.some((event) => (
|
||||
event.kind === "damage"
|
||||
&& event.sourceActorId === targetId
|
||||
&& event.targetActorId === PLAYER_AGGRO_ID
|
||||
&& event.outcome === "dodge"
|
||||
))).toBe(true);
|
||||
expect(retaliation?.rawAmount).toBe(expectedRawDamage);
|
||||
expect(wow335RogueUnfairAdvantageDamageMultiplierForRank(rank)).toBe(multiplier);
|
||||
},
|
||||
);
|
||||
|
||||
it("shares one two-second cooldown across repeated dodges", () => {
|
||||
const targetId = "unfair-advantage-cooldown-target";
|
||||
const attackId = setUpDodgeTarget(targetId, 2, 500);
|
||||
const procTimes: number[] = [];
|
||||
let attackAt = timeForDodge(targetId, attackId, 0, 1_450);
|
||||
|
||||
for (let attackRevision = 0; attackRevision < 12; attackRevision += 1) {
|
||||
useCombatStore.getState().advanceMobCombat(attackAt);
|
||||
const procs = useCombatStore.getState().combatEvents.filter((event) => (
|
||||
event.kind === "damage" && event.abilityId === WOW335_ROGUE_UNFAIR_ADVANTAGE_DAMAGE_ID
|
||||
));
|
||||
if (procs.length > procTimes.length) procTimes.push(attackAt);
|
||||
attackAt = timeForDodge(targetId, attackId, attackRevision + 1, attackAt + 500);
|
||||
if (procTimes.length === 2) break;
|
||||
}
|
||||
|
||||
expect(procTimes).toHaveLength(2);
|
||||
expect(procTimes[1]! - procTimes[0]!).toBeGreaterThanOrEqual(2_000);
|
||||
});
|
||||
|
||||
it("uses a companion Rogue's rank, melee weapon, and individual cooldown on dodge", () => {
|
||||
const playerHealth = useCombatStore.getState().health;
|
||||
usePartyStore.getState().initializeParty("unfair-advantage-companion", 80, playerHealth, "damage", 2);
|
||||
const initialMember = usePartyStore.getState().members[0]!;
|
||||
const memberId = initialMember.id;
|
||||
usePartyStore.setState((state) => ({
|
||||
members: state.members.map((member) => member.id === memberId
|
||||
? {
|
||||
...member,
|
||||
classId: "rogue",
|
||||
talentRanks: { ...member.talentRanks, "talent-2073": 2 },
|
||||
gearStats: {
|
||||
...member.gearStats,
|
||||
dodgeRating: 1_000_000,
|
||||
meleeWeapon: {
|
||||
damageMin: 100,
|
||||
damageMax: 100,
|
||||
damageSchool: "physical" as const,
|
||||
speedMs: 2_000,
|
||||
},
|
||||
},
|
||||
}
|
||||
: member),
|
||||
}));
|
||||
registerPartyRuntimePosition(memberId, [1, 0, 0]);
|
||||
|
||||
const targetId = "unfair-advantage-companion-target";
|
||||
const attackId = `${targetId}-swing`;
|
||||
useCombatStore.getState().registerMob(targetId, {
|
||||
name: targetId,
|
||||
level: 80,
|
||||
maxHealth: 100_000,
|
||||
xpReward: 0,
|
||||
attacks: [{
|
||||
...DEFAULT_ENEMY_ATTACK,
|
||||
id: attackId,
|
||||
initialCooldownMs: [0, 0],
|
||||
repeatCooldownMs: [3_000, 3_000],
|
||||
}],
|
||||
});
|
||||
setMobPosition(targetId, [2, 0, 0]);
|
||||
const member = usePartyStore.getState().members.find((candidate) => candidate.id === memberId)!;
|
||||
useCombatStore.getState().damageMob(targetId, 1, 1_100, {
|
||||
actorId: memberId,
|
||||
role: member.role,
|
||||
abilityId: "test-companion-engage",
|
||||
});
|
||||
const dodgeAt = timeForDodge(targetId, attackId, 0, 1_450, memberId);
|
||||
useCombatStore.getState().advanceMobCombat(dodgeAt);
|
||||
|
||||
const retaliation = useCombatStore.getState().combatEvents.find((event) => (
|
||||
event.kind === "damage"
|
||||
&& event.sourceActorId === memberId
|
||||
&& event.targetActorId === targetId
|
||||
&& event.abilityId === WOW335_ROGUE_UNFAIR_ADVANTAGE_DAMAGE_ID
|
||||
));
|
||||
expect(useCombatStore.getState().combatEvents.some((event) => (
|
||||
event.kind === "damage"
|
||||
&& event.sourceActorId === targetId
|
||||
&& event.targetActorId === memberId
|
||||
&& event.outcome === "dodge"
|
||||
))).toBe(true);
|
||||
expect(retaliation?.rawAmount).toBeGreaterThan(0);
|
||||
expect(usePartyStore.getState().members.find((candidate) => candidate.id === memberId)?.cooldowns[
|
||||
WOW335_ROGUE_UNFAIR_ADVANTAGE_DAMAGE_ID
|
||||
]).toBe(dodgeAt + 2_000);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user