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 +
+
+ + + +
+
+ +
+ + + + + + + + + + + +
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ Developer Console + +
+
+
+
+ +
+
+
+ + 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 +
+
+ + + +
+
+ +
+ + + + + + + + + + + +
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ Developer Console + +
+
+
+
+ +
+
+
+ + 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.name}
+
${item.description}
+ ${item.stats ? ` +
+ ${Object.entries(item.stats).map(([stat, value]) => + `
${stat}: ${value}
` + ).join('')} +
+ ` : ''} +
${this.formatPrice(item)}
+
${item.rarity}
+
+
+ + `; + + 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 ? `` : ''} + ${item.consumable ? `` : ''} +
+ `; + } + + 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 `Missing texture`; + } + + 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 = ` +
+

${ship.name}

+ ${ship.isCurrent ? 'Current' : ''} +
+
+
Class: ${ship.class}
+
Level: ${ship.level}
+
+
+
Attack: ${ship.stats.attack || 0}
+
Speed: ${ship.stats.speed || 0}
+
Defense: ${ship.stats.defense || 0}
+
+ ${!ship.isCurrent ? ` + + ` : ''} + `; + + 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 = ` +
+

${starbase.name}

+ ${starbase.isMain ? 'Main Base' : ''} +
+
+
Type: ${starbase.type}
+
Level: ${starbase.level}
+
Location: ${starbase.location}
+
+
+ + ${!starbase.isMain ? ` + + ` : ''} +
+ `; + + 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.name}

+
+ +
+
+
+ ${starbase.description} +
+
+
Benefits:
+
    + ${starbase.benefits.map(benefit => `
  • ${benefit}
  • `).join('')} +
+
+
+
+ Cost: ${starbase.price} ${starbase.currency} +
+
+ Required Level: ${starbase.requiredLevel} +
+
+
+ +
+ `; + + 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
+ + `; + + 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 += ` +
+
+ + ${template.name} +
+
+
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
+ + `; + + 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 += ` +
+
+ + ${template.name} +
+
+
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.name}

+ Level ${requirementsText} +
+
${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 +
+
+ + +
+ `; + } + + 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.currentDungeon.name}

