Added simple dungeon impl

This commit is contained in:
Joshua Barretto 2020-04-15 20:41:40 +01:00
parent 568a8ab87c
commit 3ffc5a7d87
8 changed files with 331 additions and 76 deletions

View File

@ -19,7 +19,7 @@ use common::{
};
use crate::{
sim::{WorldSim, SimChunk},
site::{Site as WorldSite, Settlement},
site::{Site as WorldSite, Settlement, Dungeon},
util::seed_expan,
};
@ -76,6 +76,32 @@ impl Civs {
}
}
for _ in 0..256 {
attempt(5, || {
let loc = find_site_loc(&mut ctx, None)?;
this.establish_site(&mut ctx, loc, |place| Site {
kind: SiteKind::Dungeon,
center: loc,
place,
population: 0.0,
stocks: Stocks::from_default(100.0),
surplus: Stocks::from_default(0.0),
values: Stocks::from_default(None),
labors: MapVec::from_default(0.01),
yields: MapVec::from_default(1.0),
productivity: MapVec::from_default(1.0),
last_exports: Stocks::from_default(0.0),
export_targets: Stocks::from_default(0.0),
trade_states: Stocks::default(),
coin: 1000.0,
})
});
}
// Tick
const SIM_YEARS: usize = 1000;
for _ in 0..SIM_YEARS {
@ -91,6 +117,10 @@ impl Civs {
// Flatten ground around sites
for site in this.sites.iter() {
if let SiteKind::Settlement = &site.kind {} else {
continue;
}
let radius = 48i32;
let wpos = site.center * Vec2::from(TerrainChunkSize::RECT_SIZE).map(|e: u32| e as i32);
@ -117,15 +147,18 @@ impl Civs {
// Place sites in world
for site in this.sites.iter() {
let radius = 48i32;
let wpos = site.center * Vec2::from(TerrainChunkSize::RECT_SIZE).map(|e: u32| e as i32);
let settlement = WorldSite::from(Settlement::generate(wpos, Some(ctx.sim), ctx.rng));
let world_site = match &site.kind {
SiteKind::Settlement => WorldSite::from(Settlement::generate(wpos, Some(ctx.sim), ctx.rng)),
SiteKind::Dungeon => WorldSite::from(Dungeon::generate(wpos, Some(ctx.sim), ctx.rng)),
};
for pos in Spiral2d::new().map(|offs| site.center + offs).take(radius.pow(2) as usize) {
let radius_chunks = (world_site.radius() / TerrainChunkSize::RECT_SIZE.x as f32).ceil() as usize;
for pos in Spiral2d::new().map(|offs| site.center + offs).take((radius_chunks * 2).pow(2)) {
ctx.sim
.get_mut(pos)
.map(|chunk| chunk.sites.push(settlement.clone()));
.map(|chunk| chunk.sites.push(world_site.clone()));
}
println!("Placed site at {:?}", site.center);
}
@ -188,7 +221,26 @@ impl Civs {
fn birth_civ(&mut self, ctx: &mut GenCtx<impl Rng>) -> Option<Id<Civ>> {
let site = attempt(5, || {
let loc = find_site_loc(ctx, None)?;
self.establish_site(ctx, loc)
self.establish_site(ctx, loc, |place| Site {
kind: SiteKind::Settlement,
center: loc,
place,
population: 24.0,
stocks: Stocks::from_default(100.0),
surplus: Stocks::from_default(0.0),
values: Stocks::from_default(None),
labors: MapVec::from_default(0.01),
yields: MapVec::from_default(1.0),
productivity: MapVec::from_default(1.0),
last_exports: Stocks::from_default(0.0),
export_targets: Stocks::from_default(0.0),
trade_states: Stocks::default(),
coin: 1000.0,
})
})?;
let civ = self.civs.insert(Civ {
@ -242,7 +294,7 @@ impl Civs {
Some(place)
}
fn establish_site(&mut self, ctx: &mut GenCtx<impl Rng>, loc: Vec2<i32>) -> Option<Id<Site>> {
fn establish_site(&mut self, ctx: &mut GenCtx<impl Rng>, loc: Vec2<i32>, site_fn: impl FnOnce(Id<Place>) -> Site) -> Option<Id<Site>> {
const SITE_AREA: Range<usize> = 64..256;
let place = match ctx.sim.get(loc).and_then(|site| site.place) {
@ -250,26 +302,7 @@ impl Civs {
None => self.establish_place(ctx, loc, SITE_AREA)?,
};
let site = self.sites.insert(Site {
kind: SiteKind::Settlement,
center: loc,
place: place,
population: 24.0,
stocks: Stocks::from_default(100.0),
surplus: Stocks::from_default(0.0),
values: Stocks::from_default(None),
labors: MapVec::from_default(0.01),
yields: MapVec::from_default(1.0),
productivity: MapVec::from_default(1.0),
last_exports: Stocks::from_default(0.0),
export_targets: Stocks::from_default(0.0),
trade_states: Stocks::default(),
coin: 1000.0,
});
let site = self.sites.insert(site_fn(place));
// Find neighbors
const MAX_NEIGHBOR_DISTANCE: f32 = 250.0;
@ -531,6 +564,7 @@ impl fmt::Display for Site {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.kind {
SiteKind::Settlement => writeln!(f, "Settlement")?,
SiteKind::Dungeon => writeln!(f, "Dungeon")?,
}
writeln!(f, "- population: {}", self.population.floor() as u32)?;
writeln!(f, "- coin: {}", self.coin.floor() as u32)?;
@ -558,6 +592,7 @@ impl fmt::Display for Site {
#[derive(Debug)]
pub enum SiteKind {
Settlement,
Dungeon,
}
impl Site {

View File

@ -0,0 +1,198 @@
use crate::{
column::ColumnSample,
sim::{SimChunk, WorldSim},
util::{Grid, RandomField, Sampler, StructureGen2d},
site::BlockMask,
};
use super::SpawnRules;
use common::{
astar::Astar,
path::Path,
spiral::Spiral2d,
terrain::{Block, BlockKind, TerrainChunkSize},
vol::{BaseVol, RectSizedVol, RectVolSize, ReadVol, WriteVol, Vox},
store::{Id, Store},
};
use hashbrown::{HashMap, HashSet};
use rand::prelude::*;
use std::{collections::VecDeque, f32};
use vek::*;
impl WorldSim {
fn can_host_dungeon(&self, pos: Vec2<i32>) -> bool {
self
.get(pos)
.map(|chunk| {
!chunk.near_cliffs && !chunk.river.is_river() && !chunk.river.is_lake()
})
.unwrap_or(false)
&& self
.get_gradient_approx(pos)
.map(|grad| grad > 0.25 && grad < 1.5)
.unwrap_or(false)
}
}
pub struct Dungeon {
origin: Vec2<i32>,
alt: i32,
noise: RandomField,
floors: Vec<Floor>,
}
pub struct GenCtx<'a, R: Rng> {
sim: Option<&'a WorldSim>,
rng: &'a mut R,
}
impl Dungeon {
pub fn generate(wpos: Vec2<i32>, sim: Option<&WorldSim>, rng: &mut impl Rng) -> Self {
let mut ctx = GenCtx { sim, rng };
let mut this = Self {
origin: wpos,
alt: ctx.sim.and_then(|sim| sim.get_alt_approx(wpos)).unwrap_or(0.0) as i32,
noise: RandomField::new(ctx.rng.gen()),
floors: (0..6)
.scan(Vec2::zero(), |stair_tile, _| {
let (floor, st) = Floor::generate(&mut ctx, *stair_tile);
*stair_tile = st;
Some(floor)
})
.collect(),
};
this
}
pub fn radius(&self) -> f32 { 1200.0 }
pub fn spawn_rules(&self, wpos: Vec2<i32>) -> SpawnRules {
SpawnRules {
..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),
) {
let rand_field = RandomField::new(0);
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;
// 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;
let make_staircase = |pos: Vec3<i32>, radius: f32, inner_radius: f32, stretch| {
if (pos.xy().magnitude_squared() as f32) < radius.powf(2.0) {
if ((pos.x as f32).atan2(pos.y as f32) / (f32::consts::PI * 2.0) * stretch + pos.z as f32) % stretch < 3.0
|| (pos.xy().magnitude_squared() as f32) < inner_radius.powf(2.0)
{
BlockMask::new(Block::new(BlockKind::Normal, Rgb::new(150, 150, 175)), 5)
} else {
BlockMask::new(Block::empty(), 1)
}
} else {
BlockMask::nothing()
}
};
let tile_pos = rpos.map(|e| e.div_euclid(TILE_SIZE));
let tile_center = tile_pos * TILE_SIZE + TILE_SIZE / 2;
let mut z = self.alt + 20;
for floor in &self.floors {
match floor.sample(tile_pos) {
Some(Tile::DownStair) | Some(Tile::Empty) => {
z -= floor.solid_depth;
for _ in 0..floor.hollow_depth {
vol.set(Vec3::new(offs.x, offs.y, z), Block::empty());
z -= 1;
}
},
Some(Tile::UpStair) => {
for i in 0..floor.solid_depth + floor.hollow_depth {
let rtile_pos = rpos - tile_center;
let mut block = make_staircase(Vec3::new(rtile_pos.x, rtile_pos.y, z), TILE_SIZE as f32 / 2.0, 1.5, 13.0);
if i >= floor.solid_depth {
block = block.resolve_with(BlockMask::new(Block::empty(), 1));
}
if let Some(block) = block.finish() {
vol.set(Vec3::new(offs.x, offs.y, z), block);
}
z -= 1;
}
},
None => z -= floor.solid_depth + floor.hollow_depth,
}
}
}
}
}
}
const CARDINALS: [Vec2<i32>; 4] = [
Vec2::new(0, 1),
Vec2::new(1, 0),
Vec2::new(0, -1),
Vec2::new(-1, 0),
];
const TILE_SIZE: i32 = 17;
pub enum Tile {
UpStair,
DownStair,
Empty,
}
pub struct Floor {
tile_offset: Vec2<i32>,
tiles: Grid<Tile>,
solid_depth: i32,
hollow_depth: i32,
}
impl Floor {
pub fn generate(ctx: &mut GenCtx<impl Rng>, stair_tile: Vec2<i32>) -> (Self, Vec2<i32>) {
let new_stair_tile = std::iter::from_fn(|| Some(FLOOR_SIZE.map(|sz| ctx.rng.gen_range(-sz / 2 + 1, sz / 2))))
.find(|pos| *pos != stair_tile)
.unwrap();
const FLOOR_SIZE: Vec2<i32> = Vec2::new(12, 12);
let tile_offset = -FLOOR_SIZE / 2;
let this = Floor {
tile_offset,
tiles: Grid::populate_from(FLOOR_SIZE, |pos| {
let tile_pos = tile_offset + pos;
if tile_pos == stair_tile {
Tile::UpStair
} else if tile_pos == new_stair_tile {
Tile::DownStair
} else {
Tile::Empty
}
}),
solid_depth: 13 * 3,
hollow_depth: 13,
};
(this, new_stair_tile)
}
pub fn sample(&self, tile_pos: Vec2<i32>) -> Option<&Tile> {
self.tiles.get(tile_pos - self.tile_offset)
}
}

View File

@ -1,7 +1,9 @@
mod settlement;
mod dungeon;
// Reexports
pub use self::settlement::Settlement;
pub use self::dungeon::Dungeon;
use crate::{
column::ColumnSample,
@ -9,30 +11,81 @@ use crate::{
};
use common::{
terrain::Block,
vol::{BaseVol, RectSizedVol, ReadVol, WriteVol},
vol::{Vox, BaseVol, RectSizedVol, ReadVol, WriteVol},
};
use std::{fmt, sync::Arc};
use vek::*;
#[derive(Copy, Clone)]
pub struct BlockMask {
block: Block,
priority: i32,
}
impl BlockMask {
pub fn new(block: Block, priority: i32) -> Self {
Self { block, priority }
}
pub fn nothing() -> Self {
Self {
block: Block::empty(),
priority: 0,
}
}
pub fn with_priority(mut self, priority: i32) -> Self {
self.priority = priority;
self
}
pub fn resolve_with(self, other: Self) -> Self {
if self.priority >= other.priority {
self
} else {
other
}
}
pub fn finish(self) -> Option<Block> {
if self.priority > 0 {
Some(self.block)
} else {
None
}
}
}
pub struct SpawnRules {
pub trees: bool,
}
impl Default for SpawnRules {
fn default() -> Self {
Self {
trees: true,
}
}
}
#[derive(Clone)]
pub enum Site {
Settlement(Arc<Settlement>),
Dungeon(Arc<Dungeon>),
}
impl Site {
pub fn radius(&self) -> f32 {
match self {
Site::Settlement(settlement) => settlement.radius(),
Site::Dungeon(dungeon) => dungeon.radius(),
}
}
pub fn spawn_rules(&self, wpos: Vec2<i32>) -> SpawnRules {
match self {
Site::Settlement(s) => s.spawn_rules(wpos)
Site::Settlement(s) => s.spawn_rules(wpos),
Site::Dungeon(d) => d.spawn_rules(wpos),
}
}
@ -44,6 +97,7 @@ impl Site {
) {
match self {
Site::Settlement(settlement) => settlement.apply_to(wpos2d, get_column, vol),
Site::Dungeon(dungeon) => dungeon.apply_to(wpos2d, get_column, vol),
}
}
}
@ -52,10 +106,15 @@ impl From<Settlement> for Site {
fn from(settlement: Settlement) -> Self { Site::Settlement(Arc::new(settlement)) }
}
impl From<Dungeon> for Site {
fn from(dungeon: Dungeon) -> Self { Site::Dungeon(Arc::new(dungeon)) }
}
impl fmt::Debug for Site {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Site::Settlement(_) => write!(f, "Settlement"),
Site::Dungeon(_) => write!(f, "Dungeon"),
}
}
}

View File

@ -4,10 +4,12 @@ use common::{
terrain::{Block, BlockKind},
vol::Vox,
};
use crate::util::{RandomField, Sampler};
use crate::{
util::{RandomField, Sampler},
site::BlockMask,
};
use super::{
Archetype,
BlockMask,
super::skeleton::*,
};

View File

@ -6,9 +6,9 @@ use common::{
};
use super::{
Archetype,
BlockMask,
super::skeleton::*,
};
use crate::site::BlockMask;
pub struct Keep;

View File

@ -3,48 +3,8 @@ pub mod keep;
use vek::*;
use rand::prelude::*;
use common::{terrain::Block, vol::Vox};
use super::skeleton::*;
#[derive(Copy, Clone)]
pub struct BlockMask {
block: Block,
priority: i32,
}
impl BlockMask {
pub fn new(block: Block, priority: i32) -> Self {
Self { block, priority }
}
pub fn nothing() -> Self {
Self {
block: Block::empty(),
priority: 0,
}
}
pub fn with_priority(mut self, priority: i32) -> Self {
self.priority = priority;
self
}
pub fn resolve_with(self, other: Self) -> Self {
if self.priority >= other.priority {
self
} else {
other
}
}
pub fn finish(self) -> Option<Block> {
if self.priority > 0 {
Some(self.block)
} else {
None
}
}
}
use crate::site::BlockMask;
pub trait Archetype {
type Attr;

View File

@ -1,5 +1,5 @@
use vek::*;
use super::archetype::BlockMask;
use crate::site::BlockMask;
#[derive(Copy, Clone, PartialEq, Eq)]
pub enum Ori {

View File

@ -451,6 +451,7 @@ impl Settlement {
.plot
.map(|p| if let Plot::Hazard = p { true } else { false })
.unwrap_or(true),
..SpawnRules::default()
}
}
@ -641,7 +642,7 @@ impl Settlement {
// Skip this structure if it's not near this chunk
if !bounds.collides_with_aabr(Aabr {
min: wpos2d - self.origin,
max: wpos2d - self.origin + vol.size_xy().map(|e| e as i32),
max: wpos2d - self.origin + vol.size_xy().map(|e| e as i32 + 1),
}) {
continue;
}
@ -659,7 +660,7 @@ impl Settlement {
continue;
};
for z in bounds.min.z.min(col.alt as i32 - 1)..bounds.max.z + 1 {
for z in bounds.min.z.min(col.alt.floor() as i32 - 1)..bounds.max.z + 1 {
let rpos = Vec3::new(x, y, z);
let wpos = Vec3::from(self.origin) + rpos;
let coffs = wpos - Vec3::from(wpos2d);