worked on dungeons + started work on quests

This commit is contained in:
Robert MacRae 2026-01-30 10:58:30 -04:00
parent 6db6e1ebdb
commit 485b5c3eb8
13 changed files with 2335 additions and 857 deletions

View File

@ -155,13 +155,37 @@ class GameInitializer {
}); });
this.socket.on('onlineIdleRewards', (data) => { this.socket.on('onlineIdleRewards', (data) => {
if (data.credits > 0 || data.experience > 0) {
console.log('[GAME INITIALIZER] Online idle rewards received:', data); console.log('[GAME INITIALIZER] Online idle rewards received:', data);
}
this.onOnlineIdleRewards(data); this.onOnlineIdleRewards(data);
}); });
// Quest completion events
this.socket.on('quest_completed', (data) => {
console.log('[GAME INITIALIZER] Quest completed:', data);
this.onQuestCompleted(data);
});
// Player stat update events
this.socket.on('player_stat_update', (data) => {
console.log('[GAME INITIALIZER] Player stat update:', data);
this.onPlayerStatUpdate(data);
});
// Quest data events
this.socket.on('quests_data', (data) => {
console.log('[GAME INITIALIZER] Quest data received:', data);
this.onQuestsData(data);
});
// PlayTime events // PlayTime events
this.socket.on('playTimeUpdated', (data) => { this.socket.on('playTimeUpdated', (data) => {
console.log('[GAME INITIALIZER] PlayTime updated from server:', data); // Only log playtime updates every minute (600,000 ms) to reduce spam
if (!this.lastPlayTimeLog || Date.now() - this.lastPlayTimeLog > 60000) {
console.log('[GAME INITIALIZER] PlayTime updated:', `${Math.floor(data.playTime / 3600000)}h ${Math.floor((data.playTime % 3600000) / 60000)}m`);
this.lastPlayTimeLog = Date.now();
}
this.onPlayTimeUpdated(data); this.onPlayTimeUpdated(data);
}); });
@ -342,6 +366,12 @@ class GameInitializer {
window.game.loadServerPlayerData(this.serverPlayerData); window.game.loadServerPlayerData(this.serverPlayerData);
console.log('[GAME INITIALIZER] Server player data applied to GameEngine'); console.log('[GAME INITIALIZER] Server player data applied to GameEngine');
// CRITICAL: Force immediate economy sync
if (window.game.systems && window.game.systems.economy && window.game.systems.economy.syncWithServerData) {
console.log('[GAME INITIALIZER] Forcing immediate economy sync with server data');
window.game.systems.economy.syncWithServerData(this.serverPlayerData);
}
// Force UI refresh // Force UI refresh
if (window.game.systems && window.game.systems.ui && window.game.systems.ui.forceRefreshAllUI) { if (window.game.systems && window.game.systems.ui && window.game.systems.ui.forceRefreshAllUI) {
console.log('[GAME INITIALIZER] Forcing UI refresh after data application'); console.log('[GAME INITIALIZER] Forcing UI refresh after data application');
@ -730,65 +760,65 @@ class GameInitializer {
username: this.currentUser.username username: this.currentUser.username
}); });
} else { } else {
console.warn('[GAME INITIALIZER] Cannot authenticate - missing socket or user data'); // Try to get from localStorage as fallback
if (!this.socket) { const storedUser = localStorage.getItem('currentUser');
console.warn('[GAME INITIALIZER] Socket is null/undefined'); if (storedUser) {
} try {
if (!this.currentUser) { const user = JSON.parse(storedUser);
console.warn('[GAME INITIALIZER] Current user is null/undefined'); const username = user.username || 'anonymous';
} } catch (e) {
} console.warn('[GAME INITIALIZER] Failed to parse stored user, using default');
}
onOfflineRewardsClaimed(data) {
if (data.success) {
if (data.rewards.credits > 0 || data.rewards.experience > 0) {
// Apply rewards to player
if (window.game && window.game.systems) {
if (data.rewards.credits > 0) {
window.game.systems.economy.addCredits(data.rewards.credits, 'offline');
}
if (data.rewards.experience > 0) {
window.game.systems.player.addExperience(data.rewards.experience);
}
// Show success message
let message = 'Offline rewards claimed!\n';
if (data.rewards.credits > 0) message += `+${data.rewards.credits} credits\n`;
if (data.rewards.experience > 0) message += `+${data.rewards.experience} experience\n`;
window.game.showNotification(message, 'success', 5000);
} }
} else { } else {
window.game.showNotification('No offline rewards available', 'info', 3000); window.game.showNotification('No offline rewards available', 'info', 3000);
} }
} else {
window.game.showNotification(`Failed to claim offline rewards: ${data.error}`, 'error', 5000);
} }
} }
onOnlineIdleRewards(data) { onOnlineIdleRewards(data) {
if (window.game && window.game.systems) { if (window.game && window.game.systems) {
// Only log if there are actual rewards to process
if (data.credits > 0 || data.experience > 0 || data.energy > 0) {
console.log('[GAME INITIALIZER] Processing online idle rewards:', data);
}
// Update player balance with online idle rewards // Update player balance with online idle rewards
if (data.credits > 0) { if (data.credits > 0) {
// The server already updated the balance, just show notification // Update the Economy system's local credits
if (window.game.systems.economy) {
window.game.systems.economy.credits += data.credits;
// Update UI immediately
if (window.game.ui) {
window.game.ui.updatePlayerStats();
console.log('[GAME INITIALIZER] UI updated with new credits');
}
} else {
console.warn('[GAME INITIALIZER] Economy system not available for credit update');
}
// Show notification
window.game.showNotification(`+${data.credits} credits (online idle)`, 'success', 2000); window.game.showNotification(`+${data.credits} credits (online idle)`, 'success', 2000);
} }
// Update experience if provided
if (data.experience > 0 && window.game.systems.player) {
window.game.systems.player.addExperience(data.experience);
console.log('[GAME INITIALIZER] Added idle experience:', data.experience);
}
} else {
console.warn('[GAME INITIALIZER] Game systems not available for idle rewards');
} }
} }
onPlayTimeUpdated(data) { onPlayTimeUpdated(data) {
console.log('[GAME INITIALIZER] PlayTime updated from server:', data); // PlayTime updates are handled in the socket listener with throttled logging
if (window.game && window.game.systems && window.game.systems.player) { if (window.game && window.game.systems && window.game.systems.player) {
const player = window.game.systems.player; const player = window.game.systems.player;
// Update playTime from server // Update playTime from server
player.stats.playTime = data.playTime; player.stats.playTime = data.playTime;
console.log('[GAME INITIALIZER] Updated local playTime to:', data.playTime, 'ms');
console.log('[GAME INITIALIZER] PlayTime in hours:', data.playTime / (1000 * 60 * 60), 'hours');
// Update UI // Update UI
player.updateUI(); player.updateUI();
} }
@ -807,6 +837,72 @@ class GameInitializer {
economy.gems = data.newBalance; economy.gems = data.newBalance;
} }
// Add item to inventory if it's a consumable or material
if (data.item && (data.item.type === 'consumable' || data.item.type === 'material')) {
if (window.game.systems.inventory) {
console.log('[GAME INITIALIZER] Adding item to inventory:', data.item);
window.game.systems.inventory.addItem(data.item, data.quantity || 1);
window.game.showNotification(`${data.item.name} added to inventory!`, 'success', 3000);
}
}
// Update owned ships if it's a ship
if (data.item && data.item.type === 'ship') {
if (!economy.ownedShips) economy.ownedShips = [];
if (!economy.ownedShips.includes(data.item.id)) {
economy.ownedShips.push(data.item.id);
console.log('[GAME INITIALIZER] Ship added to owned ships:', data.item.id);
// Add ship to BaseSystem ship gallery
if (window.game.systems.baseSystem) {
console.log('[GAME INITIALIZER] BaseSystem available, adding ship to gallery');
const shipData = {
id: data.item.id,
name: data.item.name,
class: data.item.name.replace(/\s+/g, '_').toLowerCase(), // Generate class from name
level: 1,
stats: data.item.stats || {},
texture: data.item.texturePath || `assets/textures/ships/${data.item.id}.png`,
isCurrent: false,
rarity: data.item.rarity || 'common'
};
console.log('[GAME INITIALIZER] Ship data prepared:', shipData);
// Initialize ship gallery if needed
if (!window.game.systems.baseSystem.purchasedShips) {
console.log('[GAME INITIALIZER] Initializing ship gallery');
window.game.systems.baseSystem.initializeShipGallery();
}
// Check if ship already exists
const existingShip = window.game.systems.baseSystem.purchasedShips.find(s => s.id === shipData.id);
if (!existingShip) {
// Add ship to gallery
window.game.systems.baseSystem.purchasedShips.push(shipData);
console.log('[GAME INITIALIZER] Ship added to gallery. Total ships:', window.game.systems.baseSystem.purchasedShips.length);
// Update the ship gallery UI
window.game.systems.baseSystem.updateShipGallery();
console.log('[GAME INITIALIZER] Ship gallery UI updated');
} else {
console.log('[GAME INITIALIZER] Ship already exists in gallery:', shipData.id);
}
} else {
console.error('[GAME INITIALIZER] BaseSystem not available for ship gallery');
}
}
}
// Update owned cosmetics if it's a cosmetic
if (data.item && data.item.type === 'cosmetic') {
if (!economy.ownedCosmetics) economy.ownedCosmetics = [];
if (!economy.ownedCosmetics.includes(data.item.id)) {
economy.ownedCosmetics.push(data.item.id);
console.log('[GAME INITIALIZER] Cosmetic added to owned cosmetics:', data.item.id);
}
}
// Request fresh economy data from server to ensure sync // Request fresh economy data from server to ensure sync
if (economy.requestEconomyData) { if (economy.requestEconomyData) {
setTimeout(() => { setTimeout(() => {
@ -814,9 +910,21 @@ class GameInitializer {
}, 500); }, 500);
} }
// Also request economy data immediately to prevent reset
if (economy.requestEconomyData) {
economy.requestEconomyData();
}
// Update UI // Update UI
economy.updateUI(); economy.updateUI();
// Update inventory UI if item was added
if (data.item && (data.item.type === 'consumable' || data.item.type === 'material')) {
if (window.game.systems.inventory) {
window.game.systems.inventory.updateUI();
}
}
// Show success message // Show success message
window.game.showNotification(`Purchased ${data.item.name}!`, 'success', 3000); window.game.showNotification(`Purchased ${data.item.name}!`, 'success', 3000);
} }
@ -828,8 +936,28 @@ class GameInitializer {
onShopItemsReceived(data) { onShopItemsReceived(data) {
if (data.success && window.game && window.game.systems && window.game.systems.itemSystem) { if (data.success && window.game && window.game.systems && window.game.systems.itemSystem) {
console.log('[GAME INITIALIZER] Processing shop items data structure:', Object.keys(data));
// Handle both old (data.items) and new (data.shopItems) structures
let itemsToProcess = null;
if (data.shopItems && typeof data.shopItems === 'object') {
// New structure: categorized items
console.log('[GAME INITIALIZER] Using new shop structure');
itemsToProcess = Object.values(data.shopItems).flat();
console.log('[GAME INITIALIZER] Flattened', itemsToProcess.length, 'items from categories');
} else if (data.items && Array.isArray(data.items)) {
// Old structure: flat array
console.log('[GAME INITIALIZER] Using old shop structure');
itemsToProcess = data.items;
console.log('[GAME INITIALIZER] Processing', itemsToProcess.length, 'items from flat array');
} else {
console.warn('[GAME INITIALIZER] Invalid shop items structure:', data);
return;
}
// Update ItemSystem with server data // Update ItemSystem with server data
window.game.systems.itemSystem.processServerItems(data.items); window.game.systems.itemSystem.processServerItems(itemsToProcess);
console.log('[GAME INITIALIZER] ItemSystem updated with server shop items'); console.log('[GAME INITIALIZER] ItemSystem updated with server shop items');
// Update Economy shop UI // Update Economy shop UI
@ -872,6 +1000,101 @@ class GameInitializer {
this.currentUser = null; this.currentUser = null;
this.serverPlayerData = null; this.serverPlayerData = null;
} }
onQuestCompleted(data) {
console.log('[GAME INITIALIZER] Processing quest completion:', data);
// Show quest completion notification
if (window.game && window.game.showNotification) {
const questName = data.questId.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase());
window.game.showNotification(`Quest Completed: ${questName}! 🎯`, 'success', 5000);
}
// Award quest rewards
if (data.rewards) {
if (data.rewards.experience > 0 && window.game.systems.player) {
window.game.systems.player.addExperience(data.rewards.experience);
console.log('[GAME INITIALIZER] Awarded quest experience:', data.rewards.experience);
}
if (data.rewards.credits > 0 && window.game.systems.economy) {
window.game.systems.economy.credits += data.rewards.credits;
console.log('[GAME INITIALIZER] Awarded quest credits:', data.rewards.credits);
}
}
// Update quest UI if quest system exists
if (window.game && window.game.systems && window.game.systems.questSystem) {
window.game.systems.questSystem.completeQuest(data.questId, data.rewards);
// Force fetch fresh quest data from server (Fix #1 - Corrected)
console.log('[GAME INITIALIZER] Requesting fresh quest data from server after completion');
// Request fresh quest data from server
if (window.gameInitializer && window.gameInitializer.socket) {
window.gameInitializer.socket.emit('get_quests');
}
}
// Force quest UI refresh for server-driven quests
if (typeof updateQuestDisplay === 'function') {
updateQuestDisplay();
}
// Update UI
if (window.game && window.game.ui) {
window.game.ui.updatePlayerStats();
}
}
onPlayerStatUpdate(data) {
console.log('[GAME INITIALIZER] Processing player stat update:', data);
if (window.game && window.game.systems && window.game.systems.player && window.game.systems.player.stats) {
// Update the player stat
window.game.systems.player.stats[data.stat] = data.value;
console.log('[GAME INITIALIZER] Updated player stat:', data.stat, '=', data.value);
// Update UI to reflect the change
if (window.game.ui) {
window.game.ui.updatePlayerStats();
}
// Update quest system to check for quest progress
if (window.game.systems.questSystem) {
window.game.systems.questSystem.checkQuestAvailability();
}
}
}
onQuestsData(data) {
console.log('[GAME INITIALIZER] Processing quest data from server:', data);
console.log('[GAME INITIALIZER] Quest data keys:', Object.keys(data || {}));
console.log('[GAME INITIALIZER] Main quests count:', (data?.mainQuests || []).length);
console.log('[GAME INITIALIZER] Daily quests count:', (data?.dailyQuests || []).length);
console.log('[GAME INITIALIZER] Weekly quests count:', (data?.weeklyQuests || []).length);
if (window.game && window.game.systems && window.game.systems.questSystem) {
// Load quest data into the quest system
if (window.game.systems.questSystem.loadServerQuests) {
console.log('[GAME INITIALIZER] Calling loadServerQuests with data');
window.game.systems.questSystem.loadServerQuests(data);
console.log('[GAME INITIALIZER] Loaded quest data into quest system');
} else {
console.log('[GAME INITIALIZER] loadServerQuests method not found');
}
// Update quest UI
if (typeof updateQuestDisplay === 'function') {
console.log('[GAME INITIALIZER] Calling updateQuestDisplay');
updateQuestDisplay();
} else {
console.log('[GAME INITIALIZER] updateQuestDisplay function not found');
}
} else {
console.log('[GAME INITIALIZER] Quest system not available');
}
}
} }
// Create global instance // Create global instance

View File

@ -8,28 +8,25 @@ class Economy {
constructor(gameEngine) { constructor(gameEngine) {
this.game = gameEngine; this.game = gameEngine;
// Preserve existing economy data if available (prevents wipe during re-initialization) // Currency - don't override in multiplayer mode, will be set by server data
const existingEconomy = window.game?.systems?.economy; if (window.smartSaveManager?.isMultiplayer) {
const preservedCredits = existingEconomy?.credits || 0; this.credits = 0; // Will be updated by server
const preservedGems = existingEconomy?.gems || 0; this.gems = 0; // Will be updated by server
this.premiumCurrency = 0; // Will be updated by server
} else {
this.credits = 10000; // Starting credits for singleplayer
this.gems = 50; // Starting premium currency
this.premiumCurrency = 0; // Additional premium currency
}
// Player resources // Transaction history
this.credits = preservedCredits; this.transactions = [];
this.gems = preservedGems;
this.premiumCurrency = 0;
// Transaction tracking
this.transactionHistory = [];
this.transactions = []; // Add missing transactions array
// Owned cosmetics
this.ownedCosmetics = []; // Add missing owned cosmetics array
// Shop categories // Shop categories
this.shopCategories = { this.shopCategories = {
ships: 'Ships', ships: 'Ships',
weapons: 'Weapons', weapons: 'Weapons',
modules: 'Modules', armors: 'Armors',
cosmetics: 'Cosmetics', cosmetics: 'Cosmetics',
consumables: 'Consumables', consumables: 'Consumables',
materials: 'Materials' materials: 'Materials'
@ -40,6 +37,7 @@ class Economy {
this.shopRefreshInterval = null; // Timer for 2-hour refresh this.shopRefreshInterval = null; // Timer for 2-hour refresh
this.shopHeartbeatInterval = null; // Timer for live countdown updates this.shopHeartbeatInterval = null; // Timer for live countdown updates
this.lastShopRefresh = null; // Timestamp of last refresh this.lastShopRefresh = null; // Timestamp of last refresh
this.currentShopData = null; // Current shop data from server
this.SHOP_REFRESH_INTERVAL = 2 * 60 * 60 * 1000; // 2 hours in milliseconds this.SHOP_REFRESH_INTERVAL = 2 * 60 * 60 * 1000; // 2 hours in milliseconds
this.MAX_ITEMS_PER_CATEGORY = 8; this.MAX_ITEMS_PER_CATEGORY = 8;
this.categoryPurchaseLimits = {}; // Track purchases per category per refresh this.categoryPurchaseLimits = {}; // Track purchases per category per refresh
@ -50,18 +48,92 @@ class Economy {
// Owned cosmetics // Owned cosmetics
this.ownedCosmetics = []; this.ownedCosmetics = [];
console.log('[ECONOMY] Economy system initialized with server-side ItemSystem'); // Owned ships
console.log('[ECONOMY] Preserved values - Credits:', this.credits, 'Gems:', this.gems); this.ownedShips = [];
// Set up socket listeners for economy sync console.log('[ECONOMY] Economy system initialized');
this.setupSocketListeners();
// Request fresh economy data after a short delay to ensure sync // Initialize global purchase function
if (window.smartSaveManager?.isMultiplayer) { Economy.initGlobalPurchaseFunction();
setTimeout(() => {
this.requestEconomyData();
}, 1000);
} }
// Create global purchase function for shop buttons
static initGlobalPurchaseFunction() {
window.purchaseShopItem = function(itemId) {
console.log('[GLOBAL] Purchase shop item called:', itemId);
if (window.game && window.game.systems && window.game.systems.economy) {
window.game.systems.economy.purchaseItem(itemId, 1);
} else {
console.error('[GLOBAL] Economy system not available for purchase');
}
};
// Add test function for idle system
window.testIdleRewards = function() {
console.log('[GLOBAL] Testing idle rewards...');
if (window.game && window.game.socket) {
window.game.socket.emit('testIdleRewards', {});
// Listen for response
window.game.socket.once('testIdleRewards', (data) => {
console.log('[GLOBAL] Test idle rewards response:', data);
});
} else {
console.error('[GLOBAL] No socket available for idle test');
}
};
// Add socket event monitor
window.monitorSocketEvents = function() {
if (window.game && window.game.socket) {
console.log('[GLOBAL] Monitoring socket events...');
// Monitor all incoming events
const originalOn = window.game.socket.on;
window.game.socket.on = function(event, callback) {
const wrappedCallback = function(data) {
if (event === 'onlineIdleRewards' || event === 'economy_data') {
console.log('[SOCKET MONITOR] Received event:', event, data);
}
return callback(data);
};
return originalOn.call(this, event, wrappedCallback);
};
console.log('[GLOBAL] Socket event monitoring enabled');
} else {
console.error('[GLOBAL] No socket available for monitoring');
}
};
// Add function to give player energy for testing dungeons
window.addEnergy = function(amount = 50) {
if (window.game && window.game.systems && window.game.systems.player) {
const player = window.game.systems.player;
const oldEnergy = player.attributes.energy || 0;
player.attributes.energy = Math.min(oldEnergy + amount, player.attributes.maxEnergy || 100);
console.log('[GLOBAL] Added energy:', oldEnergy, '->', player.attributes.energy);
// Update UI
if (player.updateUI) {
player.updateUI();
}
// Update dungeon UI if available
if (window.game.systems.dungeonSystem && window.game.systems.dungeonSystem.updateUI) {
window.game.systems.dungeonSystem.updateUI();
}
return player.attributes.energy;
} else {
console.error('[GLOBAL] Player system not available');
return 0;
}
};
console.log('[GLOBAL] Global functions initialized - purchaseShopItem() and testIdleRewards() available');
} }
/** /**
@ -76,9 +148,15 @@ class Economy {
// Listen for economy data updates from server // Listen for economy data updates from server
this.game.socket.on('economy_data', (data) => { this.game.socket.on('economy_data', (data) => {
console.log('[ECONOMY] Received economy data from server:', data); console.log('[ECONOMY] Received economy data from server:', data);
console.log('[ECONOMY] Current credits before update:', this.credits);
console.log('[ECONOMY] Current gems before update:', this.gems);
this.credits = data.credits || 0; this.credits = data.credits || 0;
this.gems = data.gems || 0; this.gems = data.gems || 0;
console.log('[ECONOMY] Updated credits:', this.credits);
console.log('[ECONOMY] Updated gems:', this.gems);
// Update UI immediately // Update UI immediately
if (this.game.ui) { if (this.game.ui) {
this.game.ui.updatePlayerStats(); this.game.ui.updatePlayerStats();
@ -86,6 +164,18 @@ class Economy {
console.log('[ECONOMY] Economy synced - Credits:', this.credits, 'Gems:', this.gems); console.log('[ECONOMY] Economy synced - Credits:', this.credits, 'Gems:', this.gems);
}); });
// Note: onlineIdleRewards is handled by GameInitializer to avoid duplicate event handling
// Listen for play time updates from server
this.game.socket.on('playTimeUpdated', (data) => {
console.log('[ECONOMY] Received play time update from server:', data);
// Update player stats if available
if (this.game.systems.player && this.game.systems.player.stats) {
this.game.systems.player.stats.playTime = data.playTime;
}
});
} }
/** /**
@ -314,6 +404,33 @@ class Economy {
player.ownedShips.push(ship.id); player.ownedShips.push(ship.id);
} }
// Add ship to BaseSystem ship gallery (singleplayer)
if (this.game.systems.baseSystem) {
const shipData = {
id: ship.id,
name: ship.name,
class: ship.name.replace(/\s+/g, '_').toLowerCase(), // Generate class from name
level: 1,
stats: ship.stats || {},
texture: ship.texture || `assets/textures/ships/${ship.id}.png`,
isCurrent: false,
rarity: ship.rarity || 'common'
};
// Initialize ship gallery if needed
if (!this.game.systems.baseSystem.purchasedShips) {
this.game.systems.baseSystem.initializeShipGallery();
}
// Add ship to gallery
this.game.systems.baseSystem.purchasedShips.push(shipData);
// Update the ship gallery UI
this.game.systems.baseSystem.updateShipGallery();
console.log('[ECONOMY] Ship added to BaseSystem gallery (singleplayer):', shipData.name);
}
if (debugLogger) debugLogger.logStep('Ship purchase completed', { if (debugLogger) debugLogger.logStep('Ship purchase completed', {
shipId: ship.id, shipId: ship.id,
shipName: ship.name, shipName: ship.name,
@ -472,8 +589,39 @@ class Economy {
} }
} }
// Manual sync with server data - call this to force update
syncWithServerData(serverPlayerData) {
console.log('[ECONOMY] Manual sync with server data:', {
serverCredits: serverPlayerData?.stats?.credits,
serverGems: serverPlayerData?.stats?.gems,
currentCredits: this.credits,
currentGems: this.gems
});
if (serverPlayerData?.stats?.credits !== undefined) {
this.credits = serverPlayerData.stats.credits;
console.log('[ECONOMY] Updated credits from server:', this.credits);
}
if (serverPlayerData?.stats?.gems !== undefined) {
this.gems = serverPlayerData.stats.gems;
console.log('[ECONOMY] Updated gems from server:', this.gems);
}
// Update UI after sync
this.updateUI();
}
// UI updates // UI updates
updateUI() { updateUI() {
// Debug logging to track current values
console.log('[ECONOMY] updateUI called - Current values:', {
credits: this.credits,
gems: this.gems,
gameSystemsAvailable: !!(this.game && this.game.systems),
uiSystemAvailable: !!(this.game && this.game.systems && this.game.systems.ui)
});
// Update resource display // Update resource display
if (this.game.systems.ui) { if (this.game.systems.ui) {
this.game.systems.ui.updateResourceDisplay(); this.game.systems.ui.updateResourceDisplay();
@ -487,28 +635,33 @@ class Economy {
const debugLogger = window.debugLogger; const debugLogger = window.debugLogger;
console.log('[ECONOMY] updateShopUI called'); console.log('[ECONOMY] updateShopUI called');
console.log('[ECONOMY] Multiplayer mode:', window.smartSaveManager?.isMultiplayer);
console.log('[ECONOMY] ItemSystem available:', !!(this.game.systems.itemSystem));
console.log('[ECONOMY] ItemSystem catalog:', !!(this.game.systems.itemSystem?.itemCatalog));
if (window.smartSaveManager?.isMultiplayer) { if (this.game.multiplayerMode && this.game.itemSystem && this.game.itemSystem.catalog) {
// Check if ItemSystem is ready before using it console.log('[ECONOMY] Multiplayer mode:', true);
if (!this.game.systems.itemSystem || !this.game.systems.itemSystem.itemCatalog) { console.log('[ECONOMY] ItemSystem available:', !!this.game.itemSystem);
console.log('[ECONOMY] ItemSystem not ready yet, skipping shop update'); console.log('[ECONOMY] ItemSystem catalog:', !!this.game.itemSystem.catalog);
return;
}
// Safe to use ItemSystem now const shopItems = this.game.itemSystem.catalog;
const items = Array.from(this.game.systems.itemSystem.shopItems || []); console.log('[ECONOMY] Got categorized shop items:', Object.keys(shopItems));
console.log('[ECONOMY] Rendering shop with', items.length, 'items from ItemSystem');
console.log('[ECONOMY] First few items:', items.slice(0, 3)); // Get current active category
this.renderShopItems(items); const activeCategory = this.game.itemSystem.activeCategory || 'ships';
console.log('[ECONOMY] Active shop category:', activeCategory);
// Filter items for active category
const categoryItems = shopItems[activeCategory] || [];
console.log('[ECONOMY] Using new shop structure - found', categoryItems.length, 'categories');
console.log('[ECONOMY] Filtered items for category', activeCategory, ':', categoryItems.length, 'items');
console.log('[ECONOMY] Item types in category:', categoryItems.map(item => item.type));
this.renderShopItems(categoryItems);
} else { } else {
// Singleplayer mode - use local shop data // Singleplayer mode - use local shop data
console.log('[ECONOMY] Singleplayer mode - using local shop data');
const items = Object.values(this.randomShopItems).flat(); const items = Object.values(this.randomShopItems).flat();
console.log('[ECONOMY] Rendering shop with', items.length, 'local items');
this.renderShopItems(items); // Convert to categorized structure for consistency
const categorizedItems = this.randomShopItems || {};
this.renderShopItems(categorizedItems);
} }
} }
@ -518,25 +671,27 @@ class Economy {
const activeCategory = document.querySelector('.shop-cat-btn.active')?.dataset.category || 'ships'; const activeCategory = document.querySelector('.shop-cat-btn.active')?.dataset.category || 'ships';
console.log('[ECONOMY] Active shop category:', activeCategory); console.log('[ECONOMY] Active shop category:', activeCategory);
console.log('[ECONOMY] All items types:', items.map(item => ({id: item.id, type: item.type, name: item.name})));
console.log('[ECONOMY] Unique item types:', [...new Set(items.map(item => item.type))]);
// Map category names to item types (handle plural/singular mismatches) // Handle new shop data structure (items by category) or old structure (flat array)
const categoryTypeMap = { let categoryItems = [];
'ships': 'ship',
'weapons': 'weapon',
'armors': 'armor',
'cosmetics': 'cosmetic',
'consumables': 'consumable',
'materials': 'material',
'keys': 'key'
};
const targetItemType = categoryTypeMap[activeCategory] || activeCategory; if (items && typeof items === 'object' && !Array.isArray(items)) {
console.log('[ECONOMY] Mapped category', activeCategory, 'to item type', targetItemType); // New structure: { ships: [...], weapons: [...], ... }
categoryItems = items[activeCategory] || [];
console.log('[ECONOMY] Using new shop structure - found', Object.keys(items).length, 'categories');
} else if (Array.isArray(items)) {
// Old structure: flat array of items
const targetItemType = activeCategory.slice(0, -1); // Remove 's' from 'ships', 'weapons', etc.
categoryItems = items.filter(item => item.type === targetItemType);
console.log('[ECONOMY] Using old shop structure - filtered', items.length, 'total items');
} else {
console.warn('[ECONOMY] Invalid shop items structure:', typeof items);
shopItemsElement.innerHTML = '<p>No items available</p>';
return;
}
const categoryItems = items.filter(item => item.type === targetItemType); console.log('[ECONOMY] Filtered items for category', activeCategory, ':', categoryItems.length, 'items');
console.log('[ECONOMY] Filtered items for category', activeCategory, '(type:', targetItemType, ') :', categoryItems.length, 'items'); console.log('[ECONOMY] Item types in category:', categoryItems.map(item => item.type));
if (categoryItems.length === 0) { if (categoryItems.length === 0) {
shopItemsElement.innerHTML = '<p>No items available in this category</p>'; shopItemsElement.innerHTML = '<p>No items available in this category</p>';
@ -573,6 +728,7 @@ class Economy {
<div class="shop-item-footer"> <div class="shop-item-footer">
<button class="shop-item-purchase-btn" <button class="shop-item-purchase-btn"
data-item-id="${item.id}" data-item-id="${item.id}"
onclick="purchaseShopItem('${item.id}')"
${!canAfford || isOwned ? 'disabled' : ''}> ${!canAfford || isOwned ? 'disabled' : ''}>
${isOwned ? 'Owned' : 'Purchase'} ${isOwned ? 'Owned' : 'Purchase'}
</button> </button>
@ -582,6 +738,20 @@ class Economy {
</div> </div>
`; `;
}).join(''); }).join('');
// Add event listeners to purchase buttons
shopItemsElement.querySelectorAll('.shop-item-purchase-btn').forEach(button => {
button.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
const itemId = button.getAttribute('data-item-id');
if (itemId && !button.disabled) {
console.log('[ECONOMY] Purchase button clicked for item:', itemId);
this.purchaseItem(itemId, 1);
}
});
});
} }
formatPrice(item) { formatPrice(item) {

View File

@ -184,7 +184,10 @@ class GameEngine extends EventTarget {
console.error('[GAME ENGINE] Failed to initialize DungeonSystem:', error); console.error('[GAME ENGINE] Failed to initialize DungeonSystem:', error);
}); });
} }
// REMOVED: QuestSystem should be server-driven only if (typeof QuestSystem !== 'undefined') {
this.systems.questSystem = new QuestSystem(this);
console.log('[GAME ENGINE] QuestSystem created');
}
if (typeof CraftingSystem !== 'undefined') { if (typeof CraftingSystem !== 'undefined') {
this.systems.crafting = new CraftingSystem(this); this.systems.crafting = new CraftingSystem(this);
} }
@ -426,7 +429,22 @@ class GameEngine extends EventTarget {
console.log('[GAME ENGINE] Auto-saving game...'); console.log('[GAME ENGINE] Auto-saving game...');
try { try {
await this.save(); // In multiplayer mode, save to server
if (window.smartSaveManager?.isMultiplayer) {
console.log('[GAME ENGINE] Auto-saving to server...');
if (this.socket) {
this.socket.emit('saveGameData', {
timestamp: Date.now(),
gameTime: this.gameTime
});
} else {
console.warn('[GAME ENGINE] No socket available for server save');
}
} else {
// Singleplayer mode - local save (not implemented yet)
console.log('[GAME ENGINE] Local auto-save not implemented');
}
this.showNotification('Game auto-saved', 'info', 2000); this.showNotification('Game auto-saved', 'info', 2000);
console.log('[GAME ENGINE] Auto-save completed successfully'); console.log('[GAME ENGINE] Auto-save completed successfully');
@ -644,6 +662,12 @@ class GameEngine extends EventTarget {
console.log('[GAME ENGINE] Applied gems from server:', playerData.stats.gems); console.log('[GAME ENGINE] Applied gems from server:', playerData.stats.gems);
} }
// Force manual sync to ensure economy is updated
if (this.systems.economy && this.systems.economy.syncWithServerData) {
console.log('[GAME ENGINE] Forcing manual economy sync');
this.systems.economy.syncWithServerData(playerData);
}
// Request fresh economy data from server to ensure sync // Request fresh economy data from server to ensure sync
if (this.systems.economy && this.systems.economy.requestEconomyData) { if (this.systems.economy && this.systems.economy.requestEconomyData) {
setTimeout(() => { setTimeout(() => {
@ -662,6 +686,25 @@ class GameEngine extends EventTarget {
console.log('[GAME ENGINE] Applied max energy from server:', playerData.stats.maxEnergy); console.log('[GAME ENGINE] Applied max energy from server:', playerData.stats.maxEnergy);
} }
// Ensure player has minimum energy for dungeon access
if (this.systems.player.attributes) {
// Check if energy is missing or too low
if (!this.systems.player.attributes.energy || this.systems.player.attributes.energy < 10) {
const oldEnergy = this.systems.player.attributes.energy;
this.systems.player.attributes.energy = 100;
this.systems.player.attributes.maxEnergy = Math.max(this.systems.player.attributes.maxEnergy || 0, 100);
console.log('[GAME ENGINE] Set minimum energy for dungeon access:', oldEnergy, '->', this.systems.player.attributes.energy);
}
// Also ensure currentEnergy is set if it exists
if (this.systems.player.attributes.currentEnergy !== undefined) {
if (this.systems.player.attributes.currentEnergy < 10) {
this.systems.player.attributes.currentEnergy = 100;
console.log('[GAME ENGINE] Set minimum currentEnergy for dungeon access');
}
}
}
console.log('[GAME ENGINE] Final player stats after application:', this.systems.player.stats); console.log('[GAME ENGINE] Final player stats after application:', this.systems.player.stats);
} else { } else {
console.log('[GAME ENGINE] Missing player stats or player system'); console.log('[GAME ENGINE] Missing player stats or player system');

View File

@ -3,6 +3,69 @@
* Server-driven dungeon management client * Server-driven dungeon management client
*/ */
// Create global function for dungeon start that's more reliable
window.startDungeon = function(dungeonId) {
console.log('[DUNGEON SYSTEM] startDungeon called with:', dungeonId);
console.log('[DUNGEON SYSTEM] Game available:', !!window.game);
console.log('[DUNGEON SYSTEM] Game systems available:', !!(window.game && window.game.systems));
console.log('[DUNGEON SYSTEM] Dungeon system available:', !!(window.game && window.game.systems && window.game.systems.dungeonSystem));
if (window.game && window.game.systems && window.game.systems.dungeonSystem) {
return window.game.systems.dungeonSystem.startDungeon(dungeonId);
}
console.warn('[DUNGEON SYSTEM] Game systems not available for dungeon start');
};
// Create global function for process encounter that's more reliable
window.processEncounter = function() {
console.log('[DUNGEON SYSTEM] processEncounter called');
if (window.game && window.game.systems && window.game.systems.dungeonSystem) {
return window.game.systems.dungeonSystem.processEncounter();
}
console.warn('[DUNGEON SYSTEM] Game systems not available for process encounter');
};
// Create global function for dungeon toggle that's more reliable
window.toggleDungeonSection = function(sectionId) {
// Try to use the dungeon system if available
if (window.game && window.game.systems && window.game.systems.dungeonSystem) {
return window.game.systems.dungeonSystem.toggleDungeonSection(sectionId);
}
// Fallback: Direct DOM manipulation
const section = document.getElementById(sectionId);
const indicator = document.getElementById(`${sectionId}-indicator`);
if (!section || !indicator) {
console.warn('[DUNGEON SYSTEM] Section or indicator not found:', sectionId);
return;
}
const isCollapsed = section.classList.contains('collapsed');
if (isCollapsed) {
// Expand
section.classList.remove('collapsed');
indicator.classList.remove('fa-chevron-right');
indicator.classList.add('fa-chevron-down');
} else {
// Collapse
section.classList.add('collapsed');
indicator.classList.remove('fa-chevron-down');
indicator.classList.add('fa-chevron-right');
}
// Save the state in the dungeon system if available
if (window.game && window.game.systems && window.game.systems.dungeonSystem) {
window.game.systems.dungeonSystem.collapseStates.set(sectionId, !isCollapsed);
}
console.log(`[DUNGEON SYSTEM] Toggled section ${sectionId}: ${isCollapsed ? 'expanded' : 'collapsed'}`);
};
class DungeonSystem { class DungeonSystem {
constructor(gameEngine) { constructor(gameEngine) {
this.game = gameEngine; this.game = gameEngine;
@ -13,8 +76,23 @@ class DungeonSystem {
this.dungeonProgress = 0; this.dungeonProgress = 0;
this.isExploring = false; this.isExploring = false;
// Server data loaded from server // Debouncing to prevent multiple rapid clicks
this.serverDungeons = []; this.lastProcessTime = 0;
this.processCooldown = 1000; // 1 second cooldown
// Prevent duplicate event processing
this.lastEncounterData = null;
this.lastNextRoomData = null;
// Store collapse states to preserve them during regeneration
this.collapseStates = new Map();
// Track last generation to prevent unnecessary regenerations
this.lastGenerationTime = 0;
this.generationThrottle = 500; // 500ms throttle
// Server dungeons data
this.serverDungeons = null;
this.roomTypes = {}; this.roomTypes = {};
this.enemyTemplates = {}; this.enemyTemplates = {};
@ -39,14 +117,16 @@ class DungeonSystem {
this.serverDungeons = data.dungeons || data; this.serverDungeons = data.dungeons || data;
console.log('[DUNGEON SYSTEM] Loaded grouped dungeons from server:', Object.keys(this.serverDungeons)); console.log('[DUNGEON SYSTEM] Loaded grouped dungeons from server:', Object.keys(this.serverDungeons));
// Update UI when data is loaded // Update UI when data is loaded
this.generateDungeonList(); this.forceGenerateDungeonList();
}); });
// Listen for room types response // Listen for room types response
this.game.socket.on('room_types_data', (data) => { this.game.socket.on('room_types_data', (data) => {
console.log('[DUNGEON SYSTEM] Received room types data:', data); console.log('[DUNGEON SYSTEM] Received room types data:', data);
this.roomTypes = data; this.roomTypes = data;
console.log('[DUNGEON SYSTEM] Loaded room types from server'); console.log(`[DUNGEON SYSTEM] Loaded ${Object.keys(this.roomTypes).length} room types from server`);
// Update UI when room data is loaded
this.forceGenerateDungeonList();
}); });
// Listen for enemy templates response // Listen for enemy templates response
@ -55,22 +135,157 @@ class DungeonSystem {
this.enemyTemplates = data; this.enemyTemplates = data;
console.log(`[DUNGEON SYSTEM] Loaded ${Object.keys(this.enemyTemplates).length} enemy templates from server`); console.log(`[DUNGEON SYSTEM] Loaded ${Object.keys(this.enemyTemplates).length} enemy templates from server`);
// Update UI when enemy data is loaded // Update UI when enemy data is loaded
this.generateDungeonList(); this.forceGenerateDungeonList();
}); });
// Listen for dungeon start response // Listen for dungeon start response
this.game.socket.on('dungeon_started', (data) => { this.game.socket.on('dungeon_started', (data) => {
console.log('[DUNGEON SYSTEM] Dungeon started:', data); console.log('[DUNGEON SYSTEM] Dungeon started:', data);
// Handle error responses
if (data.success === false) {
console.error('[DUNGEON SYSTEM] Failed to start dungeon:', data.error);
if (this.game && this.game.showNotification) {
this.game.showNotification(data.error, 'error', 5000);
}
return;
}
// Clear any existing dungeon state first
if (this.currentDungeon) {
console.warn('[DUNGEON SYSTEM] Clearing existing dungeon state before starting new one');
this.currentDungeon = null;
this.currentRoom = null;
this.isExploring = false;
this.dungeonProgress = 0;
}
this.currentDungeon = data.instance; this.currentDungeon = data.instance;
this.isExploring = true; this.isExploring = true;
this.dungeonProgress = 0; this.dungeonProgress = 0;
console.log('[DUNGEON SYSTEM] About to update UI - State:', {
currentDungeon: !!this.currentDungeon,
isExploring: this.isExploring,
dungeonProgress: this.dungeonProgress,
gameUIManager: !!this.game.systems.ui,
instanceId: this.currentDungeon?.id
});
// Update UI to show dungeon exploration
this.updateUI();
// Show notification to player
if (this.game && this.game.showNotification) {
this.game.showNotification(`Entered ${data.instance.dungeonId} dungeon!`, 'success', 3000);
}
}); });
// Listen for encounter response // Listen for encounter response
this.game.socket.on('encounter_data', (data) => { this.game.socket.on('encounter_data', (data) => {
// Skip duplicate events
if (this.lastEncounterData &&
this.lastEncounterData.encounterIndex === data.encounterIndex &&
this.lastEncounterData.encounter?.name === data.encounter?.name) {
console.log('[DUNGEON SYSTEM] Skipping duplicate encounter data');
return;
}
this.lastEncounterData = data;
console.log('[DUNGEON SYSTEM] Encounter received:', data); console.log('[DUNGEON SYSTEM] Encounter received:', data);
console.log('[DUNGEON SYSTEM] Current state before update:', {
currentDungeonId: this.currentDungeon?.id,
currentProgress: this.dungeonProgress,
newEncounterIndex: data.encounterIndex,
encounterType: data.encounter?.type,
encounterName: data.encounter?.name
});
this.currentRoom = data.encounter; this.currentRoom = data.encounter;
this.dungeonProgress++; this.dungeonProgress = data.encounterIndex; // Use server data, not local increment
// Update UI to show the new encounter
this.updateUI();
});
// Listen for encounter completion (auto-combat)
this.game.socket.on('encounter_completed', (data) => {
console.log('[DUNGEON SYSTEM] Encounter completed:', data);
if (data.success) {
// Check if dungeon is complete
if (data.isComplete) {
console.log('[DUNGEON SYSTEM] Dungeon completed!');
// Clear all dungeon state
this.currentDungeon = null;
this.currentRoom = null;
this.dungeonProgress = 0;
this.isExploring = false;
this.lastEncounterData = null;
this.lastNextRoomData = null;
// Show completion notification
if (this.game && this.game.showNotification) {
this.game.showNotification('Dungeon completed! 🎉', 'success', 5000);
}
// Force UI to show dungeon list
setTimeout(() => {
this.updateUI();
}, 1000);
} else {
this.currentRoom = data.nextEncounter;
this.dungeonProgress = data.encounterIndex;
// Show rewards notification
if (data.rewards && (data.rewards.credits > 0 || data.rewards.experience > 0)) {
const rewardText = [];
if (data.rewards.credits > 0) rewardText.push(`${data.rewards.credits} credits`);
if (data.rewards.experience > 0) rewardText.push(`${data.rewards.experience} exp`);
if (this.game && this.game.showNotification) {
this.game.showNotification(`Combat complete! Gained: ${rewardText.join(', ')}`, 'success', 3000);
}
}
}
// Update UI to show the new state
this.updateUI();
} else {
console.error('[DUNGEON SYSTEM] Error completing encounter:', data.error);
}
});
// Listen for next room response
this.game.socket.on('next_room_data', (data) => {
// Skip duplicate events
if (this.lastNextRoomData &&
this.lastNextRoomData.encounterIndex === data.encounterIndex &&
this.lastNextRoomData.encounter?.name === data.encounter?.name) {
console.log('[DUNGEON SYSTEM] Skipping duplicate next room data');
return;
}
this.lastNextRoomData = data;
console.log('[DUNGEON SYSTEM] Next room received:', data);
console.log('[DUNGEON SYSTEM] Current state before update:', {
currentDungeonId: this.currentDungeon?.id,
currentProgress: this.dungeonProgress,
newEncounterIndex: data.encounterIndex,
encounterType: data.encounter?.type,
encounterName: data.encounter?.name,
isComplete: data.isComplete
});
if (data.success) {
this.currentRoom = data.encounter;
this.dungeonProgress = data.encounterIndex;
// Update UI to show the new room
this.updateUI();
} else {
console.error('[DUNGEON SYSTEM] Error moving to next room:', data.error);
}
}); });
// Listen for dungeon completion response // Listen for dungeon completion response
@ -169,7 +384,7 @@ class DungeonSystem {
// Send packet to start dungeon // Send packet to start dungeon
this.game.socket.emit('start_dungeon', { this.game.socket.emit('start_dungeon', {
dungeonId: dungeonId, dungeonId: dungeonId,
userId: this.game.player?.id || 'anonymous' userId: this.game.systems.player?.id || 'anonymous'
}); });
console.log('[DUNGEON SYSTEM] Dungeon start packet sent'); console.log('[DUNGEON SYSTEM] Dungeon start packet sent');
@ -182,15 +397,24 @@ class DungeonSystem {
} }
/** /**
* Process current room encounter using Socket.IO packets * Process encounter in current dungeon room
*/ */
async processEncounter() { async processEncounter() {
if (!this.currentDungeon || !this.isExploring) { // Debounce to prevent multiple rapid clicks
console.warn('[DUNGEON SYSTEM] No active dungeon to process'); const now = Date.now();
if (now - this.lastProcessTime < this.processCooldown) {
console.log('[DUNGEON SYSTEM] Process throttled, please wait...');
return null;
}
this.lastProcessTime = now;
try {
// Safety check - make sure we have an active dungeon
if (!this.currentDungeon) {
console.error('[DUNGEON SYSTEM] No active dungeon to process encounter for');
return null; return null;
} }
try {
console.log(`[DUNGEON SYSTEM] Processing encounter for dungeon: ${this.currentDungeon.id}`); console.log(`[DUNGEON SYSTEM] Processing encounter for dungeon: ${this.currentDungeon.id}`);
if (!this.game.socket) { if (!this.game.socket) {
@ -201,7 +425,7 @@ class DungeonSystem {
// Send packet to process encounter // Send packet to process encounter
this.game.socket.emit('process_encounter', { this.game.socket.emit('process_encounter', {
instanceId: this.currentDungeon.id, instanceId: this.currentDungeon.id,
userId: this.game.player?.id || 'anonymous' userId: this.game.systems.player?.id || 'anonymous'
}); });
console.log('[DUNGEON SYSTEM] Encounter process packet sent'); console.log('[DUNGEON SYSTEM] Encounter process packet sent');
@ -233,7 +457,7 @@ class DungeonSystem {
// Send packet to complete dungeon // Send packet to complete dungeon
this.game.socket.emit('complete_dungeon', { this.game.socket.emit('complete_dungeon', {
instanceId: this.currentDungeon.id, instanceId: this.currentDungeon.id,
userId: this.game.player?.id || 'anonymous' userId: this.game.systems.player?.id || 'anonymous'
}); });
console.log('[DUNGEON SYSTEM] Dungeon completion packet sent'); console.log('[DUNGEON SYSTEM] Dungeon completion packet sent');
@ -257,7 +481,7 @@ class DungeonSystem {
// Send packet to get dungeon status // Send packet to get dungeon status
this.game.socket.emit('get_dungeon_status', { this.game.socket.emit('get_dungeon_status', {
userId: this.game.player?.id || 'anonymous' userId: this.game.systems.player?.id || 'anonymous'
}); });
console.log('[DUNGEON SYSTEM] Dungeon status request packet sent'); console.log('[DUNGEON SYSTEM] Dungeon status request packet sent');
@ -270,11 +494,27 @@ class DungeonSystem {
return null; return null;
} }
/**
* Force generate dungeon list (bypasses throttle)
*/
forceGenerateDungeonList() {
this.lastGenerationTime = 0; // Reset throttle
this.generateDungeonList();
}
/** /**
* Generate dungeon list UI using server data * Generate dungeon list UI using server data
*/ */
generateDungeonList() { generateDungeonList() {
console.log('[DUNGEON SYSTEM] Generating dungeon list UI with server data...'); const now = Date.now();
// Throttle generation to prevent excessive calls
if (now - this.lastGenerationTime < this.generationThrottle) {
return; // Silently skip instead of logging
}
this.lastGenerationTime = now;
// console.log('[DUNGEON SYSTEM] Generating dungeon list UI');
const dungeonListElement = document.getElementById('dungeonList'); const dungeonListElement = document.getElementById('dungeonList');
if (!dungeonListElement) { if (!dungeonListElement) {
@ -300,13 +540,22 @@ class DungeonSystem {
const difficultyTitle = difficulty === 'tutorial' ? 'Tutorial Dungeons' : const difficultyTitle = difficulty === 'tutorial' ? 'Tutorial Dungeons' :
difficulty.charAt(0).toUpperCase() + difficulty.slice(1) + ' Dungeons'; difficulty.charAt(0).toUpperCase() + difficulty.slice(1) + ' Dungeons';
const difficultyIcon = this.getDifficultyIcon(difficulty); const difficultyIcon = this.getDifficultyIcon(difficulty);
const sectionId = `dungeon-section-${difficulty}`;
// Add difficulty header // Add collapsible difficulty header
html += ` html += `
<h3 class="difficulty-header ${difficultyClass}"> <div class="dungeon-section">
<div class="difficulty-header ${difficultyClass} collapsible" onclick="toggleDungeonSection('${sectionId}')">
<div class="header-content">
<i class="${difficultyIcon}"></i> <i class="${difficultyIcon}"></i>
${difficultyTitle} <span>${difficultyTitle}</span>
</h3> <span class="dungeon-count">(${dungeons.length})</span>
</div>
<div class="collapse-indicator">
<i class="fas fa-chevron-down" id="${sectionId}-indicator"></i>
</div>
</div>
<div class="dungeon-content" id="${sectionId}">
`; `;
dungeons.forEach(dungeon => { dungeons.forEach(dungeon => {
@ -332,16 +581,90 @@ class DungeonSystem {
</div> </div>
</div> </div>
<button class="dungeon-btn" ${!canEnter ? 'disabled' : ''} <button class="dungeon-btn" ${!canEnter ? 'disabled' : ''}
onclick="game.systems.dungeonSystem.startDungeon('${dungeon.id}')"> onclick="startDungeon('${dungeon.id}')">
${canEnter ? 'Enter Dungeon' : 'Locked'} ${canEnter ? 'Enter Dungeon' : 'Locked'}
</button> </button>
</div> </div>
`; `;
}); });
// Close the section
html += `
</div>
</div>
`;
}); });
dungeonListElement.innerHTML = html; dungeonListElement.innerHTML = html;
console.log('[DUNGEON SYSTEM] Dungeon list UI generated successfully');
// Initialize default collapse states
this.initializeDungeonSections();
}
/**
* Initialize dungeon sections with saved collapse states
*/
initializeDungeonSections() {
// Default states: tutorial and easy expanded, others collapsed
const defaultStates = {
'dungeon-section-tutorial': false, // expanded
'dungeon-section-easy': false, // expanded
'dungeon-section-medium': true, // collapsed
'dungeon-section-hard': true, // collapsed
'dungeon-section-extreme': true // collapsed
};
Object.entries(defaultStates).forEach(([sectionId, defaultCollapsed]) => {
const section = document.getElementById(sectionId);
const indicator = document.getElementById(`${sectionId}-indicator`);
if (section && indicator) {
// Use saved state if available, otherwise use default
const shouldCollapse = this.collapseStates.has(sectionId) ?
this.collapseStates.get(sectionId) : defaultCollapsed;
if (shouldCollapse) {
section.classList.add('collapsed');
indicator.classList.remove('fa-chevron-down');
indicator.classList.add('fa-chevron-right');
}
}
});
}
/**
* Toggle dungeon section collapse/expand
*/
toggleDungeonSection(sectionId) {
// Check if game and systems are available
if (!window.game || !window.game.systems || !window.game.systems.dungeonSystem) {
console.warn('[DUNGEON SYSTEM] Game systems not available for toggle');
return;
}
const section = document.getElementById(sectionId);
const indicator = document.getElementById(`${sectionId}-indicator`);
if (!section || !indicator) return;
const isCollapsed = section.classList.contains('collapsed');
if (isCollapsed) {
// Expand
section.classList.remove('collapsed');
indicator.classList.remove('fa-chevron-right');
indicator.classList.add('fa-chevron-down');
} else {
// Collapse
section.classList.add('collapsed');
indicator.classList.remove('fa-chevron-down');
indicator.classList.add('fa-chevron-right');
}
// Save the state
this.collapseStates.set(sectionId, !isCollapsed);
console.log(`[DUNGEON SYSTEM] Toggled section ${sectionId}: ${isCollapsed ? 'expanded' : 'collapsed'}`);
} }
/** /**
@ -390,42 +713,85 @@ class DungeonSystem {
* Check if player can enter dungeon * Check if player can enter dungeon
*/ */
canEnterDungeon(dungeon) { canEnterDungeon(dungeon) {
if (!this.game.player) { if (!this.game.systems.player) {
console.log('[DUNGEON SYSTEM] No player data available');
return false; return false;
} }
const playerLevel = this.game.player.stats?.level || 1; const playerLevel = this.game.systems.player.stats?.level || 1;
const minLevel = dungeon.minLevel || 1; const minLevel = dungeon.minLevel || 1;
const maxLevel = dungeon.maxLevel || 999; const maxLevel = dungeon.maxLevel || 999;
const energyCost = dungeon.energyCost || 0; const energyCost = dungeon.energyCost || 0;
const playerEnergy = this.game.player.stats?.energy || 0; const playerEnergy = this.game.systems.player.attributes?.energy || 0;
console.log(`[DUNGEON SYSTEM] Dungeon check for ${dungeon.name}:`, {
playerLevel,
minLevel,
maxLevel,
playerEnergy,
energyCost,
canEnter: playerLevel >= minLevel && playerLevel <= maxLevel && playerEnergy >= energyCost
});
return playerLevel >= minLevel && return playerLevel >= minLevel &&
playerLevel <= maxLevel && playerLevel <= maxLevel &&
playerEnergy >= energyCost; playerEnergy >= energyCost;
} }
/**
* Exit current dungeon
*/
exitDungeon() {
console.log('[DUNGEON SYSTEM] Exiting dungeon');
if (this.currentDungeon) {
// Send exit packet to server
if (this.game.socket) {
this.game.socket.emit('exit_dungeon', {
instanceId: this.currentDungeon.id,
userId: this.game.systems.player?.id || 'anonymous'
});
}
}
// Reset local state
this.currentDungeon = null;
this.currentRoom = null;
this.isExploring = false;
this.dungeonProgress = 0;
// Update UI to show dungeon list
this.updateUI();
// Show notification
if (this.game && this.game.showNotification) {
this.game.showNotification('Exited dungeon', 'info', 2000);
}
}
/**
* Move to next room (for rooms without enemies)
*/
moveToNextRoom() {
console.log('[DUNGEON SYSTEM] Moving to next room');
if (!this.currentDungeon) {
console.warn('[DUNGEON SYSTEM] No active dungeon to continue');
return;
}
// Request next room from server
if (this.game.socket) {
this.game.socket.emit('next_room', {
instanceId: this.currentDungeon.id,
userId: this.game.systems.player?.id || 'anonymous'
});
}
}
/** /**
* Update UI with current dungeon information * Update UI with current dungeon information
*/ */
updateUI() { updateUI() {
if (this.game.uiManager) { if (this.game.systems.ui) {
this.game.uiManager.updateDungeonUI({ this.game.systems.ui.updateDungeonUI({
currentDungeon: this.currentDungeon, currentDungeon: this.currentDungeon,
currentRoom: this.currentRoom, currentRoom: this.currentRoom,
progress: this.dungeonProgress, progress: this.dungeonProgress,
isExploring: this.isExploring isExploring: this.isExploring
}); });
} else {
console.warn('[DUNGEON SYSTEM] UI manager not available in game.systems.ui');
} }
} }
@ -447,13 +813,17 @@ class DungeonSystem {
console.error('[DUNGEON SYSTEM] Socket still not available after retry'); console.error('[DUNGEON SYSTEM] Socket still not available after retry');
} }
}, 1000); }, 1000);
} else { return;
}
this.setupSocketListeners(); this.setupSocketListeners();
await this.loadServerData(); await this.loadServerData();
} }
}
console.log('[DUNGEON SYSTEM] Client dungeon system initialization complete'); // Export DungeonSystem to global scope
} if (typeof window !== 'undefined') {
window.DungeonSystem = DungeonSystem;
} }
// Export for use in GameEngine // Export for use in GameEngine

View File

@ -9,7 +9,8 @@ class ItemSystem {
// Item storage // Item storage
this.itemCatalog = new Map(); // itemId -> item data this.itemCatalog = new Map(); // itemId -> item data
this.shopItems = []; // Array of shop items this.shopItems = []; // Array of shop items (legacy)
this.shopItemsByCategory = {}; // Categorized shop items (new structure)
this.lastUpdated = null; this.lastUpdated = null;
// Loading state // Loading state
@ -56,10 +57,31 @@ class ItemSystem {
// Load shop items from server // Load shop items from server
const shopItems = await this.fetchShopItems(); const shopItems = await this.fetchShopItems();
console.log('[ITEM SYSTEM] Received', shopItems.length, 'items from server'); // Handle new shop structure (categorized) vs old structure (flat array)
let totalItems = 0;
// Process and store items if (Array.isArray(shopItems)) {
// Old structure: flat array
totalItems = shopItems.length;
console.log('[ITEM SYSTEM] Received', totalItems, 'items from server (old structure)');
this.processServerItems(shopItems); this.processServerItems(shopItems);
this.shopItemsByCategory = {}; // Clear categorized data
} else if (shopItems && typeof shopItems === 'object') {
// New structure: categorized object
totalItems = Object.values(shopItems).reduce((sum, categoryItems) => sum + categoryItems.length, 0);
console.log('[ITEM SYSTEM] Received', totalItems, 'items from server (new structure)');
console.log('[ITEM SYSTEM] Categories:', Object.keys(shopItems));
// Store categorized data
this.shopItemsByCategory = shopItems;
// Flatten all items for processing
const allItems = Object.values(shopItems).flat();
this.processServerItems(allItems);
} else {
console.warn('[ITEM SYSTEM] Invalid shop items structure received:', typeof shopItems);
totalItems = 0;
this.shopItemsByCategory = {};
}
this.lastUpdated = Date.now(); this.lastUpdated = Date.now();
console.log(`[ITEM SYSTEM] Successfully loaded ${this.itemCatalog.size} items from server`); console.log(`[ITEM SYSTEM] Successfully loaded ${this.itemCatalog.size} items from server`);
@ -113,9 +135,36 @@ class ItemSystem {
reject(new Error('Server request timeout')); reject(new Error('Server request timeout'));
}, 10000); }, 10000);
// Test server connection first
console.log('[ITEM SYSTEM] Testing server connection...');
window.game.socket.emit('ping', { timestamp: Date.now() });
// Listen for ping response
const pingHandler = (data) => {
console.log('[ITEM SYSTEM] Ping response received:', data);
console.log('[ITEM SYSTEM] Server is responding! Ping roundtrip:', Date.now() - data.received, 'ms');
window.game.socket.off('ping', pingHandler);
window.game.socket.off('pong', pingHandler);
};
window.game.socket.on('ping', pingHandler);
// Listen for pong response (backup)
const pongHandler = (data) => {
console.log('[ITEM SYSTEM] Pong response received:', data);
console.log('[ITEM SYSTEM] Server is responding! Pong roundtrip:', Date.now() - data.timestamp, 'ms');
window.game.socket.off('pong', pongHandler);
};
window.game.socket.on('pong', pongHandler);
// Request shop items from server // Request shop items from server
console.log('[ITEM SYSTEM] Emitting getShopItems request'); console.log('[ITEM SYSTEM] Emitting getShopItems request');
console.log('[ITEM SYSTEM] Socket state:', {
connected: window.game.socket.connected,
id: window.game.socket.id
});
window.game.socket.emit('getShopItems', {}); window.game.socket.emit('getShopItems', {});
console.log('[ITEM SYSTEM] Request sent, waiting for response...');
// Listen for response // Listen for response
const handleResponse = (data) => { const handleResponse = (data) => {
@ -124,13 +173,20 @@ class ItemSystem {
window.game.socket.off('shopItemsReceived', handleResponse); window.game.socket.off('shopItemsReceived', handleResponse);
console.log('[ITEM SYSTEM] Response success:', data.success); console.log('[ITEM SYSTEM] Response success:', data.success);
console.log('[ITEM SYSTEM] Response items count:', data.items?.length || 0); console.log('[ITEM SYSTEM] Response shopItems keys:', data.shopItems ? Object.keys(data.shopItems) : 'none');
if (data.success) { if (data.success) {
console.log('[ITEM SYSTEM] Successfully received', data.items?.length || 0, 'items'); console.log('[ITEM SYSTEM] Successfully received shop data');
console.log('[ITEM SYSTEM] Response timestamp:', data.timestamp); console.log('[ITEM SYSTEM] Response timestamp:', data.timestamp);
console.log('[ITEM SYSTEM] Sample item:', data.items?.[0]);
resolve(data.items || []); // Log item counts per category
if (data.shopItems) {
Object.entries(data.shopItems).forEach(([category, items]) => {
console.log(`[ITEM SYSTEM] ${category}: ${items.length} items`);
});
}
resolve(data.shopItems || {});
} else { } else {
console.error('[ITEM SYSTEM] Server returned error:', data.error); console.error('[ITEM SYSTEM] Server returned error:', data.error);
reject(new Error(data.error || 'Failed to load shop items')); reject(new Error(data.error || 'Failed to load shop items'));
@ -139,6 +195,10 @@ class ItemSystem {
console.log('[ITEM SYSTEM] Setting up shopItemsReceived listener'); console.log('[ITEM SYSTEM] Setting up shopItemsReceived listener');
window.game.socket.on('shopItemsReceived', handleResponse); window.game.socket.on('shopItemsReceived', handleResponse);
// Verify the listener was set up
const listeners = window.game.socket.listeners('shopItemsReceived');
console.log('[ITEM SYSTEM] shopItemsReceived listeners count:', listeners.length);
}); });
} }
@ -180,6 +240,13 @@ class ItemSystem {
* Process items received from server * Process items received from server
*/ */
processServerItems(items) { processServerItems(items) {
// Safety check for items parameter
if (!items || !Array.isArray(items)) {
console.error('[ITEM SYSTEM] Invalid items parameter:', items);
console.error('[ITEM SYSTEM] Expected array, got:', typeof items);
return;
}
console.log('[ITEM SYSTEM] Processing', items.length, 'items from server'); console.log('[ITEM SYSTEM] Processing', items.length, 'items from server');
console.log('[ITEM SYSTEM] Sample items:', items.slice(0, 3)); console.log('[ITEM SYSTEM] Sample items:', items.slice(0, 3));
@ -195,14 +262,14 @@ class ItemSystem {
this.shopItems.push(item); this.shopItems.push(item);
} }
console.log('[ITEM SYSTEM] Added item:', { // console.log('[ITEM SYSTEM] Added item:', {
id: item.id, // id: item.id,
name: item.name, // name: item.name,
type: item.type, // type: item.type,
rarity: item.rarity, // rarity: item.rarity,
price: item.price, // price: item.price,
categories: item.categories // categories: item.categories
}); // });
} }
console.log('[ITEM SYSTEM] Processing complete - Catalog:', this.itemCatalog.size, 'Shop items:', this.shopItems.length); console.log('[ITEM SYSTEM] Processing complete - Catalog:', this.itemCatalog.size, 'Shop items:', this.shopItems.length);
@ -238,6 +305,13 @@ class ItemSystem {
return [...this.shopItems]; return [...this.shopItems];
} }
/**
* Get shop items by category (new structure)
*/
getShopItemsByCategory() {
return this.shopItemsByCategory || {};
}
/** /**
* Get items by category * Get items by category
*/ */

