worked on dungeons + started work on quests
This commit is contained in:
parent
6db6e1ebdb
commit
485b5c3eb8
@ -155,13 +155,37 @@ class GameInitializer {
|
||||
});
|
||||
|
||||
this.socket.on('onlineIdleRewards', (data) => {
|
||||
if (data.credits > 0 || data.experience > 0) {
|
||||
console.log('[GAME INITIALIZER] Online idle rewards received:', 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
|
||||
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);
|
||||
});
|
||||
|
||||
@ -342,6 +366,12 @@ class GameInitializer {
|
||||
window.game.loadServerPlayerData(this.serverPlayerData);
|
||||
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
|
||||
if (window.game.systems && window.game.systems.ui && window.game.systems.ui.forceRefreshAllUI) {
|
||||
console.log('[GAME INITIALIZER] Forcing UI refresh after data application');
|
||||
@ -730,65 +760,65 @@ class GameInitializer {
|
||||
username: this.currentUser.username
|
||||
});
|
||||
} else {
|
||||
console.warn('[GAME INITIALIZER] Cannot authenticate - missing socket or user data');
|
||||
if (!this.socket) {
|
||||
console.warn('[GAME INITIALIZER] Socket is null/undefined');
|
||||
}
|
||||
if (!this.currentUser) {
|
||||
console.warn('[GAME INITIALIZER] Current user is null/undefined');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
// Try to get from localStorage as fallback
|
||||
const storedUser = localStorage.getItem('currentUser');
|
||||
if (storedUser) {
|
||||
try {
|
||||
const user = JSON.parse(storedUser);
|
||||
const username = user.username || 'anonymous';
|
||||
} catch (e) {
|
||||
console.warn('[GAME INITIALIZER] Failed to parse stored user, using default');
|
||||
}
|
||||
} else {
|
||||
window.game.showNotification('No offline rewards available', 'info', 3000);
|
||||
}
|
||||
} else {
|
||||
window.game.showNotification(`Failed to claim offline rewards: ${data.error}`, 'error', 5000);
|
||||
}
|
||||
}
|
||||
|
||||
onOnlineIdleRewards(data) {
|
||||
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
|
||||
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);
|
||||
}
|
||||
|
||||
// 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) {
|
||||
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) {
|
||||
const player = window.game.systems.player;
|
||||
|
||||
// Update playTime from server
|
||||
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
|
||||
player.updateUI();
|
||||
}
|
||||
@ -807,6 +837,72 @@ class GameInitializer {
|
||||
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
|
||||
if (economy.requestEconomyData) {
|
||||
setTimeout(() => {
|
||||
@ -814,9 +910,21 @@ class GameInitializer {
|
||||
}, 500);
|
||||
}
|
||||
|
||||
// Also request economy data immediately to prevent reset
|
||||
if (economy.requestEconomyData) {
|
||||
economy.requestEconomyData();
|
||||
}
|
||||
|
||||
// Update UI
|
||||
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
|
||||
window.game.showNotification(`Purchased ${data.item.name}!`, 'success', 3000);
|
||||
}
|
||||
@ -828,8 +936,28 @@ class GameInitializer {
|
||||
|
||||
onShopItemsReceived(data) {
|
||||
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
|
||||
window.game.systems.itemSystem.processServerItems(data.items);
|
||||
window.game.systems.itemSystem.processServerItems(itemsToProcess);
|
||||
console.log('[GAME INITIALIZER] ItemSystem updated with server shop items');
|
||||
|
||||
// Update Economy shop UI
|
||||
@ -872,6 +1000,101 @@ class GameInitializer {
|
||||
this.currentUser = 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
|
||||
|
||||
@ -8,28 +8,25 @@ class Economy {
|
||||
constructor(gameEngine) {
|
||||
this.game = gameEngine;
|
||||
|
||||
// Preserve existing economy data if available (prevents wipe during re-initialization)
|
||||
const existingEconomy = window.game?.systems?.economy;
|
||||
const preservedCredits = existingEconomy?.credits || 0;
|
||||
const preservedGems = existingEconomy?.gems || 0;
|
||||
// Currency - don't override in multiplayer mode, will be set by server data
|
||||
if (window.smartSaveManager?.isMultiplayer) {
|
||||
this.credits = 0; // Will be updated by server
|
||||
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
|
||||
this.credits = preservedCredits;
|
||||
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
|
||||
// Transaction history
|
||||
this.transactions = [];
|
||||
|
||||
// Shop categories
|
||||
this.shopCategories = {
|
||||
ships: 'Ships',
|
||||
weapons: 'Weapons',
|
||||
modules: 'Modules',
|
||||
armors: 'Armors',
|
||||
cosmetics: 'Cosmetics',
|
||||
consumables: 'Consumables',
|
||||
materials: 'Materials'
|
||||
@ -40,6 +37,7 @@ class Economy {
|
||||
this.shopRefreshInterval = null; // Timer for 2-hour refresh
|
||||
this.shopHeartbeatInterval = null; // Timer for live countdown updates
|
||||
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.MAX_ITEMS_PER_CATEGORY = 8;
|
||||
this.categoryPurchaseLimits = {}; // Track purchases per category per refresh
|
||||
@ -50,18 +48,92 @@ class Economy {
|
||||
// Owned cosmetics
|
||||
this.ownedCosmetics = [];
|
||||
|
||||
console.log('[ECONOMY] Economy system initialized with server-side ItemSystem');
|
||||
console.log('[ECONOMY] Preserved values - Credits:', this.credits, 'Gems:', this.gems);
|
||||
// Owned ships
|
||||
this.ownedShips = [];
|
||||
|
||||
// Set up socket listeners for economy sync
|
||||
this.setupSocketListeners();
|
||||
console.log('[ECONOMY] Economy system initialized');
|
||||
|
||||
// Request fresh economy data after a short delay to ensure sync
|
||||
if (window.smartSaveManager?.isMultiplayer) {
|
||||
setTimeout(() => {
|
||||
this.requestEconomyData();
|
||||
}, 1000);
|
||||
// Initialize global purchase function
|
||||
Economy.initGlobalPurchaseFunction();
|
||||
}
|
||||
|
||||
// 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
|
||||
this.game.socket.on('economy_data', (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.gems = data.gems || 0;
|
||||
|
||||
console.log('[ECONOMY] Updated credits:', this.credits);
|
||||
console.log('[ECONOMY] Updated gems:', this.gems);
|
||||
|
||||
// Update UI immediately
|
||||
if (this.game.ui) {
|
||||
this.game.ui.updatePlayerStats();
|
||||
@ -86,6 +164,18 @@ class Economy {
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// 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', {
|
||||
shipId: ship.id,
|
||||
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
|
||||
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
|
||||
if (this.game.systems.ui) {
|
||||
this.game.systems.ui.updateResourceDisplay();
|
||||
@ -487,28 +635,33 @@ class Economy {
|
||||
const debugLogger = window.debugLogger;
|
||||
|
||||
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) {
|
||||
// Check if ItemSystem is ready before using it
|
||||
if (!this.game.systems.itemSystem || !this.game.systems.itemSystem.itemCatalog) {
|
||||
console.log('[ECONOMY] ItemSystem not ready yet, skipping shop update');
|
||||
return;
|
||||
}
|
||||
if (this.game.multiplayerMode && this.game.itemSystem && this.game.itemSystem.catalog) {
|
||||
console.log('[ECONOMY] Multiplayer mode:', true);
|
||||
console.log('[ECONOMY] ItemSystem available:', !!this.game.itemSystem);
|
||||
console.log('[ECONOMY] ItemSystem catalog:', !!this.game.itemSystem.catalog);
|
||||
|
||||
// Safe to use ItemSystem now
|
||||
const items = Array.from(this.game.systems.itemSystem.shopItems || []);
|
||||
console.log('[ECONOMY] Rendering shop with', items.length, 'items from ItemSystem');
|
||||
console.log('[ECONOMY] First few items:', items.slice(0, 3));
|
||||
this.renderShopItems(items);
|
||||
const shopItems = this.game.itemSystem.catalog;
|
||||
console.log('[ECONOMY] Got categorized shop items:', Object.keys(shopItems));
|
||||
|
||||
// Get current active category
|
||||
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 {
|
||||
// Singleplayer mode - use local shop data
|
||||
console.log('[ECONOMY] Singleplayer mode - using local shop data');
|
||||
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';
|
||||
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)
|
||||
const categoryTypeMap = {
|
||||
'ships': 'ship',
|
||||
'weapons': 'weapon',
|
||||
'armors': 'armor',
|
||||
'cosmetics': 'cosmetic',
|
||||
'consumables': 'consumable',
|
||||
'materials': 'material',
|
||||
'keys': 'key'
|
||||
};
|
||||
// Handle new shop data structure (items by category) or old structure (flat array)
|
||||
let categoryItems = [];
|
||||
|
||||
const targetItemType = categoryTypeMap[activeCategory] || activeCategory;
|
||||
console.log('[ECONOMY] Mapped category', activeCategory, 'to item type', targetItemType);
|
||||
if (items && typeof items === 'object' && !Array.isArray(items)) {
|
||||
// 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, '(type:', targetItemType, ') :', categoryItems.length, 'items');
|
||||
console.log('[ECONOMY] Filtered items for category', activeCategory, ':', categoryItems.length, 'items');
|
||||
console.log('[ECONOMY] Item types in category:', categoryItems.map(item => item.type));
|
||||
|
||||
if (categoryItems.length === 0) {
|
||||
shopItemsElement.innerHTML = '<p>No items available in this category</p>';
|
||||
@ -573,6 +728,7 @@ class Economy {
|
||||
<div class="shop-item-footer">
|
||||
<button class="shop-item-purchase-btn"
|
||||
data-item-id="${item.id}"
|
||||
onclick="purchaseShopItem('${item.id}')"
|
||||
${!canAfford || isOwned ? 'disabled' : ''}>
|
||||
${isOwned ? 'Owned' : 'Purchase'}
|
||||
</button>
|
||||
@ -582,6 +738,20 @@ class Economy {
|
||||
</div>
|
||||
`;
|
||||
}).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) {
|
||||
|
||||
@ -184,7 +184,10 @@ class GameEngine extends EventTarget {
|
||||
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') {
|
||||
this.systems.crafting = new CraftingSystem(this);
|
||||
}
|
||||
@ -426,7 +429,22 @@ class GameEngine extends EventTarget {
|
||||
console.log('[GAME ENGINE] Auto-saving game...');
|
||||
|
||||
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);
|
||||
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);
|
||||
}
|
||||
|
||||
// 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
|
||||
if (this.systems.economy && this.systems.economy.requestEconomyData) {
|
||||
setTimeout(() => {
|
||||
@ -662,6 +686,25 @@ class GameEngine extends EventTarget {
|
||||
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);
|
||||
} else {
|
||||
console.log('[GAME ENGINE] Missing player stats or player system');
|
||||
|
||||
@ -3,6 +3,69 @@
|
||||
* 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 {
|
||||
constructor(gameEngine) {
|
||||
this.game = gameEngine;
|
||||
@ -13,8 +76,23 @@ class DungeonSystem {
|
||||
this.dungeonProgress = 0;
|
||||
this.isExploring = false;
|
||||
|
||||
// Server data loaded from server
|
||||
this.serverDungeons = [];
|
||||
// Debouncing to prevent multiple rapid clicks
|
||||
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.enemyTemplates = {};
|
||||
|
||||
@ -39,14 +117,16 @@ class DungeonSystem {
|
||||
this.serverDungeons = data.dungeons || data;
|
||||
console.log('[DUNGEON SYSTEM] Loaded grouped dungeons from server:', Object.keys(this.serverDungeons));
|
||||
// Update UI when data is loaded
|
||||
this.generateDungeonList();
|
||||
this.forceGenerateDungeonList();
|
||||
});
|
||||
|
||||
// Listen for room types response
|
||||
this.game.socket.on('room_types_data', (data) => {
|
||||
console.log('[DUNGEON SYSTEM] Received room types data:', 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
|
||||
@ -55,22 +135,157 @@ class DungeonSystem {
|
||||
this.enemyTemplates = data;
|
||||
console.log(`[DUNGEON SYSTEM] Loaded ${Object.keys(this.enemyTemplates).length} enemy templates from server`);
|
||||
// Update UI when enemy data is loaded
|
||||
this.generateDungeonList();
|
||||
this.forceGenerateDungeonList();
|
||||
});
|
||||
|
||||
// Listen for dungeon start response
|
||||
this.game.socket.on('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.isExploring = true;
|
||||
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
|
||||
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] 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.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
|
||||
@ -169,7 +384,7 @@ class DungeonSystem {
|
||||
// Send packet to start dungeon
|
||||
this.game.socket.emit('start_dungeon', {
|
||||
dungeonId: dungeonId,
|
||||
userId: this.game.player?.id || 'anonymous'
|
||||
userId: this.game.systems.player?.id || 'anonymous'
|
||||
});
|
||||
|
||||
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() {
|
||||
if (!this.currentDungeon || !this.isExploring) {
|
||||
console.warn('[DUNGEON SYSTEM] No active dungeon to process');
|
||||
// Debounce to prevent multiple rapid clicks
|
||||
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;
|
||||
}
|
||||
|
||||
try {
|
||||
console.log(`[DUNGEON SYSTEM] Processing encounter for dungeon: ${this.currentDungeon.id}`);
|
||||
|
||||
if (!this.game.socket) {
|
||||
@ -201,7 +425,7 @@ class DungeonSystem {
|
||||
// Send packet to process encounter
|
||||
this.game.socket.emit('process_encounter', {
|
||||
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');
|
||||
@ -233,7 +457,7 @@ class DungeonSystem {
|
||||
// Send packet to complete dungeon
|
||||
this.game.socket.emit('complete_dungeon', {
|
||||
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');
|
||||
@ -257,7 +481,7 @@ class DungeonSystem {
|
||||
|
||||
// Send packet to 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');
|
||||
@ -270,11 +494,27 @@ class DungeonSystem {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Force generate dungeon list (bypasses throttle)
|
||||
*/
|
||||
forceGenerateDungeonList() {
|
||||
this.lastGenerationTime = 0; // Reset throttle
|
||||
this.generateDungeonList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate dungeon list UI using server data
|
||||
*/
|
||||
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');
|
||||
if (!dungeonListElement) {
|
||||
@ -300,13 +540,22 @@ class DungeonSystem {
|
||||
const difficultyTitle = difficulty === 'tutorial' ? 'Tutorial Dungeons' :
|
||||
difficulty.charAt(0).toUpperCase() + difficulty.slice(1) + ' Dungeons';
|
||||
const difficultyIcon = this.getDifficultyIcon(difficulty);
|
||||
const sectionId = `dungeon-section-${difficulty}`;
|
||||
|
||||
// Add difficulty header
|
||||
// Add collapsible difficulty header
|
||||
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>
|
||||
${difficultyTitle}
|
||||
</h3>
|
||||
<span>${difficultyTitle}</span>
|
||||
<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 => {
|
||||
@ -332,16 +581,90 @@ class DungeonSystem {
|
||||
</div>
|
||||
</div>
|
||||
<button class="dungeon-btn" ${!canEnter ? 'disabled' : ''}
|
||||
onclick="game.systems.dungeonSystem.startDungeon('${dungeon.id}')">
|
||||
onclick="startDungeon('${dungeon.id}')">
|
||||
${canEnter ? 'Enter Dungeon' : 'Locked'}
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
|
||||
// Close the section
|
||||
html += `
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
|
||||
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
|
||||
*/
|
||||
canEnterDungeon(dungeon) {
|
||||
if (!this.game.player) {
|
||||
console.log('[DUNGEON SYSTEM] No player data available');
|
||||
if (!this.game.systems.player) {
|
||||
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 maxLevel = dungeon.maxLevel || 999;
|
||||
const energyCost = dungeon.energyCost || 0;
|
||||
const playerEnergy = this.game.player.stats?.energy || 0;
|
||||
|
||||
console.log(`[DUNGEON SYSTEM] Dungeon check for ${dungeon.name}:`, {
|
||||
playerLevel,
|
||||
minLevel,
|
||||
maxLevel,
|
||||
playerEnergy,
|
||||
energyCost,
|
||||
canEnter: playerLevel >= minLevel && playerLevel <= maxLevel && playerEnergy >= energyCost
|
||||
});
|
||||
const playerEnergy = this.game.systems.player.attributes?.energy || 0;
|
||||
|
||||
return playerLevel >= minLevel &&
|
||||
playerLevel <= maxLevel &&
|
||||
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
|
||||
*/
|
||||
updateUI() {
|
||||
if (this.game.uiManager) {
|
||||
this.game.uiManager.updateDungeonUI({
|
||||
if (this.game.systems.ui) {
|
||||
this.game.systems.ui.updateDungeonUI({
|
||||
currentDungeon: this.currentDungeon,
|
||||
currentRoom: this.currentRoom,
|
||||
progress: this.dungeonProgress,
|
||||
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');
|
||||
}
|
||||
}, 1000);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setupSocketListeners();
|
||||
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
|
||||
|
||||
@ -9,7 +9,8 @@ class ItemSystem {
|
||||
|
||||
// Item storage
|
||||
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;
|
||||
|
||||
// Loading state
|
||||
@ -56,10 +57,31 @@ class ItemSystem {
|
||||
// Load shop items from server
|
||||
const shopItems = await this.fetchShopItems();
|
||||
|
||||
console.log('[ITEM SYSTEM] Received', shopItems.length, 'items from server');
|
||||
|
||||
// Process and store items
|
||||
// Handle new shop structure (categorized) vs old structure (flat array)
|
||||
let totalItems = 0;
|
||||
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.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();
|
||||
console.log(`[ITEM SYSTEM] Successfully loaded ${this.itemCatalog.size} items from server`);
|
||||
@ -113,9 +135,36 @@ class ItemSystem {
|
||||
reject(new Error('Server request timeout'));
|
||||
}, 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
|
||||
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', {});
|
||||
console.log('[ITEM SYSTEM] Request sent, waiting for response...');
|
||||
|
||||
// Listen for response
|
||||
const handleResponse = (data) => {
|
||||
@ -124,13 +173,20 @@ class ItemSystem {
|
||||
window.game.socket.off('shopItemsReceived', handleResponse);
|
||||
|
||||
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) {
|
||||
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] 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 {
|
||||
console.error('[ITEM SYSTEM] Server returned error:', data.error);
|
||||
reject(new Error(data.error || 'Failed to load shop items'));
|
||||
@ -139,6 +195,10 @@ class ItemSystem {
|
||||
|
||||
console.log('[ITEM SYSTEM] Setting up shopItemsReceived listener');
|
||||
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
|
||||
*/
|
||||
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] Sample items:', items.slice(0, 3));
|
||||
|
||||
@ -195,14 +262,14 @@ class ItemSystem {
|
||||
this.shopItems.push(item);
|
||||
}
|
||||
|
||||
console.log('[ITEM SYSTEM] Added item:', {
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
type: item.type,
|
||||
rarity: item.rarity,
|
||||
price: item.price,
|
||||
categories: item.categories
|
||||
});
|
||||
// console.log('[ITEM SYSTEM] Added item:', {
|
||||
// id: item.id,
|
||||
// name: item.name,
|
||||
// type: item.type,
|
||||
// rarity: item.rarity,
|
||||
// price: item.price,
|
||||
// categories: item.categories
|
||||
// });
|
||||
}
|
||||
|
||||
console.log('[ITEM SYSTEM] Processing complete - Catalog:', this.itemCatalog.size, 'Shop items:', this.shopItems.length);
|
||||
@ -238,6 +305,13 @@ class ItemSystem {
|
||||
return [...this.shopItems];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get shop items by category (new structure)
|
||||
*/
|
||||
getShopItemsByCategory() {
|
||||
return this.shopItemsByCategory || {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get items by category
|
||||
*/
|
||||
|
||||
@ -39,682 +39,56 @@ class QuestSystem {
|
||||
questStatus: Object.keys(this.questStatus)
|
||||
});
|
||||
|
||||
// Main story quests
|
||||
this.mainQuests = [
|
||||
{
|
||||
id: 'tutorial_complete',
|
||||
name: 'First Steps',
|
||||
description: 'Complete the tutorial dungeon and learn the basics',
|
||||
type: 'main',
|
||||
status: 'available',
|
||||
requirements: { level: 1 },
|
||||
objectives: [
|
||||
{ id: 'clear_tutorial_dungeon', description: 'Complete the tutorial dungeon', target: 1, current: 0, type: 'tutorial_dungeon' },
|
||||
{ id: 'reach_level_2', description: 'Reach level 2', target: 2, current: 0, type: 'level' }
|
||||
],
|
||||
rewards: { credits: 500, experience: 100, gems: 5 },
|
||||
nextQuest: 'first_ship_upgrade'
|
||||
},
|
||||
{
|
||||
id: 'first_ship_upgrade',
|
||||
name: 'Ship Enhancement',
|
||||
description: 'Upgrade your ship for better performance',
|
||||
type: 'main',
|
||||
status: 'available',
|
||||
requirements: { quest: 'tutorial_complete' },
|
||||
objectives: [
|
||||
{ id: 'upgrade_weapon', description: 'Upgrade ship weapons', target: 1, current: 0, type: 'upgrade' },
|
||||
{ id: 'upgrade_shield', description: 'Upgrade ship shields', target: 1, current: 0, type: 'upgrade' }
|
||||
],
|
||||
rewards: { credits: 1000, experience: 200, gems: 10 },
|
||||
nextQuest: 'join_guild'
|
||||
},
|
||||
{
|
||||
id: 'join_guild',
|
||||
name: 'Guild Recruitment',
|
||||
description: 'Join a guild and participate in guild activities',
|
||||
type: 'main',
|
||||
status: 'available',
|
||||
requirements: { quest: 'first_ship_upgrade', level: 5 },
|
||||
objectives: [
|
||||
{ id: 'join_guild', description: 'Join a guild', target: 1, current: 0, type: 'guild' },
|
||||
{ id: 'guild_contribution', description: 'Contribute to guild', target: 100, current: 0, type: 'contribution' }
|
||||
],
|
||||
rewards: { credits: 2000, experience: 500, gems: 20 },
|
||||
nextQuest: 'master_commander'
|
||||
},
|
||||
{
|
||||
id: 'master_commander',
|
||||
name: 'Master Commander',
|
||||
description: 'Become a master commander and lead your fleet to victory',
|
||||
type: 'main',
|
||||
status: 'available',
|
||||
requirements: { quest: 'join_guild', level: 10 },
|
||||
objectives: [
|
||||
{ id: 'reach_level_10', description: 'Reach level 10', target: 10, current: 0, type: 'level' },
|
||||
{ id: 'clear_10_dungeons', description: 'Clear 10 dungeons', target: 10, current: 0, type: 'dungeon' },
|
||||
{ id: 'max_skill', description: 'Max out one skill', target: 10, current: 0, type: 'skill' }
|
||||
],
|
||||
rewards: { credits: 5000, experience: 1000, gems: 50, item: 'legendary_weapon' }
|
||||
}
|
||||
];
|
||||
// Main story quests - populated by server
|
||||
this.mainQuests = [];
|
||||
|
||||
// All possible daily quests (20 total)
|
||||
this.allDailyQuests = [
|
||||
// Easy quests (difficulty: 1)
|
||||
{
|
||||
id: 'daily_dungeon_easy',
|
||||
name: 'Quick Dungeon Run',
|
||||
description: 'Complete any dungeon',
|
||||
type: 'daily',
|
||||
difficulty: 1,
|
||||
status: 'available',
|
||||
objectives: [
|
||||
{ id: 'clear_dungeon', description: 'Clear 1 dungeon', target: 1, current: 0, type: 'dungeon' }
|
||||
],
|
||||
rewards: { credits: 100, experience: 25, gems: 1 }
|
||||
},
|
||||
{
|
||||
id: 'daily_combat_easy',
|
||||
name: 'Light Combat',
|
||||
description: 'Defeat a few enemies',
|
||||
type: 'daily',
|
||||
difficulty: 1,
|
||||
status: 'available',
|
||||
objectives: [
|
||||
{ id: 'defeat_enemies', description: 'Defeat 10 enemies', target: 10, current: 0, type: 'combat' }
|
||||
],
|
||||
rewards: { credits: 80, experience: 20, gems: 1 }
|
||||
},
|
||||
{
|
||||
id: 'daily_crafting_easy',
|
||||
name: 'Basic Crafting',
|
||||
description: 'Craft some items',
|
||||
type: 'daily',
|
||||
difficulty: 1,
|
||||
status: 'available',
|
||||
objectives: [
|
||||
{ id: 'craft_items', description: 'Craft 2 items', target: 2, current: 0, type: 'crafting' }
|
||||
],
|
||||
rewards: { credits: 90, experience: 22, gems: 1 }
|
||||
},
|
||||
{
|
||||
id: 'daily_level_easy',
|
||||
name: 'Level Up',
|
||||
description: 'Gain experience and level up',
|
||||
type: 'daily',
|
||||
difficulty: 1,
|
||||
status: 'available',
|
||||
objectives: [
|
||||
{ id: 'gain_level', description: 'Gain 1 level', target: 1, current: 0, type: 'level' }
|
||||
],
|
||||
rewards: { credits: 120, experience: 30, gems: 2 }
|
||||
},
|
||||
{
|
||||
id: 'daily_energy_easy',
|
||||
name: 'Energy Management',
|
||||
description: 'Use energy efficiently',
|
||||
type: 'daily',
|
||||
difficulty: 1,
|
||||
status: 'available',
|
||||
objectives: [
|
||||
{ id: 'use_energy', description: 'Use 50 energy', target: 50, current: 0, type: 'energy' }
|
||||
],
|
||||
rewards: { credits: 70, experience: 18, gems: 1 }
|
||||
},
|
||||
// Medium quests (difficulty: 2)
|
||||
{
|
||||
id: 'daily_dungeon_medium',
|
||||
name: 'Dungeon Explorer',
|
||||
description: 'Complete multiple dungeons',
|
||||
type: 'daily',
|
||||
difficulty: 2,
|
||||
status: 'available',
|
||||
objectives: [
|
||||
{ id: 'clear_dungeons', description: 'Clear 3 dungeons', target: 3, current: 0, type: 'dungeon' }
|
||||
],
|
||||
rewards: { credits: 300, experience: 75, gems: 3 }
|
||||
},
|
||||
{
|
||||
id: 'daily_combat_medium',
|
||||
name: 'Combat Training',
|
||||
description: 'Defeat enemies in combat',
|
||||
type: 'daily',
|
||||
difficulty: 2,
|
||||
status: 'available',
|
||||
objectives: [
|
||||
{ id: 'defeat_enemies', description: 'Defeat 20 enemies', target: 20, current: 0, type: 'combat' }
|
||||
],
|
||||
rewards: { credits: 150, experience: 40, gems: 1 }
|
||||
},
|
||||
{
|
||||
id: 'daily_combat_hard',
|
||||
name: 'Combat Veteran',
|
||||
description: 'Defeat many enemies',
|
||||
type: 'daily',
|
||||
difficulty: 2,
|
||||
status: 'available',
|
||||
objectives: [
|
||||
{ id: 'defeat_enemies', description: 'Defeat 50 enemies', target: 50, current: 0, type: 'combat' }
|
||||
],
|
||||
rewards: { credits: 250, experience: 60, gems: 3 }
|
||||
},
|
||||
{
|
||||
id: 'daily_crafting_medium',
|
||||
name: 'Master Crafter',
|
||||
description: 'Craft many items',
|
||||
type: 'daily',
|
||||
difficulty: 2,
|
||||
status: 'available',
|
||||
objectives: [
|
||||
{ id: 'craft_items', description: 'Craft 5 items', target: 5, current: 0, type: 'crafting' }
|
||||
],
|
||||
rewards: { credits: 280, experience: 70, gems: 3 }
|
||||
},
|
||||
{
|
||||
id: 'daily_upgrade_medium',
|
||||
name: 'Equipment Upgrade',
|
||||
description: 'Upgrade your equipment',
|
||||
type: 'daily',
|
||||
difficulty: 2,
|
||||
status: 'available',
|
||||
objectives: [
|
||||
{ id: 'upgrade_items', description: 'Upgrade 3 items', target: 3, current: 0, type: 'upgrade' }
|
||||
],
|
||||
rewards: { credits: 320, experience: 80, gems: 4 }
|
||||
},
|
||||
{
|
||||
id: 'daily_wealth_medium',
|
||||
name: 'Wealth Accumulator',
|
||||
description: 'Earn credits',
|
||||
type: 'daily',
|
||||
difficulty: 2,
|
||||
status: 'available',
|
||||
objectives: [
|
||||
{ id: 'earn_credits', description: 'Earn 1000 credits', target: 1000, current: 0, type: 'credits' }
|
||||
],
|
||||
rewards: { credits: 400, experience: 50, gems: 3 }
|
||||
},
|
||||
// Hard quests (difficulty: 3)
|
||||
{
|
||||
id: 'daily_dungeon_hard',
|
||||
name: 'Dungeon Master',
|
||||
description: 'Complete many dungeons',
|
||||
type: 'daily',
|
||||
difficulty: 3,
|
||||
status: 'available',
|
||||
objectives: [
|
||||
{ id: 'clear_dungeons', description: 'Clear 5 dungeons', target: 5, current: 0, type: 'dungeon' }
|
||||
],
|
||||
rewards: { credits: 600, experience: 150, gems: 6 }
|
||||
},
|
||||
{
|
||||
id: 'daily_combat_hard',
|
||||
name: 'Combat Master',
|
||||
description: 'Defeat many powerful enemies',
|
||||
type: 'daily',
|
||||
difficulty: 3,
|
||||
status: 'available',
|
||||
objectives: [
|
||||
{ id: 'defeat_enemies', description: 'Defeat 100 enemies', target: 100, current: 0, type: 'combat' }
|
||||
],
|
||||
rewards: { credits: 500, experience: 120, gems: 5 }
|
||||
},
|
||||
{
|
||||
id: 'daily_level_hard',
|
||||
name: 'Power Leveling',
|
||||
description: 'Gain multiple levels',
|
||||
type: 'daily',
|
||||
difficulty: 3,
|
||||
status: 'available',
|
||||
objectives: [
|
||||
{ id: 'gain_levels', description: 'Gain 3 levels', target: 3, current: 0, type: 'level' }
|
||||
],
|
||||
rewards: { credits: 700, experience: 200, gems: 7 }
|
||||
},
|
||||
{
|
||||
id: 'daily_boss_hard',
|
||||
name: 'Boss Hunter',
|
||||
description: 'Defeat boss enemies',
|
||||
type: 'daily',
|
||||
difficulty: 3,
|
||||
status: 'available',
|
||||
objectives: [
|
||||
{ id: 'defeat_bosses', description: 'Defeat 3 bosses', target: 3, current: 0, type: 'boss' }
|
||||
],
|
||||
rewards: { credits: 800, experience: 180, gems: 8 }
|
||||
},
|
||||
{
|
||||
id: 'daily_collection_hard',
|
||||
name: 'Master Collector',
|
||||
description: 'Collect rare items',
|
||||
type: 'daily',
|
||||
difficulty: 3,
|
||||
status: 'available',
|
||||
objectives: [
|
||||
{ id: 'collect_rare', description: 'Collect 10 rare items', target: 10, current: 0, type: 'collection' }
|
||||
],
|
||||
rewards: { credits: 650, experience: 140, gems: 6 }
|
||||
},
|
||||
// Special quests (difficulty: 4)
|
||||
{
|
||||
id: 'daily_speedrun',
|
||||
name: 'Speed Runner',
|
||||
description: 'Complete dungeons quickly',
|
||||
type: 'daily',
|
||||
difficulty: 4,
|
||||
status: 'available',
|
||||
objectives: [
|
||||
{ id: 'fast_dungeon', description: 'Complete 2 dungeons under 5 minutes', target: 2, current: 0, type: 'speedrun' }
|
||||
],
|
||||
rewards: { credits: 1000, experience: 250, gems: 10 }
|
||||
},
|
||||
{
|
||||
id: 'daily_perfection',
|
||||
name: 'Perfectionist',
|
||||
description: 'Complete objectives without taking damage',
|
||||
type: 'daily',
|
||||
difficulty: 4,
|
||||
status: 'available',
|
||||
objectives: [
|
||||
{ id: 'perfect_runs', description: '3 perfect dungeon runs', target: 3, current: 0, type: 'perfect' }
|
||||
],
|
||||
rewards: { credits: 1200, experience: 300, gems: 12 }
|
||||
},
|
||||
{
|
||||
id: 'daily_multitask',
|
||||
name: 'Multitask Master',
|
||||
description: 'Complete multiple quest types',
|
||||
type: 'daily',
|
||||
difficulty: 4,
|
||||
status: 'available',
|
||||
objectives: [
|
||||
{ id: 'dungeon_task', description: 'Clear 2 dungeons', target: 2, current: 0, type: 'dungeon' },
|
||||
{ id: 'combat_task', description: 'Defeat 30 enemies', target: 30, current: 0, type: 'combat' },
|
||||
{ id: 'craft_task', description: 'Craft 2 items', target: 2, current: 0, type: 'crafting' }
|
||||
],
|
||||
rewards: { credits: 1500, experience: 400, gems: 15 }
|
||||
},
|
||||
{
|
||||
id: 'daily_endurance',
|
||||
name: 'Endurance Test',
|
||||
description: 'Complete long activities',
|
||||
type: 'daily',
|
||||
difficulty: 4,
|
||||
status: 'available',
|
||||
objectives: [
|
||||
{ id: 'long_dungeon', description: 'Complete 1 dungeon without healing', target: 1, current: 0, type: 'endurance' }
|
||||
],
|
||||
rewards: { credits: 1100, experience: 280, gems: 11 }
|
||||
},
|
||||
{
|
||||
id: 'daily_legendary',
|
||||
name: 'Legendary Challenge',
|
||||
description: 'Complete legendary difficulty content',
|
||||
type: 'daily',
|
||||
difficulty: 4,
|
||||
status: 'available',
|
||||
objectives: [
|
||||
{ id: 'legendary_content', description: 'Complete 1 legendary dungeon', target: 1, current: 0, type: 'legendary' }
|
||||
],
|
||||
rewards: { credits: 2000, experience: 500, gems: 20, item: 'rare_material' }
|
||||
}
|
||||
];
|
||||
|
||||
// Weekly quests (25 total quests with varied objectives)
|
||||
this.allWeeklyQuests = [
|
||||
// Combat-focused weekly quests
|
||||
{
|
||||
id: 'weekly_combat_basic',
|
||||
name: 'Weekly Combat Duty',
|
||||
description: 'Complete combat objectives throughout the week',
|
||||
type: 'weekly',
|
||||
difficulty: 1,
|
||||
status: 'available',
|
||||
objectives: [
|
||||
{ id: 'defeat_enemies', description: 'Defeat 100 enemies', target: 100, current: 0, type: 'combat' }
|
||||
],
|
||||
rewards: { credits: 800, experience: 200, gems: 8 }
|
||||
},
|
||||
{
|
||||
id: 'weekly_combat_elite',
|
||||
name: 'Elite Hunter Weekly',
|
||||
description: 'Hunt down elite enemies',
|
||||
type: 'weekly',
|
||||
difficulty: 3,
|
||||
status: 'available',
|
||||
objectives: [
|
||||
{ id: 'defeat_elites', description: 'Defeat 25 elite enemies', target: 25, current: 0, type: 'elite_combat' }
|
||||
],
|
||||
rewards: { credits: 1500, experience: 400, gems: 15 }
|
||||
},
|
||||
{
|
||||
id: 'weekly_boss_hunter',
|
||||
name: 'Boss Hunter Weekly',
|
||||
description: 'Defeat powerful boss enemies',
|
||||
type: 'weekly',
|
||||
difficulty: 4,
|
||||
status: 'available',
|
||||
objectives: [
|
||||
{ id: 'defeat_bosses', description: 'Defeat 10 bosses', target: 10, current: 0, type: 'boss' }
|
||||
],
|
||||
rewards: { credits: 2500, experience: 600, gems: 25, item: 'boss_material' }
|
||||
},
|
||||
|
||||
// Dungeon-focused weekly quests
|
||||
{
|
||||
id: 'weekly_dungeon_explorer',
|
||||
name: 'Weekly Dungeon Explorer',
|
||||
description: 'Explore various dungeons',
|
||||
type: 'weekly',
|
||||
difficulty: 2,
|
||||
status: 'available',
|
||||
objectives: [
|
||||
{ id: 'clear_dungeons', description: 'Clear 15 dungeons', target: 15, current: 0, type: 'dungeon' }
|
||||
],
|
||||
rewards: { credits: 1200, experience: 300, gems: 12 }
|
||||
},
|
||||
{
|
||||
id: 'weekly_dungeon_master',
|
||||
name: 'Weekly Dungeon Master',
|
||||
description: 'Master difficult dungeons',
|
||||
type: 'weekly',
|
||||
difficulty: 3,
|
||||
status: 'available',
|
||||
objectives: [
|
||||
{ id: 'clear_hard_dungeons', description: 'Clear 10 hard dungeons', target: 10, current: 0, type: 'hard_dungeon' }
|
||||
],
|
||||
rewards: { credits: 2000, experience: 500, gems: 20 }
|
||||
},
|
||||
{
|
||||
id: 'weekly_dungeon_extreme',
|
||||
name: 'Extreme Dungeon Challenge',
|
||||
description: 'Conquer extreme difficulty dungeons',
|
||||
type: 'weekly',
|
||||
difficulty: 4,
|
||||
status: 'available',
|
||||
objectives: [
|
||||
{ id: 'clear_extreme_dungeons', description: 'Clear 5 extreme dungeons', target: 5, current: 0, type: 'extreme_dungeon' }
|
||||
],
|
||||
rewards: { credits: 3500, experience: 800, gems: 35, item: 'extreme_material' }
|
||||
},
|
||||
|
||||
// Crafting and upgrade weekly quests
|
||||
{
|
||||
id: 'weekly_crafting_basic',
|
||||
name: 'Weekly Crafting Session',
|
||||
description: 'Craft items throughout the week',
|
||||
type: 'weekly',
|
||||
difficulty: 1,
|
||||
status: 'available',
|
||||
objectives: [
|
||||
{ id: 'craft_items', description: 'Craft 20 items', target: 20, current: 0, type: 'crafting' }
|
||||
],
|
||||
rewards: { credits: 600, experience: 150, gems: 6 }
|
||||
},
|
||||
{
|
||||
id: 'weekly_crafting_master',
|
||||
name: 'Master Crafter Weekly',
|
||||
description: 'Craft advanced items',
|
||||
type: 'weekly',
|
||||
difficulty: 3,
|
||||
status: 'available',
|
||||
objectives: [
|
||||
{ id: 'craft_advanced', description: 'Craft 10 advanced items', target: 10, current: 0, type: 'advanced_crafting' }
|
||||
],
|
||||
rewards: { credits: 1800, experience: 450, gems: 18 }
|
||||
},
|
||||
{
|
||||
id: 'weekly_upgrade_specialist',
|
||||
name: 'Weekly Upgrade Specialist',
|
||||
description: 'Upgrade equipment and systems',
|
||||
type: 'weekly',
|
||||
difficulty: 2,
|
||||
status: 'available',
|
||||
objectives: [
|
||||
{ id: 'upgrade_items', description: 'Upgrade 15 items', target: 15, current: 0, type: 'upgrade' }
|
||||
],
|
||||
rewards: { credits: 1400, experience: 350, gems: 14 }
|
||||
},
|
||||
|
||||
// Progression weekly quests
|
||||
{
|
||||
id: 'weekly_level_up',
|
||||
name: 'Weekly Level Up Challenge',
|
||||
description: 'Gain levels throughout the week',
|
||||
type: 'weekly',
|
||||
difficulty: 2,
|
||||
status: 'available',
|
||||
objectives: [
|
||||
{ id: 'gain_levels', description: 'Gain 5 levels', target: 5, current: 0, type: 'level' }
|
||||
],
|
||||
rewards: { credits: 1000, experience: 250, gems: 10 }
|
||||
},
|
||||
{
|
||||
id: 'weekly_skill_master',
|
||||
name: 'Weekly Skill Mastery',
|
||||
description: 'Improve your skills',
|
||||
type: 'weekly',
|
||||
difficulty: 3,
|
||||
status: 'available',
|
||||
objectives: [
|
||||
{ id: 'improve_skills', description: 'Gain 20 skill points', target: 20, current: 0, type: 'skill' }
|
||||
],
|
||||
rewards: { credits: 1600, experience: 400, gems: 16 }
|
||||
},
|
||||
|
||||
// Resource and wealth weekly quests
|
||||
{
|
||||
id: 'weekly_wealth_collector',
|
||||
name: 'Weekly Wealth Collector',
|
||||
description: 'Accumulate wealth',
|
||||
type: 'weekly',
|
||||
difficulty: 2,
|
||||
status: 'available',
|
||||
objectives: [
|
||||
{ id: 'earn_credits', description: 'Earn 5000 credits', target: 5000, current: 0, type: 'credits' }
|
||||
],
|
||||
rewards: { credits: 2000, experience: 300, gems: 12 }
|
||||
},
|
||||
{
|
||||
id: 'weekly_resource_gatherer',
|
||||
name: 'Weekly Resource Gathering',
|
||||
description: 'Collect valuable resources',
|
||||
type: 'weekly',
|
||||
difficulty: 2,
|
||||
status: 'available',
|
||||
objectives: [
|
||||
{ id: 'collect_resources', description: 'Collect 500 resources', target: 500, current: 0, type: 'collection' }
|
||||
],
|
||||
rewards: { credits: 1200, experience: 320, gems: 13 }
|
||||
},
|
||||
|
||||
// Special activity weekly quests
|
||||
{
|
||||
id: 'weekly_energy_management',
|
||||
name: 'Weekly Energy Management',
|
||||
description: 'Use energy efficiently',
|
||||
type: 'weekly',
|
||||
difficulty: 1,
|
||||
status: 'available',
|
||||
objectives: [
|
||||
{ id: 'use_energy', description: 'Use 500 energy', target: 500, current: 0, type: 'energy' }
|
||||
],
|
||||
rewards: { credits: 800, experience: 180, gems: 8 }
|
||||
},
|
||||
{
|
||||
id: 'weekly_speed_demon',
|
||||
name: 'Weekly Speed Demon',
|
||||
description: 'Complete activities quickly',
|
||||
type: 'weekly',
|
||||
difficulty: 3,
|
||||
status: 'available',
|
||||
objectives: [
|
||||
{ id: 'speed_runs', description: 'Complete 10 speed runs', target: 10, current: 0, type: 'speedrun' }
|
||||
],
|
||||
rewards: { credits: 2200, experience: 550, gems: 22 }
|
||||
},
|
||||
{
|
||||
id: 'weekly_perfectionist',
|
||||
name: 'Weekly Perfectionist',
|
||||
description: 'Complete flawless runs',
|
||||
type: 'weekly',
|
||||
difficulty: 4,
|
||||
status: 'available',
|
||||
objectives: [
|
||||
{ id: 'perfect_runs', description: 'Complete 8 perfect runs', target: 8, current: 0, type: 'perfect' }
|
||||
],
|
||||
rewards: { credits: 3000, experience: 700, gems: 30, item: 'perfection_material' }
|
||||
},
|
||||
|
||||
// Multi-objective weekly quests
|
||||
{
|
||||
id: 'weekly_all_rounder',
|
||||
name: 'Weekly All-Rounder',
|
||||
description: 'Complete various activities',
|
||||
type: 'weekly',
|
||||
difficulty: 3,
|
||||
status: 'available',
|
||||
objectives: [
|
||||
{ id: 'dungeon_task', description: 'Clear 8 dungeons', target: 8, current: 0, type: 'dungeon' },
|
||||
{ id: 'combat_task', description: 'Defeat 50 enemies', target: 50, current: 0, type: 'combat' },
|
||||
{ id: 'craft_task', description: 'Craft 5 items', target: 5, current: 0, type: 'crafting' }
|
||||
],
|
||||
rewards: { credits: 2500, experience: 600, gems: 25 }
|
||||
},
|
||||
{
|
||||
id: 'weekly_specialist',
|
||||
name: 'Weekly Specialist',
|
||||
description: 'Focus on specialized activities',
|
||||
type: 'weekly',
|
||||
difficulty: 3,
|
||||
status: 'available',
|
||||
objectives: [
|
||||
{ id: 'special_dungeons', description: 'Clear 5 themed dungeons', target: 5, current: 0, type: 'themed_dungeon' },
|
||||
{ id: 'special_crafting', description: 'Craft 8 themed items', target: 8, current: 0, type: 'themed_crafting' }
|
||||
],
|
||||
rewards: { credits: 2300, experience: 580, gems: 23 }
|
||||
},
|
||||
|
||||
// Exploration and discovery weekly quests
|
||||
{
|
||||
id: 'weekly_explorer',
|
||||
name: 'Weekly Explorer',
|
||||
description: 'Explore new areas and content',
|
||||
type: 'weekly',
|
||||
difficulty: 2,
|
||||
status: 'available',
|
||||
objectives: [
|
||||
{ id: 'explore_areas', description: 'Explore 20 new areas', target: 20, current: 0, type: 'exploration' }
|
||||
],
|
||||
rewards: { credits: 1300, experience: 340, gems: 13 }
|
||||
},
|
||||
{
|
||||
id: 'weekly_discovery',
|
||||
name: 'Weekly Discovery',
|
||||
description: 'Discover hidden secrets',
|
||||
type: 'weekly',
|
||||
difficulty: 3,
|
||||
status: 'available',
|
||||
objectives: [
|
||||
{ id: 'discover_secrets', description: 'Discover 15 secrets', target: 15, current: 0, type: 'discovery' }
|
||||
],
|
||||
rewards: { credits: 1900, experience: 480, gems: 19 }
|
||||
},
|
||||
|
||||
// Endurance and challenge weekly quests
|
||||
{
|
||||
id: 'weekly_endurance',
|
||||
name: 'Weekly Endurance Test',
|
||||
description: 'Complete long-form challenges',
|
||||
type: 'weekly',
|
||||
difficulty: 3,
|
||||
status: 'available',
|
||||
objectives: [
|
||||
{ id: 'endurance_runs', description: 'Complete 5 endurance runs', target: 5, current: 0, type: 'endurance' }
|
||||
],
|
||||
rewards: { credits: 2100, experience: 530, gems: 21 }
|
||||
},
|
||||
{
|
||||
id: 'weekly_survivor',
|
||||
name: 'Weekly Survivor',
|
||||
description: 'Survive challenging conditions',
|
||||
type: 'weekly',
|
||||
difficulty: 4,
|
||||
status: 'available',
|
||||
objectives: [
|
||||
{ id: 'survival_runs', description: 'Complete 3 survival runs', target: 3, current: 0, type: 'survival' }
|
||||
],
|
||||
rewards: { credits: 3200, experience: 750, gems: 32, item: 'survival_material' }
|
||||
},
|
||||
|
||||
// Social and community weekly quests
|
||||
{
|
||||
id: 'weekly_helper',
|
||||
name: 'Weekly Helper',
|
||||
description: 'Assist other players',
|
||||
type: 'weekly',
|
||||
difficulty: 2,
|
||||
status: 'available',
|
||||
objectives: [
|
||||
{ id: 'assist_players', description: 'Assist 10 players', target: 10, current: 0, type: 'assist' }
|
||||
],
|
||||
rewards: { credits: 1100, experience: 280, gems: 11 }
|
||||
},
|
||||
{
|
||||
id: 'weekly_leader',
|
||||
name: 'Weekly Leader',
|
||||
description: 'Lead group activities',
|
||||
type: 'weekly',
|
||||
difficulty: 3,
|
||||
status: 'available',
|
||||
objectives: [
|
||||
{ id: 'lead_activities', description: 'Lead 5 group activities', target: 5, current: 0, type: 'leadership' }
|
||||
],
|
||||
rewards: { credits: 1700, experience: 430, gems: 17 }
|
||||
},
|
||||
|
||||
// Legendary weekly quests
|
||||
{
|
||||
id: 'weekly_legendary',
|
||||
name: 'Weekly Legendary Challenge',
|
||||
description: 'Complete legendary difficulty content',
|
||||
type: 'weekly',
|
||||
difficulty: 5,
|
||||
status: 'available',
|
||||
objectives: [
|
||||
{ id: 'legendary_content', description: 'Complete 3 legendary dungeons', target: 3, current: 0, type: 'legendary' }
|
||||
],
|
||||
rewards: { credits: 5000, experience: 1200, gems: 50, item: 'legendary_material' }
|
||||
},
|
||||
{
|
||||
id: 'weekly_mythic',
|
||||
name: 'Weekly Mythic Trial',
|
||||
description: 'Face mythic level challenges',
|
||||
type: 'weekly',
|
||||
difficulty: 5,
|
||||
status: 'available',
|
||||
objectives: [
|
||||
{ id: 'mythic_trials', description: 'Complete 2 mythic trials', target: 2, current: 0, type: 'mythic' }
|
||||
],
|
||||
rewards: { credits: 6000, experience: 1500, gems: 60, item: 'mythic_material' }
|
||||
}
|
||||
];
|
||||
|
||||
// Currently active daily quests (3 random from allDailyQuests)
|
||||
// Daily quests - populated by server
|
||||
this.dailyQuests = [];
|
||||
this.selectedDailyQuests = [];
|
||||
|
||||
// Currently active weekly quests (5 random from allWeeklyQuests)
|
||||
this.weeklyQuests = [];
|
||||
this.selectedWeeklyQuests = [];
|
||||
|
||||
// Current active quests
|
||||
this.activeQuests = [];
|
||||
this.completedQuests = [];
|
||||
this.failedQuests = [];
|
||||
this.completedDailyQuests = []; // History of completed daily quests
|
||||
this.completedWeeklyQuests = []; // History of completed weekly quests
|
||||
|
||||
// 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
|
||||
try {
|
||||
|
||||
@ -1712,7 +1712,9 @@ class UIManager {
|
||||
if (creditsElement) {
|
||||
const oldCredits = creditsElement.textContent;
|
||||
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);
|
||||
elementsUpdated++;
|
||||
|
||||
@ -1732,7 +1734,9 @@ class UIManager {
|
||||
if (gemsElement) {
|
||||
const oldGems = gemsElement.textContent;
|
||||
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);
|
||||
elementsUpdated++;
|
||||
|
||||
@ -1753,8 +1757,11 @@ class UIManager {
|
||||
const oldEnergy = energyElement.textContent;
|
||||
const currentEnergy = player.attributes.currentEnergy || player.attributes.energy || 0;
|
||||
const maxEnergy = player.attributes.maxEnergy || 100;
|
||||
// console.log('[UI MANAGER] Energy debug - player.attributes.currentEnergy:', player.attributes.currentEnergy, 'player.attributes.maxEnergy:', player.attributes.maxEnergy);
|
||||
energyElement.textContent = `${currentEnergy}/${maxEnergy}`;
|
||||
const newEnergy = `${currentEnergy}/${maxEnergy}`;
|
||||
if (oldEnergy !== newEnergy) {
|
||||
console.log('[UI MANAGER] Energy updated:', newEnergy);
|
||||
}
|
||||
energyElement.textContent = newEnergy;
|
||||
elementsUpdated++;
|
||||
|
||||
if (debugLogger) debugLogger.logStep('Energy updated', {
|
||||
@ -2630,6 +2637,94 @@ class UIManager {
|
||||
</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
|
||||
|
||||
@ -1533,6 +1533,134 @@ body.fullscreen .shop-container {
|
||||
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-item {
|
||||
background: var(--bg-tertiary);
|
||||
|
||||
@ -54,6 +54,9 @@ const io = socketIo(server, {
|
||||
}
|
||||
});
|
||||
|
||||
// Pass io instance to systems that need it
|
||||
dungeonSystem.setIO(io);
|
||||
|
||||
// Game state
|
||||
const gameInstances = new Map();
|
||||
const connectedClients = new Map();
|
||||
@ -180,7 +183,7 @@ app.get('/api/shop/items', (req, res) => {
|
||||
const shopItems = itemSystem.getRandomShopItems();
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
items: shopItems,
|
||||
shopItems: shopItems,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
} catch (error) {
|
||||
@ -195,12 +198,12 @@ app.get('/api/shop/items', (req, res) => {
|
||||
app.get('/api/shop/items/:category', (req, res) => {
|
||||
try {
|
||||
const { category } = req.params;
|
||||
const items = itemSystem.getItemsByType(category);
|
||||
const items = itemSystem.getRandomItemsByCategory(category);
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
items: items,
|
||||
category: category,
|
||||
items: items,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
} catch (error) {
|
||||
@ -299,6 +302,7 @@ app.use('/api/base', require('./routes/base'));
|
||||
|
||||
// Socket.IO handlers (based on LocalServer)
|
||||
io.on('connection', (socket) => {
|
||||
console.log('[GAME SERVER] === NEW CLIENT CONNECTION ===');
|
||||
console.log('[GAME SERVER] Client connected:', socket.id);
|
||||
connectedClients.set(socket.id, {
|
||||
connectedAt: Date.now(),
|
||||
@ -329,6 +333,13 @@ io.on('connection', (socket) => {
|
||||
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)
|
||||
socket.on('authenticate', async (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.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);
|
||||
|
||||
// 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
|
||||
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) => {
|
||||
console.log('[GAME SERVER] === getShopItems REQUEST START ===');
|
||||
console.log('[GAME SERVER] getShopItems request received from:', socket.id);
|
||||
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 {
|
||||
console.log('[GAME SERVER] Getting shop items from ItemSystem...');
|
||||
const shopItems = itemSystem.getRandomShopItems();
|
||||
console.log('[GAME SERVER] Got shop items:', shopItems.length, 'items');
|
||||
console.log('[GAME SERVER] Sample item:', shopItems[0]);
|
||||
console.log('[GAME SERVER] Got shop items by category:', Object.keys(shopItems));
|
||||
|
||||
// Log item counts per category
|
||||
Object.entries(shopItems).forEach(([category, items]) => {
|
||||
console.log(`[GAME SERVER] ${category}: ${items.length} items`);
|
||||
});
|
||||
|
||||
const response = {
|
||||
success: true,
|
||||
items: shopItems,
|
||||
shopItems: shopItems, // Changed from 'items' to 'shopItems'
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
|
||||
console.log('[GAME SERVER] Sending response:', response);
|
||||
console.log('[GAME SERVER] About to emit shopItemsReceived to:', socket.id);
|
||||
|
||||
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) {
|
||||
console.error('[GAME SERVER] Error sending shop items:', error);
|
||||
console.error('[GAME SERVER] Error stack:', error.stack);
|
||||
@ -486,6 +538,44 @@ io.on('connection', (socket) => {
|
||||
success: false,
|
||||
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 {
|
||||
const playerData = await loadPlayerData(clientData.userId);
|
||||
const playerData = await loadPlayerData(clientData.userId, clientData.username || 'Player');
|
||||
const offlineRewards = idleSystem.calculateOfflineRewards(clientData.userId);
|
||||
|
||||
if (offlineRewards.offlineTime > 0 && offlineRewards.rewards.credits > 0) {
|
||||
@ -665,7 +755,17 @@ io.on('connection', (socket) => {
|
||||
}
|
||||
|
||||
// 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:', {
|
||||
username: playerData.username,
|
||||
credits: playerData.stats.credits,
|
||||
@ -801,7 +901,18 @@ io.on('connection', (socket) => {
|
||||
// Send success response
|
||||
const response = {
|
||||
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,
|
||||
totalCost: totalCost,
|
||||
currency: currency,
|
||||
@ -865,6 +976,20 @@ io.on('connection', (socket) => {
|
||||
console.log('[GAME SERVER] Starting dungeon for:', socket.id, data);
|
||||
try {
|
||||
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, []);
|
||||
socket.emit('dungeon_started', { instance });
|
||||
} catch (error) {
|
||||
@ -877,8 +1002,29 @@ io.on('connection', (socket) => {
|
||||
console.log('[GAME SERVER] Processing encounter for:', socket.id, data);
|
||||
try {
|
||||
const { instanceId, userId } = data;
|
||||
const encounter = dungeonSystem.startEncounter(instanceId, userId);
|
||||
socket.emit('encounter_data', { encounter });
|
||||
const result = dungeonSystem.startEncounter(instanceId, userId);
|
||||
|
||||
// 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) {
|
||||
console.error('[GAME SERVER] Error processing encounter:', error);
|
||||
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) => {
|
||||
console.log('[GAME SERVER] Getting dungeon status for:', socket.id, data);
|
||||
try {
|
||||
@ -1224,9 +1409,12 @@ async function startServer() {
|
||||
|
||||
// Start online idle rewards generation (every 10 seconds)
|
||||
setInterval(async () => {
|
||||
console.log('[GAME SERVER] Idle reward timer triggered - checking', connectedClients.size, 'connected clients');
|
||||
|
||||
for (const [socketId, clientData] of connectedClients.entries()) {
|
||||
if (clientData.userId && clientData.playerData) {
|
||||
try {
|
||||
console.log('[GAME SERVER] Processing idle rewards for client:', socketId, 'user:', clientData.username);
|
||||
// Update playTime for active players
|
||||
const sessionTime = clientData.playerData.updatePlayTime();
|
||||
console.log(`[GAME SERVER] Updated playTime for ${clientData.username}: +${sessionTime}ms, Total: ${clientData.playerData.stats.playTime}ms`);
|
||||
@ -1239,11 +1427,15 @@ async function startServer() {
|
||||
|
||||
const onlineRewards = idleSystem.generateOnlineIdleRewards(clientData.userId, 10000); // 10 seconds
|
||||
|
||||
console.log('[GAME SERVER] Generated online rewards for', clientData.username, ':', onlineRewards);
|
||||
|
||||
if (onlineRewards.credits > 0) {
|
||||
// Update player data with online rewards
|
||||
clientData.playerData.stats.credits = (clientData.playerData.stats.credits || 0) + onlineRewards.credits;
|
||||
clientData.playerData.stats.experience = (clientData.playerData.stats.experience || 0) + onlineRewards.experience;
|
||||
|
||||
console.log('[GAME SERVER] Applied idle rewards - Credits:', onlineRewards.credits, 'New balance:', clientData.playerData.stats.credits);
|
||||
|
||||
// Send update to client
|
||||
io.to(socketId).emit('onlineIdleRewards', {
|
||||
credits: onlineRewards.credits,
|
||||
@ -1251,6 +1443,10 @@ async function startServer() {
|
||||
newBalance: clientData.playerData.stats.credits,
|
||||
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) {
|
||||
console.error(`[GAME SERVER] Error generating online idle rewards for ${socketId}:`, error);
|
||||
|
||||
@ -1024,10 +1024,121 @@ class DungeonSystem {
|
||||
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) {
|
||||
// Generate encounters for this dungeon
|
||||
const encounters = this.generateDungeonEncounters(dungeon);
|
||||
|
||||
this.dungeons.set(id, {
|
||||
id,
|
||||
...dungeon,
|
||||
encounters,
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString()
|
||||
});
|
||||
@ -1076,11 +1187,19 @@ class DungeonSystem {
|
||||
}
|
||||
|
||||
createInstance(dungeonId, creatorId, playerIds = []) {
|
||||
console.log('[DUNGEON SYSTEM] Creating instance:', { dungeonId, creatorId, playerIds });
|
||||
|
||||
const dungeon = this.getDungeon(dungeonId);
|
||||
if (!dungeon) {
|
||||
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
|
||||
if (playerIds.length > dungeon.maxPlayers) {
|
||||
throw new Error('Too many players for this dungeon');
|
||||
@ -1235,6 +1354,7 @@ class DungeonSystem {
|
||||
return {
|
||||
success: true,
|
||||
nextEncounter: instance.currentEncounter < dungeon.encounters.length ? dungeon.encounters[instance.currentEncounter] : null,
|
||||
encounterIndex: instance.currentEncounter,
|
||||
instance
|
||||
};
|
||||
}
|
||||
@ -1258,16 +1378,58 @@ class DungeonSystem {
|
||||
// Remove players from instance tracking
|
||||
for (const playerId of instance.players) {
|
||||
this.playerInstances.delete(playerId);
|
||||
|
||||
// Check for quest completion
|
||||
this.checkQuestCompletion(playerId, instance.dungeonId);
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
dungeon,
|
||||
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) {
|
||||
const rewards = {
|
||||
experience: 0,
|
||||
@ -1275,10 +1437,21 @@ class DungeonSystem {
|
||||
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
|
||||
const expRange = dungeon.rewards.experience;
|
||||
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.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
|
||||
if (Math.random() < itemChance) {
|
||||
const itemPool = dungeon.rewards.items;
|
||||
if (itemPool.length > 0) {
|
||||
if (itemPool && itemPool.length > 0) {
|
||||
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) {
|
||||
const instances = Array.from(this.instances.values()).filter(instance =>
|
||||
instance.players.has(userId)
|
||||
|
||||
@ -50,15 +50,25 @@ class IdleSystem {
|
||||
}
|
||||
|
||||
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)) {
|
||||
console.log('[IDLE SYSTEM] Initializing new player data for:', userId);
|
||||
this.playerLastActive.set(userId, Date.now());
|
||||
this.playerProductionRates.set(userId, { ...this.defaultProductionRates });
|
||||
console.log('[IDLE SYSTEM] Set production rates for', userId, ':', this.defaultProductionRates);
|
||||
this.playerAchievements.set(userId, {
|
||||
totalOfflineTime: 0,
|
||||
maxOfflineSession: 0,
|
||||
totalIdleCredits: 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)
|
||||
generateOnlineIdleRewards(userId, deltaTimeMs) {
|
||||
console.log('[IDLE SYSTEM] generateOnlineIdleRewards called for userId:', userId, 'deltaTimeMs:', deltaTimeMs);
|
||||
|
||||
const productionRates = this.playerProductionRates.get(userId);
|
||||
console.log('[IDLE SYSTEM] Production rates for', userId, ':', productionRates);
|
||||
|
||||
if (!productionRates) {
|
||||
console.log('[IDLE SYSTEM] No production rates found for', userId, '- returning 0 rewards');
|
||||
return {
|
||||
credits: 0,
|
||||
experience: 0,
|
||||
@ -260,12 +275,18 @@ class IdleSystem {
|
||||
}
|
||||
|
||||
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 = {
|
||||
credits: Math.floor(productionRates.credits * deltaTimeSeconds),
|
||||
credits: Math.floor(creditsBeforeFloor),
|
||||
experience: Math.floor(productionRates.experience * 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;
|
||||
}
|
||||
|
||||
|
||||
@ -81,6 +81,20 @@ class ItemSystem {
|
||||
categories: ['shop', 'dungeon_reward'],
|
||||
requirements: { level: 7 },
|
||||
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,
|
||||
maxStack: 10,
|
||||
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
|
||||
consumable: true,
|
||||
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,
|
||||
cosmetic: true,
|
||||
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 },
|
||||
stackable: false,
|
||||
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 },
|
||||
stackable: false,
|
||||
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) {
|
||||
const allItems = Object.values(this.itemCatalog).flat();
|
||||
getRandomShopItems(count = 6) {
|
||||
const shopItems = {};
|
||||
|
||||
// Get 6 random items from each category
|
||||
Object.keys(this.itemCatalog).forEach(category => {
|
||||
const categoryItems = this.itemCatalog[category] || [];
|
||||
const selectedItems = [];
|
||||
|
||||
// Randomly select items
|
||||
const shuffled = [...allItems].sort(() => Math.random() - 0.5);
|
||||
if (categoryItems.length > 0) {
|
||||
// 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++) {
|
||||
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;
|
||||
}
|
||||
|
||||
@ -660,6 +660,149 @@ class QuestSystem {
|
||||
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) {
|
||||
const playerData = this.getPlayerData(userId);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user