2020-10-24 17:57:46 +00:00
|
|
|
use crate::{column::ColumnSample, sim::SimChunk, IndexRef, CONFIG};
|
2020-10-01 21:00:46 +00:00
|
|
|
use common::{
|
2021-07-05 22:46:57 +00:00
|
|
|
assets::{self, AssetExt},
|
2020-10-06 08:18:35 +00:00
|
|
|
generation::{ChunkSupplement, EntityInfo},
|
2021-04-16 11:17:15 +00:00
|
|
|
resources::TimeOfDay,
|
2020-10-24 17:57:46 +00:00
|
|
|
terrain::Block,
|
2021-07-10 18:44:53 +00:00
|
|
|
time::DayPeriod,
|
2020-10-01 21:00:46 +00:00
|
|
|
vol::{BaseVol, ReadVol, RectSizedVol, WriteVol},
|
|
|
|
};
|
|
|
|
use rand::prelude::*;
|
2021-07-05 22:46:57 +00:00
|
|
|
use serde::Deserialize;
|
2021-07-10 18:44:53 +00:00
|
|
|
use std::f32;
|
2020-10-01 21:00:46 +00:00
|
|
|
use vek::*;
|
|
|
|
|
2021-07-16 14:30:10 +00:00
|
|
|
type Weight = u32;
|
|
|
|
type Min = u8;
|
|
|
|
type Max = u8;
|
|
|
|
|
2020-10-01 21:00:46 +00:00
|
|
|
fn close(x: f32, tgt: f32, falloff: f32) -> f32 {
|
|
|
|
(1.0 - (x - tgt).abs() / falloff).max(0.0).powf(0.125)
|
|
|
|
}
|
|
|
|
|
|
|
|
const BASE_DENSITY: f32 = 1.0e-5; // Base wildlife density
|
|
|
|
|
2021-07-05 22:46:57 +00:00
|
|
|
#[derive(Clone, Debug, Deserialize)]
|
|
|
|
pub struct SpawnEntry {
|
2021-07-11 18:24:44 +00:00
|
|
|
/// User-facing info for wiki, statistical tools, etc.
|
2021-07-05 22:46:57 +00:00
|
|
|
pub name: String,
|
|
|
|
pub note: String,
|
|
|
|
/// Rules describing what and when to spawn
|
|
|
|
pub rules: Vec<Pack>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl assets::Asset for SpawnEntry {
|
|
|
|
type Loader = assets::RonLoader;
|
|
|
|
|
|
|
|
const EXTENSION: &'static str = "ron";
|
|
|
|
}
|
|
|
|
|
|
|
|
impl SpawnEntry {
|
|
|
|
pub fn from(asset_specifier: &str) -> Self { Self::load_expect(asset_specifier).read().clone() }
|
|
|
|
|
2021-07-12 10:46:53 +00:00
|
|
|
pub fn request(&self, requested_period: DayPeriod, underwater: bool) -> Option<Pack> {
|
|
|
|
self.rules
|
|
|
|
.iter()
|
|
|
|
.find(|pack| {
|
|
|
|
let time_match = pack
|
|
|
|
.day_period
|
|
|
|
.iter()
|
|
|
|
.any(|period| *period == requested_period);
|
|
|
|
let water_match = pack.is_underwater == underwater;
|
|
|
|
time_match && water_match
|
|
|
|
})
|
|
|
|
.cloned()
|
2021-07-05 22:46:57 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Dataset of animals to spawn
|
2021-07-16 14:30:10 +00:00
|
|
|
///
|
|
|
|
/// Example:
|
|
|
|
/// ```text
|
|
|
|
/// Pack(
|
|
|
|
/// groups: [
|
|
|
|
/// (3, (1, 2, "common.entity.wild.aggressive.frostfang")),
|
|
|
|
/// (1, (1, 1, "common.entity.wild.aggressive.snow_leopard")),
|
|
|
|
/// (1, (1, 1, "common.entity.wild.aggressive.yale")),
|
|
|
|
/// (1, (1, 1, "common.entity.wild.aggressive.grolgar")),
|
|
|
|
/// ],
|
|
|
|
/// is_underwater: false,
|
|
|
|
/// day_period: [Night, Morning, Noon, Evening],
|
|
|
|
/// ),
|
|
|
|
/// ```
|
|
|
|
/// Groups:
|
|
|
|
/// ```text
|
|
|
|
/// (3, (1, 2, "common.entity.wild.aggressive.frostfang")),
|
|
|
|
/// ```
|
|
|
|
/// (3, ...) means that it has x3 chance to spawn (3/6 when every other has
|
|
|
|
/// 1/6).
|
|
|
|
///
|
|
|
|
/// (.., (1, 2, ...)) is `1..=2` group size which means that it has
|
|
|
|
/// chance to spawn as single mob or in pair
|
|
|
|
///
|
|
|
|
/// (..., (..., "common.entity.wild.aggressive.frostfang")) corresponds
|
|
|
|
/// to `assets/common/entity/wild/aggressive/frostfang.ron` file with
|
|
|
|
/// EntityConfig
|
|
|
|
///
|
|
|
|
/// Underwater:
|
|
|
|
/// `is_underwater: false` means mobs from this pack can't be spawned underwater
|
|
|
|
/// in rivers, lakes or ocean
|
|
|
|
///
|
|
|
|
/// Day period:
|
|
|
|
/// `day_period: [Night, Morning, Noon, Evening]`
|
|
|
|
/// means that mobs from this pack may be spawned in any day period without
|
|
|
|
/// exception
|
2021-07-05 22:46:57 +00:00
|
|
|
#[derive(Clone, Debug, Deserialize)]
|
|
|
|
pub struct Pack {
|
2021-07-16 14:30:10 +00:00
|
|
|
pub groups: Vec<(Weight, (Min, Max, String))>,
|
2021-07-05 22:46:57 +00:00
|
|
|
pub is_underwater: bool,
|
|
|
|
pub day_period: Vec<DayPeriod>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Pack {
|
|
|
|
pub fn generate(&self, pos: Vec3<f32>, dynamic_rng: &mut impl Rng) -> (EntityInfo, u8) {
|
|
|
|
let (_, (from, to, entity_asset)) = self
|
|
|
|
.groups
|
|
|
|
.choose_weighted(dynamic_rng, |(p, _group)| *p)
|
|
|
|
.expect("Failed to choose group");
|
|
|
|
let entity = EntityInfo::at(pos).with_asset_expect(entity_asset);
|
|
|
|
let group_size = dynamic_rng.gen_range(*from..=*to);
|
|
|
|
|
|
|
|
(entity, group_size)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-12 10:46:53 +00:00
|
|
|
pub type DensityFn = fn(&SimChunk, &ColumnSample) -> f32;
|
2021-07-05 22:46:57 +00:00
|
|
|
|
2021-07-12 10:46:53 +00:00
|
|
|
pub fn spawn_manifest() -> Vec<(&'static str, DensityFn)> {
|
2021-07-05 22:46:57 +00:00
|
|
|
// NOTE: Order matters.
|
2021-07-16 14:30:10 +00:00
|
|
|
// Entries with more specific requirements
|
|
|
|
// and overall scarcity should come first, where possible.
|
2021-07-05 22:46:57 +00:00
|
|
|
vec![
|
|
|
|
// **Tundra**
|
|
|
|
// Rock animals
|
|
|
|
("world.wildlife.spawn.tundra.rock", |c, col| {
|
|
|
|
close(c.temp, CONFIG.snow_temp, 0.15) * BASE_DENSITY * col.rock * 1.0
|
|
|
|
}),
|
|
|
|
// Core animals
|
|
|
|
("world.wildlife.spawn.tundra.core", |c, _col| {
|
|
|
|
close(c.temp, CONFIG.snow_temp, 0.15) * BASE_DENSITY * 0.5
|
|
|
|
}),
|
|
|
|
// Snowy animals
|
|
|
|
("world.wildlife.spawn.tundra.snow", |c, col| {
|
|
|
|
close(c.temp, CONFIG.snow_temp, 0.3) * BASE_DENSITY * col.snow_cover as i32 as f32 * 1.0
|
|
|
|
}),
|
|
|
|
// Forest animals
|
|
|
|
("world.wildlife.spawn.tundra.forest", |c, col| {
|
|
|
|
close(c.temp, CONFIG.snow_temp, 0.3) * col.tree_density * BASE_DENSITY * 1.4
|
|
|
|
}),
|
|
|
|
// **Taiga**
|
|
|
|
// Forest core animals
|
|
|
|
("world.wildlife.spawn.taiga.core_forest", |c, col| {
|
|
|
|
close(c.temp, CONFIG.snow_temp + 0.2, 0.2) * col.tree_density * BASE_DENSITY * 0.4
|
|
|
|
}),
|
|
|
|
// Core animals
|
|
|
|
("world.wildlife.spawn.taiga.core", |c, _col| {
|
|
|
|
close(c.temp, CONFIG.snow_temp + 0.2, 0.2) * BASE_DENSITY * 1.0
|
|
|
|
}),
|
|
|
|
// Forest area animals
|
|
|
|
("world.wildlife.spawn.taiga.forest", |c, col| {
|
|
|
|
close(c.temp, CONFIG.snow_temp + 0.2, 0.6) * col.tree_density * BASE_DENSITY * 0.9
|
|
|
|
}),
|
|
|
|
// Area animals
|
|
|
|
("world.wildlife.spawn.taiga.area", |c, _col| {
|
|
|
|
close(c.temp, CONFIG.snow_temp + 0.2, 0.6) * BASE_DENSITY * 5.0
|
|
|
|
}),
|
|
|
|
// Water animals
|
|
|
|
("world.wildlife.spawn.taiga.water", |c, col| {
|
|
|
|
close(c.temp, CONFIG.snow_temp, 0.15) * col.tree_density * BASE_DENSITY * 5.0
|
|
|
|
}),
|
|
|
|
// **Temperate**
|
|
|
|
// Area rare
|
|
|
|
("world.wildlife.spawn.temperate.rare", |c, _col| {
|
|
|
|
close(c.temp, CONFIG.temperate_temp, 0.8) * BASE_DENSITY * 0.08
|
|
|
|
}),
|
|
|
|
// River wildlife
|
|
|
|
("world.wildlife.spawn.temperate.river", |_c, col| {
|
|
|
|
close(col.temp, CONFIG.temperate_temp, 0.6)
|
|
|
|
* if col.water_dist.map(|d| d < 10.0).unwrap_or(false) {
|
|
|
|
0.001
|
|
|
|
} else {
|
|
|
|
0.0
|
|
|
|
}
|
|
|
|
}),
|
|
|
|
// Forest animals
|
|
|
|
("world.wildlife.spawn.temperate.wood", |c, col| {
|
|
|
|
close(c.temp, CONFIG.temperate_temp + 0.1, 0.5) * col.tree_density * BASE_DENSITY * 1.0
|
|
|
|
}),
|
|
|
|
// Rainforest animals
|
|
|
|
("world.wildlife.spawn.temperate.rainforest", |c, _col| {
|
|
|
|
close(c.temp, CONFIG.temperate_temp + 0.1, 0.6)
|
|
|
|
* close(c.humidity, CONFIG.forest_hum, 0.6)
|
|
|
|
* BASE_DENSITY
|
|
|
|
* 4.0
|
|
|
|
}),
|
|
|
|
// Water animals
|
|
|
|
("world.wildlife.spawn.temperate.water", |c, col| {
|
|
|
|
close(c.temp, CONFIG.temperate_temp, 1.0) * col.tree_density * BASE_DENSITY * 5.0
|
|
|
|
}),
|
|
|
|
// **Jungle**
|
|
|
|
// Rainforest animals
|
|
|
|
("world.wildlife.spawn.jungle.rainforest", |c, _col| {
|
|
|
|
close(c.temp, CONFIG.tropical_temp + 0.2, 0.2)
|
|
|
|
* close(c.humidity, CONFIG.jungle_hum, 0.2)
|
|
|
|
* BASE_DENSITY
|
|
|
|
* 2.8
|
|
|
|
}),
|
|
|
|
// Rainforest area animals
|
|
|
|
("world.wildlife.spawn.jungle.rainforest_area", |c, _col| {
|
|
|
|
close(c.temp, CONFIG.tropical_temp + 0.2, 0.3)
|
|
|
|
* close(c.humidity, CONFIG.jungle_hum, 0.2)
|
|
|
|
* BASE_DENSITY
|
|
|
|
* 8.0
|
|
|
|
}),
|
|
|
|
// **Tropical**
|
|
|
|
// Rare river animals
|
|
|
|
("world.wildlife.spawn.tropical.river_rare", |_c, col| {
|
|
|
|
close(col.temp, CONFIG.tropical_temp + 0.2, 0.5)
|
|
|
|
* if col.water_dist.map(|d| d < 10.0).unwrap_or(false) {
|
|
|
|
0.0001
|
|
|
|
} else {
|
|
|
|
0.0
|
|
|
|
}
|
|
|
|
}),
|
|
|
|
// River animals
|
2021-07-10 18:44:53 +00:00
|
|
|
("world.wildlife.spawn.tropical.river", |_c, col| {
|
2021-07-05 22:46:57 +00:00
|
|
|
close(col.temp, CONFIG.tropical_temp, 0.5)
|
|
|
|
* if col.water_dist.map(|d| d < 10.0).unwrap_or(false) {
|
|
|
|
0.001
|
|
|
|
} else {
|
|
|
|
0.0
|
|
|
|
}
|
|
|
|
}),
|
|
|
|
// Rainforest area animals
|
2021-07-10 18:44:53 +00:00
|
|
|
("world.wildlife.spawn.tropical.rainforest", |c, _col| {
|
|
|
|
close(c.temp, CONFIG.tropical_temp + 0.1, 0.4)
|
|
|
|
* close(c.humidity, CONFIG.desert_hum, 0.4)
|
|
|
|
* BASE_DENSITY
|
|
|
|
* 2.0
|
|
|
|
}),
|
2021-07-05 22:46:57 +00:00
|
|
|
// Rock animals
|
|
|
|
("world.wildlife.spawn.tropical.rock", |c, col| {
|
|
|
|
close(c.temp, CONFIG.tropical_temp + 0.1, 0.5) * col.rock * BASE_DENSITY * 5.0
|
|
|
|
}),
|
|
|
|
// **Desert**
|
|
|
|
// Area animals
|
|
|
|
("world.wildlife.spawn.desert.area", |c, _col| {
|
|
|
|
close(c.temp, CONFIG.tropical_temp + 0.1, 0.4)
|
|
|
|
* close(c.humidity, CONFIG.desert_hum, 0.4)
|
|
|
|
* BASE_DENSITY
|
|
|
|
* 0.8
|
|
|
|
}),
|
2021-07-10 18:44:53 +00:00
|
|
|
// Wasteland animals
|
2021-07-05 22:46:57 +00:00
|
|
|
("world.wildlife.spawn.desert.wasteland", |c, _col| {
|
|
|
|
close(c.temp, CONFIG.desert_temp + 0.2, 0.3)
|
|
|
|
* close(c.humidity, CONFIG.desert_hum, 0.5)
|
|
|
|
* BASE_DENSITY
|
|
|
|
* 1.3
|
|
|
|
}),
|
|
|
|
// River animals
|
2021-07-10 18:44:53 +00:00
|
|
|
("world.wildlife.spawn.desert.river", |_c, col| {
|
2021-07-05 22:46:57 +00:00
|
|
|
close(col.temp, CONFIG.desert_temp + 0.2, 0.3)
|
|
|
|
* if col.water_dist.map(|d| d < 10.0).unwrap_or(false) {
|
|
|
|
0.0001
|
|
|
|
} else {
|
|
|
|
0.0
|
|
|
|
}
|
|
|
|
}),
|
2021-07-10 18:44:53 +00:00
|
|
|
// Hot area desert
|
2021-07-05 22:46:57 +00:00
|
|
|
("world.wildlife.spawn.desert.hot", |c, _col| {
|
|
|
|
close(c.temp, CONFIG.desert_temp + 0.2, 0.3) * BASE_DENSITY * 3.8
|
|
|
|
}),
|
|
|
|
]
|
|
|
|
}
|
|
|
|
|
2020-10-01 21:00:46 +00:00
|
|
|
pub fn apply_wildlife_supplement<'a, R: Rng>(
|
|
|
|
// NOTE: Used only for dynamic elements like chests and entities!
|
|
|
|
dynamic_rng: &mut R,
|
|
|
|
wpos2d: Vec2<i32>,
|
|
|
|
mut get_column: impl FnMut(Vec2<i32>) -> Option<&'a ColumnSample<'a>>,
|
|
|
|
vol: &(impl BaseVol<Vox = Block> + RectSizedVol + ReadVol + WriteVol),
|
2021-07-12 10:46:53 +00:00
|
|
|
index: IndexRef,
|
2020-10-01 21:00:46 +00:00
|
|
|
chunk: &SimChunk,
|
|
|
|
supplement: &mut ChunkSupplement,
|
2021-04-16 11:17:15 +00:00
|
|
|
time: Option<TimeOfDay>,
|
2020-10-01 21:00:46 +00:00
|
|
|
) {
|
2021-07-16 14:30:10 +00:00
|
|
|
let scatter = &index.wildlife_spawns;
|
2020-10-01 21:00:46 +00:00
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
// Sample terrain
|
|
|
|
let col_sample = if let Some(col_sample) = get_column(offs) {
|
|
|
|
col_sample
|
|
|
|
} else {
|
|
|
|
continue;
|
|
|
|
};
|
|
|
|
|
|
|
|
let underwater = col_sample.water_level > col_sample.alt;
|
2021-04-16 11:17:15 +00:00
|
|
|
let current_day_period;
|
|
|
|
if let Some(time) = time {
|
|
|
|
current_day_period = DayPeriod::from(time.0)
|
|
|
|
} else {
|
2021-07-10 18:44:53 +00:00
|
|
|
current_day_period = DayPeriod::Noon
|
2021-04-16 11:17:15 +00:00
|
|
|
}
|
2020-10-01 21:00:46 +00:00
|
|
|
|
2021-07-05 22:46:57 +00:00
|
|
|
let entity_group = scatter
|
|
|
|
.iter()
|
|
|
|
.enumerate()
|
|
|
|
.find_map(|(_i, (entry, get_density))| {
|
2020-11-21 12:33:52 +00:00
|
|
|
let density = get_density(chunk, col_sample);
|
2021-07-05 22:46:57 +00:00
|
|
|
(density > 0.0)
|
|
|
|
.then(|| {
|
|
|
|
entry
|
2021-07-12 10:46:53 +00:00
|
|
|
.read()
|
2021-07-05 22:46:57 +00:00
|
|
|
.request(current_day_period, underwater)
|
|
|
|
.and_then(|pack| {
|
|
|
|
(dynamic_rng.gen::<f32>() < density * col_sample.spawn_rate
|
|
|
|
&& col_sample.gradient < Some(1.3))
|
|
|
|
.then(|| pack)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
.flatten()
|
|
|
|
});
|
2020-10-01 21:00:46 +00:00
|
|
|
|
2021-06-11 06:09:58 +00:00
|
|
|
let alt = col_sample.alt as i32;
|
|
|
|
|
2021-07-05 22:46:57 +00:00
|
|
|
if let Some(pack) = entity_group {
|
|
|
|
let (entity, group_size) = pack.generate(
|
2021-06-11 06:09:58 +00:00
|
|
|
(wpos2d.map(|e| e as f32) + 0.5).with_z(alt as f32),
|
|
|
|
dynamic_rng,
|
|
|
|
);
|
|
|
|
for e in 0..group_size {
|
|
|
|
// Choose a nearby position
|
|
|
|
let offs_wpos2d = (Vec2::new(
|
|
|
|
(e as f32 / group_size as f32 * 2.0 * f32::consts::PI).sin(),
|
|
|
|
(e as f32 / group_size as f32 * 2.0 * f32::consts::PI).cos(),
|
|
|
|
) * (5.0 + dynamic_rng.gen::<f32>().powf(0.5) * 5.0))
|
|
|
|
.map(|e| e as i32);
|
|
|
|
// Clamp position to chunk
|
|
|
|
let offs_wpos2d = (offs + offs_wpos2d)
|
|
|
|
.clamped(Vec2::zero(), vol.size_xy().map(|e| e as i32) - 1)
|
|
|
|
- offs;
|
|
|
|
|
|
|
|
// Find the intersection between ground and air, if there is one near the
|
|
|
|
// surface
|
|
|
|
if let Some(solid_end) = (-8..8).find(|z| {
|
|
|
|
(0..2).all(|z2| {
|
|
|
|
vol.get(Vec3::new(offs.x, offs.y, alt) + offs_wpos2d.with_z(z + z2))
|
2020-10-01 21:00:46 +00:00
|
|
|
.map(|b| !b.is_solid())
|
|
|
|
.unwrap_or(true)
|
|
|
|
})
|
2021-06-11 06:09:58 +00:00
|
|
|
}) {
|
2020-10-06 08:18:35 +00:00
|
|
|
let mut entity = entity.clone();
|
2021-06-11 06:09:58 +00:00
|
|
|
entity.pos += offs_wpos2d.with_z(solid_end).map(|e| e as f32);
|
2021-07-11 18:24:44 +00:00
|
|
|
supplement.add_entity(entity);
|
2020-10-01 21:00:46 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-07-05 22:46:57 +00:00
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
|
|
|
use hashbrown::{HashMap, HashSet};
|
|
|
|
|
|
|
|
// Checks that each entry in spawn manifest is loadable
|
|
|
|
#[test]
|
|
|
|
fn test_load_entries() {
|
2021-07-12 10:46:53 +00:00
|
|
|
let scatter = spawn_manifest();
|
2021-07-05 22:46:57 +00:00
|
|
|
for (entry, _) in scatter.into_iter() {
|
|
|
|
std::mem::drop(SpawnEntry::from(entry));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check that each spawn entry has unique name
|
|
|
|
#[test]
|
|
|
|
fn test_name_uniqueness() {
|
2021-07-12 10:46:53 +00:00
|
|
|
let scatter = spawn_manifest();
|
2021-07-05 22:46:57 +00:00
|
|
|
let mut names = HashMap::new();
|
|
|
|
for (entry, _) in scatter.into_iter() {
|
|
|
|
let SpawnEntry { name, .. } = SpawnEntry::from(entry);
|
|
|
|
if let Some(old_entry) = names.insert(name, entry) {
|
|
|
|
panic!("{}: Found name duplicate with {}", entry, old_entry);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Checks that day_period aren't overlapping
|
|
|
|
// Quite strict rule, but otherwise may produce unexpected behaviour
|
|
|
|
#[test]
|
|
|
|
fn test_check_day_periods() {
|
2021-07-12 10:46:53 +00:00
|
|
|
let scatter = spawn_manifest();
|
2021-07-05 22:46:57 +00:00
|
|
|
for (entry, _) in scatter.into_iter() {
|
|
|
|
let mut day_periods = HashSet::with_capacity(4);
|
|
|
|
let SpawnEntry { rules, .. } = SpawnEntry::from(entry);
|
|
|
|
for pack in rules {
|
|
|
|
let Pack {
|
|
|
|
day_period,
|
|
|
|
is_underwater,
|
|
|
|
..
|
|
|
|
} = pack;
|
|
|
|
for period in day_period {
|
|
|
|
if !day_periods.insert((period, is_underwater)) {
|
|
|
|
panic!(
|
|
|
|
r#"
|
|
|
|
== {}: ==
|
|
|
|
Found rules with duplicated `day_period` and `is_underwater`
|
|
|
|
If you have two of such entries,
|
|
|
|
there are big chances that second rule will be unreachable.
|
|
|
|
|
|
|
|
If you want some animals spawned in different Packs
|
|
|
|
with same day_period, add those animals to both Packs
|
|
|
|
and choose day_period to not overlap.
|
|
|
|
"#,
|
|
|
|
entry
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Checks that each entity is loadable
|
|
|
|
#[test]
|
|
|
|
fn test_load_entities() {
|
2021-07-12 10:46:53 +00:00
|
|
|
let scatter = spawn_manifest();
|
2021-07-05 22:46:57 +00:00
|
|
|
for (entry, _) in scatter.into_iter() {
|
|
|
|
let SpawnEntry { rules, .. } = SpawnEntry::from(entry);
|
|
|
|
for pack in rules {
|
|
|
|
let Pack { groups, .. } = pack;
|
|
|
|
for group in &groups {
|
|
|
|
println!("{}:", entry);
|
|
|
|
let (_, (_, _, asset)) = group;
|
|
|
|
let dummy_pos = Vec3::new(0.0, 0.0, 0.0);
|
|
|
|
let entity = EntityInfo::at(dummy_pos).with_asset_expect(asset);
|
|
|
|
std::mem::drop(entity);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Checks that group distribution has valid form
|
|
|
|
#[test]
|
|
|
|
fn test_group_choose() {
|
2021-07-12 10:46:53 +00:00
|
|
|
let scatter = spawn_manifest();
|
2021-07-05 22:46:57 +00:00
|
|
|
for (entry, _) in scatter.into_iter() {
|
|
|
|
let SpawnEntry { rules, .. } = SpawnEntry::from(entry);
|
|
|
|
for pack in rules {
|
|
|
|
let Pack { groups, .. } = pack;
|
|
|
|
let dynamic_rng = &mut rand::thread_rng();
|
|
|
|
let _ = groups
|
|
|
|
.choose_weighted(dynamic_rng, |(p, _group)| *p)
|
|
|
|
.unwrap_or_else(|err| {
|
|
|
|
panic!("{}: Failed to choose random group. Err: {}", entry, err)
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|