forked from phenom/healer-man
90 lines
3.3 KiB
TypeScript
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);
|
|
}
|