this is a very unstable version and old of the client server setup

This commit is contained in:
Robert MacRae 2026-01-26 17:14:56 -04:00
parent 5baa2c8e09
commit 5e1a2e0037
65 changed files with 34957 additions and 0 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 254 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 226 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 213 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 268 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 291 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 324 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 192 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 312 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 286 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

View File

@ -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);
});
});

View File

@ -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);
});
});

View File

@ -0,0 +1,699 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Galaxy Strike Online - Space Idle MMORPG</title>
<link rel="stylesheet" href="styles/main.css?v=2">
<link rel="stylesheet" href="styles/components.css">
<link rel="stylesheet" href="styles/tables.css">
<link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700;900&family=Space+Mono:wght@400;700&display=swap" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
<script src="https://cdn.socket.io/4.7.4/socket.io.min.js"></script>
<script src="js/SimpleLocalServer.js"></script>
<script src="js/LocalServerManager.js"></script>
</head>
<body>
<!-- Custom Title Bar -->
<div id="titleBar" class="title-bar">
<div class="title-bar-left">
<span class="title-bar-title">Galaxy Strike Online</span>
</div>
<div class="title-bar-right">
<button class="title-bar-btn" id="minimizeBtn" title="Minimize">
<i class="fas fa-minus"></i>
</button>
<button class="title-bar-btn" id="fullscreenBtn" title="Toggle Fullscreen">
<i class="fas fa-expand"></i>
</button>
<button class="title-bar-btn close-btn" id="closeBtn" title="Close">
<i class="fas fa-times"></i>
</button>
</div>
</div>
<div id="app">
<!-- Main Menu -->
<div id="mainMenu" class="main-menu">
<div class="menu-container">
<div class="menu-header">
<h1 class="menu-title">GALAXY STRIKE ONLINE</h1>
<p class="menu-subtitle">Space Idle MMORPG</p>
</div>
<div class="menu-content">
<!-- Login Section -->
<div id="loginSection" class="menu-section">
<h2 class="section-title">Account Access</h2>
<div class="login-form">
<div class="form-group">
<label for="emailInput">Email</label>
<input type="email" id="emailInput" placeholder="Enter your email" class="form-input">
</div>
<div class="form-group">
<label for="passwordInput">Password</label>
<input type="password" id="passwordInput" placeholder="Enter your password" class="form-input">
</div>
<div class="login-options">
<button class="btn btn-primary btn-large" id="loginBtn">
<i class="fas fa-sign-in-alt"></i>
Login
</button>
<button class="btn btn-secondary btn-large" id="registerBtn">
<i class="fas fa-user-plus"></i>
Register
</button>
</div>
</div>
<div class="login-notice" id="loginNotice">
<p><i class="fas fa-info-circle"></i> Connect to the live server to play</p>
</div>
</div>
<!-- Server Browser Section -->
<div id="serverSection" class="menu-section hidden">
<h2 class="section-title">Server Browser</h2>
<div class="server-controls">
<button class="btn btn-primary" id="createServerBtn">
<i class="fas fa-server"></i>
Start Local Server
</button>
<button class="btn btn-secondary" id="refreshServersBtn">
<i class="fas fa-sync"></i>
Refresh
</button>
<div class="server-filters">
<select id="regionFilter" class="filter-select">
<option value="">All Regions</option>
<option value="us-east">US East</option>
<option value="us-west">US West</option>
<option value="europe">Europe</option>
<option value="asia">Asia</option>
</select>
<select id="typeFilter" class="filter-select">
<option value="">All Types</option>
<option value="public">Public</option>
<option value="private">Private</option>
</select>
</div>
</div>
<div class="server-list" id="serverList">
<div class="server-loading" id="serverLoading">
<i class="fas fa-spinner fa-spin"></i>
<p>Loading servers...</p>
</div>
<div class="server-empty hidden" id="serverEmpty">
<i class="fas fa-server"></i>
<p>No servers found. Create your own server to play!</p>
</div>
<!-- Servers will be populated here -->
</div>
<div class="server-actions">
<button class="btn btn-secondary" id="backToLoginBtn">
<i class="fas fa-arrow-left"></i>
Back to Login
</button>
</div>
</div>
<!-- Server Join Confirmation Section -->
<div id="serverConfirmSection" class="menu-section hidden">
<h2 class="section-title">Server Selected</h2>
<div class="server-confirmation">
<div class="confirm-actions-left">
<button class="btn btn-primary btn-large btn-join-server" id="joinServerBtn">
<i class="fas fa-sign-in-alt"></i>
Join Server
</button>
</div>
<div class="selected-server-info-center">
<div class="server-preview">
<h3 id="selectedServerName">Server Name</h3>
<div class="server-details" id="selectedServerDetails">
<p class="server-info">Type: <span id="selectedServerType">Public</span></p>
<p class="server-info">Region: <span id="selectedServerRegion">US East</span></p>
<p class="server-info">Players: <span id="selectedServerPlayers">0/10</span></p>
<p class="server-info">Owner: <span id="selectedServerOwner">Unknown</span></p>
</div>
</div>
</div>
<div class="confirm-actions-right">
<button class="btn btn-info btn-large" id="serverInfoBtn">
<i class="fas fa-info"></i>
More Info
</button>
<button class="btn btn-warning btn-large" id="backToServersBtn">
<i class="fas fa-arrow-left"></i>
Back to List
</button>
</div>
</div>
</div>
<!-- Game Options Section -->
<div id="optionsSection" class="menu-section hidden">
<h2 class="section-title">Game Options</h2>
<div class="options-grid" style="display: flex !important; justify-content: space-between !important; gap: 30px !important; margin-bottom: 30px !important;">
<div class="options-left" style="display: flex !important; flex-direction: column !important; gap: 15px !important; justify-content: flex-end !important; min-width: 200px !important;">
<button class="btn btn-primary btn-large" id="continueBtn" style="width: 200px !important;">
<i class="fas fa-gamepad"></i>
Continue
</button>
<button class="btn btn-primary btn-large" id="newGameBtn" style="width: 200px !important;">
<i class="fas fa-play"></i>
New Game
</button>
</div>
<div class="options-center" style="display: flex !important; flex-direction: column !important; justify-content: center !important; align-items: center !important; flex: 1 !important; min-width: 300px !important;">
<div class="save-info-display" style="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;">
<h3 style="color: #00d4ff !important; margin-bottom: 15px !important; font-size: 1.2em !important;">Save Information</h3>
<div id="saveInfoDetails" style="color: #ffffff !important; font-size: 0.9em !important; line-height: 1.4 !important;">
<p>Select a save slot to view details</p>
</div>
</div>
</div>
<div class="options-right" style="display: flex !important; flex-direction: column !important; gap: 15px !important; justify-content: space-between !important; min-width: 200px !important;">
<button class="btn btn-info btn-large" id="settingsBtn" style="width: 200px !important;">
<i class="fas fa-cog"></i>
Settings
</button>
<button class="btn btn-warning btn-large" id="deleteSaveBtn" style="width: 200px !important;">
<i class="fas fa-trash"></i>
Delete Save
</button>
</div>
</div>
<div class="options-actions">
<button class="btn btn-secondary" id="backToSavesBtn">
<i class="fas fa-arrow-left"></i>
Back to Saves
</button>
</div>
</div>
</div>
<div class="menu-footer">
<p class="version-text">Version 1.0.0</p>
<div class="footer-links">
<button class="link-btn" id="aboutBtn">About</button>
<button class="link-btn" id="helpBtn">Help</button>
</div>
</div>
</div>
</div>
<!-- Loading Screen -->
<div id="loadingScreen" class="loading-screen hidden">
<div class="loading-content">
<h1 class="game-title">GALAXY STRIKE ONLINE</h1>
<div class="loading-bar">
<div class="loading-progress"></div>
</div>
<p class="loading-text">Initializing Universe...</p>
</div>
</div>
<!-- Main Game Interface -->
<div id="gameInterface" class="game-interface hidden">
<!-- Header -->
<header class="game-header">
<div class="header-left">
<h1 class="logo">GSO</h1>
<div class="player-info">
<span class="player-name" id="playerName">Commander</span>
<span class="player-level" id="playerLevel">Lv. 1</span>
</div>
</div>
<div class="header-center">
<div class="resources">
<div class="resource">
<i class="fas fa-coins"></i>
<span id="credits">1,000</span>
</div>
<div class="resource">
<i class="fas fa-gem"></i>
<span id="gems">10</span>
</div>
<div class="resource">
<i class="fas fa-bolt"></i>
<span id="energy">100/100</span>
</div>
</div>
</div>
<div class="header-right">
<button class="btn btn-secondary" id="settingsBtn">
<i class="fas fa-cog"></i>
</button>
<button class="btn btn-secondary" id="discordBtn">
<i class="fab fa-discord"></i>
</button>
<button class="btn btn-info" id="localServerBtn" title="Local Server">
<i class="fas fa-server"></i>
</button>
<!-- <button class="btn btn-primary" id="saveBtn" title="Save Game">
<i class="fas fa-save"></i>
</button> -->
<button class="btn btn-warning" id="returnToMenuBtn">
<i class="fas fa-home"></i>
</button>
</div>
</header>
<!-- Navigation -->
<nav class="main-nav">
<button class="nav-btn active" data-tab="dashboard">
<i class="fas fa-tachometer-alt"></i>
<span>Dashboard</span>
</button>
<button class="nav-btn" data-tab="dungeons">
<i class="fas fa-dungeon"></i>
<span>Dungeons</span>
</button>
<button class="nav-btn" data-tab="skills">
<i class="fas fa-graduation-cap"></i>
<span>Skills</span>
</button>
<button class="nav-btn" data-tab="base">
<i class="fas fa-home"></i>
<span>Base</span>
</button>
<button class="nav-btn" data-tab="quests">
<i class="fas fa-scroll"></i>
<span>Quests</span>
</button>
<button class="nav-btn" data-tab="inventory">
<i class="fas fa-backpack"></i>
<span>Inventory</span>
</button>
<button class="nav-btn" data-tab="crafting">
<i class="fas fa-hammer"></i>
<span>Crafting</span>
</button>
<button class="nav-btn" data-tab="shop">
<i class="fas fa-store"></i>
<span>Shop</span>
</button>
</nav>
<!-- Main Content Area -->
<main class="main-content">
<!-- Dashboard Tab -->
<div class="tab-content active" id="dashboard-tab">
<div class="dashboard-grid">
<div class="card">
<h3>Fleet Status</h3>
<div class="fleet-info">
<div class="ship-status">
<i class="fas fa-rocket"></i>
<div>
<p>Flagship: <span id="flagshipName">Starter Cruiser</span></p>
<p>Health: <span id="shipHealth">100%</span></p>
</div>
</div>
</div>
</div>
<div class="card">
<h3>Idle Progress</h3>
<div class="idle-stats">
<p>Offline Time: <span id="offlineTime">0h 0m</span></p>
<p>Resources Gained: <span id="offlineResources">0</span></p>
<button class="btn btn-primary" id="claimOfflineBtn">Claim Rewards</button>
</div>
<div class="stats-grid">
<div class="stat">
<span class="stat-label">Total Kills</span>
<span class="stat-value" id="totalKills">0</span>
</div>
<div class="stat">
<span class="stat-label">Dungeons Cleared</span>
<span class="stat-value" id="dungeonsCleared">0</span>
</div>
<div class="stat">
<span class="stat-label">Play Time</span>
<span class="stat-value" id="playTime">0h 0m</span>
</div>
</div>
</div>
</div>
</div>
<!-- Dungeons Tab -->
<div class="tab-content" id="dungeons-tab">
<div class="dungeons-container">
<div class="dungeon-selector">
<h2>Select Dungeon</h2>
<div class="dungeon-list" id="dungeonList">
<!-- Dungeons will be generated here -->
</div>
</div>
<div class="dungeon-view" id="dungeonView">
<div class="dungeon-placeholder">
<i class="fas fa-dungeon"></i>
<p>Select a dungeon to begin your adventure</p>
</div>
</div>
</div>
</div>
<!-- Skills Tab -->
<div class="tab-content" id="skills-tab">
<div class="skills-container">
<div class="skills-header">
<h2><i class="fas fa-graduation-cap"></i> Skills</h2>
<div class="skill-points-display">
<span class="skill-points">Skill Points: 0</span>
</div>
</div>
<div class="skill-categories">
<button class="skill-cat-btn active" data-category="combat">Combat</button>
<button class="skill-cat-btn" data-category="science">Science</button>
<button class="skill-cat-btn" data-category="crafting">Crafting</button>
</div>
<div class="skills-grid" id="skillsGrid">
<!-- Skills will be generated here -->
</div>
</div>
</div>
<!-- Base Tab -->
<div class="tab-content" id="base-tab">
<div class="base-navigation">
<button class="base-nav-btn active" data-view="overview">Base Overview</button>
<button class="base-nav-btn" data-view="visualization">Base Visualization</button>
<button class="base-nav-btn" data-view="ships">Ship Gallery</button>
<button class="base-nav-btn" data-view="starbases">Starbases</button>
</div>
<!-- Base Overview -->
<div class="base-view-content" id="base-overview">
<div class="base-container">
<div class="base-view">
<div class="base-info">
<h3>Base Information</h3>
<div class="base-stats">
<div class="stat-item">
<span class="stat-label">Power Usage:</span>
<span class="stat-value" id="basePowerUsage">0/100</span>
</div>
<div class="stat-item">
<span class="stat-label">Storage:</span>
<span class="stat-value" id="baseStorage">1000</span>
</div>
<div class="stat-item">
<span class="stat-label">Production Rate:</span>
<span class="stat-value" id="baseProduction">0/s</span>
</div>
</div>
</div>
<div class="base-rooms" id="baseRooms">
<!-- Base rooms will be generated here -->
</div>
</div>
<div class="base-upgrades">
<h3>Base Upgrades</h3>
<div class="upgrade-list" id="baseUpgrades">
<!-- Upgrades will be generated here -->
</div>
</div>
</div>
</div>
<!-- Base Visualization -->
<div class="base-view-content hidden" id="base-visualization">
<div class="base-visualization-container">
<canvas id="baseCanvas" width="800" height="600"></canvas>
<div class="base-info-overlay">
<div class="base-stats-overlay">
<h3>Base Information</h3>
<div id="baseInfoDisplay"></div>
</div>
</div>
</div>
</div>
<!-- Ship Gallery -->
<div class="base-view-content hidden" id="base-ships">
<div class="ship-gallery-container">
<h3>Your Ships</h3>
<div class="ship-layout">
<!-- Current Ship Section -->
<div class="current-ship-section">
<h4>Current Ship</h4>
<div class="current-ship-display" id="currentShipDisplay" data-debug-id="current-ship-panel">
<div class="current-ship-info">
<div class="current-ship-image">
<img src="assets/textures/ships/starter_cruiser.png" alt="Current Ship" id="currentShipImage">
</div>
<div class="current-ship-details">
<h5 id="currentShipName">Starter Cruiser</h5>
<div class="current-ship-stats">
<div class="ship-stat">
<span class="stat-label">Class:</span>
<span class="stat-value" id="currentShipClass">Light</span>
</div>
<div class="ship-stat">
<span class="stat-label">Level:</span>
<span class="stat-value" id="currentShipLevel">1</span>
</div>
<div class="ship-stat">
<span class="stat-label">Health:</span>
<span class="stat-value" id="currentShipHealth">100/100</span>
</div>
<div class="ship-stat">
<span class="stat-label">Attack:</span>
<span class="stat-value" id="currentShipAttack">10</span>
</div>
<div class="ship-stat">
<span class="stat-label">Defense:</span>
<span class="stat-value" id="currentShipDefense">5</span>
</div>
<div class="ship-stat">
<span class="stat-label">Speed:</span>
<span class="stat-value" id="currentShipSpeed">15</span>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Ship Grid -->
<div class="ship-grid-section">
<h4>Ships Collected</h4>
<div class="ship-grid" id="shipGrid">
<!-- Ships will be displayed here as cards -->
</div>
</div>
</div>
</div>
</div>
<!-- Starbases -->
<div class="base-view-content hidden" id="base-starbases">
<div class="starbases-container">
<div class="starbase-section">
<h3>Starbase Management</h3>
<div class="starbase-list" id="starbaseList">
<!-- Starbases will be displayed here -->
</div>
</div>
<div class="starbase-section">
<h3>Available Starbases</h3>
<div class="starbase-shop" id="starbasePurchaseList">
<div class="starbase-purchase-list" id="starbasePurchaseItems">
<!-- Available starbases for purchase -->
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Quests Tab -->
<div class="tab-content" id="quests-tab">
<div class="quests-container">
<div class="quest-tabs">
<button class="quest-tab-btn active" data-type="main">Main Story</button>
<button class="quest-tab-btn" data-type="daily">Daily</button>
<button class="quest-tab-btn" data-type="weekly">Weekly</button>
<button class="quest-tab-btn" data-type="completed">Completed</button>
<button class="quest-tab-btn" data-type="failed">Failed Quests</button>
</div>
<div class="daily-countdown" id="dailyCountdown">Daily quests reset in: 00:00:00</div>
<div class="weekly-countdown" id="weeklyCountdown">Weekly quests reset in: 0d 00:00</div>
<div class="quest-list" id="questList">
<!-- Quests will be generated here -->
</div>
</div>
</div>
<!-- Inventory Tab -->
<div class="tab-content" id="inventory-tab">
<div class="inventory-container">
<div class="equipment-section">
<h3>Equipment</h3>
<div class="equipment-slots">
<div class="equipment-slot">
<div class="slot-label">Weapon</div>
<div class="slot-container" id="equip-weapon">
<div class="empty-equip-slot">Empty</div>
</div>
</div>
<div class="equipment-slot">
<div class="slot-label">Armor</div>
<div class="slot-container" id="equip-armor">
<div class="empty-equip-slot">Empty</div>
</div>
</div>
<div class="equipment-slot">
<div class="slot-label">Engine</div>
<div class="slot-container" id="equip-engine">
<div class="empty-equip-slot">Empty</div>
</div>
</div>
<div class="equipment-slot">
<div class="slot-label">Shield</div>
<div class="slot-container" id="equip-shield">
<div class="empty-equip-slot">Empty</div>
</div>
</div>
<div class="equipment-slot">
<div class="slot-label">Accessory</div>
<div class="slot-container" id="equip-accessory">
<div class="empty-equip-slot">Empty</div>
</div>
</div>
</div>
</div>
<div class="inventory-main">
<div class="inventory-section">
<h3>Inventory</h3>
<div class="inventory-grid" id="inventoryGrid">
<!-- Inventory items will be generated here -->
</div>
</div>
<div class="item-details" id="itemDetails">
<p>Select an item to view details</p>
</div>
</div>
</div>
</div>
<!-- Crafting Tab -->
<div class="tab-content" id="crafting-tab">
<div class="crafting-container">
<div class="crafting-header">
<h2><i class="fas fa-hammer"></i> Crafting Station</h2>
<div class="crafting-info">
<div class="crafting-level">
<i class="fas fa-level-up-alt"></i>
<span>Crafting Level: </span>
<span id="craftingLevel">1</span>
</div>
<div class="crafting-experience">
<i class="fas fa-star"></i>
<span>Experience: </span>
<span id="craftingExp">0/100</span>
</div>
</div>
</div>
<div class="crafting-categories">
<button class="crafting-cat-btn active" data-category="weapons">Weapons</button>
<button class="crafting-cat-btn" data-category="armor">Armor</button>
<button class="crafting-cat-btn" data-category="items">Items</button>
<button class="crafting-cat-btn" data-category="ships">Ships</button>
</div>
<div class="crafting-grid" id="recipeList">
<!-- Recipes will be generated here -->
</div>
</div>
</div>
<!-- Shop Tab -->
<div class="tab-content" id="shop-tab">
<div class="shop-container">
<div class="shop-header">
<div class="shop-categories">
<button class="shop-cat-btn active" data-category="ships">Ships</button>
<button class="shop-cat-btn" data-category="weapons">Weapons</button>
<button class="shop-cat-btn" data-category="armors">Armors</button>
<!-- <button class="shop-cat-btn" data-category="upgrades">Upgrades</button> -->
<button class="shop-cat-btn" data-category="cosmetics">Cosmetics</button>
<button class="shop-cat-btn" data-category="consumables">Consumables</button>
<button class="shop-cat-btn" data-category="materials">Materials</button>
</div>
</div>
<div class="shop-content">
<div class="shop-items-container">
<div class="shop-items" id="shopItems">
<!-- Shop items will be generated here -->
</div>
</div>
</div>
</div>
</div>
</main>
</div>
<!-- Modals -->
<div class="modal-overlay hidden" id="modalOverlay">
<div class="modal" id="modal">
<div class="modal-header">
<h3 id="modalTitle">Modal Title</h3>
<button class="modal-close" id="modalClose">
<i class="fas fa-times"></i>
</button>
</div>
<div class="modal-body" id="modalBody">
<!-- Modal content will be inserted here -->
</div>
</div>
</div>
</div>
<!-- Loading Progress Indicator -->
<div class="loading-indicator" id="loadingIndicator"></div>
<div class="loading-status hidden" id="loadingStatus">Initializing...</div>
<!-- Scripts -->
<script src="../config/xp-progression.js"></script>
<script src="js/core/DebugLogger.js"></script>
<script src="js/core/Logger.js"></script>
<script src="js/core/TextureManager.js"></script>
<script src="js/core/GameEngine.js"></script>
<script src="js/core/Player.js"></script>
<script src="js/core/Inventory.js"></script>
<script src="js/core/Economy.js"></script>
<script src="js/systems/DungeonSystem.js"></script>
<script src="js/systems/SkillSystem.js"></script>
<script src="js/systems/BaseSystem.js"></script>
<script src="js/systems/QuestSystem.js"></script>
<script src="js/systems/ShipSystem.js"></script>
<script src="js/systems/IdleSystem.js"></script>
<script src="js/systems/CraftingSystem.js"></script>
<script src="js/data/GameData.js"></script>
<script src="js/ui/UIManager.js"></script>
<script src="js/SmartSaveManager.js"></script>
<script src="js/SaveSystemIntegration.js"></script>
<script src="https://cdn.socket.io/4.7.4/socket.io.min.js"></script>
<script src="js/GameInitializer.js"></script>
<script src="js/ui/LiveMainMenu.js"></script>
<script src="js/main.js"></script>
<!-- Hidden Console Window -->
<div id="consoleWindow" class="console-window">
<div class="console-header">
<span>Developer Console</span>
<button class="console-close" onclick="toggleConsole()">×</button>
</div>
<div class="console-content">
<div id="consoleOutput" class="console-output"></div>
<div class="console-input-container">
<input type="text" id="consoleInput" class="console-input" placeholder="Type command here..." onkeypress="handleConsoleInput(event)">
</div>
</div>
</div>
</body>
</html>

