Update Healer Man server

This commit is contained in:
phenom committed 2026-10-06 22:25:34 -04:00
1 parent 6114a2295f
commit 69809f7228
593 files changed
+903643 -40774

No files matched your search

+22 -1
View File
@@ -5,9 +5,16 @@ import {
} from "../abilityCatalog";
import { aiHintsForAbility, canAbilityCastWhileMoving, targetingRulesForAbility } from "../abilityRules";
import { PLAYER_AGGRO_ID } from "../aggro";
import { combatNow } from "../combatClock";
import { enemyControlPreventsActions } from "../enemyAttacks";
import type { CastFailureReason, CombatState } from "../combatStore";
import { equipmentAdjustedCombatAmount, resolvedAbilityCosts, useCombatStore } from "../combatStore";
import { isCoaBarbarianDecapitate } from "../coaBarbarianExecuteRuntime";
import { coaBarbarianDecapitateWeaponStrike, coaNormalizedMeleeWeaponSpeed } from "../coaBarbarianWeaponDamageRuntime";
import { coaGuardianFormationAbilityForCast } from "../coaGuardianFormationRuntime";
import { projectCoaGuardianParagonAbility } from "../coaGuardianParagonRuntime";
import { PLAYER_EQUIPMENT_OWNER_ID } from "../equipment";
import { resolvedEquipmentLoadout } from "../heirloomEquipment";
import { getMobPosition } from "../mobRuntimeRegistry";
import { getPartyRuntimePosition } from "../partyRuntimeRegistry";
import { healingAbilityCanAidOthers, healingAbilityHasPendingHot, healingAbilityIsArea } from "../healingAbilityRules";
@@ -79,7 +86,21 @@ function learnedAbility(ability: AbilityDefinition, combat: CombatState): Abilit
const ranked = learnedAbilityForBuild(ability, combat.level, combat.talentRanks);
if (!ranked) return null;
if (ranked.effects.every((effect) => effect.kind === "unsupported" || effect.kind === "scripted")) return null;
return ranked;
if (combat.classId === "guardian" && combat.gearRole === "tank" && ranked.dbcSpellId === 803130) return null;
if (isCoaBarbarianDecapitate(ranked)) {
const mainHand = resolvedEquipmentLoadout(combat.equipment, combat.inventory, {
ownerId: PLAYER_EQUIPMENT_OWNER_ID, ownerKind: "player", classId: combat.classId,
secondaryClassId: combat.secondaryClassId, specializationId: combat.specializationId,
role: combat.gearRole, level: combat.level,
})["main-hand"];
return coaBarbarianDecapitateWeaponStrike(ranked, combat.gearStats.meleeWeapon,
mainHand ? coaNormalizedMeleeWeaponSpeed(mainHand) : null);
}
return projectCoaGuardianParagonAbility(coaGuardianFormationAbilityForCast(ranked), {
id: PLAYER_AGGRO_ID, classId: combat.classId, talentRanks: combat.talentRanks,
role: combat.gearRole, specializationId: combat.specializationId,
health: combat.health, maxHealth: combat.maxHealth,
}, combat.auras, combatNow());
}
function isAreaAbility(ability: AbilityDefinition): boolean {
@@ -0,0 +1,55 @@
import { describe, expect, it } from "vitest";
import { PartyCastTelemetry } from "./partyCastTelemetry";
describe("Balance Lab companion cast telemetry", () => {
it("counts completed damaging casts from party releases", () => {
const telemetry = new PartyCastTelemetry();
expect(telemetry.record({
actorGroup: "party",
sourceActorId: "party-ranger",
abilityId: "ranger-power-shot",
phase: "cast-start",
})).toBe(false);
expect(telemetry.record({
actorGroup: "party",
sourceActorId: "party-ranger",
abilityId: "ranger-power-shot",
phase: "release",
})).toBe(true);
});
it("counts one cast for a channel with several release pulses", () => {
const telemetry = new PartyCastTelemetry();
const cast = {
actorGroup: "party",
sourceActorId: "party-healer",
abilityId: "channel-heal",
};
expect(telemetry.record({ ...cast, phase: "cast-start" })).toBe(false);
expect(telemetry.record({ ...cast, phase: "release" })).toBe(true);
expect(telemetry.record({ ...cast, phase: "release" })).toBe(false);
});
it("counts instant releases and ignores canceled or non-party actions", () => {
const telemetry = new PartyCastTelemetry();
const cast = {
actorGroup: "party",
sourceActorId: "party-pyromancer",
abilityId: "instant-blast",
};
expect(telemetry.record({ ...cast, phase: "release", instantCast: true })).toBe(true);
expect(telemetry.record({ ...cast, phase: "cast-start" })).toBe(false);
expect(telemetry.record({ ...cast, phase: "cast-cancel" })).toBe(false);
expect(telemetry.record({ ...cast, phase: "release" })).toBe(false);
expect(telemetry.record({ ...cast, actorGroup: "player", phase: "release", instantCast: true })).toBe(false);
expect(telemetry.record({
...cast,
abilityId: "party-auto-attack",
phase: "release",
instantCast: true,
})).toBe(false);
});
});
+30
View File
@@ -0,0 +1,30 @@
export interface PartyCastPresentationEvent {
readonly actorGroup: string;
readonly sourceActorId: string;
readonly abilityId?: string;
readonly phase: string;
readonly instantCast?: boolean;
}
/** Tracks one completed companion cast across multi-pulse channel releases. */
export class PartyCastTelemetry {
private readonly startedCasts = new Map<string, boolean>();
record(event: PartyCastPresentationEvent): boolean {
if (event.actorGroup !== "party" || !event.abilityId || event.abilityId === "party-auto-attack") return false;
const key = `${event.sourceActorId}\u0000${event.abilityId}`;
if (event.phase === "cast-start") {
this.startedCasts.set(key, false);
return false;
}
if (event.phase === "cast-cancel") {
this.startedCasts.delete(key);
return false;
}
if (event.phase !== "release") return false;
if (event.instantCast) return true;
if (!this.startedCasts.has(key) || this.startedCasts.get(key)) return false;
this.startedCasts.set(key, true);
return true;
}
}