+
+
+
+
+ Room ${this.currentDungeon.currentRoomIndex + 1} / ${this.currentDungeon.rooms.length} +
+
+ +
+ ${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 '

Treasure awaits!

'; + } + + return '

The room is empty and quiet.

'; + } + + getRoomActions(room) { + + if (room.completed) { + if (this.currentDungeon.currentRoomIndex < this.currentDungeon.rooms.length - 1) { + return ''; + } else { + return ''; + } + } + + if (room.enemies.length > 0) { + const buttonHtml = ''; + return buttonHtml; + } + + if (room.rewards) { + return ''; + } + + // Check if this is the final room + if (this.currentDungeon.currentRoomIndex >= this.currentDungeon.rooms.length - 1) { + return ''; + } + + return ''; + } + 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.name}
+
+ ${difficultyIndicator} +
+ ${rewardsHtml} +
+
+
+
${quest.description}
+
+ ${objectivesHtml} +
+ ${completionTime} +
+
+
+
+ ${isCompleted ? 'Completed' : Math.round(progressPercent) + '% Complete'} +
+
+ ${quest.status === 'available' ? + `` : + quest.status === 'active' ? + `
+ + +
` : + quest.status === 'completed' ? + 'Completed' : + quest.status === 'failed' ? + `
+ Failed: ${quest.failureReason || 'Cancelled'} + +
` : + '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.name} +
+
${ship.rarity}
+
+
+
+ +
+ `; + + 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.name}
+
Lv. ${skill.currentLevel}/${skill.maxLevel}
+
+
+
${skill.description}
+ ${skill.currentLevel > 0 && skill.currentLevel < skill.maxLevel ? ` +
+
+
+
+ ${skill.experience}/${skill.experienceToNext} XP +
+ ` : skill.currentLevel >= skill.maxLevel ? ` +
+ MAX LEVEL +
+ ` : ''} +
+ ${!skill.unlocked ? ` + + ` : skill.currentLevel < skill.maxLevel ? ` + + ` : ` + 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 = ` + + `; + + // 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 += ''; + } else { + 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 += ''; + content += ''; + } else { + content += ''; + content += ''; + } + + 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 = '
'; + content += '

Game Settings

'; + content += '
'; + content += '

Auto-Save Settings

'; + content += '

Configure how often your game automatically saves:

'; + + content += '
'; + content += ''; + content += ''; + content += '
'; + + content += '
'; + content += ''; + content += '
'; + content += '
'; + + content += '
'; + content += '

Reset Options

'; + content += '
'; + content += '

Choose your reset option:

'; + content += '
'; + content += '
'; + + content += ` +
+

Reset Character

+

Reset your character to level 1 and clear progress, but keep your inventory items.

+
    +
  • Character level and stats reset
  • +
  • Skills and abilities reset
  • +
  • Quest progress cleared
  • +
  • Economy reset to starting values
  • +
  • Inventory items preserved
  • +
+ +
+ +
+

Hard Reset

+

Completely clear ALL saved data including inventory, settings, and local storage.

+
    +
  • Character progress and stats
  • +
  • Inventory and items
  • +
  • Quest progress
  • +
  • Settings and preferences
  • +
  • All local application data
  • +
+ +
+ `; + + content += '
'; + content += '
'; + content += ''; + content += '
'; + content += '
'; + + // Force modal update with timestamp to prevent caching + const timestamp = Date.now(); + this.showModal('Settings', content + ``); + } + + saveAutoSaveSettings() { + const autoSaveInterval = document.getElementById('autoSaveInterval'); + if (autoSaveInterval) { + const selectedInterval = autoSaveInterval.value; + localStorage.setItem('autoSaveInterval', selectedInterval); + + // Update game engine auto-save if game is running + if (window.game && window.game.isRunning) { + window.game.updateAutoSaveInterval(parseInt(selectedInterval)); + } + + this.game.showNotification(`Auto-save interval set to ${selectedInterval} minute(s)`, 'success', 3000); + } + this.closeModal(); + } + + confirmResetCharacter() { + + let content = '
'; + content += '

Reset Character

'; + content += '

Warning: This will reset your character to level 1 and clear all progress, but will keep your settings and preferences.

'; + content += '
    '; + content += '
  • Character level and stats reset
  • '; + content += '
  • Skills and abilities reset
  • '; + content += '
  • Quest progress cleared
  • '; + content += '
  • Economy reset to starting values
  • '; + content += '
  • Inventory items preserved
  • '; + content += '
'; + content += '
'; + content += ''; + content += ''; + content += '
'; + content += '
'; + + this.showModal('Confirm Reset', content); + } + + confirmHardReset() { + let content = '
'; + content += '

Hard Reset - Clear All Data

'; + content += '

Warning: This will completely clear ALL saved data including:

'; + content += '
    '; + content += '
  • Character progress and stats
  • '; + content += '
  • Inventory and items
  • '; + content += '
  • Quest progress
  • '; + content += '
  • Settings and preferences
  • '; + content += '
  • All local application data
  • '; + content += '
'; + content += '

This will completely reset the application to its initial state. This action cannot be undone!

'; + content += '
'; + content += ''; + content += ''; + content += '
'; + content += '
'; + + this.showModal('Confirm Hard Reset', content); + } + + resetCharacter() { + + // Reset character data only + if (this.game && this.game.systems) { + + // Reset player to initial state + if (this.game.systems.player) { + this.game.systems.player.resetToLevel1(); + } else { + } + + // Reset economy + if (this.game.systems.economy) { + this.game.systems.economy.credits = 1000; + this.game.systems.economy.gems = 10; + } + + // Keep inventory intact for soft reset (only reset max slots if needed) + if (this.game.systems.inventory) { + // Preserve items, only reset max slots to default + if (this.game.systems.inventory.maxSlots !== 20) { + this.game.systems.inventory.maxSlots = 20; + } + } + + // Clear quest progress + if (this.game.systems.questSystem) { + this.game.systems.questSystem.mainQuests.forEach(quest => { + quest.status = quest.id === 'tutorial_complete' ? 'available' : 'locked'; + quest.objectives.forEach(obj => obj.current = 0); + }); + this.game.systems.questSystem.activeQuests = []; + this.game.systems.questSystem.completedQuests = []; + this.game.systems.questSystem.failedQuests = []; + } + + // Clear dungeon progress + if (this.game.systems.dungeonSystem) { + this.game.systems.dungeonSystem.stats = { + dungeonsAttempted: 0, + dungeonsCompleted: 0, + totalEnemiesDefeated: 0, + bestTime: Infinity, + totalLootEarned: 0 + }; + this.game.systems.dungeonSystem.currentDungeon = null; + this.game.systems.dungeonSystem.currentRoom = null; + this.game.systems.dungeonSystem.isExploring = false; + } + + // Reset skills + if (this.game.systems.skillSystem) { + this.game.systems.skillSystem.skills = {}; + } + + // Force UI update after reset + if (this.game.systems.player) { + this.game.systems.player.updateUI(); + } + + // Save the reset state + this.game.saveGame(); + + this.game.showNotification('Character reset successfully!', 'success', 5000); + this.closeModal(); + + // Clear any modal content to prevent old panel showing + const modalBody = document.getElementById('modalBody'); + if (modalBody) { + modalBody.innerHTML = ''; + } + + // Force close any open modals + const modalOverlay = document.getElementById('modalOverlay'); + if (modalOverlay) { + modalOverlay.classList.add('hidden'); + } + + // Reload the page to refresh UI + setTimeout(() => { + window.location.reload(); + }, 1000); + } + } + + hardReset() { + + // Force cache busting check + const version = Date.now(); + + // Clear in-memory game data first + if (this.game && this.game.systems) { + + // Clear inventory data + if (this.game.systems.inventory) { + this.game.systems.inventory.items = []; + this.game.systems.inventory.equipment = { + weapon: null, + armor: null, + engine: null, + shield: null, + accessory: null + }; + } + + // Clear player data + if (this.game.systems.player) { + this.game.systems.player.stats = { + level: 1, + experience: 0, + experienceToNext: 100, + skillPoints: 0, + totalKills: 0, + dungeonsCleared: 0, + playTime: 0, + lastLogin: Date.now(), + tutorialDungeonCompleted: false + }; + this.game.systems.player.attributes = { + health: 100, + maxHealth: 100, + energy: 100, + maxEnergy: 100, + attack: 10, + defense: 5, + speed: 10, + criticalChance: 0.05, + criticalDamage: 1.5 + }; + } + + // Clear economy data + if (this.game.systems.economy) { + this.game.systems.economy.credits = 1000; + this.game.systems.economy.gems = 10; + } + + // Clear quest progress including dailies + if (this.game.systems.questSystem) { + try { + // Reset main quests + if (this.game.systems.questSystem.mainQuests && Array.isArray(this.game.systems.questSystem.mainQuests)) { + this.game.systems.questSystem.mainQuests.forEach(quest => { + if (quest && quest.objectives) { + quest.status = quest.id === 'tutorial_complete' ? 'available' : 'locked'; + quest.objectives.forEach(obj => obj.current = 0); + } + }); + } + + // Reset daily quests - force all to available regardless of current status + if (this.game.systems.questSystem.dailyQuests && Array.isArray(this.game.systems.questSystem.dailyQuests)) { + this.game.systems.questSystem.dailyQuests.forEach(quest => { + if (quest && quest.objectives) { + quest.status = 'available'; + quest.objectives.forEach(obj => obj.current = 0); + } + }); + } + + // Reset procedural quests + if (this.game.systems.questSystem.proceduralQuests && Array.isArray(this.game.systems.questSystem.proceduralQuests)) { + this.game.systems.questSystem.proceduralQuests.forEach(quest => { + if (quest && quest.objectives) { + quest.status = 'available'; + quest.objectives.forEach(obj => obj.current = 0); + } + }); + } + } catch (error) { + // Continue with reset even if quest clearing fails + } + + // Force clear all quest tracking arrays + this.game.systems.questSystem.activeQuests = []; + this.game.systems.questSystem.completedQuests = []; + this.game.systems.questSystem.failedQuests = []; + + // Clear any quest timers or intervals + if (this.game.systems.questSystem.dailyCountdownInterval) { + clearInterval(this.game.systems.questSystem.dailyCountdownInterval); + this.game.systems.questSystem.dailyCountdownInterval = null; + } + + if (this.game.systems.questSystem.weeklyCountdownInterval) { + clearInterval(this.game.systems.questSystem.weeklyCountdownInterval); + this.game.systems.questSystem.weeklyCountdownInterval = null; + } + + // Reset quest statistics + this.game.systems.questSystem.stats = { + questsCompleted: 0, + dailyQuestsCompleted: 0, + totalRewardsEarned: { credits: 0, experience: 0, gems: 0 }, + lastDailyReset: Date.now() + }; + + // Force quest availability check to ensure clean state + this.game.systems.questSystem.checkQuestAvailability(); + } + } + + // Clear ALL browser storage data + localStorage.clear(); + sessionStorage.clear(); + + // Clear any IndexedDB data if present + if (window.indexedDB) { + const databases = ['galaxy_strike_online', 'game_data', 'player_data']; + databases.forEach(dbName => { + indexedDB.deleteDatabase(dbName); + }); + } + + // Clear any cookies for this domain + document.cookie.split(";").forEach(function(c) { + document.cookie = c.replace(/^ +/, "").replace(/=.*/, "=;expires=" + new Date().toUTCString() + ";path=/"); + }); + + // Clear service workers if present (with proper error handling) + if ('serviceWorker' in navigator) { + try { + navigator.serviceWorker.getRegistrations().then(function(registrations) { + registrations.forEach(function(registration) { + try { + registration.unregister(); + } catch (error) { + } + }); + }).catch(function(error) { + }); + } catch (error) { + } + } + + // Clear application cache if present (with fallback for older browsers) + if ('caches' in window) { + try { + caches.keys().then(function(cacheNames) { + return Promise.all(cacheNames.map(function(cacheName) { + return caches.delete(cacheName); + })); + }).then(function() { + }).catch(function(error) { + }); + } catch (error) { + } + } + + // Force garbage collection to clear RAM cache + if (window.gc) { + window.gc(); + } + + // Clear any global variables that might hold game data + if (window.game) { + window.game = null; + } + + this.game.showNotification('All data cleared successfully! The page will reload...', 'success', 5000); + this.closeModal(); + + // Force complete page reload with aggressive cache busting + setTimeout(() => { + // Add multiple cache-busting parameters + const timestamp = Date.now(); + const random = Math.random().toString(36).substring(7); + window.location.href = window.location.origin + window.location.pathname + '?t=' + timestamp + '&r=' + random + '&force=1'; + }, 1000); + } + + showDiscordIntegration() { + let content = '
'; + content += '

Discord Integration

'; + content += '

Connect your game to Discord for real-time updates and notifications!

'; + + content += '
'; + content += '

Features:

'; + content += '
    '; + content += '
  • Real-time achievement notifications
  • '; + content += '
  • Dungeon completion alerts
  • '; + content += '
  • Level up notifications
  • '; + content += '
  • Daily quest reminders
  • '; + content += '
  • Statistics tracking
  • '; + content += '
'; + content += '
'; + + content += '
'; + content += '

Setup Instructions:

'; + content += '
    '; + content += '
  1. Create a Discord server or use an existing one
  2. '; + content += '
  3. Create a webhook URL in server settings
  4. '; + content += '
  5. Enter the webhook URL below
  6. '; + content += '
  7. Test the connection
  8. '; + content += '
'; + content += '
'; + + content += '
'; + content += ''; + content += ''; + content += ''; + content += '
'; + + content += '
'; + + this.showModal('Discord Integration', content); + } + + testDiscordWebhook() { + const urlInput = document.getElementById('discordWebhookUrl'); + const webhookUrl = urlInput?.value; + + if (!webhookUrl) { + this.game.showNotification('Please enter a webhook URL', 'error', 3000); + return; + } + + // Test webhook (would need actual webhook implementation) + this.game.showNotification('Testing Discord webhook...', 'info', 2000); + + // Simulate webhook test + setTimeout(() => { + this.game.showNotification('Discord webhook test successful!', 'success', 3000); + }, 2000); + } + + saveDiscordWebhook() { + const urlInput = document.getElementById('discordWebhookUrl'); + const webhookUrl = urlInput?.value; + + if (!webhookUrl) { + this.game.showNotification('Please enter a webhook URL', 'error', 3000); + return; + } + + // Save webhook URL + localStorage.setItem('discordWebhookUrl', webhookUrl); + this.game.systems.player.settings.discordIntegration = true; + + this.game.showNotification('Discord integration saved!', 'success', 3000); + this.closeModal(); + } + + // Notification display + showNotification(message, type = 'info', duration = 3000) { + const notification = document.createElement('div'); + notification.className = `notification ${type}`; + notification.textContent = message; + + // Position notification + notification.style.top = `${20 + (this.notifications.length * 70)}px`; + + document.body.appendChild(notification); + this.notifications.push(notification); + + // Auto remove + setTimeout(() => { + this.removeNotification(notification); + }, duration); + + // Manual close on click + notification.addEventListener('click', () => { + this.removeNotification(notification); + }); + } + + removeNotification(notification) { + const index = this.notifications.indexOf(notification); + if (index > -1) { + this.notifications.splice(index, 1); + } + + notification.style.opacity = '0'; + notification.style.transform = 'translateX(100%)'; + + setTimeout(() => { + if (notification.parentNode) { + notification.parentNode.removeChild(notification); + } + + // Reposition remaining notifications + this.repositionNotifications(); + }, 300); + } + + repositionNotifications() { + this.notifications.forEach((notification, index) => { + notification.style.top = `${20 + (index * 70)}px`; + }); + } + + // Loading states + showLoading(elementId, message = 'Loading...') { + const element = document.getElementById(elementId); + if (element) { + element.innerHTML = ` +
+
+

${message}

+
+ `; + } + } + + hideLoading(elementId, content) { + const element = document.getElementById(elementId); + if (element) { + element.innerHTML = content || ''; + } + } + + checkAndStartTutorial() { + const debugLogger = window.debugLogger; + + if (debugLogger) debugLogger.startStep('UIManager.checkAndStartTutorial'); + + try { + // Check if First Steps quest is active and tutorial dungeon hasn't been completed + const questSystem = this.game.systems.questSystem; + const player = this.game.systems.player; + + if (!questSystem || !player) { + if (debugLogger) debugLogger.logStep('Quest system or player not available'); + return; + } + + const firstStepsQuest = questSystem.findQuest('tutorial_complete'); + const tutorialCompleted = player.stats.tutorialDungeonCompleted; + + if (debugLogger) debugLogger.logStep('Checking tutorial conditions', { + firstStepsQuestFound: !!firstStepsQuest, + questStatus: firstStepsQuest ? firstStepsQuest.status : 'not_found', + tutorialCompleted: tutorialCompleted + }); + + // Start tutorial dungeon if: + // 1. First Steps quest exists and is active + // 2. Tutorial dungeon hasn't been completed + if (firstStepsQuest && + firstStepsQuest.status === 'active' && + !tutorialCompleted) { + + if (debugLogger) debugLogger.logStep('Auto-starting tutorial dungeon'); + + // Switch to dungeons tab + this.switchTab('dungeons'); + + // Start tutorial dungeon after a short delay to let UI update + setTimeout(() => { + if (this.game.systems.dungeonSystem) { + this.game.systems.dungeonSystem.startTutorialDungeon(); + } + }, 1000); + } + + if (debugLogger) debugLogger.endStep('UIManager.checkAndStartTutorial'); + } catch (error) { + if (debugLogger) debugLogger.errorEvent(error, 'UIManager.checkAndStartTutorial'); + } + } + + // Notification system + showNotification(message, type = 'info', duration = 3000) { + const notification = document.createElement('div'); + notification.className = `notification notification-${type}`; + notification.textContent = message; + + document.body.appendChild(notification); + this.notifications.push(notification); + + // Auto-remove notification after duration + setTimeout(() => { + this.removeNotification(notification); + }, duration); + } + + removeNotification(notification) { + const index = this.notifications.indexOf(notification); + if (index > -1) { + this.notifications.splice(index, 1); + } + + if (notification.parentNode) { + notification.parentNode.removeChild(notification); + } + } + + // Utility methods + formatNumber(num) { + return this.game.formatNumber(num); + } + + formatTime(milliseconds) { + return this.game.formatTime(milliseconds); + } + + createProgressBar(current, max, showText = true) { + const percent = Math.min(100, (current / max) * 100); + const text = showText ? `${current}/${max}` : ''; + + return ` +
+
+ ${text} +
+ `; + } + + createButton(text, className = 'btn-primary', onClick = '', disabled = false) { + return ` + + `; + } +} + +// Export UIManager to global scope +if (typeof window !== 'undefined') { + window.UIManager = UIManager; +} \ No newline at end of file diff --git a/Client-Server/package-lock.json b/Client-Server/package-lock.json new file mode 100644 index 0000000..5dfbfba --- /dev/null +++ b/Client-Server/package-lock.json @@ -0,0 +1,4688 @@ +{ + "name": "galaxystrikeonline", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "galaxystrikeonline", + "version": "1.0.0", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "cors": "^2.8.6", + "express": "^4.22.1", + "socket.io": "^4.8.3" + }, + "devDependencies": { + "cross-env": "^7.0.3", + "electron": "^40.0.0", + "electron-builder": "^23.0.6" + } + }, + "node_modules/@develar/schema-utils": { + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/@develar/schema-utils/-/schema-utils-2.6.5.tgz", + "integrity": "sha512-0cp4PsWQ/9avqTVMCtZ+GirikIA36ikvjtHweU4/j8yLtgObI0+JUPhYFScgwlteveGB1rt3Cm8UhN04XayDig==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.0", + "ajv-keywords": "^3.4.1" + }, + "engines": { + "node": ">= 8.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/@electron/get": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@electron/get/-/get-2.0.3.tgz", + "integrity": "sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "env-paths": "^2.2.0", + "fs-extra": "^8.1.0", + "got": "^11.8.5", + "progress": "^2.0.3", + "semver": "^6.2.0", + "sumchecker": "^3.0.1" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "global-agent": "^3.0.0" + } + }, + "node_modules/@electron/universal": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@electron/universal/-/universal-1.2.1.tgz", + "integrity": "sha512-7323HyMh7KBAl/nPDppdLsC87G6RwRU02dy5FPeGB1eS7rUePh55+WNWiDPLhFQqqVPHzh77M69uhmoT8XnwMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@malept/cross-spawn-promise": "^1.1.0", + "asar": "^3.1.0", + "debug": "^4.3.1", + "dir-compare": "^2.4.0", + "fs-extra": "^9.0.1", + "minimatch": "^3.0.4", + "plist": "^3.0.4" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/@electron/universal/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@electron/universal/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@electron/universal/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@malept/cross-spawn-promise": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@malept/cross-spawn-promise/-/cross-spawn-promise-1.1.1.tgz", + "integrity": "sha512-RTBGWL5FWQcg9orDOCcp4LvItNzUPcyEU9bwaeJX0rJ1IQxzucC48Y0/sQLp/g6t99IQgAlGIaesJS+gTn7tVQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/malept" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/subscription/pkg/npm-.malept-cross-spawn-promise?utm_medium=referral&utm_source=npm_fund" + } + ], + "license": "Apache-2.0", + "dependencies": { + "cross-spawn": "^7.0.1" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@malept/flatpak-bundler": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@malept/flatpak-bundler/-/flatpak-bundler-0.4.0.tgz", + "integrity": "sha512-9QOtNffcOF/c1seMCDnjckb3R9WHcG34tky+FHpNKKCW0wc/scYLwMtO+ptyGUfMW0/b/n4qRiALlaFHc9Oj7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "fs-extra": "^9.0.0", + "lodash": "^4.17.15", + "tmp-promise": "^3.0.2" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@malept/flatpak-bundler/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@malept/flatpak-bundler/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@malept/flatpak-bundler/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", + "license": "MIT" + }, + "node_modules/@szmarczak/http-timer": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", + "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", + "dev": true, + "license": "MIT", + "dependencies": { + "defer-to-connect": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/@types/cacheable-request": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", + "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-cache-semantics": "*", + "@types/keyv": "^3.1.4", + "@types/node": "*", + "@types/responselike": "^1.0.0" + } + }, + "node_modules/@types/cors": { + "version": "2.8.19", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", + "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/fs-extra": { + "version": "9.0.13", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz", + "integrity": "sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@types/minimatch": "*", + "@types/node": "*" + } + }, + "node_modules/@types/http-cache-semantics": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", + "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/keyv": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", + "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/minimatch": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", + "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.10.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.9.tgz", + "integrity": "sha512-ne4A0IpG3+2ETuREInjPNhUGis1SFjv1d5asp8MzEAGtOZeTeHVDOYqOgqfhvseqg/iXty2hjBf1zAOb7RNiNw==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/@types/plist": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/plist/-/plist-3.0.5.tgz", + "integrity": "sha512-E6OCaRmAe4WDmWNsL/9RMqdkkzDCY1etutkflWk4c+AcjDU07Pcz1fQwTX0TQz+Pxqn9i4L1TU3UFpjnrcDgxA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@types/node": "*", + "xmlbuilder": ">=11.0.1" + } + }, + "node_modules/@types/responselike": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", + "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/verror": { + "version": "1.10.11", + "resolved": "https://registry.npmjs.org/@types/verror/-/verror-1.10.11.tgz", + "integrity": "sha512-RlDm9K7+o5stv0Co8i8ZRGxDbrTxhJtgjqjFyVh/tXQyl/rYtTKlnTvZ88oSTeYREWurwx20Js4kTuKCsFkUtg==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/@types/yargs": { + "version": "17.0.35", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", + "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/yauzl": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@xmldom/xmldom": { + "version": "0.8.11", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.11.tgz", + "integrity": "sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/7zip-bin": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/7zip-bin/-/7zip-bin-5.1.1.tgz", + "integrity": "sha512-sAP4LldeWNz0lNzmTird3uWfFDWWTeg6V/MsmyyLR9X1idwKBWIgt/ZvinqQldJm3LecKEs1emkbquO6PCiLVQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/ansi-align": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", + "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.1.0" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/app-builder-bin": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/app-builder-bin/-/app-builder-bin-4.0.0.tgz", + "integrity": "sha512-xwdG0FJPQMe0M0UA4Tz0zEB8rBJTRA5a476ZawAqiBkMv16GRK5xpXThOjMaEOFnZ6zabejjG4J3da0SXG63KA==", + "dev": true, + "license": "MIT" + }, + "node_modules/app-builder-lib": { + "version": "23.0.6", + "resolved": "https://registry.npmjs.org/app-builder-lib/-/app-builder-lib-23.0.6.tgz", + "integrity": "sha512-CmDyNldqqt7hMNV3EaHCPeml4iCqBZPXBOEq0M1j9KkBHPMXnQzoYECq2IQ3xv4PxADEqMeVAt/W2iAXBy4v5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@develar/schema-utils": "~2.6.5", + "@electron/universal": "1.2.1", + "@malept/flatpak-bundler": "^0.4.0", + "7zip-bin": "~5.1.1", + "async-exit-hook": "^2.0.1", + "bluebird-lst": "^1.0.9", + "builder-util": "23.0.6", + "builder-util-runtime": "9.0.1", + "chromium-pickle-js": "^0.2.0", + "debug": "^4.3.2", + "ejs": "^3.1.6", + "electron-osx-sign": "^0.6.0", + "electron-publish": "23.0.6", + "form-data": "^4.0.0", + "fs-extra": "^10.0.0", + "hosted-git-info": "^4.0.2", + "is-ci": "^3.0.0", + "isbinaryfile": "^4.0.8", + "js-yaml": "^4.1.0", + "lazy-val": "^1.0.5", + "minimatch": "^3.0.4", + "read-config-file": "6.2.0", + "sanitize-filename": "^1.6.3", + "semver": "^7.3.5", + "temp-file": "^3.4.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/app-builder-lib/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/app-builder-lib/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/app-builder-lib/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/app-builder-lib/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/asar": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/asar/-/asar-3.2.0.tgz", + "integrity": "sha512-COdw2ZQvKdFGFxXwX3oYh2/sOsJWJegrdJCGxnN4MZ7IULgRBp9P6665aqj9z1v9VwP4oP1hRBojRDQ//IGgAg==", + "deprecated": "Please use @electron/asar moving forward. There is no API change, just a package name change", + "dev": true, + "license": "MIT", + "dependencies": { + "chromium-pickle-js": "^0.2.0", + "commander": "^5.0.0", + "glob": "^7.1.6", + "minimatch": "^3.0.4" + }, + "bin": { + "asar": "bin/asar.js" + }, + "engines": { + "node": ">=10.12.0" + }, + "optionalDependencies": { + "@types/glob": "^7.1.1" + } + }, + "node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true, + "license": "MIT" + }, + "node_modules/async-exit-hook": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/async-exit-hook/-/async-exit-hook-2.0.1.tgz", + "integrity": "sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "license": "MIT", + "engines": { + "node": "^4.5.0 || >= 5.9" + } + }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/bluebird-lst": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/bluebird-lst/-/bluebird-lst-1.0.9.tgz", + "integrity": "sha512-7B1Rtx82hjnSD4PGLAjVWeYH3tHAcVUmChh85a3lltKQm6FresXh9ErQo6oAv6CqxttczC3/kEg8SY5NluPuUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "bluebird": "^3.5.5" + } + }, + "node_modules/body-parser": { + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", + "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.14.0", + "raw-body": "~2.5.3", + "type-is": "~1.6.18", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/boolean": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz", + "integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/boxen": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-5.1.2.tgz", + "integrity": "sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-align": "^3.0.0", + "camelcase": "^6.2.0", + "chalk": "^4.1.0", + "cli-boxes": "^2.2.1", + "string-width": "^4.2.2", + "type-fest": "^0.20.2", + "widest-line": "^3.1.0", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/boxen/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-alloc": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", + "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-alloc-unsafe": "^1.1.0", + "buffer-fill": "^1.0.0" + } + }, + "node_modules/buffer-alloc-unsafe": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", + "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==", + "dev": true, + "license": "MIT" + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/buffer-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.0.tgz", + "integrity": "sha512-tcBWO2Dl4e7Asr9hTGcpVrCe+F7DubpmqWCTbj4FHLmjqO2hIaC383acQubWtRJhdceqs5uBHs6Es+Sk//RKiQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/buffer-fill": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", + "integrity": "sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/builder-util": { + "version": "23.0.6", + "resolved": "https://registry.npmjs.org/builder-util/-/builder-util-23.0.6.tgz", + "integrity": "sha512-xV6JmZmHvpeUtsJNKDoKTefFLo9LRPnm4ii3ckRQe39BNu0nDyfpRLhici7KqnRxBdMSpIVIfayJX9syN+7zDw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/debug": "^4.1.6", + "@types/fs-extra": "^9.0.11", + "7zip-bin": "~5.1.1", + "app-builder-bin": "4.0.0", + "bluebird-lst": "^1.0.9", + "builder-util-runtime": "9.0.1", + "chalk": "^4.1.1", + "cross-spawn": "^7.0.3", + "debug": "^4.3.2", + "fs-extra": "^10.0.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "is-ci": "^3.0.0", + "js-yaml": "^4.1.0", + "source-map-support": "^0.5.19", + "stat-mode": "^1.0.0", + "temp-file": "^3.4.0" + } + }, + "node_modules/builder-util-runtime": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.0.1.tgz", + "integrity": "sha512-f9pjsGKjGaLWqYwIn11Lxc2YL0g8UnnxWECOS9GubCRNYWnqMz1ZjwwCedOUKk2UlD2J7zyVKJcCMt9v/pP/uQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.2", + "sax": "^1.2.4" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/builder-util/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/builder-util/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/builder-util/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cacheable-lookup": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", + "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.6.0" + } + }, + "node_modules/cacheable-request": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz", + "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^4.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^6.0.1", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chromium-pickle-js": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/chromium-pickle-js/-/chromium-pickle-js-0.2.0.tgz", + "integrity": "sha512-1R5Fho+jBq0DDydt+/vHWj5KJNJCKdARKOCwZUen84I5BreWoLqRLANH1U87eJy1tiASPtMnGqJJq0ZsLoRPOw==", + "dev": true, + "license": "MIT" + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-boxes": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", + "integrity": "sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", + "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "slice-ansi": "^3.0.0", + "string-width": "^4.2.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/clone-response": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", + "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-response": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/colors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", + "integrity": "sha512-pFGrxThWcWQ2MsAz6RtgeWe4NK2kUE1WfsrvvlctdII745EW9I0yflqhe7++M5LEc7bV2c/9/5zc8sFcpL0Drw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/compare-version": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/compare-version/-/compare-version-0.1.2.tgz", + "integrity": "sha512-pJDh5/4wrEnXX/VWRZvruAGHkzKdr46z11OlTPN+VrATlWWhSKewNCJ1futCO5C7eJB3nPMFZA1LeYtcFboZ2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/configstore": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", + "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "dot-prop": "^5.2.0", + "graceful-fs": "^4.1.2", + "make-dir": "^3.0.0", + "unique-string": "^2.0.0", + "write-file-atomic": "^3.0.0", + "xdg-basedir": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", + "license": "MIT" + }, + "node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/crc": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/crc/-/crc-3.8.0.tgz", + "integrity": "sha512-iX3mfgcTMIq3ZKLIsVFAbv7+Mc10kxabAGQb8HvjA1o3T1PIYprbakQ65d3I+2HGHt6nSKkM9PYjgoJO2KcFBQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "buffer": "^5.1.0" + } + }, + "node_modules/cross-env": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", + "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.1" + }, + "bin": { + "cross-env": "src/bin/cross-env.js", + "cross-env-shell": "src/bin/cross-env-shell.js" + }, + "engines": { + "node": ">=10.14", + "npm": ">=6", + "yarn": ">=1" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/crypto-random-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", + "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress-response/node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/dir-compare": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/dir-compare/-/dir-compare-2.4.0.tgz", + "integrity": "sha512-l9hmu8x/rjVC9Z2zmGzkhOEowZvW7pmYws5CWHutg8u1JgvsKWMx7Q/UODeu4djLZ4FgW5besw5yvMQnBHzuCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-equal": "1.0.0", + "colors": "1.0.3", + "commander": "2.9.0", + "minimatch": "3.0.4" + }, + "bin": { + "dircompare": "src/cli/dircompare.js" + } + }, + "node_modules/dir-compare/node_modules/commander": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", + "integrity": "sha512-bmkUukX8wAOjHdN26xj5c4ctEV22TQ7dQYhSmuckKhToXrkUn0iIaolHdIxYYqD55nhpSPA9zPQ1yP57GdXP2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-readlink": ">= 1.0.0" + }, + "engines": { + "node": ">= 0.6.x" + } + }, + "node_modules/dir-compare/node_modules/minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/dmg-builder": { + "version": "23.0.6", + "resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-23.0.6.tgz", + "integrity": "sha512-+2Wp3wDLAuj7qEBwqD9AssF68Uz3U0cwfvsz1qODKW1GBBEu/6X2LQUs4oDIdoIVpkai660+zyPok6f3DMfcSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "app-builder-lib": "23.0.6", + "builder-util": "23.0.6", + "builder-util-runtime": "9.0.1", + "fs-extra": "^10.0.0", + "iconv-lite": "^0.6.2", + "js-yaml": "^4.1.0" + }, + "optionalDependencies": { + "dmg-license": "^1.0.9" + } + }, + "node_modules/dmg-builder/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/dmg-builder/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/dmg-builder/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/dmg-license": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/dmg-license/-/dmg-license-1.0.11.tgz", + "integrity": "sha512-ZdzmqwKmECOWJpqefloC5OJy1+WZBBse5+MR88z9g9Zn4VY+WYUkAyojmhzJckH5YbbZGcYIuGAkY5/Ys5OM2Q==", + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "dependencies": { + "@types/plist": "^3.0.1", + "@types/verror": "^1.10.3", + "ajv": "^6.10.0", + "crc": "^3.8.0", + "iconv-corefoundation": "^1.1.7", + "plist": "^3.0.4", + "smart-buffer": "^4.0.2", + "verror": "^1.10.0" + }, + "bin": { + "dmg-license": "bin/dmg-license.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dot-prop": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", + "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-obj": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dotenv": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-9.0.2.tgz", + "integrity": "sha512-I9OvvrHp4pIARv4+x9iuewrWycX6CcZtoAu1XrzPxc5UygMJXJZYmBsynku8IkrJwgypE5DGNjDPmPRhDCptUg==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=10" + } + }, + "node_modules/dotenv-expand": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz", + "integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/duplexer3": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.5.tgz", + "integrity": "sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/electron": { + "version": "40.0.0", + "resolved": "https://registry.npmjs.org/electron/-/electron-40.0.0.tgz", + "integrity": "sha512-UyBy5yJ0/wm4gNugCtNPjvddjAknMTuXR2aCHioXicH7aKRKGDBPp4xqTEi/doVcB3R+MN3wfU9o8d/9pwgK2A==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@electron/get": "^2.0.0", + "@types/node": "^24.9.0", + "extract-zip": "^2.0.1" + }, + "bin": { + "electron": "cli.js" + }, + "engines": { + "node": ">= 12.20.55" + } + }, + "node_modules/electron-builder": { + "version": "23.0.6", + "resolved": "https://registry.npmjs.org/electron-builder/-/electron-builder-23.0.6.tgz", + "integrity": "sha512-VA50mfjjsoEQ5DHwgT61CGua6F337phNYPumMXkcedfjpAS82H23H2hnu75E77m6zf0wRNGVj9o7Z3rYpwyXlg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/yargs": "^17.0.1", + "app-builder-lib": "23.0.6", + "builder-util": "23.0.6", + "builder-util-runtime": "9.0.1", + "chalk": "^4.1.1", + "dmg-builder": "23.0.6", + "fs-extra": "^10.0.0", + "is-ci": "^3.0.0", + "lazy-val": "^1.0.5", + "read-config-file": "6.2.0", + "update-notifier": "^5.1.0", + "yargs": "^17.0.1" + }, + "bin": { + "electron-builder": "cli.js", + "install-app-deps": "install-app-deps.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/electron-builder/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/electron-builder/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/electron-builder/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/electron-osx-sign": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/electron-osx-sign/-/electron-osx-sign-0.6.0.tgz", + "integrity": "sha512-+hiIEb2Xxk6eDKJ2FFlpofCnemCbjbT5jz+BKGpVBrRNT3kWTGs4DfNX6IzGwgi33hUcXF+kFs9JW+r6Wc1LRg==", + "deprecated": "Please use @electron/osx-sign moving forward. Be aware the API is slightly different", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "bluebird": "^3.5.0", + "compare-version": "^0.1.2", + "debug": "^2.6.8", + "isbinaryfile": "^3.0.2", + "minimist": "^1.2.0", + "plist": "^3.0.1" + }, + "bin": { + "electron-osx-flat": "bin/electron-osx-flat.js", + "electron-osx-sign": "bin/electron-osx-sign.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/electron-osx-sign/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/electron-osx-sign/node_modules/isbinaryfile": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-3.0.3.tgz", + "integrity": "sha512-8cJBL5tTd2OS0dM4jz07wQd5g0dCCqIhUxPIGtZfa5L6hWlvV5MHTITy/DBAsF+Oe2LS1X3krBUhNwaGUWpWxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-alloc": "^1.2.0" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/electron-osx-sign/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "license": "MIT" + }, + "node_modules/electron-publish": { + "version": "23.0.6", + "resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-23.0.6.tgz", + "integrity": "sha512-ikjgf93uHKyjfTIxVBs3jrDY3x2C06c7Vbq7QDMIQnvMWUqZd1mNTqWUBAr1IQfwX5xvapsWxAJ5TF/Ops5KLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/fs-extra": "^9.0.11", + "builder-util": "23.0.6", + "builder-util-runtime": "9.0.1", + "chalk": "^4.1.1", + "fs-extra": "^10.0.0", + "lazy-val": "^1.0.5", + "mime": "^2.5.2" + } + }, + "node_modules/electron-publish/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/electron-publish/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/electron-publish/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/engine.io": { + "version": "6.6.5", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.5.tgz", + "integrity": "sha512-2RZdgEbXmp5+dVbRm0P7HQUImZpICccJy7rN7Tv+SFa55pH+lxnuw6/K1ZxxBfHoYpSkHLAO92oa8O4SwFXA2A==", + "license": "MIT", + "dependencies": { + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.7.2", + "cors": "~2.8.5", + "debug": "~4.4.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.18.3" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-goat": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-2.1.1.tgz", + "integrity": "sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", + "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "~1.20.3", + "content-disposition": "~0.5.4", + "content-type": "~1.0.4", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "~0.1.12", + "proxy-addr": "~2.0.7", + "qs": "~6.14.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "~0.19.0", + "serve-static": "~1.16.2", + "setprototypeof": "1.2.0", + "statuses": "~2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/extsprintf": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.4.1.tgz", + "integrity": "sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA==", + "dev": true, + "engines": [ + "node >=0.6.0" + ], + "license": "MIT", + "optional": true + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/filelist": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", + "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/finalhandler": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "statuses": "~2.0.2", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "dev": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/global-agent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-agent/-/global-agent-3.0.0.tgz", + "integrity": "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "boolean": "^3.0.1", + "es6-error": "^4.1.1", + "matcher": "^3.0.0", + "roarr": "^2.15.3", + "semver": "^7.3.2", + "serialize-error": "^7.0.1" + }, + "engines": { + "node": ">=10.0" + } + }, + "node_modules/global-agent/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/global-dirs": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.1.tgz", + "integrity": "sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ini": "2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/got": { + "version": "11.8.6", + "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", + "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/is": "^4.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.2", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" + }, + "engines": { + "node": ">=10.19.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/graceful-readlink": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", + "integrity": "sha512-8tLu60LgxF6XpdbK8OW3FA+IfTNBn1ZHGHKF4KQbEeSkajYw5PlYJcKluntgegDPTg8UkHjpet1T82vk6TQ68w==", + "dev": true, + "license": "MIT" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-yarn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz", + "integrity": "sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hosted-git-info": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/http2-wrapper": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", + "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/iconv-corefoundation": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/iconv-corefoundation/-/iconv-corefoundation-1.1.7.tgz", + "integrity": "sha512-T10qvkw0zz4wnm560lOEg0PovVqUXuOFhhHAkixw8/sycy7TJt7v/RrkEKEQnAw2viPSJu6iAkErxnzR0g8PpQ==", + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "dependencies": { + "cli-truncate": "^2.1.0", + "node-addon-api": "^1.6.3" + }, + "engines": { + "node": "^8.11.2 || >=10" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause", + "optional": true + }, + "node_modules/import-lazy": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", + "integrity": "sha512-m7ZEHgtw69qOGw+jwxXkHlrlIPdTGkyh66zXZ1ajZbxkDBNjSY/LGbmjc7h0s2ELsUDTAhFr55TrPSSqJGPG0A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ini": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", + "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-ci": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz", + "integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ci-info": "^3.2.0" + }, + "bin": { + "is-ci": "bin.js" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-installed-globally": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", + "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "global-dirs": "^3.0.0", + "is-path-inside": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-npm": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-5.0.0.tgz", + "integrity": "sha512-WW/rQLOazUq+ST/bCAVBp/2oMERWLsR7OrKyt052dNDk4DHcDE0/7QSXITlmi+VBcV13DfIbysG3tZJm5RfdBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-yarn-global": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz", + "integrity": "sha512-VjSeb/lHmkoyd8ryPVIKvOCn4D1koMqY+vqyjjUfc3xyKtP4dYOxM44sZrnqQSzSds3xyOrUTLTC9LVCVgLngw==", + "dev": true, + "license": "MIT" + }, + "node_modules/isbinaryfile": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz", + "integrity": "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/gjtorikian/" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jake": { + "version": "10.9.4", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz", + "integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "async": "^3.2.6", + "filelist": "^1.0.4", + "picocolors": "^1.1.1" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "dev": true, + "license": "ISC", + "optional": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "license": "MIT", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/latest-version": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-5.1.0.tgz", + "integrity": "sha512-weT+r0kTkRQdCdYCNtkMwWXQTMEswKrFBkm4ckQOMVhhqhIMI1UT2hMj+1iigIhgSZm5gTmrRXBNoGUgaTY1xA==", + "dev": true, + "license": "MIT", + "dependencies": { + "package-json": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lazy-val": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.5.tgz", + "integrity": "sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "dev": true, + "license": "MIT" + }, + "node_modules/lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/matcher": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz", + "integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "escape-string-regexp": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-addon-api": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-1.7.2.tgz", + "integrity": "sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/p-cancelable": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", + "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/package-json": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/package-json/-/package-json-6.5.0.tgz", + "integrity": "sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "got": "^9.6.0", + "registry-auth-token": "^4.0.0", + "registry-url": "^5.0.0", + "semver": "^6.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/package-json/node_modules/@sindresorhus/is": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", + "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/package-json/node_modules/@szmarczak/http-timer": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", + "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "defer-to-connect": "^1.0.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/package-json/node_modules/cacheable-request": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", + "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^3.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^4.1.0", + "responselike": "^1.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/package-json/node_modules/decompress-response": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", + "integrity": "sha512-BzRPQuY1ip+qDonAOz42gRm/pg9F768C+npV/4JOsxRC2sq+Rlk+Q4ZCAsOhnIaMrgarILY+RMUIvMmmX1qAEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-response": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/package-json/node_modules/defer-to-connect": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", + "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/package-json/node_modules/got": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", + "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/is": "^0.14.0", + "@szmarczak/http-timer": "^1.1.2", + "cacheable-request": "^6.0.0", + "decompress-response": "^3.3.0", + "duplexer3": "^0.1.4", + "get-stream": "^4.1.0", + "lowercase-keys": "^1.0.1", + "mimic-response": "^1.0.1", + "p-cancelable": "^1.0.0", + "to-readable-stream": "^1.0.0", + "url-parse-lax": "^3.0.0" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/package-json/node_modules/got/node_modules/get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/package-json/node_modules/got/node_modules/lowercase-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", + "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/package-json/node_modules/json-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", + "integrity": "sha512-CuUqjv0FUZIdXkHPI8MezCnFCdaTAacej1TZYulLoAg1h/PhwkdXFN4V/gzY4g+fMBCOV2xF+rp7t2XD2ns/NQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/package-json/node_modules/keyv": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", + "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.0" + } + }, + "node_modules/package-json/node_modules/normalize-url": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.1.tgz", + "integrity": "sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/package-json/node_modules/p-cancelable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", + "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/package-json/node_modules/responselike": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", + "integrity": "sha512-/Fpe5guzJk1gPqdJLJR5u7eG/gNY4nImjbRDaVWVMRhne55TCmj2i9Q+54PBRfatRC8v/rIiv9BN0pMd9OV5EQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "lowercase-keys": "^1.0.0" + } + }, + "node_modules/package-json/node_modules/responselike/node_modules/lowercase-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", + "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "license": "MIT" + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/plist": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/plist/-/plist-3.1.0.tgz", + "integrity": "sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@xmldom/xmldom": "^0.8.8", + "base64-js": "^1.5.1", + "xmlbuilder": "^15.1.1" + }, + "engines": { + "node": ">=10.4.0" + } + }, + "node_modules/prepend-http": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", + "integrity": "sha512-ravE6m9Atw9Z/jjttRUZ+clIXogdghyZAuWJ3qEzjT+jI/dL1ifAqhZeC5VHzQp1MSt1+jxKkFNemj/iO7tVUA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/pump": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", + "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/pupa": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/pupa/-/pupa-2.1.1.tgz", + "integrity": "sha512-l1jNAspIBSFqbT+y+5FosojNpVpF94nlI+wDUpqP9enwOTfHx9f0gh5nB96vl+6yTpsJsypeNrwfzPrKuHB41A==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-goat": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/qs": { + "version": "6.14.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", + "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/rc/node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true, + "license": "ISC" + }, + "node_modules/read-config-file": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/read-config-file/-/read-config-file-6.2.0.tgz", + "integrity": "sha512-gx7Pgr5I56JtYz+WuqEbQHj/xWo+5Vwua2jhb1VwM4Wid5PqYmZ4i00ZB0YEGIfkVBsCv9UrjgyqCiQfS/Oosg==", + "dev": true, + "license": "MIT", + "dependencies": { + "dotenv": "^9.0.2", + "dotenv-expand": "^5.1.0", + "js-yaml": "^4.1.0", + "json5": "^2.2.0", + "lazy-val": "^1.0.4" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/registry-auth-token": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.2.2.tgz", + "integrity": "sha512-PC5ZysNb42zpFME6D/XlIgtNGdTl8bBOCw90xQLVMpzuuubJKYDWFAEuUNc+Cn8Z8724tg2SDhDRrkVEsqfDMg==", + "dev": true, + "license": "MIT", + "dependencies": { + "rc": "1.2.8" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/registry-url": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-5.1.0.tgz", + "integrity": "sha512-8acYXXTI0AkQv6RAOjE3vOaIXZkT9wo4LOFbBKYQEEnnMNBpKqdUrI6S4NT0KPIo/WVvJ5tE/X5LF/TQUf0ekw==", + "dev": true, + "license": "MIT", + "dependencies": { + "rc": "^1.2.8" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/responselike": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", + "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lowercase-keys": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/roarr": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz", + "integrity": "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "boolean": "^3.0.1", + "detect-node": "^2.0.4", + "globalthis": "^1.0.1", + "json-stringify-safe": "^5.0.1", + "semver-compare": "^1.0.0", + "sprintf-js": "^1.1.2" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/sanitize-filename": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.3.tgz", + "integrity": "sha512-y/52Mcy7aw3gRm7IrcGDFx/bCk4AhRh2eI9luHOQM86nZsqwiRkkq2GekHXBBD+SmPidc8i2PqtYZl+pWJ8Oeg==", + "dev": true, + "license": "WTFPL OR ISC", + "dependencies": { + "truncate-utf8-bytes": "^1.0.0" + } + }, + "node_modules/sax": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.4.tgz", + "integrity": "sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/semver-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", + "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/semver-diff": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-3.1.1.tgz", + "integrity": "sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/send": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.4.1", + "range-parser": "~1.2.1", + "statuses": "~2.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/send/node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/serialize-error": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz", + "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "type-fest": "^0.13.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/serve-static": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "~0.19.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/slice-ansi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", + "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socket.io": { + "version": "4.8.3", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.3.tgz", + "integrity": "sha512-2Dd78bqzzjE6KPkD5fHZmDAKRNe3J15q+YHDrIsy9WEkqttc7GY+kT9OBLSMaPbQaEd0x1BjcmtMtXkfpc+T5A==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "cors": "~2.8.5", + "debug": "~4.4.1", + "engine.io": "~6.6.0", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/socket.io-adapter": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.6.tgz", + "integrity": "sha512-DkkO/dz7MGln0dHn5bmN3pPy+JmywNICWrJqVWiVOyvXjWQFIv9c2h24JrQLLFJ2aQVQf/Cvl1vblnd4r2apLQ==", + "license": "MIT", + "dependencies": { + "debug": "~4.4.1", + "ws": "~8.18.3" + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.5.tgz", + "integrity": "sha512-bPMmpy/5WWKHea5Y/jYAP6k74A+hvmRCQaJuJB6I/ML5JZq/KfNieUVo/3Mh7SAqn7TyFdIo6wqYHInG1MU1bQ==", + "license": "MIT", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.4.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true + }, + "node_modules/stat-mode": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stat-mode/-/stat-mode-1.0.0.tgz", + "integrity": "sha512-jH9EhtKIjuXZ2cWxmXS8ZP80XyC3iasQxMDV8jzhNJpfDb7VbQLVW4Wvsxz9QZvzV+G4YoSfBUVKDOyxLzi/sg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sumchecker": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz", + "integrity": "sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "debug": "^4.1.0" + }, + "engines": { + "node": ">= 8.0" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/temp-file": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/temp-file/-/temp-file-3.4.0.tgz", + "integrity": "sha512-C5tjlC/HCtVUOi3KWVokd4vHVViOmGjtLwIh4MuzPo/nMYTV/p1urt3RnMz2IWXDdKEGJH3k5+KPxtqRsUYGtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "async-exit-hook": "^2.0.1", + "fs-extra": "^10.0.0" + } + }, + "node_modules/temp-file/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/temp-file/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/temp-file/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/tmp": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", + "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.14" + } + }, + "node_modules/tmp-promise": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tmp-promise/-/tmp-promise-3.0.3.tgz", + "integrity": "sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tmp": "^0.2.0" + } + }, + "node_modules/to-readable-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", + "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/truncate-utf8-bytes": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz", + "integrity": "sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==", + "dev": true, + "license": "WTFPL", + "dependencies": { + "utf8-byte-length": "^1.0.1" + } + }, + "node_modules/type-fest": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", + "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-typedarray": "^1.0.0" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "license": "MIT" + }, + "node_modules/unique-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", + "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "crypto-random-string": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/update-notifier": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-5.1.0.tgz", + "integrity": "sha512-ItnICHbeMh9GqUy31hFPrD1kcuZ3rpxDZbf4KUDavXwS0bW5m7SLbDQpGX3UYr072cbrF5hFUs3r5tUsPwjfHw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "boxen": "^5.0.0", + "chalk": "^4.1.0", + "configstore": "^5.0.1", + "has-yarn": "^2.1.0", + "import-lazy": "^2.1.0", + "is-ci": "^2.0.0", + "is-installed-globally": "^0.4.0", + "is-npm": "^5.0.0", + "is-yarn-global": "^0.3.0", + "latest-version": "^5.1.0", + "pupa": "^2.1.1", + "semver": "^7.3.4", + "semver-diff": "^3.1.1", + "xdg-basedir": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/yeoman/update-notifier?sponsor=1" + } + }, + "node_modules/update-notifier/node_modules/ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/update-notifier/node_modules/is-ci": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", + "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ci-info": "^2.0.0" + }, + "bin": { + "is-ci": "bin.js" + } + }, + "node_modules/update-notifier/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/url-parse-lax": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", + "integrity": "sha512-NjFKA0DidqPa5ciFcSrXnAltTtzz84ogy+NebPvfEgAck0+TNg4UJ4IN+fB7zRZfbgUf0syOo9MDxFkDSMuFaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prepend-http": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/utf8-byte-length": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz", + "integrity": "sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA==", + "dev": true, + "license": "(WTFPL OR MIT)" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/verror": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.1.tgz", + "integrity": "sha512-veufcmxri4e3XSrT0xwfUR7kguIkaxBeosDg00yDWhk49wdwkSUrvvsm7nc75e1PUyvIeZj6nS8VQRYz2/S4Xg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/widest-line": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-3.1.0.tgz", + "integrity": "sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "string-width": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xdg-basedir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", + "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/xmlbuilder": { + "version": "15.1.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", + "integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, + "license": "ISC" + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + } + } +} diff --git a/Client-Server/package.json b/Client-Server/package.json new file mode 100644 index 0000000..4412c7c --- /dev/null +++ b/Client-Server/package.json @@ -0,0 +1,131 @@ +{ + "name": "galaxystrikeonline", + "version": "1.0.0", + "description": "Galaxy Strike Online - Space Idle MMORPG", + "license": "MIT", + "author": { + "name": "Korvarix Studios", + "email": "contact@korvarixstudios.com" + }, + "type": "commonjs", + "main": "electron-main.js", + "homepage": "./", + "scripts": { + "start": "electron .", + "dev": "electron . --dev", + "debug": "cross-env DEBUG=* electron .", + "debug-verbose": "cross-env DEBUG=* VERBOSE=true electron .", + "debug-boot": "cross-env DEBUG=boot* electron .", + "debug-renderer": "cross-env DEBUG=renderer* electron .", + "debug-main": "cross-env DEBUG=main* electron .", + "debug-windows": "set DEBUG=boot* && electron .", + "debug-windows-verbose": "set DEBUG=* && set VERBOSE=true && electron .", + "build": "electron-builder", + "build-win": "electron-builder --win", + "build-mac": "electron-builder --mac", + "build-linux": "electron-builder --linux", + "dist": "npm run build", + "pack": "electron-builder --dir", + "postinstall": "electron-builder install-app-deps" + }, + "keywords": [ + "game", + "space", + "mmorpg", + "idle", + "electron" + ], + "dependencies": { + "cors": "^2.8.6", + "express": "^4.22.1", + "socket.io": "^4.8.3" + }, + "devDependencies": { + "cross-env": "^7.0.3", + "electron": "^40.0.0", + "electron-builder": "^23.0.6" + }, + "build": { + "appId": "com.korvarixstudios.galaxystrikeonline", + "productName": "Galaxy Strike Online", + "directories": { + "output": "dist" + }, + "files": [ + "**/*", + "!node_modules", + "!dist", + "!*.md" + ], + "extraResources": [ + { + "from": "assets", + "to": "assets" + } + ], + "win": { + "target": [ + { + "target": "nsis", + "arch": [ + "x64", + "ia32" + ] + }, + { + "target": "portable", + "arch": [ + "x64", + "ia32" + ] + } + ], + "icon": "assets/icon.ico" + }, + "mac": { + "target": [ + { + "target": "dmg", + "arch": [ + "x64", + "arm64" + ] + }, + { + "target": "zip", + "arch": [ + "x64", + "arm64" + ] + } + ], + "icon": "assets/icon.icns", + "category": "public.app-category.games" + }, + "linux": { + "target": [ + { + "target": "AppImage", + "arch": [ + "x64" + ] + }, + { + "target": "deb", + "arch": [ + "x64" + ] + } + ], + "icon": "assets/icon.png", + "category": "Game", + "maintainer": "Korvarix Studios " + }, + "nsis": { + "oneClick": false, + "allowToChangeInstallationDirectory": true, + "createDesktopShortcut": true, + "createStartMenuShortcut": true + } + } +} diff --git a/Client-Server/preload.js b/Client-Server/preload.js new file mode 100644 index 0000000..e28e9b4 --- /dev/null +++ b/Client-Server/preload.js @@ -0,0 +1,33 @@ +console.log('[PRELOAD] Preload script starting'); + +const { contextBridge, ipcRenderer } = require('electron'); + +console.log('[PRELOAD] Electron modules imported successfully'); + +// Expose protected methods that allow the renderer process to use +// the ipcRenderer without exposing the entire object +try { + contextBridge.exposeInMainWorld('electronAPI', { + // Window controls + minimizeWindow: () => ipcRenderer.send('minimize-window'), + closeWindow: () => ipcRenderer.send('close-window'), + toggleFullscreen: () => ipcRenderer.send('toggle-fullscreen'), + + // Logging + log: (level, message, data) => ipcRenderer.send('log-message', { level, message, data }), + + // Save operations + createSaveFolders: (saveSlots) => ipcRenderer.invoke('create-save-folders', saveSlots), + testFileAccess: (slotPath) => ipcRenderer.invoke('test-file-access', slotPath), + saveGame: (slot, saveData) => ipcRenderer.invoke('save-game', slot, saveData), + loadGame: (slot) => ipcRenderer.invoke('load-game', slot), + + // System operations + getPath: (name) => ipcRenderer.invoke('get-path', name) + }); + + console.log('[PRELOAD] electronAPI exposed via contextBridge successfully'); +} catch (error) { + console.error('[PRELOAD] Failed to expose electronAPI:', error); + console.error('[PRELOAD] Error stack:', error.stack); +} diff --git a/Client-Server/styles/components.css b/Client-Server/styles/components.css new file mode 100644 index 0000000..bc076f6 --- /dev/null +++ b/Client-Server/styles/components.css @@ -0,0 +1,1677 @@ +/* Galaxy Strike Online - Component Styles */ + +/* Health Bars */ +.health-bar { + margin: 1rem 0; + padding: 0.75rem; + border-radius: 8px; + background: rgba(0, 0, 0, 0.3); + border: 1px solid rgba(255, 255, 255, 0.1); +} + +.health-label { + font-size: 0.9rem; + font-weight: 600; + margin-bottom: 0.5rem; + color: #fff; + text-transform: uppercase; + letter-spacing: 1px; +} + +.ship-health .health-label { + color: #4a9eff; +} + +.player-health .health-label { + color: #4ade80; +} + +.ship-health-fill { + background: linear-gradient(90deg, #4a9eff, #00d4ff); + border-radius: 4px; + transition: width 0.3s ease; +} + +.player-health-fill { + background: linear-gradient(90deg, #4ade80, #22c55e); + border-radius: 4px; + transition: width 0.3s ease; +} + +.health-bar span { + display: block; + text-align: center; + margin-top: 0.5rem; + font-size: 0.85rem; + color: rgba(255, 255, 255, 0.8); +} + +/* Progress bars */ +.progress-bar { + width: 100%; + height: 20px; + background: rgba(255, 255, 255, 0.1); + border-radius: 4px; + overflow: hidden; + position: relative; +} + +.progress-fill { + height: 100%; + transition: width 0.3s ease; + min-width: 2px; +} + +/* Ship Stats Display */ +.current-ship-stats { + background: rgba(0, 0, 0, 0.3); + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 8px; + padding: 1rem; + margin-bottom: 1rem; +} + +.stat-row { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.5rem 0; + border-bottom: 1px solid rgba(255, 255, 255, 0.05); +} + +.stat-row:last-child { + border-bottom: none; +} + +.stat-label { + color: #4a9eff; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 1px; + font-size: 0.85rem; +} + +.stat-value { + color: #fff; + font-weight: 700; + font-size: 0.9rem; +} + +/* Buttons */ +.btn { + padding: 0.75rem 1.5rem; + border: none; + border-radius: 8px; + font-family: 'Space Mono', monospace; + font-weight: 700; + cursor: pointer; + transition: all 0.3s ease; + text-transform: uppercase; + letter-spacing: 1px; + position: relative; + overflow: hidden; +} + +.btn::before { + content: ''; + position: absolute; + top: 0; + left: -100%; + width: 100%; + height: 100%; + background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); + transition: left 0.5s ease; +} + +.btn:hover::before { + left: 100%; +} + +.btn-primary { + background: var(--gradient-primary); + color: var(--bg-primary); + box-shadow: 0 4px 15px rgba(0, 212, 255, 0.3); +} + +.btn-primary:hover { + transform: translateY(-2px); + box-shadow: 0 6px 20px rgba(0, 212, 255, 0.4); +} + +.btn-secondary { + background: var(--bg-tertiary); + color: var(--text-primary); + border: 1px solid var(--border-color); +} + +.btn-secondary:hover { + border-color: var(--primary-color); + background: var(--hover-bg); +} + +.btn-success { + background: linear-gradient(135deg, #00ff88, #00cc66); + color: var(--bg-primary); + box-shadow: 0 4px 15px rgba(0, 255, 136, 0.3); +} + +.btn-warning { + background: linear-gradient(135deg, #ffaa00, #ff8800); + color: var(--bg-primary); + box-shadow: 0 4px 15px rgba(255, 170, 0, 0.3); +} + +.btn-danger { + background: linear-gradient(135deg, #ff3366, #ff0033); + color: var(--bg-primary); + box-shadow: 0 4px 15px rgba(255, 51, 102, 0.3); +} + +.btn:disabled { + opacity: 0.5; + cursor: not-allowed; + transform: none !important; +} + +/* Modals */ +.modal-overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.8); + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; + backdrop-filter: blur(5px); +} + +.modal { + background: var(--bg-secondary); + border: 1px solid var(--border-color); + border-radius: 16px; + max-width: 600px; + width: 90%; + max-height: 80vh; + overflow-y: auto; + box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5); + animation: modalSlideIn 0.3s ease; +} + +@keyframes modalSlideIn { + from { + opacity: 0; + transform: translateY(-50px) scale(0.9); + } + to { + opacity: 1; + transform: translateY(0) scale(1); + } +} + +.modal-header { + padding: 1.5rem; + border-bottom: 1px solid var(--border-color); + display: flex; + justify-content: space-between; + align-items: center; +} + +.modal-header h3 { + color: var(--primary-color); + font-family: 'Orbitron', sans-serif; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 1px; +} + +.modal-close { + background: transparent; + border: none; + color: var(--text-secondary); + font-size: 1.5rem; + cursor: pointer; + transition: all 0.3s ease; + padding: 0.5rem; +} + +.modal-close:hover { + color: var(--error-color); + transform: rotate(90deg); +} + +.modal-body { + padding: 1.5rem; +} + +/* Alert Modal Styles */ +.alert-modal .modal-header h3 { + color: var(--primary-color); +} + +.alert-modal .modal-body { + text-align: center; + padding: 2rem 1.5rem; +} + +.alert-modal .alert-message { + color: var(--text-primary); + font-size: 1rem; + line-height: 1.5; + margin-bottom: 1.5rem; + white-space: pre-line; +} + +.alert-modal .modal-footer { + padding: 0 1.5rem 1.5rem; + text-align: center; +} + +.alert-modal .btn-alert { + background: var(--gradient-primary); + color: white; + border: none; + padding: 0.75rem 2rem; + border-radius: 8px; + font-weight: 600; + cursor: pointer; + transition: all 0.3s ease; + text-transform: uppercase; + letter-spacing: 1px; +} + +.alert-modal .btn-alert:hover { + transform: translateY(-2px); + box-shadow: 0 8px 25px rgba(0, 212, 255, 0.3); +} + +/* Success Alert */ +.alert-modal.success .modal-header h3 { + color: var(--success-color); +} + +.alert-modal.success .btn-alert { + background: var(--success-color); +} + +/* Error Alert */ +.alert-modal.error .modal-header h3 { + color: var(--error-color); +} + +.alert-modal.error .btn-alert { + background: var(--error-color); +} + +/* Warning Alert */ +.alert-modal.warning .modal-header h3 { + color: var(--warning-color); +} + +.alert-modal.warning .btn-alert { + background: var(--warning-color); +} + +/* Confirmation Modal Styles */ +.confirmation-modal .modal-body { + text-align: center; + padding: 2rem 1.5rem; +} + +.confirmation-modal .confirm-message { + color: var(--text-primary); + font-size: 1rem; + line-height: 1.5; + margin-bottom: 1.5rem; + white-space: pre-line; +} + +.confirmation-modal .modal-footer { + padding: 0 1.5rem 1.5rem; + display: flex; + gap: 1rem; + justify-content: center; +} + +.confirmation-modal .btn-confirm { + background: var(--error-color); + color: white; + border: none; + padding: 0.75rem 1.5rem; + border-radius: 8px; + font-weight: 600; + cursor: pointer; + transition: all 0.3s ease; + text-transform: uppercase; + letter-spacing: 1px; +} + +.confirmation-modal .btn-confirm:hover { + transform: translateY(-2px); + box-shadow: 0 8px 25px rgba(255, 51, 102, 0.3); +} + +.confirmation-modal .btn-cancel { + background: var(--bg-tertiary); + color: var(--text-primary); + border: 1px solid var(--border-color); + padding: 0.75rem 1.5rem; + border-radius: 8px; + font-weight: 600; + cursor: pointer; + transition: all 0.3s ease; + text-transform: uppercase; + letter-spacing: 1px; +} + +.confirmation-modal .btn-cancel:hover { + background: var(--hover-bg); + border-color: var(--primary-color); +} + +/* Settings Menu Styles */ +.settings-menu { + max-width: 600px; + margin: 0 auto; +} + +.settings-section { + margin-bottom: 2rem; + padding: 1.5rem; + background: var(--bg-tertiary); + border-radius: 12px; + border: 1px solid var(--border-color); +} + +.settings-section h3 { + color: var(--primary-color); + font-family: 'Orbitron', sans-serif; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 1px; + margin-bottom: 1rem; + font-size: 1.2rem; +} + +.settings-section h4 { + color: var(--text-primary); + font-family: 'Orbitron', sans-serif; + font-weight: 600; + margin-bottom: 0.5rem; + font-size: 1rem; +} + +.settings-section p { + color: var(--text-secondary); + margin-bottom: 1rem; + line-height: 1.5; +} + +.setting-group { + margin-bottom: 1.5rem; +} + +.setting-group label { + display: block; + color: var(--text-primary); + font-weight: 600; + margin-bottom: 0.5rem; + font-size: 0.9rem; +} + +.setting-select { + width: 100%; + padding: 0.75rem; + background: var(--bg-secondary); + border: 1px solid var(--border-color); + border-radius: 8px; + color: var(--text-primary); + font-size: 0.9rem; + cursor: pointer; + transition: all 0.3s ease; +} + +.setting-select:hover { + border-color: var(--primary-color); + box-shadow: 0 0 0 2px rgba(74, 158, 255, 0.1); +} + +.setting-select:focus { + outline: none; + border-color: var(--primary-color); + box-shadow: 0 0 0 2px rgba(74, 158, 255, 0.2); +} + +.setting-actions { + display: flex; + gap: 1rem; + justify-content: flex-end; + margin-top: 1rem; +} + +.settings-description { + margin-bottom: 1rem; +} + +.reset-options { + display: flex; + flex-direction: column; + gap: 1rem; +} + +.reset-option { + padding: 1rem; + background: var(--bg-secondary); + border-radius: 8px; + border: 1px solid var(--border-color); +} + +.reset-option h4 { + color: var(--warning-color); + margin-bottom: 0.5rem; +} + +.reset-option p { + margin-bottom: 0.5rem; +} + +.reset-option ul { + margin: 0.5rem 0; + padding-left: 1.5rem; + color: var(--text-secondary); + font-size: 0.85rem; +} + +.reset-option li { + margin-bottom: 0.25rem; +} + +/* Progress Bars */ +.progress-bar { + width: 100%; + height: 8px; + background: var(--bg-tertiary); + border-radius: 4px; + overflow: hidden; + position: relative; +} + +.progress-fill { + height: 100%; + background: var(--gradient-primary); + border-radius: 4px; + transition: width 0.3s ease; + position: relative; +} + +.progress-fill::after { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent); + animation: progressShine 2s infinite; +} + +@keyframes progressShine { + 0% { transform: translateX(-100%); } + 100% { transform: translateX(100%); } +} + +/* Tooltips */ +.tooltip { + position: relative; + cursor: help; +} + +.tooltip::before { + content: attr(data-tooltip); + position: absolute; + bottom: 125%; + left: 50%; + transform: translateX(-50%); + background: var(--bg-tertiary); + color: var(--text-primary); + padding: 0.5rem 1rem; + border-radius: 6px; + border: 1px solid var(--border-color); + font-size: 0.8rem; + white-space: nowrap; + opacity: 0; + pointer-events: none; + transition: opacity 0.3s ease; + z-index: 1000; +} + +.tooltip::after { + content: ''; + position: absolute; + bottom: -5px; + left: 50%; + transform: translateX(-50%); + border-left: 5px solid transparent; + border-right: 5px solid transparent; + border-top: 5px solid var(--border-color); + opacity: 0; + pointer-events: none; + transition: opacity 0.3s ease; +} + +.tooltip:hover::before, +.tooltip:hover::after { + opacity: 1; +} + +/* Notifications */ +.notification { + position: fixed; + top: 20px; + right: 20px; + padding: 1rem 1.5rem; + border-radius: 8px; + border: 1px solid var(--border-color); + background: var(--bg-secondary); + color: var(--text-primary); + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3); + z-index: 2000; + animation: notificationSlideIn 0.3s ease; + max-width: 300px; +} + +@keyframes notificationSlideIn { + from { + opacity: 0; + transform: translateX(100%); + } + to { + opacity: 1; + transform: translateX(0); + } +} + +.notification.success { + border-color: var(--success-color); + background: linear-gradient(135deg, rgba(0, 255, 136, 0.1), rgba(0, 204, 102, 0.1)); +} + +.notification.warning { + border-color: var(--warning-color); + background: linear-gradient(135deg, rgba(255, 170, 0, 0.1), rgba(255, 136, 0, 0.1)); +} + +.notification.error { + border-color: var(--error-color); + background: linear-gradient(135deg, rgba(255, 51, 102, 0.1), rgba(255, 0, 51, 0.1)); +} + +.notification.info { + border-color: var(--primary-color); + background: linear-gradient(135deg, rgba(0, 212, 255, 0.1), rgba(0, 153, 204, 0.1)); +} + +/* Inventory Grid Container */ +#inventoryGrid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); + gap: 1.5625rem; + justify-items: center; + align-items: center; + padding: 1.5625rem; + width: 100%; + max-width: none; + overflow-y: auto; + max-height: 400px; +} + +/* Equipment Section */ +.equipment-section { + margin-bottom: 2rem; + padding: 1rem; + background: var(--bg-secondary); + border-radius: 12px; + border: 1px solid var(--border-color); +} + +.equipment-section h3 { + margin: 0 0 1rem 0; + color: var(--text-primary); + font-size: 1.2rem; + font-weight: 600; +} + +.equipment-slots { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); + gap: 1rem; +} + +.equipment-slot { + display: flex; + flex-direction: column; + align-items: center; + text-align: center; +} + +.slot-label { + font-size: 0.9rem; + color: var(--text-secondary); + margin-bottom: 0.5rem; + font-weight: 500; +} + +.slot-container { + width: 80px; + height: 80px; + border: 2px solid var(--border-color); + border-radius: 8px; + display: flex; + align-items: center; + justify-content: center; + background: var(--bg-tertiary); + transition: all 0.3s ease; + cursor: pointer; +} + +.slot-container:hover { + border-color: var(--primary-color); + box-shadow: 0 0 10px rgba(0, 123, 255, 0.3); +} + +.empty-equip-slot { + color: var(--text-muted); + font-size: 0.8rem; + text-align: center; + padding: 0.5rem; +} + +.equipped-item { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + width: 100%; + height: 100%; + padding: 0.25rem; +} + +.equipped-item i { + font-size: 1.5rem; + margin-bottom: 0.25rem; +} + +.equipped-item .item-name { + font-size: 0.7rem; + text-align: center; + line-height: 1; + word-break: break-word; +} + +/* Inventory Main Container */ +.inventory-main { + display: flex; + gap: 2rem; + flex: 1; + min-height: 0; +} + +/* Inventory Section */ +.inventory-section { + flex: 2; + min-height: 0; + display: flex; + flex-direction: column; +} + +.inventory-section h3 { + margin: 0 0 1rem 0; + color: var(--text-primary); + font-size: 1.2rem; + font-weight: 600; +} + +/* Item Details Section */ +.item-details { + flex: 1; + background: var(--card-bg); + border: 1px solid var(--border-color); + border-radius: 12px; + padding: 1rem; + overflow-y: auto; + min-height: 0; + display: flex; + flex-direction: column; +} + +.inventory-container { + display: flex; + flex-direction: column; + gap: 2rem; + height: 100%; + max-height: 600px; + overflow: hidden; +} + +/* Base Navigation Buttons - Match Quest Tab Style */ +.base-navigation { + display: flex; + gap: 0.5rem; + margin-bottom: 2rem; + justify-content: center; +} + +.base-nav-btn { + padding: 0.75rem 1.5rem; + background: var(--bg-tertiary); + border: 1px solid var(--border-color); + border-radius: 8px; + color: var(--text-secondary); + cursor: pointer; + transition: all 0.3s ease; +} + +.base-nav-btn:hover { + border-color: var(--primary-color); + color: var(--text-primary); +} + +.base-nav-btn.active { + background: var(--gradient-primary); + color: var(--bg-primary); + border-color: transparent; +} + +/* Base Rooms Grid - Smaller to prevent scrolling */ +.base-rooms { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); + gap: 0.75rem; + padding: 0.5rem; +} + +.room-item { + background: var(--bg-tertiary); + border: 1px solid var(--border-color); + border-radius: 8px; + padding: 0.75rem; + cursor: pointer; + transition: all 0.3s ease; + text-align: center; + min-height: 80px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +.room-item:hover { + border-color: var(--primary-color); + transform: translateY(-2px); +} + +.room-item i { + font-size: 1.5rem; + margin-bottom: 0.5rem; + color: var(--primary-color); +} + +.room-item h4 { + margin: 0; + color: var(--text-primary); + font-size: 0.8rem; + font-weight: 600; +} + +.room-item p { + margin: 0.25rem 0 0 0; + color: var(--text-secondary); + font-size: 0.7rem; +} + +/* Base Visualization */ +.base-visualization-container { + display: grid; + grid-template-columns: 1fr 300px; + gap: 1rem; + height: calc(100vh - 280px); +} + +#baseCanvas { + background: var(--card-bg); + border: 1px solid var(--border-color); + border-radius: 12px; + width: 100%; + height: 100%; +} + +.base-info-overlay { + display: flex; + flex-direction: column; + gap: 1rem; +} + +.base-stats-overlay { + background: var(--card-bg); + border: 1px solid var(--border-color); + border-radius: 12px; + padding: 1rem; + overflow-y: auto; + max-height: calc(100vh - 320px); +} + +.base-stats-overlay h3 { + margin: 0 0 1rem 0; + color: var(--text-primary); + font-size: 1.1rem; + font-weight: 600; +} + +#baseInfoDisplay { + color: var(--text-secondary); + font-size: 0.9rem; + line-height: 1.4; +} + +/* Loading Progress Indicator */ +.loading-indicator { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 4px; + background: linear-gradient(90deg, + var(--primary-color) 0%, + rgba(0, 212, 255, 0.3) 50%, + var(--primary-color) 100%); + background-size: 200% 100%; + animation: loading-gradient 2s ease-in-out infinite; + z-index: 10000; + transition: opacity 0.3s ease; +} + +.loading-indicator.hidden { + opacity: 0; + pointer-events: none; +} + +.loading-indicator.complete { + background: linear-gradient(90deg, #4CAF50 0%, #45a049 100%); + animation: none; +} + +.loading-indicator.error { + background: linear-gradient(90deg, #f44336 0%, #d32f2f 100%); + animation: none; +} + +@keyframes loading-gradient { + 0% { + background-position: 0% 50%; + } + 50% { + background-position: 100% 50%; + } + 100% { + background-position: 0% 50%; + } +} + +/* Loading Status Text */ +.loading-status { + position: fixed; + top: 10px; + left: 50%; + transform: translateX(-50%); + background: var(--card-bg); + border: 1px solid var(--border-color); + border-radius: 8px; + padding: 8px 16px; + color: var(--text-primary); + font-size: 0.9rem; + font-weight: 500; + z-index: 10001; + transition: all 0.3s ease; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); +} + +.loading-status.error { + background: linear-gradient(90deg, #f44336 0%, #d32f2f 100%); + color: white; + border-color: #d32f2f; +} + +.loading-status.hidden { + opacity: 0; + transform: translateX(-50%) translateY(-20px); +} + +/* Starbase Bonus Inventory Slots */ +.inventory-slot.starbase-bonus-slot { + border: 2px solid var(--primary-color); + background: rgba(0, 212, 255, 0.1); + position: relative; +} + +.inventory-slot.starbase-bonus-slot::before { + content: '+'; + position: absolute; + top: 2px; + right: 2px; + background: var(--primary-color); + color: white; + width: 12px; + height: 12px; + border-radius: 50%; + font-size: 8px; + display: flex; + align-items: center; + justify-content: center; + font-weight: bold; +} + +/* Starbases Container - Side-by-Side Box Layout */ +.starbases-container { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 1rem; + height: calc(100vh - 320px); /* Adjusted for titles outside boxes */ + max-height: calc(100vh - 320px); + overflow: hidden; /* Prevent container from scrolling */ +} + +.starbase-section { + display: flex; + flex-direction: column; + height: 100%; +} + +.starbase-section h3 { + margin: 0 0 1rem 0; + color: var(--text-primary); + font-size: 1.1rem; + font-weight: 600; + flex-shrink: 0; /* Prevent title from shrinking */ +} + +.starbase-list { + background: var(--card-bg); + border: 1px solid var(--border-color); + border-radius: 12px; + padding: 1rem; + overflow-y: scroll; /* Always show scrollbar */ + flex: 1; + min-height: 0; /* Allow flex item to shrink */ +} + +.starbase-shop { + background: var(--card-bg); + border: 1px solid var(--border-color); + border-radius: 12px; + padding: 1rem; + overflow-y: scroll; /* Always show scrollbar */ + flex: 1; + min-height: 0; /* Allow flex item to shrink */ +} + +.starbase-purchase-list { + overflow-y: scroll; /* Always show scrollbar */ + max-height: calc(100vh - 400px); /* Fixed height to ensure scrolling */ + min-height: 200px; /* Minimum height to show scrollbar */ +} + +/* Add scrollbar styling for purchase list */ +.starbase-purchase-list::-webkit-scrollbar { + width: 8px; +} + +.starbase-purchase-list::-webkit-scrollbar-track { + background: var(--bg-secondary); + border-radius: 4px; +} + +.starbase-purchase-list::-webkit-scrollbar-thumb { + background: var(--primary-color); + border-radius: 4px; +} + +.starbase-purchase-list::-webkit-scrollbar-thumb:hover { + background: var(--primary-color); + opacity: 0.8; +} + +/* Ensure scrollbars are visible */ +.starbase-list::-webkit-scrollbar, +.starbase-shop::-webkit-scrollbar { + width: 8px; +} + +.starbase-list::-webkit-scrollbar-track, +.starbase-shop::-webkit-scrollbar-track { + background: var(--bg-secondary); + border-radius: 4px; +} + +.starbase-list::-webkit-scrollbar-thumb, +.starbase-shop::-webkit-scrollbar-thumb { + background: var(--primary-color); + border-radius: 4px; +} + +.starbase-list::-webkit-scrollbar-thumb:hover, +.starbase-shop::-webkit-scrollbar-thumb:hover { + background: var(--primary-color); + opacity: 0.8; +} + +.starbase-item { + background: var(--bg-tertiary); + border: 1px solid var(--border-color); + border-radius: 8px; + padding: 1rem; + margin-bottom: 0.75rem; + cursor: pointer; + transition: all 0.3s ease; + max-height: 150px; + overflow-y: auto; +} + +.starbase-item:hover { + border-color: var(--primary-color); + transform: translateY(-2px); +} + +.starbase-item h4 { + margin: 0 0 0.5rem 0; + color: var(--text-primary); + font-size: 0.9rem; +} + +.starbase-item p { + margin: 0; + color: var(--text-secondary); + font-size: 0.8rem; +} + +.starbase-item .stats { + margin-top: 0.5rem; + font-size: 0.75rem; + color: var(--text-secondary); +} + +.starbase-item .level { + color: var(--primary-color); + font-weight: 600; +} + +.starbase-item .description { + margin-top: 0.25rem; + line-height: 1.3; + max-height: 60px; + overflow-y: auto; + padding-right: 0.5rem; +} + +/* Base Upgrades Grid Layout */ +.upgrade-list { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); + gap: 1rem; + max-height: 400px; + overflow-y: auto; + padding: 0.5rem; +} + +.upgrade-item { + background: var(--bg-tertiary); + border: 1px solid var(--border-color); + border-radius: 8px; + padding: 1rem; + cursor: pointer; + transition: all 0.3s ease; +} + +.upgrade-item:hover { + border-color: var(--primary-color); + transform: translateY(-2px); +} + +.upgrade-item h4 { + margin: 0 0 0.5rem 0; + color: var(--text-primary); + font-size: 0.9rem; +} + +.upgrade-item p { + margin: 0; + color: var(--text-secondary); + font-size: 0.8rem; +} + +.upgrade-item .cost { + margin-top: 0.5rem; + color: var(--primary-color); + font-weight: 600; + font-size: 0.85rem; +} + +/* Custom Title Bar */ +.title-bar { + position: fixed; + top: 0; + left: 0; + right: 0; + height: 32px; + background: var(--bg-primary); + border-bottom: 1px solid var(--border-color); + display: flex; + justify-content: space-between; + align-items: center; + padding: 0 8px; + z-index: 10000; + -webkit-app-region: drag; + user-select: none; +} + +.title-bar-left { + display: flex; + align-items: center; +} + +.title-bar-title { + font-size: 14px; + font-weight: 600; + color: var(--text-primary); + font-family: 'Orbitron', monospace; +} + +.title-bar-right { + display: flex; + gap: 4px; +} + +.title-bar-btn { + width: 24px; + height: 24px; + border: none; + background: transparent; + color: var(--text-primary); + border-radius: 4px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + font-size: 12px; + -webkit-app-region: no-drag; + transition: all 0.2s ease; +} + +.title-bar-btn:hover { + background: var(--bg-secondary); +} + +.title-bar-btn.close-btn:hover { + background: #e74c3c; + color: white; +} + +/* Adjust main content to account for title bar */ +#app { + margin-top: 32px; +} + +/* Fullscreen styles */ +body.fullscreen .title-bar { + display: none; +} + +body.fullscreen #app { + margin-top: 0; +} + +body.fullscreen .game-interface, +body.fullscreen .main-content { + height: 100vh !important; + max-height: 100vh !important; +} + +body.fullscreen .dashboard-grid, +body.fullscreen .dungeons-container, +body.fullscreen .skills-container, +body.fullscreen .base-container, +body.fullscreen .quests-container, +body.fullscreen .inventory-container, +body.fullscreen .shop-container { + height: calc(100vh - 120px) !important; + overflow-y: auto; +} + +/* Inventory Slots - Responsive & Larger */ +.inventory-slot { + width: clamp(160px, 14vw, 280px); + height: clamp(160px, 14vw, 280px); + min-width: 160px; + min-height: 160px; + max-width: 280px; + max-height: 280px; + background: var(--bg-secondary); + border: 1px solid var(--border-color); + border-radius: 8px; + display: flex; + align-items: center; + justify-content: center; + position: relative; +} + +/* Item Cards - Square */ +.item-card { + width: 100%; + height: 100%; + background: var(--bg-tertiary); + border: 1px solid var(--border-color); + border-radius: 8px; + padding: 0.5rem; + cursor: pointer; + transition: all 0.3s ease; + position: relative; + overflow: hidden; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +.item-card::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 2px; + background: var(--gradient-primary); + transform: scaleX(0); + transition: transform 0.3s ease; +} + +.item-card:hover { + border-color: var(--primary-color); + transform: translateY(-2px); + box-shadow: 0 5px 15px rgba(0, 212, 255, 0.2); +} + +.item-card:hover::before { + transform: scaleX(1); +} + +.item-card.common { + border-color: #888; +} + +.item-card.rare { + border-color: #0088ff; + box-shadow: 0 0 10px rgba(0, 136, 255, 0.3); +} + +.item-card.epic { + border-color: #8833ff; + box-shadow: 0 0 15px rgba(136, 51, 255, 0.3); +} + +.item-card.legendary { + border-color: #ff8800; + box-shadow: 0 0 20px rgba(255, 136, 0, 0.3); +} + +/* Item Icon - Centered & Responsive */ +.item-icon { + flex: 1; + display: flex; + align-items: center; + justify-content: center; + margin-bottom: 0.5rem; +} + +.item-icon img, +.item-icon i { + max-width: 50%; + max-height: 50%; + width: 50%; + height: 50%; + min-width: 80px; + min-height: 80px; + max-width: 160px; + max-height: 160px; + object-fit: contain; +} + +/* Item Info - Compact & Responsive */ +.item-info { + text-align: center; + font-size: clamp(0.6rem, 1.5vw, 0.9rem); + line-height: 1; +} + +.item-name { + font-weight: 600; + margin-bottom: 0.2rem; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + max-width: 100%; +} + +.item-rarity { + font-size: clamp(0.5rem, 1.2vw, 0.7rem); + opacity: 0.8; +} + +/* Item Quantity Badge - Responsive */ +.item-quantity { + position: absolute; + top: 4px; + right: 4px; + background: var(--primary-color); + color: white; + font-size: clamp(0.5rem, 1.2vw, 0.7rem); + font-weight: bold; + padding: clamp(2px, 0.5vw, 6px) clamp(4px, 0.8vw, 12px); + border-radius: 6px; + min-width: clamp(20px, 3vw, 32px); + text-align: center; +} + +/* Skill Items */ +.skill-item { + background: var(--bg-tertiary); + border: 1px solid var(--border-color); + border-radius: 8px; + padding: 1.5rem; + cursor: pointer; + transition: all 0.3s ease; +} + +.skill-item:hover { + border-color: var(--primary-color); + transform: translateY(-2px); + box-shadow: 0 5px 15px rgba(0, 212, 255, 0.2); +} + +.skill-item.locked { + opacity: 0.5; + cursor: not-allowed; +} + +.skill-item.locked:hover { + transform: none; + box-shadow: none; + border-color: var(--border-color); +} + +.skill-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 0.5rem; +} + +.skill-name { + font-weight: 700; + color: var(--text-primary); +} + +.skill-level { + color: var(--primary-color); + font-weight: 700; +} + +.skill-description { + color: var(--text-secondary); + font-size: 0.9rem; + margin-bottom: 1rem; +} + +.skill-progress { + margin-top: 0.5rem; +} + +/* Quest Items */ +.quest-item { + background: var(--bg-tertiary); + border: 1px solid var(--border-color); + border-radius: 8px; + padding: 1.5rem; + cursor: pointer; + transition: all 0.3s ease; +} + +.quest-item:hover { + border-color: var(--primary-color); + transform: translateY(-2px); + box-shadow: 0 5px 15px rgba(0, 212, 255, 0.2); +} + +.quest-item.completed { + border-color: var(--success-color); + background: linear-gradient(135deg, rgba(0, 255, 136, 0.1), rgba(0, 204, 102, 0.1)); +} + +.quest-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 0.5rem; +} + +.quest-title { + font-weight: 700; + color: var(--text-primary); +} + +.quest-reward { + color: var(--warning-color); + font-weight: 700; +} + +.quest-description { + color: var(--text-secondary); + font-size: 0.9rem; + margin-bottom: 1rem; +} + +.quest-progress { + margin-top: 0.5rem; +} + +/* Dungeon Items */ +.dungeon-item { + background: var(--bg-tertiary); + border: 1px solid var(--border-color); + border-radius: 8px; + padding: 1rem; + cursor: pointer; + transition: all 0.3s ease; +} + +.dungeon-item:hover { + border-color: var(--primary-color); + transform: translateY(-2px); + box-shadow: 0 5px 15px rgba(0, 212, 255, 0.2); +} + +.dungeon-item.selected { + border-color: var(--primary-color); + background: rgba(0, 212, 255, 0.2); +} + +.dungeon-name { + font-weight: 700; + color: var(--text-primary); + margin-bottom: 0.25rem; +} + +.dungeon-difficulty { + font-size: 0.8rem; + margin-bottom: 0.25rem; +} + +.dungeon-difficulty.easy { + color: var(--success-color); +} + +.dungeon-difficulty.medium { + color: var(--warning-color); +} + +.dungeon-difficulty.hard { + color: var(--error-color); +} + +.dungeon-rewards { + font-size: 0.8rem; + color: var(--text-secondary); +} + +/* Shop Items */ +.shop-item { + background: var(--bg-tertiary); + border: 1px solid var(--border-color); + border-radius: 8px; + padding: 1.5rem; + transition: all 0.3s ease; +} + +.shop-item:hover { + border-color: var(--primary-color); + transform: translateY(-2px); + box-shadow: 0 5px 15px rgba(0, 212, 255, 0.2); +} + +.shop-item.purchased { + opacity: 0.5; + cursor: not-allowed; +} + +.shop-item.purchased:hover { + transform: none; + box-shadow: none; + border-color: var(--border-color); +} + +.shop-name { + font-weight: 700; + color: var(--text-primary); + margin-bottom: 0.5rem; +} + +.shop-description { + color: var(--text-secondary); + font-size: 0.9rem; + margin-bottom: 1rem; +} + +.shop-price { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 1rem; +} + +.shop-cost { + color: var(--warning-color); + font-weight: 700; +} + +/* Loading States */ +.loading { + position: relative; + overflow: hidden; +} + +.loading::after { + content: ''; + position: absolute; + top: 0; + left: -100%; + width: 100%; + height: 100%; + background: linear-gradient(90deg, transparent, rgba(0, 212, 255, 0.2), transparent); + animation: loadingShine 1.5s infinite; +} + +@keyframes loadingShine { + 0% { left: -100%; } + 100% { left: 100%; } +} + +/* Animations */ +@keyframes pulse { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.5; } +} + +@keyframes bounce { + 0%, 100% { transform: translateY(0); } + 50% { transform: translateY(-10px); } +} + +@keyframes rotate { + from { transform: rotate(0deg); } + to { transform: rotate(360deg); } +} + +@keyframes glow { + 0%, 100% { box-shadow: 0 0 5px rgba(0, 212, 255, 0.5); } + 50% { box-shadow: 0 0 20px rgba(0, 212, 255, 0.8); } +} + +.pulse { + animation: pulse 2s infinite; +} + +.bounce { + animation: bounce 2s infinite; +} + +.rotate { + animation: rotate 2s linear infinite; +} + +.glow { + animation: glow 2s infinite; +} + +/* Utility Classes */ +.text-center { text-align: center; } +.text-left { text-align: left; } +.text-right { text-align: right; } + +.flex { display: flex; } +.flex-column { flex-direction: column; } +.flex-center { align-items: center; justify-content: center; } +.flex-between { justify-content: space-between; } +.flex-wrap { flex-wrap: wrap; } + +.mt-1 { margin-top: 0.5rem; } +.mt-2 { margin-top: 1rem; } +.mt-3 { margin-top: 1.5rem; } +.mt-4 { margin-top: 2rem; } + +.mb-1 { margin-bottom: 0.5rem; } +.mb-2 { margin-bottom: 1rem; } +.mb-3 { margin-bottom: 1.5rem; } +.mb-4 { margin-bottom: 2rem; } + +.ml-1 { margin-left: 0.5rem; } +.ml-2 { margin-left: 1rem; } +.mr-1 { margin-right: 0.5rem; } +.mr-2 { margin-right: 1rem; } + +.p-1 { padding: 0.5rem; } +.p-2 { padding: 1rem; } +.p-3 { padding: 1.5rem; } +.p-4 { padding: 2rem; } + +.w-full { width: 100%; } +.h-full { height: 100%; } diff --git a/Client-Server/styles/main.css b/Client-Server/styles/main.css new file mode 100644 index 0000000..2b6e807 --- /dev/null +++ b/Client-Server/styles/main.css @@ -0,0 +1,2544 @@ +/* Galaxy Strike Online - Main Styles */ + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +:root { + --primary-color: #00d4ff; + --secondary-color: #ff6b35; + --accent-color: #ff00ff; + --bg-primary: #0a0e1a; + --bg-secondary: #151923; + --bg-tertiary: #1e2433; + --text-primary: #ffffff; + --text-secondary: #b8c5d6; + --text-muted: #6b7c93; + --border-color: #2a3241; + --success-color: #00ff88; + --warning-color: #ffaa00; + --error-color: #ff3366; + --card-bg: rgba(30, 36, 51, 0.8); + --hover-bg: rgba(0, 212, 255, 0.1); + --gradient-primary: linear-gradient(135deg, #00d4ff, #0099cc); + --gradient-secondary: linear-gradient(135deg, #ff6b35, #ff4500); +} + +body { + font-family: 'Space Mono', monospace; + background: var(--bg-primary); + color: var(--text-primary); + overflow: hidden; + background-image: + radial-gradient(circle at 20% 50%, rgba(0, 212, 255, 0.1) 0%, transparent 50%), + radial-gradient(circle at 80% 80%, rgba(255, 107, 53, 0.1) 0%, transparent 50%), + radial-gradient(circle at 40% 20%, rgba(255, 0, 255, 0.05) 0%, transparent 50%); +} + +/* Login Form Styles */ +.login-form { + display: flex; + flex-direction: column; + gap: 20px; + margin-bottom: 20px; +} + +.form-group { + display: flex; + flex-direction: column; + gap: 8px; +} + +.form-group label { + color: var(--text-secondary); + font-size: 0.9rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 1px; +} + +.form-input { + background: var(--bg-tertiary); + border: 2px solid var(--border-color); + border-radius: 8px; + padding: 12px 16px; + color: var(--text-primary); + font-family: 'Space Mono', monospace; + font-size: 1rem; + transition: all 0.3s ease; +} + +.form-input:focus { + outline: none; + border-color: var(--primary-color); + box-shadow: 0 0 10px rgba(0, 212, 255, 0.3); +} + +.form-input::placeholder { + color: var(--text-muted); +} + +/* Server Browser Styles */ +.server-controls { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 20px; + gap: 15px; + flex-wrap: wrap; +} + +.server-filters { + display: flex; + gap: 10px; +} + +.filter-select { + background: var(--bg-tertiary); + border: 2px solid var(--border-color); + border-radius: 8px; + padding: 8px 12px; + color: var(--text-primary); + font-family: 'Space Mono', monospace; + font-size: 0.9rem; + cursor: pointer; + transition: all 0.3s ease; +} + +.filter-select:focus { + outline: none; + border-color: var(--primary-color); +} + +.server-list { + max-height: calc(100vh - 350px); + overflow-y: auto; + margin-bottom: 20px; + border: 1px solid var(--border-color); + border-radius: 10px; + background: var(--bg-tertiary); +} + +.server-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 15px 20px; + border-bottom: 1px solid var(--border-color); + cursor: pointer; + transition: all 0.3s ease; +} + +.server-item:last-child { + border-bottom: none; +} + +.server-item:hover { + background: var(--hover-bg); + border-left: 4px solid var(--primary-color); +} + +.server-info { + flex: 1; +} + +.server-name { + font-size: 1.1rem; + font-weight: 600; + color: var(--text-primary); + margin-bottom: 5px; +} + +.server-details { + font-size: 0.9rem; + color: var(--text-secondary); + display: flex; + gap: 15px; + flex-wrap: wrap; +} + +.server-detail { + display: flex; + align-items: center; + gap: 5px; +} + +.server-actions-right { + display: flex; + align-items: center; + gap: 10px; +} + +.server-player-count { + background: var(--bg-secondary); + padding: 4px 8px; + border-radius: 4px; + font-size: 0.8rem; + color: var(--text-secondary); + border: 1px solid var(--border-color); +} + +.server-type { + padding: 4px 8px; + border-radius: 4px; + font-size: 0.8rem; + font-weight: 600; + text-transform: uppercase; +} + +.server-type.public { + background: rgba(0, 255, 136, 0.2); + color: var(--success-color); + border: 1px solid var(--success-color); +} + +.server-type.private { + background: rgba(255, 170, 0, 0.2); + color: var(--warning-color); + border: 1px solid var(--warning-color); +} + +.server-region { + background: var(--bg-secondary); + padding: 4px 8px; + border-radius: 4px; + font-size: 0.8rem; + color: var(--text-muted); + border: 1px solid var(--border-color); +} + +.server-loading, .server-empty { + text-align: center; + padding: 60px 20px; + color: var(--text-muted); +} + +.server-loading i, .server-empty i { + font-size: 3rem; + margin-bottom: 15px; + opacity: 0.5; +} + +.server-loading p, .server-empty p { + font-size: 1.1rem; +} + +/* Server Confirmation Styles */ +.server-confirmation { + display: flex; + justify-content: space-between; + align-items: center; + gap: 30px; + margin-bottom: 30px; +} + +.server-preview { + background: var(--bg-tertiary); + border: 2px solid var(--primary-color); + border-radius: 15px; + padding: 25px; + text-align: center; + min-width: 300px; + box-shadow: 0 10px 30px rgba(0, 212, 255, 0.2); +} + +.server-preview h3 { + color: var(--primary-color); + font-size: 1.4rem; + font-weight: 700; + margin-bottom: 15px; + text-transform: uppercase; + letter-spacing: 1px; +} + +.server-details { + color: var(--text-secondary); + font-size: 0.9rem; + line-height: 1.6; +} + +.server-info { + margin: 8px 0; + display: flex; + justify-content: space-between; + align-items: center; +} + +.server-info span { + font-weight: 600; + color: var(--text-primary); +} + +.confirm-actions-left, .confirm-actions-right { + display: flex; + flex-direction: column; + gap: 15px; + min-width: 200px; +} + +.selected-server-info-center { + flex: 1; + display: flex; + justify-content: center; + align-items: center; +} + +/* Responsive Design */ +@media (max-width: 768px) { + .server-controls { + flex-direction: column; + align-items: stretch; + } + + .server-filters { + justify-content: center; + } + + .server-confirmation { + flex-direction: column; + gap: 20px; + } + + .confirm-actions-left, .confirm-actions-right { + width: 100%; + max-width: 300px; + } + + .server-details { + flex-direction: column; + gap: 8px; + } +} + +/* Animations */ +@keyframes fadeInUp { + from { + opacity: 0; + transform: translateY(30px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes pulse { + 0%, 100% { + opacity: 1; + } + 50% { + opacity: 0.5; + } +} + +.server-loading i { + animation: pulse 1.5s infinite; +} +.main-menu { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: var(--bg-primary); + display: flex; + align-items: center; + justify-content: center; + background-image: + radial-gradient(circle at 20% 50%, rgba(0, 212, 255, 0.1) 0%, transparent 50%), + radial-gradient(circle at 80% 80%, rgba(255, 107, 53, 0.1) 0%, transparent 50%), + radial-gradient(circle at 40% 20%, rgba(255, 0, 255, 0.05) 0%, transparent 50%); +} + +.menu-container { + width: 90%; + max-width: 800px; + background: var(--card-bg); + border-radius: 20px; + border: 1px solid var(--border-color); + box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5); + backdrop-filter: blur(10px); + overflow: hidden; +} + +.menu-header { + text-align: center; + padding: 40px 20px 20px; + background: var(--gradient-primary); + position: relative; +} + +.menu-title { + font-family: 'Orbitron', sans-serif; + font-size: 3rem; + font-weight: 900; + color: var(--text-primary); + text-transform: uppercase; + letter-spacing: 3px; + margin-bottom: 10px; + text-shadow: 0 0 20px rgba(0, 212, 255, 0.5); +} + +.menu-subtitle { + font-size: 1.2rem; + color: var(--text-secondary); + font-weight: 400; + opacity: 0.9; +} + +.menu-content { + padding: 30px; +} + +.menu-section { + animation: fadeInUp 0.5s ease-out; +} + +.section-title { + font-family: 'Orbitron', sans-serif; + font-size: 1.8rem; + color: var(--primary-color); + text-align: center; + margin-bottom: 30px; + text-transform: uppercase; + letter-spacing: 2px; +} + +/* Login Section */ +.login-options { + display: flex; + flex-direction: column; + gap: 20px; + margin-bottom: 20px; +} + +.btn-large { + padding: 20px 40px; + font-size: 1.2rem; + font-weight: 600; + border-radius: 12px; + transition: all 0.3s ease; + gap: 10px; + width: auto; +} + +.btn-large i { + font-size: 1.4rem; +} + +/* Enhanced server confirmation buttons */ +.btn-join-server { + background: linear-gradient(135deg, #00ff88, #00cc66) !important; + color: #000 !important; + border: 3px solid #00ff88 !important; + box-shadow: 0 6px 20px rgba(0, 255, 136, 0.4) !important; + font-weight: 700 !important; + transform: scale(1.05); + z-index: 10; + position: relative; +} + +.btn-join-server:hover { + background: linear-gradient(135deg, #00ffaa, #00ff88) !important; + transform: scale(1.08) translateY(-2px); + box-shadow: 0 8px 25px rgba(0, 255, 136, 0.5) !important; +} + +.login-notice { + text-align: center; + padding: 15px; + background: rgba(0, 212, 255, 0.1); + border: 1px solid var(--primary-color); + border-radius: 8px; + color: var(--text-secondary); +} + +.login-notice i { + color: var(--primary-color); + margin-right: 8px; +} + +/* Save Selection */ +.save-slots { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 20px; + margin-bottom: 30px; +} + +.save-slot { + background: var(--bg-secondary); + border: 2px solid var(--border-color); + border-radius: 12px; + padding: 20px; + cursor: pointer; + transition: all 0.3s ease; + position: relative; +} + +.save-slot:hover { + border-color: var(--primary-color); + transform: translateY(-2px); + box-shadow: 0 8px 25px rgba(0, 212, 255, 0.2); +} + +.slot-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 15px; +} + +.slot-number { + font-family: 'Orbitron', sans-serif; + font-weight: 700; + color: var(--text-primary); + font-size: 1.1rem; +} + +.slot-status { + padding: 4px 12px; + border-radius: 20px; + font-size: 0.8rem; + font-weight: 600; + text-transform: uppercase; +} + +.slot-status.empty { + background: var(--bg-tertiary); + color: var(--text-muted); +} + +.slot-status.has-data { + background: var(--success-color); + color: var(--bg-primary); +} + +.slot-info { + margin-bottom: 15px; +} + +.slot-name { + font-weight: 700; + color: var(--text-primary); + margin-bottom: 5px; +} + +.slot-details { + color: var(--text-muted); + font-size: 0.9rem; +} + +.slot-btn { + width: 100%; + padding: 10px; + background: var(--gradient-primary); + border: none; + border-radius: 6px; + color: var(--text-primary); + font-weight: 600; + cursor: pointer; + transition: all 0.3s ease; +} + +.slot-btn:hover { + transform: translateY(-1px); + box-shadow: 0 4px 15px rgba(0, 212, 255, 0.3); +} + +/* Save Confirmation Section */ +.save-confirmation { + display: flex !important; + justify-content: space-between !important; + gap: 30px !important; + margin-bottom: 30px !important; +} + +.confirm-actions-left { + position: relative !important; + min-width: 200px; + height: 300px; +} + +.selected-save-info-center { + display: flex !important; + flex-direction: column !important; + justify-content: center !important; + align-items: center !important; + flex: 1 !important; + min-width: 300px; +} + +.save-preview { + background: rgba(0, 212, 255, 0.1) !important; + border: 2px solid rgba(0, 212, 255, 0.3) !important; + border-radius: 10px !important; + padding: 15px !important; + text-align: left !important; + width: 100% !important; + max-width: 300px !important; + font-family: 'Space Mono', monospace !important; +} + +.save-preview h3 { + color: #00d4ff !important; + margin-bottom: 10px !important; + font-size: 1.1em !important; + text-align: center !important; +} + +.save-details { + color: #ffffff !important; + font-size: 0.9em !important; + line-height: 1.6 !important; + white-space: pre-wrap !important; +} + +.save-info { + margin: 3px 0 !important; + font-family: 'Space Mono', monospace !important; +} + +.confirm-actions-right { + position: relative !important; + min-width: 200px; + height: 300px; +} + +.confirm-actions-left .btn-large { + position: absolute !important; + width: 200px !important; + display: flex !important; + align-items: center !important; + justify-content: center !important; +} + +.confirm-actions-left #confirmContinueBtn { + bottom: 0 !important; + left: 0 !important; +} + +.confirm-actions-left #confirmNewGameBtn { + bottom: 60px !important; + left: 0 !important; +} + +/* Override display: none to actually hide buttons */ +.confirm-actions-left .btn-large[style*="display: none"] { + display: none !important; +} + +.confirm-actions-right .btn-large { + position: absolute !important; + width: 200px !important; + display: flex !important; + align-items: center !important; + justify-content: center !important; +} + +.confirm-actions-right #confirmSettingsBtn { + top: 0 !important; + right: 0 !important; +} + +.confirm-actions-right #confirmDeleteBtn { + bottom: 0 !important; + right: 0 !important; + background: linear-gradient(135deg, #ff3366, #ff0033) !important; + color: white !important; + box-shadow: 0 4px 15px rgba(255, 51, 102, 0.3) !important; +} + +.confirm-actions-right #confirmDeleteBtn:hover { + transform: translateY(-2px) !important; + box-shadow: 0 6px 20px rgba(255, 51, 102, 0.4) !important; +} + +.confirm-navigation { + display: flex; + justify-content: center; + margin-top: 20px; +} +.options-grid { + display: flex !important; + justify-content: space-between !important; + gap: 30px; + margin-bottom: 30px; +} + +.options-left { + position: relative !important; + min-width: 200px; + height: 300px; +} + +.options-center { + display: flex !important; + flex-direction: column !important; + justify-content: center !important; + align-items: center !important; + flex: 1 !important; + min-width: 300px; +} + +.save-info-display { + background: rgba(0, 212, 255, 0.1) !important; + border: 2px solid rgba(0, 212, 255, 0.3) !important; + border-radius: 10px !important; + padding: 20px !important; + text-align: center !important; + width: 100% !important; + max-width: 400px !important; +} + +.save-info-display h3 { + color: #00d4ff !important; + margin-bottom: 15px !important; + font-size: 1.2em !important; +} + +#saveInfoDetails { + color: #ffffff !important; + font-size: 0.9em !important; + line-height: 1.4 !important; +} + +.save-info-content { + background: rgba(0, 100, 200, 0.2) !important; + border-radius: 8px !important; + padding: 15px !important; + margin: 0 !important; +} + +.options-right { + position: relative !important; + min-width: 200px; + height: 300px; +} + +.options-left .btn-large { + position: absolute !important; + width: 200px !important; + display: flex !important; + align-items: center !important; + justify-content: center !important; +} + +.options-left #continueBtn { + bottom: 0 !important; + left: 0 !important; +} + +.options-left #newGameBtn { + bottom: 60px !important; + left: 0 !important; +} + +/* Override display: none to actually hide buttons */ +.options-left .btn-large[style*="display: none"] { + display: none !important; +} + +.options-right .btn-large { + position: absolute !important; + width: 200px !important; + display: flex !important; + align-items: center !important; + justify-content: center !important; +} + +.options-right #settingsBtn { + top: 0 !important; + right: 0 !important; +} + +.options-right #deleteSaveBtn { + bottom: 0 !important; + right: 0 !important; +} + +.options-actions, .save-actions { + display: flex; + justify-content: center; +} + +/* Menu Footer */ +.menu-footer { + padding: 20px 30px; + background: var(--bg-secondary); + border-top: 1px solid var(--border-color); + display: flex; + justify-content: space-between; + align-items: center; +} + +.version-text { + color: var(--text-muted); + font-size: 0.9rem; +} + +.footer-links { + display: flex; + gap: 20px; +} + +.link-btn { + background: none; + border: none; + color: var(--text-muted); + cursor: pointer; + font-size: 0.9rem; + transition: color 0.3s ease; +} + +.link-btn:hover { + color: var(--primary-color); +} + +/* Shop container styles */ +.shop-container { + display: flex !important; + flex-direction: column !important; + height: 100% !important; + background: var(--bg-secondary) !important; + border-radius: 8px !important; + overflow: hidden !important; +} + +.shop-header { + position: sticky !important; + top: 0 !important; + z-index: 10 !important; + background: var(--bg-primary) !important; + border-bottom: 2px solid var(--border-color) !important; + padding: 15px !important; +} + +.shop-categories { + display: flex !important; + gap: 10px !important; + justify-content: center !important; + flex-wrap: wrap !important; +} + +.shop-cat-btn { + padding: 10px 20px !important; + background: var(--bg-secondary) !important; + color: var(--text-primary) !important; + border: 2px solid var(--border-color) !important; + border-radius: 6px !important; + cursor: pointer !important; + font-weight: 600 !important; + transition: all 0.3s ease !important; + font-size: 0.9em !important; +} + +.shop-cat-btn:hover { + background: var(--accent-color) !important; + border-color: var(--accent-color) !important; + transform: translateY(-2px) !important; +} + +.shop-cat-btn.active { + background: var(--accent-color) !important; + color: var(--text-primary) !important; + border-color: var(--accent-color) !important; + box-shadow: 0 0 10px rgba(0, 212, 255, 0.3) !important; +} + +.shop-content { + flex: 1 !important; + overflow-y: auto !important; + padding: 20px !important; +} + +.shop-items-container { + max-width: 1200px !important; + margin: 0 auto !important; +} + +.shop-items { + display: grid !important; + grid-template-columns: repeat(auto-fill, minmax(350px, 1fr)) !important; + gap: 20px !important; + align-items: start !important; +} + +/* Shop item styles */ +.shop-item { + background: rgba(0, 212, 255, 0.05) !important; + border: 1px solid rgba(0, 212, 255, 0.2) !important; + border-radius: 8px !important; + padding: 15px !important; + margin-bottom: 15px !important; + transition: all 0.3s ease !important; +} + +.shop-item:hover { + background: rgba(0, 212, 255, 0.1) !important; + border-color: rgba(0, 212, 255, 0.4) !important; + transform: translateY(-2px) !important; +} + +.shop-item-content { + display: flex !important; + align-items: flex-start !important; + gap: 15px !important; +} + +.shop-item-image { + flex-shrink: 0 !important; + width: 80px !important; + height: 80px !important; + border-radius: 4px !important; + overflow: hidden !important; + background: rgba(0, 0, 0, 0.3) !important; + display: flex !important; + align-items: center !important; + justify-content: center !important; +} + +.shop-item-image img { + width: 100% !important; + height: 100% !important; + object-fit: contain !important; + image-rendering: pixelated !important; +} + +.shop-item-info { + flex: 1 !important; + min-width: 0 !important; +} + +.shop-item-name { + font-size: 1.1em !important; + font-weight: 600 !important; + color: #00d4ff !important; + margin-bottom: 5px !important; +} + +.shop-item-description { + color: #ffffff !important; + font-size: 0.9em !important; + margin-bottom: 10px !important; + line-height: 1.4 !important; +} + +.shop-item-stats { + display: flex !important; + flex-wrap: wrap !important; + gap: 10px !important; + margin-bottom: 10px !important; +} + +.shop-item-stats .stat { + background: rgba(0, 212, 255, 0.1) !important; + padding: 2px 8px !important; + border-radius: 4px !important; + font-size: 0.8em !important; + color: #ffffff !important; + border: 1px solid rgba(0, 212, 255, 0.2) !important; +} + +.shop-item-price { + font-size: 1.0em !important; + font-weight: 600 !important; + color: #ffd700 !important; + margin-bottom: 5px !important; +} + +.shop-item-rarity { + display: inline-block !important; + padding: 2px 8px !important; + border-radius: 4px !important; + font-size: 0.8em !important; + font-weight: 600 !important; + text-transform: uppercase !important; + margin-bottom: 10px !important; +} + +.shop-item-rarity.common { + background: rgba(128, 128, 128, 0.2) !important; + color: #808080 !important; + border: 1px solid rgba(128, 128, 128, 0.4) !important; +} + +.shop-item-rarity.uncommon { + background: rgba(0, 255, 0, 0.2) !important; + color: #00ff00 !important; + border: 1px solid rgba(0, 255, 0, 0.4) !important; +} + +.shop-item-rarity.rare { + background: rgba(0, 100, 255, 0.2) !important; + color: #0064ff !important; + border: 1px solid rgba(0, 100, 255, 0.4) !important; +} + +.shop-item-rarity.epic { + background: rgba(128, 0, 255, 0.2) !important; + color: #8000ff !important; + border: 1px solid rgba(128, 0, 255, 0.4) !important; +} + +.shop-item-rarity.legendary { + background: rgba(255, 128, 0, 0.2) !important; + color: #ff8000 !important; + border: 1px solid rgba(255, 128, 0, 0.4) !important; +} + +.shop-item-purchase-btn { + width: 100% !important; + padding: 8px 16px !important; + background: linear-gradient(135deg, #00d4ff, #0099cc) !important; + color: white !important; + border: none !important; + border-radius: 4px !important; + cursor: pointer !important; + font-weight: 600 !important; + transition: all 0.3s ease !important; +} + +.shop-item-purchase-btn:hover:not(.disabled) { + background: linear-gradient(135deg, #00ffcc, #00ccaa) !important; + transform: translateY(-1px) !important; +} + +.shop-item-purchase-btn.disabled { + background: rgba(100, 100, 100, 0.3) !important; + color: #666666 !important; + cursor: not-allowed !important; +} + +/* Shop Refresh Info */ +.shop-refresh-info { + background: var(--bg-tertiary); + border: 1px solid var(--border-color); + border-radius: 8px; + padding: 1rem; + margin-bottom: 1.5rem; + display: flex; + justify-content: space-between; + align-items: center; + grid-column: 1 / -1; +} + +.refresh-info-left { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.refresh-countdown { + display: flex; + align-items: center; + gap: 0.5rem; + color: var(--text-secondary); + font-size: 0.9rem; +} + +.refresh-countdown i { + color: var(--primary-color); +} + +.purchase-limit-info { + display: flex; + align-items: center; + gap: 0.5rem; + color: var(--text-secondary); + font-size: 0.9rem; +} + +.purchase-limit-info i { + color: #ffd700; +} + +.refresh-shop-btn { + background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)); + color: white; + border: none; + border-radius: 6px; + padding: 0.5rem 1rem; + font-size: 0.9rem; + cursor: pointer; + transition: all 0.3s ease; + display: flex; + align-items: center; + gap: 0.5rem; + white-space: nowrap; +} + +.refresh-shop-btn:hover { + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(0, 212, 255, 0.3); +} + +.refresh-shop-btn:active { + transform: translateY(0); +} + +/* Animations */ +@keyframes fadeInUp { + from { + opacity: 0; + transform: translateY(30px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.hidden { + display: none !important; +} + +/* Loading Screen */ +.loading-screen { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: var(--bg-primary); + display: flex; + align-items: center; + justify-content: center; + z-index: 9999; + transition: opacity 0.5s ease; +} + +.loading-content { + text-align: center; + max-width: 400px; +} + +.game-title { + font-family: 'Orbitron', sans-serif; + font-size: 3rem; + font-weight: 900; + background: var(--gradient-primary); + -webkit-background-clip: text; + background-clip: text; + -webkit-text-fill-color: transparent; + margin-bottom: 2rem; + text-transform: uppercase; + letter-spacing: 3px; +} + +.loading-bar { + width: 100%; + height: 4px; + background: var(--bg-tertiary); + border-radius: 2px; + overflow: hidden; + margin-bottom: 1rem; +} + +.loading-progress { + height: 100%; + background: var(--gradient-primary); + width: 0%; + transition: width 0.3s ease; + animation: loading-pulse 2s infinite; +} + +@keyframes loading-pulse { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.7; } +} + +.loading-text { + color: var(--text-secondary); + font-size: 0.9rem; +} + +/* Game Interface */ +.game-interface { + width: 100vw; + height: 100vh; + display: flex; + flex-direction: column; +} + +.hidden { + display: none !important; +} + +/* Header */ +.game-header { + height: 60px; + background: var(--bg-secondary); + border-bottom: 1px solid var(--border-color); + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 1rem; + backdrop-filter: blur(10px); +} + +.header-left { + display: flex; + align-items: center; + gap: 1rem; +} + +.logo { + font-family: 'Orbitron', sans-serif; + font-size: 1.5rem; + font-weight: 900; + color: var(--primary-color); + text-shadow: 0 0 10px rgba(0, 212, 255, 0.5); +} + +.player-info { + display: flex; + flex-direction: column; +} + +.player-name { + font-weight: 700; + color: var(--text-primary); +} + +.player-level { + font-size: 0.8rem; + color: var(--primary-color); +} + +.header-center { + flex: 1; + display: flex; + justify-content: center; +} + +.resources { + display: flex; + gap: 1.5rem; +} + +.resource { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.5rem 1rem; + background: var(--bg-tertiary); + border-radius: 20px; + border: 1px solid var(--border-color); + transition: all 0.3s ease; +} + +.resource:hover { + border-color: var(--primary-color); + box-shadow: 0 0 10px rgba(0, 212, 255, 0.3); +} + +.resource i { + color: var(--primary-color); +} + +.header-right { + display: flex; + gap: 0.5rem; +} + +/* Navigation */ +.main-nav { + height: 50px; + background: var(--bg-tertiary); + border-bottom: 1px solid var(--border-color); + display: flex; + align-items: center; + padding: 0 1rem; + gap: 0.5rem; + overflow-x: auto; + position: relative; + z-index: 10000; /* Ensure nav bar is above loading screen */ +} + +.nav-btn { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.5rem 1rem; + background: transparent; + border: 1px solid transparent; + border-radius: 8px; + color: var(--text-secondary); + cursor: pointer; + transition: all 0.3s ease; + white-space: nowrap; +} + +.nav-btn:hover { + background: var(--hover-bg); + color: var(--text-primary); + border-color: var(--primary-color); +} + +.nav-btn.active { + background: var(--gradient-primary); + color: var(--bg-primary); + border-color: transparent; +} + +.nav-btn i { + font-size: 1rem; +} + +/* Main Content */ +.main-content { + flex: 1; + overflow: hidden; /* Remove exterior scrollbar */ + padding: 1rem; + background: var(--bg-primary); + height: calc(100vh - 32px); /* Account for custom title bar */ +} + +.tab-content { + display: none; + animation: fadeIn 0.3s ease; +} + +.tab-content.active { + display: block; +} + +@keyframes fadeIn { + from { opacity: 0; transform: translateY(10px); } + to { opacity: 1; transform: translateY(0); } +} + +/* Dashboard */ +.dashboard-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: 1rem; + max-width: 1200px; + margin: 0 auto; + max-height: calc(100vh - 232px); /* Adjusted for title bar and padding */ + overflow-y: auto; + padding: 0.5rem; +} + +.card { + background: var(--card-bg); + border: 1px solid var(--border-color); + border-radius: 12px; + padding: 1.5rem; + backdrop-filter: blur(10px); + transition: all 0.3s ease; +} + +.card:hover { + border-color: var(--primary-color); + box-shadow: 0 0 20px rgba(0, 212, 255, 0.2); + transform: translateY(-2px); +} + +.card h3 { + color: var(--primary-color); + margin-bottom: 1rem; + font-family: 'Orbitron', sans-serif; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 1px; +} + +.fleet-info { + display: flex; + flex-direction: column; + gap: 1rem; +} + +.ship-status { + display: flex; + align-items: center; + gap: 1rem; +} + +.ship-status i { + font-size: 2rem; + color: var(--secondary-color); +} + +.idle-stats { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.quick-actions { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.stats-grid { + display: grid; + gap: 0.5rem; +} + +.stat { + display: flex; + justify-content: space-between; + padding: 0.5rem 0; + border-bottom: 1px solid var(--border-color); +} + +.stat:last-child { + border-bottom: none; +} + +.stat-label { + color: var(--text-secondary); +} + +.stat-value { + color: var(--primary-color); + font-weight: 700; +} + +/* Dungeons */ +.dungeons-container { + display: grid; + grid-template-columns: 300px 1fr; + gap: 1rem; + height: calc(100vh - 232px); /* Adjusted for title bar and padding */ +} + +.dungeon-selector { + background: var(--card-bg); + border: 1px solid var(--border-color); + border-radius: 12px; + padding: 1rem; + overflow-y: auto; +} + +.dungeon-list { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.dungeon-item { + padding: 1rem; + background: var(--bg-tertiary); + border: 1px solid var(--border-color); + border-radius: 8px; + cursor: pointer; + transition: all 0.3s ease; +} + +.dungeon-item:hover { + border-color: var(--primary-color); + background: var(--hover-bg); +} + +.dungeon-item.selected { + border-color: var(--primary-color); + background: rgba(0, 212, 255, 0.2); +} + +.dungeon-view { + background: var(--card-bg); + border: 1px solid var(--border-color); + border-radius: 12px; + padding: 2rem; + display: flex; + align-items: center; + justify-content: center; +} + +.dungeon-placeholder { + text-align: center; + color: var(--text-muted); +} + +.dungeon-placeholder i { + font-size: 4rem; + margin-bottom: 1rem; + opacity: 0.5; +} + +/* Skills */ +.skills-container { + max-width: 1200px; + margin: 0 auto; + max-height: calc(100vh - 232px); /* Adjusted for title bar and padding */ + overflow-y: auto; + padding: 0.5rem; +} + +.skill-categories { + display: flex; + gap: 0.5rem; + margin-bottom: 2rem; + justify-content: center; +} + +.skill-cat-btn { + padding: 0.75rem 1.5rem; + background: var(--bg-tertiary); + border: 1px solid var(--border-color); + border-radius: 8px; + color: var(--text-secondary); + cursor: pointer; + transition: all 0.3s ease; +} + +.skill-cat-btn:hover { + border-color: var(--primary-color); + color: var(--text-primary); +} + +.skill-cat-btn.active { + background: var(--gradient-primary); + color: var(--bg-primary); + border-color: transparent; +} + +.skills-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: 1rem; +} + +/* Base */ +.base-container { + display: grid; + grid-template-columns: 1fr 300px; + gap: 1rem; + height: calc(100vh - 232px); /* Adjusted for title bar (32px) + extra padding */ +} + +.base-view { + background: var(--card-bg); + border: 1px solid var(--border-color); + border-radius: 12px; + padding: 1rem; +} + +.base-upgrades { + background: var(--card-bg); + border: 1px solid var(--border-color); + border-radius: 12px; + padding: 1rem; + overflow: hidden; +} + +/* Quests */ +.quests-container { + max-width: 1200px; + margin: 0 auto; + max-height: calc(100vh - 232px); /* Adjusted for title bar and padding */ + overflow-y: auto; + padding: 0.5rem; +} + +.quest-tabs { + display: flex; + gap: 0.5rem; + margin-bottom: 2rem; + justify-content: center; +} + +.quest-tab-btn { + padding: 0.75rem 1.5rem; + background: var(--bg-tertiary); + border: 1px solid var(--border-color); + border-radius: 8px; + color: var(--text-secondary); + cursor: pointer; + transition: all 0.3s ease; +} + +.quest-tab-btn:hover { + border-color: var(--primary-color); + color: var(--text-primary); +} + +.quest-tab-btn.active { + background: var(--gradient-primary); + color: var(--bg-primary); + border-color: transparent; +} + +.quest-list { + display: flex; + flex-direction: column; + gap: 1rem; +} + +/* Inventory - Desktop Sidebar Layout */ +.inventory-container { + display: grid; + grid-template-columns: 1fr 300px; + gap: 2rem; + height: calc(100vh - 232px); /* Adjusted for title bar and padding */ +} + +.inventory-grid { + background: var(--card-bg); + border: 1px solid var(--border-color); + border-radius: 12px; + padding: 2rem; + overflow-y: auto; +} + +.item-details { + background: var(--card-bg); + border: 1px solid var(--border-color); + border-radius: 12px; + padding: 1rem; + overflow-y: auto; +} + +/* Shop */ +.shop-container { + max-width: 1200px; + margin: 0 auto; + max-height: calc(100vh - 232px); /* Adjusted for title bar and padding */ + overflow-y: auto; + padding: 0.5rem; +} + +.shop-categories { + display: flex; + gap: 0.5rem; + margin-bottom: 2rem; + justify-content: center; +} + +.shop-cat-btn { + padding: 0.75rem 1.5rem; + background: var(--bg-tertiary); + border: 1px solid var(--border-color); + border-radius: 8px; + color: var(--text-secondary); + cursor: pointer; + transition: all 0.3s ease; +} + +.shop-cat-btn:hover { + border-color: var(--primary-color); + color: var(--text-primary); +} + +.shop-cat-btn.active { + background: var(--gradient-primary); + color: var(--bg-primary); + border-color: transparent; +} + +.shop-items { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: 1rem; +} + +/* Responsive Design */ +@media (max-width: 768px) { + .dashboard-grid { + grid-template-columns: 1fr; + } + + .dungeons-container { + grid-template-columns: 1fr; + } + + .base-container { + grid-template-columns: 1fr; + } + + .inventory-container { + grid-template-columns: 1fr; + } + + .main-nav { + overflow-x: scroll; + } + + .resources { + flex-direction: column; + gap: 0.5rem; + } + + .resource { + padding: 0.25rem 0.5rem; + font-size: 0.8rem; + } + + .game-title { + font-size: 2rem; + } +} + +@media (max-width: 480px) { + .header-center { + display: none; + } + + .nav-btn span { + display: none; + } + + .nav-btn { + padding: 0.5rem; + } +} + +/* Quest difficulty indicators */ +.quest-difficulty { + display: flex; + gap: 2px; + font-size: 0.9rem; + color: #ffd700; + margin-right: 1rem; +} + +.difficulty-1 { + color: #4ade80; /* Green for easy */ +} + +.difficulty-2 { + color: #60a5fa; /* Blue for medium */ +} + +.difficulty-3 { + color: #f59e0b; /* Orange for hard */ +} + +.difficulty-4 { + color: #ef4444; /* Red for special/legendary */ +} + +/* Quest header layout */ +.quest-header-info { + display: flex; + align-items: center; + gap: 1rem; +} + +/* Completed quest styles */ +.all-objectives-completed { + color: #4ade80; + font-weight: 600; + padding: 0.5rem; + background: rgba(74, 222, 128, 0.1); + border-radius: 4px; + text-align: center; +} + +.completion-time { + font-size: 0.8rem; + color: var(--text-secondary); + margin-top: 0.5rem; + text-align: right; +} + +/* Daily countdown improvements */ +.daily-countdown { + margin-bottom: 1.5rem; + padding: 1rem; + background: var(--bg-tertiary); + border: 1px solid var(--border-color); + border-radius: 8px; +} + +/* Daily countdown improvements */ +.weekly-countdown { + margin-bottom: 1.5rem; + padding: 1rem; + background: var(--bg-tertiary); + border: 1px solid var(--border-color); + border-radius: 8px; +} + +.countdown-container { + display: flex; + align-items: center; + gap: 0.5rem; + justify-content: center; + color: var(--text-primary); +} + +.countdown-container i { + color: var(--primary-color); +} + +/* Modern Crafting Tab Styles - Match Skills Layout */ +.crafting-container { + padding: 20px; + max-width: 1200px; + margin: 0 auto; + max-height: calc(100vh - 232px); /* Adjusted for title bar and padding */ + overflow-y: auto; + padding: 0.5rem; +} + +/* Custom Scrollbar for Crafting Container */ +.crafting-container::-webkit-scrollbar { + width: 8px; +} + +.crafting-container::-webkit-scrollbar-track { + background: var(--bg-tertiary); + border-radius: 4px; +} + +.crafting-container::-webkit-scrollbar-thumb { + background: var(--primary-color); + border-radius: 4px; + border: 1px solid var(--bg-tertiary); +} + +.crafting-container::-webkit-scrollbar-thumb:hover { + background: var(--primary-hover); +} + +/* Global Custom Scrollbar Styling */ +.skills-container::-webkit-scrollbar, +.quests-container::-webkit-scrollbar, +.base-container::-webkit-scrollbar, +.shop-container::-webkit-scrollbar, +.dungeons-container::-webkit-scrollbar, +.dungeon-selector::-webkit-scrollbar, +.inventory-container::-webkit-scrollbar, +.inventory-grid::-webkit-scrollbar, +.item-details::-webkit-scrollbar, +.shop-content::-webkit-scrollbar, +.server-list::-webkit-scrollbar, +.console-output::-webkit-scrollbar, +.upgrade-list::-webkit-scrollbar { + width: 8px; +} + +.skills-container::-webkit-scrollbar-track, +.quests-container::-webkit-scrollbar-track, +.base-container::-webkit-scrollbar-track, +.shop-container::-webkit-scrollbar-track, +.dungeons-container::-webkit-scrollbar-track, +.dungeon-selector::-webkit-scrollbar-track, +.inventory-container::-webkit-scrollbar-track, +.inventory-grid::-webkit-scrollbar-track, +.item-details::-webkit-scrollbar-track, +.shop-content::-webkit-scrollbar-track, +.server-list::-webkit-scrollbar-track, +.console-output::-webkit-scrollbar-track, +.upgrade-list::-webkit-scrollbar-track { + background: var(--bg-tertiary); + border-radius: 4px; +} + +.skills-container::-webkit-scrollbar-thumb, +.quests-container::-webkit-scrollbar-thumb, +.base-container::-webkit-scrollbar-thumb, +.shop-container::-webkit-scrollbar-thumb, +.dungeons-container::-webkit-scrollbar-thumb, +.dungeon-selector::-webkit-scrollbar-thumb, +.inventory-container::-webkit-scrollbar-thumb, +.inventory-grid::-webkit-scrollbar-thumb, +.item-details::-webkit-scrollbar-thumb, +.shop-content::-webkit-scrollbar-thumb, +.server-list::-webkit-scrollbar-thumb, +.console-output::-webkit-scrollbar-thumb, +.upgrade-list::-webkit-scrollbar-thumb { + background: var(--primary-color); + border-radius: 4px; + border: 1px solid var(--bg-tertiary); +} + +.skills-container::-webkit-scrollbar-thumb:hover, +.quests-container::-webkit-scrollbar-thumb:hover, +.base-container::-webkit-scrollbar-thumb:hover, +.shop-container::-webkit-scrollbar-thumb:hover, +.dungeons-container::-webkit-scrollbar-thumb:hover, +.dungeon-selector::-webkit-scrollbar-thumb:hover, +.inventory-container::-webkit-scrollbar-thumb:hover, +.inventory-grid::-webkit-scrollbar-thumb:hover, +.item-details::-webkit-scrollbar-thumb:hover, +.shop-content::-webkit-scrollbar-thumb:hover, +.server-list::-webkit-scrollbar-thumb:hover, +.console-output::-webkit-scrollbar-thumb:hover, +.upgrade-list::-webkit-scrollbar-thumb:hover { + background: var(--primary-hover); +} + +.crafting-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 20px; + padding: 15px 20px; + background: var(--bg-secondary); + border: 1px solid var(--border-color); + border-radius: 12px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); +} + +.crafting-header h2 { + color: var(--primary-color); + font-size: 1.5rem; + font-weight: 600; + display: flex; + align-items: center; + gap: 10px; + margin: 0; +} + +.crafting-header h2 i { + font-size: 1.3rem; +} + +.crafting-info { + display: flex; + gap: 20px; + align-items: center; +} + +.crafting-level, +.crafting-experience { + background: var(--bg-tertiary); + padding: 8px 16px; + border-radius: 8px; + border: 1px solid var(--border-color); + display: flex; + align-items: center; + gap: 8px; + color: var(--text-primary); + font-size: 0.9rem; +} + +.crafting-level i, +.crafting-experience i { + color: var(--primary-color); +} + +.crafting-categories { + display: flex; + gap: 0.5rem; + margin-bottom: 2rem; + justify-content: center; +} + +.crafting-cat-btn { + padding: 0.75rem 1.5rem; + background: var(--bg-tertiary); + border: 1px solid var(--border-color); + border-radius: 8px; + color: var(--text-muted); + cursor: pointer; + transition: all 0.3s ease; +} + +.crafting-cat-btn:hover { + border-color: var(--primary-color); + color: var(--text-primary); +} + +.crafting-cat-btn.active { + background: var(--gradient-primary); + color: var(--bg-primary); + border-color: transparent; +} + +.crafting-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + gap: 1rem; +} + +/* Recipe Item Styles - Match Skill Cards */ +.recipe-item { + background: var(--card-bg); + border: 1px solid var(--border-color); + border-radius: 12px; + padding: 1.5rem; + cursor: pointer; + transition: all 0.3s ease; + position: relative; + overflow: hidden; +} + +.recipe-item:hover { + transform: translateY(-2px); + box-shadow: 0 8px 25px rgba(0, 0, 0, 0.3); + border-color: var(--primary-color); +} + +.recipe-item.can-craft { + border-color: var(--success-color); +} + +.recipe-item.can-craft:hover { + border-color: var(--success-color); + box-shadow: 0 8px 25px rgba(0, 255, 136, 0.2); +} + +.recipe-item.locked { + opacity: 0.5; + cursor: not-allowed; + background: var(--bg-tertiary); + border-color: var(--border-color); +} + +.recipe-item.missing-materials { + opacity: 0.7; + border-color: var(--warning-color); +} + +.recipe-item.missing-materials:hover { + border-color: var(--warning-color); + box-shadow: 0 8px 25px rgba(255, 170, 0, 0.2); +} + +.recipe-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 1rem; +} + +.recipe-header h4 { + color: var(--text-primary); + font-size: 1.1rem; + font-weight: 600; + margin: 0; +} + +.recipe-level { + background: var(--bg-tertiary); + color: var(--text-muted); + padding: 4px 8px; + border-radius: 6px; + font-size: 0.8rem; + font-weight: 500; +} + +.recipe-description { + color: var(--text-muted); + font-size: 0.9rem; + margin-bottom: 1rem; + line-height: 1.4; +} + +.recipe-materials { + margin-bottom: 1rem; +} + +.material-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 6px 0; + border-bottom: 1px solid rgba(255, 255, 255, 0.1); +} + +.material-item:last-child { + border-bottom: none; +} + +.material-item.missing { + color: var(--error-color); +} + +.material-name { + font-size: 0.85rem; + color: var(--text-secondary); +} + +.material-quantity { + font-size: 0.85rem; + font-weight: 600; + color: var(--text-primary); +} + +.material-item.missing .material-quantity { + color: var(--error-color); +} + +.missing-materials-text { + color: var(--error-color); + font-size: 0.8rem; + margin-top: 8px; + padding: 8px; + background: rgba(255, 51, 102, 0.1); + border-radius: 6px; + border: 1px solid rgba(255, 51, 102, 0.3); +} + +.missing-materials-text i { + margin-right: 5px; +} + +.recipe-time { + display: flex; + align-items: center; + gap: 6px; + color: var(--text-muted); + font-size: 0.8rem; + margin-top: 0.5rem; +} + +.recipe-time i { + color: var(--primary-color); +} + +.crafting-container { + display: flex; + flex-direction: column; + height: 100%; + padding: 20px; + background: var(--bg-primary); +} + +.crafting-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 20px; + padding: 15px 20px; + background: var(--bg-secondary); + border: 1px solid var(--border-color); + border-radius: 12px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); +} + +.crafting-header h2 { + color: var(--primary-color); + font-size: 1.5rem; + font-weight: 600; + display: flex; + align-items: center; + gap: 10px; +} + +.crafting-header h2 i { + font-size: 1.3rem; +} + +.crafting-info { + display: flex; + gap: 30px; +} + +.crafting-level, +.crafting-experience { + display: flex; + align-items: center; + gap: 8px; + color: var(--text-secondary); + font-size: 0.9rem; +} + +.crafting-level i, +.crafting-experience i { + color: var(--warning-color); +} + +.crafting-level span:last-child, +.crafting-experience span:last-child { + color: var(--text-primary); + font-weight: 600; +} + +.crafting-content { + display: flex; + gap: 20px; + flex: 1; + min-height: 0; +} + +.crafting-sidebar { + width: 250px; + background: var(--bg-secondary); + border: 1px solid var(--border-color); + border-radius: 12px; + padding: 20px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); +} + +.crafting-categories h3 { + color: var(--primary-color); + font-size: 1.1rem; + margin-bottom: 15px; + display: flex; + align-items: center; + gap: 8px; +} + +.crafting-cat-btn { + display: flex; + align-items: center; + gap: 10px; + width: 100%; + padding: 12px 15px; + margin-bottom: 8px; + background: var(--bg-tertiary); + border: 1px solid var(--border-color); + border-radius: 8px; + color: var(--text-secondary); + font-size: 0.9rem; + cursor: pointer; + transition: all 0.3s ease; +} + +.crafting-cat-btn:hover { + background: var(--hover-bg); + border-color: var(--primary-color); + color: var(--text-primary); + transform: translateX(5px); +} + +.crafting-cat-btn.active { + background: var(--gradient-primary); + border-color: var(--primary-color); + color: var(--text-primary); + box-shadow: 0 2px 8px rgba(0, 212, 255, 0.3); +} + +.crafting-cat-btn i { + width: 16px; + text-align: center; +} + +.crafting-main { + flex: 1; + display: flex; + gap: 20px; +} + +.recipe-list { + flex: 1; + background: var(--bg-secondary); + border: 1px solid var(--border-color); + border-radius: 12px; + padding: 20px; + overflow-y: auto; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); +} + +.crafting-details { + width: 350px; + background: var(--bg-secondary); + border: 1px solid var(--border-color); + border-radius: 12px; + padding: 20px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); +} + +.selected-recipe { + display: flex; + flex-direction: column; + align-items: center; + text-align: center; + color: var(--text-muted); +} + +.selected-recipe i { + font-size: 3rem; + margin-bottom: 15px; + opacity: 0.5; +} + +.selected-recipe h3 { + margin-bottom: 10px; + color: var(--text-secondary); +} + +/* Recipe Item Styles */ +.recipe-item { + background: var(--bg-tertiary); + border: 1px solid var(--border-color); + border-radius: 8px; + padding: 15px; + margin-bottom: 12px; + cursor: pointer; + transition: all 0.3s ease; +} + +.recipe-item:hover { + background: var(--hover-bg); + border-color: var(--primary-color); + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(0, 212, 255, 0.2); +} + +.recipe-item.selected { + background: var(--gradient-primary); + border-color: var(--primary-color); + box-shadow: 0 4px 12px rgba(0, 212, 255, 0.3); +} + +.recipe-item.locked { + opacity: 0.6; + cursor: not-allowed; +} + +.recipe-item.locked:hover { + transform: none; + border-color: var(--border-color); +} + +.recipe-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 8px; +} + +.recipe-header h4 { + color: var(--text-primary); + font-size: 1rem; +} + +.recipe-level { + background: var(--bg-primary); + color: var(--warning-color); + padding: 4px 8px; + border-radius: 4px; + font-size: 0.8rem; + font-weight: 600; +} + +.recipe-description { + color: var(--text-secondary); + font-size: 0.85rem; + margin-bottom: 10px; +} + +.recipe-materials { + display: flex; + gap: 8px; + flex-wrap: wrap; +} + +.material-tag { + background: var(--bg-primary); + color: var(--text-muted); + padding: 2px 6px; + border-radius: 4px; + font-size: 0.75rem; +} + +/* Enhanced Recipe Item Styles */ +.recipe-item.locked { + opacity: 0.5; + cursor: not-allowed; + background: var(--bg-tertiary); + border-color: var(--border-color); +} + +.recipe-item.missing-materials { + opacity: 0.7; + border-color: var(--warning-color); +} + +.recipe-item.missing-materials:hover { + border-color: var(--warning-color); + box-shadow: 0 4px 12px rgba(255, 170, 0, 0.2); +} + +.material-item.missing { + color: var(--error-color); +} + +.material-item.missing .material-quantity { + color: var(--error-color); + font-weight: 600; +} + +.missing-materials-text { + color: var(--error-color); + font-size: 0.85rem; + margin-top: 8px; + padding: 8px; + background: rgba(255, 51, 102, 0.1); + border-radius: 4px; + border: 1px solid rgba(255, 51, 102, 0.3); +} + +.missing-materials-text i { + margin-right: 5px; +} + +/* Responsive Design */ +@media (max-width: 1200px) { + .crafting-content { + flex-direction: column; + } + + .crafting-sidebar { + width: 100%; + } + + .crafting-main { + flex-direction: column; + } + + .crafting-details { + width: 100%; + } +} + +/* Debug Console Styles */ +.console-window { + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 600px; + height: 400px; + background: var(--bg-secondary); + border: 2px solid var(--primary-color); + border-radius: 8px; + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.8); + display: none; + flex-direction: column; + z-index: 10000; + font-family: 'Courier New', monospace; +} + +.console-header { + background: var(--bg-tertiary); + padding: 10px 15px; + border-bottom: 1px solid var(--border-color); + display: flex; + justify-content: space-between; + align-items: center; + color: var(--text-primary); + font-weight: 600; +} + +.console-close { + background: none; + border: none; + color: var(--text-secondary); + font-size: 18px; + cursor: pointer; + padding: 0; + width: 24px; + height: 24px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 4px; + transition: all 0.2s ease; +} + +.console-close:hover { + background: var(--error-color); + color: white; +} + +.console-content { + flex: 1; + display: flex; + flex-direction: column; + overflow: hidden; +} + +.console-output { + flex: 1; + padding: 15px; + overflow-y: auto; + background: var(--bg-primary); + color: var(--text-primary); + font-size: 12px; + line-height: 1.4; +} + +.console-line { + margin-bottom: 5px; + word-wrap: break-word; +} + +.console-line.console-info { + color: var(--primary-color); +} + +.console-line.console-success { + color: var(--success-color); +} + +.console-line.console-error { + color: var(--error-color); +} + +.console-line.console-warning { + color: var(--warning-color); +} + +.console-input-container { + padding: 10px 15px; + background: var(--bg-tertiary); + border-top: 1px solid var(--border-color); +} + +.console-input { + width: 100%; + background: var(--bg-primary); + border: 1px solid var(--border-color); + border-radius: 4px; + padding: 8px 12px; + color: var(--text-primary); + font-family: 'Courier New', monospace; + font-size: 12px; + outline: none; +} + +.console-input:focus { + border-color: var(--primary-color); + box-shadow: 0 0 0 2px rgba(0, 212, 255, 0.2); +} + +.console-input::placeholder { + color: var(--text-muted); +} + +/* Skills Tab Styles */ +.skills-container { + padding: 20px; +} + +.skills-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 20px; + padding: 15px 20px; + background: var(--bg-secondary); + border: 1px solid var(--border-color); + border-radius: 12px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); +} + +.skills-header h2 { + color: var(--primary-color); + font-size: 1.5rem; + font-weight: 600; + display: flex; + align-items: center; + gap: 10px; + margin: 0; +} + +.skills-header h2 i { + font-size: 1.3rem; +} + +.skill-points-display { + background: var(--bg-tertiary); + padding: 8px 16px; + border-radius: 8px; + border: 1px solid var(--border-color); +} + +.skill-points { + color: var(--warning-color); + font-weight: 600; + font-size: 1rem; +} diff --git a/Client-Server/styles/tables.css b/Client-Server/styles/tables.css new file mode 100644 index 0000000..6a0eb84 --- /dev/null +++ b/Client-Server/styles/tables.css @@ -0,0 +1,842 @@ +/* Table Styles for Galaxy Strike Online */ + +/* Base Table Styles */ +.dungeon-table, +.skills-table, +.base-rooms-table, +.base-upgrades-table, +.ship-gallery-table, +.starbase-management-table, +.starbase-shop-table, +.quests-table, +.inventory-table, +.shop-table { + width: 100%; + border-collapse: collapse; + background: var(--card-bg); + border-radius: 8px; + overflow: hidden; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); + margin: 10px 0; +} + +.dungeon-table th, +.skills-table th, +.base-rooms-table th, +.base-upgrades-table th, +.ship-gallery-table th, +.starbase-management-table th, +.starbase-shop-table th, +.quests-table th, +.inventory-table th, +.shop-table th { + background: var(--gradient-primary); + color: var(--text-primary); + padding: 12px 15px; + text-align: left; + font-weight: 600; + font-size: 14px; + border-bottom: 2px solid rgba(255, 255, 255, 0.1); +} + +.dungeon-table td, +.skills-table td, +.base-rooms-table td, +.base-upgrades-table td, +.ship-gallery-table td, +.starbase-management-table td, +.starbase-shop-table td, +.quests-table td, +.inventory-table td, +.shop-table td { + padding: 12px 15px; + border-bottom: 1px solid rgba(255, 255, 255, 0.1); + color: #e0e0e0; + font-size: 14px; +} + +.dungeon-table tr:hover, +.skills-table tr:hover, +.base-rooms-table tr:hover, +.base-upgrades-table tr:hover, +.ship-gallery-table tr:hover, +.starbase-management-table tr:hover, +.starbase-shop-table tr:hover, +.quests-table tr:hover, +.inventory-table tr:hover, +.shop-table tr:hover { + background: rgba(102, 126, 234, 0.1); + transition: background 0.3s ease; +} + +/* Dungeon Table Specific */ +.dungeon-table .difficulty-easy { color: #00ff00; } +.dungeon-table .difficulty-medium { color: #ffff00; } +.dungeon-table .difficulty-hard { color: #ff9900; } +.dungeon-table .difficulty-extreme { color: #ff0000; } + +/* Skills Table Specific */ +.skills-table .skill-level { + font-weight: bold; + color: #667eea; +} + +.skills-table .skill-progress { + width: 100px; + height: 8px; + background: rgba(255, 255, 255, 0.1); + border-radius: 4px; + overflow: hidden; +} + +.skills-table .progress-fill { + height: 100%; + background: linear-gradient(90deg, #667eea, #764ba2); + transition: width 0.3s ease; +} + +/* Base Tables Specific */ +.base-rooms-table .room-status-active { color: #00ff00; } +.base-rooms-table .room-status-inactive { color: #ff0000; } +.base-rooms-table .room-status-upgrading { color: #ffff00; } + +.base-upgrades-table .upgrade-level { + font-weight: bold; + color: #667eea; +} + +/* Ship Gallery Grid Specific */ +.ship-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); + gap: 15px; + padding: 20px 0; +} + +.ship-card { + background: var(--card-bg); + border-radius: 12px; + padding: 15px; + border: 2px solid var(--primary-color); + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); + transition: all 0.3s ease; + position: relative; + overflow: hidden; + text-align: center; +} + +.ship-card:hover { + transform: translateY(-5px); + border-color: var(--primary-color); + box-shadow: 0 8px 30px rgba(0, 212, 255, 0.4); +} + +.ship-card.active { + border-color: var(--success-color); + box-shadow: 0 8px 30px rgba(0, 255, 136, 0.2); +} + +.ship-card.active::before { + content: "ACTIVE"; + position: absolute; + top: 10px; + right: 10px; + background: var(--gradient-secondary); + color: var(--text-primary); + padding: 4px 8px; + border-radius: 4px; + font-size: 10px; + font-weight: bold; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.ship-card-header { + display: flex; + flex-direction: column; + align-items: center; + gap: 10px; + margin-bottom: 15px; +} + +.ship-card-image { + width: 80px; + height: 80px; + border-radius: 8px; + object-fit: cover; + border: 2px solid var(--primary-color); + margin: 0 auto; +} + +.ship-card-info { + text-align: center; +} + +.ship-card-rarity { + color: var(--text-secondary); + font-size: 14px; + font-weight: bold; + text-transform: uppercase; + letter-spacing: 0.5px; + padding: 4px 8px; + border-radius: 4px; + background: var(--hover-bg); + border: 1px solid var(--border-color); +} + +.ship-card-rarity.common { + color: #888; + border-color: #888; +} + +.ship-card-rarity.rare { + color: var(--primary-color); + border-color: var(--primary-color); +} + +.ship-card-rarity.epic { + color: var(--accent-color); + border-color: var(--accent-color); +} + +.ship-card-rarity.legendary { + color: var(--warning-color); + border-color: var(--warning-color); +} + +.ship-card-stats { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 8px; + margin-bottom: 15px; +} + +.ship-card-stat { + display: flex; + justify-content: space-between; + align-items: center; + padding: 6px 10px; + background: rgba(255, 255, 255, 0.05); + border-radius: 4px; + border: 1px solid rgba(255, 255, 255, 0.1); +} + +.ship-card-stat .stat-label { + color: var(--text-muted); + font-size: 11px; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.ship-card-stat .stat-value { + color: var(--text-secondary); + font-weight: bold; + font-size: 12px; +} + +.ship-card-stat .stat-value.health { + color: var(--success-color); +} + +.ship-card-stat .stat-value.attack { + color: var(--warning-color); +} + +.ship-card-stat .stat-value.defense { + color: var(--accent-color); +} + +.ship-card-stat .stat-value.speed { + color: var(--secondary-color); +} + +.ship-card-actions { + display: flex; + gap: 10px; + justify-content: space-between; +} + +.ship-card-actions .btn-action { + flex: 1; + padding: 8px 12px; + border: none; + border-radius: 6px; + cursor: pointer; + font-size: 12px; + font-weight: 600; + transition: all 0.3s ease; + text-transform: uppercase; + text-align: center; +} + +.btn-action.btn-switch { + background: var(--gradient-primary); + color: var(--text-primary); +} + +.btn-action.btn-switch:hover { + background: var(--gradient-secondary); + transform: translateY(-2px); +} + +.btn-action.btn-switch:disabled { + background: var(--text-muted); + cursor: not-allowed; + transform: none; +} + +.btn-action.btn-upgrade { + background: var(--gradient-secondary); + color: var(--text-primary); +} + +.btn-action.btn-upgrade:hover { + background: var(--gradient-primary); + transform: translateY(-2px); +} + +.btn-action.btn-repair { + background: var(--gradient-secondary); + color: var(--text-primary); +} + +.btn-action.btn-repair:hover { + background: var(--gradient-primary); + transform: translateY(-2px); +} + +/* Ship Gallery Layout */ +.ship-layout { + display: flex; + gap: 30px; + margin-top: 20px; +} + +.current-ship-section { + flex: 0 0 400px; + background: var(--card-bg); + border-radius: 8px; + padding: 20px; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); + border: 2px solid var(--primary-color); +} + +.ship-grid-section { + flex: 1; + min-width: 0; /* Prevent flex item from overflowing */ +} + +.ship-grid-section h4 { + color: var(--primary-color); + margin-bottom: 20px; + font-size: 16px; + text-transform: uppercase; + letter-spacing: 1px; +} + +.current-ship-section h4 { + color: var(--primary-color); + margin-bottom: 15px; + font-size: 18px; + text-transform: uppercase; + letter-spacing: 1px; + text-align: center; +} + +/* Current Ship Display */ +.current-ship-display { + display: flex; + flex-direction: column; + align-items: center; + gap: 20px; + text-align: center; +} + +.current-ship-image { + flex-shrink: 0; + order: 1; +} + +.current-ship-image img { + width: 120px; + height: 120px; + object-fit: cover; + border-radius: 8px; + border: 2px solid var(--primary-color); + box-shadow: 0 4px 15px rgba(0, 212, 255, 0.3); +} + +.current-ship-details { + flex: 1; + order: 2; + min-width: 0; + text-align: center; +} + +.current-ship-details h5 { + color: var(--text-primary); + margin-bottom: 15px; + font-size: 20px; + font-weight: bold; + text-align: center; +} + +.current-ship-stats { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 10px; +} + +.ship-stat { + display: flex; + justify-content: space-between; + align-items: center; + padding: 8px 12px; + background: var(--hover-bg); + border-radius: 4px; + border: 1px solid var(--border-color); +} + +.ship-stat .stat-label { + color: var(--text-muted); + font-size: 12px; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.ship-stat .stat-value { + color: var(--text-secondary); + font-weight: bold; + font-size: 14px; +} + +.ship-stat .stat-value.health { + color: var(--success-color); +} + +.ship-stat .stat-value.attack { + color: var(--warning-color); +} + +.ship-stat .stat-value.defense { + color: var(--accent-color); +} + +.ship-stat .stat-value.speed { + color: var(--secondary-color); +} + +.ship-table-section { + margin-top: 30px; +} + +.ship-table-section h4 { + color: #667eea; + margin-bottom: 15px; + font-size: 16px; + text-transform: uppercase; + letter-spacing: 1px; +} + +/* Responsive Design */ +@media (max-width: 768px) { + .ship-layout { + flex-direction: column; + gap: 20px; + } + + .current-ship-section { + flex: 1; + width: 100%; + } + + .current-ship-display { + flex-direction: row; + align-items: flex-start; + gap: 15px; + } + + .current-ship-image img { + width: 100px; + height: 100px; + } + + .current-ship-stats { + grid-template-columns: 1fr; + } + + .ship-grid { + grid-template-columns: 1fr; + gap: 15px; + padding: 15px 0; + } + + .ship-card { + padding: 15px; + } + + .ship-card-header { + flex-direction: row; + align-items: flex-start; + gap: 12px; + } + + .ship-card-image { + width: 80px; + height: 80px; + } + + .ship-card-stats { + grid-template-columns: 1fr; + gap: 6px; + } + + .ship-card-actions { + flex-direction: column; + gap: 8px; + } + + .ship-card-actions .btn-action { + padding: 10px 12px; + font-size: 11px; + } +} + +@media (max-width: 480px) { + .ship-layout { + gap: 15px; + } + + .current-ship-section { + padding: 15px; + } + + .current-ship-display { + flex-direction: column; + align-items: center; + text-align: center; + gap: 15px; + } + + .current-ship-image { + order: 1; + } + + .current-ship-details { + order: 2; + text-align: center; + } + + .ship-grid { + grid-template-columns: 1fr; + gap: 10px; + padding: 10px 0; + } + + .ship-card { + padding: 12px; + } + + .ship-card-header { + flex-direction: column; + align-items: center; + text-align: center; + gap: 10px; + } + + .ship-card-image { + width: 80px; + height: 80px; + margin: 0 auto; + } + + .ship-card-info { + text-align: center; + } + + .ship-card-name { + font-size: 14px; + } + + .ship-card-class { + font-size: 11px; + } +} + +/* Console Window Styles */ +.console-window { + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 600px; + height: 400px; + background: var(--bg-secondary); + border: 2px solid var(--primary-color); + border-radius: 8px; + box-shadow: 0 8px 30px rgba(0, 0, 0, 0.8); + z-index: 10000; + display: none; + flex-direction: column; + font-family: 'Courier New', monospace; +} + +.console-header { + background: var(--gradient-primary); + color: var(--text-primary); + padding: 10px 15px; + border-radius: 6px 6px 0 0; + display: flex; + justify-content: space-between; + align-items: center; + font-weight: bold; + font-size: 14px; +} + +.console-close { + background: none; + border: none; + color: var(--text-primary); + font-size: 20px; + cursor: pointer; + padding: 0; + width: 24px; + height: 24px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 4px; + transition: background 0.3s ease; +} + +.console-close:hover { + background: rgba(255, 255, 255, 0.2); +} + +.console-content { + flex: 1; + display: flex; + flex-direction: column; + overflow: hidden; +} + +.console-output { + flex: 1; + padding: 15px; + overflow-y: auto; + background: var(--bg-primary); + color: var(--text-primary); + font-size: 12px; + line-height: 1.4; + border-bottom: 1px solid var(--border-color); +} + +.console-output .console-line { + margin-bottom: 5px; + word-wrap: break-word; +} + +.console-output .console-error { + color: var(--error-color); +} + +.console-output .console-success { + color: var(--success-color); +} + +.console-output .console-warning { + color: var(--warning-color); +} + +.console-output .console-info { + color: var(--primary-color); +} + +.console-input-container { + padding: 10px; + background: var(--bg-secondary); +} + +.console-input { + width: 100%; + background: var(--bg-primary); + border: 1px solid var(--border-color); + color: var(--text-primary); + padding: 8px 12px; + font-family: 'Courier New', monospace; + font-size: 12px; + border-radius: 4px; + outline: none; +} + +.console-input:focus { + border-color: var(--primary-color); + box-shadow: 0 0 0 2px rgba(0, 212, 255, 0.2); +} + +/* Starbase Tables Specific */ +.starbase-management-table .starbase-level { + font-weight: bold; + color: #667eea; +} + +.starbase-shop-table .starbase-cost { + font-weight: bold; + color: #ffd700; +} + +/* Quests Table Specific */ +.quests-table .quest-type-main { color: #667eea; } +.quests-table .quest-type-daily { color: #00ff00; } +.quests-table .quest-type-procedural { color: #ff9900; } +.quests-table .quest-type-completed { color: #888888; } +.quests-table .quest-type-failed { color: #ff0000; } + +.quests-table .quest-progress { + width: 100px; + height: 8px; + background: rgba(255, 255, 255, 0.1); + border-radius: 4px; + overflow: hidden; +} + +.quests-table .progress-fill { + height: 100%; + background: linear-gradient(90deg, #00ff00, #00cc00); + transition: width 0.3s ease; +} + +/* Inventory Table Specific */ +.inventory-table .item-rarity-common { color: #888888; } +.inventory-table .item-rarity-uncommon { color: #00ff00; } +.inventory-table .item-rarity-rare { color: #0088ff; } +.inventory-table .item-rarity-epic { color: #8833ff; } +.inventory-table .item-rarity-legendary { color: #ff8800; } + +.inventory-table .item-stats { + font-size: 12px; + color: #cccccc; +} + +/* Shop Table Specific */ +.shop-table .item-price { + font-weight: bold; + color: #ffd700; +} + +.shop-table .item-description { + font-size: 12px; + color: #cccccc; + max-width: 200px; +} + +/* Action Buttons */ +.dungeon-table .btn-action, +.skills-table .btn-action, +.base-rooms-table .btn-action, +.base-upgrades-table .btn-action, +.ship-gallery-table .btn-action, +.starbase-management-table .btn-action, +.starbase-shop-table .btn-action, +.quests-table .btn-action, +.inventory-table .btn-action, +.shop-table .btn-action { + padding: 6px 12px; + border: none; + border-radius: 4px; + cursor: pointer; + font-size: 12px; + font-weight: 600; + transition: all 0.3s ease; + text-transform: uppercase; +} + +.btn-action.btn-primary { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; +} + +.btn-action.btn-primary:hover { + background: linear-gradient(135deg, #764ba2 0%, #667eea 100%); + transform: translateY(-1px); +} + +.btn-action.btn-secondary { + background: rgba(255, 255, 255, 0.1); + color: white; + border: 1px solid rgba(255, 255, 255, 0.2); +} + +.btn-action.btn-secondary:hover { + background: rgba(255, 255, 255, 0.2); + transform: translateY(-1px); +} + +.btn-action.btn-success { + background: linear-gradient(135deg, #00ff00 0%, #00cc00 100%); + color: white; +} + +.btn-action.btn-success:hover { + background: linear-gradient(135deg, #00cc00 0%, #00ff00 100%); + transform: translateY(-1px); +} + +.btn-action.btn-danger { + background: linear-gradient(135deg, #ff0000 0%, #cc0000 100%); + color: white; +} + +.btn-action.btn-danger:hover { + background: linear-gradient(135deg, #cc0000 0%, #ff0000 100%); + transform: translateY(-1px); +} + +/* Responsive Design */ +@media (max-width: 768px) { + .dungeon-table, + .skills-table, + .base-rooms-table, + .base-upgrades-table, + .ship-gallery-table, + .starbase-management-table, + .starbase-shop-table, + .quests-table, + .inventory-table, + .shop-table { + font-size: 12px; + } + + .dungeon-table th, + .skills-table th, + .base-rooms-table th, + .base-upgrades-table th, + .ship-gallery-table th, + .starbase-management-table th, + .starbase-shop-table th, + .quests-table th, + .inventory-table th, + .shop-table th { + padding: 8px 10px; + font-size: 12px; + } + + .dungeon-table td, + .skills-table td, + .base-rooms-table td, + .base-upgrades-table td, + .ship-gallery-table td, + .starbase-management-table td, + .starbase-shop-table td, + .quests-table td, + .inventory-table td, + .shop-table td { + padding: 8px 10px; + font-size: 12px; + } + + .btn-action { + padding: 4px 8px; + font-size: 10px; + } +}