import { useEffect, useState } from "react"; import type { AuraDisposition } from "../game/combatAuras"; import { useCombatStore } from "../game/combatStore"; import { formatTimedEffectTimer } from "../game/timedEffectDisplay"; import { auraDefinitionDisplaysInStrip, auraDefinitionIcon, auraDefinitionIsDispellableDebuff, auraDefinitionIsLongTerm, auraDescription, auraDisplayName, groupAurasForDisplay, } from "./auraDisplay"; import type { StatusEffectInspectHandler, StatusEffectInspection } from "./statusEffectInspection"; interface AuraStripProps { readonly targetId: string; readonly targetIds?: readonly string[]; readonly dispositions?: readonly AuraDisposition[]; readonly className?: string; readonly hideLongTerm?: boolean; readonly showOnlyLongTerm?: boolean; readonly highlightDispellableDebuffs?: boolean; readonly targetLabels?: Readonly>; readonly onInspect?: StatusEffectInspectHandler; readonly tooltipId?: string; } export function AuraStrip({ targetId, targetIds, dispositions = ["buff", "debuff"], className = "", hideLongTerm = false, showOnlyLongTerm = false, highlightDispellableDebuffs = false, targetLabels, onInspect, tooltipId, }: AuraStripProps) { const allAuras = useCombatStore((state) => state.auras); const [now, setNow] = useState(() => Date.now()); const requestedTargetIds = targetIds ?? [targetId]; const auras = groupAurasForDisplay(allAuras.filter((aura) => ( requestedTargetIds.includes(aura.targetId) && dispositions.includes(aura.definition.disposition) && auraDefinitionDisplaysInStrip(aura.definition) && (!hideLongTerm || !auraDefinitionIsLongTerm(aura.definition)) && (!showOnlyLongTerm || auraDefinitionIsLongTerm(aura.definition)) && (aura.expiresAt === null || aura.expiresAt > now) ))); useEffect(() => { if (!auras.some((aura) => aura.expiresAt !== null)) return undefined; const timer = window.setInterval(() => setNow(Date.now()), 100); return () => window.clearInterval(timer); }, [auras]); if (!auras.length) return null; return ( {auras.map((aura) => { const remaining = aura.expiresAt === null ? null : Math.max(0, aura.expiresAt - now); const timer = remaining === null ? "" : formatTimedEffectTimer(remaining); const displayName = auraDisplayName(aura.definition); const targetLabel = targetLabels?.[aura.targetId]; const initial = displayName.charAt(0).toUpperCase() || "*"; const icon = auraDefinitionIcon(aura.definition); const dispellable = highlightDispellableDebuffs && auraDefinitionIsDispellableDebuff(aura.definition); const dispelLabel = dispellable ? `, dispellable ${aura.definition.dispelCategory}` : ""; const inspection: StatusEffectInspection = { id: aura.instanceId, name: displayName, disposition: aura.definition.disposition, description: auraDescription(aura), remainingLabel: timer ? `${timer} remaining` : "Permanent", stacks: aura.stacks, ...(targetLabel ? { targetLabel } : {}), ...(aura.definition.dispelCategory ? { dispelCategory: aura.definition.dispelCategory } : {}), }; return ( onInspect(inspection) : undefined} onPointerLeave={onInspect ? (event) => { if (event.currentTarget !== document.activeElement) onInspect(null); } : undefined} onFocus={onInspect ? () => onInspect(inspection) : undefined} onBlur={onInspect ? () => onInspect(null) : undefined} > {icon ? : } {aura.stacks > 1 ? {aura.stacks} : null} {timer ? : null} ); })} ); }