diff --git a/src/entities/tasks/components.rs b/src/entities/tasks/components.rs index 32f54cb..43194cb 100644 --- a/src/entities/tasks/components.rs +++ b/src/entities/tasks/components.rs @@ -58,9 +58,18 @@ pub enum HaulStep { /// At cargo — picking up this tick. PickingUp, /// Cargo in HaulSlot — walking to destination. - MovingToDest, + MovingToDest { + /// Chosen drop position, computed once on first tick. + /// Stored here so Dropping uses the exact same tile — avoids + /// a second search that could return a different result. + chosen_drop: Option, + }, /// At destination — dropping this tick. - Dropping, + Dropping { + /// Exact tile returned by the MovingToDest search. Used directly + /// so cargo_tiles always matches where the entity walked. + drop_pos: IVec3, + }, /// Haul complete. Done, } @@ -69,9 +78,16 @@ pub enum HaulStep { #[derive(Clone, Debug, PartialEq)] pub enum DropStep { /// Walking to drop position. - Moving, + Moving { + /// Chosen drop position, computed once on first tick. + chosen_drop: Option, + }, /// At drop position — dropping this tick. - Dropping, + Dropping { + /// Exact tile returned by the Moving search. Used directly + /// so cargo_tiles always matches where the entity walked. + drop_pos: IVec3, + }, /// Drop complete. Done, } diff --git a/src/entities/tasks/executor.rs b/src/entities/tasks/executor.rs index 28463d1..b4d0712 100644 --- a/src/entities/tasks/executor.rs +++ b/src/entities/tasks/executor.rs @@ -9,7 +9,7 @@ //! Uses Changed + Changed to minimise queries. use crate::entities::behaviour::{EntityBehaviourRegistry, EntityType}; -use crate::entities::cargo::HaulSlot; +use crate::entities::cargo::{Cargo, HaulSlot}; use crate::entities::shared_components::Ambulatory; use crate::entities::tasks::components::{ ChopStep, DropStep, HaulStep, IdleState, Task, TaskQueue, TaskState, @@ -33,6 +33,7 @@ pub fn task_executor_system( mut rng_q: Query<&mut WyRand, With>, mut tick: Local, tree_parts: Query<(Entity, &TreePart)>, + mut cargo_query: Query<&mut Cargo>, mut query: Query<( Entity, &mut TaskQueue, @@ -124,7 +125,7 @@ pub fn task_executor_system( ambulatory.target = Some(Vec3::new( target.x as f32, target.y as f32, - transform.translation.z, + target.z as f32 + 1.0, )); ambulatory.current_path = None; } @@ -250,7 +251,7 @@ pub fn task_executor_system( } if let Some(_) = tilemap_mut.remove_cargo(cargo_pos) { haul.pick_up(*cargo_entity); - *step = HaulStep::MovingToDest; + *step = HaulStep::MovingToDest { chosen_drop: None }; } else { failed_writer.write(TaskFailed { entity, @@ -260,11 +261,16 @@ pub fn task_executor_system( *state = TaskState::Failed; } } - HaulStep::MovingToDest => { + HaulStep::MovingToDest { chosen_drop } => { + if chosen_drop.is_none() { + *chosen_drop = Some( + tilemap_mut + .find_nearest_free_cargo_tile(*dest, 8) + .unwrap_or(*dest), + ); + } + let drop_pos = chosen_drop.unwrap(); if ambulatory.target.is_none() { - let drop_pos = tilemap_mut - .find_nearest_free_cargo_tile(*dest, 8) - .unwrap_or(*dest); ambulatory.target = Some(Vec3::new( drop_pos.x as f32, drop_pos.y as f32, @@ -280,19 +286,15 @@ pub fn task_executor_system( * (crate::constants::TILE_SIZE * 1.5); if dist_sq <= arrive_sq { ambulatory.target = None; - *step = HaulStep::Dropping; + *step = HaulStep::Dropping { drop_pos }; } } - HaulStep::Dropping => { - if let Some(cargo) = haul.release() { - let drop_pos = tilemap_mut - .find_nearest_free_cargo_tile( - transform.translation.as_ivec3(), - 8, - ) - .unwrap_or(transform.translation.as_ivec3()); - let _ = tilemap_mut.place_cargo(drop_pos, cargo); - // TODO: update Cargo.tile_pos via CargoDroppedEvent + HaulStep::Dropping { drop_pos } => { + if let Some(cargo_entity) = haul.release() { + let _ = tilemap_mut.place_cargo(*drop_pos, cargo_entity); + if let Ok(mut cargo) = cargo_query.get_mut(cargo_entity) { + cargo.tile_pos = *drop_pos; + } } *step = HaulStep::Done; } @@ -308,11 +310,16 @@ pub fn task_executor_system( }; match step { - DropStep::Moving => { + DropStep::Moving { chosen_drop } => { + if chosen_drop.is_none() { + *chosen_drop = Some( + tilemap_mut + .find_nearest_free_cargo_tile(*pos, 8) + .unwrap_or(*pos), + ); + } + let drop_pos = chosen_drop.unwrap(); if ambulatory.target.is_none() { - let drop_pos = tilemap_mut - .find_nearest_free_cargo_tile(*pos, 8) - .unwrap_or(*pos); ambulatory.target = Some(Vec3::new( drop_pos.x as f32, drop_pos.y as f32, @@ -328,18 +335,15 @@ pub fn task_executor_system( * (crate::constants::TILE_SIZE * 1.5); if dist_sq <= arrive_sq { ambulatory.target = None; - *step = DropStep::Dropping; + *step = DropStep::Dropping { drop_pos }; } } - DropStep::Dropping => { - if let Some(cargo) = haul.release() { - let drop_pos = tilemap_mut - .find_nearest_free_cargo_tile( - transform.translation.as_ivec3(), - 8, - ) - .unwrap_or(transform.translation.as_ivec3()); - let _ = tilemap_mut.place_cargo(drop_pos, cargo); + DropStep::Dropping { drop_pos } => { + if let Some(cargo_entity) = haul.release() { + let _ = tilemap_mut.place_cargo(*drop_pos, cargo_entity); + if let Ok(mut cargo) = cargo_query.get_mut(cargo_entity) { + cargo.tile_pos = *drop_pos; + } } *step = DropStep::Done; } diff --git a/src/world/generation/forestry.rs b/src/world/generation/forestry.rs index fad4d07..872b36d 100644 --- a/src/world/generation/forestry.rs +++ b/src/world/generation/forestry.rs @@ -298,39 +298,24 @@ pub fn fell_tree( let trunk_y = trunk_pos.y; let canopy_radius_world = (TREE_LEAF_BASE_RADIUS * TILE_SIZE) as i32 + ITILE_SIZE; - let to_remove: SmallVec<[_; 96]> = tree_parts + let to_remove: SmallVec<[(_, IVec3, bool); 96]> = tree_parts .iter() .filter(|(_, part)| { part.chunk_pos == target_chunk && (part.tile_pos.x - trunk_x).abs() <= canopy_radius_world && (part.tile_pos.y - trunk_y).abs() <= canopy_radius_world }) - .map(|(entity, part)| (entity, part.tile_pos)) + .map(|(entity, part)| (entity, part.tile_pos, part.is_trunk)) .collect(); - // Collect unique occlusion positions across all removed tiles, then fire once each. - // Adjacent tree parts share column positions, so deduplication cuts event count - // significantly vs. firing per-tile per-column. let mut dirty_columns: FxHashSet = FxHashSet::default(); - // calculate_visibility traces up arbitrarily deep, so refresh the full column. - // z_total is constant per call — hoist above the loop. let z_total = crate::world::chunks::Z_BELOW as i32 + crate::world::chunks::Z_ABOVE as i32 + 1; - - // Collect trunk positions before despawning — we need is_trunk from TreePart - // while the entity still exists. This is why we DON'T immediately despawn - // in the to_remove mapping step. let mut trunk_positions: SmallVec<[IVec3; 8]> = SmallVec::new(); - for (entity, tile_pos) in to_remove.iter() { - // Check if this was a trunk (is_trunk stored on TreePart). - // We need to look up is_trunk before the entity is despawned. - // to_remove was collected from tree_parts query, so find it again: - if let Ok((_, part)) = tree_parts.get(*entity) { - if part.is_trunk { - trunk_positions.push(*tile_pos); - } + for (entity, tile_pos, is_trunk) in to_remove.iter() { + if *is_trunk { + trunk_positions.push(*tile_pos); } - tilemap.remove_fixture(tile_pos); tile_changed.write(TileChangedEvent { pos: *tile_pos }); commands.entity(*entity).despawn();