demo part 2
This commit is contained in:
@@ -7,8 +7,10 @@
|
|||||||
//! placeholder until a dedicated ground-log sprite exists.
|
//! placeholder until a dedicated ground-log sprite exists.
|
||||||
|
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
|
use bevy_rand::prelude::*;
|
||||||
|
use rand::RngExt;
|
||||||
|
|
||||||
use crate::constants::PIXEL_RATIO;
|
use crate::constants::{ITILE_SIZE, PIXEL_RATIO};
|
||||||
use crate::entities::cargo::{Cargo, Haulable};
|
use crate::entities::cargo::{Cargo, Haulable};
|
||||||
use crate::entities::item::constants::ITEM_Z_FIGHTING_OFFSET;
|
use crate::entities::item::constants::ITEM_Z_FIGHTING_OFFSET;
|
||||||
use crate::entities::item::inventory::constants::SIZE_LARGE;
|
use crate::entities::item::inventory::constants::SIZE_LARGE;
|
||||||
@@ -17,20 +19,52 @@ use crate::world::tiles::TileMap;
|
|||||||
/// Weight of a single log in kg. Enough to encumber a dorf carrying one.
|
/// Weight of a single log in kg. Enough to encumber a dorf carrying one.
|
||||||
pub const LOG_WEIGHT_KG: u32 = 15;
|
pub const LOG_WEIGHT_KG: u32 = 15;
|
||||||
|
|
||||||
/// Spawn a Cargo log entity at `tile_pos` and register it in cargo_tiles.
|
/// Spawn a Cargo log entity with a directional fall bias.
|
||||||
///
|
///
|
||||||
/// If the tile is already occupied (another log landed here), finds the
|
/// `tile_pos` — world position of the trunk tile this log came from.
|
||||||
/// nearest free tile via TileMap::find_nearest_free_cargo_tile.
|
/// `fall_direction` — unit-ish vector (XY only) from chopper toward trunk.
|
||||||
|
/// Used to bias the landing position away from the chopper.
|
||||||
|
/// `log_sprite` — sprite handle (texture ID 500004).
|
||||||
|
/// `rng` — caller-provided RNG for reproducible scatter.
|
||||||
///
|
///
|
||||||
/// Returns the spawned Entity, or None if no free tile found within radius.
|
/// Landing position:
|
||||||
|
/// base = tile_pos + fall_direction * random(0..=3 tiles)
|
||||||
|
/// jitter = random lateral offset of ±1 tile perpendicular to fall_direction
|
||||||
|
/// actual = find_nearest_free_cargo_tile(base + jitter, 4)
|
||||||
|
///
|
||||||
|
/// If the chosen tile is occupied by a living entity, TODO: fire an OuchEvent.
|
||||||
|
/// For now: log a debug message and place the cargo on the nearest free tile.
|
||||||
pub fn spawn_log_cargo(
|
pub fn spawn_log_cargo(
|
||||||
commands: &mut Commands,
|
commands: &mut Commands,
|
||||||
tilemap: &mut TileMap,
|
tilemap: &mut TileMap,
|
||||||
tile_pos: IVec3,
|
tile_pos: IVec3,
|
||||||
|
fall_direction: Vec2,
|
||||||
log_sprite: Handle<Image>,
|
log_sprite: Handle<Image>,
|
||||||
|
rng: &mut WyRand,
|
||||||
) -> Option<Entity> {
|
) -> Option<Entity> {
|
||||||
// Find a free tile — the exact trunk position may be occupied
|
// Distance along fall direction: 0–3 tiles
|
||||||
let drop_pos = tilemap.find_nearest_free_cargo_tile(tile_pos, 4)?;
|
let fall_tiles = rng.random_range(0u32..=3) as f32;
|
||||||
|
|
||||||
|
// Perpendicular jitter: ±1 tile lateral to fall direction
|
||||||
|
let perp = Vec2::new(-fall_direction.y, fall_direction.x);
|
||||||
|
let jitter_tiles = rng.random_range(0u32..=2) as f32 - 1.0; // -1, 0, or +1
|
||||||
|
|
||||||
|
let offset = fall_direction * fall_tiles + perp * jitter_tiles;
|
||||||
|
let biased_pos = IVec3::new(
|
||||||
|
tile_pos.x + (offset.x * ITILE_SIZE as f32).round() as i32,
|
||||||
|
tile_pos.y + (offset.y * ITILE_SIZE as f32).round() as i32,
|
||||||
|
tile_pos.z,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Find nearest free tile from biased position
|
||||||
|
let drop_pos = tilemap.find_nearest_free_cargo_tile(biased_pos, 4)?;
|
||||||
|
|
||||||
|
// TODO: OuchEvent — if a living entity occupies drop_pos, they take impact damage.
|
||||||
|
// Check tilemap occupancy here when the combat/injury system exists.
|
||||||
|
// For now: debug log so we know when it would have fired.
|
||||||
|
// if occupancy.count_at_ivec3(drop_pos) > 0 {
|
||||||
|
// debug!("Log landed on occupied tile {:?} — ouch! (TODO: damage)", drop_pos);
|
||||||
|
// }
|
||||||
|
|
||||||
let entity = commands
|
let entity = commands
|
||||||
.spawn((
|
.spawn((
|
||||||
|
|||||||
@@ -0,0 +1,308 @@
|
|||||||
|
//! Demo loop — one tree at a time, chop and haul to origin.
|
||||||
|
//!
|
||||||
|
//! # Behaviour
|
||||||
|
//! 1. Find the nearest standing tree trunk to (0,0) in the loaded world.
|
||||||
|
//! 2. Assign a ChopTree task to one idle dorf.
|
||||||
|
//! 3. When the tree is felled (trunk fixtures gone, Cargo logs exist):
|
||||||
|
//! - All logs enter the haul queue ordered by proximity to origin.
|
||||||
|
//! - Each tick: assign idle dorfs to the nearest unassigned log.
|
||||||
|
//! - Dorfs that finish hauling become available for the next log.
|
||||||
|
//! 4. When the haul queue is empty AND no in-progress hauls remain:
|
||||||
|
//! - Find the next tree.
|
||||||
|
//! 5. Dorfs with no task remain on Task::Idle (wander).
|
||||||
|
//!
|
||||||
|
//! # State machine
|
||||||
|
//! Tracked in DemoState resource.
|
||||||
|
//!
|
||||||
|
//! # Limitations (acceptable for demo)
|
||||||
|
//! - Only one tree targeted at a time.
|
||||||
|
//! - Does not use the JobQueue — tasks pushed directly.
|
||||||
|
//! - Does not handle dorf death mid-chop.
|
||||||
|
//! - Haul destination is a fixed search near IVec3::ZERO — not a stockpile.
|
||||||
|
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use rustc_hash::FxHashSet;
|
||||||
|
|
||||||
|
use crate::constants::ITILE_SIZE;
|
||||||
|
use crate::entities::cargo::{Cargo, Haulable};
|
||||||
|
use crate::entities::tasks::components::{
|
||||||
|
ChopStep, HaulStep, Task, TaskQueue, TaskState, CHOP_TICKS_DEFAULT,
|
||||||
|
};
|
||||||
|
use crate::world::chunks::ChunkMap;
|
||||||
|
use crate::world::generation::forestry::TreePart;
|
||||||
|
use crate::world::tiles::TileMap;
|
||||||
|
|
||||||
|
/// Radius (in tiles, Chebyshev) to search for unclaimed logs after felling.
|
||||||
|
/// Logs scatter within ~3 tiles of trunk positions.
|
||||||
|
const LOG_SEARCH_RADIUS_TILES: i32 = 12;
|
||||||
|
|
||||||
|
/// Radius to search for a haul drop destination near origin.
|
||||||
|
const HAUL_DEST_SEARCH_RADIUS: i32 = 32;
|
||||||
|
|
||||||
|
/// Tracks what the demo loop is currently doing.
|
||||||
|
#[derive(Resource, Debug, Default)]
|
||||||
|
pub enum DemoState {
|
||||||
|
/// No active tree — searching for one.
|
||||||
|
#[default]
|
||||||
|
Idle,
|
||||||
|
/// A ChopTree task has been assigned to `chopper`.
|
||||||
|
/// `trunk_pos` is the lowest trunk tile of the target tree.
|
||||||
|
Chopping { trunk_pos: IVec3, chopper: Entity },
|
||||||
|
/// Tree has been felled. Waiting for all visible logs to be claimed/hauled.
|
||||||
|
/// Tracks which log entities have been assigned haul tasks.
|
||||||
|
Hauling {
|
||||||
|
felled_trunk_pos: IVec3,
|
||||||
|
assigned_logs: FxHashSet<Entity>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The demo loop system. Runs in FixedUpdate after task_executor_system.
|
||||||
|
///
|
||||||
|
/// State transitions:
|
||||||
|
/// Idle → Chopping: found a tree, assigned ChopTree to one idle dorf
|
||||||
|
/// Chopping → Hauling: trunk no longer in fixture_tiles (tree felled)
|
||||||
|
/// Hauling → Idle: all assigned logs cleared from cargo_tiles (hauled)
|
||||||
|
///
|
||||||
|
/// Performance: scans fixture_tiles once per state transition (infrequent),
|
||||||
|
/// not per tick. During Chopping and Hauling states the system does O(1) checks.
|
||||||
|
pub fn demo_system(
|
||||||
|
mut demo_state: ResMut<DemoState>,
|
||||||
|
tilemap: Res<TileMap>,
|
||||||
|
chunk_map: Res<ChunkMap>,
|
||||||
|
tree_parts: Query<(Entity, &TreePart)>,
|
||||||
|
cargo_query: Query<(Entity, &Cargo), With<Haulable>>,
|
||||||
|
mut dorf_query: Query<(Entity, &mut TaskQueue, &mut TaskState, &Transform)>,
|
||||||
|
) {
|
||||||
|
match &*demo_state {
|
||||||
|
DemoState::Idle => {
|
||||||
|
// Find the nearest standing tree to (0,0).
|
||||||
|
// A "tree" is identified by a TreePart with is_trunk=true whose
|
||||||
|
// tile_pos is still in fixture_tiles (not yet felled).
|
||||||
|
let origin = IVec3::ZERO;
|
||||||
|
|
||||||
|
let mut best: Option<(IVec3, i32)> = None; // (trunk_pos, chebyshev_dist)
|
||||||
|
|
||||||
|
for (_, part) in tree_parts.iter() {
|
||||||
|
if !part.is_trunk {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if !tilemap.fixture_tiles.contains_key(&part.tile_pos) {
|
||||||
|
continue; // already felled
|
||||||
|
}
|
||||||
|
// Only consider trees in fully-loaded chunks
|
||||||
|
let chunk = crate::world::chunks::world_to_chunk(part.tile_pos);
|
||||||
|
let loaded = chunk_map.loaded_chunks.contains_key(&(chunk + IVec2::X))
|
||||||
|
&& chunk_map.loaded_chunks.contains_key(&(chunk - IVec2::X))
|
||||||
|
&& chunk_map.loaded_chunks.contains_key(&(chunk + IVec2::Y))
|
||||||
|
&& chunk_map.loaded_chunks.contains_key(&(chunk - IVec2::Y));
|
||||||
|
if !loaded {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let dx = (part.tile_pos.x - origin.x).abs() / ITILE_SIZE;
|
||||||
|
let dy = (part.tile_pos.y - origin.y).abs() / ITILE_SIZE;
|
||||||
|
let dist = dx.max(dy);
|
||||||
|
|
||||||
|
if best.map_or(true, |(_, best_dist)| dist < best_dist) {
|
||||||
|
best = Some((part.tile_pos, dist));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some((trunk_pos, _)) = best else {
|
||||||
|
// No trees found — nothing to do
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Find the lowest trunk tile (minimum z) for this tree's XY column.
|
||||||
|
// fell_tree is called with the lowest trunk pos.
|
||||||
|
let lowest_trunk = tree_parts
|
||||||
|
.iter()
|
||||||
|
.filter(|(_, p)| {
|
||||||
|
p.is_trunk
|
||||||
|
&& p.tile_pos.x == trunk_pos.x
|
||||||
|
&& p.tile_pos.y == trunk_pos.y
|
||||||
|
&& tilemap.fixture_tiles.contains_key(&p.tile_pos)
|
||||||
|
})
|
||||||
|
.map(|(_, p)| p.tile_pos)
|
||||||
|
.min_by_key(|pos| pos.z)
|
||||||
|
.unwrap_or(trunk_pos);
|
||||||
|
|
||||||
|
// Find one idle dorf — prefer closest to the tree.
|
||||||
|
let mut best_dorf: Option<(Entity, i32)> = None;
|
||||||
|
for (entity, queue, state, transform) in dorf_query.iter() {
|
||||||
|
if !is_idle_dorf(&queue, &state) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let pos = transform.translation.as_ivec3();
|
||||||
|
let dx = (pos.x - lowest_trunk.x).abs() / ITILE_SIZE;
|
||||||
|
let dy = (pos.y - lowest_trunk.y).abs() / ITILE_SIZE;
|
||||||
|
let dist = dx.max(dy);
|
||||||
|
if best_dorf.map_or(true, |(_, d)| dist < d) {
|
||||||
|
best_dorf = Some((entity, dist));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some((chopper, _)) = best_dorf else {
|
||||||
|
return; // no idle dorfs available
|
||||||
|
};
|
||||||
|
|
||||||
|
// Assign ChopTree task
|
||||||
|
if let Ok((_, mut queue, mut state, _)) = dorf_query.get_mut(chopper) {
|
||||||
|
// Clear current idle task and push ChopTree
|
||||||
|
queue.clear();
|
||||||
|
queue.push(Task::ChopTree {
|
||||||
|
trunk_pos: lowest_trunk,
|
||||||
|
chop_ticks: CHOP_TICKS_DEFAULT,
|
||||||
|
step: ChopStep::MovingToTree,
|
||||||
|
});
|
||||||
|
*state = TaskState::Pending;
|
||||||
|
}
|
||||||
|
|
||||||
|
*demo_state = DemoState::Chopping {
|
||||||
|
trunk_pos: lowest_trunk,
|
||||||
|
chopper,
|
||||||
|
};
|
||||||
|
|
||||||
|
info!(
|
||||||
|
"Demo: assigned ChopTree at {:?} to dorf {:?}",
|
||||||
|
lowest_trunk, chopper
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
DemoState::Chopping { trunk_pos, chopper } => {
|
||||||
|
let trunk_pos = *trunk_pos;
|
||||||
|
let chopper = *chopper;
|
||||||
|
|
||||||
|
// Check if the tree has been felled (fixture gone from tilemap)
|
||||||
|
if tilemap.fixture_tiles.contains_key(&trunk_pos) {
|
||||||
|
// Still standing — check chopper hasn't abandoned the task
|
||||||
|
if let Ok((_, queue, _, _)) = dorf_query.get(chopper) {
|
||||||
|
let still_chopping = queue.current().map_or(
|
||||||
|
false,
|
||||||
|
|t| matches!(t, Task::ChopTree { trunk_pos: tp, .. } if *tp == trunk_pos),
|
||||||
|
);
|
||||||
|
if !still_chopping && queue.is_empty() {
|
||||||
|
// Chopper abandoned — reassign
|
||||||
|
*demo_state = DemoState::Idle;
|
||||||
|
warn!("Demo: chopper {:?} abandoned ChopTree — resetting", chopper);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tree is felled — transition to Hauling
|
||||||
|
info!("Demo: tree at {:?} felled, assigning haul tasks", trunk_pos);
|
||||||
|
|
||||||
|
// Find all Cargo logs near the trunk position
|
||||||
|
let search_world = LOG_SEARCH_RADIUS_TILES * ITILE_SIZE;
|
||||||
|
let logs: Vec<(Entity, IVec3)> = cargo_query
|
||||||
|
.iter()
|
||||||
|
.filter(|(_, cargo)| {
|
||||||
|
cargo.name == "log"
|
||||||
|
&& (cargo.tile_pos.x - trunk_pos.x).abs() <= search_world
|
||||||
|
&& (cargo.tile_pos.y - trunk_pos.y).abs() <= search_world
|
||||||
|
})
|
||||||
|
.map(|(e, cargo)| (e, cargo.tile_pos))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
if logs.is_empty() {
|
||||||
|
// No logs found — tree may have had no trunks or logs all orphaned
|
||||||
|
warn!("Demo: no logs found after felling {:?}", trunk_pos);
|
||||||
|
*demo_state = DemoState::Idle;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find haul destination — nearest standable tile to (0,0)
|
||||||
|
let dest = tilemap
|
||||||
|
.find_nearest_free_cargo_tile(IVec3::ZERO, HAUL_DEST_SEARCH_RADIUS)
|
||||||
|
.unwrap_or(IVec3::ZERO);
|
||||||
|
|
||||||
|
// Assign one HaulCargo task per log to idle dorfs
|
||||||
|
let mut assigned_logs: FxHashSet<Entity> = FxHashSet::default();
|
||||||
|
let mut logs_iter = logs.iter();
|
||||||
|
|
||||||
|
// Re-collect idle dorfs (mutable query needed)
|
||||||
|
// Must collect entities first to avoid double-borrow
|
||||||
|
let idle_dorfs: Vec<Entity> = dorf_query
|
||||||
|
.iter()
|
||||||
|
.filter(|(_, queue, state, _)| is_idle_dorf(queue, state))
|
||||||
|
.map(|(e, _, _, _)| e)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
for dorf_entity in idle_dorfs {
|
||||||
|
let Some(&(log_entity, log_pos)) = logs_iter.next() else {
|
||||||
|
break; // more dorfs than logs — remaining dorfs stay idle
|
||||||
|
};
|
||||||
|
if let Ok((_, mut queue, mut state, _)) = dorf_query.get_mut(dorf_entity) {
|
||||||
|
queue.clear();
|
||||||
|
queue.push(Task::HaulCargo {
|
||||||
|
cargo_entity: log_entity,
|
||||||
|
cargo_pos: log_pos,
|
||||||
|
dest,
|
||||||
|
step: HaulStep::MovingToCargo,
|
||||||
|
});
|
||||||
|
*state = TaskState::Pending;
|
||||||
|
assigned_logs.insert(log_entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Any remaining unassigned logs stay on the floor — future dorfs
|
||||||
|
// will be assigned when they become idle if the demo loops.
|
||||||
|
// For simplicity: log unassigned count but don't retry this tick.
|
||||||
|
let unassigned = logs.len().saturating_sub(assigned_logs.len());
|
||||||
|
if unassigned > 0 {
|
||||||
|
info!(
|
||||||
|
"Demo: {} logs unassigned (not enough idle dorfs)",
|
||||||
|
unassigned
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
*demo_state = DemoState::Hauling {
|
||||||
|
felled_trunk_pos: trunk_pos,
|
||||||
|
assigned_logs,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
DemoState::Hauling {
|
||||||
|
felled_trunk_pos,
|
||||||
|
assigned_logs,
|
||||||
|
} => {
|
||||||
|
let felled_trunk_pos = *felled_trunk_pos;
|
||||||
|
|
||||||
|
// Check if all assigned logs have been hauled (removed from cargo_tiles)
|
||||||
|
// Check via cargo_query: if the Cargo entity still exists
|
||||||
|
// and is still in cargo_tiles, it hasn't been hauled yet.
|
||||||
|
let remaining = assigned_logs
|
||||||
|
.iter()
|
||||||
|
.filter(|&&log_entity| {
|
||||||
|
cargo_query
|
||||||
|
.get(log_entity)
|
||||||
|
.map(|(_, cargo)| tilemap.cargo_tiles.contains_key(&cargo.tile_pos))
|
||||||
|
.unwrap_or(false)
|
||||||
|
})
|
||||||
|
.count();
|
||||||
|
|
||||||
|
if remaining == 0 {
|
||||||
|
info!(
|
||||||
|
"Demo: all logs hauled from {:?}, finding next tree",
|
||||||
|
felled_trunk_pos
|
||||||
|
);
|
||||||
|
*demo_state = DemoState::Idle;
|
||||||
|
}
|
||||||
|
// else: still hauling, check again next tick (cheap)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if a dorf has no active task or is purely wandering idle.
|
||||||
|
/// Used to find dorfs available for task assignment.
|
||||||
|
#[inline]
|
||||||
|
fn is_idle_dorf(queue: &TaskQueue, state: &TaskState) -> bool {
|
||||||
|
// Dorf is available if: queue is empty, OR current task is Idle (wandering)
|
||||||
|
queue.is_empty()
|
||||||
|
|| matches!(queue.current(), Some(Task::Idle { .. }))
|
||||||
|
|| *state == TaskState::Pending
|
||||||
|
&& queue
|
||||||
|
.current()
|
||||||
|
.map_or(true, |t| matches!(t, Task::Idle { .. }))
|
||||||
|
}
|
||||||
@@ -176,13 +176,23 @@ pub fn task_executor_system(
|
|||||||
&mut tile_changed,
|
&mut tile_changed,
|
||||||
&mut occlusion,
|
&mut occlusion,
|
||||||
);
|
);
|
||||||
|
// Compute fall direction: from chopper (entity's position) toward trunk
|
||||||
|
let chopper_pos = transform.translation.truncate(); // XY only
|
||||||
|
let trunk_xy = Vec2::new(trunk_pos.x as f32, trunk_pos.y as f32);
|
||||||
|
let mut fall_dir = (trunk_xy - chopper_pos).normalize_or_zero();
|
||||||
|
// If dorf is standing on the trunk (distance ~0), fall direction is arbitrary
|
||||||
|
if fall_dir == Vec2::ZERO {
|
||||||
|
fall_dir = Vec2::new(1.0, 0.0); // default: fall east
|
||||||
|
}
|
||||||
let log_sprite = asset_server.load("tree_trunk.png");
|
let log_sprite = asset_server.load("tree_trunk.png");
|
||||||
for &pos in trunk_positions.iter() {
|
for &pos in trunk_positions.iter() {
|
||||||
crate::entities::cargo::spawn_log_cargo(
|
crate::entities::cargo::spawn_log_cargo(
|
||||||
&mut commands,
|
&mut commands,
|
||||||
&mut tilemap_mut,
|
&mut tilemap_mut,
|
||||||
pos,
|
pos,
|
||||||
|
fall_dir,
|
||||||
log_sprite.clone(),
|
log_sprite.clone(),
|
||||||
|
&mut rng,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
*step = ChopStep::Done;
|
*step = ChopStep::Done;
|
||||||
@@ -291,8 +301,14 @@ pub fn task_executor_system(
|
|||||||
}
|
}
|
||||||
HaulStep::Dropping { drop_pos } => {
|
HaulStep::Dropping { drop_pos } => {
|
||||||
if let Some(cargo_entity) = haul.release() {
|
if let Some(cargo_entity) = haul.release() {
|
||||||
let _ = tilemap_mut.place_cargo(*drop_pos, cargo_entity);
|
if tilemap_mut.place_cargo(*drop_pos, cargo_entity).is_err() {
|
||||||
if let Ok(mut cargo) = cargo_query.get_mut(cargo_entity) {
|
warn!(
|
||||||
|
"place_cargo failed at {:?} — tile occupied. \
|
||||||
|
Cargo entity {:?} orphaned.",
|
||||||
|
drop_pos, cargo_entity
|
||||||
|
);
|
||||||
|
} else if let Ok(mut cargo) = cargo_query.get_mut(cargo_entity)
|
||||||
|
{
|
||||||
cargo.tile_pos = *drop_pos;
|
cargo.tile_pos = *drop_pos;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -340,8 +356,14 @@ pub fn task_executor_system(
|
|||||||
}
|
}
|
||||||
DropStep::Dropping { drop_pos } => {
|
DropStep::Dropping { drop_pos } => {
|
||||||
if let Some(cargo_entity) = haul.release() {
|
if let Some(cargo_entity) = haul.release() {
|
||||||
let _ = tilemap_mut.place_cargo(*drop_pos, cargo_entity);
|
if tilemap_mut.place_cargo(*drop_pos, cargo_entity).is_err() {
|
||||||
if let Ok(mut cargo) = cargo_query.get_mut(cargo_entity) {
|
warn!(
|
||||||
|
"place_cargo failed at {:?} — tile occupied. \
|
||||||
|
Cargo entity {:?} orphaned.",
|
||||||
|
drop_pos, cargo_entity
|
||||||
|
);
|
||||||
|
} else if let Ok(mut cargo) = cargo_query.get_mut(cargo_entity)
|
||||||
|
{
|
||||||
cargo.tile_pos = *drop_pos;
|
cargo.tile_pos = *drop_pos;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
pub mod components;
|
pub mod components;
|
||||||
|
pub mod demo;
|
||||||
pub mod events;
|
pub mod events;
|
||||||
pub mod executor;
|
pub mod executor;
|
||||||
pub mod idle;
|
pub mod idle;
|
||||||
|
|
||||||
pub use components::{IdleState, Task, TaskQueue, TaskState};
|
pub use components::{IdleState, Task, TaskQueue, TaskState};
|
||||||
|
pub use demo::{demo_system, DemoState};
|
||||||
pub use events::{TaskBlocked, TaskClaimed, TaskCompleted, TaskDropped, TaskFailed};
|
pub use events::{TaskBlocked, TaskClaimed, TaskCompleted, TaskDropped, TaskFailed};
|
||||||
pub use executor::task_executor_system;
|
pub use executor::task_executor_system;
|
||||||
|
|
||||||
|
|||||||
+11
-4
@@ -1,10 +1,12 @@
|
|||||||
//! TasksPlugin — registers task infrastructure: executor, events, idle logic.
|
//! TasksPlugin — registers task infrastructure: executor, events, idle logic, demo loop.
|
||||||
//!
|
//!
|
||||||
//! Systems:
|
//! Systems:
|
||||||
//! - task_executor_system (FixedUpdate, after pathfinding movement setup)
|
//! - task_executor_system (FixedUpdate)
|
||||||
|
//! - demo_system (FixedUpdate, after task_executor_system)
|
||||||
|
|
||||||
use crate::entities::tasks::{
|
use crate::entities::tasks::{
|
||||||
task_executor_system, TaskBlocked, TaskClaimed, TaskCompleted, TaskDropped, TaskFailed,
|
demo_system, task_executor_system, DemoState, TaskBlocked, TaskClaimed, TaskCompleted,
|
||||||
|
TaskDropped, TaskFailed,
|
||||||
};
|
};
|
||||||
use bevy::prelude::*;
|
use bevy::prelude::*;
|
||||||
|
|
||||||
@@ -16,6 +18,11 @@ impl Plugin for TasksPlugin {
|
|||||||
.add_message::<TaskCompleted>()
|
.add_message::<TaskCompleted>()
|
||||||
.add_message::<TaskFailed>()
|
.add_message::<TaskFailed>()
|
||||||
.add_message::<TaskDropped>()
|
.add_message::<TaskDropped>()
|
||||||
.add_message::<TaskBlocked>();
|
.add_message::<TaskBlocked>()
|
||||||
|
.init_resource::<DemoState>()
|
||||||
|
.add_systems(
|
||||||
|
bevy::app::FixedUpdate,
|
||||||
|
demo_system.after(task_executor_system),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user