Files
healer-man/src/ui/partyHealPopups.ts
T
2026-09-20 21:50:50 -04:00

50 lines
1.6 KiB
TypeScript

import type { StructuredCombatEvent } from "../game/combatEvents";
export const PARTY_HEAL_POPUP_LIFETIME_MS = 1_500;
export const MAX_PARTY_HEAL_POPUPS = 20;
export interface PartyHealPopup {
readonly key: string;
readonly targetActorId: string;
readonly amount: number;
readonly critical: boolean;
readonly horizontalOffset: number;
}
function eventKey(event: StructuredCombatEvent): string {
return `${event.occurredAt}:${event.id}`;
}
/** Convert only newly committed, effective in-combat heals into party-frame UI events. */
export function addedPartyHealPopups(
previousEvents: readonly StructuredCombatEvent[],
nextEvents: readonly StructuredCombatEvent[],
inCombat: boolean,
): PartyHealPopup[] {
if (!inCombat || previousEvents === nextEvents) return [];
const previousKeys = new Set(previousEvents.map(eventKey));
return nextEvents
.filter((event) => (
event.kind === "healing"
&& event.effectiveAmount > 0
&& !previousKeys.has(eventKey(event))
))
.map((event) => ({
key: eventKey(event),
targetActorId: event.targetActorId,
amount: Math.max(1, Math.round(event.effectiveAmount)),
critical: event.critical,
horizontalOffset: ((event.id % 3) - 1) * 14,
}));
}
export function appendPartyHealPopups(
current: readonly PartyHealPopup[],
added: readonly PartyHealPopup[],
): PartyHealPopup[] {
if (added.length === 0) return [...current];
const addedKeys = new Set(added.map((popup) => popup.key));
return [...current.filter((popup) => !addedKeys.has(popup.key)), ...added]
.slice(-MAX_PARTY_HEAL_POPUPS);
}