import type { DomainEvent } from "./events.js"; import { type HpRange, type HpVariant, hpForVariant } from "./hp-range.js"; import { type CombatantId, type DomainError, type Encounter, findCombatant, isDomainError, } from "./types.js"; export interface SetHpVariantSuccess { readonly encounter: Encounter; readonly events: DomainEvent[]; } /** * Switches a combatant's max HP between the minimum, average and maximum roll * of its Hit Dice. * * The shift is applied as a delta so manual HP edits and damage already taken * survive the switch: a creature missing 5 HP still misses 5 HP afterwards. */ export function setHpVariant( encounter: Encounter, combatantId: CombatantId, variant: HpVariant | undefined, range: HpRange, ): SetHpVariantSuccess | DomainError { const found = findCombatant(encounter, combatantId); if (isDomainError(found)) return found; const { combatant } = found; if (combatant.hpVariant === variant) { return { encounter, events: [] }; } const delta = hpForVariant(range, variant) - hpForVariant(range, combatant.hpVariant); const newMaxHp = combatant.maxHp === undefined ? undefined : Math.max(1, combatant.maxHp + delta); const newCurrentHp = combatant.currentHp === undefined || newMaxHp === undefined ? combatant.currentHp : Math.max(0, Math.min(combatant.currentHp + delta, newMaxHp)); return { encounter: { ...encounter, combatants: encounter.combatants.map((c) => c.id === combatantId ? { ...c, maxHp: newMaxHp, currentHp: newCurrentHp, hpVariant: variant, } : c, ), }, events: [ { type: "HpVariantSet", combatantId, variant, previousMaxHp: combatant.maxHp, newMaxHp, }, ], }; }