Files
initiative/apps/web/src/hooks/use-rules-edition.ts
Lukas e62c49434c
All checks were successful
CI / check (push) Successful in 2m21s
CI / build-image (push) Successful in 24s
Add Pathfinder 2e game system mode
Implements PF2e as an alternative game system alongside D&D 5e/5.5e.
Settings modal "Game System" selector switches conditions, bestiary,
stat block layout, and initiative calculation between systems.

- Valued conditions with increment/decrement UX (Clumsy 2, Frightened 3)
- 2,502 PF2e creatures from bundled search index (77 sources)
- PF2e stat block: level, traits, Perception, Fort/Ref/Will, ability mods
- Perception-based initiative rolling
- System-scoped source cache (D&D and PF2e sources don't collide)
- Backwards-compatible condition rehydration (ConditionId[] → ConditionEntry[])
- Difficulty indicator hidden in PF2e mode (excluded from MVP)

Closes dostulata/initiative#19

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 01:26:22 +02:00

61 lines
1.4 KiB
TypeScript

import type { RulesEdition } from "@initiative/domain";
import { useCallback, useSyncExternalStore } from "react";
const STORAGE_KEY = "initiative:game-system";
const OLD_STORAGE_KEY = "initiative:rules-edition";
const listeners = new Set<() => void>();
let currentEdition: RulesEdition = loadEdition();
function loadEdition(): RulesEdition {
try {
const raw = localStorage.getItem(STORAGE_KEY);
if (raw === "5e" || raw === "5.5e" || raw === "pf2e") return raw;
// Migrate from old key
const old = localStorage.getItem(OLD_STORAGE_KEY);
if (old === "5e" || old === "5.5e") {
localStorage.setItem(STORAGE_KEY, old);
localStorage.removeItem(OLD_STORAGE_KEY);
return old;
}
} catch {
// storage unavailable
}
return "5.5e";
}
function saveEdition(edition: RulesEdition): void {
try {
localStorage.setItem(STORAGE_KEY, edition);
} catch {
// quota exceeded or storage unavailable
}
}
function notifyAll(): void {
for (const listener of listeners) {
listener();
}
}
function subscribe(callback: () => void): () => void {
listeners.add(callback);
return () => listeners.delete(callback);
}
function getSnapshot(): RulesEdition {
return currentEdition;
}
export function useRulesEdition() {
const edition = useSyncExternalStore(subscribe, getSnapshot);
const setEdition = useCallback((next: RulesEdition) => {
currentEdition = next;
saveEdition(next);
notifyAll();
}, []);
return { edition, setEdition } as const;
}