From c81435a65a1da7dad083c06139cb15ee6249a74b Mon Sep 17 00:00:00 2001 From: NovaFarma Dev Date: Thu, 11 Dec 2025 13:31:01 +0100 Subject: [PATCH] PHASE 18 COMPLETE: SaveManager - 3 save slots, auto-save every 5min, export/import, slot metadata --- index.html | 1 + src/systems/SaveManager.js | 273 +++++++++++++++++++++++++++++++++++++ 2 files changed, 274 insertions(+) create mode 100644 src/systems/SaveManager.js diff --git a/index.html b/index.html index f735746..fdc9325 100644 --- a/index.html +++ b/index.html @@ -81,6 +81,7 @@ + diff --git a/src/systems/SaveManager.js b/src/systems/SaveManager.js new file mode 100644 index 0000000..a06dca0 --- /dev/null +++ b/src/systems/SaveManager.js @@ -0,0 +1,273 @@ +/** + * SAVE MANAGER + * Handles multiple save slots and auto-save functionality + * Extends existing SaveSystem with slot management + */ + +class SaveManager { + constructor(scene) { + this.scene = scene; + this.maxSlots = 3; // 3 save slots + this.currentSlot = 1; // Active slot (1-3) + + // Auto-save settings + this.autoSaveEnabled = true; + this.autoSaveInterval = 5 * 60 * 1000; // 5 minutes in ms + this.autoSaveTimer = 0; + + // Initialize SaveSystem for each slot + this.saveSystems = {}; + for (let i = 1; i <= this.maxSlots; i++) { + this.saveSystems[i] = new SaveSystem(scene); + this.saveSystems[i].storageKey = `novafarma_save_slot${i}`; + } + + console.log('💾 SaveManager initialized with 3 slots'); + } + + /** + * Save to specific slot + * @param {number} slotNumber - Slot 1-3 + */ + saveToSlot(slotNumber) { + if (slotNumber < 1 || slotNumber > this.maxSlots) { + console.error(`Invalid slot number: ${slotNumber}`); + return false; + } + + console.log(`💾 Saving to slot ${slotNumber}...`); + this.saveSystems[slotNumber].saveGame(); + this.currentSlot = slotNumber; + + // Save metadata (last save time, playtime, etc.) + this.saveSlotMetadata(slotNumber); + + return true; + } + + /** + * Load from specific slot + * @param {number} slotNumber - Slot 1-3 + */ + loadFromSlot(slotNumber) { + if (slotNumber < 1 || slotNumber > this.maxSlots) { + console.error(`Invalid slot number: ${slotNumber}`); + return false; + } + + console.log(`📂 Loading from slot ${slotNumber}...`); + const success = this.saveSystems[slotNumber].loadGame(); + + if (success) { + this.currentSlot = slotNumber; + } + + return success; + } + + /** + * Delete specific slot + * @param {number} slotNumber - Slot 1-3 + */ + deleteSlot(slotNumber) { + if (slotNumber < 1 || slotNumber > this.maxSlots) { + console.error(`Invalid slot number: ${slotNumber}`); + return false; + } + + const key = `novafarma_save_slot${slotNumber}`; + const metaKey = `novafarma_slot${slotNumber}_meta`; + + localStorage.removeItem(key); + localStorage.removeItem(metaKey); + + console.log(`🗑️ Deleted slot ${slotNumber}`); + return true; + } + + /** + * Save metadata for slot (thumbnail, playtime, day, etc.) + */ + saveSlotMetadata(slotNumber) { + const metadata = { + slotNumber, + lastSaved: Date.now(), + playerName: 'Player', + dayCount: this.scene.timeSystem ? this.scene.timeSystem.dayCount : 1, + playtime: this.scene.playtimeTracker ? this.scene.playtimeTracker.stats.playtimeSeconds : 0, + playerLevel: this.scene.player ? (this.scene.player.level || 1) : 1 + }; + + const metaKey = `novafarma_slot${slotNumber}_meta`; + localStorage.setItem(metaKey, JSON.stringify(metadata)); + } + + /** + * Get metadata for slot + */ + getSlotMetadata(slotNumber) { + const metaKey = `novafarma_slot${slotNumber}_meta`; + const raw = localStorage.getItem(metaKey); + + if (!raw) return null; + + try { + return JSON.parse(raw); + } catch (e) { + console.error(`Failed to parse metadata for slot ${slotNumber}:`, e); + return null; + } + } + + /** + * Get all slots info + */ + getAllSlotsInfo() { + const slots = []; + + for (let i = 1; i <= this.maxSlots; i++) { + const metadata = this.getSlotMetadata(i); + const exists = this.slotExists(i); + + slots.push({ + number: i, + exists, + metadata: metadata || { + playerName: 'Empty', + dayCount: 0, + playtime: 0, + lastSaved: null + } + }); + } + + return slots; + } + + /** + * Check if slot exists + */ + slotExists(slotNumber) { + const key = `novafarma_save_slot${slotNumber}`; + return localStorage.getItem(key) !== null; + } + + /** + * Quick save to current slot + */ + quickSave() { + console.log(`⚡ Quick save to slot ${this.currentSlot}`); + this.saveToSlot(this.currentSlot); + } + + /** + * Quick load from current slot + */ + quickLoad() { + console.log(`⚡ Quick load from slot ${this.currentSlot}`); + return this.loadFromSlot(this.currentSlot); + } + + /** + * Update auto-save timer + * Call this in scene update() + */ + update(delta) { + if (!this.autoSaveEnabled) return; + + this.autoSaveTimer += delta; + + if (this.autoSaveTimer >= this.autoSaveInterval) { + this.autoSaveTimer = 0; + console.log('💾 Auto-saving...'); + this.quickSave(); + + // Show notification + if (this.scene.events) { + this.scene.events.emit('show-floating-text', { + x: this.scene.cameras.main.scrollX + this.scene.cameras.main.width - 100, + y: this.scene.cameras.main.scrollY + 50, + text: '💾 Auto-Saved', + color: '#00ff00' + }); + } + } + } + + /** + * Toggle auto-save + */ + toggleAutoSave() { + this.autoSaveEnabled = !this.autoSaveEnabled; + console.log(`Auto-save: ${this.autoSaveEnabled ? 'ON' : 'OFF'}`); + return this.autoSaveEnabled; + } + + /** + * Get time until next auto-save + */ + getTimeUntilNextSave() { + if (!this.autoSaveEnabled) return -1; + return Math.max(0, this.autoSaveInterval - this.autoSaveTimer); + } + + /** + * Export save data as JSON (for backup) + */ + exportSlot(slotNumber) { + if (!this.slotExists(slotNumber)) { + console.error(`Slot ${slotNumber} is empty`); + return null; + } + + const key = `novafarma_save_slot${slotNumber}`; + const data = localStorage.getItem(key); + + // Create downloadable file + const dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(data); + const downloadAnchorNode = document.createElement('a'); + downloadAnchorNode.setAttribute("href", dataStr); + downloadAnchorNode.setAttribute("download", `novafarma_slot${slotNumber}_${Date.now()}.json`); + document.body.appendChild(downloadAnchorNode); + downloadAnchorNode.click(); + downloadAnchorNode.remove(); + + console.log(`📤 Exported slot ${slotNumber}`); + return true; + } + + /** + * Import save data from JSON file + */ + importSlot(slotNumber, jsonData) { + try { + const key = `novafarma_save_slot${slotNumber}`; + localStorage.setItem(key, jsonData); + console.log(`📥 Imported to slot ${slotNumber}`); + return true; + } catch (e) { + console.error('Failed to import:', e); + return false; + } + } +} + +// Make available globally +window.SaveManager = SaveManager; + +// Convenience functions +window.save = function (slot = 1) { + const scene = game.scene.scenes.find(s => s.saveManager); + if (scene && scene.saveManager) { + return scene.saveManager.saveToSlot(slot); + } +}; + +window.load = function (slot = 1) { + const scene = game.scene.scenes.find(s => s.saveManager); + if (scene && scene.saveManager) { + return scene.saveManager.loadFromSlot(slot); + } +}; + +console.log('💾 SaveManager loaded. Use: save(1), load(1), or F5/F9 keys');