92 lines
3.7 KiB
TypeScript
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);
|
|
}
|