Arcane Tycoon — Heroes & Magic theme park tycoon game
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
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
// ============ 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user