86 lines
2.5 KiB
Rust
86 lines
2.5 KiB
Rust
//! Idle task implementation — wandering behaviour refactored here.
|
|
//!
|
|
//! This module contains the logic that was previously in the wandering
|
|
//! system. Now it's called by the task executor when Task::Idle is active.
|
|
|
|
use crate::constants::ITILE_SIZE;
|
|
use crate::entities::shared_components::Ambulatory;
|
|
use crate::entities::tasks::components::Task;
|
|
use crate::world::tiles::tilemap::TileMap;
|
|
use bevy::prelude::*;
|
|
use bevy_rand::prelude::*;
|
|
use rand::RngExt;
|
|
|
|
/// Execute Idle task: set target for wandering, let pathfinding handle movement.
|
|
pub(super) fn execute_idle(
|
|
entity: Entity,
|
|
task: &mut Task,
|
|
transform: &Transform,
|
|
ambulatory: &mut Ambulatory,
|
|
tilemap: &TileMap,
|
|
rng: &mut WyRand,
|
|
) {
|
|
let Task::Idle {
|
|
target,
|
|
wander_radius,
|
|
origin,
|
|
} = task
|
|
else {
|
|
return;
|
|
};
|
|
|
|
let entity_pos = (transform.translation / ITILE_SIZE as f32).as_ivec3();
|
|
|
|
// Track if target actually changed this tick
|
|
let mut target_changed = false;
|
|
|
|
// If reached target or target is no longer standable, pick new target
|
|
if entity_pos == *target || !tilemap.is_standable(*target) {
|
|
if let Some(new_target) = pick_wander_target(origin, *wander_radius, tilemap, rng) {
|
|
*target = new_target;
|
|
target_changed = true;
|
|
}
|
|
}
|
|
|
|
// Only reset path if target changed or no path exists
|
|
if target_changed || ambulatory.current_path.is_none() {
|
|
ambulatory.target = Some(Vec3::new(
|
|
target.x as f32,
|
|
target.y as f32,
|
|
transform.translation.z,
|
|
));
|
|
ambulatory.current_path = None;
|
|
ambulatory.path_index = 0;
|
|
}
|
|
}
|
|
|
|
/// Pick a random standable tile within wander radius of origin.
|
|
/// Uses reservoir sampling to avoid heap allocation.
|
|
fn pick_wander_target(
|
|
origin: &IVec3,
|
|
radius: i32,
|
|
tilemap: &TileMap,
|
|
rng: &mut WyRand,
|
|
) -> Option<IVec3> {
|
|
let mut chosen = None;
|
|
let mut count = 0u32;
|
|
|
|
for dx in -radius..=radius {
|
|
for dy in -radius..=radius {
|
|
let candidate = IVec3::new(
|
|
origin.x + dx * ITILE_SIZE,
|
|
origin.y + dy * ITILE_SIZE,
|
|
origin.z,
|
|
);
|
|
if tilemap.is_standable(candidate) {
|
|
count += 1;
|
|
// Reservoir sampling: 1/count chance to replace chosen
|
|
if rng.random_range(0..count) == 0 {
|
|
chosen = Some(candidate);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
chosen
|
|
}
|