Task fix attempt
This commit is contained in:
@@ -49,6 +49,9 @@ pub struct Cargo {
|
||||
pub size: u8,
|
||||
/// Sprite handle for rendering when on the ground.
|
||||
pub ground_sprite: Handle<Image>,
|
||||
/// 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.
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<Cargo>>) {
|
||||
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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<IVec3> },
|
||||
/// At cargo — picking up this tick.
|
||||
PickingUp,
|
||||
/// Cargo in HaulSlot — walking to destination.
|
||||
|
||||
@@ -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<TaskFailed>,
|
||||
mut logs_spawned: MessageReader<LogsSpawned>,
|
||||
) {
|
||||
// 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);
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
+107
-21
@@ -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<TaskClaimed>,
|
||||
mut completed_writer: MessageWriter<TaskCompleted>,
|
||||
mut failed_writer: MessageWriter<TaskFailed>,
|
||||
mut logs_spawned_writer: MessageWriter<LogsSpawned>,
|
||||
mut tile_changed: MessageWriter<TileChangedEvent>,
|
||||
mut occlusion: MessageWriter<TileOcclusionEvent>,
|
||||
) {
|
||||
@@ -241,7 +243,9 @@ pub fn task_executor_system(
|
||||
fall_dir = Vec2::new(1.0, 0.0); // default: fall east
|
||||
}
|
||||
let log_sprite: Handle<Image> = asset_server.load("log_cargo.png");
|
||||
let mut log_entities: SmallVec<[Entity; 8]> = SmallVec::new();
|
||||
for &pos in trunk_positions.iter() {
|
||||
if let Some(log_entity) =
|
||||
crate::entities::cargo::spawn_log_cargo(
|
||||
&mut commands,
|
||||
&mut tilemap_mut,
|
||||
@@ -249,7 +253,17 @@ pub fn task_executor_system(
|
||||
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,24 +303,77 @@ pub fn task_executor_system(
|
||||
*state = TaskState::Failed;
|
||||
continue;
|
||||
}
|
||||
if ambulatory.target.is_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(
|
||||
cargo_pos.x as f32,
|
||||
cargo_pos.y as f32,
|
||||
cargo_pos.z as f32 + 1.0,
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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 = (crate::constants::TILE_SIZE * 1.5)
|
||||
* (crate::constants::TILE_SIZE * 1.5);
|
||||
let pickup_range_sq =
|
||||
(ITILE_SIZE as f32 * 1.5) * (ITILE_SIZE as f32 * 1.5);
|
||||
if dist_sq <= pickup_range_sq {
|
||||
ambulatory.target = None;
|
||||
info!(
|
||||
"[HAUL] {:?} arrived at cargo {:?}, picking up",
|
||||
entity, cargo_entity
|
||||
);
|
||||
*step = HaulStep::PickingUp;
|
||||
}
|
||||
}
|
||||
}
|
||||
HaulStep::PickingUp => {
|
||||
if haul.is_occupied() {
|
||||
failed_writer.write(TaskFailed {
|
||||
@@ -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 };
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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::<TaskFailed>()
|
||||
.add_message::<TaskDropped>()
|
||||
.add_message::<TaskBlocked>()
|
||||
.add_message::<LogsSpawned>()
|
||||
.init_resource::<DemoState>()
|
||||
.add_systems(
|
||||
bevy::app::FixedUpdate,
|
||||
|
||||
Reference in New Issue
Block a user