1use 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
23pub 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}