Tiled Setup: - Installed Tiled v1.11.2 (winget) - Created workflow documentation (.agent/workflows/tiled-map-setup.md) - Generated demo Tiled map files (farm_map.tmx, .json, .tsx) - Created TILED_INTEGRATION_STATUS.md documentation Bug Fixes: - SaveSystem.js: Fixed compatibility with Flat2DTerrainSystem - InteractionSystem.js: Added null checks for terrainSystem - PreloadScene.js: Tiled asset loading (currently not used) Documentation: - Created DNEVNIK.md (development diary) - Updated QUICK_TASK_REFERENCE.md with recent work Note: Tiled integration incomplete (tileset size issues) - Reverted to procedural Flat2DTerrainSystem (working) - Future work: Create proper 192x192 tileset PNGs Session: 2h (20:00-22:00) Date: 14.12.2024
282 lines
11 KiB
JavaScript
282 lines
11 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 - FLAT 2D (no seed, no crops yet!)
|
|
const terrainSeed = this.scene.terrainSystem.noise?.seed || 0; // Optional for old system
|
|
|
|
// Flat2DTerrainSystem doesn't have cropsMap yet - skip for now
|
|
const cropsData = this.scene.terrainSystem.cropsMap
|
|
? Array.from(this.scene.terrainSystem.cropsMap.entries())
|
|
: [];
|
|
|
|
const decorData = this.scene.terrainSystem.decorationsMap
|
|
? 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.5, // 2D Flat Version
|
|
timestamp: Date.now(),
|
|
player: { x: playerPos.x, y: playerPos.y },
|
|
terrain: {
|
|
seed: terrainSeed,
|
|
crops: cropsData,
|
|
decorations: decorData
|
|
},
|
|
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... DISABLED - Fresh start!');
|
|
// ONEMOGOČENO - vedno vrne false za fresh start
|
|
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();
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|