From 9329b4ce5526aa6d407d9f3436bad7d7b7d450c6 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Fri, 7 Aug 2020 17:47:50 +0100 Subject: [PATCH] Added monsters to caves --- common/src/astar.rs | 12 ++- common/src/path.rs | 52 +++++---- world/src/layer/mod.rs | 102 ++++++++++++++++++ world/src/lib.rs | 3 + world/src/site/castle/keep.rs | 20 ++++ world/src/site/castle/mod.rs | 27 ++++- .../settlement/building/archetype/keep.rs | 15 ++- world/src/util/wgrid.rs | 26 +++++ 8 files changed, 230 insertions(+), 27 deletions(-) create mode 100644 world/src/site/castle/keep.rs create mode 100644 world/src/util/wgrid.rs diff --git a/common/src/astar.rs b/common/src/astar.rs index 920aeeee88..35781f0317 100644 --- a/common/src/astar.rs +++ b/common/src/astar.rs @@ -122,11 +122,9 @@ impl Astar { let iter_limit = self.max_iters.min(self.iter + iters); while self.iter < iter_limit { if let Some(PathEntry { node, cost }) = self.potential_nodes.pop() { - self.cheapest_cost = Some(cost); if satisfied(&node) { return PathResult::Path(self.reconstruct_path_to(node)); } else { - self.cheapest_node = Some(node.clone()); for neighbor in neighbors(&node) { let node_cheapest = self.cheapest_scores.get(&node).unwrap_or(&f32::MAX); let neighbor_cheapest = @@ -136,12 +134,18 @@ impl Astar { if cost < *neighbor_cheapest { self.came_from.insert(neighbor.clone(), node.clone()); self.cheapest_scores.insert(neighbor.clone(), cost); - let neighbor_cost = cost + heuristic(&neighbor); + let h = heuristic(&neighbor); + let neighbor_cost = cost + h; self.final_scores.insert(neighbor.clone(), neighbor_cost); + if self.cheapest_cost.map(|cc| h < cc).unwrap_or(true) { + self.cheapest_node = Some(node.clone()); + self.cheapest_cost = Some(h); + }; + if self.visited.insert(neighbor.clone()) { self.potential_nodes.push(PathEntry { - node: neighbor.clone(), + node: neighbor, cost: neighbor_cost, }); } diff --git a/common/src/path.rs b/common/src/path.rs index 5cfb245fc8..510724b597 100644 --- a/common/src/path.rs +++ b/common/src/path.rs @@ -99,6 +99,11 @@ impl Route { V: BaseVol + ReadVol, { let (next0, next1, next_tgt, be_precise) = loop { + // If we've reached the end of the path, stop + if self.next(0).is_none() { + return None; + } + let next0 = self .next(0) .unwrap_or_else(|| pos.map(|e| e.floor() as i32)); @@ -110,7 +115,7 @@ impl Route { } let be_precise = DIAGONALS.iter().any(|pos| { - (-2..2) + (-1..2) .all(|z| vol.get(next0 + Vec3::new(pos.x, pos.y, z)) .map(|b| !b.is_solid()) .unwrap_or(false)) @@ -312,7 +317,7 @@ impl Route { #[derive(Default, Clone, Debug)] pub struct Chaser { last_search_tgt: Option>, - route: Option, + route: Option<(Route, bool)>, /// We use this hasher (AAHasher) because: /// (1) we care about DDOS attacks (ruling out FxHash); /// (2) we don't care about determinism across computers (we can use @@ -335,14 +340,22 @@ impl Chaser { let pos_to_tgt = pos.distance(tgt); // If we're already close to the target then there's nothing to do - if ((pos - tgt) * Vec3::new(1.0, 1.0, 2.0)).magnitude_squared() + let end = self.route + .as_ref() + .and_then(|(r, _)| r.path.end().copied()) + .map(|e| e.map(|e| e as f32 + 0.5)) + .unwrap_or(tgt); + if ((pos - end) * Vec3::new(1.0, 1.0, 2.0)).magnitude_squared() < traversal_cfg.min_tgt_dist.powf(2.0) { self.route = None; return None; } - let bearing = if let Some(end) = self.route.as_ref().and_then(|r| r.path().end().copied()) { + let bearing = if let Some((end, complete)) = self.route + .as_ref() + .and_then(|(r, complete)| Some((r.path().end().copied()?, *complete))) + { let end_to_tgt = end.map(|e| e as f32).distance(tgt); // If the target has moved significantly since the path was generated then it's // time to search for a new path. Also, do this randomly from time @@ -350,12 +363,12 @@ impl Chaser { // theory this shouldn't happen, but in practice the world is full // of unpredictable obstacles that are more than willing to mess up // our day. TODO: Come up with a better heuristic for this - if end_to_tgt > pos_to_tgt * 0.3 + 5.0 || thread_rng().gen::() < 0.001 { + if (end_to_tgt > pos_to_tgt * 0.3 + 5.0 && complete) || thread_rng().gen::() < 0.001 { None } else { self.route .as_mut() - .and_then(|r| r.traverse(vol, pos, vel, traversal_cfg)) + .and_then(|(r, _)| r.traverse(vol, pos, vel, traversal_cfg)) } } else { None @@ -376,7 +389,9 @@ impl Chaser { || self.astar.is_some() || self.route.is_none() { - let (start_pos, path) = find_path(&mut self.astar, vol, pos, tgt); + self.last_search_tgt = Some(tgt); + + let (path, complete) = find_path(&mut self.astar, vol, pos, tgt); self.route = path.map(|path| { let start_index = path @@ -385,10 +400,10 @@ impl Chaser { .min_by_key(|(_, node)| node.xy().map(|e| e as f32).distance_squared(pos.xy() + tgt_dir) as i32) .map(|(idx, _)| idx); - Route { + (Route { path, next_idx: start_index.unwrap_or(0), - } + }, complete) }); } @@ -424,13 +439,14 @@ where .unwrap_or(true) } -#[allow(clippy::float_cmp)] // TODO: Pending review in #587 +/// Attempt to search for a path to a target, returning the path (if one was found) +/// and whether it is complete (reaches the target) fn find_path( astar: &mut Option, DefaultHashBuilder>>, vol: &V, startf: Vec3, endf: Vec3, -) -> (Vec3, Option>>) +) -> (Option>>, bool) where V: BaseVol + ReadVol, { @@ -452,7 +468,7 @@ where get_walkable_z(endf.map(|e| e.floor() as i32)), ) { (Some(start), Some(end)) => (start, end), - _ => return (startf, None), + _ => return (None, false), }; let heuristic = |pos: &Vec3| (pos.distance_squared(end) as f32).sqrt(); @@ -554,19 +570,19 @@ where *astar = Some(new_astar); - (startf, match path_result { + match path_result { PathResult::Path(path) => { *astar = None; - Some(path) + (Some(path), true) }, PathResult::None(path) => { *astar = None; - Some(path) + (Some(path), false) }, PathResult::Exhausted(path) => { *astar = None; - Some(path) + (Some(path), false) }, - PathResult::Pending => None, - }) + PathResult::Pending => (None, false), + } } diff --git a/world/src/layer/mod.rs b/world/src/layer/mod.rs index 687eb44bd8..100ff61f1e 100644 --- a/world/src/layer/mod.rs +++ b/world/src/layer/mod.rs @@ -5,9 +5,11 @@ use crate::{ }; use common::{ assets, + comp, lottery::Lottery, terrain::{Block, BlockKind}, vol::{BaseVol, ReadVol, RectSizedVol, Vox, WriteVol}, + generation::{ChunkSupplement, EntityInfo}, }; use noise::NoiseFn; use std::{ @@ -15,6 +17,7 @@ use std::{ ops::{Mul, Sub}, }; use vek::*; +use rand::prelude::*; pub fn apply_paths_to<'a>( wpos2d: Vec2, @@ -186,3 +189,102 @@ pub fn apply_caves_to<'a>( } } } + +pub fn apply_caves_supplement<'a>( + rng: &mut impl Rng, + wpos2d: Vec2, + mut get_column: impl FnMut(Vec2) -> Option<&'a ColumnSample<'a>>, + vol: &(impl BaseVol + RectSizedVol + ReadVol + WriteVol), + index: &Index, + supplement: &mut ChunkSupplement, +) { + for y in 0..vol.size_xy().y as i32 { + for x in 0..vol.size_xy().x as i32 { + let offs = Vec2::new(x, y); + + let wpos2d = wpos2d + offs; + + // Sample terrain + let col_sample = if let Some(col_sample) = get_column(offs) { + col_sample + } else { + continue; + }; + let surface_z = col_sample.riverless_alt.floor() as i32; + + if let Some((cave_dist, cave_nearest, cave, _)) = col_sample + .cave + .filter(|(dist, _, cave, _)| *dist < cave.width) + { + let cave_x = (cave_dist / cave.width).min(1.0); + + // Relative units + let cave_floor = 0.0 - 0.5 * (1.0 - cave_x.powf(2.0)).max(0.0).sqrt() * cave.width; + let cave_height = (1.0 - cave_x.powf(2.0)).max(0.0).sqrt() * cave.width; + + // Abs units + let cave_base = (cave.alt + cave_floor) as i32; + let cave_roof = (cave.alt + cave_height) as i32; + + // Scatter things in caves + if RandomField::new(index.seed).chance(wpos2d.into(), 0.0001) + && cave_base < surface_z as i32 - 40 + { + let entity = EntityInfo::at(Vec3::new(wpos2d.x as f32, wpos2d.y as f32, cave_base as f32)) + .with_alignment(comp::Alignment::Enemy) + .with_body(match rng.gen_range(0, 6) { + 0 => { + let species = match rng.gen_range(0, 2) { + 0 => comp::quadruped_small::Species::Truffler, + _ => comp::quadruped_small::Species::Hyena, + }; + comp::quadruped_small::Body::random_with(rng, &species).into() + }, + 1 => { + let species = match rng.gen_range(0, 3) { + 0 => comp::quadruped_medium::Species::Tarasque, + 1 => comp::quadruped_medium::Species::Frostfang, + _ => comp::quadruped_medium::Species::Bonerattler, + }; + comp::quadruped_medium::Body::random_with(rng, &species).into() + }, + 2 => { + let species = match rng.gen_range(0, 3) { + 0 => comp::quadruped_low::Species::Maneater, + 1 => comp::quadruped_low::Species::Rocksnapper, + _ => comp::quadruped_low::Species::Salamander, + }; + comp::quadruped_low::Body::random_with(rng, &species).into() + }, + 3 => { + let species = match rng.gen_range(0, 3) { + 0 => comp::critter::Species::Fungome, + 1 => comp::critter::Species::Axolotl, + _ => comp::critter::Species::Rat, + }; + comp::critter::Body::random_with(rng, &species).into() + }, + 4 => { + let species = match rng.gen_range(0, 1) { + _ => comp::golem::Species::StoneGolem, + }; + comp::golem::Body::random_with(rng, &species).into() + }, + _ => { + let species = match rng.gen_range(0, 4) { + 0 => comp::biped_large::Species::Ogre, + 1 => comp::biped_large::Species::Cyclops, + 2 => comp::biped_large::Species::Wendigo, + _ => comp::biped_large::Species::Troll, + }; + comp::biped_large::Body::random_with(rng, &species).into() + }, + }) + .with_automatic_name(); + + supplement.add_entity(entity); + } + } + } + } +} diff --git a/world/src/lib.rs b/world/src/lib.rs index d36576595b..9f08d58570 100644 --- a/world/src/lib.rs +++ b/world/src/lib.rs @@ -228,6 +228,9 @@ impl World { supplement.add_entity(EntityInfo::at(gen_entity_pos()).into_waypoint()); } + // Apply layer supplement + layer::apply_caves_supplement(&mut rng, chunk_wpos2d, sample_get, &chunk, &self.index, &mut supplement); + // Apply site supplementary information sim_chunk.sites.iter().for_each(|site| { self.index.sites[*site].apply_supplement( diff --git a/world/src/site/castle/keep.rs b/world/src/site/castle/keep.rs new file mode 100644 index 0000000000..2dff94facc --- /dev/null +++ b/world/src/site/castle/keep.rs @@ -0,0 +1,20 @@ +use vek::*; +use crate::{ + util::{attempt, Grid, RandomField, Sampler, CARDINALS, DIRS}, +}; + +pub struct Keep { + offset: Vec2, + cols: Grid, +} + +const KEEP_CELL_STOREY: i32 = 12; + +pub struct KeepCol { + z_offset: i32, + storeys: Vec, +} + +enum KeepCell { + Cube, +} diff --git a/world/src/site/castle/mod.rs b/world/src/site/castle/mod.rs index afd2404442..5d600ece84 100644 --- a/world/src/site/castle/mod.rs +++ b/world/src/site/castle/mod.rs @@ -1,3 +1,5 @@ +mod keep; + use super::SpawnRules; use crate::{ block::block_from_structure, @@ -52,6 +54,11 @@ pub struct Castle { keeps: Vec, rounded_towers: bool, ridged: bool, + flags: bool, + + evil: bool, + + keep: Option, } pub struct GenCtx<'a, R: Rng> { @@ -70,7 +77,7 @@ impl Castle { let radius = 150; - let this = Self { + let mut this = Self { origin: wpos, alt: ctx .sim @@ -116,6 +123,8 @@ impl Castle { .collect(), rounded_towers: ctx.rng.gen(), ridged: ctx.rng.gen(), + flags: ctx.rng.gen(), + evil: ctx.rng.gen(), keeps: (0..keep_count) .map(|i| { let angle = (i as f32 / keep_count as f32) * f32::consts::PI * 2.0; @@ -141,6 +150,8 @@ impl Castle { } }) .collect(), + + keep: None, }; this @@ -283,7 +294,16 @@ impl Castle { let wall_alt = wall_alt + (wall_sample.alt as i32 - wall_alt - 10).max(0); let keep_archetype = KeepArchetype { - flag_color: Rgb::new(200, 80, 40), + flag_color: if self.evil { + Rgb::new(80, 10, 130) + } else { + Rgb::new(200, 80, 40) + }, + stone_color: if self.evil { + Rgb::new(65, 60, 55) + } else { + Rgb::new(100, 100, 110) + }, }; for z in -10..64 { @@ -307,6 +327,7 @@ impl Castle { &Attr { storeys: 2, is_tower: false, + flag: self.flags, ridged: false, rounded: true, has_doors: false, @@ -347,6 +368,7 @@ impl Castle { &Attr { storeys: 3, is_tower: true, + flag: self.flags, ridged: self.ridged, rounded: self.rounded_towers, has_doors: false, @@ -387,6 +409,7 @@ impl Castle { &Attr { storeys: keep.storeys, is_tower: keep.is_tower, + flag: self.flags, ridged: self.ridged, rounded: self.rounded_towers, has_doors: true, diff --git a/world/src/site/settlement/building/archetype/keep.rs b/world/src/site/settlement/building/archetype/keep.rs index 476ab57f8d..a77df1c6ce 100644 --- a/world/src/site/settlement/building/archetype/keep.rs +++ b/world/src/site/settlement/building/archetype/keep.rs @@ -12,11 +12,13 @@ use vek::*; pub struct Keep { pub flag_color: Rgb, + pub stone_color: Rgb, } pub struct Attr { pub storeys: i32, pub is_tower: bool, + pub flag: bool, pub ridged: bool, pub rounded: bool, pub has_doors: bool, @@ -36,6 +38,7 @@ impl Archetype for Keep { attr: Attr { storeys, is_tower: false, + flag: false, ridged: false, rounded: true, has_doors: true, @@ -51,6 +54,7 @@ impl Archetype for Keep { attr: Attr { storeys: storeys + rng.gen_range(1, 3), is_tower: true, + flag: true, ridged: false, rounded: true, has_doors: false, @@ -68,6 +72,7 @@ impl Archetype for Keep { ( Self { flag_color: Rgb::new(200, 80, 40), + stone_color: Rgb::new(100, 100, 110), }, skel, ) @@ -114,7 +119,11 @@ impl Archetype for Keep { let brick_tex_pos = (pos + Vec3::new(pos.z, pos.z, 0)) / Vec3::new(2, 2, 1); let brick_tex = RandomField::new(0).get(brick_tex_pos) as u8 % 24; let foundation = make_block(80 + brick_tex, 80 + brick_tex, 80 + brick_tex); - let wall = make_block(100 + brick_tex, 100 + brick_tex, 110 + brick_tex); + let wall = make_block( + self.stone_color.r + brick_tex, + self.stone_color.g + brick_tex, + self.stone_color.b + brick_tex, + ); let window = BlockMask::new( Block::new(BlockKind::Window1, make_meta(ori.flip())), normal_layer, @@ -199,9 +208,9 @@ impl Archetype for Keep { if profile.y > roof_height && (min_dist < rampart_width - 1 || (attr.is_tower && min_dist < rampart_width)) { - if attr.is_tower && center_offset == Vec2::zero() && profile.y < roof_height + 16 { + if attr.is_tower && attr.flag && center_offset == Vec2::zero() && profile.y < roof_height + 16 { pole - } else if attr.is_tower + } else if attr.is_tower && attr.flag && center_offset.x == 0 && center_offset.y > 0 && center_offset.y < 8 diff --git a/world/src/util/wgrid.rs b/world/src/util/wgrid.rs new file mode 100644 index 0000000000..92b71f6dad --- /dev/null +++ b/world/src/util/wgrid.rs @@ -0,0 +1,26 @@ +use super::Grid; +use vek::*; + +pub struct WGrid { + cell_size: u32, + grid: Grid, +} + +impl WGrid { + pub fn new(radius: u32, cell_size: u32, default_cell: T) -> Self + where T: Clone + { + Self { + cell_size, + grid: Grid::new(Vec2::broadcast(radius as i32 * 2 + 1), default_cell), + } + } + + fn offset(&self) -> Vec2 { + self.grid.size() / 2 + } + + pub fn get_local(&self, pos: Vec2) -> Option<&T> { + self.grid.get(pos + self.offset()) + } +}