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 = [], skillTree = null, } = 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 // Use provided skillTree if available (e.g., from barracks), otherwise generate if ( !skillTree && 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"; // Use provided skillTree if available (from barracks), otherwise use generated one characterSheet.treeDef = skillTree || null; // Pass generated tree // Pass inventoryManager - use hubInventoryManager in Hub mode, gameLoop's in combat if ( gameStateManager.currentState === "STATE_COMBAT" && gameStateManager.gameLoop?.inventoryManager ) { characterSheet.inventoryManager = gameStateManager.gameLoop.inventoryManager; } else if (gameStateManager.hubInventoryManager) { characterSheet.inventoryManager = gameStateManager.hubInventoryManager; } // 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"); // Check if we have any roster (not just deployable units) const rosterExists = gameStateManager.rosterManager.roster.length > 0; const deployableUnits = gameStateManager.rosterManager.getDeployableUnits(); if (rosterExists) { // We have a roster, use ROSTER mode (even if no deployable units) // Setting availablePool will trigger willUpdate() which calls _initializeData() teamBuilder.availablePool = deployableUnits || []; teamBuilder._poolExplicitlySet = true; console.log( "TeamBuilder: Populated with roster units", deployableUnits?.length || 0, "deployable out of", gameStateManager.rosterManager.roster.length, "total" ); } else { // No roster yet, use draft mode teamBuilder.availablePool = []; teamBuilder._poolExplicitlySet = false; console.log("TeamBuilder: No roster available, using draft mode"); } 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(); // Lazy-load debug commands - only load when first accessed // Creates async wrappers for all methods that load the module on first call if (typeof window !== "undefined") { let debugCommandsInstance = null; let debugCommandsLoading = null; // List of all debug command methods (for creating async wrappers) const debugCommandMethods = [ "addXP", "setLevel", "addSkillPoints", "unlockSkill", "unlockAllSkills", "addItem", "addCurrency", "killEnemy", "healUnit", "triggerVictory", "completeObjective", "triggerNarrative", "help", "listIds", "listPlayerIds", "listEnemyIds", "listItemIds", "listUnits", "listItems", "getState", ]; // Create async wrapper functions for each method const createAsyncWrapper = (methodName) => { return function (...args) { // If already loaded, call directly if (debugCommandsInstance) { const method = debugCommandsInstance[methodName]; if (typeof method === "function") { return method.apply(debugCommandsInstance, args); } return method; } // If currently loading, wait for it if (debugCommandsLoading) { return debugCommandsLoading.then(() => { const method = debugCommandsInstance[methodName]; if (typeof method === "function") { return method.apply(debugCommandsInstance, args); } return method; }); } // Start loading the module debugCommandsLoading = import("./core/DebugCommands.js").then( (module) => { const { debugCommands } = module; if (!debugCommands._initialized) { debugCommands.init(); debugCommands._initialized = true; } debugCommandsInstance = debugCommands; return debugCommands; } ); // Wait for load, then call the method return debugCommandsLoading.then(() => { const method = debugCommandsInstance[methodName]; if (typeof method === "function") { return method.apply(debugCommandsInstance, args); } return method; }); }; }; // Create the debugCommands object with async wrappers window.debugCommands = {}; debugCommandMethods.forEach((methodName) => { window.debugCommands[methodName] = createAsyncWrapper(methodName); }); // Add a note about async nature Object.defineProperty(window.debugCommands, "_note", { value: "Debug commands are lazy-loaded. First call may take a moment.", enumerable: false, }); }