This repository has been archived on 2026-05-04. You can view files and clone it, but cannot push or open issues or pull requests.
Galaxy-Strike-Online/GameServer/server.js

1222 lines
41 KiB
JavaScript

/**
* 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');
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();
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' }));
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.getShopItems();
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.getItemsByCategory(category);
res.status(200).json({
success: true,
category: category,
items: items,
timestamp: new Date().toISOString()
});
} catch (error) {
console.error('[GAME SERVER] Error fetching shop category:', error);
res.status(500).json({
success: false,
error: 'Failed to fetch shop category'
});
}
});
app.get('/api/items/:itemId', (req, res) => {
try {
const { itemId } = req.params;
const item = itemSystem.getItem(itemId);
if (!item) {
return res.status(404).json({
success: false,
error: 'Item not found'
});
}
res.status(200).json({
success: true,
item: item
});
} catch (error) {
console.error('[GAME SERVER] Error fetching item:', error);
res.status(500).json({
success: false,
error: 'Failed to fetch item'
});
}
});
// 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] Sending shop items to:', socket.id);
try {
const shopItems = itemSystem.getShopItems();
socket.emit('shopItemsReceived', {
success: true,
items: shopItems,
timestamp: new Date().toISOString()
});
} catch (error) {
console.error('[GAME SERVER] Error sending shop items:', error);
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_room_types', () => {
console.log('[GAME SERVER] Sending room types to:', socket.id);
const roomTypes = dungeonSystem.getRoomTypes();
socket.emit('room_types_data', roomTypes);
});
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 };