tavern_keeper/src/creature.rs

228 lines
6.7 KiB
Rust

use std::{fmt::Display};
use rand::prelude::*;
use crate::{time::Time, world::{World}, generators::PersonNameGenerator, state::{GameState, Action}, entity::{Entity, Location, EntityId, EntityType}, 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 loc: Location,
pub name: String,
pub birth_date: Time,
pub profession: Profession,
pub agenda: Agenda,
pub weapon: Option<EntityId>,
pub armor: Option<EntityId>,
pub coins: u32,
}
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::new_creature(),
loc: location,
name: PersonNameGenerator::name(),
birth_date: birth_date,
profession: profession,
agenda: Agenda::Idle(0),
weapon: None,
armor: None,
coins: 0,
}
}
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<Box<dyn Action>> {
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.loc.x < destination.x {
self.loc.x += 1;
} else if self.loc.x > destination.x {
self.loc.x -= 1;
}
if self.loc.y < destination.y {
self.loc.y += 1;
} else if self.loc.y > destination.y {
self.loc.y -= 1;
}
if self.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.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::Adventurer);
person.agenda = Agenda::Idle(0);
let mut world = World::new(32);
world.add_site(Site{
entity: Entity::new_creature(),
loc: Location{x: 10, y: 10},
structure: Structure::Town(Town::new()),
coins: 0,
});
person.step(&world);
match &person.agenda {
Agenda::Traveling(_) => {},
_ => { panic!("Person should be traveling") },
}
}
}