From 4aeb217e5187589dad266f45368554b38d787dcf Mon Sep 17 00:00:00 2001 From: Robert MacRae Date: Mon, 26 Jan 2026 17:15:57 -0400 Subject: [PATCH] server data-driven now of quests, items, and dungeons + fixed saves --- GameServer/.env.example | 18 - GameServer/config/database.js | 3 + GameServer/models/PlayerData.js | 128 +++ GameServer/routes/dungeons.js | 114 +++ GameServer/server.js | 934 ++++++++++++++++++++- GameServer/systems/DungeonSystem.js | 1199 +++++++++++++++++++++++---- GameServer/systems/IdleSystem.js | 44 +- GameServer/systems/ItemSystem.js | 618 ++++++++++++++ GameServer/systems/QuestSystem.js | 245 +++++- 9 files changed, 3067 insertions(+), 236 deletions(-) delete mode 100644 GameServer/.env.example create mode 100644 GameServer/models/PlayerData.js create mode 100644 GameServer/systems/ItemSystem.js diff --git a/GameServer/.env.example b/GameServer/.env.example deleted file mode 100644 index 43ec93a..0000000 --- a/GameServer/.env.example +++ /dev/null @@ -1,18 +0,0 @@ -# Game Server Configuration -PORT=3002 -NODE_ENV=production - -# Database Configuration -MONGODB_URI=mongodb://localhost:27017/galaxystrikeonline - -# API Server Configuration -API_SERVER_URL=http://localhost:3000 -GAME_SERVER_URL=https://dev.gameserver.galaxystrike.online - -# Optional: Server identification -SERVER_NAME=Dev Game Server -SERVER_REGION=local -MAX_PLAYERS=8 - -# Logging -LOG_LEVEL=info diff --git a/GameServer/config/database.js b/GameServer/config/database.js index 01e0a24..82e0e3d 100644 --- a/GameServer/config/database.js +++ b/GameServer/config/database.js @@ -3,9 +3,12 @@ const logger = require('../utils/logger'); const connectDB = async () => { try { + console.log('[DATABASE] Connecting to MongoDB:', process.env.MONGODB_URI); const conn = await mongoose.connect(process.env.MONGODB_URI); + console.log(`[DATABASE] MongoDB Connected: ${conn.connection.host}`); logger.info(`MongoDB Connected: ${conn.connection.host}`); } catch (error) { + console.error('[DATABASE] Database connection failed:', error); logger.error('Database connection failed:', error); process.exit(1); } diff --git a/GameServer/models/PlayerData.js b/GameServer/models/PlayerData.js new file mode 100644 index 0000000..827e2b7 --- /dev/null +++ b/GameServer/models/PlayerData.js @@ -0,0 +1,128 @@ +const mongoose = require('mongoose'); + +const playerDataSchema = new mongoose.Schema({ + userId: { + type: String, + required: true, + unique: true + }, + username: { + type: String, + required: true + }, + // Player stats + stats: { + level: { type: Number, default: 1 }, + experience: { type: Number, default: 0 }, + totalExperience: { type: Number, default: 0 }, + credits: { type: Number, default: 1000 }, + gems: { type: Number, default: 0 }, + skillPoints: { type: Number, default: 0 }, + totalKills: { type: Number, default: 0 }, + dungeonsCleared: { type: Number, default: 0 }, + questsCompleted: { type: Number, default: 0 }, + playTime: { type: Number, default: 0 }, + lastLogin: { type: Date, default: Date.now } + }, + // Game systems data + inventory: { + items: [{ type: mongoose.Schema.Types.Mixed }], + maxSize: { type: Number, default: 50 } + }, + ship: { + type: mongoose.Schema.Types.Mixed, + default: null + }, + base: { + type: mongoose.Schema.Types.Mixed, + default: null + }, + quests: { + active: [{ type: mongoose.Schema.Types.Mixed }], + completed: [{ type: String }] + }, + skills: { + type: mongoose.Schema.Types.Mixed, + default: {} + }, + idleSystem: { + type: mongoose.Schema.Types.Mixed, + default: {} + }, + dungeonSystem: { + type: mongoose.Schema.Types.Mixed, + default: {} + }, + crafting: { + type: mongoose.Schema.Types.Mixed, + default: {} + }, + // Server-specific data + currentServerId: { + type: String, + default: null + }, + lastServerJoin: { + type: Date, + default: null + }, + totalPlayTime: { + type: Number, + default: 0 + } +}, { + timestamps: true +}); + +// Indexes for performance +playerDataSchema.index({ username: 1 }); +playerDataSchema.index({ 'stats.level': 1 }); + +// Note: userId field already has unique: true which creates an index automatically + +// Methods +playerDataSchema.methods.addExperience = function(amount) { + this.stats.experience += amount; + this.stats.playTime += Date.now() - (this.lastLogin || Date.now()); + this.lastLogin = new Date(); + return this.stats.experience; +}; + +playerDataSchema.methods.addCredits = function(amount) { + this.stats.credits += amount; + return this.stats.credits; +}; + +playerDataSchema.methods.updatePlayTime = function() { + const sessionTime = Date.now() - (this.lastLogin || Date.now()); + this.stats.playTime += sessionTime; + this.totalPlayTime += sessionTime; + this.lastLogin = new Date(); + return sessionTime; +}; + +playerDataSchema.methods.joinServer = function(serverId) { + this.currentServerId = serverId; + this.lastServerJoin = new Date(); + this.lastLogin = new Date(); +}; + +playerDataSchema.methods.leaveServer = function() { + console.log('[PLAYER DATA] leaveServer called for:', this.username); + + // Update play time before leaving + const sessionTime = Date.now() - (this.lastLogin || Date.now()); + this.stats.playTime += sessionTime; + this.totalPlayTime += sessionTime; + this.lastLogin = new Date(); + + // Clear server association + this.currentServerId = null; + this.lastServerJoin = null; + + console.log('[PLAYER DATA] Updated play time:', sessionTime, 'Total play time:', this.totalPlayTime); + + return sessionTime; +}; + +module.exports = mongoose.model('PlayerData', playerDataSchema); diff --git a/GameServer/routes/dungeons.js b/GameServer/routes/dungeons.js index b566b52..29a42bf 100644 --- a/GameServer/routes/dungeons.js +++ b/GameServer/routes/dungeons.js @@ -263,4 +263,118 @@ router.post('/cleanup', async (req, res) => { } }); +// Get room types +router.get('/room-types', async (req, res) => { + try { + const roomTypes = dungeonSystem.getRoomTypes(); + res.json(roomTypes); + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +// Get enemy templates +router.get('/enemy-templates', async (req, res) => { + try { + const enemyTemplates = dungeonSystem.getEnemyTemplates(); + res.json(enemyTemplates); + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +// Start dungeon (simplified for client) +router.post('/start', async (req, res) => { + try { + const { dungeonId, userId } = req.body; + + const instance = dungeonSystem.createInstance(dungeonId, userId, []); + + res.json({ + success: true, + instance + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +// Process encounter (simplified for client) +router.post('/encounter', async (req, res) => { + try { + const { instanceId, userId } = req.body; + + const result = dungeonSystem.startEncounter(instanceId, userId); + + res.json({ + success: true, + encounter: result + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +// Complete dungeon (simplified for client) +router.post('/complete', async (req, res) => { + try { + const { instanceId, userId } = req.body; + + const result = dungeonSystem.completeDungeon(instanceId); + + res.json({ + success: true, + rewards: result + }); + } catch (error) { + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +// Get player dungeon status +router.get('/status/:userId', async (req, res) => { + try { + const { userId } = req.params; + const instance = dungeonSystem.getPlayerInstance(userId); + + if (!instance) { + return res.json({ + success: true, + status: { + hasActiveDungeon: false, + currentInstance: null + } + }); + } + + res.json({ + success: true, + status: { + hasActiveDungeon: true, + currentInstance: instance + } + }); + } 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 b3e67f9..eab0cd8 100644 --- a/GameServer/server.js +++ b/GameServer/server.js @@ -7,17 +7,42 @@ const express = require('express'); const http = require('http'); const socketIo = require('socket.io'); const cors = require('cors'); -const dotenv = require('dotenv'); +const helmet = require('helmet'); +const compression = require('compression'); +const mongoose = require('mongoose'); require('dotenv').config(); const logger = require('./utils/logger'); const connectDB = require('./config/database'); +const PlayerData = require('./models/PlayerData'); + +// Import server systems for player initialization +const QuestSystem = require('./systems/QuestSystem'); +const SkillSystem = require('./systems/SkillSystem'); +const DungeonSystem = require('./systems/DungeonSystem'); +const CraftingSystem = require('./systems/CraftingSystem'); +const IdleSystem = require('./systems/IdleSystem'); +const ItemSystem = require('./systems/ItemSystem'); + +// Initialize server systems +const questSystem = new QuestSystem(); +const skillSystem = new SkillSystem(); +const dungeonSystem = new DungeonSystem(); +const craftingSystem = new CraftingSystem(); +const idleSystem = new IdleSystem(); +const itemSystem = new ItemSystem(); const app = express(); const server = http.createServer(app); const io = socketIo(server, { cors: { - origin: ["http://localhost:3000", "http://127.0.0.1:3000", "file://"], + origin: [ + "http://localhost:3000", + "http://127.0.0.1:3000", + "file://", + "https://dev.galaxystrike.online", + "https://galaxystrike.online" + ], methods: ["GET", "POST"], credentials: true } @@ -27,9 +52,20 @@ const io = socketIo(server, { const gameInstances = new Map(); const connectedClients = new Map(); +// Server-side shop item lookup using ItemSystem +function findShopItem(itemId) { + return itemSystem.getItem(itemId); +} + // Middleware app.use(cors({ - origin: ["http://localhost:3000", "http://127.0.0.1:3000", "file://"], + origin: [ + "http://localhost:3000", + "http://127.0.0.1:3000", + "file://", + "https://dev.galaxystrike.online", + "https://galaxystrike.online" + ], credentials: true })); app.use(express.json({ limit: '10mb' })); @@ -57,6 +93,69 @@ app.get('/api/ssc/version', (req, res) => { }); }); +// Shop API endpoints +app.get('/api/shop/items', (req, res) => { + try { + const shopItems = itemSystem.getShopItems(); + res.status(200).json({ + success: true, + items: shopItems, + timestamp: new Date().toISOString() + }); + } catch (error) { + console.error('[GAME SERVER] Error fetching shop items:', error); + res.status(500).json({ + success: false, + error: 'Failed to fetch shop items' + }); + } +}); + +app.get('/api/shop/items/:category', (req, res) => { + try { + const { category } = req.params; + const items = itemSystem.getItemsByCategory(category); + + res.status(200).json({ + success: true, + category: category, + items: items, + timestamp: new Date().toISOString() + }); + } catch (error) { + console.error('[GAME SERVER] Error fetching shop category:', error); + res.status(500).json({ + success: false, + error: 'Failed to fetch shop category' + }); + } +}); + +app.get('/api/items/:itemId', (req, res) => { + try { + const { itemId } = req.params; + const item = itemSystem.getItem(itemId); + + if (!item) { + return res.status(404).json({ + success: false, + error: 'Item not found' + }); + } + + res.status(200).json({ + success: true, + item: item + }); + } catch (error) { + console.error('[GAME SERVER] Error fetching item:', error); + res.status(500).json({ + success: false, + error: 'Failed to fetch item' + }); + } +}); + // Game data endpoints (similar to LocalServer) app.post('/api/game/player/:id/save', (req, res) => { const playerId = req.params.id; @@ -117,38 +216,148 @@ io.on('connection', (socket) => { instanceId: null }); + console.log('[GAME SERVER] Waiting for authentication from:', socket.id); + // Update player count on API server when new player connects updatePlayerCountOnAPI(); + // Add timeout for authentication + const authTimeout = setTimeout(() => { + const clientData = connectedClients.get(socket.id); + if (clientData && !clientData.userId) { + console.log('[GAME SERVER] Authentication timeout for:', socket.id); + socket.emit('authenticated', { + success: false, + error: 'Authentication timeout' + }); + socket.disconnect(); + } + }, 10000); // 10 seconds timeout + + // Clear timeout when authenticated + socket.on('authenticated', () => { + clearTimeout(authTimeout); + }); + // Authentication (similar to LocalServer) - socket.on('authenticate', (data) => { + socket.on('authenticate', async (data) => { console.log('[GAME SERVER] Authenticating client:', socket.id, data); - // In production, validate with API server - socket.emit('authenticated', { - success: true, - user: { - id: data.userId || socket.id, - username: data.username || 'Game Player', - token: 'game-token-' + Date.now() + try { + // Check database connection first + if (mongoose.connection.readyState !== 1) { + console.error('[GAME SERVER] Database not connected, authentication failed'); + socket.emit('authenticated', { + success: false, + error: 'Database not available' + }); + return; } - }); + + console.log('[GAME SERVER] Database is connected, proceeding with authentication'); + + // Load player data from database + const playerData = await loadPlayerData(data.userId || socket.id, data.username || 'Game Player'); + + if (playerData) { + console.log('[GAME SERVER] Player data loaded successfully:', playerData.username); + + // Store player data in client connection + const clientData = connectedClients.get(socket.id); + if (clientData) { + clientData.playerData = playerData; + clientData.userId = playerData.userId; + clientData.username = playerData.username; + } else { + console.error('[GAME SERVER] No client data found for socket:', socket.id); + } + + // Join server + playerData.joinServer(`devgame-server-${PORT}`); + + // Update last login time + if (!playerData.stats) playerData.stats = {}; + playerData.stats.lastLogin = new Date().toISOString(); + + // Ensure all required fields exist (for existing players) + if (playerData.stats.totalExperience === undefined) playerData.stats.totalExperience = playerData.stats.experience || 0; + if (playerData.stats.gems === undefined || playerData.stats.gems === 0) playerData.stats.gems = 50; // Restore gems if wiped + if (playerData.stats.skillPoints === undefined) playerData.stats.skillPoints = 0; + if (playerData.stats.totalKills === undefined) playerData.stats.totalKills = 0; + if (playerData.stats.questsCompleted === undefined) playerData.stats.questsCompleted = 0; + + await savePlayerData(playerData.userId, playerData); + + // In production, validate with API server + socket.emit('authenticated', { + success: true, + user: { + id: playerData.userId, + username: playerData.username, + token: 'game-token-' + Date.now() + }, + playerData: { + ...playerData.toObject(), + serverTimestamp: Date.now(), // Add server timestamp for time synchronization + serverTimezone: 'UTC' // Server operates in UTC timezone + } + }); + + console.log(`[GAME SERVER] ${playerData.username} authenticated with Level ${playerData.stats.level}`); + } else { + console.error('[GAME SERVER] Failed to load player data'); + socket.emit('authenticated', { + success: false, + error: 'Failed to load player data' + }); + } + } catch (error) { + console.error('[GAME SERVER] Authentication error:', error); + console.error('[GAME INITIALIZER] Error stack:', error.stack); + socket.emit('authenticated', { + success: false, + error: 'Authentication failed: ' + error.message + }); + } }); // Game data events (similar to LocalServer) - socket.on('saveGameData', (data) => { + socket.on('saveGameData', async (data) => { console.log('[GAME SERVER] Saving game data for:', socket.id); - if (connectedClients.has(socket.id)) { - connectedClients.get(socket.id).playerData = data; + const clientData = connectedClients.get(socket.id); + if (clientData && clientData.userId) { + try { + // Validate the data before saving + if (!data || typeof data !== 'object') { + console.warn('[GAME SERVER] Invalid game data received, skipping save'); + socket.emit('gameDataSaved', { success: false, error: 'Invalid game data' }); + return; + } + + // Update player data with new game data + const updatedPlayerData = { ...clientData.playerData, ...data }; + clientData.playerData = updatedPlayerData; + + // Save to database + const success = await savePlayerData(clientData.userId, updatedPlayerData); + + socket.emit('gameDataSaved', { + success: success, + message: success ? 'Game saved to server!' : 'Failed to save to server' + }); + + console.log(`[GAME SERVER] Saved game data for ${clientData.username}`); + } catch (error) { + console.error('[GAME SERVER] Error saving game data:', error); + socket.emit('gameDataSaved', { success: false, error: 'Failed to save data' }); + } + } else { + console.warn('[GAME SERVER] No client data or user ID found for saveGameData'); + socket.emit('gameDataSaved', { success: false, error: 'Player not authenticated' }); } - - socket.emit('gameDataSaved', { - success: true, - timestamp: Date.now() - }); }); - + socket.on('loadGameData', (data) => { console.log('[GAME SERVER] Loading game data for:', socket.id); @@ -160,6 +369,61 @@ io.on('connection', (socket) => { }); }); + // Shop and item system events + socket.on('getShopItems', (data) => { + console.log('[GAME SERVER] Sending shop items to:', socket.id); + + try { + const shopItems = itemSystem.getShopItems(); + socket.emit('shopItemsReceived', { + success: true, + items: shopItems, + timestamp: new Date().toISOString() + }); + } catch (error) { + console.error('[GAME SERVER] Error sending shop items:', error); + socket.emit('shopItemsReceived', { + success: false, + error: 'Failed to load shop items' + }); + } + }); + + socket.on('getItemDetails', (data) => { + const { itemId } = data; + + if (!itemId) { + socket.emit('itemDetailsReceived', { + success: false, + error: 'Item ID required' + }); + return; + } + + try { + const item = itemSystem.getItem(itemId); + + if (!item) { + socket.emit('itemDetailsReceived', { + success: false, + error: 'Item not found' + }); + return; + } + + socket.emit('itemDetailsReceived', { + success: true, + item: item + }); + } catch (error) { + console.error('[GAME SERVER] Error getting item details:', error); + socket.emit('itemDetailsReceived', { + success: false, + error: 'Failed to get item details' + }); + } + }); + // Game-specific events socket.on('joinGameInstance', (data) => { const { instanceId } = data; @@ -226,25 +490,374 @@ io.on('connection', (socket) => { } }); - socket.on('disconnect', () => { + // Idle rewards events + socket.on('claimOfflineRewards', async (data) => { + console.log('[GAME SERVER] Claiming offline rewards for:', socket.id); + + const clientData = connectedClients.get(socket.id); + if (!clientData || !clientData.userId) { + socket.emit('offlineRewardsClaimed', { success: false, error: 'Not authenticated' }); + return; + } + + try { + const playerData = await loadPlayerData(clientData.userId); + const offlineRewards = idleSystem.calculateOfflineRewards(clientData.userId); + + if (offlineRewards.offlineTime > 0 && offlineRewards.rewards.credits > 0) { + // Apply rewards to player + playerData.stats.credits = (playerData.stats.credits || 0) + offlineRewards.rewards.credits; + playerData.stats.experience = (playerData.stats.experience || 0) + offlineRewards.rewards.experience; + + // Update idle system data + if (!playerData.idleSystem) playerData.idleSystem = {}; + playerData.idleSystem.lastActive = new Date().toISOString(); + playerData.idleSystem.totalOfflineTime += offlineRewards.offlineTime; + playerData.idleSystem.totalIdleCredits += offlineRewards.rewards.credits; + + await savePlayerData(clientData.userId, playerData); + + socket.emit('offlineRewardsClaimed', { + success: true, + rewards: offlineRewards.rewards, + offlineTime: offlineRewards.offlineTime + }); + + console.log(`[GAME SERVER] Offline rewards claimed for ${clientData.username}: ${offlineRewards.rewards.credits} credits`); + } else { + socket.emit('offlineRewardsClaimed', { + success: true, + rewards: { credits: 0, experience: 0, energy: 0 }, + offlineTime: 0 + }); + } + } catch (error) { + console.error('[GAME SERVER] Error claiming offline rewards:', error); + socket.emit('offlineRewardsClaimed', { success: false, error: 'Failed to claim rewards' }); + } + }); + + // Shop purchase events + socket.on('purchaseItem', async (data) => { + console.log('[GAME SERVER] Processing purchase request:', socket.id, data); + + const clientData = connectedClients.get(socket.id); + if (!clientData || !clientData.userId) { + console.log('[GAME SERVER] Purchase failed - not authenticated'); + socket.emit('purchaseCompleted', { success: false, error: 'Not authenticated' }); + return; + } + + try { + const { itemId, quantity = 1 } = data; + + console.log('[GAME SERVER] Purchase details:', { + itemId: itemId, + quantity: quantity, + userId: clientData.userId, + username: clientData.username + }); + + if (!itemId) { + console.log('[GAME SERVER] Purchase failed - no item ID'); + socket.emit('purchaseCompleted', { success: false, error: 'Item ID required' }); + return; + } + + // Load current player data + const playerData = await loadPlayerData(clientData.userId); + console.log('[GAME SERVER] Player data loaded:', { + username: playerData.username, + credits: playerData.stats.credits, + gems: playerData.stats.gems + }); + + // Find the item in shop (server-side validation) + const item = findShopItem(itemId); + if (!item) { + console.log('[GAME SERVER] Purchase failed - item not found:', itemId); + socket.emit('purchaseCompleted', { success: false, error: 'Item not found in shop' }); + return; + } + + console.log('[GAME SERVER] Item found:', { + id: item.id, + name: item.name, + type: item.type, + price: item.price, + currency: item.currency + }); + + // Calculate total cost + const totalCost = item.price * quantity; + const currency = item.currency; + + console.log('[GAME SERVER] Cost calculation:', { + unitPrice: item.price, + quantity: quantity, + totalCost: totalCost, + currency: currency + }); + + // Check if player can afford + if (currency === 'credits' && (playerData.stats.credits || 0) < totalCost) { + console.log('[GAME SERVER] Purchase failed - insufficient credits:', { + required: totalCost, + current: playerData.stats.credits || 0 + }); + socket.emit('purchaseCompleted', { success: false, error: 'Not enough credits' }); + return; + } + + if (currency === 'gems' && (playerData.stats.gems || 0) < totalCost) { + console.log('[GAME SERVER] Purchase failed - insufficient gems:', { + required: totalCost, + current: playerData.stats.gems || 0 + }); + socket.emit('purchaseCompleted', { success: false, error: 'Not enough gems' }); + return; + } + + // Check if already owns this cosmetic + if (item.type === 'cosmetic' && playerData.ownedCosmetics && playerData.ownedCosmetics.includes(item.id)) { + console.log('[GAME SERVER] Purchase failed - already owns cosmetic:', item.id); + socket.emit('purchaseCompleted', { success: false, error: 'You already own this cosmetic' }); + return; + } + + // Process payment + console.log('[GAME SERVER] Processing payment...'); + if (currency === 'credits') { + playerData.stats.credits = (playerData.stats.credits || 0) - totalCost; + } else if (currency === 'gems') { + playerData.stats.gems = (playerData.stats.gems || 0) - totalCost; + } + + console.log('[GAME SERVER] Payment processed, new balance:', { + credits: playerData.stats.credits, + gems: playerData.stats.gems + }); + + // Give item based on type + console.log('[GAME SERVER] Adding item to inventory, type:', item.type); + switch (item.type) { + case 'ship': + if (!playerData.ownedShips) playerData.ownedShips = []; + if (!playerData.ownedShips.includes(item.id)) { + playerData.ownedShips.push(item.id); + console.log('[GAME SERVER] Ship added to owned ships:', item.id); + } else { + console.log('[GAME SERVER] Ship already owned:', item.id); + } + break; + + case 'cosmetic': + if (!playerData.ownedCosmetics) playerData.ownedCosmetics = []; + if (!playerData.ownedCosmetics.includes(item.id)) { + playerData.ownedCosmetics.push(item.id); + console.log('[GAME SERVER] Cosmetic added to owned cosmetics:', item.id); + } else { + console.log('[GAME SERVER] Cosmetic already owned:', item.id); + } + break; + + case 'consumable': + if (!playerData.inventory) playerData.inventory = { items: [] }; + playerData.inventory.items.push({ + id: item.id, + name: item.name, + type: item.type, + quantity: quantity, + acquired: new Date().toISOString() + }); + console.log('[GAME SERVER] Consumable added to inventory:', item.name); + break; + + case 'material': + if (!playerData.inventory) playerData.inventory = { items: [] }; + playerData.inventory.items.push({ + id: item.id, + name: item.name, + type: item.type, + quantity: quantity, + acquired: new Date().toISOString() + }); + console.log('[GAME SERVER] Material added to inventory:', item.name); + break; + + default: + console.warn(`[GAME SERVER] Unknown item type: ${item.type}`); + socket.emit('purchaseCompleted', { success: false, error: 'Unknown item type' }); + return; + } + + // Save updated player data + console.log('[GAME SERVER] Saving player data...'); + await savePlayerData(clientData.userId, playerData); + + // Update client data with new values + clientData.playerData = playerData; + + // Send success response + const response = { + success: true, + item: item, + quantity: quantity, + totalCost: totalCost, + currency: currency, + newBalance: currency === 'credits' ? playerData.stats.credits : playerData.stats.gems + }; + + console.log('[GAME SERVER] Sending success response:', response); + socket.emit('purchaseCompleted', response); + + console.log(`[GAME SERVER] Purchase completed for ${clientData.username}: ${item.name} x${quantity} for ${totalCost} ${currency}`); + + // Broadcast economy update to client + broadcastEconomyUpdate(socket.id); + + } catch (error) { + console.error('[GAME SERVER] Error processing purchase:', error); + socket.emit('purchaseCompleted', { success: false, error: 'Purchase failed: ' + error.message }); + } + }); + + // Dungeon System Packet Handlers + socket.on('get_dungeons', () => { + console.log('[GAME SERVER] Sending dungeons data to:', socket.id); + const dungeons = dungeonSystem.getDungeonsGroupedByDifficulty(); + socket.emit('dungeons_data', { dungeons }); + }); + + socket.on('get_room_types', () => { + console.log('[GAME SERVER] Sending room types to:', socket.id); + const roomTypes = dungeonSystem.getRoomTypes(); + socket.emit('room_types_data', roomTypes); + }); + + socket.on('get_enemy_templates', () => { + console.log('[GAME SERVER] Sending enemy templates to:', socket.id); + const enemyTemplates = dungeonSystem.getEnemyTemplates(); + socket.emit('enemy_templates_data', enemyTemplates); + }); + + // Economy System Packet Handlers + socket.on('get_economy_data', () => { + console.log('[GAME SERVER] Sending economy data to:', socket.id); + const clientData = connectedClients.get(socket.id); + if (clientData && clientData.playerData) { + const economyData = { + credits: clientData.playerData.stats.credits || 0, + gems: clientData.playerData.stats.gems || 0 + }; + socket.emit('economy_data', economyData); + } + }); + + // Function to broadcast economy updates to specific client + function broadcastEconomyUpdate(socketId) { + const clientData = connectedClients.get(socketId); + if (clientData && clientData.playerData) { + const economyData = { + credits: clientData.playerData.stats.credits || 0, + gems: clientData.playerData.stats.gems || 0 + }; + io.to(socketId).emit('economy_data', economyData); + console.log('[GAME SERVER] Broadcasted economy update to:', socketId, economyData); + } + } + + socket.on('start_dungeon', (data) => { + console.log('[GAME SERVER] Starting dungeon for:', socket.id, data); + try { + const { dungeonId, userId } = data; + const instance = dungeonSystem.createInstance(dungeonId, userId, []); + socket.emit('dungeon_started', { instance }); + } catch (error) { + console.error('[GAME SERVER] Error starting dungeon:', error); + socket.emit('dungeon_started', { success: false, error: error.message }); + } + }); + + socket.on('process_encounter', (data) => { + console.log('[GAME SERVER] Processing encounter for:', socket.id, data); + try { + const { instanceId, userId } = data; + const encounter = dungeonSystem.startEncounter(instanceId, userId); + socket.emit('encounter_data', { encounter }); + } catch (error) { + console.error('[GAME SERVER] Error processing encounter:', error); + socket.emit('encounter_data', { success: false, error: error.message }); + } + }); + + socket.on('complete_dungeon', (data) => { + console.log('[GAME SERVER] Completing dungeon for:', socket.id, data); + try { + const { instanceId, userId } = data; + const result = dungeonSystem.completeDungeon(instanceId); + socket.emit('dungeon_completed', { rewards: result }); + } catch (error) { + console.error('[GAME SERVER] Error completing dungeon:', error); + socket.emit('dungeon_completed', { success: false, error: error.message }); + } + }); + + socket.on('get_dungeon_status', (data) => { + console.log('[GAME SERVER] Getting dungeon status for:', socket.id, data); + try { + const { userId } = data; + const instance = dungeonSystem.getPlayerInstance(userId); + if (instance) { + socket.emit('dungeon_status', { + hasActiveDungeon: true, + currentInstance: instance + }); + } else { + socket.emit('dungeon_status', { + hasActiveDungeon: false, + currentInstance: null + }); + } + } catch (error) { + console.error('[GAME SERVER] Error getting dungeon status:', error); + socket.emit('dungeon_status', { success: false, error: error.message }); + } + }); + + socket.on('disconnect', async () => { console.log('[GAME SERVER] Client disconnected:', socket.id); const clientData = connectedClients.get(socket.id); - if (clientData && clientData.instanceId) { - const instance = gameInstances.get(clientData.instanceId); - if (instance) { - instance.players.delete(socket.id); - - // Clean up empty instances - if (instance.players.size === 0) { - gameInstances.delete(clientData.instanceId); + if (clientData) { + // Save player data before disconnect + if (clientData.playerData && clientData.userId) { + try { + clientData.playerData.leaveServer(); + await savePlayerData(clientData.userId, clientData.playerData); + console.log(`[GAME SERVER] Saved data for ${clientData.username} on disconnect`); + } catch (error) { + console.error('[GAME SERVER] Error saving data on disconnect:', error); + } + } + + // Handle game instance cleanup + if (clientData.instanceId) { + const instance = gameInstances.get(clientData.instanceId); + if (instance) { + instance.players.delete(socket.id); + + // Clean up empty instances + if (instance.players.size === 0) { + gameInstances.delete(clientData.instanceId); + } + + // Notify other players + socket.to(clientData.instanceId).emit('playerLeftInstance', { + playerId: socket.id, + playerCount: instance.players.size + }); } - - // Notify other players - socket.to(clientData.instanceId).emit('playerLeftInstance', { - playerId: socket.id, - playerCount: instance.players.size - }); } } @@ -255,6 +868,214 @@ io.on('connection', (socket) => { }); }); +// Load player data from database +async function loadPlayerData(userId, username) { + try { + // Check if database is connected + if (mongoose.connection.readyState !== 1) { + console.error('[GAME SERVER] Database not connected, cannot load player data'); + return null; + } + + let playerData = await PlayerData.findOne({ userId }); + + if (!playerData) { + // Create new player data with initialized systems + console.log(`[GAME SERVER] Creating new player data for ${username} with system initialization`); + + // Initialize player data in all systems + const questData = questSystem.initializePlayerData(userId); + const skillData = skillSystem.initializePlayerData(userId); + const craftingData = craftingSystem.initializePlayerData(userId); + + // For dungeons, initialize with available dungeons + const availableDungeons = dungeonSystem.getAllDungeons(); + const dungeonData = { + availableDungeons: availableDungeons.map(d => ({ + id: d.id, + name: d.name, + difficulty: d.difficulty, + minLevel: d.minLevel || 1, + maxPlayers: d.maxPlayers || 4, + description: d.description + })), + completedDungeons: [], + currentInstance: null, + dungeonProgress: {} + }; + + playerData = new PlayerData({ + userId, + username, + stats: { + level: 1, + experience: 0, + totalExperience: 0, + credits: 1000, + gems: 50, // Give some starting gems + skillPoints: 0, + totalKills: 0, + dungeonsCleared: 0, + questsCompleted: 0, + playTime: 0, + lastLogin: new Date() + }, + // Initialize with system data + quests: { + active: Array.from(questData.activeQuests.values()), + completed: Array.from(questData.completedQuests.keys()) + }, + skills: skillData, + dungeonSystem: dungeonData, + crafting: craftingData + }); + await playerData.save(); + console.log(`[GAME SERVER] Created new player data for ${username} with ${questData.activeQuests.size} active quests`); + } else { + // Auto-migration for existing players - check for missing data + console.log(`[GAME SERVER] Loading existing player data for ${username} (Level ${playerData.stats.level})`); + + let migrated = false; + let migrationLog = []; + + // Always categorize quests to ensure proper format + console.log(`[GAME SERVER] Categorizing quests for ${username}`); + console.log(`[GAME SERVER] Current playerData.quests before categorization:`, playerData.quests); + + const questData = questSystem.getPlayerData(userId); + console.log(`[GAME SERVER] QuestSystem data:`, { + activeQuestsCount: questData.activeQuests.size, + completedQuestsCount: questData.completedQuests.size, + activeQuests: Array.from(questData.activeQuests.values()) + }); + + // Categorize quests by type for client + const activeQuests = Array.from(questData.activeQuests.values()); + const mainQuests = activeQuests.filter(quest => quest.type === 'main'); + const dailyQuests = activeQuests.filter(quest => quest.type === 'daily'); + const weeklyQuests = activeQuests.filter(quest => quest.type === 'weekly'); + const tutorialQuests = activeQuests.filter(quest => quest.type === 'tutorial'); + + playerData.quests = { + main: mainQuests, + daily: dailyQuests, + weekly: weeklyQuests, + tutorial: tutorialQuests, + active: activeQuests, + completed: Array.from(questData.completedQuests.keys()) + }; + + console.log(`[GAME SERVER] Quest categorization complete:`, { + main: mainQuests.length, + daily: dailyQuests.length, + weekly: weeklyQuests.length, + tutorial: tutorialQuests.length, + total: activeQuests.length + }); + + console.log(`[GAME SERVER] Final playerData.quests after categorization:`, playerData.quests); + + // IMPORTANT: Save the updated quest data to database + try { + await savePlayerData(userId, playerData); + console.log(`[GAME SERVER] Saved categorized quest data for ${username}`); + } catch (error) { + console.error(`[GAME SERVER] Failed to save quest data for ${username}:`, error); + } + + // Migrate skills if missing or empty + if (!playerData.skills || Object.keys(playerData.skills).length === 0) { + console.log(`[GAME SERVER] Migrating skills for ${username}`); + const skillData = skillSystem.initializePlayerData(userId); + playerData.skills = skillData; + migrated = true; + migrationLog.push('Added skill data'); + } + + // Migrate dungeons if missing or empty + if (!playerData.dungeonSystem || !playerData.dungeonSystem.availableDungeons || + (playerData.dungeonSystem.availableDungeons && playerData.dungeonSystem.availableDungeons.length === 0)) { + console.log(`[GAME SERVER] Migrating dungeons for ${username}`); + const availableDungeons = dungeonSystem.getAllDungeons(); + playerData.dungeonSystem = { + availableDungeons: availableDungeons.map(d => ({ + id: d.id, + name: d.name, + difficulty: d.difficulty, + minLevel: d.minLevel || 1, + maxPlayers: d.maxPlayers || 4, + description: d.description + })), + completedDungeons: [], + currentInstance: null, + dungeonProgress: {} + }; + migrated = true; + migrationLog.push(`Added ${availableDungeons.length} available dungeons`); + } + + // Migrate crafting if missing or empty (this field might not exist at all) + if (!playerData.crafting || Object.keys(playerData.crafting).length === 0) { + console.log(`[GAME SERVER] Migrating crafting for ${username}`); + const craftingData = craftingSystem.initializePlayerData(userId); + playerData.crafting = craftingData; + migrated = true; + migrationLog.push('Added crafting data'); + } + + // Initialize idle system data + const idleData = idleSystem.initializePlayerData(userId); + if (!playerData.idleSystem) { + playerData.idleSystem = { + lastActive: new Date().toISOString(), + totalOfflineTime: 0, + totalIdleCredits: 0 + }; + migrated = true; + migrationLog.push('Added idle system data'); + } + + // Save if migration occurred + if (migrated) { + await playerData.save(); + console.log(`[GAME SERVER] Migration completed for ${username}: ${migrationLog.join(', ')}`); + } + + // Update existing player login info + playerData.username = username; + playerData.lastLogin = new Date(); + await playerData.save(); + } + + return playerData; + } catch (error) { + console.error('[GAME SERVER] Error loading player data:', error); + return null; + } +} + +// Save player data to database +async function savePlayerData(userId, playerData) { + try { + // Check if database is connected + if (mongoose.connection.readyState !== 1) { + console.error('[GAME SERVER] Database not connected, cannot save player data'); + return false; + } + + await PlayerData.findOneAndUpdate( + { userId }, + playerData, + { upsert: true, new: true } + ); + console.log(`[GAME SERVER] Saved player data for ${playerData.username}`); + return true; + } catch (error) { + console.error('[GAME SERVER] Error saving player data:', error); + return false; + } +} + // Update player count on API server async function updatePlayerCountOnAPI() { try { @@ -290,9 +1111,13 @@ const PORT = process.env.PORT || 3002; async function startServer() { try { + console.log('[GAME SERVER] Attempting to connect to database...'); + // Connect to database await connectDB(); + console.log('[GAME SERVER] Database connection established'); + server.listen(PORT, async () => { console.log(`[GAME SERVER] Game server running on port ${PORT}`); console.log(`[GAME SERVER] Socket.IO ready for multiplayer connections`); @@ -302,6 +1127,43 @@ async function startServer() { // Start periodic player count updates setInterval(updatePlayerCountOnAPI, 30000); // Update every 30 seconds + + // Start online idle rewards generation (every 10 seconds) + setInterval(async () => { + for (const [socketId, clientData] of connectedClients.entries()) { + if (clientData.userId && clientData.playerData) { + try { + // Update playTime for active players + const sessionTime = clientData.playerData.updatePlayTime(); + console.log(`[GAME SERVER] Updated playTime for ${clientData.username}: +${sessionTime}ms, Total: ${clientData.playerData.stats.playTime}ms`); + + // Send playTime update to client + io.to(socketId).emit('playTimeUpdated', { + playTime: clientData.playerData.stats.playTime, + sessionTime: sessionTime + }); + + const onlineRewards = idleSystem.generateOnlineIdleRewards(clientData.userId, 10000); // 10 seconds + + if (onlineRewards.credits > 0) { + // Update player data with online rewards + clientData.playerData.stats.credits = (clientData.playerData.stats.credits || 0) + onlineRewards.credits; + clientData.playerData.stats.experience = (clientData.playerData.stats.experience || 0) + onlineRewards.experience; + + // Send update to client + io.to(socketId).emit('onlineIdleRewards', { + credits: onlineRewards.credits, + experience: onlineRewards.experience, + newBalance: clientData.playerData.stats.credits, + playTime: clientData.playerData.stats.playTime + }); + } + } catch (error) { + console.error(`[GAME SERVER] Error generating online idle rewards for ${socketId}:`, error); + } + } + } + }, 10000); // Every 10 seconds }); } catch (error) { console.error('[GAME SERVER] Failed to start server:', error); diff --git a/GameServer/systems/DungeonSystem.js b/GameServer/systems/DungeonSystem.js index d211e46..e163cb1 100644 --- a/GameServer/systems/DungeonSystem.js +++ b/GameServer/systems/DungeonSystem.js @@ -8,205 +8,1028 @@ class DungeonSystem { this.dungeons = new Map(); this.instances = new Map(); // instanceId -> dungeon instance this.playerInstances = new Map(); // userId -> instanceId + + // Initialize room templates + this.roomTypes = { + entrance: { name: 'Entrance', enemies: 0, rewards: false }, + corridor: { name: 'Corridor', enemies: 1, rewards: false }, + chamber: { name: 'Chamber', enemies: 2, rewards: true }, + treasure: { name: 'Treasure Room', enemies: 0, rewards: true }, + boss: { name: 'Boss Room', enemies: 1, rewards: true, isBoss: true }, + exit: { name: 'Exit', enemies: 0, rewards: false } + }; + + // Initialize enemy templates + this.enemyTemplates = { + // Original enemies + training_drone: { + name: 'Training Drone', + health: 10, + attack: 5, + defense: 2, + experience: 5, + credits: 3 + }, + practice_target: { + name: 'Practice Target', + health: 5, + attack: 0, + defense: 0, + experience: 2, + credits: 1 + }, + alien_guardian: { + name: 'Alien Guardian', + health: 50, + attack: 8, + defense: 5, + experience: 25, + credits: 15 + }, + ancient_drone: { + name: 'Ancient Drone', + health: 30, + attack: 12, + defense: 2, + experience: 20, + credits: 10 + }, + crystal_golem: { + name: 'Crystal Golem', + health: 80, + attack: 6, + defense: 10, + experience: 35, + credits: 25 + }, + space_pirate: { + name: 'Space Pirate', + health: 25, + attack: 10, + defense: 3, + experience: 15, + credits: 12 + }, + pirate_captain: { + name: 'Pirate Captain', + health: 40, + attack: 15, + defense: 6, + experience: 30, + credits: 20 + }, + defense_turret: { + name: 'Defense Turret', + health: 20, + attack: 18, + defense: 8, + experience: 18, + credits: 8 + }, + security_drone: { + name: 'Security Drone', + health: 35, + attack: 14, + defense: 4, + experience: 22, + credits: 15 + }, + corrupted_ai: { + name: 'Corrupted AI', + health: 60, + attack: 20, + defense: 2, + experience: 40, + credits: 30 + }, + virus_program: { + name: 'Virus Program', + health: 15, + attack: 25, + defense: 1, + experience: 20, + credits: 12 + }, + mining_drone: { + name: 'Mining Drone', + health: 20, + attack: 8, + defense: 3, + experience: 12, + credits: 8 + }, + rock_creature: { + name: 'Rock Creature', + health: 45, + attack: 6, + defense: 12, + experience: 25, + credits: 15 + }, + explosive_asteroid: { + name: 'Explosive Asteroid', + health: 10, + attack: 30, + defense: 0, + experience: 15, + credits: 5 + }, + energy_being: { + name: 'Energy Being', + health: 55, + attack: 22, + defense: 3, + experience: 45, + credits: 35 + }, + phase_shifter: { + name: 'Phase Shifter', + health: 30, + attack: 28, + defense: 1, + experience: 35, + credits: 25 + }, + quantum_entity: { + name: 'Quantum Entity', + health: 70, + attack: 35, + defense: 5, + experience: 60, + credits: 50 + }, + + // NEW ENEMIES - Space Theme + maintenance_drone: { + name: 'Maintenance Drone', + health: 28, + attack: 11, + defense: 4, + experience: 18, + credits: 12 + }, + security_android: { + name: 'Security Android', + health: 42, + attack: 16, + defense: 7, + experience: 28, + credits: 20 + }, + station_ai: { + name: 'Station AI', + health: 65, + attack: 24, + defense: 3, + experience: 45, + credits: 32 + }, + ice_elemental: { + name: 'Ice Elemental', + health: 38, + attack: 18, + defense: 8, + experience: 32, + credits: 24 + }, + frost_wyrm: { + name: 'Frost Wyrm', + health: 72, + attack: 26, + defense: 6, + experience: 52, + credits: 38 + }, + gravity_wraith: { + name: 'Gravity Wraith', + health: 85, + attack: 32, + defense: 4, + experience: 68, + credits: 48 + }, + void_stalker: { + name: 'Void Stalker', + health: 78, + attack: 38, + defense: 5, + experience: 72, + credits: 52 + }, + singularity_spawn: { + name: 'Singularity Spawn', + health: 95, + attack: 42, + defense: 8, + experience: 85, + credits: 65 + }, + plasma_elemental: { + name: 'Plasma Elemental', + health: 58, + attack: 28, + defense: 4, + experience: 48, + credits: 35 + }, + solar_guardian: { + name: 'Solar Guardian', + health: 82, + attack: 34, + defense: 7, + experience: 65, + credits: 48 + }, + fusion_core: { + name: 'Fusion Core', + health: 68, + attack: 30, + defense: 12, + experience: 58, + credits: 42 + }, + scrap_golem: { + name: 'Scrap Golem', + health: 35, + attack: 14, + defense: 9, + experience: 22, + credits: 16 + }, + hull_breacher: { + name: 'Hull Breacher', + health: 32, + attack: 20, + defense: 3, + experience: 26, + credits: 18 + }, + salage_drone: { + name: 'Salvage Drone', + health: 22, + attack: 12, + defense: 5, + experience: 16, + credits: 11 + }, + + // NEW ENEMIES - Planet Theme + plant_beast: { + name: 'Plant Beast', + health: 48, + attack: 15, + defense: 8, + experience: 35, + credits: 26 + }, + tribal_warrior: { + name: 'Tribal Warrior', + health: 38, + attack: 18, + defense: 6, + experience: 28, + credits: 20 + }, + jungle_spirit: { + name: 'Jungle Spirit', + health: 55, + attack: 22, + defense: 4, + experience: 42, + credits: 30 + }, + sand_worm: { + name: 'Sand Worm', + health: 75, + attack: 28, + defense: 9, + experience: 58, + credits: 42 + }, + mummy_guardian: { + name: 'Mummy Guardian', + health: 62, + attack: 24, + defense: 7, + experience: 48, + credits: 35 + }, + heat_elemental: { + name: 'Heat Elemental', + health: 52, + attack: 26, + defense: 3, + experience: 45, + credits: 32 + }, + lava_elemental: { + name: 'Lava Elemental', + health: 68, + attack: 30, + defense: 5, + experience: 55, + credits: 40 + }, + fire_demon: { + name: 'Fire Demon', + health: 72, + attack: 32, + defense: 6, + experience: 62, + credits: 45 + }, + magma_beast: { + name: 'Magma Beast', + health: 85, + attack: 28, + defense: 12, + experience: 68, + credits: 50 + }, + cryo_mutant: { + name: 'Cryo Mutant', + health: 45, + attack: 20, + defense: 7, + experience: 38, + credits: 28 + }, + frost_android: { + name: 'Frost Android', + health: 52, + attack: 22, + defense: 8, + experience: 42, + credits: 30 + }, + ice_wraith: { + name: 'Ice Wraith', + health: 58, + attack: 25, + defense: 4, + experience: 48, + credits: 35 + }, + swamp_beast: { + name: 'Swamp Beast', + health: 35, + attack: 16, + defense: 9, + experience: 24, + credits: 18 + }, + toxic_spitter: { + name: 'Toxic Spitter', + health: 28, + attack: 19, + defense: 3, + experience: 22, + credits: 16 + }, + mud_golem: { + name: 'Mud Golem', + health: 42, + attack: 12, + defense: 11, + experience: 26, + credits: 19 + }, + + // NEW ENEMIES - Technology Theme + glitch_wraith: { + name: 'Glitch Wraith', + health: 48, + attack: 26, + defense: 2, + experience: 45, + credits: 32 + }, + firewall_guardian: { + name: 'Firewall Guardian', + health: 65, + attack: 22, + defense: 8, + experience: 52, + credits: 38 + }, + data_vampire: { + name: 'Data Vampire', + health: 38, + attack: 30, + defense: 3, + experience: 35, + credits: 26 + }, + assembly_drone: { + name: 'Assembly Drone', + health: 32, + attack: 15, + defense: 6, + experience: 24, + credits: 17 + }, + welder_bot: { + name: 'Welder Bot', + health: 28, + attack: 20, + defense: 4, + experience: 22, + credits: 15 + }, + factory_overseer: { + name: 'Factory Overseer', + health: 58, + attack: 24, + defense: 7, + experience: 46, + credits: 33 + }, + quantum_phantom: { + name: 'Quantum Phantom', + health: 78, + attack: 35, + defense: 4, + experience: 68, + credits: 50 + }, + particle_accelerator: { + name: 'Particle Accelerator', + health: 92, + attack: 40, + defense: 6, + experience: 85, + credits: 62 + }, + reality_bender: { + name: 'Reality Bender', + health: 88, + attack: 45, + defense: 3, + experience: 92, + credits: 68 + }, + sentinel_program: { + name: 'Sentinel Program', + health: 42, + attack: 21, + defense: 8, + experience: 32, + credits: 24 + }, + data_corruptor: { + name: 'Data Corruptor', + health: 35, + attack: 25, + defense: 3, + experience: 28, + credits: 20 + }, + system_guardian: { + name: 'System Guardian', + health: 55, + attack: 23, + defense: 9, + experience: 42, + credits: 30 + }, + + // NEW ENEMIES - Biome/Elemental Theme + shard_elemental: { + name: 'Shard Elemental', + health: 45, + attack: 19, + defense: 10, + experience: 38, + credits: 28 + }, + resonance_beast: { + name: 'Resonance Beast', + health: 52, + attack: 22, + defense: 6, + experience: 42, + credits: 30 + }, + mutant_horror: { + name: 'Mutant Horror', + health: 68, + attack: 28, + defense: 5, + experience: 58, + credits: 42 + }, + toxic_slime: { + name: 'Toxic Slime', + health: 42, + attack: 18, + defense: 8, + experience: 32, + credits: 24 + }, + radiation_beast: { + name: 'Radiation Beast', + health: 75, + attack: 30, + defense: 4, + experience: 65, + credits: 48 + }, + shadow_demon: { + name: 'Shadow Demon', + health: 72, + attack: 34, + defense: 3, + experience: 68, + credits: 50 + }, + nightmare_stalker: { + name: 'Nightmare Stalker', + health: 85, + attack: 38, + defense: 5, + experience: 78, + credits: 58 + }, + void_walker: { + name: 'Void Walker', + health: 92, + attack: 42, + defense: 7, + experience: 88, + credits: 65 + }, + temporal_paradox: { + name: 'Temporal Paradox', + health: 78, + attack: 40, + defense: 4, + experience: 75, + credits: 55 + }, + future_soldier: { + name: 'Future Soldier', + health: 65, + attack: 32, + defense: 9, + experience: 58, + credits: 42 + }, + past_guardian: { + name: 'Past Guardian', + health: 70, + attack: 28, + defense: 12, + experience: 62, + credits: 45 + }, + + // NEW ENEMIES - Military/War Theme + enemy_soldier: { + name: 'Enemy Soldier', + health: 45, + attack: 20, + defense: 7, + experience: 35, + credits: 26 + }, + combat_drone: { + name: 'Combat Drone', + health: 38, + attack: 22, + defense: 5, + experience: 30, + credits: 22 + }, + field_commander: { + name: 'Field Commander', + health: 62, + attack: 28, + defense: 9, + experience: 52, + credits: 38 + }, + turret_system: { + name: 'Turret System', + health: 48, + attack: 26, + defense: 10, + experience: 38, + credits: 28 + }, + combat_android: { + name: 'Combat Android', + health: 55, + attack: 24, + defense: 8, + experience: 45, + credits: 33 + }, + base_commander: { + name: 'Base Commander', + health: 72, + attack: 30, + defense: 11, + experience: 62, + credits: 45 + }, + weapon_drone: { + name: 'Weapon Drone', + health: 42, + attack: 28, + defense: 4, + experience: 38, + credits: 28 + }, + test_subject: { + name: 'Test Subject', + health: 58, + attack: 25, + defense: 6, + experience: 48, + credits: 35 + }, + chief_scientist: { + name: 'Chief Scientist', + health: 35, + attack: 32, + defense: 3, + experience: 42, + credits: 30 + }, + + // NEW ENEMIES - Special/Unique Theme + nightmare_creature: { + name: 'Nightmare Creature', + health: 62, + attack: 28, + defense: 5, + experience: 55, + credits: 40 + }, + dream_guardian: { + name: 'Dream Guardian', + health: 68, + attack: 30, + defense: 8, + experience: 58, + credits: 42 + }, + subconscious_demon: { + name: 'Subconscious Demon', + health: 75, + attack: 34, + defense: 4, + experience: 68, + credits: 50 + }, + memory_fragment: { + name: 'Memory Fragment', + health: 48, + attack: 26, + defense: 6, + experience: 45, + credits: 33 + }, + forgetfulness_demon: { + name: 'Forgetfulness Demon', + health: 55, + attack: 30, + defense: 3, + experience: 48, + credits: 35 + }, + nostalgia_spirit: { + name: 'Nostalgia Spirit', + health: 52, + attack: 24, + defense: 9, + experience: 42, + credits: 30 + }, + rift_demon: { + name: 'Rift Demon', + health: 88, + attack: 44, + defense: 5, + experience: 92, + credits: 68 + }, + dimensional_hunter: { + name: 'Dimensional Hunter', + health: 95, + attack: 48, + defense: 8, + experience: 105, + credits: 78 + }, + reality_tear: { + name: 'Reality Tear', + health: 102, + attack: 52, + defense: 3, + experience: 115, + credits: 85 + } + }; + this.initializeDungeons(); } initializeDungeons() { // Tutorial Dungeon - this.addDungeon('tutorial_caverns', { - name: 'Tutorial Caverns', - description: 'Learn the basics of dungeon crawling', - difficulty: 'easy', + this.addDungeon('tutorial', { + name: 'Tutorial Dungeon', + description: 'Learn the basics of dungeon exploration in this guided tutorial', + difficulty: 'tutorial', + enemyTypes: ['training_drone', 'practice_target'], + rewardMultiplier: 0.5, + oneTimeOnly: true, + healthType: 'player', // Ground mission + energyCost: 0, 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 - } + estimatedTime: 15 }); - // Combat Dungeon - this.addDungeon('abandoned_mines', { - name: 'Abandoned Mines', - description: 'Dangerous mines filled with hostile creatures', + // Space Theme Dungeons + this.addDungeon('alien_ruins', { + name: 'Alien Ruins', + description: 'Ancient alien structures filled with mysterious technology', difficulty: 'medium', + enemyTypes: ['alien_guardian', 'ancient_drone', 'crystal_golem'], + rewardMultiplier: 1.2, + healthType: 'player', // Ground mission + energyCost: 20, 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' - } + estimatedTime: 25 }); - // 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' - } + this.addDungeon('pirate_lair', { + name: 'Pirate Lair', + description: 'Dangerous pirate hideouts with valuable loot', + difficulty: 'easy', + enemyTypes: ['space_pirate', 'pirate_captain', 'defense_turret'], + rewardMultiplier: 1.0, + healthType: 'ship', // Space mission + energyCost: 15, + minLevel: 3, + maxLevel: 10, + maxPlayers: 4, + estimatedTime: 20 }); - // Raid Dungeon - this.addDungeon('dragon_lair', { - name: 'Dragon Lair', - description: 'Face the ultimate dragon challenge', + this.addDungeon('corrupted_vault', { + name: 'Corrupted AI Vault', + description: ' malfunctioning AI facilities with corrupted security', 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 - } + enemyTypes: ['security_drone', 'corrupted_ai', 'virus_program'], + rewardMultiplier: 1.5, + healthType: 'ship', // Space mission + energyCost: 25, + minLevel: 10, + maxLevel: 20, + maxPlayers: 4, + estimatedTime: 35 }); + + this.addDungeon('asteroid_mine', { + name: 'Asteroid Mine', + description: 'Abandoned mining facilities in asteroid fields', + difficulty: 'easy', + enemyTypes: ['mining_drone', 'rock_creature', 'explosive_asteroid'], + rewardMultiplier: 0.8, + healthType: 'ship', // Space mission + energyCost: 10, + minLevel: 2, + maxLevel: 8, + maxPlayers: 4, + estimatedTime: 18 + }); + + this.addDungeon('nebula_anomaly', { + name: 'Nebula Anomaly', + description: 'Strange energy anomalies in deep space', + difficulty: 'extreme', + enemyTypes: ['energy_being', 'phase_shifter', 'quantum_entity'], + rewardMultiplier: 2.0, + healthType: 'ship', // Space mission + energyCost: 30, + minLevel: 15, + maxLevel: 25, + maxPlayers: 4, + estimatedTime: 40 + }); + + this.addDungeon('space_station', { + name: 'Abandoned Space Station', + description: 'A derelict space station floating in the void', + difficulty: 'medium', + enemyTypes: ['maintenance_drone', 'security_android', 'station_ai'], + rewardMultiplier: 1.3, + healthType: 'player', + energyCost: 22, + minLevel: 6, + maxLevel: 16, + maxPlayers: 4, + estimatedTime: 28 + }); + + this.addDungeon('comet_core', { + name: 'Comet Core', + description: 'The frozen heart of a passing comet', + difficulty: 'hard', + enemyTypes: ['ice_elemental', 'frost_wyrm', 'crystal_guardian'], + rewardMultiplier: 1.6, + healthType: 'ship', + energyCost: 28, + minLevel: 12, + maxLevel: 22, + maxPlayers: 4, + estimatedTime: 32 + }); + + this.addDungeon('black_hole_perimeter', { + name: 'Black Hole Perimeter', + description: 'Dangerous space near a black hole event horizon', + difficulty: 'extreme', + enemyTypes: ['gravity_wraith', 'void_stalker', 'singularity_spawn'], + rewardMultiplier: 2.5, + healthType: 'ship', + energyCost: 35, + minLevel: 18, + maxLevel: 30, + maxPlayers: 4, + estimatedTime: 45 + }); + + this.addDungeon('star_forge', { + name: 'Star Forge', + description: 'Ancient facility that harnesses stellar energy', + difficulty: 'hard', + enemyTypes: ['plasma_elemental', 'solar_guardian', 'fusion_core'], + rewardMultiplier: 1.8, + healthType: 'ship', + energyCost: 30, + minLevel: 14, + maxLevel: 24, + maxPlayers: 4, + estimatedTime: 35 + }); + + this.addDungeon('debris_field', { + name: 'Ship Debris Field', + description: 'Graveyard of destroyed spacecraft', + difficulty: 'easy', + enemyTypes: ['scrap_golem', 'hull_breacher', 'salage_drone'], + rewardMultiplier: 0.9, + healthType: 'ship', + energyCost: 12, + minLevel: 3, + maxLevel: 9, + maxPlayers: 4, + estimatedTime: 20 + }); + + // Planet Theme Dungeons + this.addDungeon('jungle_temple', { + name: 'Jungle Temple', + description: 'Overgrown temple hidden in dense alien jungle', + difficulty: 'medium', + enemyTypes: ['plant_beast', 'tribal_warrior', 'jungle_spirit'], + rewardMultiplier: 1.4, + healthType: 'player', + energyCost: 25, + minLevel: 7, + maxLevel: 17, + maxPlayers: 4, + estimatedTime: 30 + }); + + this.addDungeon('desert_pyramid', { + name: 'Desert Pyramid', + description: 'Ancient pyramid buried under endless sand dunes', + difficulty: 'hard', + enemyTypes: ['sand_worm', 'mummy_guardian', 'heat_elemental'], + rewardMultiplier: 1.7, + healthType: 'player', + energyCost: 28, + minLevel: 13, + maxLevel: 23, + maxPlayers: 4, + estimatedTime: 33 + }); + + this.addDungeon('volcanic_caverns', { + name: 'Volcanic Caverns', + description: 'Molten caverns deep within an active volcano', + difficulty: 'hard', + enemyTypes: ['lava_elemental', 'fire_demon', 'magma_beast'], + rewardMultiplier: 1.6, + healthType: 'player', + energyCost: 26, + minLevel: 12, + maxLevel: 22, + maxPlayers: 4, + estimatedTime: 32 + }); + + this.addDungeon('arctic_research', { + name: 'Arctic Research Base', + description: 'Frozen research facility with failed experiments', + difficulty: 'medium', + enemyTypes: ['cryo_mutant', 'frost_android', 'ice_wraith'], + rewardMultiplier: 1.5, + healthType: 'player', + energyCost: 24, + minLevel: 8, + maxLevel: 18, + maxPlayers: 4, + estimatedTime: 29 + }); + + this.addDungeon('swamp_lair', { + name: 'Swamp Lair', + description: 'Murky swamp inhabited by strange creatures', + difficulty: 'easy', + enemyTypes: ['swamp_beast', 'toxic_spitter', 'mud_golem'], + rewardMultiplier: 1.1, + healthType: 'player', + energyCost: 18, + minLevel: 4, + maxLevel: 12, + maxPlayers: 4, + estimatedTime: 22 + }); + + // Technology Theme Dungeons + this.addDungeon('cyber_realm', { + name: 'Cyber Realm', + description: 'Virtual reality space corrupted by malware', + difficulty: 'hard', + enemyTypes: ['glitch_wraith', 'firewall_guardian', 'data_vampire'], + rewardMultiplier: 1.9, + healthType: 'player', + energyCost: 32, + minLevel: 16, + maxLevel: 26, + maxPlayers: 4, + estimatedTime: 38 + }); + + this.addDungeon('robot_factory', { + name: 'Robot Factory', + description: 'Automated factory producing hostile machines', + difficulty: 'medium', + enemyTypes: ['assembly_drone', 'welder_bot', 'factory_overseer'], + rewardMultiplier: 1.4, + healthType: 'player', + energyCost: 23, + minLevel: 9, + maxLevel: 19, + maxPlayers: 4, + estimatedTime: 27 + }); + + this.addDungeon('quantum_lab', { + name: 'Quantum Laboratory', + description: 'Research facility experimenting with quantum physics', + difficulty: 'extreme', + enemyTypes: ['quantum_phantom', 'particle_accelerator', 'reality_bender'], + rewardMultiplier: 2.3, + healthType: 'player', + energyCost: 38, + minLevel: 20, + maxLevel: 30, + maxPlayers: 4, + estimatedTime: 42 + }); + + this.addDungeon('server_farm', { + name: 'Server Farm', + description: 'Massive data center with rogue security programs', + difficulty: 'medium', + enemyTypes: ['sentinel_program', 'data_corruptor', 'system_guardian'], + rewardMultiplier: 1.3, + healthType: 'player', + energyCost: 21, + minLevel: 7, + maxLevel: 17, + maxPlayers: 4, + estimatedTime: 25 + }); + + console.log(`[DUNGEON SYSTEM] Initialized ${this.dungeons.size} dungeons`); } addDungeon(id, dungeon) { this.dungeons.set(id, { id, ...dungeon, - createdAt: new Date().toISOString() + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString() }); } @@ -218,6 +1041,36 @@ class DungeonSystem { return Array.from(this.dungeons.values()); } + getDungeonsGroupedByDifficulty() { + const dungeonsByDifficulty = { + tutorial: [], + easy: [], + medium: [], + hard: [], + extreme: [] + }; + + this.dungeons.forEach(dungeon => { + if (dungeonsByDifficulty[dungeon.difficulty]) { + dungeonsByDifficulty[dungeon.difficulty].push(dungeon); + } + }); + + return dungeonsByDifficulty; + } + + getRoomTypes() { + return this.roomTypes; + } + + getEnemyTemplates() { + return this.enemyTemplates; + } + + getEnemyTemplate(enemyId) { + return this.enemyTemplates[enemyId]; + } + getDungeonsByDifficulty(difficulty) { return Array.from(this.dungeons.values()).filter(dungeon => dungeon.difficulty === difficulty); } diff --git a/GameServer/systems/IdleSystem.js b/GameServer/systems/IdleSystem.js index 3f2343d..9926bfe 100644 --- a/GameServer/systems/IdleSystem.js +++ b/GameServer/systems/IdleSystem.js @@ -9,12 +9,20 @@ class IdleSystem { 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 + this.playerAchievements = new Map(); // userId -> achievements // 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 + credits: 0.1, // 1 credit every 10 seconds (0.1 per second) + experience: 0, // no auto experience - only from dungeons + energy: 1/300 // 1 energy every 5 minutes (1/300 per second) + }; + + // Offline rates (different from online rates) + this.offlineProductionRates = { + credits: 1/60, // 1 credit every 1 minute (1/60 per second) + experience: 0, // no experience offline - only from dungeons + energy: 1/300 // 1 energy every 5 minutes (same as online) }; // Idle rewards @@ -90,12 +98,13 @@ class IdleSystem { const productionRates = this.playerProductionRates.get(userId); const achievements = this.playerAchievements.get(userId); - // Calculate offline rewards + // Calculate offline rewards using offline rates const offlineSeconds = Math.floor(cappedOfflineTime / 1000); + const offlineRates = this.offlineProductionRates; const rewards = { - credits: Math.floor(productionRates.credits * offlineSeconds), - experience: Math.floor(productionRates.experience * offlineSeconds), - energy: Math.floor(productionRates.energy * offlineSeconds), + credits: Math.floor(offlineRates.credits * offlineSeconds), + experience: Math.floor(offlineRates.experience * offlineSeconds), + energy: Math.floor(offlineRates.energy * offlineSeconds), items: [] }; @@ -239,6 +248,27 @@ class IdleSystem { return results; } + // Generate online idle rewards (called periodically while player is online) + generateOnlineIdleRewards(userId, deltaTimeMs) { + const productionRates = this.playerProductionRates.get(userId); + if (!productionRates) { + return { + credits: 0, + experience: 0, + energy: 0 + }; + } + + const deltaTimeSeconds = deltaTimeMs / 1000; + const rewards = { + credits: Math.floor(productionRates.credits * deltaTimeSeconds), + experience: Math.floor(productionRates.experience * deltaTimeSeconds), + energy: Math.floor(productionRates.energy * deltaTimeSeconds) + }; + + return rewards; + } + // Reset idle data for a user resetPlayerData(userId) { this.playerLastActive.delete(userId); diff --git a/GameServer/systems/ItemSystem.js b/GameServer/systems/ItemSystem.js new file mode 100644 index 0000000..7c6c9a0 --- /dev/null +++ b/GameServer/systems/ItemSystem.js @@ -0,0 +1,618 @@ +/** + * Galaxy Strike Online - Item System + * Centralized item management for shop, inventory, dungeons, and all game systems + */ + +class ItemSystem { + constructor() { + // Master item catalog - single source of truth + this.itemCatalog = { + // Ships + ships: [ + { + id: 'starter_cruiser_common', + name: 'Starter Cruiser', + type: 'ship', + rarity: 'common', + price: 5000, + currency: 'credits', + description: 'Reliable starter cruiser for new pilots', + texture: 'assets/textures/ships/starter_cruiser.png', + stats: { attack: 15, speed: 10, defense: 12, hull: 100 }, + categories: ['shop', 'dungeon_reward'], + requirements: { level: 1 }, + stackable: false + }, + { + id: 'starter_cruiser_uncommon', + name: 'Starter Cruiser II', + type: 'ship', + rarity: 'uncommon', + price: 12000, + currency: 'credits', + description: 'Upgraded starter cruiser with enhanced systems', + texture: 'assets/textures/ships/starter_cruiser.png', + stats: { attack: 18, speed: 12, defense: 15, hull: 120 }, + categories: ['shop', 'dungeon_reward'], + requirements: { level: 5 }, + stackable: false + }, + { + id: 'starter_cruiser_rare', + name: 'Starter Cruiser III', + type: 'ship', + rarity: 'rare', + price: 25000, + currency: 'credits', + description: 'Elite starter cruiser with maximum upgrades', + texture: 'assets/textures/ships/starter_cruiser.png', + stats: { attack: 22, speed: 15, defense: 18, hull: 150 }, + categories: ['shop', 'dungeon_reward'], + requirements: { level: 10 }, + stackable: false + }, + { + id: 'interceptor_common', + name: 'Interceptor', + type: 'ship', + rarity: 'common', + price: 8000, + currency: 'credits', + description: 'Fast attack ship for hit-and-run tactics', + texture: 'assets/textures/ships/interceptor.png', + stats: { attack: 12, speed: 18, defense: 8, hull: 80 }, + categories: ['shop', 'dungeon_reward'], + requirements: { level: 3 }, + stackable: false + }, + { + id: 'interceptor_uncommon', + name: 'Interceptor II', + type: 'ship', + rarity: 'uncommon', + price: 18000, + currency: 'credits', + description: 'Enhanced interceptor with improved weapons', + texture: 'assets/textures/ships/interceptor.png', + stats: { attack: 15, speed: 22, defense: 10, hull: 95 }, + categories: ['shop', 'dungeon_reward'], + requirements: { level: 7 }, + stackable: false + } + ], + + // Materials + materials: [ + { + id: 'steel_plating', + name: 'Steel Plating', + type: 'material', + rarity: 'common', + price: 100, + currency: 'credits', + description: 'Basic armor material for ship upgrades', + categories: ['shop', 'dungeon_loot', 'crafting'], + requirements: { level: 1 }, + stackable: true, + maxStack: 99, + effects: { defense: 5 } + }, + { + id: 'energy_core', + name: 'Energy Core', + type: 'material', + rarity: 'uncommon', + price: 250, + currency: 'credits', + description: 'Power source for advanced upgrades', + categories: ['shop', 'dungeon_loot', 'crafting'], + requirements: { level: 5 }, + stackable: true, + maxStack: 50, + effects: { energy: 10 } + }, + { + id: 'quantum_fuel', + name: 'Quantum Fuel', + type: 'material', + rarity: 'rare', + price: 500, + currency: 'credits', + description: 'Advanced fuel for long-range travel', + categories: ['shop', 'dungeon_loot', 'crafting'], + requirements: { level: 10 }, + stackable: true, + maxStack: 25, + effects: { speed: 15 } + }, + { + id: 'dark_matter', + name: 'Dark Matter', + type: 'material', + rarity: 'legendary', + price: 2000, + currency: 'gems', + description: 'Exotic matter for ultimate upgrades', + categories: ['shop', 'dungeon_loot', 'crafting'], + requirements: { level: 15 }, + stackable: true, + maxStack: 10, + effects: { attack: 20, defense: 20 } + } + ], + + // Consumables + consumables: [ + { + id: 'repair_kit', + name: 'Repair Kit', + type: 'consumable', + rarity: 'common', + price: 50, + currency: 'credits', + description: 'Instantly repairs ship damage', + categories: ['shop', 'dungeon_loot'], + requirements: { level: 1 }, + stackable: true, + maxStack: 20, + effects: { hull_repair: 50 }, + consumable: true, + cooldown: 30000 // 30 seconds + }, + { + id: 'energy_boost', + name: 'Energy Boost', + type: 'consumable', + rarity: 'common', + price: 75, + currency: 'credits', + description: 'Temporary energy increase', + categories: ['shop', 'dungeon_loot'], + requirements: { level: 1 }, + stackable: true, + maxStack: 15, + effects: { energy_boost: 25, duration: 60000 }, // 1 minute + consumable: true, + cooldown: 45000 // 45 seconds + }, + { + id: 'shield_booster', + name: 'Shield Booster', + type: 'consumable', + rarity: 'uncommon', + price: 150, + currency: 'credits', + description: 'Temporary shield enhancement', + categories: ['shop', 'dungeon_loot'], + requirements: { level: 5 }, + stackable: true, + maxStack: 10, + effects: { shield_boost: 50, duration: 90000 }, // 1.5 minutes + consumable: true, + cooldown: 60000 // 1 minute + }, + { + id: 'damage_amplifier', + name: 'Damage Amplifier', + type: 'consumable', + rarity: 'rare', + price: 300, + currency: 'credits', + description: 'Increases damage output temporarily', + categories: ['shop', 'dungeon_loot'], + requirements: { level: 8 }, + stackable: true, + maxStack: 5, + effects: { damage_multiplier: 1.5, duration: 45000 }, // 45 seconds + consumable: true, + cooldown: 120000 // 2 minutes + } + ], + + // Cosmetics + cosmetics: [ + { + id: 'red_paint', + name: 'Red Paint Job', + type: 'cosmetic', + rarity: 'common', + price: 500, + currency: 'credits', + description: 'Red color scheme for your ship', + categories: ['shop'], + requirements: { level: 1 }, + stackable: false, + cosmetic: true, + slot: 'paint' + }, + { + id: 'blue_paint', + name: 'Blue Paint Job', + type: 'cosmetic', + rarity: 'common', + price: 500, + currency: 'credits', + description: 'Blue color scheme for your ship', + categories: ['shop'], + requirements: { level: 1 }, + stackable: false, + cosmetic: true, + slot: 'paint' + }, + { + id: 'gold_trim', + name: 'Gold Trim', + type: 'cosmetic', + rarity: 'uncommon', + price: 1000, + currency: 'credits', + description: 'Gold accent trim for your ship', + categories: ['shop'], + requirements: { level: 5 }, + stackable: false, + cosmetic: true, + slot: 'trim' + }, + { + id: 'rainbow_effect', + name: 'Rainbow Engine Effect', + type: 'cosmetic', + rarity: 'rare', + price: 2500, + currency: 'credits', + description: 'Colorful engine trail effect', + categories: ['shop'], + requirements: { level: 10 }, + stackable: false, + cosmetic: true, + slot: 'engine' + } + ], + + // Weapons + weapons: [ + { + id: 'laser_cannon_common', + name: 'Laser Cannon', + type: 'weapon', + rarity: 'common', + price: 1000, + currency: 'credits', + description: 'Basic laser weapon for beginners', + categories: ['shop', 'dungeon_loot'], + requirements: { level: 1 }, + stackable: false, + stats: { attack: 10, range: 100, fire_rate: 2 } + }, + { + id: 'plasma_rifle_uncommon', + name: 'Plasma Rifle', + type: 'weapon', + rarity: 'uncommon', + price: 2500, + currency: 'credits', + description: 'Plasma-based weapon with increased damage', + categories: ['shop', 'dungeon_loot'], + requirements: { level: 5 }, + stackable: false, + stats: { attack: 18, range: 120, fire_rate: 1.5 } + }, + { + id: 'quantum_blaster_rare', + name: 'Quantum Blaster', + type: 'weapon', + rarity: 'rare', + price: 6000, + currency: 'credits', + description: 'Advanced quantum weapon with high damage output', + categories: ['shop', 'dungeon_loot'], + requirements: { level: 10 }, + stackable: false, + stats: { attack: 30, range: 150, fire_rate: 1 } + } + ], + + // Armors + armors: [ + { + id: 'basic_shield_common', + name: 'Basic Shield Generator', + type: 'armor', + rarity: 'common', + price: 800, + currency: 'credits', + description: 'Basic shield protection for beginners', + categories: ['shop', 'dungeon_loot'], + requirements: { level: 1 }, + stackable: false, + stats: { defense: 8, shield_capacity: 50, recharge_rate: 5 } + }, + { + id: 'energy_armor_uncommon', + name: 'Energy Armor', + type: 'armor', + rarity: 'uncommon', + price: 2000, + currency: 'credits', + description: 'Energy-based armor with enhanced protection', + categories: ['shop', 'dungeon_loot'], + requirements: { level: 5 }, + stackable: false, + stats: { defense: 15, shield_capacity: 100, recharge_rate: 8 } + }, + { + id: 'quantum_deflector_rare', + name: 'Quantum Deflector', + type: 'armor', + rarity: 'rare', + price: 5000, + currency: 'credits', + description: 'Advanced quantum armor with maximum protection', + categories: ['shop', 'dungeon_loot'], + requirements: { level: 10 }, + stackable: false, + stats: { defense: 25, shield_capacity: 200, recharge_rate: 12 } + } + ], + + // Dungeon-specific rewards + dungeon_rewards: [ + { + id: 'dungeon_key_basic', + name: 'Basic Dungeon Key', + type: 'key', + rarity: 'common', + price: 200, + currency: 'credits', + description: 'Key to enter basic dungeons', + categories: ['shop', 'dungeon_loot'], + requirements: { level: 1 }, + stackable: true, + maxStack: 10, + consumable: true, + dungeon_access: ['basic_dungeon', 'mines'] + }, + { + id: 'dungeon_key_advanced', + name: 'Advanced Dungeon Key', + type: 'key', + rarity: 'uncommon', + price: 800, + currency: 'credits', + description: 'Key to enter advanced dungeons', + categories: ['shop', 'dungeon_loot'], + requirements: { level: 8 }, + stackable: true, + maxStack: 5, + consumable: true, + dungeon_access: ['advanced_dungeon', 'fortress'] + }, + { + id: 'treasure_chest', + name: 'Treasure Chest', + type: 'container', + rarity: 'rare', + price: 0, + currency: 'credits', + description: 'Contains random valuable items', + categories: ['dungeon_loot'], + requirements: { level: 1 }, + stackable: true, + maxStack: 5, + consumable: true, + loot_table: 'treasure_chest_common' + } + ] + }; + + // Initialize item lookup maps for performance + this.itemLookup = new Map(); + this.categoryLookup = new Map(); + this.rarityLookup = new Map(); + + this.buildLookupMaps(); + } + + /** + * Build lookup maps for efficient item retrieval + */ + buildLookupMaps() { + // Build ID lookup + for (const [category, items] of Object.entries(this.itemCatalog)) { + for (const item of items) { + this.itemLookup.set(item.id, item); + + // Build category lookup + if (!this.categoryLookup.has(category)) { + this.categoryLookup.set(category, []); + } + this.categoryLookup.get(category).push(item); + + // Build rarity lookup + if (!this.rarityLookup.has(item.rarity)) { + this.rarityLookup.set(item.rarity, []); + } + this.rarityLookup.get(item.rarity).push(item); + } + } + } + + /** + * Get item by ID + */ + getItem(itemId) { + return this.itemLookup.get(itemId) || null; + } + + /** + * Get all items in a category + */ + getItemsByCategory(category) { + return this.categoryLookup.get(category) || []; + } + + /** + * Get items by rarity + */ + getItemsByRarity(rarity) { + return this.rarityLookup.get(rarity) || []; + } + + /** + * Get items available for specific context (shop, dungeon, etc.) + */ + getItemsByContext(context) { + const results = []; + + for (const item of this.itemLookup.values()) { + if (item.categories && item.categories.includes(context)) { + results.push(item); + } + } + + return results; + } + + /** + * Get shop items (items available in shop) + */ + getShopItems() { + return this.getItemsByContext('shop'); + } + + /** + * Get dungeon loot items + */ + getDungeonLootItems() { + return this.getItemsByContext('dungeon_loot'); + } + + /** + * Get dungeon reward items + */ + getDungeonRewardItems() { + return this.getItemsByContext('dungeon_reward'); + } + + /** + * Get crafting materials + */ + getCraftingMaterials() { + return this.getItemsByContext('crafting'); + } + + /** + * Check if player meets item requirements + */ + canPlayerUseItem(playerData, item) { + if (!item.requirements) return true; + + // Check level requirement + if (item.requirements.level && playerData.stats.level < item.requirements.level) { + return false; + } + + // Add other requirement checks here (skills, quests, etc.) + + return true; + } + + /** + * Get filtered items for player (based on requirements) + */ + getAvailableItemsForPlayer(playerData, context = null) { + let items = context ? this.getItemsByContext(context) : Array.from(this.itemLookup.values()); + + return items.filter(item => this.canPlayerUseItem(playerData, item)); + } + + /** + * Generate random loot based on rarity weights + */ + generateLoot(rarityWeights = null, context = null) { + const defaultWeights = { + common: 50, + uncommon: 30, + rare: 15, + legendary: 5 + }; + + const weights = rarityWeights || defaultWeights; + const availableItems = context ? this.getItemsByContext(context) : Array.from(this.itemLookup.values()); + + // Filter by rarity weights + const weightedItems = []; + for (const item of availableItems) { + const weight = weights[item.rarity] || 0; + for (let i = 0; i < weight; i++) { + weightedItems.push(item); + } + } + + if (weightedItems.length === 0) return null; + + // Random selection + const randomIndex = Math.floor(Math.random() * weightedItems.length); + return weightedItems[randomIndex]; + } + + /** + * Get item statistics + */ + getItemStats() { + const stats = { + totalItems: this.itemLookup.size, + byCategory: {}, + byRarity: {}, + byType: {} + }; + + for (const item of this.itemLookup.values()) { + // Count by category + for (const category of item.categories || []) { + stats.byCategory[category] = (stats.byCategory[category] || 0) + 1; + } + + // Count by rarity + stats.byRarity[item.rarity] = (stats.byRarity[item.rarity] || 0) + 1; + + // Count by type + stats.byType[item.type] = (stats.byType[item.type] || 0) + 1; + } + + return stats; + } + + /** + * Validate item data structure + */ + validateItem(item) { + const required = ['id', 'name', 'type', 'rarity']; + const missing = required.filter(field => !item[field]); + + if (missing.length > 0) { + return { valid: false, errors: [`Missing required fields: ${missing.join(', ')}`] }; + } + + const errors = []; + + // Validate type + if (!['ship', 'material', 'consumable', 'cosmetic', 'key', 'container'].includes(item.type)) { + errors.push(`Invalid item type: ${item.type}`); + } + + // Validate rarity + if (!['common', 'uncommon', 'rare', 'legendary'].includes(item.rarity)) { + errors.push(`Invalid rarity: ${item.rarity}`); + } + + // Validate stackable items + if (item.stackable && (!item.maxStack || item.maxStack < 1)) { + errors.push('Stackable items must have maxStack >= 1'); + } + + return { valid: errors.length === 0, errors }; + } +} + +module.exports = ItemSystem; diff --git a/GameServer/systems/QuestSystem.js b/GameServer/systems/QuestSystem.js index f976ca6..582ae61 100644 --- a/GameServer/systems/QuestSystem.js +++ b/GameServer/systems/QuestSystem.js @@ -30,6 +30,176 @@ class QuestSystem { timeLimit: null }); + // Main Story quests + this.addQuest('main_story_beginning', { + name: 'The Beginning', + description: 'Start your journey as a space pilot', + type: 'main', + difficulty: 'easy', + requirements: { + level: 1, + battlesWon: 1 + }, + rewards: { + experience: 200, + credits: 1000, + skillPoints: 2 + }, + prerequisites: [], + repeatable: false, + timeLimit: null + }); + + this.addQuest('main_story_first_dungeon', { + name: 'First Dungeon', + description: 'Complete your first dungeon run', + type: 'main', + difficulty: 'medium', + requirements: { + level: 5, + dungeonsCleared: 1 + }, + rewards: { + experience: 500, + credits: 2000, + items: ['rare_weapon'] + }, + prerequisites: ['main_story_beginning'], + repeatable: false, + timeLimit: null + }); + + this.addQuest('main_story_space_exploration', { + name: 'Space Explorer', + description: 'Explore 10 different sectors', + type: 'main', + difficulty: 'medium', + requirements: { + level: 10, + sectorsExplored: 10 + }, + rewards: { + experience: 1000, + credits: 5000, + items: ['explorer_badge'] + }, + prerequisites: ['main_story_first_dungeon'], + repeatable: false, + timeLimit: null + }); + + // Daily quests + this.addQuest('daily_battles', { + name: 'Daily Battles', + description: 'Win 5 battles today', + type: 'daily', + difficulty: 'easy', + requirements: { + battlesWon: 5 + }, + rewards: { + experience: 150, + credits: 750, + gems: 5 + }, + prerequisites: [], + repeatable: true, + timeLimit: 24 * 60 * 60 * 1000 // 24 hours + }); + + this.addQuest('daily_exploration', { + name: 'Daily Exploration', + description: 'Explore 3 new sectors today', + type: 'daily', + difficulty: 'easy', + requirements: { + sectorsExplored: 3 + }, + rewards: { + experience: 100, + credits: 500, + gems: 3 + }, + prerequisites: [], + repeatable: true, + timeLimit: 24 * 60 * 60 * 1000 // 24 hours + }); + + this.addQuest('daily_resources', { + name: 'Daily Resource Collection', + description: 'Collect 1000 resources today', + type: 'daily', + difficulty: 'medium', + requirements: { + resourcesCollected: 1000 + }, + rewards: { + experience: 200, + credits: 1000, + gems: 8 + }, + prerequisites: [], + repeatable: true, + timeLimit: 24 * 60 * 60 * 1000 // 24 hours + }); + + // Weekly quests + this.addQuest('weekly_champion', { + name: 'Weekly Champion', + description: 'Win 50 battles this week', + type: 'weekly', + difficulty: 'hard', + requirements: { + battlesWon: 50 + }, + rewards: { + experience: 2000, + credits: 10000, + gems: 50, + items: ['champion_title'] + }, + prerequisites: [], + repeatable: true, + timeLimit: 7 * 24 * 60 * 60 * 1000 // 7 days + }); + + this.addQuest('weekly_dungeon_master', { + name: 'Weekly Dungeon Master', + description: 'Complete 10 dungeons this week', + type: 'weekly', + difficulty: 'hard', + requirements: { + dungeonsCleared: 10 + }, + rewards: { + experience: 3000, + credits: 15000, + gems: 75, + items: ['dungeon_master_cape'] + }, + prerequisites: [], + repeatable: true, + timeLimit: 7 * 24 * 60 * 60 * 1000 // 7 days + }); + + this.addQuest('weekly_wealth_collector', { + name: 'Weekly Wealth Collector', + description: 'Earn 10000 credits this week', + type: 'weekly', + difficulty: 'medium', + requirements: { + creditsEarned: 10000 + }, + rewards: { + experience: 1500, + credits: 5000, + gems: 25 + }, + prerequisites: [], + repeatable: true, + timeLimit: 7 * 24 * 60 * 60 * 1000 // 7 days + }); + // Combat quests this.addQuest('warrior_path', { name: 'Warrior Path', @@ -152,18 +322,89 @@ class QuestSystem { initializePlayerData(userId) { if (!this.playerQuests.has(userId)) { - this.playerQuests.set(userId, { + const playerData = { activeQuests: new Map(), completedQuests: new Map(), questHistory: [], totalQuestsCompleted: 0, dailyQuestsCompleted: 0, weeklyQuestsCompleted: 0 - }); + }; + + // Assign starting quests to new players + this.assignStartingQuests(userId, playerData); + + this.playerQuests.set(userId, playerData); } return this.playerQuests.get(userId); } + assignStartingQuests(userId, playerData) { + console.log(`[QUEST SYSTEM] Assigning starting quests to player ${userId}`); + + // Assign main story quests + const mainStoryQuests = ['main_story_beginning', 'main_story_first_dungeon']; + mainStoryQuests.forEach(questId => { + const quest = this.quests.get(questId); + if (quest && !playerData.activeQuests.has(questId) && !playerData.completedQuests.has(questId)) { + playerData.activeQuests.set(questId, { + ...quest, + progress: 0, + startedAt: Date.now() + }); + console.log(`[QUEST SYSTEM] Assigned main story quest: ${quest.name}`); + } + }); + + // Assign daily quests + this.generateDailyQuests(userId, playerData); + + // Assign weekly quests + this.generateWeeklyQuests(userId, playerData); + + console.log(`[QUEST SYSTEM] Player now has ${playerData.activeQuests.size} active quests`); + } + + generateDailyQuests(userId, playerData) { + console.log(`[QUEST SYSTEM] Generating daily quests for player ${userId}`); + + const dailyQuestTemplates = ['daily_battles', 'daily_exploration', 'daily_resources']; + + dailyQuestTemplates.forEach(questId => { + const quest = this.quests.get(questId); + if (quest) { + playerData.activeQuests.set(questId, { + ...quest, + progress: 0, + startedAt: Date.now(), + type: 'daily', + resetTime: Date.now() + (24 * 60 * 60 * 1000) // 24 hours from now + }); + console.log(`[QUEST SYSTEM] Assigned daily quest: ${quest.name}`); + } + }); + } + + generateWeeklyQuests(userId, playerData) { + console.log(`[QUEST SYSTEM] Generating weekly quests for player ${userId}`); + + const weeklyQuestTemplates = ['weekly_champion', 'weekly_dungeon_master', 'weekly_wealth_collector']; + + weeklyQuestTemplates.forEach(questId => { + const quest = this.quests.get(questId); + if (quest) { + playerData.activeQuests.set(questId, { + ...quest, + progress: 0, + startedAt: Date.now(), + type: 'weekly', + resetTime: Date.now() + (7 * 24 * 60 * 60 * 1000) // 7 days from now + }); + console.log(`[QUEST SYSTEM] Assigned weekly quest: ${quest.name}`); + } + }); + } + getPlayerData(userId) { return this.playerQuests.get(userId) || this.initializePlayerData(userId); }