removed mod support for now

This commit is contained in:
Robert MacRae 2026-01-24 20:26:33 -04:00
parent 08edb2d80d
commit 5bb8b6aed0
13 changed files with 237 additions and 1852 deletions

View File

@ -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

View File

@ -1,5 +1,4 @@
const mongoose = require('mongoose'); const mongoose = require('mongoose');
const { calculateXPToNextLevel, getLevelFromXP } = require('../config/xp-progression');
const playerSchema = new mongoose.Schema({ const playerSchema = new mongoose.Schema({
userId: { userId: {
@ -24,12 +23,10 @@ const playerSchema = new mongoose.Schema({
select: false // Don't include password in queries by default select: false // Don't include password in queries by default
}, },
// Player stats // Player stats (simplified for API server)
stats: { stats: {
level: { type: Number, default: 1 }, level: { type: Number, default: 1 },
experience: { type: Number, default: 0 }, 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 }, credits: { type: Number, default: 1000 },
dungeonsCleared: { type: Number, default: 0 }, dungeonsCleared: { type: Number, default: 0 },
playTime: { 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({ 'stats.level': 1 });
playerSchema.index({ currentServer: 1 }); playerSchema.index({ currentServer: 1 });
// Methods // Methods (simplified for API server)
playerSchema.methods.addExperience = function(amount) { playerSchema.methods.addExperience = function(amount) {
// Add to total accumulated XP this.stats.experience += amount;
this.stats.totalXP += amount; return this.stats.experience;
// 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;
}; };
playerSchema.methods.addCredits = function(amount) { playerSchema.methods.addCredits = function(amount) {

View File

@ -17,14 +17,12 @@
"keywords": ["game", "server", "mmorpg", "api", "websocket"], "keywords": ["game", "server", "mmorpg", "api", "websocket"],
"dependencies": { "dependencies": {
"express": "^4.18.2", "express": "^4.18.2",
"socket.io": "^4.7.4",
"cors": "^2.8.5", "cors": "^2.8.5",
"helmet": "^7.1.0", "helmet": "^7.1.0",
"dotenv": "^16.3.1", "dotenv": "^16.3.1",
"bcryptjs": "^2.4.3", "bcryptjs": "^2.4.3",
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^9.0.2",
"mongoose": "^8.0.3", "mongoose": "^8.0.3",
"redis": "^4.6.11",
"winston": "^3.11.0", "winston": "^3.11.0",
"joi": "^17.11.0", "joi": "^17.11.0",
"rate-limiter-flexible": "^2.4.2", "rate-limiter-flexible": "^2.4.2",

View File

@ -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;

View File

@ -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;

View File

@ -10,7 +10,6 @@ const logger = require('./utils/logger');
const connectDB = require('./config/database'); const connectDB = require('./config/database');
const authRoutes = require('./routes/auth'); const authRoutes = require('./routes/auth');
const serverRoutes = require('./routes/servers'); const serverRoutes = require('./routes/servers');
const modsRoutes = require('./routes/mods');
const { errorHandler, notFound } = require('./middleware/errorHandler'); const { errorHandler, notFound } = require('./middleware/errorHandler');
// Override console.error to properly log error objects // 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/auth', authRoutes);
app.use('/api/servers', serverRoutes); app.use('/api/servers', serverRoutes);
app.use('/api/mods', modsRoutes);
// Health check // Health check
app.get('/health', (req, res) => { app.get('/health', (req, res) => {
@ -124,7 +122,7 @@ async function startServer() {
logger.info('Database connected successfully'); logger.info('Database connected successfully');
// Start API server // Start API server
const PORT = process.env.PORT || 3001; const PORT = process.env.PORT || 3000;
server.listen(PORT, () => { server.listen(PORT, () => {
logger.info(`API Server running on port ${PORT}`); logger.info(`API Server running on port ${PORT}`);
logger.info('API Server handles: Authentication, Server Browser, User Data'); logger.info('API Server handles: Authentication, Server Browser, User Data');

View File

@ -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();

17
GameServer/.env.example Normal file
View File

@ -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

View File

@ -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();

View File

@ -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
};

View File

@ -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;

View File

@ -1,202 +1,273 @@
/** /**
* Game Server - Real-time Multiplayer * Game Server - Based on Client LocalServer Infrastructure
* Handles actual game instances, player connections, and real-time gameplay * Handles real-time multiplayer game instances and gameplay
*/ */
const express = require('express'); const express = require('express');
const http = require('http'); const http = require('http');
const socketIo = require('socket.io'); const socketIo = require('socket.io');
const cors = require('cors'); const cors = require('cors');
const helmet = require('helmet');
const compression = require('compression');
const dotenv = require('dotenv'); const dotenv = require('dotenv');
require('dotenv').config(); require('dotenv').config();
const logger = require('./utils/logger'); const logger = require('./utils/logger');
const connectDB = require('./config/database'); 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 app = express();
const server = http.createServer(app); const server = http.createServer(app);
const io = socketIo(server, { const io = socketIo(server, {
cors: { 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"], methods: ["GET", "POST"],
credentials: true credentials: true
} }
}); });
// Game state
const gameInstances = new Map();
const connectedClients = new Map();
// Middleware // Middleware
app.use(helmet());
app.use(compression());
app.use(cors({ 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 credentials: true
})); }));
app.use(express.json({ limit: '10mb' })); app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true })); 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) => { app.get('/health', (req, res) => {
res.status(200).json({ res.status(200).json({
status: 'Game Server OK', status: 'OK',
service: 'galaxystrikeonline-game',
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
uptime: process.uptime(), uptime: process.uptime(),
activeServers: Object.keys(gameServers).length, mode: 'game-server',
connectedPlayers: connectedPlayers.size activeInstances: gameInstances.size,
connectedClients: connectedClients.size
}); });
}); });
// Get server status // API version endpoint
app.get('/api/game/status', (req, res) => { app.get('/api/ssc/version', (req, res) => {
res.json({ res.status(200).json({
activeServers: Object.keys(gameServers).length, version: '1.0.0',
connectedPlayers: connectedPlayers.size, service: 'galaxystrikeonline-game-server',
timestamp: new Date().toISOString() timestamp: new Date().toISOString(),
mode: 'multiplayer'
}); });
}); });
// Mod management routes // Game data endpoints (similar to LocalServer)
app.use('/api/mods', modRoutes); app.post('/api/game/player/:id/save', (req, res) => {
const playerId = req.params.id;
const playerData = req.body;
// Error handling // Store player data in game instance
app.use(notFound); if (connectedClients.has(playerId)) {
app.use(errorHandler); connectedClients.get(playerId).playerData = playerData;
// Global game server instances // Broadcast to other players in same instance
const gameServers = {}; const instanceId = connectedClients.get(playerId).instanceId;
let serverRegistration; // Global reference to registration service if (gameInstances.has(instanceId)) {
io.to(instanceId).emit('playerDataUpdated', {
playerId,
timestamp: Date.now()
});
}
}
// Player tracking res.status(200).json({
const connectedPlayers = new Set(); // Track actual player connections success: true,
let socketHandlers; message: 'Player data saved to game server'
});
io.on('connection', (socket) => {
logger.info(`Game Server: Player connected - ${socket.id}`);
socketHandlers.handleConnection(socket);
}); });
// Handle uncaught errors app.get('/api/game/player/:id', (req, res) => {
process.on('uncaughtException', (error) => { const playerId = req.params.id;
logger.error('Uncaught Exception:', error);
if (connectedClients.has(playerId)) {
const playerData = connectedClients.get(playerId).playerData;
res.status(200).json({
success: true,
player: playerData
}); });
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...`);
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 { } else {
logger.warn('[GRACEFUL SHUTDOWN] Failed to unregister from API'); res.status(404).json({
success: false,
error: 'Player not connected to game server'
});
} }
}
// 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);
}); });
// Force shutdown after 30 seconds // Socket.IO handlers (based on LocalServer)
setTimeout(() => { io.on('connection', (socket) => {
logger.error('[GRACEFUL SHUTDOWN] Forced shutdown after timeout'); console.log('[GAME SERVER] Client connected:', socket.id);
process.exit(1); connectedClients.set(socket.id, {
}, 30000); connectedAt: Date.now(),
playerData: null,
instanceId: null
});
} catch (error) { // Authentication (similar to LocalServer)
logger.error('[GRACEFUL SHUTDOWN] Error during shutdown:', error); socket.on('authenticate', (data) => {
process.exit(1); 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()
}
});
});
// 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
});
// 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);
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
});
} }
} }
process.on('SIGTERM', () => gracefulShutdown('SIGTERM')); connectedClients.delete(socket.id);
process.on('SIGINT', () => gracefulShutdown('SIGINT'));
// Handle Socket.IO adapter errors
io.engine.on('connection_error', (err) => {
logger.error('Socket.IO connection error:', err);
}); });
io.of('/').adapter.on('error', (err) => { // Welcome message (similar to LocalServer)
logger.error('Socket.IO adapter error:', err); socket.emit('welcome', {
message: 'Connected to game server',
serverInfo: {
mode: 'multiplayer',
activeInstances: gameInstances.size,
timestamp: new Date().toISOString()
}
});
}); });
// Initialize database and game systems // Start server
async function startGameServer() { const PORT = process.env.PORT || 3001;
async function startServer() {
try { try {
// Connect to database // Connect to database
await connectDB(); 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, () => { server.listen(PORT, () => {
logger.info(`Game Server running on port ${PORT}`); console.log(`[GAME SERVER] Game server running on port ${PORT}`);
logger.info('Game Server handles: Real-time Multiplayer, Game Instances, Socket.IO'); console.log(`[GAME SERVER] Socket.IO ready for multiplayer connections`);
logger.info(`Game Server Name: ${serverName}`);
logger.info(`Game Server Region: ${serverRegion}`);
logger.info(`Game Server URL: ${gameServerUrl}`);
logger.info(`API Server URL: ${apiUrl}`);
}); });
} catch (error) { } catch (error) {
logger.error('Failed to start Game Server:', error); console.error('[GAME SERVER] Failed to start server:', error);
process.exit(1); process.exit(1);
} }
} }
startGameServer(); startServer();
module.exports = { app, server, io, gameServers }; module.exports = { app, server, io, gameInstances, connectedClients };

View File

@ -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();