Game-Server/Client/js/core/GameEngine.js
2026-01-30 10:58:30 -04:00

754 lines
33 KiB
JavaScript

/**
* Galaxy Strike Online - Game Engine
* Core game loop and state management
*/
class GameEngine extends EventTarget {
constructor() {
// Must call super() first since we extend EventTarget
super();
// Basic game state
this.isRunning = false;
this.isPaused = false;
this.gameTime = 0;
this.lastSaveTime = 0;
this.autoSaveInterval = 5000; // 5 seconds
this.gameLogicInterval = 1000; // 1 second for game updates
// Game systems
this.systems = {};
// Save slot configuration
this.saveSlotInfo = {
slot: 1,
useFileSystem: true
};
// Game state
this.state = {
paused: false,
currentTab: 'dashboard',
notifications: []
};
// Event listeners
this.eventListeners = new Map();
// Initialize immediately
this.init();
}
setMultiplayerMode(isMultiplayer, socket = null, serverData = null, currentUser = null) {
const debugLogger = window.debugLogger;
console.log('[GAME ENGINE] Setting multiplayer mode:', isMultiplayer);
console.log('[GAME ENGINE] Previous mode was:', this.isMultiplayer);
if (debugLogger) debugLogger.logStep('setMultiplayerMode', { isMultiplayer, previousMode: this.isMultiplayer });
// CRITICAL: Once set to multiplayer, never allow fallback to singleplayer
if (this.isMultiplayer && !isMultiplayer) {
console.warn('[GAME ENGINE] ATTEMPTED FALLBACK TO SINGLEPLAYER - BLOCKING!');
console.log('[GAME ENGINE] Preserving multiplayer mode');
return; // Don't allow fallback to singleplayer
}
this.isMultiplayer = isMultiplayer;
this.socket = socket;
this.serverData = serverData;
this.currentUser = currentUser;
// Store multiplayer settings for systems that need them
this.multiplayerConfig = {
isMultiplayer,
socket,
serverData,
currentUser
};
console.log('[GAME ENGINE] Multiplayer mode configured:', {
isMultiplayer,
hasSocket: !!socket,
hasServerData: !!serverData,
hasCurrentUser: !!currentUser
});
}
// Get random integer between min and max (inclusive)
getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
// Get random float between min and max
getRandomFloat(min, max) {
return Math.random() * (max - min) + min;
}
async init() {
console.log('[GAME ENGINE] Initializing game engine');
const logger = window.logger;
const debugLogger = window.debugLogger;
if (logger) await logger.info('Initializing game engine');
if (debugLogger) await debugLogger.startStep('gameEngineInit');
try {
// In multiplayer mode, use simplified initialization to avoid hanging
if (this.isMultiplayer) {
console.log('[GAME ENGINE] Using simplified multiplayer initialization');
try {
await this.initializeMultiplayerSystems();
console.log('[GAME ENGINE] Multiplayer initialization complete - skipping event listeners');
} catch (multiplayerError) {
console.error('[GAME ENGINE] Multiplayer systems initialization failed:', multiplayerError);
// Don't fall back to singleplayer - keep multiplayer mode but with minimal systems
console.log('[GAME ENGINE] Continuing with minimal multiplayer systems');
// Create essential systems only
this.systems.player = new Player(this);
this.systems.inventory = new Inventory(this);
this.systems.economy = new Economy(this);
this.systems.itemSystem = new ItemSystem(this);
this.systems.idleSystem = new IdleSystem(this);
}
} else {
// Full initialization for singleplayer
await this.initializeSystemsForLoad();
if (debugLogger) await debugLogger.logStep('Systems initialized, setting up event listeners');
// Set up event listeners (only in singleplayer to avoid conflicts)
await this.setupEventListeners();
}
if (debugLogger) await debugLogger.endStep('gameEngineInit', {
systemsInitialized: Object.keys(this.systems).length,
eventListeners: this.eventListeners.size
});
} catch (error) {
console.error('Failed to initialize game:', error);
if (logger) await logger.errorEvent(error, 'Game Engine Initialization');
if (debugLogger) await debugLogger.errorEvent(error, 'Game Engine Initialization');
}
}
// Simplified multiplayer-only system initialization to avoid hanging
async initializeMultiplayerSystems() {
console.log('[GAME ENGINE] Initializing multiplayer systems');
try {
// Initialize texture manager first
this.systems.textureManager = new TextureManager(this);
// Create essential systems immediately
console.log('[GAME ENGINE] Creating essential systems');
this.systems.player = new Player(this);
this.systems.inventory = new Inventory(this);
this.systems.economy = new Economy(this);
this.systems.ui = new UIManager(this);
this.systems.idleSystem = new IdleSystem(this);
this.systems.itemSystem = new ItemSystem(this);
console.log('[GAME ENGINE] Essential systems created successfully');
// Initialize ItemSystem ONCE and emit event when ready
console.log('[GAME ENGINE] Initializing ItemSystem (single initialization)');
this.systems.itemSystem.initialize().then(() => {
console.log('[GAME ENGINE] ItemSystem fully initialized, emitting ready event');
this.emit('itemSystemReady');
}).catch(error => {
console.error('[GAME ENGINE] ItemSystem initialization failed:', error);
// Still emit event so Economy can fallback gracefully
this.emit('itemSystemReady');
});
// Initialize Economy (without ItemSystem - it will wait for the event)
console.log('[GAME ENGINE] Initializing Economy system');
this.systems.economy.initialize().catch(error => {
console.error('[GAME ENGINE] Economy initialization failed:', error);
});
// Create additional systems asynchronously to avoid blocking
setTimeout(() => {
console.log('[GAME ENGINE] Creating additional multiplayer systems asynchronously');
if (typeof SkillSystem !== 'undefined') {
this.systems.skillSystem = new SkillSystem(this);
}
if (typeof DungeonSystem !== 'undefined') {
this.systems.dungeonSystem = new DungeonSystem(this);
// Initialize server-driven dungeon system
this.systems.dungeonSystem.initialize().then(() => {
console.log('[GAME ENGINE] DungeonSystem initialized with server data');
}).catch(error => {
console.error('[GAME ENGINE] Failed to initialize DungeonSystem:', error);
});
}
if (typeof QuestSystem !== 'undefined') {
this.systems.questSystem = new QuestSystem(this);
console.log('[GAME ENGINE] QuestSystem created');
}
if (typeof CraftingSystem !== 'undefined') {
this.systems.crafting = new CraftingSystem(this);
}
if (typeof BaseSystem !== 'undefined') {
this.systems.base = new BaseSystem(this);
}
console.log('[GAME ENGINE] All multiplayer systems created asynchronously');
}, 100); // Create after 100ms delay
} catch (error) {
console.error('[GAME ENGINE] Error in multiplayer systems initialization:', error);
// Don't re-throw - allow game to continue with basic systems
console.log('[GAME ENGINE] Continuing with available systems');
}
}
async initializeSystemsForLoad() {
const logger = window.logger;
const debugLogger = window.debugLogger;
if (debugLogger) await debugLogger.startStep('initializeSystemsForLoad');
if (logger) {
await logger.timeAsync('Game Systems Initialization for Load', async () => {
await logger.info('Initializing game systems for loading');
if (debugLogger) await debugLogger.logStep('Initializing TextureManager');
// Initialize texture manager first
this.systems.textureManager = new TextureManager(this);
if (logger) await logger.systemEvent('TextureManager', 'Initialized');
if (debugLogger) await debugLogger.logStep('TextureManager initialized');
if (debugLogger) await debugLogger.logStep('Creating Player system (without initialization)');
// Create systems but don't initialize with default data
this.systems.player = new Player(this);
if (logger) await logger.systemEvent('Player', 'Created');
if (debugLogger) await debugLogger.logStep('Player system created');
if (debugLogger) await debugLogger.logStep('Creating Inventory system (without initialization)');
this.systems.inventory = new Inventory(this);
if (logger) await logger.systemEvent('Inventory', 'Created');
if (debugLogger) await debugLogger.logStep('Inventory system created');
if (debugLogger) await debugLogger.logStep('Creating Economy system (without initialization)');
this.systems.economy = new Economy(this);
if (logger) await logger.systemEvent('Economy', 'Created');
if (debugLogger) await debugLogger.logStep('Economy system created');
// In multiplayer mode, skip singleplayer systems
if (!this.isMultiplayer) {
if (debugLogger) await debugLogger.logStep('Creating IdleSystem');
this.systems.idleSystem = new IdleSystem(this);
if (logger) await logger.systemEvent('IdleSystem', 'Created');
if (debugLogger) await debugLogger.logStep('IdleSystem created');
if (debugLogger) await debugLogger.logStep('Creating ItemSystem');
this.systems.itemSystem = new ItemSystem(this);
if (logger) await logger.systemEvent('ItemSystem', 'Created');
if (debugLogger) await debugLogger.logStep('ItemSystem created');
} else {
console.log('[GAME ENGINE] Multiplayer mode - skipping singleplayer systems (IdleSystem, ItemSystem)');
}
if (debugLogger) await debugLogger.logStep('Creating UIManager');
if (typeof UIManager !== 'undefined') {
console.log('[GAME ENGINE] UIManager class found, creating real UIManager');
this.systems.ui = new UIManager(this);
// Expose UIManager globally for button onclick handlers
window.uiManager = this.systems.ui;
window.game.systems.ui = this.systems.ui;
if (logger) await logger.systemEvent('UIManager', 'Created');
if (debugLogger) await debugLogger.logStep('UIManager created and exposed');
} else {
console.error('[GAME ENGINE] UIManager class not found - this should not happen!');
if (debugLogger) await debugLogger.error('UIManager class not found - this should not happen!');
}
if (debugLogger) await debugLogger.endStep('initializeSystemsForLoad', {
systemsCreated: Object.keys(this.systems).length
});
});
}
}
async startGame(continueGame = false) {
const logger = window.logger;
const debugLogger = window.debugLogger;
console.log('[GAME ENGINE] startGame called with continueGame =', continueGame);
if (logger) await logger.info('Starting game', { continueGame });
if (debugLogger) await debugLogger.startStep('startGame', { continueGame });
try {
if (continueGame) {
console.log('[GAME ENGINE] Loading existing save data...');
if (debugLogger) await debugLogger.logStep('Loading existing save data');
await this.loadGame();
console.log('[GAME ENGINE] Save data loaded');
} else {
console.log('[GAME ENGINE] Creating new game...');
if (debugLogger) await debugLogger.logStep('Creating new game');
await this.newGame();
console.log('[GAME ENGINE] New game created');
}
// Start game loop
this.start();
console.log('[GAME ENGINE] Game loop started');
if (debugLogger) await debugLogger.logStep('Game loop started');
const loadingStatus = document.getElementById('loadingStatus');
if (loadingStatus) {
console.log('[GAME ENGINE] Hiding loading status text');
if (debugLogger) await debugLogger.logStep('Hiding loading status text');
loadingStatus.classList.add('hidden');
}
const gameInterface = document.getElementById('gameInterface');
if (gameInterface) {
console.log('[GAME ENGINE] Showing game interface');
if (debugLogger) await debugLogger.logStep('Showing game interface');
gameInterface.classList.remove('hidden');
} else {
console.warn('[GAME ENGINE] gameInterface element not found');
if (debugLogger) await debugLogger.warn('gameInterface element not found');
}
if (logger) await logger.info('Game started successfully');
if (debugLogger) await debugLogger.endStep('startGame', {
continueGame,
isRunning: this.isRunning,
gameTime: this.gameTime
});
} catch (error) {
console.error('[GAME ENGINE] Failed to start game:', error);
if (logger) await logger.errorEvent(error, 'Game Start');
if (debugLogger) await debugLogger.errorEvent(error, 'Game Start');
}
}
start() {
const debugLogger = window.debugLogger;
if (this.isRunning) {
if (debugLogger) debugLogger.log('GameEngine.start() called but game is already running', {
isRunning: this.isRunning,
gameTime: this.gameTime
});
return;
}
if (debugLogger) debugLogger.logStep('Starting game engine', {
gameLogicInterval: this.gameLogicInterval
});
this.isRunning = true;
this.lastUpdate = Date.now();
// Start game logic timer (completely independent of frame rate)
console.log('[GAME ENGINE] Starting game logic timer with interval:', this.gameLogicInterval);
this.gameLogicTimer = setInterval(() => {
this.updateGameLogic();
}, this.gameLogicInterval);
// Start auto-save
this.startAutoSave();
console.log('[GAME ENGINE] Game engine started');
if (debugLogger) debugLogger.logStep('Game engine started successfully', {
gameLogicInterval: this.gameLogicInterval,
autoSaveInterval: this.autoSaveInterval
});
}
updateGameLogic() {
const debugLogger = window.debugLogger;
// Use fixed 1-second interval for all updates
const fixedDelta = 1000; // 1 second in milliseconds
if (this.state.paused) {
if (debugLogger) debugLogger.logStep('Game logic update called but game is paused', {
gameTime: this.gameTime,
fixedDelta: fixedDelta
});
return;
}
this.gameTime += fixedDelta;
// Update player play time with fixed delta
if (this.systems.player && this.systems.player.updatePlayTime) {
this.systems.player.updatePlayTime(fixedDelta);
}
// Update all systems with fixed delta
for (const [name, system] of Object.entries(this.systems)) {
if (system && typeof system.update === 'function') {
try {
system.update(fixedDelta);
} catch (error) {
console.error(`[GAME ENGINE] Error updating ${name} system:`, error);
if (debugLogger) debugLogger.errorEvent(error, `Update ${name} system`);
}
}
}
// Update UI displays (money, gems, energy) after system updates
if (this.systems && this.systems.ui) {
try {
this.systems.ui.updateUI();
} catch (error) {
console.error('[GAME ENGINE] Error updating UI:', error);
}
}
// Emit game updated event
this.emit('gameUpdated', { gameTime: this.gameTime });
}
startAutoSave() {
const debugLogger = window.debugLogger;
// Load saved interval or use default
const savedInterval = localStorage.getItem('autoSaveInterval');
this.autoSaveInterval = savedInterval ? parseInt(savedInterval) : 5;
console.log(`[GAME ENGINE] Starting auto-save with ${this.autoSaveInterval} minute interval`);
// Clear any existing timer
this.stopAutoSave();
// Set up new timer
this.autoSaveTimer = setInterval(async () => {
console.log('[GAME ENGINE] Auto-save timer triggered - isRunning:', this.isRunning, 'paused:', this.state.paused);
if (this.isRunning && !this.state.paused) {
console.log('[GAME ENGINE] Auto-saving game...');
try {
// In multiplayer mode, save to server
if (window.smartSaveManager?.isMultiplayer) {
console.log('[GAME ENGINE] Auto-saving to server...');
if (this.socket) {
this.socket.emit('saveGameData', {
timestamp: Date.now(),
gameTime: this.gameTime
});
} else {
console.warn('[GAME ENGINE] No socket available for server save');
}
} else {
// Singleplayer mode - local save (not implemented yet)
console.log('[GAME ENGINE] Local auto-save not implemented');
}
this.showNotification('Game auto-saved', 'info', 2000);
console.log('[GAME ENGINE] Auto-save completed successfully');
} catch (error) {
console.error('[GAME ENGINE] Auto-save failed:', error);
if (debugLogger) await debugLogger.errorEvent(error, 'Auto-save');
}
} else {
console.log('[GAME ENGINE] Auto-save skipped - game not running or paused');
}
}, this.autoSaveInterval * 60 * 1000); // Convert minutes to milliseconds
}
stopAutoSave() {
if (this.autoSaveTimer) {
console.log('[GAME ENGINE] Stopping auto-save timer');
clearInterval(this.autoSaveTimer);
this.autoSaveTimer = null;
}
}
// Notification system
async showNotification(message, type = 'info', duration = 3000) {
const logger = window.logger;
if (logger) await logger.playerAction('Notification', { message, type, duration });
const notification = {
id: Date.now(),
message,
type,
duration,
timestamp: Date.now()
};
this.state.notifications.push(notification);
// Auto-remove notification after duration
setTimeout(() => {
this.removeNotification(notification.id);
}, duration);
// Update UI
if (this.systems.ui) {
// UI updates handled by individual systems
}
}
removeNotification(id) {
this.state.notifications = this.state.notifications.filter(notification => notification.id !== id);
}
// Event system
on(event, callback) {
if (!this.eventListeners.has(event)) {
this.eventListeners.set(event, []);
}
this.eventListeners.get(event).push(callback);
}
emit(event, data) {
if (this.eventListeners.has(event)) {
this.eventListeners.get(event).forEach(callback => {
try {
callback(data);
} catch (error) {
console.error(`[GAME ENGINE] Error in event listener for ${event}:`, error);
}
});
}
// Also dispatch as DOM event for UIManager
this.dispatchEvent(new CustomEvent(event, { detail: data }));
}
// Utility methods
formatNumber(num) {
if (num >= 1e9) return (num / 1e9).toFixed(2) + 'B';
if (num >= 1e6) return (num / 1e6).toFixed(2) + 'M';
if (num >= 1e3) return (num / 1e3).toFixed(2) + 'K';
return num.toString();
}
formatTime(seconds) {
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
const secs = Math.floor(seconds % 60);
if (hours > 0) {
return `${hours}h ${minutes}m ${secs}s`;
} else if (minutes > 0) {
return `${minutes}m ${secs}s`;
} else {
return `${secs}s`;
}
}
getPerformanceStats() {
const debugLogger = window.debugLogger;
const stats = {
gameTime: this.gameTime,
isRunning: this.isRunning,
lastUpdate: this.lastUpdate,
memory: null
};
// Add memory info if available
if (window.performance && window.performance.memory) {
stats.memory = {
used: window.performance.memory.usedJSHeapSize,
total: window.performance.memory.totalJSHeapSize,
limit: window.performance.memory.jsHeapSizeLimit
};
}
return stats;
}
// Load server player data (transforms server format to client format)
async loadServerPlayerData(playerData) {
console.log('[GAME ENGINE] Loading server player data with format transformation');
console.log('[GAME ENGINE] Original server data structure:', playerData);
// Transform server data format to client format
const transformedData = {
...playerData,
// Transform quests from server format to client format
quests: playerData.quests ? {
mainQuests: playerData.quests.main || [],
dailyQuests: playerData.quests.daily || [],
weeklyQuests: playerData.quests.weekly || [],
tutorialQuests: playerData.quests.tutorial || [],
activeQuests: playerData.quests.active || [],
completedQuests: playerData.quests.completed || []
} : {
mainQuests: [],
dailyQuests: [],
weeklyQuests: [],
tutorialQuests: [],
activeQuests: [],
completedQuests: []
}
};
// DEBUG: Log quest data transformation
console.log('[GAME ENGINE] Quest data transformation:', {
serverQuests: playerData.quests,
transformedQuests: transformedData.quests,
mainQuestsCount: transformedData.quests.mainQuests.length,
dailyQuestsCount: transformedData.quests.dailyQuests.length,
weeklyQuestsCount: transformedData.quests.weeklyQuests.length,
tutorialQuestsCount: transformedData.quests.tutorialQuests.length
});
// Use crafting data from server or initialize empty
transformedData.crafting = playerData.crafting || {
skill: 1,
experience: 0,
knownRecipes: [],
completedDungeons: [],
currentInstance: null,
dungeonProgress: {}
};
return transformedData;
}
async loadPlayerData(playerData) {
console.log('[GAME ENGINE] Loading server player data');
console.log('[GAME ENGINE] Full playerData structure:', playerData);
console.log('[GAME ENGINE] PlayerData keys:', Object.keys(playerData));
try {
// Apply basic player stats
if (playerData.stats && this.systems && this.systems.player) {
console.log('[GAME ENGINE] Found player stats and player system, applying...');
console.log('[GAME ENGINE] Server playerData.stats:', playerData.stats);
console.log('[GAME ENGINE] Server playerData keys:', Object.keys(playerData));
// Check for playTime in different possible locations
const possiblePlayTimeFields = [
playerData.stats?.playTime,
playerData.playTime,
playerData.totalPlayTime,
playerData.stats?.totalPlayTime
];
console.log('[GAME ENGINE] Possible playTime fields found:', possiblePlayTimeFields);
// Preserve existing playTime if server doesn't provide it
const existingPlayTime = this.systems.player.stats.playTime || 0;
console.log('[GAME ENGINE] Preserving existing playTime:', existingPlayTime);
this.systems.player.load(playerData.stats);
console.log('[GAME ENGINE] Applied player stats:', playerData.stats);
// Restore playTime if it was lost
if (!this.systems.player.stats.playTime || this.systems.player.stats.playTime === 0) {
this.systems.player.stats.playTime = existingPlayTime;
console.log('[GAME ENGINE] Restored playTime to:', existingPlayTime);
}
console.log('[GAME ENGINE] Final playTime after load:', this.systems.player.stats.playTime);
// Apply credits from server data to economy system
if (playerData.stats.credits !== undefined && this.systems.economy) {
this.systems.economy.credits = playerData.stats.credits;
console.log('[GAME ENGINE] Applied credits from server:', playerData.stats.credits);
}
// Apply gems from server data to economy system
if (playerData.stats.gems !== undefined && this.systems.economy) {
this.systems.economy.gems = playerData.stats.gems;
console.log('[GAME ENGINE] Applied gems from server:', playerData.stats.gems);
}
// Force manual sync to ensure economy is updated
if (this.systems.economy && this.systems.economy.syncWithServerData) {
console.log('[GAME ENGINE] Forcing manual economy sync');
this.systems.economy.syncWithServerData(playerData);
}
// Request fresh economy data from server to ensure sync
if (this.systems.economy && this.systems.economy.requestEconomyData) {
setTimeout(() => {
this.systems.economy.requestEconomyData();
}, 1000); // Delay to ensure socket is ready
}
// Apply energy from server data to player attributes
if (playerData.stats.currentEnergy !== undefined && this.systems.player.attributes) {
this.systems.player.attributes.currentEnergy = playerData.stats.currentEnergy;
console.log('[GAME ENGINE] Applied current energy from server:', playerData.stats.currentEnergy);
}
if (playerData.stats.maxEnergy !== undefined && this.systems.player.attributes) {
this.systems.player.attributes.maxEnergy = playerData.stats.maxEnergy;
console.log('[GAME ENGINE] Applied max energy from server:', playerData.stats.maxEnergy);
}
// Ensure player has minimum energy for dungeon access
if (this.systems.player.attributes) {
// Check if energy is missing or too low
if (!this.systems.player.attributes.energy || this.systems.player.attributes.energy < 10) {
const oldEnergy = this.systems.player.attributes.energy;
this.systems.player.attributes.energy = 100;
this.systems.player.attributes.maxEnergy = Math.max(this.systems.player.attributes.maxEnergy || 0, 100);
console.log('[GAME ENGINE] Set minimum energy for dungeon access:', oldEnergy, '->', this.systems.player.attributes.energy);
}
// Also ensure currentEnergy is set if it exists
if (this.systems.player.attributes.currentEnergy !== undefined) {
if (this.systems.player.attributes.currentEnergy < 10) {
this.systems.player.attributes.currentEnergy = 100;
console.log('[GAME ENGINE] Set minimum currentEnergy for dungeon access');
}
}
}
console.log('[GAME ENGINE] Final player stats after application:', this.systems.player.stats);
} else {
console.log('[GAME ENGINE] Missing player stats or player system');
console.log('[GAME ENGINE] - playerData.stats:', !!playerData.stats);
console.log('[GAME ENGINE] - this.systems:', !!this.systems);
console.log('[GAME ENGINE] - this.systems.player:', !!this.systems?.player);
}
// Apply inventory
if (playerData.inventory && this.systems && this.systems.inventory) {
this.systems.inventory.load(playerData.inventory);
console.log('[GAME ENGINE] Applied inventory');
}
// REMOVED: QuestSystem should be server-driven only
// Quest data will be handled by server-side systems only
// Show notification
if (this.showNotification) {
this.showNotification(`Welcome back! Level ${playerData.stats?.level || 1}`, 'success', 3000);
}
console.log('[GAME ENGINE] Server player data loaded successfully');
// Trigger UI update to refresh all tabs with new data
if (this.systems && this.systems.ui) {
this.systems.ui.updateUI();
console.log('[GAME ENGINE] Triggered UI update after server data load');
}
} catch (error) {
console.error('[GAME ENGINE] Error loading server player data:', error);
if (this.showNotification) {
this.showNotification('Failed to load server data!', 'error', 3000);
}
}
}
}
// Global game instance
let game = null;
// Export GameEngine to global scope
if (typeof window !== 'undefined') {
window.GameEngine = GameEngine;
}