diff --git a/Client-Server/assets/textures/armors/basic_armor.png b/Client-Server/assets/textures/armors/basic_armor.png
new file mode 100644
index 0000000..b837e31
Binary files /dev/null and b/Client-Server/assets/textures/armors/basic_armor.png differ
diff --git a/Client-Server/assets/textures/armors/heavy_armor.png b/Client-Server/assets/textures/armors/heavy_armor.png
new file mode 100644
index 0000000..541fd8c
Binary files /dev/null and b/Client-Server/assets/textures/armors/heavy_armor.png differ
diff --git a/Client-Server/assets/textures/armors/medium_armor.png b/Client-Server/assets/textures/armors/medium_armor.png
new file mode 100644
index 0000000..2ad549a
Binary files /dev/null and b/Client-Server/assets/textures/armors/medium_armor.png differ
diff --git a/Client-Server/assets/textures/base/command_center.png b/Client-Server/assets/textures/base/command_center.png
new file mode 100644
index 0000000..0733bf5
Binary files /dev/null and b/Client-Server/assets/textures/base/command_center.png differ
diff --git a/Client-Server/assets/textures/base/mining_facility.png b/Client-Server/assets/textures/base/mining_facility.png
new file mode 100644
index 0000000..5898f23
Binary files /dev/null and b/Client-Server/assets/textures/base/mining_facility.png differ
diff --git a/Client-Server/assets/textures/items/advanced_circuitboard.png b/Client-Server/assets/textures/items/advanced_circuitboard.png
new file mode 100644
index 0000000..21b1421
Binary files /dev/null and b/Client-Server/assets/textures/items/advanced_circuitboard.png differ
diff --git a/Client-Server/assets/textures/items/advanced_component.png b/Client-Server/assets/textures/items/advanced_component.png
new file mode 100644
index 0000000..2e216a3
Binary files /dev/null and b/Client-Server/assets/textures/items/advanced_component.png differ
diff --git a/Client-Server/assets/textures/items/advanced_components.png b/Client-Server/assets/textures/items/advanced_components.png
new file mode 100644
index 0000000..2a71830
Binary files /dev/null and b/Client-Server/assets/textures/items/advanced_components.png differ
diff --git a/Client-Server/assets/textures/items/bandages.png b/Client-Server/assets/textures/items/bandages.png
new file mode 100644
index 0000000..3ca78e8
Binary files /dev/null and b/Client-Server/assets/textures/items/bandages.png differ
diff --git a/Client-Server/assets/textures/items/basic_circuitboard.png b/Client-Server/assets/textures/items/basic_circuitboard.png
new file mode 100644
index 0000000..7912445
Binary files /dev/null and b/Client-Server/assets/textures/items/basic_circuitboard.png differ
diff --git a/Client-Server/assets/textures/items/battery.png b/Client-Server/assets/textures/items/battery.png
new file mode 100644
index 0000000..e024d52
Binary files /dev/null and b/Client-Server/assets/textures/items/battery.png differ
diff --git a/Client-Server/assets/textures/items/common_circuitboard.png b/Client-Server/assets/textures/items/common_circuitboard.png
new file mode 100644
index 0000000..c60cde1
Binary files /dev/null and b/Client-Server/assets/textures/items/common_circuitboard.png differ
diff --git a/Client-Server/assets/textures/items/copper_ore.png b/Client-Server/assets/textures/items/copper_ore.png
new file mode 100644
index 0000000..f6b6592
Binary files /dev/null and b/Client-Server/assets/textures/items/copper_ore.png differ
diff --git a/Client-Server/assets/textures/items/copper_wire.png b/Client-Server/assets/textures/items/copper_wire.png
new file mode 100644
index 0000000..82abfa3
Binary files /dev/null and b/Client-Server/assets/textures/items/copper_wire.png differ
diff --git a/Client-Server/assets/textures/items/energy_crystal.png b/Client-Server/assets/textures/items/energy_crystal.png
new file mode 100644
index 0000000..62abaf4
Binary files /dev/null and b/Client-Server/assets/textures/items/energy_crystal.png differ
diff --git a/Client-Server/assets/textures/items/health_pack.png b/Client-Server/assets/textures/items/health_pack.png
new file mode 100644
index 0000000..14694d8
Binary files /dev/null and b/Client-Server/assets/textures/items/health_pack.png differ
diff --git a/Client-Server/assets/textures/items/herbs.png b/Client-Server/assets/textures/items/herbs.png
new file mode 100644
index 0000000..c7d1e75
Binary files /dev/null and b/Client-Server/assets/textures/items/herbs.png differ
diff --git a/Client-Server/assets/textures/items/iron_ore.png b/Client-Server/assets/textures/items/iron_ore.png
new file mode 100644
index 0000000..6786de3
Binary files /dev/null and b/Client-Server/assets/textures/items/iron_ore.png differ
diff --git a/Client-Server/assets/textures/items/leather.png b/Client-Server/assets/textures/items/leather.png
new file mode 100644
index 0000000..55770b9
Binary files /dev/null and b/Client-Server/assets/textures/items/leather.png differ
diff --git a/Client-Server/assets/textures/items/mega_health_pack.png b/Client-Server/assets/textures/items/mega_health_pack.png
new file mode 100644
index 0000000..c12acc9
Binary files /dev/null and b/Client-Server/assets/textures/items/mega_health_pack.png differ
diff --git a/Client-Server/assets/textures/items/stell_plate.png b/Client-Server/assets/textures/items/stell_plate.png
new file mode 100644
index 0000000..4bd086d
Binary files /dev/null and b/Client-Server/assets/textures/items/stell_plate.png differ
diff --git a/Client-Server/assets/textures/items/tin_bar.png b/Client-Server/assets/textures/items/tin_bar.png
new file mode 100644
index 0000000..a2daeac
Binary files /dev/null and b/Client-Server/assets/textures/items/tin_bar.png differ
diff --git a/Client-Server/assets/textures/missing-texture.png b/Client-Server/assets/textures/missing-texture.png
new file mode 100644
index 0000000..b808c02
Binary files /dev/null and b/Client-Server/assets/textures/missing-texture.png differ
diff --git a/Client-Server/assets/textures/ships/heavy_cruiser.png b/Client-Server/assets/textures/ships/heavy_cruiser.png
new file mode 100644
index 0000000..38bcffa
Binary files /dev/null and b/Client-Server/assets/textures/ships/heavy_cruiser.png differ
diff --git a/Client-Server/assets/textures/ships/heavy_destroyer.png b/Client-Server/assets/textures/ships/heavy_destroyer.png
new file mode 100644
index 0000000..a9366bd
Binary files /dev/null and b/Client-Server/assets/textures/ships/heavy_destroyer.png differ
diff --git a/Client-Server/assets/textures/ships/light_destroyer.png b/Client-Server/assets/textures/ships/light_destroyer.png
new file mode 100644
index 0000000..349c011
Binary files /dev/null and b/Client-Server/assets/textures/ships/light_destroyer.png differ
diff --git a/Client-Server/assets/textures/ships/starter_cruiser.png b/Client-Server/assets/textures/ships/starter_cruiser.png
new file mode 100644
index 0000000..2676b9d
Binary files /dev/null and b/Client-Server/assets/textures/ships/starter_cruiser.png differ
diff --git a/Client-Server/assets/textures/weapons/laser_pistol.png b/Client-Server/assets/textures/weapons/laser_pistol.png
new file mode 100644
index 0000000..da34474
Binary files /dev/null and b/Client-Server/assets/textures/weapons/laser_pistol.png differ
diff --git a/Client-Server/assets/textures/weapons/laser_sniper_rifle.png b/Client-Server/assets/textures/weapons/laser_sniper_rifle.png
new file mode 100644
index 0000000..05dde27
Binary files /dev/null and b/Client-Server/assets/textures/weapons/laser_sniper_rifle.png differ
diff --git a/Client-Server/assets/textures/weapons/starter_blaster.png b/Client-Server/assets/textures/weapons/starter_blaster.png
new file mode 100644
index 0000000..2474fde
Binary files /dev/null and b/Client-Server/assets/textures/weapons/starter_blaster.png differ
diff --git a/Client-Server/electron-main copy.js b/Client-Server/electron-main copy.js
new file mode 100644
index 0000000..3296162
--- /dev/null
+++ b/Client-Server/electron-main copy.js
@@ -0,0 +1,451 @@
+const { app, BrowserWindow, Menu, shell, ipcMain } = require('electron');
+const path = require('path');
+const fs = require('fs');
+const logger = require('./js/core/Logger');
+
+console.log('[MAIN PROCESS] Electron main process starting...');
+console.log('[MAIN PROCESS] Node.js version:', process.version);
+console.log('[MAIN PROCESS] Electron version:', process.versions.electron);
+console.log('[MAIN PROCESS] Platform:', process.platform);
+console.log('[MAIN PROCESS] Current working directory:', process.cwd());
+
+// Keep a global reference of the window object
+let mainWindow;
+
+function createWindow() {
+ console.log('[MAIN PROCESS] createWindow() called');
+
+ try {
+ console.log('[MAIN PROCESS] Creating BrowserWindow...');
+ // Create the browser window
+ mainWindow = new BrowserWindow({
+ width: 1200,
+ height: 832, // 800 + 32px for custom title bar
+ minWidth: 1200,
+ minHeight: 832,
+ maxWidth: 1200,
+ maxHeight: 832,
+ resizable: false,
+ frame: false,
+ titleBarStyle: 'hidden',
+ webPreferences: {
+ nodeIntegration: true,
+ contextIsolation: false,
+ enableRemoteModule: true,
+ webSecurity: true
+ },
+ icon: path.join(__dirname, 'assets/icon.png'),
+ show: false, // Don't show until ready-to-show
+ title: 'Galaxy Strike Online'
+ });
+
+ console.log('[MAIN PROCESS] BrowserWindow created successfully');
+ console.log('[MAIN PROCESS] Loading index.html...');
+
+ // Load the index.html file
+ mainWindow.loadFile('index.html');
+
+ console.log('[MAIN PROCESS] index.html loaded, setting up electronAPI...');
+
+ // Set up electronAPI after DOM is ready
+ mainWindow.webContents.on('dom-ready', () => {
+ console.log('[MAIN PROCESS] DOM is ready, setting up electronAPI...');
+ mainWindow.webContents.executeJavaScript(`
+ console.log('[RENDERER] Setting up electronAPI...');
+ window.electronAPI = {
+ minimizeWindow: () => require('electron').ipcRenderer.send('minimize-window'),
+ closeWindow: () => require('electron').ipcRenderer.send('close-window'),
+ toggleFullscreen: () => require('electron').ipcRenderer.send('toggle-fullscreen'),
+ log: (level, message, data) => require('electron').ipcRenderer.send('log-message', { level, message, data }),
+ createSaveFolders: (saveSlots) => require('electron').ipcRenderer.invoke('create-save-folders', saveSlots),
+ testFileAccess: (slotPath) => require('electron').ipcRenderer.invoke('test-file-access', slotPath),
+ saveGame: (slot, saveData) => require('electron').ipcRenderer.invoke('save-game', slot, saveData),
+ loadGame: (slot) => require('electron').ipcRenderer.invoke('load-game', slot),
+ getPath: (name) => require('electron').ipcRenderer.invoke('get-path', name),
+ deleteSaveFile: (slot) => require('electron').ipcRenderer.invoke('delete-save-file', slot)
+ };
+ console.log('[RENDERER] electronAPI setup completed');
+ `).then(() => {
+ console.log('[MAIN PROCESS] electronAPI setup completed');
+ }).catch((error) => {
+ console.error('[MAIN PROCESS] Failed to setup electronAPI:', error);
+ });
+ });
+
+ // Show window when ready
+ mainWindow.once('ready-to-show', () => {
+ console.log('[MAIN PROCESS] Window ready-to-show event fired');
+ mainWindow.show();
+ });
+
+ // Open DevTools in development
+ if (process.argv.includes('--dev')) {
+ console.log('[MAIN PROCESS] Opening DevTools...');
+ mainWindow.webContents.openDevTools();
+ }
+
+ // Handle window closed
+ mainWindow.on('closed', () => {
+ console.log('[MAIN PROCESS] Window closed event fired');
+ mainWindow = null;
+ });
+
+ // Handle renderer process crashes
+ mainWindow.webContents.on('render-process-gone', (event, details) => {
+ console.error('[MAIN PROCESS] Renderer process crashed:', details);
+ console.error('[MAIN PROCESS] Crash reason:', details.reason);
+ console.error('[MAIN PROCESS] Exit code:', details.exitCode);
+ });
+
+ // Handle renderer process unresponsive
+ mainWindow.webContents.on('unresponsive', () => {
+ console.warn('[MAIN PROCESS] Renderer process unresponsive');
+ });
+
+ // Handle renderer process responsive again
+ mainWindow.webContents.on('responsive', () => {
+ console.log('[MAIN PROCESS] Renderer process responsive again');
+ });
+
+ // Handle console messages from renderer
+ mainWindow.webContents.on('console-message', (event, level, message, line, sourceId) => {
+ console.log(`[RENDERER CONSOLE] [${level}] ${message} (line: ${line}, source: ${sourceId})`);
+ });
+
+ // Handle page load errors
+ mainWindow.webContents.on('did-fail-load', (event, errorCode, errorDescription, validatedURL, isMainFrame) => {
+ console.error('[MAIN PROCESS] Page failed to load:', errorCode, errorDescription, validatedURL);
+ });
+
+ // Handle page load success
+ mainWindow.webContents.on('did-finish-load', () => {
+ console.log('[MAIN PROCESS] Page finished loading');
+ });
+
+ // Handle DOM ready
+ mainWindow.webContents.on('dom-ready', () => {
+ console.log('[MAIN PROCESS] DOM is ready');
+ });
+
+ // Handle external links
+ mainWindow.webContents.setWindowOpenHandler(({ url }) => {
+ console.log('[MAIN PROCESS] External link requested:', url);
+ shell.openExternal(url);
+ return { action: 'deny' };
+ });
+
+ console.log('[MAIN PROCESS] createWindow() completed successfully');
+
+ } catch (error) {
+ console.error('[MAIN PROCESS] Error in createWindow():', error);
+ console.error('[MAIN PROCESS] Error stack:', error.stack);
+ }
+}
+
+// IPC handlers for save operations
+ipcMain.handle('create-save-folders', async (event, saveSlots) => {
+ console.log('[MAIN PROCESS] create-save-folders called with saveSlots:', saveSlots);
+ try {
+ const userDataPath = app.getPath('userData');
+ console.log('[MAIN PROCESS] userDataPath:', userDataPath);
+ const savesDir = path.join(userDataPath, 'saves');
+ console.log('[MAIN PROCESS] savesDir:', savesDir);
+
+ // Create main saves directory
+ if (!fs.existsSync(savesDir)) {
+ console.log('[MAIN PROCESS] Creating saves directory:', savesDir);
+ fs.mkdirSync(savesDir, { recursive: true });
+ console.log('[MAIN PROCESS] Saves directory created successfully');
+ } else {
+ console.log('[MAIN PROCESS] Saves directory already exists');
+ }
+
+ const paths = {
+ base: savesDir,
+ slots: []
+ };
+
+ // Create save slot directories
+ for (let i = 1; i <= saveSlots; i++) {
+ const slotDir = path.join(savesDir, `slot${i}`);
+ console.log(`[MAIN PROCESS] Checking/creating slot ${i} directory:`, slotDir);
+ if (!fs.existsSync(slotDir)) {
+ console.log(`[MAIN PROCESS] Creating slot ${i} directory`);
+ fs.mkdirSync(slotDir, { recursive: true });
+
+ // Create initial save info file
+ const saveInfo = {
+ slot: i,
+ created: new Date().toISOString(),
+ version: '1.0.0',
+ exists: false
+ };
+
+ const infoPath = path.join(slotDir, 'saveinfo.json');
+ fs.writeFileSync(infoPath, JSON.stringify(saveInfo, null, 2));
+ console.log(`[MAIN PROCESS] Created save info for slot ${i}`);
+ } else {
+ console.log(`[MAIN PROCESS] Slot ${i} directory already exists`);
+ }
+ paths.slots.push(slotDir);
+ }
+
+ console.log('[MAIN PROCESS] Save folders created successfully, returning paths:', paths);
+ return { success: true, paths };
+ } catch (error) {
+ console.error('[MAIN PROCESS] Failed to create save folders:', error);
+ return { success: false, error: error.message };
+ }
+});
+
+ipcMain.handle('test-file-access', async (event, slotPath) => {
+ try {
+ const testFile = path.join(slotPath, 'access_test.txt');
+ fs.writeFileSync(testFile, 'test');
+ fs.unlinkSync(testFile);
+ return { success: true };
+ } catch (error) {
+ return { success: false, error: error.message };
+ }
+});
+
+ipcMain.handle('save-game', async (event, slot, saveData) => {
+ try {
+ const userDataPath = app.getPath('userData');
+ const savesDir = path.join(userDataPath, 'saves');
+ const slotDir = path.join(savesDir, `slot${slot}`);
+
+ // Save game data
+ const saveFilePath = path.join(slotDir, 'save.json');
+ fs.writeFileSync(saveFilePath, JSON.stringify(saveData, null, 2));
+
+ // Update save info
+ const infoPath = path.join(slotDir, 'saveinfo.json');
+ const saveInfo = {
+ slot: slot,
+ created: new Date().toISOString(),
+ lastSaved: new Date().toISOString(),
+ version: '1.0.0',
+ exists: true,
+ playTime: saveData.gameTime || 0
+ };
+ fs.writeFileSync(infoPath, JSON.stringify(saveInfo, null, 2));
+
+ return { success: true };
+ } catch (error) {
+ console.error('Failed to save game:', error);
+ return { success: false, error: error.message };
+ }
+});
+
+ipcMain.handle('load-game', async (event, slot) => {
+ try {
+ const userDataPath = app.getPath('userData');
+ const savesDir = path.join(userDataPath, 'saves');
+ const slotDir = path.join(savesDir, `slot${slot}`);
+ const saveFilePath = path.join(slotDir, 'save.json');
+
+ if (fs.existsSync(saveFilePath)) {
+ const saveContent = fs.readFileSync(saveFilePath, 'utf8');
+ const saveData = JSON.parse(saveContent);
+ return { success: true, data: saveData };
+ } else {
+ return { success: false, error: 'Save file not found' };
+ }
+ } catch (error) {
+ console.error('Failed to load game:', error);
+ return { success: false, error: error.message };
+ }
+});
+
+ipcMain.handle('get-path', async (event, name) => {
+ try {
+ return app.getPath(name);
+ } catch (error) {
+ return null;
+ }
+});
+
+ipcMain.handle('delete-save-file', async (event, slot) => {
+ console.log('[MAIN PROCESS] delete-save-file called for slot:', slot);
+ try {
+ const userDataPath = app.getPath('userData');
+ const savesDir = path.join(userDataPath, 'saves');
+ const slotDir = path.join(savesDir, `slot${slot}`);
+ const saveFilePath = path.join(slotDir, 'save.json');
+ const infoFilePath = path.join(slotDir, 'saveinfo.json');
+
+ console.log('[MAIN PROCESS] Attempting to delete save files from:', slotDir);
+
+ let deletedFiles = [];
+
+ // Delete save file if it exists
+ if (fs.existsSync(saveFilePath)) {
+ console.log('[MAIN PROCESS] Deleting save file:', saveFilePath);
+ fs.unlinkSync(saveFilePath);
+ deletedFiles.push('save.json');
+ }
+
+ // Delete save info file if it exists
+ if (fs.existsSync(infoFilePath)) {
+ console.log('[MAIN PROCESS] Deleting save info file:', infoFilePath);
+ fs.unlinkSync(infoFilePath);
+ deletedFiles.push('saveinfo.json');
+ }
+
+ // Create empty save info file to indicate slot is empty
+ const saveInfo = {
+ slot: slot,
+ created: new Date().toISOString(),
+ version: '1.0.0',
+ exists: false,
+ deleted: new Date().toISOString()
+ };
+ fs.writeFileSync(infoFilePath, JSON.stringify(saveInfo, null, 2));
+
+ console.log('[MAIN PROCESS] Successfully deleted save files for slot', slot, ':', deletedFiles);
+ return { success: true, deletedFiles };
+ } catch (error) {
+ console.error('[MAIN PROCESS] Failed to delete save file:', error);
+ return { success: false, error: error.message };
+ }
+});
+
+// IPC handlers for window controls
+// Handle logging from renderer process
+ipcMain.on('log-message', async (event, { level, message, data }) => {
+ try {
+ switch (level) {
+ case 'error':
+ await logger.error(message, data);
+ break;
+ case 'warn':
+ await logger.warn(message, data);
+ break;
+ case 'info':
+ await logger.info(message, data);
+ break;
+ case 'debug':
+ await logger.debug(message, data);
+ break;
+ default:
+ await logger.info(message, data);
+ }
+ } catch (error) {
+ console.error('Failed to log message from renderer:', error);
+ // Fallback to console logging to prevent infinite loops
+ console.log(`[${level}] ${message}`, data || '');
+ }
+});
+
+ipcMain.on('minimize-window', () => {
+ if (mainWindow) {
+ mainWindow.minimize();
+ }
+});
+
+ipcMain.on('close-window', () => {
+ if (mainWindow) {
+ mainWindow.close();
+ }
+});
+
+ipcMain.on('toggle-fullscreen', () => {
+ if (mainWindow) {
+ const isFullscreen = mainWindow.isFullScreen();
+ if (isFullscreen) {
+ mainWindow.setFullScreen(false);
+ mainWindow.setSize(1200, 832);
+ mainWindow.center();
+ } else {
+ mainWindow.setFullScreen(true);
+ }
+ }
+});
+
+// This method will be called when Electron has finished initialization
+app.whenReady().then(async () => {
+ console.log('[MAIN PROCESS] Electron app ready, starting initialization...');
+
+ try {
+ // Initialize logger with app data path
+ console.log('[MAIN PROCESS] Initializing logger...');
+ await logger.initialize(app.getPath('userData'));
+ console.log('[MAIN PROCESS] Logger initialized');
+
+ await logger.info('Galaxy Strike Online application starting');
+ console.log('[MAIN PROCESS] Logger info message sent');
+
+ console.log('[MAIN PROCESS] Creating main window...');
+ createWindow();
+
+ app.on('activate', () => {
+ console.log('[MAIN PROCESS] Activate event fired');
+ // On macOS it's common to re-create a window in the app when the dock icon is clicked
+ if (BrowserWindow.getAllWindows().length === 0) {
+ console.log('[MAIN PROCESS] No windows exist, creating new window');
+ createWindow();
+ }
+ });
+
+ console.log('[MAIN PROCESS] App initialization completed successfully');
+
+ } catch (error) {
+ console.error('[MAIN PROCESS] Error during app initialization:', error);
+ console.error('[MAIN PROCESS] Error stack:', error.stack);
+ }
+}).catch((error) => {
+ console.error('[MAIN PROCESS] Error in app.whenReady():', error);
+ console.error('[MAIN PROCESS] Error stack:', error.stack);
+});
+
+// Quit when all windows are closed
+app.on('window-all-closed', () => {
+ // On macOS it's common for applications and their menu bar to stay active
+ if (process.platform !== 'darwin') {
+ logger.info('Application shutting down');
+ app.quit();
+ }
+});
+
+// Handle uncaught exceptions
+process.on('uncaughtException', async (error) => {
+ console.error('[MAIN PROCESS] Uncaught Exception:', error);
+ console.error('[MAIN PROCESS] Uncaught Exception stack:', error.stack);
+
+ try {
+ if (logger && typeof logger.errorEvent === 'function') {
+ await logger.errorEvent(error, 'Uncaught Exception in Main Process');
+ }
+ } catch (logError) {
+ console.error('[MAIN PROCESS] Failed to log uncaught exception:', logError);
+ }
+
+ console.error('[MAIN PROCESS] Application will continue running despite uncaught exception');
+});
+
+// Handle unhandled promise rejections
+process.on('unhandledRejection', (reason, promise) => {
+ console.error('[MAIN PROCESS] Unhandled Promise Rejection at:', promise, 'reason:', reason);
+ console.error('[MAIN PROCESS] Rejection reason stack:', reason.stack);
+});
+
+// Handle unhandled rejections
+process.on('unhandledRejection', async (reason, promise) => {
+ // Avoid logging the logging system's own errors to prevent infinite loops
+ if (reason && reason.message && reason.message.includes('object could not be cloned')) {
+ console.warn('IPC cloning error detected - this is expected during logger initialization');
+ return;
+ }
+
+ await logger.error('Unhandled Rejection', { reason: reason.toString(), promise: promise.toString() });
+ console.error('Unhandled Rejection at:', promise, 'reason:', reason);
+});
+
+// Security: Prevent new window creation
+app.on('web-contents-created', (event, contents) => {
+ contents.on('new-window', (event, navigationUrl) => {
+ event.preventDefault();
+ shell.openExternal(navigationUrl);
+ });
+});
diff --git a/Client-Server/electron-main.js b/Client-Server/electron-main.js
new file mode 100644
index 0000000..3296162
--- /dev/null
+++ b/Client-Server/electron-main.js
@@ -0,0 +1,451 @@
+const { app, BrowserWindow, Menu, shell, ipcMain } = require('electron');
+const path = require('path');
+const fs = require('fs');
+const logger = require('./js/core/Logger');
+
+console.log('[MAIN PROCESS] Electron main process starting...');
+console.log('[MAIN PROCESS] Node.js version:', process.version);
+console.log('[MAIN PROCESS] Electron version:', process.versions.electron);
+console.log('[MAIN PROCESS] Platform:', process.platform);
+console.log('[MAIN PROCESS] Current working directory:', process.cwd());
+
+// Keep a global reference of the window object
+let mainWindow;
+
+function createWindow() {
+ console.log('[MAIN PROCESS] createWindow() called');
+
+ try {
+ console.log('[MAIN PROCESS] Creating BrowserWindow...');
+ // Create the browser window
+ mainWindow = new BrowserWindow({
+ width: 1200,
+ height: 832, // 800 + 32px for custom title bar
+ minWidth: 1200,
+ minHeight: 832,
+ maxWidth: 1200,
+ maxHeight: 832,
+ resizable: false,
+ frame: false,
+ titleBarStyle: 'hidden',
+ webPreferences: {
+ nodeIntegration: true,
+ contextIsolation: false,
+ enableRemoteModule: true,
+ webSecurity: true
+ },
+ icon: path.join(__dirname, 'assets/icon.png'),
+ show: false, // Don't show until ready-to-show
+ title: 'Galaxy Strike Online'
+ });
+
+ console.log('[MAIN PROCESS] BrowserWindow created successfully');
+ console.log('[MAIN PROCESS] Loading index.html...');
+
+ // Load the index.html file
+ mainWindow.loadFile('index.html');
+
+ console.log('[MAIN PROCESS] index.html loaded, setting up electronAPI...');
+
+ // Set up electronAPI after DOM is ready
+ mainWindow.webContents.on('dom-ready', () => {
+ console.log('[MAIN PROCESS] DOM is ready, setting up electronAPI...');
+ mainWindow.webContents.executeJavaScript(`
+ console.log('[RENDERER] Setting up electronAPI...');
+ window.electronAPI = {
+ minimizeWindow: () => require('electron').ipcRenderer.send('minimize-window'),
+ closeWindow: () => require('electron').ipcRenderer.send('close-window'),
+ toggleFullscreen: () => require('electron').ipcRenderer.send('toggle-fullscreen'),
+ log: (level, message, data) => require('electron').ipcRenderer.send('log-message', { level, message, data }),
+ createSaveFolders: (saveSlots) => require('electron').ipcRenderer.invoke('create-save-folders', saveSlots),
+ testFileAccess: (slotPath) => require('electron').ipcRenderer.invoke('test-file-access', slotPath),
+ saveGame: (slot, saveData) => require('electron').ipcRenderer.invoke('save-game', slot, saveData),
+ loadGame: (slot) => require('electron').ipcRenderer.invoke('load-game', slot),
+ getPath: (name) => require('electron').ipcRenderer.invoke('get-path', name),
+ deleteSaveFile: (slot) => require('electron').ipcRenderer.invoke('delete-save-file', slot)
+ };
+ console.log('[RENDERER] electronAPI setup completed');
+ `).then(() => {
+ console.log('[MAIN PROCESS] electronAPI setup completed');
+ }).catch((error) => {
+ console.error('[MAIN PROCESS] Failed to setup electronAPI:', error);
+ });
+ });
+
+ // Show window when ready
+ mainWindow.once('ready-to-show', () => {
+ console.log('[MAIN PROCESS] Window ready-to-show event fired');
+ mainWindow.show();
+ });
+
+ // Open DevTools in development
+ if (process.argv.includes('--dev')) {
+ console.log('[MAIN PROCESS] Opening DevTools...');
+ mainWindow.webContents.openDevTools();
+ }
+
+ // Handle window closed
+ mainWindow.on('closed', () => {
+ console.log('[MAIN PROCESS] Window closed event fired');
+ mainWindow = null;
+ });
+
+ // Handle renderer process crashes
+ mainWindow.webContents.on('render-process-gone', (event, details) => {
+ console.error('[MAIN PROCESS] Renderer process crashed:', details);
+ console.error('[MAIN PROCESS] Crash reason:', details.reason);
+ console.error('[MAIN PROCESS] Exit code:', details.exitCode);
+ });
+
+ // Handle renderer process unresponsive
+ mainWindow.webContents.on('unresponsive', () => {
+ console.warn('[MAIN PROCESS] Renderer process unresponsive');
+ });
+
+ // Handle renderer process responsive again
+ mainWindow.webContents.on('responsive', () => {
+ console.log('[MAIN PROCESS] Renderer process responsive again');
+ });
+
+ // Handle console messages from renderer
+ mainWindow.webContents.on('console-message', (event, level, message, line, sourceId) => {
+ console.log(`[RENDERER CONSOLE] [${level}] ${message} (line: ${line}, source: ${sourceId})`);
+ });
+
+ // Handle page load errors
+ mainWindow.webContents.on('did-fail-load', (event, errorCode, errorDescription, validatedURL, isMainFrame) => {
+ console.error('[MAIN PROCESS] Page failed to load:', errorCode, errorDescription, validatedURL);
+ });
+
+ // Handle page load success
+ mainWindow.webContents.on('did-finish-load', () => {
+ console.log('[MAIN PROCESS] Page finished loading');
+ });
+
+ // Handle DOM ready
+ mainWindow.webContents.on('dom-ready', () => {
+ console.log('[MAIN PROCESS] DOM is ready');
+ });
+
+ // Handle external links
+ mainWindow.webContents.setWindowOpenHandler(({ url }) => {
+ console.log('[MAIN PROCESS] External link requested:', url);
+ shell.openExternal(url);
+ return { action: 'deny' };
+ });
+
+ console.log('[MAIN PROCESS] createWindow() completed successfully');
+
+ } catch (error) {
+ console.error('[MAIN PROCESS] Error in createWindow():', error);
+ console.error('[MAIN PROCESS] Error stack:', error.stack);
+ }
+}
+
+// IPC handlers for save operations
+ipcMain.handle('create-save-folders', async (event, saveSlots) => {
+ console.log('[MAIN PROCESS] create-save-folders called with saveSlots:', saveSlots);
+ try {
+ const userDataPath = app.getPath('userData');
+ console.log('[MAIN PROCESS] userDataPath:', userDataPath);
+ const savesDir = path.join(userDataPath, 'saves');
+ console.log('[MAIN PROCESS] savesDir:', savesDir);
+
+ // Create main saves directory
+ if (!fs.existsSync(savesDir)) {
+ console.log('[MAIN PROCESS] Creating saves directory:', savesDir);
+ fs.mkdirSync(savesDir, { recursive: true });
+ console.log('[MAIN PROCESS] Saves directory created successfully');
+ } else {
+ console.log('[MAIN PROCESS] Saves directory already exists');
+ }
+
+ const paths = {
+ base: savesDir,
+ slots: []
+ };
+
+ // Create save slot directories
+ for (let i = 1; i <= saveSlots; i++) {
+ const slotDir = path.join(savesDir, `slot${i}`);
+ console.log(`[MAIN PROCESS] Checking/creating slot ${i} directory:`, slotDir);
+ if (!fs.existsSync(slotDir)) {
+ console.log(`[MAIN PROCESS] Creating slot ${i} directory`);
+ fs.mkdirSync(slotDir, { recursive: true });
+
+ // Create initial save info file
+ const saveInfo = {
+ slot: i,
+ created: new Date().toISOString(),
+ version: '1.0.0',
+ exists: false
+ };
+
+ const infoPath = path.join(slotDir, 'saveinfo.json');
+ fs.writeFileSync(infoPath, JSON.stringify(saveInfo, null, 2));
+ console.log(`[MAIN PROCESS] Created save info for slot ${i}`);
+ } else {
+ console.log(`[MAIN PROCESS] Slot ${i} directory already exists`);
+ }
+ paths.slots.push(slotDir);
+ }
+
+ console.log('[MAIN PROCESS] Save folders created successfully, returning paths:', paths);
+ return { success: true, paths };
+ } catch (error) {
+ console.error('[MAIN PROCESS] Failed to create save folders:', error);
+ return { success: false, error: error.message };
+ }
+});
+
+ipcMain.handle('test-file-access', async (event, slotPath) => {
+ try {
+ const testFile = path.join(slotPath, 'access_test.txt');
+ fs.writeFileSync(testFile, 'test');
+ fs.unlinkSync(testFile);
+ return { success: true };
+ } catch (error) {
+ return { success: false, error: error.message };
+ }
+});
+
+ipcMain.handle('save-game', async (event, slot, saveData) => {
+ try {
+ const userDataPath = app.getPath('userData');
+ const savesDir = path.join(userDataPath, 'saves');
+ const slotDir = path.join(savesDir, `slot${slot}`);
+
+ // Save game data
+ const saveFilePath = path.join(slotDir, 'save.json');
+ fs.writeFileSync(saveFilePath, JSON.stringify(saveData, null, 2));
+
+ // Update save info
+ const infoPath = path.join(slotDir, 'saveinfo.json');
+ const saveInfo = {
+ slot: slot,
+ created: new Date().toISOString(),
+ lastSaved: new Date().toISOString(),
+ version: '1.0.0',
+ exists: true,
+ playTime: saveData.gameTime || 0
+ };
+ fs.writeFileSync(infoPath, JSON.stringify(saveInfo, null, 2));
+
+ return { success: true };
+ } catch (error) {
+ console.error('Failed to save game:', error);
+ return { success: false, error: error.message };
+ }
+});
+
+ipcMain.handle('load-game', async (event, slot) => {
+ try {
+ const userDataPath = app.getPath('userData');
+ const savesDir = path.join(userDataPath, 'saves');
+ const slotDir = path.join(savesDir, `slot${slot}`);
+ const saveFilePath = path.join(slotDir, 'save.json');
+
+ if (fs.existsSync(saveFilePath)) {
+ const saveContent = fs.readFileSync(saveFilePath, 'utf8');
+ const saveData = JSON.parse(saveContent);
+ return { success: true, data: saveData };
+ } else {
+ return { success: false, error: 'Save file not found' };
+ }
+ } catch (error) {
+ console.error('Failed to load game:', error);
+ return { success: false, error: error.message };
+ }
+});
+
+ipcMain.handle('get-path', async (event, name) => {
+ try {
+ return app.getPath(name);
+ } catch (error) {
+ return null;
+ }
+});
+
+ipcMain.handle('delete-save-file', async (event, slot) => {
+ console.log('[MAIN PROCESS] delete-save-file called for slot:', slot);
+ try {
+ const userDataPath = app.getPath('userData');
+ const savesDir = path.join(userDataPath, 'saves');
+ const slotDir = path.join(savesDir, `slot${slot}`);
+ const saveFilePath = path.join(slotDir, 'save.json');
+ const infoFilePath = path.join(slotDir, 'saveinfo.json');
+
+ console.log('[MAIN PROCESS] Attempting to delete save files from:', slotDir);
+
+ let deletedFiles = [];
+
+ // Delete save file if it exists
+ if (fs.existsSync(saveFilePath)) {
+ console.log('[MAIN PROCESS] Deleting save file:', saveFilePath);
+ fs.unlinkSync(saveFilePath);
+ deletedFiles.push('save.json');
+ }
+
+ // Delete save info file if it exists
+ if (fs.existsSync(infoFilePath)) {
+ console.log('[MAIN PROCESS] Deleting save info file:', infoFilePath);
+ fs.unlinkSync(infoFilePath);
+ deletedFiles.push('saveinfo.json');
+ }
+
+ // Create empty save info file to indicate slot is empty
+ const saveInfo = {
+ slot: slot,
+ created: new Date().toISOString(),
+ version: '1.0.0',
+ exists: false,
+ deleted: new Date().toISOString()
+ };
+ fs.writeFileSync(infoFilePath, JSON.stringify(saveInfo, null, 2));
+
+ console.log('[MAIN PROCESS] Successfully deleted save files for slot', slot, ':', deletedFiles);
+ return { success: true, deletedFiles };
+ } catch (error) {
+ console.error('[MAIN PROCESS] Failed to delete save file:', error);
+ return { success: false, error: error.message };
+ }
+});
+
+// IPC handlers for window controls
+// Handle logging from renderer process
+ipcMain.on('log-message', async (event, { level, message, data }) => {
+ try {
+ switch (level) {
+ case 'error':
+ await logger.error(message, data);
+ break;
+ case 'warn':
+ await logger.warn(message, data);
+ break;
+ case 'info':
+ await logger.info(message, data);
+ break;
+ case 'debug':
+ await logger.debug(message, data);
+ break;
+ default:
+ await logger.info(message, data);
+ }
+ } catch (error) {
+ console.error('Failed to log message from renderer:', error);
+ // Fallback to console logging to prevent infinite loops
+ console.log(`[${level}] ${message}`, data || '');
+ }
+});
+
+ipcMain.on('minimize-window', () => {
+ if (mainWindow) {
+ mainWindow.minimize();
+ }
+});
+
+ipcMain.on('close-window', () => {
+ if (mainWindow) {
+ mainWindow.close();
+ }
+});
+
+ipcMain.on('toggle-fullscreen', () => {
+ if (mainWindow) {
+ const isFullscreen = mainWindow.isFullScreen();
+ if (isFullscreen) {
+ mainWindow.setFullScreen(false);
+ mainWindow.setSize(1200, 832);
+ mainWindow.center();
+ } else {
+ mainWindow.setFullScreen(true);
+ }
+ }
+});
+
+// This method will be called when Electron has finished initialization
+app.whenReady().then(async () => {
+ console.log('[MAIN PROCESS] Electron app ready, starting initialization...');
+
+ try {
+ // Initialize logger with app data path
+ console.log('[MAIN PROCESS] Initializing logger...');
+ await logger.initialize(app.getPath('userData'));
+ console.log('[MAIN PROCESS] Logger initialized');
+
+ await logger.info('Galaxy Strike Online application starting');
+ console.log('[MAIN PROCESS] Logger info message sent');
+
+ console.log('[MAIN PROCESS] Creating main window...');
+ createWindow();
+
+ app.on('activate', () => {
+ console.log('[MAIN PROCESS] Activate event fired');
+ // On macOS it's common to re-create a window in the app when the dock icon is clicked
+ if (BrowserWindow.getAllWindows().length === 0) {
+ console.log('[MAIN PROCESS] No windows exist, creating new window');
+ createWindow();
+ }
+ });
+
+ console.log('[MAIN PROCESS] App initialization completed successfully');
+
+ } catch (error) {
+ console.error('[MAIN PROCESS] Error during app initialization:', error);
+ console.error('[MAIN PROCESS] Error stack:', error.stack);
+ }
+}).catch((error) => {
+ console.error('[MAIN PROCESS] Error in app.whenReady():', error);
+ console.error('[MAIN PROCESS] Error stack:', error.stack);
+});
+
+// Quit when all windows are closed
+app.on('window-all-closed', () => {
+ // On macOS it's common for applications and their menu bar to stay active
+ if (process.platform !== 'darwin') {
+ logger.info('Application shutting down');
+ app.quit();
+ }
+});
+
+// Handle uncaught exceptions
+process.on('uncaughtException', async (error) => {
+ console.error('[MAIN PROCESS] Uncaught Exception:', error);
+ console.error('[MAIN PROCESS] Uncaught Exception stack:', error.stack);
+
+ try {
+ if (logger && typeof logger.errorEvent === 'function') {
+ await logger.errorEvent(error, 'Uncaught Exception in Main Process');
+ }
+ } catch (logError) {
+ console.error('[MAIN PROCESS] Failed to log uncaught exception:', logError);
+ }
+
+ console.error('[MAIN PROCESS] Application will continue running despite uncaught exception');
+});
+
+// Handle unhandled promise rejections
+process.on('unhandledRejection', (reason, promise) => {
+ console.error('[MAIN PROCESS] Unhandled Promise Rejection at:', promise, 'reason:', reason);
+ console.error('[MAIN PROCESS] Rejection reason stack:', reason.stack);
+});
+
+// Handle unhandled rejections
+process.on('unhandledRejection', async (reason, promise) => {
+ // Avoid logging the logging system's own errors to prevent infinite loops
+ if (reason && reason.message && reason.message.includes('object could not be cloned')) {
+ console.warn('IPC cloning error detected - this is expected during logger initialization');
+ return;
+ }
+
+ await logger.error('Unhandled Rejection', { reason: reason.toString(), promise: promise.toString() });
+ console.error('Unhandled Rejection at:', promise, 'reason:', reason);
+});
+
+// Security: Prevent new window creation
+app.on('web-contents-created', (event, contents) => {
+ contents.on('new-window', (event, navigationUrl) => {
+ event.preventDefault();
+ shell.openExternal(navigationUrl);
+ });
+});
diff --git a/Client-Server/index copy.html b/Client-Server/index copy.html
new file mode 100644
index 0000000..51f732f
--- /dev/null
+++ b/Client-Server/index copy.html
@@ -0,0 +1,699 @@
+
+
+
+
+
+ Galaxy Strike Online - Space Idle MMORPG
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Galaxy Strike Online
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
GALAXY STRIKE ONLINE
+
+
Initializing Universe...
+
+
+
+
+
+
+
+
+
+
+
+
+ Dashboard
+
+
+
+ Dungeons
+
+
+
+ Skills
+
+
+
+ Base
+
+
+
+ Quests
+
+
+
+ Inventory
+
+
+
+ Crafting
+
+
+
+ Shop
+
+
+
+
+
+
+
+
+
+
Fleet Status
+
+
+
+
+
Flagship: Starter Cruiser
+
Health: 100%
+
+
+
+
+
+
+
Idle Progress
+
+
Offline Time: 0h 0m
+
Resources Gained: 0
+
Claim Rewards
+
+
+
+ Total Kills
+ 0
+
+
+ Dungeons Cleared
+ 0
+
+
+ Play Time
+ 0h 0m
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Select a dungeon to begin your adventure
+
+
+
+
+
+
+
+
+
+
+ Combat
+ Science
+ Crafting
+
+
+
+
+
+
+
+
+
+
+ Base Overview
+ Base Visualization
+ Ship Gallery
+ Starbases
+
+
+
+
+
+
+
+
Base Information
+
+
+ Power Usage:
+ 0/100
+
+
+ Storage:
+ 1000
+
+
+ Production Rate:
+ 0/s
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Your Ships
+
+
+
+
+
Current Ship
+
+
+
+
+
+
+
Starter Cruiser
+
+
+ Class:
+ Light
+
+
+ Level:
+ 1
+
+
+ Health:
+ 100/100
+
+
+ Attack:
+ 10
+
+
+ Defense:
+ 5
+
+
+ Speed:
+ 15
+
+
+
+
+
+
+
+
+
+
Ships Collected
+
+
+
+
+
+
+
+
+
+
+
+
+
Starbase Management
+
+
+
+
+
+
Available Starbases
+
+
+
+
+
+
+
+
+
+
+ Main Story
+ Daily
+ Weekly
+ Completed
+ Failed Quests
+
+
Daily quests reset in: 00:00:00
+
Weekly quests reset in: 0d 00:00
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Select an item to view details
+
+
+
+
+
+
+
+
+
+
+ Weapons
+ Armor
+ Items
+ Ships
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Initializing...
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Client-Server/index.html b/Client-Server/index.html
new file mode 100644
index 0000000..51f732f
--- /dev/null
+++ b/Client-Server/index.html
@@ -0,0 +1,699 @@
+
+
+
+
+
+ Galaxy Strike Online - Space Idle MMORPG
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Galaxy Strike Online
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
GALAXY STRIKE ONLINE
+
+
Initializing Universe...
+
+
+
+
+
+
+
+
+
+
+
+
+ Dashboard
+
+
+
+ Dungeons
+
+
+
+ Skills
+
+
+
+ Base
+
+
+
+ Quests
+
+
+
+ Inventory
+
+
+
+ Crafting
+
+
+
+ Shop
+
+
+
+
+
+
+
+
+
+
Fleet Status
+
+
+
+
+
Flagship: Starter Cruiser
+
Health: 100%
+
+
+
+
+
+
+
Idle Progress
+
+
Offline Time: 0h 0m
+
Resources Gained: 0
+
Claim Rewards
+
+
+
+ Total Kills
+ 0
+
+
+ Dungeons Cleared
+ 0
+
+
+ Play Time
+ 0h 0m
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Select a dungeon to begin your adventure
+
+
+
+
+
+
+
+
+
+
+ Combat
+ Science
+ Crafting
+
+
+
+
+
+
+
+
+
+
+ Base Overview
+ Base Visualization
+ Ship Gallery
+ Starbases
+
+
+
+
+
+
+
+
Base Information
+
+
+ Power Usage:
+ 0/100
+
+
+ Storage:
+ 1000
+
+
+ Production Rate:
+ 0/s
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Your Ships
+
+
+
+
+
Current Ship
+
+
+
+
+
+
+
Starter Cruiser
+
+
+ Class:
+ Light
+
+
+ Level:
+ 1
+
+
+ Health:
+ 100/100
+
+
+ Attack:
+ 10
+
+
+ Defense:
+ 5
+
+
+ Speed:
+ 15
+
+
+
+
+
+
+
+
+
+
Ships Collected
+
+
+
+
+
+
+
+
+
+
+
+
+
Starbase Management
+
+
+
+
+
+
Available Starbases
+
+
+
+
+
+
+
+
+
+
+ Main Story
+ Daily
+ Weekly
+ Completed
+ Failed Quests
+
+
Daily quests reset in: 00:00:00
+
Weekly quests reset in: 0d 00:00
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Select an item to view details
+
+
+
+
+
+
+
+
+
+
+ Weapons
+ Armor
+ Items
+ Ships
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Initializing...
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Client-Server/js/GameInitializer.js b/Client-Server/js/GameInitializer.js
new file mode 100644
index 0000000..5508e52
--- /dev/null
+++ b/Client-Server/js/GameInitializer.js
@@ -0,0 +1,720 @@
+/**
+ * Game Initializer
+ * Handles initialization of multiplayer game modes
+ */
+
+console.log('[GAME INITIALIZER] GameInitializer.js script loaded');
+
+class GameInitializer {
+ constructor() {
+ console.log('[GAME INITIALIZER] Constructor called');
+ this.gameMode = 'multiplayer';
+ this.serverData = null;
+ this.authToken = null;
+ this.currentUser = null;
+ this.socket = null;
+ this.apiBaseUrl = 'https://api.korvarix.com/api'; // API Server
+ this.gameServerUrl = 'https://dev.gameserver.galaxystrike.online'; // Game Server for Socket.IO (local dev server)
+
+ console.log('[GAME INITIALIZER] Constructor - gameServerUrl set to:', this.gameServerUrl);
+ }
+
+ updateServerUrls(apiUrl, gameUrl) {
+ console.log('[GAME INITIALIZER] Updating server URLs:', { apiUrl: apiUrl, gameUrl: gameUrl });
+ console.log('[GAME INITIALIZER] Previous gameServerUrl:', this.gameServerUrl);
+ this.apiBaseUrl = apiUrl;
+ this.gameServerUrl = gameUrl;
+ console.log('[GAME INITIALIZER] New gameServerUrl:', this.gameServerUrl);
+ }
+
+ initializeMultiplayer(server, serverData, authToken, currentUser) {
+ console.log('[GAME INITIALIZER] Initializing multiplayer game mode');
+ this.gameMode = 'multiplayer';
+ this.serverData = { ...server, ...serverData };
+ this.authToken = authToken;
+ this.currentUser = currentUser;
+
+ // Initialize Socket.IO connection
+ this.initializeSocketConnection();
+
+ // Set SmartSaveManager to multiplayer mode
+ if (window.smartSaveManager) {
+ window.smartSaveManager.setMultiplayerMode(true, this);
+ }
+
+ // Update UI for multiplayer mode
+ this.updateUIForMultiplayerMode();
+
+ console.log('[GAME INITIALIZER] Multiplayer game initialized');
+ }
+
+ initializeSocketConnection() {
+ if (!this.serverData) {
+ console.error('[GAME INITIALIZER] No server data for socket connection');
+ return;
+ }
+
+ console.log('[GAME INITIALIZER] Initializing Socket.IO connection');
+ console.log('[GAME INITIALIZER] Using gameServerUrl:', this.gameServerUrl);
+
+ // Check if we're in local mode and should use mock socket
+ if (this.gameServerUrl.includes('localhost') && window.localServerManager && window.localServerManager.localServer) {
+ console.log('[GAME INITIALIZER] Using mock socket for local mode');
+ this.socket = window.localServerManager.localServer.createMockSocket();
+
+ // Trigger connected event immediately since mock socket auto-connects
+ setTimeout(() => {
+ this.onSocketConnected();
+ }, 200);
+
+ return;
+ }
+
+ // FORCE THE URL - Override any undefined issues
+ const FORCED_URL = 'https://dev.gameserver.galaxystrike.online';
+ console.log('[GAME INITIALIZER] FORCING URL to:', FORCED_URL);
+ console.log('[GAME INITIALIZER] Original this.gameServerUrl:', this.gameServerUrl);
+
+ // Connect to the game server with FORCED URL
+ this.socket = io(FORCED_URL, {
+ auth: {
+ token: this.authToken,
+ serverId: this.serverData.id
+ }
+ });
+
+ console.log('[GAME INITIALIZER] Socket.IO connection initiated to FORCED URL:', FORCED_URL);
+
+ // Socket event handlers
+ this.socket.on('connect', () => {
+ console.log('[GAME INITIALIZER] Connected to server');
+ this.onSocketConnected();
+ });
+
+ this.socket.on('disconnect', () => {
+ console.log('[GAME INITIALIZER] Disconnected from server');
+ this.onSocketDisconnected();
+ });
+
+ this.socket.on('error', (error) => {
+ console.error('[GAME INITIALIZER] Socket error:', error);
+ });
+
+ this.socket.on('force_disconnect', (data) => {
+ console.warn('[GAME INITIALIZER] Force disconnected:', data);
+ this.onForceDisconnect(data);
+ });
+
+ // Game-specific events
+ this.socket.on('playerJoined', (data) => {
+ console.log('[GAME INITIALIZER] Player joined:', data);
+ this.onPlayerJoined(data);
+ });
+
+ this.socket.on('playerLeft', (data) => {
+ console.log('[GAME INITIALIZER] Player left:', data);
+ this.onPlayerLeft(data);
+ });
+
+ this.socket.on('gameUpdate', (data) => {
+ console.log('[GAME INITIALIZER] Game update:', data);
+ this.onGameUpdate(data);
+ });
+
+ this.socket.on('chatMessage', (data) => {
+ console.log('[GAME INITIALIZER] Chat message:', data);
+ this.onChatMessage(data);
+ });
+
+ // Server data events
+ this.socket.on('authenticated', (data) => {
+ console.log('[GAME INITIALIZER] Authentication response:', data);
+ this.onAuthenticated(data);
+ });
+
+ this.socket.on('gameDataLoaded', (data) => {
+ console.log('[GAME INITIALIZER] Game data loaded:', data);
+ this.onGameDataLoaded(data);
+ });
+
+ this.socket.on('updatePlayerStats', (data) => {
+ console.log('[GAME INITIALIZER] Player stats updated on server:', data);
+ this.onGameDataSaved(data);
+ });
+
+ this.socket.on('gameDataSaved', (data) => {
+ console.log('[GAME INITIALIZER] Game data saved:', data);
+ this.onGameDataSaved(data);
+ });
+ }
+
+ onSocketConnected() {
+ // Join the server room
+ this.socket.emit('joinServer', {
+ serverId: this.serverData.id,
+ userId: this.currentUser.userId,
+ username: this.currentUser.username
+ });
+
+ // Authenticate with server to get player data
+ this.authenticateWithServer();
+
+ // Show connected status
+ this.showConnectionStatus('Connected', 'success');
+ }
+
+ onSocketDisconnected() {
+ console.log('[GAME INITIALIZER] Socket disconnected - switching to singleplayer mode');
+
+ // Show disconnected status
+ this.showConnectionStatus('Disconnected', 'error');
+
+ // Switch SmartSaveManager back to singleplayer mode
+ if (window.smartSaveManager) {
+ window.smartSaveManager.setMultiplayerMode(false);
+ console.log('[GAME INITIALIZER] SmartSaveManager set to singleplayer mode');
+ }
+
+ // Clear socket reference
+ this.socket = null;
+ console.log('[GAME INITIALIZER] Socket reference cleared');
+ }
+
+ // Force disconnect from multiplayer server
+ forceDisconnect() {
+ console.log('[GAME INITIALIZER] Force disconnect called');
+
+ if (this.socket) {
+ console.log('[GAME INITIALIZER] Disconnecting socket...');
+ this.socket.disconnect();
+ this.socket = null;
+ }
+
+ // Switch to singleplayer mode
+ if (window.smartSaveManager) {
+ window.smartSaveManager.setMultiplayerMode(false);
+ console.log('[GAME INITIALIZER] Force switched to singleplayer mode');
+ }
+
+ this.showConnectionStatus('Disconnected', 'error');
+ console.log('[GAME INITIALIZER] Force disconnect completed');
+ }
+
+ onPlayerJoined(data) {
+ // Handle player joining
+ this.updatePlayerList();
+ this.showNotification(`${data.username} joined the server`, 'info');
+ }
+
+ onPlayerLeft(data) {
+ // Handle player leaving
+ this.updatePlayerList();
+ this.showNotification(`${data.username} left the server`, 'info');
+ }
+
+ onGameUpdate(data) {
+ // Handle game state updates
+ if (window.game && window.game.handleServerUpdate) {
+ window.game.handleServerUpdate(data);
+ }
+ }
+
+ onChatMessage(data) {
+ // Handle chat messages
+ if (window.game && window.game.handleChatMessage) {
+ window.game.handleChatMessage(data);
+ }
+ }
+
+ onAuthenticated(data) {
+ console.log('[GAME INITIALIZER] Authentication successful:', data);
+
+ if (data.success && data.playerData) {
+ // Store server player data from authentication (this is our primary source)
+ this.serverPlayerData = data.playerData;
+ console.log('[GAME INITIALIZER] Using authentication data as primary source:', this.serverPlayerData);
+
+ // NOW create GameEngine AFTER authentication is successful
+ if (!window.game) {
+ console.log('[GAME INITIALIZER] Creating GameEngine after successful authentication');
+ this.createGameEngineForMultiplayer();
+ }
+
+ // Set SmartSaveManager to multiplayer mode
+ if (window.smartSaveManager) {
+ window.smartSaveManager.setMultiplayerMode(true, this);
+ console.log('[GAME INITIALIZER] SmartSaveManager set to multiplayer mode');
+
+ // Apply authentication data immediately
+ window.smartSaveManager.applyServerDataToGame(data.playerData);
+ }
+
+ // Fallback: Apply authentication data to game if game is running
+ if (window.game && window.game.loadServerPlayerData) {
+ console.log('[GAME INITIALIZER] Applying authentication data to GameEngine:', data.playerData);
+ window.game.loadServerPlayerData(data.playerData);
+ console.log('[GAME INITIALIZER] Authentication data applied to GameEngine');
+
+ // Force UI refresh when authentication data is applied
+ if (window.game.systems && window.game.systems.ui && window.game.systems.ui.forceRefreshAllUI) {
+ console.log('[GAME INITIALIZER] Forcing UI refresh after authentication data application');
+ window.game.systems.ui.forceRefreshAllUI();
+ }
+ }
+
+ this.showNotification(`Welcome back! Level ${data.playerData.stats?.level || 1}`, 'success');
+ } else {
+ this.showNotification(data.error || 'Authentication failed', 'error');
+ }
+ }
+
+ createGameEngineForMultiplayer() {
+ console.log('[GAME INITIALIZER] Creating GameEngine for multiplayer mode');
+ console.log('[GAME INITIALIZER] Server player data available:', !!this.serverPlayerData);
+
+ try {
+ // Create GameEngine instance
+ window.game = new GameEngine();
+
+ // Initialize the game engine
+ console.log('[GAME INITIALIZER] About to call window.game.init()');
+ const initPromise = window.game.init();
+ console.log('[GAME INITIALIZER] GameEngine.init() returned:', typeof initPromise, initPromise);
+
+ initPromise.then(() => {
+ console.log('[GAME INITIALIZER] GameEngine initialized successfully for multiplayer');
+
+ // Apply server data immediately after initialization
+ if (this.serverPlayerData && window.game.loadServerPlayerData) {
+ console.log('[GAME INITIALIZER] Applying server player data to GameEngine:', this.serverPlayerData);
+ window.game.loadServerPlayerData(this.serverPlayerData);
+ console.log('[GAME INITIALIZER] Server player data applied to GameEngine');
+
+ // Force UI refresh
+ if (window.game.systems && window.game.systems.ui && window.game.systems.ui.forceRefreshAllUI) {
+ console.log('[GAME INITIALIZER] Forcing UI refresh after data application');
+ window.game.systems.ui.forceRefreshAllUI();
+ }
+ } else {
+ console.warn('[GAME INITIALIZER] No server player data or loadServerPlayerData method available');
+ }
+
+ // Start the game
+ if (window.game.start) {
+ window.game.start();
+ }
+
+ }).catch((error) => {
+ console.error('[GAME INITIALIZER] GameEngine init failed:', error);
+ console.error('[GAME INITIALIZER] Error details:', error.stack);
+ this.showNotification('Failed to initialize game engine', 'error');
+ });
+
+ } catch (error) {
+ console.error('[GAME INITIALIZER] Error creating GameEngine:', error);
+ this.showNotification('Error creating game engine', 'error');
+ }
+ }
+
+ onGameDataLoaded(data) {
+ console.log('[GAME INITIALIZER] Server game data loaded:', data);
+ console.log('[GAME INITIALIZER] Data success:', data.success);
+ console.log('[GAME INITIALIZER] Data content:', data.data);
+ console.log('[GAME INITIALIZER] Data keys:', data.data ? Object.keys(data.data) : 'No data object');
+
+ if (data.success && data.data && Object.keys(data.data).length > 0) {
+ // Store server player data
+ this.serverPlayerData = data.data;
+
+ console.log('[GAME INITIALIZER] Applying server data to game systems');
+ // Apply server data to game if game is running
+ if (window.game && window.game.loadServerPlayerData) {
+ window.game.loadServerPlayerData(data.data);
+
+ // Force UI refresh when server data is applied
+ if (window.game.systems && window.game.systems.ui && window.game.systems.ui.forceRefreshAllUI) {
+ window.game.systems.ui.forceRefreshAllUI();
+ }
+ } else {
+ console.warn('[GAME INITIALIZER] No game or loadServerPlayerData method available');
+ }
+ } else {
+ console.log('[GAME INITIALIZER] Ignoring empty game data - no player data to load');
+ console.log('[GAME INITIALIZER] Data analysis:', {
+ success: data.success,
+ hasData: !!data.data,
+ dataKeys: data.data ? Object.keys(data.data) : [],
+ dataLength: data.data ? JSON.stringify(data.data).length : 0
+ });
+ }
+ }
+
+ onGameDataSaved(data) {
+ console.log('[GAME INITIALIZER] Server game data saved:', data);
+
+ if (data.success) {
+ this.showNotification('Game saved to server!', 'success');
+ } else {
+ this.showNotification(data.error || 'Failed to save to server', 'error');
+ }
+ }
+
+ onForceDisconnect(data) {
+ // Handle forced disconnection from server
+ console.warn('[GAME INITIALIZER] Force disconnected by server:', data);
+
+ // Show notification to user
+ if (window.game && window.game.showNotification) {
+ window.game.showNotification(
+ `Disconnected: ${data.reason}`,
+ 'warning',
+ 10000
+ );
+ }
+
+ // Disconnect the socket
+ if (this.socket) {
+ this.socket.disconnect();
+ }
+
+ // Clean up multiplayer mode
+ if (window.game) {
+ window.game.setMultiplayerMode(false);
+ }
+
+ // Return to main menu after a delay
+ setTimeout(() => {
+ if (window.liveMainMenu) {
+ window.liveMainMenu.showLoginSection();
+ }
+ }, 2000);
+ }
+
+ initializeGameSystems() {
+ console.log('[GAME INITIALIZER] Initializing game systems');
+
+ // Wait for the main game script to be ready
+ if (typeof window.game !== 'undefined') {
+ console.log('[GAME INITIALIZER] window.game is available, calling setupGameSystems');
+ this.setupGameSystems();
+ } else {
+ console.log('[GAME INITIALIZER] window.game not available, waiting 100ms');
+ // Wait for the game to be initialized
+ setTimeout(() => this.initializeGameSystems(), 100);
+ }
+ }
+
+ setupGameSystems() {
+ if (!window.game) {
+ console.error('[GAME INITIALIZER] Game not available');
+ return;
+ }
+
+ console.log('[GAME INITIALIZER] Setting up game systems for multiplayer mode');
+
+ // Configure game for multiplayer mode
+ console.log('[GAME INITIALIZER] Configuring for multiplayer mode');
+ window.game.setMultiplayerMode(true, this.socket, this.serverData, this.currentUser);
+ window.game.gameInitializer = this; // Store reference for server polling
+
+ // DISABLE game logic in multiplayer - server is authoritative
+ if (window.game) {
+ console.log('[GAME INITIALIZER] Disabling client game logic - server is authoritative');
+
+ // Override game logic methods to do nothing in multiplayer
+ const originalUpdateGameLogic = window.game.updateGameLogic;
+ window.game.updateGameLogic = function() {
+ // In multiplayer mode, client does NO game logic
+ // Server is authoritative for ALL game data including credits
+ // Client is display-only
+ };
+
+ const originalStart = window.game.start;
+ window.game.start = function() {
+ console.log('[GAME ENGINE] Multiplayer mode - client does not run game logic, server is authoritative');
+ console.log('[GAME ENGINE] GameInitializer reference:', !!this.gameInitializer);
+ console.log('[GAME ENGINE] Socket reference:', !!(this.gameInitializer && this.gameInitializer.socket));
+ console.log('[GAME ENGINE] Game mode:', this.gameInitializer ? this.gameInitializer.gameMode : 'no gameInitializer');
+
+ this.isRunning = true;
+
+ // NO game logic timer - client is display-only
+ // Server handles all game logic including credit generation
+
+ // Start server data polling for UI updates
+ if (this.gameInitializer && this.gameInitializer.socket) {
+ console.log('[GAME ENGINE] Starting server data polling for UI updates');
+ this.serverPollTimer = setInterval(() => {
+ console.log('[GAME ENGINE] Polling server for data...');
+ this.gameInitializer.loadGameDataFromServer();
+ }, 5000); // Request updates every 5 seconds
+ } else {
+ console.warn('[GAME ENGINE] Cannot start server polling - no gameInitializer or socket');
+ }
+
+ // Only start UI updates that use server data (every second)
+ if (this.systems.ui) {
+ console.log('[GAME ENGINE] Starting multiplayer UI updates');
+ this.uiUpdateTimer = setInterval(() => {
+ if (this.systems && this.systems.ui && this.systems.ui.updateUI) {
+ console.log('[GAME ENGINE] Updating multiplayer UI with server data');
+ this.systems.ui.updateUI();
+ }
+ }, 1000);
+ }
+ };
+ }
+
+ // Game is already set up with save data, just start the game loop
+ if (window.game.start) {
+ // console.log('[GAME INITIALIZER] Calling start() to begin game loop');
+ window.game.start();
+ } else if (window.game.startGame) {
+ // console.log('[GAME INITIALIZER] Calling startGame(false) - save data already applied');
+ window.game.startGame(false); // false = don't load again (save data already applied)
+ } else {
+ console.error('[GAME INITIALIZER] No start method available on window.game');
+ }
+
+ console.log('[GAME INITIALIZER] Game systems configured');
+ }
+
+ updateUIForMultiplayerMode() {
+ // Update UI elements to show multiplayer mode
+ const playerName = document.getElementById('playerName');
+ if (playerName && this.currentUser) {
+ playerName.textContent = this.currentUser.username;
+ }
+
+ // Show multiplayer-specific UI elements
+ this.showMultiplayerUI();
+
+ // Show server info
+ this.showServerInfo();
+ }
+
+ hideMultiplayerUI() {
+ // Hide elements that are only relevant in multiplayer
+ const chatContainer = document.getElementById('chatContainer');
+ if (chatContainer) {
+ chatContainer.classList.add('hidden');
+ }
+
+ const playerList = document.getElementById('playerList');
+ if (playerList) {
+ playerList.classList.add('hidden');
+ }
+ }
+
+ showMultiplayerUI() {
+ // Show multiplayer-specific elements
+ const chatContainer = document.getElementById('chatContainer');
+ if (chatContainer) {
+ chatContainer.classList.remove('hidden');
+ }
+
+ const playerList = document.getElementById('playerList');
+ if (playerList) {
+ playerList.classList.remove('hidden');
+ }
+ }
+
+ showServerInfo() {
+ // Add server information to the UI
+ const header = document.querySelector('.game-header');
+ if (header && !header.querySelector('.server-info')) {
+ const serverInfo = document.createElement('div');
+ serverInfo.className = 'server-info';
+ serverInfo.innerHTML = `
+
+ ${this.serverData.name} (${this.serverData.currentPlayers}/${this.serverData.maxPlayers})
+ `;
+ serverInfo.style.cssText = `
+ background: rgba(0, 212, 255, 0.2);
+ color: #00d4ff;
+ padding: 4px 8px;
+ border-radius: 4px;
+ font-size: 0.8rem;
+ margin-left: 10px;
+ `;
+ header.appendChild(serverInfo);
+ }
+ }
+
+ showConnectionStatus(status, type) {
+ // Show connection status in the UI
+ const statusElement = document.getElementById('connectionStatus');
+ if (statusElement) {
+ statusElement.textContent = status;
+ statusElement.className = `connection-status ${type}`;
+ }
+ }
+
+ updatePlayerList() {
+ // Update the player list UI
+ if (this.socket && this.serverData) {
+ // Request updated player list from server
+ this.socket.emit('getPlayerList', { serverId: this.serverData.id });
+ }
+ }
+
+ showNotification(message, type = 'info') {
+ // Show a notification to the user
+ if (window.game && window.game.showNotification) {
+ window.game.showNotification(message, type, 3000);
+ } else {
+ // Fallback to alert
+ console.log(`[GAME INITIALIZER] Notification: ${message}`);
+ }
+ }
+
+ // Method to send actions to the server
+ sendGameAction(actionType, actionData) {
+ if (this.socket && this.gameMode === 'multiplayer') {
+ this.socket.emit('gameAction', {
+ type: actionType,
+ data: actionData,
+ userId: this.currentUser.userId,
+ serverId: this.serverData.id
+ });
+ }
+ }
+
+ // Method to send chat messages
+ sendChatMessage(message) {
+ if (this.socket && this.gameMode === 'multiplayer') {
+ this.socket.emit('chatMessage', {
+ message: message,
+ userId: this.currentUser.userId,
+ username: this.currentUser.username,
+ serverId: this.serverData.id
+ });
+ }
+ }
+
+ // Method to save game data to server (SECURE: only send specific updates)
+ saveGameDataToServer(gameData) {
+ if (this.socket && this.gameMode === 'multiplayer') {
+ console.log('[GAME INITIALIZER] Sending secure game updates to server');
+
+ // Only send specific, validated updates - not entire game state
+ const secureUpdates = {
+ playerStats: {
+ credits: gameData.player?.credits || 0,
+ level: gameData.player?.level || 1,
+ experience: gameData.player?.experience || 0,
+ playTime: gameData.player?.playTime || 0
+ },
+ timestamp: Date.now()
+ };
+
+ // Send only the specific updates for server validation
+ this.socket.emit('updatePlayerStats', secureUpdates);
+ }
+ }
+
+ // Method to load game data from server
+ loadGameDataFromServer() {
+ if (this.socket && this.gameMode === 'multiplayer') {
+ console.log('[GAME INITIALIZER] Loading game data from server');
+ console.log('[GAME INITIALIZER] Socket available:', !!this.socket);
+ console.log('[GAME INITIALIZER] Game mode:', this.gameMode);
+ console.log('[GAME INITIALIZER] Current user:', this.currentUser);
+ console.log('[GAME INITIALIZER] Emitting loadGameData event with username');
+
+ // Get username from current user or fallback to stored user
+ let username = 'anonymous'; // default fallback
+ if (this.currentUser?.username) {
+ username = this.currentUser.username;
+ } else {
+ // Try to get from localStorage as fallback
+ const storedUser = localStorage.getItem('currentUser');
+ if (storedUser) {
+ try {
+ const user = JSON.parse(storedUser);
+ username = user.username || 'anonymous';
+ } catch (e) {
+ console.warn('[GAME INITIALIZER] Failed to parse stored user, using default');
+ }
+ }
+ }
+
+ console.log('[GAME INITIALIZER] Using username for data load:', username);
+
+ // Send the username to load the correct player data
+ this.socket.emit('loadGameData', {
+ username: username
+ });
+ } else {
+ console.log('[GAME INITIALIZER] Cannot load game data - socket or multiplayer mode not available');
+ console.log('[GAME INITIALIZER] Socket available:', !!this.socket);
+ console.log('[GAME INITIALIZER] Game mode:', this.gameMode);
+ }
+ }
+
+ // Method to authenticate with server
+ authenticateWithServer() {
+ console.log('[GAME INITIALIZER] authenticateWithServer called');
+ console.log('[GAME INITIALIZER] Socket available:', !!this.socket);
+ console.log('[GAME INITIALIZER] Game mode:', this.gameMode);
+ console.log('[GAME INITIALIZER] Current user:', this.currentUser);
+
+ if (this.socket && this.currentUser) {
+ console.log('[GAME INITIALIZER] Sending authentication to server');
+ this.socket.emit('authenticate', {
+ userId: this.currentUser.userId,
+ username: this.currentUser.username
+ });
+ } else {
+ console.warn('[GAME INITIALIZER] Cannot authenticate - missing socket or user data');
+ if (!this.socket) {
+ console.warn('[GAME INITIALIZER] Socket is null/undefined');
+ }
+ if (!this.currentUser) {
+ console.warn('[GAME INITIALIZER] Current user is null/undefined');
+ }
+ }
+ }
+
+ // Cleanup method
+ cleanup() {
+ console.log('[GAME INITIALIZER] Cleaning up');
+
+ // Reset SmartSaveManager to singleplayer mode
+ if (window.smartSaveManager) {
+ window.smartSaveManager.setMultiplayerMode(false);
+ }
+
+ if (this.socket) {
+ this.socket.disconnect();
+ this.socket = null;
+ }
+
+ this.gameMode = null;
+ this.serverData = null;
+ this.authToken = null;
+ this.currentUser = null;
+ this.serverPlayerData = null;
+ }
+}
+
+// Create global instance
+window.gameInitializer = new GameInitializer();
+
+// Make force disconnect available globally for testing
+window.forceDisconnectMultiplayer = function() {
+ if (window.gameInitializer && window.gameInitializer.forceDisconnect) {
+ window.gameInitializer.forceDisconnect();
+ } else {
+ console.log('[GAME INITIALIZER] GameInitializer not available for force disconnect');
+ }
+};
+
+// Debug: Log the global instance immediately
+console.log('[GAME INITIALIZER] Global instance created:', window.gameInitializer);
+console.log('[GAME INITIALIZER] Global instance gameServerUrl:', window.gameInitializer.gameServerUrl);
+
+// Export for use in other scripts
+if (typeof module !== 'undefined' && module.exports) {
+ module.exports = GameInitializer;
+}
diff --git a/Client/js/LocalServer.js b/Client-Server/js/LocalServer.js
similarity index 100%
rename from Client/js/LocalServer.js
rename to Client-Server/js/LocalServer.js
diff --git a/Client/js/LocalServerManager.js b/Client-Server/js/LocalServerManager.js
similarity index 100%
rename from Client/js/LocalServerManager.js
rename to Client-Server/js/LocalServerManager.js
diff --git a/Client-Server/js/SaveSystemIntegration.js b/Client-Server/js/SaveSystemIntegration.js
new file mode 100644
index 0000000..91f5e77
--- /dev/null
+++ b/Client-Server/js/SaveSystemIntegration.js
@@ -0,0 +1,350 @@
+/**
+ * Save System Integration
+ * Integrates SmartSaveManager with existing game systems
+ */
+
+console.log('[SAVE INTEGRATION] Save system integration loading');
+
+// Override the game's save method to use SmartSaveManager
+function integrateWithGameEngine() {
+ if (window.game && window.game.save) {
+ // Store original save method
+ const originalSave = window.game.save;
+
+ // Override save method
+ window.game.save = async function() {
+ console.log('[SAVE INTEGRATION] Game save called');
+
+ // In multiplayer mode, don't save - server is authoritative
+ if (window.smartSaveManager && window.smartSaveManager.isMultiplayer) {
+ console.log('[SAVE INTEGRATION] Multiplayer mode - client save disabled, server is authoritative');
+ this.showNotification('Server manages your game data', 'info', 2000);
+ return true;
+ }
+
+ try {
+ // Get current game data
+ const gameData = this.getSaveData ? this.getSaveData() : {};
+
+ // Use SmartSaveManager for singleplayer
+ if (window.smartSaveManager) {
+ const success = await window.smartSaveManager.savePlayerData(gameData);
+
+ if (success) {
+ this.showNotification('Game saved!', 'success', 2000);
+ } else {
+ this.showNotification('Save failed!', 'error', 3000);
+ }
+
+ return success;
+ } else {
+ // Fallback to original save
+ return await originalSave.call(this);
+ }
+ } catch (error) {
+ console.error('[SAVE INTEGRATION] Save error:', error);
+ this.showNotification('Save error!', 'error', 3000);
+ return false;
+ }
+ };
+
+ console.log('[SAVE INTEGRATION] Game save method overridden');
+ }
+}
+
+// Override the game's load method to use SmartSaveManager
+function integrateLoadSystem() {
+ if (window.game && window.game.load) {
+ // Store original load method
+ const originalLoad = window.game.load;
+
+ // Override load method
+ window.game.load = async function(saveData = null) {
+ console.log('[SAVE INTEGRATION] Game load called, using SmartSaveManager');
+
+ try {
+ let dataToLoad = saveData;
+
+ // If no data provided, use SmartSaveManager
+ if (!dataToLoad && window.smartSaveManager) {
+ dataToLoad = await window.smartSaveManager.loadPlayerData();
+ }
+
+ // Load the data
+ if (dataToLoad) {
+ if (this.loadPlayerData) {
+ this.loadPlayerData(dataToLoad);
+ } else {
+ // Fallback to original load
+ return await originalLoad.call(this, dataToLoad);
+ }
+
+ console.log('[SAVE INTEGRATION] Game data loaded successfully');
+ return true;
+ } else {
+ console.log('[SAVE INTEGRATION] No save data found, starting fresh');
+ return false;
+ }
+ } catch (error) {
+ console.error('[SAVE INTEGRATION] Load error:', error);
+ return false;
+ }
+ };
+
+ console.log('[SAVE INTEGRATION] Game load method overridden');
+ }
+}
+
+// Add server data loading method to game
+function addServerDataSupport() {
+ if (window.game) {
+ // Store pending server data for later application
+ window.game.pendingServerData = null;
+
+ window.game.loadServerPlayerData = function(serverData) {
+ console.log('[SAVE INTEGRATION] Loading server player data into game');
+ console.log('[SAVE INTEGRATION] Server data received:', serverData);
+ console.log('[SAVE INTEGRATION] Server data type:', typeof serverData);
+ console.log('[SAVE INTEGRATION] Server data keys:', serverData ? Object.keys(serverData) : 'No data');
+ console.log('[SAVE INTEGRATION] Game systems available:', this.systems ? Object.keys(this.systems) : 'No systems');
+
+ // Store server data for later if systems aren't ready
+ if (!this.systems || Object.keys(this.systems).length === 0) {
+ console.log('[SAVE INTEGRATION] Game systems not ready, storing data for later');
+ this.pendingServerData = serverData;
+ return;
+ }
+
+ console.log('[SAVE INTEGRATION] Game systems ready, applying server data now');
+
+ try {
+ // Apply player stats
+ if (serverData.stats && this.systems && this.systems.player) {
+ console.log('[SAVE INTEGRATION] Applying player stats:', serverData.stats);
+ console.log('[SAVE INTEGRATION] Player system methods:', Object.getOwnPropertyNames(Object.getPrototypeOf(this.systems.player)));
+
+ // Check if load method exists
+ if (typeof this.systems.player.load === 'function') {
+ this.systems.player.load(serverData.stats);
+ console.log('[SAVE INTEGRATION] Player stats applied successfully');
+ console.log('[SAVE INTEGRATION] Updated player stats:', this.systems.player.stats);
+ } else {
+ console.warn('[SAVE INTEGRATION] Player system has no load method, trying direct assignment');
+ // Direct assignment as fallback
+ if (this.systems.player.stats) {
+ Object.assign(this.systems.player.stats, serverData.stats);
+ console.log('[SAVE INTEGRATION] Player stats assigned directly:', this.systems.player.stats);
+ }
+ }
+ } else {
+ console.warn('[SAVE INTEGRATION] No player system or stats in server data');
+ console.log('[SAVE INTEGRATION] Has serverData.stats:', !!serverData?.stats);
+ console.log('[SAVE INTEGRATION] Has systems.player:', !!(this.systems?.player));
+ }
+
+ // Apply inventory
+ if (serverData.inventory && this.systems && this.systems.inventory) {
+ console.log('[SAVE INTEGRATION] Applying player inventory:', serverData.inventory);
+ if (typeof this.systems.inventory.load === 'function') {
+ this.systems.inventory.load(serverData.inventory);
+ } else {
+ console.warn('[SAVE INTEGRATION] Inventory system has no load method');
+ }
+ }
+
+ // Apply ship data
+ if (serverData.ship && this.systems && this.systems.ship) {
+ console.log('[SAVE INTEGRATION] Applying player ship:', serverData.ship);
+ if (typeof this.systems.ship.load === 'function') {
+ this.systems.ship.load(serverData.ship);
+ } else {
+ console.warn('[SAVE INTEGRATION] Ship system has no load method');
+ }
+ }
+
+ // Apply base data
+ if (serverData.base && this.systems && this.systems.base) {
+ console.log('[SAVE INTEGRATION] Applying player base:', serverData.base);
+ if (typeof this.systems.base.load === 'function') {
+ this.systems.base.load(serverData.base);
+ } else {
+ console.warn('[SAVE INTEGRATION] Base system has no load method');
+ }
+ }
+
+ // Show notification
+ if (this.showNotification) {
+ this.showNotification(`Welcome back! Level ${serverData.stats?.level || 1}`, 'success', 3000);
+ }
+
+ console.log('[SAVE INTEGRATION] Server player data application completed');
+
+ // Force UI update
+ if (this.systems && this.systems.ui && this.systems.ui.updateUI) {
+ this.systems.ui.updateUI();
+ console.log('[SAVE INTEGRATION] Server player data application completed');
+ }
+
+ // Apply pending server data if any exists
+ if (this.pendingServerData) {
+ console.log('[SAVE INTEGRATION] Applying pending server data');
+ this.loadServerPlayerData(this.pendingServerData);
+ this.pendingServerData = null;
+ }
+ } catch (error) {
+ console.error('[SAVE INTEGRATION] Error applying server player data:', error);
+ if (this.showNotification) {
+ this.showNotification('Failed to load server data!', 'error', 3000);
+ }
+ }
+ };
+
+ // Method to check and apply pending server data
+ window.game.checkAndApplyPendingServerData = function() {
+ if (this.pendingServerData && this.systems && Object.keys(this.systems).length > 0) {
+ console.log('[SAVE INTEGRATION] Systems ready, applying pending server data');
+ this.loadServerPlayerData(this.pendingServerData);
+ this.pendingServerData = null;
+ }
+ };
+
+ // Fallback loadPlayerData method if GameEngine doesn't have it
+ if (!window.game.loadPlayerData) {
+ window.game.loadPlayerData = window.game.loadServerPlayerData;
+ }
+
+ console.log('[SAVE INTEGRATION] Server data support added to game');
+ }
+}
+
+// Add save mode switching to UI
+function addSaveModeUI() {
+ // Add save mode indicator to UI
+ const createSaveModeIndicator = () => {
+ const indicator = document.createElement('div');
+ indicator.id = 'save-mode-indicator';
+ indicator.style.cssText = `
+ position: fixed;
+ top: 10px;
+ right: 10px;
+ background: rgba(0, 0, 0, 0.8);
+ color: white;
+ padding: 8px 12px;
+ border-radius: 4px;
+ font-size: 12px;
+ z-index: 10000;
+ display: none;
+ `;
+ document.body.appendChild(indicator);
+ return indicator;
+ };
+
+ const updateSaveModeIndicator = () => {
+ const indicator = document.getElementById('save-mode-indicator') || createSaveModeIndicator();
+
+ if (window.smartSaveManager) {
+ const info = window.smartSaveManager.getSaveInfo();
+ indicator.textContent = `Save: ${info.saveLocation}`;
+ indicator.style.display = 'block';
+
+ // Color code based on mode
+ indicator.style.background = info.isMultiplayer ? 'rgba(0, 100, 200, 0.8)' : 'rgba(0, 150, 0, 0.8)';
+ }
+ };
+
+ // Update indicator when mode changes
+ if (window.smartSaveManager) {
+ const originalSetMultiplayerMode = window.smartSaveManager.setMultiplayerMode;
+ window.smartSaveManager.setMultiplayerMode = function(...args) {
+ originalSetMultiplayerMode.apply(this, args);
+ updateSaveModeIndicator();
+ };
+ }
+
+ // Initial update
+ setTimeout(updateSaveModeIndicator, 1000);
+}
+
+// Debug function to check data flow
+function debugDataFlow() {
+ console.log('[DEBUG] === DATA FLOW DEBUG ===');
+
+ // Check GameInitializer
+ if (window.gameInitializer) {
+ console.log('[DEBUG] GameInitializer exists:', !!window.gameInitializer);
+ console.log('[DEBUG] GameInitializer serverPlayerData:', window.gameInitializer.serverPlayerData);
+ console.log('[DEBUG] GameInitializer gameMode:', window.gameInitializer.gameMode);
+ } else {
+ console.log('[DEBUG] GameInitializer NOT found');
+ }
+
+ // Check game systems
+ if (window.game) {
+ console.log('[DEBUG] Game exists:', !!window.game);
+ console.log('[DEBUG] Game systems:', window.game.systems ? Object.keys(window.game.systems) : 'No systems');
+
+ if (window.game.systems && window.game.systems.player) {
+ console.log('[DEBUG] Player system exists:', !!window.game.systems.player);
+ console.log('[DEBUG] Player stats:', window.game.systems.player.stats);
+ console.log('[DEBUG] Player credits:', window.game.systems.player.stats?.credits);
+ }
+ } else {
+ console.log('[DEBUG] Game NOT found');
+ }
+
+ // Check SmartSaveManager
+ if (window.smartSaveManager) {
+ console.log('[DEBUG] SmartSaveManager exists:', !!window.smartSaveManager);
+ console.log('[DEBUG] SmartSaveManager mode:', window.smartSaveManager.isMultiplayer ? 'multiplayer' : 'singleplayer');
+ } else {
+ console.log('[DEBUG] SmartSaveManager NOT found');
+ }
+
+ console.log('[DEBUG] === END DEBUG ===');
+}
+
+// Debug function available for manual testing
+window.debugDataFlow = debugDataFlow;
+
+// Enhanced debug function for connection testing
+window.debugConnectionState = function() {
+ console.log('=== CONNECTION STATE DEBUG ===');
+ console.log('GameInitializer exists:', !!window.gameInitializer);
+ console.log('GameInitializer socket connected:', !!window.gameInitializer?.socket?.connected);
+ console.log('GameInitializer gameMode:', window.gameInitializer?.gameMode);
+ console.log('GameInitializer serverPlayerData:', !!window.gameInitializer?.serverPlayerData);
+ console.log('SmartSaveManager exists:', !!window.smartSaveManager);
+ console.log('SmartSaveManager mode:', window.smartSaveManager?.isMultiplayer ? 'multiplayer' : 'singleplayer');
+ console.log('Game exists:', !!window.game);
+ console.log('Game isRunning:', window.game?.isRunning);
+ console.log('=== END CONNECTION DEBUG ===');
+};
+
+// Initialize integration when DOM is ready
+function initializeIntegration() {
+ console.log('[SAVE INTEGRATION] Initializing save system integration');
+
+ // Wait for game to be ready
+ const checkGameReady = () => {
+ if (window.game) {
+ integrateWithGameEngine();
+ integrateLoadSystem();
+ addServerDataSupport();
+ addSaveModeUI();
+ console.log('[SAVE INTEGRATION] Integration complete');
+ } else {
+ setTimeout(checkGameReady, 500);
+ }
+ };
+
+ checkGameReady();
+}
+
+// Auto-initialize
+if (document.readyState === 'loading') {
+ document.addEventListener('DOMContentLoaded', initializeIntegration);
+} else {
+ initializeIntegration();
+}
+
+console.log('[SAVE INTEGRATION] Save system integration loaded');
diff --git a/Client/js/SimpleLocalServer.js b/Client-Server/js/SimpleLocalServer.js
similarity index 100%
rename from Client/js/SimpleLocalServer.js
rename to Client-Server/js/SimpleLocalServer.js
diff --git a/Client-Server/js/SmartSaveManager.js b/Client-Server/js/SmartSaveManager.js
new file mode 100644
index 0000000..36bb306
--- /dev/null
+++ b/Client-Server/js/SmartSaveManager.js
@@ -0,0 +1,183 @@
+/**
+ * Smart Save Manager
+ * Intelligently handles save data for both singleplayer and multiplayer modes
+ */
+
+class SmartSaveManager {
+ constructor() {
+ this.isMultiplayer = false;
+ this.serverPlayerData = null;
+ this.localSaveData = null;
+ this.gameInitializer = null;
+
+ console.log('[SMART SAVE] SmartSaveManager initialized');
+ }
+
+ setMultiplayerMode(isMultiplayer, gameInitializer = null) {
+ this.isMultiplayer = isMultiplayer;
+ this.gameInitializer = gameInitializer;
+
+ console.log(`[SMART SAVE] Set to ${isMultiplayer ? 'multiplayer' : 'singleplayer'} mode`);
+
+ if (isMultiplayer && gameInitializer) {
+ // Load server data when switching to multiplayer
+ this.loadServerData();
+ }
+ }
+
+ // Load player data (intelligently chooses source)
+ async loadPlayerData() {
+ if (this.isMultiplayer) {
+ return await this.loadServerData();
+ } else {
+ return await this.loadLocalData();
+ }
+ }
+
+ // Save player data (intelligently chooses destination)
+ async savePlayerData(gameData) {
+ if (this.isMultiplayer) {
+ return await this.saveServerData(gameData);
+ } else {
+ return await this.saveLocalData(gameData);
+ }
+ }
+
+ // Load server data
+ async loadServerData() {
+ try {
+ if (!this.gameInitializer || !this.gameInitializer.socket) {
+ // Don't warn during initialization - this is expected before socket is ready
+ // console.warn('[SMART SAVE] No multiplayer connection available');
+ return null;
+ }
+
+ console.log('[SMART SAVE] Loading server player data');
+
+ // Request data from server
+ this.gameInitializer.loadGameDataFromServer();
+
+ // Return cached server data if available
+ return this.serverPlayerData;
+ } catch (error) {
+ console.error('[SMART SAVE] Error loading server data:', error);
+ return null;
+ }
+ }
+
+ // Save server data (DISABLED - client should not send data to server)
+ async saveServerData(gameData) {
+ console.warn('[SMART SAVE] Client save disabled - server is authoritative');
+ return true; // Pretend it worked to avoid client errors
+ }
+
+ // Load local data
+ async loadLocalData() {
+ try {
+ console.log('[SMART SAVE] Loading local save data');
+
+ // Use existing local save system
+ if (window.mainMenu && window.mainMenu.loadGame) {
+ const saveData = await window.mainMenu.loadGame(1); // Load slot 1
+ this.localSaveData = saveData;
+ return saveData;
+ }
+
+ // Fallback to localStorage
+ const saveKey = 'gso_save_slot_1';
+ const saveData = localStorage.getItem(saveKey);
+
+ if (saveData) {
+ const parsed = JSON.parse(saveData);
+ this.localSaveData = parsed;
+ return parsed;
+ }
+
+ return null;
+ } catch (error) {
+ console.error('[SMART SAVE] Error loading local data:', error);
+ return null;
+ }
+ }
+
+ // Save local data
+ async saveLocalData(gameData) {
+ try {
+ // Don't save locally when in multiplayer mode
+ if (this.isMultiplayer) {
+ console.log('[SMART SAVE] Skipping local save - in multiplayer mode');
+ return true;
+ }
+
+ console.log('[SMART SAVE] Saving locally');
+
+ // Use existing local save system
+ if (window.mainMenu && window.mainMenu.saveGame) {
+ await window.mainMenu.saveGame(1, gameData); // Save to slot 1
+ this.localSaveData = gameData;
+ return true;
+ }
+
+ // Fallback to localStorage
+ const saveKey = 'gso_save_slot_1';
+ localStorage.setItem(saveKey, JSON.stringify(gameData));
+ this.localSaveData = gameData;
+
+ return true;
+ } catch (error) {
+ console.error('[SMART SAVE] Error saving local data:', error);
+ return false;
+ }
+ }
+
+ // Apply server data to game
+ applyServerDataToGame(serverData) {
+ console.log('[SMART SAVE] Applying server data to game');
+
+ this.serverPlayerData = serverData;
+
+ // Apply to game if game is running
+ if (window.game && window.game.loadPlayerData) {
+ window.game.loadPlayerData(serverData);
+ }
+
+ // Store for game engine
+ if (window.gameInitializer) {
+ window.gameInitializer.serverPlayerData = serverData;
+ }
+ }
+
+ // Get current save source info
+ getSaveInfo() {
+ return {
+ isMultiplayer: this.isMultiplayer,
+ hasServerData: !!this.serverPlayerData,
+ hasLocalData: !!this.localSaveData,
+ saveLocation: this.isMultiplayer ? 'Server Database' : 'Local Storage'
+ };
+ }
+
+ // Sync data between local and server (for migration)
+ async syncData(direction = 'toServer') {
+ if (direction === 'toServer') {
+ // Upload local data to server
+ const localData = await this.loadLocalData();
+ if (localData) {
+ await this.saveServerData(localData);
+ console.log('[SMART SAVE] Synced local data to server');
+ }
+ } else {
+ // Download server data to local
+ const serverData = await this.loadServerData();
+ if (serverData) {
+ await this.saveLocalData(serverData);
+ console.log('[SMART SAVE] Synced server data to local');
+ }
+ }
+ }
+}
+
+// Create global instance
+window.smartSaveManager = new SmartSaveManager();
+
+console.log('[SMART SAVE] SmartSaveManager loaded and available globally');
diff --git a/Client-Server/js/core/DebugLogger.js b/Client-Server/js/core/DebugLogger.js
new file mode 100644
index 0000000..ad59a1f
--- /dev/null
+++ b/Client-Server/js/core/DebugLogger.js
@@ -0,0 +1,163 @@
+/**
+ * Galaxy Strike Online - Debug Logger
+ * Enhanced debugging that integrates with existing Logger system
+ */
+
+class DebugLogger {
+ constructor() {
+ this.debugEnabled = true; // Always enabled
+ this.startTime = performance.now();
+ this.stepTimers = new Map();
+ this.debugLogs = []; // Store logs in memory
+ this.maxLogs = 1000; // Limit memory usage
+
+ // Use the existing logger if available
+ this.logger = window.logger || null;
+
+ // Log initialization
+ this.log('=== DEBUG SESSION STARTED ===');
+ }
+
+ async log(message, data = null) {
+ const timestamp = new Date().toISOString();
+ const stackTrace = new Error().stack;
+
+ // Build performance object
+ const performanceData = {
+ elapsed: `${(performance.now() - this.startTime).toFixed(2)}ms`,
+ memory: performance.memory ? {
+ used: `${(performance.memory.usedJSHeapSize / 1024 / 1024).toFixed(2)}MB`,
+ total: `${(performance.memory.totalJSHeapSize / 1024 / 1024).toFixed(2)}MB`
+ } : null
+ };
+
+ // Create single formatted log message
+ let logMessage = `[DEBUG] ${message}`;
+ if (data) {
+ logMessage += `\n${JSON.stringify(data, null, 2)}`;
+ }
+ if (performanceData) {
+ logMessage += `\n[PERF] ${performanceData.elapsed} | Memory: ${performanceData.memory?.used || 'N/A'}/${performanceData.memory?.total || 'N/A'}`;
+ }
+
+ // Add to memory logs
+ const logEntry = {
+ timestamp: timestamp,
+ message: message,
+ data: data ? JSON.stringify(data, null, 2) : '',
+ stackTrace: stackTrace ? stackTrace.split('\n').slice(0, 3).join('\n') : '',
+ performance: performanceData
+ };
+ this.debugLogs.push(logEntry);
+
+ // Limit memory usage
+ if (this.debugLogs.length > this.maxLogs) {
+ this.debugLogs.shift();
+ }
+
+ // Always log to console
+ console.log(`[DEBUG] ${message}`, data || '');
+
+ // Log performance data to console
+ if (performanceData.memory) {
+ console.log(`[PERF] ${performanceData.elapsed} | Memory: ${performanceData.memory.used}/${performanceData.memory.total}`);
+ }
+
+ // Use existing logger if available
+ if (this.logger) {
+ try {
+ await this.logger.debug(logMessage);
+ } catch (error) {
+ console.error('[DEBUG LOGGER] Failed to log via existing logger:', error);
+ }
+ } else {
+ // Fallback to electronAPI log
+ if (window.electronAPI && window.electronAPI.log) {
+ window.electronAPI.log('debug', logMessage);
+ }
+ }
+ }
+
+ async startStep(stepName) {
+ this.stepTimers.set(stepName, performance.now());
+ await this.log(`STEP START: ${stepName}`, {
+ type: 'step_start',
+ step: stepName,
+ elapsed: '0ms'
+ });
+ }
+
+ async endStep(stepName, data = null) {
+ const startTime = this.stepTimers.get(stepName);
+ const duration = startTime ? (performance.now() - startTime).toFixed(2) : 'N/A';
+
+ this.stepTimers.delete(stepName);
+ await this.log(`STEP END: ${stepName}`, {
+ type: 'step_end',
+ step: stepName,
+ duration: `${duration}ms`,
+ data
+ });
+ }
+
+ async logStep(stepName, data = null) {
+ await this.log(`STEP: ${stepName}`, {
+ type: 'step',
+ step: stepName,
+ data
+ });
+ }
+
+ getLogs() {
+ return this.debugLogs;
+ }
+
+ exportLogs() {
+ const logText = this.debugLogs.map(entry =>
+ `[${entry.timestamp}] ${entry.message}${entry.data ? '\n' + entry.data : ''}${entry.performance ? '\nPerf: ' + entry.performance.elapsed : ''}`
+ ).join('\n\n');
+
+ return logText;
+ }
+
+ clearLogs() {
+ this.debugLogs = [];
+ this.log('=== LOGS CLEARED ===');
+ }
+
+ async shutdown() {
+ await this.log('=== DEBUG SESSION ENDING ===');
+ await this.log('SESSION SUMMARY', {
+ totalLogs: this.debugLogs.length,
+ sessionDuration: `${(performance.now() - this.startTime).toFixed(2)}ms`
+ });
+
+ // No need to finalize files - the existing Logger handles that
+ console.log('[DEBUG LOGGER] Session ended cleanly');
+ }
+
+ // Convenience methods for specific logging types
+ async info(message, data = null) {
+ await this.log(`[INFO] ${message}`, data);
+ }
+
+ async error(message, data = null) {
+ await this.log(`[ERROR] ${message}`, data);
+ }
+
+ async warn(message, data = null) {
+ await this.log(`[WARN] ${message}`, data);
+ }
+
+ async errorEvent(error, context = 'Unknown') {
+ await this.error(`Error in ${context}`, {
+ name: error.name,
+ message: error.message,
+ stack: error.stack,
+ timestamp: new Date().toISOString()
+ });
+ }
+}
+
+// Global debug logger instance
+window.debugLogger = new DebugLogger();
diff --git a/Client-Server/js/core/Economy.js b/Client-Server/js/core/Economy.js
new file mode 100644
index 0000000..ecdea9c
--- /dev/null
+++ b/Client-Server/js/core/Economy.js
@@ -0,0 +1,2335 @@
+/**
+ * Galaxy Strike Online - Economy System
+ * Manages credits, gems, transactions, and shop
+ */
+
+class Economy {
+ constructor(gameEngine) {
+ const debugLogger = window.debugLogger;
+ if (debugLogger) debugLogger.log('Economy constructor called');
+
+ this.game = gameEngine;
+
+ // Currency
+ this.credits = 1000;
+ this.gems = 10;
+ this.premiumCurrency = 0; // For future premium features
+
+ // Transaction history
+ this.transactionHistory = [];
+ this.transactions = []; // Add missing transactions array
+
+ // Owned cosmetics
+ this.ownedCosmetics = []; // Add missing owned cosmetics array
+
+ // Shop categories
+ this.shopCategories = {
+ ships: 'Ships',
+ weapons: 'Weapons',
+ armors: 'Armors',
+ materials: 'Materials',
+ cosmetics: 'Cosmetics',
+ // upgrades: 'Upgrades', // Temporarily disabled
+ consumables: 'Consumables',
+ buildings: 'Buildings'
+ };
+
+ // Random shop system
+ this.randomShopItems = {}; // Current random items per category
+ this.shopRefreshInterval = null; // Timer for 2-hour refresh
+ this.shopHeartbeatInterval = null; // Timer for live countdown updates
+ this.lastShopRefresh = null; // Timestamp of last refresh
+ this.SHOP_REFRESH_INTERVAL = 2 * 60 * 60 * 1000; // 2 hours in milliseconds
+ this.MAX_ITEMS_PER_CATEGORY = 6;
+ this.categoryPurchaseLimits = {}; // Track purchases per category per refresh
+
+ // Shop items
+ this.shopItems = {
+ ships: [
+ // Starter Cruiser Variants
+ {
+ id: 'starter_cruiser_common',
+ name: 'Starter Cruiser',
+ type: 'ship',
+ rarity: 'common',
+ price: 5000,
+ currency: 'credits',
+ description: 'Reliable starter cruiser for new pilots',
+ texture: 'assets/textures/ships/starter_cruiser.png',
+ stats: { attack: 15, speed: 10, defense: 12, hull: 100 }
+ },
+ {
+ id: 'starter_cruiser_uncommon',
+ name: 'Starter Cruiser II',
+ type: 'ship',
+ rarity: 'uncommon',
+ price: 12000,
+ currency: 'credits',
+ description: 'Upgraded starter cruiser with enhanced systems',
+ texture: 'assets/textures/ships/starter_cruiser.png',
+ stats: { attack: 18, speed: 12, defense: 15, hull: 120 }
+ },
+ {
+ id: 'starter_cruiser_rare',
+ name: 'Starter Cruiser III',
+ type: 'ship',
+ rarity: 'rare',
+ price: 25000,
+ currency: 'credits',
+ description: 'Elite starter cruiser with advanced weaponry',
+ texture: 'assets/textures/ships/starter_cruiser.png',
+ stats: { attack: 22, speed: 14, defense: 18, hull: 145 }
+ },
+ {
+ id: 'starter_cruiser_epic',
+ name: 'Starter Cruiser IV',
+ type: 'ship',
+ rarity: 'epic',
+ price: 45000,
+ currency: 'credits',
+ description: 'Master starter cruiser with elite modifications',
+ texture: 'assets/textures/ships/starter_cruiser.png',
+ stats: { attack: 26, speed: 16, defense: 22, hull: 175 }
+ },
+ {
+ id: 'starter_cruiser_legendary',
+ name: 'Starter Cruiser V',
+ type: 'ship',
+ rarity: 'legendary',
+ price: 75000,
+ currency: 'credits',
+ description: 'Legendary starter cruiser with unparalleled performance',
+ texture: 'assets/textures/ships/starter_cruiser.png',
+ stats: { attack: 32, speed: 18, defense: 28, hull: 210 }
+ },
+
+ // Light Destroyer Variants
+ {
+ id: 'light_destroyer_common',
+ name: 'Light Destroyer',
+ type: 'ship',
+ rarity: 'common',
+ price: 12000,
+ currency: 'credits',
+ description: 'Fast and maneuverable light destroyer',
+ texture: 'assets/textures/ships/light_destroyer.png',
+ stats: { attack: 18, speed: 18, defense: 10, hull: 80 }
+ },
+ {
+ id: 'light_destroyer_uncommon',
+ name: 'Light Destroyer II',
+ type: 'ship',
+ rarity: 'uncommon',
+ price: 28000,
+ currency: 'credits',
+ description: 'Enhanced light destroyer with improved speed',
+ texture: 'assets/textures/ships/light_destroyer.png',
+ stats: { attack: 22, speed: 22, defense: 12, hull: 95 }
+ },
+ {
+ id: 'light_destroyer_rare',
+ name: 'Light Destroyer III',
+ type: 'ship',
+ rarity: 'rare',
+ price: 55000,
+ currency: 'credits',
+ description: 'Elite light destroyer with superior agility',
+ texture: 'assets/textures/ships/light_destroyer.png',
+ stats: { attack: 26, speed: 26, defense: 15, hull: 115 }
+ },
+ {
+ id: 'light_destroyer_epic',
+ name: 'Light Destroyer IV',
+ type: 'ship',
+ rarity: 'epic',
+ price: 95000,
+ currency: 'credits',
+ description: 'Master light destroyer with exceptional speed',
+ texture: 'assets/textures/ships/light_destroyer.png',
+ stats: { attack: 30, speed: 30, defense: 18, hull: 140 }
+ },
+ {
+ id: 'light_destroyer_legendary',
+ name: 'Light Destroyer V',
+ type: 'ship',
+ rarity: 'legendary',
+ price: 150000,
+ currency: 'credits',
+ description: 'Legendary light destroyer with unmatched velocity',
+ texture: 'assets/textures/ships/light_destroyer.png',
+ stats: { attack: 36, speed: 36, defense: 22, hull: 170 }
+ },
+
+ // Heavy Destroyer Variants
+ {
+ id: 'heavy_destroyer_common',
+ name: 'Heavy Destroyer',
+ type: 'ship',
+ rarity: 'common',
+ price: 25000,
+ currency: 'credits',
+ description: 'Powerful heavy destroyer with strong weapons',
+ texture: 'assets/textures/ships/heavy_destroyer.png',
+ stats: { attack: 25, speed: 12, defense: 18, hull: 120 }
+ },
+ {
+ id: 'heavy_destroyer_uncommon',
+ name: 'Heavy Destroyer II',
+ type: 'ship',
+ rarity: 'uncommon',
+ price: 55000,
+ currency: 'credits',
+ description: 'Upgraded heavy destroyer with enhanced firepower',
+ texture: 'assets/textures/ships/heavy_destroyer.png',
+ stats: { attack: 30, speed: 14, defense: 22, hull: 145 }
+ },
+ {
+ id: 'heavy_destroyer_rare',
+ name: 'Heavy Destroyer III',
+ type: 'ship',
+ rarity: 'rare',
+ price: 110000,
+ currency: 'credits',
+ description: 'Elite heavy destroyer with devastating weapons',
+ texture: 'assets/textures/ships/heavy_destroyer.png',
+ stats: { attack: 35, speed: 16, defense: 26, hull: 175 }
+ },
+ {
+ id: 'heavy_destroyer_epic',
+ name: 'Heavy Destroyer IV',
+ type: 'ship',
+ rarity: 'epic',
+ price: 190000,
+ currency: 'credits',
+ description: 'Master heavy destroyer with overwhelming power',
+ texture: 'assets/textures/ships/heavy_destroyer.png',
+ stats: { attack: 40, speed: 18, defense: 30, hull: 210 }
+ },
+ {
+ id: 'heavy_destroyer_legendary',
+ name: 'Heavy Destroyer V',
+ type: 'ship',
+ rarity: 'legendary',
+ price: 300000,
+ currency: 'credits',
+ description: 'Legendary heavy destroyer with ultimate destruction',
+ texture: 'assets/textures/ships/heavy_destroyer.png',
+ stats: { attack: 48, speed: 20, defense: 36, hull: 255 }
+ },
+
+ // Heavy Cruiser Variants
+ {
+ id: 'heavy_cruiser_common',
+ name: 'Heavy Cruiser',
+ type: 'ship',
+ rarity: 'common',
+ price: 45000,
+ currency: 'credits',
+ description: 'Massive heavy cruiser with excellent defense',
+ texture: 'assets/textures/ships/heavy_cruiser.png',
+ stats: { attack: 22, speed: 8, defense: 25, hull: 150 }
+ },
+ {
+ id: 'heavy_cruiser_uncommon',
+ name: 'Heavy Cruiser II',
+ type: 'ship',
+ rarity: 'uncommon',
+ price: 95000,
+ currency: 'credits',
+ description: 'Enhanced heavy cruiser with superior armor',
+ texture: 'assets/textures/ships/heavy_cruiser.png',
+ stats: { attack: 26, speed: 9, defense: 30, hull: 180 }
+ },
+ {
+ id: 'heavy_cruiser_rare',
+ name: 'Heavy Cruiser III',
+ type: 'ship',
+ rarity: 'rare',
+ price: 190000,
+ currency: 'credits',
+ description: 'Elite heavy cruiser with fortress-like defense',
+ texture: 'assets/textures/ships/heavy_cruiser.png',
+ stats: { attack: 30, speed: 10, defense: 36, hull: 220 }
+ },
+ {
+ id: 'heavy_cruiser_epic',
+ name: 'Heavy Cruiser IV',
+ type: 'ship',
+ rarity: 'epic',
+ price: 320000,
+ currency: 'credits',
+ description: 'Master heavy cruiser with impenetrable armor',
+ texture: 'assets/textures/ships/heavy_cruiser.png',
+ stats: { attack: 34, speed: 11, defense: 42, hull: 265 }
+ },
+ {
+ id: 'heavy_cruiser_legendary',
+ name: 'Heavy Cruiser V',
+ type: 'ship',
+ rarity: 'legendary',
+ price: 500000,
+ currency: 'credits',
+ description: 'Legendary heavy cruiser with ultimate defense',
+ texture: 'assets/textures/ships/heavy_cruiser.png',
+ stats: { attack: 40, speed: 12, defense: 50, hull: 320 }
+ }
+ ],
+ weapons: [
+ // Starter Blaster Variants
+ {
+ id: 'starter_blaster_common',
+ name: 'Starter Blaster',
+ type: 'weapon',
+ rarity: 'common',
+ price: 2000,
+ currency: 'credits',
+ description: 'Basic blaster for new pilots',
+ texture: 'assets/textures/weapons/starter_blaster.png',
+ stats: { damage: 10, fireRate: 2, range: 5, energy: 5 }
+ },
+ {
+ id: 'starter_blaster_uncommon',
+ name: 'Starter Blaster II',
+ type: 'weapon',
+ rarity: 'uncommon',
+ price: 5000,
+ currency: 'credits',
+ description: 'Enhanced starter blaster with better fire rate',
+ texture: 'assets/textures/weapons/starter_blaster.png',
+ stats: { damage: 12, fireRate: 2.5, range: 5.5, energy: 6 }
+ },
+ {
+ id: 'starter_blaster_rare',
+ name: 'Starter Blaster III',
+ type: 'weapon',
+ rarity: 'rare',
+ price: 12000,
+ currency: 'credits',
+ description: 'Elite starter blaster with improved damage',
+ texture: 'assets/textures/weapons/starter_blaster.png',
+ stats: { damage: 15, fireRate: 3, range: 6, energy: 7 }
+ },
+ {
+ id: 'starter_blaster_epic',
+ name: 'Starter Blaster IV',
+ type: 'weapon',
+ rarity: 'epic',
+ price: 25000,
+ currency: 'credits',
+ description: 'Master starter blaster with superior performance',
+ texture: 'assets/textures/weapons/starter_blaster.png',
+ stats: { damage: 18, fireRate: 3.5, range: 6.5, energy: 8 }
+ },
+ {
+ id: 'starter_blaster_legendary',
+ name: 'Starter Blaster V',
+ type: 'weapon',
+ rarity: 'legendary',
+ price: 50000,
+ currency: 'credits',
+ description: 'Legendary starter blaster with ultimate power',
+ texture: 'assets/textures/weapons/starter_blaster.png',
+ stats: { damage: 22, fireRate: 4, range: 7, energy: 10 }
+ },
+
+ // Laser Pistol Variants
+ {
+ id: 'laser_pistol_common',
+ name: 'Laser Pistol',
+ type: 'weapon',
+ rarity: 'common',
+ price: 3000,
+ currency: 'credits',
+ description: 'Compact laser pistol for close combat',
+ texture: 'assets/textures/weapons/laser_pistol.png',
+ stats: { damage: 8, fireRate: 3, range: 4, energy: 3 }
+ },
+ {
+ id: 'laser_pistol_uncommon',
+ name: 'Laser Pistol II',
+ type: 'weapon',
+ rarity: 'uncommon',
+ price: 7500,
+ currency: 'credits',
+ description: 'Enhanced laser pistol with better accuracy',
+ texture: 'assets/textures/weapons/laser_pistol.png',
+ stats: { damage: 10, fireRate: 3.5, range: 4.5, energy: 4 }
+ },
+ {
+ id: 'laser_pistol_rare',
+ name: 'Laser Pistol III',
+ type: 'weapon',
+ rarity: 'rare',
+ price: 18000,
+ currency: 'credits',
+ description: 'Elite laser pistol with piercing shots',
+ texture: 'assets/textures/weapons/laser_pistol.png',
+ stats: { damage: 13, fireRate: 4, range: 5, energy: 5 }
+ },
+ {
+ id: 'laser_pistol_epic',
+ name: 'Laser Pistol IV',
+ type: 'weapon',
+ rarity: 'epic',
+ price: 35000,
+ currency: 'credits',
+ description: 'Master laser pistol with rapid fire capability',
+ texture: 'assets/textures/weapons/laser_pistol.png',
+ stats: { damage: 16, fireRate: 4.5, range: 5.5, energy: 6 }
+ },
+ {
+ id: 'laser_pistol_legendary',
+ name: 'Laser Pistol V',
+ type: 'weapon',
+ rarity: 'legendary',
+ price: 70000,
+ currency: 'credits',
+ description: 'Legendary laser pistol with devastating power',
+ texture: 'assets/textures/weapons/laser_pistol.png',
+ stats: { damage: 20, fireRate: 5, range: 6, energy: 8 }
+ },
+
+ // Laser Sniper Rifle Variants
+ {
+ id: 'laser_sniper_rifle_common',
+ name: 'Laser Sniper Rifle',
+ type: 'weapon',
+ rarity: 'common',
+ price: 8000,
+ currency: 'credits',
+ description: 'Long-range laser sniper rifle for precision attacks',
+ texture: 'assets/textures/weapons/laser_sniper_rifle.png',
+ stats: { damage: 25, fireRate: 1, range: 10, energy: 8 }
+ },
+ {
+ id: 'laser_sniper_rifle_uncommon',
+ name: 'Laser Sniper Rifle II',
+ type: 'weapon',
+ rarity: 'uncommon',
+ price: 20000,
+ currency: 'credits',
+ description: 'Enhanced laser sniper rifle with better penetration',
+ texture: 'assets/textures/weapons/laser_sniper_rifle.png',
+ stats: { damage: 30, fireRate: 1.2, range: 11, energy: 9 }
+ },
+ {
+ id: 'laser_sniper_rifle_rare',
+ name: 'Laser Sniper Rifle III',
+ type: 'weapon',
+ rarity: 'rare',
+ price: 50000,
+ currency: 'credits',
+ description: 'Elite laser sniper rifle with deadly accuracy',
+ texture: 'assets/textures/weapons/laser_sniper_rifle.png',
+ stats: { damage: 36, fireRate: 1.4, range: 12, energy: 10 }
+ },
+ {
+ id: 'laser_sniper_rifle_epic',
+ name: 'Laser Sniper Rifle IV',
+ type: 'weapon',
+ rarity: 'epic',
+ price: 100000,
+ currency: 'credits',
+ description: 'Master laser sniper rifle with extreme range',
+ texture: 'assets/textures/weapons/laser_sniper_rifle.png',
+ stats: { damage: 42, fireRate: 1.6, range: 13, energy: 12 }
+ },
+ {
+ id: 'laser_sniper_rifle_legendary',
+ name: 'Laser Sniper Rifle V',
+ type: 'weapon',
+ rarity: 'legendary',
+ price: 200000,
+ currency: 'credits',
+ description: 'Legendary laser sniper rifle with unparalleled precision',
+ texture: 'assets/textures/weapons/laser_sniper_rifle.png',
+ stats: { damage: 50, fireRate: 2, range: 15, energy: 15 }
+ }
+ ],
+ armors: [
+ // Basic Armor Variants
+ {
+ id: 'basic_armor_common',
+ name: 'Basic Armor',
+ type: 'armor',
+ rarity: 'common',
+ price: 1500,
+ currency: 'credits',
+ description: 'Light protection for beginners',
+ texture: 'assets/textures/armors/basic_armor.png',
+ stats: { defense: 5, durability: 20, weight: 2, energyShield: 0 }
+ },
+ {
+ id: 'basic_armor_uncommon',
+ name: 'Basic Armor II',
+ type: 'armor',
+ rarity: 'uncommon',
+ price: 4000,
+ currency: 'credits',
+ description: 'Improved basic armor with better durability',
+ texture: 'assets/textures/armors/basic_armor.png',
+ stats: { defense: 7, durability: 25, weight: 2.2, energyShield: 2 }
+ },
+ {
+ id: 'basic_armor_rare',
+ name: 'Basic Armor III',
+ type: 'armor',
+ rarity: 'rare',
+ price: 10000,
+ currency: 'credits',
+ description: 'Elite basic armor with energy shielding',
+ texture: 'assets/textures/armors/basic_armor.png',
+ stats: { defense: 10, durability: 30, weight: 2.5, energyShield: 5 }
+ },
+ {
+ id: 'basic_armor_epic',
+ name: 'Basic Armor IV',
+ type: 'armor',
+ rarity: 'epic',
+ price: 20000,
+ currency: 'credits',
+ description: 'Master basic armor with advanced protection',
+ texture: 'assets/textures/armors/basic_armor.png',
+ stats: { defense: 13, durability: 35, weight: 2.8, energyShield: 8 }
+ },
+ {
+ id: 'basic_armor_legendary',
+ name: 'Basic Armor V',
+ type: 'armor',
+ rarity: 'legendary',
+ price: 40000,
+ currency: 'credits',
+ description: 'Legendary basic armor with ultimate defense',
+ texture: 'assets/textures/armors/basic_armor.png',
+ stats: { defense: 17, durability: 40, weight: 3, energyShield: 12 }
+ },
+
+ // Medium Armor Variants
+ {
+ id: 'medium_armor_common',
+ name: 'Medium Armor',
+ type: 'armor',
+ rarity: 'common',
+ price: 5000,
+ currency: 'credits',
+ description: 'Balanced armor for general combat',
+ texture: 'assets/textures/armors/medium_armor.png',
+ stats: { defense: 12, durability: 40, weight: 5, energyShield: 3 }
+ },
+ {
+ id: 'medium_armor_uncommon',
+ name: 'Medium Armor II',
+ type: 'armor',
+ rarity: 'uncommon',
+ price: 12000,
+ currency: 'credits',
+ description: 'Enhanced medium armor with better shielding',
+ texture: 'assets/textures/armors/medium_armor.png',
+ stats: { defense: 15, durability: 50, weight: 5.5, energyShield: 6 }
+ },
+ {
+ id: 'medium_armor_rare',
+ name: 'Medium Armor III',
+ type: 'armor',
+ rarity: 'rare',
+ price: 30000,
+ currency: 'credits',
+ description: 'Elite medium armor with advanced systems',
+ texture: 'assets/textures/armors/medium_armor.png',
+ stats: { defense: 19, durability: 60, weight: 6, energyShield: 10 }
+ },
+ {
+ id: 'medium_armor_epic',
+ name: 'Medium Armor IV',
+ type: 'armor',
+ rarity: 'epic',
+ price: 60000,
+ currency: 'credits',
+ description: 'Master medium armor with superior protection',
+ texture: 'assets/textures/armors/medium_armor.png',
+ stats: { defense: 23, durability: 70, weight: 6.5, energyShield: 15 }
+ },
+ {
+ id: 'medium_armor_legendary',
+ name: 'Medium Armor V',
+ type: 'armor',
+ rarity: 'legendary',
+ price: 100000,
+ currency: 'credits',
+ description: 'Legendary medium armor with ultimate defense',
+ texture: 'assets/textures/armors/medium_armor.png',
+ stats: { defense: 28, durability: 80, weight: 7, energyShield: 20 }
+ },
+
+ // Heavy Armor Variants
+ {
+ id: 'heavy_armor_common',
+ name: 'Heavy Armor',
+ type: 'armor',
+ rarity: 'common',
+ price: 10000,
+ currency: 'credits',
+ description: 'Maximum protection with increased weight',
+ texture: 'assets/textures/armors/heavy_armor.png',
+ stats: { defense: 20, durability: 60, weight: 8, energyShield: 5 }
+ },
+ {
+ id: 'heavy_armor_uncommon',
+ name: 'Heavy Armor II',
+ type: 'armor',
+ rarity: 'uncommon',
+ price: 25000,
+ currency: 'credits',
+ description: 'Upgraded heavy armor with better energy shielding',
+ texture: 'assets/textures/armors/heavy_armor.png',
+ stats: { defense: 25, durability: 75, weight: 9, energyShield: 10 }
+ },
+ {
+ id: 'heavy_armor_rare',
+ name: 'Heavy Armor III',
+ type: 'armor',
+ rarity: 'rare',
+ price: 60000,
+ currency: 'credits',
+ description: 'Elite heavy armor with advanced protection systems',
+ texture: 'assets/textures/armors/heavy_armor.png',
+ stats: { defense: 31, durability: 90, weight: 10, energyShield: 16 }
+ },
+ {
+ id: 'heavy_armor_epic',
+ name: 'Heavy Armor IV',
+ type: 'armor',
+ rarity: 'epic',
+ price: 120000,
+ currency: 'credits',
+ description: 'Master heavy armor with superior defense capabilities',
+ texture: 'assets/textures/armors/heavy_armor.png',
+ stats: { defense: 37, durability: 105, weight: 11, energyShield: 23 }
+ },
+ {
+ id: 'heavy_armor_legendary',
+ name: 'Heavy Armor V',
+ type: 'armor',
+ rarity: 'legendary',
+ price: 200000,
+ currency: 'credits',
+ description: 'Legendary heavy armor with ultimate protection',
+ texture: 'assets/textures/armors/heavy_armor.png',
+ stats: { defense: 45, durability: 120, weight: 12, energyShield: 30 }
+ }
+ ],
+ cosmetics: [
+ {
+ id: 'blue_paint',
+ name: 'Blue Paint Job',
+ type: 'cosmetic',
+ rarity: 'common',
+ price: 100,
+ currency: 'gems',
+ description: 'Custom blue paint for your ship'
+ },
+ {
+ id: 'golden_trim',
+ name: 'Golden Trim',
+ type: 'cosmetic',
+ rarity: 'rare',
+ price: 500,
+ currency: 'gems',
+ description: 'Luxurious golden trim for your ship'
+ }
+ ],
+ consumables: [
+ {
+ id: 'health_kit',
+ name: 'Health Kit',
+ type: 'consumable',
+ rarity: 'common',
+ price: 15,
+ currency: 'credits',
+ description: 'Restores 50 health points',
+ texture: 'assets/textures/items/health_pack.png',
+ effect: { heal: 50 }
+ },
+ {
+ id: 'mega_health_kit',
+ name: 'Mega Health Kit',
+ type: 'consumable',
+ rarity: 'uncommon',
+ price: 50,
+ currency: 'credits',
+ description: 'Restores full health',
+ texture: 'assets/textures/items/mega_health_pack.png',
+ effect: { heal: 999 }
+ }
+ ],
+ buildings: [
+ {
+ id: 'command_center',
+ name: 'Command Center',
+ type: 'building',
+ rarity: 'uncommon',
+ price: 50000,
+ currency: 'credits',
+ description: 'Central command facility for base operations and coordination',
+ texture: 'assets/textures/base/command_center.png',
+ stats: { command: 10, efficiency: 15, capacity: 20 }
+ },
+ {
+ id: 'mining_facility',
+ name: 'Mining Facility',
+ type: 'building',
+ rarity: 'common',
+ price: 25000,
+ currency: 'credits',
+ description: 'Automated mining facility for resource extraction and processing',
+ texture: 'assets/textures/base/mining_facility.png',
+ stats: { production: 12, efficiency: 8, storage: 15 }
+ }
+ ],
+ materials: [
+ {
+ id: 'iron_ore',
+ name: 'Iron Ore',
+ type: 'material',
+ rarity: 'common',
+ price: 10,
+ currency: 'credits',
+ description: 'Basic metal ore used for crafting weapons and armor',
+ texture: 'assets/textures/items/iron_ore.png',
+ stackable: true
+ },
+ {
+ id: 'copper_ore',
+ name: 'Copper Ore',
+ type: 'material',
+ rarity: 'common',
+ price: 8,
+ currency: 'credits',
+ description: 'Conductive metal ore used for wiring and electronics',
+ texture: 'assets/textures/items/copper_ore.png',
+ stackable: true
+ },
+ {
+ id: 'tin_bar',
+ name: 'Tin Bar',
+ type: 'material',
+ rarity: 'common',
+ price: 12,
+ currency: 'credits',
+ description: 'Refined tin bar used for alloys and soldering',
+ texture: 'assets/textures/items/tin_bar.png',
+ stackable: true
+ },
+ {
+ id: 'copper_wire',
+ name: 'Copper Wire',
+ type: 'material',
+ rarity: 'common',
+ price: 8,
+ currency: 'credits',
+ description: 'Conductive wiring for electronic components',
+ texture: 'assets/textures/items/copper_wire.png',
+ stackable: true
+ },
+ {
+ id: 'energy_crystal',
+ name: 'Energy Crystal',
+ type: 'material',
+ rarity: 'uncommon',
+ price: 25,
+ currency: 'credits',
+ description: 'Crystallized energy source for power systems',
+ texture: 'assets/textures/items/energy_crystal.png',
+ stackable: true
+ },
+ {
+ id: 'leather',
+ name: 'Leather',
+ type: 'material',
+ rarity: 'common',
+ price: 12,
+ currency: 'credits',
+ description: 'Durable material for crafting armor and accessories',
+ texture: 'assets/textures/items/leather.png',
+ stackable: true
+ },
+ {
+ id: 'herbs',
+ name: 'Herbs',
+ type: 'material',
+ rarity: 'common',
+ price: 5,
+ currency: 'credits',
+ description: 'Medicinal herbs used for healing items',
+ texture: 'assets/textures/items/herbs.png',
+ stackable: true
+ },
+ {
+ id: 'bandages',
+ name: 'Bandages',
+ type: 'material',
+ rarity: 'common',
+ price: 3,
+ currency: 'credits',
+ description: 'Medical bandages used for crafting healing items',
+ texture: 'assets/textures/items/bandages.png',
+ stackable: true
+ },
+ {
+ id: 'steel_plate',
+ name: 'Steel Plate',
+ type: 'material',
+ rarity: 'uncommon',
+ price: 30,
+ currency: 'credits',
+ description: 'Reinforced steel plates used for advanced armor and ship components',
+ texture: 'assets/textures/items/stell_plate.png',
+ stackable: true
+ },
+ {
+ id: 'advanced_component',
+ name: 'Advanced Component',
+ type: 'material',
+ rarity: 'rare',
+ price: 75,
+ currency: 'credits',
+ description: 'High-tech component used for advanced equipment and upgrades',
+ texture: 'assets/textures/items/advanced_component.png',
+ stackable: true
+ },
+ {
+ id: 'advanced_components',
+ name: 'Advanced Components Set',
+ type: 'material',
+ rarity: 'epic',
+ price: 150,
+ currency: 'credits',
+ description: 'Complete set of advanced components for high-end manufacturing',
+ texture: 'assets/textures/items/advanced_components.png',
+ stackable: true
+ },
+ {
+ id: 'basic_circuitboard',
+ name: 'Basic Circuit Board',
+ type: 'material',
+ rarity: 'common',
+ price: 20,
+ currency: 'credits',
+ description: 'Basic electronic circuit board for simple devices',
+ texture: 'assets/textures/items/basic_circuitboard.png',
+ stackable: true
+ },
+ {
+ id: 'common_circuitboard',
+ name: 'Common Circuit Board',
+ type: 'material',
+ rarity: 'common',
+ price: 35,
+ currency: 'credits',
+ description: 'Standard circuit board for most electronic equipment',
+ texture: 'assets/textures/items/common_circuitboard.png',
+ stackable: true
+ },
+ {
+ id: 'advanced_circuitboard',
+ name: 'Advanced Circuit Board',
+ type: 'material',
+ rarity: 'rare',
+ price: 100,
+ currency: 'credits',
+ description: 'High-performance circuit board for advanced systems',
+ texture: 'assets/textures/items/advanced_circuitboard.png',
+ stackable: true
+ },
+ {
+ id: 'battery',
+ name: 'Battery',
+ type: 'material',
+ rarity: 'uncommon',
+ price: 35,
+ currency: 'credits',
+ description: 'Power batteries used for energy-based equipment',
+ texture: 'assets/textures/items/battery.png',
+ stackable: true
+ },
+ {
+ id: 'advanced_components',
+ name: 'Advanced Components',
+ type: 'material',
+ rarity: 'rare',
+ price: 150,
+ currency: 'credits',
+ description: 'Sophisticated electronic components for advanced ship systems',
+ stackable: true
+ }
+ ]
+ };
+
+ // Owned cosmetics
+ this.ownedCosmetics = [];
+
+ if (debugLogger) debugLogger.log('Economy constructor completed', {
+ initialCredits: this.credits,
+ initialGems: this.gems,
+ shopCategoriesCount: Object.keys(this.shopCategories).length,
+ totalShopItems: Object.values(this.shopItems).reduce((total, category) => total + category.length, 0)
+ });
+ }
+
+ async initialize() {
+ const debugLogger = window.debugLogger;
+ console.log('[ECONOMY] Economy system initializing');
+ if (debugLogger) await debugLogger.startStep('economyInitialize');
+
+ try {
+ if (debugLogger) await debugLogger.logStep('Economy initialization started', {
+ currentCredits: this.credits,
+ currentGems: this.gems,
+ transactionHistoryLength: this.transactionHistory.length
+ });
+
+ // Setup shop purchase event listeners
+ this.setupShopEventListeners();
+
+ // Initialize random shop system
+ this.initializeRandomShop();
+
+ console.log('[ECONOMY] Economy system initialization completed');
+ if (debugLogger) await debugLogger.endStep('economyInitialize');
+ } catch (error) {
+ console.error('[ECONOMY] Error during initialization:', error);
+ if (debugLogger) await debugLogger.errorEvent(error, 'Economy Initialize');
+ }
+ }
+
+ initializeRandomShop() {
+ const debugLogger = window.debugLogger;
+ console.log('[ECONOMY] Initializing random shop system');
+
+ // Shop data is now loaded through the main save/load system in load()
+ // Only initialize if no shop data exists (new game)
+ if (!this.lastShopRefresh || Object.keys(this.randomShopItems).length === 0) {
+ console.log('[ECONOMY] No shop data found, generating new shop');
+ this.refreshRandomShop();
+ } else {
+ console.log('[ECONOMY] Shop data already loaded from save');
+ // Start the refresh timer for ongoing updates
+ this.startShopRefreshTimer();
+ }
+
+ if (debugLogger) debugLogger.logStep('Random shop system initialized', {
+ hasShopData: Object.keys(this.randomShopItems).length > 0,
+ lastRefresh: this.lastShopRefresh
+ });
+ }
+
+ refreshRandomShop() {
+ const debugLogger = window.debugLogger;
+ console.log('[ECONOMY] Refreshing random shop items');
+
+ const categories = Object.keys(this.shopCategories);
+ this.randomShopItems = {};
+
+ categories.forEach(category => {
+ const categoryItems = this.shopItems[category] || [];
+ if (categoryItems.length === 0) return;
+
+ // Group items by their base ID (remove tier suffixes)
+ const baseItemGroups = {};
+ categoryItems.forEach(item => {
+ // Extract base ID by removing tier suffixes (common, uncommon, rare, epic, legendary)
+ const baseId = item.id.replace(/_(common|uncommon|rare|epic|legendary)$/, '');
+ if (!baseItemGroups[baseId]) {
+ baseItemGroups[baseId] = [];
+ }
+ baseItemGroups[baseId].push(item);
+ });
+
+ // Get all unique base items
+ const uniqueBaseItems = Object.keys(baseItemGroups);
+
+ // Randomly select up to MAX_ITEMS_PER_CATEGORY base items
+ const shuffledBaseItems = [...uniqueBaseItems].sort(() => Math.random() - 0.5);
+ const selectedBaseItems = shuffledBaseItems.slice(0, Math.min(this.MAX_ITEMS_PER_CATEGORY, shuffledBaseItems.length));
+
+ // For each selected base item, randomly pick one tier variant
+ this.randomShopItems[category] = selectedBaseItems.map(baseId => {
+ const variants = baseItemGroups[baseId];
+ const selectedVariant = variants[Math.floor(Math.random() * variants.length)];
+
+ return {
+ ...selectedVariant,
+ id: `${selectedVariant.id}_random_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
+ originalId: selectedVariant.id,
+ baseId: baseId,
+ price: Math.round(selectedVariant.price * (0.8 + Math.random() * 0.4)), // ±20% price variation
+ isRandomItem: true,
+ refreshTimestamp: Date.now()
+ };
+ });
+
+ // Reset purchase limits for this category
+ this.categoryPurchaseLimits[category] = 0;
+
+ if (debugLogger) debugLogger.logStep('Random shop category refreshed', {
+ category: category,
+ totalBaseItems: uniqueBaseItems.length,
+ baseItemsSelected: selectedBaseItems.length,
+ itemsGenerated: this.randomShopItems[category].length,
+ selectedVariants: this.randomShopItems[category].map(item => ({
+ baseId: item.baseId,
+ variantId: item.originalId,
+ rarity: item.rarity,
+ name: item.name
+ })),
+ purchaseLimitReset: true
+ });
+ });
+
+ this.lastShopRefresh = Date.now();
+ this.saveRandomShopData();
+ this.startShopRefreshTimer();
+
+ // Update UI if shop tab is active
+ this.updateShopUI();
+
+ this.game.showNotification('Shop inventory refreshed!', 'info', 3000);
+
+ if (debugLogger) debugLogger.logStep('Random shop refresh completed', {
+ categoriesRefreshed: categories.length,
+ totalItemsGenerated: Object.values(this.randomShopItems).flat().length,
+ nextRefresh: new Date(this.lastShopRefresh + this.SHOP_REFRESH_INTERVAL)
+ });
+ }
+
+ startShopRefreshTimer() {
+ // Clear existing timers
+ if (this.shopRefreshInterval) {
+ clearInterval(this.shopRefreshInterval);
+ }
+ if (this.shopHeartbeatInterval) {
+ clearInterval(this.shopHeartbeatInterval);
+ }
+
+ // Set up heartbeat timer for live countdown updates (every second)
+ this.shopHeartbeatInterval = setInterval(() => {
+ this.updateShopCountdown();
+ }, 1000);
+
+ // Set up refresh timer (every 2 hours)
+ this.shopRefreshInterval = setInterval(() => {
+ this.refreshRandomShop();
+ }, this.SHOP_REFRESH_INTERVAL);
+
+ console.log('[ECONOMY] Shop refresh timers started');
+ }
+
+ updateShopCountdown() {
+ // Only update if shop tab is active and using random shop
+ const shopTab = document.getElementById('shop-tab');
+ if (!shopTab || shopTab.style.display === 'none') {
+ return;
+ }
+
+ const activeCategory = document.querySelector('.shop-cat-btn.active')?.dataset.category || 'ships';
+ if (!this.randomShopItems[activeCategory]) {
+ return;
+ }
+
+ // Find and update the countdown element
+ const countdownElement = document.querySelector('.refresh-countdown');
+ if (countdownElement) {
+ countdownElement.innerHTML = `
+
+ Next refresh in: ${this.getShopRefreshCountdown()}
+ `;
+ }
+ }
+
+ stopShopRefreshTimer() {
+ if (this.shopRefreshInterval) {
+ clearInterval(this.shopRefreshInterval);
+ this.shopRefreshInterval = null;
+ }
+ if (this.shopHeartbeatInterval) {
+ clearInterval(this.shopHeartbeatInterval);
+ this.shopHeartbeatInterval = null;
+ }
+ console.log('[ECONOMY] Shop refresh timers stopped');
+ }
+
+ loadRandomShopData() {
+ try {
+ const savedData = localStorage.getItem('gso_random_shop');
+ if (savedData) {
+ const data = JSON.parse(savedData);
+ this.randomShopItems = data.randomShopItems || {};
+ this.categoryPurchaseLimits = data.categoryPurchaseLimits || {};
+ this.lastShopRefresh = data.lastShopRefresh || null;
+ console.log('[ECONOMY] Random shop data loaded from localStorage');
+ }
+ } catch (error) {
+ console.error('[ECONOMY] Error loading random shop data:', error);
+ this.randomShopItems = {};
+ this.categoryPurchaseLimits = {};
+ this.lastShopRefresh = null;
+ }
+ }
+ saveRandomShopData() {
+ try {
+ const data = {
+ randomShopItems: this.randomShopItems,
+ categoryPurchaseLimits: this.categoryPurchaseLimits,
+ lastShopRefresh: this.lastShopRefresh
+ };
+ localStorage.setItem('gso_random_shop', JSON.stringify(data));
+ console.log('[ECONOMY] Random shop data saved to localStorage');
+ } catch (error) {
+ console.error('[ECONOMY] Error saving random shop data:', error);
+ }
+ }
+
+ shouldRefreshShop() {
+ // Check if enough time has passed since the last actual refresh
+ if (!this.lastShopRefresh) {
+ return true; // No previous refresh, need to refresh
+ }
+
+ const now = Date.now();
+ const timeSinceRefresh = now - this.lastShopRefresh;
+ return timeSinceRefresh >= this.SHOP_REFRESH_INTERVAL;
+ }
+
+ getShopRefreshCountdown() {
+ const schedule = this.getRefreshSchedule();
+ const now = Date.now();
+
+ // Calculate time until next server refresh
+ const timeUntilRefresh = schedule.nextRefresh.getTime() - now;
+
+ if (timeUntilRefresh <= 0) {
+ return 'Refreshing...';
+ }
+
+ return this.formatTimeRemaining(timeUntilRefresh);
+ }
+
+ getNextRefreshTime() {
+ // Calculate the next refresh time based on server time
+ const now = Date.now();
+
+ if (!this.lastShopRefresh) {
+ // If no last refresh, next refresh is now + interval
+ return new Date(now + this.SHOP_REFRESH_INTERVAL);
+ }
+
+ // Calculate next refresh time
+ const timeSinceRefresh = now - this.lastShopRefresh;
+ const intervalsPassed = Math.floor(timeSinceRefresh / this.SHOP_REFRESH_INTERVAL);
+ const nextRefreshTime = this.lastShopRefresh + ((intervalsPassed + 1) * this.SHOP_REFRESH_INTERVAL);
+
+ return new Date(nextRefreshTime);
+ }
+
+ getRefreshSchedule() {
+ // Generate a consistent refresh schedule based on server time
+ const now = new Date();
+ const currentHour = now.getHours();
+
+ // Refresh at even hours: 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22
+ let nextRefreshHour;
+
+ if (currentHour % 2 === 0 && now.getMinutes() === 0 && now.getSeconds() === 0) {
+ // Exactly on an even hour at :00:00, next refresh is in 2 hours
+ nextRefreshHour = currentHour + 2;
+ } else {
+ // Find the next even hour
+ nextRefreshHour = currentHour + (2 - (currentHour % 2));
+ }
+
+ // Handle midnight wrap-around
+ if (nextRefreshHour >= 24) {
+ nextRefreshHour = nextRefreshHour % 24;
+ }
+
+ const nextRefresh = new Date(now);
+ nextRefresh.setHours(nextRefreshHour, 0, 0, 0);
+
+ // If the calculated time is in the past (shouldn't happen but just in case), add 2 hours
+ if (nextRefresh <= now) {
+ nextRefresh.setHours(nextRefresh.getHours() + 2);
+ }
+
+ console.log('[ECONOMY] Current time:', now.toLocaleTimeString());
+ console.log('[ECONOMY] Next refresh at:', nextRefresh.toLocaleTimeString());
+
+ return {
+ nextRefresh: nextRefresh,
+ interval: 2 * 60 * 60 * 1000, // 2 hours
+ schedule: 'Every 2 hours at even hours (2,4,6,8,10,12,14,16,18,20,22,00)'
+ };
+ }
+
+ formatTimeRemaining(timeUntilRefresh) {
+ const hours = Math.floor(timeUntilRefresh / (1000 * 60 * 60));
+ const minutes = Math.floor((timeUntilRefresh % (1000 * 60 * 60)) / (1000 * 60));
+ const seconds = Math.floor((timeUntilRefresh % (1000 * 60)) / 1000);
+
+ if (hours > 0) {
+ return `${hours}h ${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
+ } else {
+ return `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
+ }
+ }
+
+ getItemCategory(item) {
+ // Determine category based on item type
+ switch (item.type) {
+ case 'ship': return 'ships';
+ case 'weapon': return 'weapons';
+ case 'armor': return 'armors';
+ case 'material': return 'materials';
+ case 'cosmetic': return 'cosmetics';
+ case 'upgrade': return 'upgrades';
+ case 'consumable': return 'consumables';
+ case 'building': return 'buildings';
+ default:
+ // For random items, check if we can find them in shop categories
+ for (const [category, items] of Object.entries(this.shopItems)) {
+ if (items.some(shopItem => shopItem.id === item.originalId || shopItem.id === item.id)) {
+ return category;
+ }
+ }
+ return 'unknown';
+ }
+ }
+
+ setupShopEventListeners() {
+ const debugLogger = window.debugLogger;
+ console.log('[ECONOMY] Setting up shop event listeners');
+
+ // Remove existing listeners to prevent duplicates
+ const shopItemsElement = document.getElementById('shopItems');
+ if (shopItemsElement) {
+ // Clone the element to remove all event listeners
+ const newElement = shopItemsElement.cloneNode(true);
+ shopItemsElement.parentNode.replaceChild(newElement, shopItemsElement);
+
+ // Setup shop purchase button event delegation on the new element
+ newElement.addEventListener('click', (event) => {
+ const purchaseBtn = event.target.closest('.shop-item-purchase-btn');
+ if (purchaseBtn && !purchaseBtn.disabled) {
+ const itemId = purchaseBtn.getAttribute('data-item-id');
+ console.log(`[ECONOMY] Shop purchase button clicked for item: ${itemId}`);
+
+ if (itemId) {
+ this.purchaseItem(itemId, 1);
+ } else {
+ console.error('[ECONOMY] No item ID found on purchase button');
+ }
+ }
+ });
+
+ console.log('[ECONOMY] Shop event listeners setup completed');
+ } else {
+ console.error('[ECONOMY] Shop items element not found');
+ }
+ }
+
+ // Currency management
+ addCredits(amount, source = 'unknown') {
+ const oldCredits = this.credits;
+ this.credits += amount;
+ this.addTransaction('credits', amount, source);
+
+ console.log(`[ECONOMY] Added ${amount} credits from ${source}. New total: ${this.credits}`);
+
+ // Update UI to show new credit amount
+ if (this.game.systems.ui && this.game.shouldUpdateGUI()) {
+ this.game.systems.ui.updateUI();
+ }
+
+ this.game.showNotification(`+${this.game.formatNumber(amount)} credits`, 'success', 2000);
+ }
+
+ removeCredits(amount) {
+ const oldCredits = this.credits;
+
+ if (this.credits < amount) {
+ this.game.showNotification('Not enough credits!', 'error', 3000);
+ return false;
+ }
+
+ this.credits -= amount;
+ this.addTransaction('credits', -amount, 'purchase');
+
+ console.log(`[ECONOMY] Removed ${amount} credits. New total: ${this.credits}`);
+
+ // Update UI to show new credit amount
+ if (this.game.systems.ui && this.game.shouldUpdateGUI()) {
+ this.game.systems.ui.updateUI();
+ }
+
+ return true;
+ }
+
+ addGems(amount, source = 'unknown') {
+ const oldGems = this.gems;
+ this.gems += amount;
+ this.addTransaction('gems', amount, source);
+
+ console.log(`[ECONOMY] Added ${amount} gems from ${source}. New total: ${this.gems}`);
+
+ // Update UI to show new gem amount
+ if (this.game.systems.ui && this.game.shouldUpdateGUI()) {
+ this.game.systems.ui.updateUI();
+ }
+
+ this.game.showNotification(`+${this.game.formatNumber(amount)} gems`, 'success', 2000);
+ }
+
+ removeGems(amount) {
+ const oldGems = this.gems;
+
+ if (this.gems < amount) {
+ this.game.showNotification('Not enough gems!', 'error', 3000);
+ return false;
+ }
+
+ this.gems -= amount;
+ this.addTransaction('gems', -amount, 'purchase');
+
+ console.log(`[ECONOMY] Removed ${amount} gems. New total: ${this.gems}`);
+
+ // Update UI to show new gem amount
+ if (this.game.systems.ui && this.game.shouldUpdateGUI()) {
+ this.game.systems.ui.updateUI();
+ }
+
+ return true;
+ }
+
+ // Transaction tracking
+ addTransaction(currency, amount, source) {
+ const debugLogger = window.debugLogger;
+ const transaction = {
+ id: Date.now() + Math.random().toString(36).substr(2, 9),
+ currency: currency,
+ amount: amount,
+ source: source,
+ timestamp: Date.now()
+ };
+
+ this.transactionHistory.unshift(transaction);
+
+ // Keep only last 100 transactions
+ const oldLength = this.transactionHistory.length;
+ if (this.transactionHistory.length > 100) {
+ this.transactionHistory = this.transactionHistory.slice(0, 100);
+ }
+
+ if (debugLogger) debugLogger.logStep('Transaction recorded', {
+ transactionId: transaction.id,
+ currency: currency,
+ amount: amount,
+ source: source,
+ timestamp: transaction.timestamp,
+ historyLength: this.transactionHistory.length,
+ wasTrimmed: oldLength > 100,
+ removedCount: oldLength > 100 ? oldLength - 100 : 0
+ });
+ }
+
+ // Shop functionality
+ purchaseItem(itemId, quantity = 1) {
+ const debugLogger = window.debugLogger;
+ const item = this.findShopItem(itemId);
+
+ if (!item) {
+ if (debugLogger) debugLogger.logStep('Item purchase failed - item not found', {
+ itemId: itemId,
+ quantity: quantity
+ });
+ this.game.showNotification('Item not found in shop', 'error', 3000);
+ return false;
+ }
+
+ const totalCost = item.price * quantity;
+ const currency = item.currency;
+ const oldCredits = this.credits;
+ const oldGems = this.gems;
+
+ if (debugLogger) debugLogger.logStep('Item purchase attempted', {
+ itemId: itemId,
+ itemName: item.name,
+ itemType: item.type,
+ quantity: quantity,
+ unitPrice: item.price,
+ totalCost: totalCost,
+ currency: currency,
+ currentCredits: oldCredits,
+ currentGems: oldGems
+ });
+
+ // Check if player can afford
+ if (currency === 'credits' && this.credits < totalCost) {
+ if (debugLogger) debugLogger.logStep('Item purchase failed - insufficient credits', {
+ totalCost: totalCost,
+ currentCredits: oldCredits,
+ deficit: totalCost - oldCredits
+ });
+ this.game.showNotification('Not enough credits!', 'error', 3000);
+ return false;
+ }
+
+ if (currency === 'gems' && this.gems < totalCost) {
+ if (debugLogger) debugLogger.logStep('Item purchase failed - insufficient gems', {
+ totalCost: totalCost,
+ currentGems: oldGems,
+ deficit: totalCost - oldGems
+ });
+ this.game.showNotification('Not enough gems!', 'error', 3000);
+ return false;
+ }
+
+ // Check if already owns this cosmetic
+ if (item.type === 'cosmetic' && this.ownedCosmetics.includes(item.id)) {
+ this.game.showNotification('You already own this cosmetic!', 'error', 3000);
+ return false;
+ }
+
+ // Process payment and give item based on type
+ if (currency === 'credits') {
+ this.credits -= totalCost;
+ } else if (currency === 'gems') {
+ this.gems -= totalCost;
+ }
+
+ switch (item.type) {
+ case 'ship':
+ this.purchaseShip(item, quantity);
+ break;
+ case 'cosmetic':
+ this.purchaseCosmetic(item, quantity);
+ break;
+ case 'consumable':
+ this.purchaseConsumable(item, quantity);
+ break;
+ case 'material':
+ this.purchaseMaterial(item, quantity);
+ break;
+ default:
+ console.warn(`[ECONOMY] Unknown item type: ${item.type}`);
+ return false;
+ }
+
+ // Random shop items don't have purchase limits - unlimited purchases allowed
+ if (item.isRandomItem) {
+ const category = this.getItemCategory(item);
+
+ if (debugLogger) debugLogger.logStep('Random shop purchase completed', {
+ itemId: itemId,
+ itemName: item.name,
+ category: category
+ });
+ }
+
+ // Update inventory UI to show the new item
+ if (this.game.systems.ui && this.game.systems.ui.updateInventory) {
+ this.game.systems.ui.updateInventory();
+ } else if (this.game.systems.ui && this.game.systems.ui.updateUI) {
+ this.game.systems.ui.updateUI();
+ } else {
+ console.warn('No UI update method available after purchase');
+ }
+
+ if (debugLogger) debugLogger.logStep('Item purchase completed successfully', {
+ itemId: itemId,
+ itemName: item.name,
+ itemType: item.type,
+ quantity: quantity,
+ totalCost: totalCost,
+ currency: currency,
+ oldCredits: oldCredits,
+ newCredits: this.credits,
+ oldGems: oldGems,
+ newGems: this.gems
+ });
+
+ // Update UI without calling updateShopUI to avoid circular updates
+ return true;
+ }
+
+ findShopItem(itemId) {
+ const debugLogger = window.debugLogger;
+
+ for (const category of Object.values(this.shopItems)) {
+ const item = category.find(i => i.id === itemId);
+ if (item) {
+ if (debugLogger) debugLogger.logStep('Shop item found', {
+ itemId: itemId,
+ itemName: item.name,
+ itemType: item.type,
+ itemPrice: item.price,
+ itemCurrency: item.currency
+ });
+ return item;
+ }
+ }
+
+ if (debugLogger) debugLogger.logStep('Shop item not found', {
+ itemId: itemId,
+ availableCategories: Object.keys(this.shopItems)
+ });
+ return null;
+ }
+
+ purchaseShip(ship) {
+ const debugLogger = window.debugLogger;
+ const player = this.game.systems.player;
+ const oldShipName = player.ship.name;
+ const oldShipClass = player.ship.class;
+ const oldAttributes = { ...player.attributes };
+
+ if (debugLogger) debugLogger.logStep('Ship purchase processing', {
+ shipId: ship.id,
+ shipName: ship.name,
+ shipType: ship.type,
+ shipStats: ship.stats,
+ oldShipName: oldShipName,
+ oldShipClass: oldShipClass
+ });
+
+ // Add ship to player's ship collection
+ // Add ship to base gallery
+ if (this.game.systems.baseSystem) {
+ this.game.systems.baseSystem.addShipToGallery(ship);
+ if (debugLogger) debugLogger.logStep('Ship added to base gallery');
+ }
+
+ // Replace current ship (no upgrade functionality)
+ player.ship.name = ship.name;
+ player.ship.class = ship.type;
+ player.ship.level = 1; // Reset level for new ship
+ player.ship.texture = ship.texture; // Copy texture for image display
+
+ // Set ship-specific stats (not just attributes)
+ if (ship.stats.health) {
+ player.ship.maxHealth = ship.stats.health;
+ player.ship.health = ship.stats.health;
+ }
+ if (ship.stats.attack) {
+ player.ship.attack = ship.stats.attack;
+ }
+ if (ship.stats.defense) {
+ player.ship.defense = ship.stats.defense;
+ }
+ if (ship.stats.speed) {
+ player.ship.speed = ship.stats.speed;
+ }
+ if (ship.stats.criticalChance) {
+ player.ship.criticalChance = ship.stats.criticalChance;
+ }
+ if (ship.stats.criticalDamage) {
+ player.ship.criticalDamage = ship.stats.criticalDamage;
+ }
+
+ // Also update player attributes for compatibility
+ for (const [stat, value] of Object.entries(ship.stats)) {
+ if (player.attributes[stat] !== undefined) {
+ player.attributes[stat] = value; // Replace stats instead of adding
+ }
+ }
+
+ player.updateUI();
+
+ // Also update ShipSystem display
+ if (this.game.systems.ship && this.game.systems.ship.updateCurrentShipDisplay) {
+ this.game.systems.ship.updateCurrentShipDisplay();
+ }
+
+ if (debugLogger) debugLogger.logStep('Ship purchase completed', {
+ shipId: ship.id,
+ oldShipName: oldShipName,
+ newShipName: player.ship.name,
+ oldShipClass: oldShipClass,
+ newShipClass: player.ship.class,
+ attributeChanges: Object.entries(ship.stats).map(([stat, value]) => ({
+ stat: stat,
+ oldValue: oldAttributes[stat],
+ newValue: player.attributes[stat],
+ change: value
+ }))
+ });
+ }
+
+ purchaseCosmetic(cosmetic) {
+ const debugLogger = window.debugLogger;
+ const oldOwnedCount = this.ownedCosmetics.length;
+
+ // Add to owned cosmetics
+ this.ownedCosmetics.push(cosmetic.id);
+ this.game.showNotification(`Cosmetic unlocked: ${cosmetic.name}`, 'success', 3000);
+
+ if (debugLogger) debugLogger.logStep('Cosmetic purchase completed', {
+ cosmeticId: cosmetic.id,
+ cosmeticName: cosmetic.name,
+ oldOwnedCount: oldOwnedCount,
+ newOwnedCount: this.ownedCosmetics.length,
+ totalOwnedCosmetics: this.ownedCosmetics.length
+ });
+ }
+
+ purchaseConsumable(consumable, quantity) {
+ const debugLogger = window.debugLogger;
+ const inventory = this.game.systems.inventory;
+ const oldInventorySize = inventory ? inventory.items.length : 0;
+
+ if (debugLogger) debugLogger.logStep('Consumable purchase processing', {
+ consumableId: consumable.id,
+ consumableName: consumable.name,
+ quantity: quantity,
+ effect: consumable.effect,
+ oldInventorySize: oldInventorySize
+ });
+
+ // Add to inventory
+ for (let i = 0; i < quantity; i++) {
+ const item = {
+ id: `${consumable.id}_${Date.now()}_${i}`,
+ name: consumable.name,
+ type: 'consumable',
+ rarity: consumable.rarity,
+ quantity: 1,
+ description: consumable.description,
+ consumable: true,
+ effect: consumable.effect
+ };
+
+ inventory.addItem(item);
+ }
+
+ if (debugLogger) debugLogger.logStep('Consumable purchase completed', {
+ consumableId: consumable.id,
+ quantity: quantity,
+ oldInventorySize: oldInventorySize,
+ newInventorySize: inventory ? inventory.items.length : 0,
+ itemsAdded: quantity
+ });
+ }
+
+ purchaseEquipment(equipment, quantity = 1) {
+ const inventory = this.game.systems.inventory;
+ const oldInventorySize = inventory ? inventory.items.length : 0;
+
+ // Add to inventory
+ for (let i = 0; i < quantity; i++) {
+ const item = {
+ id: `${equipment.id}_${Date.now()}_${i}`,
+ name: equipment.name,
+ type: equipment.type,
+ rarity: equipment.rarity,
+ quantity: 1,
+ description: equipment.description,
+ texture: equipment.texture,
+ stats: equipment.stats || {},
+ equipable: true
+ };
+
+ inventory.addItem(item);
+ }
+ }
+
+ purchaseMaterial(material, quantity = 1) {
+ const inventory = this.game.systems.inventory;
+ const debugLogger = window.debugLogger;
+
+ if (!inventory) {
+ console.error('[ECONOMY] Inventory system not available for material purchase');
+ return false;
+ }
+
+ try {
+ const oldInventorySize = inventory.items.length;
+ console.log(`[DEBUG] Inventory state before purchase: ${oldInventorySize} items`);
+
+ // Create item object for inventory
+ const item = {
+ id: material.id,
+ name: material.name,
+ type: 'material',
+ rarity: material.rarity || 'common',
+ quantity: quantity,
+ description: material.description || `A ${material.name} material`,
+ stackable: true,
+ stats: {},
+ equipable: false,
+ slot: null
+ };
+
+ console.log(`[DEBUG] Adding item to inventory: ${item.name}`);
+ const added = inventory.addItem(item);
+
+ console.log(`[DEBUG] addItem() returned: ${added}, new inventory size: ${inventory.items.length}`);
+
+ if (debugLogger) debugLogger.logStep('Material purchase completed', {
+ materialId: material.id,
+ materialName: material.name,
+ quantity: quantity,
+ oldInventorySize: oldInventorySize,
+ newInventorySize: inventory.items.length,
+ addedSuccessfully: added
+ });
+
+ if (!added) {
+ console.error('Failed to add material to inventory');
+ return false;
+ } else {
+ // Update inventory UI to show the new material
+ if (this.game.systems.ui && this.game.systems.ui.updateInventory) {
+ this.game.systems.ui.updateInventory();
+ } else if (this.game.systems.ui && this.game.systems.ui.updateUI) {
+ this.game.systems.ui.updateUI();
+ } else {
+ console.warn('No UI update method available after material purchase');
+ }
+ console.log('[DEBUG] Material purchase completed and UI update called');
+ return true;
+ }
+ } catch (error) {
+ console.error('Exception in purchaseMaterial:', error);
+ if (debugLogger) debugLogger.errorEvent('Purchase Material Exception', error, {
+ materialId: material.id,
+ materialName: material.name
+ });
+ return false;
+ }
+ }
+
+ // Reward generation
+ generateRewards(difficulty = 'normal', source = 'dungeon') {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('generateRewards', {
+ difficulty: difficulty,
+ source: source
+ });
+
+ const baseRewards = {
+ easy: { credits: [50, 150], gems: [0, 1], experience: [20, 50] },
+ normal: { credits: [100, 300], gems: [1, 3], experience: [50, 100] },
+ hard: { credits: [200, 500], gems: [2, 5], experience: [100, 200] },
+ extreme: { credits: [500, 1000], gems: [5, 10], experience: [200, 400] }
+ };
+
+ const rewards = baseRewards[difficulty] || baseRewards.normal;
+ const generatedRewards = {};
+
+ // Generate credits
+ generatedRewards.credits = Math.floor(
+ Math.random() * (rewards.credits[1] - rewards.credits[0] + 1) + rewards.credits[0]
+ );
+
+ // Generate gems
+ generatedRewards.gems = Math.floor(
+ Math.random() * (rewards.gems[1] - rewards.gems[0] + 1) + rewards.gems[0]
+ );
+
+ // Generate experience
+ generatedRewards.experience = Math.floor(
+ Math.random() * (rewards.experience[1] - rewards.experience[0] + 1) + rewards.experience[0]
+ );
+
+ // Bonus for premium currency (rare)
+ if (Math.random() < 0.05) { // 5% chance
+ generatedRewards.premiumCurrency = 1;
+ }
+
+ if (debugLogger) debugLogger.endStep('generateRewards', {
+ difficulty: difficulty,
+ source: source,
+ generatedRewards: generatedRewards,
+ rewardRanges: rewards
+ });
+
+ return generatedRewards;
+ }
+
+ giveRewards(rewards, source = 'unknown') {
+ const debugLogger = window.debugLogger;
+ const player = this.game.systems.player;
+ const oldCredits = this.credits;
+ const oldGems = this.gems;
+ const oldExperience = player.stats.experience;
+ const oldLevel = player.stats.level;
+
+ if (debugLogger) debugLogger.startStep('giveRewards', {
+ source: source,
+ rewards: rewards,
+ oldPlayerState: {
+ credits: oldCredits,
+ gems: oldGems,
+ experience: oldExperience,
+ level: oldLevel
+ }
+ });
+
+ let totalRewards = [];
+
+ // Add credits
+ if (rewards.credits > 0) {
+ this.addCredits(rewards.credits, source);
+ totalRewards.push(`${rewards.credits} credits`);
+ }
+
+ // Add gems
+ if (rewards.gems > 0) {
+ this.addGems(rewards.gems, source);
+ totalRewards.push(`${rewards.gems} gems`);
+ }
+
+ // Add premium currency
+ if (rewards.premiumCurrency > 0) {
+ this.addPremiumCurrency(rewards.premiumCurrency, source);
+ totalRewards.push(`${rewards.premiumCurrency} premium currency`);
+ }
+
+ // Add experience
+ if (rewards.experience > 0) {
+ player.addExperience(rewards.experience);
+ totalRewards.push(`${rewards.experience} experience`);
+ }
+
+ // Add materials
+ if (rewards.materials && rewards.materials.length > 0) {
+ const inventory = this.game.systems.inventory;
+ alert(`[DUNGEON DEBUG] Adding ${rewards.materials.length} materials to inventory\nInventory available: ${!!inventory}\nMaterials: ${JSON.stringify(rewards.materials, null, 2)}`);
+
+ for (const material of rewards.materials) {
+ // Create proper item object like in purchaseMaterial
+ const itemObject = {
+ id: material.id,
+ name: material.id.replace('_', ' ').replace(/\b\w/g, l => l.toUpperCase()), // Convert ID to readable name
+ type: 'material',
+ rarity: 'common',
+ quantity: material.quantity,
+ description: `A ${material.id.replace('_', ' ')} material`,
+ stackable: true,
+ stats: {},
+ equipable: false,
+ slot: null
+ };
+
+ alert(`[DUNGEON DEBUG] Fixed! Adding material as full item object:\n${JSON.stringify(itemObject, null, 2)}`);
+ inventory.addItem(itemObject);
+ totalRewards.push(`${material.quantity}x ${material.id}`);
+ }
+ }
+
+ // Add items
+ if (rewards.items && rewards.items.length > 0) {
+ const inventory = this.game.systems.inventory;
+ for (const item of rewards.items) {
+ inventory.addItem(item.id, item.quantity || 1);
+ totalRewards.push(`${item.quantity || 1}x ${item.id}`);
+ }
+ }
+
+ // Show reward notification
+ if (totalRewards.length > 0) {
+ const rewardText = totalRewards.join(', ');
+ this.game.showNotification(`Rewards: ${rewardText}`, 'success', 3000);
+ }
+
+ if (debugLogger) debugLogger.endStep('giveRewards', {
+ source: source,
+ rewardsGiven: rewards,
+ totalRewardsText: totalRewards.join(', '),
+ currencyChanges: {
+ credits: { old: oldCredits, new: this.credits, change: this.credits - oldCredits },
+ gems: { old: oldGems, new: this.gems, change: this.gems - oldGems }
+ },
+ playerChanges: {
+ experience: { old: oldExperience, new: player.stats.experience, change: player.stats.experience - oldExperience },
+ level: { old: oldLevel, new: player.stats.level, leveledUp: player.stats.level > oldLevel }
+ }
+ });
+ }
+
+ // Daily rewards and bonuses
+ claimDailyReward() {
+ const debugLogger = window.debugLogger;
+ const lastClaim = localStorage.getItem('lastDailyReward');
+ const today = new Date().toDateString();
+
+ if (debugLogger) debugLogger.startStep('claimDailyReward', {
+ lastClaim: lastClaim,
+ today: today,
+ alreadyClaimed: lastClaim === today
+ });
+
+ if (lastClaim === today) {
+ this.game.showNotification('Daily reward already claimed!', 'warning', 3000);
+ if (debugLogger) debugLogger.endStep('claimDailyReward', {
+ success: false,
+ reason: 'Already claimed today'
+ });
+ return false;
+ }
+
+ // Calculate reward based on consecutive days
+ const consecutiveDays = parseInt(localStorage.getItem('consecutiveDailyRewards') || '0') + 1;
+ const baseReward = 100;
+ const bonusMultiplier = Math.min(consecutiveDays * 0.1, 2); // Max 2x bonus
+ const credits = Math.floor(baseReward * (1 + bonusMultiplier));
+ const gems = Math.min(Math.floor(consecutiveDays / 7), 5); // 1 gem per week, max 5
+
+ if (debugLogger) debugLogger.logStep('Daily reward calculation', {
+ consecutiveDays: consecutiveDays,
+ baseReward: baseReward,
+ bonusMultiplier: bonusMultiplier,
+ creditsReward: credits,
+ gemsReward: gems
+ });
+
+ // Give rewards
+ this.addCredits(credits, 'daily_reward');
+ this.addGems(gems, 'daily_reward');
+
+ // Update daily reward tracking
+ localStorage.setItem('lastDailyReward', today);
+ localStorage.setItem('consecutiveDailyRewards', consecutiveDays.toString());
+
+ // Show notification
+ const rewardText = `${credits} credits${gems > 0 ? ` and ${gems} gems` : ''}`;
+ this.game.showNotification(`Daily reward claimed: ${rewardText}! (${consecutiveDays} day streak)`, 'success', 4000);
+
+ if (debugLogger) debugLogger.endStep('claimDailyReward', {
+ success: true,
+ consecutiveDays: consecutiveDays,
+ creditsReward: credits,
+ gemsReward: gems,
+ rewardText: rewardText,
+ newLastClaim: today,
+ newConsecutiveDays: consecutiveDays
+ });
+
+ return true;
+ }
+
+ // UI updates
+ updateUI() {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('updateUI', {
+ currentCredits: this.credits,
+ currentGems: this.gems,
+ currentPremiumCurrency: this.premiumCurrency
+ });
+
+ // Update resource displays
+ const creditsElement = document.getElementById('credits');
+ if (creditsElement) {
+ creditsElement.textContent = this.game.formatNumber(this.credits);
+ }
+
+ const gemsElement = document.getElementById('gems');
+ if (gemsElement) {
+ gemsElement.textContent = this.game.formatNumber(this.gems);
+ }
+
+ const premiumElement = document.getElementById('premiumCurrency');
+ if (premiumElement) {
+ premiumElement.textContent = this.game.formatNumber(this.premiumCurrency);
+ }
+
+ // Update shop UI when shop tab is active
+ this.updateShopUI();
+
+ if (debugLogger) debugLogger.endStep('updateUI', {
+ creditsUpdated: !!creditsElement,
+ gemsUpdated: !!gemsElement,
+ premiumUpdated: !!premiumElement,
+ shopUIUpdated: true
+ });
+ }
+
+ // Reset economy (for new game)
+ reset() {
+ const debugLogger = window.debugLogger;
+ const oldState = {
+ credits: this.credits,
+ gems: this.gems,
+ premiumCurrency: this.premiumCurrency,
+ transactionCount: this.transactions.length,
+ ownedCosmeticsCount: this.ownedCosmetics.length
+ };
+
+ if (debugLogger) debugLogger.startStep('reset', {
+ oldState: oldState
+ });
+
+ this.credits = 1000;
+ this.gems = 10;
+ this.premiumCurrency = 0;
+ this.transactions = [];
+ this.ownedCosmetics = [];
+
+ // Reset daily rewards
+ localStorage.removeItem('lastDailyReward');
+ localStorage.removeItem('consecutiveDailyRewards');
+
+ if (debugLogger) debugLogger.endStep('reset', {
+ oldState: oldState,
+ newState: {
+ credits: this.credits,
+ gems: this.gems,
+ premiumCurrency: this.premiumCurrency,
+ transactionCount: this.transactions.length,
+ ownedCosmeticsCount: this.ownedCosmetics.length
+ }
+ });
+ }
+
+ // Clear all data
+ clear() {
+ const debugLogger = window.debugLogger;
+ const oldState = {
+ credits: this.credits,
+ gems: this.gems,
+ premiumCurrency: this.premiumCurrency,
+ transactionCount: this.transactions.length,
+ ownedCosmeticsCount: this.ownedCosmetics.length
+ };
+
+ if (debugLogger) debugLogger.startStep('clear', {
+ oldState: oldState
+ });
+
+ this.credits = 0;
+ this.gems = 0;
+ this.premiumCurrency = 0;
+ this.transactions = [];
+ this.ownedCosmetics = [];
+
+ if (debugLogger) debugLogger.endStep('clear', {
+ oldState: oldState,
+ newState: {
+ credits: this.credits,
+ gems: this.gems,
+ premiumCurrency: this.premiumCurrency,
+ transactionCount: this.transactions.length,
+ ownedCosmeticsCount: this.ownedCosmetics.length
+ }
+ });
+ }
+
+ updateShopUI() {
+ const debugLogger = window.debugLogger;
+ const shopItemsElement = document.getElementById('shopItems');
+
+ if (!shopItemsElement) {
+ if (debugLogger) debugLogger.log('updateShopUI: Shop items element not found');
+ return;
+ }
+
+ const activeCategory = document.querySelector('.shop-cat-btn.active')?.dataset.category || 'ships';
+
+ // Always use random shop items when the system is available
+ // If no random items exist for this category, trigger a refresh
+ if (!this.randomShopItems[activeCategory] || this.randomShopItems[activeCategory].length === 0) {
+ console.log(`[ECONOMY] No random items for ${activeCategory}, triggering refresh`);
+ this.refreshRandomShop();
+ }
+
+ const items = this.randomShopItems[activeCategory] || [];
+ const isRandomShop = true; // Always random shop when system is available
+
+ if (debugLogger) debugLogger.startStep('updateShopUI', {
+ activeCategory: activeCategory,
+ itemCount: items.length,
+ isRandomShop: isRandomShop,
+ currentCredits: this.credits,
+ currentGems: this.gems,
+ currentPremiumCurrency: this.premiumCurrency
+ });
+
+ shopItemsElement.innerHTML = '';
+
+ // Add shop refresh info if using random shop
+ if (isRandomShop) {
+ const refreshInfo = document.createElement('div');
+ refreshInfo.className = 'shop-refresh-info';
+ refreshInfo.innerHTML = `
+
+
+
+ Next refresh in: ${this.getShopRefreshCountdown()}
+
+
+ `;
+ shopItemsElement.appendChild(refreshInfo);
+ }
+
+ items.forEach(item => {
+ const itemElement = document.createElement('div');
+ itemElement.className = 'shop-item';
+
+ const canAfford = this.canAfford(item);
+ const isOwned = item.type === 'cosmetic' && this.ownedCosmetics.includes(item.id);
+
+ itemElement.innerHTML = `
+
+ ${item.texture ?
+ `
+
+
` : ''}
+
+
${item.name}
+
${item.description}
+ ${item.stats ? `
+
+ ${Object.entries(item.stats).map(([stat, value]) =>
+ `
${stat}: ${value}
`
+ ).join('')}
+
+ ` : ''}
+
${this.formatPrice(item)}
+
${item.rarity}
+
+
+
+ ${isOwned ? 'Owned' : canAfford ? 'Purchase' : 'Cannot Afford'}
+
+ `;
+
+ shopItemsElement.appendChild(itemElement);
+
+ if (debugLogger) debugLogger.logStep('Shop item rendered', {
+ itemId: item.id,
+ itemName: item.name,
+ itemCategory: activeCategory,
+ itemPrice: item.price,
+ itemCurrency: item.currency,
+ canAfford: canAfford,
+ isOwned: isOwned
+ });
+ });
+
+ // Re-setup event listeners since we just recreated all the buttons
+ this.setupShopEventListeners();
+
+ if (debugLogger) debugLogger.endStep('updateShopUI', {
+ activeCategory: activeCategory,
+ itemsRendered: items.length,
+ shopUIUpdated: true
+ });
+ }
+
+ // Testing and utility methods
+ testPurchase(itemId) {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('testPurchase', {
+ itemId: itemId
+ });
+
+ const item = this.findShopItem(itemId);
+ if (!item) {
+ if (debugLogger) debugLogger.endStep('testPurchase', {
+ success: false,
+ reason: 'Item not found',
+ itemId: itemId
+ });
+ return false;
+ }
+
+ const result = this.purchaseItem(itemId);
+
+ if (debugLogger) debugLogger.endStep('testPurchase', {
+ success: result,
+ itemId: itemId,
+ itemName: item.name,
+ itemCategory: item.type
+ });
+
+ return result;
+ }
+
+ formatItemStats(item) {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('formatItemStats', {
+ itemId: item.id,
+ itemType: item.type
+ });
+
+ let stats = [];
+
+ if (item.stats) {
+ for (const [stat, value] of Object.entries(item.stats)) {
+ stats.push(`${stat}: +${value}`);
+ }
+ }
+
+ if (item.effect) {
+ if (item.effect.attackMultiplier) {
+ stats.push(`Attack: x${item.effect.attackMultiplier}`);
+ }
+ if (item.effect.defense) {
+ stats.push(`Defense: +${item.effect.defense}`);
+ }
+ if (item.effect.healing) {
+ stats.push(`Healing: +${item.effect.healing}`);
+ }
+ if (item.effect.energyRestore) {
+ stats.push(`Energy: +${item.effect.energyRestore}`);
+ }
+ }
+
+ const formattedStats = stats.length > 0 ? stats.join(', ') : 'No special stats';
+
+ if (debugLogger) debugLogger.endStep('formatItemStats', {
+ itemId: item.id,
+ formattedStats: formattedStats,
+ statCount: stats.length
+ });
+
+ return formattedStats;
+ }
+
+ // Save and load
+ save() {
+ const debugLogger = window.debugLogger;
+
+ // if (debugLogger) debugLogger.startStep('save', {
+ // currentCredits: this.credits,
+ // currentGems: this.gems,
+ // currentPremiumCurrency: this.premiumCurrency,
+ // transactionCount: this.transactions.length,
+ // ownedCosmeticsCount: this.ownedCosmetics.length
+ // });
+
+ const saveData = {
+ credits: this.credits,
+ gems: this.gems,
+ premiumCurrency: this.premiumCurrency,
+ transactions: this.transactions,
+ ownedCosmetics: this.ownedCosmetics,
+ // Include shop data with timestamp for proper refresh calculation
+ shopData: {
+ randomShopItems: this.randomShopItems,
+ categoryPurchaseLimits: this.categoryPurchaseLimits,
+ lastShopRefresh: this.lastShopRefresh,
+ saveTimestamp: Date.now() // When this save was created
+ }
+ };
+
+ // if (debugLogger) debugLogger.endStep('save', {
+ // saveDataSize: JSON.stringify(saveData).length,
+ // economyState: saveData
+ // });
+
+ return saveData;
+ }
+
+ load(data) {
+ const debugLogger = window.debugLogger;
+ const oldState = {
+ credits: this.credits,
+ gems: this.gems,
+ premiumCurrency: this.premiumCurrency,
+ transactionCount: this.transactions.length,
+ ownedCosmeticsCount: this.ownedCosmetics.length
+ };
+
+ // if (debugLogger) debugLogger.startStep('load', {
+ // oldState: oldState,
+ // loadData: data
+ // });
+
+ try {
+ this.credits = data.credits || 0;
+ this.gems = data.gems || 0;
+ this.premiumCurrency = data.premiumCurrency || 0;
+ this.transactions = data.transactions || [];
+ this.ownedCosmetics = data.ownedCosmetics || [];
+
+ // Load shop data and calculate if refresh is needed
+ if (data.shopData) {
+ console.log('[ECONOMY] Loading shop data from save');
+
+ // Restore shop data
+ this.randomShopItems = data.shopData.randomShopItems || {};
+ this.categoryPurchaseLimits = data.shopData.categoryPurchaseLimits || {};
+ this.lastShopRefresh = data.shopData.lastShopRefresh || null;
+
+ // Calculate if shop should have refreshed while game was closed
+ const saveTimestamp = data.shopData.saveTimestamp || Date.now();
+ const currentTime = Date.now();
+ const timeSinceSave = currentTime - saveTimestamp;
+
+ if (this.lastShopRefresh) {
+ const timeSinceLastRefresh = currentTime - this.lastShopRefresh;
+ const totalIntervalsPassed = Math.floor(timeSinceLastRefresh / this.SHOP_REFRESH_INTERVAL);
+
+ if (totalIntervalsPassed > 0) {
+ console.log(`[ECONOMY] Shop missed ${totalIntervalsPassed} refresh(es) while game was closed, refreshing now`);
+ this.refreshRandomShop();
+ } else {
+ console.log('[ECONOMY] Shop items still valid, no refresh needed');
+ }
+ } else {
+ console.log('[ECONOMY] No previous shop refresh, generating new shop');
+ this.refreshRandomShop();
+ }
+ } else {
+ console.log('[ECONOMY] No shop data in save, initializing new shop');
+ this.refreshRandomShop();
+ }
+
+ if (debugLogger) debugLogger.endStep('load', {
+ success: true,
+ oldState: oldState,
+ newState: {
+ credits: this.credits,
+ gems: this.gems,
+ premiumCurrency: this.premiumCurrency,
+ transactionCount: this.transactions.length,
+ ownedCosmeticsCount: this.ownedCosmetics.length
+ }
+ });
+ } catch (error) {
+ if (debugLogger) debugLogger.errorEvent('load', error, {
+ oldState: oldState,
+ error: error.message
+ });
+ throw error;
+ }
+ }
+
+ // Utility methods for shop
+ canAfford(item) {
+ if (item.currency === 'credits') {
+ return this.credits >= item.price;
+ } else if (item.currency === 'gems') {
+ return this.gems >= item.price;
+ } else if (item.currency === 'premium') {
+ return this.premiumCurrency >= item.price;
+ }
+ return false;
+ }
+
+ formatPrice(item) {
+ const currencySymbols = {
+ 'credits': '₵',
+ 'gems': '💎',
+ 'premium': '⭐'
+ };
+
+ const symbol = currencySymbols[item.currency] || item.currency;
+ return `${symbol}${this.game.formatNumber(item.price)}`;
+ }
+}
diff --git a/Client-Server/js/core/GameEngine.js b/Client-Server/js/core/GameEngine.js
new file mode 100644
index 0000000..f991638
--- /dev/null
+++ b/Client-Server/js/core/GameEngine.js
@@ -0,0 +1,1567 @@
+/**
+ * Galaxy Strike Online - Game Engine
+ * Core game loop and state management
+ */
+
+class GameEngine extends EventTarget {
+ constructor() {
+ super(); // Call EventTarget constructor first
+
+ console.log('🛑 DEBUG STOP 1: GameEngine constructor starting');
+ const debugLogger = window.debugLogger;
+ if (debugLogger) debugLogger.log('GameEngine constructor called', {
+ autoSaveInterval: 5,
+ timestamp: new Date().toISOString()
+ });
+
+ this.isRunning = false;
+ this.lastUpdate = 0;
+ this.gameTime = 0;
+
+ // Auto-save settings
+ this.autoSaveInterval = 1; // Default 1 minute
+ this.autoSaveTimer = null;
+ this.lastAutoSave = 0;
+
+ // Save slot information
+ this.saveSlotInfo = {
+ slot: 1, // Default save slot
+ useFileSystem: !!window.electronAPI // Use file system if Electron API is available
+ };
+
+ console.log('🛑 DEBUG STOP 2: Save slot info initialized:', this.saveSlotInfo);
+
+ // GUI update settings
+ this.guiUpdateInterval = 1000; // Update GUI once per second (1000ms)
+ this.lastGUIUpdate = 0;
+
+ // Game logic settings (independent of frame rate)
+ this.gameLogicInterval = 1000; // Update game logic every 1 second
+ this.gameLogicTimer = null;
+
+ // Game state
+ this.state = {
+ paused: false,
+ currentTab: 'dashboard',
+ notifications: [],
+ loading: true
+ };
+
+ console.log('🛑 DEBUG STOP 3: Game state initialized:', this.state);
+
+ // Systems
+ this.systems = {};
+
+ // Event listeners
+ this.eventListeners = new Map();
+
+ console.log('🛑 DEBUG STOP 4: About to call this.init()');
+ this.init();
+ console.log('🛑 DEBUG STOP 5: GameEngine constructor completed');
+ }
+
+ setMultiplayerMode(isMultiplayer, socket = null, serverData = null, currentUser = null) {
+ const debugLogger = window.debugLogger;
+
+ console.log('[GAME ENGINE] Setting multiplayer mode:', isMultiplayer);
+ if (debugLogger) debugLogger.logStep('setMultiplayerMode', { isMultiplayer });
+
+ this.isMultiplayer = isMultiplayer;
+ this.socket = socket;
+ this.serverData = serverData;
+ this.currentUser = currentUser;
+
+ // Store multiplayer settings for systems that need them
+ this.multiplayerConfig = {
+ isMultiplayer,
+ socket,
+ serverData,
+ currentUser
+ };
+
+ console.log('[GAME ENGINE] Multiplayer mode configured:', {
+ isMultiplayer,
+ hasSocket: !!socket,
+ hasServerData: !!serverData,
+ hasCurrentUser: !!currentUser
+ });
+ }
+
+ // Get random integer between min and max (inclusive)
+ getRandomInt(min, max) {
+ return Math.floor(Math.random() * (max - min + 1)) + min;
+ }
+
+ // Get random float between min and max
+ getRandomFloat(min, max) {
+ return Math.random() * (max - min) + min;
+ }
+
+ async init() {
+ console.log('[GAME ENGINE] Initializing game engine');
+ const logger = window.logger;
+ const debugLogger = window.debugLogger;
+
+ if (logger) await logger.info('Initializing game engine');
+ if (debugLogger) await debugLogger.startStep('gameEngineInit');
+
+ try {
+ // Initialize core systems but don't setup new game data
+ await this.initializeSystemsForLoad();
+
+ if (debugLogger) await debugLogger.logStep('Systems initialized, setting up event listeners');
+
+ // Set up event listeners
+ await this.setupEventListeners();
+
+ if (debugLogger) await debugLogger.logStep('Event listeners setup complete');
+
+ if (logger) await logger.info('Game engine initialization completed');
+ if (debugLogger) await debugLogger.endStep('gameEngineInit', {
+ systemsInitialized: Object.keys(this.systems).length,
+ eventListeners: this.eventListeners.size
+ });
+
+ } catch (error) {
+ console.error('Failed to initialize game:', error);
+ if (logger) await logger.errorEvent(error, 'Game Engine Initialization');
+ if (debugLogger) await debugLogger.errorEvent(error, 'Game Engine Initialization');
+ }
+ }
+
+ async startGame(continueGame = false) {
+ const logger = window.logger;
+ const debugLogger = window.debugLogger;
+
+ console.log('[GAME ENGINE] startGame called with continueGame =', continueGame);
+ if (logger) await logger.info('Starting game', { continueGame });
+ if (debugLogger) await debugLogger.startStep('startGame', { continueGame });
+
+ try {
+ if (continueGame) {
+ console.log('[GAME ENGINE] Loading existing save data...');
+ if (debugLogger) await debugLogger.logStep('Loading existing save data');
+ await this.loadGame();
+ console.log('[GAME ENGINE] Save data loaded');
+ } else {
+ console.log('[GAME ENGINE] Creating new game...');
+ if (debugLogger) await debugLogger.logStep('Creating new game');
+ await this.newGame();
+ console.log('[GAME ENGINE] New game created');
+ }
+
+ // Start game loop
+ // CRITICAL: Ensure UIManager is initialized before starting game
+ if (this.systems.ui) {
+ console.log('[GAME ENGINE] Final UIManager initialization check...');
+ try {
+ await this.systems.ui.initialize();
+ console.log('[GAME ENGINE] UIManager initialized successfully');
+ } catch (error) {
+ console.error('[GAME ENGINE] UIManager initialization failed:', error);
+ }
+ }
+
+ this.start();
+ console.log('[GAME ENGINE] Game loop started');
+ if (debugLogger) await debugLogger.logStep('Game loop started');
+
+ const loadingStatus = document.getElementById('loadingStatus');
+ if (loadingStatus) {
+ console.log('[GAME ENGINE] Hiding loading status text');
+ if (debugLogger) await debugLogger.logStep('Hiding loading status text');
+ loadingStatus.classList.add('hidden');
+ }
+
+ const gameInterface = document.getElementById('gameInterface');
+ if (gameInterface) {
+ console.log('[GAME ENGINE] Showing game interface');
+ if (debugLogger) await debugLogger.logStep('Showing game interface');
+ gameInterface.classList.remove('hidden');
+
+ // CRITICAL: Initialize UIManager after showing interface
+ console.log('[GAME ENGINE] Initializing UIManager after showing interface...');
+ if (this.systems.ui) {
+ try {
+ await this.systems.ui.initialize();
+ console.log('[GAME ENGINE] UIManager initialized successfully (post-interface)');
+ } catch (error) {
+ console.error('[GAME ENGINE] UIManager initialization failed:', error);
+ }
+ } else {
+ console.log('[GAME ENGINE] UIManager not found when trying to initialize after interface');
+ }
+ } else {
+ console.warn('[GAME ENGINE] gameInterface element not found');
+ if (debugLogger) await debugLogger.warn('gameInterface element not found');
+ }
+
+ if (logger) await logger.info('Game started successfully');
+ if (debugLogger) await debugLogger.endStep('startGame', {
+ continueGame,
+ isRunning: this.isRunning,
+ gameTime: this.gameTime
+ });
+
+ } catch (error) {
+ console.error('[GAME ENGINE] Failed to start game:', error);
+ if (logger) await logger.errorEvent(error, 'Game Start');
+ if (debugLogger) await debugLogger.errorEvent(error, 'Game Start');
+ }
+ }
+
+ async initializeSystemsForLoad() {
+ const logger = window.logger;
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) await debugLogger.startStep('initializeSystemsForLoad');
+
+ if (logger) {
+ await logger.timeAsync('Game Systems Initialization for Load', async () => {
+ await logger.info('Initializing game systems for loading');
+
+ if (debugLogger) await debugLogger.logStep('Initializing TextureManager');
+ // Initialize texture manager first
+ this.systems.textureManager = new TextureManager(this);
+ if (logger) await logger.systemEvent('TextureManager', 'Initialized');
+ if (debugLogger) await debugLogger.logStep('TextureManager initialized');
+
+ if (debugLogger) await debugLogger.logStep('Creating Player system (without initialization)');
+ // Create systems but don't initialize with default data
+ this.systems.player = new Player(this);
+ if (logger) await logger.systemEvent('Player', 'Created');
+ if (debugLogger) await debugLogger.logStep('Player system created');
+
+ if (debugLogger) await debugLogger.logStep('Creating Inventory system (without initialization)');
+ this.systems.inventory = new Inventory(this);
+ if (logger) await logger.systemEvent('Inventory', 'Created');
+ if (debugLogger) await debugLogger.logStep('Inventory system created');
+
+ if (debugLogger) await debugLogger.logStep('Creating Economy system (without initialization)');
+ this.systems.economy = new Economy(this);
+ if (logger) await logger.systemEvent('Economy', 'Created');
+ if (debugLogger) await debugLogger.logStep('Economy system created');
+
+ if (debugLogger) await debugLogger.logStep('Creating IdleSystem');
+ this.systems.idleSystem = new IdleSystem(this);
+ if (logger) await logger.systemEvent('IdleSystem', 'Created');
+ if (debugLogger) await debugLogger.logStep('IdleSystem created');
+
+ if (debugLogger) await debugLogger.logStep('Creating DungeonSystem');
+ this.systems.dungeonSystem = new DungeonSystem(this);
+ if (logger) await logger.systemEvent('DungeonSystem', 'Created');
+ if (debugLogger) await debugLogger.logStep('DungeonSystem created');
+
+ if (debugLogger) await debugLogger.logStep('Creating SkillSystem');
+ this.systems.skillSystem = new SkillSystem(this);
+ if (logger) await logger.systemEvent('SkillSystem', 'Created');
+ if (debugLogger) await debugLogger.logStep('SkillSystem created');
+
+ if (debugLogger) await debugLogger.logStep('Creating BaseSystem');
+ this.systems.baseSystem = new BaseSystem(this);
+ if (logger) await logger.systemEvent('BaseSystem', 'Created');
+ if (debugLogger) await debugLogger.logStep('BaseSystem created');
+
+ if (debugLogger) await debugLogger.logStep('Creating QuestSystem');
+ this.systems.questSystem = new QuestSystem(this);
+ if (logger) await logger.systemEvent('QuestSystem', 'Created');
+ if (debugLogger) await debugLogger.logStep('QuestSystem created');
+
+ if (debugLogger) await debugLogger.logStep('Creating ShipSystem');
+ this.systems.shipSystem = new ShipSystem(this);
+ if (logger) await logger.systemEvent('ShipSystem', 'Created');
+ if (debugLogger) await debugLogger.logStep('ShipSystem created');
+
+ if (debugLogger) await debugLogger.logStep('Creating UIManager');
+ if (typeof UIManager !== 'undefined') {
+ console.log('[GAME ENGINE] UIManager class found, creating real UIManager');
+ this.systems.ui = new UIManager(this);
+ // Expose UIManager globally for button onclick handlers
+ window.uiManager = this.systems.ui;
+ window.game.systems.ui = this.systems.ui;
+ if (logger) await logger.systemEvent('UIManager', 'Created');
+ if (debugLogger) await debugLogger.logStep('UIManager created and exposed');
+ } else {
+ console.error('[GAME ENGINE] UIManager class not found - this should not happen!');
+ if (debugLogger) await debugLogger.error('UIManager class not found - this should not happen!');
+ // Create minimal UI system to prevent crashes (should never be needed based on logs)
+ this.systems.ui = {
+ showNotification: (message, type, duration) => {
+ console.log(`[UI MINIMAL] ${type}: ${message}`);
+ },
+ updateUI: () => {
+ console.log('[UI MINIMAL] updateUI called');
+ },
+ switchTab: (tabName) => {
+ console.log('[UI MINIMAL] switchTab called');
+ }
+ };
+ }
+ if (debugLogger) await debugLogger.logStep('Creating CraftingSystem');
+ this.systems.crafting = new CraftingSystem(this);
+ if (logger) await logger.systemEvent('CraftingSystem', 'Created');
+ if (debugLogger) await debugLogger.logStep('CraftingSystem created');
+
+ if (debugLogger) await debugLogger.endStep('initializeSystemsForLoad', {
+ systemsCreated: Object.keys(this.systems).length
+ });
+ });
+ }
+ }
+
+ async initializeSystems() {
+ const logger = window.logger;
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) await debugLogger.startStep('initializeSystems');
+
+ if (logger) {
+ await logger.timeAsync('Game Systems Initialization', async () => {
+ await logger.info('Initializing game systems');
+
+ if (debugLogger) await debugLogger.logStep('Initializing TextureManager');
+ // Initialize texture manager first
+ this.systems.textureManager = new TextureManager(this);
+ if (logger) await logger.systemEvent('TextureManager', 'Initialized');
+ if (debugLogger) await debugLogger.logStep('TextureManager initialized');
+
+ if (debugLogger) await debugLogger.logStep('Initializing Player system');
+ // Initialize in dependency order
+ this.systems.player = new Player(this);
+ if (logger) await logger.systemEvent('Player', 'Initialized');
+ if (debugLogger) await debugLogger.logStep('Player system initialized', {
+ playerLevel: this.systems.player.stats.level,
+ playerExperience: this.systems.player.stats.experience
+ });
+
+ if (debugLogger) await debugLogger.logStep('Initializing Inventory system');
+ this.systems.inventory = new Inventory(this);
+ if (logger) await logger.systemEvent('Inventory', 'Initialized');
+ if (debugLogger) await debugLogger.logStep('Inventory system initialized', {
+ inventorySize: this.systems.inventory.items.length
+ });
+
+ if (debugLogger) await debugLogger.logStep('Initializing Economy system');
+ this.systems.economy = new Economy(this);
+ if (logger) await logger.systemEvent('Economy', 'Initialized');
+ if (debugLogger) await debugLogger.logStep('Economy system initialized', {
+ credits: this.systems.economy.credits,
+ gems: this.systems.economy.gems
+ });
+
+ if (debugLogger) await debugLogger.logStep('Initializing IdleSystem');
+ this.systems.idleSystem = new IdleSystem(this);
+ if (logger) await logger.systemEvent('IdleSystem', 'Initialized');
+ if (debugLogger) await debugLogger.logStep('IdleSystem initialized');
+
+ if (debugLogger) await debugLogger.logStep('Initializing DungeonSystem');
+ this.systems.dungeonSystem = new DungeonSystem(this);
+ if (logger) await logger.systemEvent('DungeonSystem', 'Initialized');
+ if (debugLogger) await debugLogger.logStep('DungeonSystem initialized');
+
+ if (debugLogger) await debugLogger.logStep('Initializing SkillSystem');
+ this.systems.skillSystem = new SkillSystem(this);
+ if (logger) await logger.systemEvent('SkillSystem', 'Initialized');
+ if (debugLogger) await debugLogger.logStep('SkillSystem initialized');
+
+ if (debugLogger) await debugLogger.logStep('Initializing BaseSystem');
+ this.systems.baseSystem = new BaseSystem(this);
+ if (logger) await logger.systemEvent('BaseSystem', 'Initialized');
+ if (debugLogger) await debugLogger.logStep('BaseSystem initialized');
+
+ if (debugLogger) await debugLogger.logStep('Initializing QuestSystem');
+ this.systems.questSystem = new QuestSystem(this);
+ await this.systems.questSystem.initialize();
+ if (logger) await logger.systemEvent('QuestSystem', 'Initialized');
+ if (debugLogger) await debugLogger.logStep('QuestSystem initialized');
+
+ if (debugLogger) await debugLogger.logStep('Initializing ShipSystem');
+ this.systems.ship = new ShipSystem(this);
+ if (logger) await logger.systemEvent('ShipSystem', 'Initialized');
+ if (debugLogger) await debugLogger.logStep('ShipSystem initialized');
+
+ if (debugLogger) await debugLogger.logStep('UIManager already initialized in initializeSystemsForLoad');
+
+ if (debugLogger) await debugLogger.logStep('Initializing CraftingSystem');
+ this.systems.crafting = new CraftingSystem(this);
+ if (logger) await logger.systemEvent('CraftingSystem', 'Initialized');
+ if (debugLogger) await debugLogger.logStep('CraftingSystem initialized');
+
+ if (logger) await logger.info('All game systems initialized');
+ if (debugLogger) await debugLogger.logStep('All systems created, running individual initializations');
+
+ if (debugLogger) await debugLogger.logStep('Running individual system initializations');
+ for (const [name, system] of Object.entries(this.systems)) {
+ if (system.initialize) {
+ if (debugLogger) await debugLogger.logStep(`Initializing ${name} system`);
+ await system.initialize();
+ if (debugLogger) await debugLogger.logStep(`${name} system initialization complete`);
+ } else {
+ if (debugLogger) await debugLogger.logStep(`${name} system has no initialize method`);
+ }
+ }
+
+ if (debugLogger) await debugLogger.endStep('initializeSystems', {
+ totalSystems: Object.keys(this.systems).length,
+ systemsList: Object.keys(this.systems)
+ });
+ });
+ } else {
+ // Fallback without timing
+ if (debugLogger) await debugLogger.logStep('Logger not available, using fallback initialization');
+
+ // Initialize texture manager first
+ this.systems.textureManager = new TextureManager(this);
+
+ // Initialize in dependency order
+ this.systems.player = new Player(this);
+ this.systems.inventory = new Inventory(this);
+ this.systems.economy = new Economy(this);
+ this.systems.idleSystem = new IdleSystem(this);
+ this.systems.dungeonSystem = new DungeonSystem(this);
+ this.systems.skillSystem = new SkillSystem(this);
+ this.systems.baseSystem = new BaseSystem(this);
+ this.systems.questSystem = new QuestSystem(this);
+ // UIManager already initialized in initializeSystemsForLoad
+ this.systems.crafting = new CraftingSystem(this);
+
+ for (const [name, system] of Object.entries(this.systems)) {
+ if (system.initialize) {
+ await system.initialize();
+ }
+ }
+
+ if (debugLogger) await debugLogger.endStep('initializeSystems', {
+ fallbackMode: true,
+ totalSystems: Object.keys(this.systems).length
+ });
+ }
+ }
+
+ async setupEventListeners() {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) await debugLogger.startStep('setupEventListeners');
+
+ // Window events
+ if (debugLogger) await debugLogger.logStep('Setting up window event listeners');
+ window.addEventListener('beforeunload', () => {
+ if (debugLogger) debugLogger.logStep('beforeunload event triggered - saving game');
+ this.save();
+ });
+
+ window.addEventListener('visibilitychange', () => {
+ // if (debugLogger) debugLogger.logStep('visibilitychange event triggered', {
+ // hidden: document.hidden,
+ // visibilityState: document.visibilityState
+ // });
+ if (document.hidden) {
+ // if (debugLogger) debugLogger.logStep('Document hidden - saving game');
+ this.save();
+ }
+ });
+
+ // Keyboard shortcuts
+ if (debugLogger) await debugLogger.logStep('Setting up keyboard shortcuts');
+ document.addEventListener('keydown', (event) => {
+ if (debugLogger) debugLogger.logStep('Key pressed', {
+ key: event.key,
+ code: event.code,
+ ctrlKey: event.ctrlKey,
+ altKey: event.altKey,
+ shiftKey: event.shiftKey
+ });
+
+ // Ctrl+S for manual save
+ if (event.ctrlKey && event.key === 's') {
+ event.preventDefault();
+ if (debugLogger) debugLogger.logStep('Manual save shortcut triggered (Ctrl+S)');
+ this.save();
+ }
+ });
+
+ // Game loop events
+ if (debugLogger) await debugLogger.logStep('Setting up game loop events');
+ this.addEventListener('gameStarted', () => {
+ if (debugLogger) debugLogger.logStep('Game started event received');
+ });
+
+ this.addEventListener('gameStopped', () => {
+ if (debugLogger) debugLogger.logStep('Game stopped event received');
+ });
+
+ this.addEventListener('gameSaved', () => {
+ // if (debugLogger) debugLogger.logStep('Game saved event received');
+ });
+
+ this.addEventListener('gameLoaded', () => {
+ if (debugLogger) debugLogger.logStep('Game loaded event received');
+ });
+
+ // Setup UI event listeners
+ if (debugLogger) await debugLogger.logStep('Setting up UI event listeners');
+ if (this.systems.ui && typeof this.systems.ui.setupEventListeners === 'function' && !this.uiEventListenersSetup) {
+ this.systems.ui.setupEventListeners();
+ this.uiEventListenersSetup = true; // Prevent duplicate setup
+ if (debugLogger) await debugLogger.logStep('UI event listeners setup complete');
+ } else if (this.uiEventListenersSetup) {
+ if (debugLogger) await debugLogger.logStep('UI event listeners already setup, skipping');
+ }
+
+ if (debugLogger) await debugLogger.endStep('setupEventListeners', {
+ windowEvents: 2,
+ keyboardShortcuts: 2,
+ gameLoopEvents: 4
+ });
+ }
+
+ start() {
+ const debugLogger = window.debugLogger;
+
+ if (this.isRunning) {
+ if (debugLogger) debugLogger.log('GameEngine.start() called but game is already running', {
+ isRunning: this.isRunning,
+ gameTime: this.gameTime
+ });
+ return;
+ }
+
+ if (debugLogger) debugLogger.logStep('Starting game engine', {
+ gameLogicInterval: this.gameLogicInterval
+ });
+
+ this.isRunning = true;
+ this.lastUpdate = performance.now();
+
+ if (debugLogger) debugLogger.logStep('Game engine started, beginning game loop');
+
+ this.gameLoop();
+
+ // Loading screen is now handled by startGame() method
+ // Don't duplicate the hiding logic here
+
+ if (debugLogger) debugLogger.logStep('Game loop initiated');
+ }
+
+ async stop() {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) await debugLogger.startStep('stopGame');
+
+ console.log('[GAME ENGINE] Stopping game and saving...');
+ if (debugLogger) await debugLogger.logStep('Stopping game engine');
+
+ if (!this.isRunning) {
+ if (debugLogger) debugLogger.logStep('Game already stopped, ignoring stop request');
+ return;
+ }
+
+ this.isRunning = false;
+
+ // Clear game logic timer
+ if (this.gameLogicTimer) {
+ clearInterval(this.gameLogicTimer);
+ this.gameLogicTimer = null;
+ }
+
+ // Clear auto-save timer
+ if (this.autoSaveTimer) {
+ clearInterval(this.autoSaveTimer);
+ this.autoSaveTimer = null;
+ }
+
+ // Clear server polling timer (multiplayer mode)
+ if (this.serverPollTimer) {
+ clearInterval(this.serverPollTimer);
+ this.serverPollTimer = null;
+ }
+
+ // Stop economy system timers
+ if (this.systems.economy) {
+ this.systems.economy.stopShopTimers();
+ }
+
+ console.log('[GAME ENGINE] Game stopped and saved successfully');
+
+ if (debugLogger) await debugLogger.endStep('stopGame', {
+ gameTime: this.gameTime,
+ isRunning: this.isRunning
+ });
+ }
+
+ startAutoSave() {
+ const debugLogger = window.debugLogger;
+
+ // Load saved interval or use default
+ const savedInterval = localStorage.getItem('autoSaveInterval');
+ this.autoSaveInterval = savedInterval ? parseInt(savedInterval) : 5;
+
+ console.log(`[GAME ENGINE] Starting auto-save with ${this.autoSaveInterval} minute interval`);
+ if (debugLogger) debugLogger.logStep('Starting auto-save system', {
+ interval: this.autoSaveInterval,
+ intervalMs: this.autoSaveInterval * 60 * 1000,
+ savedInterval: savedInterval,
+ isRunning: this.isRunning
+ });
+
+ // Clear any existing timer
+ this.stopAutoSave();
+
+ // Set up new timer
+ this.autoSaveTimer = setInterval(async () => {
+ if (debugLogger) debugLogger.logStep('Auto-save timer triggered', {
+ isRunning: this.isRunning,
+ paused: this.state.paused,
+ gameTime: this.gameTime,
+ lastAutoSave: this.lastAutoSave
+ });
+
+ console.log('[GAME ENGINE] Auto-save timer triggered - isRunning:', this.isRunning, 'paused:', this.state.paused);
+
+ if (this.isRunning && !this.state.paused) {
+ console.log('[GAME ENGINE] Auto-saving game...');
+ if (debugLogger) debugLogger.logStep('Auto-saving game');
+
+ try {
+ await this.save();
+ this.showNotification('Game auto-saved', 'info', 2000);
+ console.log('[GAME ENGINE] Auto-save completed successfully');
+
+ this.lastAutoSave = Date.now();
+ if (debugLogger) debugLogger.logStep('Auto-save completed successfully', {
+ lastAutoSave: this.lastAutoSave
+ });
+
+ } catch (error) {
+ console.error('[GAME ENGINE] Auto-save failed:', error);
+ if (debugLogger) await debugLogger.errorEvent(error, 'Auto-save');
+ }
+ } else {
+ console.log('[GAME ENGINE] Auto-save skipped - game not running or paused');
+ if (debugLogger) debugLogger.logStep('Auto-save skipped', {
+ reason: this.isRunning ? 'paused' : 'not running',
+ isRunning: this.isRunning,
+ paused: this.state.paused
+ });
+ }
+ }, this.autoSaveInterval * 60 * 1000); // Convert minutes to milliseconds
+
+ this.lastAutoSave = Date.now();
+ if (debugLogger) debugLogger.logStep('Auto-save timer started', {
+ timerId: this.autoSaveTimer,
+ lastAutoSave: this.lastAutoSave
+ });
+ }
+
+ stopAutoSave() {
+ const debugLogger = window.debugLogger;
+
+ if (this.autoSaveTimer) {
+ console.log('[GAME ENGINE] Stopping auto-save timer');
+ if (debugLogger) debugLogger.logStep('Stopping auto-save timer', {
+ timerId: this.autoSaveTimer,
+ wasRunning: true
+ });
+
+ clearInterval(this.autoSaveTimer);
+ this.autoSaveTimer = null;
+
+ if (debugLogger) debugLogger.logStep('Auto-save timer stopped');
+ } else {
+ if (debugLogger) debugLogger.logStep('stopAutoSave called but no timer was active', {
+ timerId: this.autoSaveTimer,
+ wasRunning: false
+ });
+ }
+ }
+
+ updateAutoSaveInterval(newInterval) {
+ const debugLogger = window.debugLogger;
+
+ console.log(`[GAME ENGINE] Updating auto-save interval to ${newInterval} minutes`);
+ if (debugLogger) debugLogger.logStep('Updating auto-save interval', {
+ oldInterval: this.autoSaveInterval,
+ newInterval: newInterval,
+ isRunning: this.isRunning
+ });
+
+ this.autoSaveInterval = newInterval;
+
+ // Save to localStorage
+ localStorage.setItem('autoSaveInterval', newInterval.toString());
+
+ // Restart auto-save with new interval if game is running
+ if (this.isRunning) {
+ if (debugLogger) debugLogger.logStep('Restarting auto-save with new interval');
+ this.startAutoSave();
+ }
+
+ if (debugLogger) debugLogger.logStep('Auto-save interval updated successfully', {
+ currentInterval: this.autoSaveInterval,
+ savedToStorage: true
+ });
+ }
+
+ start() {
+ const debugLogger = window.debugLogger;
+
+ if (this.isRunning) {
+ if (debugLogger) debugLogger.log('GameEngine.start() called but game is already running', {
+ isRunning: this.isRunning,
+ gameTime: this.gameTime
+ });
+ return;
+ }
+
+ if (debugLogger) debugLogger.logStep('Starting game engine', {
+ gameLogicInterval: this.gameLogicInterval
+ });
+
+ this.isRunning = true;
+ this.lastUpdate = Date.now();
+
+ // Start game logic timer (completely independent of frame rate)
+ console.log('[GAME ENGINE] Starting game logic timer with interval:', this.gameLogicInterval);
+ this.gameLogicTimer = setInterval(() => {
+ this.updateGameLogic();
+ }, this.gameLogicInterval);
+
+ // Start auto-save
+ this.startAutoSave();
+
+ // Check and apply any pending server data
+ if (this.checkAndApplyPendingServerData) {
+ this.checkAndApplyPendingServerData();
+ }
+
+ console.log('[GAME ENGINE] Game engine started');
+ if (debugLogger) debugLogger.logStep('Game engine started successfully', {
+ gameLogicInterval: this.gameLogicInterval,
+ autoSaveInterval: this.autoSaveInterval
+ });
+ }
+
+ updateGameLogic() {
+ const debugLogger = window.debugLogger;
+
+ // Use fixed 1-second interval for all updates
+ const fixedDelta = 1000; // 1 second in milliseconds
+
+ console.log('[GAME ENGINE] Game logic update called - adding', fixedDelta, 'ms to playtime');
+
+ if (this.state.paused) {
+ if (debugLogger) debugLogger.logStep('Game logic update called but game is paused', {
+ gameTime: this.gameTime,
+ fixedDelta: fixedDelta
+ });
+ return;
+ }
+
+ this.gameTime += fixedDelta;
+
+
+ // Log update performance every 300 frames (approximately 5 seconds)
+ if (debugLogger && this.gameTime % 15000 === 0) { // Every 15 seconds of game time
+ debugLogger.logStep('Game logic update cycle', {
+ gameTime: this.gameTime,
+ fixedDelta: fixedDelta,
+ isRunning: this.isRunning,
+ paused: this.state.paused,
+ currentTab: this.state.currentTab
+ });
+ }
+
+ // Update player play time with fixed delta
+ if (this.systems.player && this.systems.player.updatePlayTime) {
+ console.log('[GAME ENGINE] Before update - player playTime:', this.systems.player.stats.playTime);
+ this.systems.player.updatePlayTime(fixedDelta);
+ console.log('[GAME ENGINE] After update - player playTime:', this.systems.player.stats.playTime);
+ }
+
+ // Update all systems with fixed delta
+ for (const [name, system] of Object.entries(this.systems)) {
+ if (system && typeof system.update === 'function') {
+ try {
+ system.update(fixedDelta);
+ } catch (error) {
+ console.error(`[GAME ENGINE] Error updating ${name} system:`, error);
+ if (debugLogger) debugLogger.errorEvent(error, `Update ${name} system`);
+ }
+ }
+ }
+
+ // Update UI displays (money, gems, energy) after system updates
+ // Only update UI if in multiplayer mode or if game is actively running
+ const shouldUpdateUI = this.systems && this.systems.ui &&
+ (window.smartSaveManager?.isMultiplayer || this.isRunning);
+
+ if (shouldUpdateUI) {
+ console.log('[GAME ENGINE] Updating UI displays');
+ try {
+ this.systems.ui.updateUI();
+ console.log('[GAME ENGINE] UI update completed');
+ } catch (error) {
+ console.error('[GAME ENGINE] Error updating UI:', error);
+ }
+ }
+
+ // Only emit UI update event if in multiplayer mode or game is actively running
+ const shouldEmitUIUpdate = window.smartSaveManager?.isMultiplayer || this.isRunning;
+
+ if (shouldEmitUIUpdate) {
+ this.emitUIUpdateEvent('full');
+ }
+
+ // Emit game updated event
+ this.emit('gameUpdated', { gameTime: this.gameTime });
+ }
+
+ update(deltaTime) {
+ // Legacy method - game logic now handled by updateGameLogic()
+ // This method kept for compatibility but doesn't process game systems
+ }
+
+ shouldUpdateGUI() {
+ // Use centralized UI update control from UIManager
+ if (this.systems && this.systems.ui && this.systems.ui.shouldUpdateUI) {
+ const currentTime = Date.now();
+ if (currentTime - this.lastGUIUpdate >= this.guiUpdateInterval) {
+ this.lastGUIUpdate = currentTime;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ updateGUI() {
+ const debugLogger = window.debugLogger;
+
+ console.log('[GAME ENGINE] Updating GUI');
+
+ // Update UI displays (money, gems, energy) after system updates
+ // Only update UI if in multiplayer mode or if game is actively running
+ const shouldUpdateUI = this.systems && this.systems.ui &&
+ (window.smartSaveManager?.isMultiplayer || this.isRunning);
+
+ if (shouldUpdateUI) {
+ console.log('[GAME ENGINE] Updating UI displays');
+ try {
+ this.systems.ui.updateUI();
+ console.log('[GAME ENGINE] UI update completed');
+ } catch (error) {
+ console.error('[GAME ENGINE] Error updating UI:', error);
+ }
+ } else {
+ // Skip UI updates when not in multiplayer and game is not actively running
+ if (this.systems && this.systems.ui) {
+ console.log('[GAME ENGINE] Skipping GUI updates - not in multiplayer mode');
+ }
+ }
+ }
+
+ // Event system
+ on(event, callback) {
+ if (!this.eventListeners.has(event)) {
+ this.eventListeners.set(event, []);
+ }
+ this.eventListeners.get(event).push(callback);
+ }
+
+ emit(event, data) {
+ if (this.eventListeners.has(event)) {
+ this.eventListeners.get(event).forEach(callback => {
+ try {
+ callback(data);
+ } catch (error) {
+ console.error(`[GAME ENGINE] Error in event listener for ${event}:`, error);
+ }
+ });
+ }
+
+ // Also dispatch as DOM event for UIManager
+ this.dispatchEvent(new CustomEvent(event, { detail: data }));
+ }
+
+ emitUIUpdateEvent(type, data = {}) {
+ const eventData = {
+ type: type,
+ timestamp: Date.now(),
+ ...data
+ };
+
+ console.log('[GAME ENGINE] Emitting UI update event:', eventData);
+ this.emit('uiUpdate', eventData);
+ }
+
+ // Notification system
+ async showNotification(message, type = 'info', duration = 3000) {
+ const logger = window.logger;
+ if (logger) await logger.playerAction('Notification', { message, type, duration });
+
+ const notification = {
+ id: Date.now(),
+ message,
+ type,
+ duration,
+ timestamp: Date.now()
+ };
+
+ this.state.notifications.push(notification);
+
+ // Auto-remove notification after duration
+ setTimeout(() => {
+ this.removeNotification(notification.id);
+ }, duration);
+
+ // Update UI
+ if (this.systems.ui) {
+ // UI updates handled by individual systems
+ }
+ }
+
+ removeNotification(id) {
+ this.state.notifications = this.state.notifications.filter(notification => notification.id !== id);
+ }
+
+ processNotifications() {
+ const now = Date.now();
+ this.state.notifications = this.state.notifications.filter(
+ notification => now - notification.timestamp < notification.duration
+ );
+ }
+
+ // Save/Load system
+ async save() {
+ const logger = window.logger;
+ const debugLogger = window.debugLogger;
+
+ console.log('[GAME ENGINE] Save method called');
+ if (logger) await logger.info('Saving game');
+ // if (debugLogger) await debugLogger.startStep('saveGame');
+
+ try {
+ // if (debugLogger) await debugLogger.logStep('Collecting save data from systems');
+ console.log('[GAME ENGINE] Collecting save data from systems...');
+
+ const saveData = {
+ player: this.systems.player.save(),
+ inventory: this.systems.inventory.save(),
+ economy: this.systems.economy.save(),
+ idleSystem: this.systems.idleSystem.save(),
+ dungeonSystem: this.systems.dungeonSystem.save(),
+ skillSystem: this.systems.skillSystem.save(),
+ baseSystem: this.systems.baseSystem.save(),
+ questSystem: this.systems.questSystem.save(),
+ gameTime: this.gameTime,
+ lastSave: Date.now()
+ };
+
+ // if (debugLogger) await debugLogger.logStep('Save data collected', {
+ // playerLevel: saveData.player?.stats?.level,
+ // gameTime: this.gameTime,
+ // saveDataSize: JSON.stringify(saveData).length,
+ // });
+
+ // console.log('[GAME ENGINE] Save data collected, player level:', saveData.player?.stats?.level);
+ // console.log('[GAME ENGINE] Save slot info:', this.saveSlotInfo);}
+
+ // Check if we should save to server (multiplayer mode)
+ if (window.smartSaveManager && window.smartSaveManager.isMultiplayer) {
+ console.log('[GAME ENGINE] Saving to server in multiplayer mode');
+ if (debugLogger) await debugLogger.logStep('Saving to server in multiplayer mode');
+
+ try {
+ const serverSaveResult = await window.smartSaveManager.savePlayerData(saveData);
+ if (serverSaveResult) {
+ console.log('[GAME ENGINE] Game saved successfully to server');
+ if (debugLogger) await debugLogger.logStep('Game saved successfully to server');
+ } else {
+ console.warn('[GAME ENGINE] Server save failed, falling back to local storage');
+ if (debugLogger) await debugLogger.warn('Server save failed, using local fallback');
+
+ // Fallback to localStorage
+ const saveKey = `gso_save_slot_${this.saveSlotInfo?.slot || 1}`;
+ localStorage.setItem(saveKey, JSON.stringify(saveData));
+ }
+ } catch (error) {
+ console.error('[GAME ENGINE] Error saving to server:', error);
+ if (debugLogger) await debugLogger.errorEvent(error, 'Server Save');
+
+ // Fallback to localStorage
+ const saveKey = `gso_save_slot_${this.saveSlotInfo?.slot || 1}`;
+ localStorage.setItem(saveKey, JSON.stringify(saveData));
+ console.log('[GAME ENGINE] Error fallback: Game saved to localStorage');
+ }
+ } else {
+ // Local save logic (existing code)
+ const isLocalMode = window.liveMainMenu && window.liveMainMenu.isLocalMode;
+
+ if (isLocalMode) {
+ console.log('[GAME ENGINE] Using localStorage for local mode save');
+ if (debugLogger) await debugLogger.logStep('Saving via localStorage for local mode');
+
+ try {
+ const saveKey = `gso_save_slot_${this.saveSlotInfo.slot}`;
+ const saveString = JSON.stringify(saveData);
+
+ // Save to localStorage
+ localStorage.setItem(saveKey, saveString);
+
+ console.log('[GAME ENGINE] Game saved successfully to localStorage');
+ console.log('[GAME ENGINE] Save key:', saveKey);
+ console.log('[GAME ENGINE] Save data size:', saveString.length, 'characters');
+ console.log('[GAME ENGINE] Current localStorage keys:', Object.keys(localStorage));
+
+ // Verify save exists
+ const verifySave = localStorage.getItem(saveKey);
+ console.log('[GAME ENGINE] Save verification:', !!verifySave ? 'SUCCESS' : 'FAILED');
+
+ if (debugLogger) await debugLogger.logStep('Game saved successfully to localStorage', {
+ saveKey,
+ saveDataSize: saveString.length
+ });
+
+ // Emit save event
+ this.emit('gameSaved', { slot: this.saveSlotInfo.slot, saveData });
+ // if (debugLogger) debugLogger.logStep('Game saved event emitted');
+
+ } catch (error) {
+ console.error('[GAME ENGINE] Failed to save to localStorage:', error);
+ if (debugLogger) await debugLogger.errorEvent(error, 'LocalStorage Save');
+ throw error;
+ }
+ } else {
+ // Use file system if available, otherwise localStorage (original logic)
+ if (this.saveSlotInfo && this.saveSlotInfo.useFileSystem) {
+ if (window.electronAPI) {
+ // console.log('[GAME ENGINE] Using Electron API to save game');
+ // if (debugLogger) await debugLogger.logStep('Saving via Electron API', {
+ // slot: this.saveSlotInfo.slot,
+ // useFileSystem: true
+ // });
+
+ try {
+ // Save through Electron API
+ const result = await window.electronAPI.saveGame(this.saveSlotInfo.slot, saveData);
+ if (result.success) {
+ // console.log('[GAME ENGINE] Game saved successfully via Electron API');
+ if (debugLogger) await debugLogger.logStep('Game saved successfully via Electron API', {
+ slot: this.saveSlotInfo.slot,
+ result: result
+ });
+
+ // Emit save event
+ this.emit('gameSaved', { slot: this.saveSlotInfo.slot, saveData });
+ // if (debugLogger) debugLogger.logStep('Game saved event emitted');
+
+ } else {
+ console.error('[GAME ENGINE] Failed to save via Electron API:', result.error);
+ if (debugLogger) await debugLogger.errorEvent(new Error(result.error), 'Electron API Save');
+
+ // Fallback to localStorage
+ if (debugLogger) await debugLogger.logStep('Falling back to localStorage');
+ const saveKey = `gso_save_slot_${this.saveSlotInfo.slot}`;
+ localStorage.setItem(saveKey, JSON.stringify(saveData));
+ console.log('[GAME ENGINE] Fallback: Game saved to localStorage with key:', saveKey);
+ if (debugLogger) await debugLogger.logStep('Game saved to localStorage fallback', {
+ saveKey,
+ dataSize: JSON.stringify(saveData).length
+ });
+ }
+ } catch (error) {
+ console.error('[GAME ENGINE] Electron API save error:', error);
+ if (debugLogger) await debugLogger.errorEvent(error, 'Electron API Save');
+
+ // Fallback to localStorage
+ if (debugLogger) await debugLogger.logStep('Falling back to localStorage due to error');
+ const saveKey = `gso_save_slot_${this.saveSlotInfo.slot}`;
+ localStorage.setItem(saveKey, JSON.stringify(saveData));
+ console.log('[GAME ENGINE] Error fallback: Game saved to localStorage with key:', saveKey);
+ }
+ } else {
+ console.warn('[GAME ENGINE] Electron API not available, using localStorage');
+ if (debugLogger) await debugLogger.warn('Electron API not available, using localStorage');
+
+ const saveKey = `gso_save_slot_${this.saveSlotInfo.slot}`;
+ localStorage.setItem(saveKey, JSON.stringify(saveData));
+ console.log('[GAME ENGINE] Game saved to localStorage with key:', saveKey);
+ if (debugLogger) await debugLogger.logStep('Game saved to localStorage', {
+ saveKey,
+ dataSize: JSON.stringify(saveData).length
+ });
+ }
+ } else {
+ // LocalStorage fallback
+ if (debugLogger) await debugLogger.logStep('Using localStorage save');
+ const saveKey = `gso_save_slot_${this.saveSlotInfo.slot}`;
+ localStorage.setItem(saveKey, JSON.stringify(saveData));
+ console.log('[GAME ENGINE] Game saved to localStorage with key:', saveKey);
+ if (debugLogger) await debugLogger.logStep('Game saved to localStorage', {
+ saveKey,
+ dataSize: JSON.stringify(saveData).length
+ });
+ }
+ }
+
+ if (logger) await logger.info('Game saved successfully');
+
+ // Check if we should also save to server (multiplayer mode)
+ if (window.smartSaveManager && window.smartSaveManager.isMultiplayer) {
+ console.log('[GAME ENGINE] Also saving to server in multiplayer mode');
+ try {
+ await window.smartSaveManager.savePlayerData(saveData);
+ console.log('[GAME ENGINE] Game saved successfully to server');
+ } catch (error) {
+ console.error('[GAME ENGINE] Error saving to server:', error);
+ // Don't throw error - local save was successful
+ }
+ }
+
+ // if (debugLogger) await debugLogger.endStep('saveGame', {
+ // gameTime: this.gameTime,
+ // saveSlot: this.saveSlotInfo?.slot,
+ // success: true
+ // });
+
+ }
+ } catch (error) {
+ console.error('[GAME ENGINE] Save failed:', error);
+ if (logger) await logger.errorEvent(error, 'Game Save');
+ if (debugLogger) await debugLogger.errorEvent(error, 'Game Save');
+ throw error;
+ }
+ }
+
+ async loadPlayerData(playerData) {
+ console.log('[GAME ENGINE] Loading server player data into game');
+
+ try {
+ // Apply player stats
+ if (playerData.stats && this.systems && this.systems.player) {
+ console.log('[GAME ENGINE] Applying player stats:', playerData.stats);
+ this.systems.player.load(playerData.stats);
+ }
+
+ // Apply inventory
+ if (playerData.inventory && this.systems && this.systems.inventory) {
+ console.log('[GAME ENGINE] Applying player inventory:', playerData.inventory);
+ this.systems.inventory.load(playerData.inventory);
+ }
+
+ // Apply ship data
+ if (playerData.ship && this.systems && this.systems.ship) {
+ console.log('[GAME ENGINE] Applying player ship:', playerData.ship);
+ this.systems.ship.load(playerData.ship);
+ }
+
+ // Apply base data
+ if (playerData.base && this.systems && this.systems.base) {
+ console.log('[GAME ENGINE] Applying player base:', playerData.base);
+ this.systems.base.load(playerData.base);
+ }
+
+ // Apply quest data
+ if (playerData.quests && this.systems && this.systems.quests) {
+ console.log('[GAME ENGINE] Applying player quests:', playerData.quests);
+ this.systems.quests.load(playerData.quests);
+ }
+
+ // Apply skill data
+ if (playerData.skills && this.systems && this.systems.skills) {
+ console.log('[GAME ENGINE] Applying player skills:', playerData.skills);
+ this.systems.skills.load(playerData.skills);
+ }
+
+ // Apply idle system data
+ if (playerData.idleSystem && this.systems && this.systems.idleSystem) {
+ console.log('[GAME ENGINE] Applying player idle system:', playerData.idleSystem);
+ this.systems.idleSystem.load(playerData.idleSystem);
+ }
+
+ // Apply dungeon system data
+ if (playerData.dungeonSystem && this.systems && this.systems.dungeonSystem) {
+ console.log('[GAME ENGINE] Applying player dungeon system:', playerData.dungeonSystem);
+ this.systems.dungeonSystem.load(playerData.dungeonSystem);
+ }
+
+ // Apply crafting system data
+ if (playerData.craftingSystem && this.systems && this.systems.craftingSystem) {
+ console.log('[GAME ENGINE] Applying player crafting system:', playerData.craftingSystem);
+ this.systems.craftingSystem.load(playerData.craftingSystem);
+ }
+
+ // Apply game time
+ if (playerData.gameTime !== undefined) {
+ this.gameTime = playerData.gameTime;
+ console.log('[GAME ENGINE] Game time restored:', this.gameTime);
+ }
+
+ console.log('[GAME ENGINE] Server player data applied successfully');
+
+ // Show notification to user
+ if (this.showNotification) {
+ this.showNotification(`Welcome back! Level ${playerData.stats?.level || 1}`, 'success', 3000);
+ }
+
+ } catch (error) {
+ console.error('[GAME ENGINE] Error loading server player data:', error);
+ if (this.showNotification) {
+ this.showNotification('Failed to load server data!', 'error', 3000);
+ }
+ }
+ }
+
+ async load() {
+ const logger = window.logger;
+ const debugLogger = window.debugLogger;
+
+ console.log('[GAME ENGINE] Load method called');
+ if (logger) await logger.info('Loading game');
+ if (debugLogger) await debugLogger.startStep('loadGame');
+
+ try {
+ if (debugLogger) await debugLogger.logStep('Loading save data', {
+ saveSlot: this.saveSlotInfo?.slot,
+ useFileSystem: this.saveSlotInfo?.useFileSystem
+ });
+
+ let saveData;
+
+ // Use file system if available, otherwise localStorage
+ if (this.saveSlotInfo && this.saveSlotInfo.useFileSystem && window.electronAPI) {
+ console.log('[GAME ENGINE] Loading via Electron API');
+ if (debugLogger) await debugLogger.logStep('Loading via Electron API');
+
+ const result = await window.electronAPI.loadGame(this.saveSlotInfo.slot);
+ if (result.success) {
+ saveData = result.data;
+ console.log('[GAME ENGINE] Game loaded successfully via Electron API');
+ if (debugLogger) await debugLogger.logStep('Game loaded via Electron API', {
+ slot: this.saveSlotInfo.slot,
+ dataSize: JSON.stringify(saveData).length
+ });
+ } else {
+ console.error('[GAME ENGINE] Failed to load via Electron API:', result.error);
+ if (debugLogger) await debugLogger.errorEvent(new Error(result.error), 'Electron API Load');
+ throw new Error(result.error);
+ }
+ } else {
+ // LocalStorage fallback
+ console.log('[GAME ENGINE] Loading from localStorage');
+ if (debugLogger) await debugLogger.logStep('Loading from localStorage');
+
+ const saveKey = `gso_save_slot_${this.saveSlotInfo.slot}`;
+ const saveString = localStorage.getItem(saveKey);
+
+ if (saveString) {
+ try {
+ saveData = JSON.parse(saveString);
+ console.log('[GAME ENGINE] Game loaded from localStorage');
+ if (debugLogger) await debugLogger.logStep('Game loaded from localStorage', {
+ saveKey,
+ dataSize: saveString.length
+ });
+ } catch (parseError) {
+ console.error('[GAME ENGINE] Failed to parse save data:', parseError);
+ if (debugLogger) await debugLogger.errorEvent(parseError, 'Parse Save Data');
+ throw new Error('Corrupted save data');
+ }
+ } else {
+ console.warn('[GAME ENGINE] No save data found in localStorage');
+ if (debugLogger) await debugLogger.warn('No save data found in localStorage', { saveKey });
+ throw new Error('No save data found');
+ }
+ }
+
+ // if (debugLogger) await debugLogger.logStep('Applying save data to systems');
+
+ // Apply save data to systems
+ if (saveData.player && this.systems.player) {
+ console.log('[GAME ENGINE] Loading player data...');
+ this.systems.player.load(saveData.player);
+ if (debugLogger) await debugLogger.logStep('Player data loaded', {
+ level: saveData.player?.stats?.level,
+ experience: saveData.player?.stats?.experience
+ });
+ }
+
+ if (saveData.inventory && this.systems.inventory) {
+ console.log('[GAME ENGINE] Loading inventory data...');
+ this.systems.inventory.load(saveData.inventory);
+ if (debugLogger) await debugLogger.logStep('Inventory data loaded', {
+ itemCount: saveData.inventory.items?.length || 0
+ });
+ }
+
+ if (saveData.economy && this.systems.economy) {
+ console.log('[GAME ENGINE] Loading economy data...');
+ this.systems.economy.load(saveData.economy);
+ if (debugLogger) await debugLogger.logStep('Economy data loaded', {
+ credits: saveData.economy.credits,
+ gems: saveData.economy.gems
+ });
+ }
+
+ if (saveData.gameTime) {
+ this.gameTime = saveData.gameTime;
+ if (debugLogger) await debugLogger.logStep('Game time restored', {
+ gameTime: this.gameTime
+ });
+ }
+
+ // Emit load event
+ this.emit('gameLoaded', { saveData });
+ if (debugLogger) debugLogger.logStep('Game loaded event emitted');
+
+ if (logger) await logger.info('Game loaded successfully');
+ if (debugLogger) await debugLogger.endStep('loadGame', {
+ gameTime: this.gameTime,
+ saveSlot: this.saveSlotInfo?.slot,
+ success: true
+ });
+
+ } catch (error) {
+ console.error('[GAME ENGINE] Load failed:', error);
+ if (logger) await logger.errorEvent(error, 'Game Load');
+ if (debugLogger) await debugLogger.errorEvent(error, 'Game Load');
+ throw error;
+ }
+ }
+
+ async newGame() {
+ const logger = window.logger;
+ const debugLogger = window.debugLogger;
+
+ console.log('[GAME ENGINE] Starting new game initialization');
+ if (logger) await logger.info('Starting new game');
+ if (debugLogger) await debugLogger.startStep('newGame');
+
+ try {
+ // For new games, we need to properly initialize systems with default data
+ if (debugLogger) await debugLogger.logStep('Initializing systems for new game');
+
+ // Initialize inventory with starting items
+ if (this.systems.inventory) {
+ console.log('[GAME ENGINE] Initializing inventory with starting items');
+ if (debugLogger) await debugLogger.logStep('Initializing inventory with starting items');
+ await this.systems.inventory.initialize();
+ }
+
+ // Initialize DungeonSystem for new game
+ if (this.systems.dungeonSystem) {
+ console.log('[GAME ENGINE] Initializing DungeonSystem for new game...');
+ if (debugLogger) await debugLogger.logStep('Initializing DungeonSystem for new game');
+ try {
+ await this.systems.dungeonSystem.initialize();
+ console.log('[GAME ENGINE] DungeonSystem initialized successfully for new game');
+ if (debugLogger) await debugLogger.logStep('DungeonSystem initialized successfully for new game');
+ } catch (dungeonError) {
+ console.error('[GAME ENGINE] DungeonSystem initialization failed for new game:', dungeonError);
+ if (debugLogger) await debugLogger.errorEvent(dungeonError, 'DungeonSystem initialization failed for new game');
+ }
+ } else {
+ console.log('[GAME ENGINE] DungeonSystem not found in systems!');
+ }
+
+ if (this.systems.player) {
+ console.log('[GAME ENGINE] Resetting player to initial state');
+ if (debugLogger) await debugLogger.logStep('Resetting player to initial state');
+ this.systems.player.resetToLevel1();
+ console.log('[GAME ENGINE] Player reset completed');
+ if (debugLogger) await debugLogger.logStep('Player reset completed', {
+ level: this.systems.player.stats.level,
+ experience: this.systems.player.stats.experience,
+ playTime: this.systems.player.stats.playTime
+ });
+
+ console.log('[GAME ENGINE] Setting up new player');
+ if (debugLogger) await debugLogger.logStep('Setting up new player');
+ this.systems.player.setupNewPlayer();
+ console.log('[GAME ENGINE] Player setup completed');
+ if (debugLogger) await debugLogger.logStep('Player setup completed', {
+ level: this.systems.player.stats.level,
+ experience: this.systems.player.stats.experience
+ });
+ } else {
+ console.error('[GAME ENGINE] Player system not available');
+ if (debugLogger) await debugLogger.error('Player system not available');
+ }
+
+ if (debugLogger) await debugLogger.logStep('Resetting economy system');
+ console.log('[GAME ENGINE] Step 2: Resetting economy system');
+
+ if (this.systems.economy) {
+ console.log('[GAME ENGINE] Calling economy.reset()');
+ if (debugLogger) await debugLogger.logStep('Calling economy.reset()');
+ this.systems.economy.reset();
+ console.log('[GAME ENGINE] Economy reset completed');
+ if (debugLogger) await debugLogger.logStep('Economy reset completed', {
+ credits: this.systems.economy.credits,
+ gems: this.systems.economy.gems
+ });
+ } else {
+ console.error('[GAME ENGINE] Economy system not available');
+ if (debugLogger) await debugLogger.error('Economy system not available');
+ }
+
+ // Skip inventory reset - initialize() already handles proper setup
+ if (debugLogger) await debugLogger.logStep('Skipping inventory reset - already initialized');
+ console.log('[GAME ENGINE] Skipping inventory reset - already initialized with starting items');
+
+ if (this.systems.inventory) {
+ if (debugLogger) await debugLogger.logStep('Inventory already initialized', {
+ itemCount: this.systems.inventory.items.length
+ });
+ } else {
+ console.error('[GAME ENGINE] Inventory system not available');
+ if (debugLogger) await debugLogger.error('Inventory system not available');
+ }
+
+ if (debugLogger) await debugLogger.logStep('Resetting quest system');
+ console.log('[GAME ENGINE] Step 4: Resetting quest system');
+
+ if (this.systems.questSystem) {
+ console.log('[GAME ENGINE] Calling questSystem.reset()');
+ if (debugLogger) await debugLogger.logStep('Calling questSystem.reset()');
+ this.systems.questSystem.reset();
+ console.log('[GAME ENGINE] Quest system reset completed');
+ if (debugLogger) await debugLogger.logStep('Quest system reset completed');
+
+ // Activate the tutorial quest for new games
+ console.log('[GAME ENGINE] Activating tutorial quest for new game');
+ if (debugLogger) await debugLogger.logStep('Activating tutorial quest');
+ this.systems.questSystem.startQuest('tutorial_complete');
+ console.log('[GAME ENGINE] Tutorial quest activated');
+ if (debugLogger) await debugLogger.logStep('Tutorial quest activated');
+ } else {
+ console.error('[GAME ENGINE] Quest system not available');
+ if (debugLogger) await debugLogger.error('Quest system not available');
+ }
+
+ if (debugLogger) await debugLogger.logStep('Resetting game time');
+ console.log('[GAME ENGINE] Step 5: Resetting game time');
+ this.gameTime = 0;
+ console.log('[GAME ENGINE] Game time reset to 0');
+ if (debugLogger) await debugLogger.logStep('Game time reset', { gameTime: this.gameTime });
+
+ if (logger) await logger.info('New game initialized successfully');
+ if (debugLogger) await debugLogger.endStep('newGame', {
+ gameTime: this.gameTime,
+ playerLevel: this.systems.player?.stats.level,
+ success: true
+ });
+
+ } catch (error) {
+ console.error('[GAME ENGINE] Failed to initialize new game:', error);
+ if (logger) await logger.errorEvent(error, 'New Game Initialization');
+ if (debugLogger) await debugLogger.errorEvent(error, 'New Game Initialization');
+ throw error;
+ }
+ }
+
+ // Utility methods
+ formatNumber(num) {
+ if (num >= 1e9) return (num / 1e9).toFixed(2) + 'B';
+ if (num >= 1e6) return (num / 1e6).toFixed(2) + 'M';
+ if (num >= 1e3) return (num / 1e3).toFixed(2) + 'K';
+ return num.toString();
+ }
+
+ formatTime(seconds) {
+ const hours = Math.floor(seconds / 3600);
+ const minutes = Math.floor((seconds % 3600) / 60);
+ const secs = Math.floor(seconds % 60);
+
+ if (hours > 0) {
+ return `${hours}h ${minutes}m ${secs}s`;
+ } else if (minutes > 0) {
+ return `${minutes}m ${secs}s`;
+ } else {
+ return `${secs}s`;
+ }
+ }
+
+ toggleDebugConsole() {
+ const debugLogger = window.debugLogger;
+ // if (debugLogger) debugLogger.logStep('Toggle debug console requested');
+
+ // Implementation would go here
+ // console.log('[GAME ENGINE] Debug console toggle requested');
+ }
+
+ getPerformanceStats() {
+ const debugLogger = window.debugLogger;
+
+ const stats = {
+ gameTime: this.gameTime,
+ isRunning: this.isRunning,
+ lastUpdate: this.lastUpdate,
+ memory: null
+ };
+
+ // Add memory info if available
+ if (window.performance && window.performance.memory) {
+ stats.memory = {
+ used: window.performance.memory.usedJSHeapSize,
+ total: window.performance.memory.totalJSHeapSize,
+ limit: window.performance.memory.jsHeapSizeLimit
+ };
+ }
+
+ // if (debugLogger) debugLogger.logStep('Performance stats requested', stats);
+
+ return stats;
+ }
+
+ // Simple loadPlayerData method for server data integration
+ async loadPlayerData(playerData) {
+ console.log('[GAME ENGINE] Loading server player data');
+
+ try {
+ // Apply basic player stats
+ if (playerData.stats && this.systems && this.systems.player) {
+ this.systems.player.load(playerData.stats);
+ console.log('[GAME ENGINE] Applied player stats:', playerData.stats);
+ }
+
+ // Apply inventory
+ if (playerData.inventory && this.systems && this.systems.inventory) {
+ this.systems.inventory.load(playerData.inventory);
+ console.log('[GAME ENGINE] Applied inventory');
+ }
+
+ // Apply ship data
+ if (playerData.ship && this.systems && this.systems.ship) {
+ this.systems.ship.load(playerData.ship);
+ console.log('[GAME ENGINE] Applied ship data');
+ }
+
+ // Apply base data
+ if (playerData.base && this.systems && this.systems.base) {
+ this.systems.base.load(playerData.base);
+ console.log('[GAME ENGINE] Applied base data');
+ }
+
+ // Show notification
+ if (this.showNotification) {
+ this.showNotification(`Welcome back! Level ${playerData.stats?.level || 1}`, 'success', 3000);
+ }
+
+ console.log('[GAME ENGINE] Server player data loaded successfully');
+
+ } catch (error) {
+ console.error('[GAME ENGINE] Error loading server player data:', error);
+ if (this.showNotification) {
+ this.showNotification('Failed to load server data!', 'error', 3000);
+ }
+ }
+ }
+}
+
+// Global game instance
+let game = null;
+
+// Export GameEngine to global scope
+if (typeof window !== 'undefined') {
+ window.GameEngine = GameEngine;
+}
diff --git a/Client-Server/js/core/Inventory.js b/Client-Server/js/core/Inventory.js
new file mode 100644
index 0000000..7804cfa
--- /dev/null
+++ b/Client-Server/js/core/Inventory.js
@@ -0,0 +1,1134 @@
+/**
+ * Galaxy Strike Online - Inventory System
+ * Manages player items, equipment, and storage
+ */
+
+class Inventory {
+ constructor(gameEngine) {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('Inventory.constructor', {
+ gameEngineProvided: !!gameEngine
+ });
+
+ this.game = gameEngine;
+
+ // Inventory configuration
+ this.maxSlots = 30;
+ this.baseMaxSlots = 30; // Base slots without starbase bonuses
+ this.maxStack = 999;
+
+ // Starbase inventory bonuses
+ this.starbaseBonusSlots = 0;
+ this.totalMaxSlots = this.baseMaxSlots + this.starbaseBonusSlots;
+
+ // Inventory data - ensure items is always an array
+ this.items = [];
+ this.equipment = {
+ weapon: null,
+ armor: null,
+ engine: null,
+ shield: null,
+ accessory: null
+ };
+
+ // Item categories
+ this.categories = {
+ weapon: 'Weapons',
+ armor: 'Armor',
+ engine: 'Engines',
+ shield: 'Shields',
+ accessory: 'Accessories',
+ consumable: 'Consumables',
+ material: 'Materials',
+ cosmetic: 'Cosmetics'
+ };
+
+ // Item rarities
+ this.rarities = {
+ common: { name: 'Common', color: '#888888', multiplier: 1 },
+ uncommon: { name: 'Uncommon', color: '#00ff00', multiplier: 1.2 },
+ rare: { name: 'Rare', color: '#0088ff', multiplier: 1.5 },
+ epic: { name: 'Epic', color: '#8833ff', multiplier: 2 },
+ legendary: { name: 'Legendary', color: '#ff8800', multiplier: 3 }
+ };
+
+ if (debugLogger) debugLogger.endStep('Inventory.constructor', {
+ maxSlots: this.maxSlots,
+ baseMaxSlots: this.baseMaxSlots,
+ maxStack: this.maxStack,
+ starbaseBonusSlots: this.starbaseBonusSlots,
+ totalMaxSlots: this.totalMaxSlots,
+ initialItemCount: this.items.length,
+ equipmentSlots: Object.keys(this.equipment).length,
+ categoriesCount: Object.keys(this.categories).length,
+ raritiesCount: Object.keys(this.rarities).length
+ });
+ }
+
+ async initialize() {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('Inventory.initialize', {
+ currentItemCount: this.items.length
+ });
+
+ // Initialize with starting items
+ if (this.items.length === 0) {
+ if (debugLogger) debugLogger.logStep('Adding starting items to empty inventory');
+ this.addStartingItems();
+ } else {
+ if (debugLogger) debugLogger.logStep('Inventory already has items, skipping starting items');
+ }
+
+ if (debugLogger) debugLogger.endStep('Inventory.initialize', {
+ finalItemCount: this.items.length,
+ startingItemsAdded: this.items.length > 0
+ });
+ }
+
+ addStartingItems() {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('Inventory.addStartingItems', {
+ currentItemCount: this.items.length,
+ maxSlots: this.maxSlots
+ });
+
+ const startingItems = [
+ {
+ id: 'starter_blaster_common',
+ name: 'Common Blaster',
+ type: 'weapon',
+ rarity: 'common',
+ quantity: 1,
+ stats: { attack: 5, criticalChance: 0.02 },
+ description: 'A reliable basic blaster for new pilots',
+ equipable: true,
+ slot: 'weapon'
+ },
+ {
+ id: 'basic_armor_common',
+ name: 'Basic Armor',
+ type: 'armor',
+ rarity: 'common',
+ quantity: 1,
+ stats: { defense: 3 },
+ description: 'Light armor providing basic protection',
+ equipable: true,
+ slot: 'armor'
+ }
+ ];
+
+ if (debugLogger) debugLogger.logStep('Adding starting items', {
+ startingItemCount: startingItems.length,
+ startingItems: startingItems.map(item => ({
+ id: item.id,
+ name: item.name,
+ type: item.type,
+ quantity: item.quantity
+ }))
+ });
+
+ startingItems.forEach(item => {
+ console.log(`[DEBUG] Adding starting item: ${item.name}`);
+ const result = this.addItem(item);
+ console.log(`[DEBUG] Starting item add result: ${result}, inventory size: ${this.items.length}`);
+ });
+
+ // Equip starter items
+ console.log('[INVENTORY] Equipping starter items');
+ if (debugLogger) debugLogger.logStep('Equipping starter items');
+
+ // Equip starter blaster
+ const blasterItem = this.items.find(item => item.id === 'starter_blaster');
+ if (blasterItem) {
+ console.log('[INVENTORY] Equipping starter blaster');
+ this.equipItem(blasterItem.id);
+ }
+
+ // Equip basic armor
+ const armorItem = this.items.find(item => item.id === 'basic_armor');
+ if (armorItem) {
+ console.log('[INVENTORY] Equipping basic armor');
+ this.equipItem(armorItem.id);
+ }
+
+ // Auto-stack starting items
+ if (debugLogger) debugLogger.logStep('Auto-stacking starting items');
+ this.autoStackItems();
+
+ if (debugLogger) debugLogger.endStep('Inventory.addStartingItems', {
+ finalItemCount: this.items.length,
+ itemsAdded: startingItems.length
+ });
+ }
+
+ // Item management
+ addItem(itemData, quantity = 1) {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('Inventory.addItem', {
+ itemData: itemData,
+ quantity: quantity,
+ currentItemCount: this.items.length,
+ maxSlots: this.maxSlots,
+ maxStack: this.maxStack
+ });
+
+ if (!itemData || !itemData.id) {
+ console.error('Invalid item data:', itemData);
+ if (debugLogger) debugLogger.errorEvent('Inventory.addItem', new Error('Invalid item data'), {
+ itemData: itemData,
+ quantity: quantity
+ });
+ return false;
+ }
+
+ const item = { ...itemData, quantity };
+
+ // Auto-stack: Check if exact same item already exists (same type, rarity, and stats)
+ const existingItem = this.items.find(i =>
+ i.id === item.id &&
+ i.type === item.type &&
+ i.rarity === item.rarity &&
+ JSON.stringify(i.stats) === JSON.stringify(item.stats)
+ );
+
+ if (existingItem) {
+ if (debugLogger) debugLogger.logStep('Found existing item for stacking', {
+ existingItemId: existingItem.id,
+ existingItemName: existingItem.name,
+ currentQuantity: existingItem.quantity,
+ addingQuantity: quantity,
+ maxStack: this.maxStack
+ });
+
+ // Stack items up to max stack
+ const totalQuantity = existingItem.quantity + quantity;
+ if (totalQuantity <= this.maxStack) {
+ existingItem.quantity = totalQuantity;
+ this.game.showNotification(`${item.name} +${quantity} (Stacked)`, 'success', 2000);
+
+ if (debugLogger) debugLogger.logStep('Item stacked successfully', {
+ itemId: existingItem.id,
+ oldQuantity: existingItem.quantity - quantity,
+ newQuantity: existingItem.quantity,
+ added: quantity
+ });
+ } else {
+ // Add to existing stack and create new item if needed
+ const remainingQuantity = totalQuantity - this.maxStack;
+ existingItem.quantity = this.maxStack;
+
+ if (debugLogger) debugLogger.logStep('Stack overflow, creating new item', {
+ itemId: existingItem.id,
+ maxStackReached: this.maxStack,
+ remainingQuantity: remainingQuantity,
+ currentSlots: this.items.length,
+ maxSlots: this.totalMaxSlots
+ });
+
+ // Try to add remaining quantity as new item
+ if (this.items.length < this.totalMaxSlots) {
+ const newItem = { ...itemData, quantity: remainingQuantity };
+ this.items.push(newItem);
+ this.game.showNotification(`${item.name} +${quantity} (Stack: ${this.maxStack}, New: ${remainingQuantity})`, 'success', 2000);
+
+ if (debugLogger) debugLogger.logStep('New item created for overflow', {
+ newItemId: newItem.id,
+ newItemQuantity: remainingQuantity,
+ totalSlotsUsed: this.items.length
+ });
+ } else {
+ this.game.showNotification(`${item.name} +${quantity} (Stack full: ${this.maxStack})`, 'warning', 3000);
+
+ if (debugLogger) debugLogger.logStep('Inventory full, overflow lost', {
+ lostQuantity: remainingQuantity,
+ maxSlots: this.totalMaxSlots
+ });
+ }
+ }
+ } else {
+ // Find empty slot
+ if (this.items.length >= this.totalMaxSlots) {
+ this.game.showNotification('Inventory is full!', 'error', 3000);
+
+ if (debugLogger) debugLogger.endStep('Inventory.addItem', {
+ success: false,
+ reason: 'Inventory full',
+ itemId: item.id,
+ itemName: item.name,
+ quantity: quantity,
+ currentSlots: this.items.length,
+ maxSlots: this.totalMaxSlots
+ });
+ return false;
+ }
+
+ this.items.push(item);
+ this.game.showNotification(`Acquired ${item.name}`, 'success', 2000);
+
+ if (debugLogger) debugLogger.logStep('New item added to inventory', {
+ itemId: item.id,
+ itemName: item.name,
+ itemType: item.type,
+ itemRarity: item.rarity,
+ quantity: quantity,
+ slotUsed: this.items.length - 1
+ });
+ }
+
+ this.autoStackItems(); // Reorganize inventory
+
+ if (debugLogger) debugLogger.endStep('Inventory.addItem', {
+ success: true,
+ itemId: item.id,
+ itemName: item.name,
+ quantity: quantity,
+ finalItemCount: this.items.length,
+ slotsUsed: this.items.length,
+ maxSlots: this.maxSlots
+ });
+
+ return true;
+ }
+
+ // Auto-stack and organize inventory
+ autoStackItems() {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('Inventory.autoStackItems', {
+ currentItemCount: this.items.length,
+ maxSlots: this.maxSlots,
+ maxStack: this.maxStack
+ });
+
+ const stackedItems = {};
+ const originalItemCount = this.items.length;
+
+ // Group items by exact ID, type, rarity, and stats
+ this.items.forEach(item => {
+ const stackKey = `${item.id}_${item.type}_${item.rarity}_${JSON.stringify(item.stats || {})}`;
+ if (!stackedItems[stackKey]) {
+ stackedItems[stackKey] = {
+ ...item,
+ quantity: 0
+ };
+ }
+ stackedItems[stackKey].quantity += item.quantity;
+ });
+
+ if (debugLogger) debugLogger.logStep('Items grouped for stacking', {
+ originalItemCount: originalItemCount,
+ stackGroupsCreated: Object.keys(stackedItems).length,
+ stackGroups: Object.entries(stackedItems).map(([key, item]) => ({
+ stackKey: key,
+ itemId: item.id,
+ itemName: item.name,
+ totalQuantity: item.quantity
+ }))
+ });
+
+ // Convert back to array and limit by max stack
+ const newItems = [];
+ const stackedValues = Object.values(stackedItems);
+
+ for (const item of stackedValues) {
+ if (newItems.length >= this.totalMaxSlots) break;
+
+ while (item.quantity > 0 && newItems.length < this.totalMaxSlots) {
+ const stackQuantity = Math.min(item.quantity, this.maxStack);
+ newItems.push({
+ ...item,
+ quantity: stackQuantity
+ });
+ item.quantity -= stackQuantity;
+ }
+ }
+
+ this.items = newItems;
+
+ if (debugLogger) debugLogger.endStep('Inventory.autoStackItems', {
+ originalItemCount: originalItemCount,
+ newItemCount: this.items.length,
+ stackGroupsProcessed: stackedValues.length,
+ slotsUsed: this.items.length,
+ maxSlots: this.maxSlots
+ });
+ }
+
+ removeItem(itemId, quantity = 1) {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('Inventory.removeItem', {
+ itemId: itemId,
+ quantity: quantity,
+ currentItemCount: this.items.length
+ });
+
+ const itemIndex = this.items.findIndex(item => item.id === itemId);
+ if (itemIndex === -1) {
+ if (debugLogger) debugLogger.endStep('Inventory.removeItem', {
+ success: false,
+ reason: 'Item not found',
+ itemId: itemId,
+ quantity: quantity
+ });
+ return false;
+ }
+
+ const item = this.items[itemIndex];
+ const oldQuantity = item.quantity;
+
+ if (item.quantity > quantity) {
+ item.quantity -= quantity;
+
+ if (debugLogger) debugLogger.logStep('Item quantity reduced', {
+ itemId: itemId,
+ itemName: item.name,
+ oldQuantity: oldQuantity,
+ newQuantity: item.quantity,
+ removed: quantity
+ });
+ } else {
+ this.items.splice(itemIndex, 1);
+
+ if (debugLogger) debugLogger.logStep('Item completely removed', {
+ itemId: itemId,
+ itemName: item.name,
+ oldQuantity: oldQuantity,
+ removed: oldQuantity,
+ itemIndex: itemIndex
+ });
+ }
+
+ if (debugLogger) debugLogger.endStep('Inventory.removeItem', {
+ success: true,
+ itemId: itemId,
+ itemName: item.name,
+ quantityRemoved: quantity,
+ finalItemCount: this.items.length,
+ itemStillExists: this.items.find(i => i.id === itemId) !== undefined
+ });
+
+ return true;
+ }
+
+ hasItem(itemId, quantity = 1) {
+ const item = this.items.find(item => item.id === itemId);
+ return item && item.quantity >= quantity;
+ }
+
+ getItemCount(itemId) {
+ const item = this.items.find(item => item.id === itemId);
+ return item ? item.quantity : 0;
+ }
+
+ // Equipment management
+ equipItem(itemId) {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('Inventory.equipItem', {
+ itemId: itemId,
+ currentItemCount: this.items.length,
+ currentEquipment: this.equipment
+ });
+
+ console.log('Attempting to equip item:', itemId);
+ const item = this.items.find(item => item.id === itemId);
+ console.log('Found item:', item);
+
+ if (!item) {
+ console.log('Item not found in inventory');
+ this.game.showNotification('Item not found', 'error', 3000);
+
+ if (debugLogger) debugLogger.endStep('Inventory.equipItem', {
+ success: false,
+ reason: 'Item not found',
+ itemId: itemId
+ });
+ return false;
+ }
+
+ if (!item.equipable) {
+ console.log('Item is not equipable:', item);
+ this.game.showNotification('This item cannot be equipped', 'error', 3000);
+
+ if (debugLogger) debugLogger.endStep('Inventory.equipItem', {
+ success: false,
+ reason: 'Item not equipable',
+ itemId: itemId,
+ itemName: item.name,
+ itemType: item.type
+ });
+ return false;
+ }
+
+ const slot = item.type;
+ console.log('Equipping to slot:', slot);
+ const currentItem = this.equipment[slot];
+
+ if (debugLogger) debugLogger.logStep('Equipment slot validation', {
+ itemId: itemId,
+ itemName: item.name,
+ itemType: item.type,
+ targetSlot: slot,
+ currentItemInSlot: currentItem ? currentItem.name : null,
+ itemStats: item.stats
+ });
+
+ // Unequip current item if exists
+ if (currentItem) {
+ console.log('Unequipping current item:', currentItem);
+
+ if (debugLogger) debugLogger.logStep('Unequipping current item', {
+ currentItemName: currentItem.name,
+ currentItemStats: currentItem.stats,
+ returningToInventory: true
+ });
+
+ this.addItem(currentItem, 1);
+ }
+
+ // Equip new item
+ this.equipment[slot] = item;
+ this.removeItem(itemId, 1);
+
+ // Apply item stats to player
+ this.applyEquipmentStats();
+
+ // Update UI
+ this.updateUI();
+
+ console.log('Successfully equipped:', item.name);
+ this.game.showNotification(`Equipped ${item.name}`, 'success', 3000);
+
+ if (debugLogger) debugLogger.endStep('Inventory.equipItem', {
+ success: true,
+ itemId: itemId,
+ itemName: item.name,
+ slot: slot,
+ previousItem: currentItem ? currentItem.name : null,
+ newEquipmentState: this.equipment,
+ finalItemCount: this.items.length
+ });
+
+ return true;
+ }
+
+ unequipItem(slot) {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('Inventory.unequipItem', {
+ slot: slot,
+ currentEquipment: this.equipment,
+ currentItemCount: this.items.length,
+ maxSlots: this.maxSlots
+ });
+
+ console.log('Attempting to unequip slot:', slot);
+ const item = this.equipment[slot];
+ if (!item) {
+ console.log('No item in slot:', slot);
+
+ if (debugLogger) debugLogger.endStep('Inventory.unequipItem', {
+ success: false,
+ reason: 'No item in slot',
+ slot: slot
+ });
+ return false;
+ }
+
+ // Check inventory space
+ if (this.items.length >= this.totalMaxSlots) {
+ console.log('Inventory is full, cannot unequip');
+ this.game.showNotification('Inventory is full!', 'error', 3000);
+
+ if (debugLogger) debugLogger.endStep('Inventory.unequipItem', {
+ success: false,
+ reason: 'Inventory full',
+ slot: slot,
+ itemName: item.name,
+ currentItemCount: this.items.length,
+ maxSlots: this.maxSlots
+ });
+ return false;
+ }
+
+ if (debugLogger) debugLogger.logStep('Unequipping item', {
+ slot: slot,
+ itemName: item.name,
+ itemStats: item.stats,
+ returningToInventory: true,
+ inventorySpaceAvailable: this.items.length < this.maxSlots
+ });
+
+ // Unequip item
+ this.equipment[slot] = null;
+ this.addItem(item, 1);
+
+ // Remove item stats from player
+ this.applyEquipmentStats();
+
+ // Update UI
+ this.updateUI();
+
+ console.log('Successfully unequipped:', item.name);
+ this.game.showNotification(`Unequipped ${item.name}`, 'info', 3000);
+
+ if (debugLogger) debugLogger.endStep('Inventory.unequipItem', {
+ success: true,
+ slot: slot,
+ itemName: item.name,
+ newEquipmentState: this.equipment,
+ finalItemCount: this.items.length
+ });
+
+ return true;
+ }
+
+ applyEquipmentStats() {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('Inventory.applyEquipmentStats', {
+ currentEquipment: this.equipment,
+ equippedItemsCount: Object.values(this.equipment).filter(item => item !== null).length
+ });
+
+ // Reset player stats to base
+ const player = this.game.systems.player;
+ const oldPlayerAttributes = { ...player.attributes };
+
+ if (debugLogger) debugLogger.logStep('Applying equipment bonuses', {
+ oldPlayerAttributes: oldPlayerAttributes,
+ equipmentToApply: Object.entries(this.equipment).filter(([slot, item]) => item !== null)
+ });
+
+ // Apply equipment bonuses
+ for (const [slot, item] of Object.entries(this.equipment)) {
+ if (item && item.stats) {
+ for (const [stat, value] of Object.entries(item.stats)) {
+ if (player.attributes[stat] !== undefined) {
+ const oldValue = player.attributes[stat];
+ player.attributes[stat] += value;
+
+ if (debugLogger) debugLogger.logStep('Stat bonus applied', {
+ slot: slot,
+ itemName: item.name,
+ stat: stat,
+ value: value,
+ oldValue: oldValue,
+ newValue: player.attributes[stat]
+ });
+ }
+ }
+ }
+ }
+
+ player.updateUI();
+
+ if (debugLogger) debugLogger.endStep('Inventory.applyEquipmentStats', {
+ oldPlayerAttributes: oldPlayerAttributes,
+ newPlayerAttributes: player.attributes,
+ statChanges: Object.entries(player.attributes).map(([stat, newValue]) => ({
+ stat: stat,
+ oldValue: oldPlayerAttributes[stat],
+ newValue: newValue,
+ change: newValue - oldPlayerAttributes[stat]
+ })).filter(change => change.change !== 0)
+ });
+ }
+
+ // Consumable usage
+ useItem(itemId) {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('Inventory.useItem', {
+ itemId: itemId,
+ currentItemCount: this.items.length
+ });
+
+ const item = this.items.find(item => item.id === itemId);
+ if (!item || !item.consumable) {
+ this.game.showNotification('This item cannot be used', 'error', 3000);
+
+ if (debugLogger) debugLogger.endStep('Inventory.useItem', {
+ success: false,
+ reason: !item ? 'Item not found' : 'Item not consumable',
+ itemId: itemId
+ });
+ return false;
+ }
+
+ if (debugLogger) debugLogger.logStep('Using consumable item', {
+ itemId: itemId,
+ itemName: item.name,
+ itemEffect: item.effect,
+ oldQuantity: item.quantity
+ });
+
+ const player = this.game.systems.player;
+ const oldPlayerHealth = player.attributes.health;
+ const oldPlayerEnergy = player.attributes.energy;
+
+ // Apply item effects
+ if (item.effect) {
+ if (item.effect.heal) {
+ player.heal(item.effect.heal);
+ this.game.showNotification(`Restored ${item.effect.heal} health`, 'success', 2000);
+
+ if (debugLogger) debugLogger.logStep('Healing effect applied', {
+ healAmount: item.effect.heal,
+ oldHealth: oldPlayerHealth,
+ newHealth: player.attributes.health
+ });
+ }
+
+ if (item.effect.energy) {
+ player.restoreEnergy(item.effect.energy);
+ this.game.showNotification(`Restored ${item.effect.energy} energy`, 'success', 2000);
+
+ if (debugLogger) debugLogger.logStep('Energy effect applied', {
+ energyAmount: item.effect.energy,
+ oldEnergy: oldPlayerEnergy,
+ newEnergy: player.attributes.energy
+ });
+ }
+
+ if (item.effect.buff) {
+ // Apply temporary buffs (would need buff system)
+ this.game.showNotification(`Buff applied: ${item.effect.buff}`, 'success', 2000);
+
+ if (debugLogger) debugLogger.logStep('Buff effect applied', {
+ buffType: item.effect.buff
+ });
+ }
+ }
+
+ // Remove consumed item
+ this.removeItem(itemId, 1);
+
+ if (debugLogger) debugLogger.endStep('Inventory.useItem', {
+ success: true,
+ itemId: itemId,
+ itemName: item.name,
+ effectsApplied: item.effect,
+ finalItemCount: this.items.length
+ });
+
+ return true;
+ }
+
+ // Item generation
+ generateItem(type, rarity = 'common', level = 1) {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('Inventory.generateItem', {
+ type: type,
+ rarity: rarity,
+ level: level
+ });
+
+ const itemTemplates = {
+ weapon: {
+ common: [
+ { name: 'Basic Blaster', stats: { attack: 5 } },
+ { name: 'Laser Rifle', stats: { attack: 6 } }
+ ],
+ uncommon: [
+ { name: 'Plasma Cannon', stats: { attack: 8, criticalChance: 0.02 } },
+ { name: 'Ion Blaster', stats: { attack: 7, speed: 2 } }
+ ],
+ rare: [
+ { name: 'Quantum Rifle', stats: { attack: 12, criticalChance: 0.05 } },
+ { name: 'Fusion Cannon', stats: { attack: 15, criticalDamage: 0.2 } }
+ ]
+ },
+ armor: {
+ common: [
+ { name: 'Basic Plating', stats: { defense: 3, maxHealth: 10 } },
+ { name: 'Light Armor', stats: { defense: 4, speed: -1 } }
+ ],
+ uncommon: [
+ { name: 'Reinforced Plating', stats: { defense: 6, maxHealth: 20 } },
+ { name: 'Energy Shield', stats: { defense: 5, maxEnergy: 10 } }
+ ],
+ rare: [
+ { name: 'Titanium Armor', stats: { defense: 10, maxHealth: 40 } },
+ { name: 'Phase Shield', stats: { defense: 8, criticalChance: -0.05 } }
+ ]
+ }
+ };
+
+ const templates = itemTemplates[type]?.[rarity] || itemTemplates.weapon.common;
+ const template = templates[Math.floor(Math.random() * templates.length)];
+
+ const rarityData = this.rarities[rarity];
+ const levelMultiplier = 1 + (level - 1) * 0.1;
+
+ const generatedItem = {
+ id: `${type}_${rarity}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
+ name: template.name,
+ type: type,
+ rarity: rarity,
+ quantity: 1,
+ stats: Object.fromEntries(
+ Object.entries(template.stats).map(([stat, value]) => [
+ stat,
+ Math.floor(value * rarityData.multiplier * levelMultiplier)
+ ])
+ ),
+ description: `Level ${level} ${rarityData.name} ${type}`,
+ equipable: true,
+ value: Math.floor(50 * rarityData.multiplier * levelMultiplier)
+ };
+
+ if (debugLogger) debugLogger.endStep('Inventory.generateItem', {
+ type: type,
+ rarity: rarity,
+ level: level,
+ templateUsed: template.name,
+ rarityMultiplier: rarityData.multiplier,
+ levelMultiplier: levelMultiplier,
+ generatedItem: generatedItem,
+ finalStats: generatedItem.stats,
+ itemValue: generatedItem.value
+ });
+
+ return generatedItem;
+ }
+
+ // UI updates
+ clear() {
+ const debugLogger = window.debugLogger;
+ const oldState = {
+ itemCount: this.items.length,
+ maxSlots: this.maxSlots,
+ cargo: this.cargo ? this.cargo.length : 0,
+ maxCargo: this.maxCargo
+ };
+
+ if (debugLogger) debugLogger.startStep('Inventory.clear', {
+ oldState: oldState
+ });
+
+ this.items = [];
+ this.maxSlots = 30;
+ this.cargo = [];
+ this.maxCargo = 100;
+ this.updateUI();
+
+ if (debugLogger) debugLogger.endStep('Inventory.clear', {
+ oldState: oldState,
+ newState: {
+ itemCount: this.items.length,
+ maxSlots: this.maxSlots,
+ cargo: this.cargo.length,
+ maxCargo: this.maxCargo
+ }
+ });
+ }
+
+ reset() {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('Inventory.reset');
+
+ this.clear();
+
+ if (debugLogger) debugLogger.endStep('Inventory.reset', {
+ cleared: true
+ });
+ }
+
+ updateUI() {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('Inventory.updateUI', {
+ itemCount: this.items.length,
+ equipmentCount: Object.values(this.equipment).filter(item => item !== null).length
+ });
+
+ this.updateInventoryGrid();
+ this.updateEquipmentDisplay();
+
+ if (debugLogger) debugLogger.endStep('Inventory.updateUI', {
+ inventoryGridUpdated: true,
+ equipmentDisplayUpdated: true
+ });
+ }
+
+ updateInventoryGrid() {
+ const grid = document.getElementById('inventoryGrid');
+ if (!grid) return;
+
+ grid.innerHTML = '';
+
+ // Create inventory slots using total max slots (base + starbase bonuses)
+ for (let i = 0; i < this.totalMaxSlots; i++) {
+ const slot = document.createElement('div');
+ slot.className = 'inventory-slot';
+
+ // Add special styling for starbase bonus slots
+ if (i >= this.baseMaxSlots) {
+ slot.classList.add('starbase-bonus-slot');
+ }
+
+ const item = this.items[i];
+
+ if (item) {
+ const iconHtml = this.getItemIconHtml(item.type, '24px');
+ const quantityBadge = item.quantity > 1 ? `${item.quantity}
` : '';
+
+ slot.innerHTML = `
+
+
+ ${iconHtml}
+
+
+
${item.name}
+
+ ${this.rarities[item.rarity].name}
+
+
+ ${quantityBadge}
+
+ `;
+
+ // Add click handlers
+ const itemCard = slot.querySelector('.item-card');
+ itemCard.addEventListener('click', () => this.showItemDetails(item));
+
+ if (item.equipable) {
+ itemCard.addEventListener('contextmenu', (e) => {
+ e.preventDefault();
+ this.equipItem(item.id);
+ });
+ }
+
+ if (item.consumable) {
+ itemCard.addEventListener('dblclick', () => this.useItem(item.id));
+ }
+ } else {
+ slot.innerHTML = '
';
+ }
+
+ grid.appendChild(slot);
+ }
+ }
+
+ updateEquipmentDisplay() {
+ // Update equipment slots in UI
+ for (const [slot, item] of Object.entries(this.equipment)) {
+ const slotElement = document.getElementById(`equip-${slot}`);
+ if (slotElement) {
+ if (item) {
+ const iconHtml = this.getItemIconHtml(item.type, '24px');
+ slotElement.innerHTML = `
+
+ ${iconHtml}
+
${item.name}
+
+ `;
+
+ // Add click handler to unequip
+ slotElement.onclick = () => this.unequipItem(slot);
+ } else {
+ slotElement.innerHTML = `Empty
`;
+ slotElement.onclick = null;
+ }
+ }
+ }
+ }
+
+ showItemDetails(item) {
+ const detailsElement = document.getElementById('itemDetails');
+ if (!detailsElement) return;
+
+ const rarityData = this.rarities[item.rarity];
+ const statsHtml = item.stats ?
+ Object.entries(item.stats)
+ .map(([stat, value]) => `${stat}: +${value}
`)
+ .join('') : '';
+
+ detailsElement.innerHTML = `
+ ${item.name}
+ ${item.description}
+ Type: ${this.categories[item.type]}
+ Rarity: ${rarityData.name}
+ ${item.quantity > 1 ? `Quantity: ${item.quantity}
` : ''}
+ ${statsHtml ? `${statsHtml}
` : ''}
+
+ ${item.equipable ? `Equip ` : ''}
+ ${item.consumable ? `Use ` : ''}
+
+ `;
+ }
+
+ getItemIcon(type) {
+ const icons = {
+ weapon: 'fa-sword',
+ armor: 'fa-shield-alt',
+ engine: 'fa-rocket',
+ shield: 'fa-shield-virus',
+ accessory: 'fa-ring',
+ consumable: 'fa-flask',
+ material: 'fa-cube',
+ cosmetic: 'fa-star'
+ };
+
+ const iconClass = icons[type] || 'fa-cube';
+
+ // Use texture manager for icon fallback if available
+ if (this.game.systems.textureManager) {
+ return this.game.systems.textureManager.getIcon(iconClass);
+ }
+
+ return iconClass;
+ }
+
+ getItemIconHtml(type, size = '32px') {
+ // Use texture manager for icon HTML if available
+ if (this.game.systems.textureManager && typeof this.game.systems.textureManager.getItemIconElement === 'function') {
+ return this.game.systems.textureManager.getItemIconElement(this.getItemIcon(type), size);
+ }
+
+ // Fallback to FontAwesome
+ const iconClass = this.getItemIcon(type);
+ return ` `;
+ }
+
+ // Starbase inventory integration methods
+ updateStarbaseBonusSlots(bonusSlots) {
+ this.starbaseBonusSlots = bonusSlots;
+ this.totalMaxSlots = this.baseMaxSlots + this.starbaseBonusSlots;
+}
+
+ getInventoryInfo() {
+ return {
+ currentSlots: this.items.length,
+ baseMaxSlots: this.baseMaxSlots,
+ starbaseBonusSlots: this.starbaseBonusSlots,
+ totalMaxSlots: this.totalMaxSlots,
+ availableSlots: this.totalMaxSlots - this.items.length
+ };
+ }
+
+ // Save/Load
+ save() {
+ const debugLogger = window.debugLogger;
+
+ // if (debugLogger) debugLogger.startStep('Inventory.save', {
+ // itemCount: this.items.length,
+ // equipmentCount: Object.values(this.equipment).filter(item => item !== null).length,
+ // baseMaxSlots: this.baseMaxSlots,
+ // starbaseBonusSlots: this.starbaseBonusSlots
+ // });
+
+ const saveData = {
+ items: this.items,
+ equipment: this.equipment,
+ baseMaxSlots: this.baseMaxSlots,
+ starbaseBonusSlots: this.starbaseBonusSlots
+ };
+
+ // if (debugLogger) debugLogger.endStep('Inventory.save', {
+ // saveDataSize: JSON.stringify(saveData).length,
+ // itemsSaved: this.items.length,
+ // equipmentSaved: Object.keys(this.equipment).length,
+ // saveData: saveData
+ // });
+
+ return saveData;
+ }
+
+ load(data) {
+ const debugLogger = window.debugLogger;
+ const oldState = {
+ itemCount: this.items.length,
+ equipmentCount: Object.values(this.equipment).filter(item => item !== null).length,
+ baseMaxSlots: this.baseMaxSlots,
+ starbaseBonusSlots: this.starbaseBonusSlots
+ };
+
+ if (debugLogger) debugLogger.startStep('Inventory.load', {
+ oldState: oldState,
+ loadData: data
+ });
+
+ try {
+ if (data.items) {
+ // Ensure items is always an array
+ this.items = Array.isArray(data.items) ? data.items : [];
+
+ if (debugLogger) debugLogger.logStep('Loading items', {
+ itemsLoaded: this.items.length,
+ itemsArray: Array.isArray(data.items),
+ autoStacking: true
+ });
+
+ this.autoStackItems();
+ }
+
+ if (data.equipment) {
+ this.equipment = data.equipment;
+
+ if (debugLogger) debugLogger.logStep('Loading equipment', {
+ equipmentLoaded: Object.keys(data.equipment).length,
+ equipmentState: data.equipment
+ });
+ }
+
+ // Load starbase bonus slots
+ if (data.baseMaxSlots !== undefined) {
+ this.baseMaxSlots = data.baseMaxSlots;
+
+ if (debugLogger) debugLogger.logStep('Loading base max slots', {
+ oldBaseMaxSlots: oldState.baseMaxSlots,
+ newBaseMaxSlots: this.baseMaxSlots
+ });
+ }
+
+ if (data.starbaseBonusSlots !== undefined) {
+ this.starbaseBonusSlots = data.starbaseBonusSlots;
+
+ if (debugLogger) debugLogger.logStep('Loading starbase bonus slots', {
+ oldStarbaseBonusSlots: oldState.starbaseBonusSlots,
+ newStarbaseBonusSlots: this.starbaseBonusSlots
+ });
+ }
+
+ // Recalculate total max slots
+ this.totalMaxSlots = this.baseMaxSlots + this.starbaseBonusSlots;
+
+ if (debugLogger) debugLogger.logStep('Recalculating total max slots', {
+ baseMaxSlots: this.baseMaxSlots,
+ starbaseBonusSlots: this.starbaseBonusSlots,
+ totalMaxSlots: this.totalMaxSlots
+ });
+
+ // Update UI to show loaded equipment
+ this.updateUI();
+
+ if (debugLogger) debugLogger.endStep('Inventory.load', {
+ success: true,
+ oldState: oldState,
+ newState: {
+ itemCount: this.items.length,
+ equipmentCount: Object.values(this.equipment).filter(item => item !== null).length,
+ baseMaxSlots: this.baseMaxSlots,
+ starbaseBonusSlots: this.starbaseBonusSlots,
+ totalMaxSlots: this.totalMaxSlots
+ }
+ });
+ } catch (error) {
+ if (debugLogger) debugLogger.errorEvent('Inventory.load', error, {
+ oldState: oldState,
+ loadData: data,
+ error: error.message
+ });
+ throw error;
+ }
+ }
+}
diff --git a/Client-Server/js/core/Logger.js b/Client-Server/js/core/Logger.js
new file mode 100644
index 0000000..e715594
--- /dev/null
+++ b/Client-Server/js/core/Logger.js
@@ -0,0 +1,304 @@
+/**
+ * Galaxy Strike Online - Logging System
+ * Provides file-based logging with rotation and formatting
+ * Renderer process version that uses IPC to communicate with main process
+ */
+
+class Logger {
+ constructor() {
+ this.logLevel = 'INFO';
+ this.isRenderer = typeof window !== 'undefined' && typeof window.electronAPI !== 'undefined';
+ this.timers = new Map();
+
+ this.levels = {
+ ERROR: 0,
+ WARN: 1,
+ INFO: 2,
+ DEBUG: 3
+ };
+ }
+
+ async initialize(appDataPath) {
+ if (this.isRenderer) {
+ // In renderer process, just log that we're ready
+ console.log('Logger initialized in renderer process');
+ return;
+ }
+
+ // Main process initialization (original code)
+ const fs = require('fs').promises;
+ const path = require('path');
+
+ try {
+ // Set up log directory in app storage location
+ this.logDir = path.join(appDataPath, 'logs');
+
+ // Create logs directory if it doesn't exist
+ await fs.mkdir(this.logDir, { recursive: true });
+
+ // Set current log file with full timestamp (YYYY-MM-DD-HH-MM-SS)
+ const now = new Date();
+ const timestamp = now.toISOString()
+ .replace(/T/, '-')
+ .replace(/\..+/, '')
+ .replace(/:/g, '-');
+ this.currentLogFile = path.join(this.logDir, `galaxy-strike-${timestamp}.log`);
+
+ // Test write to ensure permissions
+ await this.writeToFile('=== Galaxy Strike Online Log Session Started ===\n');
+
+ this.isInitialized = true;
+ console.log(`Logger initialized: ${this.logDir}`);
+
+ } catch (error) {
+ console.error('Failed to initialize logger:', error);
+ this.isInitialized = false;
+ }
+ }
+
+ async writeToFile(message) {
+ if (this.isRenderer) {
+ // In renderer process, send to main process via IPC
+ return;
+ }
+
+ if (!this.isInitialized || !this.currentLogFile) return;
+
+ try {
+ const fs = require('fs').promises;
+
+ // Check if file needs rotation
+ await this.rotateLogIfNeeded();
+
+ // Append message to current log file
+ await fs.appendFile(this.currentLogFile, message, 'utf8');
+ } catch (error) {
+ console.error('Failed to write to log file:', error);
+ }
+ }
+
+ async rotateLogIfNeeded() {
+ if (this.isRenderer) return;
+
+ const fs = require('fs').promises;
+ const path = require('path');
+
+ try {
+ const stats = await fs.stat(this.currentLogFile);
+
+ if (stats.size >= this.maxFileSize) {
+ // Rotate log file with full timestamp
+ const now = new Date();
+ const timestamp = now.toISOString()
+ .replace(/T/, '-')
+ .replace(/\..+/, '')
+ .replace(/:/g, '-');
+ const rotatedFile = path.join(this.logDir, `galaxy-strike-${timestamp}.log`);
+
+ await fs.rename(this.currentLogFile, rotatedFile);
+
+ // Clean up old log files
+ await this.cleanupOldLogs();
+
+ // Create new current log file with new timestamp
+ const newTimestamp = new Date().toISOString()
+ .replace(/T/, '-')
+ .replace(/\..+/, '')
+ .replace(/:/g, '-');
+ this.currentLogFile = path.join(this.logDir, `galaxy-strike-${newTimestamp}.log`);
+
+ await this.writeToFile('=== Log Rotated ===\n');
+ }
+ } catch (error) {
+ // File might not exist yet, which is fine
+ if (error.code !== 'ENOENT') {
+ console.error('Failed to rotate log:', error);
+ }
+ }
+ }
+
+ async cleanupOldLogs() {
+ if (this.isRenderer) return;
+
+ const fs = require('fs').promises;
+ const path = require('path');
+
+ try {
+ const files = await fs.readdir(this.logDir);
+ const logFiles = files
+ .filter(file => file.startsWith('galaxy-strike-') && file.endsWith('.log'))
+ .map(file => ({
+ name: file,
+ path: path.join(this.logDir, file)
+ }));
+
+ if (logFiles.length > this.maxLogFiles) {
+ // Get file stats and sort by modification time
+ const filesWithStats = await Promise.all(
+ logFiles.map(async file => {
+ const stats = await fs.stat(file.path);
+ return { ...file, mtime: stats.mtime };
+ })
+ );
+
+ filesWithStats.sort((a, b) => b.mtime - a.mtime);
+
+ // Delete oldest files
+ const filesToDelete = filesWithStats.slice(this.maxLogFiles);
+ for (const file of filesToDelete) {
+ await fs.unlink(file.path);
+ }
+ }
+ } catch (error) {
+ console.error('Failed to cleanup old logs:', error);
+ }
+ }
+
+ formatMessage(level, message, data = null) {
+ const timestamp = new Date().toISOString();
+ const dataStr = data ? ` | Data: ${JSON.stringify(data)}` : '';
+ return `[${timestamp}] [${level}] ${message}${dataStr}\n`;
+ }
+
+ shouldLog(level) {
+ const currentLevel = this.levels[this.logLevel] || 2;
+ const messageLevel = this.levels[level] || 2;
+ return messageLevel <= currentLevel;
+ }
+
+ async log(level, message, data = null) {
+ if (!this.shouldLog(level)) return;
+
+ if (this.isRenderer && window.electronAPI) {
+ // Send to main process via IPC - ensure data is serializable
+ try {
+ const serializableData = data ? this.makeSerializable(data) : null;
+ window.electronAPI.log(level, message, serializableData);
+ } catch (error) {
+ console.error('Failed to send log to main process:', error);
+ // Fallback to console
+ console.log(`[${level}] ${message}`, data || '');
+ }
+ } else {
+ // Main process logging
+ const formattedMessage = this.formatMessage(level, message, data);
+ await this.writeToFile(formattedMessage);
+ console.log(`[${level}] ${message}`, data || '');
+ }
+ }
+
+ makeSerializable(obj) {
+ try {
+ // Convert to JSON and back to ensure it's serializable
+ return JSON.parse(JSON.stringify(obj));
+ } catch (error) {
+ // If not serializable, convert to string representation
+ return {
+ type: 'non-serializable',
+ toString: obj.toString ? obj.toString() : String(obj),
+ constructor: obj.constructor ? obj.constructor.name : 'Unknown'
+ };
+ }
+ }
+
+ async error(message, data = null) {
+ await this.log('error', message, data);
+ console.error(`[ERROR] ${message}`, data || '');
+ }
+
+ async warn(message, data = null) {
+ await this.log('warn', message, data);
+ console.warn(`[WARN] ${message}`, data || '');
+ }
+
+ async info(message, data = null) {
+ await this.log('info', message, data);
+ }
+
+ async debug(message, data = null) {
+ await this.log('debug', message, data);
+ }
+
+ async gameEvent(eventType, details) {
+ const message = `Game Event: ${eventType}`;
+ await this.info(message, details);
+ }
+
+ // Timing methods
+ startTimer(name) {
+ this.timers.set(name, performance.now());
+ }
+
+ endTimer(name) {
+ const startTime = this.timers.get(name);
+ if (startTime) {
+ const duration = performance.now() - startTime;
+ this.timers.delete(name);
+ return duration;
+ }
+ return 0;
+ }
+
+ async timeAsync(name, asyncFunction) {
+ this.startTimer(name);
+ try {
+ const result = await asyncFunction();
+ const duration = this.endTimer(name);
+ await this.info(`${name} completed`, { duration: `${duration.toFixed(2)}ms` });
+ return result;
+ } catch (error) {
+ const duration = this.endTimer(name);
+ await this.error(`${name} failed`, { duration: `${duration.toFixed(2)}ms`, error: error.message });
+ throw error;
+ }
+ }
+
+ async playerAction(action, details) {
+ const message = `Player Action: ${action}`;
+ await this.info(message, details);
+ }
+
+ async systemEvent(system, event, details) {
+ const message = `System Event: ${system} - ${event}`;
+ await this.info(message, details);
+ }
+
+ async errorEvent(error, context = null) {
+ const message = `Error Event: ${error.message || error}`;
+ const data = {
+ stack: error.stack,
+ context: context
+ };
+ await this.error(message, data);
+ }
+
+ setLogLevel(level) {
+ if (this.levels.hasOwnProperty(level)) {
+ this.logLevel = level;
+ this.info(`Log level changed to: ${level}`);
+ }
+ }
+
+ getLogInfo() {
+ return {
+ isInitialized: this.isInitialized,
+ isRenderer: this.isRenderer,
+ logDirectory: this.logDir,
+ currentLogFile: this.currentLogFile,
+ logLevel: this.logLevel
+ };
+ }
+}
+
+// Export singleton instance
+const logger = new Logger();
+
+// For Node.js environments
+if (typeof module !== 'undefined' && module.exports) {
+ module.exports = logger;
+}
+
+// For browser environments
+if (typeof window !== 'undefined') {
+ window.logger = logger;
+}
diff --git a/Client-Server/js/core/Player.js b/Client-Server/js/core/Player.js
new file mode 100644
index 0000000..9f84414
--- /dev/null
+++ b/Client-Server/js/core/Player.js
@@ -0,0 +1,912 @@
+/**
+ * Galaxy Strike Online - Player System
+ * Manages player stats, levels, and progression
+ */
+
+class Player {
+ constructor(gameEngine) {
+ const debugLogger = window.debugLogger;
+ if (debugLogger) debugLogger.log('Player constructor called');
+
+ this.game = gameEngine;
+
+ // Player stats
+ this.stats = {
+ level: 1,
+ experience: 0,
+ totalXP: 0, // Total accumulated XP across all levels
+ experienceToNext: window.XPProgression ? window.XPProgression.calculateXPToNextLevel(1, 0) : 100,
+ skillPoints: 0,
+ totalKills: 0,
+ dungeonsCleared: 0,
+ playTime: 0,
+ lastLogin: Date.now()
+ };
+
+ // Base attributes
+ this.attributes = {
+ health: 100,
+ maxHealth: 100,
+ energy: 100,
+ maxEnergy: 100,
+ attack: 10,
+ defense: 5,
+ speed: 10,
+ criticalChance: 0.05,
+ criticalDamage: 1.5
+ };
+
+ // Player info
+ this.info = {
+ name: 'Commander',
+ title: 'Rookie Pilot',
+ guild: null,
+ rank: 'Cadet'
+ };
+
+ // Ship info
+ this.ship = {
+ name: 'Starter Cruiser',
+ class: 'Cruiser',
+ health: 100,
+ maxHealth: 100,
+ attack: 10,
+ defense: 5,
+ speed: 10,
+ criticalChance: 0.05,
+ criticalDamage: 1.5,
+ level: 1,
+ upgrades: []
+ };
+
+ // Settings
+ this.settings = {
+ autoSave: true,
+ notifications: true,
+ soundEffects: true,
+ music: false,
+ discordIntegration: false
+ };
+
+ if (debugLogger) debugLogger.log('Player constructor completed', {
+ initialLevel: this.stats.level,
+ initialHealth: this.attributes.health,
+ shipName: this.ship.name,
+ shipClass: this.ship.class
+ });
+ }
+
+ async initialize() {
+ const debugLogger = window.debugLogger;
+ console.log('[PLAYER] Player system initializing');
+ if (debugLogger) await debugLogger.startStep('playerInitialize');
+
+ try {
+ if (debugLogger) await debugLogger.logStep('Player initialization started');
+ // Player initialization is handled by GameEngine
+ // This method is kept for compatibility but doesn't load game data
+ console.log('[PLAYER] Player system initialization completed');
+ if (debugLogger) await debugLogger.endStep('playerInitialize');
+ } catch (error) {
+ console.error('[PLAYER] Error during initialization:', error);
+ if (debugLogger) await debugLogger.errorEvent(error, 'Player Initialize');
+ }
+ }
+
+ setupNewPlayer() {
+ const debugLogger = window.debugLogger;
+ console.log('[PLAYER] Setting up new player');
+ if (debugLogger) debugLogger.logStep('Setting up new player', {
+ currentLevel: this.stats.level,
+ currentTitle: this.info.title
+ });
+
+ this.game.showNotification('Welcome to Galaxy Strike Online, Commander!', 'success', 5000);
+ this.game.showNotification('Complete quests and explore dungeons to progress!', 'info', 4000);
+
+ if (debugLogger) debugLogger.logStep('New player setup completed', {
+ notificationsShown: 2
+ });
+ }
+
+ // Experience and leveling
+ addExperience(amount) {
+ const debugLogger = window.debugLogger;
+ const oldExperience = this.stats.experience;
+ const oldLevel = this.stats.level;
+ const oldTotalXP = this.stats.totalXP;
+
+ // Add to total accumulated XP
+ this.stats.totalXP += amount;
+
+ // Calculate new level based on total XP
+ if (window.XPProgression) {
+ const levelInfo = window.XPProgression.getLevelFromXP(this.stats.totalXP);
+ this.stats.level = levelInfo.level;
+ this.stats.experience = levelInfo.xpIntoLevel;
+ this.stats.experienceToNext = levelInfo.xpToNext;
+ } else {
+ // Fallback to old system
+ this.stats.experience += amount;
+ }
+
+ if (debugLogger) debugLogger.logStep('Experience added', {
+ amount: amount,
+ oldExperience: oldExperience,
+ newExperience: this.stats.experience,
+ oldTotalXP: oldTotalXP,
+ newTotalXP: this.stats.totalXP,
+ experienceToNext: this.stats.experienceToNext,
+ currentLevel: oldLevel,
+ newLevel: this.stats.level
+ });
+
+ // Check for level up
+ const levelsGained = this.stats.level - oldLevel;
+ if (levelsGained > 0) {
+ for (let i = 0; i < levelsGained; i++) {
+ this.levelUp();
+ }
+ }
+
+ if (debugLogger && levelsGained > 0) {
+ debugLogger.logStep('Level up(s) occurred', {
+ levelsGained: levelsGained,
+ newLevel: this.stats.level,
+ remainingExperience: this.stats.experience,
+ totalXP: this.stats.totalXP
+ });
+ }
+
+ this.game.showNotification(`+${this.game.formatNumber(amount)} XP`, 'success', 2000);
+ }
+
+ levelUp() {
+ const debugLogger = window.debugLogger;
+ const oldLevel = this.stats.level;
+ const oldSkillPoints = this.stats.skillPoints;
+ const oldMaxHealth = this.attributes.maxHealth;
+ const oldMaxEnergy = this.attributes.maxEnergy;
+ const oldAttack = this.attributes.attack;
+ const oldDefense = this.attributes.defense;
+ const oldShipMaxHealth = this.ship.maxHealth;
+ const oldTitle = this.info.title;
+
+ console.log(`[PLAYER] Level up! New level: ${this.stats.level}`);
+ if (debugLogger) debugLogger.logStep('Level up initiated', {
+ oldLevel: oldLevel,
+ newLevel: this.stats.level,
+ skillPointsGained: 2,
+ totalSkillPoints: this.stats.skillPoints + 2,
+ currentXP: this.stats.experience,
+ totalXP: this.stats.totalXP
+ });
+
+ // Update quest progress for level objectives
+ if (this.game.systems.questSystem) {
+ this.game.systems.questSystem.updateLevelProgress(this.stats.level);
+ if (debugLogger) debugLogger.logStep('Quest progress updated for new level');
+ }
+
+ // Update experience requirement for next level
+ if (window.XPProgression) {
+ const levelInfo = window.XPProgression.getLevelFromXP(this.stats.totalXP);
+ this.stats.experienceToNext = levelInfo.xpToNext;
+ } else {
+ // Fallback to old system
+ this.stats.experienceToNext = Math.floor(this.stats.experienceToNext * 1.5);
+ }
+
+ if (debugLogger) debugLogger.logStep('Experience requirement updated', {
+ newRequirement: this.stats.experienceToNext,
+ usingNewSystem: !!window.XPProgression
+ });
+
+ // Improve base stats
+ this.attributes.maxHealth += 10;
+ this.attributes.health = this.attributes.maxHealth;
+
+ // Update UI to show new level
+ if (this.game && this.game.systems && this.game.systems.ui) {
+ this.game.systems.ui.updateUI();
+ if (debugLogger) debugLogger.logStep('UI updated for new level');
+ }
+
+ this.game.showNotification(`Level Up! You are now level ${this.stats.level}!`, 'success', 3000);
+ this.attributes.maxEnergy += 5;
+ this.attributes.energy = this.attributes.maxEnergy;
+ this.attributes.attack += 2;
+ this.attributes.defense += 1;
+
+ // Update ship health
+ this.ship.maxHealth += 15;
+ this.ship.health = this.ship.maxHealth;
+
+ // Update title based on level
+ this.updateTitle();
+
+ this.game.showNotification(`Level Up! You are now level ${this.stats.level}!`, 'success', 5000);
+ this.game.showNotification(`+2 Skill Points available`, 'info', 3000);
+
+ if (debugLogger) debugLogger.logStep('Level up completed', {
+ levelChange: `${oldLevel} → ${this.stats.level}`,
+ healthChange: `${oldMaxHealth} → ${this.attributes.maxHealth}`,
+ energyChange: `${oldMaxEnergy} → ${this.attributes.maxEnergy}`,
+ attackChange: `${oldAttack} → ${this.attributes.attack}`,
+ defenseChange: `${oldDefense} → ${this.attributes.defense}`,
+ shipHealthChange: `${oldShipMaxHealth} → ${this.ship.maxHealth}`,
+ titleChange: `${oldTitle} → ${this.info.title}`,
+ skillPointsChange: `${oldSkillPoints} → ${this.stats.skillPoints + 2}`
+ });
+
+ // Add skill points after logging the changes
+ this.stats.skillPoints += 2;
+ }
+
+ updateTitle() {
+ const debugLogger = window.debugLogger;
+ const oldTitle = this.info.title;
+
+ const titles = {
+ 1: 'Rookie Pilot',
+ 5: 'Space Cadet',
+ 10: 'Star Explorer',
+ 15: 'Galaxy Ranger',
+ 20: 'Space Captain',
+ 25: 'Star Commander',
+ 30: 'Galaxy Admiral',
+ 40: 'Space Legend',
+ 50: 'Cosmic Master'
+ };
+
+ for (const [level, title] of Object.entries(titles)) {
+ if (this.stats.level >= parseInt(level)) {
+ this.info.title = title;
+ }
+ }
+
+ if (debugLogger && oldTitle !== this.info.title) {
+ debugLogger.logStep('Player title updated', {
+ level: this.stats.level,
+ oldTitle: oldTitle,
+ newTitle: this.info.title
+ });
+ }
+ }
+
+ // Combat stats
+ takeDamage(amount) {
+ const debugLogger = window.debugLogger;
+ const oldHealth = this.attributes.health;
+ const actualDamage = Math.max(1, amount - this.attributes.defense);
+ this.attributes.health = Math.max(0, this.attributes.health - actualDamage);
+
+ if (debugLogger) debugLogger.logStep('Player took damage', {
+ damageAmount: amount,
+ playerDefense: this.attributes.defense,
+ actualDamage: actualDamage,
+ oldHealth: oldHealth,
+ newHealth: this.attributes.health,
+ healthRemaining: this.attributes.health > 0
+ });
+
+ if (this.attributes.health === 0) {
+ this.onDeath();
+ }
+ return actualDamage;
+ }
+
+ heal(amount) {
+ const debugLogger = window.debugLogger;
+ const oldHealth = this.attributes.health;
+ const healAmount = Math.min(amount, this.attributes.maxHealth - this.attributes.health);
+ this.attributes.health += healAmount;
+
+ if (debugLogger) debugLogger.logStep('Player healed', {
+ healAmount: amount,
+ actualHealAmount: healAmount,
+ oldHealth: oldHealth,
+ newHealth: this.attributes.health,
+ maxHealth: this.attributes.maxHealth,
+ healthPercent: Math.round((this.attributes.health / this.attributes.maxHealth) * 100)
+ });
+
+ return healAmount;
+ }
+
+ useEnergy(amount) {
+ const debugLogger = window.debugLogger;
+ const oldEnergy = this.attributes.energy;
+
+ if (this.attributes.energy < amount) {
+ if (debugLogger) debugLogger.logStep('Energy use failed - insufficient energy', {
+ requestedAmount: amount,
+ currentEnergy: oldEnergy,
+ deficit: amount - oldEnergy
+ });
+ return false;
+ }
+
+ this.attributes.energy -= amount;
+
+ if (debugLogger) debugLogger.logStep('Energy used', {
+ amountUsed: amount,
+ oldEnergy: oldEnergy,
+ newEnergy: this.attributes.energy,
+ maxEnergy: this.attributes.maxEnergy,
+ energyPercent: Math.round((this.attributes.energy / this.attributes.maxEnergy) * 100)
+ });
+
+ // Update UI to show energy change
+ this.updateUI();
+
+ return true;
+ }
+
+ restoreEnergy(amount) {
+ const debugLogger = window.debugLogger;
+ const oldEnergy = this.attributes.energy;
+ const restoreAmount = Math.min(amount, this.attributes.maxEnergy - this.attributes.energy);
+ this.attributes.energy += restoreAmount;
+
+ if (debugLogger) debugLogger.logStep('Energy restored', {
+ restoreAmount: amount,
+ actualRestoreAmount: restoreAmount,
+ oldEnergy: oldEnergy,
+ newEnergy: this.attributes.energy,
+ maxEnergy: this.attributes.maxEnergy,
+ energyPercent: Math.round((this.attributes.energy / this.attributes.maxEnergy) * 100)
+ });
+
+ // Update UI to show energy change
+ this.updateUI();
+
+ return restoreAmount;
+ }
+
+ // Energy regeneration
+ regenerateEnergy(deltaTime) {
+ const regenerationRate = this.getMaxEnergy() * 0.1; // 10% of max energy per second
+ const energyToRegen = (deltaTime / 1000) * regenerationRate;
+
+ if (this.attributes.energy < this.getMaxEnergy()) {
+ this.attributes.energy = Math.min(this.attributes.energy + energyToRegen, this.getMaxEnergy());
+
+ const debugLogger = window.debugLogger;
+ if (debugLogger) debugLogger.logStep('Energy regenerated', {
+ deltaTime: deltaTime,
+ energyRegenerated: energyToRegen,
+ currentEnergy: this.attributes.energy,
+ maxEnergy: this.getMaxEnergy()
+ });
+ }
+ }
+
+ // Combat calculations
+ calculateDamage(enemyDifficulty = 'normal') {
+ const debugLogger = window.debugLogger;
+ const baseDamage = this.ship.attack || this.attributes.attack;
+
+ // Adjust critical chance based on enemy difficulty
+ let criticalChance = this.ship.criticalChance || this.attributes.criticalChance;
+ const difficultyMultipliers = {
+ 'tutorial': 1.5, // Higher chance against easy enemies
+ 'easy': 1.2,
+ 'normal': 1.0,
+ 'medium': 0.9,
+ 'hard': 0.7, // Lower chance against hard enemies
+ 'extreme': 0.5 // Much lower chance against extreme enemies
+ };
+
+ const originalCriticalChance = criticalChance;
+ criticalChance *= (difficultyMultipliers[enemyDifficulty] || 1.0);
+
+ const criticalRoll = Math.random();
+
+ let damage = baseDamage;
+ let isCritical = false;
+
+ if (criticalRoll < criticalChance) {
+ damage *= (this.ship.criticalDamage || this.attributes.criticalDamage);
+ isCritical = true;
+ }
+
+ // Add some randomness
+ const randomMultiplier = this.game.getRandomFloat(0.9, 1.1);
+ damage *= randomMultiplier;
+
+ const finalDamage = Math.floor(damage);
+
+ if (debugLogger) debugLogger.logStep('Damage calculation completed', {
+ enemyDifficulty: enemyDifficulty,
+ baseDamage: baseDamage,
+ originalCriticalChance: originalCriticalChance,
+ adjustedCriticalChance: criticalChance,
+ criticalRoll: criticalRoll,
+ criticalDamageMultiplier: this.ship.criticalDamage || this.attributes.criticalDamage,
+ randomMultiplier: randomMultiplier,
+ isCritical: isCritical,
+ finalDamage: finalDamage
+ });
+
+ return {
+ damage: finalDamage,
+ isCritical
+ };
+ }
+
+ onDeath() {
+ const debugLogger = window.debugLogger;
+ const oldCredits = this.game.systems.economy?.credits || 0;
+
+ console.log('[PLAYER] Player death occurred');
+ if (debugLogger) debugLogger.logStep('Player death triggered', {
+ currentLevel: this.stats.level,
+ oldCredits: oldCredits,
+ totalKills: this.stats.totalKills,
+ dungeonsCleared: this.stats.dungeonsCleared
+ });
+
+ this.game.showNotification('Your ship was destroyed! Respawning...', 'error', 3000);
+
+ // Reset health and energy
+ this.attributes.health = this.attributes.maxHealth;
+ this.attributes.energy = this.attributes.maxEnergy;
+ this.ship.health = this.ship.maxHealth;
+
+ // Apply death penalty
+ const lostCredits = Math.floor(this.game.systems.economy.credits * 0.1);
+ this.game.systems.economy.removeCredits(lostCredits);
+
+ const newCredits = this.game.systems.economy?.credits || 0;
+
+ this.game.showNotification(`Death penalty: -${this.game.formatNumber(lostCredits)} credits`, 'warning', 3000);
+
+ if (debugLogger) debugLogger.logStep('Player death completed', {
+ healthRestored: this.attributes.health,
+ energyRestored: this.attributes.energy,
+ shipHealthRestored: this.ship.health,
+ creditsLost: lostCredits,
+ oldCredits: oldCredits,
+ newCredits: newCredits,
+ penaltyPercentage: 10
+ });
+ }
+
+ resetToLevel1() {
+ const debugLogger = window.debugLogger;
+ const oldStats = { ...this.stats };
+ const oldAttributes = { ...this.attributes };
+ const oldInfo = { ...this.info };
+ const oldShip = { ...this.ship };
+
+ console.log('[PLAYER] Resetting player to level 1');
+ if (debugLogger) debugLogger.logStep('Player reset to level 1 initiated', {
+ oldLevel: oldStats.level,
+ oldExperience: oldStats.experience,
+ oldKills: oldStats.totalKills
+ });
+
+ // Reset stats to initial values
+ this.stats = {
+ level: 1,
+ experience: 0,
+ totalXP: 0, // Total accumulated XP across all levels
+ experienceToNext: window.XPProgression ? window.XPProgression.calculateXPToNextLevel(1, 0) : 100,
+ skillPoints: 0,
+ totalKills: 0,
+ dungeonsCleared: 0,
+ playTime: 0,
+ lastLogin: Date.now(),
+ tutorialDungeonCompleted: false
+ };
+
+ // Reset attributes to base values
+ this.attributes = {
+ health: 100,
+ maxHealth: 100,
+ energy: 100,
+ maxEnergy: 100,
+ attack: 10,
+ defense: 5,
+ speed: 10,
+ criticalChance: 0.05,
+ criticalDamage: 1.5
+ };
+
+ // Reset info
+ this.info = {
+ name: 'Commander',
+ title: 'Rookie Pilot',
+ guild: null,
+ rank: 'Cadet'
+ };
+
+ // Reset ship
+ this.ship = {
+ name: 'Starter Cruiser',
+ class: 'Cruiser',
+ health: 1000,
+ maxHealth: 1000,
+ attack: 10,
+ defense: 5,
+ speed: 10,
+ criticalChance: 0.05,
+ criticalDamage: 1.5,
+ level: 1,
+ upgrades: []
+ };
+
+ console.log('=== DEBUG: Character Reset ===');
+ console.log('Player health reset to:', this.attributes.health, '/', this.attributes.maxHealth);
+ console.log('Ship health reset to:', this.ship.health, '/', this.ship.maxHealth);
+
+ // Reset skills
+ this.skills = {};
+
+ // Reset settings to defaults
+ this.settings = {
+ autoSave: true,
+ notifications: true,
+ soundEffects: true,
+ music: false,
+ theme: 'dark'
+ };
+
+ if (debugLogger) debugLogger.logStep('Player reset to level 1 completed', {
+ newLevel: this.stats.level,
+ newHealth: this.attributes.health,
+ newShipHealth: this.ship.health,
+ skillsCleared: true,
+ settingsReset: true
+ });
+ }
+
+ // Ship management
+ upgradeShip(upgradeType) {
+ const debugLogger = window.debugLogger;
+ const upgradeCosts = {
+ health: 100,
+ attack: 150,
+ defense: 120,
+ speed: 80,
+ critical: 200
+ };
+
+ const cost = upgradeCosts[upgradeType];
+ const oldCredits = this.game.systems.economy?.credits || 0;
+
+ if (debugLogger) debugLogger.logStep('Ship upgrade attempted', {
+ upgradeType: upgradeType,
+ cost: cost,
+ currentCredits: oldCredits,
+ canAfford: oldCredits >= cost
+ });
+
+ if (!cost || !this.game.systems.economy || this.game.systems.economy.credits < cost) {
+ if (debugLogger) debugLogger.logStep('Ship upgrade failed - insufficient funds or invalid type', {
+ upgradeType: upgradeType,
+ cost: cost,
+ currentCredits: oldCredits,
+ deficit: cost - oldCredits,
+ economySystemAvailable: !!this.game.systems.economy
+ });
+ return false;
+ }
+
+ const oldShipStats = { ...this.ship };
+ const oldPlayerStats = { ...this.attributes };
+
+ if (this.game.systems.economy) {
+ this.game.systems.economy.removeCredits(cost);
+ } else {
+ if (debugLogger) debugLogger.log('Economy system not available during ship upgrade');
+ return false;
+ }
+
+ switch (upgradeType) {
+ case 'health':
+ this.ship.maxHealth += 20;
+ this.ship.health = this.ship.maxHealth;
+ this.attributes.maxHealth += 10;
+ this.attributes.health = this.attributes.maxHealth;
+ break;
+ case 'attack':
+ this.ship.attack += 3;
+ break;
+ case 'defense':
+ this.ship.defense += 2;
+ break;
+ case 'speed':
+ this.ship.speed += 2;
+ break;
+ case 'critical':
+ this.ship.criticalChance = Math.min(0.5, this.ship.criticalChance + 0.02);
+ this.ship.criticalDamage += 0.1;
+ break;
+ }
+
+ this.ship.upgrades.push(upgradeType);
+ this.game.showNotification(`Ship upgraded: ${upgradeType}!`, 'success', 3000);
+
+ if (debugLogger) debugLogger.logStep('Ship upgrade completed', {
+ upgradeType: upgradeType,
+ cost: cost,
+ oldCredits: oldCredits,
+ newCredits: this.game.systems.economy?.credits || 0,
+ shipChanges: {
+ oldMaxHealth: oldShipStats.maxHealth,
+ newMaxHealth: this.ship.maxHealth,
+ oldAttack: oldShipStats.attack,
+ newAttack: this.ship.attack,
+ oldDefense: oldShipStats.defense,
+ newDefense: this.ship.defense,
+ oldSpeed: oldShipStats.speed,
+ newSpeed: this.ship.speed,
+ oldCriticalChance: oldShipStats.criticalChance,
+ newCriticalChance: this.ship.criticalChance,
+ oldCriticalDamage: oldShipStats.criticalDamage,
+ newCriticalDamage: this.ship.criticalDamage
+ },
+ playerChanges: {
+ oldMaxHealth: oldPlayerStats.maxHealth,
+ newMaxHealth: this.attributes.maxHealth,
+ oldHealth: oldPlayerStats.health,
+ newHealth: this.attributes.health
+ },
+ totalUpgrades: this.ship.upgrades.length
+ });
+
+ return true;
+ }
+
+ // Statistics tracking
+ incrementKills() {
+ const debugLogger = window.debugLogger;
+ const oldKills = this.stats.totalKills;
+ this.stats.totalKills++;
+
+ if (debugLogger) debugLogger.logStep('Kill count incremented', {
+ oldKills: oldKills,
+ newKills: this.stats.totalKills,
+ currentLevel: this.stats.level
+ });
+
+ // Update quest progress for combat objectives
+ if (this.game && this.game.systems && this.game.systems.questSystem) {
+ this.game.systems.questSystem.onEnemyDefeated();
+ if (debugLogger) debugLogger.logStep('Quest system notified of enemy defeat');
+ }
+ }
+
+ incrementDungeonsCleared() {
+ const debugLogger = window.debugLogger;
+ const oldDungeons = this.stats.dungeonsCleared;
+ this.stats.dungeonsCleared++;
+
+ if (debugLogger) debugLogger.logStep('Dungeons cleared incremented', {
+ oldDungeons: oldDungeons,
+ newDungeons: this.stats.dungeonsCleared,
+ currentLevel: this.stats.level
+ });
+ }
+
+ updatePlayTime(deltaTime) {
+ console.log('[PLAYER] updatePlayTime called with deltaTime:', deltaTime, 'ms');
+ console.log('[PLAYER] Before update - playTime:', this.stats.playTime, 'ms');
+
+ // Use real computer time delta
+ this.stats.playTime += deltaTime;
+
+ console.log('[PLAYER] After update - playTime:', this.stats.playTime, 'ms');
+ console.log('[PLAYER] PlayTime in seconds:', this.stats.playTime / 1000, 'seconds');
+ console.log('[PLAYER] PlayTime in minutes:', this.stats.playTime / 60000, 'minutes');
+ console.log('[PLAYER] PlayTime in hours:', this.stats.playTime / 3600000, 'hours');
+ }
+
+ // UI updates
+ updateUI() {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.logStep('Player UI update started', {
+ currentLevel: this.stats.level,
+ currentHealth: this.attributes.health,
+ currentEnergy: this.attributes.energy,
+ totalKills: this.stats.totalKills
+ });
+
+ // Update player info
+ const playerNameElement = document.getElementById('playerName');
+ const playerLevelElement = document.getElementById('playerLevel');
+
+ if (playerNameElement) {
+ playerNameElement.textContent = `${this.info.name} - ${this.info.title}`;
+ }
+
+ if (playerLevelElement) {
+ playerLevelElement.textContent = `Lv. ${this.stats.level}`;
+ }
+
+ // Update health and energy only if in multiplayer mode or game is actively running
+ const shouldUpdateUI = window.smartSaveManager?.isMultiplayer || this.game?.isRunning;
+
+ if (shouldUpdateUI && this.game && this.game.systems && this.game.systems.ui) {
+ this.game.systems.ui.updateResourceDisplay();
+ }
+
+ // Update stats
+ const totalKillsElement = document.getElementById('totalKills');
+ const dungeonsClearedElement = document.getElementById('dungeonsCleared');
+ const playTimeElement = document.getElementById('playTime');
+
+ if (totalKillsElement) {
+ totalKillsElement.textContent = this.game.formatNumber(this.stats.totalKills);
+ }
+
+ if (dungeonsClearedElement) {
+ dungeonsClearedElement.textContent = this.game.formatNumber(this.stats.dungeonsCleared);
+ }
+
+ if (playTimeElement) {
+ playTimeElement.textContent = this.game.formatTime(this.stats.playTime / 1000);
+ }
+
+ // Update ship info
+ const flagshipNameElement = document.getElementById('flagshipName');
+ const shipHealthElement = document.getElementById('shipHealth');
+
+ if (flagshipNameElement) {
+ flagshipNameElement.textContent = this.ship.name;
+ }
+
+ if (shipHealthElement) {
+ const healthPercent = Math.round((this.ship.health / this.ship.maxHealth) * 100);
+ shipHealthElement.textContent = `${healthPercent}%`;
+ }
+
+ if (debugLogger) debugLogger.logStep('Player UI update completed', {
+ elementsUpdated: {
+ playerName: !!playerNameElement,
+ playerLevel: !!playerLevelElement,
+ totalKills: !!totalKillsElement,
+ dungeonsCleared: !!dungeonsClearedElement,
+ playTime: !!playTimeElement,
+ flagshipName: !!flagshipNameElement,
+ shipHealth: !!shipHealthElement
+ }
+ });
+ }
+
+ // Save/Load
+ save() {
+ const debugLogger = window.debugLogger;
+
+ const saveData = {
+ stats: this.stats,
+ attributes: this.attributes,
+ info: this.info,
+ ship: this.ship,
+ settings: this.settings
+ };
+
+ // if (debugLogger) debugLogger.logStep('Player save data prepared', {
+ // level: this.stats.level,
+ // experience: this.stats.experience,
+ // totalKills: this.stats.totalKills,
+ // dungeonsCleared: this.stats.dungeonsCleared,
+ // playTime: this.stats.playTime,
+ // shipName: this.ship.name,
+ // shipLevel: this.ship.level,
+ // upgradesCount: this.ship.upgrades.length,
+ // dataSize: JSON.stringify(saveData).length
+ // });
+
+ return saveData;
+ }
+
+ load(data) {
+ const debugLogger = window.debugLogger;
+ console.log('[PLAYER] Loading player data:', data);
+ console.log('[PLAYER] Current level before load:', this.stats.level);
+
+ if (debugLogger) debugLogger.logStep('Player load initiated', {
+ hasData: !!data,
+ dataKeys: data ? Object.keys(data) : [],
+ currentLevel: this.stats.level,
+ currentExperience: this.stats.experience
+ });
+
+ try {
+ if (data.stats) {
+ console.log('[PLAYER] Loading stats:', data.stats);
+ const oldStats = { ...this.stats };
+ this.stats = { ...this.stats, ...data.stats };
+ console.log('[PLAYER] Level after stats load:', this.stats.level);
+
+ if (debugLogger) debugLogger.logStep('Player stats loaded', {
+ oldLevel: oldStats.level,
+ newLevel: this.stats.level,
+ oldExperience: oldStats.experience,
+ newExperience: this.stats.experience,
+ oldKills: oldStats.totalKills,
+ newKills: this.stats.totalKills
+ });
+ }
+
+ if (data.attributes) {
+ console.log('[PLAYER] Loading attributes:', data.attributes);
+ const oldAttributes = { ...this.attributes };
+ this.attributes = { ...this.attributes, ...data.attributes };
+
+ if (debugLogger) debugLogger.logStep('Player attributes loaded', {
+ oldHealth: oldAttributes.health,
+ newHealth: this.attributes.health,
+ oldMaxHealth: oldAttributes.maxHealth,
+ newMaxHealth: this.attributes.maxHealth,
+ oldAttack: oldAttributes.attack,
+ newAttack: this.attributes.attack,
+ oldDefense: oldAttributes.defense,
+ newDefense: this.attributes.defense
+ });
+ }
+
+ if (data.info) {
+ console.log('[PLAYER] Loading info:', data.info);
+ const oldInfo = { ...this.info };
+ this.info = { ...this.info, ...data.info };
+
+ if (debugLogger) debugLogger.logStep('Player info loaded', {
+ oldName: oldInfo.name,
+ newName: this.info.name,
+ oldTitle: oldInfo.title,
+ newTitle: this.info.title,
+ oldGuild: oldInfo.guild,
+ newGuild: this.info.guild
+ });
+ }
+
+ if (data.ship) {
+ console.log('[PLAYER] Loading ship:', data.ship);
+ const oldShip = { ...this.ship };
+ this.ship = { ...this.ship, ...data.ship };
+
+ if (debugLogger) debugLogger.logStep('Player ship loaded', {
+ oldShipName: oldShip.name,
+ newShipName: this.ship.name,
+ oldShipLevel: oldShip.level,
+ newShipLevel: this.ship.level,
+ oldUpgrades: oldShip.upgrades.length,
+ newUpgrades: this.ship.upgrades.length
+ });
+ }
+
+ if (data.settings) {
+ console.log('[PLAYER] Loading settings:', data.settings);
+ const oldSettings = { ...this.settings };
+ this.settings = { ...this.settings, ...data.settings };
+
+ if (debugLogger) debugLogger.logStep('Player settings loaded', {
+ oldAutoSave: oldSettings.autoSave,
+ newAutoSave: this.settings.autoSave,
+ oldNotifications: oldSettings.notifications,
+ newNotifications: this.settings.notifications
+ });
+ }
+
+ console.log('[PLAYER] Final level after load:', this.stats.level);
+
+ if (debugLogger) debugLogger.logStep('Player load completed successfully', {
+ finalLevel: this.stats.level,
+ finalExperience: this.stats.experience,
+ finalHealth: this.attributes.health,
+ finalShipHealth: this.ship.health,
+ totalDataSections: ['stats', 'attributes', 'info', 'ship', 'settings'].filter(key => data[key]).length
+ });
+
+ } catch (error) {
+ console.error('[PLAYER] Error loading player data:', error);
+ if (debugLogger) debugLogger.errorEvent(error, 'Player Load');
+ throw error;
+ }
+ }
+}
diff --git a/Client-Server/js/core/TextureManager.js b/Client-Server/js/core/TextureManager.js
new file mode 100644
index 0000000..4246d66
--- /dev/null
+++ b/Client-Server/js/core/TextureManager.js
@@ -0,0 +1,142 @@
+/**
+ * Galaxy Strike Online - Texture Manager
+ * Handles texture loading and missing texture fallbacks
+ */
+
+class TextureManager {
+ constructor(gameEngine) {
+ this.game = gameEngine;
+ this.textures = new Map();
+ this.missingTextureUrl = 'assets/textures/missing-texture.png';
+
+ // Initialize missing texture
+ this.loadMissingTexture();
+ }
+
+ async loadMissingTexture() {
+ try {
+ const img = new Image();
+ img.src = this.missingTextureUrl;
+ await img.decode();
+ this.textures.set('missing', img);
+ } catch (error) {
+ console.warn('Could not load missing texture, creating fallback');
+ this.createFallbackTexture();
+ }
+ }
+
+ createFallbackTexture() {
+ const canvas = document.createElement('canvas');
+ canvas.width = 64;
+ canvas.height = 64;
+ const ctx = canvas.getContext('2d');
+
+ // Create a pink and black checkerboard pattern
+ const squareSize = 8;
+ for (let y = 0; y < 8; y++) {
+ for (let x = 0; x < 8; x++) {
+ ctx.fillStyle = (x + y) % 2 === 0 ? '#FF00FF' : '#000000';
+ ctx.fillRect(x * squareSize, y * squareSize, squareSize, squareSize);
+ }
+ }
+
+ // Add "GSO Missing" text
+ ctx.fillStyle = '#FFFFFF';
+ ctx.font = 'bold 12px Arial';
+ ctx.textAlign = 'center';
+ ctx.textBaseline = 'middle';
+ ctx.fillText('GSO', 32, 28);
+ ctx.fillText('Missing', 32, 36);
+
+ const img = new Image();
+ img.src = canvas.toDataURL();
+ this.textures.set('missing', img);
+ }
+
+ async loadTexture(textureId, textureUrl) {
+ // Check if already loaded
+ if (this.textures.has(textureId)) {
+ return this.textures.get(textureId);
+ }
+
+ try {
+ const img = new Image();
+ img.src = textureUrl;
+ await img.decode();
+ this.textures.set(textureId, img);
+ return img;
+ } catch (error) {
+ console.warn(`Failed to load texture ${textureId} from ${textureUrl}, using missing texture`);
+ return this.getMissingTexture();
+ }
+ }
+
+ getTexture(textureId) {
+ return this.textures.get(textureId) || this.getMissingTexture();
+ }
+
+ getMissingTexture() {
+ return this.textures.get('missing') || this.createFallbackTexture();
+ }
+
+ // Icon fallback for FontAwesome icons
+ getIcon(iconClass) {
+ // Check if this is a valid FontAwesome icon
+ const validIconPrefixes = ['fas', 'far', 'fab', 'fal'];
+ const iconParts = iconClass.split(' ');
+ const hasValidPrefix = iconParts.some(part => validIconPrefixes.includes(part));
+
+ if (hasValidPrefix) {
+ return iconClass;
+ }
+
+ // Return missing icon fallback - use missing texture
+ return 'missing-texture';
+ }
+
+ // Get item icon as HTML element
+ getItemIconElement(iconClass, size = '32px') {
+ const icon = this.getIcon(iconClass);
+
+ if (icon === 'missing-texture') {
+ return ` `;
+ }
+
+ return ` `;
+ }
+
+ // Preload common textures
+ async preloadTextures() {
+ const commonTextures = [
+ 'ship_fighter',
+ 'ship_cruiser',
+ 'room_command_center',
+ 'room_power_core',
+ 'item_weapon',
+ 'item_shield'
+ ];
+
+ const loadPromises = commonTextures.map(textureId => {
+ const url = `assets/textures/${textureId}.png`;
+ return this.loadTexture(textureId, url);
+ });
+
+ try {
+ await Promise.all(loadPromises);
+ console.log('Common textures preloaded');
+ } catch (error) {
+ console.warn('Some textures failed to preload:', error);
+ }
+ }
+
+ // Clean up unused textures
+ cleanup() {
+ // Keep only essential textures in memory
+ const essentialTextures = ['missing'];
+ for (const [textureId, texture] of this.textures) {
+ if (!essentialTextures.includes(textureId)) {
+ this.textures.delete(textureId);
+ }
+ }
+ }
+}
diff --git a/Client-Server/js/data/GameData.js b/Client-Server/js/data/GameData.js
new file mode 100644
index 0000000..9ce79d7
--- /dev/null
+++ b/Client-Server/js/data/GameData.js
@@ -0,0 +1,570 @@
+/**
+ * Galaxy Strike Online - Game Data
+ * Static game data, constants, and configuration
+ */
+
+// Game configuration
+const GAME_CONFIG = {
+ version: '1.0.0',
+ name: 'Galaxy Strike Online',
+ maxLevel: 100,
+ saveInterval: 30000, // 30 seconds
+ notificationDuration: 3000,
+ maxNotifications: 5
+};
+
+// Player defaults
+const PLAYER_DEFAULTS = {
+ level: 1,
+ experience: 0,
+ skillPoints: 0,
+ credits: 1000,
+ gems: 10,
+ health: 100,
+ maxHealth: 100,
+ energy: 100,
+ maxEnergy: 100,
+ attack: 10,
+ defense: 5,
+ speed: 10,
+ criticalChance: 0.05,
+ criticalDamage: 1.5
+};
+
+// Experience requirements
+const EXPERIENCE_TABLE = [];
+for (let i = 1; i <= 100; i++) {
+ EXPERIENCE_TABLE[i] = Math.floor(100 * Math.pow(1.5, i - 1));
+}
+
+// Item rarities with colors and multipliers
+const ITEM_RARITIES = {
+ common: {
+ name: 'Common',
+ color: '#888888',
+ multiplier: 1.0,
+ dropChance: 0.60
+ },
+ uncommon: {
+ name: 'Uncommon',
+ color: '#00ff00',
+ multiplier: 1.2,
+ dropChance: 0.25
+ },
+ rare: {
+ name: 'Rare',
+ color: '#0088ff',
+ multiplier: 1.5,
+ dropChance: 0.10
+ },
+ epic: {
+ name: 'Epic',
+ color: '#8833ff',
+ multiplier: 2.0,
+ dropChance: 0.04
+ },
+ legendary: {
+ name: 'Legendary',
+ color: '#ff8800',
+ multiplier: 3.0,
+ dropChance: 0.01
+ }
+};
+
+// Enemy types and stats
+const ENEMY_TEMPLATES = {
+ space_pirate: {
+ name: 'Space Pirate',
+ health: 25,
+ attack: 10,
+ defense: 3,
+ speed: 8,
+ experience: 15,
+ credits: 12,
+ rarity: 'common'
+ },
+ alien_guardian: {
+ name: 'Alien Guardian',
+ health: 50,
+ attack: 8,
+ defense: 5,
+ speed: 6,
+ experience: 25,
+ credits: 15,
+ rarity: 'common'
+ },
+ mining_drone: {
+ name: 'Mining Drone',
+ health: 20,
+ attack: 8,
+ defense: 3,
+ speed: 5,
+ experience: 12,
+ credits: 8,
+ rarity: 'common'
+ },
+ security_drone: {
+ name: 'Security Drone',
+ health: 35,
+ attack: 14,
+ defense: 4,
+ speed: 10,
+ experience: 22,
+ credits: 15,
+ rarity: 'uncommon'
+ },
+ pirate_captain: {
+ name: 'Pirate Captain',
+ health: 40,
+ attack: 15,
+ defense: 6,
+ speed: 12,
+ experience: 30,
+ credits: 20,
+ rarity: 'uncommon'
+ },
+ crystal_golem: {
+ name: 'Crystal Golem',
+ health: 80,
+ attack: 6,
+ defense: 10,
+ speed: 4,
+ experience: 35,
+ credits: 25,
+ rarity: 'rare'
+ },
+ corrupted_ai: {
+ name: 'Corrupted AI',
+ health: 60,
+ attack: 20,
+ defense: 2,
+ speed: 15,
+ experience: 40,
+ credits: 30,
+ rarity: 'rare'
+ },
+ energy_being: {
+ name: 'Energy Being',
+ health: 55,
+ attack: 22,
+ defense: 3,
+ speed: 18,
+ experience: 45,
+ credits: 35,
+ rarity: 'epic'
+ },
+ quantum_entity: {
+ name: 'Quantum Entity',
+ health: 70,
+ attack: 35,
+ defense: 5,
+ speed: 20,
+ experience: 60,
+ credits: 50,
+ rarity: 'legendary'
+ }
+};
+
+// Dungeon configurations
+const DUNGEON_CONFIGS = {
+ alien_ruins: {
+ name: 'Alien Ruins',
+ description: 'Ancient alien structures filled with mysterious technology',
+ difficulty: 'medium',
+ minLevel: 3,
+ roomCount: [5, 8],
+ enemyTypes: ['alien_guardian', 'ancient_drone', 'crystal_golem'],
+ rewardMultiplier: 1.2,
+ energyCost: 20
+ },
+ pirate_lair: {
+ name: 'Pirate Lair',
+ description: 'Dangerous pirate hideouts with valuable loot',
+ difficulty: 'easy',
+ minLevel: 1,
+ roomCount: [4, 6],
+ enemyTypes: ['space_pirate', 'pirate_captain', 'defense_turret'],
+ rewardMultiplier: 1.0,
+ energyCost: 15
+ },
+ corrupted_vault: {
+ name: 'Corrupted AI Vault',
+ description: 'Malfunctioning AI facilities with corrupted security',
+ difficulty: 'hard',
+ minLevel: 5,
+ roomCount: [6, 9],
+ enemyTypes: ['security_drone', 'corrupted_ai', 'virus_program'],
+ rewardMultiplier: 1.5,
+ energyCost: 25
+ },
+ asteroid_mine: {
+ name: 'Asteroid Mine',
+ description: 'Abandoned mining facilities in asteroid fields',
+ difficulty: 'easy',
+ minLevel: 2,
+ roomCount: [4, 7],
+ enemyTypes: ['mining_drone', 'rock_creature', 'explosive_asteroid'],
+ rewardMultiplier: 0.8,
+ energyCost: 10
+ },
+ nebula_anomaly: {
+ name: 'Nebula Anomaly',
+ description: 'Strange energy anomalies in deep space',
+ difficulty: 'extreme',
+ minLevel: 8,
+ roomCount: [7, 10],
+ enemyTypes: ['energy_being', 'phase_shifter', 'quantum_entity'],
+ rewardMultiplier: 2.0,
+ energyCost: 30
+ }
+};
+
+// Skill definitions
+const SKILL_DEFINITIONS = {
+ combat: {
+ weapons_mastery: {
+ name: 'Weapons Mastery',
+ description: 'Increases weapon damage and critical chance',
+ maxLevel: 10,
+ experiencePerLevel: 100,
+ effects: {
+ attack: 2,
+ criticalChance: 0.01
+ },
+ icon: 'fa-sword'
+ },
+ shield_techniques: {
+ name: 'Shield Techniques',
+ description: 'Improves defense and energy efficiency',
+ maxLevel: 10,
+ experiencePerLevel: 100,
+ effects: {
+ defense: 2,
+ maxEnergy: 5
+ },
+ icon: 'fa-shield-alt'
+ },
+ piloting: {
+ name: 'Piloting',
+ description: 'Enhances speed and evasion',
+ maxLevel: 10,
+ experiencePerLevel: 100,
+ effects: {
+ speed: 2,
+ criticalChance: 0.005
+ },
+ icon: 'fa-rocket'
+ }
+ },
+ science: {
+ energy_manipulation: {
+ name: 'Energy Manipulation',
+ description: 'Better energy control and regeneration',
+ maxLevel: 10,
+ experiencePerLevel: 100,
+ effects: {
+ maxEnergy: 10,
+ energyRegeneration: 0.1
+ },
+ icon: 'fa-bolt'
+ },
+ alien_technology: {
+ name: 'Alien Technology',
+ description: 'Understanding and using alien artifacts',
+ maxLevel: 10,
+ experiencePerLevel: 150,
+ effects: {
+ findRarity: 0.05,
+ itemValue: 0.1
+ },
+ icon: 'fa-atom'
+ }
+ },
+ crafting: {
+ weapon_crafting: {
+ name: 'Weapon Crafting',
+ description: 'Create and upgrade weapons',
+ maxLevel: 10,
+ experiencePerLevel: 100,
+ effects: {
+ craftingBonus: 0.1,
+ weaponStats: 0.05
+ },
+ icon: 'fa-hammer'
+ },
+ armor_forging: {
+ name: 'Armor Forging',
+ description: 'Forge protective armor and shields',
+ maxLevel: 10,
+ experiencePerLevel: 100,
+ effects: {
+ craftingBonus: 0.1,
+ armorStats: 0.05
+ },
+ icon: 'fa-anvil'
+ }
+ }
+};
+
+// Shop items
+const SHOP_ITEMS = {
+ ships: [
+ {
+ id: 'fighter_mk1',
+ name: 'Fighter Mk. I',
+ type: 'ship',
+ rarity: 'common',
+ price: 5000,
+ currency: 'credits',
+ description: 'Fast and agile fighter ship',
+ stats: { attack: 15, speed: 20, defense: 8 }
+ },
+ {
+ id: 'cruiser_mk1',
+ name: 'Cruiser Mk. I',
+ type: 'ship',
+ rarity: 'uncommon',
+ price: 15000,
+ currency: 'credits',
+ description: 'Well-balanced cruiser for combat',
+ stats: { attack: 20, speed: 10, defense: 15 }
+ }
+ ],
+ upgrades: [
+ {
+ id: 'weapon_upgrade_1',
+ name: 'Weapon Upgrade I',
+ type: 'upgrade',
+ rarity: 'common',
+ price: 500,
+ currency: 'credits',
+ description: 'Increases weapon damage by 10%',
+ effect: { attackMultiplier: 1.1 }
+ },
+ {
+ id: 'shield_upgrade_1',
+ name: 'Shield Upgrade I',
+ type: 'upgrade',
+ rarity: 'common',
+ price: 400,
+ currency: 'credits',
+ description: 'Increases defense by 5 points',
+ effect: { defense: 5 }
+ }
+ ],
+ cosmetics: [
+ {
+ id: 'blue_paint',
+ name: 'Blue Paint Job',
+ type: 'cosmetic',
+ rarity: 'common',
+ price: 100,
+ currency: 'gems',
+ description: 'Custom blue paint for your ship'
+ },
+ {
+ id: 'golden_trim',
+ name: 'Golden Trim',
+ type: 'cosmetic',
+ rarity: 'rare',
+ price: 500,
+ currency: 'gems',
+ description: 'Luxurious golden trim for your ship'
+ }
+ ],
+ consumables: [
+ {
+ id: 'mega_health_kit',
+ name: 'Mega Health Kit',
+ type: 'consumable',
+ rarity: 'uncommon',
+ price: 50,
+ currency: 'credits',
+ description: 'Restores full health',
+ effect: { heal: 999 }
+ },
+ {
+ id: 'energy_boost',
+ name: 'Energy Boost',
+ type: 'consumable',
+ rarity: 'common',
+ price: 25,
+ currency: 'credits',
+ description: 'Restores 50 energy',
+ effect: { energy: 50 }
+ }
+ ]
+};
+
+// Starter equipment for new players
+const STARTER_EQUIPMENT = {
+ starter_blaster: {
+ id: 'starter_blaster',
+ name: 'Common Blaster',
+ type: 'weapon',
+ rarity: 'common',
+ description: 'A reliable basic blaster for new pilots',
+ stats: { attack: 5, criticalChance: 0.02 },
+ equipable: true,
+ slot: 'weapon',
+ value: 100,
+ stackable: false
+ },
+ basic_armor: {
+ id: 'basic_armor',
+ name: 'Basic Armor',
+ type: 'armor',
+ rarity: 'common',
+ description: 'Standard issue armor for basic protection',
+ stats: { defense: 3, health: 10 },
+ equipable: true,
+ slot: 'armor',
+ value: 150,
+ stackable: false
+ }
+};
+
+// Achievement definitions
+const ACHIEVEMENTS = {
+ first_victory: {
+ name: 'First Victory',
+ description: 'Win your first dungeon',
+ requirement: { dungeonsCompleted: 1 },
+ reward: { gems: 10, experience: 100 },
+ icon: 'fa-trophy'
+ },
+ dungeon_master: {
+ name: 'Dungeon Master',
+ description: 'Complete 50 dungeons',
+ requirement: { dungeonsCompleted: 50 },
+ reward: { gems: 100, experience: 1000 },
+ icon: 'fa-dungeon'
+ },
+ level_master: {
+ name: 'Level Master',
+ description: 'Reach level 50',
+ requirement: { level: 50 },
+ reward: { gems: 200, experience: 5000 },
+ icon: 'fa-level-up-alt'
+ },
+ wealthy_commander: {
+ name: 'Wealthy Commander',
+ description: 'Accumulate 1,000,000 credits',
+ requirement: { credits: 1000000 },
+ reward: { gems: 150, experience: 2000 },
+ icon: 'fa-coins'
+ },
+ skill_expert: {
+ name: 'Skill Expert',
+ description: 'Max out any skill',
+ requirement: { maxSkillLevel: 10 },
+ reward: { gems: 75, experience: 1500 },
+ icon: 'fa-graduation-cap'
+ }
+};
+
+// Game messages and notifications
+const GAME_MESSAGES = {
+ welcome: 'Welcome to Galaxy Strike Online, Commander!',
+ levelUp: 'Level Up! You are now level {level}!',
+ questCompleted: 'Quest completed: {questName}!',
+ dungeonCompleted: 'Dungeon completed! Time: {time}',
+ achievementUnlocked: 'Achievement Unlocked: {achievementName}!',
+ insufficientCredits: 'Not enough credits!',
+ insufficientGems: 'Not enough gems!',
+ insufficientEnergy: 'Not enough energy!',
+ inventoryFull: 'Inventory is full!',
+ skillPointsAvailable: 'You have {points} skill points available!',
+ dailyReward: 'Daily reward claimed! Day {day}',
+ offlineRewards: 'Welcome back! You were offline for {time}'
+};
+
+// Utility functions
+const GameUtils = {
+ // Get random item from array
+ getRandomItem(array) {
+ return array[Math.floor(Math.random() * array.length)];
+ },
+
+ // Get random integer between min and max (inclusive)
+ getRandomInt(min, max) {
+ return Math.floor(Math.random() * (max - min + 1)) + min;
+ },
+
+ // Get random float between min and max
+ getRandomFloat(min, max) {
+ return Math.random() * (max - min) + min;
+ },
+
+ // Check if chance succeeds
+ checkChance(chance) {
+ return Math.random() < chance;
+ },
+
+ // Format large numbers with suffixes
+ formatNumber(num) {
+ if (num >= 1000000) return (num / 1000000).toFixed(1) + 'M';
+ if (num >= 1000) return (num / 1000).toFixed(1) + 'K';
+ return Math.floor(num).toString();
+ },
+
+ // Format time in milliseconds to readable string
+ formatTime(milliseconds) {
+ const seconds = Math.floor(milliseconds / 1000);
+ const minutes = Math.floor(seconds / 60);
+ const hours = Math.floor(minutes / 60);
+ const days = Math.floor(hours / 24);
+
+ if (days > 0) return `${days}d ${hours % 24}h`;
+ if (hours > 0) return `${hours}h ${minutes % 60}m`;
+ if (minutes > 0) return `${minutes}m ${seconds % 60}s`;
+ return `${seconds}s`;
+ },
+
+ // Calculate experience needed for level
+ getExperienceForLevel(level) {
+ return EXPERIENCE_TABLE[level] || 0;
+ },
+
+ // Get item rarity by chance
+ getItemRarity() {
+ const roll = Math.random();
+ let cumulative = 0;
+
+ for (const [rarity, data] of Object.entries(ITEM_RARITIES)) {
+ cumulative += data.dropChance;
+ if (roll <= cumulative) {
+ return rarity;
+ }
+ }
+
+ return 'common';
+ },
+
+ // Deep clone object
+ deepClone(obj) {
+ return JSON.parse(JSON.stringify(obj));
+ },
+
+ // Generate unique ID
+ generateId() {
+ return Date.now().toString() + Math.random().toString(36).substr(2, 9);
+ }
+};
+
+// Export for use in other modules
+if (typeof module !== 'undefined' && module.exports) {
+ module.exports = {
+ GAME_CONFIG,
+ PLAYER_DEFAULTS,
+ EXPERIENCE_TABLE,
+ ITEM_RARITIES,
+ ENEMY_TEMPLATES,
+ DUNGEON_CONFIGS,
+ SKILL_DEFINITIONS,
+ SHOP_ITEMS,
+ ACHIEVEMENTS,
+ GAME_MESSAGES,
+ GameUtils
+ };
+}
diff --git a/Client-Server/js/main.js b/Client-Server/js/main.js
new file mode 100644
index 0000000..e9275fa
--- /dev/null
+++ b/Client-Server/js/main.js
@@ -0,0 +1,718 @@
+/**
+ * Galaxy Strike Online - Main Entry Point
+ * Initializes and starts the game
+ */
+
+console.log('[MAIN] main.js script loaded');
+
+// Wait for DOM to be loaded
+document.addEventListener('DOMContentLoaded', async () => {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('main.domContentLoaded', {
+ timestamp: new Date().toISOString(),
+ userAgent: navigator.userAgent,
+ url: window.location.href
+ });
+
+ const loadingIndicator = document.getElementById('loadingIndicator');
+ const loadingStatus = document.getElementById('loadingStatus');
+
+ if (debugLogger) debugLogger.logStep('DOM elements found', {
+ loadingIndicator: !!loadingIndicator,
+ loadingStatus: !!loadingStatus
+ });
+
+ try {
+ // Start debug logging
+ if (window.debugLogger) {
+ window.debugLogger.startStep('domLoad');
+ window.debugLogger.logStep('DOM loaded, starting initialization');
+ }
+
+ // Show loading indicator
+ if (loadingIndicator) loadingIndicator.classList.remove('hidden');
+ if (loadingStatus) {
+ loadingStatus.textContent = 'Initializing application...';
+ loadingStatus.classList.remove('hidden');
+ }
+
+ if (debugLogger) debugLogger.logStep('Loading indicator shown');
+
+ // Initialize title bar controls immediately (don't wait for DOMContentLoaded)
+ console.log('[MAIN] Initializing title bar controls immediately');
+
+ if (debugLogger) debugLogger.logStep('Initializing title bar controls');
+ initializeTitleBar();
+
+ // Wait for DOM to be ready
+ document.addEventListener('DOMContentLoaded', async () => {
+ if (debugLogger) debugLogger.startStep('main.secondDOMContentLoaded', {
+ timestamp: new Date().toISOString()
+ });
+
+ window.debugLogger.startStep('domLoad');
+ window.debugLogger.logStep('DOM loaded, starting initialization');
+
+ // Auto-start local server for singleplayer mode (DISABLED to prevent auto-start after multiplayer disconnect)
+ console.log('[MAIN] Skipping local server auto-start to prevent conflicts with multiplayer mode');
+ /*
+ if (window.localServerManager) {
+ try {
+ const serverResult = await window.localServerManager.autoStartIfSingleplayer();
+ if (serverResult.success) {
+ console.log('[MAIN] Local server started successfully:', serverResult);
+ if (debugLogger) debugLogger.logStep('Local server auto-started', {
+ port: serverResult.port,
+ url: serverResult.url
+ });
+ } else {
+ console.log('[MAIN] Local server not started:', serverResult.reason || serverResult.error);
+ if (debugLogger) debugLogger.logStep('Local server not started', {
+ reason: serverResult.reason || serverResult.error
+ });
+ }
+ } catch (error) {
+ console.error('[MAIN] Error starting local server:', error);
+ if (debugLogger) debugLogger.errorEvent(error, 'Local server startup');
+ }
+ } else {
+ console.warn('[MAIN] LocalServerManager not available');
+ }
+ */
+
+ // Title bar is already initialized, just log it
+ console.log('[MAIN] DOM loaded - title bar should already be working');
+
+ if (debugLogger) debugLogger.logStep('DOM loaded - title bar should be working');
+
+ // Show main menu instead of directly loading game
+ if (loadingStatus) {
+ loadingStatus.textContent = 'Ready';
+ loadingStatus.classList.add('hidden');
+ }
+
+ if (debugLogger) debugLogger.logStep('Loading status updated to Ready');
+
+ // Hide loading screen and show main menu
+ const loadingScreen = document.getElementById('loadingScreen');
+ const mainMenu = document.getElementById('mainMenu');
+
+ if (loadingScreen) loadingScreen.classList.add('hidden');
+ if (mainMenu) mainMenu.classList.remove('hidden');
+
+ if (debugLogger) debugLogger.logStep('Loading screen hidden, main menu shown', {
+ loadingScreenFound: !!loadingScreen,
+ mainMenuFound: !!mainMenu,
+ liveMainMenuReady: !!window.liveMainMenu
+ });
+
+ // The LiveMainMenu will initialize itself and handle authentication
+ console.log('[MAIN] Main menu displayed - LiveMainMenu will handle authentication and server browsing');
+
+ if (debugLogger) debugLogger.endStep('main.secondDOMContentLoaded', {
+ success: true,
+ mainMenuDisplayed: !!mainMenu
+ });
+ });
+
+ if (debugLogger) debugLogger.endStep('main.domContentLoaded', {
+ success: true,
+ titleBarInitialized: true
+ });
+ } catch (error) {
+ console.error('Failed to initialize game:', error);
+
+ if (debugLogger) debugLogger.errorEvent('main.domContentLoaded', error, {
+ phase: 'initialization',
+ timestamp: new Date().toISOString()
+ });
+
+ if (window.debugLogger) {
+ window.debugLogger.log('CRITICAL ERROR: Initialization failed', {
+ error: error.message,
+ stack: error.stack,
+ timestamp: new Date().toISOString()
+ });
+ }
+
+ // Show error state
+ if (loadingIndicator) loadingIndicator.classList.add('error');
+ if (loadingStatus) {
+ loadingStatus.textContent = 'Failed to load application';
+ loadingStatus.classList.add('error');
+ }
+
+ if (debugLogger) debugLogger.logStep('Error state displayed');
+
+ const logger = window.logger;
+ if (logger) {
+ await logger.errorEvent(error, 'Main.js Initialization');
+ }
+
+ if (debugLogger) debugLogger.endStep('main.domContentLoaded', {
+ success: false,
+ error: error.message
+ });
+ }
+});
+
+// Initialize title bar controls
+function initializeTitleBar() {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('main.initializeTitleBar', {
+ timestamp: new Date().toISOString(),
+ electronAPIAvailable: !!window.electronAPI
+ });
+
+ console.log('[TITLE BAR] Starting title bar initialization');
+
+ // Wait for both electronAPI and DOM elements to be available
+ const checkReady = () => {
+ const hasElectronAPI = !!window.electronAPI;
+ const hasMinimizeBtn = !!document.getElementById('minimizeBtn');
+ const hasCloseBtn = !!document.getElementById('closeBtn');
+ const hasFullscreenBtn = !!document.getElementById('fullscreenBtn');
+
+ const readyState = {
+ hasElectronAPI,
+ hasMinimizeBtn,
+ hasCloseBtn,
+ hasFullscreenBtn
+ };
+
+ console.log(`[TITLE BAR] electronAPI: ${hasElectronAPI}, minimizeBtn: ${hasMinimizeBtn}, closeBtn: ${hasCloseBtn}, fullscreenBtn: ${hasFullscreenBtn}`);
+
+ if (debugLogger) debugLogger.logStep('Title bar readiness check', readyState);
+
+ if (hasElectronAPI && hasMinimizeBtn && hasCloseBtn && hasFullscreenBtn) {
+ console.log('[TITLE BAR] All elements ready, setting up events');
+
+ if (debugLogger) debugLogger.logStep('All title bar elements ready, setting up events');
+ setupTitleBarEvents();
+
+ // Hide the "Initializing application..." text since title bar is now working
+ const loadingStatus = document.getElementById('loadingStatus');
+ if (loadingStatus && loadingStatus.textContent === 'Initializing application...') {
+ console.log('[TITLE BAR] Hiding initializing text');
+ loadingStatus.classList.add('hidden');
+
+ if (debugLogger) debugLogger.logStep('Hiding initializing text');
+ }
+
+ if (debugLogger) debugLogger.endStep('main.initializeTitleBar', {
+ success: true,
+ allElementsReady: true
+ });
+ } else {
+ if (debugLogger) debugLogger.logStep('Not all elements ready, retrying in 50ms', {
+ missingElements: Object.keys(readyState).filter(key => !readyState[key])
+ });
+ setTimeout(checkReady, 50);
+ }
+ };
+
+ checkReady();
+}
+
+function setupTitleBarEvents() {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('main.setupTitleBarEvents');
+
+ const minimizeBtn = document.getElementById('minimizeBtn');
+ const closeBtn = document.getElementById('closeBtn');
+ const fullscreenBtn = document.getElementById('fullscreenBtn');
+
+ console.log('[TITLE BAR] Setting up event listeners');
+
+ if (debugLogger) debugLogger.logStep('Title bar buttons found', {
+ minimizeBtn: !!minimizeBtn,
+ closeBtn: !!closeBtn,
+ fullscreenBtn: !!fullscreenBtn
+ });
+
+ let eventsSetup = 0;
+
+ if (minimizeBtn) {
+ console.log('[TITLE BAR] Adding minimize button listener');
+
+ if (debugLogger) debugLogger.logStep('Setting up minimize button listener');
+ minimizeBtn.addEventListener('click', (e) => {
+ console.log('[TITLE BAR] Minimize button clicked');
+
+ if (debugLogger) debugLogger.log('Title bar minimize button clicked');
+
+ e.preventDefault();
+ e.stopPropagation();
+ if (window.electronAPI && window.electronAPI.minimize) {
+ window.electronAPI.minimize();
+
+ if (debugLogger) debugLogger.logStep('Window minimized via electronAPI');
+ } else {
+ console.error('[TITLE BAR] electronAPI not available when minimize clicked');
+
+ if (debugLogger) debugLogger.log('electronAPI not available for minimize');
+ }
+ });
+ eventsSetup++;
+ } else {
+ console.error('[TITLE BAR] Minimize button not found during setup');
+
+ if (debugLogger) debugLogger.log('Minimize button not found');
+ }
+
+ if (closeBtn) {
+ console.log('[TITLE BAR] Adding close button listener');
+
+ if (debugLogger) debugLogger.logStep('Setting up close button listener');
+ closeBtn.addEventListener('click', (e) => {
+ console.log('[TITLE BAR] Close button clicked');
+
+ if (debugLogger) debugLogger.log('Title bar close button clicked');
+
+ e.preventDefault();
+ // ... rest of the code remains the same ...
+ e.stopPropagation();
+ if (window.electronAPI && window.electronAPI.closeWindow) {
+ window.electronAPI.closeWindow();
+
+ if (debugLogger) debugLogger.logStep('Window closed via electronAPI');
+ } else {
+ console.error('[TITLE BAR] electronAPI not available when close clicked');
+
+ if (debugLogger) debugLogger.log('electronAPI not available for close');
+ }
+ });
+ eventsSetup++;
+ } else {
+ console.error('[TITLE BAR] Close button not found during setup');
+
+ if (debugLogger) debugLogger.log('Close button not found');
+ }
+
+ if (fullscreenBtn) {
+ console.log('[TITLE BAR] Adding fullscreen button listener');
+
+ if (debugLogger) debugLogger.logStep('Setting up fullscreen button listener');
+ fullscreenBtn.addEventListener('click', (e) => {
+ console.log('[TITLE BAR] Fullscreen button clicked');
+
+ if (debugLogger) debugLogger.log('Title bar fullscreen button clicked');
+
+ e.preventDefault();
+ e.stopPropagation();
+ if (window.electronAPI && window.electronAPI.toggleFullscreen) {
+ window.electronAPI.toggleFullscreen();
+
+ if (debugLogger) debugLogger.logStep('Fullscreen toggled via electronAPI');
+
+ // Toggle fullscreen class on body
+ document.body.classList.toggle('fullscreen');
+ document.body.classList.toggle('fullscreen');
+ // Update icon
+ const icon = fullscreenBtn.querySelector('i');
+ if (document.body.classList.contains('fullscreen')) {
+ icon.className = 'fas fa-compress';
+
+ if (debugLogger) debugLogger.logStep('Fullscreen mode activated, icon changed to compress');
+ } else {
+ icon.className = 'fas fa-expand';
+
+ if (debugLogger) debugLogger.logStep('Fullscreen mode deactivated, icon changed to expand');
+ }
+ } else {
+ console.error('[TITLE BAR] electronAPI not available when fullscreen clicked');
+
+ if (debugLogger) debugLogger.log('electronAPI not available for fullscreen');
+ }
+ });
+ eventsSetup++;
+ } else {
+ console.error('[TITLE BAR] Fullscreen button not found during setup');
+
+ if (debugLogger) debugLogger.log('Fullscreen button not found');
+ }
+
+ console.log('[TITLE BAR] Event listeners setup complete');
+
+ if (debugLogger) debugLogger.endStep('main.setupTitleBarEvents', {
+ eventsSetup: eventsSetup,
+ totalExpectedEvents: 3
+ });
+}
+
+// Global utility functions for onclick handlers
+window.game = null;
+
+// Error handling
+window.addEventListener('error', (event) => {
+ console.error('Game error:', event.error);
+ if (window.game) {
+ window.game.showNotification('An error occurred. Please refresh the page.', 'error', 5000);
+ }
+});
+
+// Shutdown handler for debug logging
+window.addEventListener('beforeunload', async () => {
+ if (window.debugLogger) {
+ try {
+ await window.debugLogger.shutdown();
+ } catch (error) {
+ console.error('[MAIN] Failed to shutdown debug logger:', error);
+ }
+ }
+});
+
+// Performance monitoring
+if (window.performance && window.performance.memory) {
+ setInterval(() => {
+ if (window.game && window.game.isRunning) {
+ const stats = window.game.getPerformanceStats();
+ if (stats.memory && stats.memory.used / stats.memory.limit > 0.8) {
+ console.warn('High memory usage detected:', stats.memory);
+ }
+ }
+ }, 30000); // Check every 30 seconds
+}
+
+// Global console functions
+function toggleConsole() {
+ console.log('[DEBUG] toggleConsole called');
+ const consoleWindow = document.getElementById('consoleWindow');
+ const consoleInput = document.getElementById('consoleInput');
+
+ console.log('[DEBUG] consoleWindow element:', consoleWindow);
+ console.log('[DEBUG] consoleInput element:', consoleInput);
+
+ if (!consoleWindow) {
+ console.error('[DEBUG] consoleWindow element not found!');
+ return;
+ }
+
+ if (consoleWindow.style.display === 'flex') {
+ consoleWindow.style.display = 'none';
+ console.log('[DEBUG] Console hidden');
+ } else {
+ consoleWindow.style.display = 'flex';
+ console.log('[DEBUG] Console shown');
+ if (consoleInput) {
+ consoleInput.focus();
+ }
+ }
+}
+
+function handleConsoleInput(event) {
+ if (event.key === 'Enter') {
+ const input = event.target;
+ const command = input.value.trim();
+
+ if (command) {
+ executeConsoleCommand(command);
+ input.value = '';
+ }
+ }
+}
+
+function executeConsoleCommand(command) {
+ const output = document.getElementById('consoleOutput');
+ const commandLine = document.createElement('div');
+ commandLine.className = 'console-line';
+ commandLine.textContent = `> ${command}`;
+ output.appendChild(commandLine);
+
+ // Log command to file and browser console
+ console.log(`[CONSOLE] Command: ${command}`);
+ if (window.logger) {
+ window.logger.playerAction('Console Command', { command: command });
+ }
+
+ try {
+ const result = processCommand(command);
+ const resultLine = document.createElement('div');
+ resultLine.className = `console-line ${result.type || 'success'}`;
+ // Convert line breaks to HTML for proper rendering
+ resultLine.innerHTML = result.message.replace(/\n/g, ' ');
+ output.appendChild(resultLine);
+
+ // Log result to file and browser console
+ const consoleMethod = result.type === 'error' ? console.error :
+ result.type === 'info' ? console.info : console.log;
+ consoleMethod(`[CONSOLE] Result (${result.type}): ${result.message.replace(/\n/g, ' ')}`);
+
+ if (window.logger) {
+ window.logger.playerAction('Console Result', {
+ command: command,
+ result: result.type,
+ message: result.message
+ });
+ }
+ } catch (error) {
+ const errorLine = document.createElement('div');
+ errorLine.className = 'console-line console-error';
+ errorLine.textContent = `Error: ${error.message}`;
+ output.appendChild(errorLine);
+
+ // Log error to file and browser console
+ console.error(`[CONSOLE] Error: ${error.message}`);
+ if (window.logger) {
+ window.logger.errorEvent(error, 'Console Command', { command: command });
+ }
+ }
+
+ // Scroll to bottom
+ output.scrollTop = output.scrollHeight;
+}
+
+function processCommand(command) {
+ const parts = command.split(' ');
+ const cmd = parts[0].toLowerCase();
+ const args = parts.slice(1);
+
+ switch (cmd) {
+ case 'help':
+ return {
+ type: 'info',
+ message: `Available commands:\nhelp - Show this help message\nclear - Clear console output\ncoins - Add coins to player (e.g., "coins 1000")\ngems - Add gems to player (e.g., "gems 100")\nresearch - Add research points (e.g., "research 500")\ncraftingxp - Add crafting experience (e.g., "craftingxp 200")\ngiveitem - Add item to inventory (e.g., "giveitem iron_ore 10")\nhealth - Set current ship health (e.g., "health 150")\nlevel - Set current ship level (e.g., "level 5")\nunlock - Unlock a ship (e.g., "unlock heavy_fighter")\nstats - Show current player stats\nships - List all ships\ncurrent - Show current ship info`
+ };
+
+ case 'clear':
+ const output = document.getElementById('consoleOutput');
+ output.innerHTML = '';
+ return { type: 'success', message: 'Console cleared' };
+
+ case 'coins':
+ if (args.length === 0) {
+ return { type: 'error', message: 'Usage: coins ' };
+ }
+ const amount = parseInt(args[0]);
+ if (isNaN(amount)) {
+ return { type: 'error', message: 'Invalid amount' };
+ }
+ if (window.game && window.game.systems && window.game.systems.economy) {
+ window.game.systems.economy.addCredits(amount, 'console');
+ window.game.systems.economy.updateUI();
+ return { type: 'success', message: `Added ${amount} credits` };
+ }
+ return { type: 'error', message: 'Economy system not available' };
+
+ case 'gems':
+ if (args.length === 0) {
+ return { type: 'error', message: 'Usage: gems ' };
+ }
+ const gemAmount = parseInt(args[0]);
+ if (isNaN(gemAmount)) {
+ return { type: 'error', message: 'Invalid amount' };
+ }
+ if (window.game && window.game.systems && window.game.systems.economy) {
+ window.game.systems.economy.addGems(gemAmount, 'console');
+ window.game.systems.economy.updateUI();
+ return { type: 'success', message: `Added ${gemAmount} gems` };
+ }
+ return { type: 'error', message: 'Economy system not available' };
+
+ case 'research':
+ if (args.length === 0) {
+ return { type: 'error', message: 'Usage: research ' };
+ }
+ const researchAmount = parseInt(args[0]);
+ if (isNaN(researchAmount)) {
+ return { type: 'error', message: 'Invalid amount' };
+ }
+ if (window.game && window.game.systems && window.game.systems.player) {
+ const currentSkillPoints = window.game.systems.player.stats.skillPoints || 0;
+ window.game.systems.player.stats.skillPoints = currentSkillPoints + researchAmount;
+ window.game.systems.player.updateUI();
+
+ // Also update skill system UI
+ if (window.game.systems.skillSystem) {
+ window.game.systems.skillSystem.updateUI();
+ }
+
+ return { type: 'success', message: `Added ${researchAmount} skill points (Total: ${window.game.systems.player.stats.skillPoints})` };
+ }
+ return { type: 'error', message: 'Player system not available' };
+
+ case 'craftingxp':
+ if (args.length === 0) {
+ return { type: 'error', message: 'Usage: craftingxp ' };
+ }
+ const craftingXpAmount = parseInt(args[0]);
+ if (isNaN(craftingXpAmount)) {
+ return { type: 'error', message: 'Invalid amount' };
+ }
+ if (window.game && window.game.systems && window.game.systems.skillSystem) {
+ window.game.systems.skillSystem.awardCraftingExperience(craftingXpAmount);
+ const currentLevel = window.game.systems.skillSystem.getSkillLevel('crafting');
+ const currentExp = window.game.systems.skillSystem.getSkillExperience('crafting');
+ return { type: 'success', message: `Added ${craftingXpAmount} crafting experience (Level: ${currentLevel}, XP: ${currentExp})` };
+ }
+ return { type: 'error', message: 'Skill system not available' };
+
+ case 'giveitem':
+ if (args.length < 2) {
+ return { type: 'error', message: 'Usage: giveitem ' };
+ }
+ const itemId = args[0];
+ const quantity = parseInt(args[1]);
+ if (isNaN(quantity)) {
+ return { type: 'error', message: 'Invalid quantity' };
+ }
+ if (window.game && window.game.systems && window.game.systems.inventory) {
+ window.game.systems.inventory.addItem(itemId, quantity);
+ window.game.systems.ui.updateInventory();
+ return { type: 'success', message: `Added ${quantity}x ${itemId} to inventory` };
+ }
+ return { type: 'error', message: 'Inventory system not available' };
+
+ case 'health':
+ if (args.length === 0) {
+ return { type: 'error', message: 'Usage: health ' };
+ }
+ const healthAmount = parseInt(args[0]);
+ if (isNaN(healthAmount)) {
+ return { type: 'error', message: 'Invalid amount' };
+ }
+ if (window.game && window.game.systems && window.game.systems.ship) {
+ const currentShip = window.game.systems.ship.currentShip;
+ if (currentShip) {
+ currentShip.health = Math.min(healthAmount, currentShip.maxHealth);
+ window.game.systems.ship.updateCurrentShipDisplay();
+ return { type: 'success', message: `Set ship health to ${currentShip.health}/${currentShip.maxHealth}` };
+ }
+ }
+ return { type: 'error', message: 'Ship system not available' };
+
+ case 'level':
+ if (args.length === 0) {
+ return { type: 'error', message: 'Usage: level ' };
+ }
+ const levelAmount = parseInt(args[0]);
+ if (isNaN(levelAmount)) {
+ return { type: 'error', message: 'Invalid level' };
+ }
+ if (window.game && window.game.systems && window.game.systems.player) {
+ window.game.systems.player.stats.level = levelAmount;
+ window.game.systems.player.updateTitle();
+ window.game.systems.player.updateUI();
+ return { type: 'success', message: `Set player level to ${levelAmount}` };
+ }
+ return { type: 'error', message: 'Player system not available' };
+
+ case 'unlock':
+ if (args.length === 0) {
+ return { type: 'error', message: 'Usage: unlock ' };
+ }
+ const shipId = args[0];
+ if (window.game && window.game.systems && window.game.systems.ship) {
+ const ship = window.game.systems.ship.ships.find(s => s.id === shipId);
+ if (ship) {
+ ship.status = 'inactive';
+ window.game.systems.ship.renderShips();
+ return { type: 'success', message: `Unlocked ship: ${ship.name}` };
+ } else {
+ return { type: 'error', message: `Ship not found: ${shipId}` };
+ }
+ }
+ return { type: 'error', message: 'Ship system not available' };
+
+ case 'stats':
+ if (window.game && window.game.systems && window.game.systems.player && window.game.systems.economy) {
+ const player = window.game.systems.player;
+ const economy = window.game.systems.economy;
+ return {
+ type: 'info',
+ message: `Player Stats:\nLevel: ${player.stats.level}\nExperience: ${player.stats.experience}/${player.stats.experienceToNext}\nCredits: ${economy.credits}\nGems: ${economy.gems}\nTotal Kills: ${player.stats.totalKills}\nDungeons Cleared: ${player.stats.dungeonsCleared}\nTitle: ${player.info.title}`
+ };
+ }
+ return { type: 'error', message: 'Player or Economy system not available' };
+
+ case 'ships':
+ if (window.game && window.game.systems && window.game.systems.ship) {
+ const ships = window.game.systems.ship.ships;
+ const currentShip = window.game.systems.ship.currentShip;
+ const shipList = ships.map(ship =>
+ `- ${ship.name} (${ship.id}) - Level ${ship.level} - ${ship.status} - ${ship.rarity}${currentShip.id === ship.id ? ' [CURRENT]' : ''}`
+ ).join('\n');
+ return {
+ type: 'info',
+ message: `Available Ships:\n${shipList}`
+ };
+ }
+ return { type: 'error', message: 'Ship system not available' };
+
+ case 'current':
+ if (window.game && window.game.systems && window.game.systems.ship) {
+ const currentShip = window.game.systems.ship.currentShip;
+ if (currentShip) {
+ return {
+ type: 'info',
+ message: `Current Ship:
+- Name: ${currentShip.name}
+- Class: ${currentShip.class}
+- Level: ${currentShip.level}
+- Health: ${currentShip.health}/${currentShip.maxHealth}
+- Attack: ${currentShip.attack}
+- Defense: ${currentShip.defense}
+- Speed: ${currentShip.speed}
+- Rarity: ${currentShip.rarity}`
+ };
+ }
+ }
+ return { type: 'error', message: 'Ship system not available' };
+
+ default:
+ return { type: 'error', message: `Unknown command: ${cmd}. Type 'help' for available commands.` };
+ }
+}
+
+// Keyboard shortcut listener
+document.addEventListener('DOMContentLoaded', function() {
+ console.log('[DEBUG] DOMContentLoaded event fired for keyboard shortcuts');
+ document.addEventListener('keydown', function(event) {
+ // Log all key combinations for debugging
+ if (event.ctrlKey || event.altKey || event.shiftKey) {
+ console.log('[DEBUG] Key pressed:', {
+ key: event.key,
+ ctrlKey: event.ctrlKey,
+ altKey: event.altKey,
+ shiftKey: event.shiftKey,
+ code: event.code
+ });
+ }
+
+ // Ctrl+Alt+Shift+C to toggle console
+ if (event.ctrlKey && event.altKey && event.shiftKey && event.key === 'C') {
+ console.log('[DEBUG] Ctrl+Alt+Shift+C detected!');
+ event.preventDefault();
+
+ // Check if toggleConsole function exists
+ if (typeof toggleConsole === 'function') {
+ console.log('[DEBUG] toggleConsole function exists, calling it');
+ toggleConsole();
+ } else {
+ console.error('[DEBUG] toggleConsole function not found!');
+ }
+ }
+
+ // Escape to close console
+ if (event.key === 'Escape') {
+ const consoleWindow = document.getElementById('consoleWindow');
+ if (consoleWindow && consoleWindow.style.display === 'flex') {
+ consoleWindow.style.display = 'none';
+ }
+ }
+ });
+});
+
+// Initialize console output with welcome message
+document.addEventListener('DOMContentLoaded', function() {
+ const output = document.getElementById('consoleOutput');
+ if (output) {
+ const welcomeLine = document.createElement('div');
+ welcomeLine.className = 'console-line console-info';
+ welcomeLine.textContent = 'Developer Console ready. Type "help" for available commands.';
+ output.appendChild(welcomeLine);
+ }
+});
diff --git a/Client-Server/js/systems/BaseSystem.js b/Client-Server/js/systems/BaseSystem.js
new file mode 100644
index 0000000..b5acea3
--- /dev/null
+++ b/Client-Server/js/systems/BaseSystem.js
@@ -0,0 +1,2188 @@
+/**
+ * Galaxy Strike Online - Base System
+ * Manages player base building and customization
+ */
+
+class BaseSystem {
+ constructor(gameEngine) {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('BaseSystem.constructor', {
+ gameEngineProvided: !!gameEngine
+ });
+
+ this.game = gameEngine;
+
+ // Base configuration
+ this.base = {
+ name: 'Command Center Alpha',
+ level: 1,
+ experience: 0,
+ experienceToNext: 500,
+ rooms: [],
+ decorations: [],
+ power: 100,
+ maxPower: 100,
+ storage: 1000,
+ maxStorage: 1000
+ };
+
+ if (debugLogger) debugLogger.logStep('Base configuration initialized', {
+ baseName: this.base.name,
+ baseLevel: this.base.level,
+ initialPower: this.base.power,
+ maxPower: this.base.maxPower,
+ initialStorage: this.base.storage,
+ maxStorage: this.base.maxStorage
+ });
+
+ // Room types
+ this.roomTypes = {
+ command_center: {
+ name: 'Command Center',
+ description: 'Central control room for your operations',
+ size: 'large',
+ powerCost: 20,
+ storageBonus: 0,
+ buildCost: 0,
+ requiredLevel: 1,
+ maxLevel: 1,
+ icon: 'fa-satellite-dish'
+ },
+ barracks: {
+ name: 'Barracks',
+ description: 'Housing for crew and allies',
+ size: 'medium',
+ powerCost: 10,
+ storageBonus: 100,
+ buildCost: 500,
+ requiredLevel: 2,
+ maxLevel: 5,
+ icon: 'fa-home'
+ },
+ laboratory: {
+ name: 'Laboratory',
+ description: 'Research facility for new technologies',
+ size: 'medium',
+ powerCost: 15,
+ storageBonus: 50,
+ buildCost: 1000,
+ requiredLevel: 3,
+ maxLevel: 5,
+ icon: 'fa-flask'
+ },
+ workshop: {
+ name: 'Workshop',
+ description: 'Crafting and equipment modification station',
+ size: 'medium',
+ powerCost: 12,
+ storageBonus: 80,
+ buildCost: 800,
+ requiredLevel: 2,
+ maxLevel: 5,
+ icon: 'fa-hammer'
+ },
+ storage_bay: {
+ name: 'Storage Bay',
+ description: 'Additional storage for resources and items',
+ size: 'large',
+ powerCost: 5,
+ storageBonus: 500,
+ buildCost: 300,
+ requiredLevel: 1,
+ maxLevel: 10,
+ icon: 'fa-warehouse'
+ },
+ power_generator: {
+ name: 'Power Generator',
+ description: 'Generates power for base operations',
+ size: 'small',
+ powerCost: -25,
+ storageBonus: 0,
+ buildCost: 600,
+ requiredLevel: 2,
+ maxLevel: 5,
+ icon: 'fa-bolt'
+ },
+ defense_turret: {
+ name: 'Defense Turret',
+ description: 'Automated defense system',
+ size: 'small',
+ powerCost: 8,
+ storageBonus: 0,
+ buildCost: 400,
+ requiredLevel: 3,
+ maxLevel: 8,
+ icon: 'fa-crosshairs'
+ },
+ communication_array: {
+ name: 'Communication Array',
+ description: 'Long-range communication and scanning',
+ size: 'medium',
+ powerCost: 10,
+ storageBonus: 20,
+ buildCost: 700,
+ requiredLevel: 4,
+ maxLevel: 3,
+ icon: 'fa-broadcast-tower'
+ },
+ medical_bay: {
+ name: 'Medical Bay',
+ description: 'Health restoration and crew recovery',
+ size: 'medium',
+ powerCost: 12,
+ storageBonus: 30,
+ buildCost: 900,
+ requiredLevel: 3,
+ maxLevel: 4,
+ icon: 'fa-medkit'
+ },
+ recreation_room: {
+ name: 'Recreation Room',
+ description: 'Crew morale and relaxation facilities',
+ size: 'small',
+ powerCost: 6,
+ storageBonus: 10,
+ buildCost: 350,
+ requiredLevel: 2,
+ maxLevel: 3,
+ icon: 'fa-gamepad'
+ }
+ };
+
+ // Base upgrades
+ this.upgrades = {
+ power_efficiency: {
+ name: 'Power Efficiency',
+ description: 'Reduces power consumption of all rooms',
+ cost: 1000,
+ effect: { powerReduction: 0.1 },
+ maxLevel: 5,
+ currentLevel: 0
+ },
+ storage_expansion: {
+ name: 'Storage Expansion',
+ description: 'Increases inventory slots for item storage',
+ cost: 5000,
+ maxLevel: 10,
+ currentLevel: 0,
+ icon: 'fa-boxes',
+ slotBonus: 5 // +5 slots per level
+ },
+ automation_systems: {
+ name: 'Automation Systems',
+ description: 'Automated resource collection and processing',
+ cost: 1500,
+ effect: {
+ productionBonus: 0.2 // +20% production
+ },
+ maxLevel: 5,
+ currentLevel: 0
+ },
+ advanced_defenses: {
+ name: 'Advanced Defenses',
+ description: 'Improved base defense systems',
+ cost: 2000,
+ effect: { defenseBonus: 10 },
+ maxLevel: 3,
+ currentLevel: 0
+ }
+ };
+
+ // Base grid layout (10x10)
+ this.gridSize = 10;
+ this.grid = [];
+
+ if (debugLogger) debugLogger.logStep('Grid configuration initialized', {
+ gridSize: this.gridSize,
+ gridInitialized: false
+ });
+
+ // Initialize grid
+ this.initializeGrid();
+
+ if (debugLogger) debugLogger.endStep('BaseSystem.constructor', {
+ roomTypesCount: Object.keys(this.roomTypes).length,
+ upgradesCount: Object.keys(this.upgrades).length,
+ gridSize: this.gridSize,
+ gridInitialized: !!this.grid,
+ baseConfiguration: this.base
+ });
+ }
+
+ initializeBaseData() {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('BaseSystem.initializeBaseData', {
+ oldBaseName: this.base.name,
+ oldBaseLevel: this.base.level
+ });
+
+ // Initialize base with default values
+ this.base = {
+ name: 'Command Center Alpha',
+ level: 1,
+ experience: 0,
+ experienceToNext: 500,
+ rooms: [],
+ decorations: [],
+ power: 100,
+ maxPower: 100,
+ storage: 1000,
+ maxStorage: 1000
+ };
+
+ // Initialize resources
+ this.resources = {
+ energy: 100,
+ materials: 50,
+ research: 0
+ };
+
+ if (debugLogger) debugLogger.endStep('BaseSystem.initializeBaseData', {
+ newBaseName: this.base.name,
+ newBaseLevel: this.base.level,
+ roomsCount: this.base.rooms.length,
+ decorationsCount: this.base.decorations.length,
+ resourcesInitialized: this.resources
+ });
+ }
+
+ initialize() {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('BaseSystem.initialize', {
+ currentBaseLevel: this.base.level,
+ roomsCount: this.base.rooms.length
+ });
+
+ // Initialize base data
+ if (debugLogger) debugLogger.logStep('Initializing base data');
+ this.initializeBaseData();
+
+ // Initialize inventory bonus slots on game start
+ if (debugLogger) debugLogger.logStep('Updating inventory bonus slots');
+ this.updateInventoryBonusSlots();
+
+ if (debugLogger) debugLogger.endStep('BaseSystem.initialize', {
+ baseDataInitialized: true,
+ inventoryBonusSlotsUpdated: true
+ });
+ }
+
+ async initialize() {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('BaseSystem.asyncInitialize', {
+ currentBaseLevel: this.base.level,
+ roomsCount: this.base.rooms.length
+ });
+
+ // Start with command center
+ if (debugLogger) debugLogger.logStep('Adding command center room');
+ this.addRoom('command_center', 5, 5);
+
+ // Set up base navigation
+ if (debugLogger) debugLogger.logStep('Setting up base navigation');
+ this.setupBaseNavigation();
+
+ // Initialize ship gallery
+ if (debugLogger) debugLogger.logStep('Initializing ship gallery');
+ this.initializeShipGallery();
+
+ // Initialize starbase system
+ if (debugLogger) debugLogger.logStep('Initializing starbase system');
+ this.initializeStarbaseSystem();
+
+ if (debugLogger) debugLogger.endStep('BaseSystem.asyncInitialize', {
+ commandCenterAdded: true,
+ navigationSetup: true,
+ shipGalleryInitialized: true,
+ starbaseSystemInitialized: true
+ });
+ }
+
+ initializeGrid() {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('BaseSystem.initializeGrid', {
+ gridSize: this.gridSize
+ });
+
+ this.grid = [];
+ for (let y = 0; y < this.gridSize; y++) {
+ this.grid[y] = [];
+ for (let x = 0; x < this.gridSize; x++) {
+ this.grid[y][x] = null;
+ }
+ }
+
+ if (debugLogger) debugLogger.endStep('BaseSystem.initializeGrid', {
+ gridSize: this.gridSize,
+ gridCreated: true,
+ totalCells: this.gridSize * this.gridSize
+ });
+ }
+
+ // Room management
+ getRoomAt(x, y) {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.log('BaseSystem.getRoomAt called', {
+ x: x,
+ y: y,
+ gridSize: this.gridSize
+ });
+
+ // Check if coordinates are within grid bounds
+ if (x < 0 || x >= this.gridSize || y < 0 || y >= this.gridSize) {
+ if (debugLogger) debugLogger.log('Coordinates out of bounds', {
+ x: x,
+ y: y,
+ gridSize: this.gridSize
+ });
+ return null;
+ }
+
+ // Ensure grid exists
+ if (!this.grid || !this.grid[y]) {
+ console.log('[BASE SYSTEM] Grid not properly initialized in getRoomAt');
+ if (debugLogger) debugLogger.log('Grid not properly initialized', {
+ gridExists: !!this.grid,
+ gridRowExists: !!(this.grid && this.grid[y]),
+ y: y
+ });
+ return null;
+ }
+
+ // Get room ID from grid
+ const roomId = this.grid[y][x];
+ if (!roomId) {
+ if (debugLogger) debugLogger.log('No room at coordinates', {
+ x: x,
+ y: y,
+ roomId: roomId
+ });
+ return null;
+ }
+
+ // Find room by ID
+ const room = this.base.rooms.find(r => r.id === roomId) || null;
+
+ if (debugLogger) debugLogger.log('Room found at coordinates', {
+ x: x,
+ y: y,
+ roomId: roomId,
+ roomFound: !!room,
+ roomName: room ? room.name : null,
+ roomType: room ? room.type : null
+ });
+
+ return room;
+ }
+
+ canBuildRoom(roomType, x, y) {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('BaseSystem.canBuildRoom', {
+ roomType: roomType,
+ x: x,
+ y: y,
+ currentBaseLevel: this.base.level,
+ currentCredits: this.game.systems.economy.credits,
+ currentRooms: this.base.rooms.length
+ });
+
+ const roomTemplate = this.roomTypes[roomType];
+ if (!roomTemplate) {
+ if (debugLogger) debugLogger.endStep('BaseSystem.canBuildRoom', {
+ success: false,
+ reason: 'Room template not found',
+ roomType: roomType
+ });
+ return false;
+ }
+
+ if (debugLogger) debugLogger.logStep('Room template found', {
+ roomName: roomTemplate.name,
+ requiredLevel: roomTemplate.requiredLevel,
+ buildCost: roomTemplate.buildCost,
+ powerCost: roomTemplate.powerCost,
+ maxLevel: roomTemplate.maxLevel
+ });
+
+ // Check player level
+ if (this.game.systems.player.stats.level < roomTemplate.requiredLevel) {
+ if (debugLogger) debugLogger.endStep('BaseSystem.canBuildRoom', {
+ success: false,
+ reason: 'Player level too low',
+ playerLevel: this.game.systems.player.stats.level,
+ requiredLevel: roomTemplate.requiredLevel
+ });
+ return false;
+ }
+
+ // Check resources
+ if (this.game.systems.economy.credits < roomTemplate.buildCost) {
+ if (debugLogger) debugLogger.endStep('BaseSystem.canBuildRoom', {
+ success: false,
+ reason: 'Insufficient credits',
+ currentCredits: this.game.systems.economy.credits,
+ requiredCredits: roomTemplate.buildCost
+ });
+ return false;
+ }
+
+ // Check if room already exists at max level
+ const existingRoom = this.base.rooms.find(r => r.type === roomType);
+ if (existingRoom && existingRoom.level >= roomTemplate.maxLevel) {
+ if (debugLogger) debugLogger.endStep('BaseSystem.canBuildRoom', {
+ success: false,
+ reason: 'Room already at max level',
+ roomType: roomType,
+ currentLevel: existingRoom.level,
+ maxLevel: roomTemplate.maxLevel
+ });
+ return false;
+ }
+
+ // Check grid placement
+ const roomSize = this.getRoomSize(roomTemplate.size);
+ if (!this.canPlaceRoom(x, y, roomSize.width, roomSize.height)) {
+ if (debugLogger) debugLogger.endStep('BaseSystem.canBuildRoom', {
+ success: false,
+ reason: 'Cannot place room at location',
+ x: x,
+ y: y,
+ roomSize: roomSize
+ });
+ return false;
+ }
+
+ // Check power availability
+ const powerRequirement = roomTemplate.powerCost;
+ if (this.base.power + powerRequirement > this.base.maxPower) {
+ if (debugLogger) debugLogger.endStep('BaseSystem.canBuildRoom', {
+ success: false,
+ reason: 'Insufficient power',
+ currentPower: this.base.power,
+ powerRequirement: powerRequirement,
+ maxPower: this.base.maxPower,
+ totalPowerNeeded: this.base.power + powerRequirement
+ });
+ return false;
+ }
+
+ if (debugLogger) debugLogger.endStep('BaseSystem.canBuildRoom', {
+ success: true,
+ roomType: roomType,
+ roomName: roomTemplate.name,
+ x: x,
+ y: y,
+ isUpgrade: !!existingRoom,
+ currentLevel: existingRoom ? existingRoom.level : 0
+ });
+
+ return true;
+ }
+
+ getRoomSize(size) {
+ const sizes = {
+ small: { width: 1, height: 1 },
+ medium: { width: 2, height: 2 },
+ large: { width: 3, height: 3 }
+ };
+ return sizes[size] || sizes.medium;
+ }
+
+ canPlaceRoom(x, y, width, height) {
+ // Check bounds
+ if (x < 0 || y < 0 || x + width > this.gridSize || y + height > this.gridSize) {
+ return false;
+ }
+
+ // Check for collisions
+ for (let dy = 0; dy < height; dy++) {
+ for (let dx = 0; dx < width; dx++) {
+ if (this.grid[y + dy][x + dx] !== null) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ addRoom(roomType, x, y) {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('BaseSystem.addRoom', {
+ roomType: roomType,
+ x: x,
+ y: y,
+ currentRoomsCount: this.base.rooms.length,
+ currentCredits: this.game.systems.economy.credits
+ });
+
+ if (!this.canBuildRoom(roomType, x, y)) {
+ if (debugLogger) debugLogger.endStep('BaseSystem.addRoom', {
+ success: false,
+ reason: 'Cannot build room - failed validation',
+ roomType: roomType,
+ x: x,
+ y: y
+ });
+ return false;
+ }
+
+ const roomTemplate = this.roomTypes[roomType];
+ const roomSize = this.getRoomSize(roomTemplate.size);
+
+ if (debugLogger) debugLogger.logStep('Room validation passed', {
+ roomName: roomTemplate.name,
+ roomSize: roomSize,
+ buildCost: roomTemplate.buildCost
+ });
+
+ // Check if upgrading existing room
+ const existingRoom = this.base.rooms.find(r => r.type === roomType);
+ const isUpgrade = !!existingRoom;
+
+ if (isUpgrade) {
+ if (debugLogger) debugLogger.logStep('Upgrading existing room', {
+ roomId: existingRoom.id,
+ currentLevel: existingRoom.level,
+ newLevel: existingRoom.level + 1
+ });
+
+ // Upgrade existing room
+ existingRoom.level++;
+ existingRoom.powerCost = roomTemplate.powerCost * existingRoom.level;
+ existingRoom.storageBonus = roomTemplate.storageBonus * existingRoom.level;
+ } else {
+ if (debugLogger) debugLogger.logStep('Creating new room', {
+ roomType: roomType,
+ roomName: roomTemplate.name,
+ x: x,
+ y: y,
+ roomSize: roomSize
+ });
+
+ // Create new room
+ const room = {
+ id: Date.now().toString(),
+ type: roomType,
+ name: roomTemplate.name,
+ level: 1,
+ x: x,
+ y: y,
+ width: roomSize.width,
+ height: roomSize.height,
+ powerCost: roomTemplate.powerCost,
+ storageBonus: roomTemplate.storageBonus,
+ icon: roomTemplate.icon,
+ description: roomTemplate.description
+ };
+
+ this.base.rooms.push(room);
+
+ // Place on grid
+ for (let dy = 0; dy < roomSize.height; dy++) {
+ for (let dx = 0; dx < roomSize.width; dx++) {
+ this.grid[y + dy][x + dx] = room.id;
+ }
+ }
+
+ if (debugLogger) debugLogger.logStep('New room placed on grid', {
+ roomId: room.id,
+ gridPositions: roomSize.width * roomSize.height
+ });
+ }
+
+ // Update base stats
+ if (debugLogger) debugLogger.logStep('Updating base stats');
+ this.updateBaseStats();
+
+ // Deduct cost
+ const oldCredits = this.game.systems.economy.credits;
+ this.game.systems.economy.removeCredits(roomTemplate.buildCost);
+
+ if (debugLogger) debugLogger.logStep('Build cost deducted', {
+ buildCost: roomTemplate.buildCost,
+ oldCredits: oldCredits,
+ newCredits: this.game.systems.economy.credits
+ });
+
+ this.game.showNotification(`${roomTemplate.name} ${isUpgrade ? 'upgraded' : 'built'}!`, 'success', 3000);
+
+ if (debugLogger) debugLogger.endStep('BaseSystem.addRoom', {
+ success: true,
+ roomType: roomType,
+ roomName: roomTemplate.name,
+ isUpgrade: isUpgrade,
+ roomId: isUpgrade ? existingRoom.id : this.base.rooms[this.base.rooms.length - 1].id,
+ newLevel: isUpgrade ? existingRoom.level : 1,
+ finalRoomsCount: this.base.rooms.length,
+ buildCost: roomTemplate.buildCost,
+ finalCredits: this.game.systems.economy.credits
+ });
+
+ return true;
+ }
+
+ removeRoom(roomId) {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('BaseSystem.removeRoom', {
+ roomId: roomId,
+ currentRoomsCount: this.base.rooms.length
+ });
+
+ const roomIndex = this.base.rooms.findIndex(r => r.id === roomId);
+ if (roomIndex === -1) {
+ if (debugLogger) debugLogger.endStep('BaseSystem.removeRoom', {
+ success: false,
+ reason: 'Room not found',
+ roomId: roomId
+ });
+ return false;
+ }
+
+ const room = this.base.rooms[roomIndex];
+
+ if (debugLogger) debugLogger.logStep('Room found for removal', {
+ roomId: room.id,
+ roomName: room.name,
+ roomType: room.type,
+ roomLevel: room.level,
+ roomPosition: { x: room.x, y: room.y }
+ });
+
+ // Don't allow removing command center
+ if (room.type === 'command_center') {
+ this.game.showNotification('Cannot remove Command Center', 'error', 3000);
+
+ if (debugLogger) debugLogger.endStep('BaseSystem.removeRoom', {
+ success: false,
+ reason: 'Cannot remove command center',
+ roomId: roomId,
+ roomType: room.type
+ });
+ return false;
+ }
+
+ // Remove from grid
+ let gridCellsCleared = 0;
+ for (let dy = 0; dy < room.height; dy++) {
+ for (let dx = 0; dx < room.width; dx++) {
+ this.grid[room.y + dy][room.x + dx] = null;
+ gridCellsCleared++;
+ }
+ }
+
+ if (debugLogger) debugLogger.logStep('Room removed from grid', {
+ gridCellsCleared: gridCellsCleared,
+ roomSize: { width: room.width, height: room.height }
+ });
+
+ // Remove from rooms array
+ this.base.rooms.splice(roomIndex, 1);
+
+ if (debugLogger) debugLogger.logStep('Room removed from rooms array', {
+ removedIndex: roomIndex,
+ remainingRoomsCount: this.base.rooms.length
+ });
+
+ // Update base stats
+ if (debugLogger) debugLogger.logStep('Updating base stats after removal');
+ this.updateBaseStats();
+
+ // Refund some resources
+ const refundAmount = Math.floor(this.roomTypes[room.type].buildCost * 0.5);
+ const oldCredits = this.game.systems.economy.credits;
+ this.game.systems.economy.addCredits(refundAmount, 'room_refund');
+
+ if (debugLogger) debugLogger.logStep('Refund processed', {
+ refundAmount: refundAmount,
+ oldCredits: oldCredits,
+ newCredits: this.game.systems.economy.credits,
+ originalBuildCost: this.roomTypes[room.type].buildCost,
+ refundPercentage: 0.5
+ });
+
+ this.game.showNotification(`${room.name} removed. Refunded ${refundAmount} credits`, 'info', 3000);
+
+ if (debugLogger) debugLogger.endStep('BaseSystem.removeRoom', {
+ success: true,
+ roomId: roomId,
+ roomName: room.name,
+ roomType: room.type,
+ refundAmount: refundAmount,
+ finalRoomsCount: this.base.rooms.length,
+ finalCredits: this.game.systems.economy.credits
+ });
+
+ return true;
+ }
+
+ calculateBaseStats() {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('BaseSystem.calculateBaseStats', {
+ currentRoomsCount: this.base.rooms.length,
+ currentPower: this.base.power,
+ currentMaxPower: this.base.maxPower,
+ currentStorage: this.base.storage,
+ currentMaxStorage: this.base.maxStorage
+ });
+
+ let totalPowerCost = 0;
+ let totalStorageBonus = 0;
+
+ // Calculate power cost and storage bonus from rooms
+ this.base.rooms.forEach(room => {
+ const roomType = this.roomTypes[room.type];
+ const roomPowerCost = roomType.powerCost * room.level;
+ const roomStorageBonus = roomType.storageBonus * room.level;
+
+ totalPowerCost += roomPowerCost;
+ totalStorageBonus += roomStorageBonus;
+
+ if (debugLogger) debugLogger.logStep('Room contribution calculated', {
+ roomId: room.id,
+ roomType: room.type,
+ roomName: room.name,
+ roomLevel: room.level,
+ powerCost: roomPowerCost,
+ storageBonus: roomStorageBonus
+ });
+ });
+
+ // Add upgrade bonuses
+ const storageExpansion = (this.upgrades.storage_expansion?.currentLevel || 0) * (this.upgrades.storage_expansion?.slotBonus || 5);
+ totalStorageBonus += storageExpansion;
+
+ if (debugLogger) debugLogger.logStep('Upgrade bonuses calculated', {
+ storageExpansionLevel: this.upgrades.storage_expansion?.currentLevel || 0,
+ storageExpansionBonus: storageExpansion,
+ totalStorageBonusWithUpgrades: totalStorageBonus
+ });
+
+ const oldPower = this.base.power;
+ const oldMaxPower = this.base.maxPower;
+ const oldStorage = this.base.storage;
+ const oldMaxStorage = this.base.maxStorage;
+
+ this.base.power = Math.max(0, totalPowerCost);
+ this.base.maxPower = 100 + ((this.upgrades.power_efficiency?.currentLevel || 0) * 20);
+
+ // Update inventory base slots with storage upgrades
+ if (this.game.systems.inventory) {
+ const newBaseSlots = 30 + totalStorageBonus; // 30 base + storage bonuses
+ const oldBaseSlots = this.game.systems.inventory.baseMaxSlots;
+
+ this.game.systems.inventory.baseMaxSlots = newBaseSlots;
+ this.game.systems.inventory.updateStarbaseBonusSlots(this.game.systems.inventory.starbaseBonusSlots);
+
+ if (debugLogger) debugLogger.logStep('Inventory slots updated', {
+ oldBaseSlots: oldBaseSlots,
+ newBaseSlots: newBaseSlots,
+ starbaseBonusSlots: this.game.systems.inventory.starbaseBonusSlots,
+ finalMaxSlots: this.game.systems.inventory.maxSlots
+ });
+ }
+
+ // Keep legacy storage for backward compatibility
+ this.base.storage = 1000 + (totalStorageBonus * 100);
+ this.base.maxStorage = this.base.storage;
+
+ if (debugLogger) debugLogger.endStep('BaseSystem.calculateBaseStats', {
+ powerChanges: {
+ oldPower: oldPower,
+ newPower: this.base.power,
+ oldMaxPower: oldMaxPower,
+ newMaxPower: this.base.maxPower
+ },
+ storageChanges: {
+ oldStorage: oldStorage,
+ newStorage: this.base.storage,
+ oldMaxStorage: oldMaxStorage,
+ newMaxStorage: this.base.maxStorage
+ },
+ totals: {
+ totalPowerCost: totalPowerCost,
+ totalStorageBonus: totalStorageBonus,
+ storageExpansionBonus: storageExpansion
+ },
+ roomsProcessed: this.base.rooms.length
+ });
+ }
+
+ // Base upgrades
+ upgradeBase(upgradeId) {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('BaseSystem.upgradeBase', {
+ upgradeId: upgradeId,
+ currentCredits: this.game.systems.economy.credits,
+ currentUpgradeLevel: this.upgrades[upgradeId] ? this.upgrades[upgradeId].currentLevel : null
+ });
+
+ const upgrade = this.upgrades[upgradeId];
+ if (!upgrade) {
+ if (debugLogger) debugLogger.endStep('BaseSystem.upgradeBase', {
+ success: false,
+ reason: 'Upgrade not found',
+ upgradeId: upgradeId
+ });
+ return false;
+ }
+
+ if (debugLogger) debugLogger.logStep('Upgrade found', {
+ upgradeName: upgrade.name,
+ currentLevel: upgrade.currentLevel,
+ maxLevel: upgrade.maxLevel,
+ baseCost: upgrade.cost
+ });
+
+ if (upgrade.currentLevel >= upgrade.maxLevel) {
+ this.game.showNotification('Upgrade at maximum level', 'warning', 3000);
+
+ if (debugLogger) debugLogger.endStep('BaseSystem.upgradeBase', {
+ success: false,
+ reason: 'Upgrade at maximum level',
+ upgradeId: upgradeId,
+ currentLevel: upgrade.currentLevel,
+ maxLevel: upgrade.maxLevel
+ });
+ return false;
+ }
+
+ const cost = upgrade.cost * (upgrade.currentLevel + 1);
+ if (this.game.systems.economy.credits < cost) {
+ this.game.showNotification('Not enough credits', 'error', 3000);
+
+ if (debugLogger) debugLogger.endStep('BaseSystem.upgradeBase', {
+ success: false,
+ reason: 'Insufficient credits',
+ upgradeId: upgradeId,
+ requiredCredits: cost,
+ currentCredits: this.game.systems.economy.credits
+ });
+ return false;
+ }
+
+ if (debugLogger) debugLogger.logStep('Upgrade validation passed', {
+ upgradeId: upgradeId,
+ currentLevel: upgrade.currentLevel,
+ newLevel: upgrade.currentLevel + 1,
+ cost: cost
+ });
+
+ // Apply upgrade
+ const oldCredits = this.game.systems.economy.credits;
+ this.game.systems.economy.removeCredits(cost);
+ upgrade.currentLevel++;
+
+ if (debugLogger) debugLogger.logStep('Upgrade applied', {
+ oldCredits: oldCredits,
+ newCredits: this.game.systems.economy.credits,
+ costDeducted: cost,
+ newUpgradeLevel: upgrade.currentLevel
+ });
+
+ // Apply effects
+ let statsUpdated = false;
+ if (upgrade.effect.powerReduction) {
+ if (debugLogger) debugLogger.logStep('Applying power reduction effect');
+ this.updateBaseStats();
+ statsUpdated = true;
+ }
+
+ if (upgrade.effect.storageBonus) {
+ if (debugLogger) debugLogger.logStep('Applying storage bonus effect');
+ this.updateBaseStats();
+ statsUpdated = true;
+ }
+
+ if (upgrade.effect.productionBonus) {
+ if (debugLogger) debugLogger.logStep('Applying production bonus effect', {
+ productionBonus: upgrade.effect.productionBonus
+ });
+ statsUpdated = true;
+ }
+
+ if (upgrade.effect.defenseBonus) {
+ if (debugLogger) debugLogger.logStep('Applying defense bonus effect', {
+ defenseBonus: upgrade.effect.defenseBonus
+ });
+ statsUpdated = true;
+ }
+
+ if (!statsUpdated) {
+ // For upgrades like storage_expansion that don't have effects in the effect object
+ if (debugLogger) debugLogger.logStep('Updating base stats for upgrade');
+ this.updateBaseStats();
+ }
+
+ this.game.showNotification(`${upgrade.name} upgraded to level ${upgrade.currentLevel}!`, 'success', 3000);
+
+ if (debugLogger) debugLogger.endStep('BaseSystem.upgradeBase', {
+ success: true,
+ upgradeId: upgradeId,
+ upgradeName: upgrade.name,
+ oldLevel: upgrade.currentLevel - 1,
+ newLevel: upgrade.currentLevel,
+ cost: cost,
+ finalCredits: this.game.systems.economy.credits,
+ effectsApplied: statsUpdated
+ });
+
+ return true;
+ }
+
+ // Base production
+ update(deltaTime) {
+ if (this.game.state.paused) {
+ return;
+ }
+
+ // Auto production from automation systems
+ const autoProductionRate = (this.upgrades.automation_systems?.currentLevel || 0) * 0.05;
+ if (autoProductionRate > 0) {
+ // Use real computer time delta
+ const production = (deltaTime / 1000) * autoProductionRate * 10; // 10 credits per second per level
+
+ this.game.systems.economy.addCredits(Math.floor(production), 'base_production');
+ }
+
+ // Power generation from power generators
+ const powerGenerators = this.base.rooms.filter(r => r.type === 'power_generator');
+ if (powerGenerators.length > 0) {
+ const powerGeneration = powerGenerators.reduce((total, gen) => total + Math.abs(gen.powerCost), 0);
+
+ // Power generation would affect other systems
+ }
+
+ // Research production
+ if ((this.upgrades.research_lab?.currentLevel || 0) > 0) {
+ const researchRate = (this.upgrades.research_lab?.currentLevel || 0) * 0.1;
+ // Research points generation would go here
+ }
+
+ // Resource processing
+ if ((this.upgrades.resource_processor?.currentLevel || 0) > 0) {
+ const processingRate = (this.upgrades.resource_processor?.currentLevel || 0) * 0.05;
+ // Resource processing would go here
+ }
+ }
+
+ // UI updates
+ updateUI() {
+ this.setupBaseNavigation();
+ this.updateBaseDisplay();
+ this.updateUpgradesList();
+ this.updateShipGallery();
+ this.updateStarbaseList();
+ }
+
+ // Base Navigation
+ setupBaseNavigation() {
+ const navButtons = document.querySelectorAll('.base-nav-btn');
+ navButtons.forEach(button => {
+ button.addEventListener('click', () => {
+ const view = button.dataset.view;
+ this.switchBaseView(view);
+ });
+ });
+ }
+
+ switchBaseView(view) {
+ // Hide all views
+ document.querySelectorAll('.base-view-content').forEach(content => {
+ content.classList.add('hidden');
+ });
+
+ // Remove active class from all buttons
+ document.querySelectorAll('.base-nav-btn').forEach(btn => {
+ btn.classList.remove('active');
+ });
+
+ // Show selected view
+ const selectedView = document.getElementById(`base-${view}`);
+ if (selectedView) {
+ selectedView.classList.remove('hidden');
+ }
+
+ // Add active class to clicked button
+ const activeBtn = document.querySelector(`[data-view="${view}"]`);
+ if (activeBtn) {
+ activeBtn.classList.add('active');
+ }
+
+ // Initialize view-specific content
+ if (view === 'visualization') {
+ this.initializeBaseVisualization();
+ } else if (view === 'ships') {
+ this.updateShipGallery();
+ } else if (view === 'starbases') {
+ this.updateStarbaseList();
+ this.updateStarbasePurchaseList();
+ }
+ }
+
+ // Base Visualization
+ initializeBaseVisualization() {
+ const canvas = document.getElementById('baseCanvas');
+ if (!canvas) return;
+
+ const ctx = canvas.getContext('2d');
+
+ // Clear canvas
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+
+ // Draw base grid
+ this.drawBaseVisualization(ctx, canvas);
+
+ // Update info overlay
+ this.updateBaseInfoOverlay();
+ }
+
+ drawBaseVisualization(ctx, canvas) {
+ const cellSize = 40;
+ const offsetX = (canvas.width - this.gridSize * cellSize) / 2;
+ const offsetY = (canvas.height - this.gridSize * cellSize) / 2;
+
+ // Draw grid
+ ctx.strokeStyle = '#333';
+ ctx.lineWidth = 1;
+
+ for (let y = 0; y <= this.gridSize; y++) {
+ ctx.beginPath();
+ ctx.moveTo(offsetX, offsetY + y * cellSize);
+ ctx.lineTo(offsetX + this.gridSize * cellSize, offsetY + y * cellSize);
+ ctx.stroke();
+ }
+
+ for (let x = 0; x <= this.gridSize; x++) {
+ ctx.beginPath();
+ ctx.moveTo(offsetX + x * cellSize, offsetY);
+ ctx.lineTo(offsetX + x * cellSize, offsetY + this.gridSize * cellSize);
+ ctx.stroke();
+ }
+
+ // Draw rooms
+ for (let y = 0; y < this.gridSize; y++) {
+ for (let x = 0; x < this.gridSize; x++) {
+ const roomId = this.grid[y][x];
+ if (roomId) {
+ const room = this.base.rooms.find(r => r.id === roomId);
+ if (room) {
+ this.drawRoom(ctx, room, x, y, cellSize, offsetX, offsetY);
+ }
+ }
+ }
+ }
+ }
+
+ drawRoom(ctx, room, gridX, gridY, cellSize, offsetX, offsetY) {
+ const x = offsetX + gridX * cellSize;
+ const y = offsetY + gridY * cellSize;
+
+ // Room colors by type
+ const roomColors = {
+ command_center: '#4CAF50',
+ power_generator: '#FFC107',
+ defense_turret: '#F44336',
+ storage: '#2196F3',
+ research_lab: '#9C27B0',
+ factory: '#FF9800',
+ medical_bay: '#00BCD4',
+ hangar: '#795548'
+ };
+
+ const color = roomColors[room.type] || '#666';
+
+ // Draw room
+ ctx.fillStyle = color;
+ ctx.fillRect(x + 2, y + 2, cellSize - 4, cellSize - 4);
+
+ // Draw room border
+ ctx.strokeStyle = '#fff';
+ ctx.lineWidth = 2;
+ ctx.strokeRect(x + 2, y + 2, cellSize - 4, cellSize - 4);
+
+ // Draw room icon/initial
+ ctx.fillStyle = '#fff';
+ ctx.font = 'bold 16px Arial';
+ ctx.textAlign = 'center';
+ ctx.textBaseline = 'middle';
+ ctx.fillText(room.name.charAt(0), x + cellSize/2, y + cellSize/2);
+ }
+
+ updateBaseInfoOverlay() {
+ const infoDisplay = document.getElementById('baseInfoDisplay');
+ if (!infoDisplay) return;
+
+ const totalRooms = this.base.rooms.length;
+ const powerUsage = this.base.power;
+ const maxPower = this.base.maxPower;
+ const storage = this.base.storage;
+ const maxStorage = this.base.maxStorage;
+
+ infoDisplay.innerHTML = `
+
+ Base Name: ${this.base.name}
+
+
+ Level: ${this.base.level}
+
+
+ Total Rooms: ${totalRooms}
+
+
+ Power Usage: ${powerUsage}/${maxPower}
+
+
+ Storage: ${storage}/${maxStorage}
+
+ `;
+ }
+
+ // Ship Gallery
+ initializeShipGallery() {
+ // Initialize purchased ships array
+ if (!this.purchasedShips) {
+ this.purchasedShips = [];
+
+ // Add current ship to gallery with proper timestamp ID
+ const player = this.game.systems.player;
+ if (player.ship) {
+ this.purchasedShips.push({
+ id: Date.now().toString(), // Use timestamp instead of hardcoded 'current_ship'
+ name: player.ship.name || 'Default Ship',
+ class: player.ship.class || 'fighter',
+ level: player.ship.level || 1,
+ stats: { ...player.attributes },
+ isCurrent: true
+ });
+ }
+ }
+ }
+
+ updateShipGallery() {
+ const shipGrid = document.getElementById('shipGrid');
+ if (!shipGrid) {
+ return;
+ }
+
+ if (!this.purchasedShips) {
+ this.initializeShipGallery();
+ }
+
+ if (!this.purchasedShips || this.purchasedShips.length === 0) {
+ shipGrid.innerHTML = 'No ships purchased yet. Visit the shop to buy ships!
';
+ return;
+ }
+
+ shipGrid.innerHTML = '';
+
+ this.purchasedShips.forEach(ship => {
+ const shipElement = document.createElement('div');
+ shipElement.className = `ship-card ${ship.isCurrent ? 'current-ship' : ''}`;
+
+ shipElement.innerHTML = `
+
+
+
Class: ${ship.class}
+
Level: ${ship.level}
+
+
+
Attack: ${ship.stats.attack || 0}
+
Speed: ${ship.stats.speed || 0}
+
Defense: ${ship.stats.defense || 0}
+
+ ${!ship.isCurrent ? `
+ Equip Ship
+ ` : ''}
+ `;
+
+ shipGrid.appendChild(shipElement);
+ });
+ }
+
+ equipShip(shipId) {
+ const ship = this.purchasedShips.find(s => s.id === shipId);
+ if (!ship) {
+ this.game.showNotification('Ship not found!', 'error', 3000);
+ return;
+ }
+
+ if (ship.isCurrent) {
+ this.game.showNotification('This ship is already equipped!', 'info', 3000);
+ return;
+ }
+
+ const player = this.game.systems.player;
+
+ player.ship.name = ship.name;
+ player.ship.class = ship.class;
+ player.ship.level = ship.level;
+ player.ship.texture = ship.texture; // Copy texture for image display
+
+ // Apply ship-specific stats (not just attributes)
+ if (ship.stats.health || ship.stats.hull) {
+ const healthValue = ship.stats.health || ship.stats.hull || 100;
+ player.ship.maxHealth = healthValue;
+ player.ship.health = healthValue;
+ }
+ if (ship.stats.attack) {
+ player.ship.attack = ship.stats.attack;
+ }
+ if (ship.stats.defense) {
+ player.ship.defense = ship.stats.defense;
+ }
+ if (ship.stats.speed) {
+ player.ship.speed = ship.stats.speed;
+ }
+ if (ship.stats.criticalChance) {
+ player.ship.criticalChance = ship.stats.criticalChance;
+ }
+ if (ship.stats.criticalDamage) {
+ player.ship.criticalDamage = ship.stats.criticalDamage;
+ }
+
+ // Also update player attributes for compatibility
+ player.attributes.attack = ship.stats.attack || player.attributes.attack;
+ player.attributes.speed = ship.stats.speed || player.attributes.speed;
+ player.attributes.defense = ship.stats.defense || player.attributes.defense;
+ if (ship.stats.health || ship.stats.hull) {
+ const healthValue = ship.stats.health || ship.stats.hull || 100;
+ player.attributes.maxHealth = healthValue;
+ player.attributes.health = healthValue;
+ }
+
+ // Update isCurrent flags
+ this.purchasedShips.forEach(s => s.isCurrent = false);
+ ship.isCurrent = true;
+
+ // Force immediate gallery update after flag changes
+ const shipGrid = document.getElementById('shipGrid');
+ if (shipGrid) {
+ shipGrid.innerHTML = '';
+ this.updateShipGallery();
+ }
+
+ // Also update ShipSystem's currentShip to keep systems synchronized
+ if (this.game.systems.ship) {
+ // Find or create a ship object in ShipSystem format
+ const shipSystemShip = {
+ id: ship.id,
+ name: ship.name,
+ image: ship.texture || `assets/textures/ships/${ship.class.toLowerCase()}.png`,
+ class: ship.class,
+ level: ship.level,
+ stats: ship.stats,
+ status: 'active',
+ rarity: 'Common'
+ };
+
+ // Update ShipSystem's current ship
+ this.game.systems.ship.currentShip = shipSystemShip;
+
+ // Update the ships array in ShipSystem if needed
+ const existingShipIndex = this.game.systems.ship.ships.findIndex(s => s.id === ship.id);
+ if (existingShipIndex >= 0) {
+ this.game.systems.ship.ships[existingShipIndex].status = 'active';
+ this.game.systems.ship.ships[existingShipIndex] = shipSystemShip;
+ } else {
+ // Add to ships array if not present
+ this.game.systems.ship.ships.push(shipSystemShip);
+ }
+
+ // Mark all other ships as inactive
+ this.game.systems.ship.ships.forEach(s => {
+ if (s.id !== ship.id) s.status = 'inactive';
+ });
+ }
+
+ // Only update player UI if in multiplayer mode or game is actively running
+ if (this.game.shouldUpdateGUI()) {
+ player.updateUI();
+ }
+
+ this.updateShipGallery();
+
+ // Also update ShipSystem display
+ if (this.game.systems.ship && this.game.systems.ship.updateCurrentShipDisplay) {
+ this.game.systems.ship.updateCurrentShipDisplay();
+ } else {
+ this.updateCurrentShipDisplayDirect();
+ }
+
+ // Force save the game to persist the ship change
+ this.game.save().then(() => {
+ // Force complete gallery refresh after save completes
+ const shipGrid = document.getElementById('shipGrid');
+ if (shipGrid) {
+ shipGrid.innerHTML = ''; // Clear completely
+ this.updateShipGallery(); // Rebuild from scratch
+ }
+
+ // Force ShipSystem to sync with new current ship
+ if (this.game.systems.ship && this.game.systems.ship.syncWithPlayerShip) {
+ this.game.systems.ship.syncWithPlayerShip();
+ }
+ });
+
+ this.game.showNotification(`Equipped ${ship.name}!`, 'success', 3000);
+ }
+
+ updateCurrentShipDisplayDirect() {
+ const player = this.game.systems.player;
+ if (!player || !player.ship) {
+ return;
+ }
+
+ const elements = {
+ currentShipImage: document.getElementById('currentShipImage'),
+ currentShipName: document.getElementById('currentShipName'),
+ currentShipClass: document.getElementById('currentShipClass'),
+ currentShipLevel: document.getElementById('currentShipLevel'),
+ currentShipHealth: document.getElementById('currentShipHealth'),
+ currentShipAttack: document.getElementById('currentShipAttack'),
+ currentShipDefense: document.getElementById('currentShipDefense'),
+ currentShipSpeed: document.getElementById('currentShipSpeed')
+ };
+
+ const ship = player.ship;
+
+ if (elements.currentShipImage) {
+ const imagePath = ship.texture || `assets/textures/ships/${ship.class.toLowerCase()}.png`;
+ console.log(`[DEBUG] Ship image path: ${imagePath}, texture: ${ship.texture}, class: ${ship.class}`);
+ elements.currentShipImage.src = imagePath;
+
+ // Add error handling for image loading
+ elements.currentShipImage.onerror = function() {
+ console.error(`[DEBUG] Failed to load image: ${imagePath}`);
+ // Fallback to default ship image
+ this.src = 'assets/textures/ships/starter_cruiser.png';
+ };
+ }
+
+ if (elements.currentShipName) elements.currentShipName.textContent = ship.name || 'Unknown Ship';
+ if (elements.currentShipClass) elements.currentShipClass.textContent = ship.class || 'Unknown';
+ if (elements.currentShipLevel) elements.currentShipLevel.textContent = ship.level || 1;
+ if (elements.currentShipHealth) elements.currentShipHealth.textContent = `${ship.health}/${ship.maxHealth}`;
+ if (elements.currentShipAttack) elements.currentShipAttack.textContent = ship.attack || 0;
+ if (elements.currentShipDefense) elements.currentShipDefense.textContent = ship.defense || ship.defence || 0;
+ if (elements.currentShipSpeed) elements.currentShipSpeed.textContent = ship.speed || 0;
+ }
+
+ addShipToGallery(shipData) {
+ this.purchasedShips.push({
+ id: Date.now().toString(),
+ name: shipData.name,
+ class: shipData.type,
+ level: 1,
+ stats: { ...shipData.stats },
+ texture: shipData.texture, // Include texture for image display
+ isCurrent: false
+ });
+
+ this.updateShipGallery();
+ }
+
+ // Starbase System
+ initializeStarbaseSystem() {
+ // Initialize starbase collection
+ if (!this.starbases) {
+ this.starbases = [
+ {
+ id: 'main_base',
+ name: 'Command Center Alpha',
+ type: 'command',
+ level: 1,
+ location: 'Sector Alpha-1',
+ isMain: true
+ }
+ ];
+ }
+
+ // Define available starbase types
+ this.starbaseTypes = {
+ mining: {
+ name: 'Mining Outpost',
+ description: 'Automated mining facility for resource extraction',
+ price: 25000,
+ currency: 'credits',
+ icon: 'fa-hammer',
+ benefits: ['+500 credits/hour', '+50 gems/day', '+5 inventory slots'],
+ requiredLevel: 5,
+ inventoryBonus: 5
+ },
+ research: {
+ name: 'Research Station',
+ description: 'Advanced research facility for technology development',
+ price: 50000,
+ currency: 'credits',
+ icon: 'fa-flask',
+ benefits: ['+100 experience/hour', '+10% skill progression', '+10 inventory slots'],
+ requiredLevel: 10,
+ inventoryBonus: 10
+ },
+ defense: {
+ name: 'Defense Platform',
+ description: 'Military installation for sector protection',
+ price: 40000,
+ currency: 'credits',
+ icon: 'fa-shield-alt',
+ benefits: ['+15% combat effectiveness', '+100 defense rating'],
+ requiredLevel: 8,
+ inventoryBonus: 8
+ },
+ trading: {
+ name: 'Trading Hub',
+ description: 'Commercial center for trade and commerce',
+ price: 75000,
+ currency: 'credits',
+ icon: 'fa-store',
+ benefits: ['+20% shop discounts', '+200 gems/day'],
+ requiredLevel: 12,
+ inventoryBonus: 15
+ }
+ };
+ }
+
+ updateStarbaseList() {
+ const starbaseList = document.getElementById('starbaseList');
+ if (!starbaseList) return;
+
+ starbaseList.innerHTML = '';
+
+ // Initialize starbases if not exists
+ if (!this.starbases) {
+ this.initializeStarbaseSystem();
+ }
+
+ if (!this.starbases || this.starbases.length === 0) {
+ starbaseList.innerHTML = 'No starbases owned yet.
';
+ return;
+ }
+
+ this.starbases.forEach(starbase => {
+ const starbaseElement = document.createElement('div');
+ starbaseElement.className = `starbase-card ${starbase.isMain ? 'main-starbase' : ''}`;
+
+ starbaseElement.innerHTML = `
+
+
+
Type: ${starbase.type}
+
Level: ${starbase.level}
+
Location: ${starbase.location}
+
+
+
+ View Details
+
+ ${!starbase.isMain ? `
+
+ Decommission
+
+ ` : ''}
+
+ `;
+
+ starbaseList.appendChild(starbaseElement);
+ });
+ }
+
+ updateStarbasePurchaseList() {
+ const purchaseList = document.getElementById('starbasePurchaseItems');
+ if (!purchaseList) return;
+
+ purchaseList.innerHTML = '';
+
+ const player = this.game.systems.player;
+
+ Object.entries(this.starbaseTypes).forEach(([type, starbase]) => {
+ const starbaseElement = document.createElement('div');
+ starbaseElement.className = 'starbase-purchase-card';
+
+ const canAfford = this.game.systems.economy.credits >= starbase.price;
+ const meetsLevel = player.stats.level >= starbase.requiredLevel;
+ const canPurchase = canAfford && meetsLevel;
+
+ starbaseElement.innerHTML = `
+
+
+ ${starbase.description}
+
+
+
Benefits:
+
+ ${starbase.benefits.map(benefit => `${benefit} `).join('')}
+
+
+
+
+ Cost: ${starbase.price} ${starbase.currency}
+
+
+ Required Level: ${starbase.requiredLevel}
+
+
+
+
+ ${!meetsLevel ? 'Level Required' : !canAfford ? 'Insufficient Funds' : 'Purchase Starbase'}
+
+
+ `;
+
+ purchaseList.appendChild(starbaseElement);
+ });
+ }
+
+ purchaseStarbase(type) {
+ const starbaseTemplate = this.starbaseTypes[type];
+ if (!starbaseTemplate) {
+ this.game.showNotification('Invalid starbase type!', 'error', 3000);
+ return;
+ }
+
+ const player = this.game.systems.player;
+ const economy = this.game.systems.economy;
+
+ // Check requirements
+ if (player.stats.level < starbaseTemplate.requiredLevel) {
+ this.game.showNotification(`Level ${starbaseTemplate.requiredLevel} required!`, 'error', 3000);
+ return;
+ }
+
+ if (economy.credits < starbaseTemplate.price) {
+ this.game.showNotification('Insufficient credits!', 'error', 3000);
+ return;
+ }
+
+ // Process payment
+ economy.credits -= starbaseTemplate.price;
+
+ // Create new starbase
+ const newStarbase = {
+ id: Date.now().toString(),
+ name: `${starbaseTemplate.name} ${this.starbases.length}`,
+ type: type,
+ level: 1,
+ location: `Sector ${String.fromCharCode(65 + this.starbases.length)}-${this.starbases.length}`,
+ isMain: false,
+ benefits: starbaseTemplate.benefits
+ };
+
+ this.starbases.push(newStarbase);
+
+ // Apply benefits
+ this.applyStarbaseBenefits(newStarbase);
+
+ // Only update UI if in multiplayer mode or game is actively running
+ if (this.game.shouldUpdateGUI()) {
+ economy.updateUI();
+ this.updateStarbaseList();
+ this.updateStarbasePurchaseList();
+ }
+
+ this.game.showNotification(`Purchased ${starbaseTemplate.name}!`, 'success', 4000);
+ }
+
+ applyStarbaseBenefits(starbase) {
+ // Apply passive benefits based on starbase type
+ switch (starbase.type) {
+ case 'mining':
+ // Start passive credit generation
+ this.startMiningProduction(starbase);
+ // Apply inventory bonus
+ this.updateInventoryBonusSlots();
+ break;
+ case 'research':
+ // Apply experience bonus
+ this.game.systems.player.experienceMultiplier = (this.game.systems.player.experienceMultiplier || 1) + 0.1;
+ // Apply inventory bonus
+ this.updateInventoryBonusSlots();
+ break;
+ case 'defense':
+ // Apply defense bonus
+ this.game.systems.player.attributes.defense = Math.floor(this.game.systems.player.attributes.defense * 1.2);
+ // Apply inventory bonus
+ this.updateInventoryBonusSlots();
+ break;
+ case 'trading':
+ // Apply shop discount
+ this.game.systems.economy.discountMultiplier = 0.85;
+ // Apply inventory bonus
+ this.updateInventoryBonusSlots();
+ break;
+ }
+ }
+
+ updateInventoryBonusSlots() {
+ // Calculate total inventory bonus from all owned starbases
+ let totalBonusSlots = 0;
+
+ if (this.starbases && this.starbases.length > 0) {
+ this.starbases.forEach(starbase => {
+ const starbaseType = this.starbaseTypes[starbase.type];
+ if (starbaseType && starbaseType.inventoryBonus) {
+ totalBonusSlots += starbaseType.inventoryBonus;
+ }
+ });
+ }
+
+ // Update inventory system with new bonus slots
+ if (this.game.systems.inventory) {
+ this.game.systems.inventory.updateStarbaseBonusSlots(totalBonusSlots);
+ }
+ }
+
+ startMiningProduction(starbase) {
+ // Only start mining if in multiplayer mode or game is actively running
+ const shouldStartMining = window.smartSaveManager?.isMultiplayer || this.game?.isRunning;
+
+ if (shouldStartMining && !this.miningInterval) {
+ // Set up passive credit generation
+ this.miningInterval = setInterval(() => {
+ this.game.systems.economy.addCredits(500, 'mining_outpost');
+ }, 3600000); // 1 hour
+
+ console.log('[BASE SYSTEM] Mining production started');
+ } else if (!shouldStartMining) {
+ console.log('[BASE SYSTEM] Skipping mining production - not in multiplayer mode');
+ }
+ }
+
+ viewStarbaseDetails(starbaseId) {
+ const starbase = this.starbases.find(s => s.id === starbaseId);
+ if (!starbase) return;
+
+ const details = `
+ ${starbase.name}
+ Type: ${starbase.type}
+ Level: ${starbase.level}
+ Location: ${starbase.location}
+ ${starbase.benefits ? `
+ Active Benefits:
+
+ ${starbase.benefits.map(benefit => `${benefit} `).join('')}
+
+ ` : ''}
+ `;
+
+ if (this.game && this.game.systems && this.game.systems.ui) {
+ this.game.systems.ui.showModal('Starbase Details', details);
+ }
+ }
+
+ decommissionStarbase(starbaseId) {
+ if (!confirm('Are you sure you want to decommission this starbase? You will receive 50% of its value back.')) {
+ return;
+ }
+
+ const starbaseIndex = this.starbases.findIndex(s => s.id === starbaseId);
+ if (starbaseIndex === -1) return;
+
+ const starbase = this.starbases[starbaseIndex];
+ const refundAmount = Math.floor(this.starbaseTypes[starbase.type].price * 0.5);
+
+ // Remove starbase
+ this.starbases.splice(starbaseIndex, 1);
+
+ // Give refund
+ this.game.systems.economy.addCredits(refundAmount, 'starbase_refund');
+
+ // Update inventory bonus slots after removal
+ this.updateInventoryBonusSlots();
+
+ // Update UI
+ this.updateStarbaseList();
+ this.updateStarbasePurchaseList();
+
+ this.game.showNotification(`Decommissioned ${starbase.name}. Refunded ${refundAmount} credits.`, 'info', 4000);
+ }
+
+ updateBaseDisplay() {
+ const baseRoomsElement = document.getElementById('baseRooms');
+ if (!baseRoomsElement) return;
+
+ // Ensure grid is initialized
+ if (!this.grid || this.grid.length === 0) {
+ console.log('[BASE SYSTEM] Grid not initialized, initializing now');
+ this.initializeGrid();
+ }
+
+ baseRoomsElement.innerHTML = '';
+
+ // Create grid container
+ const gridContainer = document.createElement('div');
+ gridContainer.className = 'base-grid';
+
+ // Create grid cells
+ for (let y = 0; y < this.gridSize; y++) {
+ for (let x = 0; x < this.gridSize; x++) {
+ const cell = document.createElement('div');
+ cell.className = 'base-cell';
+ cell.dataset.x = x;
+ cell.dataset.y = y;
+
+ const room = this.getRoomAt(x, y);
+ if (room) {
+ // Check if this is the main cell of the room
+ if (room.x === x && room.y === y) {
+ cell.className += ` room ${room.type}`;
+ cell.innerHTML = `
+
+
+
+ L${room.level}
+ `;
+ cell.title = `${room.name} (Level ${room.level})`;
+ } else {
+ // Other cells of the same room are just filled
+ cell.innerHTML = '';
+ }
+ cell.title = `${room.name} (Level ${room.level})`;
+ } else {
+ cell.className += ' empty';
+ cell.addEventListener('click', () => this.showRoomBuildMenu(x, y));
+ }
+
+ gridContainer.appendChild(cell);
+ }
+ }
+
+ baseRoomsElement.appendChild(gridContainer);
+
+ // Add base info with connected inventory storage
+ const baseInfo = document.createElement('div');
+ baseInfo.className = 'base-info';
+
+ // Get inventory information
+ const inventoryInfo = this.game.systems.inventory ? this.game.systems.inventory.getInventoryInfo() : null;
+
+ baseInfo.innerHTML = `
+ ${this.base.name}
+
+
Level: ${this.base.level}
+
Power: ${this.base.power}/${this.base.maxPower}
+
Storage: ${inventoryInfo ? `${inventoryInfo.currentSlots}/${inventoryInfo.totalMaxSlots}` : `${this.base.storage}/${this.base.maxStorage}`} slots
+ ${inventoryInfo ? `
+ Base: ${inventoryInfo.baseMaxSlots} | Starbase Bonus: +${inventoryInfo.starbaseBonusSlots}
+
` : ''}
+
+ `;
+
+ baseRoomsElement.appendChild(baseInfo);
+ }
+
+ updateUpgradesList() {
+ const upgradesElement = document.getElementById('baseUpgrades');
+ if (!upgradesElement) return;
+
+ upgradesElement.innerHTML = '';
+
+ Object.entries(this.upgrades).forEach(([upgradeId, upgrade]) => {
+ const upgradeElement = document.createElement('div');
+ upgradeElement.className = 'upgrade-item';
+
+ const cost = upgrade.cost * (upgrade.currentLevel + 1);
+ const canAfford = this.game.systems.economy.credits >= cost;
+ const isMaxLevel = upgrade.currentLevel >= upgrade.maxLevel;
+
+ upgradeElement.innerHTML = `
+ ${upgrade.name}
+ ${upgrade.description}
+ Level: ${upgrade.currentLevel}/${upgrade.maxLevel}
+ Cost: ${cost} credits
+
+ ${isMaxLevel ? 'MAX' : 'Upgrade'}
+
+ `;
+
+ upgradesElement.appendChild(upgradeElement);
+ });
+}
+
+showRoomBuildMenu(x, y) {
+ const availableRooms = Object.entries(this.roomTypes)
+ .filter(([roomType, template]) =>
+ template.requiredLevel <= this.game.systems.player.stats.level
+ )
+ .filter(([roomType, template]) => this.canBuildRoom(roomType, x, y));
+
+ if (availableRooms.length === 0) {
+ this.game.showNotification('No rooms available for this location', 'warning', 3000);
+ return;
+ }
+
+ let menuContent = 'Build Room ';
+
+ availableRooms.forEach(([roomType, template]) => {
+ const roomSize = this.getRoomSize(template.size);
+ menuContent += `
+
+
+
+
Size: ${roomSize.width}x${roomSize.height}
+
Power: ${template.powerCost > 0 ? '+' : ''}${template.powerCost}
+
Cost: ${template.buildCost} credits
+
+
${template.description}
+
+ `;
+ });
+
+ menuContent += '
';
+}
+
+ updateUpgradesList() {
+ const upgradesElement = document.getElementById('baseUpgrades');
+ if (!upgradesElement) return;
+
+ upgradesElement.innerHTML = '';
+
+ Object.entries(this.upgrades).forEach(([upgradeId, upgrade]) => {
+ const upgradeElement = document.createElement('div');
+ upgradeElement.className = 'upgrade-item';
+
+ const cost = upgrade.cost * (upgrade.currentLevel + 1);
+ const canAfford = this.game.systems.economy.credits >= cost;
+ const isMaxLevel = upgrade.currentLevel >= upgrade.maxLevel;
+
+ upgradeElement.innerHTML = `
+ ${upgrade.name}
+ ${upgrade.description}
+ Level: ${upgrade.currentLevel}/${upgrade.maxLevel}
+ Cost: ${cost} credits
+
+ ${isMaxLevel ? 'MAX' : 'Upgrade'}
+
+ `;
+
+ upgradesElement.appendChild(upgradeElement);
+ });
+ }
+
+ showRoomBuildMenu(x, y) {
+ const availableRooms = Object.entries(this.roomTypes)
+ .filter(([roomType, template]) =>
+ template.requiredLevel <= this.game.systems.player.stats.level
+ )
+ .filter(([roomType, template]) => this.canBuildRoom(roomType, x, y));
+
+ if (availableRooms.length === 0) {
+ this.game.showNotification('No rooms available for this location', 'warning', 3000);
+ return;
+ }
+
+ let menuContent = 'Build Room ';
+
+ availableRooms.forEach(([roomType, template]) => {
+ const roomSize = this.getRoomSize(template.size);
+ menuContent += `
+
+
+
+
Size: ${roomSize.width}x${roomSize.height}
+
Power: ${template.powerCost > 0 ? '+' : ''}${template.powerCost}
+
Cost: ${template.buildCost} credits
+
+
${template.description}
+
+ `;
+ });
+
+ menuContent += '
';
+
+ if (this.game && this.game.systems && this.game.systems.ui) {
+ this.game.systems.ui.showModal('Build Room', menuContent);
+ }
+ }
+
+ // Save/Load
+ save() {
+ const debugLogger = window.debugLogger;
+
+ // if (debugLogger) debugLogger.startStep('BaseSystem.save', {
+ // baseName: this.base.name,
+ // baseLevel: this.base.level,
+ // roomsCount: this.base.rooms.length,
+ // gridCellsCount: this.grid.flat().filter(cell => cell !== null).length,
+ // upgradesCount: Object.keys(this.upgrades).length
+ // });
+
+ const saveData = {
+ base: this.base,
+ grid: this.grid,
+ upgrades: this.upgrades,
+ purchasedShips: this.purchasedShips || []
+ };
+
+ // if (debugLogger) debugLogger.endStep('BaseSystem.save', {
+ // saveDataSize: JSON.stringify(saveData).length,
+ // baseSaved: {
+ // name: saveData.base.name,
+ // level: saveData.base.level,
+ // roomsCount: saveData.base.rooms.length
+ // },
+ // gridSaved: {
+ // gridSize: this.gridSize,
+ // occupiedCells: this.grid.flat().filter(cell => cell !== null).length
+ // },
+ // upgradesSaved: Object.keys(saveData.upgrades).map(id => ({
+ // id: id,
+ // currentLevel: saveData.upgrades[id].currentLevel
+ // })),
+ // saveData: saveData
+ // });
+
+ return saveData;
+ }
+
+ load(data) {
+ const debugLogger = window.debugLogger;
+ const oldState = {
+ baseName: this.base.name,
+ baseLevel: this.base.level,
+ roomsCount: this.base.rooms.length,
+ upgradesState: Object.keys(this.upgrades).map(id => ({
+ id: id,
+ currentLevel: this.upgrades[id].currentLevel
+ }))
+ };
+
+ if (debugLogger) debugLogger.startStep('BaseSystem.load', {
+ oldState: oldState,
+ loadData: data
+ });
+
+ try {
+ if (data.base) {
+ const oldBase = { ...this.base };
+ this.base = { ...this.base, ...data.base };
+
+ if (debugLogger) debugLogger.logStep('Base data loaded', {
+ oldBase: {
+ name: oldBase.name,
+ level: oldBase.level,
+ roomsCount: oldBase.rooms.length
+ },
+ newBase: {
+ name: this.base.name,
+ level: this.base.level,
+ roomsCount: this.base.rooms.length
+ }
+ });
+ }
+
+ if (data.grid) {
+ const oldGrid = this.grid;
+ this.grid = data.grid;
+
+ if (debugLogger) debugLogger.logStep('Grid data loaded', {
+ oldGridCells: oldGrid.flat().filter(cell => cell !== null).length,
+ newGridCells: this.grid.flat().filter(cell => cell !== null).length,
+ gridSize: this.gridSize
+ });
+ }
+
+ if (data.upgrades) {
+ const oldUpgrades = Object.keys(this.upgrades).map(id => ({
+ id: id,
+ currentLevel: this.upgrades[id].currentLevel
+ }));
+
+ Object.entries(data.upgrades).forEach(([upgradeId, upgrade]) => {
+ if (this.upgrades[upgradeId]) {
+ Object.assign(this.upgrades[upgradeId], upgrade);
+
+ if (debugLogger) debugLogger.logStep('Upgrade loaded', {
+ upgradeId: upgradeId,
+ oldLevel: oldUpgrades.find(u => u.id === upgradeId)?.currentLevel || 0,
+ newLevel: this.upgrades[upgradeId].currentLevel
+ });
+ }
+ });
+ }
+
+ // Load purchased ships
+ if (data.purchasedShips) {
+ this.purchasedShips = data.purchasedShips;
+ if (debugLogger) debugLogger.logStep('Purchased ships loaded', {
+ shipsCount: this.purchasedShips.length,
+ shipNames: this.purchasedShips.map(s => s.name)
+ });
+
+ // Find the current ship from gallery and sync with player
+ const currentGalleryShip = this.purchasedShips.find(s => s.isCurrent);
+ if (currentGalleryShip) {
+ const player = this.game.systems.player;
+ if (player && player.ship) {
+ console.log('[DEBUG] Syncing player ship with gallery current ship:', currentGalleryShip);
+
+ // Update player's ship with gallery current ship data
+ player.ship.name = currentGalleryShip.name;
+ player.ship.class = currentGalleryShip.class;
+ player.ship.level = currentGalleryShip.level;
+ player.ship.texture = currentGalleryShip.texture;
+
+ // Update player attributes with ship stats
+ if (currentGalleryShip.stats) {
+ Object.assign(player.attributes, currentGalleryShip.stats);
+
+ // Update player.ship properties for consistency
+ player.ship.health = currentGalleryShip.stats.health || currentGalleryShip.stats.hull || 100;
+ player.ship.maxHealth = currentGalleryShip.stats.maxHealth || currentGalleryShip.stats.hull || 100;
+ player.ship.attack = currentGalleryShip.stats.attack || 10;
+ player.ship.defence = currentGalleryShip.stats.defense || currentGalleryShip.stats.defence || 5;
+ player.ship.speed = currentGalleryShip.stats.speed || 10;
+ }
+
+ console.log('[DEBUG] Player ship updated:', player.ship);
+ console.log('[DEBUG] Player attributes updated:', player.attributes);
+
+ // Only update UI displays if in multiplayer mode or game is actively running
+ if (this.game.shouldUpdateGUI()) {
+ player.updateUI();
+ if (this.game.systems.ship && this.game.systems.ship.updateCurrentShipDisplay) {
+ this.game.systems.ship.updateCurrentShipDisplay();
+ }
+ }
+ }
+ }
+ }
+
+ // Update base stats after loading
+ if (debugLogger) debugLogger.logStep('Updating base stats after load');
+ this.updateBaseStats();
+
+ // Update ship gallery after loading ships
+ if (data.purchasedShips) {
+ this.updateShipGallery();
+ }
+
+ if (debugLogger) debugLogger.endStep('BaseSystem.load', {
+ success: true,
+ oldState: oldState,
+ newState: {
+ baseName: this.base.name,
+ baseLevel: this.base.level,
+ roomsCount: this.base.rooms.length,
+ upgradesState: Object.keys(this.upgrades).map(id => ({
+ id: id,
+ currentLevel: this.upgrades[id].currentLevel
+ }))
+ },
+ baseStatsUpdated: true
+ });
+ } catch (error) {
+ if (debugLogger) debugLogger.errorEvent('BaseSystem.load', error, {
+ oldState: oldState,
+ loadData: data,
+ error: error.message
+ });
+ throw error;
+ }
+ }
+
+reset() {
+ this.baseLevel = 1;
+ this.rooms = {};
+ // Reset upgrades to initial state
+ this.upgrades = {
+ power_efficiency: {
+ name: 'Power Efficiency',
+ description: 'Reduces power consumption of all rooms',
+ cost: 1000,
+ effect: { powerReduction: 0.1 },
+ maxLevel: 5,
+ currentLevel: 0
+ },
+ storage_expansion: {
+ name: 'Storage Expansion',
+ description: 'Increases inventory slots for item storage',
+ cost: 5000,
+ maxLevel: 10,
+ currentLevel: 0,
+ icon: 'fa-boxes',
+ slotBonus: 5
+ },
+ automation_systems: {
+ name: 'Automation Systems',
+ description: 'Automated resource generation and processing',
+ cost: 2500,
+ maxLevel: 10,
+ currentLevel: 0,
+ icon: 'fa-robot'
+ }
+ };
+ this.resources = {
+ energy: 100,
+ materials: 50,
+ research: 0
+ };
+ this.initializeBaseData();
+}
+
+updateBaseStats() {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('BaseSystem.updateBaseStats');
+
+ // Calculate total power and storage from rooms
+ let totalPower = 0;
+ let totalStorage = 0;
+
+ this.base.rooms.forEach(room => {
+ const roomTemplate = this.roomTypes[room.type];
+ if (roomTemplate) {
+ totalPower += roomTemplate.powerGeneration || 0;
+ totalStorage += roomTemplate.storageBonus || 0;
+ }
+ });
+
+ // Update base stats
+ this.base.power = Math.max(0, totalPower);
+ this.base.maxStorage = 1000 + totalStorage;
+
+ // Update UI if available
+ if (this.game.systems.ui && this.game.systems.ui.updateBaseSystem) {
+ this.game.systems.ui.updateBaseSystem();
+ }
+
+ // if (debugLogger) debugLogger.endStep('BaseSystem.updateBaseStats', {
+ // totalPower: this.base.power,
+ // maxStorage: this.base.maxStorage,
+ // roomsCount: this.base.rooms.length
+ // });
+ }
+}
diff --git a/Client-Server/js/systems/CraftingSystem.js b/Client-Server/js/systems/CraftingSystem.js
new file mode 100644
index 0000000..a21618e
--- /dev/null
+++ b/Client-Server/js/systems/CraftingSystem.js
@@ -0,0 +1,657 @@
+/**
+ * Galaxy Strike Online - Crafting System
+ * Handles item crafting, recipes, and crafting skill progression
+ */
+
+class CraftingSystem extends BaseSystem {
+ constructor(gameEngine) {
+ super(gameEngine);
+
+ this.recipes = new Map();
+ this.currentCategory = 'weapons';
+ this.selectedRecipe = null;
+
+ this.initializeRecipes();
+ }
+
+ initializeRecipes() {
+ // Weapon Recipes
+ this.addRecipe('basic_blaster', {
+ name: 'Basic Blaster',
+ category: 'weapons',
+ description: 'A simple energy blaster for beginners',
+ requirements: {
+ weapon_crafting: 1,
+ crafting: 1
+ },
+ materials: [
+ { id: 'iron_ore', quantity: 5 },
+ { id: 'energy_crystal', quantity: 2 }
+ ],
+ results: [
+ { id: 'basic_blaster', quantity: 1 }
+ ],
+ experience: 10,
+ craftingTime: 3000 // 3 seconds
+ });
+
+ this.addRecipe('enhanced_blaster', {
+ name: 'Enhanced Blaster',
+ category: 'weapons',
+ description: 'An improved blaster with better damage output',
+ requirements: {
+ weapon_crafting: 3,
+ crafting: 5
+ },
+ materials: [
+ { id: 'iron_ore', quantity: 10 },
+ { id: 'energy_crystal', quantity: 5 },
+ { id: 'copper_wire', quantity: 3 }
+ ],
+ results: [
+ { id: 'enhanced_blaster', quantity: 1 }
+ ],
+ experience: 25,
+ craftingTime: 5000 // 5 seconds
+ });
+
+ this.addRecipe('laser_sniper_rifle', {
+ name: 'Laser Sniper Rifle',
+ category: 'weapons',
+ description: 'A long-range precision laser weapon',
+ requirements: {
+ weapon_crafting: 3,
+ crafting: 5
+ },
+ materials: [
+ { id: 'advanced_circuitboard', quantity: 2 },
+ { id: 'energy_crystal', quantity: 8 },
+ { id: 'steel_plate', quantity: 5 },
+ { id: 'copper_wire', quantity: 4 }
+ ],
+ results: [
+ { id: 'laser_sniper_rifle', quantity: 1 }
+ ],
+ experience: 40,
+ craftingTime: 8000 // 8 seconds
+ });
+
+ this.addRecipe('plasma_cannon', {
+ name: 'Plasma Cannon',
+ category: 'weapons',
+ description: 'A devastating plasma-based weapon',
+ requirements: {
+ weapon_crafting: 5,
+ crafting: 7
+ },
+ materials: [
+ { id: 'advanced_components', quantity: 3 },
+ { id: 'energy_crystal', quantity: 12 },
+ { id: 'steel_plate', quantity: 8 },
+ { id: 'battery', quantity: 3 }
+ ],
+ results: [
+ { id: 'plasma_cannon', quantity: 1 }
+ ],
+ experience: 60,
+ craftingTime: 12000 // 12 seconds
+ });
+
+ // Armor Recipes
+ this.addRecipe('basic_armor', {
+ name: 'Basic Armor',
+ category: 'armor',
+ description: 'Light armor providing basic protection',
+ requirements: {
+ armor_forging: 1,
+ crafting: 1
+ },
+ materials: [
+ { id: 'iron_ore', quantity: 8 },
+ { id: 'leather', quantity: 3 }
+ ],
+ results: [
+ { id: 'basic_armor', quantity: 1 }
+ ],
+ experience: 15,
+ craftingTime: 4000 // 4 seconds
+ });
+
+ this.addRecipe('reinforced_armor', {
+ name: 'Reinforced Armor',
+ category: 'armor',
+ description: 'Heavy armor with enhanced protection',
+ requirements: {
+ armor_forging: 4,
+ crafting: 6
+ },
+ materials: [
+ { id: 'iron_ore', quantity: 15 },
+ { id: 'steel_plate', quantity: 5 },
+ { id: 'leather', quantity: 5 }
+ ],
+ results: [
+ { id: 'reinforced_armor', quantity: 1 }
+ ],
+ experience: 35,
+ craftingTime: 6000 // 6 seconds
+ });
+
+ // Item Recipes
+ this.addRecipe('health_kit', {
+ name: 'Health Kit',
+ category: 'items',
+ description: 'A medical kit that restores health',
+ requirements: {
+ crafting: 2
+ },
+ materials: [
+ { id: 'herbs', quantity: 3 },
+ { id: 'bandages', quantity: 2 }
+ ],
+ results: [
+ { id: 'health_kit', quantity: 3 }
+ ],
+ experience: 5,
+ craftingTime: 2000 // 2 seconds
+ });
+
+ this.addRecipe('basic_circuit', {
+ name: 'Basic Circuit',
+ category: 'items',
+ description: 'Create a basic electronic circuit',
+ requirements: {
+ crafting: 3
+ },
+ materials: [
+ { id: 'basic_circuitboard', quantity: 1 },
+ { id: 'copper_wire', quantity: 2 }
+ ],
+ results: [
+ { id: 'basic_circuit', quantity: 1 }
+ ],
+ experience: 8,
+ craftingTime: 2500 // 2.5 seconds
+ });
+
+ this.addRecipe('advanced_circuit', {
+ name: 'Advanced Circuit',
+ category: 'items',
+ description: 'Create an advanced electronic circuit',
+ requirements: {
+ crafting: 5
+ },
+ materials: [
+ { id: 'advanced_circuitboard', quantity: 1 },
+ { id: 'energy_crystal', quantity: 2 },
+ { id: 'copper_wire', quantity: 3 }
+ ],
+ results: [
+ { id: 'advanced_circuit', quantity: 1 }
+ ],
+ experience: 15,
+ craftingTime: 4000 // 4 seconds
+ });
+
+ this.addRecipe('electronic_device', {
+ name: 'Electronic Device',
+ category: 'items',
+ description: 'Create a complex electronic device',
+ requirements: {
+ crafting: 7
+ },
+ materials: [
+ { id: 'advanced_components', quantity: 1 },
+ { id: 'battery', quantity: 2 },
+ { id: 'common_circuitboard', quantity: 1 }
+ ],
+ results: [
+ { id: 'electronic_device', quantity: 1 }
+ ],
+ experience: 20,
+ craftingTime: 5000 // 5 seconds
+ });
+
+ // Ship Component Recipes
+ this.addRecipe('shield_generator', {
+ name: 'Shield Generator',
+ category: 'ships',
+ description: 'A basic shield generator for ship protection',
+ requirements: {
+ engineering: 2,
+ crafting: 4
+ },
+ materials: [
+ { id: 'energy_crystal', quantity: 8 },
+ { id: 'steel_plate', quantity: 5 },
+ { id: 'copper_wire', quantity: 4 }
+ ],
+ results: [
+ { id: 'shield_generator', quantity: 1 }
+ ],
+ experience: 30,
+ craftingTime: 8000 // 8 seconds
+ });
+
+ this.addRecipe('engine_upgrade', {
+ name: 'Engine Upgrade',
+ category: 'ships',
+ description: 'An upgrade that improves ship engine performance',
+ requirements: {
+ engineering: 5,
+ crafting: 7
+ },
+ materials: [
+ { id: 'steel_plate', quantity: 10 },
+ { id: 'energy_crystal', quantity: 6 },
+ { id: 'rare_metal', quantity: 2 }
+ ],
+ results: [
+ { id: 'engine_upgrade', quantity: 1 }
+ ],
+ experience: 50,
+ craftingTime: 10000 // 10 seconds
+ });
+
+ this.addRecipe('quantum_computer', {
+ name: 'Quantum Computer',
+ category: 'ships',
+ description: 'Advanced quantum computer for high-end ship systems',
+ requirements: {
+ engineering: 8,
+ crafting: 10
+ },
+ materials: [
+ { id: 'advanced_components', quantity: 3 },
+ { id: 'energy_crystal', quantity: 8 },
+ { id: 'rare_metal', quantity: 4 },
+ { id: 'copper_wire', quantity: 6 }
+ ],
+ results: [
+ { id: 'quantum_computer', quantity: 1 }
+ ],
+ experience: 100,
+ craftingTime: 15000 // 15 seconds
+ });
+ }
+
+ addRecipe(id, recipe) {
+ recipe.id = id;
+ recipe.unlocked = false;
+ this.recipes.set(id, recipe);
+ }
+
+ update(deltaTime) {
+ // Check for newly unlocked recipes
+ this.checkRecipeUnlocks();
+ }
+
+ checkRecipeUnlocks() {
+ const skillSystem = this.game.systems.skillSystem;
+ if (!skillSystem) return;
+
+ for (const [id, recipe] of this.recipes) {
+ if (!recipe.unlocked) {
+ let canCraft = true;
+
+ // Check skill requirements
+ if (recipe.requirements) {
+ for (const [skillName, requiredLevel] of Object.entries(recipe.requirements)) {
+ const skillLevel = skillSystem.getSkillLevel(skillName);
+ if (skillLevel < requiredLevel) {
+ canCraft = false;
+ break;
+ }
+ }
+ }
+
+ if (canCraft) {
+ recipe.unlocked = true;
+ console.log(`[CRAFTING] Recipe unlocked: ${recipe.name}`);
+ }
+ }
+ }
+ }
+
+ getRecipesByCategory(category) {
+ return Array.from(this.recipes.values())
+ .filter(recipe => recipe.category === category);
+ }
+
+ canCraftRecipe(recipeId) {
+ const recipe = this.recipes.get(recipeId);
+ if (!recipe) return false;
+
+ // Check skill requirements
+ if (recipe.requirements) {
+ const skillSystem = this.game.systems.skillSystem;
+ if (!skillSystem) return false;
+
+ for (const [skillName, requiredLevel] of Object.entries(recipe.requirements)) {
+ const skillLevel = skillSystem.getSkillLevel(skillName);
+ if (skillLevel < requiredLevel) {
+ return false;
+ }
+ }
+ }
+
+ // Check materials
+ if (recipe.materials) {
+ for (const material of recipe.materials) {
+ const inventory = this.game.systems.inventory;
+ if (!inventory || !inventory.hasItem(material.id, material.quantity)) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ getMissingMaterials(recipeId) {
+ const recipe = this.recipes.get(recipeId);
+ if (!recipe || !recipe.materials) return [];
+
+ const missing = [];
+ const inventory = this.game.systems.inventory;
+
+ console.log(`[CRAFTING DEBUG] Checking materials for recipe: ${recipe.name}`);
+ console.log(`[CRAFTING DEBUG] Inventory system:`, inventory);
+
+ for (const material of recipe.materials) {
+ let currentCount = 0;
+
+ // Safely get current material count
+ if (inventory && typeof inventory.getItemCount === 'function') {
+ try {
+ currentCount = inventory.getItemCount(material.id);
+ // Ensure we have a valid number
+ currentCount = typeof currentCount === 'number' && !isNaN(currentCount) ? currentCount : 0;
+ } catch (error) {
+ console.log(`[CRAFTING DEBUG] Error getting count for ${material.id}:`, error);
+ currentCount = 0;
+ }
+ console.log(`[CRAFTING DEBUG] Material ${material.id}: current=${currentCount}, required=${material.quantity}`);
+ } else {
+ console.log(`[CRAFTING DEBUG] Inventory or getItemCount not available for ${material.id}`);
+ currentCount = 0;
+ }
+
+ // Ensure required quantity is also a valid number
+ const requiredQuantity = typeof material.quantity === 'number' && !isNaN(material.quantity) ? material.quantity : 0;
+
+ // Check if we have enough materials
+ if (currentCount < requiredQuantity) {
+ missing.push({
+ id: material.id,
+ required: requiredQuantity,
+ current: currentCount,
+ missing: Math.max(0, requiredQuantity - currentCount)
+ });
+ }
+ }
+
+ console.log(`[CRAFTING DEBUG] Missing materials:`, missing);
+ return missing;
+ }
+
+ async craftRecipe(recipeId) {
+ const recipe = this.recipes.get(recipeId);
+ if (!recipe) {
+ console.error(`[CRAFTING] Recipe not found: ${recipeId}`);
+ return false;
+ }
+
+ if (!this.canCraftRecipe(recipeId)) {
+ console.log(`[CRAFTING] Cannot craft recipe: ${recipe.name}`);
+ return false;
+ }
+
+ console.log(`[CRAFTING] Starting to craft: ${recipe.name}`);
+
+ // Remove materials
+ if (recipe.materials) {
+ for (const material of recipe.materials) {
+ this.game.systems.inventory.removeItem(material.id, material.quantity);
+ }
+ }
+
+ // Add crafting experience
+ if (recipe.experience) {
+ this.game.systems.skillSystem.awardCraftingExperience(recipe.experience);
+ }
+
+ // Wait for crafting time
+ await new Promise(resolve => setTimeout(resolve, recipe.craftingTime));
+
+ // Add results to inventory
+ if (recipe.results) {
+ for (const result of recipe.results) {
+ this.game.systems.inventory.addItem(result.id, result.quantity);
+ }
+ }
+
+ // Update quest progress
+ if (this.game.systems.questSystem) {
+ this.game.systems.questSystem.onItemCrafted();
+ }
+
+ console.log(`[CRAFTING] Successfully crafted: ${recipe.name}`);
+ return true;
+ }
+
+ selectRecipe(recipeId) {
+ this.selectedRecipe = this.recipes.get(recipeId);
+ return this.selectedRecipe;
+ }
+
+ getSelectedRecipe() {
+ return this.selectedRecipe;
+ }
+
+ updateUI() {
+ this.updateRecipeList();
+ this.updateCraftingDetails();
+ this.updateCraftingInfo();
+ }
+
+ updateRecipeList() {
+ const recipeListElement = document.getElementById('recipeList');
+ if (!recipeListElement) return;
+
+ const recipes = this.getRecipesByCategory(this.currentCategory);
+
+ recipeListElement.innerHTML = '';
+
+ if (recipes.length === 0) {
+ recipeListElement.innerHTML = 'No recipes available in this category
';
+ return;
+ }
+
+ recipes.forEach(recipe => {
+ const recipeElement = document.createElement('div');
+ recipeElement.className = 'recipe-item';
+ recipeElement.dataset.recipeId = recipe.id;
+
+ const canCraft = this.canCraftRecipe(recipe.id);
+ const missingMaterials = this.getMissingMaterials(recipe.id);
+
+ // Check if recipe is unlocked (skill requirements met)
+ const skillSystem = this.game.systems.skillSystem;
+ let skillRequirementsMet = true;
+ if (recipe.requirements && skillSystem) {
+ for (const [skillName, requiredLevel] of Object.entries(recipe.requirements)) {
+ const skillLevel = skillSystem.getSkillLevel(skillName);
+ if (skillLevel < requiredLevel) {
+ skillRequirementsMet = false;
+ break;
+ }
+ }
+ }
+
+ // Apply styling based on status
+ if (!skillRequirementsMet) {
+ recipeElement.classList.add('locked');
+ } else if (!canCraft) {
+ recipeElement.classList.add('missing-materials');
+ } else {
+ recipeElement.classList.add('can-craft');
+ }
+
+ // Generate requirements text
+ const requirementsText = recipe.requirements ?
+ Object.entries(recipe.requirements).map(([skill, level]) => `${skill}: ${level}`).join(', ') : 'None';
+
+ // Generate materials with missing status
+ const materialsHtml = recipe.materials ? recipe.materials.map(mat => {
+ const missing = missingMaterials.find(m => m.id === mat.id);
+ const currentCount = missing ? missing.current : 0;
+ const requiredCount = mat.quantity || 0;
+
+ if (missing) {
+ return `
+ ${mat.id}
+ ${currentCount}/${requiredCount}
+
`;
+ } else {
+ return `
+ ${mat.id}
+ ${currentCount}/${requiredCount}
+
`;
+ }
+ }).join('') : '';
+
+ recipeElement.innerHTML = `
+
+ ${recipe.description}
+
+ ${materialsHtml}
+
+ ${missingMaterials.length > 0 ? `
+
+
+ Missing: ${missingMaterials.map(m => `${m.missing}x ${m.id}`).join(', ')}
+
+ ` : ''}
+
+
+ ${recipe.craftingTime / 1000}s
+
+ `;
+
+ recipeElement.addEventListener('click', () => {
+ this.selectRecipe(recipe.id);
+ this.updateCraftingDetails();
+ });
+
+ recipeListElement.appendChild(recipeElement);
+ });
+ }
+
+ updateCraftingDetails() {
+ const detailsElement = document.getElementById('craftingDetails');
+ if (!detailsElement) return;
+
+ if (!this.selectedRecipe) {
+ detailsElement.innerHTML = `
+
+
Select a Recipe
+
Choose a recipe from the list to see details and craft items.
+
+ `;
+ return;
+ }
+
+ const recipe = this.selectedRecipe;
+ const canCraft = this.canCraftRecipe(recipe.id);
+
+ detailsElement.innerHTML = `
+
+
${recipe.name}
+
${recipe.description}
+
+
+
Requirements:
+ ${recipe.requirements ? Object.entries(recipe.requirements).map(([skill, level]) =>
+ `
+ ${skill}
+ Level ${level}
+
`
+ ).join('') : '
No special requirements
'}
+
+
+
+
Materials Needed:
+ ${recipe.materials ? recipe.materials.map(mat =>
+ `
+ ${mat.id}
+ x${mat.quantity}
+ Have: ${this.game.systems.inventory?.getItemCount(mat.id) || 0}
+
`
+ ).join('') : '
No materials needed
'}
+
+
+
+
Results:
+ ${recipe.results ? recipe.results.map(result =>
+ `
+ ${result.id}
+ x${result.quantity}
+
`
+ ).join('') : ''}
+
+
+
+
+
+ ${recipe.experience} XP
+
+
+
+ ${recipe.craftingTime / 1000} seconds
+
+
+
+
+ ${canCraft ? 'Craft Item' : 'Cannot Craft'}
+
+
+ `;
+ }
+
+ updateCraftingInfo() {
+ const skillSystem = this.game.systems.skillSystem;
+ if (!skillSystem) return;
+
+ const craftingLevel = skillSystem.getSkillLevel('crafting');
+ const craftingExp = skillSystem.getSkillExperience('crafting');
+ const expNeeded = skillSystem.getExperienceNeeded('crafting');
+
+ const levelElement = document.getElementById('craftingLevel');
+ const expElement = document.getElementById('craftingExp');
+
+ if (levelElement) levelElement.textContent = craftingLevel;
+ if (expElement) expElement.textContent = `${craftingExp}/${expNeeded}`;
+ }
+
+ switchCategory(category) {
+ this.currentCategory = category;
+ this.selectedRecipe = null;
+
+ // Update UI only if in multiplayer mode or game is actively running
+ const shouldUpdateUI = window.smartSaveManager?.isMultiplayer || this.game?.isRunning;
+
+ if (shouldUpdateUI) {
+ this.updateUI();
+ }
+ }
+}
+
+// Export for use in GameEngine
+if (typeof module !== 'undefined' && module.exports) {
+ module.exports = CraftingSystem;
+}
diff --git a/Client-Server/js/systems/DungeonSystem.js b/Client-Server/js/systems/DungeonSystem.js
new file mode 100644
index 0000000..851c485
--- /dev/null
+++ b/Client-Server/js/systems/DungeonSystem.js
@@ -0,0 +1,1985 @@
+/**
+ * Galaxy Strike Online - Dungeon System
+ * Manages procedural dungeon generation and exploration
+ */
+
+class DungeonSystem {
+ constructor(gameEngine) {
+ this.game = gameEngine;
+
+ // Dungeon configuration
+ this.dungeonTypes = {
+ tutorial: {
+ name: 'Tutorial Dungeon',
+ description: 'Learn the basics of dungeon exploration in this guided tutorial',
+ difficulty: 'tutorial',
+ enemyTypes: ['training_drone', 'practice_target'],
+ rewardMultiplier: 0.5,
+ oneTimeOnly: true,
+ healthType: 'player', // Ground mission
+ energyCost: 0
+ },
+ alien_ruins: {
+ name: 'Alien Ruins',
+ description: 'Ancient alien structures filled with mysterious technology',
+ difficulty: 'medium',
+ enemyTypes: ['alien_guardian', 'ancient_drone', 'crystal_golem'],
+ rewardMultiplier: 1.2,
+ healthType: 'player', // Ground mission
+ energyCost: 20
+ },
+ pirate_lair: {
+ name: 'Pirate Lair',
+ description: 'Dangerous pirate hideouts with valuable loot',
+ difficulty: 'easy',
+ enemyTypes: ['space_pirate', 'pirate_captain', 'defense_turret'],
+ rewardMultiplier: 1.0,
+ healthType: 'ship', // Space mission
+ energyCost: 15
+ },
+ corrupted_vault: {
+ name: 'Corrupted AI Vault',
+ description: ' malfunctioning AI facilities with corrupted security',
+ difficulty: 'hard',
+ enemyTypes: ['security_drone', 'corrupted_ai', 'virus_program'],
+ rewardMultiplier: 1.5,
+ healthType: 'ship', // Space mission
+ energyCost: 25
+ },
+ asteroid_mine: {
+ name: 'Asteroid Mine',
+ description: 'Abandoned mining facilities in asteroid fields',
+ difficulty: 'easy',
+ enemyTypes: ['mining_drone', 'rock_creature', 'explosive_asteroid'],
+ rewardMultiplier: 0.8,
+ healthType: 'ship', // Space mission
+ energyCost: 10
+ },
+ nebula_anomaly: {
+ name: 'Nebula Anomaly',
+ description: 'Strange energy anomalies in deep space',
+ difficulty: 'extreme',
+ enemyTypes: ['energy_being', 'phase_shifter', 'quantum_entity'],
+ rewardMultiplier: 2.0,
+ healthType: 'ship', // Space mission
+ energyCost: 30
+ },
+
+ // NEW DUNGEONS - Space Theme
+ space_station: {
+ name: 'Abandoned Space Station',
+ description: 'A derelict space station floating in the void',
+ difficulty: 'medium',
+ enemyTypes: ['maintenance_drone', 'security_android', 'station_ai'],
+ rewardMultiplier: 1.3,
+ healthType: 'player',
+ energyCost: 22
+ },
+ comet_core: {
+ name: 'Comet Core',
+ description: 'The frozen heart of a passing comet',
+ difficulty: 'hard',
+ enemyTypes: ['ice_elemental', 'frost_wyrm', 'crystal_guardian'],
+ rewardMultiplier: 1.6,
+ healthType: 'ship',
+ energyCost: 28
+ },
+ black_hole_perimeter: {
+ name: 'Black Hole Perimeter',
+ description: 'Dangerous space near a black hole event horizon',
+ difficulty: 'extreme',
+ enemyTypes: ['gravity_wraith', 'void_stalker', 'singularity_spawn'],
+ rewardMultiplier: 2.5,
+ healthType: 'ship',
+ energyCost: 35
+ },
+ star_forge: {
+ name: 'Star Forge',
+ description: 'Ancient facility that harnesses stellar energy',
+ difficulty: 'hard',
+ enemyTypes: ['plasma_elemental', 'solar_guardian', 'fusion_core'],
+ rewardMultiplier: 1.8,
+ healthType: 'ship',
+ energyCost: 30
+ },
+ debris_field: {
+ name: 'Ship Debris Field',
+ description: 'Graveyard of destroyed spacecraft',
+ difficulty: 'easy',
+ enemyTypes: ['scrap_golem', 'hull_breacher', 'salage_drone'],
+ rewardMultiplier: 0.9,
+ healthType: 'ship',
+ energyCost: 12
+ },
+
+ // NEW DUNGEONS - Planet Theme
+ jungle_temple: {
+ name: 'Jungle Temple',
+ description: 'Overgrown temple hidden in dense alien jungle',
+ difficulty: 'medium',
+ enemyTypes: ['plant_beast', 'tribal_warrior', 'jungle_spirit'],
+ rewardMultiplier: 1.4,
+ healthType: 'player',
+ energyCost: 25
+ },
+ desert_pyramid: {
+ name: 'Desert Pyramid',
+ description: 'Ancient pyramid buried under endless sand dunes',
+ difficulty: 'hard',
+ enemyTypes: ['sand_worm', 'mummy_guardian', 'heat_elemental'],
+ rewardMultiplier: 1.7,
+ healthType: 'player',
+ energyCost: 28
+ },
+ volcanic_caverns: {
+ name: 'Volcanic Caverns',
+ description: 'Molten caverns deep within an active volcano',
+ difficulty: 'hard',
+ enemyTypes: ['lava_elemental', 'fire_demon', 'magma_beast'],
+ rewardMultiplier: 1.6,
+ healthType: 'player',
+ energyCost: 26
+ },
+ arctic_research: {
+ name: 'Arctic Research Base',
+ description: 'Frozen research facility with failed experiments',
+ difficulty: 'medium',
+ enemyTypes: ['cryo_mutant', 'frost_android', 'ice_wraith'],
+ rewardMultiplier: 1.5,
+ healthType: 'player',
+ energyCost: 24
+ },
+ swamp_lair: {
+ name: 'Swamp Lair',
+ description: 'Murky swamp inhabited by strange creatures',
+ difficulty: 'easy',
+ enemyTypes: ['swamp_beast', 'toxic_spitter', 'mud_golem'],
+ rewardMultiplier: 1.1,
+ healthType: 'player',
+ energyCost: 18
+ },
+
+ // NEW DUNGEONS - Technology Theme
+ cyber_realm: {
+ name: 'Cyber Realm',
+ description: 'Virtual reality space corrupted by malware',
+ difficulty: 'hard',
+ enemyTypes: ['glitch_wraith', 'firewall_guardian', 'data_vampire'],
+ rewardMultiplier: 1.9,
+ healthType: 'player',
+ energyCost: 32
+ },
+ robot_factory: {
+ name: 'Robot Factory',
+ description: 'Automated factory producing hostile machines',
+ difficulty: 'medium',
+ enemyTypes: ['assembly_drone', 'welder_bot', 'factory_overseer'],
+ rewardMultiplier: 1.4,
+ healthType: 'player',
+ energyCost: 23
+ },
+ quantum_lab: {
+ name: 'Quantum Laboratory',
+ description: 'Research facility experimenting with quantum physics',
+ difficulty: 'extreme',
+ enemyTypes: ['quantum_phantom', 'particle_accelerator', 'reality_bender'],
+ rewardMultiplier: 2.3,
+ healthType: 'player',
+ energyCost: 38
+ },
+ server_farm: {
+ name: 'Server Farm',
+ description: 'Massive data center with rogue security programs',
+ difficulty: 'medium',
+ enemyTypes: ['sentinel_program', 'data_corruptor', 'system_guardian'],
+ rewardMultiplier: 1.3,
+ healthType: 'player',
+ energyCost: 21
+ },
+
+ // NEW DUNGEONS - Biome/Elemental Theme
+ crystal_caves: {
+ name: 'Crystal Caves',
+ description: 'Caves filled with energy-infused crystals',
+ difficulty: 'medium',
+ enemyTypes: ['crystal_golem', 'shard_elemental', 'resonance_beast'],
+ rewardMultiplier: 1.4,
+ healthType: 'player',
+ energyCost: 22
+ },
+ toxic_wastes: {
+ name: 'Toxic Wastes',
+ description: 'Polluted wasteland filled with mutated creatures',
+ difficulty: 'hard',
+ enemyTypes: ['mutant_horror', 'toxic_slime', 'radiation_beast'],
+ rewardMultiplier: 1.7,
+ healthType: 'player',
+ energyCost: 27
+ },
+ shadow_realm: {
+ name: 'Shadow Realm',
+ description: 'Dark dimension inhabited by shadow creatures',
+ difficulty: 'extreme',
+ enemyTypes: ['shadow_demon', 'nightmare_stalker', 'void_walker'],
+ rewardMultiplier: 2.2,
+ healthType: 'player',
+ energyCost: 36
+ },
+ time_anomaly: {
+ name: 'Time Anomaly',
+ description: 'Area where time flows unpredictably',
+ difficulty: 'extreme',
+ enemyTypes: ['temporal_paradox', 'future_soldier', 'past_guardian'],
+ rewardMultiplier: 2.4,
+ healthType: 'player',
+ energyCost: 40
+ },
+
+ // NEW DUNGEONS - Military/War Theme
+ war_zone: {
+ name: 'Active War Zone',
+ description: 'Battlefield with ongoing combat operations',
+ difficulty: 'hard',
+ enemyTypes: ['enemy_soldier', 'combat_drone', 'field_commander'],
+ rewardMultiplier: 1.8,
+ healthType: 'player',
+ energyCost: 29
+ },
+ military_base: {
+ name: 'Abandoned Military Base',
+ description: 'Former military installation with automated defenses',
+ difficulty: 'medium',
+ enemyTypes: ['turret_system', 'combat_android', 'base_commander'],
+ rewardMultiplier: 1.5,
+ healthType: 'player',
+ energyCost: 24
+ },
+ weapons_testing: {
+ name: 'Weapons Testing Facility',
+ description: 'Secret facility testing advanced weaponry',
+ difficulty: 'hard',
+ enemyTypes: ['weapon_drone', 'test_subject', 'chief_scientist'],
+ rewardMultiplier: 1.9,
+ healthType: 'player',
+ energyCost: 31
+ },
+
+ // NEW DUNGEONS - Special/Unique Theme
+ dream_scape: {
+ name: 'Dream Scape',
+ description: 'Surreal landscape shaped by collective dreams',
+ difficulty: 'medium',
+ enemyTypes: ['nightmare_creature', 'dream_guardian', 'subconscious_demon'],
+ rewardMultiplier: 1.6,
+ healthType: 'player',
+ energyCost: 26
+ },
+ memory_palace: {
+ name: 'Memory Palace',
+ description: 'Mental realm storing forgotten memories',
+ difficulty: 'hard',
+ enemyTypes: ['memory_fragment', 'forgetfulness_demon', 'nostalgia_spirit'],
+ rewardMultiplier: 1.7,
+ healthType: 'player',
+ energyCost: 28
+ },
+ dimension_rift: {
+ name: 'Dimension Rift',
+ description: 'Tear between dimensions with interdimensional invaders',
+ difficulty: 'extreme',
+ enemyTypes: ['rift_demon', 'dimensional_hunter', 'reality_tear'],
+ rewardMultiplier: 2.6,
+ healthType: 'player',
+ energyCost: 42
+ }
+ };
+
+ // Current dungeon state
+ this.currentDungeon = null;
+ this.currentRoom = null;
+ this.dungeonProgress = 0;
+ this.isExploring = false;
+
+ // Dungeon templates
+ this.roomTypes = {
+ entrance: { name: 'Entrance', enemies: 0, rewards: false },
+ corridor: { name: 'Corridor', enemies: 1, rewards: false },
+ chamber: { name: 'Chamber', enemies: 2, rewards: true },
+ treasure: { name: 'Treasure Room', enemies: 0, rewards: true },
+ boss: { name: 'Boss Room', enemies: 1, rewards: true, isBoss: true },
+ exit: { name: 'Exit', enemies: 0, rewards: false }
+ };
+
+ // Enemy templates
+ this.enemyTemplates = {
+ // Original enemies
+ training_drone: {
+ name: 'Training Drone',
+ health: 10,
+ attack: 5,
+ defense: 2,
+ experience: 5,
+ credits: 3
+ },
+ practice_target: {
+ name: 'Practice Target',
+ health: 5,
+ attack: 0,
+ defense: 0,
+ experience: 2,
+ credits: 1
+ },
+ alien_guardian: {
+ name: 'Alien Guardian',
+ health: 50,
+ attack: 8,
+ defense: 5,
+ experience: 25,
+ credits: 15
+ },
+ ancient_drone: {
+ name: 'Ancient Drone',
+ health: 30,
+ attack: 12,
+ defense: 2,
+ experience: 20,
+ credits: 10
+ },
+ crystal_golem: {
+ name: 'Crystal Golem',
+ health: 80,
+ attack: 6,
+ defense: 10,
+ experience: 35,
+ credits: 25
+ },
+ space_pirate: {
+ name: 'Space Pirate',
+ health: 25,
+ attack: 10,
+ defense: 3,
+ experience: 15,
+ credits: 12
+ },
+ pirate_captain: {
+ name: 'Pirate Captain',
+ health: 40,
+ attack: 15,
+ defense: 6,
+ experience: 30,
+ credits: 20
+ },
+ defense_turret: {
+ name: 'Defense Turret',
+ health: 20,
+ attack: 18,
+ defense: 8,
+ experience: 18,
+ credits: 8
+ },
+ security_drone: {
+ name: 'Security Drone',
+ health: 35,
+ attack: 14,
+ defense: 4,
+ experience: 22,
+ credits: 15
+ },
+ corrupted_ai: {
+ name: 'Corrupted AI',
+ health: 60,
+ attack: 20,
+ defense: 2,
+ experience: 40,
+ credits: 30
+ },
+ virus_program: {
+ name: 'Virus Program',
+ health: 15,
+ attack: 25,
+ defense: 1,
+ experience: 20,
+ credits: 12
+ },
+ mining_drone: {
+ name: 'Mining Drone',
+ health: 20,
+ attack: 8,
+ defense: 3,
+ experience: 12,
+ credits: 8
+ },
+ rock_creature: {
+ name: 'Rock Creature',
+ health: 45,
+ attack: 6,
+ defense: 12,
+ experience: 25,
+ credits: 15
+ },
+ explosive_asteroid: {
+ name: 'Explosive Asteroid',
+ health: 10,
+ attack: 30,
+ defense: 0,
+ experience: 15,
+ credits: 5
+ },
+ energy_being: {
+ name: 'Energy Being',
+ health: 55,
+ attack: 22,
+ defense: 3,
+ experience: 45,
+ credits: 35
+ },
+ phase_shifter: {
+ name: 'Phase Shifter',
+ health: 30,
+ attack: 28,
+ defense: 1,
+ experience: 35,
+ credits: 25
+ },
+ quantum_entity: {
+ name: 'Quantum Entity',
+ health: 70,
+ attack: 35,
+ defense: 5,
+ experience: 60,
+ credits: 50
+ },
+
+ // NEW ENEMIES - Space Theme
+ maintenance_drone: {
+ name: 'Maintenance Drone',
+ health: 28,
+ attack: 11,
+ defense: 4,
+ experience: 18,
+ credits: 12
+ },
+ security_android: {
+ name: 'Security Android',
+ health: 42,
+ attack: 16,
+ defense: 7,
+ experience: 28,
+ credits: 20
+ },
+ station_ai: {
+ name: 'Station AI',
+ health: 65,
+ attack: 24,
+ defense: 3,
+ experience: 45,
+ credits: 32
+ },
+ ice_elemental: {
+ name: 'Ice Elemental',
+ health: 38,
+ attack: 18,
+ defense: 8,
+ experience: 32,
+ credits: 24
+ },
+ frost_wyrm: {
+ name: 'Frost Wyrm',
+ health: 72,
+ attack: 26,
+ defense: 6,
+ experience: 52,
+ credits: 38
+ },
+ gravity_wraith: {
+ name: 'Gravity Wraith',
+ health: 85,
+ attack: 32,
+ defense: 4,
+ experience: 68,
+ credits: 48
+ },
+ void_stalker: {
+ name: 'Void Stalker',
+ health: 78,
+ attack: 38,
+ defense: 5,
+ experience: 72,
+ credits: 52
+ },
+ singularity_spawn: {
+ name: 'Singularity Spawn',
+ health: 95,
+ attack: 42,
+ defense: 8,
+ experience: 85,
+ credits: 65
+ },
+ plasma_elemental: {
+ name: 'Plasma Elemental',
+ health: 58,
+ attack: 28,
+ defense: 4,
+ experience: 48,
+ credits: 35
+ },
+ solar_guardian: {
+ name: 'Solar Guardian',
+ health: 82,
+ attack: 34,
+ defense: 7,
+ experience: 65,
+ credits: 48
+ },
+ fusion_core: {
+ name: 'Fusion Core',
+ health: 68,
+ attack: 30,
+ defense: 12,
+ experience: 58,
+ credits: 42
+ },
+ scrap_golem: {
+ name: 'Scrap Golem',
+ health: 35,
+ attack: 14,
+ defense: 9,
+ experience: 22,
+ credits: 16
+ },
+ hull_breacher: {
+ name: 'Hull Breacher',
+ health: 32,
+ attack: 20,
+ defense: 3,
+ experience: 26,
+ credits: 18
+ },
+ salage_drone: {
+ name: 'Salvage Drone',
+ health: 22,
+ attack: 12,
+ defense: 5,
+ experience: 16,
+ credits: 11
+ },
+
+ // NEW ENEMIES - Planet Theme
+ plant_beast: {
+ name: 'Plant Beast',
+ health: 48,
+ attack: 15,
+ defense: 8,
+ experience: 35,
+ credits: 26
+ },
+ tribal_warrior: {
+ name: 'Tribal Warrior',
+ health: 38,
+ attack: 18,
+ defense: 6,
+ experience: 28,
+ credits: 20
+ },
+ jungle_spirit: {
+ name: 'Jungle Spirit',
+ health: 55,
+ attack: 22,
+ defense: 4,
+ experience: 42,
+ credits: 30
+ },
+ sand_worm: {
+ name: 'Sand Worm',
+ health: 75,
+ attack: 28,
+ defense: 9,
+ experience: 58,
+ credits: 42
+ },
+ mummy_guardian: {
+ name: 'Mummy Guardian',
+ health: 62,
+ attack: 24,
+ defense: 7,
+ experience: 48,
+ credits: 35
+ },
+ heat_elemental: {
+ name: 'Heat Elemental',
+ health: 52,
+ attack: 26,
+ defense: 3,
+ experience: 45,
+ credits: 32
+ },
+ lava_elemental: {
+ name: 'Lava Elemental',
+ health: 68,
+ attack: 30,
+ defense: 5,
+ experience: 55,
+ credits: 40
+ },
+ fire_demon: {
+ name: 'Fire Demon',
+ health: 72,
+ attack: 32,
+ defense: 6,
+ experience: 62,
+ credits: 45
+ },
+ magma_beast: {
+ name: 'Magma Beast',
+ health: 85,
+ attack: 28,
+ defense: 12,
+ experience: 68,
+ credits: 50
+ },
+ cryo_mutant: {
+ name: 'Cryo Mutant',
+ health: 45,
+ attack: 20,
+ defense: 7,
+ experience: 38,
+ credits: 28
+ },
+ frost_android: {
+ name: 'Frost Android',
+ health: 52,
+ attack: 22,
+ defense: 8,
+ experience: 42,
+ credits: 30
+ },
+ ice_wraith: {
+ name: 'Ice Wraith',
+ health: 58,
+ attack: 25,
+ defense: 4,
+ experience: 48,
+ credits: 35
+ },
+ swamp_beast: {
+ name: 'Swamp Beast',
+ health: 35,
+ attack: 16,
+ defense: 9,
+ experience: 24,
+ credits: 18
+ },
+ toxic_spitter: {
+ name: 'Toxic Spitter',
+ health: 28,
+ attack: 19,
+ defense: 3,
+ experience: 22,
+ credits: 16
+ },
+ mud_golem: {
+ name: 'Mud Golem',
+ health: 42,
+ attack: 12,
+ defense: 11,
+ experience: 26,
+ credits: 19
+ },
+
+ // NEW ENEMIES - Technology Theme
+ glitch_wraith: {
+ name: 'Glitch Wraith',
+ health: 48,
+ attack: 26,
+ defense: 2,
+ experience: 45,
+ credits: 32
+ },
+ firewall_guardian: {
+ name: 'Firewall Guardian',
+ health: 65,
+ attack: 22,
+ defense: 8,
+ experience: 52,
+ credits: 38
+ },
+ data_vampire: {
+ name: 'Data Vampire',
+ health: 38,
+ attack: 30,
+ defense: 3,
+ experience: 35,
+ credits: 26
+ },
+ assembly_drone: {
+ name: 'Assembly Drone',
+ health: 32,
+ attack: 15,
+ defense: 6,
+ experience: 24,
+ credits: 17
+ },
+ welder_bot: {
+ name: 'Welder Bot',
+ health: 28,
+ attack: 20,
+ defense: 4,
+ experience: 22,
+ credits: 15
+ },
+ factory_overseer: {
+ name: 'Factory Overseer',
+ health: 58,
+ attack: 24,
+ defense: 7,
+ experience: 46,
+ credits: 33
+ },
+ quantum_phantom: {
+ name: 'Quantum Phantom',
+ health: 78,
+ attack: 35,
+ defense: 4,
+ experience: 68,
+ credits: 50
+ },
+ particle_accelerator: {
+ name: 'Particle Accelerator',
+ health: 92,
+ attack: 40,
+ defense: 6,
+ experience: 85,
+ credits: 62
+ },
+ reality_bender: {
+ name: 'Reality Bender',
+ health: 88,
+ attack: 45,
+ defense: 3,
+ experience: 92,
+ credits: 68
+ },
+ sentinel_program: {
+ name: 'Sentinel Program',
+ health: 42,
+ attack: 21,
+ defense: 8,
+ experience: 32,
+ credits: 24
+ },
+ data_corruptor: {
+ name: 'Data Corruptor',
+ health: 35,
+ attack: 25,
+ defense: 3,
+ experience: 28,
+ credits: 20
+ },
+ system_guardian: {
+ name: 'System Guardian',
+ health: 55,
+ attack: 23,
+ defense: 9,
+ experience: 42,
+ credits: 30
+ },
+
+ // NEW ENEMIES - Biome/Elemental Theme
+ shard_elemental: {
+ name: 'Shard Elemental',
+ health: 45,
+ attack: 19,
+ defense: 10,
+ experience: 38,
+ credits: 28
+ },
+ resonance_beast: {
+ name: 'Resonance Beast',
+ health: 52,
+ attack: 22,
+ defense: 6,
+ experience: 42,
+ credits: 30
+ },
+ mutant_horror: {
+ name: 'Mutant Horror',
+ health: 68,
+ attack: 28,
+ defense: 5,
+ experience: 58,
+ credits: 42
+ },
+ toxic_slime: {
+ name: 'Toxic Slime',
+ health: 42,
+ attack: 18,
+ defense: 8,
+ experience: 32,
+ credits: 24
+ },
+ radiation_beast: {
+ name: 'Radiation Beast',
+ health: 75,
+ attack: 30,
+ defense: 4,
+ experience: 65,
+ credits: 48
+ },
+ shadow_demon: {
+ name: 'Shadow Demon',
+ health: 72,
+ attack: 34,
+ defense: 3,
+ experience: 68,
+ credits: 50
+ },
+ nightmare_stalker: {
+ name: 'Nightmare Stalker',
+ health: 85,
+ attack: 38,
+ defense: 5,
+ experience: 78,
+ credits: 58
+ },
+ void_walker: {
+ name: 'Void Walker',
+ health: 92,
+ attack: 42,
+ defense: 7,
+ experience: 88,
+ credits: 65
+ },
+ temporal_paradox: {
+ name: 'Temporal Paradox',
+ health: 78,
+ attack: 40,
+ defense: 4,
+ experience: 75,
+ credits: 55
+ },
+ future_soldier: {
+ name: 'Future Soldier',
+ health: 65,
+ attack: 32,
+ defense: 9,
+ experience: 58,
+ credits: 42
+ },
+ past_guardian: {
+ name: 'Past Guardian',
+ health: 70,
+ attack: 28,
+ defense: 12,
+ experience: 62,
+ credits: 45
+ },
+
+ // NEW ENEMIES - Military/War Theme
+ enemy_soldier: {
+ name: 'Enemy Soldier',
+ health: 45,
+ attack: 20,
+ defense: 7,
+ experience: 35,
+ credits: 26
+ },
+ combat_drone: {
+ name: 'Combat Drone',
+ health: 38,
+ attack: 22,
+ defense: 5,
+ experience: 30,
+ credits: 22
+ },
+ field_commander: {
+ name: 'Field Commander',
+ health: 62,
+ attack: 28,
+ defense: 9,
+ experience: 52,
+ credits: 38
+ },
+ turret_system: {
+ name: 'Turret System',
+ health: 48,
+ attack: 26,
+ defense: 10,
+ experience: 38,
+ credits: 28
+ },
+ combat_android: {
+ name: 'Combat Android',
+ health: 55,
+ attack: 24,
+ defense: 8,
+ experience: 45,
+ credits: 33
+ },
+ base_commander: {
+ name: 'Base Commander',
+ health: 72,
+ attack: 30,
+ defense: 11,
+ experience: 62,
+ credits: 45
+ },
+ weapon_drone: {
+ name: 'Weapon Drone',
+ health: 42,
+ attack: 28,
+ defense: 4,
+ experience: 38,
+ credits: 28
+ },
+ test_subject: {
+ name: 'Test Subject',
+ health: 58,
+ attack: 25,
+ defense: 6,
+ experience: 48,
+ credits: 35
+ },
+ chief_scientist: {
+ name: 'Chief Scientist',
+ health: 35,
+ attack: 32,
+ defense: 3,
+ experience: 42,
+ credits: 30
+ },
+
+ // NEW ENEMIES - Special/Unique Theme
+ nightmare_creature: {
+ name: 'Nightmare Creature',
+ health: 62,
+ attack: 28,
+ defense: 5,
+ experience: 55,
+ credits: 40
+ },
+ dream_guardian: {
+ name: 'Dream Guardian',
+ health: 68,
+ attack: 30,
+ defense: 8,
+ experience: 58,
+ credits: 42
+ },
+ subconscious_demon: {
+ name: 'Subconscious Demon',
+ health: 75,
+ attack: 34,
+ defense: 4,
+ experience: 68,
+ credits: 50
+ },
+ memory_fragment: {
+ name: 'Memory Fragment',
+ health: 48,
+ attack: 26,
+ defense: 6,
+ experience: 45,
+ credits: 33
+ },
+ forgetfulness_demon: {
+ name: 'Forgetfulness Demon',
+ health: 55,
+ attack: 30,
+ defense: 3,
+ experience: 48,
+ credits: 35
+ },
+ nostalgia_spirit: {
+ name: 'Nostalgia Spirit',
+ health: 52,
+ attack: 24,
+ defense: 9,
+ experience: 42,
+ credits: 30
+ },
+ rift_demon: {
+ name: 'Rift Demon',
+ health: 88,
+ attack: 44,
+ defense: 5,
+ experience: 92,
+ credits: 68
+ },
+ dimensional_hunter: {
+ name: 'Dimensional Hunter',
+ health: 95,
+ attack: 48,
+ defense: 8,
+ experience: 105,
+ credits: 78
+ },
+ reality_tear: {
+ name: 'Reality Tear',
+ health: 102,
+ attack: 52,
+ defense: 3,
+ experience: 115,
+ credits: 85
+ }
+ };
+
+ // Statistics
+ this.stats = {
+ dungeonsAttempted: 0,
+ dungeonsCompleted: 0,
+ totalEnemiesDefeated: 0,
+ bestTime: Infinity,
+ totalLootEarned: 0
+ };
+ }
+
+ async initialize() {
+ this.generateDungeonList();
+ }
+
+ generateDungeonList() {
+
+ const dungeonListElement = document.getElementById('dungeonList');
+ if (!dungeonListElement) {
+ console.error('Dungeon list element not found!');
+ return;
+ }
+
+ // Clear existing list
+ dungeonListElement.innerHTML = '';
+
+ const questSystem = this.game.systems.questSystem;
+ const firstStepsQuest = questSystem ? questSystem.findQuest('tutorial_complete') : null;
+ const showTutorialDungeon = firstStepsQuest && firstStepsQuest.status === 'active';
+
+ Object.entries(this.dungeonTypes).forEach(([key, dungeon]) => {
+ // Skip tutorial dungeon unless First Steps quest is active
+ if (key === 'tutorial' && !showTutorialDungeon) {
+ return;
+ }
+
+ const dungeonElement = document.createElement('div');
+ dungeonElement.className = 'dungeon-item';
+ dungeonElement.dataset.dungeonType = key;
+
+ // Check if tutorial dungeon is completed
+ const isCompleted = key === 'tutorial' && this.game.systems.player.stats.tutorialDungeonCompleted;
+ const statusClass = isCompleted ? 'completed' : '';
+ const statusText = isCompleted ? 'COMPLETED' : '';
+
+ dungeonElement.innerHTML = `
+ ${dungeon.name}
+ ${dungeon.difficulty.toUpperCase()}
+ ${statusText ? `${statusText}
` : ''}
+ ${dungeon.description}
+ Rewards: ${dungeon.rewardMultiplier}x
+ Energy Cost: ${dungeon.energyCost || this.getEnergyCost(key)}
+ `;
+
+ dungeonElement.addEventListener('click', () => {
+ if (isCompleted) {
+ this.game.showNotification('Tutorial dungeon has already been completed!', 'warning', 3000);
+ } else {
+ this.selectDungeon(key);
+ }
+ });
+
+ dungeonListElement.appendChild(dungeonElement);
+ });
+
+ }
+
+ selectDungeon(type) {
+
+ // Check energy cost
+ const energyCost = this.getEnergyCost(type);
+ const player = this.game.systems.player;
+
+
+ if (!player.useEnergy(energyCost)) {
+ this.game.showNotification(`Not enough energy! Need ${energyCost} energy`, 'error', 3000);
+ return;
+ }
+
+
+ // Ensure we're on the Dungeons tab so the user can see the dungeon
+ if (this.game.ui && this.game.ui.currentTab !== 'dungeons') {
+ this.game.ui.switchTab('dungeons');
+ }
+
+ // Remove previous selection
+ document.querySelectorAll('.dungeon-item').forEach(item => {
+ item.classList.remove('selected');
+ });
+
+ // Add selection to clicked dungeon
+ const selectedElement = document.querySelector(`[data-dungeon-type="${type}"]`);
+ if (selectedElement) {
+ selectedElement.classList.add('selected');
+ }
+
+
+ // Generate dungeon
+ this.generateDungeon(type);
+ this.displayDungeon();
+
+
+ this.game.showNotification(`Entered dungeon! -${energyCost} energy`, 'info', 3000);
+ }
+
+ getEnergyCost(type) {
+ const costs = {
+ tutorial: 0,
+ alien_ruins: 20,
+ pirate_lair: 15,
+ corrupted_vault: 25,
+ asteroid_mine: 10,
+ nebula_anomaly: 30
+ };
+ return costs[type] || 15;
+ }
+
+ calculateDungeonRewards(type, difficulty) {
+ const dungeonTemplate = this.dungeonTypes[type];
+ const baseRewards = {
+ credits: 50,
+ experience: 25,
+ items: []
+ };
+
+ // Apply difficulty multiplier
+ const difficultyMultipliers = {
+ tutorial: 0.5,
+ easy: 1.0,
+ medium: 1.5,
+ hard: 2.0
+ };
+
+ const multiplier = difficultyMultipliers[difficulty] || 1.0;
+ const rewardMultiplier = dungeonTemplate.rewardMultiplier || 1.0;
+
+ baseRewards.credits = Math.floor(baseRewards.credits * multiplier * rewardMultiplier);
+ baseRewards.experience = Math.floor(baseRewards.experience * multiplier * rewardMultiplier);
+
+ return baseRewards;
+ }
+
+ generateDungeon(type) {
+
+ const dungeonTemplate = this.dungeonTypes[type];
+
+ // Check if tutorial dungeon has already been completed
+ if (type === 'tutorial' && this.game.systems.player.stats.tutorialDungeonCompleted) {
+ this.game.showNotification('Tutorial dungeon has already been completed!', 'warning', 3000);
+ return;
+ }
+
+
+ this.currentDungeon = {
+ type: type,
+ name: dungeonTemplate.name,
+ description: dungeonTemplate.description,
+ difficulty: dungeonTemplate.difficulty,
+ healthType: dungeonTemplate.healthType,
+ enemyTypes: dungeonTemplate.enemyTypes,
+ rewardMultiplier: dungeonTemplate.rewardMultiplier,
+ rooms: [],
+ currentRoomIndex: 0,
+ startTime: Date.now(),
+ completed: false
+ };
+
+
+ // Generate rooms - ensure minimum of 3 rooms (entrance, at least 1 middle, boss)
+ const roomCount = Math.max(3, this.game.getRandomInt(5, 8));
+ this.currentDungeon.rooms = this.generateRoomLayout(roomCount, dungeonTemplate);
+
+ // Set current room
+ this.currentRoom = this.currentDungeon.rooms[0];
+ this.dungeonProgress = 0;
+
+
+ // Show dungeon view and hide list
+ this.showDungeonView();
+
+ }
+
+ generateRoomLayout(roomCount, dungeonTemplate) {
+ const rooms = [];
+
+
+ // Always start with entrance
+ rooms.push(this.createRoom('entrance', dungeonTemplate));
+
+ // Generate middle rooms (subtract 2 for entrance and boss room)
+ const middleRoomCount = roomCount - 2;
+
+ for (let i = 0; i < middleRoomCount; i++) {
+ const roomType = this.getRandomRoomType();
+ const room = this.createRoom(roomType, dungeonTemplate);
+ rooms.push(room);
+ }
+
+ // Always end with boss room (exit is handled by completing the boss room)
+ rooms.push(this.createRoom('boss', dungeonTemplate));
+
+
+ // Verify room accessibility
+ for (let i = 0; i < rooms.length; i++) {
+ }
+
+ return rooms;
+ }
+
+ getRandomRoomType() {
+ const weights = {
+ corridor: 40,
+ chamber: 30,
+ treasure: 0, // Temporarily disabled
+ entrance: 5,
+ boss: 5,
+ exit: 0
+ };
+
+ const totalWeight = Object.values(weights).reduce((sum, weight) => sum + weight, 0);
+ let random = Math.random() * totalWeight;
+
+ for (const [type, weight] of Object.entries(weights)) {
+ random -= weight;
+ if (random <= 0) {
+ return type;
+ }
+ }
+
+ return 'corridor';
+ }
+
+ createRoom(roomType, dungeonTemplate) {
+ const template = this.roomTypes[roomType];
+ const room = {
+ type: roomType,
+ name: template.name,
+ enemies: [],
+ rewards: null,
+ explored: false,
+ completed: false
+ };
+
+ // Generate enemies
+ if (template.enemies > 0) {
+ for (let i = 0; i < template.enemies; i++) {
+ const enemyType = dungeonTemplate.enemyTypes[
+ Math.floor(Math.random() * dungeonTemplate.enemyTypes.length)
+ ];
+ room.enemies.push(this.createEnemy(enemyType, template.isBoss));
+ }
+ }
+
+ // Generate rewards
+ if (template.rewards) {
+ room.rewards = this.generateRoomRewards(dungeonTemplate.difficulty, template.isBoss);
+ }
+
+ return room;
+ }
+
+ createEnemy(enemyType, isBoss = false) {
+ const template = this.enemyTemplates[enemyType];
+ const bossMultiplier = isBoss ? 2.5 : 1.0;
+
+ return {
+ id: Date.now() + Math.random().toString(36).substr(2, 9),
+ type: enemyType,
+ name: isBoss ? `Boss ${template.name}` : template.name,
+ health: Math.floor(template.health * bossMultiplier),
+ maxHealth: Math.floor(template.health * bossMultiplier),
+ attack: Math.floor(template.attack * bossMultiplier),
+ defense: Math.floor(template.defense * bossMultiplier),
+ experience: Math.floor(template.experience * bossMultiplier),
+ credits: Math.floor(template.credits * bossMultiplier),
+ isBoss: isBoss
+ };
+ }
+
+ generateRoomRewards(difficulty, isBoss = false) {
+ const baseRewards = this.game.systems.economy.generateRewards(difficulty, 'dungeon');
+ const multiplier = isBoss ? 2.0 : 1.0;
+
+ const rewards = {
+ credits: Math.floor(baseRewards.credits * multiplier),
+ experience: Math.floor(baseRewards.experience * multiplier),
+ materials: []
+ };
+
+ // Add crafting materials based on chance and difficulty
+ const materialChance = isBoss ? 0.9 : 0.4;
+ if (Math.random() < materialChance) {
+ const materialCount = isBoss ? this.game.getRandomInt(3, 6) : this.game.getRandomInt(1, 3);
+
+ // Define material pools with weights for better balance
+ const materialPools = {
+ tutorial: [
+ { id: 'iron_ore', weight: 40 },
+ { id: 'copper_wire', weight: 30 },
+ { id: 'herbs', weight: 20 },
+ { id: 'bandages', weight: 10 }
+ ],
+ easy: [
+ { id: 'iron_ore', weight: 30 },
+ { id: 'copper_wire', weight: 25 },
+ { id: 'energy_crystal', weight: 15 },
+ { id: 'leather', weight: 15 },
+ { id: 'herbs', weight: 10 },
+ { id: 'bandages', weight: 5 }
+ ],
+ medium: [
+ { id: 'iron_ore', weight: 25 },
+ { id: 'steel_plate', weight: 20 },
+ { id: 'energy_crystal', weight: 20 },
+ { id: 'copper_wire', weight: 15 },
+ { id: 'rare_metal', weight: 5 },
+ { id: 'bandages', weight: 15 }
+ ],
+ hard: [
+ { id: 'steel_plate', weight: 30 },
+ { id: 'energy_crystal', weight: 25 },
+ { id: 'rare_metal', weight: 15 },
+ { id: 'battery', weight: 20 },
+ { id: 'bandages', weight: 10 }
+ ],
+ extreme: [
+ { id: 'rare_metal', weight: 30 },
+ { id: 'energy_crystal', weight: 25 },
+ { id: 'battery', weight: 25 },
+ { id: 'advanced_components', weight: 20 }
+ ]
+ };
+
+ const pool = materialPools[difficulty] || materialPools.easy;
+
+ // Helper function to get weighted random material
+ function getWeightedRandomMaterial(materialPool) {
+ const totalWeight = materialPool.reduce((sum, mat) => sum + mat.weight, 0);
+ let random = Math.random() * totalWeight;
+
+ for (const material of materialPool) {
+ random -= material.weight;
+ if (random <= 0) {
+ return material.id;
+ }
+ }
+ return materialPool[0].id; // Fallback
+ }
+
+ for (let i = 0; i < materialCount; i++) {
+ const material = getWeightedRandomMaterial(pool);
+ const quantity = this.game.getRandomInt(1, isBoss ? 3 : 2);
+
+ rewards.materials.push({
+ id: material,
+ quantity: quantity
+ });
+ }
+ }
+
+ return rewards;
+ }
+
+ displayDungeon() {
+
+ const dungeonViewElement = document.getElementById('dungeonView');
+
+ if (!dungeonViewElement) {
+ const allElements = document.querySelectorAll('[id*="dungeon"]');
+ return;
+ }
+
+ if (!this.currentDungeon) {
+ return;
+ }
+
+ const room = this.currentRoom;
+ if (room) {
+ // Room exists, continue processing
+ }
+
+ const progress = Math.floor((this.currentDungeon.currentRoomIndex / this.currentDungeon.rooms.length) * 100);
+
+
+ let content = `
+
+
+
+
+ ${this.getHealthDisplay()}
+
+
+
+
${room.name}
+ ${this.getRoomDescription(room)}
+
+
+
+ ${this.getRoomContent(room)}
+
+
+
+ ${this.getRoomActions(room)}
+
+
+ `;
+
+
+ dungeonViewElement.innerHTML = content;
+
+
+ // Only add event listeners for buttons that don't use inline onclick handlers
+ const completeDungeonBtn = dungeonViewElement.querySelector('button[onclick*="completeDungeon"]');
+ if (completeDungeonBtn) {
+ completeDungeonBtn.addEventListener('click', (e) => {
+ e.preventDefault();
+ this.completeDungeon();
+ });
+ }
+
+ }
+
+ getHealthDisplay() {
+ const player = this.game.systems.player;
+ const healthType = this.currentDungeon.healthType;
+
+
+ if (healthType === 'ship') {
+ const shipHealth = player.ship.health || 0;
+ const shipMaxHealth = player.ship.maxHealth || 1000;
+ const healthPercent = Math.round((shipHealth / shipMaxHealth) * 100);
+ return `
+
+
Ship Health
+
+
${shipHealth} / ${shipMaxHealth}
+
+ `;
+ } else {
+ const playerHealth = player.attributes.health || 0;
+ const playerMaxHealth = player.attributes.maxHealth || 100;
+ const healthPercent = Math.round((playerHealth / playerMaxHealth) * 100);
+ return `
+
+
Player Health
+
+
${playerHealth} / ${playerMaxHealth}
+
+ `;
+ }
+ }
+
+ getRoomDescription(room) {
+ const descriptions = {
+ entrance: 'You enter the dungeon. The air is thick with anticipation.',
+ corridor: 'A narrow corridor stretches ahead. You can hear distant sounds.',
+ chamber: 'You enter a large chamber. The echoes of past battles linger here.',
+ treasure: 'Golden light glimmers from piles of treasure in this room.',
+ boss: 'A massive presence fills the room. This is the guardian of this dungeon.',
+ exit: 'You can see the exit. Freedom awaits!'
+ };
+
+ return `${descriptions[room.type] || 'You continue exploring...'}
`;
+ }
+
+ getRoomContent(room) {
+ if (room.completed) {
+ return 'This room has been cleared.
';
+ }
+
+ if (room.enemies.length > 0) {
+ const enemiesHtml = room.enemies.map(enemy => `
+
+
${enemy.name}
+
+
+
${enemy.health} / ${enemy.maxHealth}
+
+
ATK: ${enemy.attack} | DEF: ${enemy.defense}
+
+ `).join('');
+
+ return `${enemiesHtml}
`;
+ }
+
+ if (room.rewards && !room.completed) {
+ return '';
+ }
+
+ return 'The room is empty and quiet.
';
+ }
+
+ getRoomActions(room) {
+
+ if (room.completed) {
+ if (this.currentDungeon.currentRoomIndex < this.currentDungeon.rooms.length - 1) {
+ return 'Continue to Next Room ';
+ } else {
+ return 'Complete Dungeon ';
+ }
+ }
+
+ if (room.enemies.length > 0) {
+ const buttonHtml = 'Start Combat ';
+ return buttonHtml;
+ }
+
+ if (room.rewards) {
+ return 'Claim Rewards ';
+ }
+
+ // Check if this is the final room
+ if (this.currentDungeon.currentRoomIndex >= this.currentDungeon.rooms.length - 1) {
+ return 'Complete Dungeon ';
+ }
+
+ return 'Move Forward ';
+ }
+ async startCombat() {
+
+ if (this.isExploring) {
+ return;
+ }
+
+ this.isExploring = true;
+ const room = this.currentRoom;
+ const enemies = room.enemies.filter(e => e.health > 0);
+
+
+ if (enemies.length === 0) {
+ this.completeRoom();
+ return;
+ }
+
+ // Simulate combat
+ await this.simulateCombat(enemies);
+ }
+
+ async simulateCombat(enemies) {
+
+ try {
+ const player = this.game.systems.player;
+
+ const healthType = this.currentDungeon.healthType;
+
+ let combatLog = [];
+
+ // Handle case where there are no enemies
+ if (enemies.length === 0) {
+ combatLog.push('Room was empty - no enemies found');
+ this.completeRoom();
+ return;
+ }
+
+ for (const [index, enemy] of enemies.entries()) {
+
+ // Player attacks
+ const playerDamage = player.calculateDamage(this.currentDungeon.difficulty);
+
+ const actualDamage = Math.max(1, playerDamage.damage - enemy.defense);
+ enemy.health = Math.max(0, enemy.health - actualDamage);
+
+ combatLog.push(`You dealt ${actualDamage} damage to ${enemy.name}${playerDamage.isCritical ? ' (CRITICAL!)' : ''}`);
+
+ // Update UI after player attack to show enemy health change
+ this.displayDungeon();
+ // Also update global player UI to ensure health bars are updated only if in multiplayer mode or game is actively running
+ const shouldUpdateUI = window.smartSaveManager?.isMultiplayer || this.game?.isRunning;
+
+ if (shouldUpdateUI && player.updateUI) {
+ player.updateUI();
+ }
+
+ // Small delay to make the combat visible
+ await new Promise(resolve => setTimeout(resolve, 500));
+
+ // Enemy attacks if still alive
+ if (enemy.health > 0) {
+ let enemyDamage, damageTarget;
+
+ if (healthType === 'ship') {
+ // Space missions: higher damage, target ship health
+ enemyDamage = Math.max(1, Math.floor(enemy.attack * 1.5) - player.ship.defense);
+ damageTarget = 'ship';
+ this.applyShipDamage(enemyDamage);
+ combatLog.push(`${enemy.name} dealt ${enemyDamage} damage to your ship`);
+ } else {
+ // Ground missions: normal damage, target player health
+ enemyDamage = Math.max(1, enemy.attack - player.attributes.defense);
+ damageTarget = 'player';
+ player.takeDamage(enemyDamage);
+ combatLog.push(`${enemy.name} dealt ${enemyDamage} damage to you`);
+ }
+
+ // Update UI after enemy attack to show player health change
+ this.displayDungeon();
+ // Also update global player UI to ensure health bars are updated
+ if (player.updateUI) {
+ player.updateUI();
+ }
+
+ // Small delay to make the combat visible
+ await new Promise(resolve => setTimeout(resolve, 500));
+
+ // Check for destruction
+ if (healthType === 'ship' && player.ship.health <= 0) {
+ this.handleShipDestruction();
+ } else if (healthType === 'player' && player.attributes.health <= 0) {
+ this.handlePlayerDefeat();
+ }
+ } else {
+ // Enemy defeated
+ player.addExperience(enemy.experience);
+ this.game.systems.economy.addCredits(enemy.credits, 'dungeon');
+ player.incrementKills();
+ this.stats.totalEnemiesDefeated++;
+
+ // Update quest progress for combat objectives
+ if (this.game.systems.questSystem) {
+ this.game.systems.questSystem.onEnemyDefeated();
+ }
+
+ combatLog.push(`${enemy.name} defeated! +${enemy.experience} XP, +${enemy.credits} credits`);
+
+ // Update UI after enemy defeat
+ this.displayDungeon();
+ // Also update global player UI to ensure health bars are updated
+ if (player.updateUI) {
+ player.updateUI();
+ }
+
+ // Small delay to make the combat visible
+ await new Promise(resolve => setTimeout(resolve, 500));
+ }
+ }
+
+ // Show combat results
+ this.game.showNotification('Combat completed!', 'success', 3000);
+ combatLog.forEach(message => this.game.showNotification(message, 'info', 2000));
+
+ // Check if all enemies defeated
+ const remainingEnemies = this.currentRoom.enemies.filter(e => e.health > 0);
+ if (remainingEnemies.length === 0) {
+ this.completeRoom();
+ }
+
+ this.isExploring = false;
+ this.displayDungeon();
+ } catch (error) {
+ console.error('[DUNGEON] Error in simulateCombat:', error);
+ console.error('[DUNGEON] Error stack:', error.stack);
+ this.isExploring = false;
+ }
+ }
+
+ // Health system methods
+ applyShipDamage(amount) {
+ const player = this.game.systems.player;
+ player.ship.health = Math.max(0, player.ship.health - amount);
+
+ // Only update player UI if in multiplayer mode or game is actively running
+ if (this.game.shouldUpdateGUI()) {
+ player.updateUI();
+ }
+
+ // Check for ship destruction
+ if (player.ship.health <= 0) {
+ this.handleShipDestruction();
+ }
+ }
+
+ handleShipDestruction() {
+ const player = this.game.systems.player;
+
+ // Show destruction message
+ this.game.showNotification('Your ship has been destroyed!', 'error', 5000);
+ this.game.showNotification('Dungeon failed - all progress lost.', 'warning', 4000);
+
+ // Remove the current ship from inventory
+ if (this.game.systems.inventory) {
+ this.game.systems.inventory.removeItem(player.ship);
+ }
+
+ // Reset to a basic ship if available in inventory
+ const availableShips = this.game.systems.inventory.getItemsByType('ship');
+ if (availableShips.length > 0) {
+ player.ship = availableShips[0];
+ this.game.showNotification(`Switched to ${player.ship.name}`, 'info', 3000);
+ } else {
+ // No ships available - create a basic one
+ player.ship = {
+ name: 'Basic Fighter',
+ health: 1000,
+ maxHealth: 1000,
+ defense: 5,
+ attack: 10,
+ type: 'ship'
+ };
+ this.game.showNotification('No ships available - using emergency fighter', 'warning', 3000);
+ }
+
+ // Apply dungeon failure penalty
+ this.applyDungeonFailurePenalty();
+
+ // Exit dungeon
+ this.exitDungeon();
+ }
+
+ applyDungeonFailurePenalty() {
+ const player = this.game.systems.player;
+
+ // Lose some credits as penalty
+ const creditPenalty = Math.floor(this.game.systems.economy.credits * 0.1); // 10% credit loss
+ this.game.systems.economy.removeCredits(creditPenalty);
+
+ // Lose some experience
+ const expPenalty = Math.floor(player.experience * 0.05); // 5% exp loss
+ player.experience = Math.max(0, player.experience - expPenalty);
+
+ // Update failure statistics
+ this.stats.dungeonsFailed++;
+
+ this.game.showNotification(`Dungeon penalty: -${creditPenalty} credits, -${expPenalty} XP`, 'warning', 4000);
+ }
+
+ handlePlayerDefeat() {
+ this.game.showNotification('You have been defeated!', 'error', 5000);
+ this.game.showNotification('You\'ve been forced to retreat from the dungeon.', 'warning', 4000);
+
+ // Heal player to prevent death loop
+ const player = this.game.systems.player;
+ player.attributes.health = Math.floor(player.attributes.maxHealth * 0.5);
+
+ // Exit dungeon
+ this.exitDungeon();
+ }
+
+ exitDungeon() {
+ this.currentDungeon = null;
+ this.currentRoom = null;
+ this.dungeonProgress = 0;
+ this.isExploring = false;
+
+ // Return to main view
+ this.generateDungeonList();
+ }
+
+ claimRewards() {
+ const room = this.currentRoom;
+ if (!room.rewards || room.completed) return;
+
+ // Give all rewards including materials
+ this.game.systems.economy.giveRewards(room.rewards, 'dungeon');
+
+ room.rewards = null; // Remove rewards after claiming
+
+ this.game.showNotification('Rewards claimed!', 'success', 3000);
+
+ // Use the proper completeRoom method to ensure consistent behavior
+ this.completeRoom();
+ }
+
+ completeRoom() {
+ const room = this.currentRoom;
+ room.completed = true;
+ room.explored = true;
+
+ // Reset exploring flag when completing a room
+ this.isExploring = false;
+
+
+ // Auto-claim rewards if available
+ if (room.rewards) {
+ // Give credits and experience
+ this.game.systems.economy.giveRewards(room.rewards, 'dungeon');
+
+ // Add crafting materials to inventory
+ if (room.rewards.materials && room.rewards.materials.length > 0) {
+ let materialText = '';
+ room.rewards.materials.forEach(material => {
+ this.game.systems.inventory.addItem(material.id, material.quantity);
+ materialText += `${material.quantity}x ${material.id}, `;
+ });
+
+ materialText = materialText.slice(0, -2);
+
+ this.game.showNotification(`Materials found: ${materialText}`, 'success', 4000);
+ }
+
+ room.rewards = null;
+ }
+
+ this.game.showNotification(`${room.name} completed!`, 'success', 3000);
+
+ // Check if this is a boss room - if so, automatically complete the dungeon
+ if (room.type === 'boss') {
+ setTimeout(() => {
+ this.completeDungeon();
+ }, 2000); // Small delay to show the completion message first
+ } else {
+ this.displayDungeon();
+ }
+
+ }
+
+ nextRoom() {
+
+ // Log all rooms for debugging
+ this.currentDungeon.rooms.forEach((room, index) => {
+ });
+
+ if (this.currentDungeon.currentRoomIndex >= this.currentDungeon.rooms.length - 1) {
+ return;
+ }
+
+ const oldIndex = this.currentDungeon.currentRoomIndex;
+ this.currentDungeon.currentRoomIndex++;
+ this.currentRoom = this.currentDungeon.rooms[this.currentDungeon.currentRoomIndex];
+
+
+ this.displayDungeon();
+ }
+
+ completeDungeon() {
+ if (!this.currentDungeon) {
+ return;
+ }
+
+ const completionTime = Date.now() - this.currentDungeon.startTime;
+ const player = this.game.systems.player;
+ const economy = this.game.systems.economy;
+
+ // Generate proper rewards including materials
+ const baseRewards = this.generateRoomRewards(this.currentDungeon.difficulty, false);
+
+ // Apply time bonus
+ const timeBonus = Math.floor(completionTime < 300000 ? 50 : 0); // Bonus for completing in under 5 minutes
+ baseRewards.credits += timeBonus;
+ baseRewards.experience += Math.floor(timeBonus / 2);
+
+ // Give rewards
+ economy.giveRewards(baseRewards, 'dungeon');
+ player.addExperience(baseRewards.experience);
+
+ // Update statistics
+ this.stats.dungeonsCompleted++;
+ this.stats.totalTimeInDungeons += completionTime;
+
+ // Update player dungeons cleared stat
+ player.stats.dungeonsCleared++;
+
+ // Update quest progress for dungeon objectives
+ if (this.game.systems.questSystem) {
+ // Check if this is a tutorial dungeon
+ if (this.currentDungeon.type === 'tutorial') {
+ // Mark tutorial dungeon as completed in player stats
+ player.stats.tutorialDungeonCompleted = true;
+ // Update tutorial dungeon quest progress
+ this.game.systems.questSystem.updateTutorialDungeonProgress();
+ } else {
+ // Update regular dungeon quest progress using the standard method
+ this.game.systems.questSystem.onDungeonCompleted();
+ }
+ }
+
+ // Show completion message
+ this.game.showNotification(`Dungeon completed! Time: ${this.game.formatTime(completionTime)}`, 'success', 5000);
+ if (timeBonus > 0) {
+ this.game.showNotification(`Time bonus: +${timeBonus} credits!`, 'info', 3000);
+ }
+
+ // Reset dungeon state
+ this.currentDungeon = null;
+ this.currentRoom = null;
+ this.isExploring = false;
+
+ // Return to dungeon list view
+ this.showDungeonList();
+ this.generateDungeonList();
+
+ // Only force UI refresh if in multiplayer mode or game is actively running
+ if (this.game.shouldUpdateGUI()) {
+ this.updateUI();
+ }
+ }
+
+ showDungeonList() {
+ const dungeonListElement = document.getElementById('dungeonList');
+ const dungeonViewElement = document.getElementById('dungeonView');
+
+ if (dungeonListElement) {
+ dungeonListElement.style.display = 'flex';
+ }
+
+ if (dungeonViewElement) {
+ dungeonViewElement.style.display = 'none';
+ // Clear the dungeon view content to prevent frozen display
+ dungeonViewElement.innerHTML = '';
+ }
+ }
+
+ showDungeonView() {
+
+ const dungeonListElement = document.getElementById('dungeonList');
+ const dungeonViewElement = document.getElementById('dungeonView');
+
+
+ if (dungeonListElement) {
+ dungeonListElement.style.display = 'none';
+ }
+
+ if (dungeonViewElement) {
+ dungeonViewElement.style.display = 'flex';
+ }
+
+
+ // Display the current dungeon
+ this.displayDungeon();
+
+ }
+
+ // UI updates
+ updateUI() {
+ // Update dungeon statistics if elements exist
+ const dungeonsClearedElement = document.getElementById('dungeonsCleared');
+ if (dungeonsClearedElement) {
+ dungeonsClearedElement.textContent = this.stats.dungeonsCompleted;
+ }
+ }
+
+ // Save/Load
+ save() {
+ return {
+ stats: this.stats,
+ currentDungeon: this.currentDungeon,
+ currentRoom: this.currentRoom,
+ dungeonProgress: this.dungeonProgress
+ };
+ }
+
+ load(data) {
+ if (data.stats) this.stats = { ...this.stats, ...data.stats };
+ if (data.currentDungeon) this.currentDungeon = data.currentDungeon;
+ if (data.currentRoom) this.currentRoom = data.currentRoom;
+ if (data.dungeonProgress !== undefined) this.dungeonProgress = data.dungeonProgress;
+ }
+}
diff --git a/Client-Server/js/systems/IdleSystem.js b/Client-Server/js/systems/IdleSystem.js
new file mode 100644
index 0000000..0eec3bd
--- /dev/null
+++ b/Client-Server/js/systems/IdleSystem.js
@@ -0,0 +1,357 @@
+/**
+ * Galaxy Strike Online - Idle System
+ * Manages offline progression and idle mechanics
+ */
+
+class IdleSystem {
+ constructor(gameEngine) {
+ this.game = gameEngine;
+
+ // Idle settings
+ this.maxOfflineTime = 7 * 24 * 60 * 60 * 1000; // 7 days in milliseconds
+ this.lastActiveTime = Date.now();
+ this.accumulatedTime = 0; // Track time for resource generation
+
+ // Idle production rates
+ this.productionRates = {
+ credits: 10, // credits per second (increased for better gameplay)
+ experience: 1, // experience per second (increased for better progression)
+ energy: 0.5 // energy regeneration per second
+ };
+
+ // Offline rewards
+ this.offlineRewards = {
+ credits: 0,
+ experience: 0,
+ energy: 0,
+ items: []
+ };
+
+ // Idle bonuses
+ this.bonuses = {
+ premium: 1.0,
+ guild: 1.0,
+ research: 1.0
+ };
+
+ // Idle achievements
+ this.achievements = {
+ totalOfflineTime: 0,
+ maxOfflineSession: 0,
+ totalIdleCredits: 0,
+ totalIdleExperience: 0
+ };
+ }
+
+ async initialize() {
+ // Calculate offline progress if returning
+ this.calculateOfflineProgress();
+}
+
+ calculateOfflineProgress(offlineTime = null) {
+ const currentTime = Date.now();
+ const actualOfflineTime = offlineTime || (currentTime - this.lastActiveTime);
+
+ // Cap offline time to maximum
+ const cappedOfflineTime = Math.min(actualOfflineTime, this.maxOfflineTime);
+
+ if (cappedOfflineTime < 60000) { // Less than 1 minute
+ return;
+ }
+
+ // Calculate production
+ const totalBonus = this.getTotalBonus();
+ const productionSeconds = cappedOfflineTime / 1000;
+
+ this.offlineRewards = {
+ credits: Math.floor(this.productionRates.credits * productionSeconds * totalBonus),
+ experience: Math.floor(this.productionRates.experience * productionSeconds * totalBonus),
+ energy: Math.min(
+ this.game.systems.player.attributes.maxEnergy,
+ Math.floor(this.productionRates.energy * productionSeconds)
+ ),
+ items: this.generateIdleItems(cappedOfflineTime)
+ };
+
+ // Update achievements
+ this.achievements.totalOfflineTime += cappedOfflineTime;
+ this.achievements.maxOfflineSession = Math.max(this.achievements.maxOfflineSession, cappedOfflineTime);
+ this.achievements.totalIdleCredits += this.offlineRewards.credits;
+ this.achievements.totalIdleExperience += this.offlineRewards.experience;
+
+ // Show offline rewards notification
+ this.showOfflineRewards(cappedOfflineTime);
+}
+
+ getTotalBonus() {
+ return this.bonuses.premium * this.bonuses.guild * this.bonuses.research;
+ }
+
+ generateIdleItems(offlineTime) {
+ const items = [];
+ const hours = offlineTime / (1000 * 60 * 60);
+
+ // Chance to find items based on offline time
+ const itemChance = Math.min(0.5, hours * 0.05);
+
+ if (Math.random() < itemChance) {
+ const itemCount = Math.floor(hours / 2) + 1;
+
+ for (let i = 0; i < itemCount; i++) {
+ const rarity = this.getRandomItemRarity();
+ const item = this.game.systems.inventory.generateItem('consumable', rarity);
+ items.push(item);
+ }
+ }
+
+ return items;
+ }
+
+ getRandomItemRarity() {
+ const roll = Math.random();
+ if (roll < 0.05) return 'legendary';
+ if (roll < 0.15) return 'epic';
+ if (roll < 0.35) return 'rare';
+ if (roll < 0.65) return 'uncommon';
+ return 'common';
+ }
+
+ showOfflineRewards(offlineTime) {
+ const timeString = this.game.formatTime(offlineTime);
+
+ this.game.showNotification(
+ `Welcome back! You were offline for ${timeString}`,
+ 'info',
+ 5000
+ );
+
+ // Format rewards message
+ let rewardsMessage = 'Offline Rewards:\n';
+ if (this.offlineRewards.credits > 0) {
+ rewardsMessage += `+${this.game.formatNumber(this.offlineRewards.credits)} credits\n`;
+ }
+ if (this.offlineRewards.experience > 0) {
+ rewardsMessage += `+${this.game.formatNumber(this.offlineRewards.experience)} XP\n`;
+ }
+ if (this.offlineRewards.energy > 0) {
+ rewardsMessage += `+${this.game.formatNumber(this.offlineRewards.energy)} energy\n`;
+ }
+ if (this.offlineRewards.items.length > 0) {
+ rewardsMessage += `+${this.offlineRewards.items.length} items\n`;
+ }
+
+ this.game.showNotification(rewardsMessage, 'success', 5000);
+ }
+
+ claimOfflineRewards() {
+ if (this.offlineRewards.credits === 0 &&
+ this.offlineRewards.experience === 0 &&
+ this.offlineRewards.items.length === 0) {
+ this.game.showNotification('No offline rewards to claim', 'info', 3000);
+ return;
+ }
+
+ // Give rewards
+ if (this.offlineRewards.credits > 0) {
+ this.game.systems.economy.addCredits(this.offlineRewards.credits, 'offline');
+ }
+
+ if (this.offlineRewards.experience > 0) {
+ this.game.systems.player.addExperience(this.offlineRewards.experience);
+ }
+
+ if (this.offlineRewards.energy > 0) {
+ this.game.systems.player.restoreEnergy(this.offlineRewards.energy);
+ }
+
+ // Add items to inventory
+ if (this.offlineRewards.items.length > 0) {
+ const inventory = this.game.systems.inventory;
+ this.offlineRewards.items.forEach(item => {
+ inventory.addItem(item);
+ });
+ }
+
+ // Reset offline rewards
+ this.offlineRewards = {
+ credits: 0,
+ experience: 0,
+ energy: 0,
+ items: []
+ };
+
+ this.game.showNotification('Offline rewards claimed!', 'success', 3000);
+ }
+
+ // Active idle production
+ update(deltaTime) {
+ if (this.game.state.paused) return;
+
+ // Use real computer time delta
+ const seconds = deltaTime / 1000;
+ const totalBonus = this.getTotalBonus();
+
+ // Only add resources once per second, not every frame
+ this.accumulatedTime += seconds;
+
+ if (this.accumulatedTime >= 1.0) {
+ // Calculate active production
+ const activeCredits = Math.floor(this.productionRates.credits * totalBonus);
+ const activeExperience = Math.floor(this.productionRates.experience * totalBonus);
+ // const activeEnergy = this.productionRates.energy * totalBonus * 0.1; // Energy is handled differently
+
+ // Add resources
+ if (activeCredits > 0) {
+ this.game.systems.economy.addCredits(activeCredits, 'idle');
+ }
+ if (activeExperience > 0) {
+ this.game.systems.player.addExperience(activeExperience);
+ }
+
+ // Regenerate energy
+ this.game.systems.player.restoreEnergy(this.productionRates.energy);
+
+ // Reset accumulated time, keeping any remainder
+ this.accumulatedTime -= 1.0;
+
+ // Debugging: Log when resources are added
+ // console.debug(`[IDLE] Added ${activeCredits} credits and ${activeExperience} XP. Accumulated time: ${this.accumulatedTime.toFixed(2)}s`);
+ }
+
+ // Update last active time for offline calculations
+ this.lastActiveTime = Date.now();
+ }
+
+ // Upgrade production rates
+ upgradeProduction(type) {
+ const upgradeCosts = {
+ credits: 100,
+ experience: 150,
+ energy: 80
+ };
+
+ const cost = upgradeCosts[type];
+ if (!cost || this.game.systems.economy.credits < cost) {
+ return false;
+ }
+
+ this.game.systems.economy.removeCredits(cost);
+
+ switch (type) {
+ case 'credits':
+ this.productionRates.credits += 2;
+ break;
+ case 'experience':
+ this.productionRates.experience += 1;
+ break;
+ case 'energy':
+ this.productionRates.energy += 0.2;
+ break;
+ }
+
+ this.game.showNotification(`Production upgraded: ${type}!`, 'success', 3000);
+return true;
+ }
+
+ // Bonus management
+ setBonus(type, value) {
+ if (this.bonuses[type] !== undefined) {
+ this.bonuses[type] = value;
+ this.game.showNotification(`${type} bonus set to ${value}x`, 'info', 3000);
+ }
+ }
+
+ // Achievement checking
+ checkAchievements() {
+ const achievements = [
+ {
+ id: 'idle_warrior',
+ name: 'Idle Warrior',
+ description: 'Earn 1,000,000 credits from idle',
+ condition: () => this.achievements.totalIdleCredits >= 1000000,
+ reward: { gems: 50, experience: 1000 }
+ },
+ {
+ id: 'time_master',
+ name: 'Time Master',
+ description: 'Accumulate 24 hours of offline time',
+ condition: () => this.achievements.totalOfflineTime >= 24 * 60 * 60 * 1000,
+ reward: { gems: 25, experience: 500 }
+ },
+ {
+ id: 'marathon_idle',
+ name: 'Marathon Idle',
+ description: 'Be offline for more than 12 hours at once',
+ condition: () => this.achievements.maxOfflineSession >= 12 * 60 * 60 * 1000,
+ reward: { gems: 100, experience: 2000 }
+ }
+ ];
+
+ achievements.forEach(achievement => {
+ if (achievement.condition()) {
+ this.unlockAchievement(achievement);
+ }
+ });
+ }
+
+ unlockAchievement(achievement) {
+ this.game.showNotification(`Achievement Unlocked: ${achievement.name}!`, 'success', 5000);
+ this.game.showNotification(achievement.description, 'info', 3000);
+
+ // Give rewards
+ if (achievement.reward.gems) {
+ this.game.systems.economy.addGems(achievement.reward.gems, 'achievement');
+ }
+
+ if (achievement.reward.experience) {
+ this.game.systems.player.addExperience(achievement.reward.experience);
+ }
+ }
+
+ // UI updates
+ updateUI() {
+ const offlineTimeElement = document.getElementById('offlineTime');
+ const offlineResourcesElement = document.getElementById('offlineResources');
+ const claimOfflineBtn = document.getElementById('claimOfflineBtn');
+
+ if (offlineTimeElement) {
+ const totalRewards = this.offlineRewards.credits +
+ this.offlineRewards.experience +
+ (this.offlineRewards.items.length * 100);
+ offlineTimeElement.textContent = totalRewards > 0 ? 'Available' : 'None';
+ }
+
+ if (offlineResourcesElement) {
+ const totalRewards = this.offlineRewards.credits +
+ this.offlineRewards.experience +
+ (this.offlineRewards.items.length * 100);
+ offlineResourcesElement.textContent = this.game.formatNumber(totalRewards);
+ }
+
+ if (claimOfflineBtn) {
+ const hasRewards = this.offlineRewards.credits > 0 ||
+ this.offlineRewards.experience > 0 ||
+ this.offlineRewards.items.length > 0;
+ claimOfflineBtn.disabled = !hasRewards;
+ }
+ }
+
+ // Save/Load
+ save() {
+ return {
+ lastActiveTime: this.lastActiveTime,
+ productionRates: this.productionRates,
+ bonuses: this.bonuses,
+ achievements: this.achievements,
+ offlineRewards: this.offlineRewards
+ };
+ }
+
+ load(data) {
+ if (data.lastActiveTime) this.lastActiveTime = data.lastActiveTime;
+ if (data.productionRates) this.productionRates = { ...this.productionRates, ...data.productionRates };
+ if (data.bonuses) this.bonuses = { ...this.bonuses, ...data.bonuses };
+ if (data.achievements) this.achievements = { ...this.achievements, ...data.achievements };
+ if (data.offlineRewards) this.offlineRewards = data.offlineRewards;
+}
+}
diff --git a/Client-Server/js/systems/QuestSystem.js b/Client-Server/js/systems/QuestSystem.js
new file mode 100644
index 0000000..3e01127
--- /dev/null
+++ b/Client-Server/js/systems/QuestSystem.js
@@ -0,0 +1,2726 @@
+/**
+ * Galaxy Strike Online - Quest System
+ * Manages hand-crafted and procedural quests
+ */
+
+class QuestSystem {
+ constructor(gameEngine) {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('QuestSystem.constructor', {
+ gameEngineProvided: !!gameEngine
+ });
+
+ this.game = gameEngine;
+
+ // Quest types
+ this.questTypes = {
+ main: 'Main Story',
+ daily: 'Daily',
+ weekly: 'Weekly',
+ completed: 'Completed',
+ failed: 'Failed Quests'
+ };
+
+ // Quest status
+ this.questStatus = {
+ available: 'available',
+ active: 'active',
+ completed: 'completed',
+ failed: 'failed'
+ };
+
+ if (debugLogger) debugLogger.logStep('Quest system configuration initialized', {
+ questTypes: Object.keys(this.questTypes),
+ questStatus: Object.keys(this.questStatus)
+ });
+
+ // Main story quests
+ this.mainQuests = [
+ {
+ id: 'tutorial_complete',
+ name: 'First Steps',
+ description: 'Complete the tutorial dungeon and learn the basics',
+ type: 'main',
+ status: 'available',
+ requirements: { level: 1 },
+ objectives: [
+ { id: 'clear_tutorial_dungeon', description: 'Complete the tutorial dungeon', target: 1, current: 0, type: 'tutorial_dungeon' },
+ { id: 'reach_level_2', description: 'Reach level 2', target: 2, current: 0, type: 'level' }
+ ],
+ rewards: { credits: 500, experience: 100, gems: 5 },
+ nextQuest: 'first_ship_upgrade'
+ },
+ {
+ id: 'first_ship_upgrade',
+ name: 'Ship Enhancement',
+ description: 'Upgrade your ship for better performance',
+ type: 'main',
+ status: 'available',
+ requirements: { quest: 'tutorial_complete' },
+ objectives: [
+ { id: 'upgrade_weapon', description: 'Upgrade ship weapons', target: 1, current: 0, type: 'upgrade' },
+ { id: 'upgrade_shield', description: 'Upgrade ship shields', target: 1, current: 0, type: 'upgrade' }
+ ],
+ rewards: { credits: 1000, experience: 200, gems: 10 },
+ nextQuest: 'join_guild'
+ },
+ {
+ id: 'join_guild',
+ name: 'Guild Recruitment',
+ description: 'Join a guild and participate in guild activities',
+ type: 'main',
+ status: 'available',
+ requirements: { quest: 'first_ship_upgrade', level: 5 },
+ objectives: [
+ { id: 'join_guild', description: 'Join a guild', target: 1, current: 0, type: 'guild' },
+ { id: 'guild_contribution', description: 'Contribute to guild', target: 100, current: 0, type: 'contribution' }
+ ],
+ rewards: { credits: 2000, experience: 500, gems: 20 },
+ nextQuest: 'master_commander'
+ },
+ {
+ id: 'master_commander',
+ name: 'Master Commander',
+ description: 'Become a master commander and lead your fleet to victory',
+ type: 'main',
+ status: 'available',
+ requirements: { quest: 'join_guild', level: 10 },
+ objectives: [
+ { id: 'reach_level_10', description: 'Reach level 10', target: 10, current: 0, type: 'level' },
+ { id: 'clear_10_dungeons', description: 'Clear 10 dungeons', target: 10, current: 0, type: 'dungeon' },
+ { id: 'max_skill', description: 'Max out one skill', target: 10, current: 0, type: 'skill' }
+ ],
+ rewards: { credits: 5000, experience: 1000, gems: 50, item: 'legendary_weapon' }
+ }
+ ];
+
+ // All possible daily quests (20 total)
+ this.allDailyQuests = [
+ // Easy quests (difficulty: 1)
+ {
+ id: 'daily_dungeon_easy',
+ name: 'Quick Dungeon Run',
+ description: 'Complete any dungeon',
+ type: 'daily',
+ difficulty: 1,
+ status: 'available',
+ objectives: [
+ { id: 'clear_dungeon', description: 'Clear 1 dungeon', target: 1, current: 0, type: 'dungeon' }
+ ],
+ rewards: { credits: 100, experience: 25, gems: 1 }
+ },
+ {
+ id: 'daily_combat_easy',
+ name: 'Light Combat',
+ description: 'Defeat a few enemies',
+ type: 'daily',
+ difficulty: 1,
+ status: 'available',
+ objectives: [
+ { id: 'defeat_enemies', description: 'Defeat 10 enemies', target: 10, current: 0, type: 'combat' }
+ ],
+ rewards: { credits: 80, experience: 20, gems: 1 }
+ },
+ {
+ id: 'daily_crafting_easy',
+ name: 'Basic Crafting',
+ description: 'Craft some items',
+ type: 'daily',
+ difficulty: 1,
+ status: 'available',
+ objectives: [
+ { id: 'craft_items', description: 'Craft 2 items', target: 2, current: 0, type: 'crafting' }
+ ],
+ rewards: { credits: 90, experience: 22, gems: 1 }
+ },
+ {
+ id: 'daily_level_easy',
+ name: 'Level Up',
+ description: 'Gain experience and level up',
+ type: 'daily',
+ difficulty: 1,
+ status: 'available',
+ objectives: [
+ { id: 'gain_level', description: 'Gain 1 level', target: 1, current: 0, type: 'level' }
+ ],
+ rewards: { credits: 120, experience: 30, gems: 2 }
+ },
+ {
+ id: 'daily_energy_easy',
+ name: 'Energy Management',
+ description: 'Use energy efficiently',
+ type: 'daily',
+ difficulty: 1,
+ status: 'available',
+ objectives: [
+ { id: 'use_energy', description: 'Use 50 energy', target: 50, current: 0, type: 'energy' }
+ ],
+ rewards: { credits: 70, experience: 18, gems: 1 }
+ },
+ // Medium quests (difficulty: 2)
+ {
+ id: 'daily_dungeon_medium',
+ name: 'Dungeon Explorer',
+ description: 'Complete multiple dungeons',
+ type: 'daily',
+ difficulty: 2,
+ status: 'available',
+ objectives: [
+ { id: 'clear_dungeons', description: 'Clear 3 dungeons', target: 3, current: 0, type: 'dungeon' }
+ ],
+ rewards: { credits: 300, experience: 75, gems: 3 }
+ },
+ {
+ id: 'daily_combat_medium',
+ name: 'Combat Training',
+ description: 'Defeat enemies in combat',
+ type: 'daily',
+ difficulty: 2,
+ status: 'available',
+ objectives: [
+ { id: 'defeat_enemies', description: 'Defeat 20 enemies', target: 20, current: 0, type: 'combat' }
+ ],
+ rewards: { credits: 150, experience: 40, gems: 1 }
+ },
+ {
+ id: 'daily_combat_hard',
+ name: 'Combat Veteran',
+ description: 'Defeat many enemies',
+ type: 'daily',
+ difficulty: 2,
+ status: 'available',
+ objectives: [
+ { id: 'defeat_enemies', description: 'Defeat 50 enemies', target: 50, current: 0, type: 'combat' }
+ ],
+ rewards: { credits: 250, experience: 60, gems: 3 }
+ },
+ {
+ id: 'daily_crafting_medium',
+ name: 'Master Crafter',
+ description: 'Craft many items',
+ type: 'daily',
+ difficulty: 2,
+ status: 'available',
+ objectives: [
+ { id: 'craft_items', description: 'Craft 5 items', target: 5, current: 0, type: 'crafting' }
+ ],
+ rewards: { credits: 280, experience: 70, gems: 3 }
+ },
+ {
+ id: 'daily_upgrade_medium',
+ name: 'Equipment Upgrade',
+ description: 'Upgrade your equipment',
+ type: 'daily',
+ difficulty: 2,
+ status: 'available',
+ objectives: [
+ { id: 'upgrade_items', description: 'Upgrade 3 items', target: 3, current: 0, type: 'upgrade' }
+ ],
+ rewards: { credits: 320, experience: 80, gems: 4 }
+ },
+ {
+ id: 'daily_wealth_medium',
+ name: 'Wealth Accumulator',
+ description: 'Earn credits',
+ type: 'daily',
+ difficulty: 2,
+ status: 'available',
+ objectives: [
+ { id: 'earn_credits', description: 'Earn 1000 credits', target: 1000, current: 0, type: 'credits' }
+ ],
+ rewards: { credits: 400, experience: 50, gems: 3 }
+ },
+ // Hard quests (difficulty: 3)
+ {
+ id: 'daily_dungeon_hard',
+ name: 'Dungeon Master',
+ description: 'Complete many dungeons',
+ type: 'daily',
+ difficulty: 3,
+ status: 'available',
+ objectives: [
+ { id: 'clear_dungeons', description: 'Clear 5 dungeons', target: 5, current: 0, type: 'dungeon' }
+ ],
+ rewards: { credits: 600, experience: 150, gems: 6 }
+ },
+ {
+ id: 'daily_combat_hard',
+ name: 'Combat Master',
+ description: 'Defeat many powerful enemies',
+ type: 'daily',
+ difficulty: 3,
+ status: 'available',
+ objectives: [
+ { id: 'defeat_enemies', description: 'Defeat 100 enemies', target: 100, current: 0, type: 'combat' }
+ ],
+ rewards: { credits: 500, experience: 120, gems: 5 }
+ },
+ {
+ id: 'daily_level_hard',
+ name: 'Power Leveling',
+ description: 'Gain multiple levels',
+ type: 'daily',
+ difficulty: 3,
+ status: 'available',
+ objectives: [
+ { id: 'gain_levels', description: 'Gain 3 levels', target: 3, current: 0, type: 'level' }
+ ],
+ rewards: { credits: 700, experience: 200, gems: 7 }
+ },
+ {
+ id: 'daily_boss_hard',
+ name: 'Boss Hunter',
+ description: 'Defeat boss enemies',
+ type: 'daily',
+ difficulty: 3,
+ status: 'available',
+ objectives: [
+ { id: 'defeat_bosses', description: 'Defeat 3 bosses', target: 3, current: 0, type: 'boss' }
+ ],
+ rewards: { credits: 800, experience: 180, gems: 8 }
+ },
+ {
+ id: 'daily_collection_hard',
+ name: 'Master Collector',
+ description: 'Collect rare items',
+ type: 'daily',
+ difficulty: 3,
+ status: 'available',
+ objectives: [
+ { id: 'collect_rare', description: 'Collect 10 rare items', target: 10, current: 0, type: 'collection' }
+ ],
+ rewards: { credits: 650, experience: 140, gems: 6 }
+ },
+ // Special quests (difficulty: 4)
+ {
+ id: 'daily_speedrun',
+ name: 'Speed Runner',
+ description: 'Complete dungeons quickly',
+ type: 'daily',
+ difficulty: 4,
+ status: 'available',
+ objectives: [
+ { id: 'fast_dungeon', description: 'Complete 2 dungeons under 5 minutes', target: 2, current: 0, type: 'speedrun' }
+ ],
+ rewards: { credits: 1000, experience: 250, gems: 10 }
+ },
+ {
+ id: 'daily_perfection',
+ name: 'Perfectionist',
+ description: 'Complete objectives without taking damage',
+ type: 'daily',
+ difficulty: 4,
+ status: 'available',
+ objectives: [
+ { id: 'perfect_runs', description: '3 perfect dungeon runs', target: 3, current: 0, type: 'perfect' }
+ ],
+ rewards: { credits: 1200, experience: 300, gems: 12 }
+ },
+ {
+ id: 'daily_multitask',
+ name: 'Multitask Master',
+ description: 'Complete multiple quest types',
+ type: 'daily',
+ difficulty: 4,
+ status: 'available',
+ objectives: [
+ { id: 'dungeon_task', description: 'Clear 2 dungeons', target: 2, current: 0, type: 'dungeon' },
+ { id: 'combat_task', description: 'Defeat 30 enemies', target: 30, current: 0, type: 'combat' },
+ { id: 'craft_task', description: 'Craft 2 items', target: 2, current: 0, type: 'crafting' }
+ ],
+ rewards: { credits: 1500, experience: 400, gems: 15 }
+ },
+ {
+ id: 'daily_endurance',
+ name: 'Endurance Test',
+ description: 'Complete long activities',
+ type: 'daily',
+ difficulty: 4,
+ status: 'available',
+ objectives: [
+ { id: 'long_dungeon', description: 'Complete 1 dungeon without healing', target: 1, current: 0, type: 'endurance' }
+ ],
+ rewards: { credits: 1100, experience: 280, gems: 11 }
+ },
+ {
+ id: 'daily_legendary',
+ name: 'Legendary Challenge',
+ description: 'Complete legendary difficulty content',
+ type: 'daily',
+ difficulty: 4,
+ status: 'available',
+ objectives: [
+ { id: 'legendary_content', description: 'Complete 1 legendary dungeon', target: 1, current: 0, type: 'legendary' }
+ ],
+ rewards: { credits: 2000, experience: 500, gems: 20, item: 'rare_material' }
+ }
+ ];
+
+ // Weekly quests (25 total quests with varied objectives)
+ this.allWeeklyQuests = [
+ // Combat-focused weekly quests
+ {
+ id: 'weekly_combat_basic',
+ name: 'Weekly Combat Duty',
+ description: 'Complete combat objectives throughout the week',
+ type: 'weekly',
+ difficulty: 1,
+ status: 'available',
+ objectives: [
+ { id: 'defeat_enemies', description: 'Defeat 100 enemies', target: 100, current: 0, type: 'combat' }
+ ],
+ rewards: { credits: 800, experience: 200, gems: 8 }
+ },
+ {
+ id: 'weekly_combat_elite',
+ name: 'Elite Hunter Weekly',
+ description: 'Hunt down elite enemies',
+ type: 'weekly',
+ difficulty: 3,
+ status: 'available',
+ objectives: [
+ { id: 'defeat_elites', description: 'Defeat 25 elite enemies', target: 25, current: 0, type: 'elite_combat' }
+ ],
+ rewards: { credits: 1500, experience: 400, gems: 15 }
+ },
+ {
+ id: 'weekly_boss_hunter',
+ name: 'Boss Hunter Weekly',
+ description: 'Defeat powerful boss enemies',
+ type: 'weekly',
+ difficulty: 4,
+ status: 'available',
+ objectives: [
+ { id: 'defeat_bosses', description: 'Defeat 10 bosses', target: 10, current: 0, type: 'boss' }
+ ],
+ rewards: { credits: 2500, experience: 600, gems: 25, item: 'boss_material' }
+ },
+
+ // Dungeon-focused weekly quests
+ {
+ id: 'weekly_dungeon_explorer',
+ name: 'Weekly Dungeon Explorer',
+ description: 'Explore various dungeons',
+ type: 'weekly',
+ difficulty: 2,
+ status: 'available',
+ objectives: [
+ { id: 'clear_dungeons', description: 'Clear 15 dungeons', target: 15, current: 0, type: 'dungeon' }
+ ],
+ rewards: { credits: 1200, experience: 300, gems: 12 }
+ },
+ {
+ id: 'weekly_dungeon_master',
+ name: 'Weekly Dungeon Master',
+ description: 'Master difficult dungeons',
+ type: 'weekly',
+ difficulty: 3,
+ status: 'available',
+ objectives: [
+ { id: 'clear_hard_dungeons', description: 'Clear 10 hard dungeons', target: 10, current: 0, type: 'hard_dungeon' }
+ ],
+ rewards: { credits: 2000, experience: 500, gems: 20 }
+ },
+ {
+ id: 'weekly_dungeon_extreme',
+ name: 'Extreme Dungeon Challenge',
+ description: 'Conquer extreme difficulty dungeons',
+ type: 'weekly',
+ difficulty: 4,
+ status: 'available',
+ objectives: [
+ { id: 'clear_extreme_dungeons', description: 'Clear 5 extreme dungeons', target: 5, current: 0, type: 'extreme_dungeon' }
+ ],
+ rewards: { credits: 3500, experience: 800, gems: 35, item: 'extreme_material' }
+ },
+
+ // Crafting and upgrade weekly quests
+ {
+ id: 'weekly_crafting_basic',
+ name: 'Weekly Crafting Session',
+ description: 'Craft items throughout the week',
+ type: 'weekly',
+ difficulty: 1,
+ status: 'available',
+ objectives: [
+ { id: 'craft_items', description: 'Craft 20 items', target: 20, current: 0, type: 'crafting' }
+ ],
+ rewards: { credits: 600, experience: 150, gems: 6 }
+ },
+ {
+ id: 'weekly_crafting_master',
+ name: 'Master Crafter Weekly',
+ description: 'Craft advanced items',
+ type: 'weekly',
+ difficulty: 3,
+ status: 'available',
+ objectives: [
+ { id: 'craft_advanced', description: 'Craft 10 advanced items', target: 10, current: 0, type: 'advanced_crafting' }
+ ],
+ rewards: { credits: 1800, experience: 450, gems: 18 }
+ },
+ {
+ id: 'weekly_upgrade_specialist',
+ name: 'Weekly Upgrade Specialist',
+ description: 'Upgrade equipment and systems',
+ type: 'weekly',
+ difficulty: 2,
+ status: 'available',
+ objectives: [
+ { id: 'upgrade_items', description: 'Upgrade 15 items', target: 15, current: 0, type: 'upgrade' }
+ ],
+ rewards: { credits: 1400, experience: 350, gems: 14 }
+ },
+
+ // Progression weekly quests
+ {
+ id: 'weekly_level_up',
+ name: 'Weekly Level Up Challenge',
+ description: 'Gain levels throughout the week',
+ type: 'weekly',
+ difficulty: 2,
+ status: 'available',
+ objectives: [
+ { id: 'gain_levels', description: 'Gain 5 levels', target: 5, current: 0, type: 'level' }
+ ],
+ rewards: { credits: 1000, experience: 250, gems: 10 }
+ },
+ {
+ id: 'weekly_skill_master',
+ name: 'Weekly Skill Mastery',
+ description: 'Improve your skills',
+ type: 'weekly',
+ difficulty: 3,
+ status: 'available',
+ objectives: [
+ { id: 'improve_skills', description: 'Gain 20 skill points', target: 20, current: 0, type: 'skill' }
+ ],
+ rewards: { credits: 1600, experience: 400, gems: 16 }
+ },
+
+ // Resource and wealth weekly quests
+ {
+ id: 'weekly_wealth_collector',
+ name: 'Weekly Wealth Collector',
+ description: 'Accumulate wealth',
+ type: 'weekly',
+ difficulty: 2,
+ status: 'available',
+ objectives: [
+ { id: 'earn_credits', description: 'Earn 5000 credits', target: 5000, current: 0, type: 'credits' }
+ ],
+ rewards: { credits: 2000, experience: 300, gems: 12 }
+ },
+ {
+ id: 'weekly_resource_gatherer',
+ name: 'Weekly Resource Gathering',
+ description: 'Collect valuable resources',
+ type: 'weekly',
+ difficulty: 2,
+ status: 'available',
+ objectives: [
+ { id: 'collect_resources', description: 'Collect 500 resources', target: 500, current: 0, type: 'collection' }
+ ],
+ rewards: { credits: 1200, experience: 320, gems: 13 }
+ },
+
+ // Special activity weekly quests
+ {
+ id: 'weekly_energy_management',
+ name: 'Weekly Energy Management',
+ description: 'Use energy efficiently',
+ type: 'weekly',
+ difficulty: 1,
+ status: 'available',
+ objectives: [
+ { id: 'use_energy', description: 'Use 500 energy', target: 500, current: 0, type: 'energy' }
+ ],
+ rewards: { credits: 800, experience: 180, gems: 8 }
+ },
+ {
+ id: 'weekly_speed_demon',
+ name: 'Weekly Speed Demon',
+ description: 'Complete activities quickly',
+ type: 'weekly',
+ difficulty: 3,
+ status: 'available',
+ objectives: [
+ { id: 'speed_runs', description: 'Complete 10 speed runs', target: 10, current: 0, type: 'speedrun' }
+ ],
+ rewards: { credits: 2200, experience: 550, gems: 22 }
+ },
+ {
+ id: 'weekly_perfectionist',
+ name: 'Weekly Perfectionist',
+ description: 'Complete flawless runs',
+ type: 'weekly',
+ difficulty: 4,
+ status: 'available',
+ objectives: [
+ { id: 'perfect_runs', description: 'Complete 8 perfect runs', target: 8, current: 0, type: 'perfect' }
+ ],
+ rewards: { credits: 3000, experience: 700, gems: 30, item: 'perfection_material' }
+ },
+
+ // Multi-objective weekly quests
+ {
+ id: 'weekly_all_rounder',
+ name: 'Weekly All-Rounder',
+ description: 'Complete various activities',
+ type: 'weekly',
+ difficulty: 3,
+ status: 'available',
+ objectives: [
+ { id: 'dungeon_task', description: 'Clear 8 dungeons', target: 8, current: 0, type: 'dungeon' },
+ { id: 'combat_task', description: 'Defeat 50 enemies', target: 50, current: 0, type: 'combat' },
+ { id: 'craft_task', description: 'Craft 5 items', target: 5, current: 0, type: 'crafting' }
+ ],
+ rewards: { credits: 2500, experience: 600, gems: 25 }
+ },
+ {
+ id: 'weekly_specialist',
+ name: 'Weekly Specialist',
+ description: 'Focus on specialized activities',
+ type: 'weekly',
+ difficulty: 3,
+ status: 'available',
+ objectives: [
+ { id: 'special_dungeons', description: 'Clear 5 themed dungeons', target: 5, current: 0, type: 'themed_dungeon' },
+ { id: 'special_crafting', description: 'Craft 8 themed items', target: 8, current: 0, type: 'themed_crafting' }
+ ],
+ rewards: { credits: 2300, experience: 580, gems: 23 }
+ },
+
+ // Exploration and discovery weekly quests
+ {
+ id: 'weekly_explorer',
+ name: 'Weekly Explorer',
+ description: 'Explore new areas and content',
+ type: 'weekly',
+ difficulty: 2,
+ status: 'available',
+ objectives: [
+ { id: 'explore_areas', description: 'Explore 20 new areas', target: 20, current: 0, type: 'exploration' }
+ ],
+ rewards: { credits: 1300, experience: 340, gems: 13 }
+ },
+ {
+ id: 'weekly_discovery',
+ name: 'Weekly Discovery',
+ description: 'Discover hidden secrets',
+ type: 'weekly',
+ difficulty: 3,
+ status: 'available',
+ objectives: [
+ { id: 'discover_secrets', description: 'Discover 15 secrets', target: 15, current: 0, type: 'discovery' }
+ ],
+ rewards: { credits: 1900, experience: 480, gems: 19 }
+ },
+
+ // Endurance and challenge weekly quests
+ {
+ id: 'weekly_endurance',
+ name: 'Weekly Endurance Test',
+ description: 'Complete long-form challenges',
+ type: 'weekly',
+ difficulty: 3,
+ status: 'available',
+ objectives: [
+ { id: 'endurance_runs', description: 'Complete 5 endurance runs', target: 5, current: 0, type: 'endurance' }
+ ],
+ rewards: { credits: 2100, experience: 530, gems: 21 }
+ },
+ {
+ id: 'weekly_survivor',
+ name: 'Weekly Survivor',
+ description: 'Survive challenging conditions',
+ type: 'weekly',
+ difficulty: 4,
+ status: 'available',
+ objectives: [
+ { id: 'survival_runs', description: 'Complete 3 survival runs', target: 3, current: 0, type: 'survival' }
+ ],
+ rewards: { credits: 3200, experience: 750, gems: 32, item: 'survival_material' }
+ },
+
+ // Social and community weekly quests
+ {
+ id: 'weekly_helper',
+ name: 'Weekly Helper',
+ description: 'Assist other players',
+ type: 'weekly',
+ difficulty: 2,
+ status: 'available',
+ objectives: [
+ { id: 'assist_players', description: 'Assist 10 players', target: 10, current: 0, type: 'assist' }
+ ],
+ rewards: { credits: 1100, experience: 280, gems: 11 }
+ },
+ {
+ id: 'weekly_leader',
+ name: 'Weekly Leader',
+ description: 'Lead group activities',
+ type: 'weekly',
+ difficulty: 3,
+ status: 'available',
+ objectives: [
+ { id: 'lead_activities', description: 'Lead 5 group activities', target: 5, current: 0, type: 'leadership' }
+ ],
+ rewards: { credits: 1700, experience: 430, gems: 17 }
+ },
+
+ // Legendary weekly quests
+ {
+ id: 'weekly_legendary',
+ name: 'Weekly Legendary Challenge',
+ description: 'Complete legendary difficulty content',
+ type: 'weekly',
+ difficulty: 5,
+ status: 'available',
+ objectives: [
+ { id: 'legendary_content', description: 'Complete 3 legendary dungeons', target: 3, current: 0, type: 'legendary' }
+ ],
+ rewards: { credits: 5000, experience: 1200, gems: 50, item: 'legendary_material' }
+ },
+ {
+ id: 'weekly_mythic',
+ name: 'Weekly Mythic Trial',
+ description: 'Face mythic level challenges',
+ type: 'weekly',
+ difficulty: 5,
+ status: 'available',
+ objectives: [
+ { id: 'mythic_trials', description: 'Complete 2 mythic trials', target: 2, current: 0, type: 'mythic' }
+ ],
+ rewards: { credits: 6000, experience: 1500, gems: 60, item: 'mythic_material' }
+ }
+ ];
+
+ // Currently active daily quests (3 random from allDailyQuests)
+ this.dailyQuests = [];
+ this.selectedDailyQuests = [];
+
+ // Currently active weekly quests (5 random from allWeeklyQuests)
+ this.weeklyQuests = [];
+ this.selectedWeeklyQuests = [];
+
+ // Current active quests
+ this.activeQuests = [];
+ this.completedQuests = [];
+ this.failedQuests = [];
+ this.completedDailyQuests = []; // History of completed daily quests
+ this.completedWeeklyQuests = []; // History of completed weekly quests
+
+ // Initialize daily quests with safety check
+ try {
+ if (this.allDailyQuests && Array.isArray(this.allDailyQuests)) {
+ console.log('[QUEST SYSTEM] Initializing daily quests...');
+ this.randomizeDailyQuests();
+ } else {
+ console.warn('[QUEST SYSTEM] allDailyQuests not properly initialized, skipping daily quest initialization');
+ this.dailyQuests = [];
+ this.selectedDailyQuests = [];
+ }
+ } catch (error) {
+ console.error('[QUEST SYSTEM] Error initializing daily quests:', error);
+ // Fallback to empty arrays to prevent crash
+ this.dailyQuests = [];
+ this.selectedDailyQuests = [];
+ this.activeQuests = [];
+ }
+
+ // Initialize weekly quests with safety check
+ try {
+ if (this.allWeeklyQuests && Array.isArray(this.allWeeklyQuests)) {
+ console.log('[QUEST SYSTEM] Initializing weekly quests...');
+ this.randomizeWeeklyQuests();
+ } else {
+ console.warn('[QUEST SYSTEM] allWeeklyQuests not properly initialized, skipping weekly quest initialization');
+ this.weeklyQuests = [];
+ this.selectedWeeklyQuests = [];
+ }
+ } catch (error) {
+ console.error('[QUEST SYSTEM] Error initializing weekly quests:', error);
+ // Fallback to empty arrays to prevent crash
+ this.weeklyQuests = [];
+ this.selectedWeeklyQuests = [];
+ this.activeQuests = [];
+ }
+
+ // Procedural quest templates
+ this.proceduralTemplates = {
+ bounty: {
+ name: 'Bounty Hunt',
+ description: 'Hunt down dangerous targets in the galaxy',
+ objectives: [
+ { id: 'defeat_targets', description: 'Defeat {target} targets', target: 5, current: 0, type: 'combat' }
+ ],
+ rewards: { credits: 300, experience: 75 }
+ },
+ exploration: {
+ name: 'Exploration Mission',
+ description: 'Explore uncharted regions of space',
+ objectives: [
+ { id: 'explore_areas', description: 'Explore {target} areas', target: 3, current: 0, type: 'exploration' }
+ ],
+ rewards: { credits: 250, experience: 60 }
+ },
+ collection: {
+ name: 'Resource Collection',
+ description: 'Collect valuable resources',
+ objectives: [
+ { id: 'collect_resources', description: 'Collect {target} resources', target: 100, current: 0, type: 'collection' }
+ ],
+ rewards: { credits: 200, experience: 50 }
+ },
+ escort: {
+ name: 'Escort Mission',
+ description: 'Escort valuable cargo through dangerous space',
+ objectives: [
+ { id: 'escort_complete', description: 'Complete escort mission', target: 1, current: 0, type: 'escort' }
+ ],
+ rewards: { credits: 400, experience: 100 }
+ }
+ };
+
+ // Quest generation settings
+ this.maxProceduralQuests = 3;
+ this.proceduralQuestRefresh = 30 * 60 * 1000; // 30 minutes
+
+ // Statistics
+ this.stats = {
+ questsCompleted: 0,
+ dailyQuestsCompleted: 0,
+ weeklyQuestsCompleted: 0,
+ totalRewardsEarned: { credits: 0, experience: 0, gems: 0 },
+ lastDailyReset: Date.now(),
+ lastWeeklyReset: Date.now()
+ };
+
+ // Initialize daily quests
+ this.randomizeDailyQuests();
+
+ // Initialize weekly quests
+ this.randomizeWeeklyQuests();
+
+ if (debugLogger) debugLogger.endStep('QuestSystem.constructor', {
+ mainQuestsCount: this.mainQuests.length,
+ allDailyQuestsCount: this.allDailyQuests.length,
+ allWeeklyQuestsCount: this.allWeeklyQuests.length,
+ maxProceduralQuests: this.maxProceduralQuests,
+ proceduralQuestRefresh: this.proceduralQuestRefresh,
+ initialStats: this.stats,
+ dailyQuestsInitialized: this.dailyQuests.length,
+ weeklyQuestsInitialized: this.weeklyQuests.length
+ });
+ }
+
+ async initialize() {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('QuestSystem.initialize', {
+ mainQuestsCount: this.mainQuests.length,
+ activeQuestsCount: this.activeQuests.length,
+ dailyQuestsCount: this.dailyQuests.length
+ });
+
+ // Initialize main quests
+ if (debugLogger) debugLogger.logStep('Initializing main quests');
+ this.initializeMainQuests();
+
+ // Check for daily reset
+ if (debugLogger) debugLogger.logStep('Checking for daily reset');
+ this.checkDailyReset();
+
+ // Check for weekly reset
+ if (debugLogger) debugLogger.logStep('Checking for weekly reset');
+ this.checkWeeklyReset();
+
+ // Start daily countdown timer
+ if (debugLogger) debugLogger.logStep('Starting daily countdown timer');
+ this.startDailyCountdown();
+
+ // Start weekly countdown timer
+ if (debugLogger) debugLogger.logStep('Starting weekly countdown timer');
+ this.startWeeklyCountdown();
+
+ // Update UI
+ this.updateQuestList();
+
+ if (debugLogger) debugLogger.endStep('QuestSystem.initialize', {
+ mainQuestsInitialized: true,
+ dailyResetChecked: true,
+ weeklyResetChecked: true,
+ countdownStarted: true,
+ weeklyQuestsInitialized: true,
+ uiUpdated: true
+ });
+ }
+
+ initializeMainQuests() {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('QuestSystem.initializeMainQuests', {
+ mainQuestsCount: this.mainQuests.length
+ });
+
+ // Set all quests to locked initially, then unlock based on requirements
+ this.mainQuests.forEach(quest => {
+ quest.status = 'locked';
+ });
+
+ if (debugLogger) debugLogger.logStep('All main quests set to locked status');
+
+ // Unlock first quest
+ if (this.mainQuests.length > 0) {
+ const firstQuest = this.mainQuests[0];
+ firstQuest.status = 'available';
+
+ if (debugLogger) debugLogger.logStep('First quest unlocked', {
+ questId: firstQuest.id,
+ questName: firstQuest.name
+ });
+ }
+
+ // Check quest availability based on requirements
+ if (debugLogger) debugLogger.logStep('Checking quest availability based on requirements');
+ this.checkQuestAvailability();
+
+ if (debugLogger) debugLogger.endStep('QuestSystem.initializeMainQuests', {
+ questsProcessed: this.mainQuests.length,
+ availableQuests: this.mainQuests.filter(q => q.status === 'available').length,
+ lockedQuests: this.mainQuests.filter(q => q.status === 'locked').length
+ });
+ }
+
+ checkQuestAvailability() {
+ const debugLogger = window.debugLogger;
+ const player = this.game.systems.player;
+
+ if (debugLogger) debugLogger.startStep('QuestSystem.checkQuestAvailability', {
+ playerLevel: player.stats.level,
+ mainQuestsCount: this.mainQuests.length
+ });
+
+ const availabilityChanges = [];
+
+ this.mainQuests.forEach(quest => {
+ const oldStatus = quest.status;
+ const requirementsMet = this.checkQuestRequirements(quest);
+
+ if (quest.status === 'locked' && requirementsMet) {
+ quest.status = 'available';
+ availabilityChanges.push({
+ questId: quest.id,
+ questName: quest.name,
+ oldStatus: oldStatus,
+ newStatus: 'available',
+ reason: 'Requirements met'
+ });
+ this.game.showNotification(`New quest available: ${quest.name}`, 'info', 5000);
+ } else if (quest.status === 'available' && !requirementsMet) {
+ // Hide quests that were available but no longer meet requirements
+ quest.status = 'locked';
+ availabilityChanges.push({
+ questId: quest.id,
+ questName: quest.name,
+ oldStatus: oldStatus,
+ newStatus: 'locked',
+ reason: 'Requirements no longer met'
+ });
+ }
+ });
+
+ if (debugLogger) debugLogger.endStep('QuestSystem.checkQuestAvailability', {
+ availabilityChanges: availabilityChanges,
+ questsProcessed: this.mainQuests.length,
+ availableQuests: this.mainQuests.filter(q => q.status === 'available').length,
+ lockedQuests: this.mainQuests.filter(q => q.status === 'locked').length
+ });
+ }
+
+ // Quest management
+ startQuest(questId) {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('QuestSystem.startQuest', {
+ questId: questId,
+ currentActiveQuests: this.activeQuests.length
+ });
+
+ const quest = this.findQuest(questId);
+ if (!quest) {
+ console.log('Quest not found:', questId);
+
+ if (debugLogger) debugLogger.endStep('QuestSystem.startQuest', {
+ success: false,
+ reason: 'Quest not found',
+ questId: questId
+ });
+ return false;
+ }
+
+ console.log('Attempting to start quest:', questId, 'status:', quest.status);
+
+ if (debugLogger) debugLogger.logStep('Quest found', {
+ questId: quest.id,
+ questName: quest.name,
+ questType: quest.type,
+ currentStatus: quest.status,
+ requirements: quest.requirements
+ });
+
+ if (quest.status !== 'available') {
+ console.log('Quest not available, status:', quest.status);
+ this.game.showNotification('Quest is not available', 'error', 3000);
+
+ if (debugLogger) debugLogger.endStep('QuestSystem.startQuest', {
+ success: false,
+ reason: 'Quest not available',
+ questId: questId,
+ questName: quest.name,
+ currentStatus: quest.status
+ });
+ return false;
+ }
+
+ // Check requirements
+ const requirementsMet = this.checkQuestRequirements(quest);
+ console.log('Requirements met:', requirementsMet, 'for quest:', questId);
+
+ if (debugLogger) debugLogger.logStep('Requirements check', {
+ requirements: quest.requirements,
+ requirementsMet: requirementsMet
+ });
+
+ if (!requirementsMet) {
+ this.game.showNotification('Requirements not met', 'error', 3000);
+
+ if (debugLogger) debugLogger.endStep('QuestSystem.startQuest', {
+ success: false,
+ reason: 'Requirements not met',
+ questId: questId,
+ questName: quest.name,
+ requirements: quest.requirements
+ });
+ return false;
+ }
+
+ console.log('Before status change - quest status:', quest.status);
+ quest.status = 'active';
+ console.log('After status change - quest status:', quest.status);
+
+ // Also update the quest in the main quests array to ensure consistency
+ const mainQuest = this.mainQuests.find(q => q.id === questId);
+ if (mainQuest) {
+ mainQuest.status = 'active';
+ console.log('Updated mainQuest status to:', mainQuest.status);
+
+ if (debugLogger) debugLogger.logStep('Main quest status updated', {
+ questId: mainQuest.id,
+ newStatus: mainQuest.status
+ });
+ }
+
+ this.activeQuests.push(quest);
+
+ // Check initial progress for existing player stats
+ if (debugLogger) debugLogger.logStep('Checking initial quest progress');
+ this.checkInitialQuestProgress(quest);
+
+ // Update the UI to reflect the status change
+ this.updateQuestList();
+
+ this.game.showNotification(`Quest started: ${quest.name}`, 'success', 3000);
+
+ if (debugLogger) debugLogger.endStep('QuestSystem.startQuest', {
+ success: true,
+ questId: questId,
+ questName: quest.name,
+ questType: quest.type,
+ objectives: quest.objectives.map(obj => ({
+ id: obj.id,
+ description: obj.description,
+ target: obj.target,
+ current: obj.current
+ })),
+ activeQuestsCount: this.activeQuests.length
+ });
+
+ return true;
+ }
+
+ completeQuest(questId) {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('QuestSystem.completeQuest', {
+ questId: questId,
+ currentActiveQuests: this.activeQuests.length,
+ completedQuestsCount: this.completedQuests.length
+ });
+
+ const quest = this.findQuest(questId);
+ if (!quest) {
+ if (debugLogger) debugLogger.endStep('QuestSystem.completeQuest', {
+ success: false,
+ reason: 'Quest not found',
+ questId: questId
+ });
+ return false;
+ }
+
+ if (debugLogger) debugLogger.logStep('Quest found for completion', {
+ questId: quest.id,
+ questName: quest.name,
+ questType: quest.type,
+ currentStatus: quest.status,
+ objectives: quest.objectives.map(obj => ({
+ id: obj.id,
+ description: obj.description,
+ target: obj.target,
+ current: obj.current,
+ completed: obj.current >= obj.target
+ }))
+ });
+
+ if (quest.status !== 'active') {
+ this.game.showNotification('Quest is not active', 'error', 3000);
+
+ if (debugLogger) debugLogger.endStep('QuestSystem.completeQuest', {
+ success: false,
+ reason: 'Quest not active',
+ questId: questId,
+ questName: quest.name,
+ currentStatus: quest.status
+ });
+ return false;
+ }
+
+ // Check if all objectives are completed
+ const allObjectivesComplete = quest.objectives.every(obj => obj.current >= obj.target);
+ if (!allObjectivesComplete) {
+ this.game.showNotification('Not all objectives completed', 'warning', 3000);
+
+ if (debugLogger) debugLogger.endStep('QuestSystem.completeQuest', {
+ success: false,
+ reason: 'Not all objectives completed',
+ questId: questId,
+ questName: quest.name,
+ objectives: quest.objectives.map(obj => ({
+ id: obj.id,
+ current: obj.current,
+ target: obj.target,
+ completed: obj.current >= obj.target
+ }))
+ });
+ return false;
+ }
+
+ // Complete quest
+ quest.status = 'completed';
+ quest.completedAt = Date.now();
+ this.completedQuests.push(quest);
+
+ if (debugLogger) debugLogger.logStep('Quest marked as completed', {
+ questId: quest.id,
+ questName: quest.name,
+ completedAt: quest.completedAt,
+ rewards: quest.rewards
+ });
+
+ // Save completed daily quests to history
+ if (quest.type === 'daily') {
+ const questCopy = { ...quest, completedAt: Date.now() };
+ this.completedDailyQuests.push(questCopy);
+
+ if (debugLogger) debugLogger.logStep('Daily quest added to history', {
+ questId: quest.id,
+ questName: quest.name,
+ completedDailyQuestsCount: this.completedDailyQuests.length
+ });
+ }
+
+ // Save completed weekly quests to history
+ if (quest.type === 'weekly') {
+ const questCopy = { ...quest, completedAt: Date.now() };
+ this.completedWeeklyQuests.push(questCopy);
+
+ if (debugLogger) debugLogger.logStep('Weekly quest added to history', {
+ questId: quest.id,
+ questName: quest.name,
+ completedWeeklyQuestsCount: this.completedWeeklyQuests.length
+ });
+ }
+
+ // Remove from active quests
+ const activeIndex = this.activeQuests.findIndex(q => q.id === questId);
+ if (activeIndex !== -1) {
+ this.activeQuests.splice(activeIndex, 1);
+
+ if (debugLogger) debugLogger.logStep('Quest removed from active list', {
+ questId: questId,
+ removedIndex: activeIndex,
+ remainingActiveQuests: this.activeQuests.length
+ });
+ }
+
+ // Give rewards
+ if (debugLogger) debugLogger.logStep('Giving quest rewards');
+ this.giveQuestRewards(quest);
+
+ // Update statistics
+ const oldStats = { ...this.stats };
+ this.stats.questsCompleted++;
+ if (quest.type === 'daily') {
+ this.stats.dailyQuestsCompleted++;
+ }
+ if (quest.type === 'weekly') {
+ this.stats.weeklyQuestsCompleted++;
+ }
+
+ if (debugLogger) debugLogger.logStep('Quest statistics updated', {
+ oldStats: oldStats,
+ newStats: this.stats,
+ questsCompletedIncrement: this.stats.questsCompleted - oldStats.questsCompleted,
+ dailyQuestsCompletedIncrement: this.stats.dailyQuestsCompleted - oldStats.dailyQuestsCompleted,
+ weeklyQuestsCompletedIncrement: this.stats.weeklyQuestsCompleted - oldStats.weeklyQuestsCompleted
+ });
+
+ // Unlock next quest if it's a main quest
+ if (quest.nextQuest) {
+ if (debugLogger) debugLogger.logStep('Unlocking next quest', {
+ nextQuestId: quest.nextQuest
+ });
+ this.unlockNextQuest(quest.nextQuest);
+ }
+
+ // Check for other quests that might now be available
+ if (debugLogger) debugLogger.logStep('Checking for newly available quests');
+ this.checkQuestAvailability();
+
+ this.game.showNotification(`Quest completed: ${quest.name}!`, 'success', 5000);
+
+ if (debugLogger) debugLogger.endStep('QuestSystem.completeQuest', {
+ success: true,
+ questId: questId,
+ questName: quest.name,
+ questType: quest.type,
+ rewardsGiven: quest.rewards,
+ nextQuestUnlocked: quest.nextQuest || null,
+ finalActiveQuestsCount: this.activeQuests.length,
+ completedQuestsCount: this.completedQuests.length
+ });
+
+ return true;
+ }
+
+ giveQuestRewards(quest) {
+ const debugLogger = window.debugLogger;
+
+ if (quest.rewards.credits) {
+ this.game.systems.economy.addCredits(quest.rewards.credits, 'quest');
+ this.stats.totalRewardsEarned.credits += quest.rewards.credits;
+ rewardsGiven.credits = quest.rewards.credits;
+
+ if (debugLogger) debugLogger.logStep('Credits reward given', {
+ amount: quest.rewards.credits,
+ oldCredits: oldPlayerStats.credits,
+ newCredits: this.game.systems.economy.credits
+ });
+ }
+
+ if (quest.rewards.experience) {
+ this.game.systems.player.addExperience(quest.rewards.experience);
+ this.stats.totalRewardsEarned.experience += quest.rewards.experience;
+ rewardsGiven.experience = quest.rewards.experience;
+
+ if (debugLogger) debugLogger.logStep('Experience reward given', {
+ amount: quest.rewards.experience,
+ oldExperience: oldPlayerStats.experience,
+ newExperience: this.game.systems.player.stats.experience,
+ oldLevel: oldPlayerStats.level,
+ newLevel: this.game.systems.player.stats.level,
+ leveledUp: this.game.systems.player.stats.level > oldPlayerStats.level
+ });
+ }
+
+ if (quest.rewards.gems) {
+ this.game.systems.economy.addGems(quest.rewards.gems, 'quest');
+ this.stats.totalRewardsEarned.gems += quest.rewards.gems;
+ rewardsGiven.gems = quest.rewards.gems;
+
+ if (debugLogger) debugLogger.logStep('Gems reward given', {
+ amount: quest.rewards.gems,
+ oldGems: oldPlayerStats.gems,
+ newGems: this.game.systems.economy.gems
+ });
+ }
+
+ if (quest.rewards.item) {
+ const item = this.game.systems.inventory.generateItem('weapon', 'legendary');
+ this.game.systems.inventory.addItem(item);
+ rewardsGiven.item = item;
+
+ if (debugLogger) debugLogger.logStep('Item reward given', {
+ itemGenerated: item,
+ itemType: item.type,
+ itemRarity: item.rarity,
+ itemName: item.name
+ });
+ }
+
+ if (debugLogger) debugLogger.endStep('QuestSystem.giveQuestRewards', {
+ questId: quest.id,
+ questName: quest.name,
+ rewardsGiven: rewardsGiven,
+ playerChanges: {
+ credits: { old: oldPlayerStats.credits, new: this.game.systems.economy.credits },
+ gems: { old: oldPlayerStats.gems, new: this.game.systems.economy.gems },
+ experience: { old: oldPlayerStats.experience, new: this.game.systems.player.stats.experience },
+ level: { old: oldPlayerStats.level, new: this.game.systems.player.stats.level }
+ },
+ totalRewardsEarnedUpdated: this.stats.totalRewardsEarned
+ });
+ }
+
+ // Objective progress
+ updateObjectiveProgress(type, amount, context = {}) {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('QuestSystem.updateObjectiveProgress', {
+ type: type,
+ amount: amount,
+ context: context,
+ activeQuestsCount: this.activeQuests.length
+ });
+
+ const progressUpdates = [];
+
+ // Update all active quests
+ this.activeQuests.forEach(quest => {
+ const questProgress = [];
+
+ quest.objectives.forEach(objective => {
+ if (objective.type === type && objective.current < objective.target) {
+ const oldProgress = objective.current;
+ objective.current = Math.min(objective.current + amount, objective.target);
+ const actualProgress = objective.current - oldProgress;
+
+ if (actualProgress > 0) {
+ questProgress.push({
+ objectiveId: objective.id,
+ description: objective.description,
+ oldProgress: oldProgress,
+ newProgress: objective.current,
+ target: objective.target,
+ progressMade: actualProgress,
+ completed: objective.current >= objective.target
+ });
+ }
+ }
+ });
+
+ if (questProgress.length > 0) {
+ progressUpdates.push({
+ questId: quest.id,
+ questName: quest.name,
+ questType: quest.type,
+ objectivesUpdated: questProgress
+ });
+
+ // Check if quest is completed
+ if (quest.objectives.every(obj => obj.current >= obj.target)) {
+ this.game.showNotification(`Quest ready to turn in: ${quest.name}`, 'success', 4000);
+
+ if (debugLogger) debugLogger.logStep('Quest completed via progress update', {
+ questId: quest.id,
+ questName: quest.name,
+ allObjectivesCompleted: true
+ });
+ }
+ }
+ });
+
+ if (debugLogger) debugLogger.endStep('QuestSystem.updateObjectiveProgress', {
+ type: type,
+ amount: amount,
+ progressUpdates: progressUpdates,
+ questsUpdated: progressUpdates.length
+ });
+ }
+
+ // Event listeners for quest progress
+ onDungeonCompleted() {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.log('QuestSystem.onDungeonCompleted triggered', {
+ activeQuestsCount: this.activeQuests.length
+ });
+
+ this.updateObjectiveProgress('dungeon', 1);
+ }
+
+ onEnemyDefeated() {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.log('QuestSystem.onEnemyDefeated triggered', {
+ activeQuestsCount: this.activeQuests.length
+ });
+
+ this.updateObjectiveProgress('combat', 1);
+ }
+
+ onLevelUp(newLevel) {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.log('QuestSystem.onLevelUp triggered', {
+ newLevel: newLevel,
+ activeQuestsCount: this.activeQuests.length
+ });
+
+ this.updateObjectiveProgress('level', 1);
+ }
+
+ onItemCrafted() {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.log('QuestSystem.onItemCrafted triggered', {
+ activeQuestsCount: this.activeQuests.length
+ });
+
+ this.updateObjectiveProgress('crafting', 1);
+ }
+
+ // Procedural quest generation (deprecated - replaced by weekly quests)
+ /*
+ generateProceduralQuests() {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('QuestSystem.generateProceduralQuests', {
+ currentActiveQuests: this.activeQuests.length,
+ maxProceduralQuests: this.maxProceduralQuests,
+ proceduralTemplatesCount: Object.keys(this.proceduralTemplates).length,
+ proceduralQuestRefresh: this.proceduralQuestRefresh
+ });
+
+ // Clear existing procedural quests
+ const oldProceduralQuests = this.activeQuests.filter(q => q.type === 'procedural');
+ this.activeQuests = this.activeQuests.filter(q => q.type !== 'procedural');
+
+ if (debugLogger) debugLogger.logStep('Cleared existing procedural quests', {
+ oldProceduralQuestsCount: oldProceduralQuests.length,
+ oldProceduralQuests: oldProceduralQuests.map(q => ({ id: q.id, name: q.name }))
+ });
+
+ // Generate new procedural quests
+ const templates = Object.keys(this.proceduralTemplates);
+ const questCount = Math.min(this.maxProceduralQuests, templates.length);
+ const generatedQuests = [];
+
+ for (let i = 0; i < questCount; i++) {
+ const templateKey = templates[Math.floor(Math.random() * templates.length)];
+ const template = this.proceduralTemplates[templateKey];
+
+ const quest = {
+ id: `procedural_${Date.now()}_${i}`,
+ name: template.name,
+ description: template.description.replace('{target}', template.objectives[0].target),
+ type: 'procedural',
+ status: 'available',
+ objectives: template.objectives.map(obj => ({ ...obj })),
+ rewards: { ...template.rewards },
+ expiresAt: Date.now() + this.proceduralQuestRefresh
+ };
+
+ this.activeQuests.push(quest);
+ generatedQuests.push(quest);
+
+ if (debugLogger) debugLogger.logStep('Procedural quest generated', {
+ templateKey: templateKey,
+ questId: quest.id,
+ questName: quest.name,
+ questDescription: quest.description,
+ objectives: quest.objectives,
+ rewards: quest.rewards,
+ expiresAt: quest.expiresAt
+ });
+ }
+
+ // Schedule next refresh
+ setTimeout(() => this.generateProceduralQuests(), this.proceduralQuestRefresh);
+
+ if (debugLogger) debugLogger.endStep('QuestSystem.generateProceduralQuests', {
+ oldProceduralQuestsRemoved: oldProceduralQuests.length,
+ newProceduralQuestsGenerated: generatedQuests.length,
+ generatedQuests: generatedQuests.map(q => ({ id: q.id, name: q.name, type: q.type })),
+ nextRefreshScheduled: true,
+ refreshInterval: this.proceduralQuestRefresh
+ });
+ }
+ */
+
+ randomizeDailyQuests() {
+ const debugLogger = window.debugLogger;
+
+ console.log('[QUEST SYSTEM] randomizeDailyQuests called');
+
+ // Safety check for allDailyQuests
+ if (!this.allDailyQuests || !Array.isArray(this.allDailyQuests)) {
+ console.error('[QUEST SYSTEM] allDailyQuests is not available or not an array');
+ return;
+ }
+
+ console.log(`[QUEST SYSTEM] allDailyQuests count: ${this.allDailyQuests.length}`);
+
+ if (debugLogger) debugLogger.startStep('QuestSystem.randomizeDailyQuests', {
+ allDailyQuestsCount: this.allDailyQuests.length,
+ currentDailyQuestsCount: this.dailyQuests.length,
+ currentSelectedDailyQuestsCount: this.selectedDailyQuests.length
+ });
+
+ // Clear current daily quests
+ this.dailyQuests = [];
+ this.selectedDailyQuests = [];
+
+ if (debugLogger) debugLogger.logStep('Cleared current daily quests');
+
+ // Select 3 random quests from allDailyQuests
+ const shuffled = [...this.allDailyQuests].sort(() => Math.random() - 0.5);
+ this.selectedDailyQuests = shuffled.slice(0, 3);
+
+ console.log(`[QUEST SYSTEM] Selected ${this.selectedDailyQuests.length} random daily quests`);
+
+ if (debugLogger) debugLogger.logStep('Selected random daily quests', {
+ selectedQuests: this.selectedDailyQuests.map(q => ({
+ id: q.id,
+ name: q.name,
+ difficulty: q.difficulty
+ }))
+ });
+
+ // Create deep copies for active quests and automatically start them
+ this.selectedDailyQuests.forEach(questTemplate => {
+ try {
+ const quest = {
+ ...questTemplate,
+ id: `${questTemplate.id}_${Date.now()}`,
+ status: 'active', // Auto-start daily quests
+ objectives: questTemplate.objectives.map(obj => ({ ...obj, current: 0 }))
+ };
+ this.dailyQuests.push(quest);
+ this.activeQuests.push(quest); // Add to active quests for progress tracking
+
+ console.log(`[QUEST SYSTEM] Created daily quest: ${quest.id}, name: ${quest.name}, status: ${quest.status}`);
+ } catch (error) {
+ console.error('[QUEST SYSTEM] Error creating daily quest from template:', error);
+ }
+ });
+
+ if (debugLogger) debugLogger.endStep('QuestSystem.randomizeDailyQuests', {
+ dailyQuestsCreated: this.dailyQuests.length,
+ activeQuestsCount: this.activeQuests.length,
+ selectedDailyQuestsCount: this.selectedDailyQuests.length
+ });
+ }
+
+ // Daily reset
+ reset() {
+ const debugLogger = window.debugLogger;
+ const oldState = {
+ activeQuestsCount: this.activeQuests.length,
+ completedQuestsCount: this.completedQuests.length,
+ dailyQuestsCount: this.dailyQuests.length,
+ selectedDailyQuestsCount: this.selectedDailyQuests.length
+ };
+
+ if (debugLogger) debugLogger.startStep('QuestSystem.reset', {
+ oldState: oldState
+ });
+
+ this.activeQuests = [];
+ this.completedQuests = [];
+ this.dailyQuests = [];
+ this.selectedDailyQuests = [];
+ this.lastDailyReset = Date.now();
+
+ // Reset main quest statuses
+ this.mainQuests.forEach(quest => {
+ quest.status = quest.id === 'tutorial_complete' ? 'available' : 'locked';
+ });
+
+ if (debugLogger) debugLogger.endStep('QuestSystem.reset', {
+ oldState: oldState,
+ newState: {
+ activeQuestsCount: this.activeQuests.length,
+ completedQuestsCount: this.completedQuests.length,
+ dailyQuestsCount: this.dailyQuests.length,
+ selectedDailyQuestsCount: this.selectedDailyQuests.length,
+ mainQuestsReset: true
+ }
+ });
+ }
+
+ clear() {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('QuestSystem.clear');
+
+ this.reset();
+
+ if (debugLogger) debugLogger.endStep('QuestSystem.clear', {
+ resetCompleted: true
+ });
+ }
+
+ checkDailyReset() {
+ const debugLogger = window.debugLogger;
+ const now = Date.now();
+ const lastReset = this.lastDailyReset;
+ const daysSinceReset = Math.floor((now - lastReset) / (24 * 60 * 60 * 1000));
+
+ if (debugLogger) debugLogger.startStep('QuestSystem.checkDailyReset', {
+ now: now,
+ lastReset: lastReset,
+ daysSinceReset: daysSinceReset,
+ threshold: 1
+ });
+
+ if (daysSinceReset >= 1) {
+ if (debugLogger) debugLogger.logStep('Daily reset triggered', {
+ daysSinceReset: daysSinceReset
+ });
+
+ this.resetDailyQuests();
+ this.stats.lastDailyReset = now;
+ this.stats.dailyQuestsCompleted = 0;
+
+ this.game.showNotification('Daily quests refreshed!', 'success', 4000);
+
+ if (debugLogger) debugLogger.logStep('Daily reset completed', {
+ newLastDailyReset: this.stats.lastDailyReset,
+ dailyQuestsCompletedReset: this.stats.dailyQuestsCompleted
+ });
+ }
+
+ // Remove only COMPLETED daily quests from active list, not all daily quests
+ const oldActiveQuestsCount = this.activeQuests.length;
+ this.activeQuests = this.activeQuests.filter(q => q.type !== 'daily' || q.status !== 'completed');
+ const removedDailyQuests = oldActiveQuestsCount - this.activeQuests.length;
+
+ if (debugLogger) debugLogger.endStep('QuestSystem.checkDailyReset', {
+ resetTriggered: daysSinceReset >= 1,
+ removedDailyQuests: removedDailyQuests,
+ finalActiveQuestsCount: this.activeQuests.length
+ });
+ }
+
+ resetDailyQuests() {
+ // Remove old daily quests from active list first
+ this.activeQuests = this.activeQuests.filter(q => q.type !== 'daily');
+
+ // Generate new random daily quests
+ this.randomizeDailyQuests();
+ }
+
+ startDailyCountdown() {
+ console.log('[QUEST SYSTEM] Starting daily countdown timer');
+
+ // Update countdown immediately
+ this.updateDailyCountdown();
+
+ // Only start timer if in multiplayer mode or game is actively running
+ const shouldStartTimer = window.smartSaveManager?.isMultiplayer || this.game?.isRunning;
+
+ if (shouldStartTimer) {
+ // Update every second
+ this.dailyCountdownInterval = setInterval(() => {
+ this.updateDailyCountdown();
+ }, 1000);
+
+ console.log('[QUEST SYSTEM] Daily countdown timer started with interval:', this.dailyCountdownInterval);
+ } else {
+ console.log('[QUEST SYSTEM] Skipping daily countdown timer - not in multiplayer mode');
+ }
+ }
+
+ updateDailyCountdown() {
+ // Always update countdown so it's ready when user switches to daily tab
+ const now = new Date();
+ const tomorrow = new Date(now);
+ tomorrow.setDate(tomorrow.getDate() + 1);
+ tomorrow.setHours(0, 0, 0, 0); // Set to midnight
+
+ const timeUntilReset = tomorrow - now;
+ const hours = Math.floor(timeUntilReset / (1000 * 60 * 60));
+ const minutes = Math.floor((timeUntilReset % (1000 * 60 * 60)) / (1000 * 60));
+ const seconds = Math.floor((timeUntilReset % (1000 * 60)) / 1000);
+
+ // Update countdown display
+ const countdownElement = document.getElementById('dailyCountdown');
+ if (countdownElement) {
+ countdownElement.textContent = `Daily quests reset in: ${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
+ console.log('[QUEST SYSTEM] Daily countdown updated:', countdownElement.textContent);
+ } else {
+ console.log('[QUEST SYSTEM] Daily countdown element not found');
+ }
+ }
+
+ // Weekly quest management functions
+ randomizeWeeklyQuests() {
+ const debugLogger = window.debugLogger;
+
+ console.log('[QUEST SYSTEM] randomizeWeeklyQuests called');
+
+ // Safety check for allWeeklyQuests
+ if (!this.allWeeklyQuests || !Array.isArray(this.allWeeklyQuests)) {
+ console.error('[QUEST SYSTEM] allWeeklyQuests not properly initialized');
+ this.weeklyQuests = [];
+ this.selectedWeeklyQuests = [];
+ return;
+ }
+
+ console.log(`[QUEST SYSTEM] allWeeklyQuests count: ${this.allWeeklyQuests.length}`);
+
+ if (debugLogger) debugLogger.startStep('QuestSystem.randomizeWeeklyQuests', {
+ allWeeklyQuestsCount: this.allWeeklyQuests.length,
+ currentWeeklyQuestsCount: this.weeklyQuests.length,
+ currentSelectedWeeklyQuestsCount: this.selectedWeeklyQuests.length
+ });
+
+ // Clear existing weekly quests from active quests
+ this.activeQuests = this.activeQuests.filter(q => q.type !== 'weekly');
+
+ // Select 5 random weekly quests
+ const shuffled = [...this.allWeeklyQuests].sort(() => Math.random() - 0.5);
+ this.selectedWeeklyQuests = shuffled.slice(0, 5);
+ this.weeklyQuests = this.selectedWeeklyQuests.map(quest => ({ ...quest }));
+
+ // Add weekly quests to active quests
+ this.weeklyQuests.forEach(quest => {
+ if (quest.status === 'available') {
+ this.activeQuests.push(quest);
+ }
+ });
+
+ console.log(`[QUEST SYSTEM] Created ${this.weeklyQuests.length} weekly quests`);
+
+ if (debugLogger) debugLogger.endStep('QuestSystem.randomizeWeeklyQuests', {
+ weeklyQuestsCreated: this.weeklyQuests.length,
+ activeQuestsCount: this.activeQuests.length,
+ selectedWeeklyQuestsCount: this.selectedWeeklyQuests.length
+ });
+ }
+
+ checkWeeklyReset() {
+ const debugLogger = window.debugLogger;
+ const now = Date.now();
+ const lastReset = this.lastWeeklyReset || 0;
+
+ // Calculate if we need to reset based on Saturday midnight
+ const currentDateTime = new Date();
+ const dayOfWeek = currentDateTime.getDay(); // 0 = Sunday, 1 = Monday, ..., 6 = Saturday
+ const currentHour = currentDateTime.getHours();
+ const currentMinute = currentDateTime.getMinutes();
+ const currentSecond = currentDateTime.getSeconds();
+
+ let shouldReset = false;
+ let resetTime;
+
+ if (dayOfWeek === 6) { // Today is Saturday
+ resetTime = new Date(currentDateTime);
+ resetTime.setHours(0, 0, 0, 0); // Set to midnight
+
+ // Reset at exactly midnight on Saturday
+ if (currentHour === 0 && currentMinute === 0 && currentSecond === 0 && lastReset < resetTime.getTime()) {
+ shouldReset = true;
+ }
+ } else {
+ // Calculate last Saturday's reset time (midnight)
+ const daysSinceLastSaturday = (dayOfWeek + 1) % 7 || 7;
+ const lastSaturday = new Date(currentDateTime);
+ lastSaturday.setDate(currentDateTime.getDate() - daysSinceLastSaturday);
+ lastSaturday.setHours(0, 0, 0, 0);
+ resetTime = lastSaturday;
+
+ if (lastReset < resetTime.getTime()) {
+ shouldReset = true;
+ }
+ }
+
+ if (debugLogger) debugLogger.startStep('QuestSystem.checkWeeklyReset', {
+ now: now,
+ lastReset: lastReset,
+ currentDayOfWeek: dayOfWeek,
+ currentHour: currentHour,
+ currentMinute: currentMinute,
+ resetTime: resetTime ? resetTime.getTime() : null,
+ shouldReset: shouldReset
+ });
+
+ if (shouldReset) {
+ console.log('[QUEST SYSTEM] Weekly reset triggered');
+
+ // Reset weekly quests
+ this.lastWeeklyReset = now;
+
+ // Remove completed weekly quests from active quests
+ const oldActiveQuestsCount = this.activeQuests.length;
+ this.activeQuests = this.activeQuests.filter(q => q.type !== 'weekly' || q.status !== 'completed');
+ const removedWeeklyQuests = oldActiveQuestsCount - this.activeQuests.length;
+
+ if (debugLogger) debugLogger.endStep('QuestSystem.checkWeeklyReset', {
+ resetTriggered: true,
+ removedWeeklyQuests: removedWeeklyQuests,
+ finalActiveQuestsCount: this.activeQuests.length
+ });
+
+ // Clear weekly quest history and generate new ones
+ this.completedWeeklyQuests = [];
+ this.activeQuests = this.activeQuests.filter(q => q.type !== 'weekly');
+
+ // Generate new random weekly quests
+ this.randomizeWeeklyQuests();
+
+ // Update UI to show new weekly quests
+ this.updateQuestList();
+
+ // Show notification to player
+ this.game.showNotification('Weekly quests have been reset! New quests available.', 'info', 5000);
+ }
+ }
+
+ startWeeklyCountdown() {
+ console.log('[QUEST SYSTEM] Starting weekly countdown timer');
+
+ // Update countdown immediately
+ this.updateWeeklyCountdown();
+
+ // Only start timer if in multiplayer mode or game is actively running
+ const shouldStartTimer = window.smartSaveManager?.isMultiplayer || this.game?.isRunning;
+
+ if (shouldStartTimer) {
+ // Update every minute (weekly changes less frequently)
+ this.weeklyCountdownInterval = setInterval(() => {
+ this.updateWeeklyCountdown();
+ // Check for weekly reset every minute
+ this.checkWeeklyReset();
+ }, 60000); // 1 minute
+
+ console.log('[QUEST SYSTEM] Weekly countdown timer started with interval:', this.weeklyCountdownInterval);
+ } else {
+ console.log('[QUEST SYSTEM] Skipping weekly countdown timer - not in multiplayer mode');
+ }
+ }
+
+ updateWeeklyCountdown() {
+ const now = new Date();
+ const dayOfWeek = now.getDay(); // 0 = Sunday, 1 = Monday, ..., 6 = Saturday
+ const currentHour = now.getHours();
+ const currentMinute = now.getMinutes();
+ const currentSecond = now.getSeconds();
+
+ console.log('[QUEST SYSTEM] Weekly countdown debug:', {
+ now: now.toString(),
+ dayOfWeek: dayOfWeek,
+ dayName: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'][dayOfWeek],
+ currentHour: currentHour,
+ currentMinute: currentMinute,
+ currentSecond: currentSecond
+ });
+
+ // Calculate days until Saturday midnight (00:00:00)
+ let daysUntilSaturday;
+ if (dayOfWeek === 6) { // Today is Saturday
+ // Check if it's before midnight
+ if (now.getHours() < 0 || (now.getHours() === 0 && now.getMinutes() === 0 && now.getSeconds() === 0)) {
+ daysUntilSaturday = 0;
+ } else {
+ // It's after midnight on Saturday, next reset is next Saturday
+ daysUntilSaturday = 7;
+ }
+ } else {
+ // Simple calculation: days until Saturday = 6 - current day
+ // Sunday (0) -> 6 days, Monday (1) -> 5 days, ..., Friday (5) -> 1 day
+ daysUntilSaturday = 6 - dayOfWeek;
+ if (daysUntilSaturday <= 0) daysUntilSaturday += 7; // Ensure positive
+ }
+
+ console.log('[QUEST SYSTEM] Days until Saturday:', daysUntilSaturday);
+
+ // Create the target reset time (Saturday midnight)
+ const nextSaturday = new Date(now);
+ nextSaturday.setDate(now.getDate() + daysUntilSaturday);
+ nextSaturday.setHours(0, 0, 0, 0); // Set to midnight
+
+ console.log('[QUEST SYSTEM] Next Saturday reset time:', nextSaturday.toString());
+
+ let timeUntilReset = nextSaturday.getTime() - now.getTime();
+
+ // Ensure timeUntilReset is positive (handle edge cases)
+ if (timeUntilReset <= 0) {
+ console.log('[QUEST SYSTEM] Time until reset is negative or zero, adding 7 days');
+ nextSaturday.setDate(nextSaturday.getDate() + 7);
+ timeUntilReset = nextSaturday.getTime() - now.getTime();
+ }
+
+ console.log('[QUEST SYSTEM] Time until reset (ms):', timeUntilReset);
+
+ // Convert to days, hours, minutes, seconds
+ const totalSeconds = Math.floor(timeUntilReset / 1000);
+ const days = Math.floor(totalSeconds / (24 * 60 * 60));
+ const hours = Math.floor((totalSeconds % (24 * 60 * 60)) / (60 * 60));
+ const minutes = Math.floor((totalSeconds % (60 * 60)) / 60);
+ const seconds = totalSeconds % 60;
+
+ console.log('[QUEST SYSTEM] Time breakdown:', {
+ timeUntilReset: timeUntilReset,
+ totalSeconds: totalSeconds,
+ days: days,
+ hours: hours,
+ minutes: minutes,
+ seconds: seconds,
+ daysUntilSaturday: daysUntilSaturday,
+ nextSaturdayDate: nextSaturday.toString(),
+ currentDate: now.toString()
+ });
+
+ // Update countdown display
+ const countdownElement = document.getElementById('weeklyCountdown');
+ if (countdownElement) {
+ // Use the calculated days from timeUntilReset, not daysUntilSaturday
+ if (days > 0) {
+ countdownElement.textContent = `Weekly quests reset in: ${days}d ${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
+ } else if (hours > 0) {
+ countdownElement.textContent = `Weekly quests reset in: ${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
+ } else {
+ countdownElement.textContent = `Weekly quests reset in: ${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
+ }
+ console.log('[QUEST SYSTEM] Weekly countdown updated:', countdownElement.textContent, `(${days}d ${hours}h ${minutes}m ${seconds}s)`);
+ } else {
+ console.log('[QUEST SYSTEM] Weekly countdown element not found');
+ }
+ }
+
+// ... (rest of the code remains the same)
+ // Quest requirements and unlocking
+ checkQuestRequirements(quest) {
+ console.log('Checking requirements for quest:', quest.id, quest.requirements);
+
+ if (!quest.requirements) {
+ console.log('No requirements, returning true');
+ return true;
+ }
+
+ const player = this.game.systems.player;
+ const dungeonSystem = this.game.systems.dungeonSystem;
+
+ if (quest.requirements.level) {
+ console.log('Level requirement:', quest.requirements.level, 'Player level:', player.stats.level);
+ if (player.stats.level < quest.requirements.level) {
+ console.log('Level requirement not met');
+ return false;
+ }
+ }
+
+ if (quest.requirements.quest) {
+ const requiredQuest = this.findQuest(quest.requirements.quest);
+ console.log('Required quest:', quest.requirements.quest, 'Found:', requiredQuest ? requiredQuest.id : 'null', 'Status:', requiredQuest ? requiredQuest.status : 'null');
+ if (!requiredQuest || requiredQuest.status !== 'completed') {
+ console.log('Quest requirement not met');
+ return false;
+ }
+ }
+
+ if (quest.requirements.guild) {
+ // Check if player is in a guild
+ if (!player.stats.guildJoined) {
+ return false;
+ }
+ }
+
+ if (quest.requirements.dungeons) {
+ const dungeonsCleared = player.stats.dungeonsCleared || 0;
+ if (dungeonsCleared < quest.requirements.dungeons) {
+ return false;
+ }
+ }
+
+ if (quest.requirements.upgrades) {
+ // Check ship upgrade requirements
+ const upgradesCompleted = this.checkUpgradeProgress(quest);
+ if (upgradesCompleted < quest.requirements.upgrades) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ checkUpgradeProgress(quest) {
+ const player = this.game.systems.player;
+ let upgradesCompleted = 0;
+
+ quest.objectives.forEach(objective => {
+ if (objective.type === 'upgrade') {
+ if (player.stats[`upgraded_${objective.id}`]) {
+ upgradesCompleted++;
+ }
+ }
+ });
+
+ return upgradesCompleted;
+ }
+
+ getMaxSkillLevel(player) {
+ // Get the highest skill level from player
+ if (!player.skills) return 0;
+
+ let maxLevel = 0;
+ Object.values(player.skills).forEach(skill => {
+ if (skill.level > maxLevel) {
+ maxLevel = skill.level;
+ }
+ });
+
+ return maxLevel;
+ }
+
+ unlockNextQuest(nextQuestId) {
+ const nextQuest = this.findQuest(nextQuestId);
+ if (nextQuest && nextQuest.status === 'locked') {
+ nextQuest.status = 'available';
+ this.game.showNotification(`New quest available: ${nextQuest.name}`, 'info', 4000);
+ }
+ }
+
+ findQuest(questId) {
+ // Search in all quest arrays including failed quests with safety checks
+ const allQuests = [
+ ...(this.mainQuests || []),
+ ...(this.dailyQuests || []),
+ ...(this.activeQuests || []),
+ ...(this.completedQuests || []),
+ ...(this.failedQuests || [])
+ ];
+
+ return allQuests.find(q => q.id === questId);
+ }
+
+ getQuestsByType(type) {
+ console.log(`[QUEST SYSTEM] getQuestsByType called with type: ${type}`);
+
+ switch (type) {
+ case 'main':
+ const mainQuests = (this.mainQuests || []).filter(q => q.status === 'available' || q.status === 'active');
+ console.log(`[QUEST SYSTEM] Main quests found: ${mainQuests.length}`);
+ return mainQuests;
+ case 'daily':
+ // Ensure daily quests are initialized
+ if (!this.dailyQuests || this.dailyQuests.length === 0) {
+ console.log('[QUEST SYSTEM] Daily quests not initialized, forcing initialization');
+ this.randomizeDailyQuests();
+ }
+
+ const dailyQuests = (this.dailyQuests || []).filter(q => q.status !== 'failed');
+ console.log(`[QUEST SYSTEM] Daily quests found: ${dailyQuests.length}`);
+ console.log(`[QUEST SYSTEM] All daily quests array:`, this.dailyQuests);
+ dailyQuests.forEach(quest => {
+ console.log(`[QUEST SYSTEM] Daily quest: ${quest.id}, status: ${quest.status}, name: ${quest.name}`);
+ });
+ return dailyQuests;
+ case 'weekly':
+ const weeklyQuests = (this.weeklyQuests || []).filter(q => q.status !== 'failed');
+ console.log(`[QUEST SYSTEM] Weekly quests found: ${weeklyQuests.length}`);
+ return weeklyQuests;
+ case 'completed':
+ return this.getCompletedQuests();
+ case 'failed':
+ return (this.failedQuests || []);
+ default:
+ console.log(`[QUEST SYSTEM] Unknown quest type: ${type}`);
+ return [];
+ }
+ }
+
+ getCompletedQuests() {
+ const completed = [];
+
+ // Add completed main quests
+ if (this.mainQuests) {
+ completed.push(...this.mainQuests.filter(q => q.status === 'completed'));
+ }
+
+ // Add completed daily quests (from history)
+ if (this.completedDailyQuests) {
+ completed.push(...this.completedDailyQuests);
+ }
+
+ // Add completed weekly quests
+ if (this.weeklyQuests) {
+ completed.push(...this.weeklyQuests.filter(q => q.status === 'completed'));
+ }
+
+ // Sort by completion time (most recent first)
+ return completed.sort((a, b) => (b.completedAt || 0) - (a.completedAt || 0));
+ }
+
+ // UI updates
+ updateUI() {
+ this.updateQuestList();
+ this.updateQuestStats();
+ }
+
+ updateQuestList() {
+ console.log('[QUEST SYSTEM] updateQuestList called');
+ const questListElement = document.getElementById('questList');
+ if (!questListElement) {
+ console.log('[QUEST SYSTEM] questListElement not found');
+ return;
+ }
+
+ const activeType = document.querySelector('.quest-tab-btn.active')?.dataset.type || 'main';
+ console.log(`[QUEST SYSTEM] Active quest tab type: ${activeType}`);
+
+ const quests = this.getQuestsByType(activeType);
+ console.log(`[QUEST SYSTEM] Getting quests for type: ${activeType}, found: ${quests.length} quests`);
+
+ questListElement.innerHTML = '';
+
+ if (quests.length === 0) {
+ console.log(`[QUEST SYSTEM] No quests found for type: ${activeType}`);
+ questListElement.innerHTML = 'No quests available
';
+ return;
+ }
+
+ console.log(`[QUEST SYSTEM] Rendering ${quests.length} quests for type: ${activeType}`);
+ quests.forEach(quest => {
+ console.log(`[QUEST SYSTEM] Rendering quest: ${quest.id}, status: ${quest.status}`);
+ const questElement = document.createElement('div');
+ questElement.className = `quest-item ${quest.status}`;
+
+ // Add difficulty indicator for daily quests
+ const difficultyIndicator = quest.type === 'daily' && quest.difficulty ?
+ `
+ ${'★'.repeat(quest.difficulty)}
+
` : '';
+
+ // Handle completed quests differently
+ const isCompleted = quest.status === 'completed';
+ const progressPercent = isCompleted ? 100 : (quest.objectives.length > 0 ?
+ (quest.objectives.reduce((sum, obj) => sum + (obj.current / obj.target), 0) / quest.objectives.length) * 100 : 0);
+
+ const objectivesHtml = isCompleted ?
+ '✓ All objectives completed
' :
+ quest.objectives.map(obj => `
+
+
${obj.description}
+
${obj.current}/${obj.target}
+
+
+ `).join('');
+
+ const rewardsHtml = Object.entries(quest.rewards)
+ .filter(([key]) => key !== 'item')
+ .map(([key, value]) => {
+ const icon = key === 'credits' ? 'fa-coins' : key === 'experience' ? 'fa-star' : 'fa-gem';
+ return ` ${value}
`;
+ }).join('');
+
+ // Add completion time for completed quests
+ const completionTime = isCompleted && quest.completedAt ?
+ `Completed: ${new Date(quest.completedAt).toLocaleDateString()}
` : '';
+
+ questElement.innerHTML = `
+
+ ${quest.description}
+
+ ${objectivesHtml}
+
+ ${completionTime}
+
+
+
${isCompleted ? 'Completed' : Math.round(progressPercent) + '% Complete'}
+
+
+ ${quest.status === 'available' ?
+ `
Start Quest ` :
+ quest.status === 'active' ?
+ `
+
+ Cancel Quest
+
+
+ Turn In
+
+
` :
+ quest.status === 'completed' ?
+ '
Completed ' :
+ quest.status === 'failed' ?
+ `
+ Failed: ${quest.failureReason || 'Cancelled'}
+
+ Retry Quest
+
+
` :
+ '
Available '
+ }
+
+ `;
+
+ questListElement.appendChild(questElement);
+ });
+ }
+
+ updateQuestStats() {
+ // Update quest statistics if elements exist
+ const questsCompletedElement = document.getElementById('questsCompleted');
+ if (questsCompletedElement) {
+ questsCompletedElement.textContent = this.stats.questsCompleted;
+ }
+ }
+
+ // Quest cancellation
+ cancelQuest(questId) {
+ const quest = this.findQuest(questId);
+ if (!quest) {
+ this.game.showNotification('Quest not found!', 'error', 3000);
+ return;
+ }
+
+ if (quest.status !== 'active') {
+ this.game.showNotification('Only active quests can be cancelled!', 'warning', 3000);
+ return;
+ }
+
+ if (!confirm(`Are you sure you want to cancel "${quest.name}"? This quest will be marked as failed.`)) {
+ return;
+ }
+
+ // Move quest to failed
+ quest.status = 'failed';
+ quest.failedAt = Date.now();
+ quest.failureReason = 'Cancelled by player';
+
+ // Remove from active quests
+ this.activeQuests = this.activeQuests.filter(q => q.id !== questId);
+
+ // Add to failed quests
+ this.failedQuests.push({...quest});
+
+ // Apply failure penalty
+ this.applyQuestFailurePenalty(quest);
+
+ this.game.showNotification(`Quest "${quest.name}" cancelled and marked as failed.`, 'warning', 4000);
+ this.switchToFailedTab();
+ this.updateQuestList();
+ }
+
+ // Quest failure handling
+ failQuest(questId, reason = 'Time expired') {
+ const quest = this.findQuest(questId);
+ if (!quest) return;
+
+ if (quest.status !== 'active') return;
+
+ quest.status = 'failed';
+ quest.failedAt = Date.now();
+ quest.failureReason = reason;
+
+ // Remove from active quests
+ this.activeQuests = this.activeQuests.filter(q => q.id !== questId);
+
+ // Add to failed quests
+ this.failedQuests.push({...quest});
+
+ // Apply failure penalty
+ this.applyQuestFailurePenalty(quest);
+
+ this.game.showNotification(`Quest "${quest.name}" failed: ${reason}`, 'error', 4000);
+ this.switchToFailedTab();
+ this.updateQuestList();
+ }
+
+ applyQuestFailurePenalty(quest) {
+ // Apply failure penalties
+ const player = this.game.systems.player;
+
+ // Reduce reputation
+ if (player.stats.reputation) {
+ player.stats.reputation = Math.max(0, player.stats.reputation - 10);
+ }
+
+ // Apply other penalties based on quest type
+ switch (quest.type) {
+ case 'daily':
+ this.stats.dailyQuestsCompleted = Math.max(0, this.stats.dailyQuestsCompleted - 1);
+ break;
+ }
+
+ // Only update player UI if in multiplayer mode or game is actively running
+ if (this.game.shouldUpdateGUI()) {
+ player.updateUI();
+ }
+ }
+
+ // Retry failed quest
+ retryQuest(questId) {
+ const failedQuestIndex = this.failedQuests.findIndex(q => q.id === questId);
+ if (failedQuestIndex === -1) {
+ this.game.showNotification('Failed quest not found!', 'error', 3000);
+ return;
+ }
+
+ const quest = this.failedQuests[failedQuestIndex];
+
+ // Check if retry is allowed (24 hour cooldown)
+ const timeSinceFailure = Date.now() - quest.failedAt;
+ const retryCooldown = 24 * 60 * 60 * 1000; // 24 hours
+
+ if (timeSinceFailure < retryCooldown) {
+ const remainingTime = Math.ceil((retryCooldown - timeSinceFailure) / (60 * 60 * 1000));
+ this.game.showNotification(`Can retry this quest in ${remainingTime} hours.`, 'warning', 3000);
+ return;
+ }
+
+ // Reset quest progress
+ quest.objectives.forEach(obj => {
+ obj.current = 0;
+ });
+ quest.status = 'available';
+ quest.failedAt = null;
+ quest.failureReason = null;
+
+ // Remove from failed quests
+ this.failedQuests.splice(failedQuestIndex, 1);
+
+ this.game.showNotification(`Quest "${quest.name}" is now available to retry.`, 'success', 3000);
+ this.updateQuestList();
+ }
+
+ // Switch to failed quests tab
+ switchToFailedTab() {
+ // Remove active class from all quest tab buttons
+ document.querySelectorAll('.quest-tab-btn').forEach(btn => {
+ btn.classList.remove('active');
+ });
+
+ // Add active class to failed quests tab
+ const failedTabBtn = document.querySelector('[data-type="failed"]');
+ if (failedTabBtn) {
+ failedTabBtn.classList.add('active');
+ }
+
+ // Update the quest list to show failed quests
+ this.updateQuestList();
+ }
+
+ // Quest progress tracking
+ updateQuestProgress(objectiveType, amount = 1) {
+ // Update progress for all active quests with this objective type
+ [...(this.activeQuests || []), ...(this.mainQuests || []), ...(this.dailyQuests || [])].forEach(quest => {
+ if (quest.status === 'active') {
+ quest.objectives.forEach(objective => {
+ if (objective.type === objectiveType || objective.id.includes(objectiveType)) {
+ const oldValue = objective.current;
+ objective.current = Math.min(objective.current + amount, objective.target);
+
+ if (objective.current !== oldValue) {
+ this.game.showNotification(`${objective.description}: ${objective.current}/${objective.target}`, 'info', 2000);
+ }
+ }
+ });
+ }
+ });
+
+ this.updateQuestList();
+ this.checkQuestCompletion();
+ }
+
+ updateLevelProgress(newLevel) {
+ // Update progress for level-based objectives
+ [...(this.activeQuests || []), ...(this.mainQuests || []), ...(this.dailyQuests || [])].forEach(quest => {
+ if (quest.status === 'active') {
+ quest.objectives.forEach(objective => {
+ if (objective.type === 'level' || objective.id.includes('level')) {
+ const oldValue = objective.current;
+ objective.current = Math.min(newLevel, objective.target);
+
+ if (objective.current !== oldValue) {
+ this.game.showNotification(`${objective.description}: ${objective.current}/${objective.target}`, 'info', 2000);
+ }
+ }
+ });
+ }
+ });
+
+ this.updateQuestList();
+ this.checkQuestCompletion();
+ }
+
+ updateDungeonProgress() {
+ // Update progress for dungeon-based objectives
+ [...(this.activeQuests || []), ...(this.mainQuests || []), ...(this.dailyQuests || [])].forEach(quest => {
+ if (quest.status === 'active') {
+ quest.objectives.forEach(objective => {
+ if (objective.type === 'dungeon' || objective.id.includes('dungeon') || objective.id.includes('clear_dungeon')) {
+ const oldValue = objective.current;
+ objective.current = Math.min(objective.current + 1, objective.target);
+
+ if (objective.current !== oldValue) {
+ this.game.showNotification(`${objective.description}: ${objective.current}/${objective.target}`, 'info', 2000);
+ }
+ }
+ });
+ }
+ });
+
+ this.updateQuestList();
+ this.checkQuestCompletion();
+ }
+
+ updateTutorialDungeonProgress() {
+ // Update progress for tutorial dungeon objectives
+ [...(this.activeQuests || []), ...(this.mainQuests || []), ...(this.dailyQuests || [])].forEach(quest => {
+ if (quest.status === 'active') {
+ quest.objectives.forEach(objective => {
+ if (objective.type === 'tutorial_dungeon' || objective.id.includes('tutorial_dungeon')) {
+ const oldValue = objective.current;
+ objective.current = 1; // Tutorial dungeon is completed
+
+ if (objective.current !== oldValue) {
+ this.game.showNotification(`${objective.description}: ${objective.current}/${objective.target}`, 'info', 2000);
+ }
+ }
+ });
+ }
+ });
+
+ this.updateQuestList();
+ this.checkQuestCompletion();
+ }
+
+ updateUpgradeProgress(upgradeType) {
+ // Update progress for upgrade objectives
+ [...(this.activeQuests || []), ...(this.mainQuests || []), ...(this.dailyQuests || [])].forEach(quest => {
+ if (quest.status === 'active') {
+ quest.objectives.forEach(objective => {
+ if (objective.type === 'upgrade' && (objective.id === upgradeType || objective.id.includes(upgradeType))) {
+ const oldValue = objective.current;
+ objective.current = 1; // Upgrade completed
+
+ if (objective.current !== oldValue) {
+ this.game.showNotification(`${objective.description}: ${objective.current}/${objective.target}`, 'info', 2000);
+ }
+ }
+ });
+ }
+ });
+
+ this.updateQuestList();
+ this.checkQuestCompletion();
+ this.checkQuestAvailability();
+ }
+
+ updateGuildProgress(action, amount = 1) {
+ // Update progress for guild objectives
+ [...(this.activeQuests || []), ...(this.mainQuests || []), ...(this.dailyQuests || [])].forEach(quest => {
+ if (quest.status === 'active') {
+ quest.objectives.forEach(objective => {
+ if (action === 'join' && (objective.type === 'guild' || objective.id.includes('guild'))) {
+ const oldValue = objective.current;
+ objective.current = 1; // Guild joined
+
+ if (objective.current !== oldValue) {
+ this.game.showNotification(`${objective.description}: ${objective.current}/${objective.target}`, 'info', 2000);
+ }
+ }
+
+ if (action === 'contribute' && (objective.type === 'contribution' || objective.id.includes('contribution'))) {
+ const oldValue = objective.current;
+ objective.current = Math.min(objective.current + amount, objective.target);
+
+ if (objective.current !== oldValue) {
+ this.game.showNotification(`${objective.description}: ${objective.current}/${objective.target}`, 'info', 2000);
+ }
+ }
+ });
+ }
+ });
+
+ this.updateQuestList();
+ this.checkQuestCompletion();
+ this.checkQuestAvailability();
+ }
+
+ updateSkillProgress() {
+ // Update progress for skill objectives
+ [...(this.activeQuests || []), ...(this.mainQuests || []), ...(this.dailyQuests || [])].forEach(quest => {
+ if (quest.status === 'active') {
+ quest.objectives.forEach(objective => {
+ if (objective.type === 'skill' || objective.id.includes('skill')) {
+ const player = this.game.systems.player;
+ const maxSkillLevel = this.getMaxSkillLevel(player);
+ const oldValue = objective.current;
+ objective.current = Math.min(maxSkillLevel, objective.target);
+
+ if (objective.current !== oldValue) {
+ this.game.showNotification(`${objective.description}: ${objective.current}/${objective.target}`, 'info', 2000);
+ }
+ }
+ });
+ }
+ });
+
+ this.updateQuestList();
+ this.checkQuestCompletion();
+ this.checkQuestAvailability();
+ }
+
+ checkInitialQuestProgress(quest) {
+ const player = this.game.systems.player;
+ const dungeonSystem = this.game.systems.dungeonSystem;
+
+ console.log(`[QUEST SYSTEM] Checking initial progress for quest: ${quest.id}`);
+ console.log(`[QUEST SYSTEM] Player stats:`, player.stats);
+
+ quest.objectives.forEach(objective => {
+ console.log(`[QUEST SYSTEM] Processing objective: ${objective.id}, type: ${objective.type}`);
+
+ // Check level-based objectives
+ if (objective.type === 'level' || objective.id.includes('level')) {
+ objective.current = Math.min(player.stats.level, objective.target);
+ console.log(`[QUEST SYSTEM] Level objective ${objective.id}: ${objective.current}/${objective.target}`);
+ }
+
+ // Check dungeon-based objectives
+ if (objective.type === 'dungeon' || objective.id.includes('dungeon') || objective.id.includes('clear_dungeon')) {
+ // Check any dungeon completion - use multiple sources
+ const playerDungeons = player.stats.dungeonsCleared || 0;
+ const dungeonSystemStats = dungeonSystem ? dungeonSystem.stats.dungeonsCompleted || 0 : 0;
+ const dungeonsCleared = Math.max(playerDungeons, dungeonSystemStats);
+
+ objective.current = Math.min(dungeonsCleared, objective.target);
+
+ // Debug logging
+ console.log(`Dungeon objective check: Player=${playerDungeons}, DungeonSystem=${dungeonSystemStats}, Final=${dungeonsCleared}`);
+ }
+
+ // Check tutorial dungeon objective
+ if (objective.type === 'tutorial_dungeon' || objective.id.includes('tutorial_dungeon')) {
+ // Check if tutorial dungeon has been completed
+ const tutorialCompleted = player.stats.tutorialDungeonCompleted || false;
+ objective.current = tutorialCompleted ? 1 : 0;
+ console.log(`[QUEST SYSTEM] Tutorial dungeon objective ${objective.id}: ${objective.current}/${objective.target}, completed: ${tutorialCompleted}`);
+ }
+
+ // Check upgrade objectives
+ if (objective.type === 'upgrade' || objective.id.includes('upgrade')) {
+ const upgradeCompleted = player.stats[`upgraded_${objective.id}`] || false;
+ objective.current = upgradeCompleted ? 1 : 0;
+ }
+
+ // Check guild objectives
+ if (objective.type === 'guild' || objective.id.includes('guild')) {
+ const guildJoined = player.stats.guildJoined || false;
+ objective.current = guildJoined ? 1 : 0;
+ }
+
+ // Check contribution objectives
+ if (objective.type === 'contribution' || objective.id.includes('contribution')) {
+ const contribution = player.stats.guildContribution || 0;
+ objective.current = Math.min(contribution, objective.target);
+ }
+
+ // Check skill objectives
+ if (objective.type === 'skill' || objective.id.includes('skill')) {
+ const maxSkillLevel = this.getMaxSkillLevel(player);
+ objective.current = Math.min(maxSkillLevel, objective.target);
+ }
+
+ // Check other objective types
+ if (objective.type === 'collection' || objective.id.includes('collect')) {
+ // Check inventory for collected items
+ const inventory = this.game.systems.inventory;
+ if (inventory && inventory.items) {
+ const totalCollected = Object.values(inventory.items).reduce((sum, item) => sum + item.quantity, 0);
+ objective.current = Math.min(totalCollected, objective.target);
+ }
+ }
+ });
+
+ // Check if quest is already complete
+ const allComplete = quest.objectives.every(obj => obj.current >= obj.target);
+ if (allComplete) {
+ quest.status = 'completed';
+ this.completeQuest(quest.id);
+ }
+ }
+
+ checkQuestCompletion() {
+ // Check if any active quests are complete
+ this.activeQuests.forEach(quest => {
+ if (quest.status === 'active') {
+ const allComplete = quest.objectives.every(obj => obj.current >= obj.target);
+ if (allComplete) {
+ quest.status = 'completed';
+ this.completeQuest(quest.id);
+ }
+ }
+ });
+ }
+
+ // Save/Load
+ save() {
+ const debugLogger = window.debugLogger;
+
+ // if (debugLogger) debugLogger.startStep('QuestSystem.save', {
+ // mainQuestsCount: this.mainQuests.length,
+ // dailyQuestsCount: this.dailyQuests.length,
+ // activeQuestsCount: this.activeQuests.length,
+ // completedQuestsCount: this.completedQuests.length,
+ // currentStats: this.stats
+ // });
+
+ const saveData = {
+ mainQuests: this.mainQuests,
+ dailyQuests: this.dailyQuests,
+ activeQuests: this.activeQuests,
+ completedQuests: this.completedQuests,
+ stats: this.stats
+ };
+
+ // if (debugLogger) debugLogger.endStep('QuestSystem.save', {
+ // saveDataSize: JSON.stringify(saveData).length,
+ // mainQuestsSaved: this.mainQuests.length,
+ // dailyQuestsSaved: this.dailyQuests.length,
+ // activeQuestsSaved: this.activeQuests.length,
+ // completedQuestsSaved: this.completedQuests.length,
+ // statsSaved: this.stats,
+ // saveData: saveData
+ // });
+
+ return saveData;
+ }
+
+ load(data) {
+ const debugLogger = window.debugLogger;
+ const oldState = {
+ mainQuestsCount: this.mainQuests.length,
+ dailyQuestsCount: this.dailyQuests.length,
+ activeQuestsCount: this.activeQuests.length,
+ completedQuestsCount: this.completedQuests.length,
+ stats: this.stats
+ };
+
+ if (debugLogger) debugLogger.startStep('QuestSystem.load', {
+ oldState: oldState,
+ loadData: data
+ });
+
+ try {
+ if (data.mainQuests) {
+ this.mainQuests = data.mainQuests;
+
+ if (debugLogger) debugLogger.logStep('Loaded main quests', {
+ mainQuestsLoaded: this.mainQuests.length,
+ mainQuests: this.mainQuests.map(q => ({
+ id: q.id,
+ name: q.name,
+ status: q.status
+ }))
+ });
+ }
+
+ if (data.dailyQuests) {
+ this.dailyQuests = data.dailyQuests;
+
+ if (debugLogger) debugLogger.logStep('Loaded daily quests', {
+ dailyQuestsLoaded: this.dailyQuests.length,
+ dailyQuests: this.dailyQuests.map(q => ({
+ id: q.id,
+ name: q.name,
+ status: q.status
+ }))
+ });
+ }
+
+ if (data.activeQuests) {
+ this.activeQuests = data.activeQuests;
+
+ if (debugLogger) debugLogger.logStep('Loaded active quests', {
+ activeQuestsLoaded: this.activeQuests.length,
+ activeQuests: this.activeQuests.map(q => ({
+ id: q.id,
+ name: q.name,
+ type: q.type,
+ status: q.status
+ }))
+ });
+ }
+
+ if (data.completedQuests) {
+ this.completedQuests = data.completedQuests;
+
+ if (debugLogger) debugLogger.logStep('Loaded completed quests', {
+ completedQuestsLoaded: this.completedQuests.length,
+ completedQuests: this.completedQuests.map(q => ({
+ id: q.id,
+ name: q.name,
+ type: q.type,
+ completedAt: q.completedAt
+ }))
+ });
+ }
+
+ if (data.stats) {
+ const oldStats = { ...this.stats };
+ this.stats = { ...this.stats, ...data.stats };
+
+ if (debugLogger) debugLogger.logStep('Loaded quest statistics', {
+ oldStats: oldStats,
+ newStats: this.stats,
+ statsMerged: true
+ });
+ }
+
+ // Check for daily reset after loading
+ if (debugLogger) debugLogger.logStep('Checking daily reset after load');
+ this.checkDailyReset();
+
+ if (debugLogger) debugLogger.endStep('QuestSystem.load', {
+ success: true,
+ oldState: oldState,
+ newState: {
+ mainQuestsCount: this.mainQuests.length,
+ dailyQuestsCount: this.dailyQuests.length,
+ activeQuestsCount: this.activeQuests.length,
+ completedQuestsCount: this.completedQuests.length,
+ stats: this.stats
+ },
+ dailyResetChecked: true
+ });
+ } catch (error) {
+ if (debugLogger) debugLogger.errorEvent('QuestSystem.load', error, {
+ oldState: oldState,
+ loadData: data,
+ error: error.message
+ });
+ throw error;
+ }
+ }
+}
diff --git a/Client-Server/js/systems/ShipSystem.js b/Client-Server/js/systems/ShipSystem.js
new file mode 100644
index 0000000..e3dcb7e
--- /dev/null
+++ b/Client-Server/js/systems/ShipSystem.js
@@ -0,0 +1,223 @@
+class ShipSystem {
+ constructor(game) {
+ this.game = game;
+ this.ships = [];
+ this.currentShip = null;
+ this.initializeShips();
+ }
+
+ initializeShips() {
+ // Initialize with player's current ship instead of static data
+ this.ships = [];
+
+ // Wait for game systems to be ready, then sync with player ship
+ setTimeout(() => {
+ this.syncWithPlayerShip();
+ }, 100);
+ }
+
+ syncWithPlayerShip() {
+ const player = this.game.systems.player;
+ if (!player || !player.ship) {
+ return;
+ }
+
+ if (player && player.ship) {
+ // Create ship object from player's current ship
+ const playerShip = {
+ id: 'current_ship',
+ name: player.ship.name || 'Starter Cruiser',
+ class: player.ship.class || 'Cruiser',
+ level: player.ship.level || 1,
+ health: player.ship.health || player.ship.maxHealth || 100,
+ maxHealth: player.ship.maxHealth || 100,
+ attack: player.ship.attack || player.attributes.attack || 10,
+ defense: player.ship.defence || player.attributes.defense || 5,
+ speed: player.ship.speed || player.attributes.speed || 10,
+ image: player.ship.texture || 'assets/textures/ships/starter_cruiser.png',
+ status: 'active',
+ experience: 0,
+ requiredExp: 100,
+ rarity: 'Common'
+ };
+
+ this.ships = [playerShip];
+ this.currentShip = playerShip;
+
+ // Update the display immediately
+ this.updateCurrentShipDisplay();
+ }
+ }
+
+ renderShips() {
+ const shipGrid = document.getElementById('shipGrid');
+ if (!shipGrid) return;
+
+ shipGrid.innerHTML = '';
+
+ this.ships.forEach(ship => {
+ const shipCard = this.createShipCard(ship);
+ shipGrid.appendChild(shipCard);
+ });
+ }
+
+ createShipCard(ship) {
+ const card = document.createElement('div');
+ card.className = `ship-card ${ship.status === 'active' ? 'active' : ''}`;
+ card.dataset.shipId = ship.id;
+
+ card.innerHTML = `
+
+
+
+ ${ship.status === 'active' ? 'ACTIVE' : 'SWITCH'}
+
+
+ `;
+
+ return card;
+ }
+
+ updateCurrentShipDisplay() {
+ // Use player's ship data instead of this.currentShip
+ const player = this.game.systems.player;
+ if (!player || !player.ship) {
+ return;
+ }
+
+ const elements = {
+ currentShipImage: document.getElementById('currentShipImage'),
+ currentShipName: document.getElementById('currentShipName'),
+ currentShipClass: document.getElementById('currentShipClass'),
+ currentShipLevel: document.getElementById('currentShipLevel'),
+ currentShipHealth: document.getElementById('currentShipHealth'),
+ currentShipAttack: document.getElementById('currentShipAttack'),
+ currentShipDefense: document.getElementById('currentShipDefense'),
+ currentShipSpeed: document.getElementById('currentShipSpeed')
+ };
+
+ // Use player's ship data
+ const ship = player.ship;
+
+ if (elements.currentShipImage) {
+ // Use the ship's texture if available, otherwise fallback
+ const imagePath = ship.texture || `assets/textures/ships/starter_cruiser.png`;
+ elements.currentShipImage.src = imagePath;
+ elements.currentShipImage.alt = ship.name;
+ }
+ if (elements.currentShipName) elements.currentShipName.textContent = ship.name;
+ if (elements.currentShipClass) elements.currentShipClass.textContent = ship.class || 'Unknown';
+ if (elements.currentShipLevel) elements.currentShipLevel.textContent = ship.level || 1;
+ if (elements.currentShipHealth) elements.currentShipHealth.textContent = `${ship.health}/${ship.maxHealth}`;
+ if (elements.currentShipAttack) elements.currentShipAttack.textContent = ship.attack || 0;
+ if (elements.currentShipDefense) elements.currentShipDefense.textContent = ship.defence || ship.defense || 0;
+ if (elements.currentShipSpeed) elements.currentShipSpeed.textContent = ship.speed || 0;
+ }
+
+ switchShip(shipId) {
+ const ship = this.ships.find(s => s.id === shipId);
+ if (!ship || ship.status === 'active') return;
+
+ // Deactivate current ship
+ if (this.currentShip) {
+ this.currentShip.status = 'inactive';
+ }
+
+ // Activate new ship
+ ship.status = 'active';
+ this.currentShip = ship;
+
+ // Update displays
+ this.renderShips();
+ this.updateCurrentShipDisplay();
+
+ // Show notification
+ this.game.showNotification(`Switched to ${ship.name}!`, 'success', 3000);
+ }
+
+ upgradeShip(shipId) {
+ const ship = this.ships.find(s => s.id === shipId);
+ if (!ship) return;
+
+ const upgradeCost = ship.level * 1000;
+
+ if (this.game.systems.economy.getCredits() < upgradeCost) {
+ this.game.showNotification(`Not enough credits! Need ${upgradeCost} credits.`, 'error', 3000);
+ return;
+ }
+
+ // Upgrade ship
+ this.game.systems.economy.removeCredits(upgradeCost);
+ ship.level++;
+ ship.maxHealth += 10;
+ ship.health = ship.maxHealth; // Full heal on upgrade
+ ship.attack += 2;
+ ship.defense += 1;
+ ship.speed += 1;
+ ship.requiredExp = ship.level * 100;
+
+ // Update displays
+ this.renderShips();
+ this.updateCurrentShipDisplay();
+
+ this.game.showNotification(`${ship.name} upgraded to level ${ship.level}!`, 'success', 3000);
+ }
+
+ repairShip(shipId) {
+ const ship = this.ships.find(s => s.id === shipId);
+ if (!ship || ship.health >= ship.maxHealth) return;
+
+ const repairCost = Math.floor((ship.maxHealth - ship.health) * 0.5);
+
+ if (this.game.systems.economy.getCredits() < repairCost) {
+ this.game.showNotification(`Not enough credits! Need ${repairCost} credits.`, 'error', 3000);
+ return;
+ }
+
+ // Repair ship
+ this.game.systems.economy.removeCredits(repairCost);
+ ship.health = ship.maxHealth;
+
+ // Update displays
+ this.renderShips();
+ this.updateCurrentShipDisplay();
+
+ this.game.showNotification(`${ship.name} fully repaired!`, 'success', 3000);
+ }
+
+ addExperience(shipId, amount) {
+ const ship = this.ships.find(s => s.id === shipId);
+ if (!ship) return;
+
+ ship.experience += amount;
+
+ // Check for level up
+ while (ship.experience >= ship.requiredExp) {
+ ship.experience -= ship.requiredExp;
+ this.upgradeShip(shipId);
+ }
+
+ this.renderShips();
+ if (this.currentShip && this.currentShip.id === shipId) {
+ this.updateCurrentShipDisplay();
+ }
+ }
+
+ getShip(shipId) {
+ return this.ships.find(s => s.id === shipId);
+ }
+
+ getCurrentShip() {
+ return this.currentShip;
+ }
+
+ getAllShips() {
+ return this.ships;
+ }
+}
diff --git a/Client-Server/js/systems/SkillSystem.js b/Client-Server/js/systems/SkillSystem.js
new file mode 100644
index 0000000..1148052
--- /dev/null
+++ b/Client-Server/js/systems/SkillSystem.js
@@ -0,0 +1,596 @@
+/**
+ * Galaxy Strike Online - Skill System
+ * Manages skills, progression, and specialization
+ */
+
+class SkillSystem {
+ constructor(gameEngine) {
+ this.game = gameEngine;
+
+ // Skill categories
+ this.categories = {
+ combat: 'Combat',
+ science: 'Science',
+ crafting: 'Crafting'
+ };
+
+ // Skill definitions
+ this.skills = {
+ combat: {
+ weapons_mastery: {
+ name: 'Weapons Mastery',
+ description: 'Increases weapon damage and critical chance',
+ maxLevel: 10,
+ currentLevel: 0,
+ experience: 0,
+ experienceToNext: 100,
+ effects: {
+ attack: 2,
+ criticalChance: 0.01
+ },
+ icon: 'fa-sword',
+ unlocked: true
+ },
+ shield_techniques: {
+ name: 'Shield Techniques',
+ description: 'Improves defense and energy efficiency',
+ maxLevel: 10,
+ currentLevel: 0,
+ experience: 0,
+ experienceToNext: 100,
+ effects: {
+ defense: 2,
+ maxEnergy: 5
+ },
+ icon: 'fa-shield-alt',
+ unlocked: true
+ },
+ piloting: {
+ name: 'Piloting',
+ description: 'Enhances speed and evasion',
+ maxLevel: 10,
+ currentLevel: 0,
+ experience: 0,
+ experienceToNext: 100,
+ effects: {
+ speed: 2,
+ criticalChance: 0.005
+ },
+ icon: 'fa-rocket',
+ unlocked: true
+ },
+ tactical_analysis: {
+ name: 'Tactical Analysis',
+ description: 'Reveals enemy weaknesses and improves accuracy',
+ maxLevel: 10,
+ currentLevel: 0,
+ experience: 0,
+ experienceToNext: 100,
+ effects: {
+ criticalDamage: 0.05,
+ attack: 1
+ },
+ icon: 'fa-brain',
+ unlocked: false,
+ requiredLevel: 5
+ }
+ },
+ science: {
+ engineering: {
+ name: 'Engineering',
+ description: 'Technical skills for ship components and machinery',
+ maxLevel: 10,
+ currentLevel: 0,
+ experience: 0,
+ experienceToNext: 100,
+ effects: {
+ craftingBonus: 0.08,
+ shipStats: 0.03
+ },
+ icon: 'fa-wrench',
+ unlocked: true
+ },
+ energy_manipulation: {
+ name: 'Energy Manipulation',
+ description: 'Better energy control and regeneration',
+ maxLevel: 10,
+ currentLevel: 0,
+ experience: 0,
+ experienceToNext: 100,
+ effects: {
+ maxEnergy: 10,
+ energyRegeneration: 0.1
+ },
+ icon: 'fa-bolt',
+ unlocked: true
+ },
+ alien_technology: {
+ name: 'Alien Technology',
+ description: 'Understanding and using alien artifacts',
+ maxLevel: 10,
+ currentLevel: 0,
+ experience: 0,
+ experienceToNext: 100,
+ effects: {
+ findRarity: 0.05,
+ itemValue: 0.1
+ },
+ icon: 'fa-atom',
+ unlocked: false,
+ requiredLevel: 3
+ },
+ quantum_physics: {
+ name: 'Quantum Physics',
+ description: 'Advanced quantum mechanics for better equipment',
+ maxLevel: 10,
+ currentLevel: 0,
+ experience: 0,
+ experienceToNext: 100,
+ effects: {
+ criticalDamage: 0.1,
+ attack: 3
+ },
+ icon: 'fa-microscope',
+ unlocked: false,
+ requiredLevel: 8
+ },
+ bio_engineering: {
+ name: 'Bio-Engineering',
+ description: 'Biological enhancements and healing',
+ maxLevel: 10,
+ currentLevel: 0,
+ experience: 0,
+ experienceToNext: 100,
+ effects: {
+ maxHealth: 15,
+ healthRegeneration: 0.05
+ },
+ icon: 'fa-dna',
+ unlocked: false,
+ requiredLevel: 6
+ }
+ },
+ crafting: {
+ crafting: {
+ name: 'General Crafting',
+ description: 'Basic crafting skills for all items',
+ maxLevel: 10,
+ currentLevel: 0,
+ experience: 0,
+ experienceToNext: 100,
+ effects: {
+ craftingBonus: 0.05
+ },
+ icon: 'fa-hammer',
+ unlocked: true
+ },
+ weapon_crafting: {
+ name: 'Weapon Crafting',
+ description: 'Create and upgrade weapons',
+ maxLevel: 10,
+ currentLevel: 0,
+ experience: 0,
+ experienceToNext: 100,
+ effects: {
+ craftingBonus: 0.1,
+ weaponStats: 0.05
+ },
+ icon: 'fa-hammer',
+ unlocked: true
+ },
+ armor_forging: {
+ name: 'Armor Forging',
+ description: 'Forge protective armor and shields',
+ maxLevel: 10,
+ currentLevel: 0,
+ experience: 0,
+ experienceToNext: 100,
+ effects: {
+ craftingBonus: 0.1,
+ armorStats: 0.05
+ },
+ icon: 'fa-anvil',
+ unlocked: true
+ },
+ resource_extraction: {
+ name: 'Resource Extraction',
+ description: 'Better resource gathering and efficiency',
+ maxLevel: 10,
+ currentLevel: 0,
+ experience: 0,
+ experienceToNext: 100,
+ effects: {
+ resourceBonus: 0.15,
+ findResources: 0.1
+ },
+ icon: 'fa-gem',
+ unlocked: false,
+ requiredLevel: 4
+ },
+ engineering: {
+ name: 'Engineering',
+ description: 'Advanced ship modifications and systems',
+ maxLevel: 10,
+ currentLevel: 0,
+ experience: 0,
+ experienceToNext: 100,
+ effects: {
+ shipUpgrades: 0.2,
+ systemEfficiency: 0.1
+ },
+ icon: 'fa-cogs',
+ unlocked: false,
+ requiredLevel: 7
+ }
+ }
+ };
+
+ // Skill experience rates
+ this.experienceRates = {
+ combat: 1.0,
+ science: 0.8,
+ crafting: 0.6
+ };
+
+ // Active buffs from skills
+ this.activeBuffs = {};
+ }
+
+ async initialize() {
+}
+
+ // Skill management
+ addSkillExperience(category, skillId, amount) {
+ const skill = this.skills[category]?.[skillId];
+ if (!skill || skill.currentLevel >= skill.maxLevel) {
+ return false;
+ }
+
+ skill.experience += amount;
+
+ // Check for level up
+ while (skill.experience >= skill.experienceToNext && skill.currentLevel < skill.maxLevel) {
+ this.levelUpSkill(category, skillId);
+ }
+
+ this.applySkillEffects();
+return true;
+ }
+
+ levelUpSkill(category, skillId) {
+ const skill = this.skills[category][skillId];
+
+ // Handle excess experience
+ const excessExperience = skill.experience - skill.experienceToNext;
+
+ skill.currentLevel++;
+ skill.experienceToNext = Math.floor(skill.experienceToNext * 1.5);
+
+ // Set experience to excess (minimum 0)
+ skill.experience = Math.max(0, excessExperience);
+
+ // Apply skill effects
+ this.applySkillEffects();
+
+ this.game.showNotification(`${skill.name} leveled up to ${skill.currentLevel}!`, 'success', 4000);
+ this.game.showNotification('Skill effects applied!', 'info', 3000);
+ }
+
+ upgradeSkill(category, skillId) {
+ const skill = this.skills[category]?.[skillId];
+ const player = this.game.systems.player;
+
+ if (!skill) {
+ this.game.showNotification('Skill not found', 'error', 3000);
+ return false;
+ }
+
+ if (!skill.unlocked) {
+ this.game.showNotification('Skill is locked', 'error', 3000);
+ return false;
+ }
+
+ if (skill.currentLevel >= skill.maxLevel) {
+ this.game.showNotification('Skill is at maximum level', 'warning', 3000);
+ return false;
+ }
+
+ if (player.stats.skillPoints < 1) {
+ this.game.showNotification('Not enough skill points', 'error', 3000);
+ return false;
+ }
+
+ // Use skill point and level up
+ player.stats.skillPoints--;
+ this.levelUpSkill(category, skillId);
+
+ // Update UI to refresh skill points display only if in multiplayer mode or game is actively running
+ const shouldUpdateUI = window.smartSaveManager?.isMultiplayer || this.game?.isRunning;
+
+ if (shouldUpdateUI) {
+ this.updateUI();
+ }
+
+ return true;
+ }
+
+ unlockSkill(category, skillId) {
+ const skill = this.skills[category]?.[skillId];
+ const player = this.game.systems.player;
+
+ if (!skill) {
+ this.game.showNotification('Skill not found', 'error', 3000);
+ return false;
+ }
+
+ if (skill.unlocked) {
+ this.game.showNotification('Skill is already unlocked', 'warning', 3000);
+ return false;
+ }
+
+ if (skill.requiredLevel && player.stats.level < skill.requiredLevel) {
+ this.game.showNotification(`Requires level ${skill.requiredLevel}`, 'error', 3000);
+ return false;
+ }
+
+ if (player.stats.skillPoints < 2) {
+ this.game.showNotification('Requires 2 skill points to unlock', 'error', 3000);
+ return false;
+ }
+
+ // Unlock skill
+ player.stats.skillPoints -= 2;
+ skill.unlocked = true;
+ skill.currentLevel = 1;
+
+ this.applySkillEffects();
+
+ // Update UI to refresh skill points display only if in multiplayer mode or game is actively running
+ const shouldUpdateUI = window.smartSaveManager?.isMultiplayer || this.game?.isRunning;
+
+ if (shouldUpdateUI) {
+ this.updateUI();
+ }
+
+ this.game.showNotification(`${skill.name} unlocked!`, 'success', 4000);
+ return true;
+ }
+
+ applySkillEffects() {
+ const player = this.game.systems.player;
+
+ // Reset to base stats first
+ this.resetToBaseStats();
+
+ // Apply all skill effects
+ Object.values(this.skills).forEach(skill => {
+ if (skill.level > 0) {
+ const skillData = this.skillData[skill.id];
+ if (skillData && skillData.effects) {
+ Object.entries(skillData.effects).forEach(([effect, value]) => {
+ const totalEffect = value * skill.level;
+
+ switch (effect) {
+ case 'attack':
+ player.attributes.attack += totalEffect;
+ break;
+ case 'defense':
+ player.attributes.defense += totalEffect;
+ break;
+ case 'speed':
+ player.attributes.speed += totalEffect;
+ break;
+ case 'maxHealth':
+ player.attributes.maxHealth += totalEffect;
+ break;
+ case 'maxEnergy':
+ player.attributes.maxEnergy += totalEffect;
+ break;
+ case 'criticalChance':
+ player.attributes.criticalChance += totalEffect;
+ break;
+ case 'criticalDamage':
+ player.attributes.criticalDamage += totalEffect;
+ break;
+ case 'energyRegeneration':
+ case 'healthRegeneration':
+ case 'craftingBonus':
+ case 'weaponStats':
+ case 'armorStats':
+ case 'resourceBonus':
+ case 'shipUpgrades':
+ case 'systemEfficiency':
+ case 'findRarity':
+ case 'itemValue':
+ case 'findResources':
+ // Store these for other systems to use
+ if (!this.activeBuffs[effect]) {
+ this.activeBuffs[effect] = 0;
+ }
+ this.activeBuffs[effect] += totalEffect;
+ break;
+ }
+ });
+ }
+ }
+ });
+
+ player.updateUI();
+ }
+
+ resetToBaseStats() {
+ const player = this.game.systems.player;
+
+ // Reset to base values (would need to store base stats separately)
+ // For now, we'll use initial values
+ const baseStats = {
+ attack: 10 + (player.stats.level - 1) * 2,
+ defense: 5 + (player.stats.level - 1) * 1,
+ speed: 10,
+ maxHealth: 100 + (player.stats.level - 1) * 10,
+ maxEnergy: 100 + (player.stats.level - 1) * 5,
+ criticalChance: 0.05,
+ criticalDamage: 1.5
+ };
+
+ Object.assign(player.attributes, baseStats);
+ this.activeBuffs = {};
+ }
+
+ // Skill experience from actions
+ awardCombatExperience(amount) {
+ this.addSkillExperience('combat', 'weapons_mastery', amount);
+ this.addSkillExperience('combat', 'tactical_analysis', amount * 0.5);
+ }
+
+ awardScienceExperience(amount) {
+ this.addSkillExperience('science', 'energy_manipulation', amount);
+ this.addSkillExperience('science', 'alien_technology', amount * 0.3);
+ }
+
+ awardCraftingExperience(amount) {
+ this.addSkillExperience('crafting', 'weapon_crafting', amount);
+ this.addSkillExperience('crafting', 'armor_forging', amount * 0.5);
+ }
+
+ // Skill checks
+ getSkillLevel(category, skillId) {
+ return this.skills[category]?.[skillId]?.currentLevel || 0;
+ }
+
+ hasSkill(category, skillId, minimumLevel = 1) {
+ const skill = this.skills[category]?.[skillId];
+ return skill && skill.unlocked && skill.currentLevel >= minimumLevel;
+ }
+
+ getSkillBonus(effect) {
+ return this.activeBuffs[effect] || 0;
+ }
+
+ // UI updates
+ updateUI() {
+ this.updateSkillsGrid();
+ this.updateSkillPointsDisplay();
+ }
+
+ updateSkillsGrid() {
+ const skillsGridElement = document.getElementById('skillsGrid');
+ if (!skillsGridElement) return;
+
+ const activeCategory = document.querySelector('.skill-cat-btn.active')?.dataset.category || 'combat';
+ const skills = this.skills[activeCategory] || {};
+
+ skillsGridElement.innerHTML = '';
+
+ Object.entries(skills).forEach(([skillId, skill]) => {
+ const skillElement = document.createElement('div');
+ skillElement.className = `skill-item ${!skill.unlocked ? 'locked' : ''}`;
+
+ const progressPercent = skill.currentLevel > 0 ?
+ (skill.experience / skill.experienceToNext) * 100 : 0;
+
+ // Use texture manager for icon fallback
+ const iconClass = this.game.systems.textureManager ?
+ this.game.systems.textureManager.getIcon(skill.icon) :
+ (skill.icon || 'fa-question');
+
+ skillElement.innerHTML = `
+
+ ${skill.description}
+ ${skill.currentLevel > 0 && skill.currentLevel < skill.maxLevel ? `
+
+
+
${skill.experience}/${skill.experienceToNext} XP
+
+ ` : skill.currentLevel >= skill.maxLevel ? `
+
+ MAX LEVEL
+
+ ` : ''}
+
+ ${!skill.unlocked ? `
+
+ Unlock (2 Points)
+
+ ` : skill.currentLevel < skill.maxLevel ? `
+
+ Upgrade (1 Point)
+
+ ` : `
+ MAX LEVEL
+ `}
+
+ ${skill.requiredLevel && !skill.unlocked ? `
+ Requires Level ${skill.requiredLevel}
+ ` : ''}
+ `;
+
+ skillsGridElement.appendChild(skillElement);
+ });
+ }
+
+ updateSkillPointsDisplay() {
+ const player = this.game.systems.player;
+ // Update skill points display if element exists
+ const skillPointsElements = document.querySelectorAll('.skill-points');
+ skillPointsElements.forEach(element => {
+ element.textContent = `Skill Points: ${player.stats.skillPoints}`;
+ });
+ }
+
+ // Save/Load
+ save() {
+ return {
+ skills: this.skills,
+ activeBuffs: this.activeBuffs
+ };
+ }
+
+ load(data) {
+ if (data.skills) {
+ // Deep merge to preserve structure
+ for (const [category, skills] of Object.entries(data.skills)) {
+ if (this.skills[category]) {
+ for (const [skillId, skillData] of Object.entries(skills)) {
+ if (this.skills[category][skillId]) {
+ Object.assign(this.skills[category][skillId], skillData);
+ }
+ }
+ }
+ }
+ }
+
+ if (data.activeBuffs) {
+ this.activeBuffs = data.activeBuffs;
+ }
+
+ this.applySkillEffects();
+}
+
+reset() {
+ this.skillPoints = 0;
+ this.unlockedSkills = [];
+ this.activeBuffs = [];
+ // Skills are already defined in constructor, just reset levels
+ Object.values(this.skills).forEach(category => {
+ Object.values(category).forEach(skill => {
+ skill.currentLevel = 0;
+ skill.experience = 0;
+ });
+ });
+}
+
+clear() {
+ this.reset();
+}
+}
diff --git a/Client-Server/js/ui/LiveMainMenu.js b/Client-Server/js/ui/LiveMainMenu.js
new file mode 100644
index 0000000..b18d573
--- /dev/null
+++ b/Client-Server/js/ui/LiveMainMenu.js
@@ -0,0 +1,1202 @@
+/**
+ * Live Main Menu System
+ * Handles server authentication, server browser, and multiplayer game initialization
+ */
+
+console.log('[LIVE MAIN MENU] LiveMainMenu.js script loaded');
+
+class LiveMainMenu {
+ constructor() {
+ console.log('[LIVE MAIN MENU] Constructor called');
+
+ // Check if DOM is ready
+ if (document.readyState === 'loading') {
+ console.warn('[LIVE MAIN MENU] DOM not ready yet, elements may not be found');
+ } else {
+ console.log('[LIVE MAIN MENU] DOM is ready');
+ }
+
+ this.mainMenu = document.getElementById('mainMenu');
+ console.log('[LIVE MAIN MENU] Main menu element found:', !!this.mainMenu);
+
+ this.selectedServer = null;
+ this.authToken = null;
+ this.currentUser = null;
+ this.servers = []; // Renamed from serverList to avoid conflict with DOM element
+ this.apiBaseUrl = 'https://api.korvarix.com/api'; // API Server URL
+ this.gameServerUrl = 'https://dev.gameserver.galaxystrike.online'; // Game Server URL for Socket.IO (local dev server)
+ this.localGameServerUrl = 'http://localhost:3002'; // Local Game Server URL
+ this.isLocalMode = false; // Track if we're in local mode
+
+ console.log('[LIVE MAIN MENU] Initializing elements');
+ this.initializeElements();
+
+ console.log('[LIVE MAIN MENU] Initializing event listeners');
+ this.bindEvents();
+
+ // Check for existing auth token
+ this.checkExistingAuth();
+
+ // DISABLE local server check - always use remote multiplayer server
+ // this.checkForLocalServer();
+ console.log('[LIVE MAIN MENU] Local server check disabled - using remote multiplayer server only');
+ console.log('[LIVE MAIN MENU] Using remote API:', this.apiBaseUrl);
+ console.log('[LIVE MAIN MENU] Using remote game server:', this.gameServerUrl);
+
+ // Initialize SmartSaveManager to singleplayer mode by default
+ if (window.smartSaveManager) {
+ window.smartSaveManager.setMultiplayerMode(false);
+ }
+
+ console.log('[LIVE MAIN MENU] Constructor completed');
+ }
+
+ initializeElements() {
+ // Menu sections
+ this.loginSection = document.getElementById('loginSection');
+ this.serverSection = document.getElementById('serverSection');
+ this.serverConfirmSection = document.getElementById('serverConfirmSection');
+ this.optionsSection = document.getElementById('optionsSection');
+
+ // Login form elements
+ this.emailInput = document.getElementById('emailInput');
+ this.passwordInput = document.getElementById('passwordInput');
+ this.loginBtn = document.getElementById('loginBtn');
+ this.registerBtn = document.getElementById('registerBtn');
+ this.loginNotice = document.getElementById('loginNotice');
+
+ // Server browser elements
+ this.createServerBtn = document.getElementById('createServerBtn');
+ this.refreshServersBtn = document.getElementById('refreshServersBtn');
+ this.regionFilter = document.getElementById('regionFilter');
+ this.typeFilter = document.getElementById('typeFilter');
+ this.serverList = document.getElementById('serverList');
+ this.serverLoading = document.getElementById('serverLoading');
+ this.serverEmpty = document.getElementById('serverEmpty');
+
+ // Server confirmation elements
+ this.joinServerBtn = document.getElementById('joinServerBtn');
+ this.serverInfoBtn = document.getElementById('serverInfoBtn');
+ this.leaveServerBtn = document.getElementById('leaveServerBtn');
+
+ // Server detail elements
+ this.selectedServerName = document.getElementById('selectedServerName');
+ this.selectedServerType = document.getElementById('selectedServerType');
+ this.selectedServerRegion = document.getElementById('selectedServerRegion');
+ this.selectedServerPlayers = document.getElementById('selectedServerPlayers');
+ this.selectedServerOwner = document.getElementById('selectedServerOwner');
+
+ // Navigation buttons
+ this.backToLoginBtn = document.getElementById('backToLoginBtn');
+ this.backToServersBtn = document.getElementById('backToServersBtn');
+
+ console.log('[LIVE MAIN MENU] All elements initialized');
+ }
+
+ bindEvents() {
+ console.log('[LIVE MAIN MENU] Binding events...');
+
+ // Login events
+ if (this.loginBtn) {
+ this.loginBtn.addEventListener('click', () => this.handleLogin());
+ console.log('[LIVE MAIN MENU] Login button event bound');
+ } else {
+ console.warn('[LIVE MAIN MENU] Login button not found');
+ }
+
+ if (this.registerBtn) {
+ this.registerBtn.addEventListener('click', () => this.handleRegister());
+ console.log('[LIVE MAIN MENU] Register button event bound');
+ } else {
+ console.warn('[LIVE MAIN MENU] Register button not found');
+ }
+
+ // Enter key login
+ if (this.passwordInput) {
+ this.passwordInput.addEventListener('keypress', (e) => {
+ if (e.key === 'Enter') this.handleLogin();
+ });
+ console.log('[LIVE MAIN MENU] Password input event bound');
+ } else {
+ console.warn('[LIVE MAIN MENU] Password input not found');
+ }
+
+ // Server browser events
+ if (this.createServerBtn) {
+ this.createServerBtn.addEventListener('click', () => this.startLocalServer());
+ console.log('[LIVE MAIN MENU] Create server button event bound (now starts local server)');
+ } else {
+ console.warn('[LIVE MAIN MENU] Create server button not found');
+ }
+
+ if (this.refreshServersBtn) {
+ this.refreshServersBtn.addEventListener('click', () => this.refreshServerListWithoutAnimation());
+ console.log('[LIVE MAIN MENU] Refresh servers button event bound');
+ } else {
+ console.warn('[LIVE MAIN MENU] Refresh servers button not found');
+ }
+
+ if (this.regionFilter) {
+ this.regionFilter.addEventListener('change', () => this.filterServers());
+ console.log('[LIVE MAIN MENU] Region filter event bound');
+ } else {
+ console.warn('[LIVE MAIN MENU] Region filter not found');
+ }
+
+ if (this.typeFilter) {
+ this.typeFilter.addEventListener('change', () => this.filterServers());
+ console.log('[LIVE MAIN MENU] Type filter event bound');
+ } else {
+ console.warn('[LIVE MAIN MENU] Type filter not found');
+ }
+
+ // Server confirmation events
+ if (this.joinServerBtn) {
+ this.joinServerBtn.addEventListener('click', () => this.joinServer());
+ console.log('[LIVE MAIN MENU] Join server button event bound');
+ } else {
+ console.warn('[LIVE MAIN MENU] Join server not found');
+ }
+
+ // Navigation events
+ if (this.backToLoginBtn) {
+ this.backToLoginBtn.addEventListener('click', () => this.showLoginSection());
+ console.log('[LIVE MAIN MENU] Back to login button event bound');
+ } else {
+ console.warn('[LIVE MAIN MENU] Back to login button not found');
+ }
+
+ if (this.backToServersBtn) {
+ this.backToServersBtn.addEventListener('click', () => this.showServerSection());
+ console.log('[LIVE MAIN MENU] Back to servers button event bound');
+ } else {
+ console.warn('[LIVE MAIN MENU] Back to servers button not found');
+ }
+
+ console.log('[LIVE MAIN MENU] All events bound (with warnings for missing elements)');
+ }
+
+ checkExistingAuth() {
+ const token = localStorage.getItem('authToken');
+ const user = localStorage.getItem('currentUser');
+
+ if (token && user) {
+ this.authToken = token;
+ this.currentUser = JSON.parse(user);
+ console.log('[LIVE MAIN MENU] Found existing auth token, validating...');
+ this.validateToken();
+ } else {
+ console.log('[LIVE MAIN MENU] No existing auth found');
+ this.showLoginSection();
+ }
+ }
+
+ async validateToken() {
+ try {
+ const response = await fetch(`${this.apiBaseUrl}/auth/verify`, {
+ method: 'GET',
+ headers: {
+ 'Authorization': `Bearer ${this.authToken}`,
+ 'Content-Type': 'application/json'
+ }
+ });
+
+ if (response.ok) {
+ const data = await response.json();
+ this.currentUser = data.user;
+ localStorage.setItem('currentUser', JSON.stringify(this.currentUser));
+ console.log('[LIVE MAIN MENU] Token validated successfully');
+ this.showServerSection();
+ } else {
+ console.log('[LIVE MAIN MENU] Token invalid, clearing auth');
+ this.clearAuth();
+ this.showLoginSection();
+ }
+ } catch (error) {
+ console.error('[LIVE MAIN MENU] Token validation failed:', error);
+ this.clearAuth();
+ this.showLoginSection();
+ }
+ }
+
+ clearAuth() {
+ this.authToken = null;
+ this.currentUser = null;
+ localStorage.removeItem('authToken');
+ localStorage.removeItem('currentUser');
+ }
+
+ showSection(sectionName, animate = true) {
+ // Don't re-animate if we're showing the same section
+ if (this.currentSection === sectionName) {
+ animate = false;
+ }
+
+ // Hide all sections
+ this.loginSection?.classList.add('hidden');
+ this.serverSection?.classList.add('hidden');
+ this.serverConfirmSection?.classList.add('hidden');
+ this.optionsSection?.classList.add('hidden');
+
+ // Show selected section
+ const section = document.getElementById(`${sectionName}Section`);
+ if (section) {
+ section.classList.remove('hidden');
+
+ // Control animation
+ if (animate) {
+ section.style.animation = 'fadeInUp 0.5s ease-out';
+ } else {
+ section.style.animation = 'none';
+ }
+
+ this.currentSection = sectionName;
+ }
+ }
+
+ showLoginSection() {
+ this.showSection('login');
+ this.clearLoginForm();
+ }
+
+ showServerSection(refreshServers = true, animate = true) {
+ this.showSection('server', animate);
+ if (refreshServers) {
+ this.refreshServerList();
+ }
+ }
+
+ showServerConfirmSection() {
+ this.showSection('serverConfirm');
+ this.updateServerConfirmSection();
+ }
+
+ clearLoginForm() {
+ if (this.emailInput) this.emailInput.value = '';
+ if (this.passwordInput) this.passwordInput.value = '';
+ this.clearLoginNotice();
+ }
+
+ clearLoginNotice() {
+ if (this.loginNotice) {
+ this.loginNotice.innerHTML = `
+ Connect to the live server to play
+
+ Note: You need an internet connection to play Galaxy Strike Online.
+
+ `;
+ }
+ }
+
+ showLoginNotice(message, type = 'info') {
+ if (this.loginNotice) {
+ const iconClass = type === 'error' ? 'fa-exclamation-triangle' :
+ type === 'success' ? 'fa-check-circle' : 'fa-info-circle';
+ this.loginNotice.innerHTML = ` ${message}
`;
+ this.loginNotice.className = `login-notice ${type}`;
+ this.loginNotice.style.display = 'block';
+ }
+ }
+
+ hideLoginNotice() {
+ if (this.loginNotice) {
+ this.loginNotice.style.display = 'none';
+ }
+ }
+
+ async handleLogin() {
+ const email = this.emailInput?.value?.trim();
+ const password = this.passwordInput?.value?.trim();
+
+ if (!email || !password) {
+ this.showLoginNotice('Please enter email and password', 'error');
+ return;
+ }
+
+ // Disable login button
+ if (this.loginBtn) {
+ this.loginBtn.disabled = true;
+ this.loginBtn.innerHTML = ' Logging in...';
+ }
+
+ try {
+ const response = await fetch(`${this.apiBaseUrl}/auth/login`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({ email, password })
+ });
+
+ // Check if response is HTML (error page) instead of JSON
+ const contentType = response.headers.get('content-type');
+ if (!contentType || !contentType.includes('application/json')) {
+ throw new Error('API server is not available. Please check your connection.');
+ }
+
+ const data = await response.json();
+
+ if (response.ok) {
+ this.authToken = data.token;
+ this.currentUser = data.user;
+
+ // Save to localStorage
+ localStorage.setItem('authToken', this.authToken);
+ localStorage.setItem('currentUser', JSON.stringify(this.currentUser));
+
+ this.showLoginNotice('Login successful!', 'success');
+ console.log('[LIVE MAIN MENU] Login successful');
+
+ // Show server browser after a short delay
+ setTimeout(() => this.showServerSection(), 1000);
+ } else {
+ this.showLoginNotice(data.error || 'Login failed', 'error');
+ }
+ } catch (error) {
+ console.error('[LIVE MAIN MENU] Login error:', error);
+ this.showLoginNotice('Connection error. Please try again.', 'error');
+ } finally {
+ // Re-enable login button
+ if (this.loginBtn) {
+ this.loginBtn.disabled = false;
+ this.loginBtn.innerHTML = ' Login';
+ }
+ }
+ }
+
+ async handleRegister() {
+ const email = this.emailInput?.value?.trim();
+ const password = this.passwordInput?.value?.trim();
+
+ if (!email || !password) {
+ this.showLoginNotice('Please enter email and password', 'error');
+ return;
+ }
+
+ if (password.length < 6) {
+ this.showLoginNotice('Password must be at least 6 characters', 'error');
+ return;
+ }
+
+ // Disable register button
+ if (this.registerBtn) {
+ this.registerBtn.disabled = true;
+ this.registerBtn.innerHTML = ' Registering...';
+ }
+
+ try {
+ // Generate a username from email
+ const username = email.split('@')[0];
+
+ const response = await fetch(`${this.apiBaseUrl}/auth/register`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({ username, email, password })
+ });
+
+ // Check if response is HTML (error page) instead of JSON
+ const contentType = response.headers.get('content-type');
+ if (!contentType || !contentType.includes('application/json')) {
+ throw new Error('API server is not available. Please check your connection.');
+ }
+
+ const data = await response.json();
+
+ if (response.ok) {
+ this.authToken = data.token;
+ this.currentUser = data.user;
+
+ // Save to localStorage
+ localStorage.setItem('authToken', this.authToken);
+ localStorage.setItem('currentUser', JSON.stringify(this.currentUser));
+
+ this.showLoginNotice('Registration successful!', 'success');
+ console.log('[LIVE MAIN MENU] Registration successful');
+
+ // Show server browser after a short delay
+ setTimeout(() => this.showServerSection(), 1000);
+ } else {
+ this.showLoginNotice(data.error || 'Registration failed', 'error');
+ }
+ } catch (error) {
+ console.error('[LIVE MAIN MENU] Registration error:', error);
+ this.showLoginNotice('Connection error. Please try again.', 'error');
+ } finally {
+ // Re-enable register button
+ if (this.registerBtn) {
+ this.registerBtn.disabled = false;
+ this.registerBtn.innerHTML = ' Register';
+ }
+ }
+ }
+
+ async refreshServerListWithoutAnimation() {
+ // Ensure we're in server section without animation
+ if (this.currentSection !== 'server') {
+ this.showServerSection(true, false); // Refresh but don't animate
+ } else {
+ // Already in server section, just refresh
+ await this.refreshServerList();
+ }
+ }
+
+ async refreshServerList() {
+ // Build server list with local server, dev server, and API servers
+ const servers = [];
+
+ if (!this.authToken) {
+ console.error('[LIVE MAIN MENU] No auth token for server list');
+ return;
+ }
+
+ // Show loading state
+ if (this.serverLoading) this.serverLoading.classList.remove('hidden');
+ if (this.serverEmpty) this.serverEmpty.classList.add('hidden');
+
+ try {
+ let response;
+
+ // Add local server if available
+ if (this.isLocalMode && window.localServerManager && window.localServerManager.localServer && window.localServerManager.localServer.mockRequest) {
+ console.log('[LIVE MAIN MENU] Using SimpleLocalServer mock API for local server');
+ const localResponse = await window.localServerManager.localServer.mockRequest('GET', '/api/servers');
+ if (localResponse.success && localResponse.servers) {
+ servers.push(...localResponse.servers);
+ }
+ }
+
+ // Skip local dev server check to avoid blocking
+ console.log('[LIVE MAIN MENU] Skipping local dev server check to avoid blocking');
+
+ // Add API servers
+ console.log('[LIVE MAIN MENU] Fetching server list from:', `${this.apiBaseUrl}/servers`);
+ const apiResponse = await fetch(`${this.apiBaseUrl}/servers`, {
+ method: 'GET',
+ headers: {
+ 'Authorization': `Bearer ${this.authToken}`,
+ 'Content-Type': 'application/json'
+ }
+ });
+ if (apiResponse.ok) {
+ const apiData = await apiResponse.json();
+ if (apiData.success && apiData.servers) {
+ servers.push(...apiData.servers);
+ console.log(`[LIVE MAIN MENU] Server list loaded: ${servers.length} servers found`);
+
+ // Debug: Log server list data
+ console.log('[LIVE MAIN MENU] Server list loaded:', servers);
+ if (servers.length > 0) {
+ console.log('[LIVE MAIN MENU] First server details:', servers[0]);
+ }
+ }
+ }
+ } catch (error) {
+ console.error('[LIVE MAIN MENU] Error fetching server list:', error);
+ this.showLoginNotice('Connection error. Please try again.', 'error');
+ } finally {
+ // Hide loading state
+ if (this.serverLoading) this.serverLoading.classList.add('hidden');
+ }
+
+ // Store servers
+ this.servers = servers;
+
+ // Render server list
+ this.renderServerList();
+ }
+
+ renderServerList() {
+ const filteredServers = this.getFilteredServers();
+
+ // Handle empty state
+ if (filteredServers.length === 0) {
+ if (this.serverEmpty) this.serverEmpty.classList.remove('hidden');
+ // Remove existing server items smoothly
+ this.removeServerItemsSmoothly();
+ return;
+ } else {
+ if (this.serverEmpty) this.serverEmpty.classList.add('hidden');
+ }
+
+ // Get current server items
+ const currentItems = this.serverList?.querySelectorAll('.server-item') || [];
+
+ // Create new server items HTML
+ const serverListHtml = filteredServers.map(server => this.createServerItem(server)).join('');
+
+ // Create a temporary container to hold new items
+ const tempDiv = document.createElement('div');
+ tempDiv.innerHTML = serverListHtml;
+ const newItems = Array.from(tempDiv.querySelectorAll('.server-item'));
+
+ // Smooth update: fade out current items, then replace with new ones
+ this.updateServerItemsSmoothly(currentItems, newItems);
+ }
+
+ removeServerItemsSmoothly() {
+ const existingItems = this.serverList?.querySelectorAll('.server-item') || [];
+
+ if (existingItems.length === 0) return;
+
+ // Add fade-out transition
+ existingItems.forEach(item => {
+ item.style.transition = 'opacity 0.2s ease-out';
+ item.style.opacity = '0';
+ });
+
+ // Remove items after fade out
+ setTimeout(() => {
+ existingItems.forEach(item => item.remove());
+ }, 200);
+}
+ removeServerItemsSmoothly() {
+ const existingItems = this.serverList?.querySelectorAll('.server-item') || [];
+
+ if (existingItems.length === 0) return;
+
+ // Add fade-out transition
+ existingItems.forEach(item => {
+ item.style.transition = 'opacity 0.2s ease-out';
+ item.style.opacity = '0';
+ });
+
+ // Remove items after fade out
+ setTimeout(() => {
+ existingItems.forEach(item => item.remove());
+ }, 200);
+ }
+
+ updateServerItemsSmoothly(currentItems, newItems) {
+ // Fade out current items
+ if (currentItems.length > 0) {
+ currentItems.forEach(item => {
+ item.style.transition = 'opacity 0.2s ease-out';
+ item.style.opacity = '0';
+ });
+
+ // Replace with new items after fade out
+ setTimeout(() => {
+ currentItems.forEach(item => item.remove());
+ this.addNewServerItems(newItems);
+ }, 200);
+ } else {
+ // No current items, add new ones immediately
+ this.addNewServerItems(newItems);
+ }
+ }
+
+ addNewServerItems(newItems) {
+ if (!this.serverList) return;
+
+ // Add fade-in transition to new items
+ newItems.forEach(item => {
+ item.style.transition = 'opacity 0.3s ease-in';
+ item.style.opacity = '0';
+ });
+
+ // Add new items to DOM
+ newItems.forEach(item => {
+ this.serverList.appendChild(item);
+ });
+
+ // Add click handlers
+ newItems.forEach(item => {
+ item.addEventListener('click', () => {
+ const serverId = item.dataset.serverId;
+ this.selectServer(serverId);
+ });
+ });
+
+ // Fade in new items
+ setTimeout(() => {
+ newItems.forEach(item => {
+ item.style.opacity = '1';
+ });
+ }, 50);
+ }
+
+ createServerItem(server) {
+ const playerCount = `${server.currentPlayers}/${server.maxPlayers}`;
+ const createdDate = new Date(server.createdAt).toLocaleDateString();
+
+ return `
+
+
+
${server.name}
+
+
+
+ ${server.region || 'Unknown'}
+
+
+
+ ${createdDate}
+
+
+
+
+ ${server.type}
+ ${playerCount}
+
+
+
+ `;
+ }
+
+ getFilteredServers() {
+ let filtered = [...this.servers];
+
+ const regionFilter = this.regionFilter?.value;
+ const typeFilter = this.typeFilter?.value;
+
+ if (regionFilter) {
+ filtered = filtered.filter(server => server.region === regionFilter);
+ }
+
+ if (typeFilter) {
+ filtered = filtered.filter(server => server.type === typeFilter);
+ }
+
+ return filtered;
+ }
+
+ filterServers() {
+ this.renderServerList();
+ }
+
+ selectServer(serverId) {
+ this.selectedServer = this.servers.find(server => server.id === serverId);
+ if (this.selectedServer) {
+ console.log('[LIVE MAIN MENU] Server selected:', this.selectedServer);
+ this.showServerConfirmSection();
+ }
+ }
+
+ updateServerConfirmSection() {
+ if (!this.selectedServer) return;
+
+ // Update server details
+ if (this.selectedServerName) this.selectedServerName.textContent = this.selectedServer.name;
+ if (this.selectedServerType) this.selectedServerType.textContent = this.selectedServer.type;
+ if (this.selectedServerRegion) this.selectedServerRegion.textContent = this.selectedServer.region || 'Unknown';
+ if (this.selectedServerPlayers) this.selectedServerPlayers.textContent =
+ `${this.selectedServer.currentPlayers}/${this.selectedServer.maxPlayers}`;
+ if (this.selectedServerOwner) this.selectedServerOwner.textContent = this.selectedServer.ownerName || 'Unknown';
+ }
+
+ async joinServer() {
+ if (!this.selectedServer || !this.authToken) {
+ console.error('[LIVE MAIN MENU] No server selected or not authenticated');
+ return;
+ }
+
+ // Disable join button
+ if (this.joinServerBtn) {
+ this.joinServerBtn.disabled = true;
+ this.joinServerBtn.innerHTML = ' Joining...';
+ }
+
+ try {
+ let response;
+
+ // Use SimpleLocalServer mock API if in local mode
+ if (this.isLocalMode && window.localServerManager && window.localServerManager.localServer && window.localServerManager.localServer.mockRequest) {
+ console.log('[LIVE MAIN MENU] Using SimpleLocalServer mock API for join server');
+ response = await window.localServerManager.localServer.mockRequest('POST', `/servers/${this.selectedServer.id}/join`, {
+ userId: this.currentUser.id,
+ token: this.authToken
+ });
+ } else {
+ console.log('[LIVE MAIN MENU] Using real fetch for join server');
+ response = await fetch(`${this.apiBaseUrl}/servers/${this.selectedServer.id}/join`, {
+ method: 'POST',
+ headers: {
+ 'Authorization': `Bearer ${this.authToken}`,
+ 'Content-Type': 'application/json'
+ }
+ });
+ }
+
+ // Check if response is HTML (error page) instead of JSON
+ const contentType = response.headers ? response.headers.get('content-type') : 'application/json';
+ if (!contentType || !contentType.includes('application/json')) {
+ throw new Error('API server is not available. Please check your connection.');
+ }
+
+ const data = await response.json();
+
+ if (response.ok) {
+ console.log('[LIVE MAIN MENU] Joined server successfully');
+
+ // Apply existing save data if in local mode
+ if (this.isLocalMode && window.localServerManager && window.localServerManager.localServer) {
+ console.log('[LIVE MAIN MENU] Applying existing save data for local mode...');
+
+ // Store save data for later application (after GameEngine is created)
+ this.pendingSaveData = window.localServerManager.localServer.existingSaveData;
+
+ if (this.pendingSaveData) {
+ console.log('[LIVE MAIN MENU] Save data stored for later application');
+ } else {
+ console.log('[LIVE MAIN MENU] No save data to store');
+ }
+ }
+
+ this.launchMultiplayerGame(this.selectedServer, data.server);
+ } else {
+ console.error('[LIVE MAIN MENU] Failed to join server:', data.error);
+ alert(data.error || 'Failed to join server');
+ }
+ } catch (error) {
+ console.error('[LIVE MAIN MENU] Join server error:', error);
+ alert('Connection error. Please try again.');
+ } finally {
+ // Re-enable join button
+ if (this.joinServerBtn) {
+ this.joinServerBtn.disabled = false;
+ this.joinServerBtn.innerHTML = ' Join Server';
+ }
+ }
+ }
+
+ showServerInfo() {
+ if (!this.selectedServer) return;
+
+ const info = `
+Server Information:
+Name: ${this.selectedServer.name}
+Type: ${this.selectedServer.type}
+Region: ${this.selectedServer.region}
+Players: ${this.selectedServer.currentPlayers}/${this.selectedServer.maxPlayers}
+Owner: ${this.selectedServer.ownerName}
+Created: ${new Date(this.selectedServer.createdAt).toLocaleString()}
+Status: ${this.selectedServer.status}
+ `.trim();
+
+ alert(info);
+ }
+
+ leaveServer() {
+ this.selectedServer = null;
+ this.showServerSection();
+ }
+
+ showCreateServerDialog() {
+ return new Promise((resolve) => {
+ // Create modal dialog
+ const modal = document.createElement('div');
+ modal.className = 'modal-overlay';
+ modal.innerHTML = `
+
+
+
+
+ Server Name:
+
+
+
+ Server Type:
+
+ Public
+ Private
+
+
+
+ Region:
+
+ US East
+ US West
+ Europe
+ Asia
+
+
+
+ Max Players:
+
+
+
+
+
+ `;
+
+ // Add styles for modal
+ const style = document.createElement('style');
+ style.textContent = `
+ .modal-overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background: rgba(0, 0, 0, 0.8);
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ z-index: 10000;
+ }
+ .modal-dialog {
+ background: var(--bg-primary);
+ border: 1px solid var(--border-color);
+ border-radius: 8px;
+ padding: 20px;
+ min-width: 400px;
+ max-width: 500px;
+ }
+ .modal-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 20px;
+ }
+ .modal-header h3 {
+ margin: 0;
+ color: var(--text-primary);
+ }
+ .modal-close {
+ background: none;
+ border: none;
+ color: var(--text-secondary);
+ font-size: 24px;
+ cursor: pointer;
+ }
+ .form-group {
+ margin-bottom: 15px;
+ }
+ .form-group label {
+ display: block;
+ margin-bottom: 5px;
+ color: var(--text-primary);
+ }
+ .form-group input,
+ .form-group select {
+ width: 100%;
+ padding: 8px;
+ background: var(--bg-secondary);
+ border: 1px solid var(--border-color);
+ border-radius: 4px;
+ color: var(--text-primary);
+ }
+ .modal-footer {
+ display: flex;
+ justify-content: flex-end;
+ gap: 10px;
+ margin-top: 20px;
+ }
+ .btn {
+ padding: 8px 16px;
+ border: none;
+ border-radius: 4px;
+ cursor: pointer;
+ }
+ .btn-primary {
+ background: var(--primary-color);
+ color: white;
+ }
+ .btn-secondary {
+ background: var(--bg-secondary);
+ color: var(--text-primary);
+ border: 1px solid var(--border-color);
+ }
+ `;
+ document.head.appendChild(style);
+
+ // Add modal to page
+ document.body.appendChild(modal);
+
+ // Get elements
+ const closeBtn = modal.querySelector('.modal-close');
+ const cancelBtn = modal.querySelector('#cancelBtn');
+ const createBtn = modal.querySelector('#createBtn');
+ const nameInput = modal.querySelector('#serverName');
+
+ // Event handlers
+ const closeModal = () => {
+ document.body.removeChild(modal);
+ document.head.removeChild(style);
+ resolve(null);
+ };
+
+ const handleCreate = () => {
+ const name = nameInput.value.trim();
+ const type = modal.querySelector('#serverType').value;
+ const region = modal.querySelector('#serverRegion').value;
+ const maxPlayers = parseInt(modal.querySelector('#maxPlayers').value);
+
+ if (!name) {
+ nameInput.focus();
+ return;
+ }
+
+ if (maxPlayers < 1 || maxPlayers > 20) {
+ modal.querySelector('#maxPlayers').focus();
+ return;
+ }
+
+ document.body.removeChild(modal);
+ document.head.removeChild(style);
+ resolve({ name, type, region, maxPlayers });
+ };
+
+ closeBtn.addEventListener('click', closeModal);
+ cancelBtn.addEventListener('click', closeModal);
+ createBtn.addEventListener('click', handleCreate);
+
+ // Handle Enter key in name input
+ nameInput.addEventListener('keypress', (e) => {
+ if (e.key === 'Enter') {
+ handleCreate();
+ }
+ });
+
+ // Focus on name input
+ nameInput.focus();
+ });
+ }
+
+ async handleCreateServer() {
+ try {
+ // Get server details from user using custom dialog
+ const serverDetails = await this.showCreateServerDialog();
+
+ if (!serverDetails) {
+ return; // User cancelled
+ }
+
+ const { name, type, maxPlayers, region } = serverDetails;
+
+ // Show loading state
+ if (this.serverLoading) this.serverLoading.classList.remove('hidden');
+
+ const response = await fetch(`${this.apiBaseUrl}/servers/create`, {
+ method: 'POST',
+ headers: {
+ 'Authorization': `Bearer ${this.authToken}`,
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({
+ name,
+ type,
+ maxPlayers,
+ region,
+ username: this.currentUser.username
+ })
+ });
+
+ // Check if response is HTML (error page) instead of JSON
+ const contentType = response.headers.get('content-type');
+ if (!contentType || !contentType.includes('application/json')) {
+ throw new Error('API server is not available. Please check your connection.');
+ }
+
+ if (response.ok) {
+ const data = await response.json();
+ console.log('[LIVE MAIN MENU] Server created:', data.server);
+
+ // Refresh server list
+ await this.refreshServerList();
+
+ // Show success message
+ this.showLoginNotice('Server created successfully!', 'success');
+
+ // Auto-join the created server
+ this.joinServer(data.server.id);
+ } else {
+ const errorData = await response.json();
+ console.error('[LIVE MAIN MENU] Failed to create server:', errorData);
+ this.showLoginNotice(`Failed to create server: ${errorData.error}`, 'error');
+ }
+ } catch (error) {
+ console.error('[LIVE MAIN MENU] Create server error:', error);
+ this.showLoginNotice('Failed to create server', 'error');
+ } finally {
+ // Hide loading state
+ if (this.serverLoading) this.serverLoading.classList.add('hidden');
+ }
+ }
+
+ launchMultiplayerGame(server, serverData) {
+ console.log('[LIVE MAIN MENU] Launching multiplayer game on server:', server.name);
+
+ // Set SmartSaveManager to multiplayer mode
+ if (window.smartSaveManager && window.gameInitializer) {
+ window.smartSaveManager.setMultiplayerMode(true, window.gameInitializer);
+ console.log('[LIVE MAIN MENU] SmartSaveManager set to multiplayer mode');
+ }
+
+ // Hide main menu and start game in multiplayer mode
+ this.hideLoadingScreenAndShowGame();
+ this.initializeMultiplayerGame(server, serverData);
+ }
+
+ hideLoadingScreenAndShowGame() {
+ console.log('[LIVE MAIN MENU] Hiding main menu and showing game interface');
+
+ // Hide main menu
+ if (this.mainMenu) {
+ this.mainMenu.classList.add('hidden');
+ }
+
+ // Show game interface
+ const gameInterface = document.getElementById('gameInterface');
+ if (gameInterface) {
+ gameInterface.classList.remove('hidden');
+ }
+
+ // Note: gameContainer element doesn't exist in HTML, so we skip it
+ }
+
+ initializeMultiplayerGame(server, serverData) {
+ console.log('[LIVE MAIN MENU] Initializing multiplayer game');
+
+ // DELAY GameEngine creation until after multiplayer connection is ready
+ // First, set up GameInitializer and socket connection
+ if (!window.gameInitializer) {
+ console.log('[LIVE MAIN MENU] Creating new GameInitializer for multiplayer');
+ window.gameInitializer = new GameInitializer();
+ }
+
+ // Set up server URLs and connection first
+ window.gameInitializer.updateServerUrls(
+ 'https://api.korvarix.com/api',
+ server.url || 'https://dev.gameserver.galaxystrike.online'
+ );
+
+ // Initialize multiplayer mode (this will handle GameEngine creation after authentication)
+ window.gameInitializer.initializeMultiplayer(server, serverData, this.authToken, this.currentUser);
+
+ // Add simple return to menu functionality
+ window.returnToMainMenu = () => {
+ console.log('[LIVE MAIN MENU] Return to main menu requested');
+ if (window.liveMainMenu) {
+ window.liveMainMenu.showLoginSection();
+ }
+ };
+ }
+
+ checkForLocalServer() {
+ console.log('[LIVE MAIN MENU] Checking for local server...');
+
+ // Check if local server manager is available and running
+ if (window.localServerManager) {
+ const serverStatus = window.localServerManager.getStatus();
+
+ if (serverStatus.isRunning) {
+ console.log('[LIVE MAIN MENU] Local server detected, switching to local mode');
+ this.isLocalMode = true;
+ this.apiBaseUrl = `http://localhost:${serverStatus.port}/api`;
+ this.gameServerUrl = `http://localhost:${serverStatus.port}`;
+
+ console.log(`[LIVE MAIN MENU] Updated API URL to: ${this.apiBaseUrl}`);
+
+ // Update button to show local mode
+ if (this.createServerBtn) {
+ this.createServerBtn.innerHTML = ' Local Server Running';
+ this.createServerBtn.classList.remove('btn-primary');
+ this.createServerBtn.classList.add('btn-success');
+ }
+
+ // Auto-login for local mode
+ this.autoLoginLocalMode();
+
+ } else {
+ console.log('[LIVE MAIN MENU] No local server running');
+ }
+ } else {
+ console.log('[LIVE MAIN MENU] Local server manager not available');
+ }
+ }
+
+ async startLocalServer() {
+ console.log('[LIVE MAIN MENU] Starting local server...');
+
+ // Disable button to prevent multiple clicks
+ if (this.createServerBtn) {
+ this.createServerBtn.disabled = true;
+ this.createServerBtn.innerHTML = ' Starting...';
+ }
+
+ try {
+ // Check if LocalServerManager is available
+ if (!window.localServerManager) {
+ console.error('[LIVE MAIN MENU] LocalServerManager not available');
+ this.showLoginNotice('Local server manager not available', 'error');
+
+ // Reset button
+ if (this.createServerBtn) {
+ this.createServerBtn.disabled = false;
+ this.createServerBtn.innerHTML = ' Start Local Server';
+ }
+ return;
+ }
+
+ // Start the local server
+ const result = await window.localServerManager.startServer();
+
+ if (result.success) {
+ console.log('[LIVE MAIN MENU] Local server started successfully:', result);
+
+ // Update to local mode
+ this.isLocalMode = true;
+ this.apiBaseUrl = `http://localhost:${result.port}/api`;
+ this.gameServerUrl = `http://localhost:${result.port}`;
+
+ console.log(`[LIVE MAIN MENU] Updated to local mode - API: ${this.apiBaseUrl}`);
+
+ // Update button to show running state
+ if (this.createServerBtn) {
+ this.createServerBtn.innerHTML = ' Local Server Running';
+ this.createServerBtn.classList.remove('btn-primary');
+ this.createServerBtn.classList.add('btn-success');
+ }
+
+ // Auto-login for local mode
+ await this.autoLoginLocalMode();
+
+ // Show success message
+ this.showLoginNotice('Local server started successfully!', 'success');
+
+ } else {
+ console.error('[LIVE MAIN MENU] Failed to start local server:', result.error);
+ this.showLoginNotice(`Failed to start local server: ${result.error}`, 'error');
+
+ // Reset button to original state
+ if (this.createServerBtn) {
+ this.createServerBtn.innerHTML = ' Start Local Server';
+ this.createServerBtn.disabled = false;
+ }
+ }
+
+ } catch (error) {
+ console.error('[LIVE MAIN MENU] Error starting local server:', error);
+ this.showLoginNotice('Error starting local server', 'error');
+
+ // Reset button to original state
+ if (this.createServerBtn) {
+ this.createServerBtn.innerHTML = ' Start Local Server';
+ this.createServerBtn.disabled = false;
+ }
+ }
+ }
+}
+
+// End of LiveMainMenu class
+
+// Initialize LiveMainMenu when DOM is ready
+document.addEventListener('DOMContentLoaded', () => {
+ if (typeof window !== 'undefined') {
+ window.liveMainMenu = new LiveMainMenu();
+ }
+});
+
+// Export for use in other scripts
+if (typeof module !== 'undefined' && module.exports) {
+ module.exports = LiveMainMenu;
+}
diff --git a/Client-Server/js/ui/MainMenu.js b/Client-Server/js/ui/MainMenu.js
new file mode 100644
index 0000000..1b7f2c5
--- /dev/null
+++ b/Client-Server/js/ui/MainMenu.js
@@ -0,0 +1,1251 @@
+/**
+ * Main Menu System
+ * Handles the main menu UI, save management, and game initialization
+ */
+
+console.log('[MAIN MENU] MainMenu.js script loaded');
+
+class MainMenu {
+ constructor() {
+ console.log('[MAIN MENU] Constructor called');
+ this.mainMenu = document.getElementById('mainMenu');
+ this.selectedSlot = null;
+ this.saveSlotsCount = 3; // Number of save slots
+
+ console.log('[MAIN MENU] Initializing elements');
+ this.initializeElements();
+
+ console.log('[MAIN MENU] Initializing event listeners');
+ this.bindEvents();
+
+ // Delay save folder verification until electronAPI is available
+ this.initializeAfterElectronAPI();
+ }
+
+ initializeAfterElectronAPI() {
+ // Check if electronAPI is available, if not wait for it
+ if (window.electronAPI) {
+ console.log('[MAIN MENU] electronAPI available, proceeding with initialization');
+ this.completeInitialization();
+ } else {
+ console.log('[MAIN MENU] electronAPI not available, waiting...');
+ // Wait for electronAPI to be available
+ setTimeout(() => {
+ this.initializeAfterElectronAPI();
+ }, 100);
+ }
+ }
+
+ async completeInitialization() {
+ console.log('[MAIN MENU] Starting save folder verification');
+ this.verifySaveFolders();
+
+ console.log('[MAIN MENU] Loading save data');
+ await this.loadSaveData();
+
+ console.log('[MAIN MENU] Updating save slots');
+ this.updateSaveSlots();
+
+ console.log('[MAIN MENU] Constructor completed');
+ }
+
+ verifySaveFolders() {
+ console.log('[MAIN MENU] Verifying save folders...');
+ console.log('[MAIN MENU] window.electronAPI available:', !!window.electronAPI);
+
+ try {
+ // Use Electron's IPC instead of direct Node.js access
+ if (window.electronAPI) {
+ console.log('[MAIN MENU] Using Electron API for folder operations');
+ console.log('[MAIN MENU] createSaveFolders method available:', typeof window.electronAPI.createSaveFolders);
+
+ // Request folder creation through main process
+ window.electronAPI.createSaveFolders(this.saveSlotsCount).then((result) => {
+ console.log('[MAIN MENU] createSaveFolders response:', result);
+ if (result.success) {
+ console.log('[MAIN MENU] Save folders created successfully:', result.paths);
+ this.savePaths = result.paths;
+ } else {
+ console.error('[MAIN MENU] Failed to create save folders:', result.error);
+ console.log('[MAIN MENU] Falling back to localStorage save system');
+ this.useLocalStorageFallback();
+ }
+ }).catch((error) => {
+ console.error('[MAIN MENU] IPC error:', error);
+ console.log('[MAIN MENU] Falling back to localStorage save system');
+ this.useLocalStorageFallback();
+ });
+
+ } else {
+ console.log('[MAIN MENU] Electron API not available, using localStorage fallback');
+ this.useLocalStorageFallback();
+ }
+
+ } catch (error) {
+ console.error('[MAIN MENU] Error verifying save folders:', error);
+ console.log('[MAIN MENU] Falling back to localStorage save system');
+ this.useLocalStorageFallback();
+ }
+ }
+
+ hideLoadingScreenAndShowGame() {
+ console.log('[MAIN MENU] Hiding loading screen and showing game interface');
+
+ // Hide loading screen
+ const loadingScreen = document.getElementById('loadingScreen');
+ if (loadingScreen) {
+ loadingScreen.classList.add('hidden');
+ console.log('[MAIN MENU] Loading screen hidden');
+ }
+
+ // Show game interface
+ const gameInterface = document.getElementById('gameInterface');
+ if (gameInterface) {
+ gameInterface.classList.remove('hidden');
+ console.log('[MAIN MENU] Game interface shown');
+ }
+
+ // Show game container
+ const gameContainer = document.getElementById('gameContainer');
+ if (gameContainer) {
+ gameContainer.classList.remove('hidden');
+ console.log('[MAIN MENU] Game container shown');
+ }
+ }
+
+ useLocalStorageFallback() {
+ // Fallback for browser testing or when Electron API fails
+ for (let i = 1; i <= this.saveSlotsCount; i++) {
+ const saveKey = `gso_save_slot_${i}`;
+ if (!localStorage.getItem(saveKey)) {
+ const saveInfo = {
+ slot: i,
+ created: new Date().toISOString(),
+ version: '1.0.0',
+ exists: false
+ };
+ localStorage.setItem(saveKey, JSON.stringify(saveInfo));
+ console.log(`[MAIN MENU] Created localStorage save info for slot ${i}`);
+ }
+ }
+
+ // Mark that we're using localStorage
+ this.savePaths = null;
+ console.log('[MAIN MENU] Using localStorage save system');
+ }
+
+ initializeElements() {
+ // Menu sections
+ this.mainMenu = document.getElementById('mainMenu');
+ this.loginSection = document.getElementById('loginSection');
+ this.saveSection = document.getElementById('saveSection');
+ this.optionsSection = document.getElementById('optionsSection');
+
+ // Login buttons
+ this.ssoLoginBtn = document.getElementById('ssoLoginBtn');
+ this.offlineBtn = document.getElementById('offlineBtn');
+
+ // Save selection
+ this.saveSlots = document.querySelectorAll('.save-slot');
+ this.backToLoginBtn = document.getElementById('backToLoginBtn');
+
+ // Save confirmation section
+ this.confirmSection = document.getElementById('confirmSection');
+ this.confirmSlotTitle = document.getElementById('confirmSlotTitle');
+ this.confirmSaveDetails = document.getElementById('confirmSaveDetails');
+ this.confirmLevel = document.getElementById('confirmLevel');
+ this.confirmShip = document.getElementById('confirmShip');
+ this.confirmPlayTime = document.getElementById('confirmPlayTime');
+ this.confirmLastPlayed = document.getElementById('confirmLastPlayed');
+ this.confirmNewGameBtn = document.getElementById('confirmNewGameBtn');
+ this.confirmContinueBtn = document.getElementById('confirmContinueBtn');
+ this.confirmDeleteBtn = document.getElementById('confirmDeleteBtn');
+ console.log('[MAIN MENU] confirmDeleteBtn found:', !!this.confirmDeleteBtn);
+ this.confirmSettingsBtn = document.getElementById('confirmSettingsBtn');
+ this.backToSavesFromConfirmBtn = document.getElementById('backToSavesFromConfirmBtn');
+
+ // Game options
+ this.newGameBtn = document.getElementById('newGameBtn');
+ this.continueBtn = document.getElementById('continueBtn');
+ this.deleteSaveBtn = document.getElementById('deleteSaveBtn');
+ console.log('[MAIN MENU] deleteSaveBtn found:', !!this.deleteSaveBtn);
+ this.settingsBtn = document.getElementById('settingsBtn');
+ this.quitBtn = document.getElementById('quitBtn');
+ this.backToSavesBtn = document.getElementById('backToSavesBtn');
+
+ // Footer links
+ this.aboutBtn = document.getElementById('aboutBtn');
+ this.helpBtn = document.getElementById('helpBtn');
+ }
+
+ bindEvents() {
+ // Login events
+ this.ssoLoginBtn?.addEventListener('click', () => this.handleSSOLogin());
+ this.offlineBtn?.addEventListener('click', () => this.handleOfflineLogin());
+
+ // Save selection events
+ this.saveSlots.forEach(slot => {
+ slot.addEventListener('click', () => this.selectSaveSlot(slot));
+ });
+
+ this.backToLoginBtn?.addEventListener('click', () => this.showLoginSection());
+
+ // Save confirmation events
+ this.confirmNewGameBtn?.addEventListener('click', () => this.startNewGame());
+ this.confirmContinueBtn?.addEventListener('click', () => this.continueGame());
+ this.confirmDeleteBtn?.addEventListener('click', () => {
+ console.log('[MAIN MENU] Delete button clicked');
+ this.deleteSave();
+});
+ this.confirmSettingsBtn?.addEventListener('click', () => this.showSettings());
+ this.backToSavesFromConfirmBtn?.addEventListener('click', () => this.showSaveSection());
+
+ // Game options events
+ this.newGameBtn?.addEventListener('click', () => this.startNewGame());
+ this.continueBtn?.addEventListener('click', () => this.continueGame());
+ this.deleteSaveBtn?.addEventListener('click', () => {
+ console.log('[MAIN MENU] Delete save button clicked');
+ this.deleteSave();
+});
+ this.settingsBtn?.addEventListener('click', () => this.showSettings());
+ this.quitBtn?.addEventListener('click', () => this.quitGame());
+ this.backToSavesBtn?.addEventListener('click', async () => this.showSaveSection());
+
+ // Footer events
+ this.aboutBtn?.addEventListener('click', () => this.showAbout());
+ this.helpBtn?.addEventListener('click', () => this.showHelp());
+ }
+
+ showSection(sectionName) {
+ // Hide all sections
+ this.loginSection?.classList.add('hidden');
+ this.saveSection?.classList.add('hidden');
+ this.confirmSection?.classList.add('hidden');
+ this.optionsSection?.classList.add('hidden');
+
+ // Show selected section
+ const section = document.getElementById(`${sectionName}Section`);
+ if (section) {
+ section.classList.remove('hidden');
+ this.currentSection = sectionName;
+ }
+ }
+
+ showLoginSection() {
+ this.showSection('login');
+ }
+
+ async showSaveSection() {
+ this.showSection('save');
+ await this.loadSaveData();
+ this.updateSaveSlots();
+ }
+
+ showConfirmSection() {
+ this.showSection('confirm');
+ // Clear any previous data to prevent contamination
+ this.clearConfirmSectionData();
+ this.updateConfirmSection();
+ }
+
+ updateConfirmSection() {
+ const hasSave = this.selectedSlot && this.saveData[this.selectedSlot];
+
+ // Update slot title
+ this.confirmSlotTitle.textContent = `Slot ${this.selectedSlot}`;
+
+ if (hasSave) {
+ const saveData = this.saveData[this.selectedSlot];
+ const player = saveData.player || {};
+
+ // Update save details
+ const playerLevel = player.stats?.level || player.level || 1;
+
+ // Get ship name from multiple possible fields
+ const shipName = player.shipName || player.ship?.name || player.currentShip || player.selectedShip || saveData.shipName || saveData.currentShip || 'Unknown Ship';
+
+ // Calculate play time - check multiple possible fields and format properly
+ const playTime = player.playTime || player.stats?.playTime || saveData.playTime || saveData.totalPlayTime || 0;
+
+ // Handle different time formats (seconds, milliseconds, or already formatted)
+ let totalSeconds = 0;
+ if (typeof playTime === 'number') {
+ // If it's a large number, it might be milliseconds
+ if (playTime > 3600) {
+ totalSeconds = Math.floor(playTime / 1000);
+ } else {
+ totalSeconds = Math.floor(playTime);
+ }
+ } else if (typeof playTime === 'string') {
+ // If it's already formatted, use it as-is
+ const playTimeText = playTime;
+ } else {
+ totalSeconds = 0;
+ }
+
+ // Format time as dd.hh.nn.ss
+ const days = Math.floor(totalSeconds / 86400);
+ const hours = Math.floor((totalSeconds % 86400) / 3600);
+ const minutes = Math.floor((totalSeconds % 3600) / 60);
+ const seconds = totalSeconds % 60;
+
+ let playTimeText = '';
+ if (days > 0) {
+ playTimeText = `${days}d ${hours}h ${minutes}m ${seconds}s`;
+ } else if (hours > 0) {
+ playTimeText = `${hours}h ${minutes}m ${seconds}s`;
+ } else if (minutes > 0) {
+ playTimeText = `${minutes}m ${seconds}s`;
+ } else {
+ playTimeText = `${seconds}s`;
+ }
+
+ // Last played - check multiple possible date fields
+ const lastPlayed = saveData.lastPlayed || saveData.lastSave || saveData.timestamp || saveData.date || new Date();
+ const formattedDate = lastPlayed instanceof Date ? lastPlayed.toLocaleDateString() :
+ (typeof lastPlayed === 'string' ? lastPlayed : new Date(lastPlayed).toLocaleDateString());
+ const lastPlayedText = formattedDate === new Date().toLocaleDateString() ? 'Today' : formattedDate;
+
+ // Restore textContent properties
+ this.confirmLevel.textContent = playerLevel;
+ this.confirmShip.textContent = shipName;
+ this.confirmPlayTime.textContent = playTimeText;
+ this.confirmLastPlayed.textContent = lastPlayedText;
+
+ // Format save info with consistent alignment and labels
+ const saveInfoText = `Level: ${playerLevel}\n\nShip: ${shipName}\n\nPlay Time: ${playTimeText}\n\nLast Played: ${lastPlayedText}`;
+
+ // Update save details with pre-formatted text for alignment
+ this.confirmSaveDetails.innerHTML = `${saveInfoText} `;
+
+ // Has save data - always show Continue, hide New Game
+ this.confirmContinueBtn.style.display = 'block';
+ this.confirmContinueBtn.disabled = false;
+ this.confirmDeleteBtn.style.display = 'block';
+ this.confirmDeleteBtn.disabled = false;
+ this.confirmSettingsBtn.style.display = 'block';
+ this.confirmSettingsBtn.disabled = false;
+ this.confirmNewGameBtn.style.display = 'none';
+ this.confirmNewGameBtn.disabled = true;
+ } else {
+ // No save data - show new game option
+ this.confirmLevel.textContent = 'New';
+ this.confirmShip.textContent = 'New Adventure';
+ this.confirmPlayTime.textContent = '0h 0m';
+ this.confirmLastPlayed.textContent = 'Never';
+
+ // Show new game and settings buttons, hide continue and delete
+ this.confirmNewGameBtn.style.display = 'block';
+ this.confirmNewGameBtn.disabled = false;
+ this.confirmSettingsBtn.style.display = 'block';
+ this.confirmSettingsBtn.disabled = false;
+ this.confirmContinueBtn.style.display = 'none';
+ this.confirmDeleteBtn.style.display = 'none';
+ }
+ }
+
+ showOptionsSection() {
+ this.showSection('options');
+ // Clear any previous data to prevent contamination
+ this.clearOptionsSectionData();
+ this.updateGameOptions();
+ }
+
+ clearConfirmSectionData() {
+ // Clear individual text content elements
+ if (this.confirmLevel) this.confirmLevel.textContent = '';
+ if (this.confirmShip) this.confirmShip.textContent = '';
+ if (this.confirmPlayTime) this.confirmPlayTime.textContent = '';
+ if (this.confirmLastPlayed) this.confirmLastPlayed.textContent = '';
+
+ // Clear the save details display
+ if (this.confirmSaveDetails) {
+ this.confirmSaveDetails.innerHTML = '';
+ }
+ }
+
+ clearOptionsSectionData() {
+ // Clear the save info details display
+ const saveInfoDetails = document.getElementById('saveInfoDetails');
+ if (saveInfoDetails) {
+ saveInfoDetails.innerHTML = '';
+ }
+ }
+
+ handleSSOLogin() {
+ // Placeholder for SSO login
+ console.log('[MAIN MENU] SSO login requested (placeholder)');
+
+ // Show loading state
+ this.ssoLoginBtn.disabled = true;
+ this.ssoLoginBtn.innerHTML = ' Connecting...';
+
+ // Simulate SSO login (will be implemented later)
+ setTimeout(() => {
+ this.isLoggedIn = true;
+ this.ssoLoginBtn.innerHTML = ' Logged In';
+ this.ssoLoginBtn.disabled = false;
+
+ // Show save selection
+ this.showSaveSection();
+ }, 1500);
+ }
+
+ handleOfflineLogin() {
+ console.log('[MAIN MENU] Login successful');
+ this.isLoggedIn = true;
+
+ // Show save selection
+ setTimeout(async () => {
+ await this.showSaveSection();
+ }, 1500);
+ }
+
+ async loadSaveData() {
+ // Initialize saveData object
+ this.saveData = {};
+
+ console.log('[MAIN MENU] Loading save data from file system');
+
+ // Check if we have save paths and should use file system
+ if (this.savePaths && window.electronAPI) {
+ console.log('[MAIN MENU] Using file system for save data');
+
+ // Load save data from file system using the load-game API
+ for (let i = 1; i <= 3; i++) {
+ try {
+ console.log(`[MAIN MENU] Loading save file for slot ${i}`);
+
+ const result = await window.electronAPI.loadGame(i);
+
+ if (result.success && result.data) {
+ this.saveData[i] = result.data;
+ console.log(`[MAIN MENU] Slot ${i} loaded successfully:`, this.saveData[i]);
+ } else {
+ console.log(`[MAIN MENU] Slot ${i} has no save file or failed to load:`, result.error);
+ this.saveData[i] = null;
+ }
+ } catch (error) {
+ console.error(`[MAIN MENU] Error reading save slot ${i}:`, error);
+ this.saveData[i] = null;
+ }
+ }
+ } else {
+ console.log('[MAIN MENU] Using localStorage fallback for save data');
+ console.log('[MAIN MENU] Available localStorage keys:', Object.keys(localStorage));
+
+ // Fallback to localStorage
+ for (let i = 1; i <= 3; i++) {
+ const saveKey = `gso_save_slot_${i}`;
+ const saveData = localStorage.getItem(saveKey);
+
+ console.log(`[MAIN MENU] Slot ${i} key: ${saveKey}, data:`, saveData);
+
+ if (saveData) {
+ try {
+ this.saveData[i] = JSON.parse(saveData);
+ console.log(`[MAIN MENU] Slot ${i} parsed successfully:`, this.saveData[i]);
+ } catch (error) {
+ console.error(`[MAIN MENU] Error loading save slot ${i}:`, error);
+ this.saveData[i] = null;
+ }
+ } else {
+ console.log(`[MAIN MENU] Slot ${i} has no data`);
+ this.saveData[i] = null;
+ }
+ }
+ }
+
+ console.log('[MAIN MENU] Final saveData object:', this.saveData);
+ }
+
+ updateSaveSlots() {
+ console.log('[MAIN MENU] Updating save slots UI');
+ console.log('[MAIN MENU] this.saveSlots:', this.saveSlots);
+ console.log('[MAIN MENU] this.saveData:', this.saveData);
+
+ // Check if saveSlots elements exist
+ if (!this.saveSlots || this.saveSlots.length === 0) {
+ console.log('[MAIN MENU] Save slots not found, re-initializing elements');
+ this.saveSlots = document.querySelectorAll('.save-slot');
+ console.log('[MAIN MENU] Re-initialized saveSlots:', this.saveSlots);
+ }
+
+ if (!this.saveSlots || this.saveSlots.length === 0) {
+ console.error('[MAIN MENU] Could not find save slot elements in DOM');
+ return;
+ }
+
+ this.saveSlots.forEach((slot, index) => {
+ const slotNumber = index + 1;
+ const saveInfo = this.saveData[slotNumber];
+
+ console.log(`[MAIN MENU] Processing slot ${slotNumber}, saveInfo:`, saveInfo);
+
+ const slotStatus = slot.querySelector('.slot-status');
+ const slotName = slot.querySelector('.slot-name');
+ const slotDetails = slot.querySelector('.slot-details');
+ const slotBtn = slot.querySelector('.slot-btn');
+
+ console.log(`[MAIN MENU] Found elements for slot ${slotNumber}:`, {
+ slotStatus: !!slotStatus,
+ slotName: !!slotName,
+ slotDetails: !!slotDetails,
+ slotBtn: !!slotBtn
+ });
+
+ // Check if this is a valid save game (has actual game data)
+ console.log(`[MAIN MENU] Validating save data for slot ${slotNumber}:`, {
+ saveInfo: saveInfo,
+ hasSaveInfo: !!saveInfo,
+ hasPlayer: !!(saveInfo && saveInfo.player),
+ hasPlayerStats: !!(saveInfo && saveInfo.player && saveInfo.player.stats),
+ playerLevel: saveInfo && saveInfo.player && saveInfo.player.stats ? saveInfo.player.stats.level : 'no player.stats',
+ playerKeys: saveInfo && saveInfo.player ? Object.keys(saveInfo.player) : 'no player'
+ });
+
+ const hasValidSave = saveInfo && saveInfo.player && saveInfo.player.stats && saveInfo.player.stats.level !== undefined;
+
+ if (hasValidSave) {
+ // Update with existing save data
+ console.log(`[MAIN MENU] Slot ${slotNumber} has valid save data - showing Load`);
+ if (slotStatus) {
+ slotStatus.textContent = 'Level ' + (saveInfo.player.stats.level || 1);
+ slotStatus.className = 'slot-status has-data';
+ }
+ if (slotName) {
+ slotName.textContent = saveInfo.player.info.name || 'Commander';
+ }
+
+ // Format playtime and last played
+ const playTime = saveInfo.player.stats.playTime || saveInfo.player.playTime || 0;
+
+ // Handle different time formats (seconds, milliseconds, or already formatted)
+ let totalSeconds = 0;
+ if (typeof playTime === 'number') {
+ // If it's a large number, it might be milliseconds
+ if (playTime > 3600) {
+ totalSeconds = Math.floor(playTime / 1000);
+ } else {
+ totalSeconds = Math.floor(playTime);
+ }
+ } else if (typeof playTime === 'string') {
+ // If it's already formatted, use it as-is
+ var playtime = playTime;
+ } else {
+ totalSeconds = 0;
+ }
+
+ // Only format if we haven't already set playtime from a string
+ if (typeof playtime !== 'string') {
+ // Format time as dd.hh.nn.ss
+ const days = Math.floor(totalSeconds / 86400);
+ const hours = Math.floor((totalSeconds % 86400) / 3600);
+ const minutes = Math.floor((totalSeconds % 3600) / 60);
+ const seconds = totalSeconds % 60;
+
+ if (days > 0) {
+ playtime = `${days}d ${hours}h ${minutes}m ${seconds}s`;
+ } else if (hours > 0) {
+ playtime = `${hours}h ${minutes}m ${seconds}s`;
+ } else if (minutes > 0) {
+ playtime = `${minutes}m ${seconds}s`;
+ } else {
+ playtime = `${seconds}s`;
+ }
+ }
+
+ const lastSaved = saveInfo.lastSave ? new Date(saveInfo.lastSave).toLocaleDateString() : 'Unknown';
+ if (slotDetails) {
+ slotDetails.textContent = `Playtime: ${playtime} | Saved: ${lastSaved}`;
+ }
+
+ if (slotBtn) {
+ slotBtn.textContent = 'Select';
+ slotBtn.onclick = () => this.selectSaveSlot(slot);
+ }
+ } else {
+ // Empty slot or invalid/placeholder save
+ console.log(`[MAIN MENU] Slot ${slotNumber} is empty or invalid - showing New Game`);
+ if (slotStatus) {
+ slotStatus.textContent = 'Empty';
+ slotStatus.className = 'slot-status empty';
+ }
+ if (slotName) {
+ slotName.textContent = 'New Game';
+ }
+ if (slotDetails) {
+ slotDetails.textContent = 'Start a new adventure';
+ }
+ if (slotBtn) {
+ slotBtn.textContent = 'Select';
+ slotBtn.onclick = () => this.selectSaveSlot(slot);
+ }
+ }
+ });
+ }
+
+ selectSaveSlot(slot) {
+ const slotNumber = parseInt(slot.dataset.slot);
+ this.selectedSlot = slotNumber;
+
+ console.log(`[MAIN MENU] Save slot ${slotNumber} selected`);
+
+ // Show confirmation section
+ this.showConfirmSection();
+ }
+
+ updateGameOptions() {
+ const hasSave = this.selectedSlot && this.saveData[this.selectedSlot];
+
+ // Update button states based on save data
+ if (hasSave) {
+ // Has save data - always show Continue, hide New Game
+ this.continueBtn.style.display = 'block';
+ this.continueBtn.disabled = false;
+ this.continueBtn.innerHTML = ' Continue';
+
+ this.newGameBtn.style.display = 'none';
+
+ this.deleteSaveBtn.style.display = 'block';
+ this.deleteSaveBtn.disabled = false;
+ this.deleteSaveBtn.innerHTML = ' Delete Save';
+
+ // Update center information display
+ this.updateSaveInfoDisplay();
+ } else {
+ // Show new game button, hide continue button
+ this.newGameBtn.style.display = 'block';
+ this.newGameBtn.disabled = false;
+ this.newGameBtn.innerHTML = ' Start New Game';
+
+ this.continueBtn.style.display = 'none';
+
+ this.deleteSaveBtn.style.display = 'none';
+
+ // Update center information display for empty slot
+ this.updateSaveInfoDisplay();
+ }
+ }
+
+ updateSaveInfoDisplay() {
+ const saveInfoDetails = document.getElementById('saveInfoDetails');
+ if (!saveInfoDetails) return;
+
+ const hasSave = this.selectedSlot && this.saveData[this.selectedSlot];
+
+ if (hasSave) {
+ const saveData = this.saveData[this.selectedSlot];
+ const player = saveData.player || {};
+
+ // Format save information
+ const level = player.stats?.level || player.level || 1;
+ const name = player.info ? player.info.name : 'Commander';
+ const ship = player.shipName || 'Unknown Ship';
+
+ // Calculate play time - check multiple possible fields and format properly
+ const playTime = player.playTime || player.stats?.playTime || saveData.playTime || saveData.totalPlayTime || 0;
+
+ // Handle different time formats (seconds, milliseconds, or already formatted)
+ let totalSeconds = 0;
+ if (typeof playTime === 'number') {
+ // If it's a large number, it might be milliseconds
+ if (playTime > 3600) {
+ totalSeconds = Math.floor(playTime / 1000);
+ } else {
+ totalSeconds = Math.floor(playTime);
+ }
+ } else if (typeof playTime === 'string') {
+ // If it's already formatted, use it as-is
+ var playtime = playTime;
+ } else {
+ totalSeconds = 0;
+ }
+
+ // Only format if we haven't already set playtime from a string
+ if (typeof playtime !== 'string') {
+ // Format time as dd.hh.nn.ss
+ const days = Math.floor(totalSeconds / 86400);
+ const hours = Math.floor((totalSeconds % 86400) / 3600);
+ const minutes = Math.floor((totalSeconds % 3600) / 60);
+ const seconds = totalSeconds % 60;
+
+ if (days > 0) {
+ playtime = `${days}d ${hours}h ${minutes}m ${seconds}s`;
+ } else if (hours > 0) {
+ playtime = `${hours}h ${minutes}m ${seconds}s`;
+ } else if (minutes > 0) {
+ playtime = `${minutes}m ${seconds}s`;
+ } else {
+ playtime = `${seconds}s`;
+ }
+ }
+
+ // Check multiple possible date fields and use current date if none found
+ const lastSaved = saveData.lastPlayed || saveData.lastSave || saveData.timestamp || new Date().toLocaleDateString();
+ const formattedDate = lastSaved === new Date().toLocaleDateString() ? 'Today' :
+ (typeof lastSaved === 'string' ? lastSaved : new Date(lastSaved).toLocaleDateString());
+
+ // Format save info with consistent alignment and labels
+ const saveInfoText = `Level: ${level}\n\nShip: ${ship}\n\nPlay Time: ${playtime}\n\nLast Played: ${formattedDate}`;
+
+ // Update save details with pre-formatted text for alignment
+ saveInfoDetails.innerHTML = `${saveInfoText} `;
+ } else {
+ saveInfoDetails.innerHTML = `
+
+
Slot: ${this.selectedSlot || 'None'}
+
Status: Empty
+
Ready for new adventure
+
+ `;
+ }
+ }
+
+ startNewGame() {
+ if (!this.selectedSlot) {
+ console.error('[MAIN MENU] No save slot selected');
+ return;
+ }
+
+ console.log(`[MAIN MENU] Starting new game in slot ${this.selectedSlot}`);
+
+ // Clear existing save data for this slot
+ const saveKey = `gso_save_slot_${this.selectedSlot}`;
+ localStorage.removeItem(saveKey);
+ this.saveData[this.selectedSlot] = null;
+
+ // Start the game
+ this.launchGame('new');
+ }
+
+ continueGame() {
+ if (!this.selectedSlot || !this.saveData[this.selectedSlot]) {
+ console.error('[MAIN MENU] No save data to continue');
+ return;
+ }
+
+ console.log(`[MAIN MENU] Continuing game from slot ${this.selectedSlot}`);
+ this.launchGame('continue');
+ }
+
+ async deleteSave() {
+ console.log('[MAIN MENU] deleteSave called');
+
+ if (!this.selectedSlot || !this.saveData[this.selectedSlot]) {
+ console.error('[MAIN MENU] No save data to delete', {
+ selectedSlot: this.selectedSlot,
+ hasSaveData: !!this.saveData[this.selectedSlot]
+ });
+ return;
+ }
+
+ const saveData = this.saveData[this.selectedSlot];
+ const saveInfo = saveData.player ?
+ `Level ${saveData.player.stats?.level || saveData.player.level || 1} ${saveData.player.shipName || saveData.player.ship?.name || 'Player'}` :
+ 'Unknown save data';
+
+ const confirmMessage = `Are you sure you want to delete the save from slot ${this.selectedSlot}?\n\n${saveInfo}\n\nThis action cannot be undone!`;
+
+ console.log('[MAIN MENU] Showing confirmation modal');
+
+ // Show custom confirmation modal
+ this.showConfirm(confirmMessage, () => {
+ console.log('[MAIN MENU] User confirmed deletion, executing delete');
+ this.executeDeleteSave();
+ }, () => {
+ console.log('[MAIN MENU] User cancelled deletion');
+ });
+ }
+
+ async executeDeleteSave() {
+ console.log('[MAIN MENU] executeDeleteSave called', {
+ selectedSlot: this.selectedSlot
+ });
+
+ try {
+ console.log(`[MAIN MENU] Deleting save data from slot ${this.selectedSlot}`);
+
+ // Remove from localStorage
+ const saveKey = `gso_save_slot_${this.selectedSlot}`;
+ console.log(`[MAIN MENU] Removing localStorage key: ${saveKey}`);
+ localStorage.removeItem(saveKey);
+
+ // Verify removal
+ const remainingData = localStorage.getItem(saveKey);
+ console.log(`[MAIN MENU] Verification - save data still exists: ${!!remainingData}`);
+
+ // Remove from file system if available
+ if (window.electronAPI && window.electronAPI.deleteSaveFile) {
+ console.log(`[MAIN MENU] Deleting save file via Electron API`);
+ await window.electronAPI.deleteSaveFile(this.selectedSlot);
+ console.log(`[MAIN MENU] Save file deleted from slot ${this.selectedSlot} via Electron API`);
+ } else if (window.electronAPI && window.electronAPI.deleteSave) {
+ console.log(`[MAIN MENU] Deleting save file via Electron API (deleteSave method)`);
+ await window.electronAPI.deleteSave(this.selectedSlot);
+ console.log(`[MAIN MENU] Save file deleted from slot ${this.selectedSlot} via Electron API`);
+ } else {
+ console.log(`[MAIN MENU] No Electron API available for file deletion, only localStorage deletion performed`);
+ console.log(`[MAIN MENU] Available electronAPI methods:`, Object.keys(window.electronAPI || {}));
+ }
+
+ // Clear from memory
+ console.log(`[MAIN MENU] Clearing save data from memory`);
+ this.saveData[this.selectedSlot] = null;
+
+ // Update UI
+ console.log(`[MAIN MENU] Updating UI after deletion`);
+ this.updateSaveSlots();
+ this.updateGameOptions();
+
+ console.log(`[MAIN MENU] Save data successfully deleted from slot ${this.selectedSlot}`);
+
+ // Show success message
+ this.showAlert(`Save data from slot ${this.selectedSlot} has been deleted successfully.`, 'success');
+
+ // Return to save selection screen
+ console.log(`[MAIN MENU] Returning to save selection screen`);
+ this.showSaveSection();
+
+ } catch (error) {
+ console.error('[MAIN MENU] Failed to delete save data:', error);
+ this.showAlert('Failed to delete save data. Please try again.', 'error');
+ }
+ }
+
+ loadGame(slotNumber) {
+ this.selectedSlot = slotNumber;
+ console.log(`[MAIN MENU] Loading game from slot ${slotNumber}`);
+ this.launchGame('continue');
+ }
+
+ async launchGame(mode) {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('MainMenu.launchGame', {
+ mode: mode,
+ selectedSlot: this.selectedSlot,
+ timestamp: new Date().toISOString()
+ });
+
+ console.log('[MAIN MENU] Launching game in', mode, 'mode');
+ console.log('[MAIN MENU] Selected save slot:', this.selectedSlot);
+
+ try {
+ // Hide main menu
+ this.hide();
+
+ // Show loading screen
+ const loadingScreen = document.getElementById('loadingScreen');
+ if (loadingScreen) {
+ loadingScreen.classList.remove('hidden');
+ console.log('[MAIN MENU] Loading screen shown');
+
+ if (debugLogger) debugLogger.logStep('Loading screen displayed');
+ }
+
+ // Initialize game systems first
+ console.log('[MAIN MENU] Starting game systems initialization...');
+
+ if (debugLogger) debugLogger.logStep('Starting game systems initialization');
+
+ try {
+ await this.initializeGameSystems();
+ console.log('[MAIN MENU] Game systems initialized successfully');
+
+ if (debugLogger) debugLogger.logStep('Game systems initialized successfully');
+
+ // Set save slot in game engine
+ if (window.game) {
+ console.log('[MAIN MENU] Setting active save slot to:', this.selectedSlot);
+
+ // Pass save path information to game engine
+ if (this.savePaths) {
+ window.game.saveSlotInfo = {
+ slot: this.selectedSlot,
+ path: this.savePaths.slots[this.selectedSlot - 1],
+ useFileSystem: true
+ };
+ console.log('[MAIN MENU] Using file system for saves');
+
+ if (debugLogger) debugLogger.logStep('Save slot info set with file system', {
+ slot: this.selectedSlot,
+ path: this.savePaths.slots[this.selectedSlot - 1]
+ });
+ } else {
+ window.game.saveSlotInfo = {
+ slot: this.selectedSlot,
+ useFileSystem: false
+ };
+ console.log('[MAIN MENU] Using localStorage for saves');
+
+ if (debugLogger) debugLogger.logStep('Save slot info set with localStorage', {
+ slot: this.selectedSlot
+ });
+ }
+
+ // Start the game
+ if (mode === 'continue') {
+ console.log('[MAIN MENU] Starting game in continue mode');
+
+ if (debugLogger) debugLogger.logStep('Starting game in continue mode');
+ await window.game.startGame(true);
+ } else {
+ console.log('[MAIN MENU] Starting new game');
+
+ if (debugLogger) debugLogger.logStep('Starting new game');
+ await window.game.startGame(false);
+ }
+
+ // Perform immediate UI updates after game starts
+ console.log('[MAIN MENU] Performing immediate UI updates after game start');
+
+ // Update GUI immediately
+ if (window.game && window.game.updateGUI) {
+ console.log('[MAIN MENU] Updating GUI immediately');
+ window.game.updateGUI();
+ }
+
+ // Update daily countdown immediately
+ if (window.game && window.game.systems && window.game.systems.questSystem && window.game.systems.questSystem.updateDailyCountdown) {
+ console.log('[MAIN MENU] Updating daily countdown immediately');
+ window.game.systems.questSystem.updateDailyCountdown();
+ }
+
+ // Update weekly countdown immediately
+ if (window.game && window.game.systems && window.game.systems.questSystem && window.game.systems.questSystem.updateWeeklyCountdown) {
+ console.log('[MAIN MENU] Updating weekly countdown immediately');
+ window.game.systems.questSystem.updateWeeklyCountdown();
+ }
+
+ // Hide loading screen and show game interface
+ this.hideLoadingScreenAndShowGame();
+
+ if (debugLogger) debugLogger.endStep('MainMenu.launchGame', {
+ success: true,
+ mode: mode
+ });
+ } else {
+ throw new Error('Game engine not initialized');
+ }
+ } catch (error) {
+ console.error('[MAIN MENU] Game systems initialization failed:', error);
+
+ if (debugLogger) {
+ debugLogger.errorEvent('MainMenu.launchGame', error, {
+ phase: 'initialization',
+ mode: mode
+ });
+ debugLogger.endStep('MainMenu.launchGame', {
+ success: false,
+ error: error.message
+ });
+ }
+
+ // Show error and return to menu
+ this.showAlert('Failed to initialize game. Check console for details.\n\nError: ' + error.message, 'error');
+ this.show();
+ }
+ } catch (error) {
+ console.error('[MAIN MENU] Game systems initialization failed:', error);
+
+ if (debugLogger) {
+ debugLogger.errorEvent('MainMenu.launchGame', error, {
+ phase: 'initialization',
+ mode: mode
+ });
+ debugLogger.endStep('MainMenu.launchGame', {
+ success: false,
+ error: error.message
+ });
+ }
+
+ // Show error and return to menu
+ this.showAlert('Failed to initialize game. Check console for details.\n\nError: ' + error.message, 'error');
+ this.show();
+ }
+ }
+
+ async initializeGameSystems() {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('MainMenu.initializeGameSystems', {
+ timestamp: new Date().toISOString()
+ });
+
+ console.log('[MAIN MENU] Initializing game systems');
+
+ try {
+ // Initialize logger if available
+ const logger = window.logger;
+ if (logger) {
+ await logger.timeAsync('Game Systems Initialization', async () => {
+ await logger.info('Initializing game systems from main menu');
+ });
+ }
+
+ if (debugLogger) debugLogger.logStep('Creating GameEngine instance');
+
+ // Prevent duplicate GameEngine creation
+ if (window.game) {
+ console.log('[MAIN MENU] GameEngine already exists, skipping creation');
+ if (debugLogger) debugLogger.logStep('GameEngine already exists, skipping creation');
+ return;
+ }
+
+ // Create game engine
+ window.game = new GameEngine();
+
+ if (debugLogger) debugLogger.logStep('GameEngine created, calling init()');
+
+ // Initialize game systems
+ await window.game.init();
+
+ if (debugLogger) debugLogger.logStep('GameEngine init() completed successfully');
+
+ console.log('[MAIN MENU] Game systems initialized successfully');
+
+ if (debugLogger) debugLogger.endStep('MainMenu.initializeGameSystems', {
+ success: true
+ });
+
+ } catch (error) {
+ console.error('[MAIN MENU] Failed to initialize game systems:', error);
+
+ if (debugLogger) {
+ debugLogger.errorEvent('MainMenu.initializeGameSystems', error, {
+ phase: 'initialization',
+ timestamp: new Date().toISOString()
+ });
+ debugLogger.endStep('MainMenu.initializeGameSystems', {
+ success: false,
+ error: error.message
+ });
+ }
+
+ // Show error and return to menu
+ this.showAlert('Failed to initialize game. Please try again.\n\nError: ' + error.message, 'error');
+ this.show();
+ }
+ }
+
+ showSettings() {
+ console.log('[MAIN MENU] Settings requested (placeholder)');
+ // Placeholder for settings menu
+ this.showAlert('Settings menu coming soon!', 'info');
+ }
+
+ showAbout() {
+ console.log('[MAIN MENU] About requested');
+ this.showAlert('Galaxy Strike Online\nVersion 1.0.0\n\nA space-themed idle MMORPG built with Electron.\n\n© 2024 Korvarix Studios', 'info', 'About');
+ }
+
+ showHelp() {
+ console.log('[MAIN MENU] Help requested');
+ this.showAlert('Galaxy Strike Online - Help\n\n• Login with your account to play\n• Select a save slot to store your progress\n• Start a new game or continue existing save\n• Use Ctrl+Alt+Shift+C for developer console\n\nFor more help, visit our Discord or documentation.', 'info', 'Help');
+ }
+
+ quitGame() {
+ console.log('[MAIN MENU] Quit requested');
+
+ this.showConfirm('Are you sure you want to quit Galaxy Strike Online?', () => {
+ // In Electron, this would close the app
+ if (window.electronAPI && window.electronAPI.quit) {
+ window.electronAPI.quit();
+ } else {
+ // Fallback for browser
+ window.close();
+ }
+ });
+ }
+
+ // Public methods for external access
+ show() {
+ this.mainMenu?.classList.remove('hidden');
+ this.showSaveSection();
+ }
+
+ hide() {
+ this.mainMenu?.classList.add('hidden');
+ }
+
+ // Custom alert function to replace browser alerts
+ showAlert(message, type = 'info', title = null) {
+ const modalOverlay = document.getElementById('modalOverlay');
+ const modal = document.getElementById('modal');
+ const modalTitle = document.getElementById('modalTitle');
+ const modalBody = document.getElementById('modalBody');
+
+ console.log('[MAIN MENU] showAlert called', {
+ modalOverlay: !!modalOverlay,
+ modal: !!modal,
+ modalTitle: !!modalTitle,
+ modalBody: !!modalBody
+ });
+
+ if (!modalOverlay || !modal || !modalTitle || !modalBody) {
+ // Fallback to browser alert if modal elements aren't found
+ alert(message);
+ return;
+ }
+
+ // Set title based on type
+ const titles = {
+ 'info': 'Information',
+ 'success': 'Success',
+ 'error': 'Error',
+ 'warning': 'Warning'
+ };
+
+ const alertTitle = title || titles[type] || 'Information';
+
+ // Set modal content
+ modalTitle.textContent = alertTitle;
+ modalBody.innerHTML = `
+ ${message}
+
+ `;
+
+ // Set modal type class
+ modal.className = `modal alert-modal ${type}`;
+
+ // Show modal
+ modalOverlay.classList.remove('hidden');
+
+ // Add click outside to close
+ modalOverlay.onclick = (e) => {
+ if (e.target === modalOverlay) {
+ this.closeAlert();
+ }
+ };
+
+ // Add escape key to close
+ const handleEscape = (e) => {
+ if (e.key === 'Escape') {
+ this.closeAlert();
+ document.removeEventListener('keydown', handleEscape);
+ }
+ };
+ document.addEventListener('keydown', handleEscape);
+ }
+
+ closeAlert() {
+ const modalOverlay = document.getElementById('modalOverlay');
+ if (modalOverlay) {
+ modalOverlay.classList.add('hidden');
+ }
+ }
+
+ // Custom confirmation function to replace browser confirm()
+ showConfirm(message, onConfirm, onCancel = null) {
+ const modalOverlay = document.getElementById('modalOverlay');
+ const modal = document.getElementById('modal');
+ const modalTitle = document.getElementById('modalTitle');
+ const modalBody = document.getElementById('modalBody');
+
+ console.log('[MAIN MENU] showConfirm called', {
+ modalOverlay: !!modalOverlay,
+ modal: !!modal,
+ modalTitle: !!modalTitle,
+ modalBody: !!modalBody
+ });
+
+ if (!modalOverlay || !modal || !modalTitle || !modalBody) {
+ console.warn('[MAIN MENU] Modal elements not found, falling back to browser confirm');
+ // Fallback to browser confirm if modal elements aren't found
+ const result = confirm(message);
+ if (result && onConfirm) onConfirm();
+ if (!result && onCancel) onCancel();
+ return;
+ }
+
+ // Set modal content
+ modalTitle.textContent = 'Confirm Action';
+ modalBody.innerHTML = `
+ ${message}
+
+ `;
+
+ // Set modal type class
+ modal.className = 'modal confirmation-modal';
+
+ // Store callbacks
+ this._confirmCallback = onConfirm;
+ this._cancelCallback = onCancel;
+
+ // Show modal
+ modalOverlay.classList.remove('hidden');
+
+ // Add click outside to close
+ modalOverlay.onclick = (e) => {
+ if (e.target === modalOverlay) {
+ this.closeConfirm();
+ }
+ };
+
+ // Add escape key to close
+ const handleEscape = (e) => {
+ if (e.key === 'Escape') {
+ this.closeConfirm();
+ document.removeEventListener('keydown', handleEscape);
+ }
+ };
+ document.addEventListener('keydown', handleEscape);
+ }
+
+ executeConfirm() {
+ console.log('[MAIN MENU] executeConfirm called', {
+ hasCallback: !!this._confirmCallback
+ });
+
+ const modalOverlay = document.getElementById('modalOverlay');
+ if (modalOverlay) {
+ modalOverlay.classList.add('hidden');
+ }
+
+ if (this._confirmCallback) {
+ console.log('[MAIN MENU] Executing confirmation callback');
+ this._confirmCallback();
+ } else {
+ console.warn('[MAIN MENU] No confirmation callback found');
+ }
+
+ // Clear callbacks
+ this._confirmCallback = null;
+ this._cancelCallback = null;
+ }
+
+ closeConfirm() {
+ const modalOverlay = document.getElementById('modalOverlay');
+ if (modalOverlay) {
+ modalOverlay.classList.add('hidden');
+ }
+
+ if (this._cancelCallback) {
+ this._cancelCallback();
+ }
+
+ // Clear callbacks
+ this._confirmCallback = null;
+ this._cancelCallback = null;
+ }
+}
+
+// Initialize main menu when DOM is ready
+document.addEventListener('DOMContentLoaded', () => {
+ console.log('[MAIN MENU] DOMContentLoaded event fired');
+ window.mainMenu = new MainMenu();
+});
+
+// Also try immediate initialization if DOM is already loaded
+if (document.readyState === 'loading') {
+ console.log('[MAIN MENU] DOM still loading, waiting for DOMContentLoaded');
+} else {
+ console.log('[MAIN MENU] DOM already loaded, initializing immediately');
+ window.mainMenu = new MainMenu();
+}
diff --git a/Client-Server/js/ui/UIManager.js b/Client-Server/js/ui/UIManager.js
new file mode 100644
index 0000000..b1bd8b7
--- /dev/null
+++ b/Client-Server/js/ui/UIManager.js
@@ -0,0 +1,2459 @@
+/**
+ * Galaxy Strike Online - UI Manager
+ * Handles all user interface interactions and updates
+ */
+
+class UIManager {
+ constructor(gameEngine) {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('UIManager.constructor', {
+ gameEngineProvided: !!gameEngine
+ });
+
+ this.game = gameEngine;
+
+ // UI state
+ this.currentTab = 'dashboard';
+ this.modalOpen = false;
+ this.notifications = [];
+
+ // Note: setupEventListeners() called in proceedWithInitialization() to prevent duplicates
+
+ if (debugLogger) debugLogger.endStep('UIManager.constructor', {
+ currentTab: this.currentTab,
+ modalOpen: this.modalOpen,
+ notificationsCount: this.notifications.length,
+ gameEngineSet: !!this.game,
+ eventListenersSetup: true
+ });
+ }
+
+ async initialize() {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('UIManager.initialize', {
+ currentTab: this.currentTab,
+ modalOpen: this.modalOpen,
+ notificationsCount: this.notifications.length
+ });
+
+ // Wait for DOM to be ready and game interface to be visible
+ const waitForDOM = () => {
+ const gameInterface = document.getElementById('gameInterface');
+ const navButtons = document.querySelectorAll('.nav-btn');
+
+ if (debugLogger) debugLogger.logStep('DOM Check', {
+ gameInterfaceExists: !!gameInterface,
+ gameInterfaceHidden: gameInterface?.classList.contains('hidden'),
+ navButtonsFound: navButtons.length,
+ documentReady: document.readyState
+ });
+
+ if (gameInterface && !gameInterface.classList.contains('hidden') && navButtons.length > 0) {
+ this.proceedWithInitialization();
+ } else {
+ setTimeout(waitForDOM, 100);
+ }
+ };
+
+ // Start the DOM check
+ waitForDOM();
+ }
+
+ proceedWithInitialization() {
+ const debugLogger = window.debugLogger;
+
+ // Setup navigation
+ // if (debugLogger) debugLogger.logStep('Setting up navigation');
+ this.setupNavigation();
+
+ // Setup event listeners
+ // if (debugLogger) debugLogger.logStep('Setting up event listeners');
+ this.setupEventListeners();
+
+ // Setup resource display
+ // if (debugLogger) debugLogger.logStep('Setting up resource display');
+ this.setupResourceDisplay();
+
+ if (debugLogger) debugLogger.endStep('UIManager.initialize', {
+ currentTab: this.currentTab,
+ modalOpen: this.modalOpen,
+ notificationsCount: this.notifications.length,
+ navigationSetup: true,
+ eventListenersSetup: true,
+ resourceDisplaySetup: true
+ });
+ }
+
+ setupNavigation() {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('UIManager.setupNavigation');
+
+ // Navigation setup moved to setupEventListeners to fix scope issues
+
+ if (debugLogger) debugLogger.endStep('UIManager.setupNavigation', {
+ success: true
+ });
+
+ const craftingCatButtons = document.querySelectorAll('.crafting-cat-btn');
+ if (debugLogger) debugLogger.logStep('Setting up crafting category navigation', {
+ craftingCatButtonsCount: craftingCatButtons.length
+ });
+
+ craftingCatButtons.forEach(btn => {
+ btn.addEventListener('click', (e) => {
+ const category = e.currentTarget.dataset.category;
+
+ if (debugLogger) debugLogger.log('Crafting category button clicked', {
+ buttonElement: btn.tagName,
+ buttonText: btn.textContent,
+ category: category
+ });
+
+ this.switchCraftingCategory(category);
+ });
+ });
+
+ if (debugLogger) debugLogger.endStep('UIManager.setupNavigation', {
+ navButtonsSetup: navButtons.length,
+ skillCatButtonsSetup: skillCatButtons.length,
+ shopCatButtonsSetup: shopCatButtons.length,
+ questTabButtonsSetup: questTabButtons.length,
+ craftingCatButtonsSetup: craftingCatButtons.length
+ });
+ }
+
+ setupEventListeners() {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('UIManager.setupEventListeners');
+
+ // Setup tab navigation here to fix scope issues with endStep
+ const navButtons = document.querySelectorAll('.nav-btn');
+ console.log(`[UIManager] Found ${navButtons.length} navigation buttons`);
+ if (debugLogger) debugLogger.logStep('Setting up tab navigation', {
+ navButtonsCount: navButtons.length
+ });
+
+ if (navButtons.length === 0) {
+ console.warn('[UIManager] No navigation buttons found!');
+ if (debugLogger) debugLogger.logStep('No navigation buttons found');
+ }
+
+ navButtons.forEach((btn, index) => {
+ console.log(`[UIManager] Setting up button ${index}:`, btn.dataset.tab);
+ // Check if button already has a listener to prevent duplicates
+ if (btn.getAttribute('data-has-listener') === 'true') {
+ console.log(`[UIManager] Button ${btn.dataset.tab} already has listener, skipping`);
+ return;
+ }
+
+ btn.addEventListener('click', (e) => {
+ console.log(`[UIManager] Navigation button clicked: ${btn.dataset.tab}`);
+ const tab = btn.dataset.tab;
+ if (tab) {
+ this.switchTab(tab);
+ }
+ });
+
+ // Mark button as having a listener
+ btn.setAttribute('data-has-listener', 'true');
+ console.log(`[UIManager] Button ${btn.dataset.tab} setup complete`);
+ });
+
+ // Set up UI update event listener from GameEngine
+ if (this.game) {
+ this.game.addEventListener('uiUpdate', (event) => {
+ this.handleUIUpdate(event.detail);
+ });
+
+ // if (debugLogger) debugLogger.logStep('UI update event listener set up');
+ }
+
+ // Modal controls
+ const modalClose = document.getElementById('modalClose');
+ const modalOverlay = document.getElementById('modalOverlay');
+
+ if (debugLogger) debugLogger.logStep('Setting up modal controls', {
+ modalCloseExists: !!modalClose,
+ modalOverlayExists: !!modalOverlay
+ });
+
+ if (modalClose) {
+ // Check if already has listener
+ if (modalClose.getAttribute('data-has-listener') === 'true') {
+ if (debugLogger) debugLogger.log('Modal close button already has listener, skipping');
+ } else {
+ modalClose.addEventListener('click', () => {
+ if (debugLogger) debugLogger.log('Modal close button clicked');
+ this.closeModal();
+ });
+ modalClose.setAttribute('data-has-listener', 'true');
+ }
+ }
+
+ if (modalOverlay) {
+ // Check if already has listener
+ if (modalOverlay.getAttribute('data-has-listener') === 'true') {
+ if (debugLogger) debugLogger.log('Modal overlay already has listener, skipping');
+ } else {
+ modalOverlay.addEventListener('click', (e) => {
+ if (e.target === modalOverlay) {
+ if (debugLogger) debugLogger.log('Modal overlay clicked - closing modal');
+ this.closeModal();
+ }
+ });
+ modalOverlay.setAttribute('data-has-listener', 'true');
+ }
+ }
+
+ // Quick action buttons
+ const claimOfflineBtn = document.getElementById('claimOfflineBtn');
+ if (claimOfflineBtn) {
+ // Check if already has listener
+ if (claimOfflineBtn.getAttribute('data-has-listener') === 'true') {
+ if (debugLogger) debugLogger.log('Claim offline button already has listener, skipping');
+ } else {
+ // if (debugLogger) debugLogger.logStep('Setting up claim offline rewards button');
+ claimOfflineBtn.addEventListener('click', () => {
+ if (debugLogger) debugLogger.log('Claim offline rewards button clicked');
+ this.game.systems.idleSystem.claimOfflineRewards();
+ });
+ claimOfflineBtn.setAttribute('data-has-listener', 'true');
+ }
+ }
+
+ const quickDungeonBtn = document.getElementById('quickDungeonBtn');
+ if (quickDungeonBtn) {
+ // if (debugLogger) debugLogger.logStep('Setting up quick dungeon button');
+ quickDungeonBtn.addEventListener('click', () => {
+ if (debugLogger) debugLogger.log('Quick dungeon button clicked', {
+ currentTab: this.currentTab,
+ targetTab: 'dungeons'
+ });
+ this.switchTab('dungeons');
+ });
+ }
+
+ // Settings and Discord buttons
+ const settingsBtn = document.getElementById('settingsBtn');
+ if (settingsBtn) {
+ // Check if already has listener
+ if (settingsBtn.getAttribute('data-has-listener') === 'true') {
+ if (debugLogger) debugLogger.log('Settings button already has listener, skipping');
+ } else {
+ // if (debugLogger) debugLogger.logStep('Setting up settings button');
+ settingsBtn.addEventListener('click', () => {
+ if (debugLogger) debugLogger.log('Settings button clicked');
+ this.showSettingsMenu();
+ });
+ settingsBtn.setAttribute('data-has-listener', 'true');
+ }
+ }
+
+ const discordBtn = document.getElementById('discordBtn');
+ if (discordBtn) {
+ // if (debugLogger) debugLogger.logStep('Setting up Discord button');
+ discordBtn.addEventListener('click', () => {
+ if (debugLogger) debugLogger.log('Discord button clicked');
+ this.showDiscordIntegration();
+ });
+ }
+
+ // Local Server button
+ const localServerBtn = document.getElementById('localServerBtn');
+ if (localServerBtn) {
+ // if (debugLogger) debugLogger.logStep('Setting up local server button');
+ localServerBtn.addEventListener('click', () => {
+ if (debugLogger) debugLogger.log('Local server button clicked');
+ this.showLocalServerControls();
+ });
+ }
+
+ // Return to Main Menu button
+ const returnToMenuBtn = document.getElementById('returnToMenuBtn');
+ if (returnToMenuBtn) {
+ // Check if button already has a listener to prevent duplicates
+ if (returnToMenuBtn.getAttribute('data-has-listener') === 'true') {
+ if (debugLogger) debugLogger.log('Return to menu button already has listener, skipping');
+ return;
+ }
+
+ // if (debugLogger) debugLogger.logStep('Setting up return to menu button');
+ returnToMenuBtn.addEventListener('click', () => {
+ if (debugLogger) debugLogger.log('Return to menu button clicked');
+ this.showReturnToMainMenuModal();
+ });
+
+ // Mark as having listener
+ returnToMenuBtn.setAttribute('data-has-listener', 'true');
+ }
+
+ // Setup sub-panel category buttons
+ const skillCatButtons = document.querySelectorAll('.skill-cat-btn');
+ if (debugLogger) debugLogger.logStep('Setting up skill category navigation', {
+ skillCatButtonsCount: skillCatButtons.length
+ });
+
+ skillCatButtons.forEach(btn => {
+ // Check if button already has a listener to prevent duplicates
+ if (btn.getAttribute('data-has-listener') === 'true') {
+ return;
+ }
+
+ btn.addEventListener('click', (e) => {
+ const category = btn.dataset.category;
+ if (debugLogger) debugLogger.log('Skill category button clicked', {
+ buttonElement: btn.tagName,
+ buttonText: btn.textContent,
+ category: category
+ });
+
+ this.switchSkillCategory(category);
+ });
+
+ // Mark button as having a listener
+ btn.setAttribute('data-has-listener', 'true');
+ });
+
+ const questTabButtons = document.querySelectorAll('.quest-tab-btn');
+ if (debugLogger) debugLogger.logStep('Setting up quest tab navigation', {
+ questTabButtonsCount: questTabButtons.length
+ });
+
+ questTabButtons.forEach(btn => {
+ // Check if button already has a listener to prevent duplicates
+ if (btn.getAttribute('data-has-listener') === 'true') {
+ return;
+ }
+
+ btn.addEventListener('click', (e) => {
+ const type = btn.dataset.type;
+ if (debugLogger) debugLogger.log('Quest tab button clicked', {
+ buttonElement: btn.tagName,
+ buttonText: btn.textContent,
+ type: type
+ });
+
+ this.switchQuestType(type);
+ });
+
+ // Mark button as having a listener
+ btn.setAttribute('data-has-listener', 'true');
+ });
+
+ const craftingCatButtons = document.querySelectorAll('.crafting-cat-btn');
+ if (debugLogger) debugLogger.logStep('Setting up crafting category navigation', {
+ craftingCatButtonsCount: craftingCatButtons.length
+ });
+
+ craftingCatButtons.forEach(btn => {
+ // Check if button already has a listener to prevent duplicates
+ if (btn.getAttribute('data-has-listener') === 'true') {
+ return;
+ }
+
+ btn.addEventListener('click', (e) => {
+ const category = e.currentTarget.dataset.category;
+
+ if (debugLogger) debugLogger.log('Crafting category button clicked', {
+ buttonElement: btn.tagName,
+ buttonText: btn.textContent,
+ category: category
+ });
+
+ this.switchCraftingCategory(category);
+ });
+
+ // Mark button as having a listener
+ btn.setAttribute('data-has-listener', 'true');
+ });
+
+ const shopCatButtons = document.querySelectorAll('.shop-cat-btn');
+ if (debugLogger) debugLogger.logStep('Setting up shop category navigation', {
+ shopCatButtonsCount: shopCatButtons.length
+ });
+
+ shopCatButtons.forEach(btn => {
+ // Check if button already has a listener to prevent duplicates
+ if (btn.getAttribute('data-has-listener') === 'true') {
+ return;
+ }
+
+ btn.addEventListener('click', (e) => {
+ const category = btn.dataset.category;
+ if (debugLogger) debugLogger.log('Shop category button clicked', {
+ buttonElement: btn.tagName,
+ buttonText: btn.textContent,
+ category: category
+ });
+
+ this.switchShopCategory(category);
+ });
+
+ // Mark button as having a listener
+ btn.setAttribute('data-has-listener', 'true');
+ });
+
+ // Keyboard shortcuts for tab switching removed
+
+ if (debugLogger) debugLogger.endStep('UIManager.setupEventListeners', {
+ modalControlsSetup: !!(modalClose || modalOverlay),
+ quickActionButtonsSetup: !!(claimOfflineBtn || quickDungeonBtn),
+ settingsButtonsSetup: !!(settingsBtn || discordBtn),
+ returnMenuButtonSetup: !!returnToMenuBtn,
+ navigationButtonsSetup: navButtons.length > 0,
+ navigationButtonsCount: navButtons.length,
+ skillCategoryButtonsSetup: skillCatButtons.length,
+ questTabButtonsSetup: questTabButtons.length,
+ craftingCategoryButtonsSetup: craftingCatButtons.length,
+ shopCategoryButtonsSetup: shopCatButtons.length
+ });
+ }
+
+ async saveGame() {
+ const debugLogger = window.debugLogger;
+
+ // if (debugLogger) debugLogger.startStep('UIManager.saveGame');
+
+ try {
+
+ // Show saving notification
+ // this.game.showNotification('Saving game...', 'info', 2000);
+
+ // Call the game engine's save method
+ await this.game.save();
+
+ // Show success notification
+ // this.game.showNotification('Game saved successfully!', 'success', 3000);
+
+
+ // if (debugLogger) debugLogger.endStep('UIManager.saveGame', {
+ // success: true
+ // });
+
+ } catch (error) {
+
+ // Show error notification
+ this.game.showNotification('Failed to save game!', 'error', 3000);
+
+ if (debugLogger) debugLogger.errorEvent(error, 'UIManager.saveGame');
+ }
+ }
+
+ showLocalServerControls() {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('UIManager.showLocalServerControls');
+
+
+ // Get current server status
+ const serverInfo = window.localServerManager ? window.localServerManager.getServerInfo() : {
+ isRunning: false,
+ status: 'Unknown',
+ port: null,
+ url: null,
+ connectedClients: 0,
+ uptime: 0
+ };
+
+ let content = '';
+ content += '
';
+ content += `
Status: ${serverInfo.status}
`;
+
+ if (serverInfo.isRunning) {
+ content += `
Port: ${serverInfo.port}
`;
+ content += `
URL: ${serverInfo.url}
`;
+ content += `
Connected Clients: ${serverInfo.connectedClients}
`;
+ content += `
Uptime: ${Math.floor(serverInfo.uptime)}s
`;
+ }
+ content += '
';
+
+ // Control buttons
+ content += '
';
+
+ if (serverInfo.isRunning) {
+ content += '';
+ content += ' Stop Server';
+ content += ' ';
+
+ content += '';
+ content += ' Restart Server';
+ content += ' ';
+ } else {
+ content += '';
+ content += ' Start Server';
+ content += ' ';
+ }
+
+ content += '
';
+
+ // Information section
+ content += '
';
+ content += '
The local server enables singleplayer mode when external servers are unavailable. Save data is stored locally on your computer. No internet connection required for local gameplay.
';
+ content += '
';
+
+ content += '
';
+
+ this.showModal('Local Server', content);
+
+ if (debugLogger) debugLogger.endStep('UIManager.showLocalServerControls', {
+ serverRunning: serverInfo.isRunning,
+ serverPort: serverInfo.port
+ });
+ }
+
+ async startLocalServer() {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('UIManager.startLocalServer');
+
+ try {
+
+ if (!window.localServerManager) {
+ this.game.showNotification('Local server manager not available', 'error', 3000);
+ return;
+ }
+
+ const result = await window.localServerManager.startServer();
+
+ if (result.success) {
+ this.game.showNotification(`Local server started on port ${result.port}`, 'success', 3000);
+ this.closeModal();
+
+ // Refresh the modal to show updated status
+ setTimeout(() => {
+ this.showLocalServerControls();
+ }, 500);
+ } else {
+ this.game.showNotification(`Failed to start server: ${result.error}`, 'error', 3000);
+ }
+
+ if (debugLogger) debugLogger.endStep('UIManager.startLocalServer', {
+ success: result.success,
+ port: result.port
+ });
+
+ } catch (error) {
+ this.game.showNotification('Error starting local server', 'error', 3000);
+
+ if (debugLogger) debugLogger.errorEvent(error, 'UIManager.startLocalServer');
+ }
+ }
+
+ async stopLocalServer() {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('UIManager.stopLocalServer');
+
+ try {
+
+ if (!window.localServerManager) {
+ this.game.showNotification('Local server manager not available', 'error', 3000);
+ return;
+ }
+
+ const result = await window.localServerManager.stopServer();
+
+ if (result.success) {
+ this.game.showNotification('Local server stopped', 'success', 3000);
+ this.closeModal();
+
+ // Refresh the modal to show updated status
+ setTimeout(() => {
+ this.showLocalServerControls();
+ }, 500);
+ } else {
+ this.game.showNotification(`Failed to stop server: ${result.error}`, 'error', 3000);
+ }
+
+ if (debugLogger) debugLogger.endStep('UIManager.stopLocalServer', {
+ success: result.success
+ });
+
+ } catch (error) {
+ this.game.showNotification('Error stopping local server', 'error', 3000);
+
+ if (debugLogger) debugLogger.errorEvent(error, 'UIManager.stopLocalServer');
+ }
+ }
+
+ async restartLocalServer() {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('UIManager.restartLocalServer');
+
+ try {
+
+ if (!window.localServerManager) {
+ this.game.showNotification('Local server manager not available', 'error', 3000);
+ return;
+ }
+
+ // Stop first
+ await this.stopLocalServer();
+
+ // Wait a moment
+ await new Promise(resolve => setTimeout(resolve, 1000));
+
+ // Start again
+ await this.startLocalServer();
+
+ if (debugLogger) debugLogger.endStep('UIManager.restartLocalServer', {
+ success: true
+ });
+
+ } catch (error) {
+ this.game.showNotification('Error restarting local server', 'error', 3000);
+
+ if (debugLogger) debugLogger.errorEvent(error, 'UIManager.restartLocalServer');
+ }
+ }
+
+ returnToMainMenu() {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('UIManager.returnToMainMenu');
+
+ // Go directly to server selection without confirmation modal
+ this.showMainMenu();
+ this.closeModal();
+
+ if (debugLogger) debugLogger.endStep('UIManager.returnToMainMenu', {
+ success: true,
+ directToServerSelection: true
+ });
+ }
+
+ async stopLocalServer() {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('UIManager.stopLocalServer');
+
+ try {
+
+ if (!window.localServerManager) {
+ this.game.showNotification('Local server manager not available', 'error', 3000);
+ return;
+ }
+
+ const result = await window.localServerManager.stopServer();
+
+ if (result.success) {
+ this.game.showNotification('Local server stopped', 'success', 3000);
+ this.closeModal();
+
+ // Refresh the modal to show updated status
+ setTimeout(() => {
+ this.showLocalServerControls();
+ }, 500);
+ } else {
+ this.game.showNotification(`Failed to stop server: ${result.error}`, 'error', 3000);
+ }
+
+ if (debugLogger) debugLogger.endStep('UIManager.stopLocalServer', {
+ success: result.success
+ });
+
+ } catch (error) {
+ this.game.showNotification('Error stopping local server', 'error', 3000);
+
+ if (debugLogger) debugLogger.errorEvent(error, 'UIManager.stopLocalServer');
+ }
+ }
+
+ async restartLocalServer() {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('UIManager.restartLocalServer');
+
+ try {
+
+ if (!window.localServerManager) {
+ this.game.showNotification('Local server manager not available', 'error', 3000);
+ return;
+ }
+
+ // Stop first
+ await this.stopLocalServer();
+
+ // Wait a moment
+ await new Promise(resolve => setTimeout(resolve, 1000));
+
+ // Start again
+ await this.startLocalServer();
+
+ if (debugLogger) debugLogger.endStep('UIManager.restartLocalServer', {
+ success: true
+ });
+
+ } catch (error) {
+ this.game.showNotification('Error restarting local server', 'error', 3000);
+
+ if (debugLogger) debugLogger.errorEvent(error, 'UIManager.restartLocalServer');
+ }
+ }
+
+ showReturnToMainMenuModal() {
+ const debugLogger = window.debugLogger;
+
+ // Show confirmation modal instead of browser confirm dialog
+ let content = '';
+ content += '
Are you sure you want to return to the main menu?
';
+ content += '
Warning: Any unsaved progress will be lost.
';
+ content += '
';
+
+ // Check if using fallback GameEngine
+ if (window.game && window.game.isFallback) {
+ content += 'Return to Menu ';
+ content += 'Cancel ';
+ } else {
+ content += 'Return to Menu ';
+ content += 'Cancel ';
+ }
+
+ content += '
';
+ content += '
';
+
+ this.showModal('Return to Main Menu', content);
+
+ if (debugLogger) debugLogger.endStep('UIManager.returnToMainMenu', {
+ success: true,
+ confirmationShown: true
+ });
+ }
+
+ async confirmReturnToMainMenu() {
+ try {
+ const debugLogger = window.debugLogger;
+
+ // Reset multiplayer mode when returning to main menu
+ if (window.smartSaveManager) {
+ window.smartSaveManager.setMultiplayerMode(false);
+ }
+
+ // Check if we're in multiplayer mode - if so, don't save locally
+ const isMultiplayer = window.smartSaveManager?.isMultiplayer;
+
+ // Always stop the game and clear timers regardless of mode
+ this.game.isRunning = false;
+
+ // Force save before stopping in multiplayer mode
+ if (isMultiplayer && this.game && this.game.save) {
+ console.log('[UI MANAGER] Force saving game before leaving multiplayer mode');
+ try {
+ await this.game.save();
+ console.log('[UI MANAGER] Game saved successfully before leaving server');
+ } catch (error) {
+ console.error('[UI MANAGER] Error saving game before leaving server:', error);
+ }
+ }
+
+ // Clear game logic timer
+ if (this.game.gameLogicTimer) {
+ clearInterval(this.game.gameLogicTimer);
+ this.game.gameLogicTimer = null;
+ }
+
+ // Clear auto-save timer
+ if (this.game.autoSaveTimer) {
+ clearInterval(this.game.autoSaveTimer);
+ this.game.autoSaveTimer = null;
+ }
+
+ // Stop economy system timers
+ if (this.game.systems.economy) {
+ this.game.systems.economy.stopShopTimers();
+ }
+
+ if (isMultiplayer) {
+ console.log('[UI MANAGER] Skipping local save - returning from multiplayer mode');
+
+ // Show main menu immediately
+ this.showMainMenu();
+ this.closeModal();
+
+ if (debugLogger) debugLogger.endStep('UIManager.confirmReturnToMainMenu', {
+ success: true,
+ multiplayerMode: true,
+ localSaveSkipped: true,
+ mainMenuShown: true
+ });
+ } else {
+ // Handle async stop properly for singleplayer mode
+ this.game.stop().then(() => {
+ try {
+
+ // if (debugLogger) debugLogger.logStep('Game saved successfully');
+ this.showMainMenu();
+ this.closeModal();
+
+ if (debugLogger) debugLogger.endStep('UIManager.confirmReturnToMainMenu', {
+ success: true,
+ gameStopped: true,
+ mainMenuShown: true
+ });
+ } catch (error) {
+ try {
+
+ if (debugLogger) debugLogger.errorEvent('UIManager.confirmReturnToMainMenu', error, {
+ phase: 'game_stop_and_save'
+ });
+
+ // Still return to menu even if save fails
+ this.showMainMenu();
+ this.closeModal();
+
+ if (debugLogger) debugLogger.endStep('UIManager.confirmReturnToMainMenu', {
+ success: true,
+ gameStopped: true,
+ saveError: true,
+ mainMenuShown: true
+ });
+ } catch (loggerError) {
+ console.error('[UI MANAGER] Debug logger error:', loggerError);
+ }
+ }
+ }).catch(error => {
+ try {
+
+ if (debugLogger) debugLogger.errorEvent('UIManager.confirmReturnToMainMenu', error, {
+ phase: 'game_stop_and_save'
+ });
+
+ // Still return to menu even if save fails
+ this.showMainMenu();
+ this.closeModal();
+
+ if (debugLogger) debugLogger.endStep('UIManager.confirmReturnToMainMenu', {
+ success: true,
+ gameStopped: true,
+ saveError: true,
+ mainMenuShown: true
+ });
+ } catch (loggerError) {
+ console.error('[UI MANAGER] Debug logger error:', loggerError);
+ }
+ });
+ }
+ } catch (error) {
+ console.error('[UI MANAGER] Error in confirmReturnToMainMenu:', error);
+ if (debugLogger) debugLogger.errorEvent('UIManager.confirmReturnToMainMenu', error, {
+ phase: 'main_logic'
+ });
+ }
+
+ // Always show main menu regardless of game state
+ try {
+ setTimeout(() => {
+ this.showMainMenu();
+ this.closeModal();
+ }, 5000); // 5 second delay to ensure full cleanup and menu readiness
+ if (debugLogger) debugLogger.endStep('UIManager.confirmReturnToMainMenu', {
+ success: true,
+ gameStopped: this.game ? !this.game.isRunning : false,
+ mainMenuShown: true
+ });
+ } catch (error) {
+ console.error('[UI MANAGER] Error during return to main menu:', error);
+ if (debugLogger) debugLogger.errorEvent('UIManager.confirmReturnToMainMenu', error, {
+ phase: 'return_to_main_menu'
+ });
+ }
+ }
+ showMainMenu() {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('UIManager.showMainMenu');
+
+
+ // Disconnect from server first
+ if (window.gameInitializer && window.gameInitializer.socket) {
+ window.gameInitializer.socket.disconnect();
+ }
+
+ const gameInterface = document.getElementById('gameInterface');
+ if (gameInterface) {
+ gameInterface.classList.add('hidden');
+
+ // if (debugLogger) debugLogger.logStep('Game interface hidden');
+ } else {
+ if (debugLogger) debugLogger.log('Game interface element not found');
+ }
+
+ // Show main menu
+ if (window.liveMainMenu) {
+ // if (debugLogger) debugLogger.logStep('About to show main menu using LiveMainMenu');
+
+ try {
+ // Show the main menu DOM element and use LiveMainMenu to show appropriate section
+ const mainMenuElement = document.getElementById('mainMenu');
+ if (mainMenuElement) {
+ mainMenuElement.classList.remove('hidden');
+ }
+
+ // Show the server section (most appropriate for returning to menu)
+ if (window.liveMainMenu && typeof window.liveMainMenu.showServerSection === 'function') {
+ window.liveMainMenu.showServerSection();
+ } else {
+ // Fallback: manually show server section
+ console.warn('LiveMainMenu.showServerSection not available, using fallback');
+
+ // Ensure main menu is visible (using the already declared mainMenuElement)
+ if (mainMenuElement) {
+ mainMenuElement.classList.remove('hidden');
+ }
+
+ const serverSection = document.getElementById('serverSection');
+ const loginSection = document.getElementById('loginSection');
+ const serverConfirmSection = document.getElementById('serverConfirmSection');
+ const optionsSection = document.getElementById('optionsSection');
+
+ // Hide all sections first
+ if (loginSection) loginSection.classList.add('hidden');
+ if (serverConfirmSection) serverConfirmSection.classList.add('hidden');
+ if (optionsSection) optionsSection.classList.add('hidden');
+
+ // Show server section
+ if (serverSection) {
+ serverSection.classList.remove('hidden');
+ }
+
+ // Try to refresh server list if available
+ if (window.liveMainMenu && typeof window.liveMainMenu.refreshServerList === 'function') {
+ window.liveMainMenu.refreshServerList();
+ }
+ }
+
+ // if (debugLogger) debugLogger.logStep('LiveMainMenu showServerSection() completed successfully');
+ } catch (error) {
+ // if (debugLogger) debugLogger.logStep('Error in LiveMainMenu operations', { error: error.message });
+ }
+
+ // Show save section specifically for returning to menu
+ setTimeout(() => {
+ try {
+ // Note: LiveMainMenu doesn't have save methods, so we'll just log this
+ if (debugLogger) debugLogger.logStep('Save section operations skipped - LiveMainMenu doesn\'t have save methods');
+ } catch (error) {
+ if (debugLogger) debugLogger.logStep('Error in save section operations', { error: error.message });
+ }
+ }, 500);
+ } else if (window.mainMenu) {
+ // Fallback to just showing the DOM element
+ // if (debugLogger) debugLogger.logStep('Using fallback DOM display for main menu');
+
+ try {
+ const mainMenuElement = document.getElementById('mainMenu');
+ if (mainMenuElement) {
+ mainMenuElement.classList.remove('hidden');
+
+ // Also show server section in fallback mode
+ const serverSection = document.getElementById('serverSection');
+ const loginSection = document.getElementById('loginSection');
+ const serverConfirmSection = document.getElementById('serverConfirmSection');
+ const optionsSection = document.getElementById('optionsSection');
+
+ // Hide all sections first
+ if (loginSection) loginSection.classList.add('hidden');
+ if (serverConfirmSection) serverConfirmSection.classList.add('hidden');
+ if (optionsSection) optionsSection.classList.add('hidden');
+
+ // Show server section
+ if (serverSection) {
+ serverSection.classList.remove('hidden');
+ }
+
+ // if (debugLogger) debugLogger.logStep('Main menu DOM element shown via fallback with server section');
+ }
+ } catch (error) {
+ // if (debugLogger) debugLogger.logStep('Error in fallback main menu display', { error: error.message });
+ }
+ } else {
+ // Final fallback: directly manipulate DOM to show server section
+ console.warn('Neither LiveMainMenu nor mainMenu available, using final fallback');
+ try {
+ const mainMenuElement = document.getElementById('mainMenu');
+ const serverSection = document.getElementById('serverSection');
+ const loginSection = document.getElementById('loginSection');
+ const serverConfirmSection = document.getElementById('serverConfirmSection');
+ const optionsSection = document.getElementById('optionsSection');
+ const gameInterface = document.getElementById('gameInterface');
+
+ // Hide game interface
+ if (gameInterface) {
+ gameInterface.classList.add('hidden');
+ }
+
+ // Show main menu
+ if (mainMenuElement) {
+ mainMenuElement.classList.remove('hidden');
+ }
+
+ // Hide all sections first
+ if (loginSection) loginSection.classList.add('hidden');
+ if (serverConfirmSection) serverConfirmSection.classList.add('hidden');
+ if (optionsSection) optionsSection.classList.add('hidden');
+
+ // Show server section
+ if (serverSection) {
+ serverSection.classList.remove('hidden');
+ }
+ } catch (error) {
+ console.error('Error in final fallback server section display', error);
+ }
+ }
+
+ if (debugLogger) debugLogger.endStep('UIManager.showMainMenu', {
+ success: true,
+ mainMenuShown: !!window.mainMenu
+ });
+ }
+
+ // Tab management
+ switchTab(tabName) {
+ if (debugLogger) debugLogger.startStep('UIManager.switchTab', {
+ fromTab: this.currentTab,
+ toTab: tabName,
+ sameTab: this.currentTab === tabName
+ });
+
+ if (this.currentTab === tabName) {
+ if (debugLogger) debugLogger.log('Switching to same tab, no action needed', {
+ tabName: tabName
+ });
+
+ if (debugLogger) debugLogger.endStep('UIManager.switchTab', {
+ success: true,
+ action: 'no_change_needed',
+ currentTab: this.currentTab
+ });
+
+ return;
+ }
+
+ const oldTab = this.currentTab;
+
+ // Update navigation buttons
+ const navButtons = document.querySelectorAll('.nav-btn');
+ let navButtonsUpdated = 0;
+
+ navButtons.forEach(btn => {
+ const wasActive = btn.classList.contains('active');
+ const shouldBeActive = btn.dataset.tab === tabName;
+ btn.classList.toggle('active', shouldBeActive);
+
+ if (wasActive !== shouldBeActive) {
+ navButtonsUpdated++;
+ }
+ });
+
+ if (debugLogger) debugLogger.logStep('Navigation buttons updated', {
+ totalButtons: navButtons.length,
+ buttonsUpdated: navButtonsUpdated
+ });
+
+ // Update tab content
+ const tabContents = document.querySelectorAll('.tab-content');
+ let tabContentsUpdated = 0;
+
+ tabContents.forEach(content => {
+ const wasActive = content.classList.contains('active');
+ const shouldBeActive = content.id === `${tabName}-tab`;
+ content.classList.toggle('active', shouldBeActive);
+
+ if (wasActive !== shouldBeActive) {
+ tabContentsUpdated++;
+ }
+ });
+
+ if (debugLogger) debugLogger.logStep('Tab contents updated', {
+ totalContents: tabContents.length,
+ contentsUpdated: tabContentsUpdated
+ });
+
+ this.currentTab = tabName;
+ this.game.state.currentTab = tabName;
+
+ if (debugLogger) debugLogger.logStep('Tab state updated', {
+ oldTab: oldTab,
+ newTab: this.currentTab,
+ gameStateUpdated: true
+ });
+
+ // Update specific tab content only if in multiplayer mode or game is actively running
+ if (this.shouldUpdateUI()) {
+ this.updateTabContent(tabName);
+ }
+
+ if (debugLogger) debugLogger.endStep('UIManager.switchTab', {
+ success: true,
+ oldTab: oldTab,
+ newTab: this.currentTab,
+ navButtonsUpdated: navButtonsUpdated,
+ tabContentsUpdated: tabContentsUpdated,
+ tabContentUpdated: true
+ });
+ }
+
+ updateTabContent(tabName) {
+ if (!this.shouldUpdateUI()) {
+ return;
+ }
+
+ if (debugLogger) debugLogger.startStep('UIManager.updateTabContent', {
+ tabName: tabName,
+ currentTab: this.currentTab
+ });
+
+ let contentUpdated = false;
+ let updateError = null;
+
+ try {
+ switch(tabName) {
+ case 'dashboard':
+ // if (debugLogger) debugLogger.logStep('Updating dashboard tab content');
+ // Dashboard is the default view, no additional content to update
+ contentUpdated = true;
+ break;
+ case 'dungeons':
+ // if (debugLogger) debugLogger.logStep('Updating dungeons tab content');
+
+ // Ensure dungeon list is generated
+ if (window.game && window.game.systems && window.game.systems.dungeonSystem) {
+ // if (debugLogger) debugLogger.logStep('Ensuring dungeon list is generated...');
+ try {
+ window.game.systems.dungeonSystem.generateDungeonList();
+ } catch (error) {
+ if (debugLogger) debugLogger.errorEvent('UIManager.updateTabContent', error, {
+ tabName: tabName
+ });
+ }
+ }
+
+ // Only regenerate dungeon list if it's empty (first time load)
+ const dungeonListElement = document.getElementById('dungeonList');
+ if (dungeonListElement && dungeonListElement.children.length === 0) {
+ if (this.game && this.game.systems && this.game.systems.dungeonSystem) {
+ this.game.systems.dungeonSystem.generateDungeonList();
+ } else {
+ if (debugLogger) debugLogger.log('Dungeon system not available');
+ }
+ }
+ contentUpdated = true;
+ break;
+ case 'skills':
+ // if (debugLogger) debugLogger.logStep('Updating skills tab content');
+ if (this.game && this.game.systems && this.game.systems.skillSystem) {
+ this.game.systems.skillSystem.updateUI();
+ } else {
+ if (debugLogger) debugLogger.log('Skill system not available');
+ }
+ contentUpdated = true;
+ break;
+ case 'base':
+ // if (debugLogger) debugLogger.logStep('Updating base tab content');
+ if (this.game && this.game.systems && this.game.systems.baseSystem) {
+ this.game.systems.baseSystem.updateUI();
+ } else {
+ if (debugLogger) debugLogger.log('Base system not available');
+ }
+ contentUpdated = true;
+ break;
+ case 'quests':
+ // if (debugLogger) debugLogger.logStep('Updating quests tab content');
+ if (this.game && this.game.systems && this.game.systems.questSystem) {
+ this.game.systems.questSystem.updateUI();
+ } else {
+ if (debugLogger) debugLogger.log('Quest system not available');
+ }
+ contentUpdated = true;
+ break;
+ case 'inventory':
+ // if (debugLogger) debugLogger.logStep('Updating inventory tab content');
+ if (this.game && this.game.systems && this.game.systems.inventory) {
+ this.game.systems.inventory.updateUI();
+ } else {
+ if (debugLogger) debugLogger.log('Inventory system not available');
+ }
+ contentUpdated = true;
+ break;
+ case 'shop':
+ // if (debugLogger) debugLogger.logStep('Updating shop tab content');
+ if (this.game && this.game.systems && this.game.systems.economy) {
+ this.game.systems.economy.updateUI();
+ } else {
+ if (debugLogger) debugLogger.log('Economy system not available');
+ }
+ contentUpdated = true;
+ break;
+ case 'crafting':
+ // if (debugLogger) debugLogger.logStep('Updating crafting tab content');
+ if (this.game && this.game.systems && this.game.systems.crafting) {
+ this.game.systems.crafting.updateUI();
+ } else {
+ if (debugLogger) debugLogger.log('Crafting system not available');
+ }
+ contentUpdated = true;
+ break;
+ default:
+ // if (debugLogger) debugLogger.log('Unknown tab name, no content updated', {
+ // tabName: tabName
+ // });
+ contentUpdated = false;
+ }
+ } catch (error) {
+ updateError = error;
+ if (debugLogger) debugLogger.errorEvent('UIManager.updateTabContent', error, {
+ tabName: tabName
+ });
+ }
+
+ if (debugLogger) debugLogger.endStep('UIManager.updateTabContent', {
+ tabName: tabName,
+ contentUpdated: contentUpdated,
+ updateError: updateError ? updateError.message : null
+ });
+ }
+
+ // Category switching
+ switchSkillCategory(category) {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('UIManager.switchSkillCategory', {
+ category: category
+ });
+
+ const skillCatButtons = document.querySelectorAll('.skill-cat-btn');
+ let buttonsUpdated = 0;
+
+ skillCatButtons.forEach(btn => {
+ const wasActive = btn.classList.contains('active');
+ const shouldBeActive = btn.dataset.category === category;
+ btn.classList.toggle('active', shouldBeActive);
+
+ if (wasActive !== shouldBeActive) {
+ buttonsUpdated++;
+ }
+ });
+
+ if (debugLogger) debugLogger.logStep('Skill category buttons updated', {
+ totalButtons: skillCatButtons.length,
+ buttonsUpdated: buttonsUpdated
+ });
+
+ try {
+ this.game.systems.skillSystem.updateUI();
+
+ if (debugLogger) debugLogger.endStep('UIManager.switchSkillCategory', {
+ success: true,
+ category: category,
+ buttonsUpdated: buttonsUpdated,
+ skillUIUpdated: true
+ });
+ } catch (error) {
+ if (debugLogger) debugLogger.errorEvent('UIManager.switchSkillCategory', error, {
+ category: category
+ });
+
+ if (debugLogger) debugLogger.endStep('UIManager.switchSkillCategory', {
+ success: false,
+ category: category,
+ error: error.message
+ });
+ }
+ }
+
+ switchShopCategory(category) {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('UIManager.switchShopCategory', {
+ category: category
+ });
+
+ const shopCatButtons = document.querySelectorAll('.shop-cat-btn');
+ let buttonsUpdated = 0;
+
+ shopCatButtons.forEach(btn => {
+ const wasActive = btn.classList.contains('active');
+ const shouldBeActive = btn.dataset.category === category;
+ btn.classList.toggle('active', shouldBeActive);
+
+ if (wasActive !== shouldBeActive) {
+ buttonsUpdated++;
+ }
+ });
+
+ if (debugLogger) debugLogger.logStep('Shop category buttons updated', {
+ totalButtons: shopCatButtons.length,
+ buttonsUpdated: buttonsUpdated
+ });
+
+ try {
+ this.game.systems.economy.updateUI();
+
+ if (debugLogger) debugLogger.endStep('UIManager.switchShopCategory', {
+ success: true,
+ category: category,
+ buttonsUpdated: buttonsUpdated,
+ economyUIUpdated: true
+ });
+ } catch (error) {
+ if (debugLogger) debugLogger.errorEvent('UIManager.switchShopCategory', error, {
+ category: category
+ });
+
+ if (debugLogger) debugLogger.endStep('UIManager.switchShopCategory', {
+ success: false,
+ category: category,
+ error: error.message
+ });
+ }
+ }
+
+ switchQuestType(type) {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('UIManager.switchQuestType', {
+ type: type
+ });
+
+ const questTabButtons = document.querySelectorAll('.quest-tab-btn');
+ let buttonsUpdated = 0;
+
+ questTabButtons.forEach(btn => {
+ const wasActive = btn.classList.contains('active');
+ const shouldBeActive = btn.dataset.type === type;
+ btn.classList.toggle('active', shouldBeActive);
+
+ if (wasActive !== shouldBeActive) {
+ buttonsUpdated++;
+ }
+ });
+
+ if (debugLogger) debugLogger.logStep('Quest tab buttons updated', {
+ totalButtons: questTabButtons.length,
+ buttonsUpdated: buttonsUpdated
+ });
+
+ try {
+ this.game.systems.questSystem.updateUI();
+
+ if (debugLogger) debugLogger.endStep('UIManager.switchQuestType', {
+ success: true,
+ type: type,
+ buttonsUpdated: buttonsUpdated,
+ questUIUpdated: true
+ });
+ } catch (error) {
+ if (debugLogger) debugLogger.errorEvent('UIManager.switchQuestType', error, {
+ type: type
+ });
+
+ if (debugLogger) debugLogger.endStep('UIManager.switchQuestType', {
+ success: false,
+ type: type,
+ error: error.message
+ });
+ }
+ }
+
+ switchCraftingCategory(category) {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('UIManager.switchCraftingCategory', {
+ category: category
+ });
+
+ const craftingCatButtons = document.querySelectorAll('.crafting-cat-btn');
+ let buttonsUpdated = 0;
+
+ craftingCatButtons.forEach(btn => {
+ const wasActive = btn.classList.contains('active');
+ const shouldBeActive = btn.dataset.category === category;
+ btn.classList.toggle('active', shouldBeActive);
+
+ if (wasActive !== shouldBeActive) {
+ buttonsUpdated++;
+ }
+ });
+
+ if (debugLogger) debugLogger.logStep('Crafting category buttons updated', {
+ totalButtons: craftingCatButtons.length,
+ buttonsUpdated: buttonsUpdated
+ });
+
+ try {
+ this.game.systems.crafting.switchCategory(category);
+
+ if (debugLogger) debugLogger.endStep('UIManager.switchCraftingCategory', {
+ success: true,
+ category: category,
+ buttonsUpdated: buttonsUpdated,
+ craftingUIUpdated: true
+ });
+ } catch (error) {
+ if (debugLogger) debugLogger.errorEvent('UIManager.switchCraftingCategory', error, {
+ category: category
+ });
+
+ if (debugLogger) debugLogger.endStep('UIManager.switchCraftingCategory', {
+ success: false,
+ category: category,
+ error: error.message
+ });
+ }
+ }
+
+ // Modal management
+ showModal(title, content) {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('UIManager.showModal', {
+ title: title,
+ contentLength: content.length,
+ modalWasOpen: this.modalOpen
+ });
+
+ const modalOverlay = document.getElementById('modalOverlay');
+ const modalTitle = document.getElementById('modalTitle');
+ const modalBody = document.getElementById('modalBody');
+
+ if (!modalOverlay || !modalTitle || !modalBody) {
+ const missingElements = {
+ modalOverlay: !modalOverlay,
+ modalTitle: !modalTitle,
+ modalBody: !modalBody
+ };
+
+ if (debugLogger) debugLogger.endStep('UIManager.showModal', {
+ success: false,
+ reason: 'missing_modal_elements',
+ missingElements: missingElements
+ });
+
+ return;
+ }
+
+ const oldTitle = modalTitle.textContent;
+ const oldContent = modalBody.innerHTML;
+
+ modalTitle.textContent = title;
+ modalBody.innerHTML = content;
+
+ modalOverlay.classList.remove('hidden');
+ this.modalOpen = true;
+
+ if (debugLogger) debugLogger.endStep('UIManager.showModal', {
+ success: true,
+ title: title,
+ contentLength: content.length,
+ oldTitle: oldTitle,
+ oldContentLength: oldContent.length,
+ modalNowOpen: true
+ });
+ }
+
+ closeModal() {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('UIManager.closeModal', {
+ modalWasOpen: this.modalOpen
+ });
+
+ const modalOverlay = document.getElementById('modalOverlay');
+
+ if (modalOverlay) {
+ const wasHidden = modalOverlay.classList.contains('hidden');
+ modalOverlay.classList.add('hidden');
+
+ if (debugLogger) debugLogger.logStep('Modal overlay hidden', {
+ wasAlreadyHidden: wasHidden
+ });
+ } else {
+ if (debugLogger) debugLogger.log('Modal overlay element not found');
+ }
+
+ const wasModalOpen = this.modalOpen;
+ this.modalOpen = false;
+
+ if (debugLogger) debugLogger.endStep('UIManager.closeModal', {
+ success: true,
+ modalOverlayFound: !!modalOverlay,
+ wasModalOpen: wasModalOpen,
+ modalNowClosed: !this.modalOpen
+ });
+ }
+
+ // Force refresh all UI elements (called when server data is updated)
+ forceRefreshAllUI() {
+ if (window.smartSaveManager?.isMultiplayer) {
+ console.log('[UI MANAGER] Force refreshing all UI elements with server data');
+
+ // Update all resource displays
+ this.updateResourceDisplay();
+
+ // Update current tab content
+ if (this.currentTab) {
+ this.updateTabContent(this.currentTab);
+ }
+
+ // Update quest system
+ if (this.game && this.game.systems && this.game.systems.questSystem) {
+ this.game.systems.questSystem.updateUI();
+ }
+
+ console.log('[UI MANAGER] UI refresh completed');
+ }
+ }
+
+ // Handle UI update events from game engine
+ handleUIUpdate(data) {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('UIManager.handleUIUpdate', data);
+
+ if (debugLogger) debugLogger.log('Handling UI update event:', data);
+
+ switch (data.type) {
+ case 'full':
+ this.updateAllUI();
+ break;
+ case 'economy':
+ this.updateResourceDisplay();
+ break;
+ case 'player':
+ this.updateResourceDisplay();
+ break;
+ case 'quests':
+ if (this.game && this.game.systems && this.game.systems.questSystem) {
+ this.game.systems.questSystem.updateUI();
+ }
+ break;
+ case 'dailyCountdown':
+ if (this.game && this.game.systems && this.game.systems.questSystem) {
+ this.game.systems.questSystem.updateDailyCountdown();
+ }
+ break;
+ case 'weeklyCountdown':
+ if (this.game && this.game.systems && this.game.systems.questSystem) {
+ this.game.systems.questSystem.updateWeeklyCountdown();
+ }
+ break;
+ default:
+ if (debugLogger) debugLogger.log('Unknown update type:', data.type);
+ }
+
+ if (debugLogger) debugLogger.endStep('UIManager.handleUIUpdate', {
+ type: data.type,
+ handled: true
+ });
+ }
+
+ updateAllUI() {
+ if (!this.shouldUpdateUI()) {
+ return;
+ }
+
+ // if (debugLogger) debugLogger.log('Updating all UI elements');
+
+ this.updateResourceDisplay();
+
+ // Update other UI components as needed
+ if (this.game && this.game.systems) {
+ // Update quest system
+ if (this.game.systems.questSystem) {
+ this.game.systems.questSystem.updateUI();
+ }
+
+ // Update daily countdown
+ if (this.game.systems.questSystem) {
+ this.game.systems.questSystem.updateDailyCountdown();
+ this.game.systems.questSystem.updateWeeklyCountdown();
+ }
+ }
+
+ // if (debugLogger) debugLogger.log('All UI elements updated');
+ }
+
+ // Resource display
+ setupResourceDisplay() {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('UIManager.setupResourceDisplay');
+
+ // Resource display is handled in updateResourceDisplay method
+
+ if (debugLogger) debugLogger.endStep('UIManager.setupResourceDisplay', {
+ setupCompleted: true
+ });
+ }
+
+ // Centralized UI update control - blocks all UI updates when not in multiplayer mode
+ shouldUpdateUI() {
+ const shouldUpdate = window.smartSaveManager?.isMultiplayer || this.game?.isRunning;
+
+ if (!shouldUpdate) {
+ console.log('[UI MANAGER] Blocking UI update - not in multiplayer mode');
+ return false;
+ }
+
+ return true;
+ }
+
+ // Centralized update resource display - all calls should go through this
+ updateResourceDisplay() {
+ if (!this.shouldUpdateUI()) {
+ return;
+ }
+
+ // In multiplayer mode, ensure we're using server data
+ if (window.smartSaveManager?.isMultiplayer) {
+ console.log('[UI MANAGER] Updating resource display in multiplayer mode - using server data');
+ }
+
+ const debugLogger = window.debugLogger;
+
+ try {
+ // Safety checks - return early if systems aren't available
+ if (!this.game || !this.game.systems) {
+ if (debugLogger) debugLogger.log('Game systems not available, skipping resource display update');
+ return;
+ }
+
+ if (!this.game.systems.player || !this.game.systems.economy) {
+ if (debugLogger) debugLogger.log('Player or economy system not available, skipping resource display update');
+ return;
+ }
+
+ const player = this.game.systems.player;
+ const economy = this.game.systems.economy;
+
+ // Additional safety checks for player properties
+ if (!player.stats || !player.attributes || !player.ship) {
+ if (debugLogger) debugLogger.log('Player properties not fully initialized, skipping resource display update');
+ return;
+ }
+
+ if (debugLogger) debugLogger.startStep('UIManager.updateResourceDisplay', {
+ gameSystemsAvailable: !!(this.game && this.game.systems),
+ playerSystemAvailable: !!(this.game && this.game.systems && this.game.systems.player),
+ economySystemAvailable: !!(this.game && this.game.systems && this.game.systems.economy),
+ playerStatsAvailable: !!player.stats,
+ playerAttributesAvailable: !!player.attributes,
+ playerShipAvailable: !!player.ship
+ });
+
+ let elementsUpdated = 0;
+ let elementsNotFound = 0;
+
+ // Update player level with safety checks
+ const playerLevelElement = document.getElementById('playerLevel');
+ if (playerLevelElement) {
+ const oldLevel = playerLevelElement.textContent;
+ const playerLevel = player.stats.level || 1;
+ console.log('[UI MANAGER] Updating player level:', { oldLevel, newLevel: playerLevel, playerStats: player.stats });
+ playerLevelElement.textContent = `Lv. ${playerLevel}`;
+ elementsUpdated++;
+
+ if (debugLogger) debugLogger.logStep('Player level updated', {
+ oldLevel: oldLevel,
+ newLevel: playerLevelElement.textContent,
+ playerStatsLevel: player.stats.level
+ });
+ } else {
+ elementsNotFound++;
+ if (debugLogger) debugLogger.log('Player level element not found');
+ }
+
+ // Update ship level with safety checks
+ const shipLevelElement = document.getElementById('currentShipLevel');
+ if (shipLevelElement) {
+ const oldShipLevel = shipLevelElement.textContent;
+ const shipLevel = player.ship.level || player.stats.level || 1;
+ shipLevelElement.textContent = shipLevel;
+ elementsUpdated++;
+
+ if (debugLogger) debugLogger.logStep('Ship level updated', {
+ oldShipLevel: oldShipLevel,
+ newShipLevel: shipLevelElement.textContent,
+ playerShipLevel: player.ship.level,
+ playerStatsLevel: player.stats.level
+ });
+ } else {
+ elementsNotFound++;
+ if (debugLogger) debugLogger.log('Ship level element not found');
+ }
+
+ // Update credits with safety checks
+ const creditsElement = document.getElementById('credits');
+ if (creditsElement) {
+ const oldCredits = creditsElement.textContent;
+ const creditsAmount = economy.credits || 0;
+ console.log('[UI MANAGER] Updating credits:', { oldCredits, newCredits: creditsAmount, economyCredits: economy.credits, economySystem: !!economy });
+ creditsElement.textContent = this.game.formatNumber(creditsAmount);
+ elementsUpdated++;
+
+ if (debugLogger) debugLogger.logStep('Credits updated', {
+ oldCredits: oldCredits,
+ newCredits: creditsElement.textContent,
+ economyCredits: economy.credits,
+ formattedCredits: this.game.formatNumber(creditsAmount)
+ });
+ } else {
+ elementsNotFound++;
+ if (debugLogger) debugLogger.log('Credits element not found');
+ }
+
+ // Update gems with safety checks
+ const gemsElement = document.getElementById('gems');
+ if (gemsElement) {
+ const oldGems = gemsElement.textContent;
+ const gemsAmount = economy.gems || 0;
+ gemsElement.textContent = this.game.formatNumber(gemsAmount);
+ elementsUpdated++;
+
+ if (debugLogger) debugLogger.logStep('Gems updated', {
+ oldGems: oldGems,
+ newGems: gemsElement.textContent,
+ economyGems: economy.gems,
+ formattedGems: this.game.formatNumber(gemsAmount)
+ });
+ } else {
+ elementsNotFound++;
+ if (debugLogger) debugLogger.log('Gems element not found');
+ }
+
+ // Update energy with safety checks
+ const energyElement = document.getElementById('energy');
+ if (energyElement) {
+ const oldEnergy = energyElement.textContent;
+ // Ensure we're using the correct energy values, not credits
+ const currentEnergy = Math.floor(player.attributes.energy || 0);
+ const maxEnergy = Math.floor(player.attributes.maxEnergy || 100);
+ energyElement.textContent = `${currentEnergy}/${maxEnergy}`;
+ elementsUpdated++;
+
+ if (debugLogger) debugLogger.logStep('Energy updated', {
+ oldEnergy: oldEnergy,
+ newEnergy: energyElement.textContent,
+ currentEnergy: currentEnergy,
+ maxEnergy: maxEnergy,
+ playerEnergy: player.attributes.energy,
+ playerMaxEnergy: player.attributes.maxEnergy
+ });
+ } else {
+ elementsNotFound++;
+ if (debugLogger) debugLogger.log('Energy element not found');
+ }
+
+ if (debugLogger) debugLogger.endStep('UIManager.updateResourceDisplay', {
+ elementsUpdated: elementsUpdated,
+ elementsNotFound: elementsNotFound,
+ playerLevel: player.stats.level || 1,
+ shipLevel: player.ship.level || player.stats.level || 1,
+ credits: economy.credits || 0,
+ gems: economy.gems || 0,
+ currentEnergy: Math.floor(player.attributes.energy || 0),
+ maxEnergy: Math.floor(player.attributes.maxEnergy || 100)
+ });
+ } catch (error) {
+ if (debugLogger) debugLogger.log('Error in updateResourceDisplay', {
+ error: error.message,
+ stack: error.stack
+ });
+ }
+ }
+
+ updateUI() {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('UIManager.updateUI', {
+ currentTab: this.currentTab,
+ modalOpen: this.modalOpen
+ });
+
+ // Update resource display only if in multiplayer mode or game is actively running
+ if (this.shouldUpdateUI()) {
+ if (debugLogger) debugLogger.logStep('Updating resource display');
+ this.updateResourceDisplay();
+ }
+
+ // Update tab content only if in multiplayer mode or game is actively running
+ if (this.shouldUpdateUI()) {
+ if (debugLogger) debugLogger.logStep('Updating tab content', {
+ currentTab: this.currentTab
+ });
+ this.updateTabContent(this.currentTab);
+ }
+
+ if (debugLogger) debugLogger.endStep('UIManager.updateUI', {
+ resourceDisplayUpdated: true,
+ tabContentUpdated: true,
+ currentTab: this.currentTab
+ });
+ }
+
+ // Menus
+ showSettingsMenu() {
+
+ // Clear any existing modal content first
+ const modalBody = document.getElementById('modalBody');
+ if (modalBody) {
+ modalBody.innerHTML = '';
+ }
+
+ // Get current auto-save setting
+ const currentAutoSaveInterval = localStorage.getItem('autoSaveInterval') || '5';
+
+ let content = '