haulage system WIP
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user