2020-06-27 23:37:14 +00:00
|
|
|
use super::SpawnRules;
|
|
|
|
use crate::{
|
|
|
|
block::block_from_structure,
|
|
|
|
column::ColumnSample,
|
|
|
|
sim::WorldSim,
|
|
|
|
site::{
|
2020-06-28 15:09:31 +00:00
|
|
|
settlement::building::{
|
|
|
|
archetype::keep::{Attr, Keep},
|
|
|
|
Archetype, Branch, Ori,
|
|
|
|
},
|
2020-06-27 23:37:14 +00:00
|
|
|
BlockMask,
|
|
|
|
},
|
|
|
|
util::{attempt, Grid, RandomField, Sampler, CARDINALS, DIRS},
|
|
|
|
};
|
|
|
|
use common::{
|
|
|
|
assets,
|
|
|
|
astar::Astar,
|
|
|
|
comp,
|
|
|
|
generation::{ChunkSupplement, EntityInfo},
|
|
|
|
npc,
|
|
|
|
store::{Id, Store},
|
|
|
|
terrain::{Block, BlockKind, Structure, TerrainChunkSize},
|
|
|
|
vol::{BaseVol, ReadVol, RectSizedVol, RectVolSize, Vox, WriteVol},
|
|
|
|
};
|
|
|
|
use core::{f32, hash::BuildHasherDefault};
|
|
|
|
use fxhash::FxHasher64;
|
|
|
|
use lazy_static::lazy_static;
|
|
|
|
use rand::prelude::*;
|
|
|
|
use std::sync::Arc;
|
|
|
|
use vek::*;
|
|
|
|
|
|
|
|
struct Segment {
|
|
|
|
offset: Vec2<i32>,
|
|
|
|
locus: i32,
|
|
|
|
height: i32,
|
|
|
|
is_tower: bool,
|
|
|
|
}
|
|
|
|
|
|
|
|
struct Tower {
|
|
|
|
offset: Vec2<i32>,
|
|
|
|
alt: i32,
|
|
|
|
}
|
|
|
|
|
|
|
|
pub struct Castle {
|
|
|
|
origin: Vec2<i32>,
|
|
|
|
alt: i32,
|
|
|
|
seed: u32,
|
2020-06-28 15:09:31 +00:00
|
|
|
radius: i32,
|
2020-06-27 23:37:14 +00:00
|
|
|
towers: Vec<Tower>,
|
|
|
|
segments: Vec<Segment>,
|
2020-06-29 12:43:16 +00:00
|
|
|
rounded_towers: bool,
|
2020-06-27 23:37:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub struct GenCtx<'a, R: Rng> {
|
|
|
|
sim: Option<&'a WorldSim>,
|
|
|
|
rng: &'a mut R,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Castle {
|
|
|
|
#[allow(clippy::let_and_return)] // TODO: Pending review in #587
|
|
|
|
pub fn generate(wpos: Vec2<i32>, sim: Option<&WorldSim>, rng: &mut impl Rng) -> Self {
|
|
|
|
let mut ctx = GenCtx { sim, rng };
|
|
|
|
|
|
|
|
let boundary_towers = ctx.rng.gen_range(5, 10);
|
2020-06-28 15:09:31 +00:00
|
|
|
let boundary_noise = ctx.rng.gen_range(-2i32, 8).max(1) as f32;
|
|
|
|
|
|
|
|
let radius = 150;
|
2020-06-27 23:37:14 +00:00
|
|
|
|
|
|
|
let this = Self {
|
|
|
|
origin: wpos,
|
|
|
|
alt: ctx
|
|
|
|
.sim
|
|
|
|
.and_then(|sim| sim.get_alt_approx(wpos))
|
|
|
|
.unwrap_or(0.0) as i32
|
|
|
|
+ 6,
|
|
|
|
seed: ctx.rng.gen(),
|
2020-06-28 15:09:31 +00:00
|
|
|
radius,
|
2020-06-27 23:37:14 +00:00
|
|
|
|
|
|
|
towers: (0..boundary_towers)
|
|
|
|
.map(|i| {
|
|
|
|
let angle = (i as f32 / boundary_towers as f32) * f32::consts::PI * 2.0;
|
2020-06-28 15:09:31 +00:00
|
|
|
let dir = Vec2::new(angle.cos(), angle.sin());
|
|
|
|
let dist = radius as f32 + ((angle * boundary_noise).sin() - 1.0) * 40.0;
|
2020-06-27 23:37:14 +00:00
|
|
|
|
2020-06-28 15:09:31 +00:00
|
|
|
let mut offset = (dir * dist).map(|e| e as i32);
|
|
|
|
// Try to move the tower around until it's not intersecting a path
|
|
|
|
for i in (1..80).step_by(5) {
|
|
|
|
if ctx
|
|
|
|
.sim
|
|
|
|
.and_then(|sim| sim.get_nearest_path(wpos + offset))
|
2020-06-30 11:28:25 +00:00
|
|
|
.map(|(dist, _, _, _)| dist > 24.0)
|
2020-06-28 15:09:31 +00:00
|
|
|
.unwrap_or(true)
|
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
offset = (dir * dist)
|
|
|
|
.map(|e| (e + ctx.rng.gen_range(-1.0, 1.0) * i as f32) as i32);
|
|
|
|
}
|
2020-06-27 23:37:14 +00:00
|
|
|
|
|
|
|
Tower {
|
|
|
|
offset,
|
|
|
|
alt: ctx
|
|
|
|
.sim
|
|
|
|
.and_then(|sim| sim.get_alt_approx(wpos + offset))
|
2020-06-28 15:09:31 +00:00
|
|
|
.unwrap_or(0.0) as i32
|
|
|
|
+ 2,
|
2020-06-27 23:37:14 +00:00
|
|
|
}
|
|
|
|
})
|
|
|
|
.collect(),
|
2020-06-29 12:43:16 +00:00
|
|
|
rounded_towers: ctx.rng.gen(),
|
2020-06-27 23:37:14 +00:00
|
|
|
|
2020-06-28 15:09:31 +00:00
|
|
|
segments: (0..0) //rng.gen_range(18, 24))
|
2020-06-27 23:37:14 +00:00
|
|
|
.map(|_| {
|
2020-06-28 15:09:31 +00:00
|
|
|
let dir = Vec2::new(ctx.rng.gen_range(-1.0, 1.0), ctx.rng.gen_range(-1.0, 1.0))
|
|
|
|
.normalized();
|
|
|
|
let dist = 16.0 + ctx.rng.gen_range(0.0f32, 1.0).powf(0.5) * 64.0;
|
2020-06-27 23:37:14 +00:00
|
|
|
let height = 48.0 - (dist / 64.0).powf(2.0) * 32.0;
|
|
|
|
|
|
|
|
Segment {
|
2020-06-28 15:09:31 +00:00
|
|
|
offset: (dir * dist).map(|e| e as i32),
|
|
|
|
locus: ctx.rng.gen_range(6, 26),
|
|
|
|
height: height as i32,
|
|
|
|
is_tower: height > 36.0,
|
|
|
|
}
|
2020-06-27 23:37:14 +00:00
|
|
|
})
|
|
|
|
.collect(),
|
|
|
|
};
|
|
|
|
|
|
|
|
this
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn get_origin(&self) -> Vec2<i32> { self.origin }
|
|
|
|
|
|
|
|
pub fn radius(&self) -> f32 { 1200.0 }
|
|
|
|
|
|
|
|
#[allow(clippy::needless_update)] // TODO: Pending review in #587
|
|
|
|
pub fn spawn_rules(&self, wpos: Vec2<i32>) -> SpawnRules {
|
|
|
|
SpawnRules {
|
2020-06-28 15:09:31 +00:00
|
|
|
trees: wpos.distance_squared(self.origin) > self.radius.pow(2),
|
2020-06-27 23:37:14 +00:00
|
|
|
..SpawnRules::default()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn apply_to<'a>(
|
|
|
|
&'a self,
|
|
|
|
wpos2d: Vec2<i32>,
|
|
|
|
mut get_column: impl FnMut(Vec2<i32>) -> Option<&'a ColumnSample<'a>>,
|
|
|
|
vol: &mut (impl BaseVol<Vox = Block> + RectSizedVol + ReadVol + WriteVol),
|
|
|
|
) {
|
|
|
|
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;
|
|
|
|
let rpos = wpos2d - self.origin;
|
|
|
|
|
|
|
|
// Apply the dungeon entrance
|
|
|
|
let col_sample = if let Some(col) = get_column(offs) {
|
|
|
|
col
|
|
|
|
} else {
|
|
|
|
continue;
|
|
|
|
};
|
|
|
|
|
2020-06-29 12:43:16 +00:00
|
|
|
let (wall_dist, wall_pos, wall_alt, wall_ori, towers) = (0..self.towers.len())
|
2020-06-27 23:37:14 +00:00
|
|
|
.map(|i| {
|
|
|
|
let tower0 = &self.towers[i];
|
|
|
|
let tower1 = &self.towers[(i + 1) % self.towers.len()];
|
|
|
|
|
|
|
|
let wall = LineSegment2 {
|
|
|
|
start: tower0.offset.map(|e| e as f32),
|
|
|
|
end: tower1.offset.map(|e| e as f32),
|
|
|
|
};
|
|
|
|
|
2020-06-28 15:09:31 +00:00
|
|
|
let projected = wall
|
|
|
|
.projected_point(rpos.map(|e| e as f32))
|
2020-06-29 12:43:16 +00:00
|
|
|
.map(|e| e.floor() as i32);
|
2020-06-27 23:37:14 +00:00
|
|
|
|
2020-06-28 15:09:31 +00:00
|
|
|
let tower0_dist = tower0
|
|
|
|
.offset
|
|
|
|
.map(|e| e as f32)
|
|
|
|
.distance(projected.map(|e| e as f32));
|
|
|
|
let tower1_dist = tower1
|
|
|
|
.offset
|
|
|
|
.map(|e| e as f32)
|
|
|
|
.distance(projected.map(|e| e as f32));
|
2020-06-27 23:37:14 +00:00
|
|
|
let tower_lerp = tower0_dist / (tower0_dist + tower1_dist);
|
2020-06-28 15:09:31 +00:00
|
|
|
let wall_ori = if (tower0.offset.x - tower1.offset.x).abs()
|
|
|
|
< (tower0.offset.y - tower1.offset.y).abs()
|
|
|
|
{
|
|
|
|
Ori::East
|
|
|
|
} else {
|
|
|
|
Ori::North
|
|
|
|
};
|
2020-06-27 23:37:14 +00:00
|
|
|
|
|
|
|
(
|
|
|
|
wall.distance_to_point(rpos.map(|e| e as f32)) as i32,
|
|
|
|
projected,
|
|
|
|
Lerp::lerp(tower0.alt as f32, tower1.alt as f32, tower_lerp) as i32,
|
2020-06-28 15:09:31 +00:00
|
|
|
wall_ori,
|
2020-06-29 12:43:16 +00:00
|
|
|
[tower0, tower1],
|
2020-06-27 23:37:14 +00:00
|
|
|
)
|
|
|
|
})
|
|
|
|
.min_by_key(|x| x.0)
|
|
|
|
.unwrap();
|
2020-06-30 11:28:25 +00:00
|
|
|
let border_pos = (wall_pos - rpos).map(|e| e.abs());
|
|
|
|
let wall_normal = (rpos - wall_pos).map(|e| e as f32);
|
|
|
|
let wall_rpos = if wall_ori == Ori::East {
|
|
|
|
rpos
|
|
|
|
} else {
|
|
|
|
rpos.yx()
|
|
|
|
};
|
|
|
|
let head_space = col_sample.path
|
|
|
|
.map(|(dist, _, path, _)| path.head_space(dist))
|
|
|
|
.unwrap_or(0);
|
2020-06-27 23:37:14 +00:00
|
|
|
|
2020-06-29 12:43:16 +00:00
|
|
|
// Apply the dungeon entrance
|
|
|
|
let wall_sample = if let Some(col) = get_column(offs + wall_pos - rpos) {
|
|
|
|
col
|
|
|
|
} else {
|
|
|
|
col_sample
|
|
|
|
};
|
|
|
|
|
2020-06-27 23:37:14 +00:00
|
|
|
for z in -10..64 {
|
2020-06-28 15:09:31 +00:00
|
|
|
let wpos = Vec3::new(wpos2d.x, wpos2d.y, col_sample.alt as i32 + z);
|
|
|
|
|
|
|
|
let keep = Keep {
|
|
|
|
flag_color: Rgb::new(200, 80, 40),
|
|
|
|
};
|
2020-06-27 23:37:14 +00:00
|
|
|
|
|
|
|
// Boundary
|
2020-06-30 11:28:25 +00:00
|
|
|
let wall_z = wpos.z - wall_alt;
|
|
|
|
let mut mask = if z < head_space {
|
|
|
|
BlockMask::nothing()
|
2020-06-28 15:09:31 +00:00
|
|
|
} else {
|
2020-06-30 11:28:25 +00:00
|
|
|
keep.draw(
|
|
|
|
Vec3::from(wall_rpos) + Vec3::unit_z() * wpos.z - wall_alt,
|
|
|
|
wall_dist,
|
|
|
|
Vec2::new(border_pos.reduce_max(), border_pos.reduce_min()),
|
|
|
|
rpos - wall_pos,
|
|
|
|
wall_z,
|
|
|
|
wall_ori,
|
|
|
|
4,
|
|
|
|
0,
|
|
|
|
&Attr {
|
|
|
|
height: 16,
|
|
|
|
is_tower: false,
|
|
|
|
rounded: true,
|
|
|
|
},
|
|
|
|
)
|
2020-06-28 15:09:31 +00:00
|
|
|
};
|
2020-06-27 23:37:14 +00:00
|
|
|
for tower in &self.towers {
|
|
|
|
let tower_wpos = Vec3::new(
|
|
|
|
self.origin.x + tower.offset.x,
|
|
|
|
self.origin.y + tower.offset.y,
|
|
|
|
tower.alt,
|
|
|
|
);
|
|
|
|
let tower_locus = 10;
|
|
|
|
|
|
|
|
let border_pos = (tower_wpos - wpos).xy().map(|e| e.abs());
|
2020-06-28 15:09:31 +00:00
|
|
|
mask = mask.resolve_with(keep.draw(
|
|
|
|
if (tower_wpos.x - wpos.x).abs() < (tower_wpos.y - wpos.y).abs() {
|
|
|
|
wpos - tower_wpos
|
|
|
|
} else {
|
|
|
|
Vec3::new(
|
|
|
|
wpos.y - tower_wpos.y,
|
|
|
|
wpos.x - tower_wpos.x,
|
|
|
|
wpos.z - tower_wpos.z,
|
|
|
|
)
|
|
|
|
},
|
2020-06-27 23:37:14 +00:00
|
|
|
border_pos.reduce_max() - tower_locus,
|
|
|
|
Vec2::new(border_pos.reduce_max(), border_pos.reduce_min()),
|
|
|
|
(wpos - tower_wpos).xy(),
|
|
|
|
wpos.z - tower.alt,
|
2020-06-28 15:09:31 +00:00
|
|
|
Ori::East,
|
|
|
|
tower_locus,
|
|
|
|
0,
|
|
|
|
&Attr {
|
|
|
|
height: 28,
|
|
|
|
is_tower: true,
|
2020-06-29 12:43:16 +00:00
|
|
|
rounded: self.rounded_towers,
|
2020-06-28 15:09:31 +00:00
|
|
|
},
|
2020-06-27 23:37:14 +00:00
|
|
|
));
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(block) = mask.finish() {
|
|
|
|
let _ = vol.set(Vec3::new(offs.x, offs.y, wpos.z), block);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[allow(clippy::or_fun_call)] // TODO: Pending review in #587
|
|
|
|
pub fn apply_supplement<'a>(
|
|
|
|
&'a self,
|
|
|
|
rng: &mut impl Rng,
|
|
|
|
wpos2d: Vec2<i32>,
|
|
|
|
_get_column: impl FnMut(Vec2<i32>) -> Option<&'a ColumnSample<'a>>,
|
|
|
|
supplement: &mut ChunkSupplement,
|
|
|
|
) {
|
|
|
|
// TODO
|
|
|
|
}
|
|
|
|
}
|