Release Healer Man 0.1.41
This commit is contained in:
1 parent
197a64117c
commit
c3f8a69b51
42 files changed
+886
-127
No files matched your search
@@ -6,14 +6,16 @@ import {
|
||||
import { aiHintsForAbility, canAbilityCastWhileMoving, targetingRulesForAbility } from "../abilityRules";
|
||||
import { PLAYER_AGGRO_ID } from "../aggro";
|
||||
import type { CastFailureReason, CombatState } from "../combatStore";
|
||||
import { useCombatStore } from "../combatStore";
|
||||
import { equipmentAdjustedCombatAmount, resolvedAbilityCosts, useCombatStore } from "../combatStore";
|
||||
import { getMobPosition } from "../mobRuntimeRegistry";
|
||||
import { getPartyRuntimePosition } from "../partyRuntimeRegistry";
|
||||
import { healingAbilityCanAidOthers, healingAbilityHasPendingHot, healingAbilityIsArea } from "../healingAbilityRules";
|
||||
import type { PartyState } from "../partyStore";
|
||||
import { usePartyStore } from "../partyStore";
|
||||
import type { PartyRole } from "../partyRoles";
|
||||
import type { BenchmarkScenarioDefinition } from "./benchmarkTypes";
|
||||
|
||||
export const BENCHMARK_POLICY_ID = "baseline-v1";
|
||||
export const BENCHMARK_POLICY_ID = "baseline-v3";
|
||||
|
||||
export interface BenchmarkPolicyDecision {
|
||||
readonly made: boolean;
|
||||
@@ -116,14 +118,35 @@ function scoreAbility(
|
||||
const canHeal = categories.has("heal") || categories.has("emergency-heal") || categories.has("area-heal");
|
||||
const canShield = categories.has("mitigation") && ability.effects.some((effect) => effect.kind === "shield");
|
||||
if ((!canHeal && !canShield) || !lowest || injured.length === 0) return null;
|
||||
friendlyTarget = ability.target === "self"
|
||||
friendlyTarget = !healingAbilityCanAidOthers(ability)
|
||||
? targets.find((target) => target.id === PLAYER_AGGRO_ID) ?? lowest
|
||||
: lowest;
|
||||
if (friendlyTarget.missingPercent <= 0.005) return null;
|
||||
const pendingHot = healingAbilityHasPendingHot(ability, PLAYER_AGGRO_ID, friendlyTarget.id, combat.effects);
|
||||
const directHeal = ability.effects.some(effect => effect.kind === "heal");
|
||||
if (pendingHot && !directHeal) return null;
|
||||
score += canHeal ? 100 : 70;
|
||||
score += friendlyTarget.missingPercent * 180;
|
||||
const healingAmount = ability.effects.reduce((total, effect) => total + (effect.kind === "heal" || effect.kind === "hot"
|
||||
? (equipmentAdjustedCombatAmount(effect, combat.level, combat.gearStats, combat.classId, ability)
|
||||
+ (effect.kind === "heal" ? friendlyTarget!.maxHealth * (effect.percentMaxHealth ?? 0) : 0))
|
||||
* (effect.kind === "hot" ? Math.min(3, effect.ticks) : 1) : 0), 0);
|
||||
score += Math.min(70, healingAmount / Math.max(1, friendlyTarget.maxHealth) * 100);
|
||||
if (ability.effects.some(effect => effect.kind === "heal" && (effect.percentMaxHealth ?? 0) >= 0.5)) {
|
||||
score += friendlyTarget.missingPercent >= 0.4 ? 100 : -100;
|
||||
}
|
||||
const healthCost = resolvedAbilityCosts(ability, combat.talentModifiers, combat).find(cost => cost.resource === "health")?.amount ?? 0;
|
||||
if (healthCost > 0) {
|
||||
if (combat.health / combat.maxHealth < 0.5) return null;
|
||||
if (friendlyTarget.id === PLAYER_AGGRO_ID && healingAbilityCanAidOthers(ability)) return null;
|
||||
score -= healthCost / combat.maxHealth * 500;
|
||||
}
|
||||
if (pendingHot && friendlyTarget.missingPercent < 0.5) score -= 120;
|
||||
if (categories.has("emergency-heal")) score += friendlyTarget.missingPercent >= 0.5 ? 100 : -15;
|
||||
if (categories.has("area-heal") || area) score += injured.length >= 2 ? 35 * injured.length : -35;
|
||||
if (healingAbilityIsArea(ability)) score += injured.length >= 2 ? 35 * injured.length : -35;
|
||||
if (!directHeal && friendlyTarget.missingPercent >= 0.5) score -= 60;
|
||||
if (canShield) score += friendlyTarget.missingPercent >= 0.2 ? 45 : -20;
|
||||
if (canShield && (combat.actors[friendlyTarget.id]?.absorb ?? 0) > 0) return null;
|
||||
} else {
|
||||
const hostileDamage = categories.has("damage") || categories.has("execute") || categories.has("debuff");
|
||||
if (role === "damage" && !hostileDamage) return null;
|
||||
@@ -176,6 +199,31 @@ export class BenchmarkController {
|
||||
.filter((candidate): candidate is ScoredAbility => candidate !== null)
|
||||
.sort((left, right) => right.score - left.score || left.ability.id.localeCompare(right.ability.id));
|
||||
|
||||
// Rage healers must begin weapon attacks to fund their heals before allies are injured.
|
||||
if (role === "healer" && combat.selectedTargetId
|
||||
&& combat.abilities.some(ability => ability.effects.some(effect => effect.kind === "heal") && ability.cost.resource === "rage")) {
|
||||
const starter = combat.abilities.map(ability => learnedAbility(ability, combat)).find(ability => ability
|
||||
&& ability.target === "hostile" && ability.range.max <= 5
|
||||
&& ability.effects.some(effect => effect.kind === "damage")
|
||||
&& resolvedAbilityCosts(ability, combat.talentModifiers, combat).every(cost => cost.amount === 0));
|
||||
if (starter && !combat.autoAttackTargetId) candidates.push({ ability: starter, score: 0, friendlyTarget: null });
|
||||
if (combat.resource < 35) {
|
||||
const generators = combat.abilities.map(ability => learnedAbility(ability, combat)).filter(ability => ability
|
||||
&& ability.target === "hostile" && ability.effects.some(effect => (effect.kind === "resource"
|
||||
&& (!effect.resource || effect.resource === "rage") && effect.amount > 0)
|
||||
|| ((effect.kind === "damage" || effect.kind === "dot")
|
||||
&& effect.onDamageResource?.resource === "rage" && effect.onDamageResource.amount > 0))
|
||||
&& resolvedAbilityCosts(ability, combat.talentModifiers, combat).every(cost => cost.resource !== "rage"
|
||||
&& (cost.resource !== "health" || (combat.health / combat.maxHealth > 0.65 && cost.amount / combat.maxHealth <= 0.1))));
|
||||
for (const generator of generators) {
|
||||
if (!generator) continue;
|
||||
const needsRageHeal = targets.some(target => target.id !== PLAYER_AGGRO_ID && target.missingPercent > 0.1);
|
||||
candidates.push({ ability: generator, score: needsRageHeal && combat.resource < 20 ? 400 : -10, friendlyTarget: null });
|
||||
candidates.sort((left, right) => right.score - left.score || left.ability.id.localeCompare(right.ability.id));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let resourceBlocked = false;
|
||||
let invalidFailures = 0;
|
||||
for (const candidate of candidates) {
|
||||
@@ -203,7 +251,8 @@ export class BenchmarkController {
|
||||
...(targeting.shape === "ground"
|
||||
? { groundPosition: ability.target === "hostile" || ability.target === "any"
|
||||
? hostilePosition ?? combat.playerPosition
|
||||
: combat.playerPosition }
|
||||
: ability.target === "self" || friendlyTarget?.id === PLAYER_AGGRO_ID ? combat.playerPosition
|
||||
: getPartyRuntimePosition(friendlyTarget?.id ?? "") ?? combat.playerPosition }
|
||||
: {}),
|
||||
});
|
||||
if (result.ok) {
|
||||
|
||||
Reference in new issue
Block a user