use super::SpawnRules; use crate::{ block::block_from_structure, column::ColumnSample, sim::WorldSim, site::{ settlement::building::{ archetype::keep::{Attr, Keep as KeepArchetype}, Archetype, Branch, Ori, }, BlockMask, }, util::{attempt, Grid, RandomField, Sampler, CARDINALS, DIRS}, }; use common::{ assets, astar::Astar, comp, generation::{ChunkSupplement, EntityInfo}, npc, spiral::Spiral2d, 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 Keep { offset: Vec2, locus: i32, storeys: i32, is_tower: bool, alt: i32, } struct Tower { offset: Vec2, alt: i32, } pub struct Castle { origin: Vec2, alt: i32, seed: u32, radius: i32, towers: Vec, keeps: Vec, rounded_towers: bool, ridged: bool, } pub struct GenCtx<'a, R: Rng> { sim: Option<&'a mut WorldSim>, rng: &'a mut R, } impl Castle { #[allow(clippy::let_and_return)] // TODO: Pending review in #587 pub fn generate(wpos: Vec2, sim: Option<&mut WorldSim>, rng: &mut impl Rng) -> Self { let mut ctx = GenCtx { sim, rng }; let boundary_towers = ctx.rng.gen_range(5, 10); let keep_count = ctx.rng.gen_range(1, 4); let boundary_noise = ctx.rng.gen_range(-2i32, 8).max(1) as f32; let radius = 150; let this = Self { origin: wpos, alt: ctx .sim .as_ref() .and_then(|sim| sim.get_alt_approx(wpos)) .unwrap_or(0.0) as i32 + 6, seed: ctx.rng.gen(), radius, 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 = radius as f32 + ((angle * boundary_noise).sin() - 1.0) * 40.0; 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 .as_ref() .and_then(|sim| sim.get_nearest_path(wpos + offset)) .map(|(dist, _, _, _)| dist > 24.0) .unwrap_or(true) { break; } offset = (dir * dist) .map(|e| (e + ctx.rng.gen_range(-1.0, 1.0) * i as f32) as i32); } Tower { offset, alt: ctx .sim .as_ref() .and_then(|sim| sim.get_alt_approx(wpos + offset)) .unwrap_or(0.0) as i32 + 2, } }) .collect(), rounded_towers: ctx.rng.gen(), ridged: ctx.rng.gen(), keeps: (0..keep_count) .map(|i| { let angle = (i as f32 / keep_count as f32) * f32::consts::PI * 2.0; let dir = Vec2::new(angle.cos(), angle.sin()); let dist = (radius as f32 + ((angle * boundary_noise).sin() - 1.0) * 40.0) * 0.3; let locus = ctx.rng.gen_range(20, 26); let offset = (dir * dist).map(|e| e as i32); let storeys = ctx.rng.gen_range(1, 8).clamped(3, 5); Keep { offset, locus, storeys, is_tower: true, alt: ctx .sim .as_ref() .and_then(|sim| sim.get_alt_approx(wpos + offset)) .unwrap_or(0.0) as i32 + 2, } }) .collect(), }; this } pub fn contains_point(&self, wpos: Vec2) -> bool { let lpos = wpos - self.origin; for i in 0..self.towers.len() { let tower0 = &self.towers[i]; let tower1 = &self.towers[(i + 1) % self.towers.len()]; if lpos.determine_side(Vec2::zero(), tower0.offset) > 0 && lpos.determine_side(Vec2::zero(), tower1.offset) <= 0 && lpos.determine_side(tower0.offset, tower1.offset) > 0 { return true; } } false } pub fn get_origin(&self) -> Vec2 { 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) -> SpawnRules { SpawnRules { trees: wpos.distance_squared(self.origin) > self.radius.pow(2), ..SpawnRules::default() } } pub fn apply_to<'a>( &'a self, wpos2d: Vec2, mut get_column: impl FnMut(Vec2) -> Option<&'a ColumnSample<'a>>, vol: &mut (impl BaseVol + 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; if rpos.magnitude_squared() > (self.radius + 64).pow(2) { continue; } let col_sample = if let Some(col) = get_column(offs) { col } else { continue; }; // Inner ground if self.contains_point(wpos2d) { let surface_z = col_sample.alt as i32; for z in -5..3 { let pos = Vec3::new(offs.x, offs.y, surface_z + z); if z > 0 { if vol.get(pos).unwrap().kind() != BlockKind::Water { let _ = vol.set(pos, Block::empty()); } } else { let _ = vol.set( pos, Block::new( BlockKind::Normal, col_sample.sub_surface_color.map(|e| (e * 255.0) as u8), ), ); } } } let (wall_dist, wall_pos, wall_alt, wall_ori, towers) = (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.floor() 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); let wall_ori = if (tower0.offset.x - tower1.offset.x).abs() < (tower0.offset.y - tower1.offset.y).abs() { Ori::North } else { Ori::East }; ( 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, wall_ori, [tower0, tower1], ) }) .min_by_key(|x| x.0) .unwrap(); 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); let wall_sample = if let Some(col) = get_column(offs + wall_pos - rpos) { col } else { col_sample }; // Make sure particularly weird terrain doesn't give us underground walls let wall_alt = wall_alt + (wall_sample.alt as i32 - wall_alt - 10).max(0); let keep_archetype = KeepArchetype { flag_color: Rgb::new(200, 80, 40), }; for z in -10..64 { let wpos = Vec3::new(wpos2d.x, wpos2d.y, col_sample.alt as i32 + z); // Boundary wall let wall_z = wpos.z - wall_alt; if z < head_space { continue; } let mut mask = keep_archetype.draw( Vec3::from(wall_rpos) + Vec3::unit_z() * wpos.z - wall_alt, wall_dist, border_pos, rpos - wall_pos, wall_z, wall_ori, 4, 0, &Attr { storeys: 2, is_tower: false, ridged: false, rounded: true, has_doors: false, }, ); // Boundary towers 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_archetype.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, ) }, border_pos.reduce_max() - tower_locus, Vec2::new(border_pos.reduce_min(), border_pos.reduce_max()), (wpos - tower_wpos).xy(), wpos.z - tower.alt, if border_pos.x > border_pos.y { Ori::East } else { Ori::North }, tower_locus, 0, &Attr { storeys: 3, is_tower: true, ridged: self.ridged, rounded: self.rounded_towers, has_doors: false, }, )); } // Keeps for keep in &self.keeps { let keep_wpos = Vec3::new( self.origin.x + keep.offset.x, self.origin.y + keep.offset.y, keep.alt, ); let border_pos = (keep_wpos - wpos).xy().map(|e| e.abs()); mask = mask.resolve_with(keep_archetype.draw( if (keep_wpos.x - wpos.x).abs() < (keep_wpos.y - wpos.y).abs() { wpos - keep_wpos } else { Vec3::new( wpos.y - keep_wpos.y, wpos.x - keep_wpos.x, wpos.z - keep_wpos.z, ) }, border_pos.reduce_max() - keep.locus, Vec2::new(border_pos.reduce_min(), border_pos.reduce_max()), (wpos - keep_wpos).xy(), wpos.z - keep.alt, if border_pos.x > border_pos.y { Ori::East } else { Ori::North }, keep.locus, 0, &Attr { storeys: keep.storeys, is_tower: keep.is_tower, ridged: self.ridged, rounded: self.rounded_towers, has_doors: true, }, )); } 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, _get_column: impl FnMut(Vec2) -> Option<&'a ColumnSample<'a>>, supplement: &mut ChunkSupplement, ) { // TODO } }