haulage system WIP

This commit is contained in:
2026-03-21 23:39:58 +00:00
parent dcdfb375f0
commit c776b91a44
8 changed files with 277 additions and 1 deletions
+67
View File
@@ -188,6 +188,13 @@ impl FixtureTileData {
}
/// Tile map using FxHashMap for fast lookups. No Arc wrapper - single-threaded access.
/// Error type for cargo placement failures.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CargoPlaceError {
/// A Cargo entity already occupies this tile.
TileOccupied,
}
#[derive(Resource, Default)]
pub struct TileMap {
/// O(1) standability lookups via bitsets (~2KB per chunk).
@@ -198,6 +205,9 @@ pub struct TileMap {
pub fixture_tiles: FxHashMap<IVec3, FixtureTileData>,
/// Entity references per tile position.
pub item_tiles: FxHashMap<IVec3, Vec<u32>>,
/// One Cargo entity per tile position. Enforces single-occupancy.
/// Cargo does not affect standability — purely for lookup and placement validation.
pub cargo_tiles: FxHashMap<IVec3, Entity>,
}
impl TileMap {
@@ -419,9 +429,66 @@ impl TileMap {
self.floor_tiles.remove(&pos);
self.fixture_tiles.remove(&pos);
self.item_tiles.remove(&pos);
self.cargo_tiles.remove(&pos);
}
}
}
self.chunks.remove(&chunk_pos);
}
/// Place a Cargo entity at a tile position.
/// Returns Err if the tile is already occupied by another Cargo.
/// Does NOT check standability — Cargo can sit on any tile including mid-air.
pub fn place_cargo(&mut self, pos: IVec3, entity: Entity) -> Result<(), CargoPlaceError> {
if self.cargo_tiles.contains_key(&pos) {
return Err(CargoPlaceError::TileOccupied);
}
self.cargo_tiles.insert(pos, entity);
Ok(())
}
/// Remove a Cargo entity from a tile. Returns the entity if one existed.
#[inline]
pub fn remove_cargo(&mut self, pos: &IVec3) -> Option<Entity> {
self.cargo_tiles.remove(pos)
}
/// Check if a tile has Cargo on it.
#[inline]
pub fn has_cargo(&self, pos: &IVec3) -> bool {
self.cargo_tiles.contains_key(pos)
}
/// Find the nearest free tile to `origin` that:
/// - Has no Cargo on it
/// - Is standable (entity can reach it)
/// - Is within `max_radius` tiles (Chebyshev distance)
///
/// Uses a spiral outward search — O(radius²) worst case but returns immediately
/// on first free tile found. Searches only at origin.z (same z-level).
/// Returns None if no free tile found within radius.
pub fn find_nearest_free_cargo_tile(&self, origin: IVec3, max_radius: i32) -> Option<IVec3> {
if !self.cargo_tiles.contains_key(&origin) && self.is_standable(origin) {
return Some(origin);
}
for r in 1..=max_radius {
for dx in -r..=r {
for dy in -r..=r {
if dx.abs() != r && dy.abs() != r {
continue;
}
let candidate = IVec3::new(
origin.x + dx * ITILE_SIZE,
origin.y + dy * ITILE_SIZE,
origin.z,
);
if !self.cargo_tiles.contains_key(&candidate) && self.is_standable(candidate) {
return Some(candidate);
}
}
}
}
None
}
}