import { gameStateManager } from "./core/GameStateManager.js"; /** @type {HTMLElement | null} */ const gameViewport = document.querySelector("game-viewport"); /** @type {HTMLElement | null} */ const teamBuilder = document.querySelector("team-builder"); /** @type {HTMLElement | null} */ const mainMenu = document.getElementById("main-menu"); /** @type {HTMLElement | null} */ const hubScreen = document.querySelector("hub-screen"); /** @type {HTMLElement | null} */ const btnNewRun = document.getElementById("btn-start"); /** @type {HTMLElement | null} */ const btnContinue = document.getElementById("btn-load"); /** @type {HTMLElement | null} */ const loadingOverlay = document.getElementById("loading-overlay"); /** @type {HTMLElement | null} */ const loadingMessage = document.getElementById("loading-message"); /** @type {HTMLElement | null} */ const uiLayer = document.getElementById("ui-layer"); // --- Event Listeners --- // Character Sheet Integration let currentCharacterSheet = null; window.addEventListener("open-character-sheet", async (e) => { let { unit, unitId, readOnly = false, inventory = [] } = e.detail; // Resolve unit from ID if needed if (!unit && unitId && gameStateManager.gameLoop?.unitManager) { unit = gameStateManager.gameLoop.unitManager.getUnitById(unitId); } if (!unit) { console.warn("open-character-sheet event missing unit or unitId"); return; } // If character sheet is already open, close it (toggle behavior) if (currentCharacterSheet) { currentCharacterSheet.remove(); currentCharacterSheet = null; // Resume GameLoop if it was paused if (gameStateManager.gameLoop && gameStateManager.gameLoop.isPaused) { gameStateManager.gameLoop.resume(); } return; } // Pause GameLoop if in combat let wasPaused = false; if ( gameStateManager.gameLoop && gameStateManager.currentState === "STATE_COMBAT" ) { wasPaused = gameStateManager.gameLoop.isPaused; if (!wasPaused && gameStateManager.gameLoop.isRunning) { gameStateManager.gameLoop.pause(); } } // Dynamically import CharacterSheet component const { CharacterSheet } = await import("./ui/components/character-sheet.js"); // Generate skill tree using SkillTreeFactory if available let skillTree = null; if (gameStateManager.gameLoop?.classRegistry && unit.activeClassId) { try { const { SkillTreeFactory } = await import( "./factories/SkillTreeFactory.js" ); // Load skill tree template const templateResponse = await fetch( "assets/data/skill_trees/template_standard_30.json" ); if (templateResponse.ok) { const template = await templateResponse.json(); const templateRegistry = { [template.id]: template }; // Get class definition const classDef = gameStateManager.gameLoop.classRegistry.get( unit.activeClassId ); if (classDef && classDef.skillTreeData) { // Get skill registry - import it directly const { skillRegistry } = await import("./managers/SkillRegistry.js"); // Convert Map to object for SkillTreeFactory const skillMap = Object.fromEntries(skillRegistry.skills); // Create factory and generate tree const factory = new SkillTreeFactory(templateRegistry, skillMap); skillTree = factory.createTree(classDef); } } } catch (error) { console.warn( "Failed to load skill tree template, using fallback:", error ); } } // Create character sheet const characterSheet = document.createElement("character-sheet"); characterSheet.unit = unit; characterSheet.readOnly = readOnly; characterSheet.inventory = inventory; characterSheet.gameMode = gameStateManager.currentState === "STATE_COMBAT" ? "DUNGEON" : "HUB"; characterSheet.treeDef = skillTree; // Pass generated tree // Pass inventoryManager from gameLoop if available if (gameStateManager.gameLoop?.inventoryManager) { characterSheet.inventoryManager = gameStateManager.gameLoop.inventoryManager; } // Handle close event const handleClose = () => { characterSheet.remove(); currentCharacterSheet = null; // Resume GameLoop if it was paused if ( !wasPaused && gameStateManager.gameLoop && gameStateManager.gameLoop.isPaused ) { gameStateManager.gameLoop.resume(); } }; // Handle equip-item event (update unit equipment) const handleEquipItem = (event) => { const { unitId, slot, item, oldItem } = event.detail; // Equipment is already updated in the component // This event can be used for persistence or other side effects console.log(`Equipped ${item.name} to ${slot} slot for unit ${unitId}`); }; // Handle unlock-request event from SkillTreeUI const handleUnlockRequest = (event) => { const { nodeId, cost } = event.detail; const mastery = unit.classMastery?.[unit.activeClassId]; if (!mastery) { console.warn("No mastery data found for unit"); return; } if (mastery.skillPoints < cost) { console.warn("Insufficient skill points"); return; } // Deduct skill points and unlock node mastery.skillPoints -= cost; if (!mastery.unlockedNodes) { mastery.unlockedNodes = []; } if (!mastery.unlockedNodes.includes(nodeId)) { mastery.unlockedNodes.push(nodeId); } // Trigger update in character sheet characterSheet.requestUpdate(); console.log(`Unlocked node ${nodeId} for ${cost} skill points`); }; // Handle skill-unlocked event to refresh combat HUD const handleSkillUnlocked = (event) => { const { unitId } = event.detail; // If we're in combat and the gameLoop exists, update combat state to refresh the HUD if ( gameStateManager?.gameLoop && gameStateManager.currentState === "STATE_COMBAT" ) { // Update combat state to refresh the HUD with newly unlocked skills gameStateManager.gameLoop.updateCombatState().catch(console.error); } }; characterSheet.addEventListener("close", handleClose); characterSheet.addEventListener("equip-item", handleEquipItem); characterSheet.addEventListener("unlock-request", handleUnlockRequest); characterSheet.addEventListener("skill-unlocked", handleSkillUnlocked); // Append to document body - dialog will handle its own display document.body.appendChild(characterSheet); currentCharacterSheet = characterSheet; }); window.addEventListener("gamestate-changed", async (e) => { const { newState } = e.detail; console.log("gamestate-changed", newState); switch (newState) { case "STATE_MAIN_MENU": // Check if we should show hub or main menu const hasRoster = gameStateManager.rosterManager.roster.length > 0; const hasCompletedMissions = gameStateManager.missionManager.completedMissions.size > 0; const shouldShowHub = hasRoster || hasCompletedMissions; loadingMessage.textContent = shouldShowHub ? "INITIALIZING HUB..." : "INITIALIZING MAIN MENU..."; break; case "STATE_TEAM_BUILDER": loadingMessage.textContent = "INITIALIZING TEAM BUILDER..."; break; case "STATE_DEPLOYMENT": case "STATE_COMBAT": import("./ui/game-viewport.js"); loadingMessage.textContent = "INITIALIZING GAME ENGINE..."; break; } loadingOverlay.toggleAttribute("hidden", false); mainMenu.toggleAttribute("hidden", true); gameViewport.toggleAttribute("hidden", true); teamBuilder.toggleAttribute("hidden", true); if (hubScreen) { hubScreen.toggleAttribute("hidden", true); } switch (newState) { case "STATE_MAIN_MENU": // Check if we should show hub or main menu const hasRoster = gameStateManager.rosterManager.roster.length > 0; const hasCompletedMissions = gameStateManager.missionManager.completedMissions.size > 0; const shouldShowHub = hasRoster || hasCompletedMissions; if (shouldShowHub) { // Load HubScreen dynamically await import("./ui/screens/hub-screen.js"); await import("./ui/components/mission-board.js"); const hub = document.querySelector("hub-screen"); if (hub) { hub.toggleAttribute("hidden", false); } else { // Create hub-screen if it doesn't exist in HTML const hubElement = document.createElement("hub-screen"); document.body.appendChild(hubElement); hubElement.toggleAttribute("hidden", false); } } else { // Show main menu for new players mainMenu.toggleAttribute("hidden", false); } break; case "STATE_TEAM_BUILDER": await import("./ui/team-builder.js"); teamBuilder.toggleAttribute("hidden", false); break; case "STATE_DEPLOYMENT": case "STATE_COMBAT": await import("./ui/game-viewport.js"); gameViewport.toggleAttribute("hidden", false); // Squad will be updated by game-viewport's #updateSquad() method // which listens to gamestate-changed events break; } loadingOverlay.toggleAttribute("hidden", true); }); window.addEventListener("save-check-complete", (e) => { if (e.detail.hasSave) { btnContinue.disabled = false; btnContinue.style.borderColor = "#00ff00"; btnContinue.style.color = "#00ff00"; } }); // Set up embark listener once (not inside button click) teamBuilder.addEventListener("embark", async (e) => { await gameStateManager.handleEmbark(e); // Squad will be updated from activeRunData in gamestate-changed handler // which has IDs after recruitment }); btnNewRun.addEventListener("click", async () => { gameStateManager.startNewGame(); }); btnContinue.addEventListener("click", async () => { gameStateManager.continueGame(); }); // Handle request-team-builder event from HubScreen window.addEventListener("request-team-builder", async (e) => { const { missionId } = e.detail; if (missionId) { // Set the active mission in MissionManager gameStateManager.missionManager.activeMissionId = missionId; // Transition to team builder await gameStateManager.transitionTo("STATE_TEAM_BUILDER", { missionId }); } }); // Handle save-and-quit event from HubScreen window.addEventListener("save-and-quit", async () => { // Save current state and return to main menu // This could trigger a save dialog or auto-save console.log("Save and quit requested"); // For now, just transition back to main menu // In the future, this could save and close the game }); // Boot gameStateManager.init();