Fix job queueing system: add JobId tracking for failed job reassignment
- Add JobId (rolling u16 counter) to track jobs through their lifecycle - Add job_id field to all Task variants (Idle, GoTo, ChopTree, HaulCargo, DropHauled) - Add unclaim_job() method to requeue failed jobs for reassignment - Change task failure paths to use unclaim_job() instead of complete_job_by_id() - This allows failed jobs to be picked up by another dorf instead of being stuck - Add to_job_kind() and get_job_id() helper methods to Task - Fix pathfinding is_standable_tile debug logging spam - Fix chunk bounds check in ChunkData::is_standable (was checking wrong z range)
This commit is contained in:
@@ -41,7 +41,7 @@ pub struct Container {
|
||||
pub is_open: bool,
|
||||
/// Items held. May include entities that themselves have Container components.
|
||||
/// TODO: migrate away from Entity when stable ID scheme exists.
|
||||
pub contents: SmallVec<[Entity; 4]>,
|
||||
pub contents: SmallVec<[Entity; 8]>,
|
||||
/// Cached total weight of contents (updated on add/remove, not per-tick).
|
||||
pub current_weight: u32,
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ use crate::constants::*;
|
||||
use crate::entities::behaviour::{EntityBehaviourRegistry, EntityType};
|
||||
use crate::entities::cargo::{CarryVisualState, HaulSlot};
|
||||
use crate::entities::shared_components::Ambulatory;
|
||||
use crate::entities::tasks::job_queue::JobId;
|
||||
use crate::entities::tasks::{IdleState, Task, TaskQueue, TaskState};
|
||||
use crate::game::SpawnDelay;
|
||||
use crate::world::VisibleGameEntity;
|
||||
@@ -57,6 +58,7 @@ impl Dorf {
|
||||
carry_visual: CarryVisualState::new(normal_sprite, carry_sprite),
|
||||
task_queue: TaskQueue {
|
||||
tasks: VecDeque::from([Task::Idle {
|
||||
job_id: JobId::default(),
|
||||
origin,
|
||||
sigma_world: behaviour.idle.sigma_world,
|
||||
state: IdleState::Picking {
|
||||
|
||||
@@ -902,25 +902,7 @@ fn validate_next_steps(tilemap: &TileMap, path: &[Vec3], start_index: usize, ste
|
||||
}
|
||||
|
||||
fn is_standable_tile(tilemap: &TileMap, pos: IVec3) -> bool {
|
||||
let result = tilemap.is_standable(pos);
|
||||
if !result {
|
||||
let chunk_pos = crate::world::chunks::world_to_chunk(pos);
|
||||
let chunk_exists = tilemap.chunks.contains_key(&chunk_pos);
|
||||
let (local_x, local_y, z) = if let Some(c) = tilemap.chunks.get(&chunk_pos) {
|
||||
crate::world::tiles::chunk_data::ChunkData::world_to_local(pos)
|
||||
} else {
|
||||
(0, 0, 0)
|
||||
};
|
||||
|
||||
let floor_exists = tilemap.floor_tiles.contains_key(&pos);
|
||||
let fixture_exists = tilemap.fixture_tiles.contains_key(&pos);
|
||||
|
||||
info!(
|
||||
"[PATH] is_standable_tile=false: pos={:?} chunk={:?} chunk_loaded={} local=({},{},{}) floor={} fixture={}",
|
||||
pos, chunk_pos, chunk_exists, local_x, local_y, z, floor_exists, fixture_exists
|
||||
);
|
||||
}
|
||||
result
|
||||
tilemap.is_standable(pos)
|
||||
}
|
||||
|
||||
/// Sample a random standable tile on an edge of `next_chunk`, picking the one
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
use bevy::prelude::*;
|
||||
use std::collections::VecDeque;
|
||||
|
||||
use crate::entities::tasks::{job_queue::JobId, JobKind};
|
||||
|
||||
/// Maximum consecutive pick failures before panic.
|
||||
/// Prevents infinite retry loops when tilemap has no standable tiles.
|
||||
pub const IDLE_MAX_RETRIES: u32 = 50;
|
||||
@@ -112,6 +114,7 @@ pub const CHOP_TICKS_DEFAULT: u32 = 120;
|
||||
#[repr(u8)]
|
||||
pub enum Task {
|
||||
Idle {
|
||||
job_id: JobId,
|
||||
/// Home position in world units — centre of Gaussian distribution.
|
||||
/// Set at spawn, does not drift.
|
||||
origin: IVec3,
|
||||
@@ -125,6 +128,7 @@ pub enum Task {
|
||||
/// Completes when entity is within `threshold_tiles` of target.
|
||||
/// Uses existing pathfinding infrastructure.
|
||||
GoTo {
|
||||
job_id: JobId,
|
||||
target: IVec3,
|
||||
/// Completion threshold in tiles (Chebyshev distance).
|
||||
threshold_tiles: i32,
|
||||
@@ -132,6 +136,7 @@ pub enum Task {
|
||||
|
||||
/// Walk to a tree trunk and fell it. Produces Cargo log entities on completion.
|
||||
ChopTree {
|
||||
job_id: JobId,
|
||||
/// World position of the lowest trunk fixture tile.
|
||||
trunk_pos: IVec3,
|
||||
/// Ticks required to chop. Future: derived from skill + tool.
|
||||
@@ -142,6 +147,7 @@ pub enum Task {
|
||||
/// Pick up a specific Cargo entity and haul it to dest.
|
||||
/// Renamed from HaulObject for consistency with the Cargo type.
|
||||
HaulCargo {
|
||||
job_id: JobId,
|
||||
/// The Cargo entity to pick up.
|
||||
cargo_entity: Entity,
|
||||
/// Tile position of the cargo in the world (for pathfinding).
|
||||
@@ -153,6 +159,7 @@ pub enum Task {
|
||||
|
||||
/// Drop whatever is in HaulSlot at or near pos.
|
||||
DropHauled {
|
||||
job_id: JobId,
|
||||
/// Preferred drop position. Actual drop may be nearby if occupied.
|
||||
pos: IVec3,
|
||||
step: DropStep,
|
||||
@@ -178,6 +185,37 @@ impl Task {
|
||||
Task::DropHauled { .. } => "DropHauled",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_job_kind(&self) -> Option<JobKind> {
|
||||
match self {
|
||||
// 1. Handle the "Global" job types
|
||||
Task::ChopTree { trunk_pos, .. } => Some(JobKind::FellTree {
|
||||
trunk_pos: *trunk_pos,
|
||||
}),
|
||||
Task::HaulCargo {
|
||||
cargo_entity,
|
||||
cargo_pos,
|
||||
dest,
|
||||
..
|
||||
} => Some(JobKind::HaulCargo {
|
||||
cargo_entity: *cargo_entity,
|
||||
cargo_pos: *cargo_pos,
|
||||
dest: *dest,
|
||||
}),
|
||||
|
||||
// 2. Handle everything else (GoTo, DropHauled, Idle, etc.)
|
||||
// We use the underscore _ to say "for any other variant, do this"
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_job_id(&self) -> Option<JobId> {
|
||||
match self {
|
||||
Task::ChopTree { job_id, .. } => Some(*job_id),
|
||||
Task::HaulCargo { job_id, .. } => Some(*job_id),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Queue of tasks for an entity. Front of queue is currently executing task.
|
||||
|
||||
@@ -45,7 +45,7 @@ pub struct TaskBlocked {
|
||||
#[derive(Message, Clone)]
|
||||
pub struct LogsSpawned {
|
||||
/// Entities of the spawned logs.
|
||||
pub log_entities: SmallVec<[Entity; 8]>,
|
||||
pub log_entities: SmallVec<[Entity; 12]>,
|
||||
/// Destination for hauling (currently origin, later stockpile).
|
||||
pub dest: IVec3,
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ use crate::entities::tasks::components::{
|
||||
ChopStep, DropStep, HaulStep, IdleState, Task, TaskQueue, TaskState,
|
||||
};
|
||||
use crate::entities::tasks::events::{LogsSpawned, TaskClaimed, TaskCompleted, TaskFailed};
|
||||
use crate::entities::tasks::job_queue::{JobKind, JobQueue};
|
||||
use crate::entities::tasks::job_queue::{JobId, JobKind, JobQueue};
|
||||
use crate::entities::tasks::tasks::idle::execute_idle;
|
||||
use crate::world::chunks::ChunkMap;
|
||||
use crate::world::generation::forestry::{fell_tree, TreePart};
|
||||
@@ -79,6 +79,7 @@ pub fn task_executor_system(
|
||||
|
||||
if queue.is_empty() {
|
||||
queue.push(Task::Idle {
|
||||
job_id: JobId::default(),
|
||||
origin,
|
||||
sigma_world: behaviour.idle.sigma_world,
|
||||
state: IdleState::Picking {
|
||||
@@ -102,8 +103,6 @@ pub fn task_executor_system(
|
||||
// Execute current task if Active
|
||||
if *state == TaskState::Active {
|
||||
if let Some(current_task) = queue.current_mut() {
|
||||
let mut failed_reason: Option<&'static str> = None;
|
||||
|
||||
match current_task {
|
||||
Task::Idle { .. } => {
|
||||
execute_idle(
|
||||
@@ -119,6 +118,7 @@ pub fn task_executor_system(
|
||||
);
|
||||
}
|
||||
Task::GoTo {
|
||||
job_id: _,
|
||||
target,
|
||||
threshold_tiles,
|
||||
} => {
|
||||
@@ -139,11 +139,13 @@ pub fn task_executor_system(
|
||||
}
|
||||
}
|
||||
Task::ChopTree {
|
||||
job_id,
|
||||
trunk_pos,
|
||||
chop_ticks,
|
||||
step,
|
||||
} => match step {
|
||||
ChopStep::MovingToTree { ref mut approach } => {
|
||||
let job_id_val = *job_id;
|
||||
if !tilemap_mut.fixture_tiles.contains_key(trunk_pos) {
|
||||
info!(
|
||||
"TASK FAILED: {:?} for {:?} - {}",
|
||||
@@ -154,6 +156,7 @@ pub fn task_executor_system(
|
||||
task: current_task.clone(),
|
||||
reason: "tree already gone",
|
||||
});
|
||||
job_queue.unclaim_job(job_id_val);
|
||||
*state = TaskState::Failed;
|
||||
continue;
|
||||
}
|
||||
@@ -207,6 +210,7 @@ pub fn task_executor_system(
|
||||
task: current_task.clone(),
|
||||
reason,
|
||||
});
|
||||
job_queue.unclaim_job(job_id_val);
|
||||
*state = TaskState::Failed;
|
||||
continue;
|
||||
}
|
||||
@@ -259,7 +263,7 @@ 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, IVec3); 8]> =
|
||||
let mut log_entities: SmallVec<[(Entity, IVec3); 12]> =
|
||||
SmallVec::new();
|
||||
// Loop from 0 up to the number of trunk segments found
|
||||
for i in 0..trunk_count {
|
||||
@@ -321,20 +325,25 @@ pub fn task_executor_system(
|
||||
}
|
||||
ChopStep::Done => {
|
||||
*state = TaskState::Completed;
|
||||
// Remove job from queue
|
||||
job_queue.complete_job_by_id(current_task.get_job_id().unwrap());
|
||||
}
|
||||
},
|
||||
Task::HaulCargo {
|
||||
job_id,
|
||||
cargo_entity,
|
||||
cargo_pos,
|
||||
dest,
|
||||
step,
|
||||
} => {
|
||||
let job_id_val = *job_id;
|
||||
let Some(ref mut haul) = haul_slot else {
|
||||
failed_writer.write(TaskFailed {
|
||||
entity,
|
||||
task: current_task.clone(),
|
||||
reason: "entity has no HaulSlot",
|
||||
});
|
||||
job_queue.unclaim_job(job_id_val);
|
||||
*state = TaskState::Failed;
|
||||
continue;
|
||||
};
|
||||
@@ -356,6 +365,7 @@ pub fn task_executor_system(
|
||||
task: current_task.clone(),
|
||||
reason: "cargo no longer exists",
|
||||
});
|
||||
job_queue.unclaim_job(job_id_val);
|
||||
*state = TaskState::Failed;
|
||||
continue;
|
||||
}
|
||||
@@ -475,6 +485,7 @@ pub fn task_executor_system(
|
||||
task: current_task.clone(),
|
||||
reason: "no standable tile near cargo",
|
||||
});
|
||||
job_queue.unclaim_job(job_id_val);
|
||||
*state = TaskState::Failed;
|
||||
continue;
|
||||
}
|
||||
@@ -508,6 +519,7 @@ pub fn task_executor_system(
|
||||
task: current_task.clone(),
|
||||
reason: "HaulSlot already occupied",
|
||||
});
|
||||
job_queue.unclaim_job(job_id_val);
|
||||
*state = TaskState::Failed;
|
||||
continue;
|
||||
}
|
||||
@@ -596,10 +608,11 @@ pub fn task_executor_system(
|
||||
HaulStep::Done => {
|
||||
info!("[HAUL] {:?} haul task COMPLETE", entity);
|
||||
*state = TaskState::Completed;
|
||||
job_queue.complete_job_by_id(current_task.get_job_id().unwrap());
|
||||
}
|
||||
}
|
||||
}
|
||||
Task::DropHauled { pos, step } => {
|
||||
Task::DropHauled { job_id, pos, step } => {
|
||||
let Some(ref mut haul) = haul_slot else {
|
||||
*state = TaskState::Completed;
|
||||
continue;
|
||||
@@ -651,6 +664,7 @@ pub fn task_executor_system(
|
||||
*step = DropStep::Done;
|
||||
}
|
||||
DropStep::Done => {
|
||||
job_queue.complete_job_by_id(current_task.get_job_id().unwrap());
|
||||
*state = TaskState::Completed;
|
||||
}
|
||||
}
|
||||
@@ -677,6 +691,7 @@ pub fn task_executor_system(
|
||||
|
||||
if completed_task.is_terminal() && queue.is_empty() {
|
||||
queue.push(Task::Idle {
|
||||
job_id: JobId::default(),
|
||||
origin,
|
||||
sigma_world: behaviour.idle.sigma_world,
|
||||
state: IdleState::Picking {
|
||||
|
||||
@@ -2,7 +2,7 @@ use crate::entities::behaviour::{EntityBehaviourRegistry, EntityType};
|
||||
use crate::entities::cargo::HaulSlot;
|
||||
use crate::entities::shared_components::Ambulatory;
|
||||
use crate::entities::tasks::components::{Task, TaskQueue, TaskState};
|
||||
use crate::entities::tasks::job_queue::{JobKind, JobQueue};
|
||||
use crate::entities::tasks::job_queue::{JobId, JobKind, JobQueue};
|
||||
use crate::entities::tasks::jobs::{FellTreeJob, HaulCargoJob, IdleJob};
|
||||
use crate::world::tiles::TileMap;
|
||||
use bevy::prelude::*;
|
||||
@@ -35,7 +35,9 @@ pub fn job_assignment_system(
|
||||
let dorf_pos_ivec = transform.translation.as_ivec3();
|
||||
let dorf_pos_2d = Vec2::new(transform.translation.x, transform.translation.y);
|
||||
|
||||
if let Some(job) = job_queue.pop_best_pathfinding(&tilemap, dorf_pos_ivec, dorf_pos_2d) {
|
||||
if let Some((job_id, job)) =
|
||||
job_queue.pop_best_pathfinding(&tilemap, dorf_pos_ivec, dorf_pos_2d)
|
||||
{
|
||||
info!(
|
||||
"[ASSIGN] Job assigned to dorf at {:?}: {:?}",
|
||||
dorf_pos_ivec.xy(),
|
||||
@@ -51,7 +53,7 @@ pub fn job_assignment_system(
|
||||
JobKind::FellTree { trunk_pos } => {
|
||||
info!("[ASSIGN] Dorf assigned to FellTree at {:?}", trunk_pos);
|
||||
queue.clear();
|
||||
queue.push(FellTreeJob::start(trunk_pos));
|
||||
queue.push(FellTreeJob::start(job_id, trunk_pos));
|
||||
*state = TaskState::Pending;
|
||||
}
|
||||
JobKind::HaulCargo {
|
||||
@@ -61,7 +63,7 @@ pub fn job_assignment_system(
|
||||
} => {
|
||||
info!("[ASSIGN] Dorf assigned to HaulCargo at {:?}", cargo_pos);
|
||||
queue.clear();
|
||||
queue.push(HaulCargoJob::start(cargo_entity, cargo_pos, dest));
|
||||
queue.push(HaulCargoJob::start(job_id, cargo_entity, cargo_pos, dest));
|
||||
*state = TaskState::Pending;
|
||||
}
|
||||
}
|
||||
@@ -69,7 +71,7 @@ pub fn job_assignment_system(
|
||||
let behaviour = EntityBehaviourRegistry::global_get("dorf");
|
||||
let origin = transform.translation.as_ivec3();
|
||||
if queue.is_empty() {
|
||||
queue.push(IdleJob::start(origin, &behaviour.idle));
|
||||
queue.push(IdleJob::start(JobId::default(), origin, &behaviour.idle));
|
||||
*state = TaskState::Pending;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ use smallvec::SmallVec;
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::VecDeque;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum JobKind {
|
||||
FellTree {
|
||||
trunk_pos: IVec3,
|
||||
@@ -44,14 +44,27 @@ impl JobKind {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct JobId(u16);
|
||||
|
||||
impl JobId {
|
||||
#[inline]
|
||||
pub fn next(&mut self) -> JobId {
|
||||
self.0 = self.0.wrapping_add(1);
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
struct Entry {
|
||||
kind: JobKind,
|
||||
claimed: bool,
|
||||
id: JobId,
|
||||
}
|
||||
|
||||
#[derive(Resource, Default)]
|
||||
pub struct JobQueue {
|
||||
jobs: VecDeque<Entry>,
|
||||
next_id: JobId,
|
||||
}
|
||||
|
||||
impl JobQueue {
|
||||
@@ -60,15 +73,30 @@ impl JobQueue {
|
||||
self.jobs.push_back(Entry {
|
||||
kind,
|
||||
claimed: false,
|
||||
id: self.next_id.next(),
|
||||
});
|
||||
}
|
||||
|
||||
pub fn complete_job_by_id(&mut self, id: JobId) {
|
||||
if let Some(index) = self.jobs.iter().position(|j| j.id == id) {
|
||||
self.jobs.remove(index);
|
||||
info!("[QUEUE] Job ID {:?} removed from global queue", id);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unclaim_job(&mut self, id: JobId) {
|
||||
if let Some(entry) = self.jobs.iter_mut().find(|j| j.id == id) {
|
||||
entry.claimed = false;
|
||||
info!("[QUEUE] Job ID {:?} marked unclaimed for reassignment", id);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pop_best_pathfinding(
|
||||
&mut self,
|
||||
tilemap: &crate::world::tiles::TileMap,
|
||||
dorf_pos: IVec3,
|
||||
_dorf_pos_2d: Vec2,
|
||||
) -> Option<JobKind> {
|
||||
) -> Option<(JobId, JobKind)> {
|
||||
if self.jobs.is_empty() {
|
||||
return None;
|
||||
}
|
||||
@@ -140,7 +168,7 @@ impl JobQueue {
|
||||
|
||||
self.jobs[idx].claimed = true;
|
||||
// Note: JobKind needs `#[derive(Clone)]` if it doesn't have it already
|
||||
return Some(self.jobs[idx].kind.clone());
|
||||
return Some((self.jobs[idx].id, self.jobs[idx].kind.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
use crate::entities::behaviour::IdleBehaviour;
|
||||
use crate::entities::tasks::components::{ChopStep, HaulStep, IdleState, Task, CHOP_TICKS_DEFAULT};
|
||||
use crate::entities::tasks::job_queue::JobId;
|
||||
use bevy::prelude::{Entity, IVec3};
|
||||
|
||||
pub struct FellTreeJob;
|
||||
|
||||
impl FellTreeJob {
|
||||
pub fn start(trunk_pos: IVec3) -> Task {
|
||||
pub fn start(job_id: JobId, trunk_pos: IVec3) -> Task {
|
||||
Task::ChopTree {
|
||||
job_id,
|
||||
trunk_pos,
|
||||
chop_ticks: CHOP_TICKS_DEFAULT,
|
||||
step: ChopStep::MovingToTree { approach: None },
|
||||
@@ -17,8 +19,9 @@ impl FellTreeJob {
|
||||
pub struct HaulCargoJob;
|
||||
|
||||
impl HaulCargoJob {
|
||||
pub fn start(cargo_entity: Entity, cargo_pos: IVec3, dest: IVec3) -> Task {
|
||||
pub fn start(job_id: JobId, cargo_entity: Entity, cargo_pos: IVec3, dest: IVec3) -> Task {
|
||||
Task::HaulCargo {
|
||||
job_id,
|
||||
cargo_entity,
|
||||
cargo_pos,
|
||||
dest,
|
||||
@@ -30,8 +33,9 @@ impl HaulCargoJob {
|
||||
pub struct IdleJob;
|
||||
|
||||
impl IdleJob {
|
||||
pub fn start(origin: IVec3, behaviour: &IdleBehaviour) -> Task {
|
||||
pub fn start(job_id: JobId, origin: IVec3, behaviour: &IdleBehaviour) -> Task {
|
||||
Task::Idle {
|
||||
job_id,
|
||||
origin,
|
||||
sigma_world: behaviour.sigma_world,
|
||||
state: IdleState::Picking {
|
||||
|
||||
@@ -22,6 +22,7 @@ pub fn execute_idle(
|
||||
current_tick: u32,
|
||||
) -> TaskResult {
|
||||
let Task::Idle {
|
||||
job_id,
|
||||
origin,
|
||||
sigma_world,
|
||||
state,
|
||||
|
||||
@@ -311,7 +311,7 @@ pub fn fell_tree(
|
||||
|
||||
let mut dirty_columns: FxHashSet<IVec3> = FxHashSet::default();
|
||||
let z_total = crate::world::chunks::Z_BELOW as i32 + crate::world::chunks::Z_ABOVE as i32 + 1;
|
||||
let mut trunk_positions: SmallVec<[IVec3; 8]> = SmallVec::new();
|
||||
let mut trunk_positions: SmallVec<[IVec3; 12]> = SmallVec::new();
|
||||
|
||||
for (entity, tile_pos, is_trunk) in to_remove.iter() {
|
||||
if *is_trunk {
|
||||
|
||||
@@ -99,8 +99,12 @@ impl ChunkData {
|
||||
// chunkdata.rs
|
||||
|
||||
pub fn is_standable(&self, local_x: i32, local_y: i32, z: i32) -> bool {
|
||||
// 1. Bounds check (z is an index here, e.g., 0 to 64)
|
||||
if z < 0 || z >= (Z_BELOW + Z_ABOVE) as i32 {
|
||||
// 1. Bounds check: z is a signed local Z index (e.g. -Z_BELOW to Z_ABOVE)
|
||||
if z < -(Z_BELOW as i32) || z > (Z_ABOVE as i32) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if local_x < 0 || local_x >= CHUNK_SIZE || local_y < 0 || local_y >= CHUNK_SIZE {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -114,9 +118,10 @@ impl ChunkData {
|
||||
let in_fixture = (self.stand_in_fixture[word] & mask) != 0;
|
||||
|
||||
// 2. Check the tile immediately below (z - 1)
|
||||
if z <= 0 {
|
||||
// Can't stand at the absolute bottom of the generated world
|
||||
if z <= -(Z_BELOW as i32) {
|
||||
return false;
|
||||
} // Bottom of the world
|
||||
}
|
||||
|
||||
let below_idx = Self::pos_to_index(local_x, local_y, z - 1);
|
||||
let below_word = below_idx / 32;
|
||||
|
||||
Reference in New Issue
Block a user