Skip to main content

proof_engine/editor/
quest_editor.rs

1#[allow(dead_code, unused_variables, unused_mut, unused_imports)]
2
3use glam::{Vec2, Vec3, Vec4, Quat, Mat4};
4use std::collections::{HashMap, VecDeque, HashSet, BTreeMap};
5
6// ============================================================
7// SECTION 1: QUEST GRAPH NODE TYPES
8// ============================================================
9
10#[derive(Debug, Clone, PartialEq)]
11pub enum QuestNodeType {
12    QuestStart,
13    Objective,
14    ConditionCheck,
15    Reward,
16    Branch,
17    Fail,
18    Completion,
19    Timer,
20    Trigger,
21}
22
23#[derive(Debug, Clone)]
24pub struct QuestStartNode {
25    pub id: u64,
26    pub position: Vec2,
27    pub quest_id: String,
28    pub quest_name: String,
29    pub description: String,
30    pub quest_giver_id: String,
31    pub prerequisites: Vec<QuestPrerequisite>,
32    pub initial_journal_entry: String,
33    pub start_map_markers: Vec<MapMarker>,
34    pub on_start_events: Vec<String>,
35    pub output_port: u64,
36    pub tags: Vec<String>,
37    pub comment: String,
38    pub auto_start: bool,
39    pub recommended_level: u32,
40    pub difficulty_rating: f32,
41}
42
43impl QuestStartNode {
44    pub fn new(id: u64) -> Self {
45        QuestStartNode {
46            id,
47            position: Vec2::ZERO,
48            quest_id: format!("quest_{}", id),
49            quest_name: String::new(),
50            description: String::new(),
51            quest_giver_id: String::new(),
52            prerequisites: Vec::new(),
53            initial_journal_entry: String::new(),
54            start_map_markers: Vec::new(),
55            on_start_events: Vec::new(),
56            output_port: 0,
57            tags: Vec::new(),
58            comment: String::new(),
59            auto_start: false,
60            recommended_level: 1,
61            difficulty_rating: 1.0,
62        }
63    }
64
65    pub fn check_prerequisites(&self, state: &QuestStateStore) -> bool {
66        for prereq in &self.prerequisites {
67            if !prereq.is_satisfied(state) {
68                return false;
69            }
70        }
71        true
72    }
73}
74
75#[derive(Debug, Clone)]
76pub struct QuestPrerequisite {
77    pub prereq_type: PrerequisiteType,
78    pub quest_id: Option<String>,
79    pub level_required: Option<u32>,
80    pub faction_id: Option<String>,
81    pub faction_rep_required: Option<i32>,
82    pub flag_name: Option<String>,
83    pub negated: bool,
84}
85
86#[derive(Debug, Clone, PartialEq)]
87pub enum PrerequisiteType {
88    QuestCompleted,
89    QuestActive,
90    QuestNotStarted,
91    PlayerLevel,
92    FactionRep,
93    Flag,
94    ItemOwned,
95}
96
97impl QuestPrerequisite {
98    pub fn is_satisfied(&self, state: &QuestStateStore) -> bool {
99        let result = match &self.prereq_type {
100            PrerequisiteType::QuestCompleted => {
101                if let Some(qid) = &self.quest_id {
102                    state.get_quest_state(qid) == QuestState::Completed
103                } else { false }
104            }
105            PrerequisiteType::QuestActive => {
106                if let Some(qid) = &self.quest_id {
107                    state.get_quest_state(qid) == QuestState::Active
108                } else { false }
109            }
110            PrerequisiteType::QuestNotStarted => {
111                if let Some(qid) = &self.quest_id {
112                    state.get_quest_state(qid) == QuestState::NotStarted
113                } else { true }
114            }
115            PrerequisiteType::PlayerLevel => {
116                if let Some(level) = self.level_required {
117                    state.player_level >= level
118                } else { true }
119            }
120            PrerequisiteType::FactionRep => {
121                if let (Some(fid), Some(req)) = (&self.faction_id, &self.faction_rep_required) {
122                    state.get_faction_rep(fid) >= *req
123                } else { false }
124            }
125            PrerequisiteType::Flag => {
126                if let Some(fname) = &self.flag_name {
127                    state.get_flag(fname)
128                } else { false }
129            }
130            PrerequisiteType::ItemOwned => {
131                false // Would check inventory
132            }
133        };
134        if self.negated { !result } else { result }
135    }
136}
137
138#[derive(Debug, Clone)]
139pub struct ObjectiveNode {
140    pub id: u64,
141    pub position: Vec2,
142    pub objective_id: String,
143    pub objective: QuestObjective,
144    pub input_port: u64,
145    pub success_port: u64,
146    pub failure_port: Option<u64>,
147    pub optional: bool,
148    pub secret: bool,
149    pub journal_entry_on_start: Option<String>,
150    pub journal_entry_on_complete: Option<String>,
151    pub map_markers: Vec<MapMarker>,
152    pub time_limit: Option<f32>,
153    pub elapsed_time: f32,
154    pub tags: Vec<String>,
155    pub comment: String,
156    pub completion_xp_bonus: u32,
157}
158
159impl ObjectiveNode {
160    pub fn new(id: u64, objective: QuestObjective) -> Self {
161        ObjectiveNode {
162            id,
163            position: Vec2::ZERO,
164            objective_id: format!("obj_{}", id),
165            objective,
166            input_port: 0,
167            success_port: 1,
168            failure_port: None,
169            optional: false,
170            secret: false,
171            journal_entry_on_start: None,
172            journal_entry_on_complete: None,
173            map_markers: Vec::new(),
174            time_limit: None,
175            elapsed_time: 0.0,
176            tags: Vec::new(),
177            comment: String::new(),
178            completion_xp_bonus: 0,
179        }
180    }
181
182    pub fn update(&mut self, delta: f32, events: &[QuestEvent]) -> ObjectiveNodeResult {
183        if let Some(limit) = self.time_limit {
184            self.elapsed_time += delta;
185            if self.elapsed_time >= limit {
186                return ObjectiveNodeResult::TimedOut;
187            }
188        }
189        for event in events {
190            self.objective.process_event(event);
191        }
192        match self.objective.get_status() {
193            ObjectiveStatus::Completed => ObjectiveNodeResult::Succeeded,
194            ObjectiveStatus::Failed => ObjectiveNodeResult::Failed,
195            ObjectiveStatus::Active => ObjectiveNodeResult::InProgress(self.objective.get_progress()),
196            ObjectiveStatus::NotStarted => ObjectiveNodeResult::NotStarted,
197        }
198    }
199
200    pub fn time_remaining(&self) -> Option<f32> {
201        self.time_limit.map(|limit| (limit - self.elapsed_time).max(0.0))
202    }
203
204    pub fn progress_display(&self) -> String {
205        self.objective.get_progress_display()
206    }
207}
208
209#[derive(Debug, Clone)]
210pub enum ObjectiveNodeResult {
211    NotStarted,
212    InProgress(f32),
213    Succeeded,
214    Failed,
215    TimedOut,
216}
217
218#[derive(Debug, Clone)]
219pub struct ConditionCheckNode {
220    pub id: u64,
221    pub position: Vec2,
222    pub conditions: Vec<QuestCondition>,
223    pub logic: ConditionLogic,
224    pub true_port: u64,
225    pub false_port: u64,
226    pub input_port: u64,
227    pub check_label: String,
228    pub tags: Vec<String>,
229    pub comment: String,
230}
231
232#[derive(Debug, Clone, PartialEq)]
233pub enum ConditionLogic {
234    All,
235    Any,
236    None,
237    Majority,
238}
239
240impl ConditionCheckNode {
241    pub fn new(id: u64) -> Self {
242        ConditionCheckNode {
243            id,
244            position: Vec2::ZERO,
245            conditions: Vec::new(),
246            logic: ConditionLogic::All,
247            true_port: 0,
248            false_port: 1,
249            input_port: 0,
250            check_label: String::new(),
251            tags: Vec::new(),
252            comment: String::new(),
253        }
254    }
255
256    pub fn evaluate(&self, state: &QuestStateStore) -> bool {
257        let results: Vec<bool> = self.conditions.iter()
258            .map(|c| c.evaluate(state))
259            .collect();
260        match self.logic {
261            ConditionLogic::All => results.iter().all(|&r| r),
262            ConditionLogic::Any => results.iter().any(|&r| r),
263            ConditionLogic::None => results.iter().all(|&r| !r),
264            ConditionLogic::Majority => {
265                let true_count = results.iter().filter(|&&r| r).count();
266                true_count * 2 > results.len()
267            }
268        }
269    }
270}
271
272#[derive(Debug, Clone)]
273pub struct RewardNode {
274    pub id: u64,
275    pub position: Vec2,
276    pub reward_table: RewardTable,
277    pub input_port: u64,
278    pub output_port: u64,
279    pub reward_label: String,
280    pub announce_rewards: bool,
281    pub delay_secs: f32,
282    pub tags: Vec<String>,
283    pub comment: String,
284}
285
286impl RewardNode {
287    pub fn new(id: u64) -> Self {
288        RewardNode {
289            id,
290            position: Vec2::ZERO,
291            reward_table: RewardTable::new(),
292            input_port: 0,
293            output_port: 1,
294            reward_label: "Quest Reward".to_string(),
295            announce_rewards: true,
296            delay_secs: 0.0,
297            tags: Vec::new(),
298            comment: String::new(),
299        }
300    }
301
302    pub fn roll_rewards(&self, player_level: u32, rng: &mut SimpleRng) -> Vec<RolledReward> {
303        self.reward_table.roll(player_level, rng)
304    }
305}
306
307#[derive(Debug, Clone)]
308pub struct BranchNode {
309    pub id: u64,
310    pub position: Vec2,
311    pub branch_type: BranchType,
312    pub conditions: Vec<(QuestCondition, u64)>,
313    pub default_port: u64,
314    pub input_port: u64,
315    pub tags: Vec<String>,
316    pub comment: String,
317}
318
319#[derive(Debug, Clone, PartialEq)]
320pub enum BranchType {
321    FirstTrue,
322    AllTrue,
323    Random,
324    PlayerChoice,
325    ValueSwitch(String),
326}
327
328impl BranchNode {
329    pub fn new(id: u64) -> Self {
330        BranchNode {
331            id,
332            position: Vec2::ZERO,
333            branch_type: BranchType::FirstTrue,
334            conditions: Vec::new(),
335            default_port: 0,
336            input_port: 0,
337            tags: Vec::new(),
338            comment: String::new(),
339        }
340    }
341
342    pub fn evaluate(&self, state: &QuestStateStore, rng: &mut SimpleRng) -> u64 {
343        match &self.branch_type {
344            BranchType::FirstTrue => {
345                for (cond, port) in &self.conditions {
346                    if cond.evaluate(state) {
347                        return *port;
348                    }
349                }
350                self.default_port
351            }
352            BranchType::AllTrue => {
353                let all_true = self.conditions.iter().all(|(c, _)| c.evaluate(state));
354                if all_true && !self.conditions.is_empty() {
355                    self.conditions[0].1
356                } else {
357                    self.default_port
358                }
359            }
360            BranchType::Random => {
361                if self.conditions.is_empty() { return self.default_port; }
362                let idx = rng.next_usize(self.conditions.len());
363                self.conditions[idx].1
364            }
365            BranchType::PlayerChoice => {
366                self.default_port // Player picks - handled externally
367            }
368            BranchType::ValueSwitch(var_name) => {
369                // Would need value from state
370                self.default_port
371            }
372        }
373    }
374}
375
376#[derive(Debug, Clone)]
377pub struct FailNode {
378    pub id: u64,
379    pub position: Vec2,
380    pub fail_reason: String,
381    pub fail_message: String,
382    pub on_fail_events: Vec<String>,
383    pub penalty_reputation: Vec<(String, i32)>,
384    pub penalty_xp: i32,
385    pub allow_retry: bool,
386    pub retry_cooldown: f32,
387    pub input_port: u64,
388    pub tags: Vec<String>,
389    pub comment: String,
390}
391
392impl FailNode {
393    pub fn new(id: u64) -> Self {
394        FailNode {
395            id,
396            position: Vec2::ZERO,
397            fail_reason: String::new(),
398            fail_message: "Quest Failed".to_string(),
399            on_fail_events: Vec::new(),
400            penalty_reputation: Vec::new(),
401            penalty_xp: 0,
402            allow_retry: false,
403            retry_cooldown: 0.0,
404            input_port: 0,
405            tags: Vec::new(),
406            comment: String::new(),
407        }
408    }
409
410    pub fn get_penalty_description(&self) -> String {
411        let mut parts = Vec::new();
412        if self.penalty_xp < 0 {
413            parts.push(format!("{} XP", self.penalty_xp));
414        }
415        for (faction, delta) in &self.penalty_reputation {
416            if *delta != 0 {
417                parts.push(format!("{}: {:+}", faction, delta));
418            }
419        }
420        parts.join(", ")
421    }
422}
423
424#[derive(Debug, Clone)]
425pub struct CompletionNode {
426    pub id: u64,
427    pub position: Vec2,
428    pub completion_type: CompletionType,
429    pub completion_message: String,
430    pub journal_entry: String,
431    pub on_complete_events: Vec<String>,
432    pub unlock_quests: Vec<String>,
433    pub set_flags: Vec<String>,
434    pub mark_map_markers_complete: bool,
435    pub credits_sequence: bool,
436    pub input_port: u64,
437    pub tags: Vec<String>,
438    pub comment: String,
439}
440
441#[derive(Debug, Clone, PartialEq)]
442pub enum CompletionType {
443    Success,
444    PartialSuccess,
445    Bittersweet,
446    PyrrhicVictory,
447}
448
449impl CompletionNode {
450    pub fn new(id: u64) -> Self {
451        CompletionNode {
452            id,
453            position: Vec2::ZERO,
454            completion_type: CompletionType::Success,
455            completion_message: "Quest Complete!".to_string(),
456            journal_entry: String::new(),
457            on_complete_events: Vec::new(),
458            unlock_quests: Vec::new(),
459            set_flags: Vec::new(),
460            mark_map_markers_complete: true,
461            credits_sequence: false,
462            input_port: 0,
463            tags: Vec::new(),
464            comment: String::new(),
465        }
466    }
467}
468
469#[derive(Debug, Clone)]
470pub struct TimerNode {
471    pub id: u64,
472    pub position: Vec2,
473    pub duration_secs: f32,
474    pub elapsed: f32,
475    pub on_expire_port: u64,
476    pub on_complete_port: u64,
477    pub show_timer_hud: bool,
478    pub timer_label: String,
479    pub timer_color: Vec4,
480    pub pause_when_inactive: bool,
481    pub running: bool,
482    pub input_port: u64,
483    pub tags: Vec<String>,
484    pub comment: String,
485}
486
487impl TimerNode {
488    pub fn new(id: u64) -> Self {
489        TimerNode {
490            id,
491            position: Vec2::ZERO,
492            duration_secs: 60.0,
493            elapsed: 0.0,
494            on_expire_port: 0,
495            on_complete_port: 1,
496            show_timer_hud: true,
497            timer_label: String::new(),
498            timer_color: Vec4::new(1.0, 0.5, 0.0, 1.0),
499            pause_when_inactive: false,
500            running: false,
501            input_port: 0,
502            tags: Vec::new(),
503            comment: String::new(),
504        }
505    }
506
507    pub fn update(&mut self, delta: f32) -> Option<bool> {
508        if !self.running { return None; }
509        self.elapsed += delta;
510        if self.elapsed >= self.duration_secs {
511            self.running = false;
512            Some(true) // expired
513        } else {
514            None
515        }
516    }
517
518    pub fn reset(&mut self) { self.elapsed = 0.0; self.running = false; }
519    pub fn start(&mut self) { self.running = true; }
520    pub fn remaining(&self) -> f32 { (self.duration_secs - self.elapsed).max(0.0) }
521    pub fn progress(&self) -> f32 { (self.elapsed / self.duration_secs).clamp(0.0, 1.0) }
522
523    pub fn format_remaining(&self) -> String {
524        let rem = self.remaining();
525        let mins = (rem / 60.0) as u32;
526        let secs = (rem % 60.0) as u32;
527        format!("{}:{:02}", mins, secs)
528    }
529}
530
531#[derive(Debug, Clone)]
532pub struct TriggerNode {
533    pub id: u64,
534    pub position: Vec2,
535    pub trigger_type: QuestTriggerType,
536    pub trigger_data: HashMap<String, String>,
537    pub output_port: u64,
538    pub one_shot: bool,
539    pub triggered: bool,
540    pub cooldown: f32,
541    pub last_triggered: f32,
542    pub input_port: u64,
543    pub tags: Vec<String>,
544    pub comment: String,
545}
546
547#[derive(Debug, Clone, PartialEq)]
548pub enum QuestTriggerType {
549    EnterArea(Vec3, f32),
550    KillTarget(String),
551    InteractObject(String),
552    ItemPickup(String),
553    TimePassed(f32),
554    Custom(String),
555    PlayerNearNPC(String, f32),
556    QuestStateChange(String, QuestState),
557}
558
559impl TriggerNode {
560    pub fn new(id: u64) -> Self {
561        TriggerNode {
562            id,
563            position: Vec2::ZERO,
564            trigger_type: QuestTriggerType::Custom("default".to_string()),
565            trigger_data: HashMap::new(),
566            output_port: 0,
567            one_shot: true,
568            triggered: false,
569            cooldown: 0.0,
570            last_triggered: -f32::MAX,
571            input_port: 0,
572            tags: Vec::new(),
573            comment: String::new(),
574        }
575    }
576
577    pub fn check_trigger(&self, event: &QuestEvent, current_time: f32) -> bool {
578        if self.one_shot && self.triggered { return false; }
579        if current_time - self.last_triggered < self.cooldown { return false; }
580        match (&self.trigger_type, event) {
581            (QuestTriggerType::KillTarget(target_id), QuestEvent::EnemyKilled { enemy_id, .. }) => {
582                Some(target_id.as_str()) == enemy_id.as_deref()
583            }
584            (QuestTriggerType::ItemPickup(item_id), QuestEvent::ItemPickedUp { item_id: picked_id, .. }) => {
585                item_id == picked_id
586            }
587            (QuestTriggerType::InteractObject(obj_id), QuestEvent::ObjectInteracted { object_id }) => {
588                obj_id == object_id
589            }
590            (QuestTriggerType::Custom(name), QuestEvent::CustomEvent { name: event_name, .. }) => {
591                name == event_name
592            }
593            _ => false,
594        }
595    }
596}
597
598// ============================================================
599// SECTION 2: OBJECTIVE TYPES (20+)
600// ============================================================
601
602#[derive(Debug, Clone, PartialEq)]
603pub enum ObjectiveStatus {
604    NotStarted,
605    Active,
606    Completed,
607    Failed,
608}
609
610#[derive(Debug, Clone)]
611pub enum QuestObjective {
612    KillEnemy(KillEnemyObjective),
613    CollectItem(CollectItemObjective),
614    ReachLocation(ReachLocationObjective),
615    TalkToNPC(TalkToNPCObjective),
616    ProtectTarget(ProtectTargetObjective),
617    EscortTarget(EscortTargetObjective),
618    SurviveWaves(SurviveWavesObjective),
619    SolvePuzzle(SolvePuzzleObjective),
620    CraftItem(CraftItemObjective),
621    UseAbility(UseAbilityObjective),
622    ExploreArea(ExploreAreaObjective),
623    TakePhoto(TakePhotoObjective),
624    BuildStructure(BuildStructureObjective),
625    DefeatBoss(DefeatBossObjective),
626    FindSecret(FindSecretObjective),
627    DeliverItem(DeliverItemObjective),
628    InvestigateClue(InvestigateClueObjective),
629    RepairObject(RepairObjectObjective),
630    PlantDevice(PlantDeviceObjective),
631    HackTerminal(HackTerminalObjective),
632    SneakPast(SneakPastObjective),
633    PickpocketTarget(PickpocketTargetObjective),
634}
635
636impl QuestObjective {
637    pub fn get_status(&self) -> ObjectiveStatus {
638        match self {
639            QuestObjective::KillEnemy(o) => o.status(),
640            QuestObjective::CollectItem(o) => o.status(),
641            QuestObjective::ReachLocation(o) => o.status(),
642            QuestObjective::TalkToNPC(o) => o.status(),
643            QuestObjective::ProtectTarget(o) => o.status(),
644            QuestObjective::EscortTarget(o) => o.status(),
645            QuestObjective::SurviveWaves(o) => o.status(),
646            QuestObjective::SolvePuzzle(o) => o.status(),
647            QuestObjective::CraftItem(o) => o.status(),
648            QuestObjective::UseAbility(o) => o.status(),
649            QuestObjective::ExploreArea(o) => o.status(),
650            QuestObjective::TakePhoto(o) => o.status(),
651            QuestObjective::BuildStructure(o) => o.status(),
652            QuestObjective::DefeatBoss(o) => o.status(),
653            QuestObjective::FindSecret(o) => o.status(),
654            QuestObjective::DeliverItem(o) => o.status(),
655            QuestObjective::InvestigateClue(o) => o.status(),
656            QuestObjective::RepairObject(o) => o.status(),
657            QuestObjective::PlantDevice(o) => o.status(),
658            QuestObjective::HackTerminal(o) => o.status(),
659            QuestObjective::SneakPast(o) => o.status(),
660            QuestObjective::PickpocketTarget(o) => o.status(),
661        }
662    }
663
664    pub fn get_progress(&self) -> f32 {
665        match self {
666            QuestObjective::KillEnemy(o) => o.progress(),
667            QuestObjective::CollectItem(o) => o.progress(),
668            QuestObjective::ReachLocation(o) => o.progress(),
669            QuestObjective::TalkToNPC(o) => o.progress(),
670            QuestObjective::ProtectTarget(o) => o.progress(),
671            QuestObjective::EscortTarget(o) => o.progress(),
672            QuestObjective::SurviveWaves(o) => o.progress(),
673            QuestObjective::SolvePuzzle(o) => o.progress(),
674            QuestObjective::CraftItem(o) => o.progress(),
675            QuestObjective::UseAbility(o) => o.progress(),
676            QuestObjective::ExploreArea(o) => o.progress(),
677            QuestObjective::TakePhoto(o) => o.progress(),
678            QuestObjective::BuildStructure(o) => o.progress(),
679            QuestObjective::DefeatBoss(o) => o.progress(),
680            QuestObjective::FindSecret(o) => o.progress(),
681            QuestObjective::DeliverItem(o) => o.progress(),
682            QuestObjective::InvestigateClue(o) => o.progress(),
683            QuestObjective::RepairObject(o) => o.progress(),
684            QuestObjective::PlantDevice(o) => o.progress(),
685            QuestObjective::HackTerminal(o) => o.progress(),
686            QuestObjective::SneakPast(o) => o.progress(),
687            QuestObjective::PickpocketTarget(o) => o.progress(),
688        }
689    }
690
691    pub fn get_progress_display(&self) -> String {
692        match self {
693            QuestObjective::KillEnemy(o) => format!("{}/{}", o.killed_count, o.required_count),
694            QuestObjective::CollectItem(o) => format!("{}/{}", o.collected_count, o.required_count),
695            QuestObjective::SurviveWaves(o) => format!("Wave {}/{}", o.waves_survived, o.total_waves),
696            QuestObjective::ExploreArea(o) => {
697                let visited = o.areas.iter().filter(|a| a.visited).count();
698                format!("{}/{}", visited, o.areas.len())
699            }
700            QuestObjective::BuildStructure(o) => format!("{:.0}%", o.build_progress * 100.0),
701            QuestObjective::HackTerminal(o) => format!("{:.0}%", o.hack_progress * 100.0),
702            _ => String::new(),
703        }
704    }
705
706    pub fn process_event(&mut self, event: &QuestEvent) {
707        match self {
708            QuestObjective::KillEnemy(o) => o.process_event(event),
709            QuestObjective::CollectItem(o) => o.process_event(event),
710            QuestObjective::ReachLocation(o) => o.process_event(event),
711            QuestObjective::TalkToNPC(o) => o.process_event(event),
712            QuestObjective::ProtectTarget(o) => o.process_event(event),
713            QuestObjective::EscortTarget(o) => o.process_event(event),
714            QuestObjective::SurviveWaves(o) => o.process_event(event),
715            QuestObjective::SolvePuzzle(o) => o.process_event(event),
716            QuestObjective::CraftItem(o) => o.process_event(event),
717            QuestObjective::UseAbility(o) => o.process_event(event),
718            QuestObjective::ExploreArea(o) => o.process_event(event),
719            QuestObjective::TakePhoto(o) => o.process_event(event),
720            QuestObjective::BuildStructure(o) => o.process_event(event),
721            QuestObjective::DefeatBoss(o) => o.process_event(event),
722            QuestObjective::FindSecret(o) => o.process_event(event),
723            QuestObjective::DeliverItem(o) => o.process_event(event),
724            QuestObjective::InvestigateClue(o) => o.process_event(event),
725            QuestObjective::RepairObject(o) => o.process_event(event),
726            QuestObjective::PlantDevice(o) => o.process_event(event),
727            QuestObjective::HackTerminal(o) => o.process_event(event),
728            QuestObjective::SneakPast(o) => o.process_event(event),
729            QuestObjective::PickpocketTarget(o) => o.process_event(event),
730        }
731    }
732
733    pub fn description(&self) -> String {
734        match self {
735            QuestObjective::KillEnemy(o) => format!("Kill {} {}", o.required_count, o.enemy_type),
736            QuestObjective::CollectItem(o) => format!("Collect {} {}", o.required_count, o.item_id),
737            QuestObjective::ReachLocation(o) => format!("Reach {}", o.location_name),
738            QuestObjective::TalkToNPC(o) => format!("Talk to {}", o.npc_name),
739            QuestObjective::ProtectTarget(o) => format!("Protect {} for {}s", o.target_id, o.duration_secs),
740            QuestObjective::EscortTarget(o) => format!("Escort {} to {}", o.target_id, o.destination_name),
741            QuestObjective::SurviveWaves(o) => format!("Survive {} waves", o.total_waves),
742            QuestObjective::SolvePuzzle(o) => format!("Solve the {}", o.puzzle_id),
743            QuestObjective::CraftItem(o) => format!("Craft {}", o.item_id),
744            QuestObjective::UseAbility(o) => format!("Use {} {} times", o.ability_id, o.required_count),
745            QuestObjective::ExploreArea(o) => format!("Explore {}", o.area_name),
746            QuestObjective::TakePhoto(o) => format!("Take photo of {}", o.subject_id),
747            QuestObjective::BuildStructure(o) => format!("Build {}", o.structure_id),
748            QuestObjective::DefeatBoss(o) => format!("Defeat {}", o.boss_id),
749            QuestObjective::FindSecret(o) => format!("Find the secret"),
750            QuestObjective::DeliverItem(o) => format!("Deliver {} to {}", o.item_id, o.recipient_id),
751            QuestObjective::InvestigateClue(o) => format!("Investigate {}", o.location_name),
752            QuestObjective::RepairObject(o) => format!("Repair {}", o.object_id),
753            QuestObjective::PlantDevice(o) => format!("Plant device at {}", o.location_name),
754            QuestObjective::HackTerminal(o) => format!("Hack terminal {}", o.terminal_id),
755            QuestObjective::SneakPast(o) => format!("Sneak past {} guards", o.guard_count),
756            QuestObjective::PickpocketTarget(o) => format!("Pickpocket {}", o.target_id),
757        }
758    }
759}
760
761// --- Kill Enemy ---
762#[derive(Debug, Clone)]
763pub struct KillEnemyObjective {
764    pub enemy_type: String,
765    pub enemy_id: Option<String>,
766    pub required_count: u32,
767    pub killed_count: u32,
768    pub in_area: Option<String>,
769    pub with_weapon_type: Option<String>,
770    pub require_stealth_kill: bool,
771    pub allow_assists: bool,
772}
773
774impl KillEnemyObjective {
775    pub fn status(&self) -> ObjectiveStatus {
776        if self.killed_count >= self.required_count { ObjectiveStatus::Completed } else { ObjectiveStatus::Active }
777    }
778    pub fn progress(&self) -> f32 {
779        if self.required_count == 0 { return 1.0; }
780        (self.killed_count as f32 / self.required_count as f32).min(1.0)
781    }
782    pub fn process_event(&mut self, event: &QuestEvent) {
783        if let QuestEvent::EnemyKilled { enemy_type, enemy_id, weapon_type, is_stealth } = event {
784            if self.killed_count >= self.required_count { return; }
785            let type_match = &self.enemy_type == enemy_type || self.enemy_type == "*";
786            let id_match = self.enemy_id.as_ref().map(|id| id == enemy_id.as_deref().unwrap_or("")).unwrap_or(true);
787            let weapon_match = self.with_weapon_type.as_ref().map(|w| Some(w) == weapon_type.as_ref()).unwrap_or(true);
788            let stealth_match = !self.require_stealth_kill || *is_stealth;
789            if type_match && id_match && weapon_match && stealth_match {
790                self.killed_count += 1;
791            }
792        }
793    }
794}
795
796// --- Collect Item ---
797#[derive(Debug, Clone)]
798pub struct CollectItemObjective {
799    pub item_id: String,
800    pub item_name: String,
801    pub required_count: u32,
802    pub collected_count: u32,
803    pub quality_requirement: Option<f32>,
804    pub allow_partial: bool,
805    pub consume_on_completion: bool,
806}
807
808impl CollectItemObjective {
809    pub fn status(&self) -> ObjectiveStatus {
810        if self.collected_count >= self.required_count { ObjectiveStatus::Completed } else { ObjectiveStatus::Active }
811    }
812    pub fn progress(&self) -> f32 {
813        if self.required_count == 0 { return 1.0; }
814        (self.collected_count as f32 / self.required_count as f32).min(1.0)
815    }
816    pub fn process_event(&mut self, event: &QuestEvent) {
817        if let QuestEvent::ItemPickedUp { item_id, quantity, quality } = event {
818            if item_id == &self.item_id {
819                let qual_ok = self.quality_requirement.map(|q| *quality >= q).unwrap_or(true);
820                if qual_ok {
821                    self.collected_count = (self.collected_count + quantity).min(self.required_count);
822                }
823            }
824        }
825    }
826}
827
828// --- Reach Location ---
829#[derive(Debug, Clone)]
830pub struct ReachLocationObjective {
831    pub location_id: String,
832    pub location_name: String,
833    pub position: Vec3,
834    pub radius: f32,
835    pub required_time_secs: Option<f32>,
836    pub time_at_location: f32,
837    pub reached: bool,
838    pub require_no_combat: bool,
839}
840
841impl ReachLocationObjective {
842    pub fn status(&self) -> ObjectiveStatus {
843        if self.reached { ObjectiveStatus::Completed } else { ObjectiveStatus::Active }
844    }
845    pub fn progress(&self) -> f32 { if self.reached { 1.0 } else { 0.0 } }
846    pub fn process_event(&mut self, event: &QuestEvent) {
847        if let QuestEvent::PlayerMoved { position, is_in_combat } = event {
848            if self.require_no_combat && *is_in_combat { return; }
849            let dist = (*position - self.position).length();
850            if dist <= self.radius {
851                if let Some(req_time) = self.required_time_secs {
852                    // Need to track delta time - simplified
853                    self.time_at_location += 0.016;
854                    if self.time_at_location >= req_time { self.reached = true; }
855                } else {
856                    self.reached = true;
857                }
858            } else {
859                self.time_at_location = 0.0;
860            }
861        }
862    }
863}
864
865// --- Talk To NPC ---
866#[derive(Debug, Clone)]
867pub struct TalkToNPCObjective {
868    pub npc_id: String,
869    pub npc_name: String,
870    pub required_topic: Option<String>,
871    pub talked: bool,
872    pub dialogue_completed: bool,
873    pub require_specific_ending: Option<String>,
874}
875
876impl TalkToNPCObjective {
877    pub fn status(&self) -> ObjectiveStatus {
878        if self.talked && self.dialogue_completed { ObjectiveStatus::Completed } else { ObjectiveStatus::Active }
879    }
880    pub fn progress(&self) -> f32 {
881        match (self.talked, self.dialogue_completed) {
882            (false, _) => 0.0,
883            (true, false) => 0.5,
884            (true, true) => 1.0,
885        }
886    }
887    pub fn process_event(&mut self, event: &QuestEvent) {
888        match event {
889            QuestEvent::DialogueStarted { npc_id } => {
890                if npc_id == &self.npc_id { self.talked = true; }
891            }
892            QuestEvent::DialogueEnded { npc_id, ending } => {
893                if npc_id == &self.npc_id {
894                    let ending_ok = self.require_specific_ending.as_ref()
895                        .map(|req| req == ending)
896                        .unwrap_or(true);
897                    if ending_ok { self.dialogue_completed = true; }
898                }
899            }
900            _ => {}
901        }
902    }
903}
904
905// --- Protect Target ---
906#[derive(Debug, Clone)]
907pub struct ProtectTargetObjective {
908    pub target_id: String,
909    pub target_name: String,
910    pub duration_secs: f32,
911    pub protected_secs: f32,
912    pub target_health_min: f32,
913    pub failed: bool,
914    pub running: bool,
915}
916
917impl ProtectTargetObjective {
918    pub fn status(&self) -> ObjectiveStatus {
919        if self.failed { return ObjectiveStatus::Failed; }
920        if self.protected_secs >= self.duration_secs { ObjectiveStatus::Completed } else { ObjectiveStatus::Active }
921    }
922    pub fn progress(&self) -> f32 {
923        if self.failed { return 0.0; }
924        (self.protected_secs / self.duration_secs).min(1.0)
925    }
926    pub fn process_event(&mut self, event: &QuestEvent) {
927        match event {
928            QuestEvent::NPCDied { npc_id } => {
929                if npc_id == &self.target_id { self.failed = true; }
930            }
931            QuestEvent::NPCDamaged { npc_id, health_percent } => {
932                if npc_id == &self.target_id && *health_percent < self.target_health_min {
933                    self.failed = true;
934                }
935            }
936            QuestEvent::TimePassed { delta } => {
937                if self.running && !self.failed {
938                    self.protected_secs += delta;
939                }
940            }
941            _ => {}
942        }
943    }
944}
945
946// --- Escort Target ---
947#[derive(Debug, Clone)]
948pub struct EscortTargetObjective {
949    pub target_id: String,
950    pub destination_name: String,
951    pub destination_pos: Vec3,
952    pub destination_radius: f32,
953    pub reached: bool,
954    pub target_died: bool,
955    pub must_stay_near: bool,
956    pub stay_near_radius: f32,
957    pub player_too_far: bool,
958}
959
960impl EscortTargetObjective {
961    pub fn status(&self) -> ObjectiveStatus {
962        if self.target_died { ObjectiveStatus::Failed }
963        else if self.reached { ObjectiveStatus::Completed }
964        else { ObjectiveStatus::Active }
965    }
966    pub fn progress(&self) -> f32 { if self.reached { 1.0 } else if self.target_died { 0.0 } else { 0.5 } }
967    pub fn process_event(&mut self, event: &QuestEvent) {
968        match event {
969            QuestEvent::NPCDied { npc_id } => {
970                if npc_id == &self.target_id { self.target_died = true; }
971            }
972            QuestEvent::NPCMoved { npc_id, position } => {
973                if npc_id == &self.target_id {
974                    let dist = (*position - self.destination_pos).length();
975                    if dist <= self.destination_radius { self.reached = true; }
976                }
977            }
978            _ => {}
979        }
980    }
981}
982
983// --- Survive Waves ---
984#[derive(Debug, Clone)]
985pub struct SurviveWavesObjective {
986    pub total_waves: u32,
987    pub waves_survived: u32,
988    pub current_wave_enemies_remaining: u32,
989    pub player_died: bool,
990    pub between_waves: bool,
991    pub wave_countdown: f32,
992    pub wave_interval: f32,
993    pub enemy_spawn_table: Vec<WaveEnemy>,
994}
995
996#[derive(Debug, Clone)]
997pub struct WaveEnemy {
998    pub enemy_type: String,
999    pub count: u32,
1000    pub wave_start: u32,
1001}
1002
1003impl SurviveWavesObjective {
1004    pub fn status(&self) -> ObjectiveStatus {
1005        if self.player_died { ObjectiveStatus::Failed }
1006        else if self.waves_survived >= self.total_waves { ObjectiveStatus::Completed }
1007        else { ObjectiveStatus::Active }
1008    }
1009    pub fn progress(&self) -> f32 {
1010        if self.total_waves == 0 { return 1.0; }
1011        self.waves_survived as f32 / self.total_waves as f32
1012    }
1013    pub fn process_event(&mut self, event: &QuestEvent) {
1014        match event {
1015            QuestEvent::EnemyKilled { .. } => {
1016                if self.current_wave_enemies_remaining > 0 {
1017                    self.current_wave_enemies_remaining -= 1;
1018                    if self.current_wave_enemies_remaining == 0 {
1019                        self.waves_survived += 1;
1020                        self.between_waves = true;
1021                        self.wave_countdown = self.wave_interval;
1022                    }
1023                }
1024            }
1025            QuestEvent::PlayerDied => { self.player_died = true; }
1026            QuestEvent::TimePassed { delta } => {
1027                if self.between_waves {
1028                    self.wave_countdown -= delta;
1029                    if self.wave_countdown <= 0.0 { self.between_waves = false; }
1030                }
1031            }
1032            _ => {}
1033        }
1034    }
1035}
1036
1037// --- Solve Puzzle ---
1038#[derive(Debug, Clone)]
1039pub struct SolvePuzzleObjective {
1040    pub puzzle_id: String,
1041    pub puzzle_name: String,
1042    pub solved: bool,
1043    pub attempts: u32,
1044    pub max_attempts: Option<u32>,
1045    pub failed: bool,
1046    pub hint_unlocked: bool,
1047}
1048
1049impl SolvePuzzleObjective {
1050    pub fn status(&self) -> ObjectiveStatus {
1051        if self.failed { ObjectiveStatus::Failed }
1052        else if self.solved { ObjectiveStatus::Completed }
1053        else { ObjectiveStatus::Active }
1054    }
1055    pub fn progress(&self) -> f32 { if self.solved { 1.0 } else { 0.0 } }
1056    pub fn process_event(&mut self, event: &QuestEvent) {
1057        if let QuestEvent::PuzzleSolved { puzzle_id } = event {
1058            if puzzle_id == &self.puzzle_id { self.solved = true; }
1059        }
1060        if let QuestEvent::PuzzleAttempted { puzzle_id, success } = event {
1061            if puzzle_id == &self.puzzle_id {
1062                self.attempts += 1;
1063                if *success { self.solved = true; }
1064                else if let Some(max) = self.max_attempts {
1065                    if self.attempts >= max { self.failed = true; }
1066                }
1067            }
1068        }
1069    }
1070}
1071
1072// --- Craft Item ---
1073#[derive(Debug, Clone)]
1074pub struct CraftItemObjective {
1075    pub item_id: String,
1076    pub item_name: String,
1077    pub required_count: u32,
1078    pub crafted_count: u32,
1079    pub quality_min: Option<f32>,
1080    pub at_station_id: Option<String>,
1081}
1082
1083impl CraftItemObjective {
1084    pub fn status(&self) -> ObjectiveStatus {
1085        if self.crafted_count >= self.required_count { ObjectiveStatus::Completed } else { ObjectiveStatus::Active }
1086    }
1087    pub fn progress(&self) -> f32 {
1088        (self.crafted_count as f32 / self.required_count.max(1) as f32).min(1.0)
1089    }
1090    pub fn process_event(&mut self, event: &QuestEvent) {
1091        if let QuestEvent::ItemCrafted { item_id, quality, station_id } = event {
1092            if item_id == &self.item_id {
1093                let qual_ok = self.quality_min.map(|q| *quality >= q).unwrap_or(true);
1094                let station_ok = self.at_station_id.as_ref().map(|s| Some(s) == station_id.as_ref()).unwrap_or(true);
1095                if qual_ok && station_ok && self.crafted_count < self.required_count {
1096                    self.crafted_count += 1;
1097                }
1098            }
1099        }
1100    }
1101}
1102
1103// --- Use Ability ---
1104#[derive(Debug, Clone)]
1105pub struct UseAbilityObjective {
1106    pub ability_id: String,
1107    pub ability_name: String,
1108    pub required_count: u32,
1109    pub use_count: u32,
1110    pub on_enemy_type: Option<String>,
1111    pub require_hit: bool,
1112}
1113
1114impl UseAbilityObjective {
1115    pub fn status(&self) -> ObjectiveStatus {
1116        if self.use_count >= self.required_count { ObjectiveStatus::Completed } else { ObjectiveStatus::Active }
1117    }
1118    pub fn progress(&self) -> f32 { (self.use_count as f32 / self.required_count.max(1) as f32).min(1.0) }
1119    pub fn process_event(&mut self, event: &QuestEvent) {
1120        if let QuestEvent::AbilityUsed { ability_id, hit, target_type } = event {
1121            if ability_id == &self.ability_id {
1122                let hit_ok = !self.require_hit || *hit;
1123                let target_ok = self.on_enemy_type.as_ref().map(|t| Some(t) == target_type.as_ref()).unwrap_or(true);
1124                if hit_ok && target_ok && self.use_count < self.required_count {
1125                    self.use_count += 1;
1126                }
1127            }
1128        }
1129    }
1130}
1131
1132// --- Explore Area ---
1133#[derive(Debug, Clone)]
1134pub struct ExploreAreaObjective {
1135    pub area_name: String,
1136    pub areas: Vec<AreaPoint>,
1137    pub require_all: bool,
1138    pub discover_radius: f32,
1139}
1140
1141#[derive(Debug, Clone)]
1142pub struct AreaPoint {
1143    pub id: String,
1144    pub name: String,
1145    pub position: Vec3,
1146    pub visited: bool,
1147    pub optional: bool,
1148}
1149
1150impl ExploreAreaObjective {
1151    pub fn status(&self) -> ObjectiveStatus {
1152        let required: Vec<&AreaPoint> = self.areas.iter().filter(|a| !a.optional).collect();
1153        let visited = required.iter().filter(|a| a.visited).count();
1154        if self.require_all {
1155            if visited >= required.len() { ObjectiveStatus::Completed } else { ObjectiveStatus::Active }
1156        } else {
1157            if visited > 0 { ObjectiveStatus::Completed } else { ObjectiveStatus::Active }
1158        }
1159    }
1160    pub fn progress(&self) -> f32 {
1161        let required: Vec<&AreaPoint> = self.areas.iter().filter(|a| !a.optional).collect();
1162        if required.is_empty() { return 1.0; }
1163        let visited = required.iter().filter(|a| a.visited).count();
1164        visited as f32 / required.len() as f32
1165    }
1166    pub fn process_event(&mut self, event: &QuestEvent) {
1167        if let QuestEvent::PlayerMoved { position, .. } = event {
1168            for area in &mut self.areas {
1169                if !area.visited {
1170                    let dist = (*position - area.position).length();
1171                    if dist <= self.discover_radius { area.visited = true; }
1172                }
1173            }
1174        }
1175    }
1176}
1177
1178// --- Take Photo ---
1179#[derive(Debug, Clone)]
1180pub struct TakePhotoObjective {
1181    pub subject_id: String,
1182    pub subject_name: String,
1183    pub required_count: u32,
1184    pub taken_count: u32,
1185    pub min_quality: f32,
1186    pub must_be_undetected: bool,
1187    pub subject_behavior: Option<String>,
1188}
1189
1190impl TakePhotoObjective {
1191    pub fn status(&self) -> ObjectiveStatus {
1192        if self.taken_count >= self.required_count { ObjectiveStatus::Completed } else { ObjectiveStatus::Active }
1193    }
1194    pub fn progress(&self) -> f32 { (self.taken_count as f32 / self.required_count.max(1) as f32).min(1.0) }
1195    pub fn process_event(&mut self, event: &QuestEvent) {
1196        if let QuestEvent::PhotoTaken { subject_id, quality, undetected } = event {
1197            if subject_id == &self.subject_id {
1198                let qual_ok = *quality >= self.min_quality;
1199                let detect_ok = !self.must_be_undetected || *undetected;
1200                if qual_ok && detect_ok && self.taken_count < self.required_count {
1201                    self.taken_count += 1;
1202                }
1203            }
1204        }
1205    }
1206}
1207
1208// --- Build Structure ---
1209#[derive(Debug, Clone)]
1210pub struct BuildStructureObjective {
1211    pub structure_id: String,
1212    pub structure_name: String,
1213    pub build_progress: f32,
1214    pub required_progress: f32,
1215    pub at_location: Option<Vec3>,
1216    pub required_resources: Vec<(String, u32)>,
1217    pub resources_placed: HashMap<String, u32>,
1218}
1219
1220impl BuildStructureObjective {
1221    pub fn status(&self) -> ObjectiveStatus {
1222        if self.build_progress >= self.required_progress { ObjectiveStatus::Completed } else { ObjectiveStatus::Active }
1223    }
1224    pub fn progress(&self) -> f32 { (self.build_progress / self.required_progress.max(0.001)).min(1.0) }
1225    pub fn process_event(&mut self, event: &QuestEvent) {
1226        if let QuestEvent::StructureBuilt { structure_id, progress } = event {
1227            if structure_id == &self.structure_id { self.build_progress = *progress; }
1228        }
1229        if let QuestEvent::ResourcePlaced { structure_id, resource_id, amount } = event {
1230            if structure_id == &self.structure_id {
1231                *self.resources_placed.entry(resource_id.clone()).or_insert(0) += amount;
1232            }
1233        }
1234    }
1235}
1236
1237// --- Defeat Boss ---
1238#[derive(Debug, Clone)]
1239pub struct DefeatBossObjective {
1240    pub boss_id: String,
1241    pub boss_name: String,
1242    pub defeated: bool,
1243    pub phases_completed: u32,
1244    pub total_phases: u32,
1245    pub require_no_deaths: bool,
1246    pub player_died: bool,
1247    pub time_limit: Option<f32>,
1248    pub elapsed: f32,
1249}
1250
1251impl DefeatBossObjective {
1252    pub fn status(&self) -> ObjectiveStatus {
1253        if self.require_no_deaths && self.player_died { return ObjectiveStatus::Failed; }
1254        if self.time_limit.map(|t| self.elapsed >= t).unwrap_or(false) { return ObjectiveStatus::Failed; }
1255        if self.defeated { ObjectiveStatus::Completed } else { ObjectiveStatus::Active }
1256    }
1257    pub fn progress(&self) -> f32 {
1258        if self.defeated { return 1.0; }
1259        if self.total_phases == 0 { return 0.0; }
1260        self.phases_completed as f32 / self.total_phases as f32
1261    }
1262    pub fn process_event(&mut self, event: &QuestEvent) {
1263        match event {
1264            QuestEvent::EnemyKilled { enemy_id, .. } => {
1265                if enemy_id.as_deref() == Some(&self.boss_id) { self.defeated = true; }
1266            }
1267            QuestEvent::BossPhaseCompleted { boss_id, phase } => {
1268                if boss_id == &self.boss_id { self.phases_completed = *phase; }
1269            }
1270            QuestEvent::PlayerDied => { self.player_died = true; }
1271            QuestEvent::TimePassed { delta } => { self.elapsed += delta; }
1272            _ => {}
1273        }
1274    }
1275}
1276
1277// --- Find Secret ---
1278#[derive(Debug, Clone)]
1279pub struct FindSecretObjective {
1280    pub secret_id: String,
1281    pub location_hint: String,
1282    pub found: bool,
1283    pub reveal_on_find: bool,
1284}
1285
1286impl FindSecretObjective {
1287    pub fn status(&self) -> ObjectiveStatus { if self.found { ObjectiveStatus::Completed } else { ObjectiveStatus::Active } }
1288    pub fn progress(&self) -> f32 { if self.found { 1.0 } else { 0.0 } }
1289    pub fn process_event(&mut self, event: &QuestEvent) {
1290        if let QuestEvent::SecretFound { secret_id } = event {
1291            if secret_id == &self.secret_id { self.found = true; }
1292        }
1293    }
1294}
1295
1296// --- Deliver Item ---
1297#[derive(Debug, Clone)]
1298pub struct DeliverItemObjective {
1299    pub item_id: String,
1300    pub item_name: String,
1301    pub recipient_id: String,
1302    pub recipient_name: String,
1303    pub quantity: u32,
1304    pub delivered: u32,
1305    pub item_can_be_damaged: bool,
1306    pub item_condition: f32,
1307    pub condition_min: f32,
1308}
1309
1310impl DeliverItemObjective {
1311    pub fn status(&self) -> ObjectiveStatus {
1312        if self.delivered >= self.quantity { ObjectiveStatus::Completed }
1313        else if self.item_can_be_damaged && self.item_condition < self.condition_min { ObjectiveStatus::Failed }
1314        else { ObjectiveStatus::Active }
1315    }
1316    pub fn progress(&self) -> f32 { (self.delivered as f32 / self.quantity.max(1) as f32).min(1.0) }
1317    pub fn process_event(&mut self, event: &QuestEvent) {
1318        if let QuestEvent::ItemDelivered { item_id, recipient_id, quantity } = event {
1319            if item_id == &self.item_id && recipient_id == &self.recipient_id {
1320                self.delivered += quantity;
1321            }
1322        }
1323    }
1324}
1325
1326// --- Investigate Clue ---
1327#[derive(Debug, Clone)]
1328pub struct InvestigateClueObjective {
1329    pub location_name: String,
1330    pub clue_ids: Vec<String>,
1331    pub clues_found: HashSet<String>,
1332    pub required_clues: u32,
1333    pub all_clues_required: bool,
1334}
1335
1336impl InvestigateClueObjective {
1337    pub fn status(&self) -> ObjectiveStatus {
1338        let found = self.clues_found.len() as u32;
1339        let needed = if self.all_clues_required { self.clue_ids.len() as u32 } else { self.required_clues };
1340        if found >= needed { ObjectiveStatus::Completed } else { ObjectiveStatus::Active }
1341    }
1342    pub fn progress(&self) -> f32 {
1343        let needed = if self.all_clues_required { self.clue_ids.len() } else { self.required_clues as usize };
1344        if needed == 0 { return 1.0; }
1345        (self.clues_found.len() as f32 / needed as f32).min(1.0)
1346    }
1347    pub fn process_event(&mut self, event: &QuestEvent) {
1348        if let QuestEvent::ClueFound { clue_id } = event {
1349            if self.clue_ids.contains(clue_id) { self.clues_found.insert(clue_id.clone()); }
1350        }
1351    }
1352}
1353
1354// --- Repair Object ---
1355#[derive(Debug, Clone)]
1356pub struct RepairObjectObjective {
1357    pub object_id: String,
1358    pub object_name: String,
1359    pub repair_progress: f32,
1360    pub required_progress: f32,
1361    pub requires_item: Option<String>,
1362    pub item_consumed: bool,
1363}
1364
1365impl RepairObjectObjective {
1366    pub fn status(&self) -> ObjectiveStatus {
1367        if self.repair_progress >= self.required_progress { ObjectiveStatus::Completed } else { ObjectiveStatus::Active }
1368    }
1369    pub fn progress(&self) -> f32 { (self.repair_progress / self.required_progress.max(0.001)).min(1.0) }
1370    pub fn process_event(&mut self, event: &QuestEvent) {
1371        if let QuestEvent::ObjectRepaired { object_id, progress } = event {
1372            if object_id == &self.object_id { self.repair_progress = *progress; }
1373        }
1374    }
1375}
1376
1377// --- Plant Device ---
1378#[derive(Debug, Clone)]
1379pub struct PlantDeviceObjective {
1380    pub device_id: String,
1381    pub location_name: String,
1382    pub target_objects: Vec<String>,
1383    pub planted_on: Vec<String>,
1384    pub required_count: u32,
1385    pub undetected: bool,
1386    pub was_detected: bool,
1387}
1388
1389impl PlantDeviceObjective {
1390    pub fn status(&self) -> ObjectiveStatus {
1391        if self.undetected && self.was_detected { return ObjectiveStatus::Failed; }
1392        if self.planted_on.len() >= self.required_count as usize { ObjectiveStatus::Completed } else { ObjectiveStatus::Active }
1393    }
1394    pub fn progress(&self) -> f32 { (self.planted_on.len() as f32 / self.required_count.max(1) as f32).min(1.0) }
1395    pub fn process_event(&mut self, event: &QuestEvent) {
1396        match event {
1397            QuestEvent::DevicePlanted { device_id, object_id } => {
1398                if device_id == &self.device_id && self.target_objects.contains(object_id) {
1399                    if !self.planted_on.contains(object_id) { self.planted_on.push(object_id.clone()); }
1400                }
1401            }
1402            QuestEvent::PlayerDetected => { self.was_detected = true; }
1403            _ => {}
1404        }
1405    }
1406}
1407
1408// --- Hack Terminal ---
1409#[derive(Debug, Clone)]
1410pub struct HackTerminalObjective {
1411    pub terminal_id: String,
1412    pub terminal_name: String,
1413    pub hack_progress: f32,
1414    pub required_progress: f32,
1415    pub difficulty: f32,
1416    pub failed_attempts: u32,
1417    pub max_attempts: Option<u32>,
1418    pub locked_out: bool,
1419}
1420
1421impl HackTerminalObjective {
1422    pub fn status(&self) -> ObjectiveStatus {
1423        if self.locked_out { ObjectiveStatus::Failed }
1424        else if self.hack_progress >= self.required_progress { ObjectiveStatus::Completed }
1425        else { ObjectiveStatus::Active }
1426    }
1427    pub fn progress(&self) -> f32 { (self.hack_progress / self.required_progress.max(0.001)).min(1.0) }
1428    pub fn process_event(&mut self, event: &QuestEvent) {
1429        match event {
1430            QuestEvent::TerminalHacked { terminal_id, progress, success } => {
1431                if terminal_id == &self.terminal_id {
1432                    if *success {
1433                        self.hack_progress = *progress;
1434                    } else {
1435                        self.failed_attempts += 1;
1436                        if let Some(max) = self.max_attempts {
1437                            if self.failed_attempts >= max { self.locked_out = true; }
1438                        }
1439                    }
1440                }
1441            }
1442            _ => {}
1443        }
1444    }
1445}
1446
1447// --- Sneak Past ---
1448#[derive(Debug, Clone)]
1449pub struct SneakPastObjective {
1450    pub area_id: String,
1451    pub guard_count: u32,
1452    pub guards_passed: u32,
1453    pub was_detected: bool,
1454    pub destination_reached: bool,
1455}
1456
1457impl SneakPastObjective {
1458    pub fn status(&self) -> ObjectiveStatus {
1459        if self.was_detected { ObjectiveStatus::Failed }
1460        else if self.destination_reached { ObjectiveStatus::Completed }
1461        else { ObjectiveStatus::Active }
1462    }
1463    pub fn progress(&self) -> f32 { if self.destination_reached { 1.0 } else { 0.0 } }
1464    pub fn process_event(&mut self, event: &QuestEvent) {
1465        match event {
1466            QuestEvent::PlayerDetected => { self.was_detected = true; }
1467            QuestEvent::PlayerMoved { position, .. } => {}
1468            _ => {}
1469        }
1470    }
1471}
1472
1473// --- Pickpocket Target ---
1474#[derive(Debug, Clone)]
1475pub struct PickpocketTargetObjective {
1476    pub target_id: String,
1477    pub target_name: String,
1478    pub item_id: String,
1479    pub stolen: bool,
1480    pub without_detection: bool,
1481    pub detected: bool,
1482}
1483
1484impl PickpocketTargetObjective {
1485    pub fn status(&self) -> ObjectiveStatus {
1486        if self.without_detection && self.detected { ObjectiveStatus::Failed }
1487        else if self.stolen { ObjectiveStatus::Completed }
1488        else { ObjectiveStatus::Active }
1489    }
1490    pub fn progress(&self) -> f32 { if self.stolen { 1.0 } else { 0.0 } }
1491    pub fn process_event(&mut self, event: &QuestEvent) {
1492        match event {
1493            QuestEvent::Pickpocketed { target_id, item_id, detected } => {
1494                if target_id == &self.target_id && item_id == &self.item_id {
1495                    self.stolen = true;
1496                    if *detected { self.detected = true; }
1497                }
1498            }
1499            _ => {}
1500        }
1501    }
1502}
1503
1504// ============================================================
1505// SECTION 3: QUEST EVENTS
1506// ============================================================
1507
1508#[derive(Debug, Clone)]
1509pub enum QuestEvent {
1510    EnemyKilled { enemy_type: String, enemy_id: Option<String>, weapon_type: Option<String>, is_stealth: bool },
1511    ItemPickedUp { item_id: String, quantity: u32, quality: f32 },
1512    PlayerMoved { position: Vec3, is_in_combat: bool },
1513    DialogueStarted { npc_id: String },
1514    DialogueEnded { npc_id: String, ending: String },
1515    NPCDied { npc_id: String },
1516    NPCDamaged { npc_id: String, health_percent: f32 },
1517    NPCMoved { npc_id: String, position: Vec3 },
1518    PuzzleSolved { puzzle_id: String },
1519    PuzzleAttempted { puzzle_id: String, success: bool },
1520    ItemCrafted { item_id: String, quality: f32, station_id: Option<String> },
1521    AbilityUsed { ability_id: String, hit: bool, target_type: Option<String> },
1522    PhotoTaken { subject_id: String, quality: f32, undetected: bool },
1523    StructureBuilt { structure_id: String, progress: f32 },
1524    ResourcePlaced { structure_id: String, resource_id: String, amount: u32 },
1525    BossPhaseCompleted { boss_id: String, phase: u32 },
1526    SecretFound { secret_id: String },
1527    ItemDelivered { item_id: String, recipient_id: String, quantity: u32 },
1528    ClueFound { clue_id: String },
1529    ObjectRepaired { object_id: String, progress: f32 },
1530    DevicePlanted { device_id: String, object_id: String },
1531    PlayerDetected,
1532    PlayerDied,
1533    TerminalHacked { terminal_id: String, progress: f32, success: bool },
1534    Pickpocketed { target_id: String, item_id: String, detected: bool },
1535    TimePassed { delta: f32 },
1536    ObjectInteracted { object_id: String },
1537    CustomEvent { name: String, data: HashMap<String, String> },
1538}
1539
1540// ============================================================
1541// SECTION 4: COMPLETION CONDITIONS & REWARD SYSTEM
1542// ============================================================
1543
1544#[derive(Debug, Clone)]
1545pub struct QuestCondition {
1546    pub condition_type: QuestConditionType,
1547    pub negated: bool,
1548}
1549
1550#[derive(Debug, Clone)]
1551pub enum QuestConditionType {
1552    QuestState { quest_id: String, state: QuestState },
1553    PlayerLevel { min: u32, max: Option<u32> },
1554    FactionRep { faction_id: String, min: i32, max: Option<i32> },
1555    HasItem { item_id: String, quantity: u32 },
1556    FlagSet(String),
1557    TimeSince { event: String, secs: f32 },
1558    ObjectiveComplete { quest_id: String, objective_id: String },
1559    PlayerStat { stat: String, min: f32, max: Option<f32> },
1560}
1561
1562impl QuestCondition {
1563    pub fn evaluate(&self, state: &QuestStateStore) -> bool {
1564        let result = match &self.condition_type {
1565            QuestConditionType::QuestState { quest_id, state: expected } => {
1566                state.get_quest_state(quest_id) == *expected
1567            }
1568            QuestConditionType::PlayerLevel { min, max } => {
1569                let level = state.player_level;
1570                level >= *min && max.map(|m| level <= m).unwrap_or(true)
1571            }
1572            QuestConditionType::FactionRep { faction_id, min, max } => {
1573                let rep = state.get_faction_rep(faction_id);
1574                rep >= *min && max.map(|m| rep <= m).unwrap_or(true)
1575            }
1576            QuestConditionType::HasItem { item_id, quantity } => {
1577                state.get_item_count(item_id) >= *quantity
1578            }
1579            QuestConditionType::FlagSet(flag) => {
1580                state.get_flag(flag)
1581            }
1582            QuestConditionType::TimeSince { event, secs } => {
1583                state.get_time_since(event) >= *secs
1584            }
1585            QuestConditionType::ObjectiveComplete { quest_id, objective_id } => {
1586                state.is_objective_complete(quest_id, objective_id)
1587            }
1588            QuestConditionType::PlayerStat { stat, min, max } => {
1589                let val = state.get_player_stat(stat);
1590                val >= *min && max.map(|m| val <= m).unwrap_or(true)
1591            }
1592        };
1593        if self.negated { !result } else { result }
1594    }
1595}
1596
1597#[derive(Debug, Clone)]
1598pub struct RewardTable {
1599    pub xp_reward: XpReward,
1600    pub item_rewards: Vec<ItemRewardEntry>,
1601    pub currency_rewards: Vec<CurrencyReward>,
1602    pub reputation_rewards: Vec<ReputationReward>,
1603    pub unlock_rewards: Vec<UnlockReward>,
1604    pub skill_rewards: Vec<SkillReward>,
1605}
1606
1607impl RewardTable {
1608    pub fn new() -> Self {
1609        RewardTable {
1610            xp_reward: XpReward::default(),
1611            item_rewards: Vec::new(),
1612            currency_rewards: Vec::new(),
1613            reputation_rewards: Vec::new(),
1614            unlock_rewards: Vec::new(),
1615            skill_rewards: Vec::new(),
1616        }
1617    }
1618
1619    pub fn roll(&self, player_level: u32, rng: &mut SimpleRng) -> Vec<RolledReward> {
1620        let mut rewards = Vec::new();
1621        // XP
1622        let xp = self.xp_reward.calculate(player_level);
1623        if xp > 0 {
1624            rewards.push(RolledReward::Xp(xp));
1625        }
1626        // Items
1627        for item_entry in &self.item_rewards {
1628            if rng.next_f32() <= item_entry.drop_chance {
1629                let quantity = item_entry.roll_quantity(rng);
1630                let quality = item_entry.roll_quality(rng);
1631                rewards.push(RolledReward::Item {
1632                    item_id: item_entry.item_id.clone(),
1633                    quantity,
1634                    quality,
1635                    rarity: item_entry.rarity.clone(),
1636                });
1637            }
1638        }
1639        // Currency
1640        for currency in &self.currency_rewards {
1641            let amount = currency.roll_amount(player_level, rng);
1642            if amount > 0 {
1643                rewards.push(RolledReward::Currency {
1644                    currency_id: currency.currency_id.clone(),
1645                    amount,
1646                });
1647            }
1648        }
1649        // Reputation
1650        for rep in &self.reputation_rewards {
1651            rewards.push(RolledReward::Reputation {
1652                faction_id: rep.faction_id.clone(),
1653                delta: rep.calculate_delta(player_level),
1654            });
1655        }
1656        // Unlocks
1657        for unlock in &self.unlock_rewards {
1658            rewards.push(RolledReward::Unlock { unlock_id: unlock.unlock_id.clone(), unlock_type: unlock.unlock_type.clone() });
1659        }
1660        rewards
1661    }
1662
1663    pub fn add_item(&mut self, item_id: &str, quantity_min: u32, quantity_max: u32, drop_chance: f32, rarity: ItemRarity) {
1664        self.item_rewards.push(ItemRewardEntry {
1665            item_id: item_id.to_string(),
1666            quantity_min,
1667            quantity_max,
1668            drop_chance,
1669            rarity,
1670            quality_min: 0.5,
1671            quality_max: 1.0,
1672            quality_weights: Vec::new(),
1673        });
1674    }
1675
1676    pub fn add_xp(&mut self, base: u32, level_scale: f32) {
1677        self.xp_reward = XpReward { base, level_scale_factor: level_scale, level_cap: 100, diminish_above_level: None };
1678    }
1679
1680    pub fn add_currency(&mut self, currency_id: &str, base_min: u32, base_max: u32, level_scale: f32) {
1681        self.currency_rewards.push(CurrencyReward {
1682            currency_id: currency_id.to_string(),
1683            base_amount_min: base_min,
1684            base_amount_max: base_max,
1685            level_scale_factor: level_scale,
1686        });
1687    }
1688
1689    pub fn total_estimated_xp(&self, player_level: u32) -> u32 {
1690        self.xp_reward.calculate(player_level)
1691    }
1692}
1693
1694#[derive(Debug, Clone, Default)]
1695pub struct XpReward {
1696    pub base: u32,
1697    pub level_scale_factor: f32,
1698    pub level_cap: u32,
1699    pub diminish_above_level: Option<u32>,
1700}
1701
1702impl XpReward {
1703    pub fn calculate(&self, player_level: u32) -> u32 {
1704        let level = player_level.min(self.level_cap);
1705        let scaled = self.base as f32 * (1.0 + level as f32 * self.level_scale_factor);
1706        if let Some(cap_level) = self.diminish_above_level {
1707            if player_level > cap_level {
1708                let excess = (player_level - cap_level) as f32;
1709                let diminish = 1.0 / (1.0 + excess * 0.1);
1710                return (scaled * diminish) as u32;
1711            }
1712        }
1713        scaled as u32
1714    }
1715}
1716
1717#[derive(Debug, Clone)]
1718pub struct ItemRewardEntry {
1719    pub item_id: String,
1720    pub quantity_min: u32,
1721    pub quantity_max: u32,
1722    pub drop_chance: f32,
1723    pub rarity: ItemRarity,
1724    pub quality_min: f32,
1725    pub quality_max: f32,
1726    pub quality_weights: Vec<(f32, f32)>,
1727}
1728
1729impl ItemRewardEntry {
1730    pub fn roll_quantity(&self, rng: &mut SimpleRng) -> u32 {
1731        if self.quantity_min >= self.quantity_max { return self.quantity_min; }
1732        self.quantity_min + rng.next_u32(self.quantity_max - self.quantity_min + 1)
1733    }
1734    pub fn roll_quality(&self, rng: &mut SimpleRng) -> f32 {
1735        let t = rng.next_f32();
1736        self.quality_min + t * (self.quality_max - self.quality_min)
1737    }
1738}
1739
1740#[derive(Debug, Clone, PartialEq)]
1741pub enum ItemRarity {
1742    Common,
1743    Uncommon,
1744    Rare,
1745    Epic,
1746    Legendary,
1747    Mythic,
1748}
1749
1750impl ItemRarity {
1751    pub fn display_color(&self) -> Vec4 {
1752        match self {
1753            ItemRarity::Common => Vec4::new(0.8, 0.8, 0.8, 1.0),
1754            ItemRarity::Uncommon => Vec4::new(0.2, 0.8, 0.2, 1.0),
1755            ItemRarity::Rare => Vec4::new(0.2, 0.4, 1.0, 1.0),
1756            ItemRarity::Epic => Vec4::new(0.6, 0.1, 0.9, 1.0),
1757            ItemRarity::Legendary => Vec4::new(1.0, 0.6, 0.0, 1.0),
1758            ItemRarity::Mythic => Vec4::new(1.0, 0.0, 0.3, 1.0),
1759        }
1760    }
1761
1762    pub fn drop_chance_modifier(&self) -> f32 {
1763        match self {
1764            ItemRarity::Common => 1.0,
1765            ItemRarity::Uncommon => 0.5,
1766            ItemRarity::Rare => 0.2,
1767            ItemRarity::Epic => 0.05,
1768            ItemRarity::Legendary => 0.01,
1769            ItemRarity::Mythic => 0.001,
1770        }
1771    }
1772}
1773
1774#[derive(Debug, Clone)]
1775pub struct CurrencyReward {
1776    pub currency_id: String,
1777    pub base_amount_min: u32,
1778    pub base_amount_max: u32,
1779    pub level_scale_factor: f32,
1780}
1781
1782impl CurrencyReward {
1783    pub fn roll_amount(&self, player_level: u32, rng: &mut SimpleRng) -> u32 {
1784        let range = self.base_amount_max - self.base_amount_min;
1785        let base = if range > 0 { self.base_amount_min + rng.next_u32(range + 1) } else { self.base_amount_min };
1786        let scaled = base as f32 * (1.0 + player_level as f32 * self.level_scale_factor);
1787        scaled as u32
1788    }
1789}
1790
1791#[derive(Debug, Clone)]
1792pub struct ReputationReward {
1793    pub faction_id: String,
1794    pub base_delta: i32,
1795    pub level_scale: f32,
1796    pub related_factions: Vec<(String, f32)>,
1797}
1798
1799impl ReputationReward {
1800    pub fn calculate_delta(&self, player_level: u32) -> i32 {
1801        let scaled = self.base_delta as f32 * (1.0 + player_level as f32 * self.level_scale);
1802        scaled as i32
1803    }
1804}
1805
1806#[derive(Debug, Clone)]
1807pub struct UnlockReward {
1808    pub unlock_id: String,
1809    pub unlock_type: UnlockType,
1810    pub display_name: String,
1811}
1812
1813#[derive(Debug, Clone, PartialEq)]
1814pub enum UnlockType {
1815    Ability,
1816    Recipe,
1817    Area,
1818    Item,
1819    NPC,
1820    Quest,
1821}
1822
1823#[derive(Debug, Clone)]
1824pub struct SkillReward {
1825    pub skill_id: String,
1826    pub skill_name: String,
1827    pub points: u32,
1828}
1829
1830#[derive(Debug, Clone)]
1831pub enum RolledReward {
1832    Xp(u32),
1833    Item { item_id: String, quantity: u32, quality: f32, rarity: ItemRarity },
1834    Currency { currency_id: String, amount: u32 },
1835    Reputation { faction_id: String, delta: i32 },
1836    Unlock { unlock_id: String, unlock_type: UnlockType },
1837    Skill { skill_id: String, points: u32 },
1838}
1839
1840impl RolledReward {
1841    pub fn display_text(&self) -> String {
1842        match self {
1843            RolledReward::Xp(amount) => format!("+{} XP", amount),
1844            RolledReward::Item { item_id, quantity, rarity, .. } => {
1845                format!("{} x{} ({:?})", item_id, quantity, rarity)
1846            }
1847            RolledReward::Currency { currency_id, amount } => format!("+{} {}", amount, currency_id),
1848            RolledReward::Reputation { faction_id, delta } => {
1849                format!("{}: {:+}", faction_id, delta)
1850            }
1851            RolledReward::Unlock { unlock_id, .. } => format!("Unlocked: {}", unlock_id),
1852            RolledReward::Skill { skill_id, points } => format!("{} +{} pts", skill_id, points),
1853        }
1854    }
1855}
1856
1857// ============================================================
1858// SECTION 5: QUEST CHAINS & PREREQUISITES
1859// ============================================================
1860
1861#[derive(Debug, Clone)]
1862pub struct QuestChain {
1863    pub chain_id: String,
1864    pub chain_name: String,
1865    pub chain_type: ChainType,
1866    pub quests: Vec<ChainQuestEntry>,
1867    pub description: String,
1868    pub icon: String,
1869    pub completed: bool,
1870    pub active_quest_idx: usize,
1871}
1872
1873#[derive(Debug, Clone, PartialEq)]
1874pub enum ChainType {
1875    Sequential,
1876    Parallel,
1877    Mutex,
1878    Branching,
1879    Repeatable { cooldown: f32 },
1880}
1881
1882#[derive(Debug, Clone)]
1883pub struct ChainQuestEntry {
1884    pub quest_id: String,
1885    pub required: bool,
1886    pub auto_start: bool,
1887    pub completion_condition: Option<QuestCondition>,
1888    pub unlock_condition: Option<QuestCondition>,
1889    pub branch_id: Option<String>,
1890    pub order: u32,
1891}
1892
1893impl QuestChain {
1894    pub fn new(chain_id: String, name: String, chain_type: ChainType) -> Self {
1895        QuestChain {
1896            chain_id,
1897            chain_name: name,
1898            chain_type,
1899            quests: Vec::new(),
1900            description: String::new(),
1901            icon: String::new(),
1902            completed: false,
1903            active_quest_idx: 0,
1904        }
1905    }
1906
1907    pub fn add_quest(&mut self, quest_id: &str, required: bool) {
1908        let order = self.quests.len() as u32;
1909        self.quests.push(ChainQuestEntry {
1910            quest_id: quest_id.to_string(),
1911            required,
1912            auto_start: false,
1913            completion_condition: None,
1914            unlock_condition: None,
1915            branch_id: None,
1916            order,
1917        });
1918    }
1919
1920    pub fn get_next_quests(&self, state: &QuestStateStore) -> Vec<&str> {
1921        match &self.chain_type {
1922            ChainType::Sequential => {
1923                let mut result = Vec::new();
1924                for entry in &self.quests {
1925                    let quest_state = state.get_quest_state(&entry.quest_id);
1926                    if quest_state == QuestState::NotStarted {
1927                        // Check if all previous quests are done
1928                        let prev_done = self.quests.iter()
1929                            .filter(|e| e.order < entry.order && e.required)
1930                            .all(|e| state.get_quest_state(&e.quest_id) == QuestState::Completed);
1931                        if prev_done {
1932                            result.push(entry.quest_id.as_str());
1933                        }
1934                        break;
1935                    }
1936                }
1937                result
1938            }
1939            ChainType::Parallel => {
1940                self.quests.iter()
1941                    .filter(|e| state.get_quest_state(&e.quest_id) == QuestState::NotStarted)
1942                    .map(|e| e.quest_id.as_str())
1943                    .collect()
1944            }
1945            ChainType::Mutex => {
1946                let any_active = self.quests.iter().any(|e| state.get_quest_state(&e.quest_id) == QuestState::Active);
1947                if any_active { return Vec::new(); }
1948                self.quests.iter()
1949                    .filter(|e| state.get_quest_state(&e.quest_id) == QuestState::NotStarted)
1950                    .map(|e| e.quest_id.as_str())
1951                    .take(1)
1952                    .collect()
1953            }
1954            _ => Vec::new(),
1955        }
1956    }
1957
1958    pub fn is_chain_complete(&self, state: &QuestStateStore) -> bool {
1959        match &self.chain_type {
1960            ChainType::Sequential | ChainType::Parallel => {
1961                self.quests.iter()
1962                    .filter(|e| e.required)
1963                    .all(|e| state.get_quest_state(&e.quest_id) == QuestState::Completed)
1964            }
1965            ChainType::Mutex => {
1966                self.quests.iter()
1967                    .any(|e| state.get_quest_state(&e.quest_id) == QuestState::Completed)
1968            }
1969            _ => false,
1970        }
1971    }
1972
1973    pub fn get_completion_percentage(&self, state: &QuestStateStore) -> f32 {
1974        if self.quests.is_empty() { return 0.0; }
1975        let completed = self.quests.iter()
1976            .filter(|e| state.get_quest_state(&e.quest_id) == QuestState::Completed)
1977            .count();
1978        completed as f32 / self.quests.len() as f32
1979    }
1980
1981    pub fn get_cooldown_remaining(&self, state: &QuestStateStore, current_time: f32) -> Option<f32> {
1982        if let ChainType::Repeatable { cooldown } = &self.chain_type {
1983            let last = state.get_chain_last_completion(&self.chain_id);
1984            let remaining = cooldown - (current_time - last);
1985            if remaining > 0.0 { Some(remaining) } else { None }
1986        } else {
1987            None
1988        }
1989    }
1990}
1991
1992// ============================================================
1993// SECTION 6: FACTION REPUTATION
1994// ============================================================
1995
1996#[derive(Debug, Clone)]
1997pub struct FactionSystem {
1998    pub factions: HashMap<String, Faction>,
1999    pub relationships: HashMap<(String, String), FactionRelationship>,
2000    pub rep_tiers: Vec<RepTier>,
2001    pub reputation_events: Vec<RepEvent>,
2002}
2003
2004#[derive(Debug, Clone)]
2005pub struct Faction {
2006    pub id: String,
2007    pub name: String,
2008    pub description: String,
2009    pub reputation: i32,
2010    pub reputation_min: i32,
2011    pub reputation_max: i32,
2012    pub current_tier: ReputationTier,
2013    pub allied_factions: Vec<String>,
2014    pub enemy_factions: Vec<String>,
2015    pub neutral_factions: Vec<String>,
2016    pub color: Vec4,
2017    pub icon: String,
2018    pub is_player_faction: bool,
2019}
2020
2021#[derive(Debug, Clone, PartialEq)]
2022pub enum ReputationTier {
2023    Hated,
2024    Hostile,
2025    Unfriendly,
2026    Neutral,
2027    Friendly,
2028    Honored,
2029    Revered,
2030    Exalted,
2031}
2032
2033impl ReputationTier {
2034    pub fn from_rep(rep: i32) -> Self {
2035        match rep {
2036            i32::MIN..=-3000 => ReputationTier::Hated,
2037            -2999..=-2000 => ReputationTier::Hostile,
2038            -1999..=-1000 => ReputationTier::Unfriendly,
2039            -999..=999 => ReputationTier::Neutral,
2040            1000..=1999 => ReputationTier::Friendly,
2041            2000..=2999 => ReputationTier::Honored,
2042            3000..=3999 => ReputationTier::Revered,
2043            _ => ReputationTier::Exalted,
2044        }
2045    }
2046
2047    pub fn display_name(&self) -> &str {
2048        match self {
2049            ReputationTier::Hated => "Hated",
2050            ReputationTier::Hostile => "Hostile",
2051            ReputationTier::Unfriendly => "Unfriendly",
2052            ReputationTier::Neutral => "Neutral",
2053            ReputationTier::Friendly => "Friendly",
2054            ReputationTier::Honored => "Honored",
2055            ReputationTier::Revered => "Revered",
2056            ReputationTier::Exalted => "Exalted",
2057        }
2058    }
2059
2060    pub fn next_tier_threshold(&self) -> Option<i32> {
2061        match self {
2062            ReputationTier::Hated => Some(-3000),
2063            ReputationTier::Hostile => Some(-2000),
2064            ReputationTier::Unfriendly => Some(-1000),
2065            ReputationTier::Neutral => Some(1000),
2066            ReputationTier::Friendly => Some(2000),
2067            ReputationTier::Honored => Some(3000),
2068            ReputationTier::Revered => Some(4000),
2069            ReputationTier::Exalted => None,
2070        }
2071    }
2072
2073    pub fn color(&self) -> Vec4 {
2074        match self {
2075            ReputationTier::Hated => Vec4::new(0.8, 0.0, 0.0, 1.0),
2076            ReputationTier::Hostile => Vec4::new(0.9, 0.2, 0.1, 1.0),
2077            ReputationTier::Unfriendly => Vec4::new(0.9, 0.5, 0.1, 1.0),
2078            ReputationTier::Neutral => Vec4::new(0.8, 0.8, 0.8, 1.0),
2079            ReputationTier::Friendly => Vec4::new(0.4, 0.8, 0.4, 1.0),
2080            ReputationTier::Honored => Vec4::new(0.2, 0.9, 0.3, 1.0),
2081            ReputationTier::Revered => Vec4::new(0.2, 0.6, 1.0, 1.0),
2082            ReputationTier::Exalted => Vec4::new(1.0, 0.9, 0.0, 1.0),
2083        }
2084    }
2085}
2086
2087#[derive(Debug, Clone)]
2088pub struct FactionRelationship {
2089    pub faction_a: String,
2090    pub faction_b: String,
2091    pub relationship_type: RelationshipType,
2092    pub rep_share_factor: f32,
2093}
2094
2095#[derive(Debug, Clone, PartialEq)]
2096pub enum RelationshipType {
2097    Allied,
2098    Enemy,
2099    Neutral,
2100    Rival,
2101}
2102
2103#[derive(Debug, Clone)]
2104pub struct RepTier {
2105    pub tier: ReputationTier,
2106    pub min_rep: i32,
2107    pub unlocks: Vec<String>,
2108    pub icon: String,
2109    pub description: String,
2110}
2111
2112#[derive(Debug, Clone)]
2113pub struct RepEvent {
2114    pub faction_id: String,
2115    pub delta: i32,
2116    pub reason: String,
2117    pub timestamp: f64,
2118    pub quest_id: Option<String>,
2119}
2120
2121impl FactionSystem {
2122    pub fn new() -> Self {
2123        let mut sys = FactionSystem {
2124            factions: HashMap::new(),
2125            relationships: HashMap::new(),
2126            rep_tiers: Self::default_tiers(),
2127            reputation_events: Vec::new(),
2128        };
2129        sys
2130    }
2131
2132    fn default_tiers() -> Vec<RepTier> {
2133        vec![
2134            RepTier { tier: ReputationTier::Hated, min_rep: i32::MIN, unlocks: Vec::new(), icon: "hated".into(), description: "They want you dead".into() },
2135            RepTier { tier: ReputationTier::Hostile, min_rep: -3000, unlocks: Vec::new(), icon: "hostile".into(), description: "Attack on sight".into() },
2136            RepTier { tier: ReputationTier::Unfriendly, min_rep: -2000, unlocks: Vec::new(), icon: "unfriendly".into(), description: "Uncomfortable".into() },
2137            RepTier { tier: ReputationTier::Neutral, min_rep: -1000, unlocks: Vec::new(), icon: "neutral".into(), description: "Neither ally nor enemy".into() },
2138            RepTier { tier: ReputationTier::Friendly, min_rep: 1000, unlocks: vec!["basic_trade".into()], icon: "friendly".into(), description: "Warmly received".into() },
2139            RepTier { tier: ReputationTier::Honored, min_rep: 2000, unlocks: vec!["discount_trade".into()], icon: "honored".into(), description: "Deep respect".into() },
2140            RepTier { tier: ReputationTier::Revered, min_rep: 3000, unlocks: vec!["special_items".into()], icon: "revered".into(), description: "Near legendary status".into() },
2141            RepTier { tier: ReputationTier::Exalted, min_rep: 4000, unlocks: vec!["exclusive_rewards".into(), "faction_title".into()], icon: "exalted".into(), description: "A true champion".into() },
2142        ]
2143    }
2144
2145    pub fn add_faction(&mut self, faction: Faction) {
2146        self.factions.insert(faction.id.clone(), faction);
2147    }
2148
2149    pub fn apply_rep_delta(&mut self, faction_id: &str, delta: i32, reason: &str, quest_id: Option<&str>) {
2150        // Apply to main faction
2151        if let Some(faction) = self.factions.get_mut(faction_id) {
2152            let new_rep = (faction.reputation + delta).clamp(faction.reputation_min, faction.reputation_max);
2153            let old_tier = faction.current_tier.clone();
2154            faction.reputation = new_rep;
2155            faction.current_tier = ReputationTier::from_rep(new_rep);
2156            self.reputation_events.push(RepEvent {
2157                faction_id: faction_id.to_string(),
2158                delta,
2159                reason: reason.to_string(),
2160                timestamp: 0.0,
2161                quest_id: quest_id.map(|s| s.to_string()),
2162            });
2163        }
2164        // Propagate to related factions
2165        let related: Vec<(String, f32)> = self.relationships.iter()
2166            .filter(|((a, b), _)| a == faction_id || b == faction_id)
2167            .map(|((a, b), rel)| {
2168                let other = if a == faction_id { b.clone() } else { a.clone() };
2169                (other, rel.rep_share_factor * if rel.relationship_type == RelationshipType::Enemy { -1.0 } else { 1.0 })
2170            })
2171            .collect();
2172        for (other_id, factor) in related {
2173            if let Some(other_faction) = self.factions.get_mut(&other_id) {
2174                let shared_delta = (delta as f32 * factor) as i32;
2175                let new_rep = (other_faction.reputation + shared_delta).clamp(other_faction.reputation_min, other_faction.reputation_max);
2176                other_faction.reputation = new_rep;
2177                other_faction.current_tier = ReputationTier::from_rep(new_rep);
2178            }
2179        }
2180    }
2181
2182    pub fn get_tier_unlocks(&self, faction_id: &str) -> Vec<String> {
2183        if let Some(faction) = self.factions.get(faction_id) {
2184            let tier = &faction.current_tier;
2185            for rep_tier in &self.rep_tiers {
2186                if &rep_tier.tier == tier {
2187                    return rep_tier.unlocks.clone();
2188                }
2189            }
2190        }
2191        Vec::new()
2192    }
2193
2194    pub fn get_relationship(&self, faction_a: &str, faction_b: &str) -> Option<&FactionRelationship> {
2195        self.relationships.get(&(faction_a.to_string(), faction_b.to_string()))
2196            .or_else(|| self.relationships.get(&(faction_b.to_string(), faction_a.to_string())))
2197    }
2198
2199    pub fn add_relationship(&mut self, faction_a: &str, faction_b: &str, rel_type: RelationshipType, share_factor: f32) {
2200        self.relationships.insert(
2201            (faction_a.to_string(), faction_b.to_string()),
2202            FactionRelationship {
2203                faction_a: faction_a.to_string(),
2204                faction_b: faction_b.to_string(),
2205                relationship_type: rel_type,
2206                rep_share_factor: share_factor,
2207            }
2208        );
2209    }
2210
2211    pub fn progress_to_next_tier(&self, faction_id: &str) -> Option<f32> {
2212        if let Some(faction) = self.factions.get(faction_id) {
2213            let current_threshold = match &faction.current_tier {
2214                ReputationTier::Hated => i32::MIN,
2215                ReputationTier::Hostile => -3000,
2216                ReputationTier::Unfriendly => -2000,
2217                ReputationTier::Neutral => -1000,
2218                ReputationTier::Friendly => 1000,
2219                ReputationTier::Honored => 2000,
2220                ReputationTier::Revered => 3000,
2221                ReputationTier::Exalted => return None,
2222            };
2223            if let Some(next) = faction.current_tier.next_tier_threshold() {
2224                let range = next - current_threshold;
2225                let progress = faction.reputation - current_threshold;
2226                return Some((progress as f32 / range as f32).clamp(0.0, 1.0));
2227            }
2228        }
2229        None
2230    }
2231}
2232
2233// ============================================================
2234// SECTION 7: JOURNAL SYSTEM
2235// ============================================================
2236
2237#[derive(Debug, Clone)]
2238pub struct JournalSystem {
2239    pub entries: Vec<JournalEntry>,
2240    pub active_quest_entries: HashMap<String, usize>,
2241    pub clues: Vec<ClueEntry>,
2242    pub map_markers: Vec<MapMarker>,
2243    pub notification_queue: VecDeque<JournalNotification>,
2244    pub max_entries: usize,
2245}
2246
2247#[derive(Debug, Clone)]
2248pub struct JournalEntry {
2249    pub entry_id: String,
2250    pub quest_id: String,
2251    pub timestamp: f64,
2252    pub entry_type: JournalEntryType,
2253    pub title: String,
2254    pub content: String,
2255    pub read: bool,
2256    pub important: bool,
2257    pub category: JournalCategory,
2258    pub media: Vec<JournalMedia>,
2259    pub linked_entries: Vec<String>,
2260    pub linked_map_markers: Vec<String>,
2261}
2262
2263#[derive(Debug, Clone, PartialEq)]
2264pub enum JournalEntryType {
2265    QuestStart,
2266    QuestUpdate,
2267    QuestComplete,
2268    QuestFail,
2269    ObjectiveComplete,
2270    ClueDiscovered,
2271    NPCEncounter,
2272    LocationDiscovered,
2273    LoreEntry,
2274    PlayerNote,
2275}
2276
2277#[derive(Debug, Clone, PartialEq)]
2278pub enum JournalCategory {
2279    MainQuest,
2280    SideQuest,
2281    WorldEvent,
2282    Character,
2283    Location,
2284    Lore,
2285    Bestiary,
2286    Crafting,
2287}
2288
2289#[derive(Debug, Clone)]
2290pub struct JournalMedia {
2291    pub media_type: JournalMediaType,
2292    pub path: String,
2293    pub caption: String,
2294}
2295
2296#[derive(Debug, Clone)]
2297pub enum JournalMediaType {
2298    Image,
2299    Map,
2300    Portrait,
2301    Codex,
2302}
2303
2304#[derive(Debug, Clone)]
2305pub struct ClueEntry {
2306    pub clue_id: String,
2307    pub quest_id: String,
2308    pub title: String,
2309    pub description: String,
2310    pub found_at: String,
2311    pub connects_to: Vec<String>,
2312    pub is_red_herring: bool,
2313    pub solved: bool,
2314    pub location: Option<Vec3>,
2315}
2316
2317#[derive(Debug, Clone)]
2318pub struct MapMarker {
2319    pub marker_id: String,
2320    pub quest_id: Option<String>,
2321    pub marker_type: MapMarkerType,
2322    pub position: Vec3,
2323    pub label: String,
2324    pub description: String,
2325    pub icon: String,
2326    pub color: Vec4,
2327    pub visible: bool,
2328    pub completed: bool,
2329    pub tracked: bool,
2330    pub sub_markers: Vec<MapMarker>,
2331    pub priority: i32,
2332}
2333
2334impl MapMarker {
2335    pub fn new(id: &str, marker_type: MapMarkerType, position: Vec3, label: &str) -> Self {
2336        MapMarker {
2337            marker_id: id.to_string(),
2338            quest_id: None,
2339            marker_type,
2340            position,
2341            label: label.to_string(),
2342            description: String::new(),
2343            icon: String::new(),
2344            color: Vec4::new(1.0, 0.9, 0.0, 1.0),
2345            visible: true,
2346            completed: false,
2347            tracked: false,
2348            sub_markers: Vec::new(),
2349            priority: 0,
2350        }
2351    }
2352}
2353
2354#[derive(Debug, Clone, PartialEq)]
2355pub enum MapMarkerType {
2356    QuestObjective,
2357    QuestStart,
2358    QuestEnd,
2359    NPCLocation,
2360    ItemLocation,
2361    DangerZone,
2362    Discoverable,
2363    POI,
2364    FastTravel,
2365    Custom,
2366}
2367
2368#[derive(Debug, Clone)]
2369pub struct JournalNotification {
2370    pub notification_type: JournalNotificationType,
2371    pub title: String,
2372    pub description: String,
2373    pub icon: String,
2374    pub duration: f32,
2375    pub timestamp: f64,
2376}
2377
2378#[derive(Debug, Clone)]
2379pub enum JournalNotificationType {
2380    QuestAdded,
2381    QuestCompleted,
2382    QuestFailed,
2383    ObjectiveUpdated,
2384    ClueFound,
2385    ReputationChange { faction: String, delta: i32 },
2386    RewardReceived { rewards: Vec<String> },
2387}
2388
2389impl JournalSystem {
2390    pub fn new() -> Self {
2391        JournalSystem {
2392            entries: Vec::new(),
2393            active_quest_entries: HashMap::new(),
2394            clues: Vec::new(),
2395            map_markers: Vec::new(),
2396            notification_queue: VecDeque::new(),
2397            max_entries: 1000,
2398        }
2399    }
2400
2401    pub fn add_entry(&mut self, quest_id: &str, entry_type: JournalEntryType, title: &str, content: &str, category: JournalCategory) -> String {
2402        let entry_id = format!("journal_{}_{}", quest_id, self.entries.len());
2403        let entry = JournalEntry {
2404            entry_id: entry_id.clone(),
2405            quest_id: quest_id.to_string(),
2406            timestamp: 0.0,
2407            entry_type: entry_type.clone(),
2408            title: title.to_string(),
2409            content: content.to_string(),
2410            read: false,
2411            important: false,
2412            category,
2413            media: Vec::new(),
2414            linked_entries: Vec::new(),
2415            linked_map_markers: Vec::new(),
2416        };
2417        let idx = self.entries.len();
2418        self.active_quest_entries.insert(quest_id.to_string(), idx);
2419        self.entries.push(entry);
2420        if self.entries.len() > self.max_entries {
2421            self.entries.remove(0);
2422        }
2423        self.notification_queue.push_back(JournalNotification {
2424            notification_type: match entry_type {
2425                JournalEntryType::QuestStart => JournalNotificationType::QuestAdded,
2426                JournalEntryType::QuestComplete => JournalNotificationType::QuestCompleted,
2427                JournalEntryType::QuestFail => JournalNotificationType::QuestFailed,
2428                _ => JournalNotificationType::ObjectiveUpdated,
2429            },
2430            title: title.to_string(),
2431            description: content.chars().take(80).collect(),
2432            icon: String::new(),
2433            duration: 5.0,
2434            timestamp: 0.0,
2435        });
2436        entry_id
2437    }
2438
2439    pub fn add_clue(&mut self, clue: ClueEntry) {
2440        self.clues.push(clue);
2441    }
2442
2443    pub fn add_map_marker(&mut self, marker: MapMarker) {
2444        self.map_markers.push(marker);
2445    }
2446
2447    pub fn remove_marker(&mut self, marker_id: &str) {
2448        self.map_markers.retain(|m| m.marker_id != marker_id);
2449    }
2450
2451    pub fn complete_marker(&mut self, marker_id: &str) {
2452        if let Some(marker) = self.map_markers.iter_mut().find(|m| m.marker_id == marker_id) {
2453            marker.completed = true;
2454        }
2455    }
2456
2457    pub fn get_entries_for_quest(&self, quest_id: &str) -> Vec<&JournalEntry> {
2458        self.entries.iter().filter(|e| e.quest_id == quest_id).collect()
2459    }
2460
2461    pub fn get_active_markers(&self) -> Vec<&MapMarker> {
2462        self.map_markers.iter().filter(|m| m.visible && !m.completed).collect()
2463    }
2464
2465    pub fn get_clue_web_for_quest(&self, quest_id: &str) -> Vec<(&ClueEntry, Vec<&ClueEntry>)> {
2466        let quest_clues: Vec<&ClueEntry> = self.clues.iter().filter(|c| c.quest_id == quest_id).collect();
2467        quest_clues.iter().map(|clue| {
2468            let connected: Vec<&ClueEntry> = clue.connects_to.iter()
2469                .filter_map(|id| self.clues.iter().find(|c| &c.clue_id == id))
2470                .collect();
2471            (*clue, connected)
2472        }).collect()
2473    }
2474
2475    pub fn mark_as_read(&mut self, entry_id: &str) {
2476        if let Some(entry) = self.entries.iter_mut().find(|e| e.entry_id == entry_id) {
2477            entry.read = true;
2478        }
2479    }
2480
2481    pub fn unread_count(&self) -> usize {
2482        self.entries.iter().filter(|e| !e.read).count()
2483    }
2484
2485    pub fn get_notification(&mut self) -> Option<JournalNotification> {
2486        self.notification_queue.pop_front()
2487    }
2488
2489    pub fn sort_entries_by_time(&mut self) {
2490        self.entries.sort_by(|a, b| b.timestamp.partial_cmp(&a.timestamp).unwrap_or(std::cmp::Ordering::Equal));
2491    }
2492}
2493
2494// ============================================================
2495// SECTION 8: QUEST STATE MACHINE
2496// ============================================================
2497
2498#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2499pub enum QuestState {
2500    NotStarted,
2501    Active,
2502    Paused,
2503    Completed,
2504    Failed,
2505    Abandoned,
2506}
2507
2508impl QuestState {
2509    pub fn can_transition_to(&self, new_state: &QuestState) -> bool {
2510        match (self, new_state) {
2511            (QuestState::NotStarted, QuestState::Active) => true,
2512            (QuestState::Active, QuestState::Paused) => true,
2513            (QuestState::Active, QuestState::Completed) => true,
2514            (QuestState::Active, QuestState::Failed) => true,
2515            (QuestState::Active, QuestState::Abandoned) => true,
2516            (QuestState::Paused, QuestState::Active) => true,
2517            (QuestState::Paused, QuestState::Abandoned) => true,
2518            (QuestState::Failed, QuestState::Active) => true, // retry
2519            _ => false,
2520        }
2521    }
2522
2523    pub fn display_name(&self) -> &str {
2524        match self {
2525            QuestState::NotStarted => "Not Started",
2526            QuestState::Active => "Active",
2527            QuestState::Paused => "Paused",
2528            QuestState::Completed => "Completed",
2529            QuestState::Failed => "Failed",
2530            QuestState::Abandoned => "Abandoned",
2531        }
2532    }
2533
2534    pub fn color(&self) -> Vec4 {
2535        match self {
2536            QuestState::NotStarted => Vec4::new(0.5, 0.5, 0.5, 1.0),
2537            QuestState::Active => Vec4::new(0.2, 0.6, 1.0, 1.0),
2538            QuestState::Paused => Vec4::new(0.8, 0.7, 0.1, 1.0),
2539            QuestState::Completed => Vec4::new(0.2, 0.8, 0.2, 1.0),
2540            QuestState::Failed => Vec4::new(0.9, 0.2, 0.2, 1.0),
2541            QuestState::Abandoned => Vec4::new(0.4, 0.4, 0.4, 1.0),
2542        }
2543    }
2544}
2545
2546#[derive(Debug, Clone)]
2547pub struct QuestInstance {
2548    pub quest_id: String,
2549    pub state: QuestState,
2550    pub started_at: f64,
2551    pub completed_at: Option<f64>,
2552    pub failed_at: Option<f64>,
2553    pub paused_at: Option<f64>,
2554    pub active_objective_ids: Vec<String>,
2555    pub completed_objective_ids: Vec<String>,
2556    pub failed_objective_ids: Vec<String>,
2557    pub objective_order: VecDeque<String>,
2558    pub variables: HashMap<String, String>,
2559    pub retry_count: u32,
2560    pub partial_score: f32,
2561    pub tracking: bool,
2562    pub state_history: Vec<(QuestState, f64)>,
2563}
2564
2565impl QuestInstance {
2566    pub fn new(quest_id: &str) -> Self {
2567        QuestInstance {
2568            quest_id: quest_id.to_string(),
2569            state: QuestState::NotStarted,
2570            started_at: 0.0,
2571            completed_at: None,
2572            failed_at: None,
2573            paused_at: None,
2574            active_objective_ids: Vec::new(),
2575            completed_objective_ids: Vec::new(),
2576            failed_objective_ids: Vec::new(),
2577            objective_order: VecDeque::new(),
2578            variables: HashMap::new(),
2579            retry_count: 0,
2580            partial_score: 0.0,
2581            tracking: false,
2582            state_history: Vec::new(),
2583        }
2584    }
2585
2586    pub fn transition(&mut self, new_state: QuestState, timestamp: f64) -> Result<(), String> {
2587        if !self.state.can_transition_to(&new_state) {
2588            return Err(format!("Cannot transition from {:?} to {:?}", self.state, new_state));
2589        }
2590        self.state_history.push((self.state.clone(), timestamp));
2591        match &new_state {
2592            QuestState::Active => {
2593                if self.started_at == 0.0 { self.started_at = timestamp; }
2594                self.paused_at = None;
2595            }
2596            QuestState::Completed => { self.completed_at = Some(timestamp); }
2597            QuestState::Failed => { self.failed_at = Some(timestamp); }
2598            QuestState::Paused => { self.paused_at = Some(timestamp); }
2599            _ => {}
2600        }
2601        self.state = new_state;
2602        Ok(())
2603    }
2604
2605    pub fn complete_objective(&mut self, obj_id: &str) {
2606        self.active_objective_ids.retain(|id| id != obj_id);
2607        if !self.completed_objective_ids.contains(&obj_id.to_string()) {
2608            self.completed_objective_ids.push(obj_id.to_string());
2609        }
2610    }
2611
2612    pub fn fail_objective(&mut self, obj_id: &str) {
2613        self.active_objective_ids.retain(|id| id != obj_id);
2614        if !self.failed_objective_ids.contains(&obj_id.to_string()) {
2615            self.failed_objective_ids.push(obj_id.to_string());
2616        }
2617    }
2618
2619    pub fn add_objective(&mut self, obj_id: &str) {
2620        if !self.active_objective_ids.contains(&obj_id.to_string()) {
2621            self.active_objective_ids.push(obj_id.to_string());
2622            self.objective_order.push_back(obj_id.to_string());
2623        }
2624    }
2625
2626    pub fn calculate_completion_score(&self) -> f32 {
2627        let total = self.completed_objective_ids.len() + self.failed_objective_ids.len();
2628        if total == 0 { return 0.0; }
2629        self.completed_objective_ids.len() as f32 / total as f32
2630    }
2631
2632    pub fn elapsed_time(&self, current_time: f64) -> f64 {
2633        if self.started_at == 0.0 { return 0.0; }
2634        current_time - self.started_at
2635    }
2636}
2637
2638#[derive(Debug, Clone)]
2639pub struct QuestStateStore {
2640    pub quest_instances: HashMap<String, QuestInstance>,
2641    pub faction_reps: HashMap<String, i32>,
2642    pub player_level: u32,
2643    pub player_stats: HashMap<String, f32>,
2644    pub flags: HashSet<String>,
2645    pub item_inventory: HashMap<String, u32>,
2646    pub event_times: HashMap<String, f64>,
2647    pub chain_completions: HashMap<String, f64>,
2648    pub current_time: f64,
2649}
2650
2651impl QuestStateStore {
2652    pub fn new() -> Self {
2653        QuestStateStore {
2654            quest_instances: HashMap::new(),
2655            faction_reps: HashMap::new(),
2656            player_level: 1,
2657            player_stats: HashMap::new(),
2658            flags: HashSet::new(),
2659            item_inventory: HashMap::new(),
2660            event_times: HashMap::new(),
2661            chain_completions: HashMap::new(),
2662            current_time: 0.0,
2663        }
2664    }
2665
2666    pub fn get_quest_state(&self, quest_id: &str) -> QuestState {
2667        self.quest_instances.get(quest_id)
2668            .map(|q| q.state.clone())
2669            .unwrap_or(QuestState::NotStarted)
2670    }
2671
2672    pub fn start_quest(&mut self, quest_id: &str) -> Result<(), String> {
2673        let instance = self.quest_instances.entry(quest_id.to_string()).or_insert_with(|| QuestInstance::new(quest_id));
2674        instance.transition(QuestState::Active, self.current_time)
2675    }
2676
2677    pub fn complete_quest(&mut self, quest_id: &str) -> Result<(), String> {
2678        let time = self.current_time;
2679        if let Some(instance) = self.quest_instances.get_mut(quest_id) {
2680            instance.transition(QuestState::Completed, time)
2681        } else {
2682            Err(format!("Quest {} not found", quest_id))
2683        }
2684    }
2685
2686    pub fn fail_quest(&mut self, quest_id: &str) -> Result<(), String> {
2687        let time = self.current_time;
2688        if let Some(instance) = self.quest_instances.get_mut(quest_id) {
2689            instance.transition(QuestState::Failed, time)
2690        } else {
2691            Err(format!("Quest {} not found", quest_id))
2692        }
2693    }
2694
2695    pub fn get_faction_rep(&self, faction_id: &str) -> i32 {
2696        self.faction_reps.get(faction_id).copied().unwrap_or(0)
2697    }
2698
2699    pub fn modify_faction_rep(&mut self, faction_id: &str, delta: i32) {
2700        let rep = self.faction_reps.entry(faction_id.to_string()).or_insert(0);
2701        *rep += delta;
2702    }
2703
2704    pub fn get_flag(&self, name: &str) -> bool {
2705        self.flags.contains(name)
2706    }
2707
2708    pub fn set_flag(&mut self, name: &str) {
2709        self.flags.insert(name.to_string());
2710    }
2711
2712    pub fn clear_flag(&mut self, name: &str) {
2713        self.flags.remove(name);
2714    }
2715
2716    pub fn get_item_count(&self, item_id: &str) -> u32 {
2717        self.item_inventory.get(item_id).copied().unwrap_or(0)
2718    }
2719
2720    pub fn add_item(&mut self, item_id: &str, quantity: u32) {
2721        *self.item_inventory.entry(item_id.to_string()).or_insert(0) += quantity;
2722    }
2723
2724    pub fn get_player_stat(&self, stat: &str) -> f32 {
2725        self.player_stats.get(stat).copied().unwrap_or(0.0)
2726    }
2727
2728    pub fn get_time_since(&self, event: &str) -> f32 {
2729        if let Some(&t) = self.event_times.get(event) {
2730            (self.current_time - t) as f32
2731        } else {
2732            f32::MAX
2733        }
2734    }
2735
2736    pub fn record_event(&mut self, event: &str) {
2737        self.event_times.insert(event.to_string(), self.current_time);
2738    }
2739
2740    pub fn is_objective_complete(&self, quest_id: &str, obj_id: &str) -> bool {
2741        self.quest_instances.get(quest_id)
2742            .map(|q| q.completed_objective_ids.contains(&obj_id.to_string()))
2743            .unwrap_or(false)
2744    }
2745
2746    pub fn get_chain_last_completion(&self, chain_id: &str) -> f32 {
2747        self.chain_completions.get(chain_id).copied().unwrap_or(-f64::MAX) as f32
2748    }
2749
2750    pub fn get_active_quests(&self) -> Vec<&QuestInstance> {
2751        self.quest_instances.values().filter(|q| q.state == QuestState::Active).collect()
2752    }
2753
2754    pub fn get_completed_quests(&self) -> Vec<&QuestInstance> {
2755        self.quest_instances.values().filter(|q| q.state == QuestState::Completed).collect()
2756    }
2757}
2758
2759// ============================================================
2760// SECTION 9: DEBUG TOOLS
2761// ============================================================
2762
2763#[derive(Debug, Clone)]
2764pub struct QuestDebugTools {
2765    pub log: VecDeque<DebugLogEntry>,
2766    pub max_log: usize,
2767    pub breakpoints: HashSet<String>,
2768    pub watch_objectives: Vec<String>,
2769    pub force_complete_queue: VecDeque<String>,
2770    pub force_fail_queue: VecDeque<String>,
2771    pub override_conditions: HashMap<String, bool>,
2772    pub reputation_inspector: ReputationInspector,
2773    pub condition_tester: ConditionTester,
2774    pub objective_overrides: HashMap<String, ObjectiveOverride>,
2775    pub slow_motion: bool,
2776    pub slow_motion_scale: f32,
2777    pub record_events: bool,
2778    pub recorded_events: Vec<QuestEvent>,
2779    pub current_scope_dump: Vec<(String, String)>,
2780}
2781
2782#[derive(Debug, Clone)]
2783pub struct DebugLogEntry {
2784    pub level: LogLevel,
2785    pub timestamp: f64,
2786    pub quest_id: Option<String>,
2787    pub message: String,
2788    pub source: String,
2789}
2790
2791#[derive(Debug, Clone, PartialEq)]
2792pub enum LogLevel {
2793    Info,
2794    Warning,
2795    Error,
2796    Verbose,
2797}
2798
2799#[derive(Debug, Clone)]
2800pub struct ReputationInspector {
2801    pub selected_faction: Option<String>,
2802    pub show_history: bool,
2803    pub history_count: usize,
2804    pub filter_quest_id: Option<String>,
2805}
2806
2807#[derive(Debug, Clone)]
2808pub struct ConditionTester {
2809    pub test_conditions: Vec<(String, QuestCondition)>,
2810    pub results: Vec<(String, bool)>,
2811    pub test_state: QuestStateStore,
2812    pub running: bool,
2813}
2814
2815impl ConditionTester {
2816    pub fn new() -> Self {
2817        ConditionTester {
2818            test_conditions: Vec::new(),
2819            results: Vec::new(),
2820            test_state: QuestStateStore::new(),
2821            running: false,
2822        }
2823    }
2824
2825    pub fn run_all(&mut self) {
2826        self.results.clear();
2827        for (name, cond) in &self.test_conditions.clone() {
2828            let result = cond.evaluate(&self.test_state);
2829            self.results.push((name.clone(), result));
2830        }
2831    }
2832
2833    pub fn add_test(&mut self, name: &str, condition: QuestCondition) {
2834        self.test_conditions.push((name.to_string(), condition));
2835    }
2836
2837    pub fn get_pass_rate(&self) -> f32 {
2838        if self.results.is_empty() { return 0.0; }
2839        let passed = self.results.iter().filter(|(_, r)| *r).count();
2840        passed as f32 / self.results.len() as f32
2841    }
2842}
2843
2844#[derive(Debug, Clone)]
2845pub struct ObjectiveOverride {
2846    pub objective_id: String,
2847    pub force_status: ObjectiveStatus,
2848    pub force_progress: Option<f32>,
2849    pub active: bool,
2850}
2851
2852impl QuestDebugTools {
2853    pub fn new() -> Self {
2854        QuestDebugTools {
2855            log: VecDeque::new(),
2856            max_log: 500,
2857            breakpoints: HashSet::new(),
2858            watch_objectives: Vec::new(),
2859            force_complete_queue: VecDeque::new(),
2860            force_fail_queue: VecDeque::new(),
2861            override_conditions: HashMap::new(),
2862            reputation_inspector: ReputationInspector {
2863                selected_faction: None,
2864                show_history: false,
2865                history_count: 20,
2866                filter_quest_id: None,
2867            },
2868            condition_tester: ConditionTester::new(),
2869            objective_overrides: HashMap::new(),
2870            slow_motion: false,
2871            slow_motion_scale: 0.1,
2872            record_events: false,
2873            recorded_events: Vec::new(),
2874            current_scope_dump: Vec::new(),
2875        }
2876    }
2877
2878    pub fn log(&mut self, level: LogLevel, message: &str, quest_id: Option<&str>, source: &str) {
2879        self.log.push_back(DebugLogEntry {
2880            level,
2881            timestamp: 0.0,
2882            quest_id: quest_id.map(|s| s.to_string()),
2883            message: message.to_string(),
2884            source: source.to_string(),
2885        });
2886        if self.log.len() > self.max_log {
2887            self.log.pop_front();
2888        }
2889    }
2890
2891    pub fn force_complete_quest(&mut self, quest_id: &str) {
2892        self.force_complete_queue.push_back(quest_id.to_string());
2893        self.log(LogLevel::Warning, &format!("Force completing: {}", quest_id), Some(quest_id), "debug");
2894    }
2895
2896    pub fn force_fail_quest(&mut self, quest_id: &str) {
2897        self.force_fail_queue.push_back(quest_id.to_string());
2898        self.log(LogLevel::Warning, &format!("Force failing: {}", quest_id), Some(quest_id), "debug");
2899    }
2900
2901    pub fn set_condition_override(&mut self, condition_id: &str, value: bool) {
2902        self.override_conditions.insert(condition_id.to_string(), value);
2903    }
2904
2905    pub fn clear_condition_override(&mut self, condition_id: &str) {
2906        self.override_conditions.remove(condition_id);
2907    }
2908
2909    pub fn override_objective(&mut self, obj_id: &str, status: ObjectiveStatus, progress: Option<f32>) {
2910        self.objective_overrides.insert(obj_id.to_string(), ObjectiveOverride {
2911            objective_id: obj_id.to_string(),
2912            force_status: status,
2913            force_progress: progress,
2914            active: true,
2915        });
2916    }
2917
2918    pub fn clear_override(&mut self, obj_id: &str) {
2919        if let Some(ov) = self.objective_overrides.get_mut(obj_id) {
2920            ov.active = false;
2921        }
2922    }
2923
2924    pub fn dump_state(&mut self, state: &QuestStateStore) {
2925        self.current_scope_dump.clear();
2926        self.current_scope_dump.push(("player_level".to_string(), state.player_level.to_string()));
2927        for (faction, rep) in &state.faction_reps {
2928            self.current_scope_dump.push((format!("rep_{}", faction), rep.to_string()));
2929        }
2930        for (item, qty) in &state.item_inventory {
2931            self.current_scope_dump.push((format!("item_{}", item), qty.to_string()));
2932        }
2933        for flag in &state.flags {
2934            self.current_scope_dump.push((format!("flag_{}", flag), "true".to_string()));
2935        }
2936        self.current_scope_dump.sort_by(|a, b| a.0.cmp(&b.0));
2937    }
2938
2939    pub fn get_log_for_quest(&self, quest_id: &str) -> Vec<&DebugLogEntry> {
2940        self.log.iter().filter(|e| e.quest_id.as_deref() == Some(quest_id)).collect()
2941    }
2942
2943    pub fn clear_log(&mut self) { self.log.clear(); }
2944
2945    pub fn get_error_count(&self) -> usize {
2946        self.log.iter().filter(|e| e.level == LogLevel::Error).count()
2947    }
2948
2949    pub fn get_warning_count(&self) -> usize {
2950        self.log.iter().filter(|e| e.level == LogLevel::Warning).count()
2951    }
2952}
2953
2954// ============================================================
2955// SECTION 10: QUEST GRAPH & FULL QUEST EDITOR
2956// ============================================================
2957
2958#[derive(Debug, Clone)]
2959pub enum QuestNodeEnum {
2960    QuestStart(QuestStartNode),
2961    Objective(ObjectiveNode),
2962    ConditionCheck(ConditionCheckNode),
2963    Reward(RewardNode),
2964    Branch(BranchNode),
2965    Fail(FailNode),
2966    Completion(CompletionNode),
2967    Timer(TimerNode),
2968    Trigger(TriggerNode),
2969}
2970
2971impl QuestNodeEnum {
2972    pub fn id(&self) -> u64 {
2973        match self {
2974            QuestNodeEnum::QuestStart(n) => n.id,
2975            QuestNodeEnum::Objective(n) => n.id,
2976            QuestNodeEnum::ConditionCheck(n) => n.id,
2977            QuestNodeEnum::Reward(n) => n.id,
2978            QuestNodeEnum::Branch(n) => n.id,
2979            QuestNodeEnum::Fail(n) => n.id,
2980            QuestNodeEnum::Completion(n) => n.id,
2981            QuestNodeEnum::Timer(n) => n.id,
2982            QuestNodeEnum::Trigger(n) => n.id,
2983        }
2984    }
2985
2986    pub fn position(&self) -> Vec2 {
2987        match self {
2988            QuestNodeEnum::QuestStart(n) => n.position,
2989            QuestNodeEnum::Objective(n) => n.position,
2990            QuestNodeEnum::ConditionCheck(n) => n.position,
2991            QuestNodeEnum::Reward(n) => n.position,
2992            QuestNodeEnum::Branch(n) => n.position,
2993            QuestNodeEnum::Fail(n) => n.position,
2994            QuestNodeEnum::Completion(n) => n.position,
2995            QuestNodeEnum::Timer(n) => n.position,
2996            QuestNodeEnum::Trigger(n) => n.position,
2997        }
2998    }
2999
3000    pub fn set_position(&mut self, pos: Vec2) {
3001        match self {
3002            QuestNodeEnum::QuestStart(n) => n.position = pos,
3003            QuestNodeEnum::Objective(n) => n.position = pos,
3004            QuestNodeEnum::ConditionCheck(n) => n.position = pos,
3005            QuestNodeEnum::Reward(n) => n.position = pos,
3006            QuestNodeEnum::Branch(n) => n.position = pos,
3007            QuestNodeEnum::Fail(n) => n.position = pos,
3008            QuestNodeEnum::Completion(n) => n.position = pos,
3009            QuestNodeEnum::Timer(n) => n.position = pos,
3010            QuestNodeEnum::Trigger(n) => n.position = pos,
3011        }
3012    }
3013
3014    pub fn node_type(&self) -> QuestNodeType {
3015        match self {
3016            QuestNodeEnum::QuestStart(_) => QuestNodeType::QuestStart,
3017            QuestNodeEnum::Objective(_) => QuestNodeType::Objective,
3018            QuestNodeEnum::ConditionCheck(_) => QuestNodeType::ConditionCheck,
3019            QuestNodeEnum::Reward(_) => QuestNodeType::Reward,
3020            QuestNodeEnum::Branch(_) => QuestNodeType::Branch,
3021            QuestNodeEnum::Fail(_) => QuestNodeType::Fail,
3022            QuestNodeEnum::Completion(_) => QuestNodeType::Completion,
3023            QuestNodeEnum::Timer(_) => QuestNodeType::Timer,
3024            QuestNodeEnum::Trigger(_) => QuestNodeType::Trigger,
3025        }
3026    }
3027
3028    pub fn tags(&self) -> &[String] {
3029        match self {
3030            QuestNodeEnum::QuestStart(n) => &n.tags,
3031            QuestNodeEnum::Objective(n) => &n.tags,
3032            QuestNodeEnum::ConditionCheck(n) => &n.tags,
3033            QuestNodeEnum::Reward(n) => &n.tags,
3034            QuestNodeEnum::Branch(n) => &n.tags,
3035            QuestNodeEnum::Fail(n) => &n.tags,
3036            QuestNodeEnum::Completion(n) => &n.tags,
3037            QuestNodeEnum::Timer(n) => &n.tags,
3038            QuestNodeEnum::Trigger(n) => &n.tags,
3039        }
3040    }
3041}
3042
3043#[derive(Debug, Clone)]
3044pub struct QuestNodeConnection {
3045    pub from_node: u64,
3046    pub from_port: usize,
3047    pub to_node: u64,
3048    pub to_port: usize,
3049    pub label: Option<String>,
3050    pub condition: Option<QuestCondition>,
3051}
3052
3053#[derive(Debug, Clone)]
3054pub struct QuestGraph {
3055    pub id: String,
3056    pub quest_id: String,
3057    pub name: String,
3058    pub nodes: HashMap<u64, QuestNodeEnum>,
3059    pub connections: Vec<QuestNodeConnection>,
3060    pub start_node_id: Option<u64>,
3061    pub metadata: QuestGraphMetadata,
3062    next_id: u64,
3063}
3064
3065#[derive(Debug, Clone, Default)]
3066pub struct QuestGraphMetadata {
3067    pub author: String,
3068    pub version: String,
3069    pub created: String,
3070    pub modified: String,
3071    pub description: String,
3072    pub tags: Vec<String>,
3073    pub category: String,
3074    pub reward_summary: String,
3075}
3076
3077impl QuestGraph {
3078    pub fn new(id: String, quest_id: String, name: String) -> Self {
3079        QuestGraph {
3080            id,
3081            quest_id,
3082            name,
3083            nodes: HashMap::new(),
3084            connections: Vec::new(),
3085            start_node_id: None,
3086            metadata: QuestGraphMetadata::default(),
3087            next_id: 1,
3088        }
3089    }
3090
3091    pub fn allocate_id(&mut self) -> u64 {
3092        let id = self.next_id;
3093        self.next_id += 1;
3094        id
3095    }
3096
3097    pub fn add_node(&mut self, node: QuestNodeEnum) -> u64 {
3098        let id = node.id();
3099        if let QuestNodeEnum::QuestStart(_) = &node {
3100            self.start_node_id = Some(id);
3101        }
3102        self.nodes.insert(id, node);
3103        id
3104    }
3105
3106    pub fn remove_node(&mut self, id: u64) {
3107        self.nodes.remove(&id);
3108        self.connections.retain(|c| c.from_node != id && c.to_node != id);
3109        if self.start_node_id == Some(id) { self.start_node_id = None; }
3110    }
3111
3112    pub fn connect(&mut self, from: u64, from_port: usize, to: u64, to_port: usize, label: Option<String>) {
3113        self.connections.retain(|c| !(c.from_node == from && c.from_port == from_port));
3114        self.connections.push(QuestNodeConnection {
3115            from_node: from, from_port,
3116            to_node: to, to_port,
3117            label, condition: None,
3118        });
3119    }
3120
3121    pub fn get_outputs(&self, node_id: u64) -> Vec<&QuestNodeConnection> {
3122        self.connections.iter().filter(|c| c.from_node == node_id).collect()
3123    }
3124
3125    pub fn get_inputs(&self, node_id: u64) -> Vec<&QuestNodeConnection> {
3126        self.connections.iter().filter(|c| c.to_node == node_id).collect()
3127    }
3128
3129    pub fn next_node(&self, from: u64, port: usize) -> Option<u64> {
3130        self.connections.iter()
3131            .find(|c| c.from_node == from && c.from_port == port)
3132            .map(|c| c.to_node)
3133    }
3134
3135    pub fn validate(&self) -> Vec<QuestValidationError> {
3136        let mut errors = Vec::new();
3137        if self.start_node_id.is_none() {
3138            errors.push(QuestValidationError {
3139                node_id: None,
3140                severity: QuestValidationSeverity::Error,
3141                message: "Quest has no start node".into(),
3142            });
3143        }
3144        for (id, node) in &self.nodes {
3145            match node {
3146                QuestNodeEnum::Objective(n) => {
3147                    if self.get_outputs(*id).is_empty() {
3148                        errors.push(QuestValidationError {
3149                            node_id: Some(*id),
3150                            severity: QuestValidationSeverity::Warning,
3151                            message: "Objective node has no output".into(),
3152                        });
3153                    }
3154                }
3155                QuestNodeEnum::ConditionCheck(n) => {
3156                    let has_true = self.connections.iter().any(|c| c.from_node == *id && c.from_port == 0);
3157                    let has_false = self.connections.iter().any(|c| c.from_node == *id && c.from_port == 1);
3158                    if !has_true {
3159                        errors.push(QuestValidationError {
3160                            node_id: Some(*id),
3161                            severity: QuestValidationSeverity::Warning,
3162                            message: "ConditionCheck missing true branch".into(),
3163                        });
3164                    }
3165                    if !has_false {
3166                        errors.push(QuestValidationError {
3167                            node_id: Some(*id),
3168                            severity: QuestValidationSeverity::Warning,
3169                            message: "ConditionCheck missing false branch".into(),
3170                        });
3171                    }
3172                }
3173                QuestNodeEnum::Reward(n) => {
3174                    if n.reward_table.xp_reward.base == 0 && n.reward_table.item_rewards.is_empty() {
3175                        errors.push(QuestValidationError {
3176                            node_id: Some(*id),
3177                            severity: QuestValidationSeverity::Info,
3178                            message: "Reward node has no rewards defined".into(),
3179                        });
3180                    }
3181                }
3182                _ => {}
3183            }
3184        }
3185        // Check unreachable nodes
3186        if let Some(start) = self.start_node_id {
3187            let reachable = self.find_reachable(start);
3188            for id in self.nodes.keys() {
3189                if !reachable.contains(id) {
3190                    errors.push(QuestValidationError {
3191                        node_id: Some(*id),
3192                        severity: QuestValidationSeverity::Warning,
3193                        message: "Unreachable node".into(),
3194                    });
3195                }
3196            }
3197        }
3198        errors
3199    }
3200
3201    pub fn find_reachable(&self, start: u64) -> HashSet<u64> {
3202        let mut visited = HashSet::new();
3203        let mut queue = VecDeque::new();
3204        queue.push_back(start);
3205        while let Some(id) = queue.pop_front() {
3206            if visited.contains(&id) { continue; }
3207            visited.insert(id);
3208            for conn in self.get_outputs(id) {
3209                queue.push_back(conn.to_node);
3210            }
3211        }
3212        visited
3213    }
3214
3215    pub fn auto_layout(&mut self) {
3216        if let Some(start) = self.start_node_id {
3217            let node_w = 220.0_f32;
3218            let node_h = 120.0_f32;
3219            let x_gap = 60.0_f32;
3220            let y_gap = 30.0_f32;
3221            let mut queue = VecDeque::new();
3222            let mut visited = HashSet::new();
3223            let mut col_row: HashMap<usize, usize> = HashMap::new();
3224            let mut positions: HashMap<u64, Vec2> = HashMap::new();
3225            queue.push_back((start, 0usize));
3226            while let Some((id, col)) = queue.pop_front() {
3227                if visited.contains(&id) { continue; }
3228                visited.insert(id);
3229                let row = *col_row.entry(col).or_insert(0);
3230                *col_row.entry(col).or_insert(0) += 1;
3231                positions.insert(id, Vec2::new(
3232                    col as f32 * (node_w + x_gap),
3233                    row as f32 * (node_h + y_gap),
3234                ));
3235                for conn in self.get_outputs(id) {
3236                    queue.push_back((conn.to_node, col + 1));
3237                }
3238            }
3239            for (id, pos) in positions {
3240                if let Some(node) = self.nodes.get_mut(&id) {
3241                    node.set_position(pos);
3242                }
3243            }
3244        }
3245    }
3246
3247    pub fn collect_all_objectives(&self) -> Vec<&ObjectiveNode> {
3248        self.nodes.values().filter_map(|n| {
3249            if let QuestNodeEnum::Objective(obj) = n { Some(obj) } else { None }
3250        }).collect()
3251    }
3252
3253    pub fn collect_all_rewards(&self) -> Vec<&RewardNode> {
3254        self.nodes.values().filter_map(|n| {
3255            if let QuestNodeEnum::Reward(r) = n { Some(r) } else { None }
3256        }).collect()
3257    }
3258}
3259
3260#[derive(Debug, Clone)]
3261pub struct QuestValidationError {
3262    pub node_id: Option<u64>,
3263    pub severity: QuestValidationSeverity,
3264    pub message: String,
3265}
3266
3267#[derive(Debug, Clone, PartialEq)]
3268pub enum QuestValidationSeverity {
3269    Info,
3270    Warning,
3271    Error,
3272}
3273
3274// ============================================================
3275// FULL QUEST EDITOR STRUCT
3276// ============================================================
3277
3278#[derive(Debug, Clone, Default)]
3279pub struct QuestEditorGraphState {
3280    pub pan_offset: Vec2,
3281    pub zoom: f32,
3282    pub canvas_size: Vec2,
3283    pub selected_nodes: HashSet<u64>,
3284    pub hovered_node: Option<u64>,
3285    pub dragging_node: Option<u64>,
3286    pub drag_start_pos: Vec2,
3287    pub drag_node_start: Vec2,
3288    pub connection_drag_from: Option<(u64, usize)>,
3289    pub connection_drag_pos: Vec2,
3290    pub selection_box_start: Option<Vec2>,
3291    pub selection_box_end: Vec2,
3292    pub grid_size: f32,
3293    pub snap_to_grid: bool,
3294    pub show_grid: bool,
3295    pub context_menu_pos: Option<Vec2>,
3296    pub context_menu_node: Option<u64>,
3297    pub bezier_smoothness: f32,
3298}
3299
3300impl QuestEditorGraphState {
3301    pub fn new() -> Self {
3302        QuestEditorGraphState {
3303            zoom: 1.0,
3304            canvas_size: Vec2::new(1600.0, 900.0),
3305            grid_size: 20.0,
3306            snap_to_grid: true,
3307            show_grid: true,
3308            bezier_smoothness: 0.5,
3309            ..Default::default()
3310        }
3311    }
3312
3313    pub fn world_to_screen(&self, world: Vec2) -> Vec2 { (world + self.pan_offset) * self.zoom }
3314    pub fn screen_to_world(&self, screen: Vec2) -> Vec2 { screen / self.zoom - self.pan_offset }
3315
3316    pub fn snap(&self, pos: Vec2) -> Vec2 {
3317        if self.snap_to_grid {
3318            Vec2::new(
3319                (pos.x / self.grid_size).round() * self.grid_size,
3320                (pos.y / self.grid_size).round() * self.grid_size,
3321            )
3322        } else { pos }
3323    }
3324
3325    pub fn zoom_at(&mut self, point: Vec2, delta: f32) {
3326        let world = self.screen_to_world(point);
3327        self.zoom = (self.zoom * (1.0 + delta * 0.1)).clamp(0.05, 5.0);
3328        let new_world = self.screen_to_world(point);
3329        self.pan_offset += new_world - world;
3330    }
3331}
3332
3333#[derive(Debug)]
3334pub struct QuestEditor {
3335    pub graphs: HashMap<String, QuestGraph>,
3336    pub active_graph_id: Option<String>,
3337    pub graph_state: QuestEditorGraphState,
3338    pub quest_state_store: QuestStateStore,
3339    pub faction_system: FactionSystem,
3340    pub journal: JournalSystem,
3341    pub chains: HashMap<String, QuestChain>,
3342    pub debug_tools: QuestDebugTools,
3343    pub undo_stack: VecDeque<QuestEditorAction>,
3344    pub redo_stack: VecDeque<QuestEditorAction>,
3345    pub max_undo: usize,
3346    pub validation_errors: Vec<QuestValidationError>,
3347    pub search_query: String,
3348    pub search_results: Vec<QuestSearchResult>,
3349    pub selected_chain_id: Option<String>,
3350    pub show_journal: bool,
3351    pub show_debug: bool,
3352    pub show_factions: bool,
3353    pub show_chains: bool,
3354    pub show_validation: bool,
3355    pub show_reward_preview: bool,
3356    pub show_statistics: bool,
3357    pub node_id_counter: u64,
3358    pub dirty: bool,
3359    pub status_message: String,
3360    pub status_timer: f32,
3361    pub rng: SimpleRng,
3362    pub panel_config: QuestEditorPanels,
3363    pub recent_files: Vec<String>,
3364    pub last_save_path: Option<String>,
3365    pub event_log: VecDeque<QuestEvent>,
3366    pub max_event_log: usize,
3367    pub preview_player_level: u32,
3368    pub reward_preview_results: Vec<RolledReward>,
3369    pub graph_annotations: Vec<QuestAnnotation>,
3370}
3371
3372#[derive(Debug, Clone)]
3373pub struct QuestEditorPanels {
3374    pub left_width: f32,
3375    pub right_width: f32,
3376    pub bottom_height: f32,
3377    pub show_left: bool,
3378    pub show_right: bool,
3379    pub show_bottom: bool,
3380}
3381
3382impl Default for QuestEditorPanels {
3383    fn default() -> Self {
3384        QuestEditorPanels {
3385            left_width: 260.0,
3386            right_width: 320.0,
3387            bottom_height: 180.0,
3388            show_left: true,
3389            show_right: true,
3390            show_bottom: true,
3391        }
3392    }
3393}
3394
3395#[derive(Debug, Clone)]
3396pub struct QuestSearchResult {
3397    pub graph_id: String,
3398    pub node_id: u64,
3399    pub field: String,
3400    pub excerpt: String,
3401}
3402
3403#[derive(Debug, Clone)]
3404pub struct QuestAnnotation {
3405    pub id: u64,
3406    pub position: Vec2,
3407    pub size: Vec2,
3408    pub title: String,
3409    pub text: String,
3410    pub color: Vec4,
3411    pub collapsed: bool,
3412    pub graph_id: String,
3413}
3414
3415impl QuestAnnotation {
3416    pub fn new(id: u64, graph_id: &str, position: Vec2) -> Self {
3417        QuestAnnotation {
3418            id,
3419            position,
3420            size: Vec2::new(200.0, 100.0),
3421            title: "Note".to_string(),
3422            text: String::new(),
3423            color: Vec4::new(0.8, 0.9, 0.6, 0.5),
3424            collapsed: false,
3425            graph_id: graph_id.to_string(),
3426        }
3427    }
3428}
3429
3430#[derive(Debug, Clone)]
3431pub enum QuestEditorAction {
3432    AddNode { graph_id: String, node: QuestNodeEnum },
3433    RemoveNode { graph_id: String, node: QuestNodeEnum, connections: Vec<QuestNodeConnection> },
3434    MoveNode { graph_id: String, node_id: u64, old_pos: Vec2, new_pos: Vec2 },
3435    AddConnection { graph_id: String, connection: QuestNodeConnection },
3436    RemoveConnection { graph_id: String, connection: QuestNodeConnection },
3437    EditObjective { graph_id: String, node_id: u64, old: QuestObjective, new: QuestObjective },
3438    AddReward { graph_id: String, node_id: u64, reward: ItemRewardEntry },
3439    EditStartNode { graph_id: String, node_id: u64, old: QuestStartNode, new: QuestStartNode },
3440    BatchAction(Vec<QuestEditorAction>),
3441}
3442
3443impl QuestEditor {
3444    pub fn new() -> Self {
3445        let mut faction_system = FactionSystem::new();
3446        // Add some sample factions
3447        faction_system.add_faction(Faction {
3448            id: "merchants_guild".to_string(),
3449            name: "Merchants Guild".to_string(),
3450            description: "A powerful trading consortium".to_string(),
3451            reputation: 0,
3452            reputation_min: -5000,
3453            reputation_max: 5000,
3454            current_tier: ReputationTier::Neutral,
3455            allied_factions: vec!["craftsmen".to_string()],
3456            enemy_factions: vec!["thieves_guild".to_string()],
3457            neutral_factions: vec!["city_guard".to_string()],
3458            color: Vec4::new(0.8, 0.6, 0.1, 1.0),
3459            icon: "merchant_icon".to_string(),
3460            is_player_faction: false,
3461        });
3462        QuestEditor {
3463            graphs: HashMap::new(),
3464            active_graph_id: None,
3465            graph_state: QuestEditorGraphState::new(),
3466            quest_state_store: QuestStateStore::new(),
3467            faction_system,
3468            journal: JournalSystem::new(),
3469            chains: HashMap::new(),
3470            debug_tools: QuestDebugTools::new(),
3471            undo_stack: VecDeque::new(),
3472            redo_stack: VecDeque::new(),
3473            max_undo: 100,
3474            validation_errors: Vec::new(),
3475            search_query: String::new(),
3476            search_results: Vec::new(),
3477            selected_chain_id: None,
3478            show_journal: false,
3479            show_debug: false,
3480            show_factions: false,
3481            show_chains: false,
3482            show_validation: false,
3483            show_reward_preview: false,
3484            show_statistics: false,
3485            node_id_counter: 1000,
3486            dirty: false,
3487            status_message: "Ready".to_string(),
3488            status_timer: 0.0,
3489            rng: SimpleRng::new(12345),
3490            panel_config: QuestEditorPanels::default(),
3491            recent_files: Vec::new(),
3492            last_save_path: None,
3493            event_log: VecDeque::new(),
3494            max_event_log: 500,
3495            preview_player_level: 10,
3496            reward_preview_results: Vec::new(),
3497            graph_annotations: Vec::new(),
3498        }
3499    }
3500
3501    pub fn allocate_id(&mut self) -> u64 {
3502        self.node_id_counter += 1;
3503        self.node_id_counter
3504    }
3505
3506    pub fn active_graph(&self) -> Option<&QuestGraph> {
3507        self.active_graph_id.as_ref().and_then(|id| self.graphs.get(id))
3508    }
3509
3510    pub fn active_graph_mut(&mut self) -> Option<&mut QuestGraph> {
3511        self.active_graph_id.as_ref().and_then(|id| self.graphs.get_mut(id))
3512    }
3513
3514    pub fn new_quest(&mut self, quest_name: &str, quest_id: &str) -> String {
3515        let graph_id = format!("graph_{}", self.allocate_id());
3516        let mut graph = QuestGraph::new(graph_id.clone(), quest_id.to_string(), quest_name.to_string());
3517        // Create default start node
3518        let start_id = self.allocate_id();
3519        let mut start = QuestStartNode::new(start_id);
3520        start.quest_id = quest_id.to_string();
3521        start.quest_name = quest_name.to_string();
3522        start.position = Vec2::new(100.0, 200.0);
3523        graph.add_node(QuestNodeEnum::QuestStart(start));
3524        self.graphs.insert(graph_id.clone(), graph);
3525        self.active_graph_id = Some(graph_id.clone());
3526        self.dirty = true;
3527        graph_id
3528    }
3529
3530    pub fn open_graph(&mut self, id: &str) {
3531        if self.graphs.contains_key(id) {
3532            self.active_graph_id = Some(id.to_string());
3533            self.validate_active();
3534        }
3535    }
3536
3537    pub fn add_node(&mut self, node_type: QuestNodeType, position: Vec2) -> Option<u64> {
3538        let id = self.allocate_id();
3539        let node = self.create_node(node_type, id, position);
3540        let nid = node.id();
3541        let graph_id = self.active_graph_id.clone();
3542        if let Some(gid) = graph_id {
3543            let action = QuestEditorAction::AddNode { graph_id: gid, node: node.clone() };
3544            self.push_undo(action);
3545            if let Some(graph) = self.active_graph_mut() {
3546                graph.add_node(node);
3547                self.dirty = true;
3548            }
3549            Some(nid)
3550        } else {
3551            None
3552        }
3553    }
3554
3555    fn create_node(&self, node_type: QuestNodeType, id: u64, position: Vec2) -> QuestNodeEnum {
3556        match node_type {
3557            QuestNodeType::QuestStart => {
3558                let mut n = QuestStartNode::new(id);
3559                n.position = position;
3560                QuestNodeEnum::QuestStart(n)
3561            }
3562            QuestNodeType::Objective => {
3563                let obj = QuestObjective::KillEnemy(KillEnemyObjective {
3564                    enemy_type: "any".to_string(),
3565                    enemy_id: None,
3566                    required_count: 1,
3567                    killed_count: 0,
3568                    in_area: None,
3569                    with_weapon_type: None,
3570                    require_stealth_kill: false,
3571                    allow_assists: true,
3572                });
3573                let mut n = ObjectiveNode::new(id, obj);
3574                n.position = position;
3575                QuestNodeEnum::Objective(n)
3576            }
3577            QuestNodeType::ConditionCheck => {
3578                let mut n = ConditionCheckNode::new(id);
3579                n.position = position;
3580                QuestNodeEnum::ConditionCheck(n)
3581            }
3582            QuestNodeType::Reward => {
3583                let mut n = RewardNode::new(id);
3584                n.position = position;
3585                QuestNodeEnum::Reward(n)
3586            }
3587            QuestNodeType::Branch => {
3588                let mut n = BranchNode::new(id);
3589                n.position = position;
3590                QuestNodeEnum::Branch(n)
3591            }
3592            QuestNodeType::Fail => {
3593                let mut n = FailNode::new(id);
3594                n.position = position;
3595                QuestNodeEnum::Fail(n)
3596            }
3597            QuestNodeType::Completion => {
3598                let mut n = CompletionNode::new(id);
3599                n.position = position;
3600                QuestNodeEnum::Completion(n)
3601            }
3602            QuestNodeType::Timer => {
3603                let mut n = TimerNode::new(id);
3604                n.position = position;
3605                QuestNodeEnum::Timer(n)
3606            }
3607            QuestNodeType::Trigger => {
3608                let mut n = TriggerNode::new(id);
3609                n.position = position;
3610                QuestNodeEnum::Trigger(n)
3611            }
3612        }
3613    }
3614
3615    pub fn remove_selected_nodes(&mut self) {
3616        let selected: Vec<u64> = self.graph_state.selected_nodes.iter().cloned().collect();
3617        for id in &selected {
3618            let action_opt = if let Some(graph) = self.active_graph_mut() {
3619                if let Some(node) = graph.nodes.get(id).cloned() {
3620                    let conns: Vec<QuestNodeConnection> = graph.connections.iter()
3621                        .filter(|c| c.from_node == *id || c.to_node == *id)
3622                        .cloned()
3623                        .collect();
3624                    Some(QuestEditorAction::RemoveNode { graph_id: graph.id.clone(), node, connections: conns })
3625                } else { None }
3626            } else { None };
3627            if let Some(action) = action_opt {
3628                self.push_undo(action);
3629                if let Some(graph) = self.active_graph_mut() {
3630                    graph.remove_node(*id);
3631                }
3632            }
3633        }
3634        self.graph_state.selected_nodes.clear();
3635        self.dirty = true;
3636    }
3637
3638    pub fn connect_nodes(&mut self, from: u64, from_port: usize, to: u64, to_port: usize) {
3639        let conn = QuestNodeConnection { from_node: from, from_port, to_node: to, to_port, label: None, condition: None };
3640        if let Some(gid) = self.active_graph_id.clone() {
3641            let action = QuestEditorAction::AddConnection { graph_id: gid, connection: conn.clone() };
3642            self.push_undo(action);
3643            if let Some(graph) = self.active_graph_mut() {
3644                graph.connect(from, from_port, to, to_port, None);
3645                self.dirty = true;
3646            }
3647        }
3648    }
3649
3650    fn push_undo(&mut self, action: QuestEditorAction) {
3651        self.redo_stack.clear();
3652        self.undo_stack.push_back(action);
3653        if self.undo_stack.len() > self.max_undo {
3654            self.undo_stack.pop_front();
3655        }
3656    }
3657
3658    pub fn undo(&mut self) {
3659        if let Some(action) = self.undo_stack.pop_back() {
3660            self.redo_stack.push_back(action.clone());
3661            self.apply_undo(action);
3662        }
3663    }
3664
3665    pub fn redo(&mut self) {
3666        if let Some(action) = self.redo_stack.pop_back() {
3667            self.undo_stack.push_back(action.clone());
3668            self.apply_redo(action);
3669        }
3670    }
3671
3672    fn apply_undo(&mut self, action: QuestEditorAction) {
3673        match action {
3674            QuestEditorAction::AddNode { graph_id, node } => {
3675                if let Some(g) = self.graphs.get_mut(&graph_id) { g.remove_node(node.id()); }
3676            }
3677            QuestEditorAction::RemoveNode { graph_id, node, connections } => {
3678                if let Some(g) = self.graphs.get_mut(&graph_id) {
3679                    g.add_node(node);
3680                    for conn in connections { g.connections.push(conn); }
3681                }
3682            }
3683            QuestEditorAction::MoveNode { graph_id, node_id, old_pos, .. } => {
3684                if let Some(g) = self.graphs.get_mut(&graph_id) {
3685                    if let Some(n) = g.nodes.get_mut(&node_id) { n.set_position(old_pos); }
3686                }
3687            }
3688            QuestEditorAction::AddConnection { graph_id, connection } => {
3689                if let Some(g) = self.graphs.get_mut(&graph_id) {
3690                    g.connections.retain(|c| !(c.from_node == connection.from_node && c.from_port == connection.from_port));
3691                }
3692            }
3693            QuestEditorAction::RemoveConnection { graph_id, connection } => {
3694                if let Some(g) = self.graphs.get_mut(&graph_id) { g.connections.push(connection); }
3695            }
3696            QuestEditorAction::BatchAction(actions) => {
3697                for a in actions.into_iter().rev() { self.apply_undo(a); }
3698            }
3699            _ => {}
3700        }
3701        self.dirty = true;
3702    }
3703
3704    fn apply_redo(&mut self, action: QuestEditorAction) {
3705        match action {
3706            QuestEditorAction::AddNode { graph_id, node } => {
3707                if let Some(g) = self.graphs.get_mut(&graph_id) { g.add_node(node); }
3708            }
3709            QuestEditorAction::RemoveNode { graph_id, node, .. } => {
3710                if let Some(g) = self.graphs.get_mut(&graph_id) { g.remove_node(node.id()); }
3711            }
3712            QuestEditorAction::MoveNode { graph_id, node_id, new_pos, .. } => {
3713                if let Some(g) = self.graphs.get_mut(&graph_id) {
3714                    if let Some(n) = g.nodes.get_mut(&node_id) { n.set_position(new_pos); }
3715                }
3716            }
3717            QuestEditorAction::AddConnection { graph_id, connection } => {
3718                if let Some(g) = self.graphs.get_mut(&graph_id) {
3719                    g.connect(connection.from_node, connection.from_port, connection.to_node, connection.to_port, connection.label);
3720                }
3721            }
3722            QuestEditorAction::RemoveConnection { graph_id, connection } => {
3723                if let Some(g) = self.graphs.get_mut(&graph_id) {
3724                    g.connections.retain(|c| !(c.from_node == connection.from_node && c.from_port == connection.from_port));
3725                }
3726            }
3727            QuestEditorAction::BatchAction(actions) => {
3728                for a in actions { self.apply_redo(a); }
3729            }
3730            _ => {}
3731        }
3732        self.dirty = true;
3733    }
3734
3735    pub fn validate_active(&mut self) {
3736        if let Some(graph) = self.active_graph() {
3737            self.validation_errors = graph.validate();
3738        }
3739    }
3740
3741    pub fn update(&mut self, delta: f32) {
3742        self.status_timer -= delta;
3743        if self.status_timer < 0.0 { self.status_timer = 0.0; }
3744        // Process debug queues
3745        while let Some(quest_id) = self.debug_tools.force_complete_queue.pop_front() {
3746            let _ = self.quest_state_store.complete_quest(&quest_id);
3747            self.journal.add_entry(&quest_id, JournalEntryType::QuestComplete,
3748                "Quest Complete", "Force completed by debug tools", JournalCategory::MainQuest);
3749        }
3750        while let Some(quest_id) = self.debug_tools.force_fail_queue.pop_front() {
3751            let _ = self.quest_state_store.fail_quest(&quest_id);
3752            self.journal.add_entry(&quest_id, JournalEntryType::QuestFail,
3753                "Quest Failed", "Force failed by debug tools", JournalCategory::MainQuest);
3754        }
3755        self.quest_state_store.current_time += delta as f64;
3756    }
3757
3758    pub fn process_event(&mut self, event: QuestEvent) {
3759        if self.debug_tools.record_events {
3760            self.debug_tools.recorded_events.push(event.clone());
3761        }
3762        self.event_log.push_back(event.clone());
3763        if self.event_log.len() > self.max_event_log {
3764            self.event_log.pop_front();
3765        }
3766        self.debug_tools.log(LogLevel::Verbose, &format!("Event: {:?}", std::mem::discriminant(&event)), None, "event_system");
3767    }
3768
3769    pub fn set_status(&mut self, msg: &str, duration: f32) {
3770        self.status_message = msg.to_string();
3771        self.status_timer = duration;
3772    }
3773
3774    pub fn preview_rewards(&mut self, node_id: u64) {
3775        let reward_node_clone = self.active_graph()
3776            .and_then(|g| g.nodes.get(&node_id))
3777            .and_then(|n| if let QuestNodeEnum::Reward(r) = n { Some(r.clone()) } else { None });
3778        if let Some(reward_node) = reward_node_clone {
3779            let results = reward_node.roll_rewards(self.preview_player_level, &mut self.rng);
3780            self.reward_preview_results = results;
3781            self.show_reward_preview = true;
3782        }
3783    }
3784
3785    pub fn search(&mut self, query: &str) {
3786        self.search_query = query.to_string();
3787        self.search_results.clear();
3788        let query_lower = query.to_lowercase();
3789        for (graph_id, graph) in &self.graphs {
3790            for (node_id, node) in &graph.nodes {
3791                match node {
3792                    QuestNodeEnum::QuestStart(n) => {
3793                        if n.quest_name.to_lowercase().contains(&query_lower) {
3794                            self.search_results.push(QuestSearchResult {
3795                                graph_id: graph_id.clone(),
3796                                node_id: *node_id,
3797                                field: "quest_name".to_string(),
3798                                excerpt: n.quest_name.clone(),
3799                            });
3800                        }
3801                        if n.description.to_lowercase().contains(&query_lower) {
3802                            self.search_results.push(QuestSearchResult {
3803                                graph_id: graph_id.clone(),
3804                                node_id: *node_id,
3805                                field: "description".to_string(),
3806                                excerpt: n.description.chars().take(60).collect(),
3807                            });
3808                        }
3809                    }
3810                    QuestNodeEnum::Objective(n) => {
3811                        let desc = n.objective.description();
3812                        if desc.to_lowercase().contains(&query_lower) {
3813                            self.search_results.push(QuestSearchResult {
3814                                graph_id: graph_id.clone(),
3815                                node_id: *node_id,
3816                                field: "objective".to_string(),
3817                                excerpt: desc,
3818                            });
3819                        }
3820                    }
3821                    QuestNodeEnum::Fail(n) => {
3822                        if n.fail_message.to_lowercase().contains(&query_lower) {
3823                            self.search_results.push(QuestSearchResult {
3824                                graph_id: graph_id.clone(),
3825                                node_id: *node_id,
3826                                field: "fail_message".to_string(),
3827                                excerpt: n.fail_message.clone(),
3828                            });
3829                        }
3830                    }
3831                    _ => {}
3832                }
3833                for tag in node.tags() {
3834                    if tag.to_lowercase().contains(&query_lower) {
3835                        self.search_results.push(QuestSearchResult {
3836                            graph_id: graph_id.clone(),
3837                            node_id: *node_id,
3838                            field: "tag".to_string(),
3839                            excerpt: tag.clone(),
3840                        });
3841                    }
3842                }
3843            }
3844        }
3845    }
3846
3847    pub fn select_all(&mut self) {
3848        if let Some(graph) = self.active_graph() {
3849            self.graph_state.selected_nodes = graph.nodes.keys().cloned().collect();
3850        }
3851    }
3852
3853    pub fn deselect_all(&mut self) {
3854        self.graph_state.selected_nodes.clear();
3855    }
3856
3857    pub fn auto_layout_active(&mut self) {
3858        if let Some(graph) = self.active_graph_mut() {
3859            graph.auto_layout();
3860        }
3861    }
3862
3863    pub fn add_chain(&mut self, chain: QuestChain) {
3864        self.chains.insert(chain.chain_id.clone(), chain);
3865        self.dirty = true;
3866    }
3867
3868    pub fn get_statistics(&self) -> QuestEditorStats {
3869        let total_quests = self.graphs.len();
3870        let total_nodes: usize = self.graphs.values().map(|g| g.nodes.len()).sum();
3871        let total_connections: usize = self.graphs.values().map(|g| g.connections.len()).sum();
3872        let active_quests = self.quest_state_store.quest_instances.values()
3873            .filter(|q| q.state == QuestState::Active).count();
3874        let completed_quests = self.quest_state_store.quest_instances.values()
3875            .filter(|q| q.state == QuestState::Completed).count();
3876        let total_objectives: usize = self.graphs.values()
3877            .flat_map(|g| g.nodes.values())
3878            .filter(|n| matches!(n, QuestNodeEnum::Objective(_)))
3879            .count();
3880        let total_rewards: usize = self.graphs.values()
3881            .flat_map(|g| g.nodes.values())
3882            .filter(|n| matches!(n, QuestNodeEnum::Reward(_)))
3883            .count();
3884        QuestEditorStats {
3885            total_quests,
3886            total_nodes,
3887            total_connections,
3888            active_quests,
3889            completed_quests,
3890            total_objectives,
3891            total_rewards,
3892            total_chains: self.chains.len(),
3893            journal_entries: self.journal.entries.len(),
3894            map_markers: self.journal.map_markers.len(),
3895            validation_errors: self.validation_errors.iter().filter(|e| e.severity == QuestValidationSeverity::Error).count(),
3896            validation_warnings: self.validation_errors.iter().filter(|e| e.severity == QuestValidationSeverity::Warning).count(),
3897        }
3898    }
3899
3900    pub fn export_to_json(&self, graph_id: &str) -> Result<String, String> {
3901        let graph = self.graphs.get(graph_id).ok_or("Graph not found")?;
3902        let mut json = String::new();
3903        json.push_str("{\n");
3904        json.push_str(&format!("  \"id\": \"{}\",\n", graph.id));
3905        json.push_str(&format!("  \"quest_id\": \"{}\",\n", graph.quest_id));
3906        json.push_str(&format!("  \"name\": \"{}\",\n", graph.name));
3907        json.push_str(&format!("  \"node_count\": {},\n", graph.nodes.len()));
3908        json.push_str(&format!("  \"connection_count\": {}\n", graph.connections.len()));
3909        json.push('}');
3910        Ok(json)
3911    }
3912
3913    pub fn add_faction_rep_delta(&mut self, faction_id: &str, delta: i32, reason: &str, quest_id: Option<&str>) {
3914        self.faction_system.apply_rep_delta(faction_id, delta, reason, quest_id);
3915        self.quest_state_store.modify_faction_rep(faction_id, delta);
3916        self.journal.notification_queue.push_back(JournalNotification {
3917            notification_type: JournalNotificationType::ReputationChange {
3918                faction: faction_id.to_string(),
3919                delta,
3920            },
3921            title: format!("Reputation: {}", faction_id),
3922            description: format!("{:+} - {}", delta, reason),
3923            icon: "rep_icon".to_string(),
3924            duration: 4.0,
3925            timestamp: self.quest_state_store.current_time,
3926        });
3927    }
3928
3929    pub fn generate_journal_entry_for_quest(&mut self, quest_id: &str, graph_id: &str) {
3930        if let Some(graph) = self.graphs.get(graph_id) {
3931            if let Some(start_node_id) = graph.start_node_id {
3932                if let Some(QuestNodeEnum::QuestStart(start)) = graph.nodes.get(&start_node_id) {
3933                    self.journal.add_entry(
3934                        quest_id,
3935                        JournalEntryType::QuestStart,
3936                        &start.quest_name,
3937                        &start.description,
3938                        JournalCategory::MainQuest,
3939                    );
3940                    for marker in &start.start_map_markers.clone() {
3941                        self.journal.add_map_marker(marker.clone());
3942                    }
3943                }
3944            }
3945        }
3946    }
3947}
3948
3949#[derive(Debug, Clone, Default)]
3950pub struct QuestEditorStats {
3951    pub total_quests: usize,
3952    pub total_nodes: usize,
3953    pub total_connections: usize,
3954    pub active_quests: usize,
3955    pub completed_quests: usize,
3956    pub total_objectives: usize,
3957    pub total_rewards: usize,
3958    pub total_chains: usize,
3959    pub journal_entries: usize,
3960    pub map_markers: usize,
3961    pub validation_errors: usize,
3962    pub validation_warnings: usize,
3963}
3964
3965// ============================================================
3966// EXTRA: SIMPLE RNG (No external deps)
3967// ============================================================
3968
3969#[derive(Debug, Clone)]
3970pub struct SimpleRng {
3971    state: u64,
3972}
3973
3974impl SimpleRng {
3975    pub fn new(seed: u64) -> Self { SimpleRng { state: seed } }
3976
3977    pub fn next_u64(&mut self) -> u64 {
3978        self.state ^= self.state << 13;
3979        self.state ^= self.state >> 7;
3980        self.state ^= self.state << 17;
3981        self.state
3982    }
3983
3984    pub fn next_f32(&mut self) -> f32 {
3985        (self.next_u64() >> 33) as f32 / (u32::MAX as f32)
3986    }
3987
3988    pub fn next_u32(&mut self, max: u32) -> u32 {
3989        if max == 0 { return 0; }
3990        (self.next_u64() % max as u64) as u32
3991    }
3992
3993    pub fn next_usize(&mut self, max: usize) -> usize {
3994        if max == 0 { return 0; }
3995        (self.next_u64() % max as u64) as usize
3996    }
3997
3998    pub fn next_range_i32(&mut self, min: i32, max: i32) -> i32 {
3999        if min >= max { return min; }
4000        let range = (max - min) as u32;
4001        min + self.next_u32(range) as i32
4002    }
4003
4004    pub fn next_range_f32(&mut self, min: f32, max: f32) -> f32 {
4005        min + self.next_f32() * (max - min)
4006    }
4007
4008    pub fn shuffle<T>(&mut self, slice: &mut Vec<T>) {
4009        let len = slice.len();
4010        for i in (1..len).rev() {
4011            let j = self.next_usize(i + 1);
4012            slice.swap(i, j);
4013        }
4014    }
4015
4016    pub fn weighted_pick(&mut self, weights: &[f32]) -> Option<usize> {
4017        if weights.is_empty() { return None; }
4018        let total: f32 = weights.iter().sum();
4019        if total <= 0.0 { return None; }
4020        let mut cursor = self.next_f32() * total;
4021        for (i, &w) in weights.iter().enumerate() {
4022            cursor -= w;
4023            if cursor <= 0.0 { return Some(i); }
4024        }
4025        Some(weights.len() - 1)
4026    }
4027}
4028
4029// ============================================================
4030// EXTRA: LOOT TABLE SYSTEM
4031// ============================================================
4032
4033#[derive(Debug, Clone)]
4034pub struct LootTable {
4035    pub table_id: String,
4036    pub name: String,
4037    pub entries: Vec<LootEntry>,
4038    pub guaranteed_entries: Vec<LootEntry>,
4039    pub max_rolls: u32,
4040    pub min_rolls: u32,
4041    pub quality_modifier: f32,
4042    pub luck_affects: bool,
4043}
4044
4045#[derive(Debug, Clone)]
4046pub struct LootEntry {
4047    pub item_id: String,
4048    pub item_name: String,
4049    pub weight: f32,
4050    pub quantity_min: u32,
4051    pub quantity_max: u32,
4052    pub quality_min: f32,
4053    pub quality_max: f32,
4054    pub rarity: ItemRarity,
4055    pub level_requirement: Option<u32>,
4056    pub conditions: Vec<QuestCondition>,
4057    pub nested_table: Option<String>,
4058}
4059
4060impl LootTable {
4061    pub fn new(table_id: &str, name: &str) -> Self {
4062        LootTable {
4063            table_id: table_id.to_string(),
4064            name: name.to_string(),
4065            entries: Vec::new(),
4066            guaranteed_entries: Vec::new(),
4067            max_rolls: 1,
4068            min_rolls: 1,
4069            quality_modifier: 1.0,
4070            luck_affects: true,
4071        }
4072    }
4073
4074    pub fn add_entry(&mut self, item_id: &str, weight: f32, qty_min: u32, qty_max: u32, rarity: ItemRarity) {
4075        self.entries.push(LootEntry {
4076            item_id: item_id.to_string(),
4077            item_name: item_id.to_string(),
4078            weight,
4079            quantity_min: qty_min,
4080            quantity_max: qty_max,
4081            quality_min: 0.5,
4082            quality_max: 1.0,
4083            rarity,
4084            level_requirement: None,
4085            conditions: Vec::new(),
4086            nested_table: None,
4087        });
4088    }
4089
4090    pub fn roll(&self, rng: &mut SimpleRng, player_level: u32, luck: f32) -> Vec<LootResult> {
4091        let mut results = Vec::new();
4092        // Guaranteed drops
4093        for entry in &self.guaranteed_entries {
4094            if let Some(req) = entry.level_requirement {
4095                if player_level < req { continue; }
4096            }
4097            let qty = if entry.quantity_min >= entry.quantity_max {
4098                entry.quantity_min
4099            } else {
4100                entry.quantity_min + rng.next_u32(entry.quantity_max - entry.quantity_min + 1)
4101            };
4102            let quality = entry.quality_min + rng.next_f32() * (entry.quality_max - entry.quality_min);
4103            results.push(LootResult {
4104                item_id: entry.item_id.clone(),
4105                quantity: qty,
4106                quality: (quality * self.quality_modifier).min(1.0),
4107                rarity: entry.rarity.clone(),
4108                from_table: self.table_id.clone(),
4109            });
4110        }
4111        // Random rolls
4112        let num_rolls = self.min_rolls + rng.next_u32(self.max_rolls - self.min_rolls + 1);
4113        let weights: Vec<f32> = self.entries.iter()
4114            .map(|e| {
4115                let luck_bonus = if self.luck_affects { luck * e.rarity.drop_chance_modifier() } else { 0.0 };
4116                (e.weight * (1.0 + luck_bonus)).max(0.0)
4117            })
4118            .collect();
4119        for _ in 0..num_rolls {
4120            if let Some(idx) = rng.weighted_pick(&weights) {
4121                let entry = &self.entries[idx];
4122                if let Some(req) = entry.level_requirement {
4123                    if player_level < req { continue; }
4124                }
4125                let qty = if entry.quantity_min >= entry.quantity_max {
4126                    entry.quantity_min
4127                } else {
4128                    entry.quantity_min + rng.next_u32(entry.quantity_max - entry.quantity_min + 1)
4129                };
4130                let quality = entry.quality_min + rng.next_f32() * (entry.quality_max - entry.quality_min);
4131                results.push(LootResult {
4132                    item_id: entry.item_id.clone(),
4133                    quantity: qty,
4134                    quality: (quality * self.quality_modifier).min(1.0),
4135                    rarity: entry.rarity.clone(),
4136                    from_table: self.table_id.clone(),
4137                });
4138            }
4139        }
4140        results
4141    }
4142
4143    pub fn get_total_weight(&self) -> f32 {
4144        self.entries.iter().map(|e| e.weight).sum()
4145    }
4146
4147    pub fn get_probability(&self, item_id: &str) -> f32 {
4148        let total = self.get_total_weight();
4149        if total == 0.0 { return 0.0; }
4150        let item_weight: f32 = self.entries.iter()
4151            .filter(|e| e.item_id == item_id)
4152            .map(|e| e.weight)
4153            .sum();
4154        item_weight / total
4155    }
4156}
4157
4158#[derive(Debug, Clone)]
4159pub struct LootResult {
4160    pub item_id: String,
4161    pub quantity: u32,
4162    pub quality: f32,
4163    pub rarity: ItemRarity,
4164    pub from_table: String,
4165}
4166
4167impl LootResult {
4168    pub fn display(&self) -> String {
4169        format!("{} x{} ({:.0}% quality, {:?})", self.item_id, self.quantity, self.quality * 100.0, self.rarity)
4170    }
4171}
4172
4173// ============================================================
4174// EXTRA: XP CURVE CALCULATOR
4175// ============================================================
4176
4177#[derive(Debug, Clone)]
4178pub struct XpCurve {
4179    pub curve_type: XpCurveType,
4180    pub base_xp: u32,
4181    pub max_level: u32,
4182    pub custom_values: Vec<u32>,
4183}
4184
4185#[derive(Debug, Clone, PartialEq)]
4186pub enum XpCurveType {
4187    Linear { increment: u32 },
4188    Exponential { base: f32 },
4189    Polynomial { exponent: f32 },
4190    Custom,
4191    Flat,
4192}
4193
4194impl XpCurve {
4195    pub fn new_exponential(base_xp: u32, max_level: u32, base: f32) -> Self {
4196        XpCurve {
4197            curve_type: XpCurveType::Exponential { base },
4198            base_xp,
4199            max_level,
4200            custom_values: Vec::new(),
4201        }
4202    }
4203
4204    pub fn xp_for_level(&self, level: u32) -> u32 {
4205        if level == 0 { return 0; }
4206        let level = level.min(self.max_level);
4207        match &self.curve_type {
4208            XpCurveType::Linear { increment } => {
4209                self.base_xp + increment * (level - 1)
4210            }
4211            XpCurveType::Exponential { base } => {
4212                (self.base_xp as f32 * base.powi(level as i32 - 1)) as u32
4213            }
4214            XpCurveType::Polynomial { exponent } => {
4215                (self.base_xp as f32 * (level as f32).powf(*exponent)) as u32
4216            }
4217            XpCurveType::Flat => self.base_xp,
4218            XpCurveType::Custom => {
4219                self.custom_values.get((level - 1) as usize).copied().unwrap_or(self.base_xp)
4220            }
4221        }
4222    }
4223
4224    pub fn total_xp_for_level(&self, target_level: u32) -> u32 {
4225        (1..target_level).map(|l| self.xp_for_level(l)).sum()
4226    }
4227
4228    pub fn level_from_total_xp(&self, total_xp: u32) -> u32 {
4229        let mut accumulated = 0u32;
4230        for level in 1..=self.max_level {
4231            let needed = self.xp_for_level(level);
4232            if accumulated + needed > total_xp { return level.saturating_sub(1); }
4233            accumulated += needed;
4234        }
4235        self.max_level
4236    }
4237
4238    pub fn progress_in_level(&self, total_xp: u32) -> f32 {
4239        let level = self.level_from_total_xp(total_xp);
4240        let xp_for_this = self.total_xp_for_level(level);
4241        let xp_needed = self.xp_for_level(level + 1);
4242        if xp_needed == 0 { return 1.0; }
4243        let progress_xp = total_xp - xp_for_this;
4244        (progress_xp as f32 / xp_needed as f32).clamp(0.0, 1.0)
4245    }
4246
4247    pub fn generate_table(&self) -> Vec<(u32, u32, u32)> {
4248        let mut table = Vec::new();
4249        let mut total = 0u32;
4250        for level in 1..=self.max_level {
4251            let xp = self.xp_for_level(level);
4252            total += xp;
4253            table.push((level, xp, total));
4254        }
4255        table
4256    }
4257}
4258
4259// ============================================================
4260// EXTRA: QUEST NODE STYLE
4261// ============================================================
4262
4263#[derive(Debug, Clone)]
4264pub struct QuestNodeStyle {
4265    pub header_color: Vec4,
4266    pub body_color: Vec4,
4267    pub border_color: Vec4,
4268    pub selected_border: Vec4,
4269    pub text_color: Vec4,
4270    pub port_color_in: Vec4,
4271    pub port_color_out: Vec4,
4272    pub rounding: f32,
4273    pub border_width: f32,
4274}
4275
4276impl QuestNodeStyle {
4277    pub fn for_type(node_type: &QuestNodeType) -> Self {
4278        let (header, body) = match node_type {
4279            QuestNodeType::QuestStart => (Vec4::new(0.15, 0.6, 0.15, 1.0), Vec4::new(0.1, 0.35, 0.1, 1.0)),
4280            QuestNodeType::Completion => (Vec4::new(0.1, 0.5, 0.8, 1.0), Vec4::new(0.05, 0.3, 0.5, 1.0)),
4281            QuestNodeType::Fail => (Vec4::new(0.7, 0.1, 0.1, 1.0), Vec4::new(0.4, 0.05, 0.05, 1.0)),
4282            QuestNodeType::Objective => (Vec4::new(0.2, 0.4, 0.7, 1.0), Vec4::new(0.1, 0.25, 0.45, 1.0)),
4283            QuestNodeType::ConditionCheck => (Vec4::new(0.6, 0.5, 0.0, 1.0), Vec4::new(0.35, 0.3, 0.0, 1.0)),
4284            QuestNodeType::Reward => (Vec4::new(0.6, 0.45, 0.0, 1.0), Vec4::new(0.35, 0.25, 0.0, 1.0)),
4285            QuestNodeType::Branch => (Vec4::new(0.5, 0.2, 0.5, 1.0), Vec4::new(0.3, 0.1, 0.3, 1.0)),
4286            QuestNodeType::Timer => (Vec4::new(0.7, 0.3, 0.0, 1.0), Vec4::new(0.45, 0.15, 0.0, 1.0)),
4287            QuestNodeType::Trigger => (Vec4::new(0.4, 0.6, 0.5, 1.0), Vec4::new(0.2, 0.4, 0.3, 1.0)),
4288        };
4289        QuestNodeStyle {
4290            header_color: header,
4291            body_color: body,
4292            border_color: Vec4::new(0.35, 0.35, 0.35, 1.0),
4293            selected_border: Vec4::new(1.0, 0.8, 0.0, 1.0),
4294            text_color: Vec4::new(1.0, 1.0, 1.0, 1.0),
4295            port_color_in: Vec4::new(0.6, 1.0, 0.6, 1.0),
4296            port_color_out: Vec4::new(1.0, 0.6, 0.6, 1.0),
4297            rounding: 6.0,
4298            border_width: 1.5,
4299        }
4300    }
4301}
4302
4303// ============================================================
4304// EXTRA: GRAPH STATISTICS COLLECTOR
4305// ============================================================
4306
4307#[derive(Debug, Clone)]
4308pub struct QuestGraphAnalyzer {
4309    pub graph_id: String,
4310    pub node_type_counts: HashMap<String, usize>,
4311    pub objective_types: Vec<String>,
4312    pub longest_path_length: usize,
4313    pub dead_ends: Vec<u64>,
4314    pub orphan_nodes: Vec<u64>,
4315    pub total_estimated_xp: u32,
4316    pub branch_factor_avg: f32,
4317    pub cycle_count: usize,
4318}
4319
4320impl QuestGraphAnalyzer {
4321    pub fn analyze(graph: &QuestGraph, player_level: u32, rng: &mut SimpleRng) -> Self {
4322        let mut node_type_counts: HashMap<String, usize> = HashMap::new();
4323        let mut objective_types = Vec::new();
4324        let mut dead_ends = Vec::new();
4325        let mut orphan_nodes = Vec::new();
4326        let mut total_xp = 0u32;
4327
4328        let reachable = if let Some(start) = graph.start_node_id {
4329            graph.find_reachable(start)
4330        } else { HashSet::new() };
4331
4332        for (id, node) in &graph.nodes {
4333            let type_name = format!("{:?}", node.node_type());
4334            *node_type_counts.entry(type_name).or_insert(0) += 1;
4335            if !reachable.contains(id) {
4336                orphan_nodes.push(*id);
4337                continue;
4338            }
4339            match node {
4340                QuestNodeEnum::Objective(obj) => {
4341                    objective_types.push(obj.objective.description());
4342                    if graph.get_outputs(*id).is_empty() { dead_ends.push(*id); }
4343                }
4344                QuestNodeEnum::Reward(r) => {
4345                    total_xp += r.reward_table.total_estimated_xp(player_level);
4346                }
4347                _ => {}
4348            }
4349        }
4350
4351        let branch_counts: Vec<usize> = graph.nodes.keys()
4352            .map(|id| graph.get_outputs(*id).len())
4353            .collect();
4354        let branch_factor_avg = if branch_counts.is_empty() { 0.0 } else {
4355            branch_counts.iter().sum::<usize>() as f32 / branch_counts.len() as f32
4356        };
4357
4358        let longest_path = if let Some(start) = graph.start_node_id {
4359            Self::find_longest_path(graph, start)
4360        } else { 0 };
4361
4362        QuestGraphAnalyzer {
4363            graph_id: graph.id.clone(),
4364            node_type_counts,
4365            objective_types,
4366            longest_path_length: longest_path,
4367            dead_ends,
4368            orphan_nodes,
4369            total_estimated_xp: total_xp,
4370            branch_factor_avg,
4371            cycle_count: 0,
4372        }
4373    }
4374
4375    fn find_longest_path(graph: &QuestGraph, start: u64) -> usize {
4376        let mut best = 0usize;
4377        let mut stack = vec![(start, 0usize, HashSet::new())];
4378        while let Some((node, depth, mut visited)) = stack.pop() {
4379            if visited.contains(&node) { continue; }
4380            visited.insert(node);
4381            best = best.max(depth);
4382            for conn in graph.get_outputs(node) {
4383                stack.push((conn.to_node, depth + 1, visited.clone()));
4384            }
4385        }
4386        best
4387    }
4388
4389    pub fn generate_report(&self) -> String {
4390        let mut report = String::new();
4391        report.push_str("=== QUEST GRAPH ANALYSIS ===\n\n");
4392        report.push_str("Node Types:\n");
4393        for (t, c) in &self.node_type_counts {
4394            report.push_str(&format!("  {}: {}\n", t, c));
4395        }
4396        report.push_str(&format!("\nLongest path: {} nodes\n", self.longest_path_length));
4397        report.push_str(&format!("Dead ends: {}\n", self.dead_ends.len()));
4398        report.push_str(&format!("Orphan nodes: {}\n", self.orphan_nodes.len()));
4399        report.push_str(&format!("Estimated XP reward: {}\n", self.total_estimated_xp));
4400        report.push_str(&format!("Average branch factor: {:.2}\n", self.branch_factor_avg));
4401        report.push_str(&format!("\nObjectives ({}):\n", self.objective_types.len()));
4402        for obj in &self.objective_types {
4403            report.push_str(&format!("  - {}\n", obj));
4404        }
4405        report
4406    }
4407}
4408
4409// ============================================================
4410// EXTRA: SAVE/LOAD
4411// ============================================================
4412
4413#[derive(Debug, Clone)]
4414pub struct QuestProjectFile {
4415    pub version: String,
4416    pub graphs: Vec<SerializedQuestGraph>,
4417    pub chains: Vec<QuestChain>,
4418    pub faction_state: Vec<(String, i32)>,
4419    pub state_flags: Vec<String>,
4420    pub metadata: QuestProjectMetadata,
4421}
4422
4423#[derive(Debug, Clone)]
4424pub struct SerializedQuestGraph {
4425    pub id: String,
4426    pub quest_id: String,
4427    pub name: String,
4428    pub node_count: usize,
4429    pub data: String,
4430}
4431
4432#[derive(Debug, Clone)]
4433pub struct QuestProjectMetadata {
4434    pub project_name: String,
4435    pub game_title: String,
4436    pub author: String,
4437    pub created: String,
4438    pub modified: String,
4439    pub version: String,
4440    pub editor_version: String,
4441}
4442
4443impl QuestProjectFile {
4444    pub fn new(project_name: &str) -> Self {
4445        QuestProjectFile {
4446            version: "1.0.0".to_string(),
4447            graphs: Vec::new(),
4448            chains: Vec::new(),
4449            faction_state: Vec::new(),
4450            state_flags: Vec::new(),
4451            metadata: QuestProjectMetadata {
4452                project_name: project_name.to_string(),
4453                game_title: String::new(),
4454                author: String::new(),
4455                created: String::new(),
4456                modified: String::new(),
4457                version: "1.0.0".to_string(),
4458                editor_version: "1.0.0".to_string(),
4459            },
4460        }
4461    }
4462
4463    pub fn from_editor(editor: &QuestEditor) -> Self {
4464        let mut file = QuestProjectFile::new("project");
4465        for (id, graph) in &editor.graphs {
4466            file.graphs.push(SerializedQuestGraph {
4467                id: graph.id.clone(),
4468                quest_id: graph.quest_id.clone(),
4469                name: graph.name.clone(),
4470                node_count: graph.nodes.len(),
4471                data: String::new(),
4472            });
4473        }
4474        for (id, chain) in &editor.chains {
4475            file.chains.push(chain.clone());
4476        }
4477        for (faction_id, rep) in &editor.quest_state_store.faction_reps {
4478            file.faction_state.push((faction_id.clone(), *rep));
4479        }
4480        for flag in &editor.quest_state_store.flags {
4481            file.state_flags.push(flag.clone());
4482        }
4483        file
4484    }
4485}
4486
4487// ============================================================
4488// EXTRA: REPEATABLE QUEST TRACKER
4489// ============================================================
4490
4491#[derive(Debug, Clone)]
4492pub struct RepeatableQuestTracker {
4493    pub quest_id: String,
4494    pub completion_count: u32,
4495    pub last_completion_time: f64,
4496    pub cooldown_secs: f64,
4497    pub max_completions: Option<u32>,
4498    pub rewards_scale_with_completions: bool,
4499    pub reward_scale_factor: f32,
4500    pub reward_cap_multiplier: f32,
4501}
4502
4503impl RepeatableQuestTracker {
4504    pub fn new(quest_id: &str, cooldown: f64) -> Self {
4505        RepeatableQuestTracker {
4506            quest_id: quest_id.to_string(),
4507            completion_count: 0,
4508            last_completion_time: -f64::MAX,
4509            cooldown_secs: cooldown,
4510            max_completions: None,
4511            rewards_scale_with_completions: false,
4512            reward_scale_factor: 1.0,
4513            reward_cap_multiplier: 2.0,
4514        }
4515    }
4516
4517    pub fn can_start(&self, current_time: f64) -> bool {
4518        if let Some(max) = self.max_completions {
4519            if self.completion_count >= max { return false; }
4520        }
4521        current_time - self.last_completion_time >= self.cooldown_secs
4522    }
4523
4524    pub fn complete(&mut self, current_time: f64) {
4525        self.completion_count += 1;
4526        self.last_completion_time = current_time;
4527    }
4528
4529    pub fn get_reward_multiplier(&self) -> f32 {
4530        if !self.rewards_scale_with_completions { return 1.0; }
4531        let base = 1.0 + (self.completion_count as f32 * self.reward_scale_factor);
4532        base.min(self.reward_cap_multiplier)
4533    }
4534
4535    pub fn cooldown_remaining(&self, current_time: f64) -> f64 {
4536        (self.cooldown_secs - (current_time - self.last_completion_time)).max(0.0)
4537    }
4538
4539    pub fn format_cooldown(&self, current_time: f64) -> String {
4540        let rem = self.cooldown_remaining(current_time);
4541        let hours = (rem / 3600.0) as u64;
4542        let mins = ((rem % 3600.0) / 60.0) as u64;
4543        let secs = (rem % 60.0) as u64;
4544        if hours > 0 {
4545            format!("{}h {}m", hours, mins)
4546        } else if mins > 0 {
4547            format!("{}m {}s", mins, secs)
4548        } else {
4549            format!("{}s", secs)
4550        }
4551    }
4552}
4553
4554// ============================================================
4555// EXTRA: OBJECTIVE PROGRESS TRACKER (for UI display)
4556// ============================================================
4557
4558#[derive(Debug, Clone)]
4559pub struct ObjectiveProgressDisplay {
4560    pub objective_id: String,
4561    pub description: String,
4562    pub progress_text: String,
4563    pub progress_value: f32,
4564    pub status: ObjectiveStatus,
4565    pub optional: bool,
4566    pub secret: bool,
4567    pub time_remaining: Option<f32>,
4568    pub map_markers: Vec<MapMarker>,
4569    pub new_this_session: bool,
4570}
4571
4572impl ObjectiveProgressDisplay {
4573    pub fn from_node(node: &ObjectiveNode) -> Self {
4574        let status = node.objective.get_status();
4575        ObjectiveProgressDisplay {
4576            objective_id: node.objective_id.clone(),
4577            description: node.objective.description(),
4578            progress_text: node.progress_display(),
4579            progress_value: node.objective.get_progress(),
4580            status,
4581            optional: node.optional,
4582            secret: node.secret,
4583            time_remaining: node.time_remaining(),
4584            map_markers: node.map_markers.clone(),
4585            new_this_session: false,
4586        }
4587    }
4588
4589    pub fn status_color(&self) -> Vec4 {
4590        match self.status {
4591            ObjectiveStatus::NotStarted => Vec4::new(0.5, 0.5, 0.5, 1.0),
4592            ObjectiveStatus::Active => Vec4::new(0.9, 0.9, 0.9, 1.0),
4593            ObjectiveStatus::Completed => Vec4::new(0.2, 0.9, 0.2, 1.0),
4594            ObjectiveStatus::Failed => Vec4::new(0.9, 0.2, 0.2, 1.0),
4595        }
4596    }
4597
4598    pub fn progress_bar_color(&self) -> Vec4 {
4599        match self.status {
4600            ObjectiveStatus::Active => Vec4::new(0.2, 0.5, 0.9, 1.0),
4601            ObjectiveStatus::Completed => Vec4::new(0.2, 0.8, 0.2, 1.0),
4602            ObjectiveStatus::Failed => Vec4::new(0.8, 0.2, 0.2, 1.0),
4603            _ => Vec4::new(0.4, 0.4, 0.4, 1.0),
4604        }
4605    }
4606}
4607
4608// ============================================================
4609// EXTRA: QUEST HUD MANAGER
4610// ============================================================
4611
4612#[derive(Debug, Clone)]
4613pub struct QuestHudManager {
4614    pub tracked_quests: Vec<TrackedQuestDisplay>,
4615    pub max_tracked: usize,
4616    pub show_completed_briefly: bool,
4617    pub completed_display_time: f32,
4618    pub notifications: VecDeque<QuestHudNotification>,
4619    pub objective_updates: VecDeque<ObjectiveUpdateAnim>,
4620    pub world_markers: Vec<WorldMapMarker>,
4621}
4622
4623#[derive(Debug, Clone)]
4624pub struct TrackedQuestDisplay {
4625    pub quest_id: String,
4626    pub quest_name: String,
4627    pub objectives: Vec<ObjectiveProgressDisplay>,
4628    pub is_main_quest: bool,
4629    pub pin_position: Option<u32>,
4630    pub recently_updated: bool,
4631    pub update_flash_time: f32,
4632}
4633
4634#[derive(Debug, Clone)]
4635pub struct QuestHudNotification {
4636    pub notification_id: String,
4637    pub text: String,
4638    pub sub_text: String,
4639    pub icon: String,
4640    pub sound: Option<String>,
4641    pub duration: f32,
4642    pub elapsed: f32,
4643    pub style: HudNotificationStyle,
4644}
4645
4646#[derive(Debug, Clone, PartialEq)]
4647pub enum HudNotificationStyle {
4648    QuestStarted,
4649    QuestComplete,
4650    QuestFailed,
4651    ObjectiveComplete,
4652    ObjectiveFailed,
4653    RewardReceived,
4654    RepChanged,
4655}
4656
4657#[derive(Debug, Clone)]
4658pub struct ObjectiveUpdateAnim {
4659    pub objective_id: String,
4660    pub update_type: ObjectiveUpdateType,
4661    pub elapsed: f32,
4662    pub duration: f32,
4663}
4664
4665#[derive(Debug, Clone)]
4666pub enum ObjectiveUpdateType {
4667    Progress(f32),
4668    Completed,
4669    Failed,
4670    Added,
4671    Removed,
4672}
4673
4674#[derive(Debug, Clone)]
4675pub struct WorldMapMarker {
4676    pub marker_id: String,
4677    pub world_pos: Vec3,
4678    pub label: String,
4679    pub icon_id: String,
4680    pub color: Vec4,
4681    pub is_visible: bool,
4682    pub distance_to_player: f32,
4683    pub compass_angle: f32,
4684    pub show_on_compass: bool,
4685    pub show_in_world: bool,
4686    pub min_distance: f32,
4687    pub max_distance: f32,
4688}
4689
4690impl WorldMapMarker {
4691    pub fn update_from_player(&mut self, player_pos: Vec3, player_forward: Vec3) {
4692        let to_marker = self.world_pos - player_pos;
4693        self.distance_to_player = to_marker.length();
4694        let angle = to_marker.x.atan2(to_marker.z);
4695        let player_angle = player_forward.x.atan2(player_forward.z);
4696        self.compass_angle = angle - player_angle;
4697        self.is_visible = self.distance_to_player >= self.min_distance &&
4698            (self.max_distance == 0.0 || self.distance_to_player <= self.max_distance);
4699    }
4700
4701    pub fn get_screen_edge_pos(&self, screen_size: Vec2) -> Vec2 {
4702        let half = screen_size * 0.5;
4703        let angle = self.compass_angle;
4704        let cos_a = angle.cos();
4705        let sin_a = angle.sin();
4706        let scale_x = half.x / sin_a.abs().max(0.001);
4707        let scale_y = half.y / cos_a.abs().max(0.001);
4708        let scale = scale_x.min(scale_y);
4709        Vec2::new(half.x + sin_a * scale, half.y - cos_a * scale)
4710    }
4711}
4712
4713impl QuestHudManager {
4714    pub fn new() -> Self {
4715        QuestHudManager {
4716            tracked_quests: Vec::new(),
4717            max_tracked: 5,
4718            show_completed_briefly: true,
4719            completed_display_time: 5.0,
4720            notifications: VecDeque::new(),
4721            objective_updates: VecDeque::new(),
4722            world_markers: Vec::new(),
4723        }
4724    }
4725
4726    pub fn update(&mut self, delta: f32) {
4727        // Update notifications
4728        self.notifications.retain_mut(|n| {
4729            n.elapsed += delta;
4730            n.elapsed < n.duration
4731        });
4732        // Update objective anims
4733        self.objective_updates.retain_mut(|a| {
4734            a.elapsed += delta;
4735            a.elapsed < a.duration
4736        });
4737        // Update flash timers
4738        for quest in &mut self.tracked_quests {
4739            if quest.recently_updated {
4740                quest.update_flash_time -= delta;
4741                if quest.update_flash_time <= 0.0 {
4742                    quest.recently_updated = false;
4743                }
4744            }
4745        }
4746    }
4747
4748    pub fn track_quest(&mut self, quest_id: &str, quest_name: &str, is_main: bool) {
4749        if self.tracked_quests.iter().any(|q| q.quest_id == quest_id) { return; }
4750        if self.tracked_quests.len() >= self.max_tracked { return; }
4751        self.tracked_quests.push(TrackedQuestDisplay {
4752            quest_id: quest_id.to_string(),
4753            quest_name: quest_name.to_string(),
4754            objectives: Vec::new(),
4755            is_main_quest: is_main,
4756            pin_position: None,
4757            recently_updated: true,
4758            update_flash_time: 3.0,
4759        });
4760    }
4761
4762    pub fn untrack_quest(&mut self, quest_id: &str) {
4763        self.tracked_quests.retain(|q| q.quest_id != quest_id);
4764    }
4765
4766    pub fn push_notification(&mut self, text: &str, sub_text: &str, style: HudNotificationStyle, icon: &str) {
4767        let duration = match style {
4768            HudNotificationStyle::QuestComplete | HudNotificationStyle::QuestFailed => 6.0,
4769            HudNotificationStyle::ObjectiveComplete => 4.0,
4770            _ => 3.0,
4771        };
4772        self.notifications.push_back(QuestHudNotification {
4773            notification_id: format!("notif_{}", self.notifications.len()),
4774            text: text.to_string(),
4775            sub_text: sub_text.to_string(),
4776            icon: icon.to_string(),
4777            sound: None,
4778            duration,
4779            elapsed: 0.0,
4780            style,
4781        });
4782    }
4783
4784    pub fn get_active_notifications(&self) -> Vec<&QuestHudNotification> {
4785        self.notifications.iter().collect()
4786    }
4787}
4788
4789// ============================================================
4790// MODULE-LEVEL UTILITY FUNCTIONS
4791// ============================================================
4792
4793pub fn create_sample_quest_graph() -> QuestGraph {
4794    let mut graph = QuestGraph::new("quest_graph_001".to_string(), "the_lost_artifact".to_string(), "The Lost Artifact".to_string());
4795
4796    let start_id = 1u64;
4797    let mut start = QuestStartNode::new(start_id);
4798    start.quest_name = "The Lost Artifact".to_string();
4799    start.description = "An ancient artifact has been stolen. Track it down and recover it.".to_string();
4800    start.quest_giver_id = "npc_scholar_aldred".to_string();
4801    start.position = Vec2::new(50.0, 200.0);
4802    graph.add_node(QuestNodeEnum::QuestStart(start));
4803
4804    let obj1_id = 2u64;
4805    let obj1 = QuestObjective::TalkToNPC(TalkToNPCObjective {
4806        npc_id: "npc_witness_sara".to_string(),
4807        npc_name: "Sara the Witness".to_string(),
4808        required_topic: Some("artifact_theft".to_string()),
4809        talked: false,
4810        dialogue_completed: false,
4811        require_specific_ending: None,
4812    });
4813    let mut obj_node1 = ObjectiveNode::new(obj1_id, obj1);
4814    obj_node1.position = Vec2::new(300.0, 100.0);
4815    obj_node1.journal_entry_on_start = Some("Find someone who witnessed the theft.".to_string());
4816    graph.add_node(QuestNodeEnum::Objective(obj_node1));
4817
4818    let obj2_id = 3u64;
4819    let obj2 = QuestObjective::InvestigateClue(InvestigateClueObjective {
4820        location_name: "The Old Warehouse".to_string(),
4821        clue_ids: vec!["clue_footprints".to_string(), "clue_torn_cloth".to_string(), "clue_stolen_tools".to_string()],
4822        clues_found: HashSet::new(),
4823        required_clues: 2,
4824        all_clues_required: false,
4825    });
4826    let mut obj_node2 = ObjectiveNode::new(obj2_id, obj2);
4827    obj_node2.position = Vec2::new(600.0, 100.0);
4828    obj_node2.journal_entry_on_start = Some("Investigate the old warehouse for clues.".to_string());
4829    graph.add_node(QuestNodeEnum::Objective(obj_node2));
4830
4831    let obj3_id = 4u64;
4832    let obj3 = QuestObjective::KillEnemy(KillEnemyObjective {
4833        enemy_type: "thief".to_string(),
4834        enemy_id: Some("boss_thief_marcus".to_string()),
4835        required_count: 1,
4836        killed_count: 0,
4837        in_area: Some("thief_hideout".to_string()),
4838        with_weapon_type: None,
4839        require_stealth_kill: false,
4840        allow_assists: false,
4841    });
4842    let mut obj_node3 = ObjectiveNode::new(obj3_id, obj3);
4843    obj_node3.position = Vec2::new(900.0, 100.0);
4844    obj_node3.journal_entry_on_start = Some("Defeat Marcus and recover the artifact.".to_string());
4845    graph.add_node(QuestNodeEnum::Objective(obj_node3));
4846
4847    let reward_id = 5u64;
4848    let mut reward_node = RewardNode::new(reward_id);
4849    reward_node.position = Vec2::new(1200.0, 100.0);
4850    reward_node.reward_table.add_xp(500, 0.1);
4851    reward_node.reward_table.add_item("ancient_artifact", 1, 1, 1.0, ItemRarity::Rare);
4852    reward_node.reward_table.add_currency("gold", 100, 200, 0.05);
4853    graph.add_node(QuestNodeEnum::Reward(reward_node));
4854
4855    let complete_id = 6u64;
4856    let mut complete_node = CompletionNode::new(complete_id);
4857    complete_node.position = Vec2::new(1500.0, 100.0);
4858    complete_node.completion_message = "The artifact has been recovered!".to_string();
4859    complete_node.unlock_quests = vec!["quest_ancient_mystery_2".to_string()];
4860    complete_node.set_flags = vec!["artifact_recovered".to_string()];
4861    graph.add_node(QuestNodeEnum::Completion(complete_node));
4862
4863    let fail_id = 7u64;
4864    let mut fail_node = FailNode::new(fail_id);
4865    fail_node.position = Vec2::new(900.0, 350.0);
4866    fail_node.fail_reason = "artifact_destroyed".to_string();
4867    fail_node.fail_message = "The artifact was destroyed. The quest has failed.".to_string();
4868    fail_node.allow_retry = false;
4869    graph.add_node(QuestNodeEnum::Fail(fail_node));
4870
4871    graph.connect(1, 0, 2, 0, None);
4872    graph.connect(2, 0, 3, 0, Some("witness_interviewed".to_string()));
4873    graph.connect(3, 0, 4, 0, Some("clues_found".to_string()));
4874    graph.connect(4, 0, 5, 0, Some("boss_defeated".to_string()));
4875    graph.connect(4, 1, 7, 0, Some("artifact_destroyed".to_string()));
4876    graph.connect(5, 0, 6, 0, None);
4877
4878    graph
4879}
4880
4881pub fn calculate_quest_reward_at_level(reward: &RewardTable, level: u32, rng: &mut SimpleRng) -> Vec<RolledReward> {
4882    reward.roll(level, rng)
4883}
4884
4885pub fn get_faction_tier_display(faction: &Faction) -> String {
4886    let tier = &faction.current_tier;
4887    let next_threshold = tier.next_tier_threshold();
4888    if let Some(next) = next_threshold {
4889        let current_threshold = match tier {
4890            ReputationTier::Neutral => -1000,
4891            ReputationTier::Friendly => 1000,
4892            ReputationTier::Honored => 2000,
4893            ReputationTier::Revered => 3000,
4894            _ => faction.reputation_min,
4895        };
4896        let range = next - current_threshold;
4897        let progress = faction.reputation - current_threshold;
4898        format!("{}: {}/{}", tier.display_name(), progress, range)
4899    } else {
4900        format!("{} (Max)", tier.display_name())
4901    }
4902}
4903
4904pub fn format_xp(xp: u32) -> String {
4905    if xp >= 1_000_000 {
4906        format!("{:.1}M XP", xp as f32 / 1_000_000.0)
4907    } else if xp >= 1_000 {
4908        format!("{:.1}K XP", xp as f32 / 1_000.0)
4909    } else {
4910        format!("{} XP", xp)
4911    }
4912}
4913
4914pub fn format_currency(amount: u32, currency_id: &str) -> String {
4915    let symbol = match currency_id {
4916        "gold" => "G",
4917        "silver" => "S",
4918        "copper" => "C",
4919        "credits" => "CR",
4920        _ => currency_id,
4921    };
4922    if amount >= 1_000_000 {
4923        format!("{:.1}M {}", amount as f32 / 1_000_000.0, symbol)
4924    } else if amount >= 1_000 {
4925        format!("{:.1}K {}", amount as f32 / 1_000.0, symbol)
4926    } else {
4927        format!("{} {}", amount, symbol)
4928    }
4929}
4930
4931pub fn interpolate_rep_color(rep: i32) -> Vec4 {
4932    let tier = ReputationTier::from_rep(rep);
4933    tier.color()
4934}
4935
4936pub fn check_all_prerequisites(quest: &QuestStartNode, state: &QuestStateStore) -> (bool, Vec<String>) {
4937    let mut unmet = Vec::new();
4938    let mut all_met = true;
4939    for prereq in &quest.prerequisites {
4940        if !prereq.is_satisfied(state) {
4941            all_met = false;
4942            let reason = match &prereq.prereq_type {
4943                PrerequisiteType::QuestCompleted => format!("Quest required: {:?}", prereq.quest_id),
4944                PrerequisiteType::PlayerLevel => format!("Level required: {:?}", prereq.level_required),
4945                PrerequisiteType::FactionRep => format!("Reputation required: {:?} {:?}", prereq.faction_id, prereq.faction_rep_required),
4946                PrerequisiteType::Flag => format!("Flag required: {:?}", prereq.flag_name),
4947                _ => "Unknown requirement".to_string(),
4948            };
4949            unmet.push(reason);
4950        }
4951    }
4952    (all_met, unmet)
4953}