Complete browser game inspired by OpenRCT2 with fantasy twist: - Custom roller coaster designer with physics-based ratings + on-ride POV - 10 animated rides, 7 shops, 16 scenery items, path network & guest AI - Heroes guild vs monster invasions (5 classes, XP/gear/bosses) - Magic spell system (8 spells), research tree, economy/marketing/loans - Day-night cycle, weather, park rating, awards, 4 scenarios - Save/load slots + autosave, procedural WebAudio SFX/music - Isometric canvas renderer, minimap, diagnostics overlay - Test suites: smoke(13), linkcheck, inputcheck, framecheck, rendercheck, flow
79 lines
2.0 KiB
JavaScript
79 lines
2.0 KiB
JavaScript
// ============ save.js — localStorage slots, autosave, export/import ============
|
|
import { serialize, deserialize } from '../game/state.js';
|
|
import { download } from '../core/util.js';
|
|
|
|
const KEY = 'arcane_tycoon_save_';
|
|
const SLOTS = ['auto', 'slot1', 'slot2', 'slot3'];
|
|
|
|
export function listSaves() {
|
|
const out = [];
|
|
for (const s of SLOTS) {
|
|
try {
|
|
const raw = localStorage.getItem(KEY + s);
|
|
if (!raw) { out.push({ slot: s, exists: false }); continue; }
|
|
const d = JSON.parse(raw);
|
|
out.push({
|
|
slot: s, exists: true,
|
|
parkName: d.park?.name || 'Park',
|
|
scenario: d.scenario,
|
|
date: `Y${d.time?.year} M${(d.time?.month ?? 0) + 1}`,
|
|
cash: d.cash,
|
|
guests: d.guests?.length ?? 0,
|
|
savedAt: d.savedAt,
|
|
});
|
|
} catch {
|
|
out.push({ slot: s, exists: false });
|
|
}
|
|
}
|
|
return out;
|
|
}
|
|
|
|
export function saveTo(state, slot) {
|
|
const data = serialize(state);
|
|
data.savedAt = Date.now();
|
|
try {
|
|
localStorage.setItem(KEY + slot, JSON.stringify(data));
|
|
return true;
|
|
} catch (e) {
|
|
console.error('save failed', e);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
export function loadFrom(slot) {
|
|
try {
|
|
const raw = localStorage.getItem(KEY + slot);
|
|
if (!raw) return null;
|
|
return deserialize(JSON.parse(raw));
|
|
} catch (e) {
|
|
console.error('load failed', e);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export function hasAutosave() {
|
|
try { return !!localStorage.getItem(KEY + 'auto'); } catch { return false; }
|
|
}
|
|
|
|
export function autosave(state) {
|
|
return saveTo(state, 'auto');
|
|
}
|
|
|
|
export function exportSave(state) {
|
|
const data = serialize(state);
|
|
data.savedAt = Date.now();
|
|
const name = (state.park.name || 'park').replace(/\W+/g, '_').toLowerCase();
|
|
download(`arcane-tycoon-${name}.json`, JSON.stringify(data));
|
|
}
|
|
|
|
export function importSaveText(text) {
|
|
try {
|
|
const data = JSON.parse(text);
|
|
if (!data.version) throw new Error('Not an Arcane Tycoon save');
|
|
return deserialize(data);
|
|
} catch (e) {
|
|
console.error('import failed', e);
|
|
return null;
|
|
}
|
|
}
|