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(); 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); }