use std::collections::HashMap; use crate::chunk::{Chunk, TileFlags}; use crate::entity::{Entity, EntityKind}; pub fn tile_to_chunk(tx: i16, ty: i16) -> (i16, i16) { (tx >> 5, ty >> 5) } pub fn tile_to_local(tx: i16, ty: i16) -> (u8, u8) { ((tx & 31) as u8, (ty & 31) as u8) } pub fn local_to_tile(cx: i16, cy: i16, lx: u8, ly: u8) -> (i16, i16) { ((cx << 5) | lx as i16, (cy << 5) | ly as i16) } pub struct World { chunks: HashMap<(i16, i16), Chunk>, pub entities: HashMap, chunk_entities: HashMap<(i16, i16), Vec>, next_entity_id: u32, } impl World { pub fn new() -> Self { Self { chunks: HashMap::new(), entities: HashMap::new(), chunk_entities: HashMap::new(), next_entity_id: 1, } } pub fn chunk(&self, cx: i16, cy: i16) -> Option<&Chunk> { self.chunks.get(&(cx, cy)) } pub fn set_chunk(&mut self, cx: i16, cy: i16, chunk: Chunk) { self.chunks.insert((cx, cy), chunk); } pub fn tile_flags(&self, wx: i16, wy: i16) -> Option { let (cx, cy) = tile_to_chunk(wx, wy); let (lx, ly) = tile_to_local(wx, wy); self.chunks.get(&(cx, cy)).map(|c| c.tile_flags(lx, ly)) } pub fn set_tile(&mut self, wx: i16, wy: i16, id: u16) { let (cx, cy) = tile_to_chunk(wx, wy); let (lx, ly) = tile_to_local(wx, wy); let chunk = self.chunks.entry((cx, cy)).or_insert_with(Chunk::new); chunk.set_tile(lx, ly, id); } pub fn spawn_entity(&mut self, kind: EntityKind, type_id: u16, pos: (i16, i16), hp: u16) -> u32 { let id = self.next_entity_id; self.next_entity_id += 1; let entity = Entity::new(id, kind, type_id, pos, hp); self.add_entity(entity); id } /// True if any entity stands on `(wx, wy)`. Goes through the chunk index, so it is /// cheap enough to call per pathfinder expansion. pub fn occupied(&self, wx: i16, wy: i16) -> bool { let (cx, cy) = tile_to_chunk(wx, wy); self.entities_in_chunk(cx, cy).iter() .any(|id| self.entities[id].pos == (wx, wy)) } /// The movement blocker predicate: a tile blocks if it is solid, outside the loaded /// world, or occupied by an entity. Both the sim and the pathfinder plan with this. pub fn blocked(&self, wx: i32, wy: i32) -> bool { let Ok(wx) = i16::try_from(wx) else { return true }; let Ok(wy) = i16::try_from(wy) else { return true }; self.tile_flags(wx, wy).map_or(true, |f| f.collidable()) || self.occupied(wx, wy) } pub fn add_entity(&mut self, entity: Entity) { let chunk_key = tile_to_chunk(entity.pos.0, entity.pos.1); let id = entity.id; self.entities.insert(id, entity); self.chunk_entities.entry(chunk_key).or_default().push(id); } pub fn remove_entity(&mut self, id: u32) { if let Some(entity) = self.entities.remove(&id) { let chunk_key = tile_to_chunk(entity.pos.0, entity.pos.1); if let Some(list) = self.chunk_entities.get_mut(&chunk_key) { list.retain(|&eid| eid != id); } } } pub fn move_entity(&mut self, id: u32, new_pos: (i16, i16)) { if let Some(entity) = self.entities.get_mut(&id) { let old_chunk = tile_to_chunk(entity.pos.0, entity.pos.1); let new_chunk = tile_to_chunk(new_pos.0, new_pos.1); entity.pos = new_pos; if old_chunk != new_chunk { if let Some(list) = self.chunk_entities.get_mut(&old_chunk) { list.retain(|&eid| eid != id); } self.chunk_entities.entry(new_chunk).or_default().push(id); } } } pub fn entities_in_chunk(&self, cx: i16, cy: i16) -> &[u32] { self.chunk_entities.get(&(cx, cy)).map_or(&[], Vec::as_slice) } /// Returns all entities within a 30×30 tile viewport centered on `center`. pub fn entities_in_viewport(&self, center: (i16, i16)) -> Vec<&Entity> { let half: i16 = 15; let min_x = center.0 - half; let max_x = center.0 + half - 1; let min_y = center.1 - half; let max_y = center.1 + half - 1; let min_cx = tile_to_chunk(min_x, min_y).0; let max_cx = tile_to_chunk(max_x, max_y).0; let min_cy = tile_to_chunk(min_x, min_y).1; let max_cy = tile_to_chunk(max_x, max_y).1; let mut result = Vec::new(); for cy in min_cy..=max_cy { for cx in min_cx..=max_cx { for &eid in self.entities_in_chunk(cx, cy) { if let Some(e) = self.entities.get(&eid) { if e.pos.0 >= min_x && e.pos.0 <= max_x && e.pos.1 >= min_y && e.pos.1 <= max_y { result.push(e); } } } } } result } }