forked from phenom/healer-man
Update Healer Man server
This commit is contained in:
1 parent
1fedb9368d
commit
cc3b2fa4d5
27 files changed
+1988
-83
No files matched your search
@@ -25,6 +25,10 @@ import { deriveCharacterStats, MANA_POOL_MULTIPLIER } from "./wotlkStats";
|
||||
import { subscribeCombatPresentation, type CombatPresentationEvent } from "./combatPresentation";
|
||||
import { loadCanonicalBenchmarkBuilds } from "./benchmark/benchmarkBuilds";
|
||||
import { runtimePartyTalentRanksForLevel } from "./partyTalentRuntime";
|
||||
import {
|
||||
ROGUE_DEADLY_POISON_DOSE_TAG,
|
||||
ROGUE_ENVENOM_BUFF_TAG,
|
||||
} from "./roguePoison";
|
||||
|
||||
function allocateTalentRanks(name: string, ranks: number): void {
|
||||
const talent = TALENT_NODES.find((node) => node.name === name);
|
||||
@@ -667,6 +671,38 @@ describe("combat store", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("drops or redirects companion threat and immediately retargets the enemy", () => {
|
||||
const tank = usePartyStore.getState().members.find((member) => member.role === "tank")!;
|
||||
const damage = usePartyStore.getState().members.find((member) => member.role === "damage")!;
|
||||
registerPartyRuntimePosition(tank.id, [0, 0, 0]);
|
||||
registerPartyRuntimePosition(damage.id, [0, 0, 0]);
|
||||
useCombatStore.getState().registerMob("threat-effect-boss", {
|
||||
name: "Threat effect boss", boss: true, maxHealth: 1_000, xpReward: 0,
|
||||
});
|
||||
setMobPosition("threat-effect-boss", [2, 0, 0]);
|
||||
useCombatStore.getState().damageMob("threat-effect-boss", 10, 1_000,
|
||||
{ actorId: tank.id, role: "tank" });
|
||||
useCombatStore.getState().damageMob("threat-effect-boss", 100, 1_100,
|
||||
{ actorId: damage.id, role: "damage" });
|
||||
|
||||
useCombatStore.getState().applyActorThreatEffect(damage.id,
|
||||
{ kind: "threat", mode: "percent", amount: -100 }, null, 1_200);
|
||||
expect(useCombatStore.getState().mobs["threat-effect-boss"]).toMatchObject({
|
||||
targetActorId: tank.id,
|
||||
threatByActor: { [damage.id]: 0 },
|
||||
});
|
||||
|
||||
useCombatStore.getState().damageMob("threat-effect-boss", 80, 1_300,
|
||||
{ actorId: damage.id, role: "damage" });
|
||||
const tankThreat = useCombatStore.getState().mobs["threat-effect-boss"].threatByActor[tank.id];
|
||||
useCombatStore.getState().applyActorThreatEffect(damage.id,
|
||||
{ kind: "threat", mode: "redirect", amount: 100 }, tank.id, 1_400);
|
||||
expect(useCombatStore.getState().mobs["threat-effect-boss"].threatByActor).toMatchObject({
|
||||
[damage.id]: 0,
|
||||
[tank.id]: tankThreat + 80,
|
||||
});
|
||||
});
|
||||
|
||||
it("taunts bosses at max plus one, forces immediately, and resumes normal threat after expiry", () => {
|
||||
const tank = usePartyStore.getState().members.find((member) => member.role === "tank")!;
|
||||
registerPartyRuntimePosition(tank.id, [0, 0, 0]);
|
||||
@@ -1518,6 +1554,125 @@ describe("combat store", () => {
|
||||
expect(useCombatStore.getState()).toMatchObject(beforeFelVitality);
|
||||
});
|
||||
|
||||
it("builds, refreshes, and consumes source-specific Deadly Poison doses", () => {
|
||||
useCombatStore.getState().initializeCharacter({
|
||||
classId: "rogue",
|
||||
level: 80,
|
||||
specializationId: "rogue:assassination",
|
||||
talentRanks: runtimePartyTalentRanksForLevel("rogue", "Assassination", 80),
|
||||
});
|
||||
useCombatStore.getState().synchronizeActors();
|
||||
registerTarget("poison-target", [2, 0, 0], 1_000_000, 0);
|
||||
for (const abilityId of ["rogue-instant-poison", "rogue-deadly-poison"]) {
|
||||
const ability = useCombatStore.getState().abilities.find((candidate) => candidate.id === abilityId)!;
|
||||
const effect = ability.effects.find((candidate) => candidate.kind === "apply-aura")!;
|
||||
expect(useCombatStore.getState().applyActorAura(
|
||||
PLAYER_AURA_ENTITY_ID,
|
||||
PLAYER_AURA_ENTITY_ID,
|
||||
ability,
|
||||
effect,
|
||||
1_000,
|
||||
)).toBe(true);
|
||||
}
|
||||
|
||||
expect(useCombatStore.getState().triggerRogueWeaponPoisons(
|
||||
PLAYER_AURA_ENTITY_ID,
|
||||
"poison-target",
|
||||
"wow335-rogue-shiv",
|
||||
1,
|
||||
1_001,
|
||||
)).toMatchObject({ deadlyApplications: 1, deadlyStacks: 1 });
|
||||
|
||||
for (let hit = 0; hit < 200; hit += 1) {
|
||||
useCombatStore.getState().triggerRogueWeaponPoisons(
|
||||
PLAYER_AURA_ENTITY_ID,
|
||||
"poison-target",
|
||||
`test-hit-${hit}`,
|
||||
1,
|
||||
1_002 + hit,
|
||||
);
|
||||
}
|
||||
const stacked = useCombatStore.getState();
|
||||
expect(stacked.auras.find((aura) => (
|
||||
aura.targetId === "poison-target"
|
||||
&& aura.definition.utilityTags?.includes(ROGUE_DEADLY_POISON_DOSE_TAG)
|
||||
))?.stacks).toBe(5);
|
||||
expect(stacked.effects.find((effect) => (
|
||||
effect.targetId === "poison-target" && effect.sourceAbilityId === "rogue-deadly-poison"
|
||||
))).toMatchObject({ kind: "dot", remainingTicks: 4 });
|
||||
expect(stacked.combatEvents.some((event) => (
|
||||
event.abilityId === "rogue-instant-poison" && event.effectiveAmount > 0
|
||||
))).toBe(true);
|
||||
|
||||
expect(useCombatStore.getState().consumeRogueDeadlyPoison(
|
||||
PLAYER_AURA_ENTITY_ID,
|
||||
"poison-target",
|
||||
4,
|
||||
2_000,
|
||||
)).toBe(4);
|
||||
expect(useCombatStore.getState().auras.find((aura) => (
|
||||
aura.targetId === "poison-target"
|
||||
&& aura.definition.utilityTags?.includes(ROGUE_DEADLY_POISON_DOSE_TAG)
|
||||
))?.stacks).toBe(1);
|
||||
expect(useCombatStore.getState().consumeRogueDeadlyPoison(
|
||||
PLAYER_AURA_ENTITY_ID,
|
||||
"poison-target",
|
||||
5,
|
||||
2_001,
|
||||
)).toBe(1);
|
||||
expect(useCombatStore.getState().effects.some((effect) => (
|
||||
effect.targetId === "poison-target" && effect.sourceAbilityId === "rogue-deadly-poison"
|
||||
))).toBe(false);
|
||||
});
|
||||
|
||||
it("casts Envenom with dose-scaled Nature damage and a combo-scaled poison buff", () => {
|
||||
useCombatStore.getState().initializeCharacter({
|
||||
classId: "rogue",
|
||||
level: 80,
|
||||
specializationId: "rogue:assassination",
|
||||
talentRanks: runtimePartyTalentRanksForLevel("rogue", "Assassination", 80),
|
||||
});
|
||||
useCombatStore.getState().synchronizeActors();
|
||||
registerTarget("envenom-target", [2, 0, 0], 1_000_000, 0);
|
||||
for (const abilityId of ["rogue-instant-poison", "rogue-deadly-poison"]) {
|
||||
const ability = useCombatStore.getState().abilities.find((candidate) => candidate.id === abilityId)!;
|
||||
const effect = ability.effects.find((candidate) => candidate.kind === "apply-aura")!;
|
||||
useCombatStore.getState().applyActorAura(
|
||||
PLAYER_AURA_ENTITY_ID,
|
||||
PLAYER_AURA_ENTITY_ID,
|
||||
ability,
|
||||
effect,
|
||||
1_000,
|
||||
);
|
||||
}
|
||||
for (let hit = 0; hit < 200; hit += 1) {
|
||||
useCombatStore.getState().triggerRogueWeaponPoisons(
|
||||
PLAYER_AURA_ENTITY_ID,
|
||||
"envenom-target",
|
||||
`envenom-setup-${hit}`,
|
||||
1,
|
||||
1_001 + hit,
|
||||
);
|
||||
}
|
||||
useCombatStore.setState({ comboPoints: 5, comboTargetId: "envenom-target" });
|
||||
const envenom = useCombatStore.getState().abilities.find((ability) => ability.name === "Envenom")!;
|
||||
const result = useCombatStore.getState().castAbility(envenom.id, [0, 0, 0], 3_000);
|
||||
|
||||
expect(result).toMatchObject({ ok: true, abilityId: envenom.id });
|
||||
expect(result.damage).toBeGreaterThan(0);
|
||||
expect(useCombatStore.getState().auras.find((aura) => (
|
||||
aura.targetId === "envenom-target"
|
||||
&& aura.definition.utilityTags?.includes(ROGUE_DEADLY_POISON_DOSE_TAG)
|
||||
))?.stacks).toBe(5);
|
||||
expect(useCombatStore.getState().auras.find((aura) => (
|
||||
aura.targetId === PLAYER_AURA_ENTITY_ID
|
||||
&& aura.definition.utilityTags?.includes(ROGUE_ENVENOM_BUFF_TAG)
|
||||
))?.expiresAt).toBe(9_000);
|
||||
expect(useCombatStore.getState().combatEvents.some((event) => (
|
||||
event.abilityId === envenom.id && event.school === "nature" && event.effectiveAmount > 0
|
||||
))).toBe(true);
|
||||
});
|
||||
|
||||
it("applies Manastorm boss multipliers and a single bonus-loot chance at registration", () => {
|
||||
useCombatStore.getState().initializeCharacter({ classId: "priest", level: 10 });
|
||||
useCombatStore.getState().registerMob("storm-boss", {
|
||||
|
||||
Reference in new issue
Block a user