diff --git a/assets/common/cave_scatter.ron b/assets/common/cave_scatter.ron new file mode 100644 index 0000000000..d45653c98e --- /dev/null +++ b/assets/common/cave_scatter.ron @@ -0,0 +1,10 @@ +[ + (25, Velorite), + (50, VeloriteFrag), + (15, WhiteFlower), + (80, ShortGrass), + (150, Mushroom), + (5, Chest), + (2, Crate), + (1, Scarecrow), +] diff --git a/common/src/comp/inventory/item/mod.rs b/common/src/comp/inventory/item/mod.rs index 3990e1c5d7..4ea11edd80 100644 --- a/common/src/comp/inventory/item/mod.rs +++ b/common/src/comp/inventory/item/mod.rs @@ -1,5 +1,4 @@ pub mod armor; -pub mod lottery; pub mod tool; // Reexports @@ -9,6 +8,7 @@ use crate::{ assets::{self, Asset}, effect::Effect, terrain::{Block, BlockKind}, + lottery::Lottery, }; use serde::{Deserialize, Serialize}; use specs::{Component, FlaggedStorage}; @@ -161,7 +161,7 @@ impl Item { BlockKind::ShortGrass => Some(assets::load_expect_cloned("common.items.grasses.short")), BlockKind::Coconut => Some(assets::load_expect_cloned("common.items.food.coconut")), BlockKind::Chest => { - let chosen = assets::load_expect::>("common.loot_table"); + let chosen = assets::load_expect::>("common.loot_table"); let chosen = chosen.choose(); Some(assets::load_expect_cloned(chosen)) diff --git a/common/src/lib.rs b/common/src/lib.rs index 3bddacde79..6f0632e0b6 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -39,5 +39,6 @@ pub mod terrain; pub mod util; pub mod vol; pub mod volumes; +pub mod lottery; pub use loadout_builder::LoadoutBuilder; diff --git a/common/src/comp/inventory/item/lottery.rs b/common/src/lottery.rs similarity index 81% rename from common/src/comp/inventory/item/lottery.rs rename to common/src/lottery.rs index 6cbeed52d7..13b5ff8142 100644 --- a/common/src/comp/inventory/item/lottery.rs +++ b/common/src/lottery.rs @@ -1,25 +1,19 @@ use crate::assets::{self, Asset}; use rand::prelude::*; -use serde::{Deserialize, Serialize}; +use serde::{Deserialize, Serialize, de::DeserializeOwned}; use std::{fs::File, io::BufReader}; -// Generate a random float between 0 and 1 -pub fn rand() -> f32 { - let mut rng = rand::thread_rng(); - rng.gen() -} - #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct Lottery { items: Vec<(f32, T)>, total: f32, } -impl Asset for Lottery { +impl Asset for Lottery { const ENDINGS: &'static [&'static str] = &["ron"]; fn parse(buf_reader: BufReader) -> Result { - ron::de::from_reader::, Vec<(f32, String)>>(buf_reader) + ron::de::from_reader::, Vec<(f32, T)>>(buf_reader) .map(|items| Lottery::from_rates(items.into_iter())) .map_err(assets::Error::parse_error) } @@ -37,8 +31,8 @@ impl Lottery { Self { items, total } } - pub fn choose(&self) -> &T { - let x = rand() * self.total; + pub fn choose_seeded(&self, seed: u32) -> &T { + let x = ((seed % 65536) as f32 / 65536.0) * self.total; &self.items[self .items .binary_search_by(|(y, _)| y.partial_cmp(&x).unwrap()) @@ -46,6 +40,10 @@ impl Lottery { .1 } + pub fn choose(&self) -> &T { + self.choose_seeded(thread_rng().gen()) + } + pub fn iter(&self) -> impl Iterator { self.items.iter() } } diff --git a/server/src/events/entity_manipulation.rs b/server/src/events/entity_manipulation.rs index ff6f4ae2b6..852b8317bc 100644 --- a/server/src/events/entity_manipulation.rs +++ b/server/src/events/entity_manipulation.rs @@ -2,7 +2,7 @@ use crate::{client::Client, Server, SpawnPoint, StateExt}; use common::{ assets, comp::{ - self, item::lottery::Lottery, object, Alignment, Body, Damage, DamageSource, Group, + self, object, Alignment, Body, Damage, DamageSource, Group, HealthChange, HealthSource, Player, Pos, Stats, }, msg::{PlayerListUpdate, ServerMsg}, @@ -12,6 +12,7 @@ use common::{ sys::combat::BLOCK_ANGLE, terrain::{Block, TerrainGrid}, vol::{ReadVol, Vox}, + lottery::Lottery, }; use specs::{join::Join, saveload::MarkerAllocator, Entity as EcsEntity, WorldExt}; use tracing::error; @@ -182,7 +183,7 @@ pub fn handle_destroy(server: &mut Server, entity: EcsEntity, cause: HealthSourc item_drops.remove(entity); item_drop.0 } else { - let chosen = assets::load_expect::>("common.loot_table"); + let chosen = assets::load_expect::>("common.loot_table"); let chosen = chosen.choose(); assets::load_expect_cloned(chosen) diff --git a/world/src/block/mod.rs b/world/src/block/mod.rs index 4edef6b8d4..323450661f 100644 --- a/world/src/block/mod.rs +++ b/world/src/block/mod.rs @@ -164,8 +164,6 @@ impl<'a> BlockGen<'a> { //tree_density, //forest_kind, //close_structures, - cave_xy, - cave_alt, marble, marble_small, rock, @@ -370,23 +368,6 @@ impl<'a> BlockGen<'a> { None } }) - .and_then(|block| { - // Caves - // Underground - let cave = cave_xy.powf(2.0) - * (wposf.z as f32 - cave_alt) - .div(40.0) - .powf(4.0) - .neg() - .add(1.0) - > 0.9993; - - if cave && wposf.z as f32 > water_height + 3.0 { - None - } else { - Some(block) - } - }) .or_else(|| { // Water if (wposf.z as f32) < water_height { @@ -422,14 +403,7 @@ pub struct ZCache<'a> { impl<'a> ZCache<'a> { pub fn get_z_limits(&self, block_gen: &mut BlockGen, index: &Index) -> (f32, f32, f32) { - let cave_depth = - if self.sample.cave_xy.abs() > 0.9 && self.sample.water_level <= self.sample.alt { - (self.sample.alt - self.sample.cave_alt + 8.0).max(0.0) - } else { - 0.0 - }; - - let min = self.sample.alt - (self.sample.chaos.min(1.0) * 16.0 + cave_depth); + let min = self.sample.alt - (self.sample.chaos.min(1.0) * 16.0); let min = min - 4.0; let cliff = BlockGen::get_cliff_height( diff --git a/world/src/civ/mod.rs b/world/src/civ/mod.rs index 96ff4c98f9..53888f46de 100644 --- a/world/src/civ/mod.rs +++ b/world/src/civ/mod.rs @@ -214,7 +214,8 @@ impl Civs { // TODO: Move this fn generate_cave(&self, ctx: &mut GenCtx) { - let mut pos = ctx.sim + let mut pos = ctx + .sim .get_size() .map(|sz| ctx.rng.gen_range(0, sz as i32) as f32); let mut vel = pos @@ -225,14 +226,16 @@ impl Civs { let path = (-100..100) .filter_map(|i: i32| { let depth = (i.abs() as f32 / 100.0 * std::f32::consts::PI / 2.0).cos(); - vel = (vel + Vec2::new( - ctx.rng.gen_range(-0.25, 0.25), - ctx.rng.gen_range(-0.25, 0.25), - )) - .try_normalized() - .unwrap_or_else(Vec2::unit_y); + vel = (vel + + Vec2::new( + ctx.rng.gen_range(-0.35, 0.35), + ctx.rng.gen_range(-0.35, 0.35), + )) + .try_normalized() + .unwrap_or_else(Vec2::unit_y); let old_pos = pos.map(|e| e as i32); - pos = (pos + vel * 0.5).clamped(Vec2::zero(), ctx.sim.get_size().map(|e| e as f32 - 1.0)); + pos = (pos + vel * 0.5) + .clamped(Vec2::zero(), ctx.sim.get_size().map(|e| e as f32 - 1.0)); Some((pos.map(|e| e as i32), depth)).filter(|(pos, _)| *pos != old_pos) }) .collect::>(); @@ -256,15 +259,12 @@ impl Civs { ctx.sim.get_mut(locs[2].0).unwrap().cave.0.neighbors |= 1 << ((to_next_idx as u8 + 4) % 8); let mut chunk = ctx.sim.get_mut(locs[1].0).unwrap(); - chunk.cave.0.neighbors |= - (1 << (to_prev_idx as u8)) | (1 << (to_next_idx as u8)); - let depth = locs[1].1 * 250.0; - chunk.cave.1.alt = chunk.alt - depth + ctx.rng.gen_range(-4.0, 4.0) * (depth > 10.0) as i32 as f32; - chunk.cave.1.width = ctx.rng.gen_range(12.0, 32.0); - chunk.cave.0.offset = Vec2::new( - ctx.rng.gen_range(-16, 17), - ctx.rng.gen_range(-16, 17), - ); + chunk.cave.0.neighbors |= (1 << (to_prev_idx as u8)) | (1 << (to_next_idx as u8)); + let depth = locs[1].1 * 250.0 - 20.0; + chunk.cave.1.alt = + chunk.alt - depth + ctx.rng.gen_range(-4.0, 4.0) * (depth > 10.0) as i32 as f32; + chunk.cave.1.width = ctx.rng.gen_range(6.0, 32.0); + chunk.cave.0.offset = Vec2::new(ctx.rng.gen_range(-16, 17), ctx.rng.gen_range(-16, 17)); } } @@ -492,10 +492,8 @@ impl Civs { let mut chunk = ctx.sim.get_mut(locs[1]).unwrap(); chunk.path.0.neighbors |= (1 << (to_prev_idx as u8)) | (1 << (to_next_idx as u8)); - chunk.path.0.offset = Vec2::new( - ctx.rng.gen_range(-16, 17), - ctx.rng.gen_range(-16, 17), - ); + chunk.path.0.offset = + Vec2::new(ctx.rng.gen_range(-16, 17), ctx.rng.gen_range(-16, 17)); } // Take note of the track diff --git a/world/src/column/mod.rs b/world/src/column/mod.rs index 7a1cdc72af..47c28a3bab 100644 --- a/world/src/column/mod.rs +++ b/world/src/column/mod.rs @@ -2,8 +2,8 @@ use crate::{ all::ForestKind, block::StructureMeta, sim::{ - local_cells, uniform_idx_as_vec2, vec2_as_uniform_idx, - Path, Cave, RiverKind, SimChunk, WorldSim, + local_cells, uniform_idx_as_vec2, vec2_as_uniform_idx, Cave, Path, RiverKind, SimChunk, + WorldSim, }, util::Sampler, Index, CONFIG, @@ -1038,34 +1038,6 @@ where (alt, ground, sub_surface_color) }; - // Caves - let cave_at = |wposf: Vec2| { - (sim.gen_ctx.cave_0_nz.get( - Vec3::new(wposf.x, wposf.y, alt as f64 * 8.0) - .div(800.0) - .into_array(), - ) as f32) - .powf(2.0) - .neg() - .add(1.0) - .mul((1.32 - chaos).min(1.0)) - }; - let cave_xy = cave_at(wposf); - let cave_alt = alt - 24.0 - + (sim - .gen_ctx - .cave_1_nz - .get(Vec2::new(wposf.x, wposf.y).div(48.0).into_array()) as f32) - * 8.0 - + (sim - .gen_ctx - .cave_1_nz - .get(Vec2::new(wposf.x, wposf.y).div(500.0).into_array()) as f32) - .add(1.0) - .mul(0.5) - .powf(15.0) - .mul(150.0); - let near_ocean = max_river.and_then(|(_, _, river_data, _)| { if (river_data.is_lake() || river_data.river_kind == Some(RiverKind::Ocean)) && ((alt <= water_level.max(CONFIG.sea_level + 5.0) && !is_cliffs) || !near_cliffs) @@ -1118,8 +1090,6 @@ where }, forest_kind: sim_chunk.forest_kind, close_structures: self.gen_close_structures(wpos), - cave_xy, - cave_alt, marble, marble_small, rock, @@ -1153,8 +1123,6 @@ pub struct ColumnSample<'a> { pub tree_density: f32, pub forest_kind: ForestKind, pub close_structures: [Option; 9], - pub cave_xy: f32, - pub cave_alt: f32, pub marble: f32, pub marble_small: f32, pub rock: f32, diff --git a/world/src/index.rs b/world/src/index.rs index 242cbf3dcc..e181e0ed68 100644 --- a/world/src/index.rs +++ b/world/src/index.rs @@ -3,6 +3,16 @@ use common::store::{Id, Store}; #[derive(Default)] pub struct Index { + pub seed: u32, pub time: f32, pub sites: Store, } + +impl Index { + pub fn new(seed: u32) -> Self { + Self { + seed, + ..Self::default() + } + } +} diff --git a/world/src/layer/mod.rs b/world/src/layer/mod.rs index 565a89b9b5..612b5ca9cc 100644 --- a/world/src/layer/mod.rs +++ b/world/src/layer/mod.rs @@ -1,10 +1,13 @@ use crate::{ column::ColumnSample, util::{RandomField, Sampler}, + Index, }; use common::{ terrain::{Block, BlockKind}, vol::{BaseVol, ReadVol, RectSizedVol, Vox, WriteVol}, + lottery::Lottery, + assets, }; use std::f32; use vek::*; @@ -13,6 +16,7 @@ pub fn apply_paths_to<'a>( wpos2d: Vec2, mut get_column: impl FnMut(Vec2) -> Option<&'a ColumnSample<'a>>, vol: &mut (impl BaseVol + RectSizedVol + ReadVol + WriteVol), + index: &Index, ) { for y in 0..vol.size_xy().y as i32 { for x in 0..vol.size_xy().x as i32 { @@ -100,6 +104,7 @@ pub fn apply_caves_to<'a>( wpos2d: Vec2, mut get_column: impl FnMut(Vec2) -> Option<&'a ColumnSample<'a>>, vol: &mut (impl BaseVol + RectSizedVol + ReadVol + WriteVol), + index: &Index, ) { for y in 0..vol.size_xy().y as i32 { for x in 0..vol.size_xy().x as i32 { @@ -120,14 +125,26 @@ pub fn apply_caves_to<'a>( .filter(|(dist, _, cave, _)| *dist < cave.width) { let cave_x = (cave_dist / cave.width).min(1.0); - let height = (1.0 - cave_x.powf(2.0)).max(0.0).sqrt() * cave.width; - for z in (cave.alt - height) as i32..(cave.alt + height) as i32 { - let _ = vol.set( - Vec3::new(offs.x, offs.y, z), - Block::empty(), - ); + // Relative units + let cave_floor = 0.0 - 0.5 * (1.0 - cave_x.powf(2.0)).max(0.0).sqrt() * cave.width; + let cave_height = (1.0 - cave_x.powf(2.0)).max(0.0).sqrt() * cave.width; + + // Abs units + let cave_base = (cave.alt + cave_floor) as i32; + let cave_roof = (cave.alt + cave_height) as i32; + + for z in cave_base..cave_roof { + let _ = vol.set(Vec3::new(offs.x, offs.y, z), Block::empty()); } + + // Scatter things in caves + if RandomField::new(index.seed).chance(wpos2d.into(), 0.002) && cave_base < surface_z as i32 - 25 { + let kind = *assets::load_expect::>("common.cave_scatter") + .choose_seeded(RandomField::new(index.seed + 1).get(wpos2d.into())); + let _ = vol.set(Vec3::new(offs.x, offs.y, cave_base), Block::new(kind, Rgb::zero())); + } + } } } diff --git a/world/src/lib.rs b/world/src/lib.rs index 96d8865da2..d36576595b 100644 --- a/world/src/lib.rs +++ b/world/src/lib.rs @@ -48,7 +48,7 @@ pub struct World { impl World { pub fn generate(seed: u32, opts: sim::WorldOpts) -> Self { let mut sim = sim::WorldSim::generate(seed, opts); - let mut index = Index::default(); + let mut index = Index::new(seed); let civs = civ::Civs::generate(seed, &mut sim, &mut index); sim2::simulate(&mut index, &mut sim); @@ -176,8 +176,8 @@ impl World { let mut rng = rand::thread_rng(); // Apply layers (paths, caves, etc.) - layer::apply_paths_to(chunk_wpos2d, sample_get, &mut chunk); - layer::apply_caves_to(chunk_wpos2d, sample_get, &mut chunk); + layer::apply_paths_to(chunk_wpos2d, sample_get, &mut chunk, &self.index); + layer::apply_caves_to(chunk_wpos2d, sample_get, &mut chunk, &self.index); // Apply site generation sim_chunk.sites.iter().for_each(|site| { diff --git a/world/src/sim/mod.rs b/world/src/sim/mod.rs index 584c95dbc0..92a1fe59fe 100644 --- a/world/src/sim/mod.rs +++ b/world/src/sim/mod.rs @@ -2,8 +2,8 @@ mod diffusion; mod erosion; mod location; mod map; -mod way; mod util; +mod way; // Reexports use self::erosion::Compute; @@ -15,12 +15,12 @@ pub use self::{ }, location::Location, map::{MapConfig, MapDebug}, - way::{Way, Path, Cave}, util::{ cdf_irwin_hall, downhill, get_oceans, local_cells, map_edge_factor, neighbors, uniform_idx_as_vec2, uniform_noise, uphill, vec2_as_uniform_idx, InverseCdf, ScaleBias, NEIGHBOR_DELTA, }, + way::{Cave, Path, Way}, }; use crate::{ @@ -1703,7 +1703,7 @@ impl WorldSim { /// Return the distance to the nearest way in blocks, along with the /// closest point on the way, the way metadata, and the tangent vector /// of that way. - pub fn get_nearest_way>( + pub fn get_nearest_way>( &self, wpos: Vec2, get_way: impl Fn(&SimChunk) -> Option<(Way, M)>, @@ -1722,7 +1722,8 @@ impl WorldSim { .iter() .filter_map(|ctrl| { let (way, meta) = get_way(self.get(chunk_pos + *ctrl)?)?; - let ctrl_pos = get_chunk_centre(chunk_pos + *ctrl).map(|e| e as f32) + way.offset.map(|e| e as f32); + let ctrl_pos = get_chunk_centre(chunk_pos + *ctrl).map(|e| e as f32) + + way.offset.map(|e| e as f32); let chunk_connections = way.neighbors.count_ones(); if chunk_connections == 0 { diff --git a/world/src/site/dungeon/mod.rs b/world/src/site/dungeon/mod.rs index 3e8d7ecc5e..e2decc7fc3 100644 --- a/world/src/site/dungeon/mod.rs +++ b/world/src/site/dungeon/mod.rs @@ -467,7 +467,15 @@ impl Floor { if room.boss { let boss_spawn_tile = room.area.center(); // Don't spawn the boss in a pillar - let boss_spawn_tile = boss_spawn_tile + if tile_is_pillar { 1 } else { 0 }; + let boss_tile_is_pillar = room + .pillars + .map(|pillar_space| { + boss_spawn_tile + .map(|e| e.rem_euclid(pillar_space) == 0) + .reduce_and() + }) + .unwrap_or(false); + let boss_spawn_tile = boss_spawn_tile + if boss_tile_is_pillar { 1 } else { 0 }; if tile_pos == boss_spawn_tile && tile_wcenter.xy() == wpos2d { let entity = EntityInfo::at(tile_wcenter.map(|e| e as f32))