View File

@ -39,682 +39,56 @@ class QuestSystem {
questStatus: Object.keys(this.questStatus) questStatus: Object.keys(this.questStatus)
}); });
// Main story quests // Main story quests - populated by server
this.mainQuests = [ 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) // Daily quests - populated by server
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.dailyQuests = [];
this.selectedDailyQuests = [];
// Currently active weekly quests (5 random from allWeeklyQuests)
this.weeklyQuests = []; this.weeklyQuests = [];
this.selectedWeeklyQuests = [];
// Current active quests
this.activeQuests = []; this.activeQuests = [];
this.completedQuests = []; this.completedQuests = [];
this.failedQuests = []; this.failedQuests = [];
this.completedDailyQuests = []; // History of completed daily quests
this.completedWeeklyQuests = []; // History of completed weekly quests // Quest tracking arrays
this.selectedDailyQuests = [];
this.selectedWeeklyQuests = [];
this.completedDailyQuests = [];
this.completedWeeklyQuests = [];
// Procedural quest templates (server-driven)
this.proceduralTemplates = {};
// Quest generation settings
this.maxProceduralQuests = 3;
this.proceduralQuestRefresh = 30 * 60 * 1000; // 30 minutes
// Initialize stats
this.stats = {
questsCompleted: 0,
dailyQuestsCompleted: 0,
weeklyQuestsCompleted: 0,
totalRewardsEarned: { credits: 0, experience: 0, gems: 0 },
lastDailyReset: this.getServerTime(),
lastWeeklyReset: this.getServerTime()
};
console.log('[QUEST SYSTEM] Client quest system initialized - waiting for server data');
if (debugLogger) debugLogger.endStep('QuestSystem.constructor', {
mainQuestsCount: this.mainQuests.length,
dailyQuestsCount: this.dailyQuests.length,
weeklyQuestsCount: this.weeklyQuests.length,
maxProceduralQuests: this.maxProceduralQuests,
proceduralQuestRefresh: this.proceduralQuestRefresh,
initialStats: this.stats,
dailyQuestsInitialized: this.dailyQuests.length,
weeklyQuestsInitialized: this.weeklyQuests.length
});
// Start countdown timers for server-driven quests
console.log('[QUEST SYSTEM] Starting countdown timers');
this.startDailyCountdown();
this.startWeeklyCountdown();
// Initialize daily quests with safety check // Initialize daily quests with safety check
try { try {

View File

@ -1712,7 +1712,9 @@ class UIManager {
if (creditsElement) { if (creditsElement) {
const oldCredits = creditsElement.textContent; const oldCredits = creditsElement.textContent;
const creditsAmount = economy.credits || 0; const creditsAmount = economy.credits || 0;
// console.log('[UI MANAGER] Credits debug - economy.credits:', economy.credits, 'creditsAmount:', creditsAmount); if (oldCredits !== this.game.formatNumber(creditsAmount)) {
console.log('[UI MANAGER] Credits updated:', this.game.formatNumber(creditsAmount));
}
creditsElement.textContent = this.game.formatNumber(creditsAmount); creditsElement.textContent = this.game.formatNumber(creditsAmount);
elementsUpdated++; elementsUpdated++;
@ -1732,7 +1734,9 @@ class UIManager {
if (gemsElement) { if (gemsElement) {
const oldGems = gemsElement.textContent; const oldGems = gemsElement.textContent;
const gemsAmount = economy.gems || 0; const gemsAmount = economy.gems || 0;
// console.log('[UI MANAGER] Gems debug - economy.gems:', economy.gems, 'gemsAmount:', gemsAmount); if (oldGems !== this.game.formatNumber(gemsAmount)) {
console.log('[UI MANAGER] Gems updated:', this.game.formatNumber(gemsAmount));
}
gemsElement.textContent = this.game.formatNumber(gemsAmount); gemsElement.textContent = this.game.formatNumber(gemsAmount);
elementsUpdated++; elementsUpdated++;
@ -1753,8 +1757,11 @@ class UIManager {
const oldEnergy = energyElement.textContent; const oldEnergy = energyElement.textContent;
const currentEnergy = player.attributes.currentEnergy || player.attributes.energy || 0; const currentEnergy = player.attributes.currentEnergy || player.attributes.energy || 0;
const maxEnergy = player.attributes.maxEnergy || 100; const maxEnergy = player.attributes.maxEnergy || 100;
// console.log('[UI MANAGER] Energy debug - player.attributes.currentEnergy:', player.attributes.currentEnergy, 'player.attributes.maxEnergy:', player.attributes.maxEnergy); const newEnergy = `${currentEnergy}/${maxEnergy}`;
energyElement.textContent = `${currentEnergy}/${maxEnergy}`; if (oldEnergy !== newEnergy) {
console.log('[UI MANAGER] Energy updated:', newEnergy);
}
energyElement.textContent = newEnergy;
elementsUpdated++; elementsUpdated++;
if (debugLogger) debugLogger.logStep('Energy updated', { if (debugLogger) debugLogger.logStep('Energy updated', {
@ -2630,6 +2637,94 @@ class UIManager {
</button> </button>
`; `;
} }
// Update dungeon UI when player enters/exits dungeons
updateDungeonUI(dungeonData) {
console.log('[UI MANAGER] Updating dungeon UI:', dungeonData);
console.log('[UI MANAGER] Dungeon state check:', {
isExploring: dungeonData.isExploring,
hasCurrentDungeon: !!dungeonData.currentDungeon,
hasCurrentRoom: !!dungeonData.currentRoom,
progress: dungeonData.progress,
dungeonViewElement: !!document.getElementById('dungeonView')
});
// Check if dungeon is completed (progress = -1)
if (dungeonData.progress === -1 || !dungeonData.isExploring) {
// Player is not in dungeon or dungeon is completed - show normal dungeon list
console.log('[UI MANAGER] Player not exploring or dungeon completed, showing dungeon list');
this.switchTab('dungeons');
return;
}
// Player is in dungeon - show exploration interface
const dungeonView = document.getElementById('dungeonView');
if (dungeonView) {
console.log('[UI MANAGER] Found dungeonView element, creating exploration UI');
const dungeon = dungeonData.currentDungeon;
const progress = dungeonData.progress || 0;
const currentRoom = dungeonData.currentRoom;
let content = `
<div class="dungeon-exploration">
<div class="dungeon-header">
<h3>${dungeon.dungeonId} Dungeon</h3>
<div class="dungeon-progress">
<span>Progress: Room ${progress + 1}</span>
</div>
<button class="btn btn-danger" onclick="if(window.game && window.game.systems && window.game.systems.dungeonSystem) window.game.systems.dungeonSystem.exitDungeon()">
Exit Dungeon
</button>
</div>
`;
if (currentRoom) {
content += `
<div class="current-room">
<h4>${currentRoom.name}</h4>
<p>${currentRoom.description}</p>
<div class="room-enemies">
<h5>Enemies:</h5>
${currentRoom.enemies && currentRoom.enemies.length > 0 ?
currentRoom.enemies.map((enemy, index) => {
console.log(`[UI MANAGER] Rendering enemy ${index}:`, enemy);
return `
<div class="enemy-info">
<strong>${enemy.name || 'Unknown Enemy'}</strong>
<div>HP: ${enemy.health || '??'} | ATK: ${enemy.attack || '??'} | DEF: ${enemy.defense || '??'}</div>
</div>
`}).join('') :
'<p>No enemies in this room</p>'
}
</div>
${currentRoom.enemies && currentRoom.enemies.length > 0 ?
`<button class="btn btn-primary" onclick="if(window.game && window.game.systems && window.game.systems.dungeonSystem) window.game.systems.dungeonSystem.processEncounter()">
Start Combat
</button>` :
`<button class="btn btn-success" onclick="if(window.game && window.game.systems && window.game.systems.dungeonSystem) window.game.systems.dungeonSystem.moveToNextRoom()">
Continue to Next Room
</button>`
}
</div>
`;
} else {
content += `
<div class="room-loading">
<p>Loading next room...</p>
<button class="btn btn-primary" onclick="if(window.game && window.game.systems && window.game.systems.dungeonSystem) window.game.systems.dungeonSystem.processEncounter()">
Start First Room
</button>
</div>
`;
}
content += '</div>';
dungeonView.innerHTML = content;
console.log('[UI MANAGER] Dungeon exploration UI applied successfully');
} else {
console.error('[UI MANAGER] dungeonView element not found!');
}
}
} }
// Export UIManager to global scope // Export UIManager to global scope

View File

@ -1533,6 +1533,134 @@ body.fullscreen .shop-container {
color: var(--text-secondary); color: var(--text-secondary);
} }
/* Collapsible Dungeon Sections */
.dungeon-section {
margin-bottom: 1rem;
border: 1px solid var(--border-color);
border-radius: 8px;
overflow: hidden;
background: var(--bg-secondary);
}
.difficulty-header.collapsible {
padding: 1rem;
cursor: pointer;
user-select: none;
display: flex;
justify-content: space-between;
align-items: center;
transition: background-color 0.3s ease;
margin: 0;
border: none;
border-radius: 0;
}
.difficulty-header.collapsible:hover {
background: var(--bg-tertiary);
}
.header-content {
display: flex;
align-items: center;
gap: 0.5rem;
}
.header-content i {
font-size: 1.2rem;
}
.header-content span {
font-weight: 600;
font-size: 1.1rem;
}
.dungeon-count {
background: rgba(255, 255, 255, 0.2);
padding: 0.2rem 0.5rem;
border-radius: 12px;
font-size: 0.8rem;
font-weight: 600;
min-width: 2rem;
text-align: center;
}
.collapse-indicator {
transition: transform 0.3s ease;
background: rgba(255, 255, 255, 0.1);
width: 2rem;
height: 2rem;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.collapse-indicator i {
font-size: 0.9rem;
color: rgba(255, 255, 255, 0.9);
}
.difficulty-header.collapsible:hover .collapse-indicator {
background: rgba(255, 255, 255, 0.2);
transform: scale(1.1);
}
.dungeon-content {
padding: 0 1rem 1rem;
max-height: 2000px;
overflow: hidden;
transition: all 0.3s ease;
opacity: 1;
}
.dungeon-content.collapsed {
max-height: 0;
padding: 0 1rem;
opacity: 0;
}
/* Difficulty-specific colors for collapsible headers */
.difficulty-header.tutorial {
background: linear-gradient(135deg, var(--info-color), #2c5aa0);
color: white;
}
.difficulty-header.easy {
background: linear-gradient(135deg, var(--success-color), #27ae60);
color: white;
}
.difficulty-header.medium {
background: linear-gradient(135deg, var(--warning-color), #f39c12);
color: white;
}
.difficulty-header.hard {
background: linear-gradient(135deg, var(--error-color), #e74c3c);
color: white;
}
.difficulty-header.extreme {
background: linear-gradient(135deg, #8e44ad, #9b59b6);
color: white;
}
/* Enhanced Dungeon Items in Collapsible Sections */
.dungeon-content .dungeon-item {
margin-bottom: 0.75rem;
border-left: 4px solid transparent;
transition: all 0.3s ease;
}
.dungeon-content .dungeon-item:hover {
border-left-color: var(--primary-color);
transform: translateX(4px);
}
.dungeon-content .dungeon-item:last-child {
margin-bottom: 0;
}
/* Shop Items */ /* Shop Items */
.shop-item { .shop-item {
background: var(--bg-tertiary); background: var(--bg-tertiary);

View File

@ -54,6 +54,9 @@ const io = socketIo(server, {
} }
}); });
// Pass io instance to systems that need it
dungeonSystem.setIO(io);
// Game state // Game state
const gameInstances = new Map(); const gameInstances = new Map();
const connectedClients = new Map(); const connectedClients = new Map();
@ -180,7 +183,7 @@ app.get('/api/shop/items', (req, res) => {
const shopItems = itemSystem.getRandomShopItems(); const shopItems = itemSystem.getRandomShopItems();
res.status(200).json({ res.status(200).json({
success: true, success: true,
items: shopItems, shopItems: shopItems,
timestamp: new Date().toISOString() timestamp: new Date().toISOString()
}); });
} catch (error) { } catch (error) {
@ -195,12 +198,12 @@ app.get('/api/shop/items', (req, res) => {
app.get('/api/shop/items/:category', (req, res) => { app.get('/api/shop/items/:category', (req, res) => {
try { try {
const { category } = req.params; const { category } = req.params;
const items = itemSystem.getItemsByType(category); const items = itemSystem.getRandomItemsByCategory(category);
res.status(200).json({ res.status(200).json({
success: true, success: true,
items: items,
category: category, category: category,
items: items,
timestamp: new Date().toISOString() timestamp: new Date().toISOString()
}); });
} catch (error) { } catch (error) {
@ -299,6 +302,7 @@ app.use('/api/base', require('./routes/base'));
// Socket.IO handlers (based on LocalServer) // Socket.IO handlers (based on LocalServer)
io.on('connection', (socket) => { io.on('connection', (socket) => {
console.log('[GAME SERVER] === NEW CLIENT CONNECTION ===');
console.log('[GAME SERVER] Client connected:', socket.id); console.log('[GAME SERVER] Client connected:', socket.id);
connectedClients.set(socket.id, { connectedClients.set(socket.id, {
connectedAt: Date.now(), connectedAt: Date.now(),
@ -329,6 +333,13 @@ io.on('connection', (socket) => {
clearTimeout(authTimeout); clearTimeout(authTimeout);
}); });
// Log all incoming events for debugging
socket.onAny((eventName, ...args) => {
if (eventName !== 'ping' && eventName !== 'pong') {
console.log(`[GAME SERVER] Event received: ${eventName} from ${socket.id}`, args);
}
});
// Authentication (similar to LocalServer) // Authentication (similar to LocalServer)
socket.on('authenticate', async (data) => { socket.on('authenticate', async (data) => {
console.log('[GAME SERVER] Authenticating client:', socket.id, data); console.log('[GAME SERVER] Authenticating client:', socket.id, data);
@ -376,6 +387,10 @@ io.on('connection', (socket) => {
if (playerData.stats.totalKills === undefined) playerData.stats.totalKills = 0; if (playerData.stats.totalKills === undefined) playerData.stats.totalKills = 0;
if (playerData.stats.questsCompleted === undefined) playerData.stats.questsCompleted = 0; if (playerData.stats.questsCompleted === undefined) playerData.stats.questsCompleted = 0;
// Ensure idle system production rates are initialized
idleSystem.initializePlayerData(playerData.userId);
console.log('[GAME SERVER] Idle system initialized for player:', playerData.username);
await savePlayerData(playerData.userId, playerData); await savePlayerData(playerData.userId, playerData);
// In production, validate with API server // In production, validate with API server
@ -459,26 +474,63 @@ io.on('connection', (socket) => {
}); });
}); });
// Test idle system manually
socket.on('testIdleRewards', (data) => {
console.log('[GAME SERVER] Testing idle rewards for:', socket.id);
const clientData = connectedClients.get(socket.id);
if (clientData && clientData.userId) {
const onlineRewards = idleSystem.generateOnlineIdleRewards(clientData.userId, 10000);
console.log('[GAME SERVER] Test idle rewards:', onlineRewards);
socket.emit('testIdleRewards', {
rewards: onlineRewards,
productionRates: idleSystem.playerProductionRates.get(clientData.userId)
});
} else {
socket.emit('testIdleRewards', { error: 'Not authenticated' });
}
});
// Shop and item system events // Shop and item system events
socket.on('ping', (data) => {
console.log('[GAME SERVER] Ping received from:', socket.id, data);
socket.emit('pong', {
timestamp: Date.now(),
received: data.timestamp,
serverTime: new Date().toISOString()
});
});
socket.on('getShopItems', (data) => { socket.on('getShopItems', (data) => {
console.log('[GAME SERVER] === getShopItems REQUEST START ===');
console.log('[GAME SERVER] getShopItems request received from:', socket.id); console.log('[GAME SERVER] getShopItems request received from:', socket.id);
console.log('[GAME SERVER] Request data:', data); console.log('[GAME SERVER] Request data:', data);
console.log('[GAME SERVER] ItemSystem available:', !!itemSystem);
console.log('[GAME SERVER] ItemSystem.getRandomShopItems available:', typeof itemSystem.getRandomShopItems);
try { try {
console.log('[GAME SERVER] Getting shop items from ItemSystem...'); console.log('[GAME SERVER] Getting shop items from ItemSystem...');
const shopItems = itemSystem.getRandomShopItems(); const shopItems = itemSystem.getRandomShopItems();
console.log('[GAME SERVER] Got shop items:', shopItems.length, 'items'); console.log('[GAME SERVER] Got shop items by category:', Object.keys(shopItems));
console.log('[GAME SERVER] Sample item:', shopItems[0]);
// Log item counts per category
Object.entries(shopItems).forEach(([category, items]) => {
console.log(`[GAME SERVER] ${category}: ${items.length} items`);
});
const response = { const response = {
success: true, success: true,
items: shopItems, shopItems: shopItems, // Changed from 'items' to 'shopItems'
timestamp: new Date().toISOString() timestamp: new Date().toISOString()
}; };
console.log('[GAME SERVER] Sending response:', response); console.log('[GAME SERVER] Sending response:', response);
console.log('[GAME SERVER] About to emit shopItemsReceived to:', socket.id);
socket.emit('shopItemsReceived', response); socket.emit('shopItemsReceived', response);
console.log('[GAME SERVER] Response sent successfully'); console.log('[GAME SERVER] shopItemsReceived emitted successfully');
console.log('[GAME SERVER] === getShopItems REQUEST COMPLETE ===');
} catch (error) { } catch (error) {
console.error('[GAME SERVER] Error sending shop items:', error); console.error('[GAME SERVER] Error sending shop items:', error);
console.error('[GAME SERVER] Error stack:', error.stack); console.error('[GAME SERVER] Error stack:', error.stack);
@ -486,6 +538,44 @@ io.on('connection', (socket) => {
success: false, success: false,
error: 'Failed to load shop items' error: 'Failed to load shop items'
}); });
console.log('[GAME SERVER] Error response sent');
}
});
socket.on('getShopCategory', (data) => {
console.log('[GAME SERVER] getShopCategory request received from:', socket.id);
console.log('[GAME SERVER] Request data:', data);
try {
const { category } = data;
if (!category) {
socket.emit('shopCategoryReceived', {
success: false,
error: 'Category required'
});
return;
}
console.log('[GAME SERVER] Getting items for category:', category);
const categoryItems = itemSystem.getRandomItemsByCategory(category);
console.log('[GAME SERVER] Got', categoryItems.length, 'items for category', category);
const response = {
success: true,
category: category,
items: categoryItems,
timestamp: new Date().toISOString()
};
console.log('[GAME SERVER] Sending category response:', response);
socket.emit('shopCategoryReceived', response);
console.log('[GAME SERVER] Category response sent successfully');
} catch (error) {
console.error('[GAME SERVER] Error sending category items:', error);
socket.emit('shopCategoryReceived', {
success: false,
error: 'Failed to load category items'
});
} }
}); });
@ -601,7 +691,7 @@ io.on('connection', (socket) => {
} }
try { try {
const playerData = await loadPlayerData(clientData.userId); const playerData = await loadPlayerData(clientData.userId, clientData.username || 'Player');
const offlineRewards = idleSystem.calculateOfflineRewards(clientData.userId); const offlineRewards = idleSystem.calculateOfflineRewards(clientData.userId);
if (offlineRewards.offlineTime > 0 && offlineRewards.rewards.credits > 0) { if (offlineRewards.offlineTime > 0 && offlineRewards.rewards.credits > 0) {
@ -665,7 +755,17 @@ io.on('connection', (socket) => {
} }
// Load current player data // Load current player data
const playerData = await loadPlayerData(clientData.userId); console.log('[GAME SERVER] Loading player data for purchase:', {
userId: clientData.userId,
username: clientData.username
});
const playerData = await loadPlayerData(clientData.userId, clientData.username || 'Player');
if (!playerData) {
console.log('[GAME SERVER] Purchase failed - could not load player data');
socket.emit('purchaseCompleted', { success: false, error: 'Failed to load player data' });
return;
}
console.log('[GAME SERVER] Player data loaded:', { console.log('[GAME SERVER] Player data loaded:', {
username: playerData.username, username: playerData.username,
credits: playerData.stats.credits, credits: playerData.stats.credits,
@ -801,7 +901,18 @@ io.on('connection', (socket) => {
// Send success response // Send success response
const response = { const response = {
success: true, success: true,
item: item, item: {
id: item.id,
name: item.name,
type: item.type,
rarity: item.rarity,
description: item.description,
price: item.price,
currency: item.currency,
quantity: quantity,
stats: item.stats || {},
texturePath: item.texturePath || item.texture
},
quantity: quantity, quantity: quantity,
totalCost: totalCost, totalCost: totalCost,
currency: currency, currency: currency,
@ -865,6 +976,20 @@ io.on('connection', (socket) => {
console.log('[GAME SERVER] Starting dungeon for:', socket.id, data); console.log('[GAME SERVER] Starting dungeon for:', socket.id, data);
try { try {
const { dungeonId, userId } = data; const { dungeonId, userId } = data;
// Check if dungeon is one-time only and already completed
const dungeon = dungeonSystem.getDungeon(dungeonId);
if (dungeon && dungeon.oneTimeOnly) {
const completedDungeons = dungeonSystem.getPlayerCompletedDungeons(userId);
if (completedDungeons.includes(dungeonId)) {
socket.emit('dungeon_started', {
success: false,
error: 'This dungeon can only be completed once per character.'
});
return;
}
}
const instance = dungeonSystem.createInstance(dungeonId, userId, []); const instance = dungeonSystem.createInstance(dungeonId, userId, []);
socket.emit('dungeon_started', { instance }); socket.emit('dungeon_started', { instance });
} catch (error) { } catch (error) {
@ -877,8 +1002,29 @@ io.on('connection', (socket) => {
console.log('[GAME SERVER] Processing encounter for:', socket.id, data); console.log('[GAME SERVER] Processing encounter for:', socket.id, data);
try { try {
const { instanceId, userId } = data; const { instanceId, userId } = data;
const encounter = dungeonSystem.startEncounter(instanceId, userId); const result = dungeonSystem.startEncounter(instanceId, userId);
socket.emit('encounter_data', { encounter });
// Auto-complete combat for enemies with 0 attack
if (result.encounter.enemies && result.encounter.enemies.length > 0) {
const allZeroAttack = result.encounter.enemies.every(enemy => enemy.attack === 0);
if (allZeroAttack) {
console.log('[GAME SERVER] Auto-combat: All enemies have 0 attack, completing encounter');
const completionResult = dungeonSystem.completeEncounter(instanceId, userId, { victory: true });
socket.emit('encounter_completed', {
success: true,
rewards: completionResult.rewards,
nextEncounter: completionResult.nextEncounter,
encounterIndex: completionResult.encounterIndex
});
return;
}
}
socket.emit('encounter_data', {
encounter: result.encounter,
encounterIndex: result.encounterIndex,
instance: result.instance
});
} catch (error) { } catch (error) {
console.error('[GAME SERVER] Error processing encounter:', error); console.error('[GAME SERVER] Error processing encounter:', error);
socket.emit('encounter_data', { success: false, error: error.message }); socket.emit('encounter_data', { success: false, error: error.message });
@ -897,6 +1043,45 @@ io.on('connection', (socket) => {
} }
}); });
socket.on('get_quests', (data) => {
console.log('[GAME SERVER] Getting quests for:', socket.id, data);
try {
// Get quest data from quest system
const questData = questSystem.getPlayerQuests(socket.userId || 'anonymous');
socket.emit('quests_data', questData);
} catch (error) {
console.error('[GAME SERVER] Error getting quests:', error);
socket.emit('quests_data', { success: false, error: error.message });
}
});
// Listen for quest completion events from other systems
io.on('quest_completed', (data) => {
console.log('[GAME SERVER] Quest completion event received:', data);
try {
const result = questSystem.completeQuest(data.userId, data.questId, data.rewards);
if (result.success) {
console.log('[GAME SERVER] Quest completed successfully on server:', data.questId);
} else {
console.error('[GAME SERVER] Failed to complete quest:', result.error);
}
} catch (error) {
console.error('[GAME SERVER] Error processing quest completion:', error);
}
});
socket.on('next_room', (data) => {
console.log('[GAME SERVER] Moving to next room for:', socket.id, data);
try {
const { instanceId, userId } = data;
const result = dungeonSystem.moveToNextRoom(instanceId, userId);
socket.emit('next_room_data', result);
} catch (error) {
console.error('[GAME SERVER] Error moving to next room:', error);
socket.emit('next_room_data', { success: false, error: error.message });
}
});
socket.on('get_dungeon_status', (data) => { socket.on('get_dungeon_status', (data) => {
console.log('[GAME SERVER] Getting dungeon status for:', socket.id, data); console.log('[GAME SERVER] Getting dungeon status for:', socket.id, data);
try { try {
@ -1224,9 +1409,12 @@ async function startServer() {
// Start online idle rewards generation (every 10 seconds) // Start online idle rewards generation (every 10 seconds)
setInterval(async () => { setInterval(async () => {
console.log('[GAME SERVER] Idle reward timer triggered - checking', connectedClients.size, 'connected clients');
for (const [socketId, clientData] of connectedClients.entries()) { for (const [socketId, clientData] of connectedClients.entries()) {
if (clientData.userId && clientData.playerData) { if (clientData.userId && clientData.playerData) {
try { try {
console.log('[GAME SERVER] Processing idle rewards for client:', socketId, 'user:', clientData.username);
// Update playTime for active players // Update playTime for active players
const sessionTime = clientData.playerData.updatePlayTime(); const sessionTime = clientData.playerData.updatePlayTime();
console.log(`[GAME SERVER] Updated playTime for ${clientData.username}: +${sessionTime}ms, Total: ${clientData.playerData.stats.playTime}ms`); console.log(`[GAME SERVER] Updated playTime for ${clientData.username}: +${sessionTime}ms, Total: ${clientData.playerData.stats.playTime}ms`);
@ -1239,11 +1427,15 @@ async function startServer() {
const onlineRewards = idleSystem.generateOnlineIdleRewards(clientData.userId, 10000); // 10 seconds const onlineRewards = idleSystem.generateOnlineIdleRewards(clientData.userId, 10000); // 10 seconds
console.log('[GAME SERVER] Generated online rewards for', clientData.username, ':', onlineRewards);
if (onlineRewards.credits > 0) { if (onlineRewards.credits > 0) {
// Update player data with online rewards // Update player data with online rewards
clientData.playerData.stats.credits = (clientData.playerData.stats.credits || 0) + onlineRewards.credits; clientData.playerData.stats.credits = (clientData.playerData.stats.credits || 0) + onlineRewards.credits;
clientData.playerData.stats.experience = (clientData.playerData.stats.experience || 0) + onlineRewards.experience; clientData.playerData.stats.experience = (clientData.playerData.stats.experience || 0) + onlineRewards.experience;
console.log('[GAME SERVER] Applied idle rewards - Credits:', onlineRewards.credits, 'New balance:', clientData.playerData.stats.credits);
// Send update to client // Send update to client
io.to(socketId).emit('onlineIdleRewards', { io.to(socketId).emit('onlineIdleRewards', {
credits: onlineRewards.credits, credits: onlineRewards.credits,
@ -1251,6 +1443,10 @@ async function startServer() {
newBalance: clientData.playerData.stats.credits, newBalance: clientData.playerData.stats.credits,
playTime: clientData.playerData.stats.playTime playTime: clientData.playerData.stats.playTime
}); });
console.log('[GAME SERVER] Sent onlineIdleRewards to client:', socketId);
} else {
console.log('[GAME SERVER] No idle rewards generated for', clientData.username);
} }
} catch (error) { } catch (error) {
console.error(`[GAME SERVER] Error generating online idle rewards for ${socketId}:`, error); console.error(`[GAME SERVER] Error generating online idle rewards for ${socketId}:`, error);

View File

@ -1024,10 +1024,121 @@ class DungeonSystem {
console.log(`[DUNGEON SYSTEM] Initialized ${this.dungeons.size} dungeons`); console.log(`[DUNGEON SYSTEM] Initialized ${this.dungeons.size} dungeons`);
} }
setIO(io) {
this.io = io;
console.log('[DUNGEON SYSTEM] Socket.IO instance set for quest events');
}
generateDungeonEncounters(dungeon) {
console.log('[DUNGEON SYSTEM] Generating encounters for dungeon:', dungeon.id);
const encounters = [];
const encounterCount = Math.max(3, Math.floor(dungeon.estimatedTime / 5)); // 1 encounter per ~5 minutes
console.log('[DUNGEON SYSTEM] Encounter count:', encounterCount, 'for difficulty:', dungeon.difficulty);
// Always start with entrance
encounters.push({
type: 'entrance',
name: 'Dungeon Entrance',
description: 'The entrance to the dungeon',
enemies: [],
rewards: false
});
// Generate random encounters based on dungeon difficulty
for (let i = 1; i < encounterCount - 1; i++) {
const encounterTypes = ['corridor', 'chamber'];
if (i === Math.floor(encounterCount / 2)) {
encounterTypes.push('treasure'); // Mid-dungeon treasure room
}
const type = encounterTypes[Math.floor(Math.random() * encounterTypes.length)];
const template = this.roomTypes[type];
encounters.push({
type,
name: template.name,
description: `A ${template.name.toLowerCase()} in the dungeon`,
enemies: this.generateEnemies(dungeon.enemyTypes, template.enemies),
rewards: template.rewards
});
}
// Always end with boss room (except tutorial)
if (dungeon.difficulty !== 'tutorial') {
encounters.push({
type: 'boss',
name: 'Boss Chamber',
description: 'The final chamber with a powerful enemy',
enemies: this.generateEnemies(dungeon.enemyTypes, 1, true),
rewards: true,
isBoss: true
});
} else {
// Tutorial ends with simple chamber (only practice targets for auto-completion)
encounters.push({
type: 'chamber',
name: 'Training Room',
description: 'The final training room',
enemies: this.generateEnemies(['practice_target'], 1), // Only practice targets
rewards: true
});
}
console.log('[DUNGEON SYSTEM] Generated encounters:', encounters.map((e, i) => ({
index: i,
type: e.type,
name: e.name,
enemyCount: e.enemies?.length || 0
})));
return encounters;
}
generateEnemies(enemyTypes, count, isBoss = false) {
console.log('[DUNGEON SYSTEM] Generating enemies:', { enemyTypes, count, isBoss });
console.log('[DUNGEON SYSTEM] Available enemy templates:', Object.keys(this.enemyTemplates));
const enemies = [];
for (let i = 0; i < count; i++) {
const enemyTypeId = enemyTypes[Math.floor(Math.random() * enemyTypes.length)];
const template = this.enemyTemplates[enemyTypeId];
console.log(`[DUNGEON SYSTEM] Generating enemy ${i}: type=${enemyTypeId}, template=`, template);
if (!template) {
console.warn(`[DUNGEON SYSTEM] Enemy template not found for type: ${enemyTypeId}`);
continue;
}
// Scale enemy for boss fights
const enemy = { ...template };
if (isBoss) {
enemy.health *= 3;
enemy.attack *= 2;
enemy.defense *= 1.5;
enemy.experience *= 5;
enemy.credits *= 3;
enemy.name = `Elite ${enemy.name}`;
}
enemies.push(enemy);
}
console.log('[DUNGEON SYSTEM] Generated enemies:', enemies);
return enemies;
}
addDungeon(id, dungeon) { addDungeon(id, dungeon) {
// Generate encounters for this dungeon
const encounters = this.generateDungeonEncounters(dungeon);
this.dungeons.set(id, { this.dungeons.set(id, {
id, id,
...dungeon, ...dungeon,
encounters,
createdAt: new Date().toISOString(), createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString() updatedAt: new Date().toISOString()
}); });
@ -1076,11 +1187,19 @@ class DungeonSystem {
} }
createInstance(dungeonId, creatorId, playerIds = []) { createInstance(dungeonId, creatorId, playerIds = []) {
console.log('[DUNGEON SYSTEM] Creating instance:', { dungeonId, creatorId, playerIds });
const dungeon = this.getDungeon(dungeonId); const dungeon = this.getDungeon(dungeonId);
if (!dungeon) { if (!dungeon) {
throw new Error('Dungeon not found'); throw new Error('Dungeon not found');
} }
console.log('[DUNGEON SYSTEM] Dungeon found:', {
id: dungeon.id,
encountersCount: dungeon.encounters?.length || 0,
encounters: dungeon.encounters?.map((e, i) => ({ index: i, type: e.type, name: e.name }))
});
// Validate requirements // Validate requirements
if (playerIds.length > dungeon.maxPlayers) { if (playerIds.length > dungeon.maxPlayers) {
throw new Error('Too many players for this dungeon'); throw new Error('Too many players for this dungeon');
@ -1235,6 +1354,7 @@ class DungeonSystem {
return { return {
success: true, success: true,
nextEncounter: instance.currentEncounter < dungeon.encounters.length ? dungeon.encounters[instance.currentEncounter] : null, nextEncounter: instance.currentEncounter < dungeon.encounters.length ? dungeon.encounters[instance.currentEncounter] : null,
encounterIndex: instance.currentEncounter,
instance instance
}; };
} }
@ -1258,16 +1378,58 @@ class DungeonSystem {
// Remove players from instance tracking // Remove players from instance tracking
for (const playerId of instance.players) { for (const playerId of instance.players) {
this.playerInstances.delete(playerId); this.playerInstances.delete(playerId);
// Check for quest completion
this.checkQuestCompletion(playerId, instance.dungeonId);
} }
return { return {
success: true, success: true,
dungeon, dungeon,
rewards, rewards,
instance instance,
encounterIndex: -1, // Dungeon complete
isComplete: true
}; };
} }
// Check if dungeon completion should trigger quests
checkQuestCompletion(userId, dungeonId) {
// Tutorial dungeon completion should trigger main story progression
if (dungeonId === 'tutorial') {
console.log('[DUNGEON SYSTEM] Tutorial dungeon completed, triggering quest progression');
// Set player stat for tutorial completion
if (this.io) {
this.io.emit('player_stat_update', {
userId: userId,
stat: 'tutorialDungeonCompleted',
value: true
});
// Emit quest completion event for quest system
this.io.emit('quest_completed', {
userId: userId,
questId: 'main_story_first_dungeon',
questType: 'dungeon',
dungeonId: dungeonId,
rewards: {
experience: 50,
credits: 25
}
});
}
}
}
getPlayerCompletedDungeons(userId) {
const instances = Array.from(this.instances.values()).filter(instance =>
instance.players.has(userId) && instance.status === 'completed'
);
return instances.map(instance => instance.dungeonId);
}
calculateRewards(dungeon, instance) { calculateRewards(dungeon, instance) {
const rewards = { const rewards = {
experience: 0, experience: 0,
@ -1275,10 +1437,21 @@ class DungeonSystem {
items: [] items: []
}; };
// Check if dungeon has rewards defined
if (!dungeon.rewards) {
console.warn('[DUNGEON SYSTEM] No rewards defined for dungeon:', dungeon.id);
return rewards;
}
// Calculate base rewards // Calculate base rewards
const expRange = dungeon.rewards.experience; const expRange = dungeon.rewards.experience;
const creditRange = dungeon.rewards.credits; const creditRange = dungeon.rewards.credits;
if (!expRange || !creditRange) {
console.warn('[DUNGEON SYSTEM] Incomplete rewards defined for dungeon:', dungeon.id);
return rewards;
}
rewards.experience = Math.floor(Math.random() * (expRange.max - expRange.min + 1)) + expRange.min; rewards.experience = Math.floor(Math.random() * (expRange.max - expRange.min + 1)) + expRange.min;
rewards.credits = Math.floor(Math.random() * (creditRange.max - creditRange.min + 1)) + creditRange.min; rewards.credits = Math.floor(Math.random() * (creditRange.max - creditRange.min + 1)) + creditRange.min;
@ -1292,7 +1465,7 @@ class DungeonSystem {
const itemChance = Math.max(0.1, 0.5 - (totalDeaths * 0.1)); // Lower chance with more deaths const itemChance = Math.max(0.1, 0.5 - (totalDeaths * 0.1)); // Lower chance with more deaths
if (Math.random() < itemChance) { if (Math.random() < itemChance) {
const itemPool = dungeon.rewards.items; const itemPool = dungeon.rewards.items;
if (itemPool.length > 0) { if (itemPool && itemPool.length > 0) {
rewards.items.push(itemPool[Math.floor(Math.random() * itemPool.length)]); rewards.items.push(itemPool[Math.floor(Math.random() * itemPool.length)]);
} }
} }
@ -1308,6 +1481,58 @@ class DungeonSystem {
); );
} }
moveToNextRoom(instanceId, userId) {
console.log('[DUNGEON SYSTEM] moveToNextRoom called:', { instanceId, userId });
const instance = this.getInstance(instanceId);
if (!instance) {
throw new Error('Instance not found');
}
if (!instance.players.has(userId)) {
throw new Error('Player not in instance');
}
const dungeon = this.getDungeon(instance.dungeonId);
console.log('[DUNGEON SYSTEM] Current state:', {
currentEncounter: instance.currentEncounter,
totalEncounters: dungeon.encounters.length,
dungeonId: dungeon.id
});
// Move to next encounter
instance.currentEncounter++;
console.log('[DUNGEON SYSTEM] After increment:', {
newEncounter: instance.currentEncounter,
isComplete: instance.currentEncounter >= dungeon.encounters.length
});
// Check if dungeon is complete
if (instance.currentEncounter >= dungeon.encounters.length) {
return this.completeDungeon(instanceId);
}
// Get next encounter
const encounter = dungeon.encounters[instance.currentEncounter];
console.log('[DUNGEON SYSTEM] Next encounter:', {
encounterIndex: instance.currentEncounter,
encounterType: encounter.type,
encounterName: encounter.name,
enemies: encounter.enemies
});
return {
success: true,
encounter,
encounterIndex: instance.currentEncounter,
instance,
isComplete: false
};
}
getDungeonStatistics(userId) { getDungeonStatistics(userId) {
const instances = Array.from(this.instances.values()).filter(instance => const instances = Array.from(this.instances.values()).filter(instance =>
instance.players.has(userId) instance.players.has(userId)

View File

@ -50,15 +50,25 @@ class IdleSystem {
} }
initializePlayerData(userId) { initializePlayerData(userId) {
console.log('[IDLE SYSTEM] initializePlayerData called for userId:', userId);
console.log('[IDLE SYSTEM] Current playerProductionRates size:', this.playerProductionRates.size);
if (!this.playerLastActive.has(userId)) { if (!this.playerLastActive.has(userId)) {
console.log('[IDLE SYSTEM] Initializing new player data for:', userId);
this.playerLastActive.set(userId, Date.now()); this.playerLastActive.set(userId, Date.now());
this.playerProductionRates.set(userId, { ...this.defaultProductionRates }); this.playerProductionRates.set(userId, { ...this.defaultProductionRates });
console.log('[IDLE SYSTEM] Set production rates for', userId, ':', this.defaultProductionRates);
this.playerAchievements.set(userId, { this.playerAchievements.set(userId, {
totalOfflineTime: 0, totalOfflineTime: 0,
maxOfflineSession: 0, maxOfflineSession: 0,
totalIdleCredits: 0, totalIdleCredits: 0,
totalIdleExperience: 0 totalIdleExperience: 0
}); });
console.log('[IDLE SYSTEM] Player data initialized successfully for:', userId);
} else {
console.log('[IDLE SYSTEM] Player data already exists for:', userId);
const existingRates = this.playerProductionRates.get(userId);
console.log('[IDLE SYSTEM] Existing production rates:', existingRates);
} }
} }
@ -250,8 +260,13 @@ class IdleSystem {
// Generate online idle rewards (called periodically while player is online) // Generate online idle rewards (called periodically while player is online)
generateOnlineIdleRewards(userId, deltaTimeMs) { generateOnlineIdleRewards(userId, deltaTimeMs) {
console.log('[IDLE SYSTEM] generateOnlineIdleRewards called for userId:', userId, 'deltaTimeMs:', deltaTimeMs);
const productionRates = this.playerProductionRates.get(userId); const productionRates = this.playerProductionRates.get(userId);
console.log('[IDLE SYSTEM] Production rates for', userId, ':', productionRates);
if (!productionRates) { if (!productionRates) {
console.log('[IDLE SYSTEM] No production rates found for', userId, '- returning 0 rewards');
return { return {
credits: 0, credits: 0,
experience: 0, experience: 0,
@ -260,12 +275,18 @@ class IdleSystem {
} }
const deltaTimeSeconds = deltaTimeMs / 1000; const deltaTimeSeconds = deltaTimeMs / 1000;
console.log('[IDLE SYSTEM] Delta time seconds:', deltaTimeSeconds);
console.log('[IDLE SYSTEM] Raw calculation:', productionRates.credits, '*', deltaTimeSeconds, '=', productionRates.credits * deltaTimeSeconds);
const creditsBeforeFloor = productionRates.credits * deltaTimeSeconds;
const rewards = { const rewards = {
credits: Math.floor(productionRates.credits * deltaTimeSeconds), credits: Math.floor(creditsBeforeFloor),
experience: Math.floor(productionRates.experience * deltaTimeSeconds), experience: Math.floor(productionRates.experience * deltaTimeSeconds),
energy: Math.floor(productionRates.energy * deltaTimeSeconds) energy: Math.floor(productionRates.energy * deltaTimeSeconds)
}; };
console.log('[IDLE SYSTEM] Credits before floor:', creditsBeforeFloor, 'after floor:', rewards.credits);
console.log('[IDLE SYSTEM] Calculated rewards for', userId, ':', rewards);
return rewards; return rewards;
} }

View File

@ -81,6 +81,20 @@ class ItemSystem {
categories: ['shop', 'dungeon_reward'], categories: ['shop', 'dungeon_reward'],
requirements: { level: 7 }, requirements: { level: 7 },
stackable: false stackable: false
},
{
id: 'heavy_cruiser_rare',
name: 'Heavy Cruiser',
type: 'ship',
rarity: 'rare',
price: 35000,
currency: 'credits',
description: 'Powerful heavy cruiser with superior firepower and armor',
texturePath: 'images/ships/heavy_cruiser_rare.png',
stats: { attack: 28, speed: 8, defense: 25, hull: 200 },
categories: ['shop', 'dungeon_reward'],
requirements: { level: 12 },
stackable: false
} }
], ],
@ -145,6 +159,81 @@ class ItemSystem {
stackable: true, stackable: true,
maxStack: 10, maxStack: 10,
effects: { attack: 20, defense: 20 } effects: { attack: 20, defense: 20 }
},
{
id: 'titanium_alloy',
name: 'Titanium Alloy',
type: 'material',
rarity: 'uncommon',
price: 300,
currency: 'credits',
description: 'Lightweight yet durable metal for ship construction',
texture: 'http://localhost:3002/images/items/materials/titanium_alloy.png',
categories: ['shop', 'dungeon_loot', 'crafting'],
requirements: { level: 8 },
stackable: true,
maxStack: 75,
effects: { defense: 8, speed: 3 }
},
{
id: 'plasma_coil',
name: 'Plasma Coil',
type: 'material',
rarity: 'rare',
price: 750,
currency: 'credits',
description: 'High-energy component for weapon systems',
texture: 'http://localhost:3002/images/items/materials/plasma_coil.png',
categories: ['shop', 'dungeon_loot', 'crafting'],
requirements: { level: 12 },
stackable: true,
maxStack: 30,
effects: { attack: 12, energy: 8 }
},
{
id: 'nanite_gel',
name: 'Nanite Gel',
type: 'material',
rarity: 'epic',
price: 1200,
currency: 'gems',
description: 'Self-repairing nanomachines in gel form',
texture: 'http://localhost:3002/images/items/materials/nanite_gel.png',
categories: ['shop', 'dungeon_loot', 'crafting'],
requirements: { level: 18 },
stackable: true,
maxStack: 15,
effects: { hull_repair: 25, defense: 5 }
},
{
id: 'cryo_crystal',
name: 'Cryo Crystal',
type: 'material',
rarity: 'rare',
price: 600,
currency: 'credits',
description: 'Frozen crystal that enhances cooling systems',
texture: 'http://localhost:3002/images/items/materials/cryo_crystal.png',
categories: ['shop', 'dungeon_loot', 'crafting'],
requirements: { level: 10 },
stackable: true,
maxStack: 40,
effects: { energy: 15, speed: 5 }
},
{
id: 'void_shard',
name: 'Void Shard',
type: 'material',
rarity: 'legendary',
price: 2500,
currency: 'gems',
description: 'Fragment of void space with reality-bending properties',
texture: 'http://localhost:3002/images/items/materials/void_shard.png',
categories: ['shop', 'dungeon_loot', 'crafting'],
requirements: { level: 20 },
stackable: true,
maxStack: 8,
effects: { attack: 15, defense: 15, speed: 10 }
} }
], ],
@ -217,6 +306,91 @@ class ItemSystem {
effects: { damage_multiplier: 1.5, duration: 45000 }, // 45 seconds effects: { damage_multiplier: 1.5, duration: 45000 }, // 45 seconds
consumable: true, consumable: true,
cooldown: 120000 // 2 minutes cooldown: 120000 // 2 minutes
},
{
id: 'speed_boost',
name: 'Speed Boost',
type: 'consumable',
rarity: 'uncommon',
price: 200,
currency: 'credits',
description: 'Temporary speed increase for quick escapes',
texture: 'http://localhost:3002/images/items/consumables/speed_boost.png',
categories: ['shop', 'dungeon_loot'],
requirements: { level: 6 },
stackable: true,
maxStack: 8,
effects: { speed_multiplier: 1.3, duration: 30000 }, // 30 seconds
consumable: true,
cooldown: 90000 // 1.5 minutes
},
{
id: 'nanite_repair',
name: 'Nanite Repair',
type: 'consumable',
rarity: 'epic',
price: 500,
currency: 'credits',
description: 'Advanced nanites that continuously repair hull damage',
texture: 'http://localhost:3002/images/items/consumables/nanite_repair.png',
categories: ['shop', 'dungeon_loot'],
requirements: { level: 12 },
stackable: true,
maxStack: 3,
effects: { hull_repair_rate: 10, duration: 60000 }, // 10 HP/sec for 1 minute
consumable: true,
cooldown: 180000 // 3 minutes
},
{
id: 'stealth_field',
name: 'Stealth Field Generator',
type: 'consumable',
rarity: 'rare',
price: 400,
currency: 'credits',
description: 'Temporary invisibility to enemy sensors',
texture: 'http://localhost:3002/images/items/consumables/stealth_field.png',
categories: ['shop', 'dungeon_loot'],
requirements: { level: 10 },
stackable: true,
maxStack: 4,
effects: { stealth: true, duration: 20000 }, // 20 seconds
consumable: true,
cooldown: 150000 // 2.5 minutes
},
{
id: 'emergency_warp',
name: 'Emergency Warp Core',
type: 'consumable',
rarity: 'legendary',
price: 1000,
currency: 'gems',
description: 'Instant emergency teleport to safety',
texture: 'http://localhost:3002/images/items/consumables/emergency_warp.png',
categories: ['shop', 'dungeon_loot'],
requirements: { level: 15 },
stackable: true,
maxStack: 2,
effects: { emergency_escape: true },
consumable: true,
cooldown: 300000 // 5 minutes
},
{
id: 'combat_stim',
name: 'Combat Stimulant',
type: 'consumable',
rarity: 'uncommon',
price: 250,
currency: 'credits',
description: 'Enhances combat abilities for short duration',
texture: 'http://localhost:3002/images/items/consumables/combat_stim.png',
categories: ['shop', 'dungeon_loot'],
requirements: { level: 7 },
stackable: true,
maxStack: 6,
effects: { attack: 10, speed: 5, duration: 45000 }, // 45 seconds
consumable: true,
cooldown: 120000 // 2 minutes
} }
], ],
@ -281,6 +455,81 @@ class ItemSystem {
stackable: false, stackable: false,
cosmetic: true, cosmetic: true,
slot: 'engine' slot: 'engine'
},
{
id: 'skull_decal',
name: 'Skull Decal',
type: 'cosmetic',
rarity: 'uncommon',
price: 1500,
currency: 'credits',
description: 'Intimidating skull decal for your ship',
texture: 'http://localhost:3002/images/items/cosmetics/skull_decal.png',
categories: ['shop'],
requirements: { level: 3 },
stackable: false,
cosmetic: true,
slot: 'decal'
},
{
id: 'neon_lights',
name: 'Neon Underglow',
type: 'cosmetic',
rarity: 'rare',
price: 3000,
currency: 'credits',
description: 'Colorful neon lights under your ship',
texture: 'http://localhost:3002/images/items/cosmetics/neon_lights.png',
categories: ['shop'],
requirements: { level: 8 },
stackable: false,
cosmetic: true,
slot: 'lights'
},
{
id: 'chrome_finish',
name: 'Chrome Finish',
type: 'cosmetic',
rarity: 'epic',
price: 5000,
currency: 'credits',
description: 'Reflective chrome coating for premium look',
texture: 'http://localhost:3002/images/items/cosmetics/chrome_finish.png',
categories: ['shop'],
requirements: { level: 12 },
stackable: false,
cosmetic: true,
slot: 'finish'
},
{
id: 'holographic_display',
name: 'Holographic Display',
type: 'cosmetic',
rarity: 'legendary',
price: 8000,
currency: 'gems',
description: 'Advanced holographic projection system',
texture: 'http://localhost:3002/images/items/cosmetics/holographic_display.png',
categories: ['shop'],
requirements: { level: 15 },
stackable: false,
cosmetic: true,
slot: 'display'
},
{
id: 'carbon_fiber',
name: 'Carbon Fiber Body',
type: 'cosmetic',
rarity: 'uncommon',
price: 2000,
currency: 'credits',
description: 'Lightweight carbon fiber ship body',
texture: 'http://localhost:3002/images/items/cosmetics/carbon_fiber.png',
categories: ['shop'],
requirements: { level: 6 },
stackable: false,
cosmetic: true,
slot: 'body'
} }
], ],
@ -327,6 +576,76 @@ class ItemSystem {
requirements: { level: 10 }, requirements: { level: 10 },
stackable: false, stackable: false,
stats: { attack: 30, range: 150, fire_rate: 1 } stats: { attack: 30, range: 150, fire_rate: 1 }
},
{
id: 'ion_cannon_uncommon',
name: 'Ion Cannon',
type: 'weapon',
rarity: 'uncommon',
price: 3500,
currency: 'credits',
description: 'Ion-based weapon effective against shields',
texture: 'http://localhost:3002/images/weapons/ion_cannon_uncommon.png',
categories: ['shop', 'dungeon_loot'],
requirements: { level: 6 },
stackable: false,
stats: { attack: 22, range: 110, fire_rate: 1.8, shield_damage: 1.5 }
},
{
id: 'missile_launcher_rare',
name: 'Missile Launcher',
type: 'weapon',
rarity: 'rare',
price: 8000,
currency: 'credits',
description: 'Homing missile system for heavy damage',
texture: 'http://localhost:3002/images/weapons/missile_launcher_rare.png',
categories: ['shop', 'dungeon_loot'],
requirements: { level: 12 },
stackable: false,
stats: { attack: 45, range: 200, fire_rate: 0.8, explosive: true }
},
{
id: 'plasma_thrower_epic',
name: 'Plasma Thrower',
type: 'weapon',
rarity: 'epic',
price: 12000,
currency: 'credits',
description: 'Continuous plasma stream weapon',
texture: 'http://localhost:3002/images/weapons/plasma_thrower_epic.png',
categories: ['shop', 'dungeon_loot'],
requirements: { level: 15 },
stackable: false,
stats: { attack: 35, range: 80, fire_rate: 5, continuous: true }
},
{
id: 'railgun_legendary',
name: 'Railgun',
type: 'weapon',
rarity: 'legendary',
price: 20000,
currency: 'gems',
description: 'Electromagnetic railgun with piercing shots',
texture: 'http://localhost:3002/images/weapons/railgun_legendary.png',
categories: ['shop', 'dungeon_loot'],
requirements: { level: 18 },
stackable: false,
stats: { attack: 60, range: 300, fire_rate: 0.5, piercing: true }
},
{
id: 'pulse_cannon_common',
name: 'Pulse Cannon',
type: 'weapon',
rarity: 'common',
price: 1500,
currency: 'credits',
description: 'Rapid-fire pulse weapon',
texture: 'http://localhost:3002/images/weapons/pulse_cannon_common.png',
categories: ['shop', 'dungeon_loot'],
requirements: { level: 2 },
stackable: false,
stats: { attack: 8, range: 90, fire_rate: 4 }
} }
], ],
@ -373,6 +692,76 @@ class ItemSystem {
requirements: { level: 10 }, requirements: { level: 10 },
stackable: false, stackable: false,
stats: { defense: 25, shield_capacity: 200, recharge_rate: 12 } stats: { defense: 25, shield_capacity: 200, recharge_rate: 12 }
},
{
id: 'plasma_barrier_uncommon',
name: 'Plasma Barrier',
type: 'armor',
rarity: 'uncommon',
price: 3000,
currency: 'credits',
description: 'Plasma-based barrier system',
texture: 'http://localhost:3002/images/armors/plasma_barrier_uncommon.png',
categories: ['shop', 'dungeon_loot'],
requirements: { level: 7 },
stackable: false,
stats: { defense: 18, shield_capacity: 120, recharge_rate: 10, energy_resistance: 0.8 }
},
{
id: 'reactive_armor_epic',
name: 'Reactive Armor',
type: 'armor',
rarity: 'epic',
price: 9000,
currency: 'credits',
description: 'Adaptive armor that responds to damage types',
texture: 'http://localhost:3002/images/armors/reactive_armor_epic.png',
categories: ['shop', 'dungeon_loot'],
requirements: { level: 14 },
stackable: false,
stats: { defense: 35, shield_capacity: 250, recharge_rate: 15, adaptive: true }
},
{
id: 'kinetic_dampener_rare',
name: 'Kinetic Dampener',
type: 'armor',
rarity: 'rare',
price: 6000,
currency: 'credits',
description: 'Specialized armor against kinetic weapons',
texture: 'http://localhost:3002/images/armors/kinetic_dampener_rare.png',
categories: ['shop', 'dungeon_loot'],
requirements: { level: 11 },
stackable: false,
stats: { defense: 22, shield_capacity: 180, recharge_rate: 11, kinetic_resistance: 0.7 }
},
{
id: 'phase_shifter_legendary',
name: 'Phase Shifter',
type: 'armor',
rarity: 'legendary',
price: 15000,
currency: 'gems',
description: 'Experimental phase-shifting armor system',
texture: 'http://localhost:3002/images/armors/phase_shifter_legendary.png',
categories: ['shop', 'dungeon_loot'],
requirements: { level: 18 },
stackable: false,
stats: { defense: 40, shield_capacity: 300, recharge_rate: 20, phase_shift: 0.2 }
},
{
id: 'carbon_plate_common',
name: 'Carbon Plate Armor',
type: 'armor',
rarity: 'common',
price: 1200,
currency: 'credits',
description: 'Lightweight carbon composite armor',
texture: 'http://localhost:3002/images/armors/carbon_plate_common.png',
categories: ['shop', 'dungeon_loot'],
requirements: { level: 3 },
stackable: false,
stats: { defense: 12, shield_capacity: 70, recharge_rate: 6, weight_reduction: 0.1 }
} }
], ],
@ -689,18 +1078,45 @@ class ItemSystem {
} }
/** /**
* Get random shop items with processed URLs * Get random shop items with processed URLs - 6 items per category
*/ */
getRandomShopItems(count = 8) { getRandomShopItems(count = 6) {
const allItems = Object.values(this.itemCatalog).flat(); const shopItems = {};
// Get 6 random items from each category
Object.keys(this.itemCatalog).forEach(category => {
const categoryItems = this.itemCatalog[category] || [];
const selectedItems = []; const selectedItems = [];
// Randomly select items if (categoryItems.length > 0) {
const shuffled = [...allItems].sort(() => Math.random() - 0.5); // Randomly select items from this category
const shuffled = [...categoryItems].sort(() => Math.random() - 0.5);
for (let i = 0; i < Math.min(count, shuffled.length); i++) { for (let i = 0; i < Math.min(count, shuffled.length); i++) {
selectedItems.push(this.processItem(shuffled[i])); selectedItems.push(this.processItem(shuffled[i]));
} }
}
shopItems[category] = selectedItems;
});
return shopItems;
}
/**
* Get random items for a specific category
*/
getRandomItemsByCategory(category, count = 6) {
const categoryItems = this.itemCatalog[category] || [];
const selectedItems = [];
if (categoryItems.length > 0) {
const shuffled = [...categoryItems].sort(() => Math.random() - 0.5);
for (let i = 0; i < Math.min(count, shuffled.length); i++) {
selectedItems.push(this.processItem(shuffled[i]));
}
}
return selectedItems; return selectedItems;
} }

View File

@ -660,6 +660,149 @@ class QuestSystem {
return { success: true, message: 'Daily quests reset' }; return { success: true, message: 'Daily quests reset' };
} }
getPlayerQuests(userId) {
console.log('[QUEST SYSTEM] Getting quests for user:', userId);
// Get or create player data
let playerData = this.playerQuests.get(userId);
if (!playerData) {
playerData = {
activeQuests: new Map(),
completedQuests: new Map(),
failedQuests: new Map()
};
this.playerQuests.set(userId, playerData);
}
// Prepare quest data for client
const questData = {
mainQuests: [],
dailyQuests: [],
weeklyQuests: [],
activeQuests: [],
completedQuests: [],
failedQuests: []
};
// Add main quests
for (const [questId, quest] of this.quests) {
if (quest.type === 'main') {
const playerQuest = playerData.activeQuests.get(questId) || playerData.completedQuests.get(questId) || playerData.failedQuests.get(questId);
const status = playerQuest ? playerQuest.status : 'available';
questData.mainQuests.push({
...quest,
status: status,
objectives: quest.objectives.map(obj => ({
...obj,
current: playerQuest && playerQuest.objectives && playerQuest.objectives[obj.id] ? playerQuest.objectives[obj.id].current || 0 : 0
}))
});
}
}
// Add daily quests
for (const [questId, quest] of this.quests) {
if (quest.type === 'daily') {
const playerQuest = playerData.activeQuests.get(questId) || playerData.completedQuests.get(questId) || playerData.failedQuests.get(questId);
const status = playerQuest ? playerQuest.status : 'available';
questData.dailyQuests.push({
...quest,
status: status,
objectives: quest.objectives.map(obj => ({
...obj,
current: playerQuest && playerQuest.objectives && playerQuest.objectives[obj.id] ? playerQuest.objectives[obj.id].current || 0 : 0
}))
});
}
}
// Add weekly quests
for (const [questId, quest] of this.quests) {
if (quest.type === 'weekly') {
const playerQuest = playerData.activeQuests.get(questId) || playerData.completedQuests.get(questId) || playerData.failedQuests.get(questId);
const status = playerQuest ? playerQuest.status : 'available';
questData.weeklyQuests.push({
...quest,
status: status,
objectives: quest.objectives.map(obj => ({
...obj,
current: playerQuest && playerQuest.objectives && playerQuest.objectives[obj.id] ? playerQuest.objectives[obj.id].current || 0 : 0
}))
});
}
}
// Add active quests (for compatibility)
questData.activeQuests = Array.from(playerData.activeQuests.values());
questData.completedQuests = Array.from(playerData.completedQuests.values());
questData.failedQuests = Array.from(playerData.failedQuests.values());
console.log('[QUEST SYSTEM] Returning quest data:', {
mainQuests: questData.mainQuests.length,
dailyQuests: questData.dailyQuests.length,
weeklyQuests: questData.weeklyQuests.length,
activeQuests: questData.activeQuests.length,
completedQuests: questData.completedQuests.length
});
return questData;
}
completeQuest(userId, questId, rewards = null) {
console.log('[QUEST SYSTEM] Completing quest:', questId, 'for user:', userId);
const playerData = this.playerQuests.get(userId);
if (!playerData) {
console.log('[QUEST SYSTEM] Player data not found for user:', userId);
return { success: false, error: 'Player not found' };
}
const quest = this.quests.get(questId);
if (!quest) {
console.log('[QUEST SYSTEM] Quest not found:', questId);
return { success: false, error: 'Quest not found' };
}
// Move quest from active to completed
const activeQuest = playerData.activeQuests.get(questId);
if (activeQuest) {
// Mark all objectives as completed
const completedQuest = {
...activeQuest,
status: 'completed',
completedAt: Date.now(),
objectives: quest.objectives.map(obj => ({
...obj,
current: obj.target
}))
};
playerData.activeQuests.delete(questId);
playerData.completedQuests.set(questId, completedQuest);
console.log('[QUEST SYSTEM] Quest completed successfully:', questId);
return { success: true, quest: completedQuest };
} else {
// Quest might not be active, try to complete it anyway
const completedQuest = {
...quest,
status: 'completed',
completedAt: Date.now(),
objectives: quest.objectives.map(obj => ({
...obj,
current: obj.target
}))
};
playerData.completedQuests.set(questId, completedQuest);
console.log('[QUEST SYSTEM] Quest force-completed:', questId);
return { success: true, quest: completedQuest };
}
}
resetWeeklyQuests(userId) { resetWeeklyQuests(userId) {
const playerData = this.getPlayerData(userId); const playerData = this.getPlayerData(userId);