Update Healer Man server
This commit is contained in:
1 parent
f8314e7de1
commit
1fedb9368d
76 files changed
+20556
-2523
No files matched your search
@@ -143,6 +143,33 @@ describe("shared combat rotations", () => {
|
||||
expect(abilityIsPrecombatPreparation(temporaryPet)).toBe(false);
|
||||
});
|
||||
|
||||
it("uses an eligible defensive before a configured incoming boss mechanic", () => {
|
||||
const defensive = ability("paladin", "Divine Protection");
|
||||
const decision = evaluateCombatRotation(context({
|
||||
classId: "paladin",
|
||||
specializationId: "paladin:protection",
|
||||
role: "tank",
|
||||
primaryResource: "mana",
|
||||
resources: { mana: 10_000 },
|
||||
maximumResources: { mana: 10_000 },
|
||||
abilities: [defensive],
|
||||
incomingDefensiveMechanic: {
|
||||
id: "tank-buster",
|
||||
enabled: true,
|
||||
bossKey: "test/boss",
|
||||
bossName: "Test Boss",
|
||||
bossServerEntry: 1,
|
||||
attackId: "heavy-strike",
|
||||
attackName: "Heavy Strike",
|
||||
scope: "tank",
|
||||
leadTimeMs: 2_500,
|
||||
minimumCooldownSeconds: 20,
|
||||
},
|
||||
}));
|
||||
expect(decision?.ability.name).toBe("Divine Protection");
|
||||
expect(decision?.ruleId).toBe("preemptive-tank-buster");
|
||||
});
|
||||
|
||||
it("gates representative signature abilities from every class by their exact talent node", () => {
|
||||
for (const [classId, name] of Object.entries(SIGNATURES)) {
|
||||
const signature = ability(classId as RotationContext["classId"], name);
|
||||
@@ -178,6 +205,67 @@ describe("shared combat rotations", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("keeps every required Ready-spec mechanic in its learned Playerbots profile", () => {
|
||||
const contracts = [
|
||||
{ classId: "warrior" as const, specialization: "Protection", id: "warrior:protection", actions: ["Defensive Stance", "Taunt", "Shield Bash", "Shield Slam", "Revenge", "Devastate", "Thunder Clap", "Shield Block", "Shield Wall", "Last Stand", "Enraged Regeneration"] },
|
||||
{ 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"] },
|
||||
];
|
||||
for (const contract of contracts) {
|
||||
const profile = rotationProfileFor(contract.classId, contract.id)!;
|
||||
const talents = runtimePartyTalentRanksForLevel(contract.classId, contract.specialization, 80);
|
||||
for (const name of contract.actions) {
|
||||
expect(profile.abilityPriority, `${contract.id}:${name} priority`).toContain(name);
|
||||
expect(learnedAbilityForBuild(ability(contract.classId, name), 80, talents), `${contract.id}:${name} learned`)
|
||||
.not.toBeNull();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it("executes Ready-spec taunts, dispels, resurrection, interrupts, threat redirects, and movement reactively", () => {
|
||||
const warriorTalents = runtimePartyTalentRanksForLevel("warrior", "Protection", 80);
|
||||
const taunt = learnedAbilityForBuild(ability("warrior", "Taunt"), 80, warriorTalents)!;
|
||||
expect(evaluateCombatRotation(context({ classId: "warrior", specializationId: "warrior:protection", role: "tank", primaryResource: "rage", resources: { rage: 100 }, maximumResources: { rage: 100 }, abilities: [taunt], targetIsLoose: true }))?.ability.name)
|
||||
.toBe("Taunt");
|
||||
|
||||
const priestTalents = runtimePartyTalentRanksForLevel("priest", "Discipline", 80);
|
||||
const dispel = learnedAbilityForBuild(ability("priest", "Dispel Magic"), 80, priestTalents)!;
|
||||
const resurrection = learnedAbilityForBuild(ability("priest", "Resurrection"), 80, priestTalents)!;
|
||||
const priest = context({ classId: "priest", specializationId: "priest:discipline", role: "healer", primaryResource: "mana", resources: { mana: 10_000 }, maximumResources: { mana: 10_000 } });
|
||||
expect(evaluateCombatRotation({ ...priest, abilities: [dispel], friendlyTargets: [{ id: "ally", health: 500, maxHealth: 1_000, needsDispel: true }] })?.ability.name)
|
||||
.toBe("Dispel Magic");
|
||||
expect(evaluateCombatRotation({ ...priest, abilities: [resurrection], friendlyTargets: [{ id: "ally", health: 0, maxHealth: 1_000, fallen: true }] })?.ability.name)
|
||||
.toBe("Resurrection");
|
||||
|
||||
for (const row of [
|
||||
{ classId: "hunter" as const, specializationId: "hunter:marksmanship", specialization: "Marksmanship", name: "Silencing Shot", resource: "mana" as const },
|
||||
{ classId: "mage" as const, specializationId: "mage:fire", specialization: "Fire", name: "Counterspell", resource: "mana" as const },
|
||||
{ classId: "rogue" as const, specializationId: "rogue:assassination", specialization: "Assassination", name: "Kick", resource: "energy" as const },
|
||||
]) {
|
||||
const learned = learnedAbilityForBuild(ability(row.classId, row.name), 80,
|
||||
runtimePartyTalentRanksForLevel(row.classId, row.specialization, 80))!;
|
||||
expect(evaluateCombatRotation(context({
|
||||
classId: row.classId,
|
||||
specializationId: row.specializationId,
|
||||
primaryResource: row.resource,
|
||||
resources: { [row.resource]: 10_000 },
|
||||
maximumResources: { [row.resource]: 10_000 },
|
||||
abilities: [learned],
|
||||
hostileTarget: { id: "caster", health: 10_000, maxHealth: 10_000, activeCast: { interruptible: true } },
|
||||
}))?.ability.name).toBe(row.name);
|
||||
}
|
||||
|
||||
const hunterTalents = runtimePartyTalentRanksForLevel("hunter", "Marksmanship", 80);
|
||||
const misdirection = learnedAbilityForBuild(ability("hunter", "Misdirection"), 80, hunterTalents)!;
|
||||
const disengage = learnedAbilityForBuild(ability("hunter", "Disengage"), 80, hunterTalents)!;
|
||||
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");
|
||||
});
|
||||
|
||||
it("keeps specialization talent actions isolated", () => {
|
||||
const furyTalents = runtimePartyTalentRanksForLevel("warrior", "Fury", 80);
|
||||
const armsTalents = runtimePartyTalentRanksForLevel("warrior", "Arms", 80);
|
||||
@@ -189,6 +277,60 @@ describe("shared combat rotations", () => {
|
||||
expect(learnedAbilityForBuild(bloodthirst, 80, armsTalents)).toBeNull();
|
||||
});
|
||||
|
||||
it("certifies major offensive and defensive cooldown decisions for the Ready five", () => {
|
||||
const cases = [
|
||||
{ classId: "warrior" as const, specialization: "Protection", specializationId: "warrior:protection", offensive: null, defensive: "Shield Wall", role: "tank" as const },
|
||||
{ classId: "priest" as const, specialization: "Discipline", specializationId: "priest:discipline", offensive: "Power Infusion", defensive: "Pain Suppression", role: "healer" as const },
|
||||
{ classId: "hunter" as const, specialization: "Marksmanship", specializationId: "hunter:marksmanship", offensive: "Rapid Fire", defensive: "Feign Death", role: "damage" as const },
|
||||
{ classId: "mage" as const, specialization: "Fire", specializationId: "mage:fire", offensive: "Combustion", defensive: "Ice Block", role: "damage" as const },
|
||||
{ classId: "rogue" as const, specialization: "Assassination", specializationId: "rogue:assassination", offensive: "Cold Blood", defensive: "Evasion", role: "damage" as const },
|
||||
];
|
||||
for (const row of cases) {
|
||||
const talents = runtimePartyTalentRanksForLevel(row.classId, row.specialization, 80);
|
||||
const learned = (name: string) => learnedAbilityForBuild(ability(row.classId, name), 80, talents)!;
|
||||
const primaryResource = resourceProfileForClass(row.classId).type;
|
||||
const shared = context({
|
||||
classId: row.classId,
|
||||
specializationId: row.specializationId,
|
||||
specialization: row.specialization,
|
||||
role: row.role,
|
||||
primaryResource,
|
||||
resources: { [primaryResource]: 100_000 },
|
||||
maximumResources: { [primaryResource]: 100_000 },
|
||||
hostileTarget: { id: "boss", health: 100_000, maxHealth: 100_000, boss: true },
|
||||
friendlyTargets: [{ id: "tank", health: 1_000, maxHealth: 1_000, role: "tank" }],
|
||||
});
|
||||
if (row.offensive) {
|
||||
const offensive = learned(row.offensive);
|
||||
expect(offensive, `${row.specializationId}:${row.offensive}`).toBeDefined();
|
||||
expect(evaluateCombatRotation({ ...shared, abilities: [offensive] })?.ability.name)
|
||||
.toBe(row.offensive);
|
||||
}
|
||||
const defensive = learned(row.defensive);
|
||||
expect(defensive, `${row.specializationId}:${row.defensive}`).toBeDefined();
|
||||
const defensiveContext = row.defensive === "Feign Death"
|
||||
? { ...shared, abilities: [defensive], hasDangerousThreat: true }
|
||||
: { ...shared, abilities: [defensive], health: 250 };
|
||||
expect(evaluateCombatRotation(defensiveContext)?.ability.name).toBe(row.defensive);
|
||||
}
|
||||
});
|
||||
|
||||
it("uses Marksmanship Readiness only on a durable target", () => {
|
||||
const talents = runtimePartyTalentRanksForLevel("hunter", "Marksmanship", 80);
|
||||
const readiness = learnedAbilityForBuild(ability("hunter", "Readiness"), 80, talents)!;
|
||||
const base = context({
|
||||
classId: "hunter",
|
||||
specializationId: "hunter:marksmanship",
|
||||
primaryResource: "mana",
|
||||
resources: { mana: 10_000 },
|
||||
maximumResources: { mana: 10_000 },
|
||||
abilities: [readiness],
|
||||
});
|
||||
expect(evaluateCombatRotation(base)).toBeNull();
|
||||
expect(evaluateCombatRotation({ ...base, hostileTarget: { id: "boss", health: 100_000, maxHealth: 100_000, boss: true } })?.ability.name)
|
||||
.toBe("Readiness");
|
||||
});
|
||||
|
||||
it("uses combo builders before finishers and actual combo points for the decision", () => {
|
||||
const builder = ability("rogue", "Sinister Strike");
|
||||
const finisher = ability("rogue", "Eviscerate");
|
||||
|
||||
Reference in new issue
Block a user