"use strict"; (function (global) { const SNAPSHOT_VERSION = 1; const DEFAULT_SKIP = new Set(["world", "target", "panicTarget", "targetRef", "targetEntity", "source"]); function isPlainObject(value) { if (!value || typeof value !== "object") return false; const proto = Object.getPrototypeOf(value); return proto === Object.prototype || proto === null; } function clonePlain(value, depth = 0) { if (value == null) return value; const type = typeof value; if (type === "number") return Number.isFinite(value) ? value : 0; if (type === "string" || type === "boolean") return value; if (type === "function" || type === "symbol" || type === "undefined") return undefined; if (depth > 8) return undefined; if (Array.isArray(value)) { const out = []; for (const v of value) { const c = clonePlain(v, depth + 1); if (c !== undefined) out.push(c); } return out; } if (!isPlainObject(value)) return undefined; const out = {}; for (const [key, v] of Object.entries(value)) { if (DEFAULT_SKIP.has(key)) continue; const c = clonePlain(v, depth + 1); if (c !== undefined) out[key] = c; } return out; } function copyOwnData(obj, skip = DEFAULT_SKIP) { const out = {}; if (!obj) return out; for (const key of Object.keys(obj)) { if (skip?.has?.(key)) continue; const cloned = clonePlain(obj[key]); if (cloned !== undefined) out[key] = cloned; } return out; } function applyOwnData(target, data, skip = DEFAULT_SKIP) { if (!target || !data) return target; for (const [key, value] of Object.entries(data)) { if (skip?.has?.(key) || key.startsWith("__")) continue; const c = clonePlain(value); if (c !== undefined) target[key] = c; } return target; } function refFor(worldRef, value) { if (!value || typeof value !== "object") return null; if ((worldRef?.tarinai || []).includes(value)) return { kind: "tarinai", id: value.id || "", token: value.liveToken || 0, familyKey: value.familyKey || "" }; if ((worldRef?.items || []).includes(value)) return { kind: "item", id: value.id || "", type: value.type || "" }; if ((worldRef?.ants || []).includes(value)) return { kind: "ant", id: value.id || "" }; if (Number.isFinite(value.x) && Number.isFinite(value.y)) return { kind: "point", x: value.x, y: value.y, dead: Boolean(value.dead) }; return null; } function resolveRef(worldRef, ref) { if (!ref || typeof ref !== "object") return null; if (ref.kind === "tarinai") return (worldRef.tarinai || []).find(t => t && !t.dead && ((ref.id && t.id === ref.id) || (ref.familyKey && t.familyKey === ref.familyKey))) || null; if (ref.kind === "item") return (worldRef.items || []).find(it => it && !it.dead && it.id === ref.id) || null; if (ref.kind === "ant") return (worldRef.ants || []).find(a => a && !a.dead && a.id === ref.id) || null; if (ref.kind === "point") return { x: Number(ref.x) || 0, y: Number(ref.y) || 0, dead: Boolean(ref.dead) }; return null; } function worldData(worldRef) { const keys = [ "w", "h", "viewportW", "viewportH", "time", "day", "paused", "speed", "tool", "toolSize", "toolSizes", "frozenTarinai", "fieldType", "fieldZoom", "cameraX", "cameraY", "weather", "nextWeatherChange", "deadCount", "liveIdNext", "liveIdSerial", "lastBirthAt", "maxGeneration", "family", "familyVersion", "familyTreeDirtyReason", "relationNotices", "resolvedFightIds", "eventCounters", "foodSpoilagePenalty", "foodSpoilageEvents", "lastPhase", "colonyMood", "lastColonyMoodDay", "antUpdatePhase", "shakeTimer", "shakeDuration", "shakeStrength" ]; const out = {}; for (const key of keys) { const cloned = clonePlain(worldRef?.[key]); if (cloned !== undefined) out[key] = cloned; } return out; } function snapshotEntity(worldRef, entity, skip = new Set(["world", "target", "panicTarget", "targetRef"])) { const data = copyOwnData(entity, skip); const targetRef = refFor(worldRef, entity?.target); const panicTargetRef = refFor(worldRef, entity?.panicTarget); const targetRefObject = refFor(worldRef, entity?.targetRef); if (targetRef) data.__targetRef = targetRef; if (panicTargetRef) data.__panicTargetRef = panicTargetRef; if (targetRefObject) data.__targetRefObject = targetRefObject; return data; } function createSnapshot(worldRef = global.world) { if (!worldRef) throw new Error("world is not ready"); const selectedRef = refFor(worldRef, worldRef.selected); return { version: SNAPSHOT_VERSION, app: "tarinai_colony_game", createdAt: Date.now(), summary: { day: worldRef.day || 1, time: worldRef.clockString ? worldRef.clockString() : "", fieldType: worldRef.fieldType || "garden", population: (worldRef.tarinai || []).filter(t => t && !t.dead).length, items: (worldRef.items || []).filter(it => it && !it.dead).length, }, world: worldData(worldRef), tarinai: (worldRef.tarinai || []).filter(Boolean).map(t => snapshotEntity(worldRef, t)), items: (worldRef.items || []).filter(Boolean).map(it => snapshotEntity(worldRef, it)), ants: (worldRef.ants || []).filter(Boolean).map(a => snapshotEntity(worldRef, a)), effects: (worldRef.effects || []).filter(Boolean).map(ef => snapshotEntity(worldRef, ef)), logs: clonePlain((worldRef.logs || []).slice(0, 100)) || [], selectedRef, }; } function restoreTarinaiData(worldRef, data, opts = {}) { const TarinaiClass = global.Tarinai || (typeof Tarinai !== "undefined" ? Tarinai : null); if (!TarinaiClass) throw new Error("Tarinai is not available"); const t = new TarinaiClass(worldRef, data || {}); applyOwnData(t, data || {}, new Set(["world", "target", "panicTarget", "targetRef"])); t.world = worldRef; if (opts.clearTransient !== false) { t.target = null; t.panicTarget = null; t.sleeping = false; t.insideNestBoxId = ""; } if (typeof t.refreshEffectiveSize === "function") t.refreshEffectiveSize(); return t; } function restoreSnapshot(snapshot, worldRef = global.world) { if (!snapshot || typeof snapshot !== "object") throw new Error("invalid save data"); if (!worldRef) throw new Error("world is not ready"); const data = snapshot.version ? snapshot : JSON.parse(JSON.stringify(snapshot)); const fieldType = data.world?.fieldType || "garden"; if (typeof applyFieldLayout === "function") applyFieldLayout(fieldType); if (typeof worldRef.reset === "function") worldRef.reset(0, fieldType); worldRef.tarinai = []; worldRef.items = []; worldRef.ants = []; worldRef.effects = []; worldRef.logs = Array.isArray(data.logs) ? clonePlain(data.logs) || [] : []; worldRef.selected = null; applyOwnData(worldRef, data.world || {}, new Set(["tarinai", "items", "ants", "effects", "logs", "selected", "liveTarinai", "events", "spatial"])); worldRef.events = global.TarinaiEvents || null; worldRef.liveTarinai = new Map(); worldRef.liveIdFree = []; const items = Array.isArray(data.items) ? data.items : []; for (const saved of items) { if (!saved || !saved.type) continue; const item = new Item(saved.type, Number(saved.x) || 0, Number(saved.y) || 0); applyOwnData(item, saved, new Set(["target", "targetRef", "world"])); worldRef.items.push(item); } const tarinaiList = Array.isArray(data.tarinai) ? data.tarinai : []; for (const saved of tarinaiList) { if (!saved) continue; const t = restoreTarinaiData(worldRef, saved, { clearTransient: false }); worldRef.tarinai.push(t); if (t.id) worldRef.liveTarinai.set(t.id, { token: t.liveToken || 0, target: t }); } const ants = Array.isArray(data.ants) ? data.ants : []; for (const saved of ants) { if (!saved) continue; const ant = new AntActor(worldRef, saved); applyOwnData(ant, saved, new Set(["world", "targetRef"])); ant.world = worldRef; worldRef.ants.push(ant); } const effects = Array.isArray(data.effects) ? data.effects : []; for (const saved of effects) { if (!saved || !saved.type) continue; const ef = new Effect(saved.type, Number(saved.x) || 0, Number(saved.y) || 0, saved); applyOwnData(ef, saved, new Set(["world", "target", "targetRef"])); worldRef.effects.push(ef); } const itemById = new Map(worldRef.items.map(it => [it.id, it])); const antById = new Map(worldRef.ants.map(a => [a.id, a])); const tarinaiById = new Map(worldRef.tarinai.map(t => [t.id, t])); const resolve = (ref) => { if (!ref) return null; if (ref.kind === "item") return itemById.get(ref.id) || null; if (ref.kind === "ant") return antById.get(ref.id) || null; if (ref.kind === "tarinai") return tarinaiById.get(ref.id) || worldRef.tarinai.find(t => t.familyKey === ref.familyKey) || null; return resolveRef(worldRef, ref); }; for (const [i, saved] of tarinaiList.entries()) { const t = worldRef.tarinai[i]; if (!t) continue; t.target = resolve(saved.__targetRef); t.panicTarget = resolve(saved.__panicTargetRef); } for (const [i, saved] of ants.entries()) { const ant = worldRef.ants[i]; if (!ant) continue; ant.targetRef = resolve(saved.__targetRefObject); } worldRef.selected = resolve(data.selectedRef); worldRef.family = clonePlain(data.world?.family) || worldRef.family || {}; worldRef.relationNotices = clonePlain(data.world?.relationNotices) || {}; worldRef.resolvedFightIds = clonePlain(data.world?.resolvedFightIds) || {}; worldRef.eventCounters = clonePlain(data.world?.eventCounters) || {}; worldRef.colonyMood = clonePlain(data.world?.colonyMood) || worldRef.colonyMoodDefinition?.("relaxed") || { id: "relaxed", label: "のんびり", effects: { personality: {} } }; worldRef.familyTreeDirty = true; worldRef.drawListDirty = true; worldRef.terrainDirty = true; if (typeof worldRef.updateItemCounts === "function") worldRef.updateItemCounts(); if (typeof worldRef.updateEffectCounts === "function") worldRef.updateEffectCounts(); if (typeof worldRef.rebuildSpatial === "function") worldRef.rebuildSpatial(true); if (typeof worldRef.markTerrainDirty === "function") worldRef.markTerrainDirty("load"); if (typeof worldRef.clampCamera === "function") worldRef.clampCamera(); if (typeof renderLog === "function") renderLog(worldRef.logs || []); if (typeof resetArchiveRenderState === "function") resetArchiveRenderState(); if (typeof renderArchive === "function") renderArchive(); if (typeof renderSelected === "function") renderSelected(); if (typeof renderStats === "function") renderStats(); if (typeof render === "function") render(); if (typeof syncTopButtons === "function") syncTopButtons(); global.TarinaiFreezeSystem?.afterSnapshotRestored?.(worldRef); return true; } global.TarinaiSnapshot = { version: SNAPSHOT_VERSION, clonePlain, copyOwnData, applyOwnData, refFor, resolveRef, worldData, snapshotEntity, createSnapshot, restoreSnapshot, restoreTarinaiData, }; })(typeof window !== "undefined" ? window : globalThis);