Added SpotConfig for easier spot creation

This commit is contained in:
Joshua Barretto 2021-07-29 17:47:45 +01:00
parent 76ba3496a1
commit 00c44be2da
2 changed files with 116 additions and 68 deletions

View File

@ -137,7 +137,7 @@ impl<'a> Canvas<'a> {
/// inner `CanvasInfo` such that it may be used independently.
pub fn info(&mut self) -> CanvasInfo<'a> { self.info }
pub fn get(&mut self, pos: Vec3<i32>) -> Block {
pub fn get(&self, pos: Vec3<i32>) -> Block {
self.chunk
.get(pos - self.wpos())
.ok()
@ -222,6 +222,20 @@ impl<'a> Canvas<'a> {
});
}
pub fn find_spawn_pos(&self, wpos: Vec3<i32>) -> Option<Vec3<i32>> {
let height = 2;
let search_dist: i32 = 8;
(1..search_dist * 2 + 1)
.rev()
.map(|z| wpos.z + if z % 2 != 0 { z / 2 } else { -(z / 2) })
.find(|&z| {
self.get(wpos.xy().with_z(z - 1)).is_solid()
&& (0..height).all(|z_offs| self.get(wpos.xy().with_z(z + z_offs)).is_fluid())
})
.map(|z| wpos.xy().with_z(z))
}
pub fn spawn(&mut self, entity: EntityInfo) { self.entities.push(entity); }
}

View File

