Skip to main content

proof_engine/narrative/
quest.rs

1//! Quest generation — objective chains from world state and motivations.
2
3use crate::worldgen::Rng;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
6pub enum QuestType { Fetch, Kill, Escort, Explore, Deliver, Defend, Investigate, Craft, Diplomacy, Rescue }
7
8#[derive(Debug, Clone)]
9pub struct QuestObjective { pub description: String, pub completed: bool, pub optional: bool }
10
11#[derive(Debug, Clone)]
12pub struct GeneratedQuest {
13    pub title: String,
14    pub quest_type: QuestType,
15    pub giver: String,
16    pub description: String,
17    pub objectives: Vec<QuestObjective>,
18    pub reward_description: String,
19    pub difficulty: f32,
20    pub moral_complexity: f32,
21}
22
23/// Generate a quest from world context.
24pub fn generate_quest(giver_name: &str, region_name: &str, threat_level: f32, rng: &mut Rng) -> GeneratedQuest {
25    let quest_type = random_quest_type(rng);
26    let (title, desc, objectives) = match quest_type {
27        QuestType::Fetch => {
28            let item = random_item(rng);
29            (format!("The Lost {}", item),
30             format!("{} asks you to find a {} lost in the wilds of {}.", giver_name, item, region_name),
31             vec![
32                 QuestObjective { description: format!("Find the {} in {}", item, region_name), completed: false, optional: false },
33                 QuestObjective { description: format!("Return the {} to {}", item, giver_name), completed: false, optional: false },
34             ])
35        }
36        QuestType::Kill => {
37            let monster = random_monster(rng);
38            (format!("Bane of the {}", monster),
39             format!("A {} terrorizes {}. {} begs for your aid.", monster, region_name, giver_name),
40             vec![
41                 QuestObjective { description: format!("Track the {} in {}", monster, region_name), completed: false, optional: false },
42                 QuestObjective { description: format!("Defeat the {}", monster), completed: false, optional: false },
43                 QuestObjective { description: "Collect proof of the deed".to_string(), completed: false, optional: true },
44             ])
45        }
46        QuestType::Investigate => {
47            (format!("Whispers in {}", region_name),
48             format!("Strange occurrences plague {}. {} wants answers.", region_name, giver_name),
49             vec![
50                 QuestObjective { description: "Gather clues from locals".to_string(), completed: false, optional: false },
51                 QuestObjective { description: "Investigate the source".to_string(), completed: false, optional: false },
52                 QuestObjective { description: format!("Report findings to {}", giver_name), completed: false, optional: false },
53             ])
54        }
55        _ => {
56            (format!("Task for {}", giver_name),
57             format!("{} needs your help in {}.", giver_name, region_name),
58             vec![QuestObjective { description: "Complete the task".to_string(), completed: false, optional: false }])
59        }
60    };
61
62    GeneratedQuest {
63        title, quest_type, giver: giver_name.to_string(), description: desc, objectives,
64        reward_description: random_reward(rng),
65        difficulty: threat_level * rng.range_f32(0.5, 1.5),
66        moral_complexity: rng.next_f32(),
67    }
68}
69
70fn random_quest_type(rng: &mut Rng) -> QuestType {
71    match rng.range_u32(0, 10) {
72        0..=2 => QuestType::Fetch, 3..=4 => QuestType::Kill, 5 => QuestType::Escort,
73        6 => QuestType::Explore, 7 => QuestType::Investigate, 8 => QuestType::Defend,
74        _ => QuestType::Deliver,
75    }
76}
77
78fn random_item(rng: &mut Rng) -> &'static str {
79    let items = ["Amulet", "Tome", "Crystal", "Crown", "Blade", "Chalice", "Map", "Key", "Orb", "Scroll"];
80    items[rng.next_u64() as usize % items.len()]
81}
82
83fn random_monster(rng: &mut Rng) -> &'static str {
84    let monsters = ["Wyvern", "Troll", "Banshee", "Golem", "Hydra", "Wraith", "Basilisk", "Chimera"];
85    monsters[rng.next_u64() as usize % monsters.len()]
86}
87
88fn random_reward(rng: &mut Rng) -> String {
89    let rewards = ["Gold and the gratitude of the people", "A rare enchanted item", "Knowledge of ancient secrets",
90        "Political favor and influence", "Access to restricted areas", "A powerful ally"];
91    rewards[rng.next_u64() as usize % rewards.len()].to_string()
92}
93
94#[cfg(test)]
95mod tests {
96    use super::*;
97
98    #[test]
99    fn test_generate_quest() {
100        let mut rng = Rng::new(42);
101        let q = generate_quest("Elder Vorn", "Ashwood", 0.5, &mut rng);
102        assert!(!q.title.is_empty());
103        assert!(!q.objectives.is_empty());
104    }
105}