mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Town hazards
This commit is contained in:
parent
cd97a4b2fc
commit
95af1536be
@ -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 {
|
||||
|
@ -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<i32>) -> f32 {
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
@ -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<i32>) -> SpawnRules {
|
||||
@ -46,10 +49,10 @@ impl Site {
|
||||
}
|
||||
|
||||
pub fn bounds(&self) -> Aabr<i32> {
|
||||
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<i32>) -> 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<i32>) -> Vec2<i32> {
|
||||
(wpos2d - self.origin).map(|e| e.div_euclid(TILE_SIZE as i32))
|
||||
}
|
||||
|
||||
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> {
|
||||
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<i32>) {
|
||||
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<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>> {
|
||||
(0..aabr.size().h)
|
||||
.map(move |y| (0..aabr.size().w)
|
||||
|
@ -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<i32>, // Inclusive
|
||||
zones: Grid<Option<Grid<Option<Tile>>>>,
|
||||
}
|
||||
|
||||
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<i32>) -> 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<i32>, tile: Tile) -> Option<Tile> {
|
||||
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<Id<Plot>>,
|
||||
@ -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 },
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user