2025-12-19 23:07:36 +00:00
|
|
|
import { gameStateManager } from "./core/GameStateManager.js";
|
|
|
|
|
|
2025-12-22 20:55:41 +00:00
|
|
|
/** @type {HTMLElement | null} */
|
2025-12-19 23:07:36 +00:00
|
|
|
const gameViewport = document.querySelector("game-viewport");
|
2025-12-22 20:55:41 +00:00
|
|
|
/** @type {HTMLElement | null} */
|
2025-12-19 23:07:36 +00:00
|
|
|
const teamBuilder = document.querySelector("team-builder");
|
2025-12-22 20:55:41 +00:00
|
|
|
/** @type {HTMLElement | null} */
|
2025-12-19 23:07:36 +00:00
|
|
|
const mainMenu = document.getElementById("main-menu");
|
2025-12-22 20:55:41 +00:00
|
|
|
/** @type {HTMLElement | null} */
|
2025-12-31 18:49:26 +00:00
|
|
|
const hubScreen = document.querySelector("hub-screen");
|
|
|
|
|
/** @type {HTMLElement | null} */
|
2025-12-19 23:07:36 +00:00
|
|
|
const btnNewRun = document.getElementById("btn-start");
|
2025-12-22 20:55:41 +00:00
|
|
|
/** @type {HTMLElement | null} */
|
2025-12-19 23:07:36 +00:00
|
|
|
const btnContinue = document.getElementById("btn-load");
|
2025-12-22 20:55:41 +00:00
|
|
|
/** @type {HTMLElement | null} */
|
2025-12-19 23:07:36 +00:00
|
|
|
const loadingOverlay = document.getElementById("loading-overlay");
|
2025-12-22 20:55:41 +00:00
|
|
|
/** @type {HTMLElement | null} */
|
2025-12-19 23:07:36 +00:00
|
|
|
const loadingMessage = document.getElementById("loading-message");
|
2025-12-28 00:54:03 +00:00
|
|
|
/** @type {HTMLElement | null} */
|
|
|
|
|
const uiLayer = document.getElementById("ui-layer");
|
2025-12-19 23:07:36 +00:00
|
|
|
|
|
|
|
|
// --- Event Listeners ---
|
|
|
|
|
|
2025-12-28 00:54:03 +00:00
|
|
|
// 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/CharacterSheet.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`);
|
|
|
|
|
};
|
|
|
|
|
|
2025-12-28 01:21:31 +00:00
|
|
|
// 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);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2025-12-28 00:54:03 +00:00
|
|
|
characterSheet.addEventListener("close", handleClose);
|
|
|
|
|
characterSheet.addEventListener("equip-item", handleEquipItem);
|
|
|
|
|
characterSheet.addEventListener("unlock-request", handleUnlockRequest);
|
2025-12-28 01:21:31 +00:00
|
|
|
characterSheet.addEventListener("skill-unlocked", handleSkillUnlocked);
|
2025-12-28 00:54:03 +00:00
|
|
|
|
|
|
|
|
// Append to document body - dialog will handle its own display
|
|
|
|
|
document.body.appendChild(characterSheet);
|
|
|
|
|
currentCharacterSheet = characterSheet;
|
|
|
|
|
});
|
|
|
|
|
|
2025-12-19 23:07:36 +00:00
|
|
|
window.addEventListener("gamestate-changed", async (e) => {
|
|
|
|
|
const { newState } = e.detail;
|
|
|
|
|
console.log("gamestate-changed", newState);
|
|
|
|
|
switch (newState) {
|
|
|
|
|
case "STATE_MAIN_MENU":
|
2025-12-31 18:49:26 +00:00
|
|
|
// 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...";
|
2025-12-19 23:07:36 +00:00
|
|
|
break;
|
|
|
|
|
case "STATE_TEAM_BUILDER":
|
|
|
|
|
loadingMessage.textContent = "INITIALIZING TEAM BUILDER...";
|
|
|
|
|
break;
|
2025-12-22 05:20:33 +00:00
|
|
|
case "STATE_DEPLOYMENT":
|
|
|
|
|
case "STATE_COMBAT":
|
2025-12-22 05:33:05 +00:00
|
|
|
import("./ui/game-viewport.js");
|
2025-12-19 23:07:36 +00:00
|
|
|
loadingMessage.textContent = "INITIALIZING GAME ENGINE...";
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
loadingOverlay.toggleAttribute("hidden", false);
|
|
|
|
|
mainMenu.toggleAttribute("hidden", true);
|
|
|
|
|
gameViewport.toggleAttribute("hidden", true);
|
|
|
|
|
teamBuilder.toggleAttribute("hidden", true);
|
2025-12-31 18:49:26 +00:00
|
|
|
if (hubScreen) {
|
|
|
|
|
hubScreen.toggleAttribute("hidden", true);
|
|
|
|
|
}
|
2025-12-19 23:07:36 +00:00
|
|
|
switch (newState) {
|
|
|
|
|
case "STATE_MAIN_MENU":
|
2025-12-31 18:49:26 +00:00
|
|
|
// 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/HubScreen.js");
|
|
|
|
|
await import("./ui/components/MissionBoard.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);
|
|
|
|
|
}
|
2025-12-19 23:07:36 +00:00
|
|
|
break;
|
|
|
|
|
case "STATE_TEAM_BUILDER":
|
|
|
|
|
await import("./ui/team-builder.js");
|
|
|
|
|
teamBuilder.toggleAttribute("hidden", false);
|
|
|
|
|
break;
|
2025-12-22 05:20:33 +00:00
|
|
|
case "STATE_DEPLOYMENT":
|
|
|
|
|
case "STATE_COMBAT":
|
2025-12-19 23:07:36 +00:00
|
|
|
await import("./ui/game-viewport.js");
|
|
|
|
|
gameViewport.toggleAttribute("hidden", false);
|
2025-12-28 00:54:03 +00:00
|
|
|
// Squad will be updated by game-viewport's #updateSquad() method
|
|
|
|
|
// which listens to gamestate-changed events
|
2025-12-19 23:07:36 +00:00
|
|
|
break;
|
2025-12-19 16:38:22 +00:00
|
|
|
}
|
2025-12-19 23:07:36 +00:00
|
|
|
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";
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2025-12-22 05:20:33 +00:00
|
|
|
// Set up embark listener once (not inside button click)
|
|
|
|
|
teamBuilder.addEventListener("embark", async (e) => {
|
|
|
|
|
await gameStateManager.handleEmbark(e);
|
2025-12-28 00:54:03 +00:00
|
|
|
// Squad will be updated from activeRunData in gamestate-changed handler
|
|
|
|
|
// which has IDs after recruitment
|
2025-12-22 05:20:33 +00:00
|
|
|
});
|
|
|
|
|
|
2025-12-19 23:07:36 +00:00
|
|
|
btnNewRun.addEventListener("click", async () => {
|
2025-12-22 04:40:48 +00:00
|
|
|
gameStateManager.startNewGame();
|
2025-12-19 23:07:36 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
btnContinue.addEventListener("click", async () => {
|
|
|
|
|
gameStateManager.continueGame();
|
|
|
|
|
});
|
|
|
|
|
|
2025-12-31 18:49:26 +00:00
|
|
|
// 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
|
|
|
|
|
});
|
|
|
|
|
|
2025-12-19 23:07:36 +00:00
|
|
|
// Boot
|
|
|
|
|
gameStateManager.init();
|