use std::{fmt::Display}; use rand::prelude::*; use crate::{time::Time, world::{World}, generators::PersonNameGenerator, state::{GameState, Action}, entity::{Entity, Location, EntityId}, item::ItemGenerator, events::ItemCrafted}; #[derive(Clone, Copy)] pub enum Profession { Peasant, Adventurer, Blacksmith, } #[derive(Clone, Copy)] pub enum Agenda { Idle(u32), // number of days to idle Traveling(Location), // destination Craft, } #[derive(Clone)] pub struct Creature { pub entity: Entity, pub name: String, pub birth_date: Time, pub profession: Profession, pub agenda: Agenda, pub inventory: Vec, pub weapon: Option, pub armor: Option, } pub struct CreatureGenerator; impl Display for Profession { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Profession::Peasant => write!(f, "Peasant"), Profession::Adventurer => write!(f, "Adventurer"), Profession::Blacksmith => write!(f, "Blacksmith"), } } } impl Creature { pub fn new(birth_date: Time, location: Location, profession: Profession) -> Creature { Creature { entity: Entity { id: 0, loc: location }, name: PersonNameGenerator::name(), birth_date: birth_date, profession: profession, agenda: Agenda::Idle(0), inventory: Vec::new(), weapon: None, armor: None, } } fn travel_to_random_location(&self, world: &World) -> Agenda { let mut rng = rand::thread_rng(); let dest = world.sites.keys().choose(&mut rng); if let Some(dest) = dest { Agenda::Traveling(*dest) } else { Agenda::Idle(1) } } fn select_peasant_agenda(&self, _world: &World) -> Agenda { Agenda::Idle(10) } fn select_adventurer_agenda(&self, world: &World) -> Agenda { self.travel_to_random_location(world) } fn select_blacksmith_agenda(&self, _world: &World) -> Agenda { let mut rng = rand::thread_rng(); let p = rng.gen_range(0.0..1.0); if p < 0.1 { Agenda::Idle(10) } else if p < 0.55 { Agenda::Craft } else { self.travel_to_random_location(_world) } } fn select_agenda(&self, world: &World) -> Agenda { match self.profession { Profession::Peasant => self.select_peasant_agenda(world), Profession::Adventurer => self.select_adventurer_agenda(world), Profession::Blacksmith => self.select_blacksmith_agenda(world), } } pub fn step(&mut self, world: &World) -> Vec> { match &self.agenda { Agenda::Idle(days) => { // do nothing if *days <= 0 { // pick random destination self.agenda = self.select_agenda(world); } else { self.agenda = Agenda::Idle(days - 1); } Vec::new() }, Agenda::Traveling(destination) => { // TDOO: A* pathfinding with terrain costs // move towards destination if self.entity.loc.x < destination.x { self.entity.loc.x += 1; } else if self.entity.loc.x > destination.x { self.entity.loc.x -= 1; } if self.entity.loc.y < destination.y { self.entity.loc.y += 1; } else if self.entity.loc.y > destination.y { self.entity.loc.y -= 1; } if self.entity.loc == *destination { self.agenda = Agenda::Idle(10); } Vec::new() }, Agenda::Craft => { vec![ Box::new(ItemCrafted { crafter: self.entity.id, item: ItemGenerator::generate_item(), }) ] } } } #[allow(dead_code)] pub fn set_agenda(&mut self, agenda: Agenda) { self.agenda = agenda; } pub fn say_agenda(&self, state: & GameState) -> String { match &self.agenda { Agenda::Idle(days) => format!("I'll stay here for {} days", days), Agenda::Traveling(destination) => { let dest_site = state.world.get_site_at(*destination); match dest_site { Some(site) => { return format!("I'm traveling to {}", site); }, None => return format!("I'm traveling to an unknown location"), } }, Agenda::Craft => { return format!("I'm crafting"); } } } } impl Display for Creature { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.name) } } impl CreatureGenerator { pub fn create_human(birth_date: Time, location: Location) -> Creature { // pick random profession let mut rng = rand::thread_rng(); let profession = rng.gen_range(0..3); let profession = match profession { 0 => Profession::Peasant, 1 => Profession::Adventurer, 2 => Profession::Blacksmith, _ => panic!("Invalid profession"), }; Creature::new(birth_date, location, profession) } } #[cfg(test)] mod tests { use crate::{world::{World}, site::{Site, Town, Structure}}; use super::*; #[test] fn test_person_creation() { let person = Creature::new(Time { time: 0 }, Location{x: 0, y: 0}, Profession::Peasant); assert_ne!(person.name, ""); } #[test] fn test_traveling() { let mut person = Creature::new(Time { time: 0 }, Location{x: 0, y: 0}, Profession::Peasant); person.agenda = Agenda::Traveling(Location{x: 10, y: 0}); person.step(&World::new(32)); assert_eq!(person.entity.loc, Location{x: 1, y: 0}); } #[test] fn test_start_traveling() { let mut person = Creature::new(Time { time: 0 }, Location{x: 0, y: 0}, Profession::Peasant); person.agenda = Agenda::Idle(0); let mut world = World::new(32); world.add_site(Site{ entity: Entity { id: 0, loc: Location{x: 10, y: 10} }, structure: Structure::Town(Town::new()), }); person.step(&world); match &person.agenda { Agenda::Traveling(_) => {}, _ => { panic!("Person should be traveling") }, } } }