Files
initiative/packages/application/src/roll-all-initiative-use-case.ts
Lukas 72195e90f6 Show toast when roll-all-initiative skips combatants without loaded sources
Previously the button silently did nothing for creatures whose bestiary
source wasn't loaded. Now it reports how many were skipped and why. Also
keeps the roll-all button visible (but disabled) when there's nothing
left to roll, and moves toasts to the bottom-left corner.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 18:58:25 +01:00

61 lines
1.4 KiB
TypeScript

import {
type Creature,
type CreatureId,
calculateInitiative,
type DomainError,
type DomainEvent,
isDomainError,
rollInitiative,
setInitiative,
} from "@initiative/domain";
import type { EncounterStore } from "./ports.js";
export interface RollAllResult {
events: DomainEvent[];
skippedNoSource: number;
}
export function rollAllInitiativeUseCase(
store: EncounterStore,
rollDice: () => number,
getCreature: (id: CreatureId) => Creature | undefined,
): RollAllResult | DomainError {
let encounter = store.get();
const allEvents: DomainEvent[] = [];
let skippedNoSource = 0;
for (const combatant of encounter.combatants) {
if (!combatant.creatureId) continue;
if (combatant.initiative !== undefined) continue;
const creature = getCreature(combatant.creatureId);
if (!creature) {
skippedNoSource++;
continue;
}
const { modifier } = calculateInitiative({
dexScore: creature.abilities.dex,
cr: creature.cr,
initiativeProficiency: creature.initiativeProficiency,
});
const value = rollInitiative(rollDice(), modifier);
if (isDomainError(value)) {
return value;
}
const result = setInitiative(encounter, combatant.id, value);
if (isDomainError(result)) {
return result;
}
encounter = result.encounter;
allEvents.push(...result.events);
}
store.save(encounter);
return { events: allEvents, skippedNoSource };
}