import type { AggroActorId, AggroRole, ForcedTarget, ThreatTable } from "./aggro"; export interface ThreatMeterActor { readonly id: AggroActorId; readonly name: string; readonly role: AggroRole; readonly alive: boolean; } export interface ThreatMeterRow { readonly actorId: AggroActorId; readonly name: string; readonly role: AggroRole; readonly threat: number; readonly percentOfLeader: number; readonly currentTarget: boolean; readonly forced: boolean; } export function threatMeterRows( table: ThreatTable, actors: readonly ThreatMeterActor[], currentTargetActorId: AggroActorId | null, forcedTarget: ForcedTarget | null, now: number, maximumRows = 5, ): readonly ThreatMeterRow[] { const actorById = new Map(actors.filter((actor) => actor.alive).map((actor) => [actor.id, actor])); const ranked = Object.entries(table) .map(([actorId, threat]) => ({ actor: actorById.get(actorId), threat: Number.isFinite(threat) ? Math.max(0, threat) : 0, })) .filter((entry): entry is { actor: ThreatMeterActor; threat: number } => ( Boolean(entry.actor) && entry.threat > 0 )) .sort((left, right) => ( right.threat - left.threat || Number(right.actor.id === currentTargetActorId) - Number(left.actor.id === currentTargetActorId) || left.actor.id.localeCompare(right.actor.id) )) .slice(0, Math.max(0, Math.trunc(maximumRows))); const leaderThreat = ranked[0]?.threat ?? 0; const forcedActorId = forcedTarget && forcedTarget.endsAt > now ? forcedTarget.actorId : null; return ranked.map(({ actor, threat }) => ({ actorId: actor.id, name: actor.name, role: actor.role, threat, percentOfLeader: leaderThreat > 0 ? threat / leaderThreat * 100 : 0, currentTarget: actor.id === currentTargetActorId, forced: actor.id === forcedActorId, })); }