diff --git a/API/.env.example b/API/.env.example deleted file mode 100644 index d229c52..0000000 --- a/API/.env.example +++ /dev/null @@ -1,18 +0,0 @@ -# Server Configuration -PORT=3001 -NODE_ENV=development - -# Database Configuration -MONGODB_URI=mongodb://localhost:27017/galaxystrikeonline - -# JWT Configuration -JWT_SECRET=your_jwt_secret_key_here - -# Redis Configuration (optional) -REDIS_URL=redis://localhost:6379 - -# Client URL -CLIENT_URL=http://localhost:3000 - -# Logging -LOG_LEVEL=info diff --git a/API/models/Player.js b/API/models/Player.js index a176b66..a8eaab0 100644 --- a/API/models/Player.js +++ b/API/models/Player.js @@ -1,5 +1,4 @@ const mongoose = require('mongoose'); -const { calculateXPToNextLevel, getLevelFromXP } = require('../config/xp-progression'); const playerSchema = new mongoose.Schema({ userId: { @@ -24,12 +23,10 @@ const playerSchema = new mongoose.Schema({ select: false // Don't include password in queries by default }, - // Player stats + // Player stats (simplified for API server) stats: { level: { type: Number, default: 1 }, experience: { type: Number, default: 0 }, - totalXP: { type: Number, default: 0 }, // Total accumulated XP across all levels - experienceToNext: { type: Number, default: function() { return calculateXPToNextLevel(1, 0); } }, credits: { type: Number, default: 1000 }, dungeonsCleared: { type: Number, default: 0 }, playTime: { type: Number, default: 0 }, @@ -92,21 +89,10 @@ const playerSchema = new mongoose.Schema({ playerSchema.index({ 'stats.level': 1 }); playerSchema.index({ currentServer: 1 }); -// Methods +// Methods (simplified for API server) playerSchema.methods.addExperience = function(amount) { - // Add to total accumulated XP - this.stats.totalXP += amount; - - // Calculate new level based on total XP - const levelInfo = getLevelFromXP(this.stats.totalXP); - const oldLevel = this.stats.level; - - this.stats.level = levelInfo.level; - this.stats.experience = levelInfo.xpIntoLevel; - this.stats.experienceToNext = levelInfo.xpToNext; - - // Return whether the player leveled up - return this.stats.level > oldLevel; + this.stats.experience += amount; + return this.stats.experience; }; playerSchema.methods.addCredits = function(amount) { diff --git a/API/package.json b/API/package.json index 1a637d8..8d5c3c5 100644 --- a/API/package.json +++ b/API/package.json @@ -17,14 +17,12 @@ "keywords": ["game", "server", "mmorpg", "api", "websocket"], "dependencies": { "express": "^4.18.2", - "socket.io": "^4.7.4", "cors": "^2.8.5", "helmet": "^7.1.0", "dotenv": "^16.3.1", "bcryptjs": "^2.4.3", "jsonwebtoken": "^9.0.2", "mongoose": "^8.0.3", - "redis": "^4.6.11", "winston": "^3.11.0", "joi": "^17.11.0", "rate-limiter-flexible": "^2.4.2", diff --git a/API/routes/game.js b/API/routes/game.js deleted file mode 100644 index 702c9de..0000000 --- a/API/routes/game.js +++ /dev/null @@ -1,336 +0,0 @@ -const express = require('express'); -const jwt = require('jsonwebtoken'); -const Player = require('../models/Player'); -const Ship = require('../models/Ship'); -const Inventory = require('../models/Inventory'); -const { getGameSystem } = require('../systems/GameSystem'); -const logger = require('../utils/logger'); - -const router = express.Router(); - -// Middleware to authenticate JWT token -const authenticateToken = (req, res, next) => { - const token = req.header('Authorization')?.replace('Bearer ', ''); - - if (!token) { - return res.status(401).json({ error: 'Access token required' }); - } - - try { - const decoded = jwt.verify(token, process.env.JWT_SECRET || 'fallback_secret'); - req.userId = decoded.userId; - next(); - } catch (error) { - res.status(401).json({ error: 'Invalid token' }); - } -}; - -// Get player data -router.get('/player', authenticateToken, async (req, res) => { - try { - const gameSystem = getGameSystem(); - const player = await gameSystem.loadPlayer(req.userId); - - if (!player) { - return res.status(404).json({ error: 'Player not found' }); - } - - res.json({ - userId: player.userId, - username: player.username, - stats: player.stats, - attributes: player.attributes, - info: player.info, - settings: player.settings, - dailyRewards: player.dailyRewards - }); - - } catch (error) { - logger.error('Error getting player data:', error); - res.status(500).json({ error: 'Internal server error' }); - } -}); - -// Get player's ships -router.get('/ships', authenticateToken, async (req, res) => { - try { - const ships = await Ship.find({ userId: req.userId }); - - res.json({ - ships: ships.map(ship => ({ - id: ship.id, - name: ship.name, - class: ship.class, - level: ship.level, - stats: ship.stats, - isEquipped: ship.isEquipped, - isCurrent: ship.isCurrent, - rarity: ship.rarity, - texture: ship.texture, - acquiredAt: ship.acquiredAt - })) - }); - - } catch (error) { - logger.error('Error getting player ships:', error); - res.status(500).json({ error: 'Internal server error' }); - } -}); - -// Equip a ship -router.post('/ships/equip', authenticateToken, async (req, res) => { - try { - const { shipId } = req.body; - - if (!shipId) { - return res.status(400).json({ error: 'Ship ID required' }); - } - - const gameSystem = getGameSystem(); - const ship = await gameSystem.equipShip(req.userId, shipId); - - res.json({ - message: 'Ship equipped successfully', - ship: { - id: ship.id, - name: ship.name, - class: ship.class, - level: ship.level, - stats: ship.stats, - isEquipped: ship.isEquipped, - isCurrent: ship.isCurrent - } - }); - - } catch (error) { - logger.error('Error equipping ship:', error); - res.status(500).json({ error: error.message }); - } -}); - -// Get player's inventory -router.get('/inventory', authenticateToken, async (req, res) => { - try { - let inventory = await Inventory.findOne({ userId: req.userId }); - - if (!inventory) { - // Create new inventory if it doesn't exist - inventory = new Inventory({ userId: req.userId }); - await inventory.save(); - } - - res.json({ - items: inventory.items, - equippedItems: inventory.equippedItems, - summary: inventory.getInventorySummary() - }); - - } catch (error) { - logger.error('Error getting inventory:', error); - res.status(500).json({ error: 'Internal server error' }); - } -}); - -// Add item to inventory -router.post('/inventory/add', authenticateToken, async (req, res) => { - try { - const { itemData } = req.body; - - if (!itemData) { - return res.status(400).json({ error: 'Item data required' }); - } - - let inventory = await Inventory.findOne({ userId: req.userId }); - - if (!inventory) { - inventory = new Inventory({ userId: req.userId }); - } - - await inventory.addItem(itemData); - - res.json({ - message: 'Item added to inventory', - item: itemData, - summary: inventory.getInventorySummary() - }); - - } catch (error) { - logger.error('Error adding item to inventory:', error); - res.status(500).json({ error: error.message }); - } -}); - -// Equip item -router.post('/inventory/equip', authenticateToken, async (req, res) => { - try { - const { itemId, slot } = req.body; - - if (!itemId || !slot) { - return res.status(400).json({ error: 'Item ID and slot required' }); - } - - const inventory = await Inventory.findOne({ userId: req.userId }); - - if (!inventory) { - return res.status(404).json({ error: 'Inventory not found' }); - } - - await inventory.equipItem(itemId, slot); - - res.json({ - message: 'Item equipped successfully', - equippedItems: inventory.equippedItems - }); - - } catch (error) { - logger.error('Error equipping item:', error); - res.status(500).json({ error: error.message }); - } -}); - -// Use consumable -router.post('/inventory/use', authenticateToken, async (req, res) => { - try { - const { itemId } = req.body; - - if (!itemId) { - return res.status(400).json({ error: 'Item ID required' }); - } - - const inventory = await Inventory.findOne({ userId: req.userId }); - - if (!inventory) { - return res.status(404).json({ error: 'Inventory not found' }); - } - - const effects = await inventory.useConsumable(itemId); - - res.json({ - message: 'Item used successfully', - effects, - summary: inventory.getInventorySummary() - }); - - } catch (error) { - logger.error('Error using consumable:', error); - res.status(500).json({ error: error.message }); - } -}); - -// Process game action -router.post('/action', authenticateToken, async (req, res) => { - try { - const { actionData } = req.body; - - if (!actionData) { - return res.status(400).json({ error: 'Action data required' }); - } - - const gameSystem = getGameSystem(); - const result = await gameSystem.processGameAction(req.userId, actionData); - - res.json(result); - - } catch (error) { - logger.error('Error processing game action:', error); - res.status(500).json({ error: error.message }); - } -}); - -// Claim daily reward -router.post('/daily-reward', authenticateToken, async (req, res) => { - try { - const player = await Player.findOne({ userId: req.userId }); - - if (!player) { - return res.status(404).json({ error: 'Player not found' }); - } - - const rewardResult = player.claimDailyReward(); - await player.save(); - - res.json(rewardResult); - - } catch (error) { - logger.error('Error claiming daily reward:', error); - res.status(500).json({ error: 'Internal server error' }); - } -}); - -// Save player data -router.post('/save', authenticateToken, async (req, res) => { - try { - const gameSystem = getGameSystem(); - await gameSystem.savePlayer(req.userId); - - res.json({ message: 'Game saved successfully' }); - - } catch (error) { - logger.error('Error saving game:', error); - res.status(500).json({ error: 'Internal server error' }); - } -}); - -// Get shop items -router.get('/shop/:category?', authenticateToken, async (req, res) => { - try { - const gameSystem = getGameSystem(); - const category = req.params.category; - const items = gameSystem.economy.getShopItems(category); - - res.json({ items }); - - } catch (error) { - logger.error('Error getting shop items:', error); - res.status(500).json({ error: 'Internal server error' }); - } -}); - -// Purchase item -router.post('/shop/purchase', authenticateToken, async (req, res) => { - try { - const { itemId, quantity = 1 } = req.body; - - if (!itemId) { - return res.status(400).json({ error: 'Item ID required' }); - } - - const gameSystem = getGameSystem(); - const purchaseInfo = gameSystem.economy.purchaseItem(req.userId, itemId, quantity); - - const player = await Player.findOne({ userId: req.userId }); - - if (!player.canAfford(purchaseInfo.totalCost)) { - return res.status(400).json({ error: 'Insufficient credits' }); - } - - // Deduct credits - player.spendCredits(purchaseInfo.totalCost); - await player.save(); - - // Add item to inventory - const inventory = await Inventory.findOne({ userId: req.userId }); - if (!inventory) { - return res.status(404).json({ error: 'Inventory not found' }); - } - - await inventory.addItem({ - ...purchaseInfo.item, - quantity - }); - - res.json({ - message: 'Purchase successful', - item: purchaseInfo.item, - quantity, - totalCost: purchaseInfo.totalCost, - remainingCredits: player.stats.credits - }); - - } catch (error) { - logger.error('Error purchasing item:', error); - res.status(500).json({ error: error.message }); - } -}); - -module.exports = router; diff --git a/API/routes/mods.js b/API/routes/mods.js deleted file mode 100644 index d52450b..0000000 --- a/API/routes/mods.js +++ /dev/null @@ -1,232 +0,0 @@ -const express = require('express'); -const jwt = require('jsonwebtoken'); -const logger = require('../utils/logger'); -const ModService = require('../services/ModService'); - -const router = express.Router(); - -// Middleware to authenticate JWT token -const authenticateToken = (req, res, next) => { - const token = req.header('Authorization')?.replace('Bearer ', ''); - - if (!token) { - return res.status(401).json({ error: 'Access token required' }); - } - - try { - const decoded = jwt.verify(token, process.env.JWT_SECRET || 'fallback_secret'); - req.userId = decoded.userId; - next(); - } catch (error) { - res.status(401).json({ error: 'Invalid token' }); - } -}; - -// Get all mods -router.get('/', authenticateToken, async (req, res) => { - try { - const { enabledOnly } = req.query; - const mods = await ModService.getMods(enabledOnly === 'true'); - - res.json({ - success: true, - mods - }); - } catch (error) { - logger.error('[MODS API] Error getting mods:', error); - res.status(500).json({ error: 'Internal server error' }); - } -}); - -// Get specific mod -router.get('/:id', authenticateToken, async (req, res) => { - try { - const { id } = req.params; - const mod = await ModService.getMod(parseInt(id)); - - if (!mod) { - return res.status(404).json({ error: 'Mod not found' }); - } - - res.json({ - success: true, - mod - }); - } catch (error) { - logger.error('[MODS API] Error getting mod:', error); - res.status(500).json({ error: 'Internal server error' }); - } -}); - -// Enable mod -router.post('/:id/enable', authenticateToken, async (req, res) => { - try { - const { id } = req.params; - const success = await ModService.enableMod(parseInt(id)); - - if (!success) { - return res.status(404).json({ error: 'Mod not found' }); - } - - res.json({ - success: true, - message: 'Mod enabled successfully' - }); - } catch (error) { - logger.error('[MODS API] Error enabling mod:', error); - res.status(500).json({ error: 'Internal server error' }); - } -}); - -// Disable mod -router.post('/:id/disable', authenticateToken, async (req, res) => { - try { - const { id } = req.params; - const success = await ModService.disableMod(parseInt(id)); - - if (!success) { - return res.status(404).json({ error: 'Mod not found' }); - } - - res.json({ - success: true, - message: 'Mod disabled successfully' - }); - } catch (error) { - logger.error('[MODS API] Error disabling mod:', error); - res.status(500).json({ error: 'Internal server error' }); - } -}); - -// Get mod assets by type -router.get('/assets/:type', authenticateToken, async (req, res) => { - try { - const { type } = req.params; - const assets = await ModService.getModAssets(type); - - res.json({ - success: true, - assets - }); - } catch (error) { - logger.error('[MODS API] Error getting mod assets:', error); - res.status(500).json({ error: 'Internal server error' }); - } -}); - -// Get specific mod asset -router.get('/assets/:type/:assetId', authenticateToken, async (req, res) => { - try { - const { type, assetId } = req.params; - const asset = await ModService.getModAsset(type, assetId); - - if (!asset) { - return res.status(404).json({ error: 'Asset not found' }); - } - - res.json({ - success: true, - asset - }); - } catch (error) { - logger.error('[MODS API] Error getting mod asset:', error); - res.status(500).json({ error: 'Internal server error' }); - } -}); - -// Get mod load order -router.get('/load-order', authenticateToken, async (req, res) => { - try { - const loadOrder = await ModService.getLoadOrder(); - - res.json({ - success: true, - loadOrder - }); - } catch (error) { - logger.error('[MODS API] Error getting load order:', error); - res.status(500).json({ error: 'Internal server error' }); - } -}); - -// Set mod load order -router.post('/load-order', authenticateToken, async (req, res) => { - try { - const { modIds } = req.body; - - if (!Array.isArray(modIds)) { - return res.status(400).json({ error: 'modIds must be an array' }); - } - - const success = await ModService.setLoadOrder(modIds); - - if (!success) { - return res.status(500).json({ error: 'Failed to set load order' }); - } - - res.json({ - success: true, - message: 'Load order updated successfully' - }); - } catch (error) { - logger.error('[MODS API] Error setting load order:', error); - res.status(500).json({ error: 'Internal server error' }); - } -}); - -// Get server mod preferences -router.get('/preferences/server', authenticateToken, async (req, res) => { - try { - const preferences = await ModService.getServerPreferences(); - - res.json({ - success: true, - preferences - }); - } catch (error) { - logger.error('[MODS API] Error getting server preferences:', error); - res.status(500).json({ error: 'Internal server error' }); - } -}); - -// Set server mod preference -router.post('/preferences/server', authenticateToken, async (req, res) => { - try { - const { key, value } = req.body; - - if (!key || value === undefined) { - return res.status(400).json({ error: 'key and value are required' }); - } - - const success = await ModService.setServerPreference(key, value); - - if (!success) { - return res.status(500).json({ error: 'Failed to set preference' }); - } - - res.json({ - success: true, - message: 'Preference set successfully' - }); - } catch (error) { - logger.error('[MODS API] Error setting server preference:', error); - res.status(500).json({ error: 'Internal server error' }); - } -}); - -// Reload mods from filesystem -router.post('/reload', authenticateToken, async (req, res) => { - try { - await ModService.reloadMods(); - - res.json({ - success: true, - message: 'Mods reloaded successfully' - }); - } catch (error) { - logger.error('[MODS API] Error reloading mods:', error); - res.status(500).json({ error: 'Internal server error' }); - } -}); - -module.exports = router; diff --git a/API/server.js b/API/server.js index 121e8b3..52e3dda 100644 --- a/API/server.js +++ b/API/server.js @@ -10,7 +10,6 @@ const logger = require('./utils/logger'); const connectDB = require('./config/database'); const authRoutes = require('./routes/auth'); const serverRoutes = require('./routes/servers'); -const modsRoutes = require('./routes/mods'); const { errorHandler, notFound } = require('./middleware/errorHandler'); // Override console.error to properly log error objects @@ -67,10 +66,9 @@ app.use('/api/', async (req, res, next) => { } }); -// Routes - API Server Only (Auth + Server Browser + Mods) +// Routes - API Server Only (Auth + Server Browser) app.use('/api/auth', authRoutes); app.use('/api/servers', serverRoutes); -app.use('/api/mods', modsRoutes); // Health check app.get('/health', (req, res) => { @@ -124,7 +122,7 @@ async function startServer() { logger.info('Database connected successfully'); // Start API server - const PORT = process.env.PORT || 3001; + const PORT = process.env.PORT || 3000; server.listen(PORT, () => { logger.info(`API Server running on port ${PORT}`); logger.info('API Server handles: Authentication, Server Browser, User Data'); diff --git a/API/services/ModService.js b/API/services/ModService.js deleted file mode 100644 index 390b53c..0000000 --- a/API/services/ModService.js +++ /dev/null @@ -1,182 +0,0 @@ -const logger = require('../utils/logger'); - -/** - * API Mod Service - Handles mod management through API communication with GameServer - * This service acts as a client to the GameServer's mod functionality - */ -class ApiModService { - constructor() { - // Use service discovery - try multiple common GameServer locations - this.gameServerUrls = [ - process.env.GAME_SERVER_URL, - 'http://localhost:3002', - 'http://127.0.0.1:3002', - 'http://game-server:3002' // Docker service name - ].filter(url => url); // Remove null/undefined values - - this.currentUrlIndex = 0; - } - - async getGameServerUrl() { - // Try each URL until we find a working one - for (let i = 0; i < this.gameServerUrls.length; i++) { - const url = this.gameServerUrls[i]; - try { - const response = await fetch(`${url}/health`, { - timeout: 5000 // 5 second timeout - }); - if (response.ok) { - this.currentUrlIndex = i; - return url; - } - } catch (error) { - logger.debug(`[API MOD SERVICE] GameServer at ${url} not available:`, error.message); - } - } - - throw new Error('No GameServer instances available'); - } - - async makeRequest(endpoint, options = {}) { - const url = await this.getGameServerUrl(); - const fullUrl = `${url}${endpoint}`; - - const defaultOptions = { - timeout: 10000, // 10 second timeout - headers: { - 'Content-Type': 'application/json', - 'User-Agent': 'API-ModService/1.0' - } - }; - - const finalOptions = { ...defaultOptions, ...options }; - - try { - const response = await fetch(fullUrl, finalOptions); - if (!response.ok) { - throw new Error(`HTTP ${response.status}: ${response.statusText}`); - } - return await response.json(); - } catch (error) { - logger.error(`[API MOD SERVICE] Request to ${fullUrl} failed:`, error); - - // If request failed, try next GameServer URL - this.currentUrlIndex = (this.currentUrlIndex + 1) % this.gameServerUrls.length; - throw error; - } - } - - async getMods(enabledOnly = false) { - try { - const endpoint = `/api/mods${enabledOnly ? '?enabledOnly=true' : ''}`; - return await this.makeRequest(endpoint); - } catch (error) { - logger.error('[API MOD SERVICE] Error getting mods:', error); - throw error; - } - } - - async getMod(id) { - try { - return await this.makeRequest(`/api/mods/${id}`); - } catch (error) { - logger.error('[API MOD SERVICE] Error getting mod:', error); - throw error; - } - } - - async enableMod(id) { - try { - return await this.makeRequest(`/api/mods/${id}/enable`, { - method: 'POST' - }); - } catch (error) { - logger.error('[API MOD SERVICE] Error enabling mod:', error); - throw error; - } - } - - async disableMod(id) { - try { - return await this.makeRequest(`/api/mods/${id}/disable`, { - method: 'POST' - }); - } catch (error) { - logger.error('[API MOD SERVICE] Error disabling mod:', error); - throw error; - } - } - - async getModAssets(assetType) { - try { - return await this.makeRequest(`/api/mods/assets/${assetType}`); - } catch (error) { - logger.error('[API MOD SERVICE] Error getting mod assets:', error); - throw error; - } - } - - async getModAsset(assetType, assetId) { - try { - return await this.makeRequest(`/api/mods/assets/${assetType}/${assetId}`); - } catch (error) { - logger.error('[API MOD SERVICE] Error getting mod asset:', error); - throw error; - } - } - - async getLoadOrder() { - try { - return await this.makeRequest('/api/mods/load-order'); - } catch (error) { - logger.error('[API MOD SERVICE] Error getting load order:', error); - throw error; - } - } - - async setLoadOrder(modIds) { - try { - return await this.makeRequest('/api/mods/load-order', { - method: 'POST', - body: JSON.stringify({ modIds }) - }); - } catch (error) { - logger.error('[API MOD SERVICE] Error setting load order:', error); - throw error; - } - } - - async getServerPreferences() { - try { - return await this.makeRequest('/api/mods/preferences/server'); - } catch (error) { - logger.error('[API MOD SERVICE] Error getting server preferences:', error); - throw error; - } - } - - async setServerPreference(key, value) { - try { - return await this.makeRequest('/api/mods/preferences/server', { - method: 'POST', - body: JSON.stringify({ key, value }) - }); - } catch (error) { - logger.error('[API MOD SERVICE] Error setting server preference:', error); - throw error; - } - } - - async reloadMods() { - try { - return await this.makeRequest('/api/mods/reload', { - method: 'POST' - }); - } catch (error) { - logger.error('[API MOD SERVICE] Error reloading mods:', error); - throw error; - } - } -} - -module.exports = new ApiModService(); diff --git a/GameServer/.env.example b/GameServer/.env.example new file mode 100644 index 0000000..d3e32e4 --- /dev/null +++ b/GameServer/.env.example @@ -0,0 +1,17 @@ +# Game Server Configuration +PORT=3001 +NODE_ENV=production + +# Database Configuration +MONGODB_URI=mongodb://localhost:27017/galaxystrikeonline + +# Optional: API Server URL for authentication validation +API_SERVER_URL=http://localhost:3000 + +# Optional: Server identification +SERVER_NAME=Game Server +SERVER_REGION=us-east +MAX_PLAYERS=50 + +# Logging +LOG_LEVEL=info diff --git a/GameServer/config/LocalDatabase.js b/GameServer/config/LocalDatabase.js deleted file mode 100644 index 777eee7..0000000 --- a/GameServer/config/LocalDatabase.js +++ /dev/null @@ -1,175 +0,0 @@ -const sqlite3 = require('sqlite3').verbose(); -const path = require('path'); -const fs = require('fs'); -const logger = require('../utils/logger'); - -class LocalDatabase { - constructor() { - this.db = null; - this.dbPath = null; - } - - async initialize() { - try { - // Create data directory if it doesn't exist - const dataDir = path.join(__dirname, '../../data'); - if (!fs.existsSync(dataDir)) { - fs.mkdirSync(dataDir, { recursive: true }); - logger.info(`[LOCAL DB] Created data directory: ${dataDir}`); - } - - this.dbPath = path.join(dataDir, 'mods.db'); - - logger.info(`[LOCAL DB] Initializing database at: ${this.dbPath}`); - - // Create database connection - this.db = new sqlite3.Database(this.dbPath, (err) => { - if (err) { - logger.error('[LOCAL DB] Error opening database:', err.message); - throw err; - } else { - logger.info('[LOCAL DB] Database connected successfully'); - } - }); - - // Enable foreign keys - await this.run('PRAGMA foreign_keys = ON'); - - // Create tables - await this.createTables(); - - logger.info('[LOCAL DB] Database initialized successfully'); - return true; - } catch (error) { - logger.error('[LOCAL DB] Failed to initialize database:', error); - throw error; - } - } - - async createTables() { - const tables = [ - // Mods table - `CREATE TABLE IF NOT EXISTS mods ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - name TEXT NOT NULL UNIQUE, - version TEXT NOT NULL, - author TEXT NOT NULL, - description TEXT, - enabled INTEGER DEFAULT 1, - installed_at DATETIME DEFAULT CURRENT_TIMESTAMP, - updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, - file_path TEXT NOT NULL, - checksum TEXT, - dependencies TEXT, - config TEXT - )`, - - // Mod assets table - `CREATE TABLE IF NOT EXISTS mod_assets ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - mod_id INTEGER NOT NULL, - asset_type TEXT NOT NULL, -- 'ship', 'item', 'quest', etc. - asset_id TEXT NOT NULL, - asset_data TEXT NOT NULL, -- JSON data - created_at DATETIME DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY (mod_id) REFERENCES mods (id) ON DELETE CASCADE, - UNIQUE(mod_id, asset_type, asset_id) - )`, - - // Server mod preferences table - `CREATE TABLE IF NOT EXISTS server_mod_preferences ( - key TEXT PRIMARY KEY, - value TEXT NOT NULL, - updated_at DATETIME DEFAULT CURRENT_TIMESTAMP - )`, - - // Mod load order table - `CREATE TABLE IF NOT EXISTS mod_load_order ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - mod_id INTEGER NOT NULL, - load_order INTEGER NOT NULL, - FOREIGN KEY (mod_id) REFERENCES mods (id) ON DELETE CASCADE, - UNIQUE(mod_id) - )` - ]; - - for (const table of tables) { - await this.run(table); - } - - logger.info('[LOCAL DB] All tables created successfully'); - } - - // Helper method to run SQL commands - run(sql, params = []) { - return new Promise((resolve, reject) => { - this.db.run(sql, params, function(err) { - if (err) { - logger.error('[LOCAL DB] SQL Error:', err.message); - reject(err); - } else { - resolve({ id: this.lastID, changes: this.changes }); - } - }); - }); - } - - // Helper method to get single row - get(sql, params = []) { - return new Promise((resolve, reject) => { - this.db.get(sql, params, (err, row) => { - if (err) { - logger.error('[LOCAL DB] SQL Error:', err.message); - reject(err); - } else { - resolve(row); - } - }); - }); - } - - // Helper method to get multiple rows - all(sql, params = []) { - return new Promise((resolve, reject) => { - this.db.all(sql, params, (err, rows) => { - if (err) { - logger.error('[LOCAL DB] SQL Error:', err.message); - reject(err); - } else { - resolve(rows); - } - }); - }); - } - - // Close database connection - close() { - return new Promise((resolve, reject) => { - if (this.db) { - this.db.close((err) => { - if (err) { - logger.error('[LOCAL DB] Error closing database:', err.message); - reject(err); - } else { - logger.info('[LOCAL DB] Database closed'); - resolve(); - } - }); - } else { - resolve(); - } - }); - } - - // Get database instance - getDatabase() { - return this.db; - } - - // Get database path - getDatabasePath() { - return this.dbPath; - } -} - -module.exports = new LocalDatabase(); diff --git a/GameServer/models/ModModels.js b/GameServer/models/ModModels.js deleted file mode 100644 index e13ecf3..0000000 --- a/GameServer/models/ModModels.js +++ /dev/null @@ -1,264 +0,0 @@ -const LocalDatabase = require('../config/LocalDatabase'); -const logger = require('../utils/logger'); - -/** - * Mod Model - Handles mod data in local database - */ -class ModModel { - static async create(modData) { - const { - name, - version, - author, - description, - filePath, - checksum, - dependencies, - config - } = modData; - - const sql = ` - INSERT INTO mods (name, version, author, description, file_path, checksum, dependencies, config) - VALUES (?, ?, ?, ?, ?, ?, ?, ?) - `; - - const result = await LocalDatabase.run(sql, [ - name, - version, - author, - description || null, - filePath, - checksum || null, - dependencies ? JSON.stringify(dependencies) : null, - config ? JSON.stringify(config) : null - ]); - - return await this.findById(result.id); - } - - static async findById(id) { - const sql = 'SELECT * FROM mods WHERE id = ?'; - const mod = await LocalDatabase.get(sql, [id]); - return this.parseMod(mod); - } - - static async findByName(name) { - const sql = 'SELECT * FROM mods WHERE name = ?'; - const mod = await LocalDatabase.get(sql, [name]); - return this.parseMod(mod); - } - - static async findAll(enabledOnly = false) { - const sql = enabledOnly - ? 'SELECT * FROM mods WHERE enabled = 1 ORDER BY name' - : 'SELECT * FROM mods ORDER BY name'; - - const mods = await LocalDatabase.all(sql); - return mods.map(mod => this.parseMod(mod)); - } - - static async update(id, updates) { - const fields = []; - const values = []; - - Object.keys(updates).forEach(key => { - if (key === 'dependencies' || key === 'config') { - fields.push(`${key} = ?`); - values.push(JSON.stringify(updates[key])); - } else { - fields.push(`${key} = ?`); - values.push(updates[key]); - } - }); - - if (fields.length === 0) return false; - - fields.push('updated_at = CURRENT_TIMESTAMP'); - values.push(id); - - const sql = `UPDATE mods SET ${fields.join(', ')} WHERE id = ?`; - const result = await LocalDatabase.run(sql, values); - - return result.changes > 0; - } - - static async delete(id) { - const sql = 'DELETE FROM mods WHERE id = ?'; - const result = await LocalDatabase.run(sql, [id]); - return result.changes > 0; - } - - static async enable(id) { - return await this.update(id, { enabled: 1 }); - } - - static async disable(id) { - return await this.update(id, { enabled: 0 }); - } - - static async getLoadOrder() { - const sql = ` - SELECT m.*, ml.load_order - FROM mods m - LEFT JOIN mod_load_order ml ON m.id = ml.mod_id - WHERE m.enabled = 1 - ORDER BY ml.load_order ASC, m.name ASC - `; - - const mods = await LocalDatabase.all(sql); - return mods.map(mod => this.parseMod(mod)); - } - - static async setLoadOrder(modIds) { - await LocalDatabase.run('DELETE FROM mod_load_order'); - - for (let i = 0; i < modIds.length; i++) { - await LocalDatabase.run( - 'INSERT INTO mod_load_order (mod_id, load_order) VALUES (?, ?)', - [modIds[i], i + 1] - ); - } - - return true; - } - - static parseMod(mod) { - if (!mod) return null; - - return { - ...mod, - enabled: Boolean(mod.enabled), - dependencies: mod.dependencies ? JSON.parse(mod.dependencies) : null, - config: mod.config ? JSON.parse(mod.config) : null - }; - } -} - -/** - * ModAsset Model - Handles mod assets in local database - */ -class ModAssetModel { - static async create(assetData) { - const { modId, assetType, assetId, assetData: data } = assetData; - - const sql = ` - INSERT OR REPLACE INTO mod_assets (mod_id, asset_type, asset_id, asset_data) - VALUES (?, ?, ?, ?) - `; - - const result = await LocalDatabase.run(sql, [ - modId, - assetType, - assetId, - JSON.stringify(data) - ]); - - return await this.findById(result.id); - } - - static async findById(id) { - const sql = 'SELECT * FROM mod_assets WHERE id = ?'; - const asset = await LocalDatabase.get(sql, [id]); - return this.parseAsset(asset); - } - - static async findByModId(modId) { - const sql = 'SELECT * FROM mod_assets WHERE mod_id = ?'; - const assets = await LocalDatabase.all(sql, [modId]); - return assets.map(asset => this.parseAsset(asset)); - } - - static async findByType(assetType) { - const sql = ` - SELECT ma.*, m.name as mod_name, m.enabled as mod_enabled - FROM mod_assets ma - JOIN mods m ON ma.mod_id = m.id - WHERE ma.asset_type = ? AND m.enabled = 1 - ORDER BY m.name, ma.asset_id - `; - - const assets = await LocalDatabase.all(sql, [assetType]); - return assets.map(asset => this.parseAsset(asset)); - } - - static async findByTypeAndId(assetType, assetId) { - const sql = ` - SELECT ma.*, m.name as mod_name, m.enabled as mod_enabled - FROM mod_assets ma - JOIN mods m ON ma.mod_id = m.id - WHERE ma.asset_type = ? AND ma.asset_id = ? AND m.enabled = 1 - `; - - const asset = await LocalDatabase.get(sql, [assetType, assetId]); - return this.parseAsset(asset); - } - - static async delete(id) { - const sql = 'DELETE FROM mod_assets WHERE id = ?'; - const result = await LocalDatabase.run(sql, [id]); - return result.changes > 0; - } - - static async deleteByModId(modId) { - const sql = 'DELETE FROM mod_assets WHERE mod_id = ?'; - const result = await LocalDatabase.run(sql, [modId]); - return result.changes > 0; - } - - static parseAsset(asset) { - if (!asset) return null; - - return { - ...asset, - assetData: JSON.parse(asset.asset_data), - modEnabled: Boolean(asset.mod_enabled) - }; - } -} - -/** - * ServerModPreferences Model - Handles server mod preferences - */ -class ServerModPreferencesModel { - static async set(key, value) { - const sql = ` - INSERT OR REPLACE INTO server_mod_preferences (key, value, updated_at) - VALUES (?, ?, CURRENT_TIMESTAMP) - `; - - const result = await LocalDatabase.run(sql, [key, JSON.stringify(value)]); - return result.changes > 0; - } - - static async get(key) { - const sql = 'SELECT * FROM server_mod_preferences WHERE key = ?'; - const pref = await LocalDatabase.get(sql, [key]); - - if (!pref) return null; - return JSON.parse(pref.value); - } - - static async getAll() { - const sql = 'SELECT * FROM server_mod_preferences'; - const prefs = await LocalDatabase.all(sql); - - const result = {}; - prefs.forEach(pref => { - result[pref.key] = JSON.parse(pref.value); - }); - - return result; - } - - static async delete(key) { - const sql = 'DELETE FROM server_mod_preferences WHERE key = ?'; - const result = await LocalDatabase.run(sql, [key]); - return result.changes > 0; - } -} - -module.exports = { - ModModel, - ModAssetModel, - ServerModPreferencesModel -}; diff --git a/GameServer/routes/mods.js b/GameServer/routes/mods.js deleted file mode 100644 index 87b561f..0000000 --- a/GameServer/routes/mods.js +++ /dev/null @@ -1,214 +0,0 @@ -const express = require('express'); -const logger = require('../utils/logger'); -const ModService = require('../services/ModService'); - -const router = express.Router(); - -// Get all mods -router.get('/', async (req, res) => { - try { - const { enabledOnly } = req.query; - const mods = await ModService.getMods(enabledOnly === 'true'); - - res.json({ - success: true, - mods - }); - } catch (error) { - logger.error('[GAME SERVER MODS API] Error getting mods:', error); - res.status(500).json({ error: 'Internal server error' }); - } -}); - -// Get specific mod -router.get('/:id', async (req, res) => { - try { - const { id } = req.params; - const mod = await ModService.getMod(parseInt(id)); - - if (!mod) { - return res.status(404).json({ error: 'Mod not found' }); - } - - res.json({ - success: true, - mod - }); - } catch (error) { - logger.error('[GAME SERVER MODS API] Error getting mod:', error); - res.status(500).json({ error: 'Internal server error' }); - } -}); - -// Enable mod -router.post('/:id/enable', async (req, res) => { - try { - const { id } = req.params; - const success = await ModService.enableMod(parseInt(id)); - - if (!success) { - return res.status(404).json({ error: 'Mod not found' }); - } - - res.json({ - success: true, - message: 'Mod enabled successfully' - }); - } catch (error) { - logger.error('[GAME SERVER MODS API] Error enabling mod:', error); - res.status(500).json({ error: 'Internal server error' }); - } -}); - -// Disable mod -router.post('/:id/disable', async (req, res) => { - try { - const { id } = req.params; - const success = await ModService.disableMod(parseInt(id)); - - if (!success) { - return res.status(404).json({ error: 'Mod not found' }); - } - - res.json({ - success: true, - message: 'Mod disabled successfully' - }); - } catch (error) { - logger.error('[GAME SERVER MODS API] Error disabling mod:', error); - res.status(500).json({ error: 'Internal server error' }); - } -}); - -// Get mod assets by type -router.get('/assets/:type', async (req, res) => { - try { - const { type } = req.params; - const assets = await ModService.getModAssets(type); - - res.json({ - success: true, - assets - }); - } catch (error) { - logger.error('[GAME SERVER MODS API] Error getting mod assets:', error); - res.status(500).json({ error: 'Internal server error' }); - } -}); - -// Get specific mod asset -router.get('/assets/:type/:assetId', async (req, res) => { - try { - const { type, assetId } = req.params; - const asset = await ModService.getModAsset(type, assetId); - - if (!asset) { - return res.status(404).json({ error: 'Asset not found' }); - } - - res.json({ - success: true, - asset - }); - } catch (error) { - logger.error('[GAME SERVER MODS API] Error getting mod asset:', error); - res.status(500).json({ error: 'Internal server error' }); - } -}); - -// Get mod load order -router.get('/load-order', async (req, res) => { - try { - const loadOrder = await ModService.getLoadOrder(); - - res.json({ - success: true, - loadOrder - }); - } catch (error) { - logger.error('[GAME SERVER MODS API] Error getting load order:', error); - res.status(500).json({ error: 'Internal server error' }); - } -}); - -// Set mod load order -router.post('/load-order', async (req, res) => { - try { - const { modIds } = req.body; - - if (!Array.isArray(modIds)) { - return res.status(400).json({ error: 'modIds must be an array' }); - } - - const success = await ModService.setLoadOrder(modIds); - - if (!success) { - return res.status(500).json({ error: 'Failed to set load order' }); - } - - res.json({ - success: true, - message: 'Load order updated successfully' - }); - } catch (error) { - logger.error('[GAME SERVER MODS API] Error setting load order:', error); - res.status(500).json({ error: 'Internal server error' }); - } -}); - -// Get server mod preferences -router.get('/preferences/server', async (req, res) => { - try { - const preferences = await ModService.getServerPreferences(); - - res.json({ - success: true, - preferences - }); - } catch (error) { - logger.error('[GAME SERVER MODS API] Error getting server preferences:', error); - res.status(500).json({ error: 'Internal server error' }); - } -}); - -// Set server mod preference -router.post('/preferences/server', async (req, res) => { - try { - const { key, value } = req.body; - - if (!key || value === undefined) { - return res.status(400).json({ error: 'key and value are required' }); - } - - const success = await ModService.setServerPreference(key, value); - - if (!success) { - return res.status(500).json({ error: 'Failed to set preference' }); - } - - res.json({ - success: true, - message: 'Preference set successfully' - }); - } catch (error) { - logger.error('[GAME SERVER MODS API] Error setting server preference:', error); - res.status(500).json({ error: 'Internal server error' }); - } -}); - -// Reload mods from filesystem -router.post('/reload', async (req, res) => { - try { - await ModService.reloadMods(); - - res.json({ - success: true, - message: 'Mods reloaded successfully' - }); - } catch (error) { - logger.error('[GAME SERVER MODS API] Error reloading mods:', error); - res.status(500).json({ error: 'Internal server error' }); - } -}); - -module.exports = router; diff --git a/GameServer/server.js b/GameServer/server.js index c38ea80..bcc08e6 100644 --- a/GameServer/server.js +++ b/GameServer/server.js @@ -1,202 +1,273 @@ /** - * Game Server - Real-time Multiplayer - * Handles actual game instances, player connections, and real-time gameplay + * 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 dotenv = require('dotenv'); require('dotenv').config(); const logger = require('./utils/logger'); const connectDB = require('./config/database'); -const { initializeGameSystems } = require('./systems/GameSystem'); -const SocketHandlers = require('./socket/socketHandlers'); -const ServerRegistrationService = require('./services/ServerRegistrationService'); -const ModService = require('./services/ModService'); -const modRoutes = require('./routes/mods'); -const { errorHandler, notFound } = require('./middleware/errorHandler'); const app = express(); const server = http.createServer(app); const io = socketIo(server, { cors: { - origin: ["https://galaxystrike.online", "https://api.korvarix.com", "http://localhost:3000"], + origin: ["http://localhost:3000", "http://127.0.0.1:3000", "file://"], methods: ["GET", "POST"], credentials: true } }); +// Game state +const gameInstances = new Map(); +const connectedClients = new Map(); + // Middleware -app.use(helmet()); -app.use(compression()); app.use(cors({ - origin: ["https://galaxystrike.online", "https://api.korvarix.com", "http://localhost:3000"], + origin: ["http://localhost:3000", "http://127.0.0.1:3000", "file://"], credentials: true })); app.use(express.json({ limit: '10mb' })); app.use(express.urlencoded({ extended: true })); -// Game Server Routes (minimal - mostly for health checks and server management) +// Health check endpoint app.get('/health', (req, res) => { res.status(200).json({ - status: 'Game Server OK', - service: 'galaxystrikeonline-game', + status: 'OK', timestamp: new Date().toISOString(), uptime: process.uptime(), - activeServers: Object.keys(gameServers).length, - connectedPlayers: connectedPlayers.size + mode: 'game-server', + activeInstances: gameInstances.size, + connectedClients: connectedClients.size }); }); -// Get server status -app.get('/api/game/status', (req, res) => { - res.json({ - activeServers: Object.keys(gameServers).length, - connectedPlayers: connectedPlayers.size, - timestamp: new Date().toISOString() +// 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' }); }); -// Mod management routes -app.use('/api/mods', modRoutes); - -// Error handling -app.use(notFound); -app.use(errorHandler); - -// Global game server instances -const gameServers = {}; -let serverRegistration; // Global reference to registration service - -// Player tracking -const connectedPlayers = new Set(); // Track actual player connections -let socketHandlers; - -io.on('connection', (socket) => { - logger.info(`Game Server: Player connected - ${socket.id}`); - socketHandlers.handleConnection(socket); -}); - -// Handle uncaught errors -process.on('uncaughtException', (error) => { - logger.error('Uncaught Exception:', error); -}); - -process.on('unhandledRejection', (reason, promise) => { - logger.error('Unhandled Rejection at:', promise, 'reason:', reason); -}); - -// Graceful shutdown handlers -async function gracefulShutdown(signal) { - logger.info(`[GRACEFUL SHUTDOWN] Received ${signal}, shutting down gracefully...`); +// Game data endpoints (similar to LocalServer) +app.post('/api/game/player/:id/save', (req, res) => { + const playerId = req.params.id; + const playerData = req.body; - try { - // Stop accepting new connections - server.close(async () => { - logger.info('[GRACEFUL SHUTDOWN] HTTP server closed'); - - // Unregister from API - if (serverRegistration) { - await serverRegistration.stopHeartbeat(); - const unregistered = await serverRegistration.unregisterWithAPI(); - if (unregistered) { - logger.info('[GRACEFUL SHUTDOWN] Server unregistered from API successfully'); - } else { - logger.warn('[GRACEFUL SHUTDOWN] Failed to unregister from API'); - } + // 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' + }); + } +}); + +// 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 + }); + + // Authentication (similar to LocalServer) + socket.on('authenticate', (data) => { + console.log('[GAME SERVER] Authenticating client:', socket.id, data); + + // In production, validate with API server + socket.emit('authenticated', { + success: true, + user: { + id: data.userId || socket.id, + username: data.username || 'Game Player', + token: 'game-token-' + Date.now() } - - // Shutdown mod service - await ModService.shutdown(); - - // Close database connections - const mongoose = require('mongoose'); - await mongoose.connection.close(); - logger.info('[GRACEFUL SHUTDOWN] Database connections closed'); - - process.exit(0); + }); + }); + + // Game data events (similar to LocalServer) + socket.on('saveGameData', (data) => { + console.log('[GAME SERVER] Saving game data for:', socket.id); + + if (connectedClients.has(socket.id)) { + connectedClients.get(socket.id).playerData = data; + } + + socket.emit('gameDataSaved', { + success: true, + timestamp: Date.now() + }); + }); + + 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 + }); + }); + + // 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 }); - // Force shutdown after 30 seconds - setTimeout(() => { - logger.error('[GRACEFUL SHUTDOWN] Forced shutdown after timeout'); - process.exit(1); - }, 30000); + // 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() + }); + } + }); + + socket.on('disconnect', () => { + console.log('[GAME SERVER] Client disconnected:', socket.id); - } catch (error) { - logger.error('[GRACEFUL SHUTDOWN] Error during shutdown:', error); - process.exit(1); - } -} - -process.on('SIGTERM', () => gracefulShutdown('SIGTERM')); -process.on('SIGINT', () => gracefulShutdown('SIGINT')); - -// Handle Socket.IO adapter errors -io.engine.on('connection_error', (err) => { - logger.error('Socket.IO connection error:', err); + const clientData = connectedClients.get(socket.id); + if (clientData && 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); + }); + + // Welcome message (similar to LocalServer) + socket.emit('welcome', { + message: 'Connected to game server', + serverInfo: { + mode: 'multiplayer', + activeInstances: gameInstances.size, + timestamp: new Date().toISOString() + } + }); }); -io.of('/').adapter.on('error', (err) => { - logger.error('Socket.IO adapter error:', err); -}); +// Start server +const PORT = process.env.PORT || 3001; -// Initialize database and game systems -async function startGameServer() { +async function startServer() { try { // Connect to database await connectDB(); - logger.info('Game Server: Database connected successfully'); - - // Initialize mod service - await ModService.initialize(); - logger.info('Game Server: Mod service initialized'); - - // Initialize game systems - await initializeGameSystems(); - logger.info('Game Server: Game systems initialized'); - - // Initialize server registration service - const gameServerUrl = `https://api.korvarix.com:${process.env.GAME_PORT || 3002}`; - const apiUrl = process.env.API_SERVER_URL || 'https://api.korvarix.com'; - const serverName = process.env.SERVER_NAME || 'Game Server'; - const serverRegion = process.env.SERVER_REGION || 'us-east'; - const maxPlayers = parseInt(process.env.MAX_PLAYERS) || 10; - serverRegistration = new ServerRegistrationService(gameServerUrl, apiUrl, serverName, serverRegion, maxPlayers); - - // Set up player count callback - serverRegistration.setPlayerCountCallback(() => connectedPlayers.size); - - serverRegistration.startHeartbeat(); - - // Initialize socket handlers - socketHandlers = new SocketHandlers(io, gameServers, connectedPlayers); - - // Make registration service available to socket handlers - socketHandlers.serverRegistration = serverRegistration; - - // Start server - const PORT = process.env.GAME_PORT || 3002; // Game Server on port 3002 server.listen(PORT, () => { - logger.info(`Game Server running on port ${PORT}`); - logger.info('Game Server handles: Real-time Multiplayer, Game Instances, Socket.IO'); - logger.info(`Game Server Name: ${serverName}`); - logger.info(`Game Server Region: ${serverRegion}`); - logger.info(`Game Server URL: ${gameServerUrl}`); - logger.info(`API Server URL: ${apiUrl}`); + console.log(`[GAME SERVER] Game server running on port ${PORT}`); + console.log(`[GAME SERVER] Socket.IO ready for multiplayer connections`); }); } catch (error) { - logger.error('Failed to start Game Server:', error); + console.error('[GAME SERVER] Failed to start server:', error); process.exit(1); } } -startGameServer(); +startServer(); -module.exports = { app, server, io, gameServers }; +module.exports = { app, server, io, gameInstances, connectedClients }; diff --git a/GameServer/services/ModService.js b/GameServer/services/ModService.js deleted file mode 100644 index 9c3b845..0000000 --- a/GameServer/services/ModService.js +++ /dev/null @@ -1,264 +0,0 @@ -const { ModModel, ModAssetModel, ServerModPreferencesModel } = require('../models/ModModels'); -const LocalDatabase = require('../config/LocalDatabase'); -const logger = require('../utils/logger'); -const fs = require('fs'); -const path = require('path'); -const crypto = require('crypto'); - -class ModService { - constructor() { - this.modsDirectory = path.join(__dirname, '../../mods'); - this.initialized = false; - } - - async initialize() { - try { - // Initialize local database - await LocalDatabase.initialize(); - - // Create mods directory if it doesn't exist - if (!fs.existsSync(this.modsDirectory)) { - fs.mkdirSync(this.modsDirectory, { recursive: true }); - logger.info(`[MOD SERVICE] Created mods directory: ${this.modsDirectory}`); - } - - // Load existing mods from filesystem - await this.loadModsFromFilesystem(); - - this.initialized = true; - logger.info('[MOD SERVICE] Mod service initialized successfully'); - } catch (error) { - logger.error('[MOD SERVICE] Failed to initialize mod service:', error); - throw error; - } - } - - async loadModsFromFilesystem() { - try { - if (!fs.existsSync(this.modsDirectory)) { - return; - } - - const modFolders = fs.readdirSync(this.modsDirectory, { withFileTypes: true }) - .filter(dirent => dirent.isDirectory()) - .map(dirent => dirent.name); - - for (const modFolder of modFolders) { - await this.loadModFromFolder(modFolder); - } - - logger.info(`[MOD SERVICE] Loaded ${modFolders.length} mods from filesystem`); - } catch (error) { - logger.error('[MOD SERVICE] Error loading mods from filesystem:', error); - } - } - - async loadModFromFolder(modFolder) { - try { - const modPath = path.join(this.modsDirectory, modFolder); - const manifestPath = path.join(modPath, 'mod.json'); - - if (!fs.existsSync(manifestPath)) { - logger.warn(`[MOD SERVICE] Mod ${modFolder} missing mod.json manifest`); - return; - } - - const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8')); - - // Validate required fields - if (!manifest.name || !manifest.version || !manifest.author) { - logger.warn(`[MOD SERVICE] Mod ${modFolder} has invalid manifest`); - return; - } - - // Check if mod already exists in database - const existingMod = await ModModel.findByName(manifest.name); - - // Calculate checksum of mod files - const checksum = await this.calculateModChecksum(modPath); - - const modData = { - name: manifest.name, - version: manifest.version, - author: manifest.author, - description: manifest.description || '', - filePath: modPath, - checksum, - dependencies: manifest.dependencies || [], - config: manifest.config || {} - }; - - if (existingMod) { - // Update existing mod if checksum changed - if (existingMod.checksum !== checksum) { - await ModModel.update(existingMod.id, modData); - logger.info(`[MOD SERVICE] Updated mod: ${manifest.name}`); - } - } else { - // Create new mod - await ModModel.create(modData); - logger.info(`[MOD SERVICE] Installed new mod: ${manifest.name}`); - } - - // Load mod assets - await this.loadModAssets(manifest.name, modPath); - - } catch (error) { - logger.error(`[MOD SERVICE] Error loading mod ${modFolder}:`, error); - } - } - - async loadModAssets(modName, modPath) { - try { - const mod = await ModModel.findByName(modName); - if (!mod) return; - - // Clear existing assets for this mod - await ModAssetModel.deleteByModId(mod.id); - - // Load assets from assets folder - const assetsPath = path.join(modPath, 'assets'); - if (fs.existsSync(assetsPath)) { - await this.loadAssetsFromDirectory(mod.id, assetsPath); - } - - logger.info(`[MOD SERVICE] Loaded assets for mod: ${modName}`); - } catch (error) { - logger.error(`[MOD SERVICE] Error loading assets for mod ${modName}:`, error); - } - } - - async loadAssetsFromDirectory(modId, assetsPath) { - const assetTypes = ['ships', 'items', 'quests', 'systems']; - - for (const assetType of assetTypes) { - const assetTypePath = path.join(assetsPath, assetType); - - if (fs.existsSync(assetTypePath)) { - const assetFiles = fs.readdirSync(assetTypePath); - - for (const assetFile of assetFiles) { - if (assetFile.endsWith('.json')) { - try { - const assetData = JSON.parse( - fs.readFileSync(path.join(assetTypePath, assetFile), 'utf8') - ); - - const assetId = path.basename(assetFile, '.json'); - - await ModAssetModel.create({ - modId, - assetType: assetType.slice(0, -1), // Remove 's' for singular - assetId, - assetData - }); - } catch (error) { - logger.error(`[MOD SERVICE] Error loading asset ${assetFile}:`, error); - } - } - } - } - } - } - - async calculateModChecksum(modPath) { - try { - const hash = crypto.createHash('sha256'); - - // Hash all files in mod directory - const hashDirectory = (dir) => { - const files = fs.readdirSync(dir); - - files.forEach(file => { - const filePath = path.join(dir, file); - const stat = fs.statSync(filePath); - - if (stat.isDirectory()) { - hashDirectory(filePath); - } else { - const data = fs.readFileSync(filePath); - hash.update(data); - } - }); - }; - - hashDirectory(modPath); - return hash.digest('hex'); - } catch (error) { - logger.error(`[MOD SERVICE] Error calculating checksum for ${modPath}:`, error); - return null; - } - } - - // Public API methods - async getMods(enabledOnly = false) { - return await ModModel.findAll(enabledOnly); - } - - async getMod(id) { - return await ModModel.findById(id); - } - - async getModByName(name) { - return await ModModel.findByName(name); - } - - async enableMod(id) { - const result = await ModModel.enable(id); - if (result) { - logger.info(`[MOD SERVICE] Enabled mod: ${id}`); - } - return result; - } - - async disableMod(id) { - const result = await ModModel.disable(id); - if (result) { - logger.info(`[MOD SERVICE] Disabled mod: ${id}`); - } - return result; - } - - async getModAssets(assetType) { - return await ModAssetModel.findByType(assetType); - } - - async getModAsset(assetType, assetId) { - return await ModAssetModel.findByTypeAndId(assetType, assetId); - } - - async getLoadOrder() { - return await ModModel.getLoadOrder(); - } - - async setLoadOrder(modIds) { - const result = await ModModel.setLoadOrder(modIds); - if (result) { - logger.info(`[MOD SERVICE] Updated mod load order`); - } - return result; - } - - async getServerPreferences() { - return await ServerModPreferencesModel.getAll(); - } - - async setServerPreference(key, value) { - return await ServerModPreferencesModel.set(key, value); - } - - async reloadMods() { - logger.info('[MOD SERVICE] Reloading mods from filesystem...'); - await this.loadModsFromFilesystem(); - } - - async shutdown() { - if (this.initialized) { - await LocalDatabase.close(); - this.initialized = false; - logger.info('[MOD SERVICE] Mod service shut down'); - } - } -} - -module.exports = new ModService();