Files
ai.interactive.fiction/public/js/persistence-manager.js
T
2025-04-05 11:29:30 +00:00

509 lines
15 KiB
JavaScript

/**
* Persistence Manager Module
* Handles saving and loading game state and user preferences
*/
import { BaseModule } from './base-module.js';
import { moduleRegistry } from './module-registry.js';
class PersistenceManagerModule extends BaseModule {
/**
* Create a new persistence manager
*/
constructor() {
super('persistence-manager', 'Persistence Manager');
// Storage keys
this.keys = {
gameState: 'ai-interactive-fiction-state',
preferences: 'ai-interactive-fiction-preferences',
saveSlots: 'ai-interactive-fiction-saves'
};
// Current game state
this.gameState = null;
// User preferences
this.preferences = null;
// Save slots
this.saveSlots = {};
// Default preferences
this.defaultPreferences = {
animation: {
enabled: true,
speed: 50 // 0-100 scale, 50 is default
},
tts: {
enabled: false,
provider: 'browser', // 'browser', 'api', 'kokoro'
voice: '',
volume: 1.0,
rate: 1.0,
language: 'en-us' // Default language, will be updated during initialization
},
audio: {
masterVolume: 1.0,
musicVolume: 0.7,
sfxVolume: 1.0,
musicEnabled: true,
sfxEnabled: true
},
accessibility: {
highContrast: false,
largerText: false
},
app: {
locale: 'en-us',
theme: 'default'
}
};
// Bind methods
this.bindMethods([
'saveGameState',
'loadGameState',
'savePreferences',
'loadPreferences',
'getPreference',
'updatePreference',
'resetPreferences',
'createSaveSlot',
'loadSaveSlot',
'deleteSaveSlot',
'getAllSaveSlots'
]);
// Remove circular dependency
this.dependencies = [];
}
/**
* Initialize the module
* @returns {Promise<boolean>} - Resolves with success status
*/
async initialize() {
try {
this.reportProgress(10, "Initializing persistence manager");
// Load preferences first (with default language settings)
this.loadPreferences();
// Load save slots
this.loadSaveSlots();
this.reportProgress(100, "Persistence manager ready");
return true;
} catch (error) {
console.error("Error initializing persistence manager:", error);
this.reportProgress(100, "Persistence manager failed");
return false;
}
}
/**
* Save the current game state
* @param {Object} state - Game state to save
* @returns {boolean} - Success status
*/
saveGameState(state) {
if (!state) return false;
try {
this.gameState = state;
localStorage.setItem(this.keys.gameState, JSON.stringify(state));
// Dispatch event
this.dispatchEvent('game-state-saved', {
timestamp: new Date().toISOString()
});
return true;
} catch (error) {
console.error("Error saving game state:", error);
return false;
}
}
/**
* Load the current game state
* @returns {Object|null} - Loaded game state or null if not found
*/
loadGameState() {
try {
const stateJson = localStorage.getItem(this.keys.gameState);
if (!stateJson) return null;
this.gameState = JSON.parse(stateJson);
return this.gameState;
} catch (error) {
console.error("Error loading game state:", error);
return null;
}
}
/**
* Save user preferences
* @returns {boolean} - Success status
*/
savePreferences() {
try {
localStorage.setItem(this.keys.preferences, JSON.stringify(this.preferences));
// Dispatch event
this.dispatchEvent('preferences-saved', {
timestamp: new Date().toISOString()
});
return true;
} catch (error) {
console.error("Error saving preferences:", error);
return false;
}
}
/**
* Load user preferences
* @returns {Object} - Loaded preferences or default preferences if not found
*/
loadPreferences() {
try {
const prefsJson = localStorage.getItem(this.keys.preferences);
if (prefsJson) {
// Parse stored preferences
const storedPrefs = JSON.parse(prefsJson);
// Merge with default preferences to ensure all keys exist
this.preferences = this.mergeWithDefaults(storedPrefs, this.defaultPreferences);
} else {
// Use default preferences if none found
this.preferences = JSON.parse(JSON.stringify(this.defaultPreferences));
// Try to set locale based on browser language
const browserLocale = navigator.language.toLowerCase();
if (browserLocale) {
this.preferences.app.locale = browserLocale;
}
}
return this.preferences;
} catch (error) {
console.error("Error loading preferences:", error);
// Fall back to default preferences
this.preferences = JSON.parse(JSON.stringify(this.defaultPreferences));
return this.preferences;
}
}
/**
* Merge stored preferences with defaults to ensure all keys exist
* @param {Object} stored - Stored preferences
* @param {Object} defaults - Default preferences
* @returns {Object} - Merged preferences
*/
mergeWithDefaults(stored, defaults) {
const result = {};
// For each category in defaults
for (const category in defaults) {
result[category] = {};
// Copy all settings from defaults for this category
for (const setting in defaults[category]) {
// Use stored value if it exists, otherwise use default
result[category][setting] = (stored[category] && stored[category][setting] !== undefined)
? stored[category][setting]
: defaults[category][setting];
}
// Copy any additional settings from stored that aren't in defaults
if (stored[category]) {
for (const setting in stored[category]) {
if (result[category][setting] === undefined) {
result[category][setting] = stored[category][setting];
}
}
}
}
// Copy any additional categories from stored that aren't in defaults
for (const category in stored) {
if (result[category] === undefined) {
result[category] = stored[category];
}
}
return result;
}
/**
* Get a specific preference
* @param {string} category - Preference category
* @param {string} setting - Preference setting
* @param {*} defaultValue - Default value if preference not found
* @returns {*} - Preference value
*/
getPreference(category, setting, defaultValue = null) {
if (!this.preferences) {
this.loadPreferences();
}
if (this.preferences[category] && this.preferences[category][setting] !== undefined) {
return this.preferences[category][setting];
}
// If default value provided, use it
if (defaultValue !== null) {
return defaultValue;
}
// Otherwise check default preferences
if (this.defaultPreferences[category] && this.defaultPreferences[category][setting] !== undefined) {
return this.defaultPreferences[category][setting];
}
// If all else fails, return null
return null;
}
/**
* Update a specific preference
* @param {string} category - Preference category
* @param {string} setting - Preference setting
* @param {*} value - New value
* @returns {boolean} - Success status
*/
updatePreference(category, setting, value) {
if (!this.preferences) {
this.loadPreferences();
}
// Create category if it doesn't exist
if (!this.preferences[category]) {
this.preferences[category] = {};
}
// Update preference
const oldValue = this.preferences[category][setting];
this.preferences[category][setting] = value;
// Save preferences
this.savePreferences();
// Dispatch event if value changed
if (oldValue !== value) {
this.dispatchEvent('preference-changed', {
category,
setting,
value,
oldValue
});
}
return true;
}
/**
* Reset preferences to defaults
* @returns {boolean} - Success status
*/
resetPreferences() {
try {
// Clone default preferences
this.preferences = JSON.parse(JSON.stringify(this.defaultPreferences));
// Save preferences
this.savePreferences();
// Dispatch event
this.dispatchEvent('preferences-reset', {
timestamp: new Date().toISOString()
});
return true;
} catch (error) {
console.error("Error resetting preferences:", error);
return false;
}
}
/**
* Get all preferences
* @returns {Object} - All preferences
*/
getAllPreferences() {
if (!this.preferences) {
this.loadPreferences();
}
return this.preferences;
}
/**
* Load save slots
* @returns {Object} - Save slots
*/
loadSaveSlots() {
try {
const slotsJson = localStorage.getItem(this.keys.saveSlots);
if (slotsJson) {
this.saveSlots = JSON.parse(slotsJson);
} else {
this.saveSlots = {};
}
return this.saveSlots;
} catch (error) {
console.error("Error loading save slots:", error);
this.saveSlots = {};
return this.saveSlots;
}
}
/**
* Save save slots
* @returns {boolean} - Success status
*/
saveSaveSlots() {
try {
localStorage.setItem(this.keys.saveSlots, JSON.stringify(this.saveSlots));
return true;
} catch (error) {
console.error("Error saving save slots:", error);
return false;
}
}
/**
* Create a new save slot
* @param {string} name - Save slot name
* @param {Object} state - Game state to save
* @returns {string|null} - Save slot ID or null if failed
*/
createSaveSlot(name, state) {
if (!name || !state) return null;
try {
// Generate unique ID
const id = `save_${Date.now()}`;
// Create save slot
this.saveSlots[id] = {
id,
name,
timestamp: new Date().toISOString(),
state
};
// Save save slots
this.saveSaveSlots();
// Dispatch event
this.dispatchEvent('save-slot-created', {
id,
name,
timestamp: new Date().toISOString()
});
return id;
} catch (error) {
console.error("Error creating save slot:", error);
return null;
}
}
/**
* Load a save slot
* @param {string} id - Save slot ID
* @returns {Object|null} - Game state or null if not found
*/
loadSaveSlot(id) {
if (!id || !this.saveSlots[id]) return null;
try {
const saveSlot = this.saveSlots[id];
// Set as current game state
this.gameState = saveSlot.state;
// Save current game state
this.saveGameState(this.gameState);
// Dispatch event
this.dispatchEvent('save-slot-loaded', {
id,
name: saveSlot.name,
timestamp: new Date().toISOString()
});
return this.gameState;
} catch (error) {
console.error("Error loading save slot:", error);
return null;
}
}
/**
* Delete a save slot
* @param {string} id - Save slot ID
* @returns {boolean} - Success status
*/
deleteSaveSlot(id) {
if (!id || !this.saveSlots[id]) return false;
try {
// Get save slot name before deleting
const name = this.saveSlots[id].name;
// Delete save slot
delete this.saveSlots[id];
// Save save slots
this.saveSaveSlots();
// Dispatch event
this.dispatchEvent('save-slot-deleted', {
id,
name,
timestamp: new Date().toISOString()
});
return true;
} catch (error) {
console.error("Error deleting save slot:", error);
return false;
}
}
/**
* Get all save slots
* @returns {Object} - All save slots
*/
getAllSaveSlots() {
if (!this.saveSlots) {
this.loadSaveSlots();
}
return this.saveSlots;
}
/**
* Clean up when module is disposed
*/
dispose() {
// Nothing to clean up
}
}
// Create the singleton instance
const PersistenceManager = new PersistenceManagerModule();
// Register with the module registry
moduleRegistry.register(PersistenceManager);
// Export the module
export { PersistenceManager };