Back to City Breaker
SOURCE / PINNED RELEASE

Made of little things.

City Breaker

Release
94d2b8664b10…
Author-recorded commit
df23103cbcc1…
License
LICENSE
Author’s source reference
nostr://npub1sj2vketepqw9v6fmge5fdzuj7atj9sv6qv2jss05n2z6rl64lcfqumdsnt/wss%3A%2F%2Fgit.napplet.soy%2F/n-cf8b858653e

Archive hash verified: eeeed725668c1ece…. The source-to-build association is the author’s claim; it has not been independently rebuilt.

src/game/save.ts
// Save games go through the shell's napplet-scoped storage (NAP storage), not
// browser localStorage, which napplets may not use. Without the optional storage
// domain the campaign still runs; it just cannot be resumed after reload.
import { STORAGE_DOMAIN, storage } from '@napplet/sdk';
import { runtimeHasDomain } from '../domain-availability.js';
import type { GameState } from './state';

const KEY = 'city-breaker-save-v1';

export function canSave(): boolean {
  return runtimeHasDomain(STORAGE_DOMAIN);
}

function withTimeout<T>(p: Promise<T>, ms = 4000): Promise<T> {
  return Promise.race([p, new Promise<T>((_, rej) => setTimeout(() => rej(new Error('storage timeout')), ms))]);
}

export async function saveGame(s: GameState): Promise<boolean> {
  if (!canSave()) return false;
  const trimmed: GameState = { ...s, raidHeat: s.raidHeat.slice(-600), news: s.news.slice(0, 60) };
  const json = JSON.stringify(trimmed, (_k, v) => (typeof v === 'number' && !Number.isInteger(v) ? Math.round(v * 10000) / 10000 : v));
  try {
    await withTimeout(storage.setItem(KEY, json));
    return true;
  } catch {
    return false;
  }
}

export async function loadGame(): Promise<GameState | null> {
  if (!canSave()) return null;
  try {
    const raw = await withTimeout(storage.getItem(KEY));
    if (!raw) return null;
    const s = JSON.parse(raw) as GameState;
    return s && s.version === 1 ? s : null;
  } catch {
    return null;
  }
}

export async function clearSave(): Promise<void> {
  if (!canSave()) return;
  try {
    await withTimeout(storage.removeItem(KEY));
  } catch {
    /* ignore */
  }
}