From 9df6b22721a10d6b77c5845020fc4e618e182204 Mon Sep 17 00:00:00 2001 From: Robert MacRae Date: Sat, 24 Jan 2026 21:39:56 -0400 Subject: [PATCH] fixed server software --- GameServer/.env.example | 11 +- GameServer/routes/base.js | 243 +++++++++++++ GameServer/routes/crafting.js | 212 +++++++++++ GameServer/routes/dungeons.js | 266 ++++++++++++++ GameServer/routes/idle.js | 282 +++++++++++++++ GameServer/routes/quests.js | 232 ++++++++++++ GameServer/routes/ships.js | 452 +++++++++++++++++++++++ GameServer/routes/skills.js | 187 ++++++++++ GameServer/server.js | 61 +++- GameServer/systems/BaseSystem.js | 512 +++++++++++++++++++++++++++ GameServer/systems/CraftingSystem.js | 277 +++++++++++++++ GameServer/systems/DungeonSystem.js | 507 ++++++++++++++++++++++++++ GameServer/systems/IdleSystem.js | 272 ++++++++++++++ GameServer/systems/QuestSystem.js | 439 +++++++++++++++++++++++ GameServer/systems/ShipSystem.js | 488 +++++++++++++++++++++++++ GameServer/systems/SkillSystem.js | 397 +++++++++++++++++++++ 16 files changed, 4831 insertions(+), 7 deletions(-) create mode 100644 GameServer/routes/base.js create mode 100644 GameServer/routes/crafting.js create mode 100644 GameServer/routes/dungeons.js create mode 100644 GameServer/routes/idle.js create mode 100644 GameServer/routes/quests.js create mode 100644 GameServer/routes/ships.js create mode 100644 GameServer/routes/skills.js create mode 100644 GameServer/systems/BaseSystem.js create mode 100644 GameServer/systems/CraftingSystem.js create mode 100644 GameServer/systems/DungeonSystem.js create mode 100644 GameServer/systems/IdleSystem.js create mode 100644 GameServer/systems/QuestSystem.js create mode 100644 GameServer/systems/ShipSystem.js create mode 100644 GameServer/systems/SkillSystem.js diff --git a/GameServer/.env.example b/GameServer/.env.example index d3e32e4..43ec93a 100644 --- a/GameServer/.env.example +++ b/GameServer/.env.example @@ -1,17 +1,18 @@ # Game Server Configuration -PORT=3001 +PORT=3002 NODE_ENV=production # Database Configuration MONGODB_URI=mongodb://localhost:27017/galaxystrikeonline -# Optional: API Server URL for authentication validation +# API Server Configuration API_SERVER_URL=http://localhost:3000 +GAME_SERVER_URL=https://dev.gameserver.galaxystrike.online # Optional: Server identification -SERVER_NAME=Game Server -SERVER_REGION=us-east -MAX_PLAYERS=50 +SERVER_NAME=Dev Game Server +SERVER_REGION=local +MAX_PLAYERS=8 # Logging LOG_LEVEL=info diff --git a/GameServer/routes/base.js b/GameServer/routes/base.js new file mode 100644 index 0000000..e973390 --- /dev/null +++ b/GameServer/routes/base.js @@ -0,0 +1,243 @@ +const express = require('express'); +const BaseSystem = require('../systems/BaseSystem'); + +const router = express.Router(); + +// Initialize base system +const baseSystem = new BaseSystem(); + +// Get player base +router.get('/player/:userId', async (req, res) => { + try { + const { userId } = req.params; + const base = baseSystem.getPlayerBase(userId); + + res.json({ + success: true, + base + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +// Get base statistics +router.get('/stats/:userId', async (req, res) => { + try { + const { userId } = req.params; + const stats = baseSystem.getBaseStatistics(userId); + + res.json({ + success: true, + stats + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +// Add experience to base +router.post('/experience/:userId', async (req, res) => { + try { + const { userId } = req.params; + const { amount } = req.body; + + if (!amount || amount <= 0) { + return res.status(400).json({ + success: false, + error: 'Valid experience amount required' + }); + } + + const result = baseSystem.addExperience(userId, amount); + + res.json({ + success: true, + result + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +// Build room +router.post('/build/:userId', async (req, res) => { + try { + const { userId } = req.params; + const { roomType, level = 1 } = req.body; + + if (!roomType) { + return res.status(400).json({ + success: false, + error: 'Room type required' + }); + } + + const result = baseSystem.buildRoom(userId, roomType, level); + + res.json({ + success: true, + result + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +// Upgrade room +router.post('/upgrade/:userId', async (req, res) => { + try { + const { userId } = req.params; + const { roomType } = req.body; + + if (!roomType) { + return res.status(400).json({ + success: false, + error: 'Room type required' + }); + } + + const result = baseSystem.upgradeRoom(userId, roomType); + + res.json({ + success: true, + result + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +// Demolish room +router.post('/demolish/:userId', async (req, res) => { + try { + const { userId } = req.params; + const { roomType } = req.body; + + if (!roomType) { + return res.status(400).json({ + success: false, + error: 'Room type required' + }); + } + + const result = baseSystem.demolishRoom(userId, roomType); + + res.json({ + success: true, + result + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +// Add decoration +router.post('/decoration/add/:userId', async (req, res) => { + try { + const { userId } = req.params; + const { decorationType } = req.body; + + if (!decorationType) { + return res.status(400).json({ + success: false, + error: 'Decoration type required' + }); + } + + const result = baseSystem.addDecoration(userId, decorationType); + + res.json({ + success: true, + result + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +// Remove decoration +router.post('/decoration/remove/:userId', async (req, res) => { + try { + const { userId } = req.params; + const { decorationIndex } = req.body; + + if (decorationIndex === undefined || decorationIndex === null) { + return res.status(400).json({ + success: false, + error: 'Decoration index required' + }); + } + + const result = baseSystem.removeDecoration(userId, decorationIndex); + + res.json({ + success: true, + result + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +// Get available rooms +router.get('/rooms/available/:userId', async (req, res) => { + try { + const { userId } = req.params; + const rooms = baseSystem.getAvailableRooms(userId); + + res.json({ + success: true, + rooms + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +// Get room upgrades +router.get('/rooms/upgrades/:userId', async (req, res) => { + try { + const { userId } = req.params; + const upgrades = baseSystem.getRoomUpgrades(userId); + + res.json({ + success: true, + upgrades + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +module.exports = router; diff --git a/GameServer/routes/crafting.js b/GameServer/routes/crafting.js new file mode 100644 index 0000000..010af20 --- /dev/null +++ b/GameServer/routes/crafting.js @@ -0,0 +1,212 @@ +const express = require('express'); +const CraftingSystem = require('../systems/CraftingSystem'); + +const router = express.Router(); + +// Initialize crafting system +const craftingSystem = new CraftingSystem(); + +// Get all recipes +router.get('/recipes', async (req, res) => { + try { + const recipes = craftingSystem.getAllRecipes(); + res.json({ + success: true, + recipes + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +// Get recipe by ID +router.get('/recipes/:id', async (req, res) => { + try { + const { id } = req.params; + const recipe = craftingSystem.getRecipe(id); + + if (!recipe) { + return res.status(404).json({ + success: false, + error: 'Recipe not found' + }); + } + + res.json({ + success: true, + recipe + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +// Get recipes by type +router.get('/recipes/type/:type', async (req, res) => { + try { + const { type } = req.params; + const recipes = craftingSystem.getRecipesByType(type); + + res.json({ + success: true, + recipes + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +// Get player crafting data +router.get('/player/:userId', async (req, res) => { + try { + const { userId } = req.params; + const playerData = craftingSystem.getPlayerData(userId); + + res.json({ + success: true, + playerData + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +// Check if can craft +router.post('/can-craft/:userId/:recipeId', async (req, res) => { + try { + const { userId, recipeId } = req.params; + const { inventory } = req.body; + + if (!inventory) { + return res.status(400).json({ + success: false, + error: 'Inventory data required' + }); + } + + const canCraft = craftingSystem.canCraft(userId, recipeId, inventory); + + res.json({ + success: true, + canCraft + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +// Craft item +router.post('/craft/:userId/:recipeId', async (req, res) => { + try { + const { userId, recipeId } = req.params; + const { inventory } = req.body; + + if (!inventory) { + return res.status(400).json({ + success: false, + error: 'Inventory data required' + }); + } + + const result = await craftingSystem.craftItem(userId, recipeId, inventory); + + res.json({ + success: true, + result + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +// Learn recipe +router.post('/learn/:userId/:recipeId', async (req, res) => { + try { + const { userId, recipeId } = req.params; + const result = craftingSystem.learnRecipe(userId, recipeId); + + res.json({ + success: true, + result + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +// Get player known recipes +router.get('/known/:userId', async (req, res) => { + try { + const { userId } = req.params; + const recipes = craftingSystem.getPlayerKnownRecipes(userId); + + res.json({ + success: true, + recipes + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +// Get crafting stats +router.get('/stats/:userId', async (req, res) => { + try { + const { userId } = req.params; + const stats = craftingSystem.getCraftingStats(userId); + + res.json({ + success: true, + stats + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +// Discover recipe +router.post('/discover/:userId/:recipeId', async (req, res) => { + try { + const { userId, recipeId } = req.params; + const result = craftingSystem.discoverRecipe(userId, recipeId); + + res.json({ + success: true, + result + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +module.exports = router; diff --git a/GameServer/routes/dungeons.js b/GameServer/routes/dungeons.js new file mode 100644 index 0000000..b566b52 --- /dev/null +++ b/GameServer/routes/dungeons.js @@ -0,0 +1,266 @@ +const express = require('express'); +const DungeonSystem = require('../systems/DungeonSystem'); + +const router = express.Router(); + +// Initialize dungeon system +const dungeonSystem = new DungeonSystem(); + +// Get all dungeons +router.get('/', async (req, res) => { + try { + const dungeons = dungeonSystem.getAllDungeons(); + res.json({ + success: true, + dungeons + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +// Get dungeons by difficulty +router.get('/difficulty/:difficulty', async (req, res) => { + try { + const { difficulty } = req.params; + const dungeons = dungeonSystem.getDungeonsByDifficulty(difficulty); + + res.json({ + success: true, + dungeons + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +// Get available dungeons for player +router.get('/available/:userId', async (req, res) => { + try { + const { userId } = req.params; + const { playerLevel = 1, playerCount = 1 } = req.query; + const dungeons = dungeonSystem.getAvailableDungeons(parseInt(playerLevel), parseInt(playerCount)); + + res.json({ + success: true, + dungeons + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +// Create dungeon instance +router.post('/create/:dungeonId/:creatorId', async (req, res) => { + try { + const { dungeonId, creatorId } = req.params; + const { playerIds = [] } = req.body; + + const instance = dungeonSystem.createInstance(dungeonId, creatorId, playerIds); + + res.json({ + success: true, + instance + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +// Get dungeon instance +router.get('/instance/:instanceId', async (req, res) => { + try { + const { instanceId } = req.params; + const instance = dungeonSystem.getInstance(instanceId); + + if (!instance) { + return res.status(404).json({ + success: false, + error: 'Instance not found' + }); + } + + res.json({ + success: true, + instance + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +// Get player's current instance +router.get('/player/:userId', async (req, res) => { + try { + const { userId } = req.params; + const instance = dungeonSystem.getPlayerInstance(userId); + + if (!instance) { + return res.status(404).json({ + success: false, + error: 'No active instance found' + }); + } + + res.json({ + success: true, + instance + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +// Join dungeon instance +router.post('/join/:instanceId/:userId', async (req, res) => { + try { + const { instanceId, userId } = req.params; + const instance = dungeonSystem.joinInstance(instanceId, userId); + + res.json({ + success: true, + instance + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +// Leave dungeon instance +router.post('/leave/:userId', async (req, res) => { + try { + const { userId } = req.params; + const instance = dungeonSystem.leaveInstance(userId); + + res.json({ + success: true, + instance + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +// Start encounter +router.post('/encounter/start/:instanceId/:userId', async (req, res) => { + try { + const { instanceId, userId } = req.params; + const result = dungeonSystem.startEncounter(instanceId, userId); + + res.json({ + success: true, + result + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +// Complete encounter +router.post('/encounter/complete/:instanceId/:userId', async (req, res) => { + try { + const { instanceId, userId } = req.params; + const { result } = req.body; + + if (!result) { + return res.status(400).json({ + success: false, + error: 'Encounter result required' + }); + } + + const encounterResult = dungeonSystem.completeEncounter(instanceId, userId, result); + + res.json({ + success: true, + result: encounterResult + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +// Complete dungeon +router.post('/complete/:instanceId', async (req, res) => { + try { + const { instanceId } = req.params; + const result = dungeonSystem.completeDungeon(instanceId); + + res.json({ + success: true, + result + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +// Get dungeon statistics +router.get('/stats/:userId', async (req, res) => { + try { + const { userId } = req.params; + const stats = dungeonSystem.getDungeonStatistics(userId); + + res.json({ + success: true, + stats + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +// Cleanup expired instances +router.post('/cleanup', async (req, res) => { + try { + const cleanedCount = dungeonSystem.cleanupExpiredInstances(); + + res.json({ + success: true, + cleanedCount + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +module.exports = router; diff --git a/GameServer/routes/idle.js b/GameServer/routes/idle.js new file mode 100644 index 0000000..f6381a8 --- /dev/null +++ b/GameServer/routes/idle.js @@ -0,0 +1,282 @@ +const express = require('express'); +const IdleSystem = require('../systems/IdleSystem'); + +const router = express.Router(); + +// Initialize idle system +const idleSystem = new IdleSystem(); + +// Calculate offline progress +router.post('/progress/:userId', async (req, res) => { + try { + const { userId } = req.params; + const progress = idleSystem.calculateOfflineProgress(userId); + + res.json({ + success: true, + progress: { + offlineTime: progress.offlineTime, + offlineSeconds: progress.offlineSeconds, + rewards: progress.rewards, + productionRates: progress.productionRates + } + }); + + } catch (error) { + res.status(500).json({ + success: false, + error: 'Internal server error' + }); + } +}); + +// Apply idle rewards to player data +router.post('/rewards/apply/:userId', async (req, res) => { + try { + const { userId } = req.params; + const { playerData } = req.body; + + if (!playerData) { + return res.status(400).json({ + success: false, + error: 'Player data required' + }); + } + + const progress = idleSystem.applyIdleRewards(userId, playerData); + + res.json({ + success: true, + progress, + updatedPlayerData: playerData + }); + + } catch (error) { + res.status(500).json({ + success: false, + error: 'Internal server error' + }); + } +}); + +// Get production rates +router.get('/production/:userId', async (req, res) => { + try { + const { userId } = req.params; + const productionRates = idleSystem.getProductionRates(userId); + + res.json({ + success: true, + productionRates + }); + + } catch (error) { + res.status(500).json({ + success: false, + error: 'Internal server error' + }); + } +}); + +// Update production rates +router.post('/production/update/:userId', async (req, res) => { + try { + const { userId } = req.params; + const { rates } = req.body; + + if (!rates) { + return res.status(400).json({ + success: false, + error: 'Production rates required' + }); + } + + idleSystem.updateProductionRates(userId, rates); + const updatedRates = idleSystem.getProductionRates(userId); + + res.json({ + success: true, + productionRates: updatedRates + }); + + } catch (error) { + res.status(500).json({ + success: false, + error: 'Internal server error' + }); + } +}); + +// Update bonuses +router.post('/bonuses/update/:userId', async (req, res) => { + try { + const { userId } = req.params; + const { bonuses } = req.body; + + if (!bonuses) { + return res.status(400).json({ + success: false, + error: 'Bonuses required' + }); + } + + const updatedRates = idleSystem.updateBonuses(userId, bonuses); + + res.json({ + success: true, + productionRates: updatedRates + }); + + } catch (error) { + res.status(500).json({ + success: false, + error: 'Internal server error' + }); + } +}); + +// Get achievements +router.get('/achievements/:userId', async (req, res) => { + try { + const { userId } = req.params; + const achievements = idleSystem.getAchievements(userId); + + res.json({ + success: true, + achievements + }); + + } catch (error) { + res.status(500).json({ + success: false, + error: 'Internal server error' + }); + } +}); + +// Get idle stats +router.get('/stats/:userId', async (req, res) => { + try { + const { userId } = req.params; + const stats = idleSystem.getIdleStats(userId); + + res.json({ + success: true, + stats + }); + + } catch (error) { + res.status(500).json({ + success: false, + error: 'Internal server error' + }); + } +}); + +// Calculate income for specific time +router.post('/income/calculate/:userId', async (req, res) => { + try { + const { userId } = req.params; + const { timeInSeconds } = req.body; + + if (!timeInSeconds || timeInSeconds <= 0) { + return res.status(400).json({ + success: false, + error: 'Valid time in seconds required' + }); + } + + const income = idleSystem.calculateIncomeForTime(userId, timeInSeconds); + + res.json({ + success: true, + timeInSeconds, + income + }); + + } catch (error) { + res.status(500).json({ + success: false, + error: 'Internal server error' + }); + } +}); + +// Get time until milestone +router.get('/milestone/:userId', async (req, res) => { + try { + const { userId } = req.params; + const targetCredits = parseInt(req.query.target) || 10000; + const timeUntil = idleSystem.getTimeUntilMilestone(userId, targetCredits); + + res.json({ + success: true, + targetCredits, + timeUntil + }); + + } catch (error) { + res.status(500).json({ + success: false, + error: 'Internal server error' + }); + } +}); + +// Update last active time +router.post('/active/:userId', async (req, res) => { + try { + const { userId } = req.params; + idleSystem.updateLastActive(userId); + + res.json({ + success: true, + lastActive: new Date().toISOString() + }); + + } catch (error) { + res.status(500).json({ + success: false, + error: 'Internal server error' + }); + } +}); + +// Get global idle statistics +router.get('/global', async (req, res) => { + try { + const globalStats = idleSystem.getGlobalStats(); + + res.json({ + success: true, + globalStats + }); + + } catch (error) { + res.status(500).json({ + success: false, + error: 'Internal server error' + }); + } +}); + +// Reset player data (admin only) +router.post('/reset/:userId', async (req, res) => { + try { + const { userId } = req.params; + idleSystem.resetPlayerData(userId); + + res.json({ + success: true, + message: 'Player idle data reset successfully' + }); + + } catch (error) { + res.status(500).json({ + success: false, + error: 'Internal server error' + }); + } +}); + +module.exports = router; diff --git a/GameServer/routes/quests.js b/GameServer/routes/quests.js new file mode 100644 index 0000000..c6e7d36 --- /dev/null +++ b/GameServer/routes/quests.js @@ -0,0 +1,232 @@ +const express = require('express'); +const QuestSystem = require('../systems/QuestSystem'); + +const router = express.Router(); + +// Initialize quest system +const questSystem = new QuestSystem(); + +// Get all quests +router.get('/', async (req, res) => { + try { + const quests = questSystem.getAllQuests(); + res.json({ + success: true, + quests + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +// Get quests by type +router.get('/type/:type', async (req, res) => { + try { + const { type } = req.params; + const quests = questSystem.getQuestsByType(type); + + res.json({ + success: true, + quests + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +// Get quests by difficulty +router.get('/difficulty/:difficulty', async (req, res) => { + try { + const { difficulty } = req.params; + const quests = questSystem.getQuestsByDifficulty(difficulty); + + res.json({ + success: true, + quests + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +// Get available quests for player +router.get('/available/:userId', async (req, res) => { + try { + const { userId } = req.params; + const quests = questSystem.getAvailableQuests(userId); + + res.json({ + success: true, + quests + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +// Start quest +router.post('/start/:userId/:questId', async (req, res) => { + try { + const { userId, questId } = req.params; + const result = questSystem.startQuest(userId, questId); + + res.json({ + success: true, + result + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +// Update quest progress +router.post('/progress/:userId/:questId', async (req, res) => { + try { + const { userId, questId } = req.params; + const { progressUpdates } = req.body; + + if (!progressUpdates) { + return res.status(400).json({ + success: false, + error: 'Progress updates required' + }); + } + + const result = questSystem.updateQuestProgress(userId, questId, progressUpdates); + + res.json({ + success: true, + result + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +// Complete quest +router.post('/complete/:userId/:questId', async (req, res) => { + try { + const { userId, questId } = req.params; + const result = questSystem.completeQuest(userId, questId); + + res.json({ + success: true, + result + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +// Get player active quests +router.get('/active/:userId', async (req, res) => { + try { + const { userId } = req.params; + const quests = questSystem.getPlayerActiveQuests(userId); + + res.json({ + success: true, + quests + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +// Get player completed quests +router.get('/completed/:userId', async (req, res) => { + try { + const { userId } = req.params; + const quests = questSystem.getPlayerCompletedQuests(userId); + + res.json({ + success: true, + quests + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +// Get quest statistics +router.get('/stats/:userId', async (req, res) => { + try { + const { userId } = req.params; + const stats = questSystem.getQuestStatistics(userId); + + res.json({ + success: true, + stats + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +// Reset daily quests +router.post('/reset/daily/:userId', async (req, res) => { + try { + const { userId } = req.params; + const result = questSystem.resetDailyQuests(userId); + + res.json({ + success: true, + result + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +// Reset weekly quests +router.post('/reset/weekly/:userId', async (req, res) => { + try { + const { userId } = req.params; + const result = questSystem.resetWeeklyQuests(userId); + + res.json({ + success: true, + result + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +module.exports = router; diff --git a/GameServer/routes/ships.js b/GameServer/routes/ships.js new file mode 100644 index 0000000..c4684f7 --- /dev/null +++ b/GameServer/routes/ships.js @@ -0,0 +1,452 @@ +const express = require('express'); +const ShipSystem = require('../systems/ShipSystem'); + +const router = express.Router(); + +// Initialize ship system +const shipSystem = new ShipSystem(); + +// Get all ship templates +router.get('/templates', async (req, res) => { + try { + const templates = shipSystem.getAllShipTemplates(); + res.json({ + success: true, + templates + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +// Get ship templates by class +router.get('/templates/class/:shipClass', async (req, res) => { + try { + const { shipClass } = req.params; + const templates = shipSystem.getShipTemplatesByClass(shipClass); + + res.json({ + success: true, + templates + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +// Get ship templates by rarity +router.get('/templates/rarity/:rarity', async (req, res) => { + try { + const { rarity } = req.params; + const templates = shipSystem.getShipTemplatesByRarity(rarity); + + res.json({ + success: true, + templates + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +// Get available ships for player +router.get('/available/:userId', async (req, res) => { + try { + const { userId } = req.params; + const { playerLevel = 1 } = req.query; + const ships = shipSystem.getAvailableShips(userId, parseInt(playerLevel)); + + res.json({ + success: true, + ships + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +// Get player ships +router.get('/player/:userId', async (req, res) => { + try { + const { userId } = req.params; + const ships = shipSystem.getPlayerShips(userId); + + res.json({ + success: true, + ships: ships.map(ship => ({ + id: ship.id, + name: ship.name, + class: ship.class, + rarity: ship.rarity, + level: ship.level, + experience: ship.experience, + requiredExp: ship.requiredExp, + stats: ship.stats, + maxStats: ship.maxStats, + slots: ship.slots, + equippedItems: ship.equippedItems, + texture: ship.texture, + status: ship.status, + createdAt: ship.createdAt, + lastUsed: ship.lastUsed + })) + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +// Get specific ship +router.get('/player/:userId/:shipId', async (req, res) => { + try { + const { userId, shipId } = req.params; + const ship = shipSystem.getShip(userId, shipId); + + if (!ship) { + return res.status(404).json({ + success: false, + error: 'Ship not found' + }); + } + + res.json({ + success: true, + ship: { + id: ship.id, + name: ship.name, + class: ship.class, + rarity: ship.rarity, + level: ship.level, + experience: ship.experience, + requiredExp: ship.requiredExp, + stats: ship.stats, + maxStats: ship.maxStats, + slots: ship.slots, + equippedItems: ship.equippedItems, + texture: ship.texture, + status: ship.status, + createdAt: ship.createdAt, + lastUsed: ship.lastUsed + } + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +// Get current ship +router.get('/current/:userId', async (req, res) => { + try { + const { userId } = req.params; + const currentShip = shipSystem.getCurrentShip(userId); + + if (!currentShip) { + return res.status(404).json({ + success: false, + error: 'No current ship found' + }); + } + + res.json({ + success: true, + ship: { + id: currentShip.id, + name: currentShip.name, + class: currentShip.class, + rarity: currentShip.rarity, + level: currentShip.level, + experience: currentShip.experience, + requiredExp: currentShip.requiredExp, + stats: currentShip.stats, + maxStats: currentShip.maxStats, + slots: currentShip.slots, + equippedItems: currentShip.equippedItems, + texture: currentShip.texture, + status: currentShip.status + } + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +// Create new ship +router.post('/create/:userId', async (req, res) => { + try { + const { userId } = req.params; + const { templateId, name, level = 1 } = req.body; + + if (!templateId) { + return res.status(400).json({ + success: false, + error: 'Ship template ID required' + }); + } + + const ship = shipSystem.createShip(userId, templateId, { name, level }); + const playerShips = shipSystem.getPlayerShips(userId); + playerShips.push(ship); + + res.json({ + success: true, + ship: { + id: ship.id, + name: ship.name, + class: ship.class, + rarity: ship.rarity, + level: ship.level, + experience: ship.experience, + requiredExp: ship.requiredExp, + stats: ship.stats, + maxStats: ship.maxStats, + slots: ship.slots, + equippedItems: ship.equippedItems, + texture: ship.texture, + status: ship.status, + createdAt: ship.createdAt + } + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +// Set current ship +router.post('/current/:userId/:shipId', async (req, res) => { + try { + const { userId, shipId } = req.params; + const ship = shipSystem.setCurrentShip(userId, shipId); + + res.json({ + success: true, + ship: { + id: ship.id, + name: ship.name, + class: ship.class, + level: ship.level, + stats: ship.stats, + status: ship.status, + lastUsed: ship.lastUsed + } + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +// Add experience to ship +router.post('/experience/:userId/:shipId', async (req, res) => { + try { + const { userId, shipId } = req.params; + const { amount } = req.body; + + if (!amount || amount <= 0) { + return res.status(400).json({ + success: false, + error: 'Valid experience amount required' + }); + } + + const result = shipSystem.addExperience(userId, shipId, amount); + + res.json({ + success: true, + result: { + experienceGained: result.experienceGained, + levelsGained: result.levelsGained, + newLevel: result.newLevel, + newStats: result.newStats + } + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +// Upgrade ship +router.post('/upgrade/:userId/:shipId', async (req, res) => { + try { + const { userId, shipId } = req.params; + const { upgradeType } = req.body; + + if (!upgradeType) { + return res.status(400).json({ + success: false, + error: 'Upgrade type required' + }); + } + + const result = shipSystem.upgradeShip(userId, shipId, upgradeType); + + res.json({ + success: true, + result: { + upgradeType: result.upgradeType, + newStats: result.newStats + } + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +// Equip item to ship +router.post('/equip/:userId/:shipId', async (req, res) => { + try { + const { userId, shipId } = req.params; + const { slot, itemId } = req.body; + + if (!slot || !itemId) { + return res.status(400).json({ + success: false, + error: 'Slot and item ID required' + }); + } + + const result = shipSystem.equipItem(userId, shipId, slot, itemId); + + res.json({ + success: true, + result: { + slot: result.slot, + itemId: result.itemId, + equippedItems: result.equippedItems + } + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +// Unequip item from ship +router.post('/unequip/:userId/:shipId', async (req, res) => { + try { + const { userId, shipId } = req.params; + const { slot } = req.body; + + if (!slot) { + return res.status(400).json({ + success: false, + error: 'Slot required' + }); + } + + const result = shipSystem.unequipItem(userId, shipId, slot); + + res.json({ + success: true, + result: { + slot: result.slot, + itemId: result.itemId, + equippedItems: result.equippedItems + } + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +// Repair ship +router.post('/repair/:userId/:shipId', async (req, res) => { + try { + const { userId, shipId } = req.params; + const { amount } = req.body; + + const result = shipSystem.repairShip(userId, shipId, amount); + + res.json({ + success: true, + result: { + health: result.health, + maxHealth: result.maxHealth, + repaired: result.repaired + } + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +// Delete ship +router.delete('/:userId/:shipId', async (req, res) => { + try { + const { userId, shipId } = req.params; + const deletedShip = shipSystem.deleteShip(userId, shipId); + + res.json({ + success: true, + deletedShip: { + id: deletedShip.id, + name: deletedShip.name, + class: deletedShip.class + } + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +// Get ship statistics +router.get('/stats/:userId', async (req, res) => { + try { + const { userId } = req.params; + const stats = shipSystem.getPlayerShipStats(userId); + + res.json({ + success: true, + stats + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +module.exports = router; diff --git a/GameServer/routes/skills.js b/GameServer/routes/skills.js new file mode 100644 index 0000000..e650c0f --- /dev/null +++ b/GameServer/routes/skills.js @@ -0,0 +1,187 @@ +const express = require('express'); +const SkillSystem = require('../systems/SkillSystem'); + +const router = express.Router(); + +// Initialize skill system +const skillSystem = new SkillSystem(); + +// Get all skills +router.get('/', async (req, res) => { + try { + const skills = skillSystem.getAllSkills(); + res.json({ + success: true, + skills + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +// Get skills by category +router.get('/category/:category', async (req, res) => { + try { + const { category } = req.params; + const skills = skillSystem.getSkillsByCategory(category); + + res.json({ + success: true, + skills + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +// Get player skills +router.get('/player/:userId', async (req, res) => { + try { + const { userId } = req.params; + const skills = skillSystem.getPlayerSkills(userId); + + res.json({ + success: true, + skills + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +// Add experience to skill +router.post('/experience/:userId/:skillId', async (req, res) => { + try { + const { userId, skillId } = req.params; + const { amount } = req.body; + + if (!amount || amount <= 0) { + return res.status(400).json({ + success: false, + error: 'Valid experience amount required' + }); + } + + const result = skillSystem.addExperience(userId, skillId, amount); + + res.json({ + success: true, + result + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +// Unlock skill +router.post('/unlock/:userId/:skillId', async (req, res) => { + try { + const { userId, skillId } = req.params; + const result = skillSystem.unlockSkill(userId, skillId); + + res.json({ + success: true, + result + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +// Get skill bonuses +router.get('/bonuses/:userId', async (req, res) => { + try { + const { userId } = req.params; + const bonuses = skillSystem.getSkillBonuses(userId); + + res.json({ + success: true, + bonuses + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +// Award skill points +router.post('/points/award/:userId', async (req, res) => { + try { + const { userId } = req.params; + const { amount } = req.body; + + if (!amount || amount <= 0) { + return res.status(400).json({ + success: false, + error: 'Valid point amount required' + }); + } + + const result = skillSystem.awardSkillPoints(userId, amount); + + res.json({ + success: true, + result + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +// Allocate skill point +router.post('/points/allocate/:userId/:skillId', async (req, res) => { + try { + const { userId, skillId } = req.params; + const result = skillSystem.allocateSkillPoint(userId, skillId); + + res.json({ + success: true, + result + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +// Get skill statistics +router.get('/stats/:userId', async (req, res) => { + try { + const { userId } = req.params; + const stats = skillSystem.getSkillStatistics(userId); + + res.json({ + success: true, + stats + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +module.exports = router; diff --git a/GameServer/server.js b/GameServer/server.js index bcc08e6..64a4c11 100644 --- a/GameServer/server.js +++ b/GameServer/server.js @@ -99,6 +99,15 @@ app.get('/api/game/player/:id', (req, res) => { } }); +// Game system routes +app.use('/api/crafting', require('./routes/crafting')); +app.use('/api/quests', require('./routes/quests')); +app.use('/api/skills', require('./routes/skills')); +app.use('/api/ships', require('./routes/ships')); +app.use('/api/idle', require('./routes/idle')); +app.use('/api/dungeons', require('./routes/dungeons')); +app.use('/api/base', require('./routes/base')); + // Socket.IO handlers (based on LocalServer) io.on('connection', (socket) => { console.log('[GAME SERVER] Client connected:', socket.id); @@ -251,16 +260,19 @@ io.on('connection', (socket) => { }); // Start server -const PORT = process.env.PORT || 3001; +const PORT = process.env.PORT || 3002; async function startServer() { try { // Connect to database await connectDB(); - server.listen(PORT, () => { + server.listen(PORT, async () => { console.log(`[GAME SERVER] Game server running on port ${PORT}`); console.log(`[GAME SERVER] Socket.IO ready for multiplayer connections`); + + // Register with API server + await registerWithAPIServer(); }); } catch (error) { console.error('[GAME SERVER] Failed to start server:', error); @@ -268,6 +280,51 @@ async function startServer() { } } +// Register this GameServer with the API server +async function registerWithAPIServer() { + try { + // Force use of local API server for development + const apiServerUrl = 'http://localhost:3001'; + const gameServerUrl = 'https://dev.gameserver.galaxystrike.online'; + + const serverData = { + serverId: `devgame-server-${PORT}`, + name: 'Dev Game Server', + type: 'public', // Must be 'public' or 'private' according to model + region: 'Europe', + maxPlayers: 100, // Hardcoded to allow 100 players + currentPlayers: 0, + gameServerUrl: gameServerUrl, + owner: { + userId: 'developer', + username: 'Developer' + } + }; + + console.log(`[GAME SERVER] Registering with API server at ${apiServerUrl}`); + console.log(`[GAME SERVER] Server data:`, serverData); + + const response = await fetch(`${apiServerUrl}/api/servers/register`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(serverData) + }); + + if (response.ok) { + const result = await response.json(); + console.log(`[GAME SERVER] Successfully registered with API server:`, result); + } else { + const errorText = await response.text(); + console.error(`[GAME SERVER] Failed to register with API server: ${response.status}`); + console.error(`[GAME SERVER] Error response:`, errorText); + } + } catch (error) { + console.error('[GAME SERVER] Error registering with API server:', error); + } +} + startServer(); module.exports = { app, server, io, gameInstances, connectedClients }; diff --git a/GameServer/systems/BaseSystem.js b/GameServer/systems/BaseSystem.js new file mode 100644 index 0000000..0e15c07 --- /dev/null +++ b/GameServer/systems/BaseSystem.js @@ -0,0 +1,512 @@ +/** + * Galaxy Strike Online - Server Base System + * Manages player base building and customization + */ + +class BaseSystem { + constructor() { + this.playerBases = new Map(); // userId -> base data + + // Room types + this.roomTypes = { + command_center: { + name: 'Command Center', + description: 'Central control room for your operations', + size: 'large', + powerCost: 20, + storageBonus: 0, + buildCost: 0, + requiredLevel: 1, + maxLevel: 1, + icon: 'fa-satellite-dish' + }, + barracks: { + name: 'Barracks', + description: 'Housing for crew and allies', + size: 'medium', + powerCost: 10, + storageBonus: 100, + buildCost: 500, + requiredLevel: 2, + maxLevel: 5, + icon: 'fa-home' + }, + laboratory: { + name: 'Laboratory', + description: 'Research facility for new technologies', + size: 'medium', + powerCost: 15, + storageBonus: 50, + buildCost: 1000, + requiredLevel: 3, + maxLevel: 5, + icon: 'fa-flask' + }, + workshop: { + name: 'Workshop', + description: 'Crafting and equipment modification station', + size: 'medium', + powerCost: 12, + storageBonus: 80, + buildCost: 800, + requiredLevel: 2, + maxLevel: 5, + icon: 'fa-hammer' + }, + storage_bay: { + name: 'Storage Bay', + description: 'Additional storage for resources and items', + size: 'large', + powerCost: 5, + storageBonus: 500, + buildCost: 300, + requiredLevel: 1, + maxLevel: 10, + icon: 'fa-warehouse' + }, + power_generator: { + name: 'Power Generator', + description: 'Generates power for base operations', + size: 'small', + powerCost: -25, // Generates power + storageBonus: 0, + buildCost: 400, + requiredLevel: 2, + maxLevel: 5, + icon: 'fa-bolt' + }, + defense_turret: { + name: 'Defense Turret', + description: 'Automated defense system', + size: 'small', + powerCost: 8, + storageBonus: 0, + buildCost: 600, + requiredLevel: 4, + maxLevel: 8, + icon: 'fa-crosshairs' + }, + medical_bay: { + name: 'Medical Bay', + description: 'Healing station for crew', + size: 'medium', + powerCost: 10, + storageBonus: 30, + buildCost: 750, + requiredLevel: 3, + maxLevel: 5, + icon: 'fa-heartbeat' + }, + communication_array: { + name: 'Communication Array', + description: 'Enhances communication and scanning', + size: 'large', + powerCost: 15, + storageBonus: 20, + buildCost: 1200, + requiredLevel: 5, + maxLevel: 3, + icon: 'fa-broadcast-tower' + }, + refinery: { + name: 'Refinery', + description: 'Processes raw materials into refined resources', + size: 'large', + powerCost: 20, + storageBonus: 100, + buildCost: 2000, + requiredLevel: 6, + maxLevel: 5, + icon: 'fa-industry' + } + }; + + // Decoration types + this.decorationTypes = { + trophy_case: { + name: 'Trophy Case', + description: 'Display your achievements', + buildCost: 100, + icon: 'fa-trophy' + }, + statue: { + name: 'Hero Statue', + description: 'Commemorative statue', + buildCost: 500, + icon: 'fa-monument' + }, + garden: { + name: 'Garden', + description: 'Decorative garden area', + buildCost: 200, + icon: 'fa-tree' + }, + fountain: { + name: 'Fountain', + description: 'Decorative water feature', + buildCost: 300, + icon: 'fa-water' + }, + banner: { + name: 'Banner', + description: 'Display your faction colors', + buildCost: 150, + icon: 'fa-flag' + } + }; + } + + initializePlayerBase(userId) { + if (!this.playerBases.has(userId)) { + this.playerBases.set(userId, { + name: 'Command Center Alpha', + level: 1, + experience: 0, + experienceToNext: 500, + rooms: [ + { + type: 'command_center', + level: 1, + builtAt: new Date().toISOString(), + lastUpgraded: null + } + ], + decorations: [], + power: 100, + maxPower: 100, + storage: 1000, + maxStorage: 1000, + defense: 10, + maxDefense: 10, + crewCapacity: 5, + maxCrewCapacity: 5, + researchBonus: 0, + craftingBonus: 0, + createdAt: new Date().toISOString(), + lastActive: new Date().toISOString() + }); + } + return this.playerBases.get(userId); + } + + getPlayerBase(userId) { + return this.playerBases.get(userId) || this.initializePlayerBase(userId); + } + + addExperience(userId, amount) { + const base = this.getPlayerBase(userId); + base.experience += amount; + + let levelsGained = 0; + const oldLevel = base.level; + + // Check for level up + while (base.experience >= base.experienceToNext) { + base.experience -= base.experienceToNext; + base.level += 1; + levelsGained++; + + // Update experience needed for next level + base.experienceToNext = this.getExperienceNeeded(base.level); + + // Apply level up bonuses + this.applyLevelUpBonuses(base); + } + + base.lastActive = new Date().toISOString(); + + return { + experienceGained: amount, + levelsGained, + newLevel: base.level, + experienceToNext: base.experienceToNext + }; + } + + getExperienceNeeded(level) { + // Exponential experience curve + return Math.floor(500 * Math.pow(1.5, level - 1)); + } + + applyLevelUpBonuses(base) { + // Increase base stats on level up + base.maxPower += 20; + base.maxStorage += 200; + base.maxDefense += 5; + base.maxCrewCapacity += 2; + + // Restore to max values + base.power = Math.min(base.power, base.maxPower); + base.storage = Math.min(base.storage, base.maxStorage); + base.defense = Math.min(base.defense, base.maxDefense); + base.crewCapacity = Math.min(base.crewCapacity, base.maxCrewCapacity); + } + + buildRoom(userId, roomType, level = 1) { + const base = this.getPlayerBase(userId); + const roomTemplate = this.roomTypes[roomType]; + + if (!roomTemplate) { + throw new Error('Invalid room type'); + } + + // Check requirements + if (base.level < roomTemplate.requiredLevel) { + throw new Error('Base level too low'); + } + + // Check if room already exists + const existingRoom = base.rooms.find(room => room.type === roomType); + if (existingRoom) { + throw new Error('Room already exists'); + } + + // Check power capacity + const totalPowerCost = this.calculateTotalPowerCost(base); + if (totalPowerCost + roomTemplate.powerCost > base.maxPower) { + throw new Error('Insufficient power capacity'); + } + + // Add room + const room = { + type: roomType, + level, + builtAt: new Date().toISOString(), + lastUpgraded: null + }; + + base.rooms.push(room); + this.updateBaseStats(base); + + return { + success: true, + room, + newStats: { + power: base.power, + maxPower: base.maxPower, + storage: base.storage, + maxStorage: base.maxStorage, + defense: base.defense, + maxDefense: base.maxDefense + } + }; + } + + upgradeRoom(userId, roomType) { + const base = this.getPlayerBase(userId); + const roomTemplate = this.roomTypes[roomType]; + const room = base.rooms.find(room => room.type === roomType); + + if (!room) { + throw new Error('Room not found'); + } + + if (room.level >= roomTemplate.maxLevel) { + throw new Error('Room already at max level'); + } + + // Upgrade room + room.level += 1; + room.lastUpgraded = new Date().toISOString(); + + this.updateBaseStats(base); + + return { + success: true, + room, + newStats: { + power: base.power, + maxPower: base.maxPower, + storage: base.storage, + maxStorage: base.maxStorage, + defense: base.defense, + maxDefense: base.maxDefense + } + }; + } + + demolishRoom(userId, roomType) { + const base = this.getPlayerBase(userId); + const roomIndex = base.rooms.findIndex(room => room.type === roomType); + + if (roomIndex === -1) { + throw new Error('Room not found'); + } + + const room = base.rooms[roomIndex]; + + // Cannot demolish command center + if (room.type === 'command_center') { + throw new Error('Cannot demolish command center'); + } + + // Remove room + base.rooms.splice(roomIndex, 1); + this.updateBaseStats(base); + + return { + success: true, + demolishedRoom: room + }; + } + + addDecoration(userId, decorationType) { + const base = this.getPlayerBase(userId); + const decorationTemplate = this.decorationTypes[decorationType]; + + if (!decorationTemplate) { + throw new Error('Invalid decoration type'); + } + + const decoration = { + type: decorationType, + placedAt: new Date().toISOString() + }; + + base.decorations.push(decoration); + + return { + success: true, + decoration + }; + } + + removeDecoration(userId, decorationIndex) { + const base = this.getPlayerBase(userId); + + if (decorationIndex < 0 || decorationIndex >= base.decorations.length) { + throw new Error('Invalid decoration index'); + } + + const removedDecoration = base.decorations.splice(decorationIndex, 1)[0]; + + return { + success: true, + removedDecoration + }; + } + + calculateTotalPowerCost(base) { + let totalCost = 0; + + for (const room of base.rooms) { + const roomTemplate = this.roomTypes[room.type]; + totalCost += roomTemplate.powerCost * room.level; + } + + return totalCost; + } + + updateBaseStats(base) { + // Reset to base values + base.power = 100; + base.maxPower = 100; + base.storage = 1000; + base.maxStorage = 1000; + base.defense = 10; + base.maxDefense = 10; + base.crewCapacity = 5; + base.maxCrewCapacity = 5; + base.researchBonus = 0; + base.craftingBonus = 0; + + // Apply room bonuses + for (const room of base.rooms) { + const roomTemplate = this.roomTypes[room.type]; + + base.maxPower += roomTemplate.powerCost * room.level; + base.maxStorage += roomTemplate.storageBonus * room.level; + base.maxDefense += roomTemplate.powerCost * room.level * 0.5; // Defense rooms give defense bonus + + if (room.type === 'barracks') { + base.maxCrewCapacity += 5 * room.level; + } + + if (room.type === 'laboratory') { + base.researchBonus += 10 * room.level; + } + + if (room.type === 'workshop') { + base.craftingBonus += 5 * room.level; + } + } + + // Apply level bonuses + base.maxPower += 20 * (base.level - 1); + base.maxStorage += 200 * (base.level - 1); + base.maxDefense += 5 * (base.level - 1); + base.maxCrewCapacity += 2 * (base.level - 1); + } + + getBaseStatistics(userId) { + const base = this.getPlayerBase(userId); + + return { + name: base.name, + level: base.level, + experience: base.experience, + experienceToNext: base.experienceToNext, + rooms: base.rooms.length, + decorations: base.decorations.length, + power: base.power, + maxPower: base.maxPower, + storage: base.storage, + maxStorage: base.maxStorage, + defense: base.defense, + maxDefense: base.maxDefense, + crewCapacity: base.crewCapacity, + maxCrewCapacity: base.maxCrewCapacity, + researchBonus: base.researchBonus, + craftingBonus: base.craftingBonus, + totalPowerCost: this.calculateTotalPowerCost(base), + powerEfficiency: base.maxPower > 0 ? (base.power / base.maxPower) * 100 : 0, + storageEfficiency: base.maxStorage > 0 ? (base.storage / base.maxStorage) * 100 : 0 + }; + } + + getAvailableRooms(userId) { + const base = this.getPlayerBase(userId); + const availableRooms = []; + + for (const [roomType, template] of Object.entries(this.roomTypes)) { + // Skip if already built + if (base.rooms.find(room => room.type === roomType)) { + continue; + } + + // Check level requirement + if (base.level >= template.requiredLevel) { + availableRooms.push({ + type: roomType, + ...template, + canAfford: true // In a real implementation, check player credits + }); + } + } + + return availableRooms; + } + + getRoomUpgrades(userId) { + const base = this.getPlayerBase(userId); + const upgradableRooms = []; + + for (const room of base.rooms) { + const template = this.roomTypes[room.type]; + + if (room.level < template.maxLevel) { + upgradableRooms.push({ + type: room.type, + currentLevel: room.level, + maxLevel: template.maxLevel, + nextLevelCost: template.buildCost * room.level, + template + }); + } + } + + return upgradableRooms; + } +} + +module.exports = BaseSystem; diff --git a/GameServer/systems/CraftingSystem.js b/GameServer/systems/CraftingSystem.js new file mode 100644 index 0000000..d860ed0 --- /dev/null +++ b/GameServer/systems/CraftingSystem.js @@ -0,0 +1,277 @@ +/** + * Galaxy Strike Online - Server Crafting System + * Manages crafting recipes, materials, and item creation + */ + +class CraftingSystem { + constructor() { + this.recipes = new Map(); + this.playerCrafting = new Map(); // userId -> crafting data + this.initializeRecipes(); + } + + initializeRecipes() { + // Basic weapon recipes + this.addRecipe('basic_blaster', { + name: 'Basic Blaster', + type: 'weapon', + rarity: 'common', + description: 'A simple blaster for beginners', + materials: { + 'iron_ore': 5, + 'copper_wire': 3, + 'energy_crystal': 1 + }, + results: { + 'basic_blaster': 1, + 'experience': 25 + }, + skillRequired: 1, + craftingTime: 5000 // 5 seconds + }); + + // Advanced weapon recipes + this.addRecipe('plasma_cannon', { + name: 'Plasma Cannon', + type: 'weapon', + rarity: 'rare', + description: 'A powerful plasma-based weapon', + materials: { + 'iron_ore': 15, + 'copper_wire': 10, + 'energy_crystal': 5, + 'rare_metal': 3 + }, + results: { + 'plasma_cannon': 1, + 'experience': 100 + }, + skillRequired: 10, + craftingTime: 15000 // 15 seconds + }); + + // Armor recipes + this.addRecipe('basic_armor', { + name: 'Basic Armor', + type: 'armor', + rarity: 'common', + description: 'Light protection for beginners', + materials: { + 'iron_ore': 8, + 'copper_wire': 2 + }, + results: { + 'basic_armor': 1, + 'experience': 30 + }, + skillRequired: 2, + craftingTime: 8000 // 8 seconds + }); + + // Consumable recipes + this.addRecipe('health_kit', { + name: 'Health Kit', + type: 'consumable', + rarity: 'common', + description: 'Restores health when used', + materials: { + 'iron_ore': 2, + 'energy_crystal': 1 + }, + results: { + 'health_kit': 3, + 'experience': 10 + }, + skillRequired: 1, + craftingTime: 2000 // 2 seconds + }); + } + + addRecipe(id, recipe) { + this.recipes.set(id, { + id, + ...recipe, + createdAt: new Date().toISOString() + }); + } + + getRecipe(id) { + return this.recipes.get(id); + } + + getAllRecipes() { + return Array.from(this.recipes.values()); + } + + getRecipesByType(type) { + return Array.from(this.recipes.values()).filter(recipe => recipe.type === type); + } + + getRecipesByRarity(rarity) { + return Array.from(this.recipes.values()).filter(recipe => recipe.rarity === rarity); + } + + initializePlayerData(userId) { + if (!this.playerCrafting.has(userId)) { + this.playerCrafting.set(userId, { + skill: 1, + experience: 0, + knownRecipes: new Set(['basic_blaster', 'health_kit']), // Start with basic recipes + craftingHistory: [], + totalCrafted: 0 + }); + } + return this.playerCrafting.get(userId); + } + + getPlayerData(userId) { + return this.playerCrafting.get(userId) || this.initializePlayerData(userId); + } + + canCraft(userId, recipeId, playerInventory) { + const recipe = this.getRecipe(recipeId); + if (!recipe) { + return { canCraft: false, reason: 'Recipe not found' }; + } + + const playerData = this.getPlayerData(userId); + + // Check skill requirement + if (playerData.skill < recipe.skillRequired) { + return { canCraft: false, reason: 'Insufficient crafting skill' }; + } + + // Check materials + for (const [material, amount] of Object.entries(recipe.materials)) { + const playerAmount = playerInventory[material] || 0; + if (playerAmount < amount) { + return { canCraft: false, reason: `Insufficient ${material}` }; + } + } + + return { canCraft: true }; + } + + async craftItem(userId, recipeId, playerInventory) { + const canCraftResult = this.canCraft(userId, recipeId, playerInventory); + if (!canCraftResult.canCraft) { + throw new Error(canCraftResult.reason); + } + + const recipe = this.getRecipe(recipeId); + const playerData = this.getPlayerData(userId); + + // Remove materials from inventory + for (const [material, amount] of Object.entries(recipe.materials)) { + playerInventory[material] = (playerInventory[material] || 0) - amount; + } + + // Add results to inventory + for (const [item, amount] of Object.entries(recipe.results)) { + if (item !== 'experience') { + playerInventory[item] = (playerInventory[item] || 0) + amount; + } + } + + // Add experience and update stats + const experienceGained = recipe.results.experience || 0; + playerData.experience += experienceGained; + playerData.totalCrafted += 1; + + // Check for skill up + const oldSkill = playerData.skill; + const newSkill = this.calculateSkillLevel(playerData.experience); + playerData.skill = newSkill; + + // Record crafting history + playerData.craftingHistory.push({ + recipeId, + timestamp: new Date().toISOString(), + experienceGained, + skillUp: newSkill > oldSkill + }); + + // Keep only last 100 crafting records + if (playerData.craftingHistory.length > 100) { + playerData.craftingHistory = playerData.craftingHistory.slice(-100); + } + + return { + success: true, + recipe, + results: recipe.results, + experienceGained, + newSkill, + skillUp: newSkill > oldSkill, + playerInventory + }; + } + + calculateSkillLevel(experience) { + // Simple skill progression: 100 XP per level + return Math.floor(experience / 100) + 1; + } + + learnRecipe(userId, recipeId) { + const recipe = this.getRecipe(recipeId); + if (!recipe) { + throw new Error('Recipe not found'); + } + + const playerData = this.getPlayerData(userId); + playerData.knownRecipes.add(recipeId); + + return { + success: true, + recipeId, + recipeName: recipe.name + }; + } + + getPlayerKnownRecipes(userId) { + const playerData = this.getPlayerData(userId); + return Array.from(playerData.knownRecipes).map(recipeId => this.getRecipe(recipeId)); + } + + getCraftingStats(userId) { + const playerData = this.getPlayerData(userId); + return { + skill: playerData.skill, + experience: playerData.experience, + totalCrafted: playerData.totalCrafted, + knownRecipesCount: playerData.knownRecipes.size, + recentCrafting: playerData.craftingHistory.slice(-10) + }; + } + + discoverRecipe(userId, recipeId) { + const recipe = this.getRecipe(recipeId); + if (!recipe) { + throw new Error('Recipe not found'); + } + + const playerData = this.getPlayerData(userId); + + if (playerData.knownRecipes.has(recipeId)) { + return { success: false, reason: 'Recipe already known' }; + } + + // Chance to discover based on skill level + const discoveryChance = Math.min(0.1 + (playerData.skill * 0.05), 0.5); + const discovered = Math.random() < discoveryChance; + + if (discovered) { + playerData.knownRecipes.add(recipeId); + return { + success: true, + recipeId, + recipeName: recipe.name, + discovered: true + }; + } + + return { success: false, discovered: false }; + } +} + +module.exports = CraftingSystem; diff --git a/GameServer/systems/DungeonSystem.js b/GameServer/systems/DungeonSystem.js new file mode 100644 index 0000000..d211e46 --- /dev/null +++ b/GameServer/systems/DungeonSystem.js @@ -0,0 +1,507 @@ +/** + * Galaxy Strike Online - Server Dungeon System + * Manages dungeon instances, encounters, and rewards + */ + +class DungeonSystem { + constructor() { + this.dungeons = new Map(); + this.instances = new Map(); // instanceId -> dungeon instance + this.playerInstances = new Map(); // userId -> instanceId + this.initializeDungeons(); + } + + initializeDungeons() { + // Tutorial Dungeon + this.addDungeon('tutorial_caverns', { + name: 'Tutorial Caverns', + description: 'Learn the basics of dungeon crawling', + difficulty: 'easy', + minLevel: 1, + maxLevel: 5, + maxPlayers: 4, + estimatedTime: 15, // minutes + encounters: [ + { + type: 'combat', + name: 'Cave Bats', + enemies: ['bat', 'bat', 'bat'], + difficulty: 1 + }, + { + type: 'combat', + name: 'Cave Spider', + enemies: ['spider'], + difficulty: 2 + }, + { + type: 'boss', + name: 'Cave Guardian', + enemies: ['guardian'], + difficulty: 3 + } + ], + rewards: { + experience: { min: 50, max: 100 }, + credits: { min: 100, max: 250 }, + items: ['health_kit', 'basic_weapon'] + }, + requirements: { + level: 1 + } + }); + + // Combat Dungeon + this.addDungeon('abandoned_mines', { + name: 'Abandoned Mines', + description: 'Dangerous mines filled with hostile creatures', + difficulty: 'medium', + minLevel: 5, + maxLevel: 15, + maxPlayers: 4, + estimatedTime: 30, + encounters: [ + { + type: 'combat', + name: 'Mine Guards', + enemies: ['guard', 'guard'], + difficulty: 3 + }, + { + type: 'trap', + name: 'Explosive Trap', + difficulty: 4 + }, + { + type: 'combat', + name: 'Mine Foreman', + enemies: ['foreman'], + difficulty: 5 + }, + { + type: 'treasure', + name: 'Hidden Cache', + difficulty: 3 + }, + { + type: 'boss', + name: 'Mine Overlord', + enemies: ['overlord'], + difficulty: 6 + } + ], + rewards: { + experience: { min: 200, max: 400 }, + credits: { min: 500, max: 1000 }, + items: ['rare_weapon', 'armor_piece'] + }, + requirements: { + level: 5, + item: 'torch' + } + }); + + // Puzzle Dungeon + this.addDungeon('ancient_library', { + name: 'Ancient Library', + description: 'Solve puzzles to uncover ancient knowledge', + difficulty: 'medium', + minLevel: 8, + maxLevel: 20, + maxPlayers: 2, + estimatedTime: 45, + encounters: [ + { + type: 'puzzle', + name: 'Riddle Door', + difficulty: 4 + }, + { + type: 'combat', + name: 'Library Guardians', + enemies: ['guardian', 'guardian'], + difficulty: 4 + }, + { + type: 'puzzle', + name: 'Magic Lock', + difficulty: 5 + }, + { + type: 'treasure', + name: 'Forbidden Tome', + difficulty: 5 + }, + { + type: 'boss', + name: 'Librarian Specter', + enemies: ['specter'], + difficulty: 6 + } + ], + rewards: { + experience: { min: 300, max: 600 }, + credits: { min: 750, max: 1500 }, + items: ['spell_book', 'knowledge_scroll'] + }, + requirements: { + level: 8, + skill: 'ancient_knowledge' + } + }); + + // Raid Dungeon + this.addDungeon('dragon_lair', { + name: 'Dragon Lair', + description: 'Face the ultimate dragon challenge', + difficulty: 'hard', + minLevel: 15, + maxLevel: 30, + maxPlayers: 8, + estimatedTime: 60, + encounters: [ + { + type: 'combat', + name: 'Dragon Whelps', + enemies: ['whelp', 'whelp', 'whelp', 'whelp'], + difficulty: 7 + }, + { + type: 'combat', + name: 'Dragon Guard', + enemies: ['dragon_guard', 'dragon_guard'], + difficulty: 8 + }, + { + type: 'trap', + name: 'Dragon Breath Trap', + difficulty: 9 + }, + { + type: 'combat', + name: 'Dragon Elite', + enemies: ['dragon_elite'], + difficulty: 10 + }, + { + type: 'boss', + name: 'Ancient Dragon', + enemies: ['ancient_dragon'], + difficulty: 12 + } + ], + rewards: { + experience: { min: 1000, max: 2000 }, + credits: { min: 2500, max: 5000 }, + items: ['dragon_scale', 'legendary_weapon', 'dragon_egg'] + }, + requirements: { + level: 15, + guild: true + } + }); + } + + addDungeon(id, dungeon) { + this.dungeons.set(id, { + id, + ...dungeon, + createdAt: new Date().toISOString() + }); + } + + getDungeon(id) { + return this.dungeons.get(id); + } + + getAllDungeons() { + return Array.from(this.dungeons.values()); + } + + getDungeonsByDifficulty(difficulty) { + return Array.from(this.dungeons.values()).filter(dungeon => dungeon.difficulty === difficulty); + } + + createInstance(dungeonId, creatorId, playerIds = []) { + const dungeon = this.getDungeon(dungeonId); + if (!dungeon) { + throw new Error('Dungeon not found'); + } + + // Validate requirements + if (playerIds.length > dungeon.maxPlayers) { + throw new Error('Too many players for this dungeon'); + } + + const instanceId = `instance_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; + + const instance = { + id: instanceId, + dungeonId, + creatorId, + players: new Set([creatorId, ...playerIds]), + currentEncounter: 0, + status: 'active', + startedAt: new Date().toISOString(), + completedAt: null, + progress: {}, + rewards: null, + deaths: new Map() // userId -> death count + }; + + // Initialize progress tracking + dungeon.encounters.forEach((encounter, index) => { + instance.progress[index] = { + completed: false, + attempts: 0, + deaths: 0 + }; + }); + + this.instances.set(instanceId, instance); + + // Track player instances + for (const playerId of instance.players) { + this.playerInstances.set(playerId, instanceId); + } + + return instance; + } + + getInstance(instanceId) { + return this.instances.get(instanceId); + } + + getPlayerInstance(userId) { + const instanceId = this.playerInstances.get(userId); + return instanceId ? this.getInstance(instanceId) : null; + } + + joinInstance(instanceId, userId) { + const instance = this.getInstance(instanceId); + if (!instance) { + throw new Error('Instance not found'); + } + + if (instance.status !== 'active') { + throw new Error('Instance is not active'); + } + + const dungeon = this.getDungeon(instance.dungeonId); + if (instance.players.size >= dungeon.maxPlayers) { + throw new Error('Instance is full'); + } + + // Remove from existing instance if any + const existingInstanceId = this.playerInstances.get(userId); + if (existingInstanceId) { + this.leaveInstance(userId); + } + + // Add to new instance + instance.players.add(userId); + this.playerInstances.set(userId, instanceId); + + return instance; + } + + leaveInstance(userId) { + const instanceId = this.playerInstances.get(userId); + if (!instanceId) { + return null; + } + + const instance = this.getInstance(instanceId); + if (instance) { + instance.players.delete(userId); + + // If instance is empty, clean it up + if (instance.players.size === 0) { + this.instances.delete(instanceId); + } + } + + this.playerInstances.delete(userId); + return instance; + } + + startEncounter(instanceId, userId) { + const instance = this.getInstance(instanceId); + if (!instance) { + throw new Error('Instance not found'); + } + + if (!instance.players.has(userId)) { + throw new Error('Player not in instance'); + } + + const dungeon = this.getDungeon(instance.dungeonId); + const encounter = dungeon.encounters[instance.currentEncounter]; + + if (!encounter) { + throw new Error('No more encounters'); + } + + return { + encounter, + encounterIndex: instance.currentEncounter, + instance + }; + } + + completeEncounter(instanceId, userId, result) { + const instance = this.getInstance(instanceId); + if (!instance) { + throw new Error('Instance not found'); + } + + const dungeon = this.getDungeon(instance.dungeonId); + const encounter = dungeon.encounters[instance.currentEncounter]; + + if (!encounter) { + throw new Error('No encounter to complete'); + } + + const progress = instance.progress[instance.currentEncounter]; + progress.completed = true; + progress.attempts++; + + if (result.deaths) { + progress.deaths += result.deaths; + instance.deaths.set(userId, (instance.deaths.get(userId) || 0) + result.deaths); + } + + // Move to next encounter + instance.currentEncounter++; + + // Check if dungeon is complete + if (instance.currentEncounter >= dungeon.encounters.length) { + return this.completeDungeon(instanceId); + } + + return { + success: true, + nextEncounter: instance.currentEncounter < dungeon.encounters.length ? dungeon.encounters[instance.currentEncounter] : null, + instance + }; + } + + completeDungeon(instanceId) { + const instance = this.getInstance(instanceId); + if (!instance) { + throw new Error('Instance not found'); + } + + const dungeon = this.getDungeon(instance.dungeonId); + + // Calculate rewards + const rewards = this.calculateRewards(dungeon, instance); + + // Mark as completed + instance.status = 'completed'; + instance.completedAt = new Date().toISOString(); + instance.rewards = rewards; + + // Remove players from instance tracking + for (const playerId of instance.players) { + this.playerInstances.delete(playerId); + } + + return { + success: true, + dungeon, + rewards, + instance + }; + } + + calculateRewards(dungeon, instance) { + const rewards = { + experience: 0, + credits: 0, + items: [] + }; + + // Calculate base rewards + const expRange = dungeon.rewards.experience; + const creditRange = dungeon.rewards.credits; + + rewards.experience = Math.floor(Math.random() * (expRange.max - expRange.min + 1)) + expRange.min; + rewards.credits = Math.floor(Math.random() * (creditRange.max - creditRange.min + 1)) + creditRange.min; + + // Add death penalty + const totalDeaths = Array.from(instance.deaths.values()).reduce((sum, deaths) => sum + deaths, 0); + const deathPenalty = Math.floor(totalDeaths * 0.1); // 10% penalty per death + rewards.experience = Math.max(0, rewards.experience - deathPenalty); + rewards.credits = Math.max(0, rewards.credits - (deathPenalty * 2)); + + // Add items (chance based on performance) + const itemChance = Math.max(0.1, 0.5 - (totalDeaths * 0.1)); // Lower chance with more deaths + if (Math.random() < itemChance) { + const itemPool = dungeon.rewards.items; + if (itemPool.length > 0) { + rewards.items.push(itemPool[Math.floor(Math.random() * itemPool.length)]); + } + } + + return rewards; + } + + getAvailableDungeons(playerLevel, playerCount = 1) { + return Array.from(this.dungeons.values()).filter(dungeon => + dungeon.minLevel <= playerLevel && + dungeon.maxLevel >= playerLevel && + dungeon.maxPlayers >= playerCount + ); + } + + getDungeonStatistics(userId) { + const instances = Array.from(this.instances.values()).filter(instance => + instance.players.has(userId) + ); + + const completed = instances.filter(instance => instance.status === 'completed'); + const active = instances.filter(instance => instance.status === 'active'); + + return { + totalDungeons: instances.length, + completedDungeons: completed.length, + activeDungeons: active.length, + totalDeaths: Array.from(this.playerInstances.values()).length, + recentCompletions: completed.slice(-5).map(instance => ({ + dungeonId: instance.dungeonId, + completedAt: instance.completedAt, + rewards: instance.rewards + })) + }; + } + + cleanupExpiredInstances() { + const now = Date.now(); + const expiredInstances = []; + + for (const [instanceId, instance] of this.instances) { + const age = now - new Date(instance.startedAt).getTime(); + const maxAge = 2 * 60 * 60 * 1000; // 2 hours + + if (age > maxAge && instance.status === 'active') { + expiredInstances.push(instanceId); + } + } + + // Clean up expired instances + for (const instanceId of expiredInstances) { + const instance = this.instances.get(instanceId); + if (instance) { + for (const playerId of instance.players) { + this.playerInstances.delete(playerId); + } + this.instances.delete(instanceId); + } + } + + return expiredInstances.length; + } +} + +module.exports = DungeonSystem; diff --git a/GameServer/systems/IdleSystem.js b/GameServer/systems/IdleSystem.js new file mode 100644 index 0000000..3f2343d --- /dev/null +++ b/GameServer/systems/IdleSystem.js @@ -0,0 +1,272 @@ +/** + * Galaxy Strike Online - Server Idle System + * Manages offline progression and idle mechanics + */ + +class IdleSystem { + constructor() { + // Idle settings + this.maxOfflineTime = 7 * 24 * 60 * 60 * 1000; // 7 days in milliseconds + this.playerLastActive = new Map(); // userId -> last active timestamp + this.playerProductionRates = new Map(); // userId -> production rates + + // Default production rates + this.defaultProductionRates = { + credits: 10, // credits per second (increased for better gameplay) + experience: 1, // experience per second (increased for better progression) + energy: 0.5 // energy regeneration per second + }; + + // Idle rewards + this.offlineRewards = { + credits: 0, + experience: 0, + energy: 0, + items: [] + }; + + // Idle bonuses + this.bonuses = { + premium: 1.0, + guild: 1.0, + research: 1.0 + }; + + // Idle achievements + this.achievements = { + totalOfflineTime: 0, + maxOfflineSession: 0, + totalIdleCredits: 0, + totalIdleExperience: 0 + }; + } + + initializePlayerData(userId) { + if (!this.playerLastActive.has(userId)) { + this.playerLastActive.set(userId, Date.now()); + this.playerProductionRates.set(userId, { ...this.defaultProductionRates }); + this.playerAchievements.set(userId, { + totalOfflineTime: 0, + maxOfflineSession: 0, + totalIdleCredits: 0, + totalIdleExperience: 0 + }); + } + } + + updateLastActive(userId) { + this.playerLastActive.set(userId, Date.now()); + } + + updateProductionRates(userId, rates) { + this.playerProductionRates.set(userId, { + ...this.defaultProductionRates, + ...rates + }); + } + + calculateOfflineProgress(userId) { + this.initializePlayerData(userId); + + const lastActive = this.playerLastActive.get(userId); + const currentTime = Date.now(); + const offlineTime = currentTime - lastActive; + + // Cap offline time to maximum + const cappedOfflineTime = Math.min(offlineTime, this.maxOfflineTime); + + if (cappedOfflineTime <= 0) { + return { + offlineTime: 0, + rewards: { + credits: 0, + experience: 0, + energy: 0, + items: [] + } + }; + } + + const productionRates = this.playerProductionRates.get(userId); + const achievements = this.playerAchievements.get(userId); + + // Calculate offline rewards + const offlineSeconds = Math.floor(cappedOfflineTime / 1000); + const rewards = { + credits: Math.floor(productionRates.credits * offlineSeconds), + experience: Math.floor(productionRates.experience * offlineSeconds), + energy: Math.floor(productionRates.energy * offlineSeconds), + items: [] + }; + + // Update achievements + achievements.totalOfflineTime += cappedOfflineTime; + achievements.maxOfflineSession = Math.max(achievements.maxOfflineSession, cappedOfflineTime); + achievements.totalIdleCredits += rewards.credits; + achievements.totalIdleExperience += rewards.experience; + + // Update last active time + this.playerLastActive.set(userId, currentTime); + + return { + offlineTime: cappedOfflineTime, + offlineSeconds, + rewards, + productionRates + }; + } + + applyIdleRewards(userId, playerData) { + const progress = this.calculateOfflineProgress(userId); + + if (progress.offlineTime > 0) { + // Apply rewards to player data + if (playerData.stats) { + playerData.stats.credits = (playerData.stats.credits || 0) + progress.rewards.credits; + playerData.stats.experience = (playerData.stats.experience || 0) + progress.rewards.experience; + } + + if (playerData.resources) { + playerData.resources.energy = (playerData.resources.energy || 0) + progress.rewards.energy; + } + + // Add items if any + if (progress.rewards.items && progress.rewards.items.length > 0) { + if (!playerData.inventory) { + playerData.inventory = []; + } + playerData.inventory.push(...progress.rewards.items); + } + } + + return progress; + } + + getProductionRates(userId) { + this.initializePlayerData(userId); + return this.playerProductionRates.get(userId); + } + + updateBonuses(userId, bonuses) { + this.initializePlayerData(userId); + const currentRates = this.playerProductionRates.get(userId); + + // Apply bonuses to production rates + const bonusMultiplier = + (bonuses.premium || this.defaultBonuses.premium) * + (bonuses.guild || this.defaultBonuses.guild) * + (bonuses.research || this.defaultBonuses.research); + + const updatedRates = {}; + for (const [resource, baseRate] of Object.entries(this.defaultProductionRates)) { + updatedRates[resource] = Math.floor(baseRate * bonusMultiplier); + } + + this.playerProductionRates.set(userId, updatedRates); + return updatedRates; + } + + getAchievements(userId) { + this.initializePlayerData(userId); + return this.playerAchievements.get(userId); + } + + getIdleStats(userId) { + this.initializePlayerData(userId); + const achievements = this.playerAchievements.get(userId); + const productionRates = this.playerProductionRates.get(userId); + + return { + lastActive: this.playerLastActive.get(userId), + productionRates, + achievements, + maxOfflineTime: this.maxOfflineTime, + currentOfflineTime: Date.now() - this.playerLastActive.get(userId) + }; + } + + // Calculate idle income for a specific time period + calculateIncomeForTime(userId, timeInSeconds) { + this.initializePlayerData(userId); + const productionRates = this.playerProductionRates.get(userId); + + return { + credits: Math.floor(productionRates.credits * timeInSeconds), + experience: Math.floor(productionRates.experience * timeInSeconds), + energy: Math.floor(productionRates.energy * timeInSeconds) + }; + } + + // Get time until next milestone + getTimeUntilMilestone(userId, targetCredits = 10000) { + this.initializePlayerData(userId); + const productionRates = this.playerProductionRates.get(userId); + const currentOfflineTime = Date.now() - this.playerLastActive.get(userId); + + if (productionRates.credits <= 0) { + return { seconds: Infinity, hours: Infinity }; + } + + const creditsPerSecond = productionRates.credits; + const secondsNeeded = Math.ceil(targetCredits / creditsPerSecond); + + return { + seconds: secondsNeeded, + minutes: Math.ceil(secondsNeeded / 60), + hours: Math.ceil(secondsNeeded / 3600) + }; + } + + // Process idle rewards for multiple users (batch processing) + processBatchIdleRewards(userIds) { + const results = new Map(); + + for (const userId of userIds) { + try { + const progress = this.calculateOfflineProgress(userId); + results.set(userId, { + success: true, + progress + }); + } catch (error) { + results.set(userId, { + success: false, + error: error.message + }); + } + } + + return results; + } + + // Reset idle data for a user + resetPlayerData(userId) { + this.playerLastActive.delete(userId); + this.playerProductionRates.delete(userId); + this.playerAchievements.delete(userId); + } + + // Get global idle statistics + getGlobalStats() { + const totalPlayers = this.playerLastActive.size; + let totalOfflineTime = 0; + let totalIdleCredits = 0; + let totalIdleExperience = 0; + + for (const achievements of this.playerAchievements.values()) { + totalOfflineTime += achievements.totalOfflineTime; + totalIdleCredits += achievements.totalIdleCredits; + totalIdleExperience += achievements.totalIdleExperience; + } + + return { + totalPlayers, + totalOfflineTime, + totalIdleCredits, + totalIdleExperience, + averageOfflineTime: totalPlayers > 0 ? totalOfflineTime / totalPlayers : 0 + }; + } +} + +module.exports = IdleSystem; diff --git a/GameServer/systems/QuestSystem.js b/GameServer/systems/QuestSystem.js new file mode 100644 index 0000000..f976ca6 --- /dev/null +++ b/GameServer/systems/QuestSystem.js @@ -0,0 +1,439 @@ +/** + * Galaxy Strike Online - Server Quest System + * Manages quests, progress tracking, and rewards + */ + +class QuestSystem { + constructor() { + this.quests = new Map(); + this.playerQuests = new Map(); // userId -> quest data + this.initializeQuests(); + } + + initializeQuests() { + // Tutorial quests + this.addQuest('tutorial_first_battle', { + name: 'First Battle', + description: 'Complete your first battle to learn the basics', + type: 'tutorial', + difficulty: 'easy', + requirements: { + battlesWon: 1 + }, + rewards: { + experience: 100, + credits: 500, + items: ['health_kit', 'energy_pack'] + }, + prerequisites: [], + repeatable: false, + timeLimit: null + }); + + // Combat quests + this.addQuest('warrior_path', { + name: 'Warrior Path', + description: 'Win 10 battles to prove your combat skills', + type: 'combat', + difficulty: 'medium', + requirements: { + battlesWon: 10, + enemiesDefeated: 50 + }, + rewards: { + experience: 500, + credits: 2000, + items: ['basic_armor'] + }, + prerequisites: ['tutorial_first_battle'], + repeatable: false, + timeLimit: null + }); + + // Crafting quests + this.addQuest('novice_crafter', { + name: 'Novice Crafter', + description: 'Craft 5 items to learn the basics of crafting', + type: 'crafting', + difficulty: 'easy', + requirements: { + itemsCrafted: 5 + }, + rewards: { + experience: 200, + credits: 1000, + craftingExperience: 100 + }, + prerequisites: [], + repeatable: false, + timeLimit: null + }); + + // Exploration quests + this.addQuest('explorer_spirit', { + name: 'Explorer Spirit', + description: 'Discover 5 different locations in the galaxy', + type: 'exploration', + difficulty: 'medium', + requirements: { + locationsDiscovered: 5 + }, + rewards: { + experience: 300, + credits: 1500, + items: ['navigation_device'] + }, + prerequisites: ['tutorial_first_battle'], + repeatable: false, + timeLimit: null + }); + + // Daily quests + this.addQuest('daily_bounty', { + name: 'Daily Bounty', + description: 'Complete 3 battles today', + type: 'daily', + difficulty: 'easy', + requirements: { + battlesWon: 3 + }, + rewards: { + experience: 150, + credits: 750 + }, + prerequisites: [], + repeatable: true, + timeLimit: 86400000 // 24 hours + }); + + // Weekly quests + this.addQuest('weekly_champion', { + name: 'Weekly Champion', + description: 'Win 20 battles this week', + type: 'weekly', + difficulty: 'hard', + requirements: { + battlesWon: 20 + }, + rewards: { + experience: 1000, + credits: 5000, + items: ['rare_weapon_part'] + }, + prerequisites: [], + repeatable: true, + timeLimit: 604800000 // 7 days + }); + } + + addQuest(id, quest) { + this.quests.set(id, { + id, + ...quest, + createdAt: new Date().toISOString() + }); + } + + getQuest(id) { + return this.quests.get(id); + } + + getAllQuests() { + return Array.from(this.quests.values()); + } + + getQuestsByType(type) { + return Array.from(this.quests.values()).filter(quest => quest.type === type); + } + + getQuestsByDifficulty(difficulty) { + return Array.from(this.quests.values()).filter(quest => quest.difficulty === difficulty); + } + + initializePlayerData(userId) { + if (!this.playerQuests.has(userId)) { + this.playerQuests.set(userId, { + activeQuests: new Map(), + completedQuests: new Map(), + questHistory: [], + totalQuestsCompleted: 0, + dailyQuestsCompleted: 0, + weeklyQuestsCompleted: 0 + }); + } + return this.playerQuests.get(userId); + } + + getPlayerData(userId) { + return this.playerQuests.get(userId) || this.initializePlayerData(userId); + } + + getAvailableQuests(userId) { + const playerData = this.getPlayerData(userId); + const availableQuests = []; + + for (const [questId, quest] of this.quests) { + // Skip if already active or completed (for non-repeatable quests) + if (playerData.activeQuests.has(questId)) continue; + if (playerData.completedQuests.has(questId) && !quest.repeatable) continue; + + // Check prerequisites + const prerequisitesMet = quest.prerequisites.every(prereqId => + playerData.completedQuests.has(prereqId) + ); + + if (prerequisitesMet) { + availableQuests.push({ + ...quest, + status: playerData.activeQuests.has(questId) ? 'active' : 'available' + }); + } + } + + return availableQuests; + } + + startQuest(userId, questId) { + const quest = this.getQuest(questId); + if (!quest) { + throw new Error('Quest not found'); + } + + const playerData = this.getPlayerData(userId); + + // Check if quest is already active + if (playerData.activeQuests.has(questId)) { + throw new Error('Quest already active'); + } + + // Check if quest is completed and not repeatable + if (playerData.completedQuests.has(questId) && !quest.repeatable) { + throw new Error('Quest already completed'); + } + + // Check prerequisites + const prerequisitesMet = quest.prerequisites.every(prereqId => + playerData.completedQuests.has(prereqId) + ); + + if (!prerequisitesMet) { + throw new Error('Prerequisites not met'); + } + + // Start the quest + const questProgress = { + questId, + startedAt: new Date().toISOString(), + progress: {}, + completed: false, + expiredAt: quest.timeLimit ? new Date(Date.now() + quest.timeLimit).toISOString() : null + }; + + // Initialize progress tracking + for (const requirement of Object.keys(quest.requirements)) { + questProgress.progress[requirement] = 0; + } + + playerData.activeQuests.set(questId, questProgress); + + return { + success: true, + quest, + progress: questProgress + }; + } + + updateQuestProgress(userId, questId, progressUpdates) { + const playerData = this.getPlayerData(userId); + const activeQuest = playerData.activeQuests.get(questId); + + if (!activeQuest) { + throw new Error('Quest not active'); + } + + // Check if quest is expired + if (activeQuest.expiredAt && new Date() > new Date(activeQuest.expiredAt)) { + playerData.activeQuests.delete(questId); + throw new Error('Quest expired'); + } + + const quest = this.getQuest(questId); + let updated = false; + + // Update progress + for (const [requirement, amount] of Object.entries(progressUpdates)) { + if (quest.requirements[requirement] !== undefined) { + activeQuest.progress[requirement] = Math.min( + activeQuest.progress[requirement] + amount, + quest.requirements[requirement] + ); + updated = true; + } + } + + // Check if quest is completed + const isCompleted = Object.entries(quest.requirements).every( + ([requirement, requiredAmount]) => + activeQuest.progress[requirement] >= requiredAmount + ); + + if (isCompleted && !activeQuest.completed) { + activeQuest.completed = true; + activeQuest.completedAt = new Date().toISOString(); + + return { + success: true, + questCompleted: true, + quest, + progress: activeQuest.progress + }; + } + + return { + success: true, + questCompleted: false, + quest, + progress: activeQuest.progress, + updated + }; + } + + completeQuest(userId, questId) { + const playerData = this.getPlayerData(userId); + const activeQuest = playerData.activeQuests.get(questId); + + if (!activeQuest) { + throw new Error('Quest not active'); + } + + if (!activeQuest.completed) { + throw new Error('Quest requirements not met'); + } + + const quest = this.getQuest(questId); + + // Move to completed quests + playerData.completedQuests.set(questId, { + ...activeQuest, + quest, + completedAt: new Date().toISOString() + }); + + // Remove from active quests + playerData.activeQuests.delete(questId); + + // Update statistics + playerData.totalQuestsCompleted += 1; + + if (quest.type === 'daily') { + playerData.dailyQuestsCompleted += 1; + } else if (quest.type === 'weekly') { + playerData.weeklyQuestsCompleted += 1; + } + + // Add to history + playerData.questHistory.push({ + questId, + questName: quest.name, + completedAt: new Date().toISOString(), + rewards: quest.rewards + }); + + // Keep only last 100 quest records + if (playerData.questHistory.length > 100) { + playerData.questHistory = playerData.questHistory.slice(-100); + } + + return { + success: true, + quest, + rewards: quest.rewards, + statistics: { + totalCompleted: playerData.totalQuestsCompleted, + dailyCompleted: playerData.dailyQuestsCompleted, + weeklyCompleted: playerData.weeklyQuestsCompleted + } + }; + } + + getPlayerActiveQuests(userId) { + const playerData = this.getPlayerData(userId); + const activeQuests = []; + + for (const [questId, progress] of playerData.activeQuests) { + const quest = this.getQuest(questId); + activeQuests.push({ + ...quest, + progress: progress.progress, + startedAt: progress.startedAt, + expiredAt: progress.expiredAt + }); + } + + return activeQuests; + } + + getPlayerCompletedQuests(userId) { + const playerData = this.getPlayerData(userId); + const completedQuests = []; + + for (const [questId, completion] of playerData.completedQuests) { + completedQuests.push({ + ...completion.quest, + completedAt: completion.completedAt, + progress: completion.progress + }); + } + + return completedQuests; + } + + getQuestStatistics(userId) { + const playerData = this.getPlayerData(userId); + const availableQuests = this.getAvailableQuests(userId); + + return { + activeQuests: playerData.activeQuests.size, + completedQuests: playerData.completedQuests.size, + availableQuests: availableQuests.length, + totalCompleted: playerData.totalQuestsCompleted, + dailyCompleted: playerData.dailyQuestsCompleted, + weeklyCompleted: playerData.weeklyQuestsCompleted, + recentHistory: playerData.questHistory.slice(-10) + }; + } + + resetDailyQuests(userId) { + const playerData = this.getPlayerData(userId); + + // Remove completed daily quests + for (const [questId, quest] of this.quests) { + if (quest.type === 'daily') { + playerData.completedQuests.delete(questId); + playerData.activeQuests.delete(questId); + } + } + + playerData.dailyQuestsCompleted = 0; + + return { success: true, message: 'Daily quests reset' }; + } + + resetWeeklyQuests(userId) { + const playerData = this.getPlayerData(userId); + + // Remove completed weekly quests + for (const [questId, quest] of this.quests) { + if (quest.type === 'weekly') { + playerData.completedQuests.delete(questId); + playerData.activeQuests.delete(questId); + } + } + + playerData.weeklyQuestsCompleted = 0; + + return { success: true, message: 'Weekly quests reset' }; + } +} + +module.exports = QuestSystem; diff --git a/GameServer/systems/ShipSystem.js b/GameServer/systems/ShipSystem.js new file mode 100644 index 0000000..46570dd --- /dev/null +++ b/GameServer/systems/ShipSystem.js @@ -0,0 +1,488 @@ +/** + * Galaxy Strike Online - Server Ship System + * Manages ship data, upgrades, and combat mechanics + */ + +class ShipSystem { + constructor() { + this.shipTemplates = new Map(); + this.playerShips = new Map(); // userId -> array of ships + this.initializeShipTemplates(); + } + + initializeShipTemplates() { + // Starter Ships + this.addShipTemplate('starter_cruiser', { + name: 'Starter Cruiser', + class: 'Cruiser', + description: 'A reliable cruiser for beginners', + rarity: 'Common', + baseStats: { + health: 100, + maxHealth: 100, + attack: 10, + defense: 5, + speed: 10, + energy: 50, + maxEnergy: 50 + }, + slots: { + weapon: 2, + armor: 1, + module: 1 + }, + requirements: { + level: 1, + credits: 0 + }, + experience: 0, + requiredExp: 100, + texture: 'assets/textures/ships/starter_cruiser.png' + }); + + this.addShipTemplate('light_fighter', { + name: 'Light Fighter', + class: 'Fighter', + description: 'Fast and agile fighter ship', + rarity: 'Common', + baseStats: { + health: 80, + maxHealth: 80, + attack: 15, + defense: 3, + speed: 15, + energy: 40, + maxEnergy: 40 + }, + slots: { + weapon: 3, + armor: 0, + module: 2 + }, + requirements: { + level: 3, + credits: 1000 + }, + experience: 0, + requiredExp: 150, + texture: 'assets/textures/ships/light_fighter.png' + }); + + this.addShipTemplate('heavy_bomber', { + name: 'Heavy Bomber', + class: 'Bomber', + description: 'Slow but powerful bomber', + rarity: 'Uncommon', + baseStats: { + health: 150, + maxHealth: 150, + attack: 25, + defense: 10, + speed: 5, + energy: 60, + maxEnergy: 60 + }, + slots: { + weapon: 4, + armor: 2, + module: 1 + }, + requirements: { + level: 5, + credits: 5000 + }, + experience: 0, + requiredExp: 200, + texture: 'assets/textures/ships/heavy_bomber.png' + }); + + this.addShipTemplate('exploration_vessel', { + name: 'Exploration Vessel', + class: 'Explorer', + description: 'Versatile ship for exploration missions', + rarity: 'Rare', + baseStats: { + health: 120, + maxHealth: 120, + attack: 12, + defense: 8, + speed: 12, + energy: 80, + maxEnergy: 80 + }, + slots: { + weapon: 2, + armor: 1, + module: 3 + }, + requirements: { + level: 8, + credits: 15000 + }, + experience: 0, + requiredExp: 300, + texture: 'assets/textures/ships/exploration_vessel.png' + }); + + this.addShipTemplate('battle_cruiser', { + name: 'Battle Cruiser', + class: 'Cruiser', + description: 'Heavy combat cruiser', + rarity: 'Rare', + baseStats: { + health: 200, + maxHealth: 200, + attack: 30, + defense: 15, + speed: 8, + energy: 70, + maxEnergy: 70 + }, + slots: { + weapon: 5, + armor: 3, + module: 2 + }, + requirements: { + level: 12, + credits: 50000 + }, + experience: 0, + requiredExp: 500, + texture: 'assets/textures/ships/battle_cruiser.png' + }); + + this.addShipTemplate('flagship', { + name: 'Flagship', + class: 'Capital', + description: 'Ultimate command vessel', + rarity: 'Legendary', + baseStats: { + health: 500, + maxHealth: 500, + attack: 50, + defense: 30, + speed: 6, + energy: 150, + maxEnergy: 150 + }, + slots: { + weapon: 8, + armor: 5, + module: 4 + }, + requirements: { + level: 20, + credits: 500000 + }, + experience: 0, + requiredExp: 1000, + texture: 'assets/textures/ships/flagship.png' + }); + } + + addShipTemplate(id, template) { + this.shipTemplates.set(id, { + id, + ...template, + createdAt: new Date().toISOString() + }); + } + + getShipTemplate(id) { + return this.shipTemplates.get(id); + } + + getShipTemplatesByClass(shipClass) { + return Array.from(this.shipTemplates.values()).filter(template => template.class === shipClass); + } + + getShipTemplatesByRarity(rarity) { + return Array.from(this.shipTemplates.values()).filter(template => template.rarity === rarity); + } + + getAllShipTemplates() { + return Array.from(this.shipTemplates.values()); + } + + initializePlayerShips(userId) { + if (!this.playerShips.has(userId)) { + this.playerShips.set(userId, []); + + // Give starter ship to new players + const starterShip = this.createShip(userId, 'starter_cruiser'); + if (starterShip) { + this.playerShips.get(userId).push(starterShip); + } + } + return this.playerShips.get(userId); + } + + createShip(userId, templateId, customizations = {}) { + const template = this.shipTemplates.get(templateId); + if (!template) { + throw new Error(`Ship template not found: ${templateId}`); + } + + const ship = { + id: `ship_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, + userId, + templateId, + name: customizations.name || template.name, + class: template.class, + rarity: template.rarity, + level: customizations.level || 1, + experience: 0, + requiredExp: template.requiredExp, + stats: { ...template.baseStats }, + maxStats: { ...template.baseStats }, + slots: { ...template.slots }, + equippedItems: {}, + texture: template.texture, + status: 'active', + createdAt: new Date().toISOString(), + lastUsed: new Date().toISOString() + }; + + return ship; + } + + getPlayerShips(userId) { + return this.playerShips.get(userId) || this.initializePlayerShips(userId); + } + + getShip(userId, shipId) { + const ships = this.getPlayerShips(userId); + return ships.find(ship => ship.id === shipId); + } + + getCurrentShip(userId) { + const ships = this.getPlayerShips(userId); + return ships.find(ship => ship.status === 'active' && ship.lastUsed === Math.max(...ships.map(s => new Date(s.lastUsed).getTime()))); + } + + setCurrentShip(userId, shipId) { + const ships = this.getPlayerShips(userId); + const ship = ships.find(s => s.id === shipId); + + if (!ship) { + throw new Error('Ship not found'); + } + + // Update last used time for all ships + ships.forEach(s => { + if (s.id === shipId) { + s.status = 'active'; + s.lastUsed = new Date().toISOString(); + } else { + s.status = 'inactive'; + } + }); + + return ship; + } + + addExperience(userId, shipId, amount) { + const ship = this.getShip(userId, shipId); + if (!ship) { + throw new Error('Ship not found'); + } + + ship.experience += amount; + + // Check for level up + let levelsGained = 0; + while (ship.experience >= ship.requiredExp) { + ship.experience -= ship.requiredExp; + ship.level += 1; + levelsGained++; + + // Increase stats on level up + this.levelUpShip(ship); + + // Update required experience for next level + ship.requiredExp = this.getExperienceNeeded(ship.level); + } + + return { + experienceGained: amount, + levelsGained, + newLevel: ship.level, + newStats: ship.stats + }; + } + + levelUpShip(ship) { + const statIncrease = 1.1; // 10% increase per level + + for (const [stat, value] of Object.entries(ship.stats)) { + if (typeof value === 'number') { + ship.stats[stat] = Math.floor(value * statIncrease); + ship.maxStats[stat] = Math.floor(ship.maxStats[stat] * statIncrease); + } + } + } + + getExperienceNeeded(level) { + // Exponential experience curve + return Math.floor(100 * Math.pow(1.5, level - 1)); + } + + upgradeShip(userId, shipId, upgradeType) { + const ship = this.getShip(userId, shipId); + if (!ship) { + throw new Error('Ship not found'); + } + + switch (upgradeType) { + case 'health': + ship.stats.health = Math.floor(ship.stats.health * 1.2); + ship.stats.maxHealth = Math.floor(ship.stats.maxHealth * 1.2); + break; + case 'attack': + ship.stats.attack = Math.floor(ship.stats.attack * 1.15); + break; + case 'defense': + ship.stats.defense = Math.floor(ship.stats.defense * 1.15); + break; + case 'speed': + ship.stats.speed = Math.floor(ship.stats.speed * 1.1); + break; + case 'energy': + ship.stats.energy = Math.floor(ship.stats.energy * 1.2); + ship.stats.maxEnergy = Math.floor(ship.stats.maxEnergy * 1.2); + break; + default: + throw new Error('Invalid upgrade type'); + } + + return { + upgradeType, + newStats: ship.stats + }; + } + + equipItem(userId, shipId, slot, itemId) { + const ship = this.getShip(userId, shipId); + if (!ship) { + throw new Error('Ship not found'); + } + + if (!ship.slots[slot]) { + throw new Error('Invalid slot type'); + } + + ship.equippedItems[slot] = itemId; + + return { + slot, + itemId, + equippedItems: ship.equippedItems + }; + } + + unequipItem(userId, shipId, slot) { + const ship = this.getShip(userId, shipId); + if (!ship) { + throw new Error('Ship not found'); + } + + const itemId = ship.equippedItems[slot]; + delete ship.equippedItems[slot]; + + return { + slot, + itemId, + equippedItems: ship.equippedItems + }; + } + + repairShip(userId, shipId, amount = null) { + const ship = this.getShip(userId, shipId); + if (!ship) { + throw new Error('Ship not found'); + } + + if (amount === null) { + // Full repair + ship.stats.health = ship.stats.maxHealth; + } else { + // Partial repair + ship.stats.health = Math.min(ship.stats.health + amount, ship.stats.maxHealth); + } + + return { + health: ship.stats.health, + maxHealth: ship.stats.maxHealth, + repaired: amount || (ship.stats.maxHealth - ship.stats.health) + }; + } + + deleteShip(userId, shipId) { + const ships = this.getPlayerShips(userId); + const shipIndex = ships.findIndex(ship => ship.id === shipId); + + if (shipIndex === -1) { + throw new Error('Ship not found'); + } + + const deletedShip = ships.splice(shipIndex, 1)[0]; + + // If this was the current ship, set a new current ship + if (deletedShip.status === 'active' && ships.length > 0) { + ships[0].status = 'active'; + ships[0].lastUsed = new Date().toISOString(); + } + + return deletedShip; + } + + getShipClasses() { + const classes = new Set(); + for (const template of this.shipTemplates.values()) { + classes.add(template.class); + } + return Array.from(classes).sort(); + } + + getShipRarities() { + const rarities = new Set(); + for (const template of this.shipTemplates.values()) { + rarities.add(template.rarity); + } + return Array.from(rarities).sort(); + } + + getAvailableShips(userId, playerLevel = 1) { + return Array.from(this.shipTemplates.values()) + .filter(template => template.requirements.level <= playerLevel) + .map(template => ({ + id: template.id, + name: template.name, + class: template.class, + rarity: template.rarity, + description: template.description, + requirements: template.requirements, + baseStats: template.baseStats, + slots: template.slots, + texture: template.texture + })); + } + + getPlayerShipStats(userId) { + const ships = this.getPlayerShips(userId); + + return { + totalShips: ships.length, + activeShip: this.getCurrentShip(userId), + shipClasses: ships.reduce((acc, ship) => { + acc[ship.class] = (acc[ship.class] || 0) + 1; + return acc; + }, {}), + averageLevel: ships.length > 0 ? Math.floor(ships.reduce((sum, ship) => sum + ship.level, 0) / ships.length) : 0, + totalExperience: ships.reduce((sum, ship) => sum + ship.experience, 0) + }; + } +} + +module.exports = ShipSystem; diff --git a/GameServer/systems/SkillSystem.js b/GameServer/systems/SkillSystem.js new file mode 100644 index 0000000..dc25620 --- /dev/null +++ b/GameServer/systems/SkillSystem.js @@ -0,0 +1,397 @@ +/** + * Galaxy Strike Online - Server Skill System + * Manages skill progression, experience, and abilities + */ + +class SkillSystem { + constructor() { + this.skills = new Map(); + this.playerSkills = new Map(); // userId -> skill data + this.initializeSkills(); + } + + initializeSkills() { + // Combat skills + this.addSkill('weapons_mastery', { + name: 'Weapons Mastery', + description: 'Increases damage and accuracy with all weapons', + category: 'combat', + maxLevel: 100, + experiencePerLevel: 1000, + bonuses: { + damage: 2, // +2% per level + accuracy: 1, // +1% per level + criticalChance: 0.5 // +0.5% per level + } + }); + + this.addSkill('defense_training', { + name: 'Defense Training', + description: 'Improves damage reduction and resistance', + category: 'combat', + maxLevel: 100, + experiencePerLevel: 1000, + bonuses: { + damageReduction: 1.5, // +1.5% per level + resistance: 1, // +1% per level + health: 5 // +5 HP per level + } + }); + + this.addSkill('speed_reflexes', { + name: 'Speed & Reflexes', + description: 'Enhances movement speed and reaction time', + category: 'combat', + maxLevel: 100, + experiencePerLevel: 1000, + bonuses: { + speed: 2, // +2% per level + dodgeChance: 0.8, // +0.8% per level + initiative: 1 // +1 per level + } + }); + + // Crafting skills + this.addSkill('weapons_crafting', { + name: 'Weapons Crafting', + description: 'Ability to craft and improve weapons', + category: 'crafting', + maxLevel: 100, + experiencePerLevel: 800, + bonuses: { + craftingSpeed: 2, // +2% per level + craftingSuccess: 1, // +1% per level + rareChance: 0.5 // +0.5% per level + } + }); + + this.addSkill('armor_crafting', { + name: 'Armor Crafting', + description: 'Ability to craft and improve armor', + category: 'crafting', + maxLevel: 100, + experiencePerLevel: 800, + bonuses: { + craftingSpeed: 2, // +2% per level + durabilityBonus: 3, // +3% per level + rareChance: 0.5 // +0.5% per level + } + }); + + // Social skills + this.addSkill('leadership', { + name: 'Leadership', + description: 'Improves team performance and guild bonuses', + category: 'social', + maxLevel: 100, + experiencePerLevel: 1200, + bonuses: { + teamDamage: 1, // +1% per level + guildBonus: 2, // +2% per level + maxPartySize: 0.1 // +0.1 per level (rounded) + } + }); + + this.addSkill('negotiation', { + name: 'Negotiation', + description: 'Better prices from shops and traders', + category: 'social', + maxLevel: 100, + experiencePerLevel: 1000, + bonuses: { + shopDiscount: 0.5, // -0.5% per level + tradeBonus: 1, // +1% per level + reputationGain: 2 // +2% per level + } + }); + + // Exploration skills + this.addSkill('navigation', { + name: 'Navigation', + description: 'Faster travel and better map awareness', + category: 'exploration', + maxLevel: 100, + experiencePerLevel: 900, + bonuses: { + travelSpeed: 3, // +3% per level + discoveryRange: 2, // +2% per level + mapAccuracy: 1 // +1% per level + } + }); + + this.addSkill('scavenging', { + name: 'Scavenging', + description: 'Find more materials and rare items', + category: 'exploration', + maxLevel: 100, + experiencePerLevel: 900, + bonuses: { + materialFind: 2, // +2% per level + rareFind: 1, // +1% per level + salvageBonus: 3 // +3% per level + } + }); + } + + addSkill(id, skill) { + this.skills.set(id, { + id, + ...skill, + createdAt: new Date().toISOString() + }); + } + + getSkill(id) { + return this.skills.get(id); + } + + getAllSkills() { + return Array.from(this.skills.values()); + } + + getSkillsByCategory(category) { + return Array.from(this.skills.values()).filter(skill => skill.category === category); + } + + initializePlayerData(userId) { + if (!this.playerSkills.has(userId)) { + this.playerSkills.set(userId, { + skillPoints: 0, + totalExperience: 0, + skills: new Map(), + unlockedSkills: new Set(['weapons_mastery', 'defense_training']), // Start with basic skills + skillHistory: [] + }); + + // Initialize unlocked skills at level 1 + const playerData = this.playerSkills.get(userId); + for (const skillId of playerData.unlockedSkills) { + playerData.skills.set(skillId, { + level: 1, + experience: 0, + totalExperience: 0, + unlockedAt: new Date().toISOString() + }); + } + } + return this.playerSkills.get(userId); + } + + getPlayerData(userId) { + return this.playerSkills.get(userId) || this.initializePlayerData(userId); + } + + addExperience(userId, skillId, amount) { + const skill = this.getSkill(skillId); + if (!skill) { + throw new Error('Skill not found'); + } + + const playerData = this.getPlayerData(userId); + + // Check if skill is unlocked + if (!playerData.unlockedSkills.has(skillId)) { + throw new Error('Skill not unlocked'); + } + + const playerSkill = playerData.skills.get(skillId); + if (!playerSkill) { + throw new Error('Skill data not found'); + } + + // Add experience + playerSkill.experience += amount; + playerSkill.totalExperience += amount; + playerData.totalExperience += amount; + + let levelsGained = 0; + let oldLevel = playerSkill.level; + + // Check for level up + while (playerSkill.experience >= skill.experiencePerLevel && playerSkill.level < skill.maxLevel) { + playerSkill.experience -= skill.experiencePerLevel; + playerSkill.level += 1; + levelsGained++; + } + + // Record in history + playerData.skillHistory.push({ + skillId, + experienceGained: amount, + levelsGained, + newLevel: playerSkill.level, + timestamp: new Date().toISOString() + }); + + // Keep only last 100 skill records + if (playerData.skillHistory.length > 100) { + playerData.skillHistory = playerData.skillHistory.slice(-100); + } + + return { + success: true, + experienceGained: amount, + levelsGained, + newLevel: playerSkill.level, + oldLevel, + experienceToNext: playerSkill.level < skill.maxLevel ? skill.experiencePerLevel - playerSkill.experience : 0 + }; + } + + unlockSkill(userId, skillId) { + const skill = this.getSkill(skillId); + if (!skill) { + throw new Error('Skill not found'); + } + + const playerData = this.getPlayerData(userId); + + if (playerData.unlockedSkills.has(skillId)) { + throw new Error('Skill already unlocked'); + } + + // Add to unlocked skills + playerData.unlockedSkills.add(skillId); + + // Initialize at level 1 + playerData.skills.set(skillId, { + level: 1, + experience: 0, + totalExperience: 0, + unlockedAt: new Date().toISOString() + }); + + return { + success: true, + skillId, + skillName: skill.name, + category: skill.category + }; + } + + getPlayerSkills(userId) { + const playerData = this.getPlayerData(userId); + const skills = []; + + for (const skillId of playerData.unlockedSkills) { + const skill = this.getSkill(skillId); + const playerSkill = playerData.skills.get(skillId); + + skills.push({ + ...skill, + level: playerSkill.level, + experience: playerSkill.experience, + totalExperience: playerSkill.totalExperience, + experienceToNext: playerSkill.level < skill.maxLevel ? skill.experiencePerLevel - playerSkill.experience : 0, + isMaxLevel: playerSkill.level >= skill.maxLevel, + unlockedAt: playerSkill.unlockedAt + }); + } + + return skills; + } + + getSkillBonuses(userId) { + const playerData = this.getPlayerData(userId); + const totalBonuses = { + damage: 0, + accuracy: 0, + criticalChance: 0, + damageReduction: 0, + resistance: 0, + health: 0, + speed: 0, + dodgeChance: 0, + initiative: 0, + craftingSpeed: 0, + craftingSuccess: 0, + rareChance: 0, + durabilityBonus: 0, + teamDamage: 0, + guildBonus: 0, + maxPartySize: 0, + shopDiscount: 0, + tradeBonus: 0, + reputationGain: 0, + travelSpeed: 0, + discoveryRange: 0, + mapAccuracy: 0, + materialFind: 0, + rareFind: 0, + salvageBonus: 0 + }; + + for (const skillId of playerData.unlockedSkills) { + const skill = this.getSkill(skillId); + const playerSkill = playerData.skills.get(skillId); + + if (skill && playerSkill) { + for (const [bonus, value] of Object.entries(skill.bonuses)) { + totalBonuses[bonus] += value * playerSkill.level; + } + } + } + + return totalBonuses; + } + + awardSkillPoints(userId, amount) { + const playerData = this.getPlayerData(userId); + playerData.skillPoints += amount; + + return { + success: true, + pointsAwarded: amount, + totalPoints: playerData.skillPoints + }; + } + + allocateSkillPoint(userId, skillId) { + const playerData = this.getPlayerData(userId); + + if (playerData.skillPoints <= 0) { + throw new Error('No skill points available'); + } + + const skill = this.getSkill(skillId); + if (!skill) { + throw new Error('Skill not found'); + } + + const playerSkill = playerData.skills.get(skillId); + if (!playerSkill) { + throw new Error('Skill not unlocked'); + } + + if (playerSkill.level >= skill.maxLevel) { + throw new Error('Skill already at max level'); + } + + // Allocate point + playerSkill.level += 1; + playerData.skillPoints -= 1; + + return { + success: true, + skillId, + newLevel: playerSkill.level, + remainingPoints: playerData.skillPoints + }; + } + + getSkillStatistics(userId) { + const playerData = this.getPlayerData(userId); + const skills = this.getPlayerSkills(userId); + + return { + skillPoints: playerData.skillPoints, + totalExperience: playerData.totalExperience, + unlockedSkills: playerData.unlockedSkills.size, + maxLevelSkills: skills.filter(skill => skill.isMaxLevel).length, + averageLevel: skills.length > 0 ? Math.floor(skills.reduce((sum, skill) => sum + skill.level, 0) / skills.length) : 0, + recentHistory: playerData.skillHistory.slice(-10) + }; + } +} + +module.exports = SkillSystem;