Files
dndextended/src/hooks/useDiceRoller.ts
T
2026-07-12 04:32:36 -04:00

92 lines
2.2 KiB
TypeScript

'use client';
import { useRef, useState, useCallback, useEffect } from 'react';
import * as THREE from 'three';
import {
initScene,
Die,
DieInstance,
setDice,
stopRenderLoop,
initRenderLoop,
getW,
getD,
} from '../lib/dicePhysics';
export interface RollResult {
sides: number;
value: number;
}
export function useDiceRoller() {
const mountRef = useRef<HTMLDivElement>(null);
const [isReady, setIsReady] = useState(false);
const [isRolling, setIsRolling] = useState(false);
const sceneInitRef = useRef(false);
useEffect(() => {
if (!mountRef.current || sceneInitRef.current) return;
sceneInitRef.current = true;
initScene(mountRef.current);
initRenderLoop();
const timer = setTimeout(() => setIsReady(true), 300);
return () => {
clearTimeout(timer);
stopRenderLoop();
};
}, []);
const rollDice = useCallback(
(pool: Record<number, number>): Promise<RollResult[]> => {
return new Promise((resolve) => {
const totalDice = Object.values(pool).reduce((a, b) => a + b, 0);
if (totalDice === 0) {
resolve([]);
return;
}
setIsRolling(true);
const newDice: DieInstance[] = [];
const W = getW();
const D = getD();
let idx = 0;
const order = [20, 12, 10, 8, 6, 4];
order.forEach((sides) => {
const count = pool[sides] || 0;
for (let i = 0; i < count; i++) {
const die = new Die(sides);
const side = idx % 2 === 0 ? -1 : 1;
const x = side * (W - 0.8);
const z = D - 1 - (idx % 3) * 1.1;
const dir = new THREE.Vector3(
-side * (9 + Math.random() * 4),
0.5,
-(6 + Math.random() * 4)
);
die.throwFrom(x, z, dir);
die.pos.x -= side * idx * 0.15;
newDice.push(die);
idx++;
}
});
setDice(newDice, (completedDice) => {
const results: RollResult[] = completedDice.map((d) => ({
sides: d.sides,
value: d.value ?? 0,
}));
setIsRolling(false);
resolve(results);
});
});
},
[]
);
return { mountRef, rollDice, isReady, isRolling };
}