Implement the 026-roll-initiative feature that adds d20 roll buttons for bestiary combatants' initiative using a click-to-edit pattern (d20 icon when empty, plain text when set), plus a Roll All button in the top bar that batch-rolls for all unrolled bestiary combatants, with randomness confined to the adapter layer and the domain receiving pre-resolved dice values
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,8 @@
|
||||
import type { Creature } from "@initiative/domain";
|
||||
import {
|
||||
rollAllInitiativeUseCase,
|
||||
rollInitiativeUseCase,
|
||||
} from "@initiative/application";
|
||||
import type { CombatantId, Creature, CreatureId } from "@initiative/domain";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { ActionBar } from "./components/action-bar";
|
||||
import { CombatantRow } from "./components/combatant-row";
|
||||
@@ -7,6 +11,10 @@ import { TurnNavigation } from "./components/turn-navigation";
|
||||
import { useBestiary } from "./hooks/use-bestiary";
|
||||
import { useEncounter } from "./hooks/use-encounter";
|
||||
|
||||
function rollDice(): number {
|
||||
return Math.floor(Math.random() * 20) + 1;
|
||||
}
|
||||
|
||||
export function App() {
|
||||
const {
|
||||
encounter,
|
||||
@@ -23,6 +31,7 @@ export function App() {
|
||||
toggleCondition,
|
||||
toggleConcentration,
|
||||
addFromBestiary,
|
||||
makeStore,
|
||||
} = useEncounter();
|
||||
|
||||
const { search, getCreature, isLoaded } = useBestiary();
|
||||
@@ -46,9 +55,7 @@ export function App() {
|
||||
|
||||
const handleCombatantStatBlock = useCallback(
|
||||
(creatureId: string) => {
|
||||
const creature = getCreature(
|
||||
creatureId as import("@initiative/domain").CreatureId,
|
||||
);
|
||||
const creature = getCreature(creatureId as CreatureId);
|
||||
if (creature) setSelectedCreature(creature);
|
||||
},
|
||||
[getCreature],
|
||||
@@ -65,6 +72,17 @@ export function App() {
|
||||
[isLoaded, search],
|
||||
);
|
||||
|
||||
const handleRollInitiative = useCallback(
|
||||
(id: CombatantId) => {
|
||||
rollInitiativeUseCase(makeStore(), id, rollDice(), getCreature);
|
||||
},
|
||||
[makeStore, getCreature],
|
||||
);
|
||||
|
||||
const handleRollAllInitiative = useCallback(() => {
|
||||
rollAllInitiativeUseCase(makeStore(), rollDice, getCreature);
|
||||
}, [makeStore, getCreature]);
|
||||
|
||||
// Auto-scroll to the active combatant when the turn changes
|
||||
const activeRowRef = useRef<HTMLDivElement>(null);
|
||||
useEffect(() => {
|
||||
@@ -80,9 +98,7 @@ export function App() {
|
||||
if (!window.matchMedia("(min-width: 1024px)").matches) return;
|
||||
const active = encounter.combatants[encounter.activeIndex];
|
||||
if (!active?.creatureId || !isLoaded) return;
|
||||
const creature = getCreature(
|
||||
active.creatureId as import("@initiative/domain").CreatureId,
|
||||
);
|
||||
const creature = getCreature(active.creatureId as CreatureId);
|
||||
if (creature) setSelectedCreature(creature);
|
||||
}, [encounter.activeIndex, encounter.combatants, getCreature, isLoaded]);
|
||||
|
||||
@@ -96,6 +112,7 @@ export function App() {
|
||||
onAdvanceTurn={advanceTurn}
|
||||
onRetreatTurn={retreatTurn}
|
||||
onClearEncounter={clearEncounter}
|
||||
onRollAllInitiative={handleRollAllInitiative}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -132,6 +149,9 @@ export function App() {
|
||||
? () => handleCombatantStatBlock(c.creatureId as string)
|
||||
: undefined
|
||||
}
|
||||
onRollInitiative={
|
||||
c.creatureId ? handleRollInitiative : undefined
|
||||
}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user