699
Client-Server/index.html Normal file
View File

@ -0,0 +1,699 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Galaxy Strike Online - Space Idle MMORPG</title>
<link rel="stylesheet" href="styles/main.css?v=2">
<link rel="stylesheet" href="styles/components.css">
<link rel="stylesheet" href="styles/tables.css">
<link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700;900&family=Space+Mono:wght@400;700&display=swap" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
<script src="https://cdn.socket.io/4.7.4/socket.io.min.js"></script>
<script src="js/SimpleLocalServer.js"></script>
<script src="js/LocalServerManager.js"></script>
</head>
<body>
<!-- Custom Title Bar -->
<div id="titleBar" class="title-bar">
<div class="title-bar-left">
<span class="title-bar-title">Galaxy Strike Online</span>
</div>
<div class="title-bar-right">
<button class="title-bar-btn" id="minimizeBtn" title="Minimize">
<i class="fas fa-minus"></i>
</button>
<button class="title-bar-btn" id="fullscreenBtn" title="Toggle Fullscreen">
<i class="fas fa-expand"></i>
</button>
<button class="title-bar-btn close-btn" id="closeBtn" title="Close">
<i class="fas fa-times"></i>
</button>
</div>
</div>
<div id="app">
<!-- Main Menu -->
<div id="mainMenu" class="main-menu">
<div class="menu-container">
<div class="menu-header">
<h1 class="menu-title">GALAXY STRIKE ONLINE</h1>
<p class="menu-subtitle">Space Idle MMORPG</p>
</div>
<div class="menu-content">
<!-- Login Section -->
<div id="loginSection" class="menu-section">
<h2 class="section-title">Account Access</h2>
<div class="login-form">
<div class="form-group">
<label for="emailInput">Email</label>
<input type="email" id="emailInput" placeholder="Enter your email" class="form-input">
</div>
<div class="form-group">
<label for="passwordInput">Password</label>
<input type="password" id="passwordInput" placeholder="Enter your password" class="form-input">
</div>
<div class="login-options">
<button class="btn btn-primary btn-large" id="loginBtn">
<i class="fas fa-sign-in-alt"></i>
Login
</button>
<button class="btn btn-secondary btn-large" id="registerBtn">
<i class="fas fa-user-plus"></i>
Register
</button>
</div>
</div>
<div class="login-notice" id="loginNotice">
<p><i class="fas fa-info-circle"></i> Connect to the live server to play</p>
</div>
</div>
<!-- Server Browser Section -->
<div id="serverSection" class="menu-section hidden">
<h2 class="section-title">Server Browser</h2>
<div class="server-controls">
<button class="btn btn-primary" id="createServerBtn">
<i class="fas fa-server"></i>
Start Local Server
</button>
<button class="btn btn-secondary" id="refreshServersBtn">
<i class="fas fa-sync"></i>
Refresh
</button>
<div class="server-filters">
<select id="regionFilter" class="filter-select">
<option value="">All Regions</option>
<option value="us-east">US East</option>
<option value="us-west">US West</option>
<option value="europe">Europe</option>
<option value="asia">Asia</option>
</select>
<select id="typeFilter" class="filter-select">
<option value="">All Types</option>
<option value="public">Public</option>
<option value="private">Private</option>
</select>
</div>
</div>
<div class="server-list" id="serverList">
<div class="server-loading" id="serverLoading">
<i class="fas fa-spinner fa-spin"></i>
<p>Loading servers...</p>
</div>
<div class="server-empty hidden" id="serverEmpty">
<i class="fas fa-server"></i>
<p>No servers found. Create your own server to play!</p>
</div>
<!-- Servers will be populated here -->
</div>
<div class="server-actions">
<button class="btn btn-secondary" id="backToLoginBtn">
<i class="fas fa-arrow-left"></i>
Back to Login
</button>
</div>
</div>
<!-- Server Join Confirmation Section -->
<div id="serverConfirmSection" class="menu-section hidden">
<h2 class="section-title">Server Selected</h2>
<div class="server-confirmation">
<div class="confirm-actions-left">
<button class="btn btn-primary btn-large btn-join-server" id="joinServerBtn">
<i class="fas fa-sign-in-alt"></i>
Join Server
</button>
</div>
<div class="selected-server-info-center">
<div class="server-preview">
<h3 id="selectedServerName">Server Name</h3>
<div class="server-details" id="selectedServerDetails">
<p class="server-info">Type: <span id="selectedServerType">Public</span></p>
<p class="server-info">Region: <span id="selectedServerRegion">US East</span></p>
<p class="server-info">Players: <span id="selectedServerPlayers">0/10</span></p>
<p class="server-info">Owner: <span id="selectedServerOwner">Unknown</span></p>
</div>
</div>
</div>
<div class="confirm-actions-right">
<button class="btn btn-info btn-large" id="serverInfoBtn">
<i class="fas fa-info"></i>
More Info
</button>
<button class="btn btn-warning btn-large" id="backToServersBtn">
<i class="fas fa-arrow-left"></i>
Back to List
</button>
</div>
</div>
</div>
<!-- Game Options Section -->
<div id="optionsSection" class="menu-section hidden">
<h2 class="section-title">Game Options</h2>
<div class="options-grid" style="display: flex !important; justify-content: space-between !important; gap: 30px !important; margin-bottom: 30px !important;">
<div class="options-left" style="display: flex !important; flex-direction: column !important; gap: 15px !important; justify-content: flex-end !important; min-width: 200px !important;">
<button class="btn btn-primary btn-large" id="continueBtn" style="width: 200px !important;">
<i class="fas fa-gamepad"></i>
Continue
</button>
<button class="btn btn-primary btn-large" id="newGameBtn" style="width: 200px !important;">
<i class="fas fa-play"></i>
New Game
</button>
</div>
<div class="options-center" style="display: flex !important; flex-direction: column !important; justify-content: center !important; align-items: center !important; flex: 1 !important; min-width: 300px !important;">
<div class="save-info-display" style="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;">
<h3 style="color: #00d4ff !important; margin-bottom: 15px !important; font-size: 1.2em !important;">Save Information</h3>
<div id="saveInfoDetails" style="color: #ffffff !important; font-size: 0.9em !important; line-height: 1.4 !important;">
<p>Select a save slot to view details</p>
</div>
</div>
</div>
<div class="options-right" style="display: flex !important; flex-direction: column !important; gap: 15px !important; justify-content: space-between !important; min-width: 200px !important;">
<button class="btn btn-info btn-large" id="settingsBtn" style="width: 200px !important;">
<i class="fas fa-cog"></i>
Settings
</button>
<button class="btn btn-warning btn-large" id="deleteSaveBtn" style="width: 200px !important;">
<i class="fas fa-trash"></i>
Delete Save
</button>
</div>
</div>
<div class="options-actions">
<button class="btn btn-secondary" id="backToSavesBtn">
<i class="fas fa-arrow-left"></i>
Back to Saves
</button>
</div>
</div>
</div>
<div class="menu-footer">
<p class="version-text">Version 1.0.0</p>
<div class="footer-links">
<button class="link-btn" id="aboutBtn">About</button>
<button class="link-btn" id="helpBtn">Help</button>
</div>
</div>
</div>
</div>
<!-- Loading Screen -->
<div id="loadingScreen" class="loading-screen hidden">
<div class="loading-content">
<h1 class="game-title">GALAXY STRIKE ONLINE</h1>
<div class="loading-bar">
<div class="loading-progress"></div>
</div>
<p class="loading-text">Initializing Universe...</p>
</div>
</div>
<!-- Main Game Interface -->
<div id="gameInterface" class="game-interface hidden">
<!-- Header -->
<header class="game-header">
<div class="header-left">
<h1 class="logo">GSO</h1>
<div class="player-info">
<span class="player-name" id="playerName">Commander</span>
<span class="player-level" id="playerLevel">Lv. 1</span>
</div>
</div>
<div class="header-center">
<div class="resources">
<div class="resource">
<i class="fas fa-coins"></i>
<span id="credits">1,000</span>
</div>
<div class="resource">
<i class="fas fa-gem"></i>
<span id="gems">10</span>
</div>
<div class="resource">
<i class="fas fa-bolt"></i>
<span id="energy">100/100</span>
</div>
</div>
</div>
<div class="header-right">
<button class="btn btn-secondary" id="settingsBtn">
<i class="fas fa-cog"></i>
</button>
<button class="btn btn-secondary" id="discordBtn">
<i class="fab fa-discord"></i>
</button>
<button class="btn btn-info" id="localServerBtn" title="Local Server">
<i class="fas fa-server"></i>
</button>
<!-- <button class="btn btn-primary" id="saveBtn" title="Save Game">
<i class="fas fa-save"></i>
</button> -->
<button class="btn btn-warning" id="returnToMenuBtn">
<i class="fas fa-home"></i>
</button>
</div>
</header>
<!-- Navigation -->
<nav class="main-nav">
<button class="nav-btn active" data-tab="dashboard">
<i class="fas fa-tachometer-alt"></i>
<span>Dashboard</span>
</button>
<button class="nav-btn" data-tab="dungeons">
<i class="fas fa-dungeon"></i>
<span>Dungeons</span>
</button>
<button class="nav-btn" data-tab="skills">
<i class="fas fa-graduation-cap"></i>
<span>Skills</span>
</button>
<button class="nav-btn" data-tab="base">
<i class="fas fa-home"></i>
<span>Base</span>
</button>
<button class="nav-btn" data-tab="quests">
<i class="fas fa-scroll"></i>
<span>Quests</span>
</button>
<button class="nav-btn" data-tab="inventory">
<i class="fas fa-backpack"></i>
<span>Inventory</span>
</button>
<button class="nav-btn" data-tab="crafting">
<i class="fas fa-hammer"></i>
<span>Crafting</span>
</button>
<button class="nav-btn" data-tab="shop">
<i class="fas fa-store"></i>
<span>Shop</span>
</button>
</nav>
<!-- Main Content Area -->
<main class="main-content">
<!-- Dashboard Tab -->
<div class="tab-content active" id="dashboard-tab">
<div class="dashboard-grid">
<div class="card">
<h3>Fleet Status</h3>
<div class="fleet-info">
<div class="ship-status">
<i class="fas fa-rocket"></i>
<div>
<p>Flagship: <span id="flagshipName">Starter Cruiser</span></p>
<p>Health: <span id="shipHealth">100%</span></p>
</div>
</div>
</div>
</div>
<div class="card">
<h3>Idle Progress</h3>
<div class="idle-stats">
<p>Offline Time: <span id="offlineTime">0h 0m</span></p>
<p>Resources Gained: <span id="offlineResources">0</span></p>
<button class="btn btn-primary" id="claimOfflineBtn">Claim Rewards</button>
</div>
<div class="stats-grid">
<div class="stat">
<span class="stat-label">Total Kills</span>
<span class="stat-value" id="totalKills">0</span>
</div>
<div class="stat">
<span class="stat-label">Dungeons Cleared</span>
<span class="stat-value" id="dungeonsCleared">0</span>
</div>
<div class="stat">
<span class="stat-label">Play Time</span>
<span class="stat-value" id="playTime">0h 0m</span>
</div>
</div>
</div>
</div>
</div>
<!-- Dungeons Tab -->
<div class="tab-content" id="dungeons-tab">
<div class="dungeons-container">
<div class="dungeon-selector">
<h2>Select Dungeon</h2>
<div class="dungeon-list" id="dungeonList">
<!-- Dungeons will be generated here -->
</div>
</div>
<div class="dungeon-view" id="dungeonView">
<div class="dungeon-placeholder">
<i class="fas fa-dungeon"></i>
<p>Select a dungeon to begin your adventure</p>
</div>
</div>
</div>
</div>
<!-- Skills Tab -->
<div class="tab-content" id="skills-tab">
<div class="skills-container">
<div class="skills-header">
<h2><i class="fas fa-graduation-cap"></i> Skills</h2>
<div class="skill-points-display">
<span class="skill-points">Skill Points: 0</span>
</div>
</div>
<div class="skill-categories">
<button class="skill-cat-btn active" data-category="combat">Combat</button>
<button class="skill-cat-btn" data-category="science">Science</button>
<button class="skill-cat-btn" data-category="crafting">Crafting</button>
</div>
<div class="skills-grid" id="skillsGrid">
<!-- Skills will be generated here -->
</div>
</div>
</div>
<!-- Base Tab -->
<div class="tab-content" id="base-tab">
<div class="base-navigation">
<button class="base-nav-btn active" data-view="overview">Base Overview</button>
<button class="base-nav-btn" data-view="visualization">Base Visualization</button>
<button class="base-nav-btn" data-view="ships">Ship Gallery</button>
<button class="base-nav-btn" data-view="starbases">Starbases</button>
</div>
<!-- Base Overview -->
<div class="base-view-content" id="base-overview">
<div class="base-container">
<div class="base-view">
<div class="base-info">
<h3>Base Information</h3>
<div class="base-stats">
<div class="stat-item">
<span class="stat-label">Power Usage:</span>
<span class="stat-value" id="basePowerUsage">0/100</span>
</div>
<div class="stat-item">
<span class="stat-label">Storage:</span>
<span class="stat-value" id="baseStorage">1000</span>
</div>
<div class="stat-item">
<span class="stat-label">Production Rate:</span>
<span class="stat-value" id="baseProduction">0/s</span>
</div>
</div>
</div>
<div class="base-rooms" id="baseRooms">
<!-- Base rooms will be generated here -->
</div>
</div>
<div class="base-upgrades">
<h3>Base Upgrades</h3>
<div class="upgrade-list" id="baseUpgrades">
<!-- Upgrades will be generated here -->
</div>
</div>
</div>
</div>
<!-- Base Visualization -->
<div class="base-view-content hidden" id="base-visualization">
<div class="base-visualization-container">
<canvas id="baseCanvas" width="800" height="600"></canvas>
<div class="base-info-overlay">
<div class="base-stats-overlay">
<h3>Base Information</h3>
<div id="baseInfoDisplay"></div>
</div>
</div>
</div>
</div>
<!-- Ship Gallery -->
<div class="base-view-content hidden" id="base-ships">
<div class="ship-gallery-container">
<h3>Your Ships</h3>
<div class="ship-layout">
<!-- Current Ship Section -->
<div class="current-ship-section">
<h4>Current Ship</h4>
<div class="current-ship-display" id="currentShipDisplay" data-debug-id="current-ship-panel">
<div class="current-ship-info">
<div class="current-ship-image">
<img src="assets/textures/ships/starter_cruiser.png" alt="Current Ship" id="currentShipImage">
</div>
<div class="current-ship-details">
<h5 id="currentShipName">Starter Cruiser</h5>
<div class="current-ship-stats">
<div class="ship-stat">
<span class="stat-label">Class:</span>
<span class="stat-value" id="currentShipClass">Light</span>
</div>
<div class="ship-stat">
<span class="stat-label">Level:</span>
<span class="stat-value" id="currentShipLevel">1</span>
</div>
<div class="ship-stat">
<span class="stat-label">Health:</span>
<span class="stat-value" id="currentShipHealth">100/100</span>
</div>
<div class="ship-stat">
<span class="stat-label">Attack:</span>
<span class="stat-value" id="currentShipAttack">10</span>
</div>
<div class="ship-stat">
<span class="stat-label">Defense:</span>
<span class="stat-value" id="currentShipDefense">5</span>
</div>
<div class="ship-stat">
<span class="stat-label">Speed:</span>
<span class="stat-value" id="currentShipSpeed">15</span>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Ship Grid -->
<div class="ship-grid-section">
<h4>Ships Collected</h4>
<div class="ship-grid" id="shipGrid">
<!-- Ships will be displayed here as cards -->
</div>
</div>
</div>
</div>
</div>
<!-- Starbases -->
<div class="base-view-content hidden" id="base-starbases">
<div class="starbases-container">
<div class="starbase-section">
<h3>Starbase Management</h3>
<div class="starbase-list" id="starbaseList">
<!-- Starbases will be displayed here -->
</div>
</div>
<div class="starbase-section">
<h3>Available Starbases</h3>
<div class="starbase-shop" id="starbasePurchaseList">
<div class="starbase-purchase-list" id="starbasePurchaseItems">
<!-- Available starbases for purchase -->
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Quests Tab -->
<div class="tab-content" id="quests-tab">
<div class="quests-container">
<div class="quest-tabs">
<button class="quest-tab-btn active" data-type="main">Main Story</button>
<button class="quest-tab-btn" data-type="daily">Daily</button>
<button class="quest-tab-btn" data-type="weekly">Weekly</button>
<button class="quest-tab-btn" data-type="completed">Completed</button>
<button class="quest-tab-btn" data-type="failed">Failed Quests</button>
</div>
<div class="daily-countdown" id="dailyCountdown">Daily quests reset in: 00:00:00</div>
<div class="weekly-countdown" id="weeklyCountdown">Weekly quests reset in: 0d 00:00</div>
<div class="quest-list" id="questList">
<!-- Quests will be generated here -->
</div>
</div>
</div>
<!-- Inventory Tab -->
<div class="tab-content" id="inventory-tab">
<div class="inventory-container">
<div class="equipment-section">
<h3>Equipment</h3>
<div class="equipment-slots">
<div class="equipment-slot">
<div class="slot-label">Weapon</div>
<div class="slot-container" id="equip-weapon">
<div class="empty-equip-slot">Empty</div>
</div>
</div>
<div class="equipment-slot">
<div class="slot-label">Armor</div>
<div class="slot-container" id="equip-armor">
<div class="empty-equip-slot">Empty</div>
</div>
</div>
<div class="equipment-slot">
<div class="slot-label">Engine</div>
<div class="slot-container" id="equip-engine">
<div class="empty-equip-slot">Empty</div>
</div>
</div>
<div class="equipment-slot">
<div class="slot-label">Shield</div>
<div class="slot-container" id="equip-shield">
<div class="empty-equip-slot">Empty</div>
</div>
</div>
<div class="equipment-slot">
<div class="slot-label">Accessory</div>
<div class="slot-container" id="equip-accessory">
<div class="empty-equip-slot">Empty</div>
</div>
</div>
</div>
</div>
<div class="inventory-main">
<div class="inventory-section">
<h3>Inventory</h3>
<div class="inventory-grid" id="inventoryGrid">
<!-- Inventory items will be generated here -->
</div>
</div>
<div class="item-details" id="itemDetails">
<p>Select an item to view details</p>
</div>
</div>
</div>
</div>
<!-- Crafting Tab -->
<div class="tab-content" id="crafting-tab">
<div class="crafting-container">
<div class="crafting-header">
<h2><i class="fas fa-hammer"></i> Crafting Station</h2>
<div class="crafting-info">
<div class="crafting-level">
<i class="fas fa-level-up-alt"></i>
<span>Crafting Level: </span>
<span id="craftingLevel">1</span>
</div>
<div class="crafting-experience">
<i class="fas fa-star"></i>
<span>Experience: </span>
<span id="craftingExp">0/100</span>
</div>
</div>
</div>
<div class="crafting-categories">
<button class="crafting-cat-btn active" data-category="weapons">Weapons</button>
<button class="crafting-cat-btn" data-category="armor">Armor</button>
<button class="crafting-cat-btn" data-category="items">Items</button>
<button class="crafting-cat-btn" data-category="ships">Ships</button>
</div>
<div class="crafting-grid" id="recipeList">
<!-- Recipes will be generated here -->
</div>
</div>
</div>
<!-- Shop Tab -->
<div class="tab-content" id="shop-tab">
<div class="shop-container">
<div class="shop-header">
<div class="shop-categories">
<button class="shop-cat-btn active" data-category="ships">Ships</button>
<button class="shop-cat-btn" data-category="weapons">Weapons</button>
<button class="shop-cat-btn" data-category="armors">Armors</button>
<!-- <button class="shop-cat-btn" data-category="upgrades">Upgrades</button> -->
<button class="shop-cat-btn" data-category="cosmetics">Cosmetics</button>
<button class="shop-cat-btn" data-category="consumables">Consumables</button>
<button class="shop-cat-btn" data-category="materials">Materials</button>
</div>
</div>
<div class="shop-content">
<div class="shop-items-container">
<div class="shop-items" id="shopItems">
<!-- Shop items will be generated here -->
</div>
</div>
</div>
</div>
</div>
</main>
</div>
<!-- Modals -->
<div class="modal-overlay hidden" id="modalOverlay">
<div class="modal" id="modal">
<div class="modal-header">
<h3 id="modalTitle">Modal Title</h3>
<button class="modal-close" id="modalClose">
<i class="fas fa-times"></i>
</button>
</div>
<div class="modal-body" id="modalBody">
<!-- Modal content will be inserted here -->
</div>
</div>
</div>
</div>
<!-- Loading Progress Indicator -->
<div class="loading-indicator" id="loadingIndicator"></div>
<div class="loading-status hidden" id="loadingStatus">Initializing...</div>
<!-- Scripts -->
<script src="../config/xp-progression.js"></script>
<script src="js/core/DebugLogger.js"></script>
<script src="js/core/Logger.js"></script>
<script src="js/core/TextureManager.js"></script>
<script src="js/core/GameEngine.js"></script>
<script src="js/core/Player.js"></script>
<script src="js/core/Inventory.js"></script>
<script src="js/core/Economy.js"></script>
<script src="js/systems/DungeonSystem.js"></script>
<script src="js/systems/SkillSystem.js"></script>
<script src="js/systems/BaseSystem.js"></script>
<script src="js/systems/QuestSystem.js"></script>
<script src="js/systems/ShipSystem.js"></script>
<script src="js/systems/IdleSystem.js"></script>
<script src="js/systems/CraftingSystem.js"></script>
<script src="js/data/GameData.js"></script>
<script src="js/ui/UIManager.js"></script>
<script src="js/SmartSaveManager.js"></script>
<script src="js/SaveSystemIntegration.js"></script>
<script src="https://cdn.socket.io/4.7.4/socket.io.min.js"></script>
<script src="js/GameInitializer.js"></script>
<script src="js/ui/LiveMainMenu.js"></script>
<script src="js/main.js"></script>
<!-- Hidden Console Window -->
<div id="consoleWindow" class="console-window">
<div class="console-header">
<span>Developer Console</span>
<button class="console-close" onclick="toggleConsole()">×</button>
</div>
<div class="console-content">
<div id="consoleOutput" class="console-output"></div>
<div class="console-input-container">
<input type="text" id="consoleInput" class="console-input" placeholder="Type command here..." onkeypress="handleConsoleInput(event)">
</div>
</div>
</div>
</body>
</html>

