Release Healer Man 0.1.38

This commit is contained in:
phenom committed 2026-09-12 23:55:24 -04:00
1 parent 361734bc41
commit b417b7c3c3
48 files changed
+1982 -90

No files matched your search

+19 -8
View File
@@ -8,7 +8,7 @@ import {
} from "./abilityCatalog";
import { aiHintsForAbility, runeCostForAbility } from "./abilityRules";
import { spendDeathKnightRunes } from "./combatResources";
import type { MobCombatState } from "./combatStore";
import { equipmentAdjustedCombatAmount, type MobCombatState } from "./combatStore";
import type { PartyMember } from "./partyStore";
export interface PartyAbilityContext {
@@ -20,6 +20,7 @@ export interface PartyAbilityContext {
readonly fallenAlly?: boolean;
readonly injuredAllies?: number;
readonly lowestHealthFraction?: number;
readonly canUseAbility?: (ability: AbilityDefinition) => boolean;
}
function currentResource(member: PartyMember, type: ResourceType): number {
@@ -35,6 +36,7 @@ function abilityCosts(ability: AbilityDefinition): readonly { resource: Resource
export function partyAbilityIsReady(member: PartyMember, ability: AbilityDefinition, now: number): boolean {
if (ability.passive || !isAbilityUnlocked(ability, member.level)) return false;
if (ability.talentEntryId !== undefined && !(member.talentRanks[`coa-entry-${ability.talentEntryId}`] ?? 0)) 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;
@@ -50,12 +52,18 @@ export function partyAbilityIsReady(member: PartyMember, ability: AbilityDefinit
function candidateAbilities(member: PartyMember, now: number): readonly AbilityDefinition[] {
return abilitiesForClass(member.classId)
.filter((ability) => partyAbilityIsReady(member, abilityAtLevel(ability, member.level), now))
.map((ability) => abilityAtLevel(ability, member.level));
.map((ability) => abilityAtLevel(ability, member.level, ability.talentEntryId === undefined
? undefined : member.talentRanks[`coa-entry-${ability.talentEntryId}`] ?? 0))
.filter((ability) => partyAbilityIsReady(member, ability, now));
}
function scoreAbility(member: PartyMember, ability: AbilityDefinition, context: PartyAbilityContext): number {
const categories = new Set(aiHintsForAbility(ability).categories);
const damaging = categories.has("damage") || categories.has("execute");
if (categories.has("taunt") && (member.role !== "tank" || !context.targetIsLoose)) return 0;
if (!damaging && categories.has("interrupt") && !context.target?.activeCast) return 0;
const control = categories.has("control") || ability.effects.some((effect) => effect.kind === "apply-aura" && effect.control);
if (!damaging && control && !context.requiredControl) return 0;
let score = ability.ai?.priority ?? 0;
if (member.role === "tank") {
if (context.target?.activeCast && categories.has("interrupt")) score += 1_000;
@@ -89,6 +97,7 @@ export function choosePartyAbility(
context: PartyAbilityContext,
): AbilityDefinition | null {
return candidateAbilities(member, context.now)
.filter((ability) => context.canUseAbility?.(ability) ?? true)
.map((ability) => ({ ability, score: scoreAbility(member, ability, context) }))
.filter((candidate) => candidate.score > 0)
.sort((left, right) => right.score - left.score
@@ -142,9 +151,11 @@ export function partyAbilityAmount(
: effect.kind === "finisher-damage"
? effect.baseCoefficient + effect.perComboCoefficient * 5
: 0;
const power = member.level * 4
+ member.gearStats.spellPower
+ member.gearStats.attackPower * 0.35
+ member.gearStats.rangedAttackPower * 0.35;
return Math.max(1, Math.round(Math.max(0.1, coefficient) * Math.max(8, power)));
return equipmentAdjustedCombatAmount(
"coefficient" in effect ? effect : coefficient,
member.level,
member.gearStats,
member.classId,
ability,
);
}