mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Added simple dungeon impl
This commit is contained in:
parent
568a8ab87c
commit
3ffc5a7d87
@ -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 {
|
||||
|
198
world/src/site/dungeon/mod.rs
Normal file
198
world/src/site/dungeon/mod.rs
Normal 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)
|
||||
}
|
||||
}
|
@ -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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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::*,
|
||||
};
|
||||
|
||||
|
@ -6,9 +6,9 @@ use common::{
|
||||
};
|
||||
use super::{
|
||||
Archetype,
|
||||
BlockMask,
|
||||
super::skeleton::*,
|
||||
};
|
||||
use crate::site::BlockMask;
|
||||
|
||||
pub struct Keep;
|
||||
|
||||
|
@ -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;
|
||||
|
@ -1,5 +1,5 @@
|
||||
use vek::*;
|
||||
use super::archetype::BlockMask;
|
||||
use crate::site::BlockMask;
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||
pub enum Ori {
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user