diff --git a/API/.env.example b/API/.env.example
new file mode 100644
index 0000000..d229c52
--- /dev/null
+++ b/API/.env.example
@@ -0,0 +1,18 @@
+# Server Configuration
+PORT=3001
+NODE_ENV=development
+
+# Database Configuration
+MONGODB_URI=mongodb://localhost:27017/galaxystrikeonline
+
+# JWT Configuration
+JWT_SECRET=your_jwt_secret_key_here
+
+# Redis Configuration (optional)
+REDIS_URL=redis://localhost:6379
+
+# Client URL
+CLIENT_URL=http://localhost:3000
+
+# Logging
+LOG_LEVEL=info
diff --git a/API/config/database.js b/API/config/database.js
new file mode 100644
index 0000000..25558f4
--- /dev/null
+++ b/API/config/database.js
@@ -0,0 +1,19 @@
+const mongoose = require('mongoose');
+const logger = require('../utils/logger');
+
+const connectDB = async () => {
+ try {
+ const mongoUri = process.env.MONGODB_URI || 'mongodb://localhost:27017/galaxystrikeonline';
+
+ const conn = await mongoose.connect(mongoUri, {
+ // Remove deprecated options for newer MongoDB versions
+ });
+
+ logger.info(`MongoDB Connected: ${conn.connection.host}`);
+ } catch (error) {
+ logger.error('Database connection error:', error);
+ process.exit(1);
+ }
+};
+
+module.exports = connectDB;
diff --git a/API/config/production.js b/API/config/production.js
new file mode 100644
index 0000000..05f5364
--- /dev/null
+++ b/API/config/production.js
@@ -0,0 +1,131 @@
+const logger = require('../utils/logger');
+
+const productionConfig = {
+ // Server settings
+ port: process.env.PORT || 3001,
+
+ // Database settings
+ database: {
+ uri: process.env.MONGODB_URI || 'mongodb://localhost:27017/galaxystrikeonline',
+ options: {
+ useNewUrlParser: true,
+ useUnifiedTopology: true,
+ maxPoolSize: 10,
+ serverSelectionTimeoutMS: 5000,
+ socketTimeoutMS: 45000,
+ }
+ },
+
+ // JWT settings
+ jwt: {
+ secret: process.env.JWT_SECRET,
+ expiresIn: '24h',
+ refreshExpiresIn: '7d'
+ },
+
+ // Redis settings (for sessions and caching)
+ redis: {
+ url: process.env.REDIS_URL || 'redis://localhost:6379',
+ options: {
+ retryDelayOnFailover: 100,
+ maxRetriesPerRequest: 3,
+ }
+ },
+
+ // CORS settings
+ cors: {
+ origin: process.env.CLIENT_URL || 'http://localhost:3000',
+ credentials: true,
+ methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
+ allowedHeaders: ['Content-Type', 'Authorization']
+ },
+
+ // Rate limiting
+ rateLimit: {
+ windowMs: 15 * 60 * 1000, // 15 minutes
+ max: 100, // limit each IP to 100 requests per windowMs
+ message: 'Too many requests from this IP, please try again later.',
+ standardHeaders: true,
+ legacyHeaders: false,
+ },
+
+ // Socket.IO settings
+ socketio: {
+ cors: {
+ origin: process.env.CLIENT_URL || 'http://localhost:3000',
+ methods: ['GET', 'POST']
+ },
+ pingTimeout: 60000,
+ pingInterval: 25000,
+ maxHttpBufferSize: 1e8, // 100 MB
+ },
+
+ // Logging settings
+ logging: {
+ level: process.env.LOG_LEVEL || 'info',
+ format: process.env.NODE_ENV === 'production' ? 'json' : 'simple',
+ file: {
+ enabled: true,
+ filename: 'logs/app.log',
+ maxsize: 10485760, // 10MB
+ maxFiles: 5,
+ }
+ },
+
+ // Security settings
+ security: {
+ helmet: {
+ contentSecurityPolicy: {
+ directives: {
+ defaultSrc: ["'self'"],
+ styleSrc: ["'self'", "'unsafe-inline'"],
+ scriptSrc: ["'self'"],
+ imgSrc: ["'self'", "data:", "https:"],
+ },
+ },
+ hsts: {
+ maxAge: 31536000,
+ includeSubDomains: true,
+ preload: true
+ }
+ },
+ compression: {
+ level: 6,
+ threshold: 1024,
+ }
+ },
+
+ // Game settings
+ game: {
+ maxPlayersPerServer: 50,
+ serverCleanupInterval: 300000, // 5 minutes
+ inactivePlayerTimeout: 1800000, // 30 minutes
+ autoSaveInterval: 60000, // 1 minute
+ }
+};
+
+// Validate required environment variables
+const validateConfig = () => {
+ const required = ['JWT_SECRET'];
+ const missing = required.filter(key => !process.env[key]);
+
+ if (missing.length > 0) {
+ logger.error(`Missing required environment variables: ${missing.join(', ')}`);
+ process.exit(1);
+ }
+
+ if (process.env.NODE_ENV === 'production') {
+ const prodRequired = ['MONGODB_URI', 'CLIENT_URL'];
+ const prodMissing = prodRequired.filter(key => !process.env[key]);
+
+ if (prodMissing.length > 0) {
+ logger.error(`Missing required production environment variables: ${prodMissing.join(', ')}`);
+ process.exit(1);
+ }
+ }
+};
+
+module.exports = {
+ ...productionConfig,
+ validateConfig
+};
diff --git a/API/middleware/errorHandler.js b/API/middleware/errorHandler.js
new file mode 100644
index 0000000..97ff3ac
--- /dev/null
+++ b/API/middleware/errorHandler.js
@@ -0,0 +1,134 @@
+const logger = require('../utils/logger');
+
+// Custom error classes
+class AppError extends Error {
+ constructor(message, statusCode) {
+ super(message);
+ this.statusCode = statusCode;
+ this.status = `${statusCode}`.startsWith('4') ? 'fail' : 'error';
+ this.isOperational = true;
+
+ Error.captureStackTrace(this, this.constructor);
+ }
+}
+
+class ValidationError extends AppError {
+ constructor(message) {
+ super(message, 400);
+ }
+}
+
+class AuthenticationError extends AppError {
+ constructor(message = 'Authentication failed') {
+ super(message, 401);
+ }
+}
+
+class AuthorizationError extends AppError {
+ constructor(message = 'Access denied') {
+ super(message, 403);
+ }
+}
+
+class NotFoundError extends AppError {
+ constructor(message = 'Resource not found') {
+ super(message, 404);
+ }
+}
+
+class ConflictError extends AppError {
+ constructor(message = 'Resource conflict') {
+ super(message, 409);
+ }
+}
+
+class DatabaseError extends AppError {
+ constructor(message = 'Database operation failed') {
+ super(message, 500);
+ }
+}
+
+// Error handling middleware
+const errorHandler = (err, req, res, next) => {
+ let error = { ...err };
+ error.message = err.message;
+
+ // Log error
+ logger.error({
+ error: err,
+ request: {
+ method: req.method,
+ url: req.url,
+ ip: req.ip,
+ userAgent: req.get('User-Agent'),
+ userId: req.userId
+ }
+ });
+
+ // Mongoose validation error
+ if (err.name === 'ValidationError') {
+ const message = Object.values(err.errors).map(val => val.message).join(', ');
+ error = new ValidationError(message);
+ }
+
+ // Mongoose duplicate key error
+ if (err.code === 11000) {
+ const field = Object.keys(err.keyValue)[0];
+ const value = err.keyValue[field];
+ error = new ConflictError(`${field} '${value}' already exists`);
+ }
+
+ // Mongoose cast error
+ if (err.name === 'CastError') {
+ error = new ValidationError(`Invalid ${err.path}: ${err.value}`);
+ }
+
+ // JWT errors
+ if (err.name === 'JsonWebTokenError') {
+ error = new AuthenticationError('Invalid token');
+ }
+
+ if (err.name === 'TokenExpiredError') {
+ error = new AuthenticationError('Token expired');
+ }
+
+ // Default error
+ if (!error.isOperational) {
+ error = new AppError('Something went wrong', 500);
+ }
+
+ res.status(error.statusCode || 500).json({
+ status: error.status || 'error',
+ message: error.message,
+ ...(process.env.NODE_ENV === 'development' && {
+ stack: error.stack,
+ error: err
+ })
+ });
+};
+
+// Async error wrapper
+const catchAsync = (fn) => {
+ return (req, res, next) => {
+ Promise.resolve(fn(req, res, next)).catch(next);
+ };
+};
+
+// 404 handler
+const notFound = (req, res, next) => {
+ const error = new NotFoundError(`Route ${req.originalUrl} not found`);
+ next(error);
+};
+
+module.exports = {
+ AppError,
+ ValidationError,
+ AuthenticationError,
+ AuthorizationError,
+ NotFoundError,
+ ConflictError,
+ DatabaseError,
+ errorHandler,
+ catchAsync,
+ notFound
+};
diff --git a/API/models/GameServer.js b/API/models/GameServer.js
new file mode 100644
index 0000000..0138f44
--- /dev/null
+++ b/API/models/GameServer.js
@@ -0,0 +1,134 @@
+const mongoose = require('mongoose');
+
+const gameServerSchema = new mongoose.Schema({
+ serverId: {
+ type: String,
+ required: true,
+ unique: true
+ },
+ name: {
+ type: String,
+ required: true
+ },
+ type: {
+ type: String,
+ enum: ['public', 'private'],
+ default: 'public'
+ },
+ region: {
+ type: String,
+ default: 'us-east'
+ },
+ maxPlayers: {
+ type: Number,
+ default: 10,
+ min: 1,
+ max: 20
+ },
+ currentPlayers: {
+ type: Number,
+ default: 0
+ },
+ owner: {
+ userId: { type: String, required: true },
+ username: { type: String, required: true }
+ },
+ settings: {
+ password: { type: String, default: null },
+ description: { type: String, default: '' },
+ tags: [{ type: String }]
+ },
+ status: {
+ type: String,
+ enum: ['waiting', 'active', 'full', 'offline'],
+ default: 'waiting'
+ },
+ gameServerUrl: {
+ type: String,
+ default: null
+ },
+ createdAt: {
+ type: Date,
+ default: Date.now
+ },
+ lastActivity: {
+ type: Date,
+ default: Date.now
+ }
+}, {
+ timestamps: true
+});
+
+// Indexes for performance (only for non-unique fields)
+gameServerSchema.index({ type: 1 });
+gameServerSchema.index({ region: 1 });
+gameServerSchema.index({ status: 1 });
+gameServerSchema.index({ 'owner.userId': 1 });
+
+// Methods
+gameServerSchema.methods.addPlayer = function() {
+ if (this.currentPlayers < this.maxPlayers) {
+ this.currentPlayers += 1;
+ this.lastActivity = new Date();
+
+ if (this.currentPlayers >= this.maxPlayers) {
+ this.status = 'full';
+ } else if (this.currentPlayers > 0) {
+ this.status = 'active';
+ }
+
+ return true;
+ }
+ return false;
+};
+
+gameServerSchema.methods.removePlayer = function() {
+ if (this.currentPlayers > 0) {
+ this.currentPlayers -= 1;
+ this.lastActivity = new Date();
+
+ if (this.currentPlayers === 0) {
+ this.status = 'waiting';
+ } else if (this.currentPlayers < this.maxPlayers) {
+ this.status = 'active';
+ }
+
+ return true;
+ }
+ return false;
+};
+
+gameServerSchema.methods.isFull = function() {
+ return this.currentPlayers >= this.maxPlayers;
+};
+
+gameServerSchema.methods.canJoin = function() {
+ return this.status !== 'offline' && !this.isFull();
+};
+
+// Static methods
+gameServerSchema.statics.findAvailableServers = function(filters = {}) {
+ const query = { status: { $ne: 'offline' } };
+
+ if (filters.type) {
+ query.type = filters.type;
+ }
+
+ if (filters.region) {
+ query.region = filters.region;
+ }
+
+ return this.find(query).sort({ lastActivity: -1 });
+};
+
+gameServerSchema.statics.cleanupOldServers = function(maxAge = 24 * 60 * 60 * 1000) { // 24 hours
+ const cutoffTime = new Date(Date.now() - maxAge);
+ return this.deleteMany({
+ $or: [
+ { lastActivity: { $lt: cutoffTime }, currentPlayers: 0 },
+ { status: 'offline', lastActivity: { $lt: cutoffTime } }
+ ]
+ });
+};
+
+module.exports = mongoose.model('GameServer', gameServerSchema);
diff --git a/API/models/Inventory.js b/API/models/Inventory.js
new file mode 100644
index 0000000..005beb9
--- /dev/null
+++ b/API/models/Inventory.js
@@ -0,0 +1,306 @@
+const mongoose = require('mongoose');
+
+const inventorySchema = new mongoose.Schema({
+ userId: {
+ type: String,
+ required: true,
+ ref: 'Player'
+ },
+
+ // Inventory settings
+ maxSlots: {
+ type: Number,
+ default: 50
+ },
+
+ // Items array
+ items: [{
+ id: {
+ type: String,
+ required: true
+ },
+ name: {
+ type: String,
+ required: true
+ },
+ type: {
+ type: String,
+ required: true,
+ enum: ['weapon', 'armor', 'material', 'consumable']
+ },
+ rarity: {
+ type: String,
+ enum: ['common', 'uncommon', 'rare', 'epic', 'legendary'],
+ default: 'common'
+ },
+ quantity: {
+ type: Number,
+ default: 1,
+ min: 1
+ },
+
+ // Item stats (for weapons/armor)
+ stats: {
+ attack: { type: Number, default: 0 },
+ defense: { type: Number, default: 0 },
+ speed: { type: Number, default: 0 },
+ criticalChance: { type: Number, default: 0 },
+ criticalDamage: { type: Number, default: 1.5 },
+ damage: { type: Number, default: 0 },
+ fireRate: { type: Number, default: 0 },
+ range: { type: Number, default: 0 },
+ energy: { type: Number, default: 0 },
+ health: { type: Number, default: 0 },
+ maxHealth: { type: Number, default: 0 },
+ durability: { type: Number, default: 0 },
+ weight: { type: Number, default: 0 },
+ energyShield: { type: Number, default: 0 }
+ },
+
+ // Item properties
+ description: {
+ type: String,
+ default: ''
+ },
+
+ // Equipment properties
+ equipable: {
+ type: Boolean,
+ default: false
+ },
+ slot: {
+ type: String,
+ enum: ['weapon', 'armor', 'engine', 'shield', 'special'],
+ default: null
+ },
+ isEquipped: {
+ type: Boolean,
+ default: false
+ },
+
+ // Consumable properties
+ consumable: {
+ type: Boolean,
+ default: false
+ },
+ effect: {
+ health: { type: Number, default: 0 },
+ energy: { type: Number, default: 0 },
+ attack: { type: Number, default: 0 },
+ defense: { type: Number, default: 0 },
+ speed: { type: Number, default: 0 },
+ duration: { type: Number, default: 0 }
+ },
+
+ // Stackable items
+ stackable: {
+ type: Boolean,
+ default: false
+ },
+
+ // Timestamps
+ acquiredAt: {
+ type: Date,
+ default: Date.now
+ },
+ lastUsed: {
+ type: Date,
+ default: null
+ }
+ }],
+
+ // Equipped items
+ equippedItems: {
+ weapon: { type: String, default: null },
+ armor: { type: String, default: null },
+ engine: { type: String, default: null },
+ shield: { type: String, default: null },
+ special: { type: String, default: null }
+ },
+
+ // Timestamps
+ updatedAt: {
+ type: Date,
+ default: Date.now
+ }
+}, {
+ timestamps: true
+});
+
+// Indexes for performance
+inventorySchema.index({ userId: 1 });
+inventorySchema.index({ 'items.id': 1 });
+inventorySchema.index({ 'items.type': 1 });
+
+// Methods
+inventorySchema.methods.addItem = function(itemData) {
+ // Check if item already exists and is stackable
+ const existingItem = this.items.find(item =>
+ item.id === itemData.id &&
+ item.type === itemData.type &&
+ item.stackable
+ );
+
+ if (existingItem) {
+ existingItem.quantity += itemData.quantity || 1;
+ } else {
+ // Add new item
+ const newItem = {
+ ...itemData,
+ quantity: itemData.quantity || 1,
+ acquiredAt: new Date()
+ };
+
+ this.items.push(newItem);
+ }
+
+ this.updatedAt = new Date();
+ return this.save();
+};
+
+inventorySchema.methods.removeItem = function(itemId, quantity = 1) {
+ const itemIndex = this.items.findIndex(item => item.id === itemId);
+
+ if (itemIndex === -1) {
+ throw new Error('Item not found in inventory');
+ }
+
+ const item = this.items[itemIndex];
+
+ if (item.quantity > quantity) {
+ item.quantity -= quantity;
+ } else {
+ // Remove item completely
+ this.items.splice(itemIndex, 1);
+
+ // Unequip if it was equipped
+ Object.keys(this.equippedItems).forEach(slot => {
+ if (this.equippedItems[slot] === itemId) {
+ this.equippedItems[slot] = null;
+ }
+ });
+ }
+
+ this.updatedAt = new Date();
+ return this.save();
+};
+
+inventorySchema.methods.hasItem = function(itemId, quantity = 1) {
+ const item = this.items.find(item => item.id === itemId);
+ return item && item.quantity >= quantity;
+};
+
+inventorySchema.methods.getItemCount = function(itemId) {
+ const item = this.items.find(item => item.id === itemId);
+ return item ? item.quantity : 0;
+};
+
+inventorySchema.methods.equipItem = function(itemId, slot) {
+ const item = this.items.find(item => item.id === itemId);
+
+ if (!item) {
+ throw new Error('Item not found in inventory');
+ }
+
+ if (!item.equipable) {
+ throw new Error('Item is not equipable');
+ }
+
+ if (item.slot !== slot) {
+ throw new Error('Item cannot be equipped in this slot');
+ }
+
+ // Unequip current item in slot
+ if (this.equippedItems[slot]) {
+ const currentItem = this.items.find(item => item.id === this.equippedItems[slot]);
+ if (currentItem) {
+ currentItem.isEquipped = false;
+ }
+ }
+
+ // Equip new item
+ this.equippedItems[slot] = itemId;
+ item.isEquipped = true;
+ item.lastUsed = new Date();
+
+ this.updatedAt = new Date();
+ return this.save();
+};
+
+inventorySchema.methods.unequipItem = function(slot) {
+ const itemId = this.equippedItems[slot];
+
+ if (!itemId) {
+ throw new Error('No item equipped in this slot');
+ }
+
+ const item = this.items.find(item => item.id === itemId);
+ if (item) {
+ item.isEquipped = false;
+ }
+
+ this.equippedItems[slot] = null;
+ this.updatedAt = new Date();
+ return this.save();
+};
+
+inventorySchema.methods.useConsumable = function(itemId) {
+ const item = this.items.find(item => item.id === itemId);
+
+ if (!item) {
+ throw new Error('Item not found in inventory');
+ }
+
+ if (!item.consumable) {
+ throw new Error('Item is not consumable');
+ }
+
+ if (item.quantity <= 0) {
+ throw new Error('No quantity left');
+ }
+
+ // Apply effects
+ const effects = { ...item.effect };
+
+ // Remove one from quantity
+ item.quantity -= 1;
+ item.lastUsed = new Date();
+
+ // Remove item if quantity is 0
+ if (item.quantity === 0) {
+ const itemIndex = this.items.findIndex(item => item.id === itemId);
+ this.items.splice(itemIndex, 1);
+ }
+
+ this.updatedAt = new Date();
+ this.save();
+
+ return effects;
+};
+
+inventorySchema.methods.getInventorySummary = function() {
+ const summary = {
+ totalItems: this.items.length,
+ usedSlots: this.items.length,
+ maxSlots: this.maxSlots,
+ itemsByType: {},
+ equippedItems: this.equippedItems
+ };
+
+ // Count items by type
+ this.items.forEach(item => {
+ summary.itemsByType[item.type] = (summary.itemsByType[item.type] || 0) + item.quantity;
+ });
+
+ return summary;
+};
+
+inventorySchema.methods.getItemsByType = function(type) {
+ return this.items.filter(item => item.type === type);
+};
+
+inventorySchema.methods.getItemsByRarity = function(rarity) {
+ return this.items.filter(item => item.rarity === rarity);
+};
+
+module.exports = mongoose.model('Inventory', inventorySchema);
diff --git a/API/models/Player.js b/API/models/Player.js
new file mode 100644
index 0000000..a176b66
--- /dev/null
+++ b/API/models/Player.js
@@ -0,0 +1,169 @@
+const mongoose = require('mongoose');
+const { calculateXPToNextLevel, getLevelFromXP } = require('../config/xp-progression');
+
+const playerSchema = new mongoose.Schema({
+ userId: {
+ type: String,
+ required: true,
+ unique: true
+ },
+ username: {
+ type: String,
+ required: true
+ },
+ email: {
+ type: String,
+ required: true,
+ unique: true
+ },
+
+ // Authentication
+ password: {
+ type: String,
+ required: true,
+ select: false // Don't include password in queries by default
+ },
+
+ // Player stats
+ stats: {
+ level: { type: Number, default: 1 },
+ experience: { type: Number, default: 0 },
+ totalXP: { type: Number, default: 0 }, // Total accumulated XP across all levels
+ experienceToNext: { type: Number, default: function() { return calculateXPToNextLevel(1, 0); } },
+ credits: { type: Number, default: 1000 },
+ dungeonsCleared: { type: Number, default: 0 },
+ playTime: { type: Number, default: 0 },
+ lastLogin: { type: Date, default: Date.now }
+ },
+
+ // Base attributes
+ attributes: {
+ health: { type: Number, default: 100 },
+ maxHealth: { type: Number, default: 100 },
+ energy: { type: Number, default: 100 },
+ maxEnergy: { type: Number, default: 100 },
+ attack: { type: Number, default: 10 },
+ defense: { type: Number, default: 5 },
+ speed: { type: Number, default: 10 },
+ criticalChance: { type: Number, default: 0.05 },
+ criticalDamage: { type: Number, default: 1.5 }
+ },
+
+ // Player info
+ info: {
+ name: { type: String, default: 'Commander' },
+ title: { type: String, default: 'Rookie Pilot' },
+ guild: { type: String, default: null },
+ rank: { type: String, default: 'Cadet' }
+ },
+
+ // Current ship
+ currentShip: {
+ type: mongoose.Schema.Types.ObjectId,
+ ref: 'Ship'
+ },
+
+ // Settings
+ settings: {
+ autoSave: { type: Boolean, default: true },
+ notifications: { type: Boolean, default: true },
+ soundEffects: { type: Boolean, default: true },
+ music: { type: Boolean, default: false },
+ discordIntegration: { type: Boolean, default: false }
+ },
+
+ // Daily rewards
+ dailyRewards: {
+ lastClaim: { type: Date, default: null },
+ consecutiveDays: { type: Number, default: 0 }
+ },
+
+ // Server info
+ currentServer: { type: String, default: null },
+
+ // Timestamps
+ createdAt: { type: Date, default: Date.now },
+ updatedAt: { type: Date, default: Date.now }
+}, {
+ timestamps: true
+});
+
+// Indexes for performance (only for non-unique fields)
+playerSchema.index({ 'stats.level': 1 });
+playerSchema.index({ currentServer: 1 });
+
+// Methods
+playerSchema.methods.addExperience = function(amount) {
+ // Add to total accumulated XP
+ this.stats.totalXP += amount;
+
+ // Calculate new level based on total XP
+ const levelInfo = getLevelFromXP(this.stats.totalXP);
+ const oldLevel = this.stats.level;
+
+ this.stats.level = levelInfo.level;
+ this.stats.experience = levelInfo.xpIntoLevel;
+ this.stats.experienceToNext = levelInfo.xpToNext;
+
+ // Return whether the player leveled up
+ return this.stats.level > oldLevel;
+};
+
+playerSchema.methods.addCredits = function(amount) {
+ this.stats.credits += amount;
+ return this.stats.credits;
+};
+
+playerSchema.methods.canAfford = function(cost) {
+ return this.stats.credits >= cost;
+};
+
+playerSchema.methods.spendCredits = function(cost) {
+ if (this.canAfford(cost)) {
+ this.stats.credits -= cost;
+ return true;
+ }
+ return false;
+};
+
+playerSchema.methods.updatePlayTime = function(sessionTime) {
+ this.stats.playTime += sessionTime;
+ this.stats.lastLogin = new Date();
+};
+
+playerSchema.methods.claimDailyReward = function() {
+ const today = new Date();
+ const lastClaim = this.dailyRewards.lastClaim;
+
+ // Check if already claimed today
+ if (lastClaim && lastClaim.toDateString() === today.toDateString()) {
+ return { success: false, message: 'Daily reward already claimed today' };
+ }
+
+ // Check consecutive days
+ const yesterday = new Date(today);
+ yesterday.setDate(yesterday.getDate() - 1);
+
+ if (lastClaim && lastClaim.toDateString() === yesterday.toDateString()) {
+ this.dailyRewards.consecutiveDays += 1;
+ } else {
+ this.dailyRewards.consecutiveDays = 1;
+ }
+
+ this.dailyRewards.lastClaim = today;
+
+ // Calculate reward based on consecutive days
+ const baseReward = 100;
+ const consecutiveBonus = (this.dailyRewards.consecutiveDays - 1) * 50;
+ const totalReward = baseReward + consecutiveBonus;
+
+ this.addCredits(totalReward);
+
+ return {
+ success: true,
+ reward: totalReward,
+ consecutiveDays: this.dailyRewards.consecutiveDays
+ };
+};
+
+module.exports = mongoose.model('Player', playerSchema);
diff --git a/API/models/Ship.js b/API/models/Ship.js
new file mode 100644
index 0000000..270ca97
--- /dev/null
+++ b/API/models/Ship.js
@@ -0,0 +1,189 @@
+const mongoose = require('mongoose');
+
+const shipSchema = new mongoose.Schema({
+ userId: {
+ type: String,
+ required: true,
+ ref: 'Player'
+ },
+
+ // Ship identification
+ id: {
+ type: String,
+ required: true,
+ unique: true
+ },
+ name: {
+ type: String,
+ required: true
+ },
+ class: {
+ type: String,
+ required: true,
+ enum: ['Fighter', 'Cruiser', 'Battleship', 'Carrier', 'Explorer']
+ },
+ level: {
+ type: Number,
+ default: 1
+ },
+
+ // Ship stats
+ stats: {
+ health: { type: Number, required: true },
+ maxHealth: { type: Number, required: true },
+ attack: { type: Number, required: true },
+ defense: { type: Number, required: true },
+ speed: { type: Number, required: true },
+ criticalChance: { type: Number, default: 0.05 },
+ criticalDamage: { type: Number, default: 1.5 },
+ hull: { type: Number, required: true }
+ },
+
+ // Ship appearance
+ texture: {
+ type: String,
+ required: true
+ },
+
+ // Ship progression
+ experience: {
+ type: Number,
+ default: 0
+ },
+ requiredExp: {
+ type: Number,
+ default: 100
+ },
+ upgrades: [{
+ type: String
+ }],
+
+ // Ship status
+ isEquipped: {
+ type: Boolean,
+ default: false
+ },
+ isCurrent: {
+ type: Boolean,
+ default: false
+ },
+
+ // Shop information (if purchased)
+ price: {
+ type: Number,
+ default: 0
+ },
+ rarity: {
+ type: String,
+ enum: ['common', 'uncommon', 'rare', 'epic', 'legendary'],
+ default: 'common'
+ },
+ description: {
+ type: String,
+ default: ''
+ },
+
+ // Timestamps
+ acquiredAt: {
+ type: Date,
+ default: Date.now
+ },
+ lastUsed: {
+ type: Date,
+ default: Date.now
+ }
+}, {
+ timestamps: true
+});
+
+// Indexes for performance
+shipSchema.index({ userId: 1 });
+shipSchema.index({ id: 1 });
+shipSchema.index({ isEquipped: 1 });
+shipSchema.index({ isCurrent: 1 });
+
+// Methods
+shipSchema.methods.addExperience = function(amount) {
+ this.experience += amount;
+
+ // Level up logic
+ while (this.experience >= this.requiredExp) {
+ this.experience -= this.requiredExp;
+ this.level += 1;
+ this.requiredExp = this.level * 100;
+
+ // Increase stats on level up
+ this.stats.maxHealth += 10;
+ this.stats.health = this.stats.maxHealth;
+ this.stats.attack += 2;
+ this.stats.defense += 1;
+ this.stats.speed += 1;
+ }
+
+ return this.level;
+};
+
+shipSchema.methods.takeDamage = function(damage) {
+ const actualDamage = Math.max(0, damage - this.stats.defense);
+ this.stats.health = Math.max(0, this.stats.health - actualDamage);
+
+ if (this.stats.health === 0) {
+ this.isDestroyed = true;
+ }
+
+ return actualDamage;
+};
+
+shipSchema.methods.heal = function(amount) {
+ const healAmount = Math.min(amount, this.stats.maxHealth - this.stats.health);
+ this.stats.health += healAmount;
+ this.isDestroyed = false;
+
+ return healAmount;
+};
+
+shipSchema.methods.isAlive = function() {
+ return this.stats.health > 0;
+};
+
+shipSchema.methods.getStatSummary = function() {
+ return {
+ name: this.name,
+ class: this.class,
+ level: this.level,
+ health: `${this.stats.health}/${this.stats.maxHealth}`,
+ attack: this.stats.attack,
+ defense: this.stats.defense,
+ speed: this.stats.speed,
+ criticalChance: `${(this.stats.criticalChance * 100).toFixed(1)}%`,
+ criticalDamage: `${this.stats.criticalDamage}x`
+ };
+};
+
+shipSchema.methods.upgrade = function(upgradeType) {
+ switch (upgradeType) {
+ case 'health':
+ this.stats.maxHealth += 20;
+ this.stats.health = this.stats.maxHealth;
+ break;
+ case 'attack':
+ this.stats.attack += 5;
+ break;
+ case 'defense':
+ this.stats.defense += 3;
+ break;
+ case 'speed':
+ this.stats.speed += 2;
+ break;
+ default:
+ throw new Error('Unknown upgrade type');
+ }
+
+ if (!this.upgrades.includes(upgradeType)) {
+ this.upgrades.push(upgradeType);
+ }
+
+ this.lastUsed = new Date();
+};
+
+module.exports = mongoose.model('Ship', shipSchema);
diff --git a/API/package-lock.json b/API/package-lock.json
new file mode 100644
index 0000000..60160b4
--- /dev/null
+++ b/API/package-lock.json
@@ -0,0 +1,6068 @@
+{
+ "name": "galaxystrikeonline-server",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "galaxystrikeonline-server",
+ "version": "1.0.0",
+ "license": "MIT",
+ "dependencies": {
+ "bcryptjs": "^2.4.3",
+ "compression": "^1.7.4",
+ "cors": "^2.8.5",
+ "dotenv": "^16.3.1",
+ "express": "^4.18.2",
+ "helmet": "^7.1.0",
+ "joi": "^17.11.0",
+ "jsonwebtoken": "^9.0.2",
+ "mongoose": "^8.0.3",
+ "rate-limiter-flexible": "^2.4.2",
+ "redis": "^4.6.11",
+ "socket.io": "^4.7.4",
+ "winston": "^3.11.0"
+ },
+ "devDependencies": {
+ "jest": "^29.7.0",
+ "nodemon": "^3.0.2",
+ "supertest": "^6.3.3"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
+ "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.27.1",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/compat-data": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz",
+ "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/core": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz",
+ "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/code-frame": "^7.27.1",
+ "@babel/generator": "^7.28.5",
+ "@babel/helper-compilation-targets": "^7.27.2",
+ "@babel/helper-module-transforms": "^7.28.3",
+ "@babel/helpers": "^7.28.4",
+ "@babel/parser": "^7.28.5",
+ "@babel/template": "^7.27.2",
+ "@babel/traverse": "^7.28.5",
+ "@babel/types": "^7.28.5",
+ "@jridgewell/remapping": "^2.3.5",
+ "convert-source-map": "^2.0.0",
+ "debug": "^4.1.0",
+ "gensync": "^1.0.0-beta.2",
+ "json5": "^2.2.3",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/babel"
+ }
+ },
+ "node_modules/@babel/core/node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@babel/core/node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz",
+ "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.28.5",
+ "@babel/types": "^7.28.5",
+ "@jridgewell/gen-mapping": "^0.3.12",
+ "@jridgewell/trace-mapping": "^0.3.28",
+ "jsesc": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets": {
+ "version": "7.27.2",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz",
+ "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/compat-data": "^7.27.2",
+ "@babel/helper-validator-option": "^7.27.1",
+ "browserslist": "^4.24.0",
+ "lru-cache": "^5.1.1",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-globals": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
+ "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz",
+ "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.27.1",
+ "@babel/types": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms": {
+ "version": "7.28.3",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz",
+ "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.27.1",
+ "@babel/traverse": "^7.28.3"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-plugin-utils": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz",
+ "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
+ "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
+ "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-option": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
+ "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helpers": {
+ "version": "7.28.4",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz",
+ "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/template": "^7.27.2",
+ "@babel/types": "^7.28.4"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz",
+ "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.28.5"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-async-generators": {
+ "version": "7.8.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz",
+ "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-bigint": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz",
+ "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-class-properties": {
+ "version": "7.12.13",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz",
+ "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.12.13"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-class-static-block": {
+ "version": "7.14.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz",
+ "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.14.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-import-attributes": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz",
+ "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-import-meta": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz",
+ "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-json-strings": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz",
+ "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-jsx": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz",
+ "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-logical-assignment-operators": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz",
+ "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz",
+ "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-numeric-separator": {
+ "version": "7.10.4",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz",
+ "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.10.4"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-object-rest-spread": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz",
+ "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-optional-catch-binding": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz",
+ "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-optional-chaining": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz",
+ "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.8.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-private-property-in-object": {
+ "version": "7.14.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz",
+ "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.14.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-top-level-await": {
+ "version": "7.14.5",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz",
+ "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.14.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-typescript": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz",
+ "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "7.27.2",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
+ "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.27.1",
+ "@babel/parser": "^7.27.2",
+ "@babel/types": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz",
+ "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.27.1",
+ "@babel/generator": "^7.28.5",
+ "@babel/helper-globals": "^7.28.0",
+ "@babel/parser": "^7.28.5",
+ "@babel/template": "^7.27.2",
+ "@babel/types": "^7.28.5",
+ "debug": "^4.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse/node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@babel/traverse/node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@babel/types": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz",
+ "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.28.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@bcoe/v8-coverage": {
+ "version": "0.2.3",
+ "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz",
+ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@colors/colors": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz",
+ "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.1.90"
+ }
+ },
+ "node_modules/@dabh/diagnostics": {
+ "version": "2.0.8",
+ "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.8.tgz",
+ "integrity": "sha512-R4MSXTVnuMzGD7bzHdW2ZhhdPC/igELENcq5IjEverBvq5hn1SXCWcsi6eSsdWP0/Ur+SItRRjAktmdoX/8R/Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@so-ric/colorspace": "^1.1.6",
+ "enabled": "2.0.x",
+ "kuler": "^2.0.0"
+ }
+ },
+ "node_modules/@hapi/hoek": {
+ "version": "9.3.0",
+ "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz",
+ "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@hapi/topo": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz",
+ "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@hapi/hoek": "^9.0.0"
+ }
+ },
+ "node_modules/@istanbuljs/load-nyc-config": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz",
+ "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "camelcase": "^5.3.1",
+ "find-up": "^4.1.0",
+ "get-package-type": "^0.1.0",
+ "js-yaml": "^3.13.1",
+ "resolve-from": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@istanbuljs/schema": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz",
+ "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@jest/console": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz",
+ "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/types": "^29.6.3",
+ "@types/node": "*",
+ "chalk": "^4.0.0",
+ "jest-message-util": "^29.7.0",
+ "jest-util": "^29.7.0",
+ "slash": "^3.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jest/core": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz",
+ "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/console": "^29.7.0",
+ "@jest/reporters": "^29.7.0",
+ "@jest/test-result": "^29.7.0",
+ "@jest/transform": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "@types/node": "*",
+ "ansi-escapes": "^4.2.1",
+ "chalk": "^4.0.0",
+ "ci-info": "^3.2.0",
+ "exit": "^0.1.2",
+ "graceful-fs": "^4.2.9",
+ "jest-changed-files": "^29.7.0",
+ "jest-config": "^29.7.0",
+ "jest-haste-map": "^29.7.0",
+ "jest-message-util": "^29.7.0",
+ "jest-regex-util": "^29.6.3",
+ "jest-resolve": "^29.7.0",
+ "jest-resolve-dependencies": "^29.7.0",
+ "jest-runner": "^29.7.0",
+ "jest-runtime": "^29.7.0",
+ "jest-snapshot": "^29.7.0",
+ "jest-util": "^29.7.0",
+ "jest-validate": "^29.7.0",
+ "jest-watcher": "^29.7.0",
+ "micromatch": "^4.0.4",
+ "pretty-format": "^29.7.0",
+ "slash": "^3.0.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ },
+ "peerDependencies": {
+ "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0"
+ },
+ "peerDependenciesMeta": {
+ "node-notifier": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@jest/environment": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz",
+ "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/fake-timers": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "@types/node": "*",
+ "jest-mock": "^29.7.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jest/expect": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz",
+ "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "expect": "^29.7.0",
+ "jest-snapshot": "^29.7.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jest/expect-utils": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz",
+ "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "jest-get-type": "^29.6.3"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jest/fake-timers": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz",
+ "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/types": "^29.6.3",
+ "@sinonjs/fake-timers": "^10.0.2",
+ "@types/node": "*",
+ "jest-message-util": "^29.7.0",
+ "jest-mock": "^29.7.0",
+ "jest-util": "^29.7.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jest/globals": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz",
+ "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/environment": "^29.7.0",
+ "@jest/expect": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "jest-mock": "^29.7.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jest/reporters": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz",
+ "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@bcoe/v8-coverage": "^0.2.3",
+ "@jest/console": "^29.7.0",
+ "@jest/test-result": "^29.7.0",
+ "@jest/transform": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "@jridgewell/trace-mapping": "^0.3.18",
+ "@types/node": "*",
+ "chalk": "^4.0.0",
+ "collect-v8-coverage": "^1.0.0",
+ "exit": "^0.1.2",
+ "glob": "^7.1.3",
+ "graceful-fs": "^4.2.9",
+ "istanbul-lib-coverage": "^3.0.0",
+ "istanbul-lib-instrument": "^6.0.0",
+ "istanbul-lib-report": "^3.0.0",
+ "istanbul-lib-source-maps": "^4.0.0",
+ "istanbul-reports": "^3.1.3",
+ "jest-message-util": "^29.7.0",
+ "jest-util": "^29.7.0",
+ "jest-worker": "^29.7.0",
+ "slash": "^3.0.0",
+ "string-length": "^4.0.1",
+ "strip-ansi": "^6.0.0",
+ "v8-to-istanbul": "^9.0.1"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ },
+ "peerDependencies": {
+ "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0"
+ },
+ "peerDependenciesMeta": {
+ "node-notifier": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@jest/schemas": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz",
+ "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@sinclair/typebox": "^0.27.8"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jest/source-map": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz",
+ "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/trace-mapping": "^0.3.18",
+ "callsites": "^3.0.0",
+ "graceful-fs": "^4.2.9"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jest/test-result": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz",
+ "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/console": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "@types/istanbul-lib-coverage": "^2.0.0",
+ "collect-v8-coverage": "^1.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jest/test-sequencer": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz",
+ "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/test-result": "^29.7.0",
+ "graceful-fs": "^4.2.9",
+ "jest-haste-map": "^29.7.0",
+ "slash": "^3.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jest/transform": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz",
+ "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.11.6",
+ "@jest/types": "^29.6.3",
+ "@jridgewell/trace-mapping": "^0.3.18",
+ "babel-plugin-istanbul": "^6.1.1",
+ "chalk": "^4.0.0",
+ "convert-source-map": "^2.0.0",
+ "fast-json-stable-stringify": "^2.1.0",
+ "graceful-fs": "^4.2.9",
+ "jest-haste-map": "^29.7.0",
+ "jest-regex-util": "^29.6.3",
+ "jest-util": "^29.7.0",
+ "micromatch": "^4.0.4",
+ "pirates": "^4.0.4",
+ "slash": "^3.0.0",
+ "write-file-atomic": "^4.0.2"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jest/types": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz",
+ "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/schemas": "^29.6.3",
+ "@types/istanbul-lib-coverage": "^2.0.0",
+ "@types/istanbul-reports": "^3.0.0",
+ "@types/node": "*",
+ "@types/yargs": "^17.0.8",
+ "chalk": "^4.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.13",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/remapping": {
+ "version": "2.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
+ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.31",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
+ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@mongodb-js/saslprep": {
+ "version": "1.4.4",
+ "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.4.4.tgz",
+ "integrity": "sha512-p7X/ytJDIdwUfFL/CLOhKgdfJe1Fa8uw9seJYvdOmnP9JBWGWHW69HkOixXS6Wy9yvGf1MbhcS6lVmrhy4jm2g==",
+ "license": "MIT",
+ "dependencies": {
+ "sparse-bitfield": "^3.0.3"
+ }
+ },
+ "node_modules/@noble/hashes": {
+ "version": "1.8.0",
+ "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz",
+ "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^14.21.3 || >=16"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/@paralleldrive/cuid2": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.3.1.tgz",
+ "integrity": "sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@noble/hashes": "^1.1.5"
+ }
+ },
+ "node_modules/@redis/bloom": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-1.2.0.tgz",
+ "integrity": "sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@redis/client": "^1.0.0"
+ }
+ },
+ "node_modules/@redis/client": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.6.1.tgz",
+ "integrity": "sha512-/KCsg3xSlR+nCK8/8ZYSknYxvXHwubJrU82F3Lm1Fp6789VQ0/3RJKfsmRXjqfaTA++23CvC3hqmqe/2GEt6Kw==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "cluster-key-slot": "1.1.2",
+ "generic-pool": "3.9.0",
+ "yallist": "4.0.0"
+ },
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/@redis/client/node_modules/yallist": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+ "license": "ISC"
+ },
+ "node_modules/@redis/graph": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@redis/graph/-/graph-1.1.1.tgz",
+ "integrity": "sha512-FEMTcTHZozZciLRl6GiiIB4zGm5z5F3F6a6FZCyrfxdKOhFlGkiAqlexWMBzCi4DcRoyiOsuLfW+cjlGWyExOw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@redis/client": "^1.0.0"
+ }
+ },
+ "node_modules/@redis/json": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/@redis/json/-/json-1.0.7.tgz",
+ "integrity": "sha512-6UyXfjVaTBTJtKNG4/9Z8PSpKE6XgSyEb8iwaqDcy+uKrd/DGYHTWkUdnQDyzm727V7p21WUMhsqz5oy65kPcQ==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@redis/client": "^1.0.0"
+ }
+ },
+ "node_modules/@redis/search": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@redis/search/-/search-1.2.0.tgz",
+ "integrity": "sha512-tYoDBbtqOVigEDMAcTGsRlMycIIjwMCgD8eR2t0NANeQmgK/lvxNAvYyb6bZDD4frHRhIHkJu2TBRvB0ERkOmw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@redis/client": "^1.0.0"
+ }
+ },
+ "node_modules/@redis/time-series": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-1.1.0.tgz",
+ "integrity": "sha512-c1Q99M5ljsIuc4YdaCwfUEXsofakb9c8+Zse2qxTadu8TalLXuAESzLvFAvNVbkmSlvlzIQOLpBCmWI9wTOt+g==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@redis/client": "^1.0.0"
+ }
+ },
+ "node_modules/@sideway/address": {
+ "version": "4.1.5",
+ "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz",
+ "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@hapi/hoek": "^9.0.0"
+ }
+ },
+ "node_modules/@sideway/formula": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz",
+ "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@sideway/pinpoint": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz",
+ "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@sinclair/typebox": {
+ "version": "0.27.8",
+ "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz",
+ "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@sinonjs/commons": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz",
+ "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "type-detect": "4.0.8"
+ }
+ },
+ "node_modules/@sinonjs/fake-timers": {
+ "version": "10.3.0",
+ "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz",
+ "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@sinonjs/commons": "^3.0.0"
+ }
+ },
+ "node_modules/@so-ric/colorspace": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/@so-ric/colorspace/-/colorspace-1.1.6.tgz",
+ "integrity": "sha512-/KiKkpHNOBgkFJwu9sh48LkHSMYGyuTcSFK/qMBdnOAlrRJzRSXAOFB5qwzaVQuDl8wAvHVMkaASQDReTahxuw==",
+ "license": "MIT",
+ "dependencies": {
+ "color": "^5.0.2",
+ "text-hex": "1.0.x"
+ }
+ },
+ "node_modules/@socket.io/component-emitter": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz",
+ "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==",
+ "license": "MIT"
+ },
+ "node_modules/@types/babel__core": {
+ "version": "7.20.5",
+ "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
+ "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.20.7",
+ "@babel/types": "^7.20.7",
+ "@types/babel__generator": "*",
+ "@types/babel__template": "*",
+ "@types/babel__traverse": "*"
+ }
+ },
+ "node_modules/@types/babel__generator": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
+ "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__template": {
+ "version": "7.4.4",
+ "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
+ "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.1.0",
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__traverse": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
+ "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.28.2"
+ }
+ },
+ "node_modules/@types/cors": {
+ "version": "2.8.19",
+ "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz",
+ "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/graceful-fs": {
+ "version": "4.1.9",
+ "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz",
+ "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/istanbul-lib-coverage": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz",
+ "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/istanbul-lib-report": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz",
+ "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/istanbul-lib-coverage": "*"
+ }
+ },
+ "node_modules/@types/istanbul-reports": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz",
+ "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/istanbul-lib-report": "*"
+ }
+ },
+ "node_modules/@types/node": {
+ "version": "25.0.3",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.3.tgz",
+ "integrity": "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA==",
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~7.16.0"
+ }
+ },
+ "node_modules/@types/stack-utils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz",
+ "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/triple-beam": {
+ "version": "1.3.5",
+ "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz",
+ "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==",
+ "license": "MIT"
+ },
+ "node_modules/@types/webidl-conversions": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz",
+ "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==",
+ "license": "MIT"
+ },
+ "node_modules/@types/whatwg-url": {
+ "version": "11.0.5",
+ "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz",
+ "integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/webidl-conversions": "*"
+ }
+ },
+ "node_modules/@types/yargs": {
+ "version": "17.0.35",
+ "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz",
+ "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/yargs-parser": "*"
+ }
+ },
+ "node_modules/@types/yargs-parser": {
+ "version": "21.0.3",
+ "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz",
+ "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/accepts": {
+ "version": "1.3.8",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
+ "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-types": "~2.1.34",
+ "negotiator": "0.6.3"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/accepts/node_modules/negotiator": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
+ "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/ansi-escapes": {
+ "version": "4.3.2",
+ "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz",
+ "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "type-fest": "^0.21.3"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/anymatch": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
+ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/argparse": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
+ "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "sprintf-js": "~1.0.2"
+ }
+ },
+ "node_modules/array-flatten": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
+ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
+ "license": "MIT"
+ },
+ "node_modules/asap": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
+ "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/async": {
+ "version": "3.2.6",
+ "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz",
+ "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==",
+ "license": "MIT"
+ },
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/babel-jest": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz",
+ "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/transform": "^29.7.0",
+ "@types/babel__core": "^7.1.14",
+ "babel-plugin-istanbul": "^6.1.1",
+ "babel-preset-jest": "^29.6.3",
+ "chalk": "^4.0.0",
+ "graceful-fs": "^4.2.9",
+ "slash": "^3.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.8.0"
+ }
+ },
+ "node_modules/babel-plugin-istanbul": {
+ "version": "6.1.1",
+ "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz",
+ "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.0.0",
+ "@istanbuljs/load-nyc-config": "^1.0.0",
+ "@istanbuljs/schema": "^0.1.2",
+ "istanbul-lib-instrument": "^5.0.4",
+ "test-exclude": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz",
+ "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@babel/core": "^7.12.3",
+ "@babel/parser": "^7.14.7",
+ "@istanbuljs/schema": "^0.1.2",
+ "istanbul-lib-coverage": "^3.2.0",
+ "semver": "^6.3.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/babel-plugin-jest-hoist": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz",
+ "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/template": "^7.3.3",
+ "@babel/types": "^7.3.3",
+ "@types/babel__core": "^7.1.14",
+ "@types/babel__traverse": "^7.0.6"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/babel-preset-current-node-syntax": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz",
+ "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/plugin-syntax-async-generators": "^7.8.4",
+ "@babel/plugin-syntax-bigint": "^7.8.3",
+ "@babel/plugin-syntax-class-properties": "^7.12.13",
+ "@babel/plugin-syntax-class-static-block": "^7.14.5",
+ "@babel/plugin-syntax-import-attributes": "^7.24.7",
+ "@babel/plugin-syntax-import-meta": "^7.10.4",
+ "@babel/plugin-syntax-json-strings": "^7.8.3",
+ "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4",
+ "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3",
+ "@babel/plugin-syntax-numeric-separator": "^7.10.4",
+ "@babel/plugin-syntax-object-rest-spread": "^7.8.3",
+ "@babel/plugin-syntax-optional-catch-binding": "^7.8.3",
+ "@babel/plugin-syntax-optional-chaining": "^7.8.3",
+ "@babel/plugin-syntax-private-property-in-object": "^7.14.5",
+ "@babel/plugin-syntax-top-level-await": "^7.14.5"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0 || ^8.0.0-0"
+ }
+ },
+ "node_modules/babel-preset-jest": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz",
+ "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "babel-plugin-jest-hoist": "^29.6.3",
+ "babel-preset-current-node-syntax": "^1.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/base64id": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz",
+ "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==",
+ "license": "MIT",
+ "engines": {
+ "node": "^4.5.0 || >= 5.9"
+ }
+ },
+ "node_modules/baseline-browser-mapping": {
+ "version": "2.9.10",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.10.tgz",
+ "integrity": "sha512-2VIKvDx8Z1a9rTB2eCkdPE5nSe28XnA+qivGnWHoB40hMMt/h1hSz0960Zqsn6ZyxWXUie0EBdElKv8may20AA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "baseline-browser-mapping": "dist/cli.js"
+ }
+ },
+ "node_modules/bcryptjs": {
+ "version": "2.4.3",
+ "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz",
+ "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==",
+ "license": "MIT"
+ },
+ "node_modules/binary-extensions": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
+ "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/body-parser": {
+ "version": "1.20.4",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz",
+ "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==",
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "~3.1.2",
+ "content-type": "~1.0.5",
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "destroy": "~1.2.0",
+ "http-errors": "~2.0.1",
+ "iconv-lite": "~0.4.24",
+ "on-finished": "~2.4.1",
+ "qs": "~6.14.0",
+ "raw-body": "~2.5.3",
+ "type-is": "~1.6.18",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8",
+ "npm": "1.2.8000 || >= 1.4.16"
+ }
+ },
+ "node_modules/brace-expansion": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/braces": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fill-range": "^7.1.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz",
+ "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "baseline-browser-mapping": "^2.9.0",
+ "caniuse-lite": "^1.0.30001759",
+ "electron-to-chromium": "^1.5.263",
+ "node-releases": "^2.0.27",
+ "update-browserslist-db": "^1.2.0"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/bser": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz",
+ "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "node-int64": "^0.4.0"
+ }
+ },
+ "node_modules/bson": {
+ "version": "6.10.4",
+ "resolved": "https://registry.npmjs.org/bson/-/bson-6.10.4.tgz",
+ "integrity": "sha512-WIsKqkSC0ABoBJuT1LEX+2HEvNmNKKgnTAyd0fL8qzK4SH2i9NXg+t08YtdZp/V9IZ33cxe3iV4yM0qg8lMQng==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=16.20.1"
+ }
+ },
+ "node_modules/buffer-equal-constant-time": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
+ "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/buffer-from": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
+ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/bytes": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
+ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/call-bind-apply-helpers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/call-bound": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
+ "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "get-intrinsic": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/camelcase": {
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
+ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001760",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001760.tgz",
+ "integrity": "sha512-7AAMPcueWELt1p3mi13HR/LHH0TJLT11cnwDJEs3xA4+CK/PLKeO9Kl1oru24htkyUKtkGCvAx4ohB0Ttry8Dw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "CC-BY-4.0"
+ },
+ "node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/char-regex": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz",
+ "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/chokidar": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
+ "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "anymatch": "~3.1.2",
+ "braces": "~3.0.2",
+ "glob-parent": "~5.1.2",
+ "is-binary-path": "~2.1.0",
+ "is-glob": "~4.0.1",
+ "normalize-path": "~3.0.0",
+ "readdirp": "~3.6.0"
+ },
+ "engines": {
+ "node": ">= 8.10.0"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/ci-info": {
+ "version": "3.9.0",
+ "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz",
+ "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/sibiraj-s"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/cjs-module-lexer": {
+ "version": "1.4.3",
+ "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz",
+ "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/cliui": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
+ "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.1",
+ "wrap-ansi": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/cluster-key-slot": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz",
+ "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/co": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
+ "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "iojs": ">= 1.0.0",
+ "node": ">= 0.12.0"
+ }
+ },
+ "node_modules/collect-v8-coverage": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz",
+ "integrity": "sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/color": {
+ "version": "5.0.3",
+ "resolved": "https://registry.npmjs.org/color/-/color-5.0.3.tgz",
+ "integrity": "sha512-ezmVcLR3xAVp8kYOm4GS45ZLLgIE6SPAFoduLr6hTDajwb3KZ2F46gulK3XpcwRFb5KKGCSezCBAY4Dw4HsyXA==",
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^3.1.3",
+ "color-string": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/color-string": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/color-string/-/color-string-2.1.4.tgz",
+ "integrity": "sha512-Bb6Cq8oq0IjDOe8wJmi4JeNn763Xs9cfrBcaylK1tPypWzyoy2G3l90v9k64kjphl/ZJjPIShFztenRomi8WTg==",
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/color-string/node_modules/color-name": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz",
+ "integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.20"
+ }
+ },
+ "node_modules/color/node_modules/color-convert": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-3.1.3.tgz",
+ "integrity": "sha512-fasDH2ont2GqF5HpyO4w0+BcewlhHEZOFn9c1ckZdHpJ56Qb7MHhH/IcJZbBGgvdtwdwNbLvxiBEdg336iA9Sg==",
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=14.6"
+ }
+ },
+ "node_modules/color/node_modules/color-name": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.0.tgz",
+ "integrity": "sha512-1bPaDNFm0axzE4MEAzKPuqKWeRaT43U/hyxKPBdqTfmPF+d6n7FSoTFxLVULUJOmiLp01KjhIPPH+HrXZJN4Rg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.20"
+ }
+ },
+ "node_modules/combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "delayed-stream": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/component-emitter": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz",
+ "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/compressible": {
+ "version": "2.0.18",
+ "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz",
+ "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": ">= 1.43.0 < 2"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/compression": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz",
+ "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==",
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "3.1.2",
+ "compressible": "~2.0.18",
+ "debug": "2.6.9",
+ "negotiator": "~0.6.4",
+ "on-headers": "~1.1.0",
+ "safe-buffer": "5.2.1",
+ "vary": "~1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/content-disposition": {
+ "version": "0.5.4",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
+ "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
+ "license": "MIT",
+ "dependencies": {
+ "safe-buffer": "5.2.1"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/content-type": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
+ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/cookie": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
+ "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie-signature": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz",
+ "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==",
+ "license": "MIT"
+ },
+ "node_modules/cookiejar": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz",
+ "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/cors": {
+ "version": "2.8.5",
+ "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
+ "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
+ "license": "MIT",
+ "dependencies": {
+ "object-assign": "^4",
+ "vary": "^1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/create-jest": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz",
+ "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/types": "^29.6.3",
+ "chalk": "^4.0.0",
+ "exit": "^0.1.2",
+ "graceful-fs": "^4.2.9",
+ "jest-config": "^29.7.0",
+ "jest-util": "^29.7.0",
+ "prompts": "^2.0.1"
+ },
+ "bin": {
+ "create-jest": "bin/create-jest.js"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/dedent": {
+ "version": "1.7.1",
+ "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.1.tgz",
+ "integrity": "sha512-9JmrhGZpOlEgOLdQgSm0zxFaYoQon408V1v49aqTWuXENVlnCuY9JBZcXZiCsZQWDjTm5Qf/nIvAy77mXDAjEg==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "babel-plugin-macros": "^3.1.0"
+ },
+ "peerDependenciesMeta": {
+ "babel-plugin-macros": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/deepmerge": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
+ "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/depd": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/destroy": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
+ "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8",
+ "npm": "1.2.8000 || >= 1.4.16"
+ }
+ },
+ "node_modules/detect-newline": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz",
+ "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/dezalgo": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz",
+ "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "asap": "^2.0.0",
+ "wrappy": "1"
+ }
+ },
+ "node_modules/diff-sequences": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz",
+ "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/dotenv": {
+ "version": "16.6.1",
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz",
+ "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://dotenvx.com"
+ }
+ },
+ "node_modules/dunder-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/ecdsa-sig-formatter": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
+ "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "node_modules/ee-first": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
+ "license": "MIT"
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.267",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz",
+ "integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/emittery": {
+ "version": "0.13.1",
+ "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz",
+ "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/emittery?sponsor=1"
+ }
+ },
+ "node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/enabled": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz",
+ "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==",
+ "license": "MIT"
+ },
+ "node_modules/encodeurl": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
+ "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/engine.io": {
+ "version": "6.6.4",
+ "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.4.tgz",
+ "integrity": "sha512-ZCkIjSYNDyGn0R6ewHDtXgns/Zre/NT6Agvq1/WobF7JXgFff4SeDroKiCO3fNJreU9YG429Sc81o4w5ok/W5g==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/cors": "^2.8.12",
+ "@types/node": ">=10.0.0",
+ "accepts": "~1.3.4",
+ "base64id": "2.0.0",
+ "cookie": "~0.7.2",
+ "cors": "~2.8.5",
+ "debug": "~4.3.1",
+ "engine.io-parser": "~5.2.1",
+ "ws": "~8.17.1"
+ },
+ "engines": {
+ "node": ">=10.2.0"
+ }
+ },
+ "node_modules/engine.io-parser": {
+ "version": "5.2.3",
+ "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz",
+ "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/engine.io/node_modules/debug": {
+ "version": "4.3.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
+ "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/engine.io/node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "license": "MIT"
+ },
+ "node_modules/error-ex": {
+ "version": "1.3.4",
+ "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz",
+ "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-arrayish": "^0.2.1"
+ }
+ },
+ "node_modules/es-define-property": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-object-atoms": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+ "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-set-tostringtag": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
+ "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.6",
+ "has-tostringtag": "^1.0.2",
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/escape-html": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
+ "license": "MIT"
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz",
+ "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/esprima": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
+ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "bin": {
+ "esparse": "bin/esparse.js",
+ "esvalidate": "bin/esvalidate.js"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/etag": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/execa": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
+ "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cross-spawn": "^7.0.3",
+ "get-stream": "^6.0.0",
+ "human-signals": "^2.1.0",
+ "is-stream": "^2.0.0",
+ "merge-stream": "^2.0.0",
+ "npm-run-path": "^4.0.1",
+ "onetime": "^5.1.2",
+ "signal-exit": "^3.0.3",
+ "strip-final-newline": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/execa?sponsor=1"
+ }
+ },
+ "node_modules/exit": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz",
+ "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/expect": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz",
+ "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/expect-utils": "^29.7.0",
+ "jest-get-type": "^29.6.3",
+ "jest-matcher-utils": "^29.7.0",
+ "jest-message-util": "^29.7.0",
+ "jest-util": "^29.7.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/express": {
+ "version": "4.22.1",
+ "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz",
+ "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==",
+ "license": "MIT",
+ "dependencies": {
+ "accepts": "~1.3.8",
+ "array-flatten": "1.1.1",
+ "body-parser": "~1.20.3",
+ "content-disposition": "~0.5.4",
+ "content-type": "~1.0.4",
+ "cookie": "~0.7.1",
+ "cookie-signature": "~1.0.6",
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "etag": "~1.8.1",
+ "finalhandler": "~1.3.1",
+ "fresh": "~0.5.2",
+ "http-errors": "~2.0.0",
+ "merge-descriptors": "1.0.3",
+ "methods": "~1.1.2",
+ "on-finished": "~2.4.1",
+ "parseurl": "~1.3.3",
+ "path-to-regexp": "~0.1.12",
+ "proxy-addr": "~2.0.7",
+ "qs": "~6.14.0",
+ "range-parser": "~1.2.1",
+ "safe-buffer": "5.2.1",
+ "send": "~0.19.0",
+ "serve-static": "~1.16.2",
+ "setprototypeof": "1.2.0",
+ "statuses": "~2.0.1",
+ "type-is": "~1.6.18",
+ "utils-merge": "1.0.1",
+ "vary": "~1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.10.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-safe-stringify": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz",
+ "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fb-watchman": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz",
+ "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "bser": "2.1.1"
+ }
+ },
+ "node_modules/fecha": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz",
+ "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==",
+ "license": "MIT"
+ },
+ "node_modules/fill-range": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/finalhandler": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz",
+ "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "2.6.9",
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "on-finished": "~2.4.1",
+ "parseurl": "~1.3.3",
+ "statuses": "~2.0.2",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/find-up": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "locate-path": "^5.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/fn.name": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz",
+ "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==",
+ "license": "MIT"
+ },
+ "node_modules/form-data": {
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
+ "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "es-set-tostringtag": "^2.1.0",
+ "hasown": "^2.0.2",
+ "mime-types": "^2.1.12"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/formidable": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/formidable/-/formidable-2.1.5.tgz",
+ "integrity": "sha512-Oz5Hwvwak/DCaXVVUtPn4oLMLLy1CdclLKO1LFgU7XzDpVMUU5UjlSLpGMocyQNNk8F6IJW9M/YdooSn2MRI+Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@paralleldrive/cuid2": "^2.2.2",
+ "dezalgo": "^1.0.4",
+ "once": "^1.4.0",
+ "qs": "^6.11.0"
+ },
+ "funding": {
+ "url": "https://ko-fi.com/tunnckoCore/commissions"
+ }
+ },
+ "node_modules/forwarded": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
+ "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/fresh": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
+ "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/generic-pool": {
+ "version": "3.9.0",
+ "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.9.0.tgz",
+ "integrity": "sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/get-caller-file": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": "6.* || 8.* || >= 10.*"
+ }
+ },
+ "node_modules/get-intrinsic": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "es-define-property": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
+ "function-bind": "^1.1.2",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "hasown": "^2.0.2",
+ "math-intrinsics": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-package-type": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz",
+ "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
+ "node_modules/get-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+ "license": "MIT",
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/get-stream": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
+ "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/glob": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+ "deprecated": "Glob versions prior to v9 are no longer supported",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/gopd": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/graceful-fs": {
+ "version": "4.2.11",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/has-symbols": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-tostringtag": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
+ "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-symbols": "^1.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/helmet": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/helmet/-/helmet-7.2.0.tgz",
+ "integrity": "sha512-ZRiwvN089JfMXokizgqEPXsl2Guk094yExfoDXR0cBYWxtBbaSww/w+vT4WEJsBW2iTUi1GgZ6swmoug3Oy4Xw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=16.0.0"
+ }
+ },
+ "node_modules/html-escaper": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
+ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/http-errors": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
+ "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
+ "license": "MIT",
+ "dependencies": {
+ "depd": "~2.0.0",
+ "inherits": "~2.0.4",
+ "setprototypeof": "~1.2.0",
+ "statuses": "~2.0.2",
+ "toidentifier": "~1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/human-signals": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
+ "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=10.17.0"
+ }
+ },
+ "node_modules/iconv-lite": {
+ "version": "0.4.24",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+ "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/ignore-by-default": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz",
+ "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/import-local": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz",
+ "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "pkg-dir": "^4.2.0",
+ "resolve-cwd": "^3.0.0"
+ },
+ "bin": {
+ "import-local-fixture": "fixtures/cli.js"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8.19"
+ }
+ },
+ "node_modules/inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
+ "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "license": "ISC"
+ },
+ "node_modules/ipaddr.js": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
+ "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/is-arrayish": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
+ "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/is-binary-path": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "binary-extensions": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-core-module": {
+ "version": "2.16.1",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
+ "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-generator-fn": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz",
+ "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
+ "node_modules/is-stream": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
+ "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/istanbul-lib-coverage": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz",
+ "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/istanbul-lib-instrument": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz",
+ "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@babel/core": "^7.23.9",
+ "@babel/parser": "^7.23.9",
+ "@istanbuljs/schema": "^0.1.3",
+ "istanbul-lib-coverage": "^3.2.0",
+ "semver": "^7.5.4"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/istanbul-lib-instrument/node_modules/semver": {
+ "version": "7.7.3",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
+ "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/istanbul-lib-report": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz",
+ "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "istanbul-lib-coverage": "^3.0.0",
+ "make-dir": "^4.0.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/istanbul-lib-source-maps": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz",
+ "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "debug": "^4.1.1",
+ "istanbul-lib-coverage": "^3.0.0",
+ "source-map": "^0.6.1"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/istanbul-lib-source-maps/node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/istanbul-lib-source-maps/node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/istanbul-reports": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz",
+ "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "html-escaper": "^2.0.0",
+ "istanbul-lib-report": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/jest": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz",
+ "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/core": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "import-local": "^3.0.2",
+ "jest-cli": "^29.7.0"
+ },
+ "bin": {
+ "jest": "bin/jest.js"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ },
+ "peerDependencies": {
+ "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0"
+ },
+ "peerDependenciesMeta": {
+ "node-notifier": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/jest-changed-files": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz",
+ "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "execa": "^5.0.0",
+ "jest-util": "^29.7.0",
+ "p-limit": "^3.1.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-circus": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz",
+ "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/environment": "^29.7.0",
+ "@jest/expect": "^29.7.0",
+ "@jest/test-result": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "@types/node": "*",
+ "chalk": "^4.0.0",
+ "co": "^4.6.0",
+ "dedent": "^1.0.0",
+ "is-generator-fn": "^2.0.0",
+ "jest-each": "^29.7.0",
+ "jest-matcher-utils": "^29.7.0",
+ "jest-message-util": "^29.7.0",
+ "jest-runtime": "^29.7.0",
+ "jest-snapshot": "^29.7.0",
+ "jest-util": "^29.7.0",
+ "p-limit": "^3.1.0",
+ "pretty-format": "^29.7.0",
+ "pure-rand": "^6.0.0",
+ "slash": "^3.0.0",
+ "stack-utils": "^2.0.3"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-cli": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz",
+ "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/core": "^29.7.0",
+ "@jest/test-result": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "chalk": "^4.0.0",
+ "create-jest": "^29.7.0",
+ "exit": "^0.1.2",
+ "import-local": "^3.0.2",
+ "jest-config": "^29.7.0",
+ "jest-util": "^29.7.0",
+ "jest-validate": "^29.7.0",
+ "yargs": "^17.3.1"
+ },
+ "bin": {
+ "jest": "bin/jest.js"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ },
+ "peerDependencies": {
+ "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0"
+ },
+ "peerDependenciesMeta": {
+ "node-notifier": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/jest-config": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz",
+ "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.11.6",
+ "@jest/test-sequencer": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "babel-jest": "^29.7.0",
+ "chalk": "^4.0.0",
+ "ci-info": "^3.2.0",
+ "deepmerge": "^4.2.2",
+ "glob": "^7.1.3",
+ "graceful-fs": "^4.2.9",
+ "jest-circus": "^29.7.0",
+ "jest-environment-node": "^29.7.0",
+ "jest-get-type": "^29.6.3",
+ "jest-regex-util": "^29.6.3",
+ "jest-resolve": "^29.7.0",
+ "jest-runner": "^29.7.0",
+ "jest-util": "^29.7.0",
+ "jest-validate": "^29.7.0",
+ "micromatch": "^4.0.4",
+ "parse-json": "^5.2.0",
+ "pretty-format": "^29.7.0",
+ "slash": "^3.0.0",
+ "strip-json-comments": "^3.1.1"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ },
+ "peerDependencies": {
+ "@types/node": "*",
+ "ts-node": ">=9.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "ts-node": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/jest-diff": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz",
+ "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "chalk": "^4.0.0",
+ "diff-sequences": "^29.6.3",
+ "jest-get-type": "^29.6.3",
+ "pretty-format": "^29.7.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-docblock": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz",
+ "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "detect-newline": "^3.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-each": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz",
+ "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/types": "^29.6.3",
+ "chalk": "^4.0.0",
+ "jest-get-type": "^29.6.3",
+ "jest-util": "^29.7.0",
+ "pretty-format": "^29.7.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-environment-node": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz",
+ "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/environment": "^29.7.0",
+ "@jest/fake-timers": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "@types/node": "*",
+ "jest-mock": "^29.7.0",
+ "jest-util": "^29.7.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-get-type": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz",
+ "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-haste-map": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz",
+ "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/types": "^29.6.3",
+ "@types/graceful-fs": "^4.1.3",
+ "@types/node": "*",
+ "anymatch": "^3.0.3",
+ "fb-watchman": "^2.0.0",
+ "graceful-fs": "^4.2.9",
+ "jest-regex-util": "^29.6.3",
+ "jest-util": "^29.7.0",
+ "jest-worker": "^29.7.0",
+ "micromatch": "^4.0.4",
+ "walker": "^1.0.8"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "^2.3.2"
+ }
+ },
+ "node_modules/jest-leak-detector": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz",
+ "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "jest-get-type": "^29.6.3",
+ "pretty-format": "^29.7.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-matcher-utils": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz",
+ "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "chalk": "^4.0.0",
+ "jest-diff": "^29.7.0",
+ "jest-get-type": "^29.6.3",
+ "pretty-format": "^29.7.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-message-util": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz",
+ "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.12.13",
+ "@jest/types": "^29.6.3",
+ "@types/stack-utils": "^2.0.0",
+ "chalk": "^4.0.0",
+ "graceful-fs": "^4.2.9",
+ "micromatch": "^4.0.4",
+ "pretty-format": "^29.7.0",
+ "slash": "^3.0.0",
+ "stack-utils": "^2.0.3"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-mock": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz",
+ "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/types": "^29.6.3",
+ "@types/node": "*",
+ "jest-util": "^29.7.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-pnp-resolver": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz",
+ "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ },
+ "peerDependencies": {
+ "jest-resolve": "*"
+ },
+ "peerDependenciesMeta": {
+ "jest-resolve": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/jest-regex-util": {
+ "version": "29.6.3",
+ "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz",
+ "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-resolve": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz",
+ "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "chalk": "^4.0.0",
+ "graceful-fs": "^4.2.9",
+ "jest-haste-map": "^29.7.0",
+ "jest-pnp-resolver": "^1.2.2",
+ "jest-util": "^29.7.0",
+ "jest-validate": "^29.7.0",
+ "resolve": "^1.20.0",
+ "resolve.exports": "^2.0.0",
+ "slash": "^3.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-resolve-dependencies": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz",
+ "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "jest-regex-util": "^29.6.3",
+ "jest-snapshot": "^29.7.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-runner": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz",
+ "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/console": "^29.7.0",
+ "@jest/environment": "^29.7.0",
+ "@jest/test-result": "^29.7.0",
+ "@jest/transform": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "@types/node": "*",
+ "chalk": "^4.0.0",
+ "emittery": "^0.13.1",
+ "graceful-fs": "^4.2.9",
+ "jest-docblock": "^29.7.0",
+ "jest-environment-node": "^29.7.0",
+ "jest-haste-map": "^29.7.0",
+ "jest-leak-detector": "^29.7.0",
+ "jest-message-util": "^29.7.0",
+ "jest-resolve": "^29.7.0",
+ "jest-runtime": "^29.7.0",
+ "jest-util": "^29.7.0",
+ "jest-watcher": "^29.7.0",
+ "jest-worker": "^29.7.0",
+ "p-limit": "^3.1.0",
+ "source-map-support": "0.5.13"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-runtime": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz",
+ "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/environment": "^29.7.0",
+ "@jest/fake-timers": "^29.7.0",
+ "@jest/globals": "^29.7.0",
+ "@jest/source-map": "^29.6.3",
+ "@jest/test-result": "^29.7.0",
+ "@jest/transform": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "@types/node": "*",
+ "chalk": "^4.0.0",
+ "cjs-module-lexer": "^1.0.0",
+ "collect-v8-coverage": "^1.0.0",
+ "glob": "^7.1.3",
+ "graceful-fs": "^4.2.9",
+ "jest-haste-map": "^29.7.0",
+ "jest-message-util": "^29.7.0",
+ "jest-mock": "^29.7.0",
+ "jest-regex-util": "^29.6.3",
+ "jest-resolve": "^29.7.0",
+ "jest-snapshot": "^29.7.0",
+ "jest-util": "^29.7.0",
+ "slash": "^3.0.0",
+ "strip-bom": "^4.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-snapshot": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz",
+ "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.11.6",
+ "@babel/generator": "^7.7.2",
+ "@babel/plugin-syntax-jsx": "^7.7.2",
+ "@babel/plugin-syntax-typescript": "^7.7.2",
+ "@babel/types": "^7.3.3",
+ "@jest/expect-utils": "^29.7.0",
+ "@jest/transform": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "babel-preset-current-node-syntax": "^1.0.0",
+ "chalk": "^4.0.0",
+ "expect": "^29.7.0",
+ "graceful-fs": "^4.2.9",
+ "jest-diff": "^29.7.0",
+ "jest-get-type": "^29.6.3",
+ "jest-matcher-utils": "^29.7.0",
+ "jest-message-util": "^29.7.0",
+ "jest-util": "^29.7.0",
+ "natural-compare": "^1.4.0",
+ "pretty-format": "^29.7.0",
+ "semver": "^7.5.3"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-snapshot/node_modules/semver": {
+ "version": "7.7.3",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
+ "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/jest-util": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz",
+ "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/types": "^29.6.3",
+ "@types/node": "*",
+ "chalk": "^4.0.0",
+ "ci-info": "^3.2.0",
+ "graceful-fs": "^4.2.9",
+ "picomatch": "^2.2.3"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-validate": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz",
+ "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/types": "^29.6.3",
+ "camelcase": "^6.2.0",
+ "chalk": "^4.0.0",
+ "jest-get-type": "^29.6.3",
+ "leven": "^3.1.0",
+ "pretty-format": "^29.7.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-validate/node_modules/camelcase": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
+ "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/jest-watcher": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz",
+ "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/test-result": "^29.7.0",
+ "@jest/types": "^29.6.3",
+ "@types/node": "*",
+ "ansi-escapes": "^4.2.1",
+ "chalk": "^4.0.0",
+ "emittery": "^0.13.1",
+ "jest-util": "^29.7.0",
+ "string-length": "^4.0.1"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-worker": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz",
+ "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*",
+ "jest-util": "^29.7.0",
+ "merge-stream": "^2.0.0",
+ "supports-color": "^8.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/jest-worker/node_modules/supports-color": {
+ "version": "8.1.1",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
+ "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/supports-color?sponsor=1"
+ }
+ },
+ "node_modules/joi": {
+ "version": "17.13.3",
+ "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.3.tgz",
+ "integrity": "sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@hapi/hoek": "^9.3.0",
+ "@hapi/topo": "^5.1.0",
+ "@sideway/address": "^4.1.5",
+ "@sideway/formula": "^3.0.1",
+ "@sideway/pinpoint": "^2.0.0"
+ }
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/js-yaml": {
+ "version": "3.14.2",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz",
+ "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "argparse": "^1.0.7",
+ "esprima": "^4.0.0"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/jsesc": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/json-parse-even-better-errors": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
+ "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/jsonwebtoken": {
+ "version": "9.0.3",
+ "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz",
+ "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==",
+ "license": "MIT",
+ "dependencies": {
+ "jws": "^4.0.1",
+ "lodash.includes": "^4.3.0",
+ "lodash.isboolean": "^3.0.3",
+ "lodash.isinteger": "^4.0.4",
+ "lodash.isnumber": "^3.0.3",
+ "lodash.isplainobject": "^4.0.6",
+ "lodash.isstring": "^4.0.1",
+ "lodash.once": "^4.0.0",
+ "ms": "^2.1.1",
+ "semver": "^7.5.4"
+ },
+ "engines": {
+ "node": ">=12",
+ "npm": ">=6"
+ }
+ },
+ "node_modules/jsonwebtoken/node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "license": "MIT"
+ },
+ "node_modules/jsonwebtoken/node_modules/semver": {
+ "version": "7.7.3",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
+ "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/jwa": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz",
+ "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==",
+ "license": "MIT",
+ "dependencies": {
+ "buffer-equal-constant-time": "^1.0.1",
+ "ecdsa-sig-formatter": "1.0.11",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "node_modules/jws": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz",
+ "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==",
+ "license": "MIT",
+ "dependencies": {
+ "jwa": "^2.0.1",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "node_modules/kareem": {
+ "version": "2.6.3",
+ "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.6.3.tgz",
+ "integrity": "sha512-C3iHfuGUXK2u8/ipq9LfjFfXFxAZMQJJq7vLS45r3D9Y2xQ/m4S8zaR4zMLFWh9AsNPXmcFfUDhTEO8UIC/V6Q==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
+ "node_modules/kleur": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz",
+ "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/kuler": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz",
+ "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==",
+ "license": "MIT"
+ },
+ "node_modules/leven": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz",
+ "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/lines-and-columns": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
+ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/locate-path": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-locate": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/lodash.includes": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
+ "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.isboolean": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
+ "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.isinteger": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
+ "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.isnumber": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
+ "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.isplainobject": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
+ "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.isstring": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
+ "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.once": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
+ "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==",
+ "license": "MIT"
+ },
+ "node_modules/logform": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/logform/-/logform-2.7.0.tgz",
+ "integrity": "sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@colors/colors": "1.6.0",
+ "@types/triple-beam": "^1.3.2",
+ "fecha": "^4.2.0",
+ "ms": "^2.1.1",
+ "safe-stable-stringify": "^2.3.1",
+ "triple-beam": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 12.0.0"
+ }
+ },
+ "node_modules/logform/node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "license": "MIT"
+ },
+ "node_modules/lru-cache": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^3.0.2"
+ }
+ },
+ "node_modules/make-dir": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz",
+ "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "semver": "^7.5.3"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/make-dir/node_modules/semver": {
+ "version": "7.7.3",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
+ "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/makeerror": {
+ "version": "1.0.12",
+ "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz",
+ "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "tmpl": "1.0.5"
+ }
+ },
+ "node_modules/math-intrinsics": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/media-typer": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+ "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/memory-pager": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz",
+ "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==",
+ "license": "MIT"
+ },
+ "node_modules/merge-descriptors": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
+ "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/merge-stream": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
+ "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/methods": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
+ "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/micromatch": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
+ "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "braces": "^3.0.3",
+ "picomatch": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=8.6"
+ }
+ },
+ "node_modules/mime": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
+ "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
+ "license": "MIT",
+ "bin": {
+ "mime": "cli.js"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.54.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
+ "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types/node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mimic-fn": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
+ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/mongodb": {
+ "version": "6.20.0",
+ "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.20.0.tgz",
+ "integrity": "sha512-Tl6MEIU3K4Rq3TSHd+sZQqRBoGlFsOgNrH5ltAcFBV62Re3Fd+FcaVf8uSEQFOJ51SDowDVttBTONMfoYWrWlQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@mongodb-js/saslprep": "^1.3.0",
+ "bson": "^6.10.4",
+ "mongodb-connection-string-url": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=16.20.1"
+ },
+ "peerDependencies": {
+ "@aws-sdk/credential-providers": "^3.188.0",
+ "@mongodb-js/zstd": "^1.1.0 || ^2.0.0",
+ "gcp-metadata": "^5.2.0",
+ "kerberos": "^2.0.1",
+ "mongodb-client-encryption": ">=6.0.0 <7",
+ "snappy": "^7.3.2",
+ "socks": "^2.7.1"
+ },
+ "peerDependenciesMeta": {
+ "@aws-sdk/credential-providers": {
+ "optional": true
+ },
+ "@mongodb-js/zstd": {
+ "optional": true
+ },
+ "gcp-metadata": {
+ "optional": true
+ },
+ "kerberos": {
+ "optional": true
+ },
+ "mongodb-client-encryption": {
+ "optional": true
+ },
+ "snappy": {
+ "optional": true
+ },
+ "socks": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/mongodb-connection-string-url": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.2.tgz",
+ "integrity": "sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@types/whatwg-url": "^11.0.2",
+ "whatwg-url": "^14.1.0 || ^13.0.0"
+ }
+ },
+ "node_modules/mongoose": {
+ "version": "8.20.4",
+ "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.20.4.tgz",
+ "integrity": "sha512-o4ABeT3IEk1Z4dGt3XjHJ0x9OjyWvakC1+btPpzWqCovqyidKGdbB05/g87cdh7AuWXFQKHOxt+L/OZOBps4hw==",
+ "license": "MIT",
+ "dependencies": {
+ "bson": "^6.10.4",
+ "kareem": "2.6.3",
+ "mongodb": "~6.20.0",
+ "mpath": "0.9.0",
+ "mquery": "5.0.0",
+ "ms": "2.1.3",
+ "sift": "17.1.3"
+ },
+ "engines": {
+ "node": ">=16.20.1"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mongoose"
+ }
+ },
+ "node_modules/mongoose/node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "license": "MIT"
+ },
+ "node_modules/mpath": {
+ "version": "0.9.0",
+ "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz",
+ "integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=4.0.0"
+ }
+ },
+ "node_modules/mquery": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/mquery/-/mquery-5.0.0.tgz",
+ "integrity": "sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "4.x"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/mquery/node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/mquery/node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "license": "MIT"
+ },
+ "node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+ "license": "MIT"
+ },
+ "node_modules/natural-compare": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/negotiator": {
+ "version": "0.6.4",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz",
+ "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/node-int64": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz",
+ "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.27",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz",
+ "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/nodemon": {
+ "version": "3.1.11",
+ "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.11.tgz",
+ "integrity": "sha512-is96t8F/1//UHAjNPHpbsNY46ELPpftGUoSVNXwUfMk/qdjSylYrWSu1XavVTBOn526kFiOR733ATgNBCQyH0g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "chokidar": "^3.5.2",
+ "debug": "^4",
+ "ignore-by-default": "^1.0.1",
+ "minimatch": "^3.1.2",
+ "pstree.remy": "^1.1.8",
+ "semver": "^7.5.3",
+ "simple-update-notifier": "^2.0.0",
+ "supports-color": "^5.5.0",
+ "touch": "^3.1.0",
+ "undefsafe": "^2.0.5"
+ },
+ "bin": {
+ "nodemon": "bin/nodemon.js"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/nodemon"
+ }
+ },
+ "node_modules/nodemon/node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/nodemon/node_modules/has-flag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+ "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/nodemon/node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/nodemon/node_modules/semver": {
+ "version": "7.7.3",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
+ "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/nodemon/node_modules/supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/npm-run-path": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
+ "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object-inspect": {
+ "version": "1.13.4",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
+ "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/on-finished": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
+ "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+ "license": "MIT",
+ "dependencies": {
+ "ee-first": "1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/on-headers": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz",
+ "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/one-time": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz",
+ "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==",
+ "license": "MIT",
+ "dependencies": {
+ "fn.name": "1.x.x"
+ }
+ },
+ "node_modules/onetime": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
+ "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mimic-fn": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "yocto-queue": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-limit": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/p-locate/node_modules/p-limit": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+ "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-try": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-try": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/parse-json": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
+ "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.0.0",
+ "error-ex": "^1.3.1",
+ "json-parse-even-better-errors": "^2.3.0",
+ "lines-and-columns": "^1.1.6"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/parseurl": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-parse": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
+ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/path-to-regexp": {
+ "version": "0.1.12",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
+ "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
+ "license": "MIT"
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/pirates": {
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz",
+ "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/pkg-dir": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz",
+ "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "find-up": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/pretty-format": {
+ "version": "29.7.0",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
+ "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jest/schemas": "^29.6.3",
+ "ansi-styles": "^5.0.0",
+ "react-is": "^18.0.0"
+ },
+ "engines": {
+ "node": "^14.15.0 || ^16.10.0 || >=18.0.0"
+ }
+ },
+ "node_modules/pretty-format/node_modules/ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/prompts": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz",
+ "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "kleur": "^3.0.3",
+ "sisteransi": "^1.0.5"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/proxy-addr": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
+ "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
+ "license": "MIT",
+ "dependencies": {
+ "forwarded": "0.2.0",
+ "ipaddr.js": "1.9.1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/pstree.remy": {
+ "version": "1.1.8",
+ "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
+ "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/punycode": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/pure-rand": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz",
+ "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/dubzzz"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fast-check"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/qs": {
+ "version": "6.14.0",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
+ "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "side-channel": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=0.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/range-parser": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/rate-limiter-flexible": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/rate-limiter-flexible/-/rate-limiter-flexible-2.4.2.tgz",
+ "integrity": "sha512-rMATGGOdO1suFyf/mI5LYhts71g1sbdhmd6YvdiXO2gJnd42Tt6QS4JUKJKSWVVkMtBacm6l40FR7Trjo6Iruw==",
+ "license": "ISC"
+ },
+ "node_modules/raw-body": {
+ "version": "2.5.3",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz",
+ "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==",
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "~3.1.2",
+ "http-errors": "~2.0.1",
+ "iconv-lite": "~0.4.24",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/react-is": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
+ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/readable-stream": {
+ "version": "3.6.2",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
+ "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
+ "license": "MIT",
+ "dependencies": {
+ "inherits": "^2.0.3",
+ "string_decoder": "^1.1.1",
+ "util-deprecate": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/readdirp": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "picomatch": "^2.2.1"
+ },
+ "engines": {
+ "node": ">=8.10.0"
+ }
+ },
+ "node_modules/redis": {
+ "version": "4.7.1",
+ "resolved": "https://registry.npmjs.org/redis/-/redis-4.7.1.tgz",
+ "integrity": "sha512-S1bJDnqLftzHXHP8JsT5II/CtHWQrASX5K96REjWjlmWKrviSOLWmM7QnRLstAWsu1VBBV1ffV6DzCvxNP0UJQ==",
+ "license": "MIT",
+ "workspaces": [
+ "./packages/*"
+ ],
+ "dependencies": {
+ "@redis/bloom": "1.2.0",
+ "@redis/client": "1.6.1",
+ "@redis/graph": "1.1.1",
+ "@redis/json": "1.0.7",
+ "@redis/search": "1.2.0",
+ "@redis/time-series": "1.1.0"
+ }
+ },
+ "node_modules/require-directory": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+ "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/resolve": {
+ "version": "1.22.11",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz",
+ "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-core-module": "^2.16.1",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ },
+ "bin": {
+ "resolve": "bin/resolve"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/resolve-cwd": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz",
+ "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "resolve-from": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/resolve-from": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
+ "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/resolve.exports": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz",
+ "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/safe-stable-stringify": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz",
+ "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+ "license": "MIT"
+ },
+ "node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/send": {
+ "version": "0.19.2",
+ "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz",
+ "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "destroy": "1.2.0",
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "etag": "~1.8.1",
+ "fresh": "~0.5.2",
+ "http-errors": "~2.0.1",
+ "mime": "1.6.0",
+ "ms": "2.1.3",
+ "on-finished": "~2.4.1",
+ "range-parser": "~1.2.1",
+ "statuses": "~2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/send/node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "license": "MIT"
+ },
+ "node_modules/serve-static": {
+ "version": "1.16.3",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz",
+ "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==",
+ "license": "MIT",
+ "dependencies": {
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "parseurl": "~1.3.3",
+ "send": "~0.19.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/setprototypeof": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
+ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
+ "license": "ISC"
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/side-channel": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
+ "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.3",
+ "side-channel-list": "^1.0.0",
+ "side-channel-map": "^1.0.1",
+ "side-channel-weakmap": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-list": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
+ "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-map": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
+ "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-weakmap": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
+ "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3",
+ "side-channel-map": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/sift": {
+ "version": "17.1.3",
+ "resolved": "https://registry.npmjs.org/sift/-/sift-17.1.3.tgz",
+ "integrity": "sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ==",
+ "license": "MIT"
+ },
+ "node_modules/signal-exit": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
+ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/simple-update-notifier": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz",
+ "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "semver": "^7.5.3"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/simple-update-notifier/node_modules/semver": {
+ "version": "7.7.3",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
+ "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/sisteransi": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
+ "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/slash": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
+ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/socket.io": {
+ "version": "4.8.1",
+ "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.1.tgz",
+ "integrity": "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==",
+ "license": "MIT",
+ "dependencies": {
+ "accepts": "~1.3.4",
+ "base64id": "~2.0.0",
+ "cors": "~2.8.5",
+ "debug": "~4.3.2",
+ "engine.io": "~6.6.0",
+ "socket.io-adapter": "~2.5.2",
+ "socket.io-parser": "~4.2.4"
+ },
+ "engines": {
+ "node": ">=10.2.0"
+ }
+ },
+ "node_modules/socket.io-adapter": {
+ "version": "2.5.5",
+ "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz",
+ "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "~4.3.4",
+ "ws": "~8.17.1"
+ }
+ },
+ "node_modules/socket.io-adapter/node_modules/debug": {
+ "version": "4.3.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
+ "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/socket.io-adapter/node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "license": "MIT"
+ },
+ "node_modules/socket.io-parser": {
+ "version": "4.2.4",
+ "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz",
+ "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==",
+ "license": "MIT",
+ "dependencies": {
+ "@socket.io/component-emitter": "~3.1.0",
+ "debug": "~4.3.1"
+ },
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/socket.io-parser/node_modules/debug": {
+ "version": "4.3.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
+ "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/socket.io-parser/node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "license": "MIT"
+ },
+ "node_modules/socket.io/node_modules/debug": {
+ "version": "4.3.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
+ "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/socket.io/node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "license": "MIT"
+ },
+ "node_modules/source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/source-map-support": {
+ "version": "0.5.13",
+ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz",
+ "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "buffer-from": "^1.0.0",
+ "source-map": "^0.6.0"
+ }
+ },
+ "node_modules/sparse-bitfield": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz",
+ "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==",
+ "license": "MIT",
+ "dependencies": {
+ "memory-pager": "^1.0.2"
+ }
+ },
+ "node_modules/sprintf-js": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
+ "dev": true,
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/stack-trace": {
+ "version": "0.0.10",
+ "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz",
+ "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==",
+ "license": "MIT",
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/stack-utils": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz",
+ "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "escape-string-regexp": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/statuses": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
+ "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/string_decoder": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
+ "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
+ "license": "MIT",
+ "dependencies": {
+ "safe-buffer": "~5.2.0"
+ }
+ },
+ "node_modules/string-length": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz",
+ "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "char-regex": "^1.0.2",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-bom": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz",
+ "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-final-newline": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
+ "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/strip-json-comments": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/superagent": {
+ "version": "8.1.2",
+ "resolved": "https://registry.npmjs.org/superagent/-/superagent-8.1.2.tgz",
+ "integrity": "sha512-6WTxW1EB6yCxV5VFOIPQruWGHqc3yI7hEmZK6h+pyk69Lk/Ut7rLUY6W/ONF2MjBuGjvmMiIpsrVJ2vjrHlslA==",
+ "deprecated": "Please upgrade to superagent v10.2.2+, see release notes at https://github.com/forwardemail/superagent/releases/tag/v10.2.2 - maintenance is supported by Forward Email @ https://forwardemail.net",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "component-emitter": "^1.3.0",
+ "cookiejar": "^2.1.4",
+ "debug": "^4.3.4",
+ "fast-safe-stringify": "^2.1.1",
+ "form-data": "^4.0.0",
+ "formidable": "^2.1.2",
+ "methods": "^1.1.2",
+ "mime": "2.6.0",
+ "qs": "^6.11.0",
+ "semver": "^7.3.8"
+ },
+ "engines": {
+ "node": ">=6.4.0 <13 || >=14"
+ }
+ },
+ "node_modules/superagent/node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/superagent/node_modules/mime": {
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz",
+ "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "mime": "cli.js"
+ },
+ "engines": {
+ "node": ">=4.0.0"
+ }
+ },
+ "node_modules/superagent/node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/superagent/node_modules/semver": {
+ "version": "7.7.3",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
+ "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/supertest": {
+ "version": "6.3.4",
+ "resolved": "https://registry.npmjs.org/supertest/-/supertest-6.3.4.tgz",
+ "integrity": "sha512-erY3HFDG0dPnhw4U+udPfrzXa4xhSG+n4rxfRuZWCUvjFWwKl+OxWf/7zk50s84/fAAs7vf5QAb9uRa0cCykxw==",
+ "deprecated": "Please upgrade to supertest v7.1.3+, see release notes at https://github.com/forwardemail/supertest/releases/tag/v7.1.3 - maintenance is supported by Forward Email @ https://forwardemail.net",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "methods": "^1.1.2",
+ "superagent": "^8.1.2"
+ },
+ "engines": {
+ "node": ">=6.4.0"
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/supports-preserve-symlinks-flag": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
+ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/test-exclude": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz",
+ "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "@istanbuljs/schema": "^0.1.2",
+ "glob": "^7.1.4",
+ "minimatch": "^3.0.4"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/text-hex": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz",
+ "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==",
+ "license": "MIT"
+ },
+ "node_modules/tmpl": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz",
+ "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==",
+ "dev": true,
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-number": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/toidentifier": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
+ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.6"
+ }
+ },
+ "node_modules/touch": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz",
+ "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "nodetouch": "bin/nodetouch.js"
+ }
+ },
+ "node_modules/tr46": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz",
+ "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==",
+ "license": "MIT",
+ "dependencies": {
+ "punycode": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/triple-beam": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz",
+ "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14.0.0"
+ }
+ },
+ "node_modules/type-detect": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
+ "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/type-fest": {
+ "version": "0.21.3",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz",
+ "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==",
+ "dev": true,
+ "license": "(MIT OR CC0-1.0)",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/type-is": {
+ "version": "1.6.18",
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
+ "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
+ "license": "MIT",
+ "dependencies": {
+ "media-typer": "0.3.0",
+ "mime-types": "~2.1.24"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/undefsafe": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
+ "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/undici-types": {
+ "version": "7.16.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
+ "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
+ "license": "MIT"
+ },
+ "node_modules/unpipe": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+ "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
+ "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.1"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
+ "node_modules/util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
+ "license": "MIT"
+ },
+ "node_modules/utils-merge": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
+ "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4.0"
+ }
+ },
+ "node_modules/v8-to-istanbul": {
+ "version": "9.3.0",
+ "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz",
+ "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "@jridgewell/trace-mapping": "^0.3.12",
+ "@types/istanbul-lib-coverage": "^2.0.1",
+ "convert-source-map": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10.12.0"
+ }
+ },
+ "node_modules/vary": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/walker": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz",
+ "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "makeerror": "1.0.12"
+ }
+ },
+ "node_modules/webidl-conversions": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
+ "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/whatwg-url": {
+ "version": "14.2.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz",
+ "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==",
+ "license": "MIT",
+ "dependencies": {
+ "tr46": "^5.1.0",
+ "webidl-conversions": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/winston": {
+ "version": "3.19.0",
+ "resolved": "https://registry.npmjs.org/winston/-/winston-3.19.0.tgz",
+ "integrity": "sha512-LZNJgPzfKR+/J3cHkxcpHKpKKvGfDZVPS4hfJCc4cCG0CgYzvlD6yE/S3CIL/Yt91ak327YCpiF/0MyeZHEHKA==",
+ "license": "MIT",
+ "dependencies": {
+ "@colors/colors": "^1.6.0",
+ "@dabh/diagnostics": "^2.0.8",
+ "async": "^3.2.3",
+ "is-stream": "^2.0.0",
+ "logform": "^2.7.0",
+ "one-time": "^1.0.0",
+ "readable-stream": "^3.4.0",
+ "safe-stable-stringify": "^2.3.1",
+ "stack-trace": "0.0.x",
+ "triple-beam": "^1.3.0",
+ "winston-transport": "^4.9.0"
+ },
+ "engines": {
+ "node": ">= 12.0.0"
+ }
+ },
+ "node_modules/winston-transport": {
+ "version": "4.9.0",
+ "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.9.0.tgz",
+ "integrity": "sha512-8drMJ4rkgaPo1Me4zD/3WLfI/zPdA9o2IipKODunnGDcuqbHwjsbB79ylv04LCGGzU0xQ6vTznOMpQGaLhhm6A==",
+ "license": "MIT",
+ "dependencies": {
+ "logform": "^2.7.0",
+ "readable-stream": "^3.6.2",
+ "triple-beam": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 12.0.0"
+ }
+ },
+ "node_modules/wrap-ansi": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/write-file-atomic": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz",
+ "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "imurmurhash": "^0.1.4",
+ "signal-exit": "^3.0.7"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/ws": {
+ "version": "8.17.1",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
+ "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "peerDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": ">=5.0.2"
+ },
+ "peerDependenciesMeta": {
+ "bufferutil": {
+ "optional": true
+ },
+ "utf-8-validate": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/y18n": {
+ "version": "5.0.8",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
+ "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/yargs": {
+ "version": "17.7.2",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
+ "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cliui": "^8.0.1",
+ "escalade": "^3.1.1",
+ "get-caller-file": "^2.0.5",
+ "require-directory": "^2.1.1",
+ "string-width": "^4.2.3",
+ "y18n": "^5.0.5",
+ "yargs-parser": "^21.1.1"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/yargs-parser": {
+ "version": "21.1.1",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
+ "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/yocto-queue": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ }
+ }
+}
diff --git a/API/package.json b/API/package.json
new file mode 100644
index 0000000..1a637d8
--- /dev/null
+++ b/API/package.json
@@ -0,0 +1,41 @@
+{
+ "name": "galaxystrikeonline-server",
+ "version": "1.0.0",
+ "description": "Galaxy Strike Online - Server Backend",
+ "license": "MIT",
+ "author": "Korvarix Studios",
+ "type": "commonjs",
+ "main": "server.js",
+ "scripts": {
+ "start": "node server.js",
+ "dev": "nodemon server.js",
+ "debug": "node --inspect server.js",
+ "test": "jest",
+ "migrate": "node scripts/migrate.js",
+ "seed": "node scripts/seed.js"
+ },
+ "keywords": ["game", "server", "mmorpg", "api", "websocket"],
+ "dependencies": {
+ "express": "^4.18.2",
+ "socket.io": "^4.7.4",
+ "cors": "^2.8.5",
+ "helmet": "^7.1.0",
+ "dotenv": "^16.3.1",
+ "bcryptjs": "^2.4.3",
+ "jsonwebtoken": "^9.0.2",
+ "mongoose": "^8.0.3",
+ "redis": "^4.6.11",
+ "winston": "^3.11.0",
+ "joi": "^17.11.0",
+ "rate-limiter-flexible": "^2.4.2",
+ "compression": "^1.7.4"
+ },
+ "devDependencies": {
+ "nodemon": "^3.0.2",
+ "jest": "^29.7.0",
+ "supertest": "^6.3.3"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+}
\ No newline at end of file
diff --git a/API/routes/auth.js b/API/routes/auth.js
new file mode 100644
index 0000000..1cc0372
--- /dev/null
+++ b/API/routes/auth.js
@@ -0,0 +1,214 @@
+const express = require('express');
+const bcrypt = require('bcryptjs');
+const jwt = require('jsonwebtoken');
+const Joi = require('joi');
+const { RateLimiterMemory } = require('rate-limiter-flexible');
+const Player = require('../models/Player');
+const logger = require('../utils/logger');
+
+const router = express.Router();
+
+// Rate limiting for auth routes
+const authLimiter = new RateLimiterMemory({
+ keyGenerator: (req) => req.ip,
+ points: 5, // Number of requests
+ duration: 900, // Per 15 minutes (900 seconds)
+ blockDuration: 900, // Block for 15 minutes
+ message: 'Too many authentication attempts, please try again later.'
+});
+
+// Validation schemas
+const registerSchema = Joi.object({
+ username: Joi.string().min(3).max(30).required(),
+ email: Joi.string().email().required(),
+ password: Joi.string().min(6).required()
+});
+
+const loginSchema = Joi.object({
+ email: Joi.string().email().required(),
+ password: Joi.string().required()
+});
+
+// Register route
+router.post('/register', async (req, res) => {
+ try {
+ // Rate limiting check
+ const resLimiter = await authLimiter.consume(req.ip);
+ if (!resLimiter.remainingPoints) {
+ return res.status(429).json({ error: 'Too many authentication attempts, please try again later.' });
+ }
+
+ const { error } = registerSchema.validate(req.body);
+ if (error) {
+ return res.status(400).json({ error: error.details[0].message });
+ }
+
+ const { username, email, password } = req.body;
+
+ // Check if user already exists
+ const existingUser = await Player.findOne({
+ $or: [{ email }, { username }]
+ });
+
+ if (existingUser) {
+ return res.status(400).json({
+ error: 'User with this email or username already exists'
+ });
+ }
+
+ // Hash password
+ const salt = await bcrypt.genSalt(10);
+ const hashedPassword = await bcrypt.hash(password, salt);
+
+ // Create new player
+ const player = new Player({
+ userId: `user_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
+ username,
+ email,
+ password: hashedPassword,
+ createdAt: new Date(),
+ lastLogin: new Date()
+ });
+
+ await player.save();
+
+ // Create JWT token
+ const token = jwt.sign(
+ { userId: player.userId, email: player.email },
+ process.env.JWT_SECRET || 'fallback_secret',
+ { expiresIn: '24h' }
+ );
+
+ logger.info(`New user registered: ${email}`);
+
+ res.status(201).json({
+ message: 'User registered successfully',
+ token,
+ user: {
+ userId: player.userId,
+ username: player.username,
+ email: player.email,
+ stats: player.stats
+ }
+ });
+
+ } catch (error) {
+ logger.error('Registration error:', error);
+ res.status(500).json({ error: 'Internal server error' });
+ }
+});
+
+// Login route
+router.post('/login', async (req, res) => {
+ try {
+ // Rate limiting check
+ const resLimiter = await authLimiter.consume(req.ip);
+ if (!resLimiter.remainingPoints) {
+ return res.status(429).json({ error: 'Too many authentication attempts, please try again later.' });
+ }
+
+ const { error } = loginSchema.validate(req.body);
+ if (error) {
+ return res.status(400).json({ error: error.details[0].message });
+ }
+
+ const { email, password } = req.body;
+
+ // Find user
+ const player = await Player.findOne({ email }).select('+password');
+ if (!player) {
+ return res.status(401).json({ error: 'Invalid credentials' });
+ }
+
+ // Check if password exists (for backward compatibility with existing users)
+ if (!player.password) {
+ logger.error('Player password field is missing for user:', email);
+ return res.status(401).json({
+ error: 'Account migration required. Please re-register your account.',
+ requiresMigration: true
+ });
+ }
+
+ // Check password
+ const isPasswordValid = await bcrypt.compare(password, player.password);
+ if (!isPasswordValid) {
+ return res.status(401).json({ error: 'Invalid credentials' });
+ }
+
+ // Update last login
+ player.stats.lastLogin = new Date();
+ await player.save();
+
+ // Create JWT token
+ const token = jwt.sign(
+ { userId: player.userId, email: player.email },
+ process.env.JWT_SECRET || 'fallback_secret',
+ { expiresIn: '24h' }
+ );
+
+ logger.info(`User logged in: ${email}`);
+
+ res.json({
+ message: 'Login successful',
+ token,
+ user: {
+ userId: player.userId,
+ username: player.username,
+ email: player.email,
+ stats: player.stats,
+ info: player.info
+ }
+ });
+
+ } catch (error) {
+ logger.error('Login error:', error);
+ res.status(500).json({ error: 'Internal server error' });
+ }
+});
+
+// Verify token route
+router.get('/verify', async (req, res) => {
+ try {
+ const token = req.header('Authorization')?.replace('Bearer ', '');
+
+ if (!token) {
+ return res.status(401).json({ error: 'No token provided' });
+ }
+
+ const decoded = jwt.verify(token, process.env.JWT_SECRET || 'fallback_secret');
+
+ const player = await Player.findOne({ userId: decoded.userId });
+ if (!player) {
+ return res.status(401).json({ error: 'Invalid token' });
+ }
+
+ res.json({
+ valid: true,
+ user: {
+ userId: player.userId,
+ username: player.username,
+ email: player.email,
+ stats: player.stats,
+ info: player.info
+ }
+ });
+
+ } catch (error) {
+ logger.error('Token verification error:', error);
+ res.status(401).json({ error: 'Invalid token' });
+ }
+});
+
+// Logout route
+router.post('/logout', async (req, res) => {
+ try {
+ // In a real implementation, you might want to blacklist the token
+ // For now, we'll just return success
+ res.json({ message: 'Logout successful' });
+ } catch (error) {
+ logger.error('Logout error:', error);
+ res.status(500).json({ error: 'Internal server error' });
+ }
+});
+
+module.exports = router;
diff --git a/API/routes/game.js b/API/routes/game.js
new file mode 100644
index 0000000..702c9de
--- /dev/null
+++ b/API/routes/game.js
@@ -0,0 +1,336 @@
+const express = require('express');
+const jwt = require('jsonwebtoken');
+const Player = require('../models/Player');
+const Ship = require('../models/Ship');
+const Inventory = require('../models/Inventory');
+const { getGameSystem } = require('../systems/GameSystem');
+const logger = require('../utils/logger');
+
+const router = express.Router();
+
+// Middleware to authenticate JWT token
+const authenticateToken = (req, res, next) => {
+ const token = req.header('Authorization')?.replace('Bearer ', '');
+
+ if (!token) {
+ return res.status(401).json({ error: 'Access token required' });
+ }
+
+ try {
+ const decoded = jwt.verify(token, process.env.JWT_SECRET || 'fallback_secret');
+ req.userId = decoded.userId;
+ next();
+ } catch (error) {
+ res.status(401).json({ error: 'Invalid token' });
+ }
+};
+
+// Get player data
+router.get('/player', authenticateToken, async (req, res) => {
+ try {
+ const gameSystem = getGameSystem();
+ const player = await gameSystem.loadPlayer(req.userId);
+
+ if (!player) {
+ return res.status(404).json({ error: 'Player not found' });
+ }
+
+ res.json({
+ userId: player.userId,
+ username: player.username,
+ stats: player.stats,
+ attributes: player.attributes,
+ info: player.info,
+ settings: player.settings,
+ dailyRewards: player.dailyRewards
+ });
+
+ } catch (error) {
+ logger.error('Error getting player data:', error);
+ res.status(500).json({ error: 'Internal server error' });
+ }
+});
+
+// Get player's ships
+router.get('/ships', authenticateToken, async (req, res) => {
+ try {
+ const ships = await Ship.find({ userId: req.userId });
+
+ res.json({
+ ships: ships.map(ship => ({
+ id: ship.id,
+ name: ship.name,
+ class: ship.class,
+ level: ship.level,
+ stats: ship.stats,
+ isEquipped: ship.isEquipped,
+ isCurrent: ship.isCurrent,
+ rarity: ship.rarity,
+ texture: ship.texture,
+ acquiredAt: ship.acquiredAt
+ }))
+ });
+
+ } catch (error) {
+ logger.error('Error getting player ships:', error);
+ res.status(500).json({ error: 'Internal server error' });
+ }
+});
+
+// Equip a ship
+router.post('/ships/equip', authenticateToken, async (req, res) => {
+ try {
+ const { shipId } = req.body;
+
+ if (!shipId) {
+ return res.status(400).json({ error: 'Ship ID required' });
+ }
+
+ const gameSystem = getGameSystem();
+ const ship = await gameSystem.equipShip(req.userId, shipId);
+
+ res.json({
+ message: 'Ship equipped successfully',
+ ship: {
+ id: ship.id,
+ name: ship.name,
+ class: ship.class,
+ level: ship.level,
+ stats: ship.stats,
+ isEquipped: ship.isEquipped,
+ isCurrent: ship.isCurrent
+ }
+ });
+
+ } catch (error) {
+ logger.error('Error equipping ship:', error);
+ res.status(500).json({ error: error.message });
+ }
+});
+
+// Get player's inventory
+router.get('/inventory', authenticateToken, async (req, res) => {
+ try {
+ let inventory = await Inventory.findOne({ userId: req.userId });
+
+ if (!inventory) {
+ // Create new inventory if it doesn't exist
+ inventory = new Inventory({ userId: req.userId });
+ await inventory.save();
+ }
+
+ res.json({
+ items: inventory.items,
+ equippedItems: inventory.equippedItems,
+ summary: inventory.getInventorySummary()
+ });
+
+ } catch (error) {
+ logger.error('Error getting inventory:', error);
+ res.status(500).json({ error: 'Internal server error' });
+ }
+});
+
+// Add item to inventory
+router.post('/inventory/add', authenticateToken, async (req, res) => {
+ try {
+ const { itemData } = req.body;
+
+ if (!itemData) {
+ return res.status(400).json({ error: 'Item data required' });
+ }
+
+ let inventory = await Inventory.findOne({ userId: req.userId });
+
+ if (!inventory) {
+ inventory = new Inventory({ userId: req.userId });
+ }
+
+ await inventory.addItem(itemData);
+
+ res.json({
+ message: 'Item added to inventory',
+ item: itemData,
+ summary: inventory.getInventorySummary()
+ });
+
+ } catch (error) {
+ logger.error('Error adding item to inventory:', error);
+ res.status(500).json({ error: error.message });
+ }
+});
+
+// Equip item
+router.post('/inventory/equip', authenticateToken, async (req, res) => {
+ try {
+ const { itemId, slot } = req.body;
+
+ if (!itemId || !slot) {
+ return res.status(400).json({ error: 'Item ID and slot required' });
+ }
+
+ const inventory = await Inventory.findOne({ userId: req.userId });
+
+ if (!inventory) {
+ return res.status(404).json({ error: 'Inventory not found' });
+ }
+
+ await inventory.equipItem(itemId, slot);
+
+ res.json({
+ message: 'Item equipped successfully',
+ equippedItems: inventory.equippedItems
+ });
+
+ } catch (error) {
+ logger.error('Error equipping item:', error);
+ res.status(500).json({ error: error.message });
+ }
+});
+
+// Use consumable
+router.post('/inventory/use', authenticateToken, async (req, res) => {
+ try {
+ const { itemId } = req.body;
+
+ if (!itemId) {
+ return res.status(400).json({ error: 'Item ID required' });
+ }
+
+ const inventory = await Inventory.findOne({ userId: req.userId });
+
+ if (!inventory) {
+ return res.status(404).json({ error: 'Inventory not found' });
+ }
+
+ const effects = await inventory.useConsumable(itemId);
+
+ res.json({
+ message: 'Item used successfully',
+ effects,
+ summary: inventory.getInventorySummary()
+ });
+
+ } catch (error) {
+ logger.error('Error using consumable:', error);
+ res.status(500).json({ error: error.message });
+ }
+});
+
+// Process game action
+router.post('/action', authenticateToken, async (req, res) => {
+ try {
+ const { actionData } = req.body;
+
+ if (!actionData) {
+ return res.status(400).json({ error: 'Action data required' });
+ }
+
+ const gameSystem = getGameSystem();
+ const result = await gameSystem.processGameAction(req.userId, actionData);
+
+ res.json(result);
+
+ } catch (error) {
+ logger.error('Error processing game action:', error);
+ res.status(500).json({ error: error.message });
+ }
+});
+
+// Claim daily reward
+router.post('/daily-reward', authenticateToken, async (req, res) => {
+ try {
+ const player = await Player.findOne({ userId: req.userId });
+
+ if (!player) {
+ return res.status(404).json({ error: 'Player not found' });
+ }
+
+ const rewardResult = player.claimDailyReward();
+ await player.save();
+
+ res.json(rewardResult);
+
+ } catch (error) {
+ logger.error('Error claiming daily reward:', error);
+ res.status(500).json({ error: 'Internal server error' });
+ }
+});
+
+// Save player data
+router.post('/save', authenticateToken, async (req, res) => {
+ try {
+ const gameSystem = getGameSystem();
+ await gameSystem.savePlayer(req.userId);
+
+ res.json({ message: 'Game saved successfully' });
+
+ } catch (error) {
+ logger.error('Error saving game:', error);
+ res.status(500).json({ error: 'Internal server error' });
+ }
+});
+
+// Get shop items
+router.get('/shop/:category?', authenticateToken, async (req, res) => {
+ try {
+ const gameSystem = getGameSystem();
+ const category = req.params.category;
+ const items = gameSystem.economy.getShopItems(category);
+
+ res.json({ items });
+
+ } catch (error) {
+ logger.error('Error getting shop items:', error);
+ res.status(500).json({ error: 'Internal server error' });
+ }
+});
+
+// Purchase item
+router.post('/shop/purchase', authenticateToken, async (req, res) => {
+ try {
+ const { itemId, quantity = 1 } = req.body;
+
+ if (!itemId) {
+ return res.status(400).json({ error: 'Item ID required' });
+ }
+
+ const gameSystem = getGameSystem();
+ const purchaseInfo = gameSystem.economy.purchaseItem(req.userId, itemId, quantity);
+
+ const player = await Player.findOne({ userId: req.userId });
+
+ if (!player.canAfford(purchaseInfo.totalCost)) {
+ return res.status(400).json({ error: 'Insufficient credits' });
+ }
+
+ // Deduct credits
+ player.spendCredits(purchaseInfo.totalCost);
+ await player.save();
+
+ // Add item to inventory
+ const inventory = await Inventory.findOne({ userId: req.userId });
+ if (!inventory) {
+ return res.status(404).json({ error: 'Inventory not found' });
+ }
+
+ await inventory.addItem({
+ ...purchaseInfo.item,
+ quantity
+ });
+
+ res.json({
+ message: 'Purchase successful',
+ item: purchaseInfo.item,
+ quantity,
+ totalCost: purchaseInfo.totalCost,
+ remainingCredits: player.stats.credits
+ });
+
+ } catch (error) {
+ logger.error('Error purchasing item:', error);
+ res.status(500).json({ error: error.message });
+ }
+});
+
+module.exports = router;
diff --git a/API/routes/mods.js b/API/routes/mods.js
new file mode 100644
index 0000000..d52450b
--- /dev/null
+++ b/API/routes/mods.js
@@ -0,0 +1,232 @@
+const express = require('express');
+const jwt = require('jsonwebtoken');
+const logger = require('../utils/logger');
+const ModService = require('../services/ModService');
+
+const router = express.Router();
+
+// Middleware to authenticate JWT token
+const authenticateToken = (req, res, next) => {
+ const token = req.header('Authorization')?.replace('Bearer ', '');
+
+ if (!token) {
+ return res.status(401).json({ error: 'Access token required' });
+ }
+
+ try {
+ const decoded = jwt.verify(token, process.env.JWT_SECRET || 'fallback_secret');
+ req.userId = decoded.userId;
+ next();
+ } catch (error) {
+ res.status(401).json({ error: 'Invalid token' });
+ }
+};
+
+// Get all mods
+router.get('/', authenticateToken, async (req, res) => {
+ try {
+ const { enabledOnly } = req.query;
+ const mods = await ModService.getMods(enabledOnly === 'true');
+
+ res.json({
+ success: true,
+ mods
+ });
+ } catch (error) {
+ logger.error('[MODS API] Error getting mods:', error);
+ res.status(500).json({ error: 'Internal server error' });
+ }
+});
+
+// Get specific mod
+router.get('/:id', authenticateToken, async (req, res) => {
+ try {
+ const { id } = req.params;
+ const mod = await ModService.getMod(parseInt(id));
+
+ if (!mod) {
+ return res.status(404).json({ error: 'Mod not found' });
+ }
+
+ res.json({
+ success: true,
+ mod
+ });
+ } catch (error) {
+ logger.error('[MODS API] Error getting mod:', error);
+ res.status(500).json({ error: 'Internal server error' });
+ }
+});
+
+// Enable mod
+router.post('/:id/enable', authenticateToken, async (req, res) => {
+ try {
+ const { id } = req.params;
+ const success = await ModService.enableMod(parseInt(id));
+
+ if (!success) {
+ return res.status(404).json({ error: 'Mod not found' });
+ }
+
+ res.json({
+ success: true,
+ message: 'Mod enabled successfully'
+ });
+ } catch (error) {
+ logger.error('[MODS API] Error enabling mod:', error);
+ res.status(500).json({ error: 'Internal server error' });
+ }
+});
+
+// Disable mod
+router.post('/:id/disable', authenticateToken, async (req, res) => {
+ try {
+ const { id } = req.params;
+ const success = await ModService.disableMod(parseInt(id));
+
+ if (!success) {
+ return res.status(404).json({ error: 'Mod not found' });
+ }
+
+ res.json({
+ success: true,
+ message: 'Mod disabled successfully'
+ });
+ } catch (error) {
+ logger.error('[MODS API] Error disabling mod:', error);
+ res.status(500).json({ error: 'Internal server error' });
+ }
+});
+
+// Get mod assets by type
+router.get('/assets/:type', authenticateToken, async (req, res) => {
+ try {
+ const { type } = req.params;
+ const assets = await ModService.getModAssets(type);
+
+ res.json({
+ success: true,
+ assets
+ });
+ } catch (error) {
+ logger.error('[MODS API] Error getting mod assets:', error);
+ res.status(500).json({ error: 'Internal server error' });
+ }
+});
+
+// Get specific mod asset
+router.get('/assets/:type/:assetId', authenticateToken, async (req, res) => {
+ try {
+ const { type, assetId } = req.params;
+ const asset = await ModService.getModAsset(type, assetId);
+
+ if (!asset) {
+ return res.status(404).json({ error: 'Asset not found' });
+ }
+
+ res.json({
+ success: true,
+ asset
+ });
+ } catch (error) {
+ logger.error('[MODS API] Error getting mod asset:', error);
+ res.status(500).json({ error: 'Internal server error' });
+ }
+});
+
+// Get mod load order
+router.get('/load-order', authenticateToken, async (req, res) => {
+ try {
+ const loadOrder = await ModService.getLoadOrder();
+
+ res.json({
+ success: true,
+ loadOrder
+ });
+ } catch (error) {
+ logger.error('[MODS API] Error getting load order:', error);
+ res.status(500).json({ error: 'Internal server error' });
+ }
+});
+
+// Set mod load order
+router.post('/load-order', authenticateToken, async (req, res) => {
+ try {
+ const { modIds } = req.body;
+
+ if (!Array.isArray(modIds)) {
+ return res.status(400).json({ error: 'modIds must be an array' });
+ }
+
+ const success = await ModService.setLoadOrder(modIds);
+
+ if (!success) {
+ return res.status(500).json({ error: 'Failed to set load order' });
+ }
+
+ res.json({
+ success: true,
+ message: 'Load order updated successfully'
+ });
+ } catch (error) {
+ logger.error('[MODS API] Error setting load order:', error);
+ res.status(500).json({ error: 'Internal server error' });
+ }
+});
+
+// Get server mod preferences
+router.get('/preferences/server', authenticateToken, async (req, res) => {
+ try {
+ const preferences = await ModService.getServerPreferences();
+
+ res.json({
+ success: true,
+ preferences
+ });
+ } catch (error) {
+ logger.error('[MODS API] Error getting server preferences:', error);
+ res.status(500).json({ error: 'Internal server error' });
+ }
+});
+
+// Set server mod preference
+router.post('/preferences/server', authenticateToken, async (req, res) => {
+ try {
+ const { key, value } = req.body;
+
+ if (!key || value === undefined) {
+ return res.status(400).json({ error: 'key and value are required' });
+ }
+
+ const success = await ModService.setServerPreference(key, value);
+
+ if (!success) {
+ return res.status(500).json({ error: 'Failed to set preference' });
+ }
+
+ res.json({
+ success: true,
+ message: 'Preference set successfully'
+ });
+ } catch (error) {
+ logger.error('[MODS API] Error setting server preference:', error);
+ res.status(500).json({ error: 'Internal server error' });
+ }
+});
+
+// Reload mods from filesystem
+router.post('/reload', authenticateToken, async (req, res) => {
+ try {
+ await ModService.reloadMods();
+
+ res.json({
+ success: true,
+ message: 'Mods reloaded successfully'
+ });
+ } catch (error) {
+ logger.error('[MODS API] Error reloading mods:', error);
+ res.status(500).json({ error: 'Internal server error' });
+ }
+});
+
+module.exports = router;
diff --git a/API/routes/servers.js b/API/routes/servers.js
new file mode 100644
index 0000000..47de7d5
--- /dev/null
+++ b/API/routes/servers.js
@@ -0,0 +1,419 @@
+const express = require('express');
+const jwt = require('jsonwebtoken');
+const GameServer = require('../models/GameServer');
+const logger = require('../utils/logger');
+
+const router = express.Router();
+
+// Middleware to authenticate JWT token
+const authenticateToken = (req, res, next) => {
+ const token = req.header('Authorization')?.replace('Bearer ', '');
+
+ if (!token) {
+ return res.status(401).json({ error: 'Access token required' });
+ }
+
+ try {
+ const decoded = jwt.verify(token, process.env.JWT_SECRET || 'fallback_secret');
+ req.userId = decoded.userId;
+ next();
+ } catch (error) {
+ res.status(401).json({ error: 'Invalid token' });
+ }
+};
+
+// Register new game server (for GameServer instances to register themselves)
+router.post('/register', async (req, res) => {
+ try {
+ const { serverId, name, type, region, maxPlayers, currentPlayers, gameServerUrl, owner } = req.body;
+
+ logger.info(`[API SERVER] Game server registration request:`, {
+ serverId, name, type, region, maxPlayers, currentPlayers, gameServerUrl, owner
+ });
+
+ // Check if server already exists
+ const existingServer = await GameServer.findOne({ serverId });
+ if (existingServer) {
+ // Update existing server
+ existingServer.name = name || existingServer.name;
+ existingServer.type = type || existingServer.type;
+ existingServer.region = region || existingServer.region;
+ existingServer.maxPlayers = maxPlayers || existingServer.maxPlayers;
+ existingServer.currentPlayers = currentPlayers !== undefined ? currentPlayers : existingServer.currentPlayers;
+ existingServer.gameServerUrl = gameServerUrl || existingServer.gameServerUrl;
+ existingServer.status = 'waiting';
+ existingServer.lastActivity = new Date();
+
+ await existingServer.save();
+ logger.info(`[API SERVER] Updated existing server: ${serverId} with ${existingServer.currentPlayers} players`);
+
+ return res.json({
+ success: true,
+ message: 'Server updated successfully',
+ server: existingServer
+ });
+ }
+
+ // Create new server
+ const newServer = new GameServer({
+ serverId,
+ name: name || `Game Server ${serverId}`,
+ type: type || 'public',
+ region: region || 'us-east',
+ maxPlayers: maxPlayers || 10,
+ currentPlayers: currentPlayers !== undefined ? currentPlayers : 0,
+ owner: owner || {
+ userId: 'system',
+ username: 'System'
+ },
+ status: 'waiting',
+ gameServerUrl,
+ createdAt: new Date(),
+ lastActivity: new Date()
+ });
+
+ await newServer.save();
+ logger.info(`[API SERVER] Registered new server: ${serverId}`);
+
+ res.status(201).json({
+ success: true,
+ message: 'Server registered successfully',
+ server: newServer
+ });
+
+ } catch (error) {
+ logger.error('[API SERVER] Error registering server:', error);
+ res.status(500).json({
+ success: false,
+ error: 'Failed to register server'
+ });
+ }
+});
+
+// Update server status (for GameServer instances to update their status)
+router.post('/update-status/:serverId', async (req, res) => {
+ try {
+ const { serverId } = req.params;
+ const { currentPlayers, status } = req.body;
+
+ const server = await GameServer.findOne({ serverId });
+ if (!server) {
+ return res.status(404).json({
+ success: false,
+ error: 'Server not found'
+ });
+ }
+
+ if (currentPlayers !== undefined) {
+ server.currentPlayers = currentPlayers;
+ }
+
+ if (status) {
+ server.status = status;
+ }
+
+ server.lastActivity = new Date();
+ await server.save();
+
+ logger.info(`[API SERVER] Updated server ${serverId} status:`, {
+ currentPlayers: server.currentPlayers,
+ status: server.status
+ });
+
+ res.json({
+ success: true,
+ message: 'Server status updated successfully'
+ });
+
+ } catch (error) {
+ logger.error('[API SERVER] Error updating server status:', error);
+ res.status(500).json({
+ success: false,
+ error: 'Failed to update server status'
+ });
+ }
+});
+
+// Unregister game server (for GameServer instances to unregister themselves)
+router.delete('/unregister/:serverId', async (req, res) => {
+ try {
+ const { serverId } = req.params;
+
+ logger.info(`[API SERVER] Game server unregistration request:`, { serverId });
+
+ // Find and remove server
+ const server = await GameServer.findOneAndDelete({ serverId });
+
+ if (!server) {
+ return res.status(404).json({
+ success: false,
+ error: 'Server not found'
+ });
+ }
+
+ logger.info(`[API SERVER] Unregistered server: ${serverId}`);
+
+ res.json({
+ success: true,
+ message: 'Server unregistered successfully'
+ });
+
+ } catch (error) {
+ logger.error('[API SERVER] Error unregistering server:', error);
+ res.status(500).json({
+ success: false,
+ error: 'Failed to unregister server'
+ });
+ }
+});
+
+// Get server list
+router.get('/', authenticateToken, async (req, res) => {
+ try {
+ const { type, region } = req.query;
+
+ // Build filters
+ const filters = {};
+ if (type) filters.type = type;
+ if (region) filters.region = region;
+
+ logger.info(`[API SERVER] Fetching servers for user ${req.userId} with filters:`, filters);
+
+ // Get available servers from database
+ const servers = await GameServer.findAvailableServers(filters);
+
+ logger.info(`[API SERVER] Found ${servers.length} servers in database`);
+
+ // Format server list for client
+ const serverList = servers.map(server => ({
+ id: server.serverId,
+ name: server.name,
+ type: server.type,
+ region: server.region,
+ currentPlayers: server.currentPlayers,
+ maxPlayers: server.maxPlayers,
+ status: server.status,
+ ownerName: server.owner.username,
+ createdAt: server.createdAt,
+ lastActivity: server.lastActivity
+ }));
+
+ logger.info(`[API SERVER] Returning ${serverList.length} servers to client`);
+
+ res.json({
+ success: true,
+ servers: serverList,
+ totalServers: serverList.length
+ });
+
+ } catch (error) {
+ logger.error('Error getting server list:', error);
+ res.status(500).json({ error: 'Internal server error' });
+ }
+});
+
+// Create new server
+router.post('/create', authenticateToken, async (req, res) => {
+ try {
+ const { name, type = 'public', maxPlayers = 10, region = 'us-east', settings = {} } = req.body;
+
+ if (!name) {
+ return res.status(400).json({ error: 'Server name required' });
+ }
+
+ // Generate unique server ID
+ const serverId = `server_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
+
+ // Get user info from token (you might want to fetch full user data)
+ const ownerUsername = req.body.username || 'Unknown'; // This should come from user data
+
+ // Create new server in database
+ const newServer = new GameServer({
+ serverId,
+ name,
+ type,
+ region,
+ maxPlayers,
+ owner: {
+ userId: req.userId,
+ username: ownerUsername
+ },
+ settings,
+ gameServerUrl: process.env.GAME_SERVER_URL || 'https://api.korvarix.com'
+ });
+
+ await newServer.save();
+
+ logger.info(`Server created: ${serverId} by user ${req.userId}`);
+
+ res.status(201).json({
+ message: 'Server created successfully',
+ server: {
+ id: newServer.serverId,
+ name: newServer.name,
+ type: newServer.type,
+ region: newServer.region,
+ currentPlayers: newServer.currentPlayers,
+ maxPlayers: newServer.maxPlayers,
+ status: newServer.status,
+ ownerName: newServer.owner.username,
+ createdAt: newServer.createdAt
+ }
+ });
+
+ } catch (error) {
+ logger.error('Error creating server:', error);
+ res.status(500).json({ error: 'Internal server error' });
+ }
+});
+
+// Join server
+router.post('/:serverId/join', authenticateToken, async (req, res) => {
+ try {
+ const { serverId } = req.params;
+
+ // Find server in database
+ const server = await GameServer.findOne({ serverId });
+
+ if (!server) {
+ return res.status(404).json({ error: 'Server not found' });
+ }
+
+ // Check if server can be joined
+ if (!server.canJoin()) {
+ return res.status(400).json({ error: 'Server is full or offline' });
+ }
+
+ // Add player to server
+ const playerAdded = server.addPlayer();
+ if (!playerAdded) {
+ return res.status(400).json({ error: 'Server is full' });
+ }
+
+ await server.save();
+
+ logger.info(`User ${req.userId} joined server ${serverId}`);
+
+ res.json({
+ message: 'Joined server successfully',
+ server: {
+ id: server.serverId,
+ name: server.name,
+ currentPlayers: server.currentPlayers,
+ maxPlayers: server.maxPlayers,
+ gameServerUrl: server.gameServerUrl
+ }
+ });
+
+ } catch (error) {
+ logger.error('Error joining server:', error);
+ res.status(500).json({ error: 'Internal server error' });
+ }
+});
+
+// Leave server
+router.post('/:serverId/leave', authenticateToken, async (req, res) => {
+ try {
+ const { serverId } = req.params;
+
+ // Find server in database
+ const server = await GameServer.findOne({ serverId });
+
+ if (!server) {
+ return res.status(404).json({ error: 'Server not found' });
+ }
+
+ // Remove player from server
+ const playerRemoved = server.removePlayer();
+ if (playerRemoved) {
+ await server.save();
+ logger.info(`User ${req.userId} left server ${serverId}`);
+ }
+
+ // Update player's current server
+ const Player = require('../models/Player');
+ await Player.findOneAndUpdate(
+ { userId: req.userId },
+ { currentServer: null }
+ );
+
+ res.json({
+ message: 'Left server successfully'
+ });
+
+ } catch (error) {
+ logger.error('Error leaving server:', error);
+ res.status(500).json({ error: 'Internal server error' });
+ }
+});
+
+// Get server details
+router.get('/:serverId', authenticateToken, async (req, res) => {
+ try {
+ const { serverId } = req.params;
+
+ const server = await GameServer.findOne({ serverId });
+
+ if (!server) {
+ return res.status(404).json({ error: 'Server not found' });
+ }
+
+ res.json({
+ server: {
+ id: server.serverId,
+ name: server.name,
+ type: server.type,
+ region: server.region,
+ currentPlayers: server.currentPlayers,
+ maxPlayers: server.maxPlayers,
+ status: server.status,
+ ownerName: server.owner.username,
+ settings: server.settings,
+ createdAt: server.createdAt,
+ lastActivity: server.lastActivity
+ }
+ });
+
+ } catch (error) {
+ logger.error('Error getting server details:', error);
+ res.status(500).json({ error: 'Internal server error' });
+ }
+});
+
+// Get user's current server
+router.get('/user/current', authenticateToken, async (req, res) => {
+ try {
+ const Player = require('../models/Player');
+ const player = await Player.findOne({ userId: req.userId });
+
+ if (!player || !player.currentServer) {
+ return res.json({ currentServer: null });
+ }
+
+ const server = await GameServer.findOne({ serverId: player.currentServer });
+
+ if (!server) {
+ // Clear invalid server reference
+ await Player.findOneAndUpdate(
+ { userId: req.userId },
+ { currentServer: null }
+ );
+ return res.json({ currentServer: null });
+ }
+
+ res.json({
+ currentServer: {
+ id: server.serverId,
+ name: server.name,
+ currentPlayers: server.currentPlayers,
+ maxPlayers: server.maxPlayers
+ }
+ });
+
+ } catch (error) {
+ logger.error('Error getting current server:', error);
+ res.status(500).json({ error: 'Internal server error' });
+ }
+});
+
+module.exports = router;
diff --git a/API/scripts/createTestServer.js b/API/scripts/createTestServer.js
new file mode 100644
index 0000000..a60f454
--- /dev/null
+++ b/API/scripts/createTestServer.js
@@ -0,0 +1,67 @@
+/**
+ * Create Test Server Script
+ * Adds a test server to the database for testing the server browser
+ */
+
+const mongoose = require('mongoose');
+const GameServer = require('../models/GameServer');
+require('dotenv').config();
+
+async function createTestServer() {
+ try {
+ // Connect to database
+ await mongoose.connect(process.env.MONGODB_URI);
+ console.log('Connected to database');
+
+ // Check if test server already exists
+ const existingServer = await GameServer.findOne({ serverId: 'test_server_001' });
+ if (existingServer) {
+ console.log('Test server already exists, deleting it first...');
+ await GameServer.deleteOne({ serverId: 'test_server_001' });
+ }
+
+ // Create test server
+ const testServer = new GameServer({
+ serverId: 'test_server_001',
+ name: 'Test Server - Galaxy Strike',
+ type: 'public',
+ region: 'us-east',
+ maxPlayers: 10,
+ currentPlayers: 2,
+ owner: {
+ userId: 'test_user_001',
+ username: 'TestAdmin'
+ },
+ settings: {
+ description: 'A test server for Galaxy Strike Online',
+ tags: ['test', 'beginner', 'pve']
+ },
+ status: 'active',
+ gameServerUrl: 'https://api.korvarix.com'
+ });
+
+ await testServer.save();
+ console.log('Test server created successfully!');
+ console.log('Server details:', {
+ id: testServer.serverId,
+ name: testServer.name,
+ type: testServer.type,
+ region: testServer.region,
+ currentPlayers: testServer.currentPlayers,
+ maxPlayers: testServer.maxPlayers,
+ status: testServer.status
+ });
+
+ } catch (error) {
+ console.error('Error creating test server:', error);
+ } finally {
+ await mongoose.disconnect();
+ }
+}
+
+// Run the script
+if (require.main === module) {
+ createTestServer();
+}
+
+module.exports = createTestServer;
diff --git a/API/scripts/migrate.js b/API/scripts/migrate.js
new file mode 100644
index 0000000..147bbe3
--- /dev/null
+++ b/API/scripts/migrate.js
@@ -0,0 +1,50 @@
+const mongoose = require('mongoose');
+const logger = require('../utils/logger');
+require('dotenv').config();
+
+async function migrate() {
+ try {
+ logger.info('Starting database migration...');
+
+ // Connect to database
+ await mongoose.connect(process.env.MONGODB_URI || 'mongodb://localhost:27017/galaxystrikeonline');
+
+ logger.info('Connected to database');
+
+ // Create indexes for performance
+ const db = mongoose.connection.db;
+
+ // Player indexes
+ await db.collection('players').createIndex({ userId: 1 }, { unique: true });
+ await db.collection('players').createIndex({ email: 1 }, { unique: true });
+ await db.collection('players').createIndex({ 'stats.level': 1 });
+ await db.collection('players').createIndex({ currentServer: 1 });
+
+ // Ship indexes
+ await db.collection('ships').createIndex({ userId: 1 });
+ await db.collection('ships').createIndex({ id: 1 }, { unique: true });
+ await db.collection('ships').createIndex({ isEquipped: 1 });
+ await db.collection('ships').createIndex({ isCurrent: 1 });
+
+ // Inventory indexes
+ await db.collection('inventories').createIndex({ userId: 1 }, { unique: true });
+ await db.collection('inventories').createIndex({ 'items.id': 1 });
+ await db.collection('inventories').createIndex({ 'items.type': 1 });
+
+ logger.info('Database migration completed successfully');
+
+ // Close connection
+ await mongoose.connection.close();
+ logger.info('Database connection closed');
+
+ } catch (error) {
+ logger.error('Migration failed:', error);
+ process.exit(1);
+ }
+}
+
+if (require.main === module) {
+ migrate();
+}
+
+module.exports = migrate;
diff --git a/API/scripts/migratePasswords.js b/API/scripts/migratePasswords.js
new file mode 100644
index 0000000..470af65
--- /dev/null
+++ b/API/scripts/migratePasswords.js
@@ -0,0 +1,71 @@
+/**
+ * Password Migration Script
+ * Updates existing users to have password fields
+ */
+
+const mongoose = require('mongoose');
+const bcrypt = require('bcryptjs');
+const Player = require('../models/Player');
+const logger = require('../utils/logger');
+require('dotenv').config();
+
+async function migratePasswords() {
+ try {
+ // Connect to database
+ await mongoose.connect(process.env.MONGODB_URI);
+ logger.info('Connected to database for password migration');
+
+ // Find all users without passwords
+ const usersWithoutPasswords = await Player.find({
+ password: { $exists: false }
+ });
+
+ logger.info(`Found ${usersWithoutPasswords.length} users without passwords`);
+
+ if (usersWithoutPasswords.length === 0) {
+ logger.info('No users need password migration');
+ return;
+ }
+
+ // Update each user with a default password
+ for (const user of usersWithoutPasswords) {
+ // Generate a default password (you might want to use a different approach)
+ const defaultPassword = 'tempPassword123!';
+ const salt = await bcrypt.genSalt(10);
+ const hashedPassword = await bcrypt.hash(defaultPassword, salt);
+
+ await Player.updateOne(
+ { _id: user._id },
+ {
+ $set: {
+ password: hashedPassword,
+ 'stats.lastLogin': new Date()
+ }
+ }
+ );
+
+ logger.info(`Migrated user: ${user.email} with default password`);
+ }
+
+ logger.info('Password migration completed successfully');
+
+ // Output the default password for users to change
+ console.log('\n=== MIGRATION COMPLETE ===');
+ console.log(`Updated ${usersWithoutPasswords.length} users`);
+ console.log('Default password for all migrated users: tempPassword123!');
+ console.log('Users should change their password after first login\n');
+
+ } catch (error) {
+ logger.error('Password migration error:', error);
+ console.error('Migration failed:', error);
+ } finally {
+ await mongoose.disconnect();
+ }
+}
+
+// Run the migration
+if (require.main === module) {
+ migratePasswords();
+}
+
+module.exports = migratePasswords;
diff --git a/API/scripts/seed.js b/API/scripts/seed.js
new file mode 100644
index 0000000..2825bda
--- /dev/null
+++ b/API/scripts/seed.js
@@ -0,0 +1,196 @@
+const mongoose = require('mongoose');
+const logger = require('../utils/logger');
+const Player = require('../models/Player');
+const Ship = require('../models/Ship');
+const Inventory = require('../models/Inventory');
+require('dotenv').config();
+
+async function seed() {
+ try {
+ logger.info('Starting database seeding...');
+
+ // Connect to database
+ await mongoose.connect(process.env.MONGODB_URI || 'mongodb://localhost:27017/galaxystrikeonline');
+
+ logger.info('Connected to database');
+
+ // Clear existing data (optional - remove if you want to preserve data)
+ logger.info('Clearing existing data...');
+ await Player.deleteMany({});
+ await Ship.deleteMany({});
+ await Inventory.deleteMany({});
+
+ // Create a test user
+ const testUser = new Player({
+ userId: 'test_user_001',
+ username: 'TestPlayer',
+ email: 'test@example.com',
+ password: '$2a$10$example_hashed_password_here',
+ stats: {
+ level: 1,
+ experience: 0,
+ credits: 5000,
+ dungeonsCleared: 0,
+ playTime: 0,
+ lastLogin: new Date()
+ },
+ attributes: {
+ health: 100,
+ maxHealth: 100,
+ energy: 100,
+ maxEnergy: 100,
+ attack: 10,
+ defense: 5,
+ speed: 10,
+ criticalChance: 0.05,
+ criticalDamage: 1.5
+ },
+ info: {
+ name: 'Commander',
+ title: 'Rookie Pilot',
+ guild: null,
+ rank: 'Cadet'
+ },
+ settings: {
+ autoSave: true,
+ notifications: true,
+ soundEffects: true,
+ music: false,
+ discordIntegration: false
+ },
+ dailyRewards: {
+ lastClaim: null,
+ consecutiveDays: 0
+ }
+ });
+
+ await testUser.save();
+ logger.info('Created test user');
+
+ // Create starter ship for test user
+ const starterShip = new Ship({
+ userId: testUser.userId,
+ id: 'starter_cruiser_001',
+ name: 'Starter Cruiser',
+ class: 'Cruiser',
+ level: 1,
+ stats: {
+ health: 100,
+ maxHealth: 100,
+ attack: 15,
+ defense: 12,
+ speed: 10,
+ criticalChance: 0.05,
+ criticalDamage: 1.5,
+ hull: 100
+ },
+ texture: 'assets/textures/ships/starter_cruiser.png',
+ experience: 0,
+ requiredExp: 100,
+ upgrades: [],
+ isEquipped: true,
+ isCurrent: true,
+ price: 5000,
+ rarity: 'common',
+ description: 'Reliable starter cruiser for new pilots',
+ acquiredAt: new Date(),
+ lastUsed: new Date()
+ });
+
+ await starterShip.save();
+ logger.info('Created starter ship');
+
+ // Update player with current ship
+ testUser.currentShip = starterShip._id;
+ await testUser.save();
+
+ // Create inventory for test user
+ const inventory = new Inventory({
+ userId: testUser.userId,
+ maxSlots: 50,
+ items: [
+ {
+ id: 'starter_blaster_common',
+ name: 'Common Blaster',
+ type: 'weapon',
+ rarity: 'common',
+ quantity: 1,
+ stats: {
+ attack: 5,
+ criticalChance: 0.02,
+ damage: 10,
+ fireRate: 2,
+ range: 5,
+ energy: 5
+ },
+ description: 'A reliable basic blaster for new pilots',
+ equipable: true,
+ slot: 'weapon',
+ isEquipped: false,
+ stackable: false,
+ acquiredAt: new Date()
+ },
+ {
+ id: 'basic_armor_common',
+ name: 'Basic Armor',
+ type: 'armor',
+ rarity: 'common',
+ quantity: 1,
+ stats: {
+ defense: 3,
+ durability: 20,
+ weight: 2,
+ energyShield: 0
+ },
+ description: 'Light armor providing basic protection',
+ equipable: true,
+ slot: 'armor',
+ isEquipped: false,
+ stackable: false,
+ acquiredAt: new Date()
+ },
+ {
+ id: 'health_kit',
+ name: 'Health Kit',
+ type: 'consumable',
+ rarity: 'common',
+ quantity: 3,
+ stats: {},
+ description: 'A medical kit that restores health',
+ consumable: true,
+ effect: {
+ health: 50
+ },
+ stackable: true,
+ acquiredAt: new Date()
+ }
+ ],
+ equippedItems: {
+ weapon: null,
+ armor: null,
+ engine: null,
+ shield: null,
+ special: null
+ }
+ });
+
+ await inventory.save();
+ logger.info('Created inventory with starter items');
+
+ logger.info('Database seeding completed successfully');
+
+ // Close connection
+ await mongoose.connection.close();
+ logger.info('Database connection closed');
+
+ } catch (error) {
+ logger.error('Seeding failed:', error);
+ process.exit(1);
+ }
+}
+
+if (require.main === module) {
+ seed();
+}
+
+module.exports = seed;
diff --git a/API/server.js b/API/server.js
new file mode 100644
index 0000000..121e8b3
--- /dev/null
+++ b/API/server.js
@@ -0,0 +1,154 @@
+const express = require('express');
+const http = require('http');
+const cors = require('cors');
+const helmet = require('helmet');
+const compression = require('compression');
+const rateLimit = require('rate-limiter-flexible');
+require('dotenv').config();
+
+const logger = require('./utils/logger');
+const connectDB = require('./config/database');
+const authRoutes = require('./routes/auth');
+const serverRoutes = require('./routes/servers');
+const modsRoutes = require('./routes/mods');
+const { errorHandler, notFound } = require('./middleware/errorHandler');
+
+// Override console.error to properly log error objects
+const originalConsoleError = console.error;
+console.error = (...args) => {
+ args.forEach(arg => {
+ if (arg instanceof Error) {
+ logger.error('Console Error:', {
+ message: arg.message,
+ stack: arg.stack,
+ name: arg.name
+ });
+ } else if (typeof arg === 'object' && arg !== null) {
+ logger.error('Console Error Object:', arg);
+ } else {
+ logger.error('Console Error:', arg);
+ }
+ });
+};
+
+const app = express();
+const server = http.createServer(app);
+
+// Middleware
+app.use(helmet());
+app.use(compression());
+app.use(cors({
+ origin: ["https://galaxystrike.online", "https://api.korvarix.com", "http://api.korvarix.com:3001"],
+ credentials: true
+}));
+app.use(express.json({ limit: '10mb' }));
+app.use(express.urlencoded({ extended: true }));
+
+// Static file serving
+app.use(express.static('../Website/dist'));
+
+// Rate limiting
+const { RateLimiterMemory } = require('rate-limiter-flexible');
+const limiter = new RateLimiterMemory({
+ keyGenerator: (req) => req.ip,
+ points: 100, // limit each IP to 100 requests per windowMs
+ duration: 900, // 15 minutes
+ blockDuration: 900, // Block for 15 minutes
+});
+app.use('/api/', async (req, res, next) => {
+ try {
+ const resLimiter = await limiter.consume(req.ip);
+ if (!resLimiter.remainingPoints) {
+ return res.status(429).json({ error: 'Too many requests, please try again later.' });
+ }
+ next();
+ } catch (error) {
+ next();
+ }
+});
+
+// Routes - API Server Only (Auth + Server Browser + Mods)
+app.use('/api/auth', authRoutes);
+app.use('/api/servers', serverRoutes);
+app.use('/api/mods', modsRoutes);
+
+// Health check
+app.get('/health', (req, res) => {
+ res.status(200).json({
+ status: 'API Server OK',
+ service: 'galaxystrikeonline-api',
+ timestamp: new Date().toISOString(),
+ uptime: process.uptime()
+ });
+});
+
+// API version endpoint
+app.get('/api/ssc/version', (req, res) => {
+ res.status(200).json({
+ version: '1.0.0',
+ service: 'galaxystrikeonline-api',
+ timestamp: new Date().toISOString()
+ });
+});
+
+// Fallback route for SPA - only serve index.html for non-API routes
+app.get('*', (req, res) => {
+ // Don't try to serve index.html for API routes
+ if (req.path.startsWith('/api/')) {
+ return res.status(404).json({ error: 'API endpoint not found' });
+ }
+
+ // Try dist first (for built files), fallback to public (for development)
+ const distPath = require('path').resolve(__dirname, '../dist/index.html');
+ const publicPath = require('path').resolve(__dirname, '../public/index.html');
+
+ const fs = require('fs');
+ if (fs.existsSync(distPath)) {
+ res.sendFile(distPath);
+ } else if (fs.existsSync(publicPath)) {
+ res.sendFile(publicPath);
+ } else {
+ res.status(404).json({ error: 'Frontend not found' });
+ }
+});
+
+// Error handling
+app.use(notFound);
+app.use(errorHandler);
+
+// Initialize database only (no game systems for API server)
+async function startServer() {
+ try {
+ // Connect to database
+ await connectDB();
+ logger.info('Database connected successfully');
+
+ // Start API server
+ const PORT = process.env.PORT || 3001;
+ server.listen(PORT, () => {
+ logger.info(`API Server running on port ${PORT}`);
+ logger.info('API Server handles: Authentication, Server Browser, User Data');
+ });
+ } catch (error) {
+ logger.error('Failed to start API server:', error);
+ process.exit(1);
+ }
+}
+
+// Handle uncaught errors
+process.on('uncaughtException', (error) => {
+ logger.error('Uncaught Exception:', error);
+});
+
+process.on('unhandledRejection', (reason, promise) => {
+ logger.error('Unhandled Rejection at:', promise, 'reason:', reason);
+});
+
+// Handle HTTP server errors
+server.on('error', (error) => {
+ logger.error('HTTP Server error:', error);
+});
+
+startServer();
+
+module.exports = { app, server };
diff --git a/API/services/ModService.js b/API/services/ModService.js
new file mode 100644
index 0000000..390b53c
--- /dev/null
+++ b/API/services/ModService.js
@@ -0,0 +1,182 @@
+const logger = require('../utils/logger');
+
+/**
+ * API Mod Service - Handles mod management through API communication with GameServer
+ * This service acts as a client to the GameServer's mod functionality
+ */
+class ApiModService {
+ constructor() {
+ // Use service discovery - try multiple common GameServer locations
+ this.gameServerUrls = [
+ process.env.GAME_SERVER_URL,
+ 'http://localhost:3002',
+ 'http://127.0.0.1:3002',
+ 'http://game-server:3002' // Docker service name
+ ].filter(url => url); // Remove null/undefined values
+
+ this.currentUrlIndex = 0;
+ }
+
+ async getGameServerUrl() {
+ // Try each URL until we find a working one
+ for (let i = 0; i < this.gameServerUrls.length; i++) {
+ const url = this.gameServerUrls[i];
+ try {
+ const response = await fetch(`${url}/health`, {
+ timeout: 5000 // 5 second timeout
+ });
+ if (response.ok) {
+ this.currentUrlIndex = i;
+ return url;
+ }
+ } catch (error) {
+ logger.debug(`[API MOD SERVICE] GameServer at ${url} not available:`, error.message);
+ }
+ }
+
+ throw new Error('No GameServer instances available');
+ }
+
+ async makeRequest(endpoint, options = {}) {
+ const url = await this.getGameServerUrl();
+ const fullUrl = `${url}${endpoint}`;
+
+ const defaultOptions = {
+ timeout: 10000, // 10 second timeout
+ headers: {
+ 'Content-Type': 'application/json',
+ 'User-Agent': 'API-ModService/1.0'
+ }
+ };
+
+ const finalOptions = { ...defaultOptions, ...options };
+
+ try {
+ const response = await fetch(fullUrl, finalOptions);
+ if (!response.ok) {
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
+ }
+ return await response.json();
+ } catch (error) {
+ logger.error(`[API MOD SERVICE] Request to ${fullUrl} failed:`, error);
+
+ // If request failed, try next GameServer URL
+ this.currentUrlIndex = (this.currentUrlIndex + 1) % this.gameServerUrls.length;
+ throw error;
+ }
+ }
+
+ async getMods(enabledOnly = false) {
+ try {
+ const endpoint = `/api/mods${enabledOnly ? '?enabledOnly=true' : ''}`;
+ return await this.makeRequest(endpoint);
+ } catch (error) {
+ logger.error('[API MOD SERVICE] Error getting mods:', error);
+ throw error;
+ }
+ }
+
+ async getMod(id) {
+ try {
+ return await this.makeRequest(`/api/mods/${id}`);
+ } catch (error) {
+ logger.error('[API MOD SERVICE] Error getting mod:', error);
+ throw error;
+ }
+ }
+
+ async enableMod(id) {
+ try {
+ return await this.makeRequest(`/api/mods/${id}/enable`, {
+ method: 'POST'
+ });
+ } catch (error) {
+ logger.error('[API MOD SERVICE] Error enabling mod:', error);
+ throw error;
+ }
+ }
+
+ async disableMod(id) {
+ try {
+ return await this.makeRequest(`/api/mods/${id}/disable`, {
+ method: 'POST'
+ });
+ } catch (error) {
+ logger.error('[API MOD SERVICE] Error disabling mod:', error);
+ throw error;
+ }
+ }
+
+ async getModAssets(assetType) {
+ try {
+ return await this.makeRequest(`/api/mods/assets/${assetType}`);
+ } catch (error) {
+ logger.error('[API MOD SERVICE] Error getting mod assets:', error);
+ throw error;
+ }
+ }
+
+ async getModAsset(assetType, assetId) {
+ try {
+ return await this.makeRequest(`/api/mods/assets/${assetType}/${assetId}`);
+ } catch (error) {
+ logger.error('[API MOD SERVICE] Error getting mod asset:', error);
+ throw error;
+ }
+ }
+
+ async getLoadOrder() {
+ try {
+ return await this.makeRequest('/api/mods/load-order');
+ } catch (error) {
+ logger.error('[API MOD SERVICE] Error getting load order:', error);
+ throw error;
+ }
+ }
+
+ async setLoadOrder(modIds) {
+ try {
+ return await this.makeRequest('/api/mods/load-order', {
+ method: 'POST',
+ body: JSON.stringify({ modIds })
+ });
+ } catch (error) {
+ logger.error('[API MOD SERVICE] Error setting load order:', error);
+ throw error;
+ }
+ }
+
+ async getServerPreferences() {
+ try {
+ return await this.makeRequest('/api/mods/preferences/server');
+ } catch (error) {
+ logger.error('[API MOD SERVICE] Error getting server preferences:', error);
+ throw error;
+ }
+ }
+
+ async setServerPreference(key, value) {
+ try {
+ return await this.makeRequest('/api/mods/preferences/server', {
+ method: 'POST',
+ body: JSON.stringify({ key, value })
+ });
+ } catch (error) {
+ logger.error('[API MOD SERVICE] Error setting server preference:', error);
+ throw error;
+ }
+ }
+
+ async reloadMods() {
+ try {
+ return await this.makeRequest('/api/mods/reload', {
+ method: 'POST'
+ });
+ } catch (error) {
+ logger.error('[API MOD SERVICE] Error reloading mods:', error);
+ throw error;
+ }
+ }
+}
+
+module.exports = new ApiModService();
diff --git a/API/socket/socketHandlers.js b/API/socket/socketHandlers.js
new file mode 100644
index 0000000..0e046bf
--- /dev/null
+++ b/API/socket/socketHandlers.js
@@ -0,0 +1,272 @@
+const logger = require('../utils/logger');
+const { getGameSystem } = require('../systems/GameSystem');
+const Player = require('../models/Player');
+
+class SocketHandlers {
+ constructor(io) {
+ this.io = io;
+ this.connectedUsers = new Map(); // userId -> socket.id
+ this.userSockets = new Map(); // socket.id -> userId
+ }
+
+ handleConnection(socket) {
+ logger.info(`Client connected: ${socket.id}`);
+
+ // Authentication
+ socket.on('authenticate', async (token) => {
+ try {
+ const jwt = require('jsonwebtoken');
+ const decoded = jwt.verify(token, process.env.JWT_SECRET || 'fallback_secret');
+
+ const player = await Player.findOne({ userId: decoded.userId });
+ if (!player) {
+ socket.emit('auth_error', { error: 'Player not found' });
+ return;
+ }
+
+ // Store user connection
+ this.connectedUsers.set(decoded.userId, socket.id);
+ this.userSockets.set(socket.id, decoded.userId);
+
+ socket.userId = decoded.userId;
+ socket.emit('authenticated', { userId: decoded.userId });
+
+ logger.info(`User authenticated: ${decoded.userId}`);
+
+ // Join user to their current server if any
+ if (player.currentServer) {
+ socket.join(player.currentServer);
+ this.broadcastToServer(player.currentServer, 'user_joined', {
+ userId: decoded.userId,
+ username: player.username
+ });
+ }
+
+ } catch (error) {
+ logger.error('Authentication error:', error);
+ socket.emit('auth_error', { error: 'Invalid token' });
+ }
+ });
+
+ // Server management
+ socket.on('join_server', async (data) => {
+ try {
+ if (!socket.userId) {
+ socket.emit('error', { error: 'Not authenticated' });
+ return;
+ }
+
+ const gameSystem = getGameSystem();
+ const server = await gameSystem.joinServer(data.serverId, socket.userId);
+
+ // Update player's current server
+ await Player.findOneAndUpdate(
+ { userId: socket.userId },
+ { currentServer: data.serverId }
+ );
+
+ // Join socket room
+ socket.join(data.serverId);
+
+ socket.emit('server_joined', { server });
+ this.broadcastToServer(data.serverId, 'user_joined', {
+ userId: socket.userId,
+ serverId: data.serverId
+ });
+
+ logger.info(`User ${socket.userId} joined server ${data.serverId}`);
+
+ } catch (error) {
+ logger.error('Error joining server:', error);
+ socket.emit('error', { error: error.message });
+ }
+ });
+
+ socket.on('leave_server', async (data) => {
+ try {
+ if (!socket.userId) {
+ socket.emit('error', { error: 'Not authenticated' });
+ return;
+ }
+
+ const gameSystem = getGameSystem();
+ const server = await gameSystem.leaveServer(data.serverId, socket.userId);
+
+ // Update player's current server
+ await Player.findOneAndUpdate(
+ { userId: socket.userId },
+ { currentServer: null }
+ );
+
+ // Leave socket room
+ socket.leave(data.serverId);
+
+ socket.emit('server_left', { server });
+ this.broadcastToServer(data.serverId, 'user_left', {
+ userId: socket.userId,
+ serverId: data.serverId
+ });
+
+ logger.info(`User ${socket.userId} left server ${data.serverId}`);
+
+ } catch (error) {
+ logger.error('Error leaving server:', error);
+ socket.emit('error', { error: error.message });
+ }
+ });
+
+ // Game actions
+ socket.on('game_action', async (data) => {
+ try {
+ if (!socket.userId) {
+ socket.emit('error', { error: 'Not authenticated' });
+ return;
+ }
+
+ const gameSystem = getGameSystem();
+ const result = await gameSystem.processGameAction(socket.userId, data);
+
+ socket.emit('action_result', { action: data.type, result });
+
+ // Broadcast relevant actions to server
+ if (data.broadcast && socket.userId) {
+ const player = await Player.findOne({ userId: socket.userId });
+ if (player && player.currentServer) {
+ this.broadcastToServer(player.currentServer, 'user_action', {
+ userId: socket.userId,
+ username: player.username,
+ action: data.type,
+ result
+ });
+ }
+ }
+
+ } catch (error) {
+ logger.error('Error processing game action:', error);
+ socket.emit('error', { error: error.message });
+ }
+ });
+
+ // Chat functionality
+ socket.on('send_message', async (data) => {
+ try {
+ if (!socket.userId) {
+ socket.emit('error', { error: 'Not authenticated' });
+ return;
+ }
+
+ const player = await Player.findOne({ userId: socket.userId });
+ if (!player || !player.currentServer) {
+ socket.emit('error', { error: 'Not in a server' });
+ return;
+ }
+
+ const message = {
+ userId: socket.userId,
+ username: player.username,
+ message: data.message,
+ timestamp: new Date(),
+ type: data.type || 'chat'
+ };
+
+ // Broadcast to server
+ this.broadcastToServer(player.currentServer, 'new_message', message);
+
+ logger.info(`Chat message from ${socket.userId} in server ${player.currentServer}`);
+
+ } catch (error) {
+ logger.error('Error sending message:', error);
+ socket.emit('error', { error: error.message });
+ }
+ });
+
+ // Real-time updates
+ socket.on('request_server_status', async () => {
+ try {
+ if (!socket.userId) {
+ socket.emit('error', { error: 'Not authenticated' });
+ return;
+ }
+
+ const player = await Player.findOne({ userId: socket.userId });
+ if (!player || !player.currentServer) {
+ socket.emit('server_status', { server: null });
+ return;
+ }
+
+ const gameSystem = getGameSystem();
+ const server = gameSystem.servers.get(player.currentServer);
+
+ if (server) {
+ const players = await Player.find({
+ userId: { $in: server.players }
+ }).select('userId username info.stats.level');
+
+ socket.emit('server_status', {
+ server: {
+ id: server.id,
+ name: server.name,
+ currentPlayers: server.players.length,
+ maxPlayers: server.maxPlayers,
+ players: players.map(p => ({
+ userId: p.userId,
+ username: p.username,
+ level: p.info.stats.level
+ }))
+ }
+ });
+ }
+
+ } catch (error) {
+ logger.error('Error getting server status:', error);
+ socket.emit('error', { error: error.message });
+ }
+ });
+
+ // Disconnection
+ socket.on('disconnect', async () => {
+ logger.info(`Client disconnected: ${socket.id}`);
+
+ const userId = this.userSockets.get(socket.id);
+ if (userId) {
+ // Remove from tracking
+ this.connectedUsers.delete(userId);
+ this.userSockets.delete(socket.id);
+
+ // Notify server if user was in one
+ const player = await Player.findOne({ userId });
+ if (player && player.currentServer) {
+ this.broadcastToServer(player.currentServer, 'user_disconnected', {
+ userId,
+ username: player.username
+ });
+ }
+ }
+ });
+ }
+
+ broadcastToServer(serverId, event, data) {
+ this.io.to(serverId).emit(event, data);
+ }
+
+ sendToUser(userId, event, data) {
+ const socketId = this.connectedUsers.get(userId);
+ if (socketId) {
+ this.io.to(socketId).emit(event, data);
+ }
+ }
+
+ broadcastToAll(event, data) {
+ this.io.emit(event, data);
+ }
+
+ getConnectedUsers() {
+ return Array.from(this.connectedUsers.keys());
+ }
+
+ getUserCount() {
+ return this.connectedUsers.size;
+ }
+}
+
+module.exports = SocketHandlers;
diff --git a/API/systems/EconomySystem.js b/API/systems/EconomySystem.js
new file mode 100644
index 0000000..5815d25
--- /dev/null
+++ b/API/systems/EconomySystem.js
@@ -0,0 +1,385 @@
+const logger = require('../utils/logger');
+
+class EconomySystem {
+ constructor() {
+ this.shopItems = {
+ ships: [],
+ weapons: [],
+ armors: [],
+ materials: [],
+ consumables: []
+ };
+
+ this.dailyRewards = {
+ baseReward: 100,
+ consecutiveBonus: 50,
+ maxConsecutiveDays: 30
+ };
+ }
+
+ async initialize() {
+ logger.info('Initializing Economy System...');
+
+ // Initialize shop items
+ await this.initializeShopItems();
+
+ logger.info('Economy System initialized successfully');
+ }
+
+ async initializeShopItems() {
+ // Ships
+ this.shopItems.ships = [
+ // Starter Cruiser Variants
+ {
+ id: 'starter_cruiser_common',
+ name: 'Starter Cruiser',
+ type: 'ship',
+ rarity: 'common',
+ price: 5000,
+ currency: 'credits',
+ description: 'Reliable starter cruiser for new pilots',
+ texture: 'assets/textures/ships/starter_cruiser.png',
+ stats: { attack: 15, speed: 10, defense: 12, hull: 100 }
+ },
+ {
+ id: 'starter_cruiser_uncommon',
+ name: 'Starter Cruiser II',
+ type: 'ship',
+ rarity: 'uncommon',
+ price: 12000,
+ currency: 'credits',
+ description: 'Upgraded starter cruiser with enhanced systems',
+ texture: 'assets/textures/ships/starter_cruiser.png',
+ stats: { attack: 18, speed: 12, defense: 15, hull: 120 }
+ },
+ {
+ id: 'starter_cruiser_rare',
+ name: 'Starter Cruiser III',
+ type: 'ship',
+ rarity: 'rare',
+ price: 25000,
+ currency: 'credits',
+ description: 'Elite starter cruiser with advanced weaponry',
+ texture: 'assets/textures/ships/starter_cruiser.png',
+ stats: { attack: 22, speed: 14, defense: 18, hull: 140 }
+ },
+ {
+ id: 'starter_cruiser_epic',
+ name: 'Starter Cruiser IV',
+ type: 'ship',
+ rarity: 'epic',
+ price: 50000,
+ currency: 'credits',
+ description: 'Master starter cruiser with elite modifications',
+ texture: 'assets/textures/ships/starter_cruiser.png',
+ stats: { attack: 28, speed: 16, defense: 22, hull: 160 }
+ },
+ {
+ id: 'starter_cruiser_legendary',
+ name: 'Starter Cruiser V',
+ type: 'ship',
+ rarity: 'legendary',
+ price: 100000,
+ currency: 'credits',
+ description: 'Legendary starter cruiser with unparalleled performance',
+ texture: 'assets/textures/ships/starter_cruiser.png',
+ stats: { attack: 35, speed: 18, defense: 28, hull: 180 }
+ }
+ ];
+
+ // Weapons
+ this.shopItems.weapons = [
+ // Starter Blaster Variants
+ {
+ id: 'starter_blaster_common',
+ name: 'Common Blaster',
+ type: 'weapon',
+ rarity: 'common',
+ price: 1000,
+ currency: 'credits',
+ description: 'Basic blaster for new pilots',
+ texture: 'assets/textures/weapons/starter_blaster.png',
+ stats: { damage: 10, fireRate: 2, range: 5, energy: 5 }
+ },
+ {
+ id: 'starter_blaster_uncommon',
+ name: 'Starter Blaster II',
+ type: 'weapon',
+ rarity: 'uncommon',
+ price: 2500,
+ currency: 'credits',
+ description: 'Improved blaster with better damage output',
+ texture: 'assets/textures/weapons/starter_blaster.png',
+ stats: { damage: 12, fireRate: 2.2, range: 5.5, energy: 6 }
+ },
+ {
+ id: 'starter_blaster_rare',
+ name: 'Starter Blaster III',
+ type: 'weapon',
+ rarity: 'rare',
+ price: 5000,
+ currency: 'credits',
+ description: 'Advanced blaster with enhanced capabilities',
+ texture: 'assets/textures/weapons/starter_blaster.png',
+ stats: { damage: 15, fireRate: 2.5, range: 6, energy: 7 }
+ },
+ {
+ id: 'starter_blaster_epic',
+ name: 'Starter Blaster IV',
+ type: 'weapon',
+ rarity: 'epic',
+ price: 10000,
+ currency: 'credits',
+ description: 'Elite blaster with superior performance',
+ texture: 'assets/textures/weapons/starter_blaster.png',
+ stats: { damage: 18, fireRate: 3, range: 6.5, energy: 8 }
+ },
+ {
+ id: 'starter_blaster_legendary',
+ name: 'Starter Blaster V',
+ type: 'weapon',
+ rarity: 'legendary',
+ price: 20000,
+ currency: 'credits',
+ description: 'Legendary starter blaster with ultimate power',
+ texture: 'assets/textures/weapons/starter_blaster.png',
+ stats: { damage: 22, fireRate: 4, range: 7, energy: 10 }
+ }
+ ];
+
+ // Armors
+ this.shopItems.armors = [
+ // Basic Armor Variants
+ {
+ id: 'basic_armor_common',
+ name: 'Basic Armor',
+ type: 'armor',
+ rarity: 'common',
+ price: 1500,
+ currency: 'credits',
+ description: 'Light protection for beginners',
+ texture: 'assets/textures/armors/basic_armor.png',
+ stats: { defense: 5, durability: 20, weight: 2, energyShield: 0 }
+ },
+ {
+ id: 'basic_armor_uncommon',
+ name: 'Basic Armor II',
+ type: 'armor',
+ rarity: 'uncommon',
+ price: 4000,
+ currency: 'credits',
+ description: 'Improved basic armor with better durability',
+ texture: 'assets/textures/armors/basic_armor.png',
+ stats: { defense: 7, durability: 25, weight: 2.2, energyShield: 2 }
+ },
+ {
+ id: 'basic_armor_rare',
+ name: 'Basic Armor III',
+ type: 'armor',
+ rarity: 'rare',
+ price: 8000,
+ currency: 'credits',
+ description: 'Enhanced armor with energy shielding',
+ texture: 'assets/textures/armors/basic_armor.png',
+ stats: { defense: 10, durability: 30, weight: 2.5, energyShield: 5 }
+ },
+ {
+ id: 'basic_armor_epic',
+ name: 'Basic Armor IV',
+ type: 'armor',
+ rarity: 'epic',
+ price: 15000,
+ currency: 'credits',
+ description: 'Elite armor with advanced protection systems',
+ texture: 'assets/textures/armors/basic_armor.png',
+ stats: { defense: 15, durability: 35, weight: 3, energyShield: 10 }
+ },
+ {
+ id: 'basic_armor_legendary',
+ name: 'Basic Armor V',
+ type: 'armor',
+ rarity: 'legendary',
+ price: 30000,
+ currency: 'credits',
+ description: 'Legendary armor with ultimate protection',
+ texture: 'assets/textures/armors/basic_armor.png',
+ stats: { defense: 20, durability: 40, weight: 3.5, energyShield: 15 }
+ }
+ ];
+
+ // Materials
+ this.shopItems.materials = [
+ {
+ id: 'iron_ore',
+ name: 'Iron Ore',
+ type: 'material',
+ rarity: 'common',
+ price: 50,
+ currency: 'credits',
+ description: 'Raw iron ore used for crafting basic weapons and armor',
+ stackable: true
+ },
+ {
+ id: 'copper_wire',
+ name: 'Copper Wire',
+ type: 'material',
+ rarity: 'common',
+ price: 75,
+ currency: 'credits',
+ description: 'Copper wiring used in electronic components',
+ stackable: true
+ },
+ {
+ id: 'energy_crystal',
+ name: 'Energy Crystal',
+ type: 'material',
+ rarity: 'uncommon',
+ price: 200,
+ currency: 'credits',
+ description: 'Crystallized energy used for powered equipment',
+ stackable: true
+ },
+ {
+ id: 'rare_metal',
+ name: 'Rare Metal',
+ type: 'material',
+ rarity: 'rare',
+ price: 500,
+ currency: 'credits',
+ description: 'Rare metallic alloy used for high-end crafting',
+ stackable: true
+ },
+ {
+ id: 'advanced_components',
+ name: 'Advanced Components',
+ type: 'material',
+ rarity: 'rare',
+ price: 1000,
+ currency: 'credits',
+ description: 'Sophisticated electronic components for advanced ship systems',
+ stackable: true
+ }
+ ];
+
+ // Consumables
+ this.shopItems.consumables = [
+ {
+ id: 'health_kit',
+ name: 'Health Kit',
+ type: 'consumable',
+ rarity: 'common',
+ price: 100,
+ currency: 'credits',
+ description: 'A medical kit that restores health',
+ consumable: true,
+ effect: { health: 50 }
+ },
+ {
+ id: 'energy_pack',
+ name: 'Energy Pack',
+ type: 'consumable',
+ rarity: 'common',
+ price: 150,
+ currency: 'credits',
+ description: 'A pack that restores energy',
+ consumable: true,
+ effect: { energy: 25 }
+ },
+ {
+ id: 'repair_kit',
+ name: 'Repair Kit',
+ type: 'consumable',
+ rarity: 'uncommon',
+ price: 300,
+ currency: 'credits',
+ description: 'A kit that repairs ship damage',
+ consumable: true,
+ effect: { health: 100 }
+ }
+ ];
+
+ logger.info(`Shop initialized with ${this.getTotalShopItems()} items`);
+ }
+
+ getTotalShopItems() {
+ return Object.values(this.shopItems).reduce((total, category) => total + category.length, 0);
+ }
+
+ getShopItems(category = null) {
+ if (category && this.shopItems[category]) {
+ return this.shopItems[category];
+ }
+ return this.shopItems;
+ }
+
+ getItem(itemId) {
+ for (const category of Object.values(this.shopItems)) {
+ const item = category.find(item => item.id === itemId);
+ if (item) return item;
+ }
+ return null;
+ }
+
+ purchaseItem(userId, itemId, quantity = 1) {
+ const item = this.getItem(itemId);
+ if (!item) {
+ throw new Error('Item not found in shop');
+ }
+
+ const totalCost = item.price * quantity;
+
+ return {
+ item,
+ quantity,
+ totalCost,
+ currency: item.currency
+ };
+ }
+
+ calculateDailyReward(consecutiveDays) {
+ const bonusMultiplier = Math.min(consecutiveDays - 1, this.dailyRewards.maxConsecutiveDays - 1);
+ const bonusAmount = bonusMultiplier * this.dailyRewards.consecutiveBonus;
+ const totalReward = this.dailyRewards.baseReward + bonusAmount;
+
+ return {
+ baseReward: this.dailyRewards.baseReward,
+ consecutiveBonus: bonusAmount,
+ totalReward,
+ consecutiveDays
+ };
+ }
+
+ getRandomShopItems(category, count = 6) {
+ const items = this.shopItems[category] || [];
+ const shuffled = [...items].sort(() => Math.random() - 0.5);
+ return shuffled.slice(0, Math.min(count, items.length));
+ }
+
+ refreshShopInventory() {
+ logger.info('Refreshing shop inventory...');
+ // This would typically involve database operations
+ // For now, we'll just log the refresh
+ return true;
+ }
+
+ getShopStats() {
+ const stats = {
+ totalItems: this.getTotalShopItems(),
+ itemsByCategory: {},
+ averagePriceByCategory: {}
+ };
+
+ for (const [category, items] of Object.entries(this.shopItems)) {
+ stats.itemsByCategory[category] = items.length;
+
+ if (items.length > 0) {
+ const totalPrice = items.reduce((sum, item) => sum + item.price, 0);
+ stats.averagePriceByCategory[category] = Math.round(totalPrice / items.length);
+ }
+ }
+
+ return stats;
+ }
+}
+
+module.exports = EconomySystem;
diff --git a/API/systems/GameSystem.js b/API/systems/GameSystem.js
new file mode 100644
index 0000000..0279cc2
--- /dev/null
+++ b/API/systems/GameSystem.js
@@ -0,0 +1,293 @@
+const logger = require('../utils/logger');
+const Player = require('../models/Player');
+const Ship = require('../models/Ship');
+const Inventory = require('../models/Inventory');
+const Economy = require('./EconomySystem');
+
+class GameSystem {
+ constructor() {
+ this.players = new Map();
+ this.servers = new Map();
+ this.economy = new Economy();
+ }
+
+ async initializeGameSystems() {
+ logger.info('Initializing server-side game systems...');
+
+ // Initialize economy system
+ await this.economy.initialize();
+
+ logger.info('Game systems initialized successfully');
+ }
+
+ // Player management
+ async createPlayer(userId, playerData) {
+ try {
+ const player = new Player({
+ userId,
+ ...playerData,
+ createdAt: new Date(),
+ lastLogin: new Date()
+ });
+
+ await player.save();
+ this.players.set(userId, player);
+
+ logger.info(`Created new player for user: ${userId}`);
+ return player;
+ } catch (error) {
+ logger.error('Error creating player:', error);
+ throw error;
+ }
+ }
+
+ async loadPlayer(userId) {
+ try {
+ let player = this.players.get(userId);
+
+ if (!player) {
+ player = await Player.findOne({ userId }).populate('ships inventory');
+ if (player) {
+ this.players.set(userId, player);
+ }
+ }
+
+ return player;
+ } catch (error) {
+ logger.error('Error loading player:', error);
+ throw error;
+ }
+ }
+
+ async savePlayer(userId) {
+ try {
+ const player = this.players.get(userId);
+ if (player) {
+ await player.save();
+ logger.info(`Saved player data for user: ${userId}`);
+ }
+ } catch (error) {
+ logger.error('Error saving player:', error);
+ throw error;
+ }
+ }
+
+ // Ship management
+ async addShipToPlayer(userId, shipData) {
+ try {
+ const player = await this.loadPlayer(userId);
+ if (!player) {
+ throw new Error('Player not found');
+ }
+
+ const ship = new Ship({
+ ...shipData,
+ userId,
+ acquiredAt: new Date()
+ });
+
+ await ship.save();
+ player.ships.push(ship._id);
+ await player.save();
+
+ logger.info(`Added ship ${ship.name} to player ${userId}`);
+ return ship;
+ } catch (error) {
+ logger.error('Error adding ship to player:', error);
+ throw error;
+ }
+ }
+
+ async equipShip(userId, shipId) {
+ try {
+ const player = await this.loadPlayer(userId);
+ if (!player) {
+ throw new Error('Player not found');
+ }
+
+ const ship = await Ship.findOne({ _id: shipId, userId });
+ if (!ship) {
+ throw new Error('Ship not found');
+ }
+
+ // Unequip current ship
+ if (player.currentShip) {
+ await Ship.findByIdAndUpdate(player.currentShip, { isEquipped: false });
+ }
+
+ // Equip new ship
+ ship.isEquipped = true;
+ await ship.save();
+
+ player.currentShip = ship._id;
+ await player.save();
+
+ logger.info(`Equipped ship ${ship.name} for player ${userId}`);
+ return ship;
+ } catch (error) {
+ logger.error('Error equipping ship:', error);
+ throw error;
+ }
+ }
+
+ // Server management
+ async createServer(serverData) {
+ try {
+ const serverId = `server_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
+
+ const server = {
+ id: serverId,
+ ...serverData,
+ createdAt: new Date(),
+ players: [],
+ status: 'active'
+ };
+
+ this.servers.set(serverId, server);
+ logger.info(`Created new server: ${serverId}`);
+
+ return server;
+ } catch (error) {
+ logger.error('Error creating server:', error);
+ throw error;
+ }
+ }
+
+ async joinServer(serverId, userId) {
+ try {
+ const server = this.servers.get(serverId);
+ if (!server) {
+ throw new Error('Server not found');
+ }
+
+ if (server.players.length >= server.maxPlayers) {
+ throw new Error('Server is full');
+ }
+
+ if (!server.players.includes(userId)) {
+ server.players.push(userId);
+ }
+
+ logger.info(`Player ${userId} joined server ${serverId}`);
+ return server;
+ } catch (error) {
+ logger.error('Error joining server:', error);
+ throw error;
+ }
+ }
+
+ async leaveServer(serverId, userId) {
+ try {
+ const server = this.servers.get(serverId);
+ if (!server) {
+ throw new Error('Server not found');
+ }
+
+ server.players = server.players.filter(id => id !== userId);
+
+ if (server.players.length === 0) {
+ this.servers.delete(serverId);
+ logger.info(`Server ${serverId} deleted (no players)`);
+ }
+
+ logger.info(`Player ${userId} left server ${serverId}`);
+ return server;
+ } catch (error) {
+ logger.error('Error leaving server:', error);
+ throw error;
+ }
+ }
+
+ getServerList() {
+ return Array.from(this.servers.values()).map(server => ({
+ id: server.id,
+ name: server.name,
+ type: server.type,
+ maxPlayers: server.maxPlayers,
+ currentPlayers: server.players.length,
+ status: server.status,
+ region: server.region,
+ createdAt: server.createdAt
+ }));
+ }
+
+ // Game actions
+ async processGameAction(userId, actionData) {
+ try {
+ const player = await this.loadPlayer(userId);
+ if (!player) {
+ throw new Error('Player not found');
+ }
+
+ switch (actionData.type) {
+ case 'dungeon_enter':
+ return await this.handleDungeonEnter(player, actionData);
+ case 'ship_upgrade':
+ return await this.handleShipUpgrade(player, actionData);
+ case 'item_purchase':
+ return await this.handleItemPurchase(player, actionData);
+ case 'daily_reward':
+ return await this.handleDailyReward(player, actionData);
+ default:
+ throw new Error('Unknown action type');
+ }
+ } catch (error) {
+ logger.error('Error processing game action:', error);
+ throw error;
+ }
+ }
+
+ async handleDungeonEnter(player, data) {
+ // Dungeon logic will be implemented here
+ logger.info(`Player ${player.userId} entering dungeon`);
+ return { success: true, message: 'Dungeon entered' };
+ }
+
+ async handleShipUpgrade(player, data) {
+ // Ship upgrade logic will be implemented here
+ logger.info(`Player ${player.userId} upgrading ship`);
+ return { success: true, message: 'Ship upgraded' };
+ }
+
+ async handleItemPurchase(player, data) {
+ // Item purchase logic will be implemented here
+ logger.info(`Player ${player.userId} purchasing item`);
+ return { success: true, message: 'Item purchased' };
+ }
+
+ async handleDailyReward(player, data) {
+ // Daily reward logic will be implemented here
+ logger.info(`Player ${player.userId} claiming daily reward`);
+ return { success: true, message: 'Daily reward claimed' };
+ }
+}
+
+// Singleton instance
+let gameSystem = null;
+
+async function initializeGameSystems() {
+ if (!gameSystem) {
+ gameSystem = new GameSystem();
+ try {
+ await gameSystem.initializeGameSystems();
+ } catch (error) {
+ logger.error('Failed to initialize game systems:', error);
+ throw error;
+ }
+ }
+ return gameSystem;
+}
+
+function getGameSystem() {
+ if (!gameSystem) {
+ logger.warn('Game system not initialized. Call initializeGameSystems() first.');
+ return null;
+ }
+ return gameSystem;
+}
+
+module.exports = {
+ GameSystem,
+ initializeGameSystems,
+ getGameSystem
+};
diff --git a/API/tests/api.test.js b/API/tests/api.test.js
new file mode 100644
index 0000000..6c89004
--- /dev/null
+++ b/API/tests/api.test.js
@@ -0,0 +1,220 @@
+const request = require('supertest');
+const mongoose = require('mongoose');
+const app = require('../server');
+const Player = require('../models/Player');
+
+describe('API Tests', () => {
+ let token;
+ let testUser;
+
+ beforeAll(async () => {
+ // Connect to test database
+ const mongoUri = process.env.MONGODB_TEST_URI || 'mongodb://localhost:27017/galaxystrikeonline_test';
+ await mongoose.connect(mongoUri);
+ });
+
+ afterAll(async () => {
+ // Clean up and close connection
+ await Player.deleteMany({});
+ await mongoose.connection.close();
+ });
+
+ beforeEach(async () => {
+ // Clean up before each test
+ await Player.deleteMany({});
+ });
+
+ describe('Authentication', () => {
+ test('POST /api/auth/register - should register new user', async () => {
+ const userData = {
+ username: 'testuser',
+ email: 'test@example.com',
+ password: 'password123'
+ };
+
+ const response = await request(app)
+ .post('/api/auth/register')
+ .send(userData)
+ .expect(201);
+
+ expect(response.body).toHaveProperty('token');
+ expect(response.body.user).toHaveProperty('username', userData.username);
+ expect(response.body.user).toHaveProperty('email', userData.email);
+ });
+
+ test('POST /api/auth/login - should login existing user', async () => {
+ // First register a user
+ const userData = {
+ username: 'testuser',
+ email: 'test@example.com',
+ password: 'password123'
+ };
+
+ await request(app)
+ .post('/api/auth/register')
+ .send(userData);
+
+ // Then login
+ const loginData = {
+ email: userData.email,
+ password: userData.password
+ };
+
+ const response = await request(app)
+ .post('/api/auth/login')
+ .send(loginData)
+ .expect(200);
+
+ expect(response.body).toHaveProperty('token');
+ token = response.body.token;
+ testUser = response.body.user;
+ });
+
+ test('GET /api/auth/verify - should verify token', async () => {
+ // First login to get token
+ const userData = {
+ username: 'testuser',
+ email: 'test@example.com',
+ password: 'password123'
+ };
+
+ const registerResponse = await request(app)
+ .post('/api/auth/register')
+ .send(userData);
+
+ token = registerResponse.body.token;
+
+ const response = await request(app)
+ .get('/api/auth/verify')
+ .set('Authorization', `Bearer ${token}`)
+ .expect(200);
+
+ expect(response.body).toHaveProperty('valid', true);
+ expect(response.body.user).toHaveProperty('username', userData.username);
+ });
+ });
+
+ describe('Game API', () => {
+ beforeEach(async () => {
+ // Create and login a user for game tests
+ const userData = {
+ username: 'testuser',
+ email: 'test@example.com',
+ password: 'password123'
+ };
+
+ const loginResponse = await request(app)
+ .post('/api/auth/login')
+ .send({
+ email: userData.email,
+ password: userData.password
+ });
+
+ token = loginResponse.body.token;
+ testUser = loginResponse.body.user;
+ });
+
+ test('GET /api/game/player - should get player data', async () => {
+ const response = await request(app)
+ .get('/api/game/player')
+ .set('Authorization', `Bearer ${token}`)
+ .expect(200);
+
+ expect(response.body).toHaveProperty('userId');
+ expect(response.body).toHaveProperty('stats');
+ expect(response.body).toHaveProperty('attributes');
+ });
+
+ test('GET /api/game/ships - should get player ships', async () => {
+ const response = await request(app)
+ .get('/api/game/ships')
+ .set('Authorization', `Bearer ${token}`)
+ .expect(200);
+
+ expect(response.body).toHaveProperty('ships');
+ expect(Array.isArray(response.body.ships)).toBe(true);
+ });
+
+ test('GET /api/game/inventory - should get player inventory', async () => {
+ const response = await request(app)
+ .get('/api/game/inventory')
+ .set('Authorization', `Bearer ${token}`)
+ .expect(200);
+
+ expect(response.body).toHaveProperty('items');
+ expect(response.body).toHaveProperty('summary');
+ });
+
+ test('POST /api/game/daily-reward - should claim daily reward', async () => {
+ const response = await request(app)
+ .post('/api/game/daily-reward')
+ .set('Authorization', `Bearer ${token}`)
+ .expect(200);
+
+ expect(response.body).toHaveProperty('success');
+ });
+ });
+
+ describe('Server API', () => {
+ beforeEach(async () => {
+ // Create and login a user for server tests
+ const userData = {
+ username: 'testuser',
+ email: 'test@example.com',
+ password: 'password123'
+ };
+
+ const loginResponse = await request(app)
+ .post('/api/auth/login')
+ .send({
+ email: userData.email,
+ password: userData.password
+ });
+
+ token = loginResponse.body.token;
+ testUser = loginResponse.body.user;
+ });
+
+ test('GET /api/servers - should get server list', async () => {
+ const response = await request(app)
+ .get('/api/servers')
+ .set('Authorization', `Bearer ${token}`)
+ .expect(200);
+
+ expect(response.body).toHaveProperty('servers');
+ expect(response.body).toHaveProperty('totalServers');
+ expect(Array.isArray(response.body.servers)).toBe(true);
+ });
+
+ test('POST /api/servers/create - should create new server', async () => {
+ const serverData = {
+ name: 'Test Server',
+ type: 'public',
+ maxPlayers: 10,
+ region: 'us-east'
+ };
+
+ const response = await request(app)
+ .post('/api/servers/create')
+ .set('Authorization', `Bearer ${token}`)
+ .send(serverData)
+ .expect(201);
+
+ expect(response.body).toHaveProperty('message');
+ expect(response.body.server).toHaveProperty('name', serverData.name);
+ expect(response.body.server).toHaveProperty('type', serverData.type);
+ });
+ });
+
+ describe('Health Check', () => {
+ test('GET /health - should return health status', async () => {
+ const response = await request(app)
+ .get('/health')
+ .expect(200);
+
+ expect(response.body).toHaveProperty('status', 'OK');
+ expect(response.body).toHaveProperty('timestamp');
+ expect(response.body).toHaveProperty('uptime');
+ });
+ });
+});
diff --git a/API/utils/logger.js b/API/utils/logger.js
new file mode 100644
index 0000000..ca20d28
--- /dev/null
+++ b/API/utils/logger.js
@@ -0,0 +1,27 @@
+const winston = require('winston');
+
+const logger = winston.createLogger({
+ level: process.env.LOG_LEVEL || 'info',
+ format: winston.format.combine(
+ winston.format.timestamp(),
+ winston.format.errors({ stack: true }),
+ winston.format.json()
+ ),
+ defaultMeta: { service: 'galaxystrikeonline-server' },
+ transports: [
+ new winston.transports.File({ filename: 'logs/error.log', level: 'error' }),
+ new winston.transports.File({ filename: 'logs/combined.log' })
+ ]
+});
+
+// Add console transport for development
+if (process.env.NODE_ENV !== 'production') {
+ logger.add(new winston.transports.Console({
+ format: winston.format.combine(
+ winston.format.colorize(),
+ winston.format.simple()
+ )
+ }));
+}
+
+module.exports = logger;
diff --git a/Client/assets/textures/armors/basic_armor.png b/Client/assets/textures/armors/basic_armor.png
new file mode 100644
index 0000000..b837e31
Binary files /dev/null and b/Client/assets/textures/armors/basic_armor.png differ
diff --git a/Client/assets/textures/armors/heavy_armor.png b/Client/assets/textures/armors/heavy_armor.png
new file mode 100644
index 0000000..541fd8c
Binary files /dev/null and b/Client/assets/textures/armors/heavy_armor.png differ
diff --git a/Client/assets/textures/armors/medium_armor.png b/Client/assets/textures/armors/medium_armor.png
new file mode 100644
index 0000000..2ad549a
Binary files /dev/null and b/Client/assets/textures/armors/medium_armor.png differ
diff --git a/Client/assets/textures/base/command_center.png b/Client/assets/textures/base/command_center.png
new file mode 100644
index 0000000..0733bf5
Binary files /dev/null and b/Client/assets/textures/base/command_center.png differ
diff --git a/Client/assets/textures/base/mining_facility.png b/Client/assets/textures/base/mining_facility.png
new file mode 100644
index 0000000..5898f23
Binary files /dev/null and b/Client/assets/textures/base/mining_facility.png differ
diff --git a/Client/assets/textures/items/advanced_circuitboard.png b/Client/assets/textures/items/advanced_circuitboard.png
new file mode 100644
index 0000000..21b1421
Binary files /dev/null and b/Client/assets/textures/items/advanced_circuitboard.png differ
diff --git a/Client/assets/textures/items/advanced_component.png b/Client/assets/textures/items/advanced_component.png
new file mode 100644
index 0000000..2e216a3
Binary files /dev/null and b/Client/assets/textures/items/advanced_component.png differ
diff --git a/Client/assets/textures/items/advanced_components.png b/Client/assets/textures/items/advanced_components.png
new file mode 100644
index 0000000..2a71830
Binary files /dev/null and b/Client/assets/textures/items/advanced_components.png differ
diff --git a/Client/assets/textures/items/bandages.png b/Client/assets/textures/items/bandages.png
new file mode 100644
index 0000000..3ca78e8
Binary files /dev/null and b/Client/assets/textures/items/bandages.png differ
diff --git a/Client/assets/textures/items/basic_circuitboard.png b/Client/assets/textures/items/basic_circuitboard.png
new file mode 100644
index 0000000..7912445
Binary files /dev/null and b/Client/assets/textures/items/basic_circuitboard.png differ
diff --git a/Client/assets/textures/items/battery.png b/Client/assets/textures/items/battery.png
new file mode 100644
index 0000000..e024d52
Binary files /dev/null and b/Client/assets/textures/items/battery.png differ
diff --git a/Client/assets/textures/items/common_circuitboard.png b/Client/assets/textures/items/common_circuitboard.png
new file mode 100644
index 0000000..c60cde1
Binary files /dev/null and b/Client/assets/textures/items/common_circuitboard.png differ
diff --git a/Client/assets/textures/items/copper_ore.png b/Client/assets/textures/items/copper_ore.png
new file mode 100644
index 0000000..f6b6592
Binary files /dev/null and b/Client/assets/textures/items/copper_ore.png differ
diff --git a/Client/assets/textures/items/copper_wire.png b/Client/assets/textures/items/copper_wire.png
new file mode 100644
index 0000000..82abfa3
Binary files /dev/null and b/Client/assets/textures/items/copper_wire.png differ
diff --git a/Client/assets/textures/items/energy_crystal.png b/Client/assets/textures/items/energy_crystal.png
new file mode 100644
index 0000000..62abaf4
Binary files /dev/null and b/Client/assets/textures/items/energy_crystal.png differ
diff --git a/Client/assets/textures/items/health_pack.png b/Client/assets/textures/items/health_pack.png
new file mode 100644
index 0000000..14694d8
Binary files /dev/null and b/Client/assets/textures/items/health_pack.png differ
diff --git a/Client/assets/textures/items/herbs.png b/Client/assets/textures/items/herbs.png
new file mode 100644
index 0000000..c7d1e75
Binary files /dev/null and b/Client/assets/textures/items/herbs.png differ
diff --git a/Client/assets/textures/items/iron_ore.png b/Client/assets/textures/items/iron_ore.png
new file mode 100644
index 0000000..6786de3
Binary files /dev/null and b/Client/assets/textures/items/iron_ore.png differ
diff --git a/Client/assets/textures/items/leather.png b/Client/assets/textures/items/leather.png
new file mode 100644
index 0000000..55770b9
Binary files /dev/null and b/Client/assets/textures/items/leather.png differ
diff --git a/Client/assets/textures/items/mega_health_pack.png b/Client/assets/textures/items/mega_health_pack.png
new file mode 100644
index 0000000..c12acc9
Binary files /dev/null and b/Client/assets/textures/items/mega_health_pack.png differ
diff --git a/Client/assets/textures/items/stell_plate.png b/Client/assets/textures/items/stell_plate.png
new file mode 100644
index 0000000..4bd086d
Binary files /dev/null and b/Client/assets/textures/items/stell_plate.png differ
diff --git a/Client/assets/textures/items/tin_bar.png b/Client/assets/textures/items/tin_bar.png
new file mode 100644
index 0000000..a2daeac
Binary files /dev/null and b/Client/assets/textures/items/tin_bar.png differ
diff --git a/Client/assets/textures/missing-texture.png b/Client/assets/textures/missing-texture.png
new file mode 100644
index 0000000..b808c02
Binary files /dev/null and b/Client/assets/textures/missing-texture.png differ
diff --git a/Client/assets/textures/ships/heavy_cruiser.png b/Client/assets/textures/ships/heavy_cruiser.png
new file mode 100644
index 0000000..38bcffa
Binary files /dev/null and b/Client/assets/textures/ships/heavy_cruiser.png differ
diff --git a/Client/assets/textures/ships/heavy_destroyer.png b/Client/assets/textures/ships/heavy_destroyer.png
new file mode 100644
index 0000000..a9366bd
Binary files /dev/null and b/Client/assets/textures/ships/heavy_destroyer.png differ
diff --git a/Client/assets/textures/ships/light_destroyer.png b/Client/assets/textures/ships/light_destroyer.png
new file mode 100644
index 0000000..349c011
Binary files /dev/null and b/Client/assets/textures/ships/light_destroyer.png differ
diff --git a/Client/assets/textures/ships/starter_cruiser.png b/Client/assets/textures/ships/starter_cruiser.png
new file mode 100644
index 0000000..2676b9d
Binary files /dev/null and b/Client/assets/textures/ships/starter_cruiser.png differ
diff --git a/Client/assets/textures/weapons/laser_pistol.png b/Client/assets/textures/weapons/laser_pistol.png
new file mode 100644
index 0000000..da34474
Binary files /dev/null and b/Client/assets/textures/weapons/laser_pistol.png differ
diff --git a/Client/assets/textures/weapons/laser_sniper_rifle.png b/Client/assets/textures/weapons/laser_sniper_rifle.png
new file mode 100644
index 0000000..05dde27
Binary files /dev/null and b/Client/assets/textures/weapons/laser_sniper_rifle.png differ
diff --git a/Client/assets/textures/weapons/starter_blaster.png b/Client/assets/textures/weapons/starter_blaster.png
new file mode 100644
index 0000000..2474fde
Binary files /dev/null and b/Client/assets/textures/weapons/starter_blaster.png differ
diff --git a/Client/electron-main.js b/Client/electron-main.js
new file mode 100644
index 0000000..3296162
--- /dev/null
+++ b/Client/electron-main.js
@@ -0,0 +1,451 @@
+const { app, BrowserWindow, Menu, shell, ipcMain } = require('electron');
+const path = require('path');
+const fs = require('fs');
+const logger = require('./js/core/Logger');
+
+console.log('[MAIN PROCESS] Electron main process starting...');
+console.log('[MAIN PROCESS] Node.js version:', process.version);
+console.log('[MAIN PROCESS] Electron version:', process.versions.electron);
+console.log('[MAIN PROCESS] Platform:', process.platform);
+console.log('[MAIN PROCESS] Current working directory:', process.cwd());
+
+// Keep a global reference of the window object
+let mainWindow;
+
+function createWindow() {
+ console.log('[MAIN PROCESS] createWindow() called');
+
+ try {
+ console.log('[MAIN PROCESS] Creating BrowserWindow...');
+ // Create the browser window
+ mainWindow = new BrowserWindow({
+ width: 1200,
+ height: 832, // 800 + 32px for custom title bar
+ minWidth: 1200,
+ minHeight: 832,
+ maxWidth: 1200,
+ maxHeight: 832,
+ resizable: false,
+ frame: false,
+ titleBarStyle: 'hidden',
+ webPreferences: {
+ nodeIntegration: true,
+ contextIsolation: false,
+ enableRemoteModule: true,
+ webSecurity: true
+ },
+ icon: path.join(__dirname, 'assets/icon.png'),
+ show: false, // Don't show until ready-to-show
+ title: 'Galaxy Strike Online'
+ });
+
+ console.log('[MAIN PROCESS] BrowserWindow created successfully');
+ console.log('[MAIN PROCESS] Loading index.html...');
+
+ // Load the index.html file
+ mainWindow.loadFile('index.html');
+
+ console.log('[MAIN PROCESS] index.html loaded, setting up electronAPI...');
+
+ // Set up electronAPI after DOM is ready
+ mainWindow.webContents.on('dom-ready', () => {
+ console.log('[MAIN PROCESS] DOM is ready, setting up electronAPI...');
+ mainWindow.webContents.executeJavaScript(`
+ console.log('[RENDERER] Setting up electronAPI...');
+ window.electronAPI = {
+ minimizeWindow: () => require('electron').ipcRenderer.send('minimize-window'),
+ closeWindow: () => require('electron').ipcRenderer.send('close-window'),
+ toggleFullscreen: () => require('electron').ipcRenderer.send('toggle-fullscreen'),
+ log: (level, message, data) => require('electron').ipcRenderer.send('log-message', { level, message, data }),
+ createSaveFolders: (saveSlots) => require('electron').ipcRenderer.invoke('create-save-folders', saveSlots),
+ testFileAccess: (slotPath) => require('electron').ipcRenderer.invoke('test-file-access', slotPath),
+ saveGame: (slot, saveData) => require('electron').ipcRenderer.invoke('save-game', slot, saveData),
+ loadGame: (slot) => require('electron').ipcRenderer.invoke('load-game', slot),
+ getPath: (name) => require('electron').ipcRenderer.invoke('get-path', name),
+ deleteSaveFile: (slot) => require('electron').ipcRenderer.invoke('delete-save-file', slot)
+ };
+ console.log('[RENDERER] electronAPI setup completed');
+ `).then(() => {
+ console.log('[MAIN PROCESS] electronAPI setup completed');
+ }).catch((error) => {
+ console.error('[MAIN PROCESS] Failed to setup electronAPI:', error);
+ });
+ });
+
+ // Show window when ready
+ mainWindow.once('ready-to-show', () => {
+ console.log('[MAIN PROCESS] Window ready-to-show event fired');
+ mainWindow.show();
+ });
+
+ // Open DevTools in development
+ if (process.argv.includes('--dev')) {
+ console.log('[MAIN PROCESS] Opening DevTools...');
+ mainWindow.webContents.openDevTools();
+ }
+
+ // Handle window closed
+ mainWindow.on('closed', () => {
+ console.log('[MAIN PROCESS] Window closed event fired');
+ mainWindow = null;
+ });
+
+ // Handle renderer process crashes
+ mainWindow.webContents.on('render-process-gone', (event, details) => {
+ console.error('[MAIN PROCESS] Renderer process crashed:', details);
+ console.error('[MAIN PROCESS] Crash reason:', details.reason);
+ console.error('[MAIN PROCESS] Exit code:', details.exitCode);
+ });
+
+ // Handle renderer process unresponsive
+ mainWindow.webContents.on('unresponsive', () => {
+ console.warn('[MAIN PROCESS] Renderer process unresponsive');
+ });
+
+ // Handle renderer process responsive again
+ mainWindow.webContents.on('responsive', () => {
+ console.log('[MAIN PROCESS] Renderer process responsive again');
+ });
+
+ // Handle console messages from renderer
+ mainWindow.webContents.on('console-message', (event, level, message, line, sourceId) => {
+ console.log(`[RENDERER CONSOLE] [${level}] ${message} (line: ${line}, source: ${sourceId})`);
+ });
+
+ // Handle page load errors
+ mainWindow.webContents.on('did-fail-load', (event, errorCode, errorDescription, validatedURL, isMainFrame) => {
+ console.error('[MAIN PROCESS] Page failed to load:', errorCode, errorDescription, validatedURL);
+ });
+
+ // Handle page load success
+ mainWindow.webContents.on('did-finish-load', () => {
+ console.log('[MAIN PROCESS] Page finished loading');
+ });
+
+ // Handle DOM ready
+ mainWindow.webContents.on('dom-ready', () => {
+ console.log('[MAIN PROCESS] DOM is ready');
+ });
+
+ // Handle external links
+ mainWindow.webContents.setWindowOpenHandler(({ url }) => {
+ console.log('[MAIN PROCESS] External link requested:', url);
+ shell.openExternal(url);
+ return { action: 'deny' };
+ });
+
+ console.log('[MAIN PROCESS] createWindow() completed successfully');
+
+ } catch (error) {
+ console.error('[MAIN PROCESS] Error in createWindow():', error);
+ console.error('[MAIN PROCESS] Error stack:', error.stack);
+ }
+}
+
+// IPC handlers for save operations
+ipcMain.handle('create-save-folders', async (event, saveSlots) => {
+ console.log('[MAIN PROCESS] create-save-folders called with saveSlots:', saveSlots);
+ try {
+ const userDataPath = app.getPath('userData');
+ console.log('[MAIN PROCESS] userDataPath:', userDataPath);
+ const savesDir = path.join(userDataPath, 'saves');
+ console.log('[MAIN PROCESS] savesDir:', savesDir);
+
+ // Create main saves directory
+ if (!fs.existsSync(savesDir)) {
+ console.log('[MAIN PROCESS] Creating saves directory:', savesDir);
+ fs.mkdirSync(savesDir, { recursive: true });
+ console.log('[MAIN PROCESS] Saves directory created successfully');
+ } else {
+ console.log('[MAIN PROCESS] Saves directory already exists');
+ }
+
+ const paths = {
+ base: savesDir,
+ slots: []
+ };
+
+ // Create save slot directories
+ for (let i = 1; i <= saveSlots; i++) {
+ const slotDir = path.join(savesDir, `slot${i}`);
+ console.log(`[MAIN PROCESS] Checking/creating slot ${i} directory:`, slotDir);
+ if (!fs.existsSync(slotDir)) {
+ console.log(`[MAIN PROCESS] Creating slot ${i} directory`);
+ fs.mkdirSync(slotDir, { recursive: true });
+
+ // Create initial save info file
+ const saveInfo = {
+ slot: i,
+ created: new Date().toISOString(),
+ version: '1.0.0',
+ exists: false
+ };
+
+ const infoPath = path.join(slotDir, 'saveinfo.json');
+ fs.writeFileSync(infoPath, JSON.stringify(saveInfo, null, 2));
+ console.log(`[MAIN PROCESS] Created save info for slot ${i}`);
+ } else {
+ console.log(`[MAIN PROCESS] Slot ${i} directory already exists`);
+ }
+ paths.slots.push(slotDir);
+ }
+
+ console.log('[MAIN PROCESS] Save folders created successfully, returning paths:', paths);
+ return { success: true, paths };
+ } catch (error) {
+ console.error('[MAIN PROCESS] Failed to create save folders:', error);
+ return { success: false, error: error.message };
+ }
+});
+
+ipcMain.handle('test-file-access', async (event, slotPath) => {
+ try {
+ const testFile = path.join(slotPath, 'access_test.txt');
+ fs.writeFileSync(testFile, 'test');
+ fs.unlinkSync(testFile);
+ return { success: true };
+ } catch (error) {
+ return { success: false, error: error.message };
+ }
+});
+
+ipcMain.handle('save-game', async (event, slot, saveData) => {
+ try {
+ const userDataPath = app.getPath('userData');
+ const savesDir = path.join(userDataPath, 'saves');
+ const slotDir = path.join(savesDir, `slot${slot}`);
+
+ // Save game data
+ const saveFilePath = path.join(slotDir, 'save.json');
+ fs.writeFileSync(saveFilePath, JSON.stringify(saveData, null, 2));
+
+ // Update save info
+ const infoPath = path.join(slotDir, 'saveinfo.json');
+ const saveInfo = {
+ slot: slot,
+ created: new Date().toISOString(),
+ lastSaved: new Date().toISOString(),
+ version: '1.0.0',
+ exists: true,
+ playTime: saveData.gameTime || 0
+ };
+ fs.writeFileSync(infoPath, JSON.stringify(saveInfo, null, 2));
+
+ return { success: true };
+ } catch (error) {
+ console.error('Failed to save game:', error);
+ return { success: false, error: error.message };
+ }
+});
+
+ipcMain.handle('load-game', async (event, slot) => {
+ try {
+ const userDataPath = app.getPath('userData');
+ const savesDir = path.join(userDataPath, 'saves');
+ const slotDir = path.join(savesDir, `slot${slot}`);
+ const saveFilePath = path.join(slotDir, 'save.json');
+
+ if (fs.existsSync(saveFilePath)) {
+ const saveContent = fs.readFileSync(saveFilePath, 'utf8');
+ const saveData = JSON.parse(saveContent);
+ return { success: true, data: saveData };
+ } else {
+ return { success: false, error: 'Save file not found' };
+ }
+ } catch (error) {
+ console.error('Failed to load game:', error);
+ return { success: false, error: error.message };
+ }
+});
+
+ipcMain.handle('get-path', async (event, name) => {
+ try {
+ return app.getPath(name);
+ } catch (error) {
+ return null;
+ }
+});
+
+ipcMain.handle('delete-save-file', async (event, slot) => {
+ console.log('[MAIN PROCESS] delete-save-file called for slot:', slot);
+ try {
+ const userDataPath = app.getPath('userData');
+ const savesDir = path.join(userDataPath, 'saves');
+ const slotDir = path.join(savesDir, `slot${slot}`);
+ const saveFilePath = path.join(slotDir, 'save.json');
+ const infoFilePath = path.join(slotDir, 'saveinfo.json');
+
+ console.log('[MAIN PROCESS] Attempting to delete save files from:', slotDir);
+
+ let deletedFiles = [];
+
+ // Delete save file if it exists
+ if (fs.existsSync(saveFilePath)) {
+ console.log('[MAIN PROCESS] Deleting save file:', saveFilePath);
+ fs.unlinkSync(saveFilePath);
+ deletedFiles.push('save.json');
+ }
+
+ // Delete save info file if it exists
+ if (fs.existsSync(infoFilePath)) {
+ console.log('[MAIN PROCESS] Deleting save info file:', infoFilePath);
+ fs.unlinkSync(infoFilePath);
+ deletedFiles.push('saveinfo.json');
+ }
+
+ // Create empty save info file to indicate slot is empty
+ const saveInfo = {
+ slot: slot,
+ created: new Date().toISOString(),
+ version: '1.0.0',
+ exists: false,
+ deleted: new Date().toISOString()
+ };
+ fs.writeFileSync(infoFilePath, JSON.stringify(saveInfo, null, 2));
+
+ console.log('[MAIN PROCESS] Successfully deleted save files for slot', slot, ':', deletedFiles);
+ return { success: true, deletedFiles };
+ } catch (error) {
+ console.error('[MAIN PROCESS] Failed to delete save file:', error);
+ return { success: false, error: error.message };
+ }
+});
+
+// IPC handlers for window controls
+// Handle logging from renderer process
+ipcMain.on('log-message', async (event, { level, message, data }) => {
+ try {
+ switch (level) {
+ case 'error':
+ await logger.error(message, data);
+ break;
+ case 'warn':
+ await logger.warn(message, data);
+ break;
+ case 'info':
+ await logger.info(message, data);
+ break;
+ case 'debug':
+ await logger.debug(message, data);
+ break;
+ default:
+ await logger.info(message, data);
+ }
+ } catch (error) {
+ console.error('Failed to log message from renderer:', error);
+ // Fallback to console logging to prevent infinite loops
+ console.log(`[${level}] ${message}`, data || '');
+ }
+});
+
+ipcMain.on('minimize-window', () => {
+ if (mainWindow) {
+ mainWindow.minimize();
+ }
+});
+
+ipcMain.on('close-window', () => {
+ if (mainWindow) {
+ mainWindow.close();
+ }
+});
+
+ipcMain.on('toggle-fullscreen', () => {
+ if (mainWindow) {
+ const isFullscreen = mainWindow.isFullScreen();
+ if (isFullscreen) {
+ mainWindow.setFullScreen(false);
+ mainWindow.setSize(1200, 832);
+ mainWindow.center();
+ } else {
+ mainWindow.setFullScreen(true);
+ }
+ }
+});
+
+// This method will be called when Electron has finished initialization
+app.whenReady().then(async () => {
+ console.log('[MAIN PROCESS] Electron app ready, starting initialization...');
+
+ try {
+ // Initialize logger with app data path
+ console.log('[MAIN PROCESS] Initializing logger...');
+ await logger.initialize(app.getPath('userData'));
+ console.log('[MAIN PROCESS] Logger initialized');
+
+ await logger.info('Galaxy Strike Online application starting');
+ console.log('[MAIN PROCESS] Logger info message sent');
+
+ console.log('[MAIN PROCESS] Creating main window...');
+ createWindow();
+
+ app.on('activate', () => {
+ console.log('[MAIN PROCESS] Activate event fired');
+ // On macOS it's common to re-create a window in the app when the dock icon is clicked
+ if (BrowserWindow.getAllWindows().length === 0) {
+ console.log('[MAIN PROCESS] No windows exist, creating new window');
+ createWindow();
+ }
+ });
+
+ console.log('[MAIN PROCESS] App initialization completed successfully');
+
+ } catch (error) {
+ console.error('[MAIN PROCESS] Error during app initialization:', error);
+ console.error('[MAIN PROCESS] Error stack:', error.stack);
+ }
+}).catch((error) => {
+ console.error('[MAIN PROCESS] Error in app.whenReady():', error);
+ console.error('[MAIN PROCESS] Error stack:', error.stack);
+});
+
+// Quit when all windows are closed
+app.on('window-all-closed', () => {
+ // On macOS it's common for applications and their menu bar to stay active
+ if (process.platform !== 'darwin') {
+ logger.info('Application shutting down');
+ app.quit();
+ }
+});
+
+// Handle uncaught exceptions
+process.on('uncaughtException', async (error) => {
+ console.error('[MAIN PROCESS] Uncaught Exception:', error);
+ console.error('[MAIN PROCESS] Uncaught Exception stack:', error.stack);
+
+ try {
+ if (logger && typeof logger.errorEvent === 'function') {
+ await logger.errorEvent(error, 'Uncaught Exception in Main Process');
+ }
+ } catch (logError) {
+ console.error('[MAIN PROCESS] Failed to log uncaught exception:', logError);
+ }
+
+ console.error('[MAIN PROCESS] Application will continue running despite uncaught exception');
+});
+
+// Handle unhandled promise rejections
+process.on('unhandledRejection', (reason, promise) => {
+ console.error('[MAIN PROCESS] Unhandled Promise Rejection at:', promise, 'reason:', reason);
+ console.error('[MAIN PROCESS] Rejection reason stack:', reason.stack);
+});
+
+// Handle unhandled rejections
+process.on('unhandledRejection', async (reason, promise) => {
+ // Avoid logging the logging system's own errors to prevent infinite loops
+ if (reason && reason.message && reason.message.includes('object could not be cloned')) {
+ console.warn('IPC cloning error detected - this is expected during logger initialization');
+ return;
+ }
+
+ await logger.error('Unhandled Rejection', { reason: reason.toString(), promise: promise.toString() });
+ console.error('Unhandled Rejection at:', promise, 'reason:', reason);
+});
+
+// Security: Prevent new window creation
+app.on('web-contents-created', (event, contents) => {
+ contents.on('new-window', (event, navigationUrl) => {
+ event.preventDefault();
+ shell.openExternal(navigationUrl);
+ });
+});
diff --git a/Client/index.html b/Client/index.html
new file mode 100644
index 0000000..5322b26
--- /dev/null
+++ b/Client/index.html
@@ -0,0 +1,696 @@
+
+
+
+
+
+ Galaxy Strike Online - Space Idle MMORPG
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Galaxy Strike Online
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
GALAXY STRIKE ONLINE
+
+
Initializing Universe...
+
+
+
+
+
+
+
+
+
+
+
+
+ Dashboard
+
+
+
+ Dungeons
+
+
+
+ Skills
+
+
+
+ Base
+
+
+
+ Quests
+
+
+
+ Inventory
+
+
+
+ Crafting
+
+
+
+ Shop
+
+
+
+
+
+
+
+
+
+
Fleet Status
+
+
+
+
+
Flagship: Starter Cruiser
+
Health: 100%
+
+
+
+
+
+
+
Idle Progress
+
+
Offline Time: 0h 0m
+
Resources Gained: 0
+
Claim Rewards
+
+
+
+ Total Kills
+ 0
+
+
+ Dungeons Cleared
+ 0
+
+
+ Play Time
+ 0h 0m
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Select a dungeon to begin your adventure
+
+
+
+
+
+
+
+
+
+
+ Combat
+ Science
+ Crafting
+
+
+
+
+
+
+
+
+
+
+ Base Overview
+ Base Visualization
+ Ship Gallery
+ Starbases
+
+
+
+
+
+
+
+
Base Information
+
+
+ Power Usage:
+ 0/100
+
+
+ Storage:
+ 1000
+
+
+ Production Rate:
+ 0/s
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Your Ships
+
+
+
+
+
Current Ship
+
+
+
+
+
+
+
Starter Cruiser
+
+
+ Class:
+ Light
+
+
+ Level:
+ 1
+
+
+ Health:
+ 100/100
+
+
+ Attack:
+ 10
+
+
+ Defense:
+ 5
+
+
+ Speed:
+ 15
+
+
+
+
+
+
+
+
+
+
Ships Collected
+
+
+
+
+
+
+
+
+
+
+
+
+
Starbase Management
+
+
+
+
+
+
Available Starbases
+
+
+
+
+
+
+
+
+
+
+ Main Story
+ Daily
+ Weekly
+ Completed
+ Failed Quests
+
+
Daily quests reset in: 00:00:00
+
Weekly quests reset in: 0d 00:00
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Select an item to view details
+
+
+
+
+
+
+
+
+
+
+ Weapons
+ Armor
+ Items
+ Ships
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Initializing...
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Client/js/GameInitializer.js b/Client/js/GameInitializer.js
new file mode 100644
index 0000000..dbdec32
--- /dev/null
+++ b/Client/js/GameInitializer.js
@@ -0,0 +1,365 @@
+/**
+ * Game Initializer
+ * Handles initialization of multiplayer game modes
+ */
+
+console.log('[GAME INITIALIZER] GameInitializer.js script loaded');
+
+class GameInitializer {
+ constructor() {
+ console.log('[GAME INITIALIZER] Constructor called');
+ this.gameMode = 'multiplayer';
+ this.serverData = null;
+ this.authToken = null;
+ this.currentUser = null;
+ this.socket = null;
+ this.apiBaseUrl = 'https://api.korvarix.com/api'; // API Server
+ this.gameServerUrl = 'https://api.korvarix.com'; // Game Server for Socket.IO
+ }
+
+ updateServerUrls(apiUrl, gameUrl) {
+ console.log('[GAME INITIALIZER] Updating server URLs:', { apiUrl, gameUrl });
+ this.apiBaseUrl = apiUrl;
+ this.gameServerUrl = gameUrl;
+ }
+
+ initializeMultiplayer(server, serverData, authToken, currentUser) {
+ console.log('[GAME INITIALIZER] Initializing multiplayer game mode');
+ this.gameMode = 'multiplayer';
+ this.serverData = { ...server, ...serverData };
+ this.authToken = authToken;
+ this.currentUser = currentUser;
+
+ // Initialize Socket.IO connection
+ this.initializeSocketConnection();
+
+ // Initialize game systems with multiplayer support
+ this.initializeGameSystems();
+
+ // Update UI for multiplayer mode
+ this.updateUIForMultiplayerMode();
+
+ console.log('[GAME INITIALIZER] Multiplayer game initialized');
+ }
+
+ initializeSocketConnection() {
+ if (!this.serverData) {
+ console.error('[GAME INITIALIZER] No server data for socket connection');
+ return;
+ }
+
+ console.log('[GAME INITIALIZER] Initializing Socket.IO connection');
+
+ // Check if we're in local mode and should use mock socket
+ if (this.gameServerUrl.includes('localhost') && window.localServerManager && window.localServerManager.localServer) {
+ console.log('[GAME INITIALIZER] Using mock socket for local mode');
+ this.socket = window.localServerManager.localServer.createMockSocket();
+
+ // Trigger connected event immediately since mock socket auto-connects
+ setTimeout(() => {
+ this.onSocketConnected();
+ }, 200);
+
+ return;
+ }
+
+ // Connect to the game server (different from API server)
+ this.socket = io(this.gameServerUrl, {
+ auth: {
+ token: this.authToken,
+ serverId: this.serverData.id
+ }
+ });
+
+ // Socket event handlers
+ this.socket.on('connect', () => {
+ console.log('[GAME INITIALIZER] Connected to server');
+ this.onSocketConnected();
+ });
+
+ this.socket.on('disconnect', () => {
+ console.log('[GAME INITIALIZER] Disconnected from server');
+ this.onSocketDisconnected();
+ });
+
+ this.socket.on('error', (error) => {
+ console.error('[GAME INITIALIZER] Socket error:', error);
+ });
+
+ this.socket.on('force_disconnect', (data) => {
+ console.warn('[GAME INITIALIZER] Force disconnected:', data);
+ this.onForceDisconnect(data);
+ });
+
+ // Game-specific events
+ this.socket.on('playerJoined', (data) => {
+ console.log('[GAME INITIALIZER] Player joined:', data);
+ this.onPlayerJoined(data);
+ });
+
+ this.socket.on('playerLeft', (data) => {
+ console.log('[GAME INITIALIZER] Player left:', data);
+ this.onPlayerLeft(data);
+ });
+
+ this.socket.on('gameUpdate', (data) => {
+ console.log('[GAME INITIALIZER] Game update:', data);
+ this.onGameUpdate(data);
+ });
+
+ this.socket.on('chatMessage', (data) => {
+ console.log('[GAME INITIALIZER] Chat message:', data);
+ this.onChatMessage(data);
+ });
+ }
+
+ onSocketConnected() {
+ // Join the server room
+ this.socket.emit('joinServer', {
+ serverId: this.serverData.id,
+ userId: this.currentUser.userId,
+ username: this.currentUser.username
+ });
+
+ // Show connected status
+ this.showConnectionStatus('Connected', 'success');
+ }
+
+ onSocketDisconnected() {
+ // Show disconnected status
+ this.showConnectionStatus('Disconnected', 'error');
+ }
+
+ onPlayerJoined(data) {
+ // Handle player joining
+ this.updatePlayerList();
+ this.showNotification(`${data.username} joined the server`, 'info');
+ }
+
+ onPlayerLeft(data) {
+ // Handle player leaving
+ this.updatePlayerList();
+ this.showNotification(`${data.username} left the server`, 'info');
+ }
+
+ onGameUpdate(data) {
+ // Handle game state updates
+ if (window.game && window.game.handleServerUpdate) {
+ window.game.handleServerUpdate(data);
+ }
+ }
+
+ onChatMessage(data) {
+ // Handle chat messages
+ if (window.game && window.game.handleChatMessage) {
+ window.game.handleChatMessage(data);
+ }
+ }
+
+ onForceDisconnect(data) {
+ // Handle forced disconnection from server
+ console.warn('[GAME INITIALIZER] Force disconnected by server:', data);
+
+ // Show notification to user
+ if (window.game && window.game.showNotification) {
+ window.game.showNotification(
+ `Disconnected: ${data.reason}`,
+ 'warning',
+ 10000
+ );
+ }
+
+ // Disconnect the socket
+ if (this.socket) {
+ this.socket.disconnect();
+ }
+
+ // Clean up multiplayer mode
+ if (window.game) {
+ window.game.setMultiplayerMode(false);
+ }
+
+ // Return to main menu after a delay
+ setTimeout(() => {
+ if (window.liveMainMenu) {
+ window.liveMainMenu.showLoginSection();
+ }
+ }, 2000);
+ }
+
+ initializeGameSystems() {
+ console.log('[GAME INITIALIZER] Initializing game systems');
+
+ // Wait for the main game script to be ready
+ if (typeof window.game !== 'undefined') {
+ console.log('[GAME INITIALIZER] window.game is available, calling setupGameSystems');
+ this.setupGameSystems();
+ } else {
+ console.log('[GAME INITIALIZER] window.game not available, waiting 100ms');
+ // Wait for the game to be initialized
+ setTimeout(() => this.initializeGameSystems(), 100);
+ }
+ }
+
+ setupGameSystems() {
+ if (!window.game) {
+ console.error('[GAME INITIALIZER] Game not available');
+ return;
+ }
+
+ console.log('[GAME INITIALIZER] Setting up game systems for multiplayer mode');
+
+ // Configure game for multiplayer mode
+ console.log('[GAME INITIALIZER] Configuring for multiplayer mode');
+ window.game.setMultiplayerMode(true, this.socket, this.serverData, this.currentUser);
+
+ // Game is already set up with save data, just start the game loop
+ if (window.game.start) {
+ // console.log('[GAME INITIALIZER] Calling start() to begin game loop');
+ window.game.start();
+ } else if (window.game.startGame) {
+ // console.log('[GAME INITIALIZER] Calling startGame(false) - save data already applied');
+ window.game.startGame(false); // false = don't load again (save data already applied)
+ } else {
+ console.error('[GAME INITIALIZER] No start method available on window.game');
+ }
+
+ console.log('[GAME INITIALIZER] Game systems configured');
+ }
+
+ updateUIForMultiplayerMode() {
+ // Update UI elements to show multiplayer mode
+ const playerName = document.getElementById('playerName');
+ if (playerName && this.currentUser) {
+ playerName.textContent = this.currentUser.username;
+ }
+
+ // Show multiplayer-specific UI elements
+ this.showMultiplayerUI();
+
+ // Show server info
+ this.showServerInfo();
+ }
+
+ hideMultiplayerUI() {
+ // Hide elements that are only relevant in multiplayer
+ const chatContainer = document.getElementById('chatContainer');
+ if (chatContainer) {
+ chatContainer.classList.add('hidden');
+ }
+
+ const playerList = document.getElementById('playerList');
+ if (playerList) {
+ playerList.classList.add('hidden');
+ }
+ }
+
+ showMultiplayerUI() {
+ // Show multiplayer-specific elements
+ const chatContainer = document.getElementById('chatContainer');
+ if (chatContainer) {
+ chatContainer.classList.remove('hidden');
+ }
+
+ const playerList = document.getElementById('playerList');
+ if (playerList) {
+ playerList.classList.remove('hidden');
+ }
+ }
+
+ showServerInfo() {
+ // Add server information to the UI
+ const header = document.querySelector('.game-header');
+ if (header && !header.querySelector('.server-info')) {
+ const serverInfo = document.createElement('div');
+ serverInfo.className = 'server-info';
+ serverInfo.innerHTML = `
+
+ ${this.serverData.name} (${this.serverData.currentPlayers}/${this.serverData.maxPlayers})
+ `;
+ serverInfo.style.cssText = `
+ background: rgba(0, 212, 255, 0.2);
+ color: #00d4ff;
+ padding: 4px 8px;
+ border-radius: 4px;
+ font-size: 0.8rem;
+ margin-left: 10px;
+ `;
+ header.appendChild(serverInfo);
+ }
+ }
+
+ showConnectionStatus(status, type) {
+ // Show connection status in the UI
+ const statusElement = document.getElementById('connectionStatus');
+ if (statusElement) {
+ statusElement.textContent = status;
+ statusElement.className = `connection-status ${type}`;
+ }
+ }
+
+ updatePlayerList() {
+ // Update the player list UI
+ if (this.socket && this.serverData) {
+ // Request updated player list from server
+ this.socket.emit('getPlayerList', { serverId: this.serverData.id });
+ }
+ }
+
+ showNotification(message, type = 'info') {
+ // Show a notification to the user
+ if (window.game && window.game.showNotification) {
+ window.game.showNotification(message, type, 3000);
+ } else {
+ // Fallback to alert
+ console.log(`[GAME INITIALIZER] Notification: ${message}`);
+ }
+ }
+
+ // Method to send actions to the server
+ sendGameAction(actionType, actionData) {
+ if (this.socket && this.gameMode === 'multiplayer') {
+ this.socket.emit('gameAction', {
+ type: actionType,
+ data: actionData,
+ userId: this.currentUser.userId,
+ serverId: this.serverData.id
+ });
+ }
+ }
+
+ // Method to send chat messages
+ sendChatMessage(message) {
+ if (this.socket && this.gameMode === 'multiplayer') {
+ this.socket.emit('chatMessage', {
+ message: message,
+ userId: this.currentUser.userId,
+ username: this.currentUser.username,
+ serverId: this.serverData.id
+ });
+ }
+ }
+
+ // Cleanup method
+ cleanup() {
+ console.log('[GAME INITIALIZER] Cleaning up');
+
+ if (this.socket) {
+ this.socket.disconnect();
+ this.socket = null;
+ }
+
+ this.gameMode = null;
+ this.serverData = null;
+ this.authToken = null;
+ this.currentUser = null;
+ }
+}
+
+// Create global instance
+window.gameInitializer = new GameInitializer();
+
+// Export for use in other scripts
+if (typeof module !== 'undefined' && module.exports) {
+ module.exports = GameInitializer;
+}
diff --git a/Client/js/LocalServer.js b/Client/js/LocalServer.js
new file mode 100644
index 0000000..de6d955
--- /dev/null
+++ b/Client/js/LocalServer.js
@@ -0,0 +1,418 @@
+/**
+ * Local Server for Singleplayer Mode
+ * A simplified server that runs within the Electron client for offline/singleplayer functionality
+ * NOTE: This version requires express, socket.io, and cors dependencies to be installed
+ */
+
+class LocalServer {
+ constructor() {
+ this.app = null;
+ this.server = null;
+ this.io = null;
+ this.port = null;
+ this.isRunning = false;
+ this.connectedClients = new Map();
+
+ console.log('[LOCAL SERVER] LocalServer initialized');
+ }
+
+ async initialize() {
+ try {
+ // Try to require dependencies
+ if (typeof require !== 'undefined') {
+ const express = require('express');
+ const { createServer } = require('http');
+ const { Server } = require('socket.io');
+ const cors = require('cors');
+
+ this.setupExpress(express, cors);
+ this.createServer = createServer;
+ this.ServerClass = Server;
+
+ console.log('[LOCAL SERVER] Dependencies loaded successfully');
+ return true;
+ } else {
+ console.warn('[LOCAL SERVER] require() not available, running in browser context');
+ return false;
+ }
+ } catch (error) {
+ console.error('[LOCAL SERVER] Failed to load dependencies:', error.message);
+ console.log('[LOCAL SERVER] Please install dependencies: npm install express socket.io cors');
+ return false;
+ }
+ }
+
+ setupExpress(express, cors) {
+ // Initialize Express app
+ this.app = express();
+
+ // Middleware
+ this.app.use(cors({
+ origin: ["http://localhost:3000", "http://127.0.0.1:3000", "file://"],
+ credentials: true
+ }));
+ this.app.use(express.json({ limit: '10mb' }));
+ this.app.use(express.urlencoded({ extended: true }));
+
+ // Health check endpoint
+ this.app.get('/health', (req, res) => {
+ res.status(200).json({
+ status: 'OK',
+ timestamp: new Date().toISOString(),
+ uptime: process.uptime(),
+ mode: 'local'
+ });
+ });
+
+ // API version endpoint
+ this.app.get('/api/ssc/version', (req, res) => {
+ res.status(200).json({
+ version: '1.0.0',
+ service: 'galaxystrikeonline-local-server',
+ timestamp: new Date().toISOString(),
+ mode: 'local'
+ });
+ });
+
+ // Mock authentication endpoints for singleplayer
+ this.app.post('/api/auth/login', (req, res) => {
+ const { email, password } = req.body;
+
+ // Auto-authenticate for singleplayer mode
+ const mockUser = {
+ id: 'local-user',
+ email: email || 'local@player.com',
+ username: 'Local Player',
+ token: 'local-token-' + Date.now(),
+ createdAt: new Date().toISOString()
+ };
+
+ res.status(200).json({
+ success: true,
+ user: mockUser,
+ token: mockUser.token,
+ message: 'Logged in to local mode'
+ });
+ });
+
+ this.app.post('/api/auth/register', (req, res) => {
+ const { email, password, username } = req.body;
+
+ // Auto-register for singleplayer mode
+ const mockUser = {
+ id: 'local-user',
+ email: email || 'local@player.com',
+ username: username || 'Local Player',
+ token: 'local-token-' + Date.now(),
+ createdAt: new Date().toISOString()
+ };
+
+ res.status(201).json({
+ success: true,
+ user: mockUser,
+ token: mockUser.token,
+ message: 'Registered in local mode'
+ });
+ });
+
+ // Mock server browser endpoints
+ this.app.get('/api/servers', (req, res) => {
+ // Return a single local server
+ const localServer = {
+ id: 'local-server',
+ name: 'Local Singleplayer',
+ description: 'Your personal local server for singleplayer gaming',
+ type: 'private',
+ region: 'local',
+ maxPlayers: 1,
+ currentPlayers: 0,
+ owner: 'Local Player',
+ address: 'localhost',
+ port: this.port,
+ status: 'online',
+ createdAt: new Date().toISOString(),
+ ping: 0
+ };
+
+ res.status(200).json({
+ success: true,
+ servers: [localServer]
+ });
+ });
+
+ // Mock game data endpoints
+ this.app.get('/api/game/player/:id', (req, res) => {
+ // Return player data from local storage if available
+ const playerId = req.params.id;
+ let saveData;
+
+ try {
+ // In Electron context, access localStorage differently
+ if (typeof localStorage !== 'undefined') {
+ saveData = localStorage.getItem(`gso_save_slot_1`);
+ }
+ } catch (error) {
+ console.warn('[LOCAL SERVER] Could not access localStorage:', error);
+ }
+
+ if (saveData) {
+ try {
+ const parsedData = JSON.parse(saveData);
+ res.status(200).json({
+ success: true,
+ player: parsedData.player
+ });
+ } catch (error) {
+ res.status(500).json({
+ success: false,
+ error: 'Failed to parse save data'
+ });
+ }
+ } else {
+ res.status(404).json({
+ success: false,
+ error: 'No save data found'
+ });
+ }
+ });
+
+ this.app.post('/api/game/player/:id/save', (req, res) => {
+ // Save player data to local storage
+ const playerId = req.params.id;
+ const playerData = req.body;
+
+ try {
+ let existingSaveData = '{}';
+
+ // In Electron context, access localStorage differently
+ if (typeof localStorage !== 'undefined') {
+ existingSaveData = localStorage.getItem(`gso_save_slot_1`) || '{}';
+ }
+
+ const parsedExisting = JSON.parse(existingSaveData);
+
+ // Merge player data
+ parsedExisting.player = playerData;
+ parsedExisting.lastSave = Date.now();
+
+ if (typeof localStorage !== 'undefined') {
+ localStorage.setItem(`gso_save_slot_1`, JSON.stringify(parsedExisting));
+ }
+
+ res.status(200).json({
+ success: true,
+ message: 'Player data saved locally'
+ });
+ } catch (error) {
+ res.status(500).json({
+ success: false,
+ error: 'Failed to save player data'
+ });
+ }
+ });
+ }
+
+ async start(port = 3004) {
+ if (this.isRunning) {
+ console.log('[LOCAL SERVER] Server is already running on port', this.port);
+ return { success: false, error: 'Server already running' };
+ }
+
+ try {
+ // Initialize dependencies if not already done
+ if (!this.app) {
+ const initialized = await this.initialize();
+ if (!initialized) {
+ return { success: false, error: 'Failed to initialize server dependencies' };
+ }
+ }
+
+ this.port = port;
+ this.server = this.createServer(this.app);
+ this.io = new this.ServerClass(this.server, {
+ cors: {
+ origin: ["http://localhost:3000", "http://127.0.0.1:3000", "file://"],
+ methods: ["GET", "POST"]
+ }
+ });
+
+ // Setup Socket.IO handlers
+ this.setupSocketHandlers();
+
+ // Start the server
+ await new Promise((resolve, reject) => {
+ this.server.listen(port, (error) => {
+ if (error) {
+ reject(error);
+ } else {
+ resolve();
+ }
+ });
+ });
+
+ this.isRunning = true;
+ console.log(`[LOCAL SERVER] Local server started on port ${port}`);
+
+ return {
+ success: true,
+ port: port,
+ url: `http://localhost:${port}`
+ };
+
+ } catch (error) {
+ console.error('[LOCAL SERVER] Failed to start server:', error);
+ return { success: false, error: error.message };
+ }
+ }
+
+ setupSocketHandlers() {
+ this.io.on('connection', (socket) => {
+ console.log('[LOCAL SERVER] Client connected:', socket.id);
+ this.connectedClients.set(socket.id, {
+ connectedAt: Date.now(),
+ playerData: null
+ });
+
+ // Handle authentication
+ socket.on('authenticate', (data) => {
+ console.log('[LOCAL SERVER] Authenticating client:', socket.id, data);
+
+ // Auto-authenticate for local mode
+ socket.emit('authenticated', {
+ success: true,
+ user: {
+ id: 'local-user',
+ username: 'Local Player',
+ token: 'local-token-' + Date.now()
+ }
+ });
+
+ // Update client info
+ const clientInfo = this.connectedClients.get(socket.id);
+ if (clientInfo) {
+ clientInfo.playerData = {
+ id: 'local-user',
+ username: 'Local Player'
+ };
+ }
+ });
+
+ // Handle game data sync
+ socket.on('saveGameData', (data) => {
+ console.log('[LOCAL SERVER] Saving game data for:', socket.id);
+
+ // Save to localStorage (this will be handled by the client-side save system)
+ socket.emit('gameDataSaved', {
+ success: true,
+ timestamp: Date.now()
+ });
+ });
+
+ socket.on('loadGameData', (data) => {
+ console.log('[LOCAL SERVER] Loading game data for:', socket.id);
+
+ // Load from localStorage (this will be handled by the client-side load system)
+ let saveData;
+
+ try {
+ if (typeof localStorage !== 'undefined') {
+ saveData = localStorage.getItem(`gso_save_slot_1`);
+ }
+ } catch (error) {
+ console.warn('[LOCAL SERVER] Could not access localStorage:', error);
+ }
+
+ if (saveData) {
+ try {
+ const parsedData = JSON.parse(saveData);
+ socket.emit('gameDataLoaded', {
+ success: true,
+ data: parsedData
+ });
+ } catch (error) {
+ socket.emit('gameDataLoaded', {
+ success: false,
+ error: 'Failed to parse save data'
+ });
+ }
+ } else {
+ socket.emit('gameDataLoaded', {
+ success: false,
+ error: 'No save data found'
+ });
+ }
+ });
+
+ // Handle disconnection
+ socket.on('disconnect', () => {
+ console.log('[LOCAL SERVER] Client disconnected:', socket.id);
+ this.connectedClients.delete(socket.id);
+ });
+
+ // Send welcome message
+ socket.emit('welcome', {
+ message: 'Connected to local server',
+ serverInfo: {
+ mode: 'local',
+ port: this.port,
+ timestamp: new Date().toISOString()
+ }
+ });
+ });
+ }
+
+ async stop() {
+ if (!this.isRunning) {
+ console.log('[LOCAL SERVER] Server is not running');
+ return { success: false, error: 'Server is not running' };
+ }
+
+ try {
+ // Disconnect all clients
+ if (this.io) {
+ this.io.disconnectSockets();
+ }
+
+ // Close the server
+ if (this.server) {
+ await new Promise((resolve) => {
+ this.server.close(resolve);
+ });
+ }
+
+ this.isRunning = false;
+ this.port = null;
+ this.connectedClients.clear();
+
+ console.log('[LOCAL SERVER] Local server stopped');
+ return { success: true };
+
+ } catch (error) {
+ console.error('[LOCAL SERVER] Failed to stop server:', error);
+ return { success: false, error: error.message };
+ }
+ }
+
+ getStatus() {
+ return {
+ isRunning: this.isRunning,
+ port: this.port,
+ connectedClients: this.connectedClients.size,
+ uptime: this.isRunning ? process.uptime() : 0
+ };
+ }
+
+ getUrl() {
+ return this.isRunning ? `http://localhost:${this.port}` : null;
+ }
+}
+
+// Export for use in Node.js environment
+if (typeof module !== 'undefined' && module.exports) {
+ module.exports = LocalServer;
+}
+
+// Export for use in browser environment
+if (typeof window !== 'undefined') {
+ window.LocalServer = LocalServer;
+}
diff --git a/Client/js/LocalServerManager.js b/Client/js/LocalServerManager.js
new file mode 100644
index 0000000..6347d2c
--- /dev/null
+++ b/Client/js/LocalServerManager.js
@@ -0,0 +1,224 @@
+/**
+ * Local Server Manager
+ * Manages the local server for singleplayer mode within the Electron client
+ */
+
+class LocalServerManager {
+ constructor() {
+ this.localServer = null;
+ this.isRunning = false;
+ this.port = 3004;
+ this.startupAttempts = 0;
+ this.maxStartupAttempts = 3;
+
+ console.log('[LOCAL SERVER MANAGER] LocalServerManager initialized');
+ }
+
+ async initialize() {
+ console.log('[LOCAL SERVER MANAGER] Initializing local server...');
+
+ try {
+ // In Electron renderer context, use SimpleLocalServer which doesn't require Node.js modules
+ if (typeof window !== 'undefined' && window.SimpleLocalServer) {
+ this.localServer = new window.SimpleLocalServer();
+ console.log('[LOCAL SERVER MANAGER] SimpleLocalServer class loaded from window');
+ return true;
+ } else if (typeof window !== 'undefined' && window.LocalServer) {
+ // Fallback to original LocalServer if available
+ this.localServer = new window.LocalServer();
+ console.log('[LOCAL SERVER MANAGER] LocalServer class loaded from window');
+ return true;
+ } else {
+ console.warn('[LOCAL SERVER MANAGER] No local server class available');
+ return false;
+ }
+ } catch (error) {
+ console.error('[LOCAL SERVER MANAGER] Failed to initialize local server:', error);
+ console.log('[LOCAL SERVER MANAGER] Please ensure SimpleLocalServer.js is loaded properly');
+ return false;
+ }
+ }
+
+ async startServer() {
+ if (this.isRunning) {
+ console.log('[LOCAL SERVER MANAGER] Server is already running');
+ return { success: true, port: this.port };
+ }
+
+ if (!this.localServer) {
+ const initialized = await this.initialize();
+ if (!initialized) {
+ return { success: false, error: 'Failed to initialize server' };
+ }
+ }
+
+ console.log(`[LOCAL SERVER MANAGER] Attempting to start server on port ${this.port}`);
+
+ try {
+ const result = await this.localServer.start(this.port);
+
+ if (result.success) {
+ this.isRunning = true;
+ this.port = result.port;
+ this.startupAttempts = 0;
+
+ console.log(`[LOCAL SERVER MANAGER] Server started successfully on port ${this.port}`);
+ console.log(`[LOCAL SERVER MANAGER] Server URL: ${result.url}`);
+
+ // Update LiveMainMenu to use local server
+ this.updateMainMenuForLocalMode();
+
+ return result;
+ } else {
+ console.error('[LOCAL SERVER MANAGER] Failed to start server:', result.error);
+ this.startupAttempts++;
+
+ // Try alternative ports if first attempt fails
+ if (this.startupAttempts < this.maxStartupAttempts) {
+ this.port = 3004 + this.startupAttempts;
+ console.log(`[LOCAL SERVER MANAGER] Retrying with port ${this.port}`);
+ return await this.startServer();
+ }
+
+ return result;
+ }
+ } catch (error) {
+ console.error('[LOCAL SERVER MANAGER] Exception starting server:', error);
+ return { success: false, error: error.message };
+ }
+ }
+
+ async stopServer() {
+ if (!this.isRunning || !this.localServer) {
+ console.log('[LOCAL SERVER MANAGER] Server is not running');
+ return { success: true };
+ }
+
+ console.log('[LOCAL SERVER MANAGER] Stopping local server...');
+
+ try {
+ const result = await this.localServer.stop();
+
+ if (result.success) {
+ this.isRunning = false;
+ this.port = 3004;
+ console.log('[LOCAL SERVER MANAGER] Server stopped successfully');
+ } else {
+ console.error('[LOCAL SERVER MANAGER] Failed to stop server:', result.error);
+ }
+
+ return result;
+ } catch (error) {
+ console.error('[LOCAL SERVER MANAGER] Exception stopping server:', error);
+ return { success: false, error: error.message };
+ }
+ }
+
+ getStatus() {
+ if (!this.localServer) {
+ return {
+ isRunning: false,
+ port: null,
+ connectedClients: 0,
+ uptime: 0
+ };
+ }
+
+ const status = this.localServer.getStatus();
+ return {
+ ...status,
+ url: this.isRunning ? `http://localhost:${status.port}` : null
+ };
+ }
+
+ getServerUrl() {
+ return this.isRunning ? `http://localhost:${this.port}` : null;
+ }
+
+ updateMainMenuForLocalMode() {
+ // Update LiveMainMenu to use local server URLs
+ if (window.liveMainMenu) {
+ console.log('[LOCAL SERVER MANAGER] Updating LiveMainMenu for local mode');
+
+ window.liveMainMenu.apiBaseUrl = `http://localhost:${this.port}/api`;
+ window.liveMainMenu.gameServerUrl = `http://localhost:${this.port}`;
+ window.liveMainMenu.isLocalMode = true;
+
+ console.log(`[LOCAL SERVER MANAGER] Updated API URL to: ${window.liveMainMenu.apiBaseUrl}`);
+ console.log(`[LOCAL SERVER MANAGER] Updated Game Server URL to: ${window.liveMainMenu.gameServerUrl}`);
+
+ // Also update GameInitializer URLs
+ if (window.gameInitializer) {
+ window.gameInitializer.updateServerUrls(
+ `http://localhost:${this.port}/api`,
+ `http://localhost:${this.port}`
+ );
+ console.log('[LOCAL SERVER MANAGER] Updated GameInitializer URLs for local mode');
+ } else {
+ console.warn('[LOCAL SERVER MANAGER] GameInitializer not available for URL update');
+ }
+ } else {
+ console.warn('[LOCAL SERVER MANAGER] LiveMainMenu not available for update');
+ }
+ }
+
+ // Auto-start server when in singleplayer mode
+ async autoStartIfSingleplayer() {
+ // Check if we should auto-start (no external server available)
+ try {
+ // Try to connect to external server first
+ const response = await fetch('https://api.korvarix.com/health', {
+ method: 'GET',
+ timeout: 3000
+ });
+
+ if (response.ok) {
+ console.log('[LOCAL SERVER MANAGER] External server available, not starting local server');
+ return { success: false, reason: 'External server available' };
+ }
+ } catch (error) {
+ console.log('[LOCAL SERVER MANAGER] External server not available, starting local server');
+ }
+
+ // Start local server
+ return await this.startServer();
+ }
+
+ // Handle server errors and restart if needed
+ handleServerError(error) {
+ console.error('[LOCAL SERVER MANAGER] Server error:', error);
+
+ // Try to restart server if it crashes
+ if (this.isRunning) {
+ console.log('[LOCAL SERVER MANAGER] Attempting to restart server...');
+ this.stopServer().then(() => {
+ setTimeout(() => {
+ this.startServer();
+ }, 2000); // Wait 2 seconds before restarting
+ });
+ }
+ }
+
+ // Get local server info for UI display
+ getServerInfo() {
+ return {
+ isRunning: this.isRunning,
+ port: this.port,
+ url: this.getServerUrl(),
+ status: this.isRunning ? 'Online' : 'Offline',
+ mode: 'Local Singleplayer',
+ connectedClients: this.localServer ? this.localServer.connectedClients.size : 0,
+ uptime: this.localServer ? this.localServer.uptime : 0
+ };
+ }
+}
+
+// Create global instance
+window.localServerManager = new LocalServerManager();
+
+// Auto-export for module systems
+if (typeof module !== 'undefined' && module.exports) {
+ module.exports = LocalServerManager;
+}
+
+console.log('[LOCAL SERVER MANAGER] LocalServerManager loaded and global instance created');
diff --git a/Client/js/SimpleLocalServer.js b/Client/js/SimpleLocalServer.js
new file mode 100644
index 0000000..b5d1215
--- /dev/null
+++ b/Client/js/SimpleLocalServer.js
@@ -0,0 +1,535 @@
+/**
+ * Simple Local Server for Singleplayer Mode
+ * A mock server that simulates server responses without requiring Node.js dependencies
+ * This runs entirely in the browser/renderer context
+ */
+
+class SimpleLocalServer {
+ constructor() {
+ this.isRunning = false;
+ this.port = 3004;
+ this.connectedClients = new Map();
+ this.existingSaveData = null;
+
+ // Check for existing save data on initialization
+ this.loadExistingSaveData();
+
+ this.mockData = {
+ servers: [{
+ id: 'local-server',
+ name: 'Local Singleplayer',
+ description: 'Your personal local server for singleplayer gaming',
+ type: 'private',
+ region: 'local',
+ maxPlayers: 1,
+ currentPlayers: 0,
+ owner: 'Local Player',
+ address: 'localhost',
+ port: this.port,
+ status: 'online',
+ createdAt: new Date().toISOString(),
+ ping: 0
+ }],
+ user: {
+ id: 'local-user',
+ email: 'local@player.com',
+ username: 'Local Player',
+ token: 'local-token-' + Date.now(),
+ createdAt: new Date().toISOString()
+ }
+ };
+
+ console.log('[SIMPLE LOCAL SERVER] SimpleLocalServer initialized');
+ console.log('[SIMPLE LOCAL SERVER] Existing save data found:', !!this.existingSaveData);
+ }
+
+ // Mock Socket.IO server for local mode
+ createMockSocket() {
+ console.log('[SIMPLE LOCAL SERVER] Creating mock Socket.IO connection');
+
+ const mockSocket = {
+ connected: false,
+ eventHandlers: {},
+
+ on: function(event, handler) {
+ console.log(`[MOCKET SOCKET] Registering event handler for: ${event}`);
+ if (!this.eventHandlers[event]) {
+ this.eventHandlers[event] = [];
+ }
+ this.eventHandlers[event].push(handler);
+ },
+
+ emit: function(event, data) {
+ console.log(`[MOCKET SOCKET] Emitting event: ${event}`, data);
+ },
+
+ connect: function() {
+ console.log('[MOCKET SOCKET] Connecting...');
+ this.connected = true;
+
+ // Simulate successful connection
+ setTimeout(() => {
+ if (this.eventHandlers['connect']) {
+ this.eventHandlers['connect'].forEach(handler => handler());
+ }
+ }, 100);
+ },
+
+ disconnect: function() {
+ console.log('[MOCKET SOCKET] Disconnecting...');
+ this.connected = false;
+ if (this.eventHandlers['disconnect']) {
+ this.eventHandlers['disconnect'].forEach(handler => handler());
+ }
+ }
+ };
+
+ // Auto-connect
+ mockSocket.connect();
+
+ return mockSocket;
+ }
+
+ loadExistingSaveData() {
+ try {
+ const saveData = localStorage.getItem(`gso_save_slot_1`);
+ if (saveData) {
+ this.existingSaveData = JSON.parse(saveData);
+ console.log('[SIMPLE LOCAL SERVER] Loaded existing save data:', {
+ hasPlayerData: !!this.existingSaveData.player,
+ playerLevel: this.existingSaveData.player?.stats?.level,
+ lastSave: this.existingSaveData.lastSave,
+ gameTime: this.existingSaveData.gameTime
+ });
+ } else {
+ console.log('[SIMPLE LOCAL SERVER] No existing save data found');
+ }
+ } catch (error) {
+ console.warn('[SIMPLE LOCAL SERVER] Error loading existing save data:', error);
+ this.existingSaveData = null;
+ }
+ }
+
+ applyExistingSaveDataToGame() {
+ if (!this.existingSaveData || !window.game) {
+ console.log('[SIMPLE LOCAL SERVER] No existing save data or game not available');
+ return false;
+ }
+
+ try {
+ console.log('[SIMPLE LOCAL SERVER] Applying existing save data to game...');
+
+ // Apply save data to game systems
+ if (this.existingSaveData.player && window.game.systems.player) {
+ window.game.systems.player.load(this.existingSaveData.player);
+ console.log('[SIMPLE LOCAL SERVER] Player data applied');
+ }
+
+ if (this.existingSaveData.inventory && window.game.systems.inventory) {
+ window.game.systems.inventory.load(this.existingSaveData.inventory);
+ console.log('[SIMPLE LOCAL SERVER] Inventory data applied');
+ }
+
+ if (this.existingSaveData.economy && window.game.systems.economy) {
+ window.game.systems.economy.load(this.existingSaveData.economy);
+ console.log('[SIMPLE LOCAL SERVER] Economy data applied');
+ }
+
+ if (this.existingSaveData.idleSystem && window.game.systems.idleSystem) {
+ window.game.systems.idleSystem.load(this.existingSaveData.idleSystem);
+ console.log('[SIMPLE LOCAL SERVER] Idle system data applied');
+ }
+
+ if (this.existingSaveData.dungeonSystem && window.game.systems.dungeonSystem) {
+ window.game.systems.dungeonSystem.load(this.existingSaveData.dungeonSystem);
+ console.log('[SIMPLE LOCAL SERVER] Dungeon system data applied');
+ }
+
+ if (this.existingSaveData.skillSystem && window.game.systems.skillSystem) {
+ window.game.systems.skillSystem.load(this.existingSaveData.skillSystem);
+ console.log('[SIMPLE LOCAL SERVER] Skill system data applied');
+ }
+
+ if (this.existingSaveData.baseSystem && window.game.systems.baseSystem) {
+ window.game.systems.baseSystem.load(this.existingSaveData.baseSystem);
+ console.log('[SIMPLE LOCAL SERVER] Base system data applied');
+ }
+
+ if (this.existingSaveData.questSystem && window.game.systems.questSystem) {
+ window.game.systems.questSystem.load(this.existingSaveData.questSystem);
+ console.log('[SIMPLE LOCAL SERVER] Quest system data applied');
+ }
+
+ if (this.existingSaveData.gameTime && window.game) {
+ window.game.gameTime = this.existingSaveData.gameTime;
+ console.log('[SIMPLE LOCAL SERVER] Game time applied:', this.existingSaveData.gameTime);
+ }
+
+ console.log('[SIMPLE LOCAL SERVER] All save data applied successfully');
+ return true;
+
+ } catch (error) {
+ console.error('[SIMPLE LOCAL SERVER] Error applying save data to game:', error);
+ return false;
+ }
+ }
+
+ async start(port = 3004) {
+ if (this.isRunning) {
+ console.log('[SIMPLE LOCAL SERVER] Server is already running on port', this.port);
+ return { success: false, error: 'Server already running' };
+ }
+
+ try {
+ this.port = port;
+ this.isRunning = true;
+
+ // Update mock server data with actual port
+ this.mockData.servers[0].port = port;
+
+ console.log(`[SIMPLE LOCAL SERVER] Mock local server started on port ${port}`);
+
+ return {
+ success: true,
+ port: port,
+ url: `http://localhost:${port}`
+ };
+
+ } catch (error) {
+ console.error('[SIMPLE LOCAL SERVER] Failed to start server:', error);
+ return { success: false, error: error.message };
+ }
+ }
+
+ async stop() {
+ if (!this.isRunning) {
+ console.log('[SIMPLE LOCAL SERVER] Server is not running');
+ return { success: false, error: 'Server is not running' };
+ }
+
+ try {
+ this.isRunning = false;
+ this.connectedClients.clear();
+
+ console.log('[SIMPLE LOCAL SERVER] Mock local server stopped');
+ return { success: true };
+
+ } catch (error) {
+ console.error('[SIMPLE LOCAL SERVER] Failed to stop server:', error);
+ return { success: false, error: error.message };
+ }
+ }
+
+ getStatus() {
+ return {
+ isRunning: this.isRunning,
+ port: this.port,
+ connectedClients: this.connectedClients.size,
+ uptime: this.isRunning ? 0 : 0 // Mock uptime
+ };
+ }
+
+ getUrl() {
+ return this.isRunning ? `http://localhost:${this.port}` : null;
+ }
+
+ // Mock API methods that simulate server responses
+ async mockRequest(method, url, data = null) {
+ console.log(`[SIMPLE LOCAL SERVER] Mock ${method} ${url}`, data);
+
+ // Simulate network delay
+ await new Promise(resolve => setTimeout(resolve, 100));
+
+ try {
+ if (url === '/health') {
+ return {
+ status: 200,
+ ok: true,
+ headers: {
+ get: (name) => name === 'content-type' ? 'application/json' : null
+ },
+ json: () => Promise.resolve({
+ status: 'OK',
+ timestamp: new Date().toISOString(),
+ uptime: 0,
+ mode: 'local'
+ }),
+ text: () => Promise.resolve(JSON.stringify({
+ status: 'OK',
+ timestamp: new Date().toISOString(),
+ uptime: 0,
+ mode: 'local'
+ }))
+ };
+ }
+
+ if (url === '/api/ssc/version') {
+ return {
+ status: 200,
+ ok: true,
+ headers: {
+ get: (name) => name === 'content-type' ? 'application/json' : null
+ },
+ json: () => Promise.resolve({
+ version: '1.0.0',
+ service: 'galaxystrikeonline-local-server',
+ timestamp: new Date().toISOString(),
+ mode: 'local'
+ }),
+ text: () => Promise.resolve(JSON.stringify({
+ version: '1.0.0',
+ service: 'galaxystrikeonline-local-server',
+ timestamp: new Date().toISOString(),
+ mode: 'local'
+ }))
+ };
+ }
+
+ if (url === '/api/auth/login' && method === 'POST') {
+ return {
+ status: 200,
+ ok: true,
+ headers: {
+ get: (name) => name === 'content-type' ? 'application/json' : null
+ },
+ json: () => Promise.resolve({
+ success: true,
+ user: this.mockData.user,
+ token: this.mockData.user.token,
+ message: 'Logged in to local mode'
+ }),
+ text: () => Promise.resolve(JSON.stringify({
+ success: true,
+ user: this.mockData.user,
+ token: this.mockData.user.token,
+ message: 'Logged in to local mode'
+ }))
+ };
+ }
+
+ if (url === '/api/auth/register' && method === 'POST') {
+ return {
+ status: 201,
+ ok: true,
+ headers: {
+ get: (name) => name === 'content-type' ? 'application/json' : null
+ },
+ json: () => Promise.resolve({
+ success: true,
+ user: this.mockData.user,
+ token: this.mockData.user.token,
+ message: 'Registered in local mode'
+ }),
+ text: () => Promise.resolve(JSON.stringify({
+ success: true,
+ user: this.mockData.user,
+ token: this.mockData.user.token,
+ message: 'Registered in local mode'
+ }))
+ };
+ }
+
+ if (url === '/api/servers') {
+ return {
+ status: 200,
+ ok: true,
+ headers: {
+ get: (name) => name === 'content-type' ? 'application/json' : null
+ },
+ json: () => Promise.resolve({
+ success: true,
+ servers: this.mockData.servers
+ }),
+ text: () => Promise.resolve(JSON.stringify({
+ success: true,
+ servers: this.mockData.servers
+ }))
+ };
+ }
+
+ if (url.startsWith('/servers/') && url.endsWith('/join') && method === 'POST') {
+ // Mock server join response
+ const serverId = url.split('/')[2];
+ return {
+ status: 200,
+ ok: true,
+ headers: {
+ get: (name) => name === 'content-type' ? 'application/json' : null
+ },
+ json: () => Promise.resolve({
+ success: true,
+ server: {
+ id: serverId,
+ name: 'Local Singleplayer',
+ address: 'localhost',
+ port: this.port,
+ gamePort: this.port + 1,
+ maxPlayers: 1,
+ currentPlayers: 1,
+ status: 'online'
+ },
+ message: 'Joined server successfully'
+ }),
+ text: () => Promise.resolve(JSON.stringify({
+ success: true,
+ server: {
+ id: serverId,
+ name: 'Local Singleplayer',
+ address: 'localhost',
+ port: this.port,
+ gamePort: this.port + 1,
+ maxPlayers: 1,
+ currentPlayers: 1,
+ status: 'online'
+ },
+ message: 'Joined server successfully'
+ }))
+ };
+ }
+
+ if (url.startsWith('/api/game/player/') && method === 'GET') {
+ // Return player data from existing save data or localStorage
+ let saveData = this.existingSaveData;
+
+ // If no existing save data, try localStorage
+ if (!saveData) {
+ try {
+ const localStorageData = localStorage.getItem(`gso_save_slot_1`);
+ if (localStorageData) {
+ saveData = JSON.parse(localStorageData);
+ }
+ } catch (error) {
+ console.warn('[SIMPLE LOCAL SERVER] Could not access localStorage:', error);
+ }
+ }
+
+ if (saveData) {
+ return {
+ status: 200,
+ ok: true,
+ headers: {
+ get: (name) => name === 'content-type' ? 'application/json' : null
+ },
+ json: () => Promise.resolve({
+ success: true,
+ player: saveData.player
+ }),
+ text: () => Promise.resolve(JSON.stringify({
+ success: true,
+ player: saveData.player
+ }))
+ };
+ } else {
+ return {
+ status: 404,
+ ok: false,
+ headers: {
+ get: (name) => name === 'content-type' ? 'application/json' : null
+ },
+ json: () => Promise.resolve({
+ success: false,
+ error: 'No save data found'
+ }),
+ text: () => Promise.resolve(JSON.stringify({
+ success: false,
+ error: 'No save data found'
+ }))
+ };
+ }
+ }
+
+ if (url.startsWith('/api/game/player/') && method === 'POST') {
+ // Save player data to localStorage
+ try {
+ let existingSaveData = '{}';
+
+ if (typeof localStorage !== 'undefined') {
+ existingSaveData = localStorage.getItem(`gso_save_slot_1`) || '{}';
+ }
+
+ const parsedExisting = JSON.parse(existingSaveData);
+ parsedExisting.player = data;
+ parsedExisting.lastSave = Date.now();
+
+ if (typeof localStorage !== 'undefined') {
+ localStorage.setItem(`gso_save_slot_1`, JSON.stringify(parsedExisting));
+ }
+
+ return {
+ status: 200,
+ ok: true,
+ headers: {
+ get: (name) => name === 'content-type' ? 'application/json' : null
+ },
+ json: () => Promise.resolve({
+ success: true,
+ message: 'Player data saved locally'
+ }),
+ text: () => Promise.resolve(JSON.stringify({
+ success: true,
+ message: 'Player data saved locally'
+ }))
+ };
+ } catch (error) {
+ return {
+ status: 500,
+ ok: false,
+ headers: {
+ get: (name) => name === 'content-type' ? 'application/json' : null
+ },
+ json: () => Promise.resolve({
+ success: false,
+ error: 'Failed to save player data'
+ }),
+ text: () => Promise.resolve(JSON.stringify({
+ success: false,
+ error: 'Failed to save player data'
+ }))
+ };
+ }
+ }
+
+ // Default response for unknown endpoints
+ return {
+ status: 404,
+ ok: false,
+ headers: {
+ get: (name) => name === 'content-type' ? 'application/json' : null
+ },
+ json: () => Promise.resolve({
+ success: false,
+ error: 'Endpoint not found'
+ }),
+ text: () => Promise.resolve(JSON.stringify({
+ success: false,
+ error: 'Endpoint not found'
+ }))
+ };
+
+ } catch (error) {
+ console.error('[SIMPLE LOCAL SERVER] Mock request error:', error);
+ return {
+ status: 500,
+ ok: false,
+ headers: {
+ get: (name) => name === 'content-type' ? 'application/json' : null
+ },
+ json: () => Promise.resolve({
+ success: false,
+ error: 'Internal server error'
+ }),
+ text: () => Promise.resolve(JSON.stringify({
+ success: false,
+ error: 'Internal server error'
+ }))
+ };
+ }
+ }
+}
+
+// Export for use in browser environment
+if (typeof window !== 'undefined') {
+ window.SimpleLocalServer = SimpleLocalServer;
+}
+
+console.log('[SIMPLE LOCAL SERVER] SimpleLocalServer loaded and exported to window');
diff --git a/Client/js/core/DebugLogger.js b/Client/js/core/DebugLogger.js
new file mode 100644
index 0000000..ad59a1f
--- /dev/null
+++ b/Client/js/core/DebugLogger.js
@@ -0,0 +1,163 @@
+/**
+ * Galaxy Strike Online - Debug Logger
+ * Enhanced debugging that integrates with existing Logger system
+ */
+
+class DebugLogger {
+ constructor() {
+ this.debugEnabled = true; // Always enabled
+ this.startTime = performance.now();
+ this.stepTimers = new Map();
+ this.debugLogs = []; // Store logs in memory
+ this.maxLogs = 1000; // Limit memory usage
+
+ // Use the existing logger if available
+ this.logger = window.logger || null;
+
+ // Log initialization
+ this.log('=== DEBUG SESSION STARTED ===');
+ }
+
+ async log(message, data = null) {
+ const timestamp = new Date().toISOString();
+ const stackTrace = new Error().stack;
+
+ // Build performance object
+ const performanceData = {
+ elapsed: `${(performance.now() - this.startTime).toFixed(2)}ms`,
+ memory: performance.memory ? {
+ used: `${(performance.memory.usedJSHeapSize / 1024 / 1024).toFixed(2)}MB`,
+ total: `${(performance.memory.totalJSHeapSize / 1024 / 1024).toFixed(2)}MB`
+ } : null
+ };
+
+ // Create single formatted log message
+ let logMessage = `[DEBUG] ${message}`;
+ if (data) {
+ logMessage += `\n${JSON.stringify(data, null, 2)}`;
+ }
+ if (performanceData) {
+ logMessage += `\n[PERF] ${performanceData.elapsed} | Memory: ${performanceData.memory?.used || 'N/A'}/${performanceData.memory?.total || 'N/A'}`;
+ }
+
+ // Add to memory logs
+ const logEntry = {
+ timestamp: timestamp,
+ message: message,
+ data: data ? JSON.stringify(data, null, 2) : '',
+ stackTrace: stackTrace ? stackTrace.split('\n').slice(0, 3).join('\n') : '',
+ performance: performanceData
+ };
+ this.debugLogs.push(logEntry);
+
+ // Limit memory usage
+ if (this.debugLogs.length > this.maxLogs) {
+ this.debugLogs.shift();
+ }
+
+ // Always log to console
+ console.log(`[DEBUG] ${message}`, data || '');
+
+ // Log performance data to console
+ if (performanceData.memory) {
+ console.log(`[PERF] ${performanceData.elapsed} | Memory: ${performanceData.memory.used}/${performanceData.memory.total}`);
+ }
+
+ // Use existing logger if available
+ if (this.logger) {
+ try {
+ await this.logger.debug(logMessage);
+ } catch (error) {
+ console.error('[DEBUG LOGGER] Failed to log via existing logger:', error);
+ }
+ } else {
+ // Fallback to electronAPI log
+ if (window.electronAPI && window.electronAPI.log) {
+ window.electronAPI.log('debug', logMessage);
+ }
+ }
+ }
+
+ async startStep(stepName) {
+ this.stepTimers.set(stepName, performance.now());
+ await this.log(`STEP START: ${stepName}`, {
+ type: 'step_start',
+ step: stepName,
+ elapsed: '0ms'
+ });
+ }
+
+ async endStep(stepName, data = null) {
+ const startTime = this.stepTimers.get(stepName);
+ const duration = startTime ? (performance.now() - startTime).toFixed(2) : 'N/A';
+
+ this.stepTimers.delete(stepName);
+ await this.log(`STEP END: ${stepName}`, {
+ type: 'step_end',
+ step: stepName,
+ duration: `${duration}ms`,
+ data
+ });
+ }
+
+ async logStep(stepName, data = null) {
+ await this.log(`STEP: ${stepName}`, {
+ type: 'step',
+ step: stepName,
+ data
+ });
+ }
+
+ getLogs() {
+ return this.debugLogs;
+ }
+
+ exportLogs() {
+ const logText = this.debugLogs.map(entry =>
+ `[${entry.timestamp}] ${entry.message}${entry.data ? '\n' + entry.data : ''}${entry.performance ? '\nPerf: ' + entry.performance.elapsed : ''}`
+ ).join('\n\n');
+
+ return logText;
+ }
+
+ clearLogs() {
+ this.debugLogs = [];
+ this.log('=== LOGS CLEARED ===');
+ }
+
+ async shutdown() {
+ await this.log('=== DEBUG SESSION ENDING ===');
+ await this.log('SESSION SUMMARY', {
+ totalLogs: this.debugLogs.length,
+ sessionDuration: `${(performance.now() - this.startTime).toFixed(2)}ms`
+ });
+
+ // No need to finalize files - the existing Logger handles that
+ console.log('[DEBUG LOGGER] Session ended cleanly');
+ }
+
+ // Convenience methods for specific logging types
+ async info(message, data = null) {
+ await this.log(`[INFO] ${message}`, data);
+ }
+
+ async error(message, data = null) {
+ await this.log(`[ERROR] ${message}`, data);
+ }
+
+ async warn(message, data = null) {
+ await this.log(`[WARN] ${message}`, data);
+ }
+
+ async errorEvent(error, context = 'Unknown') {
+ await this.error(`Error in ${context}`, {
+ name: error.name,
+ message: error.message,
+ stack: error.stack,
+ timestamp: new Date().toISOString()
+ });
+ }
+}
+
+// Global debug logger instance
+window.debugLogger = new DebugLogger();
diff --git a/Client/js/core/Economy.js b/Client/js/core/Economy.js
new file mode 100644
index 0000000..ecdea9c
--- /dev/null
+++ b/Client/js/core/Economy.js
@@ -0,0 +1,2335 @@
+/**
+ * Galaxy Strike Online - Economy System
+ * Manages credits, gems, transactions, and shop
+ */
+
+class Economy {
+ constructor(gameEngine) {
+ const debugLogger = window.debugLogger;
+ if (debugLogger) debugLogger.log('Economy constructor called');
+
+ this.game = gameEngine;
+
+ // Currency
+ this.credits = 1000;
+ this.gems = 10;
+ this.premiumCurrency = 0; // For future premium features
+
+ // Transaction history
+ this.transactionHistory = [];
+ this.transactions = []; // Add missing transactions array
+
+ // Owned cosmetics
+ this.ownedCosmetics = []; // Add missing owned cosmetics array
+
+ // Shop categories
+ this.shopCategories = {
+ ships: 'Ships',
+ weapons: 'Weapons',
+ armors: 'Armors',
+ materials: 'Materials',
+ cosmetics: 'Cosmetics',
+ // upgrades: 'Upgrades', // Temporarily disabled
+ consumables: 'Consumables',
+ buildings: 'Buildings'
+ };
+
+ // Random shop system
+ this.randomShopItems = {}; // Current random items per category
+ this.shopRefreshInterval = null; // Timer for 2-hour refresh
+ this.shopHeartbeatInterval = null; // Timer for live countdown updates
+ this.lastShopRefresh = null; // Timestamp of last refresh
+ this.SHOP_REFRESH_INTERVAL = 2 * 60 * 60 * 1000; // 2 hours in milliseconds
+ this.MAX_ITEMS_PER_CATEGORY = 6;
+ this.categoryPurchaseLimits = {}; // Track purchases per category per refresh
+
+ // Shop items
+ this.shopItems = {
+ ships: [
+ // Starter Cruiser Variants
+ {
+ id: 'starter_cruiser_common',
+ name: 'Starter Cruiser',
+ type: 'ship',
+ rarity: 'common',
+ price: 5000,
+ currency: 'credits',
+ description: 'Reliable starter cruiser for new pilots',
+ texture: 'assets/textures/ships/starter_cruiser.png',
+ stats: { attack: 15, speed: 10, defense: 12, hull: 100 }
+ },
+ {
+ id: 'starter_cruiser_uncommon',
+ name: 'Starter Cruiser II',
+ type: 'ship',
+ rarity: 'uncommon',
+ price: 12000,
+ currency: 'credits',
+ description: 'Upgraded starter cruiser with enhanced systems',
+ texture: 'assets/textures/ships/starter_cruiser.png',
+ stats: { attack: 18, speed: 12, defense: 15, hull: 120 }
+ },
+ {
+ id: 'starter_cruiser_rare',
+ name: 'Starter Cruiser III',
+ type: 'ship',
+ rarity: 'rare',
+ price: 25000,
+ currency: 'credits',
+ description: 'Elite starter cruiser with advanced weaponry',
+ texture: 'assets/textures/ships/starter_cruiser.png',
+ stats: { attack: 22, speed: 14, defense: 18, hull: 145 }
+ },
+ {
+ id: 'starter_cruiser_epic',
+ name: 'Starter Cruiser IV',
+ type: 'ship',
+ rarity: 'epic',
+ price: 45000,
+ currency: 'credits',
+ description: 'Master starter cruiser with elite modifications',
+ texture: 'assets/textures/ships/starter_cruiser.png',
+ stats: { attack: 26, speed: 16, defense: 22, hull: 175 }
+ },
+ {
+ id: 'starter_cruiser_legendary',
+ name: 'Starter Cruiser V',
+ type: 'ship',
+ rarity: 'legendary',
+ price: 75000,
+ currency: 'credits',
+ description: 'Legendary starter cruiser with unparalleled performance',
+ texture: 'assets/textures/ships/starter_cruiser.png',
+ stats: { attack: 32, speed: 18, defense: 28, hull: 210 }
+ },
+
+ // Light Destroyer Variants
+ {
+ id: 'light_destroyer_common',
+ name: 'Light Destroyer',
+ type: 'ship',
+ rarity: 'common',
+ price: 12000,
+ currency: 'credits',
+ description: 'Fast and maneuverable light destroyer',
+ texture: 'assets/textures/ships/light_destroyer.png',
+ stats: { attack: 18, speed: 18, defense: 10, hull: 80 }
+ },
+ {
+ id: 'light_destroyer_uncommon',
+ name: 'Light Destroyer II',
+ type: 'ship',
+ rarity: 'uncommon',
+ price: 28000,
+ currency: 'credits',
+ description: 'Enhanced light destroyer with improved speed',
+ texture: 'assets/textures/ships/light_destroyer.png',
+ stats: { attack: 22, speed: 22, defense: 12, hull: 95 }
+ },
+ {
+ id: 'light_destroyer_rare',
+ name: 'Light Destroyer III',
+ type: 'ship',
+ rarity: 'rare',
+ price: 55000,
+ currency: 'credits',
+ description: 'Elite light destroyer with superior agility',
+ texture: 'assets/textures/ships/light_destroyer.png',
+ stats: { attack: 26, speed: 26, defense: 15, hull: 115 }
+ },
+ {
+ id: 'light_destroyer_epic',
+ name: 'Light Destroyer IV',
+ type: 'ship',
+ rarity: 'epic',
+ price: 95000,
+ currency: 'credits',
+ description: 'Master light destroyer with exceptional speed',
+ texture: 'assets/textures/ships/light_destroyer.png',
+ stats: { attack: 30, speed: 30, defense: 18, hull: 140 }
+ },
+ {
+ id: 'light_destroyer_legendary',
+ name: 'Light Destroyer V',
+ type: 'ship',
+ rarity: 'legendary',
+ price: 150000,
+ currency: 'credits',
+ description: 'Legendary light destroyer with unmatched velocity',
+ texture: 'assets/textures/ships/light_destroyer.png',
+ stats: { attack: 36, speed: 36, defense: 22, hull: 170 }
+ },
+
+ // Heavy Destroyer Variants
+ {
+ id: 'heavy_destroyer_common',
+ name: 'Heavy Destroyer',
+ type: 'ship',
+ rarity: 'common',
+ price: 25000,
+ currency: 'credits',
+ description: 'Powerful heavy destroyer with strong weapons',
+ texture: 'assets/textures/ships/heavy_destroyer.png',
+ stats: { attack: 25, speed: 12, defense: 18, hull: 120 }
+ },
+ {
+ id: 'heavy_destroyer_uncommon',
+ name: 'Heavy Destroyer II',
+ type: 'ship',
+ rarity: 'uncommon',
+ price: 55000,
+ currency: 'credits',
+ description: 'Upgraded heavy destroyer with enhanced firepower',
+ texture: 'assets/textures/ships/heavy_destroyer.png',
+ stats: { attack: 30, speed: 14, defense: 22, hull: 145 }
+ },
+ {
+ id: 'heavy_destroyer_rare',
+ name: 'Heavy Destroyer III',
+ type: 'ship',
+ rarity: 'rare',
+ price: 110000,
+ currency: 'credits',
+ description: 'Elite heavy destroyer with devastating weapons',
+ texture: 'assets/textures/ships/heavy_destroyer.png',
+ stats: { attack: 35, speed: 16, defense: 26, hull: 175 }
+ },
+ {
+ id: 'heavy_destroyer_epic',
+ name: 'Heavy Destroyer IV',
+ type: 'ship',
+ rarity: 'epic',
+ price: 190000,
+ currency: 'credits',
+ description: 'Master heavy destroyer with overwhelming power',
+ texture: 'assets/textures/ships/heavy_destroyer.png',
+ stats: { attack: 40, speed: 18, defense: 30, hull: 210 }
+ },
+ {
+ id: 'heavy_destroyer_legendary',
+ name: 'Heavy Destroyer V',
+ type: 'ship',
+ rarity: 'legendary',
+ price: 300000,
+ currency: 'credits',
+ description: 'Legendary heavy destroyer with ultimate destruction',
+ texture: 'assets/textures/ships/heavy_destroyer.png',
+ stats: { attack: 48, speed: 20, defense: 36, hull: 255 }
+ },
+
+ // Heavy Cruiser Variants
+ {
+ id: 'heavy_cruiser_common',
+ name: 'Heavy Cruiser',
+ type: 'ship',
+ rarity: 'common',
+ price: 45000,
+ currency: 'credits',
+ description: 'Massive heavy cruiser with excellent defense',
+ texture: 'assets/textures/ships/heavy_cruiser.png',
+ stats: { attack: 22, speed: 8, defense: 25, hull: 150 }
+ },
+ {
+ id: 'heavy_cruiser_uncommon',
+ name: 'Heavy Cruiser II',
+ type: 'ship',
+ rarity: 'uncommon',
+ price: 95000,
+ currency: 'credits',
+ description: 'Enhanced heavy cruiser with superior armor',
+ texture: 'assets/textures/ships/heavy_cruiser.png',
+ stats: { attack: 26, speed: 9, defense: 30, hull: 180 }
+ },
+ {
+ id: 'heavy_cruiser_rare',
+ name: 'Heavy Cruiser III',
+ type: 'ship',
+ rarity: 'rare',
+ price: 190000,
+ currency: 'credits',
+ description: 'Elite heavy cruiser with fortress-like defense',
+ texture: 'assets/textures/ships/heavy_cruiser.png',
+ stats: { attack: 30, speed: 10, defense: 36, hull: 220 }
+ },
+ {
+ id: 'heavy_cruiser_epic',
+ name: 'Heavy Cruiser IV',
+ type: 'ship',
+ rarity: 'epic',
+ price: 320000,
+ currency: 'credits',
+ description: 'Master heavy cruiser with impenetrable armor',
+ texture: 'assets/textures/ships/heavy_cruiser.png',
+ stats: { attack: 34, speed: 11, defense: 42, hull: 265 }
+ },
+ {
+ id: 'heavy_cruiser_legendary',
+ name: 'Heavy Cruiser V',
+ type: 'ship',
+ rarity: 'legendary',
+ price: 500000,
+ currency: 'credits',
+ description: 'Legendary heavy cruiser with ultimate defense',
+ texture: 'assets/textures/ships/heavy_cruiser.png',
+ stats: { attack: 40, speed: 12, defense: 50, hull: 320 }
+ }
+ ],
+ weapons: [
+ // Starter Blaster Variants
+ {
+ id: 'starter_blaster_common',
+ name: 'Starter Blaster',
+ type: 'weapon',
+ rarity: 'common',
+ price: 2000,
+ currency: 'credits',
+ description: 'Basic blaster for new pilots',
+ texture: 'assets/textures/weapons/starter_blaster.png',
+ stats: { damage: 10, fireRate: 2, range: 5, energy: 5 }
+ },
+ {
+ id: 'starter_blaster_uncommon',
+ name: 'Starter Blaster II',
+ type: 'weapon',
+ rarity: 'uncommon',
+ price: 5000,
+ currency: 'credits',
+ description: 'Enhanced starter blaster with better fire rate',
+ texture: 'assets/textures/weapons/starter_blaster.png',
+ stats: { damage: 12, fireRate: 2.5, range: 5.5, energy: 6 }
+ },
+ {
+ id: 'starter_blaster_rare',
+ name: 'Starter Blaster III',
+ type: 'weapon',
+ rarity: 'rare',
+ price: 12000,
+ currency: 'credits',
+ description: 'Elite starter blaster with improved damage',
+ texture: 'assets/textures/weapons/starter_blaster.png',
+ stats: { damage: 15, fireRate: 3, range: 6, energy: 7 }
+ },
+ {
+ id: 'starter_blaster_epic',
+ name: 'Starter Blaster IV',
+ type: 'weapon',
+ rarity: 'epic',
+ price: 25000,
+ currency: 'credits',
+ description: 'Master starter blaster with superior performance',
+ texture: 'assets/textures/weapons/starter_blaster.png',
+ stats: { damage: 18, fireRate: 3.5, range: 6.5, energy: 8 }
+ },
+ {
+ id: 'starter_blaster_legendary',
+ name: 'Starter Blaster V',
+ type: 'weapon',
+ rarity: 'legendary',
+ price: 50000,
+ currency: 'credits',
+ description: 'Legendary starter blaster with ultimate power',
+ texture: 'assets/textures/weapons/starter_blaster.png',
+ stats: { damage: 22, fireRate: 4, range: 7, energy: 10 }
+ },
+
+ // Laser Pistol Variants
+ {
+ id: 'laser_pistol_common',
+ name: 'Laser Pistol',
+ type: 'weapon',
+ rarity: 'common',
+ price: 3000,
+ currency: 'credits',
+ description: 'Compact laser pistol for close combat',
+ texture: 'assets/textures/weapons/laser_pistol.png',
+ stats: { damage: 8, fireRate: 3, range: 4, energy: 3 }
+ },
+ {
+ id: 'laser_pistol_uncommon',
+ name: 'Laser Pistol II',
+ type: 'weapon',
+ rarity: 'uncommon',
+ price: 7500,
+ currency: 'credits',
+ description: 'Enhanced laser pistol with better accuracy',
+ texture: 'assets/textures/weapons/laser_pistol.png',
+ stats: { damage: 10, fireRate: 3.5, range: 4.5, energy: 4 }
+ },
+ {
+ id: 'laser_pistol_rare',
+ name: 'Laser Pistol III',
+ type: 'weapon',
+ rarity: 'rare',
+ price: 18000,
+ currency: 'credits',
+ description: 'Elite laser pistol with piercing shots',
+ texture: 'assets/textures/weapons/laser_pistol.png',
+ stats: { damage: 13, fireRate: 4, range: 5, energy: 5 }
+ },
+ {
+ id: 'laser_pistol_epic',
+ name: 'Laser Pistol IV',
+ type: 'weapon',
+ rarity: 'epic',
+ price: 35000,
+ currency: 'credits',
+ description: 'Master laser pistol with rapid fire capability',
+ texture: 'assets/textures/weapons/laser_pistol.png',
+ stats: { damage: 16, fireRate: 4.5, range: 5.5, energy: 6 }
+ },
+ {
+ id: 'laser_pistol_legendary',
+ name: 'Laser Pistol V',
+ type: 'weapon',
+ rarity: 'legendary',
+ price: 70000,
+ currency: 'credits',
+ description: 'Legendary laser pistol with devastating power',
+ texture: 'assets/textures/weapons/laser_pistol.png',
+ stats: { damage: 20, fireRate: 5, range: 6, energy: 8 }
+ },
+
+ // Laser Sniper Rifle Variants
+ {
+ id: 'laser_sniper_rifle_common',
+ name: 'Laser Sniper Rifle',
+ type: 'weapon',
+ rarity: 'common',
+ price: 8000,
+ currency: 'credits',
+ description: 'Long-range laser sniper rifle for precision attacks',
+ texture: 'assets/textures/weapons/laser_sniper_rifle.png',
+ stats: { damage: 25, fireRate: 1, range: 10, energy: 8 }
+ },
+ {
+ id: 'laser_sniper_rifle_uncommon',
+ name: 'Laser Sniper Rifle II',
+ type: 'weapon',
+ rarity: 'uncommon',
+ price: 20000,
+ currency: 'credits',
+ description: 'Enhanced laser sniper rifle with better penetration',
+ texture: 'assets/textures/weapons/laser_sniper_rifle.png',
+ stats: { damage: 30, fireRate: 1.2, range: 11, energy: 9 }
+ },
+ {
+ id: 'laser_sniper_rifle_rare',
+ name: 'Laser Sniper Rifle III',
+ type: 'weapon',
+ rarity: 'rare',
+ price: 50000,
+ currency: 'credits',
+ description: 'Elite laser sniper rifle with deadly accuracy',
+ texture: 'assets/textures/weapons/laser_sniper_rifle.png',
+ stats: { damage: 36, fireRate: 1.4, range: 12, energy: 10 }
+ },
+ {
+ id: 'laser_sniper_rifle_epic',
+ name: 'Laser Sniper Rifle IV',
+ type: 'weapon',
+ rarity: 'epic',
+ price: 100000,
+ currency: 'credits',
+ description: 'Master laser sniper rifle with extreme range',
+ texture: 'assets/textures/weapons/laser_sniper_rifle.png',
+ stats: { damage: 42, fireRate: 1.6, range: 13, energy: 12 }
+ },
+ {
+ id: 'laser_sniper_rifle_legendary',
+ name: 'Laser Sniper Rifle V',
+ type: 'weapon',
+ rarity: 'legendary',
+ price: 200000,
+ currency: 'credits',
+ description: 'Legendary laser sniper rifle with unparalleled precision',
+ texture: 'assets/textures/weapons/laser_sniper_rifle.png',
+ stats: { damage: 50, fireRate: 2, range: 15, energy: 15 }
+ }
+ ],
+ armors: [
+ // Basic Armor Variants
+ {
+ id: 'basic_armor_common',
+ name: 'Basic Armor',
+ type: 'armor',
+ rarity: 'common',
+ price: 1500,
+ currency: 'credits',
+ description: 'Light protection for beginners',
+ texture: 'assets/textures/armors/basic_armor.png',
+ stats: { defense: 5, durability: 20, weight: 2, energyShield: 0 }
+ },
+ {
+ id: 'basic_armor_uncommon',
+ name: 'Basic Armor II',
+ type: 'armor',
+ rarity: 'uncommon',
+ price: 4000,
+ currency: 'credits',
+ description: 'Improved basic armor with better durability',
+ texture: 'assets/textures/armors/basic_armor.png',
+ stats: { defense: 7, durability: 25, weight: 2.2, energyShield: 2 }
+ },
+ {
+ id: 'basic_armor_rare',
+ name: 'Basic Armor III',
+ type: 'armor',
+ rarity: 'rare',
+ price: 10000,
+ currency: 'credits',
+ description: 'Elite basic armor with energy shielding',
+ texture: 'assets/textures/armors/basic_armor.png',
+ stats: { defense: 10, durability: 30, weight: 2.5, energyShield: 5 }
+ },
+ {
+ id: 'basic_armor_epic',
+ name: 'Basic Armor IV',
+ type: 'armor',
+ rarity: 'epic',
+ price: 20000,
+ currency: 'credits',
+ description: 'Master basic armor with advanced protection',
+ texture: 'assets/textures/armors/basic_armor.png',
+ stats: { defense: 13, durability: 35, weight: 2.8, energyShield: 8 }
+ },
+ {
+ id: 'basic_armor_legendary',
+ name: 'Basic Armor V',
+ type: 'armor',
+ rarity: 'legendary',
+ price: 40000,
+ currency: 'credits',
+ description: 'Legendary basic armor with ultimate defense',
+ texture: 'assets/textures/armors/basic_armor.png',
+ stats: { defense: 17, durability: 40, weight: 3, energyShield: 12 }
+ },
+
+ // Medium Armor Variants
+ {
+ id: 'medium_armor_common',
+ name: 'Medium Armor',
+ type: 'armor',
+ rarity: 'common',
+ price: 5000,
+ currency: 'credits',
+ description: 'Balanced armor for general combat',
+ texture: 'assets/textures/armors/medium_armor.png',
+ stats: { defense: 12, durability: 40, weight: 5, energyShield: 3 }
+ },
+ {
+ id: 'medium_armor_uncommon',
+ name: 'Medium Armor II',
+ type: 'armor',
+ rarity: 'uncommon',
+ price: 12000,
+ currency: 'credits',
+ description: 'Enhanced medium armor with better shielding',
+ texture: 'assets/textures/armors/medium_armor.png',
+ stats: { defense: 15, durability: 50, weight: 5.5, energyShield: 6 }
+ },
+ {
+ id: 'medium_armor_rare',
+ name: 'Medium Armor III',
+ type: 'armor',
+ rarity: 'rare',
+ price: 30000,
+ currency: 'credits',
+ description: 'Elite medium armor with advanced systems',
+ texture: 'assets/textures/armors/medium_armor.png',
+ stats: { defense: 19, durability: 60, weight: 6, energyShield: 10 }
+ },
+ {
+ id: 'medium_armor_epic',
+ name: 'Medium Armor IV',
+ type: 'armor',
+ rarity: 'epic',
+ price: 60000,
+ currency: 'credits',
+ description: 'Master medium armor with superior protection',
+ texture: 'assets/textures/armors/medium_armor.png',
+ stats: { defense: 23, durability: 70, weight: 6.5, energyShield: 15 }
+ },
+ {
+ id: 'medium_armor_legendary',
+ name: 'Medium Armor V',
+ type: 'armor',
+ rarity: 'legendary',
+ price: 100000,
+ currency: 'credits',
+ description: 'Legendary medium armor with ultimate defense',
+ texture: 'assets/textures/armors/medium_armor.png',
+ stats: { defense: 28, durability: 80, weight: 7, energyShield: 20 }
+ },
+
+ // Heavy Armor Variants
+ {
+ id: 'heavy_armor_common',
+ name: 'Heavy Armor',
+ type: 'armor',
+ rarity: 'common',
+ price: 10000,
+ currency: 'credits',
+ description: 'Maximum protection with increased weight',
+ texture: 'assets/textures/armors/heavy_armor.png',
+ stats: { defense: 20, durability: 60, weight: 8, energyShield: 5 }
+ },
+ {
+ id: 'heavy_armor_uncommon',
+ name: 'Heavy Armor II',
+ type: 'armor',
+ rarity: 'uncommon',
+ price: 25000,
+ currency: 'credits',
+ description: 'Upgraded heavy armor with better energy shielding',
+ texture: 'assets/textures/armors/heavy_armor.png',
+ stats: { defense: 25, durability: 75, weight: 9, energyShield: 10 }
+ },
+ {
+ id: 'heavy_armor_rare',
+ name: 'Heavy Armor III',
+ type: 'armor',
+ rarity: 'rare',
+ price: 60000,
+ currency: 'credits',
+ description: 'Elite heavy armor with advanced protection systems',
+ texture: 'assets/textures/armors/heavy_armor.png',
+ stats: { defense: 31, durability: 90, weight: 10, energyShield: 16 }
+ },
+ {
+ id: 'heavy_armor_epic',
+ name: 'Heavy Armor IV',
+ type: 'armor',
+ rarity: 'epic',
+ price: 120000,
+ currency: 'credits',
+ description: 'Master heavy armor with superior defense capabilities',
+ texture: 'assets/textures/armors/heavy_armor.png',
+ stats: { defense: 37, durability: 105, weight: 11, energyShield: 23 }
+ },
+ {
+ id: 'heavy_armor_legendary',
+ name: 'Heavy Armor V',
+ type: 'armor',
+ rarity: 'legendary',
+ price: 200000,
+ currency: 'credits',
+ description: 'Legendary heavy armor with ultimate protection',
+ texture: 'assets/textures/armors/heavy_armor.png',
+ stats: { defense: 45, durability: 120, weight: 12, energyShield: 30 }
+ }
+ ],
+ cosmetics: [
+ {
+ id: 'blue_paint',
+ name: 'Blue Paint Job',
+ type: 'cosmetic',
+ rarity: 'common',
+ price: 100,
+ currency: 'gems',
+ description: 'Custom blue paint for your ship'
+ },
+ {
+ id: 'golden_trim',
+ name: 'Golden Trim',
+ type: 'cosmetic',
+ rarity: 'rare',
+ price: 500,
+ currency: 'gems',
+ description: 'Luxurious golden trim for your ship'
+ }
+ ],
+ consumables: [
+ {
+ id: 'health_kit',
+ name: 'Health Kit',
+ type: 'consumable',
+ rarity: 'common',
+ price: 15,
+ currency: 'credits',
+ description: 'Restores 50 health points',
+ texture: 'assets/textures/items/health_pack.png',
+ effect: { heal: 50 }
+ },
+ {
+ id: 'mega_health_kit',
+ name: 'Mega Health Kit',
+ type: 'consumable',
+ rarity: 'uncommon',
+ price: 50,
+ currency: 'credits',
+ description: 'Restores full health',
+ texture: 'assets/textures/items/mega_health_pack.png',
+ effect: { heal: 999 }
+ }
+ ],
+ buildings: [
+ {
+ id: 'command_center',
+ name: 'Command Center',
+ type: 'building',
+ rarity: 'uncommon',
+ price: 50000,
+ currency: 'credits',
+ description: 'Central command facility for base operations and coordination',
+ texture: 'assets/textures/base/command_center.png',
+ stats: { command: 10, efficiency: 15, capacity: 20 }
+ },
+ {
+ id: 'mining_facility',
+ name: 'Mining Facility',
+ type: 'building',
+ rarity: 'common',
+ price: 25000,
+ currency: 'credits',
+ description: 'Automated mining facility for resource extraction and processing',
+ texture: 'assets/textures/base/mining_facility.png',
+ stats: { production: 12, efficiency: 8, storage: 15 }
+ }
+ ],
+ materials: [
+ {
+ id: 'iron_ore',
+ name: 'Iron Ore',
+ type: 'material',
+ rarity: 'common',
+ price: 10,
+ currency: 'credits',
+ description: 'Basic metal ore used for crafting weapons and armor',
+ texture: 'assets/textures/items/iron_ore.png',
+ stackable: true
+ },
+ {
+ id: 'copper_ore',
+ name: 'Copper Ore',
+ type: 'material',
+ rarity: 'common',
+ price: 8,
+ currency: 'credits',
+ description: 'Conductive metal ore used for wiring and electronics',
+ texture: 'assets/textures/items/copper_ore.png',
+ stackable: true
+ },
+ {
+ id: 'tin_bar',
+ name: 'Tin Bar',
+ type: 'material',
+ rarity: 'common',
+ price: 12,
+ currency: 'credits',
+ description: 'Refined tin bar used for alloys and soldering',
+ texture: 'assets/textures/items/tin_bar.png',
+ stackable: true
+ },
+ {
+ id: 'copper_wire',
+ name: 'Copper Wire',
+ type: 'material',
+ rarity: 'common',
+ price: 8,
+ currency: 'credits',
+ description: 'Conductive wiring for electronic components',
+ texture: 'assets/textures/items/copper_wire.png',
+ stackable: true
+ },
+ {
+ id: 'energy_crystal',
+ name: 'Energy Crystal',
+ type: 'material',
+ rarity: 'uncommon',
+ price: 25,
+ currency: 'credits',
+ description: 'Crystallized energy source for power systems',
+ texture: 'assets/textures/items/energy_crystal.png',
+ stackable: true
+ },
+ {
+ id: 'leather',
+ name: 'Leather',
+ type: 'material',
+ rarity: 'common',
+ price: 12,
+ currency: 'credits',
+ description: 'Durable material for crafting armor and accessories',
+ texture: 'assets/textures/items/leather.png',
+ stackable: true
+ },
+ {
+ id: 'herbs',
+ name: 'Herbs',
+ type: 'material',
+ rarity: 'common',
+ price: 5,
+ currency: 'credits',
+ description: 'Medicinal herbs used for healing items',
+ texture: 'assets/textures/items/herbs.png',
+ stackable: true
+ },
+ {
+ id: 'bandages',
+ name: 'Bandages',
+ type: 'material',
+ rarity: 'common',
+ price: 3,
+ currency: 'credits',
+ description: 'Medical bandages used for crafting healing items',
+ texture: 'assets/textures/items/bandages.png',
+ stackable: true
+ },
+ {
+ id: 'steel_plate',
+ name: 'Steel Plate',
+ type: 'material',
+ rarity: 'uncommon',
+ price: 30,
+ currency: 'credits',
+ description: 'Reinforced steel plates used for advanced armor and ship components',
+ texture: 'assets/textures/items/stell_plate.png',
+ stackable: true
+ },
+ {
+ id: 'advanced_component',
+ name: 'Advanced Component',
+ type: 'material',
+ rarity: 'rare',
+ price: 75,
+ currency: 'credits',
+ description: 'High-tech component used for advanced equipment and upgrades',
+ texture: 'assets/textures/items/advanced_component.png',
+ stackable: true
+ },
+ {
+ id: 'advanced_components',
+ name: 'Advanced Components Set',
+ type: 'material',
+ rarity: 'epic',
+ price: 150,
+ currency: 'credits',
+ description: 'Complete set of advanced components for high-end manufacturing',
+ texture: 'assets/textures/items/advanced_components.png',
+ stackable: true
+ },
+ {
+ id: 'basic_circuitboard',
+ name: 'Basic Circuit Board',
+ type: 'material',
+ rarity: 'common',
+ price: 20,
+ currency: 'credits',
+ description: 'Basic electronic circuit board for simple devices',
+ texture: 'assets/textures/items/basic_circuitboard.png',
+ stackable: true
+ },
+ {
+ id: 'common_circuitboard',
+ name: 'Common Circuit Board',
+ type: 'material',
+ rarity: 'common',
+ price: 35,
+ currency: 'credits',
+ description: 'Standard circuit board for most electronic equipment',
+ texture: 'assets/textures/items/common_circuitboard.png',
+ stackable: true
+ },
+ {
+ id: 'advanced_circuitboard',
+ name: 'Advanced Circuit Board',
+ type: 'material',
+ rarity: 'rare',
+ price: 100,
+ currency: 'credits',
+ description: 'High-performance circuit board for advanced systems',
+ texture: 'assets/textures/items/advanced_circuitboard.png',
+ stackable: true
+ },
+ {
+ id: 'battery',
+ name: 'Battery',
+ type: 'material',
+ rarity: 'uncommon',
+ price: 35,
+ currency: 'credits',
+ description: 'Power batteries used for energy-based equipment',
+ texture: 'assets/textures/items/battery.png',
+ stackable: true
+ },
+ {
+ id: 'advanced_components',
+ name: 'Advanced Components',
+ type: 'material',
+ rarity: 'rare',
+ price: 150,
+ currency: 'credits',
+ description: 'Sophisticated electronic components for advanced ship systems',
+ stackable: true
+ }
+ ]
+ };
+
+ // Owned cosmetics
+ this.ownedCosmetics = [];
+
+ if (debugLogger) debugLogger.log('Economy constructor completed', {
+ initialCredits: this.credits,
+ initialGems: this.gems,
+ shopCategoriesCount: Object.keys(this.shopCategories).length,
+ totalShopItems: Object.values(this.shopItems).reduce((total, category) => total + category.length, 0)
+ });
+ }
+
+ async initialize() {
+ const debugLogger = window.debugLogger;
+ console.log('[ECONOMY] Economy system initializing');
+ if (debugLogger) await debugLogger.startStep('economyInitialize');
+
+ try {
+ if (debugLogger) await debugLogger.logStep('Economy initialization started', {
+ currentCredits: this.credits,
+ currentGems: this.gems,
+ transactionHistoryLength: this.transactionHistory.length
+ });
+
+ // Setup shop purchase event listeners
+ this.setupShopEventListeners();
+
+ // Initialize random shop system
+ this.initializeRandomShop();
+
+ console.log('[ECONOMY] Economy system initialization completed');
+ if (debugLogger) await debugLogger.endStep('economyInitialize');
+ } catch (error) {
+ console.error('[ECONOMY] Error during initialization:', error);
+ if (debugLogger) await debugLogger.errorEvent(error, 'Economy Initialize');
+ }
+ }
+
+ initializeRandomShop() {
+ const debugLogger = window.debugLogger;
+ console.log('[ECONOMY] Initializing random shop system');
+
+ // Shop data is now loaded through the main save/load system in load()
+ // Only initialize if no shop data exists (new game)
+ if (!this.lastShopRefresh || Object.keys(this.randomShopItems).length === 0) {
+ console.log('[ECONOMY] No shop data found, generating new shop');
+ this.refreshRandomShop();
+ } else {
+ console.log('[ECONOMY] Shop data already loaded from save');
+ // Start the refresh timer for ongoing updates
+ this.startShopRefreshTimer();
+ }
+
+ if (debugLogger) debugLogger.logStep('Random shop system initialized', {
+ hasShopData: Object.keys(this.randomShopItems).length > 0,
+ lastRefresh: this.lastShopRefresh
+ });
+ }
+
+ refreshRandomShop() {
+ const debugLogger = window.debugLogger;
+ console.log('[ECONOMY] Refreshing random shop items');
+
+ const categories = Object.keys(this.shopCategories);
+ this.randomShopItems = {};
+
+ categories.forEach(category => {
+ const categoryItems = this.shopItems[category] || [];
+ if (categoryItems.length === 0) return;
+
+ // Group items by their base ID (remove tier suffixes)
+ const baseItemGroups = {};
+ categoryItems.forEach(item => {
+ // Extract base ID by removing tier suffixes (common, uncommon, rare, epic, legendary)
+ const baseId = item.id.replace(/_(common|uncommon|rare|epic|legendary)$/, '');
+ if (!baseItemGroups[baseId]) {
+ baseItemGroups[baseId] = [];
+ }
+ baseItemGroups[baseId].push(item);
+ });
+
+ // Get all unique base items
+ const uniqueBaseItems = Object.keys(baseItemGroups);
+
+ // Randomly select up to MAX_ITEMS_PER_CATEGORY base items
+ const shuffledBaseItems = [...uniqueBaseItems].sort(() => Math.random() - 0.5);
+ const selectedBaseItems = shuffledBaseItems.slice(0, Math.min(this.MAX_ITEMS_PER_CATEGORY, shuffledBaseItems.length));
+
+ // For each selected base item, randomly pick one tier variant
+ this.randomShopItems[category] = selectedBaseItems.map(baseId => {
+ const variants = baseItemGroups[baseId];
+ const selectedVariant = variants[Math.floor(Math.random() * variants.length)];
+
+ return {
+ ...selectedVariant,
+ id: `${selectedVariant.id}_random_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
+ originalId: selectedVariant.id,
+ baseId: baseId,
+ price: Math.round(selectedVariant.price * (0.8 + Math.random() * 0.4)), // ±20% price variation
+ isRandomItem: true,
+ refreshTimestamp: Date.now()
+ };
+ });
+
+ // Reset purchase limits for this category
+ this.categoryPurchaseLimits[category] = 0;
+
+ if (debugLogger) debugLogger.logStep('Random shop category refreshed', {
+ category: category,
+ totalBaseItems: uniqueBaseItems.length,
+ baseItemsSelected: selectedBaseItems.length,
+ itemsGenerated: this.randomShopItems[category].length,
+ selectedVariants: this.randomShopItems[category].map(item => ({
+ baseId: item.baseId,
+ variantId: item.originalId,
+ rarity: item.rarity,
+ name: item.name
+ })),
+ purchaseLimitReset: true
+ });
+ });
+
+ this.lastShopRefresh = Date.now();
+ this.saveRandomShopData();
+ this.startShopRefreshTimer();
+
+ // Update UI if shop tab is active
+ this.updateShopUI();
+
+ this.game.showNotification('Shop inventory refreshed!', 'info', 3000);
+
+ if (debugLogger) debugLogger.logStep('Random shop refresh completed', {
+ categoriesRefreshed: categories.length,
+ totalItemsGenerated: Object.values(this.randomShopItems).flat().length,
+ nextRefresh: new Date(this.lastShopRefresh + this.SHOP_REFRESH_INTERVAL)
+ });
+ }
+
+ startShopRefreshTimer() {
+ // Clear existing timers
+ if (this.shopRefreshInterval) {
+ clearInterval(this.shopRefreshInterval);
+ }
+ if (this.shopHeartbeatInterval) {
+ clearInterval(this.shopHeartbeatInterval);
+ }
+
+ // Set up heartbeat timer for live countdown updates (every second)
+ this.shopHeartbeatInterval = setInterval(() => {
+ this.updateShopCountdown();
+ }, 1000);
+
+ // Set up refresh timer (every 2 hours)
+ this.shopRefreshInterval = setInterval(() => {
+ this.refreshRandomShop();
+ }, this.SHOP_REFRESH_INTERVAL);
+
+ console.log('[ECONOMY] Shop refresh timers started');
+ }
+
+ updateShopCountdown() {
+ // Only update if shop tab is active and using random shop
+ const shopTab = document.getElementById('shop-tab');
+ if (!shopTab || shopTab.style.display === 'none') {
+ return;
+ }
+
+ const activeCategory = document.querySelector('.shop-cat-btn.active')?.dataset.category || 'ships';
+ if (!this.randomShopItems[activeCategory]) {
+ return;
+ }
+
+ // Find and update the countdown element
+ const countdownElement = document.querySelector('.refresh-countdown');
+ if (countdownElement) {
+ countdownElement.innerHTML = `
+
+ Next refresh in: ${this.getShopRefreshCountdown()}
+ `;
+ }
+ }
+
+ stopShopRefreshTimer() {
+ if (this.shopRefreshInterval) {
+ clearInterval(this.shopRefreshInterval);
+ this.shopRefreshInterval = null;
+ }
+ if (this.shopHeartbeatInterval) {
+ clearInterval(this.shopHeartbeatInterval);
+ this.shopHeartbeatInterval = null;
+ }
+ console.log('[ECONOMY] Shop refresh timers stopped');
+ }
+
+ loadRandomShopData() {
+ try {
+ const savedData = localStorage.getItem('gso_random_shop');
+ if (savedData) {
+ const data = JSON.parse(savedData);
+ this.randomShopItems = data.randomShopItems || {};
+ this.categoryPurchaseLimits = data.categoryPurchaseLimits || {};
+ this.lastShopRefresh = data.lastShopRefresh || null;
+ console.log('[ECONOMY] Random shop data loaded from localStorage');
+ }
+ } catch (error) {
+ console.error('[ECONOMY] Error loading random shop data:', error);
+ this.randomShopItems = {};
+ this.categoryPurchaseLimits = {};
+ this.lastShopRefresh = null;
+ }
+ }
+ saveRandomShopData() {
+ try {
+ const data = {
+ randomShopItems: this.randomShopItems,
+ categoryPurchaseLimits: this.categoryPurchaseLimits,
+ lastShopRefresh: this.lastShopRefresh
+ };
+ localStorage.setItem('gso_random_shop', JSON.stringify(data));
+ console.log('[ECONOMY] Random shop data saved to localStorage');
+ } catch (error) {
+ console.error('[ECONOMY] Error saving random shop data:', error);
+ }
+ }
+
+ shouldRefreshShop() {
+ // Check if enough time has passed since the last actual refresh
+ if (!this.lastShopRefresh) {
+ return true; // No previous refresh, need to refresh
+ }
+
+ const now = Date.now();
+ const timeSinceRefresh = now - this.lastShopRefresh;
+ return timeSinceRefresh >= this.SHOP_REFRESH_INTERVAL;
+ }
+
+ getShopRefreshCountdown() {
+ const schedule = this.getRefreshSchedule();
+ const now = Date.now();
+
+ // Calculate time until next server refresh
+ const timeUntilRefresh = schedule.nextRefresh.getTime() - now;
+
+ if (timeUntilRefresh <= 0) {
+ return 'Refreshing...';
+ }
+
+ return this.formatTimeRemaining(timeUntilRefresh);
+ }
+
+ getNextRefreshTime() {
+ // Calculate the next refresh time based on server time
+ const now = Date.now();
+
+ if (!this.lastShopRefresh) {
+ // If no last refresh, next refresh is now + interval
+ return new Date(now + this.SHOP_REFRESH_INTERVAL);
+ }
+
+ // Calculate next refresh time
+ const timeSinceRefresh = now - this.lastShopRefresh;
+ const intervalsPassed = Math.floor(timeSinceRefresh / this.SHOP_REFRESH_INTERVAL);
+ const nextRefreshTime = this.lastShopRefresh + ((intervalsPassed + 1) * this.SHOP_REFRESH_INTERVAL);
+
+ return new Date(nextRefreshTime);
+ }
+
+ getRefreshSchedule() {
+ // Generate a consistent refresh schedule based on server time
+ const now = new Date();
+ const currentHour = now.getHours();
+
+ // Refresh at even hours: 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22
+ let nextRefreshHour;
+
+ if (currentHour % 2 === 0 && now.getMinutes() === 0 && now.getSeconds() === 0) {
+ // Exactly on an even hour at :00:00, next refresh is in 2 hours
+ nextRefreshHour = currentHour + 2;
+ } else {
+ // Find the next even hour
+ nextRefreshHour = currentHour + (2 - (currentHour % 2));
+ }
+
+ // Handle midnight wrap-around
+ if (nextRefreshHour >= 24) {
+ nextRefreshHour = nextRefreshHour % 24;
+ }
+
+ const nextRefresh = new Date(now);
+ nextRefresh.setHours(nextRefreshHour, 0, 0, 0);
+
+ // If the calculated time is in the past (shouldn't happen but just in case), add 2 hours
+ if (nextRefresh <= now) {
+ nextRefresh.setHours(nextRefresh.getHours() + 2);
+ }
+
+ console.log('[ECONOMY] Current time:', now.toLocaleTimeString());
+ console.log('[ECONOMY] Next refresh at:', nextRefresh.toLocaleTimeString());
+
+ return {
+ nextRefresh: nextRefresh,
+ interval: 2 * 60 * 60 * 1000, // 2 hours
+ schedule: 'Every 2 hours at even hours (2,4,6,8,10,12,14,16,18,20,22,00)'
+ };
+ }
+
+ formatTimeRemaining(timeUntilRefresh) {
+ const hours = Math.floor(timeUntilRefresh / (1000 * 60 * 60));
+ const minutes = Math.floor((timeUntilRefresh % (1000 * 60 * 60)) / (1000 * 60));
+ const seconds = Math.floor((timeUntilRefresh % (1000 * 60)) / 1000);
+
+ if (hours > 0) {
+ return `${hours}h ${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
+ } else {
+ return `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
+ }
+ }
+
+ getItemCategory(item) {
+ // Determine category based on item type
+ switch (item.type) {
+ case 'ship': return 'ships';
+ case 'weapon': return 'weapons';
+ case 'armor': return 'armors';
+ case 'material': return 'materials';
+ case 'cosmetic': return 'cosmetics';
+ case 'upgrade': return 'upgrades';
+ case 'consumable': return 'consumables';
+ case 'building': return 'buildings';
+ default:
+ // For random items, check if we can find them in shop categories
+ for (const [category, items] of Object.entries(this.shopItems)) {
+ if (items.some(shopItem => shopItem.id === item.originalId || shopItem.id === item.id)) {
+ return category;
+ }
+ }
+ return 'unknown';
+ }
+ }
+
+ setupShopEventListeners() {
+ const debugLogger = window.debugLogger;
+ console.log('[ECONOMY] Setting up shop event listeners');
+
+ // Remove existing listeners to prevent duplicates
+ const shopItemsElement = document.getElementById('shopItems');
+ if (shopItemsElement) {
+ // Clone the element to remove all event listeners
+ const newElement = shopItemsElement.cloneNode(true);
+ shopItemsElement.parentNode.replaceChild(newElement, shopItemsElement);
+
+ // Setup shop purchase button event delegation on the new element
+ newElement.addEventListener('click', (event) => {
+ const purchaseBtn = event.target.closest('.shop-item-purchase-btn');
+ if (purchaseBtn && !purchaseBtn.disabled) {
+ const itemId = purchaseBtn.getAttribute('data-item-id');
+ console.log(`[ECONOMY] Shop purchase button clicked for item: ${itemId}`);
+
+ if (itemId) {
+ this.purchaseItem(itemId, 1);
+ } else {
+ console.error('[ECONOMY] No item ID found on purchase button');
+ }
+ }
+ });
+
+ console.log('[ECONOMY] Shop event listeners setup completed');
+ } else {
+ console.error('[ECONOMY] Shop items element not found');
+ }
+ }
+
+ // Currency management
+ addCredits(amount, source = 'unknown') {
+ const oldCredits = this.credits;
+ this.credits += amount;
+ this.addTransaction('credits', amount, source);
+
+ console.log(`[ECONOMY] Added ${amount} credits from ${source}. New total: ${this.credits}`);
+
+ // Update UI to show new credit amount
+ if (this.game.systems.ui && this.game.shouldUpdateGUI()) {
+ this.game.systems.ui.updateUI();
+ }
+
+ this.game.showNotification(`+${this.game.formatNumber(amount)} credits`, 'success', 2000);
+ }
+
+ removeCredits(amount) {
+ const oldCredits = this.credits;
+
+ if (this.credits < amount) {
+ this.game.showNotification('Not enough credits!', 'error', 3000);
+ return false;
+ }
+
+ this.credits -= amount;
+ this.addTransaction('credits', -amount, 'purchase');
+
+ console.log(`[ECONOMY] Removed ${amount} credits. New total: ${this.credits}`);
+
+ // Update UI to show new credit amount
+ if (this.game.systems.ui && this.game.shouldUpdateGUI()) {
+ this.game.systems.ui.updateUI();
+ }
+
+ return true;
+ }
+
+ addGems(amount, source = 'unknown') {
+ const oldGems = this.gems;
+ this.gems += amount;
+ this.addTransaction('gems', amount, source);
+
+ console.log(`[ECONOMY] Added ${amount} gems from ${source}. New total: ${this.gems}`);
+
+ // Update UI to show new gem amount
+ if (this.game.systems.ui && this.game.shouldUpdateGUI()) {
+ this.game.systems.ui.updateUI();
+ }
+
+ this.game.showNotification(`+${this.game.formatNumber(amount)} gems`, 'success', 2000);
+ }
+
+ removeGems(amount) {
+ const oldGems = this.gems;
+
+ if (this.gems < amount) {
+ this.game.showNotification('Not enough gems!', 'error', 3000);
+ return false;
+ }
+
+ this.gems -= amount;
+ this.addTransaction('gems', -amount, 'purchase');
+
+ console.log(`[ECONOMY] Removed ${amount} gems. New total: ${this.gems}`);
+
+ // Update UI to show new gem amount
+ if (this.game.systems.ui && this.game.shouldUpdateGUI()) {
+ this.game.systems.ui.updateUI();
+ }
+
+ return true;
+ }
+
+ // Transaction tracking
+ addTransaction(currency, amount, source) {
+ const debugLogger = window.debugLogger;
+ const transaction = {
+ id: Date.now() + Math.random().toString(36).substr(2, 9),
+ currency: currency,
+ amount: amount,
+ source: source,
+ timestamp: Date.now()
+ };
+
+ this.transactionHistory.unshift(transaction);
+
+ // Keep only last 100 transactions
+ const oldLength = this.transactionHistory.length;
+ if (this.transactionHistory.length > 100) {
+ this.transactionHistory = this.transactionHistory.slice(0, 100);
+ }
+
+ if (debugLogger) debugLogger.logStep('Transaction recorded', {
+ transactionId: transaction.id,
+ currency: currency,
+ amount: amount,
+ source: source,
+ timestamp: transaction.timestamp,
+ historyLength: this.transactionHistory.length,
+ wasTrimmed: oldLength > 100,
+ removedCount: oldLength > 100 ? oldLength - 100 : 0
+ });
+ }
+
+ // Shop functionality
+ purchaseItem(itemId, quantity = 1) {
+ const debugLogger = window.debugLogger;
+ const item = this.findShopItem(itemId);
+
+ if (!item) {
+ if (debugLogger) debugLogger.logStep('Item purchase failed - item not found', {
+ itemId: itemId,
+ quantity: quantity
+ });
+ this.game.showNotification('Item not found in shop', 'error', 3000);
+ return false;
+ }
+
+ const totalCost = item.price * quantity;
+ const currency = item.currency;
+ const oldCredits = this.credits;
+ const oldGems = this.gems;
+
+ if (debugLogger) debugLogger.logStep('Item purchase attempted', {
+ itemId: itemId,
+ itemName: item.name,
+ itemType: item.type,
+ quantity: quantity,
+ unitPrice: item.price,
+ totalCost: totalCost,
+ currency: currency,
+ currentCredits: oldCredits,
+ currentGems: oldGems
+ });
+
+ // Check if player can afford
+ if (currency === 'credits' && this.credits < totalCost) {
+ if (debugLogger) debugLogger.logStep('Item purchase failed - insufficient credits', {
+ totalCost: totalCost,
+ currentCredits: oldCredits,
+ deficit: totalCost - oldCredits
+ });
+ this.game.showNotification('Not enough credits!', 'error', 3000);
+ return false;
+ }
+
+ if (currency === 'gems' && this.gems < totalCost) {
+ if (debugLogger) debugLogger.logStep('Item purchase failed - insufficient gems', {
+ totalCost: totalCost,
+ currentGems: oldGems,
+ deficit: totalCost - oldGems
+ });
+ this.game.showNotification('Not enough gems!', 'error', 3000);
+ return false;
+ }
+
+ // Check if already owns this cosmetic
+ if (item.type === 'cosmetic' && this.ownedCosmetics.includes(item.id)) {
+ this.game.showNotification('You already own this cosmetic!', 'error', 3000);
+ return false;
+ }
+
+ // Process payment and give item based on type
+ if (currency === 'credits') {
+ this.credits -= totalCost;
+ } else if (currency === 'gems') {
+ this.gems -= totalCost;
+ }
+
+ switch (item.type) {
+ case 'ship':
+ this.purchaseShip(item, quantity);
+ break;
+ case 'cosmetic':
+ this.purchaseCosmetic(item, quantity);
+ break;
+ case 'consumable':
+ this.purchaseConsumable(item, quantity);
+ break;
+ case 'material':
+ this.purchaseMaterial(item, quantity);
+ break;
+ default:
+ console.warn(`[ECONOMY] Unknown item type: ${item.type}`);
+ return false;
+ }
+
+ // Random shop items don't have purchase limits - unlimited purchases allowed
+ if (item.isRandomItem) {
+ const category = this.getItemCategory(item);
+
+ if (debugLogger) debugLogger.logStep('Random shop purchase completed', {
+ itemId: itemId,
+ itemName: item.name,
+ category: category
+ });
+ }
+
+ // Update inventory UI to show the new item
+ if (this.game.systems.ui && this.game.systems.ui.updateInventory) {
+ this.game.systems.ui.updateInventory();
+ } else if (this.game.systems.ui && this.game.systems.ui.updateUI) {
+ this.game.systems.ui.updateUI();
+ } else {
+ console.warn('No UI update method available after purchase');
+ }
+
+ if (debugLogger) debugLogger.logStep('Item purchase completed successfully', {
+ itemId: itemId,
+ itemName: item.name,
+ itemType: item.type,
+ quantity: quantity,
+ totalCost: totalCost,
+ currency: currency,
+ oldCredits: oldCredits,
+ newCredits: this.credits,
+ oldGems: oldGems,
+ newGems: this.gems
+ });
+
+ // Update UI without calling updateShopUI to avoid circular updates
+ return true;
+ }
+
+ findShopItem(itemId) {
+ const debugLogger = window.debugLogger;
+
+ for (const category of Object.values(this.shopItems)) {
+ const item = category.find(i => i.id === itemId);
+ if (item) {
+ if (debugLogger) debugLogger.logStep('Shop item found', {
+ itemId: itemId,
+ itemName: item.name,
+ itemType: item.type,
+ itemPrice: item.price,
+ itemCurrency: item.currency
+ });
+ return item;
+ }
+ }
+
+ if (debugLogger) debugLogger.logStep('Shop item not found', {
+ itemId: itemId,
+ availableCategories: Object.keys(this.shopItems)
+ });
+ return null;
+ }
+
+ purchaseShip(ship) {
+ const debugLogger = window.debugLogger;
+ const player = this.game.systems.player;
+ const oldShipName = player.ship.name;
+ const oldShipClass = player.ship.class;
+ const oldAttributes = { ...player.attributes };
+
+ if (debugLogger) debugLogger.logStep('Ship purchase processing', {
+ shipId: ship.id,
+ shipName: ship.name,
+ shipType: ship.type,
+ shipStats: ship.stats,
+ oldShipName: oldShipName,
+ oldShipClass: oldShipClass
+ });
+
+ // Add ship to player's ship collection
+ // Add ship to base gallery
+ if (this.game.systems.baseSystem) {
+ this.game.systems.baseSystem.addShipToGallery(ship);
+ if (debugLogger) debugLogger.logStep('Ship added to base gallery');
+ }
+
+ // Replace current ship (no upgrade functionality)
+ player.ship.name = ship.name;
+ player.ship.class = ship.type;
+ player.ship.level = 1; // Reset level for new ship
+ player.ship.texture = ship.texture; // Copy texture for image display
+
+ // Set ship-specific stats (not just attributes)
+ if (ship.stats.health) {
+ player.ship.maxHealth = ship.stats.health;
+ player.ship.health = ship.stats.health;
+ }
+ if (ship.stats.attack) {
+ player.ship.attack = ship.stats.attack;
+ }
+ if (ship.stats.defense) {
+ player.ship.defense = ship.stats.defense;
+ }
+ if (ship.stats.speed) {
+ player.ship.speed = ship.stats.speed;
+ }
+ if (ship.stats.criticalChance) {
+ player.ship.criticalChance = ship.stats.criticalChance;
+ }
+ if (ship.stats.criticalDamage) {
+ player.ship.criticalDamage = ship.stats.criticalDamage;
+ }
+
+ // Also update player attributes for compatibility
+ for (const [stat, value] of Object.entries(ship.stats)) {
+ if (player.attributes[stat] !== undefined) {
+ player.attributes[stat] = value; // Replace stats instead of adding
+ }
+ }
+
+ player.updateUI();
+
+ // Also update ShipSystem display
+ if (this.game.systems.ship && this.game.systems.ship.updateCurrentShipDisplay) {
+ this.game.systems.ship.updateCurrentShipDisplay();
+ }
+
+ if (debugLogger) debugLogger.logStep('Ship purchase completed', {
+ shipId: ship.id,
+ oldShipName: oldShipName,
+ newShipName: player.ship.name,
+ oldShipClass: oldShipClass,
+ newShipClass: player.ship.class,
+ attributeChanges: Object.entries(ship.stats).map(([stat, value]) => ({
+ stat: stat,
+ oldValue: oldAttributes[stat],
+ newValue: player.attributes[stat],
+ change: value
+ }))
+ });
+ }
+
+ purchaseCosmetic(cosmetic) {
+ const debugLogger = window.debugLogger;
+ const oldOwnedCount = this.ownedCosmetics.length;
+
+ // Add to owned cosmetics
+ this.ownedCosmetics.push(cosmetic.id);
+ this.game.showNotification(`Cosmetic unlocked: ${cosmetic.name}`, 'success', 3000);
+
+ if (debugLogger) debugLogger.logStep('Cosmetic purchase completed', {
+ cosmeticId: cosmetic.id,
+ cosmeticName: cosmetic.name,
+ oldOwnedCount: oldOwnedCount,
+ newOwnedCount: this.ownedCosmetics.length,
+ totalOwnedCosmetics: this.ownedCosmetics.length
+ });
+ }
+
+ purchaseConsumable(consumable, quantity) {
+ const debugLogger = window.debugLogger;
+ const inventory = this.game.systems.inventory;
+ const oldInventorySize = inventory ? inventory.items.length : 0;
+
+ if (debugLogger) debugLogger.logStep('Consumable purchase processing', {
+ consumableId: consumable.id,
+ consumableName: consumable.name,
+ quantity: quantity,
+ effect: consumable.effect,
+ oldInventorySize: oldInventorySize
+ });
+
+ // Add to inventory
+ for (let i = 0; i < quantity; i++) {
+ const item = {
+ id: `${consumable.id}_${Date.now()}_${i}`,
+ name: consumable.name,
+ type: 'consumable',
+ rarity: consumable.rarity,
+ quantity: 1,
+ description: consumable.description,
+ consumable: true,
+ effect: consumable.effect
+ };
+
+ inventory.addItem(item);
+ }
+
+ if (debugLogger) debugLogger.logStep('Consumable purchase completed', {
+ consumableId: consumable.id,
+ quantity: quantity,
+ oldInventorySize: oldInventorySize,
+ newInventorySize: inventory ? inventory.items.length : 0,
+ itemsAdded: quantity
+ });
+ }
+
+ purchaseEquipment(equipment, quantity = 1) {
+ const inventory = this.game.systems.inventory;
+ const oldInventorySize = inventory ? inventory.items.length : 0;
+
+ // Add to inventory
+ for (let i = 0; i < quantity; i++) {
+ const item = {
+ id: `${equipment.id}_${Date.now()}_${i}`,
+ name: equipment.name,
+ type: equipment.type,
+ rarity: equipment.rarity,
+ quantity: 1,
+ description: equipment.description,
+ texture: equipment.texture,
+ stats: equipment.stats || {},
+ equipable: true
+ };
+
+ inventory.addItem(item);
+ }
+ }
+
+ purchaseMaterial(material, quantity = 1) {
+ const inventory = this.game.systems.inventory;
+ const debugLogger = window.debugLogger;
+
+ if (!inventory) {
+ console.error('[ECONOMY] Inventory system not available for material purchase');
+ return false;
+ }
+
+ try {
+ const oldInventorySize = inventory.items.length;
+ console.log(`[DEBUG] Inventory state before purchase: ${oldInventorySize} items`);
+
+ // Create item object for inventory
+ const item = {
+ id: material.id,
+ name: material.name,
+ type: 'material',
+ rarity: material.rarity || 'common',
+ quantity: quantity,
+ description: material.description || `A ${material.name} material`,
+ stackable: true,
+ stats: {},
+ equipable: false,
+ slot: null
+ };
+
+ console.log(`[DEBUG] Adding item to inventory: ${item.name}`);
+ const added = inventory.addItem(item);
+
+ console.log(`[DEBUG] addItem() returned: ${added}, new inventory size: ${inventory.items.length}`);
+
+ if (debugLogger) debugLogger.logStep('Material purchase completed', {
+ materialId: material.id,
+ materialName: material.name,
+ quantity: quantity,
+ oldInventorySize: oldInventorySize,
+ newInventorySize: inventory.items.length,
+ addedSuccessfully: added
+ });
+
+ if (!added) {
+ console.error('Failed to add material to inventory');
+ return false;
+ } else {
+ // Update inventory UI to show the new material
+ if (this.game.systems.ui && this.game.systems.ui.updateInventory) {
+ this.game.systems.ui.updateInventory();
+ } else if (this.game.systems.ui && this.game.systems.ui.updateUI) {
+ this.game.systems.ui.updateUI();
+ } else {
+ console.warn('No UI update method available after material purchase');
+ }
+ console.log('[DEBUG] Material purchase completed and UI update called');
+ return true;
+ }
+ } catch (error) {
+ console.error('Exception in purchaseMaterial:', error);
+ if (debugLogger) debugLogger.errorEvent('Purchase Material Exception', error, {
+ materialId: material.id,
+ materialName: material.name
+ });
+ return false;
+ }
+ }
+
+ // Reward generation
+ generateRewards(difficulty = 'normal', source = 'dungeon') {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('generateRewards', {
+ difficulty: difficulty,
+ source: source
+ });
+
+ const baseRewards = {
+ easy: { credits: [50, 150], gems: [0, 1], experience: [20, 50] },
+ normal: { credits: [100, 300], gems: [1, 3], experience: [50, 100] },
+ hard: { credits: [200, 500], gems: [2, 5], experience: [100, 200] },
+ extreme: { credits: [500, 1000], gems: [5, 10], experience: [200, 400] }
+ };
+
+ const rewards = baseRewards[difficulty] || baseRewards.normal;
+ const generatedRewards = {};
+
+ // Generate credits
+ generatedRewards.credits = Math.floor(
+ Math.random() * (rewards.credits[1] - rewards.credits[0] + 1) + rewards.credits[0]
+ );
+
+ // Generate gems
+ generatedRewards.gems = Math.floor(
+ Math.random() * (rewards.gems[1] - rewards.gems[0] + 1) + rewards.gems[0]
+ );
+
+ // Generate experience
+ generatedRewards.experience = Math.floor(
+ Math.random() * (rewards.experience[1] - rewards.experience[0] + 1) + rewards.experience[0]
+ );
+
+ // Bonus for premium currency (rare)
+ if (Math.random() < 0.05) { // 5% chance
+ generatedRewards.premiumCurrency = 1;
+ }
+
+ if (debugLogger) debugLogger.endStep('generateRewards', {
+ difficulty: difficulty,
+ source: source,
+ generatedRewards: generatedRewards,
+ rewardRanges: rewards
+ });
+
+ return generatedRewards;
+ }
+
+ giveRewards(rewards, source = 'unknown') {
+ const debugLogger = window.debugLogger;
+ const player = this.game.systems.player;
+ const oldCredits = this.credits;
+ const oldGems = this.gems;
+ const oldExperience = player.stats.experience;
+ const oldLevel = player.stats.level;
+
+ if (debugLogger) debugLogger.startStep('giveRewards', {
+ source: source,
+ rewards: rewards,
+ oldPlayerState: {
+ credits: oldCredits,
+ gems: oldGems,
+ experience: oldExperience,
+ level: oldLevel
+ }
+ });
+
+ let totalRewards = [];
+
+ // Add credits
+ if (rewards.credits > 0) {
+ this.addCredits(rewards.credits, source);
+ totalRewards.push(`${rewards.credits} credits`);
+ }
+
+ // Add gems
+ if (rewards.gems > 0) {
+ this.addGems(rewards.gems, source);
+ totalRewards.push(`${rewards.gems} gems`);
+ }
+
+ // Add premium currency
+ if (rewards.premiumCurrency > 0) {
+ this.addPremiumCurrency(rewards.premiumCurrency, source);
+ totalRewards.push(`${rewards.premiumCurrency} premium currency`);
+ }
+
+ // Add experience
+ if (rewards.experience > 0) {
+ player.addExperience(rewards.experience);
+ totalRewards.push(`${rewards.experience} experience`);
+ }
+
+ // Add materials
+ if (rewards.materials && rewards.materials.length > 0) {
+ const inventory = this.game.systems.inventory;
+ alert(`[DUNGEON DEBUG] Adding ${rewards.materials.length} materials to inventory\nInventory available: ${!!inventory}\nMaterials: ${JSON.stringify(rewards.materials, null, 2)}`);
+
+ for (const material of rewards.materials) {
+ // Create proper item object like in purchaseMaterial
+ const itemObject = {
+ id: material.id,
+ name: material.id.replace('_', ' ').replace(/\b\w/g, l => l.toUpperCase()), // Convert ID to readable name
+ type: 'material',
+ rarity: 'common',
+ quantity: material.quantity,
+ description: `A ${material.id.replace('_', ' ')} material`,
+ stackable: true,
+ stats: {},
+ equipable: false,
+ slot: null
+ };
+
+ alert(`[DUNGEON DEBUG] Fixed! Adding material as full item object:\n${JSON.stringify(itemObject, null, 2)}`);
+ inventory.addItem(itemObject);
+ totalRewards.push(`${material.quantity}x ${material.id}`);
+ }
+ }
+
+ // Add items
+ if (rewards.items && rewards.items.length > 0) {
+ const inventory = this.game.systems.inventory;
+ for (const item of rewards.items) {
+ inventory.addItem(item.id, item.quantity || 1);
+ totalRewards.push(`${item.quantity || 1}x ${item.id}`);
+ }
+ }
+
+ // Show reward notification
+ if (totalRewards.length > 0) {
+ const rewardText = totalRewards.join(', ');
+ this.game.showNotification(`Rewards: ${rewardText}`, 'success', 3000);
+ }
+
+ if (debugLogger) debugLogger.endStep('giveRewards', {
+ source: source,
+ rewardsGiven: rewards,
+ totalRewardsText: totalRewards.join(', '),
+ currencyChanges: {
+ credits: { old: oldCredits, new: this.credits, change: this.credits - oldCredits },
+ gems: { old: oldGems, new: this.gems, change: this.gems - oldGems }
+ },
+ playerChanges: {
+ experience: { old: oldExperience, new: player.stats.experience, change: player.stats.experience - oldExperience },
+ level: { old: oldLevel, new: player.stats.level, leveledUp: player.stats.level > oldLevel }
+ }
+ });
+ }
+
+ // Daily rewards and bonuses
+ claimDailyReward() {
+ const debugLogger = window.debugLogger;
+ const lastClaim = localStorage.getItem('lastDailyReward');
+ const today = new Date().toDateString();
+
+ if (debugLogger) debugLogger.startStep('claimDailyReward', {
+ lastClaim: lastClaim,
+ today: today,
+ alreadyClaimed: lastClaim === today
+ });
+
+ if (lastClaim === today) {
+ this.game.showNotification('Daily reward already claimed!', 'warning', 3000);
+ if (debugLogger) debugLogger.endStep('claimDailyReward', {
+ success: false,
+ reason: 'Already claimed today'
+ });
+ return false;
+ }
+
+ // Calculate reward based on consecutive days
+ const consecutiveDays = parseInt(localStorage.getItem('consecutiveDailyRewards') || '0') + 1;
+ const baseReward = 100;
+ const bonusMultiplier = Math.min(consecutiveDays * 0.1, 2); // Max 2x bonus
+ const credits = Math.floor(baseReward * (1 + bonusMultiplier));
+ const gems = Math.min(Math.floor(consecutiveDays / 7), 5); // 1 gem per week, max 5
+
+ if (debugLogger) debugLogger.logStep('Daily reward calculation', {
+ consecutiveDays: consecutiveDays,
+ baseReward: baseReward,
+ bonusMultiplier: bonusMultiplier,
+ creditsReward: credits,
+ gemsReward: gems
+ });
+
+ // Give rewards
+ this.addCredits(credits, 'daily_reward');
+ this.addGems(gems, 'daily_reward');
+
+ // Update daily reward tracking
+ localStorage.setItem('lastDailyReward', today);
+ localStorage.setItem('consecutiveDailyRewards', consecutiveDays.toString());
+
+ // Show notification
+ const rewardText = `${credits} credits${gems > 0 ? ` and ${gems} gems` : ''}`;
+ this.game.showNotification(`Daily reward claimed: ${rewardText}! (${consecutiveDays} day streak)`, 'success', 4000);
+
+ if (debugLogger) debugLogger.endStep('claimDailyReward', {
+ success: true,
+ consecutiveDays: consecutiveDays,
+ creditsReward: credits,
+ gemsReward: gems,
+ rewardText: rewardText,
+ newLastClaim: today,
+ newConsecutiveDays: consecutiveDays
+ });
+
+ return true;
+ }
+
+ // UI updates
+ updateUI() {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('updateUI', {
+ currentCredits: this.credits,
+ currentGems: this.gems,
+ currentPremiumCurrency: this.premiumCurrency
+ });
+
+ // Update resource displays
+ const creditsElement = document.getElementById('credits');
+ if (creditsElement) {
+ creditsElement.textContent = this.game.formatNumber(this.credits);
+ }
+
+ const gemsElement = document.getElementById('gems');
+ if (gemsElement) {
+ gemsElement.textContent = this.game.formatNumber(this.gems);
+ }
+
+ const premiumElement = document.getElementById('premiumCurrency');
+ if (premiumElement) {
+ premiumElement.textContent = this.game.formatNumber(this.premiumCurrency);
+ }
+
+ // Update shop UI when shop tab is active
+ this.updateShopUI();
+
+ if (debugLogger) debugLogger.endStep('updateUI', {
+ creditsUpdated: !!creditsElement,
+ gemsUpdated: !!gemsElement,
+ premiumUpdated: !!premiumElement,
+ shopUIUpdated: true
+ });
+ }
+
+ // Reset economy (for new game)
+ reset() {
+ const debugLogger = window.debugLogger;
+ const oldState = {
+ credits: this.credits,
+ gems: this.gems,
+ premiumCurrency: this.premiumCurrency,
+ transactionCount: this.transactions.length,
+ ownedCosmeticsCount: this.ownedCosmetics.length
+ };
+
+ if (debugLogger) debugLogger.startStep('reset', {
+ oldState: oldState
+ });
+
+ this.credits = 1000;
+ this.gems = 10;
+ this.premiumCurrency = 0;
+ this.transactions = [];
+ this.ownedCosmetics = [];
+
+ // Reset daily rewards
+ localStorage.removeItem('lastDailyReward');
+ localStorage.removeItem('consecutiveDailyRewards');
+
+ if (debugLogger) debugLogger.endStep('reset', {
+ oldState: oldState,
+ newState: {
+ credits: this.credits,
+ gems: this.gems,
+ premiumCurrency: this.premiumCurrency,
+ transactionCount: this.transactions.length,
+ ownedCosmeticsCount: this.ownedCosmetics.length
+ }
+ });
+ }
+
+ // Clear all data
+ clear() {
+ const debugLogger = window.debugLogger;
+ const oldState = {
+ credits: this.credits,
+ gems: this.gems,
+ premiumCurrency: this.premiumCurrency,
+ transactionCount: this.transactions.length,
+ ownedCosmeticsCount: this.ownedCosmetics.length
+ };
+
+ if (debugLogger) debugLogger.startStep('clear', {
+ oldState: oldState
+ });
+
+ this.credits = 0;
+ this.gems = 0;
+ this.premiumCurrency = 0;
+ this.transactions = [];
+ this.ownedCosmetics = [];
+
+ if (debugLogger) debugLogger.endStep('clear', {
+ oldState: oldState,
+ newState: {
+ credits: this.credits,
+ gems: this.gems,
+ premiumCurrency: this.premiumCurrency,
+ transactionCount: this.transactions.length,
+ ownedCosmeticsCount: this.ownedCosmetics.length
+ }
+ });
+ }
+
+ updateShopUI() {
+ const debugLogger = window.debugLogger;
+ const shopItemsElement = document.getElementById('shopItems');
+
+ if (!shopItemsElement) {
+ if (debugLogger) debugLogger.log('updateShopUI: Shop items element not found');
+ return;
+ }
+
+ const activeCategory = document.querySelector('.shop-cat-btn.active')?.dataset.category || 'ships';
+
+ // Always use random shop items when the system is available
+ // If no random items exist for this category, trigger a refresh
+ if (!this.randomShopItems[activeCategory] || this.randomShopItems[activeCategory].length === 0) {
+ console.log(`[ECONOMY] No random items for ${activeCategory}, triggering refresh`);
+ this.refreshRandomShop();
+ }
+
+ const items = this.randomShopItems[activeCategory] || [];
+ const isRandomShop = true; // Always random shop when system is available
+
+ if (debugLogger) debugLogger.startStep('updateShopUI', {
+ activeCategory: activeCategory,
+ itemCount: items.length,
+ isRandomShop: isRandomShop,
+ currentCredits: this.credits,
+ currentGems: this.gems,
+ currentPremiumCurrency: this.premiumCurrency
+ });
+
+ shopItemsElement.innerHTML = '';
+
+ // Add shop refresh info if using random shop
+ if (isRandomShop) {
+ const refreshInfo = document.createElement('div');
+ refreshInfo.className = 'shop-refresh-info';
+ refreshInfo.innerHTML = `
+
+
+
+ Next refresh in: ${this.getShopRefreshCountdown()}
+
+
+ `;
+ shopItemsElement.appendChild(refreshInfo);
+ }
+
+ items.forEach(item => {
+ const itemElement = document.createElement('div');
+ itemElement.className = 'shop-item';
+
+ const canAfford = this.canAfford(item);
+ const isOwned = item.type === 'cosmetic' && this.ownedCosmetics.includes(item.id);
+
+ itemElement.innerHTML = `
+
+ ${item.texture ?
+ `
+
+
` : ''}
+
+
${item.name}
+
${item.description}
+ ${item.stats ? `
+
+ ${Object.entries(item.stats).map(([stat, value]) =>
+ `
${stat}: ${value}
`
+ ).join('')}
+
+ ` : ''}
+
${this.formatPrice(item)}
+
${item.rarity}
+
+
+
+ ${isOwned ? 'Owned' : canAfford ? 'Purchase' : 'Cannot Afford'}
+
+ `;
+
+ shopItemsElement.appendChild(itemElement);
+
+ if (debugLogger) debugLogger.logStep('Shop item rendered', {
+ itemId: item.id,
+ itemName: item.name,
+ itemCategory: activeCategory,
+ itemPrice: item.price,
+ itemCurrency: item.currency,
+ canAfford: canAfford,
+ isOwned: isOwned
+ });
+ });
+
+ // Re-setup event listeners since we just recreated all the buttons
+ this.setupShopEventListeners();
+
+ if (debugLogger) debugLogger.endStep('updateShopUI', {
+ activeCategory: activeCategory,
+ itemsRendered: items.length,
+ shopUIUpdated: true
+ });
+ }
+
+ // Testing and utility methods
+ testPurchase(itemId) {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('testPurchase', {
+ itemId: itemId
+ });
+
+ const item = this.findShopItem(itemId);
+ if (!item) {
+ if (debugLogger) debugLogger.endStep('testPurchase', {
+ success: false,
+ reason: 'Item not found',
+ itemId: itemId
+ });
+ return false;
+ }
+
+ const result = this.purchaseItem(itemId);
+
+ if (debugLogger) debugLogger.endStep('testPurchase', {
+ success: result,
+ itemId: itemId,
+ itemName: item.name,
+ itemCategory: item.type
+ });
+
+ return result;
+ }
+
+ formatItemStats(item) {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('formatItemStats', {
+ itemId: item.id,
+ itemType: item.type
+ });
+
+ let stats = [];
+
+ if (item.stats) {
+ for (const [stat, value] of Object.entries(item.stats)) {
+ stats.push(`${stat}: +${value}`);
+ }
+ }
+
+ if (item.effect) {
+ if (item.effect.attackMultiplier) {
+ stats.push(`Attack: x${item.effect.attackMultiplier}`);
+ }
+ if (item.effect.defense) {
+ stats.push(`Defense: +${item.effect.defense}`);
+ }
+ if (item.effect.healing) {
+ stats.push(`Healing: +${item.effect.healing}`);
+ }
+ if (item.effect.energyRestore) {
+ stats.push(`Energy: +${item.effect.energyRestore}`);
+ }
+ }
+
+ const formattedStats = stats.length > 0 ? stats.join(', ') : 'No special stats';
+
+ if (debugLogger) debugLogger.endStep('formatItemStats', {
+ itemId: item.id,
+ formattedStats: formattedStats,
+ statCount: stats.length
+ });
+
+ return formattedStats;
+ }
+
+ // Save and load
+ save() {
+ const debugLogger = window.debugLogger;
+
+ // if (debugLogger) debugLogger.startStep('save', {
+ // currentCredits: this.credits,
+ // currentGems: this.gems,
+ // currentPremiumCurrency: this.premiumCurrency,
+ // transactionCount: this.transactions.length,
+ // ownedCosmeticsCount: this.ownedCosmetics.length
+ // });
+
+ const saveData = {
+ credits: this.credits,
+ gems: this.gems,
+ premiumCurrency: this.premiumCurrency,
+ transactions: this.transactions,
+ ownedCosmetics: this.ownedCosmetics,
+ // Include shop data with timestamp for proper refresh calculation
+ shopData: {
+ randomShopItems: this.randomShopItems,
+ categoryPurchaseLimits: this.categoryPurchaseLimits,
+ lastShopRefresh: this.lastShopRefresh,
+ saveTimestamp: Date.now() // When this save was created
+ }
+ };
+
+ // if (debugLogger) debugLogger.endStep('save', {
+ // saveDataSize: JSON.stringify(saveData).length,
+ // economyState: saveData
+ // });
+
+ return saveData;
+ }
+
+ load(data) {
+ const debugLogger = window.debugLogger;
+ const oldState = {
+ credits: this.credits,
+ gems: this.gems,
+ premiumCurrency: this.premiumCurrency,
+ transactionCount: this.transactions.length,
+ ownedCosmeticsCount: this.ownedCosmetics.length
+ };
+
+ // if (debugLogger) debugLogger.startStep('load', {
+ // oldState: oldState,
+ // loadData: data
+ // });
+
+ try {
+ this.credits = data.credits || 0;
+ this.gems = data.gems || 0;
+ this.premiumCurrency = data.premiumCurrency || 0;
+ this.transactions = data.transactions || [];
+ this.ownedCosmetics = data.ownedCosmetics || [];
+
+ // Load shop data and calculate if refresh is needed
+ if (data.shopData) {
+ console.log('[ECONOMY] Loading shop data from save');
+
+ // Restore shop data
+ this.randomShopItems = data.shopData.randomShopItems || {};
+ this.categoryPurchaseLimits = data.shopData.categoryPurchaseLimits || {};
+ this.lastShopRefresh = data.shopData.lastShopRefresh || null;
+
+ // Calculate if shop should have refreshed while game was closed
+ const saveTimestamp = data.shopData.saveTimestamp || Date.now();
+ const currentTime = Date.now();
+ const timeSinceSave = currentTime - saveTimestamp;
+
+ if (this.lastShopRefresh) {
+ const timeSinceLastRefresh = currentTime - this.lastShopRefresh;
+ const totalIntervalsPassed = Math.floor(timeSinceLastRefresh / this.SHOP_REFRESH_INTERVAL);
+
+ if (totalIntervalsPassed > 0) {
+ console.log(`[ECONOMY] Shop missed ${totalIntervalsPassed} refresh(es) while game was closed, refreshing now`);
+ this.refreshRandomShop();
+ } else {
+ console.log('[ECONOMY] Shop items still valid, no refresh needed');
+ }
+ } else {
+ console.log('[ECONOMY] No previous shop refresh, generating new shop');
+ this.refreshRandomShop();
+ }
+ } else {
+ console.log('[ECONOMY] No shop data in save, initializing new shop');
+ this.refreshRandomShop();
+ }
+
+ if (debugLogger) debugLogger.endStep('load', {
+ success: true,
+ oldState: oldState,
+ newState: {
+ credits: this.credits,
+ gems: this.gems,
+ premiumCurrency: this.premiumCurrency,
+ transactionCount: this.transactions.length,
+ ownedCosmeticsCount: this.ownedCosmetics.length
+ }
+ });
+ } catch (error) {
+ if (debugLogger) debugLogger.errorEvent('load', error, {
+ oldState: oldState,
+ error: error.message
+ });
+ throw error;
+ }
+ }
+
+ // Utility methods for shop
+ canAfford(item) {
+ if (item.currency === 'credits') {
+ return this.credits >= item.price;
+ } else if (item.currency === 'gems') {
+ return this.gems >= item.price;
+ } else if (item.currency === 'premium') {
+ return this.premiumCurrency >= item.price;
+ }
+ return false;
+ }
+
+ formatPrice(item) {
+ const currencySymbols = {
+ 'credits': '₵',
+ 'gems': '💎',
+ 'premium': '⭐'
+ };
+
+ const symbol = currencySymbols[item.currency] || item.currency;
+ return `${symbol}${this.game.formatNumber(item.price)}`;
+ }
+}
diff --git a/Client/js/core/GameEngine.js b/Client/js/core/GameEngine.js
new file mode 100644
index 0000000..6534af9
--- /dev/null
+++ b/Client/js/core/GameEngine.js
@@ -0,0 +1,1558 @@
+/**
+ * Galaxy Strike Online - Game Engine
+ * Core game loop and state management
+ */
+
+class GameEngine extends EventTarget {
+ constructor() {
+ super(); // Call EventTarget constructor first
+
+ console.log('🛑 DEBUG STOP 1: GameEngine constructor starting');
+ const debugLogger = window.debugLogger;
+ if (debugLogger) debugLogger.log('GameEngine constructor called', {
+ autoSaveInterval: 5,
+ timestamp: new Date().toISOString()
+ });
+
+ this.isRunning = false;
+ this.lastUpdate = 0;
+ this.gameTime = 0;
+
+ // Auto-save settings
+ this.autoSaveInterval = 1; // Default 1 minute
+ this.autoSaveTimer = null;
+ this.lastAutoSave = 0;
+
+ // Save slot information
+ this.saveSlotInfo = {
+ slot: 1, // Default save slot
+ useFileSystem: !!window.electronAPI // Use file system if Electron API is available
+ };
+
+ console.log('🛑 DEBUG STOP 2: Save slot info initialized:', this.saveSlotInfo);
+
+ // GUI update settings
+ this.guiUpdateInterval = 1000; // Update GUI once per second (1000ms)
+ this.lastGUIUpdate = 0;
+
+ // Game logic settings (independent of frame rate)
+ this.gameLogicInterval = 1000; // Update game logic every 1 second
+ this.gameLogicTimer = null;
+
+ // Game state
+ this.state = {
+ paused: false,
+ currentTab: 'dashboard',
+ notifications: [],
+ loading: true
+ };
+
+ console.log('🛑 DEBUG STOP 3: Game state initialized:', this.state);
+
+ // Systems
+ this.systems = {};
+
+ // Event listeners
+ this.eventListeners = new Map();
+
+ console.log('🛑 DEBUG STOP 4: About to call this.init()');
+ this.init();
+ console.log('🛑 DEBUG STOP 5: GameEngine constructor completed');
+ }
+
+ setMultiplayerMode(isMultiplayer, socket = null, serverData = null, currentUser = null) {
+ const debugLogger = window.debugLogger;
+
+ console.log('[GAME ENGINE] Setting multiplayer mode:', isMultiplayer);
+ if (debugLogger) debugLogger.logStep('setMultiplayerMode', { isMultiplayer });
+
+ this.isMultiplayer = isMultiplayer;
+ this.socket = socket;
+ this.serverData = serverData;
+ this.currentUser = currentUser;
+
+ // Store multiplayer settings for systems that need them
+ this.multiplayerConfig = {
+ isMultiplayer,
+ socket,
+ serverData,
+ currentUser
+ };
+
+ console.log('[GAME ENGINE] Multiplayer mode configured:', {
+ isMultiplayer,
+ hasSocket: !!socket,
+ hasServerData: !!serverData,
+ hasCurrentUser: !!currentUser
+ });
+ }
+
+ // Get random integer between min and max (inclusive)
+ getRandomInt(min, max) {
+ return Math.floor(Math.random() * (max - min + 1)) + min;
+ }
+
+ // Get random float between min and max
+ getRandomFloat(min, max) {
+ return Math.random() * (max - min) + min;
+ }
+
+ async init() {
+ console.log('[GAME ENGINE] Initializing game engine');
+ const logger = window.logger;
+ const debugLogger = window.debugLogger;
+
+ if (logger) await logger.info('Initializing game engine');
+ if (debugLogger) await debugLogger.startStep('gameEngineInit');
+
+ try {
+ // Initialize core systems but don't setup new game data
+ await this.initializeSystemsForLoad();
+
+ if (debugLogger) await debugLogger.logStep('Systems initialized, setting up event listeners');
+
+ // Set up event listeners
+ await this.setupEventListeners();
+
+ if (debugLogger) await debugLogger.logStep('Event listeners setup complete');
+
+ if (logger) await logger.info('Game engine initialization completed');
+ if (debugLogger) await debugLogger.endStep('gameEngineInit', {
+ systemsInitialized: Object.keys(this.systems).length,
+ eventListeners: this.eventListeners.size
+ });
+
+ } catch (error) {
+ console.error('Failed to initialize game:', error);
+ if (logger) await logger.errorEvent(error, 'Game Engine Initialization');
+ if (debugLogger) await debugLogger.errorEvent(error, 'Game Engine Initialization');
+ }
+ }
+
+ async startGame(continueGame = false) {
+ const logger = window.logger;
+ const debugLogger = window.debugLogger;
+
+ console.log('[GAME ENGINE] startGame called with continueGame =', continueGame);
+ if (logger) await logger.info('Starting game', { continueGame });
+ if (debugLogger) await debugLogger.startStep('startGame', { continueGame });
+
+ try {
+ if (continueGame) {
+ console.log('[GAME ENGINE] Loading existing save data...');
+ if (debugLogger) await debugLogger.logStep('Loading existing save data');
+ await this.loadGame();
+ console.log('[GAME ENGINE] Save data loaded');
+ } else {
+ console.log('[GAME ENGINE] Creating new game...');
+ if (debugLogger) await debugLogger.logStep('Creating new game');
+ await this.newGame();
+ console.log('[GAME ENGINE] New game created');
+ }
+
+ // Start game loop
+ // CRITICAL: Ensure UIManager is initialized before starting game
+ if (this.systems.ui) {
+ console.log('[GAME ENGINE] Final UIManager initialization check...');
+ try {
+ await this.systems.ui.initialize();
+ console.log('[GAME ENGINE] UIManager initialized successfully');
+ } catch (error) {
+ console.error('[GAME ENGINE] UIManager initialization failed:', error);
+ }
+ }
+
+ this.start();
+ console.log('[GAME ENGINE] Game loop started');
+ if (debugLogger) await debugLogger.logStep('Game loop started');
+
+ const loadingStatus = document.getElementById('loadingStatus');
+ if (loadingStatus) {
+ console.log('[GAME ENGINE] Hiding loading status text');
+ if (debugLogger) await debugLogger.logStep('Hiding loading status text');
+ loadingStatus.classList.add('hidden');
+ }
+
+ const gameInterface = document.getElementById('gameInterface');
+ if (gameInterface) {
+ console.log('[GAME ENGINE] Showing game interface');
+ if (debugLogger) await debugLogger.logStep('Showing game interface');
+ gameInterface.classList.remove('hidden');
+
+ // CRITICAL: Initialize UIManager after showing interface
+ console.log('[GAME ENGINE] Initializing UIManager after showing interface...');
+ if (this.systems.ui) {
+ try {
+ await this.systems.ui.initialize();
+ console.log('[GAME ENGINE] UIManager initialized successfully (post-interface)');
+ } catch (error) {
+ console.error('[GAME ENGINE] UIManager initialization failed:', error);
+ }
+ } else {
+ console.log('[GAME ENGINE] UIManager not found when trying to initialize after interface');
+ }
+ } else {
+ console.warn('[GAME ENGINE] gameInterface element not found');
+ if (debugLogger) await debugLogger.warn('gameInterface element not found');
+ }
+
+ if (logger) await logger.info('Game started successfully');
+ if (debugLogger) await debugLogger.endStep('startGame', {
+ continueGame,
+ isRunning: this.isRunning,
+ gameTime: this.gameTime
+ });
+
+ } catch (error) {
+ console.error('[GAME ENGINE] Failed to start game:', error);
+ if (logger) await logger.errorEvent(error, 'Game Start');
+ if (debugLogger) await debugLogger.errorEvent(error, 'Game Start');
+ }
+ }
+
+ async initializeSystemsForLoad() {
+ const logger = window.logger;
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) await debugLogger.startStep('initializeSystemsForLoad');
+
+ if (logger) {
+ await logger.timeAsync('Game Systems Initialization for Load', async () => {
+ await logger.info('Initializing game systems for loading');
+
+ if (debugLogger) await debugLogger.logStep('Initializing TextureManager');
+ // Initialize texture manager first
+ this.systems.textureManager = new TextureManager(this);
+ if (logger) await logger.systemEvent('TextureManager', 'Initialized');
+ if (debugLogger) await debugLogger.logStep('TextureManager initialized');
+
+ if (debugLogger) await debugLogger.logStep('Creating Player system (without initialization)');
+ // Create systems but don't initialize with default data
+ this.systems.player = new Player(this);
+ if (logger) await logger.systemEvent('Player', 'Created');
+ if (debugLogger) await debugLogger.logStep('Player system created');
+
+ if (debugLogger) await debugLogger.logStep('Creating Inventory system (without initialization)');
+ this.systems.inventory = new Inventory(this);
+ if (logger) await logger.systemEvent('Inventory', 'Created');
+ if (debugLogger) await debugLogger.logStep('Inventory system created');
+
+ if (debugLogger) await debugLogger.logStep('Creating Economy system (without initialization)');
+ this.systems.economy = new Economy(this);
+ if (logger) await logger.systemEvent('Economy', 'Created');
+ if (debugLogger) await debugLogger.logStep('Economy system created');
+
+ if (debugLogger) await debugLogger.logStep('Creating IdleSystem');
+ this.systems.idleSystem = new IdleSystem(this);
+ if (logger) await logger.systemEvent('IdleSystem', 'Created');
+ if (debugLogger) await debugLogger.logStep('IdleSystem created');
+
+ if (debugLogger) await debugLogger.logStep('Creating DungeonSystem');
+ this.systems.dungeonSystem = new DungeonSystem(this);
+ if (logger) await logger.systemEvent('DungeonSystem', 'Created');
+ if (debugLogger) await debugLogger.logStep('DungeonSystem created');
+
+ if (debugLogger) await debugLogger.logStep('Creating SkillSystem');
+ this.systems.skillSystem = new SkillSystem(this);
+ if (logger) await logger.systemEvent('SkillSystem', 'Created');
+ if (debugLogger) await debugLogger.logStep('SkillSystem created');
+
+ if (debugLogger) await debugLogger.logStep('Creating BaseSystem');
+ this.systems.baseSystem = new BaseSystem(this);
+ if (logger) await logger.systemEvent('BaseSystem', 'Created');
+ if (debugLogger) await debugLogger.logStep('BaseSystem created');
+
+ if (debugLogger) await debugLogger.logStep('Creating QuestSystem');
+ this.systems.questSystem = new QuestSystem(this);
+ if (logger) await logger.systemEvent('QuestSystem', 'Created');
+ if (debugLogger) await debugLogger.logStep('QuestSystem created');
+
+ if (debugLogger) await debugLogger.logStep('Creating ShipSystem');
+ this.systems.shipSystem = new ShipSystem(this);
+ if (logger) await logger.systemEvent('ShipSystem', 'Created');
+ if (debugLogger) await debugLogger.logStep('ShipSystem created');
+
+ if (debugLogger) await debugLogger.logStep('Creating UIManager');
+ if (typeof UIManager !== 'undefined') {
+ console.log('[GAME ENGINE] UIManager class found, creating real UIManager');
+ this.systems.ui = new UIManager(this);
+ // Expose UIManager globally for button onclick handlers
+ window.uiManager = this.systems.ui;
+ window.game.systems.ui = this.systems.ui;
+ if (logger) await logger.systemEvent('UIManager', 'Created');
+ if (debugLogger) await debugLogger.logStep('UIManager created and exposed');
+ } else {
+ console.error('[GAME ENGINE] UIManager class not found - this should not happen!');
+ if (debugLogger) await debugLogger.error('UIManager class not found - this should not happen!');
+ // Create minimal UI system to prevent crashes (should never be needed based on logs)
+ this.systems.ui = {
+ showNotification: (message, type, duration) => {
+ console.log(`[UI MINIMAL] ${type}: ${message}`);
+ },
+ updateUI: () => {
+ console.log('[UI MINIMAL] updateUI called');
+ },
+ switchTab: (tabName) => {
+ console.log('[UI MINIMAL] switchTab called');
+ }
+ };
+ }
+ if (debugLogger) await debugLogger.logStep('Creating CraftingSystem');
+ this.systems.crafting = new CraftingSystem(this);
+ if (logger) await logger.systemEvent('CraftingSystem', 'Created');
+ if (debugLogger) await debugLogger.logStep('CraftingSystem created');
+
+ if (debugLogger) await debugLogger.endStep('initializeSystemsForLoad', {
+ systemsCreated: Object.keys(this.systems).length
+ });
+ });
+ }
+ }
+
+ async initializeSystems() {
+ const logger = window.logger;
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) await debugLogger.startStep('initializeSystems');
+
+ if (logger) {
+ await logger.timeAsync('Game Systems Initialization', async () => {
+ await logger.info('Initializing game systems');
+
+ if (debugLogger) await debugLogger.logStep('Initializing TextureManager');
+ // Initialize texture manager first
+ this.systems.textureManager = new TextureManager(this);
+ if (logger) await logger.systemEvent('TextureManager', 'Initialized');
+ if (debugLogger) await debugLogger.logStep('TextureManager initialized');
+
+ if (debugLogger) await debugLogger.logStep('Initializing Player system');
+ // Initialize in dependency order
+ this.systems.player = new Player(this);
+ if (logger) await logger.systemEvent('Player', 'Initialized');
+ if (debugLogger) await debugLogger.logStep('Player system initialized', {
+ playerLevel: this.systems.player.stats.level,
+ playerExperience: this.systems.player.stats.experience
+ });
+
+ if (debugLogger) await debugLogger.logStep('Initializing Inventory system');
+ this.systems.inventory = new Inventory(this);
+ if (logger) await logger.systemEvent('Inventory', 'Initialized');
+ if (debugLogger) await debugLogger.logStep('Inventory system initialized', {
+ inventorySize: this.systems.inventory.items.length
+ });
+
+ if (debugLogger) await debugLogger.logStep('Initializing Economy system');
+ this.systems.economy = new Economy(this);
+ if (logger) await logger.systemEvent('Economy', 'Initialized');
+ if (debugLogger) await debugLogger.logStep('Economy system initialized', {
+ credits: this.systems.economy.credits,
+ gems: this.systems.economy.gems
+ });
+
+ if (debugLogger) await debugLogger.logStep('Initializing IdleSystem');
+ this.systems.idleSystem = new IdleSystem(this);
+ if (logger) await logger.systemEvent('IdleSystem', 'Initialized');
+ if (debugLogger) await debugLogger.logStep('IdleSystem initialized');
+
+ if (debugLogger) await debugLogger.logStep('Initializing DungeonSystem');
+ this.systems.dungeonSystem = new DungeonSystem(this);
+ if (logger) await logger.systemEvent('DungeonSystem', 'Initialized');
+ if (debugLogger) await debugLogger.logStep('DungeonSystem initialized');
+
+ if (debugLogger) await debugLogger.logStep('Initializing SkillSystem');
+ this.systems.skillSystem = new SkillSystem(this);
+ if (logger) await logger.systemEvent('SkillSystem', 'Initialized');
+ if (debugLogger) await debugLogger.logStep('SkillSystem initialized');
+
+ if (debugLogger) await debugLogger.logStep('Initializing BaseSystem');
+ this.systems.baseSystem = new BaseSystem(this);
+ if (logger) await logger.systemEvent('BaseSystem', 'Initialized');
+ if (debugLogger) await debugLogger.logStep('BaseSystem initialized');
+
+ if (debugLogger) await debugLogger.logStep('Initializing QuestSystem');
+ this.systems.questSystem = new QuestSystem(this);
+ await this.systems.questSystem.initialize();
+ if (logger) await logger.systemEvent('QuestSystem', 'Initialized');
+ if (debugLogger) await debugLogger.logStep('QuestSystem initialized');
+
+ if (debugLogger) await debugLogger.logStep('Initializing ShipSystem');
+ this.systems.ship = new ShipSystem(this);
+ if (logger) await logger.systemEvent('ShipSystem', 'Initialized');
+ if (debugLogger) await debugLogger.logStep('ShipSystem initialized');
+
+ if (debugLogger) await debugLogger.logStep('UIManager already initialized in initializeSystemsForLoad');
+
+ if (debugLogger) await debugLogger.logStep('Initializing CraftingSystem');
+ this.systems.crafting = new CraftingSystem(this);
+ if (logger) await logger.systemEvent('CraftingSystem', 'Initialized');
+ if (debugLogger) await debugLogger.logStep('CraftingSystem initialized');
+
+ if (logger) await logger.info('All game systems initialized');
+ if (debugLogger) await debugLogger.logStep('All systems created, running individual initializations');
+
+ if (debugLogger) await debugLogger.logStep('Running individual system initializations');
+ for (const [name, system] of Object.entries(this.systems)) {
+ if (system.initialize) {
+ if (debugLogger) await debugLogger.logStep(`Initializing ${name} system`);
+ await system.initialize();
+ if (debugLogger) await debugLogger.logStep(`${name} system initialization complete`);
+ } else {
+ if (debugLogger) await debugLogger.logStep(`${name} system has no initialize method`);
+ }
+ }
+
+ if (debugLogger) await debugLogger.endStep('initializeSystems', {
+ totalSystems: Object.keys(this.systems).length,
+ systemsList: Object.keys(this.systems)
+ });
+ });
+ } else {
+ // Fallback without timing
+ if (debugLogger) await debugLogger.logStep('Logger not available, using fallback initialization');
+
+ // Initialize texture manager first
+ this.systems.textureManager = new TextureManager(this);
+
+ // Initialize in dependency order
+ this.systems.player = new Player(this);
+ this.systems.inventory = new Inventory(this);
+ this.systems.economy = new Economy(this);
+ this.systems.idleSystem = new IdleSystem(this);
+ this.systems.dungeonSystem = new DungeonSystem(this);
+ this.systems.skillSystem = new SkillSystem(this);
+ this.systems.baseSystem = new BaseSystem(this);
+ this.systems.questSystem = new QuestSystem(this);
+ // UIManager already initialized in initializeSystemsForLoad
+ this.systems.crafting = new CraftingSystem(this);
+
+ for (const [name, system] of Object.entries(this.systems)) {
+ if (system.initialize) {
+ await system.initialize();
+ }
+ }
+
+ if (debugLogger) await debugLogger.endStep('initializeSystems', {
+ fallbackMode: true,
+ totalSystems: Object.keys(this.systems).length
+ });
+ }
+ }
+
+ async setupEventListeners() {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) await debugLogger.startStep('setupEventListeners');
+
+ // Window events
+ if (debugLogger) await debugLogger.logStep('Setting up window event listeners');
+ window.addEventListener('beforeunload', () => {
+ if (debugLogger) debugLogger.logStep('beforeunload event triggered - saving game');
+ this.save();
+ });
+
+ window.addEventListener('visibilitychange', () => {
+ // if (debugLogger) debugLogger.logStep('visibilitychange event triggered', {
+ // hidden: document.hidden,
+ // visibilityState: document.visibilityState
+ // });
+ if (document.hidden) {
+ // if (debugLogger) debugLogger.logStep('Document hidden - saving game');
+ this.save();
+ }
+ });
+
+ // Keyboard shortcuts
+ if (debugLogger) await debugLogger.logStep('Setting up keyboard shortcuts');
+ document.addEventListener('keydown', (event) => {
+ if (debugLogger) debugLogger.logStep('Key pressed', {
+ key: event.key,
+ code: event.code,
+ ctrlKey: event.ctrlKey,
+ altKey: event.altKey,
+ shiftKey: event.shiftKey
+ });
+
+ // Ctrl+S for manual save
+ if (event.ctrlKey && event.key === 's') {
+ event.preventDefault();
+ if (debugLogger) debugLogger.logStep('Manual save shortcut triggered (Ctrl+S)');
+ this.save();
+ }
+ });
+
+ // Game loop events
+ if (debugLogger) await debugLogger.logStep('Setting up game loop events');
+ this.addEventListener('gameStarted', () => {
+ if (debugLogger) debugLogger.logStep('Game started event received');
+ });
+
+ this.addEventListener('gameStopped', () => {
+ if (debugLogger) debugLogger.logStep('Game stopped event received');
+ });
+
+ this.addEventListener('gameSaved', () => {
+ // if (debugLogger) debugLogger.logStep('Game saved event received');
+ });
+
+ this.addEventListener('gameLoaded', () => {
+ if (debugLogger) debugLogger.logStep('Game loaded event received');
+ });
+
+ // Setup UI event listeners
+ if (debugLogger) await debugLogger.logStep('Setting up UI event listeners');
+ if (this.systems.ui && typeof this.systems.ui.setupEventListeners === 'function' && !this.uiEventListenersSetup) {
+ this.systems.ui.setupEventListeners();
+ this.uiEventListenersSetup = true; // Prevent duplicate setup
+ if (debugLogger) await debugLogger.logStep('UI event listeners setup complete');
+ } else if (this.uiEventListenersSetup) {
+ if (debugLogger) await debugLogger.logStep('UI event listeners already setup, skipping');
+ }
+
+ if (debugLogger) await debugLogger.endStep('setupEventListeners', {
+ windowEvents: 2,
+ keyboardShortcuts: 2,
+ gameLoopEvents: 4
+ });
+ }
+
+ start() {
+ const debugLogger = window.debugLogger;
+
+ if (this.isRunning) {
+ if (debugLogger) debugLogger.log('GameEngine.start() called but game is already running', {
+ isRunning: this.isRunning,
+ gameTime: this.gameTime
+ });
+ return;
+ }
+
+ if (debugLogger) debugLogger.logStep('Starting game engine', {
+ gameLogicInterval: this.gameLogicInterval
+ });
+
+ this.isRunning = true;
+ this.lastUpdate = performance.now();
+
+ if (debugLogger) debugLogger.logStep('Game engine started, beginning game loop');
+
+ this.gameLoop();
+
+ // Loading screen is now handled by startGame() method
+ // Don't duplicate the hiding logic here
+
+ if (debugLogger) debugLogger.logStep('Game loop initiated');
+ }
+
+ async stop() {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) await debugLogger.startStep('stopGame');
+
+ console.log('[GAME ENGINE] Stopping game and saving...');
+ if (debugLogger) await debugLogger.logStep('Stopping game engine');
+
+ if (!this.isRunning) {
+ if (debugLogger) debugLogger.logStep('Game already stopped, ignoring stop request');
+ return;
+ }
+
+ this.isRunning = false;
+
+ // Clear game logic timer
+ if (this.gameLogicTimer) {
+ clearInterval(this.gameLogicTimer);
+ this.gameLogicTimer = null;
+ }
+
+ // Clear auto-save timer
+ if (this.autoSaveTimer) {
+ clearInterval(this.autoSaveTimer);
+ this.autoSaveTimer = null;
+ }
+
+ console.log('[GAME ENGINE] Game stopped and saved successfully');
+
+ if (debugLogger) await debugLogger.endStep('stopGame', {
+ gameTime: this.gameTime,
+ isRunning: this.isRunning
+ });
+ }
+
+ startAutoSave() {
+ const debugLogger = window.debugLogger;
+
+ // Load saved interval or use default
+ const savedInterval = localStorage.getItem('autoSaveInterval');
+ this.autoSaveInterval = savedInterval ? parseInt(savedInterval) : 5;
+
+ console.log(`[GAME ENGINE] Starting auto-save with ${this.autoSaveInterval} minute interval`);
+ if (debugLogger) debugLogger.logStep('Starting auto-save system', {
+ interval: this.autoSaveInterval,
+ intervalMs: this.autoSaveInterval * 60 * 1000,
+ savedInterval: savedInterval,
+ isRunning: this.isRunning
+ });
+
+ // Clear any existing timer
+ this.stopAutoSave();
+
+ // Set up new timer
+ this.autoSaveTimer = setInterval(async () => {
+ if (debugLogger) debugLogger.logStep('Auto-save timer triggered', {
+ isRunning: this.isRunning,
+ paused: this.state.paused,
+ gameTime: this.gameTime,
+ lastAutoSave: this.lastAutoSave
+ });
+
+ console.log('[GAME ENGINE] Auto-save timer triggered - isRunning:', this.isRunning, 'paused:', this.state.paused);
+
+ if (this.isRunning && !this.state.paused) {
+ console.log('[GAME ENGINE] Auto-saving game...');
+ if (debugLogger) debugLogger.logStep('Auto-saving game');
+
+ try {
+ await this.save();
+ this.showNotification('Game auto-saved', 'info', 2000);
+ console.log('[GAME ENGINE] Auto-save completed successfully');
+
+ this.lastAutoSave = Date.now();
+ if (debugLogger) debugLogger.logStep('Auto-save completed successfully', {
+ lastAutoSave: this.lastAutoSave
+ });
+
+ } catch (error) {
+ console.error('[GAME ENGINE] Auto-save failed:', error);
+ if (debugLogger) await debugLogger.errorEvent(error, 'Auto-save');
+ }
+ } else {
+ console.log('[GAME ENGINE] Auto-save skipped - game not running or paused');
+ if (debugLogger) debugLogger.logStep('Auto-save skipped', {
+ reason: this.isRunning ? 'paused' : 'not running',
+ isRunning: this.isRunning,
+ paused: this.state.paused
+ });
+ }
+ }, this.autoSaveInterval * 60 * 1000); // Convert minutes to milliseconds
+
+ this.lastAutoSave = Date.now();
+ if (debugLogger) debugLogger.logStep('Auto-save timer started', {
+ timerId: this.autoSaveTimer,
+ lastAutoSave: this.lastAutoSave
+ });
+ }
+
+ stopAutoSave() {
+ const debugLogger = window.debugLogger;
+
+ if (this.autoSaveTimer) {
+ console.log('[GAME ENGINE] Stopping auto-save timer');
+ if (debugLogger) debugLogger.logStep('Stopping auto-save timer', {
+ timerId: this.autoSaveTimer,
+ wasRunning: true
+ });
+
+ clearInterval(this.autoSaveTimer);
+ this.autoSaveTimer = null;
+
+ if (debugLogger) debugLogger.logStep('Auto-save timer stopped');
+ } else {
+ if (debugLogger) debugLogger.logStep('stopAutoSave called but no timer was active', {
+ timerId: this.autoSaveTimer,
+ wasRunning: false
+ });
+ }
+ }
+
+ updateAutoSaveInterval(newInterval) {
+ const debugLogger = window.debugLogger;
+
+ console.log(`[GAME ENGINE] Updating auto-save interval to ${newInterval} minutes`);
+ if (debugLogger) debugLogger.logStep('Updating auto-save interval', {
+ oldInterval: this.autoSaveInterval,
+ newInterval: newInterval,
+ isRunning: this.isRunning
+ });
+
+ this.autoSaveInterval = newInterval;
+
+ // Save to localStorage
+ localStorage.setItem('autoSaveInterval', newInterval.toString());
+
+ // Restart auto-save with new interval if game is running
+ if (this.isRunning) {
+ if (debugLogger) debugLogger.logStep('Restarting auto-save with new interval');
+ this.startAutoSave();
+ }
+
+ if (debugLogger) debugLogger.logStep('Auto-save interval updated successfully', {
+ currentInterval: this.autoSaveInterval,
+ savedToStorage: true
+ });
+ }
+
+ start() {
+ const debugLogger = window.debugLogger;
+
+ if (this.isRunning) {
+ if (debugLogger) debugLogger.log('GameEngine.start() called but game is already running', {
+ isRunning: this.isRunning,
+ gameTime: this.gameTime
+ });
+ return;
+ }
+
+ if (debugLogger) debugLogger.logStep('Starting game engine', {
+ gameLogicInterval: this.gameLogicInterval
+ });
+
+ this.isRunning = true;
+ this.lastUpdate = Date.now();
+
+ // Start game logic timer (completely independent of frame rate)
+ console.log('[GAME ENGINE] Starting game logic timer with interval:', this.gameLogicInterval);
+ this.gameLogicTimer = setInterval(() => {
+ this.updateGameLogic();
+ }, this.gameLogicInterval);
+
+ // Start auto-save
+ this.startAutoSave();
+
+ console.log('[GAME ENGINE] Game engine started');
+ if (debugLogger) debugLogger.logStep('Game engine started successfully', {
+ gameLogicInterval: this.gameLogicInterval,
+ autoSaveInterval: this.autoSaveInterval
+ });
+ }
+
+ updateGameLogic() {
+ const debugLogger = window.debugLogger;
+
+ // Use fixed 1-second interval for all updates
+ const fixedDelta = 1000; // 1 second in milliseconds
+
+ console.log('[GAME ENGINE] Game logic update called - adding', fixedDelta, 'ms to playtime');
+
+ if (this.state.paused) {
+ if (debugLogger) debugLogger.logStep('Game logic update called but game is paused', {
+ gameTime: this.gameTime,
+ fixedDelta: fixedDelta
+ });
+ return;
+ }
+
+ this.gameTime += fixedDelta;
+
+ console.log('[GAME ENGINE] Total game time now:', this.gameTime, 'ms');
+
+ // Emit UI update event instead of direct call
+ this.emitUIUpdateEvent('full');
+
+ // Log update performance every 300 frames (approximately 5 seconds)
+ if (debugLogger && this.gameTime % 15000 === 0) { // Every 15 seconds of game time
+ debugLogger.logStep('Game logic update cycle', {
+ gameTime: this.gameTime,
+ fixedDelta: fixedDelta,
+ isRunning: this.isRunning,
+ paused: this.state.paused,
+ currentTab: this.state.currentTab
+ });
+ }
+
+ // Update player play time with fixed delta
+ if (this.systems.player && this.systems.player.updatePlayTime) {
+ console.log('[GAME ENGINE] Before update - player playTime:', this.systems.player.stats.playTime);
+ this.systems.player.updatePlayTime(fixedDelta);
+ console.log('[GAME ENGINE] After update - player playTime:', this.systems.player.stats.playTime);
+ }
+
+ // Update all systems with fixed delta
+ for (const [name, system] of Object.entries(this.systems)) {
+ if (system && typeof system.update === 'function') {
+ try {
+ system.update(fixedDelta);
+ } catch (error) {
+ console.error(`[GAME ENGINE] Error updating ${name} system:`, error);
+ if (debugLogger) debugLogger.errorEvent(error, `Update ${name} system`);
+ }
+ }
+ }
+
+ // Update UI displays (money, gems, energy) after system updates
+ if (this.systems && this.systems.ui) {
+ console.log('[GAME ENGINE] Updating UI displays');
+ try {
+ this.systems.ui.updateUI();
+ console.log('[GAME ENGINE] UI update completed');
+ } catch (error) {
+ console.error('[GAME ENGINE] Error updating UI:', error);
+ }
+ }
+ }
+
+ update(deltaTime) {
+ // Legacy method - game logic now handled by updateGameLogic()
+ // This method kept for compatibility but doesn't process game systems
+ }
+
+ shouldUpdateGUI() {
+ const currentTime = Date.now();
+ if (currentTime - this.lastGUIUpdate >= this.guiUpdateInterval) {
+ this.lastGUIUpdate = currentTime;
+ return true;
+ }
+ return false;
+ }
+
+ updateGUI() {
+ const debugLogger = window.debugLogger;
+
+ console.log('[GAME ENGINE] Updating GUI');
+
+ // Update UI displays (money, gems, energy) after system updates
+ if (this.systems && this.systems.ui) {
+ console.log('[GAME ENGINE] Updating UI displays');
+ try {
+ this.systems.ui.updateUI();
+ console.log('[GAME ENGINE] UI update completed');
+ } catch (error) {
+ console.error('[GAME ENGINE] Error updating UI:', error);
+ }
+ }
+ }
+
+ // Event system
+ on(event, callback) {
+ if (!this.eventListeners.has(event)) {
+ this.eventListeners.set(event, []);
+ }
+ this.eventListeners.get(event).push(callback);
+ }
+
+ emit(event, data) {
+ if (this.eventListeners.has(event)) {
+ this.eventListeners.get(event).forEach(callback => {
+ try {
+ callback(data);
+ } catch (error) {
+ console.error(`[GAME ENGINE] Error in event listener for ${event}:`, error);
+ }
+ });
+ }
+
+ // Also dispatch as DOM event for UIManager
+ this.dispatchEvent(new CustomEvent(event, { detail: data }));
+ }
+
+ emitUIUpdateEvent(type, data = {}) {
+ const eventData = {
+ type: type,
+ timestamp: Date.now(),
+ ...data
+ };
+
+ console.log('[GAME ENGINE] Emitting UI update event:', eventData);
+ this.emit('uiUpdate', eventData);
+ }
+
+ // Notification system
+ async showNotification(message, type = 'info', duration = 3000) {
+ const logger = window.logger;
+ if (logger) await logger.playerAction('Notification', { message, type, duration });
+
+ const notification = {
+ id: Date.now(),
+ message,
+ type,
+ duration,
+ timestamp: Date.now()
+ };
+
+ this.state.notifications.push(notification);
+
+ // Auto-remove notification after duration
+ setTimeout(() => {
+ this.removeNotification(notification.id);
+ }, duration);
+
+ // Update UI
+ if (this.systems.ui) {
+ // UI updates handled by individual systems
+ }
+ }
+
+ removeNotification(id) {
+ this.state.notifications = this.state.notifications.filter(notification => notification.id !== id);
+ }
+
+ processNotifications() {
+ const now = Date.now();
+ this.state.notifications = this.state.notifications.filter(
+ notification => now - notification.timestamp < notification.duration
+ );
+ }
+
+ // Save/Load system
+ async save() {
+ const logger = window.logger;
+ const debugLogger = window.debugLogger;
+
+ console.log('[GAME ENGINE] Save method called');
+ if (logger) await logger.info('Saving game');
+ // if (debugLogger) await debugLogger.startStep('saveGame');
+
+ try {
+ // if (debugLogger) await debugLogger.logStep('Collecting save data from systems');
+ console.log('[GAME ENGINE] Collecting save data from systems...');
+
+ const saveData = {
+ player: this.systems.player.save(),
+ inventory: this.systems.inventory.save(),
+ economy: this.systems.economy.save(),
+ idleSystem: this.systems.idleSystem.save(),
+ dungeonSystem: this.systems.dungeonSystem.save(),
+ skillSystem: this.systems.skillSystem.save(),
+ baseSystem: this.systems.baseSystem.save(),
+ questSystem: this.systems.questSystem.save(),
+ gameTime: this.gameTime,
+ lastSave: Date.now()
+ };
+
+ // if (debugLogger) await debugLogger.logStep('Save data collected', {
+ // playerLevel: saveData.player?.stats?.level,
+ // gameTime: this.gameTime,
+ // saveDataSize: JSON.stringify(saveData).length,
+ // });
+
+ // console.log('[GAME ENGINE] Save data collected, player level:', saveData.player?.stats?.level);
+ // console.log('[GAME ENGINE] Save slot info:', this.saveSlotInfo);
+
+ // Check if we're in local mode and should use localStorage
+ const isLocalMode = window.liveMainMenu && window.liveMainMenu.isLocalMode;
+
+ if (isLocalMode) {
+ console.log('[GAME ENGINE] Using localStorage for local mode save');
+ if (debugLogger) await debugLogger.logStep('Saving via localStorage for local mode');
+
+ try {
+ const saveKey = `gso_save_slot_${this.saveSlotInfo.slot}`;
+ const saveString = JSON.stringify(saveData);
+
+ // Save to localStorage
+ localStorage.setItem(saveKey, saveString);
+
+ console.log('[GAME ENGINE] Game saved successfully to localStorage');
+ console.log('[GAME ENGINE] Save key:', saveKey);
+ console.log('[GAME ENGINE] Save data size:', saveString.length, 'characters');
+ console.log('[GAME ENGINE] Current localStorage keys:', Object.keys(localStorage));
+
+ // Verify save exists
+ const verifySave = localStorage.getItem(saveKey);
+ console.log('[GAME ENGINE] Save verification:', !!verifySave ? 'SUCCESS' : 'FAILED');
+
+ if (debugLogger) await debugLogger.logStep('Game saved successfully to localStorage', {
+ saveKey,
+ saveDataSize: saveString.length
+ });
+
+ // Emit save event
+ this.emit('gameSaved', { slot: this.saveSlotInfo.slot, saveData });
+ // if (debugLogger) debugLogger.logStep('Game saved event emitted');
+
+ } catch (error) {
+ console.error('[GAME ENGINE] Failed to save to localStorage:', error);
+ if (debugLogger) await debugLogger.errorEvent(error, 'LocalStorage Save');
+ throw error;
+ }
+ } else {
+ // Use file system if available, otherwise localStorage (original logic)
+ if (this.saveSlotInfo && this.saveSlotInfo.useFileSystem) {
+ if (window.electronAPI) {
+ // console.log('[GAME ENGINE] Using Electron API to save game');
+ // if (debugLogger) await debugLogger.logStep('Saving via Electron API', {
+ // slot: this.saveSlotInfo.slot,
+ // useFileSystem: true
+ // });
+
+ try {
+ // Save through Electron API
+ const result = await window.electronAPI.saveGame(this.saveSlotInfo.slot, saveData);
+ if (result.success) {
+ // console.log('[GAME ENGINE] Game saved successfully via Electron API');
+ if (debugLogger) await debugLogger.logStep('Game saved successfully via Electron API', {
+ slot: this.saveSlotInfo.slot,
+ result: result
+ });
+
+ // Emit save event
+ this.emit('gameSaved', { slot: this.saveSlotInfo.slot, saveData });
+ // if (debugLogger) debugLogger.logStep('Game saved event emitted');
+
+ } else {
+ console.error('[GAME ENGINE] Failed to save via Electron API:', result.error);
+ if (debugLogger) await debugLogger.errorEvent(new Error(result.error), 'Electron API Save');
+
+ // Fallback to localStorage
+ if (debugLogger) await debugLogger.logStep('Falling back to localStorage');
+ const saveKey = `gso_save_slot_${this.saveSlotInfo.slot}`;
+ localStorage.setItem(saveKey, JSON.stringify(saveData));
+ console.log('[GAME ENGINE] Fallback: Game saved to localStorage with key:', saveKey);
+ if (debugLogger) await debugLogger.logStep('Game saved to localStorage fallback', {
+ saveKey,
+ dataSize: JSON.stringify(saveData).length
+ });
+ }
+ } catch (error) {
+ console.error('[GAME ENGINE] Electron API save error:', error);
+ if (debugLogger) await debugLogger.errorEvent(error, 'Electron API Save');
+
+ // Fallback to localStorage
+ if (debugLogger) await debugLogger.logStep('Falling back to localStorage due to error');
+ const saveKey = `gso_save_slot_${this.saveSlotInfo.slot}`;
+ localStorage.setItem(saveKey, JSON.stringify(saveData));
+ console.log('[GAME ENGINE] Error fallback: Game saved to localStorage with key:', saveKey);
+ }
+ } else {
+ console.warn('[GAME ENGINE] Electron API not available, using localStorage');
+ if (debugLogger) await debugLogger.warn('Electron API not available, using localStorage');
+
+ const saveKey = `gso_save_slot_${this.saveSlotInfo.slot}`;
+ localStorage.setItem(saveKey, JSON.stringify(saveData));
+ console.log('[GAME ENGINE] Game saved to localStorage with key:', saveKey);
+ if (debugLogger) await debugLogger.logStep('Game saved to localStorage', {
+ saveKey,
+ dataSize: JSON.stringify(saveData).length
+ });
+ }
+ } else {
+ // LocalStorage fallback
+ if (debugLogger) await debugLogger.logStep('Using localStorage save');
+ const saveKey = `gso_save_slot_${this.saveSlotInfo.slot}`;
+ localStorage.setItem(saveKey, JSON.stringify(saveData));
+ console.log('[GAME ENGINE] Game saved to localStorage with key:', saveKey);
+ if (debugLogger) await debugLogger.logStep('Game saved to localStorage', {
+ saveKey,
+ dataSize: JSON.stringify(saveData).length
+ });
+ }
+ }
+
+ if (logger) await logger.info('Game saved successfully');
+ // if (debugLogger) await debugLogger.endStep('saveGame', {
+ // gameTime: this.gameTime,
+ // saveSlot: this.saveSlotInfo?.slot,
+ // success: true
+ // });
+
+ } catch (error) {
+ console.error('[GAME ENGINE] Save failed:', error);
+ if (logger) await logger.errorEvent(error, 'Game Save');
+ if (debugLogger) await debugLogger.errorEvent(error, 'Game Save');
+ throw error;
+ }
+ }
+
+ async loadGame() {
+ const logger = window.logger;
+ const debugLogger = window.debugLogger;
+
+ console.log('[GAME ENGINE] Load method called');
+ if (logger) await logger.info('Loading game');
+ if (debugLogger) await debugLogger.startStep('loadGame');
+
+ try {
+ if (debugLogger) await debugLogger.logStep('Loading save data', {
+ saveSlot: this.saveSlotInfo?.slot,
+ useFileSystem: this.saveSlotInfo?.useFileSystem
+ });
+
+ let saveData;
+
+ // Use file system if available, otherwise localStorage
+ if (this.saveSlotInfo && this.saveSlotInfo.useFileSystem && window.electronAPI) {
+ console.log('[GAME ENGINE] Loading via Electron API');
+ if (debugLogger) await debugLogger.logStep('Loading via Electron API');
+
+ const result = await window.electronAPI.loadGame(this.saveSlotInfo.slot);
+ if (result.success) {
+ saveData = result.data;
+ console.log('[GAME ENGINE] Game loaded successfully via Electron API');
+ if (debugLogger) await debugLogger.logStep('Game loaded via Electron API', {
+ slot: this.saveSlotInfo.slot,
+ dataSize: JSON.stringify(saveData).length
+ });
+ } else {
+ console.error('[GAME ENGINE] Failed to load via Electron API:', result.error);
+ if (debugLogger) await debugLogger.errorEvent(new Error(result.error), 'Electron API Load');
+ throw new Error(result.error);
+ }
+ } else {
+ // LocalStorage fallback
+ console.log('[GAME ENGINE] Loading from localStorage');
+ if (debugLogger) await debugLogger.logStep('Loading from localStorage');
+
+ const saveKey = `gso_save_slot_${this.saveSlotInfo.slot}`;
+ const saveString = localStorage.getItem(saveKey);
+
+ if (saveString) {
+ try {
+ saveData = JSON.parse(saveString);
+ console.log('[GAME ENGINE] Game loaded from localStorage');
+ if (debugLogger) await debugLogger.logStep('Game loaded from localStorage', {
+ saveKey,
+ dataSize: saveString.length
+ });
+ } catch (parseError) {
+ console.error('[GAME ENGINE] Failed to parse save data:', parseError);
+ if (debugLogger) await debugLogger.errorEvent(parseError, 'Parse Save Data');
+ throw new Error('Corrupted save data');
+ }
+ } else {
+ console.warn('[GAME ENGINE] No save data found in localStorage');
+ if (debugLogger) await debugLogger.warn('No save data found in localStorage', { saveKey });
+ throw new Error('No save data found');
+ }
+ }
+
+ if (debugLogger) await debugLogger.logStep('Applying save data to systems');
+
+ // Apply save data to systems
+ if (saveData.player && this.systems.player) {
+ console.log('[GAME ENGINE] Loading player data...');
+ this.systems.player.load(saveData.player);
+ if (debugLogger) await debugLogger.logStep('Player data loaded', {
+ level: saveData.player?.stats?.level,
+ experience: saveData.player?.stats?.experience
+ });
+ }
+
+ if (saveData.inventory && this.systems.inventory) {
+ console.log('[GAME ENGINE] Loading inventory data...');
+ this.systems.inventory.load(saveData.inventory);
+ if (debugLogger) await debugLogger.logStep('Inventory data loaded', {
+ itemCount: saveData.inventory.items?.length || 0
+ });
+ }
+
+ if (saveData.economy && this.systems.economy) {
+ console.log('[GAME ENGINE] Loading economy data...');
+ this.systems.economy.load(saveData.economy);
+ if (debugLogger) await debugLogger.logStep('Economy data loaded', {
+ credits: saveData.economy.credits,
+ gems: saveData.economy.gems
+ });
+ }
+
+ if (saveData.questSystem && this.systems.questSystem) {
+ console.log('[GAME ENGINE] Loading quest data...');
+ try {
+ this.systems.questSystem.load(saveData.questSystem);
+ if (debugLogger) await debugLogger.logStep('Quest data loaded', {
+ mainQuestsCount: saveData.questSystem.mainQuests?.length || 0,
+ dailyQuestsCount: saveData.questSystem.dailyQuests?.length || 0
+ });
+ console.log('[GAME ENGINE] Quest data loaded successfully');
+ } catch (questError) {
+ console.error('[GAME ENGINE] Failed to load quest data:', questError);
+ if (debugLogger) await debugLogger.errorEvent(questError, 'Quest data load failed');
+ // Continue with other systems instead of throwing
+ }
+ }
+
+ if (saveData.baseSystem && this.systems.baseSystem) {
+ console.log('[GAME ENGINE] Loading base data...');
+ try {
+ this.systems.baseSystem.load(saveData.baseSystem);
+ if (debugLogger) await debugLogger.logStep('Base data loaded', {
+ baseLevel: saveData.baseSystem.base?.level || 1,
+ roomsCount: saveData.baseSystem.base?.rooms?.length || 0
+ });
+ console.log('[GAME ENGINE] Base data loaded successfully');
+ } catch (baseError) {
+ console.error('[GAME ENGINE] Failed to load base data:', baseError);
+ if (debugLogger) await debugLogger.errorEvent(baseError, 'Base data load failed');
+ }
+ }
+
+ if (saveData.skillSystem && this.systems.skillSystem) {
+ console.log('[GAME ENGINE] Loading skill data...');
+ try {
+ this.systems.skillSystem.load(saveData.skillSystem);
+ if (debugLogger) await debugLogger.logStep('Skill data loaded');
+ console.log('[GAME ENGINE] Skill data loaded successfully');
+ } catch (skillError) {
+ console.error('[GAME ENGINE] Failed to load skill data:', skillError);
+ if (debugLogger) await debugLogger.errorEvent(skillError, 'Skill data load failed');
+ }
+ }
+
+ // CRITICAL: Initialize UIManager after loading save data
+ console.log('[GAME ENGINE] About to initialize UIManager - checking systems...');
+ console.log('[GAME ENGINE] Available systems:', Object.keys(this.systems));
+ console.log('[GAME ENGINE] UIManager exists:', !!this.systems.ui);
+
+ if (this.systems.ui) {
+ console.log('[GAME ENGINE] Initializing UIManager after save load...');
+ if (debugLogger) await debugLogger.logStep('Initializing UIManager after save load');
+ try {
+ await this.systems.ui.initialize();
+ console.log('[GAME ENGINE] UIManager initialized successfully');
+ if (debugLogger) await debugLogger.logStep('UIManager initialized successfully');
+ } catch (uiError) {
+ console.error('[GAME ENGINE] UIManager initialization failed:', uiError);
+ if (debugLogger) await debugLogger.errorEvent(uiError, 'UIManager initialization failed');
+ }
+ } else {
+ console.log('[GAME ENGINE] UIManager not found in systems!');
+ }
+
+ console.log('[GAME ENGINE] UIManager initialization section completed');
+
+ // Initialize DungeonSystem after UIManager is ready
+ if (this.systems.dungeonSystem) {
+ console.log('[GAME ENGINE] Initializing DungeonSystem...');
+ if (debugLogger) await debugLogger.logStep('Initializing DungeonSystem after save load');
+ try {
+ await this.systems.dungeonSystem.initialize();
+ console.log('[GAME ENGINE] DungeonSystem initialized successfully');
+ if (debugLogger) await debugLogger.logStep('DungeonSystem initialized successfully');
+ } catch (dungeonError) {
+ console.error('[GAME ENGINE] DungeonSystem initialization failed:', dungeonError);
+ if (debugLogger) await debugLogger.errorEvent(dungeonError, 'DungeonSystem initialization failed');
+ }
+ } else {
+ console.log('[GAME ENGINE] DungeonSystem not found in systems!');
+ }
+
+ // Restore game time
+ if (saveData.gameTime !== undefined) {
+ this.gameTime = saveData.gameTime;
+ console.log('[GAME ENGINE] Game time restored:', this.gameTime);
+ }
+
+ console.log('[GAME ENGINE] loadGame method completed successfully');
+
+ } catch (error) {
+ console.error('[GAME ENGINE] Failed to load game:', error);
+ if (logger) await logger.errorEvent(error, 'Game Load');
+ if (debugLogger) await debugLogger.errorEvent(error, 'loadGame');
+
+ if (debugLogger) await debugLogger.endStep('loadGame', {
+ success: false,
+ error: error.message
+ });
+
+ throw error;
+ }
+ }
+
+ async load() {
+ const logger = window.logger;
+ const debugLogger = window.debugLogger;
+
+ console.log('[GAME ENGINE] Load method called');
+ if (logger) await logger.info('Loading game');
+ if (debugLogger) await debugLogger.startStep('loadGame');
+
+ try {
+ if (debugLogger) await debugLogger.logStep('Loading save data', {
+ saveSlot: this.saveSlotInfo?.slot,
+ useFileSystem: this.saveSlotInfo?.useFileSystem
+ });
+
+ let saveData;
+
+ // Use file system if available, otherwise localStorage
+ if (this.saveSlotInfo && this.saveSlotInfo.useFileSystem && window.electronAPI) {
+ console.log('[GAME ENGINE] Loading via Electron API');
+ if (debugLogger) await debugLogger.logStep('Loading via Electron API');
+
+ const result = await window.electronAPI.loadGame(this.saveSlotInfo.slot);
+ if (result.success) {
+ saveData = result.data;
+ console.log('[GAME ENGINE] Game loaded successfully via Electron API');
+ if (debugLogger) await debugLogger.logStep('Game loaded via Electron API', {
+ slot: this.saveSlotInfo.slot,
+ dataSize: JSON.stringify(saveData).length
+ });
+ } else {
+ console.error('[GAME ENGINE] Failed to load via Electron API:', result.error);
+ if (debugLogger) await debugLogger.errorEvent(new Error(result.error), 'Electron API Load');
+ throw new Error(result.error);
+ }
+ } else {
+ // LocalStorage fallback
+ console.log('[GAME ENGINE] Loading from localStorage');
+ if (debugLogger) await debugLogger.logStep('Loading from localStorage');
+
+ const saveKey = `gso_save_slot_${this.saveSlotInfo.slot}`;
+ const saveString = localStorage.getItem(saveKey);
+
+ if (saveString) {
+ try {
+ saveData = JSON.parse(saveString);
+ console.log('[GAME ENGINE] Game loaded from localStorage');
+ if (debugLogger) await debugLogger.logStep('Game loaded from localStorage', {
+ saveKey,
+ dataSize: saveString.length
+ });
+ } catch (parseError) {
+ console.error('[GAME ENGINE] Failed to parse save data:', parseError);
+ if (debugLogger) await debugLogger.errorEvent(parseError, 'Parse Save Data');
+ throw new Error('Corrupted save data');
+ }
+ } else {
+ console.warn('[GAME ENGINE] No save data found in localStorage');
+ if (debugLogger) await debugLogger.warn('No save data found in localStorage', { saveKey });
+ throw new Error('No save data found');
+ }
+ }
+
+ // if (debugLogger) await debugLogger.logStep('Applying save data to systems');
+
+ // Apply save data to systems
+ if (saveData.player && this.systems.player) {
+ console.log('[GAME ENGINE] Loading player data...');
+ this.systems.player.load(saveData.player);
+ if (debugLogger) await debugLogger.logStep('Player data loaded', {
+ level: saveData.player?.stats?.level,
+ experience: saveData.player?.stats?.experience
+ });
+ }
+
+ if (saveData.inventory && this.systems.inventory) {
+ console.log('[GAME ENGINE] Loading inventory data...');
+ this.systems.inventory.load(saveData.inventory);
+ if (debugLogger) await debugLogger.logStep('Inventory data loaded', {
+ itemCount: saveData.inventory.items?.length || 0
+ });
+ }
+
+ if (saveData.economy && this.systems.economy) {
+ console.log('[GAME ENGINE] Loading economy data...');
+ this.systems.economy.load(saveData.economy);
+ if (debugLogger) await debugLogger.logStep('Economy data loaded', {
+ credits: saveData.economy.credits,
+ gems: saveData.economy.gems
+ });
+ }
+
+ if (saveData.gameTime) {
+ this.gameTime = saveData.gameTime;
+ if (debugLogger) await debugLogger.logStep('Game time restored', {
+ gameTime: this.gameTime
+ });
+ }
+
+ // Emit load event
+ this.emit('gameLoaded', { saveData });
+ if (debugLogger) debugLogger.logStep('Game loaded event emitted');
+
+ if (logger) await logger.info('Game loaded successfully');
+ if (debugLogger) await debugLogger.endStep('loadGame', {
+ gameTime: this.gameTime,
+ saveSlot: this.saveSlotInfo?.slot,
+ success: true
+ });
+
+ } catch (error) {
+ console.error('[GAME ENGINE] Load failed:', error);
+ if (logger) await logger.errorEvent(error, 'Game Load');
+ if (debugLogger) await debugLogger.errorEvent(error, 'Game Load');
+ throw error;
+ }
+ }
+
+ async newGame() {
+ const logger = window.logger;
+ const debugLogger = window.debugLogger;
+
+ console.log('[GAME ENGINE] Starting new game initialization');
+ if (logger) await logger.info('Starting new game');
+ if (debugLogger) await debugLogger.startStep('newGame');
+
+ try {
+ // For new games, we need to properly initialize systems with default data
+ if (debugLogger) await debugLogger.logStep('Initializing systems for new game');
+
+ // Initialize inventory with starting items
+ if (this.systems.inventory) {
+ console.log('[GAME ENGINE] Initializing inventory with starting items');
+ if (debugLogger) await debugLogger.logStep('Initializing inventory with starting items');
+ await this.systems.inventory.initialize();
+ }
+
+ // Initialize DungeonSystem for new game
+ if (this.systems.dungeonSystem) {
+ console.log('[GAME ENGINE] Initializing DungeonSystem for new game...');
+ if (debugLogger) await debugLogger.logStep('Initializing DungeonSystem for new game');
+ try {
+ await this.systems.dungeonSystem.initialize();
+ console.log('[GAME ENGINE] DungeonSystem initialized successfully for new game');
+ if (debugLogger) await debugLogger.logStep('DungeonSystem initialized successfully for new game');
+ } catch (dungeonError) {
+ console.error('[GAME ENGINE] DungeonSystem initialization failed for new game:', dungeonError);
+ if (debugLogger) await debugLogger.errorEvent(dungeonError, 'DungeonSystem initialization failed for new game');
+ }
+ } else {
+ console.log('[GAME ENGINE] DungeonSystem not found in systems!');
+ }
+
+ if (this.systems.player) {
+ console.log('[GAME ENGINE] Resetting player to initial state');
+ if (debugLogger) await debugLogger.logStep('Resetting player to initial state');
+ this.systems.player.resetToLevel1();
+ console.log('[GAME ENGINE] Player reset completed');
+ if (debugLogger) await debugLogger.logStep('Player reset completed', {
+ level: this.systems.player.stats.level,
+ experience: this.systems.player.stats.experience,
+ playTime: this.systems.player.stats.playTime
+ });
+
+ console.log('[GAME ENGINE] Setting up new player');
+ if (debugLogger) await debugLogger.logStep('Setting up new player');
+ this.systems.player.setupNewPlayer();
+ console.log('[GAME ENGINE] Player setup completed');
+ if (debugLogger) await debugLogger.logStep('Player setup completed', {
+ level: this.systems.player.stats.level,
+ experience: this.systems.player.stats.experience
+ });
+ } else {
+ console.error('[GAME ENGINE] Player system not available');
+ if (debugLogger) await debugLogger.error('Player system not available');
+ }
+
+ if (debugLogger) await debugLogger.logStep('Resetting economy system');
+ console.log('[GAME ENGINE] Step 2: Resetting economy system');
+
+ if (this.systems.economy) {
+ console.log('[GAME ENGINE] Calling economy.reset()');
+ if (debugLogger) await debugLogger.logStep('Calling economy.reset()');
+ this.systems.economy.reset();
+ console.log('[GAME ENGINE] Economy reset completed');
+ if (debugLogger) await debugLogger.logStep('Economy reset completed', {
+ credits: this.systems.economy.credits,
+ gems: this.systems.economy.gems
+ });
+ } else {
+ console.error('[GAME ENGINE] Economy system not available');
+ if (debugLogger) await debugLogger.error('Economy system not available');
+ }
+
+ // Skip inventory reset - initialize() already handles proper setup
+ if (debugLogger) await debugLogger.logStep('Skipping inventory reset - already initialized');
+ console.log('[GAME ENGINE] Skipping inventory reset - already initialized with starting items');
+
+ if (this.systems.inventory) {
+ if (debugLogger) await debugLogger.logStep('Inventory already initialized', {
+ itemCount: this.systems.inventory.items.length
+ });
+ } else {
+ console.error('[GAME ENGINE] Inventory system not available');
+ if (debugLogger) await debugLogger.error('Inventory system not available');
+ }
+
+ if (debugLogger) await debugLogger.logStep('Resetting quest system');
+ console.log('[GAME ENGINE] Step 4: Resetting quest system');
+
+ if (this.systems.questSystem) {
+ console.log('[GAME ENGINE] Calling questSystem.reset()');
+ if (debugLogger) await debugLogger.logStep('Calling questSystem.reset()');
+ this.systems.questSystem.reset();
+ console.log('[GAME ENGINE] Quest system reset completed');
+ if (debugLogger) await debugLogger.logStep('Quest system reset completed');
+
+ // Activate the tutorial quest for new games
+ console.log('[GAME ENGINE] Activating tutorial quest for new game');
+ if (debugLogger) await debugLogger.logStep('Activating tutorial quest');
+ this.systems.questSystem.startQuest('tutorial_complete');
+ console.log('[GAME ENGINE] Tutorial quest activated');
+ if (debugLogger) await debugLogger.logStep('Tutorial quest activated');
+ } else {
+ console.error('[GAME ENGINE] Quest system not available');
+ if (debugLogger) await debugLogger.error('Quest system not available');
+ }
+
+ if (debugLogger) await debugLogger.logStep('Resetting game time');
+ console.log('[GAME ENGINE] Step 5: Resetting game time');
+ this.gameTime = 0;
+ console.log('[GAME ENGINE] Game time reset to 0');
+ if (debugLogger) await debugLogger.logStep('Game time reset', { gameTime: this.gameTime });
+
+ if (logger) await logger.info('New game initialized successfully');
+ if (debugLogger) await debugLogger.endStep('newGame', {
+ gameTime: this.gameTime,
+ playerLevel: this.systems.player?.stats.level,
+ success: true
+ });
+
+ } catch (error) {
+ console.error('[GAME ENGINE] Failed to initialize new game:', error);
+ if (logger) await logger.errorEvent(error, 'New Game Initialization');
+ if (debugLogger) await debugLogger.errorEvent(error, 'New Game Initialization');
+ throw error;
+ }
+ }
+
+ // Utility methods
+ formatNumber(num) {
+ if (num >= 1e9) return (num / 1e9).toFixed(2) + 'B';
+ if (num >= 1e6) return (num / 1e6).toFixed(2) + 'M';
+ if (num >= 1e3) return (num / 1e3).toFixed(2) + 'K';
+ return num.toString();
+ }
+
+ formatTime(seconds) {
+ const hours = Math.floor(seconds / 3600);
+ const minutes = Math.floor((seconds % 3600) / 60);
+ const secs = Math.floor(seconds % 60);
+
+ if (hours > 0) {
+ return `${hours}h ${minutes}m ${secs}s`;
+ } else if (minutes > 0) {
+ return `${minutes}m ${secs}s`;
+ } else {
+ return `${secs}s`;
+ }
+ }
+
+ toggleDebugConsole() {
+ const debugLogger = window.debugLogger;
+ // if (debugLogger) debugLogger.logStep('Toggle debug console requested');
+
+ // Implementation would go here
+ // console.log('[GAME ENGINE] Debug console toggle requested');
+ }
+
+ getPerformanceStats() {
+ const debugLogger = window.debugLogger;
+
+ const stats = {
+ gameTime: this.gameTime,
+ isRunning: this.isRunning,
+ lastUpdate: this.lastUpdate,
+ memory: null
+ };
+
+ // Add memory info if available
+ if (window.performance && window.performance.memory) {
+ stats.memory = {
+ used: window.performance.memory.usedJSHeapSize,
+ total: window.performance.memory.totalJSHeapSize,
+ limit: window.performance.memory.jsHeapSizeLimit
+ };
+ }
+
+ // if (debugLogger) debugLogger.logStep('Performance stats requested', stats);
+
+ return stats;
+ }
+}
+
+// Global game instance
+let game = null;
+
+// Export GameEngine to global scope
+if (typeof window !== 'undefined') {
+ window.GameEngine = GameEngine;
+}
diff --git a/Client/js/core/Inventory.js b/Client/js/core/Inventory.js
new file mode 100644
index 0000000..7804cfa
--- /dev/null
+++ b/Client/js/core/Inventory.js
@@ -0,0 +1,1134 @@
+/**
+ * Galaxy Strike Online - Inventory System
+ * Manages player items, equipment, and storage
+ */
+
+class Inventory {
+ constructor(gameEngine) {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('Inventory.constructor', {
+ gameEngineProvided: !!gameEngine
+ });
+
+ this.game = gameEngine;
+
+ // Inventory configuration
+ this.maxSlots = 30;
+ this.baseMaxSlots = 30; // Base slots without starbase bonuses
+ this.maxStack = 999;
+
+ // Starbase inventory bonuses
+ this.starbaseBonusSlots = 0;
+ this.totalMaxSlots = this.baseMaxSlots + this.starbaseBonusSlots;
+
+ // Inventory data - ensure items is always an array
+ this.items = [];
+ this.equipment = {
+ weapon: null,
+ armor: null,
+ engine: null,
+ shield: null,
+ accessory: null
+ };
+
+ // Item categories
+ this.categories = {
+ weapon: 'Weapons',
+ armor: 'Armor',
+ engine: 'Engines',
+ shield: 'Shields',
+ accessory: 'Accessories',
+ consumable: 'Consumables',
+ material: 'Materials',
+ cosmetic: 'Cosmetics'
+ };
+
+ // Item rarities
+ this.rarities = {
+ common: { name: 'Common', color: '#888888', multiplier: 1 },
+ uncommon: { name: 'Uncommon', color: '#00ff00', multiplier: 1.2 },
+ rare: { name: 'Rare', color: '#0088ff', multiplier: 1.5 },
+ epic: { name: 'Epic', color: '#8833ff', multiplier: 2 },
+ legendary: { name: 'Legendary', color: '#ff8800', multiplier: 3 }
+ };
+
+ if (debugLogger) debugLogger.endStep('Inventory.constructor', {
+ maxSlots: this.maxSlots,
+ baseMaxSlots: this.baseMaxSlots,
+ maxStack: this.maxStack,
+ starbaseBonusSlots: this.starbaseBonusSlots,
+ totalMaxSlots: this.totalMaxSlots,
+ initialItemCount: this.items.length,
+ equipmentSlots: Object.keys(this.equipment).length,
+ categoriesCount: Object.keys(this.categories).length,
+ raritiesCount: Object.keys(this.rarities).length
+ });
+ }
+
+ async initialize() {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('Inventory.initialize', {
+ currentItemCount: this.items.length
+ });
+
+ // Initialize with starting items
+ if (this.items.length === 0) {
+ if (debugLogger) debugLogger.logStep('Adding starting items to empty inventory');
+ this.addStartingItems();
+ } else {
+ if (debugLogger) debugLogger.logStep('Inventory already has items, skipping starting items');
+ }
+
+ if (debugLogger) debugLogger.endStep('Inventory.initialize', {
+ finalItemCount: this.items.length,
+ startingItemsAdded: this.items.length > 0
+ });
+ }
+
+ addStartingItems() {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('Inventory.addStartingItems', {
+ currentItemCount: this.items.length,
+ maxSlots: this.maxSlots
+ });
+
+ const startingItems = [
+ {
+ id: 'starter_blaster_common',
+ name: 'Common Blaster',
+ type: 'weapon',
+ rarity: 'common',
+ quantity: 1,
+ stats: { attack: 5, criticalChance: 0.02 },
+ description: 'A reliable basic blaster for new pilots',
+ equipable: true,
+ slot: 'weapon'
+ },
+ {
+ id: 'basic_armor_common',
+ name: 'Basic Armor',
+ type: 'armor',
+ rarity: 'common',
+ quantity: 1,
+ stats: { defense: 3 },
+ description: 'Light armor providing basic protection',
+ equipable: true,
+ slot: 'armor'
+ }
+ ];
+
+ if (debugLogger) debugLogger.logStep('Adding starting items', {
+ startingItemCount: startingItems.length,
+ startingItems: startingItems.map(item => ({
+ id: item.id,
+ name: item.name,
+ type: item.type,
+ quantity: item.quantity
+ }))
+ });
+
+ startingItems.forEach(item => {
+ console.log(`[DEBUG] Adding starting item: ${item.name}`);
+ const result = this.addItem(item);
+ console.log(`[DEBUG] Starting item add result: ${result}, inventory size: ${this.items.length}`);
+ });
+
+ // Equip starter items
+ console.log('[INVENTORY] Equipping starter items');
+ if (debugLogger) debugLogger.logStep('Equipping starter items');
+
+ // Equip starter blaster
+ const blasterItem = this.items.find(item => item.id === 'starter_blaster');
+ if (blasterItem) {
+ console.log('[INVENTORY] Equipping starter blaster');
+ this.equipItem(blasterItem.id);
+ }
+
+ // Equip basic armor
+ const armorItem = this.items.find(item => item.id === 'basic_armor');
+ if (armorItem) {
+ console.log('[INVENTORY] Equipping basic armor');
+ this.equipItem(armorItem.id);
+ }
+
+ // Auto-stack starting items
+ if (debugLogger) debugLogger.logStep('Auto-stacking starting items');
+ this.autoStackItems();
+
+ if (debugLogger) debugLogger.endStep('Inventory.addStartingItems', {
+ finalItemCount: this.items.length,
+ itemsAdded: startingItems.length
+ });
+ }
+
+ // Item management
+ addItem(itemData, quantity = 1) {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('Inventory.addItem', {
+ itemData: itemData,
+ quantity: quantity,
+ currentItemCount: this.items.length,
+ maxSlots: this.maxSlots,
+ maxStack: this.maxStack
+ });
+
+ if (!itemData || !itemData.id) {
+ console.error('Invalid item data:', itemData);
+ if (debugLogger) debugLogger.errorEvent('Inventory.addItem', new Error('Invalid item data'), {
+ itemData: itemData,
+ quantity: quantity
+ });
+ return false;
+ }
+
+ const item = { ...itemData, quantity };
+
+ // Auto-stack: Check if exact same item already exists (same type, rarity, and stats)
+ const existingItem = this.items.find(i =>
+ i.id === item.id &&
+ i.type === item.type &&
+ i.rarity === item.rarity &&
+ JSON.stringify(i.stats) === JSON.stringify(item.stats)
+ );
+
+ if (existingItem) {
+ if (debugLogger) debugLogger.logStep('Found existing item for stacking', {
+ existingItemId: existingItem.id,
+ existingItemName: existingItem.name,
+ currentQuantity: existingItem.quantity,
+ addingQuantity: quantity,
+ maxStack: this.maxStack
+ });
+
+ // Stack items up to max stack
+ const totalQuantity = existingItem.quantity + quantity;
+ if (totalQuantity <= this.maxStack) {
+ existingItem.quantity = totalQuantity;
+ this.game.showNotification(`${item.name} +${quantity} (Stacked)`, 'success', 2000);
+
+ if (debugLogger) debugLogger.logStep('Item stacked successfully', {
+ itemId: existingItem.id,
+ oldQuantity: existingItem.quantity - quantity,
+ newQuantity: existingItem.quantity,
+ added: quantity
+ });
+ } else {
+ // Add to existing stack and create new item if needed
+ const remainingQuantity = totalQuantity - this.maxStack;
+ existingItem.quantity = this.maxStack;
+
+ if (debugLogger) debugLogger.logStep('Stack overflow, creating new item', {
+ itemId: existingItem.id,
+ maxStackReached: this.maxStack,
+ remainingQuantity: remainingQuantity,
+ currentSlots: this.items.length,
+ maxSlots: this.totalMaxSlots
+ });
+
+ // Try to add remaining quantity as new item
+ if (this.items.length < this.totalMaxSlots) {
+ const newItem = { ...itemData, quantity: remainingQuantity };
+ this.items.push(newItem);
+ this.game.showNotification(`${item.name} +${quantity} (Stack: ${this.maxStack}, New: ${remainingQuantity})`, 'success', 2000);
+
+ if (debugLogger) debugLogger.logStep('New item created for overflow', {
+ newItemId: newItem.id,
+ newItemQuantity: remainingQuantity,
+ totalSlotsUsed: this.items.length
+ });
+ } else {
+ this.game.showNotification(`${item.name} +${quantity} (Stack full: ${this.maxStack})`, 'warning', 3000);
+
+ if (debugLogger) debugLogger.logStep('Inventory full, overflow lost', {
+ lostQuantity: remainingQuantity,
+ maxSlots: this.totalMaxSlots
+ });
+ }
+ }
+ } else {
+ // Find empty slot
+ if (this.items.length >= this.totalMaxSlots) {
+ this.game.showNotification('Inventory is full!', 'error', 3000);
+
+ if (debugLogger) debugLogger.endStep('Inventory.addItem', {
+ success: false,
+ reason: 'Inventory full',
+ itemId: item.id,
+ itemName: item.name,
+ quantity: quantity,
+ currentSlots: this.items.length,
+ maxSlots: this.totalMaxSlots
+ });
+ return false;
+ }
+
+ this.items.push(item);
+ this.game.showNotification(`Acquired ${item.name}`, 'success', 2000);
+
+ if (debugLogger) debugLogger.logStep('New item added to inventory', {
+ itemId: item.id,
+ itemName: item.name,
+ itemType: item.type,
+ itemRarity: item.rarity,
+ quantity: quantity,
+ slotUsed: this.items.length - 1
+ });
+ }
+
+ this.autoStackItems(); // Reorganize inventory
+
+ if (debugLogger) debugLogger.endStep('Inventory.addItem', {
+ success: true,
+ itemId: item.id,
+ itemName: item.name,
+ quantity: quantity,
+ finalItemCount: this.items.length,
+ slotsUsed: this.items.length,
+ maxSlots: this.maxSlots
+ });
+
+ return true;
+ }
+
+ // Auto-stack and organize inventory
+ autoStackItems() {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('Inventory.autoStackItems', {
+ currentItemCount: this.items.length,
+ maxSlots: this.maxSlots,
+ maxStack: this.maxStack
+ });
+
+ const stackedItems = {};
+ const originalItemCount = this.items.length;
+
+ // Group items by exact ID, type, rarity, and stats
+ this.items.forEach(item => {
+ const stackKey = `${item.id}_${item.type}_${item.rarity}_${JSON.stringify(item.stats || {})}`;
+ if (!stackedItems[stackKey]) {
+ stackedItems[stackKey] = {
+ ...item,
+ quantity: 0
+ };
+ }
+ stackedItems[stackKey].quantity += item.quantity;
+ });
+
+ if (debugLogger) debugLogger.logStep('Items grouped for stacking', {
+ originalItemCount: originalItemCount,
+ stackGroupsCreated: Object.keys(stackedItems).length,
+ stackGroups: Object.entries(stackedItems).map(([key, item]) => ({
+ stackKey: key,
+ itemId: item.id,
+ itemName: item.name,
+ totalQuantity: item.quantity
+ }))
+ });
+
+ // Convert back to array and limit by max stack
+ const newItems = [];
+ const stackedValues = Object.values(stackedItems);
+
+ for (const item of stackedValues) {
+ if (newItems.length >= this.totalMaxSlots) break;
+
+ while (item.quantity > 0 && newItems.length < this.totalMaxSlots) {
+ const stackQuantity = Math.min(item.quantity, this.maxStack);
+ newItems.push({
+ ...item,
+ quantity: stackQuantity
+ });
+ item.quantity -= stackQuantity;
+ }
+ }
+
+ this.items = newItems;
+
+ if (debugLogger) debugLogger.endStep('Inventory.autoStackItems', {
+ originalItemCount: originalItemCount,
+ newItemCount: this.items.length,
+ stackGroupsProcessed: stackedValues.length,
+ slotsUsed: this.items.length,
+ maxSlots: this.maxSlots
+ });
+ }
+
+ removeItem(itemId, quantity = 1) {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('Inventory.removeItem', {
+ itemId: itemId,
+ quantity: quantity,
+ currentItemCount: this.items.length
+ });
+
+ const itemIndex = this.items.findIndex(item => item.id === itemId);
+ if (itemIndex === -1) {
+ if (debugLogger) debugLogger.endStep('Inventory.removeItem', {
+ success: false,
+ reason: 'Item not found',
+ itemId: itemId,
+ quantity: quantity
+ });
+ return false;
+ }
+
+ const item = this.items[itemIndex];
+ const oldQuantity = item.quantity;
+
+ if (item.quantity > quantity) {
+ item.quantity -= quantity;
+
+ if (debugLogger) debugLogger.logStep('Item quantity reduced', {
+ itemId: itemId,
+ itemName: item.name,
+ oldQuantity: oldQuantity,
+ newQuantity: item.quantity,
+ removed: quantity
+ });
+ } else {
+ this.items.splice(itemIndex, 1);
+
+ if (debugLogger) debugLogger.logStep('Item completely removed', {
+ itemId: itemId,
+ itemName: item.name,
+ oldQuantity: oldQuantity,
+ removed: oldQuantity,
+ itemIndex: itemIndex
+ });
+ }
+
+ if (debugLogger) debugLogger.endStep('Inventory.removeItem', {
+ success: true,
+ itemId: itemId,
+ itemName: item.name,
+ quantityRemoved: quantity,
+ finalItemCount: this.items.length,
+ itemStillExists: this.items.find(i => i.id === itemId) !== undefined
+ });
+
+ return true;
+ }
+
+ hasItem(itemId, quantity = 1) {
+ const item = this.items.find(item => item.id === itemId);
+ return item && item.quantity >= quantity;
+ }
+
+ getItemCount(itemId) {
+ const item = this.items.find(item => item.id === itemId);
+ return item ? item.quantity : 0;
+ }
+
+ // Equipment management
+ equipItem(itemId) {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('Inventory.equipItem', {
+ itemId: itemId,
+ currentItemCount: this.items.length,
+ currentEquipment: this.equipment
+ });
+
+ console.log('Attempting to equip item:', itemId);
+ const item = this.items.find(item => item.id === itemId);
+ console.log('Found item:', item);
+
+ if (!item) {
+ console.log('Item not found in inventory');
+ this.game.showNotification('Item not found', 'error', 3000);
+
+ if (debugLogger) debugLogger.endStep('Inventory.equipItem', {
+ success: false,
+ reason: 'Item not found',
+ itemId: itemId
+ });
+ return false;
+ }
+
+ if (!item.equipable) {
+ console.log('Item is not equipable:', item);
+ this.game.showNotification('This item cannot be equipped', 'error', 3000);
+
+ if (debugLogger) debugLogger.endStep('Inventory.equipItem', {
+ success: false,
+ reason: 'Item not equipable',
+ itemId: itemId,
+ itemName: item.name,
+ itemType: item.type
+ });
+ return false;
+ }
+
+ const slot = item.type;
+ console.log('Equipping to slot:', slot);
+ const currentItem = this.equipment[slot];
+
+ if (debugLogger) debugLogger.logStep('Equipment slot validation', {
+ itemId: itemId,
+ itemName: item.name,
+ itemType: item.type,
+ targetSlot: slot,
+ currentItemInSlot: currentItem ? currentItem.name : null,
+ itemStats: item.stats
+ });
+
+ // Unequip current item if exists
+ if (currentItem) {
+ console.log('Unequipping current item:', currentItem);
+
+ if (debugLogger) debugLogger.logStep('Unequipping current item', {
+ currentItemName: currentItem.name,
+ currentItemStats: currentItem.stats,
+ returningToInventory: true
+ });
+
+ this.addItem(currentItem, 1);
+ }
+
+ // Equip new item
+ this.equipment[slot] = item;
+ this.removeItem(itemId, 1);
+
+ // Apply item stats to player
+ this.applyEquipmentStats();
+
+ // Update UI
+ this.updateUI();
+
+ console.log('Successfully equipped:', item.name);
+ this.game.showNotification(`Equipped ${item.name}`, 'success', 3000);
+
+ if (debugLogger) debugLogger.endStep('Inventory.equipItem', {
+ success: true,
+ itemId: itemId,
+ itemName: item.name,
+ slot: slot,
+ previousItem: currentItem ? currentItem.name : null,
+ newEquipmentState: this.equipment,
+ finalItemCount: this.items.length
+ });
+
+ return true;
+ }
+
+ unequipItem(slot) {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('Inventory.unequipItem', {
+ slot: slot,
+ currentEquipment: this.equipment,
+ currentItemCount: this.items.length,
+ maxSlots: this.maxSlots
+ });
+
+ console.log('Attempting to unequip slot:', slot);
+ const item = this.equipment[slot];
+ if (!item) {
+ console.log('No item in slot:', slot);
+
+ if (debugLogger) debugLogger.endStep('Inventory.unequipItem', {
+ success: false,
+ reason: 'No item in slot',
+ slot: slot
+ });
+ return false;
+ }
+
+ // Check inventory space
+ if (this.items.length >= this.totalMaxSlots) {
+ console.log('Inventory is full, cannot unequip');
+ this.game.showNotification('Inventory is full!', 'error', 3000);
+
+ if (debugLogger) debugLogger.endStep('Inventory.unequipItem', {
+ success: false,
+ reason: 'Inventory full',
+ slot: slot,
+ itemName: item.name,
+ currentItemCount: this.items.length,
+ maxSlots: this.maxSlots
+ });
+ return false;
+ }
+
+ if (debugLogger) debugLogger.logStep('Unequipping item', {
+ slot: slot,
+ itemName: item.name,
+ itemStats: item.stats,
+ returningToInventory: true,
+ inventorySpaceAvailable: this.items.length < this.maxSlots
+ });
+
+ // Unequip item
+ this.equipment[slot] = null;
+ this.addItem(item, 1);
+
+ // Remove item stats from player
+ this.applyEquipmentStats();
+
+ // Update UI
+ this.updateUI();
+
+ console.log('Successfully unequipped:', item.name);
+ this.game.showNotification(`Unequipped ${item.name}`, 'info', 3000);
+
+ if (debugLogger) debugLogger.endStep('Inventory.unequipItem', {
+ success: true,
+ slot: slot,
+ itemName: item.name,
+ newEquipmentState: this.equipment,
+ finalItemCount: this.items.length
+ });
+
+ return true;
+ }
+
+ applyEquipmentStats() {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('Inventory.applyEquipmentStats', {
+ currentEquipment: this.equipment,
+ equippedItemsCount: Object.values(this.equipment).filter(item => item !== null).length
+ });
+
+ // Reset player stats to base
+ const player = this.game.systems.player;
+ const oldPlayerAttributes = { ...player.attributes };
+
+ if (debugLogger) debugLogger.logStep('Applying equipment bonuses', {
+ oldPlayerAttributes: oldPlayerAttributes,
+ equipmentToApply: Object.entries(this.equipment).filter(([slot, item]) => item !== null)
+ });
+
+ // Apply equipment bonuses
+ for (const [slot, item] of Object.entries(this.equipment)) {
+ if (item && item.stats) {
+ for (const [stat, value] of Object.entries(item.stats)) {
+ if (player.attributes[stat] !== undefined) {
+ const oldValue = player.attributes[stat];
+ player.attributes[stat] += value;
+
+ if (debugLogger) debugLogger.logStep('Stat bonus applied', {
+ slot: slot,
+ itemName: item.name,
+ stat: stat,
+ value: value,
+ oldValue: oldValue,
+ newValue: player.attributes[stat]
+ });
+ }
+ }
+ }
+ }
+
+ player.updateUI();
+
+ if (debugLogger) debugLogger.endStep('Inventory.applyEquipmentStats', {
+ oldPlayerAttributes: oldPlayerAttributes,
+ newPlayerAttributes: player.attributes,
+ statChanges: Object.entries(player.attributes).map(([stat, newValue]) => ({
+ stat: stat,
+ oldValue: oldPlayerAttributes[stat],
+ newValue: newValue,
+ change: newValue - oldPlayerAttributes[stat]
+ })).filter(change => change.change !== 0)
+ });
+ }
+
+ // Consumable usage
+ useItem(itemId) {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('Inventory.useItem', {
+ itemId: itemId,
+ currentItemCount: this.items.length
+ });
+
+ const item = this.items.find(item => item.id === itemId);
+ if (!item || !item.consumable) {
+ this.game.showNotification('This item cannot be used', 'error', 3000);
+
+ if (debugLogger) debugLogger.endStep('Inventory.useItem', {
+ success: false,
+ reason: !item ? 'Item not found' : 'Item not consumable',
+ itemId: itemId
+ });
+ return false;
+ }
+
+ if (debugLogger) debugLogger.logStep('Using consumable item', {
+ itemId: itemId,
+ itemName: item.name,
+ itemEffect: item.effect,
+ oldQuantity: item.quantity
+ });
+
+ const player = this.game.systems.player;
+ const oldPlayerHealth = player.attributes.health;
+ const oldPlayerEnergy = player.attributes.energy;
+
+ // Apply item effects
+ if (item.effect) {
+ if (item.effect.heal) {
+ player.heal(item.effect.heal);
+ this.game.showNotification(`Restored ${item.effect.heal} health`, 'success', 2000);
+
+ if (debugLogger) debugLogger.logStep('Healing effect applied', {
+ healAmount: item.effect.heal,
+ oldHealth: oldPlayerHealth,
+ newHealth: player.attributes.health
+ });
+ }
+
+ if (item.effect.energy) {
+ player.restoreEnergy(item.effect.energy);
+ this.game.showNotification(`Restored ${item.effect.energy} energy`, 'success', 2000);
+
+ if (debugLogger) debugLogger.logStep('Energy effect applied', {
+ energyAmount: item.effect.energy,
+ oldEnergy: oldPlayerEnergy,
+ newEnergy: player.attributes.energy
+ });
+ }
+
+ if (item.effect.buff) {
+ // Apply temporary buffs (would need buff system)
+ this.game.showNotification(`Buff applied: ${item.effect.buff}`, 'success', 2000);
+
+ if (debugLogger) debugLogger.logStep('Buff effect applied', {
+ buffType: item.effect.buff
+ });
+ }
+ }
+
+ // Remove consumed item
+ this.removeItem(itemId, 1);
+
+ if (debugLogger) debugLogger.endStep('Inventory.useItem', {
+ success: true,
+ itemId: itemId,
+ itemName: item.name,
+ effectsApplied: item.effect,
+ finalItemCount: this.items.length
+ });
+
+ return true;
+ }
+
+ // Item generation
+ generateItem(type, rarity = 'common', level = 1) {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('Inventory.generateItem', {
+ type: type,
+ rarity: rarity,
+ level: level
+ });
+
+ const itemTemplates = {
+ weapon: {
+ common: [
+ { name: 'Basic Blaster', stats: { attack: 5 } },
+ { name: 'Laser Rifle', stats: { attack: 6 } }
+ ],
+ uncommon: [
+ { name: 'Plasma Cannon', stats: { attack: 8, criticalChance: 0.02 } },
+ { name: 'Ion Blaster', stats: { attack: 7, speed: 2 } }
+ ],
+ rare: [
+ { name: 'Quantum Rifle', stats: { attack: 12, criticalChance: 0.05 } },
+ { name: 'Fusion Cannon', stats: { attack: 15, criticalDamage: 0.2 } }
+ ]
+ },
+ armor: {
+ common: [
+ { name: 'Basic Plating', stats: { defense: 3, maxHealth: 10 } },
+ { name: 'Light Armor', stats: { defense: 4, speed: -1 } }
+ ],
+ uncommon: [
+ { name: 'Reinforced Plating', stats: { defense: 6, maxHealth: 20 } },
+ { name: 'Energy Shield', stats: { defense: 5, maxEnergy: 10 } }
+ ],
+ rare: [
+ { name: 'Titanium Armor', stats: { defense: 10, maxHealth: 40 } },
+ { name: 'Phase Shield', stats: { defense: 8, criticalChance: -0.05 } }
+ ]
+ }
+ };
+
+ const templates = itemTemplates[type]?.[rarity] || itemTemplates.weapon.common;
+ const template = templates[Math.floor(Math.random() * templates.length)];
+
+ const rarityData = this.rarities[rarity];
+ const levelMultiplier = 1 + (level - 1) * 0.1;
+
+ const generatedItem = {
+ id: `${type}_${rarity}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
+ name: template.name,
+ type: type,
+ rarity: rarity,
+ quantity: 1,
+ stats: Object.fromEntries(
+ Object.entries(template.stats).map(([stat, value]) => [
+ stat,
+ Math.floor(value * rarityData.multiplier * levelMultiplier)
+ ])
+ ),
+ description: `Level ${level} ${rarityData.name} ${type}`,
+ equipable: true,
+ value: Math.floor(50 * rarityData.multiplier * levelMultiplier)
+ };
+
+ if (debugLogger) debugLogger.endStep('Inventory.generateItem', {
+ type: type,
+ rarity: rarity,
+ level: level,
+ templateUsed: template.name,
+ rarityMultiplier: rarityData.multiplier,
+ levelMultiplier: levelMultiplier,
+ generatedItem: generatedItem,
+ finalStats: generatedItem.stats,
+ itemValue: generatedItem.value
+ });
+
+ return generatedItem;
+ }
+
+ // UI updates
+ clear() {
+ const debugLogger = window.debugLogger;
+ const oldState = {
+ itemCount: this.items.length,
+ maxSlots: this.maxSlots,
+ cargo: this.cargo ? this.cargo.length : 0,
+ maxCargo: this.maxCargo
+ };
+
+ if (debugLogger) debugLogger.startStep('Inventory.clear', {
+ oldState: oldState
+ });
+
+ this.items = [];
+ this.maxSlots = 30;
+ this.cargo = [];
+ this.maxCargo = 100;
+ this.updateUI();
+
+ if (debugLogger) debugLogger.endStep('Inventory.clear', {
+ oldState: oldState,
+ newState: {
+ itemCount: this.items.length,
+ maxSlots: this.maxSlots,
+ cargo: this.cargo.length,
+ maxCargo: this.maxCargo
+ }
+ });
+ }
+
+ reset() {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('Inventory.reset');
+
+ this.clear();
+
+ if (debugLogger) debugLogger.endStep('Inventory.reset', {
+ cleared: true
+ });
+ }
+
+ updateUI() {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('Inventory.updateUI', {
+ itemCount: this.items.length,
+ equipmentCount: Object.values(this.equipment).filter(item => item !== null).length
+ });
+
+ this.updateInventoryGrid();
+ this.updateEquipmentDisplay();
+
+ if (debugLogger) debugLogger.endStep('Inventory.updateUI', {
+ inventoryGridUpdated: true,
+ equipmentDisplayUpdated: true
+ });
+ }
+
+ updateInventoryGrid() {
+ const grid = document.getElementById('inventoryGrid');
+ if (!grid) return;
+
+ grid.innerHTML = '';
+
+ // Create inventory slots using total max slots (base + starbase bonuses)
+ for (let i = 0; i < this.totalMaxSlots; i++) {
+ const slot = document.createElement('div');
+ slot.className = 'inventory-slot';
+
+ // Add special styling for starbase bonus slots
+ if (i >= this.baseMaxSlots) {
+ slot.classList.add('starbase-bonus-slot');
+ }
+
+ const item = this.items[i];
+
+ if (item) {
+ const iconHtml = this.getItemIconHtml(item.type, '24px');
+ const quantityBadge = item.quantity > 1 ? `${item.quantity}
` : '';
+
+ slot.innerHTML = `
+
+
+ ${iconHtml}
+
+
+
${item.name}
+
+ ${this.rarities[item.rarity].name}
+
+
+ ${quantityBadge}
+
+ `;
+
+ // Add click handlers
+ const itemCard = slot.querySelector('.item-card');
+ itemCard.addEventListener('click', () => this.showItemDetails(item));
+
+ if (item.equipable) {
+ itemCard.addEventListener('contextmenu', (e) => {
+ e.preventDefault();
+ this.equipItem(item.id);
+ });
+ }
+
+ if (item.consumable) {
+ itemCard.addEventListener('dblclick', () => this.useItem(item.id));
+ }
+ } else {
+ slot.innerHTML = '
';
+ }
+
+ grid.appendChild(slot);
+ }
+ }
+
+ updateEquipmentDisplay() {
+ // Update equipment slots in UI
+ for (const [slot, item] of Object.entries(this.equipment)) {
+ const slotElement = document.getElementById(`equip-${slot}`);
+ if (slotElement) {
+ if (item) {
+ const iconHtml = this.getItemIconHtml(item.type, '24px');
+ slotElement.innerHTML = `
+
+ ${iconHtml}
+
${item.name}
+
+ `;
+
+ // Add click handler to unequip
+ slotElement.onclick = () => this.unequipItem(slot);
+ } else {
+ slotElement.innerHTML = `Empty
`;
+ slotElement.onclick = null;
+ }
+ }
+ }
+ }
+
+ showItemDetails(item) {
+ const detailsElement = document.getElementById('itemDetails');
+ if (!detailsElement) return;
+
+ const rarityData = this.rarities[item.rarity];
+ const statsHtml = item.stats ?
+ Object.entries(item.stats)
+ .map(([stat, value]) => `${stat}: +${value}
`)
+ .join('') : '';
+
+ detailsElement.innerHTML = `
+ ${item.name}
+ ${item.description}
+ Type: ${this.categories[item.type]}
+ Rarity: ${rarityData.name}
+ ${item.quantity > 1 ? `Quantity: ${item.quantity}
` : ''}
+ ${statsHtml ? `${statsHtml}
` : ''}
+
+ ${item.equipable ? `Equip ` : ''}
+ ${item.consumable ? `Use ` : ''}
+
+ `;
+ }
+
+ getItemIcon(type) {
+ const icons = {
+ weapon: 'fa-sword',
+ armor: 'fa-shield-alt',
+ engine: 'fa-rocket',
+ shield: 'fa-shield-virus',
+ accessory: 'fa-ring',
+ consumable: 'fa-flask',
+ material: 'fa-cube',
+ cosmetic: 'fa-star'
+ };
+
+ const iconClass = icons[type] || 'fa-cube';
+
+ // Use texture manager for icon fallback if available
+ if (this.game.systems.textureManager) {
+ return this.game.systems.textureManager.getIcon(iconClass);
+ }
+
+ return iconClass;
+ }
+
+ getItemIconHtml(type, size = '32px') {
+ // Use texture manager for icon HTML if available
+ if (this.game.systems.textureManager && typeof this.game.systems.textureManager.getItemIconElement === 'function') {
+ return this.game.systems.textureManager.getItemIconElement(this.getItemIcon(type), size);
+ }
+
+ // Fallback to FontAwesome
+ const iconClass = this.getItemIcon(type);
+ return ` `;
+ }
+
+ // Starbase inventory integration methods
+ updateStarbaseBonusSlots(bonusSlots) {
+ this.starbaseBonusSlots = bonusSlots;
+ this.totalMaxSlots = this.baseMaxSlots + this.starbaseBonusSlots;
+}
+
+ getInventoryInfo() {
+ return {
+ currentSlots: this.items.length,
+ baseMaxSlots: this.baseMaxSlots,
+ starbaseBonusSlots: this.starbaseBonusSlots,
+ totalMaxSlots: this.totalMaxSlots,
+ availableSlots: this.totalMaxSlots - this.items.length
+ };
+ }
+
+ // Save/Load
+ save() {
+ const debugLogger = window.debugLogger;
+
+ // if (debugLogger) debugLogger.startStep('Inventory.save', {
+ // itemCount: this.items.length,
+ // equipmentCount: Object.values(this.equipment).filter(item => item !== null).length,
+ // baseMaxSlots: this.baseMaxSlots,
+ // starbaseBonusSlots: this.starbaseBonusSlots
+ // });
+
+ const saveData = {
+ items: this.items,
+ equipment: this.equipment,
+ baseMaxSlots: this.baseMaxSlots,
+ starbaseBonusSlots: this.starbaseBonusSlots
+ };
+
+ // if (debugLogger) debugLogger.endStep('Inventory.save', {
+ // saveDataSize: JSON.stringify(saveData).length,
+ // itemsSaved: this.items.length,
+ // equipmentSaved: Object.keys(this.equipment).length,
+ // saveData: saveData
+ // });
+
+ return saveData;
+ }
+
+ load(data) {
+ const debugLogger = window.debugLogger;
+ const oldState = {
+ itemCount: this.items.length,
+ equipmentCount: Object.values(this.equipment).filter(item => item !== null).length,
+ baseMaxSlots: this.baseMaxSlots,
+ starbaseBonusSlots: this.starbaseBonusSlots
+ };
+
+ if (debugLogger) debugLogger.startStep('Inventory.load', {
+ oldState: oldState,
+ loadData: data
+ });
+
+ try {
+ if (data.items) {
+ // Ensure items is always an array
+ this.items = Array.isArray(data.items) ? data.items : [];
+
+ if (debugLogger) debugLogger.logStep('Loading items', {
+ itemsLoaded: this.items.length,
+ itemsArray: Array.isArray(data.items),
+ autoStacking: true
+ });
+
+ this.autoStackItems();
+ }
+
+ if (data.equipment) {
+ this.equipment = data.equipment;
+
+ if (debugLogger) debugLogger.logStep('Loading equipment', {
+ equipmentLoaded: Object.keys(data.equipment).length,
+ equipmentState: data.equipment
+ });
+ }
+
+ // Load starbase bonus slots
+ if (data.baseMaxSlots !== undefined) {
+ this.baseMaxSlots = data.baseMaxSlots;
+
+ if (debugLogger) debugLogger.logStep('Loading base max slots', {
+ oldBaseMaxSlots: oldState.baseMaxSlots,
+ newBaseMaxSlots: this.baseMaxSlots
+ });
+ }
+
+ if (data.starbaseBonusSlots !== undefined) {
+ this.starbaseBonusSlots = data.starbaseBonusSlots;
+
+ if (debugLogger) debugLogger.logStep('Loading starbase bonus slots', {
+ oldStarbaseBonusSlots: oldState.starbaseBonusSlots,
+ newStarbaseBonusSlots: this.starbaseBonusSlots
+ });
+ }
+
+ // Recalculate total max slots
+ this.totalMaxSlots = this.baseMaxSlots + this.starbaseBonusSlots;
+
+ if (debugLogger) debugLogger.logStep('Recalculating total max slots', {
+ baseMaxSlots: this.baseMaxSlots,
+ starbaseBonusSlots: this.starbaseBonusSlots,
+ totalMaxSlots: this.totalMaxSlots
+ });
+
+ // Update UI to show loaded equipment
+ this.updateUI();
+
+ if (debugLogger) debugLogger.endStep('Inventory.load', {
+ success: true,
+ oldState: oldState,
+ newState: {
+ itemCount: this.items.length,
+ equipmentCount: Object.values(this.equipment).filter(item => item !== null).length,
+ baseMaxSlots: this.baseMaxSlots,
+ starbaseBonusSlots: this.starbaseBonusSlots,
+ totalMaxSlots: this.totalMaxSlots
+ }
+ });
+ } catch (error) {
+ if (debugLogger) debugLogger.errorEvent('Inventory.load', error, {
+ oldState: oldState,
+ loadData: data,
+ error: error.message
+ });
+ throw error;
+ }
+ }
+}
diff --git a/Client/js/core/Logger.js b/Client/js/core/Logger.js
new file mode 100644
index 0000000..26b2221
--- /dev/null
+++ b/Client/js/core/Logger.js
@@ -0,0 +1,306 @@
+/**
+ * Galaxy Strike Online - Logging System
+ * Provides file-based logging with rotation and formatting
+ * Renderer process version that uses IPC to communicate with main process
+ */
+
+class Logger {
+ constructor() {
+ this.logLevel = 'INFO';
+ this.isRenderer = typeof window !== 'undefined' && typeof window.electronAPI !== 'undefined';
+ this.timers = new Map();
+
+ this.levels = {
+ ERROR: 0,
+ WARN: 1,
+ INFO: 2,
+ DEBUG: 3
+ };
+ }
+
+ async initialize(appDataPath) {
+ if (this.isRenderer) {
+ // In renderer process, just log that we're ready
+ console.log('Logger initialized in renderer process');
+ return;
+ }
+
+ // Main process initialization (original code)
+ const fs = require('fs').promises;
+ const path = require('path');
+
+ try {
+ // Set up log directory in app storage location
+ this.logDir = path.join(appDataPath, 'logs');
+
+ // Create logs directory if it doesn't exist
+ await fs.mkdir(this.logDir, { recursive: true });
+
+ // Set current log file with full timestamp (YYYY-MM-DD-HH-MM-SS)
+ const now = new Date();
+ const timestamp = now.toISOString()
+ .replace(/T/, '-')
+ .replace(/\..+/, '')
+ .replace(/:/g, '-');
+ this.currentLogFile = path.join(this.logDir, `galaxy-strike-${timestamp}.log`);
+
+ // Test write to ensure permissions
+ await this.writeToFile('=== Galaxy Strike Online Log Session Started ===\n');
+
+ this.isInitialized = true;
+ console.log(`Logger initialized: ${this.logDir}`);
+
+ } catch (error) {
+ console.error('Failed to initialize logger:', error);
+ this.isInitialized = false;
+ }
+ }
+
+ async writeToFile(message) {
+ if (this.isRenderer) {
+ // In renderer process, send to main process via IPC
+ return;
+ }
+
+ if (!this.isInitialized || !this.currentLogFile) return;
+
+ try {
+ const fs = require('fs').promises;
+
+ // Check if file needs rotation
+ await this.rotateLogIfNeeded();
+
+ // Append message to current log file
+ await fs.appendFile(this.currentLogFile, message, 'utf8');
+ } catch (error) {
+ console.error('Failed to write to log file:', error);
+ }
+ }
+
+ async rotateLogIfNeeded() {
+ if (this.isRenderer) return;
+
+ const fs = require('fs').promises;
+ const path = require('path');
+
+ try {
+ const stats = await fs.stat(this.currentLogFile);
+
+ if (stats.size >= this.maxFileSize) {
+ // Rotate log file with full timestamp
+ const now = new Date();
+ const timestamp = now.toISOString()
+ .replace(/T/, '-')
+ .replace(/\..+/, '')
+ .replace(/:/g, '-');
+ const rotatedFile = path.join(this.logDir, `galaxy-strike-${timestamp}.log`);
+
+ await fs.rename(this.currentLogFile, rotatedFile);
+
+ // Clean up old log files
+ await this.cleanupOldLogs();
+
+ // Create new current log file with new timestamp
+ const newTimestamp = new Date().toISOString()
+ .replace(/T/, '-')
+ .replace(/\..+/, '')
+ .replace(/:/g, '-');
+ this.currentLogFile = path.join(this.logDir, `galaxy-strike-${newTimestamp}.log`);
+
+ await this.writeToFile('=== Log Rotated ===\n');
+ }
+ } catch (error) {
+ // File might not exist yet, which is fine
+ if (error.code !== 'ENOENT') {
+ console.error('Failed to rotate log:', error);
+ }
+ }
+ }
+
+ async cleanupOldLogs() {
+ if (this.isRenderer) return;
+
+ const fs = require('fs').promises;
+ const path = require('path');
+
+ try {
+ const files = await fs.readdir(this.logDir);
+ const logFiles = files
+ .filter(file => file.startsWith('galaxy-strike-') && file.endsWith('.log'))
+ .map(file => ({
+ name: file,
+ path: path.join(this.logDir, file)
+ }));
+
+ if (logFiles.length > this.maxLogFiles) {
+ // Get file stats and sort by modification time
+ const filesWithStats = await Promise.all(
+ logFiles.map(async file => {
+ const stats = await fs.stat(file.path);
+ return { ...file, mtime: stats.mtime };
+ })
+ );
+
+ filesWithStats.sort((a, b) => b.mtime - a.mtime);
+
+ // Delete oldest files
+ const filesToDelete = filesWithStats.slice(this.maxLogFiles);
+ for (const file of filesToDelete) {
+ await fs.unlink(file.path);
+ }
+ }
+ } catch (error) {
+ console.error('Failed to cleanup old logs:', error);
+ }
+ }
+
+ formatMessage(level, message, data = null) {
+ const timestamp = new Date().toISOString();
+ const dataStr = data ? ` | Data: ${JSON.stringify(data)}` : '';
+ return `[${timestamp}] [${level}] ${message}${dataStr}\n`;
+ }
+
+ shouldLog(level) {
+ const currentLevel = this.levels[this.logLevel] || 2;
+ const messageLevel = this.levels[level] || 2;
+ return messageLevel <= currentLevel;
+ }
+
+ async log(level, message, data = null) {
+ if (!this.shouldLog(level)) return;
+
+ if (this.isRenderer && window.electronAPI) {
+ // Send to main process via IPC - ensure data is serializable
+ try {
+ const serializableData = data ? this.makeSerializable(data) : null;
+ window.electronAPI.log(level, message, serializableData);
+ } catch (error) {
+ console.error('Failed to send log to main process:', error);
+ // Fallback to console
+ console.log(`[${level}] ${message}`, data || '');
+ }
+ } else {
+ // Main process logging
+ const formattedMessage = this.formatMessage(level, message, data);
+ await this.writeToFile(formattedMessage);
+ console.log(`[${level}] ${message}`, data || '');
+ }
+ }
+
+ makeSerializable(obj) {
+ try {
+ // Convert to JSON and back to ensure it's serializable
+ return JSON.parse(JSON.stringify(obj));
+ } catch (error) {
+ // If not serializable, convert to string representation
+ return {
+ type: 'non-serializable',
+ toString: obj.toString ? obj.toString() : String(obj),
+ constructor: obj.constructor ? obj.constructor.name : 'Unknown'
+ };
+ }
+ }
+
+ async error(message, data = null) {
+ await this.log('error', message, data);
+ console.error(`[ERROR] ${message}`, data || '');
+ }
+
+ async warn(message, data = null) {
+ await this.log('warn', message, data);
+ console.warn(`[WARN] ${message}`, data || '');
+ }
+
+ async info(message, data = null) {
+ await this.log('info', message, data);
+ console.info(`[INFO] ${message}`, data || '');
+ }
+
+ async debug(message, data = null) {
+ await this.log('debug', message, data);
+ console.debug(`[DEBUG] ${message}`, data || '');
+ }
+
+ async gameEvent(eventType, details) {
+ const message = `Game Event: ${eventType}`;
+ await this.info(message, details);
+ }
+
+ // Timing methods
+ startTimer(name) {
+ this.timers.set(name, performance.now());
+ }
+
+ endTimer(name) {
+ const startTime = this.timers.get(name);
+ if (startTime) {
+ const duration = performance.now() - startTime;
+ this.timers.delete(name);
+ return duration;
+ }
+ return 0;
+ }
+
+ async timeAsync(name, asyncFunction) {
+ this.startTimer(name);
+ try {
+ const result = await asyncFunction();
+ const duration = this.endTimer(name);
+ await this.info(`${name} completed`, { duration: `${duration.toFixed(2)}ms` });
+ return result;
+ } catch (error) {
+ const duration = this.endTimer(name);
+ await this.error(`${name} failed`, { duration: `${duration.toFixed(2)}ms`, error: error.message });
+ throw error;
+ }
+ }
+
+ async playerAction(action, details) {
+ const message = `Player Action: ${action}`;
+ await this.info(message, details);
+ }
+
+ async systemEvent(system, event, details) {
+ const message = `System Event: ${system} - ${event}`;
+ await this.info(message, details);
+ }
+
+ async errorEvent(error, context = null) {
+ const message = `Error Event: ${error.message || error}`;
+ const data = {
+ stack: error.stack,
+ context: context
+ };
+ await this.error(message, data);
+ }
+
+ setLogLevel(level) {
+ if (this.levels.hasOwnProperty(level)) {
+ this.logLevel = level;
+ this.info(`Log level changed to: ${level}`);
+ }
+ }
+
+ getLogInfo() {
+ return {
+ isInitialized: this.isInitialized,
+ isRenderer: this.isRenderer,
+ logDirectory: this.logDir,
+ currentLogFile: this.currentLogFile,
+ logLevel: this.logLevel
+ };
+ }
+}
+
+// Export singleton instance
+const logger = new Logger();
+
+// For Node.js environments
+if (typeof module !== 'undefined' && module.exports) {
+ module.exports = logger;
+}
+
+// For browser environments
+if (typeof window !== 'undefined') {
+ window.logger = logger;
+}
diff --git a/Client/js/core/Player.js b/Client/js/core/Player.js
new file mode 100644
index 0000000..c244c42
--- /dev/null
+++ b/Client/js/core/Player.js
@@ -0,0 +1,910 @@
+/**
+ * Galaxy Strike Online - Player System
+ * Manages player stats, levels, and progression
+ */
+
+class Player {
+ constructor(gameEngine) {
+ const debugLogger = window.debugLogger;
+ if (debugLogger) debugLogger.log('Player constructor called');
+
+ this.game = gameEngine;
+
+ // Player stats
+ this.stats = {
+ level: 1,
+ experience: 0,
+ totalXP: 0, // Total accumulated XP across all levels
+ experienceToNext: window.XPProgression ? window.XPProgression.calculateXPToNextLevel(1, 0) : 100,
+ skillPoints: 0,
+ totalKills: 0,
+ dungeonsCleared: 0,
+ playTime: 0,
+ lastLogin: Date.now()
+ };
+
+ // Base attributes
+ this.attributes = {
+ health: 100,
+ maxHealth: 100,
+ energy: 100,
+ maxEnergy: 100,
+ attack: 10,
+ defense: 5,
+ speed: 10,
+ criticalChance: 0.05,
+ criticalDamage: 1.5
+ };
+
+ // Player info
+ this.info = {
+ name: 'Commander',
+ title: 'Rookie Pilot',
+ guild: null,
+ rank: 'Cadet'
+ };
+
+ // Ship info
+ this.ship = {
+ name: 'Starter Cruiser',
+ class: 'Cruiser',
+ health: 100,
+ maxHealth: 100,
+ attack: 10,
+ defense: 5,
+ speed: 10,
+ criticalChance: 0.05,
+ criticalDamage: 1.5,
+ level: 1,
+ upgrades: []
+ };
+
+ // Settings
+ this.settings = {
+ autoSave: true,
+ notifications: true,
+ soundEffects: true,
+ music: false,
+ discordIntegration: false
+ };
+
+ if (debugLogger) debugLogger.log('Player constructor completed', {
+ initialLevel: this.stats.level,
+ initialHealth: this.attributes.health,
+ shipName: this.ship.name,
+ shipClass: this.ship.class
+ });
+ }
+
+ async initialize() {
+ const debugLogger = window.debugLogger;
+ console.log('[PLAYER] Player system initializing');
+ if (debugLogger) await debugLogger.startStep('playerInitialize');
+
+ try {
+ if (debugLogger) await debugLogger.logStep('Player initialization started');
+ // Player initialization is handled by GameEngine
+ // This method is kept for compatibility but doesn't load game data
+ console.log('[PLAYER] Player system initialization completed');
+ if (debugLogger) await debugLogger.endStep('playerInitialize');
+ } catch (error) {
+ console.error('[PLAYER] Error during initialization:', error);
+ if (debugLogger) await debugLogger.errorEvent(error, 'Player Initialize');
+ }
+ }
+
+ setupNewPlayer() {
+ const debugLogger = window.debugLogger;
+ console.log('[PLAYER] Setting up new player');
+ if (debugLogger) debugLogger.logStep('Setting up new player', {
+ currentLevel: this.stats.level,
+ currentTitle: this.info.title
+ });
+
+ this.game.showNotification('Welcome to Galaxy Strike Online, Commander!', 'success', 5000);
+ this.game.showNotification('Complete quests and explore dungeons to progress!', 'info', 4000);
+
+ if (debugLogger) debugLogger.logStep('New player setup completed', {
+ notificationsShown: 2
+ });
+ }
+
+ // Experience and leveling
+ addExperience(amount) {
+ const debugLogger = window.debugLogger;
+ const oldExperience = this.stats.experience;
+ const oldLevel = this.stats.level;
+ const oldTotalXP = this.stats.totalXP;
+
+ // Add to total accumulated XP
+ this.stats.totalXP += amount;
+
+ // Calculate new level based on total XP
+ if (window.XPProgression) {
+ const levelInfo = window.XPProgression.getLevelFromXP(this.stats.totalXP);
+ this.stats.level = levelInfo.level;
+ this.stats.experience = levelInfo.xpIntoLevel;
+ this.stats.experienceToNext = levelInfo.xpToNext;
+ } else {
+ // Fallback to old system
+ this.stats.experience += amount;
+ }
+
+ if (debugLogger) debugLogger.logStep('Experience added', {
+ amount: amount,
+ oldExperience: oldExperience,
+ newExperience: this.stats.experience,
+ oldTotalXP: oldTotalXP,
+ newTotalXP: this.stats.totalXP,
+ experienceToNext: this.stats.experienceToNext,
+ currentLevel: oldLevel,
+ newLevel: this.stats.level
+ });
+
+ // Check for level up
+ const levelsGained = this.stats.level - oldLevel;
+ if (levelsGained > 0) {
+ for (let i = 0; i < levelsGained; i++) {
+ this.levelUp();
+ }
+ }
+
+ if (debugLogger && levelsGained > 0) {
+ debugLogger.logStep('Level up(s) occurred', {
+ levelsGained: levelsGained,
+ newLevel: this.stats.level,
+ remainingExperience: this.stats.experience,
+ totalXP: this.stats.totalXP
+ });
+ }
+
+ this.game.showNotification(`+${this.game.formatNumber(amount)} XP`, 'success', 2000);
+ }
+
+ levelUp() {
+ const debugLogger = window.debugLogger;
+ const oldLevel = this.stats.level;
+ const oldSkillPoints = this.stats.skillPoints;
+ const oldMaxHealth = this.attributes.maxHealth;
+ const oldMaxEnergy = this.attributes.maxEnergy;
+ const oldAttack = this.attributes.attack;
+ const oldDefense = this.attributes.defense;
+ const oldShipMaxHealth = this.ship.maxHealth;
+ const oldTitle = this.info.title;
+
+ console.log(`[PLAYER] Level up! New level: ${this.stats.level}`);
+ if (debugLogger) debugLogger.logStep('Level up initiated', {
+ oldLevel: oldLevel,
+ newLevel: this.stats.level,
+ skillPointsGained: 2,
+ totalSkillPoints: this.stats.skillPoints + 2,
+ currentXP: this.stats.experience,
+ totalXP: this.stats.totalXP
+ });
+
+ // Update quest progress for level objectives
+ if (this.game.systems.questSystem) {
+ this.game.systems.questSystem.updateLevelProgress(this.stats.level);
+ if (debugLogger) debugLogger.logStep('Quest progress updated for new level');
+ }
+
+ // Update experience requirement for next level
+ if (window.XPProgression) {
+ const levelInfo = window.XPProgression.getLevelFromXP(this.stats.totalXP);
+ this.stats.experienceToNext = levelInfo.xpToNext;
+ } else {
+ // Fallback to old system
+ this.stats.experienceToNext = Math.floor(this.stats.experienceToNext * 1.5);
+ }
+
+ if (debugLogger) debugLogger.logStep('Experience requirement updated', {
+ newRequirement: this.stats.experienceToNext,
+ usingNewSystem: !!window.XPProgression
+ });
+
+ // Improve base stats
+ this.attributes.maxHealth += 10;
+ this.attributes.health = this.attributes.maxHealth;
+
+ // Update UI to show new level
+ if (this.game && this.game.systems && this.game.systems.ui) {
+ this.game.systems.ui.updateUI();
+ if (debugLogger) debugLogger.logStep('UI updated for new level');
+ }
+
+ this.game.showNotification(`Level Up! You are now level ${this.stats.level}!`, 'success', 3000);
+ this.attributes.maxEnergy += 5;
+ this.attributes.energy = this.attributes.maxEnergy;
+ this.attributes.attack += 2;
+ this.attributes.defense += 1;
+
+ // Update ship health
+ this.ship.maxHealth += 15;
+ this.ship.health = this.ship.maxHealth;
+
+ // Update title based on level
+ this.updateTitle();
+
+ this.game.showNotification(`Level Up! You are now level ${this.stats.level}!`, 'success', 5000);
+ this.game.showNotification(`+2 Skill Points available`, 'info', 3000);
+
+ if (debugLogger) debugLogger.logStep('Level up completed', {
+ levelChange: `${oldLevel} → ${this.stats.level}`,
+ healthChange: `${oldMaxHealth} → ${this.attributes.maxHealth}`,
+ energyChange: `${oldMaxEnergy} → ${this.attributes.maxEnergy}`,
+ attackChange: `${oldAttack} → ${this.attributes.attack}`,
+ defenseChange: `${oldDefense} → ${this.attributes.defense}`,
+ shipHealthChange: `${oldShipMaxHealth} → ${this.ship.maxHealth}`,
+ titleChange: `${oldTitle} → ${this.info.title}`,
+ skillPointsChange: `${oldSkillPoints} → ${this.stats.skillPoints + 2}`
+ });
+
+ // Add skill points after logging the changes
+ this.stats.skillPoints += 2;
+ }
+
+ updateTitle() {
+ const debugLogger = window.debugLogger;
+ const oldTitle = this.info.title;
+
+ const titles = {
+ 1: 'Rookie Pilot',
+ 5: 'Space Cadet',
+ 10: 'Star Explorer',
+ 15: 'Galaxy Ranger',
+ 20: 'Space Captain',
+ 25: 'Star Commander',
+ 30: 'Galaxy Admiral',
+ 40: 'Space Legend',
+ 50: 'Cosmic Master'
+ };
+
+ for (const [level, title] of Object.entries(titles)) {
+ if (this.stats.level >= parseInt(level)) {
+ this.info.title = title;
+ }
+ }
+
+ if (debugLogger && oldTitle !== this.info.title) {
+ debugLogger.logStep('Player title updated', {
+ level: this.stats.level,
+ oldTitle: oldTitle,
+ newTitle: this.info.title
+ });
+ }
+ }
+
+ // Combat stats
+ takeDamage(amount) {
+ const debugLogger = window.debugLogger;
+ const oldHealth = this.attributes.health;
+ const actualDamage = Math.max(1, amount - this.attributes.defense);
+ this.attributes.health = Math.max(0, this.attributes.health - actualDamage);
+
+ if (debugLogger) debugLogger.logStep('Player took damage', {
+ damageAmount: amount,
+ playerDefense: this.attributes.defense,
+ actualDamage: actualDamage,
+ oldHealth: oldHealth,
+ newHealth: this.attributes.health,
+ healthRemaining: this.attributes.health > 0
+ });
+
+ if (this.attributes.health === 0) {
+ this.onDeath();
+ }
+ return actualDamage;
+ }
+
+ heal(amount) {
+ const debugLogger = window.debugLogger;
+ const oldHealth = this.attributes.health;
+ const healAmount = Math.min(amount, this.attributes.maxHealth - this.attributes.health);
+ this.attributes.health += healAmount;
+
+ if (debugLogger) debugLogger.logStep('Player healed', {
+ healAmount: amount,
+ actualHealAmount: healAmount,
+ oldHealth: oldHealth,
+ newHealth: this.attributes.health,
+ maxHealth: this.attributes.maxHealth,
+ healthPercent: Math.round((this.attributes.health / this.attributes.maxHealth) * 100)
+ });
+
+ return healAmount;
+ }
+
+ useEnergy(amount) {
+ const debugLogger = window.debugLogger;
+ const oldEnergy = this.attributes.energy;
+
+ if (this.attributes.energy < amount) {
+ if (debugLogger) debugLogger.logStep('Energy use failed - insufficient energy', {
+ requestedAmount: amount,
+ currentEnergy: oldEnergy,
+ deficit: amount - oldEnergy
+ });
+ return false;
+ }
+
+ this.attributes.energy -= amount;
+
+ if (debugLogger) debugLogger.logStep('Energy used', {
+ amountUsed: amount,
+ oldEnergy: oldEnergy,
+ newEnergy: this.attributes.energy,
+ maxEnergy: this.attributes.maxEnergy,
+ energyPercent: Math.round((this.attributes.energy / this.attributes.maxEnergy) * 100)
+ });
+
+ // Update UI to show energy change
+ this.updateUI();
+
+ return true;
+ }
+
+ restoreEnergy(amount) {
+ const debugLogger = window.debugLogger;
+ const oldEnergy = this.attributes.energy;
+ const restoreAmount = Math.min(amount, this.attributes.maxEnergy - this.attributes.energy);
+ this.attributes.energy += restoreAmount;
+
+ if (debugLogger) debugLogger.logStep('Energy restored', {
+ restoreAmount: amount,
+ actualRestoreAmount: restoreAmount,
+ oldEnergy: oldEnergy,
+ newEnergy: this.attributes.energy,
+ maxEnergy: this.attributes.maxEnergy,
+ energyPercent: Math.round((this.attributes.energy / this.attributes.maxEnergy) * 100)
+ });
+
+ // Update UI to show energy change
+ this.updateUI();
+
+ return restoreAmount;
+ }
+
+ // Energy regeneration
+ regenerateEnergy(deltaTime) {
+ const regenerationRate = this.getMaxEnergy() * 0.1; // 10% of max energy per second
+ const energyToRegen = (deltaTime / 1000) * regenerationRate;
+
+ if (this.attributes.energy < this.getMaxEnergy()) {
+ this.attributes.energy = Math.min(this.attributes.energy + energyToRegen, this.getMaxEnergy());
+
+ const debugLogger = window.debugLogger;
+ if (debugLogger) debugLogger.logStep('Energy regenerated', {
+ deltaTime: deltaTime,
+ energyRegenerated: energyToRegen,
+ currentEnergy: this.attributes.energy,
+ maxEnergy: this.getMaxEnergy()
+ });
+ }
+ }
+
+ // Combat calculations
+ calculateDamage(enemyDifficulty = 'normal') {
+ const debugLogger = window.debugLogger;
+ const baseDamage = this.ship.attack || this.attributes.attack;
+
+ // Adjust critical chance based on enemy difficulty
+ let criticalChance = this.ship.criticalChance || this.attributes.criticalChance;
+ const difficultyMultipliers = {
+ 'tutorial': 1.5, // Higher chance against easy enemies
+ 'easy': 1.2,
+ 'normal': 1.0,
+ 'medium': 0.9,
+ 'hard': 0.7, // Lower chance against hard enemies
+ 'extreme': 0.5 // Much lower chance against extreme enemies
+ };
+
+ const originalCriticalChance = criticalChance;
+ criticalChance *= (difficultyMultipliers[enemyDifficulty] || 1.0);
+
+ const criticalRoll = Math.random();
+
+ let damage = baseDamage;
+ let isCritical = false;
+
+ if (criticalRoll < criticalChance) {
+ damage *= (this.ship.criticalDamage || this.attributes.criticalDamage);
+ isCritical = true;
+ }
+
+ // Add some randomness
+ const randomMultiplier = this.game.getRandomFloat(0.9, 1.1);
+ damage *= randomMultiplier;
+
+ const finalDamage = Math.floor(damage);
+
+ if (debugLogger) debugLogger.logStep('Damage calculation completed', {
+ enemyDifficulty: enemyDifficulty,
+ baseDamage: baseDamage,
+ originalCriticalChance: originalCriticalChance,
+ adjustedCriticalChance: criticalChance,
+ criticalRoll: criticalRoll,
+ criticalDamageMultiplier: this.ship.criticalDamage || this.attributes.criticalDamage,
+ randomMultiplier: randomMultiplier,
+ isCritical: isCritical,
+ finalDamage: finalDamage
+ });
+
+ return {
+ damage: finalDamage,
+ isCritical
+ };
+ }
+
+ onDeath() {
+ const debugLogger = window.debugLogger;
+ const oldCredits = this.game.systems.economy?.credits || 0;
+
+ console.log('[PLAYER] Player death occurred');
+ if (debugLogger) debugLogger.logStep('Player death triggered', {
+ currentLevel: this.stats.level,
+ oldCredits: oldCredits,
+ totalKills: this.stats.totalKills,
+ dungeonsCleared: this.stats.dungeonsCleared
+ });
+
+ this.game.showNotification('Your ship was destroyed! Respawning...', 'error', 3000);
+
+ // Reset health and energy
+ this.attributes.health = this.attributes.maxHealth;
+ this.attributes.energy = this.attributes.maxEnergy;
+ this.ship.health = this.ship.maxHealth;
+
+ // Apply death penalty
+ const lostCredits = Math.floor(this.game.systems.economy.credits * 0.1);
+ this.game.systems.economy.removeCredits(lostCredits);
+
+ const newCredits = this.game.systems.economy?.credits || 0;
+
+ this.game.showNotification(`Death penalty: -${this.game.formatNumber(lostCredits)} credits`, 'warning', 3000);
+
+ if (debugLogger) debugLogger.logStep('Player death completed', {
+ healthRestored: this.attributes.health,
+ energyRestored: this.attributes.energy,
+ shipHealthRestored: this.ship.health,
+ creditsLost: lostCredits,
+ oldCredits: oldCredits,
+ newCredits: newCredits,
+ penaltyPercentage: 10
+ });
+ }
+
+ resetToLevel1() {
+ const debugLogger = window.debugLogger;
+ const oldStats = { ...this.stats };
+ const oldAttributes = { ...this.attributes };
+ const oldInfo = { ...this.info };
+ const oldShip = { ...this.ship };
+
+ console.log('[PLAYER] Resetting player to level 1');
+ if (debugLogger) debugLogger.logStep('Player reset to level 1 initiated', {
+ oldLevel: oldStats.level,
+ oldExperience: oldStats.experience,
+ oldKills: oldStats.totalKills
+ });
+
+ // Reset stats to initial values
+ this.stats = {
+ level: 1,
+ experience: 0,
+ totalXP: 0, // Total accumulated XP across all levels
+ experienceToNext: window.XPProgression ? window.XPProgression.calculateXPToNextLevel(1, 0) : 100,
+ skillPoints: 0,
+ totalKills: 0,
+ dungeonsCleared: 0,
+ playTime: 0,
+ lastLogin: Date.now(),
+ tutorialDungeonCompleted: false
+ };
+
+ // Reset attributes to base values
+ this.attributes = {
+ health: 100,
+ maxHealth: 100,
+ energy: 100,
+ maxEnergy: 100,
+ attack: 10,
+ defense: 5,
+ speed: 10,
+ criticalChance: 0.05,
+ criticalDamage: 1.5
+ };
+
+ // Reset info
+ this.info = {
+ name: 'Commander',
+ title: 'Rookie Pilot',
+ guild: null,
+ rank: 'Cadet'
+ };
+
+ // Reset ship
+ this.ship = {
+ name: 'Starter Cruiser',
+ class: 'Cruiser',
+ health: 1000,
+ maxHealth: 1000,
+ attack: 10,
+ defense: 5,
+ speed: 10,
+ criticalChance: 0.05,
+ criticalDamage: 1.5,
+ level: 1,
+ upgrades: []
+ };
+
+ console.log('=== DEBUG: Character Reset ===');
+ console.log('Player health reset to:', this.attributes.health, '/', this.attributes.maxHealth);
+ console.log('Ship health reset to:', this.ship.health, '/', this.ship.maxHealth);
+
+ // Reset skills
+ this.skills = {};
+
+ // Reset settings to defaults
+ this.settings = {
+ autoSave: true,
+ notifications: true,
+ soundEffects: true,
+ music: false,
+ theme: 'dark'
+ };
+
+ if (debugLogger) debugLogger.logStep('Player reset to level 1 completed', {
+ newLevel: this.stats.level,
+ newHealth: this.attributes.health,
+ newShipHealth: this.ship.health,
+ skillsCleared: true,
+ settingsReset: true
+ });
+ }
+
+ // Ship management
+ upgradeShip(upgradeType) {
+ const debugLogger = window.debugLogger;
+ const upgradeCosts = {
+ health: 100,
+ attack: 150,
+ defense: 120,
+ speed: 80,
+ critical: 200
+ };
+
+ const cost = upgradeCosts[upgradeType];
+ const oldCredits = this.game.systems.economy?.credits || 0;
+
+ if (debugLogger) debugLogger.logStep('Ship upgrade attempted', {
+ upgradeType: upgradeType,
+ cost: cost,
+ currentCredits: oldCredits,
+ canAfford: oldCredits >= cost
+ });
+
+ if (!cost || !this.game.systems.economy || this.game.systems.economy.credits < cost) {
+ if (debugLogger) debugLogger.logStep('Ship upgrade failed - insufficient funds or invalid type', {
+ upgradeType: upgradeType,
+ cost: cost,
+ currentCredits: oldCredits,
+ deficit: cost - oldCredits,
+ economySystemAvailable: !!this.game.systems.economy
+ });
+ return false;
+ }
+
+ const oldShipStats = { ...this.ship };
+ const oldPlayerStats = { ...this.attributes };
+
+ if (this.game.systems.economy) {
+ this.game.systems.economy.removeCredits(cost);
+ } else {
+ if (debugLogger) debugLogger.log('Economy system not available during ship upgrade');
+ return false;
+ }
+
+ switch (upgradeType) {
+ case 'health':
+ this.ship.maxHealth += 20;
+ this.ship.health = this.ship.maxHealth;
+ this.attributes.maxHealth += 10;
+ this.attributes.health = this.attributes.maxHealth;
+ break;
+ case 'attack':
+ this.ship.attack += 3;
+ break;
+ case 'defense':
+ this.ship.defense += 2;
+ break;
+ case 'speed':
+ this.ship.speed += 2;
+ break;
+ case 'critical':
+ this.ship.criticalChance = Math.min(0.5, this.ship.criticalChance + 0.02);
+ this.ship.criticalDamage += 0.1;
+ break;
+ }
+
+ this.ship.upgrades.push(upgradeType);
+ this.game.showNotification(`Ship upgraded: ${upgradeType}!`, 'success', 3000);
+
+ if (debugLogger) debugLogger.logStep('Ship upgrade completed', {
+ upgradeType: upgradeType,
+ cost: cost,
+ oldCredits: oldCredits,
+ newCredits: this.game.systems.economy?.credits || 0,
+ shipChanges: {
+ oldMaxHealth: oldShipStats.maxHealth,
+ newMaxHealth: this.ship.maxHealth,
+ oldAttack: oldShipStats.attack,
+ newAttack: this.ship.attack,
+ oldDefense: oldShipStats.defense,
+ newDefense: this.ship.defense,
+ oldSpeed: oldShipStats.speed,
+ newSpeed: this.ship.speed,
+ oldCriticalChance: oldShipStats.criticalChance,
+ newCriticalChance: this.ship.criticalChance,
+ oldCriticalDamage: oldShipStats.criticalDamage,
+ newCriticalDamage: this.ship.criticalDamage
+ },
+ playerChanges: {
+ oldMaxHealth: oldPlayerStats.maxHealth,
+ newMaxHealth: this.attributes.maxHealth,
+ oldHealth: oldPlayerStats.health,
+ newHealth: this.attributes.health
+ },
+ totalUpgrades: this.ship.upgrades.length
+ });
+
+ return true;
+ }
+
+ // Statistics tracking
+ incrementKills() {
+ const debugLogger = window.debugLogger;
+ const oldKills = this.stats.totalKills;
+ this.stats.totalKills++;
+
+ if (debugLogger) debugLogger.logStep('Kill count incremented', {
+ oldKills: oldKills,
+ newKills: this.stats.totalKills,
+ currentLevel: this.stats.level
+ });
+
+ // Update quest progress for combat objectives
+ if (this.game && this.game.systems && this.game.systems.questSystem) {
+ this.game.systems.questSystem.onEnemyDefeated();
+ if (debugLogger) debugLogger.logStep('Quest system notified of enemy defeat');
+ }
+ }
+
+ incrementDungeonsCleared() {
+ const debugLogger = window.debugLogger;
+ const oldDungeons = this.stats.dungeonsCleared;
+ this.stats.dungeonsCleared++;
+
+ if (debugLogger) debugLogger.logStep('Dungeons cleared incremented', {
+ oldDungeons: oldDungeons,
+ newDungeons: this.stats.dungeonsCleared,
+ currentLevel: this.stats.level
+ });
+ }
+
+ updatePlayTime(deltaTime) {
+ console.log('[PLAYER] updatePlayTime called with deltaTime:', deltaTime, 'ms');
+ console.log('[PLAYER] Before update - playTime:', this.stats.playTime, 'ms');
+
+ // Use real computer time delta
+ this.stats.playTime += deltaTime;
+
+ console.log('[PLAYER] After update - playTime:', this.stats.playTime, 'ms');
+ console.log('[PLAYER] PlayTime in seconds:', this.stats.playTime / 1000, 'seconds');
+ console.log('[PLAYER] PlayTime in minutes:', this.stats.playTime / 60000, 'minutes');
+ console.log('[PLAYER] PlayTime in hours:', this.stats.playTime / 3600000, 'hours');
+ }
+
+ // UI updates
+ updateUI() {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.logStep('Player UI update started', {
+ currentLevel: this.stats.level,
+ currentHealth: this.attributes.health,
+ currentEnergy: this.attributes.energy,
+ totalKills: this.stats.totalKills
+ });
+
+ // Update player info
+ const playerNameElement = document.getElementById('playerName');
+ const playerLevelElement = document.getElementById('playerLevel');
+
+ if (playerNameElement) {
+ playerNameElement.textContent = `${this.info.name} - ${this.info.title}`;
+ }
+
+ if (playerLevelElement) {
+ playerLevelElement.textContent = `Lv. ${this.stats.level}`;
+ }
+
+ // Update health and energy
+ if (this.game && this.game.systems && this.game.systems.ui) {
+ this.game.systems.ui.updateResourceDisplay();
+ }
+
+ // Update stats
+ const totalKillsElement = document.getElementById('totalKills');
+ const dungeonsClearedElement = document.getElementById('dungeonsCleared');
+ const playTimeElement = document.getElementById('playTime');
+
+ if (totalKillsElement) {
+ totalKillsElement.textContent = this.game.formatNumber(this.stats.totalKills);
+ }
+
+ if (dungeonsClearedElement) {
+ dungeonsClearedElement.textContent = this.game.formatNumber(this.stats.dungeonsCleared);
+ }
+
+ if (playTimeElement) {
+ playTimeElement.textContent = this.game.formatTime(this.stats.playTime / 1000);
+ }
+
+ // Update ship info
+ const flagshipNameElement = document.getElementById('flagshipName');
+ const shipHealthElement = document.getElementById('shipHealth');
+
+ if (flagshipNameElement) {
+ flagshipNameElement.textContent = this.ship.name;
+ }
+
+ if (shipHealthElement) {
+ const healthPercent = Math.round((this.ship.health / this.ship.maxHealth) * 100);
+ shipHealthElement.textContent = `${healthPercent}%`;
+ }
+
+ if (debugLogger) debugLogger.logStep('Player UI update completed', {
+ elementsUpdated: {
+ playerName: !!playerNameElement,
+ playerLevel: !!playerLevelElement,
+ totalKills: !!totalKillsElement,
+ dungeonsCleared: !!dungeonsClearedElement,
+ playTime: !!playTimeElement,
+ flagshipName: !!flagshipNameElement,
+ shipHealth: !!shipHealthElement
+ }
+ });
+ }
+
+ // Save/Load
+ save() {
+ const debugLogger = window.debugLogger;
+
+ const saveData = {
+ stats: this.stats,
+ attributes: this.attributes,
+ info: this.info,
+ ship: this.ship,
+ settings: this.settings
+ };
+
+ // if (debugLogger) debugLogger.logStep('Player save data prepared', {
+ // level: this.stats.level,
+ // experience: this.stats.experience,
+ // totalKills: this.stats.totalKills,
+ // dungeonsCleared: this.stats.dungeonsCleared,
+ // playTime: this.stats.playTime,
+ // shipName: this.ship.name,
+ // shipLevel: this.ship.level,
+ // upgradesCount: this.ship.upgrades.length,
+ // dataSize: JSON.stringify(saveData).length
+ // });
+
+ return saveData;
+ }
+
+ load(data) {
+ const debugLogger = window.debugLogger;
+ console.log('[PLAYER] Loading player data:', data);
+ console.log('[PLAYER] Current level before load:', this.stats.level);
+
+ if (debugLogger) debugLogger.logStep('Player load initiated', {
+ hasData: !!data,
+ dataKeys: data ? Object.keys(data) : [],
+ currentLevel: this.stats.level,
+ currentExperience: this.stats.experience
+ });
+
+ try {
+ if (data.stats) {
+ console.log('[PLAYER] Loading stats:', data.stats);
+ const oldStats = { ...this.stats };
+ this.stats = { ...this.stats, ...data.stats };
+ console.log('[PLAYER] Level after stats load:', this.stats.level);
+
+ if (debugLogger) debugLogger.logStep('Player stats loaded', {
+ oldLevel: oldStats.level,
+ newLevel: this.stats.level,
+ oldExperience: oldStats.experience,
+ newExperience: this.stats.experience,
+ oldKills: oldStats.totalKills,
+ newKills: this.stats.totalKills
+ });
+ }
+
+ if (data.attributes) {
+ console.log('[PLAYER] Loading attributes:', data.attributes);
+ const oldAttributes = { ...this.attributes };
+ this.attributes = { ...this.attributes, ...data.attributes };
+
+ if (debugLogger) debugLogger.logStep('Player attributes loaded', {
+ oldHealth: oldAttributes.health,
+ newHealth: this.attributes.health,
+ oldMaxHealth: oldAttributes.maxHealth,
+ newMaxHealth: this.attributes.maxHealth,
+ oldAttack: oldAttributes.attack,
+ newAttack: this.attributes.attack,
+ oldDefense: oldAttributes.defense,
+ newDefense: this.attributes.defense
+ });
+ }
+
+ if (data.info) {
+ console.log('[PLAYER] Loading info:', data.info);
+ const oldInfo = { ...this.info };
+ this.info = { ...this.info, ...data.info };
+
+ if (debugLogger) debugLogger.logStep('Player info loaded', {
+ oldName: oldInfo.name,
+ newName: this.info.name,
+ oldTitle: oldInfo.title,
+ newTitle: this.info.title,
+ oldGuild: oldInfo.guild,
+ newGuild: this.info.guild
+ });
+ }
+
+ if (data.ship) {
+ console.log('[PLAYER] Loading ship:', data.ship);
+ const oldShip = { ...this.ship };
+ this.ship = { ...this.ship, ...data.ship };
+
+ if (debugLogger) debugLogger.logStep('Player ship loaded', {
+ oldShipName: oldShip.name,
+ newShipName: this.ship.name,
+ oldShipLevel: oldShip.level,
+ newShipLevel: this.ship.level,
+ oldUpgrades: oldShip.upgrades.length,
+ newUpgrades: this.ship.upgrades.length
+ });
+ }
+
+ if (data.settings) {
+ console.log('[PLAYER] Loading settings:', data.settings);
+ const oldSettings = { ...this.settings };
+ this.settings = { ...this.settings, ...data.settings };
+
+ if (debugLogger) debugLogger.logStep('Player settings loaded', {
+ oldAutoSave: oldSettings.autoSave,
+ newAutoSave: this.settings.autoSave,
+ oldNotifications: oldSettings.notifications,
+ newNotifications: this.settings.notifications
+ });
+ }
+
+ console.log('[PLAYER] Final level after load:', this.stats.level);
+
+ if (debugLogger) debugLogger.logStep('Player load completed successfully', {
+ finalLevel: this.stats.level,
+ finalExperience: this.stats.experience,
+ finalHealth: this.attributes.health,
+ finalShipHealth: this.ship.health,
+ totalDataSections: ['stats', 'attributes', 'info', 'ship', 'settings'].filter(key => data[key]).length
+ });
+
+ } catch (error) {
+ console.error('[PLAYER] Error loading player data:', error);
+ if (debugLogger) debugLogger.errorEvent(error, 'Player Load');
+ throw error;
+ }
+ }
+}
diff --git a/Client/js/core/TextureManager.js b/Client/js/core/TextureManager.js
new file mode 100644
index 0000000..4246d66
--- /dev/null
+++ b/Client/js/core/TextureManager.js
@@ -0,0 +1,142 @@
+/**
+ * Galaxy Strike Online - Texture Manager
+ * Handles texture loading and missing texture fallbacks
+ */
+
+class TextureManager {
+ constructor(gameEngine) {
+ this.game = gameEngine;
+ this.textures = new Map();
+ this.missingTextureUrl = 'assets/textures/missing-texture.png';
+
+ // Initialize missing texture
+ this.loadMissingTexture();
+ }
+
+ async loadMissingTexture() {
+ try {
+ const img = new Image();
+ img.src = this.missingTextureUrl;
+ await img.decode();
+ this.textures.set('missing', img);
+ } catch (error) {
+ console.warn('Could not load missing texture, creating fallback');
+ this.createFallbackTexture();
+ }
+ }
+
+ createFallbackTexture() {
+ const canvas = document.createElement('canvas');
+ canvas.width = 64;
+ canvas.height = 64;
+ const ctx = canvas.getContext('2d');
+
+ // Create a pink and black checkerboard pattern
+ const squareSize = 8;
+ for (let y = 0; y < 8; y++) {
+ for (let x = 0; x < 8; x++) {
+ ctx.fillStyle = (x + y) % 2 === 0 ? '#FF00FF' : '#000000';
+ ctx.fillRect(x * squareSize, y * squareSize, squareSize, squareSize);
+ }
+ }
+
+ // Add "GSO Missing" text
+ ctx.fillStyle = '#FFFFFF';
+ ctx.font = 'bold 12px Arial';
+ ctx.textAlign = 'center';
+ ctx.textBaseline = 'middle';
+ ctx.fillText('GSO', 32, 28);
+ ctx.fillText('Missing', 32, 36);
+
+ const img = new Image();
+ img.src = canvas.toDataURL();
+ this.textures.set('missing', img);
+ }
+
+ async loadTexture(textureId, textureUrl) {
+ // Check if already loaded
+ if (this.textures.has(textureId)) {
+ return this.textures.get(textureId);
+ }
+
+ try {
+ const img = new Image();
+ img.src = textureUrl;
+ await img.decode();
+ this.textures.set(textureId, img);
+ return img;
+ } catch (error) {
+ console.warn(`Failed to load texture ${textureId} from ${textureUrl}, using missing texture`);
+ return this.getMissingTexture();
+ }
+ }
+
+ getTexture(textureId) {
+ return this.textures.get(textureId) || this.getMissingTexture();
+ }
+
+ getMissingTexture() {
+ return this.textures.get('missing') || this.createFallbackTexture();
+ }
+
+ // Icon fallback for FontAwesome icons
+ getIcon(iconClass) {
+ // Check if this is a valid FontAwesome icon
+ const validIconPrefixes = ['fas', 'far', 'fab', 'fal'];
+ const iconParts = iconClass.split(' ');
+ const hasValidPrefix = iconParts.some(part => validIconPrefixes.includes(part));
+
+ if (hasValidPrefix) {
+ return iconClass;
+ }
+
+ // Return missing icon fallback - use missing texture
+ return 'missing-texture';
+ }
+
+ // Get item icon as HTML element
+ getItemIconElement(iconClass, size = '32px') {
+ const icon = this.getIcon(iconClass);
+
+ if (icon === 'missing-texture') {
+ return ` `;
+ }
+
+ return ` `;
+ }
+
+ // Preload common textures
+ async preloadTextures() {
+ const commonTextures = [
+ 'ship_fighter',
+ 'ship_cruiser',
+ 'room_command_center',
+ 'room_power_core',
+ 'item_weapon',
+ 'item_shield'
+ ];
+
+ const loadPromises = commonTextures.map(textureId => {
+ const url = `assets/textures/${textureId}.png`;
+ return this.loadTexture(textureId, url);
+ });
+
+ try {
+ await Promise.all(loadPromises);
+ console.log('Common textures preloaded');
+ } catch (error) {
+ console.warn('Some textures failed to preload:', error);
+ }
+ }
+
+ // Clean up unused textures
+ cleanup() {
+ // Keep only essential textures in memory
+ const essentialTextures = ['missing'];
+ for (const [textureId, texture] of this.textures) {
+ if (!essentialTextures.includes(textureId)) {
+ this.textures.delete(textureId);
+ }
+ }
+ }
+}
diff --git a/Client/js/data/GameData.js b/Client/js/data/GameData.js
new file mode 100644
index 0000000..9ce79d7
--- /dev/null
+++ b/Client/js/data/GameData.js
@@ -0,0 +1,570 @@
+/**
+ * Galaxy Strike Online - Game Data
+ * Static game data, constants, and configuration
+ */
+
+// Game configuration
+const GAME_CONFIG = {
+ version: '1.0.0',
+ name: 'Galaxy Strike Online',
+ maxLevel: 100,
+ saveInterval: 30000, // 30 seconds
+ notificationDuration: 3000,
+ maxNotifications: 5
+};
+
+// Player defaults
+const PLAYER_DEFAULTS = {
+ level: 1,
+ experience: 0,
+ skillPoints: 0,
+ credits: 1000,
+ gems: 10,
+ health: 100,
+ maxHealth: 100,
+ energy: 100,
+ maxEnergy: 100,
+ attack: 10,
+ defense: 5,
+ speed: 10,
+ criticalChance: 0.05,
+ criticalDamage: 1.5
+};
+
+// Experience requirements
+const EXPERIENCE_TABLE = [];
+for (let i = 1; i <= 100; i++) {
+ EXPERIENCE_TABLE[i] = Math.floor(100 * Math.pow(1.5, i - 1));
+}
+
+// Item rarities with colors and multipliers
+const ITEM_RARITIES = {
+ common: {
+ name: 'Common',
+ color: '#888888',
+ multiplier: 1.0,
+ dropChance: 0.60
+ },
+ uncommon: {
+ name: 'Uncommon',
+ color: '#00ff00',
+ multiplier: 1.2,
+ dropChance: 0.25
+ },
+ rare: {
+ name: 'Rare',
+ color: '#0088ff',
+ multiplier: 1.5,
+ dropChance: 0.10
+ },
+ epic: {
+ name: 'Epic',
+ color: '#8833ff',
+ multiplier: 2.0,
+ dropChance: 0.04
+ },
+ legendary: {
+ name: 'Legendary',
+ color: '#ff8800',
+ multiplier: 3.0,
+ dropChance: 0.01
+ }
+};
+
+// Enemy types and stats
+const ENEMY_TEMPLATES = {
+ space_pirate: {
+ name: 'Space Pirate',
+ health: 25,
+ attack: 10,
+ defense: 3,
+ speed: 8,
+ experience: 15,
+ credits: 12,
+ rarity: 'common'
+ },
+ alien_guardian: {
+ name: 'Alien Guardian',
+ health: 50,
+ attack: 8,
+ defense: 5,
+ speed: 6,
+ experience: 25,
+ credits: 15,
+ rarity: 'common'
+ },
+ mining_drone: {
+ name: 'Mining Drone',
+ health: 20,
+ attack: 8,
+ defense: 3,
+ speed: 5,
+ experience: 12,
+ credits: 8,
+ rarity: 'common'
+ },
+ security_drone: {
+ name: 'Security Drone',
+ health: 35,
+ attack: 14,
+ defense: 4,
+ speed: 10,
+ experience: 22,
+ credits: 15,
+ rarity: 'uncommon'
+ },
+ pirate_captain: {
+ name: 'Pirate Captain',
+ health: 40,
+ attack: 15,
+ defense: 6,
+ speed: 12,
+ experience: 30,
+ credits: 20,
+ rarity: 'uncommon'
+ },
+ crystal_golem: {
+ name: 'Crystal Golem',
+ health: 80,
+ attack: 6,
+ defense: 10,
+ speed: 4,
+ experience: 35,
+ credits: 25,
+ rarity: 'rare'
+ },
+ corrupted_ai: {
+ name: 'Corrupted AI',
+ health: 60,
+ attack: 20,
+ defense: 2,
+ speed: 15,
+ experience: 40,
+ credits: 30,
+ rarity: 'rare'
+ },
+ energy_being: {
+ name: 'Energy Being',
+ health: 55,
+ attack: 22,
+ defense: 3,
+ speed: 18,
+ experience: 45,
+ credits: 35,
+ rarity: 'epic'
+ },
+ quantum_entity: {
+ name: 'Quantum Entity',
+ health: 70,
+ attack: 35,
+ defense: 5,
+ speed: 20,
+ experience: 60,
+ credits: 50,
+ rarity: 'legendary'
+ }
+};
+
+// Dungeon configurations
+const DUNGEON_CONFIGS = {
+ alien_ruins: {
+ name: 'Alien Ruins',
+ description: 'Ancient alien structures filled with mysterious technology',
+ difficulty: 'medium',
+ minLevel: 3,
+ roomCount: [5, 8],
+ enemyTypes: ['alien_guardian', 'ancient_drone', 'crystal_golem'],
+ rewardMultiplier: 1.2,
+ energyCost: 20
+ },
+ pirate_lair: {
+ name: 'Pirate Lair',
+ description: 'Dangerous pirate hideouts with valuable loot',
+ difficulty: 'easy',
+ minLevel: 1,
+ roomCount: [4, 6],
+ enemyTypes: ['space_pirate', 'pirate_captain', 'defense_turret'],
+ rewardMultiplier: 1.0,
+ energyCost: 15
+ },
+ corrupted_vault: {
+ name: 'Corrupted AI Vault',
+ description: 'Malfunctioning AI facilities with corrupted security',
+ difficulty: 'hard',
+ minLevel: 5,
+ roomCount: [6, 9],
+ enemyTypes: ['security_drone', 'corrupted_ai', 'virus_program'],
+ rewardMultiplier: 1.5,
+ energyCost: 25
+ },
+ asteroid_mine: {
+ name: 'Asteroid Mine',
+ description: 'Abandoned mining facilities in asteroid fields',
+ difficulty: 'easy',
+ minLevel: 2,
+ roomCount: [4, 7],
+ enemyTypes: ['mining_drone', 'rock_creature', 'explosive_asteroid'],
+ rewardMultiplier: 0.8,
+ energyCost: 10
+ },
+ nebula_anomaly: {
+ name: 'Nebula Anomaly',
+ description: 'Strange energy anomalies in deep space',
+ difficulty: 'extreme',
+ minLevel: 8,
+ roomCount: [7, 10],
+ enemyTypes: ['energy_being', 'phase_shifter', 'quantum_entity'],
+ rewardMultiplier: 2.0,
+ energyCost: 30
+ }
+};
+
+// Skill definitions
+const SKILL_DEFINITIONS = {
+ combat: {
+ weapons_mastery: {
+ name: 'Weapons Mastery',
+ description: 'Increases weapon damage and critical chance',
+ maxLevel: 10,
+ experiencePerLevel: 100,
+ effects: {
+ attack: 2,
+ criticalChance: 0.01
+ },
+ icon: 'fa-sword'
+ },
+ shield_techniques: {
+ name: 'Shield Techniques',
+ description: 'Improves defense and energy efficiency',
+ maxLevel: 10,
+ experiencePerLevel: 100,
+ effects: {
+ defense: 2,
+ maxEnergy: 5
+ },
+ icon: 'fa-shield-alt'
+ },
+ piloting: {
+ name: 'Piloting',
+ description: 'Enhances speed and evasion',
+ maxLevel: 10,
+ experiencePerLevel: 100,
+ effects: {
+ speed: 2,
+ criticalChance: 0.005
+ },
+ icon: 'fa-rocket'
+ }
+ },
+ science: {
+ energy_manipulation: {
+ name: 'Energy Manipulation',
+ description: 'Better energy control and regeneration',
+ maxLevel: 10,
+ experiencePerLevel: 100,
+ effects: {
+ maxEnergy: 10,
+ energyRegeneration: 0.1
+ },
+ icon: 'fa-bolt'
+ },
+ alien_technology: {
+ name: 'Alien Technology',
+ description: 'Understanding and using alien artifacts',
+ maxLevel: 10,
+ experiencePerLevel: 150,
+ effects: {
+ findRarity: 0.05,
+ itemValue: 0.1
+ },
+ icon: 'fa-atom'
+ }
+ },
+ crafting: {
+ weapon_crafting: {
+ name: 'Weapon Crafting',
+ description: 'Create and upgrade weapons',
+ maxLevel: 10,
+ experiencePerLevel: 100,
+ effects: {
+ craftingBonus: 0.1,
+ weaponStats: 0.05
+ },
+ icon: 'fa-hammer'
+ },
+ armor_forging: {
+ name: 'Armor Forging',
+ description: 'Forge protective armor and shields',
+ maxLevel: 10,
+ experiencePerLevel: 100,
+ effects: {
+ craftingBonus: 0.1,
+ armorStats: 0.05
+ },
+ icon: 'fa-anvil'
+ }
+ }
+};
+
+// Shop items
+const SHOP_ITEMS = {
+ ships: [
+ {
+ id: 'fighter_mk1',
+ name: 'Fighter Mk. I',
+ type: 'ship',
+ rarity: 'common',
+ price: 5000,
+ currency: 'credits',
+ description: 'Fast and agile fighter ship',
+ stats: { attack: 15, speed: 20, defense: 8 }
+ },
+ {
+ id: 'cruiser_mk1',
+ name: 'Cruiser Mk. I',
+ type: 'ship',
+ rarity: 'uncommon',
+ price: 15000,
+ currency: 'credits',
+ description: 'Well-balanced cruiser for combat',
+ stats: { attack: 20, speed: 10, defense: 15 }
+ }
+ ],
+ upgrades: [
+ {
+ id: 'weapon_upgrade_1',
+ name: 'Weapon Upgrade I',
+ type: 'upgrade',
+ rarity: 'common',
+ price: 500,
+ currency: 'credits',
+ description: 'Increases weapon damage by 10%',
+ effect: { attackMultiplier: 1.1 }
+ },
+ {
+ id: 'shield_upgrade_1',
+ name: 'Shield Upgrade I',
+ type: 'upgrade',
+ rarity: 'common',
+ price: 400,
+ currency: 'credits',
+ description: 'Increases defense by 5 points',
+ effect: { defense: 5 }
+ }
+ ],
+ cosmetics: [
+ {
+ id: 'blue_paint',
+ name: 'Blue Paint Job',
+ type: 'cosmetic',
+ rarity: 'common',
+ price: 100,
+ currency: 'gems',
+ description: 'Custom blue paint for your ship'
+ },
+ {
+ id: 'golden_trim',
+ name: 'Golden Trim',
+ type: 'cosmetic',
+ rarity: 'rare',
+ price: 500,
+ currency: 'gems',
+ description: 'Luxurious golden trim for your ship'
+ }
+ ],
+ consumables: [
+ {
+ id: 'mega_health_kit',
+ name: 'Mega Health Kit',
+ type: 'consumable',
+ rarity: 'uncommon',
+ price: 50,
+ currency: 'credits',
+ description: 'Restores full health',
+ effect: { heal: 999 }
+ },
+ {
+ id: 'energy_boost',
+ name: 'Energy Boost',
+ type: 'consumable',
+ rarity: 'common',
+ price: 25,
+ currency: 'credits',
+ description: 'Restores 50 energy',
+ effect: { energy: 50 }
+ }
+ ]
+};
+
+// Starter equipment for new players
+const STARTER_EQUIPMENT = {
+ starter_blaster: {
+ id: 'starter_blaster',
+ name: 'Common Blaster',
+ type: 'weapon',
+ rarity: 'common',
+ description: 'A reliable basic blaster for new pilots',
+ stats: { attack: 5, criticalChance: 0.02 },
+ equipable: true,
+ slot: 'weapon',
+ value: 100,
+ stackable: false
+ },
+ basic_armor: {
+ id: 'basic_armor',
+ name: 'Basic Armor',
+ type: 'armor',
+ rarity: 'common',
+ description: 'Standard issue armor for basic protection',
+ stats: { defense: 3, health: 10 },
+ equipable: true,
+ slot: 'armor',
+ value: 150,
+ stackable: false
+ }
+};
+
+// Achievement definitions
+const ACHIEVEMENTS = {
+ first_victory: {
+ name: 'First Victory',
+ description: 'Win your first dungeon',
+ requirement: { dungeonsCompleted: 1 },
+ reward: { gems: 10, experience: 100 },
+ icon: 'fa-trophy'
+ },
+ dungeon_master: {
+ name: 'Dungeon Master',
+ description: 'Complete 50 dungeons',
+ requirement: { dungeonsCompleted: 50 },
+ reward: { gems: 100, experience: 1000 },
+ icon: 'fa-dungeon'
+ },
+ level_master: {
+ name: 'Level Master',
+ description: 'Reach level 50',
+ requirement: { level: 50 },
+ reward: { gems: 200, experience: 5000 },
+ icon: 'fa-level-up-alt'
+ },
+ wealthy_commander: {
+ name: 'Wealthy Commander',
+ description: 'Accumulate 1,000,000 credits',
+ requirement: { credits: 1000000 },
+ reward: { gems: 150, experience: 2000 },
+ icon: 'fa-coins'
+ },
+ skill_expert: {
+ name: 'Skill Expert',
+ description: 'Max out any skill',
+ requirement: { maxSkillLevel: 10 },
+ reward: { gems: 75, experience: 1500 },
+ icon: 'fa-graduation-cap'
+ }
+};
+
+// Game messages and notifications
+const GAME_MESSAGES = {
+ welcome: 'Welcome to Galaxy Strike Online, Commander!',
+ levelUp: 'Level Up! You are now level {level}!',
+ questCompleted: 'Quest completed: {questName}!',
+ dungeonCompleted: 'Dungeon completed! Time: {time}',
+ achievementUnlocked: 'Achievement Unlocked: {achievementName}!',
+ insufficientCredits: 'Not enough credits!',
+ insufficientGems: 'Not enough gems!',
+ insufficientEnergy: 'Not enough energy!',
+ inventoryFull: 'Inventory is full!',
+ skillPointsAvailable: 'You have {points} skill points available!',
+ dailyReward: 'Daily reward claimed! Day {day}',
+ offlineRewards: 'Welcome back! You were offline for {time}'
+};
+
+// Utility functions
+const GameUtils = {
+ // Get random item from array
+ getRandomItem(array) {
+ return array[Math.floor(Math.random() * array.length)];
+ },
+
+ // Get random integer between min and max (inclusive)
+ getRandomInt(min, max) {
+ return Math.floor(Math.random() * (max - min + 1)) + min;
+ },
+
+ // Get random float between min and max
+ getRandomFloat(min, max) {
+ return Math.random() * (max - min) + min;
+ },
+
+ // Check if chance succeeds
+ checkChance(chance) {
+ return Math.random() < chance;
+ },
+
+ // Format large numbers with suffixes
+ formatNumber(num) {
+ if (num >= 1000000) return (num / 1000000).toFixed(1) + 'M';
+ if (num >= 1000) return (num / 1000).toFixed(1) + 'K';
+ return Math.floor(num).toString();
+ },
+
+ // Format time in milliseconds to readable string
+ formatTime(milliseconds) {
+ const seconds = Math.floor(milliseconds / 1000);
+ const minutes = Math.floor(seconds / 60);
+ const hours = Math.floor(minutes / 60);
+ const days = Math.floor(hours / 24);
+
+ if (days > 0) return `${days}d ${hours % 24}h`;
+ if (hours > 0) return `${hours}h ${minutes % 60}m`;
+ if (minutes > 0) return `${minutes}m ${seconds % 60}s`;
+ return `${seconds}s`;
+ },
+
+ // Calculate experience needed for level
+ getExperienceForLevel(level) {
+ return EXPERIENCE_TABLE[level] || 0;
+ },
+
+ // Get item rarity by chance
+ getItemRarity() {
+ const roll = Math.random();
+ let cumulative = 0;
+
+ for (const [rarity, data] of Object.entries(ITEM_RARITIES)) {
+ cumulative += data.dropChance;
+ if (roll <= cumulative) {
+ return rarity;
+ }
+ }
+
+ return 'common';
+ },
+
+ // Deep clone object
+ deepClone(obj) {
+ return JSON.parse(JSON.stringify(obj));
+ },
+
+ // Generate unique ID
+ generateId() {
+ return Date.now().toString() + Math.random().toString(36).substr(2, 9);
+ }
+};
+
+// Export for use in other modules
+if (typeof module !== 'undefined' && module.exports) {
+ module.exports = {
+ GAME_CONFIG,
+ PLAYER_DEFAULTS,
+ EXPERIENCE_TABLE,
+ ITEM_RARITIES,
+ ENEMY_TEMPLATES,
+ DUNGEON_CONFIGS,
+ SKILL_DEFINITIONS,
+ SHOP_ITEMS,
+ ACHIEVEMENTS,
+ GAME_MESSAGES,
+ GameUtils
+ };
+}
diff --git a/Client/js/main.js b/Client/js/main.js
new file mode 100644
index 0000000..ec83f21
--- /dev/null
+++ b/Client/js/main.js
@@ -0,0 +1,716 @@
+/**
+ * Galaxy Strike Online - Main Entry Point
+ * Initializes and starts the game
+ */
+
+console.log('[MAIN] main.js script loaded');
+
+// Wait for DOM to be loaded
+document.addEventListener('DOMContentLoaded', async () => {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('main.domContentLoaded', {
+ timestamp: new Date().toISOString(),
+ userAgent: navigator.userAgent,
+ url: window.location.href
+ });
+
+ const loadingIndicator = document.getElementById('loadingIndicator');
+ const loadingStatus = document.getElementById('loadingStatus');
+
+ if (debugLogger) debugLogger.logStep('DOM elements found', {
+ loadingIndicator: !!loadingIndicator,
+ loadingStatus: !!loadingStatus
+ });
+
+ try {
+ // Start debug logging
+ if (window.debugLogger) {
+ window.debugLogger.startStep('domLoad');
+ window.debugLogger.logStep('DOM loaded, starting initialization');
+ }
+
+ // Show loading indicator
+ if (loadingIndicator) loadingIndicator.classList.remove('hidden');
+ if (loadingStatus) {
+ loadingStatus.textContent = 'Initializing application...';
+ loadingStatus.classList.remove('hidden');
+ }
+
+ if (debugLogger) debugLogger.logStep('Loading indicator shown');
+
+ // Initialize title bar controls immediately (don't wait for DOMContentLoaded)
+ console.log('[MAIN] Initializing title bar controls immediately');
+
+ if (debugLogger) debugLogger.logStep('Initializing title bar controls');
+ initializeTitleBar();
+
+ // Wait for DOM to be ready
+ document.addEventListener('DOMContentLoaded', async () => {
+ if (debugLogger) debugLogger.startStep('main.secondDOMContentLoaded', {
+ timestamp: new Date().toISOString()
+ });
+
+ window.debugLogger.startStep('domLoad');
+ window.debugLogger.logStep('DOM loaded, starting initialization');
+
+ // Auto-start local server for singleplayer mode
+ console.log('[MAIN] Checking local server status...');
+ if (window.localServerManager) {
+ try {
+ const serverResult = await window.localServerManager.autoStartIfSingleplayer();
+ if (serverResult.success) {
+ console.log('[MAIN] Local server started successfully:', serverResult);
+ if (debugLogger) debugLogger.logStep('Local server auto-started', {
+ port: serverResult.port,
+ url: serverResult.url
+ });
+ } else {
+ console.log('[MAIN] Local server not started:', serverResult.reason || serverResult.error);
+ if (debugLogger) debugLogger.logStep('Local server not started', {
+ reason: serverResult.reason || serverResult.error
+ });
+ }
+ } catch (error) {
+ console.error('[MAIN] Error starting local server:', error);
+ if (debugLogger) debugLogger.errorEvent(error, 'Local server startup');
+ }
+ } else {
+ console.warn('[MAIN] LocalServerManager not available');
+ }
+
+ // Title bar is already initialized, just log it
+ console.log('[MAIN] DOM loaded - title bar should already be working');
+
+ if (debugLogger) debugLogger.logStep('DOM loaded - title bar should be working');
+
+ // Show main menu instead of directly loading game
+ if (loadingStatus) {
+ loadingStatus.textContent = 'Ready';
+ loadingStatus.classList.add('hidden');
+ }
+
+ if (debugLogger) debugLogger.logStep('Loading status updated to Ready');
+
+ // Hide loading screen and show main menu
+ const loadingScreen = document.getElementById('loadingScreen');
+ const mainMenu = document.getElementById('mainMenu');
+
+ if (loadingScreen) loadingScreen.classList.add('hidden');
+ if (mainMenu) mainMenu.classList.remove('hidden');
+
+ if (debugLogger) debugLogger.logStep('Loading screen hidden, main menu shown', {
+ loadingScreenFound: !!loadingScreen,
+ mainMenuFound: !!mainMenu,
+ liveMainMenuReady: !!window.liveMainMenu
+ });
+
+ // The LiveMainMenu will initialize itself and handle authentication
+ console.log('[MAIN] Main menu displayed - LiveMainMenu will handle authentication and server browsing');
+
+ if (debugLogger) debugLogger.endStep('main.secondDOMContentLoaded', {
+ success: true,
+ mainMenuDisplayed: !!mainMenu
+ });
+ });
+
+ if (debugLogger) debugLogger.endStep('main.domContentLoaded', {
+ success: true,
+ titleBarInitialized: true
+ });
+ } catch (error) {
+ console.error('Failed to initialize game:', error);
+
+ if (debugLogger) debugLogger.errorEvent('main.domContentLoaded', error, {
+ phase: 'initialization',
+ timestamp: new Date().toISOString()
+ });
+
+ if (window.debugLogger) {
+ window.debugLogger.log('CRITICAL ERROR: Initialization failed', {
+ error: error.message,
+ stack: error.stack,
+ timestamp: new Date().toISOString()
+ });
+ }
+
+ // Show error state
+ if (loadingIndicator) loadingIndicator.classList.add('error');
+ if (loadingStatus) {
+ loadingStatus.textContent = 'Failed to load application';
+ loadingStatus.classList.add('error');
+ }
+
+ if (debugLogger) debugLogger.logStep('Error state displayed');
+
+ const logger = window.logger;
+ if (logger) {
+ await logger.errorEvent(error, 'Main.js Initialization');
+ }
+
+ if (debugLogger) debugLogger.endStep('main.domContentLoaded', {
+ success: false,
+ error: error.message
+ });
+ }
+});
+
+// Initialize title bar controls
+function initializeTitleBar() {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('main.initializeTitleBar', {
+ timestamp: new Date().toISOString(),
+ electronAPIAvailable: !!window.electronAPI
+ });
+
+ console.log('[TITLE BAR] Starting title bar initialization');
+
+ // Wait for both electronAPI and DOM elements to be available
+ const checkReady = () => {
+ const hasElectronAPI = !!window.electronAPI;
+ const hasMinimizeBtn = !!document.getElementById('minimizeBtn');
+ const hasCloseBtn = !!document.getElementById('closeBtn');
+ const hasFullscreenBtn = !!document.getElementById('fullscreenBtn');
+
+ const readyState = {
+ hasElectronAPI,
+ hasMinimizeBtn,
+ hasCloseBtn,
+ hasFullscreenBtn
+ };
+
+ console.log(`[TITLE BAR] electronAPI: ${hasElectronAPI}, minimizeBtn: ${hasMinimizeBtn}, closeBtn: ${hasCloseBtn}, fullscreenBtn: ${hasFullscreenBtn}`);
+
+ if (debugLogger) debugLogger.logStep('Title bar readiness check', readyState);
+
+ if (hasElectronAPI && hasMinimizeBtn && hasCloseBtn && hasFullscreenBtn) {
+ console.log('[TITLE BAR] All elements ready, setting up events');
+
+ if (debugLogger) debugLogger.logStep('All title bar elements ready, setting up events');
+ setupTitleBarEvents();
+
+ // Hide the "Initializing application..." text since title bar is now working
+ const loadingStatus = document.getElementById('loadingStatus');
+ if (loadingStatus && loadingStatus.textContent === 'Initializing application...') {
+ console.log('[TITLE BAR] Hiding initializing text');
+ loadingStatus.classList.add('hidden');
+
+ if (debugLogger) debugLogger.logStep('Hiding initializing text');
+ }
+
+ if (debugLogger) debugLogger.endStep('main.initializeTitleBar', {
+ success: true,
+ allElementsReady: true
+ });
+ } else {
+ if (debugLogger) debugLogger.logStep('Not all elements ready, retrying in 50ms', {
+ missingElements: Object.keys(readyState).filter(key => !readyState[key])
+ });
+ setTimeout(checkReady, 50);
+ }
+ };
+
+ checkReady();
+}
+
+function setupTitleBarEvents() {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('main.setupTitleBarEvents');
+
+ const minimizeBtn = document.getElementById('minimizeBtn');
+ const closeBtn = document.getElementById('closeBtn');
+ const fullscreenBtn = document.getElementById('fullscreenBtn');
+
+ console.log('[TITLE BAR] Setting up event listeners');
+
+ if (debugLogger) debugLogger.logStep('Title bar buttons found', {
+ minimizeBtn: !!minimizeBtn,
+ closeBtn: !!closeBtn,
+ fullscreenBtn: !!fullscreenBtn
+ });
+
+ let eventsSetup = 0;
+
+ if (minimizeBtn) {
+ console.log('[TITLE BAR] Adding minimize button listener');
+
+ if (debugLogger) debugLogger.logStep('Setting up minimize button listener');
+ minimizeBtn.addEventListener('click', (e) => {
+ console.log('[TITLE BAR] Minimize button clicked');
+
+ if (debugLogger) debugLogger.log('Title bar minimize button clicked');
+
+ e.preventDefault();
+ e.stopPropagation();
+ if (window.electronAPI && window.electronAPI.minimize) {
+ window.electronAPI.minimize();
+
+ if (debugLogger) debugLogger.logStep('Window minimized via electronAPI');
+ } else {
+ console.error('[TITLE BAR] electronAPI not available when minimize clicked');
+
+ if (debugLogger) debugLogger.log('electronAPI not available for minimize');
+ }
+ });
+ eventsSetup++;
+ } else {
+ console.error('[TITLE BAR] Minimize button not found during setup');
+
+ if (debugLogger) debugLogger.log('Minimize button not found');
+ }
+
+ if (closeBtn) {
+ console.log('[TITLE BAR] Adding close button listener');
+
+ if (debugLogger) debugLogger.logStep('Setting up close button listener');
+ closeBtn.addEventListener('click', (e) => {
+ console.log('[TITLE BAR] Close button clicked');
+
+ if (debugLogger) debugLogger.log('Title bar close button clicked');
+
+ e.preventDefault();
+ // ... rest of the code remains the same ...
+ e.stopPropagation();
+ if (window.electronAPI && window.electronAPI.closeWindow) {
+ window.electronAPI.closeWindow();
+
+ if (debugLogger) debugLogger.logStep('Window closed via electronAPI');
+ } else {
+ console.error('[TITLE BAR] electronAPI not available when close clicked');
+
+ if (debugLogger) debugLogger.log('electronAPI not available for close');
+ }
+ });
+ eventsSetup++;
+ } else {
+ console.error('[TITLE BAR] Close button not found during setup');
+
+ if (debugLogger) debugLogger.log('Close button not found');
+ }
+
+ if (fullscreenBtn) {
+ console.log('[TITLE BAR] Adding fullscreen button listener');
+
+ if (debugLogger) debugLogger.logStep('Setting up fullscreen button listener');
+ fullscreenBtn.addEventListener('click', (e) => {
+ console.log('[TITLE BAR] Fullscreen button clicked');
+
+ if (debugLogger) debugLogger.log('Title bar fullscreen button clicked');
+
+ e.preventDefault();
+ e.stopPropagation();
+ if (window.electronAPI && window.electronAPI.toggleFullscreen) {
+ window.electronAPI.toggleFullscreen();
+
+ if (debugLogger) debugLogger.logStep('Fullscreen toggled via electronAPI');
+
+ // Toggle fullscreen class on body
+ document.body.classList.toggle('fullscreen');
+ document.body.classList.toggle('fullscreen');
+ // Update icon
+ const icon = fullscreenBtn.querySelector('i');
+ if (document.body.classList.contains('fullscreen')) {
+ icon.className = 'fas fa-compress';
+
+ if (debugLogger) debugLogger.logStep('Fullscreen mode activated, icon changed to compress');
+ } else {
+ icon.className = 'fas fa-expand';
+
+ if (debugLogger) debugLogger.logStep('Fullscreen mode deactivated, icon changed to expand');
+ }
+ } else {
+ console.error('[TITLE BAR] electronAPI not available when fullscreen clicked');
+
+ if (debugLogger) debugLogger.log('electronAPI not available for fullscreen');
+ }
+ });
+ eventsSetup++;
+ } else {
+ console.error('[TITLE BAR] Fullscreen button not found during setup');
+
+ if (debugLogger) debugLogger.log('Fullscreen button not found');
+ }
+
+ console.log('[TITLE BAR] Event listeners setup complete');
+
+ if (debugLogger) debugLogger.endStep('main.setupTitleBarEvents', {
+ eventsSetup: eventsSetup,
+ totalExpectedEvents: 3
+ });
+}
+
+// Global utility functions for onclick handlers
+window.game = null;
+
+// Error handling
+window.addEventListener('error', (event) => {
+ console.error('Game error:', event.error);
+ if (window.game) {
+ window.game.showNotification('An error occurred. Please refresh the page.', 'error', 5000);
+ }
+});
+
+// Shutdown handler for debug logging
+window.addEventListener('beforeunload', async () => {
+ if (window.debugLogger) {
+ try {
+ await window.debugLogger.shutdown();
+ } catch (error) {
+ console.error('[MAIN] Failed to shutdown debug logger:', error);
+ }
+ }
+});
+
+// Performance monitoring
+if (window.performance && window.performance.memory) {
+ setInterval(() => {
+ if (window.game && window.game.isRunning) {
+ const stats = window.game.getPerformanceStats();
+ if (stats.memory && stats.memory.used / stats.memory.total > 0.9) {
+ console.warn('High memory usage detected:', stats.memory);
+ }
+ }
+ }, 30000); // Check every 30 seconds
+}
+
+// Global console functions
+function toggleConsole() {
+ console.log('[DEBUG] toggleConsole called');
+ const consoleWindow = document.getElementById('consoleWindow');
+ const consoleInput = document.getElementById('consoleInput');
+
+ console.log('[DEBUG] consoleWindow element:', consoleWindow);
+ console.log('[DEBUG] consoleInput element:', consoleInput);
+
+ if (!consoleWindow) {
+ console.error('[DEBUG] consoleWindow element not found!');
+ return;
+ }
+
+ if (consoleWindow.style.display === 'flex') {
+ consoleWindow.style.display = 'none';
+ console.log('[DEBUG] Console hidden');
+ } else {
+ consoleWindow.style.display = 'flex';
+ console.log('[DEBUG] Console shown');
+ if (consoleInput) {
+ consoleInput.focus();
+ }
+ }
+}
+
+function handleConsoleInput(event) {
+ if (event.key === 'Enter') {
+ const input = event.target;
+ const command = input.value.trim();
+
+ if (command) {
+ executeConsoleCommand(command);
+ input.value = '';
+ }
+ }
+}
+
+function executeConsoleCommand(command) {
+ const output = document.getElementById('consoleOutput');
+ const commandLine = document.createElement('div');
+ commandLine.className = 'console-line';
+ commandLine.textContent = `> ${command}`;
+ output.appendChild(commandLine);
+
+ // Log command to file and browser console
+ console.log(`[CONSOLE] Command: ${command}`);
+ if (window.logger) {
+ window.logger.playerAction('Console Command', { command: command });
+ }
+
+ try {
+ const result = processCommand(command);
+ const resultLine = document.createElement('div');
+ resultLine.className = `console-line ${result.type || 'success'}`;
+ // Convert line breaks to HTML for proper rendering
+ resultLine.innerHTML = result.message.replace(/\n/g, ' ');
+ output.appendChild(resultLine);
+
+ // Log result to file and browser console
+ const consoleMethod = result.type === 'error' ? console.error :
+ result.type === 'info' ? console.info : console.log;
+ consoleMethod(`[CONSOLE] Result (${result.type}): ${result.message.replace(/\n/g, ' ')}`);
+
+ if (window.logger) {
+ window.logger.playerAction('Console Result', {
+ command: command,
+ result: result.type,
+ message: result.message
+ });
+ }
+ } catch (error) {
+ const errorLine = document.createElement('div');
+ errorLine.className = 'console-line console-error';
+ errorLine.textContent = `Error: ${error.message}`;
+ output.appendChild(errorLine);
+
+ // Log error to file and browser console
+ console.error(`[CONSOLE] Error: ${error.message}`);
+ if (window.logger) {
+ window.logger.errorEvent(error, 'Console Command', { command: command });
+ }
+ }
+
+ // Scroll to bottom
+ output.scrollTop = output.scrollHeight;
+}
+
+function processCommand(command) {
+ const parts = command.split(' ');
+ const cmd = parts[0].toLowerCase();
+ const args = parts.slice(1);
+
+ switch (cmd) {
+ case 'help':
+ return {
+ type: 'info',
+ message: `Available commands:\nhelp - Show this help message\nclear - Clear console output\ncoins - Add coins to player (e.g., "coins 1000")\ngems - Add gems to player (e.g., "gems 100")\nresearch - Add research points (e.g., "research 500")\ncraftingxp - Add crafting experience (e.g., "craftingxp 200")\ngiveitem - Add item to inventory (e.g., "giveitem iron_ore 10")\nhealth - Set current ship health (e.g., "health 150")\nlevel - Set current ship level (e.g., "level 5")\nunlock - Unlock a ship (e.g., "unlock heavy_fighter")\nstats - Show current player stats\nships - List all ships\ncurrent - Show current ship info`
+ };
+
+ case 'clear':
+ const output = document.getElementById('consoleOutput');
+ output.innerHTML = '';
+ return { type: 'success', message: 'Console cleared' };
+
+ case 'coins':
+ if (args.length === 0) {
+ return { type: 'error', message: 'Usage: coins ' };
+ }
+ const amount = parseInt(args[0]);
+ if (isNaN(amount)) {
+ return { type: 'error', message: 'Invalid amount' };
+ }
+ if (window.game && window.game.systems && window.game.systems.economy) {
+ window.game.systems.economy.addCredits(amount, 'console');
+ window.game.systems.economy.updateUI();
+ return { type: 'success', message: `Added ${amount} credits` };
+ }
+ return { type: 'error', message: 'Economy system not available' };
+
+ case 'gems':
+ if (args.length === 0) {
+ return { type: 'error', message: 'Usage: gems ' };
+ }
+ const gemAmount = parseInt(args[0]);
+ if (isNaN(gemAmount)) {
+ return { type: 'error', message: 'Invalid amount' };
+ }
+ if (window.game && window.game.systems && window.game.systems.economy) {
+ window.game.systems.economy.addGems(gemAmount, 'console');
+ window.game.systems.economy.updateUI();
+ return { type: 'success', message: `Added ${gemAmount} gems` };
+ }
+ return { type: 'error', message: 'Economy system not available' };
+
+ case 'research':
+ if (args.length === 0) {
+ return { type: 'error', message: 'Usage: research ' };
+ }
+ const researchAmount = parseInt(args[0]);
+ if (isNaN(researchAmount)) {
+ return { type: 'error', message: 'Invalid amount' };
+ }
+ if (window.game && window.game.systems && window.game.systems.player) {
+ const currentSkillPoints = window.game.systems.player.stats.skillPoints || 0;
+ window.game.systems.player.stats.skillPoints = currentSkillPoints + researchAmount;
+ window.game.systems.player.updateUI();
+
+ // Also update skill system UI
+ if (window.game.systems.skillSystem) {
+ window.game.systems.skillSystem.updateUI();
+ }
+
+ return { type: 'success', message: `Added ${researchAmount} skill points (Total: ${window.game.systems.player.stats.skillPoints})` };
+ }
+ return { type: 'error', message: 'Player system not available' };
+
+ case 'craftingxp':
+ if (args.length === 0) {
+ return { type: 'error', message: 'Usage: craftingxp ' };
+ }
+ const craftingXpAmount = parseInt(args[0]);
+ if (isNaN(craftingXpAmount)) {
+ return { type: 'error', message: 'Invalid amount' };
+ }
+ if (window.game && window.game.systems && window.game.systems.skillSystem) {
+ window.game.systems.skillSystem.awardCraftingExperience(craftingXpAmount);
+ const currentLevel = window.game.systems.skillSystem.getSkillLevel('crafting');
+ const currentExp = window.game.systems.skillSystem.getSkillExperience('crafting');
+ return { type: 'success', message: `Added ${craftingXpAmount} crafting experience (Level: ${currentLevel}, XP: ${currentExp})` };
+ }
+ return { type: 'error', message: 'Skill system not available' };
+
+ case 'giveitem':
+ if (args.length < 2) {
+ return { type: 'error', message: 'Usage: giveitem ' };
+ }
+ const itemId = args[0];
+ const quantity = parseInt(args[1]);
+ if (isNaN(quantity)) {
+ return { type: 'error', message: 'Invalid quantity' };
+ }
+ if (window.game && window.game.systems && window.game.systems.inventory) {
+ window.game.systems.inventory.addItem(itemId, quantity);
+ window.game.systems.ui.updateInventory();
+ return { type: 'success', message: `Added ${quantity}x ${itemId} to inventory` };
+ }
+ return { type: 'error', message: 'Inventory system not available' };
+
+ case 'health':
+ if (args.length === 0) {
+ return { type: 'error', message: 'Usage: health ' };
+ }
+ const healthAmount = parseInt(args[0]);
+ if (isNaN(healthAmount)) {
+ return { type: 'error', message: 'Invalid amount' };
+ }
+ if (window.game && window.game.systems && window.game.systems.ship) {
+ const currentShip = window.game.systems.ship.currentShip;
+ if (currentShip) {
+ currentShip.health = Math.min(healthAmount, currentShip.maxHealth);
+ window.game.systems.ship.updateCurrentShipDisplay();
+ return { type: 'success', message: `Set ship health to ${currentShip.health}/${currentShip.maxHealth}` };
+ }
+ }
+ return { type: 'error', message: 'Ship system not available' };
+
+ case 'level':
+ if (args.length === 0) {
+ return { type: 'error', message: 'Usage: level ' };
+ }
+ const levelAmount = parseInt(args[0]);
+ if (isNaN(levelAmount)) {
+ return { type: 'error', message: 'Invalid level' };
+ }
+ if (window.game && window.game.systems && window.game.systems.player) {
+ window.game.systems.player.stats.level = levelAmount;
+ window.game.systems.player.updateTitle();
+ window.game.systems.player.updateUI();
+ return { type: 'success', message: `Set player level to ${levelAmount}` };
+ }
+ return { type: 'error', message: 'Player system not available' };
+
+ case 'unlock':
+ if (args.length === 0) {
+ return { type: 'error', message: 'Usage: unlock ' };
+ }
+ const shipId = args[0];
+ if (window.game && window.game.systems && window.game.systems.ship) {
+ const ship = window.game.systems.ship.ships.find(s => s.id === shipId);
+ if (ship) {
+ ship.status = 'inactive';
+ window.game.systems.ship.renderShips();
+ return { type: 'success', message: `Unlocked ship: ${ship.name}` };
+ } else {
+ return { type: 'error', message: `Ship not found: ${shipId}` };
+ }
+ }
+ return { type: 'error', message: 'Ship system not available' };
+
+ case 'stats':
+ if (window.game && window.game.systems && window.game.systems.player && window.game.systems.economy) {
+ const player = window.game.systems.player;
+ const economy = window.game.systems.economy;
+ return {
+ type: 'info',
+ message: `Player Stats:\nLevel: ${player.stats.level}\nExperience: ${player.stats.experience}/${player.stats.experienceToNext}\nCredits: ${economy.credits}\nGems: ${economy.gems}\nTotal Kills: ${player.stats.totalKills}\nDungeons Cleared: ${player.stats.dungeonsCleared}\nTitle: ${player.info.title}`
+ };
+ }
+ return { type: 'error', message: 'Player or Economy system not available' };
+
+ case 'ships':
+ if (window.game && window.game.systems && window.game.systems.ship) {
+ const ships = window.game.systems.ship.ships;
+ const currentShip = window.game.systems.ship.currentShip;
+ const shipList = ships.map(ship =>
+ `- ${ship.name} (${ship.id}) - Level ${ship.level} - ${ship.status} - ${ship.rarity}${currentShip.id === ship.id ? ' [CURRENT]' : ''}`
+ ).join('\n');
+ return {
+ type: 'info',
+ message: `Available Ships:\n${shipList}`
+ };
+ }
+ return { type: 'error', message: 'Ship system not available' };
+
+ case 'current':
+ if (window.game && window.game.systems && window.game.systems.ship) {
+ const currentShip = window.game.systems.ship.currentShip;
+ if (currentShip) {
+ return {
+ type: 'info',
+ message: `Current Ship:
+- Name: ${currentShip.name}
+- Class: ${currentShip.class}
+- Level: ${currentShip.level}
+- Health: ${currentShip.health}/${currentShip.maxHealth}
+- Attack: ${currentShip.attack}
+- Defense: ${currentShip.defense}
+- Speed: ${currentShip.speed}
+- Rarity: ${currentShip.rarity}`
+ };
+ }
+ }
+ return { type: 'error', message: 'Ship system not available' };
+
+ default:
+ return { type: 'error', message: `Unknown command: ${cmd}. Type 'help' for available commands.` };
+ }
+}
+
+// Keyboard shortcut listener
+document.addEventListener('DOMContentLoaded', function() {
+ console.log('[DEBUG] DOMContentLoaded event fired for keyboard shortcuts');
+ document.addEventListener('keydown', function(event) {
+ // Log all key combinations for debugging
+ if (event.ctrlKey || event.altKey || event.shiftKey) {
+ console.log('[DEBUG] Key pressed:', {
+ key: event.key,
+ ctrlKey: event.ctrlKey,
+ altKey: event.altKey,
+ shiftKey: event.shiftKey,
+ code: event.code
+ });
+ }
+
+ // Ctrl+Alt+Shift+C to toggle console
+ if (event.ctrlKey && event.altKey && event.shiftKey && event.key === 'C') {
+ console.log('[DEBUG] Ctrl+Alt+Shift+C detected!');
+ event.preventDefault();
+
+ // Check if toggleConsole function exists
+ if (typeof toggleConsole === 'function') {
+ console.log('[DEBUG] toggleConsole function exists, calling it');
+ toggleConsole();
+ } else {
+ console.error('[DEBUG] toggleConsole function not found!');
+ }
+ }
+
+ // Escape to close console
+ if (event.key === 'Escape') {
+ const consoleWindow = document.getElementById('consoleWindow');
+ if (consoleWindow && consoleWindow.style.display === 'flex') {
+ consoleWindow.style.display = 'none';
+ }
+ }
+ });
+});
+
+// Initialize console output with welcome message
+document.addEventListener('DOMContentLoaded', function() {
+ const output = document.getElementById('consoleOutput');
+ if (output) {
+ const welcomeLine = document.createElement('div');
+ welcomeLine.className = 'console-line console-info';
+ welcomeLine.textContent = 'Developer Console ready. Type "help" for available commands.';
+ output.appendChild(welcomeLine);
+ }
+});
diff --git a/Client/js/systems/BaseSystem.js b/Client/js/systems/BaseSystem.js
new file mode 100644
index 0000000..391e9f3
--- /dev/null
+++ b/Client/js/systems/BaseSystem.js
@@ -0,0 +1,2173 @@
+/**
+ * Galaxy Strike Online - Base System
+ * Manages player base building and customization
+ */
+
+class BaseSystem {
+ constructor(gameEngine) {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('BaseSystem.constructor', {
+ gameEngineProvided: !!gameEngine
+ });
+
+ this.game = gameEngine;
+
+ // Base configuration
+ this.base = {
+ name: 'Command Center Alpha',
+ level: 1,
+ experience: 0,
+ experienceToNext: 500,
+ rooms: [],
+ decorations: [],
+ power: 100,
+ maxPower: 100,
+ storage: 1000,
+ maxStorage: 1000
+ };
+
+ if (debugLogger) debugLogger.logStep('Base configuration initialized', {
+ baseName: this.base.name,
+ baseLevel: this.base.level,
+ initialPower: this.base.power,
+ maxPower: this.base.maxPower,
+ initialStorage: this.base.storage,
+ maxStorage: this.base.maxStorage
+ });
+
+ // 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,
+ storageBonus: 0,
+ buildCost: 600,
+ requiredLevel: 2,
+ maxLevel: 5,
+ icon: 'fa-bolt'
+ },
+ defense_turret: {
+ name: 'Defense Turret',
+ description: 'Automated defense system',
+ size: 'small',
+ powerCost: 8,
+ storageBonus: 0,
+ buildCost: 400,
+ requiredLevel: 3,
+ maxLevel: 8,
+ icon: 'fa-crosshairs'
+ },
+ communication_array: {
+ name: 'Communication Array',
+ description: 'Long-range communication and scanning',
+ size: 'medium',
+ powerCost: 10,
+ storageBonus: 20,
+ buildCost: 700,
+ requiredLevel: 4,
+ maxLevel: 3,
+ icon: 'fa-broadcast-tower'
+ },
+ medical_bay: {
+ name: 'Medical Bay',
+ description: 'Health restoration and crew recovery',
+ size: 'medium',
+ powerCost: 12,
+ storageBonus: 30,
+ buildCost: 900,
+ requiredLevel: 3,
+ maxLevel: 4,
+ icon: 'fa-medkit'
+ },
+ recreation_room: {
+ name: 'Recreation Room',
+ description: 'Crew morale and relaxation facilities',
+ size: 'small',
+ powerCost: 6,
+ storageBonus: 10,
+ buildCost: 350,
+ requiredLevel: 2,
+ maxLevel: 3,
+ icon: 'fa-gamepad'
+ }
+ };
+
+ // Base upgrades
+ this.upgrades = {
+ power_efficiency: {
+ name: 'Power Efficiency',
+ description: 'Reduces power consumption of all rooms',
+ cost: 1000,
+ effect: { powerReduction: 0.1 },
+ maxLevel: 5,
+ currentLevel: 0
+ },
+ storage_expansion: {
+ name: 'Storage Expansion',
+ description: 'Increases inventory slots for item storage',
+ cost: 5000,
+ maxLevel: 10,
+ currentLevel: 0,
+ icon: 'fa-boxes',
+ slotBonus: 5 // +5 slots per level
+ },
+ automation_systems: {
+ name: 'Automation Systems',
+ description: 'Automated resource collection and processing',
+ cost: 1500,
+ effect: {
+ productionBonus: 0.2 // +20% production
+ },
+ maxLevel: 5,
+ currentLevel: 0
+ },
+ advanced_defenses: {
+ name: 'Advanced Defenses',
+ description: 'Improved base defense systems',
+ cost: 2000,
+ effect: { defenseBonus: 10 },
+ maxLevel: 3,
+ currentLevel: 0
+ }
+ };
+
+ // Base grid layout (10x10)
+ this.gridSize = 10;
+ this.grid = [];
+
+ if (debugLogger) debugLogger.logStep('Grid configuration initialized', {
+ gridSize: this.gridSize,
+ gridInitialized: false
+ });
+
+ // Initialize grid
+ this.initializeGrid();
+
+ if (debugLogger) debugLogger.endStep('BaseSystem.constructor', {
+ roomTypesCount: Object.keys(this.roomTypes).length,
+ upgradesCount: Object.keys(this.upgrades).length,
+ gridSize: this.gridSize,
+ gridInitialized: !!this.grid,
+ baseConfiguration: this.base
+ });
+ }
+
+ initializeBaseData() {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('BaseSystem.initializeBaseData', {
+ oldBaseName: this.base.name,
+ oldBaseLevel: this.base.level
+ });
+
+ // Initialize base with default values
+ this.base = {
+ name: 'Command Center Alpha',
+ level: 1,
+ experience: 0,
+ experienceToNext: 500,
+ rooms: [],
+ decorations: [],
+ power: 100,
+ maxPower: 100,
+ storage: 1000,
+ maxStorage: 1000
+ };
+
+ // Initialize resources
+ this.resources = {
+ energy: 100,
+ materials: 50,
+ research: 0
+ };
+
+ if (debugLogger) debugLogger.endStep('BaseSystem.initializeBaseData', {
+ newBaseName: this.base.name,
+ newBaseLevel: this.base.level,
+ roomsCount: this.base.rooms.length,
+ decorationsCount: this.base.decorations.length,
+ resourcesInitialized: this.resources
+ });
+ }
+
+ initialize() {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('BaseSystem.initialize', {
+ currentBaseLevel: this.base.level,
+ roomsCount: this.base.rooms.length
+ });
+
+ // Initialize base data
+ if (debugLogger) debugLogger.logStep('Initializing base data');
+ this.initializeBaseData();
+
+ // Initialize inventory bonus slots on game start
+ if (debugLogger) debugLogger.logStep('Updating inventory bonus slots');
+ this.updateInventoryBonusSlots();
+
+ if (debugLogger) debugLogger.endStep('BaseSystem.initialize', {
+ baseDataInitialized: true,
+ inventoryBonusSlotsUpdated: true
+ });
+ }
+
+ async initialize() {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('BaseSystem.asyncInitialize', {
+ currentBaseLevel: this.base.level,
+ roomsCount: this.base.rooms.length
+ });
+
+ // Start with command center
+ if (debugLogger) debugLogger.logStep('Adding command center room');
+ this.addRoom('command_center', 5, 5);
+
+ // Set up base navigation
+ if (debugLogger) debugLogger.logStep('Setting up base navigation');
+ this.setupBaseNavigation();
+
+ // Initialize ship gallery
+ if (debugLogger) debugLogger.logStep('Initializing ship gallery');
+ this.initializeShipGallery();
+
+ // Initialize starbase system
+ if (debugLogger) debugLogger.logStep('Initializing starbase system');
+ this.initializeStarbaseSystem();
+
+ if (debugLogger) debugLogger.endStep('BaseSystem.asyncInitialize', {
+ commandCenterAdded: true,
+ navigationSetup: true,
+ shipGalleryInitialized: true,
+ starbaseSystemInitialized: true
+ });
+ }
+
+ initializeGrid() {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('BaseSystem.initializeGrid', {
+ gridSize: this.gridSize
+ });
+
+ this.grid = [];
+ for (let y = 0; y < this.gridSize; y++) {
+ this.grid[y] = [];
+ for (let x = 0; x < this.gridSize; x++) {
+ this.grid[y][x] = null;
+ }
+ }
+
+ if (debugLogger) debugLogger.endStep('BaseSystem.initializeGrid', {
+ gridSize: this.gridSize,
+ gridCreated: true,
+ totalCells: this.gridSize * this.gridSize
+ });
+ }
+
+ // Room management
+ getRoomAt(x, y) {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.log('BaseSystem.getRoomAt called', {
+ x: x,
+ y: y,
+ gridSize: this.gridSize
+ });
+
+ // Check if coordinates are within grid bounds
+ if (x < 0 || x >= this.gridSize || y < 0 || y >= this.gridSize) {
+ if (debugLogger) debugLogger.log('Coordinates out of bounds', {
+ x: x,
+ y: y,
+ gridSize: this.gridSize
+ });
+ return null;
+ }
+
+ // Ensure grid exists
+ if (!this.grid || !this.grid[y]) {
+ console.log('[BASE SYSTEM] Grid not properly initialized in getRoomAt');
+ if (debugLogger) debugLogger.log('Grid not properly initialized', {
+ gridExists: !!this.grid,
+ gridRowExists: !!(this.grid && this.grid[y]),
+ y: y
+ });
+ return null;
+ }
+
+ // Get room ID from grid
+ const roomId = this.grid[y][x];
+ if (!roomId) {
+ if (debugLogger) debugLogger.log('No room at coordinates', {
+ x: x,
+ y: y,
+ roomId: roomId
+ });
+ return null;
+ }
+
+ // Find room by ID
+ const room = this.base.rooms.find(r => r.id === roomId) || null;
+
+ if (debugLogger) debugLogger.log('Room found at coordinates', {
+ x: x,
+ y: y,
+ roomId: roomId,
+ roomFound: !!room,
+ roomName: room ? room.name : null,
+ roomType: room ? room.type : null
+ });
+
+ return room;
+ }
+
+ canBuildRoom(roomType, x, y) {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('BaseSystem.canBuildRoom', {
+ roomType: roomType,
+ x: x,
+ y: y,
+ currentBaseLevel: this.base.level,
+ currentCredits: this.game.systems.economy.credits,
+ currentRooms: this.base.rooms.length
+ });
+
+ const roomTemplate = this.roomTypes[roomType];
+ if (!roomTemplate) {
+ if (debugLogger) debugLogger.endStep('BaseSystem.canBuildRoom', {
+ success: false,
+ reason: 'Room template not found',
+ roomType: roomType
+ });
+ return false;
+ }
+
+ if (debugLogger) debugLogger.logStep('Room template found', {
+ roomName: roomTemplate.name,
+ requiredLevel: roomTemplate.requiredLevel,
+ buildCost: roomTemplate.buildCost,
+ powerCost: roomTemplate.powerCost,
+ maxLevel: roomTemplate.maxLevel
+ });
+
+ // Check player level
+ if (this.game.systems.player.stats.level < roomTemplate.requiredLevel) {
+ if (debugLogger) debugLogger.endStep('BaseSystem.canBuildRoom', {
+ success: false,
+ reason: 'Player level too low',
+ playerLevel: this.game.systems.player.stats.level,
+ requiredLevel: roomTemplate.requiredLevel
+ });
+ return false;
+ }
+
+ // Check resources
+ if (this.game.systems.economy.credits < roomTemplate.buildCost) {
+ if (debugLogger) debugLogger.endStep('BaseSystem.canBuildRoom', {
+ success: false,
+ reason: 'Insufficient credits',
+ currentCredits: this.game.systems.economy.credits,
+ requiredCredits: roomTemplate.buildCost
+ });
+ return false;
+ }
+
+ // Check if room already exists at max level
+ const existingRoom = this.base.rooms.find(r => r.type === roomType);
+ if (existingRoom && existingRoom.level >= roomTemplate.maxLevel) {
+ if (debugLogger) debugLogger.endStep('BaseSystem.canBuildRoom', {
+ success: false,
+ reason: 'Room already at max level',
+ roomType: roomType,
+ currentLevel: existingRoom.level,
+ maxLevel: roomTemplate.maxLevel
+ });
+ return false;
+ }
+
+ // Check grid placement
+ const roomSize = this.getRoomSize(roomTemplate.size);
+ if (!this.canPlaceRoom(x, y, roomSize.width, roomSize.height)) {
+ if (debugLogger) debugLogger.endStep('BaseSystem.canBuildRoom', {
+ success: false,
+ reason: 'Cannot place room at location',
+ x: x,
+ y: y,
+ roomSize: roomSize
+ });
+ return false;
+ }
+
+ // Check power availability
+ const powerRequirement = roomTemplate.powerCost;
+ if (this.base.power + powerRequirement > this.base.maxPower) {
+ if (debugLogger) debugLogger.endStep('BaseSystem.canBuildRoom', {
+ success: false,
+ reason: 'Insufficient power',
+ currentPower: this.base.power,
+ powerRequirement: powerRequirement,
+ maxPower: this.base.maxPower,
+ totalPowerNeeded: this.base.power + powerRequirement
+ });
+ return false;
+ }
+
+ if (debugLogger) debugLogger.endStep('BaseSystem.canBuildRoom', {
+ success: true,
+ roomType: roomType,
+ roomName: roomTemplate.name,
+ x: x,
+ y: y,
+ isUpgrade: !!existingRoom,
+ currentLevel: existingRoom ? existingRoom.level : 0
+ });
+
+ return true;
+ }
+
+ getRoomSize(size) {
+ const sizes = {
+ small: { width: 1, height: 1 },
+ medium: { width: 2, height: 2 },
+ large: { width: 3, height: 3 }
+ };
+ return sizes[size] || sizes.medium;
+ }
+
+ canPlaceRoom(x, y, width, height) {
+ // Check bounds
+ if (x < 0 || y < 0 || x + width > this.gridSize || y + height > this.gridSize) {
+ return false;
+ }
+
+ // Check for collisions
+ for (let dy = 0; dy < height; dy++) {
+ for (let dx = 0; dx < width; dx++) {
+ if (this.grid[y + dy][x + dx] !== null) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ addRoom(roomType, x, y) {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('BaseSystem.addRoom', {
+ roomType: roomType,
+ x: x,
+ y: y,
+ currentRoomsCount: this.base.rooms.length,
+ currentCredits: this.game.systems.economy.credits
+ });
+
+ if (!this.canBuildRoom(roomType, x, y)) {
+ if (debugLogger) debugLogger.endStep('BaseSystem.addRoom', {
+ success: false,
+ reason: 'Cannot build room - failed validation',
+ roomType: roomType,
+ x: x,
+ y: y
+ });
+ return false;
+ }
+
+ const roomTemplate = this.roomTypes[roomType];
+ const roomSize = this.getRoomSize(roomTemplate.size);
+
+ if (debugLogger) debugLogger.logStep('Room validation passed', {
+ roomName: roomTemplate.name,
+ roomSize: roomSize,
+ buildCost: roomTemplate.buildCost
+ });
+
+ // Check if upgrading existing room
+ const existingRoom = this.base.rooms.find(r => r.type === roomType);
+ const isUpgrade = !!existingRoom;
+
+ if (isUpgrade) {
+ if (debugLogger) debugLogger.logStep('Upgrading existing room', {
+ roomId: existingRoom.id,
+ currentLevel: existingRoom.level,
+ newLevel: existingRoom.level + 1
+ });
+
+ // Upgrade existing room
+ existingRoom.level++;
+ existingRoom.powerCost = roomTemplate.powerCost * existingRoom.level;
+ existingRoom.storageBonus = roomTemplate.storageBonus * existingRoom.level;
+ } else {
+ if (debugLogger) debugLogger.logStep('Creating new room', {
+ roomType: roomType,
+ roomName: roomTemplate.name,
+ x: x,
+ y: y,
+ roomSize: roomSize
+ });
+
+ // Create new room
+ const room = {
+ id: Date.now().toString(),
+ type: roomType,
+ name: roomTemplate.name,
+ level: 1,
+ x: x,
+ y: y,
+ width: roomSize.width,
+ height: roomSize.height,
+ powerCost: roomTemplate.powerCost,
+ storageBonus: roomTemplate.storageBonus,
+ icon: roomTemplate.icon,
+ description: roomTemplate.description
+ };
+
+ this.base.rooms.push(room);
+
+ // Place on grid
+ for (let dy = 0; dy < roomSize.height; dy++) {
+ for (let dx = 0; dx < roomSize.width; dx++) {
+ this.grid[y + dy][x + dx] = room.id;
+ }
+ }
+
+ if (debugLogger) debugLogger.logStep('New room placed on grid', {
+ roomId: room.id,
+ gridPositions: roomSize.width * roomSize.height
+ });
+ }
+
+ // Update base stats
+ if (debugLogger) debugLogger.logStep('Updating base stats');
+ this.updateBaseStats();
+
+ // Deduct cost
+ const oldCredits = this.game.systems.economy.credits;
+ this.game.systems.economy.removeCredits(roomTemplate.buildCost);
+
+ if (debugLogger) debugLogger.logStep('Build cost deducted', {
+ buildCost: roomTemplate.buildCost,
+ oldCredits: oldCredits,
+ newCredits: this.game.systems.economy.credits
+ });
+
+ this.game.showNotification(`${roomTemplate.name} ${isUpgrade ? 'upgraded' : 'built'}!`, 'success', 3000);
+
+ if (debugLogger) debugLogger.endStep('BaseSystem.addRoom', {
+ success: true,
+ roomType: roomType,
+ roomName: roomTemplate.name,
+ isUpgrade: isUpgrade,
+ roomId: isUpgrade ? existingRoom.id : this.base.rooms[this.base.rooms.length - 1].id,
+ newLevel: isUpgrade ? existingRoom.level : 1,
+ finalRoomsCount: this.base.rooms.length,
+ buildCost: roomTemplate.buildCost,
+ finalCredits: this.game.systems.economy.credits
+ });
+
+ return true;
+ }
+
+ removeRoom(roomId) {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('BaseSystem.removeRoom', {
+ roomId: roomId,
+ currentRoomsCount: this.base.rooms.length
+ });
+
+ const roomIndex = this.base.rooms.findIndex(r => r.id === roomId);
+ if (roomIndex === -1) {
+ if (debugLogger) debugLogger.endStep('BaseSystem.removeRoom', {
+ success: false,
+ reason: 'Room not found',
+ roomId: roomId
+ });
+ return false;
+ }
+
+ const room = this.base.rooms[roomIndex];
+
+ if (debugLogger) debugLogger.logStep('Room found for removal', {
+ roomId: room.id,
+ roomName: room.name,
+ roomType: room.type,
+ roomLevel: room.level,
+ roomPosition: { x: room.x, y: room.y }
+ });
+
+ // Don't allow removing command center
+ if (room.type === 'command_center') {
+ this.game.showNotification('Cannot remove Command Center', 'error', 3000);
+
+ if (debugLogger) debugLogger.endStep('BaseSystem.removeRoom', {
+ success: false,
+ reason: 'Cannot remove command center',
+ roomId: roomId,
+ roomType: room.type
+ });
+ return false;
+ }
+
+ // Remove from grid
+ let gridCellsCleared = 0;
+ for (let dy = 0; dy < room.height; dy++) {
+ for (let dx = 0; dx < room.width; dx++) {
+ this.grid[room.y + dy][room.x + dx] = null;
+ gridCellsCleared++;
+ }
+ }
+
+ if (debugLogger) debugLogger.logStep('Room removed from grid', {
+ gridCellsCleared: gridCellsCleared,
+ roomSize: { width: room.width, height: room.height }
+ });
+
+ // Remove from rooms array
+ this.base.rooms.splice(roomIndex, 1);
+
+ if (debugLogger) debugLogger.logStep('Room removed from rooms array', {
+ removedIndex: roomIndex,
+ remainingRoomsCount: this.base.rooms.length
+ });
+
+ // Update base stats
+ if (debugLogger) debugLogger.logStep('Updating base stats after removal');
+ this.updateBaseStats();
+
+ // Refund some resources
+ const refundAmount = Math.floor(this.roomTypes[room.type].buildCost * 0.5);
+ const oldCredits = this.game.systems.economy.credits;
+ this.game.systems.economy.addCredits(refundAmount, 'room_refund');
+
+ if (debugLogger) debugLogger.logStep('Refund processed', {
+ refundAmount: refundAmount,
+ oldCredits: oldCredits,
+ newCredits: this.game.systems.economy.credits,
+ originalBuildCost: this.roomTypes[room.type].buildCost,
+ refundPercentage: 0.5
+ });
+
+ this.game.showNotification(`${room.name} removed. Refunded ${refundAmount} credits`, 'info', 3000);
+
+ if (debugLogger) debugLogger.endStep('BaseSystem.removeRoom', {
+ success: true,
+ roomId: roomId,
+ roomName: room.name,
+ roomType: room.type,
+ refundAmount: refundAmount,
+ finalRoomsCount: this.base.rooms.length,
+ finalCredits: this.game.systems.economy.credits
+ });
+
+ return true;
+ }
+
+ calculateBaseStats() {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('BaseSystem.calculateBaseStats', {
+ currentRoomsCount: this.base.rooms.length,
+ currentPower: this.base.power,
+ currentMaxPower: this.base.maxPower,
+ currentStorage: this.base.storage,
+ currentMaxStorage: this.base.maxStorage
+ });
+
+ let totalPowerCost = 0;
+ let totalStorageBonus = 0;
+
+ // Calculate power cost and storage bonus from rooms
+ this.base.rooms.forEach(room => {
+ const roomType = this.roomTypes[room.type];
+ const roomPowerCost = roomType.powerCost * room.level;
+ const roomStorageBonus = roomType.storageBonus * room.level;
+
+ totalPowerCost += roomPowerCost;
+ totalStorageBonus += roomStorageBonus;
+
+ if (debugLogger) debugLogger.logStep('Room contribution calculated', {
+ roomId: room.id,
+ roomType: room.type,
+ roomName: room.name,
+ roomLevel: room.level,
+ powerCost: roomPowerCost,
+ storageBonus: roomStorageBonus
+ });
+ });
+
+ // Add upgrade bonuses
+ const storageExpansion = (this.upgrades.storage_expansion?.currentLevel || 0) * (this.upgrades.storage_expansion?.slotBonus || 5);
+ totalStorageBonus += storageExpansion;
+
+ if (debugLogger) debugLogger.logStep('Upgrade bonuses calculated', {
+ storageExpansionLevel: this.upgrades.storage_expansion?.currentLevel || 0,
+ storageExpansionBonus: storageExpansion,
+ totalStorageBonusWithUpgrades: totalStorageBonus
+ });
+
+ const oldPower = this.base.power;
+ const oldMaxPower = this.base.maxPower;
+ const oldStorage = this.base.storage;
+ const oldMaxStorage = this.base.maxStorage;
+
+ this.base.power = Math.max(0, totalPowerCost);
+ this.base.maxPower = 100 + ((this.upgrades.power_efficiency?.currentLevel || 0) * 20);
+
+ // Update inventory base slots with storage upgrades
+ if (this.game.systems.inventory) {
+ const newBaseSlots = 30 + totalStorageBonus; // 30 base + storage bonuses
+ const oldBaseSlots = this.game.systems.inventory.baseMaxSlots;
+
+ this.game.systems.inventory.baseMaxSlots = newBaseSlots;
+ this.game.systems.inventory.updateStarbaseBonusSlots(this.game.systems.inventory.starbaseBonusSlots);
+
+ if (debugLogger) debugLogger.logStep('Inventory slots updated', {
+ oldBaseSlots: oldBaseSlots,
+ newBaseSlots: newBaseSlots,
+ starbaseBonusSlots: this.game.systems.inventory.starbaseBonusSlots,
+ finalMaxSlots: this.game.systems.inventory.maxSlots
+ });
+ }
+
+ // Keep legacy storage for backward compatibility
+ this.base.storage = 1000 + (totalStorageBonus * 100);
+ this.base.maxStorage = this.base.storage;
+
+ if (debugLogger) debugLogger.endStep('BaseSystem.calculateBaseStats', {
+ powerChanges: {
+ oldPower: oldPower,
+ newPower: this.base.power,
+ oldMaxPower: oldMaxPower,
+ newMaxPower: this.base.maxPower
+ },
+ storageChanges: {
+ oldStorage: oldStorage,
+ newStorage: this.base.storage,
+ oldMaxStorage: oldMaxStorage,
+ newMaxStorage: this.base.maxStorage
+ },
+ totals: {
+ totalPowerCost: totalPowerCost,
+ totalStorageBonus: totalStorageBonus,
+ storageExpansionBonus: storageExpansion
+ },
+ roomsProcessed: this.base.rooms.length
+ });
+ }
+
+ // Base upgrades
+ upgradeBase(upgradeId) {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('BaseSystem.upgradeBase', {
+ upgradeId: upgradeId,
+ currentCredits: this.game.systems.economy.credits,
+ currentUpgradeLevel: this.upgrades[upgradeId] ? this.upgrades[upgradeId].currentLevel : null
+ });
+
+ const upgrade = this.upgrades[upgradeId];
+ if (!upgrade) {
+ if (debugLogger) debugLogger.endStep('BaseSystem.upgradeBase', {
+ success: false,
+ reason: 'Upgrade not found',
+ upgradeId: upgradeId
+ });
+ return false;
+ }
+
+ if (debugLogger) debugLogger.logStep('Upgrade found', {
+ upgradeName: upgrade.name,
+ currentLevel: upgrade.currentLevel,
+ maxLevel: upgrade.maxLevel,
+ baseCost: upgrade.cost
+ });
+
+ if (upgrade.currentLevel >= upgrade.maxLevel) {
+ this.game.showNotification('Upgrade at maximum level', 'warning', 3000);
+
+ if (debugLogger) debugLogger.endStep('BaseSystem.upgradeBase', {
+ success: false,
+ reason: 'Upgrade at maximum level',
+ upgradeId: upgradeId,
+ currentLevel: upgrade.currentLevel,
+ maxLevel: upgrade.maxLevel
+ });
+ return false;
+ }
+
+ const cost = upgrade.cost * (upgrade.currentLevel + 1);
+ if (this.game.systems.economy.credits < cost) {
+ this.game.showNotification('Not enough credits', 'error', 3000);
+
+ if (debugLogger) debugLogger.endStep('BaseSystem.upgradeBase', {
+ success: false,
+ reason: 'Insufficient credits',
+ upgradeId: upgradeId,
+ requiredCredits: cost,
+ currentCredits: this.game.systems.economy.credits
+ });
+ return false;
+ }
+
+ if (debugLogger) debugLogger.logStep('Upgrade validation passed', {
+ upgradeId: upgradeId,
+ currentLevel: upgrade.currentLevel,
+ newLevel: upgrade.currentLevel + 1,
+ cost: cost
+ });
+
+ // Apply upgrade
+ const oldCredits = this.game.systems.economy.credits;
+ this.game.systems.economy.removeCredits(cost);
+ upgrade.currentLevel++;
+
+ if (debugLogger) debugLogger.logStep('Upgrade applied', {
+ oldCredits: oldCredits,
+ newCredits: this.game.systems.economy.credits,
+ costDeducted: cost,
+ newUpgradeLevel: upgrade.currentLevel
+ });
+
+ // Apply effects
+ let statsUpdated = false;
+ if (upgrade.effect.powerReduction) {
+ if (debugLogger) debugLogger.logStep('Applying power reduction effect');
+ this.updateBaseStats();
+ statsUpdated = true;
+ }
+
+ if (upgrade.effect.storageBonus) {
+ if (debugLogger) debugLogger.logStep('Applying storage bonus effect');
+ this.updateBaseStats();
+ statsUpdated = true;
+ }
+
+ if (upgrade.effect.productionBonus) {
+ if (debugLogger) debugLogger.logStep('Applying production bonus effect', {
+ productionBonus: upgrade.effect.productionBonus
+ });
+ statsUpdated = true;
+ }
+
+ if (upgrade.effect.defenseBonus) {
+ if (debugLogger) debugLogger.logStep('Applying defense bonus effect', {
+ defenseBonus: upgrade.effect.defenseBonus
+ });
+ statsUpdated = true;
+ }
+
+ if (!statsUpdated) {
+ // For upgrades like storage_expansion that don't have effects in the effect object
+ if (debugLogger) debugLogger.logStep('Updating base stats for upgrade');
+ this.updateBaseStats();
+ }
+
+ this.game.showNotification(`${upgrade.name} upgraded to level ${upgrade.currentLevel}!`, 'success', 3000);
+
+ if (debugLogger) debugLogger.endStep('BaseSystem.upgradeBase', {
+ success: true,
+ upgradeId: upgradeId,
+ upgradeName: upgrade.name,
+ oldLevel: upgrade.currentLevel - 1,
+ newLevel: upgrade.currentLevel,
+ cost: cost,
+ finalCredits: this.game.systems.economy.credits,
+ effectsApplied: statsUpdated
+ });
+
+ return true;
+ }
+
+ // Base production
+ update(deltaTime) {
+ if (this.game.state.paused) {
+ return;
+ }
+
+ // Auto production from automation systems
+ const autoProductionRate = (this.upgrades.automation_systems?.currentLevel || 0) * 0.05;
+ if (autoProductionRate > 0) {
+ // Use real computer time delta
+ const production = (deltaTime / 1000) * autoProductionRate * 10; // 10 credits per second per level
+
+ this.game.systems.economy.addCredits(Math.floor(production), 'base_production');
+ }
+
+ // Power generation from power generators
+ const powerGenerators = this.base.rooms.filter(r => r.type === 'power_generator');
+ if (powerGenerators.length > 0) {
+ const powerGeneration = powerGenerators.reduce((total, gen) => total + Math.abs(gen.powerCost), 0);
+
+ // Power generation would affect other systems
+ }
+
+ // Research production
+ if ((this.upgrades.research_lab?.currentLevel || 0) > 0) {
+ const researchRate = (this.upgrades.research_lab?.currentLevel || 0) * 0.1;
+ // Research points generation would go here
+ }
+
+ // Resource processing
+ if ((this.upgrades.resource_processor?.currentLevel || 0) > 0) {
+ const processingRate = (this.upgrades.resource_processor?.currentLevel || 0) * 0.05;
+ // Resource processing would go here
+ }
+ }
+
+ // UI updates
+ updateUI() {
+ this.setupBaseNavigation();
+ this.updateBaseDisplay();
+ this.updateUpgradesList();
+ this.updateShipGallery();
+ this.updateStarbaseList();
+ }
+
+ // Base Navigation
+ setupBaseNavigation() {
+ const navButtons = document.querySelectorAll('.base-nav-btn');
+ navButtons.forEach(button => {
+ button.addEventListener('click', () => {
+ const view = button.dataset.view;
+ this.switchBaseView(view);
+ });
+ });
+ }
+
+ switchBaseView(view) {
+ // Hide all views
+ document.querySelectorAll('.base-view-content').forEach(content => {
+ content.classList.add('hidden');
+ });
+
+ // Remove active class from all buttons
+ document.querySelectorAll('.base-nav-btn').forEach(btn => {
+ btn.classList.remove('active');
+ });
+
+ // Show selected view
+ const selectedView = document.getElementById(`base-${view}`);
+ if (selectedView) {
+ selectedView.classList.remove('hidden');
+ }
+
+ // Add active class to clicked button
+ const activeBtn = document.querySelector(`[data-view="${view}"]`);
+ if (activeBtn) {
+ activeBtn.classList.add('active');
+ }
+
+ // Initialize view-specific content
+ if (view === 'visualization') {
+ this.initializeBaseVisualization();
+ } else if (view === 'ships') {
+ this.updateShipGallery();
+ } else if (view === 'starbases') {
+ this.updateStarbaseList();
+ this.updateStarbasePurchaseList();
+ }
+ }
+
+ // Base Visualization
+ initializeBaseVisualization() {
+ const canvas = document.getElementById('baseCanvas');
+ if (!canvas) return;
+
+ const ctx = canvas.getContext('2d');
+
+ // Clear canvas
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
+
+ // Draw base grid
+ this.drawBaseVisualization(ctx, canvas);
+
+ // Update info overlay
+ this.updateBaseInfoOverlay();
+ }
+
+ drawBaseVisualization(ctx, canvas) {
+ const cellSize = 40;
+ const offsetX = (canvas.width - this.gridSize * cellSize) / 2;
+ const offsetY = (canvas.height - this.gridSize * cellSize) / 2;
+
+ // Draw grid
+ ctx.strokeStyle = '#333';
+ ctx.lineWidth = 1;
+
+ for (let y = 0; y <= this.gridSize; y++) {
+ ctx.beginPath();
+ ctx.moveTo(offsetX, offsetY + y * cellSize);
+ ctx.lineTo(offsetX + this.gridSize * cellSize, offsetY + y * cellSize);
+ ctx.stroke();
+ }
+
+ for (let x = 0; x <= this.gridSize; x++) {
+ ctx.beginPath();
+ ctx.moveTo(offsetX + x * cellSize, offsetY);
+ ctx.lineTo(offsetX + x * cellSize, offsetY + this.gridSize * cellSize);
+ ctx.stroke();
+ }
+
+ // Draw rooms
+ for (let y = 0; y < this.gridSize; y++) {
+ for (let x = 0; x < this.gridSize; x++) {
+ const roomId = this.grid[y][x];
+ if (roomId) {
+ const room = this.base.rooms.find(r => r.id === roomId);
+ if (room) {
+ this.drawRoom(ctx, room, x, y, cellSize, offsetX, offsetY);
+ }
+ }
+ }
+ }
+ }
+
+ drawRoom(ctx, room, gridX, gridY, cellSize, offsetX, offsetY) {
+ const x = offsetX + gridX * cellSize;
+ const y = offsetY + gridY * cellSize;
+
+ // Room colors by type
+ const roomColors = {
+ command_center: '#4CAF50',
+ power_generator: '#FFC107',
+ defense_turret: '#F44336',
+ storage: '#2196F3',
+ research_lab: '#9C27B0',
+ factory: '#FF9800',
+ medical_bay: '#00BCD4',
+ hangar: '#795548'
+ };
+
+ const color = roomColors[room.type] || '#666';
+
+ // Draw room
+ ctx.fillStyle = color;
+ ctx.fillRect(x + 2, y + 2, cellSize - 4, cellSize - 4);
+
+ // Draw room border
+ ctx.strokeStyle = '#fff';
+ ctx.lineWidth = 2;
+ ctx.strokeRect(x + 2, y + 2, cellSize - 4, cellSize - 4);
+
+ // Draw room icon/initial
+ ctx.fillStyle = '#fff';
+ ctx.font = 'bold 16px Arial';
+ ctx.textAlign = 'center';
+ ctx.textBaseline = 'middle';
+ ctx.fillText(room.name.charAt(0), x + cellSize/2, y + cellSize/2);
+ }
+
+ updateBaseInfoOverlay() {
+ const infoDisplay = document.getElementById('baseInfoDisplay');
+ if (!infoDisplay) return;
+
+ const totalRooms = this.base.rooms.length;
+ const powerUsage = this.base.power;
+ const maxPower = this.base.maxPower;
+ const storage = this.base.storage;
+ const maxStorage = this.base.maxStorage;
+
+ infoDisplay.innerHTML = `
+
+ Base Name: ${this.base.name}
+
+
+ Level: ${this.base.level}
+
+
+ Total Rooms: ${totalRooms}
+
+
+ Power Usage: ${powerUsage}/${maxPower}
+
+
+ Storage: ${storage}/${maxStorage}
+
+ `;
+ }
+
+ // Ship Gallery
+ initializeShipGallery() {
+ // Initialize purchased ships array
+ if (!this.purchasedShips) {
+ this.purchasedShips = [];
+
+ // Add current ship to gallery with proper timestamp ID
+ const player = this.game.systems.player;
+ if (player.ship) {
+ this.purchasedShips.push({
+ id: Date.now().toString(), // Use timestamp instead of hardcoded 'current_ship'
+ name: player.ship.name || 'Default Ship',
+ class: player.ship.class || 'fighter',
+ level: player.ship.level || 1,
+ stats: { ...player.attributes },
+ isCurrent: true
+ });
+ }
+ }
+ }
+
+ updateShipGallery() {
+ const shipGrid = document.getElementById('shipGrid');
+ if (!shipGrid) {
+ return;
+ }
+
+ if (!this.purchasedShips) {
+ this.initializeShipGallery();
+ }
+
+ if (!this.purchasedShips || this.purchasedShips.length === 0) {
+ shipGrid.innerHTML = 'No ships purchased yet. Visit the shop to buy ships!
';
+ return;
+ }
+
+ shipGrid.innerHTML = '';
+
+ this.purchasedShips.forEach(ship => {
+ const shipElement = document.createElement('div');
+ shipElement.className = `ship-card ${ship.isCurrent ? 'current-ship' : ''}`;
+
+ shipElement.innerHTML = `
+
+
+
Class: ${ship.class}
+
Level: ${ship.level}
+
+
+
Attack: ${ship.stats.attack || 0}
+
Speed: ${ship.stats.speed || 0}
+
Defense: ${ship.stats.defense || 0}
+
+ ${!ship.isCurrent ? `
+ Equip Ship
+ ` : ''}
+ `;
+
+ shipGrid.appendChild(shipElement);
+ });
+ }
+
+ equipShip(shipId) {
+ const ship = this.purchasedShips.find(s => s.id === shipId);
+ if (!ship) {
+ this.game.showNotification('Ship not found!', 'error', 3000);
+ return;
+ }
+
+ if (ship.isCurrent) {
+ this.game.showNotification('This ship is already equipped!', 'info', 3000);
+ return;
+ }
+
+ const player = this.game.systems.player;
+
+ player.ship.name = ship.name;
+ player.ship.class = ship.class;
+ player.ship.level = ship.level;
+ player.ship.texture = ship.texture; // Copy texture for image display
+
+ // Apply ship-specific stats (not just attributes)
+ if (ship.stats.health || ship.stats.hull) {
+ const healthValue = ship.stats.health || ship.stats.hull || 100;
+ player.ship.maxHealth = healthValue;
+ player.ship.health = healthValue;
+ }
+ if (ship.stats.attack) {
+ player.ship.attack = ship.stats.attack;
+ }
+ if (ship.stats.defense) {
+ player.ship.defense = ship.stats.defense;
+ }
+ if (ship.stats.speed) {
+ player.ship.speed = ship.stats.speed;
+ }
+ if (ship.stats.criticalChance) {
+ player.ship.criticalChance = ship.stats.criticalChance;
+ }
+ if (ship.stats.criticalDamage) {
+ player.ship.criticalDamage = ship.stats.criticalDamage;
+ }
+
+ // Also update player attributes for compatibility
+ player.attributes.attack = ship.stats.attack || player.attributes.attack;
+ player.attributes.speed = ship.stats.speed || player.attributes.speed;
+ player.attributes.defense = ship.stats.defense || player.attributes.defense;
+ if (ship.stats.health || ship.stats.hull) {
+ const healthValue = ship.stats.health || ship.stats.hull || 100;
+ player.attributes.maxHealth = healthValue;
+ player.attributes.health = healthValue;
+ }
+
+ // Update isCurrent flags
+ this.purchasedShips.forEach(s => s.isCurrent = false);
+ ship.isCurrent = true;
+
+ // Force immediate gallery update after flag changes
+ const shipGrid = document.getElementById('shipGrid');
+ if (shipGrid) {
+ shipGrid.innerHTML = '';
+ this.updateShipGallery();
+ }
+
+ // Also update ShipSystem's currentShip to keep systems synchronized
+ if (this.game.systems.ship) {
+ // Find or create a ship object in ShipSystem format
+ const shipSystemShip = {
+ id: ship.id,
+ name: ship.name,
+ image: ship.texture || `assets/textures/ships/${ship.class.toLowerCase()}.png`,
+ class: ship.class,
+ level: ship.level,
+ stats: ship.stats,
+ status: 'active',
+ rarity: 'Common'
+ };
+
+ // Update ShipSystem's current ship
+ this.game.systems.ship.currentShip = shipSystemShip;
+
+ // Update the ships array in ShipSystem if needed
+ const existingShipIndex = this.game.systems.ship.ships.findIndex(s => s.id === ship.id);
+ if (existingShipIndex >= 0) {
+ this.game.systems.ship.ships[existingShipIndex].status = 'active';
+ this.game.systems.ship.ships[existingShipIndex] = shipSystemShip;
+ } else {
+ // Add to ships array if not present
+ this.game.systems.ship.ships.push(shipSystemShip);
+ }
+
+ // Mark all other ships as inactive
+ this.game.systems.ship.ships.forEach(s => {
+ if (s.id !== ship.id) s.status = 'inactive';
+ });
+ }
+
+ player.updateUI();
+ this.updateShipGallery();
+
+ // Also update ShipSystem display
+ if (this.game.systems.ship && this.game.systems.ship.updateCurrentShipDisplay) {
+ this.game.systems.ship.updateCurrentShipDisplay();
+ } else {
+ this.updateCurrentShipDisplayDirect();
+ }
+
+ // Force save the game to persist the ship change
+ this.game.save().then(() => {
+ // Force complete gallery refresh after save completes
+ const shipGrid = document.getElementById('shipGrid');
+ if (shipGrid) {
+ shipGrid.innerHTML = ''; // Clear completely
+ this.updateShipGallery(); // Rebuild from scratch
+ }
+
+ // Force ShipSystem to sync with new current ship
+ if (this.game.systems.ship && this.game.systems.ship.syncWithPlayerShip) {
+ this.game.systems.ship.syncWithPlayerShip();
+ }
+ });
+
+ this.game.showNotification(`Equipped ${ship.name}!`, 'success', 3000);
+ }
+
+ updateCurrentShipDisplayDirect() {
+ const player = this.game.systems.player;
+ if (!player || !player.ship) {
+ return;
+ }
+
+ const elements = {
+ currentShipImage: document.getElementById('currentShipImage'),
+ currentShipName: document.getElementById('currentShipName'),
+ currentShipClass: document.getElementById('currentShipClass'),
+ currentShipLevel: document.getElementById('currentShipLevel'),
+ currentShipHealth: document.getElementById('currentShipHealth'),
+ currentShipAttack: document.getElementById('currentShipAttack'),
+ currentShipDefense: document.getElementById('currentShipDefense'),
+ currentShipSpeed: document.getElementById('currentShipSpeed')
+ };
+
+ const ship = player.ship;
+
+ if (elements.currentShipImage) {
+ const imagePath = ship.texture || `assets/textures/ships/${ship.class.toLowerCase()}.png`;
+ console.log(`[DEBUG] Ship image path: ${imagePath}, texture: ${ship.texture}, class: ${ship.class}`);
+ elements.currentShipImage.src = imagePath;
+
+ // Add error handling for image loading
+ elements.currentShipImage.onerror = function() {
+ console.error(`[DEBUG] Failed to load image: ${imagePath}`);
+ // Fallback to default ship image
+ this.src = 'assets/textures/ships/starter_cruiser.png';
+ };
+ }
+
+ if (elements.currentShipName) elements.currentShipName.textContent = ship.name || 'Unknown Ship';
+ if (elements.currentShipClass) elements.currentShipClass.textContent = ship.class || 'Unknown';
+ if (elements.currentShipLevel) elements.currentShipLevel.textContent = ship.level || 1;
+ if (elements.currentShipHealth) elements.currentShipHealth.textContent = `${ship.health}/${ship.maxHealth}`;
+ if (elements.currentShipAttack) elements.currentShipAttack.textContent = ship.attack || 0;
+ if (elements.currentShipDefense) elements.currentShipDefense.textContent = ship.defense || ship.defence || 0;
+ if (elements.currentShipSpeed) elements.currentShipSpeed.textContent = ship.speed || 0;
+ }
+
+ addShipToGallery(shipData) {
+ this.purchasedShips.push({
+ id: Date.now().toString(),
+ name: shipData.name,
+ class: shipData.type,
+ level: 1,
+ stats: { ...shipData.stats },
+ texture: shipData.texture, // Include texture for image display
+ isCurrent: false
+ });
+
+ this.updateShipGallery();
+ }
+
+ // Starbase System
+ initializeStarbaseSystem() {
+ // Initialize starbase collection
+ if (!this.starbases) {
+ this.starbases = [
+ {
+ id: 'main_base',
+ name: 'Command Center Alpha',
+ type: 'command',
+ level: 1,
+ location: 'Sector Alpha-1',
+ isMain: true
+ }
+ ];
+ }
+
+ // Define available starbase types
+ this.starbaseTypes = {
+ mining: {
+ name: 'Mining Outpost',
+ description: 'Automated mining facility for resource extraction',
+ price: 25000,
+ currency: 'credits',
+ icon: 'fa-hammer',
+ benefits: ['+500 credits/hour', '+50 gems/day', '+5 inventory slots'],
+ requiredLevel: 5,
+ inventoryBonus: 5
+ },
+ research: {
+ name: 'Research Station',
+ description: 'Advanced research facility for technology development',
+ price: 50000,
+ currency: 'credits',
+ icon: 'fa-flask',
+ benefits: ['+100 experience/hour', '+10% skill progression', '+10 inventory slots'],
+ requiredLevel: 10,
+ inventoryBonus: 10
+ },
+ defense: {
+ name: 'Defense Platform',
+ description: 'Military installation for sector protection',
+ price: 40000,
+ currency: 'credits',
+ icon: 'fa-shield-alt',
+ benefits: ['+15% combat effectiveness', '+100 defense rating'],
+ requiredLevel: 8,
+ inventoryBonus: 8
+ },
+ trading: {
+ name: 'Trading Hub',
+ description: 'Commercial center for trade and commerce',
+ price: 75000,
+ currency: 'credits',
+ icon: 'fa-store',
+ benefits: ['+20% shop discounts', '+200 gems/day'],
+ requiredLevel: 12,
+ inventoryBonus: 15
+ }
+ };
+ }
+
+ updateStarbaseList() {
+ const starbaseList = document.getElementById('starbaseList');
+ if (!starbaseList) return;
+
+ starbaseList.innerHTML = '';
+
+ // Initialize starbases if not exists
+ if (!this.starbases) {
+ this.initializeStarbaseSystem();
+ }
+
+ if (!this.starbases || this.starbases.length === 0) {
+ starbaseList.innerHTML = 'No starbases owned yet.
';
+ return;
+ }
+
+ this.starbases.forEach(starbase => {
+ const starbaseElement = document.createElement('div');
+ starbaseElement.className = `starbase-card ${starbase.isMain ? 'main-starbase' : ''}`;
+
+ starbaseElement.innerHTML = `
+
+
+
Type: ${starbase.type}
+
Level: ${starbase.level}
+
Location: ${starbase.location}
+
+
+
+ View Details
+
+ ${!starbase.isMain ? `
+
+ Decommission
+
+ ` : ''}
+
+ `;
+
+ starbaseList.appendChild(starbaseElement);
+ });
+ }
+
+ updateStarbasePurchaseList() {
+ const purchaseList = document.getElementById('starbasePurchaseItems');
+ if (!purchaseList) return;
+
+ purchaseList.innerHTML = '';
+
+ const player = this.game.systems.player;
+
+ Object.entries(this.starbaseTypes).forEach(([type, starbase]) => {
+ const starbaseElement = document.createElement('div');
+ starbaseElement.className = 'starbase-purchase-card';
+
+ const canAfford = this.game.systems.economy.credits >= starbase.price;
+ const meetsLevel = player.stats.level >= starbase.requiredLevel;
+ const canPurchase = canAfford && meetsLevel;
+
+ starbaseElement.innerHTML = `
+
+
+ ${starbase.description}
+
+
+
Benefits:
+
+ ${starbase.benefits.map(benefit => `${benefit} `).join('')}
+
+
+
+
+ Cost: ${starbase.price} ${starbase.currency}
+
+
+ Required Level: ${starbase.requiredLevel}
+
+
+
+
+ ${!meetsLevel ? 'Level Required' : !canAfford ? 'Insufficient Funds' : 'Purchase Starbase'}
+
+
+ `;
+
+ purchaseList.appendChild(starbaseElement);
+ });
+ }
+
+ purchaseStarbase(type) {
+ const starbaseTemplate = this.starbaseTypes[type];
+ if (!starbaseTemplate) {
+ this.game.showNotification('Invalid starbase type!', 'error', 3000);
+ return;
+ }
+
+ const player = this.game.systems.player;
+ const economy = this.game.systems.economy;
+
+ // Check requirements
+ if (player.stats.level < starbaseTemplate.requiredLevel) {
+ this.game.showNotification(`Level ${starbaseTemplate.requiredLevel} required!`, 'error', 3000);
+ return;
+ }
+
+ if (economy.credits < starbaseTemplate.price) {
+ this.game.showNotification('Insufficient credits!', 'error', 3000);
+ return;
+ }
+
+ // Process payment
+ economy.credits -= starbaseTemplate.price;
+
+ // Create new starbase
+ const newStarbase = {
+ id: Date.now().toString(),
+ name: `${starbaseTemplate.name} ${this.starbases.length}`,
+ type: type,
+ level: 1,
+ location: `Sector ${String.fromCharCode(65 + this.starbases.length)}-${this.starbases.length}`,
+ isMain: false,
+ benefits: starbaseTemplate.benefits
+ };
+
+ this.starbases.push(newStarbase);
+
+ // Apply benefits
+ this.applyStarbaseBenefits(newStarbase);
+
+ // Update UI
+ economy.updateUI();
+ this.updateStarbaseList();
+ this.updateStarbasePurchaseList();
+
+ this.game.showNotification(`Purchased ${starbaseTemplate.name}!`, 'success', 4000);
+ }
+
+ applyStarbaseBenefits(starbase) {
+ // Apply passive benefits based on starbase type
+ switch (starbase.type) {
+ case 'mining':
+ // Start passive credit generation
+ this.startMiningProduction(starbase);
+ // Apply inventory bonus
+ this.updateInventoryBonusSlots();
+ break;
+ case 'research':
+ // Apply experience bonus
+ this.game.systems.player.experienceMultiplier = (this.game.systems.player.experienceMultiplier || 1) + 0.1;
+ // Apply inventory bonus
+ this.updateInventoryBonusSlots();
+ break;
+ case 'defense':
+ // Apply defense bonus
+ this.game.systems.player.attributes.defense = Math.floor(this.game.systems.player.attributes.defense * 1.2);
+ // Apply inventory bonus
+ this.updateInventoryBonusSlots();
+ break;
+ case 'trading':
+ // Apply shop discount
+ this.game.systems.economy.discountMultiplier = 0.85;
+ // Apply inventory bonus
+ this.updateInventoryBonusSlots();
+ break;
+ }
+ }
+
+ updateInventoryBonusSlots() {
+ // Calculate total inventory bonus from all owned starbases
+ let totalBonusSlots = 0;
+
+ if (this.starbases && this.starbases.length > 0) {
+ this.starbases.forEach(starbase => {
+ const starbaseType = this.starbaseTypes[starbase.type];
+ if (starbaseType && starbaseType.inventoryBonus) {
+ totalBonusSlots += starbaseType.inventoryBonus;
+ }
+ });
+ }
+
+ // Update inventory system with new bonus slots
+ if (this.game.systems.inventory) {
+ this.game.systems.inventory.updateStarbaseBonusSlots(totalBonusSlots);
+ }
+ }
+
+ startMiningProduction(starbase) {
+ // Set up passive credit generation
+ if (!this.miningInterval) {
+ this.miningInterval = setInterval(() => {
+ this.game.systems.economy.addCredits(500, 'mining_outpost');
+ }, 3600000); // 1 hour
+ }
+ }
+
+ viewStarbaseDetails(starbaseId) {
+ const starbase = this.starbases.find(s => s.id === starbaseId);
+ if (!starbase) return;
+
+ const details = `
+ ${starbase.name}
+ Type: ${starbase.type}
+ Level: ${starbase.level}
+ Location: ${starbase.location}
+ ${starbase.benefits ? `
+ Active Benefits:
+
+ ${starbase.benefits.map(benefit => `${benefit} `).join('')}
+
+ ` : ''}
+ `;
+
+ if (this.game && this.game.systems && this.game.systems.ui) {
+ this.game.systems.ui.showModal('Starbase Details', details);
+ }
+ }
+
+ decommissionStarbase(starbaseId) {
+ if (!confirm('Are you sure you want to decommission this starbase? You will receive 50% of its value back.')) {
+ return;
+ }
+
+ const starbaseIndex = this.starbases.findIndex(s => s.id === starbaseId);
+ if (starbaseIndex === -1) return;
+
+ const starbase = this.starbases[starbaseIndex];
+ const refundAmount = Math.floor(this.starbaseTypes[starbase.type].price * 0.5);
+
+ // Remove starbase
+ this.starbases.splice(starbaseIndex, 1);
+
+ // Give refund
+ this.game.systems.economy.addCredits(refundAmount, 'starbase_refund');
+
+ // Update inventory bonus slots after removal
+ this.updateInventoryBonusSlots();
+
+ // Update UI
+ this.updateStarbaseList();
+ this.updateStarbasePurchaseList();
+
+ this.game.showNotification(`Decommissioned ${starbase.name}. Refunded ${refundAmount} credits.`, 'info', 4000);
+ }
+
+ updateBaseDisplay() {
+ const baseRoomsElement = document.getElementById('baseRooms');
+ if (!baseRoomsElement) return;
+
+ // Ensure grid is initialized
+ if (!this.grid || this.grid.length === 0) {
+ console.log('[BASE SYSTEM] Grid not initialized, initializing now');
+ this.initializeGrid();
+ }
+
+ baseRoomsElement.innerHTML = '';
+
+ // Create grid container
+ const gridContainer = document.createElement('div');
+ gridContainer.className = 'base-grid';
+
+ // Create grid cells
+ for (let y = 0; y < this.gridSize; y++) {
+ for (let x = 0; x < this.gridSize; x++) {
+ const cell = document.createElement('div');
+ cell.className = 'base-cell';
+ cell.dataset.x = x;
+ cell.dataset.y = y;
+
+ const room = this.getRoomAt(x, y);
+ if (room) {
+ // Check if this is the main cell of the room
+ if (room.x === x && room.y === y) {
+ cell.className += ` room ${room.type}`;
+ cell.innerHTML = `
+
+
+
+ L${room.level}
+ `;
+ cell.title = `${room.name} (Level ${room.level})`;
+ } else {
+ // Other cells of the same room are just filled
+ cell.innerHTML = '';
+ }
+ cell.title = `${room.name} (Level ${room.level})`;
+ } else {
+ cell.className += ' empty';
+ cell.addEventListener('click', () => this.showRoomBuildMenu(x, y));
+ }
+
+ gridContainer.appendChild(cell);
+ }
+ }
+
+ baseRoomsElement.appendChild(gridContainer);
+
+ // Add base info with connected inventory storage
+ const baseInfo = document.createElement('div');
+ baseInfo.className = 'base-info';
+
+ // Get inventory information
+ const inventoryInfo = this.game.systems.inventory ? this.game.systems.inventory.getInventoryInfo() : null;
+
+ baseInfo.innerHTML = `
+ ${this.base.name}
+
+
Level: ${this.base.level}
+
Power: ${this.base.power}/${this.base.maxPower}
+
Storage: ${inventoryInfo ? `${inventoryInfo.currentSlots}/${inventoryInfo.totalMaxSlots}` : `${this.base.storage}/${this.base.maxStorage}`} slots
+ ${inventoryInfo ? `
+ Base: ${inventoryInfo.baseMaxSlots} | Starbase Bonus: +${inventoryInfo.starbaseBonusSlots}
+
` : ''}
+
+ `;
+
+ baseRoomsElement.appendChild(baseInfo);
+ }
+
+ updateUpgradesList() {
+ const upgradesElement = document.getElementById('baseUpgrades');
+ if (!upgradesElement) return;
+
+ upgradesElement.innerHTML = '';
+
+ Object.entries(this.upgrades).forEach(([upgradeId, upgrade]) => {
+ const upgradeElement = document.createElement('div');
+ upgradeElement.className = 'upgrade-item';
+
+ const cost = upgrade.cost * (upgrade.currentLevel + 1);
+ const canAfford = this.game.systems.economy.credits >= cost;
+ const isMaxLevel = upgrade.currentLevel >= upgrade.maxLevel;
+
+ upgradeElement.innerHTML = `
+ ${upgrade.name}
+ ${upgrade.description}
+ Level: ${upgrade.currentLevel}/${upgrade.maxLevel}
+ Cost: ${cost} credits
+
+ ${isMaxLevel ? 'MAX' : 'Upgrade'}
+
+ `;
+
+ upgradesElement.appendChild(upgradeElement);
+ });
+}
+
+showRoomBuildMenu(x, y) {
+ const availableRooms = Object.entries(this.roomTypes)
+ .filter(([roomType, template]) =>
+ template.requiredLevel <= this.game.systems.player.stats.level
+ )
+ .filter(([roomType, template]) => this.canBuildRoom(roomType, x, y));
+
+ if (availableRooms.length === 0) {
+ this.game.showNotification('No rooms available for this location', 'warning', 3000);
+ return;
+ }
+
+ let menuContent = 'Build Room ';
+
+ availableRooms.forEach(([roomType, template]) => {
+ const roomSize = this.getRoomSize(template.size);
+ menuContent += `
+
+
+
+
Size: ${roomSize.width}x${roomSize.height}
+
Power: ${template.powerCost > 0 ? '+' : ''}${template.powerCost}
+
Cost: ${template.buildCost} credits
+
+
${template.description}
+
+ `;
+ });
+
+ menuContent += '
';
+}
+
+ updateUpgradesList() {
+ const upgradesElement = document.getElementById('baseUpgrades');
+ if (!upgradesElement) return;
+
+ upgradesElement.innerHTML = '';
+
+ Object.entries(this.upgrades).forEach(([upgradeId, upgrade]) => {
+ const upgradeElement = document.createElement('div');
+ upgradeElement.className = 'upgrade-item';
+
+ const cost = upgrade.cost * (upgrade.currentLevel + 1);
+ const canAfford = this.game.systems.economy.credits >= cost;
+ const isMaxLevel = upgrade.currentLevel >= upgrade.maxLevel;
+
+ upgradeElement.innerHTML = `
+ ${upgrade.name}
+ ${upgrade.description}
+ Level: ${upgrade.currentLevel}/${upgrade.maxLevel}
+ Cost: ${cost} credits
+
+ ${isMaxLevel ? 'MAX' : 'Upgrade'}
+
+ `;
+
+ upgradesElement.appendChild(upgradeElement);
+ });
+ }
+
+ showRoomBuildMenu(x, y) {
+ const availableRooms = Object.entries(this.roomTypes)
+ .filter(([roomType, template]) =>
+ template.requiredLevel <= this.game.systems.player.stats.level
+ )
+ .filter(([roomType, template]) => this.canBuildRoom(roomType, x, y));
+
+ if (availableRooms.length === 0) {
+ this.game.showNotification('No rooms available for this location', 'warning', 3000);
+ return;
+ }
+
+ let menuContent = 'Build Room ';
+
+ availableRooms.forEach(([roomType, template]) => {
+ const roomSize = this.getRoomSize(template.size);
+ menuContent += `
+
+
+
+
Size: ${roomSize.width}x${roomSize.height}
+
Power: ${template.powerCost > 0 ? '+' : ''}${template.powerCost}
+
Cost: ${template.buildCost} credits
+
+
${template.description}
+
+ `;
+ });
+
+ menuContent += '
';
+
+ if (this.game && this.game.systems && this.game.systems.ui) {
+ this.game.systems.ui.showModal('Build Room', menuContent);
+ }
+ }
+
+ // Save/Load
+ save() {
+ const debugLogger = window.debugLogger;
+
+ // if (debugLogger) debugLogger.startStep('BaseSystem.save', {
+ // baseName: this.base.name,
+ // baseLevel: this.base.level,
+ // roomsCount: this.base.rooms.length,
+ // gridCellsCount: this.grid.flat().filter(cell => cell !== null).length,
+ // upgradesCount: Object.keys(this.upgrades).length
+ // });
+
+ const saveData = {
+ base: this.base,
+ grid: this.grid,
+ upgrades: this.upgrades,
+ purchasedShips: this.purchasedShips || []
+ };
+
+ // if (debugLogger) debugLogger.endStep('BaseSystem.save', {
+ // saveDataSize: JSON.stringify(saveData).length,
+ // baseSaved: {
+ // name: saveData.base.name,
+ // level: saveData.base.level,
+ // roomsCount: saveData.base.rooms.length
+ // },
+ // gridSaved: {
+ // gridSize: this.gridSize,
+ // occupiedCells: this.grid.flat().filter(cell => cell !== null).length
+ // },
+ // upgradesSaved: Object.keys(saveData.upgrades).map(id => ({
+ // id: id,
+ // currentLevel: saveData.upgrades[id].currentLevel
+ // })),
+ // saveData: saveData
+ // });
+
+ return saveData;
+ }
+
+ load(data) {
+ const debugLogger = window.debugLogger;
+ const oldState = {
+ baseName: this.base.name,
+ baseLevel: this.base.level,
+ roomsCount: this.base.rooms.length,
+ upgradesState: Object.keys(this.upgrades).map(id => ({
+ id: id,
+ currentLevel: this.upgrades[id].currentLevel
+ }))
+ };
+
+ if (debugLogger) debugLogger.startStep('BaseSystem.load', {
+ oldState: oldState,
+ loadData: data
+ });
+
+ try {
+ if (data.base) {
+ const oldBase = { ...this.base };
+ this.base = { ...this.base, ...data.base };
+
+ if (debugLogger) debugLogger.logStep('Base data loaded', {
+ oldBase: {
+ name: oldBase.name,
+ level: oldBase.level,
+ roomsCount: oldBase.rooms.length
+ },
+ newBase: {
+ name: this.base.name,
+ level: this.base.level,
+ roomsCount: this.base.rooms.length
+ }
+ });
+ }
+
+ if (data.grid) {
+ const oldGrid = this.grid;
+ this.grid = data.grid;
+
+ if (debugLogger) debugLogger.logStep('Grid data loaded', {
+ oldGridCells: oldGrid.flat().filter(cell => cell !== null).length,
+ newGridCells: this.grid.flat().filter(cell => cell !== null).length,
+ gridSize: this.gridSize
+ });
+ }
+
+ if (data.upgrades) {
+ const oldUpgrades = Object.keys(this.upgrades).map(id => ({
+ id: id,
+ currentLevel: this.upgrades[id].currentLevel
+ }));
+
+ Object.entries(data.upgrades).forEach(([upgradeId, upgrade]) => {
+ if (this.upgrades[upgradeId]) {
+ Object.assign(this.upgrades[upgradeId], upgrade);
+
+ if (debugLogger) debugLogger.logStep('Upgrade loaded', {
+ upgradeId: upgradeId,
+ oldLevel: oldUpgrades.find(u => u.id === upgradeId)?.currentLevel || 0,
+ newLevel: this.upgrades[upgradeId].currentLevel
+ });
+ }
+ });
+ }
+
+ // Load purchased ships
+ if (data.purchasedShips) {
+ this.purchasedShips = data.purchasedShips;
+ if (debugLogger) debugLogger.logStep('Purchased ships loaded', {
+ shipsCount: this.purchasedShips.length,
+ shipNames: this.purchasedShips.map(s => s.name)
+ });
+
+ // Find the current ship from gallery and sync with player
+ const currentGalleryShip = this.purchasedShips.find(s => s.isCurrent);
+ if (currentGalleryShip) {
+ const player = this.game.systems.player;
+ if (player && player.ship) {
+ console.log('[DEBUG] Syncing player ship with gallery current ship:', currentGalleryShip);
+
+ // Update player's ship with gallery current ship data
+ player.ship.name = currentGalleryShip.name;
+ player.ship.class = currentGalleryShip.class;
+ player.ship.level = currentGalleryShip.level;
+ player.ship.texture = currentGalleryShip.texture;
+
+ // Update player attributes with ship stats
+ if (currentGalleryShip.stats) {
+ Object.assign(player.attributes, currentGalleryShip.stats);
+
+ // Update player.ship properties for consistency
+ player.ship.health = currentGalleryShip.stats.health || currentGalleryShip.stats.hull || 100;
+ player.ship.maxHealth = currentGalleryShip.stats.maxHealth || currentGalleryShip.stats.hull || 100;
+ player.ship.attack = currentGalleryShip.stats.attack || 10;
+ player.ship.defence = currentGalleryShip.stats.defense || currentGalleryShip.stats.defence || 5;
+ player.ship.speed = currentGalleryShip.stats.speed || 10;
+ }
+
+ console.log('[DEBUG] Player ship updated:', player.ship);
+ console.log('[DEBUG] Player attributes updated:', player.attributes);
+
+ // Update UI displays
+ player.updateUI();
+ if (this.game.systems.ship && this.game.systems.ship.updateCurrentShipDisplay) {
+ this.game.systems.ship.updateCurrentShipDisplay();
+ }
+ }
+ }
+ }
+
+ // Update base stats after loading
+ if (debugLogger) debugLogger.logStep('Updating base stats after load');
+ this.updateBaseStats();
+
+ // Update ship gallery after loading ships
+ if (data.purchasedShips) {
+ this.updateShipGallery();
+ }
+
+ if (debugLogger) debugLogger.endStep('BaseSystem.load', {
+ success: true,
+ oldState: oldState,
+ newState: {
+ baseName: this.base.name,
+ baseLevel: this.base.level,
+ roomsCount: this.base.rooms.length,
+ upgradesState: Object.keys(this.upgrades).map(id => ({
+ id: id,
+ currentLevel: this.upgrades[id].currentLevel
+ }))
+ },
+ baseStatsUpdated: true
+ });
+ } catch (error) {
+ if (debugLogger) debugLogger.errorEvent('BaseSystem.load', error, {
+ oldState: oldState,
+ loadData: data,
+ error: error.message
+ });
+ throw error;
+ }
+ }
+
+reset() {
+ this.baseLevel = 1;
+ this.rooms = {};
+ // Reset upgrades to initial state
+ this.upgrades = {
+ power_efficiency: {
+ name: 'Power Efficiency',
+ description: 'Reduces power consumption of all rooms',
+ cost: 1000,
+ effect: { powerReduction: 0.1 },
+ maxLevel: 5,
+ currentLevel: 0
+ },
+ storage_expansion: {
+ name: 'Storage Expansion',
+ description: 'Increases inventory slots for item storage',
+ cost: 5000,
+ maxLevel: 10,
+ currentLevel: 0,
+ icon: 'fa-boxes',
+ slotBonus: 5
+ },
+ automation_systems: {
+ name: 'Automation Systems',
+ description: 'Automated resource generation and processing',
+ cost: 2500,
+ maxLevel: 10,
+ currentLevel: 0,
+ icon: 'fa-robot'
+ }
+ };
+ this.resources = {
+ energy: 100,
+ materials: 50,
+ research: 0
+ };
+ this.initializeBaseData();
+}
+
+updateBaseStats() {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('BaseSystem.updateBaseStats');
+
+ // Calculate total power and storage from rooms
+ let totalPower = 0;
+ let totalStorage = 0;
+
+ this.base.rooms.forEach(room => {
+ const roomTemplate = this.roomTypes[room.type];
+ if (roomTemplate) {
+ totalPower += roomTemplate.powerGeneration || 0;
+ totalStorage += roomTemplate.storageBonus || 0;
+ }
+ });
+
+ // Update base stats
+ this.base.power = Math.max(0, totalPower);
+ this.base.maxStorage = 1000 + totalStorage;
+
+ // Update UI if available
+ if (this.game.systems.ui && this.game.systems.ui.updateBaseSystem) {
+ this.game.systems.ui.updateBaseSystem();
+ }
+
+ // if (debugLogger) debugLogger.endStep('BaseSystem.updateBaseStats', {
+ // totalPower: this.base.power,
+ // maxStorage: this.base.maxStorage,
+ // roomsCount: this.base.rooms.length
+ // });
+ }
+}
diff --git a/Client/js/systems/CraftingSystem.js b/Client/js/systems/CraftingSystem.js
new file mode 100644
index 0000000..0e4843a
--- /dev/null
+++ b/Client/js/systems/CraftingSystem.js
@@ -0,0 +1,651 @@
+/**
+ * Galaxy Strike Online - Crafting System
+ * Handles item crafting, recipes, and crafting skill progression
+ */
+
+class CraftingSystem extends BaseSystem {
+ constructor(gameEngine) {
+ super(gameEngine);
+
+ this.recipes = new Map();
+ this.currentCategory = 'weapons';
+ this.selectedRecipe = null;
+
+ this.initializeRecipes();
+ }
+
+ initializeRecipes() {
+ // Weapon Recipes
+ this.addRecipe('basic_blaster', {
+ name: 'Basic Blaster',
+ category: 'weapons',
+ description: 'A simple energy blaster for beginners',
+ requirements: {
+ weapon_crafting: 1,
+ crafting: 1
+ },
+ materials: [
+ { id: 'iron_ore', quantity: 5 },
+ { id: 'energy_crystal', quantity: 2 }
+ ],
+ results: [
+ { id: 'basic_blaster', quantity: 1 }
+ ],
+ experience: 10,
+ craftingTime: 3000 // 3 seconds
+ });
+
+ this.addRecipe('enhanced_blaster', {
+ name: 'Enhanced Blaster',
+ category: 'weapons',
+ description: 'An improved blaster with better damage output',
+ requirements: {
+ weapon_crafting: 3,
+ crafting: 5
+ },
+ materials: [
+ { id: 'iron_ore', quantity: 10 },
+ { id: 'energy_crystal', quantity: 5 },
+ { id: 'copper_wire', quantity: 3 }
+ ],
+ results: [
+ { id: 'enhanced_blaster', quantity: 1 }
+ ],
+ experience: 25,
+ craftingTime: 5000 // 5 seconds
+ });
+
+ this.addRecipe('laser_sniper_rifle', {
+ name: 'Laser Sniper Rifle',
+ category: 'weapons',
+ description: 'A long-range precision laser weapon',
+ requirements: {
+ weapon_crafting: 3,
+ crafting: 5
+ },
+ materials: [
+ { id: 'advanced_circuitboard', quantity: 2 },
+ { id: 'energy_crystal', quantity: 8 },
+ { id: 'steel_plate', quantity: 5 },
+ { id: 'copper_wire', quantity: 4 }
+ ],
+ results: [
+ { id: 'laser_sniper_rifle', quantity: 1 }
+ ],
+ experience: 40,
+ craftingTime: 8000 // 8 seconds
+ });
+
+ this.addRecipe('plasma_cannon', {
+ name: 'Plasma Cannon',
+ category: 'weapons',
+ description: 'A devastating plasma-based weapon',
+ requirements: {
+ weapon_crafting: 5,
+ crafting: 7
+ },
+ materials: [
+ { id: 'advanced_components', quantity: 3 },
+ { id: 'energy_crystal', quantity: 12 },
+ { id: 'steel_plate', quantity: 8 },
+ { id: 'battery', quantity: 3 }
+ ],
+ results: [
+ { id: 'plasma_cannon', quantity: 1 }
+ ],
+ experience: 60,
+ craftingTime: 12000 // 12 seconds
+ });
+
+ // Armor Recipes
+ this.addRecipe('basic_armor', {
+ name: 'Basic Armor',
+ category: 'armor',
+ description: 'Light armor providing basic protection',
+ requirements: {
+ armor_forging: 1,
+ crafting: 1
+ },
+ materials: [
+ { id: 'iron_ore', quantity: 8 },
+ { id: 'leather', quantity: 3 }
+ ],
+ results: [
+ { id: 'basic_armor', quantity: 1 }
+ ],
+ experience: 15,
+ craftingTime: 4000 // 4 seconds
+ });
+
+ this.addRecipe('reinforced_armor', {
+ name: 'Reinforced Armor',
+ category: 'armor',
+ description: 'Heavy armor with enhanced protection',
+ requirements: {
+ armor_forging: 4,
+ crafting: 6
+ },
+ materials: [
+ { id: 'iron_ore', quantity: 15 },
+ { id: 'steel_plate', quantity: 5 },
+ { id: 'leather', quantity: 5 }
+ ],
+ results: [
+ { id: 'reinforced_armor', quantity: 1 }
+ ],
+ experience: 35,
+ craftingTime: 6000 // 6 seconds
+ });
+
+ // Item Recipes
+ this.addRecipe('health_kit', {
+ name: 'Health Kit',
+ category: 'items',
+ description: 'A medical kit that restores health',
+ requirements: {
+ crafting: 2
+ },
+ materials: [
+ { id: 'herbs', quantity: 3 },
+ { id: 'bandages', quantity: 2 }
+ ],
+ results: [
+ { id: 'health_kit', quantity: 3 }
+ ],
+ experience: 5,
+ craftingTime: 2000 // 2 seconds
+ });
+
+ this.addRecipe('basic_circuit', {
+ name: 'Basic Circuit',
+ category: 'items',
+ description: 'Create a basic electronic circuit',
+ requirements: {
+ crafting: 3
+ },
+ materials: [
+ { id: 'basic_circuitboard', quantity: 1 },
+ { id: 'copper_wire', quantity: 2 }
+ ],
+ results: [
+ { id: 'basic_circuit', quantity: 1 }
+ ],
+ experience: 8,
+ craftingTime: 2500 // 2.5 seconds
+ });
+
+ this.addRecipe('advanced_circuit', {
+ name: 'Advanced Circuit',
+ category: 'items',
+ description: 'Create an advanced electronic circuit',
+ requirements: {
+ crafting: 5
+ },
+ materials: [
+ { id: 'advanced_circuitboard', quantity: 1 },
+ { id: 'energy_crystal', quantity: 2 },
+ { id: 'copper_wire', quantity: 3 }
+ ],
+ results: [
+ { id: 'advanced_circuit', quantity: 1 }
+ ],
+ experience: 15,
+ craftingTime: 4000 // 4 seconds
+ });
+
+ this.addRecipe('electronic_device', {
+ name: 'Electronic Device',
+ category: 'items',
+ description: 'Create a complex electronic device',
+ requirements: {
+ crafting: 7
+ },
+ materials: [
+ { id: 'advanced_components', quantity: 1 },
+ { id: 'battery', quantity: 2 },
+ { id: 'common_circuitboard', quantity: 1 }
+ ],
+ results: [
+ { id: 'electronic_device', quantity: 1 }
+ ],
+ experience: 20,
+ craftingTime: 5000 // 5 seconds
+ });
+
+ // Ship Component Recipes
+ this.addRecipe('shield_generator', {
+ name: 'Shield Generator',
+ category: 'ships',
+ description: 'A basic shield generator for ship protection',
+ requirements: {
+ engineering: 2,
+ crafting: 4
+ },
+ materials: [
+ { id: 'energy_crystal', quantity: 8 },
+ { id: 'steel_plate', quantity: 5 },
+ { id: 'copper_wire', quantity: 4 }
+ ],
+ results: [
+ { id: 'shield_generator', quantity: 1 }
+ ],
+ experience: 30,
+ craftingTime: 8000 // 8 seconds
+ });
+
+ this.addRecipe('engine_upgrade', {
+ name: 'Engine Upgrade',
+ category: 'ships',
+ description: 'An upgrade that improves ship engine performance',
+ requirements: {
+ engineering: 5,
+ crafting: 7
+ },
+ materials: [
+ { id: 'steel_plate', quantity: 10 },
+ { id: 'energy_crystal', quantity: 6 },
+ { id: 'rare_metal', quantity: 2 }
+ ],
+ results: [
+ { id: 'engine_upgrade', quantity: 1 }
+ ],
+ experience: 50,
+ craftingTime: 10000 // 10 seconds
+ });
+
+ this.addRecipe('quantum_computer', {
+ name: 'Quantum Computer',
+ category: 'ships',
+ description: 'Advanced quantum computer for high-end ship systems',
+ requirements: {
+ engineering: 8,
+ crafting: 10
+ },
+ materials: [
+ { id: 'advanced_components', quantity: 3 },
+ { id: 'energy_crystal', quantity: 8 },
+ { id: 'rare_metal', quantity: 4 },
+ { id: 'copper_wire', quantity: 6 }
+ ],
+ results: [
+ { id: 'quantum_computer', quantity: 1 }
+ ],
+ experience: 100,
+ craftingTime: 15000 // 15 seconds
+ });
+ }
+
+ addRecipe(id, recipe) {
+ recipe.id = id;
+ recipe.unlocked = false;
+ this.recipes.set(id, recipe);
+ }
+
+ update(deltaTime) {
+ // Check for newly unlocked recipes
+ this.checkRecipeUnlocks();
+ }
+
+ checkRecipeUnlocks() {
+ const skillSystem = this.game.systems.skillSystem;
+ if (!skillSystem) return;
+
+ for (const [id, recipe] of this.recipes) {
+ if (!recipe.unlocked) {
+ let canCraft = true;
+
+ // Check skill requirements
+ if (recipe.requirements) {
+ for (const [skillName, requiredLevel] of Object.entries(recipe.requirements)) {
+ const skillLevel = skillSystem.getSkillLevel(skillName);
+ if (skillLevel < requiredLevel) {
+ canCraft = false;
+ break;
+ }
+ }
+ }
+
+ if (canCraft) {
+ recipe.unlocked = true;
+ console.log(`[CRAFTING] Recipe unlocked: ${recipe.name}`);
+ }
+ }
+ }
+ }
+
+ getRecipesByCategory(category) {
+ return Array.from(this.recipes.values())
+ .filter(recipe => recipe.category === category);
+ }
+
+ canCraftRecipe(recipeId) {
+ const recipe = this.recipes.get(recipeId);
+ if (!recipe) return false;
+
+ // Check skill requirements
+ if (recipe.requirements) {
+ const skillSystem = this.game.systems.skillSystem;
+ if (!skillSystem) return false;
+
+ for (const [skillName, requiredLevel] of Object.entries(recipe.requirements)) {
+ const skillLevel = skillSystem.getSkillLevel(skillName);
+ if (skillLevel < requiredLevel) {
+ return false;
+ }
+ }
+ }
+
+ // Check materials
+ if (recipe.materials) {
+ for (const material of recipe.materials) {
+ const inventory = this.game.systems.inventory;
+ if (!inventory || !inventory.hasItem(material.id, material.quantity)) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ getMissingMaterials(recipeId) {
+ const recipe = this.recipes.get(recipeId);
+ if (!recipe || !recipe.materials) return [];
+
+ const missing = [];
+ const inventory = this.game.systems.inventory;
+
+ console.log(`[CRAFTING DEBUG] Checking materials for recipe: ${recipe.name}`);
+ console.log(`[CRAFTING DEBUG] Inventory system:`, inventory);
+
+ for (const material of recipe.materials) {
+ let currentCount = 0;
+
+ // Safely get current material count
+ if (inventory && typeof inventory.getItemCount === 'function') {
+ try {
+ currentCount = inventory.getItemCount(material.id);
+ // Ensure we have a valid number
+ currentCount = typeof currentCount === 'number' && !isNaN(currentCount) ? currentCount : 0;
+ } catch (error) {
+ console.log(`[CRAFTING DEBUG] Error getting count for ${material.id}:`, error);
+ currentCount = 0;
+ }
+ console.log(`[CRAFTING DEBUG] Material ${material.id}: current=${currentCount}, required=${material.quantity}`);
+ } else {
+ console.log(`[CRAFTING DEBUG] Inventory or getItemCount not available for ${material.id}`);
+ currentCount = 0;
+ }
+
+ // Ensure required quantity is also a valid number
+ const requiredQuantity = typeof material.quantity === 'number' && !isNaN(material.quantity) ? material.quantity : 0;
+
+ // Check if we have enough materials
+ if (currentCount < requiredQuantity) {
+ missing.push({
+ id: material.id,
+ required: requiredQuantity,
+ current: currentCount,
+ missing: Math.max(0, requiredQuantity - currentCount)
+ });
+ }
+ }
+
+ console.log(`[CRAFTING DEBUG] Missing materials:`, missing);
+ return missing;
+ }
+
+ async craftRecipe(recipeId) {
+ const recipe = this.recipes.get(recipeId);
+ if (!recipe) {
+ console.error(`[CRAFTING] Recipe not found: ${recipeId}`);
+ return false;
+ }
+
+ if (!this.canCraftRecipe(recipeId)) {
+ console.log(`[CRAFTING] Cannot craft recipe: ${recipe.name}`);
+ return false;
+ }
+
+ console.log(`[CRAFTING] Starting to craft: ${recipe.name}`);
+
+ // Remove materials
+ if (recipe.materials) {
+ for (const material of recipe.materials) {
+ this.game.systems.inventory.removeItem(material.id, material.quantity);
+ }
+ }
+
+ // Add crafting experience
+ if (recipe.experience) {
+ this.game.systems.skillSystem.awardCraftingExperience(recipe.experience);
+ }
+
+ // Wait for crafting time
+ await new Promise(resolve => setTimeout(resolve, recipe.craftingTime));
+
+ // Add results to inventory
+ if (recipe.results) {
+ for (const result of recipe.results) {
+ this.game.systems.inventory.addItem(result.id, result.quantity);
+ }
+ }
+
+ // Update quest progress
+ if (this.game.systems.questSystem) {
+ this.game.systems.questSystem.onItemCrafted();
+ }
+
+ console.log(`[CRAFTING] Successfully crafted: ${recipe.name}`);
+ return true;
+ }
+
+ selectRecipe(recipeId) {
+ this.selectedRecipe = this.recipes.get(recipeId);
+ return this.selectedRecipe;
+ }
+
+ getSelectedRecipe() {
+ return this.selectedRecipe;
+ }
+
+ updateUI() {
+ this.updateRecipeList();
+ this.updateCraftingDetails();
+ this.updateCraftingInfo();
+ }
+
+ updateRecipeList() {
+ const recipeListElement = document.getElementById('recipeList');
+ if (!recipeListElement) return;
+
+ const recipes = this.getRecipesByCategory(this.currentCategory);
+
+ recipeListElement.innerHTML = '';
+
+ if (recipes.length === 0) {
+ recipeListElement.innerHTML = 'No recipes available in this category
';
+ return;
+ }
+
+ recipes.forEach(recipe => {
+ const recipeElement = document.createElement('div');
+ recipeElement.className = 'recipe-item';
+ recipeElement.dataset.recipeId = recipe.id;
+
+ const canCraft = this.canCraftRecipe(recipe.id);
+ const missingMaterials = this.getMissingMaterials(recipe.id);
+
+ // Check if recipe is unlocked (skill requirements met)
+ const skillSystem = this.game.systems.skillSystem;
+ let skillRequirementsMet = true;
+ if (recipe.requirements && skillSystem) {
+ for (const [skillName, requiredLevel] of Object.entries(recipe.requirements)) {
+ const skillLevel = skillSystem.getSkillLevel(skillName);
+ if (skillLevel < requiredLevel) {
+ skillRequirementsMet = false;
+ break;
+ }
+ }
+ }
+
+ // Apply styling based on status
+ if (!skillRequirementsMet) {
+ recipeElement.classList.add('locked');
+ } else if (!canCraft) {
+ recipeElement.classList.add('missing-materials');
+ } else {
+ recipeElement.classList.add('can-craft');
+ }
+
+ // Generate requirements text
+ const requirementsText = recipe.requirements ?
+ Object.entries(recipe.requirements).map(([skill, level]) => `${skill}: ${level}`).join(', ') : 'None';
+
+ // Generate materials with missing status
+ const materialsHtml = recipe.materials ? recipe.materials.map(mat => {
+ const missing = missingMaterials.find(m => m.id === mat.id);
+ const currentCount = missing ? missing.current : 0;
+ const requiredCount = mat.quantity || 0;
+
+ if (missing) {
+ return `
+ ${mat.id}
+ ${currentCount}/${requiredCount}
+
`;
+ } else {
+ return `
+ ${mat.id}
+ ${currentCount}/${requiredCount}
+
`;
+ }
+ }).join('') : '';
+
+ recipeElement.innerHTML = `
+
+ ${recipe.description}
+
+ ${materialsHtml}
+
+ ${missingMaterials.length > 0 ? `
+
+
+ Missing: ${missingMaterials.map(m => `${m.missing}x ${m.id}`).join(', ')}
+
+ ` : ''}
+
+
+ ${recipe.craftingTime / 1000}s
+
+ `;
+
+ recipeElement.addEventListener('click', () => {
+ this.selectRecipe(recipe.id);
+ this.updateCraftingDetails();
+ });
+
+ recipeListElement.appendChild(recipeElement);
+ });
+ }
+
+ updateCraftingDetails() {
+ const detailsElement = document.getElementById('craftingDetails');
+ if (!detailsElement) return;
+
+ if (!this.selectedRecipe) {
+ detailsElement.innerHTML = `
+
+
Select a Recipe
+
Choose a recipe from the list to see details and craft items.
+
+ `;
+ return;
+ }
+
+ const recipe = this.selectedRecipe;
+ const canCraft = this.canCraftRecipe(recipe.id);
+
+ detailsElement.innerHTML = `
+
+
${recipe.name}
+
${recipe.description}
+
+
+
Requirements:
+ ${recipe.requirements ? Object.entries(recipe.requirements).map(([skill, level]) =>
+ `
+ ${skill}
+ Level ${level}
+
`
+ ).join('') : '
No special requirements
'}
+
+
+
+
Materials Needed:
+ ${recipe.materials ? recipe.materials.map(mat =>
+ `
+ ${mat.id}
+ x${mat.quantity}
+ Have: ${this.game.systems.inventory?.getItemCount(mat.id) || 0}
+
`
+ ).join('') : '
No materials needed
'}
+
+
+
+
Results:
+ ${recipe.results ? recipe.results.map(result =>
+ `
+ ${result.id}
+ x${result.quantity}
+
`
+ ).join('') : ''}
+
+
+
+
+
+ ${recipe.experience} XP
+
+
+
+ ${recipe.craftingTime / 1000} seconds
+
+
+
+
+ ${canCraft ? 'Craft Item' : 'Cannot Craft'}
+
+
+ `;
+ }
+
+ updateCraftingInfo() {
+ const skillSystem = this.game.systems.skillSystem;
+ if (!skillSystem) return;
+
+ const craftingLevel = skillSystem.getSkillLevel('crafting');
+ const craftingExp = skillSystem.getSkillExperience('crafting');
+ const expNeeded = skillSystem.getExperienceNeeded('crafting');
+
+ const levelElement = document.getElementById('craftingLevel');
+ const expElement = document.getElementById('craftingExp');
+
+ if (levelElement) levelElement.textContent = craftingLevel;
+ if (expElement) expElement.textContent = `${craftingExp}/${expNeeded}`;
+ }
+
+ switchCategory(category) {
+ this.currentCategory = category;
+ this.selectedRecipe = null;
+ this.updateUI();
+ }
+}
+
+// Export for use in GameEngine
+if (typeof module !== 'undefined' && module.exports) {
+ module.exports = CraftingSystem;
+}
diff --git a/Client/js/systems/DungeonSystem.js b/Client/js/systems/DungeonSystem.js
new file mode 100644
index 0000000..2bce1dc
--- /dev/null
+++ b/Client/js/systems/DungeonSystem.js
@@ -0,0 +1,1977 @@
+/**
+ * Galaxy Strike Online - Dungeon System
+ * Manages procedural dungeon generation and exploration
+ */
+
+class DungeonSystem {
+ constructor(gameEngine) {
+ this.game = gameEngine;
+
+ // Dungeon configuration
+ this.dungeonTypes = {
+ tutorial: {
+ name: 'Tutorial Dungeon',
+ description: 'Learn the basics of dungeon exploration in this guided tutorial',
+ difficulty: 'tutorial',
+ enemyTypes: ['training_drone', 'practice_target'],
+ rewardMultiplier: 0.5,
+ oneTimeOnly: true,
+ healthType: 'player', // Ground mission
+ energyCost: 0
+ },
+ alien_ruins: {
+ name: 'Alien Ruins',
+ description: 'Ancient alien structures filled with mysterious technology',
+ difficulty: 'medium',
+ enemyTypes: ['alien_guardian', 'ancient_drone', 'crystal_golem'],
+ rewardMultiplier: 1.2,
+ healthType: 'player', // Ground mission
+ energyCost: 20
+ },
+ pirate_lair: {
+ name: 'Pirate Lair',
+ description: 'Dangerous pirate hideouts with valuable loot',
+ difficulty: 'easy',
+ enemyTypes: ['space_pirate', 'pirate_captain', 'defense_turret'],
+ rewardMultiplier: 1.0,
+ healthType: 'ship', // Space mission
+ energyCost: 15
+ },
+ corrupted_vault: {
+ name: 'Corrupted AI Vault',
+ description: ' malfunctioning AI facilities with corrupted security',
+ difficulty: 'hard',
+ enemyTypes: ['security_drone', 'corrupted_ai', 'virus_program'],
+ rewardMultiplier: 1.5,
+ healthType: 'ship', // Space mission
+ energyCost: 25
+ },
+ asteroid_mine: {
+ name: 'Asteroid Mine',
+ description: 'Abandoned mining facilities in asteroid fields',
+ difficulty: 'easy',
+ enemyTypes: ['mining_drone', 'rock_creature', 'explosive_asteroid'],
+ rewardMultiplier: 0.8,
+ healthType: 'ship', // Space mission
+ energyCost: 10
+ },
+ nebula_anomaly: {
+ name: 'Nebula Anomaly',
+ description: 'Strange energy anomalies in deep space',
+ difficulty: 'extreme',
+ enemyTypes: ['energy_being', 'phase_shifter', 'quantum_entity'],
+ rewardMultiplier: 2.0,
+ healthType: 'ship', // Space mission
+ energyCost: 30
+ },
+
+ // NEW DUNGEONS - Space Theme
+ space_station: {
+ name: 'Abandoned Space Station',
+ description: 'A derelict space station floating in the void',
+ difficulty: 'medium',
+ enemyTypes: ['maintenance_drone', 'security_android', 'station_ai'],
+ rewardMultiplier: 1.3,
+ healthType: 'player',
+ energyCost: 22
+ },
+ comet_core: {
+ name: 'Comet Core',
+ description: 'The frozen heart of a passing comet',
+ difficulty: 'hard',
+ enemyTypes: ['ice_elemental', 'frost_wyrm', 'crystal_guardian'],
+ rewardMultiplier: 1.6,
+ healthType: 'ship',
+ energyCost: 28
+ },
+ black_hole_perimeter: {
+ name: 'Black Hole Perimeter',
+ description: 'Dangerous space near a black hole event horizon',
+ difficulty: 'extreme',
+ enemyTypes: ['gravity_wraith', 'void_stalker', 'singularity_spawn'],
+ rewardMultiplier: 2.5,
+ healthType: 'ship',
+ energyCost: 35
+ },
+ star_forge: {
+ name: 'Star Forge',
+ description: 'Ancient facility that harnesses stellar energy',
+ difficulty: 'hard',
+ enemyTypes: ['plasma_elemental', 'solar_guardian', 'fusion_core'],
+ rewardMultiplier: 1.8,
+ healthType: 'ship',
+ energyCost: 30
+ },
+ debris_field: {
+ name: 'Ship Debris Field',
+ description: 'Graveyard of destroyed spacecraft',
+ difficulty: 'easy',
+ enemyTypes: ['scrap_golem', 'hull_breacher', 'salage_drone'],
+ rewardMultiplier: 0.9,
+ healthType: 'ship',
+ energyCost: 12
+ },
+
+ // NEW DUNGEONS - Planet Theme
+ jungle_temple: {
+ name: 'Jungle Temple',
+ description: 'Overgrown temple hidden in dense alien jungle',
+ difficulty: 'medium',
+ enemyTypes: ['plant_beast', 'tribal_warrior', 'jungle_spirit'],
+ rewardMultiplier: 1.4,
+ healthType: 'player',
+ energyCost: 25
+ },
+ desert_pyramid: {
+ name: 'Desert Pyramid',
+ description: 'Ancient pyramid buried under endless sand dunes',
+ difficulty: 'hard',
+ enemyTypes: ['sand_worm', 'mummy_guardian', 'heat_elemental'],
+ rewardMultiplier: 1.7,
+ healthType: 'player',
+ energyCost: 28
+ },
+ volcanic_caverns: {
+ name: 'Volcanic Caverns',
+ description: 'Molten caverns deep within an active volcano',
+ difficulty: 'hard',
+ enemyTypes: ['lava_elemental', 'fire_demon', 'magma_beast'],
+ rewardMultiplier: 1.6,
+ healthType: 'player',
+ energyCost: 26
+ },
+ arctic_research: {
+ name: 'Arctic Research Base',
+ description: 'Frozen research facility with failed experiments',
+ difficulty: 'medium',
+ enemyTypes: ['cryo_mutant', 'frost_android', 'ice_wraith'],
+ rewardMultiplier: 1.5,
+ healthType: 'player',
+ energyCost: 24
+ },
+ swamp_lair: {
+ name: 'Swamp Lair',
+ description: 'Murky swamp inhabited by strange creatures',
+ difficulty: 'easy',
+ enemyTypes: ['swamp_beast', 'toxic_spitter', 'mud_golem'],
+ rewardMultiplier: 1.1,
+ healthType: 'player',
+ energyCost: 18
+ },
+
+ // NEW DUNGEONS - Technology Theme
+ cyber_realm: {
+ name: 'Cyber Realm',
+ description: 'Virtual reality space corrupted by malware',
+ difficulty: 'hard',
+ enemyTypes: ['glitch_wraith', 'firewall_guardian', 'data_vampire'],
+ rewardMultiplier: 1.9,
+ healthType: 'player',
+ energyCost: 32
+ },
+ robot_factory: {
+ name: 'Robot Factory',
+ description: 'Automated factory producing hostile machines',
+ difficulty: 'medium',
+ enemyTypes: ['assembly_drone', 'welder_bot', 'factory_overseer'],
+ rewardMultiplier: 1.4,
+ healthType: 'player',
+ energyCost: 23
+ },
+ quantum_lab: {
+ name: 'Quantum Laboratory',
+ description: 'Research facility experimenting with quantum physics',
+ difficulty: 'extreme',
+ enemyTypes: ['quantum_phantom', 'particle_accelerator', 'reality_bender'],
+ rewardMultiplier: 2.3,
+ healthType: 'player',
+ energyCost: 38
+ },
+ server_farm: {
+ name: 'Server Farm',
+ description: 'Massive data center with rogue security programs',
+ difficulty: 'medium',
+ enemyTypes: ['sentinel_program', 'data_corruptor', 'system_guardian'],
+ rewardMultiplier: 1.3,
+ healthType: 'player',
+ energyCost: 21
+ },
+
+ // NEW DUNGEONS - Biome/Elemental Theme
+ crystal_caves: {
+ name: 'Crystal Caves',
+ description: 'Caves filled with energy-infused crystals',
+ difficulty: 'medium',
+ enemyTypes: ['crystal_golem', 'shard_elemental', 'resonance_beast'],
+ rewardMultiplier: 1.4,
+ healthType: 'player',
+ energyCost: 22
+ },
+ toxic_wastes: {
+ name: 'Toxic Wastes',
+ description: 'Polluted wasteland filled with mutated creatures',
+ difficulty: 'hard',
+ enemyTypes: ['mutant_horror', 'toxic_slime', 'radiation_beast'],
+ rewardMultiplier: 1.7,
+ healthType: 'player',
+ energyCost: 27
+ },
+ shadow_realm: {
+ name: 'Shadow Realm',
+ description: 'Dark dimension inhabited by shadow creatures',
+ difficulty: 'extreme',
+ enemyTypes: ['shadow_demon', 'nightmare_stalker', 'void_walker'],
+ rewardMultiplier: 2.2,
+ healthType: 'player',
+ energyCost: 36
+ },
+ time_anomaly: {
+ name: 'Time Anomaly',
+ description: 'Area where time flows unpredictably',
+ difficulty: 'extreme',
+ enemyTypes: ['temporal_paradox', 'future_soldier', 'past_guardian'],
+ rewardMultiplier: 2.4,
+ healthType: 'player',
+ energyCost: 40
+ },
+
+ // NEW DUNGEONS - Military/War Theme
+ war_zone: {
+ name: 'Active War Zone',
+ description: 'Battlefield with ongoing combat operations',
+ difficulty: 'hard',
+ enemyTypes: ['enemy_soldier', 'combat_drone', 'field_commander'],
+ rewardMultiplier: 1.8,
+ healthType: 'player',
+ energyCost: 29
+ },
+ military_base: {
+ name: 'Abandoned Military Base',
+ description: 'Former military installation with automated defenses',
+ difficulty: 'medium',
+ enemyTypes: ['turret_system', 'combat_android', 'base_commander'],
+ rewardMultiplier: 1.5,
+ healthType: 'player',
+ energyCost: 24
+ },
+ weapons_testing: {
+ name: 'Weapons Testing Facility',
+ description: 'Secret facility testing advanced weaponry',
+ difficulty: 'hard',
+ enemyTypes: ['weapon_drone', 'test_subject', 'chief_scientist'],
+ rewardMultiplier: 1.9,
+ healthType: 'player',
+ energyCost: 31
+ },
+
+ // NEW DUNGEONS - Special/Unique Theme
+ dream_scape: {
+ name: 'Dream Scape',
+ description: 'Surreal landscape shaped by collective dreams',
+ difficulty: 'medium',
+ enemyTypes: ['nightmare_creature', 'dream_guardian', 'subconscious_demon'],
+ rewardMultiplier: 1.6,
+ healthType: 'player',
+ energyCost: 26
+ },
+ memory_palace: {
+ name: 'Memory Palace',
+ description: 'Mental realm storing forgotten memories',
+ difficulty: 'hard',
+ enemyTypes: ['memory_fragment', 'forgetfulness_demon', 'nostalgia_spirit'],
+ rewardMultiplier: 1.7,
+ healthType: 'player',
+ energyCost: 28
+ },
+ dimension_rift: {
+ name: 'Dimension Rift',
+ description: 'Tear between dimensions with interdimensional invaders',
+ difficulty: 'extreme',
+ enemyTypes: ['rift_demon', 'dimensional_hunter', 'reality_tear'],
+ rewardMultiplier: 2.6,
+ healthType: 'player',
+ energyCost: 42
+ }
+ };
+
+ // Current dungeon state
+ this.currentDungeon = null;
+ this.currentRoom = null;
+ this.dungeonProgress = 0;
+ this.isExploring = false;
+
+ // Dungeon templates
+ this.roomTypes = {
+ entrance: { name: 'Entrance', enemies: 0, rewards: false },
+ corridor: { name: 'Corridor', enemies: 1, rewards: false },
+ chamber: { name: 'Chamber', enemies: 2, rewards: true },
+ treasure: { name: 'Treasure Room', enemies: 0, rewards: true },
+ boss: { name: 'Boss Room', enemies: 1, rewards: true, isBoss: true },
+ exit: { name: 'Exit', enemies: 0, rewards: false }
+ };
+
+ // Enemy templates
+ this.enemyTemplates = {
+ // Original enemies
+ training_drone: {
+ name: 'Training Drone',
+ health: 10,
+ attack: 5,
+ defense: 2,
+ experience: 5,
+ credits: 3
+ },
+ practice_target: {
+ name: 'Practice Target',
+ health: 5,
+ attack: 0,
+ defense: 0,
+ experience: 2,
+ credits: 1
+ },
+ alien_guardian: {
+ name: 'Alien Guardian',
+ health: 50,
+ attack: 8,
+ defense: 5,
+ experience: 25,
+ credits: 15
+ },
+ ancient_drone: {
+ name: 'Ancient Drone',
+ health: 30,
+ attack: 12,
+ defense: 2,
+ experience: 20,
+ credits: 10
+ },
+ crystal_golem: {
+ name: 'Crystal Golem',
+ health: 80,
+ attack: 6,
+ defense: 10,
+ experience: 35,
+ credits: 25
+ },
+ space_pirate: {
+ name: 'Space Pirate',
+ health: 25,
+ attack: 10,
+ defense: 3,
+ experience: 15,
+ credits: 12
+ },
+ pirate_captain: {
+ name: 'Pirate Captain',
+ health: 40,
+ attack: 15,
+ defense: 6,
+ experience: 30,
+ credits: 20
+ },
+ defense_turret: {
+ name: 'Defense Turret',
+ health: 20,
+ attack: 18,
+ defense: 8,
+ experience: 18,
+ credits: 8
+ },
+ security_drone: {
+ name: 'Security Drone',
+ health: 35,
+ attack: 14,
+ defense: 4,
+ experience: 22,
+ credits: 15
+ },
+ corrupted_ai: {
+ name: 'Corrupted AI',
+ health: 60,
+ attack: 20,
+ defense: 2,
+ experience: 40,
+ credits: 30
+ },
+ virus_program: {
+ name: 'Virus Program',
+ health: 15,
+ attack: 25,
+ defense: 1,
+ experience: 20,
+ credits: 12
+ },
+ mining_drone: {
+ name: 'Mining Drone',
+ health: 20,
+ attack: 8,
+ defense: 3,
+ experience: 12,
+ credits: 8
+ },
+ rock_creature: {
+ name: 'Rock Creature',
+ health: 45,
+ attack: 6,
+ defense: 12,
+ experience: 25,
+ credits: 15
+ },
+ explosive_asteroid: {
+ name: 'Explosive Asteroid',
+ health: 10,
+ attack: 30,
+ defense: 0,
+ experience: 15,
+ credits: 5
+ },
+ energy_being: {
+ name: 'Energy Being',
+ health: 55,
+ attack: 22,
+ defense: 3,
+ experience: 45,
+ credits: 35
+ },
+ phase_shifter: {
+ name: 'Phase Shifter',
+ health: 30,
+ attack: 28,
+ defense: 1,
+ experience: 35,
+ credits: 25
+ },
+ quantum_entity: {
+ name: 'Quantum Entity',
+ health: 70,
+ attack: 35,
+ defense: 5,
+ experience: 60,
+ credits: 50
+ },
+
+ // NEW ENEMIES - Space Theme
+ maintenance_drone: {
+ name: 'Maintenance Drone',
+ health: 28,
+ attack: 11,
+ defense: 4,
+ experience: 18,
+ credits: 12
+ },
+ security_android: {
+ name: 'Security Android',
+ health: 42,
+ attack: 16,
+ defense: 7,
+ experience: 28,
+ credits: 20
+ },
+ station_ai: {
+ name: 'Station AI',
+ health: 65,
+ attack: 24,
+ defense: 3,
+ experience: 45,
+ credits: 32
+ },
+ ice_elemental: {
+ name: 'Ice Elemental',
+ health: 38,
+ attack: 18,
+ defense: 8,
+ experience: 32,
+ credits: 24
+ },
+ frost_wyrm: {
+ name: 'Frost Wyrm',
+ health: 72,
+ attack: 26,
+ defense: 6,
+ experience: 52,
+ credits: 38
+ },
+ gravity_wraith: {
+ name: 'Gravity Wraith',
+ health: 85,
+ attack: 32,
+ defense: 4,
+ experience: 68,
+ credits: 48
+ },
+ void_stalker: {
+ name: 'Void Stalker',
+ health: 78,
+ attack: 38,
+ defense: 5,
+ experience: 72,
+ credits: 52
+ },
+ singularity_spawn: {
+ name: 'Singularity Spawn',
+ health: 95,
+ attack: 42,
+ defense: 8,
+ experience: 85,
+ credits: 65
+ },
+ plasma_elemental: {
+ name: 'Plasma Elemental',
+ health: 58,
+ attack: 28,
+ defense: 4,
+ experience: 48,
+ credits: 35
+ },
+ solar_guardian: {
+ name: 'Solar Guardian',
+ health: 82,
+ attack: 34,
+ defense: 7,
+ experience: 65,
+ credits: 48
+ },
+ fusion_core: {
+ name: 'Fusion Core',
+ health: 68,
+ attack: 30,
+ defense: 12,
+ experience: 58,
+ credits: 42
+ },
+ scrap_golem: {
+ name: 'Scrap Golem',
+ health: 35,
+ attack: 14,
+ defense: 9,
+ experience: 22,
+ credits: 16
+ },
+ hull_breacher: {
+ name: 'Hull Breacher',
+ health: 32,
+ attack: 20,
+ defense: 3,
+ experience: 26,
+ credits: 18
+ },
+ salage_drone: {
+ name: 'Salvage Drone',
+ health: 22,
+ attack: 12,
+ defense: 5,
+ experience: 16,
+ credits: 11
+ },
+
+ // NEW ENEMIES - Planet Theme
+ plant_beast: {
+ name: 'Plant Beast',
+ health: 48,
+ attack: 15,
+ defense: 8,
+ experience: 35,
+ credits: 26
+ },
+ tribal_warrior: {
+ name: 'Tribal Warrior',
+ health: 38,
+ attack: 18,
+ defense: 6,
+ experience: 28,
+ credits: 20
+ },
+ jungle_spirit: {
+ name: 'Jungle Spirit',
+ health: 55,
+ attack: 22,
+ defense: 4,
+ experience: 42,
+ credits: 30
+ },
+ sand_worm: {
+ name: 'Sand Worm',
+ health: 75,
+ attack: 28,
+ defense: 9,
+ experience: 58,
+ credits: 42
+ },
+ mummy_guardian: {
+ name: 'Mummy Guardian',
+ health: 62,
+ attack: 24,
+ defense: 7,
+ experience: 48,
+ credits: 35
+ },
+ heat_elemental: {
+ name: 'Heat Elemental',
+ health: 52,
+ attack: 26,
+ defense: 3,
+ experience: 45,
+ credits: 32
+ },
+ lava_elemental: {
+ name: 'Lava Elemental',
+ health: 68,
+ attack: 30,
+ defense: 5,
+ experience: 55,
+ credits: 40
+ },
+ fire_demon: {
+ name: 'Fire Demon',
+ health: 72,
+ attack: 32,
+ defense: 6,
+ experience: 62,
+ credits: 45
+ },
+ magma_beast: {
+ name: 'Magma Beast',
+ health: 85,
+ attack: 28,
+ defense: 12,
+ experience: 68,
+ credits: 50
+ },
+ cryo_mutant: {
+ name: 'Cryo Mutant',
+ health: 45,
+ attack: 20,
+ defense: 7,
+ experience: 38,
+ credits: 28
+ },
+ frost_android: {
+ name: 'Frost Android',
+ health: 52,
+ attack: 22,
+ defense: 8,
+ experience: 42,
+ credits: 30
+ },
+ ice_wraith: {
+ name: 'Ice Wraith',
+ health: 58,
+ attack: 25,
+ defense: 4,
+ experience: 48,
+ credits: 35
+ },
+ swamp_beast: {
+ name: 'Swamp Beast',
+ health: 35,
+ attack: 16,
+ defense: 9,
+ experience: 24,
+ credits: 18
+ },
+ toxic_spitter: {
+ name: 'Toxic Spitter',
+ health: 28,
+ attack: 19,
+ defense: 3,
+ experience: 22,
+ credits: 16
+ },
+ mud_golem: {
+ name: 'Mud Golem',
+ health: 42,
+ attack: 12,
+ defense: 11,
+ experience: 26,
+ credits: 19
+ },
+
+ // NEW ENEMIES - Technology Theme
+ glitch_wraith: {
+ name: 'Glitch Wraith',
+ health: 48,
+ attack: 26,
+ defense: 2,
+ experience: 45,
+ credits: 32
+ },
+ firewall_guardian: {
+ name: 'Firewall Guardian',
+ health: 65,
+ attack: 22,
+ defense: 8,
+ experience: 52,
+ credits: 38
+ },
+ data_vampire: {
+ name: 'Data Vampire',
+ health: 38,
+ attack: 30,
+ defense: 3,
+ experience: 35,
+ credits: 26
+ },
+ assembly_drone: {
+ name: 'Assembly Drone',
+ health: 32,
+ attack: 15,
+ defense: 6,
+ experience: 24,
+ credits: 17
+ },
+ welder_bot: {
+ name: 'Welder Bot',
+ health: 28,
+ attack: 20,
+ defense: 4,
+ experience: 22,
+ credits: 15
+ },
+ factory_overseer: {
+ name: 'Factory Overseer',
+ health: 58,
+ attack: 24,
+ defense: 7,
+ experience: 46,
+ credits: 33
+ },
+ quantum_phantom: {
+ name: 'Quantum Phantom',
+ health: 78,
+ attack: 35,
+ defense: 4,
+ experience: 68,
+ credits: 50
+ },
+ particle_accelerator: {
+ name: 'Particle Accelerator',
+ health: 92,
+ attack: 40,
+ defense: 6,
+ experience: 85,
+ credits: 62
+ },
+ reality_bender: {
+ name: 'Reality Bender',
+ health: 88,
+ attack: 45,
+ defense: 3,
+ experience: 92,
+ credits: 68
+ },
+ sentinel_program: {
+ name: 'Sentinel Program',
+ health: 42,
+ attack: 21,
+ defense: 8,
+ experience: 32,
+ credits: 24
+ },
+ data_corruptor: {
+ name: 'Data Corruptor',
+ health: 35,
+ attack: 25,
+ defense: 3,
+ experience: 28,
+ credits: 20
+ },
+ system_guardian: {
+ name: 'System Guardian',
+ health: 55,
+ attack: 23,
+ defense: 9,
+ experience: 42,
+ credits: 30
+ },
+
+ // NEW ENEMIES - Biome/Elemental Theme
+ shard_elemental: {
+ name: 'Shard Elemental',
+ health: 45,
+ attack: 19,
+ defense: 10,
+ experience: 38,
+ credits: 28
+ },
+ resonance_beast: {
+ name: 'Resonance Beast',
+ health: 52,
+ attack: 22,
+ defense: 6,
+ experience: 42,
+ credits: 30
+ },
+ mutant_horror: {
+ name: 'Mutant Horror',
+ health: 68,
+ attack: 28,
+ defense: 5,
+ experience: 58,
+ credits: 42
+ },
+ toxic_slime: {
+ name: 'Toxic Slime',
+ health: 42,
+ attack: 18,
+ defense: 8,
+ experience: 32,
+ credits: 24
+ },
+ radiation_beast: {
+ name: 'Radiation Beast',
+ health: 75,
+ attack: 30,
+ defense: 4,
+ experience: 65,
+ credits: 48
+ },
+ shadow_demon: {
+ name: 'Shadow Demon',
+ health: 72,
+ attack: 34,
+ defense: 3,
+ experience: 68,
+ credits: 50
+ },
+ nightmare_stalker: {
+ name: 'Nightmare Stalker',
+ health: 85,
+ attack: 38,
+ defense: 5,
+ experience: 78,
+ credits: 58
+ },
+ void_walker: {
+ name: 'Void Walker',
+ health: 92,
+ attack: 42,
+ defense: 7,
+ experience: 88,
+ credits: 65
+ },
+ temporal_paradox: {
+ name: 'Temporal Paradox',
+ health: 78,
+ attack: 40,
+ defense: 4,
+ experience: 75,
+ credits: 55
+ },
+ future_soldier: {
+ name: 'Future Soldier',
+ health: 65,
+ attack: 32,
+ defense: 9,
+ experience: 58,
+ credits: 42
+ },
+ past_guardian: {
+ name: 'Past Guardian',
+ health: 70,
+ attack: 28,
+ defense: 12,
+ experience: 62,
+ credits: 45
+ },
+
+ // NEW ENEMIES - Military/War Theme
+ enemy_soldier: {
+ name: 'Enemy Soldier',
+ health: 45,
+ attack: 20,
+ defense: 7,
+ experience: 35,
+ credits: 26
+ },
+ combat_drone: {
+ name: 'Combat Drone',
+ health: 38,
+ attack: 22,
+ defense: 5,
+ experience: 30,
+ credits: 22
+ },
+ field_commander: {
+ name: 'Field Commander',
+ health: 62,
+ attack: 28,
+ defense: 9,
+ experience: 52,
+ credits: 38
+ },
+ turret_system: {
+ name: 'Turret System',
+ health: 48,
+ attack: 26,
+ defense: 10,
+ experience: 38,
+ credits: 28
+ },
+ combat_android: {
+ name: 'Combat Android',
+ health: 55,
+ attack: 24,
+ defense: 8,
+ experience: 45,
+ credits: 33
+ },
+ base_commander: {
+ name: 'Base Commander',
+ health: 72,
+ attack: 30,
+ defense: 11,
+ experience: 62,
+ credits: 45
+ },
+ weapon_drone: {
+ name: 'Weapon Drone',
+ health: 42,
+ attack: 28,
+ defense: 4,
+ experience: 38,
+ credits: 28
+ },
+ test_subject: {
+ name: 'Test Subject',
+ health: 58,
+ attack: 25,
+ defense: 6,
+ experience: 48,
+ credits: 35
+ },
+ chief_scientist: {
+ name: 'Chief Scientist',
+ health: 35,
+ attack: 32,
+ defense: 3,
+ experience: 42,
+ credits: 30
+ },
+
+ // NEW ENEMIES - Special/Unique Theme
+ nightmare_creature: {
+ name: 'Nightmare Creature',
+ health: 62,
+ attack: 28,
+ defense: 5,
+ experience: 55,
+ credits: 40
+ },
+ dream_guardian: {
+ name: 'Dream Guardian',
+ health: 68,
+ attack: 30,
+ defense: 8,
+ experience: 58,
+ credits: 42
+ },
+ subconscious_demon: {
+ name: 'Subconscious Demon',
+ health: 75,
+ attack: 34,
+ defense: 4,
+ experience: 68,
+ credits: 50
+ },
+ memory_fragment: {
+ name: 'Memory Fragment',
+ health: 48,
+ attack: 26,
+ defense: 6,
+ experience: 45,
+ credits: 33
+ },
+ forgetfulness_demon: {
+ name: 'Forgetfulness Demon',
+ health: 55,
+ attack: 30,
+ defense: 3,
+ experience: 48,
+ credits: 35
+ },
+ nostalgia_spirit: {
+ name: 'Nostalgia Spirit',
+ health: 52,
+ attack: 24,
+ defense: 9,
+ experience: 42,
+ credits: 30
+ },
+ rift_demon: {
+ name: 'Rift Demon',
+ health: 88,
+ attack: 44,
+ defense: 5,
+ experience: 92,
+ credits: 68
+ },
+ dimensional_hunter: {
+ name: 'Dimensional Hunter',
+ health: 95,
+ attack: 48,
+ defense: 8,
+ experience: 105,
+ credits: 78
+ },
+ reality_tear: {
+ name: 'Reality Tear',
+ health: 102,
+ attack: 52,
+ defense: 3,
+ experience: 115,
+ credits: 85
+ }
+ };
+
+ // Statistics
+ this.stats = {
+ dungeonsAttempted: 0,
+ dungeonsCompleted: 0,
+ totalEnemiesDefeated: 0,
+ bestTime: Infinity,
+ totalLootEarned: 0
+ };
+ }
+
+ async initialize() {
+ this.generateDungeonList();
+ }
+
+ generateDungeonList() {
+
+ const dungeonListElement = document.getElementById('dungeonList');
+ if (!dungeonListElement) {
+ console.error('Dungeon list element not found!');
+ return;
+ }
+
+ // Clear existing list
+ dungeonListElement.innerHTML = '';
+
+ const questSystem = this.game.systems.questSystem;
+ const firstStepsQuest = questSystem ? questSystem.findQuest('tutorial_complete') : null;
+ const showTutorialDungeon = firstStepsQuest && firstStepsQuest.status === 'active';
+
+ Object.entries(this.dungeonTypes).forEach(([key, dungeon]) => {
+ // Skip tutorial dungeon unless First Steps quest is active
+ if (key === 'tutorial' && !showTutorialDungeon) {
+ return;
+ }
+
+ const dungeonElement = document.createElement('div');
+ dungeonElement.className = 'dungeon-item';
+ dungeonElement.dataset.dungeonType = key;
+
+ // Check if tutorial dungeon is completed
+ const isCompleted = key === 'tutorial' && this.game.systems.player.stats.tutorialDungeonCompleted;
+ const statusClass = isCompleted ? 'completed' : '';
+ const statusText = isCompleted ? 'COMPLETED' : '';
+
+ dungeonElement.innerHTML = `
+ ${dungeon.name}
+ ${dungeon.difficulty.toUpperCase()}
+ ${statusText ? `${statusText}
` : ''}
+ ${dungeon.description}
+ Rewards: ${dungeon.rewardMultiplier}x
+ Energy Cost: ${dungeon.energyCost || this.getEnergyCost(key)}
+ `;
+
+ dungeonElement.addEventListener('click', () => {
+ if (isCompleted) {
+ this.game.showNotification('Tutorial dungeon has already been completed!', 'warning', 3000);
+ } else {
+ this.selectDungeon(key);
+ }
+ });
+
+ dungeonListElement.appendChild(dungeonElement);
+ });
+
+ }
+
+ selectDungeon(type) {
+
+ // Check energy cost
+ const energyCost = this.getEnergyCost(type);
+ const player = this.game.systems.player;
+
+
+ if (!player.useEnergy(energyCost)) {
+ this.game.showNotification(`Not enough energy! Need ${energyCost} energy`, 'error', 3000);
+ return;
+ }
+
+
+ // Ensure we're on the Dungeons tab so the user can see the dungeon
+ if (this.game.ui && this.game.ui.currentTab !== 'dungeons') {
+ this.game.ui.switchTab('dungeons');
+ }
+
+ // Remove previous selection
+ document.querySelectorAll('.dungeon-item').forEach(item => {
+ item.classList.remove('selected');
+ });
+
+ // Add selection to clicked dungeon
+ const selectedElement = document.querySelector(`[data-dungeon-type="${type}"]`);
+ if (selectedElement) {
+ selectedElement.classList.add('selected');
+ }
+
+
+ // Generate dungeon
+ this.generateDungeon(type);
+ this.displayDungeon();
+
+
+ this.game.showNotification(`Entered dungeon! -${energyCost} energy`, 'info', 3000);
+ }
+
+ getEnergyCost(type) {
+ const costs = {
+ tutorial: 0,
+ alien_ruins: 20,
+ pirate_lair: 15,
+ corrupted_vault: 25,
+ asteroid_mine: 10,
+ nebula_anomaly: 30
+ };
+ return costs[type] || 15;
+ }
+
+ calculateDungeonRewards(type, difficulty) {
+ const dungeonTemplate = this.dungeonTypes[type];
+ const baseRewards = {
+ credits: 50,
+ experience: 25,
+ items: []
+ };
+
+ // Apply difficulty multiplier
+ const difficultyMultipliers = {
+ tutorial: 0.5,
+ easy: 1.0,
+ medium: 1.5,
+ hard: 2.0
+ };
+
+ const multiplier = difficultyMultipliers[difficulty] || 1.0;
+ const rewardMultiplier = dungeonTemplate.rewardMultiplier || 1.0;
+
+ baseRewards.credits = Math.floor(baseRewards.credits * multiplier * rewardMultiplier);
+ baseRewards.experience = Math.floor(baseRewards.experience * multiplier * rewardMultiplier);
+
+ return baseRewards;
+ }
+
+ generateDungeon(type) {
+
+ const dungeonTemplate = this.dungeonTypes[type];
+
+ // Check if tutorial dungeon has already been completed
+ if (type === 'tutorial' && this.game.systems.player.stats.tutorialDungeonCompleted) {
+ this.game.showNotification('Tutorial dungeon has already been completed!', 'warning', 3000);
+ return;
+ }
+
+
+ this.currentDungeon = {
+ type: type,
+ name: dungeonTemplate.name,
+ description: dungeonTemplate.description,
+ difficulty: dungeonTemplate.difficulty,
+ healthType: dungeonTemplate.healthType,
+ enemyTypes: dungeonTemplate.enemyTypes,
+ rewardMultiplier: dungeonTemplate.rewardMultiplier,
+ rooms: [],
+ currentRoomIndex: 0,
+ startTime: Date.now(),
+ completed: false
+ };
+
+
+ // Generate rooms - ensure minimum of 3 rooms (entrance, at least 1 middle, boss)
+ const roomCount = Math.max(3, this.game.getRandomInt(5, 8));
+ this.currentDungeon.rooms = this.generateRoomLayout(roomCount, dungeonTemplate);
+
+ // Set current room
+ this.currentRoom = this.currentDungeon.rooms[0];
+ this.dungeonProgress = 0;
+
+
+ // Show dungeon view and hide list
+ this.showDungeonView();
+
+ }
+
+ generateRoomLayout(roomCount, dungeonTemplate) {
+ const rooms = [];
+
+
+ // Always start with entrance
+ rooms.push(this.createRoom('entrance', dungeonTemplate));
+
+ // Generate middle rooms (subtract 2 for entrance and boss room)
+ const middleRoomCount = roomCount - 2;
+
+ for (let i = 0; i < middleRoomCount; i++) {
+ const roomType = this.getRandomRoomType();
+ const room = this.createRoom(roomType, dungeonTemplate);
+ rooms.push(room);
+ }
+
+ // Always end with boss room (exit is handled by completing the boss room)
+ rooms.push(this.createRoom('boss', dungeonTemplate));
+
+
+ // Verify room accessibility
+ for (let i = 0; i < rooms.length; i++) {
+ }
+
+ return rooms;
+ }
+
+ getRandomRoomType() {
+ const weights = {
+ corridor: 40,
+ chamber: 30,
+ treasure: 0, // Temporarily disabled
+ entrance: 5,
+ boss: 5,
+ exit: 0
+ };
+
+ const totalWeight = Object.values(weights).reduce((sum, weight) => sum + weight, 0);
+ let random = Math.random() * totalWeight;
+
+ for (const [type, weight] of Object.entries(weights)) {
+ random -= weight;
+ if (random <= 0) {
+ return type;
+ }
+ }
+
+ return 'corridor';
+ }
+
+ createRoom(roomType, dungeonTemplate) {
+ const template = this.roomTypes[roomType];
+ const room = {
+ type: roomType,
+ name: template.name,
+ enemies: [],
+ rewards: null,
+ explored: false,
+ completed: false
+ };
+
+ // Generate enemies
+ if (template.enemies > 0) {
+ for (let i = 0; i < template.enemies; i++) {
+ const enemyType = dungeonTemplate.enemyTypes[
+ Math.floor(Math.random() * dungeonTemplate.enemyTypes.length)
+ ];
+ room.enemies.push(this.createEnemy(enemyType, template.isBoss));
+ }
+ }
+
+ // Generate rewards
+ if (template.rewards) {
+ room.rewards = this.generateRoomRewards(dungeonTemplate.difficulty, template.isBoss);
+ }
+
+ return room;
+ }
+
+ createEnemy(enemyType, isBoss = false) {
+ const template = this.enemyTemplates[enemyType];
+ const bossMultiplier = isBoss ? 2.5 : 1.0;
+
+ return {
+ id: Date.now() + Math.random().toString(36).substr(2, 9),
+ type: enemyType,
+ name: isBoss ? `Boss ${template.name}` : template.name,
+ health: Math.floor(template.health * bossMultiplier),
+ maxHealth: Math.floor(template.health * bossMultiplier),
+ attack: Math.floor(template.attack * bossMultiplier),
+ defense: Math.floor(template.defense * bossMultiplier),
+ experience: Math.floor(template.experience * bossMultiplier),
+ credits: Math.floor(template.credits * bossMultiplier),
+ isBoss: isBoss
+ };
+ }
+
+ generateRoomRewards(difficulty, isBoss = false) {
+ const baseRewards = this.game.systems.economy.generateRewards(difficulty, 'dungeon');
+ const multiplier = isBoss ? 2.0 : 1.0;
+
+ const rewards = {
+ credits: Math.floor(baseRewards.credits * multiplier),
+ experience: Math.floor(baseRewards.experience * multiplier),
+ materials: []
+ };
+
+ // Add crafting materials based on chance and difficulty
+ const materialChance = isBoss ? 0.9 : 0.4;
+ if (Math.random() < materialChance) {
+ const materialCount = isBoss ? this.game.getRandomInt(3, 6) : this.game.getRandomInt(1, 3);
+
+ // Define material pools with weights for better balance
+ const materialPools = {
+ tutorial: [
+ { id: 'iron_ore', weight: 40 },
+ { id: 'copper_wire', weight: 30 },
+ { id: 'herbs', weight: 20 },
+ { id: 'bandages', weight: 10 }
+ ],
+ easy: [
+ { id: 'iron_ore', weight: 30 },
+ { id: 'copper_wire', weight: 25 },
+ { id: 'energy_crystal', weight: 15 },
+ { id: 'leather', weight: 15 },
+ { id: 'herbs', weight: 10 },
+ { id: 'bandages', weight: 5 }
+ ],
+ medium: [
+ { id: 'iron_ore', weight: 25 },
+ { id: 'steel_plate', weight: 20 },
+ { id: 'energy_crystal', weight: 20 },
+ { id: 'copper_wire', weight: 15 },
+ { id: 'rare_metal', weight: 5 },
+ { id: 'bandages', weight: 15 }
+ ],
+ hard: [
+ { id: 'steel_plate', weight: 30 },
+ { id: 'energy_crystal', weight: 25 },
+ { id: 'rare_metal', weight: 15 },
+ { id: 'battery', weight: 20 },
+ { id: 'bandages', weight: 10 }
+ ],
+ extreme: [
+ { id: 'rare_metal', weight: 30 },
+ { id: 'energy_crystal', weight: 25 },
+ { id: 'battery', weight: 25 },
+ { id: 'advanced_components', weight: 20 }
+ ]
+ };
+
+ const pool = materialPools[difficulty] || materialPools.easy;
+
+ // Helper function to get weighted random material
+ function getWeightedRandomMaterial(materialPool) {
+ const totalWeight = materialPool.reduce((sum, mat) => sum + mat.weight, 0);
+ let random = Math.random() * totalWeight;
+
+ for (const material of materialPool) {
+ random -= material.weight;
+ if (random <= 0) {
+ return material.id;
+ }
+ }
+ return materialPool[0].id; // Fallback
+ }
+
+ for (let i = 0; i < materialCount; i++) {
+ const material = getWeightedRandomMaterial(pool);
+ const quantity = this.game.getRandomInt(1, isBoss ? 3 : 2);
+
+ rewards.materials.push({
+ id: material,
+ quantity: quantity
+ });
+ }
+ }
+
+ return rewards;
+ }
+
+ displayDungeon() {
+
+ const dungeonViewElement = document.getElementById('dungeonView');
+
+ if (!dungeonViewElement) {
+ const allElements = document.querySelectorAll('[id*="dungeon"]');
+ return;
+ }
+
+ if (!this.currentDungeon) {
+ return;
+ }
+
+ const room = this.currentRoom;
+ if (room) {
+ // Room exists, continue processing
+ }
+
+ const progress = Math.floor((this.currentDungeon.currentRoomIndex / this.currentDungeon.rooms.length) * 100);
+
+
+ let content = `
+
+
+
+
+ ${this.getHealthDisplay()}
+
+
+
+
${room.name}
+ ${this.getRoomDescription(room)}
+
+
+
+ ${this.getRoomContent(room)}
+
+
+
+ ${this.getRoomActions(room)}
+
+
+ `;
+
+
+ dungeonViewElement.innerHTML = content;
+
+
+ // Only add event listeners for buttons that don't use inline onclick handlers
+ const completeDungeonBtn = dungeonViewElement.querySelector('button[onclick*="completeDungeon"]');
+ if (completeDungeonBtn) {
+ completeDungeonBtn.addEventListener('click', (e) => {
+ e.preventDefault();
+ this.completeDungeon();
+ });
+ }
+
+ }
+
+ getHealthDisplay() {
+ const player = this.game.systems.player;
+ const healthType = this.currentDungeon.healthType;
+
+
+ if (healthType === 'ship') {
+ const shipHealth = player.ship.health || 0;
+ const shipMaxHealth = player.ship.maxHealth || 1000;
+ const healthPercent = Math.round((shipHealth / shipMaxHealth) * 100);
+ return `
+
+
Ship Health
+
+
${shipHealth} / ${shipMaxHealth}
+
+ `;
+ } else {
+ const playerHealth = player.attributes.health || 0;
+ const playerMaxHealth = player.attributes.maxHealth || 100;
+ const healthPercent = Math.round((playerHealth / playerMaxHealth) * 100);
+ return `
+
+
Player Health
+
+
${playerHealth} / ${playerMaxHealth}
+
+ `;
+ }
+ }
+
+ getRoomDescription(room) {
+ const descriptions = {
+ entrance: 'You enter the dungeon. The air is thick with anticipation.',
+ corridor: 'A narrow corridor stretches ahead. You can hear distant sounds.',
+ chamber: 'You enter a large chamber. The echoes of past battles linger here.',
+ treasure: 'Golden light glimmers from piles of treasure in this room.',
+ boss: 'A massive presence fills the room. This is the guardian of this dungeon.',
+ exit: 'You can see the exit. Freedom awaits!'
+ };
+
+ return `${descriptions[room.type] || 'You continue exploring...'}
`;
+ }
+
+ getRoomContent(room) {
+ if (room.completed) {
+ return 'This room has been cleared.
';
+ }
+
+ if (room.enemies.length > 0) {
+ const enemiesHtml = room.enemies.map(enemy => `
+
+
${enemy.name}
+
+
+
${enemy.health} / ${enemy.maxHealth}
+
+
ATK: ${enemy.attack} | DEF: ${enemy.defense}
+
+ `).join('');
+
+ return `${enemiesHtml}
`;
+ }
+
+ if (room.rewards && !room.completed) {
+ return '';
+ }
+
+ return 'The room is empty and quiet.
';
+ }
+
+ getRoomActions(room) {
+
+ if (room.completed) {
+ if (this.currentDungeon.currentRoomIndex < this.currentDungeon.rooms.length - 1) {
+ return 'Continue to Next Room ';
+ } else {
+ return 'Complete Dungeon ';
+ }
+ }
+
+ if (room.enemies.length > 0) {
+ const buttonHtml = 'Start Combat ';
+ return buttonHtml;
+ }
+
+ if (room.rewards) {
+ return 'Claim Rewards ';
+ }
+
+ // Check if this is the final room
+ if (this.currentDungeon.currentRoomIndex >= this.currentDungeon.rooms.length - 1) {
+ return 'Complete Dungeon ';
+ }
+
+ return 'Move Forward ';
+ }
+ async startCombat() {
+
+ if (this.isExploring) {
+ return;
+ }
+
+ this.isExploring = true;
+ const room = this.currentRoom;
+ const enemies = room.enemies.filter(e => e.health > 0);
+
+
+ if (enemies.length === 0) {
+ this.completeRoom();
+ return;
+ }
+
+ // Simulate combat
+ await this.simulateCombat(enemies);
+ }
+
+ async simulateCombat(enemies) {
+
+ try {
+ const player = this.game.systems.player;
+
+ const healthType = this.currentDungeon.healthType;
+
+ let combatLog = [];
+
+ // Handle case where there are no enemies
+ if (enemies.length === 0) {
+ combatLog.push('Room was empty - no enemies found');
+ this.completeRoom();
+ return;
+ }
+
+ for (const [index, enemy] of enemies.entries()) {
+
+ // Player attacks
+ const playerDamage = player.calculateDamage(this.currentDungeon.difficulty);
+
+ const actualDamage = Math.max(1, playerDamage.damage - enemy.defense);
+ enemy.health = Math.max(0, enemy.health - actualDamage);
+
+ combatLog.push(`You dealt ${actualDamage} damage to ${enemy.name}${playerDamage.isCritical ? ' (CRITICAL!)' : ''}`);
+
+ // Update UI after player attack to show enemy health change
+ this.displayDungeon();
+ // Also update global player UI to ensure health bars are updated
+ if (player.updateUI) {
+ player.updateUI();
+ }
+
+ // Small delay to make the combat visible
+ await new Promise(resolve => setTimeout(resolve, 500));
+
+ // Enemy attacks if still alive
+ if (enemy.health > 0) {
+ let enemyDamage, damageTarget;
+
+ if (healthType === 'ship') {
+ // Space missions: higher damage, target ship health
+ enemyDamage = Math.max(1, Math.floor(enemy.attack * 1.5) - player.ship.defense);
+ damageTarget = 'ship';
+ this.applyShipDamage(enemyDamage);
+ combatLog.push(`${enemy.name} dealt ${enemyDamage} damage to your ship`);
+ } else {
+ // Ground missions: normal damage, target player health
+ enemyDamage = Math.max(1, enemy.attack - player.attributes.defense);
+ damageTarget = 'player';
+ player.takeDamage(enemyDamage);
+ combatLog.push(`${enemy.name} dealt ${enemyDamage} damage to you`);
+ }
+
+ // Update UI after enemy attack to show player health change
+ this.displayDungeon();
+ // Also update global player UI to ensure health bars are updated
+ if (player.updateUI) {
+ player.updateUI();
+ }
+
+ // Small delay to make the combat visible
+ await new Promise(resolve => setTimeout(resolve, 500));
+
+ // Check for destruction
+ if (healthType === 'ship' && player.ship.health <= 0) {
+ this.handleShipDestruction();
+ } else if (healthType === 'player' && player.attributes.health <= 0) {
+ this.handlePlayerDefeat();
+ }
+ } else {
+ // Enemy defeated
+ player.addExperience(enemy.experience);
+ this.game.systems.economy.addCredits(enemy.credits, 'dungeon');
+ player.incrementKills();
+ this.stats.totalEnemiesDefeated++;
+
+ // Update quest progress for combat objectives
+ if (this.game.systems.questSystem) {
+ this.game.systems.questSystem.onEnemyDefeated();
+ }
+
+ combatLog.push(`${enemy.name} defeated! +${enemy.experience} XP, +${enemy.credits} credits`);
+
+ // Update UI after enemy defeat
+ this.displayDungeon();
+ // Also update global player UI to ensure health bars are updated
+ if (player.updateUI) {
+ player.updateUI();
+ }
+
+ // Small delay to make the combat visible
+ await new Promise(resolve => setTimeout(resolve, 500));
+ }
+ }
+
+ // Show combat results
+ this.game.showNotification('Combat completed!', 'success', 3000);
+ combatLog.forEach(message => this.game.showNotification(message, 'info', 2000));
+
+ // Check if all enemies defeated
+ const remainingEnemies = this.currentRoom.enemies.filter(e => e.health > 0);
+ if (remainingEnemies.length === 0) {
+ this.completeRoom();
+ }
+
+ this.isExploring = false;
+ this.displayDungeon();
+ } catch (error) {
+ console.error('[DUNGEON] Error in simulateCombat:', error);
+ console.error('[DUNGEON] Error stack:', error.stack);
+ this.isExploring = false;
+ }
+ }
+
+ // Health system methods
+ applyShipDamage(amount) {
+ const player = this.game.systems.player;
+ player.ship.health = Math.max(0, player.ship.health - amount);
+ player.updateUI();
+
+ // Check for ship destruction
+ if (player.ship.health <= 0) {
+ this.handleShipDestruction();
+ }
+ }
+
+ handleShipDestruction() {
+ const player = this.game.systems.player;
+
+ // Show destruction message
+ this.game.showNotification('Your ship has been destroyed!', 'error', 5000);
+ this.game.showNotification('Dungeon failed - all progress lost.', 'warning', 4000);
+
+ // Remove the current ship from inventory
+ if (this.game.systems.inventory) {
+ this.game.systems.inventory.removeItem(player.ship);
+ }
+
+ // Reset to a basic ship if available in inventory
+ const availableShips = this.game.systems.inventory.getItemsByType('ship');
+ if (availableShips.length > 0) {
+ player.ship = availableShips[0];
+ this.game.showNotification(`Switched to ${player.ship.name}`, 'info', 3000);
+ } else {
+ // No ships available - create a basic one
+ player.ship = {
+ name: 'Basic Fighter',
+ health: 1000,
+ maxHealth: 1000,
+ defense: 5,
+ attack: 10,
+ type: 'ship'
+ };
+ this.game.showNotification('No ships available - using emergency fighter', 'warning', 3000);
+ }
+
+ // Apply dungeon failure penalty
+ this.applyDungeonFailurePenalty();
+
+ // Exit dungeon
+ this.exitDungeon();
+ }
+
+ applyDungeonFailurePenalty() {
+ const player = this.game.systems.player;
+
+ // Lose some credits as penalty
+ const creditPenalty = Math.floor(this.game.systems.economy.credits * 0.1); // 10% credit loss
+ this.game.systems.economy.removeCredits(creditPenalty);
+
+ // Lose some experience
+ const expPenalty = Math.floor(player.experience * 0.05); // 5% exp loss
+ player.experience = Math.max(0, player.experience - expPenalty);
+
+ // Update failure statistics
+ this.stats.dungeonsFailed++;
+
+ this.game.showNotification(`Dungeon penalty: -${creditPenalty} credits, -${expPenalty} XP`, 'warning', 4000);
+ }
+
+ handlePlayerDefeat() {
+ this.game.showNotification('You have been defeated!', 'error', 5000);
+ this.game.showNotification('You\'ve been forced to retreat from the dungeon.', 'warning', 4000);
+
+ // Heal player to prevent death loop
+ const player = this.game.systems.player;
+ player.attributes.health = Math.floor(player.attributes.maxHealth * 0.5);
+
+ // Exit dungeon
+ this.exitDungeon();
+ }
+
+ exitDungeon() {
+ this.currentDungeon = null;
+ this.currentRoom = null;
+ this.dungeonProgress = 0;
+ this.isExploring = false;
+
+ // Return to main view
+ this.generateDungeonList();
+ }
+
+ claimRewards() {
+ const room = this.currentRoom;
+ if (!room.rewards || room.completed) return;
+
+ // Give all rewards including materials
+ this.game.systems.economy.giveRewards(room.rewards, 'dungeon');
+
+ room.rewards = null; // Remove rewards after claiming
+
+ this.game.showNotification('Rewards claimed!', 'success', 3000);
+
+ // Use the proper completeRoom method to ensure consistent behavior
+ this.completeRoom();
+ }
+
+ completeRoom() {
+ const room = this.currentRoom;
+ room.completed = true;
+ room.explored = true;
+
+ // Reset exploring flag when completing a room
+ this.isExploring = false;
+
+
+ // Auto-claim rewards if available
+ if (room.rewards) {
+ // Give credits and experience
+ this.game.systems.economy.giveRewards(room.rewards, 'dungeon');
+
+ // Add crafting materials to inventory
+ if (room.rewards.materials && room.rewards.materials.length > 0) {
+ let materialText = '';
+ room.rewards.materials.forEach(material => {
+ this.game.systems.inventory.addItem(material.id, material.quantity);
+ materialText += `${material.quantity}x ${material.id}, `;
+ });
+
+ materialText = materialText.slice(0, -2);
+
+ this.game.showNotification(`Materials found: ${materialText}`, 'success', 4000);
+ }
+
+ room.rewards = null;
+ }
+
+ this.game.showNotification(`${room.name} completed!`, 'success', 3000);
+
+ // Check if this is a boss room - if so, automatically complete the dungeon
+ if (room.type === 'boss') {
+ setTimeout(() => {
+ this.completeDungeon();
+ }, 2000); // Small delay to show the completion message first
+ } else {
+ this.displayDungeon();
+ }
+
+ }
+
+ nextRoom() {
+
+ // Log all rooms for debugging
+ this.currentDungeon.rooms.forEach((room, index) => {
+ });
+
+ if (this.currentDungeon.currentRoomIndex >= this.currentDungeon.rooms.length - 1) {
+ return;
+ }
+
+ const oldIndex = this.currentDungeon.currentRoomIndex;
+ this.currentDungeon.currentRoomIndex++;
+ this.currentRoom = this.currentDungeon.rooms[this.currentDungeon.currentRoomIndex];
+
+
+ this.displayDungeon();
+ }
+
+ completeDungeon() {
+ if (!this.currentDungeon) {
+ return;
+ }
+
+ const completionTime = Date.now() - this.currentDungeon.startTime;
+ const player = this.game.systems.player;
+ const economy = this.game.systems.economy;
+
+ // Generate proper rewards including materials
+ const baseRewards = this.generateRoomRewards(this.currentDungeon.difficulty, false);
+
+ // Apply time bonus
+ const timeBonus = Math.floor(completionTime < 300000 ? 50 : 0); // Bonus for completing in under 5 minutes
+ baseRewards.credits += timeBonus;
+ baseRewards.experience += Math.floor(timeBonus / 2);
+
+ // Give rewards
+ economy.giveRewards(baseRewards, 'dungeon');
+ player.addExperience(baseRewards.experience);
+
+ // Update statistics
+ this.stats.dungeonsCompleted++;
+ this.stats.totalTimeInDungeons += completionTime;
+
+ // Update player dungeons cleared stat
+ player.stats.dungeonsCleared++;
+
+ // Update quest progress for dungeon objectives
+ if (this.game.systems.questSystem) {
+ // Check if this is a tutorial dungeon
+ if (this.currentDungeon.type === 'tutorial') {
+ // Mark tutorial dungeon as completed in player stats
+ player.stats.tutorialDungeonCompleted = true;
+ // Update tutorial dungeon quest progress
+ this.game.systems.questSystem.updateTutorialDungeonProgress();
+ } else {
+ // Update regular dungeon quest progress using the standard method
+ this.game.systems.questSystem.onDungeonCompleted();
+ }
+ }
+
+ // Show completion message
+ this.game.showNotification(`Dungeon completed! Time: ${this.game.formatTime(completionTime)}`, 'success', 5000);
+ if (timeBonus > 0) {
+ this.game.showNotification(`Time bonus: +${timeBonus} credits!`, 'info', 3000);
+ }
+
+ // Reset dungeon state
+ this.currentDungeon = null;
+ this.currentRoom = null;
+ this.isExploring = false;
+
+ // Return to dungeon list view
+ this.showDungeonList();
+ this.generateDungeonList();
+
+ // Force UI refresh
+ this.updateUI();
+ }
+
+ showDungeonList() {
+ const dungeonListElement = document.getElementById('dungeonList');
+ const dungeonViewElement = document.getElementById('dungeonView');
+
+ if (dungeonListElement) {
+ dungeonListElement.style.display = 'flex';
+ }
+
+ if (dungeonViewElement) {
+ dungeonViewElement.style.display = 'none';
+ // Clear the dungeon view content to prevent frozen display
+ dungeonViewElement.innerHTML = '';
+ }
+ }
+
+ showDungeonView() {
+
+ const dungeonListElement = document.getElementById('dungeonList');
+ const dungeonViewElement = document.getElementById('dungeonView');
+
+
+ if (dungeonListElement) {
+ dungeonListElement.style.display = 'none';
+ }
+
+ if (dungeonViewElement) {
+ dungeonViewElement.style.display = 'flex';
+ }
+
+
+ // Display the current dungeon
+ this.displayDungeon();
+
+ }
+
+ // UI updates
+ updateUI() {
+ // Update dungeon statistics if elements exist
+ const dungeonsClearedElement = document.getElementById('dungeonsCleared');
+ if (dungeonsClearedElement) {
+ dungeonsClearedElement.textContent = this.stats.dungeonsCompleted;
+ }
+ }
+
+ // Save/Load
+ save() {
+ return {
+ stats: this.stats,
+ currentDungeon: this.currentDungeon,
+ currentRoom: this.currentRoom,
+ dungeonProgress: this.dungeonProgress
+ };
+ }
+
+ load(data) {
+ if (data.stats) this.stats = { ...this.stats, ...data.stats };
+ if (data.currentDungeon) this.currentDungeon = data.currentDungeon;
+ if (data.currentRoom) this.currentRoom = data.currentRoom;
+ if (data.dungeonProgress !== undefined) this.dungeonProgress = data.dungeonProgress;
+ }
+}
diff --git a/Client/js/systems/IdleSystem.js b/Client/js/systems/IdleSystem.js
new file mode 100644
index 0000000..0eec3bd
--- /dev/null
+++ b/Client/js/systems/IdleSystem.js
@@ -0,0 +1,357 @@
+/**
+ * Galaxy Strike Online - Idle System
+ * Manages offline progression and idle mechanics
+ */
+
+class IdleSystem {
+ constructor(gameEngine) {
+ this.game = gameEngine;
+
+ // Idle settings
+ this.maxOfflineTime = 7 * 24 * 60 * 60 * 1000; // 7 days in milliseconds
+ this.lastActiveTime = Date.now();
+ this.accumulatedTime = 0; // Track time for resource generation
+
+ // Idle production rates
+ this.productionRates = {
+ 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
+ };
+
+ // Offline 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
+ };
+ }
+
+ async initialize() {
+ // Calculate offline progress if returning
+ this.calculateOfflineProgress();
+}
+
+ calculateOfflineProgress(offlineTime = null) {
+ const currentTime = Date.now();
+ const actualOfflineTime = offlineTime || (currentTime - this.lastActiveTime);
+
+ // Cap offline time to maximum
+ const cappedOfflineTime = Math.min(actualOfflineTime, this.maxOfflineTime);
+
+ if (cappedOfflineTime < 60000) { // Less than 1 minute
+ return;
+ }
+
+ // Calculate production
+ const totalBonus = this.getTotalBonus();
+ const productionSeconds = cappedOfflineTime / 1000;
+
+ this.offlineRewards = {
+ credits: Math.floor(this.productionRates.credits * productionSeconds * totalBonus),
+ experience: Math.floor(this.productionRates.experience * productionSeconds * totalBonus),
+ energy: Math.min(
+ this.game.systems.player.attributes.maxEnergy,
+ Math.floor(this.productionRates.energy * productionSeconds)
+ ),
+ items: this.generateIdleItems(cappedOfflineTime)
+ };
+
+ // Update achievements
+ this.achievements.totalOfflineTime += cappedOfflineTime;
+ this.achievements.maxOfflineSession = Math.max(this.achievements.maxOfflineSession, cappedOfflineTime);
+ this.achievements.totalIdleCredits += this.offlineRewards.credits;
+ this.achievements.totalIdleExperience += this.offlineRewards.experience;
+
+ // Show offline rewards notification
+ this.showOfflineRewards(cappedOfflineTime);
+}
+
+ getTotalBonus() {
+ return this.bonuses.premium * this.bonuses.guild * this.bonuses.research;
+ }
+
+ generateIdleItems(offlineTime) {
+ const items = [];
+ const hours = offlineTime / (1000 * 60 * 60);
+
+ // Chance to find items based on offline time
+ const itemChance = Math.min(0.5, hours * 0.05);
+
+ if (Math.random() < itemChance) {
+ const itemCount = Math.floor(hours / 2) + 1;
+
+ for (let i = 0; i < itemCount; i++) {
+ const rarity = this.getRandomItemRarity();
+ const item = this.game.systems.inventory.generateItem('consumable', rarity);
+ items.push(item);
+ }
+ }
+
+ return items;
+ }
+
+ getRandomItemRarity() {
+ const roll = Math.random();
+ if (roll < 0.05) return 'legendary';
+ if (roll < 0.15) return 'epic';
+ if (roll < 0.35) return 'rare';
+ if (roll < 0.65) return 'uncommon';
+ return 'common';
+ }
+
+ showOfflineRewards(offlineTime) {
+ const timeString = this.game.formatTime(offlineTime);
+
+ this.game.showNotification(
+ `Welcome back! You were offline for ${timeString}`,
+ 'info',
+ 5000
+ );
+
+ // Format rewards message
+ let rewardsMessage = 'Offline Rewards:\n';
+ if (this.offlineRewards.credits > 0) {
+ rewardsMessage += `+${this.game.formatNumber(this.offlineRewards.credits)} credits\n`;
+ }
+ if (this.offlineRewards.experience > 0) {
+ rewardsMessage += `+${this.game.formatNumber(this.offlineRewards.experience)} XP\n`;
+ }
+ if (this.offlineRewards.energy > 0) {
+ rewardsMessage += `+${this.game.formatNumber(this.offlineRewards.energy)} energy\n`;
+ }
+ if (this.offlineRewards.items.length > 0) {
+ rewardsMessage += `+${this.offlineRewards.items.length} items\n`;
+ }
+
+ this.game.showNotification(rewardsMessage, 'success', 5000);
+ }
+
+ claimOfflineRewards() {
+ if (this.offlineRewards.credits === 0 &&
+ this.offlineRewards.experience === 0 &&
+ this.offlineRewards.items.length === 0) {
+ this.game.showNotification('No offline rewards to claim', 'info', 3000);
+ return;
+ }
+
+ // Give rewards
+ if (this.offlineRewards.credits > 0) {
+ this.game.systems.economy.addCredits(this.offlineRewards.credits, 'offline');
+ }
+
+ if (this.offlineRewards.experience > 0) {
+ this.game.systems.player.addExperience(this.offlineRewards.experience);
+ }
+
+ if (this.offlineRewards.energy > 0) {
+ this.game.systems.player.restoreEnergy(this.offlineRewards.energy);
+ }
+
+ // Add items to inventory
+ if (this.offlineRewards.items.length > 0) {
+ const inventory = this.game.systems.inventory;
+ this.offlineRewards.items.forEach(item => {
+ inventory.addItem(item);
+ });
+ }
+
+ // Reset offline rewards
+ this.offlineRewards = {
+ credits: 0,
+ experience: 0,
+ energy: 0,
+ items: []
+ };
+
+ this.game.showNotification('Offline rewards claimed!', 'success', 3000);
+ }
+
+ // Active idle production
+ update(deltaTime) {
+ if (this.game.state.paused) return;
+
+ // Use real computer time delta
+ const seconds = deltaTime / 1000;
+ const totalBonus = this.getTotalBonus();
+
+ // Only add resources once per second, not every frame
+ this.accumulatedTime += seconds;
+
+ if (this.accumulatedTime >= 1.0) {
+ // Calculate active production
+ const activeCredits = Math.floor(this.productionRates.credits * totalBonus);
+ const activeExperience = Math.floor(this.productionRates.experience * totalBonus);
+ // const activeEnergy = this.productionRates.energy * totalBonus * 0.1; // Energy is handled differently
+
+ // Add resources
+ if (activeCredits > 0) {
+ this.game.systems.economy.addCredits(activeCredits, 'idle');
+ }
+ if (activeExperience > 0) {
+ this.game.systems.player.addExperience(activeExperience);
+ }
+
+ // Regenerate energy
+ this.game.systems.player.restoreEnergy(this.productionRates.energy);
+
+ // Reset accumulated time, keeping any remainder
+ this.accumulatedTime -= 1.0;
+
+ // Debugging: Log when resources are added
+ // console.debug(`[IDLE] Added ${activeCredits} credits and ${activeExperience} XP. Accumulated time: ${this.accumulatedTime.toFixed(2)}s`);
+ }
+
+ // Update last active time for offline calculations
+ this.lastActiveTime = Date.now();
+ }
+
+ // Upgrade production rates
+ upgradeProduction(type) {
+ const upgradeCosts = {
+ credits: 100,
+ experience: 150,
+ energy: 80
+ };
+
+ const cost = upgradeCosts[type];
+ if (!cost || this.game.systems.economy.credits < cost) {
+ return false;
+ }
+
+ this.game.systems.economy.removeCredits(cost);
+
+ switch (type) {
+ case 'credits':
+ this.productionRates.credits += 2;
+ break;
+ case 'experience':
+ this.productionRates.experience += 1;
+ break;
+ case 'energy':
+ this.productionRates.energy += 0.2;
+ break;
+ }
+
+ this.game.showNotification(`Production upgraded: ${type}!`, 'success', 3000);
+return true;
+ }
+
+ // Bonus management
+ setBonus(type, value) {
+ if (this.bonuses[type] !== undefined) {
+ this.bonuses[type] = value;
+ this.game.showNotification(`${type} bonus set to ${value}x`, 'info', 3000);
+ }
+ }
+
+ // Achievement checking
+ checkAchievements() {
+ const achievements = [
+ {
+ id: 'idle_warrior',
+ name: 'Idle Warrior',
+ description: 'Earn 1,000,000 credits from idle',
+ condition: () => this.achievements.totalIdleCredits >= 1000000,
+ reward: { gems: 50, experience: 1000 }
+ },
+ {
+ id: 'time_master',
+ name: 'Time Master',
+ description: 'Accumulate 24 hours of offline time',
+ condition: () => this.achievements.totalOfflineTime >= 24 * 60 * 60 * 1000,
+ reward: { gems: 25, experience: 500 }
+ },
+ {
+ id: 'marathon_idle',
+ name: 'Marathon Idle',
+ description: 'Be offline for more than 12 hours at once',
+ condition: () => this.achievements.maxOfflineSession >= 12 * 60 * 60 * 1000,
+ reward: { gems: 100, experience: 2000 }
+ }
+ ];
+
+ achievements.forEach(achievement => {
+ if (achievement.condition()) {
+ this.unlockAchievement(achievement);
+ }
+ });
+ }
+
+ unlockAchievement(achievement) {
+ this.game.showNotification(`Achievement Unlocked: ${achievement.name}!`, 'success', 5000);
+ this.game.showNotification(achievement.description, 'info', 3000);
+
+ // Give rewards
+ if (achievement.reward.gems) {
+ this.game.systems.economy.addGems(achievement.reward.gems, 'achievement');
+ }
+
+ if (achievement.reward.experience) {
+ this.game.systems.player.addExperience(achievement.reward.experience);
+ }
+ }
+
+ // UI updates
+ updateUI() {
+ const offlineTimeElement = document.getElementById('offlineTime');
+ const offlineResourcesElement = document.getElementById('offlineResources');
+ const claimOfflineBtn = document.getElementById('claimOfflineBtn');
+
+ if (offlineTimeElement) {
+ const totalRewards = this.offlineRewards.credits +
+ this.offlineRewards.experience +
+ (this.offlineRewards.items.length * 100);
+ offlineTimeElement.textContent = totalRewards > 0 ? 'Available' : 'None';
+ }
+
+ if (offlineResourcesElement) {
+ const totalRewards = this.offlineRewards.credits +
+ this.offlineRewards.experience +
+ (this.offlineRewards.items.length * 100);
+ offlineResourcesElement.textContent = this.game.formatNumber(totalRewards);
+ }
+
+ if (claimOfflineBtn) {
+ const hasRewards = this.offlineRewards.credits > 0 ||
+ this.offlineRewards.experience > 0 ||
+ this.offlineRewards.items.length > 0;
+ claimOfflineBtn.disabled = !hasRewards;
+ }
+ }
+
+ // Save/Load
+ save() {
+ return {
+ lastActiveTime: this.lastActiveTime,
+ productionRates: this.productionRates,
+ bonuses: this.bonuses,
+ achievements: this.achievements,
+ offlineRewards: this.offlineRewards
+ };
+ }
+
+ load(data) {
+ if (data.lastActiveTime) this.lastActiveTime = data.lastActiveTime;
+ if (data.productionRates) this.productionRates = { ...this.productionRates, ...data.productionRates };
+ if (data.bonuses) this.bonuses = { ...this.bonuses, ...data.bonuses };
+ if (data.achievements) this.achievements = { ...this.achievements, ...data.achievements };
+ if (data.offlineRewards) this.offlineRewards = data.offlineRewards;
+}
+}
diff --git a/Client/js/systems/QuestSystem.js b/Client/js/systems/QuestSystem.js
new file mode 100644
index 0000000..7a443dc
--- /dev/null
+++ b/Client/js/systems/QuestSystem.js
@@ -0,0 +1,2725 @@
+/**
+ * Galaxy Strike Online - Quest System
+ * Manages hand-crafted and procedural quests
+ */
+
+class QuestSystem {
+ constructor(gameEngine) {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('QuestSystem.constructor', {
+ gameEngineProvided: !!gameEngine
+ });
+
+ this.game = gameEngine;
+
+ // Quest types
+ this.questTypes = {
+ main: 'Main Story',
+ daily: 'Daily',
+ weekly: 'Weekly',
+ completed: 'Completed',
+ failed: 'Failed Quests'
+ };
+
+ // Quest status
+ this.questStatus = {
+ available: 'available',
+ active: 'active',
+ completed: 'completed',
+ failed: 'failed'
+ };
+
+ if (debugLogger) debugLogger.logStep('Quest system configuration initialized', {
+ questTypes: Object.keys(this.questTypes),
+ questStatus: Object.keys(this.questStatus)
+ });
+
+ // Main story quests
+ this.mainQuests = [
+ {
+ id: 'tutorial_complete',
+ name: 'First Steps',
+ description: 'Complete the tutorial dungeon and learn the basics',
+ type: 'main',
+ status: 'available',
+ requirements: { level: 1 },
+ objectives: [
+ { id: 'clear_tutorial_dungeon', description: 'Complete the tutorial dungeon', target: 1, current: 0, type: 'tutorial_dungeon' },
+ { id: 'reach_level_2', description: 'Reach level 2', target: 2, current: 0, type: 'level' }
+ ],
+ rewards: { credits: 500, experience: 100, gems: 5 },
+ nextQuest: 'first_ship_upgrade'
+ },
+ {
+ id: 'first_ship_upgrade',
+ name: 'Ship Enhancement',
+ description: 'Upgrade your ship for better performance',
+ type: 'main',
+ status: 'available',
+ requirements: { quest: 'tutorial_complete' },
+ objectives: [
+ { id: 'upgrade_weapon', description: 'Upgrade ship weapons', target: 1, current: 0, type: 'upgrade' },
+ { id: 'upgrade_shield', description: 'Upgrade ship shields', target: 1, current: 0, type: 'upgrade' }
+ ],
+ rewards: { credits: 1000, experience: 200, gems: 10 },
+ nextQuest: 'join_guild'
+ },
+ {
+ id: 'join_guild',
+ name: 'Guild Recruitment',
+ description: 'Join a guild and participate in guild activities',
+ type: 'main',
+ status: 'available',
+ requirements: { quest: 'first_ship_upgrade', level: 5 },
+ objectives: [
+ { id: 'join_guild', description: 'Join a guild', target: 1, current: 0, type: 'guild' },
+ { id: 'guild_contribution', description: 'Contribute to guild', target: 100, current: 0, type: 'contribution' }
+ ],
+ rewards: { credits: 2000, experience: 500, gems: 20 },
+ nextQuest: 'master_commander'
+ },
+ {
+ id: 'master_commander',
+ name: 'Master Commander',
+ description: 'Become a master commander and lead your fleet to victory',
+ type: 'main',
+ status: 'available',
+ requirements: { quest: 'join_guild', level: 10 },
+ objectives: [
+ { id: 'reach_level_10', description: 'Reach level 10', target: 10, current: 0, type: 'level' },
+ { id: 'clear_10_dungeons', description: 'Clear 10 dungeons', target: 10, current: 0, type: 'dungeon' },
+ { id: 'max_skill', description: 'Max out one skill', target: 10, current: 0, type: 'skill' }
+ ],
+ rewards: { credits: 5000, experience: 1000, gems: 50, item: 'legendary_weapon' }
+ }
+ ];
+
+ // All possible daily quests (20 total)
+ this.allDailyQuests = [
+ // Easy quests (difficulty: 1)
+ {
+ id: 'daily_dungeon_easy',
+ name: 'Quick Dungeon Run',
+ description: 'Complete any dungeon',
+ type: 'daily',
+ difficulty: 1,
+ status: 'available',
+ objectives: [
+ { id: 'clear_dungeon', description: 'Clear 1 dungeon', target: 1, current: 0, type: 'dungeon' }
+ ],
+ rewards: { credits: 100, experience: 25, gems: 1 }
+ },
+ {
+ id: 'daily_combat_easy',
+ name: 'Light Combat',
+ description: 'Defeat a few enemies',
+ type: 'daily',
+ difficulty: 1,
+ status: 'available',
+ objectives: [
+ { id: 'defeat_enemies', description: 'Defeat 10 enemies', target: 10, current: 0, type: 'combat' }
+ ],
+ rewards: { credits: 80, experience: 20, gems: 1 }
+ },
+ {
+ id: 'daily_crafting_easy',
+ name: 'Basic Crafting',
+ description: 'Craft some items',
+ type: 'daily',
+ difficulty: 1,
+ status: 'available',
+ objectives: [
+ { id: 'craft_items', description: 'Craft 2 items', target: 2, current: 0, type: 'crafting' }
+ ],
+ rewards: { credits: 90, experience: 22, gems: 1 }
+ },
+ {
+ id: 'daily_level_easy',
+ name: 'Level Up',
+ description: 'Gain experience and level up',
+ type: 'daily',
+ difficulty: 1,
+ status: 'available',
+ objectives: [
+ { id: 'gain_level', description: 'Gain 1 level', target: 1, current: 0, type: 'level' }
+ ],
+ rewards: { credits: 120, experience: 30, gems: 2 }
+ },
+ {
+ id: 'daily_energy_easy',
+ name: 'Energy Management',
+ description: 'Use energy efficiently',
+ type: 'daily',
+ difficulty: 1,
+ status: 'available',
+ objectives: [
+ { id: 'use_energy', description: 'Use 50 energy', target: 50, current: 0, type: 'energy' }
+ ],
+ rewards: { credits: 70, experience: 18, gems: 1 }
+ },
+ // Medium quests (difficulty: 2)
+ {
+ id: 'daily_dungeon_medium',
+ name: 'Dungeon Explorer',
+ description: 'Complete multiple dungeons',
+ type: 'daily',
+ difficulty: 2,
+ status: 'available',
+ objectives: [
+ { id: 'clear_dungeons', description: 'Clear 3 dungeons', target: 3, current: 0, type: 'dungeon' }
+ ],
+ rewards: { credits: 300, experience: 75, gems: 3 }
+ },
+ {
+ id: 'daily_combat_medium',
+ name: 'Combat Training',
+ description: 'Defeat enemies in combat',
+ type: 'daily',
+ difficulty: 2,
+ status: 'available',
+ objectives: [
+ { id: 'defeat_enemies', description: 'Defeat 20 enemies', target: 20, current: 0, type: 'combat' }
+ ],
+ rewards: { credits: 150, experience: 40, gems: 1 }
+ },
+ {
+ id: 'daily_combat_hard',
+ name: 'Combat Veteran',
+ description: 'Defeat many enemies',
+ type: 'daily',
+ difficulty: 2,
+ status: 'available',
+ objectives: [
+ { id: 'defeat_enemies', description: 'Defeat 50 enemies', target: 50, current: 0, type: 'combat' }
+ ],
+ rewards: { credits: 250, experience: 60, gems: 3 }
+ },
+ {
+ id: 'daily_crafting_medium',
+ name: 'Master Crafter',
+ description: 'Craft many items',
+ type: 'daily',
+ difficulty: 2,
+ status: 'available',
+ objectives: [
+ { id: 'craft_items', description: 'Craft 5 items', target: 5, current: 0, type: 'crafting' }
+ ],
+ rewards: { credits: 280, experience: 70, gems: 3 }
+ },
+ {
+ id: 'daily_upgrade_medium',
+ name: 'Equipment Upgrade',
+ description: 'Upgrade your equipment',
+ type: 'daily',
+ difficulty: 2,
+ status: 'available',
+ objectives: [
+ { id: 'upgrade_items', description: 'Upgrade 3 items', target: 3, current: 0, type: 'upgrade' }
+ ],
+ rewards: { credits: 320, experience: 80, gems: 4 }
+ },
+ {
+ id: 'daily_wealth_medium',
+ name: 'Wealth Accumulator',
+ description: 'Earn credits',
+ type: 'daily',
+ difficulty: 2,
+ status: 'available',
+ objectives: [
+ { id: 'earn_credits', description: 'Earn 1000 credits', target: 1000, current: 0, type: 'credits' }
+ ],
+ rewards: { credits: 400, experience: 50, gems: 3 }
+ },
+ // Hard quests (difficulty: 3)
+ {
+ id: 'daily_dungeon_hard',
+ name: 'Dungeon Master',
+ description: 'Complete many dungeons',
+ type: 'daily',
+ difficulty: 3,
+ status: 'available',
+ objectives: [
+ { id: 'clear_dungeons', description: 'Clear 5 dungeons', target: 5, current: 0, type: 'dungeon' }
+ ],
+ rewards: { credits: 600, experience: 150, gems: 6 }
+ },
+ {
+ id: 'daily_combat_hard',
+ name: 'Combat Master',
+ description: 'Defeat many powerful enemies',
+ type: 'daily',
+ difficulty: 3,
+ status: 'available',
+ objectives: [
+ { id: 'defeat_enemies', description: 'Defeat 100 enemies', target: 100, current: 0, type: 'combat' }
+ ],
+ rewards: { credits: 500, experience: 120, gems: 5 }
+ },
+ {
+ id: 'daily_level_hard',
+ name: 'Power Leveling',
+ description: 'Gain multiple levels',
+ type: 'daily',
+ difficulty: 3,
+ status: 'available',
+ objectives: [
+ { id: 'gain_levels', description: 'Gain 3 levels', target: 3, current: 0, type: 'level' }
+ ],
+ rewards: { credits: 700, experience: 200, gems: 7 }
+ },
+ {
+ id: 'daily_boss_hard',
+ name: 'Boss Hunter',
+ description: 'Defeat boss enemies',
+ type: 'daily',
+ difficulty: 3,
+ status: 'available',
+ objectives: [
+ { id: 'defeat_bosses', description: 'Defeat 3 bosses', target: 3, current: 0, type: 'boss' }
+ ],
+ rewards: { credits: 800, experience: 180, gems: 8 }
+ },
+ {
+ id: 'daily_collection_hard',
+ name: 'Master Collector',
+ description: 'Collect rare items',
+ type: 'daily',
+ difficulty: 3,
+ status: 'available',
+ objectives: [
+ { id: 'collect_rare', description: 'Collect 10 rare items', target: 10, current: 0, type: 'collection' }
+ ],
+ rewards: { credits: 650, experience: 140, gems: 6 }
+ },
+ // Special quests (difficulty: 4)
+ {
+ id: 'daily_speedrun',
+ name: 'Speed Runner',
+ description: 'Complete dungeons quickly',
+ type: 'daily',
+ difficulty: 4,
+ status: 'available',
+ objectives: [
+ { id: 'fast_dungeon', description: 'Complete 2 dungeons under 5 minutes', target: 2, current: 0, type: 'speedrun' }
+ ],
+ rewards: { credits: 1000, experience: 250, gems: 10 }
+ },
+ {
+ id: 'daily_perfection',
+ name: 'Perfectionist',
+ description: 'Complete objectives without taking damage',
+ type: 'daily',
+ difficulty: 4,
+ status: 'available',
+ objectives: [
+ { id: 'perfect_runs', description: '3 perfect dungeon runs', target: 3, current: 0, type: 'perfect' }
+ ],
+ rewards: { credits: 1200, experience: 300, gems: 12 }
+ },
+ {
+ id: 'daily_multitask',
+ name: 'Multitask Master',
+ description: 'Complete multiple quest types',
+ type: 'daily',
+ difficulty: 4,
+ status: 'available',
+ objectives: [
+ { id: 'dungeon_task', description: 'Clear 2 dungeons', target: 2, current: 0, type: 'dungeon' },
+ { id: 'combat_task', description: 'Defeat 30 enemies', target: 30, current: 0, type: 'combat' },
+ { id: 'craft_task', description: 'Craft 2 items', target: 2, current: 0, type: 'crafting' }
+ ],
+ rewards: { credits: 1500, experience: 400, gems: 15 }
+ },
+ {
+ id: 'daily_endurance',
+ name: 'Endurance Test',
+ description: 'Complete long activities',
+ type: 'daily',
+ difficulty: 4,
+ status: 'available',
+ objectives: [
+ { id: 'long_dungeon', description: 'Complete 1 dungeon without healing', target: 1, current: 0, type: 'endurance' }
+ ],
+ rewards: { credits: 1100, experience: 280, gems: 11 }
+ },
+ {
+ id: 'daily_legendary',
+ name: 'Legendary Challenge',
+ description: 'Complete legendary difficulty content',
+ type: 'daily',
+ difficulty: 4,
+ status: 'available',
+ objectives: [
+ { id: 'legendary_content', description: 'Complete 1 legendary dungeon', target: 1, current: 0, type: 'legendary' }
+ ],
+ rewards: { credits: 2000, experience: 500, gems: 20, item: 'rare_material' }
+ }
+ ];
+
+ // Weekly quests (25 total quests with varied objectives)
+ this.allWeeklyQuests = [
+ // Combat-focused weekly quests
+ {
+ id: 'weekly_combat_basic',
+ name: 'Weekly Combat Duty',
+ description: 'Complete combat objectives throughout the week',
+ type: 'weekly',
+ difficulty: 1,
+ status: 'available',
+ objectives: [
+ { id: 'defeat_enemies', description: 'Defeat 100 enemies', target: 100, current: 0, type: 'combat' }
+ ],
+ rewards: { credits: 800, experience: 200, gems: 8 }
+ },
+ {
+ id: 'weekly_combat_elite',
+ name: 'Elite Hunter Weekly',
+ description: 'Hunt down elite enemies',
+ type: 'weekly',
+ difficulty: 3,
+ status: 'available',
+ objectives: [
+ { id: 'defeat_elites', description: 'Defeat 25 elite enemies', target: 25, current: 0, type: 'elite_combat' }
+ ],
+ rewards: { credits: 1500, experience: 400, gems: 15 }
+ },
+ {
+ id: 'weekly_boss_hunter',
+ name: 'Boss Hunter Weekly',
+ description: 'Defeat powerful boss enemies',
+ type: 'weekly',
+ difficulty: 4,
+ status: 'available',
+ objectives: [
+ { id: 'defeat_bosses', description: 'Defeat 10 bosses', target: 10, current: 0, type: 'boss' }
+ ],
+ rewards: { credits: 2500, experience: 600, gems: 25, item: 'boss_material' }
+ },
+
+ // Dungeon-focused weekly quests
+ {
+ id: 'weekly_dungeon_explorer',
+ name: 'Weekly Dungeon Explorer',
+ description: 'Explore various dungeons',
+ type: 'weekly',
+ difficulty: 2,
+ status: 'available',
+ objectives: [
+ { id: 'clear_dungeons', description: 'Clear 15 dungeons', target: 15, current: 0, type: 'dungeon' }
+ ],
+ rewards: { credits: 1200, experience: 300, gems: 12 }
+ },
+ {
+ id: 'weekly_dungeon_master',
+ name: 'Weekly Dungeon Master',
+ description: 'Master difficult dungeons',
+ type: 'weekly',
+ difficulty: 3,
+ status: 'available',
+ objectives: [
+ { id: 'clear_hard_dungeons', description: 'Clear 10 hard dungeons', target: 10, current: 0, type: 'hard_dungeon' }
+ ],
+ rewards: { credits: 2000, experience: 500, gems: 20 }
+ },
+ {
+ id: 'weekly_dungeon_extreme',
+ name: 'Extreme Dungeon Challenge',
+ description: 'Conquer extreme difficulty dungeons',
+ type: 'weekly',
+ difficulty: 4,
+ status: 'available',
+ objectives: [
+ { id: 'clear_extreme_dungeons', description: 'Clear 5 extreme dungeons', target: 5, current: 0, type: 'extreme_dungeon' }
+ ],
+ rewards: { credits: 3500, experience: 800, gems: 35, item: 'extreme_material' }
+ },
+
+ // Crafting and upgrade weekly quests
+ {
+ id: 'weekly_crafting_basic',
+ name: 'Weekly Crafting Session',
+ description: 'Craft items throughout the week',
+ type: 'weekly',
+ difficulty: 1,
+ status: 'available',
+ objectives: [
+ { id: 'craft_items', description: 'Craft 20 items', target: 20, current: 0, type: 'crafting' }
+ ],
+ rewards: { credits: 600, experience: 150, gems: 6 }
+ },
+ {
+ id: 'weekly_crafting_master',
+ name: 'Master Crafter Weekly',
+ description: 'Craft advanced items',
+ type: 'weekly',
+ difficulty: 3,
+ status: 'available',
+ objectives: [
+ { id: 'craft_advanced', description: 'Craft 10 advanced items', target: 10, current: 0, type: 'advanced_crafting' }
+ ],
+ rewards: { credits: 1800, experience: 450, gems: 18 }
+ },
+ {
+ id: 'weekly_upgrade_specialist',
+ name: 'Weekly Upgrade Specialist',
+ description: 'Upgrade equipment and systems',
+ type: 'weekly',
+ difficulty: 2,
+ status: 'available',
+ objectives: [
+ { id: 'upgrade_items', description: 'Upgrade 15 items', target: 15, current: 0, type: 'upgrade' }
+ ],
+ rewards: { credits: 1400, experience: 350, gems: 14 }
+ },
+
+ // Progression weekly quests
+ {
+ id: 'weekly_level_up',
+ name: 'Weekly Level Up Challenge',
+ description: 'Gain levels throughout the week',
+ type: 'weekly',
+ difficulty: 2,
+ status: 'available',
+ objectives: [
+ { id: 'gain_levels', description: 'Gain 5 levels', target: 5, current: 0, type: 'level' }
+ ],
+ rewards: { credits: 1000, experience: 250, gems: 10 }
+ },
+ {
+ id: 'weekly_skill_master',
+ name: 'Weekly Skill Mastery',
+ description: 'Improve your skills',
+ type: 'weekly',
+ difficulty: 3,
+ status: 'available',
+ objectives: [
+ { id: 'improve_skills', description: 'Gain 20 skill points', target: 20, current: 0, type: 'skill' }
+ ],
+ rewards: { credits: 1600, experience: 400, gems: 16 }
+ },
+
+ // Resource and wealth weekly quests
+ {
+ id: 'weekly_wealth_collector',
+ name: 'Weekly Wealth Collector',
+ description: 'Accumulate wealth',
+ type: 'weekly',
+ difficulty: 2,
+ status: 'available',
+ objectives: [
+ { id: 'earn_credits', description: 'Earn 5000 credits', target: 5000, current: 0, type: 'credits' }
+ ],
+ rewards: { credits: 2000, experience: 300, gems: 12 }
+ },
+ {
+ id: 'weekly_resource_gatherer',
+ name: 'Weekly Resource Gathering',
+ description: 'Collect valuable resources',
+ type: 'weekly',
+ difficulty: 2,
+ status: 'available',
+ objectives: [
+ { id: 'collect_resources', description: 'Collect 500 resources', target: 500, current: 0, type: 'collection' }
+ ],
+ rewards: { credits: 1200, experience: 320, gems: 13 }
+ },
+
+ // Special activity weekly quests
+ {
+ id: 'weekly_energy_management',
+ name: 'Weekly Energy Management',
+ description: 'Use energy efficiently',
+ type: 'weekly',
+ difficulty: 1,
+ status: 'available',
+ objectives: [
+ { id: 'use_energy', description: 'Use 500 energy', target: 500, current: 0, type: 'energy' }
+ ],
+ rewards: { credits: 800, experience: 180, gems: 8 }
+ },
+ {
+ id: 'weekly_speed_demon',
+ name: 'Weekly Speed Demon',
+ description: 'Complete activities quickly',
+ type: 'weekly',
+ difficulty: 3,
+ status: 'available',
+ objectives: [
+ { id: 'speed_runs', description: 'Complete 10 speed runs', target: 10, current: 0, type: 'speedrun' }
+ ],
+ rewards: { credits: 2200, experience: 550, gems: 22 }
+ },
+ {
+ id: 'weekly_perfectionist',
+ name: 'Weekly Perfectionist',
+ description: 'Complete flawless runs',
+ type: 'weekly',
+ difficulty: 4,
+ status: 'available',
+ objectives: [
+ { id: 'perfect_runs', description: 'Complete 8 perfect runs', target: 8, current: 0, type: 'perfect' }
+ ],
+ rewards: { credits: 3000, experience: 700, gems: 30, item: 'perfection_material' }
+ },
+
+ // Multi-objective weekly quests
+ {
+ id: 'weekly_all_rounder',
+ name: 'Weekly All-Rounder',
+ description: 'Complete various activities',
+ type: 'weekly',
+ difficulty: 3,
+ status: 'available',
+ objectives: [
+ { id: 'dungeon_task', description: 'Clear 8 dungeons', target: 8, current: 0, type: 'dungeon' },
+ { id: 'combat_task', description: 'Defeat 50 enemies', target: 50, current: 0, type: 'combat' },
+ { id: 'craft_task', description: 'Craft 5 items', target: 5, current: 0, type: 'crafting' }
+ ],
+ rewards: { credits: 2500, experience: 600, gems: 25 }
+ },
+ {
+ id: 'weekly_specialist',
+ name: 'Weekly Specialist',
+ description: 'Focus on specialized activities',
+ type: 'weekly',
+ difficulty: 3,
+ status: 'available',
+ objectives: [
+ { id: 'special_dungeons', description: 'Clear 5 themed dungeons', target: 5, current: 0, type: 'themed_dungeon' },
+ { id: 'special_crafting', description: 'Craft 8 themed items', target: 8, current: 0, type: 'themed_crafting' }
+ ],
+ rewards: { credits: 2300, experience: 580, gems: 23 }
+ },
+
+ // Exploration and discovery weekly quests
+ {
+ id: 'weekly_explorer',
+ name: 'Weekly Explorer',
+ description: 'Explore new areas and content',
+ type: 'weekly',
+ difficulty: 2,
+ status: 'available',
+ objectives: [
+ { id: 'explore_areas', description: 'Explore 20 new areas', target: 20, current: 0, type: 'exploration' }
+ ],
+ rewards: { credits: 1300, experience: 340, gems: 13 }
+ },
+ {
+ id: 'weekly_discovery',
+ name: 'Weekly Discovery',
+ description: 'Discover hidden secrets',
+ type: 'weekly',
+ difficulty: 3,
+ status: 'available',
+ objectives: [
+ { id: 'discover_secrets', description: 'Discover 15 secrets', target: 15, current: 0, type: 'discovery' }
+ ],
+ rewards: { credits: 1900, experience: 480, gems: 19 }
+ },
+
+ // Endurance and challenge weekly quests
+ {
+ id: 'weekly_endurance',
+ name: 'Weekly Endurance Test',
+ description: 'Complete long-form challenges',
+ type: 'weekly',
+ difficulty: 3,
+ status: 'available',
+ objectives: [
+ { id: 'endurance_runs', description: 'Complete 5 endurance runs', target: 5, current: 0, type: 'endurance' }
+ ],
+ rewards: { credits: 2100, experience: 530, gems: 21 }
+ },
+ {
+ id: 'weekly_survivor',
+ name: 'Weekly Survivor',
+ description: 'Survive challenging conditions',
+ type: 'weekly',
+ difficulty: 4,
+ status: 'available',
+ objectives: [
+ { id: 'survival_runs', description: 'Complete 3 survival runs', target: 3, current: 0, type: 'survival' }
+ ],
+ rewards: { credits: 3200, experience: 750, gems: 32, item: 'survival_material' }
+ },
+
+ // Social and community weekly quests
+ {
+ id: 'weekly_helper',
+ name: 'Weekly Helper',
+ description: 'Assist other players',
+ type: 'weekly',
+ difficulty: 2,
+ status: 'available',
+ objectives: [
+ { id: 'assist_players', description: 'Assist 10 players', target: 10, current: 0, type: 'assist' }
+ ],
+ rewards: { credits: 1100, experience: 280, gems: 11 }
+ },
+ {
+ id: 'weekly_leader',
+ name: 'Weekly Leader',
+ description: 'Lead group activities',
+ type: 'weekly',
+ difficulty: 3,
+ status: 'available',
+ objectives: [
+ { id: 'lead_activities', description: 'Lead 5 group activities', target: 5, current: 0, type: 'leadership' }
+ ],
+ rewards: { credits: 1700, experience: 430, gems: 17 }
+ },
+
+ // Legendary weekly quests
+ {
+ id: 'weekly_legendary',
+ name: 'Weekly Legendary Challenge',
+ description: 'Complete legendary difficulty content',
+ type: 'weekly',
+ difficulty: 5,
+ status: 'available',
+ objectives: [
+ { id: 'legendary_content', description: 'Complete 3 legendary dungeons', target: 3, current: 0, type: 'legendary' }
+ ],
+ rewards: { credits: 5000, experience: 1200, gems: 50, item: 'legendary_material' }
+ },
+ {
+ id: 'weekly_mythic',
+ name: 'Weekly Mythic Trial',
+ description: 'Face mythic level challenges',
+ type: 'weekly',
+ difficulty: 5,
+ status: 'available',
+ objectives: [
+ { id: 'mythic_trials', description: 'Complete 2 mythic trials', target: 2, current: 0, type: 'mythic' }
+ ],
+ rewards: { credits: 6000, experience: 1500, gems: 60, item: 'mythic_material' }
+ }
+ ];
+
+ // Currently active daily quests (3 random from allDailyQuests)
+ this.dailyQuests = [];
+ this.selectedDailyQuests = [];
+
+ // Currently active weekly quests (5 random from allWeeklyQuests)
+ this.weeklyQuests = [];
+ this.selectedWeeklyQuests = [];
+
+ // Current active quests
+ this.activeQuests = [];
+ this.completedQuests = [];
+ this.failedQuests = [];
+ this.completedDailyQuests = []; // History of completed daily quests
+ this.completedWeeklyQuests = []; // History of completed weekly quests
+
+ // Initialize daily quests with safety check
+ try {
+ if (this.allDailyQuests && Array.isArray(this.allDailyQuests)) {
+ console.log('[QUEST SYSTEM] Initializing daily quests...');
+ this.randomizeDailyQuests();
+ } else {
+ console.warn('[QUEST SYSTEM] allDailyQuests not properly initialized, skipping daily quest initialization');
+ this.dailyQuests = [];
+ this.selectedDailyQuests = [];
+ }
+ } catch (error) {
+ console.error('[QUEST SYSTEM] Error initializing daily quests:', error);
+ // Fallback to empty arrays to prevent crash
+ this.dailyQuests = [];
+ this.selectedDailyQuests = [];
+ this.activeQuests = [];
+ }
+
+ // Initialize weekly quests with safety check
+ try {
+ if (this.allWeeklyQuests && Array.isArray(this.allWeeklyQuests)) {
+ console.log('[QUEST SYSTEM] Initializing weekly quests...');
+ this.randomizeWeeklyQuests();
+ } else {
+ console.warn('[QUEST SYSTEM] allWeeklyQuests not properly initialized, skipping weekly quest initialization');
+ this.weeklyQuests = [];
+ this.selectedWeeklyQuests = [];
+ }
+ } catch (error) {
+ console.error('[QUEST SYSTEM] Error initializing weekly quests:', error);
+ // Fallback to empty arrays to prevent crash
+ this.weeklyQuests = [];
+ this.selectedWeeklyQuests = [];
+ this.activeQuests = [];
+ }
+
+ // Procedural quest templates
+ this.proceduralTemplates = {
+ bounty: {
+ name: 'Bounty Hunt',
+ description: 'Hunt down dangerous targets in the galaxy',
+ objectives: [
+ { id: 'defeat_targets', description: 'Defeat {target} targets', target: 5, current: 0, type: 'combat' }
+ ],
+ rewards: { credits: 300, experience: 75 }
+ },
+ exploration: {
+ name: 'Exploration Mission',
+ description: 'Explore uncharted regions of space',
+ objectives: [
+ { id: 'explore_areas', description: 'Explore {target} areas', target: 3, current: 0, type: 'exploration' }
+ ],
+ rewards: { credits: 250, experience: 60 }
+ },
+ collection: {
+ name: 'Resource Collection',
+ description: 'Collect valuable resources',
+ objectives: [
+ { id: 'collect_resources', description: 'Collect {target} resources', target: 100, current: 0, type: 'collection' }
+ ],
+ rewards: { credits: 200, experience: 50 }
+ },
+ escort: {
+ name: 'Escort Mission',
+ description: 'Escort valuable cargo through dangerous space',
+ objectives: [
+ { id: 'escort_complete', description: 'Complete escort mission', target: 1, current: 0, type: 'escort' }
+ ],
+ rewards: { credits: 400, experience: 100 }
+ }
+ };
+
+ // Quest generation settings
+ this.maxProceduralQuests = 3;
+ this.proceduralQuestRefresh = 30 * 60 * 1000; // 30 minutes
+
+ // Statistics
+ this.stats = {
+ questsCompleted: 0,
+ dailyQuestsCompleted: 0,
+ weeklyQuestsCompleted: 0,
+ totalRewardsEarned: { credits: 0, experience: 0, gems: 0 },
+ lastDailyReset: Date.now(),
+ lastWeeklyReset: Date.now()
+ };
+
+ // Initialize daily quests
+ this.randomizeDailyQuests();
+
+ // Initialize weekly quests
+ this.randomizeWeeklyQuests();
+
+ if (debugLogger) debugLogger.endStep('QuestSystem.constructor', {
+ mainQuestsCount: this.mainQuests.length,
+ allDailyQuestsCount: this.allDailyQuests.length,
+ allWeeklyQuestsCount: this.allWeeklyQuests.length,
+ maxProceduralQuests: this.maxProceduralQuests,
+ proceduralQuestRefresh: this.proceduralQuestRefresh,
+ initialStats: this.stats,
+ dailyQuestsInitialized: this.dailyQuests.length,
+ weeklyQuestsInitialized: this.weeklyQuests.length
+ });
+ }
+
+ async initialize() {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('QuestSystem.initialize', {
+ mainQuestsCount: this.mainQuests.length,
+ activeQuestsCount: this.activeQuests.length,
+ dailyQuestsCount: this.dailyQuests.length
+ });
+
+ // Initialize main quests
+ if (debugLogger) debugLogger.logStep('Initializing main quests');
+ this.initializeMainQuests();
+
+ // Check for daily reset
+ if (debugLogger) debugLogger.logStep('Checking for daily reset');
+ this.checkDailyReset();
+
+ // Check for weekly reset
+ if (debugLogger) debugLogger.logStep('Checking for weekly reset');
+ this.checkWeeklyReset();
+
+ // Start daily countdown timer
+ if (debugLogger) debugLogger.logStep('Starting daily countdown timer');
+ this.startDailyCountdown();
+
+ // Start weekly countdown timer
+ if (debugLogger) debugLogger.logStep('Starting weekly countdown timer');
+ this.startWeeklyCountdown();
+
+ // Update UI
+ this.updateQuestList();
+
+ if (debugLogger) debugLogger.endStep('QuestSystem.initialize', {
+ mainQuestsInitialized: true,
+ dailyResetChecked: true,
+ weeklyResetChecked: true,
+ countdownStarted: true,
+ weeklyQuestsInitialized: true,
+ uiUpdated: true
+ });
+ }
+
+ initializeMainQuests() {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('QuestSystem.initializeMainQuests', {
+ mainQuestsCount: this.mainQuests.length
+ });
+
+ // Set all quests to locked initially, then unlock based on requirements
+ this.mainQuests.forEach(quest => {
+ quest.status = 'locked';
+ });
+
+ if (debugLogger) debugLogger.logStep('All main quests set to locked status');
+
+ // Unlock first quest
+ if (this.mainQuests.length > 0) {
+ const firstQuest = this.mainQuests[0];
+ firstQuest.status = 'available';
+
+ if (debugLogger) debugLogger.logStep('First quest unlocked', {
+ questId: firstQuest.id,
+ questName: firstQuest.name
+ });
+ }
+
+ // Check quest availability based on requirements
+ if (debugLogger) debugLogger.logStep('Checking quest availability based on requirements');
+ this.checkQuestAvailability();
+
+ if (debugLogger) debugLogger.endStep('QuestSystem.initializeMainQuests', {
+ questsProcessed: this.mainQuests.length,
+ availableQuests: this.mainQuests.filter(q => q.status === 'available').length,
+ lockedQuests: this.mainQuests.filter(q => q.status === 'locked').length
+ });
+ }
+
+ checkQuestAvailability() {
+ const debugLogger = window.debugLogger;
+ const player = this.game.systems.player;
+
+ if (debugLogger) debugLogger.startStep('QuestSystem.checkQuestAvailability', {
+ playerLevel: player.stats.level,
+ mainQuestsCount: this.mainQuests.length
+ });
+
+ const availabilityChanges = [];
+
+ this.mainQuests.forEach(quest => {
+ const oldStatus = quest.status;
+ const requirementsMet = this.checkQuestRequirements(quest);
+
+ if (quest.status === 'locked' && requirementsMet) {
+ quest.status = 'available';
+ availabilityChanges.push({
+ questId: quest.id,
+ questName: quest.name,
+ oldStatus: oldStatus,
+ newStatus: 'available',
+ reason: 'Requirements met'
+ });
+ this.game.showNotification(`New quest available: ${quest.name}`, 'info', 5000);
+ } else if (quest.status === 'available' && !requirementsMet) {
+ // Hide quests that were available but no longer meet requirements
+ quest.status = 'locked';
+ availabilityChanges.push({
+ questId: quest.id,
+ questName: quest.name,
+ oldStatus: oldStatus,
+ newStatus: 'locked',
+ reason: 'Requirements no longer met'
+ });
+ }
+ });
+
+ if (debugLogger) debugLogger.endStep('QuestSystem.checkQuestAvailability', {
+ availabilityChanges: availabilityChanges,
+ questsProcessed: this.mainQuests.length,
+ availableQuests: this.mainQuests.filter(q => q.status === 'available').length,
+ lockedQuests: this.mainQuests.filter(q => q.status === 'locked').length
+ });
+ }
+
+ // Quest management
+ startQuest(questId) {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('QuestSystem.startQuest', {
+ questId: questId,
+ currentActiveQuests: this.activeQuests.length
+ });
+
+ const quest = this.findQuest(questId);
+ if (!quest) {
+ console.log('Quest not found:', questId);
+
+ if (debugLogger) debugLogger.endStep('QuestSystem.startQuest', {
+ success: false,
+ reason: 'Quest not found',
+ questId: questId
+ });
+ return false;
+ }
+
+ console.log('Attempting to start quest:', questId, 'status:', quest.status);
+
+ if (debugLogger) debugLogger.logStep('Quest found', {
+ questId: quest.id,
+ questName: quest.name,
+ questType: quest.type,
+ currentStatus: quest.status,
+ requirements: quest.requirements
+ });
+
+ if (quest.status !== 'available') {
+ console.log('Quest not available, status:', quest.status);
+ this.game.showNotification('Quest is not available', 'error', 3000);
+
+ if (debugLogger) debugLogger.endStep('QuestSystem.startQuest', {
+ success: false,
+ reason: 'Quest not available',
+ questId: questId,
+ questName: quest.name,
+ currentStatus: quest.status
+ });
+ return false;
+ }
+
+ // Check requirements
+ const requirementsMet = this.checkQuestRequirements(quest);
+ console.log('Requirements met:', requirementsMet, 'for quest:', questId);
+
+ if (debugLogger) debugLogger.logStep('Requirements check', {
+ requirements: quest.requirements,
+ requirementsMet: requirementsMet
+ });
+
+ if (!requirementsMet) {
+ this.game.showNotification('Requirements not met', 'error', 3000);
+
+ if (debugLogger) debugLogger.endStep('QuestSystem.startQuest', {
+ success: false,
+ reason: 'Requirements not met',
+ questId: questId,
+ questName: quest.name,
+ requirements: quest.requirements
+ });
+ return false;
+ }
+
+ console.log('Before status change - quest status:', quest.status);
+ quest.status = 'active';
+ console.log('After status change - quest status:', quest.status);
+
+ // Also update the quest in the main quests array to ensure consistency
+ const mainQuest = this.mainQuests.find(q => q.id === questId);
+ if (mainQuest) {
+ mainQuest.status = 'active';
+ console.log('Updated mainQuest status to:', mainQuest.status);
+
+ if (debugLogger) debugLogger.logStep('Main quest status updated', {
+ questId: mainQuest.id,
+ newStatus: mainQuest.status
+ });
+ }
+
+ this.activeQuests.push(quest);
+
+ // Check initial progress for existing player stats
+ if (debugLogger) debugLogger.logStep('Checking initial quest progress');
+ this.checkInitialQuestProgress(quest);
+
+ // Update the UI to reflect the status change
+ this.updateQuestList();
+
+ this.game.showNotification(`Quest started: ${quest.name}`, 'success', 3000);
+
+ if (debugLogger) debugLogger.endStep('QuestSystem.startQuest', {
+ success: true,
+ questId: questId,
+ questName: quest.name,
+ questType: quest.type,
+ objectives: quest.objectives.map(obj => ({
+ id: obj.id,
+ description: obj.description,
+ target: obj.target,
+ current: obj.current
+ })),
+ activeQuestsCount: this.activeQuests.length
+ });
+
+ return true;
+ }
+
+ completeQuest(questId) {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('QuestSystem.completeQuest', {
+ questId: questId,
+ currentActiveQuests: this.activeQuests.length,
+ completedQuestsCount: this.completedQuests.length
+ });
+
+ const quest = this.findQuest(questId);
+ if (!quest) {
+ if (debugLogger) debugLogger.endStep('QuestSystem.completeQuest', {
+ success: false,
+ reason: 'Quest not found',
+ questId: questId
+ });
+ return false;
+ }
+
+ if (debugLogger) debugLogger.logStep('Quest found for completion', {
+ questId: quest.id,
+ questName: quest.name,
+ questType: quest.type,
+ currentStatus: quest.status,
+ objectives: quest.objectives.map(obj => ({
+ id: obj.id,
+ description: obj.description,
+ target: obj.target,
+ current: obj.current,
+ completed: obj.current >= obj.target
+ }))
+ });
+
+ if (quest.status !== 'active') {
+ this.game.showNotification('Quest is not active', 'error', 3000);
+
+ if (debugLogger) debugLogger.endStep('QuestSystem.completeQuest', {
+ success: false,
+ reason: 'Quest not active',
+ questId: questId,
+ questName: quest.name,
+ currentStatus: quest.status
+ });
+ return false;
+ }
+
+ // Check if all objectives are completed
+ const allObjectivesComplete = quest.objectives.every(obj => obj.current >= obj.target);
+ if (!allObjectivesComplete) {
+ this.game.showNotification('Not all objectives completed', 'warning', 3000);
+
+ if (debugLogger) debugLogger.endStep('QuestSystem.completeQuest', {
+ success: false,
+ reason: 'Not all objectives completed',
+ questId: questId,
+ questName: quest.name,
+ objectives: quest.objectives.map(obj => ({
+ id: obj.id,
+ current: obj.current,
+ target: obj.target,
+ completed: obj.current >= obj.target
+ }))
+ });
+ return false;
+ }
+
+ // Complete quest
+ quest.status = 'completed';
+ quest.completedAt = Date.now();
+ this.completedQuests.push(quest);
+
+ if (debugLogger) debugLogger.logStep('Quest marked as completed', {
+ questId: quest.id,
+ questName: quest.name,
+ completedAt: quest.completedAt,
+ rewards: quest.rewards
+ });
+
+ // Save completed daily quests to history
+ if (quest.type === 'daily') {
+ const questCopy = { ...quest, completedAt: Date.now() };
+ this.completedDailyQuests.push(questCopy);
+
+ if (debugLogger) debugLogger.logStep('Daily quest added to history', {
+ questId: quest.id,
+ questName: quest.name,
+ completedDailyQuestsCount: this.completedDailyQuests.length
+ });
+ }
+
+ // Save completed weekly quests to history
+ if (quest.type === 'weekly') {
+ const questCopy = { ...quest, completedAt: Date.now() };
+ this.completedWeeklyQuests.push(questCopy);
+
+ if (debugLogger) debugLogger.logStep('Weekly quest added to history', {
+ questId: quest.id,
+ questName: quest.name,
+ completedWeeklyQuestsCount: this.completedWeeklyQuests.length
+ });
+ }
+
+ // Remove from active quests
+ const activeIndex = this.activeQuests.findIndex(q => q.id === questId);
+ if (activeIndex !== -1) {
+ this.activeQuests.splice(activeIndex, 1);
+
+ if (debugLogger) debugLogger.logStep('Quest removed from active list', {
+ questId: questId,
+ removedIndex: activeIndex,
+ remainingActiveQuests: this.activeQuests.length
+ });
+ }
+
+ // Give rewards
+ if (debugLogger) debugLogger.logStep('Giving quest rewards');
+ this.giveQuestRewards(quest);
+
+ // Update statistics
+ const oldStats = { ...this.stats };
+ this.stats.questsCompleted++;
+ if (quest.type === 'daily') {
+ this.stats.dailyQuestsCompleted++;
+ }
+ if (quest.type === 'weekly') {
+ this.stats.weeklyQuestsCompleted++;
+ }
+
+ if (debugLogger) debugLogger.logStep('Quest statistics updated', {
+ oldStats: oldStats,
+ newStats: this.stats,
+ questsCompletedIncrement: this.stats.questsCompleted - oldStats.questsCompleted,
+ dailyQuestsCompletedIncrement: this.stats.dailyQuestsCompleted - oldStats.dailyQuestsCompleted,
+ weeklyQuestsCompletedIncrement: this.stats.weeklyQuestsCompleted - oldStats.weeklyQuestsCompleted
+ });
+
+ // Unlock next quest if it's a main quest
+ if (quest.nextQuest) {
+ if (debugLogger) debugLogger.logStep('Unlocking next quest', {
+ nextQuestId: quest.nextQuest
+ });
+ this.unlockNextQuest(quest.nextQuest);
+ }
+
+ // Check for other quests that might now be available
+ if (debugLogger) debugLogger.logStep('Checking for newly available quests');
+ this.checkQuestAvailability();
+
+ this.game.showNotification(`Quest completed: ${quest.name}!`, 'success', 5000);
+
+ if (debugLogger) debugLogger.endStep('QuestSystem.completeQuest', {
+ success: true,
+ questId: questId,
+ questName: quest.name,
+ questType: quest.type,
+ rewardsGiven: quest.rewards,
+ nextQuestUnlocked: quest.nextQuest || null,
+ finalActiveQuestsCount: this.activeQuests.length,
+ completedQuestsCount: this.completedQuests.length
+ });
+
+ return true;
+ }
+
+ giveQuestRewards(quest) {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('QuestSystem.giveQuestRewards', {
+ questId: quest.id,
+ questName: quest.name,
+ questType: quest.type,
+ rewards: quest.rewards
+ });
+
+ const oldPlayerStats = {
+ credits: this.game.systems.economy.credits,
+ gems: this.game.systems.economy.gems,
+ experience: this.game.systems.player.stats.experience,
+ level: this.game.systems.player.stats.level
+ };
+
+ const rewardsGiven = {};
+
+ if (quest.rewards.credits) {
+ this.game.systems.economy.addCredits(quest.rewards.credits, 'quest');
+ this.stats.totalRewardsEarned.credits += quest.rewards.credits;
+ rewardsGiven.credits = quest.rewards.credits;
+
+ if (debugLogger) debugLogger.logStep('Credits reward given', {
+ amount: quest.rewards.credits,
+ oldCredits: oldPlayerStats.credits,
+ newCredits: this.game.systems.economy.credits
+ });
+ }
+
+ if (quest.rewards.experience) {
+ this.game.systems.player.addExperience(quest.rewards.experience);
+ this.stats.totalRewardsEarned.experience += quest.rewards.experience;
+ rewardsGiven.experience = quest.rewards.experience;
+
+ if (debugLogger) debugLogger.logStep('Experience reward given', {
+ amount: quest.rewards.experience,
+ oldExperience: oldPlayerStats.experience,
+ newExperience: this.game.systems.player.stats.experience,
+ oldLevel: oldPlayerStats.level,
+ newLevel: this.game.systems.player.stats.level,
+ leveledUp: this.game.systems.player.stats.level > oldPlayerStats.level
+ });
+ }
+
+ if (quest.rewards.gems) {
+ this.game.systems.economy.addGems(quest.rewards.gems, 'quest');
+ this.stats.totalRewardsEarned.gems += quest.rewards.gems;
+ rewardsGiven.gems = quest.rewards.gems;
+
+ if (debugLogger) debugLogger.logStep('Gems reward given', {
+ amount: quest.rewards.gems,
+ oldGems: oldPlayerStats.gems,
+ newGems: this.game.systems.economy.gems
+ });
+ }
+
+ if (quest.rewards.item) {
+ const item = this.game.systems.inventory.generateItem('weapon', 'legendary');
+ this.game.systems.inventory.addItem(item);
+ rewardsGiven.item = item;
+
+ if (debugLogger) debugLogger.logStep('Item reward given', {
+ itemGenerated: item,
+ itemType: item.type,
+ itemRarity: item.rarity,
+ itemName: item.name
+ });
+ }
+
+ if (debugLogger) debugLogger.endStep('QuestSystem.giveQuestRewards', {
+ questId: quest.id,
+ questName: quest.name,
+ rewardsGiven: rewardsGiven,
+ playerChanges: {
+ credits: { old: oldPlayerStats.credits, new: this.game.systems.economy.credits },
+ gems: { old: oldPlayerStats.gems, new: this.game.systems.economy.gems },
+ experience: { old: oldPlayerStats.experience, new: this.game.systems.player.stats.experience },
+ level: { old: oldPlayerStats.level, new: this.game.systems.player.stats.level }
+ },
+ totalRewardsEarnedUpdated: this.stats.totalRewardsEarned
+ });
+ }
+
+ // Objective progress
+ updateObjectiveProgress(type, amount, context = {}) {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('QuestSystem.updateObjectiveProgress', {
+ type: type,
+ amount: amount,
+ context: context,
+ activeQuestsCount: this.activeQuests.length
+ });
+
+ const progressUpdates = [];
+
+ // Update all active quests
+ this.activeQuests.forEach(quest => {
+ const questProgress = [];
+
+ quest.objectives.forEach(objective => {
+ if (objective.type === type && objective.current < objective.target) {
+ const oldProgress = objective.current;
+ objective.current = Math.min(objective.current + amount, objective.target);
+ const actualProgress = objective.current - oldProgress;
+
+ if (actualProgress > 0) {
+ questProgress.push({
+ objectiveId: objective.id,
+ description: objective.description,
+ oldProgress: oldProgress,
+ newProgress: objective.current,
+ target: objective.target,
+ progressMade: actualProgress,
+ completed: objective.current >= objective.target
+ });
+ }
+ }
+ });
+
+ if (questProgress.length > 0) {
+ progressUpdates.push({
+ questId: quest.id,
+ questName: quest.name,
+ questType: quest.type,
+ objectivesUpdated: questProgress
+ });
+
+ // Check if quest is completed
+ if (quest.objectives.every(obj => obj.current >= obj.target)) {
+ this.game.showNotification(`Quest ready to turn in: ${quest.name}`, 'success', 4000);
+
+ if (debugLogger) debugLogger.logStep('Quest completed via progress update', {
+ questId: quest.id,
+ questName: quest.name,
+ allObjectivesCompleted: true
+ });
+ }
+ }
+ });
+
+ if (debugLogger) debugLogger.endStep('QuestSystem.updateObjectiveProgress', {
+ type: type,
+ amount: amount,
+ progressUpdates: progressUpdates,
+ questsUpdated: progressUpdates.length
+ });
+ }
+
+ // Event listeners for quest progress
+ onDungeonCompleted() {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.log('QuestSystem.onDungeonCompleted triggered', {
+ activeQuestsCount: this.activeQuests.length
+ });
+
+ this.updateObjectiveProgress('dungeon', 1);
+ }
+
+ onEnemyDefeated() {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.log('QuestSystem.onEnemyDefeated triggered', {
+ activeQuestsCount: this.activeQuests.length
+ });
+
+ this.updateObjectiveProgress('combat', 1);
+ }
+
+ onLevelUp(newLevel) {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.log('QuestSystem.onLevelUp triggered', {
+ newLevel: newLevel,
+ activeQuestsCount: this.activeQuests.length
+ });
+
+ this.updateObjectiveProgress('level', 1);
+ }
+
+ onItemCrafted() {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.log('QuestSystem.onItemCrafted triggered', {
+ activeQuestsCount: this.activeQuests.length
+ });
+
+ this.updateObjectiveProgress('crafting', 1);
+ }
+
+ // Procedural quest generation (deprecated - replaced by weekly quests)
+ /*
+ generateProceduralQuests() {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('QuestSystem.generateProceduralQuests', {
+ currentActiveQuests: this.activeQuests.length,
+ maxProceduralQuests: this.maxProceduralQuests,
+ proceduralTemplatesCount: Object.keys(this.proceduralTemplates).length,
+ proceduralQuestRefresh: this.proceduralQuestRefresh
+ });
+
+ // Clear existing procedural quests
+ const oldProceduralQuests = this.activeQuests.filter(q => q.type === 'procedural');
+ this.activeQuests = this.activeQuests.filter(q => q.type !== 'procedural');
+
+ if (debugLogger) debugLogger.logStep('Cleared existing procedural quests', {
+ oldProceduralQuestsCount: oldProceduralQuests.length,
+ oldProceduralQuests: oldProceduralQuests.map(q => ({ id: q.id, name: q.name }))
+ });
+
+ // Generate new procedural quests
+ const templates = Object.keys(this.proceduralTemplates);
+ const questCount = Math.min(this.maxProceduralQuests, templates.length);
+ const generatedQuests = [];
+
+ for (let i = 0; i < questCount; i++) {
+ const templateKey = templates[Math.floor(Math.random() * templates.length)];
+ const template = this.proceduralTemplates[templateKey];
+
+ const quest = {
+ id: `procedural_${Date.now()}_${i}`,
+ name: template.name,
+ description: template.description.replace('{target}', template.objectives[0].target),
+ type: 'procedural',
+ status: 'available',
+ objectives: template.objectives.map(obj => ({ ...obj })),
+ rewards: { ...template.rewards },
+ expiresAt: Date.now() + this.proceduralQuestRefresh
+ };
+
+ this.activeQuests.push(quest);
+ generatedQuests.push(quest);
+
+ if (debugLogger) debugLogger.logStep('Procedural quest generated', {
+ templateKey: templateKey,
+ questId: quest.id,
+ questName: quest.name,
+ questDescription: quest.description,
+ objectives: quest.objectives,
+ rewards: quest.rewards,
+ expiresAt: quest.expiresAt
+ });
+ }
+
+ // Schedule next refresh
+ setTimeout(() => this.generateProceduralQuests(), this.proceduralQuestRefresh);
+
+ if (debugLogger) debugLogger.endStep('QuestSystem.generateProceduralQuests', {
+ oldProceduralQuestsRemoved: oldProceduralQuests.length,
+ newProceduralQuestsGenerated: generatedQuests.length,
+ generatedQuests: generatedQuests.map(q => ({ id: q.id, name: q.name, type: q.type })),
+ nextRefreshScheduled: true,
+ refreshInterval: this.proceduralQuestRefresh
+ });
+ }
+ */
+
+ randomizeDailyQuests() {
+ const debugLogger = window.debugLogger;
+
+ console.log('[QUEST SYSTEM] randomizeDailyQuests called');
+
+ // Safety check for allDailyQuests
+ if (!this.allDailyQuests || !Array.isArray(this.allDailyQuests)) {
+ console.error('[QUEST SYSTEM] allDailyQuests is not available or not an array');
+ return;
+ }
+
+ console.log(`[QUEST SYSTEM] allDailyQuests count: ${this.allDailyQuests.length}`);
+
+ if (debugLogger) debugLogger.startStep('QuestSystem.randomizeDailyQuests', {
+ allDailyQuestsCount: this.allDailyQuests.length,
+ currentDailyQuestsCount: this.dailyQuests.length,
+ currentSelectedDailyQuestsCount: this.selectedDailyQuests.length
+ });
+
+ // Clear current daily quests
+ this.dailyQuests = [];
+ this.selectedDailyQuests = [];
+
+ if (debugLogger) debugLogger.logStep('Cleared current daily quests');
+
+ // Select 3 random quests from allDailyQuests
+ const shuffled = [...this.allDailyQuests].sort(() => Math.random() - 0.5);
+ this.selectedDailyQuests = shuffled.slice(0, 3);
+
+ console.log(`[QUEST SYSTEM] Selected ${this.selectedDailyQuests.length} random daily quests`);
+
+ if (debugLogger) debugLogger.logStep('Selected random daily quests', {
+ selectedQuests: this.selectedDailyQuests.map(q => ({
+ id: q.id,
+ name: q.name,
+ difficulty: q.difficulty
+ }))
+ });
+
+ // Create deep copies for active quests and automatically start them
+ this.selectedDailyQuests.forEach(questTemplate => {
+ try {
+ const quest = {
+ ...questTemplate,
+ id: `${questTemplate.id}_${Date.now()}`,
+ status: 'active', // Auto-start daily quests
+ objectives: questTemplate.objectives.map(obj => ({ ...obj, current: 0 }))
+ };
+ this.dailyQuests.push(quest);
+ this.activeQuests.push(quest); // Add to active quests for progress tracking
+
+ console.log(`[QUEST SYSTEM] Created daily quest: ${quest.id}, name: ${quest.name}, status: ${quest.status}`);
+ } catch (error) {
+ console.error('[QUEST SYSTEM] Error creating daily quest from template:', error);
+ }
+ });
+
+ if (debugLogger) debugLogger.endStep('QuestSystem.randomizeDailyQuests', {
+ dailyQuestsCreated: this.dailyQuests.length,
+ activeQuestsCount: this.activeQuests.length,
+ selectedDailyQuestsCount: this.selectedDailyQuests.length
+ });
+ }
+
+ // Daily reset
+ reset() {
+ const debugLogger = window.debugLogger;
+ const oldState = {
+ activeQuestsCount: this.activeQuests.length,
+ completedQuestsCount: this.completedQuests.length,
+ dailyQuestsCount: this.dailyQuests.length,
+ selectedDailyQuestsCount: this.selectedDailyQuests.length
+ };
+
+ if (debugLogger) debugLogger.startStep('QuestSystem.reset', {
+ oldState: oldState
+ });
+
+ this.activeQuests = [];
+ this.completedQuests = [];
+ this.dailyQuests = [];
+ this.selectedDailyQuests = [];
+ this.lastDailyReset = Date.now();
+
+ // Reset main quest statuses
+ this.mainQuests.forEach(quest => {
+ quest.status = quest.id === 'tutorial_complete' ? 'available' : 'locked';
+ });
+
+ if (debugLogger) debugLogger.endStep('QuestSystem.reset', {
+ oldState: oldState,
+ newState: {
+ activeQuestsCount: this.activeQuests.length,
+ completedQuestsCount: this.completedQuests.length,
+ dailyQuestsCount: this.dailyQuests.length,
+ selectedDailyQuestsCount: this.selectedDailyQuests.length,
+ mainQuestsReset: true
+ }
+ });
+ }
+
+ clear() {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('QuestSystem.clear');
+
+ this.reset();
+
+ if (debugLogger) debugLogger.endStep('QuestSystem.clear', {
+ resetCompleted: true
+ });
+ }
+
+ checkDailyReset() {
+ const debugLogger = window.debugLogger;
+ const now = Date.now();
+ const lastReset = this.lastDailyReset;
+ const daysSinceReset = Math.floor((now - lastReset) / (24 * 60 * 60 * 1000));
+
+ if (debugLogger) debugLogger.startStep('QuestSystem.checkDailyReset', {
+ now: now,
+ lastReset: lastReset,
+ daysSinceReset: daysSinceReset,
+ threshold: 1
+ });
+
+ if (daysSinceReset >= 1) {
+ if (debugLogger) debugLogger.logStep('Daily reset triggered', {
+ daysSinceReset: daysSinceReset
+ });
+
+ this.resetDailyQuests();
+ this.stats.lastDailyReset = now;
+ this.stats.dailyQuestsCompleted = 0;
+
+ this.game.showNotification('Daily quests refreshed!', 'success', 4000);
+
+ if (debugLogger) debugLogger.logStep('Daily reset completed', {
+ newLastDailyReset: this.stats.lastDailyReset,
+ dailyQuestsCompletedReset: this.stats.dailyQuestsCompleted
+ });
+ }
+
+ // Remove only COMPLETED daily quests from active list, not all daily quests
+ const oldActiveQuestsCount = this.activeQuests.length;
+ this.activeQuests = this.activeQuests.filter(q => q.type !== 'daily' || q.status !== 'completed');
+ const removedDailyQuests = oldActiveQuestsCount - this.activeQuests.length;
+
+ if (debugLogger) debugLogger.endStep('QuestSystem.checkDailyReset', {
+ resetTriggered: daysSinceReset >= 1,
+ removedDailyQuests: removedDailyQuests,
+ finalActiveQuestsCount: this.activeQuests.length
+ });
+ }
+
+ resetDailyQuests() {
+ // Remove old daily quests from active list first
+ this.activeQuests = this.activeQuests.filter(q => q.type !== 'daily');
+
+ // Generate new random daily quests
+ this.randomizeDailyQuests();
+ }
+
+ startDailyCountdown() {
+ console.log('[QUEST SYSTEM] Starting daily countdown timer');
+
+ // Update countdown immediately
+ this.updateDailyCountdown();
+
+ // Update every second
+ this.dailyCountdownInterval = setInterval(() => {
+ this.updateDailyCountdown();
+ }, 1000);
+
+ console.log('[QUEST SYSTEM] Daily countdown timer started with interval:', this.dailyCountdownInterval);
+ }
+
+ updateDailyCountdown() {
+ // Always update countdown so it's ready when user switches to daily tab
+ const now = new Date();
+ const tomorrow = new Date(now);
+ tomorrow.setDate(tomorrow.getDate() + 1);
+ tomorrow.setHours(0, 0, 0, 0); // Set to midnight
+
+ const timeUntilReset = tomorrow - now;
+ const hours = Math.floor(timeUntilReset / (1000 * 60 * 60));
+ const minutes = Math.floor((timeUntilReset % (1000 * 60 * 60)) / (1000 * 60));
+ const seconds = Math.floor((timeUntilReset % (1000 * 60)) / 1000);
+
+ // Update countdown display
+ const countdownElement = document.getElementById('dailyCountdown');
+ if (countdownElement) {
+ countdownElement.textContent = `Daily quests reset in: ${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
+ console.log('[QUEST SYSTEM] Daily countdown updated:', countdownElement.textContent);
+ } else {
+ console.log('[QUEST SYSTEM] Daily countdown element not found');
+ }
+ }
+
+ // Weekly quest management functions
+ randomizeWeeklyQuests() {
+ const debugLogger = window.debugLogger;
+
+ console.log('[QUEST SYSTEM] randomizeWeeklyQuests called');
+
+ // Safety check for allWeeklyQuests
+ if (!this.allWeeklyQuests || !Array.isArray(this.allWeeklyQuests)) {
+ console.error('[QUEST SYSTEM] allWeeklyQuests not properly initialized');
+ this.weeklyQuests = [];
+ this.selectedWeeklyQuests = [];
+ return;
+ }
+
+ console.log(`[QUEST SYSTEM] allWeeklyQuests count: ${this.allWeeklyQuests.length}`);
+
+ if (debugLogger) debugLogger.startStep('QuestSystem.randomizeWeeklyQuests', {
+ allWeeklyQuestsCount: this.allWeeklyQuests.length,
+ currentWeeklyQuestsCount: this.weeklyQuests.length,
+ currentSelectedWeeklyQuestsCount: this.selectedWeeklyQuests.length
+ });
+
+ // Clear existing weekly quests from active quests
+ this.activeQuests = this.activeQuests.filter(q => q.type !== 'weekly');
+
+ // Select 5 random weekly quests
+ const shuffled = [...this.allWeeklyQuests].sort(() => Math.random() - 0.5);
+ this.selectedWeeklyQuests = shuffled.slice(0, 5);
+ this.weeklyQuests = this.selectedWeeklyQuests.map(quest => ({ ...quest }));
+
+ // Add weekly quests to active quests
+ this.weeklyQuests.forEach(quest => {
+ if (quest.status === 'available') {
+ this.activeQuests.push(quest);
+ }
+ });
+
+ console.log(`[QUEST SYSTEM] Created ${this.weeklyQuests.length} weekly quests`);
+
+ if (debugLogger) debugLogger.endStep('QuestSystem.randomizeWeeklyQuests', {
+ weeklyQuestsCreated: this.weeklyQuests.length,
+ activeQuestsCount: this.activeQuests.length,
+ selectedWeeklyQuestsCount: this.selectedWeeklyQuests.length
+ });
+ }
+
+ checkWeeklyReset() {
+ const debugLogger = window.debugLogger;
+ const now = Date.now();
+ const lastReset = this.lastWeeklyReset || 0;
+
+ // Calculate if we need to reset based on Saturday midnight
+ const currentDateTime = new Date();
+ const dayOfWeek = currentDateTime.getDay(); // 0 = Sunday, 1 = Monday, ..., 6 = Saturday
+ const currentHour = currentDateTime.getHours();
+ const currentMinute = currentDateTime.getMinutes();
+ const currentSecond = currentDateTime.getSeconds();
+
+ let shouldReset = false;
+ let resetTime;
+
+ if (dayOfWeek === 6) { // Today is Saturday
+ resetTime = new Date(currentDateTime);
+ resetTime.setHours(0, 0, 0, 0); // Set to midnight
+
+ // Reset at exactly midnight on Saturday
+ if (currentHour === 0 && currentMinute === 0 && currentSecond === 0 && lastReset < resetTime.getTime()) {
+ shouldReset = true;
+ }
+ } else {
+ // Calculate last Saturday's reset time (midnight)
+ const daysSinceLastSaturday = (dayOfWeek + 1) % 7 || 7;
+ const lastSaturday = new Date(currentDateTime);
+ lastSaturday.setDate(currentDateTime.getDate() - daysSinceLastSaturday);
+ lastSaturday.setHours(0, 0, 0, 0);
+ resetTime = lastSaturday;
+
+ if (lastReset < resetTime.getTime()) {
+ shouldReset = true;
+ }
+ }
+
+ if (debugLogger) debugLogger.startStep('QuestSystem.checkWeeklyReset', {
+ now: now,
+ lastReset: lastReset,
+ currentDayOfWeek: dayOfWeek,
+ currentHour: currentHour,
+ currentMinute: currentMinute,
+ resetTime: resetTime ? resetTime.getTime() : null,
+ shouldReset: shouldReset
+ });
+
+ if (shouldReset) {
+ console.log('[QUEST SYSTEM] Weekly reset triggered');
+
+ // Reset weekly quests
+ this.lastWeeklyReset = now;
+
+ // Remove completed weekly quests from active quests
+ const oldActiveQuestsCount = this.activeQuests.length;
+ this.activeQuests = this.activeQuests.filter(q => q.type !== 'weekly' || q.status !== 'completed');
+ const removedWeeklyQuests = oldActiveQuestsCount - this.activeQuests.length;
+
+ if (debugLogger) debugLogger.endStep('QuestSystem.checkWeeklyReset', {
+ resetTriggered: true,
+ removedWeeklyQuests: removedWeeklyQuests,
+ finalActiveQuestsCount: this.activeQuests.length
+ });
+
+ // Clear weekly quest history and generate new ones
+ this.completedWeeklyQuests = [];
+ this.activeQuests = this.activeQuests.filter(q => q.type !== 'weekly');
+
+ // Generate new random weekly quests
+ this.randomizeWeeklyQuests();
+
+ // Update UI to show new weekly quests
+ this.updateQuestList();
+
+ // Show notification to player
+ this.game.showNotification('Weekly quests have been reset! New quests available.', 'info', 5000);
+ }
+ }
+
+ startWeeklyCountdown() {
+ console.log('[QUEST SYSTEM] Starting weekly countdown timer');
+
+ // Update countdown immediately
+ this.updateWeeklyCountdown();
+
+ // Update every minute (weekly changes less frequently)
+ this.weeklyCountdownInterval = setInterval(() => {
+ this.updateWeeklyCountdown();
+ // Check for weekly reset every minute
+ this.checkWeeklyReset();
+ }, 60000); // Update every minute
+
+ console.log('[QUEST SYSTEM] Weekly countdown timer started with interval:', this.weeklyCountdownInterval);
+ }
+
+ updateWeeklyCountdown() {
+ const now = new Date();
+ const dayOfWeek = now.getDay(); // 0 = Sunday, 1 = Monday, ..., 6 = Saturday
+ const currentHour = now.getHours();
+ const currentMinute = now.getMinutes();
+ const currentSecond = now.getSeconds();
+
+ console.log('[QUEST SYSTEM] Weekly countdown debug:', {
+ now: now.toString(),
+ dayOfWeek: dayOfWeek,
+ dayName: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'][dayOfWeek],
+ currentHour: currentHour,
+ currentMinute: currentMinute,
+ currentSecond: currentSecond
+ });
+
+ // Calculate days until Saturday midnight (00:00:00)
+ let daysUntilSaturday;
+ if (dayOfWeek === 6) { // Today is Saturday
+ // Check if it's before midnight
+ if (now.getHours() < 0 || (now.getHours() === 0 && now.getMinutes() === 0 && now.getSeconds() === 0)) {
+ daysUntilSaturday = 0;
+ } else {
+ // It's after midnight on Saturday, next reset is next Saturday
+ daysUntilSaturday = 7;
+ }
+ } else {
+ // Simple calculation: days until Saturday = 6 - current day
+ // Sunday (0) -> 6 days, Monday (1) -> 5 days, ..., Friday (5) -> 1 day
+ daysUntilSaturday = 6 - dayOfWeek;
+ if (daysUntilSaturday <= 0) daysUntilSaturday += 7; // Ensure positive
+ }
+
+ console.log('[QUEST SYSTEM] Days until Saturday:', daysUntilSaturday);
+
+ // Create the target reset time (Saturday midnight)
+ const nextSaturday = new Date(now);
+ nextSaturday.setDate(now.getDate() + daysUntilSaturday);
+ nextSaturday.setHours(0, 0, 0, 0); // Set to midnight
+
+ console.log('[QUEST SYSTEM] Next Saturday reset time:', nextSaturday.toString());
+
+ let timeUntilReset = nextSaturday.getTime() - now.getTime();
+
+ // Ensure timeUntilReset is positive (handle edge cases)
+ if (timeUntilReset <= 0) {
+ console.log('[QUEST SYSTEM] Time until reset is negative or zero, adding 7 days');
+ nextSaturday.setDate(nextSaturday.getDate() + 7);
+ timeUntilReset = nextSaturday.getTime() - now.getTime();
+ }
+
+ console.log('[QUEST SYSTEM] Time until reset (ms):', timeUntilReset);
+
+ // Convert to days, hours, minutes, seconds
+ const totalSeconds = Math.floor(timeUntilReset / 1000);
+ const days = Math.floor(totalSeconds / (24 * 60 * 60));
+ const hours = Math.floor((totalSeconds % (24 * 60 * 60)) / (60 * 60));
+ const minutes = Math.floor((totalSeconds % (60 * 60)) / 60);
+ const seconds = totalSeconds % 60;
+
+ console.log('[QUEST SYSTEM] Time breakdown:', {
+ timeUntilReset: timeUntilReset,
+ totalSeconds: totalSeconds,
+ days: days,
+ hours: hours,
+ minutes: minutes,
+ seconds: seconds,
+ daysUntilSaturday: daysUntilSaturday,
+ nextSaturdayDate: nextSaturday.toString(),
+ currentDate: now.toString()
+ });
+
+ // Update countdown display
+ const countdownElement = document.getElementById('weeklyCountdown');
+ if (countdownElement) {
+ // Use the calculated days from timeUntilReset, not daysUntilSaturday
+ if (days > 0) {
+ countdownElement.textContent = `Weekly quests reset in: ${days}d ${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
+ } else if (hours > 0) {
+ countdownElement.textContent = `Weekly quests reset in: ${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
+ } else {
+ countdownElement.textContent = `Weekly quests reset in: ${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
+ }
+ console.log('[QUEST SYSTEM] Weekly countdown updated:', countdownElement.textContent, `(${days}d ${hours}h ${minutes}m ${seconds}s)`);
+ } else {
+ console.log('[QUEST SYSTEM] Weekly countdown element not found');
+ }
+ }
+
+// ... (rest of the code remains the same)
+ // Quest requirements and unlocking
+ checkQuestRequirements(quest) {
+ console.log('Checking requirements for quest:', quest.id, quest.requirements);
+
+ if (!quest.requirements) {
+ console.log('No requirements, returning true');
+ return true;
+ }
+
+ const player = this.game.systems.player;
+ const dungeonSystem = this.game.systems.dungeonSystem;
+
+ if (quest.requirements.level) {
+ console.log('Level requirement:', quest.requirements.level, 'Player level:', player.stats.level);
+ if (player.stats.level < quest.requirements.level) {
+ console.log('Level requirement not met');
+ return false;
+ }
+ }
+
+ if (quest.requirements.quest) {
+ const requiredQuest = this.findQuest(quest.requirements.quest);
+ console.log('Required quest:', quest.requirements.quest, 'Found:', requiredQuest ? requiredQuest.id : 'null', 'Status:', requiredQuest ? requiredQuest.status : 'null');
+ if (!requiredQuest || requiredQuest.status !== 'completed') {
+ console.log('Quest requirement not met');
+ return false;
+ }
+ }
+
+ if (quest.requirements.guild) {
+ // Check if player is in a guild
+ if (!player.stats.guildJoined) {
+ return false;
+ }
+ }
+
+ if (quest.requirements.dungeons) {
+ const dungeonsCleared = player.stats.dungeonsCleared || 0;
+ if (dungeonsCleared < quest.requirements.dungeons) {
+ return false;
+ }
+ }
+
+ if (quest.requirements.upgrades) {
+ // Check ship upgrade requirements
+ const upgradesCompleted = this.checkUpgradeProgress(quest);
+ if (upgradesCompleted < quest.requirements.upgrades) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ checkUpgradeProgress(quest) {
+ const player = this.game.systems.player;
+ let upgradesCompleted = 0;
+
+ quest.objectives.forEach(objective => {
+ if (objective.type === 'upgrade') {
+ if (player.stats[`upgraded_${objective.id}`]) {
+ upgradesCompleted++;
+ }
+ }
+ });
+
+ return upgradesCompleted;
+ }
+
+ getMaxSkillLevel(player) {
+ // Get the highest skill level from player
+ if (!player.skills) return 0;
+
+ let maxLevel = 0;
+ Object.values(player.skills).forEach(skill => {
+ if (skill.level > maxLevel) {
+ maxLevel = skill.level;
+ }
+ });
+
+ return maxLevel;
+ }
+
+ unlockNextQuest(nextQuestId) {
+ const nextQuest = this.findQuest(nextQuestId);
+ if (nextQuest && nextQuest.status === 'locked') {
+ nextQuest.status = 'available';
+ this.game.showNotification(`New quest available: ${nextQuest.name}`, 'info', 4000);
+ }
+ }
+
+ findQuest(questId) {
+ // Search in all quest arrays including failed quests with safety checks
+ const allQuests = [
+ ...(this.mainQuests || []),
+ ...(this.dailyQuests || []),
+ ...(this.activeQuests || []),
+ ...(this.completedQuests || []),
+ ...(this.failedQuests || [])
+ ];
+
+ return allQuests.find(q => q.id === questId);
+ }
+
+ getQuestsByType(type) {
+ console.log(`[QUEST SYSTEM] getQuestsByType called with type: ${type}`);
+
+ switch (type) {
+ case 'main':
+ const mainQuests = (this.mainQuests || []).filter(q => q.status === 'available' || q.status === 'active');
+ console.log(`[QUEST SYSTEM] Main quests found: ${mainQuests.length}`);
+ return mainQuests;
+ case 'daily':
+ // Ensure daily quests are initialized
+ if (!this.dailyQuests || this.dailyQuests.length === 0) {
+ console.log('[QUEST SYSTEM] Daily quests not initialized, forcing initialization');
+ this.randomizeDailyQuests();
+ }
+
+ const dailyQuests = (this.dailyQuests || []).filter(q => q.status !== 'failed');
+ console.log(`[QUEST SYSTEM] Daily quests found: ${dailyQuests.length}`);
+ console.log(`[QUEST SYSTEM] All daily quests array:`, this.dailyQuests);
+ dailyQuests.forEach(quest => {
+ console.log(`[QUEST SYSTEM] Daily quest: ${quest.id}, status: ${quest.status}, name: ${quest.name}`);
+ });
+ return dailyQuests;
+ case 'weekly':
+ const weeklyQuests = (this.weeklyQuests || []).filter(q => q.status !== 'failed');
+ console.log(`[QUEST SYSTEM] Weekly quests found: ${weeklyQuests.length}`);
+ return weeklyQuests;
+ case 'completed':
+ return this.getCompletedQuests();
+ case 'failed':
+ return (this.failedQuests || []);
+ default:
+ console.log(`[QUEST SYSTEM] Unknown quest type: ${type}`);
+ return [];
+ }
+ }
+
+ getCompletedQuests() {
+ const completed = [];
+
+ // Add completed main quests
+ if (this.mainQuests) {
+ completed.push(...this.mainQuests.filter(q => q.status === 'completed'));
+ }
+
+ // Add completed daily quests (from history)
+ if (this.completedDailyQuests) {
+ completed.push(...this.completedDailyQuests);
+ }
+
+ // Add completed weekly quests
+ if (this.weeklyQuests) {
+ completed.push(...this.weeklyQuests.filter(q => q.status === 'completed'));
+ }
+
+ // Sort by completion time (most recent first)
+ return completed.sort((a, b) => (b.completedAt || 0) - (a.completedAt || 0));
+ }
+
+ // UI updates
+ updateUI() {
+ this.updateQuestList();
+ this.updateQuestStats();
+ }
+
+ updateQuestList() {
+ console.log('[QUEST SYSTEM] updateQuestList called');
+ const questListElement = document.getElementById('questList');
+ if (!questListElement) {
+ console.log('[QUEST SYSTEM] questListElement not found');
+ return;
+ }
+
+ const activeType = document.querySelector('.quest-tab-btn.active')?.dataset.type || 'main';
+ console.log(`[QUEST SYSTEM] Active quest tab type: ${activeType}`);
+
+ const quests = this.getQuestsByType(activeType);
+ console.log(`[QUEST SYSTEM] Getting quests for type: ${activeType}, found: ${quests.length} quests`);
+
+ questListElement.innerHTML = '';
+
+ if (quests.length === 0) {
+ console.log(`[QUEST SYSTEM] No quests found for type: ${activeType}`);
+ questListElement.innerHTML = 'No quests available
';
+ return;
+ }
+
+ console.log(`[QUEST SYSTEM] Rendering ${quests.length} quests for type: ${activeType}`);
+ quests.forEach(quest => {
+ console.log(`[QUEST SYSTEM] Rendering quest: ${quest.id}, status: ${quest.status}`);
+ const questElement = document.createElement('div');
+ questElement.className = `quest-item ${quest.status}`;
+
+ // Add difficulty indicator for daily quests
+ const difficultyIndicator = quest.type === 'daily' && quest.difficulty ?
+ `
+ ${'★'.repeat(quest.difficulty)}
+
` : '';
+
+ // Handle completed quests differently
+ const isCompleted = quest.status === 'completed';
+ const progressPercent = isCompleted ? 100 : (quest.objectives.length > 0 ?
+ (quest.objectives.reduce((sum, obj) => sum + (obj.current / obj.target), 0) / quest.objectives.length) * 100 : 0);
+
+ const objectivesHtml = isCompleted ?
+ '✓ All objectives completed
' :
+ quest.objectives.map(obj => `
+
+
${obj.description}
+
${obj.current}/${obj.target}
+
+
+ `).join('');
+
+ const rewardsHtml = Object.entries(quest.rewards)
+ .filter(([key]) => key !== 'item')
+ .map(([key, value]) => {
+ const icon = key === 'credits' ? 'fa-coins' : key === 'experience' ? 'fa-star' : 'fa-gem';
+ return ` ${value}
`;
+ }).join('');
+
+ // Add completion time for completed quests
+ const completionTime = isCompleted && quest.completedAt ?
+ `Completed: ${new Date(quest.completedAt).toLocaleDateString()}
` : '';
+
+ questElement.innerHTML = `
+
+ ${quest.description}
+
+ ${objectivesHtml}
+
+ ${completionTime}
+
+
+
${isCompleted ? 'Completed' : Math.round(progressPercent) + '% Complete'}
+
+
+ ${quest.status === 'available' ?
+ `
Start Quest ` :
+ quest.status === 'active' ?
+ `
+
+ Cancel Quest
+
+
+ Turn In
+
+
` :
+ quest.status === 'completed' ?
+ '
Completed ' :
+ quest.status === 'failed' ?
+ `
+ Failed: ${quest.failureReason || 'Cancelled'}
+
+ Retry Quest
+
+
` :
+ '
Available '
+ }
+
+ `;
+
+ questListElement.appendChild(questElement);
+ });
+ }
+
+ updateQuestStats() {
+ // Update quest statistics if elements exist
+ const questsCompletedElement = document.getElementById('questsCompleted');
+ if (questsCompletedElement) {
+ questsCompletedElement.textContent = this.stats.questsCompleted;
+ }
+ }
+
+ // Quest cancellation
+ cancelQuest(questId) {
+ const quest = this.findQuest(questId);
+ if (!quest) {
+ this.game.showNotification('Quest not found!', 'error', 3000);
+ return;
+ }
+
+ if (quest.status !== 'active') {
+ this.game.showNotification('Only active quests can be cancelled!', 'warning', 3000);
+ return;
+ }
+
+ if (!confirm(`Are you sure you want to cancel "${quest.name}"? This quest will be marked as failed.`)) {
+ return;
+ }
+
+ // Move quest to failed
+ quest.status = 'failed';
+ quest.failedAt = Date.now();
+ quest.failureReason = 'Cancelled by player';
+
+ // Remove from active quests
+ this.activeQuests = this.activeQuests.filter(q => q.id !== questId);
+
+ // Add to failed quests
+ this.failedQuests.push({...quest});
+
+ // Apply failure penalty
+ this.applyQuestFailurePenalty(quest);
+
+ this.game.showNotification(`Quest "${quest.name}" cancelled and marked as failed.`, 'warning', 4000);
+ this.switchToFailedTab();
+ this.updateQuestList();
+ }
+
+ // Quest failure handling
+ failQuest(questId, reason = 'Time expired') {
+ const quest = this.findQuest(questId);
+ if (!quest) return;
+
+ if (quest.status !== 'active') return;
+
+ quest.status = 'failed';
+ quest.failedAt = Date.now();
+ quest.failureReason = reason;
+
+ // Remove from active quests
+ this.activeQuests = this.activeQuests.filter(q => q.id !== questId);
+
+ // Add to failed quests
+ this.failedQuests.push({...quest});
+
+ // Apply failure penalty
+ this.applyQuestFailurePenalty(quest);
+
+ this.game.showNotification(`Quest "${quest.name}" failed: ${reason}`, 'error', 4000);
+ this.switchToFailedTab();
+ this.updateQuestList();
+ }
+
+ applyQuestFailurePenalty(quest) {
+ // Apply failure penalties
+ const player = this.game.systems.player;
+
+ // Reduce reputation
+ if (player.stats.reputation) {
+ player.stats.reputation = Math.max(0, player.stats.reputation - 10);
+ }
+
+ // Apply other penalties based on quest type
+ switch (quest.type) {
+ case 'daily':
+ this.stats.dailyQuestsCompleted = Math.max(0, this.stats.dailyQuestsCompleted - 1);
+ break;
+ }
+
+ player.updateUI();
+ }
+
+ // Retry failed quest
+ retryQuest(questId) {
+ const failedQuestIndex = this.failedQuests.findIndex(q => q.id === questId);
+ if (failedQuestIndex === -1) {
+ this.game.showNotification('Failed quest not found!', 'error', 3000);
+ return;
+ }
+
+ const quest = this.failedQuests[failedQuestIndex];
+
+ // Check if retry is allowed (24 hour cooldown)
+ const timeSinceFailure = Date.now() - quest.failedAt;
+ const retryCooldown = 24 * 60 * 60 * 1000; // 24 hours
+
+ if (timeSinceFailure < retryCooldown) {
+ const remainingTime = Math.ceil((retryCooldown - timeSinceFailure) / (60 * 60 * 1000));
+ this.game.showNotification(`Can retry this quest in ${remainingTime} hours.`, 'warning', 3000);
+ return;
+ }
+
+ // Reset quest progress
+ quest.objectives.forEach(obj => {
+ obj.current = 0;
+ });
+ quest.status = 'available';
+ quest.failedAt = null;
+ quest.failureReason = null;
+
+ // Remove from failed quests
+ this.failedQuests.splice(failedQuestIndex, 1);
+
+ this.game.showNotification(`Quest "${quest.name}" is now available to retry.`, 'success', 3000);
+ this.updateQuestList();
+ }
+
+ // Switch to failed quests tab
+ switchToFailedTab() {
+ // Remove active class from all quest tab buttons
+ document.querySelectorAll('.quest-tab-btn').forEach(btn => {
+ btn.classList.remove('active');
+ });
+
+ // Add active class to failed quests tab
+ const failedTabBtn = document.querySelector('[data-type="failed"]');
+ if (failedTabBtn) {
+ failedTabBtn.classList.add('active');
+ }
+
+ // Update the quest list to show failed quests
+ this.updateQuestList();
+ }
+
+ // Quest progress tracking
+ updateQuestProgress(objectiveType, amount = 1) {
+ // Update progress for all active quests with this objective type
+ [...(this.activeQuests || []), ...(this.mainQuests || []), ...(this.dailyQuests || [])].forEach(quest => {
+ if (quest.status === 'active') {
+ quest.objectives.forEach(objective => {
+ if (objective.type === objectiveType || objective.id.includes(objectiveType)) {
+ const oldValue = objective.current;
+ objective.current = Math.min(objective.current + amount, objective.target);
+
+ if (objective.current !== oldValue) {
+ this.game.showNotification(`${objective.description}: ${objective.current}/${objective.target}`, 'info', 2000);
+ }
+ }
+ });
+ }
+ });
+
+ this.updateQuestList();
+ this.checkQuestCompletion();
+ }
+
+ updateLevelProgress(newLevel) {
+ // Update progress for level-based objectives
+ [...(this.activeQuests || []), ...(this.mainQuests || []), ...(this.dailyQuests || [])].forEach(quest => {
+ if (quest.status === 'active') {
+ quest.objectives.forEach(objective => {
+ if (objective.type === 'level' || objective.id.includes('level')) {
+ const oldValue = objective.current;
+ objective.current = Math.min(newLevel, objective.target);
+
+ if (objective.current !== oldValue) {
+ this.game.showNotification(`${objective.description}: ${objective.current}/${objective.target}`, 'info', 2000);
+ }
+ }
+ });
+ }
+ });
+
+ this.updateQuestList();
+ this.checkQuestCompletion();
+ }
+
+ updateDungeonProgress() {
+ // Update progress for dungeon-based objectives
+ [...(this.activeQuests || []), ...(this.mainQuests || []), ...(this.dailyQuests || [])].forEach(quest => {
+ if (quest.status === 'active') {
+ quest.objectives.forEach(objective => {
+ if (objective.type === 'dungeon' || objective.id.includes('dungeon') || objective.id.includes('clear_dungeon')) {
+ const oldValue = objective.current;
+ objective.current = Math.min(objective.current + 1, objective.target);
+
+ if (objective.current !== oldValue) {
+ this.game.showNotification(`${objective.description}: ${objective.current}/${objective.target}`, 'info', 2000);
+ }
+ }
+ });
+ }
+ });
+
+ this.updateQuestList();
+ this.checkQuestCompletion();
+ }
+
+ updateTutorialDungeonProgress() {
+ // Update progress for tutorial dungeon objectives
+ [...(this.activeQuests || []), ...(this.mainQuests || []), ...(this.dailyQuests || [])].forEach(quest => {
+ if (quest.status === 'active') {
+ quest.objectives.forEach(objective => {
+ if (objective.type === 'tutorial_dungeon' || objective.id.includes('tutorial_dungeon')) {
+ const oldValue = objective.current;
+ objective.current = 1; // Tutorial dungeon is completed
+
+ if (objective.current !== oldValue) {
+ this.game.showNotification(`${objective.description}: ${objective.current}/${objective.target}`, 'info', 2000);
+ }
+ }
+ });
+ }
+ });
+
+ this.updateQuestList();
+ this.checkQuestCompletion();
+ }
+
+ updateUpgradeProgress(upgradeType) {
+ // Update progress for upgrade objectives
+ [...(this.activeQuests || []), ...(this.mainQuests || []), ...(this.dailyQuests || [])].forEach(quest => {
+ if (quest.status === 'active') {
+ quest.objectives.forEach(objective => {
+ if (objective.type === 'upgrade' && (objective.id === upgradeType || objective.id.includes(upgradeType))) {
+ const oldValue = objective.current;
+ objective.current = 1; // Upgrade completed
+
+ if (objective.current !== oldValue) {
+ this.game.showNotification(`${objective.description}: ${objective.current}/${objective.target}`, 'info', 2000);
+ }
+ }
+ });
+ }
+ });
+
+ this.updateQuestList();
+ this.checkQuestCompletion();
+ this.checkQuestAvailability();
+ }
+
+ updateGuildProgress(action, amount = 1) {
+ // Update progress for guild objectives
+ [...(this.activeQuests || []), ...(this.mainQuests || []), ...(this.dailyQuests || [])].forEach(quest => {
+ if (quest.status === 'active') {
+ quest.objectives.forEach(objective => {
+ if (action === 'join' && (objective.type === 'guild' || objective.id.includes('guild'))) {
+ const oldValue = objective.current;
+ objective.current = 1; // Guild joined
+
+ if (objective.current !== oldValue) {
+ this.game.showNotification(`${objective.description}: ${objective.current}/${objective.target}`, 'info', 2000);
+ }
+ }
+
+ if (action === 'contribute' && (objective.type === 'contribution' || objective.id.includes('contribution'))) {
+ const oldValue = objective.current;
+ objective.current = Math.min(objective.current + amount, objective.target);
+
+ if (objective.current !== oldValue) {
+ this.game.showNotification(`${objective.description}: ${objective.current}/${objective.target}`, 'info', 2000);
+ }
+ }
+ });
+ }
+ });
+
+ this.updateQuestList();
+ this.checkQuestCompletion();
+ this.checkQuestAvailability();
+ }
+
+ updateSkillProgress() {
+ // Update progress for skill objectives
+ [...(this.activeQuests || []), ...(this.mainQuests || []), ...(this.dailyQuests || [])].forEach(quest => {
+ if (quest.status === 'active') {
+ quest.objectives.forEach(objective => {
+ if (objective.type === 'skill' || objective.id.includes('skill')) {
+ const player = this.game.systems.player;
+ const maxSkillLevel = this.getMaxSkillLevel(player);
+ const oldValue = objective.current;
+ objective.current = Math.min(maxSkillLevel, objective.target);
+
+ if (objective.current !== oldValue) {
+ this.game.showNotification(`${objective.description}: ${objective.current}/${objective.target}`, 'info', 2000);
+ }
+ }
+ });
+ }
+ });
+
+ this.updateQuestList();
+ this.checkQuestCompletion();
+ this.checkQuestAvailability();
+ }
+
+ checkInitialQuestProgress(quest) {
+ const player = this.game.systems.player;
+ const dungeonSystem = this.game.systems.dungeonSystem;
+
+ console.log(`[QUEST SYSTEM] Checking initial progress for quest: ${quest.id}`);
+ console.log(`[QUEST SYSTEM] Player stats:`, player.stats);
+
+ quest.objectives.forEach(objective => {
+ console.log(`[QUEST SYSTEM] Processing objective: ${objective.id}, type: ${objective.type}`);
+
+ // Check level-based objectives
+ if (objective.type === 'level' || objective.id.includes('level')) {
+ objective.current = Math.min(player.stats.level, objective.target);
+ console.log(`[QUEST SYSTEM] Level objective ${objective.id}: ${objective.current}/${objective.target}`);
+ }
+
+ // Check dungeon-based objectives
+ if (objective.type === 'dungeon' || objective.id.includes('dungeon') || objective.id.includes('clear_dungeon')) {
+ // Check any dungeon completion - use multiple sources
+ const playerDungeons = player.stats.dungeonsCleared || 0;
+ const dungeonSystemStats = dungeonSystem ? dungeonSystem.stats.dungeonsCompleted || 0 : 0;
+ const dungeonsCleared = Math.max(playerDungeons, dungeonSystemStats);
+
+ objective.current = Math.min(dungeonsCleared, objective.target);
+
+ // Debug logging
+ console.log(`Dungeon objective check: Player=${playerDungeons}, DungeonSystem=${dungeonSystemStats}, Final=${dungeonsCleared}`);
+ }
+
+ // Check tutorial dungeon objective
+ if (objective.type === 'tutorial_dungeon' || objective.id.includes('tutorial_dungeon')) {
+ // Check if tutorial dungeon has been completed
+ const tutorialCompleted = player.stats.tutorialDungeonCompleted || false;
+ objective.current = tutorialCompleted ? 1 : 0;
+ console.log(`[QUEST SYSTEM] Tutorial dungeon objective ${objective.id}: ${objective.current}/${objective.target}, completed: ${tutorialCompleted}`);
+ }
+
+ // Check upgrade objectives
+ if (objective.type === 'upgrade' || objective.id.includes('upgrade')) {
+ const upgradeCompleted = player.stats[`upgraded_${objective.id}`] || false;
+ objective.current = upgradeCompleted ? 1 : 0;
+ }
+
+ // Check guild objectives
+ if (objective.type === 'guild' || objective.id.includes('guild')) {
+ const guildJoined = player.stats.guildJoined || false;
+ objective.current = guildJoined ? 1 : 0;
+ }
+
+ // Check contribution objectives
+ if (objective.type === 'contribution' || objective.id.includes('contribution')) {
+ const contribution = player.stats.guildContribution || 0;
+ objective.current = Math.min(contribution, objective.target);
+ }
+
+ // Check skill objectives
+ if (objective.type === 'skill' || objective.id.includes('skill')) {
+ const maxSkillLevel = this.getMaxSkillLevel(player);
+ objective.current = Math.min(maxSkillLevel, objective.target);
+ }
+
+ // Check other objective types
+ if (objective.type === 'collection' || objective.id.includes('collect')) {
+ // Check inventory for collected items
+ const inventory = this.game.systems.inventory;
+ if (inventory && inventory.items) {
+ const totalCollected = Object.values(inventory.items).reduce((sum, item) => sum + item.quantity, 0);
+ objective.current = Math.min(totalCollected, objective.target);
+ }
+ }
+ });
+
+ // Check if quest is already complete
+ const allComplete = quest.objectives.every(obj => obj.current >= obj.target);
+ if (allComplete) {
+ quest.status = 'completed';
+ this.completeQuest(quest.id);
+ }
+ }
+
+ checkQuestCompletion() {
+ // Check if any active quests are complete
+ this.activeQuests.forEach(quest => {
+ if (quest.status === 'active') {
+ const allComplete = quest.objectives.every(obj => obj.current >= obj.target);
+ if (allComplete) {
+ quest.status = 'completed';
+ this.completeQuest(quest.id);
+ }
+ }
+ });
+ }
+
+ // Save/Load
+ save() {
+ const debugLogger = window.debugLogger;
+
+ // if (debugLogger) debugLogger.startStep('QuestSystem.save', {
+ // mainQuestsCount: this.mainQuests.length,
+ // dailyQuestsCount: this.dailyQuests.length,
+ // activeQuestsCount: this.activeQuests.length,
+ // completedQuestsCount: this.completedQuests.length,
+ // currentStats: this.stats
+ // });
+
+ const saveData = {
+ mainQuests: this.mainQuests,
+ dailyQuests: this.dailyQuests,
+ activeQuests: this.activeQuests,
+ completedQuests: this.completedQuests,
+ stats: this.stats
+ };
+
+ // if (debugLogger) debugLogger.endStep('QuestSystem.save', {
+ // saveDataSize: JSON.stringify(saveData).length,
+ // mainQuestsSaved: this.mainQuests.length,
+ // dailyQuestsSaved: this.dailyQuests.length,
+ // activeQuestsSaved: this.activeQuests.length,
+ // completedQuestsSaved: this.completedQuests.length,
+ // statsSaved: this.stats,
+ // saveData: saveData
+ // });
+
+ return saveData;
+ }
+
+ load(data) {
+ const debugLogger = window.debugLogger;
+ const oldState = {
+ mainQuestsCount: this.mainQuests.length,
+ dailyQuestsCount: this.dailyQuests.length,
+ activeQuestsCount: this.activeQuests.length,
+ completedQuestsCount: this.completedQuests.length,
+ stats: this.stats
+ };
+
+ if (debugLogger) debugLogger.startStep('QuestSystem.load', {
+ oldState: oldState,
+ loadData: data
+ });
+
+ try {
+ if (data.mainQuests) {
+ this.mainQuests = data.mainQuests;
+
+ if (debugLogger) debugLogger.logStep('Loaded main quests', {
+ mainQuestsLoaded: this.mainQuests.length,
+ mainQuests: this.mainQuests.map(q => ({
+ id: q.id,
+ name: q.name,
+ status: q.status
+ }))
+ });
+ }
+
+ if (data.dailyQuests) {
+ this.dailyQuests = data.dailyQuests;
+
+ if (debugLogger) debugLogger.logStep('Loaded daily quests', {
+ dailyQuestsLoaded: this.dailyQuests.length,
+ dailyQuests: this.dailyQuests.map(q => ({
+ id: q.id,
+ name: q.name,
+ status: q.status
+ }))
+ });
+ }
+
+ if (data.activeQuests) {
+ this.activeQuests = data.activeQuests;
+
+ if (debugLogger) debugLogger.logStep('Loaded active quests', {
+ activeQuestsLoaded: this.activeQuests.length,
+ activeQuests: this.activeQuests.map(q => ({
+ id: q.id,
+ name: q.name,
+ type: q.type,
+ status: q.status
+ }))
+ });
+ }
+
+ if (data.completedQuests) {
+ this.completedQuests = data.completedQuests;
+
+ if (debugLogger) debugLogger.logStep('Loaded completed quests', {
+ completedQuestsLoaded: this.completedQuests.length,
+ completedQuests: this.completedQuests.map(q => ({
+ id: q.id,
+ name: q.name,
+ type: q.type,
+ completedAt: q.completedAt
+ }))
+ });
+ }
+
+ if (data.stats) {
+ const oldStats = { ...this.stats };
+ this.stats = { ...this.stats, ...data.stats };
+
+ if (debugLogger) debugLogger.logStep('Loaded quest statistics', {
+ oldStats: oldStats,
+ newStats: this.stats,
+ statsMerged: true
+ });
+ }
+
+ // Check for daily reset after loading
+ if (debugLogger) debugLogger.logStep('Checking daily reset after load');
+ this.checkDailyReset();
+
+ if (debugLogger) debugLogger.endStep('QuestSystem.load', {
+ success: true,
+ oldState: oldState,
+ newState: {
+ mainQuestsCount: this.mainQuests.length,
+ dailyQuestsCount: this.dailyQuests.length,
+ activeQuestsCount: this.activeQuests.length,
+ completedQuestsCount: this.completedQuests.length,
+ stats: this.stats
+ },
+ dailyResetChecked: true
+ });
+ } catch (error) {
+ if (debugLogger) debugLogger.errorEvent('QuestSystem.load', error, {
+ oldState: oldState,
+ loadData: data,
+ error: error.message
+ });
+ throw error;
+ }
+ }
+}
diff --git a/Client/js/systems/ShipSystem.js b/Client/js/systems/ShipSystem.js
new file mode 100644
index 0000000..e3dcb7e
--- /dev/null
+++ b/Client/js/systems/ShipSystem.js
@@ -0,0 +1,223 @@
+class ShipSystem {
+ constructor(game) {
+ this.game = game;
+ this.ships = [];
+ this.currentShip = null;
+ this.initializeShips();
+ }
+
+ initializeShips() {
+ // Initialize with player's current ship instead of static data
+ this.ships = [];
+
+ // Wait for game systems to be ready, then sync with player ship
+ setTimeout(() => {
+ this.syncWithPlayerShip();
+ }, 100);
+ }
+
+ syncWithPlayerShip() {
+ const player = this.game.systems.player;
+ if (!player || !player.ship) {
+ return;
+ }
+
+ if (player && player.ship) {
+ // Create ship object from player's current ship
+ const playerShip = {
+ id: 'current_ship',
+ name: player.ship.name || 'Starter Cruiser',
+ class: player.ship.class || 'Cruiser',
+ level: player.ship.level || 1,
+ health: player.ship.health || player.ship.maxHealth || 100,
+ maxHealth: player.ship.maxHealth || 100,
+ attack: player.ship.attack || player.attributes.attack || 10,
+ defense: player.ship.defence || player.attributes.defense || 5,
+ speed: player.ship.speed || player.attributes.speed || 10,
+ image: player.ship.texture || 'assets/textures/ships/starter_cruiser.png',
+ status: 'active',
+ experience: 0,
+ requiredExp: 100,
+ rarity: 'Common'
+ };
+
+ this.ships = [playerShip];
+ this.currentShip = playerShip;
+
+ // Update the display immediately
+ this.updateCurrentShipDisplay();
+ }
+ }
+
+ renderShips() {
+ const shipGrid = document.getElementById('shipGrid');
+ if (!shipGrid) return;
+
+ shipGrid.innerHTML = '';
+
+ this.ships.forEach(ship => {
+ const shipCard = this.createShipCard(ship);
+ shipGrid.appendChild(shipCard);
+ });
+ }
+
+ createShipCard(ship) {
+ const card = document.createElement('div');
+ card.className = `ship-card ${ship.status === 'active' ? 'active' : ''}`;
+ card.dataset.shipId = ship.id;
+
+ card.innerHTML = `
+
+
+
+ ${ship.status === 'active' ? 'ACTIVE' : 'SWITCH'}
+
+
+ `;
+
+ return card;
+ }
+
+ updateCurrentShipDisplay() {
+ // Use player's ship data instead of this.currentShip
+ const player = this.game.systems.player;
+ if (!player || !player.ship) {
+ return;
+ }
+
+ const elements = {
+ currentShipImage: document.getElementById('currentShipImage'),
+ currentShipName: document.getElementById('currentShipName'),
+ currentShipClass: document.getElementById('currentShipClass'),
+ currentShipLevel: document.getElementById('currentShipLevel'),
+ currentShipHealth: document.getElementById('currentShipHealth'),
+ currentShipAttack: document.getElementById('currentShipAttack'),
+ currentShipDefense: document.getElementById('currentShipDefense'),
+ currentShipSpeed: document.getElementById('currentShipSpeed')
+ };
+
+ // Use player's ship data
+ const ship = player.ship;
+
+ if (elements.currentShipImage) {
+ // Use the ship's texture if available, otherwise fallback
+ const imagePath = ship.texture || `assets/textures/ships/starter_cruiser.png`;
+ elements.currentShipImage.src = imagePath;
+ elements.currentShipImage.alt = ship.name;
+ }
+ if (elements.currentShipName) elements.currentShipName.textContent = ship.name;
+ if (elements.currentShipClass) elements.currentShipClass.textContent = ship.class || 'Unknown';
+ if (elements.currentShipLevel) elements.currentShipLevel.textContent = ship.level || 1;
+ if (elements.currentShipHealth) elements.currentShipHealth.textContent = `${ship.health}/${ship.maxHealth}`;
+ if (elements.currentShipAttack) elements.currentShipAttack.textContent = ship.attack || 0;
+ if (elements.currentShipDefense) elements.currentShipDefense.textContent = ship.defence || ship.defense || 0;
+ if (elements.currentShipSpeed) elements.currentShipSpeed.textContent = ship.speed || 0;
+ }
+
+ switchShip(shipId) {
+ const ship = this.ships.find(s => s.id === shipId);
+ if (!ship || ship.status === 'active') return;
+
+ // Deactivate current ship
+ if (this.currentShip) {
+ this.currentShip.status = 'inactive';
+ }
+
+ // Activate new ship
+ ship.status = 'active';
+ this.currentShip = ship;
+
+ // Update displays
+ this.renderShips();
+ this.updateCurrentShipDisplay();
+
+ // Show notification
+ this.game.showNotification(`Switched to ${ship.name}!`, 'success', 3000);
+ }
+
+ upgradeShip(shipId) {
+ const ship = this.ships.find(s => s.id === shipId);
+ if (!ship) return;
+
+ const upgradeCost = ship.level * 1000;
+
+ if (this.game.systems.economy.getCredits() < upgradeCost) {
+ this.game.showNotification(`Not enough credits! Need ${upgradeCost} credits.`, 'error', 3000);
+ return;
+ }
+
+ // Upgrade ship
+ this.game.systems.economy.removeCredits(upgradeCost);
+ ship.level++;
+ ship.maxHealth += 10;
+ ship.health = ship.maxHealth; // Full heal on upgrade
+ ship.attack += 2;
+ ship.defense += 1;
+ ship.speed += 1;
+ ship.requiredExp = ship.level * 100;
+
+ // Update displays
+ this.renderShips();
+ this.updateCurrentShipDisplay();
+
+ this.game.showNotification(`${ship.name} upgraded to level ${ship.level}!`, 'success', 3000);
+ }
+
+ repairShip(shipId) {
+ const ship = this.ships.find(s => s.id === shipId);
+ if (!ship || ship.health >= ship.maxHealth) return;
+
+ const repairCost = Math.floor((ship.maxHealth - ship.health) * 0.5);
+
+ if (this.game.systems.economy.getCredits() < repairCost) {
+ this.game.showNotification(`Not enough credits! Need ${repairCost} credits.`, 'error', 3000);
+ return;
+ }
+
+ // Repair ship
+ this.game.systems.economy.removeCredits(repairCost);
+ ship.health = ship.maxHealth;
+
+ // Update displays
+ this.renderShips();
+ this.updateCurrentShipDisplay();
+
+ this.game.showNotification(`${ship.name} fully repaired!`, 'success', 3000);
+ }
+
+ addExperience(shipId, amount) {
+ const ship = this.ships.find(s => s.id === shipId);
+ if (!ship) return;
+
+ ship.experience += amount;
+
+ // Check for level up
+ while (ship.experience >= ship.requiredExp) {
+ ship.experience -= ship.requiredExp;
+ this.upgradeShip(shipId);
+ }
+
+ this.renderShips();
+ if (this.currentShip && this.currentShip.id === shipId) {
+ this.updateCurrentShipDisplay();
+ }
+ }
+
+ getShip(shipId) {
+ return this.ships.find(s => s.id === shipId);
+ }
+
+ getCurrentShip() {
+ return this.currentShip;
+ }
+
+ getAllShips() {
+ return this.ships;
+ }
+}
diff --git a/Client/js/systems/SkillSystem.js b/Client/js/systems/SkillSystem.js
new file mode 100644
index 0000000..2f1a59e
--- /dev/null
+++ b/Client/js/systems/SkillSystem.js
@@ -0,0 +1,588 @@
+/**
+ * Galaxy Strike Online - Skill System
+ * Manages skills, progression, and specialization
+ */
+
+class SkillSystem {
+ constructor(gameEngine) {
+ this.game = gameEngine;
+
+ // Skill categories
+ this.categories = {
+ combat: 'Combat',
+ science: 'Science',
+ crafting: 'Crafting'
+ };
+
+ // Skill definitions
+ this.skills = {
+ combat: {
+ weapons_mastery: {
+ name: 'Weapons Mastery',
+ description: 'Increases weapon damage and critical chance',
+ maxLevel: 10,
+ currentLevel: 0,
+ experience: 0,
+ experienceToNext: 100,
+ effects: {
+ attack: 2,
+ criticalChance: 0.01
+ },
+ icon: 'fa-sword',
+ unlocked: true
+ },
+ shield_techniques: {
+ name: 'Shield Techniques',
+ description: 'Improves defense and energy efficiency',
+ maxLevel: 10,
+ currentLevel: 0,
+ experience: 0,
+ experienceToNext: 100,
+ effects: {
+ defense: 2,
+ maxEnergy: 5
+ },
+ icon: 'fa-shield-alt',
+ unlocked: true
+ },
+ piloting: {
+ name: 'Piloting',
+ description: 'Enhances speed and evasion',
+ maxLevel: 10,
+ currentLevel: 0,
+ experience: 0,
+ experienceToNext: 100,
+ effects: {
+ speed: 2,
+ criticalChance: 0.005
+ },
+ icon: 'fa-rocket',
+ unlocked: true
+ },
+ tactical_analysis: {
+ name: 'Tactical Analysis',
+ description: 'Reveals enemy weaknesses and improves accuracy',
+ maxLevel: 10,
+ currentLevel: 0,
+ experience: 0,
+ experienceToNext: 100,
+ effects: {
+ criticalDamage: 0.05,
+ attack: 1
+ },
+ icon: 'fa-brain',
+ unlocked: false,
+ requiredLevel: 5
+ }
+ },
+ science: {
+ engineering: {
+ name: 'Engineering',
+ description: 'Technical skills for ship components and machinery',
+ maxLevel: 10,
+ currentLevel: 0,
+ experience: 0,
+ experienceToNext: 100,
+ effects: {
+ craftingBonus: 0.08,
+ shipStats: 0.03
+ },
+ icon: 'fa-wrench',
+ unlocked: true
+ },
+ energy_manipulation: {
+ name: 'Energy Manipulation',
+ description: 'Better energy control and regeneration',
+ maxLevel: 10,
+ currentLevel: 0,
+ experience: 0,
+ experienceToNext: 100,
+ effects: {
+ maxEnergy: 10,
+ energyRegeneration: 0.1
+ },
+ icon: 'fa-bolt',
+ unlocked: true
+ },
+ alien_technology: {
+ name: 'Alien Technology',
+ description: 'Understanding and using alien artifacts',
+ maxLevel: 10,
+ currentLevel: 0,
+ experience: 0,
+ experienceToNext: 100,
+ effects: {
+ findRarity: 0.05,
+ itemValue: 0.1
+ },
+ icon: 'fa-atom',
+ unlocked: false,
+ requiredLevel: 3
+ },
+ quantum_physics: {
+ name: 'Quantum Physics',
+ description: 'Advanced quantum mechanics for better equipment',
+ maxLevel: 10,
+ currentLevel: 0,
+ experience: 0,
+ experienceToNext: 100,
+ effects: {
+ criticalDamage: 0.1,
+ attack: 3
+ },
+ icon: 'fa-microscope',
+ unlocked: false,
+ requiredLevel: 8
+ },
+ bio_engineering: {
+ name: 'Bio-Engineering',
+ description: 'Biological enhancements and healing',
+ maxLevel: 10,
+ currentLevel: 0,
+ experience: 0,
+ experienceToNext: 100,
+ effects: {
+ maxHealth: 15,
+ healthRegeneration: 0.05
+ },
+ icon: 'fa-dna',
+ unlocked: false,
+ requiredLevel: 6
+ }
+ },
+ crafting: {
+ crafting: {
+ name: 'General Crafting',
+ description: 'Basic crafting skills for all items',
+ maxLevel: 10,
+ currentLevel: 0,
+ experience: 0,
+ experienceToNext: 100,
+ effects: {
+ craftingBonus: 0.05
+ },
+ icon: 'fa-hammer',
+ unlocked: true
+ },
+ weapon_crafting: {
+ name: 'Weapon Crafting',
+ description: 'Create and upgrade weapons',
+ maxLevel: 10,
+ currentLevel: 0,
+ experience: 0,
+ experienceToNext: 100,
+ effects: {
+ craftingBonus: 0.1,
+ weaponStats: 0.05
+ },
+ icon: 'fa-hammer',
+ unlocked: true
+ },
+ armor_forging: {
+ name: 'Armor Forging',
+ description: 'Forge protective armor and shields',
+ maxLevel: 10,
+ currentLevel: 0,
+ experience: 0,
+ experienceToNext: 100,
+ effects: {
+ craftingBonus: 0.1,
+ armorStats: 0.05
+ },
+ icon: 'fa-anvil',
+ unlocked: true
+ },
+ resource_extraction: {
+ name: 'Resource Extraction',
+ description: 'Better resource gathering and efficiency',
+ maxLevel: 10,
+ currentLevel: 0,
+ experience: 0,
+ experienceToNext: 100,
+ effects: {
+ resourceBonus: 0.15,
+ findResources: 0.1
+ },
+ icon: 'fa-gem',
+ unlocked: false,
+ requiredLevel: 4
+ },
+ engineering: {
+ name: 'Engineering',
+ description: 'Advanced ship modifications and systems',
+ maxLevel: 10,
+ currentLevel: 0,
+ experience: 0,
+ experienceToNext: 100,
+ effects: {
+ shipUpgrades: 0.2,
+ systemEfficiency: 0.1
+ },
+ icon: 'fa-cogs',
+ unlocked: false,
+ requiredLevel: 7
+ }
+ }
+ };
+
+ // Skill experience rates
+ this.experienceRates = {
+ combat: 1.0,
+ science: 0.8,
+ crafting: 0.6
+ };
+
+ // Active buffs from skills
+ this.activeBuffs = {};
+ }
+
+ async initialize() {
+}
+
+ // Skill management
+ addSkillExperience(category, skillId, amount) {
+ const skill = this.skills[category]?.[skillId];
+ if (!skill || skill.currentLevel >= skill.maxLevel) {
+ return false;
+ }
+
+ skill.experience += amount;
+
+ // Check for level up
+ while (skill.experience >= skill.experienceToNext && skill.currentLevel < skill.maxLevel) {
+ this.levelUpSkill(category, skillId);
+ }
+
+ this.applySkillEffects();
+return true;
+ }
+
+ levelUpSkill(category, skillId) {
+ const skill = this.skills[category][skillId];
+
+ // Handle excess experience
+ const excessExperience = skill.experience - skill.experienceToNext;
+
+ skill.currentLevel++;
+ skill.experienceToNext = Math.floor(skill.experienceToNext * 1.5);
+
+ // Set experience to excess (minimum 0)
+ skill.experience = Math.max(0, excessExperience);
+
+ // Apply skill effects
+ this.applySkillEffects();
+
+ this.game.showNotification(`${skill.name} leveled up to ${skill.currentLevel}!`, 'success', 4000);
+ this.game.showNotification('Skill effects applied!', 'info', 3000);
+ }
+
+ upgradeSkill(category, skillId) {
+ const skill = this.skills[category]?.[skillId];
+ const player = this.game.systems.player;
+
+ if (!skill) {
+ this.game.showNotification('Skill not found', 'error', 3000);
+ return false;
+ }
+
+ if (!skill.unlocked) {
+ this.game.showNotification('Skill is locked', 'error', 3000);
+ return false;
+ }
+
+ if (skill.currentLevel >= skill.maxLevel) {
+ this.game.showNotification('Skill is at maximum level', 'warning', 3000);
+ return false;
+ }
+
+ if (player.stats.skillPoints < 1) {
+ this.game.showNotification('Not enough skill points', 'error', 3000);
+ return false;
+ }
+
+ // Use skill point and level up
+ player.stats.skillPoints--;
+ this.levelUpSkill(category, skillId);
+
+ // Update UI to refresh skill points display
+ this.updateUI();
+
+ return true;
+ }
+
+ unlockSkill(category, skillId) {
+ const skill = this.skills[category]?.[skillId];
+ const player = this.game.systems.player;
+
+ if (!skill) {
+ this.game.showNotification('Skill not found', 'error', 3000);
+ return false;
+ }
+
+ if (skill.unlocked) {
+ this.game.showNotification('Skill is already unlocked', 'warning', 3000);
+ return false;
+ }
+
+ if (skill.requiredLevel && player.stats.level < skill.requiredLevel) {
+ this.game.showNotification(`Requires level ${skill.requiredLevel}`, 'error', 3000);
+ return false;
+ }
+
+ if (player.stats.skillPoints < 2) {
+ this.game.showNotification('Requires 2 skill points to unlock', 'error', 3000);
+ return false;
+ }
+
+ // Unlock skill
+ player.stats.skillPoints -= 2;
+ skill.unlocked = true;
+ skill.currentLevel = 1;
+
+ this.applySkillEffects();
+
+ // Update UI to refresh skill points display
+ this.updateUI();
+
+ this.game.showNotification(`${skill.name} unlocked!`, 'success', 4000);
+ return true;
+ }
+
+ applySkillEffects() {
+ const player = this.game.systems.player;
+
+ // Reset to base stats first
+ this.resetToBaseStats();
+
+ // Apply all skill effects
+ Object.values(this.skills).forEach(skill => {
+ if (skill.level > 0) {
+ const skillData = this.skillData[skill.id];
+ if (skillData && skillData.effects) {
+ Object.entries(skillData.effects).forEach(([effect, value]) => {
+ const totalEffect = value * skill.level;
+
+ switch (effect) {
+ case 'attack':
+ player.attributes.attack += totalEffect;
+ break;
+ case 'defense':
+ player.attributes.defense += totalEffect;
+ break;
+ case 'speed':
+ player.attributes.speed += totalEffect;
+ break;
+ case 'maxHealth':
+ player.attributes.maxHealth += totalEffect;
+ break;
+ case 'maxEnergy':
+ player.attributes.maxEnergy += totalEffect;
+ break;
+ case 'criticalChance':
+ player.attributes.criticalChance += totalEffect;
+ break;
+ case 'criticalDamage':
+ player.attributes.criticalDamage += totalEffect;
+ break;
+ case 'energyRegeneration':
+ case 'healthRegeneration':
+ case 'craftingBonus':
+ case 'weaponStats':
+ case 'armorStats':
+ case 'resourceBonus':
+ case 'shipUpgrades':
+ case 'systemEfficiency':
+ case 'findRarity':
+ case 'itemValue':
+ case 'findResources':
+ // Store these for other systems to use
+ if (!this.activeBuffs[effect]) {
+ this.activeBuffs[effect] = 0;
+ }
+ this.activeBuffs[effect] += totalEffect;
+ break;
+ }
+ });
+ }
+ }
+ });
+
+ player.updateUI();
+ }
+
+ resetToBaseStats() {
+ const player = this.game.systems.player;
+
+ // Reset to base values (would need to store base stats separately)
+ // For now, we'll use initial values
+ const baseStats = {
+ attack: 10 + (player.stats.level - 1) * 2,
+ defense: 5 + (player.stats.level - 1) * 1,
+ speed: 10,
+ maxHealth: 100 + (player.stats.level - 1) * 10,
+ maxEnergy: 100 + (player.stats.level - 1) * 5,
+ criticalChance: 0.05,
+ criticalDamage: 1.5
+ };
+
+ Object.assign(player.attributes, baseStats);
+ this.activeBuffs = {};
+ }
+
+ // Skill experience from actions
+ awardCombatExperience(amount) {
+ this.addSkillExperience('combat', 'weapons_mastery', amount);
+ this.addSkillExperience('combat', 'tactical_analysis', amount * 0.5);
+ }
+
+ awardScienceExperience(amount) {
+ this.addSkillExperience('science', 'energy_manipulation', amount);
+ this.addSkillExperience('science', 'alien_technology', amount * 0.3);
+ }
+
+ awardCraftingExperience(amount) {
+ this.addSkillExperience('crafting', 'weapon_crafting', amount);
+ this.addSkillExperience('crafting', 'armor_forging', amount * 0.5);
+ }
+
+ // Skill checks
+ getSkillLevel(category, skillId) {
+ return this.skills[category]?.[skillId]?.currentLevel || 0;
+ }
+
+ hasSkill(category, skillId, minimumLevel = 1) {
+ const skill = this.skills[category]?.[skillId];
+ return skill && skill.unlocked && skill.currentLevel >= minimumLevel;
+ }
+
+ getSkillBonus(effect) {
+ return this.activeBuffs[effect] || 0;
+ }
+
+ // UI updates
+ updateUI() {
+ this.updateSkillsGrid();
+ this.updateSkillPointsDisplay();
+ }
+
+ updateSkillsGrid() {
+ const skillsGridElement = document.getElementById('skillsGrid');
+ if (!skillsGridElement) return;
+
+ const activeCategory = document.querySelector('.skill-cat-btn.active')?.dataset.category || 'combat';
+ const skills = this.skills[activeCategory] || {};
+
+ skillsGridElement.innerHTML = '';
+
+ Object.entries(skills).forEach(([skillId, skill]) => {
+ const skillElement = document.createElement('div');
+ skillElement.className = `skill-item ${!skill.unlocked ? 'locked' : ''}`;
+
+ const progressPercent = skill.currentLevel > 0 ?
+ (skill.experience / skill.experienceToNext) * 100 : 0;
+
+ // Use texture manager for icon fallback
+ const iconClass = this.game.systems.textureManager ?
+ this.game.systems.textureManager.getIcon(skill.icon) :
+ (skill.icon || 'fa-question');
+
+ skillElement.innerHTML = `
+
+ ${skill.description}
+ ${skill.currentLevel > 0 && skill.currentLevel < skill.maxLevel ? `
+
+
+
${skill.experience}/${skill.experienceToNext} XP
+
+ ` : skill.currentLevel >= skill.maxLevel ? `
+
+ MAX LEVEL
+
+ ` : ''}
+
+ ${!skill.unlocked ? `
+
+ Unlock (2 Points)
+
+ ` : skill.currentLevel < skill.maxLevel ? `
+
+ Upgrade (1 Point)
+
+ ` : `
+ MAX LEVEL
+ `}
+
+ ${skill.requiredLevel && !skill.unlocked ? `
+ Requires Level ${skill.requiredLevel}
+ ` : ''}
+ `;
+
+ skillsGridElement.appendChild(skillElement);
+ });
+ }
+
+ updateSkillPointsDisplay() {
+ const player = this.game.systems.player;
+ // Update skill points display if element exists
+ const skillPointsElements = document.querySelectorAll('.skill-points');
+ skillPointsElements.forEach(element => {
+ element.textContent = `Skill Points: ${player.stats.skillPoints}`;
+ });
+ }
+
+ // Save/Load
+ save() {
+ return {
+ skills: this.skills,
+ activeBuffs: this.activeBuffs
+ };
+ }
+
+ load(data) {
+ if (data.skills) {
+ // Deep merge to preserve structure
+ for (const [category, skills] of Object.entries(data.skills)) {
+ if (this.skills[category]) {
+ for (const [skillId, skillData] of Object.entries(skills)) {
+ if (this.skills[category][skillId]) {
+ Object.assign(this.skills[category][skillId], skillData);
+ }
+ }
+ }
+ }
+ }
+
+ if (data.activeBuffs) {
+ this.activeBuffs = data.activeBuffs;
+ }
+
+ this.applySkillEffects();
+}
+
+reset() {
+ this.skillPoints = 0;
+ this.unlockedSkills = [];
+ this.activeBuffs = [];
+ // Skills are already defined in constructor, just reset levels
+ Object.values(this.skills).forEach(category => {
+ Object.values(category).forEach(skill => {
+ skill.currentLevel = 0;
+ skill.experience = 0;
+ });
+ });
+}
+
+clear() {
+ this.reset();
+}
+}
diff --git a/Client/js/ui/LiveMainMenu.js b/Client/js/ui/LiveMainMenu.js
new file mode 100644
index 0000000..71bfb34
--- /dev/null
+++ b/Client/js/ui/LiveMainMenu.js
@@ -0,0 +1,1433 @@
+/**
+ * Live Main Menu System
+ * Handles server authentication, server browser, and multiplayer game initialization
+ */
+
+console.log('[LIVE MAIN MENU] LiveMainMenu.js script loaded');
+
+class LiveMainMenu {
+ constructor() {
+ console.log('[LIVE MAIN MENU] Constructor called');
+
+ // Check if DOM is ready
+ if (document.readyState === 'loading') {
+ console.warn('[LIVE MAIN MENU] DOM not ready yet, elements may not be found');
+ } else {
+ console.log('[LIVE MAIN MENU] DOM is ready');
+ }
+
+ this.mainMenu = document.getElementById('mainMenu');
+ console.log('[LIVE MAIN MENU] Main menu element found:', !!this.mainMenu);
+
+ this.selectedServer = null;
+ this.authToken = null;
+ this.currentUser = null;
+ this.servers = []; // Renamed from serverList to avoid conflict with DOM element
+ this.apiBaseUrl = 'https://api.korvarix.com/api'; // API Server URL
+ this.gameServerUrl = 'https://api.korvarix.com'; // Game Server URL for Socket.IO
+ this.isLocalMode = false; // Track if we're in local mode
+
+ console.log('[LIVE MAIN MENU] Initializing elements');
+ this.initializeElements();
+
+ console.log('[LIVE MAIN MENU] Initializing event listeners');
+ this.bindEvents();
+
+ // Check for existing auth token
+ this.checkExistingAuth();
+
+ // Check for local server and update URLs if needed
+ this.checkForLocalServer();
+
+ console.log('[LIVE MAIN MENU] Constructor completed');
+ }
+
+ initializeElements() {
+ // Menu sections
+ this.loginSection = document.getElementById('loginSection');
+ this.serverSection = document.getElementById('serverSection');
+ this.serverConfirmSection = document.getElementById('serverConfirmSection');
+ this.optionsSection = document.getElementById('optionsSection');
+
+ // Login form elements
+ this.emailInput = document.getElementById('emailInput');
+ this.passwordInput = document.getElementById('passwordInput');
+ this.loginBtn = document.getElementById('loginBtn');
+ this.registerBtn = document.getElementById('registerBtn');
+ this.loginNotice = document.getElementById('loginNotice');
+
+ // Server browser elements
+ this.createServerBtn = document.getElementById('createServerBtn');
+ this.refreshServersBtn = document.getElementById('refreshServersBtn');
+ this.regionFilter = document.getElementById('regionFilter');
+ this.typeFilter = document.getElementById('typeFilter');
+ this.serverList = document.getElementById('serverList');
+ this.serverLoading = document.getElementById('serverLoading');
+ this.serverEmpty = document.getElementById('serverEmpty');
+
+ // Server confirmation elements
+ this.joinServerBtn = document.getElementById('joinServerBtn');
+ this.serverInfoBtn = document.getElementById('serverInfoBtn');
+ this.leaveServerBtn = document.getElementById('leaveServerBtn');
+
+ // Server detail elements
+ this.selectedServerName = document.getElementById('selectedServerName');
+ this.selectedServerType = document.getElementById('selectedServerType');
+ this.selectedServerRegion = document.getElementById('selectedServerRegion');
+ this.selectedServerPlayers = document.getElementById('selectedServerPlayers');
+ this.selectedServerOwner = document.getElementById('selectedServerOwner');
+
+ // Navigation buttons
+ this.backToLoginBtn = document.getElementById('backToLoginBtn');
+ this.backToServersBtn = document.getElementById('backToServersBtn');
+
+ console.log('[LIVE MAIN MENU] All elements initialized');
+ }
+
+ bindEvents() {
+ console.log('[LIVE MAIN MENU] Binding events...');
+
+ // Login events
+ if (this.loginBtn) {
+ this.loginBtn.addEventListener('click', () => this.handleLogin());
+ console.log('[LIVE MAIN MENU] Login button event bound');
+ } else {
+ console.warn('[LIVE MAIN MENU] Login button not found');
+ }
+
+ if (this.registerBtn) {
+ this.registerBtn.addEventListener('click', () => this.handleRegister());
+ console.log('[LIVE MAIN MENU] Register button event bound');
+ } else {
+ console.warn('[LIVE MAIN MENU] Register button not found');
+ }
+
+ // Enter key login
+ if (this.passwordInput) {
+ this.passwordInput.addEventListener('keypress', (e) => {
+ if (e.key === 'Enter') this.handleLogin();
+ });
+ console.log('[LIVE MAIN MENU] Password input event bound');
+ } else {
+ console.warn('[LIVE MAIN MENU] Password input not found');
+ }
+
+ // Server browser events
+ if (this.createServerBtn) {
+ this.createServerBtn.addEventListener('click', () => this.startLocalServer());
+ console.log('[LIVE MAIN MENU] Create server button event bound (now starts local server)');
+ } else {
+ console.warn('[LIVE MAIN MENU] Create server button not found');
+ }
+
+ if (this.refreshServersBtn) {
+ this.refreshServersBtn.addEventListener('click', () => this.refreshServerListWithoutAnimation());
+ console.log('[LIVE MAIN MENU] Refresh servers button event bound');
+ } else {
+ console.warn('[LIVE MAIN MENU] Refresh servers button not found');
+ }
+
+ if (this.regionFilter) {
+ this.regionFilter.addEventListener('change', () => this.filterServers());
+ console.log('[LIVE MAIN MENU] Region filter event bound');
+ } else {
+ console.warn('[LIVE MAIN MENU] Region filter not found');
+ }
+
+ if (this.typeFilter) {
+ this.typeFilter.addEventListener('change', () => this.filterServers());
+ console.log('[LIVE MAIN MENU] Type filter event bound');
+ } else {
+ console.warn('[LIVE MAIN MENU] Type filter not found');
+ }
+
+ // Server confirmation events
+ if (this.joinServerBtn) {
+ this.joinServerBtn.addEventListener('click', () => this.joinServer());
+ console.log('[LIVE MAIN MENU] Join server button event bound');
+ } else {
+ console.warn('[LIVE MAIN MENU] Join server not found');
+ }
+
+ // Navigation events
+ if (this.backToLoginBtn) {
+ this.backToLoginBtn.addEventListener('click', () => this.showLoginSection());
+ console.log('[LIVE MAIN MENU] Back to login button event bound');
+ } else {
+ console.warn('[LIVE MAIN MENU] Back to login button not found');
+ }
+
+ if (this.backToServersBtn) {
+ this.backToServersBtn.addEventListener('click', () => this.showServerSection());
+ console.log('[LIVE MAIN MENU] Back to servers button event bound');
+ } else {
+ console.warn('[LIVE MAIN MENU] Back to servers button not found');
+ }
+
+ console.log('[LIVE MAIN MENU] All events bound (with warnings for missing elements)');
+ }
+
+ checkExistingAuth() {
+ const token = localStorage.getItem('authToken');
+ const user = localStorage.getItem('currentUser');
+
+ if (token && user) {
+ this.authToken = token;
+ this.currentUser = JSON.parse(user);
+ console.log('[LIVE MAIN MENU] Found existing auth token, validating...');
+ this.validateToken();
+ } else {
+ console.log('[LIVE MAIN MENU] No existing auth found');
+ this.showLoginSection();
+ }
+ }
+
+ async validateToken() {
+ try {
+ const response = await fetch(`${this.apiBaseUrl}/auth/verify`, {
+ method: 'GET',
+ headers: {
+ 'Authorization': `Bearer ${this.authToken}`,
+ 'Content-Type': 'application/json'
+ }
+ });
+
+ if (response.ok) {
+ const data = await response.json();
+ this.currentUser = data.user;
+ localStorage.setItem('currentUser', JSON.stringify(this.currentUser));
+ console.log('[LIVE MAIN MENU] Token validated successfully');
+ this.showServerSection();
+ } else {
+ console.log('[LIVE MAIN MENU] Token invalid, clearing auth');
+ this.clearAuth();
+ this.showLoginSection();
+ }
+ } catch (error) {
+ console.error('[LIVE MAIN MENU] Token validation failed:', error);
+ this.clearAuth();
+ this.showLoginSection();
+ }
+ }
+
+ clearAuth() {
+ this.authToken = null;
+ this.currentUser = null;
+ localStorage.removeItem('authToken');
+ localStorage.removeItem('currentUser');
+ }
+
+ showSection(sectionName, animate = true) {
+ // Don't re-animate if we're showing the same section
+ if (this.currentSection === sectionName) {
+ animate = false;
+ }
+
+ // Hide all sections
+ this.loginSection?.classList.add('hidden');
+ this.serverSection?.classList.add('hidden');
+ this.serverConfirmSection?.classList.add('hidden');
+ this.optionsSection?.classList.add('hidden');
+
+ // Show selected section
+ const section = document.getElementById(`${sectionName}Section`);
+ if (section) {
+ section.classList.remove('hidden');
+
+ // Control animation
+ if (animate) {
+ section.style.animation = 'fadeInUp 0.5s ease-out';
+ } else {
+ section.style.animation = 'none';
+ }
+
+ this.currentSection = sectionName;
+ }
+ }
+
+ showLoginSection() {
+ this.showSection('login');
+ this.clearLoginForm();
+ }
+
+ showServerSection(refreshServers = true, animate = true) {
+ this.showSection('server', animate);
+ if (refreshServers) {
+ this.refreshServerList();
+ }
+ }
+
+ showServerConfirmSection() {
+ this.showSection('serverConfirm');
+ this.updateServerConfirmSection();
+ }
+
+ clearLoginForm() {
+ if (this.emailInput) this.emailInput.value = '';
+ if (this.passwordInput) this.passwordInput.value = '';
+ this.clearLoginNotice();
+ }
+
+ clearLoginNotice() {
+ if (this.loginNotice) {
+ this.loginNotice.innerHTML = `
+ Connect to the live server to play
+
+ Note: You need an internet connection to play Galaxy Strike Online.
+
+ `;
+ }
+ }
+
+ showLoginNotice(message, type = 'info') {
+ if (this.loginNotice) {
+ const iconClass = type === 'error' ? 'fa-exclamation-triangle' :
+ type === 'success' ? 'fa-check-circle' : 'fa-info-circle';
+ this.loginNotice.innerHTML = ` ${message}
`;
+ this.loginNotice.className = `login-notice ${type}`;
+ this.loginNotice.style.display = 'block';
+ }
+ }
+
+ hideLoginNotice() {
+ if (this.loginNotice) {
+ this.loginNotice.style.display = 'none';
+ }
+ }
+
+ async handleLogin() {
+ const email = this.emailInput?.value?.trim();
+ const password = this.passwordInput?.value?.trim();
+
+ if (!email || !password) {
+ this.showLoginNotice('Please enter email and password', 'error');
+ return;
+ }
+
+ // Disable login button
+ if (this.loginBtn) {
+ this.loginBtn.disabled = true;
+ this.loginBtn.innerHTML = ' Logging in...';
+ }
+
+ try {
+ const response = await fetch(`${this.apiBaseUrl}/auth/login`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({ email, password })
+ });
+
+ // Check if response is HTML (error page) instead of JSON
+ const contentType = response.headers.get('content-type');
+ if (!contentType || !contentType.includes('application/json')) {
+ throw new Error('API server is not available. Please check your connection.');
+ }
+
+ const data = await response.json();
+
+ if (response.ok) {
+ this.authToken = data.token;
+ this.currentUser = data.user;
+
+ // Save to localStorage
+ localStorage.setItem('authToken', this.authToken);
+ localStorage.setItem('currentUser', JSON.stringify(this.currentUser));
+
+ this.showLoginNotice('Login successful!', 'success');
+ console.log('[LIVE MAIN MENU] Login successful');
+
+ // Show server browser after a short delay
+ setTimeout(() => this.showServerSection(), 1000);
+ } else {
+ this.showLoginNotice(data.error || 'Login failed', 'error');
+ }
+ } catch (error) {
+ console.error('[LIVE MAIN MENU] Login error:', error);
+ this.showLoginNotice('Connection error. Please try again.', 'error');
+ } finally {
+ // Re-enable login button
+ if (this.loginBtn) {
+ this.loginBtn.disabled = false;
+ this.loginBtn.innerHTML = ' Login';
+ }
+ }
+ }
+
+ async handleRegister() {
+ const email = this.emailInput?.value?.trim();
+ const password = this.passwordInput?.value?.trim();
+
+ if (!email || !password) {
+ this.showLoginNotice('Please enter email and password', 'error');
+ return;
+ }
+
+ if (password.length < 6) {
+ this.showLoginNotice('Password must be at least 6 characters', 'error');
+ return;
+ }
+
+ // Disable register button
+ if (this.registerBtn) {
+ this.registerBtn.disabled = true;
+ this.registerBtn.innerHTML = ' Registering...';
+ }
+
+ try {
+ // Generate a username from email
+ const username = email.split('@')[0];
+
+ const response = await fetch(`${this.apiBaseUrl}/auth/register`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({ username, email, password })
+ });
+
+ // Check if response is HTML (error page) instead of JSON
+ const contentType = response.headers.get('content-type');
+ if (!contentType || !contentType.includes('application/json')) {
+ throw new Error('API server is not available. Please check your connection.');
+ }
+
+ const data = await response.json();
+
+ if (response.ok) {
+ this.authToken = data.token;
+ this.currentUser = data.user;
+
+ // Save to localStorage
+ localStorage.setItem('authToken', this.authToken);
+ localStorage.setItem('currentUser', JSON.stringify(this.currentUser));
+
+ this.showLoginNotice('Registration successful!', 'success');
+ console.log('[LIVE MAIN MENU] Registration successful');
+
+ // Show server browser after a short delay
+ setTimeout(() => this.showServerSection(), 1000);
+ } else {
+ this.showLoginNotice(data.error || 'Registration failed', 'error');
+ }
+ } catch (error) {
+ console.error('[LIVE MAIN MENU] Registration error:', error);
+ this.showLoginNotice('Connection error. Please try again.', 'error');
+ } finally {
+ // Re-enable register button
+ if (this.registerBtn) {
+ this.registerBtn.disabled = false;
+ this.registerBtn.innerHTML = ' Register';
+ }
+ }
+ }
+
+ async refreshServerListWithoutAnimation() {
+ // Ensure we're in server section without animation
+ if (this.currentSection !== 'server') {
+ this.showServerSection(true, false); // Refresh but don't animate
+ } else {
+ // Already in server section, just refresh
+ await this.refreshServerList();
+ }
+ }
+
+ async refreshServerList() {
+ if (!this.authToken) {
+ console.error('[LIVE MAIN MENU] No auth token for server list');
+ return;
+ }
+
+ // Show loading state
+ if (this.serverLoading) this.serverLoading.classList.remove('hidden');
+ if (this.serverEmpty) this.serverEmpty.classList.add('hidden');
+
+ try {
+ let response;
+
+ // Use SimpleLocalServer mock API if in local mode
+ if (this.isLocalMode && window.localServerManager && window.localServerManager.localServer && window.localServerManager.localServer.mockRequest) {
+ console.log('[LIVE MAIN MENU] Using SimpleLocalServer mock API for server list');
+ response = await window.localServerManager.localServer.mockRequest('GET', '/api/servers');
+ } else {
+ console.log('[LIVE MAIN MENU] Fetching server list from:', `${this.apiBaseUrl}/servers`);
+ response = await fetch(`${this.apiBaseUrl}/servers`, {
+ method: 'GET',
+ headers: {
+ 'Authorization': `Bearer ${this.authToken}`,
+ 'Content-Type': 'application/json'
+ }
+ });
+ }
+
+ console.log('[LIVE MAIN MENU] Server list response status:', response.status);
+
+ // Check if response is HTML (error page) instead of JSON
+ const contentType = response.headers ? response.headers.get('content-type') : 'application/json';
+ if (!contentType || !contentType.includes('application/json')) {
+ throw new Error('API server is not available. Please check your connection.');
+ }
+
+ if (response.ok) {
+ const data = await response.json();
+ this.servers = data.servers || [];
+ console.log('[LIVE MAIN MENU] Server list loaded:', this.servers);
+ this.renderServerList();
+ } else {
+ const errorText = await response.text();
+ console.error('[LIVE MAIN MENU] Failed to load server list - Status:', response.status, 'Error:', errorText);
+ this.servers = [];
+ this.renderServerList();
+
+ // Show error message to user
+ this.showLoginNotice(`Failed to connect to server: ${response.status}`, 'error');
+ }
+ } catch (error) {
+ console.error('[LIVE MAIN MENU] Server list error:', error);
+ this.servers = [];
+ this.renderServerList();
+
+ // Show error message to user
+ this.showLoginNotice(`Network error: ${error.message}`, 'error');
+ } finally {
+ // Hide loading state
+ if (this.serverLoading) this.serverLoading.classList.add('hidden');
+ }
+ }
+
+ renderServerList() {
+ if (!this.servers) return;
+
+ const filteredServers = this.getFilteredServers();
+
+ // Handle empty state
+ if (filteredServers.length === 0) {
+ if (this.serverEmpty) this.serverEmpty.classList.remove('hidden');
+ // Remove existing server items smoothly
+ this.removeServerItemsSmoothly();
+ return;
+ } else {
+ if (this.serverEmpty) this.serverEmpty.classList.add('hidden');
+ }
+
+ // Get current server items
+ const currentItems = this.serverList?.querySelectorAll('.server-item') || [];
+
+ // Create new server items HTML
+ const serverListHtml = filteredServers.map(server => this.createServerItem(server)).join('');
+
+ // Create a temporary container to hold new items
+ const tempDiv = document.createElement('div');
+ tempDiv.innerHTML = serverListHtml;
+ const newItems = Array.from(tempDiv.querySelectorAll('.server-item'));
+
+ // Smooth update: fade out current items, then replace with new ones
+ this.updateServerItemsSmoothly(currentItems, newItems);
+ }
+
+ removeServerItemsSmoothly() {
+ const existingItems = this.serverList?.querySelectorAll('.server-item') || [];
+
+ if (existingItems.length === 0) return;
+
+ // Add fade-out transition
+ existingItems.forEach(item => {
+ item.style.transition = 'opacity 0.2s ease-out';
+ item.style.opacity = '0';
+ });
+
+ // Remove items after fade out
+ setTimeout(() => {
+ existingItems.forEach(item => item.remove());
+ }, 200);
+ }
+
+ updateServerItemsSmoothly(currentItems, newItems) {
+ // Fade out current items
+ if (currentItems.length > 0) {
+ currentItems.forEach(item => {
+ item.style.transition = 'opacity 0.2s ease-out';
+ item.style.opacity = '0';
+ });
+
+ // Replace with new items after fade out
+ setTimeout(() => {
+ currentItems.forEach(item => item.remove());
+ this.addNewServerItems(newItems);
+ }, 200);
+ } else {
+ // No current items, add new ones immediately
+ this.addNewServerItems(newItems);
+ }
+ }
+
+ addNewServerItems(newItems) {
+ if (!this.serverList) return;
+
+ // Add fade-in transition to new items
+ newItems.forEach(item => {
+ item.style.transition = 'opacity 0.3s ease-in';
+ item.style.opacity = '0';
+ });
+
+ // Add new items to DOM
+ newItems.forEach(item => {
+ this.serverList.appendChild(item);
+ });
+
+ // Add click handlers
+ newItems.forEach(item => {
+ item.addEventListener('click', () => {
+ const serverId = item.dataset.serverId;
+ this.selectServer(serverId);
+ });
+ });
+
+ // Fade in new items
+ setTimeout(() => {
+ newItems.forEach(item => {
+ item.style.opacity = '1';
+ });
+ }, 50);
+ }
+
+ createServerItem(server) {
+ const playerCount = `${server.currentPlayers}/${server.maxPlayers}`;
+ const createdDate = new Date(server.createdAt).toLocaleDateString();
+
+ return `
+
+
+
${server.name}
+
+
+
+ ${server.region || 'Unknown'}
+
+
+
+ ${createdDate}
+
+
+
+
+ ${server.type}
+ ${playerCount}
+
+
+
+ `;
+ }
+
+ getFilteredServers() {
+ let filtered = [...this.servers];
+
+ const regionFilter = this.regionFilter?.value;
+ const typeFilter = this.typeFilter?.value;
+
+ if (regionFilter) {
+ filtered = filtered.filter(server => server.region === regionFilter);
+ }
+
+ if (typeFilter) {
+ filtered = filtered.filter(server => server.type === typeFilter);
+ }
+
+ return filtered;
+ }
+
+ filterServers() {
+ this.renderServerList();
+ }
+
+ selectServer(serverId) {
+ this.selectedServer = this.servers.find(server => server.id === serverId);
+ if (this.selectedServer) {
+ console.log('[LIVE MAIN MENU] Server selected:', this.selectedServer);
+ this.showServerConfirmSection();
+ }
+ }
+
+ updateServerConfirmSection() {
+ if (!this.selectedServer) return;
+
+ // Update server details
+ if (this.selectedServerName) this.selectedServerName.textContent = this.selectedServer.name;
+ if (this.selectedServerType) this.selectedServerType.textContent = this.selectedServer.type;
+ if (this.selectedServerRegion) this.selectedServerRegion.textContent = this.selectedServer.region || 'Unknown';
+ if (this.selectedServerPlayers) this.selectedServerPlayers.textContent =
+ `${this.selectedServer.currentPlayers}/${this.selectedServer.maxPlayers}`;
+ if (this.selectedServerOwner) this.selectedServerOwner.textContent = this.selectedServer.ownerName || 'Unknown';
+ }
+
+ async joinServer() {
+ if (!this.selectedServer || !this.authToken) {
+ console.error('[LIVE MAIN MENU] No server selected or not authenticated');
+ return;
+ }
+
+ // Disable join button
+ if (this.joinServerBtn) {
+ this.joinServerBtn.disabled = true;
+ this.joinServerBtn.innerHTML = ' Joining...';
+ }
+
+ try {
+ let response;
+
+ // Use SimpleLocalServer mock API if in local mode
+ if (this.isLocalMode && window.localServerManager && window.localServerManager.localServer && window.localServerManager.localServer.mockRequest) {
+ console.log('[LIVE MAIN MENU] Using SimpleLocalServer mock API for join server');
+ response = await window.localServerManager.localServer.mockRequest('POST', `/servers/${this.selectedServer.id}/join`, {
+ userId: this.currentUser.id,
+ token: this.authToken
+ });
+ } else {
+ console.log('[LIVE MAIN MENU] Using real fetch for join server');
+ response = await fetch(`${this.apiBaseUrl}/servers/${this.selectedServer.id}/join`, {
+ method: 'POST',
+ headers: {
+ 'Authorization': `Bearer ${this.authToken}`,
+ 'Content-Type': 'application/json'
+ }
+ });
+ }
+
+ // Check if response is HTML (error page) instead of JSON
+ const contentType = response.headers ? response.headers.get('content-type') : 'application/json';
+ if (!contentType || !contentType.includes('application/json')) {
+ throw new Error('API server is not available. Please check your connection.');
+ }
+
+ const data = await response.json();
+
+ if (response.ok) {
+ console.log('[LIVE MAIN MENU] Joined server successfully');
+
+ // Apply existing save data if in local mode
+ if (this.isLocalMode && window.localServerManager && window.localServerManager.localServer) {
+ console.log('[LIVE MAIN MENU] Applying existing save data for local mode...');
+
+ // Store save data for later application (after GameEngine is created)
+ this.pendingSaveData = window.localServerManager.localServer.existingSaveData;
+
+ if (this.pendingSaveData) {
+ console.log('[LIVE MAIN MENU] Save data stored for later application');
+ } else {
+ console.log('[LIVE MAIN MENU] No save data to store');
+ }
+ }
+
+ this.launchMultiplayerGame(this.selectedServer, data.server);
+ } else {
+ console.error('[LIVE MAIN MENU] Failed to join server:', data.error);
+ alert(data.error || 'Failed to join server');
+ }
+ } catch (error) {
+ console.error('[LIVE MAIN MENU] Join server error:', error);
+ alert('Connection error. Please try again.');
+ } finally {
+ // Re-enable join button
+ if (this.joinServerBtn) {
+ this.joinServerBtn.disabled = false;
+ this.joinServerBtn.innerHTML = ' Join Server';
+ }
+ }
+ }
+
+ showServerInfo() {
+ if (!this.selectedServer) return;
+
+ const info = `
+Server Information:
+Name: ${this.selectedServer.name}
+Type: ${this.selectedServer.type}
+Region: ${this.selectedServer.region}
+Players: ${this.selectedServer.currentPlayers}/${this.selectedServer.maxPlayers}
+Owner: ${this.selectedServer.ownerName}
+Created: ${new Date(this.selectedServer.createdAt).toLocaleString()}
+Status: ${this.selectedServer.status}
+ `.trim();
+
+ alert(info);
+ }
+
+ leaveServer() {
+ this.selectedServer = null;
+ this.showServerSection();
+ }
+
+ showCreateServerDialog() {
+ return new Promise((resolve) => {
+ // Create modal dialog
+ const modal = document.createElement('div');
+ modal.className = 'modal-overlay';
+ modal.innerHTML = `
+
+
+
+
+ Server Name:
+
+
+
+ Server Type:
+
+ Public
+ Private
+
+
+
+ Region:
+
+ US East
+ US West
+ Europe
+ Asia
+
+
+
+ Max Players:
+
+
+
+
+
+ `;
+
+ // Add styles for modal
+ const style = document.createElement('style');
+ style.textContent = `
+ .modal-overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background: rgba(0, 0, 0, 0.8);
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ z-index: 10000;
+ }
+ .modal-dialog {
+ background: var(--bg-primary);
+ border: 1px solid var(--border-color);
+ border-radius: 8px;
+ padding: 20px;
+ min-width: 400px;
+ max-width: 500px;
+ }
+ .modal-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 20px;
+ }
+ .modal-header h3 {
+ margin: 0;
+ color: var(--text-primary);
+ }
+ .modal-close {
+ background: none;
+ border: none;
+ color: var(--text-secondary);
+ font-size: 24px;
+ cursor: pointer;
+ }
+ .form-group {
+ margin-bottom: 15px;
+ }
+ .form-group label {
+ display: block;
+ margin-bottom: 5px;
+ color: var(--text-primary);
+ }
+ .form-group input,
+ .form-group select {
+ width: 100%;
+ padding: 8px;
+ background: var(--bg-secondary);
+ border: 1px solid var(--border-color);
+ border-radius: 4px;
+ color: var(--text-primary);
+ }
+ .modal-footer {
+ display: flex;
+ justify-content: flex-end;
+ gap: 10px;
+ margin-top: 20px;
+ }
+ .btn {
+ padding: 8px 16px;
+ border: none;
+ border-radius: 4px;
+ cursor: pointer;
+ }
+ .btn-primary {
+ background: var(--primary-color);
+ color: white;
+ }
+ .btn-secondary {
+ background: var(--bg-secondary);
+ color: var(--text-primary);
+ border: 1px solid var(--border-color);
+ }
+ `;
+ document.head.appendChild(style);
+
+ // Add modal to page
+ document.body.appendChild(modal);
+
+ // Get elements
+ const closeBtn = modal.querySelector('.modal-close');
+ const cancelBtn = modal.querySelector('#cancelBtn');
+ const createBtn = modal.querySelector('#createBtn');
+ const nameInput = modal.querySelector('#serverName');
+
+ // Event handlers
+ const closeModal = () => {
+ document.body.removeChild(modal);
+ document.head.removeChild(style);
+ resolve(null);
+ };
+
+ const handleCreate = () => {
+ const name = nameInput.value.trim();
+ const type = modal.querySelector('#serverType').value;
+ const region = modal.querySelector('#serverRegion').value;
+ const maxPlayers = parseInt(modal.querySelector('#maxPlayers').value);
+
+ if (!name) {
+ nameInput.focus();
+ return;
+ }
+
+ if (maxPlayers < 1 || maxPlayers > 20) {
+ modal.querySelector('#maxPlayers').focus();
+ return;
+ }
+
+ document.body.removeChild(modal);
+ document.head.removeChild(style);
+ resolve({ name, type, region, maxPlayers });
+ };
+
+ closeBtn.addEventListener('click', closeModal);
+ cancelBtn.addEventListener('click', closeModal);
+ createBtn.addEventListener('click', handleCreate);
+
+ // Handle Enter key in name input
+ nameInput.addEventListener('keypress', (e) => {
+ if (e.key === 'Enter') {
+ handleCreate();
+ }
+ });
+
+ // Focus on name input
+ nameInput.focus();
+ });
+ }
+
+ async handleCreateServer() {
+ try {
+ // Get server details from user using custom dialog
+ const serverDetails = await this.showCreateServerDialog();
+
+ if (!serverDetails) {
+ return; // User cancelled
+ }
+
+ const { name, type, maxPlayers, region } = serverDetails;
+
+ // Show loading state
+ if (this.serverLoading) this.serverLoading.classList.remove('hidden');
+
+ const response = await fetch(`${this.apiBaseUrl}/servers/create`, {
+ method: 'POST',
+ headers: {
+ 'Authorization': `Bearer ${this.authToken}`,
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({
+ name,
+ type,
+ maxPlayers,
+ region,
+ username: this.currentUser.username
+ })
+ });
+
+ // Check if response is HTML (error page) instead of JSON
+ const contentType = response.headers.get('content-type');
+ if (!contentType || !contentType.includes('application/json')) {
+ throw new Error('API server is not available. Please check your connection.');
+ }
+
+ if (response.ok) {
+ const data = await response.json();
+ console.log('[LIVE MAIN MENU] Server created:', data.server);
+
+ // Refresh server list
+ await this.refreshServerList();
+
+ // Show success message
+ this.showLoginNotice('Server created successfully!', 'success');
+
+ // Auto-join the created server
+ this.joinServer(data.server.id);
+ } else {
+ const errorData = await response.json();
+ console.error('[LIVE MAIN MENU] Failed to create server:', errorData);
+ this.showLoginNotice(`Failed to create server: ${errorData.error}`, 'error');
+ }
+ } catch (error) {
+ console.error('[LIVE MAIN MENU] Create server error:', error);
+ this.showLoginNotice('Failed to create server', 'error');
+ } finally {
+ // Hide loading state
+ if (this.serverLoading) this.serverLoading.classList.add('hidden');
+ }
+ }
+
+ launchMultiplayerGame(server, serverData) {
+ console.log('[LIVE MAIN MENU] Launching multiplayer game on server:', server.name);
+ // Hide main menu and start game in multiplayer mode
+ this.hideLoadingScreenAndShowGame();
+ this.initializeMultiplayerGame(server, serverData);
+ }
+
+ hideLoadingScreenAndShowGame() {
+ console.log('[LIVE MAIN MENU] Hiding main menu and showing game interface');
+
+ // Hide main menu
+ if (this.mainMenu) {
+ this.mainMenu.classList.add('hidden');
+ }
+
+ // Show game interface
+ const gameInterface = document.getElementById('gameInterface');
+ if (gameInterface) {
+ gameInterface.classList.remove('hidden');
+ }
+
+ // Note: gameContainer element doesn't exist in HTML, so we skip it
+ }
+
+ initializeMultiplayerGame(server, serverData) {
+ console.log('[LIVE MAIN MENU] Initializing multiplayer game');
+
+ // Check if GameEngine is available, if not create a basic fallback
+ if (typeof GameEngine === 'undefined') {
+ console.warn('[LIVE MAIN MENU] GameEngine class not available, creating fallback');
+
+ // Create a basic fallback GameEngine class
+ window.GameEngine = class GameEngine extends EventTarget {
+ constructor() {
+ super();
+ this.systems = {};
+ this.gameTime = 0;
+ this.isRunning = false;
+ this.isFallback = true; // Mark as fallback for UIManager
+ console.log('[LIVE MAIN MENU] Fallback GameEngine created');
+ }
+
+ async init() {
+ console.log('[LIVE MAIN MENU] Fallback GameEngine init() called');
+ return true;
+ }
+
+ setMultiplayerMode(isMultiplayer, socket, serverData, currentUser) {
+ console.log('[LIVE MAIN MENU] Fallback GameEngine setMultiplayerMode called');
+ this.isMultiplayer = isMultiplayer;
+ this.socket = socket;
+ this.serverData = serverData;
+ this.currentUser = currentUser;
+ }
+
+ startGame(continueGame) {
+ console.log('[LIVE MAIN MENU] Fallback GameEngine startGame called, continueGame:', continueGame);
+ this.isRunning = true;
+
+ // Add simple return to menu functionality
+ window.returnToMainMenu = () => {
+ console.log('[LIVE MAIN MENU] Return to main menu requested');
+ if (window.liveMainMenu) {
+ window.liveMainMenu.showLoginSection();
+ }
+ };
+ }
+
+ getPerformanceStats() {
+ console.log('[LIVE MAIN MENU] Fallback GameEngine getPerformanceStats called');
+ return {
+ gameTime: this.gameTime,
+ isRunning: this.isRunning,
+ fps: 60,
+ memory: 'N/A (fallback mode)'
+ };
+ }
+
+ showNotification(message, type = 'info', duration = 3000) {
+ console.log(`[LIVE MAIN MENU] Fallback notification: ${message} (${type})`);
+ // Simple console notification for fallback mode
+ if (typeof console !== 'undefined') {
+ console.log(`📢 ${type.toUpperCase()}: ${message}`);
+ }
+ }
+
+ save() {
+ console.log('[LIVE MAIN MENU] Fallback GameEngine save called');
+ // Save will be handled by the main save logic when it detects local mode
+ return Promise.resolve({ success: true });
+ }
+ };
+ }
+
+ // Create GameEngine if it doesn't exist
+ if (!window.game) {
+ console.log('[LIVE MAIN MENU] Creating new GameEngine instance for multiplayer');
+ try {
+ window.game = new GameEngine();
+
+ // Initialize the game engine
+ window.game.init().then(() => {
+ console.log('[LIVE MAIN MENU] GameEngine initialized successfully for multiplayer');
+
+ // Apply pending save data if available (after GameEngine is ready)
+ if (window.liveMainMenu && window.liveMainMenu.pendingSaveData) {
+ console.log('[LIVE MAIN MENU] Applying pending save data after GameEngine init...');
+
+ try {
+ const saveData = window.liveMainMenu.pendingSaveData;
+ console.log('[LIVE MAIN MENU] Save data structure:', {
+ hasPlayer: !!saveData.player,
+ hasInventory: !!saveData.inventory,
+ hasEconomy: !!saveData.economy,
+ hasIdleSystem: !!saveData.idleSystem,
+ hasDungeonSystem: !!saveData.dungeonSystem,
+ hasSkillSystem: !!saveData.skillSystem,
+ hasBaseSystem: !!saveData.baseSystem,
+ hasQuestSystem: !!saveData.questSystem,
+ gameTime: saveData.gameTime,
+ playerLevel: saveData.player?.stats?.level
+ });
+
+ console.log('[LIVE MAIN MENU] Game systems available:', {
+ hasGame: !!window.game,
+ hasSystems: !!window.game?.systems,
+ hasPlayer: !!window.game?.systems?.player,
+ hasInventory: !!window.game?.systems?.inventory,
+ hasEconomy: !!window.game?.systems?.economy,
+ hasIdleSystem: !!window.game?.systems?.idleSystem,
+ hasDungeonSystem: !!window.game?.systems?.dungeonSystem,
+ hasSkillSystem: !!window.game?.systems?.skillSystem,
+ hasBaseSystem: !!window.game?.systems?.baseSystem,
+ hasQuestSystem: !!window.game?.systems?.questSystem
+ });
+
+ let appliedCount = 0;
+
+ // Apply save data to game systems
+ if (saveData.player && window.game.systems.player) {
+ window.game.systems.player.load(saveData.player);
+ console.log('[LIVE MAIN MENU] ✅ Player data loaded from pending save');
+ appliedCount++;
+ } else {
+ console.log('[LIVE MAIN MENU] ❌ Player data NOT loaded - saveData.player:', !!saveData.player, 'window.game.systems.player:', !!window.game?.systems?.player);
+ }
+
+ if (saveData.inventory && window.game.systems.inventory) {
+ window.game.systems.inventory.load(saveData.inventory);
+ console.log('[LIVE MAIN MENU] ✅ Inventory data loaded from pending save');
+ appliedCount++;
+ } else {
+ console.log('[LIVE MAIN MENU] ❌ Inventory data NOT loaded - saveData.inventory:', !!saveData.inventory, 'window.game.systems.inventory:', !!window.game?.systems?.inventory);
+ }
+
+ if (saveData.economy && window.game.systems.economy) {
+ window.game.systems.economy.load(saveData.economy);
+ console.log('[LIVE MAIN MENU] ✅ Economy data loaded from pending save');
+ appliedCount++;
+ } else {
+ console.log('[LIVE MAIN MENU] ❌ Economy data NOT loaded - saveData.economy:', !!saveData.economy, 'window.game.systems.economy:', !!window.game?.systems?.economy);
+ }
+
+ if (saveData.idleSystem && window.game.systems.idleSystem) {
+ window.game.systems.idleSystem.load(saveData.idleSystem);
+ console.log('[LIVE MAIN MENU] ✅ Idle system data loaded from pending save');
+ appliedCount++;
+ } else {
+ console.log('[LIVE MAIN MENU] ❌ Idle system data NOT loaded - saveData.idleSystem:', !!saveData.idleSystem, 'window.game.systems.idleSystem:', !!window.game?.systems?.idleSystem);
+ }
+
+ if (saveData.dungeonSystem && window.game.systems.dungeonSystem) {
+ window.game.systems.dungeonSystem.load(saveData.dungeonSystem);
+ console.log('[LIVE MAIN MENU] ✅ Dungeon system data loaded from pending save');
+ appliedCount++;
+ } else {
+ console.log('[LIVE MAIN MENU] ❌ Dungeon system data NOT loaded - saveData.dungeonSystem:', !!saveData.dungeonSystem, 'window.game.systems.dungeonSystem:', !!window.game?.systems?.dungeonSystem);
+ }
+
+ if (saveData.skillSystem && window.game.systems.skillSystem) {
+ window.game.systems.skillSystem.load(saveData.skillSystem);
+ console.log('[LIVE MAIN MENU] ✅ Skill system data loaded from pending save');
+ appliedCount++;
+ } else {
+ console.log('[LIVE MAIN MENU] ❌ Skill system data NOT loaded - saveData.skillSystem:', !!saveData.skillSystem, 'window.game.systems.skillSystem:', !!window.game?.systems?.skillSystem);
+ }
+
+ if (saveData.baseSystem && window.game.systems.baseSystem) {
+ window.game.systems.baseSystem.load(saveData.baseSystem);
+ console.log('[LIVE MAIN MENU] ✅ Base system data loaded from pending save');
+ appliedCount++;
+ } else {
+ console.log('[LIVE MAIN MENU] ❌ Base system data NOT loaded - saveData.baseSystem:', !!saveData.baseSystem, 'window.game.systems.baseSystem:', !!window.game?.systems?.baseSystem);
+ }
+
+ if (saveData.questSystem && window.game.systems.questSystem) {
+ window.game.systems.questSystem.load(saveData.questSystem);
+ console.log('[LIVE MAIN MENU] ✅ Quest system data loaded from pending save');
+ appliedCount++;
+ } else {
+ console.log('[LIVE MAIN MENU] ❌ Quest system data NOT loaded - saveData.questSystem:', !!saveData.questSystem, 'window.game.systems.questSystem:', !!window.game?.systems?.questSystem);
+ }
+
+ if (saveData.gameTime && window.game) {
+ window.game.gameTime = saveData.gameTime;
+ console.log('[LIVE MAIN MENU] ✅ Game time loaded from pending save:', saveData.gameTime);
+ appliedCount++;
+ } else {
+ console.log('[LIVE MAIN MENU] ❌ Game time NOT loaded - saveData.gameTime:', !!saveData.gameTime, 'window.game:', !!window.game);
+ }
+
+ console.log(`[LIVE MAIN MENU] Save data application complete: ${appliedCount}/9 systems loaded`);
+
+ if (appliedCount === 0) {
+ console.warn('[LIVE MAIN MENU] ⚠️ NO save data was applied! This is why the UI shows fresh game state.');
+ console.warn('[LIVE MAIN MENU] ⚠️ The fallback GameEngine does not have the required game systems.');
+ }
+
+ console.log('[LIVE MAIN MENU] Pending save data applied successfully');
+
+ // Clear pending save data
+ window.liveMainMenu.pendingSaveData = null;
+
+ } catch (error) {
+ console.error('[LIVE MAIN MENU] Error applying pending save data:', error);
+ }
+ } else {
+ console.log('[LIVE MAIN MENU] No pending save data to apply');
+ }
+
+ // Now initialize multiplayer mode through GameInitializer
+ if (window.gameInitializer) {
+ window.gameInitializer.initializeMultiplayer(server, serverData, this.authToken, this.currentUser);
+ }
+ }).catch(error => {
+ console.error('[LIVE MAIN MENU] Failed to initialize GameEngine for multiplayer:', error);
+ this.showLoginNotice('Failed to initialize game: ' + error.message, 'error');
+ });
+ } catch (error) {
+ console.error('[LIVE MAIN MENU] Error creating GameEngine:', error);
+ this.showLoginNotice('Failed to create game engine: ' + error.message, 'error');
+ }
+ } else {
+ console.log('[LIVE MAIN MENU] GameEngine already exists, initializing multiplayer mode');
+
+ // Initialize multiplayer mode through GameInitializer
+ if (window.gameInitializer) {
+ window.gameInitializer.initializeMultiplayer(server, serverData, this.authToken, this.currentUser);
+ }
+ }
+ }
+
+ async startLocalServer() {
+ console.log('[LIVE MAIN MENU] Starting local server...');
+
+ try {
+ if (!window.localServerManager) {
+ console.error('[LIVE MAIN MENU] LocalServerManager not available');
+ this.showLoginNotice('Local server manager not available', 'error');
+ return;
+ }
+
+ // Update button to show loading state
+ if (this.createServerBtn) {
+ this.createServerBtn.innerHTML = ' Starting...';
+ this.createServerBtn.disabled = true;
+ }
+
+ // Show loading message
+ this.showLoginNotice('Starting local server...', 'info');
+
+ const result = await window.localServerManager.startServer();
+
+ if (result.success) {
+ console.log('[LIVE MAIN MENU] Local server started successfully:', result);
+
+ // Update to local mode
+ this.isLocalMode = true;
+ this.apiBaseUrl = `http://localhost:${result.port}/api`;
+ this.gameServerUrl = `http://localhost:${result.port}`;
+
+ console.log(`[LIVE MAIN MENU] Updated to local mode - API: ${this.apiBaseUrl}`);
+
+ // Update button to show running state
+ if (this.createServerBtn) {
+ this.createServerBtn.innerHTML = ' Local Server Running';
+ this.createServerBtn.classList.remove('btn-primary');
+ this.createServerBtn.classList.add('btn-success');
+ }
+
+ // Auto-login for local mode
+ await this.autoLoginLocalMode();
+
+ // Show success message
+ this.showLoginNotice('Local server started successfully!', 'success');
+
+ } else {
+ console.error('[LIVE MAIN MENU] Failed to start local server:', result.error);
+ this.showLoginNotice(`Failed to start local server: ${result.error}`, 'error');
+
+ // Reset button to original state
+ if (this.createServerBtn) {
+ this.createServerBtn.innerHTML = ' Start Local Server';
+ this.createServerBtn.disabled = false;
+ }
+ }
+
+ } catch (error) {
+ console.error('[LIVE MAIN MENU] Error starting local server:', error);
+ this.showLoginNotice('Error starting local server', 'error');
+
+ // Reset button to original state
+ if (this.createServerBtn) {
+ this.createServerBtn.innerHTML = ' Start Local Server';
+ this.createServerBtn.disabled = false;
+ }
+ }
+ }
+
+ checkForLocalServer() {
+ console.log('[LIVE MAIN MENU] Checking for local server...');
+
+ // Check if local server manager is available and running
+ if (window.localServerManager) {
+ const serverStatus = window.localServerManager.getStatus();
+
+ if (serverStatus.isRunning) {
+ console.log('[LIVE MAIN MENU] Local server detected, switching to local mode');
+ this.isLocalMode = true;
+ this.apiBaseUrl = `http://localhost:${serverStatus.port}/api`;
+ this.gameServerUrl = `http://localhost:${serverStatus.port}`;
+
+ console.log(`[LIVE MAIN MENU] Updated API URL to: ${this.apiBaseUrl}`);
+ console.log(`[LIVE MAIN MENU] Updated Game Server URL to: ${this.gameServerUrl}`);
+
+ // Update login notice to show local mode
+ this.showLoginNotice('Local server active - Singleplayer mode available', 'info');
+
+ // Auto-login for local mode
+ this.autoLoginLocalMode();
+ } else {
+ console.log('[LIVE MAIN MENU] Local server not running, using remote servers');
+ }
+ } else {
+ console.log('[LIVE MAIN MENU] LocalServerManager not available');
+ }
+ }
+
+ async autoLoginLocalMode() {
+ console.log('[LIVE MAIN MENU] Auto-logging in to local mode...');
+
+ try {
+ let response;
+
+ // Use SimpleLocalServer mock API if available
+ if (window.localServerManager && window.localServerManager.localServer && window.localServerManager.localServer.mockRequest) {
+ console.log('[LIVE MAIN MENU] Using SimpleLocalServer mock API');
+ response = await window.localServerManager.localServer.mockRequest('POST', '/api/auth/login', {
+ email: 'local@player.com',
+ password: 'local'
+ });
+ } else {
+ // Fallback to real fetch
+ console.log('[LIVE MAIN MENU] Using real fetch for local login');
+ response = await fetch(`${this.apiBaseUrl}/auth/login`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({
+ email: 'local@player.com',
+ password: 'local'
+ })
+ });
+ }
+
+ const data = await response.json();
+
+ if (data.success) {
+ console.log('[LIVE MAIN MENU] Local mode login successful');
+ this.authToken = data.token;
+ this.currentUser = data.user;
+
+ // Update UI
+ this.hideLoginNotice();
+ this.showServerSection(false, false); // Show section without refreshing or animating
+
+ // Load servers (will show local server)
+ this.refreshServerList();
+ } else {
+ console.error('[LIVE MAIN MENU] Local mode login failed:', data);
+ }
+ } catch (error) {
+ console.error('[LIVE MAIN MENU] Error in local mode auto-login:', error);
+ }
+ }
+}
+
+// Initialize the live main menu when DOM is ready
+function initializeLiveMainMenu() {
+ console.log('[LIVE MAIN MENU] Initializing LiveMainMenu...');
+
+ // Wait a bit for DOM to be fully ready
+ setTimeout(() => {
+ if (!window.liveMainMenu) {
+ console.log('[LIVE MAIN MENU] Creating new LiveMainMenu instance');
+ window.liveMainMenu = new LiveMainMenu();
+ } else {
+ console.log('[LIVE MAIN MENU] LiveMainMenu already exists');
+ }
+ }, 100);
+}
+
+// Try multiple initialization methods
+if (document.readyState === 'loading') {
+ // DOM is still loading, wait for DOMContentLoaded
+ document.addEventListener('DOMContentLoaded', initializeLiveMainMenu);
+} else {
+ // DOM is already ready, initialize immediately
+ initializeLiveMainMenu();
+}
+
+// Also try initialization as a fallback
+setTimeout(() => {
+ if (!window.liveMainMenu) {
+ console.warn('[LIVE MAIN MENU] Fallback initialization triggered');
+ window.liveMainMenu = new LiveMainMenu();
+ }
+}, 1000);
+
+// Export for use in other scripts
+if (typeof module !== 'undefined' && module.exports) {
+ module.exports = LiveMainMenu;
+}
diff --git a/Client/js/ui/MainMenu.js b/Client/js/ui/MainMenu.js
new file mode 100644
index 0000000..1b7f2c5
--- /dev/null
+++ b/Client/js/ui/MainMenu.js
@@ -0,0 +1,1251 @@
+/**
+ * Main Menu System
+ * Handles the main menu UI, save management, and game initialization
+ */
+
+console.log('[MAIN MENU] MainMenu.js script loaded');
+
+class MainMenu {
+ constructor() {
+ console.log('[MAIN MENU] Constructor called');
+ this.mainMenu = document.getElementById('mainMenu');
+ this.selectedSlot = null;
+ this.saveSlotsCount = 3; // Number of save slots
+
+ console.log('[MAIN MENU] Initializing elements');
+ this.initializeElements();
+
+ console.log('[MAIN MENU] Initializing event listeners');
+ this.bindEvents();
+
+ // Delay save folder verification until electronAPI is available
+ this.initializeAfterElectronAPI();
+ }
+
+ initializeAfterElectronAPI() {
+ // Check if electronAPI is available, if not wait for it
+ if (window.electronAPI) {
+ console.log('[MAIN MENU] electronAPI available, proceeding with initialization');
+ this.completeInitialization();
+ } else {
+ console.log('[MAIN MENU] electronAPI not available, waiting...');
+ // Wait for electronAPI to be available
+ setTimeout(() => {
+ this.initializeAfterElectronAPI();
+ }, 100);
+ }
+ }
+
+ async completeInitialization() {
+ console.log('[MAIN MENU] Starting save folder verification');
+ this.verifySaveFolders();
+
+ console.log('[MAIN MENU] Loading save data');
+ await this.loadSaveData();
+
+ console.log('[MAIN MENU] Updating save slots');
+ this.updateSaveSlots();
+
+ console.log('[MAIN MENU] Constructor completed');
+ }
+
+ verifySaveFolders() {
+ console.log('[MAIN MENU] Verifying save folders...');
+ console.log('[MAIN MENU] window.electronAPI available:', !!window.electronAPI);
+
+ try {
+ // Use Electron's IPC instead of direct Node.js access
+ if (window.electronAPI) {
+ console.log('[MAIN MENU] Using Electron API for folder operations');
+ console.log('[MAIN MENU] createSaveFolders method available:', typeof window.electronAPI.createSaveFolders);
+
+ // Request folder creation through main process
+ window.electronAPI.createSaveFolders(this.saveSlotsCount).then((result) => {
+ console.log('[MAIN MENU] createSaveFolders response:', result);
+ if (result.success) {
+ console.log('[MAIN MENU] Save folders created successfully:', result.paths);
+ this.savePaths = result.paths;
+ } else {
+ console.error('[MAIN MENU] Failed to create save folders:', result.error);
+ console.log('[MAIN MENU] Falling back to localStorage save system');
+ this.useLocalStorageFallback();
+ }
+ }).catch((error) => {
+ console.error('[MAIN MENU] IPC error:', error);
+ console.log('[MAIN MENU] Falling back to localStorage save system');
+ this.useLocalStorageFallback();
+ });
+
+ } else {
+ console.log('[MAIN MENU] Electron API not available, using localStorage fallback');
+ this.useLocalStorageFallback();
+ }
+
+ } catch (error) {
+ console.error('[MAIN MENU] Error verifying save folders:', error);
+ console.log('[MAIN MENU] Falling back to localStorage save system');
+ this.useLocalStorageFallback();
+ }
+ }
+
+ hideLoadingScreenAndShowGame() {
+ console.log('[MAIN MENU] Hiding loading screen and showing game interface');
+
+ // Hide loading screen
+ const loadingScreen = document.getElementById('loadingScreen');
+ if (loadingScreen) {
+ loadingScreen.classList.add('hidden');
+ console.log('[MAIN MENU] Loading screen hidden');
+ }
+
+ // Show game interface
+ const gameInterface = document.getElementById('gameInterface');
+ if (gameInterface) {
+ gameInterface.classList.remove('hidden');
+ console.log('[MAIN MENU] Game interface shown');
+ }
+
+ // Show game container
+ const gameContainer = document.getElementById('gameContainer');
+ if (gameContainer) {
+ gameContainer.classList.remove('hidden');
+ console.log('[MAIN MENU] Game container shown');
+ }
+ }
+
+ useLocalStorageFallback() {
+ // Fallback for browser testing or when Electron API fails
+ for (let i = 1; i <= this.saveSlotsCount; i++) {
+ const saveKey = `gso_save_slot_${i}`;
+ if (!localStorage.getItem(saveKey)) {
+ const saveInfo = {
+ slot: i,
+ created: new Date().toISOString(),
+ version: '1.0.0',
+ exists: false
+ };
+ localStorage.setItem(saveKey, JSON.stringify(saveInfo));
+ console.log(`[MAIN MENU] Created localStorage save info for slot ${i}`);
+ }
+ }
+
+ // Mark that we're using localStorage
+ this.savePaths = null;
+ console.log('[MAIN MENU] Using localStorage save system');
+ }
+
+ initializeElements() {
+ // Menu sections
+ this.mainMenu = document.getElementById('mainMenu');
+ this.loginSection = document.getElementById('loginSection');
+ this.saveSection = document.getElementById('saveSection');
+ this.optionsSection = document.getElementById('optionsSection');
+
+ // Login buttons
+ this.ssoLoginBtn = document.getElementById('ssoLoginBtn');
+ this.offlineBtn = document.getElementById('offlineBtn');
+
+ // Save selection
+ this.saveSlots = document.querySelectorAll('.save-slot');
+ this.backToLoginBtn = document.getElementById('backToLoginBtn');
+
+ // Save confirmation section
+ this.confirmSection = document.getElementById('confirmSection');
+ this.confirmSlotTitle = document.getElementById('confirmSlotTitle');
+ this.confirmSaveDetails = document.getElementById('confirmSaveDetails');
+ this.confirmLevel = document.getElementById('confirmLevel');
+ this.confirmShip = document.getElementById('confirmShip');
+ this.confirmPlayTime = document.getElementById('confirmPlayTime');
+ this.confirmLastPlayed = document.getElementById('confirmLastPlayed');
+ this.confirmNewGameBtn = document.getElementById('confirmNewGameBtn');
+ this.confirmContinueBtn = document.getElementById('confirmContinueBtn');
+ this.confirmDeleteBtn = document.getElementById('confirmDeleteBtn');
+ console.log('[MAIN MENU] confirmDeleteBtn found:', !!this.confirmDeleteBtn);
+ this.confirmSettingsBtn = document.getElementById('confirmSettingsBtn');
+ this.backToSavesFromConfirmBtn = document.getElementById('backToSavesFromConfirmBtn');
+
+ // Game options
+ this.newGameBtn = document.getElementById('newGameBtn');
+ this.continueBtn = document.getElementById('continueBtn');
+ this.deleteSaveBtn = document.getElementById('deleteSaveBtn');
+ console.log('[MAIN MENU] deleteSaveBtn found:', !!this.deleteSaveBtn);
+ this.settingsBtn = document.getElementById('settingsBtn');
+ this.quitBtn = document.getElementById('quitBtn');
+ this.backToSavesBtn = document.getElementById('backToSavesBtn');
+
+ // Footer links
+ this.aboutBtn = document.getElementById('aboutBtn');
+ this.helpBtn = document.getElementById('helpBtn');
+ }
+
+ bindEvents() {
+ // Login events
+ this.ssoLoginBtn?.addEventListener('click', () => this.handleSSOLogin());
+ this.offlineBtn?.addEventListener('click', () => this.handleOfflineLogin());
+
+ // Save selection events
+ this.saveSlots.forEach(slot => {
+ slot.addEventListener('click', () => this.selectSaveSlot(slot));
+ });
+
+ this.backToLoginBtn?.addEventListener('click', () => this.showLoginSection());
+
+ // Save confirmation events
+ this.confirmNewGameBtn?.addEventListener('click', () => this.startNewGame());
+ this.confirmContinueBtn?.addEventListener('click', () => this.continueGame());
+ this.confirmDeleteBtn?.addEventListener('click', () => {
+ console.log('[MAIN MENU] Delete button clicked');
+ this.deleteSave();
+});
+ this.confirmSettingsBtn?.addEventListener('click', () => this.showSettings());
+ this.backToSavesFromConfirmBtn?.addEventListener('click', () => this.showSaveSection());
+
+ // Game options events
+ this.newGameBtn?.addEventListener('click', () => this.startNewGame());
+ this.continueBtn?.addEventListener('click', () => this.continueGame());
+ this.deleteSaveBtn?.addEventListener('click', () => {
+ console.log('[MAIN MENU] Delete save button clicked');
+ this.deleteSave();
+});
+ this.settingsBtn?.addEventListener('click', () => this.showSettings());
+ this.quitBtn?.addEventListener('click', () => this.quitGame());
+ this.backToSavesBtn?.addEventListener('click', async () => this.showSaveSection());
+
+ // Footer events
+ this.aboutBtn?.addEventListener('click', () => this.showAbout());
+ this.helpBtn?.addEventListener('click', () => this.showHelp());
+ }
+
+ showSection(sectionName) {
+ // Hide all sections
+ this.loginSection?.classList.add('hidden');
+ this.saveSection?.classList.add('hidden');
+ this.confirmSection?.classList.add('hidden');
+ this.optionsSection?.classList.add('hidden');
+
+ // Show selected section
+ const section = document.getElementById(`${sectionName}Section`);
+ if (section) {
+ section.classList.remove('hidden');
+ this.currentSection = sectionName;
+ }
+ }
+
+ showLoginSection() {
+ this.showSection('login');
+ }
+
+ async showSaveSection() {
+ this.showSection('save');
+ await this.loadSaveData();
+ this.updateSaveSlots();
+ }
+
+ showConfirmSection() {
+ this.showSection('confirm');
+ // Clear any previous data to prevent contamination
+ this.clearConfirmSectionData();
+ this.updateConfirmSection();
+ }
+
+ updateConfirmSection() {
+ const hasSave = this.selectedSlot && this.saveData[this.selectedSlot];
+
+ // Update slot title
+ this.confirmSlotTitle.textContent = `Slot ${this.selectedSlot}`;
+
+ if (hasSave) {
+ const saveData = this.saveData[this.selectedSlot];
+ const player = saveData.player || {};
+
+ // Update save details
+ const playerLevel = player.stats?.level || player.level || 1;
+
+ // Get ship name from multiple possible fields
+ const shipName = player.shipName || player.ship?.name || player.currentShip || player.selectedShip || saveData.shipName || saveData.currentShip || 'Unknown Ship';
+
+ // Calculate play time - check multiple possible fields and format properly
+ const playTime = player.playTime || player.stats?.playTime || saveData.playTime || saveData.totalPlayTime || 0;
+
+ // Handle different time formats (seconds, milliseconds, or already formatted)
+ let totalSeconds = 0;
+ if (typeof playTime === 'number') {
+ // If it's a large number, it might be milliseconds
+ if (playTime > 3600) {
+ totalSeconds = Math.floor(playTime / 1000);
+ } else {
+ totalSeconds = Math.floor(playTime);
+ }
+ } else if (typeof playTime === 'string') {
+ // If it's already formatted, use it as-is
+ const playTimeText = playTime;
+ } else {
+ totalSeconds = 0;
+ }
+
+ // Format time as dd.hh.nn.ss
+ const days = Math.floor(totalSeconds / 86400);
+ const hours = Math.floor((totalSeconds % 86400) / 3600);
+ const minutes = Math.floor((totalSeconds % 3600) / 60);
+ const seconds = totalSeconds % 60;
+
+ let playTimeText = '';
+ if (days > 0) {
+ playTimeText = `${days}d ${hours}h ${minutes}m ${seconds}s`;
+ } else if (hours > 0) {
+ playTimeText = `${hours}h ${minutes}m ${seconds}s`;
+ } else if (minutes > 0) {
+ playTimeText = `${minutes}m ${seconds}s`;
+ } else {
+ playTimeText = `${seconds}s`;
+ }
+
+ // Last played - check multiple possible date fields
+ const lastPlayed = saveData.lastPlayed || saveData.lastSave || saveData.timestamp || saveData.date || new Date();
+ const formattedDate = lastPlayed instanceof Date ? lastPlayed.toLocaleDateString() :
+ (typeof lastPlayed === 'string' ? lastPlayed : new Date(lastPlayed).toLocaleDateString());
+ const lastPlayedText = formattedDate === new Date().toLocaleDateString() ? 'Today' : formattedDate;
+
+ // Restore textContent properties
+ this.confirmLevel.textContent = playerLevel;
+ this.confirmShip.textContent = shipName;
+ this.confirmPlayTime.textContent = playTimeText;
+ this.confirmLastPlayed.textContent = lastPlayedText;
+
+ // Format save info with consistent alignment and labels
+ const saveInfoText = `Level: ${playerLevel}\n\nShip: ${shipName}\n\nPlay Time: ${playTimeText}\n\nLast Played: ${lastPlayedText}`;
+
+ // Update save details with pre-formatted text for alignment
+ this.confirmSaveDetails.innerHTML = `${saveInfoText} `;
+
+ // Has save data - always show Continue, hide New Game
+ this.confirmContinueBtn.style.display = 'block';
+ this.confirmContinueBtn.disabled = false;
+ this.confirmDeleteBtn.style.display = 'block';
+ this.confirmDeleteBtn.disabled = false;
+ this.confirmSettingsBtn.style.display = 'block';
+ this.confirmSettingsBtn.disabled = false;
+ this.confirmNewGameBtn.style.display = 'none';
+ this.confirmNewGameBtn.disabled = true;
+ } else {
+ // No save data - show new game option
+ this.confirmLevel.textContent = 'New';
+ this.confirmShip.textContent = 'New Adventure';
+ this.confirmPlayTime.textContent = '0h 0m';
+ this.confirmLastPlayed.textContent = 'Never';
+
+ // Show new game and settings buttons, hide continue and delete
+ this.confirmNewGameBtn.style.display = 'block';
+ this.confirmNewGameBtn.disabled = false;
+ this.confirmSettingsBtn.style.display = 'block';
+ this.confirmSettingsBtn.disabled = false;
+ this.confirmContinueBtn.style.display = 'none';
+ this.confirmDeleteBtn.style.display = 'none';
+ }
+ }
+
+ showOptionsSection() {
+ this.showSection('options');
+ // Clear any previous data to prevent contamination
+ this.clearOptionsSectionData();
+ this.updateGameOptions();
+ }
+
+ clearConfirmSectionData() {
+ // Clear individual text content elements
+ if (this.confirmLevel) this.confirmLevel.textContent = '';
+ if (this.confirmShip) this.confirmShip.textContent = '';
+ if (this.confirmPlayTime) this.confirmPlayTime.textContent = '';
+ if (this.confirmLastPlayed) this.confirmLastPlayed.textContent = '';
+
+ // Clear the save details display
+ if (this.confirmSaveDetails) {
+ this.confirmSaveDetails.innerHTML = '';
+ }
+ }
+
+ clearOptionsSectionData() {
+ // Clear the save info details display
+ const saveInfoDetails = document.getElementById('saveInfoDetails');
+ if (saveInfoDetails) {
+ saveInfoDetails.innerHTML = '';
+ }
+ }
+
+ handleSSOLogin() {
+ // Placeholder for SSO login
+ console.log('[MAIN MENU] SSO login requested (placeholder)');
+
+ // Show loading state
+ this.ssoLoginBtn.disabled = true;
+ this.ssoLoginBtn.innerHTML = ' Connecting...';
+
+ // Simulate SSO login (will be implemented later)
+ setTimeout(() => {
+ this.isLoggedIn = true;
+ this.ssoLoginBtn.innerHTML = ' Logged In';
+ this.ssoLoginBtn.disabled = false;
+
+ // Show save selection
+ this.showSaveSection();
+ }, 1500);
+ }
+
+ handleOfflineLogin() {
+ console.log('[MAIN MENU] Login successful');
+ this.isLoggedIn = true;
+
+ // Show save selection
+ setTimeout(async () => {
+ await this.showSaveSection();
+ }, 1500);
+ }
+
+ async loadSaveData() {
+ // Initialize saveData object
+ this.saveData = {};
+
+ console.log('[MAIN MENU] Loading save data from file system');
+
+ // Check if we have save paths and should use file system
+ if (this.savePaths && window.electronAPI) {
+ console.log('[MAIN MENU] Using file system for save data');
+
+ // Load save data from file system using the load-game API
+ for (let i = 1; i <= 3; i++) {
+ try {
+ console.log(`[MAIN MENU] Loading save file for slot ${i}`);
+
+ const result = await window.electronAPI.loadGame(i);
+
+ if (result.success && result.data) {
+ this.saveData[i] = result.data;
+ console.log(`[MAIN MENU] Slot ${i} loaded successfully:`, this.saveData[i]);
+ } else {
+ console.log(`[MAIN MENU] Slot ${i} has no save file or failed to load:`, result.error);
+ this.saveData[i] = null;
+ }
+ } catch (error) {
+ console.error(`[MAIN MENU] Error reading save slot ${i}:`, error);
+ this.saveData[i] = null;
+ }
+ }
+ } else {
+ console.log('[MAIN MENU] Using localStorage fallback for save data');
+ console.log('[MAIN MENU] Available localStorage keys:', Object.keys(localStorage));
+
+ // Fallback to localStorage
+ for (let i = 1; i <= 3; i++) {
+ const saveKey = `gso_save_slot_${i}`;
+ const saveData = localStorage.getItem(saveKey);
+
+ console.log(`[MAIN MENU] Slot ${i} key: ${saveKey}, data:`, saveData);
+
+ if (saveData) {
+ try {
+ this.saveData[i] = JSON.parse(saveData);
+ console.log(`[MAIN MENU] Slot ${i} parsed successfully:`, this.saveData[i]);
+ } catch (error) {
+ console.error(`[MAIN MENU] Error loading save slot ${i}:`, error);
+ this.saveData[i] = null;
+ }
+ } else {
+ console.log(`[MAIN MENU] Slot ${i} has no data`);
+ this.saveData[i] = null;
+ }
+ }
+ }
+
+ console.log('[MAIN MENU] Final saveData object:', this.saveData);
+ }
+
+ updateSaveSlots() {
+ console.log('[MAIN MENU] Updating save slots UI');
+ console.log('[MAIN MENU] this.saveSlots:', this.saveSlots);
+ console.log('[MAIN MENU] this.saveData:', this.saveData);
+
+ // Check if saveSlots elements exist
+ if (!this.saveSlots || this.saveSlots.length === 0) {
+ console.log('[MAIN MENU] Save slots not found, re-initializing elements');
+ this.saveSlots = document.querySelectorAll('.save-slot');
+ console.log('[MAIN MENU] Re-initialized saveSlots:', this.saveSlots);
+ }
+
+ if (!this.saveSlots || this.saveSlots.length === 0) {
+ console.error('[MAIN MENU] Could not find save slot elements in DOM');
+ return;
+ }
+
+ this.saveSlots.forEach((slot, index) => {
+ const slotNumber = index + 1;
+ const saveInfo = this.saveData[slotNumber];
+
+ console.log(`[MAIN MENU] Processing slot ${slotNumber}, saveInfo:`, saveInfo);
+
+ const slotStatus = slot.querySelector('.slot-status');
+ const slotName = slot.querySelector('.slot-name');
+ const slotDetails = slot.querySelector('.slot-details');
+ const slotBtn = slot.querySelector('.slot-btn');
+
+ console.log(`[MAIN MENU] Found elements for slot ${slotNumber}:`, {
+ slotStatus: !!slotStatus,
+ slotName: !!slotName,
+ slotDetails: !!slotDetails,
+ slotBtn: !!slotBtn
+ });
+
+ // Check if this is a valid save game (has actual game data)
+ console.log(`[MAIN MENU] Validating save data for slot ${slotNumber}:`, {
+ saveInfo: saveInfo,
+ hasSaveInfo: !!saveInfo,
+ hasPlayer: !!(saveInfo && saveInfo.player),
+ hasPlayerStats: !!(saveInfo && saveInfo.player && saveInfo.player.stats),
+ playerLevel: saveInfo && saveInfo.player && saveInfo.player.stats ? saveInfo.player.stats.level : 'no player.stats',
+ playerKeys: saveInfo && saveInfo.player ? Object.keys(saveInfo.player) : 'no player'
+ });
+
+ const hasValidSave = saveInfo && saveInfo.player && saveInfo.player.stats && saveInfo.player.stats.level !== undefined;
+
+ if (hasValidSave) {
+ // Update with existing save data
+ console.log(`[MAIN MENU] Slot ${slotNumber} has valid save data - showing Load`);
+ if (slotStatus) {
+ slotStatus.textContent = 'Level ' + (saveInfo.player.stats.level || 1);
+ slotStatus.className = 'slot-status has-data';
+ }
+ if (slotName) {
+ slotName.textContent = saveInfo.player.info.name || 'Commander';
+ }
+
+ // Format playtime and last played
+ const playTime = saveInfo.player.stats.playTime || saveInfo.player.playTime || 0;
+
+ // Handle different time formats (seconds, milliseconds, or already formatted)
+ let totalSeconds = 0;
+ if (typeof playTime === 'number') {
+ // If it's a large number, it might be milliseconds
+ if (playTime > 3600) {
+ totalSeconds = Math.floor(playTime / 1000);
+ } else {
+ totalSeconds = Math.floor(playTime);
+ }
+ } else if (typeof playTime === 'string') {
+ // If it's already formatted, use it as-is
+ var playtime = playTime;
+ } else {
+ totalSeconds = 0;
+ }
+
+ // Only format if we haven't already set playtime from a string
+ if (typeof playtime !== 'string') {
+ // Format time as dd.hh.nn.ss
+ const days = Math.floor(totalSeconds / 86400);
+ const hours = Math.floor((totalSeconds % 86400) / 3600);
+ const minutes = Math.floor((totalSeconds % 3600) / 60);
+ const seconds = totalSeconds % 60;
+
+ if (days > 0) {
+ playtime = `${days}d ${hours}h ${minutes}m ${seconds}s`;
+ } else if (hours > 0) {
+ playtime = `${hours}h ${minutes}m ${seconds}s`;
+ } else if (minutes > 0) {
+ playtime = `${minutes}m ${seconds}s`;
+ } else {
+ playtime = `${seconds}s`;
+ }
+ }
+
+ const lastSaved = saveInfo.lastSave ? new Date(saveInfo.lastSave).toLocaleDateString() : 'Unknown';
+ if (slotDetails) {
+ slotDetails.textContent = `Playtime: ${playtime} | Saved: ${lastSaved}`;
+ }
+
+ if (slotBtn) {
+ slotBtn.textContent = 'Select';
+ slotBtn.onclick = () => this.selectSaveSlot(slot);
+ }
+ } else {
+ // Empty slot or invalid/placeholder save
+ console.log(`[MAIN MENU] Slot ${slotNumber} is empty or invalid - showing New Game`);
+ if (slotStatus) {
+ slotStatus.textContent = 'Empty';
+ slotStatus.className = 'slot-status empty';
+ }
+ if (slotName) {
+ slotName.textContent = 'New Game';
+ }
+ if (slotDetails) {
+ slotDetails.textContent = 'Start a new adventure';
+ }
+ if (slotBtn) {
+ slotBtn.textContent = 'Select';
+ slotBtn.onclick = () => this.selectSaveSlot(slot);
+ }
+ }
+ });
+ }
+
+ selectSaveSlot(slot) {
+ const slotNumber = parseInt(slot.dataset.slot);
+ this.selectedSlot = slotNumber;
+
+ console.log(`[MAIN MENU] Save slot ${slotNumber} selected`);
+
+ // Show confirmation section
+ this.showConfirmSection();
+ }
+
+ updateGameOptions() {
+ const hasSave = this.selectedSlot && this.saveData[this.selectedSlot];
+
+ // Update button states based on save data
+ if (hasSave) {
+ // Has save data - always show Continue, hide New Game
+ this.continueBtn.style.display = 'block';
+ this.continueBtn.disabled = false;
+ this.continueBtn.innerHTML = ' Continue';
+
+ this.newGameBtn.style.display = 'none';
+
+ this.deleteSaveBtn.style.display = 'block';
+ this.deleteSaveBtn.disabled = false;
+ this.deleteSaveBtn.innerHTML = ' Delete Save';
+
+ // Update center information display
+ this.updateSaveInfoDisplay();
+ } else {
+ // Show new game button, hide continue button
+ this.newGameBtn.style.display = 'block';
+ this.newGameBtn.disabled = false;
+ this.newGameBtn.innerHTML = ' Start New Game';
+
+ this.continueBtn.style.display = 'none';
+
+ this.deleteSaveBtn.style.display = 'none';
+
+ // Update center information display for empty slot
+ this.updateSaveInfoDisplay();
+ }
+ }
+
+ updateSaveInfoDisplay() {
+ const saveInfoDetails = document.getElementById('saveInfoDetails');
+ if (!saveInfoDetails) return;
+
+ const hasSave = this.selectedSlot && this.saveData[this.selectedSlot];
+
+ if (hasSave) {
+ const saveData = this.saveData[this.selectedSlot];
+ const player = saveData.player || {};
+
+ // Format save information
+ const level = player.stats?.level || player.level || 1;
+ const name = player.info ? player.info.name : 'Commander';
+ const ship = player.shipName || 'Unknown Ship';
+
+ // Calculate play time - check multiple possible fields and format properly
+ const playTime = player.playTime || player.stats?.playTime || saveData.playTime || saveData.totalPlayTime || 0;
+
+ // Handle different time formats (seconds, milliseconds, or already formatted)
+ let totalSeconds = 0;
+ if (typeof playTime === 'number') {
+ // If it's a large number, it might be milliseconds
+ if (playTime > 3600) {
+ totalSeconds = Math.floor(playTime / 1000);
+ } else {
+ totalSeconds = Math.floor(playTime);
+ }
+ } else if (typeof playTime === 'string') {
+ // If it's already formatted, use it as-is
+ var playtime = playTime;
+ } else {
+ totalSeconds = 0;
+ }
+
+ // Only format if we haven't already set playtime from a string
+ if (typeof playtime !== 'string') {
+ // Format time as dd.hh.nn.ss
+ const days = Math.floor(totalSeconds / 86400);
+ const hours = Math.floor((totalSeconds % 86400) / 3600);
+ const minutes = Math.floor((totalSeconds % 3600) / 60);
+ const seconds = totalSeconds % 60;
+
+ if (days > 0) {
+ playtime = `${days}d ${hours}h ${minutes}m ${seconds}s`;
+ } else if (hours > 0) {
+ playtime = `${hours}h ${minutes}m ${seconds}s`;
+ } else if (minutes > 0) {
+ playtime = `${minutes}m ${seconds}s`;
+ } else {
+ playtime = `${seconds}s`;
+ }
+ }
+
+ // Check multiple possible date fields and use current date if none found
+ const lastSaved = saveData.lastPlayed || saveData.lastSave || saveData.timestamp || new Date().toLocaleDateString();
+ const formattedDate = lastSaved === new Date().toLocaleDateString() ? 'Today' :
+ (typeof lastSaved === 'string' ? lastSaved : new Date(lastSaved).toLocaleDateString());
+
+ // Format save info with consistent alignment and labels
+ const saveInfoText = `Level: ${level}\n\nShip: ${ship}\n\nPlay Time: ${playtime}\n\nLast Played: ${formattedDate}`;
+
+ // Update save details with pre-formatted text for alignment
+ saveInfoDetails.innerHTML = `${saveInfoText} `;
+ } else {
+ saveInfoDetails.innerHTML = `
+
+
Slot: ${this.selectedSlot || 'None'}
+
Status: Empty
+
Ready for new adventure
+
+ `;
+ }
+ }
+
+ startNewGame() {
+ if (!this.selectedSlot) {
+ console.error('[MAIN MENU] No save slot selected');
+ return;
+ }
+
+ console.log(`[MAIN MENU] Starting new game in slot ${this.selectedSlot}`);
+
+ // Clear existing save data for this slot
+ const saveKey = `gso_save_slot_${this.selectedSlot}`;
+ localStorage.removeItem(saveKey);
+ this.saveData[this.selectedSlot] = null;
+
+ // Start the game
+ this.launchGame('new');
+ }
+
+ continueGame() {
+ if (!this.selectedSlot || !this.saveData[this.selectedSlot]) {
+ console.error('[MAIN MENU] No save data to continue');
+ return;
+ }
+
+ console.log(`[MAIN MENU] Continuing game from slot ${this.selectedSlot}`);
+ this.launchGame('continue');
+ }
+
+ async deleteSave() {
+ console.log('[MAIN MENU] deleteSave called');
+
+ if (!this.selectedSlot || !this.saveData[this.selectedSlot]) {
+ console.error('[MAIN MENU] No save data to delete', {
+ selectedSlot: this.selectedSlot,
+ hasSaveData: !!this.saveData[this.selectedSlot]
+ });
+ return;
+ }
+
+ const saveData = this.saveData[this.selectedSlot];
+ const saveInfo = saveData.player ?
+ `Level ${saveData.player.stats?.level || saveData.player.level || 1} ${saveData.player.shipName || saveData.player.ship?.name || 'Player'}` :
+ 'Unknown save data';
+
+ const confirmMessage = `Are you sure you want to delete the save from slot ${this.selectedSlot}?\n\n${saveInfo}\n\nThis action cannot be undone!`;
+
+ console.log('[MAIN MENU] Showing confirmation modal');
+
+ // Show custom confirmation modal
+ this.showConfirm(confirmMessage, () => {
+ console.log('[MAIN MENU] User confirmed deletion, executing delete');
+ this.executeDeleteSave();
+ }, () => {
+ console.log('[MAIN MENU] User cancelled deletion');
+ });
+ }
+
+ async executeDeleteSave() {
+ console.log('[MAIN MENU] executeDeleteSave called', {
+ selectedSlot: this.selectedSlot
+ });
+
+ try {
+ console.log(`[MAIN MENU] Deleting save data from slot ${this.selectedSlot}`);
+
+ // Remove from localStorage
+ const saveKey = `gso_save_slot_${this.selectedSlot}`;
+ console.log(`[MAIN MENU] Removing localStorage key: ${saveKey}`);
+ localStorage.removeItem(saveKey);
+
+ // Verify removal
+ const remainingData = localStorage.getItem(saveKey);
+ console.log(`[MAIN MENU] Verification - save data still exists: ${!!remainingData}`);
+
+ // Remove from file system if available
+ if (window.electronAPI && window.electronAPI.deleteSaveFile) {
+ console.log(`[MAIN MENU] Deleting save file via Electron API`);
+ await window.electronAPI.deleteSaveFile(this.selectedSlot);
+ console.log(`[MAIN MENU] Save file deleted from slot ${this.selectedSlot} via Electron API`);
+ } else if (window.electronAPI && window.electronAPI.deleteSave) {
+ console.log(`[MAIN MENU] Deleting save file via Electron API (deleteSave method)`);
+ await window.electronAPI.deleteSave(this.selectedSlot);
+ console.log(`[MAIN MENU] Save file deleted from slot ${this.selectedSlot} via Electron API`);
+ } else {
+ console.log(`[MAIN MENU] No Electron API available for file deletion, only localStorage deletion performed`);
+ console.log(`[MAIN MENU] Available electronAPI methods:`, Object.keys(window.electronAPI || {}));
+ }
+
+ // Clear from memory
+ console.log(`[MAIN MENU] Clearing save data from memory`);
+ this.saveData[this.selectedSlot] = null;
+
+ // Update UI
+ console.log(`[MAIN MENU] Updating UI after deletion`);
+ this.updateSaveSlots();
+ this.updateGameOptions();
+
+ console.log(`[MAIN MENU] Save data successfully deleted from slot ${this.selectedSlot}`);
+
+ // Show success message
+ this.showAlert(`Save data from slot ${this.selectedSlot} has been deleted successfully.`, 'success');
+
+ // Return to save selection screen
+ console.log(`[MAIN MENU] Returning to save selection screen`);
+ this.showSaveSection();
+
+ } catch (error) {
+ console.error('[MAIN MENU] Failed to delete save data:', error);
+ this.showAlert('Failed to delete save data. Please try again.', 'error');
+ }
+ }
+
+ loadGame(slotNumber) {
+ this.selectedSlot = slotNumber;
+ console.log(`[MAIN MENU] Loading game from slot ${slotNumber}`);
+ this.launchGame('continue');
+ }
+
+ async launchGame(mode) {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('MainMenu.launchGame', {
+ mode: mode,
+ selectedSlot: this.selectedSlot,
+ timestamp: new Date().toISOString()
+ });
+
+ console.log('[MAIN MENU] Launching game in', mode, 'mode');
+ console.log('[MAIN MENU] Selected save slot:', this.selectedSlot);
+
+ try {
+ // Hide main menu
+ this.hide();
+
+ // Show loading screen
+ const loadingScreen = document.getElementById('loadingScreen');
+ if (loadingScreen) {
+ loadingScreen.classList.remove('hidden');
+ console.log('[MAIN MENU] Loading screen shown');
+
+ if (debugLogger) debugLogger.logStep('Loading screen displayed');
+ }
+
+ // Initialize game systems first
+ console.log('[MAIN MENU] Starting game systems initialization...');
+
+ if (debugLogger) debugLogger.logStep('Starting game systems initialization');
+
+ try {
+ await this.initializeGameSystems();
+ console.log('[MAIN MENU] Game systems initialized successfully');
+
+ if (debugLogger) debugLogger.logStep('Game systems initialized successfully');
+
+ // Set save slot in game engine
+ if (window.game) {
+ console.log('[MAIN MENU] Setting active save slot to:', this.selectedSlot);
+
+ // Pass save path information to game engine
+ if (this.savePaths) {
+ window.game.saveSlotInfo = {
+ slot: this.selectedSlot,
+ path: this.savePaths.slots[this.selectedSlot - 1],
+ useFileSystem: true
+ };
+ console.log('[MAIN MENU] Using file system for saves');
+
+ if (debugLogger) debugLogger.logStep('Save slot info set with file system', {
+ slot: this.selectedSlot,
+ path: this.savePaths.slots[this.selectedSlot - 1]
+ });
+ } else {
+ window.game.saveSlotInfo = {
+ slot: this.selectedSlot,
+ useFileSystem: false
+ };
+ console.log('[MAIN MENU] Using localStorage for saves');
+
+ if (debugLogger) debugLogger.logStep('Save slot info set with localStorage', {
+ slot: this.selectedSlot
+ });
+ }
+
+ // Start the game
+ if (mode === 'continue') {
+ console.log('[MAIN MENU] Starting game in continue mode');
+
+ if (debugLogger) debugLogger.logStep('Starting game in continue mode');
+ await window.game.startGame(true);
+ } else {
+ console.log('[MAIN MENU] Starting new game');
+
+ if (debugLogger) debugLogger.logStep('Starting new game');
+ await window.game.startGame(false);
+ }
+
+ // Perform immediate UI updates after game starts
+ console.log('[MAIN MENU] Performing immediate UI updates after game start');
+
+ // Update GUI immediately
+ if (window.game && window.game.updateGUI) {
+ console.log('[MAIN MENU] Updating GUI immediately');
+ window.game.updateGUI();
+ }
+
+ // Update daily countdown immediately
+ if (window.game && window.game.systems && window.game.systems.questSystem && window.game.systems.questSystem.updateDailyCountdown) {
+ console.log('[MAIN MENU] Updating daily countdown immediately');
+ window.game.systems.questSystem.updateDailyCountdown();
+ }
+
+ // Update weekly countdown immediately
+ if (window.game && window.game.systems && window.game.systems.questSystem && window.game.systems.questSystem.updateWeeklyCountdown) {
+ console.log('[MAIN MENU] Updating weekly countdown immediately');
+ window.game.systems.questSystem.updateWeeklyCountdown();
+ }
+
+ // Hide loading screen and show game interface
+ this.hideLoadingScreenAndShowGame();
+
+ if (debugLogger) debugLogger.endStep('MainMenu.launchGame', {
+ success: true,
+ mode: mode
+ });
+ } else {
+ throw new Error('Game engine not initialized');
+ }
+ } catch (error) {
+ console.error('[MAIN MENU] Game systems initialization failed:', error);
+
+ if (debugLogger) {
+ debugLogger.errorEvent('MainMenu.launchGame', error, {
+ phase: 'initialization',
+ mode: mode
+ });
+ debugLogger.endStep('MainMenu.launchGame', {
+ success: false,
+ error: error.message
+ });
+ }
+
+ // Show error and return to menu
+ this.showAlert('Failed to initialize game. Check console for details.\n\nError: ' + error.message, 'error');
+ this.show();
+ }
+ } catch (error) {
+ console.error('[MAIN MENU] Game systems initialization failed:', error);
+
+ if (debugLogger) {
+ debugLogger.errorEvent('MainMenu.launchGame', error, {
+ phase: 'initialization',
+ mode: mode
+ });
+ debugLogger.endStep('MainMenu.launchGame', {
+ success: false,
+ error: error.message
+ });
+ }
+
+ // Show error and return to menu
+ this.showAlert('Failed to initialize game. Check console for details.\n\nError: ' + error.message, 'error');
+ this.show();
+ }
+ }
+
+ async initializeGameSystems() {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('MainMenu.initializeGameSystems', {
+ timestamp: new Date().toISOString()
+ });
+
+ console.log('[MAIN MENU] Initializing game systems');
+
+ try {
+ // Initialize logger if available
+ const logger = window.logger;
+ if (logger) {
+ await logger.timeAsync('Game Systems Initialization', async () => {
+ await logger.info('Initializing game systems from main menu');
+ });
+ }
+
+ if (debugLogger) debugLogger.logStep('Creating GameEngine instance');
+
+ // Prevent duplicate GameEngine creation
+ if (window.game) {
+ console.log('[MAIN MENU] GameEngine already exists, skipping creation');
+ if (debugLogger) debugLogger.logStep('GameEngine already exists, skipping creation');
+ return;
+ }
+
+ // Create game engine
+ window.game = new GameEngine();
+
+ if (debugLogger) debugLogger.logStep('GameEngine created, calling init()');
+
+ // Initialize game systems
+ await window.game.init();
+
+ if (debugLogger) debugLogger.logStep('GameEngine init() completed successfully');
+
+ console.log('[MAIN MENU] Game systems initialized successfully');
+
+ if (debugLogger) debugLogger.endStep('MainMenu.initializeGameSystems', {
+ success: true
+ });
+
+ } catch (error) {
+ console.error('[MAIN MENU] Failed to initialize game systems:', error);
+
+ if (debugLogger) {
+ debugLogger.errorEvent('MainMenu.initializeGameSystems', error, {
+ phase: 'initialization',
+ timestamp: new Date().toISOString()
+ });
+ debugLogger.endStep('MainMenu.initializeGameSystems', {
+ success: false,
+ error: error.message
+ });
+ }
+
+ // Show error and return to menu
+ this.showAlert('Failed to initialize game. Please try again.\n\nError: ' + error.message, 'error');
+ this.show();
+ }
+ }
+
+ showSettings() {
+ console.log('[MAIN MENU] Settings requested (placeholder)');
+ // Placeholder for settings menu
+ this.showAlert('Settings menu coming soon!', 'info');
+ }
+
+ showAbout() {
+ console.log('[MAIN MENU] About requested');
+ this.showAlert('Galaxy Strike Online\nVersion 1.0.0\n\nA space-themed idle MMORPG built with Electron.\n\n© 2024 Korvarix Studios', 'info', 'About');
+ }
+
+ showHelp() {
+ console.log('[MAIN MENU] Help requested');
+ this.showAlert('Galaxy Strike Online - Help\n\n• Login with your account to play\n• Select a save slot to store your progress\n• Start a new game or continue existing save\n• Use Ctrl+Alt+Shift+C for developer console\n\nFor more help, visit our Discord or documentation.', 'info', 'Help');
+ }
+
+ quitGame() {
+ console.log('[MAIN MENU] Quit requested');
+
+ this.showConfirm('Are you sure you want to quit Galaxy Strike Online?', () => {
+ // In Electron, this would close the app
+ if (window.electronAPI && window.electronAPI.quit) {
+ window.electronAPI.quit();
+ } else {
+ // Fallback for browser
+ window.close();
+ }
+ });
+ }
+
+ // Public methods for external access
+ show() {
+ this.mainMenu?.classList.remove('hidden');
+ this.showSaveSection();
+ }
+
+ hide() {
+ this.mainMenu?.classList.add('hidden');
+ }
+
+ // Custom alert function to replace browser alerts
+ showAlert(message, type = 'info', title = null) {
+ const modalOverlay = document.getElementById('modalOverlay');
+ const modal = document.getElementById('modal');
+ const modalTitle = document.getElementById('modalTitle');
+ const modalBody = document.getElementById('modalBody');
+
+ console.log('[MAIN MENU] showAlert called', {
+ modalOverlay: !!modalOverlay,
+ modal: !!modal,
+ modalTitle: !!modalTitle,
+ modalBody: !!modalBody
+ });
+
+ if (!modalOverlay || !modal || !modalTitle || !modalBody) {
+ // Fallback to browser alert if modal elements aren't found
+ alert(message);
+ return;
+ }
+
+ // Set title based on type
+ const titles = {
+ 'info': 'Information',
+ 'success': 'Success',
+ 'error': 'Error',
+ 'warning': 'Warning'
+ };
+
+ const alertTitle = title || titles[type] || 'Information';
+
+ // Set modal content
+ modalTitle.textContent = alertTitle;
+ modalBody.innerHTML = `
+ ${message}
+
+ `;
+
+ // Set modal type class
+ modal.className = `modal alert-modal ${type}`;
+
+ // Show modal
+ modalOverlay.classList.remove('hidden');
+
+ // Add click outside to close
+ modalOverlay.onclick = (e) => {
+ if (e.target === modalOverlay) {
+ this.closeAlert();
+ }
+ };
+
+ // Add escape key to close
+ const handleEscape = (e) => {
+ if (e.key === 'Escape') {
+ this.closeAlert();
+ document.removeEventListener('keydown', handleEscape);
+ }
+ };
+ document.addEventListener('keydown', handleEscape);
+ }
+
+ closeAlert() {
+ const modalOverlay = document.getElementById('modalOverlay');
+ if (modalOverlay) {
+ modalOverlay.classList.add('hidden');
+ }
+ }
+
+ // Custom confirmation function to replace browser confirm()
+ showConfirm(message, onConfirm, onCancel = null) {
+ const modalOverlay = document.getElementById('modalOverlay');
+ const modal = document.getElementById('modal');
+ const modalTitle = document.getElementById('modalTitle');
+ const modalBody = document.getElementById('modalBody');
+
+ console.log('[MAIN MENU] showConfirm called', {
+ modalOverlay: !!modalOverlay,
+ modal: !!modal,
+ modalTitle: !!modalTitle,
+ modalBody: !!modalBody
+ });
+
+ if (!modalOverlay || !modal || !modalTitle || !modalBody) {
+ console.warn('[MAIN MENU] Modal elements not found, falling back to browser confirm');
+ // Fallback to browser confirm if modal elements aren't found
+ const result = confirm(message);
+ if (result && onConfirm) onConfirm();
+ if (!result && onCancel) onCancel();
+ return;
+ }
+
+ // Set modal content
+ modalTitle.textContent = 'Confirm Action';
+ modalBody.innerHTML = `
+ ${message}
+
+ `;
+
+ // Set modal type class
+ modal.className = 'modal confirmation-modal';
+
+ // Store callbacks
+ this._confirmCallback = onConfirm;
+ this._cancelCallback = onCancel;
+
+ // Show modal
+ modalOverlay.classList.remove('hidden');
+
+ // Add click outside to close
+ modalOverlay.onclick = (e) => {
+ if (e.target === modalOverlay) {
+ this.closeConfirm();
+ }
+ };
+
+ // Add escape key to close
+ const handleEscape = (e) => {
+ if (e.key === 'Escape') {
+ this.closeConfirm();
+ document.removeEventListener('keydown', handleEscape);
+ }
+ };
+ document.addEventListener('keydown', handleEscape);
+ }
+
+ executeConfirm() {
+ console.log('[MAIN MENU] executeConfirm called', {
+ hasCallback: !!this._confirmCallback
+ });
+
+ const modalOverlay = document.getElementById('modalOverlay');
+ if (modalOverlay) {
+ modalOverlay.classList.add('hidden');
+ }
+
+ if (this._confirmCallback) {
+ console.log('[MAIN MENU] Executing confirmation callback');
+ this._confirmCallback();
+ } else {
+ console.warn('[MAIN MENU] No confirmation callback found');
+ }
+
+ // Clear callbacks
+ this._confirmCallback = null;
+ this._cancelCallback = null;
+ }
+
+ closeConfirm() {
+ const modalOverlay = document.getElementById('modalOverlay');
+ if (modalOverlay) {
+ modalOverlay.classList.add('hidden');
+ }
+
+ if (this._cancelCallback) {
+ this._cancelCallback();
+ }
+
+ // Clear callbacks
+ this._confirmCallback = null;
+ this._cancelCallback = null;
+ }
+}
+
+// Initialize main menu when DOM is ready
+document.addEventListener('DOMContentLoaded', () => {
+ console.log('[MAIN MENU] DOMContentLoaded event fired');
+ window.mainMenu = new MainMenu();
+});
+
+// Also try immediate initialization if DOM is already loaded
+if (document.readyState === 'loading') {
+ console.log('[MAIN MENU] DOM still loading, waiting for DOMContentLoaded');
+} else {
+ console.log('[MAIN MENU] DOM already loaded, initializing immediately');
+ window.mainMenu = new MainMenu();
+}
diff --git a/Client/js/ui/UIManager.js b/Client/js/ui/UIManager.js
new file mode 100644
index 0000000..04913cb
--- /dev/null
+++ b/Client/js/ui/UIManager.js
@@ -0,0 +1,2321 @@
+/**
+ * Galaxy Strike Online - UI Manager
+ * Handles all user interface interactions and updates
+ */
+
+class UIManager {
+ constructor(gameEngine) {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('UIManager.constructor', {
+ gameEngineProvided: !!gameEngine
+ });
+
+ this.game = gameEngine;
+
+ // UI state
+ this.currentTab = 'dashboard';
+ this.modalOpen = false;
+ this.notifications = [];
+
+ // Note: setupEventListeners() called in proceedWithInitialization() to prevent duplicates
+
+ if (debugLogger) debugLogger.endStep('UIManager.constructor', {
+ currentTab: this.currentTab,
+ modalOpen: this.modalOpen,
+ notificationsCount: this.notifications.length,
+ gameEngineSet: !!this.game,
+ eventListenersSetup: true
+ });
+ }
+
+ async initialize() {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('UIManager.initialize', {
+ currentTab: this.currentTab,
+ modalOpen: this.modalOpen,
+ notificationsCount: this.notifications.length
+ });
+
+ // Wait for DOM to be ready and game interface to be visible
+ const waitForDOM = () => {
+ const gameInterface = document.getElementById('gameInterface');
+ const navButtons = document.querySelectorAll('.nav-btn');
+
+ if (debugLogger) debugLogger.logStep('DOM Check', {
+ gameInterfaceExists: !!gameInterface,
+ gameInterfaceHidden: gameInterface?.classList.contains('hidden'),
+ navButtonsFound: navButtons.length,
+ documentReady: document.readyState
+ });
+
+ if (gameInterface && !gameInterface.classList.contains('hidden') && navButtons.length > 0) {
+ this.proceedWithInitialization();
+ } else {
+ setTimeout(waitForDOM, 100);
+ }
+ };
+
+ // Start the DOM check
+ waitForDOM();
+ }
+
+ proceedWithInitialization() {
+ const debugLogger = window.debugLogger;
+
+ // Setup navigation
+ // if (debugLogger) debugLogger.logStep('Setting up navigation');
+ this.setupNavigation();
+
+ // Setup event listeners
+ // if (debugLogger) debugLogger.logStep('Setting up event listeners');
+ this.setupEventListeners();
+
+ // Setup resource display
+ // if (debugLogger) debugLogger.logStep('Setting up resource display');
+ this.setupResourceDisplay();
+
+ if (debugLogger) debugLogger.endStep('UIManager.initialize', {
+ currentTab: this.currentTab,
+ modalOpen: this.modalOpen,
+ notificationsCount: this.notifications.length,
+ navigationSetup: true,
+ eventListenersSetup: true,
+ resourceDisplaySetup: true
+ });
+ }
+
+ setupNavigation() {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('UIManager.setupNavigation');
+
+ // Navigation setup moved to setupEventListeners to fix scope issues
+
+ if (debugLogger) debugLogger.endStep('UIManager.setupNavigation', {
+ success: true
+ });
+
+ const craftingCatButtons = document.querySelectorAll('.crafting-cat-btn');
+ if (debugLogger) debugLogger.logStep('Setting up crafting category navigation', {
+ craftingCatButtonsCount: craftingCatButtons.length
+ });
+
+ craftingCatButtons.forEach(btn => {
+ btn.addEventListener('click', (e) => {
+ const category = e.currentTarget.dataset.category;
+
+ if (debugLogger) debugLogger.log('Crafting category button clicked', {
+ buttonElement: btn.tagName,
+ buttonText: btn.textContent,
+ category: category
+ });
+
+ this.switchCraftingCategory(category);
+ });
+ });
+
+ if (debugLogger) debugLogger.endStep('UIManager.setupNavigation', {
+ navButtonsSetup: navButtons.length,
+ skillCatButtonsSetup: skillCatButtons.length,
+ shopCatButtonsSetup: shopCatButtons.length,
+ questTabButtonsSetup: questTabButtons.length,
+ craftingCatButtonsSetup: craftingCatButtons.length
+ });
+ }
+
+ setupEventListeners() {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('UIManager.setupEventListeners');
+
+ // Setup tab navigation here to fix scope issues with endStep
+ const navButtons = document.querySelectorAll('.nav-btn');
+ console.log(`[UIManager] Found ${navButtons.length} navigation buttons`);
+ if (debugLogger) debugLogger.logStep('Setting up tab navigation', {
+ navButtonsCount: navButtons.length
+ });
+
+ if (navButtons.length === 0) {
+ console.warn('[UIManager] No navigation buttons found!');
+ if (debugLogger) debugLogger.logStep('No navigation buttons found');
+ }
+
+ navButtons.forEach((btn, index) => {
+ console.log(`[UIManager] Setting up button ${index}:`, btn.dataset.tab);
+ // Check if button already has a listener to prevent duplicates
+ if (btn.getAttribute('data-has-listener') === 'true') {
+ console.log(`[UIManager] Button ${btn.dataset.tab} already has listener, skipping`);
+ return;
+ }
+
+ btn.addEventListener('click', (e) => {
+ console.log(`[UIManager] Navigation button clicked: ${btn.dataset.tab}`);
+ const tab = btn.dataset.tab;
+ if (tab) {
+ this.switchTab(tab);
+ }
+ });
+
+ // Mark button as having a listener
+ btn.setAttribute('data-has-listener', 'true');
+ console.log(`[UIManager] Button ${btn.dataset.tab} setup complete`);
+ });
+
+ // Set up UI update event listener from GameEngine
+ if (this.game) {
+ this.game.addEventListener('uiUpdate', (event) => {
+ this.handleUIUpdate(event.detail);
+ });
+
+ // if (debugLogger) debugLogger.logStep('UI update event listener set up');
+ }
+
+ // Modal controls
+ const modalClose = document.getElementById('modalClose');
+ const modalOverlay = document.getElementById('modalOverlay');
+
+ if (debugLogger) debugLogger.logStep('Setting up modal controls', {
+ modalCloseExists: !!modalClose,
+ modalOverlayExists: !!modalOverlay
+ });
+
+ if (modalClose) {
+ // Check if already has listener
+ if (modalClose.getAttribute('data-has-listener') === 'true') {
+ if (debugLogger) debugLogger.log('Modal close button already has listener, skipping');
+ } else {
+ modalClose.addEventListener('click', () => {
+ if (debugLogger) debugLogger.log('Modal close button clicked');
+ this.closeModal();
+ });
+ modalClose.setAttribute('data-has-listener', 'true');
+ }
+ }
+
+ if (modalOverlay) {
+ // Check if already has listener
+ if (modalOverlay.getAttribute('data-has-listener') === 'true') {
+ if (debugLogger) debugLogger.log('Modal overlay already has listener, skipping');
+ } else {
+ modalOverlay.addEventListener('click', (e) => {
+ if (e.target === modalOverlay) {
+ if (debugLogger) debugLogger.log('Modal overlay clicked - closing modal');
+ this.closeModal();
+ }
+ });
+ modalOverlay.setAttribute('data-has-listener', 'true');
+ }
+ }
+
+ // Quick action buttons
+ const claimOfflineBtn = document.getElementById('claimOfflineBtn');
+ if (claimOfflineBtn) {
+ // Check if already has listener
+ if (claimOfflineBtn.getAttribute('data-has-listener') === 'true') {
+ if (debugLogger) debugLogger.log('Claim offline button already has listener, skipping');
+ } else {
+ // if (debugLogger) debugLogger.logStep('Setting up claim offline rewards button');
+ claimOfflineBtn.addEventListener('click', () => {
+ if (debugLogger) debugLogger.log('Claim offline rewards button clicked');
+ this.game.systems.idleSystem.claimOfflineRewards();
+ });
+ claimOfflineBtn.setAttribute('data-has-listener', 'true');
+ }
+ }
+
+ const quickDungeonBtn = document.getElementById('quickDungeonBtn');
+ if (quickDungeonBtn) {
+ // if (debugLogger) debugLogger.logStep('Setting up quick dungeon button');
+ quickDungeonBtn.addEventListener('click', () => {
+ if (debugLogger) debugLogger.log('Quick dungeon button clicked', {
+ currentTab: this.currentTab,
+ targetTab: 'dungeons'
+ });
+ this.switchTab('dungeons');
+ });
+ }
+
+ // Settings and Discord buttons
+ const settingsBtn = document.getElementById('settingsBtn');
+ if (settingsBtn) {
+ // Check if already has listener
+ if (settingsBtn.getAttribute('data-has-listener') === 'true') {
+ if (debugLogger) debugLogger.log('Settings button already has listener, skipping');
+ } else {
+ // if (debugLogger) debugLogger.logStep('Setting up settings button');
+ settingsBtn.addEventListener('click', () => {
+ if (debugLogger) debugLogger.log('Settings button clicked');
+ this.showSettingsMenu();
+ });
+ settingsBtn.setAttribute('data-has-listener', 'true');
+ }
+ }
+
+ const discordBtn = document.getElementById('discordBtn');
+ if (discordBtn) {
+ // if (debugLogger) debugLogger.logStep('Setting up Discord button');
+ discordBtn.addEventListener('click', () => {
+ if (debugLogger) debugLogger.log('Discord button clicked');
+ this.showDiscordIntegration();
+ });
+ }
+
+ // Local Server button
+ const localServerBtn = document.getElementById('localServerBtn');
+ if (localServerBtn) {
+ // if (debugLogger) debugLogger.logStep('Setting up local server button');
+ localServerBtn.addEventListener('click', () => {
+ if (debugLogger) debugLogger.log('Local server button clicked');
+ this.showLocalServerControls();
+ });
+ }
+
+ // Return to Main Menu button
+ const returnToMenuBtn = document.getElementById('returnToMenuBtn');
+ if (returnToMenuBtn) {
+ // Check if button already has a listener to prevent duplicates
+ if (returnToMenuBtn.getAttribute('data-has-listener') === 'true') {
+ if (debugLogger) debugLogger.log('Return to menu button already has listener, skipping');
+ return;
+ }
+
+ // if (debugLogger) debugLogger.logStep('Setting up return to menu button');
+ returnToMenuBtn.addEventListener('click', () => {
+ if (debugLogger) debugLogger.log('Return to menu button clicked');
+ this.returnToMainMenu();
+ });
+
+ // Mark as having listener
+ returnToMenuBtn.setAttribute('data-has-listener', 'true');
+ }
+
+ // Setup sub-panel category buttons
+ const skillCatButtons = document.querySelectorAll('.skill-cat-btn');
+ if (debugLogger) debugLogger.logStep('Setting up skill category navigation', {
+ skillCatButtonsCount: skillCatButtons.length
+ });
+
+ skillCatButtons.forEach(btn => {
+ // Check if button already has a listener to prevent duplicates
+ if (btn.getAttribute('data-has-listener') === 'true') {
+ return;
+ }
+
+ btn.addEventListener('click', (e) => {
+ const category = btn.dataset.category;
+ if (debugLogger) debugLogger.log('Skill category button clicked', {
+ buttonElement: btn.tagName,
+ buttonText: btn.textContent,
+ category: category
+ });
+
+ this.switchSkillCategory(category);
+ });
+
+ // Mark button as having a listener
+ btn.setAttribute('data-has-listener', 'true');
+ });
+
+ const questTabButtons = document.querySelectorAll('.quest-tab-btn');
+ if (debugLogger) debugLogger.logStep('Setting up quest tab navigation', {
+ questTabButtonsCount: questTabButtons.length
+ });
+
+ questTabButtons.forEach(btn => {
+ // Check if button already has a listener to prevent duplicates
+ if (btn.getAttribute('data-has-listener') === 'true') {
+ return;
+ }
+
+ btn.addEventListener('click', (e) => {
+ const type = btn.dataset.type;
+ if (debugLogger) debugLogger.log('Quest tab button clicked', {
+ buttonElement: btn.tagName,
+ buttonText: btn.textContent,
+ type: type
+ });
+
+ this.switchQuestType(type);
+ });
+
+ // Mark button as having a listener
+ btn.setAttribute('data-has-listener', 'true');
+ });
+
+ const craftingCatButtons = document.querySelectorAll('.crafting-cat-btn');
+ if (debugLogger) debugLogger.logStep('Setting up crafting category navigation', {
+ craftingCatButtonsCount: craftingCatButtons.length
+ });
+
+ craftingCatButtons.forEach(btn => {
+ // Check if button already has a listener to prevent duplicates
+ if (btn.getAttribute('data-has-listener') === 'true') {
+ return;
+ }
+
+ btn.addEventListener('click', (e) => {
+ const category = e.currentTarget.dataset.category;
+
+ if (debugLogger) debugLogger.log('Crafting category button clicked', {
+ buttonElement: btn.tagName,
+ buttonText: btn.textContent,
+ category: category
+ });
+
+ this.switchCraftingCategory(category);
+ });
+
+ // Mark button as having a listener
+ btn.setAttribute('data-has-listener', 'true');
+ });
+
+ const shopCatButtons = document.querySelectorAll('.shop-cat-btn');
+ if (debugLogger) debugLogger.logStep('Setting up shop category navigation', {
+ shopCatButtonsCount: shopCatButtons.length
+ });
+
+ shopCatButtons.forEach(btn => {
+ // Check if button already has a listener to prevent duplicates
+ if (btn.getAttribute('data-has-listener') === 'true') {
+ return;
+ }
+
+ btn.addEventListener('click', (e) => {
+ const category = btn.dataset.category;
+ if (debugLogger) debugLogger.log('Shop category button clicked', {
+ buttonElement: btn.tagName,
+ buttonText: btn.textContent,
+ category: category
+ });
+
+ this.switchShopCategory(category);
+ });
+
+ // Mark button as having a listener
+ btn.setAttribute('data-has-listener', 'true');
+ });
+
+ // Keyboard shortcuts for tab switching removed
+
+ if (debugLogger) debugLogger.endStep('UIManager.setupEventListeners', {
+ modalControlsSetup: !!(modalClose || modalOverlay),
+ quickActionButtonsSetup: !!(claimOfflineBtn || quickDungeonBtn),
+ settingsButtonsSetup: !!(settingsBtn || discordBtn),
+ returnMenuButtonSetup: !!returnToMenuBtn,
+ navigationButtonsSetup: navButtons.length > 0,
+ navigationButtonsCount: navButtons.length,
+ skillCategoryButtonsSetup: skillCatButtons.length,
+ questTabButtonsSetup: questTabButtons.length,
+ craftingCategoryButtonsSetup: craftingCatButtons.length,
+ shopCategoryButtonsSetup: shopCatButtons.length
+ });
+ }
+
+ async saveGame() {
+ const debugLogger = window.debugLogger;
+
+ // if (debugLogger) debugLogger.startStep('UIManager.saveGame');
+
+ try {
+
+ // Show saving notification
+ // this.game.showNotification('Saving game...', 'info', 2000);
+
+ // Call the game engine's save method
+ await this.game.save();
+
+ // Show success notification
+ // this.game.showNotification('Game saved successfully!', 'success', 3000);
+
+
+ // if (debugLogger) debugLogger.endStep('UIManager.saveGame', {
+ // success: true
+ // });
+
+ } catch (error) {
+
+ // Show error notification
+ this.game.showNotification('Failed to save game!', 'error', 3000);
+
+ if (debugLogger) debugLogger.errorEvent(error, 'UIManager.saveGame');
+ }
+ }
+
+ showLocalServerControls() {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('UIManager.showLocalServerControls');
+
+
+ // Get current server status
+ const serverInfo = window.localServerManager ? window.localServerManager.getServerInfo() : {
+ isRunning: false,
+ status: 'Unknown',
+ port: null,
+ url: null,
+ connectedClients: 0,
+ uptime: 0
+ };
+
+ let content = '';
+ content += '
';
+ content += `
Status: ${serverInfo.status}
`;
+
+ if (serverInfo.isRunning) {
+ content += `
Port: ${serverInfo.port}
`;
+ content += `
URL: ${serverInfo.url}
`;
+ content += `
Connected Clients: ${serverInfo.connectedClients}
`;
+ content += `
Uptime: ${Math.floor(serverInfo.uptime)}s
`;
+ }
+ content += '
';
+
+ // Control buttons
+ content += '
';
+
+ if (serverInfo.isRunning) {
+ content += '';
+ content += ' Stop Server';
+ content += ' ';
+
+ content += '';
+ content += ' Restart Server';
+ content += ' ';
+ } else {
+ content += '';
+ content += ' Start Server';
+ content += ' ';
+ }
+
+ content += '
';
+
+ // Information section
+ content += '
';
+ content += '
The local server enables singleplayer mode when external servers are unavailable. Save data is stored locally on your computer. No internet connection required for local gameplay.
';
+ content += '
';
+
+ content += '
';
+
+ this.showModal('Local Server', content);
+
+ if (debugLogger) debugLogger.endStep('UIManager.showLocalServerControls', {
+ serverRunning: serverInfo.isRunning,
+ serverPort: serverInfo.port
+ });
+ }
+
+ async startLocalServer() {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('UIManager.startLocalServer');
+
+ try {
+
+ if (!window.localServerManager) {
+ this.game.showNotification('Local server manager not available', 'error', 3000);
+ return;
+ }
+
+ const result = await window.localServerManager.startServer();
+
+ if (result.success) {
+ this.game.showNotification(`Local server started on port ${result.port}`, 'success', 3000);
+ this.closeModal();
+
+ // Refresh the modal to show updated status
+ setTimeout(() => {
+ this.showLocalServerControls();
+ }, 500);
+ } else {
+ this.game.showNotification(`Failed to start server: ${result.error}`, 'error', 3000);
+ }
+
+ if (debugLogger) debugLogger.endStep('UIManager.startLocalServer', {
+ success: result.success,
+ port: result.port
+ });
+
+ } catch (error) {
+ this.game.showNotification('Error starting local server', 'error', 3000);
+
+ if (debugLogger) debugLogger.errorEvent(error, 'UIManager.startLocalServer');
+ }
+ }
+
+ async stopLocalServer() {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('UIManager.stopLocalServer');
+
+ try {
+
+ if (!window.localServerManager) {
+ this.game.showNotification('Local server manager not available', 'error', 3000);
+ return;
+ }
+
+ const result = await window.localServerManager.stopServer();
+
+ if (result.success) {
+ this.game.showNotification('Local server stopped', 'success', 3000);
+ this.closeModal();
+
+ // Refresh the modal to show updated status
+ setTimeout(() => {
+ this.showLocalServerControls();
+ }, 500);
+ } else {
+ this.game.showNotification(`Failed to stop server: ${result.error}`, 'error', 3000);
+ }
+
+ if (debugLogger) debugLogger.endStep('UIManager.stopLocalServer', {
+ success: result.success
+ });
+
+ } catch (error) {
+ this.game.showNotification('Error stopping local server', 'error', 3000);
+
+ if (debugLogger) debugLogger.errorEvent(error, 'UIManager.stopLocalServer');
+ }
+ }
+
+ async restartLocalServer() {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('UIManager.restartLocalServer');
+
+ try {
+
+ if (!window.localServerManager) {
+ this.game.showNotification('Local server manager not available', 'error', 3000);
+ return;
+ }
+
+ // Stop first
+ await this.stopLocalServer();
+
+ // Wait a moment
+ await new Promise(resolve => setTimeout(resolve, 1000));
+
+ // Start again
+ await this.startLocalServer();
+
+ if (debugLogger) debugLogger.endStep('UIManager.restartLocalServer', {
+ success: true
+ });
+
+ } catch (error) {
+ this.game.showNotification('Error restarting local server', 'error', 3000);
+
+ if (debugLogger) debugLogger.errorEvent(error, 'UIManager.restartLocalServer');
+ }
+ }
+
+ returnToMainMenu() {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('UIManager.returnToMainMenu');
+
+ // Go directly to server selection without confirmation modal
+ this.showMainMenu();
+ this.closeModal();
+
+ if (debugLogger) debugLogger.endStep('UIManager.returnToMainMenu', {
+ success: true,
+ directToServerSelection: true
+ });
+ }
+
+ async stopLocalServer() {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('UIManager.stopLocalServer');
+
+ try {
+
+ if (!window.localServerManager) {
+ this.game.showNotification('Local server manager not available', 'error', 3000);
+ return;
+ }
+
+ const result = await window.localServerManager.stopServer();
+
+ if (result.success) {
+ this.game.showNotification('Local server stopped', 'success', 3000);
+ this.closeModal();
+
+ // Refresh the modal to show updated status
+ setTimeout(() => {
+ this.showLocalServerControls();
+ }, 500);
+ } else {
+ this.game.showNotification(`Failed to stop server: ${result.error}`, 'error', 3000);
+ }
+
+ if (debugLogger) debugLogger.endStep('UIManager.stopLocalServer', {
+ success: result.success
+ });
+
+ } catch (error) {
+ this.game.showNotification('Error stopping local server', 'error', 3000);
+
+ if (debugLogger) debugLogger.errorEvent(error, 'UIManager.stopLocalServer');
+ }
+ }
+
+ async restartLocalServer() {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('UIManager.restartLocalServer');
+
+ try {
+
+ if (!window.localServerManager) {
+ this.game.showNotification('Local server manager not available', 'error', 3000);
+ return;
+ }
+
+ // Stop first
+ await this.stopLocalServer();
+
+ // Wait a moment
+ await new Promise(resolve => setTimeout(resolve, 1000));
+
+ // Start again
+ await this.startLocalServer();
+
+ if (debugLogger) debugLogger.endStep('UIManager.restartLocalServer', {
+ success: true
+ });
+
+ } catch (error) {
+ this.game.showNotification('Error restarting local server', 'error', 3000);
+
+ if (debugLogger) debugLogger.errorEvent(error, 'UIManager.restartLocalServer');
+ }
+ }
+
+ showReturnToMainMenuModal() {
+ const debugLogger = window.debugLogger;
+
+ // Show confirmation modal instead of browser confirm dialog
+ let content = '';
+ content += '
Are you sure you want to return to the main menu?
';
+ content += '
Warning: Any unsaved progress will be lost.
';
+ content += '
';
+
+ // Check if using fallback GameEngine
+ if (window.game && window.game.isFallback) {
+ content += 'Return to Menu ';
+ content += 'Cancel ';
+ } else {
+ content += 'Return to Menu ';
+ content += 'Cancel ';
+ if (debugLogger) debugLogger.endStep('UIManager.returnToMainMenu', {
+ success: true,
+ confirmationShown: true
+ });
+ }
+}
+
+ confirmReturnToMainMenu() {
+ try {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('UIManager.confirmReturnToMainMenu', {
+ gameRunning: this.game ? this.game.isRunning : false
+ });
+
+
+ // Stop the game engine and save
+ if (this.game && typeof this.game.isRunning === 'boolean') {
+
+ // if (debugLogger) debugLogger.logStep('Stopping game engine and saving');
+
+ // Handle async stop properly
+ this.game.stop().then(() => {
+ try {
+
+ // if (debugLogger) debugLogger.logStep('Game saved successfully');
+ this.showMainMenu();
+ this.closeModal();
+
+ if (debugLogger) debugLogger.endStep('UIManager.confirmReturnToMainMenu', {
+ success: true,
+ gameStopped: true,
+ mainMenuShown: true
+ });
+ } catch (error) {
+ }
+ }).catch(error => {
+ try {
+
+ if (debugLogger) debugLogger.errorEvent('UIManager.confirmReturnToMainMenu', error, {
+ phase: 'game_stop_and_save'
+ });
+
+ // Still return to menu even if save fails
+ this.showMainMenu();
+ this.closeModal();
+
+ if (debugLogger) debugLogger.endStep('UIManager.confirmReturnToMainMenu', {
+ success: true,
+ gameStopped: true,
+ saveError: true,
+ mainMenuShown: true
+ });
+ } catch (fallbackError) {
+ }
+ });
+ }
+
+ // Always show main menu regardless of game state
+ setTimeout(() => {
+ this.showMainMenu();
+ this.closeModal();
+ }, 5000); // 5 second delay to ensure full cleanup and menu readiness
+
+ if (debugLogger) debugLogger.endStep('UIManager.confirmReturnToMainMenu', {
+ success: true,
+ gameStopped: this.game ? !this.game.isRunning : false,
+ mainMenuShown: true
+ });
+ } catch (error) {
+ }
+ }
+
+ showMainMenu() {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('UIManager.showMainMenu');
+
+
+ // Disconnect from server first
+ if (window.gameInitializer && window.gameInitializer.socket) {
+ window.gameInitializer.socket.disconnect();
+ }
+
+ const gameInterface = document.getElementById('gameInterface');
+ if (gameInterface) {
+ gameInterface.classList.add('hidden');
+
+ // if (debugLogger) debugLogger.logStep('Game interface hidden');
+ } else {
+ if (debugLogger) debugLogger.log('Game interface element not found');
+ }
+
+ // Show main menu
+ if (window.liveMainMenu) {
+ // if (debugLogger) debugLogger.logStep('About to show main menu using LiveMainMenu');
+
+ try {
+ // Show the main menu DOM element and use LiveMainMenu to show appropriate section
+ const mainMenuElement = document.getElementById('mainMenu');
+ if (mainMenuElement) {
+ mainMenuElement.classList.remove('hidden');
+ }
+
+ // Show the server section (most appropriate for returning to menu)
+ if (window.liveMainMenu && typeof window.liveMainMenu.showServerSection === 'function') {
+ window.liveMainMenu.showServerSection();
+ } else {
+ // Fallback: manually show server section
+ console.warn('LiveMainMenu.showServerSection not available, using fallback');
+
+ // Ensure main menu is visible (using the already declared mainMenuElement)
+ if (mainMenuElement) {
+ mainMenuElement.classList.remove('hidden');
+ }
+
+ const serverSection = document.getElementById('serverSection');
+ const loginSection = document.getElementById('loginSection');
+ const serverConfirmSection = document.getElementById('serverConfirmSection');
+ const optionsSection = document.getElementById('optionsSection');
+
+ // Hide all sections first
+ if (loginSection) loginSection.classList.add('hidden');
+ if (serverConfirmSection) serverConfirmSection.classList.add('hidden');
+ if (optionsSection) optionsSection.classList.add('hidden');
+
+ // Show server section
+ if (serverSection) {
+ serverSection.classList.remove('hidden');
+ }
+
+ // Try to refresh server list if available
+ if (window.liveMainMenu && typeof window.liveMainMenu.refreshServerList === 'function') {
+ window.liveMainMenu.refreshServerList();
+ }
+ }
+
+ // if (debugLogger) debugLogger.logStep('LiveMainMenu showServerSection() completed successfully');
+ } catch (error) {
+ // if (debugLogger) debugLogger.logStep('Error in LiveMainMenu operations', { error: error.message });
+ }
+
+ // Show save section specifically for returning to menu
+ setTimeout(() => {
+ try {
+ // Note: LiveMainMenu doesn't have save methods, so we'll just log this
+ if (debugLogger) debugLogger.logStep('Save section operations skipped - LiveMainMenu doesn\'t have save methods');
+ } catch (error) {
+ if (debugLogger) debugLogger.logStep('Error in save section operations', { error: error.message });
+ }
+ }, 500);
+ } else if (window.mainMenu) {
+ // Fallback to just showing the DOM element
+ // if (debugLogger) debugLogger.logStep('Using fallback DOM display for main menu');
+
+ try {
+ const mainMenuElement = document.getElementById('mainMenu');
+ if (mainMenuElement) {
+ mainMenuElement.classList.remove('hidden');
+
+ // Also show server section in fallback mode
+ const serverSection = document.getElementById('serverSection');
+ const loginSection = document.getElementById('loginSection');
+ const serverConfirmSection = document.getElementById('serverConfirmSection');
+ const optionsSection = document.getElementById('optionsSection');
+
+ // Hide all sections first
+ if (loginSection) loginSection.classList.add('hidden');
+ if (serverConfirmSection) serverConfirmSection.classList.add('hidden');
+ if (optionsSection) optionsSection.classList.add('hidden');
+
+ // Show server section
+ if (serverSection) {
+ serverSection.classList.remove('hidden');
+ }
+
+ // if (debugLogger) debugLogger.logStep('Main menu DOM element shown via fallback with server section');
+ }
+ } catch (error) {
+ // if (debugLogger) debugLogger.logStep('Error in fallback main menu display', { error: error.message });
+ }
+ } else {
+ // Final fallback: directly manipulate DOM to show server section
+ console.warn('Neither LiveMainMenu nor mainMenu available, using final fallback');
+ try {
+ const mainMenuElement = document.getElementById('mainMenu');
+ const serverSection = document.getElementById('serverSection');
+ const loginSection = document.getElementById('loginSection');
+ const serverConfirmSection = document.getElementById('serverConfirmSection');
+ const optionsSection = document.getElementById('optionsSection');
+ const gameInterface = document.getElementById('gameInterface');
+
+ // Hide game interface
+ if (gameInterface) {
+ gameInterface.classList.add('hidden');
+ }
+
+ // Show main menu
+ if (mainMenuElement) {
+ mainMenuElement.classList.remove('hidden');
+ }
+
+ // Hide all sections first
+ if (loginSection) loginSection.classList.add('hidden');
+ if (serverConfirmSection) serverConfirmSection.classList.add('hidden');
+ if (optionsSection) optionsSection.classList.add('hidden');
+
+ // Show server section
+ if (serverSection) {
+ serverSection.classList.remove('hidden');
+ }
+ } catch (error) {
+ console.error('Error in final fallback server section display', error);
+ }
+ }
+
+ if (debugLogger) debugLogger.endStep('UIManager.showMainMenu', {
+ success: true,
+ mainMenuShown: !!window.mainMenu
+ });
+ }
+
+ // Tab management
+ switchTab(tabName) {
+ if (debugLogger) debugLogger.startStep('UIManager.switchTab', {
+ fromTab: this.currentTab,
+ toTab: tabName,
+ sameTab: this.currentTab === tabName
+ });
+
+ if (this.currentTab === tabName) {
+ if (debugLogger) debugLogger.log('Switching to same tab, no action needed', {
+ tabName: tabName
+ });
+
+ if (debugLogger) debugLogger.endStep('UIManager.switchTab', {
+ success: true,
+ action: 'no_change_needed',
+ currentTab: this.currentTab
+ });
+
+ return;
+ }
+
+ const oldTab = this.currentTab;
+
+ // Update navigation buttons
+ const navButtons = document.querySelectorAll('.nav-btn');
+ let navButtonsUpdated = 0;
+
+ navButtons.forEach(btn => {
+ const wasActive = btn.classList.contains('active');
+ const shouldBeActive = btn.dataset.tab === tabName;
+ btn.classList.toggle('active', shouldBeActive);
+
+ if (wasActive !== shouldBeActive) {
+ navButtonsUpdated++;
+ }
+ });
+
+ if (debugLogger) debugLogger.logStep('Navigation buttons updated', {
+ totalButtons: navButtons.length,
+ buttonsUpdated: navButtonsUpdated
+ });
+
+ // Update tab content
+ const tabContents = document.querySelectorAll('.tab-content');
+ let tabContentsUpdated = 0;
+
+ tabContents.forEach(content => {
+ const wasActive = content.classList.contains('active');
+ const shouldBeActive = content.id === `${tabName}-tab`;
+ content.classList.toggle('active', shouldBeActive);
+
+ if (wasActive !== shouldBeActive) {
+ tabContentsUpdated++;
+ }
+ });
+
+ if (debugLogger) debugLogger.logStep('Tab contents updated', {
+ totalContents: tabContents.length,
+ contentsUpdated: tabContentsUpdated
+ });
+
+ this.currentTab = tabName;
+ this.game.state.currentTab = tabName;
+
+ if (debugLogger) debugLogger.logStep('Tab state updated', {
+ oldTab: oldTab,
+ newTab: this.currentTab,
+ gameStateUpdated: true
+ });
+
+ // Update specific tab content
+ this.updateTabContent(tabName);
+
+ if (debugLogger) debugLogger.endStep('UIManager.switchTab', {
+ success: true,
+ oldTab: oldTab,
+ newTab: this.currentTab,
+ navButtonsUpdated: navButtonsUpdated,
+ tabContentsUpdated: tabContentsUpdated,
+ tabContentUpdated: true
+ });
+ }
+
+ updateTabContent(tabName) {
+ if (debugLogger) debugLogger.startStep('UIManager.updateTabContent', {
+ tabName: tabName,
+ currentTab: this.currentTab
+ });
+
+ let contentUpdated = false;
+ let updateError = null;
+
+ try {
+ switch(tabName) {
+ case 'dashboard':
+ // if (debugLogger) debugLogger.logStep('Updating dashboard tab content');
+ // Dashboard is the default view, no additional content to update
+ contentUpdated = true;
+ break;
+ case 'dungeons':
+ // if (debugLogger) debugLogger.logStep('Updating dungeons tab content');
+
+ // Ensure dungeon list is generated
+ if (window.game && window.game.systems && window.game.systems.dungeonSystem) {
+ // if (debugLogger) debugLogger.logStep('Ensuring dungeon list is generated...');
+ try {
+ window.game.systems.dungeonSystem.generateDungeonList();
+ } catch (error) {
+ if (debugLogger) debugLogger.errorEvent('UIManager.updateTabContent', error, {
+ tabName: tabName
+ });
+ }
+ }
+
+ // Only regenerate dungeon list if it's empty (first time load)
+ const dungeonListElement = document.getElementById('dungeonList');
+ if (dungeonListElement && dungeonListElement.children.length === 0) {
+ if (this.game && this.game.systems && this.game.systems.dungeonSystem) {
+ this.game.systems.dungeonSystem.generateDungeonList();
+ } else {
+ if (debugLogger) debugLogger.log('Dungeon system not available');
+ }
+ }
+ contentUpdated = true;
+ break;
+ case 'skills':
+ // if (debugLogger) debugLogger.logStep('Updating skills tab content');
+ if (this.game && this.game.systems && this.game.systems.skillSystem) {
+ this.game.systems.skillSystem.updateUI();
+ } else {
+ if (debugLogger) debugLogger.log('Skill system not available');
+ }
+ contentUpdated = true;
+ break;
+ case 'base':
+ // if (debugLogger) debugLogger.logStep('Updating base tab content');
+ if (this.game && this.game.systems && this.game.systems.baseSystem) {
+ this.game.systems.baseSystem.updateUI();
+ } else {
+ if (debugLogger) debugLogger.log('Base system not available');
+ }
+ contentUpdated = true;
+ break;
+ case 'quests':
+ // if (debugLogger) debugLogger.logStep('Updating quests tab content');
+ if (this.game && this.game.systems && this.game.systems.questSystem) {
+ this.game.systems.questSystem.updateUI();
+ } else {
+ if (debugLogger) debugLogger.log('Quest system not available');
+ }
+ contentUpdated = true;
+ break;
+ case 'inventory':
+ // if (debugLogger) debugLogger.logStep('Updating inventory tab content');
+ if (this.game && this.game.systems && this.game.systems.inventory) {
+ this.game.systems.inventory.updateUI();
+ } else {
+ if (debugLogger) debugLogger.log('Inventory system not available');
+ }
+ contentUpdated = true;
+ break;
+ case 'shop':
+ // if (debugLogger) debugLogger.logStep('Updating shop tab content');
+ if (this.game && this.game.systems && this.game.systems.economy) {
+ this.game.systems.economy.updateUI();
+ } else {
+ if (debugLogger) debugLogger.log('Economy system not available');
+ }
+ contentUpdated = true;
+ break;
+ case 'crafting':
+ // if (debugLogger) debugLogger.logStep('Updating crafting tab content');
+ if (this.game && this.game.systems && this.game.systems.crafting) {
+ this.game.systems.crafting.updateUI();
+ } else {
+ if (debugLogger) debugLogger.log('Crafting system not available');
+ }
+ contentUpdated = true;
+ break;
+ default:
+ // if (debugLogger) debugLogger.log('Unknown tab name, no content updated', {
+ // tabName: tabName
+ // });
+ contentUpdated = false;
+ }
+ } catch (error) {
+ updateError = error;
+ if (debugLogger) debugLogger.errorEvent('UIManager.updateTabContent', error, {
+ tabName: tabName
+ });
+ }
+
+ if (debugLogger) debugLogger.endStep('UIManager.updateTabContent', {
+ tabName: tabName,
+ contentUpdated: contentUpdated,
+ updateError: updateError ? updateError.message : null
+ });
+ }
+
+ // Category switching
+ switchSkillCategory(category) {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('UIManager.switchSkillCategory', {
+ category: category
+ });
+
+ const skillCatButtons = document.querySelectorAll('.skill-cat-btn');
+ let buttonsUpdated = 0;
+
+ skillCatButtons.forEach(btn => {
+ const wasActive = btn.classList.contains('active');
+ const shouldBeActive = btn.dataset.category === category;
+ btn.classList.toggle('active', shouldBeActive);
+
+ if (wasActive !== shouldBeActive) {
+ buttonsUpdated++;
+ }
+ });
+
+ if (debugLogger) debugLogger.logStep('Skill category buttons updated', {
+ totalButtons: skillCatButtons.length,
+ buttonsUpdated: buttonsUpdated
+ });
+
+ try {
+ this.game.systems.skillSystem.updateUI();
+
+ if (debugLogger) debugLogger.endStep('UIManager.switchSkillCategory', {
+ success: true,
+ category: category,
+ buttonsUpdated: buttonsUpdated,
+ skillUIUpdated: true
+ });
+ } catch (error) {
+ if (debugLogger) debugLogger.errorEvent('UIManager.switchSkillCategory', error, {
+ category: category
+ });
+
+ if (debugLogger) debugLogger.endStep('UIManager.switchSkillCategory', {
+ success: false,
+ category: category,
+ error: error.message
+ });
+ }
+ }
+
+ switchShopCategory(category) {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('UIManager.switchShopCategory', {
+ category: category
+ });
+
+ const shopCatButtons = document.querySelectorAll('.shop-cat-btn');
+ let buttonsUpdated = 0;
+
+ shopCatButtons.forEach(btn => {
+ const wasActive = btn.classList.contains('active');
+ const shouldBeActive = btn.dataset.category === category;
+ btn.classList.toggle('active', shouldBeActive);
+
+ if (wasActive !== shouldBeActive) {
+ buttonsUpdated++;
+ }
+ });
+
+ if (debugLogger) debugLogger.logStep('Shop category buttons updated', {
+ totalButtons: shopCatButtons.length,
+ buttonsUpdated: buttonsUpdated
+ });
+
+ try {
+ this.game.systems.economy.updateUI();
+
+ if (debugLogger) debugLogger.endStep('UIManager.switchShopCategory', {
+ success: true,
+ category: category,
+ buttonsUpdated: buttonsUpdated,
+ economyUIUpdated: true
+ });
+ } catch (error) {
+ if (debugLogger) debugLogger.errorEvent('UIManager.switchShopCategory', error, {
+ category: category
+ });
+
+ if (debugLogger) debugLogger.endStep('UIManager.switchShopCategory', {
+ success: false,
+ category: category,
+ error: error.message
+ });
+ }
+ }
+
+ switchQuestType(type) {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('UIManager.switchQuestType', {
+ type: type
+ });
+
+ const questTabButtons = document.querySelectorAll('.quest-tab-btn');
+ let buttonsUpdated = 0;
+
+ questTabButtons.forEach(btn => {
+ const wasActive = btn.classList.contains('active');
+ const shouldBeActive = btn.dataset.type === type;
+ btn.classList.toggle('active', shouldBeActive);
+
+ if (wasActive !== shouldBeActive) {
+ buttonsUpdated++;
+ }
+ });
+
+ if (debugLogger) debugLogger.logStep('Quest tab buttons updated', {
+ totalButtons: questTabButtons.length,
+ buttonsUpdated: buttonsUpdated
+ });
+
+ try {
+ this.game.systems.questSystem.updateUI();
+
+ if (debugLogger) debugLogger.endStep('UIManager.switchQuestType', {
+ success: true,
+ type: type,
+ buttonsUpdated: buttonsUpdated,
+ questUIUpdated: true
+ });
+ } catch (error) {
+ if (debugLogger) debugLogger.errorEvent('UIManager.switchQuestType', error, {
+ type: type
+ });
+
+ if (debugLogger) debugLogger.endStep('UIManager.switchQuestType', {
+ success: false,
+ type: type,
+ error: error.message
+ });
+ }
+ }
+
+ switchCraftingCategory(category) {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('UIManager.switchCraftingCategory', {
+ category: category
+ });
+
+ const craftingCatButtons = document.querySelectorAll('.crafting-cat-btn');
+ let buttonsUpdated = 0;
+
+ craftingCatButtons.forEach(btn => {
+ const wasActive = btn.classList.contains('active');
+ const shouldBeActive = btn.dataset.category === category;
+ btn.classList.toggle('active', shouldBeActive);
+
+ if (wasActive !== shouldBeActive) {
+ buttonsUpdated++;
+ }
+ });
+
+ if (debugLogger) debugLogger.logStep('Crafting category buttons updated', {
+ totalButtons: craftingCatButtons.length,
+ buttonsUpdated: buttonsUpdated
+ });
+
+ try {
+ this.game.systems.crafting.switchCategory(category);
+
+ if (debugLogger) debugLogger.endStep('UIManager.switchCraftingCategory', {
+ success: true,
+ category: category,
+ buttonsUpdated: buttonsUpdated,
+ craftingUIUpdated: true
+ });
+ } catch (error) {
+ if (debugLogger) debugLogger.errorEvent('UIManager.switchCraftingCategory', error, {
+ category: category
+ });
+
+ if (debugLogger) debugLogger.endStep('UIManager.switchCraftingCategory', {
+ success: false,
+ category: category,
+ error: error.message
+ });
+ }
+ }
+
+ // Modal management
+ showModal(title, content) {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('UIManager.showModal', {
+ title: title,
+ contentLength: content.length,
+ modalWasOpen: this.modalOpen
+ });
+
+ const modalOverlay = document.getElementById('modalOverlay');
+ const modalTitle = document.getElementById('modalTitle');
+ const modalBody = document.getElementById('modalBody');
+
+ if (!modalOverlay || !modalTitle || !modalBody) {
+ const missingElements = {
+ modalOverlay: !modalOverlay,
+ modalTitle: !modalTitle,
+ modalBody: !modalBody
+ };
+
+ if (debugLogger) debugLogger.endStep('UIManager.showModal', {
+ success: false,
+ reason: 'missing_modal_elements',
+ missingElements: missingElements
+ });
+
+ return;
+ }
+
+ const oldTitle = modalTitle.textContent;
+ const oldContent = modalBody.innerHTML;
+
+ modalTitle.textContent = title;
+ modalBody.innerHTML = content;
+
+ modalOverlay.classList.remove('hidden');
+ this.modalOpen = true;
+
+ if (debugLogger) debugLogger.endStep('UIManager.showModal', {
+ success: true,
+ title: title,
+ contentLength: content.length,
+ oldTitle: oldTitle,
+ oldContentLength: oldContent.length,
+ modalNowOpen: true
+ });
+ }
+
+ closeModal() {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('UIManager.closeModal', {
+ modalWasOpen: this.modalOpen
+ });
+
+ const modalOverlay = document.getElementById('modalOverlay');
+
+ if (modalOverlay) {
+ const wasHidden = modalOverlay.classList.contains('hidden');
+ modalOverlay.classList.add('hidden');
+
+ if (debugLogger) debugLogger.logStep('Modal overlay hidden', {
+ wasAlreadyHidden: wasHidden
+ });
+ } else {
+ if (debugLogger) debugLogger.log('Modal overlay element not found');
+ }
+
+ const wasModalOpen = this.modalOpen;
+ this.modalOpen = false;
+
+ if (debugLogger) debugLogger.endStep('UIManager.closeModal', {
+ success: true,
+ modalOverlayFound: !!modalOverlay,
+ wasModalOpen: wasModalOpen,
+ modalNowClosed: !this.modalOpen
+ });
+ }
+
+ // Handle UI update events from GameEngine
+ handleUIUpdate(data) {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('UIManager.handleUIUpdate', data);
+
+ if (debugLogger) debugLogger.log('Handling UI update event:', data);
+
+ switch (data.type) {
+ case 'full':
+ this.updateAllUI();
+ break;
+ case 'economy':
+ this.updateResourceDisplay();
+ break;
+ case 'player':
+ this.updateResourceDisplay();
+ break;
+ case 'quests':
+ if (this.game && this.game.systems && this.game.systems.questSystem) {
+ this.game.systems.questSystem.updateUI();
+ }
+ break;
+ case 'dailyCountdown':
+ if (this.game && this.game.systems && this.game.systems.questSystem) {
+ this.game.systems.questSystem.updateDailyCountdown();
+ }
+ break;
+ case 'weeklyCountdown':
+ if (this.game && this.game.systems && this.game.systems.questSystem) {
+ this.game.systems.questSystem.updateWeeklyCountdown();
+ }
+ break;
+ default:
+ if (debugLogger) debugLogger.log('Unknown update type:', data.type);
+ }
+
+ if (debugLogger) debugLogger.endStep('UIManager.handleUIUpdate', {
+ type: data.type,
+ handled: true
+ });
+ }
+
+ updateAllUI() {
+ // if (debugLogger) debugLogger.log('Updating all UI elements');
+
+ this.updateResourceDisplay();
+
+ // Update other UI components as needed
+ if (this.game && this.game.systems) {
+ // Update quest system
+ if (this.game.systems.questSystem) {
+ this.game.systems.questSystem.updateUI();
+ }
+
+ // Update daily countdown
+ if (this.game.systems.questSystem) {
+ this.game.systems.questSystem.updateDailyCountdown();
+ this.game.systems.questSystem.updateWeeklyCountdown();
+ }
+ }
+
+ // if (debugLogger) debugLogger.log('All UI elements updated');
+ }
+
+ // Resource display
+ setupResourceDisplay() {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('UIManager.setupResourceDisplay');
+
+ // Resource display is handled in updateResourceDisplay method
+
+ if (debugLogger) debugLogger.endStep('UIManager.setupResourceDisplay', {
+ setupCompleted: true
+ });
+ }
+
+ updateResourceDisplay() {
+ const debugLogger = window.debugLogger;
+
+ try {
+ // Safety checks - return early if systems aren't available
+ if (!this.game || !this.game.systems) {
+ if (debugLogger) debugLogger.log('Game systems not available, skipping resource display update');
+ return;
+ }
+
+ if (!this.game.systems.player || !this.game.systems.economy) {
+ if (debugLogger) debugLogger.log('Player or economy system not available, skipping resource display update');
+ return;
+ }
+
+ const player = this.game.systems.player;
+ const economy = this.game.systems.economy;
+
+ // Additional safety checks for player properties
+ if (!player.stats || !player.attributes || !player.ship) {
+ if (debugLogger) debugLogger.log('Player properties not fully initialized, skipping resource display update');
+ return;
+ }
+
+ if (debugLogger) debugLogger.startStep('UIManager.updateResourceDisplay', {
+ gameSystemsAvailable: !!(this.game && this.game.systems),
+ playerSystemAvailable: !!(this.game && this.game.systems && this.game.systems.player),
+ economySystemAvailable: !!(this.game && this.game.systems && this.game.systems.economy),
+ playerStatsAvailable: !!player.stats,
+ playerAttributesAvailable: !!player.attributes,
+ playerShipAvailable: !!player.ship
+ });
+
+ let elementsUpdated = 0;
+ let elementsNotFound = 0;
+
+ // Update player level with safety checks
+ const playerLevelElement = document.getElementById('playerLevel');
+ if (playerLevelElement) {
+ const oldLevel = playerLevelElement.textContent;
+ const playerLevel = player.stats.level || 1;
+ playerLevelElement.textContent = `Lv. ${playerLevel}`;
+ elementsUpdated++;
+
+ if (debugLogger) debugLogger.logStep('Player level updated', {
+ oldLevel: oldLevel,
+ newLevel: playerLevelElement.textContent,
+ playerStatsLevel: player.stats.level
+ });
+ } else {
+ elementsNotFound++;
+ if (debugLogger) debugLogger.log('Player level element not found');
+ }
+
+ // Update ship level with safety checks
+ const shipLevelElement = document.getElementById('currentShipLevel');
+ if (shipLevelElement) {
+ const oldShipLevel = shipLevelElement.textContent;
+ const shipLevel = player.ship.level || player.stats.level || 1;
+ shipLevelElement.textContent = shipLevel;
+ elementsUpdated++;
+
+ if (debugLogger) debugLogger.logStep('Ship level updated', {
+ oldShipLevel: oldShipLevel,
+ newShipLevel: shipLevelElement.textContent,
+ playerShipLevel: player.ship.level,
+ playerStatsLevel: player.stats.level
+ });
+ } else {
+ elementsNotFound++;
+ if (debugLogger) debugLogger.log('Ship level element not found');
+ }
+
+ // Update credits with safety checks
+ const creditsElement = document.getElementById('credits');
+ if (creditsElement) {
+ const oldCredits = creditsElement.textContent;
+ const creditsAmount = economy.credits || 0;
+ creditsElement.textContent = this.game.formatNumber(creditsAmount);
+ elementsUpdated++;
+
+ if (debugLogger) debugLogger.logStep('Credits updated', {
+ oldCredits: oldCredits,
+ newCredits: creditsElement.textContent,
+ economyCredits: economy.credits,
+ formattedCredits: this.game.formatNumber(creditsAmount)
+ });
+ } else {
+ elementsNotFound++;
+ if (debugLogger) debugLogger.log('Credits element not found');
+ }
+
+ // Update gems with safety checks
+ const gemsElement = document.getElementById('gems');
+ if (gemsElement) {
+ const oldGems = gemsElement.textContent;
+ const gemsAmount = economy.gems || 0;
+ gemsElement.textContent = this.game.formatNumber(gemsAmount);
+ elementsUpdated++;
+
+ if (debugLogger) debugLogger.logStep('Gems updated', {
+ oldGems: oldGems,
+ newGems: gemsElement.textContent,
+ economyGems: economy.gems,
+ formattedGems: this.game.formatNumber(gemsAmount)
+ });
+ } else {
+ elementsNotFound++;
+ if (debugLogger) debugLogger.log('Gems element not found');
+ }
+
+ // Update energy with safety checks
+ const energyElement = document.getElementById('energy');
+ if (energyElement) {
+ const oldEnergy = energyElement.textContent;
+ // Ensure we're using the correct energy values, not credits
+ const currentEnergy = Math.floor(player.attributes.energy || 0);
+ const maxEnergy = Math.floor(player.attributes.maxEnergy || 100);
+ energyElement.textContent = `${currentEnergy}/${maxEnergy}`;
+ elementsUpdated++;
+
+ if (debugLogger) debugLogger.logStep('Energy updated', {
+ oldEnergy: oldEnergy,
+ newEnergy: energyElement.textContent,
+ currentEnergy: currentEnergy,
+ maxEnergy: maxEnergy,
+ playerEnergy: player.attributes.energy,
+ playerMaxEnergy: player.attributes.maxEnergy
+ });
+ } else {
+ elementsNotFound++;
+ if (debugLogger) debugLogger.log('Energy element not found');
+ }
+
+ if (debugLogger) debugLogger.endStep('UIManager.updateResourceDisplay', {
+ elementsUpdated: elementsUpdated,
+ elementsNotFound: elementsNotFound,
+ playerLevel: player.stats.level || 1,
+ shipLevel: player.ship.level || player.stats.level || 1,
+ credits: economy.credits || 0,
+ gems: economy.gems || 0,
+ currentEnergy: Math.floor(player.attributes.energy || 0),
+ maxEnergy: Math.floor(player.attributes.maxEnergy || 100)
+ });
+ } catch (error) {
+ if (debugLogger) debugLogger.log('Error in updateResourceDisplay', {
+ error: error.message,
+ stack: error.stack
+ });
+ }
+ }
+
+ updateUI() {
+ const debugLogger = window.debugLogger;
+
+ if (debugLogger) debugLogger.startStep('UIManager.updateUI', {
+ currentTab: this.currentTab,
+ modalOpen: this.modalOpen
+ });
+
+ // Update resource display
+ if (debugLogger) debugLogger.logStep('Updating resource display');
+ this.updateResourceDisplay();
+
+ // Update tab content
+ if (debugLogger) debugLogger.logStep('Updating tab content', {
+ currentTab: this.currentTab
+ });
+ this.updateTabContent(this.currentTab);
+
+ if (debugLogger) debugLogger.endStep('UIManager.updateUI', {
+ resourceDisplayUpdated: true,
+ tabContentUpdated: true,
+ currentTab: this.currentTab
+ });
+ }
+
+ // Menus
+ showSettingsMenu() {
+
+ // Clear any existing modal content first
+ const modalBody = document.getElementById('modalBody');
+ if (modalBody) {
+ modalBody.innerHTML = '';
+ }
+
+ // Get current auto-save setting
+ const currentAutoSaveInterval = localStorage.getItem('autoSaveInterval') || '5';
+
+ let content = '