import type { DomainEvent } from "./events.js"; import { PERSISTENT_DAMAGE_DEFINITIONS, type PersistentDamageEntry, type PersistentDamageType, VALID_PERSISTENT_DAMAGE_TYPES, } from "./persistent-damage-types.js"; import { type CombatantId, type DomainError, type Encounter, findCombatant, isDomainError, } from "./types.js"; export { PERSISTENT_DAMAGE_DEFINITIONS, PERSISTENT_DAMAGE_TYPES, type PersistentDamageDefinition, type PersistentDamageEntry, type PersistentDamageType, VALID_PERSISTENT_DAMAGE_TYPES, } from "./persistent-damage-types.js"; export interface PersistentDamageSuccess { readonly encounter: Encounter; readonly events: DomainEvent[]; } function applyPersistentDamage( encounter: Encounter, combatantId: CombatantId, newEntries: readonly PersistentDamageEntry[] | undefined, ): Encounter { return { combatants: encounter.combatants.map((c) => c.id === combatantId ? { ...c, persistentDamage: newEntries } : c, ), activeIndex: encounter.activeIndex, roundNumber: encounter.roundNumber, }; } export function addPersistentDamage( encounter: Encounter, combatantId: CombatantId, damageType: PersistentDamageType, formula: string, ): PersistentDamageSuccess | DomainError { if (!VALID_PERSISTENT_DAMAGE_TYPES.has(damageType)) { return { kind: "domain-error", code: "unknown-damage-type", message: `Unknown persistent damage type "${damageType}"`, }; } if (formula.trim().length === 0) { return { kind: "domain-error", code: "empty-formula", message: "Persistent damage formula must not be empty", }; } const found = findCombatant(encounter, combatantId); if (isDomainError(found)) return found; const { combatant: target } = found; const current = target.persistentDamage ?? []; // Replace existing entry of same type, or append const filtered = current.filter((e) => e.type !== damageType); const newEntries = [ ...filtered, { type: damageType, formula: formula.trim() }, ]; // Sort by definition order const order = PERSISTENT_DAMAGE_DEFINITIONS.map((d) => d.type); newEntries.sort((a, b) => order.indexOf(a.type) - order.indexOf(b.type)); return { encounter: applyPersistentDamage(encounter, combatantId, newEntries), events: [ { type: "PersistentDamageAdded", combatantId, damageType, formula: formula.trim(), }, ], }; } export function removePersistentDamage( encounter: Encounter, combatantId: CombatantId, damageType: PersistentDamageType, ): PersistentDamageSuccess | DomainError { const found = findCombatant(encounter, combatantId); if (isDomainError(found)) return found; const { combatant: target } = found; const current = target.persistentDamage ?? []; if (!current.some((e) => e.type === damageType)) { return { kind: "domain-error", code: "persistent-damage-not-active", message: `Persistent ${damageType} damage is not active`, }; } const filtered = current.filter((e) => e.type !== damageType); return { encounter: applyPersistentDamage( encounter, combatantId, filtered.length > 0 ? filtered : undefined, ), events: [{ type: "PersistentDamageRemoved", combatantId, damageType }], }; }