View File

@ -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 = `
<i class="fas fa-server"></i>
<span>${this.serverData.name} (${this.serverData.currentPlayers}/${this.serverData.maxPlayers})</span>
`;
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;
}

View File

@ -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');

View File

@ -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');

View File

@ -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();

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -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;
}

View File

@ -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;
}
}
}

View File

@ -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 `<img src="assets/textures/missing-texture.png" style="width: ${size}; height: ${size}; object-fit: contain;" alt="Missing texture">`;
}
return `<i class="fas ${icon}" style="font-size: ${size};"></i>`;
}
// 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);
}
}
}
}

View File

@ -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
};
}

718
Client-Server/js/main.js Normal file
View File

@ -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, '<br>');
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 <amount> - Add coins to player (e.g., "coins 1000")\ngems <amount> - Add gems to player (e.g., "gems 100")\nresearch <amount> - Add research points (e.g., "research 500")\ncraftingxp <amount> - Add crafting experience (e.g., "craftingxp 200")\ngiveitem <item_id> <quantity> - Add item to inventory (e.g., "giveitem iron_ore 10")\nhealth <amount> - Set current ship health (e.g., "health 150")\nlevel <level> - Set current ship level (e.g., "level 5")\nunlock <ship_id> - 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 <amount>' };
}
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 <amount>' };
}
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 <amount>' };
}
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 <amount>' };
}
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 <item_id> <quantity>' };
}
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 <amount>' };
}
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 <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 <ship_id>' };
}
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);
}
});