@ -3,32 +3,45 @@ use crate::{
util::seed_expan,
Canvas,
};
use common::{
comp,
generation::EntityInfo,
terrain::{Block, BlockKind, Structure},
};
use common::{generation::EntityInfo, terrain::Structure};
use rand::prelude::*;
use rand_chacha::ChaChaRng;
use std::ops::Range;
use vek::*;
/// Spots are localised structures that spawn in the world. Conceptually, they
/// fit somewhere between the tree generator and the site generator: an attempt
/// to marry the simplicity of the former with the capability of the latter.
/// They are not globally visible to the game: this means that they do not
/// appear on the map, and cannot interact with rtsim (much).
///
/// To add a new spot, one must:
///
/// 1. Add a new variant to the [`Spot`] enum.
///
/// 2. Add a new entry to [`Spot::generate`] that tells the system where to
/// generate your new spot.
///
/// 3. Add a new arm to the `match` expression in [`Spot::apply_spots_to`] that
/// tells the generator how to generate a spot, including the base structure
/// that composes the spot and the entities that should be spawned there.
#[derive(Copy, Clone, Debug)]
pub enum Spot {
Camp,
BanditCamp,
MerchantCamp,
SaurokCamp,
}
impl Spot {
pub fn generate(world: &mut WorldSim) {
Self::generate_spots(
Spot::Camp,
Spot::MerchantCamp,
world,
10.0,
|g, c| g < 0.25 && !c.near_cliffs() && !c.river.near_water() && !c.path.0.is_way(),
false,
);
Self::generate_spots(
Spot::BanditCamp,
Spot::SaurokCamp,
world,
10.0,
|g, c| g < 0.25 && !c.near_cliffs() && !c.river.near_water() && !c.path.0.is_way(),
@ -37,10 +50,16 @@ impl Spot {
}
fn generate_spots(
// What kind of spot are we generating?
spot: Spot,
world: &mut WorldSim,
freq: f32, // Per sq km
// How often should this spot appear (per square km, on average)?
freq: f32,
// What tests should we perform to see whether we can spawn the spot here? The two
// parameters are the gradient of the terrain and the [`SimChunk`] of the candidate
// location.
mut valid: impl FnMut(f32, &SimChunk) -> bool,
// Should we allow trees to spawn close to the spot?
trees: bool,
) {
let world_size = world.get_size();
@ -60,69 +79,84 @@ impl Spot {
}
}
pub fn apply_spots_to(canvas: &mut Canvas, dynamic_rng: &mut impl Rng) {
pub fn apply_spots_to(canvas: &mut Canvas, _dynamic_rng: &mut impl Rng) {
let nearby_spots = canvas.nearby_spots().collect::<Vec<_>>();
for (spot_wpos, spot, seed) in nearby_spots.iter().copied() {
for (spot_wpos2d, spot, seed) in nearby_spots.iter().copied() {
let mut rng = ChaChaRng::from_seed(seed_expan::rng_state(seed));
match spot {
Spot::Camp => {
canvas.foreach_col_area(
Aabr {
min: spot_wpos - 8,
max: spot_wpos + 8,
},
|canvas, wpos2d, col| {
if nearby_spots
.iter()
.any(|(wpos, _, _)| wpos.distance_squared(wpos2d) < 64)
{
for z in -8..32 {
canvas.set(
wpos2d.with_z(col.alt as i32 + z),
Block::new(BlockKind::Misc, Rgb::broadcast(255)),
);
}
}
},
);
},
Spot::BanditCamp => {
let structures = Structure::load_group("dungeon_entrances.grassland").read();
let structure = structures.choose(&mut rng).unwrap();
let origin = spot_wpos.with_z(
canvas
.col_or_gen(spot_wpos)
.map(|c| c.alt as i32)
.unwrap_or(0),
);
canvas.blit_structure(origin, &structure, seed);
let spawn_radius = 12;
let avg_num = 5.0;
#[derive(Default)]
struct SpotConfig<'a> {
// The manifest containing a list of possible base structures for the spot (one will be
// chosen)
base_structures: Option<&'a str>,
// The maximum distance from the centre of the spot that entities will spawn
entity_radius: f32,
// The entities that should be spawned in the spot, from closest to furthest
// (count_range, spec)
entities: &'a [(Range<u32>, &'a str)],
}
canvas.foreach_col_area(
Aabr {
min: spot_wpos - spawn_radius,
max: spot_wpos + spawn_radius,
},
|canvas, wpos2d, col| {
if dynamic_rng.gen_bool(avg_num / (spawn_radius * 2).pow(2) as f64) {
if let Some(z) = (-8..8).rev().map(|z| col.alt as i32 + z).find(|z| {
canvas.get(wpos2d.with_z(z + 2)).is_fluid()
&& canvas.get(wpos2d.with_z(z + 1)).is_fluid()
&& canvas.get(wpos2d.with_z(z + 0)).is_solid()
}) {
canvas.spawn(
EntityInfo::at(wpos2d.map(|e| e as f32 + 0.5).with_z(z as f32))
.with_asset_expect("common.entity.spot.bandit_camp.saurok")
.with_alignment(comp::Alignment::Enemy),
);
}
}
},
);
let spot_config = match spot {
Spot::MerchantCamp => SpotConfig {
base_structures: Some("trees.quirky"),
entity_radius: 6.0,
entities: &[
(1..3, "common.entity.village.merchant"),
(2..5, "common.entity.village.villager"),
],
},
Spot::SaurokCamp => SpotConfig {
base_structures: Some("dungeon_entrances.grassland"),
entity_radius: 12.0,
entities: &[(4..6, "common.entity.spot.bandit_camp.saurok")],
},
};
// Blit base structure
if let Some(base_structures) = spot_config.base_structures {
let structures = Structure::load_group(base_structures).read();
let structure = structures.choose(&mut rng).unwrap();
let origin = spot_wpos2d.with_z(
canvas
.col_or_gen(spot_wpos2d)
.map(|c| c.alt as i32)
.unwrap_or(0),
);
canvas.blit_structure(origin, &structure, seed);
}
// Spawn entities
const PHI: f32 = 1.618;
let dir_offset = rng.gen::<f32>();
let mut i = 0;
for (spawn_count, spec) in spot_config.entities {
let spawn_count = rng.gen_range(spawn_count.clone());
for _ in 0..spawn_count {
let dir = Vec2::new(
((dir_offset + i as f32 * PHI) * std::f32::consts::TAU).sin(),
((dir_offset + i as f32 * PHI) * std::f32::consts::TAU).cos(),
);
let dist = i as f32 / spawn_count as f32 * spot_config.entity_radius;
let wpos2d = spot_wpos2d + (dir * dist).map(|e| e.round() as i32);
let alt = canvas.col_or_gen(wpos2d).map(|c| c.alt as i32).unwrap_or(0);
if let Some(wpos) = canvas
.area()
.contains_point(wpos2d)
.then(|| canvas.find_spawn_pos(wpos2d.with_z(alt)))
.flatten()
{
canvas.spawn(
EntityInfo::at(wpos.map(|e| e as f32) + Vec3::new(0.5, 0.5, 0.0))
.with_asset_expect(spec),
);
}
i += 1;
}
}
}
}