Diablo2D — Shadows of Tristram: complete browser ARPG

- Isometric canvas renderer (depth-sorted, FOV/fog, additive lighting)
- 3 classes x 20 skills, 4 acts x 4 floors + boss lairs, torment I-X
- Diablo-style loot: rarities, affix tiers, 14 legendaries, vendor, stash
- Rogue camp with 6 NPCs: Charsi/Akara/Kashya/Cain/Gheed/storage
- NPC quest chain (accept -> hunt -> turn in) with rewards & gating
- Procedural WebAudio SFX + generative music, EN/VI localization
- Saves, settings, waypoints, hardcore mode, PWA manifest
- 93-assertion headless suite + browser E2E via CDP
This commit is contained in:
2026-08-23 06:59:36 +00:00
commit fc1fa2d51e
42 changed files with 11784 additions and 0 deletions
+101
View File
@@ -0,0 +1,101 @@
/* ============================================================
* Diablo2D — save.js : localStorage persistence layer
* ============================================================ */
'use strict';
window.D2 = window.D2 || {};
(function (D2) {
const PREFIX = 'd2d.';
const SAVE_VERSION = 3;
let storageOk = true;
try {
localStorage.setItem(PREFIX + '__probe', '1');
localStorage.removeItem(PREFIX + '__probe');
} catch (e) {
storageOk = false;
}
function get(key, fallback = null) {
if (!storageOk) return fallback;
try {
const raw = localStorage.getItem(PREFIX + key);
if (raw == null) return fallback;
return JSON.parse(raw);
} catch (e) {
console.warn('[save] corrupt entry', key, e);
return fallback;
}
}
function set(key, value) {
if (!storageOk) return false;
try {
localStorage.setItem(PREFIX + key, JSON.stringify(value));
return true;
} catch (e) {
console.error('[save] write failed', e);
return false;
}
}
function remove(key) {
if (!storageOk) return;
try { localStorage.removeItem(PREFIX + key); } catch (e) {}
}
function hasMeta() {
return !!get('meta', null);
}
function writeMeta(meta) {
meta.version = SAVE_VERSION;
meta.savedAt = Date.now();
set('meta', meta);
}
function readMeta() {
return get('meta', null);
}
function wipeAll() {
remove('meta');
remove('char');
remove('world');
remove('settings');
}
/* full snapshot: meta + char + world */
function serializeSnapshot() {
return JSON.stringify({
v: SAVE_VERSION,
meta: readMeta(),
char: get('char'),
world: get('world'),
});
}
function restoreSnapshot(text) {
try {
const obj = JSON.parse(text);
if (!obj || typeof obj !== 'object' || !obj.char) return false;
if (obj.meta) set('meta', obj.meta);
if (obj.char) set('char', obj.char);
if (obj.world) set('world', obj.world);
return true;
} catch (e) {
return false;
}
}
D2.save = {
version: SAVE_VERSION,
get storageOk() { return storageOk; },
KEY: { META: 'meta', CHAR: 'char', WORLD: 'world', SETTINGS: 'settings' },
get, set, remove,
hasMeta, writeMeta, readMeta,
wipeAll,
serializeSnapshot, restoreSnapshot,
};
})(window.D2);