PHASE 18 COMPLETE: SaveManager - 3 save slots, auto-save every 5min, export/import, slot metadata
This commit is contained in:
@@ -81,6 +81,7 @@
|
|||||||
<script src="src/systems/Antigravity.js"></script>
|
<script src="src/systems/Antigravity.js"></script>
|
||||||
<script src="src/systems/PathfindingSystem.js"></script>
|
<script src="src/systems/PathfindingSystem.js"></script>
|
||||||
<script src="src/systems/SaveSystem.js"></script>
|
<script src="src/systems/SaveSystem.js"></script>
|
||||||
|
<script src="src/systems/SaveManager.js"></script>
|
||||||
<!-- TimeSystem merged into WeatherSystem -->
|
<!-- TimeSystem merged into WeatherSystem -->
|
||||||
<script src="src/systems/StatsSystem.js"></script>
|
<script src="src/systems/StatsSystem.js"></script>
|
||||||
<script src="src/systems/InventorySystem.js"></script>
|
<script src="src/systems/InventorySystem.js"></script>
|
||||||
|
|||||||
273
src/systems/SaveManager.js
Normal file
273
src/systems/SaveManager.js
Normal file
@@ -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');
|
||||||
Reference in New Issue
Block a user