This commit is contained in:
2026-03-22 14:34:59 +00:00
parent 57e377c093
commit 54eabe85e2
3 changed files with 61 additions and 56 deletions
+20 -4
View File
@@ -58,9 +58,18 @@ pub enum HaulStep {
/// At cargo — picking up this tick. /// At cargo — picking up this tick.
PickingUp, PickingUp,
/// Cargo in HaulSlot — walking to destination. /// 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<IVec3>,
},
/// At destination — dropping this tick. /// 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. /// Haul complete.
Done, Done,
} }
@@ -69,9 +78,16 @@ pub enum HaulStep {
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub enum DropStep { pub enum DropStep {
/// Walking to drop position. /// Walking to drop position.
Moving, Moving {
/// Chosen drop position, computed once on first tick.
chosen_drop: Option<IVec3>,
},
/// At drop position — dropping this tick. /// 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. /// Drop complete.
Done, Done,
} }
+36 -32
View File
@@ -9,7 +9,7 @@
//! Uses Changed<TaskQueue> + Changed<TaskState> to minimise queries. //! Uses Changed<TaskQueue> + Changed<TaskState> to minimise queries.
use crate::entities::behaviour::{EntityBehaviourRegistry, EntityType}; 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::shared_components::Ambulatory;
use crate::entities::tasks::components::{ use crate::entities::tasks::components::{
ChopStep, DropStep, HaulStep, IdleState, Task, TaskQueue, TaskState, ChopStep, DropStep, HaulStep, IdleState, Task, TaskQueue, TaskState,
@@ -33,6 +33,7 @@ pub fn task_executor_system(
mut rng_q: Query<&mut WyRand, With<GlobalRng>>, mut rng_q: Query<&mut WyRand, With<GlobalRng>>,
mut tick: Local<u32>, mut tick: Local<u32>,
tree_parts: Query<(Entity, &TreePart)>, tree_parts: Query<(Entity, &TreePart)>,
mut cargo_query: Query<&mut Cargo>,
mut query: Query<( mut query: Query<(
Entity, Entity,
&mut TaskQueue, &mut TaskQueue,
@@ -124,7 +125,7 @@ pub fn task_executor_system(
ambulatory.target = Some(Vec3::new( ambulatory.target = Some(Vec3::new(
target.x as f32, target.x as f32,
target.y as f32, target.y as f32,
transform.translation.z, target.z as f32 + 1.0,
)); ));
ambulatory.current_path = None; ambulatory.current_path = None;
} }
@@ -250,7 +251,7 @@ pub fn task_executor_system(
} }
if let Some(_) = tilemap_mut.remove_cargo(cargo_pos) { if let Some(_) = tilemap_mut.remove_cargo(cargo_pos) {
haul.pick_up(*cargo_entity); haul.pick_up(*cargo_entity);
*step = HaulStep::MovingToDest; *step = HaulStep::MovingToDest { chosen_drop: None };
} else { } else {
failed_writer.write(TaskFailed { failed_writer.write(TaskFailed {
entity, entity,
@@ -260,11 +261,16 @@ pub fn task_executor_system(
*state = TaskState::Failed; *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() { 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( ambulatory.target = Some(Vec3::new(
drop_pos.x as f32, drop_pos.x as f32,
drop_pos.y as f32, drop_pos.y as f32,
@@ -280,19 +286,15 @@ pub fn task_executor_system(
* (crate::constants::TILE_SIZE * 1.5); * (crate::constants::TILE_SIZE * 1.5);
if dist_sq <= arrive_sq { if dist_sq <= arrive_sq {
ambulatory.target = None; ambulatory.target = None;
*step = HaulStep::Dropping; *step = HaulStep::Dropping { drop_pos };
} }
} }
HaulStep::Dropping => { HaulStep::Dropping { drop_pos } => {
if let Some(cargo) = haul.release() { if let Some(cargo_entity) = haul.release() {
let drop_pos = tilemap_mut let _ = tilemap_mut.place_cargo(*drop_pos, cargo_entity);
.find_nearest_free_cargo_tile( if let Ok(mut cargo) = cargo_query.get_mut(cargo_entity) {
transform.translation.as_ivec3(), cargo.tile_pos = *drop_pos;
8, }
)
.unwrap_or(transform.translation.as_ivec3());
let _ = tilemap_mut.place_cargo(drop_pos, cargo);
// TODO: update Cargo.tile_pos via CargoDroppedEvent
} }
*step = HaulStep::Done; *step = HaulStep::Done;
} }
@@ -308,11 +310,16 @@ pub fn task_executor_system(
}; };
match step { 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() { 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( ambulatory.target = Some(Vec3::new(
drop_pos.x as f32, drop_pos.x as f32,
drop_pos.y as f32, drop_pos.y as f32,
@@ -328,18 +335,15 @@ pub fn task_executor_system(
* (crate::constants::TILE_SIZE * 1.5); * (crate::constants::TILE_SIZE * 1.5);
if dist_sq <= arrive_sq { if dist_sq <= arrive_sq {
ambulatory.target = None; ambulatory.target = None;
*step = DropStep::Dropping; *step = DropStep::Dropping { drop_pos };
} }
} }
DropStep::Dropping => { DropStep::Dropping { drop_pos } => {
if let Some(cargo) = haul.release() { if let Some(cargo_entity) = haul.release() {
let drop_pos = tilemap_mut let _ = tilemap_mut.place_cargo(*drop_pos, cargo_entity);
.find_nearest_free_cargo_tile( if let Ok(mut cargo) = cargo_query.get_mut(cargo_entity) {
transform.translation.as_ivec3(), cargo.tile_pos = *drop_pos;
8, }
)
.unwrap_or(transform.translation.as_ivec3());
let _ = tilemap_mut.place_cargo(drop_pos, cargo);
} }
*step = DropStep::Done; *step = DropStep::Done;
} }
+5 -20
View File
@@ -298,39 +298,24 @@ pub fn fell_tree(
let trunk_y = trunk_pos.y; let trunk_y = trunk_pos.y;
let canopy_radius_world = (TREE_LEAF_BASE_RADIUS * TILE_SIZE) as i32 + ITILE_SIZE; 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() .iter()
.filter(|(_, part)| { .filter(|(_, part)| {
part.chunk_pos == target_chunk part.chunk_pos == target_chunk
&& (part.tile_pos.x - trunk_x).abs() <= canopy_radius_world && (part.tile_pos.x - trunk_x).abs() <= canopy_radius_world
&& (part.tile_pos.y - trunk_y).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();
// 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<IVec3> = FxHashSet::default(); let mut dirty_columns: FxHashSet<IVec3> = 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; 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(); let mut trunk_positions: SmallVec<[IVec3; 8]> = SmallVec::new();
for (entity, tile_pos) in to_remove.iter() { for (entity, tile_pos, is_trunk) in to_remove.iter() {
// Check if this was a trunk (is_trunk stored on TreePart). if *is_trunk {
// We need to look up is_trunk before the entity is despawned. trunk_positions.push(*tile_pos);
// 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);
}
} }
tilemap.remove_fixture(tile_pos); tilemap.remove_fixture(tile_pos);
tile_changed.write(TileChangedEvent { pos: *tile_pos }); tile_changed.write(TileChangedEvent { pos: *tile_pos });
commands.entity(*entity).despawn(); commands.entity(*entity).despawn();