From 1f5dfe5182c89fe9710f6ed41ad6ef6e0d3d9391 Mon Sep 17 00:00:00 2001 From: popertots Date: Mon, 23 Mar 2026 19:31:30 +0000 Subject: [PATCH] Task fix attempt --- src/entities/cargo/components.rs | 3 + src/entities/cargo/log.rs | 1 + src/entities/cargo/mod.rs | 4 +- src/entities/cargo/systems.rs | 18 +++- src/entities/tasks/components.rs | 3 +- src/entities/tasks/demo.rs | 34 ++++++- src/entities/tasks/events.rs | 11 +++ src/entities/tasks/executor.rs | 158 ++++++++++++++++++++++++------- src/entities/tasks/mod.rs | 4 +- src/plugins/cargo.rs | 10 +- src/plugins/tasks.rs | 5 +- 11 files changed, 203 insertions(+), 48 deletions(-) diff --git a/src/entities/cargo/components.rs b/src/entities/cargo/components.rs index b1fcdac..676f882 100644 --- a/src/entities/cargo/components.rs +++ b/src/entities/cargo/components.rs @@ -49,6 +49,9 @@ pub struct Cargo { pub size: u8, /// Sprite handle for rendering when on the ground. pub ground_sprite: Handle, + /// True once this cargo has been hauled to its destination. + /// Used to prevent re-hauling logs that have already been delivered. + pub delivered: bool, } /// Marks a Cargo entity as haulable by entities with a HaulSlot. diff --git a/src/entities/cargo/log.rs b/src/entities/cargo/log.rs index 70aff81..e88d338 100644 --- a/src/entities/cargo/log.rs +++ b/src/entities/cargo/log.rs @@ -93,6 +93,7 @@ pub fn spawn_log_cargo( weight: LOG_WEIGHT_KG, size: SIZE_LARGE, ground_sprite: log_sprite.clone(), + delivered: false, }, Haulable, Sprite { diff --git a/src/entities/cargo/mod.rs b/src/entities/cargo/mod.rs index 1055e6f..e98a600 100644 --- a/src/entities/cargo/mod.rs +++ b/src/entities/cargo/mod.rs @@ -5,6 +5,8 @@ pub mod systems; pub use crate::world::tiles::tilemap::CargoPlaceError; pub use components::{Cargo, CarryVisualState, HaulSlot, Haulable}; pub use log::spawn_log_cargo; -pub use systems::{any_hauling, carry_visual_system, haul_encumbrance_system}; +pub use systems::{ + any_hauling, cargo_position_sync_system, carry_visual_system, haul_encumbrance_system, +}; pub use crate::plugins::cargo::CargoPlugin; diff --git a/src/entities/cargo/systems.rs b/src/entities/cargo/systems.rs index c5dabd3..a6cffd8 100644 --- a/src/entities/cargo/systems.rs +++ b/src/entities/cargo/systems.rs @@ -1,8 +1,9 @@ -//! Cargo systems: carry visual cycling and haul encumbrance. +//! Cargo systems: carry visual cycling, position sync, and haul encumbrance. use bevy::prelude::*; -use crate::entities::cargo::components::{CarryVisualState, HaulSlot}; +use crate::entities::cargo::components::{Cargo, CarryVisualState, HaulSlot}; +use crate::entities::item::constants::ITEM_Z_FIGHTING_OFFSET; use crate::entities::item::inventory::constants::ENCUMBERED_SPEED_MULTIPLIER; use crate::entities::item::inventory::InventoryChangedEvent; use crate::entities::shared_components::Ambulatory; @@ -60,3 +61,16 @@ pub fn haul_encumbrance_system( events.write(InventoryChangedEvent { carrier: entity }); } } + +/// Sync cargo visual position when tile_pos changes. +/// When cargo is picked up or dropped, tile_pos updates but Transform doesn't. +/// This system keeps Transform in sync with the logical position. +pub fn cargo_position_sync_system(mut query: Query<(&Cargo, &mut Transform), Changed>) { + for (cargo, mut transform) in query.iter_mut() { + transform.translation = Vec3::new( + cargo.tile_pos.x as f32, + cargo.tile_pos.y as f32, + cargo.tile_pos.z as f32 - ITEM_Z_FIGHTING_OFFSET, + ); + } +} diff --git a/src/entities/tasks/components.rs b/src/entities/tasks/components.rs index 71a4b4a..0442eac 100644 --- a/src/entities/tasks/components.rs +++ b/src/entities/tasks/components.rs @@ -55,7 +55,8 @@ pub enum ChopStep { #[derive(Clone, Debug, PartialEq)] pub enum HaulStep { /// Walking to the cargo tile. - MovingToCargo, + /// `approach` locks the target to prevent pathfinding stutter. + MovingToCargo { approach: Option }, /// At cargo — picking up this tick. PickingUp, /// Cargo in HaulSlot — walking to destination. diff --git a/src/entities/tasks/demo.rs b/src/entities/tasks/demo.rs index 1452226..3c9f546 100644 --- a/src/entities/tasks/demo.rs +++ b/src/entities/tasks/demo.rs @@ -30,7 +30,7 @@ use crate::entities::cargo::{Cargo, HaulSlot, Haulable}; use crate::entities::tasks::components::{ ChopStep, HaulStep, Task, TaskQueue, TaskState, CHOP_TICKS_DEFAULT, }; -use crate::entities::tasks::events::TaskFailed; +use crate::entities::tasks::events::{LogsSpawned, TaskFailed}; use crate::world::chunks::ChunkMap; use crate::world::generation::forestry::TreePart; use crate::world::tiles::TileMap; @@ -84,6 +84,7 @@ pub fn demo_system( haul_slot_query: Query<&HaulSlot>, mut dorf_query: Query<(Entity, &mut TaskQueue, &mut TaskState, &Transform)>, mut task_failed: MessageReader, + mut logs_spawned: MessageReader, ) { // Handle task failures that should reset the demo state for retry for event in task_failed.read() { @@ -98,6 +99,35 @@ pub fn demo_system( } } + // Handle logs spawned from felling — transition to Hauling + // Collect events first to avoid double-mutable borrow conflict with demo_state + let pending_logs: Vec<_> = logs_spawned.read().collect(); + for event in pending_logs { + if let DemoState::Chopping { + trunk_pos, + chopper: _, + } = *demo_state + { + // Look up cargo positions from the newly spawned entities + let mut unassigned: VecDeque<(Entity, IVec3)> = VecDeque::new(); + for &log_entity in event.log_entities.iter() { + if let Ok((_, cargo)) = cargo_query.get(log_entity) { + unassigned.push_back((log_entity, cargo.tile_pos)); + } + } + info!( + "[DEMO] → Hauling: {} logs spawned from tree at {:?}", + unassigned.len(), + trunk_pos + ); + *demo_state = DemoState::Hauling { + felled_trunk_pos: trunk_pos, + unassigned, + in_progress: Default::default(), + }; + } + } + match &mut *demo_state { DemoState::Idle => { // Find the nearest standing tree to (0,0). @@ -345,7 +375,7 @@ pub fn demo_system( cargo_entity: log_entity, cargo_pos: log_pos, dest, - step: HaulStep::MovingToCargo, + step: HaulStep::MovingToCargo { approach: None }, }); *state = TaskState::Pending; in_progress.insert(dorf_entity); diff --git a/src/entities/tasks/events.rs b/src/entities/tasks/events.rs index 888335b..315ae71 100644 --- a/src/entities/tasks/events.rs +++ b/src/entities/tasks/events.rs @@ -6,6 +6,7 @@ use crate::entities::tasks::components::Task; use bevy::prelude::*; +use smallvec::SmallVec; #[derive(Message, Clone)] pub struct TaskClaimed { @@ -38,3 +39,13 @@ pub struct TaskBlocked { pub task: Task, pub blocker: &'static str, } + +/// Fired when logs are spawned from felling a tree. +/// The demo listens for this to queue HaulCargo tasks. +#[derive(Message, Clone)] +pub struct LogsSpawned { + /// Entities of the spawned logs. + pub log_entities: SmallVec<[Entity; 8]>, + /// Destination for hauling (currently origin, later stockpile). + pub dest: IVec3, +} diff --git a/src/entities/tasks/executor.rs b/src/entities/tasks/executor.rs index 61a9782..a64e153 100644 --- a/src/entities/tasks/executor.rs +++ b/src/entities/tasks/executor.rs @@ -14,7 +14,7 @@ use crate::entities::shared_components::Ambulatory; use crate::entities::tasks::components::{ ChopStep, DropStep, HaulStep, IdleState, Task, TaskQueue, TaskState, }; -use crate::entities::tasks::events::{TaskClaimed, TaskCompleted, TaskFailed}; +use crate::entities::tasks::events::{LogsSpawned, TaskClaimed, TaskCompleted, TaskFailed}; use crate::entities::tasks::idle::execute_idle; use crate::world::chunks::ChunkMap; use crate::world::generation::forestry::{fell_tree, TreePart}; @@ -23,6 +23,7 @@ use crate::world::tiles::visibility::TileOcclusionEvent; use crate::world::tiles::TileMap; use bevy::prelude::*; use bevy_rand::prelude::*; +use smallvec::SmallVec; /// Main task executor. Runs in FixedUpdate. pub fn task_executor_system( @@ -47,6 +48,7 @@ pub fn task_executor_system( mut claimed_writer: MessageWriter, mut completed_writer: MessageWriter, mut failed_writer: MessageWriter, + mut logs_spawned_writer: MessageWriter, mut tile_changed: MessageWriter, mut occlusion: MessageWriter, ) { @@ -241,15 +243,27 @@ pub fn task_executor_system( fall_dir = Vec2::new(1.0, 0.0); // default: fall east } let log_sprite: Handle = asset_server.load("log_cargo.png"); + let mut log_entities: SmallVec<[Entity; 8]> = SmallVec::new(); for &pos in trunk_positions.iter() { - crate::entities::cargo::spawn_log_cargo( - &mut commands, - &mut tilemap_mut, - pos, - fall_dir, - log_sprite.clone(), - &mut rng, - ); + if let Some(log_entity) = + crate::entities::cargo::spawn_log_cargo( + &mut commands, + &mut tilemap_mut, + pos, + fall_dir, + log_sprite.clone(), + &mut rng, + ) + { + log_entities.push(log_entity); + } + } + // Emit event so demo can queue HaulCargo tasks for these logs + if !log_entities.is_empty() { + logs_spawned_writer.write(LogsSpawned { + log_entities, + dest: IVec3::ZERO, + }); } *step = ChopStep::Done; } else { @@ -277,7 +291,9 @@ pub fn task_executor_system( }; match step { - HaulStep::MovingToCargo => { + HaulStep::MovingToCargo { approach } => { + use crate::constants::ITILE_SIZE; + if !tilemap_mut.cargo_tiles.contains_key(cargo_pos) { failed_writer.write(TaskFailed { entity, @@ -287,22 +303,75 @@ pub fn task_executor_system( *state = TaskState::Failed; continue; } - if ambulatory.target.is_none() { - ambulatory.target = Some(Vec3::new( - cargo_pos.x as f32, - cargo_pos.y as f32, - cargo_pos.z as f32 + 1.0, - )); - ambulatory.current_path = None; + + if approach.is_none() { + // Search for nearest standable tile to approach cargo. + // Radius 0 = cargo tile itself (can stand in same tile as cargo). + // Radius 1-2 = adjacent tiles if cargo tile is blocked. + let cargo_z = cargo_pos.z; + let approach_tile = (0..=2i32).find_map(|radius: i32| { + for dx in -radius..=radius { + for dy in -radius..=radius { + if radius > 0 + && dx.abs() != radius + && dy.abs() != radius + { + continue; + } + let candidate = IVec3::new( + cargo_pos.x + dx * ITILE_SIZE, + cargo_pos.y + dy * ITILE_SIZE, + cargo_z, + ); + if tilemap_mut.is_standable(candidate) { + return Some(candidate); + } + } + } + None + }); + + match approach_tile { + Some(tile) => { + *approach = Some(tile); + info!( + "[HAUL] {:?} target set: cargo={:?} approach={:?}", + entity, cargo_pos, tile + ); + // +1.0 z-offset for entity standing height (same as trees) + ambulatory.target = Some(Vec3::new( + tile.x as f32, + tile.y as f32, + tile.z as f32 + 1.0, + )); + ambulatory.current_path = None; + } + None => { + failed_writer.write(TaskFailed { + entity, + task: current_task.clone(), + reason: "no standable tile near cargo", + }); + *state = TaskState::Failed; + continue; + } + } } - let dx = transform.translation.x - cargo_pos.x as f32; - let dy = transform.translation.y - cargo_pos.y as f32; - let dist_sq = dx * dx + dy * dy; - let pickup_range_sq = (crate::constants::TILE_SIZE * 1.5) - * (crate::constants::TILE_SIZE * 1.5); - if dist_sq <= pickup_range_sq { - ambulatory.target = None; - *step = HaulStep::PickingUp; + + // Check arrival at cargo tile + if approach.is_some() && ambulatory.target.is_none() { + let dx = transform.translation.x - cargo_pos.x as f32; + let dy = transform.translation.y - cargo_pos.y as f32; + let dist_sq = dx * dx + dy * dy; + let pickup_range_sq = + (ITILE_SIZE as f32 * 1.5) * (ITILE_SIZE as f32 * 1.5); + if dist_sq <= pickup_range_sq { + info!( + "[HAUL] {:?} arrived at cargo {:?}, picking up", + entity, cargo_entity + ); + *step = HaulStep::PickingUp; + } } } HaulStep::PickingUp => { @@ -317,6 +386,10 @@ pub fn task_executor_system( } if let Some(_) = tilemap_mut.remove_cargo(cargo_pos) { haul.pick_up(*cargo_entity); + info!( + "[HAUL] {:?} picked up {:?} → hauling to {:?}", + entity, cargo_entity, dest + ); *step = HaulStep::MovingToDest { chosen_drop: None }; } else { failed_writer.write(TaskFailed { @@ -337,6 +410,8 @@ pub fn task_executor_system( } let drop_pos = chosen_drop.unwrap(); if ambulatory.target.is_none() { + info!("[HAUL] {:?} target set: drop at {:?}", entity, drop_pos); + // +1.0 z-offset for entity standing height (same as approach) ambulatory.target = Some(Vec3::new( drop_pos.x as f32, drop_pos.y as f32, @@ -344,19 +419,28 @@ pub fn task_executor_system( )); ambulatory.current_path = None; } - let target_pos = ambulatory.target.unwrap_or(Vec3::ZERO); - let dx = transform.translation.x - target_pos.x; - let dy = transform.translation.y - target_pos.y; + + // Always check arrival distance, not gated by target status + let dx = transform.translation.x - drop_pos.x as f32; + let dy = transform.translation.y - drop_pos.y as f32; let dist_sq = dx * dx + dy * dy; - let arrive_sq = (crate::constants::TILE_SIZE * 1.5) - * (crate::constants::TILE_SIZE * 1.5); + let arrive_sq = (crate::constants::TILE_SIZE as f32 * 1.5) + * (crate::constants::TILE_SIZE as f32 * 1.5); if dist_sq <= arrive_sq { ambulatory.target = None; + info!( + "[HAUL] {:?} arrived at drop point {:?}", + entity, drop_pos + ); *step = HaulStep::Dropping { drop_pos }; } } HaulStep::Dropping { drop_pos } => { if let Some(cargo_entity) = haul.release() { + info!( + "[HAUL] {:?} dropped {:?} at {:?}", + entity, cargo_entity, drop_pos + ); if tilemap_mut.place_cargo(*drop_pos, cargo_entity).is_err() { warn!( "place_cargo failed at {:?} — tile occupied. \ @@ -371,6 +455,7 @@ pub fn task_executor_system( *step = HaulStep::Done; } HaulStep::Done => { + info!("[HAUL] {:?} haul task COMPLETE", entity); *state = TaskState::Completed; } } @@ -395,16 +480,17 @@ pub fn task_executor_system( ambulatory.target = Some(Vec3::new( drop_pos.x as f32, drop_pos.y as f32, - drop_pos.z as f32 + 1.0, + drop_pos.z as f32, )); ambulatory.current_path = None; } - let target_pos = ambulatory.target.unwrap_or(Vec3::ZERO); - let dx = transform.translation.x - target_pos.x; - let dy = transform.translation.y - target_pos.y; + + // Always check arrival distance, not gated by target status + let dx = transform.translation.x - drop_pos.x as f32; + let dy = transform.translation.y - drop_pos.y as f32; let dist_sq = dx * dx + dy * dy; - let arrive_sq = (crate::constants::TILE_SIZE * 1.5) - * (crate::constants::TILE_SIZE * 1.5); + let arrive_sq = (crate::constants::TILE_SIZE as f32 * 1.5) + * (crate::constants::TILE_SIZE as f32 * 1.5); if dist_sq <= arrive_sq { ambulatory.target = None; *step = DropStep::Dropping { drop_pos }; diff --git a/src/entities/tasks/mod.rs b/src/entities/tasks/mod.rs index 57bdba5..9f6e1d2 100644 --- a/src/entities/tasks/mod.rs +++ b/src/entities/tasks/mod.rs @@ -5,10 +5,10 @@ pub mod executor; pub mod idle; pub use components::{IdleState, Task, TaskQueue, TaskState}; -pub use demo::{demo_system, DemoState}; #[cfg(debug_assertions)] pub use demo::debug_task_queues; -pub use events::{TaskBlocked, TaskClaimed, TaskCompleted, TaskDropped, TaskFailed}; +pub use demo::{demo_system, DemoState}; +pub use events::{LogsSpawned, TaskBlocked, TaskClaimed, TaskCompleted, TaskDropped, TaskFailed}; pub use executor::task_executor_system; pub use crate::plugins::tasks::TasksPlugin; diff --git a/src/plugins/cargo.rs b/src/plugins/cargo.rs index e517268..9396d03 100644 --- a/src/plugins/cargo.rs +++ b/src/plugins/cargo.rs @@ -4,7 +4,9 @@ //! - haul_encumbrance_system (FixedUpdate, before update_encumbrance) //! - carry_visual_system (Update, run_if any_hauling) -use crate::entities::cargo::{any_hauling, carry_visual_system, haul_encumbrance_system}; +use crate::entities::cargo::{ + any_hauling, cargo_position_sync_system, carry_visual_system, haul_encumbrance_system, +}; use bevy::prelude::*; pub struct CargoPlugin; @@ -13,7 +15,11 @@ impl Plugin for CargoPlugin { fn build(&self, app: &mut App) { app.add_systems( FixedUpdate, - haul_encumbrance_system.before(crate::entities::item::inventory::update_encumbrance), + ( + haul_encumbrance_system + .before(crate::entities::item::inventory::update_encumbrance), + cargo_position_sync_system, + ), ) .add_systems(Update, carry_visual_system.run_if(any_hauling)); } diff --git a/src/plugins/tasks.rs b/src/plugins/tasks.rs index 5945e7f..2d8eaf6 100644 --- a/src/plugins/tasks.rs +++ b/src/plugins/tasks.rs @@ -6,8 +6,8 @@ //! - debug_task_queues (FixedUpdate, after demo_system, debug only) use crate::entities::tasks::{ - demo_system, task_executor_system, DemoState, TaskBlocked, TaskClaimed, TaskCompleted, - TaskDropped, TaskFailed, + demo_system, task_executor_system, DemoState, LogsSpawned, TaskBlocked, TaskClaimed, + TaskCompleted, TaskDropped, TaskFailed, }; use bevy::prelude::*; @@ -20,6 +20,7 @@ impl Plugin for TasksPlugin { .add_message::() .add_message::() .add_message::() + .add_message::() .init_resource::() .add_systems( bevy::app::FixedUpdate,