diff --git a/src/creature.rs b/src/creature.rs index 7a5724c..4519a0a 100644 --- a/src/creature.rs +++ b/src/creature.rs @@ -1,17 +1,20 @@ use std::{fmt::Display}; use rand::prelude::*; -use crate::{time::Time, world::{World}, generators::PersonNameGenerator, state::{GameState, Action}, entity::{Entity, Location}}; +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), - Traveling(Location), + Idle(u32), // number of days to idle + Traveling(Location), // destination + Craft, } #[derive(Clone)] @@ -21,16 +24,75 @@ pub struct Creature { 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) -> 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::Peasant, + 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), } } @@ -40,9 +102,7 @@ impl Creature { // do nothing if *days <= 0 { // pick random destination - let mut rng = rand::thread_rng(); - let dest = world.sites.keys().choose(&mut rng); - self.agenda = Agenda::Traveling(*dest.unwrap()); + self.agenda = self.select_agenda(world); } else { self.agenda = Agenda::Idle(days - 1); } @@ -66,6 +126,14 @@ impl Creature { } Vec::new() }, + Agenda::Craft => { + vec![ + Box::new(ItemCrafted { + crafter: self.entity.id, + item: ItemGenerator::generate_item(), + }) + ] + } } } @@ -85,6 +153,9 @@ impl Creature { }, None => return format!("I'm traveling to an unknown location"), } + }, + Agenda::Craft => { + return format!("I'm crafting"); } } } @@ -96,6 +167,23 @@ impl Display for Creature { } } + + +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}}; @@ -104,13 +192,13 @@ mod tests { #[test] fn test_person_creation() { - let person = Creature::new(Time { time: 0 }, Location{x: 0, y: 0}); + 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}); + 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}); @@ -118,7 +206,7 @@ mod tests { #[test] fn test_start_traveling() { - let mut person = Creature::new(Time { time: 0 }, Location{x: 0, y: 0}); + 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{ @@ -132,4 +220,4 @@ mod tests { } } -} \ No newline at end of file +} diff --git a/src/events/item.rs b/src/events/item.rs new file mode 100644 index 0000000..a82bcb8 --- /dev/null +++ b/src/events/item.rs @@ -0,0 +1,20 @@ +use crate::{entity::EntityId, item::Item, state::{Action, GameState, self}}; + +pub struct ItemCrafted { + pub crafter: EntityId, + pub item: Item, +} + +impl Action for ItemCrafted { + fn apply(&self, state: &mut GameState) { + state.add_item(self.item.clone()); + } + + fn description(&self) -> String { + format!("{} crafted", self.item.name) + } + + fn notable(&self) -> bool { + true + } +} \ No newline at end of file diff --git a/src/events/mod.rs b/src/events/mod.rs index 3ba0597..67fd495 100644 --- a/src/events/mod.rs +++ b/src/events/mod.rs @@ -2,8 +2,10 @@ mod found_town; mod world_genesis; mod person_genesis; mod tavern_built; +mod item; pub use found_town::FoundTown; pub use world_genesis::WorldGenesis; pub use person_genesis::PersonGenesis; -pub use tavern_built::TavernBuilt; \ No newline at end of file +pub use tavern_built::TavernBuilt; +pub use item::ItemCrafted; \ No newline at end of file diff --git a/src/item.rs b/src/item.rs new file mode 100644 index 0000000..09fde02 --- /dev/null +++ b/src/item.rs @@ -0,0 +1,44 @@ +use crate::entity::{Entity, Location}; + +#[derive(Clone)] +pub enum ItemType { + Weapon(Weapon), + Armor(Armor), +} + +#[derive(Clone)] +pub struct Item { + pub entity: Entity, + pub name: String, + pub item_type: ItemType, +} + +#[derive(Clone)] +pub struct Weapon { + pub damage_base: u32, + pub damage_dice: u32, + pub damage_sides: u32, +} + +#[derive(Clone)] +pub struct Armor { + pub armor_class: u32, +} + + + +pub struct ItemGenerator; + +impl ItemGenerator { + pub fn generate_item() -> Item { + Item { + entity: Entity { id: 0, loc: Location{ x: 0, y: 0 } }, + name: "Sword".to_string(), + item_type: ItemType::Weapon(Weapon { + damage_base: 0, + damage_dice: 1, + damage_sides: 6, + }), + } + } +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 0d6ad44..0ac93e2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,6 +8,7 @@ mod site; mod ui; mod entity; mod game; +mod item; use ui::App; use noise::{Perlin, ScalePoint, Add, NoiseFn, ScaleBias}; diff --git a/src/state.rs b/src/state.rs index eabaa4f..0b18775 100644 --- a/src/state.rs +++ b/src/state.rs @@ -3,7 +3,8 @@ use std::collections::HashMap; use rand::prelude::*; use crate::entity::{Location, EntityId}; -use crate::creature::Creature; +use crate::creature::{Creature, Profession, CreatureGenerator}; +use crate::item::Item; use crate::site::{Town, Tavern}; use crate::time::Time; use crate::world::{World, Terrain}; @@ -12,6 +13,7 @@ pub struct GameState { pub time: Time, pub world: World, pub creatures: HashMap, + pub items: HashMap, pub events: Vec>, pub tavern: Option, } @@ -52,6 +54,7 @@ impl GameState { GameState { time: Time { time: 0 }, creatures: HashMap::new(), + items: HashMap::new(), world: world, events: events, tavern: None, @@ -119,10 +122,10 @@ impl GameState { time: self.time, effect: Box::new(PersonGenesis { town: town.clone(), - person: Creature::new( + person: CreatureGenerator::create_human( self.time.substract_years(rng.gen_range(18..30)), - Location { x: x as i32, y: y as i32 } - ), + Location { x: x as i32, y: y as i32 }, + ) }) }); } @@ -137,6 +140,13 @@ impl GameState { self.creatures.insert(id, creature); } + pub(crate) fn add_item(&mut self, mut item: Item) -> EntityId { + let id = (self.items.len() + 1) as u32; // avoid 0 id + item.entity.id = id; + self.items.insert(id, item); + id + } + pub fn build_tavern(&mut self) { let mut rng = rand::thread_rng(); loop { @@ -157,6 +167,7 @@ impl GameState { } } } + } #[cfg(test)] diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 9304d1a..883d040 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -238,7 +238,8 @@ impl<'a> App<'a> { self.conversation.add_line(ChatLine::new( guest_id.unwrap(), guest.name.clone(), - "Hello, I'm ".to_owned() + &guest.name + ".", + "Hello, I'm ".to_owned() + &guest.name + ", nice to meet you! " + + "I'm a " + &guest.profession.to_string() + ".", false )); self.conversation_scroll = 0;