class SaveSystem { constructor(scene) { this.scene = scene; this.storageKey = 'novafarma_savefile'; } saveGame() { console.log('💾 Saving game...'); if (!this.scene.player || !this.scene.terrainSystem) { console.error('Cannot save: Player or TerrainSystem missing.'); return; } const playerPos = this.scene.player.getPosition(); // Zberi podatke o NPCjih const npcsData = this.scene.npcs.map(npc => ({ type: npc.type, x: npc.gridX, y: npc.gridY })); // Zberi podatke o terenu const terrainSeed = this.scene.terrainSystem.noise.seed; // Zberi dinamične podatke terena (Crops & Modified Decor/Buildings) const cropsData = Array.from(this.scene.terrainSystem.cropsMap.entries()); // We only save Decorations that are NOT default? // For simplicity, let's save ALL current decorations, and on Load clear everything and rebuild. // Actually, decorationsMap contains objects with gridX, gridY, type. const decorData = Array.from(this.scene.terrainSystem.decorationsMap.values()); // Inventory const inventoryData = { slots: this.scene.inventorySystem.slots, gold: this.scene.inventorySystem.gold || 0 }; const saveData = { version: 2.4, // Nazaj na pixel art timestamp: Date.now(), player: { x: playerPos.x, y: playerPos.y }, terrain: { seed: terrainSeed, crops: cropsData, // array of [key, value] decorations: decorData // array of objects }, npcs: npcsData, inventory: inventoryData, time: { gameTime: this.scene.timeSystem ? this.scene.timeSystem.gameTime : 8, dayCount: this.scene.timeSystem ? this.scene.timeSystem.dayCount : 1 }, stats: this.scene.statsSystem ? { health: this.scene.statsSystem.health, hunger: this.scene.statsSystem.hunger, thirst: this.scene.statsSystem.thirst } : null, camera: { zoom: this.scene.cameras.main.zoom } }; try { const jsonString = JSON.stringify(saveData); // Compress data to save space try { const compressed = Compression.compress(jsonString); localStorage.setItem(this.storageKey, 'LZW:' + compressed); console.log(`✅ Game saved! Size: ${jsonString.length} -> ${compressed.length} chars`); } catch (compErr) { console.warn("Compression failed, saving raw JSON:", compErr); localStorage.setItem(this.storageKey, jsonString); } // Pokaži obvestilo (preko UIScene če obstaja) this.showNotification('GAME SAVED'); } catch (e) { console.error('❌ Failed to save game:', e); this.showNotification('SAVE FAILED'); } } loadGame() { console.log('📂 Loading game...'); let rawData = localStorage.getItem(this.storageKey); if (!rawData) { console.log('⚠️ No save file found.'); this.showNotification('NO SAVE FOUND'); return false; } try { let jsonString = rawData; // Check for compression if (rawData.startsWith('LZW:')) { const compressed = rawData.substring(4); // Remove prefix jsonString = Compression.decompress(compressed); } const saveData = JSON.parse(jsonString); console.log('Loading save data:', saveData); // Preveri verzijo - če je stara, izbriši save if (!saveData.version || saveData.version < 2.4) { console.log('⚠️ Stara verzija save file-a detected, clearing...'); localStorage.removeItem(this.storageKey); this.showNotification('OLD SAVE CLEARED - NEW GAME'); return false; } // 1. Load Player if (this.scene.player) { // Zahteva metodo setPosition(gridX, gridY) v Player.js // Trenutno imamo moveToGrid ampak za instant load rabimo direkten set. // Uporabimo updatePosition logic iz NPC, ali pa kar moveToGrid s hitrostjo 0? // Bolje dodati setGridPosition v Player.js. // Za zdaj workaround: this.scene.player.gridX = saveData.player.x; this.scene.player.gridY = saveData.player.y; // Force update screen pos const screenPos = this.scene.player.iso.toScreen(saveData.player.x, saveData.player.y); this.scene.player.sprite.setPosition( screenPos.x + this.scene.player.offsetX, screenPos.y + this.scene.player.offsetY ); this.scene.player.updateDepth(); } // 2. Load Terrain (Regenerate + Restore dynamic) if (this.scene.terrainSystem && saveData.terrain) { // A) Seed / Base Terrain if (saveData.terrain.seed && this.scene.terrainSystem.noise.seed !== saveData.terrain.seed) { // Regenerate world if seed mismatch // (Actually we might want to ALWAYS regenerate to clear default decors then overwrite?) // Current logic: generate() spawns default decorations. // To handle persistence properly: // 1. Clear current decorations // 2. Load saved decorations this.scene.terrainSystem.noise = new PerlinNoise(saveData.terrain.seed); // this.scene.terrainSystem.generate(); // This re-adds random flowers // Instead of full generate, we might just re-calc tiles if seed changed? // For now assume seed is constant for "New Game", but let's re-run generate to be safe } // Clear EVERYTHING first this.scene.terrainSystem.decorationsMap.clear(); this.scene.terrainSystem.decorations = []; this.scene.terrainSystem.cropsMap.clear(); // We should also hide active sprites? this.scene.terrainSystem.visibleDecorations.forEach(s => s.setVisible(false)); this.scene.terrainSystem.visibleDecorations.clear(); this.scene.terrainSystem.visibleCrops.forEach(s => s.setVisible(false)); this.scene.terrainSystem.visibleCrops.clear(); // Sproščanje objektov se samodejno dogaja preko release() v clearanju zgoraj // B) Restore Crops if (saveData.terrain.crops) { // Map was saved as array of entries saveData.terrain.crops.forEach(entry => { const [key, cropData] = entry; this.scene.terrainSystem.cropsMap.set(key, cropData); // Set flag on tile const [gx, gy] = key.split(',').map(Number); const tile = this.scene.terrainSystem.getTile(gx, gy); if (tile) tile.hasCrop = true; }); } // C) Restore Decorations (Flowers, Houses, Walls, Fences...) if (saveData.terrain.decorations) { saveData.terrain.decorations.forEach(d => { this.scene.terrainSystem.decorations.push(d); this.scene.terrainSystem.decorationsMap.set(d.id, d); const tile = this.scene.terrainSystem.getTile(d.gridX, d.gridY); if (tile) tile.hasDecoration = true; }); } // Force Update Visuals this.scene.terrainSystem.updateCulling(this.scene.cameras.main); } // 3. Load Inventory if (this.scene.inventorySystem && saveData.inventory) { this.scene.inventorySystem.slots = saveData.inventory.slots; this.scene.inventorySystem.gold = saveData.inventory.gold; this.scene.inventorySystem.updateUI(); } // 4. Load Time & Stats if (this.scene.timeSystem && saveData.time) { this.scene.timeSystem.gameTime = saveData.time.gameTime; this.scene.timeSystem.dayCount = saveData.time.dayCount || 1; } if (this.scene.statsSystem && saveData.stats) { this.scene.statsSystem.health = saveData.stats.health; this.scene.statsSystem.hunger = saveData.stats.hunger; this.scene.statsSystem.thirst = saveData.stats.thirst; } // 3. Load NPCs // 3. Load NPCs // Pobriši trenutne this.scene.npcs.forEach(npc => npc.destroy()); this.scene.npcs = []; let hasMerchant = false; // Ustvari shranjene if (saveData.npcs) { saveData.npcs.forEach(npcData => { const npc = new NPC( this.scene, npcData.x, npcData.y, this.scene.terrainOffsetX, this.scene.terrainOffsetY, npcData.type ); this.scene.npcs.push(npc); if (npcData.type === 'merchant') hasMerchant = true; }); } // Force Spawn Merchant if missing if (!hasMerchant) { // Spawn near current player position so user can find him const px = saveData.player ? saveData.player.x : 50; const py = saveData.player ? saveData.player.y : 50; const mX = Math.max(0, Math.min(99, Math.floor(px) + 3)); const mY = Math.max(0, Math.min(99, Math.floor(py) + 3)); const merchant = new NPC(this.scene, mX, mY, this.scene.terrainOffsetX, this.scene.terrainOffsetY, 'merchant'); this.scene.npcs.push(merchant); console.log("🏪 FORCE SPAWNED MERCHANT at", mX, mY); } // 4. Camera if (saveData.camera) { this.scene.cameras.main.setZoom(saveData.camera.zoom); } this.showNotification('GAME LOADED'); return true; } catch (e) { console.error('❌ Failed to load game:', e); this.showNotification('LOAD FAILED'); return false; } } showNotification(text) { const uiScene = this.scene.scene.get('UIScene'); if (uiScene) { const width = uiScene.cameras.main.width; const height = uiScene.cameras.main.height; const msg = uiScene.add.text(width / 2, height / 2, text, { fontFamily: 'Courier New', fontSize: '32px', fill: '#ffffff', backgroundColor: '#000000', padding: { x: 10, y: 5 } }); msg.setOrigin(0.5); msg.setScrollFactor(0); uiScene.tweens.add({ targets: msg, alpha: 0, duration: 2000, delay: 500, onComplete: () => { msg.destroy(); } }); } } }