File diff suppressed because it is too large Load Diff

View File

@ -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 = '<p class="no-recipes">No recipes available in this category</p>';
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 `<div class="material-item missing">
<span class="material-name">${mat.id}</span>
<span class="material-quantity">${currentCount}/${requiredCount}</span>
</div>`;
} else {
return `<div class="material-item">
<span class="material-name">${mat.id}</span>
<span class="material-quantity">${currentCount}/${requiredCount}</span>
</div>`;
}
}).join('') : '';
recipeElement.innerHTML = `
<div class="recipe-header">
<h4>${recipe.name}</h4>
<span class="recipe-level">Level ${requirementsText}</span>
</div>
<div class="recipe-description">${recipe.description}</div>
<div class="recipe-materials">
${materialsHtml}
</div>
${missingMaterials.length > 0 ? `
<div class="missing-materials-text">
<i class="fas fa-exclamation-triangle"></i>
Missing: ${missingMaterials.map(m => `${m.missing}x ${m.id}`).join(', ')}
</div>
` : ''}
<div class="recipe-time">
<i class="fas fa-clock"></i>
<span>${recipe.craftingTime / 1000}s</span>
</div>
`;
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 = `
<div class="selected-recipe">
<h3>Select a Recipe</h3>
<p>Choose a recipe from the list to see details and craft items.</p>
</div>
`;
return;
}
const recipe = this.selectedRecipe;
const canCraft = this.canCraftRecipe(recipe.id);
detailsElement.innerHTML = `
<div class="selected-recipe">
<h3>${recipe.name}</h3>
<p class="recipe-description">${recipe.description}</p>
<div class="recipe-requirements">
<h4>Requirements:</h4>
${recipe.requirements ? Object.entries(recipe.requirements).map(([skill, level]) =>
`<div class="requirement-item">
<span class="skill-name">${skill}</span>
<span class="skill-level">Level ${level}</span>
</div>`
).join('') : '<p>No special requirements</p>'}
</div>
<div class="recipe-materials-needed">
<h4>Materials Needed:</h4>
${recipe.materials ? recipe.materials.map(mat =>
`<div class="material-needed">
<span class="material-name">${mat.id}</span>
<span class="material-needed">x${mat.quantity}</span>
<span class="material-have">Have: ${this.game.systems.inventory?.getItemCount(mat.id) || 0}</span>
</div>`
).join('') : '<p>No materials needed</p>'}
</div>
<div class="recipe-results">
<h4>Results:</h4>
${recipe.results ? recipe.results.map(result =>
`<div class="result-item">
<span class="result-name">${result.id}</span>
<span class="result-quantity">x${result.quantity}</span>
</div>`
).join('') : ''}
</div>
<div class="recipe-info">
<div class="experience-reward">
<i class="fas fa-star"></i>
<span>${recipe.experience} XP</span>
</div>
<div class="crafting-time">
<i class="fas fa-clock"></i>
<span>${recipe.craftingTime / 1000} seconds</span>
</div>
</div>
<button class="btn btn-primary craft-btn ${canCraft ? '' : 'disabled'}"
${canCraft ? `onclick="window.game.systems.crafting.craftRecipe('${recipe.id}')"` : 'disabled'}>
${canCraft ? 'Craft Item' : 'Cannot Craft'}
</button>
</div>
`;
}
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;
}

