/** * Main Menu System * Handles the main menu UI, save management, and game initialization */ console.log('[MAIN MENU] MainMenu.js script loaded'); class MainMenu { constructor() { console.log('[MAIN MENU] Constructor called'); this.mainMenu = document.getElementById('mainMenu'); this.selectedSlot = null; this.saveSlotsCount = 3; // Number of save slots console.log('[MAIN MENU] Initializing elements'); this.initializeElements(); console.log('[MAIN MENU] Initializing event listeners'); this.bindEvents(); // Delay save folder verification until electronAPI is available this.initializeAfterElectronAPI(); } initializeAfterElectronAPI() { // Check if electronAPI is available, if not wait for it if (window.electronAPI) { console.log('[MAIN MENU] electronAPI available, proceeding with initialization'); this.completeInitialization(); } else { console.log('[MAIN MENU] electronAPI not available, waiting...'); // Wait for electronAPI to be available setTimeout(() => { this.initializeAfterElectronAPI(); }, 100); } } async completeInitialization() { console.log('[MAIN MENU] Starting save folder verification'); this.verifySaveFolders(); console.log('[MAIN MENU] Loading save data'); await this.loadSaveData(); console.log('[MAIN MENU] Updating save slots'); this.updateSaveSlots(); console.log('[MAIN MENU] Constructor completed'); } verifySaveFolders() { console.log('[MAIN MENU] Verifying save folders...'); console.log('[MAIN MENU] window.electronAPI available:', !!window.electronAPI); try { // Use Electron's IPC instead of direct Node.js access if (window.electronAPI) { console.log('[MAIN MENU] Using Electron API for folder operations'); console.log('[MAIN MENU] createSaveFolders method available:', typeof window.electronAPI.createSaveFolders); // Request folder creation through main process window.electronAPI.createSaveFolders(this.saveSlotsCount).then((result) => { console.log('[MAIN MENU] createSaveFolders response:', result); if (result.success) { console.log('[MAIN MENU] Save folders created successfully:', result.paths); this.savePaths = result.paths; } else { console.error('[MAIN MENU] Failed to create save folders:', result.error); console.log('[MAIN MENU] Falling back to localStorage save system'); this.useLocalStorageFallback(); } }).catch((error) => { console.error('[MAIN MENU] IPC error:', error); console.log('[MAIN MENU] Falling back to localStorage save system'); this.useLocalStorageFallback(); }); } else { console.log('[MAIN MENU] Electron API not available, using localStorage fallback'); this.useLocalStorageFallback(); } } catch (error) { console.error('[MAIN MENU] Error verifying save folders:', error); console.log('[MAIN MENU] Falling back to localStorage save system'); this.useLocalStorageFallback(); } } hideLoadingScreenAndShowGame() { console.log('[MAIN MENU] Hiding loading screen and showing game interface'); // Hide loading screen const loadingScreen = document.getElementById('loadingScreen'); if (loadingScreen) { loadingScreen.classList.add('hidden'); console.log('[MAIN MENU] Loading screen hidden'); } // Show game interface const gameInterface = document.getElementById('gameInterface'); if (gameInterface) { gameInterface.classList.remove('hidden'); console.log('[MAIN MENU] Game interface shown'); } // Show game container const gameContainer = document.getElementById('gameContainer'); if (gameContainer) { gameContainer.classList.remove('hidden'); console.log('[MAIN MENU] Game container shown'); } } useLocalStorageFallback() { // Fallback for browser testing or when Electron API fails for (let i = 1; i <= this.saveSlotsCount; i++) { const saveKey = `gso_save_slot_${i}`; if (!localStorage.getItem(saveKey)) { const saveInfo = { slot: i, created: new Date().toISOString(), version: '1.0.0', exists: false }; localStorage.setItem(saveKey, JSON.stringify(saveInfo)); console.log(`[MAIN MENU] Created localStorage save info for slot ${i}`); } } // Mark that we're using localStorage this.savePaths = null; console.log('[MAIN MENU] Using localStorage save system'); } initializeElements() { // Menu sections this.mainMenu = document.getElementById('mainMenu'); this.loginSection = document.getElementById('loginSection'); this.saveSection = document.getElementById('saveSection'); this.optionsSection = document.getElementById('optionsSection'); // Login buttons this.ssoLoginBtn = document.getElementById('ssoLoginBtn'); this.offlineBtn = document.getElementById('offlineBtn'); // Save selection this.saveSlots = document.querySelectorAll('.save-slot'); this.backToLoginBtn = document.getElementById('backToLoginBtn'); // Save confirmation section this.confirmSection = document.getElementById('confirmSection'); this.confirmSlotTitle = document.getElementById('confirmSlotTitle'); this.confirmSaveDetails = document.getElementById('confirmSaveDetails'); this.confirmLevel = document.getElementById('confirmLevel'); this.confirmShip = document.getElementById('confirmShip'); this.confirmPlayTime = document.getElementById('confirmPlayTime'); this.confirmLastPlayed = document.getElementById('confirmLastPlayed'); this.confirmNewGameBtn = document.getElementById('confirmNewGameBtn'); this.confirmContinueBtn = document.getElementById('confirmContinueBtn'); this.confirmDeleteBtn = document.getElementById('confirmDeleteBtn'); console.log('[MAIN MENU] confirmDeleteBtn found:', !!this.confirmDeleteBtn); this.confirmSettingsBtn = document.getElementById('confirmSettingsBtn'); this.backToSavesFromConfirmBtn = document.getElementById('backToSavesFromConfirmBtn'); // Game options this.newGameBtn = document.getElementById('newGameBtn'); this.continueBtn = document.getElementById('continueBtn'); this.deleteSaveBtn = document.getElementById('deleteSaveBtn'); console.log('[MAIN MENU] deleteSaveBtn found:', !!this.deleteSaveBtn); this.settingsBtn = document.getElementById('settingsBtn'); this.quitBtn = document.getElementById('quitBtn'); this.backToSavesBtn = document.getElementById('backToSavesBtn'); // Footer links this.aboutBtn = document.getElementById('aboutBtn'); this.helpBtn = document.getElementById('helpBtn'); } bindEvents() { // Login events this.ssoLoginBtn?.addEventListener('click', () => this.handleSSOLogin()); this.offlineBtn?.addEventListener('click', () => this.handleOfflineLogin()); // Save selection events this.saveSlots.forEach(slot => { slot.addEventListener('click', () => this.selectSaveSlot(slot)); }); this.backToLoginBtn?.addEventListener('click', () => this.showLoginSection()); // Save confirmation events this.confirmNewGameBtn?.addEventListener('click', () => this.startNewGame()); this.confirmContinueBtn?.addEventListener('click', () => this.continueGame()); this.confirmDeleteBtn?.addEventListener('click', () => { console.log('[MAIN MENU] Delete button clicked'); this.deleteSave(); }); this.confirmSettingsBtn?.addEventListener('click', () => this.showSettings()); this.backToSavesFromConfirmBtn?.addEventListener('click', () => this.showSaveSection()); // Game options events this.newGameBtn?.addEventListener('click', () => this.startNewGame()); this.continueBtn?.addEventListener('click', () => this.continueGame()); this.deleteSaveBtn?.addEventListener('click', () => { console.log('[MAIN MENU] Delete save button clicked'); this.deleteSave(); }); this.settingsBtn?.addEventListener('click', () => this.showSettings()); this.quitBtn?.addEventListener('click', () => this.quitGame()); this.backToSavesBtn?.addEventListener('click', async () => this.showSaveSection()); // Footer events this.aboutBtn?.addEventListener('click', () => this.showAbout()); this.helpBtn?.addEventListener('click', () => this.showHelp()); } showSection(sectionName) { // Hide all sections this.loginSection?.classList.add('hidden'); this.saveSection?.classList.add('hidden'); this.confirmSection?.classList.add('hidden'); this.optionsSection?.classList.add('hidden'); // Show selected section const section = document.getElementById(`${sectionName}Section`); if (section) { section.classList.remove('hidden'); this.currentSection = sectionName; } } showLoginSection() { this.showSection('login'); } async showSaveSection() { this.showSection('save'); await this.loadSaveData(); this.updateSaveSlots(); } showConfirmSection() { this.showSection('confirm'); // Clear any previous data to prevent contamination this.clearConfirmSectionData(); this.updateConfirmSection(); } updateConfirmSection() { const hasSave = this.selectedSlot && this.saveData[this.selectedSlot]; // Update slot title this.confirmSlotTitle.textContent = `Slot ${this.selectedSlot}`; if (hasSave) { const saveData = this.saveData[this.selectedSlot]; const player = saveData.player || {}; // Update save details const playerLevel = player.stats?.level || player.level || 1; // Get ship name from multiple possible fields const shipName = player.shipName || player.ship?.name || player.currentShip || player.selectedShip || saveData.shipName || saveData.currentShip || 'Unknown Ship'; // Calculate play time - check multiple possible fields and format properly const playTime = player.playTime || player.stats?.playTime || saveData.playTime || saveData.totalPlayTime || 0; // Handle different time formats (seconds, milliseconds, or already formatted) let totalSeconds = 0; if (typeof playTime === 'number') { // If it's a large number, it might be milliseconds if (playTime > 3600) { totalSeconds = Math.floor(playTime / 1000); } else { totalSeconds = Math.floor(playTime); } } else if (typeof playTime === 'string') { // If it's already formatted, use it as-is const playTimeText = playTime; } else { totalSeconds = 0; } // Format time as dd.hh.nn.ss const days = Math.floor(totalSeconds / 86400); const hours = Math.floor((totalSeconds % 86400) / 3600); const minutes = Math.floor((totalSeconds % 3600) / 60); const seconds = totalSeconds % 60; let playTimeText = ''; if (days > 0) { playTimeText = `${days}d ${hours}h ${minutes}m ${seconds}s`; } else if (hours > 0) { playTimeText = `${hours}h ${minutes}m ${seconds}s`; } else if (minutes > 0) { playTimeText = `${minutes}m ${seconds}s`; } else { playTimeText = `${seconds}s`; } // Last played - check multiple possible date fields const lastPlayed = saveData.lastPlayed || saveData.lastSave || saveData.timestamp || saveData.date || new Date(); const formattedDate = lastPlayed instanceof Date ? lastPlayed.toLocaleDateString() : (typeof lastPlayed === 'string' ? lastPlayed : new Date(lastPlayed).toLocaleDateString()); const lastPlayedText = formattedDate === new Date().toLocaleDateString() ? 'Today' : formattedDate; // Restore textContent properties this.confirmLevel.textContent = playerLevel; this.confirmShip.textContent = shipName; this.confirmPlayTime.textContent = playTimeText; this.confirmLastPlayed.textContent = lastPlayedText; // Format save info with consistent alignment and labels const saveInfoText = `Level: ${playerLevel}\n\nShip: ${shipName}\n\nPlay Time: ${playTimeText}\n\nLast Played: ${lastPlayedText}`; // Update save details with pre-formatted text for alignment this.confirmSaveDetails.innerHTML = `
${saveInfoText}`;
// Has save data - always show Continue, hide New Game
this.confirmContinueBtn.style.display = 'block';
this.confirmContinueBtn.disabled = false;
this.confirmDeleteBtn.style.display = 'block';
this.confirmDeleteBtn.disabled = false;
this.confirmSettingsBtn.style.display = 'block';
this.confirmSettingsBtn.disabled = false;
this.confirmNewGameBtn.style.display = 'none';
this.confirmNewGameBtn.disabled = true;
} else {
// No save data - show new game option
this.confirmLevel.textContent = 'New';
this.confirmShip.textContent = 'New Adventure';
this.confirmPlayTime.textContent = '0h 0m';
this.confirmLastPlayed.textContent = 'Never';
// Show new game and settings buttons, hide continue and delete
this.confirmNewGameBtn.style.display = 'block';
this.confirmNewGameBtn.disabled = false;
this.confirmSettingsBtn.style.display = 'block';
this.confirmSettingsBtn.disabled = false;
this.confirmContinueBtn.style.display = 'none';
this.confirmDeleteBtn.style.display = 'none';
}
}
showOptionsSection() {
this.showSection('options');
// Clear any previous data to prevent contamination
this.clearOptionsSectionData();
this.updateGameOptions();
}
clearConfirmSectionData() {
// Clear individual text content elements
if (this.confirmLevel) this.confirmLevel.textContent = '';
if (this.confirmShip) this.confirmShip.textContent = '';
if (this.confirmPlayTime) this.confirmPlayTime.textContent = '';
if (this.confirmLastPlayed) this.confirmLastPlayed.textContent = '';
// Clear the save details display
if (this.confirmSaveDetails) {
this.confirmSaveDetails.innerHTML = '';
}
}
clearOptionsSectionData() {
// Clear the save info details display
const saveInfoDetails = document.getElementById('saveInfoDetails');
if (saveInfoDetails) {
saveInfoDetails.innerHTML = '';
}
}
handleSSOLogin() {
// Placeholder for SSO login
console.log('[MAIN MENU] SSO login requested (placeholder)');
// Show loading state
this.ssoLoginBtn.disabled = true;
this.ssoLoginBtn.innerHTML = ' Connecting...';
// Simulate SSO login (will be implemented later)
setTimeout(() => {
this.isLoggedIn = true;
this.ssoLoginBtn.innerHTML = ' Logged In';
this.ssoLoginBtn.disabled = false;
// Show save selection
this.showSaveSection();
}, 1500);
}
handleOfflineLogin() {
console.log('[MAIN MENU] Login successful');
this.isLoggedIn = true;
// Show save selection
setTimeout(async () => {
await this.showSaveSection();
}, 1500);
}
async loadSaveData() {
// Initialize saveData object
this.saveData = {};
console.log('[MAIN MENU] Loading save data from file system');
// Check if we have save paths and should use file system
if (this.savePaths && window.electronAPI) {
console.log('[MAIN MENU] Using file system for save data');
// Load save data from file system using the load-game API
for (let i = 1; i <= 3; i++) {
try {
console.log(`[MAIN MENU] Loading save file for slot ${i}`);
const result = await window.electronAPI.loadGame(i);
if (result.success && result.data) {
this.saveData[i] = result.data;
console.log(`[MAIN MENU] Slot ${i} loaded successfully:`, this.saveData[i]);
} else {
console.log(`[MAIN MENU] Slot ${i} has no save file or failed to load:`, result.error);
this.saveData[i] = null;
}
} catch (error) {
console.error(`[MAIN MENU] Error reading save slot ${i}:`, error);
this.saveData[i] = null;
}
}
} else {
console.log('[MAIN MENU] Using localStorage fallback for save data');
console.log('[MAIN MENU] Available localStorage keys:', Object.keys(localStorage));
// Fallback to localStorage
for (let i = 1; i <= 3; i++) {
const saveKey = `gso_save_slot_${i}`;
const saveData = localStorage.getItem(saveKey);
console.log(`[MAIN MENU] Slot ${i} key: ${saveKey}, data:`, saveData);
if (saveData) {
try {
this.saveData[i] = JSON.parse(saveData);
console.log(`[MAIN MENU] Slot ${i} parsed successfully:`, this.saveData[i]);
} catch (error) {
console.error(`[MAIN MENU] Error loading save slot ${i}:`, error);
this.saveData[i] = null;
}
} else {
console.log(`[MAIN MENU] Slot ${i} has no data`);
this.saveData[i] = null;
}
}
}
console.log('[MAIN MENU] Final saveData object:', this.saveData);
}
updateSaveSlots() {
console.log('[MAIN MENU] Updating save slots UI');
console.log('[MAIN MENU] this.saveSlots:', this.saveSlots);
console.log('[MAIN MENU] this.saveData:', this.saveData);
// Check if saveSlots elements exist
if (!this.saveSlots || this.saveSlots.length === 0) {
console.log('[MAIN MENU] Save slots not found, re-initializing elements');
this.saveSlots = document.querySelectorAll('.save-slot');
console.log('[MAIN MENU] Re-initialized saveSlots:', this.saveSlots);
}
if (!this.saveSlots || this.saveSlots.length === 0) {
console.error('[MAIN MENU] Could not find save slot elements in DOM');
return;
}
this.saveSlots.forEach((slot, index) => {
const slotNumber = index + 1;
const saveInfo = this.saveData[slotNumber];
console.log(`[MAIN MENU] Processing slot ${slotNumber}, saveInfo:`, saveInfo);
const slotStatus = slot.querySelector('.slot-status');
const slotName = slot.querySelector('.slot-name');
const slotDetails = slot.querySelector('.slot-details');
const slotBtn = slot.querySelector('.slot-btn');
console.log(`[MAIN MENU] Found elements for slot ${slotNumber}:`, {
slotStatus: !!slotStatus,
slotName: !!slotName,
slotDetails: !!slotDetails,
slotBtn: !!slotBtn
});
// Check if this is a valid save game (has actual game data)
console.log(`[MAIN MENU] Validating save data for slot ${slotNumber}:`, {
saveInfo: saveInfo,
hasSaveInfo: !!saveInfo,
hasPlayer: !!(saveInfo && saveInfo.player),
hasPlayerStats: !!(saveInfo && saveInfo.player && saveInfo.player.stats),
playerLevel: saveInfo && saveInfo.player && saveInfo.player.stats ? saveInfo.player.stats.level : 'no player.stats',
playerKeys: saveInfo && saveInfo.player ? Object.keys(saveInfo.player) : 'no player'
});
const hasValidSave = saveInfo && saveInfo.player && saveInfo.player.stats && saveInfo.player.stats.level !== undefined;
if (hasValidSave) {
// Update with existing save data
console.log(`[MAIN MENU] Slot ${slotNumber} has valid save data - showing Load`);
if (slotStatus) {
slotStatus.textContent = 'Level ' + (saveInfo.player.stats.level || 1);
slotStatus.className = 'slot-status has-data';
}
if (slotName) {
slotName.textContent = saveInfo.player.info.name || 'Commander';
}
// Format playtime and last played
const playTime = saveInfo.player.stats.playTime || saveInfo.player.playTime || 0;
// Handle different time formats (seconds, milliseconds, or already formatted)
let totalSeconds = 0;
if (typeof playTime === 'number') {
// If it's a large number, it might be milliseconds
if (playTime > 3600) {
totalSeconds = Math.floor(playTime / 1000);
} else {
totalSeconds = Math.floor(playTime);
}
} else if (typeof playTime === 'string') {
// If it's already formatted, use it as-is
var playtime = playTime;
} else {
totalSeconds = 0;
}
// Only format if we haven't already set playtime from a string
if (typeof playtime !== 'string') {
// Format time as dd.hh.nn.ss
const days = Math.floor(totalSeconds / 86400);
const hours = Math.floor((totalSeconds % 86400) / 3600);
const minutes = Math.floor((totalSeconds % 3600) / 60);
const seconds = totalSeconds % 60;
if (days > 0) {
playtime = `${days}d ${hours}h ${minutes}m ${seconds}s`;
} else if (hours > 0) {
playtime = `${hours}h ${minutes}m ${seconds}s`;
} else if (minutes > 0) {
playtime = `${minutes}m ${seconds}s`;
} else {
playtime = `${seconds}s`;
}
}
const lastSaved = saveInfo.lastSave ? new Date(saveInfo.lastSave).toLocaleDateString() : 'Unknown';
if (slotDetails) {
slotDetails.textContent = `Playtime: ${playtime} | Saved: ${lastSaved}`;
}
if (slotBtn) {
slotBtn.textContent = 'Select';
slotBtn.onclick = () => this.selectSaveSlot(slot);
}
} else {
// Empty slot or invalid/placeholder save
console.log(`[MAIN MENU] Slot ${slotNumber} is empty or invalid - showing New Game`);
if (slotStatus) {
slotStatus.textContent = 'Empty';
slotStatus.className = 'slot-status empty';
}
if (slotName) {
slotName.textContent = 'New Game';
}
if (slotDetails) {
slotDetails.textContent = 'Start a new adventure';
}
if (slotBtn) {
slotBtn.textContent = 'Select';
slotBtn.onclick = () => this.selectSaveSlot(slot);
}
}
});
}
selectSaveSlot(slot) {
const slotNumber = parseInt(slot.dataset.slot);
this.selectedSlot = slotNumber;
console.log(`[MAIN MENU] Save slot ${slotNumber} selected`);
// Show confirmation section
this.showConfirmSection();
}
updateGameOptions() {
const hasSave = this.selectedSlot && this.saveData[this.selectedSlot];
// Update button states based on save data
if (hasSave) {
// Has save data - always show Continue, hide New Game
this.continueBtn.style.display = 'block';
this.continueBtn.disabled = false;
this.continueBtn.innerHTML = ' Continue';
this.newGameBtn.style.display = 'none';
this.deleteSaveBtn.style.display = 'block';
this.deleteSaveBtn.disabled = false;
this.deleteSaveBtn.innerHTML = ' Delete Save';
// Update center information display
this.updateSaveInfoDisplay();
} else {
// Show new game button, hide continue button
this.newGameBtn.style.display = 'block';
this.newGameBtn.disabled = false;
this.newGameBtn.innerHTML = ' Start New Game';
this.continueBtn.style.display = 'none';
this.deleteSaveBtn.style.display = 'none';
// Update center information display for empty slot
this.updateSaveInfoDisplay();
}
}
updateSaveInfoDisplay() {
const saveInfoDetails = document.getElementById('saveInfoDetails');
if (!saveInfoDetails) return;
const hasSave = this.selectedSlot && this.saveData[this.selectedSlot];
if (hasSave) {
const saveData = this.saveData[this.selectedSlot];
const player = saveData.player || {};
// Format save information
const level = player.stats?.level || player.level || 1;
const name = player.info ? player.info.name : 'Commander';
const ship = player.shipName || 'Unknown Ship';
// Calculate play time - check multiple possible fields and format properly
const playTime = player.playTime || player.stats?.playTime || saveData.playTime || saveData.totalPlayTime || 0;
// Handle different time formats (seconds, milliseconds, or already formatted)
let totalSeconds = 0;
if (typeof playTime === 'number') {
// If it's a large number, it might be milliseconds
if (playTime > 3600) {
totalSeconds = Math.floor(playTime / 1000);
} else {
totalSeconds = Math.floor(playTime);
}
} else if (typeof playTime === 'string') {
// If it's already formatted, use it as-is
var playtime = playTime;
} else {
totalSeconds = 0;
}
// Only format if we haven't already set playtime from a string
if (typeof playtime !== 'string') {
// Format time as dd.hh.nn.ss
const days = Math.floor(totalSeconds / 86400);
const hours = Math.floor((totalSeconds % 86400) / 3600);
const minutes = Math.floor((totalSeconds % 3600) / 60);
const seconds = totalSeconds % 60;
if (days > 0) {
playtime = `${days}d ${hours}h ${minutes}m ${seconds}s`;
} else if (hours > 0) {
playtime = `${hours}h ${minutes}m ${seconds}s`;
} else if (minutes > 0) {
playtime = `${minutes}m ${seconds}s`;
} else {
playtime = `${seconds}s`;
}
}
// Check multiple possible date fields and use current date if none found
const lastSaved = saveData.lastPlayed || saveData.lastSave || saveData.timestamp || new Date().toLocaleDateString();
const formattedDate = lastSaved === new Date().toLocaleDateString() ? 'Today' :
(typeof lastSaved === 'string' ? lastSaved : new Date(lastSaved).toLocaleDateString());
// Format save info with consistent alignment and labels
const saveInfoText = `Level: ${level}\n\nShip: ${ship}\n\nPlay Time: ${playtime}\n\nLast Played: ${formattedDate}`;
// Update save details with pre-formatted text for alignment
saveInfoDetails.innerHTML = `${saveInfoText}`;
} else {
saveInfoDetails.innerHTML = `
Slot: ${this.selectedSlot || 'None'}
Status: Empty
Ready for new adventure