aether-shards/src/index.js

352 lines
12 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 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();