fixed server software
This commit is contained in:
parent
a166f27d23
commit
9df6b22721
@ -1,17 +1,18 @@
|
|||||||
# Game Server Configuration
|
# Game Server Configuration
|
||||||
PORT=3001
|
PORT=3002
|
||||||
NODE_ENV=production
|
NODE_ENV=production
|
||||||
|
|
||||||
# Database Configuration
|
# Database Configuration
|
||||||
MONGODB_URI=mongodb://localhost:27017/galaxystrikeonline
|
MONGODB_URI=mongodb://localhost:27017/galaxystrikeonline
|
||||||
|
|
||||||
# Optional: API Server URL for authentication validation
|
# API Server Configuration
|
||||||
API_SERVER_URL=http://localhost:3000
|
API_SERVER_URL=http://localhost:3000
|
||||||
|
GAME_SERVER_URL=https://dev.gameserver.galaxystrike.online
|
||||||
|
|
||||||
# Optional: Server identification
|
# Optional: Server identification
|
||||||
SERVER_NAME=Game Server
|
SERVER_NAME=Dev Game Server
|
||||||
SERVER_REGION=us-east
|
SERVER_REGION=local
|
||||||
MAX_PLAYERS=50
|
MAX_PLAYERS=8
|
||||||
|
|
||||||
# Logging
|
# Logging
|
||||||
LOG_LEVEL=info
|
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)
|
// Socket.IO handlers (based on LocalServer)
|
||||||
io.on('connection', (socket) => {
|
io.on('connection', (socket) => {
|
||||||
console.log('[GAME SERVER] Client connected:', socket.id);
|
console.log('[GAME SERVER] Client connected:', socket.id);
|
||||||
@ -251,16 +260,19 @@ io.on('connection', (socket) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Start server
|
// Start server
|
||||||
const PORT = process.env.PORT || 3001;
|
const PORT = process.env.PORT || 3002;
|
||||||
|
|
||||||
async function startServer() {
|
async function startServer() {
|
||||||
try {
|
try {
|
||||||
// Connect to database
|
// Connect to database
|
||||||
await connectDB();
|
await connectDB();
|
||||||
|
|
||||||
server.listen(PORT, () => {
|
server.listen(PORT, async () => {
|
||||||
console.log(`[GAME SERVER] Game server running on port ${PORT}`);
|
console.log(`[GAME SERVER] Game server running on port ${PORT}`);
|
||||||
console.log(`[GAME SERVER] Socket.IO ready for multiplayer connections`);
|
console.log(`[GAME SERVER] Socket.IO ready for multiplayer connections`);
|
||||||
|
|
||||||
|
// Register with API server
|
||||||
|
await registerWithAPIServer();
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[GAME SERVER] Failed to start server:', 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();
|
startServer();
|
||||||
|
|
||||||
module.exports = { app, server, io, gameInstances, connectedClients };
|
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