File diff suppressed because it is too large Load Diff

View File

@ -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;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -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 = `
<div class="ship-card-header">
<img src="${ship.image}" alt="${ship.name}" class="ship-card-image">
<div class="ship-card-info">
<div class="ship-card-rarity ${ship.rarity.toLowerCase()}">${ship.rarity}</div>
</div>
</div>
<div class="ship-card-actions">
<button class="btn-action btn-switch" onclick="game.systems.ship.switchShip('${ship.id}')"
${ship.status === 'active' ? 'disabled' : ''}>
${ship.status === 'active' ? 'ACTIVE' : 'SWITCH'}
</button>
</div>
`;
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;
}
}

View File

@ -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 = `
<div class="skill-header">
<div class="skill-icon">
<i class="fas ${iconClass}"></i>
</div>
<div class="skill-info">
<div class="skill-name">${skill.name}</div>
<div class="skill-level">Lv. ${skill.currentLevel}/${skill.maxLevel}</div>
</div>
</div>
<div class="skill-description">${skill.description}</div>
${skill.currentLevel > 0 && skill.currentLevel < skill.maxLevel ? `
<div class="skill-progress">
<div class="progress-bar">
<div class="progress-fill" style="width: ${progressPercent}%"></div>
</div>
<span>${skill.experience}/${skill.experienceToNext} XP</span>
</div>
` : skill.currentLevel >= skill.maxLevel ? `
<div class="skill-max-level">
<span>MAX LEVEL</span>
</div>
` : ''}
<div class="skill-actions">
${!skill.unlocked ? `
<button class="btn btn-warning" onclick="if(window.game && window.game.systems) window.game.systems.skillSystem.unlockSkill('${activeCategory}', '${skillId}')">
Unlock (2 Points)
</button>
` : skill.currentLevel < skill.maxLevel ? `
<button class="btn btn-primary" onclick="if(window.game && window.game.systems) window.game.systems.skillSystem.upgradeSkill('${activeCategory}', '${skillId}')">
Upgrade (1 Point)
</button>
` : `
<span class="max-level">MAX LEVEL</span>
`}
</div>
${skill.requiredLevel && !skill.unlocked ? `
<div class="skill-requirement">Requires Level ${skill.requiredLevel}</div>
` : ''}
`;
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();
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

4688
Client-Server/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

131
Client-Server/package.json Normal file
View File

@ -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 <contact@korvarixstudios.com>"
},
"nsis": {
"oneClick": false,
"allowToChangeInstallationDirectory": true,
"createDesktopShortcut": true,
"createStartMenuShortcut": true
}
}
}

33
Client-Server/preload.js Normal file
View File

@ -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);
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -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;
}
}