Files
healer-man/src/ui/auraDisplay.ts
T
2026-09-25 21:37:10 -04:00

92 lines
3.7 KiB
TypeScript

import { PLAYER_AGGRO_ID } from "../game/aggro";
import type { ActiveAura, AuraDefinition } from "../game/combatAuras";
export const LONG_TERM_AURA_DURATION_MS = 60_000;
export function auraDefinitionDisplaysInStrip(definition: AuraDefinition): boolean {
return definition.hideFromAuraStrip !== true;
}
export function auraDefinitionIcon(definition: AuraDefinition): string | null {
return definition.icon?.trim() || null;
}
export function auraDisplayName(definition: AuraDefinition): string {
return definition.name.replace(/\s+\((?:DBC\s+\d+,\s*)?Aura\s+\d+\)$/i, "").trim();
}
function auraDisplayGroupKey(aura: ActiveAura): string {
return [
aura.targetId,
aura.definition.disposition,
auraDisplayName(aura.definition).toLowerCase(),
auraDefinitionIcon(aura.definition) ?? "",
].join("\u0000");
}
function auraDisplayExpiry(aura: ActiveAura): number {
return aura.expiresAt ?? Number.POSITIVE_INFINITY;
}
/** Collapses a spell's multiple DBC aura effects and repeated sources into one HUD icon. */
export function groupAurasForDisplay(auras: readonly ActiveAura[]): ActiveAura[] {
const grouped = new Map<string, { representative: ActiveAura; stacksByDefinition: Map<string, number> }>();
for (const aura of auras) {
const key = auraDisplayGroupKey(aura);
const existing = grouped.get(key);
if (!existing) {
grouped.set(key, { representative: aura, stacksByDefinition: new Map([[aura.definition.id, aura.stacks]]) });
continue;
}
const representative = auraDisplayExpiry(aura) > auraDisplayExpiry(existing.representative)
|| (auraDisplayExpiry(aura) === auraDisplayExpiry(existing.representative) && aura.appliedAt > existing.representative.appliedAt)
? aura
: existing.representative;
existing.stacksByDefinition.set(aura.definition.id,
(existing.stacksByDefinition.get(aura.definition.id) ?? 0) + aura.stacks);
existing.representative = representative;
}
return [...grouped.values()].map(({ representative, stacksByDefinition }) => {
const stacks = Math.max(...stacksByDefinition.values());
return representative.stacks === stacks ? representative : { ...representative, stacks };
});
}
/** Permanent and one-minute-or-longer auras are persistent party-frame clutter. */
export function auraDefinitionIsLongTerm(definition: AuraDefinition): boolean {
return definition.durationMs === null || definition.durationMs >= LONG_TERM_AURA_DURATION_MS;
}
export function auraDefinitionIsDispellableDebuff(definition: AuraDefinition): boolean {
return definition.disposition === "debuff" && definition.dispelCategory !== undefined;
}
export function auraDescription(aura: ActiveAura): string {
const authored = aura.definition.description?.trim();
if (authored) return authored;
const absorb = aura.absorbs.reduce((total, pool) => total + pool.remaining, 0);
if (absorb > 0) return `Absorbs up to ${Math.ceil(absorb)} incoming damage.`;
if (aura.definition.controlImmunities?.length) {
return `Grants immunity to ${aura.definition.controlImmunities.join(", ")}.`;
}
if (aura.definition.modifiers?.length) {
return aura.definition.disposition === "buff"
? "Improves combat attributes while active."
: "Reduces combat effectiveness while active.";
}
if (aura.definition.procs?.length) return "Can trigger an additional effect during combat.";
return aura.definition.disposition === "buff"
? "A beneficial effect is active."
: "A harmful effect is active.";
}
export function auraDisplaysInPlayerStatusPanel(
aura: ActiveAura,
now: number,
): boolean {
return aura.targetId === PLAYER_AGGRO_ID
&& auraDefinitionDisplaysInStrip(aura.definition)
&& (aura.expiresAt === null || aura.expiresAt > now);
}