1512 lines
52 KiB
JavaScript
1512 lines
52 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');
|
|
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
|
|
}
|
|
});
|
|
|
|
// Pass io instance to systems that need it
|
|
dungeonSystem.setIO(io);
|
|
|
|
// 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,
|
|
shopItems: 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.getRandomItemsByCategory(category);
|
|
|
|
res.status(200).json({
|
|
success: true,
|
|
category: category,
|
|
items: items,
|
|
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] === NEW CLIENT CONNECTION ===');
|
|
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);
|
|
});
|
|
|
|
// Log all incoming events for debugging
|
|
socket.onAny((eventName, ...args) => {
|
|
if (eventName !== 'ping' && eventName !== 'pong') {
|
|
console.log(`[GAME SERVER] Event received: ${eventName} from ${socket.id}`, args);
|
|
}
|
|
});
|
|
|
|
// 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;
|
|
|
|
// Ensure idle system production rates are initialized
|
|
idleSystem.initializePlayerData(playerData.userId);
|
|
console.log('[GAME SERVER] Idle system initialized for player:', playerData.username);
|
|
|
|
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
|
|
});
|
|
});
|
|
|
|
// Test idle system manually
|
|
socket.on('testIdleRewards', (data) => {
|
|
console.log('[GAME SERVER] Testing idle rewards for:', socket.id);
|
|
|
|
const clientData = connectedClients.get(socket.id);
|
|
if (clientData && clientData.userId) {
|
|
const onlineRewards = idleSystem.generateOnlineIdleRewards(clientData.userId, 10000);
|
|
console.log('[GAME SERVER] Test idle rewards:', onlineRewards);
|
|
|
|
socket.emit('testIdleRewards', {
|
|
rewards: onlineRewards,
|
|
productionRates: idleSystem.playerProductionRates.get(clientData.userId)
|
|
});
|
|
} else {
|
|
socket.emit('testIdleRewards', { error: 'Not authenticated' });
|
|
}
|
|
});
|
|
|
|
// Shop and item system events
|
|
socket.on('ping', (data) => {
|
|
console.log('[GAME SERVER] Ping received from:', socket.id, data);
|
|
socket.emit('pong', {
|
|
timestamp: Date.now(),
|
|
received: data.timestamp,
|
|
serverTime: new Date().toISOString()
|
|
});
|
|
});
|
|
|
|
socket.on('getShopItems', (data) => {
|
|
console.log('[GAME SERVER] === getShopItems REQUEST START ===');
|
|
console.log('[GAME SERVER] getShopItems request received from:', socket.id);
|
|
console.log('[GAME SERVER] Request data:', data);
|
|
console.log('[GAME SERVER] ItemSystem available:', !!itemSystem);
|
|
console.log('[GAME SERVER] ItemSystem.getRandomShopItems available:', typeof itemSystem.getRandomShopItems);
|
|
|
|
try {
|
|
console.log('[GAME SERVER] Getting shop items from ItemSystem...');
|
|
const shopItems = itemSystem.getRandomShopItems();
|
|
console.log('[GAME SERVER] Got shop items by category:', Object.keys(shopItems));
|
|
|
|
// Log item counts per category
|
|
Object.entries(shopItems).forEach(([category, items]) => {
|
|
console.log(`[GAME SERVER] ${category}: ${items.length} items`);
|
|
});
|
|
|
|
const response = {
|
|
success: true,
|
|
shopItems: shopItems, // Changed from 'items' to 'shopItems'
|
|
timestamp: new Date().toISOString()
|
|
};
|
|
|
|
console.log('[GAME SERVER] Sending response:', response);
|
|
console.log('[GAME SERVER] About to emit shopItemsReceived to:', socket.id);
|
|
|
|
socket.emit('shopItemsReceived', response);
|
|
console.log('[GAME SERVER] shopItemsReceived emitted successfully');
|
|
console.log('[GAME SERVER] === getShopItems REQUEST COMPLETE ===');
|
|
} 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'
|
|
});
|
|
console.log('[GAME SERVER] Error response sent');
|
|
}
|
|
});
|
|
|
|
socket.on('getShopCategory', (data) => {
|
|
console.log('[GAME SERVER] getShopCategory request received from:', socket.id);
|
|
console.log('[GAME SERVER] Request data:', data);
|
|
|
|
try {
|
|
const { category } = data;
|
|
if (!category) {
|
|
socket.emit('shopCategoryReceived', {
|
|
success: false,
|
|
error: 'Category required'
|
|
});
|
|
return;
|
|
}
|
|
|
|
console.log('[GAME SERVER] Getting items for category:', category);
|
|
const categoryItems = itemSystem.getRandomItemsByCategory(category);
|
|
console.log('[GAME SERVER] Got', categoryItems.length, 'items for category', category);
|
|
|
|
const response = {
|
|
success: true,
|
|
category: category,
|
|
items: categoryItems,
|
|
timestamp: new Date().toISOString()
|
|
};
|
|
|
|
console.log('[GAME SERVER] Sending category response:', response);
|
|
socket.emit('shopCategoryReceived', response);
|
|
console.log('[GAME SERVER] Category response sent successfully');
|
|
} catch (error) {
|
|
console.error('[GAME SERVER] Error sending category items:', error);
|
|
socket.emit('shopCategoryReceived', {
|
|
success: false,
|
|
error: 'Failed to load category 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, clientData.username || 'Player');
|
|
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
|
|
console.log('[GAME SERVER] Loading player data for purchase:', {
|
|
userId: clientData.userId,
|
|
username: clientData.username
|
|
});
|
|
|
|
const playerData = await loadPlayerData(clientData.userId, clientData.username || 'Player');
|
|
if (!playerData) {
|
|
console.log('[GAME SERVER] Purchase failed - could not load player data');
|
|
socket.emit('purchaseCompleted', { success: false, error: 'Failed to load player data' });
|
|
return;
|
|
}
|
|
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: {
|
|
id: item.id,
|
|
name: item.name,
|
|
type: item.type,
|
|
rarity: item.rarity,
|
|
description: item.description,
|
|
price: item.price,
|
|
currency: item.currency,
|
|
quantity: quantity,
|
|
stats: item.stats || {},
|
|
texturePath: item.texturePath || item.texture
|
|
},
|
|
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;
|
|
|
|
// Check if dungeon is one-time only and already completed
|
|
const dungeon = dungeonSystem.getDungeon(dungeonId);
|
|
if (dungeon && dungeon.oneTimeOnly) {
|
|
const completedDungeons = dungeonSystem.getPlayerCompletedDungeons(userId);
|
|
if (completedDungeons.includes(dungeonId)) {
|
|
socket.emit('dungeon_started', {
|
|
success: false,
|
|
error: 'This dungeon can only be completed once per character.'
|
|
});
|
|
return;
|
|
}
|
|
}
|
|
|
|
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 result = dungeonSystem.startEncounter(instanceId, userId);
|
|
|
|
// Auto-complete combat for enemies with 0 attack
|
|
if (result.encounter.enemies && result.encounter.enemies.length > 0) {
|
|
const allZeroAttack = result.encounter.enemies.every(enemy => enemy.attack === 0);
|
|
if (allZeroAttack) {
|
|
console.log('[GAME SERVER] Auto-combat: All enemies have 0 attack, completing encounter');
|
|
const completionResult = dungeonSystem.completeEncounter(instanceId, userId, { victory: true });
|
|
socket.emit('encounter_completed', {
|
|
success: true,
|
|
rewards: completionResult.rewards,
|
|
nextEncounter: completionResult.nextEncounter,
|
|
encounterIndex: completionResult.encounterIndex
|
|
});
|
|
return;
|
|
}
|
|
}
|
|
|
|
socket.emit('encounter_data', {
|
|
encounter: result.encounter,
|
|
encounterIndex: result.encounterIndex,
|
|
instance: result.instance
|
|
});
|
|
} 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_quests', (data) => {
|
|
console.log('[GAME SERVER] Getting quests for:', socket.id, data);
|
|
try {
|
|
// Get quest data from quest system
|
|
const questData = questSystem.getPlayerQuests(socket.userId || 'anonymous');
|
|
socket.emit('quests_data', questData);
|
|
} catch (error) {
|
|
console.error('[GAME SERVER] Error getting quests:', error);
|
|
socket.emit('quests_data', { success: false, error: error.message });
|
|
}
|
|
});
|
|
|
|
// Listen for quest completion events from other systems
|
|
io.on('quest_completed', (data) => {
|
|
console.log('[GAME SERVER] Quest completion event received:', data);
|
|
try {
|
|
const result = questSystem.completeQuest(data.userId, data.questId, data.rewards);
|
|
if (result.success) {
|
|
console.log('[GAME SERVER] Quest completed successfully on server:', data.questId);
|
|
} else {
|
|
console.error('[GAME SERVER] Failed to complete quest:', result.error);
|
|
}
|
|
} catch (error) {
|
|
console.error('[GAME SERVER] Error processing quest completion:', error);
|
|
}
|
|
});
|
|
|
|
socket.on('next_room', (data) => {
|
|
console.log('[GAME SERVER] Moving to next room for:', socket.id, data);
|
|
try {
|
|
const { instanceId, userId } = data;
|
|
const result = dungeonSystem.moveToNextRoom(instanceId, userId);
|
|
socket.emit('next_room_data', result);
|
|
} catch (error) {
|
|
console.error('[GAME SERVER] Error moving to next room:', error);
|
|
socket.emit('next_room_data', { 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 () => {
|
|
console.log('[GAME SERVER] Idle reward timer triggered - checking', connectedClients.size, 'connected clients');
|
|
|
|
for (const [socketId, clientData] of connectedClients.entries()) {
|
|
if (clientData.userId && clientData.playerData) {
|
|
try {
|
|
console.log('[GAME SERVER] Processing idle rewards for client:', socketId, 'user:', clientData.username);
|
|
// 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
|
|
|
|
console.log('[GAME SERVER] Generated online rewards for', clientData.username, ':', onlineRewards);
|
|
|
|
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;
|
|
|
|
console.log('[GAME SERVER] Applied idle rewards - Credits:', onlineRewards.credits, 'New balance:', clientData.playerData.stats.credits);
|
|
|
|
// 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
|
|
});
|
|
|
|
console.log('[GAME SERVER] Sent onlineIdleRewards to client:', socketId);
|
|
} else {
|
|
console.log('[GAME SERVER] No idle rewards generated for', clientData.username);
|
|
}
|
|
} 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 };
|