aether-shards/src/index.js
Matthew Mone ac0f3cc396 Enhance testing and integration of inventory and character management systems
Add comprehensive tests for the InventoryManager and InventoryContainer to validate item management functionalities. Implement integration tests for the CharacterSheet component, ensuring proper interaction with the inventory system. Update the Explorer class to support new inventory features and maintain backward compatibility. Refactor related components for improved clarity and performance.
2025-12-27 16:54:03 -08:00

226 lines
7.7 KiB
JavaScript

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();