use std::{collections::HashMap, fmt::Display, time::Duration}; use bevy::prelude::Reflect; use serde::{Deserialize, Serialize}; use uuid::Uuid; use super::prelude::*; #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize, Reflect)] pub struct SiteId(Uuid); impl SiteId { pub fn generate() -> Self { Self(Uuid::new_v4()) } } impl Display for SiteId { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "site-{}", self.0) } } #[derive(Debug, Serialize, Deserialize, Reflect)] pub struct Site { id: SiteId, accumulated_time: Duration, pub name: String, pub definition: SiteDef, pub position: Coords, pub areas: Vec, } impl Site { #[inline] const fn minimum_tick() -> Duration { Duration::from_secs(3600) } pub fn new( definition: SiteDef, name: impl Into, position: Coords, areas: Vec, ) -> Self { Self { id: SiteId::generate(), name: name.into(), definition, position, areas, accumulated_time: Duration::default(), } } pub fn id(&self) -> SiteId { self.id } fn inner_tick(&mut self) { // Maps where creatures are going during patrol let mut patrol_map: HashMap = HashMap::new(); let site_size = self.areas.len(); for (area_index, area) in self.areas.iter_mut().enumerate() { // Resolve combat per area if let Some(results) = combat::resolve_combat(&area.population, 600) { for attack in results.attacks { // println!( // "{attacker} [{attacker_id}] attacks {target} [{target_id}]: {attack}", // attacker = attack.attacker.map_or("Natural causes", |attacker| &area // .find_creature(attacker) // .unwrap() // .definition // .name_singular), // attacker_id = attack // .attacker // .map_or("".into(), |id| id.to_string()), // target = area // .find_creature(attack.target) // .unwrap() // .definition // .name_singular, // target_id = attack.target // ); if let Some(damage) = attack.damage { let target = match area .population .iter_mut() .find(|creature| creature.id() == attack.target) { Some(target) => target, None => continue, }; target.state.damage = target.state.damage.saturating_add(damage); } } for death in results.kills { area.population .retain(|creature| creature.id() != death.killed); } } // Resolve patrolling creatures if site_size > 1 { for creature in area.population.iter() { if fastrand::f32() < creature.patrol_chance() { // Make sure the creature patrols to a different area let target_index = { // Remove the current area from the range let index = fastrand::usize(0..site_size - 1); if index < area_index { index } else { // Skip the current area index + 1 } }; patrol_map.insert(creature.id(), (area_index, target_index)); } } } } if !patrol_map.is_empty() { println!("Patrols:"); for (id, (from, to)) in patrol_map.iter() { println!("\t[{id}] patrols [{from}] -> [{to}]") } } // Apply patrol map for (creature_id, (from_index, to_index)) in patrol_map { let creature = self .areas .get_mut(from_index) .expect("Patrol: getting the area where the patrolling creature is") .remove_creature(creature_id) .expect("Patrol: finding the creature inside the area"); self.areas .get_mut(to_index) .expect("Patrol: getting the area where the creature is moving to") .population .push(creature); } println!("Tick! {self}"); } pub fn generate_from_def( site_def: &SiteDef, name: impl Into, position: Coords, ) -> Self { Self::new( site_def.clone(), name, position, vec![ SiteArea::default(); fastrand::usize(site_def.min_size as usize..=site_def.max_size as usize) ], ) } pub fn populate_randomly(&mut self, creature_defs: &[&CreatureDef]) { let area_count = self.areas.len(); { let min_count = area_count; let max_count = (5 + (area_count - 1) * 3).max(min_count); for _ in 0..fastrand::usize(min_count..=max_count) { let area_t = fastrand::f32().powi(2); let index = (area_t * (area_count - 1) as f32).round() as usize; let creature = Creature::generate_from_def( (*fastrand::choice(creature_defs.iter()).unwrap()).clone(), ); self.areas .get_mut(index) .expect("Creature generation index for depth is out of bounds") .population .push(creature) } } } pub fn advance_time(&mut self, time: Duration) { self.accumulated_time += time; while self.accumulated_time >= Self::minimum_tick() { self.inner_tick(); self.accumulated_time -= Self::minimum_tick(); } } /// Finds a creature with given id, removes it from the site and returns it with ownership pub fn remove_creature(&mut self, id: CreatureId) -> Option { for area in self.areas.iter_mut() { if let Some(creature) = area.remove_creature(id) { return Some(creature); } } None } } impl Display for Site { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, "site \"{name}\" [{id}] [", name = self.name, id = self.id )?; for (index, area) in self.areas.iter().enumerate() { write!(f, "\n\tarea {index} [")?; for creature in area.population.iter() { write!(f, "\n\t\t{creature}")?; } if !area.population.is_empty() { write!(f, "\n\t")?; } write!(f, "]")?; } if !self.areas.is_empty() { writeln!(f)?; } write!(f, "]") } } #[derive(Clone, Debug, Default, Serialize, Deserialize, Reflect)] pub struct SiteArea { pub population: Vec, } impl SiteArea { pub fn from_creatures(creatures: &[Creature]) -> Self { Self { population: creatures.to_vec(), } } pub fn find_creature(&self, id: CreatureId) -> Option<&Creature> { self.population.iter().find(|creature| creature.id() == id) } pub fn find_creature_mut(&mut self, id: CreatureId) -> Option<&mut Creature> { self.population .iter_mut() .find(|creature| creature.id() == id) } /// Finds a creature with given id, removes it from the area and returns it with ownership pub fn remove_creature(&mut self, id: CreatureId) -> Option { for i in 0..self.population.len() { if self.population[i].id() == id { return Some(self.population.swap_remove(i)); } } None } }