aether-shards/src/index.js

227 lines
7.7 KiB
JavaScript
Raw Normal View History

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 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/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`);
};
characterSheet.addEventListener("close", handleClose);
characterSheet.addEventListener("equip-item", handleEquipItem);
characterSheet.addEventListener("unlock-request", handleUnlockRequest);
// 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":
loadingMessage.textContent = "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);
switch (newState) {
case "STATE_MAIN_MENU":
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();
});
// Boot
gameStateManager.init();