/** * Game Server - Based on Client LocalServer Infrastructure * Handles real-time multiplayer game instances and gameplay */ const express = require('express'); const http = require('http'); const socketIo = require('socket.io'); const cors = require('cors'); const helmet = require('helmet'); const compression = require('compression'); const mongoose = require('mongoose'); const fs = require('fs'); const path = require('path'); require('dotenv').config(); const logger = require('./utils/logger'); const connectDB = require('./config/database'); const PlayerData = require('./models/PlayerData'); // Import server systems for player initialization const QuestSystem = require('./systems/QuestSystem'); const SkillSystem = require('./systems/SkillSystem'); const DungeonSystem = require('./systems/DungeonSystem'); const CraftingSystem = require('./systems/CraftingSystem'); const IdleSystem = require('./systems/IdleSystem'); const ItemSystem = require('./systems/ItemSystem'); // Initialize server systems const questSystem = new QuestSystem(); const skillSystem = new SkillSystem(); const dungeonSystem = new DungeonSystem(); const craftingSystem = new CraftingSystem(); const idleSystem = new IdleSystem(); const itemSystem = new ItemSystem(); // Set server URL for ItemSystem const SERVER_URL = process.env.SERVER_URL || 'https://dev.gameserver.galaxystrike.online'; itemSystem.setServerUrl(SERVER_URL); const app = express(); const server = http.createServer(app); const io = socketIo(server, { cors: { origin: [ "http://localhost:3000", "http://127.0.0.1:3000", "file://", "https://dev.galaxystrike.online", "https://galaxystrike.online" ], methods: ["GET", "POST"], credentials: true } }); // Game state const gameInstances = new Map(); const connectedClients = new Map(); // Server-side shop item lookup using ItemSystem function findShopItem(itemId) { return itemSystem.getItem(itemId); } // Middleware app.use(cors({ origin: [ "http://localhost:3000", "http://127.0.0.1:3000", "file://", "https://dev.galaxystrike.online", "https://galaxystrike.online" ], credentials: true })); app.use(express.json({ limit: '10mb' })); // Serve static files from the client directory app.use(express.static(path.join(__dirname, '../Client'))); // Serve ships from server-side storage app.use('/images/ships', (req, res, next) => { const requestedPath = req.path; const serverImagePath = path.join(__dirname, 'assets/images/ships', requestedPath); console.log('[IMAGE SERVER] Ship requested:', requestedPath); if (fs.existsSync(serverImagePath)) { res.sendFile(serverImagePath); } else { const placeholderPath = path.join(__dirname, 'assets/images/ui/placeholder.png'); res.sendFile(placeholderPath); } }); // Serve weapons from server-side storage app.use('/images/weapons', (req, res, next) => { const requestedPath = req.path; const serverImagePath = path.join(__dirname, 'assets/images/weapons', requestedPath); console.log('[IMAGE SERVER] Weapon requested:', requestedPath); if (fs.existsSync(serverImagePath)) { res.sendFile(serverImagePath); } else { const placeholderPath = path.join(__dirname, 'assets/images/ui/placeholder.png'); res.sendFile(placeholderPath); } }); // Serve armors from server-side storage app.use('/images/armors', (req, res, next) => { const requestedPath = req.path; const serverImagePath = path.join(__dirname, 'assets/images/armors', requestedPath); console.log('[IMAGE SERVER] Armor requested:', requestedPath); if (fs.existsSync(serverImagePath)) { res.sendFile(serverImagePath); } else { const placeholderPath = path.join(__dirname, 'assets/images/ui/placeholder.png'); res.sendFile(placeholderPath); } }); // Serve other items (materials, consumables, cosmetics) from server-side storage app.use('/images/items', (req, res, next) => { const requestedPath = req.path; const serverImagePath = path.join(__dirname, 'assets/images/items', requestedPath); console.log('[IMAGE SERVER] Item requested:', requestedPath); if (fs.existsSync(serverImagePath)) { res.sendFile(serverImagePath); } else { const placeholderPath = path.join(__dirname, 'assets/images/ui/placeholder.png'); res.sendFile(placeholderPath); } }); // Serve UI elements and icons from server-side storage app.use('/images/ui', (req, res, next) => { const requestedPath = req.path; const serverImagePath = path.join(__dirname, 'assets/images/ui', requestedPath); if (fs.existsSync(serverImagePath)) { res.sendFile(serverImagePath); } else { res.status(404).send('UI asset not found'); } }); app.use(express.urlencoded({ extended: true })); // Health check endpoint app.get('/health', (req, res) => { res.status(200).json({ status: 'OK', timestamp: new Date().toISOString(), uptime: process.uptime(), mode: 'game-server', activeInstances: gameInstances.size, connectedClients: connectedClients.size }); }); // API version endpoint app.get('/api/ssc/version', (req, res) => { res.status(200).json({ version: '1.0.0', service: 'galaxystrikeonline-game-server', timestamp: new Date().toISOString(), mode: 'multiplayer' }); }); // Shop API endpoints app.get('/api/shop/items', (req, res) => { try { const shopItems = itemSystem.getRandomShopItems(); res.status(200).json({ success: true, items: shopItems, timestamp: new Date().toISOString() }); } catch (error) { console.error('[GAME SERVER] Error fetching shop items:', error); res.status(500).json({ success: false, error: 'Failed to fetch shop items' }); } }); app.get('/api/shop/items/:category', (req, res) => { try { const { category } = req.params; const items = itemSystem.getItemsByType(category); res.status(200).json({ success: true, items: items, category: category, timestamp: new Date().toISOString() }); } catch (error) { console.error('[GAME SERVER] Error fetching category items:', error); res.status(500).json({ success: false, error: 'Failed to fetch category items' }); } }); app.get('/api/items/:itemId', (req, res) => { try { const { itemId } = req.params; // Find item across all categories const allItems = itemSystem.getAllItems(); let item = null; for (const [category, items] of Object.entries(allItems)) { item = items.find(i => i.id === itemId); if (item) break; } if (!item) { return res.status(404).json({ success: false, error: 'Item not found' }); } res.status(200).json({ success: true, item: item, timestamp: new Date().toISOString() }); } catch (error) { console.error('[GAME SERVER] Error fetching item details:', error); res.status(500).json({ success: false, error: 'Failed to fetch item details' }); } }); // Game data endpoints (similar to LocalServer) app.post('/api/game/player/:id/save', (req, res) => { const playerId = req.params.id; const playerData = req.body; // Store player data in game instance if (connectedClients.has(playerId)) { connectedClients.get(playerId).playerData = playerData; // Broadcast to other players in same instance const instanceId = connectedClients.get(playerId).instanceId; if (gameInstances.has(instanceId)) { io.to(instanceId).emit('playerDataUpdated', { playerId, timestamp: Date.now() }); } } res.status(200).json({ success: true, message: 'Player data saved to game server' }); }); app.get('/api/game/player/:id', (req, res) => { const playerId = req.params.id; if (connectedClients.has(playerId)) { const playerData = connectedClients.get(playerId).playerData; res.status(200).json({ success: true, player: playerData }); } else { res.status(404).json({ success: false, error: 'Player not connected to game server' }); } }); // Game system routes app.use('/api/crafting', require('./routes/crafting')); app.use('/api/quests', require('./routes/quests')); app.use('/api/skills', require('./routes/skills')); app.use('/api/ships', require('./routes/ships')); app.use('/api/idle', require('./routes/idle')); app.use('/api/dungeons', require('./routes/dungeons')); app.use('/api/base', require('./routes/base')); // Socket.IO handlers (based on LocalServer) io.on('connection', (socket) => { console.log('[GAME SERVER] Client connected:', socket.id); connectedClients.set(socket.id, { connectedAt: Date.now(), playerData: null, instanceId: null }); console.log('[GAME SERVER] Waiting for authentication from:', socket.id); // Update player count on API server when new player connects updatePlayerCountOnAPI(); // Add timeout for authentication const authTimeout = setTimeout(() => { const clientData = connectedClients.get(socket.id); if (clientData && !clientData.userId) { console.log('[GAME SERVER] Authentication timeout for:', socket.id); socket.emit('authenticated', { success: false, error: 'Authentication timeout' }); socket.disconnect(); } }, 10000); // 10 seconds timeout // Clear timeout when authenticated socket.on('authenticated', () => { clearTimeout(authTimeout); }); // Authentication (similar to LocalServer) socket.on('authenticate', async (data) => { console.log('[GAME SERVER] Authenticating client:', socket.id, data); try { // Check database connection first if (mongoose.connection.readyState !== 1) { console.error('[GAME SERVER] Database not connected, authentication failed'); socket.emit('authenticated', { success: false, error: 'Database not available' }); return; } console.log('[GAME SERVER] Database is connected, proceeding with authentication'); // Load player data from database const playerData = await loadPlayerData(data.userId || socket.id, data.username || 'Game Player'); if (playerData) { console.log('[GAME SERVER] Player data loaded successfully:', playerData.username); // Store player data in client connection const clientData = connectedClients.get(socket.id); if (clientData) { clientData.playerData = playerData; clientData.userId = playerData.userId; clientData.username = playerData.username; } else { console.error('[GAME SERVER] No client data found for socket:', socket.id); } // Join server playerData.joinServer(`devgame-server-${PORT}`); // Update last login time if (!playerData.stats) playerData.stats = {}; playerData.stats.lastLogin = new Date().toISOString(); // Ensure all required fields exist (for existing players) if (playerData.stats.totalExperience === undefined) playerData.stats.totalExperience = playerData.stats.experience || 0; if (playerData.stats.gems === undefined || playerData.stats.gems === 0) playerData.stats.gems = 50; // Restore gems if wiped if (playerData.stats.skillPoints === undefined) playerData.stats.skillPoints = 0; if (playerData.stats.totalKills === undefined) playerData.stats.totalKills = 0; if (playerData.stats.questsCompleted === undefined) playerData.stats.questsCompleted = 0; await savePlayerData(playerData.userId, playerData); // In production, validate with API server socket.emit('authenticated', { success: true, user: { id: playerData.userId, username: playerData.username, token: 'game-token-' + Date.now() }, playerData: { ...playerData.toObject(), serverTimestamp: Date.now(), // Add server timestamp for time synchronization serverTimezone: 'UTC' // Server operates in UTC timezone } }); console.log(`[GAME SERVER] ${playerData.username} authenticated with Level ${playerData.stats.level}`); } else { console.error('[GAME SERVER] Failed to load player data'); socket.emit('authenticated', { success: false, error: 'Failed to load player data' }); } } catch (error) { console.error('[GAME SERVER] Authentication error:', error); console.error('[GAME INITIALIZER] Error stack:', error.stack); socket.emit('authenticated', { success: false, error: 'Authentication failed: ' + error.message }); } }); // Game data events (similar to LocalServer) socket.on('saveGameData', async (data) => { console.log('[GAME SERVER] Saving game data for:', socket.id); const clientData = connectedClients.get(socket.id); if (clientData && clientData.userId) { try { // Validate the data before saving if (!data || typeof data !== 'object') { console.warn('[GAME SERVER] Invalid game data received, skipping save'); socket.emit('gameDataSaved', { success: false, error: 'Invalid game data' }); return; } // Update player data with new game data const updatedPlayerData = { ...clientData.playerData, ...data }; clientData.playerData = updatedPlayerData; // Save to database const success = await savePlayerData(clientData.userId, updatedPlayerData); socket.emit('gameDataSaved', { success: success, message: success ? 'Game saved to server!' : 'Failed to save to server' }); console.log(`[GAME SERVER] Saved game data for ${clientData.username}`); } catch (error) { console.error('[GAME SERVER] Error saving game data:', error); socket.emit('gameDataSaved', { success: false, error: 'Failed to save data' }); } } else { console.warn('[GAME SERVER] No client data or user ID found for saveGameData'); socket.emit('gameDataSaved', { success: false, error: 'Player not authenticated' }); } }); socket.on('loadGameData', (data) => { console.log('[GAME SERVER] Loading game data for:', socket.id); const playerData = connectedClients.get(socket.id)?.playerData || {}; socket.emit('gameDataLoaded', { success: true, data: playerData }); }); // Shop and item system events socket.on('getShopItems', (data) => { console.log('[GAME SERVER] getShopItems request received from:', socket.id); console.log('[GAME SERVER] Request data:', data); try { console.log('[GAME SERVER] Getting shop items from ItemSystem...'); const shopItems = itemSystem.getRandomShopItems(); console.log('[GAME SERVER] Got shop items:', shopItems.length, 'items'); console.log('[GAME SERVER] Sample item:', shopItems[0]); const response = { success: true, items: shopItems, timestamp: new Date().toISOString() }; console.log('[GAME SERVER] Sending response:', response); socket.emit('shopItemsReceived', response); console.log('[GAME SERVER] Response sent successfully'); } catch (error) { console.error('[GAME SERVER] Error sending shop items:', error); console.error('[GAME SERVER] Error stack:', error.stack); socket.emit('shopItemsReceived', { success: false, error: 'Failed to load shop items' }); } }); socket.on('getItemDetails', (data) => { const { itemId } = data; if (!itemId) { socket.emit('itemDetailsReceived', { success: false, error: 'Item ID required' }); return; } try { const item = itemSystem.getItem(itemId); if (!item) { socket.emit('itemDetailsReceived', { success: false, error: 'Item not found' }); return; } socket.emit('itemDetailsReceived', { success: true, item: item }); } catch (error) { console.error('[GAME SERVER] Error getting item details:', error); socket.emit('itemDetailsReceived', { success: false, error: 'Failed to get item details' }); } }); // Game-specific events socket.on('joinGameInstance', (data) => { const { instanceId } = data; if (!gameInstances.has(instanceId)) { gameInstances.set(instanceId, { id: instanceId, players: new Set(), createdAt: Date.now() }); } const instance = gameInstances.get(instanceId); instance.players.add(socket.id); connectedClients.get(socket.id).instanceId = instanceId; socket.join(instanceId); socket.emit('joinedGameInstance', { instanceId, playerCount: instance.players.size }); // Notify other players socket.to(instanceId).emit('playerJoinedInstance', { playerId: socket.id, playerCount: instance.players.size }); }); socket.on('leaveGameInstance', (data) => { const clientData = connectedClients.get(socket.id); if (clientData && clientData.instanceId) { const instance = gameInstances.get(clientData.instanceId); if (instance) { instance.players.delete(socket.id); socket.leave(clientData.instanceId); // Clean up empty instances if (instance.players.size === 0) { gameInstances.delete(clientData.instanceId); } // Notify other players socket.to(clientData.instanceId).emit('playerLeftInstance', { playerId: socket.id, playerCount: instance.players.size }); } clientData.instanceId = null; } }); socket.on('gameAction', (data) => { const clientData = connectedClients.get(socket.id); if (clientData && clientData.instanceId) { // Broadcast game action to other players in same instance socket.to(clientData.instanceId).emit('gameAction', { playerId: socket.id, action: data, timestamp: Date.now() }); } }); // Idle rewards events socket.on('claimOfflineRewards', async (data) => { console.log('[GAME SERVER] Claiming offline rewards for:', socket.id); const clientData = connectedClients.get(socket.id); if (!clientData || !clientData.userId) { socket.emit('offlineRewardsClaimed', { success: false, error: 'Not authenticated' }); return; } try { const playerData = await loadPlayerData(clientData.userId); const offlineRewards = idleSystem.calculateOfflineRewards(clientData.userId); if (offlineRewards.offlineTime > 0 && offlineRewards.rewards.credits > 0) { // Apply rewards to player playerData.stats.credits = (playerData.stats.credits || 0) + offlineRewards.rewards.credits; playerData.stats.experience = (playerData.stats.experience || 0) + offlineRewards.rewards.experience; // Update idle system data if (!playerData.idleSystem) playerData.idleSystem = {}; playerData.idleSystem.lastActive = new Date().toISOString(); playerData.idleSystem.totalOfflineTime += offlineRewards.offlineTime; playerData.idleSystem.totalIdleCredits += offlineRewards.rewards.credits; await savePlayerData(clientData.userId, playerData); socket.emit('offlineRewardsClaimed', { success: true, rewards: offlineRewards.rewards, offlineTime: offlineRewards.offlineTime }); console.log(`[GAME SERVER] Offline rewards claimed for ${clientData.username}: ${offlineRewards.rewards.credits} credits`); } else { socket.emit('offlineRewardsClaimed', { success: true, rewards: { credits: 0, experience: 0, energy: 0 }, offlineTime: 0 }); } } catch (error) { console.error('[GAME SERVER] Error claiming offline rewards:', error); socket.emit('offlineRewardsClaimed', { success: false, error: 'Failed to claim rewards' }); } }); // Shop purchase events socket.on('purchaseItem', async (data) => { console.log('[GAME SERVER] Processing purchase request:', socket.id, data); const clientData = connectedClients.get(socket.id); if (!clientData || !clientData.userId) { console.log('[GAME SERVER] Purchase failed - not authenticated'); socket.emit('purchaseCompleted', { success: false, error: 'Not authenticated' }); return; } try { const { itemId, quantity = 1 } = data; console.log('[GAME SERVER] Purchase details:', { itemId: itemId, quantity: quantity, userId: clientData.userId, username: clientData.username }); if (!itemId) { console.log('[GAME SERVER] Purchase failed - no item ID'); socket.emit('purchaseCompleted', { success: false, error: 'Item ID required' }); return; } // Load current player data const playerData = await loadPlayerData(clientData.userId); console.log('[GAME SERVER] Player data loaded:', { username: playerData.username, credits: playerData.stats.credits, gems: playerData.stats.gems }); // Find the item in shop (server-side validation) const item = findShopItem(itemId); if (!item) { console.log('[GAME SERVER] Purchase failed - item not found:', itemId); socket.emit('purchaseCompleted', { success: false, error: 'Item not found in shop' }); return; } console.log('[GAME SERVER] Item found:', { id: item.id, name: item.name, type: item.type, price: item.price, currency: item.currency }); // Calculate total cost const totalCost = item.price * quantity; const currency = item.currency; console.log('[GAME SERVER] Cost calculation:', { unitPrice: item.price, quantity: quantity, totalCost: totalCost, currency: currency }); // Check if player can afford if (currency === 'credits' && (playerData.stats.credits || 0) < totalCost) { console.log('[GAME SERVER] Purchase failed - insufficient credits:', { required: totalCost, current: playerData.stats.credits || 0 }); socket.emit('purchaseCompleted', { success: false, error: 'Not enough credits' }); return; } if (currency === 'gems' && (playerData.stats.gems || 0) < totalCost) { console.log('[GAME SERVER] Purchase failed - insufficient gems:', { required: totalCost, current: playerData.stats.gems || 0 }); socket.emit('purchaseCompleted', { success: false, error: 'Not enough gems' }); return; } // Check if already owns this cosmetic if (item.type === 'cosmetic' && playerData.ownedCosmetics && playerData.ownedCosmetics.includes(item.id)) { console.log('[GAME SERVER] Purchase failed - already owns cosmetic:', item.id); socket.emit('purchaseCompleted', { success: false, error: 'You already own this cosmetic' }); return; } // Process payment console.log('[GAME SERVER] Processing payment...'); if (currency === 'credits') { playerData.stats.credits = (playerData.stats.credits || 0) - totalCost; } else if (currency === 'gems') { playerData.stats.gems = (playerData.stats.gems || 0) - totalCost; } console.log('[GAME SERVER] Payment processed, new balance:', { credits: playerData.stats.credits, gems: playerData.stats.gems }); // Give item based on type console.log('[GAME SERVER] Adding item to inventory, type:', item.type); switch (item.type) { case 'ship': if (!playerData.ownedShips) playerData.ownedShips = []; if (!playerData.ownedShips.includes(item.id)) { playerData.ownedShips.push(item.id); console.log('[GAME SERVER] Ship added to owned ships:', item.id); } else { console.log('[GAME SERVER] Ship already owned:', item.id); } break; case 'cosmetic': if (!playerData.ownedCosmetics) playerData.ownedCosmetics = []; if (!playerData.ownedCosmetics.includes(item.id)) { playerData.ownedCosmetics.push(item.id); console.log('[GAME SERVER] Cosmetic added to owned cosmetics:', item.id); } else { console.log('[GAME SERVER] Cosmetic already owned:', item.id); } break; case 'consumable': if (!playerData.inventory) playerData.inventory = { items: [] }; playerData.inventory.items.push({ id: item.id, name: item.name, type: item.type, quantity: quantity, acquired: new Date().toISOString() }); console.log('[GAME SERVER] Consumable added to inventory:', item.name); break; case 'material': if (!playerData.inventory) playerData.inventory = { items: [] }; playerData.inventory.items.push({ id: item.id, name: item.name, type: item.type, quantity: quantity, acquired: new Date().toISOString() }); console.log('[GAME SERVER] Material added to inventory:', item.name); break; default: console.warn(`[GAME SERVER] Unknown item type: ${item.type}`); socket.emit('purchaseCompleted', { success: false, error: 'Unknown item type' }); return; } // Save updated player data console.log('[GAME SERVER] Saving player data...'); await savePlayerData(clientData.userId, playerData); // Update client data with new values clientData.playerData = playerData; // Send success response const response = { success: true, item: item, quantity: quantity, totalCost: totalCost, currency: currency, newBalance: currency === 'credits' ? playerData.stats.credits : playerData.stats.gems }; console.log('[GAME SERVER] Sending success response:', response); socket.emit('purchaseCompleted', response); console.log(`[GAME SERVER] Purchase completed for ${clientData.username}: ${item.name} x${quantity} for ${totalCost} ${currency}`); // Broadcast economy update to client broadcastEconomyUpdate(socket.id); } catch (error) { console.error('[GAME SERVER] Error processing purchase:', error); socket.emit('purchaseCompleted', { success: false, error: 'Purchase failed: ' + error.message }); } }); // Dungeon System Packet Handlers socket.on('get_dungeons', () => { console.log('[GAME SERVER] Sending dungeons data to:', socket.id); const dungeons = dungeonSystem.getDungeonsGroupedByDifficulty(); socket.emit('dungeons_data', dungeons); }); socket.on('get_enemy_templates', () => { console.log('[GAME SERVER] Sending enemy templates to:', socket.id); const enemyTemplates = dungeonSystem.getEnemyTemplates(); socket.emit('enemy_templates_data', enemyTemplates); }); // Economy System Packet Handlers socket.on('get_economy_data', () => { console.log('[GAME SERVER] Sending economy data to:', socket.id); const clientData = connectedClients.get(socket.id); if (clientData && clientData.playerData) { const economyData = { credits: clientData.playerData.stats.credits || 0, gems: clientData.playerData.stats.gems || 0 }; socket.emit('economy_data', economyData); } }); // Function to broadcast economy updates to specific client function broadcastEconomyUpdate(socketId) { const clientData = connectedClients.get(socketId); if (clientData && clientData.playerData) { const economyData = { credits: clientData.playerData.stats.credits || 0, gems: clientData.playerData.stats.gems || 0 }; io.to(socketId).emit('economy_data', economyData); console.log('[GAME SERVER] Broadcasted economy update to:', socketId, economyData); } } socket.on('start_dungeon', (data) => { console.log('[GAME SERVER] Starting dungeon for:', socket.id, data); try { const { dungeonId, userId } = data; const instance = dungeonSystem.createInstance(dungeonId, userId, []); socket.emit('dungeon_started', { instance }); } catch (error) { console.error('[GAME SERVER] Error starting dungeon:', error); socket.emit('dungeon_started', { success: false, error: error.message }); } }); socket.on('process_encounter', (data) => { console.log('[GAME SERVER] Processing encounter for:', socket.id, data); try { const { instanceId, userId } = data; const encounter = dungeonSystem.startEncounter(instanceId, userId); socket.emit('encounter_data', { encounter }); } catch (error) { console.error('[GAME SERVER] Error processing encounter:', error); socket.emit('encounter_data', { success: false, error: error.message }); } }); socket.on('complete_dungeon', (data) => { console.log('[GAME SERVER] Completing dungeon for:', socket.id, data); try { const { instanceId, userId } = data; const result = dungeonSystem.completeDungeon(instanceId); socket.emit('dungeon_completed', { rewards: result }); } catch (error) { console.error('[GAME SERVER] Error completing dungeon:', error); socket.emit('dungeon_completed', { success: false, error: error.message }); } }); socket.on('get_dungeon_status', (data) => { console.log('[GAME SERVER] Getting dungeon status for:', socket.id, data); try { const { userId } = data; const instance = dungeonSystem.getPlayerInstance(userId); if (instance) { socket.emit('dungeon_status', { hasActiveDungeon: true, currentInstance: instance }); } else { socket.emit('dungeon_status', { hasActiveDungeon: false, currentInstance: null }); } } catch (error) { console.error('[GAME SERVER] Error getting dungeon status:', error); socket.emit('dungeon_status', { success: false, error: error.message }); } }); socket.on('disconnect', async () => { console.log('[GAME SERVER] Client disconnected:', socket.id); const clientData = connectedClients.get(socket.id); if (clientData) { // Save player data before disconnect if (clientData.playerData && clientData.userId) { try { clientData.playerData.leaveServer(); await savePlayerData(clientData.userId, clientData.playerData); console.log(`[GAME SERVER] Saved data for ${clientData.username} on disconnect`); } catch (error) { console.error('[GAME SERVER] Error saving data on disconnect:', error); } } // Handle game instance cleanup if (clientData.instanceId) { const instance = gameInstances.get(clientData.instanceId); if (instance) { instance.players.delete(socket.id); // Clean up empty instances if (instance.players.size === 0) { gameInstances.delete(clientData.instanceId); } // Notify other players socket.to(clientData.instanceId).emit('playerLeftInstance', { playerId: socket.id, playerCount: instance.players.size }); } } } connectedClients.delete(socket.id); // Update player count on API server updatePlayerCountOnAPI(); }); }); // Load player data from database async function loadPlayerData(userId, username) { try { // Check if database is connected if (mongoose.connection.readyState !== 1) { console.error('[GAME SERVER] Database not connected, cannot load player data'); return null; } let playerData = await PlayerData.findOne({ userId }); if (!playerData) { // Create new player data with initialized systems console.log(`[GAME SERVER] Creating new player data for ${username} with system initialization`); // Initialize player data in all systems const questData = questSystem.initializePlayerData(userId); const skillData = skillSystem.initializePlayerData(userId); const craftingData = craftingSystem.initializePlayerData(userId); // For dungeons, initialize with available dungeons const availableDungeons = dungeonSystem.getAllDungeons(); const dungeonData = { availableDungeons: availableDungeons.map(d => ({ id: d.id, name: d.name, difficulty: d.difficulty, minLevel: d.minLevel || 1, maxPlayers: d.maxPlayers || 4, description: d.description })), completedDungeons: [], currentInstance: null, dungeonProgress: {} }; playerData = new PlayerData({ userId, username, stats: { level: 1, experience: 0, totalExperience: 0, credits: 1000, gems: 50, // Give some starting gems skillPoints: 0, totalKills: 0, dungeonsCleared: 0, questsCompleted: 0, playTime: 0, lastLogin: new Date() }, // Initialize with system data quests: { active: Array.from(questData.activeQuests.values()), completed: Array.from(questData.completedQuests.keys()) }, skills: skillData, dungeonSystem: dungeonData, crafting: craftingData }); await playerData.save(); console.log(`[GAME SERVER] Created new player data for ${username} with ${questData.activeQuests.size} active quests`); } else { // Auto-migration for existing players - check for missing data console.log(`[GAME SERVER] Loading existing player data for ${username} (Level ${playerData.stats.level})`); let migrated = false; let migrationLog = []; // Always categorize quests to ensure proper format console.log(`[GAME SERVER] Categorizing quests for ${username}`); console.log(`[GAME SERVER] Current playerData.quests before categorization:`, playerData.quests); const questData = questSystem.getPlayerData(userId); console.log(`[GAME SERVER] QuestSystem data:`, { activeQuestsCount: questData.activeQuests.size, completedQuestsCount: questData.completedQuests.size, activeQuests: Array.from(questData.activeQuests.values()) }); // Categorize quests by type for client const activeQuests = Array.from(questData.activeQuests.values()); const mainQuests = activeQuests.filter(quest => quest.type === 'main'); const dailyQuests = activeQuests.filter(quest => quest.type === 'daily'); const weeklyQuests = activeQuests.filter(quest => quest.type === 'weekly'); const tutorialQuests = activeQuests.filter(quest => quest.type === 'tutorial'); playerData.quests = { main: mainQuests, daily: dailyQuests, weekly: weeklyQuests, tutorial: tutorialQuests, active: activeQuests, completed: Array.from(questData.completedQuests.keys()) }; console.log(`[GAME SERVER] Quest categorization complete:`, { main: mainQuests.length, daily: dailyQuests.length, weekly: weeklyQuests.length, tutorial: tutorialQuests.length, total: activeQuests.length }); console.log(`[GAME SERVER] Final playerData.quests after categorization:`, playerData.quests); // IMPORTANT: Save the updated quest data to database try { await savePlayerData(userId, playerData); console.log(`[GAME SERVER] Saved categorized quest data for ${username}`); } catch (error) { console.error(`[GAME SERVER] Failed to save quest data for ${username}:`, error); } // Migrate skills if missing or empty if (!playerData.skills || Object.keys(playerData.skills).length === 0) { console.log(`[GAME SERVER] Migrating skills for ${username}`); const skillData = skillSystem.initializePlayerData(userId); playerData.skills = skillData; migrated = true; migrationLog.push('Added skill data'); } // Migrate dungeons if missing or empty if (!playerData.dungeonSystem || !playerData.dungeonSystem.availableDungeons || (playerData.dungeonSystem.availableDungeons && playerData.dungeonSystem.availableDungeons.length === 0)) { console.log(`[GAME SERVER] Migrating dungeons for ${username}`); const availableDungeons = dungeonSystem.getAllDungeons(); playerData.dungeonSystem = { availableDungeons: availableDungeons.map(d => ({ id: d.id, name: d.name, difficulty: d.difficulty, minLevel: d.minLevel || 1, maxPlayers: d.maxPlayers || 4, description: d.description })), completedDungeons: [], currentInstance: null, dungeonProgress: {} }; migrated = true; migrationLog.push(`Added ${availableDungeons.length} available dungeons`); } // Migrate crafting if missing or empty (this field might not exist at all) if (!playerData.crafting || Object.keys(playerData.crafting).length === 0) { console.log(`[GAME SERVER] Migrating crafting for ${username}`); const craftingData = craftingSystem.initializePlayerData(userId); playerData.crafting = craftingData; migrated = true; migrationLog.push('Added crafting data'); } // Initialize idle system data const idleData = idleSystem.initializePlayerData(userId); if (!playerData.idleSystem) { playerData.idleSystem = { lastActive: new Date().toISOString(), totalOfflineTime: 0, totalIdleCredits: 0 }; migrated = true; migrationLog.push('Added idle system data'); } // Save if migration occurred if (migrated) { await playerData.save(); console.log(`[GAME SERVER] Migration completed for ${username}: ${migrationLog.join(', ')}`); } // Update existing player login info playerData.username = username; playerData.lastLogin = new Date(); await playerData.save(); } return playerData; } catch (error) { console.error('[GAME SERVER] Error loading player data:', error); return null; } } // Save player data to database async function savePlayerData(userId, playerData) { try { // Check if database is connected if (mongoose.connection.readyState !== 1) { console.error('[GAME SERVER] Database not connected, cannot save player data'); return false; } await PlayerData.findOneAndUpdate( { userId }, playerData, { upsert: true, new: true } ); console.log(`[GAME SERVER] Saved player data for ${playerData.username}`); return true; } catch (error) { console.error('[GAME SERVER] Error saving player data:', error); return false; } } // Update player count on API server async function updatePlayerCountOnAPI() { try { const apiServerUrl = 'http://localhost:3001'; const currentPlayers = connectedClients.size; console.log(`[GAME SERVER] Updating player count: ${currentPlayers} players`); const response = await fetch(`${apiServerUrl}/api/servers/update-status/devgame-server-3002`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ currentPlayers: currentPlayers, status: currentPlayers > 0 ? 'active' : 'waiting' }) }); if (response.ok) { const result = await response.json(); console.log(`[GAME SERVER] Player count updated:`, result); } else { console.error(`[GAME SERVER] Failed to update player count: ${response.status}`); } } catch (error) { console.error('[GAME SERVER] Error updating player count:', error); } } // Start server const PORT = process.env.PORT || 3002; async function startServer() { try { console.log('[GAME SERVER] Attempting to connect to database...'); // Connect to database await connectDB(); console.log('[GAME SERVER] Database connection established'); server.listen(PORT, async () => { console.log(`[GAME SERVER] Game server running on port ${PORT}`); console.log(`[GAME SERVER] Socket.IO ready for multiplayer connections`); // Register with API server await registerWithAPIServer(); // Start periodic player count updates setInterval(updatePlayerCountOnAPI, 30000); // Update every 30 seconds // Start online idle rewards generation (every 10 seconds) setInterval(async () => { for (const [socketId, clientData] of connectedClients.entries()) { if (clientData.userId && clientData.playerData) { try { // Update playTime for active players const sessionTime = clientData.playerData.updatePlayTime(); console.log(`[GAME SERVER] Updated playTime for ${clientData.username}: +${sessionTime}ms, Total: ${clientData.playerData.stats.playTime}ms`); // Send playTime update to client io.to(socketId).emit('playTimeUpdated', { playTime: clientData.playerData.stats.playTime, sessionTime: sessionTime }); const onlineRewards = idleSystem.generateOnlineIdleRewards(clientData.userId, 10000); // 10 seconds if (onlineRewards.credits > 0) { // Update player data with online rewards clientData.playerData.stats.credits = (clientData.playerData.stats.credits || 0) + onlineRewards.credits; clientData.playerData.stats.experience = (clientData.playerData.stats.experience || 0) + onlineRewards.experience; // Send update to client io.to(socketId).emit('onlineIdleRewards', { credits: onlineRewards.credits, experience: onlineRewards.experience, newBalance: clientData.playerData.stats.credits, playTime: clientData.playerData.stats.playTime }); } } catch (error) { console.error(`[GAME SERVER] Error generating online idle rewards for ${socketId}:`, error); } } } }, 10000); // Every 10 seconds }); } catch (error) { console.error('[GAME SERVER] Failed to start server:', error); process.exit(1); } } // Register this GameServer with the API server async function registerWithAPIServer() { try { // Force use of local API server for development const apiServerUrl = 'http://localhost:3001'; const gameServerUrl = 'https://dev.gameserver.galaxystrike.online'; const serverData = { serverId: `devgame-server-${PORT}`, name: 'Dev Game Server', type: 'public', // Must be 'public' or 'private' according to model region: 'Europe', maxPlayers: 100, // Hardcoded to allow 100 players currentPlayers: 0, gameServerUrl: gameServerUrl, owner: { userId: 'developer', username: 'Developer' } }; console.log(`[GAME SERVER] Registering with API server at ${apiServerUrl}`); console.log(`[GAME SERVER] Server data:`, serverData); const response = await fetch(`${apiServerUrl}/api/servers/register`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(serverData) }); if (response.ok) { const result = await response.json(); console.log(`[GAME SERVER] Successfully registered with API server:`, result); } else { const errorText = await response.text(); console.error(`[GAME SERVER] Failed to register with API server: ${response.status}`); console.error(`[GAME SERVER] Error response:`, errorText); } } catch (error) { console.error('[GAME SERVER] Error registering with API server:', error); } } startServer(); module.exports = { app, server, io, gameInstances, connectedClients };