Town hazards

This commit is contained in:
Joshua Barretto 2021-02-23 12:42:45 +00:00
parent 7d1c95eae7
commit 123651e0a6
4 changed files with 226 additions and 57 deletions

View File

@ -109,8 +109,8 @@ impl Civs {
attempt(5, || { attempt(5, || {
let (kind, size) = match ctx.rng.gen_range(0..8) { let (kind, size) = match ctx.rng.gen_range(0..8) {
0 => (SiteKind::Castle, 3), 0 => (SiteKind::Castle, 3),
1 => (SiteKind::Refactor, 5), 1 => (SiteKind::Dungeon, 0),
_ => (SiteKind::Dungeon, 0), _ => (SiteKind::Refactor, 5),
}; };
let loc = find_site_loc(&mut ctx, None, size)?; let loc = find_site_loc(&mut ctx, None, size)?;
this.establish_site(&mut ctx.reseed(), loc, |place| Site { this.establish_site(&mut ctx.reseed(), loc, |place| Site {

View File

@ -1,4 +1,8 @@
use crate::sim; use crate::sim;
use common::{
terrain::TerrainChunkSize,
vol::RectVolSize,
};
use vek::*; use vek::*;
/// A wrapper type that may contain a reference to a generated world. If not, default values will be provided. /// 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<i32>) -> f32 { pub fn get_alt_approx(&self, wpos: Vec2<i32>) -> f32 {
self.sim.and_then(|sim| sim.get_alt_approx(wpos)).unwrap_or(0.0) self.sim.and_then(|sim| sim.get_alt_approx(wpos)).unwrap_or(0.0)
} }
pub fn get_gradient_approx(&self, wpos: Vec2<i32>) -> 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<i32>) -> Option<&sim::SimChunk> {
self.sim.and_then(|sim| sim.get_wpos(wpos))
}
} }

View File

@ -3,19 +3,21 @@ mod tile;
use self::{ use self::{
plot::{Plot, PlotKind}, plot::{Plot, PlotKind},
tile::{TileGrid, Tile, TileKind, TILE_SIZE}, tile::{TileGrid, Tile, TileKind, HazardKind, TILE_SIZE},
}; };
use crate::{ use crate::{
site::SpawnRules, site::SpawnRules,
util::{Grid, attempt, CARDINALS, SQUARE_9}, util::{Grid, attempt, CARDINALS, SQUARE_4, SQUARE_9},
Canvas, Canvas,
Land, Land,
}; };
use common::{ use common::{
terrain::{Block, BlockKind, SpriteKind}, terrain::{Block, BlockKind, SpriteKind, TerrainChunkSize},
vol::RectVolSize,
store::{Id, Store}, store::{Id, Store},
astar::Astar, astar::Astar,
lottery::Lottery, lottery::Lottery,
spiral::Spiral2d,
}; };
use hashbrown::hash_map::DefaultHashBuilder; use hashbrown::hash_map::DefaultHashBuilder;
use rand::prelude::*; use rand::prelude::*;
@ -33,7 +35,8 @@ pub struct Site {
impl Site { impl Site {
pub fn radius(&self) -> f32 { 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<i32>) -> SpawnRules { pub fn spawn_rules(&self, wpos: Vec2<i32>) -> SpawnRules {
@ -46,10 +49,10 @@ impl Site {
} }
pub fn bounds(&self) -> Aabr<i32> { pub fn bounds(&self) -> Aabr<i32> {
let radius = tile::MAX_BLOCK_RADIUS; let border = 1;
Aabr { Aabr {
min: -Vec2::broadcast(radius as i32), min: self.origin + self.tile_wpos(self.tiles.bounds.min - border),
max: Vec2::broadcast(radius as i32), max: self.origin + self.tile_wpos(self.tiles.bounds.max + 1 + border),
} }
} }
@ -73,7 +76,7 @@ impl Site {
for y in 0..w { for y in 0..w {
for x in 0..w { for x in 0..w {
if self.tiles.get(*tile + Vec2::new(x, y)).is_obstacle() { 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 self.plazas
.choose(rng) .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)) .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 .filter(|&tile| self
.plazas .plazas
.iter() .iter()
@ -185,19 +189,37 @@ impl Site {
plaza 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<i32>) -> Self { pub fn generate(land: &Land, rng: &mut impl Rng, origin: Vec2<i32>) -> Self {
let mut site = Site { let mut site = Site {
origin, origin,
..Site::default() ..Site::default()
}; };
site.demarcate_obstacles(land);
site.make_plaza(land, rng); site.make_plaza(land, rng);
let build_chance = Lottery::from(vec![ let build_chance = Lottery::from(vec![
(1.0, 0), (1.0, 0),
(48.0, 1), (48.0, 1),
(5.0, 2), (5.0, 2),
(1.0, 3), (20.0, 3),
(1.0, 4),
]); ]);
let mut castles = 0; 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 // Castle
_ if castles < 1 => { _ if castles < 1 => {
if let Some((aabr, _)) = attempt(10, || site.find_roadside_aabr(rng, 16 * 16..18 * 18, Extent2::new(16, 16))) { if let Some((aabr, _)) = attempt(10, || site.find_roadside_aabr(rng, 16 * 16..18 * 18, Extent2::new(16, 16))) {
@ -295,21 +349,60 @@ impl Site {
site site
} }
pub fn wpos_tile_pos(&self, wpos2d: Vec2<i32>) -> Vec2<i32> {
(wpos2d - self.origin).map(|e| e.div_euclid(TILE_SIZE as i32))
}
pub fn wpos_tile(&self, wpos2d: Vec2<i32>) -> &Tile { pub fn wpos_tile(&self, wpos2d: Vec2<i32>) -> &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<i32>) -> Vec2<i32> {
self.origin + tile * tile::TILE_SIZE as i32
} }
pub fn tile_center_wpos(&self, tile: Vec2<i32>) -> Vec2<i32> { pub fn tile_center_wpos(&self, tile: Vec2<i32>) -> Vec2<i32> {
self.origin + tile * tile::TILE_SIZE as i32 + tile::TILE_SIZE as i32 / 2 self.origin + tile * tile::TILE_SIZE as i32 + tile::TILE_SIZE as i32 / 2
} }
pub fn render(&self, canvas: &mut Canvas, dynamic_rng: &mut impl Rng) { pub fn render_tile(&self, canvas: &mut Canvas, dynamic_rng: &mut impl Rng, tpos: Vec2<i32>) {
canvas.foreach_col(|canvas, wpos2d, col| { let tile = self.tiles.get(tpos);
let tile = self.wpos_tile(wpos2d); let twpos = self.tile_wpos(tpos);
let seed = tile.plot.map_or(0, |p| self.plot(p).seed); 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::Field | TileKind::Road => (-4..5).for_each(|z| canvas.map( match &tile.kind {
Vec3::new(wpos2d.x, wpos2d.y, col.alt as i32 + z), 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 [ |b| if [
BlockKind::Grass, BlockKind::Grass,
BlockKind::Earth, BlockKind::Earth,
@ -318,41 +411,91 @@ impl Site {
BlockKind::Rock, BlockKind::Rock,
] ]
.contains(&b.kind()) { .contains(&b.kind()) {
match tile.kind { Block::new(BlockKind::Rock, Rgb::new(55, 45, 65))
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 { } else {
b.with_sprite(SpriteKind::Empty) 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 render(&self, canvas: &mut Canvas, dynamic_rng: &mut impl Rng) {
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()) } pub fn test_site() -> Site { Site::generate(&Land::empty(), &mut thread_rng(), Vec2::zero()) }
fn wpos_is_hazard(land: &Land, wpos: Vec2<i32>) -> Option<HazardKind> {
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<i32>) -> impl Iterator<Item=Vec2<i32>> { pub fn aabr_tiles(aabr: Aabr<i32>) -> impl Iterator<Item=Vec2<i32>> {
(0..aabr.size().h) (0..aabr.size().h)
.map(move |y| (0..aabr.size().w) .map(move |y| (0..aabr.size().w)

View File

@ -9,12 +9,14 @@ pub const TILE_RADIUS: u32 = ZONE_SIZE * ZONE_RADIUS;
pub const MAX_BLOCK_RADIUS: u32 = TILE_SIZE * TILE_RADIUS; pub const MAX_BLOCK_RADIUS: u32 = TILE_SIZE * TILE_RADIUS;
pub struct TileGrid { pub struct TileGrid {
pub(crate) bounds: Aabr<i32>, // Inclusive
zones: Grid<Option<Grid<Option<Tile>>>>, zones: Grid<Option<Grid<Option<Tile>>>>,
} }
impl Default for TileGrid { impl Default for TileGrid {
fn default() -> Self { fn default() -> Self {
Self { Self {
bounds: Aabr::new_empty(Vec2::zero()),
zones: Grid::populate_from(Vec2::broadcast(ZONE_RADIUS as i32 * 2 + 1), |_| None), zones: Grid::populate_from(Vec2::broadcast(ZONE_RADIUS as i32 * 2 + 1), |_| None),
} }
} }
@ -35,6 +37,7 @@ impl TileGrid {
.unwrap_or(&EMPTY) .unwrap_or(&EMPTY)
} }
// WILL NOT EXPAND BOUNDS!
pub fn get_mut(&mut self, tpos: Vec2<i32>) -> Option<&mut Tile> { pub fn get_mut(&mut self, tpos: Vec2<i32>) -> Option<&mut Tile> {
let tpos = tpos + TILE_RADIUS as i32; let tpos = tpos + TILE_RADIUS as i32;
self.zones.get_mut(tpos.map(|e| e.div_euclid(ZONE_SIZE as i32))).and_then(|zone| { 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<i32>, tile: Tile) -> Option<Tile> { pub fn set(&mut self, tpos: Vec2<i32>, tile: Tile) -> Option<Tile> {
self.bounds.expand_to_contain_point(tpos);
self.get_mut(tpos).map(|t| std::mem::replace(t, tile)) self.get_mut(tpos).map(|t| std::mem::replace(t, tile))
} }
@ -110,6 +114,7 @@ impl TileGrid {
#[derive(Clone, PartialEq)] #[derive(Clone, PartialEq)]
pub enum TileKind { pub enum TileKind {
Empty, Empty,
Hazard(HazardKind),
Field, Field,
Road, Road,
Building { levels: u32 }, Building { levels: u32 },
@ -117,7 +122,7 @@ pub enum TileKind {
Wall, Wall,
} }
#[derive(Clone)] #[derive(Clone, PartialEq)]
pub struct Tile { pub struct Tile {
pub(crate) kind: TileKind, pub(crate) kind: TileKind,
pub(crate) plot: Option<Id<Plot>>, pub(crate) plot: Option<Id<Plot>>,
@ -144,9 +149,16 @@ impl Tile {
pub fn is_obstacle(&self) -> bool { pub fn is_obstacle(&self) -> bool {
matches!( matches!(
self.kind, self.kind,
TileKind::Building { .. } TileKind::Hazard(_)
| TileKind::Building { .. }
| TileKind::Castle | TileKind::Castle
| TileKind::Wall | TileKind::Wall
) )
} }
} }
#[derive(Copy, Clone, PartialEq)]
pub enum HazardKind {
Water,
Hill { gradient: f32 },
}