Game-Server/Client/js/ui/LiveMainMenu.js
2026-01-24 16:47:19 -04:00

1434 lines
61 KiB
JavaScript

/**
* Live Main Menu System
* Handles server authentication, server browser, and multiplayer game initialization
*/
console.log('[LIVE MAIN MENU] LiveMainMenu.js script loaded');
class LiveMainMenu {
constructor() {
console.log('[LIVE MAIN MENU] Constructor called');
// Check if DOM is ready
if (document.readyState === 'loading') {
console.warn('[LIVE MAIN MENU] DOM not ready yet, elements may not be found');
} else {
console.log('[LIVE MAIN MENU] DOM is ready');
}
this.mainMenu = document.getElementById('mainMenu');
console.log('[LIVE MAIN MENU] Main menu element found:', !!this.mainMenu);
this.selectedServer = null;
this.authToken = null;
this.currentUser = null;
this.servers = []; // Renamed from serverList to avoid conflict with DOM element
this.apiBaseUrl = 'https://api.korvarix.com/api'; // API Server URL
this.gameServerUrl = 'https://api.korvarix.com'; // Game Server URL for Socket.IO
this.isLocalMode = false; // Track if we're in local mode
console.log('[LIVE MAIN MENU] Initializing elements');
this.initializeElements();
console.log('[LIVE MAIN MENU] Initializing event listeners');
this.bindEvents();
// Check for existing auth token
this.checkExistingAuth();
// Check for local server and update URLs if needed
this.checkForLocalServer();
console.log('[LIVE MAIN MENU] Constructor completed');
}
initializeElements() {
// Menu sections
this.loginSection = document.getElementById('loginSection');
this.serverSection = document.getElementById('serverSection');
this.serverConfirmSection = document.getElementById('serverConfirmSection');
this.optionsSection = document.getElementById('optionsSection');
// Login form elements
this.emailInput = document.getElementById('emailInput');
this.passwordInput = document.getElementById('passwordInput');
this.loginBtn = document.getElementById('loginBtn');
this.registerBtn = document.getElementById('registerBtn');
this.loginNotice = document.getElementById('loginNotice');
// Server browser elements
this.createServerBtn = document.getElementById('createServerBtn');
this.refreshServersBtn = document.getElementById('refreshServersBtn');
this.regionFilter = document.getElementById('regionFilter');
this.typeFilter = document.getElementById('typeFilter');
this.serverList = document.getElementById('serverList');
this.serverLoading = document.getElementById('serverLoading');
this.serverEmpty = document.getElementById('serverEmpty');
// Server confirmation elements
this.joinServerBtn = document.getElementById('joinServerBtn');
this.serverInfoBtn = document.getElementById('serverInfoBtn');
this.leaveServerBtn = document.getElementById('leaveServerBtn');
// Server detail elements
this.selectedServerName = document.getElementById('selectedServerName');
this.selectedServerType = document.getElementById('selectedServerType');
this.selectedServerRegion = document.getElementById('selectedServerRegion');
this.selectedServerPlayers = document.getElementById('selectedServerPlayers');
this.selectedServerOwner = document.getElementById('selectedServerOwner');
// Navigation buttons
this.backToLoginBtn = document.getElementById('backToLoginBtn');
this.backToServersBtn = document.getElementById('backToServersBtn');
console.log('[LIVE MAIN MENU] All elements initialized');
}
bindEvents() {
console.log('[LIVE MAIN MENU] Binding events...');
// Login events
if (this.loginBtn) {
this.loginBtn.addEventListener('click', () => this.handleLogin());
console.log('[LIVE MAIN MENU] Login button event bound');
} else {
console.warn('[LIVE MAIN MENU] Login button not found');
}
if (this.registerBtn) {
this.registerBtn.addEventListener('click', () => this.handleRegister());
console.log('[LIVE MAIN MENU] Register button event bound');
} else {
console.warn('[LIVE MAIN MENU] Register button not found');
}
// Enter key login
if (this.passwordInput) {
this.passwordInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') this.handleLogin();
});
console.log('[LIVE MAIN MENU] Password input event bound');
} else {
console.warn('[LIVE MAIN MENU] Password input not found');
}
// Server browser events
if (this.createServerBtn) {
this.createServerBtn.addEventListener('click', () => this.startLocalServer());
console.log('[LIVE MAIN MENU] Create server button event bound (now starts local server)');
} else {
console.warn('[LIVE MAIN MENU] Create server button not found');
}
if (this.refreshServersBtn) {
this.refreshServersBtn.addEventListener('click', () => this.refreshServerListWithoutAnimation());
console.log('[LIVE MAIN MENU] Refresh servers button event bound');
} else {
console.warn('[LIVE MAIN MENU] Refresh servers button not found');
}
if (this.regionFilter) {
this.regionFilter.addEventListener('change', () => this.filterServers());
console.log('[LIVE MAIN MENU] Region filter event bound');
} else {
console.warn('[LIVE MAIN MENU] Region filter not found');
}
if (this.typeFilter) {
this.typeFilter.addEventListener('change', () => this.filterServers());
console.log('[LIVE MAIN MENU] Type filter event bound');
} else {
console.warn('[LIVE MAIN MENU] Type filter not found');
}
// Server confirmation events
if (this.joinServerBtn) {
this.joinServerBtn.addEventListener('click', () => this.joinServer());
console.log('[LIVE MAIN MENU] Join server button event bound');
} else {
console.warn('[LIVE MAIN MENU] Join server not found');
}
// Navigation events
if (this.backToLoginBtn) {
this.backToLoginBtn.addEventListener('click', () => this.showLoginSection());
console.log('[LIVE MAIN MENU] Back to login button event bound');
} else {
console.warn('[LIVE MAIN MENU] Back to login button not found');
}
if (this.backToServersBtn) {
this.backToServersBtn.addEventListener('click', () => this.showServerSection());
console.log('[LIVE MAIN MENU] Back to servers button event bound');
} else {
console.warn('[LIVE MAIN MENU] Back to servers button not found');
}
console.log('[LIVE MAIN MENU] All events bound (with warnings for missing elements)');
}
checkExistingAuth() {
const token = localStorage.getItem('authToken');
const user = localStorage.getItem('currentUser');
if (token && user) {
this.authToken = token;
this.currentUser = JSON.parse(user);
console.log('[LIVE MAIN MENU] Found existing auth token, validating...');
this.validateToken();
} else {
console.log('[LIVE MAIN MENU] No existing auth found');
this.showLoginSection();
}
}
async validateToken() {
try {
const response = await fetch(`${this.apiBaseUrl}/auth/verify`, {
method: 'GET',
headers: {
'Authorization': `Bearer ${this.authToken}`,
'Content-Type': 'application/json'
}
});
if (response.ok) {
const data = await response.json();
this.currentUser = data.user;
localStorage.setItem('currentUser', JSON.stringify(this.currentUser));
console.log('[LIVE MAIN MENU] Token validated successfully');
this.showServerSection();
} else {
console.log('[LIVE MAIN MENU] Token invalid, clearing auth');
this.clearAuth();
this.showLoginSection();
}
} catch (error) {
console.error('[LIVE MAIN MENU] Token validation failed:', error);
this.clearAuth();
this.showLoginSection();
}
}
clearAuth() {
this.authToken = null;
this.currentUser = null;
localStorage.removeItem('authToken');
localStorage.removeItem('currentUser');
}
showSection(sectionName, animate = true) {
// Don't re-animate if we're showing the same section
if (this.currentSection === sectionName) {
animate = false;
}
// Hide all sections
this.loginSection?.classList.add('hidden');
this.serverSection?.classList.add('hidden');
this.serverConfirmSection?.classList.add('hidden');
this.optionsSection?.classList.add('hidden');
// Show selected section
const section = document.getElementById(`${sectionName}Section`);
if (section) {
section.classList.remove('hidden');
// Control animation
if (animate) {
section.style.animation = 'fadeInUp 0.5s ease-out';
} else {
section.style.animation = 'none';
}
this.currentSection = sectionName;
}
}
showLoginSection() {
this.showSection('login');
this.clearLoginForm();
}
showServerSection(refreshServers = true, animate = true) {
this.showSection('server', animate);
if (refreshServers) {
this.refreshServerList();
}
}
showServerConfirmSection() {
this.showSection('serverConfirm');
this.updateServerConfirmSection();
}
clearLoginForm() {
if (this.emailInput) this.emailInput.value = '';
if (this.passwordInput) this.passwordInput.value = '';
this.clearLoginNotice();
}
clearLoginNotice() {
if (this.loginNotice) {
this.loginNotice.innerHTML = `
<p><i class="fas fa-info-circle"></i> Connect to the live server to play</p>
<p style="font-size: 0.9em; margin-top: 10px; opacity: 0.8;">
<strong>Note:</strong> You need an internet connection to play Galaxy Strike Online.
</p>
`;
}
}
showLoginNotice(message, type = 'info') {
if (this.loginNotice) {
const iconClass = type === 'error' ? 'fa-exclamation-triangle' :
type === 'success' ? 'fa-check-circle' : 'fa-info-circle';
this.loginNotice.innerHTML = `<p><i class="fas ${iconClass}"></i> ${message}</p>`;
this.loginNotice.className = `login-notice ${type}`;
this.loginNotice.style.display = 'block';
}
}
hideLoginNotice() {
if (this.loginNotice) {
this.loginNotice.style.display = 'none';
}
}
async handleLogin() {
const email = this.emailInput?.value?.trim();
const password = this.passwordInput?.value?.trim();
if (!email || !password) {
this.showLoginNotice('Please enter email and password', 'error');
return;
}
// Disable login button
if (this.loginBtn) {
this.loginBtn.disabled = true;
this.loginBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Logging in...';
}
try {
const response = await fetch(`${this.apiBaseUrl}/auth/login`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ email, password })
});
// Check if response is HTML (error page) instead of JSON
const contentType = response.headers.get('content-type');
if (!contentType || !contentType.includes('application/json')) {
throw new Error('API server is not available. Please check your connection.');
}
const data = await response.json();
if (response.ok) {
this.authToken = data.token;
this.currentUser = data.user;
// Save to localStorage
localStorage.setItem('authToken', this.authToken);
localStorage.setItem('currentUser', JSON.stringify(this.currentUser));
this.showLoginNotice('Login successful!', 'success');
console.log('[LIVE MAIN MENU] Login successful');
// Show server browser after a short delay
setTimeout(() => this.showServerSection(), 1000);
} else {
this.showLoginNotice(data.error || 'Login failed', 'error');
}
} catch (error) {
console.error('[LIVE MAIN MENU] Login error:', error);
this.showLoginNotice('Connection error. Please try again.', 'error');
} finally {
// Re-enable login button
if (this.loginBtn) {
this.loginBtn.disabled = false;
this.loginBtn.innerHTML = '<i class="fas fa-sign-in-alt"></i> Login';
}
}
}
async handleRegister() {
const email = this.emailInput?.value?.trim();
const password = this.passwordInput?.value?.trim();
if (!email || !password) {
this.showLoginNotice('Please enter email and password', 'error');
return;
}
if (password.length < 6) {
this.showLoginNotice('Password must be at least 6 characters', 'error');
return;
}
// Disable register button
if (this.registerBtn) {
this.registerBtn.disabled = true;
this.registerBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Registering...';
}
try {
// Generate a username from email
const username = email.split('@')[0];
const response = await fetch(`${this.apiBaseUrl}/auth/register`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ username, email, password })
});
// Check if response is HTML (error page) instead of JSON
const contentType = response.headers.get('content-type');
if (!contentType || !contentType.includes('application/json')) {
throw new Error('API server is not available. Please check your connection.');
}
const data = await response.json();
if (response.ok) {
this.authToken = data.token;
this.currentUser = data.user;
// Save to localStorage
localStorage.setItem('authToken', this.authToken);
localStorage.setItem('currentUser', JSON.stringify(this.currentUser));
this.showLoginNotice('Registration successful!', 'success');
console.log('[LIVE MAIN MENU] Registration successful');
// Show server browser after a short delay
setTimeout(() => this.showServerSection(), 1000);
} else {
this.showLoginNotice(data.error || 'Registration failed', 'error');
}
} catch (error) {
console.error('[LIVE MAIN MENU] Registration error:', error);
this.showLoginNotice('Connection error. Please try again.', 'error');
} finally {
// Re-enable register button
if (this.registerBtn) {
this.registerBtn.disabled = false;
this.registerBtn.innerHTML = '<i class="fas fa-user-plus"></i> Register';
}
}
}
async refreshServerListWithoutAnimation() {
// Ensure we're in server section without animation
if (this.currentSection !== 'server') {
this.showServerSection(true, false); // Refresh but don't animate
} else {
// Already in server section, just refresh
await this.refreshServerList();
}
}
async refreshServerList() {
if (!this.authToken) {
console.error('[LIVE MAIN MENU] No auth token for server list');
return;
}
// Show loading state
if (this.serverLoading) this.serverLoading.classList.remove('hidden');
if (this.serverEmpty) this.serverEmpty.classList.add('hidden');
try {
let response;
// Use SimpleLocalServer mock API if in local mode
if (this.isLocalMode && window.localServerManager && window.localServerManager.localServer && window.localServerManager.localServer.mockRequest) {
console.log('[LIVE MAIN MENU] Using SimpleLocalServer mock API for server list');
response = await window.localServerManager.localServer.mockRequest('GET', '/api/servers');
} else {
console.log('[LIVE MAIN MENU] Fetching server list from:', `${this.apiBaseUrl}/servers`);
response = await fetch(`${this.apiBaseUrl}/servers`, {
method: 'GET',
headers: {
'Authorization': `Bearer ${this.authToken}`,
'Content-Type': 'application/json'
}
});
}
console.log('[LIVE MAIN MENU] Server list response status:', response.status);
// Check if response is HTML (error page) instead of JSON
const contentType = response.headers ? response.headers.get('content-type') : 'application/json';
if (!contentType || !contentType.includes('application/json')) {
throw new Error('API server is not available. Please check your connection.');
}
if (response.ok) {
const data = await response.json();
this.servers = data.servers || [];
console.log('[LIVE MAIN MENU] Server list loaded:', this.servers);
this.renderServerList();
} else {
const errorText = await response.text();
console.error('[LIVE MAIN MENU] Failed to load server list - Status:', response.status, 'Error:', errorText);
this.servers = [];
this.renderServerList();
// Show error message to user
this.showLoginNotice(`Failed to connect to server: ${response.status}`, 'error');
}
} catch (error) {
console.error('[LIVE MAIN MENU] Server list error:', error);
this.servers = [];
this.renderServerList();
// Show error message to user
this.showLoginNotice(`Network error: ${error.message}`, 'error');
} finally {
// Hide loading state
if (this.serverLoading) this.serverLoading.classList.add('hidden');
}
}
renderServerList() {
if (!this.servers) return;
const filteredServers = this.getFilteredServers();
// Handle empty state
if (filteredServers.length === 0) {
if (this.serverEmpty) this.serverEmpty.classList.remove('hidden');
// Remove existing server items smoothly
this.removeServerItemsSmoothly();
return;
} else {
if (this.serverEmpty) this.serverEmpty.classList.add('hidden');
}
// Get current server items
const currentItems = this.serverList?.querySelectorAll('.server-item') || [];
// Create new server items HTML
const serverListHtml = filteredServers.map(server => this.createServerItem(server)).join('');
// Create a temporary container to hold new items
const tempDiv = document.createElement('div');
tempDiv.innerHTML = serverListHtml;
const newItems = Array.from(tempDiv.querySelectorAll('.server-item'));
// Smooth update: fade out current items, then replace with new ones
this.updateServerItemsSmoothly(currentItems, newItems);
}
removeServerItemsSmoothly() {
const existingItems = this.serverList?.querySelectorAll('.server-item') || [];
if (existingItems.length === 0) return;
// Add fade-out transition
existingItems.forEach(item => {
item.style.transition = 'opacity 0.2s ease-out';
item.style.opacity = '0';
});
// Remove items after fade out
setTimeout(() => {
existingItems.forEach(item => item.remove());
}, 200);
}
updateServerItemsSmoothly(currentItems, newItems) {
// Fade out current items
if (currentItems.length > 0) {
currentItems.forEach(item => {
item.style.transition = 'opacity 0.2s ease-out';
item.style.opacity = '0';
});
// Replace with new items after fade out
setTimeout(() => {
currentItems.forEach(item => item.remove());
this.addNewServerItems(newItems);
}, 200);
} else {
// No current items, add new ones immediately
this.addNewServerItems(newItems);
}
}
addNewServerItems(newItems) {
if (!this.serverList) return;
// Add fade-in transition to new items
newItems.forEach(item => {
item.style.transition = 'opacity 0.3s ease-in';
item.style.opacity = '0';
});
// Add new items to DOM
newItems.forEach(item => {
this.serverList.appendChild(item);
});
// Add click handlers
newItems.forEach(item => {
item.addEventListener('click', () => {
const serverId = item.dataset.serverId;
this.selectServer(serverId);
});
});
// Fade in new items
setTimeout(() => {
newItems.forEach(item => {
item.style.opacity = '1';
});
}, 50);
}
createServerItem(server) {
const playerCount = `${server.currentPlayers}/${server.maxPlayers}`;
const createdDate = new Date(server.createdAt).toLocaleDateString();
return `
<div class="server-item" data-server-id="${server.id}">
<div class="server-info">
<div class="server-name">${server.name}</div>
<div class="server-details">
<span class="server-detail">
<i class="fas fa-globe"></i>
<span class="server-region">${server.region || 'Unknown'}</span>
</span>
<span class="server-detail">
<i class="fas fa-calendar"></i>
<span>${createdDate}</span>
</span>
</div>
</div>
<div class="server-actions-right">
<span class="server-type ${server.type}">${server.type}</span>
<span class="server-player-count">${playerCount}</span>
<i class="fas fa-chevron-right"></i>
</div>
</div>
`;
}
getFilteredServers() {
let filtered = [...this.servers];
const regionFilter = this.regionFilter?.value;
const typeFilter = this.typeFilter?.value;
if (regionFilter) {
filtered = filtered.filter(server => server.region === regionFilter);
}
if (typeFilter) {
filtered = filtered.filter(server => server.type === typeFilter);
}
return filtered;
}
filterServers() {
this.renderServerList();
}
selectServer(serverId) {
this.selectedServer = this.servers.find(server => server.id === serverId);
if (this.selectedServer) {
console.log('[LIVE MAIN MENU] Server selected:', this.selectedServer);
this.showServerConfirmSection();
}
}
updateServerConfirmSection() {
if (!this.selectedServer) return;
// Update server details
if (this.selectedServerName) this.selectedServerName.textContent = this.selectedServer.name;
if (this.selectedServerType) this.selectedServerType.textContent = this.selectedServer.type;
if (this.selectedServerRegion) this.selectedServerRegion.textContent = this.selectedServer.region || 'Unknown';
if (this.selectedServerPlayers) this.selectedServerPlayers.textContent =
`${this.selectedServer.currentPlayers}/${this.selectedServer.maxPlayers}`;
if (this.selectedServerOwner) this.selectedServerOwner.textContent = this.selectedServer.ownerName || 'Unknown';
}
async joinServer() {
if (!this.selectedServer || !this.authToken) {
console.error('[LIVE MAIN MENU] No server selected or not authenticated');
return;
}
// Disable join button
if (this.joinServerBtn) {
this.joinServerBtn.disabled = true;
this.joinServerBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Joining...';
}
try {
let response;
// Use SimpleLocalServer mock API if in local mode
if (this.isLocalMode && window.localServerManager && window.localServerManager.localServer && window.localServerManager.localServer.mockRequest) {
console.log('[LIVE MAIN MENU] Using SimpleLocalServer mock API for join server');
response = await window.localServerManager.localServer.mockRequest('POST', `/servers/${this.selectedServer.id}/join`, {
userId: this.currentUser.id,
token: this.authToken
});
} else {
console.log('[LIVE MAIN MENU] Using real fetch for join server');
response = await fetch(`${this.apiBaseUrl}/servers/${this.selectedServer.id}/join`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.authToken}`,
'Content-Type': 'application/json'
}
});
}
// Check if response is HTML (error page) instead of JSON
const contentType = response.headers ? response.headers.get('content-type') : 'application/json';
if (!contentType || !contentType.includes('application/json')) {
throw new Error('API server is not available. Please check your connection.');
}
const data = await response.json();
if (response.ok) {
console.log('[LIVE MAIN MENU] Joined server successfully');
// Apply existing save data if in local mode
if (this.isLocalMode && window.localServerManager && window.localServerManager.localServer) {
console.log('[LIVE MAIN MENU] Applying existing save data for local mode...');
// Store save data for later application (after GameEngine is created)
this.pendingSaveData = window.localServerManager.localServer.existingSaveData;
if (this.pendingSaveData) {
console.log('[LIVE MAIN MENU] Save data stored for later application');
} else {
console.log('[LIVE MAIN MENU] No save data to store');
}
}
this.launchMultiplayerGame(this.selectedServer, data.server);
} else {
console.error('[LIVE MAIN MENU] Failed to join server:', data.error);
alert(data.error || 'Failed to join server');
}
} catch (error) {
console.error('[LIVE MAIN MENU] Join server error:', error);
alert('Connection error. Please try again.');
} finally {
// Re-enable join button
if (this.joinServerBtn) {
this.joinServerBtn.disabled = false;
this.joinServerBtn.innerHTML = '<i class="fas fa-sign-in-alt"></i> Join Server';
}
}
}
showServerInfo() {
if (!this.selectedServer) return;
const info = `
Server Information:
Name: ${this.selectedServer.name}
Type: ${this.selectedServer.type}
Region: ${this.selectedServer.region}
Players: ${this.selectedServer.currentPlayers}/${this.selectedServer.maxPlayers}
Owner: ${this.selectedServer.ownerName}
Created: ${new Date(this.selectedServer.createdAt).toLocaleString()}
Status: ${this.selectedServer.status}
`.trim();
alert(info);
}
leaveServer() {
this.selectedServer = null;
this.showServerSection();
}
showCreateServerDialog() {
return new Promise((resolve) => {
// Create modal dialog
const modal = document.createElement('div');
modal.className = 'modal-overlay';
modal.innerHTML = `
<div class="modal-dialog">
<div class="modal-header">
<h3>Create New Server</h3>
<button class="modal-close">&times;</button>
</div>
<div class="modal-body">
<div class="form-group">
<label for="serverName">Server Name:</label>
<input type="text" id="serverName" placeholder="Enter server name..." maxlength="50" required>
</div>
<div class="form-group">
<label for="serverType">Server Type:</label>
<select id="serverType">
<option value="public">Public</option>
<option value="private">Private</option>
</select>
</div>
<div class="form-group">
<label for="serverRegion">Region:</label>
<select id="serverRegion">
<option value="us-east">US East</option>
<option value="us-west">US West</option>
<option value="europe">Europe</option>
<option value="asia">Asia</option>
</select>
</div>
<div class="form-group">
<label for="maxPlayers">Max Players:</label>
<input type="number" id="maxPlayers" min="1" max="20" value="10">
</div>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" id="cancelBtn">Cancel</button>
<button class="btn btn-primary" id="createBtn">Create Server</button>
</div>
</div>
`;
// Add styles for modal
const style = document.createElement('style');
style.textContent = `
.modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.8);
display: flex;
justify-content: center;
align-items: center;
z-index: 10000;
}
.modal-dialog {
background: var(--bg-primary);
border: 1px solid var(--border-color);
border-radius: 8px;
padding: 20px;
min-width: 400px;
max-width: 500px;
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.modal-header h3 {
margin: 0;
color: var(--text-primary);
}
.modal-close {
background: none;
border: none;
color: var(--text-secondary);
font-size: 24px;
cursor: pointer;
}
.form-group {
margin-bottom: 15px;
}
.form-group label {
display: block;
margin-bottom: 5px;
color: var(--text-primary);
}
.form-group input,
.form-group select {
width: 100%;
padding: 8px;
background: var(--bg-secondary);
border: 1px solid var(--border-color);
border-radius: 4px;
color: var(--text-primary);
}
.modal-footer {
display: flex;
justify-content: flex-end;
gap: 10px;
margin-top: 20px;
}
.btn {
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
}
.btn-primary {
background: var(--primary-color);
color: white;
}
.btn-secondary {
background: var(--bg-secondary);
color: var(--text-primary);
border: 1px solid var(--border-color);
}
`;
document.head.appendChild(style);
// Add modal to page
document.body.appendChild(modal);
// Get elements
const closeBtn = modal.querySelector('.modal-close');
const cancelBtn = modal.querySelector('#cancelBtn');
const createBtn = modal.querySelector('#createBtn');
const nameInput = modal.querySelector('#serverName');
// Event handlers
const closeModal = () => {
document.body.removeChild(modal);
document.head.removeChild(style);
resolve(null);
};
const handleCreate = () => {
const name = nameInput.value.trim();
const type = modal.querySelector('#serverType').value;
const region = modal.querySelector('#serverRegion').value;
const maxPlayers = parseInt(modal.querySelector('#maxPlayers').value);
if (!name) {
nameInput.focus();
return;
}
if (maxPlayers < 1 || maxPlayers > 20) {
modal.querySelector('#maxPlayers').focus();
return;
}
document.body.removeChild(modal);
document.head.removeChild(style);
resolve({ name, type, region, maxPlayers });
};
closeBtn.addEventListener('click', closeModal);
cancelBtn.addEventListener('click', closeModal);
createBtn.addEventListener('click', handleCreate);
// Handle Enter key in name input
nameInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
handleCreate();
}
});
// Focus on name input
nameInput.focus();
});
}
async handleCreateServer() {
try {
// Get server details from user using custom dialog
const serverDetails = await this.showCreateServerDialog();
if (!serverDetails) {
return; // User cancelled
}
const { name, type, maxPlayers, region } = serverDetails;
// Show loading state
if (this.serverLoading) this.serverLoading.classList.remove('hidden');
const response = await fetch(`${this.apiBaseUrl}/servers/create`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${this.authToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
name,
type,
maxPlayers,
region,
username: this.currentUser.username
})
});
// Check if response is HTML (error page) instead of JSON
const contentType = response.headers.get('content-type');
if (!contentType || !contentType.includes('application/json')) {
throw new Error('API server is not available. Please check your connection.');
}
if (response.ok) {
const data = await response.json();
console.log('[LIVE MAIN MENU] Server created:', data.server);
// Refresh server list
await this.refreshServerList();
// Show success message
this.showLoginNotice('Server created successfully!', 'success');
// Auto-join the created server
this.joinServer(data.server.id);
} else {
const errorData = await response.json();
console.error('[LIVE MAIN MENU] Failed to create server:', errorData);
this.showLoginNotice(`Failed to create server: ${errorData.error}`, 'error');
}
} catch (error) {
console.error('[LIVE MAIN MENU] Create server error:', error);
this.showLoginNotice('Failed to create server', 'error');
} finally {
// Hide loading state
if (this.serverLoading) this.serverLoading.classList.add('hidden');
}
}
launchMultiplayerGame(server, serverData) {
console.log('[LIVE MAIN MENU] Launching multiplayer game on server:', server.name);
// Hide main menu and start game in multiplayer mode
this.hideLoadingScreenAndShowGame();
this.initializeMultiplayerGame(server, serverData);
}
hideLoadingScreenAndShowGame() {
console.log('[LIVE MAIN MENU] Hiding main menu and showing game interface');
// Hide main menu
if (this.mainMenu) {
this.mainMenu.classList.add('hidden');
}
// Show game interface
const gameInterface = document.getElementById('gameInterface');
if (gameInterface) {
gameInterface.classList.remove('hidden');
}
// Note: gameContainer element doesn't exist in HTML, so we skip it
}
initializeMultiplayerGame(server, serverData) {
console.log('[LIVE MAIN MENU] Initializing multiplayer game');
// Check if GameEngine is available, if not create a basic fallback
if (typeof GameEngine === 'undefined') {
console.warn('[LIVE MAIN MENU] GameEngine class not available, creating fallback');
// Create a basic fallback GameEngine class
window.GameEngine = class GameEngine extends EventTarget {
constructor() {
super();
this.systems = {};
this.gameTime = 0;
this.isRunning = false;
this.isFallback = true; // Mark as fallback for UIManager
console.log('[LIVE MAIN MENU] Fallback GameEngine created');
}
async init() {
console.log('[LIVE MAIN MENU] Fallback GameEngine init() called');
return true;
}
setMultiplayerMode(isMultiplayer, socket, serverData, currentUser) {
console.log('[LIVE MAIN MENU] Fallback GameEngine setMultiplayerMode called');
this.isMultiplayer = isMultiplayer;
this.socket = socket;
this.serverData = serverData;
this.currentUser = currentUser;
}
startGame(continueGame) {
console.log('[LIVE MAIN MENU] Fallback GameEngine startGame called, continueGame:', continueGame);
this.isRunning = true;
// Add simple return to menu functionality
window.returnToMainMenu = () => {
console.log('[LIVE MAIN MENU] Return to main menu requested');
if (window.liveMainMenu) {
window.liveMainMenu.showLoginSection();
}
};
}
getPerformanceStats() {
console.log('[LIVE MAIN MENU] Fallback GameEngine getPerformanceStats called');
return {
gameTime: this.gameTime,
isRunning: this.isRunning,
fps: 60,
memory: 'N/A (fallback mode)'
};
}
showNotification(message, type = 'info', duration = 3000) {
console.log(`[LIVE MAIN MENU] Fallback notification: ${message} (${type})`);
// Simple console notification for fallback mode
if (typeof console !== 'undefined') {
console.log(`📢 ${type.toUpperCase()}: ${message}`);
}
}
save() {
console.log('[LIVE MAIN MENU] Fallback GameEngine save called');
// Save will be handled by the main save logic when it detects local mode
return Promise.resolve({ success: true });
}
};
}
// Create GameEngine if it doesn't exist
if (!window.game) {
console.log('[LIVE MAIN MENU] Creating new GameEngine instance for multiplayer');
try {
window.game = new GameEngine();
// Initialize the game engine
window.game.init().then(() => {
console.log('[LIVE MAIN MENU] GameEngine initialized successfully for multiplayer');
// Apply pending save data if available (after GameEngine is ready)
if (window.liveMainMenu && window.liveMainMenu.pendingSaveData) {
console.log('[LIVE MAIN MENU] Applying pending save data after GameEngine init...');
try {
const saveData = window.liveMainMenu.pendingSaveData;
console.log('[LIVE MAIN MENU] Save data structure:', {
hasPlayer: !!saveData.player,
hasInventory: !!saveData.inventory,
hasEconomy: !!saveData.economy,
hasIdleSystem: !!saveData.idleSystem,
hasDungeonSystem: !!saveData.dungeonSystem,
hasSkillSystem: !!saveData.skillSystem,
hasBaseSystem: !!saveData.baseSystem,
hasQuestSystem: !!saveData.questSystem,
gameTime: saveData.gameTime,
playerLevel: saveData.player?.stats?.level
});
console.log('[LIVE MAIN MENU] Game systems available:', {
hasGame: !!window.game,
hasSystems: !!window.game?.systems,
hasPlayer: !!window.game?.systems?.player,
hasInventory: !!window.game?.systems?.inventory,
hasEconomy: !!window.game?.systems?.economy,
hasIdleSystem: !!window.game?.systems?.idleSystem,
hasDungeonSystem: !!window.game?.systems?.dungeonSystem,
hasSkillSystem: !!window.game?.systems?.skillSystem,
hasBaseSystem: !!window.game?.systems?.baseSystem,
hasQuestSystem: !!window.game?.systems?.questSystem
});
let appliedCount = 0;
// Apply save data to game systems
if (saveData.player && window.game.systems.player) {
window.game.systems.player.load(saveData.player);
console.log('[LIVE MAIN MENU] ✅ Player data loaded from pending save');
appliedCount++;
} else {
console.log('[LIVE MAIN MENU] ❌ Player data NOT loaded - saveData.player:', !!saveData.player, 'window.game.systems.player:', !!window.game?.systems?.player);
}
if (saveData.inventory && window.game.systems.inventory) {
window.game.systems.inventory.load(saveData.inventory);
console.log('[LIVE MAIN MENU] ✅ Inventory data loaded from pending save');
appliedCount++;
} else {
console.log('[LIVE MAIN MENU] ❌ Inventory data NOT loaded - saveData.inventory:', !!saveData.inventory, 'window.game.systems.inventory:', !!window.game?.systems?.inventory);
}
if (saveData.economy && window.game.systems.economy) {
window.game.systems.economy.load(saveData.economy);
console.log('[LIVE MAIN MENU] ✅ Economy data loaded from pending save');
appliedCount++;
} else {
console.log('[LIVE MAIN MENU] ❌ Economy data NOT loaded - saveData.economy:', !!saveData.economy, 'window.game.systems.economy:', !!window.game?.systems?.economy);
}
if (saveData.idleSystem && window.game.systems.idleSystem) {
window.game.systems.idleSystem.load(saveData.idleSystem);
console.log('[LIVE MAIN MENU] ✅ Idle system data loaded from pending save');
appliedCount++;
} else {
console.log('[LIVE MAIN MENU] ❌ Idle system data NOT loaded - saveData.idleSystem:', !!saveData.idleSystem, 'window.game.systems.idleSystem:', !!window.game?.systems?.idleSystem);
}
if (saveData.dungeonSystem && window.game.systems.dungeonSystem) {
window.game.systems.dungeonSystem.load(saveData.dungeonSystem);
console.log('[LIVE MAIN MENU] ✅ Dungeon system data loaded from pending save');
appliedCount++;
} else {
console.log('[LIVE MAIN MENU] ❌ Dungeon system data NOT loaded - saveData.dungeonSystem:', !!saveData.dungeonSystem, 'window.game.systems.dungeonSystem:', !!window.game?.systems?.dungeonSystem);
}
if (saveData.skillSystem && window.game.systems.skillSystem) {
window.game.systems.skillSystem.load(saveData.skillSystem);
console.log('[LIVE MAIN MENU] ✅ Skill system data loaded from pending save');
appliedCount++;
} else {
console.log('[LIVE MAIN MENU] ❌ Skill system data NOT loaded - saveData.skillSystem:', !!saveData.skillSystem, 'window.game.systems.skillSystem:', !!window.game?.systems?.skillSystem);
}
if (saveData.baseSystem && window.game.systems.baseSystem) {
window.game.systems.baseSystem.load(saveData.baseSystem);
console.log('[LIVE MAIN MENU] ✅ Base system data loaded from pending save');
appliedCount++;
} else {
console.log('[LIVE MAIN MENU] ❌ Base system data NOT loaded - saveData.baseSystem:', !!saveData.baseSystem, 'window.game.systems.baseSystem:', !!window.game?.systems?.baseSystem);
}
if (saveData.questSystem && window.game.systems.questSystem) {
window.game.systems.questSystem.load(saveData.questSystem);
console.log('[LIVE MAIN MENU] ✅ Quest system data loaded from pending save');
appliedCount++;
} else {
console.log('[LIVE MAIN MENU] ❌ Quest system data NOT loaded - saveData.questSystem:', !!saveData.questSystem, 'window.game.systems.questSystem:', !!window.game?.systems?.questSystem);
}
if (saveData.gameTime && window.game) {
window.game.gameTime = saveData.gameTime;
console.log('[LIVE MAIN MENU] ✅ Game time loaded from pending save:', saveData.gameTime);
appliedCount++;
} else {
console.log('[LIVE MAIN MENU] ❌ Game time NOT loaded - saveData.gameTime:', !!saveData.gameTime, 'window.game:', !!window.game);
}
console.log(`[LIVE MAIN MENU] Save data application complete: ${appliedCount}/9 systems loaded`);
if (appliedCount === 0) {
console.warn('[LIVE MAIN MENU] ⚠️ NO save data was applied! This is why the UI shows fresh game state.');
console.warn('[LIVE MAIN MENU] ⚠️ The fallback GameEngine does not have the required game systems.');
}
console.log('[LIVE MAIN MENU] Pending save data applied successfully');
// Clear pending save data
window.liveMainMenu.pendingSaveData = null;
} catch (error) {
console.error('[LIVE MAIN MENU] Error applying pending save data:', error);
}
} else {
console.log('[LIVE MAIN MENU] No pending save data to apply');
}
// Now initialize multiplayer mode through GameInitializer
if (window.gameInitializer) {
window.gameInitializer.initializeMultiplayer(server, serverData, this.authToken, this.currentUser);
}
}).catch(error => {
console.error('[LIVE MAIN MENU] Failed to initialize GameEngine for multiplayer:', error);
this.showLoginNotice('Failed to initialize game: ' + error.message, 'error');
});
} catch (error) {
console.error('[LIVE MAIN MENU] Error creating GameEngine:', error);
this.showLoginNotice('Failed to create game engine: ' + error.message, 'error');
}
} else {
console.log('[LIVE MAIN MENU] GameEngine already exists, initializing multiplayer mode');
// Initialize multiplayer mode through GameInitializer
if (window.gameInitializer) {
window.gameInitializer.initializeMultiplayer(server, serverData, this.authToken, this.currentUser);
}
}
}
async startLocalServer() {
console.log('[LIVE MAIN MENU] Starting local server...');
try {
if (!window.localServerManager) {
console.error('[LIVE MAIN MENU] LocalServerManager not available');
this.showLoginNotice('Local server manager not available', 'error');
return;
}
// Update button to show loading state
if (this.createServerBtn) {
this.createServerBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Starting...';
this.createServerBtn.disabled = true;
}
// Show loading message
this.showLoginNotice('Starting local server...', 'info');
const result = await window.localServerManager.startServer();
if (result.success) {
console.log('[LIVE MAIN MENU] Local server started successfully:', result);
// Update to local mode
this.isLocalMode = true;
this.apiBaseUrl = `http://localhost:${result.port}/api`;
this.gameServerUrl = `http://localhost:${result.port}`;
console.log(`[LIVE MAIN MENU] Updated to local mode - API: ${this.apiBaseUrl}`);
// Update button to show running state
if (this.createServerBtn) {
this.createServerBtn.innerHTML = '<i class="fas fa-check"></i> Local Server Running';
this.createServerBtn.classList.remove('btn-primary');
this.createServerBtn.classList.add('btn-success');
}
// Auto-login for local mode
await this.autoLoginLocalMode();
// Show success message
this.showLoginNotice('Local server started successfully!', 'success');
} else {
console.error('[LIVE MAIN MENU] Failed to start local server:', result.error);
this.showLoginNotice(`Failed to start local server: ${result.error}`, 'error');
// Reset button to original state
if (this.createServerBtn) {
this.createServerBtn.innerHTML = '<i class="fas fa-server"></i> Start Local Server';
this.createServerBtn.disabled = false;
}
}
} catch (error) {
console.error('[LIVE MAIN MENU] Error starting local server:', error);
this.showLoginNotice('Error starting local server', 'error');
// Reset button to original state
if (this.createServerBtn) {
this.createServerBtn.innerHTML = '<i class="fas fa-server"></i> Start Local Server';
this.createServerBtn.disabled = false;
}
}
}
checkForLocalServer() {
console.log('[LIVE MAIN MENU] Checking for local server...');
// Check if local server manager is available and running
if (window.localServerManager) {
const serverStatus = window.localServerManager.getStatus();
if (serverStatus.isRunning) {
console.log('[LIVE MAIN MENU] Local server detected, switching to local mode');
this.isLocalMode = true;
this.apiBaseUrl = `http://localhost:${serverStatus.port}/api`;
this.gameServerUrl = `http://localhost:${serverStatus.port}`;
console.log(`[LIVE MAIN MENU] Updated API URL to: ${this.apiBaseUrl}`);
console.log(`[LIVE MAIN MENU] Updated Game Server URL to: ${this.gameServerUrl}`);
// Update login notice to show local mode
this.showLoginNotice('Local server active - Singleplayer mode available', 'info');
// Auto-login for local mode
this.autoLoginLocalMode();
} else {
console.log('[LIVE MAIN MENU] Local server not running, using remote servers');
}
} else {
console.log('[LIVE MAIN MENU] LocalServerManager not available');
}
}
async autoLoginLocalMode() {
console.log('[LIVE MAIN MENU] Auto-logging in to local mode...');
try {
let response;
// Use SimpleLocalServer mock API if available
if (window.localServerManager && window.localServerManager.localServer && window.localServerManager.localServer.mockRequest) {
console.log('[LIVE MAIN MENU] Using SimpleLocalServer mock API');
response = await window.localServerManager.localServer.mockRequest('POST', '/api/auth/login', {
email: 'local@player.com',
password: 'local'
});
} else {
// Fallback to real fetch
console.log('[LIVE MAIN MENU] Using real fetch for local login');
response = await fetch(`${this.apiBaseUrl}/auth/login`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
email: 'local@player.com',
password: 'local'
})
});
}
const data = await response.json();
if (data.success) {
console.log('[LIVE MAIN MENU] Local mode login successful');
this.authToken = data.token;
this.currentUser = data.user;
// Update UI
this.hideLoginNotice();
this.showServerSection(false, false); // Show section without refreshing or animating
// Load servers (will show local server)
this.refreshServerList();
} else {
console.error('[LIVE MAIN MENU] Local mode login failed:', data);
}
} catch (error) {
console.error('[LIVE MAIN MENU] Error in local mode auto-login:', error);
}
}
}
// Initialize the live main menu when DOM is ready
function initializeLiveMainMenu() {
console.log('[LIVE MAIN MENU] Initializing LiveMainMenu...');
// Wait a bit for DOM to be fully ready
setTimeout(() => {
if (!window.liveMainMenu) {
console.log('[LIVE MAIN MENU] Creating new LiveMainMenu instance');
window.liveMainMenu = new LiveMainMenu();
} else {
console.log('[LIVE MAIN MENU] LiveMainMenu already exists');
}
}, 100);
}
// Try multiple initialization methods
if (document.readyState === 'loading') {
// DOM is still loading, wait for DOMContentLoaded
document.addEventListener('DOMContentLoaded', initializeLiveMainMenu);
} else {
// DOM is already ready, initialize immediately
initializeLiveMainMenu();
}
// Also try initialization as a fallback
setTimeout(() => {
if (!window.liveMainMenu) {
console.warn('[LIVE MAIN MENU] Fallback initialization triggered');
window.liveMainMenu = new LiveMainMenu();
}
}, 1000);
// Export for use in other scripts
if (typeof module !== 'undefined' && module.exports) {
module.exports = LiveMainMenu;
}