Began adding castles

This commit is contained in:
Joshua Barretto 2020-06-28 00:37:14 +01:00
parent 1f4cec773b
commit d6cdb0c433
10 changed files with 316 additions and 62 deletions

View File

@ -298,6 +298,11 @@ impl ChatCommand {
"View the server description",
NoAdmin,
),
ChatCommand::MakeBlock => cmd(
vec![Enum("block", BLOCK_KINDS.clone(), Required)],
"Make a block",
Admin,
),
ChatCommand::Object => cmd(
vec![Enum("object", OBJECTS.clone(), Required)],
"Spawn an object",

View File

@ -1,13 +1,8 @@
use crate::vol::Vox;
use serde::{Deserialize, Serialize};
use std::{
ops::Deref,
convert::TryFrom,
collections::HashMap,
fmt,
};
use lazy_static::lazy_static;
use enum_iterator::IntoEnumIterator;
use lazy_static::lazy_static;
use serde::{Deserialize, Serialize};
use std::{collections::HashMap, convert::TryFrom, fmt, ops::Deref};
use vek::*;
#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize, IntoEnumIterator)]
@ -96,9 +91,7 @@ pub enum BlockKind {
}
impl fmt::Display for BlockKind {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}", self)
}
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{:?}", self) }
}
lazy_static! {
@ -110,9 +103,7 @@ lazy_static! {
impl<'a> TryFrom<&'a str> for BlockKind {
type Error = ();
fn try_from(s: &'a str) -> Result<Self, Self::Error> {
BLOCK_KINDS.get(s).copied().ok_or(())
}
fn try_from(s: &'a str) -> Result<Self, Self::Error> { BLOCK_KINDS.get(s).copied().ok_or(()) }
}
impl BlockKind {

View File

@ -13,16 +13,16 @@ use common::{
npc::{self, get_npc_name},
state::TimeOfDay,
sync::{Uid, WorldSyncExt},
terrain::{TerrainChunkSize, Block, BlockKind},
terrain::{Block, BlockKind, TerrainChunkSize},
util::Dir,
vol::{RectVolSize, WriteVol},
LoadoutBuilder,
};
use rand::Rng;
use specs::{Builder, Entity as EcsEntity, Join, WorldExt};
use std::convert::TryFrom;
use vek::*;
use world::util::Sampler;
use std::convert::TryFrom;
use scan_fmt::{scan_fmt, scan_fmt_some};
use tracing::error;

View File

@ -5,7 +5,7 @@ mod econ;
use self::{Occupation::*, Stock::*};
use crate::{
sim::WorldSim,
site::{Dungeon, Settlement, Site as WorldSite},
site::{Dungeon, Settlement, Castle, Site as WorldSite},
util::{attempt, seed_expan, MapVec, CARDINALS, NEIGHBORS},
Index,
};
@ -86,9 +86,13 @@ impl Civs {
for _ in 0..INITIAL_CIV_COUNT * 3 {
attempt(5, || {
let loc = find_site_loc(&mut ctx, None)?;
let (kind, size) = match ctx.rng.gen_range(0, 2) {
0 => (SiteKind::Dungeon, 0),
_ => (SiteKind::Castle, 3),
};
let loc = find_site_loc(&mut ctx, None, size)?;
this.establish_site(&mut ctx.reseed(), loc, |place| Site {
kind: SiteKind::Dungeon,
kind,
center: loc,
place,
@ -125,10 +129,12 @@ impl Civs {
let flatten_radius = match &site.kind {
SiteKind::Settlement => 8.0,
SiteKind::Dungeon => 2.0,
SiteKind::Castle => 5.0,
};
let (raise, raise_dist): (f32, i32) = match &site.kind {
SiteKind::Settlement => (10.0, 6),
SiteKind::Castle => (0.0, 6),
_ => (0.0, 0),
};
@ -176,6 +182,9 @@ impl Civs {
SiteKind::Dungeon => {
WorldSite::dungeon(Dungeon::generate(wpos, Some(ctx.sim), &mut rng))
},
SiteKind::Castle => {
WorldSite::castle(Castle::generate(wpos, Some(ctx.sim), &mut rng))
},
});
let site_ref = &index.sites[site];
@ -272,7 +281,7 @@ 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)?;
let loc = find_site_loc(ctx, None, 1)?;
self.establish_site(ctx, loc, |place| Site {
kind: SiteKind::Settlement,
center: loc,
@ -367,7 +376,7 @@ impl Civs {
loc: Vec2<i32>,
site_fn: impl FnOnce(Id<Place>) -> Site,
) -> Option<Id<Site>> {
const SITE_AREA: Range<usize> = 64..256;
const SITE_AREA: Range<usize> = 1..4;//64..256;
let place = match ctx.sim.get(loc).and_then(|site| site.place) {
Some(place) => place,
@ -381,12 +390,13 @@ impl Civs {
let mut nearby = self
.sites
.iter()
.filter(|(_, p)| matches!(p.kind, SiteKind::Settlement | SiteKind::Castle))
.map(|(id, p)| (id, (p.center.distance_squared(loc) as f32).sqrt()))
.filter(|(_, dist)| *dist < MAX_NEIGHBOR_DISTANCE)
.collect::<Vec<_>>();
nearby.sort_by_key(|(_, dist)| *dist as i32);
if let SiteKind::Settlement = self.sites[site].kind {
if let SiteKind::Settlement | SiteKind::Castle = self.sites[site].kind {
for (nearby, _) in nearby.into_iter().take(5) {
// Find a novel path
if let Some((path, cost)) = find_path(ctx, loc, self.sites.get(nearby).center) {
@ -589,6 +599,7 @@ fn loc_suitable_for_site(sim: &WorldSim, loc: Vec2<i32>) -> bool {
if let Some(chunk) = sim.get(loc) {
!chunk.river.is_ocean()
&& !chunk.river.is_lake()
&& !chunk.river.is_river()
&& sim
.get_gradient_approx(loc)
.map(|grad| grad < 1.0)
@ -601,7 +612,7 @@ fn loc_suitable_for_site(sim: &WorldSim, loc: Vec2<i32>) -> bool {
/// Attempt to search for a location that's suitable for site construction
#[allow(clippy::useless_conversion)] // TODO: Pending review in #587
#[allow(clippy::or_fun_call)] // TODO: Pending review in #587
fn find_site_loc(ctx: &mut GenCtx<impl Rng>, near: Option<(Vec2<i32>, f32)>) -> Option<Vec2<i32>> {
fn find_site_loc(ctx: &mut GenCtx<impl Rng>, near: Option<(Vec2<i32>, f32)>, size: i32) -> Option<Vec2<i32>> {
const MAX_ATTEMPTS: usize = 100;
let mut loc = None;
for _ in 0..MAX_ATTEMPTS {
@ -621,8 +632,10 @@ fn find_site_loc(ctx: &mut GenCtx<impl Rng>, near: Option<(Vec2<i32>, f32)>) ->
),
});
if loc_suitable_for_site(&ctx.sim, test_loc) {
return Some(test_loc);
for offset in Spiral2d::new().take((size * 2 + 1).pow(2) as usize) {
if loc_suitable_for_site(&ctx.sim, test_loc + offset) {
return Some(test_loc);
}
}
loc = ctx.sim.get(test_loc).and_then(|c| {
@ -723,10 +736,7 @@ pub struct Site {
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, "{:?}", self.kind)?;
writeln!(f, "- population: {}", self.population.floor() as u32)?;
writeln!(f, "- coin: {}", self.coin.floor() as u32)?;
writeln!(f, "Stocks")?;
@ -766,6 +776,7 @@ impl fmt::Display for Site {
pub enum SiteKind {
Settlement,
Dungeon,
Castle,
}
impl Site {

View File

@ -0,0 +1,244 @@
use super::SpawnRules;
use crate::{
block::block_from_structure,
column::ColumnSample,
sim::WorldSim,
site::{
BlockMask,
settlement::building::{Archetype, Ori, Branch, archetype::keep::{Keep, Attr}},
},
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,
towers: Vec<Tower>,
segments: Vec<Segment>,
}
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);
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(),
towers: (0..boundary_towers)
.map(|i| {
let angle = (i as f32 / boundary_towers as f32) * f32::consts::PI * 2.0;
let dir = Vec2::new(
angle.cos(),
angle.sin(),
);
let dist = ctx.rng.gen_range(45.0, 190.0).clamped(75.0, 135.0);
let offset = (dir * dist).map(|e| e as i32);
Tower {
offset,
alt: ctx
.sim
.and_then(|sim| sim.get_alt_approx(wpos + offset))
.unwrap_or(0.0) as i32 + 2,
}
})
.collect(),
segments: (0..0)//rng.gen_range(18, 24))
.map(|_| {
let dir = Vec2::new(
rng.gen_range(-1.0, 1.0),
rng.gen_range(-1.0, 1.0),
).normalized();
let dist = 16.0 + rng.gen_range(0.0f32, 1.0).powf(0.5) * 64.0;
let height = 48.0 - (dist / 64.0).powf(2.0) * 32.0;
Segment {
offset: (dir * dist).map(|e| e as i32),
locus: rng.gen_range(6, 26),
height: height as i32,
is_tower: height > 36.0,
}
})
.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 {
trees: wpos.distance_squared(self.origin) > 64i32.pow(2),
..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;
};
let (wall_dist, wall_pos, wall_alt) = (0..self.towers.len())
.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),
};
let projected = wall.projected_point(rpos.map(|e| e as f32)).map(|e| e as i32);
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));
let tower_lerp = tower0_dist / (tower0_dist + tower1_dist);
(
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,
)
})
.min_by_key(|x| x.0)
.unwrap();
for z in -10..64 {
let wpos = Vec3::new(
wpos2d.x,
wpos2d.y,
col_sample.alt as i32 + z,
);
// Boundary
let border_pos = (wall_pos - rpos).map(|e| e.abs());
let mut mask = Keep.draw(
Vec3::from(rpos) + Vec3::unit_z() * wpos.z - wall_alt,
wall_dist,
Vec2::new(border_pos.reduce_max(), border_pos.reduce_min()),
rpos - wall_pos,
wpos.z - wall_alt,
Ori::North,
&Branch {
len: 0,
attr: Attr {
height: 16,
is_tower: false,
},
locus: 4,
border: 0,
children: Vec::new(),
}
);
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());
mask = mask.resolve_with(Keep.draw(
wpos - tower_wpos,
border_pos.reduce_max() - tower_locus,
Vec2::new(border_pos.reduce_max(), border_pos.reduce_min()),
(wpos - tower_wpos).xy(),
wpos.z - tower.alt,
Ori::North,
&Branch {
len: 0,
attr: Attr {
height: 28,
is_tower: true,
},
locus: tower_locus,
border: 0,
children: Vec::new(),
}
));
}
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
}
}

View File

@ -23,19 +23,6 @@ use rand::prelude::*;
use std::sync::Arc;
use vek::*;
impl WorldSim {
#[allow(dead_code)]
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,

View File

@ -1,10 +1,17 @@
mod block_mask;
mod dungeon;
mod castle;
pub mod economy;
mod settlement;
// Reexports
pub use self::{block_mask::BlockMask, dungeon::Dungeon, economy::Economy, settlement::Settlement};
pub use self::{
block_mask::BlockMask,
dungeon::Dungeon,
economy::Economy,
settlement::Settlement,
castle::Castle,
};
use crate::column::ColumnSample;
use common::{
@ -32,6 +39,7 @@ pub struct Site {
pub enum SiteKind {
Settlement(Settlement),
Dungeon(Dungeon),
Castle(Castle),
}
impl Site {
@ -49,10 +57,18 @@ impl Site {
}
}
pub fn castle(c: Castle) -> Self {
Self {
kind: SiteKind::Castle(c),
economy: Economy::default(),
}
}
pub fn radius(&self) -> f32 {
match &self.kind {
SiteKind::Settlement(settlement) => settlement.radius(),
SiteKind::Dungeon(dungeon) => dungeon.radius(),
SiteKind::Settlement(s) => s.radius(),
SiteKind::Dungeon(d) => d.radius(),
SiteKind::Castle(c) => c.radius(),
}
}
@ -60,6 +76,7 @@ impl Site {
match &self.kind {
SiteKind::Settlement(s) => s.get_origin(),
SiteKind::Dungeon(d) => d.get_origin(),
SiteKind::Castle(c) => c.get_origin(),
}
}
@ -67,6 +84,7 @@ impl Site {
match &self.kind {
SiteKind::Settlement(s) => s.spawn_rules(wpos),
SiteKind::Dungeon(d) => d.spawn_rules(wpos),
SiteKind::Castle(c) => c.spawn_rules(wpos),
}
}
@ -77,8 +95,9 @@ impl Site {
vol: &mut (impl BaseVol<Vox = Block> + RectSizedVol + ReadVol + WriteVol),
) {
match &self.kind {
SiteKind::Settlement(settlement) => settlement.apply_to(wpos2d, get_column, vol),
SiteKind::Dungeon(dungeon) => dungeon.apply_to(wpos2d, get_column, vol),
SiteKind::Settlement(s) => s.apply_to(wpos2d, get_column, vol),
SiteKind::Dungeon(d) => d.apply_to(wpos2d, get_column, vol),
SiteKind::Castle(c) => c.apply_to(wpos2d, get_column, vol),
}
}
@ -90,12 +109,9 @@ impl Site {
supplement: &mut ChunkSupplement,
) {
match &self.kind {
SiteKind::Settlement(settlement) => {
settlement.apply_supplement(rng, wpos2d, get_column, supplement)
},
SiteKind::Dungeon(dungeon) => {
dungeon.apply_supplement(rng, wpos2d, get_column, supplement)
},
SiteKind::Settlement(s) => s.apply_supplement(rng, wpos2d, get_column, supplement),
SiteKind::Dungeon(d) => d.apply_supplement(rng, wpos2d, get_column, supplement),
SiteKind::Castle(c) => c.apply_supplement(rng, wpos2d, get_column, supplement),
}
}
}

View File

@ -10,8 +10,8 @@ use vek::*;
pub struct Keep;
pub struct Attr {
height: i32,
is_tower: bool,
pub height: i32,
pub is_tower: bool,
}
impl Archetype for Keep {
@ -91,9 +91,9 @@ impl Archetype for Keep {
let ceil_height = branch.attr.height;
let door_height = 6;
let edge_pos = if (bound_offset.x == rampart_width) ^ (ori == Ori::East) {
pos.y
pos.y + pos.x
} else {
pos.x
pos.x + pos.y
};
let rampart_height = ceil_height + if edge_pos % 2 == 0 { 3 } else { 4 };
let inner = Clamp::clamp(
@ -143,7 +143,7 @@ impl Archetype for Keep {
if profile.y < rampart_height {
wall
} else {
internal
empty
}
} else {
empty

View File

@ -1,10 +1,10 @@
mod archetype;
mod skeleton;
pub mod archetype;
pub mod skeleton;
// Reexports
pub use self::archetype::Archetype;
pub use self::skeleton::*;
use self::skeleton::*;
use common::terrain::Block;
use rand::prelude::*;
use vek::*;

View File

@ -1,4 +1,4 @@
mod building;
pub mod building;
mod town;
use self::{