Files
novafarma/src/systems/SaveSystem.js
2025-12-07 23:21:12 +01:00

285 lines
12 KiB
JavaScript

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();
}
});
}
}
}