import tutorialMission from '../assets/data/missions/mission_tutorial_01.json' with { type: 'json' }; import { narrativeManager } from './NarrativeManager.js'; /** * MissionManager.js * Manages campaign progression, mission selection, narrative triggers, and objective tracking. */ export class MissionManager { constructor() { // Campaign State this.activeMissionId = null; this.completedMissions = new Set(); this.missionRegistry = new Map(); // Active Run State this.currentMissionDef = null; this.currentObjectives = []; // Register default missions this.registerMission(tutorialMission); } registerMission(missionDef) { this.missionRegistry.set(missionDef.id, missionDef); } // --- PERSISTENCE (Campaign) --- load(saveData) { this.completedMissions = new Set(saveData.completedMissions || []); // Default to Tutorial if history is empty if (this.completedMissions.size === 0) { this.activeMissionId = 'MISSION_TUTORIAL_01'; } } save() { return { completedMissions: Array.from(this.completedMissions) }; } // --- MISSION SETUP & NARRATIVE --- /** * Gets the configuration for the currently selected mission. */ getActiveMission() { if (!this.activeMissionId) return this.missionRegistry.get('MISSION_TUTORIAL_01'); return this.missionRegistry.get(this.activeMissionId); } /** * Prepares the manager for a new run. * Resets objectives and prepares narrative hooks. */ setupActiveMission() { const mission = this.getActiveMission(); this.currentMissionDef = mission; // Hydrate objectives state this.currentObjectives = mission.objectives.primary.map(obj => ({ ...obj, current: 0, complete: false })); console.log(`Mission Setup: ${mission.config.title} - Objectives:`, this.currentObjectives); } /** * Plays the intro narrative if one exists. * Returns a Promise that resolves when the game should start. */ async playIntro() { if (!this.currentMissionDef || !this.currentMissionDef.narrative || !this.currentMissionDef.narrative.intro_sequence) { return Promise.resolve(); } return new Promise((resolve) => { const introId = this.currentMissionDef.narrative.intro_sequence; // Mock loader: In real app, fetch the JSON from assets/data/narrative/ // For prototype, we'll assume narrativeManager can handle the ID or we pass a mock. // const narrativeData = await fetch(`assets/data/narrative/${introId}.json`).then(r => r.json()); // We'll simulate the event listener logic const onEnd = () => { narrativeManager.removeEventListener('narrative-end', onEnd); resolve(); }; narrativeManager.addEventListener('narrative-end', onEnd); // Trigger the manager (Assuming it has a loader, or we modify it to accept ID) // For this snippet, we assume startSequence accepts data. // In a full implementation, you'd load the JSON here. console.log(`Playing Narrative Intro: ${introId}`); // narrativeManager.startSequence(loadedJson); // Fallback for prototype if no JSON loader: setTimeout(onEnd, 100); // Instant resolve for now to prevent hanging }); } // --- GAMEPLAY LOGIC (Objectives) --- /** * Called by GameLoop whenever a relevant event occurs. * @param {string} type - 'ENEMY_DEATH', 'TURN_END', etc. * @param {Object} data - Context data */ onGameEvent(type, data) { if (!this.currentObjectives.length) return; let statusChanged = false; this.currentObjectives.forEach(obj => { if (obj.complete) return; // Logic for 'ELIMINATE_ALL' or 'ELIMINATE_UNIT' if (type === 'ENEMY_DEATH') { if (obj.type === 'ELIMINATE_ALL' || (obj.type === 'ELIMINATE_UNIT' && data.unitId === obj.target_def_id)) { obj.current++; if (obj.target_count && obj.current >= obj.target_count) { obj.complete = true; statusChanged = true; } } } }); if (statusChanged) { this.checkVictory(); } } checkVictory() { const allPrimaryComplete = this.currentObjectives.every(o => o.complete); if (allPrimaryComplete) { console.log("VICTORY! Mission Objectives Complete."); this.completeActiveMission(); // Dispatch event for GameLoop to handle Victory Screen window.dispatchEvent(new CustomEvent('mission-victory', { detail: { missionId: this.activeMissionId }})); } } completeActiveMission() { if (this.activeMissionId) { this.completedMissions.add(this.activeMissionId); // Simple campaign logic: If Tutorial done, unlock next (Placeholder) if (this.activeMissionId === 'MISSION_TUTORIAL_01') { // this.activeMissionId = 'MISSION_ACT1_01'; } } } }