diff --git a/emergence_lib/src/geometry/indexing.rs b/emergence_lib/src/geometry/indexing.rs index 972c31ab6..5221314ac 100644 --- a/emergence_lib/src/geometry/indexing.rs +++ b/emergence_lib/src/geometry/indexing.rs @@ -11,7 +11,7 @@ use crate::{ items::inventory::InventoryState, structures::Footprint, units::actions::DeliveryMode, }; -use super::{DiscreteHeight, Facing, Height, VoxelKind, VoxelObject, VoxelPos}; +use super::{DiscreteHeight, Facing, VoxelKind, VoxelObject, VoxelPos}; use core::fmt::Display; /// The overall size and arrangement of the map. @@ -220,32 +220,6 @@ impl MapGeometry { .all(|voxel_pos| self.is_valid(voxel_pos.hex)) } - /// Is the provided `voxel_pos` passable? - /// - /// Tiles that are not part of the map will return `false`. - /// Tiles that have a structure will return `false`. - /// Tiles that are more than [`Height::MAX_STEP`] above or below the current tile will return `false`. - /// Tiles that are completely full of litter will return `false`. - #[inline] - #[must_use] - pub(crate) fn is_passable(&self, starting_pos: VoxelPos, ending_pos: VoxelPos) -> bool { - if !self.is_valid(starting_pos.hex) { - return false; - } - - if !self.is_valid(ending_pos.hex) { - return false; - } - - if let Some(voxel_data) = self.get_voxel(starting_pos) { - if !voxel_data.object_kind.can_walk_through() { - return false; - } - } - - starting_pos.abs_height_diff(ending_pos) <= Height::MAX_STEP - } - /// Is there enough space for a structure with the provided `footprint` located at the `center` tile? #[inline] pub(crate) fn is_space_available( @@ -421,7 +395,7 @@ impl MapGeometry { } } - /// Updates the [`Height`] of the terrain at the provided `hex` to `height`. + /// Updates the [`DiscreteHeight`] of the terrain at the provided `hex` to `height`. #[inline] pub fn update_height(&mut self, hex: Hex, height: DiscreteHeight) { let old_height = self.get_height(hex).unwrap(); @@ -864,6 +838,8 @@ impl MapGeometry { /// Asserts that all of the heights in the map are between `Height::ZERO` and `Height::MAX`. fn validate_heights(&self) { + use crate::geometry::Height; + for voxel_pos in self.voxel_index.keys() { let height = voxel_pos.height(); assert!( diff --git a/emergence_lib/src/organisms/lifecycle.rs b/emergence_lib/src/organisms/lifecycle.rs index 976d040e9..42aff38dd 100644 --- a/emergence_lib/src/organisms/lifecycle.rs +++ b/emergence_lib/src/organisms/lifecycle.rs @@ -316,7 +316,7 @@ pub(super) fn sprout_seeds( } } else { // For units, just make sure the tile is empty. - if !map_geometry.is_passable(voxel_pos, voxel_pos) { + if map_geometry.is_voxel_clear(voxel_pos).is_err() { continue; } } diff --git a/emergence_lib/src/units/actions.rs b/emergence_lib/src/units/actions.rs index 95d38c223..9f026b128 100644 --- a/emergence_lib/src/units/actions.rs +++ b/emergence_lib/src/units/actions.rs @@ -94,13 +94,11 @@ pub(super) fn choose_actions( &map_geometry, &terrain_manifest, &terrain_query, - facing, rng, ), None => CurrentAction::wander( previous_action, unit_pos, - facing, &map_geometry, &terrain_query, &terrain_manifest, @@ -122,7 +120,6 @@ pub(super) fn choose_actions( &map_geometry, &terrain_manifest, &terrain_query, - facing, rng, ) } else { @@ -159,7 +156,6 @@ pub(super) fn choose_actions( &map_geometry, &terrain_manifest, &terrain_query, - facing, rng, ) } @@ -437,12 +433,16 @@ pub(super) fn finish_actions( RotationDirection::Right => unit.facing.rotate_clockwise(), }, UnitAction::MoveForward => { - let direction = unit.facing.direction; - if let Some(target_voxel) = - map_geometry.walkable_neighbor_in_direction(*unit.voxel_pos, direction) + if let Some(target_voxel) = map_geometry + .walkable_neighbor_in_direction(*unit.voxel_pos, unit.facing.direction) { *unit.voxel_pos = target_voxel; unit.transform.translation = target_voxel.inside_voxel(); + } else { + warn!( + "Unit {:?} tried to move forward but no walkable voxel in direction {:?}", + unit.entity, unit.facing.direction + ); } } UnitAction::Work { structure_entity } => { @@ -997,8 +997,7 @@ impl CurrentAction { /// Move toward the tile this unit is facing if able pub(super) fn move_forward( - current_tile: VoxelPos, - facing: &Facing, + current_voxel: VoxelPos, map_geometry: &MapGeometry, terrain_query: &Query<&Id>, terrain_manifest: &TerrainManifest, @@ -1007,9 +1006,8 @@ impl CurrentAction { // TODO: vary this based on the path type const PATH_MULTIPLIER: f32 = 1.5; - let target_tile = current_tile.neighbor(facing.direction); - let entity_standing_on = map_geometry.get_terrain(current_tile.hex).unwrap(); - let walking_speed = if map_geometry.get_structure(current_tile).is_some() { + let entity_standing_on = map_geometry.get_terrain(current_voxel.hex).unwrap(); + let walking_speed = if map_geometry.get_structure(current_voxel).is_some() { PATH_MULTIPLIER } else { let terrain_standing_on = terrain_query.get(entity_standing_on).unwrap(); @@ -1018,14 +1016,10 @@ impl CurrentAction { let walking_duration = UnitAction::MoveForward.duration().as_secs_f32() / walking_speed; - if map_geometry.is_passable(current_tile, target_tile) { - CurrentAction { - action: UnitAction::MoveForward, - timer: Timer::from_seconds(walking_duration, TimerMode::Once), - just_started: true, - } - } else { - CurrentAction::idle() + CurrentAction { + action: UnitAction::MoveForward, + timer: Timer::from_seconds(walking_duration, TimerMode::Once), + just_started: true, } } @@ -1041,13 +1035,7 @@ impl CurrentAction { let required_direction = unit_pos.hex.main_direction_to(target_tile_pos.hex); if required_direction == facing.direction { - CurrentAction::move_forward( - unit_pos, - facing, - map_geometry, - terrain_query, - terrain_manifest, - ) + CurrentAction::move_forward(unit_pos, map_geometry, terrain_query, terrain_manifest) } else { CurrentAction::spin_towards(facing, required_direction) } @@ -1123,7 +1111,6 @@ impl CurrentAction { map_geometry: &MapGeometry, terrain_manifest: &TerrainManifest, terrain_query: &Query<&Id>, - facing: &Facing, rng: &mut ThreadRng, ) -> Self { if unit_inventory.held_item.is_some() { @@ -1132,7 +1119,6 @@ impl CurrentAction { CurrentAction::wander( previous_action, unit_pos, - facing, map_geometry, terrain_query, terrain_manifest, @@ -1147,20 +1133,15 @@ impl CurrentAction { pub(super) fn wander( previous_action: UnitAction, unit_pos: VoxelPos, - facing: &Facing, map_geometry: &MapGeometry, terrain_query: &Query<&Id>, terrain_manifest: &TerrainManifest, rng: &mut ThreadRng, ) -> Self { match previous_action { - UnitAction::Spin { .. } => CurrentAction::move_forward( - unit_pos, - facing, - map_geometry, - terrain_query, - terrain_manifest, - ), + UnitAction::Spin { .. } => { + CurrentAction::move_forward(unit_pos, map_geometry, terrain_query, terrain_manifest) + } _ => CurrentAction::random_spin(rng), } } diff --git a/emergence_lib/src/water/mod.rs b/emergence_lib/src/water/mod.rs index 783e76c29..c69828913 100644 --- a/emergence_lib/src/water/mod.rs +++ b/emergence_lib/src/water/mod.rs @@ -39,6 +39,8 @@ pub mod water_dynamics; /// Note that soil properties are stored seperately for each soil type in [`TerrainData`](crate::terrain::terrain_manifest::TerrainData). #[derive(Resource, Debug, Clone, Copy)] pub struct WaterConfig { + /// The starting amount of water in each tile. + pub initial_water: Volume, /// The rate of evaporation per day from each tile. pub evaporation_rate: Height, /// The rate of precipitation per day on each tile. @@ -66,6 +68,7 @@ pub struct WaterConfig { impl WaterConfig { /// The default configuration for in-game water behavior. pub const IN_GAME: Self = Self { + initial_water: Volume(1.5), evaporation_rate: Height(2.0), precipitation_rate: Height(2.0), emission_rate: Volume(1e4), @@ -80,8 +83,26 @@ impl WaterConfig { }, }; + /// A dry world with very little water for testing. + pub const DRY: Self = Self { + initial_water: Volume(0.2), + evaporation_rate: Height(2.0), + precipitation_rate: Height(2.0), + emission_rate: Volume(0.0), + emission_pressure: Height(5.0), + water_items_per_tile: 50.0, + lateral_flow_rate: 1e3, + enable_oceans: false, + tide_settings: TideSettings { + amplitude: Height(0.0), + period: Days(0.3), + minimum: Height(0.0), + }, + }; + /// A configuration that disables all water behavior. pub const NULL: Self = Self { + initial_water: Volume(0.0), evaporation_rate: Height(0.0), precipitation_rate: Height(0.0), emission_rate: Volume(0.0), diff --git a/emergence_lib/src/world_gen/mod.rs b/emergence_lib/src/world_gen/mod.rs index bb5dd2c62..9d722758f 100644 --- a/emergence_lib/src/world_gen/mod.rs +++ b/emergence_lib/src/world_gen/mod.rs @@ -194,6 +194,51 @@ impl GenerationConfig { } } + /// A small flat map for testing. + pub fn flat() -> Self { + let mut terrain_weights: HashMap, f32> = HashMap::new(); + // FIXME: load from file somehow + terrain_weights.insert(Id::from_name("grassy".to_string()), 1.0); + terrain_weights.insert(Id::from_name("swampy".to_string()), 0.3); + terrain_weights.insert(Id::from_name("rocky".to_string()), 0.2); + + let mut landmark_chances: HashMap, f32> = HashMap::new(); + landmark_chances.insert(Id::from_name("spring".to_string()), 5e-4); + + let mut unit_chances: HashMap, f32> = HashMap::new(); + unit_chances.insert(Id::from_name("basket_crab".to_string()), 1e-2); + + let mut structure_chances: HashMap, f32> = HashMap::new(); + structure_chances.insert(Id::from_name("ant_hive".to_string()), 1e-3); + structure_chances.insert(Id::from_name("acacia".to_string()), 2e-2); + structure_chances.insert(Id::from_name("leuco".to_string()), 1e-2); + structure_chances.insert(Id::from_name("tide_weed".to_string()), 3e-2); + + GenerationConfig { + seed: 0, + map_radius: 10, + number_of_burn_in_ticks: 0, + unit_chances, + landmark_chances, + structure_chances, + terrain_weights, + low_frequency_noise: SimplexSettings { + frequency: 1e-2, + amplitude: 0.0, + octaves: 4, + lacunarity: 1., + gain: 0.5, + }, + high_frequency_noise: SimplexSettings { + frequency: 0.1, + amplitude: 0.0, + octaves: 2, + lacunarity: 2.3, + gain: 0.5, + }, + } + } + /// A tiny world gen config for testing. pub fn testing() -> Self { let mut terrain_weights: HashMap, f32> = HashMap::new(); @@ -242,6 +287,7 @@ mod tests { use crate::asset_management::manifest::DummyManifestPlugin; use crate::geometry::{MapGeometry, VoxelPos}; use crate::simulation::rng::GlobalRng; + use crate::water::WaterConfig; use super::*; @@ -369,6 +415,7 @@ mod tests { fn can_generate_water() { let mut app = App::new(); app.insert_resource(GenerationConfig::testing()); + app.insert_resource(WaterConfig::IN_GAME); app.insert_resource(GlobalRng::new(0)); app.add_startup_systems((generate_terrain, initialize_water_table).chain()); @@ -383,6 +430,8 @@ mod tests { }) .add_plugin(DummyManifestPlugin); app.insert_resource(GlobalRng::new(0)); + app.insert_resource(WaterConfig::IN_GAME); + app.update(); let mut unit_query = app.world.query::<&Id>(); diff --git a/emergence_lib/src/world_gen/terrain_generation.rs b/emergence_lib/src/world_gen/terrain_generation.rs index f7caff511..0b75562be 100644 --- a/emergence_lib/src/world_gen/terrain_generation.rs +++ b/emergence_lib/src/world_gen/terrain_generation.rs @@ -2,7 +2,7 @@ use crate::{ asset_management::manifest::Id, - geometry::{DiscreteHeight, Facing, MapGeometry, Volume, VoxelPos}, + geometry::{DiscreteHeight, Facing, MapGeometry, VoxelPos}, organisms::energy::StartingEnergy, player_interaction::clipboard::ClipboardData, simulation::rng::GlobalRng, @@ -13,7 +13,7 @@ use crate::{ TerrainBundle, }, utils::noise::simplex_noise, - water::WaterVolume, + water::{WaterConfig, WaterVolume}, }; use bevy::prelude::*; use hexx::{shapes::hexagon, Hex}; @@ -129,10 +129,11 @@ pub(super) fn generate_landmarks( } /// Sets the starting water table -pub(super) fn initialize_water_table(mut water_query: Query<&mut WaterVolume>) { - let starting_volume = WaterVolume::new(Volume(1.5)); - +pub(super) fn initialize_water_table( + mut water_query: Query<&mut WaterVolume>, + water_config: Res, +) { for mut water_volume in water_query.iter_mut() { - *water_volume = starting_volume; + *water_volume = WaterVolume::new(water_config.initial_water); } }