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
@@ -24,6 +24,10 @@ export interface PartyAbilityContext {
|
||||
readonly target: MobCombatState | null;
|
||||
readonly targetIsLoose?: boolean;
|
||||
readonly hasDangerousThreat?: boolean;
|
||||
readonly enemyUsesMeleeAttacks?: boolean;
|
||||
readonly incomingMeleeDanger?: boolean;
|
||||
readonly needsEscape?: boolean;
|
||||
readonly moving?: boolean;
|
||||
readonly requiredControl?: boolean;
|
||||
readonly allyNeedsDispel?: boolean;
|
||||
readonly fallenAlly?: boolean;
|
||||
@@ -35,8 +39,11 @@ export interface PartyAbilityContext {
|
||||
readonly activeAuraExclusiveGroups?: ReadonlySet<string>;
|
||||
readonly activeSummonAbilityIds?: ReadonlySet<string>;
|
||||
readonly hasActivePet?: boolean;
|
||||
readonly availableConditionalAbilityNames?: ReadonlySet<string>;
|
||||
readonly canUseAbility?: (ability: AbilityDefinition) => boolean;
|
||||
readonly incomingDefensiveMechanic?: CompanionCooldownMechanicRule | null;
|
||||
/** Inner Focus-style setup lets the next direct healing spell ignore resource costs. */
|
||||
readonly nextHealingSpellFree?: boolean;
|
||||
}
|
||||
|
||||
function currentResource(member: PartyMember, type: ResourceType): number {
|
||||
@@ -52,12 +59,17 @@ function abilityCosts(member: PartyMember, ability: AbilityDefinition): readonly
|
||||
}));
|
||||
}
|
||||
|
||||
export function partyAbilityIsReady(member: PartyMember, ability: AbilityDefinition, now: number): boolean {
|
||||
export function partyAbilityIsReady(
|
||||
member: PartyMember,
|
||||
ability: AbilityDefinition,
|
||||
now: number,
|
||||
ignoreResourceCosts = false,
|
||||
): boolean {
|
||||
if (ability.passive || !learnedAbilityForBuild(ability, member.level, member.talentRanks)) return false;
|
||||
if (member.globalCooldownEndsAt > now || (member.cooldowns[ability.id] ?? 0) > now) return false;
|
||||
if ((member.schoolLockedUntil[ability.castRules?.school ?? "physical"] ?? 0) > now) return false;
|
||||
if (aiHintsForAbility(ability).categories.includes("taunt") && member.tauntReadyAt > now) return false;
|
||||
if (abilityCosts(member, ability).some(cost => cost.resource === "health"
|
||||
if (!ignoreResourceCosts && abilityCosts(member, ability).some(cost => cost.resource === "health"
|
||||
? member.health <= cost.amount : currentResource(member, cost.resource) < cost.amount)) return false;
|
||||
const runeCost = member.classId === "death-knight" ? runeCostForAbility(ability) : null;
|
||||
return !runeCost || spendDeathKnightRunes(member.runes, runeCost, now) !== null;
|
||||
@@ -74,7 +86,7 @@ function isComboFinisher(ability: AbilityDefinition): boolean {
|
||||
|| (effect.kind === "combo" && effect.amount < 0));
|
||||
}
|
||||
|
||||
const RESERVED_SIGNATURE_ACTIONS = new Set(["Mortal Strike"]);
|
||||
const RESERVED_SIGNATURE_ACTIONS = new Set(["Mortal Strike", "Mutilate", "Envenom"]);
|
||||
|
||||
function shouldReserveForHigherPriority(
|
||||
member: PartyMember,
|
||||
@@ -87,8 +99,10 @@ function shouldReserveForHigherPriority(
|
||||
if (!rotation || decisionIndex <= 0) return false;
|
||||
return rotation.abilityPriority.slice(0, decisionIndex).some((name) => {
|
||||
const candidate = learned.find((ability) => ability.name === name);
|
||||
if (!candidate || candidate.passive || isComboFinisher(candidate)) return false;
|
||||
if (!candidate || candidate.passive) return false;
|
||||
if (!RESERVED_SIGNATURE_ACTIONS.has(candidate.name)) return false;
|
||||
if (isComboFinisher(candidate)
|
||||
&& (member.comboPoints < 4 || member.comboTargetId !== decision.targetId)) return false;
|
||||
if ((member.cooldowns[candidate.id] ?? 0) > now) return false;
|
||||
if ((member.schoolLockedUntil[candidate.castRules?.school ?? "physical"] ?? 0) > now) return false;
|
||||
const categories = aiHintsForAbility(candidate).categories;
|
||||
@@ -141,7 +155,12 @@ export function choosePartyRotationDecision(
|
||||
comboPoints: member.comboPoints,
|
||||
comboTargetId: member.comboTargetId,
|
||||
runes: member.runes,
|
||||
abilities: learned.filter((ability) => partyAbilityIsReady(member, ability, context.now)),
|
||||
abilities: learned.filter((ability) => partyAbilityIsReady(
|
||||
member,
|
||||
ability,
|
||||
context.now,
|
||||
Boolean(context.nextHealingSpellFree && ability.effects.some((effect) => effect.kind === "heal")),
|
||||
)),
|
||||
knownDamagingAbilityNames: new Set(learned.filter((ability) => {
|
||||
const categories = aiHintsForAbility(ability).categories;
|
||||
return categories.includes("damage") || categories.includes("execute");
|
||||
@@ -151,11 +170,16 @@ export function choosePartyRotationDecision(
|
||||
hostileCount: context.hostileCount,
|
||||
targetIsLoose: context.targetIsLoose,
|
||||
hasDangerousThreat: context.hasDangerousThreat,
|
||||
enemyUsesMeleeAttacks: context.enemyUsesMeleeAttacks,
|
||||
incomingMeleeDanger: context.incomingMeleeDanger,
|
||||
needsEscape: context.needsEscape,
|
||||
moving: context.moving,
|
||||
requiredControl: context.requiredControl,
|
||||
maintainedAbilityIds: context.maintainedAbilityIds,
|
||||
activeAuraExclusiveGroups: context.activeAuraExclusiveGroups,
|
||||
activeSummonAbilityIds: context.activeSummonAbilityIds,
|
||||
hasActivePet: context.hasActivePet,
|
||||
availableConditionalAbilityNames: context.availableConditionalAbilityNames,
|
||||
canUseAbility: context.canUseAbility,
|
||||
cooldownPolicy: currentCompanionCooldownPolicy(),
|
||||
incomingDefensiveMechanic: context.incomingDefensiveMechanic,
|
||||
@@ -176,13 +200,14 @@ export function commitPartyAbilityUse(
|
||||
member: PartyMember,
|
||||
ability: AbilityDefinition,
|
||||
now: number,
|
||||
options: { readonly ignoreResourceCosts?: boolean } = {},
|
||||
): PartyMember | null {
|
||||
if (!partyAbilityIsReady(member, ability, now)) return null;
|
||||
if (!partyAbilityIsReady(member, ability, now, options.ignoreResourceCosts)) return null;
|
||||
const resourcePools = { ...member.resourcePools };
|
||||
const lastResourceSpentAt = { ...member.lastResourceSpentAt };
|
||||
let resource = member.resource;
|
||||
let health = member.health;
|
||||
for (const cost of abilityCosts(member, ability)) {
|
||||
for (const cost of options.ignoreResourceCosts ? [] : abilityCosts(member, ability)) {
|
||||
const amount = cost.amount;
|
||||
if (cost.resource === "health") { health -= amount; continue; }
|
||||
const current = currentResource(member, cost.resource);
|
||||
@@ -214,7 +239,26 @@ export function partyAbilityAmount(
|
||||
member: PartyMember,
|
||||
ability: AbilityDefinition,
|
||||
effect: AbilityEffect,
|
||||
poisonDoses = 0,
|
||||
): number {
|
||||
if (effect.kind === "finisher-damage" && effect.poison) {
|
||||
const consumedDoses = Math.max(0, Math.min(
|
||||
effect.poison.maximumDoses,
|
||||
Math.floor(poisonDoses),
|
||||
));
|
||||
return equipmentAdjustedCombatAmount(
|
||||
{
|
||||
coefficient: 0,
|
||||
basePoints: effect.poison.basePointsPerDose * consumedDoses,
|
||||
dieSides: 1,
|
||||
attackPowerCoefficient: effect.poison.attackPowerCoefficientPerDose * consumedDoses,
|
||||
},
|
||||
member.level,
|
||||
member.gearStats,
|
||||
member.classId,
|
||||
ability,
|
||||
);
|
||||
}
|
||||
const coefficient = "coefficient" in effect
|
||||
? effect.coefficient
|
||||
: effect.kind === "finisher-damage"
|
||||
|
||||
Reference in new issue
Block a user