175 lines
3.7 KiB
TypeScript
175 lines
3.7 KiB
TypeScript
import type { EncounterStore } from "@initiative/application";
|
|
import {
|
|
addCombatantUseCase,
|
|
adjustHpUseCase,
|
|
advanceTurnUseCase,
|
|
editCombatantUseCase,
|
|
removeCombatantUseCase,
|
|
setHpUseCase,
|
|
setInitiativeUseCase,
|
|
} from "@initiative/application";
|
|
import type { CombatantId, DomainEvent, Encounter } from "@initiative/domain";
|
|
import {
|
|
combatantId,
|
|
createEncounter,
|
|
isDomainError,
|
|
} from "@initiative/domain";
|
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
import {
|
|
loadEncounter,
|
|
saveEncounter,
|
|
} from "../persistence/encounter-storage.js";
|
|
|
|
function createDemoEncounter(): Encounter {
|
|
const result = createEncounter([
|
|
{ id: combatantId("1"), name: "Aria" },
|
|
{ id: combatantId("2"), name: "Brak" },
|
|
{ id: combatantId("3"), name: "Cael" },
|
|
]);
|
|
|
|
if (isDomainError(result)) {
|
|
throw new Error(`Failed to create demo encounter: ${result.message}`);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
function initializeEncounter(): Encounter {
|
|
const stored = loadEncounter();
|
|
if (stored !== null) return stored;
|
|
return createDemoEncounter();
|
|
}
|
|
|
|
function deriveNextId(encounter: Encounter): number {
|
|
let max = 0;
|
|
for (const c of encounter.combatants) {
|
|
const match = /^c-(\d+)$/.exec(c.id);
|
|
if (match) {
|
|
const n = Number.parseInt(match[1], 10);
|
|
if (n > max) max = n;
|
|
}
|
|
}
|
|
return max;
|
|
}
|
|
|
|
export function useEncounter() {
|
|
const [encounter, setEncounter] = useState<Encounter>(initializeEncounter);
|
|
const [events, setEvents] = useState<DomainEvent[]>([]);
|
|
const encounterRef = useRef(encounter);
|
|
encounterRef.current = encounter;
|
|
|
|
useEffect(() => {
|
|
saveEncounter(encounter);
|
|
}, [encounter]);
|
|
|
|
const makeStore = useCallback((): EncounterStore => {
|
|
return {
|
|
get: () => encounterRef.current,
|
|
save: (e) => setEncounter(e),
|
|
};
|
|
}, []);
|
|
|
|
const advanceTurn = useCallback(() => {
|
|
const result = advanceTurnUseCase(makeStore());
|
|
|
|
if (isDomainError(result)) {
|
|
return;
|
|
}
|
|
|
|
setEvents((prev) => [...prev, ...result]);
|
|
}, [makeStore]);
|
|
|
|
const nextId = useRef(deriveNextId(encounter));
|
|
|
|
const addCombatant = useCallback(
|
|
(name: string) => {
|
|
const id = combatantId(`c-${++nextId.current}`);
|
|
const result = addCombatantUseCase(makeStore(), id, name);
|
|
|
|
if (isDomainError(result)) {
|
|
return;
|
|
}
|
|
|
|
setEvents((prev) => [...prev, ...result]);
|
|
},
|
|
[makeStore],
|
|
);
|
|
|
|
const removeCombatant = useCallback(
|
|
(id: CombatantId) => {
|
|
const result = removeCombatantUseCase(makeStore(), id);
|
|
|
|
if (isDomainError(result)) {
|
|
return;
|
|
}
|
|
|
|
setEvents((prev) => [...prev, ...result]);
|
|
},
|
|
[makeStore],
|
|
);
|
|
|
|
const editCombatant = useCallback(
|
|
(id: CombatantId, newName: string) => {
|
|
const result = editCombatantUseCase(makeStore(), id, newName);
|
|
|
|
if (isDomainError(result)) {
|
|
return;
|
|
}
|
|
|
|
setEvents((prev) => [...prev, ...result]);
|
|
},
|
|
[makeStore],
|
|
);
|
|
|
|
const setInitiative = useCallback(
|
|
(id: CombatantId, value: number | undefined) => {
|
|
const result = setInitiativeUseCase(makeStore(), id, value);
|
|
|
|
if (isDomainError(result)) {
|
|
return;
|
|
}
|
|
|
|
setEvents((prev) => [...prev, ...result]);
|
|
},
|
|
[makeStore],
|
|
);
|
|
|
|
const setHp = useCallback(
|
|
(id: CombatantId, maxHp: number | undefined) => {
|
|
const result = setHpUseCase(makeStore(), id, maxHp);
|
|
|
|
if (isDomainError(result)) {
|
|
return;
|
|
}
|
|
|
|
setEvents((prev) => [...prev, ...result]);
|
|
},
|
|
[makeStore],
|
|
);
|
|
|
|
const adjustHp = useCallback(
|
|
(id: CombatantId, delta: number) => {
|
|
const result = adjustHpUseCase(makeStore(), id, delta);
|
|
|
|
if (isDomainError(result)) {
|
|
return;
|
|
}
|
|
|
|
setEvents((prev) => [...prev, ...result]);
|
|
},
|
|
[makeStore],
|
|
);
|
|
|
|
return {
|
|
encounter,
|
|
events,
|
|
advanceTurn,
|
|
addCombatant,
|
|
removeCombatant,
|
|
editCombatant,
|
|
setInitiative,
|
|
setHp,
|
|
adjustHp,
|
|
} as const;
|
|
}
|