115 lines
3.0 KiB
JavaScript
115 lines
3.0 KiB
JavaScript
(function (root, factory) {
|
|
"use strict";
|
|
|
|
const api = factory();
|
|
if (typeof module === "object" && module.exports) module.exports = api;
|
|
root.ArchiveWorldState = api;
|
|
}(typeof globalThis !== "undefined" ? globalThis : this, function () {
|
|
"use strict";
|
|
|
|
const VERSION = 1;
|
|
const STORAGE_KEY = "zxh_archive_world_v1";
|
|
const PALETTES = Object.freeze(["brass", "moss", "berry"]);
|
|
const LANDMARK_IDS = Object.freeze([
|
|
"gate",
|
|
"library",
|
|
"study",
|
|
"inn",
|
|
"workshop",
|
|
"playroom",
|
|
"museum",
|
|
"garden"
|
|
]);
|
|
const SPAWN = Object.freeze({ x: 640, y: 830 });
|
|
|
|
function validateName(value) {
|
|
const name = String(value || "").trim().replace(/\s+/g, " ");
|
|
return name.length >= 1 && name.length <= 20 ? name : null;
|
|
}
|
|
|
|
function validatePalette(value) {
|
|
return PALETTES.includes(value) ? value : null;
|
|
}
|
|
|
|
function fresh(name, palette) {
|
|
return {
|
|
version: VERSION,
|
|
name: validateName(name) || "",
|
|
palette: validatePalette(palette) || "brass",
|
|
discovered: [],
|
|
complete: false,
|
|
position: { x: SPAWN.x, y: SPAWN.y },
|
|
soundEnabled: false
|
|
};
|
|
}
|
|
|
|
function clamp(value, min, max, fallback) {
|
|
const number = Number(value);
|
|
return Number.isFinite(number) ? Math.min(max, Math.max(min, number)) : fallback;
|
|
}
|
|
|
|
function normalize(candidate) {
|
|
if (!candidate || candidate.version !== VERSION) return null;
|
|
const name = validateName(candidate.name);
|
|
const palette = validatePalette(candidate.palette);
|
|
if (!name || !palette) return null;
|
|
|
|
const discovered = Array.isArray(candidate.discovered)
|
|
? Array.from(new Set(candidate.discovered.filter((id) => LANDMARK_IDS.includes(id))))
|
|
: [];
|
|
|
|
return {
|
|
version: VERSION,
|
|
name,
|
|
palette,
|
|
discovered,
|
|
complete: LANDMARK_IDS.every((id) => discovered.includes(id)),
|
|
position: {
|
|
x: clamp(candidate.position && candidate.position.x, 36, 1244, SPAWN.x),
|
|
y: clamp(candidate.position && candidate.position.y, 36, 924, SPAWN.y)
|
|
},
|
|
soundEnabled: candidate.soundEnabled === true
|
|
};
|
|
}
|
|
|
|
function parse(raw) {
|
|
if (!raw) return null;
|
|
try {
|
|
return normalize(JSON.parse(raw));
|
|
} catch (_error) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function discover(state, landmarkId) {
|
|
const current = normalize(state);
|
|
if (!current || !LANDMARK_IDS.includes(landmarkId)) return current;
|
|
if (!current.discovered.includes(landmarkId)) current.discovered.push(landmarkId);
|
|
current.complete = LANDMARK_IDS.every((id) => current.discovered.includes(id));
|
|
return current;
|
|
}
|
|
|
|
function withPosition(state, x, y) {
|
|
const current = normalize(state);
|
|
if (!current) return null;
|
|
current.position.x = clamp(x, 36, 1244, SPAWN.x);
|
|
current.position.y = clamp(y, 36, 924, SPAWN.y);
|
|
return current;
|
|
}
|
|
|
|
return Object.freeze({
|
|
VERSION,
|
|
STORAGE_KEY,
|
|
PALETTES,
|
|
LANDMARK_IDS,
|
|
SPAWN,
|
|
validateName,
|
|
validatePalette,
|
|
fresh,
|
|
normalize,
|
|
parse,
|
|
discover,
|
|
withPosition
|
|
});
|
|
}));
|