import type { DomainEvent } from "./events.js"; import type { CombatantId, DomainError, Encounter } from "./types.js"; export interface SetHpSuccess { readonly encounter: Encounter; readonly events: DomainEvent[]; } /** * Pure function that sets, updates, or clears a combatant's max HP. * * - Setting maxHp initializes currentHp to maxHp (full health). * - Updating maxHp clamps currentHp to the new maxHp if needed. * - Clearing maxHp (undefined) also clears currentHp. */ export function setHp( encounter: Encounter, combatantId: CombatantId, maxHp: number | undefined, ): SetHpSuccess | DomainError { const targetIdx = encounter.combatants.findIndex((c) => c.id === combatantId); if (targetIdx === -1) { return { kind: "domain-error", code: "combatant-not-found", message: `No combatant found with ID "${combatantId}"`, }; } if (maxHp !== undefined && (!Number.isInteger(maxHp) || maxHp < 1)) { return { kind: "domain-error", code: "invalid-max-hp", message: `Max HP must be a positive integer, got ${maxHp}`, }; } const target = encounter.combatants[targetIdx]; const previousMaxHp = target.maxHp; const previousCurrentHp = target.currentHp; let newMaxHp: number | undefined; let newCurrentHp: number | undefined; if (maxHp === undefined) { newMaxHp = undefined; newCurrentHp = undefined; } else if (previousMaxHp === undefined) { // First time setting HP — full health newMaxHp = maxHp; newCurrentHp = maxHp; } else { // Updating existing maxHp newMaxHp = maxHp; if (previousCurrentHp === previousMaxHp) { // At full health — stay at full health newCurrentHp = maxHp; } else { // Clamp currentHp to new max newCurrentHp = Math.min(previousCurrentHp ?? maxHp, maxHp); } } return { encounter: { combatants: encounter.combatants.map((c) => c.id === combatantId ? { ...c, maxHp: newMaxHp, currentHp: newCurrentHp, tempHp: newMaxHp === undefined ? undefined : c.tempHp, } : c, ), activeIndex: encounter.activeIndex, roundNumber: encounter.roundNumber, }, events: [ { type: "MaxHpSet", combatantId, previousMaxHp, newMaxHp, previousCurrentHp, newCurrentHp, }, ], }; }