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
@@ -56,6 +56,8 @@ export interface RotationContext {
|
||||
readonly comboPoints: number;
|
||||
readonly comboTargetId: string | null;
|
||||
readonly runes: readonly DeathKnightRuneState[];
|
||||
/** Learned Reaping rank, used only to expose its otherwise-omitted Blood Strike trigger fallback. */
|
||||
readonly deathKnightReapingRank?: number;
|
||||
readonly abilities: readonly AbilityDefinition[];
|
||||
/** Learned actions capable of hostile damage, including unavailable ones. */
|
||||
readonly knownDamagingAbilityNames?: ReadonlySet<string>;
|
||||
@@ -76,6 +78,8 @@ export interface RotationContext {
|
||||
readonly activeAuraExclusiveGroups?: ReadonlySet<string>;
|
||||
readonly activeSummonAbilityIds?: ReadonlySet<string>;
|
||||
readonly hasActivePet?: boolean;
|
||||
/** True only when an in-range living ally has a depleted pool restored by this party-scoped effect. */
|
||||
readonly partyResourceDeficit?: (ability: AbilityDefinition) => boolean;
|
||||
/** Proc/state-gated actions that the caller has positively made available. */
|
||||
readonly availableConditionalAbilityNames?: ReadonlySet<string>;
|
||||
readonly canUseAbility?: (ability: AbilityDefinition) => boolean;
|
||||
@@ -104,9 +108,9 @@ const profile = (
|
||||
|
||||
/** Playerbots priorities translated to the executable Healer Man spell model. */
|
||||
export const CLASSIC_ROTATION_PROFILES: readonly RotationProfile[] = Object.freeze([
|
||||
profile("warrior:arms", "warrior", ["Battle Stance", "Bloodrage", "Rend", "Mortal Strike", "Bladestorm", "Overpower", "Execute", "Slam", "Heroic Strike"]),
|
||||
profile("warrior:fury", "warrior", ["Berserker Stance", "Battle Stance", "Bloodrage", "Bloodthirst", "Whirlwind", "Execute", "Sunder Armor", "Heroic Strike"]),
|
||||
profile("warrior:protection", "warrior", ["Defensive Stance", "Shield Block", "Last Stand", "Shield Wall", "Enraged Regeneration", "Taunt", "Shield Bash", "Bloodrage", "Shield Slam", "Revenge", "Devastate", "Thunder Clap", "Sunder Armor", "Heroic Strike"]),
|
||||
profile("warrior:arms", "warrior", ["Battle Stance", "Bloodrage", "Rend", "Mortal Strike", "Bladestorm", "Overpower", "Victory Rush", "Execute", "Slam", "Heroic Strike"]),
|
||||
profile("warrior:fury", "warrior", ["Berserker Stance", "Battle Stance", "Bloodrage", "Bloodthirst", "Whirlwind", "Victory Rush", "Execute", "Sunder Armor", "Heroic Strike"]),
|
||||
profile("warrior:protection", "warrior", ["Defensive Stance", "Shield Block", "Last Stand", "Shield Wall", "Enraged Regeneration", "Taunt", "Shield Bash", "Bloodrage", "Shield Slam", "Revenge", "Victory Rush", "Devastate", "Thunder Clap", "Sunder Armor", "Heroic Strike"]),
|
||||
profile("paladin:holy", "paladin", ["Concentration Aura", "Devotion Aura", "Blessing of Might", "Seal of Wisdom", "Seal of Light", "Seal of Righteousness", "Holy Shock", "Flash of Light", "Holy Light", "Sacred Shield", "Judgement of Light"]),
|
||||
profile("paladin:protection", "paladin", ["Devotion Aura", "Blessing of Might", "Seal of Vengeance", "Seal of Corruption", "Seal of Wisdom", "Seal of Righteousness", "Hammer of the Righteous", "Shield of Righteousness", "Holy Shield", "Consecration", "Judgement of Wisdom"]),
|
||||
profile("paladin:retribution", "paladin", ["Retribution Aura", "Devotion Aura", "Blessing of Might", "Seal of Command", "Seal of Vengeance", "Seal of Corruption", "Seal of Righteousness", "Divine Storm", "Crusader Strike", "Judgement of Wisdom", "Consecration", "Exorcism", "Hammer of Wrath"]),
|
||||
@@ -143,6 +147,7 @@ const PROFILE_BY_SPECIALIZATION = new Map(
|
||||
|
||||
const CONDITIONAL_ABILITY_NAMES = new Set([
|
||||
"Overpower",
|
||||
"Pestilence",
|
||||
"Revenge",
|
||||
"Victory Rush",
|
||||
]);
|
||||
@@ -163,6 +168,7 @@ const TARGET_HEALTH_GATES = new Map<string, number>([
|
||||
]);
|
||||
|
||||
const MINIMUM_HOSTILE_TARGETS = new Map<string, number>([
|
||||
["Pestilence", 2],
|
||||
["Multi-Shot", 2],
|
||||
["Volley", 3],
|
||||
["Flamestrike", 3],
|
||||
@@ -401,6 +407,15 @@ function scoreAbility(
|
||||
if ((context.hostileCount ?? 1) < minimumHostileTargetsForAbility(ability.name)) return null;
|
||||
if (CONDITIONAL_ABILITY_NAMES.has(ability.name)
|
||||
&& !context.availableConditionalAbilityNames?.has(ability.name)) return null;
|
||||
if (ability.name === "Victory Rush" && context.availableConditionalAbilityNames?.has("Victory Rush")) {
|
||||
return {
|
||||
ability,
|
||||
score: 1_016_500,
|
||||
ruleId: "reactive-improved-victory-rush",
|
||||
reason: "a parry opened the Victory Rush window",
|
||||
targetId: context.hostileTarget?.id ?? null,
|
||||
};
|
||||
}
|
||||
if (context.classId === "druid") {
|
||||
const specializationId = rotationProfile?.specializationId;
|
||||
if (specializationId === "druid:feral-bear" && CAT_FORM_ACTION_NAMES.has(ability.name)) return null;
|
||||
@@ -641,7 +656,18 @@ function scoreAbility(
|
||||
reason = "missing pet or summon";
|
||||
}
|
||||
}
|
||||
if (categories.has("resource")) score += resourceFraction < 0.3 ? 2_600 : -1_000;
|
||||
if (categories.has("resource")) {
|
||||
const partyResourceDeficit = ability.effects.some((effect) => (
|
||||
effect.kind === "resource" && effect.scope === "party"
|
||||
)) && (context.partyResourceDeficit?.(ability) ?? false);
|
||||
if (partyResourceDeficit) {
|
||||
score += 2_600;
|
||||
ruleId = "party-resource-restoration";
|
||||
reason = "an in-range ally has a depleted resource pool";
|
||||
} else {
|
||||
score += resourceFraction < 0.3 ? 2_600 : -1_000;
|
||||
}
|
||||
}
|
||||
if (damaging) score += 1_000 + throughput(ability, context.comboPoints) * 10;
|
||||
if (isComboFinisher(ability)) score += 2_400;
|
||||
if (isComboBuilder(ability)) score += context.comboPoints < 4 ? 1_400 : 0;
|
||||
@@ -655,11 +681,32 @@ function scoreAbility(
|
||||
}
|
||||
|
||||
export function evaluateCombatRotation(context: RotationContext): RotationDecision | null {
|
||||
const rotationProfile = rotationProfileFor(
|
||||
const baseRotationProfile = rotationProfileFor(
|
||||
context.classId,
|
||||
context.specializationId,
|
||||
context.specialization,
|
||||
);
|
||||
const hasSpendableBaseBloodRune = context.runes.some((rune) => (
|
||||
rune.baseType === "blood" && rune.readyAt <= context.now
|
||||
));
|
||||
const reapingRank = Math.max(0, Math.trunc(context.deathKnightReapingRank ?? 0));
|
||||
const reapingTriggerFallbacks: string[] = [];
|
||||
if (baseRotationProfile?.specializationId === "death-knight:blood"
|
||||
&& reapingRank > 0
|
||||
&& hasSpendableBaseBloodRune) {
|
||||
reapingTriggerFallbacks.push("Blood Strike");
|
||||
if (context.availableConditionalAbilityNames?.has("Pestilence")) {
|
||||
reapingTriggerFallbacks.push("Pestilence");
|
||||
}
|
||||
}
|
||||
const rotationProfile = baseRotationProfile && reapingTriggerFallbacks.length > 0
|
||||
? {
|
||||
...baseRotationProfile,
|
||||
// Keep the normal Blood priority intact. Reaping triggers are final
|
||||
// fallbacks, with Blood Strike preferred over Pestilence.
|
||||
abilityPriority: Object.freeze([...baseRotationProfile.abilityPriority, ...reapingTriggerFallbacks]),
|
||||
}
|
||||
: baseRotationProfile;
|
||||
const lowest = [...(context.friendlyTargets ?? [])]
|
||||
.filter((target) => target.health > 0 && !target.fallen)
|
||||
.sort((left, right) => (
|
||||
@@ -668,7 +715,10 @@ export function evaluateCombatRotation(context: RotationContext): RotationDecisi
|
||||
))[0] ?? null;
|
||||
const decision = context.abilities
|
||||
.filter((ability) => !ability.passive)
|
||||
.filter(abilityHasRotationSemantics)
|
||||
.filter((ability) => abilityHasRotationSemantics(ability)
|
||||
|| (ability.name === "Pestilence"
|
||||
&& reapingRank > 0
|
||||
&& context.availableConditionalAbilityNames?.has("Pestilence") === true))
|
||||
.filter((ability) => !context.moving || ability.castTimeMs <= 0 || canAbilityCastWhileMoving(ability))
|
||||
.filter((ability) => {
|
||||
if (context.classId !== "death-knight") return true;
|
||||
|
||||
Reference in new issue
Block a user