import type { Inventory, RabbitData, ResourceDropData, ResourceNodeData, Vec2 } from './types'; import type { UpgradeId } from './upgrades'; const STORAGE_KEY = 'forest-save-v1'; export interface SaveData { version: 3; inventory: Inventory; upgradeLevels: Record; /** World state (character/nodes/drops) is optional so older saves without it still load fine — * progress is restored either way, just with a freshly spawned world. */ character?: Vec2; nodes?: ResourceNodeData[]; resourceDrops?: ResourceDropData[]; /** Absent on saves from before rabbits existed, which get a freshly spawned batch instead. */ rabbits?: RabbitData[]; /** Absent on older saves, which are treated as day 1. */ day?: number; /** In-game clock hour (0-24). Absent on older saves, which are treated as noon. */ hour?: number; /** Absent on saves from before the character had hp, which default to a full characterMaxHp. */ characterHp?: number; } function isValidSave(value: unknown): value is SaveData { if (!value || typeof value !== 'object') return false; const data = value as Partial; return ( data.version === 3 && typeof data.inventory === 'object' && data.inventory !== null && typeof data.inventory.logs === 'number' && typeof data.inventory.twigs === 'number' && typeof data.inventory.coins === 'number' && typeof data.upgradeLevels === 'object' && data.upgradeLevels !== null ); } /** Reads the saved game from localStorage, or null if there's none / it's corrupted (including a * save from an older, incompatible schema version — that's treated the same as no save). */ export function loadSave(): SaveData | null { try { const raw = localStorage.getItem(STORAGE_KEY); if (!raw) return null; const parsed: unknown = JSON.parse(raw); return isValidSave(parsed) ? parsed : null; } catch { // localStorage unavailable (private mode, disabled) or corrupted JSON — treat as no save. return null; } } /** Writes the save to localStorage. Best-effort: silently no-ops if storage is unavailable/full. */ export function writeSave(data: SaveData): void { try { localStorage.setItem(STORAGE_KEY, JSON.stringify(data)); } catch { // Ignore — e.g. private browsing mode or quota exceeded. } } /** Deletes the saved game, if any — used when starting a brand new game. */ export function clearSave(): void { try { localStorage.removeItem(STORAGE_KEY); } catch { // Ignore — e.g. private browsing mode. } } /** Whether a valid, resumable save currently exists (used to enable/disable "Continue" in the menu). */ export function hasSave(): boolean { return loadSave() !== null; }