diff --git a/Cargo.lock b/Cargo.lock index aea01c2083..1c98e40f9b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2650,6 +2650,12 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fc7aa29613bd6a620df431842069224d8bc9011086b1db4c0e0cd47fa03ec9a" +[[package]] +name = "libm" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7d73b3f436185384286bd8098d17ec07c9a7d2388a6599f824d8502b529702a" + [[package]] name = "libsqlite3-sys" version = "0.18.0" @@ -3345,6 +3351,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" dependencies = [ "autocfg", + "libm 0.2.1", ] [[package]] @@ -3587,7 +3594,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3278e0492f961fd4ae70909f56b2723a7e8d01a228427294e19cdfdebda89a17" dependencies = [ "cfg-if 0.1.10", - "libm", + "libm 0.1.4", ] [[package]] @@ -3965,6 +3972,16 @@ dependencies = [ "getrandom 0.2.2", ] +[[package]] +name = "rand_distr" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da9e8f32ad24fb80d07d2323a9a2ce8b30d68a62b8cb4df88119ff49a698f038" +dependencies = [ + "num-traits", + "rand 0.8.3", +] + [[package]] name = "rand_hc" version = "0.2.0" @@ -5574,6 +5591,7 @@ dependencies = [ "prometheus", "prometheus-hyper", "rand 0.8.3", + "rand_distr", "rayon", "ron", "scan_fmt", diff --git a/server/Cargo.toml b/server/Cargo.toml index 3866cc5b55..d3e05894b8 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -49,6 +49,7 @@ diesel = { version = "1.4.3", features = ["sqlite"] } diesel_migrations = "1.4.0" dotenv = "0.15.0" slab = "0.4" +rand_distr = "0.4.0" # Plugins plugin-api = { package = "veloren-plugin-api", path = "../plugin/api"} diff --git a/server/src/rtsim/entity.rs b/server/src/rtsim/entity.rs index 42ad0fafcf..2c7e3bb202 100644 --- a/server/src/rtsim/entity.rs +++ b/server/src/rtsim/entity.rs @@ -6,6 +6,8 @@ use common::{ store::Id, terrain::TerrainGrid, }; +use rand_distr::{Distribution, Normal}; +use std::f32::consts::PI; use world::{ civ::{Site, Track}, util::RandomPerm, @@ -132,56 +134,107 @@ impl Entity { } pub fn tick(&mut self, time: &Time, terrain: &TerrainGrid, world: &World, index: &IndexRef) { - self.brain.route = match self.brain.route { + self.brain.route = match self.brain.route.clone() { Travel::Lost => { - if !self.get_body().is_humanoid() { - if let Some(target_id) = world - .civs() - .sites - .iter() - .filter(|s| match self.get_body() { - comp::Body::Ship(_) => s.1.is_settlement(), - _ => s.1.is_dungeon(), - }) - .filter(|_| thread_rng().gen_range(0i32..4) == 0) - .min_by_key(|(_, site)| { - let wpos = site.center * TerrainChunk::RECT_SIZE.map(|e| e as i32); - let dist = wpos.map(|e| e as f32).distance(self.pos.xy()) as u32; - dist + if dist < 96 { 100_000 } else { 0 } - }) - .map(|(id, _)| id) - { - Travel::Direct { target_id } - } else { - Travel::Lost - } - } else if let Some(nearest_site_id) = world - .civs() - .sites - .iter() - .min_by_key(|(_, site)| { - let wpos = site.center * TerrainChunk::RECT_SIZE.map(|e| e as i32); - wpos.map(|e| e as f32).distance(self.pos.xy()) as u32 - }) - .map(|(id, _)| id) - { - let nearest_site = &world.civs().sites[nearest_site_id]; - let site_wpos = nearest_site.center * TerrainChunk::RECT_SIZE.map(|e| e as i32); - let dist = site_wpos.map(|e| e as f32).distance(self.pos.xy()) as u32; - if dist < 32 { - Travel::InSite { - site_id: nearest_site_id, + match self.get_body() { + comp::Body::Humanoid(_) => { + if let Some(nearest_site_id) = world + .civs() + .sites + .iter() + .min_by_key(|(_, site)| { + let wpos = site.center * TerrainChunk::RECT_SIZE.map(|e| e as i32); + wpos.map(|e| e as f32).distance(self.pos.xy()) as u32 + }) + .map(|(id, _)| id) + { + let nearest_site = &world.civs().sites[nearest_site_id]; + let site_wpos = + nearest_site.center * TerrainChunk::RECT_SIZE.map(|e| e as i32); + let dist = site_wpos.map(|e| e as f32).distance(self.pos.xy()) as u32; + if dist < 32 { + Travel::InSite { + site_id: nearest_site_id, + } + } else { + Travel::Direct { + target_id: nearest_site_id, + } + } + } else { + // Somehow no nearest site could be found + // Logically this should never happen, but if it does the rtsim entity + // will just sit tight + Travel::Lost } - } else { - Travel::Direct { - target_id: nearest_site_id, + }, + comp::Body::Ship(_) => { + if let Some((target_id, site)) = world + .civs() + .sites + .iter() + .filter(|s| match self.get_body() { + comp::Body::Ship(_) => s.1.is_settlement(), + _ => s.1.is_dungeon(), + }) + .filter(|_| thread_rng().gen_range(0i32..4) == 0) + .min_by_key(|(_, site)| { + let wpos = site.center * TerrainChunk::RECT_SIZE.map(|e| e as i32); + let dist = wpos.map(|e| e as f32).distance(self.pos.xy()) as u32; + dist + if dist < 96 { 100_000 } else { 0 } + }) + { + let mut rng = thread_rng(); + if let (Ok(normalpos), Ok(normalrad)) = + (Normal::new(0.0, 64.0), (Normal::new(0.0, PI / 8.0))) + { + let mut path = Vec::>::default(); + let target_site_pos = site.center.map(|e| e as f32) + * TerrainChunk::RECT_SIZE.map(|e| e as f32); + let offset_site_pos = + target_site_pos.map(|v| v + normalpos.sample(&mut rng)); + let dir_vec = (offset_site_pos - self.pos.xy()).normalized(); + let dist = (offset_site_pos - self.pos.xy()).magnitude(); + let inbetween_dir = dir_vec.rotated_z(normalrad.sample(&mut rng)); + let inbetween_pos = self.pos.xy() + (inbetween_dir * (dist / 2.0)); + + path.push(inbetween_pos.map(|e| e as i32)); + path.push(target_site_pos.map(|e| e as i32)); + + Travel::CustomPath { + target_id, + path, + progress: 0, + } + } else { + Travel::Direct { target_id } + } + } else { + Travel::Lost } - } - } else { - // Somehow no nearest site could be found - // Logically this should never happen, but if it does the rtsim entity will just - // sit tight - Travel::Lost + }, + _ => { + if let Some(target_id) = world + .civs() + .sites + .iter() + .filter(|s| match self.get_body() { + comp::Body::Ship(_) => s.1.is_settlement(), + _ => s.1.is_dungeon(), + }) + .filter(|_| thread_rng().gen_range(0i32..4) == 0) + .min_by_key(|(_, site)| { + let wpos = site.center * TerrainChunk::RECT_SIZE.map(|e| e as i32); + let dist = wpos.map(|e| e as f32).distance(self.pos.xy()) as u32; + dist + if dist < 96 { 100_000 } else { 0 } + }) + .map(|(id, _)| id) + { + Travel::Direct { target_id } + } else { + Travel::Lost + } + }, } }, Travel::InSite { site_id } => { @@ -251,6 +304,60 @@ impl Entity { Travel::Direct { target_id } } }, + Travel::CustomPath { + target_id, + path, + progress, + } => { + let site = &world.civs().sites[target_id]; + let destination_name = site + .site_tmp + .map_or("".to_string(), |id| index.sites[id].name().to_string()); + + if let Some(wpos) = &path.get(progress) { + let dist = wpos.map(|e| e as f32).distance(self.pos.xy()) as u32; + if dist < 64 { + if progress + 1 < path.len() { + Travel::CustomPath { + target_id, + path, + progress: progress + 1, + } + } else { + Travel::InSite { site_id: target_id } + } + } else { + let travel_to = self.pos.xy() + + Vec3::from( + (wpos.map(|e| e as f32 + 0.5) - self.pos.xy()) + .try_normalized() + .unwrap_or_else(Vec2::zero), + ) * 64.0; + let travel_to_alt = world + .sim() + .get_alt_approx(travel_to.map(|e| e as i32)) + .unwrap_or(0.0) as i32; + let travel_to = terrain + .find_space(Vec3::new( + travel_to.x as i32, + travel_to.y as i32, + travel_to_alt, + )) + .map(|e| e as f32) + + Vec3::new(0.5, 0.5, 0.0); + + self.controller.travel_to = Some((travel_to, destination_name)); + self.controller.speed_factor = 0.70; + Travel::CustomPath { + target_id, + path, + progress, + } + } + } else { + Travel::Direct { target_id } + } + }, Travel::Path { target_id, track_id, @@ -345,6 +452,7 @@ impl Entity { } } +#[derive(Clone)] enum Travel { Lost, InSite { @@ -353,6 +461,11 @@ enum Travel { Direct { target_id: Id, }, + CustomPath { + target_id: Id, + path: Vec>, + progress: usize, + }, Path { target_id: Id, track_id: Id,