aether-shards/src/managers/MissionManager.js

158 lines
5.4 KiB
JavaScript
Raw Normal View History

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';
}
}
}
}