833 lines
31 KiB
JavaScript
833 lines
31 KiB
JavaScript
/**
|
|
* Galaxy Strike Online - Client Dungeon System
|
|
* 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;
|
|
|
|
// Current dungeon state (runtime only)
|
|
this.currentDungeon = null;
|
|
this.currentRoom = null;
|
|
this.dungeonProgress = 0;
|
|
this.isExploring = false;
|
|
|
|
// 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 = {};
|
|
|
|
console.log('[DUNGEON SYSTEM] Client DungeonSystem initialized - server-driven mode');
|
|
|
|
// Set up socket event listeners
|
|
this.setupSocketListeners();
|
|
}
|
|
|
|
/**
|
|
* Set up Socket.IO event listeners for dungeon data
|
|
*/
|
|
setupSocketListeners() {
|
|
if (!this.game.socket) {
|
|
console.warn('[DUNGEON SYSTEM] No socket available for event listeners');
|
|
return;
|
|
}
|
|
|
|
// Listen for dungeon data response
|
|
this.game.socket.on('dungeons_data', (data) => {
|
|
console.log('[DUNGEON SYSTEM] Received dungeons data:', data);
|
|
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.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 ${Object.keys(this.roomTypes).length} room types from server`);
|
|
// Update UI when room data is loaded
|
|
this.forceGenerateDungeonList();
|
|
});
|
|
|
|
// Listen for enemy templates response
|
|
this.game.socket.on('enemy_templates_data', (data) => {
|
|
console.log('[DUNGEON SYSTEM] Received enemy templates data:', data);
|
|
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.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 = 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
|
|
this.game.socket.on('dungeon_completed', (data) => {
|
|
console.log('[DUNGEON SYSTEM] Dungeon completed:', data);
|
|
// Reset dungeon state
|
|
this.currentDungeon = null;
|
|
this.currentRoom = null;
|
|
this.isExploring = false;
|
|
this.dungeonProgress = 0;
|
|
});
|
|
|
|
// Listen for dungeon status response
|
|
this.game.socket.on('dungeon_status', (data) => {
|
|
console.log('[DUNGEON SYSTEM] Dungeon status received:', data);
|
|
});
|
|
|
|
console.log('[DUNGEON SYSTEM] Socket event listeners set up');
|
|
}
|
|
|
|
/**
|
|
* Load dungeon data from server using Socket.IO packets
|
|
*/
|
|
async loadServerData() {
|
|
try {
|
|
console.log('[DUNGEON SYSTEM] Loading dungeon data from server via packets...');
|
|
|
|
if (!this.game.socket) {
|
|
console.error('[DUNGEON SYSTEM] No socket connection available');
|
|
return;
|
|
}
|
|
|
|
// Request dungeons from server
|
|
this.game.socket.emit('get_dungeons');
|
|
|
|
// Request room types from server
|
|
this.game.socket.emit('get_room_types');
|
|
|
|
// Request enemy templates from server
|
|
this.game.socket.emit('get_enemy_templates');
|
|
|
|
console.log('[DUNGEON SYSTEM] Server data requests sent via packets');
|
|
|
|
} catch (error) {
|
|
console.error('[DUNGEON SYSTEM] Error loading server data:', error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get all available dungeons
|
|
*/
|
|
getAllDungeons() {
|
|
return this.serverDungeons;
|
|
}
|
|
|
|
/**
|
|
* Get dungeons by difficulty
|
|
*/
|
|
getDungeonsByDifficulty(difficulty) {
|
|
return this.serverDungeons.filter(dungeon => dungeon.difficulty === difficulty);
|
|
}
|
|
|
|
/**
|
|
* Get specific dungeon by ID
|
|
*/
|
|
getDungeon(dungeonId) {
|
|
return this.serverDungeons.find(dungeon => dungeon.id === dungeonId);
|
|
}
|
|
|
|
/**
|
|
* Get room type by ID
|
|
*/
|
|
getRoomType(roomTypeId) {
|
|
return this.roomTypes[roomTypeId];
|
|
}
|
|
|
|
/**
|
|
* Get enemy template by ID
|
|
*/
|
|
getEnemyTemplate(enemyId) {
|
|
return this.enemyTemplates[enemyId];
|
|
}
|
|
|
|
/**
|
|
* Start exploring a dungeon using Socket.IO packets
|
|
*/
|
|
async startDungeon(dungeonId) {
|
|
try {
|
|
console.log(`[DUNGEON SYSTEM] Starting dungeon: ${dungeonId}`);
|
|
|
|
if (!this.game.socket) {
|
|
console.error('[DUNGEON SYSTEM] No socket connection available');
|
|
return null;
|
|
}
|
|
|
|
// Send packet to start dungeon
|
|
this.game.socket.emit('start_dungeon', {
|
|
dungeonId: dungeonId,
|
|
userId: this.game.systems.player?.id || 'anonymous'
|
|
});
|
|
|
|
console.log('[DUNGEON SYSTEM] Dungeon start packet sent');
|
|
return true;
|
|
|
|
} catch (error) {
|
|
console.error('[DUNGEON SYSTEM] Error starting dungeon:', error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Process encounter in current dungeon room
|
|
*/
|
|
async processEncounter() {
|
|
// 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;
|
|
}
|
|
|
|
console.log(`[DUNGEON SYSTEM] Processing encounter for dungeon: ${this.currentDungeon.id}`);
|
|
|
|
if (!this.game.socket) {
|
|
console.error('[DUNGEON SYSTEM] No socket connection available');
|
|
return null;
|
|
}
|
|
|
|
// Send packet to process encounter
|
|
this.game.socket.emit('process_encounter', {
|
|
instanceId: this.currentDungeon.id,
|
|
userId: this.game.systems.player?.id || 'anonymous'
|
|
});
|
|
|
|
console.log('[DUNGEON SYSTEM] Encounter process packet sent');
|
|
return true;
|
|
|
|
} catch (error) {
|
|
console.error('[DUNGEON SYSTEM] Error processing encounter:', error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Complete current dungeon using Socket.IO packets
|
|
*/
|
|
async completeDungeon() {
|
|
if (!this.currentDungeon || !this.isExploring) {
|
|
console.warn('[DUNGEON SYSTEM] No active dungeon to complete');
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
console.log(`[DUNGEON SYSTEM] Completing dungeon: ${this.currentDungeon.id}`);
|
|
|
|
if (!this.game.socket) {
|
|
console.error('[DUNGEON SYSTEM] No socket connection available');
|
|
return null;
|
|
}
|
|
|
|
// Send packet to complete dungeon
|
|
this.game.socket.emit('complete_dungeon', {
|
|
instanceId: this.currentDungeon.id,
|
|
userId: this.game.systems.player?.id || 'anonymous'
|
|
});
|
|
|
|
console.log('[DUNGEON SYSTEM] Dungeon completion packet sent');
|
|
return true;
|
|
|
|
} catch (error) {
|
|
console.error('[DUNGEON SYSTEM] Error completing dungeon:', error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get player's current dungeon status using Socket.IO packets
|
|
*/
|
|
async getDungeonStatus() {
|
|
try {
|
|
if (!this.game.socket) {
|
|
console.error('[DUNGEON SYSTEM] No socket connection available');
|
|
return null;
|
|
}
|
|
|
|
// Send packet to get dungeon status
|
|
this.game.socket.emit('get_dungeon_status', {
|
|
userId: this.game.systems.player?.id || 'anonymous'
|
|
});
|
|
|
|
console.log('[DUNGEON SYSTEM] Dungeon status request packet sent');
|
|
return true;
|
|
|
|
} catch (error) {
|
|
console.error('[DUNGEON SYSTEM] Error getting dungeon status:', error);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Force generate dungeon list (bypasses throttle)
|
|
*/
|
|
forceGenerateDungeonList() {
|
|
this.lastGenerationTime = 0; // Reset throttle
|
|
this.generateDungeonList();
|
|
}
|
|
|
|
/**
|
|
* Generate dungeon list UI using server data
|
|
*/
|
|
generateDungeonList() {
|
|
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) {
|
|
console.error('[DUNGEON SYSTEM] Dungeon list element not found');
|
|
return;
|
|
}
|
|
|
|
// Clear existing content
|
|
dungeonListElement.innerHTML = '';
|
|
|
|
if (!this.serverDungeons || Object.keys(this.serverDungeons).length === 0) {
|
|
dungeonListElement.innerHTML = '<p>Loading dungeons from server...</p>';
|
|
return;
|
|
}
|
|
|
|
// Generate HTML for each difficulty category
|
|
let html = '';
|
|
|
|
Object.entries(this.serverDungeons).forEach(([difficulty, dungeons]) => {
|
|
if (!dungeons || dungeons.length === 0) return;
|
|
|
|
const difficultyClass = difficulty === 'tutorial' ? 'tutorial' : difficulty;
|
|
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 collapsible difficulty header
|
|
html += `
|
|
<div class="dungeon-section">
|
|
<div class="difficulty-header ${difficultyClass} collapsible" onclick="toggleDungeonSection('${sectionId}')">
|
|
<div class="header-content">
|
|
<i class="${difficultyIcon}"></i>
|
|
<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 => {
|
|
const canEnter = this.canEnterDungeon(dungeon);
|
|
const statusClass = canEnter ? 'available' : 'locked';
|
|
const energyCost = dungeon.energyCost || 0;
|
|
const healthType = dungeon.healthType || 'player';
|
|
const healthIcon = healthType === 'ship' ? '🚀' : '👤';
|
|
|
|
// Each dungeon in its own individual container using proper CSS classes
|
|
html += `
|
|
<div class="dungeon-item ${statusClass}" data-dungeon-id="${dungeon.id}">
|
|
<div class="dungeon-name">${dungeon.name}</div>
|
|
<div class="dungeon-difficulty ${difficulty}">
|
|
<i class="${difficultyIcon}"></i> ${difficulty} - ${energyCost} Energy
|
|
</div>
|
|
<div class="dungeon-description">${dungeon.description}</div>
|
|
<div class="health-type">${healthIcon}</div>
|
|
<div class="dungeon-enemies">
|
|
<strong>Enemies:</strong>
|
|
<div class="enemy-list">
|
|
${this.generateEnemyList(dungeon.enemyTypes || [])}
|
|
</div>
|
|
</div>
|
|
<button class="dungeon-btn" ${!canEnter ? 'disabled' : ''}
|
|
onclick="startDungeon('${dungeon.id}')">
|
|
${canEnter ? 'Enter Dungeon' : 'Locked'}
|
|
</button>
|
|
</div>
|
|
`;
|
|
});
|
|
|
|
// Close the section
|
|
html += `
|
|
</div>
|
|
</div>
|
|
`;
|
|
});
|
|
|
|
dungeonListElement.innerHTML = html;
|
|
|
|
// 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'}`);
|
|
}
|
|
|
|
/**
|
|
* Get difficulty icon for dungeon
|
|
*/
|
|
getDifficultyIcon(difficulty) {
|
|
const icons = {
|
|
tutorial: 'fas fa-graduation-cap',
|
|
easy: 'fas fa-smile',
|
|
medium: 'fas fa-meh',
|
|
hard: 'fas fa-frown',
|
|
extreme: 'fas fa-skull'
|
|
};
|
|
return icons[difficulty] || 'fas fa-question';
|
|
}
|
|
|
|
/**
|
|
* Generate enemy list HTML for dungeon
|
|
*/
|
|
generateEnemyList(enemyTypes) {
|
|
if (!enemyTypes || enemyTypes.length === 0) {
|
|
return '<span class="no-enemies">No enemies</span>';
|
|
}
|
|
|
|
let html = '';
|
|
enemyTypes.forEach(enemyType => {
|
|
const enemy = this.getEnemyTemplate(enemyType);
|
|
if (enemy) {
|
|
html += `
|
|
<div class="enemy-item">
|
|
<span class="enemy-name">${enemy.name}</span>
|
|
<div class="enemy-stats">
|
|
<span class="health">❤️ ${enemy.health}</span>
|
|
<span class="attack">⚔️ ${enemy.attack}</span>
|
|
<span class="defense">🛡️ ${enemy.defense}</span>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
});
|
|
|
|
return html;
|
|
}
|
|
|
|
/**
|
|
* Check if player can enter dungeon
|
|
*/
|
|
canEnterDungeon(dungeon) {
|
|
if (!this.game.systems.player) {
|
|
return false;
|
|
}
|
|
|
|
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.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.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');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Initialize system and load server data
|
|
*/
|
|
async initialize() {
|
|
console.log('[DUNGEON SYSTEM] Initializing client dungeon system...');
|
|
|
|
// Set up socket listeners if not already done
|
|
if (!this.game.socket) {
|
|
console.warn('[DUNGEON SYSTEM] Socket not available during initialization, will retry...');
|
|
// Retry after a short delay
|
|
setTimeout(() => {
|
|
if (this.game.socket) {
|
|
this.setupSocketListeners();
|
|
this.loadServerData();
|
|
} else {
|
|
console.error('[DUNGEON SYSTEM] Socket still not available after retry');
|
|
}
|
|
}, 1000);
|
|
return;
|
|
}
|
|
|
|
this.setupSocketListeners();
|
|
await this.loadServerData();
|
|
}
|
|
}
|
|
|
|
// Export DungeonSystem to global scope
|
|
if (typeof window !== 'undefined') {
|
|
window.DungeonSystem = DungeonSystem;
|
|
}
|
|
|
|
// Export for use in GameEngine
|
|
if (typeof module !== 'undefined' && module.exports) {
|
|
module.exports = DungeonSystem;
|
|
}
|