this is a very unstable version and old of the client server setup
BIN
Client-Server/assets/textures/armors/basic_armor.png
Normal file
|
After Width: | Height: | Size: 187 KiB |
BIN
Client-Server/assets/textures/armors/heavy_armor.png
Normal file
|
After Width: | Height: | Size: 254 KiB |
BIN
Client-Server/assets/textures/armors/medium_armor.png
Normal file
|
After Width: | Height: | Size: 226 KiB |
BIN
Client-Server/assets/textures/base/command_center.png
Normal file
|
After Width: | Height: | Size: 151 KiB |
BIN
Client-Server/assets/textures/base/mining_facility.png
Normal file
|
After Width: | Height: | Size: 213 KiB |
BIN
Client-Server/assets/textures/items/advanced_circuitboard.png
Normal file
|
After Width: | Height: | Size: 268 KiB |
BIN
Client-Server/assets/textures/items/advanced_component.png
Normal file
|
After Width: | Height: | Size: 156 KiB |
BIN
Client-Server/assets/textures/items/advanced_components.png
Normal file
|
After Width: | Height: | Size: 59 KiB |
BIN
Client-Server/assets/textures/items/bandages.png
Normal file
|
After Width: | Height: | Size: 101 KiB |
BIN
Client-Server/assets/textures/items/basic_circuitboard.png
Normal file
|
After Width: | Height: | Size: 291 KiB |
BIN
Client-Server/assets/textures/items/battery.png
Normal file
|
After Width: | Height: | Size: 111 KiB |
BIN
Client-Server/assets/textures/items/common_circuitboard.png
Normal file
|
After Width: | Height: | Size: 324 KiB |
BIN
Client-Server/assets/textures/items/copper_ore.png
Normal file
|
After Width: | Height: | Size: 145 KiB |
BIN
Client-Server/assets/textures/items/copper_wire.png
Normal file
|
After Width: | Height: | Size: 192 KiB |
BIN
Client-Server/assets/textures/items/energy_crystal.png
Normal file
|
After Width: | Height: | Size: 122 KiB |
BIN
Client-Server/assets/textures/items/health_pack.png
Normal file
|
After Width: | Height: | Size: 108 KiB |
BIN
Client-Server/assets/textures/items/herbs.png
Normal file
|
After Width: | Height: | Size: 212 KiB |
BIN
Client-Server/assets/textures/items/iron_ore.png
Normal file
|
After Width: | Height: | Size: 312 KiB |
BIN
Client-Server/assets/textures/items/leather.png
Normal file
|
After Width: | Height: | Size: 159 KiB |
BIN
Client-Server/assets/textures/items/mega_health_pack.png
Normal file
|
After Width: | Height: | Size: 138 KiB |
BIN
Client-Server/assets/textures/items/stell_plate.png
Normal file
|
After Width: | Height: | Size: 286 KiB |
BIN
Client-Server/assets/textures/items/tin_bar.png
Normal file
|
After Width: | Height: | Size: 123 KiB |
BIN
Client-Server/assets/textures/missing-texture.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
Client-Server/assets/textures/ships/heavy_cruiser.png
Normal file
|
After Width: | Height: | Size: 214 KiB |
BIN
Client-Server/assets/textures/ships/heavy_destroyer.png
Normal file
|
After Width: | Height: | Size: 200 KiB |
BIN
Client-Server/assets/textures/ships/light_destroyer.png
Normal file
|
After Width: | Height: | Size: 162 KiB |
BIN
Client-Server/assets/textures/ships/starter_cruiser.png
Normal file
|
After Width: | Height: | Size: 174 KiB |
BIN
Client-Server/assets/textures/weapons/laser_pistol.png
Normal file
|
After Width: | Height: | Size: 185 KiB |
BIN
Client-Server/assets/textures/weapons/laser_sniper_rifle.png
Normal file
|
After Width: | Height: | Size: 120 KiB |
BIN
Client-Server/assets/textures/weapons/starter_blaster.png
Normal file
|
After Width: | Height: | Size: 140 KiB |
451
Client-Server/electron-main copy.js
Normal 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);
|
||||||
|
});
|
||||||
|
});
|
||||||
451
Client-Server/electron-main.js
Normal 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);
|
||||||
|
});
|
||||||
|
});
|
||||||
699
Client-Server/index copy.html
Normal 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
@ -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>
|
||||||
720
Client-Server/js/GameInitializer.js
Normal 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;
|
||||||
|
}
|
||||||
350
Client-Server/js/SaveSystemIntegration.js
Normal 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');
|
||||||
183
Client-Server/js/SmartSaveManager.js
Normal 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');
|
||||||
163
Client-Server/js/core/DebugLogger.js
Normal 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();
|
||||||
2335
Client-Server/js/core/Economy.js
Normal file
1567
Client-Server/js/core/GameEngine.js
Normal file
1134
Client-Server/js/core/Inventory.js
Normal file
304
Client-Server/js/core/Logger.js
Normal 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;
|
||||||
|
}
|
||||||
912
Client-Server/js/core/Player.js
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
142
Client-Server/js/core/TextureManager.js
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
570
Client-Server/js/data/GameData.js
Normal 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
@ -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);
|
||||||
|
}
|
||||||
|
});
|
||||||
2188
Client-Server/js/systems/BaseSystem.js
Normal file
657
Client-Server/js/systems/CraftingSystem.js
Normal 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;
|
||||||
|
}
|
||||||
1985
Client-Server/js/systems/DungeonSystem.js
Normal file
357
Client-Server/js/systems/IdleSystem.js
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
2726
Client-Server/js/systems/QuestSystem.js
Normal file
223
Client-Server/js/systems/ShipSystem.js
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
596
Client-Server/js/systems/SkillSystem.js
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
1202
Client-Server/js/ui/LiveMainMenu.js
Normal file
1251
Client-Server/js/ui/MainMenu.js
Normal file
2459
Client-Server/js/ui/UIManager.js
Normal file
4688
Client-Server/package-lock.json
generated
Normal file
131
Client-Server/package.json
Normal 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
@ -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);
|
||||||
|
}
|
||||||
1677
Client-Server/styles/components.css
Normal file
2544
Client-Server/styles/main.css
Normal file
842
Client-Server/styles/tables.css
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||