Skip to content
This repository has been archived by the owner on Nov 22, 2023. It is now read-only.

Commit

Permalink
Units no longer get stuck wandering / avoiding indefinitely (#985)
Browse files Browse the repository at this point in the history
* Add more world gen configs for testing

* Test out flat terrain settings

* Revert "Test out flat terrain settings"

This reverts commit eb847ef.

* Add logging for UnitAction::MoveForward

* Add more logging to UnitAction::move_forward

* Remove redundant is_passable method and check
  • Loading branch information
alice-i-cecile authored Jun 29, 2023
1 parent d625acb commit fb8d513
Show file tree
Hide file tree
Showing 6 changed files with 100 additions and 72 deletions.
32 changes: 4 additions & 28 deletions emergence_lib/src/geometry/indexing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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!(
Expand Down
2 changes: 1 addition & 1 deletion emergence_lib/src/organisms/lifecycle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
Expand Down
55 changes: 18 additions & 37 deletions emergence_lib/src/units/actions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -122,7 +120,6 @@ pub(super) fn choose_actions(
&map_geometry,
&terrain_manifest,
&terrain_query,
facing,
rng,
)
} else {
Expand Down Expand Up @@ -159,7 +156,6 @@ pub(super) fn choose_actions(
&map_geometry,
&terrain_manifest,
&terrain_query,
facing,
rng,
)
}
Expand Down Expand Up @@ -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 } => {
Expand Down Expand Up @@ -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>>,
terrain_manifest: &TerrainManifest,
Expand All @@ -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();
Expand All @@ -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,
}
}

Expand All @@ -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)
}
Expand Down Expand Up @@ -1123,7 +1111,6 @@ impl CurrentAction {
map_geometry: &MapGeometry,
terrain_manifest: &TerrainManifest,
terrain_query: &Query<&Id<Terrain>>,
facing: &Facing,
rng: &mut ThreadRng,
) -> Self {
if unit_inventory.held_item.is_some() {
Expand All @@ -1132,7 +1119,6 @@ impl CurrentAction {
CurrentAction::wander(
previous_action,
unit_pos,
facing,
map_geometry,
terrain_query,
terrain_manifest,
Expand All @@ -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>>,
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),
}
}
Expand Down
21 changes: 21 additions & 0 deletions emergence_lib/src/water/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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),
Expand All @@ -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),
Expand Down
49 changes: 49 additions & 0 deletions emergence_lib/src/world_gen/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,51 @@ impl GenerationConfig {
}
}

/// A small flat map for testing.
pub fn flat() -> Self {
let mut terrain_weights: HashMap<Id<Terrain>, 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<Id<Structure>, f32> = HashMap::new();
landmark_chances.insert(Id::from_name("spring".to_string()), 5e-4);

let mut unit_chances: HashMap<Id<Unit>, f32> = HashMap::new();
unit_chances.insert(Id::from_name("basket_crab".to_string()), 1e-2);

let mut structure_chances: HashMap<Id<Structure>, 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<Id<Terrain>, f32> = HashMap::new();
Expand Down Expand Up @@ -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::*;

Expand Down Expand Up @@ -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());

Expand All @@ -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<Unit>>();
Expand Down
13 changes: 7 additions & 6 deletions emergence_lib/src/world_gen/terrain_generation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -13,7 +13,7 @@ use crate::{
TerrainBundle,
},
utils::noise::simplex_noise,
water::WaterVolume,
water::{WaterConfig, WaterVolume},
};
use bevy::prelude::*;
use hexx::{shapes::hexagon, Hex};
Expand Down Expand Up @@ -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<WaterConfig>,
) {
for mut water_volume in water_query.iter_mut() {
*water_volume = starting_volume;
*water_volume = WaterVolume::new(water_config.initial_water);
}
}

0 comments on commit fb8d513

Please sign in to comment.