Update Healer Man server
This commit is contained in:
1 parent
1fedb9368d
commit
cc3b2fa4d5
27 files changed
+1988
-83
No files matched your search
@@ -211,7 +211,7 @@ describe("shared combat rotations", () => {
|
||||
{ classId: "priest" as const, specialization: "Discipline", id: "priest:discipline", actions: ["Penance", "Power Word: Shield", "Flash Heal", "Greater Heal", "Renew", "Dispel Magic", "Cure Disease", "Resurrection", "Pain Suppression", "Inner Focus", "Power Infusion", "Shadowfiend"] },
|
||||
{ classId: "hunter" as const, specialization: "Marksmanship", id: "hunter:marksmanship", actions: ["Call Pet", "Aspect of the Dragonhawk", "Serpent Sting", "Chimera Shot", "Aimed Shot", "Arcane Shot", "Steady Shot", "Kill Shot", "Rapid Fire", "Readiness", "Misdirection", "Feign Death", "Silencing Shot", "Disengage"] },
|
||||
{ classId: "mage" as const, specialization: "Fire", id: "mage:fire", actions: ["Molten Armor", "Living Bomb", "Fireball", "Scorch", "Fire Blast", "Combustion", "Mirror Image", "Counterspell", "Fire Ward", "Frost Ward", "Ice Block", "Blink"] },
|
||||
{ classId: "rogue" as const, specialization: "Assassination", id: "rogue:assassination", actions: ["Instant Poison", "Deadly Poison", "Mutilate", "Rupture", "Envenom", "Cold Blood", "Kick", "Tricks of the Trade", "Evasion", "Cloak of Shadows", "Vanish"] },
|
||||
{ classId: "rogue" as const, specialization: "Assassination", id: "rogue:assassination", actions: ["Instant Poison", "Deadly Poison", "Mutilate", "Shiv", "Rupture", "Envenom", "Cold Blood", "Kick", "Tricks of the Trade", "Evasion", "Cloak of Shadows", "Vanish"] },
|
||||
];
|
||||
for (const contract of contracts) {
|
||||
const profile = rotationProfileFor(contract.classId, contract.id)!;
|
||||
@@ -263,7 +263,7 @@ describe("shared combat rotations", () => {
|
||||
const hunter = context({ classId: "hunter", specializationId: "hunter:marksmanship", primaryResource: "mana", resources: { mana: 10_000 }, maximumResources: { mana: 10_000 }, friendlyTargets: [{ id: "tank", health: 1_000, maxHealth: 1_000, role: "tank" }] });
|
||||
expect(evaluateCombatRotation({ ...hunter, abilities: [misdirection], hostileTarget: { id: "boss", health: 100_000, maxHealth: 100_000, boss: true } }))
|
||||
.toMatchObject({ ability: { name: "Misdirection" }, targetId: "tank" });
|
||||
expect(evaluateCombatRotation({ ...hunter, abilities: [disengage], moving: true })?.ability.name).toBe("Disengage");
|
||||
expect(evaluateCombatRotation({ ...hunter, abilities: [disengage], needsEscape: true })?.ability.name).toBe("Disengage");
|
||||
});
|
||||
|
||||
it("keeps specialization talent actions isolated", () => {
|
||||
@@ -310,11 +310,83 @@ describe("shared combat rotations", () => {
|
||||
expect(defensive, `${row.specializationId}:${row.defensive}`).toBeDefined();
|
||||
const defensiveContext = row.defensive === "Feign Death"
|
||||
? { ...shared, abilities: [defensive], hasDangerousThreat: true }
|
||||
: { ...shared, abilities: [defensive], health: 250 };
|
||||
: row.defensive === "Evasion"
|
||||
? { ...shared, abilities: [defensive], health: 400, enemyUsesMeleeAttacks: true }
|
||||
: { ...shared, abilities: [defensive], health: 250 };
|
||||
expect(evaluateCombatRotation(defensiveContext)?.ability.name).toBe(row.defensive);
|
||||
}
|
||||
});
|
||||
|
||||
it("prepares emergency healing with Inner Focus before direct or area healing", () => {
|
||||
const talents = runtimePartyTalentRanksForLevel("priest", "Discipline", 80);
|
||||
const innerFocus = learnedAbilityForBuild(ability("priest", "Inner Focus"), 80, talents)!;
|
||||
const priest = context({
|
||||
classId: "priest",
|
||||
specializationId: "priest:discipline",
|
||||
role: "healer",
|
||||
primaryResource: "mana",
|
||||
resources: { mana: 10_000 },
|
||||
maximumResources: { mana: 10_000 },
|
||||
abilities: [innerFocus],
|
||||
});
|
||||
|
||||
expect(evaluateCombatRotation({
|
||||
...priest,
|
||||
friendlyTargets: [{ id: "tank", health: 400, maxHealth: 1_000, role: "tank" }],
|
||||
})).toMatchObject({ ability: { name: "Inner Focus" }, ruleId: "prepare-emergency-heal" });
|
||||
expect(evaluateCombatRotation({
|
||||
...priest,
|
||||
friendlyTargets: [
|
||||
{ id: "tank", health: 600, maxHealth: 1_000, role: "tank" },
|
||||
{ id: "rogue", health: 600, maxHealth: 1_000, role: "damage" },
|
||||
],
|
||||
})).toMatchObject({ ability: { name: "Inner Focus" }, ruleId: "prepare-area-heal" });
|
||||
expect(evaluateCombatRotation({
|
||||
...priest,
|
||||
friendlyTargets: [{ id: "tank", health: 800, maxHealth: 1_000, role: "tank" }],
|
||||
})).toBeNull();
|
||||
});
|
||||
|
||||
it("uses Rogue defenses for boss threat, melee danger, and lethal threat resets", () => {
|
||||
const talents = runtimePartyTalentRanksForLevel("rogue", "Assassination", 80);
|
||||
const evasion = learnedAbilityForBuild(ability("rogue", "Evasion"), 80, talents)!;
|
||||
const vanish = learnedAbilityForBuild(ability("rogue", "Vanish"), 80, talents)!;
|
||||
const rogue = context({
|
||||
classId: "rogue",
|
||||
specializationId: "rogue:assassination",
|
||||
primaryResource: "energy",
|
||||
resources: { energy: 100 },
|
||||
maximumResources: { energy: 100 },
|
||||
hostileTarget: { id: "boss", health: 100_000, maxHealth: 100_000, boss: true },
|
||||
});
|
||||
|
||||
expect(evaluateCombatRotation({ ...rogue, abilities: [evasion], hasDangerousThreat: true })?.ability.name)
|
||||
.toBe("Evasion");
|
||||
expect(evaluateCombatRotation({ ...rogue, abilities: [evasion], health: 400,
|
||||
enemyUsesMeleeAttacks: true })?.ability.name).toBe("Evasion");
|
||||
expect(evaluateCombatRotation({ ...rogue, abilities: [evasion], incomingMeleeDanger: true }))
|
||||
.toMatchObject({ ability: { name: "Evasion" }, ruleId: "preemptive-melee-evasion" });
|
||||
expect(evaluateCombatRotation({ ...rogue, abilities: [vanish], hasDangerousThreat: true })?.ability.name)
|
||||
.toBe("Vanish");
|
||||
expect(evaluateCombatRotation({ ...rogue, abilities: [vanish], health: 200 }))
|
||||
.toMatchObject({ ability: { name: "Vanish" }, ruleId: "emergency-vanish" });
|
||||
});
|
||||
|
||||
it("uses Blink and Disengage to create distance, while Fire Blast covers movement", () => {
|
||||
const mage = context({ classId: "mage", specializationId: "mage:fire", primaryResource: "mana",
|
||||
resources: { mana: 10_000 }, maximumResources: { mana: 10_000 } });
|
||||
expect(evaluateCombatRotation({ ...mage, abilities: [ability("mage", "Blink")], needsEscape: true })?.ability.name)
|
||||
.toBe("Blink");
|
||||
expect(evaluateCombatRotation({ ...mage, abilities: [ability("mage", "Fire Blast")], moving: true })?.ability.name)
|
||||
.toBe("Fire Blast");
|
||||
|
||||
const talents = runtimePartyTalentRanksForLevel("hunter", "Marksmanship", 80);
|
||||
const disengage = learnedAbilityForBuild(ability("hunter", "Disengage"), 80, talents)!;
|
||||
const hunter = context({ classId: "hunter", specializationId: "hunter:marksmanship", primaryResource: "mana",
|
||||
resources: { mana: 10_000 }, maximumResources: { mana: 10_000 }, abilities: [disengage], needsEscape: true });
|
||||
expect(evaluateCombatRotation(hunter)?.ability.name).toBe("Disengage");
|
||||
});
|
||||
|
||||
it("uses Marksmanship Readiness only on a durable target", () => {
|
||||
const talents = runtimePartyTalentRanksForLevel("hunter", "Marksmanship", 80);
|
||||
const readiness = learnedAbilityForBuild(ability("hunter", "Readiness"), 80, talents)!;
|
||||
@@ -344,6 +416,22 @@ describe("shared combat rotations", () => {
|
||||
expect(finishing?.ability.id).toBe(finisher.id);
|
||||
});
|
||||
|
||||
it("uses Envenom as the Playerbots Assassination finisher ahead of Rupture", () => {
|
||||
const talents = runtimePartyTalentRanksForLevel("rogue", "Assassination", 80);
|
||||
const rupture = learnedAbilityForBuild(ability("rogue", "Rupture"), 80, talents)!;
|
||||
const envenom = learnedAbilityForBuild(ability("rogue", "Envenom"), 80, talents)!;
|
||||
const assassination = context({
|
||||
specializationId: "rogue:assassination",
|
||||
comboPoints: 5,
|
||||
comboTargetId: "target",
|
||||
abilities: [envenom, rupture],
|
||||
});
|
||||
|
||||
expect(evaluateCombatRotation(assassination)?.ability.name).toBe("Envenom");
|
||||
expect(evaluateCombatRotation({ ...assassination, maintainedAbilityIds: new Set([rupture.id]) })?.ability.name)
|
||||
.toBe("Envenom");
|
||||
});
|
||||
|
||||
it("reacts before throughput and conserves long offensive cooldowns for durable targets", () => {
|
||||
const kick = ability("rogue", "Kick");
|
||||
const strike = ability("rogue", "Sinister Strike");
|
||||
|
||||
Reference in new issue
Block a user