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.
226 lines
7.7 KiB
JavaScript
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();
|