From e41ca0f13d713abe700e5b31bbd90b5efbca8e05 Mon Sep 17 00:00:00 2001 From: Joshua Barretto Date: Tue, 23 Feb 2021 12:42:45 +0000 Subject: [PATCH] Town hazards --- world/src/civ/mod.rs | 4 +- world/src/land.rs | 14 +++ world/src/site2/mod.rs | 249 +++++++++++++++++++++++++++++++--------- world/src/site2/tile.rs | 16 ++- 4 files changed, 226 insertions(+), 57 deletions(-) diff --git a/world/src/civ/mod.rs b/world/src/civ/mod.rs index ce451ce42d..19bac38ed2 100644 --- a/world/src/civ/mod.rs +++ b/world/src/civ/mod.rs @@ -109,8 +109,8 @@ impl Civs { attempt(5, || { let (kind, size) = match ctx.rng.gen_range(0..8) { 0 => (SiteKind::Castle, 3), - 1 => (SiteKind::Refactor, 5), - _ => (SiteKind::Dungeon, 0), + 1 => (SiteKind::Dungeon, 0), + _ => (SiteKind::Refactor, 5), }; let loc = find_site_loc(&mut ctx, None, size)?; this.establish_site(&mut ctx.reseed(), loc, |place| Site { diff --git a/world/src/land.rs b/world/src/land.rs index 9790b3091b..2bf8ec6b09 100644 --- a/world/src/land.rs +++ b/world/src/land.rs @@ -1,4 +1,8 @@ use crate::sim; +use common::{ + terrain::TerrainChunkSize, + vol::RectVolSize, +}; use vek::*; /// A wrapper type that may contain a reference to a generated world. If not, default values will be provided. @@ -18,4 +22,14 @@ impl<'a> Land<'a> { pub fn get_alt_approx(&self, wpos: Vec2) -> f32 { self.sim.and_then(|sim| sim.get_alt_approx(wpos)).unwrap_or(0.0) } + + pub fn get_gradient_approx(&self, wpos: Vec2) -> f32 { + self.sim + .and_then(|sim| sim.get_gradient_approx(wpos.map2(TerrainChunkSize::RECT_SIZE, |e, sz| e.div_euclid(sz as i32)))) + .unwrap_or(0.0) + } + + pub fn get_chunk_at(&self, wpos: Vec2) -> Option<&sim::SimChunk> { + self.sim.and_then(|sim| sim.get_wpos(wpos)) + } } diff --git a/world/src/site2/mod.rs b/world/src/site2/mod.rs index 5bb834dab5..3be06ebf8f 100644 --- a/world/src/site2/mod.rs +++ b/world/src/site2/mod.rs @@ -3,19 +3,21 @@ mod tile; use self::{ plot::{Plot, PlotKind}, - tile::{TileGrid, Tile, TileKind, TILE_SIZE}, + tile::{TileGrid, Tile, TileKind, HazardKind, TILE_SIZE}, }; use crate::{ site::SpawnRules, - util::{Grid, attempt, CARDINALS, SQUARE_9}, + util::{Grid, attempt, CARDINALS, SQUARE_4, SQUARE_9}, Canvas, Land, }; use common::{ - terrain::{Block, BlockKind, SpriteKind}, + terrain::{Block, BlockKind, SpriteKind, TerrainChunkSize}, + vol::RectVolSize, store::{Id, Store}, astar::Astar, lottery::Lottery, + spiral::Spiral2d, }; use hashbrown::hash_map::DefaultHashBuilder; use rand::prelude::*; @@ -33,7 +35,8 @@ pub struct Site { impl Site { pub fn radius(&self) -> f32 { - (tile::MAX_BLOCK_RADIUS.pow(2) as f32 * 2.0).sqrt() + ((self.tiles.bounds.min.map(|e| e.abs()).reduce_max() + .max(self.tiles.bounds.max.map(|e| e.abs()).reduce_max()) + 1) * tile::TILE_SIZE as i32) as f32 } pub fn spawn_rules(&self, wpos: Vec2) -> SpawnRules { @@ -46,10 +49,10 @@ impl Site { } pub fn bounds(&self) -> Aabr { - let radius = tile::MAX_BLOCK_RADIUS; + let border = 1; Aabr { - min: -Vec2::broadcast(radius as i32), - max: Vec2::broadcast(radius as i32), + min: self.origin + self.tile_wpos(self.tiles.bounds.min - border), + max: self.origin + self.tile_wpos(self.tiles.bounds.max + 1 + border), } } @@ -73,7 +76,7 @@ impl Site { for y in 0..w { for x in 0..w { if self.tiles.get(*tile + Vec2::new(x, y)).is_obstacle() { - return 100.0; + return 1000.0; } } } @@ -145,6 +148,7 @@ impl Site { self.plazas .choose(rng) .map(|&p| self.plot(p).root_tile + (Vec2::new(rng.gen_range(-1.0..1.0), rng.gen_range(-1.0..1.0)).normalized() * 24.0).map(|e| e as i32)) + .filter(|tile| !self.tiles.get(*tile).is_obstacle()) .filter(|&tile| self .plazas .iter() @@ -185,19 +189,37 @@ impl Site { plaza } + pub fn demarcate_obstacles(&mut self, land: &Land) { + const SEARCH_RADIUS: u32 = 96; + + Spiral2d::new() + .take((SEARCH_RADIUS * 2 + 1).pow(2) as usize) + .for_each(|tile| { + if let Some(kind) = wpos_is_hazard(land, self.tile_wpos(tile)) { + for &rpos in &SQUARE_4 { + // `get_mut` doesn't increase generation bounds + self.tiles.get_mut(tile - rpos - 1).map(|tile| tile.kind = TileKind::Hazard(kind)); + } + } + }); + } + pub fn generate(land: &Land, rng: &mut impl Rng, origin: Vec2) -> Self { let mut site = Site { origin, ..Site::default() }; + site.demarcate_obstacles(land); + site.make_plaza(land, rng); let build_chance = Lottery::from(vec![ (1.0, 0), (48.0, 1), (5.0, 2), - (1.0, 3), + (20.0, 3), + (1.0, 4), ]); let mut castles = 0; @@ -247,6 +269,38 @@ impl Site { }); } }, + // Field + 3 => { + attempt(10, || { + let search_pos = attempt(16, || { + let tile = (Vec2::new( + rng.gen_range(-1.0..1.0), + rng.gen_range(-1.0..1.0), + ).normalized() * rng.gen_range(32.0..48.0)).map(|e| e as i32); + + if site + .plazas + .iter() + .all(|&p| site.plot(p).root_tile.distance_squared(tile) > 20i32.pow(2)) + && rng.gen_range(0..48) > tile.map(|e| e.abs()).reduce_max() + { + Some(tile) + } else { + None + } + }) + .unwrap_or_else(Vec2::zero); + site.tiles.find_near( + search_pos, + |center, _| site.tiles.grow_aabr(center, 9..25, Extent2::new(3, 3)).ok()) + }) + .map(|(aabr, _)| { + site.blit_aabr(aabr, Tile { + kind: TileKind::Field, + plot: None, + }); + }); + }, // Castle _ if castles < 1 => { if let Some((aabr, _)) = attempt(10, || site.find_roadside_aabr(rng, 16 * 16..18 * 18, Extent2::new(16, 16))) { @@ -295,64 +349,153 @@ impl Site { site } + pub fn wpos_tile_pos(&self, wpos2d: Vec2) -> Vec2 { + (wpos2d - self.origin).map(|e| e.div_euclid(TILE_SIZE as i32)) + } + pub fn wpos_tile(&self, wpos2d: Vec2) -> &Tile { - self.tiles.get((wpos2d - self.origin).map(|e| e.div_euclid(TILE_SIZE as i32))) + self.tiles.get(self.wpos_tile_pos(wpos2d)) + } + + pub fn tile_wpos(&self, tile: Vec2) -> Vec2 { + self.origin + tile * tile::TILE_SIZE as i32 } pub fn tile_center_wpos(&self, tile: Vec2) -> Vec2 { self.origin + tile * tile::TILE_SIZE as i32 + tile::TILE_SIZE as i32 / 2 } + pub fn render_tile(&self, canvas: &mut Canvas, dynamic_rng: &mut impl Rng, tpos: Vec2) { + let tile = self.tiles.get(tpos); + let twpos = self.tile_wpos(tpos); + let cols = (-(TILE_SIZE as i32)..TILE_SIZE as i32 * 2).map(|y| (-(TILE_SIZE as i32)..TILE_SIZE as i32 * 2).map(move |x| (twpos + Vec2::new(x, y), Vec2::new(x, y)))).flatten(); + + match &tile.kind { + TileKind::Empty | TileKind::Hazard(_) => {}, + TileKind::Road => cols.for_each(|(wpos2d, offs)| { + let tpos = self.tile_wpos(wpos2d); + + let is_x = [ + self.tiles.get(tpos - Vec2::unit_x()) == tile, + self.tiles.get(tpos) == tile, + self.tiles.get(tpos + Vec2::unit_x()) == tile, + ]; + + let dist_x = [ + if is_x[0] ^ is_x[1] { Some((offs.x % tile::TILE_SIZE as i32) * if is_x[1] { -1 } else { 1 }) } else { None }, + if is_x[1] ^ is_x[2] { Some((tile::TILE_SIZE as i32 - offs.x % tile::TILE_SIZE as i32) * if is_x[1] { -1 } else { 1 }) } else { None }, + ].iter().filter_map(|x| *x).min(); + + let is_y = [ + self.tiles.get(tpos - Vec2::unit_y()) == tile, + self.tiles.get(tpos) == tile, + self.tiles.get(tpos + Vec2::unit_y()) == tile, + ]; + + let dist_y = [ + if is_y[0] ^ is_y[1] { Some((offs.y % tile::TILE_SIZE as i32) * if is_y[1] { -1 } else { 1 }) } else { None }, + if is_y[1] ^ is_y[2] { Some((tile::TILE_SIZE as i32 - offs.y % tile::TILE_SIZE as i32) * if is_y[1] { -1 } else { 1 }) } else { None }, + ].iter().filter_map(|x| *x).min(); + + let dist = dist_x.unwrap_or(-(tile::TILE_SIZE as i32)).min(dist_y.unwrap_or(-(tile::TILE_SIZE as i32))); + + if dist > 4 { + let alt = canvas.col(wpos2d).map_or(0, |c| c.alt as i32); + (-4..5).for_each(|z| canvas.map( + Vec3::new(wpos2d.x, wpos2d.y, alt + z), + |b| if [ + BlockKind::Grass, + BlockKind::Earth, + BlockKind::Sand, + BlockKind::Snow, + BlockKind::Rock, + ] + .contains(&b.kind()) { + Block::new(BlockKind::Rock, Rgb::new(55, 45, 65)) + } else { + b.with_sprite(SpriteKind::Empty) + }, + )); + } + }), + _ => {}, + } + } + pub fn render(&self, canvas: &mut Canvas, dynamic_rng: &mut impl Rng) { - canvas.foreach_col(|canvas, wpos2d, col| { - let tile = self.wpos_tile(wpos2d); - let seed = tile.plot.map_or(0, |p| self.plot(p).seed); - match tile.kind { - TileKind::Field | TileKind::Road => (-4..5).for_each(|z| canvas.map( - Vec3::new(wpos2d.x, wpos2d.y, col.alt as i32 + z), - |b| if [ - BlockKind::Grass, - BlockKind::Earth, - BlockKind::Sand, - BlockKind::Snow, - BlockKind::Rock, - ] - .contains(&b.kind()) { - match tile.kind { - TileKind::Field => Block::new(BlockKind::Earth, Rgb::new(40, 5 + (seed % 32) as u8, 0)), - TileKind::Road => Block::new(BlockKind::Rock, Rgb::new(55, 45, 65)), - _ => unreachable!(), - } - } else { - b.with_sprite(SpriteKind::Empty) - }, - )), - TileKind::Building { levels } => { - let base_alt = tile.plot.map(|p| self.plot(p)).map_or(col.alt as i32, |p| p.base_alt); - for z in base_alt - 12..base_alt + 4 + 6 * levels as i32 { - canvas.set( - Vec3::new(wpos2d.x, wpos2d.y, z), - Block::new(BlockKind::Wood, Rgb::new(180, 90 + (seed % 64) as u8, 120)) - ); - } - }, - TileKind::Castle | TileKind::Wall => { - let base_alt = tile.plot.map(|p| self.plot(p)).map_or(col.alt as i32, |p| p.base_alt); - for z in base_alt - 12..base_alt + if tile.kind == TileKind::Wall { 24 } else { 40 } { - canvas.set( - Vec3::new(wpos2d.x, wpos2d.y, z), - Block::new(BlockKind::Wood, Rgb::new(40, 40, 55)) - ); - } - }, - _ => {}, + let tile_aabr = Aabr { + min: self.wpos_tile_pos(canvas.wpos()) - 1, + max: self.wpos_tile_pos(canvas.wpos() + TerrainChunkSize::RECT_SIZE.map(|e| e as i32) + 2) + 3, // Round up, uninclusive, border + }; + + for y in tile_aabr.min.y..tile_aabr.max.y { + for x in tile_aabr.min.x..tile_aabr.max.x { + self.render_tile(canvas, dynamic_rng, Vec2::new(x, y)); } - }); + } + + // canvas.foreach_col(|canvas, wpos2d, col| { + // let tile = self.wpos_tile(wpos2d); + // let seed = tile.plot.map_or(0, |p| self.plot(p).seed); + // match tile.kind { + // TileKind::Field | TileKind::Road => (-4..5).for_each(|z| canvas.map( + // Vec3::new(wpos2d.x, wpos2d.y, col.alt as i32 + z), + // |b| if [ + // BlockKind::Grass, + // BlockKind::Earth, + // BlockKind::Sand, + // BlockKind::Snow, + // BlockKind::Rock, + // ] + // .contains(&b.kind()) { + // match tile.kind { + // TileKind::Field => Block::new(BlockKind::Earth, Rgb::new(40, 5 + (seed % 32) as u8, 0)), + // TileKind::Road => Block::new(BlockKind::Rock, Rgb::new(55, 45, 65)), + // _ => unreachable!(), + // } + // } else { + // b.with_sprite(SpriteKind::Empty) + // }, + // )), + // TileKind::Building { levels } => { + // let base_alt = tile.plot.map(|p| self.plot(p)).map_or(col.alt as i32, |p| p.base_alt); + // for z in base_alt - 12..base_alt + 4 + 6 * levels as i32 { + // canvas.set( + // Vec3::new(wpos2d.x, wpos2d.y, z), + // Block::new(BlockKind::Wood, Rgb::new(180, 90 + (seed % 64) as u8, 120)) + // ); + // } + // }, + // TileKind::Castle | TileKind::Wall => { + // let base_alt = tile.plot.map(|p| self.plot(p)).map_or(col.alt as i32, |p| p.base_alt); + // for z in base_alt - 12..base_alt + if tile.kind == TileKind::Wall { 24 } else { 40 } { + // canvas.set( + // Vec3::new(wpos2d.x, wpos2d.y, z), + // Block::new(BlockKind::Wood, Rgb::new(40, 40, 55)) + // ); + // } + // }, + // _ => {}, + // } + // }); } } pub fn test_site() -> Site { Site::generate(&Land::empty(), &mut thread_rng(), Vec2::zero()) } +fn wpos_is_hazard(land: &Land, wpos: Vec2) -> Option { + if land + .get_chunk_at(wpos) + .map_or(true, |c| c.river.near_water()) + { + Some(HazardKind::Water) + } else if let Some(gradient) = Some(land.get_gradient_approx(wpos)).filter(|g| *g > 0.8) { + Some(HazardKind::Hill { gradient }) + } else { + None + } +} + pub fn aabr_tiles(aabr: Aabr) -> impl Iterator> { (0..aabr.size().h) .map(move |y| (0..aabr.size().w) diff --git a/world/src/site2/tile.rs b/world/src/site2/tile.rs index 64d23da4c0..64b7ff8fa7 100644 --- a/world/src/site2/tile.rs +++ b/world/src/site2/tile.rs @@ -9,12 +9,14 @@ pub const TILE_RADIUS: u32 = ZONE_SIZE * ZONE_RADIUS; pub const MAX_BLOCK_RADIUS: u32 = TILE_SIZE * TILE_RADIUS; pub struct TileGrid { + pub(crate) bounds: Aabr, // Inclusive zones: Grid>>>, } impl Default for TileGrid { fn default() -> Self { Self { + bounds: Aabr::new_empty(Vec2::zero()), zones: Grid::populate_from(Vec2::broadcast(ZONE_RADIUS as i32 * 2 + 1), |_| None), } } @@ -35,6 +37,7 @@ impl TileGrid { .unwrap_or(&EMPTY) } + // WILL NOT EXPAND BOUNDS! pub fn get_mut(&mut self, tpos: Vec2) -> Option<&mut Tile> { let tpos = tpos + TILE_RADIUS as i32; self.zones.get_mut(tpos.map(|e| e.div_euclid(ZONE_SIZE as i32))).and_then(|zone| { @@ -47,6 +50,7 @@ impl TileGrid { } pub fn set(&mut self, tpos: Vec2, tile: Tile) -> Option { + self.bounds.expand_to_contain_point(tpos); self.get_mut(tpos).map(|t| std::mem::replace(t, tile)) } @@ -110,6 +114,7 @@ impl TileGrid { #[derive(Clone, PartialEq)] pub enum TileKind { Empty, + Hazard(HazardKind), Field, Road, Building { levels: u32 }, @@ -117,7 +122,7 @@ pub enum TileKind { Wall, } -#[derive(Clone)] +#[derive(Clone, PartialEq)] pub struct Tile { pub(crate) kind: TileKind, pub(crate) plot: Option>, @@ -144,9 +149,16 @@ impl Tile { pub fn is_obstacle(&self) -> bool { matches!( self.kind, - TileKind::Building { .. } + TileKind::Hazard(_) + | TileKind::Building { .. } | TileKind::Castle | TileKind::Wall ) } } + +#[derive(Copy, Clone, PartialEq)] +pub enum HazardKind { + Water, + Hill { gradient: f32 }, +}