Files
healer-man/src/ui/auraDisplay.ts
T
2026-09-09 11:21:33 -04:00

90 lines
3.3 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, ActiveAura>();
for (const aura of auras) {
const key = auraDisplayGroupKey(aura);
const existing = grouped.get(key);
if (!existing) {
grouped.set(key, aura);
continue;
}
const representative = auraDisplayExpiry(aura) > auraDisplayExpiry(existing)
|| (auraDisplayExpiry(aura) === auraDisplayExpiry(existing) && aura.appliedAt > existing.appliedAt)
? aura
: existing;
const stacks = Math.max(existing.stacks, aura.stacks);
grouped.set(key, representative.stacks === stacks
? representative
: { ...representative, stacks });
}
return [...grouped.values()];
}
/** 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);
}