55 lines
1.8 KiB
TypeScript
55 lines
1.8 KiB
TypeScript
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,
|
|
}));
|
|
}
|