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,194 @@
|
||||
import { beforeEach, describe, expect, it } from "vitest";
|
||||
import { PLAYER_AGGRO_ID } from "./aggro";
|
||||
import { applyAura } from "./combatAuras";
|
||||
import { clearMobRuntimeRegistry, setMobPosition } from "./mobRuntimeRegistry";
|
||||
import { clearPartyRuntimeRegistry, updatePartyRuntimePosition } from "./partyRuntimeRegistry";
|
||||
import { useCombatStore, PLAYER_AURA_ENTITY_ID } from "./combatStore";
|
||||
import { usePartyStore } from "./partyStore";
|
||||
import type { EnemyAttackDefinition } from "./enemyAttacks";
|
||||
import { WOW335_DRUID_PREDATORY_INSTINCTS_TALENT_ID } from "./wow335DruidPredatoryInstinctsRuntime";
|
||||
|
||||
const TARGET_ID = "druid-predatory-instincts-target";
|
||||
const ENEMY_ID = "druid-predatory-instincts-area-enemy";
|
||||
const AREA_ATTACK: EnemyAttackDefinition = {
|
||||
id: "predatory-area-hit",
|
||||
name: "Predatory Instincts Area Hit",
|
||||
delivery: "area",
|
||||
target: "nearby-party",
|
||||
school: "fire",
|
||||
animation: "cast",
|
||||
castTimeMs: 0,
|
||||
range: 80,
|
||||
radius: 80,
|
||||
cooldownMs: 10_000,
|
||||
initialCooldownMs: [0, 0],
|
||||
repeatCooldownMs: [10_000, 10_000],
|
||||
damageMultiplier: 1,
|
||||
};
|
||||
|
||||
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 areaHitTime(actorId: string): number {
|
||||
for (let now = 3_101; now < 20_000; now += 1) {
|
||||
if (stableUnit(`${ENEMY_ID}:${AREA_ATTACK.id}:${actorId}:0:${now}:outcome`) >= 0.2) return now;
|
||||
}
|
||||
throw new Error(`Could not find an area-hit roll for ${actorId}`);
|
||||
}
|
||||
|
||||
function catFormAura(actorId: string, now = 1_000) {
|
||||
return {
|
||||
definition: {
|
||||
id: "wow335:768:cat-form",
|
||||
name: "Cat Form",
|
||||
disposition: "buff" as const,
|
||||
durationMs: null,
|
||||
},
|
||||
sourceId: actorId,
|
||||
targetId: actorId,
|
||||
now,
|
||||
};
|
||||
}
|
||||
|
||||
function setCatForm(actorId: string): void {
|
||||
useCombatStore.setState((state) => ({
|
||||
auras: [...applyAura(state.auras, catFormAura(actorId)).auras],
|
||||
}));
|
||||
}
|
||||
|
||||
function registerCriticalTarget(): void {
|
||||
useCombatStore.getState().registerMob(TARGET_ID, { name: TARGET_ID, maxHealth: 1_000_000, xpReward: 0 });
|
||||
useCombatStore.setState((state) => ({
|
||||
mobs: {
|
||||
...state.mobs,
|
||||
[TARGET_ID]: {
|
||||
...state.mobs[TARGET_ID]!,
|
||||
characterDefender: {
|
||||
level: 80,
|
||||
armor: 0,
|
||||
dodgePercent: 0,
|
||||
parryPercent: 0,
|
||||
blockPercent: 0,
|
||||
blockValue: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
}));
|
||||
setMobPosition(TARGET_ID, [4, 0, 0]);
|
||||
}
|
||||
|
||||
function criticalDamageForSource(sourceActorId: string, abilityId: string): number {
|
||||
const member = usePartyStore.getState().members.find((candidate) => candidate.id === sourceActorId);
|
||||
useCombatStore.getState().damageMob(TARGET_ID, 100, 2_000, {
|
||||
actorId: sourceActorId,
|
||||
role: member?.role ?? "damage",
|
||||
abilityId,
|
||||
}, "physical", {
|
||||
attackKind: "melee",
|
||||
seed: `druid-predatory-critical-${sourceActorId}-${abilityId}`,
|
||||
abilityId,
|
||||
criticalChanceBonusPercent: 1_000,
|
||||
rules: { canMiss: false, canCrit: true, canDodge: false, canParry: false, canBlock: false },
|
||||
});
|
||||
return useCombatStore.getState().lastCombatResult!.damageBeforeMitigation;
|
||||
}
|
||||
|
||||
function areaDamageForActor(actorKind: "player" | "companion", rank: number): number {
|
||||
clearMobRuntimeRegistry();
|
||||
clearPartyRuntimeRegistry();
|
||||
useCombatStore.getState().initializeCharacter({ classId: "druid", level: 80, xp: 0 });
|
||||
useCombatStore.getState().setPlayerPosition([0, 0, 0]);
|
||||
usePartyStore.getState().initializeParty(`druid-predatory-area-${actorKind}`, 1, useCombatStore.getState().health);
|
||||
const firstMember = usePartyStore.getState().members[0]!;
|
||||
usePartyStore.setState((state) => ({
|
||||
members: state.members.map((member, index) => index === 0
|
||||
? {
|
||||
...member,
|
||||
classId: "druid",
|
||||
talentRanks: { ...member.talentRanks, [WOW335_DRUID_PREDATORY_INSTINCTS_TALENT_ID]: rank },
|
||||
}
|
||||
: member),
|
||||
}));
|
||||
for (const member of usePartyStore.getState().members) updatePartyRuntimePosition(member.id, 0, 0, 0);
|
||||
useCombatStore.getState().registerMob(ENEMY_ID, {
|
||||
name: ENEMY_ID,
|
||||
level: 80,
|
||||
maxHealth: 1_000,
|
||||
xpReward: 0,
|
||||
attacks: [{
|
||||
...AREA_ATTACK,
|
||||
target: actorKind === "player" ? "primary" : "nearby-party",
|
||||
}],
|
||||
});
|
||||
setMobPosition(ENEMY_ID, [2, 0, 0]);
|
||||
useCombatStore.setState((state) => ({
|
||||
talentRanks: { ...state.talentRanks, [WOW335_DRUID_PREDATORY_INSTINCTS_TALENT_ID]: rank },
|
||||
mobs: { ...state.mobs, [ENEMY_ID]: { ...state.mobs[ENEMY_ID]!, attackDamage: 100 } },
|
||||
}));
|
||||
|
||||
const actorId = actorKind === "player" ? PLAYER_AURA_ENTITY_ID : firstMember.id;
|
||||
if (actorKind === "player") setCatForm(actorId);
|
||||
else {
|
||||
useCombatStore.setState((state) => ({ auras: [...applyAura(state.auras, catFormAura(actorId)).auras] }));
|
||||
}
|
||||
const healthBefore = actorKind === "player"
|
||||
? useCombatStore.getState().health
|
||||
: usePartyStore.getState().members.find((member) => member.id === actorId)!.health;
|
||||
useCombatStore.getState().damageMob(ENEMY_ID, 1, 1_449);
|
||||
useCombatStore.getState().advanceMobCombat(1_450);
|
||||
useCombatStore.getState().advanceMobCombat(areaHitTime(actorId));
|
||||
const healthAfter = actorKind === "player"
|
||||
? useCombatStore.getState().health
|
||||
: usePartyStore.getState().members.find((member) => member.id === actorId)!.health;
|
||||
return healthBefore - healthAfter;
|
||||
}
|
||||
|
||||
describe("WoW Druid Predatory Instincts integration", () => {
|
||||
beforeEach(() => {
|
||||
clearMobRuntimeRegistry();
|
||||
clearPartyRuntimeRegistry();
|
||||
useCombatStore.getState().initializeCharacter({ classId: "druid", level: 80, xp: 0 });
|
||||
usePartyStore.getState().initializeParty("druid-predatory-critical-tests", 1, useCombatStore.getState().health);
|
||||
});
|
||||
|
||||
it("applies the Cat Form critical-damage rank to the player and generated companion", () => {
|
||||
registerCriticalTarget();
|
||||
useCombatStore.setState((state) => ({
|
||||
talentRanks: { ...state.talentRanks, [WOW335_DRUID_PREDATORY_INSTINCTS_TALENT_ID]: 3 },
|
||||
}));
|
||||
const playerWithoutCat = criticalDamageForSource(PLAYER_AGGRO_ID, "predatory-player-test");
|
||||
setCatForm(PLAYER_AURA_ENTITY_ID);
|
||||
const playerWithCat = criticalDamageForSource(PLAYER_AGGRO_ID, "predatory-player-test-cat");
|
||||
|
||||
const member = usePartyStore.getState().members[0]!;
|
||||
usePartyStore.setState((state) => ({
|
||||
members: state.members.map((candidate) => candidate.id === member.id ? {
|
||||
...candidate,
|
||||
classId: "druid",
|
||||
talentRanks: { ...candidate.talentRanks, [WOW335_DRUID_PREDATORY_INSTINCTS_TALENT_ID]: 3 },
|
||||
} : candidate),
|
||||
}));
|
||||
const companionWithoutCat = criticalDamageForSource(member.id, "predatory-companion-test");
|
||||
setCatForm(member.id);
|
||||
const companionWithCat = criticalDamageForSource(member.id, "predatory-companion-test-cat");
|
||||
|
||||
expect(playerWithoutCat).toBe(200);
|
||||
expect(playerWithCat).toBe(215);
|
||||
expect(companionWithoutCat).toBe(200);
|
||||
expect(companionWithCat).toBe(215);
|
||||
});
|
||||
|
||||
it.each(["player", "companion"] as const)("reduces only area attack damage for a Cat Form %s", (actorKind) => {
|
||||
const withoutTalent = areaDamageForActor(actorKind, 0);
|
||||
const withTalent = areaDamageForActor(actorKind, 3);
|
||||
|
||||
expect(withoutTalent).toBeGreaterThan(0);
|
||||
expect(withTalent).toBe(Math.round(withoutTalent * 0.9));
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user