fixed server software
This commit is contained in:
parent
a166f27d23
commit
9df6b22721
@ -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
|
||||
|
||||
243
GameServer/routes/base.js
Normal file
243
GameServer/routes/base.js
Normal file
@ -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;
|
||||
212
GameServer/routes/crafting.js
Normal file
212
GameServer/routes/crafting.js
Normal file
@ -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;
|
||||
266
GameServer/routes/dungeons.js
Normal file
266
GameServer/routes/dungeons.js
Normal file
@ -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;
|
||||
282
GameServer/routes/idle.js
Normal file
282
GameServer/routes/idle.js
Normal file
@ -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;
|
||||
232
GameServer/routes/quests.js
Normal file
232
GameServer/routes/quests.js
Normal file
@ -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;
|
||||
452
GameServer/routes/ships.js
Normal file
452
GameServer/routes/ships.js
Normal file
@ -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;
|
||||
187
GameServer/routes/skills.js
Normal file
187
GameServer/routes/skills.js
Normal file
@ -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;
|
||||
@ -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 };
|
||||
|
||||
512
GameServer/systems/BaseSystem.js
Normal file
512
GameServer/systems/BaseSystem.js
Normal file
@ -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;
|
||||
277
GameServer/systems/CraftingSystem.js
Normal file
277
GameServer/systems/CraftingSystem.js
Normal file
@ -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;
|
||||
507
GameServer/systems/DungeonSystem.js
Normal file
507
GameServer/systems/DungeonSystem.js
Normal file
@ -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;
|
||||
272
GameServer/systems/IdleSystem.js
Normal file
272
GameServer/systems/IdleSystem.js
Normal file
@ -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;
|
||||
439
GameServer/systems/QuestSystem.js
Normal file
439
GameServer/systems/QuestSystem.js
Normal file
@ -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;
|
||||
488
GameServer/systems/ShipSystem.js
Normal file
488
GameServer/systems/ShipSystem.js
Normal file
@ -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;
|
||||
397
GameServer/systems/SkillSystem.js
Normal file
397
GameServer/systems/SkillSystem.js
Normal file
@ -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;
|
||||
Reference in New Issue
Block a user